commit 3ce9174229de91411a9abf5381a1f335fe0c6a98 Author: tpearson Date: Sat Jan 9 23:52:48 2010 +0000 Added abandoned KDE3 version of Amarok git-svn-id: svn://anonsvn.kde.org/home/kde/branches/trinity/applications/amarok@1072335 283d02a7-25f6-0310-bc7c-ecb5cbfe19da diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 00000000..6a040b39 --- /dev/null +++ b/AUTHORS @@ -0,0 +1,15 @@ +Alexandre Oliveira +Christian Muehlhaeuser +Frederik Holljen +Gábor Lehel +Ian Monroe +Jeff Mitchell +Mark Kretschmann +Martin Aumueller +Max Howell +Mike Diehl +Paul Cifarelli +Pierpaolo Di Panfilo +Roman Becker +Seb Ruiz +Stanislav Karchebny diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000..432227e4 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,51 @@ +project(extragear-multimedia) + +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_SOURCE_DIR}/cmake/modules ) + +# search packages used by KDE +find_package(KDE4 REQUIRED) +include (KDE4Defaults) +include (MacroLibrary) +include(MacroOptionalAddSubdirectory) +find_package(RUBY) +find_package(KdeMultimedia) +find_package(OpenGL) +find_package(Xine) +# are these two really required ? +if (APPLE) + find_package(Carbon REQUIRED) +endif (APPLE) +set(TAGLIB_MIN_VERSION "1.5") +find_package(Taglib) + +#amarok needs to be before add_definitions, since it builds +#some qt-only software +if(TAGLIB_FOUND AND RUBY_EXECUTABLE AND RUBY_INCLUDE_PATH) + macro_optional_add_subdirectory(amarok) +endif(TAGLIB_FOUND AND RUBY_EXECUTABLE AND RUBY_INCLUDE_PATH) + + +add_definitions (${QT_DEFINITIONS} ${KDE4_DEFINITIONS}) + +include_directories (${CMAKE_SOURCE_DIR} ${CMAKE_BINARY_DIR} ${KDE4_INCLUDES}) + +include (ConfigureChecks.cmake) + +if(TAGLIB_FOUND) +include_directories (${TAGLIB_INCLUDES}) +endif(TAGLIB_FOUND) + +if(KDEMULTIMEDIA_FOUND) + macro_optional_add_subdirectory(k3b) + macro_optional_add_subdirectory(kaudiocreator) +endif(KDEMULTIMEDIA_FOUND) +macro_optional_add_subdirectory(kaffeine) +macro_optional_add_subdirectory(kmid) +macro_optional_add_subdirectory(kplayer) +macro_optional_add_subdirectory(kmplayer) +macro_optional_add_subdirectory(doc) + +if(QT_QTOPENGL_FOUND AND OPENGL_FOUND AND XINE_FOUND) + macro_optional_add_subdirectory(kaffeinegl) +endif(QT_QTOPENGL_FOUND AND OPENGL_FOUND AND XINE_FOUND) + diff --git a/COPYING b/COPYING new file mode 100644 index 00000000..6822c1a4 --- /dev/null +++ b/COPYING @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 51 Franklin Steet, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) 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 +this service 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 make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. 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. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute 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 and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +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 +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the 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 a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, 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. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE 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. + + 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 +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Steet, Fifth Floor, Boston, MA 02110-1301 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision 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, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This 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 Library General +Public License instead of this License. diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 00000000..97c97e4e --- /dev/null +++ b/ChangeLog @@ -0,0 +1,2876 @@ +Amarok ChangeLog +================ +(C) 2002-2007 the Amarok authors. + +VERSION 1.4.10 + BUGFIX: + * Fix vulnerability in the Magnatune database parsing code. Secunia + Advisory #SA31418. Thanks to Google Alerts for notifying us about this + vulnerability. + +VERSION 1.4.9 + BUGFIXES: + * The last.fm dialog did not always properly disable options when the + username was not entered. + * Fix Amazon Cover fetching by using their new web service api. + * Don't insert items into Dynamic Mode that don't exist. + * If unavailable tracks are in the Playlist and random mode is on, don't + stop those tracks if selected; continue with available tracks. + + +VERSION 1.4.8 + CHANGES: + * Optimise some database queries with the mysql backend. Patch by Alf + Eaton (BR 152749). + * Don't show the device plugin dialog when a new device is plugged in. + Apparently it's not obvious that you have to hit OK after selecting "Do + not handle" if you don't want it handled, so disabling it prevents it + from being shown repeatedly. + * Better support for iPhone/iPod Touch mounted via fuse/sshfs (libgpod 0.6.0 + or newer required). + * Only re-render the context view when visible if changing ratings, scores + or labels for songs. + + BUGFIXES: + * Last.fm metadata would not update with xine 1.1.8. (BR 150429) + * Amarok would forget podcast channel and episode settings when using the + postgresql backend. + * When adding file types with the Generic Media Device sometimes the + extensions would be prepended with & and would not save. (BR 151806) + * For improved compatibility with newer iPods, convert file extensions to + lower case during transfer. + * Replace slashes in artist name with spaces when querying Wikipedia + e.g. AC/DC, To/Die/For. (BR 150001) + * Always rebuild the dynamic mode cache when in Suggested songs mode, + so that we don't land up with stale suggestions. Patch by Jer Johnson + + * Sort albums made in the same year alphabetically in 'ascending + order'. (BR 149408) + * Statistics tool shouldn't show samplers in 'favorite albums'. + * Duplicate songs were not allowed in playlist when adding from the + collection browser. (BR 149643) + * Make sure the localUrl of a PodcastEpisode is valid after a failed + download. (BR 147351) + * Fix off-by-one error causing Smart Playlists to not load tracks with a + rating >= 4.5. (BR 148916) + * Don't enable "Configure Podcasts" at the top-level Podcasts folder if + there is nothing beneath it. (BR 146504) + * Generic Media Device could copy some non-ASCII filenames to turn to + gibberish. Thanks to David Smith for the fix. + * Fixed possible GUI freeze when Amarok was showing the dialog for + installing mp3 support. Patch by Sascha Sommer . + (BR 147126) + * Amarok could needlessely reinitialize connections to MySQL databases + after a configuration change. Combined with a bug in MySQL libraries, + this could lead to a crash. + * Pressing Preveious Track in a Dynamic Playlist could cause undefined + behavior in certain edge conditions. Now it always plays the current + track. (BR 148317) + * Immediately after loading a dynamic playlist, you couldn't drag a + track to the top of the playlist. (BR 149263) + * Fix transferring files with UTF8 names to MTP devices. Thanks to Kevin + Becker for the fix. (BR 139722) + * Display warning that iPod sysinfo could not be written in the case of + incorrect file permissions. Patch by Christian Ober-Blöbaum + . (BR 148607) + * Fix Czech character conversion to ASCII for Generic Media Device. Patch + by Matěj Laitl . (BR 149125) + + +VERSION 1.4.7 + CHANGES: + * Updated the Cool Streams. + * Improved application icon. Thanks go to Pasi Lallinaho. + * Upgraded SQLite to 3.4.1 + * SQL improvements providing optimisations on intensive queries. Patch by + Gosta . (BR 142999) + + BUGFIXES: + * Wikipedia artist lookup would freeze Amarok if the artist was not found + and the locale was not English. (BR 142764) + * Cannot limit smart playlists to more than 1000 tracks. (BR 148084) + * Fixed the formatting in the "Extended Info" pane for podcasts. + * Don't show "Not Rated" for items rated with half a star. Patch by Tuomas + Nurmi . (BR 144675) + * Copy, don't move items from Cool Streams to folders. (BR 147404) + * Sometimes folders in the playlistbrowser could be lost. (BR 147404) + * NJB devices could have tags corrupted that contained Unicode characters. + Patch by Kun Xi . (BR 147223) + * Show OSD when changing song rating via shortcut. Patch by Tuomas Nurmi + . (BR 146918) + * Show the stars indicating rating with the correct size in the OSD. Patch + Patch by Tuomas Nurmi . (BR 147059) + + +VERSION 1.4.6 + CHANGES: + * Improved icon theme, kindly provided by Landy DeField + . Big thanks! + * Playlist now sends notifications to scripts if items are added, removed, + reordered, or if the playlist is cleared. Useful for script authors. + Thanks to Miguel Angel Alvarez for the patch. + * iPod device plugin now handles RockBox devices. Thanks to Michael + Buesch for the patch. + * Organising files will only delete empty parent folders if the folder + is within the collection hierarchy. (BR 136757) + * The default cover image preview size has been increased to 130px. + * The "hide menubar" option has been removed. It's too dangerous and led + to countless support requests. + * Generic media device can now handle any KIO-compatible URL, including + obex and smb. Manage your bluetooth phone's music collection through + Amarok! + * Upgraded SQLite to 3.3.17. + * Append an album to the playlist by right-clicking on it from within + the Cover Manager. Patch by Doug Reich . + * Faster playlist handling. Patch by Ovy . (BR 142255) + * The moodbar process has been given a higher priority. (BR 136867) + * Allow for lyrics scripts to specifiy site, site_url, and add_url from + within the script. This will allow for "meta lyrics" scripts. Patch by + Sergio Pistone . (BR 141885) + * First rating star now lets you toggle between no rating, half a star, + and one full star. + + BUGFIXES: + * Uninstalling scripts would in some cases leave files behind. Patch by + Sergio Pistone . (BR 143716) + * Last.fm "Custom Station" stream works again. (BR 146020) + * Fix regression where the "Show Script Manager" button displayed on the + Lyrics tab of the Context Browser wouldn't actually show the Script + Manager. + * Don't show ratings from the previous track's rating change in the OSD on + playing the next track. + * The config dialog is now less tall and fits on widescreen displays. + * Making a dynamic playlist with the number of previously played tracks to + show set to zero and attempting to play the first track would cause a + crash. (BR 145157) + * If "Stop after current track" was used, the last track would not be + counted or rated in the user's statistics. (BR 140980) + * Generic media device wouldn't allow you to drop a folder on the + viewport, meaning you couldn't move subfolders to the top level of the + mount point. + * Made the settings dialog less tall. (BR 141250) + * Star ratings now update instantly in the Context Browser, OSD, and + Collection Browser. + * lyrc script did not work behind proxy due to a stray quote mark. Gentoo + Bug 166050. + * Fix compilation on kde-3.3 systems. + * amarok_live.py now uses popen correctly. Patch by Luke Macken + . (BR 127804) + * Make amarok_proxy.rb use HTTP/1.0 as we don't support chunked responses. + Patch by solsTiCE . (BR 141819) + * Fix Quadratic loading in Playlists. Patch by Ovy + . (BR 142255) + * Correctly set iPod model. Patch by İsmail Dönmez . + * Fix detection of vfat devices on FreeBSD. (BR 141614) + * Right-click on volume slider would change the volume. (BR 141672) + + +VERSION 1.4.5 + FEATURES: + * Added support for custom song labels. Labels can be managed + through the GUI or using new DCOP functions. (BR 89314) + * New DCOP functions to make it easier for scripts to use Amarok's + Dynamic Collection feature. + * Download songs from Shared Music (DAAP) directly into the collection. + * Fadeout for Helix engine when pressing Stop. + * Guided editing of the collection/playlist/devices filters. Patch by + Giovanni Venturi . (BR 139292) + * Added GUI options for fadeout and fadeout on exit. Both are now enabled + by default. + * Support for Speex (.spx), WavPack (.wv) and TrueAudio (.tta) files in + the collection thanks to taglib plugins by Lukáš Lalinský + . + * Search inside of lyrics, by using "/" on Context Browser. Patch by + Carles Pina i Estany . (BR 139210) + * "Automatically show context browser" feature makes a return, as per + popular request. It is however disabled by default. + * Improved keyboard navigation: Space key is now a shortcut for Play/Pause, + and cursor left/right seeks forward/backward. + * Cover images are shown in collection browser. Patch by Trever Fischer + . (BR 91044) + * Send cover art to MTP media devices if they support it. + * Elapsed time can be shown in OSD. Patch by Christian Engels + . (BR 120051) + * New redownload manager for the Magnatune.com store. Allows re-download + of any previous purchase free of charge (in any format). + * New items in the playlist are colorized, as a visual cue. + * Show rating as stars in flat collection view. Patch by Daniel Faust + . (BR 133797) + * Synchronize play count, last played time and date of modification to + iPods. Patch by Michael . (BR 136759) + * Propose list of composers in collection when editing the composer tag + from the playlist. (BR 137775) + * Greatly improved sound quality for the xine equalizer. Patch by Tobias + Knieper . (BR 127307) + * Fancy graphical volume slider for the OSD. Patch by Alexander Bechikov + . + * Shoutcast stream directory. Contributed by Adam Pigg . + * Support for %composer and %genre when guessing tags from filenames. + * Cached lyrics are now AFT-enabled, and will follow your files around as + you move and rename them. + + CHANGES: + * Added configure option to build without DAAP support. + * Album covers are now downloaded and added to album directory when + purchasing from the Magnatune.com store. (BR 136680) + * Update context browser when a change in the collection has been detected. + (BR 140588) + * Ignore leading 'The ' when sorting playlist by artist. (BR 139829) + * Smart Playlists now have 'does not start with' and 'does not end with' + options, as well as a dropdown for mount points. (BR 139552) + * Support for cue files not matching audio files' name. Patch by Dawid + Wróbel . (BR 128046) + * Script Manager now remembers if categories were open or closed. + * Restart collection scanner as long as not more than 5 % of the files + make it crash. (BR 106474) + * Ensure the first selected item in the Collection Browser stays visible + when the search field is cleared using the clear button. + * Duplicate filenames are now allowed on MTP media devices if the files are + in different folders. + * Save media device transfer queue when adding items or after transfers. + (BR 138885) + * Upgraded internal SQLite to 3.3.12. + * MTP media devices are not automatically connected on start-up. This + should solve slow loading times for those with large collections on an + MTP media device. Contributed by Mikko Seppälä. (BR 138409) + * Internationalize unknown artist/album/genre strings. Contributed by Mikko + Seppälä. (BR 138409) + * Don't assume that a device returning 0 tracks is invalid. It could just + have no tracks on. Contributed by Mikko Seppälä. (BR 138409) + * Magnatune store look now matches rest of Amarok much better. + * Album art is displayed on the Magnatune purchase dialog. + * Generic media device now has an option to force VFAT-safe filenames even + on non-VFAT filesystems. + * Double-clicked items in sidebar and urls passed on the command line + are treated equally: append them to playlist if not yet there and start + playing the first if nothing is playing. + * "Scan Changes" button was replaced with "Update Collection" menu entry. + * Consistent double-click behavior in sidebar. (BR 138125) + * Propose name of currently loaded playlist when saving current one. + * Remove support for older libmtp versions. We now require 0.0.15 or + newer. + * Deleting a playlist item on an MTP media device now results in it being + removed from the playlist. + * Magnatune store is lazy loaded to improve startup times. + * Dynamic mode logic has been rethought to provide a faster and better + user experience. + * When checking for duplicate files on a Rio Karma media device, use + track number in addition to artist, album & title. (BR 137152) + * The XMMS visualization interface has been removed. LibVisual supersedes + this feature. + * It is now possible to select the time unit for length-based smart + playlists. (BR 136841) + * Show shadowed cover images in the system tray tooltip. (BR 136589) + * Amarok won't crossfade if it was paused, and user started another + track. Patch by Tuomas Nurmi . + (BR 136428) + * Amarok now saves playlists with relative paths by default. + + BUGFIXES: + * Disable seeking in streams. (BR 140364) + * With the default theme, the playlist browser info pane would not show + the horizontal scrollbar if necessary. (BR 134221) + * Some .rm files would make Amarok crash. (BR 137695) + * Remember 'User Cover Art for Folder Icons' when organizing files. + (BR 138562) + * "Listening since..." has been changed to the more clear "First + Played..." Patch by Andrew Ash . (BR 131727) + * Fixed regression: the DEL key no longer worked in the + playlist after opening the File Browser context menu. (BR 140197) + * Smart playlists now work correctly with "is not" filters containing + numbers. Patch by Felix Rotthowe . + * Context browser would not display updated covers correctly. (BR 130518) + * The select custom cover dialog no longer starts in the wrong directory + for compilations. (BR 131776) + * Amarok's xine engine would cut off approximately the last second of an + audio file. (BR 135190) + * Cue sheet would remain enabled when switching to a stream. Patch + by Ted Percival . (BR 127683) + * Length of tracks wouldn't be shown correctly for some cue files. + Patch by Dawid Wróbel (BR 139707) + * Assume that all dots but the last in script executable files belong to + the script name. (BR 139460) + * Don't crash when quitting while initially loading the playlist. + (BR 136353) + * The same track could be queued multiple times for transferring to a + media device. (BR 129136) + * Migrate statistics for files moved from outside to the collection. + (BR 127776) + * Select All/Copy action would not copy from context browser. (BR 138635) + * Xine-engine: When a track was fading out (after pressing Stop), and you + started another track, Amarok could become unresponsive. + * Improved seeking with xine-engine. No longer jumps to 0 when you seek + too quickly. Patch by Alexander Bechikov . (BR 99808) + * Improved cover images handling for Various Artists. Patch by Tobias + Knieper . (BR 136833) + * Don't enable a mount point for devices that can't support them (mtp, + njb). + * With SQLite, the search in the collection browser was case-sensitive + with UTF-8. Patch by Stanislav Nikolov . (BR 138482) + * (Don't) Show Under Various Artists would not work when multiple albums + are selected. Patch by Tobias Knieper . + (BR 112422) + * Changed temp download location for Magnatune purchases. (BR 137912) + * Fixed potential double payment issues in the Magnatune store. + * Only synchronize already set values to media devices. (BR 138150) + * Correctly update total playlist play time when removing last.fm + streams. Patch by Modestas Vainius . (BR 134333) + * File organization jobs could not be canceled. Patch by Wenli Liu + . (BR 136527) + * Sending filenames to MTP media devices as UTF-8 caused problems, use + Latin-1 instead. + * It's now possible to delete a file from an MTP media device and + re-upload it without having to reconnect the device. + * Wikipedia links to edit sections are no longer shown. + * Metadata is read from Rio Karma media devices as UTF-8. + * Last.fm streams could be paused with DCOP or global shortcuts. + (BR 133013) + * Dynamic mode can still be used after a collection rescan. (BR 133269) + * Dynamic mode will repopulate from all available sources. (BR 137212) + * Dynamic mode no longer repeats songs often. (BR 107693) + * When transferring files to a Generic media device, having certain + characters (such as '#') in a tag field could cause a directory based on + that field to not be created. + * Editing lyrics from within the context browser no longer removes all + linebreaks. + * Read metadata from MTP media devices as UTF-8. + * Some shoutcast streams would show an empty title. (BR 127741) + * Pause would act as Play/Pause. (BR 116101) + * The same track would sometimes be shown twice in suggested songs. + (BR 129395) + * Detect VFAT partitioned devices on FreeBSD. Patch by Daniel + O'Connor . + * Favorite Tracks wouldn't be shown on Context Browser, and + Statistics Panel would be empty for SQLite users. (BR 136791) + * Volume slider in the player window would not react correctly to + the mouse wheel. (BR 136714) + * When using a proxy set by script, context browser wouldn't work + properly, and the application would crash when closing. (BR 112437) + * Proxy settings wouldn't be respected when downloading podcast + episodes. (BR 134028) + * Xine engine could hang when skipping through tracks quickly with + crossfade on. + * Fix crash when an MTP media device returned a playlist with an + invalid track ID. (BR 136552) + * The Install MP3 support script would be run regardless of what the + user answered to the shown dialog. (BR 136294) + * OSD wouldn't always show up-to-date ratings. Patch by Tuomas Nurmi + . (BR 125612) + + +VERSION 1.4.4 + FEATURES: + * Transfer .wav-files to iPods. (BR 131130) + * Xine and Helix engines now support three different crossfading modes: + always, on manual track changes only, or on automatic track changes + only. + * Manually specify local file for podcast episodes via right-click menu. + * Action menu entry for adding podcasts to Amarok. Based on .desktop files + by Harald Sitter and Fabio Bacigalupo . + * Open podcast items with external application from right-click menu. + * Synchronize listened flag for podcast between Amarok and iPods. + * Added integrated Magnatune.com music store. Includes artist and album + info and full previews of all tracks. + * Fade-out for xine-engine when pressing Stop. Patch by Tuomas Nurmi + . (BR 127316) + * Support downloading of files from an MTP device. + * Purged podcast episodes can be readded by increasing the purge number. + * Added rudimentary support for the Rio Karma. (BR 132713) + * Support creation and editing of playlists on MTP media devices. + * Undo/Redo functionality is now available over sessions. (BR 131072) + * Allow the creation of empty playlists in the playlist browser. Available + either from the Add button in the toolbar or the context menu of a + playlist folder. (BR 133543) + + CHANGES: + * Ignore leading "The " when sorting artists on media devices. (BR 136233) + * Improved handling of VFAT/ASCII files and paths when organizing the + collection and using the Generic media device. + * Enable playing audio CDs on CD insert. Patch by Will Stephenson + . (BR 136106) + * Bring Amarok main window to front when starting amarok again without + arguments. Patch by Lubos Lunak . (BR 135396) + * Don't switch to playlist browser after saving a playlist from files tab. + (BR 130189) + * Add .ape and .mpc to possible file types supported by a generic media + device. (BR 133491) + * Move button for saving current playlist from playlist browser toolbar to + playlist toolbar. (BR 129300) + * Run 'kdeeject -q devicenode' when no post-disconnect command has been + configured for media devices. + * Reduced memory usage for MTP media devices. (BR 134663) + * Faster searching on playlist and startup, due to some optimizing in + string usage. Patch by Ovidiu Gheorghioiu . + * Correctly translate media:, home:, ... style urls on KDE 3.5 and newer. + * When tracks are added to the collection and Playlist entries already + exist (as determined by the file tracking code), the corresponding + Playlist entries are updated to the new location and enabled if they + were previously disabled. + * When file tracking is updating Playlist entries, multiple entries of the + same song will now all be updated, instead of just one. + * When tracks are removed from the collection (deleted on disk or moved + outside of a collection folder) any corresponding entry in the Playlist + will be disabled. + * Dragging podcasts to to playlist will insert them in a chronological + order, so you can listen to the oldest first automatically! + * Improve application startup times dramaticaly by lazy loading podcast + episodes. + * Transferring tracks to an MTP device now shows a progress bar and + doesn't hang the rest of the UI. (only available for libmtp >= 0.0.15) + * Show a proper tag dialog when viewing information for DAAP music shares. + + BUGFIXES: + * Ipod Mode on Collection Browser would have duplicated headers. + * Multiple problems related to Amarok using wrong playlists on Dynamic Mode + fixed. + * Deleting files from generic media devices would not update the progress + bar, resulting in the progress staying at 0%. (BR 130009) + * If nothing at all existed on a generic device, the first item + transferred would incorrectly show that an error had occured during + transfer. (BR 133528) + * Synchronising a smart playlist to a device when it didn't exist before + would crash Amarok. (BR 135956) + * Proxies would not take into account certain settings in KDE's Proxy + control center modules for PAC files and more. (BR 123021) + * Generic media devices would not accept files with an extension that only + differs in case from a supported extension. (BR 135261) + * Xine-engine: Pausing during crossfade would not work properly. Patches by + Markus Kaufhold . (BR 122514 & 135285) + * Stop a running cross-fading operation before starting another one. Patch + by Markus Kaufhold . (BR 128629) + * Queuing again would dequeue. (BR 121206) + * In some cases, the Removal and Enqueue buttons in the queue manager would + have no icons. (BR 115895) + * Don't change length of position slider when navigating within a track. + (BR 122569) + * Direct copying of non-local items would result in wrong properties on + iPods. (BR 135681) + * Honor setting to show Amarok's menu in main toolbar. + * "Burn this album" would burn all albums of the same name. (BR 121963) + * Ignore double-clicks on tree item openers. (BR 125121) + * Visibility of sidebar tabs would depend on the current locale. (BR 135316) + * Ctrl-C for copying urls from the tag editor would not work when selected + with the mouse. (BR 123327) + * Check for some integral data types for improved DAAP portability. + (BR 132939) + * Take disc number into account when checking if a song is already on an + iPod. (BR 135643) + * Editing metadata in the playlist itself now matches possible alternatives + case-insensitively. (BR 135683) + * Fix loading directory in external browser in the tag editor when the path + contains parentheses. (BR 132961) + * Stop scripts using a proxy when it's disabled in KDE. Patch by Felix Geyer + . + * While playing Last.fm Streams, sometimes metadata wouldn't be updated + on track changes. Patch by Tom Kaitchuck . + * Speed patch to load playlist columns from statistic tables on population + of the playlist, makes adding to the playlist and starting up faster. + Thanks Ovy ! (BR 135324) + * Save MTP playlists when they are renamed so we don't lose changes. + * Prevent new podcastepisodes from showing up in the playlistbrowser twice + by opening it's parent before adding. (BR 134108) + * New iPods would not get initialized. + * Files that were detected as being added back to the collection would not + always be re-enabled in the Playlist. (BR 130359) + * Fix some spelling and layout issues. Part of a patch by Malcolm Parsons + . + * Correctly handle horizontal wheel events in position slider. (BR 119254) + * Don't rescan collection while transcoding. (BR 133423) + * Don't try to copy to collection from urls without kio slaves. + * Don't quit immediately if amarokrc was removed. (BR 134439) + * The DAAP client would crash Amarok under certain conditions when + kdelibs was compiled with asserts on. (BR 132851) + * Configuring the toolbar would disable the stop button. Patch by + Markus Kaufhold . (BR 132477) + * Changed tags of songs on iPods would not propagate to its database. + (BR 133842) + * Fixed playlist encoding problems. (BR 133613) + * Cover images for compilation albums can now be displayed full size in + the context browser. + * Dragging compilation albums from the collection browser or the playlist + would show multiple cover images in the tooltip. (BR 133916) + * Don't crash when calling repopulate dynamic mode from dcop. (BR 133716) + * Last.fm streams work with proxies. (BR 131137) + * Don't try to read m4a tags from apparently invalid files. (BR 133288) + * Some podcasts would insert line breaks in author/title information and + cause graphical errors. (BR 133591) + * File tracking could fail on files that were copies of each other but + with different ID3v1 or APE tags. + + +VERSION 1.4.3: + FEATURES: + * New DCOP: player trackCurrentTimeMs, returns the current track position + in milliseconds. + * Amarok File Tracking (formerly ATF) goes public! See + http://amarok.kde.org/wiki/Amarok_File_Tracking for more information. + * DAAP client now supports Zeroconf. With mDNSResponder properly setup + Amarok automatically shows local DAAP servers. + * DAAP client saves manually added computers between sessions. + + CHANGES: + * Performance with big playlists has been improved by a magnitude. This + also makes application shutdown faster. + * Remove the option to enable/disable history in dynamic mode. (BR 133076) + * Reduce the minimum available tracks to show to 0. (BR 131223) + * Change in file tracking behavior: IDs are no longer embedded into tags + but are calculated from a portion of the file data instead, letting + users with read-only music stores take advantage of it. + * Don't report "/dev/hd" style devices as new media devices. (BR 127831) + * Smart Playlists only load media from currently mounted devices. + + BUGFIXES: + * Dequeuing tracks whilst in dynamic mode might not work. (BR 133449) + * When marking podcast episodes as listened, update the channel icon if + necessary. (BR 133497) + * Don't always mark podcast channel icon as "listened" on rescan if. + (BR 133495) + * User added streams were not editable once saved. (BR 133483) + * Cover images were not displayed in some cases. (BR 133174) + * Fixed bug which prevented Amarok from creating the collection database + in rare circumstances using SQLite. (BR 133072) + * Collection scanner would only restart a maximum of 2 times instead of + 20. (fixed in SVN revision 578922) + * MTP media device support would not compile against libmtp versions >= + 0.0.12. (fixed in SVN revision 576121) + * AudioCD playback would stutter and sometimes freeze Amarok. (BR 133015) + * Dynamic Collection broke flat collection view when the Filename column + was added (BR 132874) + * DAAP client shows connection errors to the user and no longer says + "Loading" perpetually. After a failed connection, the user can now + try again. + * Don't empty media device transfer queue when canceling a transfer. + * Ctrl-C for copying urls from the tag editor would not work. (BR 123327) + * Delete covers from the filesystem when requested. + * Show context menu on right-click in empty area of media device + browser. (BR 127154) + * Sort numeric columns in flat collection view numerically. (BR 130667) + + +VERSION 1.4.2: + FEATURES: + * Handle itpc:// and pcast:// url protocols for adding podcast feeds. + (BR 128918) + * New DCOP call "collection: totalComposers" returns the number of + different composers in your collection. + * Synchronize playlists to media devices. + * Support for MTP/PlaysForSure media devices. (BR 128532) + * iPod plugin usable with iTunes phones. (BR 131487) + * Browse collection by composer. (BR 122452) + * New DCOP call "playlist: filenames" returns the filenames of the songs + currently in the playlist. Patch by Arash Abedinzadeh + + * Lyrics can be edited directly on Context Browser's Lyrics tab. + * Collection browse mode similar to that used by some portable players. + Patch by Joe Rabinoff . (BR 130586) + * BPM field. Patch by Alf B Lervåg and Aaron + VonderHaar . (BR 123142) + * Improved crossfading for xine-engine: Honours the 'Crossfade Length' + setting precisely, and uses a better mixing style profile. Patch by + Enrico Ros . + * Media and collection browser tabs now support dropping. + * Allow for deleting all the tracks on a playlist from iPods. (BR 127855) + * Ability to create custom last.fm station from the GUI. + * Ability to mark podcasts as listened. + * Show error messages when connecting to last.fm streams fails. + * A new media device implements a DAAP client. So Amarok can connect + to iTunes, Firefly Media Server etc. (BR 100513) + * Dynamic Collection: improved support for songs on removable external + harddisks, SMB and NFS shares + + CHANGES: + * Skip tracks that failed to transfer to media devices instead of stopping + transfer process. (BR 130008) + * libtunepimp 0.5.0 actually compiles successfully now. + * Lift size limit on pathnames and comments in collection databases not + managed by MySQL. (BR 130585) + * Generic media device plugin is improved. Users can configure supported + filetypes and get more control over the location of songs and podcasts + on disk (Patch by eute). + * Move composer tag to its own database table. + * Re-enable adding videos to iPods with recent libgpod-cvs. (BR 130117) + * Include Skip, Love and Ban in playlist right-click menu for last.fm + streams. + * Advanced Tag Features (ATF) deferred to 1.4.3: Public release delayed + pending some bug fixes in both Amarok and a dependency. It will be + automatically disabled the first time you run 1.4.2 if you had it enabled + from 1.4.2-beta1. (It will still be available in subversion snapshots.) + * Optionally finish transferring all queued tracks to media device after + pressing disconnect button. (BR 129716) + * It's now possible to edit scores and ratings for multiple tracks in + TagDialog. + * TagDialog won't make Amarok unresponsive while committing tags changes + to files anymore. + * Exact playtime as tooltip in statusbar. Patch by Markus Kaufhold + . (BR 130463) + * Suspend collection rescanning while organizing files. (BR 129885) + * Always use metadata from original file for transcoded files transfered + to media devices. (BR 131171) + * Enhancements to ATF/statistics to allow for better tracking of stats as + files are moved. + * Tag Editing Dialog is now ATF-enabled. + * In-line tag editing is now ATF-enabled. + * Previously, using ATF with MP3 files would wipe out existing UFID frames + from other applications. Now Amarok plays nicely and only touches its + own UFID frame. + * ATF no longer requires a restart to enable or disable it. + * ATF read-only functions are always enabled if a UID is found in the + file. Option in the configuration dialog now only controls whether new + UIDs are written to new files. + * ATF will now automatically run the rescan and clear the Playlist only on + the first time it is enabled. After that it will simply display an info + reminding users that they may need a rescan if their library has changed + since the last time it was enabled. + + BUGFIXES: + * DCOP calls to add and remove ATF tags are no longer allowed to run while + the collection is being scanned. + * Last.fm streams no longer freeze Amarok's GUI with xine-engine. + * Sometimes metadata wasn't updated with Last.fm streams. + * Update context browser on score and rating changes. (BR 132496) + * Double colons in the collection filter would lead to invalid queries. + (BR 132551) + * Handle changed semantics of MySQL 5.0.23+ (BR 132114) + * Do not try to detach() KURLs, as this would not work for non-ascii urls. + (BR 132355) + * Adding songs while at end of playlist could crash in dynamic mode. + Patch by Joe Rabinoff . (BR 128340) + * Don't update accessdate when setting songs rating or score. (BR 132274) + * Increasing or decreasing volume while muted would not correctly unmute. + (BR 132228) + * Better resize behavior in iPod collection view mode. Patch by Joe Rabinoff + (BR 132016) + * Make sure a track's compilation status is returned properly when running + with Postgresql. + * Check directory structure on iPods of unknown type in order to detect + iTunes phones. (BR 131910) + * Make 'Clear' individually translatable for playlists. (BR 131521) + * Retain column visibility for flat collection view. (BR 126685) + * Honour proxy exceptions for MusicBrainz lookups. Patch by N. Cat + . (BR 131377) + * Correctly pass links containing parentheses to external browsers. Patch + by Thomas Lindroth . (BR 131307) + * iPods would not show podcast descriptions. (BR 129824) + * Carry over rounding increments to next larger unit for fuzzy time + display. (BR 131383) + * If disabled, don't show splash screen - even on Kubuntu. (BR 125210) + * Correctly request last.fm similar artist information for artists + containing non-ASCII characters. Patch by Thomas Lindroth + . (BR 131254) + * Support non-chronologically ordered podcast feeds. (BR 119911) + * Support for libvisual 0.4.0 was fixed. Patch by Dennis Smit. + * Adding songs already on a media device to playlists would not work. + * Fix adding smart playlists to media devices. (BR 130540) + * Reverse check for mount point and device node when connecting to iPods + for better handling of device nodes pointed to by symlinks. (BR 129965) + * Make handling of filenames on iPods case-insensitive and thus fix + fix problems with too many orphaned and stale items. (BR 126431) + * Correct action of queueing current item in dynamic mode. (BR 130313) + * Double clicking in the filebrowser will append to playlist. (BR 117465) + * Fixed problems with last.fm streams containing spaces, e.g. "Hip Hop". + * When generic media devices were specified manually, transferred files + would not always get converted to VFAT-friendly names if they were on a + VFAT filesystem. + * When using ATF, tags in MP3 files would be written as ID3v2 only and + existing ID3v1 tags would be stripped, which could lead to media devices + and tagging libraries that were not ID3v2.4-aware to report that no tag + existed. Now both tags are written with identical data. + * Correct handling of filenames with special characters. (BR 132243) + + +VERSION 1.4.1: + FEATURES: + * Support for last.fm streams. (BR 111983) + * New playlist toolbar menu entry for adding streams to the playlist. + (BR 129349) + + CHANGES: + * Upgraded internal SQLite to 3.3.6. + * Inotify support disabled for now, due to stability issues. + * Tag editor is no longer modal. + * Provide warning dialog when deleting items from the playlistbrowser. + (BR 129313) + * GUI layout reverted to the classic Amarok layout. + * The Extended Info panel in the playlistbrowser is now resizeable. + + BUGFIXES: + * Pressing return in the search bar of the Collection Browser immediately + after typing a query no longer appends the wrong items to the playlist. + * Fix crash when pressing Back or Forward buttons multiple times quickly + in Artist tab. Patch by Thomas Lindroth . + * Fix problems where blanks would be added to data if SQLite was busy. + Patch by Thomas Lindroth . (BR 127608) + * Automatically refresh stream lyrics on new metadata. + * Set half star ratings on multiple selected tracks when clicking on an + item. (BR 129449) + * Only enable Show Extended Info in the Playlist Browser when information + is available. (BR 126590) + * Disable global shortcut for ratings when ratings are disabled. + (BR 129414) + * Autodetect button in Media Devices configuration dialog would not + properly signal changes, so that new devices were not always saved. + + +VERSION 1.4.1-beta1: + FEATURES: + * Much improved and completed custom icon theme by Vadim Petrunin + . + * LibVisual 0.4 supported and required. + * Support for custom scoring algorithms, via scripts. + * Creative Nomad Jukebox support (untested!). Submitted by Andres Oton + . (BR 103185) + * Inotify support. On kernels 2.6.13 and above with Inotify support + compiled in, the collection will automatically be rescanned and + updated as soon as a watched folder has changed. + + CHANGES: + * First-run wizard can no longer be restarted from the application menu. + However, it can still be invoked with "amarok --wizard". + * Astraweb lyrics script was removed for being crappy and unmaintained. If + you want to maintain it, grab it from SVN and release on kde-apps.org. + * "Append Count" option of dynamic playlists has been removed. It is + now always one. (BR 120044) + * Context browser can now play/queue specific discs of an album or + compilation. + * Automatically imported playlists go into a separate category. + * Block quitting amaroK until all on-going media device operations have + finished with a consistent state. + * Interface choice in wizard removed. + * MoodBar has been removed. The maintainer has not been updating it, and + it was causing crashes for many people. + * Usability improvements for the Script Manager, including a tree view. + * Use KMimeType for resolving file type for metadata acquisition before + falling back to extension based guessing. + * Removed the "detailed mode" in the playlist-browser. + * Also copy non-local URLs to collection when dropped onto collection + browser. + * Speed up connecting media devices with a lot of tracks to be submitted + to last.fm. + * For media without metadata, try to read metadata after transfer to + the iPod (e.g. when copying an audio CD via KIOslaves). + * Hint at starting a transcode script for transcoding while transferring + to media devices. (BR 127155) + * If a disc number is present, append it to the album's name when + organizing files. (BR 126867) + * Configure, which of fresh podcasts, newest & favorite albums are shown + in context browser home view. Patch by Patrick Muench . + (BR 127043) + * Dynamic mode no longer skips to the next song if you press play (via + dcop, for instance) while already playing a track. Instead it restarts + the current one. + * The Actions menu has been renamed the Engage menu. It's way cooler, + right? I mean, Star Trek is really cool, right? + * Multiple podcasts can be configured at once by selecting multiple channels + or by configuring the children of a folder. + + BUGFIXES: + * Allow dropping of tracks after non-existant items in the playlist. + * Make changes to the default dynamic playlists persistent. + * Send UTF-8 encoded requests to Wikipedia. Thanks to Thomas Lindroth + for the patch. (BR 127654) + * Correctly restore podcast channel title when fetching fails. + * Show error message when xine mp3 decoder isn't installed, don't just + play next track. + * Properly render and optimise playlist loading icons. + * Properly import and export XSPF playlist formats. + * Optimise addition of playlists to the playlistbrowser. + * In context browser, show localized date for podcasts. (BR 127853) + * Regression in dynamic mode caused it to skip the first track in the + playlist whenever it was started. (BR 127451) + * Stop Playing after Track: remember current track (BR 127312) + * Radio streams were broken for protocols other than HTTP. (BR 127848) + * Collection Browser would not set/unset/burn albums with ', The' in + their name. + * Prevent breakage when xine couldn't initialize the audio device. Patch + from Ilya Konstantinov . (BR 115960) + * Allow for recognition of the webdav protocol. Patch by Ilya + Konstantinov . (BR 126847) + * Setting a rating on an unplayed track would affect score generated. + Patch by Patrick Muench . (BR 127475) + * Stop tags with different capitalisation being treated as the same + when building the collection. + * Make database connections actually get closed when no longer used. + (BR 123113) + * xine engine would truncate the last seconds of a track, if no other + track followed in the playlist. + * Fixed AudioCD playback with xine-engine. Patch by Markus Kaufhold + . (BR 127388) + * If dynamic mode was turned on and then off, the previous random and + repeat modes would be forgotten. (BR 123743) + * Removing the current track through DCOP while editing a field of the + track in the playlist would cause a crash. (BR 119152) + * Make characters encoded with % (such as a forward slash, %2f) display + correctly. (BR 105266) + + +VERSION 1.4.0: + FEATURES: + * New DCOP call "player: version()". Returns the amaroK version. + * iFP has persistent settings when transferring tracks to the device. + * GStreamer-0.10 engine now supports Audio CDs. + * Context menus for entries in the statistics tool. (BR 124945) + + CHANGES: + * Composer, Disc Number and File Size columns in flat collection view. + * 'k' or 'm' suffixes for matching filesize in kibi or respectively mebi + bytes. + * Groupings when transferring files to media devices are now persistent. + (BR 127158) + * Transfer contents of smart playlists to media device without adding + them to a playlist. (BR 126997) + * Set %albumartist to Various Artists, but keep %artist as the track's + artist when organizing compilations. (BR 126936) + * Discard empty tokens surrounded by {} in custom organize file format. + (BR 124337) + * GStreamer-0.10 engine was disabled for this release (not yet stable). + * Only pick genres for Smart playlists that exist in your collection. + * VFAT plugin completely rewritten since 1.4beta3. Name is now changed to + "Generic Audio Player" to make it less needlessly technical. + * Don't limit the number of episodes shown with a new podcast, since the + user can limit the number shown afterwards by configuring the channel. + * Automatically populate the playlist with items if it is empty when a + dynamic playlist is loaded. (BR 126594) + * Unplayed/unrated tracks are no longer shown in the statistics dialog. + * Removed the option "Import Playlists". It's now always enabled. + * Show total track time in context browser (BR 126548) + * Derive filename for downloaded podcast episodes from their url in the + rss feed. (BR 125966) + * Only show albums/artists/genres with more than 3 tracks when listing + favourite albums/artists/genres. (BR 126435) + * libtunepimp 0.5 compiles successfully. + * Podcasts are automatically configured to be checked for updates. + * Show only 2 decimal places for scores in the statistics module. + * Replace 'Move to Collection' in file browser context menu by 'Organize + Files' for collection directories. (BR 125702) + * Removed the option "Show Status Bar". It's now always enabled. + * Tracks from a media device scan be submitted to last.fm immediately, + without waiting for tracks to be played in amaroK. Patch by Iain + Benson . (BR 125690) + * Any failed attempts to submit to last.fm are now automatically retried + in the background, without waiting for new tracks to be played. + * Smart playlists can be constructed using mixed ALL and ANY matches + (BR 124483) + * Configure media devices in global settings, disable media browser when + no media device is configured. + * Dynamic Playlist bar made more conspicuous. + * The Konqueror setting to show a 'delete' entry in the menu is now + respected, if the setting exists and KDE is version 3.4 or higher. + * Cover art from m4a files. Updated m4a taglib patch by Jochen Issing + and patch by Shane King + . (BR 125414) + + BUGFIXES: + * The playlist would incorrectly sort after using the queue manager in + dynamic mode. + * Sort disc numbers numerically (BR 127114) + * Smart Playlists using 'last played time' now filter correctly. + (BR 127145) + * If "Transcode Whenever Possible" was selected for transferring to media + devices, if the file was in the device's preferred format, transcoding + would not take place. Thanks to Ants Aasma for the patch. (BR 127109) + * Fix possible loss of database after changing settings. (BR 126880) + * Only include audio files when expanding directories. (BR 126765) + * Correctly handle 'Cancel' in confirmation dialog for deleting items + from media devices. (BR 126989) + * Smart-Playlist random mode was not 'sticking'. (BR 126877) + * Statusbar log files would only ever write to the first log after all + four logs had been filled. + * iFP: Don't pretend to add newly transferred files to wrong folders. + * Set a podcast as listened only when it really has been listened to. + * All tracks from a cuesheet will now submit correctly to last.fm. + (BR 114969) + * xine-engine will now correctly detect a change when only one of the + artist or album metadata changes. Patch by Kim Rasmussen + . (BR 126648) + * Less than and between criteria in a smart playlist for playcount, rating + or score of 0 now work. (BR 97046) + * Empty genres are no longer displayed in the collection browser. + (BR 126495) + * Fix regression causing drag and drop of playlist track items in the + playlistbrowser to be functionless. (BR 126387) + * Fix regression causing podcast purge property to be ignored. (BR 126194) + * Automatically convert MySql/PostgreSql passwords from 1.3 to 1.4 state. + * Popup Messages would flicker when being shown. + * Some 1.3 podcasts wouldn't get transferred to 1.4 settings. + * New podcasts didn't get a default save location. (BR 126196) + * Fixed encoding problems with lyrics scripts. + * Mark/unmark as compilation is now stored in the file tag so it is + remembered when the colection is rescanned. (BR 120428) + * Submissions from media devices are timestamped so as to be less likely + to conflict with submissions from another last.fm client. (BR 125367) + * The MySQL connection will no longer time out when idle. (BR 120198) + * Load manually configured media devices even after failed DCOP queries. + Patch by Iain Benson . (BR 125692) + * Copy/move to collection recurses into directories. (BR 125334) + * Amazon no longer tries to refetch invalid entries. (BR 125168) + * Skip hidden directories while scanning the collection. (BR 115478) + * Instead of cancelling collection organiziation operations when starting + new one append to running one. + * Correctly show & in playlist 'Burn' right-click submenu. Patch by + Laszlo Pandy . (BR 125117) + * Disable option to delete remote items in playlist right-click menu. + (BR 124745) + * Reload playlist browser podcasts when switching database engines. + * Podcast tables recreated on startup if they don't exist. + + +VERSION 1.4-beta3: + FEATURES: + * amaroK now supports multiple media devices of varying types (currently + iPods, UMS/VFAT, and iFP devices). + * Autodetection of iPods and UMS/VFAT devices (if KDE has HAL/DBUS support + compiled in). + * New DCOP call "devices: showDeviceList()" to show the Device Manager's + current device knowledge. + * amaroK now has a custom icon theme, and an option to switch back to the + system icons, if preferred (in the General settings section). + * Collection browser view is separated alphabetically. Patch by + Christian Hoenig . + * Ease navigation with track slider below playlist window by showing mood. + (BR 121715) + * Show context information for podcasts. + * Filebrowser: toolbar button to change to the directory of the currently + playing song. (BR 115479) + * Added "Play Audio CD" entry to the amaroK menu. (BR 103409) + * GStreamer-0.10 engine now supports visualizations. + * xine-engine: Show metadata for ogg vorbis streams. (BR 122505) + * Drag and drop podcast urls directly onto podcast folders for addition. + * Add media directly into directories for iRiver ifp devices. + * Button to directly edit lyrics from the context browser. (BR 123515) + * Support for SMIL playlists. (BR 121983) + * Support for WAX playlists. (BR 120980) + * Handle the Year tag when playing AudioCDs. Patch by Markus Kaufhold + . (BR 123428) + * Ignore 'The ' in artist names when sorting in the cover manager, as per + the collection browser. (BR 122858) + * Add autocompletion to the composer field in the tag dialog. (BR 123026) + + CHANGES: + * In context browser, show information about recently updated podcasts, + recently added and favourite albums when nothing is playing. + * Ratings can now have half stars: click again on the last star in the + rating to toggle it between a half and a full star. + * Improved handling of embedded cover art, utilizing the database. Patch + by Shane King . (BR 124563) + * Statistics tool has had numerous improvements. + * Optimise: Only rerender the CollectionBrowser when relevant. + * Disable detection of iPod model and thus solve g_object_get related + problems. (BR 121990) + * Don't block GUI when trying to transfer large numbers of items already + on media device. (BR 123570) + * Update playlist items when their location is changed during organizing + files. (BR 123752) + * Recursively add tracks when directories are dropped to the media browser + and the collection browser. (BR 123982) + * Visualizations now receive stereo data from amaroK. (BR 118765) + * Upgraded internal SQLite library to version 3.3.4. + * Podcast information is stored in the database. + * Improved password handling in the PostgreSQL config dialog. Patch by + Peter C. Ndikuwera . (BR 118304) + + BUGFIXES: + * Expand-By smart playlists were returning the wrong number of values. + * Fix display of media device transfer queues larger than 4 GB. (BR 125247) + * Fix duplicate detection when transferring to media device for tracks having + empty album tags. (BR 125203) + * Fix spuriously garbled collection scans. Patch by Shane King + . (BR 125114) + * Fix error with 'Back' link when browsing related artists. (BR 123227) + * Files with names containing '#' or '?' from smart playlists would not + get transferred to media device. (BR 122488) + * Stop Playing After Track option wouldn't be shown for the right tracks, + when there were queued tracks. Patch by Marcelo Penna Guerra + . (BR 124297) + * Don't submit podcast episodes to last.fm. (BR 118987) + * Accept system:/media/ urls into the playlist. (BR 120249) + * Fix leak of file descriptors with embedded cover art. Patch by Shane + King . (BR 123472) + * Stop collection folders being automatically removed. Instead, allow + user to remove non-existent folders by deselecting parent. (BR 123745) + * Stop delete key in playlist deleting last deselected item. (BR 123265) + * xine-engine: Show bitrate and samplerate for CD-Audio and WAV. Patch by + Markus Kaufhold . (BR 123625) + * Some podcasts would cause amaroK to hang. + * Check if directories still exist when showing Collection directories. + (BR 123834) + * Playlist popup menu had a visual glitch with Lipstik and (probably) + earlier versions of Plastik. + * Fixed a huge memory leak when using xine-engine with crossfading. + (BR 119230) + * Sometimes iRiver devices would crash upon disconnecting. (BR 123416) + * Adjust the Astraweb lyrics script for a layout change on the site. Patch + by Andrew Turner . (BR 123636) + * Directory selection would incorrectly highlight a directory in a + corner case. (BR 123635) + * Don't pretend to be able to uninstall default ContextBrowser themes. + (BR 123585) + * Fix preamp and frequency band scaling in the xine equalizer. Patch by + Tobias Knieper . (BR 116633) + * OSD text would not be stripped of empty lines. + * Playlist couldn't be shuffled if queued items existed. (BR 120221) + * Fixed renaming of Smart Playlists. (BR 122509) + * Fixed some bugs with PostgreSQL and Smart Playlists. Patch by Peter C. + Ndikuwera . (BR 123317) + * Escape invalid characters when transferring files to IFP devices. + (BR 123199) + * Escape newline characters when showing detailed information for podcast + items in the playlistbrowser. (BR 123109) + + +VERSION 1.4-beta2: + FEATURES: + * Equalizer for the GStreamer-0.10 engine. + * Crossfade in the helix engine! + * The build date is shown in the "About amaroK" dialog. + * Show album covers when dragging playlist items. Patch from Jonas + Hurrelmann . + + CHANGES: + * Summarize transfer failures to media devices instead of a message for each. + (BR 122491) + * Don't list the entry in the engine selection widget, when + it's not the active engine. Makes no sense to select this dummy engine. + * The aRts and GStreamer-0.8 engines have been removed for being obsolete. + * Automatically skip to the next track in the playlist when a track is + unplayable. (BR 116555) + * Don't check for collection changes on startup if Watch Folders is + disabled. (BR 116173) + + BUGFIXES: + * Handle .m4a files as audio when transferring to iPod video. (BR 122492) + * Smart playlists would not transfer to media devices. (BR 122838) + * Assume that .mp4 files are audio only when transferring to iPod. (BR 122591) + * Dereference symbolic links when transferring to iPod. (BR 123206) + * Correct domain for japanese wikipedia locale. (BR 122319) + * When deleting a downloaded podcast, the icon wouldn't be updated. + (BR 122440) + * Manage Files would create duplicates on collection. (BR 122519) + * On Statistics Dialog, Compilations would be shown with a random artist, + and dragging to playlist would add only the tracks by that artist. + (BR 122363) + * When editing current dynamic playlist, the adjusting of upcoming tracks + could be faulty. (BR 122401) + * Changing database on First-Run Wizard wouldn't work. + * When loading M3U playlists containing "." or "..", amaroK failed to + detect that the files are in the collection. Patch by Ted Percival + . (BR 121046) + * Konqueror sidebar would show garbage for people not using UTF-8 locales. + (BR 122395) + * "Open in External Browser" in the lyrics tab works now. + * Lyrc lyrics script handles tick characters correctly. + * Crash on startup when upgrading from 1.3, using MySQL. (BR 122042) + * No more crash on exit or deleting podcast. + * Handle metadata for .aac files as mpeg instead of mp4. (BR 121852) + + +VERSION 1.4-beta1: + FEATURES: + * AudioCD (CDDA) support for xine-engine, including CDDB lookup. Patch by + Alberto Griggio . (BR 121647) + * The Helix engine now supports direct alsa playback using Realplayer 10. + * New DCOP call "player: setVolumeRelative(int ticks)". + * Options for Random Mode to favor tracks with a higher rating, score, or + ones less recently played. + * Support for playing entire albums. This works just like normal, except + when choosing the next track, it'll go to the next track from the album + it finds in the playlist, or the first track of another album otherwise. + * Support for plain VFAT devices in the Media Device browser. + * You can now mousewheel over a track's queue label to change its position + in the queue. + * Added a time-filter to the CollectionBrowser. Now you can make it show + only those tracks, which have been added to your collection within the + last day, week, month or year. + * Fit to Width for the playlist columns is now optional (accessible in the + context menu for the column headers). + * On-the-fly transcoding when transferring to media devices, provided + that an appropriate transcoding script is running. + * Handle compilations as such on iPods. + * New DCOP calls "mediabrowser: ..." for interfacing with media devices. + * Multiple simultaneously connected media devices. + * Lyrics support is now scriptable. This allows to add support for any + lyrics site, and makes it possible to provide upgrades. (BR 94437) + * New DCOP call "contextbrowser: showLyrics(string)". + * New 'File Size' column in the playlist. + * Amarok now supports ASX playlist files. (BR 114051) + * New DCOP call "collection: isDirInCollection(const QString& path )". + * New DCOP call "playlist: removeByIndex(int)". (BR 119143) + * For mp3, aac/mp4, and ogg vorbis, it's possible to use Disc Number and + Composer tags. (BR 110675) (BR 90503) + * For xine-lib 1.1.1 and greater, xine engine has gapless playback. amaroK + is now "The Wall" compatible. (BR 77766) + * Option for selecting external web browser in amaroK. No longer requires + KDE-Base. (BR 106015) + * Press Enter in the Collection Browser filter to send all the visible + tracks to the playlist. + * Hold Ctrl while pressing Enter in the playlist's filter to apply to all + visible items instead of just the first, and Shift to only queue and not + play them. + * Tags can be edited inline in the playlist by clicking on a single selected + item. + * Switchable Wikipedia locale. (BR 104383) + * Initial port of GStreamer engine to GStreamer 0.10. + * Drag albums and compilations from context browser to media device and + playlist browser. + * Browse your collection and other related artists with context browser. + * Copy artwork to iPods capable of displaying it. + * Show extended podcast info on iPod. + * Optionally update playcount for items played on iPod and submit them + to last.fm and synchronize ratings between amaroK and iPod. + * Tracks can now be rated from 1-5 stars manually, in addition to the score + which amaroK calculates automatically based on your listening habits. You + can use the 'Rating' column and Win+1..5 to change the rating. + * Ability to copy items from iPod and from filebrowser to collection. + * New 'Last Played' column in the playlist, showing when the track was last + played. (Like in the Context Browser.) + * Browsers can be now accessed with keyboard shortcuts, Ctrl+1..5. + Also Ctrl+0 to close the current one, and Ctrl+Tab to switch the focus + between the playlist and the active browser. + * Downloaded podcast episodes can be deleted from the context menu. + * New DCOP call "player: osdEnabled". + * Add contents of smart amaroK playlists as playlist to media device. + * Mediabrowser support for the iRiver iFP series! + * New dcop call playlistbrowser loadPlaylist. (BR 110082) + * New Edit Track Information dialog. Lyrics can be edited there, comments + can have more than one line, some statistics and tag guessing from + filename. (BR 93982) + * Show/hide browsers via context menu. (BR 110823) + * Display disk space on media device. + * Copy standard and amaroK playlists to media device. + * Create playlist from items transferred to iPod. + * Edit dumb iPod playlists with media browser. + * Ability to read audible.com .aa file metadata and to transfer audiobooks + to iPod via file browser. + * Optionally add new podcasts to media device transfer queue on download + and remove podcasts already listened to on media device connect. + * Add podcast shows to the Podcast folder on iPods. + * Persistent media device transfer queue. + * Incremental update of media device view. + * Automatic scanning for stale and orphaned iPod items. + * Moodbar! + * configure: report not included extra features (BR 115057) + * Ability to uninstall context-browser themes. (BR 111449) + * More columns available in the Flat View of the Collection Browser. + * New Collection Scanner, running in an external process. No longer can + amaroK crash while scanning the Collection :) + * Statistics tool! + * Dragging external playlists into the playlist browser will add them. + * NMM engine now has a configure dialog. + * Collection scanner now supports WMA, MP4/AAC, and RealMedia (RA,RV,RM). + * You can now Organize Music from the Collection Browser, to move and + rename files to a logical place in your collection folders based on their + tags. + * Option to crossfade only on manual track changes. Useful for listening + to consecutive tracks on a single album. + + CHANGES: + * Dynamic Mode is now stateless, meaning there's no Dynamic Mode any more, + only loading and unloading of Dynamic Playlists. There's also now a nice + info bar above the playlist when a Dynamic Playlist is loaded. + * The major huge context menu used for hiding/showing columns in the + playlist has been replaced with a shorter one and a nice dialog. + * Elapsed time / length in the systray tooltip now updates in real time as + the song progresses. + * Tooltips in the playlist for truncated text are now shown directly above + the text, giving the effect of it being expanded to its full length. + * The option for restarting scripts automatically at startup is removed, as + it is now the default behaviour. + * Reduced memory usage for large playlists to under 30% of pre-1.4 versions. + (Measured as the difference in memory usage between an empty playlist and + loading the 'All Collection' smart playlist.) + * Import iTunes album art from directories. + * Media Devices (Apple iPod, iRiver iFP, ...) are now handled with plugins. + * New default image for albums with no cover art. + * When tabbing between cells while editing tags in the playlist, autosave + the contents of the previous tag you edited, so you don't have to + constantly go in and out of editing mode to edit lots of tags. + * When saving playlists, if there's already one with the same name, instead + of complaining about it, smartly append (2), (3), etc. to the end. + * 'Stop Playing After Track' now has a shortcut (Ctrl+Alt+V), and a global + shortcut for the currently playing track (Ctrl+Win+V). + * Various keyboard usability and focus tweaks so using amaroK with the + keyboard is nicer. + * Upgraded internal SQLite database library to version 3.2.7. + * Recoding mp3 tags has been removed due to many unjustified + complications. + * Viewing track information of remote media will show the url. + * "Update"-button is now hidden in the collection browser if "Watch + folders for changes" is enabled in the options. + * Playlist Browser now remembers which entries were open across startups. + * The tooltip and the menu from the queue icon in the statusbar now shows + the total length of the queued tracks. + * The Home tab has been merged into the Current tab, now called Music. + * New look for the current track marker in the playlist. Pimp my roK! + * When turning either random or dynamic mode on, turn the other off, + instead of completely disabling random mode when dynamic is on. + * libgpod from gtkpod replaces kio based iPod support for improved + compatibility with various iPod models. + * Podcast settings are hierarchical now, meaning you can set settings + for the category's, newly added podcasts take the settings from there parent category. + + BUGFIXES: + * Dragging text to a filter line edit would still show the "Filter + Here..." text in the background. (BR 108876) + * Don't show an empty playlist length holder in the statusbar. + * Allow for % and _ in tags, and filter them correctly. + * Do not copy files of types an iPod is not capable of playing to the + iPod. (BR 117486) + * Also take track number into account when comparing tags for checking + if a track is already present on iPod. (BR 117380) + * iPod nanos would not switch off during playing songs added with amaroK + because of their file size not being set. + * "Show Fullsize" now works for ID3 embedded cover images. (BR 114517) + * Fix possible bug when saving unencoded podcasts to strange file systems. + * OSD Preview did not update colours when toggling 'Use custom colours' + option. (BR 115965) + * Cached lyrics are not erased when rescanning. (BR 110489) + * No more "can't create amazon table" warnings. (BR 113930) + * Creating a new playlist via drag-and-drop no longer shows duplicates + of each song until amaroK is restarted. + + +VERSION 1.3.9: + FEATURES: + * Support for libtunepimp 0.4. (BR 94988) + + BUGFIXES: + * Fix leak of file descriptors with embedded cover art. Patch by Shane + King . (BR 123472) + * Playlist popup menu had a visual glitch with Lipstik and (probably) + earlier versions of Plastik. + * Fix preamp and frequency band scaling in the xine equalizer. Patch by + Tobias Knieper . (BR 116633) + * Fixed a huge memory leak when using xine-engine with crossfading. + (BR 119230) + * Fix memory leak in the helix engine when the player and playlist are + not visible. + * Stream with URLs containing "&" wouldn't be correctly saved. + (BR 121846) + * Playlist Browser would save invalid PLS Playlists. (BR 122875) + * Refresh All Podcasts wouldn't consider subfolders. (BR 122783) + * When using a folder as playlist, deleting the playlist would delete + the folder and all files inside it. (BR 122480) + * OSD was showing "No track playing" for tracks without metadata. + * Smart Playlists with playcount or score related conditions wouldn't + match all songs properly. (BR 97046) + * With enormous queues, stop menu would take a lot of time to show up. + (BR 120677) + + +VERSION 1.3.8: + BUGFIXES: + * NMM engine would crash when seeking after the playlist finished, + state Empty wasn't emitted. + * Fixed URL of the Nectarine radio stream. + * Fix crash after changing the alsa device in the helix configuration + dialog. + * When amaroK exits, send SIGTERM to running scripts. (BR 119159) + * Old error messages could be shown instead of current track lyrics. + * The equalizer in the helix engine now works properly at low sample + frequencies. + * Fixed some threading issues in loading XML playlists. + * Lyrics that are available on lyrc would be shown as "not found". + * The helix engine now includes protection so that misbehaving streams + do not cause the visualizations to leak memory. + + +VERSION 1.3.7: + CHANGES: + * In the tree view, sort tracks alphabetically first, unless one of the + categories is by album, then sort by track number first. (BR 112830) + * No longer delete Amazon covers every 90 days, instead relying on + RefreshImages to re-download covers every 80 days to comply with + the TOS of the Amazon web service. + + BUGFIXES: + * Fix weirdness when overwriting a playlist by dragging a file to the + browser. + * When using Year - Album on Collection Browser, if two albums had the + same year, the order would be pseudo-random. Patch by Xepo + . (BR 115584) + * Fix build issue on PCLinuxOS with "cpu_set undeclared". + * Fix crash in helix engine caused by improper reference counting + of the audiostreamresponse object. + * Helix engine no longer declares it is "empty" on a track change + (caused problems with context browser). + * Tag dialog doesn't delete year tags any more when editing multiple + tracks. + * amaroK would crash or hang when fetching similar artists information + from last.fm (BR 116399) + * Fix memory leak in the helix engine. (BR 116223) + * When changing the database type, the apply button wouldn't be enabled, + and it would be necessary to restart amaroK for it to work properly. + * Fix for regression in Qt 3.3.5, causing amaroK to crash when clearing + the playlist. (BR 116004) + * Zombie directories are removed automatically from the collection + scanner. (BR 115779) + * Dates wouldn't be properly loaded when editing Smart Playlists. + * Number of songs to add when using dynamic mode wouldn't be respected, + if the smartplaylist didn't have a ORDER BY statement. (BR 115860) + * Fix visibility related build problem on some distros. + + +VERSION 1.3.6: + BUGFIXES: + * Fix autoscan with PostgreSQL. (BR 111209) + * Fix problem with sequences in PostgreSQL support. (BR 115075) + * Fix potential crash at startup while accessing amazon.com. (BR 115838) + * Potential crash when loading media from the Collection. (BR 115234) + * Podcast apply to all button was faulty. + * last.fm queue wouldn't be saved to disk. Patch by John Patterson + . (BR 115212) + * Podcast download directory would only be effective next time the + application started. + * Don't crash when attempting to save an empty playlist from the Playlist + menu. + * Loading dynamic playlists with sources did not work properly. + * Fix build issue on some Linux kernel 2.4 distros. (BR 115068) + + +VERSION 1.3.5: + BUGFIXES: + * Fixed a build issue. + * Fixed potential crash at startup. (BR 114983) + + +VERSION 1.3.4: + FEATURES: + * Helix-engine supports ALSA (using RealPlayer 11). (BR 113909) + * Atom feed compatibility for podcasts. + * Statusbar messages are logged to a file, statusbar.log. (BR 99899) + * Podcast configuration now provides the ability to set the values for + all podcasts. (BR 114371) + * Downloading multiple podcasts will throw them into a queue, and + each will be downloaded sequentially. (BR 114370) + * Playlistbrowser items can be dragged into folders. + + CHANGES: + * Categories in the playlist browser are now always in the order of: + Playlists, Smart Playlists, Dynamic Playlists, Radio Streams, then + Podcasts, regardless of sorting options. (Items in the categories + are still sorted normally.) + * Reworked systray icon handling -- mostly under the hood, but it'll + now update properly - eg. when you change the cover. (BR 111014) + * Tooltip for the queue icon in the statusbar will now show the album + cover of the upcoming track. + * Totals in the collection browser will now reflect the visible items + if you set a filter. + * Podcast settings "download on request" and "stream on request" have + been merged. + * About button in script manager now uses a KAboutDialog and supports + rich text format in the README file. (BR 110961) + * After filtering the collection browser, if only a single item is left + visible, it will automatically be expanded. + * Added items for the Equalizer, Visualizations, and Queue Manager to + the context menus of the volume slider, analyzer, and statusbar queue + icon, respectively. + + BUGFIXES: + * If you queue an album from the context browser and then undo, the + queue icon in the statusbar is now updated properly (and hence + doesn't crash if you click on it). + * helix-engine no longer emits new metaData if only the bitrate of a + stream changes. (BR 114348) + * Fix amaroK attempting to destroy your computer, reach through the + monitor and violently strangle you if you attempt to exit while the + collection is being scanned. (BR 114597) (BR 114859) + * Postgresql code cleanup and fixed regression for manual collection + scanning. Autoscan still does not work. (BR 111209) + * File browser now sets to home if it was on a remote directory to prevent + annoying error messages. (BR 114498) + * Podcast settings would not add a trailing slash to podcast save + locations. (BR 114712) + * Workaround for stability issues with HyperThreading on Linux. + Added a configure check to deal with buggy GLIBC's. (BR 99199) + * xine-engine: Equalizer became inactive on trackchange when crossfading + was enabled. (BR 114492) + * Pausing a track would abort lyrics and wiki fetch jobs. (BR 114576) + * Dynamic mode did not respect repeat track mode. (BR 114585) + * The Script Manager no longer captures the script's stdout. + * Enqueuing files with amarok -e would not work for relative paths if the + working directories of the new and the running instance of amarok differ. + * Visualizations would only work when amarok was run as amarokapp. + (BR 99627) + * The number of podcasts items would be limited even when the user didn't + set it. (BR 114353) + * Switching system language wouldn't affect the root folder names on + Playlist Browser. + * On Context Browser, when showing a cached lyric, "add", "search", and + "open in external browser" buttons wouldn't work. "Open in External + Browser" is now disabled for cached lyrics. (BR 110812) + * Refreshing all podcasts when folder existed caused a crash. + * Multiple job statusbar widget was broken. (BR 114278) + * HTML in tags was getting interpreted in the context browser. + * Changing the podcast purge count could sometimes cause amaroK to hang. + * NMM-engine: Fixed crash after playing a song to the end, the trackEnd + signal was not emitted from the GUI thread. + * With Random Mode enabled and Repeat Playlist disabled, when it got to + the last track, it would play it a second time and then keep on playing + other tracks, instead of just stopping. + * Smart-Playlists were broken with PostgreSQL. Patch by Michael Landin + Hostbaek . (BR 114269) + * Collection scanner ignored files with non-ascii characters. (BR 114195) + * Don't show "Change Collection Setup"-box for non-local files. + * Fixed issue with loading playlists containing remote URL's. + * Dynamic mode history tracks would be forgotten if there was no current + track on startup. (BR 110160) + * Fixed problems with "Retrieve Similar Artists" feature in combination + with SQLite, which could lead to 100% CPU usage. (BR 104447) + * Tabbing between items and cells in the playlist while editing them now + works much nicer (goes in order and doesn't tab to invisible columns), + and you can also now use Alt+Up, Down, Left, Right to navigate between + cells as well. + * Podcast settings failed to remember the save location. (BR 114128) + * Tray icon would stop filling up and showing play/pause icon if show + player window was toggled. (BR 93711) + * If player window is toggled during playback, playlist window's caption + now correctly shows the current track's name. + * Crossfade length would be enabled in Playback options when "No + crossfading" was selected. + * If an engine does not support crossfading, "No crossfading" is now + selected in Playback options. + + +VERSION 1.3.3: + FEATURES: + * New DCOP call "contextbrowser: showHome". + * New DCOP call "contextbrowser: showCurrentTrack". + * New DCOP call "contextbrowser: showLyrics". + * New DCOP call "contextbrowser: showWiki". + * Saving a playlist will cleverly pick a default name if possible. + * Dragging an album cover into the playlist from the context browser + will append the album. + * Middle mouse button on the current track will toggle play/pause. + * Ctrl-Right click on a selection of tracks will queue all of them, not + just the track below the cursor. (BR 112841) + * CoverManager allows for downloads from Amazon Canada. (BR 113238) + * New DCOP call "playlistbrowser: addPlaylist". + * New DCOP call "playlistbrowser: scanPodcasts". Will check all podcasts + for new episodes. + * New DCOP call "playlistbrowser: addPodcast". + * New DCOP call "player: type". Returns the current track's file type. + * New DCOP call "collection: migrateFile". Updates the collection db for + changes made to filenames, keeping stats intact. + * Smartplaylist has Length property. (BR 113039) + * Added a mouse-over effect for the volume slider. + + CHANGES: + * Adding a playlistbrowser folder will automatically focus the lineedit + for renaming the item. + * Removing podcasts will delete all downloaded media. + * Playlists in the playlistbrowser can no longer be removed, only deleted. + * Removing tracks when in dynamic mode will only replace up to the minimum + upcoming tracks requirement. + * Playlist columns are automatically resized when adding or removing + columns. + * Added a warning dialog when HyperThreading is enabled. (BR 99199) + * Blacklisted GStreamer's autoaudiosink, which is really a crapsink. + * Added a context menu to the volume slider. + * When viewing covers in fullsize, the window has a maximum size, and + scrollbars are shown if necessary. The user can also scroll the cover + by dragging it. Patch by Eyal Lotem . (BR 103990) + + BUGFIXES: + * Patch fixing an almost-infinite directory-scanning problem while + building the Collection. Patch by Dirk Mueller . + * Cover Manager: Album view setting became out of sync. Patch by Michael + Pujos . (BR 113370) + * Starting the first track in the playlist when in dynamic mode would skip + it. (BR 110160) + * Position slider in player-window disappeared after 2 hours. (BR 97128) + * PlaylistBrowser duplicated items when overwriting playlists. (BR 108693) + * Podcast settings would forget about the purge items checkbox. + * The Stop button in the toolbar was always enabled at startup. + * GStreamer-Engine: Could not seek to position 00:00:00. (BR 106483) + * Don't crossfade the last track in the playlist. (BR 96478) + * If files were in the transfer queue before connecting the iPod they + would be uploaded without checking if they already exist on the device. + * Using dynamic mode's playlist shuffle would result in repeated tracks + tracks during a populate operation. + * Fixed Xine config options were disappearing on ESC key. (BR 113225) + * Fixed problems with visibility enabled compilers. Patch by Unai Garro + . (BR 113056) + * Fix regression causing dynamic mode playlist shuffle to break for + smart playlists which relied on ordering and limits. (BR 113121) + * Automatic podcast downloads did not do anything. (BR 113129) + * Playlist browser items were not properly saved on quit (with Qt 3.3.5). + Patch by Matthieu Bedouet . (BR 113020) + * amaroK could crash on startup, if on last exit sorting was enabled in + the playlist. (BR 113042) + * Adding entries to a playlist and saving it could duplicate some tracks, + if the playlist hadn't been expanded before. (BR 111579) + + +VERSION 1.3.2: + FEATURES: + * Tabs will open automatically when dragging files between tabs. + Patch by Christian Baumgart . + * Two new dcop calls which allow scripts to read many of amaroK's + configuration options. script readConfig(key) for strings, integers and + bools. script readListConfig(key) for lists. Note that these functions + aren't guaranteed to always return the latest settings (though many do). + * Added a right click menu for blank areas of the playlist, with options + to save, clear or shuffle the playlist and to "enable the dynamic + mode & repopulate". + * Playcount is shown in the tag dialog. + * New volume slider, both better looking and better working than + the old one. + * Podcasts can be saved to any location. (BR 111059) + * Added "Save as Playlist" option to the collection and file browser + context menus as well. + * Allow removing of items in the Media Device browser transfer + queue. + + CHANGES: + * Scroll wheel to switch tabs in context browser. + * Repopulate button is enabled or disabled together with dynamic mode. + * No warning dialog when starting if the directory File Browser is on + doesn't exist anymore. It just reverts to home. (BR 99208) + * Sorting on Collection Browser now shows "Unknown" items first, and + "Various Artists" last. Years are sorted descending now. + * When selecting 'Play' from the context menu on multiple items, + it'll now play the first and queue the rest. + + BUGFIXES: + * The Equalizer and QueueManager widgets were broken on window managers + other than KWin. + * "Year - Album" category in the Collection Browser didn't allow for + dragging tracks or fetching cover images. + * Xine engine no longer adds images to the playlist. + * The delete key for removing playlist items works even if the file + browser is open. (BR 100145) + * Filenames with XML entity codes were not playable in dynamic mode + and caused it to stop. (BR 108783) + * If the album or artist contained "&", cover fetching wouldn't work + properly. + * When restarting, Playlist Browser items used for playlist shuffle + wouldn't be properly marked, though they would be taken into account. + * Don't crash after changing Podcast options, or after manually deleting + its first item. + * When renaming a playlist, the "." would be removed from the filename. + Paych by Elliot Pahl . (BR 112204) + * When using next and previous on Tagdialog, after passing by a stream, + the fields would be always disabled. (BR 112060) + * Restarting track when in dynamic mode didn't work. + * Fix issues with the GStreamer engine and alsasink, and reenable it. + Patch by Vincent Tondellier . (BR 112103) + * Dynamic playlist shuffle had some incorrect smart playlist handling. + * Robustified the code for handling the '# of tracks in the playlist' + part of the statusbar, it should not ever get out of sync with + reality now. Nice side effect is you can see the track count + increase while a playlist is loading. + * "Last played - not in the last" smart playlists would only work for + sqlite. (BR 112248) + * Podcast and Dynamic subfolders are correctly restored on application + start. (BR 112162) + * Dropping tracks onto playlist browser folders will work correctly. + * Invalid podcasts are no longer discarded on quit. (BR 112116) + * Fixed playing of files that have special characters like '#' in + helix engine. + * Fixed issue where selecting multiple items after filtering the + playlist would cause all the other items 'between' them (but + invisible due to the filter) to also get selected. + + +VERSION 1.3.1: + FEATURES: + * Added 'Set as Playlist (Crop)' and 'Save as Playlist' options in the + playlist context menu. (BR 99932) + * Support for iPod shuffle devices. Patch by Guenter Schwann + . + * Media Device browser now has a connect button for connecting + your iPod after amaroK has already been started. Also includes + configurable mounting/unmounting options. + * Holding down the stop button (as opposed to just clicking it) pops + up a menu letting you stop either now, after the current track, or + after the end of the queue. + * Collection browser filter now fully supports the same Google-esque + syntax as the playlist filter, plus one extra: lyrics:"stuff to search + for" to search in cached lyrics. + * Pressing Shift+Enter after filtering the playlist will now queue + the first track. (BR 111054) + * Display short statistics in the collection browser depending on the + categorisation method. + * New DCOP call "collection: totalTracks". Returns the total number of + tracks in the collection. + * New DCOP call "collection: totalGenres". Returns the total number of + genres in the collection. + * New DCOP call "collection: totalCompilations". Returns the total number + of compilations in the collection. + * New DCOP call "collection: totalArtists". Returns the total number of + artists in the collection. + * New DCOP call "collection: totalAlbums". Returns the total number of + tracks in the collection. + * New DCOP call "collection: similarArtists(int artists)". Returns the + similar artists of the current track, results are limited by 'artists'. + * New DCOP call "playlist: repopulate". Repopulates the playlist with + tracks from dynamic mode. + * New DCOP call "player: showBrowser". Allows for showing of playlist + window browser, see the handbook for useage. + * New DCOP call "player: setLyricsByPath". Allows adding custom lyrics + for tracks. + * Add an icon in the statusbar displaying the number of queued tracks; + click on it to pop up a menu letting you jump to their locations in + the playlist. + + CHANGES: + * New "Blue Danna" splash screen. Created by Nenad Grujicic, modified by + Nathan Adolph. + * 'Stop after track' is now saved (and so remembered across amaroK + restarts). + * Ported playlist + filter-lineedit behaviour to collection browser as + well: you can move between the view and the filter with the up/down + buttons, and just typing into the view will set the filter. (BR 108656) + * Wiki Tab links use the color set for links, instead of "Selected + Background". Style Authors can use "AMAROK_LINKCOLOR" if they want that + color. (BR 111228) + * The Equalizer widget has been pimped. + * Pressing 'up' in the playlist filter will now take you to the end of + the playlist, in addition to down going to the beginning, as before. + * When jumping to the current track, it now gets centered instead of only + barely showing. + * GStreamer-engine was rewritten. The crossfading feature was removed for + now (it didn't work right with recent GStreamer versions). Improvements: + 1) Reduced CPU usage 2) Reduced latency 3) Increased stability + * No need to restart amaroK to use your iPod! + * Improved Konqueror Sidebar. + * The bundled "Shouter" AmarokScript (for radio stream serving) has been + updated and improved. + + BUGFIXES: + * amaroK wouldn't remember current track when restarting. (BR 110282) + * Some memory leaks found and fixed. + * Fix buzz and subsequent clicking when equalizer enabled in Helix and + GStreamer engines compiled with GCC 4.0.1. + * Burn option wouldn't show up for "Year - Album" items on Collection + Browser. + * Tray's tooltip would show things like 69:40 of 1:12:01. + * Wiki Tab wouldn't work for names that contained "/". (BR 111634) + * With KDE 3.4, the proper context menu wouldn't be shown for File + Browser. Patch by Christian Baumgart . + (BR 103305) + * Playcounter and Access Date wouldn't be updated properly for PostgreSQL. + Patch by Tonton . (BR 111519) + * Clicking twice on the uninstall button for the same script, would make + amaroK crash. + * Fixed an obscure crash when you emptied the playlist, had the focus on + it, and pressed up. + * No longer show dynamic info popup on application startup. Patch by + Christian Baumgart . + * Sometimes the system tray tooltip did not update on song change. + * Polishing for the collection browser and expanded item states. Patch + by Christian Baumgart . + * With xine-engine amaroK always treated remote media like radio streams. + * Selecting Classical equalizer preset prompted for name. + * Fixed konqueror sidebar compilation with kde <= 3.3 and gcc patched for + visibility. + * Konqueror sidebar can switch again between tabs. + * Fixed playing of oggs in helix engine. + * Fixed crash in helix engine when switching engines if helix/realplayer + not installed. + * Undo/Redo for the playlist was broken in some cases. + * On Collection Browser, when grouping by Genre/Artist/Year-Album it + wouldn't show the tracks. (BR 110890) + * SmartPlaylist Editor would reset "Match Any" to "Match All" when + editing. Patch by Kevin Henderson (BR 110918). + * Podcasts and playlist tracks would be sorted lexicographically + (BR 97297). + * Saved dynamic playlists were not removable. + * xine-engine: amaroK would get stuck on exit if the Equalizer was enabled + and the engine playing. (BR 110791) + * Dequeued items sometimes weren't being repainted properly. + + +VERSION 1.3: + FEATURES: + * The tyranny of deleting covers every 90 days is over. Instead, amaroK now + automatically downloads the covers every 80 days to comply with + Amazon.com requirements. + + CHANGES: + * Removed 'Apply' button from dynamic config, all config options are now + hot! (Automatically applied on alteration) + * Minimum score changed from 1 to 0. (BR 107944) + * Playlist item lengths now shown with hours when necessary. + + BUGFIXES: + * M3U playlists would be broken after editing. (BR 109774) + * When there's no artist tag, don't show tons of unrelated songs and + albums in Context Browser. (BR 110319) + * Advertisements were showing up in Lyrics Tab for some songs. + * When editing tags in Playlist Window, only try to write the new tag if + it's different from the old one. (BR 110299) + * Changes to the score in the Edit Track Information dialog should only be + applied after clicking on the "Save and Close" button. + * When only the score is changed, amaroK shouldn't complain if the file is + read-only. (BR 109054) + * Mark/Unmark as compilation wouldn't work with SQLite. (BR 109275) + * Album Covers whose name or artist contained "'" wouldn't show up when + fetched from Amazon. (BR 109700) + * Edit Track Information dialog wouldn't update collection database if + filename contained non latin1 characters. Patch by Andrey Yasniy + (BR 110030) + * SmartPlaylist category created in the PlaylistBrowser once the + collection has been built for the first time. + * Refresh the context browser as appropriate when editing tags. (BR 108884) + * Cover image shown if track has no title. + * Statusbar cancel button will terminate a podcast download. + * Don't show multiple popup messages when retrieving podcast information. + * Don't crash when adding podcasts. (BR 109982) + * Tracks with urls containg apostrophes would not cache lyrics. + * PostgreSQL compile problem (BR 110033) + + +VERSION 1.3-beta3: + FEATURES: + * New "not in the last" option for the date fields in Smart Playlists. + (BR 107725) + * New OSD tokens: %directory and %type (shows whether it's a stream, or + otherwise the extension). + * New DCOP call "player: lyrics" (BR 100306) and Lyrics Caching. (BR 97961) + * New DCOP call "player: transferDeviceFiles". Transfers queued files to + the Media Device. + * New DCOP call "player: queueForTransfer". Queues files for transfer to + the Media Device. + * Download your favourite podcasts and let amaroK manage them for you! + * 17 Equalizer presets. (BR 96302) + * xine-engine supports crossfading. Note: Your audio device must support + mixing. SBLive, dmix or ALSA 1.0.9 will do the trick. + * Shuffle the queue list in the queue manager. (BR 108861) + * The audio plugin (autodetect, ALSA, esd etc.) for xine-engine is now + configurable. + * Playlist-Browser now remembers the state and layout of its tree view. + * Show a stop icon next to the track to stop playing after. + * Miniature player window for the minimalists out there! (BR 85876) + * "Stop Playing After Track" now also works for queued tracks. + * "Open in External Browser" button for Lyrics Tab, patch from Nick + Tryon (Dhraakellian). + * Funky shadow effect for the album cover @ Context-Browser and OSD. + (BR 108334) + * Create playlists by dragging tracks onto the Playlist Category in the + PlaylistBrowser. (BR 75029) + * Show OSD when pausing and unpausing. (BR 104508) + * Make 'The' prefix of artists be transparent in the collection + browser and sort accordingly. (BR 85959) + + CHANGES: + * TagLib version 1.4 is required. + * Renamed "Track Name" column to "Filename", "Extension" to "Type". + * "Use hardware volume mixer" option has been removed. + * "Play AudioCD" gets disabled for engines that don't support KIO. + * The OSD (by default) and systray tooltip now show the same infos in + the same order as the columns in the playlist. + * xine-engine's configuration dialog has been reworked and simplified. + * xine-engine has been given the highest engine plugin rank. + * Systray tooltip now shows "elapsed time / total time" for the length. + + BUGFIXES: + * When playing, the text in the current track's columns wouldn't get + ellipsii added if the column was too short. + * Dragging 'All Collection' smart playlist made amaroK hang. + * Compilations reported incorrect number of tracks in the Context + Browser. (BR 109651) + * Track play icon remains even when stopped playing. (BR 107284) + * Sometimes valid tracks were not submitted to AudioScrobbler. (BR 100278) + * Current playlist is now being remembered when amaroK crashes. (BR 98689) + * Playlist-Browser saves its state after each change, so that no data + is lost when amaroK crashes. (BR 108814) + * Crash when trying to save Smart Playlists after creating a Collection + for the first time. + * Context menu of compilations was empty in context browser. + * Don't append albums and compilations when clicking on text in the + context browser. (BR 98797) + * xine-engine: pre-amp for the equalizer works now. (BR 104882) + * Crash when changing the number of minimum upcoming tracks right after + starting amaroK. (BR 108251) + + +VERSION 1.3-beta2: + FEATURES: + * New DCOP call "collection: scanCollectionChanges" Scans for changes made + to the collection. + * Support for "media:" URLs. Patch by Sergio Cambra + (BR 102668) + * Support for visualizations in the Helix engine. + * Queue manager to help organise your queued tracks. (BR 90594) + * Ability to create Smart Playlists based on file path. (BR 92467) + * Per track scripting via custom playlist context menu items. + * Added advanced, Google-esque syntax to the playlist filter. Lets you do + things like artist:sirenia, "pink floyd", artist:"pink floyd", or even + score:>50. When just typing words, it works as before. (BR 99312) + + CHANGES: + * Upgraded included SQLite library to version 3.2.2. + * Bumped GStreamer and GStreamer-plugins dependency to version 0.8.6. + * aKode-engine has been disabled (too buggy/incomplete). + * Repopulate upcoming tracks on demand when using dynamic mode. + * Remodel the playlist browser to incorporate dynamic mode more fully. + + BUGFIXES: + * Don't show textual URLs in Wikipedia Tab. (BR 108031) + * Don't refresh the collection view on update scans, if nothing changed. + * xine-engine: Don't pop up hundreds of error messages when something + goes wrong. Patch from John Lash (BR 101646) + * Automatic theme download with KNewStuff works now. (BR 107313) + * Clicking on "Lookup track at musicbrainz" use %2520 for spaces in URL. + (BR 107946) + * Crash when loading dynamic playlists without a collection. + * Crash when saving smart playlist without a collection. + * Do not call TagLib::MPEG::File for non-mpeg files - some FLAC files + would cause the CPU to start running in circles. (BR 107029) + * Many Helix engine improvements. + * Crash when dragging playlist items into Playlist Browser. (BR 107709) + * Improved context display when playing radio streams with xine-engine. + * Number of album tracks was incorrect when showing statistics by album. + (BR 107762) + * Massive performance speedup for the default analyzer (BlockAnalyzer). + * Dynamic mode will grab tracks from closed playlists. + * Covermanager tooltips were persistent even when window closed. Tooltips + have now been replaced with statusbar text. (BR 106976) + * Turning off dynamic mode when items were filtered only 're-enabled' the + visible items. + * Disable random mode on startup if dynamic mode is on. (BR 107311) + * The user is warned if saving tags failed. (BR 91568) + * Sub-Folders in Playlist Browser are correctly saved and restored. + * Crash after clicking on remove playlists in dynamic mode. + * Crash on Context Menu in dynamic mode. + + +VERSION 1.3-beta1: + FEATURES: + * Add Media dialog allows for multiple file selection. (BR 105903) + * The browser-sidebar has been redesigned for improved usability. + * Cue file sheet support. Patch from Martin Ehmke . + (BR 92271). + * New OSD text token, %playcount, will write the playcount. + * SmartPlaylists are editable. (BR 91036) + * PlaylistBrowser gets a makeover! + * New playlist column "Playcount" for track play counts. + * New playlist column "Extension" allows easy sorting of playlist for + compatible file types for portable media players. + * Ability to save streams to the PlaylistBrowser (BR 91075, BR 104139) + * New DCOP call "playlist: popupMessage" Displays a popup message box + in the playlist window.. + * New "year - album" - group by mode for collection browser. (BR 94845) + * New DCOP call "player: setScoreByPath(url, int)". Sets score of a track + specified by it's path. + * New DCOP call "player: setScore(int)". Sets score of the current track. + * New DCOP call "player: path()". Returns the path of the current track. + * New DCOP call "playlist: saveM3u(path, relativePaths)". + * New ScriptManager notification: "volumeChange: int". + * Tooltips for album covers in the CoverManager. (BR 103996) + * Automatic download of themes and scripts via KNewStuff. + * Different analyzers available for the playlist window. + * New DCOP call "player: enableRepeatTrack" sets repeat track on or + off. + * HelixPlayer-engine. + * 'Load' and 'Append' entries for smart playlist context menus. (BR 99213) + * Support for reading embedded images from ID3 tags. (BR 88492) + * Wikipedia tab in ContextBrowser allows for artist biography retrieval + and more, supporting 9 different languages! (BR 98050) (BR 104383) + * Show "title by artist" on playlists titlebar and taskbar. (BR 97670) + * Option to show stats in the Home tab by album. Patch from Cédric + Brégardis . + * New DCOP call "script: listRunningScripts()". Returns a list of all + currently running scripts. (BR 102649) + * New DCOP call "script: stopScript(name)". Stops a script. (BR 102649) + * New DCOP call "script: runScript(name)". Runs a script. (BR 102649) + * New form of playlist manipulation - Dynamic Mode. + * New DCOP call "player: enableRepeatPlaylist" sets repeat playlist on or + off. (BR 102754) + * Add Score widget into the tag editor. (BR 100084) + * Support for PostgreSQL as database backend. (BR 99863) + + CHANGES: + * "amarokscript" filename extension is now mandatory for script packages. + * Append Suggestions has been superceded by Dynamic Mode. + * Add a label (with shortcut) to the Playlist filter. + + BUGFIXES: + * Message box when saving of playlist failed (BR 105520) + * Avoid weird results when fetching lyrics with slow connections. + (BR 103561) (BR 101327) + * Compensate for reversed slider widget in reverse layout locales, such as + Hebrew and Arabic. Patch from Assaf Gillat . + (BR 102978) + * Playlist playMedia now works with streams. + * Context Browser is updated when current track's tags are changed. + (BR 102839) + * Clearing the playlist while playing a track does not lead to a confusing + interface anymore. (BR 103510) + + +==BEGIN KDE 3.3 DEPENDENCY== + +VERSION 1.2.4: + FEATURES: + * Queue selected tracks shortcut, Ctrl+D. (BR 83675) + + BUGFIXES: + * The first engine entry in the config dialog was always blank. + * If you filtered by more than one word in Collection Browser, adding + expandable items (eg: artists or albums) wouldn't work. (BR 100150) + * Updating the collection without any changes being made to it kept + the Update button disabled forever. + * Application freezes when switching shoutcast streams. (BR 103890) + * MusicBrainz lookup was not escaping quote characters. (BR 103740) + * Fixed crash when clicking the "clear" button in CoverManager's filter + widget. + * Update lyrics page on new radio stream metadata. (BR 99725) + * xine-engine was reporting bogus tracklengths for ogg vorbis. (BR 102547) + + +VERSION 1.2.3: + FEATURES: + * Graphequalizer script can now enable and disable the equalizer. + * New DCOP call "player: equalizerEnabled" returns whether or not + the equalizer is enabled. + * OSD notification for mute. + * Mute global shortcut, Win+M. + * Add %comment token for comment display in OSD. (BR 100944) + * View/Edit track entry into context menus of ContextBrowser and + CollectionBrowser. + * You can mark/unmark albums as compilations via CollectionBrowser's + right-click contextmenu. + * New DCOP call "collection: query(const QString& sql)". + Allows to make arbitrary queries on the Collection database. + * New DCOP call "playlist: removeCurrentTrack()". (BR 92973) + + CHANGES: + * Show "Artist - Title" for compilation discs in CollectionBrowser + and ContextBrowser. + * Upgraded internal SQLite database to 3.2.0. + * DCOP call saveCurrentPlaylist() now returns the path to current.xml. + + BUGFIXES: + * Appropriate context menu entry for changing queue status for multiple + playlist items. + * Fix regression preventing dequeuing multiple selected tracks. + * 'Show Toolbar' remembers its settings between sessions. (BR 98662) + * When doing Musicbrainz lookup from the Context browser, search for the + real track, not the whole album. + * Memleak when a radio stream stalled. (BR 102047) + * The Collection Scan finally checks for the right file modification time. + * Adding a compilation disc from ContextBrowser was broken. + * GStreamer-engine: Reduced the gap when switching to next track without + crossfading. + * GStreamer-engine: amaroK was swallowing the beginning of a track when + Fade-in was set to zero. (BR 94472) + * Use a better highlight color in the "Configure Collection" dialog. + (BR 102059) + * "Remove Duplicates / Missing" fixed. Removes dead entries correctly. + * Fix units for samplerate. (BR 101528) + * amaroK using 100% CPU on some systems. (BR 101524) + (a KHTML bug which got exposed by code in amaroK 1.2.2) + + +VERSION 1.2.2: + FEATURES: + * Context Browser CSS styles can now be installed and selected from the + appearance settings. + * Append Suggestions now has an icon in the statusbar. + * When selecting multiple files, the "View/Edit Meta Information" dialog + will show the tags that are common to all of them. (BR 100423) + * A line graph equalizer added as a script "graphequalizer." + + CHANGES: + * Add 25-track and 50-track smart-playlists. + * Update current-track icons to include greater padding. + * The contextbrowser now uses data:-URLs instead of temp image files, so + they cannot be left on disk when amaroK terminates unexpectedly, and the + Konqueror/Universal sidebar can show them when amaroK is not running. + + BUGFIXES: + * escape '&' char in contextmenu entry (BR 101276) + * Track is set as a number in the database, so shouldn't be added rounded + by quotes. (BR 101208) + * Rewrote the broken .pls playlist parser. + * Handle delay gap between songs properly with aRts engine. (BR 90404) + * Switched order of "Make playlist" and "Queue after current track" menus + to avoid playlist destruction. (BR 96164 part 1) + * Visualizations with LibVisual didn't work in some cases. (BR 99627) + * amaroK could fail to build if the whole kdeextragear-1 module was + compiled, due to conflicts with K3B on the MusicBrainz check. (BR 100906) + * Images shown on OSD where incorrect for action notifications. + * The handbook translations were not built when amaroK was installed from + the tarball. I've written a new release script in Ruby, which can + handle the new structure of kde-i18n. (BR 100498) + * GStreamer-engine can now play vorbis radio streams properly, with + full metadata support. (BR 89821) + * GStreamer-engine now uses the "decodebin" autoplugger, which fixes + the lag issues that some users had during crossfading. (BR 99570) + + +VERSION 1.2.1: + FIX: Made the Tag-Editor only operate on visible items. (BR 100268) + ADD: Database settings added to the first-run wizard. + FIX: playlist2html generates UTF-8 output now. (BR 100140) + FIX: Bitrate/length showed random values for untagged mp3 files. (BR 100200) + FIX: Crash when recoding stream MetaData without CODEC selected. (BR 100077) + CHG: Show an additional "Compilations with Artist" box in ContextBrowser. + ADD: Remember collapse-state of boxes in ContextBrowser. (BR 98664) + ADD: Display an error when unable to connect to MySQL. + ADD: Konqueror Sidebar now has full drag and drop support. + CHG: Replaced "Blue Wolf" icon with Nenad Grujicic's amaroK 1.1 + icon, due to legal issues. + ADD: Parameter "%score" shows the current song's score in OSD. + CHG: When you delete a song within amaroK, it gets removed from + the Collection automatically. + FIX: Directory column in the playlist was eating the first letter. + ADD: New DCOP call "playlist: setStopAfterCurrent(bool)". (BR 99944) + FIX: Coverfetcher: Do not crash when no cover was found. (BR 99942) + ADD: Support for amazon.co.jp cover fetching + CHG: Toolbar items reordered for optimal usability, as suggested by + Aaron "Tom Green" Seigo. + FIX: Show covers for albums containing chars '#' or '?'. (BR 96971 99780) + ADD: Help file for the playlist2html script. + ADD: New DCOP call "playlist: int getActiveIndex()". + ADD: New DCOP call "playlist: playByIndex(int)". + CHG: Upgraded internal SQLite database to 3.1.3. + FIX: Update the database after editing tags in playlist. (BR 99593) + ADD: New DCOP function "player: trackPlayCounter". (BR 99575) + ADD: .ram playlist support with code from Kaffeine. (BR 96101) + FIX: amaroK can now determine the correct track-length even for formats + unknown to TagLib. Makes it possible to seek e.g. in m4a tracks. + ADD: Can now pick from multiple Musicbrainz results. Patch from + Jonathan Halcrow . (BR 89701) + ADD: May now set a custom cover on multiple albums in the Cover-Manager. + ADD: Support relative path of tracks in writing playlists. (BR 91053) + FIX: Don't inline-edit tags for the whole playlist's selection. + FIX: Fix "Recode Tags" crash issues. (BR 95041) + ADD: "Set Custom Cover" can fetch remote images. (BR 90499) + +VERSION 1.2: + ADD: "Repeat Track" status is reflected by an icon in the playlist. + ADD: New icons from tightcode for statusbar and repeatTrack. + ADD: New Smart-Playlist "Ever Played". + CHG: Bumped GStreamer version requirement to 0.8.4. + CHG: Made it possible to use artsdsink with GStreamer again. + CHG: Don't read m3u files recursively when dropping a folder on the + playlist. No more doubled entries. + FIX: Shoutcast radio with GStreamer is improved, no more dropouts when + starting a stream. + ADD: The "Similar Artists" feature (using Audioscrobbler) can now be + switched off. (BR 95280) + FIX: Error in Shoutcast http-request, which made it impossible to play + many radio streams with GStreamer and aRts. (BR 97211, 98569) + CHG: Better default directory for selecting a custom cover. + FIX: ContextBrowser reloads after setting a custom cover. (BR 96548) + FIX: Cover-Manager's full-screen view works with Bughira (brushed metal). + ADD: Script-Manager can auto-run scripts on application startup. + ADD: aKode engine, depends on KDE 3.4. No configure check yet. + FIX: Don't add non-audio files to the Collection. + CHG: We now use the SqlLoader, which greatly improves the performance of + adding stuff to the playlist from SmartPlaylists and the Collection. + +VERSION 1.2-beta4: + ADD: It is now possible to select the right image if there are multiple + results from Amazon. Patch from Gregory Isabelli . + (BR 93287) + CHG: Reorganized the DCOP interface. We used to have all DCOP functions in the + "player" group. Now it's splitted up into several categories. Attention + script writers: Adjust your DCOP calls! + FIX: The loader is now more robust and should always find amarokapp. + CHG: The search-browser has been integrated into the file-browser. + CHG: OSD can have fake transparency and new fancy shadow. + ADD: DCOP function "shortStatusMessage", shows a temporary message on the + application's statusbar. + FIX: Frequent crashes when writing tags. (BR 95344) + FIX: CoverManager updates its status display correctly. + FIX: "isPlaying" DCOP function now works correctly. (BR 90894) + ADD: Automatic crash report generator, sends backtraces to amaroK HQ. + ADD: DCOP function "saveCurrentPlaylist". Writes the playlist to current.xml, + for scripts that need to access the playlist contents. + ADD: Playlist2html, a script for playlist exporting. (BR 96199) + ADD: Improved statusbar, with animated error notification widget. + ADD: New progress display system, can show multiple expandable progress + widgets in the statusbar. + ADD: Alarm script, starts playing music at specified alarm time. + ADD: Script-Manager for DCOP script extensions is now functional. Refer to the + amaroK Wiki for information on script writing. + ADD: Collection-Browser shows a help message in flat-mode when filter is + empty. (BR 97000) + CHG: It is possible to select the Database Engine (SQLite, MySQL) runtime, + without amaroK restart. New Database Engines can be added, they need to + inherit DbConnection and implement its' virtual methods (see + SqliteConnection and MySqlConnection). + CHG: New amaroK icon "Blue Wolf", made by Da-Flow. + FIX: Possible crash when enabling Player-Window. (BR 94668) + +VERSION 1.2-beta3: + ADD: Smart Playlists can have a random order or a score weighted random order + (BR 90861) + ADD: Show total length of selected songs in statusbar. (BR 90284) + ADD: Context-Browser now caches the tab widgets. Patch from Matias Costa + . (BR 95999) + FIX: RAND and REP buttons were always enabled at startup. (BR 95861) + ADD: Implemented "Append Suggestions" functionality. It means that when + enabled, amaroK will append a couple of suggested songs to playlist when + you play a track. This produces a continuous playlist, something similar + to listening to radio. + ADD: Implemented "Play Media..." functionality. + FIX: Playlist-Browser was appending to playlist when clicking "Load". Now it + replaces the current playlist again, as intended. + ADD: Profile for KDELIRC (Remote Controls). Patch by Dirk Ziegelmeier + . + ADD: Remove Duplicates now also removes dead entries from playlist. + FIX: Accept album-dragging from the ContextBrowser. (BR 86020) + FIX: Configure check was missing for the Konqueror Sidebar (depends on + KDE-Base). + FIX: Browser splitter was drawn incorrectly with some styles. (BR 95333) + ADD: DCOP call for relative seek. Patch by Andreas Pfaller. (BR 84989) + CHG: Bumped TagLib dependency to 1.3.1. (1.3 is too damn buggy) + FIX: CTRL-M can show the menubar again after hiding. (BR 94139) + ADD: Support for last.fm streams. + FIX: amaroK icon shows correctly in window decoration under GNOME. + ADD: Support for ID3v2 cover images. (Thanks to M. Thiesen!) (BR 88492) + ADD: DCOP calls for the status of Random Mode, Repeat Playlist and Repeat + Track. + ADD: DCOP call to return the sample rate. + ADD: DCOP call to return the track number. (BR 94825) + FIX: GStreamer-engine provides better scope synchronisation. + ADD: Save current track position and play queue on exit. (BR 90379) + FIX: Fix Directory column on playlist, show absolute directory path instead of + empty string. (BR 90361) + ADD: DCOP call to scan your collection. (BR 84621) + FIX: When an engine fails to load, respect the rank while choosing the next + engine. + +VERSION 1.2-beta2: + FIX: Classic amaroK theme looks better. + ADD: Context Browser has CSS styling. + FIX: Cover fetching improvements/fixes. + ADD: Last played: yesterday, etc. in ContextBrowser. + FIX: Big speedup for PlaylistLoader, when adding many items. + ADD: Show songs you once played, but didn't play for the longest time on + ContextBrowser's Home-page. (least played) (BR 89479) + FIX: Don't crash on song switch, when there's only one visible playlist item + and repeat-list is activated. (BR 94030) + CHG: Add and queue tracks after the current track. (BR 94121) + ADD: DCOP call to raise the equalizer configuration dialog. + ADD: Konqueror sidebar to view playing info and control amaroK. + ADD: DCOP call to clear the playlist. (BR 90149) + ADD: DCOP call to enable/disable the equalizer. + ADD: DCOP call to return the score of the currently playing track. + ADD: Audioscrobbler submit queue stored on disk. Tracks that are listened when + offline will be available for submitting later. + CHG: "Start Scan" button was renamed to "Update". Now it starts an incremental + scan instead of a full rescan. + FIX: Lyrics parsing failed for certain songs. (BR 94269) + ADD: xine-engine saves config, and implements crossfade, bug fixed too. + ADD: Player-Window can also show the BlockAnalyzer. + CHG: Run incremental scanning once a minute instead of every 30 seconds. + FIX: When collection scanning was interrupted with Cancel, incremental + scanning didn't work any longer. + CHG: Handle incremental file scanning in a thread. Now the GUI doesn't get + blocked every 30 seconds, anymore. (BR 93564) + ADD: CollectionBrowser now offers two operation modes: + The classical TreeView and a new FlatView (like the WinAmp Library). + FIX: Caching of local cover images was broken for non-unique filenames. + (BR 94068) + FIX: "Visualizations" menu entry was always disabled. + FIX: Play button was sometimes stuck in disabled state. + FIX: OSD was showing "%artist - %track" instead of "%artist - %title". + FIX: Forward command line option --engine to amarokapp. + FIX: CoverFetcher was always looking for "album - album". + +VERSION 1.2-beta1: + ADD: Full support for Audioscrobbler, including submission of tracks. + FIX: Arts engine resumes from position when session is restored. + ADD: Vorbis stream metadata support (GStreamer-engine). (BR 82378) + ADD: Cover image and lyric fetchers include filters for common extensions, + such as (Disc 1). (BR 90630) + ADD: Ability to choose from four different Amazon locales. (BR 90664) + ADD: OSD now draws gradient instead of solid colour. + ADD: 'Stop after current song' functionality. (BR 88652) + FIX: Queue function from context/collection browsers actually properly queues + tracks. (BR 90319) + ADD: MySQL database support. Patch by Andreas Mair . + Please refer to mailing list for detailed instructions. + ADD: Metadata history for streams in Context-Browser. (BR 89839) + ADD: Command line option --engine. + ADD: OSD text is now configurable, and it displays the album cover. + FIX: Remote folders are read recursively when dropped on the playlist. + FIX: Audiocd protocol in filebrowser had empty folders. + ADD: Cache system for current-track animation in playlist. Reduces CPU load + when the playlist is visible. + ADD: 10-band IIR equalizer for GStreamer and xine engines. + FIX: The background gradient effect in Context-Browser is now much faster. The + gradient also looks nicer. (BR 91276) + FIX: Password-protected streams did not work correctly. (BR 91184). Patch by + . + ADD: NMM-engine was rewritten and updated for the latest NMM release. Supports + audio and video playback. + ADD: Cover-Manager supports drag-and-drop. + ADD: Tags are now read from the Collection database if they are already + stored. This speeds up adding items to the playlist. (BR 90137) + ADD: Context-browser shows "Suggested Tracks", utilizing audioscrobbler. + FIX: Configure does no longer print "Good - Configure has finished" when a + dependency is missing. + ADD: Intelligent automatic resize for playlist columns + ADD: Shaded current-track marker in playlist. + ADD: Automatic song lyrics display. + CHG: Internal SQLite upgraded to 3.0.8. + +VERSION 1.1.1: + FIX: Crash when using GStreamer-engine on 64bit. (BR 90869) + CHG: New splash screen by Nenad Grujicic . + FIX: Crash when fetching 1 missing cover using the fetch button. (BR 90673) + REM: Unsupported option "Show Metadata in Playlist". + ADD: Menubar (optional). + FIX: GStreamer-engine now resumes playback at correct position. + ADD: iCandy for Context-Browser: Background gradient and toolbar. + CHG: Collection-Browser now has a toolbar instead of menubar. + FIX: With "Title Streaming" disabled GStreamer could not play streams. + FIX: Osssink is now the default sink for GStreamer. If sink initialization + fails, a dialog will ask to select another sink. + FIX: Pausing failed on some systems with GStreamer-engine. (BR 90417) + FIX: Never scan the same directory twice. + FIX: Disable CD-burning menu for streams. (BR 90336) + ADD: Open Cover-Manager from Context-Browser popup-menu and main menu. + FIX: Made amaroK build with --disable-amazon flag. + FIX: Docs translations were not installed correctly. (BR 90307) + FIX: GStreamer-engine refused to play some mp3 files. (BR 90317) + +VERSION 1.1: + FIX: Huge speedup for Context-Browser, makes changing tracks faster. + ADD: Progress display for Cover-Manager. + CHG: Systray animation is now optional. + CHG: Updated included sqlite to 3.0.7 (stable). + ADD: Tag editor can operate on multiple files (mass tagging). + FIX: Collection encoding broken for non-latin1 characters. (BR 89747) + ADD: Popup-menu for cover images in Context-Browser. + FIX: The first track to play is now random for random-mode. (BR 77055) + FIX: Show systray on startup. (BR 89661) + FIX: Let xine recognise tracks that have non lower-case extensions. + +VERSION 1.1-beta2: + ADD: K3B integration for burning CDs. (BR 88052) + ADD: Third category for Collection-Browser. (BR 83609) + ADD: Playlist search now supports categories. (BR 86296) + ADD: Support for MAS (Media Application Server). MAS-engine + is in experimental state. + ADD: Context-Browser shows information about radio streams. + ADD: Custom Smart Playlists with built-in editor. + ADD: Systray icon shows track progress and play status. + CHG: Imported SQLite3 and ported CollectionDB. + ADD: "Cool-Streams", a list of amaroK Squad recommended streams for + playlist-browser. + ADD: Detecting Sampler/VA discs in CollectionBrowser (shown as + "Various Artists"). (BR 81683) + ADD: Configuration GUI for xine-engine. + ADD: Next and previous track buttons for Tag-Editor. + ADD: Player-window adapts to current color scheme. + ADD: Crossfading and fade-in/out function for GStreamer-Engine. + ADD: Genre and Favorite Tracks by Artist smart playlist in the + Playlist-Browser. + ADD: IMMS-like rating system for songs. + FIX: aRts-engine has been ported to the new engine interface and is + available again (but not recommended). + FIX: Try to autodetect Sampler-Discs and show them properly in the + Contextbrowser. (BR 87182) + FIX: Multiple items can now be selected in the CoverManager. + Thanks John Hughes (BR 87584) + FIX: Various fixes for certain Artist/Album names, which had problems + with cover support. + FIX: Sorting the collection is now case-insensitive. (BR 84141) + CHG: Symlink infinite recursion check for collection scan. + FIX: Show all accessible cover images in the tooltip. (BR 87283) + FIX: Clicking an album in the ContextBrowser adds items in the correct + order, now. (BR 87733) + +VERSION 1.1-beta1: + ADD: Wizard for configuring amaroK on first startup. + CHG: Made it possible to use the next/previous buttons when amaroK is + not playing. + ADD: DCOP call to switch Random Mode on or off. (BR 84460) + ADD: DCOP call to retrieve current track's cover image. (BR 85364) + FIX: Problem with cover-saving for certain artist/album names. (BR 84171) + FIX: Show contextual information for songs, even if they are not in the + current collection instead of an ugly empty box. + ADD: GstEngine: Support for custom output plugin parameters. (BR 83949) + ADD: CoverManager - for downloading and managing album cover images. + CHG: Refactored engine plugin interface. Each engine can now provide specific + configuration GUIs. + ADD: As-you-type search for FileBrowser. + ADD: Seeking with mousewheel in playerwindow. + REM: Stream-Browser. + ADD: New meta-info dialog, with editable tags and MusicBrainz support. + ADD: Inline-tag editing auto-completion based on the Collection Database. + ADD: Deleting files physically from playlist context menu. (BR 75208) + ADD: Fadeouts for GStreamer-Engine. + ADD: New Playlist Browser, organizes multiple playlists, and offers smart + playlist functionality. + ADD: Support for redirected streams and streams with no specified port. + ADD: KIO support for GStreamer engine. Allows playing media via all + protocols supported by KIO (ftp, audiocd, fish, etc). + ADD: SearchBrowser operation can now be aborted. + ADD: Progressbar in CollectionBrowser informs about scan progress, and a + button was added for aborting the scan. (BR 83019) + ADD: Playlist sliders (volume and position) now move directly when clicked + outside of the handle. (BR 83611) + ADD: Untagged tracks now go into Collection too, listed as "unknown". + ADD: Automatic album cover fetching is back and improved. + ADD: Option for automatically switching to Context when playback is started. + CHG: Stream timeout value is now determined from KDE user settings. + ADD: Support for password-protected streams, by wef . + FIX: GStreamer engine must not allow non-audio filetypes in playlist. + ADD: Icon for "Menu" button in toolbar. Improves Usability. + +VERSION 1.0.2: + ADD: xine-engine plugin, audio only. + FIX: aRts-engine: Compatibility with newer aRts versions improved. + FIX: aRts-engine: Streams sometimes stopping shortly after playback was + started. (BR 84417) + CHG: Increased stream connect timeout to 12 seconds. + +VERSION 1.0.1: + FIX: Short dropouts after starting a stream with GStreamer. + FIX: amaroK starting invisible when systray icon is disabled. + FIX: Playlist analyzer looks freaky on some systems. (BR 83671) + FIX: Display filename in title column for wav files. (BR 83650) + FIX: Don't show crash dialog when no engine plugins are found. + FIX: Compile issue for KDE < 3.2.1 users. Sorry :( + +VERSION 1.0: + FIX: Plugin versions are validated. Prevents crashes with ancient plugins. + FIX: Configure now checks for gtk/gdk headers for the XMMSwrapper. + REM: Removed cover download feature for this release. + FIX: Do not crash if an unreadable dir is added to the collection. + FIX: Check database-sanity on startup and recreate broken tables (BR 83205). + FIX: CollectionBrowser was broken, when amaroK was running "localized". + FIX: TitleProxy hogging 100% CPU when unable to connect to server. + CHG: Bumped GStreamer requirement to 0.8.1. + ADD: Glowing player window icons. + ADD: amaroK finally remembers if it was hidden on exit. + ADD: OSDPreview now has snap to regions. + FIX: Newly shown columns in playlist can now be resized. + FIX: BR 82020: next/prev buttons disabled when they shouldn't be. + ADD: ToolbarAnalyzer remembers it's framerate, allowed fps: {50, 40, 30, 20}. + ADD: Full streaming audio support for GStreamer engine. + FIX: Don't allow user to get into a situation where there is no Menu. + ADD: Using Welcome-page power-links you can switch between XMMS and amaroK mode. + CHG: New icons and splash screen, by Roman Becker . + ADD: Allow the current GL analyzer to be detached/attached from the + main window with the 'd' key. + FIX: Filtering the collection now searches the second category, too (BR 81681). + FIX: Filter in playlist was only working for the first argument. + CHG: Collection-Monitor now processes removed dirs in a thread. + ADD: Added a switch to toggle OSD's text-shadow. (BR 82011). + ADD: More detailed track information dialog for Collection Browser. + FIX: Track length was always 0 for certain filetypes (e.g. mod, wav) (BR 82673). + FIX: Gst engine refusing to add certain filetypes to the playlist, when + the engine was idle (BR 82713). + FIX: Rare playlist redraw bug, which resulted in messed up items. + +VERSION 1.0-beta4: + ADD: CollectionDB now caches and rescales images. This binds cover art usage + in amaroK to the collection, but offers greatly improved speed for cover + retrieval and uses less memory. + FIX: Cover not shown in ContextBrowser, when song gets played for the first + time ever (BR 81241). + ADD: Cover art fetcher, downloads album cover images from amazon.com. + ADD: Configure->Playback->Device && default device option for audiosinks. + ADD: ContextBrowser now also shows your overall-favorites and the newest tracks + in your collection. Therefor I had to reset the statistics, sorry. + FIX: Decode %-encoded characters in filenames, like %2f for a slash. (BR 74576). + CHG: Songs you click in ContextBrowser will now directly start to play and won't + be added to the playlist, if they already are there. + FIX: "Start Scan" menu-entry gets disabled while scanning. (BR 81619). + FIX: Directories with non-ascii chars don't get scanned (CB) in multibyte locales. + CHG: Enhanced "Fill-Down" feature for track column (auto-increment) (BR 81194). + FIX: Closing xmms-visualizations freezes amaroK (BR 81326). + FIX: CollectionBrowser does not sort by tracknumber (BR 79600). + FIX: ContextBrowser's URLRequests need to be escaped. + FIX: Always show OSD (if enabled) on volume changes. + FIX: Filtering the collection using tokens with number(s) at the beginning + or end failed. (BR 81621). + FIX: FileBrowser didn't remember its current folder (BR 81816). + ADD: Expand/collapse items by doubleclicking in Collection (BR 81710). + FIX: Allow OSD still to be shown via shortcut when disabled (BR 80388). + FIX: Collection: live-monitoring dirs for changes works again. + FIX: Changing volume by mousewheel on systray icon works again. + ADD: Collection automatically rescans itself on startup. + ADD: "Add to Playlist" feature in CollectionBrowser, appends tracks to playlist. + ADD: Clear button for CollectionBrowser search. + FIX: Problem with invisible "Play next" marker in playlist. + FIX: Don't try to create sql-tables on every startup, but only on + sql-scheme (DATABASE_VERSION) changes. + FIX: Display splash screen on correct desktop with Xinerama. + CHG: CollectionBrowser filter now works in "search-as-you-type" mode. + FIX: Prevent TitleProxy from showing the same metadata over and over. + FIX: Compatibility bugfixes to TitleProxy, thanks to Daniel Molkentin + . I think we've now got 100% Shoutcast compatibility. + ADD: Allow changing volume by using the mousewheel anywhere on the toolbar. + FIX: Wheel-scrolling toolbar's volume slider doesn't change volume (BR 81155). + FIX: ContextBrowser is now shown in proper colors for every scheme. + CHG: Added track's physical location to the Meta Information dialog. + FIX: Show last playtime in localtime instead of UTC. + FIX: ContextBrowser not showing all items for current album. + FIX: Not all SQL queries were "string-escaped". + ADD: Added statistics database, which keeps track of how often and when you play + a specific song. + +VERSION 1.0-beta3: + ADD: Additional volume slider for playlist window. + ADD: ContextBrowser shows you images and information to the current song/artist. + It depends on the collection and is presented as an HTML widget. + CHG: Improved color handling and visual feedback in the GUI. + ADD: Global shortcut for play/pause action, as requested by multimedia-keyboard + users (BR 79541). + CHG: Small player-window can be switched off now. + FIX: CollectionBrowser out of order after scanning. + FIX: TitleProxy partly rewritten. Should be more compatible with many streams + and not be able to freeze the app any longer. + FIX: When playing a stream with title streaming activated, the track is not + marked as playing (BR 79999). + FIX: Invoking "Track Information" in Collection Browser sometimes crashed + the application (BR 80266). + FIX: In CollectionBrowser's folder setup dialog pressing cancel did not abort + (BR 80451). Thanks to Michael Pyne for patch. + ADD: Option for selecting sound output system (OSS/Alsa). Currently only + used with GStreamer engine. + CHG: Extended and updated handbook, thanks to Mike Diehl . + ADD: Context menu item "Make Playlist" in Collection Browser generates new + playlists on the fly, without the need for drag-and-drop. + CHG: Renamed several files and folders in the source code tree, resulting in + improved code accessibility. + +VERSION 1.0-beta2: + FIX: Crash on AMD64 due to assumption about pointer size. + CHG: SQLite library sourcecode now included with amaroK. + CHG: The collection-thread now inserts its data in a temporary database while + scanning, which allows us to safely use the collection in the meantime. + This is done by two concurrent sqlite-connections (thread-safe). Wrote a + new class named CollectionDB, which handles the database communication + for the collection. + ADD: URLDrag from Playlist, so you can drag and drop to xmms. Doesn't work with + the FileBrowser yet, but it will! + CHG: CollectionBrowser now fills the database inside of a thread, resulting in + improved performance. + ADD: Mini track-position slider in statusbar. + FIX: Don't try to crossfade with engines that do not support this feature. + ADD: XMMS visualization plugins can be configured with their GUI. + FIX: Collection filtering had some regressions + FIX: Loader on some systems not able to start amaroK. + FIX: Switching engines at runtime breaking volume control. + FIX: GstEngine skipping tracks directly after starting, when crossfading enabled. + CHG: Database system now works with linked tables. Saves hdd-space and cpu-time. + CHG: If you remove the current song from the playlist, we don't define the next + song anymore, but let it be randomly selected (only when random mode is on!) + CHG: Random Mode now respects the playlist filter and only picks items, which are + currently visible in the playlist. Also removed a crash situation. + CHG: Removed the search-token index. Searching now iterates through the playlist, + offering direct and specific access to the metadata. + FIX: Bug where fill-down would cause lots of extra tags to be written when a search is + in progress (BR 79482). + FIX: Defect in plugin framework code, leading to a crash on some systems + during engine plugin initialization. + FIX: Restoring current playlist on startup (BR 79436, BR 79439). + ADD: Searching the Collection with a filter. + FIX: BrowserWin's QLabels are painted white in amaroK's own color scheme. + +VERSION 1.0-beta1: + ADD: Search Browser - search stuff on your hdd + ADD: song count on playlist statusbar + ADD: support for XMMS visualization plugins + ADD: Collection Browser - a database powered music collection manager + ADD: Playlist toolbar is now configurable + ADD: toolbar analyzer in playlist window + ADD: use XML playlists internally within amaroK so tags don't have to be + loaded/reloaded all the time. Makes undo/redo much quicker. + FIX: non latin1 locale issues with loading directories and tags (thanks Leo Zhu) + ADD: clicking shuffle will sort the playlist by the nextQueue first, and + randomise the rest + ADD: Play Next can now handle several songs through a queue. The queue can be + manipulated by using the context menu or by CTRL+right clicking. + ADD: much improved gstreamer engine, now working with visualizations + CHG: GstEngine requires gstreamer-0.8 + FIX: Show move pointer instead of hand when moving preview OSD. + ADD: sorting by artist subsorts by album and track, sorting by album subsorts + by track, enjoy! + ADD: browserTabs float over the playlist when in set to not overlap + FIX: communication loader<-->amarok failing on FreeBSD + FIX: loader forgetting to close socket descriptors + FIX: FileBrowser remembers that state of its view between sessions + CHG: converted engines to plugins. they are now dynamically loaded at runtime + ADD: plugin framework + CHG: made amaroK aRts-independent. with the --without-arts configure switch + it's possible to build the app without aRts support, using only NMM or GST + ADD: Shift drag appends items to the end of the playlist. + FIX: startup notification icon staying on screen when amaroK started by loader + FIX: amaroK showing the "X" icon instead of the correct one + +VERSION 0.9: + CHG: playlistBrowser removed until next release + FIX: playerWidget font is now configurable, you need to start new track for the + scrolling marquee to get updated. Default font is used by default. + FIX: fixed several stability issues concerning stream-playback + ADD: whatsthis for all configurable options. + FIX: amaroK registering with dcop as "amarok-PID". it's back to just "amarok" now. + FIX: OSD not updating correctly when changing volume + +VERSION 0.9-beta3: + ADD: "Show Current Track" button in playlist. + ADD: Volume OSD when changing with mousewheel over trayicon. + CHG: software volume mixer uses a logarithmic function to make the scale more natural + ADD: Global shortcuts to display OSD and increase/decrease volume. + (Win+o and Win+KP_Add/KP_Subtract by default, respectively) + ADD: DCOP calls to control OSD and playback volume + ADD: ported config-GUI for audio decoders to new engine (works currently with + modplug_artsplugin) + FIX: show correct track-length when playing .mod or .sid with aRts-engine + ADD: loader application, starts and controls amaroK. it reduces the lag when handing + command line arguments to amaroK and makes the splash load faster + ADD: playlist items, which couldn't be opened / read (for some reason) will be marked + with a grey background color + ADD: pasting clipboard selection into playlist with MidButton, X11-style + CHG: refined on-screen-display with more polished look + FIX: skipping broken/non-existant tracks + CHG: If the current song is paused, the Play Button will resume, not restart it. + FIX: respect "hide playlist with main window" and playlist minimize/hide behaviour. + ADD: new OSD configuration options: bgcolor, screen position + +VERSION 0.9-beta2: + CHG: some look-and-feel polishing in the main player window + ADD: option to turn off analyzers + ADD: splash-screen shown during program startup (optional) + FIX: made stream playback with TitleProxy more stable (by using an unbuffered socket) + ADD: show stream metadata in on-screen-display + CHG: transformed "EQ" button into a togglebutton, which can also hide the effect browser + ADD: new OpenGL analyzer, contributed by Enrico Ros + FIX: FreeBSD compile fixes, contributed by Markus Brueffer + FIX: rewritten configure: checks properly for kdemultimedia presence, + and adds --without-opengl and --without-gstreamer arguments + +VERSION 0.9-beta1: + ADD: display warning when artsd is not running with realtime priority + ADD: Audioproperties are loaded as you scroll the playlist and get saved to playlist files + ADD: If trackname column is hidden, the title column will show the trackname until a title + tag can replace it. If no title tag is found the trackname stays. + CHG: Pressing "back" in Random Mode now works as expected and walks backwards + through the list of recently played songs. + ADD: TitleProxy searches for a free local port (contributed by Stefan Gehn) + CHG: Random Mode now stores the recently played songs in a buffer, which prevents + playing the same songs too often. + ADD: "Play Next" context menu option + ADD: selected aRts-effects will be remembered on next program start, including settings + FIX: sort numerical playlist columns in correct order + ADD: logarithmic fading algorithm makes crossfading smoother + ADD: Select a series of tracks, start inline tag-editing a tag and amaroK will prompt you to + edit that tag for all tracks one-by-one. Also available: fill-down. + ADD: improved crossfading: will fade out smoothly when the stop button is pressed + FIX: O(n) behavior for playlist scrolling fixed + ADD: setting to make playlist colours the KDE defaults + ADD: support for tag-editing directly in playlist + CHG: replaced old FileBrowser with the comfortable fileselector from KDevelop + CHG: analyzers now powered by a new, more flexible FFT routine + ADD: hide/show selected playlist columns + CHG: upgrade streambrowser to kderadiostation 0.5 + FIX: many streams not loading from browser and AddItem dialog + CHG: amaroK moved out of kdenonbeta. we are now member of KDE Extra Gear 1 + ADD: on-screen-display (OSD), shows an overlay with information on the currently playing track + CHG: use KMultiTabBar for browser selection + CHG: migrated settings system to KConfig XT + ADD: playlist columns for length and bitrate + ADD: merged new audio engine in. this provides a generic interface class, with multiple + backends. right now there is a backend for aRts and one for GStreamer (still rudimentary) + +==BEGIN KDE 3.2 DEPENDENCY== + +VERSION 0.8.3: + FIX: build issue + +VERSION 0.8.2: + ADD: added Hide/Show Playlist global shortcut (thanks gogo) + CHG: mousewheel over trayicon behaviour changed + CHG: search tokens can now be entered in random order + ("Presley Elvis" will find "Elvis Presley") + FIX: qt 3.1 compile issues + +VERSION 0.8.1: + FIX: compilation problem with KDE < 3.1.3 + +VERSION 0.8.0: + FIX: KDE 3.1 compatibility re-gained + ADD: hitting return in the search field of the playlist starts playback of the + first visible playlist entry (Qt >=3.2 only) + FIX: fixed crash bug in playlist searching + FIX: fixed crash bug when removing playlist-items + CHG: new layout has been adopted + ADD: added accepting files dropped onto systray icon + FIX: significant reduction in memory consumption for PlaylistItems + FIX: hardware mixer works again + CHG: replaced sliders with custom slider class, which fits better in our design + FIX: exchanged c32-app-amarok.png with the correct (active) version + FIX: amarok.desktop file. now we show up in the k-menu again. + FIX: crossfading aRts module. the fading is now much smoother than before + FIX: crossfading bug. before the fix amaroK sometimes mixed up the two xfade sources, + so it sort of faded in reverse (==crap) + ADD: tag reading in separate thread + ADD: re-added m_optCrossFade, so we don't lose the crossfade length on switching it on/off. + set default crossfade length to 2500. + CHG: "Title Streaming" on by default + CHG: integrated streambrowser into playlist window + ADD: added dcop implementation for url adding. Relevant diffs for mediacontrol are + available. + FIX: libamarokarts detection code + ADD: added long-awaited DCOP methods for manipulating the playback. This also adds + integration with kdeaddons/kicker-applets/mediacontrol. + CHG: moved DCOP handler to a separate class/file + ADD: threaded playlist insertion + FIX: removed bugs and waste code keyhandling in browser*, it mostly works as expected + now with various keypresses going to the correct places + FIX: cleaned the playlist class's public interface, also fixed some unreported bugs in + process (inconsistent recursive behavior), please keep the encapsulation, it's a + good thing (tm) + FIX: tweaked undo/redo behavior + CHG: exchanged old player icons with new ones made by + Alper Ayazoglu a.k.a. cubon + ADD: clicking on EQ button activates effect selection widget + ADD: KJanusWidget as a sidebar for filebrowser mode selection + FIX: pushing enter in lineedit goes up a level + ADD: a stream browser, can only DnD, separate window, not great yet + FIX: finally fixed the ancient "annoying-noise-when-pressing-pause" bug + FIX: should keep track of currently played item no matter what you do to the playlist, + has a nice side effect of remembering the last played song, too. + FIX: write undo for Shuffle + FIX: the expandbutton doesn't fire events when it has had its stack expanded + (behaviour a-la Winamp Classic) + FIX: crash when pressing right mouse button while stream is connecting + ADD: show bitrate for streams with icecast support + FIX: save stream names as #EXTINF in m3u files + ADD: bug report dialog + ADD: proxy for decoding shoutcast/icecast metadata (experimental!) + ADD: amaroK now in bugs.kde.org + ADD: configurable delay after each track. currently 0-10 seconds in 1 sec increments + but could easily be made to use finder increments if ppl want - piggz (www.piggz.co.uk) + ADD: viswidgetv2. it seems a lot smoother on my machine. + its quite easy to tweak the dynamics is needed. is accessible the same as the other + widgets, just click until it appears (though it looks the same as the original widget + it just acts differently) - piggz (www.piggz.co.uk) + ADD: combo with history and completion for dir/file chooser + ADD: in configure.in.in for checking the version of TagLib, if compiled from CVS, if not, + then show, that it uses bundled version of TagLib - Stormy + FIX: font dialog sizing issues + ADD: resume playback option. Using this means your track starts up again where you left it + last time you quit amaroK. Excellent feature for us developers :-) + +VERSION 0.7.0: + FIX: collection of fixes related to showing/raising/hiding the playlist + when showing/raising/hiding the mainWidget + FIX: by muesli: make playlist searches a bit faster at the expense of memory + FIX: (partial fix) bitrate/samplerate font overlap at large font sizes + change: less staccato loading of widgets + change: pause makes the analyser bars fall to zero rather than just vanish + ADD: xfade when starting tracks by doubleclick + FIX: global shortcuts can now be changed + FIX: tracks skipping randomly + change: "BrowserWin Enabled" on by default + change: "Save Playlist" on by default + change: "Show Metainfo" on by default + FIX: make loading playlist not block UI + FIX: on startup load playlist after UI is shown + change: "Software Mixer Only" on by default + FIX: make timedisplay also work for streams + FIX: volume slider adjusting + FIX: when dropping tracks to PL, order will stay the same as in FileBrowser + ADD: FileBrowser sortable by clicking on header + ADD: analyzer that distorts a bitmap + ADD: multiple analyzers now possible + ADD: "Software Mixer Only" option + Removed stale sigplay() + Cleaned a couple "deprecated" warnings + ADD: undo and redo playlist actions + FIX: rewritten config dialog and moved into separate file + ADD: started configurable colors + change: spectrum analyser bars now have dynamics, ie. they move smoothly between values + ADD: mouse wheel over systray icon changes the track, hold shift to change the volume + change: rearranged menu order for systray (quit = last) + change: moved volume slider to the right, lets see if this is better + ADD: started a font selection page in settings + FIX: Stream urls are now properly demangled/unescaped (%20 => space etc) + +VERSION 0.6.91: + FIX: ExpandButton submenu now slightly delayed + FIX: dropping items into playlist + ADD: drop-target indicator line in PlaylistWidget, providing visual feedback + ADD: tray menu + ADD: random mode + ADD: crossfading between tracks + ADD: vertical lines between columns in Playlist + ADD: alternating item colors in Playlist + ADD: column "directory" in PlaylistWidget (for Grue:) + ADD: sorting by clicking on column headers in PlaylistWidget + FIX: rewrote directory reading code in BrowserWidget.cpp. + code is now much more readable, and it also fixes a bug. + ADD: additional columns in playlist for tags + FIX: made metainfo reading algorithm faster + change: switched to TagLib for metainfo reading + ADD: button "play" in PlayerWidget.cpp is now a toggleButton + ADD: tray icon + FIX: playlist window is optionally hideable with main widget when iconified to tray + +VERSION 0.6.0: + Release :) + +VERSION 0.6.0-PRE5: + fixed: animated buttons don't get stuck anymore + fixed: invoking help + changed: MetaInfo reading now off by default. the slowdown was potentially + confusing to new users + added: documentation + fixed: cleaned up Makefile.am a bit + fixed: defined new APP_VERSION macro, since the old approach did not work + with CVS + changed: put amarok into KDE CVS (KDENONBETA) + added: applied Stormchaser's button patch. the AmarokButtons now work + in a more standard conform way. Thanks Stormchaser, blessed be :) + +VERSION 0.6.0-PRE4: + added: buttons in playlist window for play, pause, stop, next, prev. + a.k.a. stakker mode :) + removed: "load" button. this functionality is now provided by "Add item" + added: more sanity checks on pointers + fixed: when track in playlist does not exist, we now skip to the next track + fixed: all aRts references are freed correctly at program exit + fixed: effects will not be forgotten any more when EffectWidget is closed + +VERSION 0.6.0-PRE3: + fixed: crash when URLs were dropped onto filebrowser from other apps + fixed: URL dialog now accepts remote files + added: correct caption for ArtsConfigWidget + added: "amaroK Handbook" menu entry, calling KHelpCenter + changed: amarok gets installed into multimedia now + fixed: PlayObject configuration + +VERSION 0.6.0-PRE2: + changed: safety question at program exit now off by default + removed: button "sub" - it was useless + changed: clearing playlist does not stop playing anymore - for Grue ;) + fixed: potential crash at startup + added: menu option to configure PlayObject + fixed: crash when removing currently playing track + +VERSION 0.6.0-PRE1: + fixed: flicker in glowing item + fixed: another memory leak in analyzer (hopefully the last one!) + added: playlist widget can display metainfo instead of filenames + added: repeat track / repeat playlist + +VERSION 0.5.2 - 0.5.2-DEV6: + fixed: memory leak in analyzer code. + added: shortcut for copying current title to the clipboard + added: slider position can be changed by just clicking somewhere on the slider + added: icon + added: url can be entered directly above the filebrowser widget + changed: removed the "jump" widget. you can now enter a filter string + directly above the playlist widget + added: playlists (.m3u and .pls) can now directly be dragged into the playlist + added: support for .pls (audio/x-scpls) + added: amarok is now completely network-transparent. any kind of folder, + local as well as remote, can be browsed and played. + added: check for libamarokarts. amarok won't crash anymore if it's not found + added: the time display now has a mode for showing the remaining time, too + fixed: crash when clearing playlist, after playlist has played till the end. + clearing the playlist stops the playing now. + added: new gfx in playerwidget + fixed: progressbar sometimes not working, zero tracklength + fixed: font of bitrate/frequency display too big on some systems + added: command line options + added: timedisplay is now updated during seeks + added: saving window positions and size on exit + added: due to popular request, I finally changed the behaviour of the "play" + button. it's now possible to start a track on a fresh playlist without + double-clicking an item. + fixed: compile error on GCC 3.3.1 in visQueue.cpp. bugfix by thiago + added: completely rewrote drag-and-drop code. works recursively now (optionally). + plus dragging stuff from other applications into amaroK also works now. + +VERSION 0.5.1: + added a Tip of the Day at startup to explain the user interface a bit + added restarting of artsd on first program start to make sure it registers + the new mcopclasses + fixed possible compile error in viswidget.cpp + amaroK uses much less CPU now than it used to. This was mainly achieved by + using a new FFT-analyzer module, which I took from Noatuns "Winskin"-plugin, + and modified slightly to my needs. Also some other optimizations were made, + which improved the standby performance, when no song is playing. I'm still + not satisfied with overall performance, tho, but it seems that most of the + load is produced by the aRts code itself, so this will rather be difficult + to improve. + fixed crash when "next" or "previous" was pressed without a track + loaded + thanks to valgrind I was able to find and squish some serious bugs, + most of which were related to pointers. to sum it up: pointers are evil. + valgrind is great. + lots of UI-changes in the main widget. uses a background pixmap now, a + custom font and widget for the time-display, and generally looks better + fixed issues with the liquid skin. unfortunately, there seems to be no way + to display pushbuttons correctly with a black background under liquid. so, + until I find a solution for that, the expandbutton widget doesn't look quite + as cool as it used to. maybe I should ask mosfet about this.. + +VERSION 0.50: + renamed 0.15 to 0.50 + +VERSION 0.15: + playing streams now works! *yipeeee* + fixed tons of bugs in aRts playing code. i think i got it right now. + fixed loading and saving of playlists. can cope with all protocols now. + fixed a bug in EffectWidget.cpp, that gave a compile error on some systems. + Converting QString into std::string was not done correctly. Thanks to + Whitehawk Stormchaser for that one :) + changed project name to "amaroK" and built new project-file + +VERSION 0.14 (internal): + implemented use of arts-software-mixing, in case hardware-mixing + (/dev/mixer) doesn't work + fixed crash when play was pressed without selecting a file + changed the direction of the volume-slider. maximum is now at the top + added automatic saving of current playlist on exit + added previous/next track + added two radiobuttons in the playerwidget for toggling the + playlist/equalizer on and off. admitted, the equalizer doesn't yet exist, so + it's just a dummy button :P + added popup-menu for the playerwidget. opens on + right mouse button. this menu finally replaces the ugly menubar. + added some icons (from noatun) for the player-buttons instead of text + added pause function + changed most names in the source to comply with the + (unofficial?) KDE c++ coding standard (using the prefix "m_" for member + attributes and so on). This was real slave-work :/ + cleaned up code in several classes + fixed problem where subwidgets got keyboard focus and were drawn dark with + the liquid style. switched off focus completely, since it's not needed for + this type of application + +VERSION 0.13 (internal): + added cute animated pushbuttons with sub-menus + added saving playlists + added dragging items inside of playlist widget + added forward declarations in header files to reduce compile time + added saving of browserwin/splitter size + rewrote track information widget. used a html table for the text. looks much + nicer now :) + fixed sorting function + fixed jump widget. removed huge memory leaks in the widget + fixed flicker in analyzer widget + tons of bugfixes in playing code. partly rewritten. seems to be much more + stable now + +VERSION 0.12 (internal): + added ChangeLog and TODO + added grid under scope display + added saving of options, like current directory and playlist + added detection of mimetypes + added adjusting volume by mousewheel + added skipping to next track after playing + added loads of sanity/safety checks + bugfixes (tons of) in playlist code, partly rewritten + bugfixes in scope code + + +VERSION 0.1 - 0.11: + internal versions, no changelog + tried no less then 4 different sound interfaces: + mpg123, smpeg, alsaplayer, and finally aRts diff --git a/ConfigureChecks.cmake b/ConfigureChecks.cmake new file mode 100644 index 00000000..7f59dcfb --- /dev/null +++ b/ConfigureChecks.cmake @@ -0,0 +1,34 @@ +include(CheckIncludeFile) +include(CheckIncludeFiles) +include(CheckSymbolExists) +include(CheckFunctionExists) +include(CheckLibraryExists) +include(CheckPrototypeExists) +include(CheckTypeSize) +include(MacroBoolTo01) + +# The FindKDE4.cmake module sets _KDE4_PLATFORM_DEFINITIONS with +# definitions like _GNU_SOURCE that are needed on each platform. +set(CMAKE_REQUIRED_DEFINITIONS ${_KDE4_PLATFORM_DEFINITIONS}) + +#check for libz using the cmake supplied FindZLIB.cmake +macro_bool_to_01(ZLIB_FOUND HAVE_LIBZ) +macro_bool_to_01(JPEG_FOUND HAVE_LIBJPEG) +macro_bool_to_01(PNG_FOUND HAVE_LIBPNG) +macro_bool_to_01(CARBON_FOUND HAVE_CARBON) +macro_bool_to_01(NJB_FOUND HAVE_LIBNJB) +macro_bool_to_01(IFP_FOUND HAVE_IFP) +macro_bool_to_01(LIBVISUAL_FOUND HAVE_LIBVISUAL) +macro_bool_to_01(MTP_FOUND HAVE_MTP) + +#now check for dlfcn.h using the cmake supplied CHECK_include_FILE() macro +# If definitions like -D_GNU_SOURCE are needed for these checks they +# should be added to _KDE4_PLATFORM_DEFINITIONS when it is originally +# defined outside this file. Here we include these definitions in +# CMAKE_REQUIRED_DEFINITIONS so they will be included in the build of +# checks below. +set(CMAKE_REQUIRED_DEFINITIONS ${_KDE4_PLATFORM_DEFINITIONS}) +if (WIN32) + set(CMAKE_REQUIRED_LIBRARIES ${KDEWIN32_LIBRARIES} ) + set(CMAKE_REQUIRED_INCLUDES ${KDEWIN32_INCLUDES} ) +endif (WIN32) diff --git a/INSTALL b/INSTALL new file mode 100644 index 00000000..934f158f --- /dev/null +++ b/INSTALL @@ -0,0 +1,185 @@ +Installing Amarok +================= + +In order to compile and install Amarok on your system, type the following in the +base directory of the Amarok distribution: + + + % ./configure --prefix=`kde-config --prefix` + % make + % make install + + +Note: --enable-final is not supported. + + +The GNU installation instructions follow. + + +Basic Installation +================== + + These are generic installation instructions. + + The `configure' shell script attempts to guess correct values for +various system-dependent variables used during compilation. It uses +those values to create a `Makefile' in each directory of the package. +It may also create one or more `.h' files containing system-dependent +definitions. Finally, it creates a shell script `config.status' that +you can run in the future to recreate the current configuration, a file +`config.cache' that saves the results of its tests to speed up +reconfiguring, and a file `config.log' containing compiler output +(useful mainly for debugging `configure'). + + If you need to do unusual things to compile the package, please try +to figure out how `configure' could check whether to do them, and mail +diffs or instructions to the address given in the `README' so they can +be considered for the next release. If at some point `config.cache' +contains results you don't want to keep, you may remove or edit it. + + The file `configure.in' is used to create `configure' by a program +called `autoconf'. You only need `configure.in' if you want to change +it or regenerate `configure' using a newer version of `autoconf'. + +The simplest way to compile this package is: + + 1. `cd' to the directory containing the package's source code and type + `./configure' to configure the package for your system. If you're + using `csh' on an old version of System V, you might need to type + `sh ./configure' instead to prevent `csh' from trying to execute + `configure' itself. + + Running `configure' takes a while. While running, it prints some + messages telling which features it is checking for. + + 2. Type `make' to compile the package. + + 3. Type `make install' to install the programs and any data files and + documentation. + + 4. You can remove the program binaries and object files from the + source code directory by typing `make clean'. + +Compilers and Options +===================== + + Some systems require unusual options for compilation or linking that +the `configure' script does not know about. You can give `configure' +initial values for variables by setting them in the environment. Using +a Bourne-compatible shell, you can do that on the command line like +this: + CC=c89 CFLAGS=-O2 LIBS=-lposix ./configure + +Or on systems that have the `env' program, you can do it like this: + env CPPFLAGS=-I/usr/local/include LDFLAGS=-s ./configure + +Compiling For Multiple Architectures +==================================== + + You can compile the package for more than one kind of computer at the +same time, by placing the object files for each architecture in their +own directory. To do this, you must use a version of `make' that +supports the `VPATH' variable, such as GNU `make'. `cd' to the +directory where you want the object files and executables to go and run +the `configure' script. `configure' automatically checks for the +source code in the directory that `configure' is in and in `..'. + + If you have to use a `make' that does not supports the `VPATH' +variable, you have to compile the package for one architecture at a time +in the source code directory. After you have installed the package for +one architecture, use `make distclean' before reconfiguring for another +architecture. + +Installation Names +================== + + By default, `make install' will install the package's files in +`/usr/local/bin', `/usr/local/man', etc. You can specify an +installation prefix other than `/usr/local' by giving `configure' the +option `--prefix=PATH'. + + You can specify separate installation prefixes for +architecture-specific files and architecture-independent files. If you +give `configure' the option `--exec-prefix=PATH', the package will use +PATH as the prefix for installing programs and libraries. +Documentation and other data files will still use the regular prefix. + + If the package supports it, you can cause programs to be installed +with an extra prefix or suffix on their names by giving `configure' the +option `--program-prefix=PREFIX' or `--program-suffix=SUFFIX'. + +Optional Features +================= + + Some packages pay attention to `--enable-FEATURE' options to +`configure', where FEATURE indicates an optional part of the package. +They may also pay attention to `--with-PACKAGE' options, where PACKAGE +is something like `gnu-as' or `x' (for the X Window System). The +`README' should mention any `--enable-' and `--with-' options that the +package recognizes. + + For packages that use the X Window System, `configure' can usually +find the X include and library files automatically, but if it doesn't, +you can use the `configure' options `--x-includes=DIR' and +`--x-libraries=DIR' to specify their locations. + +Specifying the System Type +========================== + + There may be some features `configure' can not figure out +automatically, but needs to determine by the type of host the package +will run on. Usually `configure' can figure that out, but if it prints +a message saying it can not guess the host type, give it the +`--host=TYPE' option. TYPE can either be a short name for the system +type, such as `sun4', or a canonical name with three fields: + CPU-COMPANY-SYSTEM + +See the file `config.sub' for the possible values of each field. If +`config.sub' isn't included in this package, then this package doesn't +need to know the host type. + + If you are building compiler tools for cross-compiling, you can also +use the `--target=TYPE' option to select the type of system they will +produce code for and the `--build=TYPE' option to select the type of +system on which you are compiling the package. + +Sharing Defaults +================ + + If you want to set default values for `configure' scripts to share, +you can create a site shell script called `config.site' that gives +default values for variables like `CC', `cache_file', and `prefix'. +`configure' looks for `PREFIX/share/config.site' if it exists, then +`PREFIX/etc/config.site' if it exists. Or, you can set the +`CONFIG_SITE' environment variable to the location of the site script. +A warning: not all `configure' scripts look for a site script. + +Operation Controls +================== + + `configure' recognizes the following options to control how it +operates. + +`--cache-file=FILE' + Use and save the results of the tests in FILE instead of + `./config.cache'. Set FILE to `/dev/null' to disable caching, for + debugging `configure'. + +`--help' + Print a summary of the options to `configure', and exit. + +`--quiet' +`--silent' +`-q' + Do not print messages saying which checks are being made. + +`--srcdir=DIR' + Look for the package's source code in directory DIR. Usually + `configure' can determine that directory automatically. + +`--version' + Print the version of Autoconf used to generate the `configure' + script, and exit. + +`configure' also accepts some other, not widely useful, options. + diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 00000000..ef742941 --- /dev/null +++ b/Makefile.am @@ -0,0 +1,5 @@ +AUTOMAKE_OPTIONS = foreign 1.5 + +include admin/deps.am +include admin/Doxyfile.am +SUBDIRS=$(TOPSUBDIRS) diff --git a/Makefile.am.in b/Makefile.am.in new file mode 100644 index 00000000..49558690 --- /dev/null +++ b/Makefile.am.in @@ -0,0 +1,4 @@ +AUTOMAKE_OPTIONS = foreign 1.5 + +include admin/deps.am +include admin/Doxyfile.am diff --git a/Makefile.cvs b/Makefile.cvs new file mode 100644 index 00000000..be59a869 --- /dev/null +++ b/Makefile.cvs @@ -0,0 +1,14 @@ +all: + @echo "This Makefile is only for the CVS repository" + @echo "This will be deleted before making the distribution" + @echo "" + @if test ! -d admin; then \ + echo "Please recheckout this module!" ;\ + echo "for cvs: use checkout once and after that update again" ;\ + echo "for cvsup: checkout kde-common from cvsup and" ;\ + echo " link kde-common/admin to ./admin" ;\ + exit 1 ;\ + fi + $(MAKE) -f admin/Makefile.common cvs + +.SILENT: diff --git a/README b/README new file mode 100644 index 00000000..6d66e381 --- /dev/null +++ b/README @@ -0,0 +1,223 @@ + + Amarok - the audio player for KDE +=================================== + +There are many media players around these days, true. What's missing from most +players is a user interface that doesn't get in the way of the user. How many +buttons do you have to press for simply adding some new tracks to the playlist? +Amarok tries to be a little different, providing a simple drag and drop +interface that really makes playlist handling easy. + + + FEATURES +========== + + * Quick and simple drag and drop playlist creation + * Music library (built-in SQLite, MySQL, or PostgreSQL) + * Multiple backends supported (xine, Helix, and NMM) + * Plays all audio formats known to man + * 10 band equalizer + * Automatic cover art download using Amazon services + * The unique and powerful stylable context browser + * Automatic play-statistics generation (iRate style) + * Full lyrics download + * Learn about your music with integrated Wikipedia + * Full last.fm support + * Visualisations with libvisual + * Crossfading and gapless playback + * Fully configurable OSD for track changes + * K3B (CD-burning) integration + * Podcast support + * Access to iPod, iRiver IFP, USB Mass Storage and many other devices + * Powerful scripting interface + * Complete DCOP Access + * KDE integration + * Preview and buy albums from the Magnatune.com music store +-------------------------------------------------------------------------------- + + + DEPENDENCIES +============== + +Required + * KDE-Libs 3.3 (or newer) + http://www.kde.org + + * TagLib 1.4 (or newer) + (metadata tagging library) + http://freshmeat.net/projects/taglib + + * Ruby 1.8 + (programming language, used for scoring, lyrics, last.fm streams) + http://www.ruby-lang.org + + * One of the multimedia frameworks listed below: + +Recommended + * xine-lib 1.0.2 (or newer) + Note: xine-lib 1.1.1 is required for gapless playback. + (multimedia framework) + http://xinehq.de/ + +Optional + * RealPlayer 10 or HelixPlayer 1.0 + (multimedia framework) + http://www.real.com + (Note: only HelixPlayer is exactly RealPlayer without MP3 support) + + * KDE-Base 3.3 (or newer) + (needed for Konqueror Sidebar) + http://www.kde.org + + * MySQL 4 or 5 + (faster database support) + http://www.mysql.com + + * PostgreSQL 7.4 + (faster database support) + http://www.postgresql.org + + * OpenGL accelerated X-Server + (visualization rendering) + + * Libvisual 0.4.0 + SDL 1.2 + (visualization framework) + http://localhost.nl/~synap/libvisual/ + http://www.libsdl.org + + * ProjectM 0.96 (or newer) + (visualization plugins for Libvisual or XMMS) + http://xmms-projectm.sourceforge.net/ + + * libtunepimp 0.3 (or newer) + (automatic tagging support) + http://www.musicbrainz.org/ + + * K3B 0.11 (or newer) + (CD burning support) + http://www.k3b.org + + * libgpod 0.4.2 (or newer) + (iPod support) + Note: libgpod 0.6.0 is required for the newest Apple iPods. + http://www.gtkpod.org/libgpod.html + + * libifp 1.0.0.2 + (iRiver iFP support) + http://ifp-driver.sourceforge.net/libifp/ + + * libmp4v2 (mpeg4ip 1.5 is recommended, faad2 is less reliable) + (MP4/AAC tag reading & writing) + http://www.sf.net/projects/mpeg4ip + http://www.audiocoding.com + + * libnjb 2.2.4 (older versions may work) + (NJB mediadevice (Creative Nomad/Zen family, Dell DJ devices) + http://www.sf.net/projects/libnjb + + * libmtp 0.1.1 (or newer) + (MTP media device support AKA PlaysForSure) + http://libmtp.sourceforge.net/ + + * libkarma 0.0.5 && OMFS 0.6.1 + (Rio Karma support via USB) + http://freakysoft.de/html/libkarma/ && http://linux-karma.sf.net/ + +Please note, if compiling from source you must also install the devel versions +of these packages. +-------------------------------------------------------------------------------- + + + IMPORTANT INSTALL INSTRUCTIONS +================================ + +In order to compile and install Amarok on your system, type the following in the +base directory of the Amarok distribution: + + + % ./configure --prefix=`kde-config --prefix` + % make + % make install + + +Note: --enable-final is not guaranteed to work + + +Packages for popular distributions are available at http://amarok.kde.org +-------------------------------------------------------------------------------- + + + INSTALLATION-FAQ +================== + +Q: Can I improve Amarok's startup time? +A: Prelinking Amarok has spectacular results; however if you have binary openGL + drivers (eg Nvidia drivers), you will need to compile Amarok --without-opengl + in order to get the amarokapp binary to prelink (the amarok binary is not + important here). +-------------------------------------------------------------------------------- + + + OTHER-FAQS +============ + +For answers to problems like "Amarok won't play any MP3s!" and "My MP3s skip +and stutter!" please visit: + + http://amarok.kde.org/ +-------------------------------------------------------------------------------- + + + INFORMATION FOR PACKAGERS +=========================== + +For Amarok packages we suggest you build: + + % ./configure --disable-debug + +It is possible to build Amarok to use MySQL as the database backend. Using +MySQL makes the Amarok collection faster. + +We suggest compiling Os, there is no particular part of Amarok that would +benefit from optimisation, so the smallest binary is probably the best route. + +In order to limit the dependencies the Amarok package demands we suggest +splitting Amarok into the following packages: + + 1. Amarok + one backend + 2. xine-engine + 3. Helix-engine + 4. amarok_libvisual + 5. ipod media device + 6. ifp media device + 7. njb media device + 8. mtp media device + 9. rio karma media device + +Amarok is modular and will be fully functional as long as one of 2 or 3 is +also installed. Hence we suggest Amarok + one backend. Feel free to include the +helix, MAS and NMM engines if you can satisfy their dependencies. + +Amarok ships with two binaries: amarok and amarokapp. The amarok binary is a +wrapper designed to speed up command line argument passing. amarokapp is the +real Amarok. + +If you make packages for Amarok please let us know and we'll link to you on the +homepage (as long as you don't object). +-------------------------------------------------------------------------------- + + + CONTRIBUTING +============== + +If you wish to contribute to Amarok, you should build it from SVN and subscribe +to the amarok-devel mailing list. The IRC channel is also a place where +it's nice to be, since you can talk to other developers much easier, and +get instant notification of commits to the SVN. For instant email notification +of commits, visit http://commitfilter.kde.org/. +-------------------------------------------------------------------------------- + + +WWW : http://amarok.kde.org +MAIL: amarok@kde.org +IRC : irc.freenode.net - #amarok, #amarok.de, #amarok.es diff --git a/TODO b/TODO new file mode 100644 index 00000000..f03e5158 --- /dev/null +++ b/TODO @@ -0,0 +1,297 @@ +TODO-list for Amarok +====================== + == reported by +-->nick == assigned to + + +SHORT-TERM (URGENT): + When using "Copy files to collection" in the filebrowser, tags wont get recognised from + urls which are organised from media:/ locations. + + When playing a stream from e.g. Ampache, even if an Artist is detected, Wikipedia + will pull up "Artist - Song" instead of just Artist. + + PlaylistBrowser::slotDoubleClicked() and PlaylistBrowser::addSelectedToPlaylist() + should be merged for better code reusage. They are practically the same. + + I just deleted a tag by accident: Clicked on a file in the playlist, pressed + delete to remove the track from the playlist. I had not noticed that it + went into edit mode, though. So I clicked somewhere else, and BAM, the new + empty tag was written. My tag was gone :S IMHO we should require confirmation + before writing, e.g. only write the tag after pressing enter. + + When you start a lastfm stream, the old metadata from the last stream is + shown before the new stream starts. Doesn't look nice. + + update podcast-tuples when the feed get's corrected after a mistake by the podcaster/publisher. eg. pubdate, url, title. + + We need to check for pkg-config when configuring, otherwise we can't check for things + like libgpod. Currently its a silent error, and its very difficult to debug without + trawling through configure.in.in + + Don't reload media device plugins when saving config settings. Its annoying, slow + and all around unnecessary. It also crashes if the device is connected/loaded. + Cannot reproduce anything of this with ipods - i think i fixed that already. + + After doing "Delete downloaded podcast", the icon still indicates downloaded. + + LastFm: + - determine how hard it would be to tag songs + - display history in the playlist somehow I'm against this. + -subitems? + -just disabled? + - 'stop after current track' should work + - when you've never listenend to lastfm before, all stream links on the + website are disabled ("Download the player"). we need to do a handshake on + startup once, and then set a config item, e.g. handshaked=true. + + xine-engine: when "xine could not init any audio driver" happens, always retry + with Autodetect. This seems to be an issue with leftover amarokrc's from 1.3. + + Adding an m3u with non existant items has filesize = 16,777,216.0 TB! + + Refactor code for PlaylistBrowser::removeSelectedItems(). I can see pathological cases + where it won't work. + + Is using the score for the number, but the rating for the vis. in the context browser + confusing? (I think it is, but that might be becuase my theme doesn't use stars but a + partially-filled bar). If so it should be reverted for beta 1, I have a better solution + already planned but that won't be in time. + + The various UI and behaviour refactorings for Dynamic Mode should be finished up and + polished. Among them, add a button to turn it off in the bar-above-the-playlist, fix + drag-and-dropping of dynamic playlists, and when loading a dynamic playlist, clear the + existing playlist first, instead of making it dynamic (which has all sorts of nasty side + effects). + + Podcast Fixme's: + * Have i listened to podcast streamed? + + Loading normal (M3U) playlists from the playlist browser doesn't block the UI per se, + but it does nothing for really long time, until suddenly the whole playlist shows up, + which is strange and confusing. + + Tooltips don't work for 3rd level items in the Playlist Browser. They do how + however work (for me) for 2nd level items. Very bad, cause you can't read the + text! + + When starting a drag in the collection browser, we can block the gui if the user makes a + selection which is sql intensive (eg, select all). Instead of calculating the items + in each of those nodes, we should add the sql to the drag and allow the playlist loader + to do the work for us (which is threaded). + See SqlLoader class in playlistloader. We do the very same stuff for + Smart-Playlists already. + + The unknown album should ALWAYS be shown last. Year has a higher weighting than album + name. We should special case the Unknown album. + + When all 5 browser tabs are activated, the current tab button is drawn a few + pixels below the normal position. This has the side effect that the separator line + at the bottom of the bottom becomes invisible. + You can see the problem easily when you hide the current browser by clicking twice + the button twice. This problem AFAIK also affects 1.3-branch. + + Add DCOP functions for showing dialogs, similar to kdialog. + + More consistent playlistbrowser drag and drop actions; No dropping of default streams, set + e->accept( false ) if dropping one type onto another. + + Use KIO queues for podcast download queues. Should simplify the lot. Problems atm included + selecting multiple downloads individually, and automatic downloads occuring for more that one + podcast item. + + In EngineController::play(), add a sanity check when querying the track length + from the engine. Apparently AAC can deliver ridiculous values with GST and Helix, + like 1192479:38:49. If length is too extreme, default to the TagLib provided + value. + + Currently custom dynamic playlist selection is simply based on the titles. So its less then + ideal. Via some method, each playlist should be assigned iteratively a number paired with its + type.. Dynamic playlists and static playlists should probably have seperate numbering so + that the "next" number can be determined by looking at one XML file. + Remembering which PartyEntry was used on close should be remembered with the same method. + + Smart playlists bypass checks on whether the media is playable or not. + Or perhaps such media shouldn't be in the collection at all? This is the sort of + thing we're going to have to worry about now that we have m4a and wma tag support. + + Consider moving the GHNS provider list to a different server than amarok.kde.org, + possibly download.kde.org or similar. (When our site is down, the GHNS feature fails + to work entirely) + + Amarok fails to download any stream playlist from http://dir.xiph.org. Part of the problem is + PlaylistLoader::isPlaylistFile(const KURL&), which only looks at the filename's extension. This + fails for URLs like http://dir.xiph.org/listen.php?pid=669641&file=listen.m3u, because the filename + is "listen.php". So it must look at the whole url instead. + + On first-ever-run, if Amarok crashes building the sqlite collection, it is impossible to _ever_ + build a collection unless the user's amarokrc file is deleted! This MUST be fixed before 1.2! + ::muesli:: We could just _always_ try to create the sql tables on startup. This would fail on + ::muesli:: systems which already got the tables, but would fix the situation where tables are + ::muesli:: missing, as in this situation. Just pump the debug of these calls to /dev/null. + + Just before we load any engine plugin, write to a file, and it we don't succeed the next time + Amarok starts show a big warning saying "Gah!" and load a different one. + + If you inline set the title tag to "", prettyTitle gets inserted as the title tag. + + When the KDE color scheme is changed while Amarok is running, all toolbar buttons (those with text) + lose their texts. + + The scrobbler code should report some sort of error message rather than just saying that the + submit failed. That way when last.fm goes down we wouldn't get so many people asking why they + can't submit. It'd also be nice to be able to tell people they've got their password wrong. + + When hovering the mouse over the Repeat/Random icons in the bottom taskbar, the popups that appear + not only have inconsistent icons with the ones shown in the taskbar, but they're pixellated and ugly + as sin. Should probably be redone as SVGs. + + Xine proxy value is not saved across sessions. Problem exists in Xine-UI as well, at least for me. + Maybe an upstream bug and nothing we can fix. + +MID-TERM: + Implement a "Find My Music" button in the first-run wizard. Just a basic + thing that will look at file extensions and try to figure out where the users + music is located. Ignore /opt/, /usr etc. to avoid adding sound effects. Prompt + user with the directories at the end of the scan, allowing them to exclude some. + + The FileBrowser doesn't check for recursive symlinks. This is a KDirOperator (or the + classes it depends on) problem. + + Basically need to re-add some of the functionality I removed in re-doing dynamic + --> custom playlist creation. + 1) the default dynamic playlists need to be more insistent, the same way the + the default smart playlists are. + Implement leinir's mockup: + www.leinir.dk/temp/gallery/?image=pictures/amarok-playlist-bar-dynamic-mode.png + The idea with the look of the top is, as you can probably see from the colours + otherwise present, that it's the active window titlebar/colour/font, with the name of the + playlist being un-bolded (if the window title is bold)... + eean: Some people will identify the active window by titlebar colour alone :) + + Album covers as icons from filebrowser. + + When the player window shades, it should lose its title bar and be replaced with something more + compactish. This would probably have to be created manually. + + Make the player window scroll the text in the opposite direction if using right-2-left text + (eg hebrew or arabic) + + Use Metabar's box retraction animation to the context browser. More iCandy. Any Javascript experts around? + + Should be able to open collection browser by right clicking on stuff in contextB, so show the + artist or album that is right clicked. + + AudioCD support for gst engine (cdparanoia ! *sink). -->markey + + We should cut down on unnecessary/redundant debug output/warnings (noise). + + Perhaps a nice idea: when you drag playlistItems to a branch in the collection browser + it prompts to change the track's tags, so drag a track to the "moolaaa" album and that + track get's it's album tag changed to "moolaaa" and is inserted into the branch, so the user + know's what has happened. + + User asked for a CTRL-J itty-bitty dialog that allows you to jump to a specific part + of a track. I'll see if I can do it with tiny amount of code -->mxcl + + Don't rely on KDE timeouts to see whether a file is accessible or not, + since this really sucks for disconnected network shares. XMMS handles + this way better and it really is a problem in userland. maybe a thread + helps, which simply tries to fopen() the file. if this task hasn't been + finished in (let's say) 3 seconds, jump to the next song. XMMS even + remembers such files and their folders, so that it's not going to open + another file from that folder for the next few minutes. what about hdd + sleep-timeouts? opinions? + + Determine some behavior for the clear/shuffle/etc. buttons when a search is in action + clear -> only clear the stuff that was searched for? + shuffle -> stop the search and do normal shuffle? + ADDitionally what to do when user rearranges a set of items that are the search result? + No big deal to me as long as the behaviour is consistent. I.e. if shuffle shuffles + the search, then clear should clear the search only, and visa versa. + + Make playlist toolbar buttons toggle like the playerwidget buttons. + + Toolbar button in FileBrowser for switching recursive directory reading. + + Can't resize newly displayed columns if they are hidden at beginning of session -->mxcl + + Add an option "clean up playlist on startup". + Do you mean remove duplicates or dead entries etc. ? Cool. + + Option: don't crossfade for sequential tracks on same album. Comments? -->mxcl + Of course, there would still be the up to 150ms gap, but we can fix that some other time.. + Please clarify: is this the same as BR #75388 ? + No, this is just to make crossfading not ruin album transitions + + Add dirs to combo history when user adds a track from that dir to the playlist. + + "Show playlist" to the right click menu, redundant but necessary. + + More KTips, better KTips, somehow use the "Did you know" tip dialog (eg kmail, gideon, etc.) -->mxcl + + Option to automatically adjust column widths. + + Option to implicitly sort playlist by { track, album } (on drop only) -->mxcl + + Playmode indication button in main widget (repeat track/pl/shuffle), clickable. + + +LONG-TERM: + + Support multiple collections. This could be really powerful, and could be handled + with the collection browser. We could support non-local collections, audio cd/dvds, + NFS/Samba etc. This would also allow us to retrieve tracks from other networked pcs. + Auto-polling of added collections for a 'hot-sync' style detection of collections. + + Use more accurate interpolation for analyzers (cubic or spline) + + Tabbed playlists. + Pro: it's convenient to have several playlists + Contra: the playlist is getting cluttered enough as it is! + I already added a comment wrt this to the b.k.o bug, but here's the idea: + switch between playlists with the playlist browser -- that's what it's there for. + This avoids the clutter. The way it'd work is the currently playing playlist would + have the same fancy fading thing the current track has. The context menu for playlists + would have two seperate items, one for showing and one for playing. When just showing, + the previous playlist would continue playing while you view/edit the other one. + (If you doubleclick a song in the new playlist, it would naturally start playing that + one instead). What to do when the user doubleclicks a playlist -- show, play, or both + -- is TBD. + + Make windows magnetic / sticking together (difficult). + + Implement beat detection (thread?), interface should glow/move to + the beat, visualisations have access to beat/bpm info. + + Audio system info widget, showing all available codecs and similar info. + + Resizable playerwidget, like in Winamp3. + +IDEAS: + + Bookmarks inside of tracks (good for very long tracks), and nifty bookmark browser + + Using filelight (as a kpart maybe) for a graphical representation of the playlist. so + you could see at first glance how the altogether playing time is divided into different + albums, tunes and so on. + + +DO-NOT-IMPLEMENT (stuff that was rejected): + + Allow removing of playlist items by dragging back into browser. + I think this is really weird. When I drag something I expect that someting to be + ADDed or opened in the target. Not removed from the sender. + (RFC: is this still appropriate or would it be misleading?) + imho, it's misleading and not hid-compatible. i would rather + expect that file to be copied to the browser's current directory. + + "Hide playlist when main widget is not active" option (?? comments please: ) + Noo, we have it hiding into tray, thats enough (imagine how much flashing will be + if i drag mouse around with "focus follows mouse" on - this is the one i use all the time) + Hence it's an option, you'd not use it with focus follows mouse. But it was just an idea + anyway.. dunno if I like the sound of it anymore either. + + +BACKTRACES/DEBUG/VALGRIND: + diff --git a/acinclude.m4 b/acinclude.m4 new file mode 100644 index 00000000..6b26319f --- /dev/null +++ b/acinclude.m4 @@ -0,0 +1,11945 @@ +## -*- autoconf -*- + +dnl This file is part of the KDE libraries/packages +dnl Copyright (C) 1997 Janos Farkas (chexum@shadow.banki.hu) +dnl (C) 1997,98,99 Stephan Kulow (coolo@kde.org) + +dnl This file is free software; you can redistribute it and/or +dnl modify it under the terms of the GNU Library General Public +dnl License as published by the Free Software Foundation; either +dnl version 2 of the License, or (at your option) any later version. + +dnl This library is distributed in the hope that it will be useful, +dnl but WITHOUT ANY WARRANTY; without even the implied warranty of +dnl MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +dnl Library General Public License for more details. + +dnl You should have received a copy of the GNU Library General Public License +dnl along with this library; see the file COPYING.LIB. If not, write to +dnl the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, +dnl Boston, MA 02110-1301, USA. + +dnl IMPORTANT NOTE: +dnl Please do not modify this file unless you expect your modifications to be +dnl carried into every other module in the repository. +dnl +dnl Single-module modifications are best placed in configure.in for kdelibs +dnl and kdebase or configure.in.in if present. + +# KDE_PATH_X_DIRECT +dnl Internal subroutine of AC_PATH_X. +dnl Set ac_x_includes and/or ac_x_libraries. +AC_DEFUN([KDE_PATH_X_DIRECT], +[ +AC_REQUIRE([KDE_CHECK_LIB64]) + +if test "$ac_x_includes" = NO; then + # Guess where to find include files, by looking for this one X11 .h file. + test -z "$x_direct_test_include" && x_direct_test_include=X11/Intrinsic.h + + # First, try using that file with no special directory specified. +AC_TRY_CPP([#include <$x_direct_test_include>], +[# We can compile using X headers with no special include directory. +ac_x_includes=], +[# Look for the header file in a standard set of common directories. +# Check X11 before X11Rn because it is often a symlink to the current release. + for ac_dir in \ + /usr/X11/include \ + /usr/X11R6/include \ + /usr/X11R5/include \ + /usr/X11R4/include \ + \ + /usr/include/X11 \ + /usr/include/X11R6 \ + /usr/include/X11R5 \ + /usr/include/X11R4 \ + \ + /usr/local/X11/include \ + /usr/local/X11R6/include \ + /usr/local/X11R5/include \ + /usr/local/X11R4/include \ + \ + /usr/local/include/X11 \ + /usr/local/include/X11R6 \ + /usr/local/include/X11R5 \ + /usr/local/include/X11R4 \ + \ + /usr/X386/include \ + /usr/x386/include \ + /usr/XFree86/include/X11 \ + \ + /usr/include \ + /usr/local/include \ + /usr/unsupported/include \ + /usr/athena/include \ + /usr/local/x11r5/include \ + /usr/lpp/Xamples/include \ + \ + /usr/openwin/include \ + /usr/openwin/share/include \ + ; \ + do + if test -r "$ac_dir/$x_direct_test_include"; then + ac_x_includes=$ac_dir + break + fi + done]) +fi # $ac_x_includes = NO + +if test "$ac_x_libraries" = NO; then + # Check for the libraries. + + test -z "$x_direct_test_library" && x_direct_test_library=Xt + test -z "$x_direct_test_function" && x_direct_test_function=XtMalloc + + # See if we find them without any special options. + # Don't add to $LIBS permanently. + ac_save_LIBS="$LIBS" + LIBS="-l$x_direct_test_library $LIBS" +AC_TRY_LINK([#include ], [${x_direct_test_function}(1)], +[LIBS="$ac_save_LIBS" +# We can link X programs with no special library path. +ac_x_libraries=], +[LIBS="$ac_save_LIBS" +# First see if replacing the include by lib works. +# Check X11 before X11Rn because it is often a symlink to the current release. +for ac_dir in `echo "$ac_x_includes" | sed s/include/lib${kdelibsuff}/` \ + /usr/X11/lib${kdelibsuff} \ + /usr/X11R6/lib${kdelibsuff} \ + /usr/X11R5/lib${kdelibsuff} \ + /usr/X11R4/lib${kdelibsuff} \ + \ + /usr/lib${kdelibsuff}/X11 \ + /usr/lib${kdelibsuff}/X11R6 \ + /usr/lib${kdelibsuff}/X11R5 \ + /usr/lib${kdelibsuff}/X11R4 \ + \ + /usr/local/X11/lib${kdelibsuff} \ + /usr/local/X11R6/lib${kdelibsuff} \ + /usr/local/X11R5/lib${kdelibsuff} \ + /usr/local/X11R4/lib${kdelibsuff} \ + \ + /usr/local/lib${kdelibsuff}/X11 \ + /usr/local/lib${kdelibsuff}/X11R6 \ + /usr/local/lib${kdelibsuff}/X11R5 \ + /usr/local/lib${kdelibsuff}/X11R4 \ + \ + /usr/X386/lib${kdelibsuff} \ + /usr/x386/lib${kdelibsuff} \ + /usr/XFree86/lib${kdelibsuff}/X11 \ + \ + /usr/lib${kdelibsuff} \ + /usr/local/lib${kdelibsuff} \ + /usr/unsupported/lib${kdelibsuff} \ + /usr/athena/lib${kdelibsuff} \ + /usr/local/x11r5/lib${kdelibsuff} \ + /usr/lpp/Xamples/lib${kdelibsuff} \ + /lib/usr/lib${kdelibsuff}/X11 \ + \ + /usr/openwin/lib${kdelibsuff} \ + /usr/openwin/share/lib${kdelibsuff} \ + ; \ +do +dnl Don't even attempt the hair of trying to link an X program! + for ac_extension in a so sl; do + if test -r $ac_dir/lib${x_direct_test_library}.$ac_extension; then + ac_x_libraries=$ac_dir + break 2 + fi + done +done]) +fi # $ac_x_libraries = NO +]) + + +dnl ------------------------------------------------------------------------ +dnl Find a file (or one of more files in a list of dirs) +dnl ------------------------------------------------------------------------ +dnl +AC_DEFUN([AC_FIND_FILE], +[ +$3=NO +for i in $2; +do + for j in $1; + do + echo "configure: __oline__: $i/$j" >&AC_FD_CC + if test -r "$i/$j"; then + echo "taking that" >&AC_FD_CC + $3=$i + break 2 + fi + done +done +]) + +dnl KDE_FIND_PATH(program-name, variable-name, list-of-dirs, +dnl if-not-found, test-parameter, prepend-path) +dnl +dnl Look for program-name in list-of-dirs+$PATH. +dnl If prepend-path is set, look in $PATH+list-of-dirs instead. +dnl If found, $variable-name is set. If not, if-not-found is evaluated. +dnl test-parameter: if set, the program is executed with this arg, +dnl and only a successful exit code is required. +AC_DEFUN([KDE_FIND_PATH], +[ + AC_MSG_CHECKING([for $1]) + if test -n "$$2"; then + kde_cv_path="$$2"; + else + kde_cache=`echo $1 | sed 'y%./+-%__p_%'` + + AC_CACHE_VAL(kde_cv_path_$kde_cache, + [ + kde_cv_path="NONE" + kde_save_IFS=$IFS + IFS=':' + dirs="" + for dir in $PATH; do + dirs="$dirs $dir" + done + if test -z "$6"; then dnl Append dirs in PATH (default) + dirs="$3 $dirs" + else dnl Prepend dirs in PATH (if 6th arg is set) + dirs="$dirs $3" + fi + IFS=$kde_save_IFS + + for dir in $dirs; do + if test -x "$dir/$1"; then + if test -n "$5" + then + evalstr="$dir/$1 $5 2>&1 " + if eval $evalstr; then + kde_cv_path="$dir/$1" + break + fi + else + kde_cv_path="$dir/$1" + break + fi + fi + done + + eval "kde_cv_path_$kde_cache=$kde_cv_path" + + ]) + + eval "kde_cv_path=\"`echo '$kde_cv_path_'$kde_cache`\"" + + fi + + if test -z "$kde_cv_path" || test "$kde_cv_path" = NONE; then + AC_MSG_RESULT(not found) + $4 + else + AC_MSG_RESULT($kde_cv_path) + $2=$kde_cv_path + + fi +]) + +AC_DEFUN([KDE_MOC_ERROR_MESSAGE], +[ + AC_MSG_ERROR([No Qt meta object compiler (moc) found! +Please check whether you installed Qt correctly. +You need to have a running moc binary. +configure tried to run $ac_cv_path_moc and the test didn't +succeed. If configure shouldn't have tried this one, set +the environment variable MOC to the right one before running +configure. +]) +]) + +AC_DEFUN([KDE_UIC_ERROR_MESSAGE], +[ + AC_MSG_WARN([No Qt ui compiler (uic) found! +Please check whether you installed Qt correctly. +You need to have a running uic binary. +configure tried to run $ac_cv_path_uic and the test didn't +succeed. If configure shouldn't have tried this one, set +the environment variable UIC to the right one before running +configure. +]) +]) + + +AC_DEFUN([KDE_CHECK_UIC_FLAG], +[ + AC_MSG_CHECKING([whether uic supports -$1 ]) + kde_cache=`echo $1 | sed 'y% .=/+-%____p_%'` + AC_CACHE_VAL(kde_cv_prog_uic_$kde_cache, + [ + cat >conftest.ui < +EOT + ac_uic_testrun="$UIC_PATH -$1 $2 conftest.ui >/dev/null" + if AC_TRY_EVAL(ac_uic_testrun); then + eval "kde_cv_prog_uic_$kde_cache=yes" + else + eval "kde_cv_prog_uic_$kde_cache=no" + fi + rm -f conftest* + ]) + + if eval "test \"`echo '$kde_cv_prog_uic_'$kde_cache`\" = yes"; then + AC_MSG_RESULT([yes]) + : + $3 + else + AC_MSG_RESULT([no]) + : + $4 + fi +]) + + +dnl ------------------------------------------------------------------------ +dnl Find the meta object compiler and the ui compiler in the PATH, +dnl in $QTDIR/bin, and some more usual places +dnl ------------------------------------------------------------------------ +dnl +AC_DEFUN([AC_PATH_QT_MOC_UIC], +[ + AC_REQUIRE([KDE_CHECK_PERL]) + qt_bindirs="" + for dir in $kde_qt_dirs; do + qt_bindirs="$qt_bindirs $dir/bin $dir/src/moc" + done + qt_bindirs="$qt_bindirs /usr/bin /usr/X11R6/bin /usr/local/qt/bin" + if test ! "$ac_qt_bindir" = "NO"; then + qt_bindirs="$ac_qt_bindir $qt_bindirs" + fi + + KDE_FIND_PATH(moc, MOC, [$qt_bindirs], [KDE_MOC_ERROR_MESSAGE]) + if test -z "$UIC_NOT_NEEDED"; then + KDE_FIND_PATH(uic, UIC_PATH, [$qt_bindirs], [UIC_PATH=""]) + if test -z "$UIC_PATH" ; then + KDE_UIC_ERROR_MESSAGE + exit 1 + else + UIC=$UIC_PATH + + if test $kde_qtver = 3; then + KDE_CHECK_UIC_FLAG(L,[/nonexistent],ac_uic_supports_libpath=yes,ac_uic_supports_libpath=no) + KDE_CHECK_UIC_FLAG(nounload,,ac_uic_supports_nounload=yes,ac_uic_supports_nounload=no) + + if test x$ac_uic_supports_libpath = xyes; then + UIC="$UIC -L \$(kde_widgetdir)" + fi + if test x$ac_uic_supports_nounload = xyes; then + UIC="$UIC -nounload" + fi + fi + fi + else + UIC="echo uic not available: " + fi + + AC_SUBST(MOC) + AC_SUBST(UIC) + + UIC_TR="i18n" + if test $kde_qtver = 3; then + UIC_TR="tr2i18n" + fi + + AC_SUBST(UIC_TR) +]) + +AC_DEFUN([KDE_1_CHECK_PATHS], +[ + KDE_1_CHECK_PATH_HEADERS + + KDE_TEST_RPATH= + + if test -n "$USE_RPATH"; then + + if test -n "$kde_libraries"; then + KDE_TEST_RPATH="-R $kde_libraries" + fi + + if test -n "$qt_libraries"; then + KDE_TEST_RPATH="$KDE_TEST_RPATH -R $qt_libraries" + fi + + if test -n "$x_libraries"; then + KDE_TEST_RPATH="$KDE_TEST_RPATH -R $x_libraries" + fi + + KDE_TEST_RPATH="$KDE_TEST_RPATH $KDE_EXTRA_RPATH" + fi + +AC_MSG_CHECKING([for KDE libraries installed]) +ac_link='$LIBTOOL_SHELL --silent --mode=link ${CXX-g++} -o conftest $CXXFLAGS $all_includes $CPPFLAGS $LDFLAGS $all_libraries conftest.$ac_ext $LIBS -lkdecore $LIBQT $KDE_TEST_RPATH 1>&5' + +if AC_TRY_EVAL(ac_link) && test -s conftest; then + AC_MSG_RESULT(yes) +else + AC_MSG_ERROR([your system fails at linking a small KDE application! +Check, if your compiler is installed correctly and if you have used the +same compiler to compile Qt and kdelibs as you did use now. +For more details about this problem, look at the end of config.log.]) +fi + +if eval `KDEDIR= ./conftest 2>&5`; then + kde_result=done +else + kde_result=problems +fi + +KDEDIR= ./conftest 2> /dev/null >&5 # make an echo for config.log +kde_have_all_paths=yes + +KDE_SET_PATHS($kde_result) + +]) + +AC_DEFUN([KDE_SET_PATHS], +[ + kde_cv_all_paths="kde_have_all_paths=\"yes\" \ + kde_htmldir=\"$kde_htmldir\" \ + kde_appsdir=\"$kde_appsdir\" \ + kde_icondir=\"$kde_icondir\" \ + kde_sounddir=\"$kde_sounddir\" \ + kde_datadir=\"$kde_datadir\" \ + kde_locale=\"$kde_locale\" \ + kde_cgidir=\"$kde_cgidir\" \ + kde_confdir=\"$kde_confdir\" \ + kde_kcfgdir=\"$kde_kcfgdir\" \ + kde_mimedir=\"$kde_mimedir\" \ + kde_toolbardir=\"$kde_toolbardir\" \ + kde_wallpaperdir=\"$kde_wallpaperdir\" \ + kde_templatesdir=\"$kde_templatesdir\" \ + kde_bindir=\"$kde_bindir\" \ + kde_servicesdir=\"$kde_servicesdir\" \ + kde_servicetypesdir=\"$kde_servicetypesdir\" \ + kde_moduledir=\"$kde_moduledir\" \ + kde_styledir=\"$kde_styledir\" \ + kde_widgetdir=\"$kde_widgetdir\" \ + xdg_appsdir=\"$xdg_appsdir\" \ + xdg_menudir=\"$xdg_menudir\" \ + xdg_directorydir=\"$xdg_directorydir\" \ + kde_result=$1" +]) + +AC_DEFUN([KDE_SET_DEFAULT_PATHS], +[ +if test "$1" = "default"; then + + if test -z "$kde_htmldir"; then + kde_htmldir='\${datadir}/doc/HTML' + fi + if test -z "$kde_appsdir"; then + kde_appsdir='\${datadir}/applnk' + fi + if test -z "$kde_icondir"; then + kde_icondir='\${datadir}/icons' + fi + if test -z "$kde_sounddir"; then + kde_sounddir='\${datadir}/sounds' + fi + if test -z "$kde_datadir"; then + kde_datadir='\${datadir}/apps' + fi + if test -z "$kde_locale"; then + kde_locale='\${datadir}/locale' + fi + if test -z "$kde_cgidir"; then + kde_cgidir='\${exec_prefix}/cgi-bin' + fi + if test -z "$kde_confdir"; then + kde_confdir='\${datadir}/config' + fi + if test -z "$kde_kcfgdir"; then + kde_kcfgdir='\${datadir}/config.kcfg' + fi + if test -z "$kde_mimedir"; then + kde_mimedir='\${datadir}/mimelnk' + fi + if test -z "$kde_toolbardir"; then + kde_toolbardir='\${datadir}/toolbar' + fi + if test -z "$kde_wallpaperdir"; then + kde_wallpaperdir='\${datadir}/wallpapers' + fi + if test -z "$kde_templatesdir"; then + kde_templatesdir='\${datadir}/templates' + fi + if test -z "$kde_bindir"; then + kde_bindir='\${exec_prefix}/bin' + fi + if test -z "$kde_servicesdir"; then + kde_servicesdir='\${datadir}/services' + fi + if test -z "$kde_servicetypesdir"; then + kde_servicetypesdir='\${datadir}/servicetypes' + fi + if test -z "$kde_moduledir"; then + if test "$kde_qtver" = "2"; then + kde_moduledir='\${libdir}/kde2' + else + kde_moduledir='\${libdir}/kde3' + fi + fi + if test -z "$kde_styledir"; then + kde_styledir='\${libdir}/kde3/plugins/styles' + fi + if test -z "$kde_widgetdir"; then + kde_widgetdir='\${libdir}/kde3/plugins/designer' + fi + if test -z "$xdg_appsdir"; then + xdg_appsdir='\${datadir}/applications/kde' + fi + if test -z "$xdg_menudir"; then + xdg_menudir='\${sysconfdir}/xdg/menus' + fi + if test -z "$xdg_directorydir"; then + xdg_directorydir='\${datadir}/desktop-directories' + fi + + KDE_SET_PATHS(defaults) + +else + + if test $kde_qtver = 1; then + AC_MSG_RESULT([compiling]) + KDE_1_CHECK_PATHS + else + AC_MSG_ERROR([path checking not yet supported for KDE 2]) + fi + +fi +]) + +AC_DEFUN([KDE_CHECK_PATHS_FOR_COMPLETENESS], +[ if test -z "$kde_htmldir" || test -z "$kde_appsdir" || + test -z "$kde_icondir" || test -z "$kde_sounddir" || + test -z "$kde_datadir" || test -z "$kde_locale" || + test -z "$kde_cgidir" || test -z "$kde_confdir" || + test -z "$kde_kcfgdir" || + test -z "$kde_mimedir" || test -z "$kde_toolbardir" || + test -z "$kde_wallpaperdir" || test -z "$kde_templatesdir" || + test -z "$kde_bindir" || test -z "$kde_servicesdir" || + test -z "$kde_servicetypesdir" || test -z "$kde_moduledir" || + test -z "$kde_styledir" || test -z "kde_widgetdir" || + test -z "$xdg_appsdir" || test -z "$xdg_menudir" || test -z "$xdg_directorydir" || + test "x$kde_have_all_paths" != "xyes"; then + kde_have_all_paths=no + fi +]) + +AC_DEFUN([KDE_MISSING_PROG_ERROR], +[ + AC_MSG_ERROR([The important program $1 was not found! +Please check whether you installed KDE correctly. +]) +]) + +AC_DEFUN([KDE_MISSING_ARTS_ERROR], +[ + AC_MSG_ERROR([The important program $1 was not found! +Please check whether you installed aRts correctly or use +--without-arts to compile without aRts support (this will remove functionality). +]) +]) + +AC_DEFUN([KDE_SET_DEFAULT_BINDIRS], +[ + kde_default_bindirs="/usr/bin /usr/local/bin /opt/local/bin /usr/X11R6/bin /opt/kde/bin /opt/kde3/bin /usr/kde/bin /usr/local/kde/bin" + test -n "$KDEDIR" && kde_default_bindirs="$KDEDIR/bin $kde_default_bindirs" + if test -n "$KDEDIRS"; then + kde_save_IFS=$IFS + IFS=: + for dir in $KDEDIRS; do + kde_default_bindirs="$dir/bin $kde_default_bindirs " + done + IFS=$kde_save_IFS + fi +]) + +AC_DEFUN([KDE_SUBST_PROGRAMS], +[ + AC_ARG_WITH(arts, + AC_HELP_STRING([--without-arts],[build without aRts [default=no]]), + [build_arts=$withval], + [build_arts=yes] + ) + AM_CONDITIONAL(include_ARTS, test "$build_arts" '!=' "no") + if test "$build_arts" = "no"; then + AC_DEFINE(WITHOUT_ARTS, 1, [Defined if compiling without arts]) + fi + + KDE_SET_DEFAULT_BINDIRS + kde_default_bindirs="$exec_prefix/bin $prefix/bin $kde_libs_prefix/bin $kde_default_bindirs" + KDE_FIND_PATH(dcopidl, DCOPIDL, [$kde_default_bindirs], [KDE_MISSING_PROG_ERROR(dcopidl)]) + KDE_FIND_PATH(dcopidl2cpp, DCOPIDL2CPP, [$kde_default_bindirs], [KDE_MISSING_PROG_ERROR(dcopidl2cpp)]) + if test "$build_arts" '!=' "no"; then + KDE_FIND_PATH(mcopidl, MCOPIDL, [$kde_default_bindirs], [KDE_MISSING_ARTS_ERROR(mcopidl)]) + KDE_FIND_PATH(artsc-config, ARTSCCONFIG, [$kde_default_bindirs], [KDE_MISSING_ARTS_ERROR(artsc-config)]) + fi + KDE_FIND_PATH(meinproc, MEINPROC, [$kde_default_bindirs]) + + kde32ornewer=1 + kde33ornewer=1 + if test -n "$kde_qtver" && test "$kde_qtver" -lt 3; then + kde32ornewer= + kde33ornewer= + else + if test "$kde_qtver" = "3"; then + if test "$kde_qtsubver" -le 1; then + kde32ornewer= + fi + if test "$kde_qtsubver" -le 2; then + kde33ornewer= + fi + if test "$KDECONFIG" != "compiled"; then + if test `$KDECONFIG --version | grep KDE | sed 's/KDE: \(...\).*/\1/'` = 3.2; then + kde33ornewer= + fi + fi + fi + fi + + if test -n "$kde32ornewer"; then + KDE_FIND_PATH(kconfig_compiler, KCONFIG_COMPILER, [$kde_default_bindirs], [KDE_MISSING_PROG_ERROR(kconfig_compiler)]) + KDE_FIND_PATH(dcopidlng, DCOPIDLNG, [$kde_default_bindirs], [KDE_MISSING_PROG_ERROR(dcopidlng)]) + fi + if test -n "$kde33ornewer"; then + KDE_FIND_PATH(makekdewidgets, MAKEKDEWIDGETS, [$kde_default_bindirs], [KDE_MISSING_PROG_ERROR(makekdewidgets)]) + AC_SUBST(MAKEKDEWIDGETS) + fi + KDE_FIND_PATH(xmllint, XMLLINT, [${prefix}/bin ${exec_prefix}/bin], [XMLLINT=""]) + + if test -n "$MEINPROC" -a "$MEINPROC" != "compiled"; then + kde_sharedirs="/usr/share/kde /usr/local/share /usr/share /opt/kde3/share /opt/kde/share $prefix/share" + test -n "$KDEDIR" && kde_sharedirs="$KDEDIR/share $kde_sharedirs" + AC_FIND_FILE(apps/ksgmltools2/customization/kde-chunk.xsl, $kde_sharedirs, KDE_XSL_STYLESHEET) + if test "$KDE_XSL_STYLESHEET" = "NO"; then + KDE_XSL_STYLESHEET="" + else + KDE_XSL_STYLESHEET="$KDE_XSL_STYLESHEET/apps/ksgmltools2/customization/kde-chunk.xsl" + fi + fi + + DCOP_DEPENDENCIES='$(DCOPIDL)' + if test -n "$kde32ornewer"; then + KCFG_DEPENDENCIES='$(KCONFIG_COMPILER)' + DCOP_DEPENDENCIES='$(DCOPIDL) $(DCOPIDLNG)' + AC_SUBST(KCONFIG_COMPILER) + AC_SUBST(KCFG_DEPENDENCIES) + AC_SUBST(DCOPIDLNG) + fi + AC_SUBST(DCOPIDL) + AC_SUBST(DCOPIDL2CPP) + AC_SUBST(DCOP_DEPENDENCIES) + AC_SUBST(MCOPIDL) + AC_SUBST(ARTSCCONFIG) + AC_SUBST(MEINPROC) + AC_SUBST(KDE_XSL_STYLESHEET) + AC_SUBST(XMLLINT) +])dnl + +AC_DEFUN([AC_CREATE_KFSSTND], +[ +AC_REQUIRE([AC_CHECK_RPATH]) + +AC_MSG_CHECKING([for KDE paths]) +kde_result="" +kde_cached_paths=yes +AC_CACHE_VAL(kde_cv_all_paths, +[ + KDE_SET_DEFAULT_PATHS($1) + kde_cached_paths=no +]) +eval "$kde_cv_all_paths" +KDE_CHECK_PATHS_FOR_COMPLETENESS +if test "$kde_have_all_paths" = "no" && test "$kde_cached_paths" = "yes"; then + # wrong values were cached, may be, we can set better ones + kde_result= + kde_htmldir= kde_appsdir= kde_icondir= kde_sounddir= + kde_datadir= kde_locale= kde_cgidir= kde_confdir= kde_kcfgdir= + kde_mimedir= kde_toolbardir= kde_wallpaperdir= kde_templatesdir= + kde_bindir= kde_servicesdir= kde_servicetypesdir= kde_moduledir= + kde_have_all_paths= + kde_styledir= + kde_widgetdir= + xdg_appsdir = xdg_menudir= xdg_directorydir= + KDE_SET_DEFAULT_PATHS($1) + eval "$kde_cv_all_paths" + KDE_CHECK_PATHS_FOR_COMPLETENESS + kde_result="$kde_result (cache overridden)" +fi +if test "$kde_have_all_paths" = "no"; then + AC_MSG_ERROR([configure could not run a little KDE program to test the environment. +Since it had compiled and linked before, it must be a strange problem on your system. +Look at config.log for details. If you are not able to fix this, look at +http://www.kde.org/faq/installation.html or any www.kde.org mirror. +(If you're using an egcs version on Linux, you may update binutils!) +]) +else + rm -f conftest* + AC_MSG_RESULT($kde_result) +fi + +bindir=$kde_bindir + +KDE_SUBST_PROGRAMS + +]) + +AC_DEFUN([AC_SUBST_KFSSTND], +[ +AC_SUBST(kde_htmldir) +AC_SUBST(kde_appsdir) +AC_SUBST(kde_icondir) +AC_SUBST(kde_sounddir) +AC_SUBST(kde_datadir) +AC_SUBST(kde_locale) +AC_SUBST(kde_confdir) +AC_SUBST(kde_kcfgdir) +AC_SUBST(kde_mimedir) +AC_SUBST(kde_wallpaperdir) +AC_SUBST(kde_bindir) +dnl X Desktop Group standards +AC_SUBST(xdg_appsdir) +AC_SUBST(xdg_menudir) +AC_SUBST(xdg_directorydir) +dnl for KDE 2 +AC_SUBST(kde_templatesdir) +AC_SUBST(kde_servicesdir) +AC_SUBST(kde_servicetypesdir) +AC_SUBST(kde_moduledir) +AC_SUBST(kdeinitdir, '$(kde_moduledir)') +AC_SUBST(kde_styledir) +AC_SUBST(kde_widgetdir) +if test "$kde_qtver" = 1; then + kde_minidir="$kde_icondir/mini" +else +# for KDE 1 - this breaks KDE2 apps using minidir, but +# that's the plan ;-/ + kde_minidir="/dev/null" +fi +dnl AC_SUBST(kde_minidir) +dnl AC_SUBST(kde_cgidir) +dnl AC_SUBST(kde_toolbardir) +]) + +AC_DEFUN([KDE_MISC_TESTS], +[ + dnl Checks for libraries. + AC_CHECK_LIB(util, main, [LIBUTIL="-lutil"]) dnl for *BSD + AC_SUBST(LIBUTIL) + AC_CHECK_LIB(compat, main, [LIBCOMPAT="-lcompat"]) dnl for *BSD + AC_SUBST(LIBCOMPAT) + kde_have_crypt= + AC_CHECK_LIB(crypt, crypt, [LIBCRYPT="-lcrypt"; kde_have_crypt=yes], + AC_CHECK_LIB(c, crypt, [kde_have_crypt=yes], [ + AC_MSG_WARN([you have no crypt in either libcrypt or libc. +You should install libcrypt from another source or configure with PAM +support]) + kde_have_crypt=no + ])) + AC_SUBST(LIBCRYPT) + if test $kde_have_crypt = yes; then + AC_DEFINE_UNQUOTED(HAVE_CRYPT, 1, [Defines if your system has the crypt function]) + fi + AC_CHECK_SOCKLEN_T + AC_CHECK_LIB(dnet, dnet_ntoa, [X_EXTRA_LIBS="$X_EXTRA_LIBS -ldnet"]) + if test $ac_cv_lib_dnet_dnet_ntoa = no; then + AC_CHECK_LIB(dnet_stub, dnet_ntoa, + [X_EXTRA_LIBS="$X_EXTRA_LIBS -ldnet_stub"]) + fi + AC_CHECK_FUNC(inet_ntoa) + if test $ac_cv_func_inet_ntoa = no; then + AC_CHECK_LIB(nsl, inet_ntoa, X_EXTRA_LIBS="$X_EXTRA_LIBS -lnsl") + fi + AC_CHECK_FUNC(connect) + if test $ac_cv_func_connect = no; then + AC_CHECK_LIB(socket, connect, X_EXTRA_LIBS="-lsocket $X_EXTRA_LIBS", , + $X_EXTRA_LIBS) + fi + + AC_CHECK_FUNC(remove) + if test $ac_cv_func_remove = no; then + AC_CHECK_LIB(posix, remove, X_EXTRA_LIBS="$X_EXTRA_LIBS -lposix") + fi + + # BSDI BSD/OS 2.1 needs -lipc for XOpenDisplay. + AC_CHECK_FUNC(shmat, , + AC_CHECK_LIB(ipc, shmat, X_EXTRA_LIBS="$X_EXTRA_LIBS -lipc")) + + # more headers that need to be explicitly included on darwin + AC_CHECK_HEADERS(sys/types.h stdint.h) + + # sys/bitypes.h is needed for uint32_t and friends on Tru64 + AC_CHECK_HEADERS(sys/bitypes.h) + + # darwin requires a poll emulation library + AC_CHECK_LIB(poll, poll, LIB_POLL="-lpoll") + + # for some image handling on Mac OS X + AC_CHECK_HEADERS(Carbon/Carbon.h) + + # CoreAudio framework + AC_CHECK_HEADER(CoreAudio/CoreAudio.h, [ + AC_DEFINE(HAVE_COREAUDIO, 1, [Define if you have the CoreAudio API]) + FRAMEWORK_COREAUDIO="-Wl,-framework,CoreAudio" + ]) + + AC_CHECK_RES_INIT + AC_SUBST(LIB_POLL) + AC_SUBST(FRAMEWORK_COREAUDIO) + LIBSOCKET="$X_EXTRA_LIBS" + AC_SUBST(LIBSOCKET) + AC_SUBST(X_EXTRA_LIBS) + AC_CHECK_LIB(ucb, killpg, [LIBUCB="-lucb"]) dnl for Solaris2.4 + AC_SUBST(LIBUCB) + + case $host in dnl this *is* LynxOS specific + *-*-lynxos* ) + AC_MSG_CHECKING([LynxOS header file wrappers]) + [CFLAGS="$CFLAGS -D__NO_INCLUDE_WARN__"] + AC_MSG_RESULT(disabled) + AC_CHECK_LIB(bsd, gethostbyname, [LIBSOCKET="-lbsd"]) dnl for LynxOS + ;; + esac + + KDE_CHECK_TYPES + KDE_CHECK_LIBDL + KDE_CHECK_STRLCPY + KDE_CHECK_PIE_SUPPORT + +# darwin needs this to initialize the environment +AC_CHECK_HEADERS(crt_externs.h) +AC_CHECK_FUNC(_NSGetEnviron, [AC_DEFINE(HAVE_NSGETENVIRON, 1, [Define if your system needs _NSGetEnviron to set up the environment])]) + +AH_VERBATIM(_DARWIN_ENVIRON, +[ +#if defined(HAVE_NSGETENVIRON) && defined(HAVE_CRT_EXTERNS_H) +# include +# include +# define environ (*_NSGetEnviron()) +#endif +]) + +AH_VERBATIM(_AIX_STRINGS_H_BZERO, +[ +/* + * AIX defines FD_SET in terms of bzero, but fails to include + * that defines bzero. + */ + +#if defined(_AIX) +#include +#endif +]) + +AC_CHECK_FUNCS([vsnprintf snprintf]) + +AH_VERBATIM(_TRU64,[ +/* + * On HP-UX, the declaration of vsnprintf() is needed every time ! + */ + +#if !defined(HAVE_VSNPRINTF) || defined(hpux) +#if __STDC__ +#include +#include +#else +#include +#endif +#ifdef __cplusplus +extern "C" +#endif +int vsnprintf(char *str, size_t n, char const *fmt, va_list ap); +#ifdef __cplusplus +extern "C" +#endif +int snprintf(char *str, size_t n, char const *fmt, ...); +#endif +]) + +]) + +dnl ------------------------------------------------------------------------ +dnl Find the header files and libraries for X-Windows. Extended the +dnl macro AC_PATH_X +dnl ------------------------------------------------------------------------ +dnl +AC_DEFUN([K_PATH_X], +[ +AC_REQUIRE([KDE_MISC_TESTS])dnl +AC_REQUIRE([KDE_CHECK_LIB64]) + +AC_ARG_ENABLE( + embedded, + AC_HELP_STRING([--enable-embedded],[link to Qt-embedded, don't use X]), + kde_use_qt_emb=$enableval, + kde_use_qt_emb=no +) + +AC_ARG_ENABLE( + qtopia, + AC_HELP_STRING([--enable-qtopia],[link to Qt-embedded, link to the Qtopia Environment]), + kde_use_qt_emb_palm=$enableval, + kde_use_qt_emb_palm=no +) + +AC_ARG_ENABLE( + mac, + AC_HELP_STRING([--enable-mac],[link to Qt/Mac (don't use X)]), + kde_use_qt_mac=$enableval, + kde_use_qt_mac=no +) + +# used to disable x11-specific stuff on special platforms +AM_CONDITIONAL(include_x11, test "$kde_use_qt_emb" = "no" && test "$kde_use_qt_mac" = "no") + +if test "$kde_use_qt_emb" = "no" && test "$kde_use_qt_mac" = "no"; then + +AC_MSG_CHECKING(for X) + +AC_CACHE_VAL(kde_cv_have_x, +[# One or both of the vars are not set, and there is no cached value. +if test "{$x_includes+set}" = set || test "$x_includes" = NONE; then + kde_x_includes=NO +else + kde_x_includes=$x_includes +fi +if test "{$x_libraries+set}" = set || test "$x_libraries" = NONE; then + kde_x_libraries=NO +else + kde_x_libraries=$x_libraries +fi + +# below we use the standard autoconf calls +ac_x_libraries=$kde_x_libraries +ac_x_includes=$kde_x_includes + +KDE_PATH_X_DIRECT +dnl AC_PATH_X_XMKMF picks /usr/lib as the path for the X libraries. +dnl Unfortunately, if compiling with the N32 ABI, this is not the correct +dnl location. The correct location is /usr/lib32 or an undefined value +dnl (the linker is smart enough to pick the correct default library). +dnl Things work just fine if you use just AC_PATH_X_DIRECT. +dnl Solaris has a similar problem. AC_PATH_X_XMKMF forces x_includes to +dnl /usr/openwin/include, which doesn't work. /usr/include does work, so +dnl x_includes should be left alone. +case "$host" in +mips-sgi-irix6*) + ;; +*-*-solaris*) + ;; +*) + _AC_PATH_X_XMKMF + if test -z "$ac_x_includes"; then + ac_x_includes="." + fi + if test -z "$ac_x_libraries"; then + ac_x_libraries="/usr/lib${kdelibsuff}" + fi +esac +#from now on we use our own again + +# when the user already gave --x-includes, we ignore +# what the standard autoconf macros told us. +if test "$kde_x_includes" = NO; then + kde_x_includes=$ac_x_includes +fi + +# for --x-libraries too +if test "$kde_x_libraries" = NO; then + kde_x_libraries=$ac_x_libraries +fi + +if test "$kde_x_includes" = NO; then + AC_MSG_ERROR([Can't find X includes. Please check your installation and add the correct paths!]) +fi + +if test "$kde_x_libraries" = NO; then + AC_MSG_ERROR([Can't find X libraries. Please check your installation and add the correct paths!]) +fi + +# Record where we found X for the cache. +kde_cv_have_x="have_x=yes \ + kde_x_includes=$kde_x_includes kde_x_libraries=$kde_x_libraries" +])dnl + +eval "$kde_cv_have_x" + +if test "$have_x" != yes; then + AC_MSG_RESULT($have_x) + no_x=yes +else + AC_MSG_RESULT([libraries $kde_x_libraries, headers $kde_x_includes]) +fi + +if test -z "$kde_x_includes" || test "x$kde_x_includes" = xNONE; then + X_INCLUDES="" + x_includes="."; dnl better than nothing :- + else + x_includes=$kde_x_includes + X_INCLUDES="-I$x_includes" +fi + +if test -z "$kde_x_libraries" || test "x$kde_x_libraries" = xNONE || test "$kde_x_libraries" = "/usr/lib"; then + X_LDFLAGS="" + x_libraries="/usr/lib"; dnl better than nothing :- + else + x_libraries=$kde_x_libraries + X_LDFLAGS="-L$x_libraries" +fi +all_includes="$X_INCLUDES" +all_libraries="$X_LDFLAGS $LDFLAGS_AS_NEEDED $LDFLAGS_NEW_DTAGS" + +# Check for libraries that X11R6 Xt/Xaw programs need. +ac_save_LDFLAGS="$LDFLAGS" +LDFLAGS="$LDFLAGS $X_LDFLAGS" +# SM needs ICE to (dynamically) link under SunOS 4.x (so we have to +# check for ICE first), but we must link in the order -lSM -lICE or +# we get undefined symbols. So assume we have SM if we have ICE. +# These have to be linked with before -lX11, unlike the other +# libraries we check for below, so use a different variable. +# --interran@uluru.Stanford.EDU, kb@cs.umb.edu. +AC_CHECK_LIB(ICE, IceConnectionNumber, + [LIBSM="-lSM -lICE"], , $X_EXTRA_LIBS) +LDFLAGS="$ac_save_LDFLAGS" + +LIB_X11='-lX11 $(LIBSOCKET)' + +AC_MSG_CHECKING(for libXext) +AC_CACHE_VAL(kde_cv_have_libXext, +[ +kde_ldflags_safe="$LDFLAGS" +kde_libs_safe="$LIBS" + +LDFLAGS="$LDFLAGS $X_LDFLAGS $USER_LDFLAGS" +LIBS="-lXext -lX11 $LIBSOCKET" + +AC_TRY_LINK([ +#include +#ifdef STDC_HEADERS +# include +#endif +], +[ +printf("hello Xext\n"); +], +kde_cv_have_libXext=yes, +kde_cv_have_libXext=no +) + +LDFLAGS=$kde_ldflags_safe +LIBS=$kde_libs_safe +]) + +AC_MSG_RESULT($kde_cv_have_libXext) + +if test "$kde_cv_have_libXext" = "no"; then + AC_MSG_ERROR([We need a working libXext to proceed. Since configure +can't find it itself, we stop here assuming that make wouldn't find +them either.]) +fi + +LIB_XEXT="-lXext" +QTE_NORTTI="" + +elif test "$kde_use_qt_emb" = "yes"; then + dnl We're using QT Embedded + CPPFLAGS=-DQWS + CXXFLAGS="$CXXFLAGS -fno-rtti" + QTE_NORTTI="-fno-rtti -DQWS" + X_PRE_LIBS="" + LIB_X11="" + LIB_XEXT="" + LIB_XRENDER="" + LIBSM="" + X_INCLUDES="" + X_LDFLAGS="" + x_includes="" + x_libraries="" +elif test "$kde_use_qt_mac" = "yes"; then + dnl We're using QT/Mac (I use QT_MAC so that qglobal.h doesn't *have* to + dnl be included to get the information) --Sam + CXXFLAGS="$CXXFLAGS -DQT_MAC -no-cpp-precomp" + CFLAGS="$CFLAGS -DQT_MAC -no-cpp-precomp" + X_PRE_LIBS="" + LIB_X11="" + LIB_XEXT="" + LIB_XRENDER="" + LIBSM="" + X_INCLUDES="" + X_LDFLAGS="" + x_includes="" + x_libraries="" +fi +AC_SUBST(X_PRE_LIBS) +AC_SUBST(LIB_X11) +AC_SUBST(LIB_XRENDER) +AC_SUBST(LIBSM) +AC_SUBST(X_INCLUDES) +AC_SUBST(X_LDFLAGS) +AC_SUBST(x_includes) +AC_SUBST(x_libraries) +AC_SUBST(QTE_NORTTI) +AC_SUBST(LIB_XEXT) + +]) + +AC_DEFUN([KDE_PRINT_QT_PROGRAM], +[ +AC_REQUIRE([KDE_USE_QT]) +cat > conftest.$ac_ext < +#include +EOF +if test "$kde_qtver" = "2"; then +cat >> conftest.$ac_ext < +#include +#include +EOF + +if test $kde_qtsubver -gt 0; then +cat >> conftest.$ac_ext <> conftest.$ac_ext < +#include +#include +EOF +fi + +echo "#if ! ($kde_qt_verstring)" >> conftest.$ac_ext +cat >> conftest.$ac_ext <> conftest.$ac_ext <> conftest.$ac_ext <> conftest.$ac_ext <> conftest.$ac_ext <&AC_FD_CC + cat conftest.$ac_ext >&AC_FD_CC +fi + +rm -f conftest* +CXXFLAGS="$ac_cxxflags_safe" +LDFLAGS="$ac_ldflags_safe" +LIBS="$ac_libs_safe" + +LD_LIBRARY_PATH="$ac_LD_LIBRARY_PATH_safe" +export LD_LIBRARY_PATH +LIBRARY_PATH="$ac_LIBRARY_PATH" +export LIBRARY_PATH +AC_LANG_RESTORE +]) + +if test "$kde_cv_qt_direct" = "yes"; then + AC_MSG_RESULT(yes) + $1 +else + AC_MSG_RESULT(no) + $2 +fi +]) + +dnl ------------------------------------------------------------------------ +dnl Try to find the Qt headers and libraries. +dnl $(QT_LDFLAGS) will be -Lqtliblocation (if needed) +dnl and $(QT_INCLUDES) will be -Iqthdrlocation (if needed) +dnl ------------------------------------------------------------------------ +dnl +AC_DEFUN([AC_PATH_QT_1_3], +[ +AC_REQUIRE([K_PATH_X]) +AC_REQUIRE([KDE_USE_QT]) +AC_REQUIRE([KDE_CHECK_LIB64]) + +dnl ------------------------------------------------------------------------ +dnl Add configure flag to enable linking to MT version of Qt library. +dnl ------------------------------------------------------------------------ + +AC_ARG_ENABLE( + mt, + AC_HELP_STRING([--disable-mt],[link to non-threaded Qt (deprecated)]), + kde_use_qt_mt=$enableval, + [ + if test $kde_qtver = 3; then + kde_use_qt_mt=yes + else + kde_use_qt_mt=no + fi + ] +) + +USING_QT_MT="" + +dnl ------------------------------------------------------------------------ +dnl If we not get --disable-qt-mt then adjust some vars for the host. +dnl ------------------------------------------------------------------------ + +KDE_MT_LDFLAGS= +KDE_MT_LIBS= +if test "x$kde_use_qt_mt" = "xyes"; then + KDE_CHECK_THREADING + if test "x$kde_use_threading" = "xyes"; then + CPPFLAGS="$USE_THREADS -DQT_THREAD_SUPPORT $CPPFLAGS" + KDE_MT_LDFLAGS="$USE_THREADS" + KDE_MT_LIBS="$LIBPTHREAD" + else + kde_use_qt_mt=no + fi +fi +AC_SUBST(KDE_MT_LDFLAGS) +AC_SUBST(KDE_MT_LIBS) + +kde_qt_was_given=yes + +dnl ------------------------------------------------------------------------ +dnl If we haven't been told how to link to Qt, we work it out for ourselves. +dnl ------------------------------------------------------------------------ +if test -z "$LIBQT_GLOB"; then + if test "x$kde_use_qt_emb" = "xyes"; then + LIBQT_GLOB="libqte.*" + else + LIBQT_GLOB="libqt.*" + fi +fi + +dnl ------------------------------------------------------------ +dnl If we got --enable-embedded then adjust the Qt library name. +dnl ------------------------------------------------------------ +if test "x$kde_use_qt_emb" = "xyes"; then + qtlib="qte" +else + qtlib="qt" +fi + +kde_int_qt="-l$qtlib" + +if test -z "$LIBQPE"; then +dnl ------------------------------------------------------------ +dnl If we got --enable-palmtop then add -lqpe to the link line +dnl ------------------------------------------------------------ + if test "x$kde_use_qt_emb" = "xyes"; then + if test "x$kde_use_qt_emb_palm" = "xyes"; then + LIB_QPE="-lqpe" + else + LIB_QPE="" + fi + else + LIB_QPE="" + fi +fi + +dnl ------------------------------------------------------------------------ +dnl If we got --enable-qt-mt then adjust the Qt library name for the host. +dnl ------------------------------------------------------------------------ + +if test "x$kde_use_qt_mt" = "xyes"; then + LIBQT="-l$qtlib-mt" + kde_int_qt="-l$qtlib-mt" + LIBQT_GLOB="lib$qtlib-mt.*" + USING_QT_MT="using -mt" +else + LIBQT="-l$qtlib" +fi + +if test $kde_qtver != 1; then + + AC_REQUIRE([AC_FIND_PNG]) + AC_REQUIRE([AC_FIND_JPEG]) + LIBQT="$LIBQT $LIBPNG $LIBJPEG" +fi + +if test $kde_qtver = 3; then + AC_REQUIRE([KDE_CHECK_LIBDL]) + LIBQT="$LIBQT $LIBDL" +fi + +AC_MSG_CHECKING([for Qt]) + +if test "x$kde_use_qt_emb" != "xyes" && test "x$kde_use_qt_mac" != "xyes"; then +LIBQT="$LIBQT $X_PRE_LIBS -lXext -lX11 $LIBSM $LIBSOCKET" +fi +ac_qt_includes=NO ac_qt_libraries=NO ac_qt_bindir=NO +qt_libraries="" +qt_includes="" +AC_ARG_WITH(qt-dir, + AC_HELP_STRING([--with-qt-dir=DIR],[where the root of Qt is installed ]), + [ ac_qt_includes="$withval"/include + ac_qt_libraries="$withval"/lib${kdelibsuff} + ac_qt_bindir="$withval"/bin + ]) + +AC_ARG_WITH(qt-includes, + AC_HELP_STRING([--with-qt-includes=DIR],[where the Qt includes are. ]), + [ + ac_qt_includes="$withval" + ]) + +kde_qt_libs_given=no + +AC_ARG_WITH(qt-libraries, + AC_HELP_STRING([--with-qt-libraries=DIR],[where the Qt library is installed.]), + [ ac_qt_libraries="$withval" + kde_qt_libs_given=yes + ]) + +AC_CACHE_VAL(ac_cv_have_qt, +[#try to guess Qt locations + +qt_incdirs="" +for dir in $kde_qt_dirs; do + qt_incdirs="$qt_incdirs $dir/include $dir" +done +if test -z "$PKG_CONFIG"; then + AC_PATH_PROG(PKG_CONFIG, pkg-config, no) +fi +if test "$PKG_CONFIG" != "no" ; then + if $PKG_CONFIG --exists qt-mt ; then + qt_incdirs="$qt_incdirs `$PKG_CONFIG --variable=includedir qt-mt`" + fi +fi +qt_incdirs="$QTINC $qt_incdirs /usr/local/qt/include /usr/include/qt /usr/include /usr/X11R6/include/X11/qt /usr/X11R6/include/qt /usr/X11R6/include/qt2 /usr/include/qt3 $x_includes" +if test ! "$ac_qt_includes" = "NO"; then + qt_incdirs="$ac_qt_includes $qt_incdirs" +fi + +if test "$kde_qtver" != "1"; then + kde_qt_header=qstyle.h +else + kde_qt_header=qglobal.h +fi + +AC_FIND_FILE($kde_qt_header, $qt_incdirs, qt_incdir) +ac_qt_includes="$qt_incdir" + +qt_libdirs="" +for dir in $kde_qt_dirs; do + qt_libdirs="$qt_libdirs $dir/lib${kdelibsuff} $dir/lib $dir" +done +if test -z "$PKG_CONFIG"; then + AC_PATH_PROG(PKG_CONFIG, pkg-config, no) +fi +if test "$PKG_CONFIG" != "no" ; then + if $PKG_CONFIG --exists qt-mt ; then + qt_libdirs="$qt_incdirs `$PKG_CONFIG --variable=libdir qt-mt`" + fi +fi +qt_libdirs="$QTLIB $qt_libdirs /usr/X11R6/lib /usr/lib /usr/local/qt/lib $x_libraries" +if test ! "$ac_qt_libraries" = "NO"; then + qt_libdir=$ac_qt_libraries +else + qt_libdirs="$ac_qt_libraries $qt_libdirs" + # if the Qt was given, the chance is too big that libqt.* doesn't exist + qt_libdir=NONE + for dir in $qt_libdirs; do + try="ls -1 $dir/${LIBQT_GLOB}" + if test -n "`$try 2> /dev/null`"; then qt_libdir=$dir; break; else echo "tried $dir" >&AC_FD_CC ; fi + done +fi +for a in $qt_libdir/lib`echo ${kde_int_qt} | sed 's,^-l,,'`_incremental.*; do + if test -e "$a"; then + LIBQT="$LIBQT ${kde_int_qt}_incremental" + break + fi +done + +ac_qt_libraries="$qt_libdir" + +AC_LANG_SAVE +AC_LANG_CPLUSPLUS + +ac_cxxflags_safe="$CXXFLAGS" +ac_ldflags_safe="$LDFLAGS" +ac_libs_safe="$LIBS" + +CXXFLAGS="$CXXFLAGS -I$qt_incdir $all_includes" +LDFLAGS="$LDFLAGS -L$qt_libdir $all_libraries $USER_LDFLAGS $KDE_MT_LDFLAGS" +LIBS="$LIBS $LIBQT $KDE_MT_LIBS" + +KDE_PRINT_QT_PROGRAM + +if AC_TRY_EVAL(ac_link) && test -s conftest; then + rm -f conftest* +else + echo "configure: failed program was:" >&AC_FD_CC + cat conftest.$ac_ext >&AC_FD_CC + ac_qt_libraries="NO" +fi +rm -f conftest* +CXXFLAGS="$ac_cxxflags_safe" +LDFLAGS="$ac_ldflags_safe" +LIBS="$ac_libs_safe" + +AC_LANG_RESTORE +if test "$ac_qt_includes" = NO || test "$ac_qt_libraries" = NO; then + ac_cv_have_qt="have_qt=no" + ac_qt_notfound="" + missing_qt_mt="" + if test "$ac_qt_includes" = NO; then + if test "$ac_qt_libraries" = NO; then + ac_qt_notfound="(headers and libraries)"; + else + ac_qt_notfound="(headers)"; + fi + else + if test "x$kde_use_qt_mt" = "xyes"; then + missing_qt_mt=" +Make sure that you have compiled Qt with thread support!" + ac_qt_notfound="(library $qtlib-mt)"; + else + ac_qt_notfound="(library $qtlib)"; + fi + fi + + AC_MSG_ERROR([Qt ($kde_qt_minversion) $ac_qt_notfound not found. Please check your installation! +For more details about this problem, look at the end of config.log.$missing_qt_mt]) +else + have_qt="yes" +fi +]) + +eval "$ac_cv_have_qt" + +if test "$have_qt" != yes; then + AC_MSG_RESULT([$have_qt]); +else + ac_cv_have_qt="have_qt=yes \ + ac_qt_includes=$ac_qt_includes ac_qt_libraries=$ac_qt_libraries" + AC_MSG_RESULT([libraries $ac_qt_libraries, headers $ac_qt_includes $USING_QT_MT]) + + qt_libraries="$ac_qt_libraries" + qt_includes="$ac_qt_includes" +fi + +if test ! "$kde_qt_libs_given" = "yes" && test ! "$kde_qtver" = 3; then + KDE_CHECK_QT_DIRECT(qt_libraries= ,[]) +fi + +AC_SUBST(qt_libraries) +AC_SUBST(qt_includes) + +if test "$qt_includes" = "$x_includes" || test -z "$qt_includes"; then + QT_INCLUDES="" +else + QT_INCLUDES="-I$qt_includes" + all_includes="$QT_INCLUDES $all_includes" +fi + +if test "$qt_libraries" = "$x_libraries" || test -z "$qt_libraries"; then + QT_LDFLAGS="" +else + QT_LDFLAGS="-L$qt_libraries" + all_libraries="$QT_LDFLAGS $all_libraries" +fi +test -z "$KDE_MT_LDFLAGS" || all_libraries="$all_libraries $KDE_MT_LDFLAGS" + +AC_SUBST(QT_INCLUDES) +AC_SUBST(QT_LDFLAGS) +AC_PATH_QT_MOC_UIC + +KDE_CHECK_QT_JPEG + +if test "x$kde_use_qt_emb" != "xyes" && test "x$kde_use_qt_mac" != "xyes"; then +LIB_QT="$kde_int_qt $LIBJPEG_QT "'$(LIBZ) $(LIBPNG) -lXext $(LIB_X11) $(LIBSM)' +else +LIB_QT="$kde_int_qt $LIBJPEG_QT "'$(LIBZ) $(LIBPNG)' +fi +test -z "$KDE_MT_LIBS" || LIB_QT="$LIB_QT $KDE_MT_LIBS" +for a in $qt_libdir/lib`echo ${kde_int_qt} | sed 's,^-l,,'`_incremental.*; do + if test -e "$a"; then + LIB_QT="$LIB_QT ${kde_int_qt}_incremental" + break + fi +done + +AC_SUBST(LIB_QT) +AC_SUBST(LIB_QPE) + +AC_SUBST(kde_qtver) +]) + +AC_DEFUN([AC_PATH_QT], +[ +AC_PATH_QT_1_3 +]) + +AC_DEFUN([KDE_CHECK_UIC_PLUGINS], +[ +AC_REQUIRE([AC_PATH_QT_MOC_UIC]) + +if test x$ac_uic_supports_libpath = xyes; then + +AC_MSG_CHECKING([if UIC has KDE plugins available]) +AC_CACHE_VAL(kde_cv_uic_plugins, +[ +cat > actest.ui << EOF + +NewConnectionDialog + + + + testInput + + + + +EOF + + + +kde_cv_uic_plugins=no +kde_line="$UIC_PATH -L $kde_widgetdir" +if test x$ac_uic_supports_nounload = xyes; then + kde_line="$kde_line -nounload" +fi +kde_line="$kde_line -impl actest.h actest.ui > actest.cpp" +if AC_TRY_EVAL(kde_line); then + # if you're trying to debug this check and think it's incorrect, + # better check your installation. The check _is_ correct - your + # installation is not. + if test -f actest.cpp && grep klineedit actest.cpp > /dev/null; then + kde_cv_uic_plugins=yes + fi +fi +rm -f actest.ui actest.cpp +]) + +AC_MSG_RESULT([$kde_cv_uic_plugins]) +if test "$kde_cv_uic_plugins" != yes; then + AC_MSG_ERROR([ +you need to install kdelibs first. + +If you did install kdelibs, then the Qt version that is picked up by +this configure is not the same version you used to compile kdelibs. +The Qt Plugin installed by kdelibs is *ONLY* loadable if it is the +_same Qt version_, compiled with the _same compiler_ and the same Qt +configuration settings. +]) +fi +fi +]) + +AC_DEFUN([KDE_CHECK_FINAL], +[ + AC_ARG_ENABLE(final, + AC_HELP_STRING([--enable-final], + [build size optimized apps (experimental - needs lots of memory)]), + kde_use_final=$enableval, kde_use_final=no) + + if test "x$kde_use_final" = "xyes"; then + KDE_USE_FINAL_TRUE="" + KDE_USE_FINAL_FALSE="#" + else + KDE_USE_FINAL_TRUE="#" + KDE_USE_FINAL_FALSE="" + fi + AC_SUBST(KDE_USE_FINAL_TRUE) + AC_SUBST(KDE_USE_FINAL_FALSE) +]) + +AC_DEFUN([KDE_CHECK_CLOSURE], +[ + AC_ARG_ENABLE(closure, + AC_HELP_STRING([--enable-closure],[delay template instantiation]), + kde_use_closure=$enableval, kde_use_closure=no) + + KDE_NO_UNDEFINED="" + if test "x$kde_use_closure" = "xyes"; then + KDE_USE_CLOSURE_TRUE="" + KDE_USE_CLOSURE_FALSE="#" +# CXXFLAGS="$CXXFLAGS $REPO" + else + KDE_USE_CLOSURE_TRUE="#" + KDE_USE_CLOSURE_FALSE="" + KDE_NO_UNDEFINED="" + case $host in + *-*-linux-gnu) + KDE_CHECK_COMPILER_FLAG([Wl,--no-undefined], + [KDE_CHECK_COMPILER_FLAG([Wl,--allow-shlib-undefined], + [KDE_NO_UNDEFINED="-Wl,--no-undefined -Wl,--allow-shlib-undefined"], + [KDE_NO_UNDEFINED=""])], + [KDE_NO_UNDEFINED=""]) + ;; + esac + fi + AC_SUBST(KDE_USE_CLOSURE_TRUE) + AC_SUBST(KDE_USE_CLOSURE_FALSE) + AC_SUBST(KDE_NO_UNDEFINED) +]) + +dnl Check if the linker supports --enable-new-dtags and --as-needed +AC_DEFUN([KDE_CHECK_NEW_LDFLAGS], +[ + AC_ARG_ENABLE(new_ldflags, + AC_HELP_STRING([--enable-new-ldflags], + [enable the new linker flags]), + kde_use_new_ldflags=$enableval, + kde_use_new_ldflags=no) + + LDFLAGS_AS_NEEDED="" + LDFLAGS_NEW_DTAGS="" + if test "x$kde_use_new_ldflags" = "xyes"; then + LDFLAGS_NEW_DTAGS="" + KDE_CHECK_COMPILER_FLAG([Wl,--enable-new-dtags], + [LDFLAGS_NEW_DTAGS="-Wl,--enable-new-dtags"],) + + KDE_CHECK_COMPILER_FLAG([Wl,--as-needed], + [LDFLAGS_AS_NEEDED="-Wl,--as-needed"],) + fi + AC_SUBST(LDFLAGS_AS_NEEDED) + AC_SUBST(LDFLAGS_NEW_DTAGS) +]) + +AC_DEFUN([KDE_CHECK_NMCHECK], +[ + AC_ARG_ENABLE(nmcheck,AC_HELP_STRING([--enable-nmcheck],[enable automatic namespace cleanness check]), + kde_use_nmcheck=$enableval, kde_use_nmcheck=no) + + if test "$kde_use_nmcheck" = "yes"; then + KDE_USE_NMCHECK_TRUE="" + KDE_USE_NMCHECK_FALSE="#" + else + KDE_USE_NMCHECK_TRUE="#" + KDE_USE_NMCHECK_FALSE="" + fi + AC_SUBST(KDE_USE_NMCHECK_TRUE) + AC_SUBST(KDE_USE_NMCHECK_FALSE) +]) + +AC_DEFUN([KDE_EXPAND_MAKEVAR], [ +savex=$exec_prefix +test "x$exec_prefix" = xNONE && exec_prefix=$prefix +tmp=$$2 +while $1=`eval echo "$tmp"`; test "x$$1" != "x$tmp"; do tmp=$$1; done +exec_prefix=$savex +]) + +dnl ------------------------------------------------------------------------ +dnl Now, the same with KDE +dnl $(KDE_LDFLAGS) will be the kdeliblocation (if needed) +dnl and $(kde_includes) will be the kdehdrlocation (if needed) +dnl ------------------------------------------------------------------------ +dnl +AC_DEFUN([AC_BASE_PATH_KDE], +[ +AC_REQUIRE([KDE_CHECK_STL]) +AC_REQUIRE([AC_PATH_QT])dnl +AC_REQUIRE([KDE_CHECK_LIB64]) + +AC_CHECK_RPATH +AC_MSG_CHECKING([for KDE]) + +if test "${prefix}" != NONE; then + kde_includes=${includedir} + KDE_EXPAND_MAKEVAR(ac_kde_includes, includedir) + + kde_libraries=${libdir} + KDE_EXPAND_MAKEVAR(ac_kde_libraries, libdir) + +else + ac_kde_includes= + ac_kde_libraries= + kde_libraries="" + kde_includes="" +fi + +AC_CACHE_VAL(ac_cv_have_kde, +[#try to guess kde locations + +if test "$kde_qtver" = 1; then + kde_check_header="ksock.h" + kde_check_lib="libkdecore.la" +else + kde_check_header="ksharedptr.h" + kde_check_lib="libkio.la" +fi + +if test -z "$1"; then + +kde_incdirs="$kde_libs_prefix/include /usr/lib/kde/include /usr/local/kde/include /usr/local/include /usr/kde/include /usr/include/kde /usr/include /opt/kde3/include /opt/kde/include $x_includes $qt_includes" +test -n "$KDEDIR" && kde_incdirs="$KDEDIR/include $KDEDIR/include/kde $KDEDIR $kde_incdirs" +kde_incdirs="$ac_kde_includes $kde_incdirs" +AC_FIND_FILE($kde_check_header, $kde_incdirs, kde_incdir) +ac_kde_includes="$kde_incdir" + +if test -n "$ac_kde_includes" && test ! -r "$ac_kde_includes/$kde_check_header"; then + AC_MSG_ERROR([ +in the prefix, you've chosen, are no KDE headers installed. This will fail. +So, check this please and use another prefix!]) +fi + +kde_libdirs="$kde_libs_prefix/lib${kdelibsuff} /usr/lib/kde/lib${kdelibsuff} /usr/local/kde/lib${kdelibsuff} /usr/kde/lib${kdelibsuff} /usr/lib${kdelibsuff}/kde /usr/lib${kdelibsuff}/kde3 /usr/lib${kdelibsuff} /usr/X11R6/lib${kdelibsuff} /usr/local/lib${kdelibsuff} /opt/kde3/lib${kdelibsuff} /opt/kde/lib${kdelibsuff} /usr/X11R6/kde/lib${kdelibsuff}" +test -n "$KDEDIR" && kde_libdirs="$KDEDIR/lib${kdelibsuff} $KDEDIR $kde_libdirs" +kde_libdirs="$ac_kde_libraries $libdir $kde_libdirs" +AC_FIND_FILE($kde_check_lib, $kde_libdirs, kde_libdir) +ac_kde_libraries="$kde_libdir" + +kde_widgetdir=NO +dnl this might be somewhere else +AC_FIND_FILE("kde3/plugins/designer/kdewidgets.la", $kde_libdirs, kde_widgetdir) + +if test -n "$ac_kde_libraries" && test ! -r "$ac_kde_libraries/$kde_check_lib"; then +AC_MSG_ERROR([ +in the prefix, you've chosen, are no KDE libraries installed. This will fail. +So, check this please and use another prefix!]) +fi + +if test -n "$kde_widgetdir" && test ! -r "$kde_widgetdir/kde3/plugins/designer/kdewidgets.la"; then +AC_MSG_ERROR([ +I can't find the designer plugins. These are required and should have been installed +by kdelibs]) +fi + +if test -n "$kde_widgetdir"; then + kde_widgetdir="$kde_widgetdir/kde3/plugins/designer" +fi + + +if test "$ac_kde_includes" = NO || test "$ac_kde_libraries" = NO || test "$kde_widgetdir" = NO; then + ac_cv_have_kde="have_kde=no" +else + ac_cv_have_kde="have_kde=yes \ + ac_kde_includes=$ac_kde_includes ac_kde_libraries=$ac_kde_libraries" +fi + +else dnl test -z $1, e.g. from kdelibs + + ac_cv_have_kde="have_kde=no" + +fi +])dnl + +eval "$ac_cv_have_kde" + +if test "$have_kde" != "yes"; then + if test "${prefix}" = NONE; then + ac_kde_prefix="$ac_default_prefix" + else + ac_kde_prefix="$prefix" + fi + if test "$exec_prefix" = NONE; then + ac_kde_exec_prefix="$ac_kde_prefix" + AC_MSG_RESULT([will be installed in $ac_kde_prefix]) + else + ac_kde_exec_prefix="$exec_prefix" + AC_MSG_RESULT([will be installed in $ac_kde_prefix and $ac_kde_exec_prefix]) + fi + + kde_libraries="${libdir}" + kde_includes="${includedir}" + +else + ac_cv_have_kde="have_kde=yes \ + ac_kde_includes=$ac_kde_includes ac_kde_libraries=$ac_kde_libraries" + AC_MSG_RESULT([libraries $ac_kde_libraries, headers $ac_kde_includes]) + + kde_libraries="$ac_kde_libraries" + kde_includes="$ac_kde_includes" +fi +AC_SUBST(kde_libraries) +AC_SUBST(kde_includes) + +if test "$kde_includes" = "$x_includes" || test "$kde_includes" = "$qt_includes" || test "$kde_includes" = "/usr/include"; then + KDE_INCLUDES="" +else + KDE_INCLUDES="-I$kde_includes" + all_includes="$KDE_INCLUDES $all_includes" +fi + +KDE_DEFAULT_CXXFLAGS="-DQT_CLEAN_NAMESPACE -DQT_NO_ASCII_CAST -DQT_NO_STL -DQT_NO_COMPAT -DQT_NO_TRANSLATION" + +KDE_LDFLAGS="-L$kde_libraries" +if test ! "$kde_libraries" = "$x_libraries" && test ! "$kde_libraries" = "$qt_libraries" ; then + all_libraries="$KDE_LDFLAGS $all_libraries" +fi + +AC_SUBST(KDE_LDFLAGS) +AC_SUBST(KDE_INCLUDES) + +AC_REQUIRE([KDE_CHECK_EXTRA_LIBS]) + +all_libraries="$all_libraries $USER_LDFLAGS" +all_includes="$all_includes $USER_INCLUDES" +AC_SUBST(all_includes) +AC_SUBST(all_libraries) + +if test -z "$1"; then +KDE_CHECK_UIC_PLUGINS +fi + +ac_kde_libraries="$kde_libdir" + +AC_SUBST(AUTODIRS) + + +]) + +AC_DEFUN([KDE_CHECK_EXTRA_LIBS], +[ +AC_MSG_CHECKING(for extra includes) +AC_ARG_WITH(extra-includes,AC_HELP_STRING([--with-extra-includes=DIR],[adds non standard include paths]), + kde_use_extra_includes="$withval", + kde_use_extra_includes=NONE +) +kde_extra_includes= +if test -n "$kde_use_extra_includes" && \ + test "$kde_use_extra_includes" != "NONE"; then + + ac_save_ifs=$IFS + IFS=':' + for dir in $kde_use_extra_includes; do + kde_extra_includes="$kde_extra_includes $dir" + USER_INCLUDES="$USER_INCLUDES -I$dir" + done + IFS=$ac_save_ifs + kde_use_extra_includes="added" +else + kde_use_extra_includes="no" +fi +AC_SUBST(USER_INCLUDES) + +AC_MSG_RESULT($kde_use_extra_includes) + +kde_extra_libs= +AC_MSG_CHECKING(for extra libs) +AC_ARG_WITH(extra-libs,AC_HELP_STRING([--with-extra-libs=DIR],[adds non standard library paths]), + kde_use_extra_libs=$withval, + kde_use_extra_libs=NONE +) +if test -n "$kde_use_extra_libs" && \ + test "$kde_use_extra_libs" != "NONE"; then + + ac_save_ifs=$IFS + IFS=':' + for dir in $kde_use_extra_libs; do + kde_extra_libs="$kde_extra_libs $dir" + KDE_EXTRA_RPATH="$KDE_EXTRA_RPATH -R $dir" + USER_LDFLAGS="$USER_LDFLAGS -L$dir" + done + IFS=$ac_save_ifs + kde_use_extra_libs="added" +else + kde_use_extra_libs="no" +fi + +AC_SUBST(USER_LDFLAGS) + +AC_MSG_RESULT($kde_use_extra_libs) + +]) + +AC_DEFUN([KDE_1_CHECK_PATH_HEADERS], +[ + AC_MSG_CHECKING([for KDE headers installed]) + AC_LANG_SAVE + AC_LANG_CPLUSPLUS +cat > conftest.$ac_ext < +#endif +#include +#include "confdefs.h" +#include + +int main() { + printf("kde_htmldir=\\"%s\\"\n", KApplication::kde_htmldir().data()); + printf("kde_appsdir=\\"%s\\"\n", KApplication::kde_appsdir().data()); + printf("kde_icondir=\\"%s\\"\n", KApplication::kde_icondir().data()); + printf("kde_sounddir=\\"%s\\"\n", KApplication::kde_sounddir().data()); + printf("kde_datadir=\\"%s\\"\n", KApplication::kde_datadir().data()); + printf("kde_locale=\\"%s\\"\n", KApplication::kde_localedir().data()); + printf("kde_cgidir=\\"%s\\"\n", KApplication::kde_cgidir().data()); + printf("kde_confdir=\\"%s\\"\n", KApplication::kde_configdir().data()); + printf("kde_mimedir=\\"%s\\"\n", KApplication::kde_mimedir().data()); + printf("kde_toolbardir=\\"%s\\"\n", KApplication::kde_toolbardir().data()); + printf("kde_wallpaperdir=\\"%s\\"\n", + KApplication::kde_wallpaperdir().data()); + printf("kde_bindir=\\"%s\\"\n", KApplication::kde_bindir().data()); + printf("kde_partsdir=\\"%s\\"\n", KApplication::kde_partsdir().data()); + printf("kde_servicesdir=\\"/tmp/dummy\\"\n"); + printf("kde_servicetypesdir=\\"/tmp/dummy\\"\n"); + printf("kde_moduledir=\\"/tmp/dummy\\"\n"); + printf("kde_styledir=\\"/tmp/dummy\\"\n"); + printf("kde_widgetdir=\\"/tmp/dummy\\"\n"); + printf("xdg_appsdir=\\"/tmp/dummy\\"\n"); + printf("xdg_menudir=\\"/tmp/dummy\\"\n"); + printf("xdg_directorydir=\\"/tmp/dummy\\"\n"); + printf("kde_kcfgdir=\\"/tmp/dummy\\"\n"); + return 0; + } +EOF + + ac_save_CPPFLAGS=$CPPFLAGS + CPPFLAGS="$all_includes $CPPFLAGS" + if AC_TRY_EVAL(ac_compile); then + AC_MSG_RESULT(yes) + else + AC_MSG_ERROR([your system is not able to compile a small KDE application! +Check, if you installed the KDE header files correctly. +For more details about this problem, look at the end of config.log.]) + fi + CPPFLAGS=$ac_save_CPPFLAGS + + AC_LANG_RESTORE +]) + +AC_DEFUN([KDE_CHECK_KDEQTADDON], +[ +AC_MSG_CHECKING(for kde-qt-addon) +AC_CACHE_VAL(kde_cv_have_kdeqtaddon, +[ + kde_ldflags_safe="$LDFLAGS" + kde_libs_safe="$LIBS" + kde_cxxflags_safe="$CXXFLAGS" + + LIBS="-lkde-qt-addon $LIBQT $LIBS" + CXXFLAGS="$CXXFLAGS -I$prefix/include -I$prefix/include/kde $all_includes" + LDFLAGS="$LDFLAGS $all_libraries $USER_LDFLAGS" + + AC_TRY_LINK([ + #include + ], + [ + QDomDocument doc; + ], + kde_cv_have_kdeqtaddon=yes, + kde_cv_have_kdeqtaddon=no + ) + + LDFLAGS=$kde_ldflags_safe + LIBS=$kde_libs_safe + CXXFLAGS=$kde_cxxflags_safe +]) + +AC_MSG_RESULT($kde_cv_have_kdeqtaddon) + +if test "$kde_cv_have_kdeqtaddon" = "no"; then + AC_MSG_ERROR([Can't find libkde-qt-addon. You need to install it first. +It is a separate package (and CVS module) named kde-qt-addon.]) +fi +]) + +AC_DEFUN([KDE_CREATE_LIBS_ALIASES], +[ + AC_REQUIRE([KDE_MISC_TESTS]) + AC_REQUIRE([KDE_CHECK_LIBDL]) + AC_REQUIRE([K_PATH_X]) + +if test $kde_qtver = 3; then + case $host in + *cygwin*) lib_kded="-lkdeinit_kded" ;; + *) lib_kded="" ;; + esac + AC_SUBST(LIB_KDED, $lib_kded) + AC_SUBST(LIB_KDECORE, "-lkdecore") + AC_SUBST(LIB_KDEUI, "-lkdeui") + AC_SUBST(LIB_KIO, "-lkio") + AC_SUBST(LIB_KJS, "-lkjs") + AC_SUBST(LIB_SMB, "-lsmb") + AC_SUBST(LIB_KAB, "-lkab") + AC_SUBST(LIB_KABC, "-lkabc") + AC_SUBST(LIB_KHTML, "-lkhtml") + AC_SUBST(LIB_KSPELL, "-lkspell") + AC_SUBST(LIB_KPARTS, "-lkparts") + AC_SUBST(LIB_KDEPRINT, "-lkdeprint") + AC_SUBST(LIB_KUTILS, "-lkutils") + AC_SUBST(LIB_KDEPIM, "-lkdepim") + AC_SUBST(LIB_KIMPROXY, "-lkimproxy") + AC_SUBST(LIB_KNEWSTUFF, "-lknewstuff") + AC_SUBST(LIB_KDNSSD, "-lkdnssd") + AC_SUBST(LIB_KUNITTEST, "-lkunittest") +# these are for backward compatibility + AC_SUBST(LIB_KSYCOCA, "-lkio") + AC_SUBST(LIB_KFILE, "-lkio") +elif test $kde_qtver = 2; then + AC_SUBST(LIB_KDECORE, "-lkdecore") + AC_SUBST(LIB_KDEUI, "-lkdeui") + AC_SUBST(LIB_KIO, "-lkio") + AC_SUBST(LIB_KSYCOCA, "-lksycoca") + AC_SUBST(LIB_SMB, "-lsmb") + AC_SUBST(LIB_KFILE, "-lkfile") + AC_SUBST(LIB_KAB, "-lkab") + AC_SUBST(LIB_KHTML, "-lkhtml") + AC_SUBST(LIB_KSPELL, "-lkspell") + AC_SUBST(LIB_KPARTS, "-lkparts") + AC_SUBST(LIB_KDEPRINT, "-lkdeprint") +else + AC_SUBST(LIB_KDECORE, "-lkdecore -lXext $(LIB_QT)") + AC_SUBST(LIB_KDEUI, "-lkdeui $(LIB_KDECORE)") + AC_SUBST(LIB_KFM, "-lkfm $(LIB_KDECORE)") + AC_SUBST(LIB_KFILE, "-lkfile $(LIB_KFM) $(LIB_KDEUI)") + AC_SUBST(LIB_KAB, "-lkab $(LIB_KIMGIO) $(LIB_KDECORE)") +fi +]) + +AC_DEFUN([AC_PATH_KDE], +[ + AC_BASE_PATH_KDE + AC_ARG_ENABLE(path-check,AC_HELP_STRING([--disable-path-check],[don't try to find out, where to install]), + [ + if test "$enableval" = "no"; + then ac_use_path_checking="default" + else ac_use_path_checking="" + fi + ], + [ + if test "$kde_qtver" = 1; + then ac_use_path_checking="" + else ac_use_path_checking="default" + fi + ] + ) + + AC_CREATE_KFSSTND($ac_use_path_checking) + + AC_SUBST_KFSSTND + KDE_CREATE_LIBS_ALIASES +]) + +dnl KDE_CHECK_FUNC_EXT(, [headers], [sample-use], [C prototype], [autoheader define], [call if found]) +AC_DEFUN([KDE_CHECK_FUNC_EXT], +[ +AC_MSG_CHECKING(for $1) +AC_CACHE_VAL(kde_cv_func_$1, +[ +AC_LANG_SAVE +AC_LANG_CPLUSPLUS +save_CXXFLAGS="$CXXFLAGS" +kde_safe_LIBS="$LIBS" +LIBS="$LIBS $X_EXTRA_LIBS" +if test "$GXX" = "yes"; then +CXXFLAGS="$CXXFLAGS -pedantic-errors" +fi +AC_TRY_COMPILE([ +$2 +], +[ +$3 +], +kde_cv_func_$1=yes, +kde_cv_func_$1=no) +CXXFLAGS="$save_CXXFLAGS" +LIBS="$kde_safe_LIBS" +AC_LANG_RESTORE +]) + +AC_MSG_RESULT($kde_cv_func_$1) + +AC_MSG_CHECKING([if $1 needs custom prototype]) +AC_CACHE_VAL(kde_cv_proto_$1, +[ +if test "x$kde_cv_func_$1" = xyes; then + kde_cv_proto_$1=no +else + case "$1" in + setenv|unsetenv|usleep|random|srandom|seteuid|mkstemps|mkstemp|revoke|vsnprintf|strlcpy|strlcat) + kde_cv_proto_$1="yes - in libkdefakes" + ;; + *) + kde_cv_proto_$1=unknown + ;; + esac +fi + +if test "x$kde_cv_proto_$1" = xunknown; then + +AC_LANG_SAVE +AC_LANG_CPLUSPLUS + kde_safe_libs=$LIBS + LIBS="$LIBS $X_EXTRA_LIBS" + AC_TRY_LINK([ +$2 + +extern "C" $4; +], +[ +$3 +], +[ kde_cv_func_$1=yes + kde_cv_proto_$1=yes ], + [kde_cv_proto_$1="$1 unavailable"] +) +LIBS=$kde_safe_libs +AC_LANG_RESTORE +fi +]) +AC_MSG_RESULT($kde_cv_proto_$1) + +if test "x$kde_cv_func_$1" = xyes; then + AC_DEFINE(HAVE_$5, 1, [Define if you have $1]) + $6 +fi +if test "x$kde_cv_proto_$1" = xno; then + AC_DEFINE(HAVE_$5_PROTO, 1, + [Define if you have the $1 prototype]) +fi + +AH_VERBATIM([_HAVE_$5_PROTO], +[ +#if !defined(HAVE_$5_PROTO) +#ifdef __cplusplus +extern "C" { +#endif +$4; +#ifdef __cplusplus +} +#endif +#endif +]) +]) + +AC_DEFUN([AC_CHECK_SETENV], +[ + KDE_CHECK_FUNC_EXT(setenv, [ +#include +], + [setenv("VAR", "VALUE", 1);], + [int setenv (const char *, const char *, int)], + [SETENV]) +]) + +AC_DEFUN([AC_CHECK_UNSETENV], +[ + KDE_CHECK_FUNC_EXT(unsetenv, [ +#include +], + [unsetenv("VAR");], + [void unsetenv (const char *)], + [UNSETENV]) +]) + +AC_DEFUN([AC_CHECK_GETDOMAINNAME], +[ + KDE_CHECK_FUNC_EXT(getdomainname, [ +#include +#include +#include +], + [ +char buffer[200]; +getdomainname(buffer, 200); +], + [#include + int getdomainname (char *, size_t)], + [GETDOMAINNAME]) +]) + +AC_DEFUN([AC_CHECK_GETHOSTNAME], +[ + KDE_CHECK_FUNC_EXT(gethostname, [ +#include +#include +], + [ +char buffer[200]; +gethostname(buffer, 200); +], + [int gethostname (char *, unsigned int)], + [GETHOSTNAME]) +]) + +AC_DEFUN([AC_CHECK_USLEEP], +[ + KDE_CHECK_FUNC_EXT(usleep, [ +#include +], + [ +usleep(200); +], + [int usleep (unsigned int)], + [USLEEP]) +]) + + +AC_DEFUN([AC_CHECK_RANDOM], +[ + KDE_CHECK_FUNC_EXT(random, [ +#include +], + [ +random(); +], + [long int random(void)], + [RANDOM]) + + KDE_CHECK_FUNC_EXT(srandom, [ +#include +], + [ +srandom(27); +], + [void srandom(unsigned int)], + [SRANDOM]) + +]) + +AC_DEFUN([AC_CHECK_INITGROUPS], +[ + KDE_CHECK_FUNC_EXT(initgroups, [ +#include +#include +#include +], + [ +char buffer[200]; +initgroups(buffer, 27); +], + [int initgroups(const char *, gid_t)], + [INITGROUPS]) +]) + +AC_DEFUN([AC_CHECK_MKSTEMPS], +[ + KDE_CHECK_FUNC_EXT(mkstemps, [ +#include +#include +], + [ +mkstemps("/tmp/aaaXXXXXX", 6); +], + [int mkstemps(char *, int)], + [MKSTEMPS]) +]) + +AC_DEFUN([AC_CHECK_MKSTEMP], +[ + KDE_CHECK_FUNC_EXT(mkstemp, [ +#include +#include +], + [ +mkstemp("/tmp/aaaXXXXXX"); +], + [int mkstemp(char *)], + [MKSTEMP]) +]) + +AC_DEFUN([AC_CHECK_MKDTEMP], +[ + KDE_CHECK_FUNC_EXT(mkdtemp, [ +#include +#include +], + [ +mkdtemp("/tmp/aaaXXXXXX"); +], + [char *mkdtemp(char *)], + [MKDTEMP]) +]) + + +AC_DEFUN([AC_CHECK_RES_INIT], +[ + AC_MSG_CHECKING([if res_init needs -lresolv]) + kde_libs_safe="$LIBS" + LIBS="$LIBS $X_EXTRA_LIBS -lresolv" + AC_TRY_LINK( + [ +#include +#include +#include +#include + ], + [ + res_init(); + ], + [ + LIBRESOLV="-lresolv" + AC_MSG_RESULT(yes) + AC_DEFINE(HAVE_RES_INIT, 1, [Define if you have the res_init function]) + ], + [ AC_MSG_RESULT(no) ] + ) + LIBS=$kde_libs_safe + AC_SUBST(LIBRESOLV) + + KDE_CHECK_FUNC_EXT(res_init, + [ +#include +#include +#include +#include + ], + [res_init()], + [int res_init(void)], + [RES_INIT]) +]) + +AC_DEFUN([AC_CHECK_STRLCPY], +[ + KDE_CHECK_FUNC_EXT(strlcpy, [ +#include +], +[ char buf[20]; + strlcpy(buf, "KDE function test", sizeof(buf)); +], + [unsigned long strlcpy(char*, const char*, unsigned long)], + [STRLCPY]) +]) + +AC_DEFUN([AC_CHECK_STRLCAT], +[ + KDE_CHECK_FUNC_EXT(strlcat, [ +#include +], +[ char buf[20]; + buf[0]='\0'; + strlcat(buf, "KDE function test", sizeof(buf)); +], + [unsigned long strlcat(char*, const char*, unsigned long)], + [STRLCAT]) +]) + +AC_DEFUN([AC_CHECK_RES_QUERY], +[ + KDE_CHECK_FUNC_EXT(res_query, [ +#include +#include +#include +#include +#include +], +[ +res_query(NULL, 0, 0, NULL, 0); +], + [int res_query(const char *, int, int, unsigned char *, int)], + [RES_QUERY]) +]) + +AC_DEFUN([AC_CHECK_DN_SKIPNAME], +[ + KDE_CHECK_FUNC_EXT(dn_skipname, [ +#include +#include +#include +#include +], +[ +dn_skipname (NULL, NULL); +], + [int dn_skipname (unsigned char *, unsigned char *)], + [DN_SKIPNAME]) +]) + + +AC_DEFUN([AC_FIND_GIF], + [AC_MSG_CHECKING([for giflib]) +AC_CACHE_VAL(ac_cv_lib_gif, +[ac_save_LIBS="$LIBS" +if test "x$kde_use_qt_emb" != "xyes" && test "x$kde_use_qt_mac" != "xyes"; then +LIBS="$all_libraries -lgif -lX11 $LIBSOCKET" +else +LIBS="$all_libraries -lgif" +fi +AC_TRY_LINK(dnl +[ +#ifdef __cplusplus +extern "C" { +#endif +int GifLastError(void); +#ifdef __cplusplus +} +#endif +/* We use char because int might match the return type of a gcc2 + builtin and then its argument prototype would still apply. */ +], + [return GifLastError();], + eval "ac_cv_lib_gif=yes", + eval "ac_cv_lib_gif=no") +LIBS="$ac_save_LIBS" +])dnl +if eval "test \"`echo $ac_cv_lib_gif`\" = yes"; then + AC_MSG_RESULT(yes) + AC_DEFINE_UNQUOTED(HAVE_LIBGIF, 1, [Define if you have libgif]) +else + AC_MSG_ERROR(You need giflib30. Please install the kdesupport package) +fi +]) + +AC_DEFUN([KDE_FIND_JPEG_HELPER], +[ +AC_MSG_CHECKING([for libjpeg$2]) +AC_CACHE_VAL(ac_cv_lib_jpeg_$1, +[ +ac_save_LIBS="$LIBS" +LIBS="$all_libraries $USER_LDFLAGS -ljpeg$2 -lm" +ac_save_CFLAGS="$CFLAGS" +CFLAGS="$CFLAGS $all_includes $USER_INCLUDES" +AC_TRY_LINK( +[ +#ifdef __cplusplus +extern "C" { +#endif +void jpeg_CreateDecompress(); +#ifdef __cplusplus +} +#endif +], +[jpeg_CreateDecompress();], + eval "ac_cv_lib_jpeg_$1=-ljpeg$2", + eval "ac_cv_lib_jpeg_$1=no") +LIBS="$ac_save_LIBS" +CFLAGS="$ac_save_CFLAGS" +]) + +if eval "test ! \"`echo $ac_cv_lib_jpeg_$1`\" = no"; then + LIBJPEG="$ac_cv_lib_jpeg_$1" + AC_MSG_RESULT($ac_cv_lib_jpeg_$1) +else + AC_MSG_RESULT(no) + $3 +fi + +]) + +AC_DEFUN([AC_FIND_JPEG], +[ +dnl first look for libraries +KDE_FIND_JPEG_HELPER(6b, 6b, + KDE_FIND_JPEG_HELPER(normal, [], + [ + LIBJPEG= + ] + ) +) + +dnl then search the headers (can't use simply AC_TRY_xxx, as jpeglib.h +dnl requires system dependent includes loaded before it) +jpeg_incdirs="$includedir /usr/include /usr/local/include $kde_extra_includes" +AC_FIND_FILE(jpeglib.h, $jpeg_incdirs, jpeg_incdir) +test "x$jpeg_incdir" = xNO && jpeg_incdir= + +dnl if headers _and_ libraries are missing, this is no error, and we +dnl continue with a warning (the user will get no jpeg support in khtml) +dnl if only one is missing, it means a configuration error, but we still +dnl only warn +if test -n "$jpeg_incdir" && test -n "$LIBJPEG" ; then + AC_DEFINE_UNQUOTED(HAVE_LIBJPEG, 1, [Define if you have libjpeg]) +else + if test -n "$jpeg_incdir" || test -n "$LIBJPEG" ; then + AC_MSG_WARN([ +There is an installation error in jpeg support. You seem to have only one +of either the headers _or_ the libraries installed. You may need to either +provide correct --with-extra-... options, or the development package of +libjpeg6b. You can get a source package of libjpeg from http://www.ijg.org/ +Disabling JPEG support. +]) + else + AC_MSG_WARN([libjpeg not found. disable JPEG support.]) + fi + jpeg_incdir= + LIBJPEG= +fi + +AC_SUBST(LIBJPEG) +AH_VERBATIM(_AC_CHECK_JPEG, +[/* + * jpeg.h needs HAVE_BOOLEAN, when the system uses boolean in system + * headers and I'm too lazy to write a configure test as long as only + * unixware is related + */ +#ifdef _UNIXWARE +#define HAVE_BOOLEAN +#endif +]) +]) + +AC_DEFUN([KDE_CHECK_QT_JPEG], +[ +if test -n "$LIBJPEG"; then +AC_MSG_CHECKING([if Qt needs $LIBJPEG]) +AC_CACHE_VAL(kde_cv_qt_jpeg, +[ +AC_LANG_SAVE +AC_LANG_CPLUSPLUS +ac_save_LIBS="$LIBS" +LIBS="$all_libraries $USER_LDFLAGS $LIBQT" +LIBS=`echo $LIBS | sed "s/$LIBJPEG//"` +ac_save_CXXFLAGS="$CXXFLAGS" +CXXFLAGS="$CXXFLAGS $all_includes $USER_INCLUDES" +AC_TRY_LINK( +[#include ], + [ + int argc; + char** argv; + QApplication app(argc, argv);], + eval "kde_cv_qt_jpeg=no", + eval "kde_cv_qt_jpeg=yes") +LIBS="$ac_save_LIBS" +CXXFLAGS="$ac_save_CXXFLAGS" +AC_LANG_RESTORE +fi +]) + +if eval "test ! \"`echo $kde_cv_qt_jpeg`\" = no"; then + AC_MSG_RESULT(yes) + LIBJPEG_QT='$(LIBJPEG)' +else + AC_MSG_RESULT(no) + LIBJPEG_QT= +fi + +]) + +AC_DEFUN([AC_FIND_ZLIB], +[ +AC_REQUIRE([KDE_CHECK_EXTRA_LIBS]) +AC_MSG_CHECKING([for libz]) +AC_CACHE_VAL(ac_cv_lib_z, +[ +kde_save_LIBS="$LIBS" +LIBS="$all_libraries $USER_LDFLAGS -lz $LIBSOCKET" +kde_save_CFLAGS="$CFLAGS" +CFLAGS="$CFLAGS $all_includes $USER_INCLUDES" +AC_TRY_LINK(dnl +[ +#include +#include +], +[ + char buf[42]; + gzFile f = (gzFile) 0; + /* this would segfault.. but we only link, don't run */ + (void) gzgets(f, buf, sizeof(buf)); + + return (strcmp(zlibVersion(), ZLIB_VERSION) == 0); +], + eval "ac_cv_lib_z='-lz'", + eval "ac_cv_lib_z=no") +LIBS="$kde_save_LIBS" +CFLAGS="$kde_save_CFLAGS" +])dnl +if test ! "$ac_cv_lib_z" = no; then + AC_DEFINE_UNQUOTED(HAVE_LIBZ, 1, [Define if you have libz]) + LIBZ="$ac_cv_lib_z" + AC_MSG_RESULT($ac_cv_lib_z) +else + AC_MSG_ERROR(not found. + Possibly configure picks up an outdated version + installed by XFree86. Remove it from your system. + + Check your installation and look into config.log) + LIBZ="" +fi +AC_SUBST(LIBZ) +]) + +AC_DEFUN([KDE_TRY_TIFFLIB], +[ +AC_MSG_CHECKING([for libtiff $1]) + +AC_CACHE_VAL(kde_cv_libtiff_$1, +[ +AC_LANG_SAVE +AC_LANG_CPLUSPLUS +kde_save_LIBS="$LIBS" +if test "x$kde_use_qt_emb" != "xyes" && test "x$kde_use_qt_mac" != "xyes"; then +LIBS="$all_libraries $USER_LDFLAGS -l$1 $LIBJPEG $LIBZ -lX11 $LIBSOCKET -lm" +else +LIBS="$all_libraries $USER_LDFLAGS -l$1 $LIBJPEG $LIBZ -lm" +fi +kde_save_CXXFLAGS="$CXXFLAGS" +CXXFLAGS="$CXXFLAGS $all_includes $USER_INCLUDES" + +AC_TRY_LINK(dnl +[ +#include +], + [return (TIFFOpen( "", "r") == 0); ], +[ + kde_cv_libtiff_$1="-l$1 $LIBJPEG $LIBZ" +], [ + kde_cv_libtiff_$1=no +]) + +LIBS="$kde_save_LIBS" +CXXFLAGS="$kde_save_CXXFLAGS" +AC_LANG_RESTORE +]) + +if test "$kde_cv_libtiff_$1" = "no"; then + AC_MSG_RESULT(no) + LIBTIFF="" + $3 +else + LIBTIFF="$kde_cv_libtiff_$1" + AC_MSG_RESULT(yes) + AC_DEFINE_UNQUOTED(HAVE_LIBTIFF, 1, [Define if you have libtiff]) + $2 +fi + +]) + +AC_DEFUN([AC_FIND_TIFF], +[ +AC_REQUIRE([K_PATH_X]) +AC_REQUIRE([AC_FIND_ZLIB]) +AC_REQUIRE([AC_FIND_JPEG]) +AC_REQUIRE([KDE_CHECK_EXTRA_LIBS]) + +KDE_TRY_TIFFLIB(tiff, [], + KDE_TRY_TIFFLIB(tiff34)) + +AC_SUBST(LIBTIFF) +]) + +AC_DEFUN([KDE_FIND_LIBEXR], +[ +AC_REQUIRE([KDE_CHECK_EXTRA_LIBS]) +AC_REQUIRE([AC_FIND_ZLIB]) +AC_CACHE_VAL(ac_cv_libexr, +[ + if test -z "$PKG_CONFIG"; then + AC_PATH_PROG(PKG_CONFIG, pkg-config, no) + fi + + AC_MSG_CHECKING([for OpenEXR libraries]) + + if test "$PKG_CONFIG" = "no" ; then + AC_MSG_RESULT(no) + echo "*** The pkg-config script could not be found. Make sure it is" + echo "*** in your path, or set the PKG_CONFIG environment variable" + echo "*** to the full path to pkg-config." + echo "*** Or see http://www.freedesktop.org/software/pkgconfig to get pkg-config." + else + if ! $PKG_CONFIG --exists OpenEXR ; then + AC_MSG_RESULT(no) + EXRSTATUS=no + else + if ! $PKG_CONFIG --atleast-version="1.1.1" OpenEXR ; then + AC_MSG_RESULT(no) + EXRSTATUS=old + else + kde_save_LIBS="$LIBS" + LIBS="$LIBS $all_libraries $USER_LDFLAGS `pkg-config --libs OpenEXR` $LIBZ" + AC_LANG_SAVE + AC_LANG_CPLUSPLUS + kde_save_CXXFLAGS="$CXXFLAGS" + EXR_FLAGS=`$PKG_CONFIG --cflags OpenEXR` + CXXFLAGS="$CXXFLAGS $all_includes $USER_INCLUDES $EXR_FLAGS" + + AC_TRY_LINK(dnl + [ + #include + ], + [ + using namespace Imf; + RgbaInputFile file ("dummy"); + return 0; + ], + eval "ac_cv_libexr='`pkg-config --libs OpenEXR`'", + eval "ac_cv_libexr=no" + ) + LIBS="$kde_save_LIBS" + CXXFLAGS="$kde_save_CXXFLAGS" + AC_LANG_RESTORE + ])dnl + if eval "test ! \"`echo $ac_cv_libexr`\" = no"; then + AC_DEFINE_UNQUOTED(HAVE_EXR, 1, [Define if you have OpenEXR]) + LIB_EXR="$ac_cv_libexr" + AC_MSG_RESULT($ac_cv_libexr) + else + AC_MSG_RESULT(no) + LIB_EXR="" + fi + fi + fi + fi + AC_SUBST(LIB_EXR) + AC_SUBST(EXR_FLAGS) +]) + + + +AC_DEFUN([AC_FIND_PNG], +[ +AC_REQUIRE([KDE_CHECK_EXTRA_LIBS]) +AC_REQUIRE([AC_FIND_ZLIB]) +AC_MSG_CHECKING([for libpng]) +AC_CACHE_VAL(ac_cv_lib_png, +[ +kde_save_LIBS="$LIBS" +if test "x$kde_use_qt_emb" != "xyes" && test "x$kde_use_qt_mac" != "xyes"; then +LIBS="$LIBS $all_libraries $USER_LDFLAGS -lpng $LIBZ -lm -lX11 $LIBSOCKET" +else +LIBS="$LIBS $all_libraries $USER_LDFLAGS -lpng $LIBZ -lm" +fi +kde_save_CFLAGS="$CFLAGS" +CFLAGS="$CFLAGS $all_includes $USER_INCLUDES" + +AC_TRY_LINK(dnl + [ + #include + ], + [ + png_structp png_ptr = png_create_read_struct( /* image ptr */ + PNG_LIBPNG_VER_STRING, 0, 0, 0 ); + return( png_ptr != 0 ); + ], + eval "ac_cv_lib_png='-lpng $LIBZ -lm'", + eval "ac_cv_lib_png=no" +) +LIBS="$kde_save_LIBS" +CFLAGS="$kde_save_CFLAGS" +])dnl +if eval "test ! \"`echo $ac_cv_lib_png`\" = no"; then + AC_DEFINE_UNQUOTED(HAVE_LIBPNG, 1, [Define if you have libpng]) + LIBPNG="$ac_cv_lib_png" + AC_SUBST(LIBPNG) + AC_MSG_RESULT($ac_cv_lib_png) +else + AC_MSG_RESULT(no) + LIBPNG="" + AC_SUBST(LIBPNG) +fi +]) + + +AC_DEFUN([AC_FIND_JASPER], +[ +AC_REQUIRE([KDE_CHECK_EXTRA_LIBS]) +AC_REQUIRE([AC_FIND_JPEG]) +AC_MSG_CHECKING([for jasper]) +AC_CACHE_VAL(ac_cv_jasper, +[ +kde_save_LIBS="$LIBS" +LIBS="$LIBS $all_libraries $USER_LDFLAGS -ljasper $LIBJPEG -lm" +kde_save_CFLAGS="$CFLAGS" +CFLAGS="$CFLAGS $all_includes $USER_INCLUDES" + +AC_TRY_LINK(dnl + [ + #include + ], + [ + return( jas_init() ); + ], + eval "ac_cv_jasper='-ljasper $LIBJPEG -lm'", + eval "ac_cv_jasper=no" +) +LIBS="$kde_save_LIBS" +CFLAGS="$kde_save_CFLAGS" +])dnl +if eval "test ! \"`echo $ac_cv_jasper`\" = no"; then + AC_DEFINE_UNQUOTED(HAVE_JASPER, 1, [Define if you have jasper]) + LIB_JASPER="$ac_cv_jasper" + AC_MSG_RESULT($ac_cv_jasper) +else + AC_MSG_RESULT(no) + LIB_JASPER="" +fi +AC_SUBST(LIB_JASPER) +]) + +AC_DEFUN([AC_CHECK_BOOL], +[ + AC_DEFINE_UNQUOTED(HAVE_BOOL, 1, [You _must_ have bool]) +]) + +AC_DEFUN([AC_CHECK_GNU_EXTENSIONS], +[ +AC_MSG_CHECKING(if you need GNU extensions) +AC_CACHE_VAL(ac_cv_gnu_extensions, +[ +cat > conftest.c << EOF +#include + +#ifdef __GNU_LIBRARY__ +yes +#endif +EOF + +if (eval "$ac_cpp conftest.c") 2>&5 | + egrep "yes" >/dev/null 2>&1; then + rm -rf conftest* + ac_cv_gnu_extensions=yes +else + ac_cv_gnu_extensions=no +fi +]) + +AC_MSG_RESULT($ac_cv_gnu_extensions) +if test "$ac_cv_gnu_extensions" = "yes"; then + AC_DEFINE_UNQUOTED(_GNU_SOURCE, 1, [Define if you need to use the GNU extensions]) +fi +]) + +AC_DEFUN([KDE_CHECK_COMPILER_FLAG], +[ +AC_MSG_CHECKING([whether $CXX supports -$1]) +kde_cache=`echo $1 | sed 'y% .=/+-,%____p__%'` +AC_CACHE_VAL(kde_cv_prog_cxx_$kde_cache, +[ + AC_LANG_SAVE + AC_LANG_CPLUSPLUS + save_CXXFLAGS="$CXXFLAGS" + CXXFLAGS="$CXXFLAGS -$1" + AC_TRY_LINK([],[ return 0; ], [eval "kde_cv_prog_cxx_$kde_cache=yes"], []) + CXXFLAGS="$save_CXXFLAGS" + AC_LANG_RESTORE +]) +if eval "test \"`echo '$kde_cv_prog_cxx_'$kde_cache`\" = yes"; then + AC_MSG_RESULT(yes) + : + $2 +else + AC_MSG_RESULT(no) + : + $3 +fi +]) + +AC_DEFUN([KDE_CHECK_C_COMPILER_FLAG], +[ +AC_MSG_CHECKING([whether $CC supports -$1]) +kde_cache=`echo $1 | sed 'y% .=/+-,%____p__%'` +AC_CACHE_VAL(kde_cv_prog_cc_$kde_cache, +[ + AC_LANG_SAVE + AC_LANG_C + save_CFLAGS="$CFLAGS" + CFLAGS="$CFLAGS -$1" + AC_TRY_LINK([],[ return 0; ], [eval "kde_cv_prog_cc_$kde_cache=yes"], []) + CFLAGS="$save_CFLAGS" + AC_LANG_RESTORE +]) +if eval "test \"`echo '$kde_cv_prog_cc_'$kde_cache`\" = yes"; then + AC_MSG_RESULT(yes) + : + $2 +else + AC_MSG_RESULT(no) + : + $3 +fi +]) + + +dnl AC_REMOVE_FORBIDDEN removes forbidden arguments from variables +dnl use: AC_REMOVE_FORBIDDEN(CC, [-forbid -bad-option whatever]) +dnl it's all white-space separated +AC_DEFUN([AC_REMOVE_FORBIDDEN], +[ __val=$$1 + __forbid=" $2 " + if test -n "$__val"; then + __new="" + ac_save_IFS=$IFS + IFS=" " + for i in $__val; do + case "$__forbid" in + *" $i "*) AC_MSG_WARN([found forbidden $i in $1, removing it]) ;; + *) # Careful to not add spaces, where there were none, because otherwise + # libtool gets confused, if we change e.g. CXX + if test -z "$__new" ; then __new=$i ; else __new="$__new $i" ; fi ;; + esac + done + IFS=$ac_save_IFS + $1=$__new + fi +]) + + +AC_DEFUN([KDE_CHECK_FOR_BAD_COMPILER], +[ + AC_MSG_CHECKING([whether $CC is blacklisted]) + + dnl In theory we have tu run this test against $CC and $CXX + dnl in C and in C++ mode, because its perfectly legal for + dnl the user to mix compiler versions, since C has a defined + dnl ABI. + dnl + dnl For now, we assume the user is not on crack. + + AC_TRY_COMPILE([ +#ifdef __GNUC__ +#if __GNUC__ == 4 && __GNUC_MINOR__ == 0 && __GNUC_PATCHLEVEL__ == 0 +choke me +#endif +#endif +], , + kde_bad_compiler=no, + kde_bad_compiler=yes +) + + AC_MSG_RESULT($kde_bad_compiler) + +if test "$kde_bad_compiler" = "yes"; then + AC_MSG_ERROR([ + +This particular compiler version is blacklisted because it +is known to miscompile KDE. Please use a newer version, or +if that is not yet available, choose an older version. + +Please do not report a bug or bother us reporting this +configure error. We know about it, and we introduced +it by intention to avoid untraceable bugs or crashes in KDE. + +]) +fi + +]) + + +AC_DEFUN([KDE_CHECK_FOR_OPT_NOINLINE_MATCH], +[ + AC_CACHE_CHECK([whether system headers can cope with -O2 -fno-inline], + kde_cv_opt_noinline_match, + [ + kde_cv_opt_noinline_match=irrelevant + dnl if we don't use both -O2 and -fno-inline, this check is moot + if echo "$CFLAGS" | grep -e -O2 >/dev/null 2>/dev/null \ + && echo "$CFLAGS" | grep -e -fno-inline >/dev/null 2>/dev/null ; then + + ac_cflags_save="$CFLAGS" + CFLAGS="$CFLAGS -D_USE_GNU" + + AC_TRY_LINK([ + #include +], [ const char *pt, *et; + et = __extension__ ({ char __a0, __a1, __a2; (__builtin_constant_p ( ";," ) && ((size_t)(const void *)(( ";," )+ 1) - (size_t)(const void *)( ";," ) == 1) ? ((__a0 =((__const char *) ( ";," ))[0], __a0 == '\0') ? ((void) ( pt ),((void *)0) ) : ((__a1 = ((__const char *) ( ";," ))[1], __a1== '\0') ? (__extension__ (__builtin_constant_p ( __a0 ) && ( __a0 ) == '\0' ? (char *) __rawmemchr ( pt , __a0) : strchr( pt , __a0 ))) : ((__a2 = ((__const char *) ( ";," ))[2], __a2 == '\0') ? __strpbrk_c2 ( pt , __a0, __a1) :(((__const char *) ( ";," ))[3] == '\0' ? __strpbrk_c3 ( pt ,__a0, __a1, __a2): strpbrk ( pt , ";," ))))) : strpbrk ( pt , ";," )); }) ; +], + kde_cv_opt_noinline_match=yes, + kde_cv_opt_noinline_match=no + ) + + CFLAGS="$ac_cflags_save" + fi + ]) +]) + + +dnl AC_VALIDIFY_CXXFLAGS checks for forbidden flags the user may have given +AC_DEFUN([AC_VALIDIFY_CXXFLAGS], +[dnl +if test "x$kde_use_qt_emb" != "xyes"; then + AC_REMOVE_FORBIDDEN(CXX, [-fno-rtti -rpath]) + AC_REMOVE_FORBIDDEN(CXXFLAGS, [-fno-rtti -rpath]) +else + AC_REMOVE_FORBIDDEN(CXX, [-rpath]) + AC_REMOVE_FORBIDDEN(CXXFLAGS, [-rpath]) +fi +]) + +AC_DEFUN([AC_CHECK_COMPILERS], +[ + AC_ARG_ENABLE(debug, + AC_HELP_STRING([--enable-debug=ARG],[enables debug symbols (yes|no|full) [default=no]]), + [ + case $enableval in + yes) + kde_use_debug_code="yes" + kde_use_debug_define=no + ;; + full) + kde_use_debug_code="full" + kde_use_debug_define=no + ;; + *) + kde_use_debug_code="no" + kde_use_debug_define=yes + ;; + esac + ], + [kde_use_debug_code="no" + kde_use_debug_define=no + ]) + + dnl Just for configure --help + AC_ARG_ENABLE(dummyoption, + AC_HELP_STRING([--disable-debug], + [disables debug output and debug symbols [default=no]]), + [],[]) + + AC_ARG_ENABLE(strict, + AC_HELP_STRING([--enable-strict], + [compiles with strict compiler options (may not work!)]), + [ + if test $enableval = "no"; then + kde_use_strict_options="no" + else + kde_use_strict_options="yes" + fi + ], [kde_use_strict_options="no"]) + + AC_ARG_ENABLE(warnings,AC_HELP_STRING([--disable-warnings],[disables compilation with -Wall and similar]), + [ + if test $enableval = "no"; then + kde_use_warnings="no" + else + kde_use_warnings="yes" + fi + ], [kde_use_warnings="yes"]) + + dnl enable warnings for debug build + if test "$kde_use_debug_code" != "no"; then + kde_use_warnings=yes + fi + + AC_ARG_ENABLE(profile,AC_HELP_STRING([--enable-profile],[creates profiling infos [default=no]]), + [kde_use_profiling=$enableval], + [kde_use_profiling="no"] + ) + + dnl this prevents stupid AC_PROG_CC to add "-g" to the default CFLAGS + CFLAGS=" $CFLAGS" + + AC_PROG_CC + + AC_PROG_CPP + + if test "$GCC" = "yes"; then + if test "$kde_use_debug_code" != "no"; then + if test $kde_use_debug_code = "full"; then + CFLAGS="-g3 -fno-inline $CFLAGS" + else + CFLAGS="-g -O2 -fno-schedule-insns -fno-inline $CFLAGS" + fi + else + CFLAGS="-O2 $CFLAGS" + fi + fi + + if test "$kde_use_debug_define" = "yes"; then + CFLAGS="-DNDEBUG $CFLAGS" + fi + + + case "$host" in + *-*-sysv4.2uw*) CFLAGS="-D_UNIXWARE $CFLAGS";; + *-*-sysv5uw7*) CFLAGS="-D_UNIXWARE7 $CFLAGS";; + esac + + if test -z "$LDFLAGS" && test "$kde_use_debug_code" = "no" && test "$GCC" = "yes"; then + LDFLAGS="" + fi + + CXXFLAGS=" $CXXFLAGS" + + AC_PROG_CXX + + KDE_CHECK_FOR_BAD_COMPILER + + if test "$GXX" = "yes" || test "$CXX" = "KCC"; then + if test "$kde_use_debug_code" != "no"; then + if test "$CXX" = "KCC"; then + CXXFLAGS="+K0 -Wall -pedantic -W -Wpointer-arith -Wwrite-strings $CXXFLAGS" + else + if test "$kde_use_debug_code" = "full"; then + CXXFLAGS="-g3 -fno-inline $CXXFLAGS" + else + CXXFLAGS="-g -O2 -fno-schedule-insns -fno-inline $CXXFLAGS" + fi + fi + KDE_CHECK_COMPILER_FLAG(fno-builtin,[CXXFLAGS="-fno-builtin $CXXFLAGS"]) + + dnl convenience compiler flags + KDE_CHECK_COMPILER_FLAG(Woverloaded-virtual, [WOVERLOADED_VIRTUAL="-Woverloaded-virtual"], [WOVERLOADED_VRITUAL=""]) + AC_SUBST(WOVERLOADED_VIRTUAL) + else + if test "$CXX" = "KCC"; then + CXXFLAGS="+K3 $CXXFLAGS" + else + CXXFLAGS="-O2 $CXXFLAGS" + fi + fi + fi + + if test "$kde_use_debug_define" = "yes"; then + CXXFLAGS="-DNDEBUG -DNO_DEBUG $CXXFLAGS" + fi + + if test "$kde_use_profiling" = "yes"; then + KDE_CHECK_COMPILER_FLAG(pg, + [ + CFLAGS="-pg $CFLAGS" + CXXFLAGS="-pg $CXXFLAGS" + ]) + fi + + if test "$kde_use_warnings" = "yes"; then + if test "$GCC" = "yes"; then + CXXFLAGS="-Wall -W -Wpointer-arith $CXXFLAGS" + case $host in + *-*-linux-gnu) + CFLAGS="-std=iso9899:1990 -W -Wall -Wchar-subscripts -Wshadow -Wpointer-arith -Wmissing-prototypes -Wwrite-strings -D_XOPEN_SOURCE=500 -D_BSD_SOURCE $CFLAGS" + CXXFLAGS="-ansi -D_XOPEN_SOURCE=500 -D_BSD_SOURCE -Wcast-align -Wchar-subscripts $CXXFLAGS" + KDE_CHECK_COMPILER_FLAG(Wmissing-format-attribute, [CXXFLAGS="$CXXFLAGS -Wformat-security -Wmissing-format-attribute"]) + KDE_CHECK_C_COMPILER_FLAG(Wmissing-format-attribute, [CFLAGS="$CFLAGS -Wformat-security -Wmissing-format-attribute"]) + ;; + esac + KDE_CHECK_COMPILER_FLAG(Wundef,[CXXFLAGS="-Wundef $CXXFLAGS"]) + KDE_CHECK_COMPILER_FLAG(Wno-long-long,[CXXFLAGS="-Wno-long-long $CXXFLAGS"]) + dnl ### FIXME: revert for KDE 4 + KDE_CHECK_COMPILER_FLAG(Wno-non-virtual-dtor,[CXXFLAGS="$CXXFLAGS -Wno-non-virtual-dtor"]) + fi + fi + + if test "$GXX" = "yes" && test "$kde_use_strict_options" = "yes"; then + CXXFLAGS="-Wcast-qual -Wshadow -Wcast-align $CXXFLAGS" + fi + + AC_ARG_ENABLE(pch, + AC_HELP_STRING([--enable-pch], + [enables precompiled header support (currently only KCC or gcc >=3.4+unsermake) [default=no]]), + [ kde_use_pch=$enableval ],[ kde_use_pch=no ]) + + HAVE_GCC_VISIBILITY=0 + AC_SUBST([HAVE_GCC_VISIBILITY]) + + if test "$GXX" = "yes"; then + gcc_no_reorder_blocks=NO + KDE_CHECK_COMPILER_FLAG(fno-reorder-blocks,[gcc_no_reorder_blocks=YES]) + if test $kde_use_debug_code != "no" && \ + test $kde_use_debug_code != "full" && \ + test "YES" = "$gcc_no_reorder_blocks" ; then + CXXFLAGS="$CXXFLAGS -fno-reorder-blocks" + CFLAGS="$CFLAGS -fno-reorder-blocks" + fi + KDE_CHECK_COMPILER_FLAG(fno-exceptions,[CXXFLAGS="$CXXFLAGS -fno-exceptions"]) + KDE_CHECK_COMPILER_FLAG(fno-check-new, [CXXFLAGS="$CXXFLAGS -fno-check-new"]) + KDE_CHECK_COMPILER_FLAG(fno-common, [CXXFLAGS="$CXXFLAGS -fno-common"]) + KDE_CHECK_COMPILER_FLAG(fexceptions, [USE_EXCEPTIONS="-fexceptions"], USE_EXCEPTIONS= ) + ENABLE_PERMISSIVE_FLAG="-fpermissive" + + if test "$kde_use_pch" = "yes"; then + AC_MSG_CHECKING(whether gcc supports precompiling c header files) + echo >conftest.h + if $CC -x c-header conftest.h >/dev/null 2>/dev/null; then + kde_gcc_supports_pch=yes + AC_MSG_RESULT(yes) + else + kde_gcc_supports_pch=no + AC_MSG_RESULT(no) + fi + if test "$kde_gcc_supports_pch" = "yes"; then + AC_MSG_CHECKING(whether gcc supports precompiling c++ header files) + if $CXX -x c++-header conftest.h >/dev/null 2>/dev/null; then + kde_gcc_supports_pch=yes + AC_MSG_RESULT(yes) + else + kde_gcc_supports_pch=no + AC_MSG_RESULT(no) + fi + fi + rm -f conftest.h conftest.h.gch + fi + + KDE_CHECK_FOR_OPT_NOINLINE_MATCH + if test "x$kde_cv_opt_noinline_match" = "xno" ; then + CFLAGS="`echo "$CFLAGS" | sed "s/ -fno-inline//"`" + fi + fi + AM_CONDITIONAL(unsermake_enable_pch, test "$kde_use_pch" = "yes" && test "$kde_gcc_supports_pch" = "yes") + if test "$CXX" = "KCC"; then + dnl unfortunately we currently cannot disable exception support in KCC + dnl because doing so is binary incompatible and Qt by default links with exceptions :-( + dnl KDE_CHECK_COMPILER_FLAG(-no_exceptions,[CXXFLAGS="$CXXFLAGS --no_exceptions"]) + dnl KDE_CHECK_COMPILER_FLAG(-exceptions, [USE_EXCEPTIONS="--exceptions"], USE_EXCEPTIONS= ) + + if test "$kde_use_pch" = "yes"; then + dnl TODO: support --pch-dir! + KDE_CHECK_COMPILER_FLAG(-pch,[CXXFLAGS="$CXXFLAGS --pch"]) + dnl the below works (but the dir must exist), but it's + dnl useless for a whole package. + dnl The are precompiled headers for each source file, so when compiling + dnl from scratch, it doesn't make a difference, and they take up + dnl around ~5Mb _per_ sourcefile. + dnl KDE_CHECK_COMPILER_FLAG(-pch_dir /tmp, + dnl [CXXFLAGS="$CXXFLAGS --pch_dir `pwd`/pcheaders"]) + fi + dnl this flag controls inlining. by default KCC inlines in optimisation mode + dnl all implementations that are defined inside the class {} declaration. + dnl because of templates-compatibility with broken gcc compilers, this + dnl can cause excessive inlining. This flag limits it to a sane level + KDE_CHECK_COMPILER_FLAG(-inline_keyword_space_time=6,[CXXFLAGS="$CXXFLAGS --inline_keyword_space_time=6"]) + KDE_CHECK_COMPILER_FLAG(-inline_auto_space_time=2,[CXXFLAGS="$CXXFLAGS --inline_auto_space_time=2"]) + KDE_CHECK_COMPILER_FLAG(-inline_implicit_space_time=2.0,[CXXFLAGS="$CXXFLAGS --inline_implicit_space_time=2.0"]) + KDE_CHECK_COMPILER_FLAG(-inline_generated_space_time=2.0,[CXXFLAGS="$CXXFLAGS --inline_generated_space_time=2.0"]) + dnl Some source files are shared between multiple executables + dnl (or libraries) and some of those need template instantiations. + dnl In that case KCC needs to compile those sources with + dnl --one_instantiation_per_object. To make it easy for us we compile + dnl _all_ objects with that flag (--one_per is a shorthand). + KDE_CHECK_COMPILER_FLAG(-one_per, [CXXFLAGS="$CXXFLAGS --one_per"]) + fi + AC_SUBST(USE_EXCEPTIONS) + dnl obsolete macro - provided to keep things going + USE_RTTI= + AC_SUBST(USE_RTTI) + + case "$host" in + *-*-irix*) test "$GXX" = yes && CXXFLAGS="-D_LANGUAGE_C_PLUS_PLUS -D__LANGUAGE_C_PLUS_PLUS $CXXFLAGS" ;; + *-*-sysv4.2uw*) CXXFLAGS="-D_UNIXWARE $CXXFLAGS";; + *-*-sysv5uw7*) CXXFLAGS="-D_UNIXWARE7 $CXXFLAGS";; + *-*-solaris*) + if test "$GXX" = yes; then + libstdcpp=`$CXX -print-file-name=libstdc++.so` + if test ! -f $libstdcpp; then + AC_MSG_ERROR([You've compiled gcc without --enable-shared. This doesn't work with KDE. Please recompile gcc with --enable-shared to receive a libstdc++.so]) + fi + fi + ;; + esac + + AC_VALIDIFY_CXXFLAGS + + AC_PROG_CXXCPP + + if test "$GCC" = yes; then + NOOPT_CFLAGS=-O0 + fi + KDE_CHECK_COMPILER_FLAG(O0,[NOOPT_CXXFLAGS=-O0]) + + AC_ARG_ENABLE(coverage, + AC_HELP_STRING([--enable-coverage],[use gcc coverage testing]), [ + if test "$am_cv_CC_dependencies_compiler_type" = "gcc3"; then + ac_coverage_compiler="-fprofile-arcs -ftest-coverage" + ac_coverage_linker="-lgcc" + elif test "$am_cv_CC_dependencies_compiler_type" = "gcc"; then + ac_coverage_compiler="-fprofile-arcs -ftest-coverage" + ac_coverage_linker="" + else + AC_MSG_ERROR([coverage with your compiler is not supported]) + fi + CFLAGS="$CFLAGS $ac_coverage_compiler" + CXXFLAGS="$CXXFLAGS $ac_coverage_compiler" + LDFLAGS="$LDFLAGS $ac_coverage_linker" + ]) + + AC_SUBST(NOOPT_CXXFLAGS) + AC_SUBST(NOOPT_CFLAGS) + AC_SUBST(ENABLE_PERMISSIVE_FLAG) + + KDE_CHECK_NEW_LDFLAGS + KDE_CHECK_FINAL + KDE_CHECK_CLOSURE + KDE_CHECK_NMCHECK + + ifdef([AM_DEPENDENCIES], AC_REQUIRE([KDE_ADD_DEPENDENCIES]), []) +]) + +AC_DEFUN([KDE_CHECK_VISIBILITY_GCC_BUG], + [ + AC_CACHE_CHECK([for gcc -fvisibility-inlines-hidden bug], kde_cv_val_gcc_visibility_bug, + [ + AC_LANG_SAVE + AC_LANG_CPLUSPLUS + + safe_CXXFLAGS=$CXXFLAGS + safe_LDFLAGS=$LDFLAGS + CXXFLAGS="$CXXFLAGS -fPIC -fvisibility-inlines-hidden -O0" + LDFLAGS="$LDFLAGS -shared -fPIC" + + AC_TRY_LINK( + [ + /* http://gcc.gnu.org/bugzilla/show_bug.cgi?id=19664 */ + #include + int some_function( void ) __attribute__ ((visibility("default"))); + int some_function( void ) + { + std::string s("blafasel"); + return 0; + } + ], [/* elvis is alive */], + kde_cv_val_gcc_visibility_bug=no, kde_cv_val_gcc_visibility_bug=yes) + + CXXFLAGS=$safe_CXXFLAGS + LDFLAGS=$safe_LDFLAGS + AC_LANG_RESTORE + ] + ) + + if test x$kde_cv_val_gcc_visibility_bug = xno; then + CXXFLAGS="$CXXFLAGS -fvisibility-inlines-hidden" + fi + ] +) + +AC_DEFUN([KDE_ENABLE_HIDDEN_VISIBILITY], +[ + AC_BEFORE([AC_PATH_QT_1_3], [KDE_ENABLE_HIDDEN_VISIBILITY]) + + AC_MSG_CHECKING([grepping for visibility push/pop in headers]) + + if test "x$GXX" = "xyes"; then + AC_LANG_SAVE + AC_LANG_CPLUSPLUS + AC_EGREP_CPP( + [GCC visibility push], + [ #include + ], + [ + AC_MSG_RESULT(yes) + kde_stdc_visibility_patched=yes ], + [ + AC_MSG_RESULT(no) + AC_MSG_WARN([Your libstdc++ doesn't appear to be patched for + visibility support. Disabling -fvisibility=hidden]) + + kde_stdc_visibility_patched=no ]) + + AC_LANG_RESTORE + + kde_have_gcc_visibility=no + KDE_CHECK_COMPILER_FLAG(fvisibility=hidden, + [ + kde_have_gcc_visibility=yes + dnl the whole toolchain is just a mess, gcc is just too buggy + dnl to handle STL with visibility enabled. Lets reconsider + dnl when gcc 4.2 is out or when things get fixed in the compiler. + dnl Contact mueller@kde.org for details. + AC_ARG_ENABLE(gcc-hidden-visibility, + AC_HELP_STRING([--enable-gcc-hidden-visibility],[toolchain hidden visibility [default=no]]), + [kde_have_gcc_visibility=$enableval], + [kde_have_gcc_visibility=no]) + + AC_CACHE_CHECK([if Qt is patched for -fvisibility], kde_cv_val_qt_gcc_visibility_patched, + [ + AC_LANG_SAVE + AC_LANG_CPLUSPLUS + + safe_CXXFLAGS=$CXXFLAGS + CXXFLAGS="$CXXFLAGS $all_includes" + + AC_TRY_COMPILE( + [ +#include +#if Q_EXPORT - 0 != 0 +/* if this compiles, then Q_EXPORT is undefined */ +/* if Q_EXPORT is nonempty, this will break compilation */ +#endif + ], [/* elvis is alive */], + kde_cv_val_qt_gcc_visibility_patched=no, kde_cv_val_qt_gcc_visibility_patched=yes) + + CXXFLAGS=$safe_CXXFLAGS + AC_LANG_RESTORE + ] + ) + + if test x$kde_have_gcc_visibility = "xyes" && test x$kde_stdc_visibility_patched = "xyes" && test x$kde_cv_val_qt_gcc_visibility_patched = "xyes"; then + CXXFLAGS="$CXXFLAGS -fvisibility=hidden" + KDE_CHECK_VISIBILITY_GCC_BUG + HAVE_GCC_VISIBILITY=1 + AC_DEFINE_UNQUOTED(__KDE_HAVE_GCC_VISIBILITY, "$HAVE_GCC_VISIBILITY", [define to 1 if -fvisibility is supported]) + fi + ]) + fi +]) + +AC_DEFUN([KDE_ADD_DEPENDENCIES], +[ + [A]M_DEPENDENCIES(CC) + [A]M_DEPENDENCIES(CXX) +]) + +dnl just a wrapper to clean up configure.in +AC_DEFUN([KDE_PROG_LIBTOOL], +[ +AC_REQUIRE([AC_CHECK_COMPILERS]) +AC_REQUIRE([AC_ENABLE_SHARED]) +AC_REQUIRE([AC_ENABLE_STATIC]) + +AC_REQUIRE([AC_LIBTOOL_DLOPEN]) +AC_REQUIRE([KDE_CHECK_LIB64]) + +AC_OBJEXT +AC_EXEEXT + +AM_PROG_LIBTOOL +AC_LIBTOOL_CXX + +LIBTOOL_SHELL="/bin/sh ./libtool" +# LIBTOOL="$LIBTOOL --silent" +KDE_PLUGIN="-avoid-version -module -no-undefined \$(KDE_NO_UNDEFINED) \$(KDE_RPATH) \$(KDE_MT_LDFLAGS)" +AC_SUBST(KDE_PLUGIN) + +# This hack ensures that libtool creates shared libs for kunittest plugins. By default check_LTLIBRARIES makes static libs. +KDE_CHECK_PLUGIN="\$(KDE_PLUGIN) -rpath \$(libdir)" +AC_SUBST(KDE_CHECK_PLUGIN) + +# we patch configure quite some so we better keep that consistent for incremental runs +AC_SUBST(AUTOCONF,'$(SHELL) $(top_srcdir)/admin/cvs.sh configure || touch configure') +]) + +AC_DEFUN([KDE_CHECK_LIB64], +[ + AC_ARG_ENABLE(libsuffix, + AC_HELP_STRING([--enable-libsuffix], + [/lib directory suffix (64,32,none,auto[=default])]), + kdelibsuff=$enableval, kdelibsuff="auto") + + if test "$kdelibsuff" = "auto"; then + +cat > conftest.c << EOF +#include +int main() { + return 0; +} +EOF + kdelibsuff=`$CC conftest.c -o conftest.out; ldd conftest.out |sed -ne '/libc.so/{ + s,.*/lib\([[^\/]]*\)/.*,\1, + p +}'` + rm -rf conftest.* + fi + + if test "$kdelibsuff" = "no" || test "$kdelibsuff" = "none"; then + kdelibsuff= + fi + if test -z "$kdelibsuff"; then + AC_MSG_RESULT([not using lib directory suffix]) + AC_DEFINE(KDELIBSUFF, [""], Suffix for lib directories) + else + if test "$libdir" = '${exec_prefix}/lib'; then + libdir="$libdir${kdelibsuff}" + AC_SUBST([libdir], ["$libdir"]) dnl ugly hack for lib64 platforms + fi + AC_DEFINE_UNQUOTED(KDELIBSUFF, ["${kdelibsuff}"], Suffix for lib directories) + AC_MSG_RESULT([using lib directory suffix $kdelibsuff]) + fi +]) + +AC_DEFUN([KDE_CHECK_TYPES], +[ AC_CHECK_SIZEOF(int, 4)dnl + AC_CHECK_SIZEOF(short)dnl + AC_CHECK_SIZEOF(long, 4)dnl + AC_CHECK_SIZEOF(char *, 4)dnl +])dnl + +dnl Not used - kept for compat only? +AC_DEFUN([KDE_DO_IT_ALL], +[ +AC_CANONICAL_SYSTEM +AC_ARG_PROGRAM +AM_INIT_AUTOMAKE($1, $2) +AM_DISABLE_LIBRARIES +AC_PREFIX_DEFAULT(${KDEDIR:-/usr/local/kde}) +AC_CHECK_COMPILERS +KDE_PROG_LIBTOOL +AM_KDE_WITH_NLS +AC_PATH_KDE +]) + +AC_DEFUN([AC_CHECK_RPATH], +[ +AC_MSG_CHECKING(for rpath) +AC_ARG_ENABLE(rpath, + AC_HELP_STRING([--disable-rpath],[do not use the rpath feature of ld]), + USE_RPATH=$enableval, USE_RPATH=yes) + +if test -z "$KDE_RPATH" && test "$USE_RPATH" = "yes"; then + + KDE_RPATH="-R \$(libdir)" + + if test "$kde_libraries" != "$libdir"; then + KDE_RPATH="$KDE_RPATH -R \$(kde_libraries)" + fi + + if test -n "$qt_libraries"; then + KDE_RPATH="$KDE_RPATH -R \$(qt_libraries)" + fi + dnl $x_libraries is set to /usr/lib in case + if test -n "$X_LDFLAGS"; then + X_RPATH="-R \$(x_libraries)" + KDE_RPATH="$KDE_RPATH $X_RPATH" + fi + if test -n "$KDE_EXTRA_RPATH"; then + KDE_RPATH="$KDE_RPATH \$(KDE_EXTRA_RPATH)" + fi +fi +AC_SUBST(KDE_EXTRA_RPATH) +AC_SUBST(KDE_RPATH) +AC_SUBST(X_RPATH) +AC_MSG_RESULT($USE_RPATH) +]) + +dnl Check for the type of the third argument of getsockname +AC_DEFUN([AC_CHECK_SOCKLEN_T], +[ + AC_MSG_CHECKING(for socklen_t) + AC_CACHE_VAL(kde_cv_socklen_t, + [ + AC_LANG_PUSH(C++) + kde_cv_socklen_t=no + AC_TRY_COMPILE([ + #include + #include + ], + [ + socklen_t len; + getpeername(0,0,&len); + ], + [ + kde_cv_socklen_t=yes + kde_cv_socklen_t_equiv=socklen_t + ]) + AC_LANG_POP(C++) + ]) + AC_MSG_RESULT($kde_cv_socklen_t) + if test $kde_cv_socklen_t = no; then + AC_MSG_CHECKING([for socklen_t equivalent for socket functions]) + AC_CACHE_VAL(kde_cv_socklen_t_equiv, + [ + kde_cv_socklen_t_equiv=int + AC_LANG_PUSH(C++) + for t in int size_t unsigned long "unsigned long"; do + AC_TRY_COMPILE([ + #include + #include + ], + [ + $t len; + getpeername(0,0,&len); + ], + [ + kde_cv_socklen_t_equiv="$t" + break + ]) + done + AC_LANG_POP(C++) + ]) + AC_MSG_RESULT($kde_cv_socklen_t_equiv) + fi + AC_DEFINE_UNQUOTED(kde_socklen_t, $kde_cv_socklen_t_equiv, + [type to use in place of socklen_t if not defined]) + AC_DEFINE_UNQUOTED(ksize_t, $kde_cv_socklen_t_equiv, + [type to use in place of socklen_t if not defined (deprecated, use kde_socklen_t)]) +]) + +dnl This is a merge of some macros out of the gettext aclocal.m4 +dnl since we don't need anything, I took the things we need +dnl the copyright for them is: +dnl > +dnl Copyright (C) 1994, 1995, 1996, 1997, 1998 Free Software Foundation, Inc. +dnl This Makefile.in is free software; the Free Software Foundation +dnl gives unlimited permission to copy and/or distribute it, +dnl with or without modifications, as long as this notice is preserved. + +dnl This program is distributed in the hope that it will be useful, +dnl but WITHOUT ANY WARRANTY, to the extent permitted by law; without +dnl even the implied warranty of MERCHANTABILITY or FITNESS FOR A +dnl PARTICULAR PURPOSE. +dnl > +dnl for this file it is relicensed under LGPL + +AC_DEFUN([AM_KDE_WITH_NLS], + [ + dnl If we use NLS figure out what method + + AM_PATH_PROG_WITH_TEST_KDE(MSGFMT, msgfmt, + [test -n "`$ac_dir/$ac_word --version 2>&1 | grep 'GNU gettext'`"], msgfmt) + AC_PATH_PROG(GMSGFMT, gmsgfmt, $MSGFMT) + + if test -z "`$GMSGFMT --version 2>&1 | grep 'GNU gettext'`"; then + AC_MSG_RESULT([found msgfmt program is not GNU msgfmt; ignore it]) + GMSGFMT=":" + fi + MSGFMT=$GMSGFMT + AC_SUBST(GMSGFMT) + AC_SUBST(MSGFMT) + + AM_PATH_PROG_WITH_TEST_KDE(XGETTEXT, xgettext, + [test -z "`$ac_dir/$ac_word -h 2>&1 | grep '(HELP)'`"], :) + + dnl Test whether we really found GNU xgettext. + if test "$XGETTEXT" != ":"; then + dnl If it is no GNU xgettext we define it as : so that the + dnl Makefiles still can work. + if $XGETTEXT --omit-header /dev/null 2> /dev/null; then + : ; + else + AC_MSG_RESULT( + [found xgettext programs is not GNU xgettext; ignore it]) + XGETTEXT=":" + fi + fi + AC_SUBST(XGETTEXT) + + ]) + +# Search path for a program which passes the given test. +# Ulrich Drepper , 1996. + +# serial 1 +# Stephan Kulow: I appended a _KDE against name conflicts + +dnl AM_PATH_PROG_WITH_TEST_KDE(VARIABLE, PROG-TO-CHECK-FOR, +dnl TEST-PERFORMED-ON-FOUND_PROGRAM [, VALUE-IF-NOT-FOUND [, PATH]]) +AC_DEFUN([AM_PATH_PROG_WITH_TEST_KDE], +[# Extract the first word of "$2", so it can be a program name with args. +set dummy $2; ac_word=[$]2 +AC_MSG_CHECKING([for $ac_word]) +AC_CACHE_VAL(ac_cv_path_$1, +[case "[$]$1" in + /*) + ac_cv_path_$1="[$]$1" # Let the user override the test with a path. + ;; + *) + IFS="${IFS= }"; ac_save_ifs="$IFS"; IFS="${IFS}:" + for ac_dir in ifelse([$5], , $PATH, [$5]); do + test -z "$ac_dir" && ac_dir=. + if test -f $ac_dir/$ac_word; then + if [$3]; then + ac_cv_path_$1="$ac_dir/$ac_word" + break + fi + fi + done + IFS="$ac_save_ifs" +dnl If no 4th arg is given, leave the cache variable unset, +dnl so AC_PATH_PROGS will keep looking. +ifelse([$4], , , [ test -z "[$]ac_cv_path_$1" && ac_cv_path_$1="$4" +])dnl + ;; +esac])dnl +$1="$ac_cv_path_$1" +if test -n "[$]$1"; then + AC_MSG_RESULT([$]$1) +else + AC_MSG_RESULT(no) +fi +AC_SUBST($1)dnl +]) + + +# Check whether LC_MESSAGES is available in . +# Ulrich Drepper , 1995. + +# serial 1 + +AC_DEFUN([AM_LC_MESSAGES], + [if test $ac_cv_header_locale_h = yes; then + AC_CACHE_CHECK([for LC_MESSAGES], am_cv_val_LC_MESSAGES, + [AC_TRY_LINK([#include ], [return LC_MESSAGES], + am_cv_val_LC_MESSAGES=yes, am_cv_val_LC_MESSAGES=no)]) + if test $am_cv_val_LC_MESSAGES = yes; then + AC_DEFINE(HAVE_LC_MESSAGES, 1, [Define if your locale.h file contains LC_MESSAGES]) + fi + fi]) + +dnl From Jim Meyering. +dnl FIXME: migrate into libit. + +AC_DEFUN([AM_FUNC_OBSTACK], +[AC_CACHE_CHECK([for obstacks], am_cv_func_obstack, + [AC_TRY_LINK([#include "obstack.h"], + [struct obstack *mem;obstack_free(mem,(char *) 0)], + am_cv_func_obstack=yes, + am_cv_func_obstack=no)]) + if test $am_cv_func_obstack = yes; then + AC_DEFINE(HAVE_OBSTACK) + else + LIBOBJS="$LIBOBJS obstack.o" + fi +]) + +dnl From Jim Meyering. Use this if you use the GNU error.[ch]. +dnl FIXME: Migrate into libit + +AC_DEFUN([AM_FUNC_ERROR_AT_LINE], +[AC_CACHE_CHECK([for error_at_line], am_cv_lib_error_at_line, + [AC_TRY_LINK([],[error_at_line(0, 0, "", 0, "");], + am_cv_lib_error_at_line=yes, + am_cv_lib_error_at_line=no)]) + if test $am_cv_lib_error_at_line = no; then + LIBOBJS="$LIBOBJS error.o" + fi + AC_SUBST(LIBOBJS)dnl +]) + +# Macro to add for using GNU gettext. +# Ulrich Drepper , 1995. + +# serial 1 +# Stephan Kulow: I put a KDE in it to avoid name conflicts + +AC_DEFUN([AM_KDE_GNU_GETTEXT], + [AC_REQUIRE([AC_PROG_MAKE_SET])dnl + AC_REQUIRE([AC_PROG_RANLIB])dnl + AC_REQUIRE([AC_HEADER_STDC])dnl + AC_REQUIRE([AC_TYPE_OFF_T])dnl + AC_REQUIRE([AC_TYPE_SIZE_T])dnl + AC_REQUIRE([AC_FUNC_ALLOCA])dnl + AC_REQUIRE([AC_FUNC_MMAP])dnl + AC_REQUIRE([AM_KDE_WITH_NLS])dnl + AC_CHECK_HEADERS([limits.h locale.h nl_types.h string.h values.h alloca.h]) + AC_CHECK_FUNCS([getcwd munmap putenv setlocale strchr strcasecmp \ +__argz_count __argz_stringify __argz_next]) + + AC_MSG_CHECKING(for stpcpy) + AC_CACHE_VAL(kde_cv_func_stpcpy, + [ + kde_safe_cxxflags=$CXXFLAGS + CXXFLAGS="-Werror" + AC_LANG_SAVE + AC_LANG_CPLUSPLUS + AC_TRY_COMPILE([ + #include + ], + [ + char buffer[200]; + stpcpy(buffer, buffer); + ], + kde_cv_func_stpcpy=yes, + kde_cv_func_stpcpy=no) + AC_LANG_RESTORE + CXXFLAGS=$kde_safe_cxxflags + ]) + AC_MSG_RESULT($kde_cv_func_stpcpy) + if eval "test \"`echo $kde_cv_func_stpcpy`\" = yes"; then + AC_DEFINE(HAVE_STPCPY, 1, [Define if you have stpcpy]) + fi + + AM_LC_MESSAGES + + if test "x$CATOBJEXT" != "x"; then + if test "x$ALL_LINGUAS" = "x"; then + LINGUAS= + else + AC_MSG_CHECKING(for catalogs to be installed) + NEW_LINGUAS= + for lang in ${LINGUAS=$ALL_LINGUAS}; do + case "$ALL_LINGUAS" in + *$lang*) NEW_LINGUAS="$NEW_LINGUAS $lang" ;; + esac + done + LINGUAS=$NEW_LINGUAS + AC_MSG_RESULT($LINGUAS) + fi + + dnl Construct list of names of catalog files to be constructed. + if test -n "$LINGUAS"; then + for lang in $LINGUAS; do CATALOGS="$CATALOGS $lang$CATOBJEXT"; done + fi + fi + + ]) + +AC_DEFUN([AC_HAVE_XPM], + [AC_REQUIRE_CPP()dnl + AC_REQUIRE([KDE_CHECK_EXTRA_LIBS]) + + test -z "$XPM_LDFLAGS" && XPM_LDFLAGS= + test -z "$XPM_INCLUDE" && XPM_INCLUDE= + + AC_ARG_WITH(xpm,AC_HELP_STRING([--without-xpm],[disable color pixmap XPM tests]), + xpm_test=$withval, xpm_test="yes") + if test "x$xpm_test" = xno; then + ac_cv_have_xpm=no + else + AC_MSG_CHECKING(for XPM) + AC_CACHE_VAL(ac_cv_have_xpm, + [ + ac_save_ldflags="$LDFLAGS" + ac_save_cflags="$CFLAGS" + if test "x$kde_use_qt_emb" != "xyes" && test "x$kde_use_qt_mac" != "xyes"; then + LDFLAGS="$LDFLAGS $X_LDFLAGS $USER_LDFLAGS $LDFLAGS $XPM_LDFLAGS $all_libraries -lXpm -lX11 -lXext $LIBZ $LIBSOCKET" + else + LDFLAGS="$LDFLAGS $X_LDFLAGS $USER_LDFLAGS $LDFLAGS $XPM_LDFLAGS $all_libraries -lXpm $LIBZ $LIBSOCKET" + fi + CFLAGS="$CFLAGS $X_INCLUDES $USER_INCLUDES" + test -n "$XPM_INCLUDE" && CFLAGS="-I$XPM_INCLUDE $CFLAGS" + AC_TRY_LINK([#include ],[], + ac_cv_have_xpm="yes",ac_cv_have_xpm="no") + LDFLAGS="$ac_save_ldflags" + CFLAGS="$ac_save_cflags" + ])dnl + + if test "$ac_cv_have_xpm" = no; then + AC_MSG_RESULT(no) + XPM_LDFLAGS="" + XPMINC="" + $2 + else + AC_DEFINE(HAVE_XPM, 1, [Define if you have XPM support]) + if test "$XPM_LDFLAGS" = ""; then + XPMLIB='-lXpm $(LIB_X11)' + else + XPMLIB="-L$XPM_LDFLAGS -lXpm "'$(LIB_X11)' + fi + if test "$XPM_INCLUDE" = ""; then + XPMINC="" + else + XPMINC="-I$XPM_INCLUDE" + fi + AC_MSG_RESULT(yes) + $1 + fi + fi + AC_SUBST(XPMINC) + AC_SUBST(XPMLIB) +]) + +AC_DEFUN([AC_HAVE_DPMS], + [AC_REQUIRE_CPP()dnl + AC_REQUIRE([KDE_CHECK_EXTRA_LIBS]) + + test -z "$DPMS_LDFLAGS" && DPMS_LDFLAGS= + test -z "$DPMS_INCLUDE" && DPMS_INCLUDE= + DPMS_LIB= + + AC_ARG_WITH(dpms,AC_HELP_STRING([--without-dpms],[disable DPMS power saving]), + dpms_test=$withval, dpms_test="yes") + if test "x$dpms_test" = xno; then + ac_cv_have_dpms=no + else + AC_MSG_CHECKING(for DPMS) + dnl Note: ac_cv_have_dpms can be no, yes, or -lXdpms. + dnl 'yes' means DPMS_LIB="", '-lXdpms' means DPMS_LIB="-lXdpms". + AC_CACHE_VAL(ac_cv_have_dpms, + [ + if test "x$kde_use_qt_emb" = "xyes" || test "x$kde_use_qt_mac" = "xyes"; then + AC_MSG_RESULT(no) + ac_cv_have_dpms="no" + else + ac_save_ldflags="$LDFLAGS" + ac_save_cflags="$CFLAGS" + ac_save_libs="$LIBS" + LDFLAGS="$LDFLAGS $DPMS_LDFLAGS $all_libraries" + LIBS="-lX11 -lXext $LIBSOCKET" + CFLAGS="$CFLAGS $X_INCLUDES" + test -n "$DPMS_INCLUDE" && CFLAGS="-I$DPMS_INCLUDE $CFLAGS" + AC_TRY_LINK([ + #include + #include + #include + #include + int foo_test_dpms() + { return DPMSSetTimeouts( 0, 0, 0, 0 ); }],[], + ac_cv_have_dpms="yes", [ + LIBS="-lXdpms $LIBS" + AC_TRY_LINK([ + #include + #include + #include + #include + int foo_test_dpms() + { return DPMSSetTimeouts( 0, 0, 0, 0 ); }],[], + [ + ac_cv_have_dpms="-lXdpms" + ],ac_cv_have_dpms="no") + ]) + LDFLAGS="$ac_save_ldflags" + CFLAGS="$ac_save_cflags" + LIBS="$ac_save_libs" + fi + ])dnl + + if test "$ac_cv_have_dpms" = no; then + AC_MSG_RESULT(no) + DPMS_LDFLAGS="" + DPMSINC="" + $2 + else + AC_DEFINE(HAVE_DPMS, 1, [Define if you have DPMS support]) + if test "$ac_cv_have_dpms" = "-lXdpms"; then + DPMS_LIB="-lXdpms" + fi + if test "$DPMS_LDFLAGS" = ""; then + DPMSLIB="$DPMS_LIB "'$(LIB_X11)' + else + DPMSLIB="$DPMS_LDFLAGS $DPMS_LIB "'$(LIB_X11)' + fi + if test "$DPMS_INCLUDE" = ""; then + DPMSINC="" + else + DPMSINC="-I$DPMS_INCLUDE" + fi + AC_MSG_RESULT(yes) + $1 + fi + fi + ac_save_cflags="$CFLAGS" + CFLAGS="$CFLAGS $X_INCLUDES" + test -n "$DPMS_INCLUDE" && CFLAGS="-I$DPMS_INCLUDE $CFLAGS" + AH_TEMPLATE(HAVE_DPMSCAPABLE_PROTO, + [Define if you have the DPMSCapable prototype in ]) + AC_CHECK_DECL(DPMSCapable, + AC_DEFINE(HAVE_DPMSCAPABLE_PROTO),, + [#include + #include ]) + AH_TEMPLATE(HAVE_DPMSINFO_PROTO, + [Define if you have the DPMSInfo prototype in ]) + AC_CHECK_DECL(DPMSInfo, + AC_DEFINE(HAVE_DPMSINFO_PROTO),, + [#include + #include ]) + CFLAGS="$ac_save_cflags" + AC_SUBST(DPMSINC) + AC_SUBST(DPMSLIB) +]) + +AC_DEFUN([AC_HAVE_GL], + [AC_REQUIRE_CPP()dnl + AC_REQUIRE([KDE_CHECK_EXTRA_LIBS]) + + test -z "$GL_LDFLAGS" && GL_LDFLAGS= + test -z "$GL_INCLUDE" && GL_INCLUDE= + + AC_ARG_WITH(gl,AC_HELP_STRING([--without-gl],[disable 3D GL modes]), + gl_test=$withval, gl_test="yes") + if test "x$kde_use_qt_emb" = "xyes"; then + # GL and Qt Embedded is a no-go for now. + ac_cv_have_gl=no + elif test "x$gl_test" = xno; then + ac_cv_have_gl=no + else + AC_MSG_CHECKING(for GL) + AC_CACHE_VAL(ac_cv_have_gl, + [ + AC_LANG_SAVE + AC_LANG_CPLUSPLUS + ac_save_ldflags=$LDFLAGS + ac_save_cxxflags=$CXXFLAGS + ac_save_libs=$LIBS + LDFLAGS="$LDFLAGS $GL_LDFLAGS $X_LDFLAGS $all_libraries" + LIBS="$LIBS -lGL -lGLU" + test "x$kde_use_qt_mac" != xyes && test "x$kde_use_qt_emb" != xyes && LIBS="$LIBS -lX11" + LIBS="$LIBS $LIB_XEXT -lm $LIBSOCKET" + CXXFLAGS="$CFLAGS $X_INCLUDES" + test -n "$GL_INCLUDE" && CFLAGS="-I$GL_INCLUDE $CFLAGS" + AC_TRY_LINK([#include +#include +], [], + ac_cv_have_gl="yes", ac_cv_have_gl="no") + AC_LANG_RESTORE + LDFLAGS=$ac_save_ldflags + CXXFLAGS=$ac_save_cxxflags + LIBS=$ac_save_libs + ])dnl + + if test "$ac_cv_have_gl" = "no"; then + AC_MSG_RESULT(no) + GL_LDFLAGS="" + GLINC="" + $2 + else + AC_DEFINE(HAVE_GL, 1, [Defines if you have GL (Mesa, OpenGL, ...)]) + if test "$GL_LDFLAGS" = ""; then + GLLIB='-lGLU -lGL $(LIB_X11)' + else + GLLIB="$GL_LDFLAGS -lGLU -lGL "'$(LIB_X11)' + fi + if test "$GL_INCLUDE" = ""; then + GLINC="" + else + GLINC="-I$GL_INCLUDE" + fi + AC_MSG_RESULT($ac_cv_have_gl) + $1 + fi + fi + AC_SUBST(GLINC) + AC_SUBST(GLLIB) +]) + + + dnl shadow password and PAM magic - maintained by ossi@kde.org + +AC_DEFUN([KDE_PAM], [ + AC_REQUIRE([KDE_CHECK_LIBDL]) + + want_pam= + AC_ARG_WITH(pam, + AC_HELP_STRING([--with-pam[=ARG]],[enable support for PAM: ARG=[yes|no|service name]]), + [ if test "x$withval" = "xyes"; then + want_pam=yes + pam_service=kde + elif test "x$withval" = "xno"; then + want_pam=no + else + want_pam=yes + pam_service=$withval + fi + ], [ pam_service=kde ]) + + use_pam= + PAMLIBS= + if test "x$want_pam" != xno; then + AC_CHECK_LIB(pam, pam_start, [ + AC_CHECK_HEADER(security/pam_appl.h, + [ pam_header=security/pam_appl.h ], + [ AC_CHECK_HEADER(pam/pam_appl.h, + [ pam_header=pam/pam_appl.h ], + [ + AC_MSG_WARN([PAM detected, but no headers found! +Make sure you have the necessary development packages installed.]) + ] + ) + ] + ) + ], , $LIBDL) + if test -z "$pam_header"; then + if test "x$want_pam" = xyes; then + AC_MSG_ERROR([--with-pam was specified, but cannot compile with PAM!]) + fi + else + AC_DEFINE(HAVE_PAM, 1, [Defines if you have PAM (Pluggable Authentication Modules)]) + PAMLIBS="$PAM_MISC_LIB -lpam $LIBDL" + use_pam=yes + + dnl darwin claims to be something special + if test "$pam_header" = "pam/pam_appl.h"; then + AC_DEFINE(HAVE_PAM_PAM_APPL_H, 1, [Define if your PAM headers are in pam/ instead of security/]) + fi + + dnl test whether struct pam_message is const (Linux) or not (Sun) + AC_MSG_CHECKING(for const pam_message) + AC_EGREP_HEADER([struct pam_message], $pam_header, + [ AC_EGREP_HEADER([const struct pam_message], $pam_header, + [AC_MSG_RESULT([const: Linux-type PAM])], + [AC_MSG_RESULT([nonconst: Sun-type PAM]) + AC_DEFINE(PAM_MESSAGE_NONCONST, 1, [Define if your PAM support takes non-const arguments (Solaris)])] + )], + [AC_MSG_RESULT([not found - assume const, Linux-type PAM])]) + fi + fi + + AC_SUBST(PAMLIBS) +]) + +dnl DEF_PAM_SERVICE(arg name, full name, define name) +AC_DEFUN([DEF_PAM_SERVICE], [ + AC_ARG_WITH($1-pam, + AC_HELP_STRING([--with-$1-pam=[val]],[override PAM service from --with-pam for $2]), + [ if test "x$use_pam" = xyes; then + $3_PAM_SERVICE=$withval + else + AC_MSG_ERROR([Cannot use use --with-$1-pam, as no PAM was detected. +You may want to enforce it by using --with-pam.]) + fi + ], + [ if test "x$use_pam" = xyes; then + $3_PAM_SERVICE="$pam_service" + fi + ]) + if test -n "$$3_PAM_SERVICE"; then + AC_MSG_RESULT([The PAM service used by $2 will be $$3_PAM_SERVICE]) + AC_DEFINE_UNQUOTED($3_PAM_SERVICE, "$$3_PAM_SERVICE", [The PAM service to be used by $2]) + fi + AC_SUBST($3_PAM_SERVICE) +]) + +AC_DEFUN([KDE_SHADOWPASSWD], [ + AC_REQUIRE([KDE_PAM]) + + AC_CHECK_LIB(shadow, getspent, + [ LIBSHADOW="-lshadow" + ac_use_shadow=yes + ], + [ dnl for UnixWare + AC_CHECK_LIB(gen, getspent, + [ LIBGEN="-lgen" + ac_use_shadow=yes + ], + [ AC_CHECK_FUNC(getspent, + [ ac_use_shadow=yes ], + [ ac_use_shadow=no ]) + ]) + ]) + AC_SUBST(LIBSHADOW) + AC_SUBST(LIBGEN) + + AC_MSG_CHECKING([for shadow passwords]) + + AC_ARG_WITH(shadow, + AC_HELP_STRING([--with-shadow],[If you want shadow password support]), + [ if test "x$withval" != "xno"; then + use_shadow=yes + else + use_shadow=no + fi + ], [ + use_shadow="$ac_use_shadow" + ]) + + if test "x$use_shadow" = xyes; then + AC_MSG_RESULT(yes) + AC_DEFINE(HAVE_SHADOW, 1, [Define if you use shadow passwords]) + else + AC_MSG_RESULT(no) + LIBSHADOW= + LIBGEN= + fi + + dnl finally make the relevant binaries setuid root, if we have shadow passwds. + dnl this still applies, if we could use it indirectly through pam. + if test "x$use_shadow" = xyes || + ( test "x$use_pam" = xyes && test "x$ac_use_shadow" = xyes ); then + case $host in + *-*-freebsd* | *-*-netbsd* | *-*-openbsd*) + SETUIDFLAGS="-m 4755 -o root";; + *) + SETUIDFLAGS="-m 4755";; + esac + fi + AC_SUBST(SETUIDFLAGS) + +]) + +AC_DEFUN([KDE_PASSWDLIBS], [ + AC_REQUIRE([KDE_MISC_TESTS]) dnl for LIBCRYPT + AC_REQUIRE([KDE_PAM]) + AC_REQUIRE([KDE_SHADOWPASSWD]) + + if test "x$use_pam" = "xyes"; then + PASSWDLIBS="$PAMLIBS" + else + PASSWDLIBS="$LIBCRYPT $LIBSHADOW $LIBGEN" + fi + + dnl FreeBSD uses a shadow-like setup, where /etc/passwd holds the users, but + dnl /etc/master.passwd holds the actual passwords. /etc/master.passwd requires + dnl root to read, so kcheckpass needs to be root (even when using pam, since pam + dnl may need to read /etc/master.passwd). + case $host in + *-*-freebsd*) + SETUIDFLAGS="-m 4755 -o root" + ;; + *) + ;; + esac + + AC_SUBST(PASSWDLIBS) +]) + +AC_DEFUN([KDE_CHECK_LIBDL], +[ +AC_CHECK_LIB(dl, dlopen, [ +LIBDL="-ldl" +ac_cv_have_dlfcn=yes +]) + +AC_CHECK_LIB(dld, shl_unload, [ +LIBDL="-ldld" +ac_cv_have_shload=yes +]) + +AC_SUBST(LIBDL) +]) + +AC_DEFUN([KDE_CHECK_DLOPEN], +[ +KDE_CHECK_LIBDL +AC_CHECK_HEADERS(dlfcn.h dl.h) +if test "$ac_cv_header_dlfcn_h" = "no"; then + ac_cv_have_dlfcn=no +fi + +if test "$ac_cv_header_dl_h" = "no"; then + ac_cv_have_shload=no +fi + +dnl XXX why change enable_dlopen? its already set by autoconf's AC_ARG_ENABLE +dnl (MM) +AC_ARG_ENABLE(dlopen, +AC_HELP_STRING([--disable-dlopen],[link statically [default=no]]), +enable_dlopen=$enableval, +enable_dlopen=yes) + +# override the user's opinion, if we know it better ;) +if test "$ac_cv_have_dlfcn" = "no" && test "$ac_cv_have_shload" = "no"; then + enable_dlopen=no +fi + +if test "$ac_cv_have_dlfcn" = "yes"; then + AC_DEFINE_UNQUOTED(HAVE_DLFCN, 1, [Define if you have dlfcn]) +fi + +if test "$ac_cv_have_shload" = "yes"; then + AC_DEFINE_UNQUOTED(HAVE_SHLOAD, 1, [Define if you have shload]) +fi + +if test "$enable_dlopen" = no ; then + test -n "$1" && eval $1 +else + test -n "$2" && eval $2 +fi + +]) + +AC_DEFUN([KDE_CHECK_DYNAMIC_LOADING], +[ +KDE_CHECK_DLOPEN(libtool_enable_shared=yes, libtool_enable_static=no) +KDE_PROG_LIBTOOL +AC_MSG_CHECKING([dynamic loading]) +eval "`egrep '^build_libtool_libs=' libtool`" +if test "$build_libtool_libs" = "yes" && test "$enable_dlopen" = "yes"; then + dynamic_loading=yes + AC_DEFINE_UNQUOTED(HAVE_DYNAMIC_LOADING) +else + dynamic_loading=no +fi +AC_MSG_RESULT($dynamic_loading) +if test "$dynamic_loading" = "yes"; then + $1 +else + $2 +fi +]) + +AC_DEFUN([KDE_ADD_INCLUDES], +[ +if test -z "$1"; then + test_include="Pix.h" +else + test_include="$1" +fi + +AC_MSG_CHECKING([for libg++ ($test_include)]) + +AC_CACHE_VAL(kde_cv_libgpp_includes, +[ +kde_cv_libgpp_includes=no + + for ac_dir in \ + \ + /usr/include/g++ \ + /usr/include \ + /usr/unsupported/include \ + /opt/include \ + $extra_include \ + ; \ + do + if test -r "$ac_dir/$test_include"; then + kde_cv_libgpp_includes=$ac_dir + break + fi + done +]) + +AC_MSG_RESULT($kde_cv_libgpp_includes) +if test "$kde_cv_libgpp_includes" != "no"; then + all_includes="-I$kde_cv_libgpp_includes $all_includes $USER_INCLUDES" +fi +]) +]) + +AC_DEFUN([KDE_CHECK_LIBPTHREAD], +[ + dnl This code is here specifically to handle the + dnl various flavors of threading library on FreeBSD + dnl 4-, 5-, and 6-, and the (weird) rules around it. + dnl There may be an environment PTHREAD_LIBS that + dnl specifies what to use; otherwise, search for it. + dnl -pthread is special cased and unsets LIBPTHREAD + dnl below if found. + LIBPTHREAD="" + + if test -n "$PTHREAD_LIBS"; then + if test "x$PTHREAD_LIBS" = "x-pthread" ; then + LIBPTHREAD="PTHREAD" + else + PTHREAD_LIBS_save="$PTHREAD_LIBS" + PTHREAD_LIBS=`echo "$PTHREAD_LIBS_save" | sed -e 's,^-l,,g'` + AC_MSG_CHECKING([for pthread_create in $PTHREAD_LIBS]) + KDE_CHECK_LIB($PTHREAD_LIBS, pthread_create, [ + LIBPTHREAD="$PTHREAD_LIBS_save"]) + PTHREAD_LIBS="$PTHREAD_LIBS_save" + fi + fi + + dnl Is this test really needed, in the face of the Tru64 test below? + if test -z "$LIBPTHREAD"; then + AC_CHECK_LIB(pthread, pthread_create, [LIBPTHREAD="-lpthread"]) + fi + + dnl This is a special Tru64 check, see BR 76171 issue #18. + if test -z "$LIBPTHREAD" ; then + AC_MSG_CHECKING([for pthread_create in -lpthread]) + kde_safe_libs=$LIBS + LIBS="$LIBS -lpthread" + AC_TRY_LINK([#include ],[(void)pthread_create(0,0,0,0);],[ + AC_MSG_RESULT(yes) + LIBPTHREAD="-lpthread"],[ + AC_MSG_RESULT(no)]) + LIBS=$kde_safe_libs + fi + + dnl Un-special-case for FreeBSD. + if test "x$LIBPTHREAD" = "xPTHREAD" ; then + LIBPTHREAD="" + fi + + AC_SUBST(LIBPTHREAD) +]) + +AC_DEFUN([KDE_CHECK_PTHREAD_OPTION], +[ + USE_THREADS="" + if test -z "$LIBPTHREAD"; then + KDE_CHECK_COMPILER_FLAG(pthread, [USE_THREADS="-D_THREAD_SAFE -pthread"]) + fi + + AH_VERBATIM(__svr_define, [ +#if defined(__SVR4) && !defined(__svr4__) +#define __svr4__ 1 +#endif +]) + case $host_os in + solaris*) + KDE_CHECK_COMPILER_FLAG(mt, [USE_THREADS="-mt"]) + CPPFLAGS="$CPPFLAGS -D_REENTRANT -D_POSIX_PTHREAD_SEMANTICS -DUSE_SOLARIS -DSVR4" + ;; + freebsd*) + CPPFLAGS="$CPPFLAGS -D_THREAD_SAFE $PTHREAD_CFLAGS" + ;; + aix*) + CPPFLAGS="$CPPFLAGS -D_THREAD_SAFE" + LIBPTHREAD="$LIBPTHREAD -lc_r" + ;; + linux*) CPPFLAGS="$CPPFLAGS -D_REENTRANT" + if test "$CXX" = "KCC"; then + CXXFLAGS="$CXXFLAGS --thread_safe" + NOOPT_CXXFLAGS="$NOOPT_CXXFLAGS --thread_safe" + fi + ;; + *) + ;; + esac + AC_SUBST(USE_THREADS) + AC_SUBST(LIBPTHREAD) +]) + +AC_DEFUN([KDE_CHECK_THREADING], +[ + AC_REQUIRE([KDE_CHECK_LIBPTHREAD]) + AC_REQUIRE([KDE_CHECK_PTHREAD_OPTION]) + dnl default is yes if libpthread is found and no if no libpthread is available + if test -z "$LIBPTHREAD"; then + if test -z "$USE_THREADS"; then + kde_check_threading_default=no + else + kde_check_threading_default=yes + fi + else + kde_check_threading_default=yes + fi + AC_ARG_ENABLE(threading,AC_HELP_STRING([--disable-threading],[disables threading even if libpthread found]), + kde_use_threading=$enableval, kde_use_threading=$kde_check_threading_default) + if test "x$kde_use_threading" = "xyes"; then + AC_DEFINE(HAVE_LIBPTHREAD, 1, [Define if you have a working libpthread (will enable threaded code)]) + fi +]) + +AC_DEFUN([KDE_TRY_LINK_PYTHON], +[ +if test "$kde_python_link_found" = no; then + +if test "$1" = normal; then + AC_MSG_CHECKING(if a Python application links) +else + AC_MSG_CHECKING(if Python depends on $2) +fi + +AC_CACHE_VAL(kde_cv_try_link_python_$1, +[ +kde_save_cflags="$CFLAGS" +CFLAGS="$CFLAGS $PYTHONINC" +kde_save_libs="$LIBS" +LIBS="$LIBS $LIBPYTHON $2 $LIBDL $LIBSOCKET" +kde_save_ldflags="$LDFLAGS" +LDFLAGS="$LDFLAGS $PYTHONLIB" + +AC_TRY_LINK( +[ +#include +],[ + PySys_SetArgv(1, 0); +], + [kde_cv_try_link_python_$1=yes], + [kde_cv_try_link_python_$1=no] +) +CFLAGS="$kde_save_cflags" +LIBS="$kde_save_libs" +LDFLAGS="$kde_save_ldflags" +]) + +if test "$kde_cv_try_link_python_$1" = "yes"; then + AC_MSG_RESULT(yes) + kde_python_link_found=yes + if test ! "$1" = normal; then + LIBPYTHON="$LIBPYTHON $2" + fi + $3 +else + AC_MSG_RESULT(no) + $4 +fi + +fi + +]) + +AC_DEFUN([KDE_CHECK_PYTHON_DIR], +[ +AC_MSG_CHECKING([for Python directory]) + +AC_CACHE_VAL(kde_cv_pythondir, +[ + if test -z "$PYTHONDIR"; then + kde_cv_pythondir=/usr/local + else + kde_cv_pythondir="$PYTHONDIR" + fi +]) + +AC_ARG_WITH(pythondir, +AC_HELP_STRING([--with-pythondir=pythondir],[use python installed in pythondir]), +[ + ac_python_dir=$withval +], ac_python_dir=$kde_cv_pythondir +) + +AC_MSG_RESULT($ac_python_dir) +]) + +AC_DEFUN([KDE_CHECK_PYTHON_INTERN], +[ +AC_REQUIRE([KDE_CHECK_LIBDL]) +AC_REQUIRE([KDE_CHECK_LIBPTHREAD]) +AC_REQUIRE([KDE_CHECK_PYTHON_DIR]) + +if test -z "$1"; then + version="1.5" +else + version="$1" +fi + +AC_MSG_CHECKING([for Python$version]) + +python_incdirs="$ac_python_dir/include /usr/include /usr/local/include/ $kde_extra_includes" +AC_FIND_FILE(Python.h, $python_incdirs, python_incdir) +if test ! -r $python_incdir/Python.h; then + AC_FIND_FILE(python$version/Python.h, $python_incdirs, python_incdir) + python_incdir=$python_incdir/python$version + if test ! -r $python_incdir/Python.h; then + python_incdir=no + fi +fi + +PYTHONINC=-I$python_incdir + +python_libdirs="$ac_python_dir/lib$kdelibsuff /usr/lib$kdelibsuff /usr/local /usr/lib$kdelibsuff $kde_extra_libs" +AC_FIND_FILE(libpython$version.so, $python_libdirs, python_libdir) +if test ! -r $python_libdir/libpython$version.so; then + AC_FIND_FILE(libpython$version.a, $python_libdirs, python_libdir) + if test ! -r $python_libdir/libpython$version.a; then + AC_FIND_FILE(python$version/config/libpython$version.a, $python_libdirs, python_libdir) + python_libdir=$python_libdir/python$version/config + if test ! -r $python_libdir/libpython$version.a; then + python_libdir=no + fi + fi +fi + +PYTHONLIB=-L$python_libdir +kde_orig_LIBPYTHON=$LIBPYTHON +if test -z "$LIBPYTHON"; then + LIBPYTHON=-lpython$version +fi + +AC_FIND_FILE(python$version/copy.py, $python_libdirs, python_moddir) +python_moddir=$python_moddir/python$version +if test ! -r $python_moddir/copy.py; then + python_moddir=no +fi + +PYTHONMODDIR=$python_moddir + +AC_MSG_RESULT(header $python_incdir library $python_libdir modules $python_moddir) + +if test x$python_incdir = xno || test x$python_libdir = xno || test x$python_moddir = xno; then + LIBPYTHON=$kde_orig_LIBPYTHON + test "x$PYTHONLIB" = "x-Lno" && PYTHONLIB="" + test "x$PYTHONINC" = "x-Ino" && PYTHONINC="" + $2 +else + dnl Note: this test is very weak + kde_python_link_found=no + KDE_TRY_LINK_PYTHON(normal) + KDE_TRY_LINK_PYTHON(m, -lm) + KDE_TRY_LINK_PYTHON(pthread, $LIBPTHREAD) + KDE_TRY_LINK_PYTHON(tcl, -ltcl) + KDE_TRY_LINK_PYTHON(db2, -ldb2) + KDE_TRY_LINK_PYTHON(m_and_thread, [$LIBPTHREAD -lm]) + KDE_TRY_LINK_PYTHON(m_and_thread_and_util, [$LIBPTHREAD -lm -lutil]) + KDE_TRY_LINK_PYTHON(m_and_thread_and_db3, [$LIBPTHREAD -lm -ldb-3 -lutil]) + KDE_TRY_LINK_PYTHON(pthread_and_db3, [$LIBPTHREAD -ldb-3]) + KDE_TRY_LINK_PYTHON(m_and_thread_and_db, [$LIBPTHREAD -lm -ldb -ltermcap -lutil]) + KDE_TRY_LINK_PYTHON(pthread_and_dl, [$LIBPTHREAD $LIBDL -lutil -lreadline -lncurses -lm]) + KDE_TRY_LINK_PYTHON(pthread_and_panel_curses, [$LIBPTHREAD $LIBDL -lm -lpanel -lcurses]) + KDE_TRY_LINK_PYTHON(m_and_thread_and_db_special, [$LIBPTHREAD -lm -ldb -lutil], [], + [AC_MSG_WARN([it seems, Python depends on another library. + Please set LIBPYTHON to '-lpython$version -lotherlib' before calling configure to fix this + and contact the authors to let them know about this problem]) + ]) + + LIBPYTHON="$LIBPYTHON $LIBDL $LIBSOCKET" + AC_SUBST(PYTHONINC) + AC_SUBST(PYTHONLIB) + AC_SUBST(LIBPYTHON) + AC_SUBST(PYTHONMODDIR) + AC_DEFINE(HAVE_PYTHON, 1, [Define if you have the development files for python]) +fi + +]) + + +AC_DEFUN([KDE_CHECK_PYTHON], +[ + KDE_CHECK_PYTHON_INTERN("2.5", + [KDE_CHECK_PYTHON_INTERN("2.4", + [KDE_CHECK_PYTHON_INTERN("2.3", + [KDE_CHECK_PYTHON_INTERN("2.2", + [KDE_CHECK_PYTHON_INTERN("2.1", + [KDE_CHECK_PYTHON_INTERN("2.0", + [KDE_CHECK_PYTHON_INTERN($1, $2) ]) + ]) + ]) + ]) + ]) + ]) +]) + +AC_DEFUN([KDE_CHECK_STL], +[ + AC_LANG_SAVE + AC_LANG_CPLUSPLUS + ac_save_CXXFLAGS="$CXXFLAGS" + CXXFLAGS="`echo $CXXFLAGS | sed s/-fno-exceptions//`" + + AC_MSG_CHECKING([if C++ programs can be compiled]) + AC_CACHE_VAL(kde_cv_stl_works, + [ + AC_TRY_COMPILE([ +#include +using namespace std; +],[ + string astring="Hallo Welt."; + astring.erase(0, 6); // now astring is "Welt" + return 0; +], kde_cv_stl_works=yes, + kde_cv_stl_works=no) +]) + + AC_MSG_RESULT($kde_cv_stl_works) + + if test "$kde_cv_stl_works" = "yes"; then + # back compatible + AC_DEFINE_UNQUOTED(HAVE_SGI_STL, 1, [Define if you have a STL implementation by SGI]) + else + AC_MSG_ERROR([Your Installation isn't able to compile simple C++ programs. +Check config.log for details - if you're using a Linux distribution you might miss +a package named similar to libstdc++-dev.]) + fi + + CXXFLAGS="$ac_save_CXXFLAGS" + AC_LANG_RESTORE +]) + +AC_DEFUN([AC_FIND_QIMGIO], + [AC_REQUIRE([AC_FIND_JPEG]) +AC_REQUIRE([KDE_CHECK_EXTRA_LIBS]) +AC_MSG_CHECKING([for qimgio]) +AC_CACHE_VAL(ac_cv_lib_qimgio, +[ +AC_LANG_SAVE +AC_LANG_CPLUSPLUS +ac_save_LIBS="$LIBS" +ac_save_CXXFLAGS="$CXXFLAGS" +LIBS="$all_libraries -lqimgio -lpng -lz $LIBJPEG $LIBQT" +CXXFLAGS="$CXXFLAGS -I$qt_incdir $all_includes" +AC_TRY_RUN(dnl +[ +#include +#include +int main() { + QString t = "hallo"; + t.fill('t'); + qInitImageIO(); +} +], + ac_cv_lib_qimgio=yes, + ac_cv_lib_qimgio=no, + ac_cv_lib_qimgio=no) +LIBS="$ac_save_LIBS" +CXXFLAGS="$ac_save_CXXFLAGS" +AC_LANG_RESTORE +])dnl +if eval "test \"`echo $ac_cv_lib_qimgio`\" = yes"; then + LIBQIMGIO="-lqimgio -lpng -lz $LIBJPEG" + AC_MSG_RESULT(yes) + AC_DEFINE_UNQUOTED(HAVE_QIMGIO, 1, [Define if you have the Qt extension qimgio available]) + AC_SUBST(LIBQIMGIO) +else + AC_MSG_RESULT(not found) +fi +]) + +AC_DEFUN([AM_DISABLE_LIBRARIES], +[ + AC_PROVIDE([AM_ENABLE_STATIC]) + AC_PROVIDE([AM_ENABLE_SHARED]) + enable_static=no + enable_shared=yes +]) + + +AC_DEFUN([AC_CHECK_UTMP_FILE], +[ + AC_MSG_CHECKING([for utmp file]) + + AC_CACHE_VAL(kde_cv_utmp_file, + [ + kde_cv_utmp_file=no + + for ac_file in \ + \ + /var/run/utmp \ + /var/adm/utmp \ + /etc/utmp \ + ; \ + do + if test -r "$ac_file"; then + kde_cv_utmp_file=$ac_file + break + fi + done + ]) + + if test "$kde_cv_utmp_file" != "no"; then + AC_DEFINE_UNQUOTED(UTMP, "$kde_cv_utmp_file", [Define the file for utmp entries]) + $1 + AC_MSG_RESULT($kde_cv_utmp_file) + else + $2 + AC_MSG_RESULT([non found]) + fi +]) + + +AC_DEFUN([KDE_CREATE_SUBDIRSLIST], +[ + +DO_NOT_COMPILE="$DO_NOT_COMPILE CVS debian bsd-port admin" +TOPSUBDIRS="" + +if test ! -s $srcdir/subdirs; then + dnl Note: Makefile.common creates subdirs, so this is just a fallback + files=`cd $srcdir && ls -1` + dirs=`for i in $files; do if test -d $i; then echo $i; fi; done` + for i in $dirs; do + echo $i >> $srcdir/subdirs + done +fi + +ac_topsubdirs= +if test -s $srcdir/inst-apps; then + ac_topsubdirs="`cat $srcdir/inst-apps`" +elif test -s $srcdir/subdirs; then + ac_topsubdirs="`cat $srcdir/subdirs`" +fi + +for i in $ac_topsubdirs; do + AC_MSG_CHECKING([if $i should be compiled]) + if test -d $srcdir/$i; then + install_it="yes" + for j in $DO_NOT_COMPILE; do + if test $i = $j; then + install_it="no" + fi + done + else + install_it="no" + fi + AC_MSG_RESULT($install_it) + vari=`echo $i | sed -e 's,[[-+.@]],_,g'` + if test $install_it = "yes"; then + TOPSUBDIRS="$TOPSUBDIRS $i" + eval "$vari""_SUBDIR_included=yes" + else + eval "$vari""_SUBDIR_included=no" + fi +done + +AC_SUBST(TOPSUBDIRS) +]) + +AC_DEFUN([KDE_CHECK_NAMESPACES], +[ +AC_MSG_CHECKING(whether C++ compiler supports namespaces) +AC_LANG_SAVE +AC_LANG_CPLUSPLUS +AC_TRY_COMPILE([ +], +[ +namespace Foo { + extern int i; + namespace Bar { + extern int i; + } +} + +int Foo::i = 0; +int Foo::Bar::i = 1; +],[ + AC_MSG_RESULT(yes) + AC_DEFINE(HAVE_NAMESPACES) +], [ +AC_MSG_RESULT(no) +]) +AC_LANG_RESTORE +]) + +dnl ------------------------------------------------------------------------ +dnl Check for S_ISSOCK macro. Doesn't exist on Unix SCO. faure@kde.org +dnl ------------------------------------------------------------------------ +dnl +AC_DEFUN([AC_CHECK_S_ISSOCK], +[ +AC_MSG_CHECKING(for S_ISSOCK) +AC_CACHE_VAL(ac_cv_have_s_issock, +[ +AC_TRY_LINK( +[ +#include +], +[ +struct stat buff; +int b = S_ISSOCK( buff.st_mode ); +], +ac_cv_have_s_issock=yes, +ac_cv_have_s_issock=no) +]) +AC_MSG_RESULT($ac_cv_have_s_issock) +if test "$ac_cv_have_s_issock" = "yes"; then + AC_DEFINE_UNQUOTED(HAVE_S_ISSOCK, 1, [Define if sys/stat.h declares S_ISSOCK.]) +fi + +AH_VERBATIM(_ISSOCK, +[ +#ifndef HAVE_S_ISSOCK +#define HAVE_S_ISSOCK +#define S_ISSOCK(mode) (1==0) +#endif +]) + +]) + +dnl ------------------------------------------------------------------------ +dnl Check for MAXPATHLEN macro, defines KDEMAXPATHLEN. faure@kde.org +dnl ------------------------------------------------------------------------ +dnl +AC_DEFUN([AC_CHECK_KDEMAXPATHLEN], +[ +AC_MSG_CHECKING(for MAXPATHLEN) +AC_CACHE_VAL(ac_cv_maxpathlen, +[ +cat > conftest.$ac_ext < +#endif +#include +#include +#ifndef MAXPATHLEN +#define MAXPATHLEN 1024 +#endif + +KDE_HELLO MAXPATHLEN + +EOF + +ac_try="$ac_cpp conftest.$ac_ext 2>/dev/null | grep '^KDE_HELLO' >conftest.out" + +if AC_TRY_EVAL(ac_try) && test -s conftest.out; then + ac_cv_maxpathlen=`sed 's#KDE_HELLO ##' conftest.out` +else + ac_cv_maxpathlen=1024 +fi + +rm conftest.* + +]) +AC_MSG_RESULT($ac_cv_maxpathlen) +AC_DEFINE_UNQUOTED(KDEMAXPATHLEN,$ac_cv_maxpathlen, [Define a safe value for MAXPATHLEN] ) +]) + +AC_DEFUN([KDE_CHECK_HEADER], +[ + kde_safe_cppflags=$CPPFLAGS + CPPFLAGS="$CPPFLAGS $all_includes" + AC_LANG_SAVE + AC_LANG_CPLUSPLUS + AC_CHECK_HEADER([$1], [$2], [$3], [$4]) + AC_LANG_RESTORE + CPPFLAGS=$kde_safe_cppflags +]) + +AC_DEFUN([KDE_CHECK_HEADERS], +[ + AH_CHECK_HEADERS([$1]) + AC_LANG_SAVE + kde_safe_cppflags=$CPPFLAGS + CPPFLAGS="$CPPFLAGS $all_includes" + AC_LANG_CPLUSPLUS + AC_CHECK_HEADERS([$1], [$2], [$3], [$4]) + CPPFLAGS=$kde_safe_cppflags + AC_LANG_RESTORE +]) + +AC_DEFUN([KDE_FAST_CONFIGURE], +[ + dnl makes configure fast (needs perl) + AC_ARG_ENABLE(fast-perl, AC_HELP_STRING([--disable-fast-perl],[disable fast Makefile generation (needs perl)]), + with_fast_perl=$enableval, with_fast_perl=yes) +]) + +AC_DEFUN([KDE_CONF_FILES], +[ + val= + if test -f $srcdir/configure.files ; then + val=`sed -e 's%^%\$(top_srcdir)/%' $srcdir/configure.files` + fi + CONF_FILES= + if test -n "$val" ; then + for i in $val ; do + CONF_FILES="$CONF_FILES $i" + done + fi + AC_SUBST(CONF_FILES) +])dnl + +dnl This sets the prefix, for arts and kdelibs +dnl Do NOT use in any other module. +dnl It only looks at --prefix, KDEDIR and falls back to /usr/local/kde +AC_DEFUN([KDE_SET_PREFIX_CORE], +[ + unset CDPATH + dnl make $KDEDIR the default for the installation + AC_PREFIX_DEFAULT(${KDEDIR:-/usr/local/kde}) + + if test "x$prefix" = "xNONE"; then + prefix=$ac_default_prefix + ac_configure_args="$ac_configure_args --prefix=$prefix" + fi + # And delete superfluous '/' to make compares easier + prefix=`echo "$prefix" | sed 's,//*,/,g' | sed -e 's,/$,,'` + exec_prefix=`echo "$exec_prefix" | sed 's,//*,/,g' | sed -e 's,/$,,'` + + kde_libs_prefix='$(prefix)' + kde_libs_htmldir='$(kde_htmldir)' + AC_SUBST(kde_libs_prefix) + AC_SUBST(kde_libs_htmldir) + KDE_FAST_CONFIGURE + KDE_CONF_FILES +]) + + +AC_DEFUN([KDE_SET_PREFIX], +[ + unset CDPATH + dnl We can't give real code to that macro, only a value. + dnl It only matters for --help, since we set the prefix in this function anyway. + AC_PREFIX_DEFAULT(${KDEDIR:-the kde prefix}) + + KDE_SET_DEFAULT_BINDIRS + if test "x$prefix" = "xNONE"; then + dnl no prefix given: look for kde-config in the PATH and deduce the prefix from it + KDE_FIND_PATH(kde-config, KDECONFIG, [$kde_default_bindirs], [KDE_MISSING_PROG_ERROR(kde-config)], [], prepend) + else + dnl prefix given: look for kde-config, preferrably in prefix, otherwise in PATH + kde_save_PATH="$PATH" + PATH="$exec_prefix/bin:$prefix/bin:$PATH" + KDE_FIND_PATH(kde-config, KDECONFIG, [$kde_default_bindirs], [KDE_MISSING_PROG_ERROR(kde-config)], [], prepend) + PATH="$kde_save_PATH" + fi + + kde_libs_prefix=`$KDECONFIG --prefix` + if test -z "$kde_libs_prefix" || test ! -x "$kde_libs_prefix"; then + AC_MSG_ERROR([$KDECONFIG --prefix outputed the non existant prefix '$kde_libs_prefix' for kdelibs. + This means it has been moved since you installed it. + This won't work. Please recompile kdelibs for the new prefix. + ]) + fi + kde_libs_htmldir=`$KDECONFIG --install html --expandvars` + + AC_MSG_CHECKING([where to install]) + if test "x$prefix" = "xNONE"; then + prefix=$kde_libs_prefix + AC_MSG_RESULT([$prefix (as returned by kde-config)]) + else + dnl --prefix was given. Compare prefixes and warn (in configure.in.bot.end) if different + given_prefix=$prefix + AC_MSG_RESULT([$prefix (as requested)]) + fi + + # And delete superfluous '/' to make compares easier + prefix=`echo "$prefix" | sed 's,//*,/,g' | sed -e 's,/$,,'` + exec_prefix=`echo "$exec_prefix" | sed 's,//*,/,g' | sed -e 's,/$,,'` + given_prefix=`echo "$given_prefix" | sed 's,//*,/,g' | sed -e 's,/$,,'` + + AC_SUBST(KDECONFIG) + AC_SUBST(kde_libs_prefix) + AC_SUBST(kde_libs_htmldir) + + KDE_FAST_CONFIGURE + KDE_CONF_FILES +]) + +pushdef([AC_PROG_INSTALL], +[ + dnl our own version, testing for a -p flag + popdef([AC_PROG_INSTALL]) + dnl as AC_PROG_INSTALL works as it works we first have + dnl to save if the user didn't specify INSTALL, as the + dnl autoconf one overwrites INSTALL and we have no chance to find + dnl out afterwards + test -n "$INSTALL" && kde_save_INSTALL_given=$INSTALL + test -n "$INSTALL_PROGRAM" && kde_save_INSTALL_PROGRAM_given=$INSTALL_PROGRAM + test -n "$INSTALL_SCRIPT" && kde_save_INSTALL_SCRIPT_given=$INSTALL_SCRIPT + AC_PROG_INSTALL + + if test -z "$kde_save_INSTALL_given" ; then + # OK, user hasn't given any INSTALL, autoconf found one for us + # now we test, if it supports the -p flag + AC_MSG_CHECKING(for -p flag to install) + rm -f confinst.$$.* > /dev/null 2>&1 + echo "Testtest" > confinst.$$.orig + ac_res=no + if ${INSTALL} -p confinst.$$.orig confinst.$$.new > /dev/null 2>&1 ; then + if test -f confinst.$$.new ; then + # OK, -p seems to do no harm to install + INSTALL="${INSTALL} -p" + ac_res=yes + fi + fi + rm -f confinst.$$.* + AC_MSG_RESULT($ac_res) + fi + dnl the following tries to resolve some signs and wonders coming up + dnl with different autoconf/automake versions + dnl e.g.: + dnl *automake 1.4 install-strip sets A_M_INSTALL_PROGRAM_FLAGS to -s + dnl and has INSTALL_PROGRAM = @INSTALL_PROGRAM@ $(A_M_INSTALL_PROGRAM_FLAGS) + dnl it header-vars.am, so there the actual INSTALL_PROGRAM gets the -s + dnl *automake 1.4a (and above) use INSTALL_STRIP_FLAG and only has + dnl INSTALL_PROGRAM = @INSTALL_PROGRAM@ there, but changes the + dnl install-@DIR@PROGRAMS targets to explicitly use that flag + dnl *autoconf 2.13 is dumb, and thinks it can use INSTALL_PROGRAM as + dnl INSTALL_SCRIPT, which breaks with automake <= 1.4 + dnl *autoconf >2.13 (since 10.Apr 1999) has not that failure + dnl *sometimes KDE does not use the install-@DIR@PROGRAM targets from + dnl automake (due to broken Makefile.am or whatever) to install programs, + dnl and so does not see the -s flag in automake > 1.4 + dnl to clean up that mess we: + dnl +set INSTALL_PROGRAM to use INSTALL_STRIP_FLAG + dnl which cleans KDE's program with automake > 1.4; + dnl +set INSTALL_SCRIPT to only use INSTALL, to clean up autoconf's problems + dnl with automake<=1.4 + dnl note that dues to this sometimes two '-s' flags are used (if KDE + dnl properly uses install-@DIR@PROGRAMS, but I don't care + dnl + dnl And to all this comes, that I even can't write in comments variable + dnl names used by automake, because it is so stupid to think I wanted to + dnl _use_ them, therefor I have written A_M_... instead of AM_ + dnl hmm, I wanted to say something ... ahh yes: Arghhh. + + if test -z "$kde_save_INSTALL_PROGRAM_given" ; then + INSTALL_PROGRAM='${INSTALL} $(INSTALL_STRIP_FLAG)' + fi + if test -z "$kde_save_INSTALL_SCRIPT_given" ; then + INSTALL_SCRIPT='${INSTALL}' + fi +])dnl + +AC_DEFUN([KDE_LANG_CPLUSPLUS], +[AC_LANG_CPLUSPLUS +ac_link='rm -rf SunWS_cache; ${CXX-g++} -o conftest${ac_exeext} $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS 1>&AC_FD_CC' +pushdef([AC_LANG_CPLUSPLUS], [popdef([AC_LANG_CPLUSPLUS]) KDE_LANG_CPLUSPLUS]) +]) + +pushdef([AC_LANG_CPLUSPLUS], +[popdef([AC_LANG_CPLUSPLUS]) +KDE_LANG_CPLUSPLUS +]) + +AC_DEFUN([KDE_CHECK_LONG_LONG], +[ +AC_MSG_CHECKING(for long long) +AC_CACHE_VAL(kde_cv_c_long_long, +[ + AC_LANG_SAVE + AC_LANG_CPLUSPLUS + AC_TRY_LINK([], [ + long long foo = 0; + foo = foo+1; + ], + kde_cv_c_long_long=yes, kde_cv_c_long_long=no) + AC_LANG_RESTORE +]) +AC_MSG_RESULT($kde_cv_c_long_long) +if test "$kde_cv_c_long_long" = yes; then + AC_DEFINE(HAVE_LONG_LONG, 1, [Define if you have long long as datatype]) +fi +]) + +AC_DEFUN([KDE_CHECK_LIB], +[ + kde_save_LDFLAGS="$LDFLAGS" + dnl AC_CHECK_LIB modifies LIBS, so save it here + kde_save_LIBS="$LIBS" + LDFLAGS="$LDFLAGS $all_libraries" + case $host_os in + aix*) LDFLAGS="-brtl $LDFLAGS" + test "$GCC" = yes && LDFLAGS="-Wl,$LDFLAGS" + ;; + esac + AC_CHECK_LIB($1, $2, $3, $4, $5) + LDFLAGS="$kde_save_LDFLAGS" + LIBS="$kde_save_LIBS" +]) + +AC_DEFUN([KDE_JAVA_PREFIX], +[ + dir=`dirname "$1"` + base=`basename "$1"` + list=`ls -1 $dir 2> /dev/null` + for entry in $list; do + if test -d $dir/$entry/bin; then + case $entry in + $base) + javadirs="$javadirs $dir/$entry/bin" + ;; + esac + elif test -d $dir/$entry/jre/bin; then + case $entry in + $base) + javadirs="$javadirs $dir/$entry/jre/bin" + ;; + esac + fi + done +]) + +dnl KDE_CHEC_JAVA_DIR(onlyjre) +AC_DEFUN([KDE_CHECK_JAVA_DIR], +[ + +AC_ARG_WITH(java, +AC_HELP_STRING([--with-java=javadir],[use java installed in javadir, --without-java disables]), +[ ac_java_dir=$withval +], ac_java_dir="" +) + +AC_MSG_CHECKING([for Java]) + +dnl at this point ac_java_dir is either a dir, 'no' to disable, or '' to say look in $PATH +if test "x$ac_java_dir" = "xno"; then + kde_java_bindir=no + kde_java_includedir=no + kde_java_libjvmdir=no + kde_java_libgcjdir=no + kde_java_libhpidir=no +else + if test "x$ac_java_dir" = "x"; then + + + dnl No option set -> collect list of candidate paths + if test -n "$JAVA_HOME"; then + KDE_JAVA_PREFIX($JAVA_HOME) + fi + KDE_JAVA_PREFIX(/usr/j2se) + KDE_JAVA_PREFIX(/usr/lib/j2se) + KDE_JAVA_PREFIX(/usr/j*dk*) + KDE_JAVA_PREFIX(/usr/lib/j*dk*) + KDE_JAVA_PREFIX(/opt/j*sdk*) + KDE_JAVA_PREFIX(/usr/lib/java*) + KDE_JAVA_PREFIX(/usr/java*) + KDE_JAVA_PREFIX(/usr/java/j*dk*) + KDE_JAVA_PREFIX(/usr/java/j*re*) + KDE_JAVA_PREFIX(/usr/lib/SunJava2*) + KDE_JAVA_PREFIX(/usr/lib/SunJava*) + KDE_JAVA_PREFIX(/usr/lib/IBMJava2*) + KDE_JAVA_PREFIX(/usr/lib/IBMJava*) + KDE_JAVA_PREFIX(/opt/java*) + + kde_cv_path="NONE" + kde_save_IFS=$IFS + IFS=':' + for dir in $PATH; do + if test -d "$dir"; then + javadirs="$javadirs $dir" + fi + done + IFS=$kde_save_IFS + jredirs= + + dnl Now javadirs contains a list of paths that exist, all ending with bin/ + for dir in $javadirs; do + dnl Check for the java executable + if test -x "$dir/java"; then + sane_path=$(cd $dir; /bin/pwd) + dnl And also check for a libjvm.so somewhere under there + dnl Since we have to go to the parent dir, /usr/bin is excluded, /usr is too big. + if test "$sane_path" != "/usr/bin"; then + libjvmdir=`find $dir/.. -name libjvm.so | sed 's,libjvm.so,,'|head -n 1` + if test ! -f $libjvmdir/libjvm.so; then continue; fi + jredirs="$jredirs $dir" + fi + fi + done + + dnl Now jredirs contains a reduced list, of paths where both java and ../**/libjvm.so was found + JAVAC= + JAVA= + kde_java_bindir=no + for dir in $jredirs; do + JAVA="$dir/java" + kde_java_bindir=$dir + if test -x "$dir/javac"; then + JAVAC="$dir/javac" + break + fi + done + + if test -n "$JAVAC"; then + dnl this substitution might not work - well, we test for jni.h below + kde_java_includedir=`echo $JAVAC | sed -e 's,bin/javac$,include/,'` + else + kde_java_includedir=no + fi + else + dnl config option set + kde_java_bindir=$ac_java_dir/bin + if test -x $ac_java_dir/bin/java && test ! -x $ac_java_dir/bin/javac; then + kde_java_includedir=no + else + kde_java_includedir=$ac_java_dir/include + fi + fi +fi + +dnl At this point kde_java_bindir and kde_java_includedir are either set or "no" +if test "x$kde_java_bindir" != "xno"; then + + dnl Look for libjvm.so + kde_java_libjvmdir=`find $kde_java_bindir/.. -name libjvm.so | sed 's,libjvm.so,,'|head -n 1` + dnl Look for libgcj.so + kde_java_libgcjdir=`find $kde_java_bindir/.. -name libgcj.so | sed 's,libgcj.so,,'|head -n 1` + dnl Look for libhpi.so and avoid green threads + kde_java_libhpidir=`find $kde_java_bindir/.. -name libhpi.so | grep -v green | sed 's,libhpi.so,,' | head -n 1` + + dnl Now check everything's fine under there + dnl the include dir is our flag for having the JDK + if test -d "$kde_java_includedir"; then + if test ! -x "$kde_java_bindir/javac"; then + AC_MSG_ERROR([javac not found under $kde_java_bindir - it seems you passed a wrong --with-java.]) + fi + if test ! -x "$kde_java_bindir/javah"; then + AC_MSG_ERROR([javah not found under $kde_java_bindir. javac was found though! Use --with-java or --without-java.]) + fi + if test ! -x "$kde_java_bindir/jar"; then + AC_MSG_ERROR([jar not found under $kde_java_bindir. javac was found though! Use --with-java or --without-java.]) + fi + if test ! -r "$kde_java_includedir/jni.h"; then + AC_MSG_ERROR([jni.h not found under $kde_java_includedir. Use --with-java or --without-java.]) + fi + + jni_includes="-I$kde_java_includedir" + dnl Strange thing, jni.h requires jni_md.h which is under genunix here.. + dnl and under linux here.. + + dnl not needed for gcj + + if test "x$kde_java_libgcjdir" = "x"; then + test -d "$kde_java_includedir/linux" && jni_includes="$jni_includes -I$kde_java_includedir/linux" + test -d "$kde_java_includedir/solaris" && jni_includes="$jni_includes -I$kde_java_includedir/solaris" + test -d "$kde_java_includedir/genunix" && jni_includes="$jni_includes -I$kde_java_includedir/genunix" + fi + + else + JAVAC= + jni_includes= + fi + + if test "x$kde_java_libgcjdir" = "x"; then + if test ! -r "$kde_java_libjvmdir/libjvm.so"; then + AC_MSG_ERROR([libjvm.so not found under $kde_java_libjvmdir. Use --without-java.]) + fi + else + if test ! -r "$kde_java_libgcjdir/libgcj.so"; then + AC_MSG_ERROR([libgcj.so not found under $kde_java_libgcjdir. Use --without-java.]) + fi + fi + + if test ! -x "$kde_java_bindir/java"; then + AC_MSG_ERROR([java not found under $kde_java_bindir. javac was found though! Use --with-java or --without-java.]) + fi + + dnl not needed for gcj compile + + if test "x$kde_java_libgcjdir" = "x"; then + if test ! -r "$kde_java_libhpidir/libhpi.so"; then + AC_MSG_ERROR([libhpi.so not found under $kde_java_libhpidir. Use --without-java.]) + fi + fi + + if test -n "$jni_includes"; then + dnl Check for JNI version + AC_LANG_SAVE + AC_LANG_CPLUSPLUS + ac_cxxflags_safe="$CXXFLAGS" + CXXFLAGS="$CXXFLAGS $all_includes $jni_includes" + + AC_TRY_COMPILE([ + #include + ], + [ + #ifndef JNI_VERSION_1_2 + Syntax Error + #endif + ],[ kde_jni_works=yes ], + [ kde_jni_works=no ]) + + if test $kde_jni_works = no; then + AC_MSG_ERROR([Incorrect version of $kde_java_includedir/jni.h. + You need to have Java Development Kit (JDK) version 1.2. + + Use --with-java to specify another location. + Use --without-java to configure without java support. + Or download a newer JDK and try again. + See e.g. http://java.sun.com/products/jdk/1.2 ]) + fi + + CXXFLAGS="$ac_cxxflags_safe" + AC_LANG_RESTORE + + dnl All tests ok, inform and subst the variables + + JAVAC=$kde_java_bindir/javac + JAVAH=$kde_java_bindir/javah + JAR=$kde_java_bindir/jar + AC_DEFINE_UNQUOTED(PATH_JAVA, "$kde_java_bindir/java", [Define where your java executable is]) + if test "x$kde_java_libgcjdir" = "x"; then + JVMLIBS="-L$kde_java_libjvmdir -ljvm -L$kde_java_libhpidir -lhpi" + else + JVMLIBS="-L$kde_java_libgcjdir -lgcj" + fi + AC_MSG_RESULT([java JDK in $kde_java_bindir]) + + else + AC_DEFINE_UNQUOTED(PATH_JAVA, "$kde_java_bindir/java", [Define where your java executable is]) + AC_MSG_RESULT([java JRE in $kde_java_bindir]) + fi +elif test -d "/Library/Java/Home"; then + kde_java_bindir="/Library/Java/Home/bin" + jni_includes="-I/Library/Java/Home/include" + + JAVAC=$kde_java_bindir/javac + JAVAH=$kde_java_bindir/javah + JAR=$kde_java_bindir/jar + JVMLIBS="-Wl,-framework,JavaVM" + + AC_DEFINE_UNQUOTED(PATH_JAVA, "$kde_java_bindir/java", [Define where your java executable is]) + AC_MSG_RESULT([Apple Java Framework]) +else + AC_MSG_RESULT([none found]) +fi + +AC_SUBST(JAVAC) +AC_SUBST(JAVAH) +AC_SUBST(JAR) +AC_SUBST(JVMLIBS) +AC_SUBST(jni_includes) + +# for backward compat +kde_cv_java_includedir=$kde_java_includedir +kde_cv_java_bindir=$kde_java_bindir +]) + +dnl this is a redefinition of autoconf 2.5x's AC_FOREACH. +dnl When the argument list becomes big, as in KDE for AC_OUTPUT in +dnl big packages, m4_foreach is dog-slow. So use our own version of +dnl it. (matz@kde.org) +m4_define([mm_foreach], +[m4_pushdef([$1])_mm_foreach($@)m4_popdef([$1])]) +m4_define([mm_car], [[$1]]) +m4_define([mm_car2], [[$@]]) +m4_define([_mm_foreach], +[m4_if(m4_quote($2), [], [], + [m4_define([$1], mm_car($2))$3[]_mm_foreach([$1], + mm_car2(m4_shift($2)), + [$3])])]) +m4_define([AC_FOREACH], +[mm_foreach([$1], m4_split(m4_normalize([$2])), [$3])]) + +AC_DEFUN([KDE_NEED_FLEX], +[ +kde_libs_safe=$LIBS +LIBS="$LIBS $USER_LDFLAGS" +AM_PROG_LEX +LIBS=$kde_libs_safe +if test -z "$LEXLIB"; then + AC_MSG_ERROR([You need to have flex installed.]) +fi +AC_SUBST(LEXLIB) +]) + +AC_DEFUN([AC_PATH_QTOPIA], +[ + dnl TODO: use AC_CACHE_VAL + + if test -z "$1"; then + qtopia_minver_maj=1 + qtopia_minver_min=5 + qtopia_minver_pat=0 + else + qtopia_minver_maj=`echo "$1" | sed -e "s/^\(.*\)\..*\..*$/\1/"` + qtopia_minver_min=`echo "$1" | sed -e "s/^.*\.\(.*\)\..*$/\1/"` + qtopia_minver_pat=`echo "$1" | sed -e "s/^.*\..*\.\(.*\)$/\1/"` + fi + + qtopia_minver="$qtopia_minver_maj$qtopia_minver_min$qtopia_minver_pat" + qtopia_minverstr="$qtopia_minver_maj.$qtopia_minver_min.$qtopia_minver_pat" + + AC_REQUIRE([AC_PATH_QT]) + + AC_MSG_CHECKING([for Qtopia]) + + LIB_QTOPIA="-lqpe" + AC_SUBST(LIB_QTOPIA) + + kde_qtopia_dirs="$QPEDIR /opt/Qtopia" + + ac_qtopia_incdir=NO + + AC_ARG_WITH(qtopia-dir, + AC_HELP_STRING([--with-qtopia-dir=DIR],[where the root of Qtopia is installed]), + [ ac_qtopia_incdir="$withval"/include] ) + + qtopia_incdirs="" + for dir in $kde_qtopia_dirs; do + qtopia_incdirs="$qtopia_incdirs $dir/include" + done + + if test ! "$ac_qtopia_incdir" = "NO"; then + qtopia_incdirs="$ac_qtopia_incdir $qtopia_incdirs" + fi + + qtopia_incdir="" + AC_FIND_FILE(qpe/qpeapplication.h, $qtopia_incdirs, qtopia_incdir) + ac_qtopia_incdir="$qtopia_incdir" + + if test -z "$qtopia_incdir"; then + AC_MSG_ERROR([Cannot find Qtopia headers. Please check your installation.]) + fi + + qtopia_ver_maj=`cat $qtopia_incdir/qpe/version.h | sed -n -e 's,.*QPE_VERSION "\(.*\)\..*\..*".*,\1,p'`; + qtopia_ver_min=`cat $qtopia_incdir/qpe/version.h | sed -n -e 's,.*QPE_VERSION ".*\.\(.*\)\..*".*,\1,p'`; + qtopia_ver_pat=`cat $qtopia_incdir/qpe/version.h | sed -n -e 's,.*QPE_VERSION ".*\..*\.\(.*\)".*,\1,p'`; + + qtopia_ver="$qtopia_ver_maj$qtopia_ver_min$qtopia_ver_pat" + qtopia_verstr="$qtopia_ver_maj.$qtopia_ver_min.$qtopia_ver_pat" + if test "$qtopia_ver" -lt "$qtopia_minver"; then + AC_MSG_ERROR([found Qtopia version $qtopia_verstr but version $qtopia_minverstr +is required.]) + fi + + AC_LANG_SAVE + AC_LANG_CPLUSPLUS + + ac_cxxflags_safe="$CXXFLAGS" + ac_ldflags_safe="$LDFLAGS" + ac_libs_safe="$LIBS" + + CXXFLAGS="$CXXFLAGS -I$qtopia_incdir $all_includes" + LDFLAGS="$LDFLAGS $QT_LDFLAGS $all_libraries $USER_LDFLAGS $KDE_MT_LDFLAGS" + LIBS="$LIBS $LIB_QTOPIA $LIBQT" + + cat > conftest.$ac_ext < +#include + +int main( int argc, char **argv ) +{ + QPEApplication app( argc, argv ); + return 0; +} +EOF + + if AC_TRY_EVAL(ac_link) && test -s conftest; then + rm -f conftest* + else + rm -f conftest* + AC_MSG_ERROR([Cannot link small Qtopia Application. For more details look at +the end of config.log]) + fi + + CXXFLAGS="$ac_cxxflags_safe" + LDFLAGS="$ac_ldflags_safe" + LIBS="$ac_libs_safe" + + AC_LANG_RESTORE + + QTOPIA_INCLUDES="-I$qtopia_incdir" + AC_SUBST(QTOPIA_INCLUDES) + + AC_MSG_RESULT([found version $qtopia_verstr with headers at $qtopia_incdir]) +]) + + +AC_DEFUN([KDE_INIT_DOXYGEN], +[ +AC_MSG_CHECKING([for Qt docs]) +kde_qtdir= +if test "${with_qt_dir+set}" = set; then + kde_qtdir="$with_qt_dir" +fi + +AC_FIND_FILE(qsql.html, [ $kde_qtdir/doc/html $QTDIR/doc/html /usr/share/doc/packages/qt3/html /usr/lib/qt/doc /usr/lib/qt3/doc /usr/lib/qt3/doc/html /usr/doc/qt3/html /usr/doc/qt3 /usr/share/doc/qt3-doc /usr/share/qt3/doc/html /usr/X11R6/share/doc/qt/html ], QTDOCDIR) +AC_MSG_RESULT($QTDOCDIR) + +AC_SUBST(QTDOCDIR) + +KDE_FIND_PATH(dot, DOT, [], []) +if test -n "$DOT"; then + KDE_HAVE_DOT="YES" +else + KDE_HAVE_DOT="NO" +fi +AC_SUBST(KDE_HAVE_DOT) +KDE_FIND_PATH(doxygen, DOXYGEN, [], []) +AC_SUBST(DOXYGEN) + +DOXYGEN_PROJECT_NAME="$1" +DOXYGEN_PROJECT_NUMBER="$2" +AC_SUBST(DOXYGEN_PROJECT_NAME) +AC_SUBST(DOXYGEN_PROJECT_NUMBER) + +KDE_HAS_DOXYGEN=no +if test -n "$DOXYGEN" && test -x "$DOXYGEN" && test -f $QTDOCDIR/qsql.html; then + KDE_HAS_DOXYGEN=yes +fi +AC_SUBST(KDE_HAS_DOXYGEN) + +]) + + +AC_DEFUN([AC_FIND_BZIP2], +[ +AC_MSG_CHECKING([for bzDecompress in libbz2]) +AC_CACHE_VAL(ac_cv_lib_bzip2, +[ +AC_LANG_SAVE +AC_LANG_CPLUSPLUS +kde_save_LIBS="$LIBS" +LIBS="$all_libraries $USER_LDFLAGS -lbz2 $LIBSOCKET" +kde_save_CXXFLAGS="$CXXFLAGS" +CXXFLAGS="$CXXFLAGS $all_includes $USER_INCLUDES" +AC_TRY_LINK(dnl +[ +#define BZ_NO_STDIO +#include +], + [ bz_stream s; (void) bzDecompress(&s); ], + eval "ac_cv_lib_bzip2='-lbz2'", + eval "ac_cv_lib_bzip2=no") +LIBS="$kde_save_LIBS" +CXXFLAGS="$kde_save_CXXFLAGS" +AC_LANG_RESTORE +])dnl +AC_MSG_RESULT($ac_cv_lib_bzip2) + +if test ! "$ac_cv_lib_bzip2" = no; then + BZIP2DIR=bzip2 + + LIBBZ2="$ac_cv_lib_bzip2" + AC_SUBST(LIBBZ2) + +else + + cxx_shared_flag= + ld_shared_flag= + KDE_CHECK_COMPILER_FLAG(shared, [ + ld_shared_flag="-shared" + ]) + KDE_CHECK_COMPILER_FLAG(fPIC, [ + cxx_shared_flag="-fPIC" + ]) + + AC_MSG_CHECKING([for BZ2_bzDecompress in (shared) libbz2]) + AC_CACHE_VAL(ac_cv_lib_bzip2_prefix, + [ + AC_LANG_SAVE + AC_LANG_CPLUSPLUS + kde_save_LIBS="$LIBS" + LIBS="$all_libraries $USER_LDFLAGS $ld_shared_flag -lbz2 $LIBSOCKET" + kde_save_CXXFLAGS="$CXXFLAGS" + CXXFLAGS="$CFLAGS $cxx_shared_flag $all_includes $USER_INCLUDES" + + AC_TRY_LINK(dnl + [ + #define BZ_NO_STDIO + #include + ], + [ bz_stream s; (void) BZ2_bzDecompress(&s); ], + eval "ac_cv_lib_bzip2_prefix='-lbz2'", + eval "ac_cv_lib_bzip2_prefix=no") + LIBS="$kde_save_LIBS" + CXXFLAGS="$kde_save_CXXFLAGS" + AC_LANG_RESTORE + ])dnl + + AC_MSG_RESULT($ac_cv_lib_bzip2_prefix) + + if test ! "$ac_cv_lib_bzip2_prefix" = no; then + BZIP2DIR=bzip2 + + LIBBZ2="$ac_cv_lib_bzip2_prefix" + AC_SUBST(LIBBZ2) + + AC_DEFINE(NEED_BZ2_PREFIX, 1, [Define if the libbz2 functions need the BZ2_ prefix]) + dnl else, we just ignore this + fi + +fi +AM_CONDITIONAL(include_BZIP2, test -n "$BZIP2DIR") +]) + +dnl ------------------------------------------------------------------------ +dnl Try to find the SSL headers and libraries. +dnl $(SSL_LDFLAGS) will be -Lsslliblocation (if needed) +dnl and $(SSL_INCLUDES) will be -Isslhdrlocation (if needed) +dnl ------------------------------------------------------------------------ +dnl +AC_DEFUN([KDE_CHECK_SSL], +[ +LIBSSL="-lssl -lcrypto" +AC_REQUIRE([KDE_CHECK_LIB64]) + +ac_ssl_includes=NO ac_ssl_libraries=NO +ssl_libraries="" +ssl_includes="" +AC_ARG_WITH(ssl-dir, + AC_HELP_STRING([--with-ssl-dir=DIR],[where the root of OpenSSL is installed]), + [ ac_ssl_includes="$withval"/include + ac_ssl_libraries="$withval"/lib$kdelibsuff + ]) + +want_ssl=yes +AC_ARG_WITH(ssl, + AC_HELP_STRING([--without-ssl],[disable SSL checks]), + [want_ssl=$withval]) + +if test $want_ssl = yes; then + +AC_MSG_CHECKING(for OpenSSL) + +AC_CACHE_VAL(ac_cv_have_ssl, +[#try to guess OpenSSL locations + + ssl_incdirs="/usr/include /usr/local/include /usr/ssl/include /usr/local/ssl/include $prefix/include $kde_extra_includes" + ssl_incdirs="$ac_ssl_includes $ssl_incdirs" + AC_FIND_FILE(openssl/ssl.h, $ssl_incdirs, ssl_incdir) + ac_ssl_includes="$ssl_incdir" + + ssl_libdirs="/usr/lib$kdelibsuff /usr/local/lib$kdelibsuff /usr/ssl/lib$kdelibsuff /usr/local/ssl/lib$kdelibsuff $libdir $prefix/lib$kdelibsuff $exec_prefix/lib$kdelibsuff $kde_extra_libs" + if test ! "$ac_ssl_libraries" = "NO"; then + ssl_libdirs="$ac_ssl_libraries $ssl_libdirs" + fi + + test=NONE + ssl_libdir=NONE + for dir in $ssl_libdirs; do + try="ls -1 $dir/libssl*" + if test=`eval $try 2> /dev/null`; then ssl_libdir=$dir; break; else echo "tried $dir" >&AC_FD_CC ; fi + done + + ac_ssl_libraries="$ssl_libdir" + + ac_ldflags_safe="$LDFLAGS" + ac_libs_safe="$LIBS" + + LDFLAGS="$LDFLAGS -L$ssl_libdir $all_libraries" + LIBS="$LIBS $LIBSSL -lRSAglue -lrsaref" + + AC_TRY_LINK(,void RSAPrivateEncrypt(void);RSAPrivateEncrypt();, + ac_ssl_rsaref="yes" + , + ac_ssl_rsaref="no" + ) + + LDFLAGS="$ac_ldflags_safe" + LIBS="$ac_libs_safe" + + if test "$ac_ssl_includes" = NO || test "$ac_ssl_libraries" = NO; then + have_ssl=no + else + have_ssl=yes; + fi + + ]) + + eval "$ac_cv_have_ssl" + + AC_MSG_RESULT([libraries $ac_ssl_libraries, headers $ac_ssl_includes]) + + AC_MSG_CHECKING([whether OpenSSL uses rsaref]) + AC_MSG_RESULT($ac_ssl_rsaref) + + AC_MSG_CHECKING([for easter eggs]) + AC_MSG_RESULT([none found]) + +else + have_ssl=no +fi + +if test "$have_ssl" = yes; then + AC_MSG_CHECKING(for OpenSSL version) + dnl Check for SSL version + AC_CACHE_VAL(ac_cv_ssl_version, + [ + + cat >conftest.$ac_ext < +#include + int main() { + +#ifndef OPENSSL_VERSION_NUMBER + printf("ssl_version=\\"error\\"\n"); +#else + if (OPENSSL_VERSION_NUMBER < 0x00906000) + printf("ssl_version=\\"old\\"\n"); + else + printf("ssl_version=\\"ok\\"\n"); +#endif + return (0); + } +EOF + + ac_save_CPPFLAGS=$CPPFLAGS + if test "$ac_ssl_includes" != "/usr/include"; then + CPPFLAGS="$CPPFLAGS -I$ac_ssl_includes" + fi + + if AC_TRY_EVAL(ac_link); then + + if eval `./conftest 2>&5`; then + if test $ssl_version = error; then + AC_MSG_ERROR([$ssl_incdir/openssl/opensslv.h doesn't define OPENSSL_VERSION_NUMBER !]) + else + if test $ssl_version = old; then + AC_MSG_WARN([OpenSSL version too old. Upgrade to 0.9.6 at least, see http://www.openssl.org. SSL support disabled.]) + have_ssl=no + fi + fi + ac_cv_ssl_version="ssl_version=$ssl_version" + else + AC_MSG_ERROR([Your system couldn't run a small SSL test program. + Check config.log, and if you can't figure it out, send a mail to + David Faure , attaching your config.log]) + fi + + else + AC_MSG_ERROR([Your system couldn't link a small SSL test program. + Check config.log, and if you can't figure it out, send a mail to + David Faure , attaching your config.log]) + fi + CPPFLAGS=$ac_save_CPPFLAGS + + ]) + + eval "$ac_cv_ssl_version" + AC_MSG_RESULT($ssl_version) +fi + +if test "$have_ssl" != yes; then + LIBSSL=""; +else + AC_DEFINE(HAVE_SSL, 1, [If we are going to use OpenSSL]) + ac_cv_have_ssl="have_ssl=yes \ + ac_ssl_includes=$ac_ssl_includes ac_ssl_libraries=$ac_ssl_libraries ac_ssl_rsaref=$ac_ssl_rsaref" + + + ssl_libraries="$ac_ssl_libraries" + ssl_includes="$ac_ssl_includes" + + if test "$ac_ssl_rsaref" = yes; then + LIBSSL="-lssl -lcrypto -lRSAglue -lrsaref" + fi + + if test $ssl_version = "old"; then + AC_DEFINE(HAVE_OLD_SSL_API, 1, [Define if you have OpenSSL < 0.9.6]) + fi +fi + +SSL_INCLUDES= + +if test "$ssl_includes" = "/usr/include"; then + if test -f /usr/kerberos/include/krb5.h; then + SSL_INCLUDES="-I/usr/kerberos/include" + fi +elif test "$ssl_includes" != "/usr/local/include" && test -n "$ssl_includes"; then + SSL_INCLUDES="-I$ssl_includes" +fi + +if test "$ssl_libraries" = "/usr/lib" || test "$ssl_libraries" = "/usr/local/lib" || test -z "$ssl_libraries" || test "$ssl_libraries" = "NONE"; then + SSL_LDFLAGS="" +else + SSL_LDFLAGS="-L$ssl_libraries -R$ssl_libraries" +fi + +AC_SUBST(SSL_INCLUDES) +AC_SUBST(SSL_LDFLAGS) +AC_SUBST(LIBSSL) +]) + +AC_DEFUN([KDE_CHECK_STRLCPY], +[ + AC_REQUIRE([AC_CHECK_STRLCAT]) + AC_REQUIRE([AC_CHECK_STRLCPY]) + AC_CHECK_SIZEOF(size_t) + AC_CHECK_SIZEOF(unsigned long) + + AC_MSG_CHECKING([sizeof size_t == sizeof unsigned long]) + AC_TRY_COMPILE(,[ + #if SIZEOF_SIZE_T != SIZEOF_UNSIGNED_LONG + choke me + #endif + ],AC_MSG_RESULT([yes]),[ + AC_MSG_RESULT(no) + AC_MSG_ERROR([ + Apparently on your system our assumption sizeof size_t == sizeof unsigned long + does not apply. Please mail kde-devel@kde.org with a description of your system! + ]) + ]) +]) + +AC_DEFUN([KDE_CHECK_BINUTILS], +[ + AC_MSG_CHECKING([if ld supports unversioned version maps]) + + kde_save_LDFLAGS="$LDFLAGS" + LDFLAGS="$LDFLAGS -Wl,--version-script=conftest.map" + echo "{ local: extern \"C++\" { foo }; };" > conftest.map + AC_TRY_LINK([int foo;], +[ +#ifdef __INTEL_COMPILER +icc apparently does not support libtools version-info and version-script +at the same time. Dunno where the bug is, but until somebody figured out, +better disable the optional version scripts. +#endif + + foo = 42; +], kde_supports_versionmaps=yes, kde_supports_versionmaps=no) + LDFLAGS="$kde_save_LDFLAGS" + rm -f conftest.map + AM_CONDITIONAL(include_VERSION_SCRIPT, + [test "$kde_supports_versionmaps" = "yes" && test "$kde_use_debug_code" = "no"]) + + AC_MSG_RESULT($kde_supports_versionmaps) +]) + +AC_DEFUN([AM_PROG_OBJC],[ +AC_CHECK_PROGS(OBJC, gcc, gcc) +test -z "$OBJC" && AC_MSG_ERROR([no acceptable objective-c gcc found in \$PATH]) +if test "x${OBJCFLAGS-unset}" = xunset; then + OBJCFLAGS="-g -O2" +fi +AC_SUBST(OBJCFLAGS) +_AM_IF_OPTION([no-dependencies],, [_AM_DEPENDENCIES(OBJC)]) +]) + +AC_DEFUN([KDE_CHECK_PERL], +[ + KDE_FIND_PATH(perl, PERL, [$bindir $exec_prefix/bin $prefix/bin], [ + AC_MSG_ERROR([No Perl found in your $PATH. +We need perl to generate some code.]) + ]) + AC_SUBST(PERL) +]) + +AC_DEFUN([KDE_CHECK_LARGEFILE], +[ +AC_SYS_LARGEFILE +if test "$ac_cv_sys_file_offset_bits" != no; then + CPPFLAGS="$CPPFLAGS -D_FILE_OFFSET_BITS=$ac_cv_sys_file_offset_bits" +fi + +if test "x$ac_cv_sys_large_files" != "xno"; then + CPPFLAGS="$CPPFLAGS -D_LARGE_FILES=1" +fi + +]) + +dnl A small extension to PKG_CHECK_MODULES (defined in pkg.m4.in) +dnl which allows to search for libs that get installed into the KDE prefix. +dnl +dnl Syntax: KDE_PKG_CHECK_MODULES(KSTUFF, libkexif >= 0.2 glib = 1.3.4, action-if, action-not) +dnl defines KSTUFF_LIBS, KSTUFF_CFLAGS, see pkg-config man page +dnl also defines KSTUFF_PKG_ERRORS on error +AC_DEFUN([KDE_PKG_CHECK_MODULES], [ + + PKG_CONFIG_PATH="$prefix/lib${kdelibsuff}/pkgconfig:$PKG_CONFIG_PATH" + if test "$prefix" != "$kde_libs_prefix"; then + PKG_CONFIG_PATH="$kde_libs_prefix/lib${kdelibsuff}/pkgconfig:$PKG_CONFIG_PATH" + fi + export PKG_CONFIG_PATH + PKG_CHECK_MODULES([$1],[$2],[$3],[$4]) +]) + + +dnl Check for PIE support in the compiler and linker +AC_DEFUN([KDE_CHECK_PIE_SUPPORT], +[ + AC_CACHE_CHECK([for PIE support], kde_cv_val_pie_support, + [ + AC_LANG_SAVE + AC_LANG_CPLUSPLUS + safe_CXXFLAGS=$CXXFLAGS + safe_LDFLAGS=$LDFLAGS + CXXFLAGS="$CXXFLAGS -fPIE" + LDFLAGS="$LDFLAGS -pie" + + AC_TRY_LINK([int foo;], [], [kde_cv_val_pie_support=yes], [kde_cv_val_pie_support=no]) + + CXXFLAGS=$safe_CXXFLAGS + LDFLAGS=$safe_LDFLAGS + AC_LANG_RESTORE + ]) + + AC_MSG_CHECKING(if enabling -pie/fPIE support) + + AC_ARG_ENABLE(pie, + AC_HELP_STRING([--enable-pie],[platform supports PIE linking [default=detect]]), + [kde_has_pie_support=$enableval], + [kde_has_pie_support=detect]) + + if test "$kde_has_pie_support" = "detect"; then + kde_has_pie_support=$kde_cv_val_pie_support + fi + + AC_MSG_RESULT([$kde_has_pie_support]) + + KDE_USE_FPIE="" + KDE_USE_PIE="" + + AC_SUBST([KDE_USE_FPIE]) + AC_SUBST([KDE_USE_PIE]) + + if test "$kde_has_pie_support" = "yes"; then + KDE_USE_FPIE="-fPIE" + KDE_USE_PIE="-pie" + fi +]) +# libtool.m4 - Configure libtool for the host system. -*-Autoconf-*- +## Copyright 1996, 1997, 1998, 1999, 2000, 2001 +## Free Software Foundation, Inc. +## Originally by Gordon Matzigkeit , 1996 +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, but +## WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +## General Public License for more details. +## +## You should have received a copy of the GNU 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. +## +## As a special exception to the GNU General Public License, if you +## distribute this file as part of a program that contains a +## configuration script generated by Autoconf, you may include it under +## the same distribution terms that you use for the rest of that program. + +# serial 47 AC_PROG_LIBTOOL + + +# AC_PROVIDE_IFELSE(MACRO-NAME, IF-PROVIDED, IF-NOT-PROVIDED) +# ----------------------------------------------------------- +# If this macro is not defined by Autoconf, define it here. +m4_ifdef([AC_PROVIDE_IFELSE], + [], + [m4_define([AC_PROVIDE_IFELSE], + [m4_ifdef([AC_PROVIDE_$1], + [$2], [$3])])]) + + +# AC_PROG_LIBTOOL +# --------------- +AC_DEFUN([AC_PROG_LIBTOOL], +[AC_REQUIRE([_AC_PROG_LIBTOOL])dnl +dnl If AC_PROG_CXX has already been expanded, run AC_LIBTOOL_CXX +dnl immediately, otherwise, hook it in at the end of AC_PROG_CXX. + AC_PROVIDE_IFELSE([AC_PROG_CXX], + [AC_LIBTOOL_CXX], + [define([AC_PROG_CXX], defn([AC_PROG_CXX])[AC_LIBTOOL_CXX + ])]) +dnl And a similar setup for Fortran 77 support + AC_PROVIDE_IFELSE([AC_PROG_F77], + [AC_LIBTOOL_F77], + [define([AC_PROG_F77], defn([AC_PROG_F77])[AC_LIBTOOL_F77 +])]) + +dnl Quote A][M_PROG_GCJ so that aclocal doesn't bring it in needlessly. +dnl If either AC_PROG_GCJ or A][M_PROG_GCJ have already been expanded, run +dnl AC_LIBTOOL_GCJ immediately, otherwise, hook it in at the end of both. + AC_PROVIDE_IFELSE([AC_PROG_GCJ], + [AC_LIBTOOL_GCJ], + [AC_PROVIDE_IFELSE([A][M_PROG_GCJ], + [AC_LIBTOOL_GCJ], + [AC_PROVIDE_IFELSE([LT_AC_PROG_GCJ], + [AC_LIBTOOL_GCJ], + [ifdef([AC_PROG_GCJ], + [define([AC_PROG_GCJ], defn([AC_PROG_GCJ])[AC_LIBTOOL_GCJ])]) + ifdef([A][M_PROG_GCJ], + [define([A][M_PROG_GCJ], defn([A][M_PROG_GCJ])[AC_LIBTOOL_GCJ])]) + ifdef([LT_AC_PROG_GCJ], + [define([LT_AC_PROG_GCJ], + defn([LT_AC_PROG_GCJ])[AC_LIBTOOL_GCJ])])])]) +])])# AC_PROG_LIBTOOL + + +# _AC_PROG_LIBTOOL +# ---------------- +AC_DEFUN([_AC_PROG_LIBTOOL], +[AC_REQUIRE([AC_LIBTOOL_SETUP])dnl +AC_BEFORE([$0],[AC_LIBTOOL_CXX])dnl +AC_BEFORE([$0],[AC_LIBTOOL_F77])dnl +AC_BEFORE([$0],[AC_LIBTOOL_GCJ])dnl + +# This can be used to rebuild libtool when needed +LIBTOOL_DEPS="$ac_aux_dir/ltmain.sh" + +# Always use our own libtool. +LIBTOOL='$(SHELL) $(top_builddir)/libtool --silent' +AC_SUBST(LIBTOOL)dnl + +# Prevent multiple expansion +define([AC_PROG_LIBTOOL], []) +])# _AC_PROG_LIBTOOL + + +# AC_LIBTOOL_SETUP +# ---------------- +AC_DEFUN([AC_LIBTOOL_SETUP], +[AC_PREREQ(2.50)dnl +AC_REQUIRE([AC_ENABLE_SHARED])dnl +AC_REQUIRE([AC_ENABLE_STATIC])dnl +AC_REQUIRE([AC_ENABLE_FAST_INSTALL])dnl +AC_REQUIRE([AC_CANONICAL_HOST])dnl +AC_REQUIRE([AC_CANONICAL_BUILD])dnl +AC_REQUIRE([AC_PROG_CC])dnl +AC_REQUIRE([AC_PROG_LD])dnl +AC_REQUIRE([AC_PROG_LD_RELOAD_FLAG])dnl +AC_REQUIRE([AC_PROG_NM])dnl + +AC_REQUIRE([AC_PROG_LN_S])dnl +AC_REQUIRE([AC_DEPLIBS_CHECK_METHOD])dnl +# Autoconf 2.13's AC_OBJEXT and AC_EXEEXT macros only works for C compilers! +AC_REQUIRE([AC_OBJEXT])dnl +AC_REQUIRE([AC_EXEEXT])dnl +dnl + +AC_LIBTOOL_SYS_MAX_CMD_LEN +AC_LIBTOOL_SYS_GLOBAL_SYMBOL_PIPE +AC_LIBTOOL_OBJDIR + +AC_REQUIRE([_LT_AC_SYS_COMPILER])dnl +_LT_AC_PROG_ECHO_BACKSLASH + +case $host_os in +aix3*) + # AIX sometimes has problems with the GCC collect2 program. For some + # reason, if we set the COLLECT_NAMES environment variable, the problems + # vanish in a puff of smoke. + if test "X${COLLECT_NAMES+set}" != Xset; then + COLLECT_NAMES= + export COLLECT_NAMES + fi + ;; +esac + +# Sed substitution that helps us do robust quoting. It backslashifies +# metacharacters that are still active within double-quoted strings. +Xsed='sed -e s/^X//' +[sed_quote_subst='s/\([\\"\\`$\\\\]\)/\\\1/g'] + +# Same as above, but do not quote variable references. +[double_quote_subst='s/\([\\"\\`\\\\]\)/\\\1/g'] + +# Sed substitution to delay expansion of an escaped shell variable in a +# double_quote_subst'ed string. +delay_variable_subst='s/\\\\\\\\\\\$/\\\\\\$/g' + +# Sed substitution to avoid accidental globbing in evaled expressions +no_glob_subst='s/\*/\\\*/g' + +# Constants: +rm="rm -f" + +# Global variables: +default_ofile=libtool +can_build_shared=yes + +# All known linkers require a `.a' archive for static linking (except M$VC, +# which needs '.lib'). +libext=a +ltmain="$ac_aux_dir/ltmain.sh" +ofile="$default_ofile" +with_gnu_ld="$lt_cv_prog_gnu_ld" + +AC_CHECK_TOOL(AR, ar, false) +AC_CHECK_TOOL(RANLIB, ranlib, :) +AC_CHECK_TOOL(STRIP, strip, :) + +old_CC="$CC" +old_CFLAGS="$CFLAGS" + +# Set sane defaults for various variables +test -z "$AR" && AR=ar +test -z "$AR_FLAGS" && AR_FLAGS=cru +test -z "$AS" && AS=as +test -z "$CC" && CC=cc +test -z "$LTCC" && LTCC=$CC +test -z "$DLLTOOL" && DLLTOOL=dlltool +test -z "$LD" && LD=ld +test -z "$LN_S" && LN_S="ln -s" +test -z "$MAGIC_CMD" && MAGIC_CMD=file +test -z "$NM" && NM=nm +test -z "$SED" && SED=sed +test -z "$OBJDUMP" && OBJDUMP=objdump +test -z "$RANLIB" && RANLIB=: +test -z "$STRIP" && STRIP=: +test -z "$ac_objext" && ac_objext=o + +# Determine commands to create old-style static archives. +old_archive_cmds='$AR $AR_FLAGS $oldlib$oldobjs$old_deplibs' +old_postinstall_cmds='chmod 644 $oldlib' +old_postuninstall_cmds= + +if test -n "$RANLIB"; then + case $host_os in + openbsd*) + old_postinstall_cmds="\$RANLIB -t \$oldlib~$old_postinstall_cmds" + ;; + *) + old_postinstall_cmds="\$RANLIB \$oldlib~$old_postinstall_cmds" + ;; + esac + old_archive_cmds="$old_archive_cmds~\$RANLIB \$oldlib" +fi + +# Only perform the check for file, if the check method requires it +case $deplibs_check_method in +file_magic*) + if test "$file_magic_cmd" = '$MAGIC_CMD'; then + AC_PATH_MAGIC + fi + ;; +esac + +AC_PROVIDE_IFELSE([AC_LIBTOOL_DLOPEN], enable_dlopen=yes, enable_dlopen=no) +AC_PROVIDE_IFELSE([AC_LIBTOOL_WIN32_DLL], +enable_win32_dll=yes, enable_win32_dll=no) + +AC_ARG_ENABLE([libtool-lock], + [AC_HELP_STRING([--disable-libtool-lock], + [avoid locking (might break parallel builds)])]) +test "x$enable_libtool_lock" != xno && enable_libtool_lock=yes + +AC_ARG_WITH([pic], + [AC_HELP_STRING([--with-pic], + [try to use only PIC/non-PIC objects @<:@default=use both@:>@])], + [pic_mode="$withval"], + [pic_mode=default]) +test -z "$pic_mode" && pic_mode=default + +# Use C for the default configuration in the libtool script +tagname= +AC_LIBTOOL_LANG_C_CONFIG +_LT_AC_TAGCONFIG +])# AC_LIBTOOL_SETUP + + +# _LT_AC_SYS_COMPILER +# ------------------- +AC_DEFUN([_LT_AC_SYS_COMPILER], +[AC_REQUIRE([AC_PROG_CC])dnl + +# If no C compiler was specified, use CC. +LTCC=${LTCC-"$CC"} + +# Allow CC to be a program name with arguments. +compiler=$CC +])# _LT_AC_SYS_COMPILER + + +# _LT_AC_SYS_LIBPATH_AIX +# ---------------------- +# Links a minimal program and checks the executable +# for the system default hardcoded library path. In most cases, +# this is /usr/lib:/lib, but when the MPI compilers are used +# the location of the communication and MPI libs are included too. +# If we don't find anything, use the default library path according +# to the aix ld manual. +AC_DEFUN([_LT_AC_SYS_LIBPATH_AIX], +[AC_LINK_IFELSE(AC_LANG_PROGRAM,[ +aix_libpath=`dump -H conftest$ac_exeext 2>/dev/null | $SED -n -e '/Import File Strings/,/^$/ { /^0/ { s/^0 *\(.*\)$/\1/; p; } +}'` +# Check for a 64-bit object if we didn't find anything. +if test -z "$aix_libpath"; then aix_libpath=`dump -HX64 conftest$ac_exeext 2>/dev/null | $SED -n -e '/Import File Strings/,/^$/ { /^0/ { s/^0 *\(.*\)$/\1/; p; } +}'`; fi],[]) +if test -z "$aix_libpath"; then aix_libpath="/usr/lib:/lib"; fi +])# _LT_AC_SYS_LIBPATH_AIX + + +# _LT_AC_SHELL_INIT(ARG) +# ---------------------- +AC_DEFUN([_LT_AC_SHELL_INIT], +[ifdef([AC_DIVERSION_NOTICE], + [AC_DIVERT_PUSH(AC_DIVERSION_NOTICE)], + [AC_DIVERT_PUSH(NOTICE)]) +$1 +AC_DIVERT_POP +])# _LT_AC_SHELL_INIT + + +# _LT_AC_PROG_ECHO_BACKSLASH +# -------------------------- +# Add some code to the start of the generated configure script which +# will find an echo command which doesn't interpret backslashes. +AC_DEFUN([_LT_AC_PROG_ECHO_BACKSLASH], +[_LT_AC_SHELL_INIT([ +# Check that we are running under the correct shell. +SHELL=${CONFIG_SHELL-/bin/sh} + +case X$ECHO in +X*--fallback-echo) + # Remove one level of quotation (which was required for Make). + ECHO=`echo "$ECHO" | sed 's,\\\\\[$]\\[$]0,'[$]0','` + ;; +esac + +echo=${ECHO-echo} +if test "X[$]1" = X--no-reexec; then + # Discard the --no-reexec flag, and continue. + shift +elif test "X[$]1" = X--fallback-echo; then + # Avoid inline document here, it may be left over + : +elif test "X`($echo '\t') 2>/dev/null`" = 'X\t' ; then + # Yippee, $echo works! + : +else + # Restart under the correct shell. + exec $SHELL "[$]0" --no-reexec ${1+"[$]@"} +fi + +if test "X[$]1" = X--fallback-echo; then + # used as fallback echo + shift + cat </dev/null && + echo_test_string="`eval $cmd`" && + (test "X$echo_test_string" = "X$echo_test_string") 2>/dev/null + then + break + fi + done +fi + +if test "X`($echo '\t') 2>/dev/null`" = 'X\t' && + echo_testing_string=`($echo "$echo_test_string") 2>/dev/null` && + test "X$echo_testing_string" = "X$echo_test_string"; then + : +else + # The Solaris, AIX, and Digital Unix default echo programs unquote + # backslashes. This makes it impossible to quote backslashes using + # echo "$something" | sed 's/\\/\\\\/g' + # + # So, first we look for a working echo in the user's PATH. + + lt_save_ifs="$IFS"; IFS=$PATH_SEPARATOR + for dir in $PATH /usr/ucb; do + IFS="$lt_save_ifs" + if (test -f $dir/echo || test -f $dir/echo$ac_exeext) && + test "X`($dir/echo '\t') 2>/dev/null`" = 'X\t' && + echo_testing_string=`($dir/echo "$echo_test_string") 2>/dev/null` && + test "X$echo_testing_string" = "X$echo_test_string"; then + echo="$dir/echo" + break + fi + done + IFS="$lt_save_ifs" + + if test "X$echo" = Xecho; then + # We didn't find a better echo, so look for alternatives. + if test "X`(print -r '\t') 2>/dev/null`" = 'X\t' && + echo_testing_string=`(print -r "$echo_test_string") 2>/dev/null` && + test "X$echo_testing_string" = "X$echo_test_string"; then + # This shell has a builtin print -r that does the trick. + echo='print -r' + elif (test -f /bin/ksh || test -f /bin/ksh$ac_exeext) && + test "X$CONFIG_SHELL" != X/bin/ksh; then + # If we have ksh, try running configure again with it. + ORIGINAL_CONFIG_SHELL=${CONFIG_SHELL-/bin/sh} + export ORIGINAL_CONFIG_SHELL + CONFIG_SHELL=/bin/ksh + export CONFIG_SHELL + exec $CONFIG_SHELL "[$]0" --no-reexec ${1+"[$]@"} + else + # Try using printf. + echo='printf %s\n' + if test "X`($echo '\t') 2>/dev/null`" = 'X\t' && + echo_testing_string=`($echo "$echo_test_string") 2>/dev/null` && + test "X$echo_testing_string" = "X$echo_test_string"; then + # Cool, printf works + : + elif echo_testing_string=`($ORIGINAL_CONFIG_SHELL "[$]0" --fallback-echo '\t') 2>/dev/null` && + test "X$echo_testing_string" = 'X\t' && + echo_testing_string=`($ORIGINAL_CONFIG_SHELL "[$]0" --fallback-echo "$echo_test_string") 2>/dev/null` && + test "X$echo_testing_string" = "X$echo_test_string"; then + CONFIG_SHELL=$ORIGINAL_CONFIG_SHELL + export CONFIG_SHELL + SHELL="$CONFIG_SHELL" + export SHELL + echo="$CONFIG_SHELL [$]0 --fallback-echo" + elif echo_testing_string=`($CONFIG_SHELL "[$]0" --fallback-echo '\t') 2>/dev/null` && + test "X$echo_testing_string" = 'X\t' && + echo_testing_string=`($CONFIG_SHELL "[$]0" --fallback-echo "$echo_test_string") 2>/dev/null` && + test "X$echo_testing_string" = "X$echo_test_string"; then + echo="$CONFIG_SHELL [$]0 --fallback-echo" + else + # maybe with a smaller string... + prev=: + + for cmd in 'echo test' 'sed 2q "[$]0"' 'sed 10q "[$]0"' 'sed 20q "[$]0"' 'sed 50q "[$]0"'; do + if (test "X$echo_test_string" = "X`eval $cmd`") 2>/dev/null + then + break + fi + prev="$cmd" + done + + if test "$prev" != 'sed 50q "[$]0"'; then + echo_test_string=`eval $prev` + export echo_test_string + exec ${ORIGINAL_CONFIG_SHELL-${CONFIG_SHELL-/bin/sh}} "[$]0" ${1+"[$]@"} + else + # Oops. We lost completely, so just stick with echo. + echo=echo + fi + fi + fi + fi +fi +fi + +# Copy echo and quote the copy suitably for passing to libtool from +# the Makefile, instead of quoting the original, which is used later. +ECHO=$echo +if test "X$ECHO" = "X$CONFIG_SHELL [$]0 --fallback-echo"; then + ECHO="$CONFIG_SHELL \\\$\[$]0 --fallback-echo" +fi + +AC_SUBST(ECHO) +])])# _LT_AC_PROG_ECHO_BACKSLASH + + +# _LT_AC_LOCK +# ----------- +AC_DEFUN([_LT_AC_LOCK], +[AC_ARG_ENABLE([libtool-lock], + [AC_HELP_STRING([--disable-libtool-lock], + [avoid locking (might break parallel builds)])]) +test "x$enable_libtool_lock" != xno && enable_libtool_lock=yes + +# Some flags need to be propagated to the compiler or linker for good +# libtool support. +case $host in +ia64-*-hpux*) + # Find out which ABI we are using. + echo 'int i;' > conftest.$ac_ext + if AC_TRY_EVAL(ac_compile); then + case `/usr/bin/file conftest.$ac_objext` in + *ELF-32*) + HPUX_IA64_MODE="32" + ;; + *ELF-64*) + HPUX_IA64_MODE="64" + ;; + esac + fi + rm -rf conftest* + ;; +*-*-irix6*) + # Find out which ABI we are using. + echo '[#]line __oline__ "configure"' > conftest.$ac_ext + if AC_TRY_EVAL(ac_compile); then + if test "$lt_cv_prog_gnu_ld" = yes; then + case `/usr/bin/file conftest.$ac_objext` in + *32-bit*) + LD="${LD-ld} -melf32bsmip" + ;; + *N32*) + LD="${LD-ld} -melf32bmipn32" + ;; + *64-bit*) + LD="${LD-ld} -melf64bmip" + ;; + esac + else + case `/usr/bin/file conftest.$ac_objext` in + *32-bit*) + LD="${LD-ld} -32" + ;; + *N32*) + LD="${LD-ld} -n32" + ;; + *64-bit*) + LD="${LD-ld} -64" + ;; + esac + fi + fi + rm -rf conftest* + ;; + +x86_64-*linux*|ppc*-*linux*|powerpc*-*linux*|s390*-*linux*|sparc*-*linux*) + # Find out which ABI we are using. + echo 'int i;' > conftest.$ac_ext + if AC_TRY_EVAL(ac_compile); then + case "`/usr/bin/file conftest.o`" in + *32-bit*) + LINUX_64_MODE="32" + case $host in + x86_64-*linux*) + LD="${LD-ld} -m elf_i386" + ;; + ppc64-*linux*) + LD="${LD-ld} -m elf32ppclinux" + ;; + s390x-*linux*) + LD="${LD-ld} -m elf_s390" + ;; + sparc64-*linux*) + LD="${LD-ld} -m elf32_sparc" + ;; + esac + ;; + *64-bit*) + LINUX_64_MODE="64" + case $host in + x86_64-*linux*) + LD="${LD-ld} -m elf_x86_64" + ;; + ppc*-*linux*|powerpc*-*linux*) + LD="${LD-ld} -m elf64ppc" + ;; + s390*-*linux*) + LD="${LD-ld} -m elf64_s390" + ;; + sparc*-*linux*) + LD="${LD-ld} -m elf64_sparc" + ;; + esac + ;; + esac + fi + rm -rf conftest* + ;; + +*-*-sco3.2v5*) + # On SCO OpenServer 5, we need -belf to get full-featured binaries. + SAVE_CFLAGS="$CFLAGS" + CFLAGS="$CFLAGS -belf" + AC_CACHE_CHECK([whether the C compiler needs -belf], lt_cv_cc_needs_belf, + [AC_LANG_PUSH(C) + AC_TRY_LINK([],[],[lt_cv_cc_needs_belf=yes],[lt_cv_cc_needs_belf=no]) + AC_LANG_POP]) + if test x"$lt_cv_cc_needs_belf" != x"yes"; then + # this is probably gcc 2.8.0, egcs 1.0 or newer; no need for -belf + CFLAGS="$SAVE_CFLAGS" + fi + ;; +AC_PROVIDE_IFELSE([AC_LIBTOOL_WIN32_DLL], +[*-*-cygwin* | *-*-mingw* | *-*-pw32*) + AC_CHECK_TOOL(DLLTOOL, dlltool, false) + AC_CHECK_TOOL(AS, as, false) + AC_CHECK_TOOL(OBJDUMP, objdump, false) + ;; + ]) +esac + +need_locks="$enable_libtool_lock" + +])# _LT_AC_LOCK + + +# AC_LIBTOOL_COMPILER_OPTION(MESSAGE, VARIABLE-NAME, FLAGS, +# [OUTPUT-FILE], [ACTION-SUCCESS], [ACTION-FAILURE]) +# ---------------------------------------------------------------- +# Check whether the given compiler option works +AC_DEFUN([AC_LIBTOOL_COMPILER_OPTION], +[AC_REQUIRE([LT_AC_PROG_SED]) +AC_CACHE_CHECK([$1], [$2], + [$2=no + ifelse([$4], , [ac_outfile=conftest.$ac_objext], [ac_outfile=$4]) + printf "$lt_simple_compile_test_code" > conftest.$ac_ext + lt_compiler_flag="$3" + # Insert the option either (1) after the last *FLAGS variable, or + # (2) before a word containing "conftest.", or (3) at the end. + # Note that $ac_compile itself does not contain backslashes and begins + # with a dollar sign (not a hyphen), so the echo should work correctly. + # The option is referenced via a variable to avoid confusing sed. + lt_compile=`echo "$ac_compile" | $SED \ + -e 's:.*FLAGS}? :&$lt_compiler_flag :; t' \ + -e 's: [[^ ]]*conftest\.: $lt_compiler_flag&:; t' \ + -e 's:$: $lt_compiler_flag:'` + (eval echo "\"\$as_me:__oline__: $lt_compile\"" >&AS_MESSAGE_LOG_FD) + (eval "$lt_compile" 2>conftest.err) + ac_status=$? + cat conftest.err >&AS_MESSAGE_LOG_FD + echo "$as_me:__oline__: \$? = $ac_status" >&AS_MESSAGE_LOG_FD + if (exit $ac_status) && test -s "$ac_outfile"; then + # The compiler can only warn and ignore the option if not recognized + # So say no if there are warnings + if test ! -s conftest.err; then + $2=yes + fi + fi + $rm conftest* +]) + +if test x"[$]$2" = xyes; then + ifelse([$5], , :, [$5]) +else + ifelse([$6], , :, [$6]) +fi +])# AC_LIBTOOL_COMPILER_OPTION + + +# AC_LIBTOOL_LINKER_OPTION(MESSAGE, VARIABLE-NAME, FLAGS, +# [ACTION-SUCCESS], [ACTION-FAILURE]) +# ------------------------------------------------------------ +# Check whether the given compiler option works +AC_DEFUN([AC_LIBTOOL_LINKER_OPTION], +[AC_CACHE_CHECK([$1], [$2], + [$2=no + save_LDFLAGS="$LDFLAGS" + LDFLAGS="$LDFLAGS $3" + printf "$lt_simple_link_test_code" > conftest.$ac_ext + if (eval $ac_link 2>conftest.err) && test -s conftest$ac_exeext; then + # The compiler can only warn and ignore the option if not recognized + # So say no if there are warnings + if test -s conftest.err; then + # Append any errors to the config.log. + cat conftest.err 1>&AS_MESSAGE_LOG_FD + else + $2=yes + fi + fi + $rm conftest* + LDFLAGS="$save_LDFLAGS" +]) + +if test x"[$]$2" = xyes; then + ifelse([$4], , :, [$4]) +else + ifelse([$5], , :, [$5]) +fi +])# AC_LIBTOOL_LINKER_OPTION + + +# AC_LIBTOOL_SYS_MAX_CMD_LEN +# -------------------------- +AC_DEFUN([AC_LIBTOOL_SYS_MAX_CMD_LEN], +[# find the maximum length of command line arguments +AC_MSG_CHECKING([the maximum length of command line arguments]) +AC_CACHE_VAL([lt_cv_sys_max_cmd_len], [dnl + i=0 + testring="ABCD" + + case $build_os in + msdosdjgpp*) + # On DJGPP, this test can blow up pretty badly due to problems in libc + # (any single argument exceeding 2000 bytes causes a buffer overrun + # during glob expansion). Even if it were fixed, the result of this + # check would be larger than it should be. + lt_cv_sys_max_cmd_len=12288; # 12K is about right + ;; + + gnu*) + # Under GNU Hurd, this test is not required because there is + # no limit to the length of command line arguments. + # Libtool will interpret -1 as no limit whatsoever + lt_cv_sys_max_cmd_len=-1; + ;; + + cygwin* | mingw*) + # On Win9x/ME, this test blows up -- it succeeds, but takes + # about 5 minutes as the teststring grows exponentially. + # Worse, since 9x/ME are not pre-emptively multitasking, + # you end up with a "frozen" computer, even though with patience + # the test eventually succeeds (with a max line length of 256k). + # Instead, let's just punt: use the minimum linelength reported by + # all of the supported platforms: 8192 (on NT/2K/XP). + lt_cv_sys_max_cmd_len=8192; + ;; + + *) + # If test is not a shell built-in, we'll probably end up computing a + # maximum length that is only half of the actual maximum length, but + # we can't tell. + while (test "X"`$CONFIG_SHELL [$]0 --fallback-echo "X$testring" 2>/dev/null` \ + = "XX$testring") >/dev/null 2>&1 && + new_result=`expr "X$testring" : ".*" 2>&1` && + lt_cv_sys_max_cmd_len=$new_result && + test $i != 17 # 1/2 MB should be enough + do + i=`expr $i + 1` + testring=$testring$testring + done + testring= + # Add a significant safety factor because C++ compilers can tack on massive + # amounts of additional arguments before passing them to the linker. + # It appears as though 1/2 is a usable value. + lt_cv_sys_max_cmd_len=`expr $lt_cv_sys_max_cmd_len \/ 2` + ;; + esac +]) +if test -n $lt_cv_sys_max_cmd_len ; then + AC_MSG_RESULT($lt_cv_sys_max_cmd_len) +else + AC_MSG_RESULT(none) +fi +])# AC_LIBTOOL_SYS_MAX_CMD_LEN + + +# _LT_AC_CHECK_DLFCN +# -------------------- +AC_DEFUN([_LT_AC_CHECK_DLFCN], +[AC_CHECK_HEADERS(dlfcn.h)dnl +])# _LT_AC_CHECK_DLFCN + + +# _LT_AC_TRY_DLOPEN_SELF (ACTION-IF-TRUE, ACTION-IF-TRUE-W-USCORE, +# ACTION-IF-FALSE, ACTION-IF-CROSS-COMPILING) +# ------------------------------------------------------------------ +AC_DEFUN([_LT_AC_TRY_DLOPEN_SELF], +[AC_REQUIRE([_LT_AC_CHECK_DLFCN])dnl +if test "$cross_compiling" = yes; then : + [$4] +else + lt_dlunknown=0; lt_dlno_uscore=1; lt_dlneed_uscore=2 + lt_status=$lt_dlunknown + cat > conftest.$ac_ext < +#endif + +#include + +#ifdef RTLD_GLOBAL +# define LT_DLGLOBAL RTLD_GLOBAL +#else +# ifdef DL_GLOBAL +# define LT_DLGLOBAL DL_GLOBAL +# else +# define LT_DLGLOBAL 0 +# endif +#endif + +/* We may have to define LT_DLLAZY_OR_NOW in the command line if we + find out it does not work in some platform. */ +#ifndef LT_DLLAZY_OR_NOW +# ifdef RTLD_LAZY +# define LT_DLLAZY_OR_NOW RTLD_LAZY +# else +# ifdef DL_LAZY +# define LT_DLLAZY_OR_NOW DL_LAZY +# else +# ifdef RTLD_NOW +# define LT_DLLAZY_OR_NOW RTLD_NOW +# else +# ifdef DL_NOW +# define LT_DLLAZY_OR_NOW DL_NOW +# else +# define LT_DLLAZY_OR_NOW 0 +# endif +# endif +# endif +# endif +#endif + +#ifdef __cplusplus +extern "C" void exit (int); +#endif + +void fnord() { int i=42;} +int main () +{ + void *self = dlopen (0, LT_DLGLOBAL|LT_DLLAZY_OR_NOW); + int status = $lt_dlunknown; + + if (self) + { + if (dlsym (self,"fnord")) status = $lt_dlno_uscore; + else if (dlsym( self,"_fnord")) status = $lt_dlneed_uscore; + /* dlclose (self); */ + } + + exit (status); +}] +EOF + if AC_TRY_EVAL(ac_link) && test -s conftest${ac_exeext} 2>/dev/null; then + (./conftest; exit; ) 2>/dev/null + lt_status=$? + case x$lt_status in + x$lt_dlno_uscore) $1 ;; + x$lt_dlneed_uscore) $2 ;; + x$lt_unknown|x*) $3 ;; + esac + else : + # compilation failed + $3 + fi +fi +rm -fr conftest* +])# _LT_AC_TRY_DLOPEN_SELF + + +# AC_LIBTOOL_DLOPEN_SELF +# ------------------- +AC_DEFUN([AC_LIBTOOL_DLOPEN_SELF], +[AC_REQUIRE([_LT_AC_CHECK_DLFCN])dnl +if test "x$enable_dlopen" != xyes; then + enable_dlopen=unknown + enable_dlopen_self=unknown + enable_dlopen_self_static=unknown +else + lt_cv_dlopen=no + lt_cv_dlopen_libs= + + case $host_os in + beos*) + lt_cv_dlopen="load_add_on" + lt_cv_dlopen_libs= + lt_cv_dlopen_self=yes + ;; + + mingw* | pw32*) + lt_cv_dlopen="LoadLibrary" + lt_cv_dlopen_libs= + ;; + + cygwin*) + lt_cv_dlopen="dlopen" + lt_cv_dlopen_libs= + ;; + + darwin*) + # if libdl is installed we need to link against it + AC_CHECK_LIB([dl], [dlopen], + [lt_cv_dlopen="dlopen" lt_cv_dlopen_libs="-ldl"],[ + lt_cv_dlopen="dyld" + lt_cv_dlopen_libs= + lt_cv_dlopen_self=yes + ]) + ;; + + *) + AC_CHECK_FUNC([shl_load], + [lt_cv_dlopen="shl_load"], + [AC_CHECK_LIB([dld], [shl_load], + [lt_cv_dlopen="shl_load" lt_cv_dlopen_libs="-dld"], + [AC_CHECK_FUNC([dlopen], + [lt_cv_dlopen="dlopen"], + [AC_CHECK_LIB([dl], [dlopen], + [lt_cv_dlopen="dlopen" lt_cv_dlopen_libs="-ldl"], + [AC_CHECK_LIB([svld], [dlopen], + [lt_cv_dlopen="dlopen" lt_cv_dlopen_libs="-lsvld"], + [AC_CHECK_LIB([dld], [dld_link], + [lt_cv_dlopen="dld_link" lt_cv_dlopen_libs="-dld"]) + ]) + ]) + ]) + ]) + ]) + ;; + esac + + if test "x$lt_cv_dlopen" != xno; then + enable_dlopen=yes + else + enable_dlopen=no + fi + + case $lt_cv_dlopen in + dlopen) + save_CPPFLAGS="$CPPFLAGS" + test "x$ac_cv_header_dlfcn_h" = xyes && CPPFLAGS="$CPPFLAGS -DHAVE_DLFCN_H" + + save_LDFLAGS="$LDFLAGS" + eval LDFLAGS=\"\$LDFLAGS $export_dynamic_flag_spec\" + + save_LIBS="$LIBS" + LIBS="$lt_cv_dlopen_libs $LIBS" + + AC_CACHE_CHECK([whether a program can dlopen itself], + lt_cv_dlopen_self, [dnl + _LT_AC_TRY_DLOPEN_SELF( + lt_cv_dlopen_self=yes, lt_cv_dlopen_self=yes, + lt_cv_dlopen_self=no, lt_cv_dlopen_self=cross) + ]) + + if test "x$lt_cv_dlopen_self" = xyes; then + LDFLAGS="$LDFLAGS $link_static_flag" + AC_CACHE_CHECK([whether a statically linked program can dlopen itself], + lt_cv_dlopen_self_static, [dnl + _LT_AC_TRY_DLOPEN_SELF( + lt_cv_dlopen_self_static=yes, lt_cv_dlopen_self_static=yes, + lt_cv_dlopen_self_static=no, lt_cv_dlopen_self_static=cross) + ]) + fi + + CPPFLAGS="$save_CPPFLAGS" + LDFLAGS="$save_LDFLAGS" + LIBS="$save_LIBS" + ;; + esac + + case $lt_cv_dlopen_self in + yes|no) enable_dlopen_self=$lt_cv_dlopen_self ;; + *) enable_dlopen_self=unknown ;; + esac + + case $lt_cv_dlopen_self_static in + yes|no) enable_dlopen_self_static=$lt_cv_dlopen_self_static ;; + *) enable_dlopen_self_static=unknown ;; + esac +fi +])# AC_LIBTOOL_DLOPEN_SELF + + +# AC_LIBTOOL_PROG_CC_C_O([TAGNAME]) +# --------------------------------- +# Check to see if options -c and -o are simultaneously supported by compiler +AC_DEFUN([AC_LIBTOOL_PROG_CC_C_O], +[AC_REQUIRE([_LT_AC_SYS_COMPILER])dnl +AC_CACHE_CHECK([if $compiler supports -c -o file.$ac_objext], + [_LT_AC_TAGVAR(lt_cv_prog_compiler_c_o, $1)], + [_LT_AC_TAGVAR(lt_cv_prog_compiler_c_o, $1)=no + $rm -r conftest 2>/dev/null + mkdir conftest + cd conftest + mkdir out + printf "$lt_simple_compile_test_code" > conftest.$ac_ext + + # According to Tom Tromey, Ian Lance Taylor reported there are C compilers + # that will create temporary files in the current directory regardless of + # the output directory. Thus, making CWD read-only will cause this test + # to fail, enabling locking or at least warning the user not to do parallel + # builds. + chmod -w . + + lt_compiler_flag="-o out/conftest2.$ac_objext" + # Insert the option either (1) after the last *FLAGS variable, or + # (2) before a word containing "conftest.", or (3) at the end. + # Note that $ac_compile itself does not contain backslashes and begins + # with a dollar sign (not a hyphen), so the echo should work correctly. + lt_compile=`echo "$ac_compile" | $SED \ + -e 's:.*FLAGS}? :&$lt_compiler_flag :; t' \ + -e 's: [[^ ]]*conftest\.: $lt_compiler_flag&:; t' \ + -e 's:$: $lt_compiler_flag:'` + (eval echo "\"\$as_me:__oline__: $lt_compile\"" >&AS_MESSAGE_LOG_FD) + (eval "$lt_compile" 2>out/conftest.err) + ac_status=$? + cat out/conftest.err >&AS_MESSAGE_LOG_FD + echo "$as_me:__oline__: \$? = $ac_status" >&AS_MESSAGE_LOG_FD + if (exit $ac_status) && test -s out/conftest2.$ac_objext + then + # The compiler can only warn and ignore the option if not recognized + # So say no if there are warnings + if test ! -s out/conftest.err; then + _LT_AC_TAGVAR(lt_cv_prog_compiler_c_o, $1)=yes + fi + fi + chmod u+w . + $rm conftest* out/* + rmdir out + cd .. + rmdir conftest + $rm conftest* +]) +])# AC_LIBTOOL_PROG_CC_C_O + + +# AC_LIBTOOL_SYS_HARD_LINK_LOCKS([TAGNAME]) +# ----------------------------------------- +# Check to see if we can do hard links to lock some files if needed +AC_DEFUN([AC_LIBTOOL_SYS_HARD_LINK_LOCKS], +[AC_REQUIRE([_LT_AC_LOCK])dnl + +hard_links="nottested" +if test "$_LT_AC_TAGVAR(lt_cv_prog_compiler_c_o, $1)" = no && test "$need_locks" != no; then + # do not overwrite the value of need_locks provided by the user + AC_MSG_CHECKING([if we can lock with hard links]) + hard_links=yes + $rm conftest* + ln conftest.a conftest.b 2>/dev/null && hard_links=no + touch conftest.a + ln conftest.a conftest.b 2>&5 || hard_links=no + ln conftest.a conftest.b 2>/dev/null && hard_links=no + AC_MSG_RESULT([$hard_links]) + if test "$hard_links" = no; then + AC_MSG_WARN([`$CC' does not support `-c -o', so `make -j' may be unsafe]) + need_locks=warn + fi +else + need_locks=no +fi +])# AC_LIBTOOL_SYS_HARD_LINK_LOCKS + + +# AC_LIBTOOL_OBJDIR +# ----------------- +AC_DEFUN([AC_LIBTOOL_OBJDIR], +[AC_CACHE_CHECK([for objdir], [lt_cv_objdir], +[rm -f .libs 2>/dev/null +mkdir .libs 2>/dev/null +if test -d .libs; then + lt_cv_objdir=.libs +else + # MS-DOS does not allow filenames that begin with a dot. + lt_cv_objdir=_libs +fi +rmdir .libs 2>/dev/null]) +objdir=$lt_cv_objdir +])# AC_LIBTOOL_OBJDIR + + +# AC_LIBTOOL_PROG_LD_HARDCODE_LIBPATH([TAGNAME]) +# ---------------------------------------------- +# Check hardcoding attributes. +AC_DEFUN([AC_LIBTOOL_PROG_LD_HARDCODE_LIBPATH], +[AC_MSG_CHECKING([how to hardcode library paths into programs]) +_LT_AC_TAGVAR(hardcode_action, $1)= +if test -n "$_LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)" || \ + test -n "$_LT_AC_TAGVAR(runpath_var $1)" || \ + test "X$_LT_AC_TAGVAR(hardcode_automatic, $1)"="Xyes" ; then + + # We can hardcode non-existant directories. + if test "$_LT_AC_TAGVAR(hardcode_direct, $1)" != no && + # If the only mechanism to avoid hardcoding is shlibpath_var, we + # have to relink, otherwise we might link with an installed library + # when we should be linking with a yet-to-be-installed one + ## test "$_LT_AC_TAGVAR(hardcode_shlibpath_var, $1)" != no && + test "$_LT_AC_TAGVAR(hardcode_minus_L, $1)" != no; then + # Linking always hardcodes the temporary library directory. + _LT_AC_TAGVAR(hardcode_action, $1)=relink + else + # We can link without hardcoding, and we can hardcode nonexisting dirs. + _LT_AC_TAGVAR(hardcode_action, $1)=immediate + fi +else + # We cannot hardcode anything, or else we can only hardcode existing + # directories. + _LT_AC_TAGVAR(hardcode_action, $1)=unsupported +fi +AC_MSG_RESULT([$_LT_AC_TAGVAR(hardcode_action, $1)]) + +if test "$_LT_AC_TAGVAR(hardcode_action, $1)" = relink; then + # Fast installation is not supported + enable_fast_install=no +elif test "$shlibpath_overrides_runpath" = yes || + test "$enable_shared" = no; then + # Fast installation is not necessary + enable_fast_install=needless +fi +])# AC_LIBTOOL_PROG_LD_HARDCODE_LIBPATH + + +# AC_LIBTOOL_SYS_LIB_STRIP +# ------------------------ +AC_DEFUN([AC_LIBTOOL_SYS_LIB_STRIP], +[striplib= +old_striplib= +AC_MSG_CHECKING([whether stripping libraries is possible]) +if test -n "$STRIP" && $STRIP -V 2>&1 | grep "GNU strip" >/dev/null; then + test -z "$old_striplib" && old_striplib="$STRIP --strip-debug" + test -z "$striplib" && striplib="$STRIP --strip-unneeded" + AC_MSG_RESULT([yes]) +else +# FIXME - insert some real tests, host_os isn't really good enough + case $host_os in + darwin*) + if test -n "$STRIP" ; then + striplib="$STRIP -x" + AC_MSG_RESULT([yes]) + else + AC_MSG_RESULT([no]) +fi + ;; + *) + AC_MSG_RESULT([no]) + ;; + esac +fi +])# AC_LIBTOOL_SYS_LIB_STRIP + + +# AC_LIBTOOL_SYS_DYNAMIC_LINKER +# ----------------------------- +# PORTME Fill in your ld.so characteristics +AC_DEFUN([AC_LIBTOOL_SYS_DYNAMIC_LINKER], +[AC_MSG_CHECKING([dynamic linker characteristics]) +library_names_spec= +libname_spec='lib$name' +soname_spec= +shrext=".so" +postinstall_cmds= +postuninstall_cmds= +finish_cmds= +finish_eval= +shlibpath_var= +shlibpath_overrides_runpath=unknown +version_type=none +dynamic_linker="$host_os ld.so" +sys_lib_dlsearch_path_spec="/lib /usr/lib" +sys_lib_search_path_spec="/lib /usr/lib /usr/local/lib" +need_lib_prefix=unknown +hardcode_into_libs=no + +# when you set need_version to no, make sure it does not cause -set_version +# flags to be left without arguments +need_version=unknown + +case $host_os in +aix3*) + version_type=linux + library_names_spec='${libname}${release}${shared_ext}$versuffix $libname.a' + shlibpath_var=LIBPATH + + # AIX 3 has no versioning support, so we append a major version to the name. + soname_spec='${libname}${release}${shared_ext}$major' + ;; + +aix4* | aix5*) + version_type=linux + need_lib_prefix=no + need_version=no + hardcode_into_libs=yes + if test "$host_cpu" = ia64; then + # AIX 5 supports IA64 + library_names_spec='${libname}${release}${shared_ext}$major ${libname}${release}${shared_ext}$versuffix $libname${shared_ext}' + shlibpath_var=LD_LIBRARY_PATH + else + # With GCC up to 2.95.x, collect2 would create an import file + # for dependence libraries. The import file would start with + # the line `#! .'. This would cause the generated library to + # depend on `.', always an invalid library. This was fixed in + # development snapshots of GCC prior to 3.0. + case $host_os in + aix4 | aix4.[[01]] | aix4.[[01]].*) + if { echo '#if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 97)' + echo ' yes ' + echo '#endif'; } | ${CC} -E - | grep yes > /dev/null; then + : + else + can_build_shared=no + fi + ;; + esac + # AIX (on Power*) has no versioning support, so currently we can not hardcode correct + # soname into executable. Probably we can add versioning support to + # collect2, so additional links can be useful in future. + if test "$aix_use_runtimelinking" = yes; then + # If using run time linking (on AIX 4.2 or later) use lib.so + # instead of lib.a to let people know that these are not + # typical AIX shared libraries. + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}' + else + # We preserve .a as extension for shared libraries through AIX4.2 + # and later when we are not doing run time linking. + library_names_spec='${libname}${release}.a $libname.a' + soname_spec='${libname}${release}${shared_ext}$major' + fi + shlibpath_var=LIBPATH + fi + ;; + +amigaos*) + library_names_spec='$libname.ixlibrary $libname.a' + # Create ${libname}_ixlibrary.a entries in /sys/libs. + finish_eval='for lib in `ls $libdir/*.ixlibrary 2>/dev/null`; do libname=`$echo "X$lib" | $Xsed -e '\''s%^.*/\([[^/]]*\)\.ixlibrary$%\1%'\''`; test $rm /sys/libs/${libname}_ixlibrary.a; $show "(cd /sys/libs && $LN_S $lib ${libname}_ixlibrary.a)"; (cd /sys/libs && $LN_S $lib ${libname}_ixlibrary.a) || exit 1; done' + ;; + +beos*) + library_names_spec='${libname}${shared_ext}' + dynamic_linker="$host_os ld.so" + shlibpath_var=LIBRARY_PATH + ;; + +bsdi4*) + version_type=linux + need_version=no + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + finish_cmds='PATH="\$PATH:/sbin" ldconfig $libdir' + shlibpath_var=LD_LIBRARY_PATH + sys_lib_search_path_spec="/shlib /usr/lib /usr/X11/lib /usr/contrib/lib /lib /usr/local/lib" + sys_lib_dlsearch_path_spec="/shlib /usr/lib /usr/local/lib" + # the default ld.so.conf also contains /usr/contrib/lib and + # /usr/X11R6/lib (/usr/X11 is a link to /usr/X11R6), but let us allow + # libtool to hard-code these into programs + ;; + +cygwin* | mingw* | pw32*) + version_type=windows + shrext=".dll" + need_version=no + need_lib_prefix=no + + case $GCC,$host_os in + yes,cygwin* | yes,mingw* | yes,pw32*) + library_names_spec='$libname.dll.a' + # DLL is installed to $(libdir)/../bin by postinstall_cmds + postinstall_cmds='base_file=`basename \${file}`~ + dlpath=`$SHELL 2>&1 -c '\''. $dir/'\''\${base_file}'\''i;echo \$dlname'\''`~ + dldir=$destdir/`dirname \$dlpath`~ + test -d \$dldir || mkdir -p \$dldir~ + $install_prog $dir/$dlname \$dldir/$dlname' + postuninstall_cmds='dldll=`$SHELL 2>&1 -c '\''. $file; echo \$dlname'\''`~ + dlpath=$dir/\$dldll~ + $rm \$dlpath' + shlibpath_overrides_runpath=yes + + case $host_os in + cygwin*) + # Cygwin DLLs use 'cyg' prefix rather than 'lib' + soname_spec='`echo ${libname} | sed -e 's/^lib/cyg/'``echo ${release} | $SED -e 's/[[.]]/-/g'`${versuffix}${shared_ext}' + sys_lib_search_path_spec="/usr/lib /lib/w32api /lib /usr/local/lib" + ;; + mingw*) + # MinGW DLLs use traditional 'lib' prefix + soname_spec='${libname}`echo ${release} | $SED -e 's/[[.]]/-/g'`${versuffix}${shared_ext}' + sys_lib_search_path_spec=`$CC -print-search-dirs | grep "^libraries:" | $SED -e "s/^libraries://" -e "s,=/,/,g"` + if echo "$sys_lib_search_path_spec" | [grep ';[c-zC-Z]:/' >/dev/null]; then + # It is most probably a Windows format PATH printed by + # mingw gcc, but we are running on Cygwin. Gcc prints its search + # path with ; separators, and with drive letters. We can handle the + # drive letters (cygwin fileutils understands them), so leave them, + # especially as we might pass files found there to a mingw objdump, + # which wouldn't understand a cygwinified path. Ahh. + sys_lib_search_path_spec=`echo "$sys_lib_search_path_spec" | $SED -e 's/;/ /g'` + else + sys_lib_search_path_spec=`echo "$sys_lib_search_path_spec" | $SED -e "s/$PATH_SEPARATOR/ /g"` + fi + ;; + pw32*) + # pw32 DLLs use 'pw' prefix rather than 'lib' + library_names_spec='`echo ${libname} | sed -e 's/^lib/pw/'``echo ${release} | $SED -e 's/[.]/-/g'`${versuffix}${shared_ext}' + ;; + esac + ;; + + *) + library_names_spec='${libname}`echo ${release} | $SED -e 's/[[.]]/-/g'`${versuffix}${shared_ext} $libname.lib' + ;; + esac + dynamic_linker='Win32 ld.exe' + # FIXME: first we should search . and the directory the executable is in + shlibpath_var=PATH + ;; + +darwin* | rhapsody*) + dynamic_linker="$host_os dyld" + version_type=darwin + need_lib_prefix=no + need_version=no + library_names_spec='${libname}${release}${versuffix}$shared_ext ${libname}${release}${major}$shared_ext ${libname}$shared_ext' + soname_spec='${libname}${release}${major}$shared_ext' + shlibpath_overrides_runpath=yes + shlibpath_var=DYLD_LIBRARY_PATH + shrext='$(test .$module = .yes && echo .so || echo .dylib)' + # Apple's gcc prints 'gcc -print-search-dirs' doesn't operate the same. + if test "$GCC" = yes; then + sys_lib_search_path_spec=`$CC -print-search-dirs | tr "\n" "$PATH_SEPARATOR" | sed -e 's/libraries:/@libraries:/' | tr "@" "\n" | grep "^libraries:" | sed -e "s/^libraries://" -e "s,=/,/,g" -e "s,$PATH_SEPARATOR, ,g" -e "s,.*,& /lib /usr/lib /usr/local/lib,g"` + else + sys_lib_search_path_spec='/lib /usr/lib /usr/local/lib' + fi + sys_lib_dlsearch_path_spec='/usr/local/lib /lib /usr/lib' + ;; + +dgux*) + version_type=linux + need_lib_prefix=no + need_version=no + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname$shared_ext' + soname_spec='${libname}${release}${shared_ext}$major' + shlibpath_var=LD_LIBRARY_PATH + ;; + +freebsd1*) + dynamic_linker=no + ;; + +kfreebsd*-gnu*) + version_type=linux + need_lib_prefix=no + need_version=no + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major ${libname}${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=no + hardcode_into_libs=yes + dynamic_linker='GNU ld.so' + ;; + +freebsd*) + objformat=`test -x /usr/bin/objformat && /usr/bin/objformat || echo aout` + version_type=freebsd-$objformat + case $version_type in + freebsd-elf*) + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext} $libname${shared_ext}' + need_version=no + need_lib_prefix=no + ;; + freebsd-*) + library_names_spec='${libname}${release}${shared_ext}$versuffix $libname${shared_ext}$versuffix' + need_version=yes + ;; + esac + shlibpath_var=LD_LIBRARY_PATH + case $host_os in + freebsd2*) + shlibpath_overrides_runpath=yes + ;; + freebsd3.[01]* | freebsdelf3.[01]*) + shlibpath_overrides_runpath=yes + hardcode_into_libs=yes + ;; + *) # from 3.2 on + shlibpath_overrides_runpath=no + hardcode_into_libs=yes + ;; + esac + ;; + +gnu*) + version_type=linux + need_lib_prefix=no + need_version=no + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}${major} ${libname}${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + shlibpath_var=LD_LIBRARY_PATH + hardcode_into_libs=yes + ;; + +hpux9* | hpux10* | hpux11*) + # Give a soname corresponding to the major version so that dld.sl refuses to + # link against other versions. + version_type=sunos + need_lib_prefix=no + need_version=no + case "$host_cpu" in + ia64*) + shrext='.so' + hardcode_into_libs=yes + dynamic_linker="$host_os dld.so" + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes # Unless +noenvvar is specified. + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + if test "X$HPUX_IA64_MODE" = X32; then + sys_lib_search_path_spec="/usr/lib/hpux32 /usr/local/lib/hpux32 /usr/local/lib" + else + sys_lib_search_path_spec="/usr/lib/hpux64 /usr/local/lib/hpux64" + fi + sys_lib_dlsearch_path_spec=$sys_lib_search_path_spec + ;; + hppa*64*) + shrext='.sl' + hardcode_into_libs=yes + dynamic_linker="$host_os dld.sl" + shlibpath_var=LD_LIBRARY_PATH # How should we handle SHLIB_PATH + shlibpath_overrides_runpath=yes # Unless +noenvvar is specified. + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + sys_lib_search_path_spec="/usr/lib/pa20_64 /usr/ccs/lib/pa20_64" + sys_lib_dlsearch_path_spec=$sys_lib_search_path_spec + ;; + *) + shrext='.sl' + dynamic_linker="$host_os dld.sl" + shlibpath_var=SHLIB_PATH + shlibpath_overrides_runpath=no # +s is required to enable SHLIB_PATH + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + ;; + esac + # HP-UX runs *really* slowly unless shared libraries are mode 555. + postinstall_cmds='chmod 555 $lib' + ;; + +irix5* | irix6* | nonstopux*) + case $host_os in + nonstopux*) version_type=nonstopux ;; + *) + if test "$lt_cv_prog_gnu_ld" = yes; then + version_type=linux + else + version_type=irix + fi ;; + esac + need_lib_prefix=no + need_version=no + soname_spec='${libname}${release}${shared_ext}$major' + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major ${libname}${release}${shared_ext} $libname${shared_ext}' + case $host_os in + irix5* | nonstopux*) + libsuff= shlibsuff= + ;; + *) + case $LD in # libtool.m4 will add one of these switches to LD + *-32|*"-32 "|*-melf32bsmip|*"-melf32bsmip ") + libsuff= shlibsuff= libmagic=32-bit;; + *-n32|*"-n32 "|*-melf32bmipn32|*"-melf32bmipn32 ") + libsuff=32 shlibsuff=N32 libmagic=N32;; + *-64|*"-64 "|*-melf64bmip|*"-melf64bmip ") + libsuff=64 shlibsuff=64 libmagic=64-bit;; + *) libsuff= shlibsuff= libmagic=never-match;; + esac + ;; + esac + shlibpath_var=LD_LIBRARY${shlibsuff}_PATH + shlibpath_overrides_runpath=no + sys_lib_search_path_spec="/usr/lib${libsuff} /lib${libsuff} /usr/local/lib${libsuff}" + sys_lib_dlsearch_path_spec="/usr/lib${libsuff} /lib${libsuff}" + hardcode_into_libs=yes + ;; + +# No shared lib support for Linux oldld, aout, or coff. +linux*oldld* | linux*aout* | linux*coff*) + dynamic_linker=no + ;; + +# This must be Linux ELF. +linux*) + version_type=linux + need_lib_prefix=no + need_version=no + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + finish_cmds='PATH="\$PATH:/sbin" ldconfig -n $libdir' + libsuff= + if test "x$LINUX_64_MODE" = x64; then + # Some platforms are per default 64-bit, so there's no /lib64 + if test -d /lib64 -a ! -h /lib64; then + libsuff=64 + fi + fi + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=no + sys_lib_dlsearch_path_spec="/lib${libsuff} /usr/lib${libsuff}" + sys_lib_search_path_spec="/lib${libsuff} /usr/lib${libsuff} /usr/local/lib${libsuff}" + # This implies no fast_install, which is unacceptable. + # Some rework will be needed to allow for fast_install + # before this can be enabled. + hardcode_into_libs=yes + + # We used to test for /lib/ld.so.1 and disable shared libraries on + # powerpc, because MkLinux only supported shared libraries with the + # GNU dynamic linker. Since this was broken with cross compilers, + # most powerpc-linux boxes support dynamic linking these days and + # people can always --disable-shared, the test was removed, and we + # assume the GNU/Linux dynamic linker is in use. + dynamic_linker='GNU/Linux ld.so' + ;; + +netbsd*) + version_type=sunos + need_lib_prefix=no + need_version=no + if echo __ELF__ | $CC -E - | grep __ELF__ >/dev/null; then + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${shared_ext}$versuffix' + finish_cmds='PATH="\$PATH:/sbin" ldconfig -m $libdir' + dynamic_linker='NetBSD (a.out) ld.so' + else + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major ${libname}${release}${shared_ext} ${libname}${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + dynamic_linker='NetBSD ld.elf_so' + fi + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes + hardcode_into_libs=yes + ;; + +newsos6) + version_type=linux + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes + ;; + +nto-qnx*) + version_type=linux + need_lib_prefix=no + need_version=no + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes + ;; + +openbsd*) + version_type=sunos + need_lib_prefix=no + need_version=no + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${shared_ext}$versuffix' + finish_cmds='PATH="\$PATH:/sbin" ldconfig -m $libdir' + shlibpath_var=LD_LIBRARY_PATH + if test -z "`echo __ELF__ | $CC -E - | grep __ELF__`" || test "$host_os-$host_cpu" = "openbsd2.8-powerpc"; then + case $host_os in + openbsd2.[[89]] | openbsd2.[[89]].*) + shlibpath_overrides_runpath=no + ;; + *) + shlibpath_overrides_runpath=yes + ;; + esac + else + shlibpath_overrides_runpath=yes + fi + ;; + +os2*) + libname_spec='$name' + shrext=".dll" + need_lib_prefix=no + library_names_spec='$libname${shared_ext} $libname.a' + dynamic_linker='OS/2 ld.exe' + shlibpath_var=LIBPATH + ;; + +osf3* | osf4* | osf5*) + version_type=osf + need_lib_prefix=no + need_version=no + soname_spec='${libname}${release}${shared_ext}$major' + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}' + shlibpath_var=LD_LIBRARY_PATH + sys_lib_search_path_spec="/usr/shlib /usr/ccs/lib /usr/lib/cmplrs/cc /usr/lib /usr/local/lib /var/shlib" + sys_lib_dlsearch_path_spec="$sys_lib_search_path_spec" + ;; + +sco3.2v5*) + version_type=osf + soname_spec='${libname}${release}${shared_ext}$major' + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}' + shlibpath_var=LD_LIBRARY_PATH + ;; + +solaris*) + version_type=linux + need_lib_prefix=no + need_version=no + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes + hardcode_into_libs=yes + # ldd complains unless libraries are executable + postinstall_cmds='chmod +x $lib' + ;; + +sunos4*) + version_type=sunos + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${shared_ext}$versuffix' + finish_cmds='PATH="\$PATH:/usr/etc" ldconfig $libdir' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes + if test "$with_gnu_ld" = yes; then + need_lib_prefix=no + fi + need_version=yes + ;; + +sysv4 | sysv4.2uw2* | sysv4.3* | sysv5*) + version_type=linux + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + shlibpath_var=LD_LIBRARY_PATH + case $host_vendor in + sni) + shlibpath_overrides_runpath=no + need_lib_prefix=no + export_dynamic_flag_spec='${wl}-Blargedynsym' + runpath_var=LD_RUN_PATH + ;; + siemens) + need_lib_prefix=no + ;; + motorola) + need_lib_prefix=no + need_version=no + shlibpath_overrides_runpath=no + sys_lib_search_path_spec='/lib /usr/lib /usr/ccs/lib' + ;; + esac + ;; + +sysv4*MP*) + if test -d /usr/nec ;then + version_type=linux + library_names_spec='$libname${shared_ext}.$versuffix $libname${shared_ext}.$major $libname${shared_ext}' + soname_spec='$libname${shared_ext}.$major' + shlibpath_var=LD_LIBRARY_PATH + fi + ;; + +uts4*) + version_type=linux + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + shlibpath_var=LD_LIBRARY_PATH + ;; + +*) + dynamic_linker=no + ;; +esac +AC_MSG_RESULT([$dynamic_linker]) +test "$dynamic_linker" = no && can_build_shared=no +])# AC_LIBTOOL_SYS_DYNAMIC_LINKER + + +# _LT_AC_TAGCONFIG +# ---------------- +AC_DEFUN([_LT_AC_TAGCONFIG], +[AC_ARG_WITH([tags], + [AC_HELP_STRING([--with-tags@<:@=TAGS@:>@], + [include additional configurations @<:@automatic@:>@])], + [tagnames="$withval"]) + +if test -f "$ltmain" && test -n "$tagnames"; then + if test ! -f "${ofile}"; then + AC_MSG_WARN([output file `$ofile' does not exist]) + fi + + if test -z "$LTCC"; then + eval "`$SHELL ${ofile} --config | grep '^LTCC='`" + if test -z "$LTCC"; then + AC_MSG_WARN([output file `$ofile' does not look like a libtool script]) + else + AC_MSG_WARN([using `LTCC=$LTCC', extracted from `$ofile']) + fi + fi + + # Extract list of available tagged configurations in $ofile. + # Note that this assumes the entire list is on one line. + available_tags=`grep "^available_tags=" "${ofile}" | $SED -e 's/available_tags=\(.*$\)/\1/' -e 's/\"//g'` + + lt_save_ifs="$IFS"; IFS="${IFS}$PATH_SEPARATOR," + for tagname in $tagnames; do + IFS="$lt_save_ifs" + # Check whether tagname contains only valid characters + case `$echo "X$tagname" | $Xsed -e 's:[[-_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890,/]]::g'` in + "") ;; + *) AC_MSG_ERROR([invalid tag name: $tagname]) + ;; + esac + + if grep "^# ### BEGIN LIBTOOL TAG CONFIG: $tagname$" < "${ofile}" > /dev/null + then + AC_MSG_ERROR([tag name \"$tagname\" already exists]) + fi + + # Update the list of available tags. + if test -n "$tagname"; then + echo appending configuration tag \"$tagname\" to $ofile + + case $tagname in + CXX) + if test -n "$CXX" && test "X$CXX" != "Xno"; then + AC_LIBTOOL_LANG_CXX_CONFIG + else + tagname="" + fi + ;; + + F77) + if test -n "$F77" && test "X$F77" != "Xno"; then + AC_LIBTOOL_LANG_F77_CONFIG + else + tagname="" + fi + ;; + + GCJ) + if test -n "$GCJ" && test "X$GCJ" != "Xno"; then + AC_LIBTOOL_LANG_GCJ_CONFIG + else + tagname="" + fi + ;; + + RC) + AC_LIBTOOL_LANG_RC_CONFIG + ;; + + *) + AC_MSG_ERROR([Unsupported tag name: $tagname]) + ;; + esac + + # Append the new tag name to the list of available tags. + if test -n "$tagname" ; then + available_tags="$available_tags $tagname" + fi + fi + done + IFS="$lt_save_ifs" + + # Now substitute the updated list of available tags. + if eval "sed -e 's/^available_tags=.*\$/available_tags=\"$available_tags\"/' \"$ofile\" > \"${ofile}T\""; then + mv "${ofile}T" "$ofile" + chmod +x "$ofile" + else + rm -f "${ofile}T" + AC_MSG_ERROR([unable to update list of available tagged configurations.]) + fi +fi +])# _LT_AC_TAGCONFIG + + +# AC_LIBTOOL_DLOPEN +# ----------------- +# enable checks for dlopen support +AC_DEFUN([AC_LIBTOOL_DLOPEN], + [AC_BEFORE([$0],[AC_LIBTOOL_SETUP]) +])# AC_LIBTOOL_DLOPEN + + +# AC_LIBTOOL_WIN32_DLL +# -------------------- +# declare package support for building win32 dll's +AC_DEFUN([AC_LIBTOOL_WIN32_DLL], +[AC_BEFORE([$0], [AC_LIBTOOL_SETUP]) +])# AC_LIBTOOL_WIN32_DLL + + +# AC_ENABLE_SHARED([DEFAULT]) +# --------------------------- +# implement the --enable-shared flag +# DEFAULT is either `yes' or `no'. If omitted, it defaults to `yes'. +AC_DEFUN([AC_ENABLE_SHARED], +[define([AC_ENABLE_SHARED_DEFAULT], ifelse($1, no, no, yes))dnl +AC_ARG_ENABLE([shared], + [AC_HELP_STRING([--enable-shared@<:@=PKGS@:>@], + [build shared libraries @<:@default=]AC_ENABLE_SHARED_DEFAULT[@:>@])], + [p=${PACKAGE-default} + case $enableval in + yes) enable_shared=yes ;; + no) enable_shared=no ;; + *) + enable_shared=no + # Look at the argument we got. We use all the common list separators. + lt_save_ifs="$IFS"; IFS="${IFS}$PATH_SEPARATOR," + for pkg in $enableval; do + IFS="$lt_save_ifs" + if test "X$pkg" = "X$p"; then + enable_shared=yes + fi + done + IFS="$lt_save_ifs" + ;; + esac], + [enable_shared=]AC_ENABLE_SHARED_DEFAULT) +])# AC_ENABLE_SHARED + + +# AC_DISABLE_SHARED +# ----------------- +#- set the default shared flag to --disable-shared +AC_DEFUN([AC_DISABLE_SHARED], +[AC_BEFORE([$0],[AC_LIBTOOL_SETUP])dnl +AC_ENABLE_SHARED(no) +])# AC_DISABLE_SHARED + + +# AC_ENABLE_STATIC([DEFAULT]) +# --------------------------- +# implement the --enable-static flag +# DEFAULT is either `yes' or `no'. If omitted, it defaults to `yes'. +AC_DEFUN([AC_ENABLE_STATIC], +[define([AC_ENABLE_STATIC_DEFAULT], ifelse($1, no, no, yes))dnl +AC_ARG_ENABLE([static], + [AC_HELP_STRING([--enable-static@<:@=PKGS@:>@], + [build static libraries @<:@default=]AC_ENABLE_STATIC_DEFAULT[@:>@])], + [p=${PACKAGE-default} + case $enableval in + yes) enable_static=yes ;; + no) enable_static=no ;; + *) + enable_static=no + # Look at the argument we got. We use all the common list separators. + lt_save_ifs="$IFS"; IFS="${IFS}$PATH_SEPARATOR," + for pkg in $enableval; do + IFS="$lt_save_ifs" + if test "X$pkg" = "X$p"; then + enable_static=yes + fi + done + IFS="$lt_save_ifs" + ;; + esac], + [enable_static=]AC_ENABLE_STATIC_DEFAULT) +])# AC_ENABLE_STATIC + + +# AC_DISABLE_STATIC +# ----------------- +# set the default static flag to --disable-static +AC_DEFUN([AC_DISABLE_STATIC], +[AC_BEFORE([$0],[AC_LIBTOOL_SETUP])dnl +AC_ENABLE_STATIC(no) +])# AC_DISABLE_STATIC + + +# AC_ENABLE_FAST_INSTALL([DEFAULT]) +# --------------------------------- +# implement the --enable-fast-install flag +# DEFAULT is either `yes' or `no'. If omitted, it defaults to `yes'. +AC_DEFUN([AC_ENABLE_FAST_INSTALL], +[define([AC_ENABLE_FAST_INSTALL_DEFAULT], ifelse($1, no, no, yes))dnl +AC_ARG_ENABLE([fast-install], + [AC_HELP_STRING([--enable-fast-install@<:@=PKGS@:>@], + [optimize for fast installation @<:@default=]AC_ENABLE_FAST_INSTALL_DEFAULT[@:>@])], + [p=${PACKAGE-default} + case $enableval in + yes) enable_fast_install=yes ;; + no) enable_fast_install=no ;; + *) + enable_fast_install=no + # Look at the argument we got. We use all the common list separators. + lt_save_ifs="$IFS"; IFS="${IFS}$PATH_SEPARATOR," + for pkg in $enableval; do + IFS="$lt_save_ifs" + if test "X$pkg" = "X$p"; then + enable_fast_install=yes + fi + done + IFS="$lt_save_ifs" + ;; + esac], + [enable_fast_install=]AC_ENABLE_FAST_INSTALL_DEFAULT) +])# AC_ENABLE_FAST_INSTALL + + +# AC_DISABLE_FAST_INSTALL +# ----------------------- +# set the default to --disable-fast-install +AC_DEFUN([AC_DISABLE_FAST_INSTALL], +[AC_BEFORE([$0],[AC_LIBTOOL_SETUP])dnl +AC_ENABLE_FAST_INSTALL(no) +])# AC_DISABLE_FAST_INSTALL + + +# AC_LIBTOOL_PICMODE([MODE]) +# -------------------------- +# implement the --with-pic flag +# MODE is either `yes' or `no'. If omitted, it defaults to `both'. +AC_DEFUN([AC_LIBTOOL_PICMODE], +[AC_BEFORE([$0],[AC_LIBTOOL_SETUP])dnl +pic_mode=ifelse($#,1,$1,default) +])# AC_LIBTOOL_PICMODE + + +# AC_PROG_EGREP +# ------------- +# This is predefined starting with Autoconf 2.54, so this conditional +# definition can be removed once we require Autoconf 2.54 or later. +m4_ifndef([AC_PROG_EGREP], [AC_DEFUN([AC_PROG_EGREP], +[AC_CACHE_CHECK([for egrep], [ac_cv_prog_egrep], + [if echo a | (grep -E '(a|b)') >/dev/null 2>&1 + then ac_cv_prog_egrep='grep -E' + else ac_cv_prog_egrep='egrep' + fi]) + EGREP=$ac_cv_prog_egrep + AC_SUBST([EGREP]) +])]) + + +# AC_PATH_TOOL_PREFIX +# ------------------- +# find a file program which can recognise shared library +AC_DEFUN([AC_PATH_TOOL_PREFIX], +[AC_REQUIRE([AC_PROG_EGREP])dnl +AC_MSG_CHECKING([for $1]) +AC_CACHE_VAL(lt_cv_path_MAGIC_CMD, +[case $MAGIC_CMD in +[[\\/*] | ?:[\\/]*]) + lt_cv_path_MAGIC_CMD="$MAGIC_CMD" # Let the user override the test with a path. + ;; +*) + lt_save_MAGIC_CMD="$MAGIC_CMD" + lt_save_ifs="$IFS"; IFS=$PATH_SEPARATOR +dnl $ac_dummy forces splitting on constant user-supplied paths. +dnl POSIX.2 word splitting is done only on the output of word expansions, +dnl not every word. This closes a longstanding sh security hole. + ac_dummy="ifelse([$2], , $PATH, [$2])" + for ac_dir in $ac_dummy; do + IFS="$lt_save_ifs" + test -z "$ac_dir" && ac_dir=. + if test -f $ac_dir/$1; then + lt_cv_path_MAGIC_CMD="$ac_dir/$1" + if test -n "$file_magic_test_file"; then + case $deplibs_check_method in + "file_magic "*) + file_magic_regex="`expr \"$deplibs_check_method\" : \"file_magic \(.*\)\"`" + MAGIC_CMD="$lt_cv_path_MAGIC_CMD" + if eval $file_magic_cmd \$file_magic_test_file 2> /dev/null | + $EGREP "$file_magic_regex" > /dev/null; then + : + else + cat <&2 + +*** Warning: the command libtool uses to detect shared libraries, +*** $file_magic_cmd, produces output that libtool cannot recognize. +*** The result is that libtool may fail to recognize shared libraries +*** as such. This will affect the creation of libtool libraries that +*** depend on shared libraries, but programs linked with such libtool +*** libraries will work regardless of this problem. Nevertheless, you +*** may want to report the problem to your system manager and/or to +*** bug-libtool@gnu.org + +EOF + fi ;; + esac + fi + break + fi + done + IFS="$lt_save_ifs" + MAGIC_CMD="$lt_save_MAGIC_CMD" + ;; +esac]) +MAGIC_CMD="$lt_cv_path_MAGIC_CMD" +if test -n "$MAGIC_CMD"; then + AC_MSG_RESULT($MAGIC_CMD) +else + AC_MSG_RESULT(no) +fi +])# AC_PATH_TOOL_PREFIX + + +# AC_PATH_MAGIC +# ------------- +# find a file program which can recognise a shared library +AC_DEFUN([AC_PATH_MAGIC], +[AC_PATH_TOOL_PREFIX(${ac_tool_prefix}file, /usr/bin$PATH_SEPARATOR$PATH) +if test -z "$lt_cv_path_MAGIC_CMD"; then + if test -n "$ac_tool_prefix"; then + AC_PATH_TOOL_PREFIX(file, /usr/bin$PATH_SEPARATOR$PATH) + else + MAGIC_CMD=: + fi +fi +])# AC_PATH_MAGIC + + +# AC_PROG_LD +# ---------- +# find the pathname to the GNU or non-GNU linker +AC_DEFUN([AC_PROG_LD], +[AC_ARG_WITH([gnu-ld], + [AC_HELP_STRING([--with-gnu-ld], + [assume the C compiler uses GNU ld @<:@default=no@:>@])], + [test "$withval" = no || with_gnu_ld=yes], + [with_gnu_ld=no]) +AC_REQUIRE([LT_AC_PROG_SED])dnl +AC_REQUIRE([AC_PROG_CC])dnl +AC_REQUIRE([AC_CANONICAL_HOST])dnl +AC_REQUIRE([AC_CANONICAL_BUILD])dnl +ac_prog=ld +if test "$GCC" = yes; then + # Check if gcc -print-prog-name=ld gives a path. + AC_MSG_CHECKING([for ld used by $CC]) + case $host in + *-*-mingw*) + # gcc leaves a trailing carriage return which upsets mingw + ac_prog=`($CC -print-prog-name=ld) 2>&5 | tr -d '\015'` ;; + *) + ac_prog=`($CC -print-prog-name=ld) 2>&5` ;; + esac + case $ac_prog in + # Accept absolute paths. + [[\\/]]* | ?:[[\\/]]*) + re_direlt='/[[^/]][[^/]]*/\.\./' + # Canonicalize the pathname of ld + ac_prog=`echo $ac_prog| $SED 's%\\\\%/%g'` + while echo $ac_prog | grep "$re_direlt" > /dev/null 2>&1; do + ac_prog=`echo $ac_prog| $SED "s%$re_direlt%/%"` + done + test -z "$LD" && LD="$ac_prog" + ;; + "") + # If it fails, then pretend we aren't using GCC. + ac_prog=ld + ;; + *) + # If it is relative, then search for the first ld in PATH. + with_gnu_ld=unknown + ;; + esac +elif test "$with_gnu_ld" = yes; then + AC_MSG_CHECKING([for GNU ld]) +else + AC_MSG_CHECKING([for non-GNU ld]) +fi +AC_CACHE_VAL(lt_cv_path_LD, +[if test -z "$LD"; then + lt_save_ifs="$IFS"; IFS=$PATH_SEPARATOR + for ac_dir in $PATH; do + IFS="$lt_save_ifs" + test -z "$ac_dir" && ac_dir=. + if test -f "$ac_dir/$ac_prog" || test -f "$ac_dir/$ac_prog$ac_exeext"; then + lt_cv_path_LD="$ac_dir/$ac_prog" + # Check to see if the program is GNU ld. I'd rather use --version, + # but apparently some GNU ld's only accept -v. + # Break only if it was the GNU/non-GNU ld that we prefer. + case `"$lt_cv_path_LD" -v 2>&1 &1 /dev/null; then + case $host_cpu in + i*86 ) + # Not sure whether the presence of OpenBSD here was a mistake. + # Let's accept both of them until this is cleared up. + lt_cv_deplibs_check_method='file_magic (FreeBSD|OpenBSD)/i[[3-9]]86 (compact )?demand paged shared library' + lt_cv_file_magic_cmd=/usr/bin/file + lt_cv_file_magic_test_file=`echo /usr/lib/libc.so.*` + ;; + esac + else + lt_cv_deplibs_check_method=pass_all + fi + ;; + +gnu*) + lt_cv_deplibs_check_method=pass_all + ;; + +hpux10.20* | hpux11*) + lt_cv_file_magic_cmd=/usr/bin/file + case "$host_cpu" in + ia64*) + lt_cv_deplibs_check_method='file_magic (s[[0-9]][[0-9]][[0-9]]|ELF-[[0-9]][[0-9]]) shared object file - IA64' + lt_cv_file_magic_test_file=/usr/lib/hpux32/libc.so + ;; + hppa*64*) + [lt_cv_deplibs_check_method='file_magic (s[0-9][0-9][0-9]|ELF-[0-9][0-9]) shared object file - PA-RISC [0-9].[0-9]'] + lt_cv_file_magic_test_file=/usr/lib/pa20_64/libc.sl + ;; + *) + lt_cv_deplibs_check_method='file_magic (s[[0-9]][[0-9]][[0-9]]|PA-RISC[[0-9]].[[0-9]]) shared library' + lt_cv_file_magic_test_file=/usr/lib/libc.sl + ;; + esac + ;; + +irix5* | irix6* | nonstopux*) + case $host_os in + irix5* | nonstopux*) + # this will be overridden with pass_all, but let us keep it just in case + lt_cv_deplibs_check_method="file_magic ELF 32-bit MSB dynamic lib MIPS - version 1" + ;; + *) + case $LD in + *-32|*"-32 ") libmagic=32-bit;; + *-n32|*"-n32 ") libmagic=N32;; + *-64|*"-64 ") libmagic=64-bit;; + *) libmagic=never-match;; + esac + # this will be overridden with pass_all, but let us keep it just in case + lt_cv_deplibs_check_method="file_magic ELF ${libmagic} MSB mips-[[1234]] dynamic lib MIPS - version 1" + ;; + esac + lt_cv_file_magic_test_file=`echo /lib${libsuff}/libc.so*` + lt_cv_deplibs_check_method=pass_all + ;; + +# This must be Linux ELF. +linux*) + case $host_cpu in + alpha* | hppa* | i*86 | ia64* | m68* | mips* | powerpc* | sparc* | s390* | sh* | x86_64* ) + lt_cv_deplibs_check_method=pass_all ;; + # the debian people say, arm and glibc 2.3.1 works for them with pass_all + arm* ) + lt_cv_deplibs_check_method=pass_all ;; + *) + # glibc up to 2.1.1 does not perform some relocations on ARM + lt_cv_deplibs_check_method='file_magic ELF [[0-9]][[0-9]]*-bit [[LM]]SB (shared object|dynamic lib )' ;; + esac + lt_cv_file_magic_test_file=`echo /lib/libc.so* /lib/libc-*.so` + ;; + +netbsd*) + if echo __ELF__ | $CC -E - | grep __ELF__ > /dev/null; then + lt_cv_deplibs_check_method='match_pattern /lib[[^/]]+(\.so\.[[0-9]]+\.[[0-9]]+|_pic\.a)$' + else + lt_cv_deplibs_check_method='match_pattern /lib[[^/]]+(\.so|_pic\.a)$' + fi + ;; + +newos6*) + lt_cv_deplibs_check_method='file_magic ELF [[0-9]][[0-9]]*-bit [[ML]]SB (executable|dynamic lib)' + lt_cv_file_magic_cmd=/usr/bin/file + lt_cv_file_magic_test_file=/usr/lib/libnls.so + ;; + +nto-qnx*) + lt_cv_deplibs_check_method=unknown + ;; + +openbsd*) + lt_cv_file_magic_cmd=/usr/bin/file + lt_cv_file_magic_test_file=`echo /usr/lib/libc.so.*` + if test -z "`echo __ELF__ | $CC -E - | grep __ELF__`" || test "$host_os-$host_cpu" = "openbsd2.8-powerpc"; then + lt_cv_deplibs_check_method='file_magic ELF [[0-9]][[0-9]]*-bit [[LM]]SB shared object' + else + lt_cv_deplibs_check_method='file_magic OpenBSD.* shared library' + fi + ;; + +osf3* | osf4* | osf5*) + # this will be overridden with pass_all, but let us keep it just in case + lt_cv_deplibs_check_method='file_magic COFF format alpha shared library' + lt_cv_file_magic_test_file=/shlib/libc.so + lt_cv_deplibs_check_method=pass_all + ;; + +sco3.2v5*) + lt_cv_deplibs_check_method=pass_all + ;; + +solaris*) + lt_cv_deplibs_check_method=pass_all + lt_cv_file_magic_test_file=/lib/libc.so + ;; + +sysv4 | sysv4.2uw2* | sysv4.3* | sysv5*) + case $host_vendor in + motorola) + lt_cv_deplibs_check_method='file_magic ELF [[0-9]][[0-9]]*-bit [[ML]]SB (shared object|dynamic lib) M[[0-9]][[0-9]]* Version [[0-9]]' + lt_cv_file_magic_test_file=`echo /usr/lib/libc.so*` + ;; + ncr) + lt_cv_deplibs_check_method=pass_all + ;; + sequent) + lt_cv_file_magic_cmd='/bin/file' + lt_cv_deplibs_check_method='file_magic ELF [[0-9]][[0-9]]*-bit [[LM]]SB (shared object|dynamic lib )' + ;; + sni) + lt_cv_file_magic_cmd='/bin/file' + lt_cv_deplibs_check_method="file_magic ELF [[0-9]][[0-9]]*-bit [[LM]]SB dynamic lib" + lt_cv_file_magic_test_file=/lib/libc.so + ;; + siemens) + lt_cv_deplibs_check_method=pass_all + ;; + esac + ;; + +sysv5OpenUNIX8* | sysv5UnixWare7* | sysv5uw[[78]]* | unixware7* | sysv4*uw2*) + lt_cv_deplibs_check_method=pass_all + ;; +esac +]) +file_magic_cmd=$lt_cv_file_magic_cmd +deplibs_check_method=$lt_cv_deplibs_check_method +test -z "$deplibs_check_method" && deplibs_check_method=unknown +])# AC_DEPLIBS_CHECK_METHOD + + +# AC_PROG_NM +# ---------- +# find the pathname to a BSD-compatible name lister +AC_DEFUN([AC_PROG_NM], +[AC_CACHE_CHECK([for BSD-compatible nm], lt_cv_path_NM, +[if test -n "$NM"; then + # Let the user override the test. + lt_cv_path_NM="$NM" +else + lt_save_ifs="$IFS"; IFS=$PATH_SEPARATOR + for ac_dir in $PATH /usr/ccs/bin /usr/ucb /bin; do + IFS="$lt_save_ifs" + test -z "$ac_dir" && ac_dir=. + tmp_nm="$ac_dir/${ac_tool_prefix}nm" + if test -f "$tmp_nm" || test -f "$tmp_nm$ac_exeext" ; then + # Check to see if the nm accepts a BSD-compat flag. + # Adding the `sed 1q' prevents false positives on HP-UX, which says: + # nm: unknown option "B" ignored + # Tru64's nm complains that /dev/null is an invalid object file + case `"$tmp_nm" -B /dev/null 2>&1 | sed '1q'` in + */dev/null* | *'Invalid file or object type'*) + lt_cv_path_NM="$tmp_nm -B" + break + ;; + *) + case `"$tmp_nm" -p /dev/null 2>&1 | sed '1q'` in + */dev/null*) + lt_cv_path_NM="$tmp_nm -p" + break + ;; + *) + lt_cv_path_NM=${lt_cv_path_NM="$tmp_nm"} # keep the first match, but + continue # so that we can try to find one that supports BSD flags + ;; + esac + esac + fi + done + IFS="$lt_save_ifs" + test -z "$lt_cv_path_NM" && lt_cv_path_NM=nm +fi]) +NM="$lt_cv_path_NM" +])# AC_PROG_NM + + +# AC_CHECK_LIBM +# ------------- +# check for math library +AC_DEFUN([AC_CHECK_LIBM], +[AC_REQUIRE([AC_CANONICAL_HOST])dnl +LIBM= +case $host in +*-*-beos* | *-*-cygwin* | *-*-pw32* | *-*-darwin*) + # These system don't have libm, or don't need it + ;; +*-ncr-sysv4.3*) + AC_CHECK_LIB(mw, _mwvalidcheckl, LIBM="-lmw") + AC_CHECK_LIB(m, cos, LIBM="$LIBM -lm") + ;; +*) + AC_CHECK_LIB(m, cos, LIBM="-lm") + ;; +esac +])# AC_CHECK_LIBM + + +# AC_LIBLTDL_CONVENIENCE([DIRECTORY]) +# ----------------------------------- +# sets LIBLTDL to the link flags for the libltdl convenience library and +# LTDLINCL to the include flags for the libltdl header and adds +# --enable-ltdl-convenience to the configure arguments. Note that LIBLTDL +# and LTDLINCL are not AC_SUBSTed, nor is AC_CONFIG_SUBDIRS called. If +# DIRECTORY is not provided, it is assumed to be `libltdl'. LIBLTDL will +# be prefixed with '${top_builddir}/' and LTDLINCL will be prefixed with +# '${top_srcdir}/' (note the single quotes!). If your package is not +# flat and you're not using automake, define top_builddir and +# top_srcdir appropriately in the Makefiles. +AC_DEFUN([AC_LIBLTDL_CONVENIENCE], +[AC_BEFORE([$0],[AC_LIBTOOL_SETUP])dnl + case $enable_ltdl_convenience in + no) AC_MSG_ERROR([this package needs a convenience libltdl]) ;; + "") enable_ltdl_convenience=yes + ac_configure_args="$ac_configure_args --enable-ltdl-convenience" ;; + esac + LIBLTDL='${top_builddir}/'ifelse($#,1,[$1],['libltdl'])/libltdlc.la + LTDLINCL='-I${top_srcdir}/'ifelse($#,1,[$1],['libltdl']) + # For backwards non-gettext consistent compatibility... + INCLTDL="$LTDLINCL" +])# AC_LIBLTDL_CONVENIENCE + + +# AC_LIBLTDL_INSTALLABLE([DIRECTORY]) +# ----------------------------------- +# sets LIBLTDL to the link flags for the libltdl installable library and +# LTDLINCL to the include flags for the libltdl header and adds +# --enable-ltdl-install to the configure arguments. Note that LIBLTDL +# and LTDLINCL are not AC_SUBSTed, nor is AC_CONFIG_SUBDIRS called. If +# DIRECTORY is not provided and an installed libltdl is not found, it is +# assumed to be `libltdl'. LIBLTDL will be prefixed with '${top_builddir}/' +# and LTDLINCL will be prefixed with '${top_srcdir}/' (note the single +# quotes!). If your package is not flat and you're not using automake, +# define top_builddir and top_srcdir appropriately in the Makefiles. +# In the future, this macro may have to be called after AC_PROG_LIBTOOL. +AC_DEFUN([AC_LIBLTDL_INSTALLABLE], +[AC_BEFORE([$0],[AC_LIBTOOL_SETUP])dnl + AC_CHECK_LIB(ltdl, lt_dlinit, + [test x"$enable_ltdl_install" != xyes && enable_ltdl_install=no], + [if test x"$enable_ltdl_install" = xno; then + AC_MSG_WARN([libltdl not installed, but installation disabled]) + else + enable_ltdl_install=yes + fi + ]) + if test x"$enable_ltdl_install" = x"yes"; then + ac_configure_args="$ac_configure_args --enable-ltdl-install" + LIBLTDL='${top_builddir}/'ifelse($#,1,[$1],['libltdl'])/libltdl.la + LTDLINCL='-I${top_srcdir}/'ifelse($#,1,[$1],['libltdl']) + else + ac_configure_args="$ac_configure_args --enable-ltdl-install=no" + LIBLTDL="-lltdl" + LTDLINCL= + fi + # For backwards non-gettext consistent compatibility... + INCLTDL="$LTDLINCL" +])# AC_LIBLTDL_INSTALLABLE + + +# AC_LIBTOOL_CXX +# -------------- +# enable support for C++ libraries +AC_DEFUN([AC_LIBTOOL_CXX], +[AC_REQUIRE([_LT_AC_LANG_CXX]) +])# AC_LIBTOOL_CXX + + +# _LT_AC_LANG_CXX +# --------------- +AC_DEFUN([_LT_AC_LANG_CXX], +[AC_REQUIRE([AC_PROG_CXX]) +AC_REQUIRE([AC_PROG_CXXCPP]) +_LT_AC_SHELL_INIT([tagnames=${tagnames+${tagnames},}CXX]) +])# _LT_AC_LANG_CXX + + +# AC_LIBTOOL_F77 +# -------------- +# enable support for Fortran 77 libraries +AC_DEFUN([AC_LIBTOOL_F77], +[AC_REQUIRE([_LT_AC_LANG_F77]) +])# AC_LIBTOOL_F77 + + +# _LT_AC_LANG_F77 +# --------------- +AC_DEFUN([_LT_AC_LANG_F77], +[AC_REQUIRE([AC_PROG_F77]) +_LT_AC_SHELL_INIT([tagnames=${tagnames+${tagnames},}F77]) +])# _LT_AC_LANG_F77 + + +# AC_LIBTOOL_GCJ +# -------------- +# enable support for GCJ libraries +AC_DEFUN([AC_LIBTOOL_GCJ], +[AC_REQUIRE([_LT_AC_LANG_GCJ]) +])# AC_LIBTOOL_GCJ + + +# _LT_AC_LANG_GCJ +# --------------- +AC_DEFUN([_LT_AC_LANG_GCJ], +[AC_PROVIDE_IFELSE([AC_PROG_GCJ],[], + [AC_PROVIDE_IFELSE([A][M_PROG_GCJ],[], + [AC_PROVIDE_IFELSE([LT_AC_PROG_GCJ],[], + [ifdef([AC_PROG_GCJ],[AC_REQUIRE([AC_PROG_GCJ])], + [ifdef([A][M_PROG_GCJ],[AC_REQUIRE([A][M_PROG_GCJ])], + [AC_REQUIRE([A][C_PROG_GCJ_OR_A][M_PROG_GCJ])])])])])]) +_LT_AC_SHELL_INIT([tagnames=${tagnames+${tagnames},}GCJ]) +])# _LT_AC_LANG_GCJ + + +# AC_LIBTOOL_RC +# -------------- +# enable support for Windows resource files +AC_DEFUN([AC_LIBTOOL_RC], +[AC_REQUIRE([LT_AC_PROG_RC]) +_LT_AC_SHELL_INIT([tagnames=${tagnames+${tagnames},}RC]) +])# AC_LIBTOOL_RC + + +# AC_LIBTOOL_LANG_C_CONFIG +# ------------------------ +# Ensure that the configuration vars for the C compiler are +# suitably defined. Those variables are subsequently used by +# AC_LIBTOOL_CONFIG to write the compiler configuration to `libtool'. +AC_DEFUN([AC_LIBTOOL_LANG_C_CONFIG], [_LT_AC_LANG_C_CONFIG]) +AC_DEFUN([_LT_AC_LANG_C_CONFIG], +[lt_save_CC="$CC" +AC_LANG_PUSH(C) + +# Source file extension for C test sources. +ac_ext=c + +# Object file extension for compiled C test sources. +objext=o +_LT_AC_TAGVAR(objext, $1)=$objext + +# Code to be used in simple compile tests +lt_simple_compile_test_code="int some_variable = 0;\n" + +# Code to be used in simple link tests +lt_simple_link_test_code='int main(){return(0);}\n' + +_LT_AC_SYS_COMPILER + +# +# Check for any special shared library compilation flags. +# +_LT_AC_TAGVAR(lt_prog_cc_shlib, $1)= +if test "$GCC" = no; then + case $host_os in + sco3.2v5*) + _LT_AC_TAGVAR(lt_prog_cc_shlib, $1)='-belf' + ;; + esac +fi +if test -n "$_LT_AC_TAGVAR(lt_prog_cc_shlib, $1)"; then + AC_MSG_WARN([`$CC' requires `$_LT_AC_TAGVAR(lt_prog_cc_shlib, $1)' to build shared libraries]) + if echo "$old_CC $old_CFLAGS " | grep "[[ ]]$]_LT_AC_TAGVAR(lt_prog_cc_shlib, $1)[[[ ]]" >/dev/null; then : + else + AC_MSG_WARN([add `$_LT_AC_TAGVAR(lt_prog_cc_shlib, $1)' to the CC or CFLAGS env variable and reconfigure]) + _LT_AC_TAGVAR(lt_cv_prog_cc_can_build_shared, $1)=no + fi +fi + + +# +# Check to make sure the static flag actually works. +# +AC_LIBTOOL_LINKER_OPTION([if $compiler static flag $_LT_AC_TAGVAR(lt_prog_compiler_static, $1) works], + _LT_AC_TAGVAR(lt_prog_compiler_static_works, $1), + $_LT_AC_TAGVAR(lt_prog_compiler_static, $1), + [], + [_LT_AC_TAGVAR(lt_prog_compiler_static, $1)=]) + + +## CAVEAT EMPTOR: +## There is no encapsulation within the following macros, do not change +## the running order or otherwise move them around unless you know exactly +## what you are doing... +AC_LIBTOOL_PROG_COMPILER_NO_RTTI($1) +AC_LIBTOOL_PROG_COMPILER_PIC($1) +AC_LIBTOOL_PROG_CC_C_O($1) +AC_LIBTOOL_SYS_HARD_LINK_LOCKS($1) +AC_LIBTOOL_PROG_LD_SHLIBS($1) +AC_LIBTOOL_SYS_DYNAMIC_LINKER($1) +AC_LIBTOOL_PROG_LD_HARDCODE_LIBPATH($1) +AC_LIBTOOL_SYS_LIB_STRIP +AC_LIBTOOL_DLOPEN_SELF($1) + +# Report which librarie types wil actually be built +AC_MSG_CHECKING([if libtool supports shared libraries]) +AC_MSG_RESULT([$can_build_shared]) + +AC_MSG_CHECKING([whether to build shared libraries]) +test "$can_build_shared" = "no" && enable_shared=no + +# On AIX, shared libraries and static libraries use the same namespace, and +# are all built from PIC. +case "$host_os" in +aix3*) + test "$enable_shared" = yes && enable_static=no + if test -n "$RANLIB"; then + archive_cmds="$archive_cmds~\$RANLIB \$lib" + postinstall_cmds='$RANLIB $lib' + fi + ;; + +aix4*) + if test "$host_cpu" != ia64 && test "$aix_use_runtimelinking" = no ; then + test "$enable_shared" = yes && enable_static=no + fi + ;; + darwin* | rhapsody*) + if test "$GCC" = yes; then + _LT_AC_TAGVAR(archive_cmds_need_lc, $1)=no + case "$host_os" in + rhapsody* | darwin1.[[012]]) + _LT_AC_TAGVAR(allow_undefined_flag, $1)='-Wl,-undefined -Wl,suppress' + ;; + *) # Darwin 1.3 on + if test -z ${MACOSX_DEPLOYMENT_TARGET} ; then + _LT_AC_TAGVAR(allow_undefined_flag, $1)='-Wl,-flat_namespace -Wl,-undefined -Wl,suppress' + else + case ${MACOSX_DEPLOYMENT_TARGET} in + 10.[012]) + _LT_AC_TAGVAR(allow_undefined_flag, $1)='-Wl,-flat_namespace -Wl,-undefined -Wl,suppress' + ;; + 10.*) + _LT_AC_TAGVAR(allow_undefined_flag, $1)='-Wl,-undefined -Wl,dynamic_lookup' + ;; + esac + fi + ;; + esac + output_verbose_link_cmd='echo' + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -dynamiclib $allow_undefined_flag -o $lib $compiler_flags $libobjs $deplibs -install_name $rpath/$soname $verstring' + _LT_AC_TAGVAR(module_cmds, $1)='$CC $allow_undefined_flag -o $lib -bundle $compiler_flags $libobjs $deplibs' + # Don't fix this by using the ld -exported_symbols_list flag, it doesn't exist in older darwin ld's + _LT_AC_TAGVAR(archive_expsym_cmds, $1)='sed -e "s,#.*,," -e "s,^[ ]*,," -e "s,^\(..*\),_&," < $export_symbols > $output_objdir/${libname}-symbols.expsym~$CC -dynamiclib $allow_undefined_flag -o $lib $compiler_flags $libobjs $deplibs -install_name $rpath/$soname $verstring~nmedit -s $output_objdir/${libname}-symbols.expsym ${lib}' + _LT_AC_TAGVAR(module_expsym_cmds, $1)='sed -e "s,#.*,," -e "s,^[ ]*,," -e "s,^\(..*\),_&," < $export_symbols > $output_objdir/${libname}-symbols.expsym~$CC $allow_undefined_flag -o $lib -bundle $compiler_flags $libobjs $deplibs~nmedit -s $output_objdir/${libname}-symbols.expsym ${lib}' + _LT_AC_TAGVAR(hardcode_direct, $1)=no + _LT_AC_TAGVAR(hardcode_automatic, $1)=yes + _LT_AC_TAGVAR(hardcode_shlibpath_var, $1)=unsupported + _LT_AC_TAGVAR(whole_archive_flag_spec, $1)='-all_load $convenience' + _LT_AC_TAGVAR(link_all_deplibs, $1)=yes + else + _LT_AC_TAGVAR(ld_shlibs, $1)=no + fi + ;; +esac +AC_MSG_RESULT([$enable_shared]) + +AC_MSG_CHECKING([whether to build static libraries]) +# Make sure either enable_shared or enable_static is yes. +test "$enable_shared" = yes || enable_static=yes +AC_MSG_RESULT([$enable_static]) + +AC_LIBTOOL_CONFIG($1) + +AC_LANG_POP +CC="$lt_save_CC" +])# AC_LIBTOOL_LANG_C_CONFIG + + +# AC_LIBTOOL_LANG_CXX_CONFIG +# -------------------------- +# Ensure that the configuration vars for the C compiler are +# suitably defined. Those variables are subsequently used by +# AC_LIBTOOL_CONFIG to write the compiler configuration to `libtool'. +AC_DEFUN([AC_LIBTOOL_LANG_CXX_CONFIG], [_LT_AC_LANG_CXX_CONFIG(CXX)]) +AC_DEFUN([_LT_AC_LANG_CXX_CONFIG], +[AC_LANG_PUSH(C++) +AC_REQUIRE([AC_PROG_CXX]) +AC_REQUIRE([AC_PROG_CXXCPP]) + +_LT_AC_TAGVAR(archive_cmds_need_lc, $1)=no +_LT_AC_TAGVAR(allow_undefined_flag, $1)= +_LT_AC_TAGVAR(always_export_symbols, $1)=no +_LT_AC_TAGVAR(archive_expsym_cmds, $1)= +_LT_AC_TAGVAR(export_dynamic_flag_spec, $1)= +_LT_AC_TAGVAR(hardcode_direct, $1)=no +_LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)= +_LT_AC_TAGVAR(hardcode_libdir_flag_spec_ld, $1)= +_LT_AC_TAGVAR(hardcode_libdir_separator, $1)= +_LT_AC_TAGVAR(hardcode_minus_L, $1)=no +_LT_AC_TAGVAR(hardcode_automatic, $1)=no +_LT_AC_TAGVAR(module_cmds, $1)= +_LT_AC_TAGVAR(module_expsym_cmds, $1)= +_LT_AC_TAGVAR(link_all_deplibs, $1)=unknown +_LT_AC_TAGVAR(old_archive_cmds, $1)=$old_archive_cmds +_LT_AC_TAGVAR(no_undefined_flag, $1)= +_LT_AC_TAGVAR(whole_archive_flag_spec, $1)= +_LT_AC_TAGVAR(enable_shared_with_static_runtimes, $1)=no + +# Dependencies to place before and after the object being linked: +_LT_AC_TAGVAR(predep_objects, $1)= +_LT_AC_TAGVAR(postdep_objects, $1)= +_LT_AC_TAGVAR(predeps, $1)= +_LT_AC_TAGVAR(postdeps, $1)= +_LT_AC_TAGVAR(compiler_lib_search_path, $1)= + +# Source file extension for C++ test sources. +ac_ext=cc + +# Object file extension for compiled C++ test sources. +objext=o +_LT_AC_TAGVAR(objext, $1)=$objext + +# Code to be used in simple compile tests +lt_simple_compile_test_code="int some_variable = 0;\n" + +# Code to be used in simple link tests +lt_simple_link_test_code='int main(int, char *[]) { return(0); }\n' + +# ltmain only uses $CC for tagged configurations so make sure $CC is set. +_LT_AC_SYS_COMPILER + +# Allow CC to be a program name with arguments. +lt_save_CC=$CC +lt_save_LD=$LD +lt_save_GCC=$GCC +GCC=$GXX +lt_save_with_gnu_ld=$with_gnu_ld +lt_save_path_LD=$lt_cv_path_LD +if test -n "${lt_cv_prog_gnu_ldcxx+set}"; then + lt_cv_prog_gnu_ld=$lt_cv_prog_gnu_ldcxx +else + unset lt_cv_prog_gnu_ld +fi +if test -n "${lt_cv_path_LDCXX+set}"; then + lt_cv_path_LD=$lt_cv_path_LDCXX +else + unset lt_cv_path_LD +fi +test -z "${LDCXX+set}" || LD=$LDCXX +CC=${CXX-"c++"} +compiler=$CC +_LT_AC_TAGVAR(compiler, $1)=$CC +cc_basename=`$echo X"$compiler" | $Xsed -e 's%^.*/%%'` + +# We don't want -fno-exception wen compiling C++ code, so set the +# no_builtin_flag separately +if test "$GXX" = yes; then + _LT_AC_TAGVAR(lt_prog_compiler_no_builtin_flag, $1)=' -fno-builtin' +else + _LT_AC_TAGVAR(lt_prog_compiler_no_builtin_flag, $1)= +fi + +if test "$GXX" = yes; then + # Set up default GNU C++ configuration + + AC_PROG_LD + + # Check if GNU C++ uses GNU ld as the underlying linker, since the + # archiving commands below assume that GNU ld is being used. + if test "$with_gnu_ld" = yes; then + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -shared -nostdlib $compiler_flags $predep_objects $libobjs $deplibs $postdep_objects ${wl}-soname $wl$soname -o $lib' + _LT_AC_TAGVAR(archive_expsym_cmds, $1)='$CC -shared -nostdlib $compiler_flags $predep_objects $libobjs $deplibs $postdep_objects ${wl}-soname $wl$soname ${wl}-retain-symbols-file $wl$export_symbols -o $lib' + + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}--rpath ${wl}$libdir' + _LT_AC_TAGVAR(export_dynamic_flag_spec, $1)='${wl}--export-dynamic' + + # If archive_cmds runs LD, not CC, wlarc should be empty + # XXX I think wlarc can be eliminated in ltcf-cxx, but I need to + # investigate it a little bit more. (MM) + wlarc='${wl}' + + # ancient GNU ld didn't support --whole-archive et. al. + if eval "`$CC -print-prog-name=ld` --help 2>&1" | \ + grep 'no-whole-archive' > /dev/null; then + _LT_AC_TAGVAR(whole_archive_flag_spec, $1)="$wlarc"'--whole-archive$convenience '"$wlarc"'--no-whole-archive' + else + _LT_AC_TAGVAR(whole_archive_flag_spec, $1)= + fi + else + with_gnu_ld=no + wlarc= + + # A generic and very simple default shared library creation + # command for GNU C++ for the case where it uses the native + # linker, instead of GNU ld. If possible, this setting should + # overridden to take advantage of the native linker features on + # the platform it is being used on. + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -shared -nostdlib $compiler_flags $predep_objects $libobjs $deplibs $postdep_objects -o $lib' + fi + + # Commands to make compiler produce verbose output that lists + # what "hidden" libraries, object files and flags are used when + # linking a shared library. + output_verbose_link_cmd='$CC -shared $CFLAGS -v conftest.$objext 2>&1 | grep "\-L"' + +else + GXX=no + with_gnu_ld=no + wlarc= +fi + +# PORTME: fill in a description of your system's C++ link characteristics +AC_MSG_CHECKING([whether the $compiler linker ($LD) supports shared libraries]) +_LT_AC_TAGVAR(ld_shlibs, $1)=yes +case $host_os in + aix3*) + # FIXME: insert proper C++ library support + _LT_AC_TAGVAR(ld_shlibs, $1)=no + ;; + aix4* | aix5*) + if test "$host_cpu" = ia64; then + # On IA64, the linker does run time linking by default, so we don't + # have to do anything special. + aix_use_runtimelinking=no + exp_sym_flag='-Bexport' + no_entry_flag="" + else + # KDE requires run time linking. Make it the default. + aix_use_runtimelinking=yes + exp_sym_flag='-bexport' + no_entry_flag='-bnoentry' + fi + + # When large executables or shared objects are built, AIX ld can + # have problems creating the table of contents. If linking a library + # or program results in "error TOC overflow" add -mminimal-toc to + # CXXFLAGS/CFLAGS for g++/gcc. In the cases where that is not + # enough to fix the problem, add -Wl,-bbigtoc to LDFLAGS. + + _LT_AC_TAGVAR(archive_cmds, $1)='' + _LT_AC_TAGVAR(hardcode_direct, $1)=yes + _LT_AC_TAGVAR(hardcode_libdir_separator, $1)=':' + _LT_AC_TAGVAR(link_all_deplibs, $1)=yes + + if test "$GXX" = yes; then + case $host_os in aix4.[012]|aix4.[012].*) + # We only want to do this on AIX 4.2 and lower, the check + # below for broken collect2 doesn't work under 4.3+ + collect2name=`${CC} -print-prog-name=collect2` + if test -f "$collect2name" && \ + strings "$collect2name" | grep resolve_lib_name >/dev/null + then + # We have reworked collect2 + _LT_AC_TAGVAR(hardcode_direct, $1)=yes + else + # We have old collect2 + _LT_AC_TAGVAR(hardcode_direct, $1)=unsupported + # It fails to find uninstalled libraries when the uninstalled + # path is not listed in the libpath. Setting hardcode_minus_L + # to unsupported forces relinking + _LT_AC_TAGVAR(hardcode_minus_L, $1)=yes + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' + _LT_AC_TAGVAR(hardcode_libdir_separator, $1)= + fi + esac + shared_flag='-shared' + else + # not using gcc + if test "$host_cpu" = ia64; then + # VisualAge C++, Version 5.5 for AIX 5L for IA-64, Beta 3 Release + # chokes on -Wl,-G. The following line is correct: + shared_flag='-G' + else + if test "$aix_use_runtimelinking" = yes; then + shared_flag='-qmkshrobj ${wl}-G' + else + shared_flag='-qmkshrobj' + fi + fi + fi + + # Let the compiler handle the export list. + _LT_AC_TAGVAR(always_export_symbols, $1)=no + if test "$aix_use_runtimelinking" = yes; then + # Warning - without using the other runtime loading flags (-brtl), + # -berok will link without error, but may produce a broken library. + _LT_AC_TAGVAR(allow_undefined_flag, $1)='-berok' + # Determine the default libpath from the value encoded in an empty executable. + _LT_AC_SYS_LIBPATH_AIX + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-blibpath:$libdir:'"$aix_libpath" + + _LT_AC_TAGVAR(archive_cmds, $1)="\$CC"' -o $output_objdir/$soname $compiler_flags $libobjs $deplibs `if test "x${allow_undefined_flag}" != "x"; then echo "${wl}${allow_undefined_flag}"; else :; fi` '" $shared_flag" + _LT_AC_TAGVAR(archive_expsym_cmds, $1)="\$CC"' -o $output_objdir/$soname $compiler_flags $libobjs $deplibs `if test "x${allow_undefined_flag}" != "x"; then echo "${wl}${allow_undefined_flag}"; else :; fi` '"\${wl}$exp_sym_flag:\$export_symbols $shared_flag" + else + if test "$host_cpu" = ia64; then + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-R $libdir:/usr/lib:/lib' + _LT_AC_TAGVAR(allow_undefined_flag, $1)="-z nodefs" + _LT_AC_TAGVAR(archive_expsym_cmds, $1)="\$CC $shared_flag"' -o $output_objdir/$soname $compiler_flags $libobjs $deplibs ${wl}${allow_undefined_flag} '"\${wl}$no_entry_flag \${wl}$exp_sym_flag:\$export_symbols" + else + # Determine the default libpath from the value encoded in an empty executable. + _LT_AC_SYS_LIBPATH_AIX + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-blibpath:$libdir:'"$aix_libpath" + # Warning - without using the other run time loading flags, + # -berok will link without error, but may produce a broken library. + _LT_AC_TAGVAR(no_undefined_flag, $1)=' ${wl}-bernotok' + _LT_AC_TAGVAR(allow_undefined_flag, $1)=' ${wl}-berok' + # -bexpall does not export symbols beginning with underscore (_) + _LT_AC_TAGVAR(always_export_symbols, $1)=yes + # Exported symbols can be pulled into shared objects from archives + _LT_AC_TAGVAR(whole_archive_flag_spec, $1)=' ' + _LT_AC_TAGVAR(archive_cmds_need_lc, $1)=yes + # This is similar to how AIX traditionally builds it's shared libraries. + _LT_AC_TAGVAR(archive_expsym_cmds, $1)="\$CC $shared_flag"' -o $output_objdir/$soname $compiler_flags $libobjs $deplibs ${wl}-bE:$export_symbols ${wl}-bnoentry${allow_undefined_flag}~$AR $AR_FLAGS $output_objdir/$libname$release.a $output_objdir/$soname' + fi + fi + ;; + chorus*) + case $cc_basename in + *) + # FIXME: insert proper C++ library support + _LT_AC_TAGVAR(ld_shlibs, $1)=no + ;; + esac + ;; + + cygwin* | mingw* | pw32*) + # _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1) is actually meaningless, + # as there is no search path for DLLs. + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' + _LT_AC_TAGVAR(allow_undefined_flag, $1)=no + _LT_AC_TAGVAR(always_export_symbols, $1)=no + _LT_AC_TAGVAR(enable_shared_with_static_runtimes, $1)=yes + + if $LD --help 2>&1 | grep 'auto-import' > /dev/null; then + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -shared -nostdlib $compiler_flags $predep_objects $libobjs $deplibs $postdep_objects -o $output_objdir/$soname ${wl}--image-base=0x10000000 ${wl}--out-implib,$lib' + # If the export-symbols file already is a .def file (1st line + # is EXPORTS), use it as is; otherwise, prepend... + _LT_AC_TAGVAR(archive_expsym_cmds, $1)='if test "x`$SED 1q $export_symbols`" = xEXPORTS; then + cp $export_symbols $output_objdir/$soname.def; + else + echo EXPORTS > $output_objdir/$soname.def; + cat $export_symbols >> $output_objdir/$soname.def; + fi~ + $CC -shared -nostdlib $output_objdir/$soname.def $compiler_flags $predep_objects $libobjs $deplibs $postdep_objects -o $output_objdir/$soname ${wl}--image-base=0x10000000 ${wl}--out-implib,$lib' + else + _LT_AC_TAGVAR(ld_shlibs, $1)=no + fi + ;; + + darwin* | rhapsody*) + if test "$GXX" = yes; then + _LT_AC_TAGVAR(archive_cmds_need_lc, $1)=no + case "$host_os" in + rhapsody* | darwin1.[[012]]) + _LT_AC_TAGVAR(allow_undefined_flag, $1)='-Wl,-undefined -Wl,suppress' + ;; + *) # Darwin 1.3 on + if test -z ${MACOSX_DEPLOYMENT_TARGET} ; then + _LT_AC_TAGVAR(allow_undefined_flag, $1)='-Wl,-flat_namespace -Wl,-undefined -Wl,suppress' + else + case ${MACOSX_DEPLOYMENT_TARGET} in + 10.[012]) + _LT_AC_TAGVAR(allow_undefined_flag, $1)='-Wl,-flat_namespace -Wl,-undefined -Wl,suppress' + ;; + 10.*) + _LT_AC_TAGVAR(allow_undefined_flag, $1)='-Wl,-undefined -Wl,dynamic_lookup' + ;; + esac + fi + ;; + esac + lt_int_apple_cc_single_mod=no + output_verbose_link_cmd='echo' + if $CC -dumpspecs 2>&1 | grep 'single_module' >/dev/null ; then + lt_int_apple_cc_single_mod=yes + fi + if test "X$lt_int_apple_cc_single_mod" = Xyes ; then + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -dynamiclib -single_module $allow_undefined_flag -o $lib $compiler_flags $libobjs $deplibs -install_name $rpath/$soname $verstring' + else + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -r ${wl}-bind_at_load -keep_private_externs -nostdlib -o ${lib}-master.o $libobjs~$CC -dynamiclib $allow_undefined_flag -o $lib ${lib}-master.o $compiler_flags $deplibs -install_name $rpath/$soname $verstring' + fi + _LT_AC_TAGVAR(module_cmds, $1)='$CC ${wl}-bind_at_load $allow_undefined_flag -o $lib -bundle $compiler_flags $libobjs $deplibs' + + # Don't fix this by using the ld -exported_symbols_list flag, it doesn't exist in older darwin ld's + if test "X$lt_int_apple_cc_single_mod" = Xyes ; then + _LT_AC_TAGVAR(archive_expsym_cmds, $1)='sed -e "s,#.*,," -e "s,^[ ]*,," -e "s,^\(..*\),_&," < $export_symbols > $output_objdir/${libname}-symbols.expsym~$CC -dynamiclib -single_module $allow_undefined_flag -o $lib $compiler_flags $libobjs $deplibs -install_name $rpath/$soname $verstring~nmedit -s $output_objdir/${libname}-symbols.expsym ${lib}' + else + _LT_AC_TAGVAR(archive_expsym_cmds, $1)='sed -e "s,#.*,," -e "s,^[ ]*,," -e "s,^\(..*\),_&," < $export_symbols > $output_objdir/${libname}-symbols.expsym~$CC -r ${wl}-bind_at_load -keep_private_externs -nostdlib -o ${lib}-master.o $libobjs~$CC -dynamiclib $allow_undefined_flag -o $lib ${lib}-master.o $compiler_flags $deplibs -install_name $rpath/$soname $verstring~nmedit -s $output_objdir/${libname}-symbols.expsym ${lib}' + fi + _LT_AC_TAGVAR(module_expsym_cmds, $1)='sed -e "s,#.*,," -e "s,^[ ]*,," -e "s,^\(..*\),_&," < $export_symbols > $output_objdir/${libname}-symbols.expsym~$CC $allow_undefined_flag -o $lib -bundle $compiler_flags $libobjs $deplibs~nmedit -s $output_objdir/${libname}-symbols.expsym ${lib}' + _LT_AC_TAGVAR(hardcode_direct, $1)=no + _LT_AC_TAGVAR(hardcode_automatic, $1)=yes + _LT_AC_TAGVAR(hardcode_shlibpath_var, $1)=unsupported + _LT_AC_TAGVAR(whole_archive_flag_spec, $1)='-all_load $convenience' + _LT_AC_TAGVAR(link_all_deplibs, $1)=yes + else + _LT_AC_TAGVAR(ld_shlibs, $1)=no + fi + ;; + + dgux*) + case $cc_basename in + ec++) + # FIXME: insert proper C++ library support + _LT_AC_TAGVAR(ld_shlibs, $1)=no + ;; + ghcx) + # Green Hills C++ Compiler + # FIXME: insert proper C++ library support + _LT_AC_TAGVAR(ld_shlibs, $1)=no + ;; + *) + # FIXME: insert proper C++ library support + _LT_AC_TAGVAR(ld_shlibs, $1)=no + ;; + esac + ;; + freebsd[12]*) + # C++ shared libraries reported to be fairly broken before switch to ELF + _LT_AC_TAGVAR(ld_shlibs, $1)=no + ;; + freebsd-elf*) + _LT_AC_TAGVAR(archive_cmds_need_lc, $1)=no + ;; + freebsd* | kfreebsd*-gnu) + # FreeBSD 3 and later use GNU C++ and GNU ld with standard ELF + # conventions + _LT_AC_TAGVAR(ld_shlibs, $1)=yes + ;; + gnu*) + ;; + hpux9*) + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}+b ${wl}$libdir' + _LT_AC_TAGVAR(hardcode_libdir_separator, $1)=: + _LT_AC_TAGVAR(export_dynamic_flag_spec, $1)='${wl}-E' + _LT_AC_TAGVAR(hardcode_direct, $1)=yes + _LT_AC_TAGVAR(hardcode_minus_L, $1)=yes # Not in the search PATH, + # but as the default + # location of the library. + + case $cc_basename in + CC) + # FIXME: insert proper C++ library support + _LT_AC_TAGVAR(ld_shlibs, $1)=no + ;; + aCC) + _LT_AC_TAGVAR(archive_cmds, $1)='$rm $output_objdir/$soname~$CC -b ${wl}+b ${wl}$install_libdir -o $output_objdir/$soname $compiler_flags $predep_objects $libobjs $deplibs $postdep_objects~test $output_objdir/$soname = $lib || mv $output_objdir/$soname $lib' + # Commands to make compiler produce verbose output that lists + # what "hidden" libraries, object files and flags are used when + # linking a shared library. + # + # There doesn't appear to be a way to prevent this compiler from + # explicitly linking system object files so we need to strip them + # from the output so that they don't get included in the library + # dependencies. + output_verbose_link_cmd='templist=`($CC -b $CFLAGS -v conftest.$objext 2>&1) | egrep "\-L"`; list=""; for z in $templist; do case $z in conftest.$objext) list="$list $z";; *.$objext);; *) list="$list $z";;esac; done; echo $list' + ;; + *) + if test "$GXX" = yes; then + _LT_AC_TAGVAR(archive_cmds, $1)='$rm $output_objdir/$soname~$CC -shared -nostdlib -fPIC ${wl}+b ${wl}$install_libdir -o $output_objdir/$soname $compiler_flags $predep_objects $libobjs $deplibs $postdep_objects~test $output_objdir/$soname = $lib || mv $output_objdir/$soname $lib' + else + # FIXME: insert proper C++ library support + _LT_AC_TAGVAR(ld_shlibs, $1)=no + fi + ;; + esac + ;; + hpux10*|hpux11*) + if test $with_gnu_ld = no; then + case "$host_cpu" in + hppa*64*) + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}+b ${wl}$libdir' + _LT_AC_TAGVAR(hardcode_libdir_flag_spec_ld, $1)='+b $libdir' + _LT_AC_TAGVAR(hardcode_libdir_separator, $1)=: + ;; + ia64*) + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' + ;; + *) + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}+b ${wl}$libdir' + _LT_AC_TAGVAR(hardcode_libdir_separator, $1)=: + _LT_AC_TAGVAR(export_dynamic_flag_spec, $1)='${wl}-E' + ;; + esac + fi + case "$host_cpu" in + hppa*64*) + _LT_AC_TAGVAR(hardcode_direct, $1)=no + _LT_AC_TAGVAR(hardcode_shlibpath_var, $1)=no + ;; + ia64*) + _LT_AC_TAGVAR(hardcode_direct, $1)=no + _LT_AC_TAGVAR(hardcode_shlibpath_var, $1)=no + _LT_AC_TAGVAR(hardcode_minus_L, $1)=yes # Not in the search PATH, + # but as the default + # location of the library. + ;; + *) + _LT_AC_TAGVAR(hardcode_direct, $1)=yes + _LT_AC_TAGVAR(hardcode_minus_L, $1)=yes # Not in the search PATH, + # but as the default + # location of the library. + ;; + esac + + case $cc_basename in + CC) + # FIXME: insert proper C++ library support + _LT_AC_TAGVAR(ld_shlibs, $1)=no + ;; + aCC) + case "$host_cpu" in + hppa*64*|ia64*) + _LT_AC_TAGVAR(archive_cmds, $1)='$LD -b +h $soname -o $lib $linker_flags $libobjs $deplibs' + ;; + *) + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -b ${wl}+h ${wl}$soname ${wl}+b ${wl}$install_libdir -o $lib $compiler_flags $predep_objects $libobjs $deplibs $postdep_objects' + ;; + esac + # Commands to make compiler produce verbose output that lists + # what "hidden" libraries, object files and flags are used when + # linking a shared library. + # + # There doesn't appear to be a way to prevent this compiler from + # explicitly linking system object files so we need to strip them + # from the output so that they don't get included in the library + # dependencies. + output_verbose_link_cmd='templist=`($CC -b $CFLAGS -v conftest.$objext 2>&1) | grep "\-L"`; list=""; for z in $templist; do case $z in conftest.$objext) list="$list $z";; *.$objext);; *) list="$list $z";;esac; done; echo $list' + ;; + *) + if test "$GXX" = yes; then + if test $with_gnu_ld = no; then + case "$host_cpu" in + ia64*|hppa*64*) + _LT_AC_TAGVAR(archive_cmds, $1)='$LD -b +h $soname -o $lib $linker_flags $libobjs $deplibs' + ;; + *) + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -shared -nostdlib -fPIC ${wl}+h ${wl}$soname ${wl}+b ${wl}$install_libdir -o $lib $compiler_flags $predep_objects $libobjs $deplibs $postdep_objects' + ;; + esac + fi + else + # FIXME: insert proper C++ library support + _LT_AC_TAGVAR(ld_shlibs, $1)=no + fi + ;; + esac + ;; + irix5* | irix6*) + case $cc_basename in + CC) + # SGI C++ + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -shared -all -multigot $compiler_flags $predep_objects $libobjs $deplibs $postdep_objects -soname $soname `test -n "$verstring" && echo -set_version $verstring` -update_registry ${objdir}/so_locations -o $lib' + + # Archives containing C++ object files must be created using + # "CC -ar", where "CC" is the IRIX C++ compiler. This is + # necessary to make sure instantiated templates are included + # in the archive. + _LT_AC_TAGVAR(old_archive_cmds, $1)='$CC -ar -WR,-u -o $oldlib $oldobjs' + ;; + *) + if test "$GXX" = yes; then + if test "$with_gnu_ld" = no; then + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -shared -nostdlib $compiler_flags $predep_objects $libobjs $deplibs $postdep_objects ${wl}-soname ${wl}$soname `test -n "$verstring" && echo ${wl}-set_version ${wl}$verstring` ${wl}-update_registry ${wl}${objdir}/so_locations -o $lib' + else + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -shared -nostdlib $compiler_flags $predep_objects $libobjs $deplibs $postdep_objects ${wl}-soname ${wl}$soname `test -n "$verstring" && echo ${wl}-set_version ${wl}$verstring` -o $lib' + fi + fi + _LT_AC_TAGVAR(link_all_deplibs, $1)=yes + ;; + esac + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-rpath ${wl}$libdir' + _LT_AC_TAGVAR(hardcode_libdir_separator, $1)=: + ;; + linux*) + case $cc_basename in + KCC) + # Kuck and Associates, Inc. (KAI) C++ Compiler + + # KCC will only create a shared library if the output file + # ends with ".so" (or ".sl" for HP-UX), so rename the library + # to its proper name (with version) after linking. + _LT_AC_TAGVAR(archive_cmds, $1)='tempext=`echo $shared_ext | $SED -e '\''s/\([[^()0-9A-Za-z{}]]\)/\\\\\1/g'\''`; templib=`echo $lib | $SED -e "s/\${tempext}\..*/.so/"`; $CC $compiler_flags $predep_objects $libobjs $deplibs $postdep_objects --soname $soname -o \$templib; mv \$templib $lib' + _LT_AC_TAGVAR(archive_expsym_cmds, $1)='tempext=`echo $shared_ext | $SED -e '\''s/\([[^()0-9A-Za-z{}]]\)/\\\\\1/g'\''`; templib=`echo $lib | $SED -e "s/\${tempext}\..*/.so/"`; $CC $compiler_flags $predep_objects $libobjs $deplibs $postdep_objects --soname $soname -o \$templib ${wl}-retain-symbols-file,$export_symbols; mv \$templib $lib' + # Commands to make compiler produce verbose output that lists + # what "hidden" libraries, object files and flags are used when + # linking a shared library. + # + # There doesn't appear to be a way to prevent this compiler from + # explicitly linking system object files so we need to strip them + # from the output so that they don't get included in the library + # dependencies. + output_verbose_link_cmd='templist=`$CC $CFLAGS -v conftest.$objext -o libconftest$shared_ext 2>&1 | grep "ld"`; rm -f libconftest$shared_ext; list=""; for z in $templist; do case $z in conftest.$objext) list="$list $z";; *.$objext);; *) list="$list $z";;esac; done; echo $list' + + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}--rpath,$libdir' + _LT_AC_TAGVAR(export_dynamic_flag_spec, $1)='${wl}--export-dynamic' + + # Archives containing C++ object files must be created using + # "CC -Bstatic", where "CC" is the KAI C++ compiler. + _LT_AC_TAGVAR(old_archive_cmds, $1)='$CC -Bstatic -o $oldlib $oldobjs' + ;; + icpc) + # Intel C++ + with_gnu_ld=yes + _LT_AC_TAGVAR(archive_cmds_need_lc, $1)=no + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -shared $compiler_flags $predep_objects $libobjs $deplibs $postdep_objects ${wl}-soname $wl$soname -o $lib' + _LT_AC_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $compiler_flags $predep_objects $libobjs $deplibs $postdep_objects ${wl}-soname $wl$soname ${wl}-retain-symbols-file $wl$export_symbols -o $lib' + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-rpath,$libdir' + _LT_AC_TAGVAR(export_dynamic_flag_spec, $1)='${wl}--export-dynamic' + _LT_AC_TAGVAR(whole_archive_flag_spec, $1)='${wl}--whole-archive$convenience ${wl}--no-whole-archive' + ;; + cxx) + # Compaq C++ + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -shared $compiler_flags $predep_objects $libobjs $deplibs $postdep_objects ${wl}-soname $wl$soname -o $lib' + _LT_AC_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $compiler_flags $predep_objects $libobjs $deplibs $postdep_objects ${wl}-soname $wl$soname -o $lib ${wl}-retain-symbols-file $wl$export_symbols' + + runpath_var=LD_RUN_PATH + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='-rpath $libdir' + _LT_AC_TAGVAR(hardcode_libdir_separator, $1)=: + + # Commands to make compiler produce verbose output that lists + # what "hidden" libraries, object files and flags are used when + # linking a shared library. + # + # There doesn't appear to be a way to prevent this compiler from + # explicitly linking system object files so we need to strip them + # from the output so that they don't get included in the library + # dependencies. + output_verbose_link_cmd='templist=`$CC -shared $CFLAGS -v conftest.$objext 2>&1 | grep "ld"`; templist=`echo $templist | $SED "s/\(^.*ld.*\)\( .*ld .*$\)/\1/"`; list=""; for z in $templist; do case $z in conftest.$objext) list="$list $z";; *.$objext);; *) list="$list $z";;esac; done; echo $list' + ;; + esac + ;; + lynxos*) + # FIXME: insert proper C++ library support + _LT_AC_TAGVAR(ld_shlibs, $1)=no + ;; + m88k*) + # FIXME: insert proper C++ library support + _LT_AC_TAGVAR(ld_shlibs, $1)=no + ;; + mvs*) + case $cc_basename in + cxx) + # FIXME: insert proper C++ library support + _LT_AC_TAGVAR(ld_shlibs, $1)=no + ;; + *) + # FIXME: insert proper C++ library support + _LT_AC_TAGVAR(ld_shlibs, $1)=no + ;; + esac + ;; + netbsd*) + if echo __ELF__ | $CC -E - | grep __ELF__ >/dev/null; then + _LT_AC_TAGVAR(archive_cmds, $1)='$LD -Bshareable -o $lib $predep_objects $libobjs $deplibs $postdep_objects $linker_flags' + wlarc= + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='-R$libdir' + _LT_AC_TAGVAR(hardcode_direct, $1)=yes + _LT_AC_TAGVAR(hardcode_shlibpath_var, $1)=no + fi + # Workaround some broken pre-1.5 toolchains + output_verbose_link_cmd='$CC -shared $CFLAGS -v conftest.$objext 2>&1 | grep conftest.$objext | $SED -e "s:-lgcc -lc -lgcc::"' + ;; + osf3*) + case $cc_basename in + KCC) + # Kuck and Associates, Inc. (KAI) C++ Compiler + + # KCC will only create a shared library if the output file + # ends with ".so" (or ".sl" for HP-UX), so rename the library + # to its proper name (with version) after linking. + _LT_AC_TAGVAR(archive_cmds, $1)='tempext=`echo $shared_ext | $SED -e '\''s/\([[^()0-9A-Za-z{}]]\)/\\\\\1/g'\''`; templib=`echo $lib | $SED -e "s/\${tempext}\..*/.so/"`; $CC $compiler_flags $predep_objects $libobjs $deplibs $postdep_objects --soname $soname -o \$templib; mv \$templib $lib' + + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-rpath,$libdir' + _LT_AC_TAGVAR(hardcode_libdir_separator, $1)=: + + # Archives containing C++ object files must be created using + # "CC -Bstatic", where "CC" is the KAI C++ compiler. + _LT_AC_TAGVAR(old_archive_cmds, $1)='$CC -Bstatic -o $oldlib $oldobjs' + + ;; + RCC) + # Rational C++ 2.4.1 + # FIXME: insert proper C++ library support + _LT_AC_TAGVAR(ld_shlibs, $1)=no + ;; + cxx) + _LT_AC_TAGVAR(allow_undefined_flag, $1)=' ${wl}-expect_unresolved ${wl}\*' + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -shared${allow_undefined_flag} $compiler_flags $predep_objects $libobjs $deplibs $postdep_objects ${wl}-soname $soname `test -n "$verstring" && echo ${wl}-set_version $verstring` -update_registry ${objdir}/so_locations -o $lib' + + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-rpath ${wl}$libdir' + _LT_AC_TAGVAR(hardcode_libdir_separator, $1)=: + + # Commands to make compiler produce verbose output that lists + # what "hidden" libraries, object files and flags are used when + # linking a shared library. + # + # There doesn't appear to be a way to prevent this compiler from + # explicitly linking system object files so we need to strip them + # from the output so that they don't get included in the library + # dependencies. + output_verbose_link_cmd='templist=`$CC -shared $CFLAGS -v conftest.$objext 2>&1 | grep "ld" | grep -v "ld:"`; templist=`echo $templist | $SED "s/\(^.*ld.*\)\( .*ld.*$\)/\1/"`; list=""; for z in $templist; do case $z in conftest.$objext) list="$list $z";; *.$objext);; *) list="$list $z";;esac; done; echo $list' + ;; + *) + if test "$GXX" = yes && test "$with_gnu_ld" = no; then + _LT_AC_TAGVAR(allow_undefined_flag, $1)=' ${wl}-expect_unresolved ${wl}\*' + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -shared -nostdlib ${allow_undefined_flag} $compiler_flags $predep_objects $libobjs $deplibs $postdep_objects ${wl}-soname ${wl}$soname `test -n "$verstring" && echo ${wl}-set_version ${wl}$verstring` ${wl}-update_registry ${wl}${objdir}/so_locations -o $lib' + + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-rpath ${wl}$libdir' + _LT_AC_TAGVAR(hardcode_libdir_separator, $1)=: + + # Commands to make compiler produce verbose output that lists + # what "hidden" libraries, object files and flags are used when + # linking a shared library. + output_verbose_link_cmd='$CC -shared $CFLAGS -v conftest.$objext 2>&1 | grep "\-L"' + + else + # FIXME: insert proper C++ library support + _LT_AC_TAGVAR(ld_shlibs, $1)=no + fi + ;; + esac + ;; + osf4* | osf5*) + case $cc_basename in + KCC) + # Kuck and Associates, Inc. (KAI) C++ Compiler + + # KCC will only create a shared library if the output file + # ends with ".so" (or ".sl" for HP-UX), so rename the library + # to its proper name (with version) after linking. + _LT_AC_TAGVAR(archive_cmds, $1)='tempext=`echo $shared_ext | $SED -e '\''s/\([[^()0-9A-Za-z{}]]\)/\\\\\1/g'\''`; templib=`echo $lib | $SED -e "s/\${tempext}\..*/.so/"`; $CC $compiler_flags $predep_objects $libobjs $deplibs $postdep_objects --soname $soname -o \$templib; mv \$templib $lib' + + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-rpath,$libdir' + _LT_AC_TAGVAR(hardcode_libdir_separator, $1)=: + + # Archives containing C++ object files must be created using + # the KAI C++ compiler. + _LT_AC_TAGVAR(old_archive_cmds, $1)='$CC -o $oldlib $oldobjs' + ;; + RCC) + # Rational C++ 2.4.1 + # FIXME: insert proper C++ library support + _LT_AC_TAGVAR(ld_shlibs, $1)=no + ;; + cxx) + _LT_AC_TAGVAR(allow_undefined_flag, $1)=' -expect_unresolved \*' + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -shared${allow_undefined_flag} $compiler_flags $predep_objects $libobjs $deplibs $postdep_objects -msym -soname $soname `test -n "$verstring" && echo -set_version $verstring` -update_registry ${objdir}/so_locations -o $lib' + _LT_AC_TAGVAR(archive_expsym_cmds, $1)='for i in `cat $export_symbols`; do printf "%s %s\\n" -exported_symbol "\$i" >> $lib.exp; done~ + echo "-hidden">> $lib.exp~ + $CC -shared$allow_undefined_flag $compiler_flags $predep_objects $libobjs $deplibs $postdep_objects -msym -soname $soname -Wl,-input -Wl,$lib.exp `test -n "$verstring" && echo -set_version $verstring` -update_registry $objdir/so_locations -o $lib~ + $rm $lib.exp' + + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='-rpath $libdir' + _LT_AC_TAGVAR(hardcode_libdir_separator, $1)=: + + # Commands to make compiler produce verbose output that lists + # what "hidden" libraries, object files and flags are used when + # linking a shared library. + # + # There doesn't appear to be a way to prevent this compiler from + # explicitly linking system object files so we need to strip them + # from the output so that they don't get included in the library + # dependencies. + output_verbose_link_cmd='templist=`$CC -shared $CFLAGS -v conftest.$objext 2>&1 | grep "ld" | grep -v "ld:"`; templist=`echo $templist | $SED "s/\(^.*ld.*\)\( .*ld.*$\)/\1/"`; list=""; for z in $templist; do case $z in conftest.$objext) list="$list $z";; *.$objext);; *) list="$list $z";;esac; done; echo $list' + ;; + *) + if test "$GXX" = yes && test "$with_gnu_ld" = no; then + _LT_AC_TAGVAR(allow_undefined_flag, $1)=' ${wl}-expect_unresolved ${wl}\*' + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -shared -nostdlib ${allow_undefined_flag} $compiler_flags $predep_objects $libobjs $deplibs $postdep_objects ${wl}-msym ${wl}-soname ${wl}$soname `test -n "$verstring" && echo ${wl}-set_version ${wl}$verstring` ${wl}-update_registry ${wl}${objdir}/so_locations -o $lib' + + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-rpath ${wl}$libdir' + _LT_AC_TAGVAR(hardcode_libdir_separator, $1)=: + + # Commands to make compiler produce verbose output that lists + # what "hidden" libraries, object files and flags are used when + # linking a shared library. + output_verbose_link_cmd='$CC -shared $CFLAGS -v conftest.$objext 2>&1 | grep "\-L"' + + else + # FIXME: insert proper C++ library support + _LT_AC_TAGVAR(ld_shlibs, $1)=no + fi + ;; + esac + ;; + psos*) + # FIXME: insert proper C++ library support + _LT_AC_TAGVAR(ld_shlibs, $1)=no + ;; + sco*) + _LT_AC_TAGVAR(archive_cmds_need_lc, $1)=no + case $cc_basename in + CC) + # FIXME: insert proper C++ library support + _LT_AC_TAGVAR(ld_shlibs, $1)=no + ;; + *) + # FIXME: insert proper C++ library support + _LT_AC_TAGVAR(ld_shlibs, $1)=no + ;; + esac + ;; + sunos4*) + case $cc_basename in + CC) + # Sun C++ 4.x + # FIXME: insert proper C++ library support + _LT_AC_TAGVAR(ld_shlibs, $1)=no + ;; + lcc) + # Lucid + # FIXME: insert proper C++ library support + _LT_AC_TAGVAR(ld_shlibs, $1)=no + ;; + *) + # FIXME: insert proper C++ library support + _LT_AC_TAGVAR(ld_shlibs, $1)=no + ;; + esac + ;; + solaris*) + case $cc_basename in + CC) + # Sun C++ 4.2, 5.x and Centerline C++ + _LT_AC_TAGVAR(no_undefined_flag, $1)=' -zdefs' + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -G${allow_undefined_flag} -nolib -h$soname -o $lib $compiler_flags $predep_objects $libobjs $deplibs $postdep_objects' + _LT_AC_TAGVAR(archive_expsym_cmds, $1)='$echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~$echo "local: *; };" >> $lib.exp~ + $CC -G${allow_undefined_flag} -nolib ${wl}-M ${wl}$lib.exp -h$soname -o $lib $compiler_flags $predep_objects $libobjs $deplibs $postdep_objects~$rm $lib.exp' + + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='-R$libdir' + _LT_AC_TAGVAR(hardcode_shlibpath_var, $1)=no + case $host_os in + solaris2.[0-5] | solaris2.[0-5].*) ;; + *) + # The C++ compiler is used as linker so we must use $wl + # flag to pass the commands to the underlying system + # linker. + # Supported since Solaris 2.6 (maybe 2.5.1?) + _LT_AC_TAGVAR(whole_archive_flag_spec, $1)='${wl}-z ${wl}allextract$convenience ${wl}-z ${wl}defaultextract' + ;; + esac + _LT_AC_TAGVAR(link_all_deplibs, $1)=yes + + # Commands to make compiler produce verbose output that lists + # what "hidden" libraries, object files and flags are used when + # linking a shared library. + # + # There doesn't appear to be a way to prevent this compiler from + # explicitly linking system object files so we need to strip them + # from the output so that they don't get included in the library + # dependencies. + output_verbose_link_cmd='templist=`$CC -G $CFLAGS -v conftest.$objext 2>&1 | grep "\-[[LR]]"`; list=""; for z in $templist; do case $z in conftest.$objext) list="$list $z";; *.$objext);; *) list="$list $z";;esac; done; echo $list' + + # Archives containing C++ object files must be created using + # "CC -xar", where "CC" is the Sun C++ compiler. This is + # necessary to make sure instantiated templates are included + # in the archive. + _LT_AC_TAGVAR(old_archive_cmds, $1)='$CC -xar -o $oldlib $oldobjs' + ;; + gcx) + # Green Hills C++ Compiler + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -shared $compiler_flags $predep_objects $libobjs $deplibs $postdep_objects ${wl}-h $wl$soname -o $lib' + + # The C++ compiler must be used to create the archive. + _LT_AC_TAGVAR(old_archive_cmds, $1)='$CC $LDFLAGS -archive -o $oldlib $oldobjs' + ;; + *) + # GNU C++ compiler with Solaris linker + if test "$GXX" = yes && test "$with_gnu_ld" = no; then + _LT_AC_TAGVAR(no_undefined_flag, $1)=' ${wl}-z ${wl}defs' + if $CC --version | grep -v '^2\.7' > /dev/null; then + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -shared -nostdlib $LDFLAGS $compiler_flags $predep_objects $libobjs $deplibs $postdep_objects ${wl}-h $wl$soname -o $lib' + _LT_AC_TAGVAR(archive_expsym_cmds, $1)='$echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~$echo "local: *; };" >> $lib.exp~ + $CC -shared -nostdlib ${wl}-M $wl$lib.exp -o $lib $compiler_flags $predep_objects $libobjs $deplibs $postdep_objects~$rm $lib.exp' + + # Commands to make compiler produce verbose output that lists + # what "hidden" libraries, object files and flags are used when + # linking a shared library. + output_verbose_link_cmd="$CC -shared $CFLAGS -v conftest.$objext 2>&1 | grep \"\-L\"" + else + # g++ 2.7 appears to require `-G' NOT `-shared' on this + # platform. + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -G -nostdlib $LDFLAGS $compiler_flags $predep_objects $libobjs $deplibs $postdep_objects ${wl}-h $wl$soname -o $lib' + _LT_AC_TAGVAR(archive_expsym_cmds, $1)='$echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~$echo "local: *; };" >> $lib.exp~ + $CC -G -nostdlib ${wl}-M $wl$lib.exp -o $lib $compiler_flags $predep_objects $libobjs $deplibs $postdep_objects~$rm $lib.exp' + + # Commands to make compiler produce verbose output that lists + # what "hidden" libraries, object files and flags are used when + # linking a shared library. + output_verbose_link_cmd="$CC -G $CFLAGS -v conftest.$objext 2>&1 | grep \"\-L\"" + fi + + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-R $wl$libdir' + fi + ;; + esac + ;; + sysv5OpenUNIX8* | sysv5UnixWare7* | sysv5uw[[78]]* | unixware7*) + _LT_AC_TAGVAR(archive_cmds_need_lc, $1)=no + ;; + tandem*) + case $cc_basename in + NCC) + # NonStop-UX NCC 3.20 + # FIXME: insert proper C++ library support + _LT_AC_TAGVAR(ld_shlibs, $1)=no + ;; + *) + # FIXME: insert proper C++ library support + _LT_AC_TAGVAR(ld_shlibs, $1)=no + ;; + esac + ;; + vxworks*) + # FIXME: insert proper C++ library support + _LT_AC_TAGVAR(ld_shlibs, $1)=no + ;; + *) + # FIXME: insert proper C++ library support + _LT_AC_TAGVAR(ld_shlibs, $1)=no + ;; +esac +AC_MSG_RESULT([$_LT_AC_TAGVAR(ld_shlibs, $1)]) +test "$_LT_AC_TAGVAR(ld_shlibs, $1)" = no && can_build_shared=no + +_LT_AC_TAGVAR(GCC, $1)="$GXX" +_LT_AC_TAGVAR(LD, $1)="$LD" + +## CAVEAT EMPTOR: +## There is no encapsulation within the following macros, do not change +## the running order or otherwise move them around unless you know exactly +## what you are doing... +AC_LIBTOOL_POSTDEP_PREDEP($1) +AC_LIBTOOL_PROG_COMPILER_PIC($1) +AC_LIBTOOL_PROG_CC_C_O($1) +AC_LIBTOOL_SYS_HARD_LINK_LOCKS($1) +AC_LIBTOOL_PROG_LD_SHLIBS($1) +AC_LIBTOOL_SYS_DYNAMIC_LINKER($1) +AC_LIBTOOL_PROG_LD_HARDCODE_LIBPATH($1) +AC_LIBTOOL_SYS_LIB_STRIP +AC_LIBTOOL_DLOPEN_SELF($1) + +AC_LIBTOOL_CONFIG($1) + +AC_LANG_POP +CC=$lt_save_CC +LDCXX=$LD +LD=$lt_save_LD +GCC=$lt_save_GCC +with_gnu_ldcxx=$with_gnu_ld +with_gnu_ld=$lt_save_with_gnu_ld +lt_cv_path_LDCXX=$lt_cv_path_LD +lt_cv_path_LD=$lt_save_path_LD +lt_cv_prog_gnu_ldcxx=$lt_cv_prog_gnu_ld +lt_cv_prog_gnu_ld=$lt_save_with_gnu_ld +])# AC_LIBTOOL_LANG_CXX_CONFIG + +# AC_LIBTOOL_POSTDEP_PREDEP([TAGNAME]) +# ------------------------ +# Figure out "hidden" library dependencies from verbose +# compiler output when linking a shared library. +# Parse the compiler output and extract the necessary +# objects, libraries and library flags. +AC_DEFUN([AC_LIBTOOL_POSTDEP_PREDEP],[ +dnl we can't use the lt_simple_compile_test_code here, +dnl because it contains code intended for an executable, +dnl not a library. It's possible we should let each +dnl tag define a new lt_????_link_test_code variable, +dnl but it's only used here... +ifelse([$1],[],[cat > conftest.$ac_ext < conftest.$ac_ext < conftest.$ac_ext < conftest.$ac_ext <> "$cfgfile" +ifelse([$1], [], +[#! $SHELL + +# `$echo "$cfgfile" | sed 's%^.*/%%'` - Provide generalized library-building support services. +# Generated automatically by $PROGRAM (GNU $PACKAGE $VERSION$TIMESTAMP) +# NOTE: Changes made to this file will be lost: look at ltmain.sh. +# +# Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001 +# Free Software Foundation, Inc. +# +# This file is part of GNU Libtool: +# Originally by Gordon Matzigkeit , 1996 +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU 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. +# +# As a special exception to the GNU General Public License, if you +# distribute this file as part of a program that contains a +# configuration script generated by Autoconf, you may include it under +# the same distribution terms that you use for the rest of that program. + +# A sed program that does not truncate output. +SED=$lt_SED + +# Sed that helps us avoid accidentally triggering echo(1) options like -n. +Xsed="$SED -e s/^X//" + +# The HP-UX ksh and POSIX shell print the target directory to stdout +# if CDPATH is set. +if test "X\${CDPATH+set}" = Xset; then CDPATH=:; export CDPATH; fi + +# The names of the tagged configurations supported by this script. +available_tags= + +# ### BEGIN LIBTOOL CONFIG], +[# ### BEGIN LIBTOOL TAG CONFIG: $tagname]) + +# Libtool was configured on host `(hostname || uname -n) 2>/dev/null | sed 1q`: + +# Shell to use when invoking shell scripts. +SHELL=$lt_SHELL + +# Whether or not to build shared libraries. +build_libtool_libs=$enable_shared + +# Whether or not to build static libraries. +build_old_libs=$enable_static + +# Whether or not to add -lc for building shared libraries. +build_libtool_need_lc=$_LT_AC_TAGVAR(archive_cmds_need_lc, $1) + +# Whether or not to disallow shared libs when runtime libs are static +allow_libtool_libs_with_static_runtimes=$_LT_AC_TAGVAR(enable_shared_with_static_runtimes, $1) + +# Whether or not to optimize for fast installation. +fast_install=$enable_fast_install + +# The host system. +host_alias=$host_alias +host=$host + +# An echo program that does not interpret backslashes. +echo=$lt_echo + +# The archiver. +AR=$lt_AR +AR_FLAGS=$lt_AR_FLAGS + +# A C compiler. +LTCC=$lt_LTCC + +# A language-specific compiler. +CC=$lt_[]_LT_AC_TAGVAR(compiler, $1) + +# Is the compiler the GNU C compiler? +with_gcc=$_LT_AC_TAGVAR(GCC, $1) + +# An ERE matcher. +EGREP=$lt_EGREP + +# The linker used to build libraries. +LD=$lt_[]_LT_AC_TAGVAR(LD, $1) + +# Whether we need hard or soft links. +LN_S=$lt_LN_S + +# A BSD-compatible nm program. +NM=$lt_NM + +# A symbol stripping program +STRIP=$STRIP + +# Used to examine libraries when file_magic_cmd begins "file" +MAGIC_CMD=$MAGIC_CMD + +# Used on cygwin: DLL creation program. +DLLTOOL="$DLLTOOL" + +# Used on cygwin: object dumper. +OBJDUMP="$OBJDUMP" + +# Used on cygwin: assembler. +AS="$AS" + +# The name of the directory that contains temporary libtool files. +objdir=$objdir + +# How to create reloadable object files. +reload_flag=$lt_reload_flag +reload_cmds=$lt_reload_cmds + +# How to pass a linker flag through the compiler. +wl=$lt_[]_LT_AC_TAGVAR(lt_prog_compiler_wl, $1) + +# Object file suffix (normally "o"). +objext="$ac_objext" + +# Old archive suffix (normally "a"). +libext="$libext" + +# Shared library suffix (normally ".so"). +shrext='$shrext' + +# Executable file suffix (normally ""). +exeext="$exeext" + +# Additional compiler flags for building library objects. +pic_flag=$lt_[]_LT_AC_TAGVAR(lt_prog_compiler_pic, $1) +pic_mode=$pic_mode + +# What is the maximum length of a command? +max_cmd_len=$lt_cv_sys_max_cmd_len + +# Does compiler simultaneously support -c and -o options? +compiler_c_o=$lt_[]_LT_AC_TAGVAR(lt_cv_prog_compiler_c_o, $1) + +# Must we lock files when doing compilation ? +need_locks=$lt_need_locks + +# Do we need the lib prefix for modules? +need_lib_prefix=$need_lib_prefix + +# Do we need a version for libraries? +need_version=$need_version + +# Whether dlopen is supported. +dlopen_support=$enable_dlopen + +# Whether dlopen of programs is supported. +dlopen_self=$enable_dlopen_self + +# Whether dlopen of statically linked programs is supported. +dlopen_self_static=$enable_dlopen_self_static + +# Compiler flag to prevent dynamic linking. +link_static_flag=$lt_[]_LT_AC_TAGVAR(lt_prog_compiler_static, $1) + +# Compiler flag to turn off builtin functions. +no_builtin_flag=$lt_[]_LT_AC_TAGVAR(lt_prog_compiler_no_builtin_flag, $1) + +# Compiler flag to allow reflexive dlopens. +export_dynamic_flag_spec=$lt_[]_LT_AC_TAGVAR(export_dynamic_flag_spec, $1) + +# Compiler flag to generate shared objects directly from archives. +whole_archive_flag_spec=$lt_[]_LT_AC_TAGVAR(whole_archive_flag_spec, $1) + +# Compiler flag to generate thread-safe objects. +thread_safe_flag_spec=$lt_[]_LT_AC_TAGVAR(thread_safe_flag_spec, $1) + +# Library versioning type. +version_type=$version_type + +# Format of library name prefix. +libname_spec=$lt_libname_spec + +# List of archive names. First name is the real one, the rest are links. +# The last name is the one that the linker finds with -lNAME. +library_names_spec=$lt_library_names_spec + +# The coded name of the library, if different from the real name. +soname_spec=$lt_soname_spec + +# Commands used to build and install an old-style archive. +RANLIB=$lt_RANLIB +old_archive_cmds=$lt_[]_LT_AC_TAGVAR(old_archive_cmds, $1) +old_postinstall_cmds=$lt_old_postinstall_cmds +old_postuninstall_cmds=$lt_old_postuninstall_cmds + +# Create an old-style archive from a shared archive. +old_archive_from_new_cmds=$lt_[]_LT_AC_TAGVAR(old_archive_from_new_cmds, $1) + +# Create a temporary old-style archive to link instead of a shared archive. +old_archive_from_expsyms_cmds=$lt_[]_LT_AC_TAGVAR(old_archive_from_expsyms_cmds, $1) + +# Commands used to build and install a shared archive. +archive_cmds=$lt_[]_LT_AC_TAGVAR(archive_cmds, $1) +archive_expsym_cmds=$lt_[]_LT_AC_TAGVAR(archive_expsym_cmds, $1) +postinstall_cmds=$lt_postinstall_cmds +postuninstall_cmds=$lt_postuninstall_cmds + +# Commands used to build a loadable module (assumed same as above if empty) +module_cmds=$lt_[]_LT_AC_TAGVAR(module_cmds, $1) +module_expsym_cmds=$lt_[]_LT_AC_TAGVAR(module_expsym_cmds, $1) + +# Commands to strip libraries. +old_striplib=$lt_old_striplib +striplib=$lt_striplib + +# Dependencies to place before the objects being linked to create a +# shared library. +predep_objects=$lt_[]_LT_AC_TAGVAR(predep_objects, $1) + +# Dependencies to place after the objects being linked to create a +# shared library. +postdep_objects=$lt_[]_LT_AC_TAGVAR(postdep_objects, $1) + +# Dependencies to place before the objects being linked to create a +# shared library. +predeps=$lt_[]_LT_AC_TAGVAR(predeps, $1) + +# Dependencies to place after the objects being linked to create a +# shared library. +postdeps=$lt_[]_LT_AC_TAGVAR(postdeps, $1) + +# The library search path used internally by the compiler when linking +# a shared library. +compiler_lib_search_path=$lt_[]_LT_AC_TAGVAR(compiler_lib_search_path, $1) + +# Method to check whether dependent libraries are shared objects. +deplibs_check_method=$lt_deplibs_check_method + +# Command to use when deplibs_check_method == file_magic. +file_magic_cmd=$lt_file_magic_cmd + +# Flag that allows shared libraries with undefined symbols to be built. +allow_undefined_flag=$lt_[]_LT_AC_TAGVAR(allow_undefined_flag, $1) + +# Flag that forces no undefined symbols. +no_undefined_flag=$lt_[]_LT_AC_TAGVAR(no_undefined_flag, $1) + +# Commands used to finish a libtool library installation in a directory. +finish_cmds=$lt_finish_cmds + +# Same as above, but a single script fragment to be evaled but not shown. +finish_eval=$lt_finish_eval + +# Take the output of nm and produce a listing of raw symbols and C names. +global_symbol_pipe=$lt_lt_cv_sys_global_symbol_pipe + +# Transform the output of nm in a proper C declaration +global_symbol_to_cdecl=$lt_lt_cv_sys_global_symbol_to_cdecl + +# Transform the output of nm in a C name address pair +global_symbol_to_c_name_address=$lt_lt_cv_sys_global_symbol_to_c_name_address + +# This is the shared library runtime path variable. +runpath_var=$runpath_var + +# This is the shared library path variable. +shlibpath_var=$shlibpath_var + +# Is shlibpath searched before the hard-coded library search path? +shlibpath_overrides_runpath=$shlibpath_overrides_runpath + +# How to hardcode a shared library path into an executable. +hardcode_action=$_LT_AC_TAGVAR(hardcode_action, $1) + +# Whether we should hardcode library paths into libraries. +hardcode_into_libs=$hardcode_into_libs + +# Flag to hardcode \$libdir into a binary during linking. +# This must work even if \$libdir does not exist. +hardcode_libdir_flag_spec=$lt_[]_LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1) + +# If ld is used when linking, flag to hardcode \$libdir into +# a binary during linking. This must work even if \$libdir does +# not exist. +hardcode_libdir_flag_spec_ld=$lt_[]_LT_AC_TAGVAR(hardcode_libdir_flag_spec_ld, $1) + +# Whether we need a single -rpath flag with a separated argument. +hardcode_libdir_separator=$lt_[]_LT_AC_TAGVAR(hardcode_libdir_separator, $1) + +# Set to yes if using DIR/libNAME${shared_ext} during linking hardcodes DIR into the +# resulting binary. +hardcode_direct=$_LT_AC_TAGVAR(hardcode_direct, $1) + +# Set to yes if using the -LDIR flag during linking hardcodes DIR into the +# resulting binary. +hardcode_minus_L=$_LT_AC_TAGVAR(hardcode_minus_L, $1) + +# Set to yes if using SHLIBPATH_VAR=DIR during linking hardcodes DIR into +# the resulting binary. +hardcode_shlibpath_var=$_LT_AC_TAGVAR(hardcode_shlibpath_var, $1) + +# Set to yes if building a shared library automatically hardcodes DIR into the library +# and all subsequent libraries and executables linked against it. +hardcode_automatic=$_LT_AC_TAGVAR(hardcode_automatic, $1) + +# Variables whose values should be saved in libtool wrapper scripts and +# restored at relink time. +variables_saved_for_relink="$variables_saved_for_relink" + +# Whether libtool must link a program against all its dependency libraries. +link_all_deplibs=$_LT_AC_TAGVAR(link_all_deplibs, $1) + +# Compile-time system search path for libraries +sys_lib_search_path_spec=$lt_sys_lib_search_path_spec + +# Run-time system search path for libraries +sys_lib_dlsearch_path_spec=$lt_sys_lib_dlsearch_path_spec + +# Fix the shell variable \$srcfile for the compiler. +fix_srcfile_path="$_LT_AC_TAGVAR(fix_srcfile_path, $1)" + +# Set to yes if exported symbols are required. +always_export_symbols=$_LT_AC_TAGVAR(always_export_symbols, $1) + +# The commands to list exported symbols. +export_symbols_cmds=$lt_[]_LT_AC_TAGVAR(export_symbols_cmds, $1) + +# The commands to extract the exported symbol list from a shared archive. +extract_expsyms_cmds=$lt_extract_expsyms_cmds + +# Symbols that should not be listed in the preloaded symbols. +exclude_expsyms=$lt_[]_LT_AC_TAGVAR(exclude_expsyms, $1) + +# Symbols that must always be exported. +include_expsyms=$lt_[]_LT_AC_TAGVAR(include_expsyms, $1) + +ifelse([$1],[], +[# ### END LIBTOOL CONFIG], +[# ### END LIBTOOL TAG CONFIG: $tagname]) + +__EOF__ + +ifelse([$1],[], [ + case $host_os in + aix3*) + cat <<\EOF >> "$cfgfile" + +# AIX sometimes has problems with the GCC collect2 program. For some +# reason, if we set the COLLECT_NAMES environment variable, the problems +# vanish in a puff of smoke. +if test "X${COLLECT_NAMES+set}" != Xset; then + COLLECT_NAMES= + export COLLECT_NAMES +fi +EOF + ;; + esac + + # We use sed instead of cat because bash on DJGPP gets confused if + # if finds mixed CR/LF and LF-only lines. Since sed operates in + # text mode, it properly converts lines to CR/LF. This bash problem + # is reportedly fixed, but why not run on old versions too? + sed '$q' "$ltmain" >> "$cfgfile" || (rm -f "$cfgfile"; exit 1) + + mv -f "$cfgfile" "$ofile" || \ + (rm -f "$ofile" && cp "$cfgfile" "$ofile" && rm -f "$cfgfile") + chmod +x "$ofile" +]) +else + # If there is no Makefile yet, we rely on a make rule to execute + # `config.status --recheck' to rerun these tests and create the + # libtool script then. + test -f Makefile && make "$ltmain" +fi +])# AC_LIBTOOL_CONFIG + + +# AC_LIBTOOL_PROG_COMPILER_NO_RTTI([TAGNAME]) +# ------------------------------------------- +AC_DEFUN([AC_LIBTOOL_PROG_COMPILER_NO_RTTI], +[AC_REQUIRE([_LT_AC_SYS_COMPILER])dnl + +_LT_AC_TAGVAR(lt_prog_compiler_no_builtin_flag, $1)= + +if test "$GCC" = yes; then + _LT_AC_TAGVAR(lt_prog_compiler_no_builtin_flag, $1)=' -fno-builtin' + + AC_LIBTOOL_COMPILER_OPTION([if $compiler supports -fno-rtti -fno-exceptions], + lt_cv_prog_compiler_rtti_exceptions, + [-fno-rtti -fno-exceptions], [], + [_LT_AC_TAGVAR(lt_prog_compiler_no_builtin_flag, $1)="$_LT_AC_TAGVAR(lt_prog_compiler_no_builtin_flag, $1) -fno-rtti -fno-exceptions"]) +fi +])# AC_LIBTOOL_PROG_COMPILER_NO_RTTI + + +# AC_LIBTOOL_SYS_GLOBAL_SYMBOL_PIPE +# --------------------------------- +AC_DEFUN([AC_LIBTOOL_SYS_GLOBAL_SYMBOL_PIPE], +[AC_REQUIRE([AC_CANONICAL_HOST]) +AC_REQUIRE([AC_PROG_NM]) +AC_REQUIRE([AC_OBJEXT]) +# Check for command to grab the raw symbol name followed by C symbol from nm. +AC_MSG_CHECKING([command to parse $NM output from $compiler object]) +AC_CACHE_VAL([lt_cv_sys_global_symbol_pipe], +[ +# These are sane defaults that work on at least a few old systems. +# [They come from Ultrix. What could be older than Ultrix?!! ;)] + +# Character class describing NM global symbol codes. +symcode='[[BCDEGRST]]' + +# Regexp to match symbols that can be accessed directly from C. +sympat='\([[_A-Za-z]][[_A-Za-z0-9]]*\)' + +# Transform the above into a raw symbol and a C symbol. +symxfrm='\1 \2\3 \3' + +# Transform an extracted symbol line into a proper C declaration +lt_cv_sys_global_symbol_to_cdecl="sed -n -e 's/^. .* \(.*\)$/extern int \1;/p'" + +# Transform an extracted symbol line into symbol name and symbol address +lt_cv_sys_global_symbol_to_c_name_address="sed -n -e 's/^: \([[^ ]]*\) $/ {\\\"\1\\\", (lt_ptr) 0},/p' -e 's/^$symcode \([[^ ]]*\) \([[^ ]]*\)$/ {\"\2\", (lt_ptr) \&\2},/p'" + +# Define system-specific variables. +case $host_os in +aix*) + symcode='[[BCDT]]' + ;; +cygwin* | mingw* | pw32*) + symcode='[[ABCDGISTW]]' + ;; +hpux*) # Its linker distinguishes data from code symbols + if test "$host_cpu" = ia64; then + symcode='[[ABCDEGRST]]' + fi + lt_cv_sys_global_symbol_to_cdecl="sed -n -e 's/^T .* \(.*\)$/extern int \1();/p' -e 's/^$symcode* .* \(.*\)$/extern char \1;/p'" + lt_cv_sys_global_symbol_to_c_name_address="sed -n -e 's/^: \([[^ ]]*\) $/ {\\\"\1\\\", (lt_ptr) 0},/p' -e 's/^$symcode* \([[^ ]]*\) \([[^ ]]*\)$/ {\"\2\", (lt_ptr) \&\2},/p'" + ;; +irix* | nonstopux*) + symcode='[[BCDEGRST]]' + ;; +osf*) + symcode='[[BCDEGQRST]]' + ;; +solaris* | sysv5*) + symcode='[[BDT]]' + ;; +sysv4) + symcode='[[DFNSTU]]' + ;; +esac + +# Handle CRLF in mingw tool chain +opt_cr= +case $build_os in +mingw*) + opt_cr=`echo 'x\{0,1\}' | tr x '\015'` # option cr in regexp + ;; +esac + +# If we're using GNU nm, then use its standard symbol codes. +case `$NM -V 2>&1` in +*GNU* | *'with BFD'*) + symcode='[[ABCDGISTW]]' ;; +esac + +# Try without a prefix undercore, then with it. +for ac_symprfx in "" "_"; do + + # Write the raw and C identifiers. + lt_cv_sys_global_symbol_pipe="sed -n -e 's/^.*[[ ]]\($symcode$symcode*\)[[ ]][[ ]]*\($ac_symprfx\)$sympat$opt_cr$/$symxfrm/p'" + + # Check to see that the pipe works correctly. + pipe_works=no + + rm -f conftest* + cat > conftest.$ac_ext < $nlist) && test -s "$nlist"; then + # Try sorting and uniquifying the output. + if sort "$nlist" | uniq > "$nlist"T; then + mv -f "$nlist"T "$nlist" + else + rm -f "$nlist"T + fi + + # Make sure that we snagged all the symbols we need. + if grep ' nm_test_var$' "$nlist" >/dev/null; then + if grep ' nm_test_func$' "$nlist" >/dev/null; then + cat < conftest.$ac_ext +#ifdef __cplusplus +extern "C" { +#endif + +EOF + # Now generate the symbol file. + eval "$lt_cv_sys_global_symbol_to_cdecl"' < "$nlist" | grep -v main >> conftest.$ac_ext' + + cat <> conftest.$ac_ext +#if defined (__STDC__) && __STDC__ +# define lt_ptr_t void * +#else +# define lt_ptr_t char * +# define const +#endif + +/* The mapping between symbol names and symbols. */ +const struct { + const char *name; + lt_ptr_t address; +} +lt_preloaded_symbols[[]] = +{ +EOF + $SED "s/^$symcode$symcode* \(.*\) \(.*\)$/ {\"\2\", (lt_ptr_t) \&\2},/" < "$nlist" | grep -v main >> conftest.$ac_ext + cat <<\EOF >> conftest.$ac_ext + {0, (lt_ptr_t) 0} +}; + +#ifdef __cplusplus +} +#endif +EOF + # Now try linking the two files. + mv conftest.$ac_objext conftstm.$ac_objext + lt_save_LIBS="$LIBS" + lt_save_CFLAGS="$CFLAGS" + LIBS="conftstm.$ac_objext" + CFLAGS="$CFLAGS$_LT_AC_TAGVAR(lt_prog_compiler_no_builtin_flag, $1)" + if AC_TRY_EVAL(ac_link) && test -s conftest${ac_exeext}; then + pipe_works=yes + fi + LIBS="$lt_save_LIBS" + CFLAGS="$lt_save_CFLAGS" + else + echo "cannot find nm_test_func in $nlist" >&AS_MESSAGE_LOG_FD + fi + else + echo "cannot find nm_test_var in $nlist" >&AS_MESSAGE_LOG_FD + fi + else + echo "cannot run $lt_cv_sys_global_symbol_pipe" >&AS_MESSAGE_LOG_FD + fi + else + echo "$progname: failed program was:" >&AS_MESSAGE_LOG_FD + cat conftest.$ac_ext >&5 + fi + rm -f conftest* conftst* + + # Do not use the global_symbol_pipe unless it works. + if test "$pipe_works" = yes; then + break + else + lt_cv_sys_global_symbol_pipe= + fi +done +]) +if test -z "$lt_cv_sys_global_symbol_pipe"; then + lt_cv_sys_global_symbol_to_cdecl= +fi +if test -z "$lt_cv_sys_global_symbol_pipe$lt_cv_sys_global_symbol_to_cdecl"; then + AC_MSG_RESULT(failed) +else + AC_MSG_RESULT(ok) +fi +]) # AC_LIBTOOL_SYS_GLOBAL_SYMBOL_PIPE + + +# AC_LIBTOOL_PROG_COMPILER_PIC([TAGNAME]) +# --------------------------------------- +AC_DEFUN([AC_LIBTOOL_PROG_COMPILER_PIC], +[_LT_AC_TAGVAR(lt_prog_compiler_wl, $1)= +_LT_AC_TAGVAR(lt_prog_compiler_pic, $1)= +_LT_AC_TAGVAR(lt_prog_compiler_static, $1)= + +AC_MSG_CHECKING([for $compiler option to produce PIC]) + ifelse([$1],[CXX],[ + # C++ specific cases for pic, static, wl, etc. + if test "$GXX" = yes; then + _LT_AC_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_AC_TAGVAR(lt_prog_compiler_static, $1)='-static' + + case $host_os in + aix*) + # All AIX code is PIC. + if test "$host_cpu" = ia64; then + # AIX 5 now supports IA64 processor + _LT_AC_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + fi + ;; + amigaos*) + # FIXME: we need at least 68020 code to build shared libraries, but + # adding the `-m68020' flag to GCC prevents building anything better, + # like `-m68040'. + _LT_AC_TAGVAR(lt_prog_compiler_pic, $1)='-m68020 -resident32 -malways-restore-a4' + ;; + beos* | cygwin* | irix5* | irix6* | nonstopux* | osf3* | osf4* | osf5*) + # PIC is the default for these OSes. + ;; + mingw* | os2* | pw32*) + # This hack is so that the source file can tell whether it is being + # built for inclusion in a dll (and should export symbols for example). + _LT_AC_TAGVAR(lt_prog_compiler_pic, $1)='-DDLL_EXPORT' + ;; + darwin* | rhapsody*) + # PIC is the default on this platform + # Common symbols not allowed in MH_DYLIB files + _LT_AC_TAGVAR(lt_prog_compiler_pic, $1)='-fno-common' + ;; + *djgpp*) + # DJGPP does not support shared libraries at all + _LT_AC_TAGVAR(lt_prog_compiler_pic, $1)= + ;; + sysv4*MP*) + if test -d /usr/nec; then + _LT_AC_TAGVAR(lt_prog_compiler_pic, $1)=-Kconform_pic + fi + ;; + hpux*) + # PIC is the default for IA64 HP-UX and 64-bit HP-UX, but + # not for PA HP-UX. + case "$host_cpu" in + hppa*64*|ia64*) + ;; + *) + _LT_AC_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC' + ;; + esac + ;; + *) + _LT_AC_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC' + ;; + esac + else + case $host_os in + aix4* | aix5*) + # All AIX code is PIC. + if test "$host_cpu" = ia64; then + # AIX 5 now supports IA64 processor + _LT_AC_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + else + _LT_AC_TAGVAR(lt_prog_compiler_static, $1)='-bnso -bI:/lib/syscalls.exp' + fi + ;; + chorus*) + case $cc_basename in + cxch68) + # Green Hills C++ Compiler + # _LT_AC_TAGVAR(lt_prog_compiler_static, $1)="--no_auto_instantiation -u __main -u __premain -u _abort -r $COOL_DIR/lib/libOrb.a $MVME_DIR/lib/CC/libC.a $MVME_DIR/lib/classix/libcx.s.a" + ;; + esac + ;; + dgux*) + case $cc_basename in + ec++) + _LT_AC_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' + ;; + ghcx) + # Green Hills C++ Compiler + _LT_AC_TAGVAR(lt_prog_compiler_pic, $1)='-pic' + ;; + *) + ;; + esac + ;; + freebsd* | kfreebsd*-gnu) + # FreeBSD uses GNU C++ + ;; + hpux9* | hpux10* | hpux11*) + case $cc_basename in + CC) + _LT_AC_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_AC_TAGVAR(lt_prog_compiler_static, $1)="${ac_cv_prog_cc_wl}-a ${ac_cv_prog_cc_wl}archive" + if test "$host_cpu" != ia64; then + _LT_AC_TAGVAR(lt_prog_compiler_pic, $1)='+Z' + fi + ;; + aCC) + _LT_AC_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_AC_TAGVAR(lt_prog_compiler_static, $1)="${ac_cv_prog_cc_wl}-a ${ac_cv_prog_cc_wl}archive" + case "$host_cpu" in + hppa*64*|ia64*) + # +Z the default + ;; + *) + _LT_AC_TAGVAR(lt_prog_compiler_pic, $1)='+Z' + ;; + esac + ;; + *) + ;; + esac + ;; + irix5* | irix6* | nonstopux*) + case $cc_basename in + CC) + _LT_AC_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_AC_TAGVAR(lt_prog_compiler_static, $1)='-non_shared' + # CC pic flag -KPIC is the default. + ;; + *) + ;; + esac + ;; + linux*) + case $cc_basename in + KCC) + # KAI C++ Compiler + _LT_AC_TAGVAR(lt_prog_compiler_wl, $1)='--backend -Wl,' + _LT_AC_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC' + ;; + icpc) + # Intel C++ + _LT_AC_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_AC_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' + _LT_AC_TAGVAR(lt_prog_compiler_static, $1)='-static' + ;; + cxx) + # Compaq C++ + # Make sure the PIC flag is empty. It appears that all Alpha + # Linux and Compaq Tru64 Unix objects are PIC. + _LT_AC_TAGVAR(lt_prog_compiler_pic, $1)= + _LT_AC_TAGVAR(lt_prog_compiler_static, $1)='-non_shared' + ;; + *) + ;; + esac + ;; + lynxos*) + ;; + m88k*) + ;; + mvs*) + case $cc_basename in + cxx) + _LT_AC_TAGVAR(lt_prog_compiler_pic, $1)='-W c,exportall' + ;; + *) + ;; + esac + ;; + netbsd*) + ;; + osf3* | osf4* | osf5*) + case $cc_basename in + KCC) + _LT_AC_TAGVAR(lt_prog_compiler_wl, $1)='--backend -Wl,' + ;; + RCC) + # Rational C++ 2.4.1 + _LT_AC_TAGVAR(lt_prog_compiler_pic, $1)='-pic' + ;; + cxx) + # Digital/Compaq C++ + _LT_AC_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + # Make sure the PIC flag is empty. It appears that all Alpha + # Linux and Compaq Tru64 Unix objects are PIC. + _LT_AC_TAGVAR(lt_prog_compiler_pic, $1)= + _LT_AC_TAGVAR(lt_prog_compiler_static, $1)='-non_shared' + ;; + *) + ;; + esac + ;; + psos*) + ;; + sco*) + case $cc_basename in + CC) + _LT_AC_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC' + ;; + *) + ;; + esac + ;; + solaris*) + case $cc_basename in + CC) + # Sun C++ 4.2, 5.x and Centerline C++ + _LT_AC_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' + _LT_AC_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + _LT_AC_TAGVAR(lt_prog_compiler_wl, $1)='-Qoption ld ' + ;; + gcx) + # Green Hills C++ Compiler + _LT_AC_TAGVAR(lt_prog_compiler_pic, $1)='-PIC' + ;; + *) + ;; + esac + ;; + sunos4*) + case $cc_basename in + CC) + # Sun C++ 4.x + _LT_AC_TAGVAR(lt_prog_compiler_pic, $1)='-pic' + _LT_AC_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + ;; + lcc) + # Lucid + _LT_AC_TAGVAR(lt_prog_compiler_pic, $1)='-pic' + ;; + *) + ;; + esac + ;; + tandem*) + case $cc_basename in + NCC) + # NonStop-UX NCC 3.20 + _LT_AC_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' + ;; + *) + ;; + esac + ;; + unixware*) + ;; + vxworks*) + ;; + *) + _LT_AC_TAGVAR(lt_prog_compiler_can_build_shared, $1)=no + ;; + esac + fi +], +[ + if test "$GCC" = yes; then + _LT_AC_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_AC_TAGVAR(lt_prog_compiler_static, $1)='-static' + + case $host_os in + aix*) + # All AIX code is PIC. + if test "$host_cpu" = ia64; then + # AIX 5 now supports IA64 processor + _LT_AC_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + fi + ;; + + amigaos*) + # FIXME: we need at least 68020 code to build shared libraries, but + # adding the `-m68020' flag to GCC prevents building anything better, + # like `-m68040'. + _LT_AC_TAGVAR(lt_prog_compiler_pic, $1)='-m68020 -resident32 -malways-restore-a4' + ;; + + beos* | cygwin* | irix5* | irix6* | nonstopux* | osf3* | osf4* | osf5*) + # PIC is the default for these OSes. + ;; + + mingw* | pw32* | os2*) + # This hack is so that the source file can tell whether it is being + # built for inclusion in a dll (and should export symbols for example). + _LT_AC_TAGVAR(lt_prog_compiler_pic, $1)='-DDLL_EXPORT' + ;; + + darwin* | rhapsody*) + # PIC is the default on this platform + # Common symbols not allowed in MH_DYLIB files + _LT_AC_TAGVAR(lt_prog_compiler_pic, $1)='-fno-common' + ;; + + msdosdjgpp*) + # Just because we use GCC doesn't mean we suddenly get shared libraries + # on systems that don't support them. + _LT_AC_TAGVAR(lt_prog_compiler_can_build_shared, $1)=no + enable_shared=no + ;; + + sysv4*MP*) + if test -d /usr/nec; then + _LT_AC_TAGVAR(lt_prog_compiler_pic, $1)=-Kconform_pic + fi + ;; + + hpux*) + # PIC is the default for IA64 HP-UX and 64-bit HP-UX, but + # not for PA HP-UX. + case "$host_cpu" in + hppa*64*|ia64*) + # +Z the default + ;; + *) + _LT_AC_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC' + ;; + esac + ;; + + *) + _LT_AC_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC' + ;; + esac + else + # PORTME Check for flag to pass linker flags through the system compiler. + case $host_os in + aix*) + _LT_AC_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + if test "$host_cpu" = ia64; then + # AIX 5 now supports IA64 processor + _LT_AC_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + else + _LT_AC_TAGVAR(lt_prog_compiler_static, $1)='-bnso -bI:/lib/syscalls.exp' + fi + ;; + + mingw* | pw32* | os2*) + # This hack is so that the source file can tell whether it is being + # built for inclusion in a dll (and should export symbols for example). + _LT_AC_TAGVAR(lt_prog_compiler_pic, $1)='-DDLL_EXPORT' + ;; + + hpux9* | hpux10* | hpux11*) + _LT_AC_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + # PIC is the default for IA64 HP-UX and 64-bit HP-UX, but + # not for PA HP-UX. + case "$host_cpu" in + hppa*64*|ia64*) + # +Z the default + ;; + *) + _LT_AC_TAGVAR(lt_prog_compiler_pic, $1)='+Z' + ;; + esac + # Is there a better lt_prog_compiler_static that works with the bundled CC? + _LT_AC_TAGVAR(lt_prog_compiler_static, $1)='${wl}-a ${wl}archive' + ;; + + irix5* | irix6* | nonstopux*) + _LT_AC_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + # PIC (with -KPIC) is the default. + _LT_AC_TAGVAR(lt_prog_compiler_static, $1)='-non_shared' + ;; + + newsos6) + _LT_AC_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' + _LT_AC_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + ;; + + linux*) + case $CC in + icc* | ecc*) + _LT_AC_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_AC_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' + _LT_AC_TAGVAR(lt_prog_compiler_static, $1)='-static' + ;; + ccc*) + _LT_AC_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + # All Alpha code is PIC. + _LT_AC_TAGVAR(lt_prog_compiler_static, $1)='-non_shared' + ;; + esac + ;; + + osf3* | osf4* | osf5*) + _LT_AC_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + # All OSF/1 code is PIC. + _LT_AC_TAGVAR(lt_prog_compiler_static, $1)='-non_shared' + ;; + + sco3.2v5*) + _LT_AC_TAGVAR(lt_prog_compiler_pic, $1)='-Kpic' + _LT_AC_TAGVAR(lt_prog_compiler_static, $1)='-dn' + ;; + + solaris*) + _LT_AC_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_AC_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' + _LT_AC_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + ;; + + sunos4*) + _LT_AC_TAGVAR(lt_prog_compiler_wl, $1)='-Qoption ld ' + _LT_AC_TAGVAR(lt_prog_compiler_pic, $1)='-PIC' + _LT_AC_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + ;; + + sysv4 | sysv4.2uw2* | sysv4.3* | sysv5*) + _LT_AC_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_AC_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' + _LT_AC_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + ;; + + sysv4*MP*) + if test -d /usr/nec ;then + _LT_AC_TAGVAR(lt_prog_compiler_pic, $1)='-Kconform_pic' + _LT_AC_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + fi + ;; + + uts4*) + _LT_AC_TAGVAR(lt_prog_compiler_pic, $1)='-pic' + _LT_AC_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + ;; + + *) + _LT_AC_TAGVAR(lt_prog_compiler_can_build_shared, $1)=no + ;; + esac + fi +]) +AC_MSG_RESULT([$_LT_AC_TAGVAR(lt_prog_compiler_pic, $1)]) + +# +# Check to make sure the PIC flag actually works. +# +if test -n "$_LT_AC_TAGVAR(lt_prog_compiler_pic, $1)"; then + AC_LIBTOOL_COMPILER_OPTION([if $compiler PIC flag $_LT_AC_TAGVAR(lt_prog_compiler_pic, $1) works], + _LT_AC_TAGVAR(lt_prog_compiler_pic_works, $1), + [$_LT_AC_TAGVAR(lt_prog_compiler_pic, $1)ifelse([$1],[],[ -DPIC],[ifelse([$1],[CXX],[ -DPIC],[])])], [], + [case $_LT_AC_TAGVAR(lt_prog_compiler_pic, $1) in + "" | " "*) ;; + *) _LT_AC_TAGVAR(lt_prog_compiler_pic, $1)=" $_LT_AC_TAGVAR(lt_prog_compiler_pic, $1)" ;; + esac], + [_LT_AC_TAGVAR(lt_prog_compiler_pic, $1)= + _LT_AC_TAGVAR(lt_prog_compiler_can_build_shared, $1)=no]) +fi +case "$host_os" in + # For platforms which do not support PIC, -DPIC is meaningless: + *djgpp*) + _LT_AC_TAGVAR(lt_prog_compiler_pic, $1)= + ;; + *) + _LT_AC_TAGVAR(lt_prog_compiler_pic, $1)="$_LT_AC_TAGVAR(lt_prog_compiler_pic, $1)ifelse([$1],[],[ -DPIC],[ifelse([$1],[CXX],[ -DPIC],[])])" + ;; +esac +]) + + +# AC_LIBTOOL_PROG_LD_SHLIBS([TAGNAME]) +# ------------------------------------ +# See if the linker supports building shared libraries. +AC_DEFUN([AC_LIBTOOL_PROG_LD_SHLIBS], +[AC_MSG_CHECKING([whether the $compiler linker ($LD) supports shared libraries]) +ifelse([$1],[CXX],[ + _LT_AC_TAGVAR(export_symbols_cmds, $1)='$NM $libobjs $convenience | $global_symbol_pipe | $SED '\''s/.* //'\'' | sort | uniq > $export_symbols' + case $host_os in + aix4* | aix5*) + # If we're using GNU nm, then we don't want the "-C" option. + # -C means demangle to AIX nm, but means don't demangle with GNU nm + if $NM -V 2>&1 | grep 'GNU' > /dev/null; then + _LT_AC_TAGVAR(export_symbols_cmds, $1)='$NM -Bpg $libobjs $convenience | awk '\''{ if (((\[$]2 == "T") || (\[$]2 == "D") || (\[$]2 == "B")) && ([substr](\[$]3,1,1) != ".")) { print \[$]3 } }'\'' | sort -u > $export_symbols' + else + _LT_AC_TAGVAR(export_symbols_cmds, $1)='$NM -BCpg $libobjs $convenience | awk '\''{ if (((\[$]2 == "T") || (\[$]2 == "D") || (\[$]2 == "B")) && ([substr](\[$]3,1,1) != ".")) { print \[$]3 } }'\'' | sort -u > $export_symbols' + fi + ;; + pw32*) + _LT_AC_TAGVAR(export_symbols_cmds, $1)="$ltdll_cmds" + ;; + cygwin* | mingw*) + _LT_AC_TAGVAR(export_symbols_cmds, $1)='$NM $libobjs $convenience | $global_symbol_pipe | $SED -e '\''/^[[BCDGS]] /s/.* \([[^ ]]*\)/\1 DATA/'\'' | $SED -e '\''/^[[AITW]] /s/.* //'\'' | sort | uniq > $export_symbols' + ;; + *) + _LT_AC_TAGVAR(export_symbols_cmds, $1)='$NM $libobjs $convenience | $global_symbol_pipe | $SED '\''s/.* //'\'' | sort | uniq > $export_symbols' + ;; + esac +],[ + runpath_var= + _LT_AC_TAGVAR(allow_undefined_flag, $1)= + _LT_AC_TAGVAR(enable_shared_with_static_runtimes, $1)=no + _LT_AC_TAGVAR(archive_cmds, $1)= + _LT_AC_TAGVAR(archive_expsym_cmds, $1)= + _LT_AC_TAGVAR(old_archive_From_new_cmds, $1)= + _LT_AC_TAGVAR(old_archive_from_expsyms_cmds, $1)= + _LT_AC_TAGVAR(export_dynamic_flag_spec, $1)= + _LT_AC_TAGVAR(whole_archive_flag_spec, $1)= + _LT_AC_TAGVAR(thread_safe_flag_spec, $1)= + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)= + _LT_AC_TAGVAR(hardcode_libdir_flag_spec_ld, $1)= + _LT_AC_TAGVAR(hardcode_libdir_separator, $1)= + _LT_AC_TAGVAR(hardcode_direct, $1)=no + _LT_AC_TAGVAR(hardcode_minus_L, $1)=no + _LT_AC_TAGVAR(hardcode_shlibpath_var, $1)=unsupported + _LT_AC_TAGVAR(link_all_deplibs, $1)=unknown + _LT_AC_TAGVAR(hardcode_automatic, $1)=no + _LT_AC_TAGVAR(module_cmds, $1)= + _LT_AC_TAGVAR(module_expsym_cmds, $1)= + _LT_AC_TAGVAR(always_export_symbols, $1)=no + _LT_AC_TAGVAR(export_symbols_cmds, $1)='$NM $libobjs $convenience | $global_symbol_pipe | $SED '\''s/.* //'\'' | sort | uniq > $export_symbols' + # include_expsyms should be a list of space-separated symbols to be *always* + # included in the symbol list + _LT_AC_TAGVAR(include_expsyms, $1)= + # exclude_expsyms can be an extended regexp of symbols to exclude + # it will be wrapped by ` (' and `)$', so one must not match beginning or + # end of line. Example: `a|bc|.*d.*' will exclude the symbols `a' and `bc', + # as well as any symbol that contains `d'. + _LT_AC_TAGVAR(exclude_expsyms, $1)="_GLOBAL_OFFSET_TABLE_" + # Although _GLOBAL_OFFSET_TABLE_ is a valid symbol C name, most a.out + # platforms (ab)use it in PIC code, but their linkers get confused if + # the symbol is explicitly referenced. Since portable code cannot + # rely on this symbol name, it's probably fine to never include it in + # preloaded symbol tables. + extract_expsyms_cmds= + + case $host_os in + cygwin* | mingw* | pw32*) + # FIXME: the MSVC++ port hasn't been tested in a loooong time + # When not using gcc, we currently assume that we are using + # Microsoft Visual C++. + if test "$GCC" != yes; then + with_gnu_ld=no + fi + ;; + openbsd*) + with_gnu_ld=no + ;; + esac + + _LT_AC_TAGVAR(ld_shlibs, $1)=yes + if test "$with_gnu_ld" = yes; then + # If archive_cmds runs LD, not CC, wlarc should be empty + wlarc='${wl}' + + # See if GNU ld supports shared libraries. + case $host_os in + aix3* | aix4* | aix5*) + # On AIX/PPC, the GNU linker is very broken + if test "$host_cpu" != ia64; then + _LT_AC_TAGVAR(ld_shlibs, $1)=no + cat <&2 + +*** Warning: the GNU linker, at least up to release 2.9.1, is reported +*** to be unable to reliably create shared libraries on AIX. +*** Therefore, libtool is disabling shared libraries support. If you +*** really care for shared libraries, you may want to modify your PATH +*** so that a non-GNU linker is found, and then restart. + +EOF + fi + ;; + + amigaos*) + _LT_AC_TAGVAR(archive_cmds, $1)='$rm $output_objdir/a2ixlibrary.data~$echo "#define NAME $libname" > $output_objdir/a2ixlibrary.data~$echo "#define LIBRARY_ID 1" >> $output_objdir/a2ixlibrary.data~$echo "#define VERSION $major" >> $output_objdir/a2ixlibrary.data~$echo "#define REVISION $revision" >> $output_objdir/a2ixlibrary.data~$AR $AR_FLAGS $lib $libobjs~$RANLIB $lib~(cd $output_objdir && a2ixlibrary -32)' + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' + _LT_AC_TAGVAR(hardcode_minus_L, $1)=yes + + # Samuel A. Falvo II reports + # that the semantics of dynamic libraries on AmigaOS, at least up + # to version 4, is to share data among multiple programs linked + # with the same dynamic library. Since this doesn't match the + # behavior of shared libraries on other platforms, we can't use + # them. + _LT_AC_TAGVAR(ld_shlibs, $1)=no + ;; + + beos*) + if $LD --help 2>&1 | grep ': supported targets:.* elf' > /dev/null; then + _LT_AC_TAGVAR(allow_undefined_flag, $1)=unsupported + # Joseph Beckenbach says some releases of gcc + # support --undefined. This deserves some investigation. FIXME + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -nostart $compiler_flags $libobjs $deplibs ${wl}-soname $wl$soname -o $lib' + else + _LT_AC_TAGVAR(ld_shlibs, $1)=no + fi + ;; + + cygwin* | mingw* | pw32*) + # _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1) is actually meaningless, + # as there is no search path for DLLs. + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' + _LT_AC_TAGVAR(allow_undefined_flag, $1)=no + _LT_AC_TAGVAR(always_export_symbols, $1)=no + _LT_AC_TAGVAR(enable_shared_with_static_runtimes, $1)=yes + _LT_AC_TAGVAR(export_symbols_cmds, $1)='$NM $libobjs $convenience | $global_symbol_pipe | $SED -e '\''/^[[BCDGS]] /s/.* \([[^ ]]*\)/\1 DATA/'\'' | $SED -e '\''/^[[AITW]] /s/.* //'\'' | sort | uniq > $export_symbols' + + if $LD --help 2>&1 | grep 'auto-import' > /dev/null; then + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -shared $compiler_flags $libobjs $deplibs -o $output_objdir/$soname ${wl}--image-base=0x10000000 ${wl}--out-implib,$lib' + # If the export-symbols file already is a .def file (1st line + # is EXPORTS), use it as is; otherwise, prepend... + _LT_AC_TAGVAR(archive_expsym_cmds, $1)='if test "x`$SED 1q $export_symbols`" = xEXPORTS; then + cp $export_symbols $output_objdir/$soname.def; + else + echo EXPORTS > $output_objdir/$soname.def; + cat $export_symbols >> $output_objdir/$soname.def; + fi~ + $CC -shared $output_objdir/$soname.def $compiler_flags $libobjs $deplibs -o $output_objdir/$soname ${wl}--image-base=0x10000000 ${wl}--out-implib,$lib' + else + ld_shlibs=no + fi + ;; + + netbsd*) + if echo __ELF__ | $CC -E - | grep __ELF__ >/dev/null; then + _LT_AC_TAGVAR(archive_cmds, $1)='$LD -Bshareable $libobjs $deplibs $linker_flags -o $lib' + wlarc= + else + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -shared $compiler_flags $libobjs $deplibs ${wl}-soname $wl$soname -o $lib' + _LT_AC_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $compiler_flags $libobjs $deplibs ${wl}-soname $wl$soname ${wl}-retain-symbols-file $wl$export_symbols -o $lib' + fi + ;; + + solaris* | sysv5*) + if $LD -v 2>&1 | grep 'BFD 2\.8' > /dev/null; then + _LT_AC_TAGVAR(ld_shlibs, $1)=no + cat <&2 + +*** Warning: The releases 2.8.* of the GNU linker cannot reliably +*** create shared libraries on Solaris systems. Therefore, libtool +*** is disabling shared libraries support. We urge you to upgrade GNU +*** binutils to release 2.9.1 or newer. Another option is to modify +*** your PATH or compiler configuration so that the native linker is +*** used, and then restart. + +EOF + elif $LD --help 2>&1 | grep ': supported targets:.* elf' > /dev/null; then + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -shared $compiler_flags $libobjs $deplibs ${wl}-soname $wl$soname -o $lib' + _LT_AC_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $compiler_flags $libobjs $deplibs ${wl}-soname $wl$soname ${wl}-retain-symbols-file $wl$export_symbols -o $lib' + else + _LT_AC_TAGVAR(ld_shlibs, $1)=no + fi + ;; + + sunos4*) + _LT_AC_TAGVAR(archive_cmds, $1)='$LD -assert pure-text -Bshareable -o $lib $libobjs $deplibs $linker_flags' + wlarc= + _LT_AC_TAGVAR(hardcode_direct, $1)=yes + _LT_AC_TAGVAR(hardcode_shlibpath_var, $1)=no + ;; + + *) + if $LD --help 2>&1 | grep ': supported targets:.* elf' > /dev/null; then + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -shared $compiler_flags $libobjs $deplibs ${wl}-soname $wl$soname -o $lib' + _LT_AC_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $compiler_flags $libobjs $deplibs ${wl}-soname $wl$soname ${wl}-retain-symbols-file $wl$export_symbols -o $lib' + else + _LT_AC_TAGVAR(ld_shlibs, $1)=no + fi + ;; + esac + + if test "$_LT_AC_TAGVAR(ld_shlibs, $1)" = yes; then + runpath_var=LD_RUN_PATH + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}--rpath ${wl}$libdir' + _LT_AC_TAGVAR(export_dynamic_flag_spec, $1)='${wl}--export-dynamic' + # ancient GNU ld didn't support --whole-archive et. al. + if $LD --help 2>&1 | grep 'no-whole-archive' > /dev/null; then + _LT_AC_TAGVAR(whole_archive_flag_spec, $1)="$wlarc"'--whole-archive$convenience '"$wlarc"'--no-whole-archive' + else + _LT_AC_TAGVAR(whole_archive_flag_spec, $1)= + fi + fi + else + # PORTME fill in a description of your system's linker (not GNU ld) + case $host_os in + aix3*) + _LT_AC_TAGVAR(allow_undefined_flag, $1)=unsupported + _LT_AC_TAGVAR(always_export_symbols, $1)=yes + _LT_AC_TAGVAR(archive_expsym_cmds, $1)='$LD -o $output_objdir/$soname $libobjs $deplibs $linker_flags -bE:$export_symbols -T512 -H512 -bM:SRE~$AR $AR_FLAGS $lib $output_objdir/$soname' + # Note: this linker hardcodes the directories in LIBPATH if there + # are no directories specified by -L. + _LT_AC_TAGVAR(hardcode_minus_L, $1)=yes + if test "$GCC" = yes && test -z "$link_static_flag"; then + # Neither direct hardcoding nor static linking is supported with a + # broken collect2. + _LT_AC_TAGVAR(hardcode_direct, $1)=unsupported + fi + ;; + + aix4* | aix5*) + if test "$host_cpu" = ia64; then + # On IA64, the linker does run time linking by default, so we don't + # have to do anything special. + aix_use_runtimelinking=no + exp_sym_flag='-Bexport' + no_entry_flag="" + else + # If we're using GNU nm, then we don't want the "-C" option. + # -C means demangle to AIX nm, but means don't demangle with GNU nm + if $NM -V 2>&1 | grep 'GNU' > /dev/null; then + _LT_AC_TAGVAR(export_symbols_cmds, $1)='$NM -Bpg $libobjs $convenience | awk '\''{ if (((\[$]2 == "T") || (\[$]2 == "D") || (\[$]2 == "B")) && ([substr](\[$]3,1,1) != ".")) { print \[$]3 } }'\'' | sort -u > $export_symbols' + else + _LT_AC_TAGVAR(export_symbols_cmds, $1)='$NM -BCpg $libobjs $convenience | awk '\''{ if (((\[$]2 == "T") || (\[$]2 == "D") || (\[$]2 == "B")) && ([substr](\[$]3,1,1) != ".")) { print \[$]3 } }'\'' | sort -u > $export_symbols' + fi + + # KDE requires run time linking. Make it the default. + aix_use_runtimelinking=yes + exp_sym_flag='-bexport' + no_entry_flag='-bnoentry' + fi + + # When large executables or shared objects are built, AIX ld can + # have problems creating the table of contents. If linking a library + # or program results in "error TOC overflow" add -mminimal-toc to + # CXXFLAGS/CFLAGS for g++/gcc. In the cases where that is not + # enough to fix the problem, add -Wl,-bbigtoc to LDFLAGS. + + _LT_AC_TAGVAR(archive_cmds, $1)='' + _LT_AC_TAGVAR(hardcode_direct, $1)=yes + _LT_AC_TAGVAR(hardcode_libdir_separator, $1)=':' + _LT_AC_TAGVAR(link_all_deplibs, $1)=yes + + if test "$GCC" = yes; then + case $host_os in aix4.[012]|aix4.[012].*) + # We only want to do this on AIX 4.2 and lower, the check + # below for broken collect2 doesn't work under 4.3+ + collect2name=`${CC} -print-prog-name=collect2` + if test -f "$collect2name" && \ + strings "$collect2name" | grep resolve_lib_name >/dev/null + then + # We have reworked collect2 + _LT_AC_TAGVAR(hardcode_direct, $1)=yes + else + # We have old collect2 + _LT_AC_TAGVAR(hardcode_direct, $1)=unsupported + # It fails to find uninstalled libraries when the uninstalled + # path is not listed in the libpath. Setting hardcode_minus_L + # to unsupported forces relinking + _LT_AC_TAGVAR(hardcode_minus_L, $1)=yes + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' + _LT_AC_TAGVAR(hardcode_libdir_separator, $1)= + fi + esac + shared_flag='-shared' + else + # not using gcc + if test "$host_cpu" = ia64; then + # VisualAge C++, Version 5.5 for AIX 5L for IA-64, Beta 3 Release + # chokes on -Wl,-G. The following line is correct: + shared_flag='-G' + else + if test "$aix_use_runtimelinking" = yes; then + shared_flag='-qmkshrobj ${wl}-G' + else + shared_flag='-qmkshrobj' + fi + fi + fi + + # Let the compiler handle the export list. + _LT_AC_TAGVAR(always_export_symbols, $1)=no + if test "$aix_use_runtimelinking" = yes; then + # Warning - without using the other runtime loading flags (-brtl), + # -berok will link without error, but may produce a broken library. + _LT_AC_TAGVAR(allow_undefined_flag, $1)='-berok' + # Determine the default libpath from the value encoded in an empty executable. + _LT_AC_SYS_LIBPATH_AIX + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-blibpath:$libdir:'"$aix_libpath" + _LT_AC_TAGVAR(archive_cmds, $1)="\$CC"' -o $output_objdir/$soname $compiler_flags $libobjs $deplibs `if test "x${allow_undefined_flag}" != "x"; then echo "${wl}${allow_undefined_flag}"; else :; fi` '" $shared_flag" + _LT_AC_TAGVAR(archive_expsym_cmds, $1)="\$CC"' -o $output_objdir/$soname $compiler_flags $libobjs $deplibs `if test "x${allow_undefined_flag}" != "x"; then echo "${wl}${allow_undefined_flag}"; else :; fi` '"\${wl}$exp_sym_flag:\$export_symbols $shared_flag" + else + if test "$host_cpu" = ia64; then + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-R $libdir:/usr/lib:/lib' + _LT_AC_TAGVAR(allow_undefined_flag, $1)="-z nodefs" + _LT_AC_TAGVAR(archive_expsym_cmds, $1)="\$CC $shared_flag"' -o $output_objdir/$soname $compiler_flags $libobjs $deplibs ${wl}${allow_undefined_flag} '"\${wl}$no_entry_flag \${wl}$exp_sym_flag:\$export_symbols" + else + # Determine the default libpath from the value encoded in an empty executable. + _LT_AC_SYS_LIBPATH_AIX + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-blibpath:$libdir:'"$aix_libpath" + # Warning - without using the other run time loading flags, + # -berok will link without error, but may produce a broken library. + _LT_AC_TAGVAR(no_undefined_flag, $1)=' ${wl}-bernotok' + _LT_AC_TAGVAR(allow_undefined_flag, $1)=' ${wl}-berok' + # -bexpall does not export symbols beginning with underscore (_) + _LT_AC_TAGVAR(always_export_symbols, $1)=yes + # Exported symbols can be pulled into shared objects from archives + _LT_AC_TAGVAR(whole_archive_flag_spec, $1)=' ' + _LT_AC_TAGVAR(archive_cmds_need_lc, $1)=yes + # This is similar to how AIX traditionally builds it's shared libraries. + _LT_AC_TAGVAR(archive_expsym_cmds, $1)="\$CC $shared_flag"' -o $output_objdir/$soname $compiler_flags $libobjs $deplibs ${wl}-bE:$export_symbols ${wl}-bnoentry${allow_undefined_flag}~$AR $AR_FLAGS $output_objdir/$libname$release.a $output_objdir/$soname' + fi + fi + ;; + + amigaos*) + _LT_AC_TAGVAR(archive_cmds, $1)='$rm $output_objdir/a2ixlibrary.data~$echo "#define NAME $libname" > $output_objdir/a2ixlibrary.data~$echo "#define LIBRARY_ID 1" >> $output_objdir/a2ixlibrary.data~$echo "#define VERSION $major" >> $output_objdir/a2ixlibrary.data~$echo "#define REVISION $revision" >> $output_objdir/a2ixlibrary.data~$AR $AR_FLAGS $lib $libobjs~$RANLIB $lib~(cd $output_objdir && a2ixlibrary -32)' + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' + _LT_AC_TAGVAR(hardcode_minus_L, $1)=yes + # see comment about different semantics on the GNU ld section + _LT_AC_TAGVAR(ld_shlibs, $1)=no + ;; + + bsdi4*) + _LT_AC_TAGVAR(export_dynamic_flag_spec, $1)=-rdynamic + ;; + + cygwin* | mingw* | pw32*) + # When not using gcc, we currently assume that we are using + # Microsoft Visual C++. + # hardcode_libdir_flag_spec is actually meaningless, as there is + # no search path for DLLs. + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)=' ' + _LT_AC_TAGVAR(allow_undefined_flag, $1)=no + # Tell ltmain to make .lib files, not .a files. + libext=lib + # Tell ltmain to make .dll files, not .so files. + shrext=".dll" + # FIXME: Setting linknames here is a bad hack. + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -o $lib $compiler_flags $libobjs `echo "$deplibs" | $SED -e '\''s/ -lc$//'\''` -link -dll~linknames=' + # The linker will automatically build a .lib file if we build a DLL. + _LT_AC_TAGVAR(old_archive_From_new_cmds, $1)='true' + # FIXME: Should let the user specify the lib program. + _LT_AC_TAGVAR(old_archive_cmds, $1)='lib /OUT:$oldlib$oldobjs$old_deplibs' + fix_srcfile_path='`cygpath -w "$srcfile"`' + _LT_AC_TAGVAR(enable_shared_with_static_runtimes, $1)=yes + ;; + + darwin* | rhapsody*) + if test "$GXX" = yes ; then + _LT_AC_TAGVAR(archive_cmds_need_lc, $1)=no + case "$host_os" in + rhapsody* | darwin1.[[012]]) + _LT_AC_TAGVAR(allow_undefined_flag, $1)='-Wl,-undefined -Wl,suppress' + ;; + *) # Darwin 1.3 on + if test -z ${MACOSX_DEPLOYMENT_TARGET} ; then + _LT_AC_TAGVAR(allow_undefined_flag, $1)='-Wl,-flat_namespace -Wl,-undefined -Wl,suppress' + else + case ${MACOSX_DEPLOYMENT_TARGET} in + 10.[012]) + _LT_AC_TAGVAR(allow_undefined_flag, $1)='-Wl,-flat_namespace -Wl,-undefined -Wl,suppress' + ;; + 10.*) + _LT_AC_TAGVAR(allow_undefined_flag, $1)='-Wl,-undefined -Wl,dynamic_lookup' + ;; + esac + fi + ;; + esac + lt_int_apple_cc_single_mod=no + output_verbose_link_cmd='echo' + if $CC -dumpspecs 2>&1 | grep 'single_module' >/dev/null ; then + lt_int_apple_cc_single_mod=yes + fi + if test "X$lt_int_apple_cc_single_mod" = Xyes ; then + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -dynamiclib -single_module $allow_undefined_flag -o $lib $compiler_flags $libobjs $deplibs -install_name $rpath/$soname $verstring' + else + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -r ${wl}-bind_at_load -keep_private_externs -nostdlib -o ${lib}-master.o $libobjs~$CC -dynamiclib $allow_undefined_flag -o $lib ${lib}-master.o $compiler_flags $deplibs -install_name $rpath/$soname $verstring' + fi + _LT_AC_TAGVAR(module_cmds, $1)='$CC ${wl}-bind_at_load $allow_undefined_flag -o $lib -bundle $compiler_flags $libobjs $deplibs' + # Don't fix this by using the ld -exported_symbols_list flag, it doesn't exist in older darwin ld's + if test "X$lt_int_apple_cc_single_mod" = Xyes ; then + _LT_AC_TAGVAR(archive_expsym_cmds, $1)='sed -e "s,#.*,," -e "s,^[ ]*,," -e "s,^\(..*\),_&," < $export_symbols > $output_objdir/${libname}-symbols.expsym~$CC -dynamiclib -single_module $allow_undefined_flag -o $lib $compiler_flags $libobjs $deplibs -install_name $rpath/$soname $verstring~nmedit -s $output_objdir/${libname}-symbols.expsym ${lib}' + else + _LT_AC_TAGVAR(archive_expsym_cmds, $1)='sed -e "s,#.*,," -e "s,^[ ]*,," -e "s,^\(..*\),_&," < $export_symbols > $output_objdir/${libname}-symbols.expsym~$CC -r ${wl}-bind_at_load -keep_private_externs -nostdlib -o ${lib}-master.o $libobjs~$CC -dynamiclib $allow_undefined_flag -o $lib ${lib}-master.o $compiler_flags $deplibs -install_name $rpath/$soname $verstring~nmedit -s $output_objdir/${libname}-symbols.expsym ${lib}' + fi + _LT_AC_TAGVAR(module_expsym_cmds, $1)='sed -e "s,#.*,," -e "s,^[ ]*,," -e "s,^\(..*\),_&," < $export_symbols > $output_objdir/${libname}-symbols.expsym~$CC $allow_undefined_flag -o $lib -bundle $compiler_flags $libobjs $deplibs~nmedit -s $output_objdir/${libname}-symbols.expsym ${lib}' + _LT_AC_TAGVAR(hardcode_direct, $1)=no + _LT_AC_TAGVAR(hardcode_automatic, $1)=yes + _LT_AC_TAGVAR(hardcode_shlibpath_var, $1)=unsupported + _LT_AC_TAGVAR(whole_archive_flag_spec, $1)='-all_load $convenience' + _LT_AC_TAGVAR(link_all_deplibs, $1)=yes + else + _LT_AC_TAGVAR(ld_shlibs, $1)=no + fi + ;; + + dgux*) + _LT_AC_TAGVAR(archive_cmds, $1)='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' + _LT_AC_TAGVAR(hardcode_shlibpath_var, $1)=no + ;; + + freebsd1*) + _LT_AC_TAGVAR(ld_shlibs, $1)=no + ;; + + # FreeBSD 2.2.[012] allows us to include c++rt0.o to get C++ constructor + # support. Future versions do this automatically, but an explicit c++rt0.o + # does not break anything, and helps significantly (at the cost of a little + # extra space). + freebsd2.2*) + _LT_AC_TAGVAR(archive_cmds, $1)='$LD -Bshareable -o $lib $libobjs $deplibs $linker_flags /usr/lib/c++rt0.o' + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='-R$libdir' + _LT_AC_TAGVAR(hardcode_direct, $1)=yes + _LT_AC_TAGVAR(hardcode_shlibpath_var, $1)=no + ;; + + # Unfortunately, older versions of FreeBSD 2 do not have this feature. + freebsd2*) + _LT_AC_TAGVAR(archive_cmds, $1)='$LD -Bshareable -o $lib $libobjs $deplibs $linker_flags' + _LT_AC_TAGVAR(hardcode_direct, $1)=yes + _LT_AC_TAGVAR(hardcode_minus_L, $1)=yes + _LT_AC_TAGVAR(hardcode_shlibpath_var, $1)=no + ;; + + # FreeBSD 3 and greater uses gcc -shared to do shared libraries. + freebsd* | kfreebsd*-gnu) + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -shared -o $lib $compiler_flags $libobjs $deplibs' + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='-R$libdir' + _LT_AC_TAGVAR(hardcode_direct, $1)=yes + _LT_AC_TAGVAR(hardcode_shlibpath_var, $1)=no + ;; + + hpux9*) + if test "$GCC" = yes; then + _LT_AC_TAGVAR(archive_cmds, $1)='$rm $output_objdir/$soname~$CC -shared -fPIC ${wl}+b ${wl}$install_libdir -o $output_objdir/$soname $compiler_flags $libobjs $deplibs~test $output_objdir/$soname = $lib || mv $output_objdir/$soname $lib' + else + _LT_AC_TAGVAR(archive_cmds, $1)='$rm $output_objdir/$soname~$LD -b +b $install_libdir -o $output_objdir/$soname $libobjs $deplibs $linker_flags~test $output_objdir/$soname = $lib || mv $output_objdir/$soname $lib' + fi + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}+b ${wl}$libdir' + _LT_AC_TAGVAR(hardcode_libdir_separator, $1)=: + _LT_AC_TAGVAR(hardcode_direct, $1)=yes + + # hardcode_minus_L: Not really in the search PATH, + # but as the default location of the library. + _LT_AC_TAGVAR(hardcode_minus_L, $1)=yes + _LT_AC_TAGVAR(export_dynamic_flag_spec, $1)='${wl}-E' + ;; + + hpux10* | hpux11*) + if test "$GCC" = yes -a "$with_gnu_ld" = no; then + case "$host_cpu" in + hppa*64*|ia64*) + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -shared ${wl}+h ${wl}$soname -o $lib $compiler_flags $libobjs $deplibs' + ;; + *) + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -shared -fPIC ${wl}+h ${wl}$soname ${wl}+b ${wl}$install_libdir -o $lib $compiler_flags $libobjs $deplibs' + ;; + esac + else + case "$host_cpu" in + hppa*64*|ia64*) + _LT_AC_TAGVAR(archive_cmds, $1)='$LD -b +h $soname -o $lib $libobjs $deplibs $linker_flags' + ;; + *) + _LT_AC_TAGVAR(archive_cmds, $1)='$LD -b +h $soname +b $install_libdir -o $lib $libobjs $deplibs $linker_flags' + ;; + esac + fi + if test "$with_gnu_ld" = no; then + case "$host_cpu" in + hppa*64*) + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}+b ${wl}$libdir' + _LT_AC_TAGVAR(hardcode_libdir_flag_spec_ld, $1)='+b $libdir' + _LT_AC_TAGVAR(hardcode_libdir_separator, $1)=: + _LT_AC_TAGVAR(hardcode_direct, $1)=no + _LT_AC_TAGVAR(hardcode_shlibpath_var, $1)=no + ;; + ia64*) + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' + _LT_AC_TAGVAR(hardcode_direct, $1)=no + _LT_AC_TAGVAR(hardcode_shlibpath_var, $1)=no + + # hardcode_minus_L: Not really in the search PATH, + # but as the default location of the library. + _LT_AC_TAGVAR(hardcode_minus_L, $1)=yes + ;; + *) + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}+b ${wl}$libdir' + _LT_AC_TAGVAR(hardcode_libdir_separator, $1)=: + _LT_AC_TAGVAR(hardcode_direct, $1)=yes + _LT_AC_TAGVAR(export_dynamic_flag_spec, $1)='${wl}-E' + + # hardcode_minus_L: Not really in the search PATH, + # but as the default location of the library. + _LT_AC_TAGVAR(hardcode_minus_L, $1)=yes + ;; + esac + fi + ;; + + irix5* | irix6* | nonstopux*) + if test "$GCC" = yes; then + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -shared $compiler_flags $libobjs $deplibs ${wl}-soname ${wl}$soname `test -n "$verstring" && echo ${wl}-set_version ${wl}$verstring` ${wl}-update_registry ${wl}${output_objdir}/so_locations -o $lib' + else + _LT_AC_TAGVAR(archive_cmds, $1)='$LD -shared $libobjs $deplibs $linker_flags -soname $soname `test -n "$verstring" && echo -set_version $verstring` -update_registry ${output_objdir}/so_locations -o $lib' + _LT_AC_TAGVAR(hardcode_libdir_flag_spec_ld, $1)='-rpath $libdir' + fi + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-rpath ${wl}$libdir' + _LT_AC_TAGVAR(hardcode_libdir_separator, $1)=: + _LT_AC_TAGVAR(link_all_deplibs, $1)=yes + ;; + + netbsd*) + if echo __ELF__ | $CC -E - | grep __ELF__ >/dev/null; then + _LT_AC_TAGVAR(archive_cmds, $1)='$LD -Bshareable -o $lib $libobjs $deplibs $linker_flags' # a.out + else + _LT_AC_TAGVAR(archive_cmds, $1)='$LD -shared -o $lib $libobjs $deplibs $linker_flags' # ELF + fi + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='-R$libdir' + _LT_AC_TAGVAR(hardcode_direct, $1)=yes + _LT_AC_TAGVAR(hardcode_shlibpath_var, $1)=no + ;; + + newsos6) + _LT_AC_TAGVAR(archive_cmds, $1)='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' + _LT_AC_TAGVAR(hardcode_direct, $1)=yes + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-rpath ${wl}$libdir' + _LT_AC_TAGVAR(hardcode_libdir_separator, $1)=: + _LT_AC_TAGVAR(hardcode_shlibpath_var, $1)=no + ;; + + openbsd*) + _LT_AC_TAGVAR(hardcode_direct, $1)=yes + _LT_AC_TAGVAR(hardcode_shlibpath_var, $1)=no + if test -z "`echo __ELF__ | $CC -E - | grep __ELF__`" || test "$host_os-$host_cpu" = "openbsd2.8-powerpc"; then + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag -o $lib $compiler_flags $libobjs $deplibs' + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-rpath,$libdir' + _LT_AC_TAGVAR(export_dynamic_flag_spec, $1)='${wl}-E' + else + case $host_os in + openbsd[[01]].* | openbsd2.[[0-7]] | openbsd2.[[0-7]].*) + _LT_AC_TAGVAR(archive_cmds, $1)='$LD -Bshareable -o $lib $libobjs $deplibs $linker_flags' + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='-R$libdir' + ;; + *) + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag -o $lib $compiler_flags $libobjs $deplibs' + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-rpath,$libdir' + ;; + esac + fi + ;; + + os2*) + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' + _LT_AC_TAGVAR(hardcode_minus_L, $1)=yes + _LT_AC_TAGVAR(allow_undefined_flag, $1)=unsupported + _LT_AC_TAGVAR(archive_cmds, $1)='$echo "LIBRARY $libname INITINSTANCE" > $output_objdir/$libname.def~$echo "DESCRIPTION \"$libname\"" >> $output_objdir/$libname.def~$echo DATA >> $output_objdir/$libname.def~$echo " SINGLE NONSHARED" >> $output_objdir/$libname.def~$echo EXPORTS >> $output_objdir/$libname.def~emxexp $libobjs >> $output_objdir/$libname.def~$CC -Zdll -Zcrtdll -o $lib $compiler_flags $libobjs $deplibs$output_objdir/$libname.def' + _LT_AC_TAGVAR(old_archive_From_new_cmds, $1)='emximp -o $output_objdir/$libname.a $output_objdir/$libname.def' + ;; + + osf3*) + if test "$GCC" = yes; then + _LT_AC_TAGVAR(allow_undefined_flag, $1)=' ${wl}-expect_unresolved ${wl}\*' + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -shared${allow_undefined_flag} $compiler_flags $libobjs $deplibs ${wl}-soname ${wl}$soname `test -n "$verstring" && echo ${wl}-set_version ${wl}$verstring` ${wl}-update_registry ${wl}${output_objdir}/so_locations -o $lib' + else + _LT_AC_TAGVAR(allow_undefined_flag, $1)=' -expect_unresolved \*' + _LT_AC_TAGVAR(archive_cmds, $1)='$LD -shared${allow_undefined_flag} $libobjs $deplibs $linker_flags -soname $soname `test -n "$verstring" && echo -set_version $verstring` -update_registry ${output_objdir}/so_locations -o $lib' + fi + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-rpath ${wl}$libdir' + _LT_AC_TAGVAR(hardcode_libdir_separator, $1)=: + ;; + + osf4* | osf5*) # as osf3* with the addition of -msym flag + if test "$GCC" = yes; then + _LT_AC_TAGVAR(allow_undefined_flag, $1)=' ${wl}-expect_unresolved ${wl}\*' + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -shared${allow_undefined_flag} $compiler_flags $libobjs $deplibs ${wl}-msym ${wl}-soname ${wl}$soname `test -n "$verstring" && echo ${wl}-set_version ${wl}$verstring` ${wl}-update_registry ${wl}${output_objdir}/so_locations -o $lib' + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-rpath ${wl}$libdir' + else + _LT_AC_TAGVAR(allow_undefined_flag, $1)=' -expect_unresolved \*' + _LT_AC_TAGVAR(archive_cmds, $1)='$LD -shared${allow_undefined_flag} $libobjs $deplibs $linker_flags -msym -soname $soname `test -n "$verstring" && echo -set_version $verstring` -update_registry ${output_objdir}/so_locations -o $lib' + _LT_AC_TAGVAR(archive_expsym_cmds, $1)='for i in `cat $export_symbols`; do printf "%s %s\\n" -exported_symbol "\$i" >> $lib.exp; done; echo "-hidden">> $lib.exp~ + $LD -shared${allow_undefined_flag} -input $lib.exp $linker_flags $libobjs $deplibs -soname $soname `test -n "$verstring" && echo -set_version $verstring` -update_registry ${objdir}/so_locations -o $lib~$rm $lib.exp' + + # Both c and cxx compiler support -rpath directly + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='-rpath $libdir' + fi + _LT_AC_TAGVAR(hardcode_libdir_separator, $1)=: + ;; + + sco3.2v5*) + _LT_AC_TAGVAR(archive_cmds, $1)='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' + _LT_AC_TAGVAR(hardcode_shlibpath_var, $1)=no + _LT_AC_TAGVAR(export_dynamic_flag_spec, $1)='${wl}-Bexport' + runpath_var=LD_RUN_PATH + hardcode_runpath_var=yes + ;; + + solaris*) + _LT_AC_TAGVAR(no_undefined_flag, $1)=' -z text' + if test "$GCC" = yes; then + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -shared ${wl}-h ${wl}$soname -o $lib $compiler_flags $libobjs $deplibs' + _LT_AC_TAGVAR(archive_expsym_cmds, $1)='$echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~$echo "local: *; };" >> $lib.exp~ + $CC -shared ${wl}-M ${wl}$lib.exp ${wl}-h ${wl}$soname -o $lib $compiler_flags $libobjs $deplibs~$rm $lib.exp' + else + _LT_AC_TAGVAR(archive_cmds, $1)='$LD -G${allow_undefined_flag} -h $soname -o $lib $libobjs $deplibs $linker_flags' + _LT_AC_TAGVAR(archive_expsym_cmds, $1)='$echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~$echo "local: *; };" >> $lib.exp~ + $LD -G${allow_undefined_flag} -M $lib.exp -h $soname -o $lib $libobjs $deplibs $linker_flags~$rm $lib.exp' + fi + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='-R$libdir' + _LT_AC_TAGVAR(hardcode_shlibpath_var, $1)=no + case $host_os in + solaris2.[[0-5]] | solaris2.[[0-5]].*) ;; + *) # Supported since Solaris 2.6 (maybe 2.5.1?) + _LT_AC_TAGVAR(whole_archive_flag_spec, $1)='-z allextract$convenience -z defaultextract' ;; + esac + _LT_AC_TAGVAR(link_all_deplibs, $1)=yes + ;; + + sunos4*) + if test "x$host_vendor" = xsequent; then + # Use $CC to link under sequent, because it throws in some extra .o + # files that make .init and .fini sections work. + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -G ${wl}-h $soname -o $lib $compiler_flags $libobjs $deplibs' + else + _LT_AC_TAGVAR(archive_cmds, $1)='$LD -assert pure-text -Bstatic -o $lib $libobjs $deplibs $linker_flags' + fi + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' + _LT_AC_TAGVAR(hardcode_direct, $1)=yes + _LT_AC_TAGVAR(hardcode_minus_L, $1)=yes + _LT_AC_TAGVAR(hardcode_shlibpath_var, $1)=no + ;; + + sysv4) + case $host_vendor in + sni) + _LT_AC_TAGVAR(archive_cmds, $1)='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' + _LT_AC_TAGVAR(hardcode_direct, $1)=yes # is this really true??? + ;; + siemens) + ## LD is ld it makes a PLAMLIB + ## CC just makes a GrossModule. + _LT_AC_TAGVAR(archive_cmds, $1)='$LD -G -o $lib $libobjs $deplibs $linker_flags' + _LT_AC_TAGVAR(reload_cmds, $1)='$CC -r -o $output$reload_objs' + _LT_AC_TAGVAR(hardcode_direct, $1)=no + ;; + motorola) + _LT_AC_TAGVAR(archive_cmds, $1)='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' + _LT_AC_TAGVAR(hardcode_direct, $1)=no #Motorola manual says yes, but my tests say they lie + ;; + esac + runpath_var='LD_RUN_PATH' + _LT_AC_TAGVAR(hardcode_shlibpath_var, $1)=no + ;; + + sysv4.3*) + _LT_AC_TAGVAR(archive_cmds, $1)='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' + _LT_AC_TAGVAR(hardcode_shlibpath_var, $1)=no + _LT_AC_TAGVAR(export_dynamic_flag_spec, $1)='-Bexport' + ;; + + sysv4*MP*) + if test -d /usr/nec; then + _LT_AC_TAGVAR(archive_cmds, $1)='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' + _LT_AC_TAGVAR(hardcode_shlibpath_var, $1)=no + runpath_var=LD_RUN_PATH + hardcode_runpath_var=yes + _LT_AC_TAGVAR(ld_shlibs, $1)=yes + fi + ;; + + sysv4.2uw2*) + _LT_AC_TAGVAR(archive_cmds, $1)='$LD -G -o $lib $libobjs $deplibs $linker_flags' + _LT_AC_TAGVAR(hardcode_direct, $1)=yes + _LT_AC_TAGVAR(hardcode_minus_L, $1)=no + _LT_AC_TAGVAR(hardcode_shlibpath_var, $1)=no + hardcode_runpath_var=yes + runpath_var=LD_RUN_PATH + ;; + + sysv5OpenUNIX8* | sysv5UnixWare7* | sysv5uw[[78]]* | unixware7*) + _LT_AC_TAGVAR(no_undefined_flag, $1)='${wl}-z ${wl}text' + if test "$GCC" = yes; then + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -shared ${wl}-h ${wl}$soname -o $lib $compiler_flags $libobjs $deplibs' + else + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -G ${wl}-h ${wl}$soname -o $lib $compiler_flags $libobjs $deplibs' + fi + runpath_var='LD_RUN_PATH' + _LT_AC_TAGVAR(hardcode_shlibpath_var, $1)=no + ;; + + sysv5*) + _LT_AC_TAGVAR(no_undefined_flag, $1)=' -z text' + # $CC -shared without GNU ld will not create a library from C++ + # object files and a static libstdc++, better avoid it by now + _LT_AC_TAGVAR(archive_cmds, $1)='$LD -G${allow_undefined_flag} -h $soname -o $lib $libobjs $deplibs $linker_flags' + _LT_AC_TAGVAR(archive_expsym_cmds, $1)='$echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~$echo "local: *; };" >> $lib.exp~ + $LD -G${allow_undefined_flag} -M $lib.exp -h $soname -o $lib $libobjs $deplibs $linker_flags~$rm $lib.exp' + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)= + _LT_AC_TAGVAR(hardcode_shlibpath_var, $1)=no + runpath_var='LD_RUN_PATH' + ;; + + uts4*) + _LT_AC_TAGVAR(archive_cmds, $1)='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' + _LT_AC_TAGVAR(hardcode_shlibpath_var, $1)=no + ;; + + *) + _LT_AC_TAGVAR(ld_shlibs, $1)=no + ;; + esac + fi +]) +AC_MSG_RESULT([$_LT_AC_TAGVAR(ld_shlibs, $1)]) +test "$_LT_AC_TAGVAR(ld_shlibs, $1)" = no && can_build_shared=no + +variables_saved_for_relink="PATH $shlibpath_var $runpath_var" +if test "$GCC" = yes; then + variables_saved_for_relink="$variables_saved_for_relink GCC_EXEC_PREFIX COMPILER_PATH LIBRARY_PATH" +fi + +# +# Do we need to explicitly link libc? +# +case "x$_LT_AC_TAGVAR(archive_cmds_need_lc, $1)" in +x|xyes) + # Assume -lc should be added + _LT_AC_TAGVAR(archive_cmds_need_lc, $1)=yes + + if test "$enable_shared" = yes && test "$GCC" = yes; then + case $_LT_AC_TAGVAR(archive_cmds, $1) in + *'~'*) + # FIXME: we may have to deal with multi-command sequences. + ;; + '$CC '*) + # Test whether the compiler implicitly links with -lc since on some + # systems, -lgcc has to come before -lc. If gcc already passes -lc + # to ld, don't add -lc before -lgcc. + AC_MSG_CHECKING([whether -lc should be explicitly linked in]) + $rm conftest* + printf "$lt_simple_compile_test_code" > conftest.$ac_ext + + if AC_TRY_EVAL(ac_compile) 2>conftest.err; then + soname=conftest + lib=conftest + libobjs=conftest.$ac_objext + deplibs= + wl=$_LT_AC_TAGVAR(lt_prog_compiler_wl, $1) + compiler_flags=-v + linker_flags=-v + verstring= + output_objdir=. + libname=conftest + lt_save_allow_undefined_flag=$_LT_AC_TAGVAR(allow_undefined_flag, $1) + _LT_AC_TAGVAR(allow_undefined_flag, $1)= + if AC_TRY_EVAL(_LT_AC_TAGVAR(archive_cmds, $1) 2\>\&1 \| grep \" -lc \" \>/dev/null 2\>\&1) + then + _LT_AC_TAGVAR(archive_cmds_need_lc, $1)=no + else + _LT_AC_TAGVAR(archive_cmds_need_lc, $1)=yes + fi + _LT_AC_TAGVAR(allow_undefined_flag, $1)=$lt_save_allow_undefined_flag + else + cat conftest.err 1>&5 + fi + $rm conftest* + AC_MSG_RESULT([$_LT_AC_TAGVAR(archive_cmds_need_lc, $1)]) + ;; + esac + fi + ;; +esac +])# AC_LIBTOOL_PROG_LD_SHLIBS + + +# _LT_AC_FILE_LTDLL_C +# ------------------- +# Be careful that the start marker always follows a newline. +AC_DEFUN([_LT_AC_FILE_LTDLL_C], [ +# /* ltdll.c starts here */ +# #define WIN32_LEAN_AND_MEAN +# #include +# #undef WIN32_LEAN_AND_MEAN +# #include +# +# #ifndef __CYGWIN__ +# # ifdef __CYGWIN32__ +# # define __CYGWIN__ __CYGWIN32__ +# # endif +# #endif +# +# #ifdef __cplusplus +# extern "C" { +# #endif +# BOOL APIENTRY DllMain (HINSTANCE hInst, DWORD reason, LPVOID reserved); +# #ifdef __cplusplus +# } +# #endif +# +# #ifdef __CYGWIN__ +# #include +# DECLARE_CYGWIN_DLL( DllMain ); +# #endif +# HINSTANCE __hDllInstance_base; +# +# BOOL APIENTRY +# DllMain (HINSTANCE hInst, DWORD reason, LPVOID reserved) +# { +# __hDllInstance_base = hInst; +# return TRUE; +# } +# /* ltdll.c ends here */ +])# _LT_AC_FILE_LTDLL_C + + +# _LT_AC_TAGVAR(VARNAME, [TAGNAME]) +# --------------------------------- +AC_DEFUN([_LT_AC_TAGVAR], [ifelse([$2], [], [$1], [$1_$2])]) + + +# old names +AC_DEFUN([AM_PROG_LIBTOOL], [AC_PROG_LIBTOOL]) +AC_DEFUN([AM_ENABLE_SHARED], [AC_ENABLE_SHARED($@)]) +AC_DEFUN([AM_ENABLE_STATIC], [AC_ENABLE_STATIC($@)]) +AC_DEFUN([AM_DISABLE_SHARED], [AC_DISABLE_SHARED($@)]) +AC_DEFUN([AM_DISABLE_STATIC], [AC_DISABLE_STATIC($@)]) +AC_DEFUN([AM_PROG_LD], [AC_PROG_LD]) +AC_DEFUN([AM_PROG_NM], [AC_PROG_NM]) + +# This is just to silence aclocal about the macro not being used +ifelse([AC_DISABLE_FAST_INSTALL]) + +AC_DEFUN([LT_AC_PROG_GCJ], +[AC_CHECK_TOOL(GCJ, gcj, no) + test "x${GCJFLAGS+set}" = xset || GCJFLAGS="-g -O2" + AC_SUBST(GCJFLAGS) +]) + +AC_DEFUN([LT_AC_PROG_RC], +[AC_CHECK_TOOL(RC, windres, no) +]) + +############################################################ +# NOTE: This macro has been submitted for inclusion into # +# GNU Autoconf as AC_PROG_SED. When it is available in # +# a released version of Autoconf we should remove this # +# macro and use it instead. # +############################################################ +# LT_AC_PROG_SED +# -------------- +# Check for a fully-functional sed program, that truncates +# as few characters as possible. Prefer GNU sed if found. +AC_DEFUN([LT_AC_PROG_SED], +[AC_MSG_CHECKING([for a sed that does not truncate output]) +AC_CACHE_VAL(lt_cv_path_SED, +[# Loop through the user's path and test for sed and gsed. +# Then use that list of sed's as ones to test for truncation. +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for lt_ac_prog in sed gsed; do + for ac_exec_ext in '' $ac_executable_extensions; do + if $as_executable_p "$as_dir/$lt_ac_prog$ac_exec_ext"; then + lt_ac_sed_list="$lt_ac_sed_list $as_dir/$lt_ac_prog$ac_exec_ext" + fi + done + done +done +lt_ac_max=0 +lt_ac_count=0 +# Add /usr/xpg4/bin/sed as it is typically found on Solaris +# along with /bin/sed that truncates output. +for lt_ac_sed in $lt_ac_sed_list /usr/xpg4/bin/sed; do + test ! -f $lt_ac_sed && break + cat /dev/null > conftest.in + lt_ac_count=0 + echo $ECHO_N "0123456789$ECHO_C" >conftest.in + # Check for GNU sed and select it if it is found. + if "$lt_ac_sed" --version 2>&1 < /dev/null | grep 'GNU' > /dev/null; then + lt_cv_path_SED=$lt_ac_sed + break + fi + while true; do + cat conftest.in conftest.in >conftest.tmp + mv conftest.tmp conftest.in + cp conftest.in conftest.nl + echo >>conftest.nl + $lt_ac_sed -e 's/a$//' < conftest.nl >conftest.out || break + cmp -s conftest.out conftest.nl || break + # 10000 chars as input seems more than enough + test $lt_ac_count -gt 10 && break + lt_ac_count=`expr $lt_ac_count + 1` + if test $lt_ac_count -gt $lt_ac_max; then + lt_ac_max=$lt_ac_count + lt_cv_path_SED=$lt_ac_sed + fi + done +done +]) +SED=$lt_cv_path_SED +AC_MSG_RESULT([$SED]) +]) + +dnl PKG_CHECK_MODULES(GSTUFF, gtk+-2.0 >= 1.3 glib = 1.3.4, action-if, action-not) +dnl defines GSTUFF_LIBS, GSTUFF_CFLAGS, see pkg-config man page +dnl also defines GSTUFF_PKG_ERRORS on error +AC_DEFUN([PKG_CHECK_MODULES], [ + succeeded=no + + if test -z "$PKG_CONFIG"; then + AC_PATH_PROG(PKG_CONFIG, pkg-config, no) + fi + + if test "$PKG_CONFIG" = "no" ; then + echo "*** The pkg-config script could not be found. Make sure it is" + echo "*** in your path, or set the PKG_CONFIG environment variable" + echo "*** to the full path to pkg-config." + echo "*** Or see http://www.freedesktop.org/software/pkgconfig to get pkg-config." + else + PKG_CONFIG_MIN_VERSION=0.9.0 + if $PKG_CONFIG --atleast-pkgconfig-version $PKG_CONFIG_MIN_VERSION; then + AC_MSG_CHECKING(for $2) + + if $PKG_CONFIG --exists "$2" ; then + AC_MSG_RESULT(yes) + succeeded=yes + + AC_MSG_CHECKING($1_CFLAGS) + $1_CFLAGS=`$PKG_CONFIG --cflags "$2"` + AC_MSG_RESULT($$1_CFLAGS) + + AC_MSG_CHECKING($1_LIBS) + $1_LIBS=`$PKG_CONFIG --libs "$2"` + AC_MSG_RESULT($$1_LIBS) + else + $1_CFLAGS="" + $1_LIBS="" + ## If we have a custom action on failure, don't print errors, but + ## do set a variable so people can do so. + $1_PKG_ERRORS=`$PKG_CONFIG --errors-to-stdout --print-errors "$2"` + ifelse([$4], ,echo $$1_PKG_ERRORS,) + fi + + AC_SUBST($1_CFLAGS) + AC_SUBST($1_LIBS) + else + echo "*** Your version of pkg-config is too old. You need version $PKG_CONFIG_MIN_VERSION or newer." + echo "*** See http://www.freedesktop.org/software/pkgconfig" + fi + fi + + if test $succeeded = yes; then + ifelse([$3], , :, [$3]) + else + ifelse([$4], , AC_MSG_ERROR([Library requirements ($2) not met; consider adjusting the PKG_CONFIG_PATH environment variable if your libraries are in a nonstandard prefix so pkg-config can find them.]), [$4]) + fi +]) + + diff --git a/aclocal.m4 b/aclocal.m4 new file mode 100644 index 00000000..986ff00c --- /dev/null +++ b/aclocal.m4 @@ -0,0 +1,881 @@ +# generated automatically by aclocal 1.10.1 -*- Autoconf -*- + +# Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, +# 2005, 2006, 2007, 2008 Free Software Foundation, Inc. +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +m4_ifndef([AC_AUTOCONF_VERSION], + [m4_copy([m4_PACKAGE_VERSION], [AC_AUTOCONF_VERSION])])dnl +m4_if(AC_AUTOCONF_VERSION, [2.61],, +[m4_warning([this file was generated for autoconf 2.61. +You have another version of autoconf. It may work, but is not guaranteed to. +If you have problems, you may need to regenerate the build system entirely. +To do so, use the procedure documented by the package, typically `autoreconf'.])]) + +# Copyright (C) 2002, 2003, 2005, 2006, 2007 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# AM_AUTOMAKE_VERSION(VERSION) +# ---------------------------- +# Automake X.Y traces this macro to ensure aclocal.m4 has been +# generated from the m4 files accompanying Automake X.Y. +# (This private macro should not be called outside this file.) +AC_DEFUN([AM_AUTOMAKE_VERSION], +[am__api_version='1.10' +dnl Some users find AM_AUTOMAKE_VERSION and mistake it for a way to +dnl require some minimum version. Point them to the right macro. +m4_if([$1], [1.10.1], [], + [AC_FATAL([Do not call $0, use AM_INIT_AUTOMAKE([$1]).])])dnl +]) + +# _AM_AUTOCONF_VERSION(VERSION) +# ----------------------------- +# aclocal traces this macro to find the Autoconf version. +# This is a private macro too. Using m4_define simplifies +# the logic in aclocal, which can simply ignore this definition. +m4_define([_AM_AUTOCONF_VERSION], []) + +# AM_SET_CURRENT_AUTOMAKE_VERSION +# ------------------------------- +# Call AM_AUTOMAKE_VERSION and AM_AUTOMAKE_VERSION so they can be traced. +# This function is AC_REQUIREd by AC_INIT_AUTOMAKE. +AC_DEFUN([AM_SET_CURRENT_AUTOMAKE_VERSION], +[AM_AUTOMAKE_VERSION([1.10.1])dnl +m4_ifndef([AC_AUTOCONF_VERSION], + [m4_copy([m4_PACKAGE_VERSION], [AC_AUTOCONF_VERSION])])dnl +_AM_AUTOCONF_VERSION(AC_AUTOCONF_VERSION)]) + +# AM_AUX_DIR_EXPAND -*- Autoconf -*- + +# Copyright (C) 2001, 2003, 2005 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# For projects using AC_CONFIG_AUX_DIR([foo]), Autoconf sets +# $ac_aux_dir to `$srcdir/foo'. In other projects, it is set to +# `$srcdir', `$srcdir/..', or `$srcdir/../..'. +# +# Of course, Automake must honor this variable whenever it calls a +# tool from the auxiliary directory. The problem is that $srcdir (and +# therefore $ac_aux_dir as well) can be either absolute or relative, +# depending on how configure is run. This is pretty annoying, since +# it makes $ac_aux_dir quite unusable in subdirectories: in the top +# source directory, any form will work fine, but in subdirectories a +# relative path needs to be adjusted first. +# +# $ac_aux_dir/missing +# fails when called from a subdirectory if $ac_aux_dir is relative +# $top_srcdir/$ac_aux_dir/missing +# fails if $ac_aux_dir is absolute, +# fails when called from a subdirectory in a VPATH build with +# a relative $ac_aux_dir +# +# The reason of the latter failure is that $top_srcdir and $ac_aux_dir +# are both prefixed by $srcdir. In an in-source build this is usually +# harmless because $srcdir is `.', but things will broke when you +# start a VPATH build or use an absolute $srcdir. +# +# So we could use something similar to $top_srcdir/$ac_aux_dir/missing, +# iff we strip the leading $srcdir from $ac_aux_dir. That would be: +# am_aux_dir='\$(top_srcdir)/'`expr "$ac_aux_dir" : "$srcdir//*\(.*\)"` +# and then we would define $MISSING as +# MISSING="\${SHELL} $am_aux_dir/missing" +# This will work as long as MISSING is not called from configure, because +# unfortunately $(top_srcdir) has no meaning in configure. +# However there are other variables, like CC, which are often used in +# configure, and could therefore not use this "fixed" $ac_aux_dir. +# +# Another solution, used here, is to always expand $ac_aux_dir to an +# absolute PATH. The drawback is that using absolute paths prevent a +# configured tree to be moved without reconfiguration. + +AC_DEFUN([AM_AUX_DIR_EXPAND], +[dnl Rely on autoconf to set up CDPATH properly. +AC_PREREQ([2.50])dnl +# expand $ac_aux_dir to an absolute path +am_aux_dir=`cd $ac_aux_dir && pwd` +]) + +# AM_CONDITIONAL -*- Autoconf -*- + +# Copyright (C) 1997, 2000, 2001, 2003, 2004, 2005, 2006 +# Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# serial 8 + +# AM_CONDITIONAL(NAME, SHELL-CONDITION) +# ------------------------------------- +# Define a conditional. +AC_DEFUN([AM_CONDITIONAL], +[AC_PREREQ(2.52)dnl + ifelse([$1], [TRUE], [AC_FATAL([$0: invalid condition: $1])], + [$1], [FALSE], [AC_FATAL([$0: invalid condition: $1])])dnl +AC_SUBST([$1_TRUE])dnl +AC_SUBST([$1_FALSE])dnl +_AM_SUBST_NOTMAKE([$1_TRUE])dnl +_AM_SUBST_NOTMAKE([$1_FALSE])dnl +if $2; then + $1_TRUE= + $1_FALSE='#' +else + $1_TRUE='#' + $1_FALSE= +fi +AC_CONFIG_COMMANDS_PRE( +[if test -z "${$1_TRUE}" && test -z "${$1_FALSE}"; then + AC_MSG_ERROR([[conditional "$1" was never defined. +Usually this means the macro was only invoked conditionally.]]) +fi])]) + +# Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006 +# Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# serial 9 + +# There are a few dirty hacks below to avoid letting `AC_PROG_CC' be +# written in clear, in which case automake, when reading aclocal.m4, +# will think it sees a *use*, and therefore will trigger all it's +# C support machinery. Also note that it means that autoscan, seeing +# CC etc. in the Makefile, will ask for an AC_PROG_CC use... + + +# _AM_DEPENDENCIES(NAME) +# ---------------------- +# See how the compiler implements dependency checking. +# NAME is "CC", "CXX", "GCJ", or "OBJC". +# We try a few techniques and use that to set a single cache variable. +# +# We don't AC_REQUIRE the corresponding AC_PROG_CC since the latter was +# modified to invoke _AM_DEPENDENCIES(CC); we would have a circular +# dependency, and given that the user is not expected to run this macro, +# just rely on AC_PROG_CC. +AC_DEFUN([_AM_DEPENDENCIES], +[AC_REQUIRE([AM_SET_DEPDIR])dnl +AC_REQUIRE([AM_OUTPUT_DEPENDENCY_COMMANDS])dnl +AC_REQUIRE([AM_MAKE_INCLUDE])dnl +AC_REQUIRE([AM_DEP_TRACK])dnl + +ifelse([$1], CC, [depcc="$CC" am_compiler_list=], + [$1], CXX, [depcc="$CXX" am_compiler_list=], + [$1], OBJC, [depcc="$OBJC" am_compiler_list='gcc3 gcc'], + [$1], UPC, [depcc="$UPC" am_compiler_list=], + [$1], GCJ, [depcc="$GCJ" am_compiler_list='gcc3 gcc'], + [depcc="$$1" am_compiler_list=]) + +AC_CACHE_CHECK([dependency style of $depcc], + [am_cv_$1_dependencies_compiler_type], +[if test -z "$AMDEP_TRUE" && test -f "$am_depcomp"; then + # We make a subdir and do the tests there. Otherwise we can end up + # making bogus files that we don't know about and never remove. For + # instance it was reported that on HP-UX the gcc test will end up + # making a dummy file named `D' -- because `-MD' means `put the output + # in D'. + mkdir conftest.dir + # Copy depcomp to subdir because otherwise we won't find it if we're + # using a relative directory. + cp "$am_depcomp" conftest.dir + cd conftest.dir + # We will build objects and dependencies in a subdirectory because + # it helps to detect inapplicable dependency modes. For instance + # both Tru64's cc and ICC support -MD to output dependencies as a + # side effect of compilation, but ICC will put the dependencies in + # the current directory while Tru64 will put them in the object + # directory. + mkdir sub + + am_cv_$1_dependencies_compiler_type=none + if test "$am_compiler_list" = ""; then + am_compiler_list=`sed -n ['s/^#*\([a-zA-Z0-9]*\))$/\1/p'] < ./depcomp` + fi + for depmode in $am_compiler_list; do + # Setup a source with many dependencies, because some compilers + # like to wrap large dependency lists on column 80 (with \), and + # we should not choose a depcomp mode which is confused by this. + # + # We need to recreate these files for each test, as the compiler may + # overwrite some of them when testing with obscure command lines. + # This happens at least with the AIX C compiler. + : > sub/conftest.c + for i in 1 2 3 4 5 6; do + echo '#include "conftst'$i'.h"' >> sub/conftest.c + # Using `: > sub/conftst$i.h' creates only sub/conftst1.h with + # Solaris 8's {/usr,}/bin/sh. + touch sub/conftst$i.h + done + echo "${am__include} ${am__quote}sub/conftest.Po${am__quote}" > confmf + + case $depmode in + nosideeffect) + # after this tag, mechanisms are not by side-effect, so they'll + # only be used when explicitly requested + if test "x$enable_dependency_tracking" = xyes; then + continue + else + break + fi + ;; + none) break ;; + esac + # We check with `-c' and `-o' for the sake of the "dashmstdout" + # mode. It turns out that the SunPro C++ compiler does not properly + # handle `-M -o', and we need to detect this. + if depmode=$depmode \ + source=sub/conftest.c object=sub/conftest.${OBJEXT-o} \ + depfile=sub/conftest.Po tmpdepfile=sub/conftest.TPo \ + $SHELL ./depcomp $depcc -c -o sub/conftest.${OBJEXT-o} sub/conftest.c \ + >/dev/null 2>conftest.err && + grep sub/conftst1.h sub/conftest.Po > /dev/null 2>&1 && + grep sub/conftst6.h sub/conftest.Po > /dev/null 2>&1 && + grep sub/conftest.${OBJEXT-o} sub/conftest.Po > /dev/null 2>&1 && + ${MAKE-make} -s -f confmf > /dev/null 2>&1; then + # icc doesn't choke on unknown options, it will just issue warnings + # or remarks (even with -Werror). So we grep stderr for any message + # that says an option was ignored or not supported. + # When given -MP, icc 7.0 and 7.1 complain thusly: + # icc: Command line warning: ignoring option '-M'; no argument required + # The diagnosis changed in icc 8.0: + # icc: Command line remark: option '-MP' not supported + if (grep 'ignoring option' conftest.err || + grep 'not supported' conftest.err) >/dev/null 2>&1; then :; else + am_cv_$1_dependencies_compiler_type=$depmode + break + fi + fi + done + + cd .. + rm -rf conftest.dir +else + am_cv_$1_dependencies_compiler_type=none +fi +]) +AC_SUBST([$1DEPMODE], [depmode=$am_cv_$1_dependencies_compiler_type]) +AM_CONDITIONAL([am__fastdep$1], [ + test "x$enable_dependency_tracking" != xno \ + && test "$am_cv_$1_dependencies_compiler_type" = gcc3]) +]) + + +# AM_SET_DEPDIR +# ------------- +# Choose a directory name for dependency files. +# This macro is AC_REQUIREd in _AM_DEPENDENCIES +AC_DEFUN([AM_SET_DEPDIR], +[AC_REQUIRE([AM_SET_LEADING_DOT])dnl +AC_SUBST([DEPDIR], ["${am__leading_dot}deps"])dnl +]) + + +# AM_DEP_TRACK +# ------------ +AC_DEFUN([AM_DEP_TRACK], +[AC_ARG_ENABLE(dependency-tracking, +[ --disable-dependency-tracking speeds up one-time build + --enable-dependency-tracking do not reject slow dependency extractors]) +if test "x$enable_dependency_tracking" != xno; then + am_depcomp="$ac_aux_dir/depcomp" + AMDEPBACKSLASH='\' +fi +AM_CONDITIONAL([AMDEP], [test "x$enable_dependency_tracking" != xno]) +AC_SUBST([AMDEPBACKSLASH])dnl +_AM_SUBST_NOTMAKE([AMDEPBACKSLASH])dnl +]) + +# Generate code to set up dependency tracking. -*- Autoconf -*- + +# Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005 +# Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +#serial 3 + +# _AM_OUTPUT_DEPENDENCY_COMMANDS +# ------------------------------ +AC_DEFUN([_AM_OUTPUT_DEPENDENCY_COMMANDS], +[for mf in $CONFIG_FILES; do + # Strip MF so we end up with the name of the file. + mf=`echo "$mf" | sed -e 's/:.*$//'` + # Check whether this is an Automake generated Makefile or not. + # We used to match only the files named `Makefile.in', but + # some people rename them; so instead we look at the file content. + # Grep'ing the first line is not enough: some people post-process + # each Makefile.in and add a new line on top of each file to say so. + # Grep'ing the whole file is not good either: AIX grep has a line + # limit of 2048, but all sed's we know have understand at least 4000. + if sed -n 's,^#.*generated by automake.*,X,p' "$mf" | grep X >/dev/null 2>&1; then + dirpart=`AS_DIRNAME("$mf")` + else + continue + fi + # Extract the definition of DEPDIR, am__include, and am__quote + # from the Makefile without running `make'. + DEPDIR=`sed -n 's/^DEPDIR = //p' < "$mf"` + test -z "$DEPDIR" && continue + am__include=`sed -n 's/^am__include = //p' < "$mf"` + test -z "am__include" && continue + am__quote=`sed -n 's/^am__quote = //p' < "$mf"` + # When using ansi2knr, U may be empty or an underscore; expand it + U=`sed -n 's/^U = //p' < "$mf"` + # Find all dependency output files, they are included files with + # $(DEPDIR) in their names. We invoke sed twice because it is the + # simplest approach to changing $(DEPDIR) to its actual value in the + # expansion. + for file in `sed -n " + s/^$am__include $am__quote\(.*(DEPDIR).*\)$am__quote"'$/\1/p' <"$mf" | \ + sed -e 's/\$(DEPDIR)/'"$DEPDIR"'/g' -e 's/\$U/'"$U"'/g'`; do + # Make sure the directory exists. + test -f "$dirpart/$file" && continue + fdir=`AS_DIRNAME(["$file"])` + AS_MKDIR_P([$dirpart/$fdir]) + # echo "creating $dirpart/$file" + echo '# dummy' > "$dirpart/$file" + done +done +])# _AM_OUTPUT_DEPENDENCY_COMMANDS + + +# AM_OUTPUT_DEPENDENCY_COMMANDS +# ----------------------------- +# This macro should only be invoked once -- use via AC_REQUIRE. +# +# This code is only required when automatic dependency tracking +# is enabled. FIXME. This creates each `.P' file that we will +# need in order to bootstrap the dependency handling code. +AC_DEFUN([AM_OUTPUT_DEPENDENCY_COMMANDS], +[AC_CONFIG_COMMANDS([depfiles], + [test x"$AMDEP_TRUE" != x"" || _AM_OUTPUT_DEPENDENCY_COMMANDS], + [AMDEP_TRUE="$AMDEP_TRUE" ac_aux_dir="$ac_aux_dir"]) +]) + +# Copyright (C) 1996, 1997, 2000, 2001, 2003, 2005 +# Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# serial 8 + +# AM_CONFIG_HEADER is obsolete. It has been replaced by AC_CONFIG_HEADERS. +AU_DEFUN([AM_CONFIG_HEADER], [AC_CONFIG_HEADERS($@)]) + +# Do all the work for Automake. -*- Autoconf -*- + +# Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, +# 2005, 2006, 2008 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# serial 13 + +# This macro actually does too much. Some checks are only needed if +# your package does certain things. But this isn't really a big deal. + +# AM_INIT_AUTOMAKE(PACKAGE, VERSION, [NO-DEFINE]) +# AM_INIT_AUTOMAKE([OPTIONS]) +# ----------------------------------------------- +# The call with PACKAGE and VERSION arguments is the old style +# call (pre autoconf-2.50), which is being phased out. PACKAGE +# and VERSION should now be passed to AC_INIT and removed from +# the call to AM_INIT_AUTOMAKE. +# We support both call styles for the transition. After +# the next Automake release, Autoconf can make the AC_INIT +# arguments mandatory, and then we can depend on a new Autoconf +# release and drop the old call support. +AC_DEFUN([AM_INIT_AUTOMAKE], +[AC_PREREQ([2.60])dnl +dnl Autoconf wants to disallow AM_ names. We explicitly allow +dnl the ones we care about. +m4_pattern_allow([^AM_[A-Z]+FLAGS$])dnl +AC_REQUIRE([AM_SET_CURRENT_AUTOMAKE_VERSION])dnl +AC_REQUIRE([AC_PROG_INSTALL])dnl +if test "`cd $srcdir && pwd`" != "`pwd`"; then + # Use -I$(srcdir) only when $(srcdir) != ., so that make's output + # is not polluted with repeated "-I." + AC_SUBST([am__isrc], [' -I$(srcdir)'])_AM_SUBST_NOTMAKE([am__isrc])dnl + # test to see if srcdir already configured + if test -f $srcdir/config.status; then + AC_MSG_ERROR([source directory already configured; run "make distclean" there first]) + fi +fi + +# test whether we have cygpath +if test -z "$CYGPATH_W"; then + if (cygpath --version) >/dev/null 2>/dev/null; then + CYGPATH_W='cygpath -w' + else + CYGPATH_W=echo + fi +fi +AC_SUBST([CYGPATH_W]) + +# Define the identity of the package. +dnl Distinguish between old-style and new-style calls. +m4_ifval([$2], +[m4_ifval([$3], [_AM_SET_OPTION([no-define])])dnl + AC_SUBST([PACKAGE], [$1])dnl + AC_SUBST([VERSION], [$2])], +[_AM_SET_OPTIONS([$1])dnl +dnl Diagnose old-style AC_INIT with new-style AM_AUTOMAKE_INIT. +m4_if(m4_ifdef([AC_PACKAGE_NAME], 1)m4_ifdef([AC_PACKAGE_VERSION], 1), 11,, + [m4_fatal([AC_INIT should be called with package and version arguments])])dnl + AC_SUBST([PACKAGE], ['AC_PACKAGE_TARNAME'])dnl + AC_SUBST([VERSION], ['AC_PACKAGE_VERSION'])])dnl + +_AM_IF_OPTION([no-define],, +[AC_DEFINE_UNQUOTED(PACKAGE, "$PACKAGE", [Name of package]) + AC_DEFINE_UNQUOTED(VERSION, "$VERSION", [Version number of package])])dnl + +# Some tools Automake needs. +AC_REQUIRE([AM_SANITY_CHECK])dnl +AC_REQUIRE([AC_ARG_PROGRAM])dnl +AM_MISSING_PROG(ACLOCAL, aclocal-${am__api_version}) +AM_MISSING_PROG(AUTOCONF, autoconf) +AM_MISSING_PROG(AUTOMAKE, automake-${am__api_version}) +AM_MISSING_PROG(AUTOHEADER, autoheader) +AM_MISSING_PROG(MAKEINFO, makeinfo) +AM_PROG_INSTALL_SH +AM_PROG_INSTALL_STRIP +AC_REQUIRE([AM_PROG_MKDIR_P])dnl +# We need awk for the "check" target. The system "awk" is bad on +# some platforms. +AC_REQUIRE([AC_PROG_AWK])dnl +AC_REQUIRE([AC_PROG_MAKE_SET])dnl +AC_REQUIRE([AM_SET_LEADING_DOT])dnl +_AM_IF_OPTION([tar-ustar], [_AM_PROG_TAR([ustar])], + [_AM_IF_OPTION([tar-pax], [_AM_PROG_TAR([pax])], + [_AM_PROG_TAR([v7])])]) +_AM_IF_OPTION([no-dependencies],, +[AC_PROVIDE_IFELSE([AC_PROG_CC], + [_AM_DEPENDENCIES(CC)], + [define([AC_PROG_CC], + defn([AC_PROG_CC])[_AM_DEPENDENCIES(CC)])])dnl +AC_PROVIDE_IFELSE([AC_PROG_CXX], + [_AM_DEPENDENCIES(CXX)], + [define([AC_PROG_CXX], + defn([AC_PROG_CXX])[_AM_DEPENDENCIES(CXX)])])dnl +AC_PROVIDE_IFELSE([AC_PROG_OBJC], + [_AM_DEPENDENCIES(OBJC)], + [define([AC_PROG_OBJC], + defn([AC_PROG_OBJC])[_AM_DEPENDENCIES(OBJC)])])dnl +]) +]) + + +# When config.status generates a header, we must update the stamp-h file. +# This file resides in the same directory as the config header +# that is generated. The stamp files are numbered to have different names. + +# Autoconf calls _AC_AM_CONFIG_HEADER_HOOK (when defined) in the +# loop where config.status creates the headers, so we can generate +# our stamp files there. +AC_DEFUN([_AC_AM_CONFIG_HEADER_HOOK], +[# Compute $1's index in $config_headers. +_am_arg=$1 +_am_stamp_count=1 +for _am_header in $config_headers :; do + case $_am_header in + $_am_arg | $_am_arg:* ) + break ;; + * ) + _am_stamp_count=`expr $_am_stamp_count + 1` ;; + esac +done +echo "timestamp for $_am_arg" >`AS_DIRNAME(["$_am_arg"])`/stamp-h[]$_am_stamp_count]) + +# Copyright (C) 2001, 2003, 2005 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# AM_PROG_INSTALL_SH +# ------------------ +# Define $install_sh. +AC_DEFUN([AM_PROG_INSTALL_SH], +[AC_REQUIRE([AM_AUX_DIR_EXPAND])dnl +install_sh=${install_sh-"\$(SHELL) $am_aux_dir/install-sh"} +AC_SUBST(install_sh)]) + +# Copyright (C) 2003, 2005 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# serial 2 + +# Check whether the underlying file-system supports filenames +# with a leading dot. For instance MS-DOS doesn't. +AC_DEFUN([AM_SET_LEADING_DOT], +[rm -rf .tst 2>/dev/null +mkdir .tst 2>/dev/null +if test -d .tst; then + am__leading_dot=. +else + am__leading_dot=_ +fi +rmdir .tst 2>/dev/null +AC_SUBST([am__leading_dot])]) + +# Check to see how 'make' treats includes. -*- Autoconf -*- + +# Copyright (C) 2001, 2002, 2003, 2005 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# serial 3 + +# AM_MAKE_INCLUDE() +# ----------------- +# Check to see how make treats includes. +AC_DEFUN([AM_MAKE_INCLUDE], +[am_make=${MAKE-make} +cat > confinc << 'END' +am__doit: + @echo done +.PHONY: am__doit +END +# If we don't find an include directive, just comment out the code. +AC_MSG_CHECKING([for style of include used by $am_make]) +am__include="#" +am__quote= +_am_result=none +# First try GNU make style include. +echo "include confinc" > confmf +# We grep out `Entering directory' and `Leaving directory' +# messages which can occur if `w' ends up in MAKEFLAGS. +# In particular we don't look at `^make:' because GNU make might +# be invoked under some other name (usually "gmake"), in which +# case it prints its new name instead of `make'. +if test "`$am_make -s -f confmf 2> /dev/null | grep -v 'ing directory'`" = "done"; then + am__include=include + am__quote= + _am_result=GNU +fi +# Now try BSD make style include. +if test "$am__include" = "#"; then + echo '.include "confinc"' > confmf + if test "`$am_make -s -f confmf 2> /dev/null`" = "done"; then + am__include=.include + am__quote="\"" + _am_result=BSD + fi +fi +AC_SUBST([am__include]) +AC_SUBST([am__quote]) +AC_MSG_RESULT([$_am_result]) +rm -f confinc confmf +]) + +# Fake the existence of programs that GNU maintainers use. -*- Autoconf -*- + +# Copyright (C) 1997, 1999, 2000, 2001, 2003, 2004, 2005 +# Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# serial 5 + +# AM_MISSING_PROG(NAME, PROGRAM) +# ------------------------------ +AC_DEFUN([AM_MISSING_PROG], +[AC_REQUIRE([AM_MISSING_HAS_RUN]) +$1=${$1-"${am_missing_run}$2"} +AC_SUBST($1)]) + + +# AM_MISSING_HAS_RUN +# ------------------ +# Define MISSING if not defined so far and test if it supports --run. +# If it does, set am_missing_run to use it, otherwise, to nothing. +AC_DEFUN([AM_MISSING_HAS_RUN], +[AC_REQUIRE([AM_AUX_DIR_EXPAND])dnl +AC_REQUIRE_AUX_FILE([missing])dnl +test x"${MISSING+set}" = xset || MISSING="\${SHELL} $am_aux_dir/missing" +# Use eval to expand $SHELL +if eval "$MISSING --run true"; then + am_missing_run="$MISSING --run " +else + am_missing_run= + AC_MSG_WARN([`missing' script is too old or missing]) +fi +]) + +# Copyright (C) 2003, 2004, 2005, 2006 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# AM_PROG_MKDIR_P +# --------------- +# Check for `mkdir -p'. +AC_DEFUN([AM_PROG_MKDIR_P], +[AC_PREREQ([2.60])dnl +AC_REQUIRE([AC_PROG_MKDIR_P])dnl +dnl Automake 1.8 to 1.9.6 used to define mkdir_p. We now use MKDIR_P, +dnl while keeping a definition of mkdir_p for backward compatibility. +dnl @MKDIR_P@ is magic: AC_OUTPUT adjusts its value for each Makefile. +dnl However we cannot define mkdir_p as $(MKDIR_P) for the sake of +dnl Makefile.ins that do not define MKDIR_P, so we do our own +dnl adjustment using top_builddir (which is defined more often than +dnl MKDIR_P). +AC_SUBST([mkdir_p], ["$MKDIR_P"])dnl +case $mkdir_p in + [[\\/$]]* | ?:[[\\/]]*) ;; + */*) mkdir_p="\$(top_builddir)/$mkdir_p" ;; +esac +]) + +# Helper functions for option handling. -*- Autoconf -*- + +# Copyright (C) 2001, 2002, 2003, 2005 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# serial 3 + +# _AM_MANGLE_OPTION(NAME) +# ----------------------- +AC_DEFUN([_AM_MANGLE_OPTION], +[[_AM_OPTION_]m4_bpatsubst($1, [[^a-zA-Z0-9_]], [_])]) + +# _AM_SET_OPTION(NAME) +# ------------------------------ +# Set option NAME. Presently that only means defining a flag for this option. +AC_DEFUN([_AM_SET_OPTION], +[m4_define(_AM_MANGLE_OPTION([$1]), 1)]) + +# _AM_SET_OPTIONS(OPTIONS) +# ---------------------------------- +# OPTIONS is a space-separated list of Automake options. +AC_DEFUN([_AM_SET_OPTIONS], +[AC_FOREACH([_AM_Option], [$1], [_AM_SET_OPTION(_AM_Option)])]) + +# _AM_IF_OPTION(OPTION, IF-SET, [IF-NOT-SET]) +# ------------------------------------------- +# Execute IF-SET if OPTION is set, IF-NOT-SET otherwise. +AC_DEFUN([_AM_IF_OPTION], +[m4_ifset(_AM_MANGLE_OPTION([$1]), [$2], [$3])]) + +# Check to make sure that the build environment is sane. -*- Autoconf -*- + +# Copyright (C) 1996, 1997, 2000, 2001, 2003, 2005 +# Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# serial 4 + +# AM_SANITY_CHECK +# --------------- +AC_DEFUN([AM_SANITY_CHECK], +[AC_MSG_CHECKING([whether build environment is sane]) +# Just in case +sleep 1 +echo timestamp > conftest.file +# Do `set' in a subshell so we don't clobber the current shell's +# arguments. Must try -L first in case configure is actually a +# symlink; some systems play weird games with the mod time of symlinks +# (eg FreeBSD returns the mod time of the symlink's containing +# directory). +if ( + set X `ls -Lt $srcdir/configure conftest.file 2> /dev/null` + if test "$[*]" = "X"; then + # -L didn't work. + set X `ls -t $srcdir/configure conftest.file` + fi + rm -f conftest.file + if test "$[*]" != "X $srcdir/configure conftest.file" \ + && test "$[*]" != "X conftest.file $srcdir/configure"; then + + # If neither matched, then we have a broken ls. This can happen + # if, for instance, CONFIG_SHELL is bash and it inherits a + # broken ls alias from the environment. This has actually + # happened. Such a system could not be considered "sane". + AC_MSG_ERROR([ls -t appears to fail. Make sure there is not a broken +alias in your environment]) + fi + + test "$[2]" = conftest.file + ) +then + # Ok. + : +else + AC_MSG_ERROR([newly created file is older than distributed files! +Check your system clock]) +fi +AC_MSG_RESULT(yes)]) + +# Copyright (C) 2001, 2003, 2005 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# AM_PROG_INSTALL_STRIP +# --------------------- +# One issue with vendor `install' (even GNU) is that you can't +# specify the program used to strip binaries. This is especially +# annoying in cross-compiling environments, where the build's strip +# is unlikely to handle the host's binaries. +# Fortunately install-sh will honor a STRIPPROG variable, so we +# always use install-sh in `make install-strip', and initialize +# STRIPPROG with the value of the STRIP variable (set by the user). +AC_DEFUN([AM_PROG_INSTALL_STRIP], +[AC_REQUIRE([AM_PROG_INSTALL_SH])dnl +# Installed binaries are usually stripped using `strip' when the user +# run `make install-strip'. However `strip' might not be the right +# tool to use in cross-compilation environments, therefore Automake +# will honor the `STRIP' environment variable to overrule this program. +dnl Don't test for $cross_compiling = yes, because it might be `maybe'. +if test "$cross_compiling" != no; then + AC_CHECK_TOOL([STRIP], [strip], :) +fi +INSTALL_STRIP_PROGRAM="\$(install_sh) -c -s" +AC_SUBST([INSTALL_STRIP_PROGRAM])]) + +# Copyright (C) 2006 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# _AM_SUBST_NOTMAKE(VARIABLE) +# --------------------------- +# Prevent Automake from outputting VARIABLE = @VARIABLE@ in Makefile.in. +# This macro is traced by Automake. +AC_DEFUN([_AM_SUBST_NOTMAKE]) + +# Check how to create a tarball. -*- Autoconf -*- + +# Copyright (C) 2004, 2005 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# serial 2 + +# _AM_PROG_TAR(FORMAT) +# -------------------- +# Check how to create a tarball in format FORMAT. +# FORMAT should be one of `v7', `ustar', or `pax'. +# +# Substitute a variable $(am__tar) that is a command +# writing to stdout a FORMAT-tarball containing the directory +# $tardir. +# tardir=directory && $(am__tar) > result.tar +# +# Substitute a variable $(am__untar) that extract such +# a tarball read from stdin. +# $(am__untar) < result.tar +AC_DEFUN([_AM_PROG_TAR], +[# Always define AMTAR for backward compatibility. +AM_MISSING_PROG([AMTAR], [tar]) +m4_if([$1], [v7], + [am__tar='${AMTAR} chof - "$$tardir"'; am__untar='${AMTAR} xf -'], + [m4_case([$1], [ustar],, [pax],, + [m4_fatal([Unknown tar format])]) +AC_MSG_CHECKING([how to create a $1 tar archive]) +# Loop over all known methods to create a tar archive until one works. +_am_tools='gnutar m4_if([$1], [ustar], [plaintar]) pax cpio none' +_am_tools=${am_cv_prog_tar_$1-$_am_tools} +# Do not fold the above two line into one, because Tru64 sh and +# Solaris sh will not grok spaces in the rhs of `-'. +for _am_tool in $_am_tools +do + case $_am_tool in + gnutar) + for _am_tar in tar gnutar gtar; + do + AM_RUN_LOG([$_am_tar --version]) && break + done + am__tar="$_am_tar --format=m4_if([$1], [pax], [posix], [$1]) -chf - "'"$$tardir"' + am__tar_="$_am_tar --format=m4_if([$1], [pax], [posix], [$1]) -chf - "'"$tardir"' + am__untar="$_am_tar -xf -" + ;; + plaintar) + # Must skip GNU tar: if it does not support --format= it doesn't create + # ustar tarball either. + (tar --version) >/dev/null 2>&1 && continue + am__tar='tar chf - "$$tardir"' + am__tar_='tar chf - "$tardir"' + am__untar='tar xf -' + ;; + pax) + am__tar='pax -L -x $1 -w "$$tardir"' + am__tar_='pax -L -x $1 -w "$tardir"' + am__untar='pax -r' + ;; + cpio) + am__tar='find "$$tardir" -print | cpio -o -H $1 -L' + am__tar_='find "$tardir" -print | cpio -o -H $1 -L' + am__untar='cpio -i -H $1 -d' + ;; + none) + am__tar=false + am__tar_=false + am__untar=false + ;; + esac + + # If the value was cached, stop now. We just wanted to have am__tar + # and am__untar set. + test -n "${am_cv_prog_tar_$1}" && break + + # tar/untar a dummy directory, and stop if the command works + rm -rf conftest.dir + mkdir conftest.dir + echo GrepMe > conftest.dir/file + AM_RUN_LOG([tardir=conftest.dir && eval $am__tar_ >conftest.tar]) + rm -rf conftest.dir + if test -s conftest.tar; then + AM_RUN_LOG([$am__untar /dev/null 2>&1 && break + fi +done +rm -rf conftest.dir + +AC_CACHE_VAL([am_cv_prog_tar_$1], [am_cv_prog_tar_$1=$_am_tool]) +AC_MSG_RESULT([$am_cv_prog_tar_$1])]) +AC_SUBST([am__tar]) +AC_SUBST([am__untar]) +]) # _AM_PROG_TAR + +m4_include([acinclude.m4]) diff --git a/amarok/COPYING-DOCS b/amarok/COPYING-DOCS new file mode 100644 index 00000000..4a0fe1c8 --- /dev/null +++ b/amarok/COPYING-DOCS @@ -0,0 +1,397 @@ + GNU Free Documentation License + Version 1.2, November 2002 + + + Copyright (C) 2000,2001,2002 Free Software Foundation, Inc. + 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + +0. PREAMBLE + +The purpose of this License is to make a manual, textbook, or other +functional and useful document "free" in the sense of freedom: to +assure everyone the effective freedom to copy and redistribute it, +with or without modifying it, either commercially or noncommercially. +Secondarily, this License preserves for the author and publisher a way +to get credit for their work, while not being considered responsible +for modifications made by others. + +This License is a kind of "copyleft", which means that derivative +works of the document must themselves be free in the same sense. It +complements the GNU General Public License, which is a copyleft +license designed for free software. + +We have designed this License in order to use it for manuals for free +software, because free software needs free documentation: a free +program should come with manuals providing the same freedoms that the +software does. But this License is not limited to software manuals; +it can be used for any textual work, regardless of subject matter or +whether it is published as a printed book. We recommend this License +principally for works whose purpose is instruction or reference. + + +1. APPLICABILITY AND DEFINITIONS + +This License applies to any manual or other work, in any medium, that +contains a notice placed by the copyright holder saying it can be +distributed under the terms of this License. Such a notice grants a +world-wide, royalty-free license, unlimited in duration, to use that +work under the conditions stated herein. The "Document", below, +refers to any such manual or work. Any member of the public is a +licensee, and is addressed as "you". You accept the license if you +copy, modify or distribute the work in a way requiring permission +under copyright law. + +A "Modified Version" of the Document means any work containing the +Document or a portion of it, either copied verbatim, or with +modifications and/or translated into another language. + +A "Secondary Section" is a named appendix or a front-matter section of +the Document that deals exclusively with the relationship of the +publishers or authors of the Document to the Document's overall subject +(or to related matters) and contains nothing that could fall directly +within that overall subject. (Thus, if the Document is in part a +textbook of mathematics, a Secondary Section may not explain any +mathematics.) The relationship could be a matter of historical +connection with the subject or with related matters, or of legal, +commercial, philosophical, ethical or political position regarding +them. + +The "Invariant Sections" are certain Secondary Sections whose titles +are designated, as being those of Invariant Sections, in the notice +that says that the Document is released under this License. If a +section does not fit the above definition of Secondary then it is not +allowed to be designated as Invariant. The Document may contain zero +Invariant Sections. If the Document does not identify any Invariant +Sections then there are none. + +The "Cover Texts" are certain short passages of text that are listed, +as Front-Cover Texts or Back-Cover Texts, in the notice that says that +the Document is released under this License. A Front-Cover Text may +be at most 5 words, and a Back-Cover Text may be at most 25 words. + +A "Transparent" copy of the Document means a machine-readable copy, +represented in a format whose specification is available to the +general public, that is suitable for revising the document +straightforwardly with generic text editors or (for images composed of +pixels) generic paint programs or (for drawings) some widely available +drawing editor, and that is suitable for input to text formatters or +for automatic translation to a variety of formats suitable for input +to text formatters. A copy made in an otherwise Transparent file +format whose markup, or absence of markup, has been arranged to thwart +or discourage subsequent modification by readers is not Transparent. +An image format is not Transparent if used for any substantial amount +of text. A copy that is not "Transparent" is called "Opaque". + +Examples of suitable formats for Transparent copies include plain +ASCII without markup, Texinfo input format, LaTeX input format, SGML +or XML using a publicly available DTD, and standard-conforming simple +HTML, PostScript or PDF designed for human modification. Examples of +transparent image formats include PNG, XCF and JPG. Opaque formats +include proprietary formats that can be read and edited only by +proprietary word processors, SGML or XML for which the DTD and/or +processing tools are not generally available, and the +machine-generated HTML, PostScript or PDF produced by some word +processors for output purposes only. + +The "Title Page" means, for a printed book, the title page itself, +plus such following pages as are needed to hold, legibly, the material +this License requires to appear in the title page. For works in +formats which do not have any title page as such, "Title Page" means +the text near the most prominent appearance of the work's title, +preceding the beginning of the body of the text. + +A section "Entitled XYZ" means a named subunit of the Document whose +title either is precisely XYZ or contains XYZ in parentheses following +text that translates XYZ in another language. (Here XYZ stands for a +specific section name mentioned below, such as "Acknowledgements", +"Dedications", "Endorsements", or "History".) To "Preserve the Title" +of such a section when you modify the Document means that it remains a +section "Entitled XYZ" according to this definition. + +The Document may include Warranty Disclaimers next to the notice which +states that this License applies to the Document. These Warranty +Disclaimers are considered to be included by reference in this +License, but only as regards disclaiming warranties: any other +implication that these Warranty Disclaimers may have is void and has +no effect on the meaning of this License. + + +2. VERBATIM COPYING + +You may copy and distribute the Document in any medium, either +commercially or noncommercially, provided that this License, the +copyright notices, and the license notice saying this License applies +to the Document are reproduced in all copies, and that you add no other +conditions whatsoever to those of this License. You may not use +technical measures to obstruct or control the reading or further +copying of the copies you make or distribute. However, you may accept +compensation in exchange for copies. If you distribute a large enough +number of copies you must also follow the conditions in section 3. + +You may also lend copies, under the same conditions stated above, and +you may publicly display copies. + + +3. COPYING IN QUANTITY + +If you publish printed copies (or copies in media that commonly have +printed covers) of the Document, numbering more than 100, and the +Document's license notice requires Cover Texts, you must enclose the +copies in covers that carry, clearly and legibly, all these Cover +Texts: Front-Cover Texts on the front cover, and Back-Cover Texts on +the back cover. Both covers must also clearly and legibly identify +you as the publisher of these copies. The front cover must present +the full title with all words of the title equally prominent and +visible. You may add other material on the covers in addition. +Copying with changes limited to the covers, as long as they preserve +the title of the Document and satisfy these conditions, can be treated +as verbatim copying in other respects. + +If the required texts for either cover are too voluminous to fit +legibly, you should put the first ones listed (as many as fit +reasonably) on the actual cover, and continue the rest onto adjacent +pages. + +If you publish or distribute Opaque copies of the Document numbering +more than 100, you must either include a machine-readable Transparent +copy along with each Opaque copy, or state in or with each Opaque copy +a computer-network location from which the general network-using +public has access to download using public-standard network protocols +a complete Transparent copy of the Document, free of added material. +If you use the latter option, you must take reasonably prudent steps, +when you begin distribution of Opaque copies in quantity, to ensure +that this Transparent copy will remain thus accessible at the stated +location until at least one year after the last time you distribute an +Opaque copy (directly or through your agents or retailers) of that +edition to the public. + +It is requested, but not required, that you contact the authors of the +Document well before redistributing any large number of copies, to give +them a chance to provide you with an updated version of the Document. + + +4. MODIFICATIONS + +You may copy and distribute a Modified Version of the Document under +the conditions of sections 2 and 3 above, provided that you release +the Modified Version under precisely this License, with the Modified +Version filling the role of the Document, thus licensing distribution +and modification of the Modified Version to whoever possesses a copy +of it. In addition, you must do these things in the Modified Version: + +A. Use in the Title Page (and on the covers, if any) a title distinct + from that of the Document, and from those of previous versions + (which should, if there were any, be listed in the History section + of the Document). You may use the same title as a previous version + if the original publisher of that version gives permission. +B. List on the Title Page, as authors, one or more persons or entities + responsible for authorship of the modifications in the Modified + Version, together with at least five of the principal authors of the + Document (all of its principal authors, if it has fewer than five), + unless they release you from this requirement. +C. State on the Title page the name of the publisher of the + Modified Version, as the publisher. +D. Preserve all the copyright notices of the Document. +E. Add an appropriate copyright notice for your modifications + adjacent to the other copyright notices. +F. Include, immediately after the copyright notices, a license notice + giving the public permission to use the Modified Version under the + terms of this License, in the form shown in the Addendum below. +G. Preserve in that license notice the full lists of Invariant Sections + and required Cover Texts given in the Document's license notice. +H. Include an unaltered copy of this License. +I. Preserve the section Entitled "History", Preserve its Title, and add + to it an item stating at least the title, year, new authors, and + publisher of the Modified Version as given on the Title Page. If + there is no section Entitled "History" in the Document, create one + stating the title, year, authors, and publisher of the Document as + given on its Title Page, then add an item describing the Modified + Version as stated in the previous sentence. +J. Preserve the network location, if any, given in the Document for + public access to a Transparent copy of the Document, and likewise + the network locations given in the Document for previous versions + it was based on. These may be placed in the "History" section. + You may omit a network location for a work that was published at + least four years before the Document itself, or if the original + publisher of the version it refers to gives permission. +K. For any section Entitled "Acknowledgements" or "Dedications", + Preserve the Title of the section, and preserve in the section all + the substance and tone of each of the contributor acknowledgements + and/or dedications given therein. +L. Preserve all the Invariant Sections of the Document, + unaltered in their text and in their titles. Section numbers + or the equivalent are not considered part of the section titles. +M. Delete any section Entitled "Endorsements". Such a section + may not be included in the Modified Version. +N. Do not retitle any existing section to be Entitled "Endorsements" + or to conflict in title with any Invariant Section. +O. Preserve any Warranty Disclaimers. + +If the Modified Version includes new front-matter sections or +appendices that qualify as Secondary Sections and contain no material +copied from the Document, you may at your option designate some or all +of these sections as invariant. To do this, add their titles to the +list of Invariant Sections in the Modified Version's license notice. +These titles must be distinct from any other section titles. + +You may add a section Entitled "Endorsements", provided it contains +nothing but endorsements of your Modified Version by various +parties--for example, statements of peer review or that the text has +been approved by an organization as the authoritative definition of a +standard. + +You may add a passage of up to five words as a Front-Cover Text, and a +passage of up to 25 words as a Back-Cover Text, to the end of the list +of Cover Texts in the Modified Version. Only one passage of +Front-Cover Text and one of Back-Cover Text may be added by (or +through arrangements made by) any one entity. If the Document already +includes a cover text for the same cover, previously added by you or +by arrangement made by the same entity you are acting on behalf of, +you may not add another; but you may replace the old one, on explicit +permission from the previous publisher that added the old one. + +The author(s) and publisher(s) of the Document do not by this License +give permission to use their names for publicity for or to assert or +imply endorsement of any Modified Version. + + +5. COMBINING DOCUMENTS + +You may combine the Document with other documents released under this +License, under the terms defined in section 4 above for modified +versions, provided that you include in the combination all of the +Invariant Sections of all of the original documents, unmodified, and +list them all as Invariant Sections of your combined work in its +license notice, and that you preserve all their Warranty Disclaimers. + +The combined work need only contain one copy of this License, and +multiple identical Invariant Sections may be replaced with a single +copy. If there are multiple Invariant Sections with the same name but +different contents, make the title of each such section unique by +adding at the end of it, in parentheses, the name of the original +author or publisher of that section if known, or else a unique number. +Make the same adjustment to the section titles in the list of +Invariant Sections in the license notice of the combined work. + +In the combination, you must combine any sections Entitled "History" +in the various original documents, forming one section Entitled +"History"; likewise combine any sections Entitled "Acknowledgements", +and any sections Entitled "Dedications". You must delete all sections +Entitled "Endorsements". + + +6. COLLECTIONS OF DOCUMENTS + +You may make a collection consisting of the Document and other documents +released under this License, and replace the individual copies of this +License in the various documents with a single copy that is included in +the collection, provided that you follow the rules of this License for +verbatim copying of each of the documents in all other respects. + +You may extract a single document from such a collection, and distribute +it individually under this License, provided you insert a copy of this +License into the extracted document, and follow this License in all +other respects regarding verbatim copying of that document. + + +7. AGGREGATION WITH INDEPENDENT WORKS + +A compilation of the Document or its derivatives with other separate +and independent documents or works, in or on a volume of a storage or +distribution medium, is called an "aggregate" if the copyright +resulting from the compilation is not used to limit the legal rights +of the compilation's users beyond what the individual works permit. +When the Document is included in an aggregate, this License does not +apply to the other works in the aggregate which are not themselves +derivative works of the Document. + +If the Cover Text requirement of section 3 is applicable to these +copies of the Document, then if the Document is less than one half of +the entire aggregate, the Document's Cover Texts may be placed on +covers that bracket the Document within the aggregate, or the +electronic equivalent of covers if the Document is in electronic form. +Otherwise they must appear on printed covers that bracket the whole +aggregate. + + +8. TRANSLATION + +Translation is considered a kind of modification, so you may +distribute translations of the Document under the terms of section 4. +Replacing Invariant Sections with translations requires special +permission from their copyright holders, but you may include +translations of some or all Invariant Sections in addition to the +original versions of these Invariant Sections. You may include a +translation of this License, and all the license notices in the +Document, and any Warranty Disclaimers, provided that you also include +the original English version of this License and the original versions +of those notices and disclaimers. In case of a disagreement between +the translation and the original version of this License or a notice +or disclaimer, the original version will prevail. + +If a section in the Document is Entitled "Acknowledgements", +"Dedications", or "History", the requirement (section 4) to Preserve +its Title (section 1) will typically require changing the actual +title. + + +9. TERMINATION + +You may not copy, modify, sublicense, or distribute the Document except +as expressly provided for under this License. Any other attempt to +copy, modify, sublicense or distribute the Document is void, and will +automatically terminate your rights under this License. However, +parties who have received copies, or rights, from you under this +License will not have their licenses terminated so long as such +parties remain in full compliance. + + +10. FUTURE REVISIONS OF THIS LICENSE + +The Free Software Foundation may publish new, revised versions +of the GNU Free Documentation 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. See +http://www.gnu.org/copyleft/. + +Each version of the License is given a distinguishing version number. +If the Document specifies that a particular numbered version of this +License "or any later version" applies to it, you have the option of +following the terms and conditions either of that specified version or +of any later version that has been published (not as a draft) by the +Free Software Foundation. If the Document does not specify a version +number of this License, you may choose any version ever published (not +as a draft) by the Free Software Foundation. + + +ADDENDUM: How to use this License for your documents + +To use this License in a document you have written, include a copy of +the License in the document and put the following copyright and +license notices just after the title page: + + Copyright (c) YEAR YOUR NAME. + Permission is granted to copy, distribute and/or modify this document + under the terms of the GNU Free Documentation License, Version 1.2 + or any later version published by the Free Software Foundation; + with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. + A copy of the license is included in the section entitled "GNU + Free Documentation License". + +If you have Invariant Sections, Front-Cover Texts and Back-Cover Texts, +replace the "with...Texts." line with this: + + with the Invariant Sections being LIST THEIR TITLES, with the + Front-Cover Texts being LIST, and with the Back-Cover Texts being LIST. + +If you have Invariant Sections without Cover Texts, or some other +combination of the three, merge those two alternatives to suit the +situation. + +If your document contains nontrivial examples of program code, we +recommend releasing these examples in parallel under your choice of +free software license, such as the GNU General Public License, +to permit their use in free software. diff --git a/amarok/COPYING.LIB b/amarok/COPYING.LIB new file mode 100644 index 00000000..2676d08a --- /dev/null +++ b/amarok/COPYING.LIB @@ -0,0 +1,481 @@ + GNU LIBRARY GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1991 Free Software Foundation, Inc. + 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the library GPL. It is + numbered 2 because it goes with version 2 of the ordinary GPL.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Library General Public License, applies to some +specially designated Free Software Foundation software, and to any +other libraries whose authors decide to use it. You can use it for +your libraries, 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 +this service 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 make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if +you distribute copies of the library, or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link a program with the library, you must provide +complete object files to the recipients so that they can relink them +with the library, after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + Our method of protecting your rights has two steps: (1) copyright +the library, and (2) offer you this license which gives you legal +permission to copy, distribute and/or modify the library. + + Also, for each distributor's protection, we want to make certain +that everyone understands that there is no warranty for this free +library. If the library is modified by someone else and passed on, we +want its recipients to know that what they have is not the original +version, so that any problems introduced by others will not reflect on +the original authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that companies distributing free +software will individually obtain patent licenses, thus in effect +transforming the program into proprietary software. To prevent this, +we have made it clear that any patent must be licensed for everyone's +free use or not licensed at all. + + Most GNU software, including some libraries, is covered by the ordinary +GNU General Public License, which was designed for utility programs. This +license, the GNU Library General Public License, applies to certain +designated libraries. This license is quite different from the ordinary +one; be sure to read it in full, and don't assume that anything in it is +the same as in the ordinary license. + + The reason we have a separate public license for some libraries is that +they blur the distinction we usually make between modifying or adding to a +program and simply using it. Linking a program with a library, without +changing the library, is in some sense simply using the library, and is +analogous to running a utility program or application program. However, in +a textual and legal sense, the linked executable is a combined work, a +derivative of the original library, and the ordinary General Public License +treats it as such. + + Because of this blurred distinction, using the ordinary General +Public License for libraries did not effectively promote software +sharing, because most developers did not use the libraries. We +concluded that weaker conditions might promote sharing better. + + However, unrestricted linking of non-free programs would deprive the +users of those programs of all benefit from the free status of the +libraries themselves. This Library General Public License is intended to +permit developers of non-free programs to use free libraries, while +preserving your freedom as a user of such programs to change the free +libraries that are incorporated in them. (We have not seen how to achieve +this as regards changes in header files, but we have achieved it as regards +changes in the actual functions of the Library.) The hope is that this +will lead to faster development of free libraries. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, while the latter only +works together with the library. + + Note that it is possible for a library to be covered by the ordinary +General Public License rather than by this special one. + + GNU LIBRARY GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library which +contains a notice placed by the copyright holder or other authorized +party saying it may be distributed under the terms of this Library +General Public License (also called "this License"). Each licensee is +addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also compile or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + c) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + d) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the source code distributed need not include anything that is normally +distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +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 +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Library 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 Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "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 +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY 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 +LIBRARY (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 LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! diff --git a/amarok/HACKING b/amarok/HACKING new file mode 100644 index 00000000..5050ce26 --- /dev/null +++ b/amarok/HACKING @@ -0,0 +1,269 @@ +Hacking on Amarok +----------------- + +Please respect these guidelines when coding for Amarok, thanks! + +* Where this document isn't clear, refer to Amarok code. + + +This C++ FAQ is a life saver in many situations, so you want to keep it handy: + + http://www.parashift.com/c++-faq-lite/ + + +Formatting +---------- +* Spaces, not tabs +* Indentation is 4 spaces +* Lines should be limited to 90 characters +* Spaces between brackets and argument functions +* For pointer and reference variable declarations put a space between the type + and the * or & and no space before the variable name. +* For if, else, while and similar statements put the brackets on the next line, + although brackets are not needed for single statements. +* Function and class definitions have their brackets on separate lines +* A function implementation's return type is on its own line. +* amarok.h contains some helpful macros, foreach and foreachType. Use them, + they improve coding style and readability. + +Example: + + | bool + | MyClass::myMethod( QPtrList items, const QString &name ) + | { + | if( items.isEmpty() ) + | return false; + | + | foreachType( QPtrList, items ) + | { + | (*it)->setText( 0, name ); + | debug() << "Setting item name: " << name << endl; + | } + | } + +Header includes should be listed in the following order: + - Amarok includes + - KDE includes + - Qt includes + +They should also be sorted alphabetically, for ease of locating them. A small comment +if applicable is also helpful. + +Includes in a header file should be kept to the absolute minimum, as to keep compile times +low. This can be achieved by using "forward declarations" instead of includes, like +"class QListView;" Forward declarations work for pointers and const references. + +TIP: +Kate/KDevelop users can sort the headers automatically. Select the lines you want to sort, +then Tools -> Filter Selection Through Command -> "sort". + + +Example: + + | #include "amarok.h" + | #include "debug.h" + | #include "playlist.h" + | + | #include "kdialogbase.h" //baseclass + | #include "kpushbutton.h" //see function... + | + | #include "qlistviewitem.h" + | #include "qwidget.h" + + +Comments +-------- +Comment your code. Don't comment what the code does, comment on the purpose of the code. It's +good for others reading your code, and ultimately it's good for you too. + +Comments are essential when adding a strange hack, like the following example: + + | /** Due to xine-lib, we have to make KProcess close all fds, otherwise we get "device is busy" messages + | * Used by AmarokProcIO and AmarokProcess, exploiting commSetupDoneC(), a virtual method that + | * happens to be called in the forked process + | * See bug #103750 for more information. + | */ + | class AmarokProcIO : public KProcIO + | { + | public: + | virtual int commSetupDoneC() { + | const int i = KProcIO::commSetupDoneC(); + | Amarok::closeOpenFiles(KProcIO::out[0],KProcIO::in[0],KProcIO::err[0]); + | return i; + | } + | }; + + +For headers, use the Doxygen syntax. See: http://www.stack.nl/~dimitri/doxygen/ + +Example: + + | /** + | * Start playback. + | * @param offset Start playing at @p msec position. + | * @return True for success. + | */ + | virtual bool play( uint offset = 0 ) = 0; + + +Header Formatting +----------------- +General rules apply here. Please keep header function definitions aligned nicely, +if possible. It helps greatly when looking through the code. Sorted methods, +either by name or by their function (ie, group all related methods together) is +great too. + + + | #ifndef AMAROK_QUEUEMANAGER_H + | #define AMAROK_QUEUEMANAGER_H + + | class QueueList : public KListView + | { + | Q_OBJECT + | + | public: + | Queuelist( QWidget *parent, const char *name = 0 ); + | ~QueueList() {}; + | + | public slots: + | void moveSelectedUp(); + | void moveSelectedDown(); + | }; + +#endif /* AMAROK_QUEUEMANAGER_H */ + + +0 vs NULL +--------- +The use of 0 to express a null pointer is preferred over the use of NULL. +0 is not a magic value, it's the defined value of the null pointer in C++. +NULL, on the other hand, is a preprocessor directive (#define) and not only is +it more typing than '0' but preprocessor directives are less elegant. + + | SomeClass *instance = 0; + + +Const Correctness +----------------- +Try to keep your code const correct. Declare methods const if they don't mutate the object, +and use const variables. It improves safety, and also makes it easier to understand the code. + +See: http://www.parashift.com/c++-faq-lite/const-correctness.html + + +Example: + + | bool + | MyClass::isValidFile( const QString& path ) const + | { + | const bool valid = QFile::exist( path ); + | + | return valid; + | } + + +Debugging +--------- +debug.h contains some handy functions for our debug console output. +Please use them instead of kdDebug(). + +Usage: + + | #include "debug.h" + | + | debug() << "Something is happening" << endl; + | warning() << "Something bad may happen" << endl; + | error() << "Something bad did happen!" << endl; + +Additionally, there are some macros for debugging functions: + +DEBUG_BLOCK +DEBUG_FUNC_INFO +DEBUG_LINE_INFO +DEBUG_INDENT +DEBUG_UNINDENT + +AMAROK_NOTIMPLEMENTED +AMAROK_DEPRECATED + +threadweaver.h has two additional macros: +DEBUG_THREAD_FUNC_INFO outputs the memory address of the current QThread or 'none' + if its the original GUI thread. +SHOULD_BE_GUI outputs a warning message if it occurs in a thread that isn't in + the original "GUI Thread", otherwise it is silent. Useful for documenting + functions and to prevent problems in the future. + + +Usage of Amarok::config() +------------------------- +We provide this method for convenience, but it is important to use it properly. By +inspection, we can see that we may produce very obscure bugs in the wrong case: + + | KConfig + | *config( const QString &group ) + | { + | //Slightly more useful config() that allows setting the group simultaneously + | kapp->config()->setGroup( group ); + | return kapp->config(); + | } + +Take the following example: + + | void + | f1() + | { + | KConfig *config = Amarok::config( "Group 2" ); + | config->writeEntry( "Group 2 Variable", true ); + | } + | + | void + | doStuff() + | { + | KConfig *config = Amarok::config( "Group 1" ); + | f1(); + | config->writeEntry( "Group 1 Variable", true ); + | } + +We would expect the following results: + + | [Group 1] + | Group 1 Variable = true + | + | [Group 2] + | Group 2 Variable = true + +However because the config group is changed before writing the entry: + | [Group 1] + | + | [Group 2] + | Group 1 Variable = true + | Group 2 Variable = true + +Which is clearly incorrect. And hard to see when your wondering why f1() is not +working. So do not store a value of Amarok::config, make it a habit to just +always call writeEntry or readEntry directly. + +Correct: +| amarok::config( "Group 1" )->writeEntry( "Group 1 Variable", true ); + + +Errors & Asserts +---------------- +*Never use assert() or fatal(). There must be a better option than crashing a user's +application (its not uncommon for end-users to have debugging enabled). + +*KMessageBox is fine to use to prompt the user, but do not use it to display errors +or informational messages. Instead, KDE::StatusBar has a few handy methods. Refer to +amarok/src/statusbar/statusBarBase.h + + +Copyright +--------- +To comply with the GPL, add your name, email address & the year to the top of any file +that you edit. If you bring in code or files from elsewhere, make sure its +GPL-compatible and to put the authors name, email & copyright year to the top of +those files. + + +Thanks, now have fun! + -- the Amarok developers diff --git a/amarok/Makefile.am b/amarok/Makefile.am new file mode 100644 index 00000000..2ba68323 --- /dev/null +++ b/amarok/Makefile.am @@ -0,0 +1,17 @@ +SUBDIRS = \ + src + +EXTRA_DIST = \ + AUTHORS \ + COPYING \ + ChangeLog \ + INSTALL \ + README \ + TODO \ + admin \ + amarok.kdevprj \ + amarok.lsm + +AUTOMAKE_OPTIONS = \ + foreign + diff --git a/amarok/VIS_PLAN b/amarok/VIS_PLAN new file mode 100644 index 00000000..f059593c --- /dev/null +++ b/amarok/VIS_PLAN @@ -0,0 +1,163 @@ +amaroK VIS_PLAN - Created: 22-11-2003 + + +Abstract +======== +In order to create a visualisation framework that is superior to anything +popular available for Linux today we should plan amaroK's thoroughly +before implementation. This document exists to map out our ideas and is +available for anyone to edit, please, if you have some good suggestion, or +experience with this kind of work, add some comments! Thanks, Max Howell + + +Formatting +========== +The document has no regimented formatting, but do try to keep the wordwrap set +the same! Feel free to recommend formatting if the document becomes +unmanageable. + + +Plan +==== +My initial thoughts were to make visualisations a separate process rather than +separate thread. The reasoning being, a crash in the vis won't crash amaroK, +threads can be unmanageable, *nix is good for new processes, we can use DCOP to +control the vis and presumable we just need to pass a handle to the relevant +arts server to the vis and leave it. Since the music playing bit is already a +separate process it makes sense to make the visualisation separate too! Also +this may mean that these visuals could be separate from amaroK and just depend +on KDE. + +I was also thinking that this means it may be possible to not use Qt to +do rendering. There are probably better libraries for high speed graphics and if +there isn't a way to make DCOP not depend on a QApplication, then maybe we could +write something. It would be sorted if we could make something that can react to +arts sound output in general. + we could use SDL for 2d and OpenGL for 3d rendering. + +I was also thinking it would be neat to embed the visuals somehow into amaroK, +so as the background in the playlist perhaps (I know, this would probably not +work well due to the need to re-render all the AA text all the time, but still +the idea is neat) This is similar to Sonique on Windows if anyone has ever used +it. + +If we make the visualisations not amaroK specific it may be possible to embed +visualisations into the Kicker, onto the desktop, etc. + +Do we want more than an FFT of the audio data? Would other data +be useful to visuals at all? Beat detection certainly would be a useful thing to +offer the visualisations, and it would certainly stop every visual implementing +its own detection. + + +Vis Process Architecture +======================== +I suggest having a separate process for each visualization. Each vis process would +only load one visualization plugin. This way we have good crash safety and it's also +possible to develop and ship plugins separately from amaroK. So we have one "vis loader +skeleton" in amaroK, which will be instanciated whenever we need a vis. This loader could +get started with the vis plugin as cli argument. It would also contain all the socket and +interface needed to communicate with amaroK. + +We should take care to keep the design as simple as possible. + + Why make visualisations plugins as well as binaries? It seems sensible to have +visualisations as separate processes and then communicate with amK through the socket. +Why have the extra layer of plugins if you're going to force each process to only load +one plugin anyway? + + +Merging Analyzer and Vis +======================== +If we decide on unifying vis and analyzer, the question arises how to get the rendered +bitmap graphic back into amaroK's playerwidget. It seems we have the following options: + +* Writing it into a shared memory buffer. amaroK must then read the buffer and bitBlt() + the pixmap into the framebuffer +* Transferring the pixmap over a Unix socket (or UDP network socket). This can amount to + several megabytes per second. After the transfer amaroK must bitBlt() it to the + framebuffer. +* Writing it directly into the framebuffer at the desired position, like a video overlay. + There might be technical problems doing so, and it will be difficult when the user + moves the window. + + + +[Comment from muesli, to be integrated into the paragraphs above] + +imho, it would be cool to offer this service via sockets. udp would fit per- +fectly, since it has to be realtime, anyways (better loose a packet and show +nothing, instead of a out-of-sync beat). e.g., you would be able to have +various of visualizations-clients connected to amarok, showing different +animations, at the same time. this would also remove the DCOP-dependency-chains +for the OpenGL clients. + +right now, i can imagine two implementation methods: + +A) transmit the frequency vector of the analyzer to the vis-client. beat-detection and +all that stuff gets done by the vis-client. + +B) basic beat-detection and music analysis gets done by amarok itself. the +results of that process are sent to the vis-clients. + + IMO it would be good to offer services like beat detection. This +way vis-writers have less to do and are more likely to write a vis since +beat-detection is already done, also it means beat-detection is likely to be better +as we will over time offer a really good implementation, and it means that if the user +has many visuals running, the beat-detection is only done once for all visualisations. + +[/Comment by muesli] + + + + +//FIXME beat detection code temporarily moved here + + /* + // Muesli's Beat Detection - A Night's Oddysee + // shift old elements + for ( uint x = 0; x < 18; ++x ) + for ( uint y = 42; y > 0; --y ) m_beatEnergy[x][y] = m_beatEnergy[x][y - 1]; + + // get current energy values + for ( uint x = 0; x < pScopeVector->size(); ++x ) m_beatEnergy[x][0] = pScopeVector->at(x); + + // compare to old elements and get averages + double beatAvg[18]; + // double beatVariance[18]; + // double beatMood[18]; + + for ( uint x = 0; x < 18; ++x ) + { + beatAvg[x] = 0; + for ( uint y = 1; y < 44; ++y ) beatAvg[x] += m_beatEnergy[x][y]; + + beatAvg[x] = beatAvg[x] / 43; + } + */ + /* for ( uint x = 0; x < 18; ++x ) + { + beatVariance[x] = 0; + for ( uint y = 0; y < 42; ++y ) beatVariance[x] += (pow((m_beatEnergy[x][y] - beatAvg[x]), 2) / 43); + } + + for ( uint x = 0; x < 18; ++x ) + beatMood[x] = (-0.0025714 * beatVariance[x]) + 1.5142857; + */ + + // do we have a beat? let's dance! + /* int total_hits = 0; + for ( uint x = 0; x < 18; ++x ) + { + double factor = cos( x * 4 ) * 18; + factor = beatAvg[x] * factor; + + if ( m_beatEnergy[x][0] > factor ) + { + total_hits++; + kdDebug() << "*CLAP* factor: " << factor << " - x: " << x << " - average energy: " << beatAvg[x] << " - current peak: " << m_beatEnergy[x][0] << endl; + } + } + + if ( total_hits > 3 ) kdDebug() << "***CLAPCLAPCLAP***" << endl; + */ diff --git a/amarok/amarok.kdevelop b/amarok/amarok.kdevelop new file mode 100644 index 00000000..beb9be09 --- /dev/null +++ b/amarok/amarok.kdevelop @@ -0,0 +1,239 @@ + + + + Mark Kretschmann + markey@web.de + KDevKDEAutoProject + C++ + + KDE + Qt + + . + false + + + + + + amarok + + + + amarok + default + src/magnatunebrowser/libmagnatunebrowser.la + true + + + \s--build=i386-linux --host=i386-linux --target=i386-linux\s + + + false + 1 + false + unsermake + + + + + 0 + false + + + + \s-O0 -g3 -Wall + + + + + false + false + + build + /usr/kde/cvs/bin/ + + + + + + + --enable-debug=full + + + + + kdevgccoptions + kdevgppoptions + kdevpgf77options + + + + + + + + + + + + + + + + + + + + + + false + false + + + false + *.o,*.lo,CVS + true + + + + + gtk + gnustep + python + php + perl + + + /home/mark/mysource/gideon/amarok-0.5.2-gideon/html/ + /home/mark/mysource/gideon/amarok-0.5.2-gideon/html/ + + + + + + + + + /usr/bin/libtool + + + true + false + false + true + + + + + + false + true + 10 + + + + + + + + + false + + + .h + .cpp + true + + + + + true + true + true + false + true + true + 100 + 100 + true + 250 + false + 0 + true + true + false + std=_GLIBCXX_STD;__gnu_cxx=std + true + false + false + false + true + true + true + false + .; + + + + + set + m_,_ + theValue + true + true + + + true + 3 + /usr/qt/3 + 3 + EmbeddedKDevDesigner + /usr/qt/3/bin/qmake + /usr/qt/3/bin/designer + + + + false + true + Vertical + + + + + + + true + 2 + + + -f + + + + -dP + -f + -C -d -P + -u3 -p + + + + true + true + true + true + -C + + + + + + + + + + + /home/ian/workspace/local/amarok/tags + + + diff --git a/amarok/configure.in.bot b/amarok/configure.in.bot new file mode 100644 index 00000000..acf9d76d --- /dev/null +++ b/amarok/configure.in.bot @@ -0,0 +1,336 @@ +# configure.in.bot +# This file is used for printing important messages at the end of configure + +echo "" + +if test x$amarok_error_notaglib = xyes; then + echo " ========================" + echo " === Amarok - ERROR ==========================================================" + echo " ========================" + echo " =" + echo " = Amarok cannot be built because, either the TagLib library is not installed," + echo " = or if relevant, the taglib-devel package is not installed." + echo " = TagLib can be obtained from: http://ktown.kde.org/~wheeler/taglib/" + echo " =" +fi + +if test x$amarok_error_taglibold = xyes; then + echo " ========================" + echo " === Amarok - ERROR ==========================================================" + echo " ========================" + echo " =" + echo " = Amarok cannot be built because your TagLib version is too old. Please obtain" + echo " = the version $TAGLIB_REQ_VERSION from:" + echo " = http://ktown.kde.org/~wheeler/taglib/" + echo " =" +fi + +if test x$amarok_error_noruby = xyes; then + echo " ==========================" + echo " === Amarok - ERROR ==========================================================" + echo " ==========================" + echo " =" + echo " = The Ruby programming language is not installed. Please obtain Ruby" + echo " = (version 1.8 or later) from http://ruby-lang.org, or install a distribution" + echo " = package. To build Amarok requires the Ruby header files as well, which some" + echo " = distributions package separately." + echo " =" +fi + +if test x$amarok_warning_xineold = xyes; then + echo " ==========================" + echo " === Amarok - WARNING ========================================================" + echo " ==========================" + echo " =" + echo " = Amarok requires xine-lib version: 1.0-rc4" + echo " = Amarok will still be built, but you must use another sound-engine." + echo " =" +fi + +if test x$PKGCONFIGFOUND != xyes; then + echo " ==========================" + echo " === Amarok - WARNING ========================================================" + echo " ==========================" + echo " =" + echo " = pkg-config could not be found, this means some optional components (eg the" + echo " = GStreamer-engine) cannot be built." + echo " = See README for help with this issue." + echo " =" +fi + +#if test x$amarok_warning_mas_notfound = xyes; then +# echo " ==========================" +# echo " === Amarok - WARNING ========================================================" +# echo " ==========================" +# echo " =" +# echo " = mas-config could not be found, this means that MAS-engine" +# echo " = cannot be built." +# echo " =" +#fi + +if test x$build_xine = xno; then + echo " ==========================" + echo " === Amarok - WARNING ========================================================" + echo " ==========================" + echo " =" + echo " = The recommended xine-engine will not be built. If you want to use the" + echo " = powerful xine multimedia framework with Amarok, please download xine-lib" + echo " = version $xine_version_min or higher from http://xinehq.de/" + echo " =" +fi + +if test x$have_gst_plugins = xno -a x$have_gst = xyes; then + echo " ==========================" + echo " === Amarok - WARNING ========================================================" + echo " ==========================" + echo " =" + echo " = No GStreamer plugins were detected!" + echo " = Without plugins you will not be able to play any media using the" + echo " = GStreamer-engine! You need at least the MP3 plugin and a sink plugin, (eg." + echo " = ALSAsink). Please refer to http://gstreamer.freedesktop.org/" + echo " = NOTE: you will still be able to play media with another engine plugin." + echo " =" +fi + +if test x$included_sqlite = xno; then + if test x$have_sqlite = xyes; then + echo " ==========================" + echo " === Amarok - WARNING ========================================================" + echo " ==========================" + echo " =" + echo " = You have passed the --without-included-sqlite option to configure, which" + echo " = means that SQLite will be dynamically linked instead of statically linked." + echo " = IMPORTANT: you must ensure the libsqlite.so library in your system is" + echo " = threadsafe!!! Amarok will not be stable otherwise." + echo " =" + else + echo " ========================" + echo " === Amarok - ERROR ==========================================================" + echo " ========================" + echo " =" + echo " = You have passed the --without-included-sqlite option to configure, but" + echo " = the development files for SQLite could not be found. Please make sure you" + echo " = have the relevant package installed or, even better, use the included" + echo " = sqlite (unless you *really* know what you're doing, of course)." + echo " =" + fi +fi + +if test x$amarok_warning_mysql_notfound = xyes; then + echo " ==========================" + echo " === Amarok - WARNING ========================================================" + echo " ==========================" + echo " =" + echo " = mysql_config could not be found, this means that support for MySql" + echo " = will be disabled." + echo " =" +fi + +if test x$amarok_warning_postgresql_notfound = xyes; then + echo " ==========================" + echo " === Amarok - WARNING ========================================================" + echo " ==========================" + echo " =" + echo " = pg_config could not be found, this means that support for Postgresql" + echo " = will be disabled." + echo " =" +fi + +if test x$no_engine = xyes; then + all_tests=bad + echo " ==================================" + echo " === AMAROK WILL NOT BE BUILT ================================================" + echo " ==================================" + echo " =" + echo " = No suitable multimedia framework was detected. You need to install at least" + echo " = the Xine or Helix framework as detailed in the Amarok README." + echo " =" +fi + +if test x$no_amarok = xyes; then + all_tests=bad + echo " ==================================" + echo " === AMAROK WILL NOT BE BUILT ================================================" + echo " ==================================" + echo " =" + echo " = Some mandatory dependencies are either not installed or not installed" + echo " = correctly. See the Amarok README for help with this issue. Further assistance" + echo " = can be found at http://amarok.kde.org or in amarok on irc.freenode.net." + echo " = You will still be able to build other modules from extragear/multimedia." + echo " =" + +else + + echo " ==========================" + echo " === Amarok - PLUGINS ========================================================" + echo " ==========================" + + + echo " =" + echo " = The following extra functionality will NOT be included:" + + +# if test x$build_akode != xyes; then +# echo " = - aKode-engine" +# fi + +# if test x$have_gst10 != xyes; then +# echo " = - GStreamer0.10-engine" +# fi + + if test x$build_xine != xyes; then + echo " = - xine-engine" + fi + + if test x$build_nmm != xyes; then + echo " = - NMM-engine" + fi + +# if test x$build_mas != xyes; then +# echo " = - MAS-engine" +# fi + + if test x$build_helix = xno; then + echo " = - Helix-engine" + fi + + if test x$build_yauap = xno; then + echo " = - yauap-engine" + fi + + if test x$build_libvisual != xyes; then + echo " = - libvisual Support" + fi + + if test x$enable_mysql != xyes; then + echo " = - MySql Support" + fi + + if test x$enable_postgresql != xyes; then + echo " = - Postgresql Support" + fi + + if test x$have_konqsidebar != xyes; then + echo " = - Konqueror Sidebar" + fi + + if test x$have_tunepimp != xyes; then + echo " = - MusicBrainz Support" + fi + + if test x$have_mp4v2 != xyes; then + echo " = - MP4/AAC Tag Write Support" + fi + + if test x$have_libgpod != xyes; then + if test x$have_libgpod_042 != xno; then + echo " = - iPod Support" + else + echo " = - iPod Support (at least libgpod 0.4.2 is required)" + fi + fi + + if test x$have_ifp != xyes; then + echo " = - iRiver iFP Support" + fi + + if test x$have_libnjb != xyes; then + echo " = - Creative Nomad Jukebox Support" + fi + + if test x$have_libmtp != xyes; then + echo " = - MTP Device Support" + fi + + if test x$have_libkarma != xyes; then + echo " = - Rio Karma Support" + fi + + if test x$have_daap != xyes; then + echo " = - DAAP Music Sharing Support" + fi + + echo " =" + echo " = The following extra functionality will be included:" + + +# if test x$build_akode = xyes; then +# echo " = + aKode-engine" +# fi + +# if test x$have_gst10 = xyes; then +# echo " = + GStreamer0.10-engine" +# fi + + if test x$build_xine = xyes; then + echo " = + xine-engine" + fi + + if test x$build_nmm = xyes; then + echo " = + NMM-engine" + fi + +# if test x$build_mas = xyes; then +# echo " = + MAS-engine" +# fi + + if test x$build_helix != xno; then + echo " = + Helix-engine" + fi + + if test x$build_yauap != xno; then + echo " = + yauap-engine" + fi + + if test x$build_libvisual = xyes; then + echo " = + libvisual Support" + fi + + if test x$enable_mysql = xyes; then + echo " = + MySql Support" + fi + + if test x$enable_postgresql = xyes; then + echo " = + Postgresql Support" + fi + + if test x$have_konqsidebar = xyes; then + echo " = + Konqueror Sidebar" + fi + + if test x$have_tunepimp = xyes; then + echo " = + MusicBrainz Support" + fi + + if test x$have_mp4v2 = xyes; then + echo " = + MP4/AAC Tag Write Support" + fi + + if test x$have_libgpod = xyes; then + echo " = + iPod Support" + fi + + if test x$have_ifp = xyes; then + echo " = + iRiver iFP Support" + fi + + if test x$have_libnjb = xyes; then + echo " = + Creative Nomad Jukebox Support" + fi + + if test x$have_libmtp = xyes; then + echo " = + MTP Device Support" + fi + + if test x$have_libkarma = xyes; then + echo " = + Rio Karma Support" + fi + + if test x$have_daap = xyes; then + echo " = + DAAP Music Sharing Support" + fi + + echo " =" +fi + +echo " ===============================================================================" diff --git a/amarok/configure.in.in b/amarok/configure.in.in new file mode 100644 index 00000000..539d07c2 --- /dev/null +++ b/amarok/configure.in.in @@ -0,0 +1,1138 @@ + +############################################################################### +# BEGIN PKG-CONFIG CHECK +############################################################################### + +AC_ARG_VAR(PKGCONFIGFOUND, [Path to pkg-config]) +AC_CHECK_PROG(PKGCONFIGFOUND, pkg-config,[yes]) + +############################################################################### +# END PKG-CONFIG CHECK +############################################################################### + + + +############################################################################### +# BEGIN TAGLIB CHECK +############################################################################### + +PKG_CHECK_MODULES([TAGLIB], [taglib >= 1.5], [taglib_15_found=yes], [PKG_CHECK_MODULES([TAGLIB], [taglib >= 1.4])]) + +AM_CONDITIONAL([TAGLIB_15_FOUND], [test "x$taglib_15_found" = "xyes"]) +AC_SUBST(TAGLIB_CFLAGS) +AC_SUBST(TAGLIB_LIBS) + +if test "x$taglib_15_found" = "xyes"; then + AC_DEFINE([TAGLIB_15], 1, [Taglib 1.5 or later found, disabling duplicate metadata plugins]) +fi + +############################################################################### +# END TAGLIB CHECK +############################################################################### + + + +############################################################################### +# BEGIN GSTREAMER-0.10 CHECK +############################################################################### + +#AC_ARG_WITH(gstreamer10, +# AC_HELP_STRING([--with-gstreamer10],[build Amarok with GStreamer 0.10-engine]), +# [build_gstreamer10=$withval], +# [build_gstreamer10=no] +#) +# +#if test "$build_gstreamer10" != "no"; then +# if test "$PKGCONFIGFOUND" = "yes" ; then +# # check for GStreamer +# dnl Now we're ready to ask for gstreamer libs and cflags +# dnl And we can also ask for the right version of gstreamer +# have_gst10=no +# +# GST10_MAJORMINOR=0.10 +# GST10_REQ=0.10.0 +# +# PKG_CHECK_MODULES(GST10, gstreamer-$GST10_MAJORMINOR >= $GST10_REQ gstreamer-base-$GST10_MAJORMINOR, +# have_gst10=yes,have_gst10=no) +# +# dnl Give error if we don't have gstreamer +# if test "x$have_gst10" = "xno"; then +# LIB_GST10="" +# CFLAGS_GST10="" +# else +# LIB_GST10=$GST10_LIBS +# CFLAGS_GST10=$GST10_CFLAGS +# AC_SUBST(LIB_GST10) +# AC_SUBST(CFLAGS_GST10) +# AC_SUBST(GST10_MAJORMINOR) +# AC_DEFINE(HAVE_GSTREAMER10, 1, [have GStreamer10]) +# fi +# fi +#fi +# +#AM_CONDITIONAL(with_gst10, [test x$have_gst10 = xyes]) + +############################################################################### +# END GSTREAMER-0.10 CHECK +############################################################################### + + + +############################################################################### +# BEGIN XINE CHECK +############################################################################### + +AC_ARG_WITH(xine, + AC_HELP_STRING([--without-xine],[build Amarok without xine-engine]), + [build_xine=$withval], + [build_xine=yes] +) + +if test "$build_xine" != "no"; then + PKG_CHECK_MODULES([XINE], [libxine >= 1.0.2], , [build_xine=no]) +fi + +AM_CONDITIONAL(with_xine, test x$build_xine = xyes) +AC_SUBST(XINE_CFLAGS) +AC_SUBST(XINE_LIBS) + +############################################################################### +# END XINE CHECK +############################################################################### + + + +############################################################################### +# BEGIN AKODE CHECK +############################################################################### + +#AC_ARG_WITH(akode, +# AC_HELP_STRING([--without-akode],[build Amarok without akode-engine]), +# [build_akode=$withval], +# [build_akode=yes] +#) +# +#if test "$build_akode" != "no"; then +# +# AC_CHECK_PROG(AKODE_CONFIG, akode-config, yes) +# +# if test x$AKODE_CONFIG = xyes ; then +# AC_DEFINE(HAVE_AKODE, 1, [have aKode]) +# CFLAGS_AKODE=[`akode-config --cflags`] +# LIBS_AKODE=[`akode-config --libs`] +# +# akode_version=`akode-config --version` +# akode_version=VERSION_TO_NUMBER(echo $akode_version) +# akode_version_min="2.0.0" +# akode_version_min=VERSION_TO_NUMBER(echo $akode_version_min) +# +# AC_MSG_CHECKING([for akode-lib version >= 2.0]) +# +# if test $akode_version -eq $akode_version_min \ +# -o $akode_version -gt $akode_version_min; then +# +# echo "yes" +# +# else +# echo "no" +# +# build_akode=no +# fi +# else +# build_akode=no +# fi +#fi +# +#AM_CONDITIONAL(with_akode, test x$build_akode = xyes) +#AC_SUBST(CFLAGS_AKODE) +#AC_SUBST(LIBS_AKODE) + +############################################################################### +# END AKODE CHECK +############################################################################### + + + +############################################################################### +# BEGIN NMM CHECK +############################################################################### + +AC_ARG_WITH(nmm, + AC_HELP_STRING([--with-nmm],[build Amarok with NMM-engine]), + [build_nmm=$withval], + [build_nmm=no] +) + +AC_ARG_WITH(nmm-dir, + AC_HELP_STRING([--with-nmm-dir],[path to the NMM [default=/usr/local]]), + [nmm_dir="$withval"], + [nmm_dir=/usr/local] +) + +if test "$build_nmm" != "no"; then + + CFLAGS_NMM="-I$nmm_dir/include" + LDFLAGS_NMM="-L$nmm_dir/lib" + AC_DEFINE(HAVE_NMM, 1, [have NMM]) +fi + +AM_CONDITIONAL(with_nmm, test x$build_nmm = xyes) +AC_SUBST(CFLAGS_NMM) +AC_SUBST(LDFLAGS_NMM) + +############################################################################### +# END NMM CHECK +############################################################################### + + + +############################################################################### +# BEGIN MAS CHECK +############################################################################### + +#AC_ARG_WITH(mas, +# AC_HELP_STRING([--with-mas],[build Amarok with MAS-engine]), +# [build_mas=$withval], +# [build_mas=no] +#) +# +# +#if test "$build_mas" != "no"; then +# +# AC_PATH_PROG(MAS_CONFIG, mas-config, no) +# +# if test $MAS_CONFIG = "no" +# then +# amarok_warning_mas_notfound=yes +# echo "amarok_warning_mas_notfound: $amarok_warning_mas_notfound" +# build_mas=no +# else +# AC_DEFINE(HAVE_MAS, 1, [have MAS]) +# +# CFLAGS_MAS=[`$MAS_CONFIG --cflags`] +# LIBS_MAS=[`$MAS_CONFIG --libs`] +# +# build_mas=yes +# fi +#fi +# +#AM_CONDITIONAL(with_mas, test x$build_mas = xyes) +#AC_SUBST(CFLAGS_MAS) +#AC_SUBST(LIBS_MAS) + +############################################################################### +# END MAS CHECK +############################################################################### + + + +############################################################################### +# BEGIN HELIX CHECK +############################################################################### + +AC_ARG_WITH(helix, + AC_HELP_STRING([--with-helix],[build Amarok with Helix-engine]), + [build_helix=$withval], + [build_helix=no] +) + +if test "$build_helix" != "no"; then + AC_MSG_CHECKING([for RealPlayer or HelixPlayer]) + + if test "$build_helix" = "yes"; then + HXPLAY=`type -p hxplay` + RPLAY=`type -p realplay` + if test "$RPLAY" != "" -a -x "$RPLAY"; then + HELIX_LINK=`readlink -f $RPLAY` + HELIX_LINK=`dirname $HELIX_LINK` + HELIX_LIBS=`unset CDPATH; cd $HELIX_LINK && pwd` + if test -e "$HELIX_LIBS/common/clntcore.so"; then + AC_MSG_RESULT([found RealPlayer in $HELIX_LIBS]) + build_helix=$HELIX_LIBS + fi + elif test "$HXPLAY" != "" -a -x "$HXPLAY"; then + HELIX_LINK=`readlink -f $HXPLAY` + HELIX_LINK=`dirname $HELIX_LINK` + HELIX_LIBS=`unset CDPATH; cd $HELIX_LINK && pwd` + if test -e "$HELIX_LIBS/common/clntcore.so"; then + AC_MSG_RESULT([found HelixPlayer in $HELIX_LIBS]) + build_helix=$HELIX_LIBS + fi + fi + else + if test -n "$build_helix" -a -d "$build_helix"; then + HELIX_LIBS=`unset CDPATH; cd $build_helix && pwd` + else + HELIX_LIBS="$build_helix" + fi + build_helix=$HELIX_LIBS + AC_MSG_RESULT([using $HELIX_LIBS]) + fi + + AC_DEFINE(HAVE_HELIX, 1, [have HELIX]) + if test "$build_helix" = "yes"; then + HELIX_LIBS="/usr/local/RealPlayer" + AC_MSG_RESULT(["not found, using default dir"]) + fi + AC_DEFINE_UNQUOTED(HELIX_LIBS, "${HELIX_LIBS}", [location of helix libs]) + if test "$PKGCONFIGFOUND" = "yes" ; then + PKG_CHECK_MODULES([ALSALIB], alsa, have_alsa=yes,have_alsa=no) + if test "$have_alsa" = "yes" ; then + AC_DEFINE(USE_HELIX_ALSA, 1, [support ALSA in the helix-engine]) + fi + fi +fi + +AM_CONDITIONAL(with_helix, test x$build_helix != xno) + +############################################################################### +# END HELIX CHECK +############################################################################### + +############################################################################### +# BEGIN yauap CHECK +############################################################################### + +AC_ARG_WITH(yauap, + AC_HELP_STRING([--with-yauap],[build Amarok with yauap-engine]), + [build_yauap=$withval], + [build_yauap=no] +) + +if test "$build_yauap" != "no"; then + if test "$PKGCONFIGFOUND" = "yes" ; then + # check for dbus-glib + have_yauap=no + + PKG_CHECK_MODULES(DBUS, dbus-1, + have_yauap=yes,have_yauap=no) + + + dnl Give error if we don't have gstreamer + if test "x$have_yauap" = "xno"; then + LIB_YAUAP="" + CFLAGS_YAUAP="" + else + LIB_YAUAP="$DBUS_LIBS -ldbus-qt-1" + CFLAGS_YAUAP="$DBUS_CFLAGS" + AC_SUBST(LIB_YAUAP) + AC_SUBST(CFLAGS_YAUAP) + AC_DEFINE(HAVE_YAUAP, 1, [have yauap]) + fi + fi +fi + +AM_CONDITIONAL(with_yauap, [test x$have_yauap = xyes]) + +############################################################################### +# END yauap CHECK +############################################################################### + + + +############################################################################### +# BEGIN stdint.h CHECK +############################################################################### + +AC_CHECK_HEADER(stdint.h) + +############################################################################### +# END stdint.h CHECK +############################################################################### + + +############################################################################### +# BEGIN fabsf CHECK +############################################################################### + +AC_CHECK_DECLS([fabsf],,,[#include ]) +if test "$ac_cv_have_decl_fabsf" = "yes"; then + AC_COMPILE_IFELSE( + [AC_LANG_PROGRAM( + [[#include ]], + [[return (int)fabsf(1.f);]])], + [AC_DEFINE(HAVE_FABSF, 1, [have fabsf])]) +fi + +############################################################################### +# END fabsf CHECK +############################################################################### + + + + + +############################################################################### +# BEGIN INOTIFY CHECK +############################################################################### + +AC_CHECK_HEADERS(linux/inotify.h) + +if test x"$ac_cv_header_linux_inotify_h" = x"yes"; then + AC_DEFINE(HAVE_INOTIFY, 1, [have inotify]) +fi + +############################################################################### +# END INOTIFY CHECK +############################################################################### + + + +############################################################################### +# BEGIN QT OPENGL CHECK +############################################################################### + +AC_ARG_WITH(opengl, + AC_HELP_STRING([--without-opengl],[build Amarok without OpenGL support]), + [build_opengl=$withval], + [build_opengl=yes] +) + +if test "$build_opengl" != "no"; then + AC_MSG_CHECKING(for Qt with OpenGL support) + AC_CACHE_VAL(ac_cv_kde_qt_has_opengl, + [ + AC_LANG_SAVE + AC_LANG_CPLUSPLUS + + save_CXXFLAGS="$CXXFLAGS" + save_LIBS="$LIBS" + save_LDFLAGS="$LDFLAGS" + + CXXFLAGS="$CXXFLAGS -I$qt_incdir $all_includes" + LDFLAGS="$LDFLAGS -L$qt_libdir $all_libraries $USER_LDFLAGS $KDE_MT_LDFLAGS" + LIBS="$LIBS $LIBQT $KDE_MT_LIBS" + + AC_TRY_LINK([ + #include + ], + [ + (void)new QGLWidget((QWidget*)0, "qgl"); + ], + ac_cv_kde_qt_has_opengl=yes, + ac_cv_kde_qt_has_opengl=no) + + CXXFLAGS="$save_CXXFLAGS" + LIBS="$save_LIBS" + LDFLAGS="$save_LDFLAGS" + AC_LANG_RESTORE + ]) + AC_MSG_RESULT($ac_cv_kde_qt_has_opengl) + + if test x$ac_cv_kde_qt_has_opengl = xyes; then + AC_DEFINE(HAVE_QGLWIDGET, 1, [have Qt with OpenGL support]) + gl_libs="-lGL" + else + gl_libs="" + fi + + AC_SUBST(gl_libs) +fi + +############################################################################### +# END QT OPENGL CHECK +############################################################################### + + + +############################################################################### +# BEGIN SQLITE CHECK +############################################################################### + +LIB_SQLITE="" + +AC_ARG_WITH(included-sqlite, + AC_HELP_STRING([--without-included-sqlite],[build Amarok using system sqlite library]), + [included_sqlite=$withval], + [included_sqlite=yes] +) + +if test x$included_sqlite = xno; then + if test x$PKGCONFIGFOUND = xyes; then + PKG_CHECK_MODULES(SQLITE, sqlite3 >= 3.0, have_sqlite=yes,have_sqlite=no) + + if test x$have_sqlite = xyes; then + ## AC_DEFINE(HAVE_SQLITE, 1, [have SQLite database library]) + LIB_SQLITE=`pkg-config --libs sqlite3` + else + # We don't support not having sqlite anymore + DO_NOT_COMPILE="$DO_NOT_COMPILE amarok" + no_amarok=yes + fi + fi +fi + +AC_SUBST(LIB_SQLITE) +AM_CONDITIONAL(with_included_sqlite, [test x$included_sqlite = xyes]) + + +# Determine pointer size for sqlite + +KDE_CHECK_TYPES +AC_DEFINE(SQLITE_PTR_SZ, SIZEOF_CHAR_P, [Determine pointer size for SQLite]) + +############################################################################### +# END SQLITE CHECK +############################################################################### + + + +############################################################################### +# BEGIN MYSQL CHECK +############################################################################### + +AC_ARG_ENABLE(mysql, + AC_HELP_STRING([--enable-mysql],[build Amarok with MySQL support]), + [enable_mysql=$enableval], + [enable_mysql=no] +) + +if test "$enable_mysql" = "yes"; then + + AC_CHECK_PROG(MYSQL_CONFIG, mysql_config, yes) + + if test x$MYSQL_CONFIG = xyes; then + AC_DEFINE(USE_MYSQL, 1, [MySql database support enabled]) + + mysql_includes=`mysql_config --cflags` + mysql_libs=`mysql_config --libs` + else + amarok_warning_mysql_notfound=yes + enable_mysql=no + fi + +fi + +AM_CONDITIONAL(enable_mysql, test x$enable_mysql = xyes) +AC_SUBST(mysql_includes) +AC_SUBST(mysql_libs) + +############################################################################### +# END MYSQL CHECK +############################################################################### + + + +############################################################################### +# BEGIN POSTGRESQL CHECK +############################################################################### + +AC_ARG_ENABLE(postgresql, + AC_HELP_STRING([--enable-postgresql],[build Amarok with PostgreSQL support]), + [enable_postgresql=$enableval], + [enable_postgresql=no] +) + +if test "$enable_postgresql" = "yes"; then + + AC_CHECK_PROG(POSTGRESQL_CONFIG, pg_config, yes) + + if test x$POSTGRESQL_CONFIG = xyes; then + AC_DEFINE(USE_POSTGRESQL, 1, [Postgresql database support enabled]) + + postgresql_includes=-I`pg_config --includedir` + postgresql_libs="-L`pg_config --libdir` -lpq" + else + amarok_warning_postgresql_notfound=yes + enable_postgresql=no + fi + +fi + +AM_CONDITIONAL(enable_postgresql, test x$enable_postgresql = xyes) +AC_SUBST(postgresql_includes) +AC_SUBST(postgresql_libs) + +############################################################################### +# END POSTGRESQL CHECK +############################################################################### + + + +############################################################################### +# BEGIN LIBVISUAL CHECK +############################################################################### + +AC_ARG_WITH(libvisual, + AC_HELP_STRING([--without-libvisual],[build Amarok without libvisual support]), + [with_libvisual=$withval], + [with_libvisual=yes] +) + +if test "$with_libvisual" = "yes"; then + ## libvisual plugin depends on sdl + AC_CHECK_PROG(SDL_CONFIG, sdl-config, yes) + + if test x$SDL_CONFIG = xyes; then + sdl_cflags=`sdl-config --cflags` + sdl_libs=`sdl-config --libs` + fi + + AC_SUBST(sdl_cflags) + AC_SUBST(sdl_libs) + + if test x$PKGCONFIGFOUND = xyes -a x$SDL_CONFIG = xyes; then + + PKG_CHECK_MODULES(LIBVISUAL, libvisual-0.4 >= 0.4.0, [ + LIBVISUAL_LIBS=`echo $LIBVISUAL_LIBS | sed -e 's/-lpthread//' -e 's/-ldl//'` + build_libvisual="yes" + ], [build_libvisual="no"]) + + AC_SUBST(LIBVISUAL_LIBS) + AC_SUBST(LIBVISUAL_CFLAGS) + + if test x$build_libvisual = xyes; then + AC_DEFINE(HAVE_LIBVISUAL, 1, [have LIBVISUAL]) + fi + fi +fi + +AM_CONDITIONAL(with_libvisual, test x$build_libvisual = xyes) + +############################################################################### +# END LIBVISUAL CHECK +############################################################################### + + + +############################################################################### +# BEGIN TUNEPIMP CHECK +############################################################################### + +AC_ARG_WITH(musicbrainz, + AC_HELP_STRING([--without-musicbrainz],[build Amarok without MusicBrainz support]), + [with_musicbrainz=$withval], + [with_musicbrainz=yes] +) + +if test "$with_musicbrainz" = "yes"; then + AC_CHECK_HEADER(tunepimp-0.5/tp_c.h, [build_musicbrainz="yes"], + [AC_CHECK_HEADER(tunepimp/tp_c.h, [build_musicbrainz="yes"], + [build_musicbrainz="no"])]) +fi + +if test "$build_musicbrainz" = "yes"; then + AC_CHECK_LIB(tunepimp, tr_GetPUID, + AC_DEFINE(HAVE_TUNEPIMP, 5, [have MusicBrainz 0.5.x]), + AC_CHECK_LIB(tunepimp, tp_SetFileNameEncoding, + AC_DEFINE(HAVE_TUNEPIMP, 4, [have MusicBrainz 0.4.x]), + AC_DEFINE(HAVE_TUNEPIMP, 1, [have MusicBrainz]))) + LIB_TUNEPIMP="-ltunepimp" + have_tunepimp=yes +else + AC_DEFINE(HAVE_TUNEPIMP, 0, [have TunePimp]) + LIB_TUNEPIMP="" + have_tunepimp=no +fi + +AC_SUBST(LIB_TUNEPIMP) + +############################################################################### +# END TUNEPIMP CHECK +############################################################################### + + + +############################################################################### +# BEGIN AMAZON CHECK +############################################################################### + +AC_ARG_ENABLE(amazon, + AC_HELP_STRING([--disable-amazon],[disable Amazon cover download support [default=enable]]), + [enable_amazon=$enableval], + [enable_amazon=yes] +) + +if test "$enable_amazon" != "no"; then + AC_DEFINE(AMAZON_SUPPORT, 1, [Amazon cover download support enabled]) +fi + +############################################################################### +# END AMAZON CHECK +############################################################################### + + + +############################################################################### +# BEGIN SCHED_SETAFFINITY BUGGY GLIBC CHECK +############################################################################### + +AC_MSG_CHECKING([if sched_setaffinity should be enabled]) + +AC_LANG_SAVE +AC_LANG_CPLUSPLUS + +amarok_glibcsched_works=no + +AC_TRY_COMPILE([ + #include +], +[ + cpu_set_t mask; + CPU_ZERO( &mask ); + CPU_SET( 0, &mask ); + sched_setaffinity( 0, sizeof(mask), &mask ); +], + amarok_sched_3params=yes, + amarok_sched_3params=no +) + +if test "x$amarok_sched_3params" = "xyes"; then + AC_DEFINE(SCHEDAFFINITY_SUPPORT, 1, [sched_setaffinity works correctly]) + AC_DEFINE(SCHEDAFFINITY_3PARAMS, 1, [sched_setaffinity takes three params]) + amarok_glibcsched_works=yes +fi + +if test "x$amarok_sched_3params" = "xno"; then + AC_TRY_COMPILE([ + #include + ], + [ + cpu_set_t mask; + CPU_ZERO( &mask ); + CPU_SET( 0, &mask ); + sched_setaffinity( 0, &mask ); + ], + amarok_sched_2params=yes, + amarok_sched_2params=no + ) + if test "x$amarok_sched_2params" = "xyes"; then + AC_DEFINE(SCHEDAFFINITY_SUPPORT, 1, [sched_setaffinity works correctly]) + amarok_glibcsched_works=yes + fi +fi + +AC_LANG_RESTORE + +AC_MSG_RESULT($amarok_glibcsched_works) + +############################################################################### +# END SCHED_SETAFFINITY BUGGY GLIBC CHECK +############################################################################### + + + +############################################################################### +# BEGIN KDEBASE CHECK +############################################################################### + +KDE_CHECK_HEADER(konqsidebarplugin.h, have_konqsidebar=yes, have_konqsidebar=no) +KDE_CHECK_LIB(konqsidebarplugin, _init, have_konqsidebar=$have_konqsidebar, have_konqsidebar=no) + +AM_CONDITIONAL(with_konqsidebar, [test x$have_konqsidebar = xyes]) + +############################################################################### +# END KDEBASE CHECK +############################################################################### + + + +############################################################################### +# BEGIN NJB CHECK +############################################################################### +### mediabrowser.cpp can use libnjb if available + +AC_ARG_WITH(libnjb, + AC_HELP_STRING([--with-libnjb],[build Amarok with Nomad Jukebox support from libnjb]), + [build_libnjb=$withval], + [build_libnjb=yes] +) + +if test "$build_libnjb" != "no"; then + if test "$PKGCONFIGFOUND" = "yes" ; then + + # check for libnjb + have_libnjb=no + + PKG_CHECK_MODULES(LIBNJB, libnjb, have_libnjb=yes,have_libnjb=no) + if test "x$have_libnjb" != "xno"; then + AC_DEFINE(HAVE_LIBNJB, 1, [have libnjb]) + fi + fi +fi + +AM_CONDITIONAL(with_libnjb, [test x$have_libnjb = xyes]) + +############################################################################### +# END NJB CHECK +############################################################################### + + + +############################################################################### +# BEGIN MTP CHECK +############################################################################### +### mediabrowser.cpp can use libmtp if available + +AC_ARG_WITH(libmtp, + AC_HELP_STRING([--with-libmtp],[build Amarok with support for MTP devices]), + [build_libmtp=$withval], + [build_libmtp=yes] +) +if test "$build_libmtp" != "no"; then + if test "$PKGCONFIGFOUND" = "yes" ; then + PKG_CHECK_MODULES(LIBMTP, libmtp >= 0.1.1, + [ + LIBMTP_LIBS=`echo ${LIBMTP_LIBS} | sed 's/-lusb//'` + have_libmtp=yes + ], + [ + have_libmtp=no + ]) + fi + + if test "x$have_libmtp" != "xno"; then + AC_DEFINE(HAVE_LIBMTP, 1, [have libmtp]) + fi +fi + +AM_CONDITIONAL(with_libmtp, [test x$have_libmtp = xyes]) + +############################################################################### +# END MTP CHECK +############################################################################### + +############################################################################### +# BEGIN RIO KARMA CHECK +############################################################################### +### mediabrowser.cpp can use libkarma if available + +AC_ARG_WITH(libkarma, + AC_HELP_STRING([--with-libkarma],[build Amarok with Rio Karma support]), + [build_libkarma=$withval], + [build_libkarma=yes] +) + +if test "$build_libkarma" != "no"; then + AC_CHECK_HEADERS([libkarma/lkarma.h], [have_libkarma=yes], [], []) + AC_CHECK_HEADERS([usb.h], [have_usb=yes], [], []) + + if test "$have_libkarma" = "yes"; then + AC_DEFINE(HAVE_LIBKARMA, 1, [have libkarma]) + else + AC_MSG_RESULT($have_libkarma) + have_libkarma=no + fi +fi + +AM_CONDITIONAL(with_libkarma, [test x$have_libkarma = xyes]) + +############################################################################### +# END RIO KARMA CHECK +############################################################################### + +############################################################################### +# BEGIN IFP CHECK +############################################################################### +### mediabrowser.cpp can use libifp if available + +AC_ARG_WITH(ifp, + AC_HELP_STRING([--with-ifp],[build Amarok with ifp support]), + [build_ifp=$withval], + [build_ifp=yes] +) + +if test "$build_ifp" != "no"; then + + AC_CHECK_HEADERS([ifp.h], [have_ifp=yes], [], []) + AC_CHECK_HEADERS([usb.h], [have_usb=yes], [], []) + + if test "$have_ifp" = "yes"; then + AC_DEFINE(HAVE_IFP, 1, [have ifp]) + IFP_LIBS="-lifp -lusb" + else + AC_MSG_RESULT($have_ifp) + have_ifp=no + fi + +fi + +AC_SUBST(IFP_INCLUDES) +AC_SUBST(IFP_LIBS) + +AM_CONDITIONAL(with_ifp, [test x$have_ifp = xyes]) + +############################################################################### +# END IFP CHECK +############################################################################### + + + +############################################################################### +# BEGIN LIBGPOD CHECK +############################################################################### + +AC_ARG_WITH(libgpod, + AC_HELP_STRING([--with-libgpod],[build Amarok with iPod support from libgpod]), + [build_libgpod=$withval], + [build_libgpod=yes] +) + +if test "$build_libgpod" != "no"; then + if test "$PKGCONFIGFOUND" = "yes" ; then + + # check for libgpod + have_libgpod=no + + PKG_CHECK_MODULES(LIBGPOD, libgpod-1.0, have_libgpod=yes,have_libgpod=no) + + if test "x$have_libgpod" = "xyes"; then + ac_cppflags_save=$CPPFLAGS + ac_cflags_save=$CFLAGS + ac_libs_save=$LIBS + CPPFLAGS="$CPPFLAGS $LIBGPOD_INCLUDES" + CFLAGS="$CFLAGS $LIBGPOD_CFLAGS" + LIBS="$LIBS $LIBGPOD_LIBS" + + AC_CHECK_FUNCS(itdb_track_set_thumbnails, , have_libgpod_042=no) + AC_CHECK_FUNCS(itdb_get_mountpoint, , have_libgpod_042=no) + AC_CHECK_FUNCS(itdb_device_get_ipod_info, , have_libgpod_042=no) + + AC_CHECK_MEMBER(struct _Itdb_Track.movie_flag, + [AC_DEFINE(HAVE_ITDB_MOVIE_FLAG, 1, [have libgpod movie flag])], + have_libgpod_042=no, + [#include ]) + + AC_CHECK_MEMBER(struct _Itdb_Track.skip_when_shuffling, + [AC_DEFINE(HAVE_ITDB_SKIP_SHUFFLE_FLAG, 1, [have libgpod skip when shuffling flag])], + have_libgpod_042=no, + [#include ]) + + AC_CHECK_MEMBER(struct _Itdb_Track.mark_unplayed, + [AC_DEFINE(HAVE_ITDB_MARK_UNPLAYED, 1, [have libgpod mark played flag])], + have_libgpod_042=no, + [#include ]) + + AC_CHECK_MEMBER(struct _Itdb_Track.mediatype, + [AC_DEFINE(HAVE_ITDB_MEDIATYPE, 1, [have libgpod mediatype flag])], + have_libgpod_042=no, + [#include ]) + + AC_CHECK_DECL(ITDB_IPOD_MODEL_TOUCH_BLACK, + [AC_DEFINE(HAVE_LIBGPOD_060, 1, [have at least libgpod 0.6.0])], + have_libgpod_060=no, + [#include ]) + + CPPFLAGS=$ac_cppflags_save + CFLAGS=$ac_cflags_save + LIBS=$ac_libs_save + fi + + if test "x$have_libgpod_042" = "xno"; then + have_libgpod=no + AC_MSG_RESULT(Your libgpod version is too old: at least 0.4.2 is required) + fi + + if test "x$have_libgpod" != "xno"; then + AC_DEFINE(HAVE_LIBGPOD, 1, [have libgpod]) + fi + fi +fi + +AM_CONDITIONAL(with_libgpod, [test x$have_libgpod = xyes]) + +############################################################################### +# END LIBGPOD CHECK +############################################################################### + + + +############################################################################### +# BEGIN statvfs(2) CHECK +############################################################################### + +AC_CHECK_FUNCS(statvfs) + +############################################################################### +# END statvfs(2) CHECK +############################################################################### + + + +############################################################################### +# BEGIN MP4V2 CHECK +############################################################################### +# m4a/aac tag reading and writing needs libmp4v2 from faad2 or better mpeg4ip + +AC_ARG_WITH(mp4v2, + AC_HELP_STRING([--with-mp4v2],[build Amarok with M4A/AAC tag support from mp4v2/faad2]), + [have_mp4v2=$withval], + [have_mp4v2=no] +) + +AC_ARG_WITH(mp4v2-dir, + AC_HELP_STRING([--with-mp4v2-dir],[path to mp4v2 [default=/usr]]), + [mp4v2_dir="$withval"], + [mp4v2_dir=/usr] +) + +if test "$have_mp4v2" != "no"; then + AC_LANG_SAVE + AC_LANG_CPLUSPLUS + ac_cxxflags_save=$CXXFLAGS + CXXFLAGS="$CXXFLAGS -I$mp4v2_dir/include" + ac_ldflags_save=$LDFLAGS + LDFLAGS="$LDFLAGS -L$mp4v2_dir/lib" + + # not even everyone using faad2 has + if ! test -f config.h; then + echo "#include \"confdefs.h\"" > config.h + fi + ac_cppflags_save=$CPPFLAGS + CPPFLAGS="$CPPFLAGS -I." + AC_CHECK_HEADERS(systems.h) + AC_CHECK_HEADERS([mp4.h], [have_mp4_h=yes], [], + [#ifdef HAVE_SYSTEMS_H + # include + #endif + ]) + + AC_CHECK_LIB( mp4v2, MP4Read, have_mp4v2=yes, have_mp4v2=no ) + + if test "$have_mp4v2" = "yes" -a "$have_mp4_h" = "yes"; then + AC_DEFINE(HAVE_MP4V2, 1, [have mp4v2]) + MP4V2_INCLUDES="-I$mp4v2_dir/include" + MP4V2_LIBS="-L$mp4v2_dir/lib -lmp4v2" + else + have_mp4v2=no + fi + + CPPFLAGS=$ac_cppflags_save + CXXFLAGS=$ac_cxxflags_save + LDFLAGS=$ac_ldflags_save + AC_LANG_RESTORE +fi + +AC_SUBST(MP4V2_INCLUDES) +AC_SUBST(MP4V2_LIBS) + +AM_CONDITIONAL(with_mp4v2, [test x$have_mp4v2 != xno ]) + +############################################################################### +# END MP4V2 CHECK +############################################################################### + + + +############################################################################### +# BEGIN DAAP KDE 3.4 CHECK +############################################################################### + +daapsave_CXXFLAGS="$CXXFLAGS" +CXXFLAGS="$CXXFLAGS $all_includes" +AC_LANG_SAVE +AC_LANG_CPLUSPLUS + +AC_MSG_CHECKING([if KDE is at least 3.4 for DAAP support]) +AC_COMPILE_IFELSE([ +#include +#if ! ( KDE_IS_VERSION( 3, 4, 0 ) ) +#error KDE 3.4 +#endif +], + have_kde34="yes" + DNSSD_LIBS=$LIB_KDNSSD +# echo "yes" +, + have_kde34="no" + DNSSD_LIBS="" +# echo "no" +) +CXXFLAGS="$daapsave_CXXFLAGS" +AC_LANG_RESTORE +AC_SUBST(DNSSD_LIBS) +AM_CONDITIONAL(atleast_kde34, [test x$have_kde34 != xno ]) +AC_MSG_RESULT($have_kde34) + +############################################################################### +# END DAAP KDE 3.4 CHECK +############################################################################### + + + +############################################################################### +# BEGIN OPTIONAL DAAP SUPPORT +############################################################################### + +AC_ARG_WITH(daap, + AC_HELP_STRING([--without-daap],[build Amarok without support for DAAP]), + [have_daap=$withval], + [have_daap=yes] +) + +AM_CONDITIONAL(with_daap, [test x$have_daap = xyes]) + +############################################################################### +# END OPTIONAL DAAP SUPPORT +############################################################################### + + + +############################################################################### +# BEGIN DAAP TYPE CHECKS +############################################################################### + +AC_CHECK_TYPES([uint8_t, u_int8_t, uint16_t, u_int16_t, uint32_t, u_int32_t, uint64_t, u_int64_t]) + +############################################################################### +# END DAAP TYPE CHECKS +############################################################################### + + + +############################################################################### +# BEGIN DAAP MONGREL RUBY VARIABLE +############################################################################### + +AC_PATH_PROG(RUBY, ruby, no) + +ruby_includes=[`$RUBY -rrbconfig -e 'puts Config.expand( Config::MAKEFILE_CONFIG["archdir"] )'`] +ruby_ldflags=[`$RUBY -rrbconfig -e 'puts Config.expand( Config::MAKEFILE_CONFIG["LIBRUBYARG_SHARED"] )'`] + +AC_SUBST(ruby_includes) +AC_SUBST(ruby_ldflags) +OLDCFLAGS="$CFLAGS" +CFLAGS="-I$ruby_includes -Wall" +OLDCPPFLAGS="$CPPFLAGS" +CPPFLAGS="-I$ruby_includes" #no I don't know why CPPFLAGS is used +AC_CHECK_HEADERS([ruby.h], [have_ruby_h=yes], [have_ruby_h=no]) #used in ruby check below +CFLAGS="$OLDCFLAGS" +CPPFLAGS="$OLDCPPFLAGS" + +############################################################################### +# END DAAP MONGREL RUBY VARIABLE +############################################################################### + + + +############################################################################### +# BEGIN RUBY CHECK +############################################################################### +## TODO: Check version number >= 1.8 + +if test "x$RUBY" = "xno" -o "x$have_ruby_h" = "xno"; then + amarok_error_noruby=yes + DO_NOT_COMPILE="$DO_NOT_COMPILE amarok" + no_amarok=yes +fi + +############################################################################### +# END RUBY CHECK +############################################################################### + + +############################################################################### +# BEGIN DO_NOT_COMPILE CHECK +############################################################################### + +if test x$build_xine = xno -a x$build_helix = xno; then + + DO_NOT_COMPILE="$DO_NOT_COMPILE amarok" + no_engine=yes + +fi + +############################################################################### +# END DO_NOT_COMPILE CHECK +############################################################################### + diff --git a/amarok/docs/collection_redesign.xmi b/amarok/docs/collection_redesign.xmi new file mode 100644 index 00000000..ea1c9fc5 --- /dev/null +++ b/amarok/docs/collection_redesign.xmi @@ -0,0 +1,810 @@ + + + + + umbrello uml modeller http://uml.sf.net + 1.5.5 + UnicodeUTF8 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/amarok/docs/use_umbrello_to_open_xmi_files b/amarok/docs/use_umbrello_to_open_xmi_files new file mode 100644 index 00000000..e69de29b diff --git a/amarok/src/Makefile.am b/amarok/src/Makefile.am new file mode 100644 index 00000000..92d18bfc --- /dev/null +++ b/amarok/src/Makefile.am @@ -0,0 +1,272 @@ +if with_included_sqlite + SQLITE_SUBDIR = sqlite + LIB_SQLITE_LOCAL = $(top_builddir)/amarok/src/sqlite/libsqlite.la + sqlite_includes = -I$(top_srcdir)/amarok/src/sqlite +endif + +if with_konqsidebar + KONQSIDEBAR_SUBDIR = konquisidebar +endif + +KDE_OPTIONS = nofinal + +lib_LTLIBRARIES = libamarok.la + +SUBDIRS = \ + amarokcore \ + magnatunebrowser \ + $(SQLITE_SUBDIR) \ + analyzers \ + data \ + plugin \ + images \ + loader \ + scripts \ + themes \ + vis \ + metadata \ + $(KONQSIDEBAR_SUBDIR) \ + statusbar \ + . \ + engine \ + mediadevice \ + device \ + collectionscanner + + +INCLUDES = \ + -I$(top_builddir)/amarok/src/amarokcore \ + -I$(top_builddir)/amarok/src/magnatunebrowser \ + -I$(top_srcdir)/amarok/src/amarokcore \ + -I$(top_srcdir)/amarok/src/analyzers \ + -I$(top_srcdir)/amarok/src/plugin \ + -I$(top_srcdir)/amarok/src/statusbar \ + -I$(top_srcdir)/amarok/src/mediadevice \ + -I$(top_srcdir)/amarok/src/device \ + -I$(top_srcdir)/amarok/src \ + -I$(kde_includes)/arts \ + $(TAGLIB_CFLAGS) \ + $(sqlite_includes) \ + $(mysql_includes) \ + $(postgresql_includes) \ + $(EXSCALIBAR_CFLAGS) \ + $(all_includes) + +libamarok_la_SOURCES = \ + Options1.ui \ + Options1.ui.h \ + Options2.ui \ + Options4.ui \ + Options5.ui \ + Options7.ui \ + Options8.ui \ + actionclasses.cpp \ + app.cpp \ + atomicstring.cpp \ + atomicurl.cpp \ + browserbar.cpp \ + clicklineedit.cpp \ + collectionbrowser.cpp \ + collectiondb.cpp \ + columnlist.cpp \ + configdialog.cpp \ + contextbrowser.cpp \ + coverfetcher.cpp \ + covermanager.cpp \ + cuefile.cpp \ + dbsetup.ui \ + dbsetup.ui.h \ + deletedialog.cpp \ + deletedialogbase.ui \ + deviceconfiguredialog.cpp \ + devicemanager.cpp \ + directorylist.cpp \ + dynamicmode.cpp \ + enginebase.cpp \ + enginecontroller.cpp \ + engineobserver.cpp \ + equalizergraph.cpp \ + equalizerpresetmanager.cpp \ + equalizersetup.cpp \ + expression.cpp \ + fht.cpp \ + filebrowser.cpp \ + firstrunwizard.ui \ + hintlineedit.cpp \ + htmlview.cpp \ + iconloader.cpp \ + k3bexporter.cpp \ + kbookmarkhandler.cpp \ + ktrm.cpp \ + lastfm.cpp \ + mediabrowser.cpp \ + mediadevicemanager.cpp \ + medium.cpp \ + mediumpluginmanager.cpp \ + metabundle.cpp \ + metabundlesaver.cpp \ + moodbar.cpp \ + mountpointmanager.cpp \ + multitabbar.cpp \ + mydiroperator.cpp \ + newdynamic.ui \ + organizecollectiondialog.ui \ + osd.cpp \ + pixmapviewer.cpp \ + playerwindow.cpp \ + playlist.cpp \ + playlistbrowser.cpp \ + playlistbrowseritem.cpp \ + playlistitem.cpp \ + playlistloader.cpp \ + playlistselection.cpp \ + playlistwindow.cpp \ + pluginmanager.cpp \ + podcastsettings.cpp \ + podcastsettingsbase.ui \ + prettypopupmenu.cpp \ + queuemanager.cpp \ + refreshimages.cpp \ + scancontroller.cpp \ + scriptmanager.cpp \ + scriptmanagerbase.ui \ + scrobbler.cpp \ + sliderwidget.cpp \ + smartplaylisteditor.cpp \ + socketserver.cpp \ + starmanager.cpp \ + statistics.cpp \ + systray.cpp \ + tagdialog.cpp \ + tagdialogbase.ui \ + tagguesser.cpp \ + tagguesserconfigdialog.ui \ + threadmanager.cpp \ + tooltip.cpp \ + trackpickerdialog.cpp \ + trackpickerdialogbase.ui \ + tracktooltip.cpp \ + transferdialog.cpp \ + xmlloader.cpp \ + xspfplaylist.cpp \ + editfilterdialog.cpp + +libamarok_la_LIBADD = \ + $(top_builddir)/amarok/src/amarokcore/libamarokcore.la \ + $(top_builddir)/amarok/src/analyzers/libanalyzers.la \ + $(top_builddir)/amarok/src/plugin/libplugin.la \ + $(top_builddir)/amarok/src/statusbar/libstatusbar.la \ + $(top_builddir)/amarok/src/metadata/libmetadata.la \ + $(top_builddir)/amarok/src/magnatunebrowser/libmagnatunebrowser.la \ + $(LIB_QT) $(LIB_KPARTS) -lDCOP -lkdefx $(KDE_MT_LIBS) $(LIB_KFILE) $(LIB_KDEUI) $(LIB_KDECORE) $(LIB_KHTML) $(LIB_KNEWSTUFF) \ + $(TAGLIB_LIBS) $(gl_libs) $(LIB_SQLITE) $(LIB_SQLITE_LOCAL) \ + $(LIB_TUNEPIMP) \ + $(mysql_libs) \ + $(postgresql_libs) + +libamarok_la_LDFLAGS = \ + $(all_libraries) \ + $(KDE_RPATH) + +METASOURCES = \ + AUTO + +KDE_ICON = \ + AUTO + + + +bin_SCRIPTS = amarok_proxy.rb + +bin_PROGRAMS = amarokapp +# atomicstring_unittest is excluded from the build by default to avoid compile slowdowns. +# If you intend to do work in this area and want to build it, comment the line above and +# uncomment the lines below: +#bin_PROGRAMS = amarokapp atomicstring_unittest +#atomicstring_unittest_SOURCES = atomicstring_unittest.cpp atomicstring.h +#atomicstring_unittest_LDADD = atomicstring.$(OBJEXT) $(LIB_KDECORE) + + +amarokapp_SOURCES = main.cpp + +amarokapp_LDADD = \ + $(top_builddir)/amarok/src/amarokcore/libamarokcore.la \ + libamarok.la \ + $(top_builddir)/amarok/src/analyzers/libanalyzers.la \ + $(top_builddir)/amarok/src/plugin/libplugin.la \ + $(top_builddir)/amarok/src/statusbar/libstatusbar.la \ + $(top_builddir)/amarok/src/metadata/libmetadata.la \ + $(LIB_KDECORE) + $(EXSCALIBAR_LIBS) + +amarokapp_LDFLAGS = \ + $(all_libraries) \ + $(KDE_RPATH) + +rcdir = \ + $(kde_datadir)/amarok + +rc_DATA = \ + amarokui.rc + + +configdir = \ + $(kde_confdir) + +config_DATA = \ + amarokrc + + +xdg_apps_DATA = \ + amarok.desktop + +kde_servicetypes_DATA = \ + amarok_plugin.desktop \ + amarok_codecinstall.desktop + + +servicemenudir = \ + $(kde_datadir)/konqueror/servicemenus + +servicemenu_DATA = \ + amarok_addaspodcast.desktop \ + amarok_append.desktop \ + amarok_play_audiocd.desktop + + +profiledatadir = \ + $(kde_datadir)/profiles + +profiledata_DATA = \ + amarok.profile.xml + +protocoldir = \ + $(kde_servicesdir) + +protocol_DATA = \ + amarokitpc.protocol \ + amaroklastfm.protocol \ + amarokpcast.protocol + +messages: rc.cpp + $(EXTRACTRC) `find . -name "*.rc" -o -name "*.ui" -o -name "*.kcfg"` > rc.cpp + LIST=`find . -name \*.h -o -name \*.hh -o -name \*.H -o -name \*.hxx -o -name \*.hpp -o -name \*.cpp -o -name \*.cc -o -name \*.cxx -o -name \*.ecpp -o -name \*.C`; \ + if test -n "$$LIST"; then \ + $(XGETTEXT) $$LIST -o $(podir)/amarok.pot; \ + fi + +install-exec-hook: + @echo "" + @echo "==========================" + @echo "= Amarok - INSTALLED ================================" + @echo "==========================" + @echo "=" + @echo "= Type amarok to start!" + @echo "=" + @echo "= If you have problems, please consult the README;" + @echo "= if the problems continue join us on #amarok." + @echo "=" + @echo "=======================================================" + + + diff --git a/amarok/src/Options1.ui b/amarok/src/Options1.ui new file mode 100644 index 00000000..5fa3f3bf --- /dev/null +++ b/amarok/src/Options1.ui @@ -0,0 +1,700 @@ + +Options1 + + + General + + + + 0 + 0 + 431 + 556 + + + + + 3 + 3 + 0 + 0 + + + + General + + + + unnamed + + + 0 + + + + generalBox + + + 0 + + + General Options + + + + unnamed + + + + kcfg_ShowSplashscreen + + + Sho&w splash-screen on startup + + + Check to enable the splashscreen during Amarok startup. + + + Check to enable the splashscreen during Amarok startup. + + + + + kcfg_ShowTrayIcon + + + Show tray &icon + + + Check to enable the Amarok system tray icon. + + + Check to enable the Amarok system tray icon. + + + + + layout5 + + + + unnamed + + + + spacer3_2 + + + Horizontal + + + Fixed + + + + 16 + 21 + + + + + + kcfg_AnimateTrayIcon + + + false + + + &Flash tray icon when playing + + + Check to animate the Amarok system tray icon. + + + Check to animate the Amarok system tray icon. + + + + + + + kcfg_ShowPlayerWindow + + + Show player window + + + Check to enable an extra player window. + + + Check to enable an extra player window. + + + + + layout4 + + + + unnamed + + + + textLabel1 + + + Default si&ze for cover previews: + + + kcfg_CoverPreviewSize + + + Size of the cover image in the context viewer in pixels. + + + Size of the cover image in the context viewer in pixels. + + + + + kcfg_CoverPreviewSize + + + px + + + 300 + + + 50 + + + 100 + + + Size of the cover images in the context viewer in pixels. + + + Size of the cover images in the context viewer in pixels. + + + + + spacer7 + + + Horizontal + + + Expanding + + + + 30 + 20 + + + + + + + + layout7 + + + + unnamed + + + + textLabel1_2 + + + + 150 + 0 + + + + External web &browser: + + + kComboBox_browser + + + + + kComboBox_browser + + + + 7 + 0 + 0 + 0 + + + + + 150 + 0 + + + + Choose the external web browser to be used by Amarok. + + + + + spacer7_2 + + + Horizontal + + + Expanding + + + + 50 + 21 + + + + + + + + layout8 + + + + unnamed + + + + checkBox_customBrowser + + + true + + + + 150 + 0 + + + + Use &another browser: + + + + + kLineEdit_customBrowser + + + false + + + + 7 + 0 + 0 + 0 + + + + + 150 + 0 + + + + Enter filename of external web browser. + + + + + spacer7_2_3 + + + Horizontal + + + Expanding + + + + 50 + 21 + + + + + + + + + + componentsBox + + + 0 + + + Components + + + + unnamed + + + + kcfg_UseScores + + + Use &scores + + + false + + + Scores for tracks are calculated automatically, based on your listening habits. + + + + + kcfg_UseRatings + + + Use ratings + + + You can assign ratings to tracks manually, from 1 to 5 stars. + + + + + line1 + + + HLine + + + Sunken + + + Horizontal + + + + + moodbarHelpLabel + + + You need the <a href='http://amarok.kde.org/wiki/Moodbar'>moodbar package</a> installed to enable the moodbar feature. + + + + + moodFrame + + + NoFrame + + + Raised + + + 0 + + + 0 + + + + unnamed + + + 0 + + + 0 + + + + kcfg_ShowMoodbar + + + true + + + Use &moods + + + true + + + Displays a visual representation of the current track in the slider bar of the player window and a column in the playlist window. + + + Displays a visual representation of the current track in the slider bar of the player window and a column in the playlist window. + + + + + layout8_2_2 + + + + unnamed + + + + spacerMoodier + + + Horizontal + + + Fixed + + + + 16 + 21 + + + + + + kcfg_MakeMoodier + + + true + + + Make m&oodier: + + + When enabled, the hue distribution is quantised and respread evenly, giving a prettier but less meaningful output. + + + When enabled, the hue distribution is quantised and respread evenly, giving a prettier but less meaningful output. + + + + + + Happy Like a Rainbow + + + + + Angry as Hell + + + + + Frozen in the Arctic + + + + kcfg_AlterMood + + + false + + + + + spacer7_2_2_2 + + + Horizontal + + + Expanding + + + + 130 + 20 + + + + + + + + layout7_2_2 + + + + unnamed + + + + spacerWithMusic + + + Horizontal + + + Fixed + + + + 16 + 21 + + + + + + kcfg_MoodsWithMusic + + + true + + + Stor&e mood data files with music + + + Enabling this option stores Mood data files with the music files. Disabling stores them in your home folder. + + + Enabling this option stores Mood data files with the music files. Namely, the mood file for /music/file.mp3 will be /music/file.mood. Disabling stores them in your home folder. + + + + + + + + + + + groupBox2 + + + 0 + + + Playlist-Window Options + + + + unnamed + + + + kcfg_SavePlaylist + + + &Remember current playlist on exit + + + If checked, Amarok saves the current playlist on quit and restores it when restarted.<br> + + + If checked, Amarok saves the current playlist on quit and restores it when restarted. + + + + + kcfg_RelativePlaylist + + + Manually sa&ved playlists use relative path + + + If checked, Amarok uses a relative path for the tracks of manually saved playlists + + + If checked, Amarok uses a relative path for the tracks of manually saved playlists + + + + + kcfg_AutoShowContextBrowser + + + Switch to Context &Browser on track change + + + Switch to the context browser, when playing a track.<br> + + + Switch to the context browser, when playing a track. + + + + + + + spacer5 + + + Vertical + + + Expanding + + + + 20 + 30 + + + + + + + + kcfg_ShowTrayIcon + toggled(bool) + kcfg_AnimateTrayIcon + setEnabled(bool) + + + checkBox_customBrowser + toggled(bool) + kComboBox_browser + setDisabled(bool) + + + checkBox_customBrowser + toggled(bool) + kLineEdit_customBrowser + setEnabled(bool) + + + kcfg_ShowMoodbar + toggled(bool) + General + slotUpdateMoodFrame() + + + kcfg_MakeMoodier + toggled(bool) + General + slotUpdateMoodFrame() + + + + Options1.ui.h + + + slotUpdateMoodFrame() + + + init() + + + + kcombobox.h + klineedit.h + kactivelabel.h + + diff --git a/amarok/src/Options1.ui.h b/amarok/src/Options1.ui.h new file mode 100644 index 00000000..62b322ed --- /dev/null +++ b/amarok/src/Options1.ui.h @@ -0,0 +1,218 @@ +//Released under GPLv2 or later. (C) 2005 Ian Monroe +/**************************************************************************** +** ui.h extension file, included from the uic-generated form implementation. +** +** If you want to add, delete, or rename functions or slots, use +** Qt Designer to update this file, preserving your code. +** +** You should not define a constructor or destructor in this file. +** Instead, write your code in functions called init() and destroy(). +** These will automatically be called by the form's constructor and +** destructor. +*****************************************************************************/ + +#include + +#include "amarokconfig.h" +#include "moodbar.h" +#include "starmanager.h" + +#include +#include + +void Options1::init() +{ + slotUpdateMoodFrame(); + //kcfg_CustomRatingsColors->setChecked( AmarokConfig::customRatingsColors() ); + //slotUpdateRatingsFrame(); + + QStringList browsers; + browsers << "konqueror" << "firefox" << "opera" << "galeon" << "epiphany" + << "safari" << "mozilla"; + + // Remove browsers which are not actually installed + for( QStringList::Iterator it = browsers.begin(), end = browsers.end(); it != end; ) { + if( KStandardDirs::findExe( *it ).isEmpty() ) + it = browsers.erase( it ); + else + ++it; + } +#ifdef Q_WS_MAC + if ( KStandardDirs::findExe( "open" ) != QString::null ) + browsers.prepend( i18n( "Default Browser" ) ); +#else + if ( KStandardDirs::findExe( "kfmclient" ) != QString::null ) + browsers.prepend( i18n( "Default KDE Browser" ) ); +#endif + + kComboBox_browser->insertStringList( browsers ); + kLineEdit_customBrowser->setText( AmarokConfig::externalBrowser() ); + int index = browsers.findIndex( AmarokConfig::externalBrowser() ); + if( index >= 0 ) + kComboBox_browser->setCurrentItem( index ); + else if( AmarokConfig::externalBrowser() == +#ifdef Q_WS_MAC + "open" +#else + "kfmclient openURL" +#endif + ) + { + kComboBox_browser->setCurrentItem( 0 ); + } + else + { + checkBox_customBrowser->setChecked( true ); + } +} + +void Options1::slotUpdateMoodFrame() +{ + if( Moodbar::executableExists() ) + { + moodbarHelpLabel->hide(); + moodFrame->setEnabled(true); + + kcfg_MakeMoodier->setEnabled(kcfg_ShowMoodbar->isChecked()); + kcfg_AlterMood->setEnabled(kcfg_ShowMoodbar->isChecked() && kcfg_MakeMoodier->isChecked()); + kcfg_MoodsWithMusic->setEnabled(kcfg_ShowMoodbar->isChecked()); + } + + else + { + moodbarHelpLabel->show(); + kcfg_ShowMoodbar->setChecked(false); + moodFrame->setEnabled(false); + } +} + +/* +void Options1::slotUpdateRatingsFrame() +{ + kcfg_CustomRatingsColors->setEnabled( kcfg_UseRatings->isChecked() ); + bool enableStars = kcfg_UseRatings->isChecked() && kcfg_CustomRatingsColors->isChecked(); + kcfg_FixedHalfStarColor->setEnabled( enableStars ); + + AmarokConfig::setCustomRatingsColors( enableStars ); + + StarManager::instance()->reinitStars(); + ratingsFrame->setEnabled( enableStars ); + + fivestar_1->setPixmap( *StarManager::instance()->getStar( 5 ) ); + fivestar_2->setPixmap( *StarManager::instance()->getStar( 5 ) ); + fivestar_3->setPixmap( *StarManager::instance()->getStar( 5 ) ); + fivestar_4->setPixmap( *StarManager::instance()->getStar( 5 ) ); + fivestar_5->setPixmap( *StarManager::instance()->getStar( 5 ) ); + fivestar_1->setEnabled( enableStars ); + fivestar_2->setEnabled( enableStars ); + fivestar_3->setEnabled( enableStars ); + fivestar_4->setEnabled( enableStars ); + fivestar_5->setEnabled( enableStars ); + + fourstar_1->setPixmap( *StarManager::instance()->getStar( 4 ) ); + fourstar_2->setPixmap( *StarManager::instance()->getStar( 4 ) ); + fourstar_3->setPixmap( *StarManager::instance()->getStar( 4 ) ); + fourstar_4->setPixmap( *StarManager::instance()->getStar( 4 ) ); + fourstar_1->setEnabled( enableStars ); + fourstar_2->setEnabled( enableStars ); + fourstar_3->setEnabled( enableStars ); + fourstar_4->setEnabled( enableStars ); + + threestar_1->setPixmap( *StarManager::instance()->getStar( 3 ) ); + threestar_2->setPixmap( *StarManager::instance()->getStar( 3 ) ); + threestar_3->setPixmap( *StarManager::instance()->getStar( 3 ) ); + threestar_1->setEnabled( enableStars ); + threestar_2->setEnabled( enableStars ); + threestar_3->setEnabled( enableStars ); + + twostar_1->setPixmap( *StarManager::instance()->getStar( 2 ) ); + twostar_2->setPixmap( *StarManager::instance()->getStar( 2 ) ); + twostar_1->setEnabled( enableStars ); + twostar_2->setEnabled( enableStars ); + + onestar_1->setPixmap( *StarManager::instance()->getStar( 1 ) ); + onestar_1->setEnabled( enableStars ); + + halfstar->setPixmap( *StarManager::instance()->getHalfStar() ); + halfstar->setEnabled( enableStars ); +} + +void Options1::slotFixedHalfStarColor() +{ + bool checked = kcfg_FixedHalfStarColor->isChecked(); + AmarokConfig::setFixedHalfStarColor( kcfg_FixedHalfStarColor->isChecked() ); + slotUpdateRatingsFrame(); +} + +void Options1::slotPickColorHalf() +{ + QColor halfStar; + int result = KColorDialog::getColor( halfStar ); + if( result == KColorDialog::Accepted ) + { + AmarokConfig::setStarColorHalf( halfStar ); + StarManager::instance()->setHalfColor( halfStar ); + slotUpdateRatingsFrame(); + } +} + +void Options1::slotPickColorOne() +{ + QColor oneStar; + int result = KColorDialog::getColor( oneStar ); + if( result == KColorDialog::Accepted ) + { + AmarokConfig::setStarColorOne( oneStar ); + StarManager::instance()->setColor( 1, oneStar ); + slotUpdateRatingsFrame(); + } +} + +void Options1::slotPickColorTwo() +{ + QColor twoStar; + int result = KColorDialog::getColor( twoStar ); + if( result == KColorDialog::Accepted ) + { + AmarokConfig::setStarColorTwo( twoStar ); + StarManager::instance()->setColor( 2, twoStar ); + slotUpdateRatingsFrame(); + } +} + +void Options1::slotPickColorThree() +{ + QColor threeStar; + int result = KColorDialog::getColor( threeStar ); + if( result == KColorDialog::Accepted ) + { + AmarokConfig::setStarColorThree( threeStar ); + StarManager::instance()->setColor( 3, threeStar ); + slotUpdateRatingsFrame(); + } +} + +void Options1::slotPickColorFour() +{ + QColor fourStar; + int result = KColorDialog::getColor( fourStar ); + if( result == KColorDialog::Accepted ) + { + AmarokConfig::setStarColorFour( fourStar ); + StarManager::instance()->setColor( 4, fourStar ); + slotUpdateRatingsFrame(); + } +} + +void Options1::slotPickColorFive() +{ + QColor fiveStar; + int result = KColorDialog::getColor( fiveStar ); + if( result == KColorDialog::Accepted ) + { + AmarokConfig::setStarColorFive( fiveStar ); + StarManager::instance()->setColor( 5, fiveStar ); + slotUpdateRatingsFrame(); + } +} +*/ diff --git a/amarok/src/Options2.ui b/amarok/src/Options2.ui new file mode 100644 index 00000000..71f2a31e --- /dev/null +++ b/amarok/src/Options2.ui @@ -0,0 +1,768 @@ + +Options2 + + + Options2 + + + + 0 + 0 + 447 + 564 + + + + + 3 + 3 + 0 + 0 + + + + + unnamed + + + 0 + + + + groupBox6 + + + Icons + + + + unnamed + + + + kcfg_UseCustomIconTheme + + + Use custom icon &theme (requires restart) + + + Check to enable Amarok's custom icon theme.<br> + + + Check to enable Amarok's custom icon theme. + + + + + + + groupBox4 + + + Fonts + + + + unnamed + + + + kcfg_UseCustomFonts + + + &Use custom fonts + + + false + + + Check to enable custom fonts. + + + Check to enable custom fonts. + + + + + fontGroupBox + + + false + + + + 7 + 5 + 0 + 0 + + + + NoFrame + + + 0 + + + + + + true + + + + unnamed + + + + textLabel3 + + + + 1 + 5 + 0 + 0 + + + + Playlist Window: + + + The font to use in the playlist window. + + + The font to use in the playlist window. + + + + + kcfg_PlaylistWindowFont + + + + 5 + 5 + 0 + 0 + + + + + + + The font to use in the playlist window. + + + The font to use in the playlist window. + + + + + textLabel1 + + + + 1 + 5 + 0 + 0 + + + + Player Window: + + + AlignVCenter + + + The font to use in the player window. + + + The font to use in the player window. + + + + + textLabel3_2 + + + + 1 + 5 + 0 + 0 + + + + Context Sidebar: + + + The font to use in the context browser. + + + The font to use in the context browser. + + + + + kcfg_ContextBrowserFont + + + + 5 + 5 + 0 + 0 + + + + + + + The font to use in the context browser. + + + The font to use in the context browser. + + + + + kcfg_PlayerWidgetFont + + + + 5 + 5 + 0 + 0 + + + + + + + The font to use in the context browser. + + + The font to use in the context browser. + + + + + + + + + groupBox3 + + + Color Scheme + + + + unnamed + + + + radioGroup + + + NoFrame + + + + + + + unnamed + + + 0 + + + + layout2 + + + + unnamed + + + 0 + + + + kcfg_SchemeCustom + + + &Custom color scheme + + + true + + + If selected, Amarok uses the user-defined colors in the playlist. + + + If selected, Amarok uses the user-defined colors in the playlist. + + + + + customSchemeBox + + + true + + + NoFocus + + + NoFrame + + + + + + false + + + false + + + + unnamed + + + + fgLabel + + + true + + + + 4 + 5 + 0 + 0 + + + + + 0 + 0 + + + + Fo&reground: + + + AutoText + + + kcfg_PlaylistWindowFgColor + + + Selects the color to use as foreground color in the playlist. + + + + + kcfg_PlaylistWindowFgColor + + + true + + + + 5 + 0 + 2 + 0 + + + + + + + + 255 + 255 + 255 + + + + Click to select the foreground text color in the playlist window. + + + Selects the color to use as foreground (text) color in the playlist. + + + + + bgColor + + + true + + + + 4 + 5 + 0 + 0 + + + + &Background: + + + AutoText + + + kcfg_PlaylistWindowBgColor + + + Selects the color to use as background color in the playlist. + + + + + kcfg_PlaylistWindowBgColor + + + true + + + + 5 + 0 + 2 + 0 + + + + + 0 + 0 + + + + + 192 + 32767 + + + + + + + + 32 + 32 + 80 + + + + Click to select the background color in the playlist window. + + + Selects the color to use as background color in the playlist. + + + + + + + + + kcfg_SchemeKDE + + + The current &KDE color-scheme + + + 0 + + + If selected, Amarok uses the KDE standard colors in the playlist. + + + If selected, Amarok uses the KDE standard colors in the playlist. + + + + + kcfg_SchemeAmarok + + + The classic &Amarok, "funky-monkey", theme + + + 1 + + + If selected, Amarok uses the Amarok standard colors in the playlist. + + + If selected, Amarok uses the Amarok standard colors in the playlist. + + + + + + + line1 + + + HLine + + + Sunken + + + Horizontal + + + + + layout5 + + + + unnamed + + + + textLabel1_3 + + + Color for new playlist items: + + + kcfg_NewPlaylistItemsColor + + + The color that is used when fresh items are being loaded in the playlist. + + + + + kcfg_NewPlaylistItemsColor + + + + + + The color that is used when fresh items are being loaded in the playlist. + + + + + + + + + contextGroupBox + + + Context Browser Style + + + + unnamed + + + + layout6 + + + + unnamed + + + + textLabel1_2 + + + + 5 + 5 + 0 + 0 + + + + Select a style: + + + + + + + + styleComboBox + + + Select the style of the Context Browser. + + + Select the style of the Context Browser. + + + + + + + layout5 + + + + unnamed + + + + installPushButton + + + Install New Style... + + + Click to install a new Context Browser style.<br>Tip: More styles can be found on <a href='http://kde-look.org'>http://kde-look.org</a> + + + Select and install a new Context Browser style. + + + + + retrievePushButton + + + Download Styles... + + + Click to download new Context Browser styles. + + + Select and download new Context Browser styles. + + + + + spacer2 + + + Horizontal + + + Expanding + + + + 40 + 20 + + + + + + uninstallPushButton + + + Uninstall Style + + + Click to uninstall the selected Context Browser style. + + + Uninstall the selected Context Browser style. + + + + + + + + + spacer1 + + + Vertical + + + Expanding + + + + 20 + 30 + + + + + + + + kcfg_UseCustomFonts + toggled(bool) + fontGroupBox + setEnabled(bool) + + + kcfg_SchemeCustom + toggled(bool) + customSchemeBox + setEnabled(bool) + + + installPushButton + clicked() + Options2 + installPushButton_clicked() + + + retrievePushButton + clicked() + Options2 + retrievePushButton_clicked() + + + uninstallPushButton + clicked() + Options2 + uninstallPushButton_clicked() + + + styleComboBox + activated(const QString&) + Options2 + styleComboBox_activated(const QString&) + + + + Options2.ui.h + + + installPushButton_clicked() + retrievePushButton_clicked() + uninstallPushButton_clicked() + styleComboBox_activated( const QString & s ) + updateStyleComboBox() + + + init() + + + + kfontrequester.h + kfontrequester.h + kfontrequester.h + kcolorbutton.h + kcolorbutton.h + kcolorbutton.h + kcombobox.h + + diff --git a/amarok/src/Options2.ui.h b/amarok/src/Options2.ui.h new file mode 100644 index 00000000..e3f0a54f --- /dev/null +++ b/amarok/src/Options2.ui.h @@ -0,0 +1,189 @@ +/**************************************************************************** +** ui.h extension file, included from the uic-generated form implementation. +** +** If you want to add, delete, or rename functions or slots, use +** Qt Designer to update this file, preserving your code. +** +** You should not define a constructor or destructor in this file. +** Instead, write your code in functions called init() and destroy(). +** These will automatically be called by the form's constructor and +** destructor. +*****************************************************************************/ + +#include "amarok.h" +#include "amarokconfig.h" +#include "debug.h" +#include "contextbrowser.h" + +#include +#include +#include +#include // knewstuff theme fetching +#include // " +#include // " +#include // " +#include +#include +#include + +#include +#include +#include + + +//////////////////////////////////////////////////////////////////////////////// +// class AmarokThemeNewStuff +//////////////////////////////////////////////////////////////////////////////// + +/** + * GHNS Customised Download implementation. + */ +class AmarokThemeNewStuff : public KNewStuff +{ + public: + AmarokThemeNewStuff(const QString &type, QWidget *parentWidget=0) + : KNewStuff( type, parentWidget ) + {} + + bool install( const QString& fileName ) + { + KTar archive( fileName ); + + if ( !archive.open( IO_ReadOnly ) ) { + KMessageBox::sorry( 0, i18n( "Could not read this package." ) ); + return false; + } + + const QString destination = Amarok::saveLocation( "themes/" ); + debug() << "copying to " << destination << endl; + const KArchiveDirectory* archiveDir = archive.directory(); + archiveDir->copyTo( destination, true ); + + return true; + } + + virtual bool createUploadFile( const QString& ) { return false; } +}; + + +//////////////////////////////////////////////////////////////////////////////// +// class Options2 +//////////////////////////////////////////////////////////////////////////////// + +void Options2::init() +{ + updateStyleComboBox(); + uninstallPushButton->setEnabled ( styleComboBox->currentText() != "Default" ); +} + + +// This method is basically lifted from ScriptManager::slotInstallScript() +void Options2::installPushButton_clicked() +{ + KFileDialog dia( QString::null, "*.tar *.tar.bz2 *.tar.gz|" + i18n( "Style Packages (*.tar, *.tar.bz2, *.tar.gz)" ), 0, 0, true ); + kapp->setTopWidget( &dia ); + dia.setCaption( kapp->makeStdCaption( i18n( "Select Style Package" ) ) ); + dia.setMode( KFile::File | KFile::ExistingOnly ); + if ( !dia.exec() ) return; + + KTar archive( dia.selectedURL().path() ); + + if ( !archive.open( IO_ReadOnly ) ) { + KMessageBox::sorry( 0, i18n( "Could not read this package." ) ); + return; + } + + const QString destination = Amarok::saveLocation( "themes/" ); + debug() << "copying to " << destination << endl; + const KArchiveDirectory* archiveDir = archive.directory(); + archiveDir->copyTo( destination, true ); + + updateStyleComboBox(); +} + + + +void Options2::retrievePushButton_clicked() +{ + // Delete KNewStuff's configuration entries. These entries reflect which styles + // are already installed. As we cannot yet keep them in sync after uninstalling + // styles, we deactivate the check marks entirely. + Amarok::config()->deleteGroup( "KNewStuffStatus" ); + + // we need this because KNewStuffGeneric's install function isn't clever enough + AmarokThemeNewStuff *kns = new AmarokThemeNewStuff( "amarok/themes", this ); + KNS::Engine *engine = new KNS::Engine( kns, "amarok/theme", this ); + KNS::DownloadDialog* d = new KNS::DownloadDialog( engine, this ); + d->setType( "amarok/theme" ); + // you have to do this by hand when providing your own Engine + KNS::ProviderLoader *p = new KNS::ProviderLoader( this ); + connect( p, SIGNAL( providersLoaded(Provider::List*) ), d, SLOT( slotProviders(Provider::List *) ) ); + p->load( "amarok/theme", "http://amarok.kde.org/knewstuff/amarokthemes-providers.xml" ); + + connect( d, SIGNAL( finished() ), d, SLOT( delayedDestruct() ) ); + connect( d, SIGNAL( finished() ), this, SLOT( updateStyleComboBox() ) ); + + // Due to kdelibs idiocy, KNS::DownloadDialog is /always/ non-modal. So we have to + // ensure that closing the settings dialog before the DownloadDialog doesn't crash. + QTimer::singleShot( 0, d, SLOT( exec() ) ); +} + + +void Options2::uninstallPushButton_clicked() +{ + const QString name = styleComboBox->currentText(); + + if ( name == "Default" ) + return; + + if( KMessageBox::warningContinueCancel( 0, + i18n( "

Are you sure you want to uninstall the theme %1?

" ).arg( name ), + i18n("Uninstall Theme"), i18n("Uninstall") ) == KMessageBox::Cancel ) + return; + + if ( name == AmarokConfig::contextBrowserStyleSheet() ) { + AmarokConfig::setContextBrowserStyleSheet( "Default" ); + ContextBrowser::instance()->reloadStyleSheet(); + } + + KURL themeDir( KURL::fromPathOrURL( Amarok::saveLocation( "themes/" ) ) ); + themeDir.addPath( name ); + + if( !KIO::NetAccess::del( themeDir, 0 ) ) { + KMessageBox::sorry( 0, i18n( "

Could not uninstall this theme.

" + "

You may not have sufficient permissions to delete the folder %1

." + ).arg( themeDir.isLocalFile() ? themeDir.path() : themeDir.url() ) ); + return; + } + + updateStyleComboBox(); +} + + +void Options2::styleComboBox_activated(const QString& s) +{ + bool disable = false; + QDir dir( Amarok::saveLocation( "themes/" ) + s ); + if( !dir.exists() ) + disable = true; + + uninstallPushButton->setEnabled ( !disable ); +} + + +void Options2::updateStyleComboBox() +{ + DEBUG_BLOCK + + styleComboBox->clear(); + + const QStringList styleList = kapp->dirs()->findAllResources("data","amarok/themes/*/stylesheet.css", false); + QStringList sortedList; + foreach (styleList) sortedList.append(QFileInfo( *it ).dir().dirName()); + sortedList.append( "Default" ); + sortedList.sort(); + foreach(sortedList) styleComboBox->insertItem(*it); + + styleComboBox->setCurrentItem(AmarokConfig::contextBrowserStyleSheet()); +} + diff --git a/amarok/src/Options4.ui b/amarok/src/Options4.ui new file mode 100644 index 00000000..f55eab85 --- /dev/null +++ b/amarok/src/Options4.ui @@ -0,0 +1,622 @@ + +Options4 + + + Options4 + + + + 0 + 0 + 455 + 447 + + + + + 3 + 3 + 0 + 0 + + + + + unnamed + + + + opt_crossfade + + + + 5 + 5 + 0 + 0 + + + + + 0 + 180 + + + + GroupBoxPanel + + + Sunken + + + &Transition + + + <b>Transition Behavior</b> +<p>During playback, when Amarok transitions between tracks, it can either proceed to the next track instantly (with configurable gap), or crossfade (with configurable fade period).</p> + + + + unnamed + + + + radioButtonNormalPlayback + + + &No crossfading + + + true + + + Enable normal track transition. You may insert a gap of silence between tracks. + + + + + layout11 + + + + unnamed + + + + trackDelayLengthLabel + + + + 4 + 1 + 0 + 0 + + + + + 150 + 0 + + + + Insert &gap: + + + kcfg_TrackDelayLength + + + + + kcfg_TrackDelayLength + + + + 3 + 0 + 0 + 0 + + + + + 160 + 0 + + + + ms + + + 10000 + + + 100 + + + 0 + + + Silence between tracks, in milliseconds. + + + + + + + kcfg_Crossfade + + + &Crossfading + + + false + + + Enable crossfading between tracks. + + + + + layout9 + + + + unnamed + + + + layout7 + + + + unnamed + + + + crossfadeLengthLabel + + + false + + + + 4 + 1 + 0 + 0 + + + + + 150 + 0 + + + + Crosso&ver duration: + + + kcfg_CrossfadeLength + + + + + crossfadeDropdownText + + + false + + + + 4 + 1 + 0 + 0 + + + + + 150 + 0 + + + + Crossfa&de: + + + kcfg_CrossfadeType + + + + + + + layout8 + + + + unnamed + + + + kcfg_CrossfadeLength + + + false + + + + 5 + 4 + 0 + 0 + + + + + 50 + 0 + + + + ms + + + 99999999 + + + 100 + + + 100 + + + 100 + + + The length of the crossfade between tracks, in milliseconds. + + + + + + Always + + + + + On Automatic Track Change Only + + + + + On Manual Track Change Only + + + + kcfg_CrossfadeType + + + false + + + + 5 + 4 + 0 + 0 + + + + + 50 + 0 + + + + false + + + Select when you want crossfading to occur + + + Select when you want crossfading to occur + + + + + + + + + spacer76 + + + Horizontal + + + Fixed + + + + 16 + 20 + + + + + + + + kcfg_FadeoutOnExit + + + Fade out on e&xit + + + If checked, Amarok will fade out the music on program exit. + + + + + kcfg_ResumePlayback + + + &Resume playback on start + + + If checked, Amarok will<br>resume playback from where you left it the previous session -- just like a tape-player. + + + + + spacer2 + + + Vertical + + + Expanding + + + + 31 + 30 + + + + + + opt_crossfade_2 + + + + 5 + 5 + 0 + 0 + + + + + 0 + 120 + + + + GroupBoxPanel + + + Sunken + + + &Fadeout + + + <b>Transition Behavior</b> +<p>During playback, when Amarok transitions between tracks, it can either proceed to the next track instantly (with configurable gap), or crossfade (with configurable fade period).</p> + + + + unnamed + + + + radioButtonNormalPlayback_2 + + + No &fadeout + + + true + + + Disable fadeout. Music will stop immediately. + + + + + spacer76_2 + + + Horizontal + + + Fixed + + + + 16 + 20 + + + + + + layout9_2 + + + + unnamed + + + + layout7_2 + + + + unnamed + + + + fadeoutLengthLabel + + + false + + + + 4 + 1 + 0 + 0 + + + + + 150 + 0 + + + + Fadeout &duration: + + + kcfg_FadeoutLength + + + + + + + layout8_2 + + + + unnamed + + + + kcfg_FadeoutLength + + + false + + + + 5 + 4 + 0 + 0 + + + + + 50 + 0 + + + + ms + + + 99999999 + + + 100 + + + 100 + + + 100 + + + The length of the fadeout, in milliseconds. + + + + + + + + + kcfg_Fadeout + + + Fade&out + + + false + + + Fade the music out when the Stop button is pressed. + + + + + + + + + radioButtonNormalPlayback + toggled(bool) + kcfg_TrackDelayLength + setEnabled(bool) + + + kcfg_Crossfade + toggled(bool) + kcfg_CrossfadeLength + setEnabled(bool) + + + radioButtonNormalPlayback + toggled(bool) + trackDelayLengthLabel + setEnabled(bool) + + + kcfg_Crossfade + toggled(bool) + crossfadeLengthLabel + setEnabled(bool) + + + kcfg_Crossfade + toggled(bool) + crossfadeDropdownText + setEnabled(bool) + + + kcfg_Crossfade + toggled(bool) + kcfg_CrossfadeType + setEnabled(bool) + + + kcfg_Fadeout + toggled(bool) + fadeoutLengthLabel + setEnabled(bool) + + + kcfg_Fadeout + toggled(bool) + kcfg_FadeoutLength + setEnabled(bool) + + + + diff --git a/amarok/src/Options5.ui b/amarok/src/Options5.ui new file mode 100644 index 00000000..50c54cc8 --- /dev/null +++ b/amarok/src/Options5.ui @@ -0,0 +1,530 @@ + +Options5 + + + Options5 + + + + 0 + 0 + 481 + 472 + + + + + unnamed + + + 0 + + + + kcfg_OsdEnabled + + + &Use On-Screen-Display + + + true + + + Check to enable the On-Screen-Display. <br>The OSD briefly displays track data when a new track is played. + + + Check to enable the On-Screen-Display. The OSD briefly displays track data when a new track is played. + + + + + layout2 + + + + unnamed + + + + spacer2 + + + Horizontal + + + Fixed + + + + 16 + 20 + + + + + + mainBox + + + NoFrame + + + + + + + unnamed + + + 0 + + + + fontBox + + + GroupBoxPanel + + + Sunken + + + 1 + + + &Font + + + false + + + + unnamed + + + + kcfg_OsdFont + + + + + + + + + + The font to use for the On-Screen Display. + + + The font to use for the On-Screen Display. + + + + + kcfg_OsdDrawShadow + + + Draw &shadow + + + + + + + groupBox9 + + + C&olors + + + + unnamed + + + + layout3 + + + + unnamed + + + 2 + + + + kcfg_OsdUseCustomColors + + + Use &custom colors + + + Check to enable custom colors for the On-Screen-Display. + + + Check to enable custom colors for the On-Screen-Display. + + + + + layout1 + + + + unnamed + + + + spacer1 + + + Horizontal + + + Fixed + + + + 16 + 20 + + + + + + colorsBox + + + false + + + NoFrame + + + + + + false + + + + unnamed + + + 0 + + + + textLabel2_2 + + + + 5 + 5 + 1 + 0 + + + + NoFrame + + + Background color: + + + The color of the OSD background. + + + + + kcfg_OsdTextColor + + + + 5 + 0 + 2 + 0 + + + + + + + + 255 + 0 + 0 + + + + Click to select the color of the OSD text. + + + The color of the OSD text. + + + + + kcfg_OsdBackgroundColor + + + + 5 + 0 + 2 + 0 + + + + + + + + 255 + 0 + 0 + + + + Click to select the background color of the OSD. + + + The color of the OSD background. + + + + + textLabel2 + + + + 5 + 5 + 1 + 0 + + + + Text color: + + + The color of the OSD text. + + + + + + + + + + + kcfg_OsdUseFakeTranslucency + + + Make the &background translucent + + + + + + + osdText + + + GroupBoxPanel + + + Display &Text + + + false + + + + unnamed + + + + kcfg_OsdUsePlaylistColumns + + + Display the same information as the columns in the playlist + + + + + kcfg_OsdText + + + + 7 + 7 + 0 + 0 + + + + 2 + + + PlainText + + + true + + + + + + + groupBox22 + + + + + + + unnamed + + + + textLabel1 + + + &Duration: + + + kcfg_OsdDuration + + + + + kcfg_OsdDuration + + + + 1 + 0 + 2 + 0 + + + + ms + + + Forever + + + UpDownArrows + + + 600000 + + + 0 + + + 1000 + + + 5000 + + + The time in milliseconds to show the OSD. The value must be between 500 ms and 10000 ms. + + + The time in milliseconds to show the OSD. The value must be between 500 ms and 10000 ms. + + + + + kcfg_OsdScreen + + + + 1 + 0 + 2 + 0 + + + + The screen that should display the OSD. + + + The screen that should display the OSD. + + + + + textLabel2_3 + + + Sc&reen: + + + kcfg_OsdScreen + + + + + + + + + + + + + kcfg_OsdUseCustomColors + toggled(bool) + colorsBox + setEnabled(bool) + + + kcfg_OsdEnabled + toggled(bool) + mainBox + setEnabled(bool) + + + kcfg_OsdUseCustomColors + toggled(bool) + Options5 + useCustomColorsToggled(bool) + + + kcfg_OsdUsePlaylistColumns + toggled(bool) + kcfg_OsdText + setDisabled(bool) + + + + qapplication.h + qdesktopwidget.h + osd.h + + + OSDPreviewWidget *m_pOSDPreview; + + + settingsChanged() + + + slotPositionChanged() + useCustomColorsToggled( bool on ) + + + init() + hideEvent( QHideEvent * ) + showEvent( QShowEvent * ) + + + + ktextedit.h + + diff --git a/amarok/src/Options5.ui.h b/amarok/src/Options5.ui.h new file mode 100644 index 00000000..00ad3023 --- /dev/null +++ b/amarok/src/Options5.ui.h @@ -0,0 +1,126 @@ +/*************************************************************************** +begin : 2004/02/25 +copyright : (C) Frederik Holljen +email : fh@ez.no +***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + + +/**************************************************************************** +** ui.h extension file, included from the uic-generated form implementation. +** +** If you wish to add, delete or rename functions or slots use +** Qt Designer which will update this file, preserving your code. Create an +** init() function in place of a constructor, and a destroy() function in +** place of a destructor. +*****************************************************************************/ + +#include "amarokconfig.h" +#include +#include "qstringx.h" +#include + + +void Options5::init() +{ + m_pOSDPreview = new OSDPreviewWidget( this ); //must be child!!! + m_pOSDPreview->setAlignment( static_cast( AmarokConfig::osdAlignment() ) ); + m_pOSDPreview->setOffset( AmarokConfig::osdYOffset() ); + + connect( m_pOSDPreview, SIGNAL( positionChanged() ), SLOT( slotPositionChanged() ) ); + + const int numScreens = QApplication::desktop()->numScreens(); + for( int i = 0; i < numScreens; i++ ) + kcfg_OsdScreen->insertItem( QString::number( i ) ); + + connect( kcfg_OsdDrawShadow, SIGNAL( toggled(bool) ), + m_pOSDPreview, SLOT( setDrawShadow(bool) ) ); + connect( kcfg_OsdTextColor, SIGNAL( changed(const QColor&) ), + m_pOSDPreview, SLOT( setTextColor(const QColor&) ) ); + connect( kcfg_OsdUseCustomColors, SIGNAL( toggled(bool) ), + this, SLOT( useCustomColorsToggled(bool) ) ); + connect( kcfg_OsdBackgroundColor, SIGNAL( changed(const QColor&) ), + m_pOSDPreview, SLOT( setBackgroundColor(const QColor&) ) ); + connect( kcfg_OsdFont, SIGNAL( fontSelected(const QFont&) ), + m_pOSDPreview, SLOT( setFont(const QFont&) ) ); + connect( kcfg_OsdScreen, SIGNAL( activated(int) ), + m_pOSDPreview, SLOT( setScreen(int) ) ); + connect( kcfg_OsdEnabled, SIGNAL( toggled(bool) ), + m_pOSDPreview, SLOT( setShown(bool) ) ); + + Amarok::QStringx text = i18n( + "

Tags Displayed in OSD

" + "You can use the following tokens:" + "
    " + "
  • Title - %1" + "
  • Album - %2" + "
  • Artist - %3" + "
  • Genre - %4" + "
  • Bitrate - %5" + "
  • Year - %6" + "
  • Track Length - %7" + "
  • Track Number - %8" + "
  • Filename - %9" + "
  • Directory - %10" + "
  • Type - %11" + "
  • Comment - %12" + "
  • Score - %13" + "
  • Playcount - %14" + "
  • Disc Number - %15" + "
  • Rating - %16" + "
  • Moodbar - %17" + "
  • Elapsed Time - %18" + "
" + "If you surround sections of text that contain a token with curly-braces, that section will be hidden if the token is empty, for example:" + "
%19
" + "Will not show Score: %score if the track has no score." ); + + QToolTip::add( kcfg_OsdText, text.args( QStringList() + // we don't translate these, it is not sensible to do so + << "%title" << "%album" << "%artist" << "%genre" << "%bitrate" + << "%year " << "%length" << "%track" << "%filename" << "%directory" + << "%type" << "%comment" << "%score" << "%playcount" << "%discnumber" + << "%rating" << "%moodbar" << "%elapsed" + << "%title {" + i18n( "Score: %1" ).arg( "%score" ) +'}' ) ); +} + +void +Options5::slotPositionChanged() +{ + kcfg_OsdScreen->blockSignals( true ); + kcfg_OsdScreen->setCurrentItem( m_pOSDPreview->screen() ); + kcfg_OsdScreen->blockSignals( false ); + + // Update button states (e.g. "Apply") + emit settingsChanged(); +} + +void +Options5::hideEvent( QHideEvent* ) +{ + m_pOSDPreview->hide(); +} + +void +Options5::showEvent( QShowEvent* ) +{ + useCustomColorsToggled( kcfg_OsdUseCustomColors->isChecked() ); + + m_pOSDPreview->setFont( kcfg_OsdFont->font() ); + m_pOSDPreview->setScreen( kcfg_OsdScreen->currentItem() ); + m_pOSDPreview->setShown( kcfg_OsdEnabled->isChecked() ); +} + +void +Options5::useCustomColorsToggled( bool on ) +{ + m_pOSDPreview->setUseCustomColors( on, kcfg_OsdTextColor->color(), kcfg_OsdBackgroundColor->color() ); +} diff --git a/amarok/src/Options7.ui b/amarok/src/Options7.ui new file mode 100644 index 00000000..56cf6c3c --- /dev/null +++ b/amarok/src/Options7.ui @@ -0,0 +1,81 @@ + +Options7 + + + Options7 + + + + 0 + 0 + 404 + 215 + + + + Collection Setup + + + + unnamed + + + 0 + + + + collectionFoldersBox + + + Collection Folders + + + + + databaseBox + + + Collection Database + + + + unnamed + + + + dbSetupFrame + + + + + + + + + DbSetup +
dbsetup.h
+ + 380 + 165 + + 1 + + 3 + 3 + 0 + 0 + + image0 +
+
+ + + 789c534e494dcbcc4b554829cdcdad8c2fcf4c29c95030e0524611cd48cd4ccf28010a1797249664262b2467241641a592324b8aa363156c15aab914146aadb90067111b1f + + + + + klineedit.h + kpushbutton.h + +
diff --git a/amarok/src/Options8.ui b/amarok/src/Options8.ui new file mode 100644 index 00000000..6ad0d1de --- /dev/null +++ b/amarok/src/Options8.ui @@ -0,0 +1,305 @@ + +Options8 + + + Options8 + + + + 0 + 0 + 425 + 418 + + + + + 1 + 1 + + + + + unnamed + + + 0 + + + 12 + + + + layout2 + + + + unnamed + + + 12 + + + + infoPixmap_2 + + + + 4 + 1 + 0 + 0 + + + + + + + AlignVCenter + + + + + kActiveLabel3 + + + + 5 + 4 + 0 + 0 + + + + + -1 + -1 + + + + Amarok can send the name of every song you play to last.fm. The system automatically matches you to people with similar musical tastes, and creates personalized recommendations. To learn more about last.fm, <A href='http://www.last.fm'>visit the homepage</A>. + + + + + + + groupBox3 + + + last.fm Profile + + + + unnamed + + + + kActiveLabel1 + + + + 5 + 5 + 0 + 0 + + + + <P>To use last.fm with Amarok, you need a <A href='http://www.last.fm:80/signup.php'>last.fm profile</A>. + + + + + layout3 + + + + unnamed + + + + kcfg_ScrobblerUsername + + + + 7 + 0 + 0 + 0 + + + + + + labelPassword + + + + 5 + 5 + 0 + 0 + + + + &Password: + + + kcfg_ScrobblerPassword + + + + + labelUsername + + + + 5 + 5 + 0 + 0 + + + + &Username: + + + kcfg_ScrobblerUsername + + + + + kcfg_ScrobblerPassword + + + + 7 + 0 + 0 + 0 + + + + Password + + + + + + + + + groupBox2 + + + false + + + last.fm Services + + + + unnamed + + + + textLabel1 + + + Once registered, Amarok can tell the last.fm service about your listening habits; your profile can then provide statistics and recommendations. A profile is not required to retrieve similar-artists for display in the Context Browser. + + + RichText + + + + + kcfg_SubmitPlayedSongs + + + false + + + + 7 + 5 + 0 + 0 + + + + Improve m&y profile by submitting the tracks I play + + + + + kcfg_RetrieveSimilarArtists + + + false + + + &Retrieve similar artists + + + + + + + kActiveLabel2 + + + + 5 + 5 + 0 + 0 + + + + Why not join the <A href='http://www.last.fm:80/group/Amarok+Users'>Amarok last.fm group</A> and share your musical tastes with other Amarok users? + + + + + spacer4 + + + Vertical + + + Expanding + + + + 20 + 30 + + + + + + + + kcfg_ScrobblerUsername + textChanged(const QString&) + Options8 + updateServices(const QString&) + + + + Options8.ui.h + + + updateServices( const QString & s ) + + + + kactivelabel.h + kactivelabel.h + klineedit.h + klineedit.h + kactivelabel.h + + diff --git a/amarok/src/Options8.ui.h b/amarok/src/Options8.ui.h new file mode 100644 index 00000000..d6341f6f --- /dev/null +++ b/amarok/src/Options8.ui.h @@ -0,0 +1,22 @@ +// (c) 2006 Seb Ruiz + +/**************************************************************************** +** ui.h extension file, included from the uic-generated form implementation. +** +** If you want to add, delete, or rename functions or slots, use +** Qt Designer to update this file, preserving your code. +** +** You should not define a constructor or destructor in this file. +** Instead, write your code in functions called init() and destroy(). +** These will automatically be called by the form's constructor and +** destructor. +*****************************************************************************/ + +void Options8::updateServices( const QString &s ) +{ + bool empty = s.isEmpty(); + groupBox2->setEnabled( !empty ); + kcfg_SubmitPlayedSongs->setEnabled( !empty ); + kcfg_RetrieveSimilarArtists->setEnabled( !empty ); +} + diff --git a/amarok/src/actionclasses.cpp b/amarok/src/actionclasses.cpp new file mode 100644 index 00000000..d2330e61 --- /dev/null +++ b/amarok/src/actionclasses.cpp @@ -0,0 +1,665 @@ +// Maintainer: Max Howell , (C) 2004 +// Copyright: See COPYING file that comes with this distribution + +#include "config.h" //HAVE_LIBVISUAL definition + +#include "actionclasses.h" +#include "amarok.h" +#include "amarokconfig.h" +#include "app.h" +#include "debug.h" +#include "collectiondb.h" +#include "covermanager.h" +#include "enginecontroller.h" +#include "k3bexporter.h" +#include "mediumpluginmanager.h" +#include "playlistwindow.h" +#include "playlist.h" +#include "socketserver.h" //Vis::Selector::showInstance() +#include "threadmanager.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Amarok +{ + bool repeatNone() { return AmarokConfig::repeat() == AmarokConfig::EnumRepeat::Off; } + bool repeatTrack() { return AmarokConfig::repeat() == AmarokConfig::EnumRepeat::Track; } + bool repeatAlbum() { return AmarokConfig::repeat() == AmarokConfig::EnumRepeat::Album; } + bool repeatPlaylist() { return AmarokConfig::repeat() == AmarokConfig::EnumRepeat::Playlist; } + bool randomOff() { return AmarokConfig::randomMode() == AmarokConfig::EnumRandomMode::Off; } + bool randomTracks() { return AmarokConfig::randomMode() == AmarokConfig::EnumRandomMode::Tracks; } + bool randomAlbums() { return AmarokConfig::randomMode() == AmarokConfig::EnumRandomMode::Albums; } + bool favorNone() { return AmarokConfig::favorTracks() == AmarokConfig::EnumFavorTracks::Off; } + bool favorScores() { return AmarokConfig::favorTracks() == AmarokConfig::EnumFavorTracks::HigherScores; } + bool favorRatings() { return AmarokConfig::favorTracks() == AmarokConfig::EnumFavorTracks::HigherRatings; } + bool favorLastPlay() { return AmarokConfig::favorTracks() == AmarokConfig::EnumFavorTracks::LessRecentlyPlayed; } + bool entireAlbums() { return repeatAlbum() || randomAlbums(); } +} + +using namespace Amarok; + +KHelpMenu *Menu::s_helpMenu = 0; + +static void +safePlug( KActionCollection *ac, const char *name, QWidget *w ) +{ + if( ac ) + { + KAction *a = ac->action( name ); + if( a ) a->plug( w ); + } +} + + +////////////////////////////////////////////////////////////////////////////////////////// +// MenuAction && Menu +// KActionMenu doesn't work very well, so we derived our own +////////////////////////////////////////////////////////////////////////////////////////// + +MenuAction::MenuAction( KActionCollection *ac ) + : KAction( i18n( "Amarok Menu" ), 0, ac, "amarok_menu" ) +{ + setShortcutConfigurable ( false ); //FIXME disabled as it doesn't work, should use QCursor::pos() +} + +int +MenuAction::plug( QWidget *w, int index ) +{ + KToolBar *bar = dynamic_cast(w); + + if( bar && kapp->authorizeKAction( name() ) ) + { + const int id = KAction::getToolButtonID(); + + addContainer( bar, id ); + connect( bar, SIGNAL( destroyed() ), SLOT( slotDestroyed() ) ); + + //TODO create menu on demand + //TODO create menu above and aligned within window + //TODO make the arrow point upwards! + bar->insertButton( QString::null, id, true, i18n( "Menu" ), index ); + bar->alignItemRight( id ); + + KToolBarButton* button = bar->getButton( id ); + button->setPopup( Amarok::Menu::instance() ); + button->setName( "toolbutton_amarok_menu" ); + button->setIcon( "amarok" ); + + return containerCount() - 1; + } + else return -1; +} + +Menu::Menu() +{ + KActionCollection *ac = Amarok::actionCollection(); + + setCheckable( true ); + + safePlug( ac, "repeat", this ); + safePlug( ac, "random_mode", this ); + + insertSeparator(); + + safePlug( ac, "playlist_playmedia", this ); + safePlug( ac, "play_audiocd", this ); + safePlug( ac, "lastfm_play", this ); + + insertSeparator(); + + insertItem( SmallIconSet( Amarok::icon( "covermanager" ) ), i18n( "C&over Manager" ), ID_SHOW_COVER_MANAGER ); + safePlug( ac, "queue_manager", this ); + insertItem( SmallIconSet( Amarok::icon( "visualizations" ) ), i18n( "&Visualizations" ), ID_SHOW_VIS_SELECTOR ); + insertItem( SmallIconSet( Amarok::icon( "equalizer" ) ), i18n( "E&qualizer" ), kapp, SLOT( slotConfigEqualizer() ), 0, ID_CONFIGURE_EQUALIZER ); + safePlug( ac, "script_manager", this ); + safePlug( ac, "statistics", this ); + + insertSeparator(); + + + safePlug( ac, "update_collection", this ); + insertItem( SmallIconSet( Amarok::icon( "rescan" ) ), i18n("&Rescan Collection"), ID_RESCAN_COLLECTION ); + setItemEnabled( ID_RESCAN_COLLECTION, !ThreadManager::instance()->isJobPending( "CollectionScanner" ) ); + +#ifndef Q_WS_MAC + insertSeparator(); + + safePlug( ac, KStdAction::name(KStdAction::ShowMenubar), this ); +#endif + + insertSeparator(); + + safePlug( ac, KStdAction::name(KStdAction::ConfigureToolbars), this ); + safePlug( ac, KStdAction::name(KStdAction::KeyBindings), this ); + safePlug( ac, "options_configure_globals", this ); //we created this one + safePlug( ac, KStdAction::name(KStdAction::Preferences), this ); + + insertSeparator(); + + insertItem( SmallIconSet("help"), i18n( "&Help" ), helpMenu( this ) ); + + insertSeparator(); + + safePlug( ac, KStdAction::name(KStdAction::Quit), this ); + + connect( this, SIGNAL( aboutToShow() ), SLOT( slotAboutToShow() ) ); + connect( this, SIGNAL( activated(int) ), SLOT( slotActivated(int) ) ); + + setItemEnabled( ID_SHOW_VIS_SELECTOR, false ); + #ifdef HAVE_LIBVISUAL + setItemEnabled( ID_SHOW_VIS_SELECTOR, true ); + #endif +} + +Menu* +Menu::instance() +{ + static Menu menu; + return &menu; +} + +KPopupMenu* +Menu::helpMenu( QWidget *parent ) //STATIC +{ + extern KAboutData aboutData; + + if ( s_helpMenu == 0 ) + s_helpMenu = new KHelpMenu( parent, &aboutData, Amarok::actionCollection() ); + + return s_helpMenu->menu(); +} + +void +Menu::slotAboutToShow() +{ + setItemEnabled( ID_CONFIGURE_EQUALIZER, EngineController::hasEngineProperty( "HasEqualizer" ) ); + setItemEnabled( ID_CONF_DECODER, EngineController::hasEngineProperty( "HasConfigure" ) ); +} + +void +Menu::slotActivated( int index ) +{ + switch( index ) + { + case ID_SHOW_COVER_MANAGER: + CoverManager::showOnce(); + break; + case ID_SHOW_VIS_SELECTOR: + Vis::Selector::instance()->show(); //doing it here means we delay creation of the widget + break; + case ID_RESCAN_COLLECTION: + CollectionDB::instance()->startScan(); + break; + } +} + +////////////////////////////////////////////////////////////////////////////////////////// +// PlayPauseAction +////////////////////////////////////////////////////////////////////////////////////////// + +PlayPauseAction::PlayPauseAction( KActionCollection *ac ) + : KToggleAction( i18n( "Play/Pause" ), 0, ac, "play_pause" ) + , EngineObserver( EngineController::instance() ) +{ + engineStateChanged( EngineController::engine()->state() ); + + connect( this, SIGNAL(activated()), EngineController::instance(), SLOT(playPause()) ); +} + +void +PlayPauseAction::engineStateChanged( Engine::State state, Engine::State /*oldState*/ ) +{ + QString text; + + switch( state ) { + case Engine::Playing: + setChecked( false ); + setIcon( Amarok::icon( "pause" ) ); + text = i18n( "Pause" ); + break; + case Engine::Paused: + setChecked( true ); + setIcon( Amarok::icon( "pause" ) ); + text = i18n( "Pause" ); + break; + case Engine::Empty: + setChecked( false ); + setIcon( Amarok::icon( "play" ) ); + text = i18n( "Play" ); + break; + case Engine::Idle: + return; + } + + //update menu texts for this special action + for( int x = 0; x < containerCount(); ++x ) { + QWidget *w = container( x ); + if( w->inherits( "QPopupMenu" ) ) + static_cast(w)->changeItem( itemId( x ), text ); + //TODO KToolBar sucks so much +// else if( w->inherits( "KToolBar" ) ) +// static_cast(w)->getButton( itemId( x ) )->setText( text ); + } +} + +////////////////////////////////////////////////////////////////////////////////////////// +// AnalyzerAction +////////////////////////////////////////////////////////////////////////////////////////// +#include "analyzerbase.h" + +AnalyzerAction::AnalyzerAction( KActionCollection *ac ) + : KAction( i18n( "Analyzer" ), 0, ac, "toolbar_analyzer" ) +{ + setShortcutConfigurable( false ); +} + +int +AnalyzerAction::plug( QWidget *w, int index ) +{ + //NOTE the analyzer will be deleted when the toolbar is deleted or cleared() + //we are not designed for unplugging() yet so there would be a leak if that happens + //but it's a rare event and unplugging is complicated. + + KToolBar *bar = dynamic_cast(w); + + if( bar && kapp->authorizeKAction( name() ) ) + { + const int id = KAction::getToolButtonID(); + + addContainer( w, id ); + connect( w, SIGNAL( destroyed() ), SLOT( slotDestroyed() ) ); + QWidget *container = new AnalyzerContainer( w ); + bar->insertWidget( id, 0, container, index ); + bar->setItemAutoSized( id, true ); + + return containerCount() - 1; + } + else return -1; +} + + +AnalyzerContainer::AnalyzerContainer( QWidget *parent ) + : QWidget( parent, "AnalyzerContainer" ) + , m_child( 0 ) +{ + QToolTip::add( this, i18n( "Click for more analyzers" ) ); + changeAnalyzer(); +} + +void +AnalyzerContainer::resizeEvent( QResizeEvent *) +{ + m_child->resize( size() ); +} + +void AnalyzerContainer::changeAnalyzer() +{ + delete m_child; + m_child = Analyzer::Factory::createPlaylistAnalyzer( this ); + m_child->setName( "ToolBarAnalyzer" ); + m_child->resize( size() ); + m_child->show(); +} + +void +AnalyzerContainer::mousePressEvent( QMouseEvent *e) +{ + if( e->button() == Qt::LeftButton ) { + AmarokConfig::setCurrentPlaylistAnalyzer( AmarokConfig::currentPlaylistAnalyzer() + 1 ); + changeAnalyzer(); + } +} + +void +AnalyzerContainer::contextMenuEvent( QContextMenuEvent *e) +{ +#if defined HAVE_LIBVISUAL + KPopupMenu menu; + menu.insertItem( SmallIconSet( Amarok::icon( "visualizations" ) ), i18n("&Visualizations"), Menu::ID_SHOW_VIS_SELECTOR ); + + if( menu.exec( mapToGlobal( e->pos() ) ) == Menu::ID_SHOW_VIS_SELECTOR ) + Menu::instance()->slotActivated( Menu::ID_SHOW_VIS_SELECTOR ); +#else + Q_UNUSED(e); +#endif +} + +////////////////////////////////////////////////////////////////////////////////////////// +// ToggleAction +////////////////////////////////////////////////////////////////////////////////////////// + +ToggleAction::ToggleAction( const QString &text, void ( *f ) ( bool ), KActionCollection* const ac, const char *name ) + : KToggleAction( text, 0, ac, name ) + , m_function( f ) +{} + +void ToggleAction::setChecked( bool b ) +{ + const bool announce = b != isChecked(); + + m_function( b ); + KToggleAction::setChecked( b ); + AmarokConfig::writeConfig(); //So we don't lose the setting when crashing + if( announce ) emit toggled( b ); //KToggleAction doesn't do this for us. How gay! +} + +void ToggleAction::setEnabled( bool b ) +{ + const bool announce = b != isEnabled(); + + if( !b ) + setChecked( false ); + KToggleAction::setEnabled( b ); + AmarokConfig::writeConfig(); //So we don't lose the setting when crashing + if( announce ) emit enabled( b ); +} + +////////////////////////////////////////////////////////////////////////////////////////// +// SelectAction +////////////////////////////////////////////////////////////////////////////////////////// + +SelectAction::SelectAction( const QString &text, void ( *f ) ( int ), KActionCollection* const ac, const char *name ) + : KSelectAction( text, 0, ac, name ) + , m_function( f ) +{ } + +void SelectAction::setCurrentItem( int n ) +{ + const bool announce = n != currentItem(); + + m_function( n ); + KSelectAction::setCurrentItem( n ); + AmarokConfig::writeConfig(); //So we don't lose the setting when crashing + if( announce ) emit activated( n ); +} + +void SelectAction::setEnabled( bool b ) +{ + const bool announce = b != isEnabled(); + + if( !b ) + setCurrentItem( 0 ); + KSelectAction::setEnabled( b ); + AmarokConfig::writeConfig(); //So we don't lose the setting when crashing + if( announce ) emit enabled( b ); +} + +void SelectAction::setIcons( QStringList icons ) +{ + m_icons = icons; + for( int i = 0, n = items().count(); i < n; ++i ) + popupMenu()->changeItem( i, kapp->iconLoader()->loadIconSet( *icons.at( i ), KIcon::Small ), popupMenu()->text( i ) ); +} + +QStringList SelectAction::icons() const { return m_icons; } + +QString SelectAction::currentIcon() const +{ + if( m_icons.count() ) + return *m_icons.at( currentItem() ); + return QString(); +} + +QString SelectAction::currentText() const { + return KSelectAction::currentText() + "

" + i18n("Click to change"); +} + +////////////////////////////////////////////////////////////////////////////////////////// +// VolumeAction +////////////////////////////////////////////////////////////////////////////////////////// + +VolumeAction::VolumeAction( KActionCollection *ac ) + : KAction( i18n( "Volume" ), 0, ac, "toolbar_volume" ) + , EngineObserver( EngineController::instance() ) + , m_slider( 0 ) //is QGuardedPtr +{} + +int +VolumeAction::plug( QWidget *w, int index ) +{ + //NOTE we only support one plugging currently + + delete static_cast( m_slider ); //just in case, remember, we only support one plugging! + + m_slider = new Amarok::VolumeSlider( w, Amarok::VOLUME_MAX ); + m_slider->setName( "ToolBarVolume" ); + m_slider->setValue( AmarokConfig::masterVolume() ); + m_slider->setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Ignored ); + + QToolTip::add( m_slider, i18n( "Volume control" ) ); + + EngineController* const ec = EngineController::instance(); + connect( m_slider, SIGNAL(sliderMoved( int )), ec, SLOT(setVolume( int )) ); + connect( m_slider, SIGNAL(sliderReleased( int )), ec, SLOT(setVolume( int )) ); + + static_cast(w)->insertWidget( KAction::getToolButtonID(), 0, m_slider, index ); + + return 0; +} + +void +VolumeAction::engineVolumeChanged( int value ) +{ + if( m_slider ) m_slider->setValue( value ); +} + + + +////////////////////////////////////////////////////////////////////////////////////////// +// RandomAction +////////////////////////////////////////////////////////////////////////////////////////// +RandomAction::RandomAction( KActionCollection *ac ) : + SelectAction( i18n( "Ra&ndom" ), &AmarokConfig::setRandomMode, ac, "random_mode" ) +{ + setItems( QStringList() << i18n( "&Off" ) << i18n( "&Tracks" ) << i18n( "&Albums" ) ); + setCurrentItem( AmarokConfig::randomMode() ); + setIcons( QStringList() << Amarok::icon( "random_no" ) << Amarok::icon( "random_track" ) << Amarok::icon( "random_album" ) ); +} + +void +RandomAction::setCurrentItem( int n ) +{ + if( KAction *a = parentCollection()->action( "favor_tracks" ) ) + a->setEnabled( n ); + SelectAction::setCurrentItem( n ); +} + + +////////////////////////////////////////////////////////////////////////////////////////// +// FavorAction +////////////////////////////////////////////////////////////////////////////////////////// +FavorAction::FavorAction( KActionCollection *ac ) : + SelectAction( i18n( "&Favor" ), &AmarokConfig::setFavorTracks, ac, "favor_tracks" ) +{ + setItems( QStringList() << i18n( "Off" ) + << i18n( "Higher &Scores" ) + << i18n( "Higher &Ratings" ) + << i18n( "Not Recently &Played" ) ); + + setCurrentItem( AmarokConfig::favorTracks() ); + setEnabled( AmarokConfig::randomMode() ); +} + +////////////////////////////////////////////////////////////////////////////////////////// +// RepeatAction +////////////////////////////////////////////////////////////////////////////////////////// +RepeatAction::RepeatAction( KActionCollection *ac ) : + SelectAction( i18n( "&Repeat" ), &AmarokConfig::setRepeat, ac, "repeat" ) +{ + setItems( QStringList() << i18n( "&Off" ) << i18n( "&Track" ) + << i18n( "&Album" ) << i18n( "&Playlist" ) ); + setIcons( QStringList() << Amarok::icon( "repeat_no" ) << Amarok::icon( "repeat_track" ) << Amarok::icon( "repeat_album" ) << Amarok::icon( "repeat_playlist" ) ); + setCurrentItem( AmarokConfig::repeat() ); +} + +////////////////////////////////////////////////////////////////////////////////////////// +// BurnMenuAction +////////////////////////////////////////////////////////////////////////////////////////// +BurnMenuAction::BurnMenuAction( KActionCollection *ac ) + : KAction( i18n( "Burn" ), 0, ac, "burn_menu" ) +{} + +int +BurnMenuAction::plug( QWidget *w, int index ) +{ + KToolBar *bar = dynamic_cast(w); + + if( bar && kapp->authorizeKAction( name() ) ) + { + const int id = KAction::getToolButtonID(); + + addContainer( bar, id ); + connect( bar, SIGNAL( destroyed() ), SLOT( slotDestroyed() ) ); + + bar->insertButton( QString::null, id, true, i18n( "Burn" ), index ); + + KToolBarButton* button = bar->getButton( id ); + button->setPopup( Amarok::BurnMenu::instance() ); + button->setName( "toolbutton_burn_menu" ); + button->setIcon( "k3b" ); + + return containerCount() - 1; + } + else return -1; +} + +BurnMenu::BurnMenu() +{ + insertItem( i18n("Current Playlist"), CURRENT_PLAYLIST ); + insertItem( i18n("Selected Tracks"), SELECTED_TRACKS ); + //TODO add "album" and "all tracks by artist" + + connect( this, SIGNAL( aboutToShow() ), SLOT( slotAboutToShow() ) ); + connect( this, SIGNAL( activated(int) ), SLOT( slotActivated(int) ) ); +} + +KPopupMenu* +BurnMenu::instance() +{ + static BurnMenu menu; + return &menu; +} + +void +BurnMenu::slotAboutToShow() +{} + +void +BurnMenu::slotActivated( int index ) +{ + switch( index ) + { + case CURRENT_PLAYLIST: + K3bExporter::instance()->exportCurrentPlaylist(); + break; + + case SELECTED_TRACKS: + K3bExporter::instance()->exportSelectedTracks(); + break; + } +} + +////////////////////////////////////////////////////////////////////////////////////////// +// StopMenuAction +////////////////////////////////////////////////////////////////////////////////////////// + +StopAction::StopAction( KActionCollection *ac ) + : KAction( i18n( "Stop" ), Amarok::icon( "stop" ), 0, EngineController::instance(), SLOT( stop() ), ac, "stop" ) +{} + +int +StopAction::plug( QWidget *w, int index ) +{ + KToolBar *bar = dynamic_cast(w); + + if( bar && kapp->authorizeKAction( name() ) ) + { + const int id = KAction::getToolButtonID(); + + addContainer( bar, id ); + connect( bar, SIGNAL( destroyed() ), SLOT( slotDestroyed() ) ); + + bar->insertButton( QString::null, id, SIGNAL( clicked() ), EngineController::instance(), SLOT( stop() ), + true, i18n( "Stop" ), index ); + + KToolBarButton* button = bar->getButton( id ); + button->setDelayedPopup( Amarok::StopMenu::instance() ); + button->setName( "toolbutton_stop_menu" ); + button->setIcon( Amarok::icon( "stop" ) ); + button->setEnabled( EngineController::instance()->engine()->loaded() ); // Disable button at startup + + return containerCount() - 1; + } + else return KAction::plug( w, index ); +} + +StopMenu::StopMenu() +{ + insertTitle( i18n( "Stop" ) ); + insertItem( i18n("Now"), NOW ); + insertItem( i18n("After Current Track"), AFTER_TRACK ); + insertItem( i18n("After Queue"), AFTER_QUEUE ); + + connect( this, SIGNAL( aboutToShow() ), SLOT( slotAboutToShow() ) ); + connect( this, SIGNAL( activated(int) ), SLOT( slotActivated(int) ) ); +} + +KPopupMenu* +StopMenu::instance() +{ + static StopMenu menu; + return &menu; +} + +void +StopMenu::slotAboutToShow() +{ + Playlist *pl = Playlist::instance(); + + setItemEnabled( NOW, Amarok::actionCollection()->action( "stop" )->isEnabled() ); + + setItemEnabled( AFTER_TRACK, EngineController::engine()->loaded() ); + setItemChecked( AFTER_TRACK, pl->stopAfterMode() == Playlist::StopAfterCurrent ); + + setItemEnabled( AFTER_QUEUE, pl->nextTracks().count() ); + setItemChecked( AFTER_QUEUE, pl->stopAfterMode() == Playlist::StopAfterQueue ); +} + +void +StopMenu::slotActivated( int index ) +{ + Playlist* pl = Playlist::instance(); + const int mode = pl->stopAfterMode(); + + switch( index ) + { + case NOW: + Amarok::actionCollection()->action( "stop" )->activate(); + if( mode == Playlist::StopAfterCurrent || mode == Playlist::StopAfterQueue ) + pl->setStopAfterMode( Playlist::DoNotStop ); + break; + case AFTER_TRACK: + pl->setStopAfterMode( mode == Playlist::StopAfterCurrent + ? Playlist::DoNotStop + : Playlist::StopAfterCurrent ); + break; + case AFTER_QUEUE: + pl->setStopAfterMode( mode == Playlist::StopAfterQueue + ? Playlist::DoNotStop + : Playlist::StopAfterQueue ); + break; + } +} + + +#include "actionclasses.moc" diff --git a/amarok/src/actionclasses.h b/amarok/src/actionclasses.h new file mode 100644 index 00000000..4d51534a --- /dev/null +++ b/amarok/src/actionclasses.h @@ -0,0 +1,215 @@ +// Maintainer: Max Howell , (C) 2004 +// Copyright: See COPYING file that comes with this distribution +// +// Description: a popupmenu to control various features of Amarok +// also provides Amarok's helpMenu + +#ifndef AMAROK_ACTIONCLASSES_H +#define AMAROK_ACTIONCLASSES_H + +#include "engineobserver.h" +#include "prettypopupmenu.h" +#include "sliderwidget.h" + +#include +#include +#include + +class KActionCollection; +class KHelpMenu; + + +namespace Amarok +{ + class Menu : public PrettyPopupMenu + { + Q_OBJECT + public: + static Menu *instance(); + static KPopupMenu *helpMenu( QWidget *parent = 0 ); + + enum MenuIds { + ID_CONF_DECODER, + ID_SHOW_VIS_SELECTOR, + ID_SHOW_COVER_MANAGER, + ID_CONFIGURE_EQUALIZER, + ID_RESCAN_COLLECTION + }; + + public slots: + void slotActivated( int index ); + + private slots: + void slotAboutToShow(); + + private: + Menu(); + + static KHelpMenu *s_helpMenu; + }; + + + class MenuAction : public KAction + { + public: + MenuAction( KActionCollection* ); + virtual int plug( QWidget*, int index = -1 ); + }; + + + class PlayPauseAction : public KToggleAction, public EngineObserver + { + public: + PlayPauseAction( KActionCollection* ); + virtual void engineStateChanged( Engine::State, Engine::State = Engine::Empty ); + }; + + class AnalyzerContainer : public QWidget + { + public: + AnalyzerContainer( QWidget *parent ); + protected: + virtual void resizeEvent( QResizeEvent* ); + virtual void mousePressEvent( QMouseEvent* ); + virtual void contextMenuEvent( QContextMenuEvent* ); + private: + void changeAnalyzer(); + QWidget *m_child; + }; + + class AnalyzerAction : public KAction + { + public: + AnalyzerAction( KActionCollection* ); + virtual int plug( QWidget *, int index = -1 ); + }; + + + class VolumeAction : public KAction, public EngineObserver + { + public: + VolumeAction( KActionCollection* ); + virtual int plug( QWidget *, int index = -1 ); + private: + void engineVolumeChanged( int value ); + QGuardedPtr m_slider; + }; + + + class ToggleAction : public KToggleAction + { + public: + ToggleAction( const QString &text, void ( *f ) ( bool ), KActionCollection* const ac, const char *name ); + + virtual void setChecked( bool b ); + + virtual void setEnabled( bool b ); + + private: + void ( *m_function ) ( bool ); + }; + + class SelectAction : public KSelectAction + { + public: + SelectAction( const QString &text, void ( *f ) ( int ), KActionCollection* const ac, const char *name ); + + virtual void setCurrentItem( int n ); + + virtual void setEnabled( bool b ); + + virtual void setIcons( QStringList icons ); + + virtual QString currentText() const; + + QStringList icons() const; + + QString currentIcon() const; + + private: + void ( *m_function ) ( int ); + QStringList m_icons; + }; + + + class RandomAction : public SelectAction + { + public: + RandomAction( KActionCollection *ac ); + virtual void setCurrentItem( int n ); + }; + + class FavorAction : public SelectAction + { + public: + FavorAction( KActionCollection *ac ); + }; + + class RepeatAction : public SelectAction + { + public: + RepeatAction( KActionCollection *ac ); + }; + + class BurnMenu : public KPopupMenu + { + Q_OBJECT + + public: + enum MenuIds { + CURRENT_PLAYLIST, + SELECTED_TRACKS + }; + + static KPopupMenu *instance(); + + private slots: + void slotAboutToShow(); + void slotActivated( int index ); + + private: + BurnMenu(); + }; + + + class BurnMenuAction : public KAction + { + public: + BurnMenuAction( KActionCollection* ); + virtual int plug( QWidget*, int index = -1 ); + }; + + class StopMenu : public KPopupMenu + { + Q_OBJECT + + public: + enum MenuIds { + NOW, + AFTER_TRACK, + AFTER_QUEUE + }; + + static KPopupMenu *instance(); + + private slots: + void slotAboutToShow(); + void slotActivated( int index ); + + private: + StopMenu(); + }; + + + class StopAction : public KAction + { + public: + StopAction( KActionCollection* ); + virtual int plug( QWidget*, int index = -1 ); + }; + +} /* namespace Amarok */ + + +#endif /* AMAROK_ACTIONCLASSES_H */ + diff --git a/amarok/src/amarok.desktop b/amarok/src/amarok.desktop new file mode 100644 index 00000000..603db561 --- /dev/null +++ b/amarok/src/amarok.desktop @@ -0,0 +1,114 @@ +[Desktop Entry] +Type=Application +Version=0.9.4 +Encoding=UTF-8 +Name=Amarok +Name[bn]=আমারক +Name[el]=AmaroK +Name[ga]=AmaroK +Name[lt]=amaroK +Name[mk]=Амарок +Name[ne]=अमारोक +Name[pa]=ਅਮਰੋਕ +Name[ru]=amaroK +Name[zh_TW]=AmaroK +GenericName=Audio Player +GenericName[af]=Oudio Speler +GenericName[ar]=قارئى الصوت +GenericName[bg]=Аудио плеър +GenericName[bn]=অডিও প্লেয়ার +GenericName[br]=C'hoarier klevet +GenericName[ca]=Reproductor d'àudio +GenericName[cs]=Zvukový přehrávač +GenericName[da]=Lydafspiller +GenericName[de]=Audio-Wiedergabeprogramm +GenericName[el]=Αναπαραγωγή ήχου +GenericName[eo]=Aŭdludilo +GenericName[es]=Reproductor de audio +GenericName[et]=Helifailide mängija +GenericName[eu]=Audio-erreproduktorea +GenericName[fa]=پخش‌کنندۀ صوتی +GenericName[fi]=Musiikkisoitin +GenericName[fr]=Lecteur audio +GenericName[ga]=Seinnteoir Fuaime +GenericName[gl]=Reprodutor de Áudio +GenericName[hi]=ऑडियो प्लेयर +GenericName[hu]=Zenelejátszó +GenericName[is]=Tónlistarspilari +GenericName[it]=Lettore audio +GenericName[ja]=オーディオプレーヤ +GenericName[ka]=აუდიოდამკვრელი +GenericName[km]=កម្មវិធី​ចាក់​អូឌីយ៉ូ +GenericName[lt]=Muzikos grotuvas +GenericName[mk]=Аудио-изведувач +GenericName[mn]=Аудио-изведувач +GenericName[ms]=Pemain Audio +GenericName[nb]=Musikkavspiller +GenericName[nds]=Klangafspeler +GenericName[ne]=अडियो प्लेयर +GenericName[nl]=Audiospeler +GenericName[nn]=Lydspelar +GenericName[pa]=ਆਡੀਓ ਪਲੇਅਰ +GenericName[pl]=Odtwarzacz audio +GenericName[pt]=Leitor de Áudio +GenericName[pt_BR]=Reprodutor de Áudio +GenericName[ru]=Аудиоплеер +GenericName[se]=Jietnačuojaheaddji +GenericName[sk]=Audio prehrávač +GenericName[sl]=Predvajalnik zvoka +GenericName[sq]=Audio plejer +GenericName[sr]=Аудио плејер +GenericName[sr@Latn]=Audio plejer +GenericName[ss]=Audio plejer +GenericName[sv]=Ljudspelare +GenericName[ta]=கேட்பொலி இயக்கி +GenericName[tg]=Аудио Плейер +GenericName[th]=โปรแกรมเล่นเสียง +GenericName[tr]=Müzik Çalar +GenericName[uk]=Аудіопрогравач +GenericName[uz]=Audio-pleyer +GenericName[uz@cyrillic]=Аудио-плейер +GenericName[wa]=Djouweu d' muzike +GenericName[zh_CN]=音频播放器 +GenericName[zh_TW]=音樂播放程式 +Exec=amarok %U +Comment=Amarok - Rediscover Your Music! +Comment[bg]=Amarok - преоткрий музиката! +Comment[bn]=আমারক - আপনার সঙ্গীতের এক নতুন দিগন্ত +Comment[ca]=Amarok - Redescobreix la teva música! +Comment[cs]=Amarok - objevte svou hudbu! +Comment[da]=Amarok - Genopdag din musik! +Comment[de]=Amarok - Musik neu erleben! +Comment[el]=Amarok - Ξαναανακαλύψτε τη μουσική σας! +Comment[es]=Amarok - ¡Vuelva a descubrir su música! +Comment[et]=Amarok - naudi oma muusikat! +Comment[hu]=Amarok - fedezze fel a zenéjét +Comment[is]=Amarok - enduruppgötvaðu tónlistina þína! +Comment[it]=Amarok - Riscopri la tua musica! +Comment[ja]=Amarok - あなたの音楽を再発見 +Comment[km]=Amarok - រកឃើញ​តន្ត្រី​របស់​អ្នក​ឡើង​វិញ ! +Comment[lt]=Amarok – atraskite savo muziką iš naujo! +Comment[nds]=Amarok - Beleev Dien Musik nieg! +Comment[nl]=Amarok - Herontdek uw muziek! +Comment[nn]=Amarok – gjenoppdag musikken din! +Comment[pl]=Amarok - odkryj na nowo swoją muzykę! +Comment[pt]=Amarok - Descubra de Novo a Sua Música! +Comment[pt_BR]=Amarok - Redescubra Sua Música! +Comment[sk]=Amarok - Znova objavte hudbu! +Comment[sr]=Amarok — откријте своју музику! +Comment[sr@Latn]=Amarok — otkrijte svoju muziku! +Comment[sv]=Amarok - Återupptäck din musik! +Comment[th]=Amarok - ค้นพบมิติใหม่ในดนตรีของคุณ! +Comment[tr]=Amarok - Müziğinizi yeniden keşfedin! +Comment[uk]=Amarok - заново відчуйте свою музику! +Comment[uz]=Amarok - musiqaga yangidan nazar tashlang +Comment[uz@cyrillic]=Amarok - мусиқага янгидан назар ташланг +Comment[wa]=Amarok - Ridiscovroz vosse muzike! +Comment[zh_TW]=Amarok - 重新發現你的音樂! +Icon=amarok +X-KDE-Protocols=http +#don't add inode/directory to mimetypes it leads to misbehavior +MimeType=audio/aac;audio/mp4;audio/mpeg;audio/mpegurl;audio/vnd.rn-realaudio;audio/vorbis;audio/x-flac;audio/x-mp3;audio/x-mpegurl;audio/x-ms-wma;audio/x-musepack;audio/x-oggflac;audio/x-pn-realaudio;audio/x-scpls;audio/x-speex;audio/x-vorbis;audio/x-wav;video/x-ms-asf;audio/flac;audio/ogg; +DocPath=amarok/index.html +Terminal=false +Categories=Qt;KDE;AudioVideo;Audio;Player; diff --git a/amarok/src/amarok.h b/amarok/src/amarok.h new file mode 100644 index 00000000..3be1ae26 --- /dev/null +++ b/amarok/src/amarok.h @@ -0,0 +1,345 @@ +//See COPYING file for licensing information + +#ifndef AMAROK_H +#define AMAROK_H + +#include +#include + +#include // recursiveUrlExpand +#include //Amarok::ProcIO +#include +#include + +#include "amarok_export.h" + +class KActionCollection; +class KConfig; +class QColor; +class QDateTime; +class QEvent; +class QMutex; +class QPixmap; +class QWidget; +class DynamicMode; +class QListView; +class QListViewItem; +namespace KIO { class Job; } + +namespace Amarok +{ + const int VOLUME_MAX = 100; + const int SCOPE_SIZE = 9; //= 2**9 = 512 + const int blue = 0x202050; + const int VOLUME_SENSITIVITY = 30; //for mouse wheels + const int GUI_THREAD_ID = 0; + + extern QMutex globalDirsMutex; // defined in app.cpp + + namespace ColorScheme + { + ///eg. base of the Amarok Player-window + extern QColor Base; //Amarok::blue + ///eg. text in the Amarok Player-window + extern QColor Text; //Qt::white + ///eg. background colour for Amarok::PrettySliders + extern QColor Background; //brighter blue + ///eg. outline of slider widgets in Player-window + extern QColor Foreground; //lighter blue + ///eg. KListView alternative row color + extern QColor AltBase; //grey toned base + } + + /** The version of the playlist XML format. Increase whenever it changes backwards-incompatibly. */ + inline QString xmlVersion() { return "2.4"; } + + /** + * Convenience function to return the KApplication instance KConfig object + * pre-set to a specific group. + * @param group Will pre-set the KConfig object to this group. + */ + /* FIXME: This function can lead to very bizarre and hard to figure bugs. + While we don`t fix it properly, use it like this: amarok::config( Group )->readNumEntry( ... ) */ + KConfig *config( const QString &group = "General" ); //defined in app.cpp + + /** + * @return the KActionCollection used by Amarok + * The KActionCollection is owned by the PlaylistWindow, so you must ensure + * you don't try to use this before then, but we've taken steps to prevent + * this eventuality - you should be safe. + */ + KActionCollection *actionCollection(); //defined in app.cpp + + /** + * An event handler that handles events in a generic Amarok fashion. Mainly + * useful for drops, ie offers the Amarok popup for adding tracks to the + * playlist. You shouldn't pass every event here, ie closeEvents will not be + * handled as expected! Check the source in app.cpp if you want to see what + * it can do. + * @param recipient The object that received the event. + * @param e The event you want handled in a generic fashion. + * @return true if the event was handled. + */ + bool genericEventHandler( QWidget *recipient, QEvent *e ); //defined in app.cpp + + /** + * Invoke the external web browser set in Amarok's configuration. + * @param url The URL to be opened in the browser. + * @return True if the browser could be started. + */ + bool invokeBrowser( const QString& url ); //defined in app.cpp + + /** + * Obtain an Amarok PNG image as a QPixmap + */ + QPixmap getPNG( const QString& /*fileName*/ ); //defined in app.cpp + + /** + * Obtain an Amarok JPG image as a QPixmap + */ + QPixmap getJPG( const QString& /*fileName*/ ); //defined in app.cpp + + /** + * The mainWindow is the playlistWindow or the playerWindow depending on + * the configuration of Amarok + */ + QWidget *mainWindow(); //defined in app.cpp + + /** + * Allocate one on the stack, and it'll set the busy cursor for you until it + * is destroyed + */ + class OverrideCursor { //defined in app.cpp + public: + OverrideCursor( Qt::CursorShape cursor = Qt::WaitCursor ); + ~OverrideCursor(); + }; + + /** + * For saving files to ~/.kde/share/apps/amarok/directory + * @param directory will be created if not existing, you MUST end the string + * with '/' + */ + LIBAMAROK_EXPORT QString saveLocation( const QString &directory = QString::null ); //defined in collectionreader.cpp + + KIO::Job *trashFiles( const KURL::List &files ); //defined in app.cpp + + /** + * For recursively expanding the contents of a directory into a KURL::List + * (playlists are ignored) + */ + LIBAMAROK_EXPORT KURL::List recursiveUrlExpand( const KURL &url, int maxURLs = -1 ); //defined in playlistloader.cpp + LIBAMAROK_EXPORT KURL::List recursiveUrlExpand( const KURL::List &urls, int maxURLs = -1 ); //defined in playlistloader.cpp + + QString verboseTimeSince( const QDateTime &datetime ); //defined in contextbrowser.cpp + + QString verboseTimeSince( uint time_t ); //defined in contextbrowser.cpp + + /** + * Function that must be used when separating contextBrowser escaped urls + */ + // defined in contextbrowser.cpp + void albumArtistTrackFromUrl( QString url, QString &artist, QString &album, QString &detail ); + + /** + * @return the LOWERCASE file extension without the preceding '.', or "" if there is none + */ + inline QString extension( const QString &fileName ) + { + return fileName.contains( '.' ) ? fileName.mid( fileName.findRev( '.' ) + 1 ).lower() : ""; + } + + /** Transform url into a file url if possible */ + inline KURL mostLocalURL( const KURL &url ) + { +#if KDE_VERSION < KDE_MAKE_VERSION(3,5,0) + return url; +#else + return KIO::NetAccess::mostLocalURL( url, mainWindow() ); +#endif + } + + /** + * @return the last directory in @param fileName + */ + inline QString directory( const QString &fileName ) + { + return fileName.section( '/', 0, -2 ); + } + /** Due to xine-lib, we have to make KProcess close all fds, otherwise we get "device is busy" messages + * Used by Amarok::ProcIO and Amarok::Process, exploiting commSetupDoneC(), a virtual method that + * happens to be called in the forked process + * See bug #103750 for more information. + */ + //TODO ugly hack, fix KProcess for KDE 4.0 + void closeOpenFiles(int out, int in, int err); //defined in scriptmanager.cpp + + /** + * Returns internal code for database type, DbConnection::sqlite, DbConnection::mysql, or DbConnection::postgresql + * @param type either "SQLite", "MySQL", or "Postgresql". + */ + int databaseTypeCode( const QString type ); //defined in configdialog.cpp + + void setUseScores( bool use ); //defined in app.cpp + void setUseRatings( bool use ); + void setMoodbarPrefs( bool show, bool moodier, int alter, bool withMusic ); + + bool repeatNone(); //defined in actionclasses.cpp + bool repeatTrack(); + bool repeatAlbum(); + bool repeatPlaylist(); + bool randomOff(); + bool randomTracks(); + bool randomAlbums(); + bool favorNone(); + bool favorScores(); + bool favorRatings(); + bool favorLastPlay(); + bool entireAlbums(); //repeatAlbum() || randomAlbums() + + const DynamicMode *dynamicMode(); //defined in playlist.cpp + + QListViewItem* findItemByPath( QListView *view, QString path ); //defined in playlistbrowser.cpp + QStringList splitPath( QString path ); //defined in playlistbrowser.cpp + + /** + * Creates a copy of of the KURL instance, that doesn't have any QStrings sharing memory. + **/ + KURL detachedKURL( const KURL &url ); //defined in metabundle.cpp + + /** + * Maps the icon name to a system icon or custom Amarok icon, depending on the settings. + */ + LIBAMAROK_EXPORT QString icon( const QString& name ); //defined in iconloader.cpp + + /** + * Removes accents from the string + * @param path The original path. + * @return The cleaned up path. + */ + LIBAMAROK_EXPORT QString cleanPath( const QString &path ); //defined in app.cpp + + /** + * Replaces all non-ASCII characters with '_'. + * @param path The original path. + * @return The ASCIIfied path. + */ + LIBAMAROK_EXPORT QString asciiPath( const QString &path ); //defined in app.cpp + + /** + * Transform path into one valid on VFAT file systems + * @param path The original path. + * @return The cleaned up path. + */ + LIBAMAROK_EXPORT QString vfatPath( const QString &path ); //defined in app.cpp + + /** + * Compare both strings from left to right and remove the common part from input + * @param input the string that get's cleaned. + * @param ref a reference to compare input with. + * @return The cleaned up string. + */ + LIBAMAROK_EXPORT QString decapitateString( const QString &input, const QString &ref ); + + /* + * Transform to be usable within HTML/HTML attributes + * defined in contextbrowser.cpp + */ + LIBAMAROK_EXPORT QString escapeHTML( const QString &s ); + LIBAMAROK_EXPORT QString escapeHTMLAttr( const QString &s ); + LIBAMAROK_EXPORT QString unescapeHTMLAttr( const QString &s ); + + /* defined in scriptmanager.cpp */ + /** + * Returns the proxy that should be used for a given URL. + * @param url the url. + * @return The url of the proxy, or a empty string if no proxy should be used. + */ + QString proxyForUrl(const QString& url); + + /** + * Returns the proxy that should be used for a given protocol. + * @param protocol the protocol. + * @return The url of the proxy, or a empty string if no proxy should be used. + */ + QString proxyForProtocol(const QString& protocol); + + //////////////////////////////////////////////////////////////////////////////// + // class Amarok::ProcIO + //////////////////////////////////////////////////////////////////////////////// + /** + * Due to xine-lib, we have to make KProcess close all fds, otherwise we get "device is busy" messages + * Used by Amarok::ProcIO and AmarokProcess, exploiting commSetupDoneC(), a virtual method that + * happens to be called in the forked process + * See bug #103750 for more information. + */ + class LIBAMAROK_EXPORT ProcIO : public KProcIO { + public: + ProcIO(); // ctor sets the textcodec to UTF-8, in scriptmanager.cpp + virtual int commSetupDoneC() { + const int i = KProcIO::commSetupDoneC(); + Amarok::closeOpenFiles( KProcIO::out[0],KProcIO::in[0],KProcIO::err[0] ); + return i; + }; + }; + + //////////////////////////////////////////////////////////////////////////////// + // class Amarok::Process + //////////////////////////////////////////////////////////////////////////////// + /** Due to xine-lib, we have to make KProcess close all fds, otherwise we get "device is busy" messages + * Used by Amarok::ProcIO and Amarok::Process, exploiting commSetupDoneC(), a virtual method that + * happens to be called in the forked process + * See bug #103750 for more information. + */ + class LIBAMAROK_EXPORT Process : public KProcess { + public: + Process( QObject *parent = 0 ) : KProcess( parent ) {} + virtual int commSetupDoneC() { + const int i = KProcess::commSetupDoneC(); + Amarok::closeOpenFiles(KProcess::out[0],KProcess::in[0], KProcess::err[0]); + return i; + }; + }; + + +} + + +/** + * Use this to const-iterate over QStringLists, if you like. + * Watch out for the definition of last in the scope of your for. + * + * QStringList strings; + * foreach( strings ) + * debug() << *it << endl; + */ +#define foreach( x ) \ + for( QStringList::ConstIterator it = x.begin(), end = x.end(); it != end; ++it ) + +/** + * You can use this for lists that aren't QStringLists. + * Watch out for the definition of last in the scope of your for. + * + * BundleList bundles; + * foreachType( BundleList, bundles ) + * debug() << *it.url() << endl; + */ +#define foreachType( Type, x ) \ + for( Type::ConstIterator it = x.begin(), end = x.end(); it != end; ++it ) + +/** + * Creates iterators of type @p Type. + * Watch out for the definitions of last and end in your scope. + * + * BundleList bundles; + * for( for_iterators( BundleList, bundles ); it != end; ++it ) + * debug() << *it.url() << endl; + */ +#define for_iterators( Type, x ) \ + Type::ConstIterator it = x.begin(), end = x.end(), last = x.fromLast() + + +/// Update this when necessary +#define APP_VERSION "1.4.10" + +#endif diff --git a/amarok/src/amarok.profile.xml b/amarok/src/amarok.profile.xml new file mode 100644 index 00000000..8479499e --- /dev/null +++ b/amarok/src/amarok.profile.xml @@ -0,0 +1,60 @@ + + + + Amarok + Dirk Ziegelmeier + + + + + Increase volume + + + + Decrease volume + + + + Toggle sound muting + + + + + + Start playing + + + + Stop playing + + + + Pause playing + + + + Advance to next track + + + + Go to previous track + + + + + Relative seek + Seek inside of tracks. Use negative numbers for rewind. + + + + + + + Show OSD display + Works only if OSD is enabled + + + Toggles the playlist window + + + diff --git a/amarok/src/amarok_addaspodcast.desktop b/amarok/src/amarok_addaspodcast.desktop new file mode 100644 index 00000000..0b9e5f28 --- /dev/null +++ b/amarok/src/amarok_addaspodcast.desktop @@ -0,0 +1,50 @@ +[Desktop Entry] +Encoding=UTF-8 +ServiceTypes=text/html,text/xml,application/xml,text/rss,text/rdf +Actions=addAsPodcast; +[Desktop Action addAsPodcast] +Name=Add as Podcast to Amarok +Name[af]=Voeg as Podcast by Amarok by +Name[bg]=Добавяне като подкаст в Amarok +Name[bn]=পডকাস্ট হিসেবে আমারক-এ যোগ করো +Name[ca]=Afegeix com a Podcast a l'Amarok +Name[cs]=Přidat podcast +Name[da]=Tilføj som podradioudsendelse i Amarok +Name[de]=Als Podcast zu Amarok hinzufügen +Name[el]=Προσθήκη ως εκπομπή Pod στο Amarok +Name[eo]=Aldonu kiel Podkasto al Amarok +Name[es]=Añadir como un podcast a Amarok +Name[et]=Lisa Podcastina Amarokile +Name[fa]=افزودن به عنوان Podcast به Amarok +Name[fi]=Lisää Amarokiin podcastina +Name[fr]=Ajouter en tant que podcast dans Amarok +Name[hu]=Betevés az Amarokba podcast-ként +Name[is]=Bæta við Amarok sem Podcast +Name[it]=Aggiungi come podcast in Amarok +Name[ja]=ポッドキャストとして Amarok に追加 +Name[km]=បន្ថែម​ជា​ផតខាស់​ទៅ Amarok +Name[lt]=Įterpti Podcast į Amarok grojaraštį +Name[nb]= Legg til Amarok som podkast +Name[nds]=As Podcast na Amarok tofögen +Name[ne]=अमारोकमा पोडकास्टको रूपमा थप्नुहोस् +Name[nl]=Als Podcast aan Amarok toevoegen +Name[nn]=Legg til som podkast i Amarok +Name[pa]=ਅਮਰੋਕ 'ਚ ਪੋਸਟਕਾਸਟ ਵਾਂਗ ਸ਼ਾਮਿਲ +Name[pl]=Dodaj jako podcast do Amaroka +Name[pt]=Adicionar como 'Podcast' ao Amarok +Name[pt_BR]=Adicionar como Podcast ao Amarok +Name[se]=Lasit podkásttan Amarok:ii +Name[sk]=Pridať ako Podcast do Amarok +Name[sr]=Додај као подемисију у Amarok +Name[sr@Latn]=Dodaj kao podemisiju u Amarok +Name[sv]=Lägg till som podradiosändning i Amarok +Name[th]=เพิ่มเป็นพ็อดแคสต์ไปยัง Amarok +Name[tr]=Amarok'a Podcast olarak Ekle +Name[uk]=Додати в Amarok як подкест +Name[uz]=Amarok'ka podkast sifatida qoʻshish +Name[uz@cyrillic]=Amarok'ка подкаст сифатида қўшиш +Name[wa]=Radjouter come Podcast a Amarok +Name[zh_CN]=将播客添加到 Aamarok +Name[zh_TW]=將 Podcast 加入 Amarok +Icon=amarok +Exec=dcop amarok playlistbrowser addPodcast %u diff --git a/amarok/src/amarok_append.desktop b/amarok/src/amarok_append.desktop new file mode 100644 index 00000000..1d6dac52 --- /dev/null +++ b/amarok/src/amarok_append.desktop @@ -0,0 +1,175 @@ +[Desktop Entry] +Encoding=UTF-8 +ServiceTypes=application/asx,audio/* +Actions=appendToPlaylist;appendAndPlay;queueTrack; +X-KDE-Submenu=Amarok +X-KDE-Submenu[bn]=আমারক +X-KDE-Submenu[el]=AmaroK +X-KDE-Submenu[es]=amaroK +X-KDE-Submenu[ga]=AmaroK +X-KDE-Submenu[mk]=Амарок +X-KDE-Submenu[ne]=अमारोक +X-KDE-Submenu[pa]=ਅਮਰੋਕ +X-KDE-Submenu[ru]=amaroK + +[Desktop Action appendToPlaylist] +Name=Append to Playlist +Name[af]=Voeg agter aan speel lys by +Name[ar]=اضف الى لائحة القراءة +Name[bg]=Добавяне към списъка със записи +Name[bn]=সঙ্গীত-তালিকায় সংযোজন করো +Name[br]=Ouzhpennañ d'ar roll tonioù +Name[ca]=Afegeix a la llista de reproducció +Name[cs]=Přidat do seznamu skladeb +Name[da]=Tilføj til spilleliste +Name[de]=An Amarok-Wiedergabeliste anhängen +Name[el]=Προσθήκη στη λίστα αναπαραγωγής +Name[eo]=Aldoni al Ludlisto +Name[es]=Añadir a la lista de reproducción +Name[et]=Lisa esitusnimekirja +Name[fa]=پیوستن به فهرست پخش +Name[fi]=Lisää soittolistaan +Name[fr]=Ajouter à la liste de lecture +Name[ga]=Iarcheangail le Seinmliosta +Name[gl]=Engadir á Lista de Reprodución +Name[hu]=Hozzáadás a lejátszólistához +Name[is]=Bæta við Amarok lagalistann +Name[it]=Aggiungi alla playlist +Name[ja]=プレイリストに追加 +Name[ka]=რეპერტუარში დამატება +Name[km]=បន្ថែម​ទៅ​ខាង​ចុង​បញ្ជីចាក់ +Name[lt]=Pridėti prie Amarok grojaraščio +Name[mk]=Додај на листата +Name[nb]=Legg til spilleliste +Name[nds]=Na Afspeellist tofögen +Name[ne]=बजाउने सूचीमा थप्नुहोस् +Name[nl]=Toevoegen aan afspeellijst +Name[nn]=Legg til speleliste +Name[pa]=ਸੰਗੀਤ-ਸੂਚੀ 'ਚ ਸ਼ਾਮਲ +Name[pl]=Dołącz do listy odtwarzania +Name[pt]=Adicionar à Lista de Reprodução +Name[pt_BR]=Anexar à Lista de Músicas +Name[ru]=Добавить в список +Name[se]=Lasit čuojahanlistui +Name[sk]=Pridať do zoznamu skladieb +Name[sr]=Додај у листу нумера +Name[sr@Latn]=Dodaj u listu numera +Name[sv]=Lägg till i spellistan +Name[th]=เพิ่มเข้าไปในรายการเล่น +Name[tr]=Parça Listesine Ekle +Name[uk]=Додати до списку композицій +Name[uz]=Qoʻshiq roʻyxatiga qoʻshish +Name[uz@cyrillic]=Қўшиқ рўйхатига қўшиш +Name[wa]=Mete dins l' djivêye di léjhaedje +Name[zh_CN]=追加到播放列表 +Name[zh_TW]=加入播放清單 +Icon=amarok +Exec=amarok -e %U + +[Desktop Action appendAndPlay] +Name=Append & Play +Name[af]=Voeg agter aan by & Speel +Name[ar]=اضف و اقرأء +Name[bg]=Добавяне и изпълнение +Name[bn]=সংযোজন করো এবং চালাও +Name[br]=Ouzhpennañ ha seniñ +Name[ca]=Afegeix i reprodueix +Name[cs]=Připojit a hrát +Name[da]=Tilføj og spil +Name[de]=Anhängen und abspielen +Name[el]=Προσθήκη & αναπαραγωγή +Name[eo]=Aldoni & Ludi +Name[es]=Añadir y reproducir +Name[et]=Lisa ja esita +Name[fa]=پیوستن و پخش +Name[fi]=Lisää soittolistaan ja soita +Name[fr]=Ajouter & Écouter +Name[ga]=Iarcheangail & Seinn +Name[gl]=Engadir e Reproducir +Name[hu]=Hozzáadás és lejátszás +Name[is]=Bæta við og spila +Name[it]=Aggiungi & Riproduci +Name[ja]=追加して再生 +Name[ka]=დამატება და დაკვრა +Name[km]=បន្ថែម​ខាង​ចុង & ចាក់ +Name[lt]=Pridėti ir groti +Name[mk]=Додај и пушти +Name[nb]=Legg til og spill +Name[nds]=Tofögen un afspelen +Name[ne]=थप्नुहोस् र बजाउनुहोस् +Name[nl]=Toevoegen en afspelen +Name[nn]=Legg til og spel +Name[pa]=ਜੋੜੋ ਅਤੇ ਚਲਾਓ +Name[pl]=Dołącz i odtwórz +Name[pt]=Adicionar e Reproduzir +Name[pt_BR]=Anexar & Reproduzir +Name[ru]=Добавить и воспроизвести +Name[se]=Lasit ja čuojat +Name[sk]=Pridať a zahrať +Name[sr]=Додај и пусти +Name[sr@Latn]=Dodaj i pusti +Name[sv]=Lägg till och spela +Name[th]=เพิ่ม & เล่น +Name[tr]=Parça Listesine Ekle ve Çal +Name[uk]=Додати і програти +Name[uz]=Qoʻshish va oʻynash +Name[uz@cyrillic]=Қўшиш ва ўйнаш +Name[wa]=Mete dins l' djivêye et djouwer +Name[zh_CN]=追加并播放 +Name[zh_TW]=加入播放清單 & 播放 +Icon=amarok +Exec=dcop amarok playlist playMedia %U + +[Desktop Action queueTrack] +Name=Queue Track +Name[af]=Plaas snit in wagtou +Name[ar]= اضف المسار الى صفٌ الانتظار +Name[bg]=На опашката +Name[bn]=গান সারিবদ্ধ করো +Name[br]=L&ostañ ar roudenn +Name[ca]=Encua la peça +Name[cs]=Zařadit skladbu +Name[da]=Sæt spor i kø +Name[de]=Stück in Warteschlange einstellen +Name[el]=Εισαγωγή του κομματιού στην ουρά +Name[eo]=Vicigi Trakon +Name[es]=Encolar pista +Name[et]=Sea pala järjekorda +Name[fa]=صف کردن شیار +Name[fi]=Lisää jonoon +Name[fr]=Ajouter à la file d'attente +Name[ga]=Ciúáil Amhrán +Name[gl]=Pór Pista na Cola +Name[hu]=Szám betevése a sorba +Name[is]=Setja lag í biðröð +Name[it]=Accoda traccia +Name[ja]=トラックをキュー +Name[ka]=ჩანაწერის რიგში ჩამატება +Name[km]=ដាក់​បទ​ក្នុង​ជួរ +Name[lt]=Pridėti į eilę +Name[mk]=Стави нумера во редица +Name[nb]=Legg spor i kø +Name[nds]=Stück inregen +Name[ne]=लाम ट्रयाक +Name[nl]=Track in wachtrij plaatsen +Name[nn]=Legg spor i kø +Name[pa]=ਟਰੈਕ ਕਤਾਰ 'ਚ +Name[pl]=Wstaw utwór do kolejki +Name[pt]=Colocar a Faixa em Espera +Name[pt_BR]=Faixa para Fila +Name[ru]=Поставить в очередь +Name[se]=Bija bihtá gárgadassii +Name[sk]=Zaradiť do frontu +Name[sr]=Стави нумеру у ред +Name[sr@Latn]=Stavi numeru u red +Name[sv]=Köa spår +Name[th]=เข้าคิวเพลง +Name[tr]=Parçayı Sıraya Ekle +Name[uk]=Додати доріжку в чергу +Name[uz]=Navbatga qoʻyish +Name[uz@cyrillic]=Навбатга қўйиш +Name[wa]=Mete dins l' lisse des schoûtants +Name[zh_CN]=音轨排队 +Name[zh_TW]=把曲目放入播放佇列 +Icon=amarok +Exec=amarok --queue %U diff --git a/amarok/src/amarok_codecinstall.desktop b/amarok/src/amarok_codecinstall.desktop new file mode 100644 index 00000000..aa7c2167 --- /dev/null +++ b/amarok/src/amarok_codecinstall.desktop @@ -0,0 +1,15 @@ +[Desktop Entry] +Encoding=UTF-8 +Type=ServiceType +X-KDE-ServiceType=Amarok/CodecInstall + +# The unplayable codec +[PropertyDef::X-KDE-Amarok-codec] +Type=QString + +[PropertyDef::X-KDE-Amarok-engine] +Type=QString + +# If true, Amarok will show a warning dialog about the experimental nature of this plugin. +[PropertyDef::Exec] +Type=QString diff --git a/amarok/src/amarok_export.h b/amarok/src/amarok_export.h new file mode 100644 index 00000000..23afb772 --- /dev/null +++ b/amarok/src/amarok_export.h @@ -0,0 +1,17 @@ +// Copyright: See COPYING file that comes with this distribution + +#ifndef _AMAROK_EXPORT_H_ +#define _AMAROK_EXPORT_H_ + +#include + +#ifdef __KDE_HAVE_GCC_VISIBILITY +#define LIBAMAROK_NO_EXPORT __attribute__ ((visibility("hidden"))) +#define LIBAMAROK_EXPORT __attribute__ ((visibility("default"))) +#else +#define LIBAMAROK_NO_EXPORT +#define LIBAMAROK_EXPORT +#endif + +#endif + diff --git a/amarok/src/amarok_play_audiocd.desktop b/amarok/src/amarok_play_audiocd.desktop new file mode 100644 index 00000000..6ccd96ec --- /dev/null +++ b/amarok/src/amarok_play_audiocd.desktop @@ -0,0 +1,55 @@ +[Desktop Entry] +ServiceTypes=media/audiocd +Actions=Play; +Encoding=UTF-8 +X-KDE-Priority=TopLevel + +[Desktop Action Play] +Name=Play Audio CD with Amarok +Name[af]=Speel oudio CD met Amarok +Name[bg]=Изпълнение на компактдиск с Amarok +Name[bn]=আমারক দিয়ে অডিও সিডি চালাও +Name[ca]=Reprodueix el CD Àudio amb l'Amarok +Name[cs]=Přehrát audio CD +Name[da]=Afspil lyd-cd med Amarok +Name[de]=Audio-CD mit Amarok wiedergeben +Name[el]=Αναπαραγωγή CD ήχου με το Amarok +Name[eo]=Ludi Muzikan KD-n per Amarok +Name[es]=Reproducir CD con Amarok +Name[et]=Esita audio CD Amarokis +Name[fa]=پخش دیسک فشردۀ صوتی با Amarok +Name[fi]=Soita CD-levy Amarokilla +Name[fr]=Écouter un CD Audio avec Amarok +Name[ga]=Seinn Dlúthdhiosca Fuaime le Amarok +Name[hu]=Hang-CD lejátszása az Amarokkal +Name[is]=Spila tónlistardisk með Amarok +Name[it]=Riproduci CD audio con Amarok +Name[ja]=Amarok でオーディオ CD を再生 +Name[ka]=აუიდიო CD-ის დაკვრა Amarok-ით +Name[km]=ចាក់​ស៊ីឌី​អូឌីយ៉ូ​ជា​មួយ Amarok +Name[lt]=Groti audio CD su Amarok +Name[mk]=Свири аудиоцд со Амарок +Name[nb]=Spill lyd-CD med Amarok +Name[nds]=Audio-CD mit Amarok afspelen +Name[ne]=अमारोकसँग अडियो सीडी बजाउनुहोस् +Name[nl]=Audio-cd met Amarok afspelen +Name[nn]=Spel lyd-CD med Amarok +Name[pa]=ਅਮਰੋਕ ਨਾਲ ਆਡੀਓ CD ਚਲਾਓ +Name[pl]=Odtwórz Audio CD za pomocą Amarok +Name[pt]=Tocar o CD de Áudio com o Amarok +Name[pt_BR]=Reproduzir CD de Áudio com o Amarok +Name[se]=Čuojat jietna-CD:a Amarokain +Name[sk]=Zahrať Audio CD pomocou Amarok +Name[sr]=Пусти аудио CD помоћу Amarok-а +Name[sr@Latn]=Pusti audio CD pomoću Amarok-a +Name[sv]=Spela ljud-cd med Amarok +Name[th]=เล่นซีดีบันทึกเสียงด้วย Amarok +Name[tr]=Ses CD'sini Amarok ile Çal +Name[uk]=Грати аудіо-КД в Amarok +Name[uz]=Audio-diskni Amarok bilan tinglash +Name[uz@cyrillic]=Аудио-дискни Amarok билан тинглаш +Name[wa]=Djouwer l' CD d' muzike avou Amarok +Name[zh_CN]=用 Amarok 播放音频 CD +Name[zh_TW]=以 Amarok 播放音樂 CD +Icon=amarok +Exec=amarok --cdplay %u diff --git a/amarok/src/amarok_plugin.desktop b/amarok/src/amarok_plugin.desktop new file mode 100644 index 00000000..7924352d --- /dev/null +++ b/amarok/src/amarok_plugin.desktop @@ -0,0 +1,88 @@ +[Desktop Entry] +Encoding=UTF-8 +Type=ServiceType +X-KDE-ServiceType=Amarok/Plugin +Comment=Plugin for Amarok +Comment[af]=Inprop module vir Amarok +Comment[ar]= قابس ( برنامج مضاف الى) AmaroK +Comment[bg]=Приставка за Amarok +Comment[bn]=আমারক-এর জন্য প্লাগিন +Comment[br]=Lugent evit Amarok +Comment[ca]=Connector per l'Amarok +Comment[cs]=Modul pro AmaroK +Comment[de]=Modul für Amarok +Comment[el]=Πρόσθετο για το AmaroK +Comment[eo]=Kromaĵo por Amarok +Comment[es]=Extensión para Amarok +Comment[et]=Amaroki plugin +Comment[fa]=وصله برای amaroK +Comment[fi]=Amarok-liitännäinen +Comment[fr]=Module pour Amarok +Comment[ga]=Breiseán AmaroK +Comment[gl]=Extensión para Amarok +Comment[hu]=Bővítőmodul az Amarokhoz +Comment[is]=Íforrit fyrir Amarok +Comment[it]=Plugin per Amarok +Comment[ja]=Amarok のためのプラグイン +Comment[ka]=მოდული Amarok-ისთვის +Comment[km]=កម្មវិធី​ជំនួយ​សម្រាប់ Amarok +Comment[lt]=Amarok įskiepis +Comment[mk]=Приклучок за Амарок +Comment[nb]=Programtillegg for Amarok +Comment[nds]=Moduul för Amarok +Comment[ne]=अमारोकका लागि प्लगइन +Comment[nl]=Plugin voor Amarok +Comment[nn]=Programtillegg for Amarok +Comment[pa]=ਅਮਰੋਕ ਲਈ ਪਲੱਗਇਨ +Comment[pl]=Wtyczka Amaroka +Comment[pt]='Plugin' para o Amarok +Comment[pt_BR]=Plugin para o Amarok +Comment[ru]=Модуль amaroK +Comment[se]=Lassemoduvla Amarok:ii +Comment[sk]=Amarok modul +Comment[sr]=Прикључак за Amarok +Comment[sr@Latn]=Priključak za Amarok +Comment[sv]=Insticksprogram för Amarok +Comment[th]=โปรแกรมเสริมสำหรับ Amarok +Comment[tr]=Amarok için Eklenti +Comment[uk]=Втулок для Amarok +Comment[uz]=Amarok uchun plagin +Comment[uz@cyrillic]=Amarok учун плагин +Comment[wa]=Tchôke-divins po Amarok +Comment[zh_CN]=Amarok 插件 +Comment[zh_TW]=amaroK 插件 + +# Type of plugin, e.g. "engine". +[PropertyDef::X-KDE-Amarok-plugintype] +Type=QString + +# Internal name for identification, not translated. +[PropertyDef::X-KDE-Amarok-name] +Type=QString + +# List of authors. +[PropertyDef::X-KDE-Amarok-authors] +Type=QStringList + +# List of author's email addresses. +[PropertyDef::X-KDE-Amarok-email] +Type=QStringList + +# Priority of the plugin. When KTrader returns multiple offers, the one with the highest rank is chosen. +# Range: 0 (disabled) - 255 (highest) +[PropertyDef::X-KDE-Amarok-rank] +Type=int + +# Version of the plugin. +[PropertyDef::X-KDE-Amarok-version] +Type=int + +# Version of the framework this plugin is compatible with. +# Must be bumped after making binary incompatible changes. +[PropertyDef::X-KDE-Amarok-framework-version] +Type=int + +# If true, Amarok will show a warning dialog about the experimental nature of this plugin. +[PropertyDef::X-KDE-Amarok-experimental] +Type=bool + diff --git a/amarok/src/amarok_proxy.rb b/amarok/src/amarok_proxy.rb new file mode 100644 index 00000000..7984ffcf --- /dev/null +++ b/amarok/src/amarok_proxy.rb @@ -0,0 +1,238 @@ +#!/usr/bin/env ruby +# +# Proxy server for Last.fm and DAAP. Relays the stream from the server to localhost, and +# converts the protocol to http on the fly. +# +# (c) 2006 Paul Cifarelli +# (c) 2006 Mark Kretschmann +# (c) 2006 Michael Fellinger +# (c) 2006 Ian Monroe +# (c) 2006 Martin Ellis +# (c) 2006 Alexandre Oliveira +# (c) 2006 Tom Kaitchuck +# +# License: GNU General Public License V2 + +# Amarok listens to stderr and recognizes these magic strings, do not remove them: +# "AMAROK_PROXY: startup", "AMAROK_PROXY: SYNC" + + +require 'socket' +require "uri" +$stdout.sync = true + +class Proxy + ENDL = "\r\n" + + def initialize( port, remote_url, engine, proxy ) + @engine = engine + + myputs( "running with port: #{port} and url: #{remote_url} and engine: #{engine}" ) + + # Open the amarok-facing socket + # amarok: the server port on the localhost to which the engine will connect. + amarok = TCPServer.new( port ) + myputs( "startup" ) + + # amaroks: server socket for above. + amaroks = amarok.accept + + # uri: from amarok, identifies the source of the music + uri = URI.parse( remote_url ) + myputs("host " << uri.host << " ") + myputs( port ) + + # Now we have the source of the music, determine the HTTP request that + # needs to be made to the remote server (or remote proxy). It will + # be of the form "GET ... HTTP/1.x". It will include the + # http://hostname/ part if, and only if, we're using a remote proxy. + get = get_request( uri, !proxy.nil? ) + + #Check for proxy + begin + proxy_uri = URI.parse( proxy ) + serv = TCPSocket.new( proxy_uri.host, proxy_uri.port ) + rescue + serv = TCPSocket.new( uri.host, uri.port ) + end + + serv.sync = true + myputs( "running with port: #{uri.port} and host: #{uri.host}" ) + + # Read the GET request from the engine + amaroks_get = amaroks.readline + myputs( amaroks_get.inspect ) + + myputs( get.inspect ) + myputs( "#{amaroks_get} but sending #{get}" ) + serv.puts( get ) + + # Copy the HTTP REQUEST headers from the amarok engine to the + # remote server, and signal end of headers. + myputs( "COPY from amarok -> serv" ) + cp_to_empty_outward( amaroks, serv ) + safe_write( serv, "\r\n\r\n" ) + + # Copy the HTTP RESPONSE headers from the server back to the + # amarok engine. + myputs( "COPY from serv -> amarok" ) + cp_to_empty_inward( serv, amaroks ) + + if @engine == 'gst10-engine' + 3.times do + myputs( "gst10-engine waiting for reconnect" ) + sleep 1 + break if amaroks.eof + end + amaroks = amarok.accept + safe_write( amaroks, "HTTP/1.0 200 OK\r\n\r\n" ) + amaroks.each_line do |data| + myputs( data ) + data.chomp! + break if data.empty? + end + end + + # Now stream the music! + myputs( "Before cp_all()" ) + cp_all_inward( serv, amaroks ) + + if @engine == 'helix-engine' && amaroks.eof + myputs( "EOF Detected, reconnecting" ) + amaroks = amarok.accept + cp_all_inward( serv, amaroks ) + end + end + + def safe_write( output, data ) + begin + output.write data + rescue + myputs( "error from output.write, #{$!}" ) + myputs( $!.backtrace.inspect ) + break + end + end + + def cp_to_empty_outward( income, output ) + myputs "cp_to_empty_outward( income => #{income.inspect}, output => #{output.inspect}" + income.each_line do |data| + if data =~ /User-Agent: xine\/([0-9.]+)/ + version = $1.split(".").collect { |v| v.to_i } + myputs("Found xine user agent version #{version.join(".")}") + @xineworkaround = ( version[0] <= 1 && version[1] <= 1 && version[2] <= 2 ) + end + myputs( data ) + data.chomp! + safe_write( output, data ) + myputs( "data sent.") + return if data.empty? + end + end + + def desync (data) + if data.gsub!( "SYNC", "" ) + myputs( "SYNC" ) + end + end + + def cp_to_empty_inward( income, output ) + myputs( "cp_to_empty_inward( income => #{income.inspect}, output => #{output.inspect}" ) + income.each_line do |data| + myputs( data ) + safe_write( output, data ) + return if data.chomp == "" + end + end + + def cp_all_inward( income, output ) + myputs( "cp_all( income => #{income.inspect}, output => #{output.inspect}" ) + if self.is_a?( LastFM ) and @xineworkaround + myputs( "Using buffer fill workaround." ) + filler = Array.new( 4096, 0 ) + safe_write( output, filler ) # HACK: Fill xine's buffer so that xine_open() won't block + end + if @engine == 'helix-engine' + data = income.read( 1024 ) + else + data = income.read( 4 ) + end + desync( data ) + holdover = "" + loop do + begin + safe_write( output, data ) + rescue + myputs( "error from o.write, #{$!}" ) + break + end + newdata = income.read( 1024 ) + + data = holdover + newdata[0..-5] + holdover = newdata[-4..-1] + desync( data ) + + break if newdata == nil + end + end +end + +class LastFM < Proxy +# Last.fm protocol: +# Stream consists of pure MP3 files concatenated, with the string "SYNC" in between, which +# marks a track change. The proxy notifies Amarok on track change. + + def get_request( remote_uri, via_proxy ) + # remote_uri - the URI of the stream we want + # via_proxy - true iff we're going through another proxy + if via_proxy then + url = remote_uri.to_s + else + url = "#{remote_uri.path || '/'}?#{remote_uri.query}" + end + get = "GET #{url} HTTP/1.0" + ENDL + get += "Host: #{remote_uri.host}:#{remote_uri.port}" + ENDL + ENDL + end + +end + +class DaapProxy < Proxy + def initialize( port, remote_url, engine, hash, request_id, proxy ) + @hash = hash + @requestId = request_id + super( port, remote_url, engine, proxy ) + end + + def get_request( remote_uri, via_proxy ) + # via_proxy ignored for now + get = "GET #{remote_uri.path || '/'}?#{remote_uri.query} HTTP/1.0" + ENDL + get += "Accept: */*" + ENDL + get += "User-Agent: iTunes/4.6 (Windows; N)" + ENDL + get += "Client-DAAP-Version: 3.0" + ENDL + get += "Client-DAAP-Validation: #{@hash}" + ENDL + get += "Client-DAAP-Access-Index: 2" + ENDL + get += "Client-DAAP-Request-ID: #{@requestId}" + ENDL + get += "Host: #{remote_uri.host}:#{remote_uri.port}" + ENDL + ENDL + get + end +end + +def myputs( string ) + $stdout.puts( "AMAROK_PROXY: #{string}" ) +end + +begin + myputs( ARGV ) + if( ARGV[0] == "--lastfm" ) then + option, port, remote_url, engine, proxy = ARGV + LastFM.new( port, remote_url, engine, proxy ) + else + option, port, remote_url, engine, hash, request_id, proxy = ARGV + DaapProxy.new( port, remote_url, engine, hash, request_id, proxy ) + end +rescue + myputs( $!.to_s ) + myputs( $!.backtrace.inspect ) +end + +puts( "exiting" ) diff --git a/amarok/src/amarokcore/Makefile.am b/amarok/src/amarokcore/Makefile.am new file mode 100644 index 00000000..66e8a691 --- /dev/null +++ b/amarok/src/amarokcore/Makefile.am @@ -0,0 +1,28 @@ +INCLUDES = \ + -I$(top_builddir)/amarok/src \ + -I$(top_builddir)/amarok/src/amarokcore \ + -I$(top_srcdir)/amarok/src \ + -I$(top_srcdir)/amarok/src/engine \ + -I$(top_srcdir)/amarok/src/plugin \ + -I$(top_srcdir)/amarok/src/statusbar \ + -I$(top_srcdir)/amarok/src/mediadevice \ + $(all_includes) + +noinst_LTLIBRARIES = \ + libamarokcore.la + +noinst_HEADERS = \ + amarokconfig.h \ + amarokdcophandler.h + +libamarokcore_la_SOURCES = \ + amarokdcopiface.skel \ + amarokdcophandler.cpp \ + amarokconfig.kcfgc \ + crashhandler.cpp + +METASOURCES = \ + AUTO + +kde_kcfg_DATA = \ + amarok.kcfg diff --git a/amarok/src/amarokcore/amarok.kcfg b/amarok/src/amarokcore/amarok.kcfg new file mode 100644 index 00000000..9083595c --- /dev/null +++ b/amarok/src/amarokcore/amarok.kcfg @@ -0,0 +1,705 @@ + + + +qdir.h + + + + + + + Amarok version string, used to restart aRts in new installations. + + + + + The position of the Amarok main window when Amarok is started. + + + + If set the player window will start in minimal view + false + + + + The position of the playlist window when Amarok is started. + + + + The size of the playlist window when Amarok is started. + + + + If set, Amarok saves the current playlist on quit and restores it when restarted. + true + + + + If set, amarok follows symlinks when adding files or directories to the playlist. + true + + + + Set this to display a second time label to the left of the seek slider in the player window. + true + + + + Set this to display remaining track time instead of past track time in the player window. + false + + + + A score is a number from 0 to 100, determined automatically by Amarok based on how often you listen to a track and how much of it you listen to. + true + + + + A rating is 1 to 5 stars, set manually by you to describe how well you like a given track. + false + + + + Selects whether the user wants to use custom colors for ratings stars. + false + + + + Selects whether the user wants to define a custom color for the half-star. + true + + + + Whether to repeat the current track, the current album, or the current playlist indefinitely, or neither. + + + + + + + + + + + + + + + Off + + + + Tracks or albums with the chosen property will be more likely to be chosen in Random Mode. + + + + + + + Off + + + + If set, Amarok plays the tracks or albums in the playlist in a random order. + + + + + + Off + + + + The title of the Dynamic Mode that was most recently loaded in the playlist + Random Mix + + + + The name of the custom scoring script which was most recently loaded + + + + + Enable/Disable tray icon for Amarok. + true + + + + Enable/Disable tray icon animation. + false + + + + Makes Amarok more like XMMS and other Winamp clones with separate player and playlist windows. + false + + + + Displays a visual representation of the current track in the slider bar of the player window, the playlist window, and in a column of the playlist window. + false + + + + Enabling this option stores Mood data files with the music files. Disabling stores them in your home folder. + false + + + + When enabled, the hue distribution is quantised and respread evenly, giving a prettier but less meaningful output. + true + + + + The hues are distributed according to a colour theme, giving a customisable look. + 0 + + + + At the bottom of the playlist window, the toolbar can be used for buttons such as play and stop. + true + + + + Size of the cover previews in Contextbrowser and Covermanager + 130 + + + + Enable/Disable recursive directory adding to the playlist. + true + + + + Delay between tracks, in milliseconds. + 0 + + + + Enable/Disable the playlist window. Equal to clicking the PL button in the player window. + true + + + + The number of undo levels in the playlist. + 30 + + + + The ID of the visual analyzer to display. + -1 + + + + The ID of the visual analyzer to display in playlist window. + 0 + + + + Currently unused + 70,140 + + + + Enable/Disable splashscreen during Amarok startup. + true + + + + Automatically switches to ContextBrowser when playback is started. + false + + + + Set this to the style dir you want to use. + Default + + + + If set, Amarok's manually saved playlists will contain a relative path to each track, not an absolute path. + true + + + + If set, Organize files will overwrite any existing destination. + false + + + + If set, Organize files will group directories containing the same filetype. + false + + + + If set, Organize files will group artist starting in the same character. + true + + + + If set, Organize files will ignore The in artist names. + true + + + + If set, Organize files will replace spaces in filenames by an underscore. + false + + + + If set, Organize files will use cover art as folder icons. + true + + + + The ID of the collection folder destination for Organize files. + 0 + + + + If set, Organize files will replace characters that are not compatible with vfat filesystems (such as ':', '*' and '?'). + true + + + + If set, Organize files will replace characters that are not compatible with the 7-bit ASCII character set. + false + + + + If set, Organize files will rename files according to a custom format string. + false + + + + If the custom filename scheme is enabled, then Organize files will rename files according to this format string. + %folder/%filetype/%theartist/%album/%track_-_%title.%filetype + + + + Organize files will replace substrings matching this regular expression. + + + + + Organize files will replace matching substrings with this string. + + + + + kfmclient openURL + + + + true + + + + 0 + + + + + + + The Amarok master volume, a value between 0 (muted) and 100. + 80 + 0 + 100 + + + + Enable/Disable crossfading between track changes. + false + + + + The length of the crossfade between tracks in milliseconds. + 4000 + 400 + + + + Determines whether to crossfade always, or on automatic/manual track changes only. + 0 + + + + Enable/Disable fadeout. + true + + + + The length of the fadeout in milliseconds. + 3500 + 400 + + + + true + + + + Select the sound system used to play media. Amarok currently support aRts, GStreamer, xine, and NMM; however, their availability depends on the configuration used at compile time. + + + + When enabled, an equalizer plugin filters the audio stream. + false + + + + 0 + -100 + 100 + + + + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + + + + Zero + + + + + + + Determines which Amazon server cover images should be retrieved from. + us + + + + + + + Determines in which language the information is retrieved from Wikipedia. + en + + + + + + + Enable/Disable the On-Screen Display. + true + + + + If enabled, the OSD will display the same information and in the same order as the columns in the playlist. + true + + + + Customize the OSD display text. + %artist - %title {(%length)}\n%album + + + + The font to use for the On-Screen Display. + QFont("Arial",16) + + + + Draws a shadow around the OSD-text. + true + + + + Make the background of the OSD use fake-translucency. + true + + + + You can use custom colors for the OSD if you set this true. + false + + + + The color of the OSD text. The color is specified in RGB, a comma-separated list containing three integers between 0 and 255. + #ffff00 + + + + The color of the OSD background. The color is specified in RGB, a comma-separated list containing three integers between 0 and 255. + #1500a0 + + + + #0000ff + + + + The time in milliseconds to show the OSD. A value of 0 means never hide. The default value is 5000 ms. + 5000 + 0 + + + + The Y position of the OSD relative to the chosen screen and OSD alignment. If Top alignment is chosen the Y offset is the space between the upper part of the OSD and the top of the screen. If Bottom alignment is chosen the Y offset is the space between the bottom part of the OSD and the bottom of the screen. + 50 + 0 + 10000 + + + + The screen that should display the OSD. For single-headed environments this setting should be 0. + 0 + + + + If enabled, shows the album cover in the OSD. + true + + + + The relative position of the OSD. Possible choices are Left, Middle, Right and Center. + + + + + + + Center + + + + + + + + Enabled/Disables custom fonts. + false + + + + The font to use in the playlist window. + + + + The font to use in the player window. + + + + The font to use in the context browser. + + + + + + + If set, Amarok uses the Amarok standard colors in the playlist. + false + + + + If set, Amarok uses the KDE standard colors in the playlist. + true + + + + If set, Amarok uses the user-defined colors in the playlist. + false + + + + The color to use as foreground color in the playlist. The color is specified in RGB, a comma-separated list containing three integers between 0 and 255. + #80a0ff + + + + The color to use as background color in the playlist. The color is specified in RGB, a comma-separated list containing three integers between 0 and 255. + #000000 + + + + The color to use for a half rating star, if not the default. + + + + The color to use for a single rating star, if not the default. + + + + The color to use for two rating stars, if not the default. + + + + The color to use for three rating stars, if not the default. + + + + The color to use for four rating stars, if not the default. + + + + The color to use for five rating stars, if not the default. + + + + + + + If set, Amarok resumes playback of the last played track on startup. + false + + + + Internal: URL of the track to resume on startup. + + + + Internal: Playback position in the track to resume on startup. + + + + + + + The database engine used to store collection + SQLite + + + + true + + + + true + + + + + + + + + + The host MySql server is running on + localhost + + + + The port MySql server is listening + 3306 + + + + The database's name + amarok + + + + The user's name to use for connecting MySql + + + + The user's password + + + + The user's password + + + + + + + The host Postgresql server is running on + localhost + + + + The port Postgresql server is listening + 5432 + + + + The database's name + amarok + + + + The user's name to use for connecting Postgresql + amarok + + + + The user's password + + + + The user's password + + + + + + + Whether played songs are submitted to Audioscrobbler + false + + + + The username to use for connecting to Audioscrobbler + + + + + The password to use for connecting to Audioscrobbler + + + + + Whether similar songs are retrieved from Audioscrobbler + false + + + + + + + The type of media device. + + + + The mount point used for the media device connection. + + + + The mount command used for the media device connection. + + + + The umount command used for the media device connection. + + + + Whether podcasts shows already played are automatically deleted when media device is connected. + + + + Whether Amarok statistics should be synchronized with play count/ratings on device and whether tracks played should be submitted to last.fm. + + + + Whether to automatically try to connect media device when starting Amarok. + true + + + + + + + Music Sharing servers added by the user. + + + + + Passwords stored by hostname. + + + + diff --git a/amarok/src/amarokcore/amarokconfig.kcfgc b/amarok/src/amarokcore/amarokconfig.kcfgc new file mode 100644 index 00000000..f8a0c8fd --- /dev/null +++ b/amarok/src/amarokcore/amarokconfig.kcfgc @@ -0,0 +1,8 @@ +# Code generation options for kconfig_compiler +File=amarok.kcfg +ClassName=AmarokConfig +Singleton=true +Mutators=true +MemberVariables=private +Visibility=LIBAMAROK_EXPORT +IncludeFiles=amarok_export.h diff --git a/amarok/src/amarokcore/amarokdcophandler.cpp b/amarok/src/amarokcore/amarokdcophandler.cpp new file mode 100644 index 00000000..740f9d6f --- /dev/null +++ b/amarok/src/amarokcore/amarokdcophandler.cpp @@ -0,0 +1,1059 @@ +/*************************************************************************** + amarokdcophandler.cpp - DCOP Implementation + ------------------- + begin : Sat Oct 11 2003 + copyright : (C) 2003 by Stanislav Karchebny + (C) 2004 Christian Muehlhaeuser + (C) 2005 Ian Monroe + (C) 2005 Seb Ruiz + (C) 2006 Alexandre Oliveira + email : berkus@users.sf.net +***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "amarok.h" +#include "amarokconfig.h" +#include "amarokdcophandler.h" +#include "app.h" //transferCliArgs +#include "debug.h" +#include "collectiondb.h" +#include "contextbrowser.h" +#include "devicemanager.h" +#include "enginebase.h" +#include "enginecontroller.h" +#include "equalizersetup.h" +#include "htmlview.h" +#include "mediabrowser.h" +#include "mountpointmanager.h" +#include "osd.h" +#include "playlist.h" +#include "playlistbrowser.h" +#include "playlistitem.h" +#include "playlistwindow.h" +#include "scancontroller.h" +#include "scriptmanager.h" +#include "statusbar.h" +#include "lastfm.h" + +#include + +#include +#include +#include + + +namespace Amarok +{ +///////////////////////////////////////////////////////////////////////////////////// +// class DcopPlayerHandler +///////////////////////////////////////////////////////////////////////////////////// + + DcopPlayerHandler::DcopPlayerHandler() + : DCOPObject( "player" ) + , QObject( kapp ) + { + // Register with DCOP + if ( !kapp->dcopClient()->isRegistered() ) { + kapp->dcopClient()->registerAs( "amarok", false ); + kapp->dcopClient()->setDefaultObject( objId() ); + } + } + + QString DcopPlayerHandler::version() + { + return APP_VERSION; + } + + bool DcopPlayerHandler::dynamicModeStatus() + { + return Amarok::dynamicMode(); + } + + bool DcopPlayerHandler::equalizerEnabled() + { + if(EngineController::hasEngineProperty( "HasEqualizer" )) + return AmarokConfig::equalizerEnabled(); + else + return false; + } + + bool DcopPlayerHandler::osdEnabled() + { + return AmarokConfig::osdEnabled(); + } + + bool DcopPlayerHandler::isPlaying() + { + return EngineController::engine()->state() == Engine::Playing; + } + + bool DcopPlayerHandler::randomModeStatus() + { + return AmarokConfig::randomMode(); + } + + bool DcopPlayerHandler::repeatPlaylistStatus() + { + return Amarok::repeatPlaylist(); + } + + bool DcopPlayerHandler::repeatTrackStatus() + { + return Amarok::repeatTrack(); + } + + int DcopPlayerHandler::getVolume() + { + return EngineController::engine() ->volume(); + } + + int DcopPlayerHandler::sampleRate() + { + return EngineController::instance()->bundle().sampleRate(); + } + + float DcopPlayerHandler::score() + { + const MetaBundle &bundle = EngineController::instance()->bundle(); + float score = CollectionDB::instance()->getSongPercentage( bundle.url().path() ); + return score; + } + + int DcopPlayerHandler::rating() + { + const MetaBundle &bundle = EngineController::instance()->bundle(); + int rating = CollectionDB::instance()->getSongRating( bundle.url().path() ); + return rating; + } + + int DcopPlayerHandler::status() + { + // <0 - error, 0 - stopped, 1 - paused, 2 - playing + switch( EngineController::engine()->state() ) + { + case Engine::Playing: + return 2; + case Engine::Paused: + return 1; + case Engine::Empty: + case Engine::Idle: + return 0; + } + return -1; + } + + int DcopPlayerHandler::trackCurrentTime() + { + return EngineController::instance()->trackPosition() / 1000; + } + + int DcopPlayerHandler::trackCurrentTimeMs() + { + return EngineController::instance()->trackPosition(); + } + + int DcopPlayerHandler::trackPlayCounter() + { + const MetaBundle &bundle = EngineController::instance()->bundle(); + int count = CollectionDB::instance()->getPlayCount( bundle.url().path() ); + return count; + } + + int DcopPlayerHandler::trackTotalTime() + { + return EngineController::instance()->bundle().length(); + } + + QStringList DcopPlayerHandler::labels() + { + const MetaBundle &bundle = EngineController::instance()->bundle(); + return CollectionDB::instance()->getLabels( bundle.url().path(), CollectionDB::typeUser ); + } + + QString DcopPlayerHandler::album() + { + return EngineController::instance()->bundle().album(); + } + + QString DcopPlayerHandler::artist() + { + return EngineController::instance()->bundle().artist(); + } + + QString DcopPlayerHandler::bitrate() + { + return EngineController::instance()->bundle().prettyBitrate(); + } + + QString DcopPlayerHandler::comment() + { + return EngineController::instance()->bundle().comment(); + } + + QString DcopPlayerHandler::coverImage() + { + const MetaBundle &bundle = EngineController::instance()->bundle(); + QString image = CollectionDB::instance()->albumImage( bundle, 0 ); + return image; + } + + QString DcopPlayerHandler::currentTime() + { + return MetaBundle::prettyLength( EngineController::instance()->trackPosition() / 1000 ,true ); + } + + QString DcopPlayerHandler::encodedURL() + { + return EngineController::instance()->bundle().url().url(); + } + + QString DcopPlayerHandler::engine() + { + return AmarokConfig::soundSystem(); + } + + QString DcopPlayerHandler::genre() + { + return EngineController::instance()->bundle().genre(); + } + + QString DcopPlayerHandler::lyrics() + { + return CollectionDB::instance()->getLyrics( EngineController::instance()->bundle().url().path() ); + } + + QString DcopPlayerHandler::lyricsByPath( QString path ) + { + return CollectionDB::instance()->getLyrics( path ); + } + + QString DcopPlayerHandler::lastfmStation() + { + return LastFm::Controller::stationDescription(); //return QString::null if not playing + } + + QString DcopPlayerHandler::nowPlaying() + { + return EngineController::instance()->bundle().prettyTitle(); + } + + QString DcopPlayerHandler::path() + { + return EngineController::instance()->bundle().url().path(); + } + + QString DcopPlayerHandler::setContextStyle(const QString& msg) + { + AmarokConfig::setContextBrowserStyleSheet( msg ); + ContextBrowser::instance()->reloadStyleSheet(); + + if ( QFile::exists( Amarok::saveLocation( "themes/" + msg + '/' ) + "stylesheet.css" ) ) + return "Context browser theme '"+msg+"' applied."; + else + return "No such theme '"+msg+"' exists, default theme applied."; + } + + QString DcopPlayerHandler::title() + { + return EngineController::instance()->bundle().title(); + } + + QString DcopPlayerHandler::totalTime() + { + return EngineController::instance()->bundle().prettyLength(); + } + + QString DcopPlayerHandler::track() + { + if ( EngineController::instance()->bundle().track() != 0 ) + return QString::number( EngineController::instance()->bundle().track() ); + else + return QString(); + } + + QString DcopPlayerHandler::type() + { + if (EngineController::instance()->bundle().url().protocol() == "lastfm") + return QString("LastFm Stream"); + else + return EngineController::instance()->bundle().type(); + } + + QString DcopPlayerHandler::year() + { + return QString::number( EngineController::instance()->bundle().year() ); + } + + void DcopPlayerHandler::configEqualizer() + { + if(EngineController::hasEngineProperty( "HasEqualizer" )) + EqualizerSetup::instance()->show(); + EqualizerSetup::instance()->raise(); + } + + void DcopPlayerHandler::enableOSD(bool enable) + { + Amarok::OSD::instance()->setEnabled( enable ); + AmarokConfig::setOsdEnabled( enable ); + } + + void DcopPlayerHandler::enableRandomMode( bool enable ) + { + static_cast(Amarok::actionCollection()->action( "random_mode" )) + ->setCurrentItem( enable ? AmarokConfig::EnumRandomMode::Tracks : AmarokConfig::EnumRandomMode::Off ); + } + + void DcopPlayerHandler::enableRepeatPlaylist( bool enable ) + { + static_cast( Amarok::actionCollection()->action( "repeat" ) ) + ->setCurrentItem( enable ? AmarokConfig::EnumRepeat::Playlist : AmarokConfig::EnumRepeat::Off ); + } + + void DcopPlayerHandler::enableRepeatTrack( bool enable) + { + static_cast( Amarok::actionCollection()->action( "repeat" ) ) + ->setCurrentItem( enable ? AmarokConfig::EnumRepeat::Track : AmarokConfig::EnumRepeat::Off ); + } + + void DcopPlayerHandler::mediaDeviceMount() + { + if ( MediaBrowser::instance()->currentDevice() ) + MediaBrowser::instance()->currentDevice()->connectDevice(); + } + + void DcopPlayerHandler::mediaDeviceUmount() + { + if ( MediaBrowser::instance()->currentDevice() ) + MediaBrowser::instance()->currentDevice()->disconnectDevice(); + } + + void DcopPlayerHandler::mute() + { + EngineController::instance()->mute(); + } + + void DcopPlayerHandler::next() + { + EngineController::instance() ->next(); + } + + void DcopPlayerHandler::pause() + { + EngineController::instance()->pause(); + } + + void DcopPlayerHandler::play() + { + EngineController::instance() ->play(); + } + + void DcopPlayerHandler::playPause() + { + EngineController::instance() ->playPause(); + } + + void DcopPlayerHandler::prev() + { + EngineController::instance() ->previous(); + } + + void DcopPlayerHandler::queueForTransfer( KURL url ) + { + MediaBrowser::queue()->addURL( url ); + MediaBrowser::queue()->URLsAdded(); + } + + void DcopPlayerHandler::seek(int s) + { + if ( s > 0 && EngineController::engine()->state() != Engine::Empty ) + EngineController::instance()->seek( s * 1000 ); + } + + void DcopPlayerHandler::seekRelative(int s) + { + EngineController::instance() ->seekRelative( s * 1000 ); + } + + void DcopPlayerHandler::setEqualizer(int preamp, int band60, int band170, int band310, + int band600, int band1k, int band3k, int band6k, int band12k, int band14k, int band16k) + { + if( EngineController::hasEngineProperty( "HasEqualizer" ) ) { + bool instantiated = EqualizerSetup::isInstantiated(); + EqualizerSetup* eq = EqualizerSetup::instance(); + + QValueList gains; + gains << band60 << band170 << band310 << band600 << band1k + << band3k << band6k << band12k << band14k << band16k; + + eq->setBands( preamp, gains ); + if( !instantiated ) + delete eq; + } + } + + void DcopPlayerHandler::setEqualizerEnabled( bool active ) + { + EngineController::engine()->setEqualizerEnabled( active ); + AmarokConfig::setEqualizerEnabled( active ); + + if( EqualizerSetup::isInstantiated() ) + EqualizerSetup::instance()->setActive( active ); + } + + void DcopPlayerHandler::setEqualizerPreset( QString name ) + { + if( EngineController::hasEngineProperty( "HasEqualizer" ) ) { + bool instantiated = EqualizerSetup::isInstantiated(); + EqualizerSetup* eq = EqualizerSetup::instance(); + eq->setPreset( name ); + if ( !instantiated ) + delete eq; + } + } + + void DcopPlayerHandler::setLyricsByPath( const QString& url, const QString& lyrics ) + { + CollectionDB::instance()->setLyrics( url, lyrics ); + } + + void DcopPlayerHandler::setScore( float score ) + { + const QString &url = EngineController::instance()->bundle().url().path(); + CollectionDB::instance()->setSongPercentage(url, score); + } + + void DcopPlayerHandler::setScoreByPath( const QString &url, float score ) + { + CollectionDB::instance()->setSongPercentage(url, score); + } + + void DcopPlayerHandler::setBpm( float bpm ) + { + MetaBundle bundle = EngineController::instance()->bundle(); + bundle.setBpm( bpm ); + bundle.save(); + CollectionDB::instance()->updateTags( bundle.url().path(), bundle, true ); + } + + void DcopPlayerHandler::setBpmByPath( const QString &url, float bpm ) + { + MetaBundle bundle( url ); + bundle.setBpm(bpm); + bundle.save(); + CollectionDB::instance()->updateTags( bundle.url().path(), bundle, true ); + } + + void DcopPlayerHandler::setRating( int rating ) + { + const QString &url = EngineController::instance()->bundle().url().path(); + CollectionDB::instance()->setSongRating(url, rating); + } + + void DcopPlayerHandler::setRatingByPath( const QString &url, int rating ) + { + CollectionDB::instance()->setSongRating(url, rating); + } + + void DcopPlayerHandler::setVolume(int volume) + { + EngineController::instance()->setVolume(volume); + } + + void DcopPlayerHandler::setVolumeRelative(int ticks) + { + EngineController::instance()->increaseVolume(ticks); + } + + void DcopPlayerHandler::showBrowser( QString browser ) + { + if ( browser == "context" ) + PlaylistWindow::self()->showBrowser( "ContextBrowser" ); + if ( browser == "collection" ) + PlaylistWindow::self()->showBrowser( "CollectionBrowser" ); + if ( browser == "playlist" ) + PlaylistWindow::self()->showBrowser( "PlaylistBrowser" ); + if ( browser == "media" ) + PlaylistWindow::self()->showBrowser( "MediaBrowser" ); + if ( browser == "file" ) + PlaylistWindow::self()->showBrowser( "FileBrowser" ); + } + + void DcopPlayerHandler::showOSD() + { + Amarok::OSD::instance()->forceToggleOSD(); + } + + void DcopPlayerHandler::stop() + { + EngineController::instance() ->stop(); + } + + void DcopPlayerHandler::transferDeviceFiles() + { + if ( MediaBrowser::instance()->currentDevice() ) + MediaBrowser::instance()->currentDevice()->transferFiles(); + } + + void DcopPlayerHandler::volumeDown() + { + EngineController::instance()->decreaseVolume(); + } + + void DcopPlayerHandler::volumeUp() + { + EngineController::instance()->increaseVolume(); + } + + void DcopPlayerHandler::transferCliArgs( QStringList args ) + { + DEBUG_BLOCK + + //stop startup cursor animation - do not mess with this, it's carefully crafted + //NOTE I have no idea why we need to do this, I never get startup notification from + //the amarok binary anyway --mxcl + debug() << "Startup ID: " << args.first() << endl; + kapp->setStartupId( args.first().local8Bit() ); +#ifdef Q_WS_X11 + // currently X11 only + KStartupInfo::appStarted(); +#endif + args.pop_front(); + + const int argc = args.count() + 1; + char **argv = new char*[argc]; + + QStringList::ConstIterator it = args.constBegin(); + for( int i = 1; i < argc; ++i, ++it ) { + argv[i] = qstrdup( (*it).local8Bit() ); + debug() << "Extracted: " << argv[i] << endl; + } + + // required, loader doesn't add it + argv[0] = qstrdup( "amarokapp" ); + + // re-initialize KCmdLineArgs with the new arguments + App::initCliArgs( argc, argv ); + App::handleCliArgs(); + + //FIXME are we meant to leave this around? + //FIXME are we meant to allocate it all on the heap? + //NOTE we allow the memory leak because I think there are + // some very mysterious crashes due to deleting this + //delete[] argv; + } + +///////////////////////////////////////////////////////////////////////////////////// +// class DcopPlaylistHandler +///////////////////////////////////////////////////////////////////////////////////// + + DcopPlaylistHandler::DcopPlaylistHandler() + : DCOPObject( "playlist" ) + , QObject( kapp ) + {} + + int DcopPlaylistHandler::getActiveIndex() + { + return Playlist::instance()->currentTrackIndex( false ); + } + + int DcopPlaylistHandler::getTotalTrackCount() + { + return Playlist::instance()->totalTrackCount(); + } + + QString DcopPlaylistHandler::saveCurrentPlaylist() + { + Playlist::instance()->saveXML( Playlist::defaultPlaylistPath() ); + return Playlist::defaultPlaylistPath(); + } + + void DcopPlaylistHandler::addMedia(const KURL &url) + { + Playlist::instance()->appendMedia(url); + } + + void DcopPlaylistHandler::addMediaList(const KURL::List &urls) + { + Playlist::instance()->insertMedia(urls); + } + + void DcopPlaylistHandler::queueMedia(const KURL &url) + { + Playlist::instance()->insertMedia(KURL::List( url ), Playlist::Queue); + } + + void DcopPlaylistHandler::clearPlaylist() + { + Playlist::instance()->clear(); + } + + void DcopPlaylistHandler::playByIndex(int index) + { + Playlist::instance()->activateByIndex( index ); + } + + void DcopPlaylistHandler::playMedia( const KURL &url ) + { + Playlist::instance()->insertMedia( url, Playlist::DirectPlay | Playlist::Unique); + } + + void DcopPlaylistHandler::popupMessage( const QString& msg ) + { + StatusBar::instance()->longMessageThreadSafe( msg ); + } + + void DcopPlaylistHandler::removeCurrentTrack() + { + PlaylistItem* const item = Playlist::instance()->currentTrack(); + if ( item ) { + if( item->isBeingRenamed() ) + item->setDeleteAfterEditing( true ); + else + { + Playlist::instance()->removeItem( item ); + delete item; + } + } + } + + void DcopPlaylistHandler::removeByIndex( int index ) + { + PlaylistItem* const item = + static_cast( Playlist::instance()->itemAtIndex( index ) ); + + if ( item ) { + Playlist::instance()->removeItem( item ); + delete item; + } + } + + void DcopPlaylistHandler::repopulate() + { + Playlist::instance()->repopulate(); + } + + void DcopPlaylistHandler::saveM3u( const QString& path, bool relativePaths ) + { + Playlist::instance()->saveM3U( path, relativePaths ); + } + + void DcopPlaylistHandler::setStopAfterCurrent( bool on ) + { + Playlist::instance()->setStopAfterCurrent( on ); + } + + void DcopPlaylistHandler::shortStatusMessage(const QString& msg) + { + StatusBar::instance()->shortMessage( msg ); + } + + void DcopPlaylistHandler::shufflePlaylist() + { + Playlist::instance()->shuffle(); + } + + void DcopPlaylistHandler::togglePlaylist() + { + PlaylistWindow::self()->showHide(); + } + + QStringList DcopPlaylistHandler::filenames() + { + Playlist *p_inst = Playlist::instance(); + QStringList songlist; + + if (!p_inst) + return songlist; + + PlaylistItem *p_item = p_inst->firstChild(); + + while (p_item) + { + songlist.append(p_item->filename()); + p_item = p_item->nextSibling(); + } + + return songlist; + } + + QString DcopPlaylistHandler::currentTrackUniqueId() + { + if( Playlist::instance()->currentItem() ) + return Playlist::instance()->currentItem()->uniqueId(); + return QString(); + } + +///////////////////////////////////////////////////////////////////////////////////// +// class DcopPlaylistBrowserHandler +///////////////////////////////////////////////////////////////////////////////////// + + DcopPlaylistBrowserHandler::DcopPlaylistBrowserHandler() + : DCOPObject( "playlistbrowser" ) + , QObject( kapp ) + {} + + void DcopPlaylistBrowserHandler::addPodcast( const QString &url ) + { + PlaylistBrowser::instance()->addPodcast( url ); + } + + void DcopPlaylistBrowserHandler::scanPodcasts() + { + PlaylistBrowser::instance()->scanPodcasts(); + } + + void DcopPlaylistBrowserHandler::addPlaylist( const QString &url ) + { + PlaylistBrowser::instance()->addPlaylist( url ); + } + + int DcopPlaylistBrowserHandler::loadPlaylist( const QString &playlist ) + { + return PlaylistBrowser::instance()->loadPlaylist( playlist ); + } + +///////////////////////////////////////////////////////////////////////////////////// +// class DcopContextBrowserHandler +///////////////////////////////////////////////////////////////////////////////////// + + DcopContextBrowserHandler::DcopContextBrowserHandler() + : DCOPObject( "contextbrowser" ) + , QObject( kapp ) + {} + + void DcopContextBrowserHandler::showCurrentTrack() + { + ContextBrowser::instance()->showCurrentTrack(); + } + + void DcopContextBrowserHandler::showLyrics() + { + ContextBrowser::instance()->showLyrics(); + } + + void DcopContextBrowserHandler::showWiki() + { + ContextBrowser::instance()->showWikipedia(); + } + + void DcopContextBrowserHandler::showLyrics( const QCString& lyrics ) + { + ContextBrowser::instance()->lyricsResult( lyrics ); + } + +///////////////////////////////////////////////////////////////////////////////////// +// class DcopCollectionHandler +///////////////////////////////////////////////////////////////////////////////////// + + DcopCollectionHandler::DcopCollectionHandler() + : DCOPObject( "collection" ) + , QObject( kapp ) + {} + + int DcopCollectionHandler::totalAlbums() + { + QStringList albums = CollectionDB::instance()->query( "SELECT COUNT( id ) FROM album;" ); + QString total = albums[0]; + return total.toInt(); + } + + int DcopCollectionHandler::totalArtists() + { + QStringList artists = CollectionDB::instance()->query( "SELECT COUNT( id ) FROM artist;" ); + QString total = artists[0]; + return total.toInt(); + } + + int DcopCollectionHandler::totalComposers() + { + QStringList composers = CollectionDB::instance()->query( "SELECT COUNT( id ) FROM composer;" ); + QString total = composers[0]; + return total.toInt(); + } + + int DcopCollectionHandler::totalCompilations() + { + QStringList comps = CollectionDB::instance()->query( "SELECT COUNT( DISTINCT album ) FROM tags WHERE sampler = 1;" ); + QString total = comps[0]; + return total.toInt(); + } + + int DcopCollectionHandler::totalGenres() + { + QStringList genres = CollectionDB::instance()->query( "SELECT COUNT( id ) FROM genre;" ); + QString total = genres[0]; + return total.toInt(); + } + + int DcopCollectionHandler::totalTracks() + { + QStringList tracks = CollectionDB::instance()->query( "SELECT COUNT( url ) FROM tags;" ); + QString total = tracks[0]; + int final = total.toInt(); + return final; + } + + bool DcopCollectionHandler::isDirInCollection( const QString& path ) + { + return CollectionDB::instance()->isDirInCollection( path ); + } + + bool DcopCollectionHandler::moveFile( const QString &oldURL, const QString &newURL, bool overwrite ) + { + return CollectionDB::instance()->moveFile( oldURL, newURL, overwrite ); + } + + QStringList DcopCollectionHandler::query( const QString& sql ) + { + return CollectionDB::instance()->query( sql ); + } + + QStringList DcopCollectionHandler::similarArtists( int artists ) + { + return CollectionDB::instance()->similarArtists( EngineController::instance()->bundle().artist(), artists ); + } + + void DcopCollectionHandler::migrateFile( const QString &oldURL, const QString &newURL ) + { + CollectionDB::instance()->migrateFile( oldURL, newURL ); + } + + void DcopCollectionHandler::scanCollection() + { + CollectionDB::instance()->startScan(); + } + + void DcopCollectionHandler::scanCollectionChanges() + { + CollectionDB::instance()->scanModifiedDirs(); + } + + void DcopCollectionHandler::scanPause() + { + if( ScanController::instance() ) + ScanController::instance()->requestPause(); + else + debug() << "No ScanController instance available" << endl; + } + + void DcopCollectionHandler::scanUnpause() + { + if( ScanController::instance() ) + ScanController::instance()->requestUnpause(); + else + debug() << "No ScanController instance available" << endl; + } + + void DcopCollectionHandler::scannerAcknowledged() + { + DEBUG_BLOCK + if( ScanController::instance() ) + ScanController::instance()->requestAcknowledged(); + else + debug() << "No ScanController instance available" << endl; + } + + int DcopCollectionHandler::addLabels( const QString &url, const QStringList &labels ) + { + CollectionDB *db = CollectionDB::instance(); + QString uid = db->getUniqueId( url ); + int count = 0; + foreach( labels ) + { + if( db->addLabel( url, *it, uid , CollectionDB::typeUser ) ) + count++; + } + return count; + } + + void DcopCollectionHandler::removeLabels( const QString &url, const QStringList &oldLabels ) + { + CollectionDB::instance()->removeLabels( url, oldLabels, CollectionDB::typeUser ); + } + + void DcopCollectionHandler::disableAutoScoring( bool disable ) + { + CollectionDB::instance()->disableAutoScoring( disable ); + } + + int DcopCollectionHandler::deviceId( const QString &url ) + { + return MountPointManager::instance()->getIdForUrl( url ); + } + + QString DcopCollectionHandler::relativePath( const QString &url ) + { + int deviceid = deviceId( url ); + return MountPointManager::instance()->getRelativePath( deviceid, url ); + } + + QString DcopCollectionHandler::absolutePath( int deviceid, const QString &relativePath ) + { + return MountPointManager::instance()->getAbsolutePath( deviceid, relativePath ); + } + +///////////////////////////////////////////////////////////////////////////////////// +// class DcopScriptHandler +///////////////////////////////////////////////////////////////////////////////////// + + DcopScriptHandler::DcopScriptHandler() + : DCOPObject( "script" ) + , QObject( kapp ) + {} + + bool DcopScriptHandler::runScript(const QString& name) + { + return ScriptManager::instance()->runScript(name); + } + + bool DcopScriptHandler::stopScript(const QString& name) + { + return ScriptManager::instance()->stopScript(name); + } + + QStringList DcopScriptHandler::listRunningScripts() + { + return ScriptManager::instance()->listRunningScripts(); + } + + void DcopScriptHandler::addCustomMenuItem(QString submenu, QString itemTitle ) + { + Playlist::instance()->addCustomMenuItem( submenu, itemTitle ); + } + + void DcopScriptHandler::removeCustomMenuItem(QString submenu, QString itemTitle ) + { + Playlist::instance()->removeCustomMenuItem( submenu, itemTitle ); + } + + QString DcopScriptHandler::readConfig(const QString& key) + { + QString cleanKey = key; + KConfigSkeletonItem* configItem = AmarokConfig::self()->findItem(cleanKey.remove(' ')); + if (configItem) + return configItem->property().toString(); + else + return QString(); + } + + QStringList DcopScriptHandler::readListConfig(const QString& key) + { + QString cleanKey = key; + KConfigSkeletonItem* configItem = AmarokConfig::self()->findItem(cleanKey.remove(' ')); + QStringList stringList; + if(configItem) + { + QValueList variantList = configItem->property().toList(); + QValueList::Iterator it = variantList.begin(); + while(it != variantList.end()) + { + stringList << (*it).toString(); + ++it; + } + } + return stringList; + } + + QString DcopScriptHandler::proxyForUrl(const QString& url) + { + return Amarok::proxyForUrl( url ); + } + + QString DcopScriptHandler::proxyForProtocol(const QString& protocol) + { + return Amarok::proxyForProtocol( protocol ); + } + +///////////////////////////////////////////////////////////////////////////////////// +// class DcopDevicesHandler +///////////////////////////////////////////////////////////////////////////////////// + + DcopDevicesHandler::DcopDevicesHandler() + : DCOPObject( "devices" ) + , QObject( kapp ) + {} + + void DcopDevicesHandler::mediumAdded(QString name) + { + DeviceManager::instance()->mediumAdded(name); + } + + void DcopDevicesHandler::mediumRemoved(QString name) + { + DeviceManager::instance()->mediumRemoved(name); + } + + void DcopDevicesHandler::mediumChanged(QString name) + { + DeviceManager::instance()->mediumChanged(name); + } + + QStringList DcopDevicesHandler::showDeviceList() + { + return DeviceManager::instance()->getDeviceStringList(); + } + +///////////////////////////////////////////////////////////////////////////////////// +// class DcopDevicesHandler +///////////////////////////////////////////////////////////////////////////////////// + + DcopMediaBrowserHandler::DcopMediaBrowserHandler() + : DCOPObject( "mediabrowser" ) + , QObject( kapp ) + {} + + void DcopMediaBrowserHandler::deviceConnect() + { + if ( MediaBrowser::instance()->currentDevice() ) + MediaBrowser::instance()->currentDevice()->connectDevice(); + } + + void DcopMediaBrowserHandler::deviceDisconnect() + { + if ( MediaBrowser::instance()->currentDevice() ) + MediaBrowser::instance()->currentDevice()->disconnectDevice(); + } + + QStringList DcopMediaBrowserHandler::deviceList() + { + return MediaBrowser::instance()->deviceNames(); + } + + void DcopMediaBrowserHandler::deviceSwitch( QString name ) + { + MediaBrowser::instance()->deviceSwitch( name ); + } + + void DcopMediaBrowserHandler::queue( KURL url ) + { + MediaBrowser::queue()->addURL( url ); + MediaBrowser::queue()->URLsAdded(); + } + + void DcopMediaBrowserHandler::queueList( KURL::List urls ) + { + MediaBrowser::queue()->addURLs( urls ); + } + + void DcopMediaBrowserHandler::transfer() + { + if ( MediaBrowser::instance()->currentDevice() ) + MediaBrowser::instance()->currentDevice()->transferFiles(); + } + + void DcopMediaBrowserHandler::transcodingFinished( QString src, QString dest ) + { + MediaBrowser::instance()->transcodingFinished( src, dest ); + } + +} //namespace Amarok + +#include "amarokdcophandler.moc" diff --git a/amarok/src/amarokcore/amarokdcophandler.h b/amarok/src/amarokcore/amarokdcophandler.h new file mode 100644 index 00000000..98117b5f --- /dev/null +++ b/amarok/src/amarokcore/amarokdcophandler.h @@ -0,0 +1,261 @@ +/*************************************************************************** + amarokdcophandler.h - DCOP Implementation + ------------------- + begin : Sat Oct 11 2003 + copyright : (C) 2003 by Stanislav Karchebny + (C) 2005 Ian Monroe + (C) 2005 Seb Ruiz + email : berkus@users.sf.net + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef AMAROK_DCOP_HANDLER_H +#define AMAROK_DCOP_HANDLER_H + +#include +#include "amarokdcopiface.h" +class AmarokConfig; + +namespace Amarok +{ + +class DcopPlayerHandler : public QObject, virtual public AmarokPlayerInterface +{ + Q_OBJECT + + public: + DcopPlayerHandler(); + + public: + virtual QString version(); + virtual bool dynamicModeStatus(); + virtual bool equalizerEnabled(); + virtual bool osdEnabled(); + virtual bool isPlaying(); + virtual bool randomModeStatus(); + virtual bool repeatPlaylistStatus(); + virtual bool repeatTrackStatus(); + virtual int getVolume(); + virtual int sampleRate(); + virtual float score (); + virtual int rating (); + virtual int status(); + virtual int trackCurrentTime(); + virtual int trackCurrentTimeMs(); + virtual int trackPlayCounter(); + virtual int trackTotalTime(); + virtual QStringList labels(); + virtual QString album(); + virtual QString artist(); + virtual QString bitrate(); + virtual QString comment(); + virtual QString coverImage(); + virtual QString currentTime(); + virtual QString encodedURL(); + virtual QString engine(); + virtual QString genre(); + virtual QString lyrics(); + virtual QString lyricsByPath( QString path ); + virtual QString lastfmStation(); + virtual QString nowPlaying(); + virtual QString path(); + virtual QString setContextStyle(const QString&); + virtual QString title(); + virtual QString totalTime(); + virtual QString track(); + virtual QString type(); + virtual QString year(); + virtual void configEqualizer(); + virtual void enableOSD( bool enable ); + virtual void enableRandomMode( bool enable ); + virtual void enableRepeatPlaylist( bool enable ); + virtual void enableRepeatTrack( bool enable ); + virtual void mediaDeviceMount(); + virtual void mediaDeviceUmount(); + virtual void mute(); + virtual void next(); + virtual void pause(); + virtual void play(); + virtual void playPause(); + virtual void prev(); + virtual void queueForTransfer( KURL url ); + virtual void seek( int s ); + virtual void seekRelative( int s ); + virtual void setEqualizer(int preamp, int band60, int band170, int band310, int band600, int band1k, int band3k, int band6k, int band12k, int band14k, int band16k); + virtual void setEqualizerEnabled( bool active ); + virtual void setEqualizerPreset( QString name ); + virtual void setLyricsByPath( const QString& url, const QString& lyrics ); + virtual void setScore( float score ); + virtual void setScoreByPath( const QString &url, float score ); + virtual void setBpm( float bpm ); + virtual void setBpmByPath( const QString &url, float bpm ); + virtual void setRating( int rating ); + virtual void setRatingByPath( const QString &url, int rating ); + virtual void setVolume( int ); + virtual void setVolumeRelative( int ); + virtual void showBrowser( QString browser ); + virtual void showOSD(); + virtual void stop(); + virtual void volumeDown(); + virtual void volumeUp(); + virtual void transferDeviceFiles(); + + private: + virtual void transferCliArgs( QStringList args ); +}; + + +class DcopPlaylistHandler : public QObject, virtual public AmarokPlaylistInterface +{ + Q_OBJECT + + public: + DcopPlaylistHandler(); + + public: + virtual int getActiveIndex(); + virtual int getTotalTrackCount(); + virtual QString saveCurrentPlaylist(); + virtual void addMedia(const KURL &); + virtual void queueMedia(const KURL &); + virtual void addMediaList(const KURL::List &); + virtual void clearPlaylist(); + virtual QString currentTrackUniqueId(); + virtual void playByIndex(int); + virtual void playMedia(const KURL &); + virtual void popupMessage(const QString&); + virtual void removeCurrentTrack(); + virtual void removeByIndex(int); + virtual void repopulate(); + virtual void saveM3u(const QString& path, bool relativePaths); + virtual void setStopAfterCurrent(bool); + virtual void shortStatusMessage(const QString&); + virtual void shufflePlaylist(); + virtual void togglePlaylist(); + virtual QStringList filenames(); +}; + +class DcopPlaylistBrowserHandler : public QObject, virtual public AmarokPlaylistBrowserInterface +{ + Q_OBJECT + + public: + DcopPlaylistBrowserHandler(); + + public: + virtual void addPodcast( const QString &url ); + virtual void scanPodcasts(); + virtual void addPlaylist( const QString &url ); + virtual int loadPlaylist( const QString &playlist ); +}; + +class DcopContextBrowserHandler : public QObject, virtual public AmarokContextBrowserInterface +{ + Q_OBJECT + + public: + DcopContextBrowserHandler(); + + public: + virtual void showCurrentTrack(); + virtual void showLyrics(); + virtual void showWiki(); + virtual void showLyrics( const QCString& lyrics ); +}; + + +class DcopCollectionHandler : public QObject, virtual public AmarokCollectionInterface +{ + Q_OBJECT + + public: + DcopCollectionHandler(); + + public /* DCOP */ slots: + virtual int totalAlbums(); + virtual int totalArtists(); + virtual int totalComposers(); + virtual int totalCompilations(); + virtual int totalGenres(); + virtual int totalTracks(); + virtual bool isDirInCollection( const QString &path ); + virtual bool moveFile( const QString &oldURL, const QString &newURL, bool overwrite ); + virtual QStringList query(const QString& sql); + virtual QStringList similarArtists( int artists ); + virtual void migrateFile( const QString &oldURL, const QString &newURL ); + virtual void scanCollection(); + virtual void scanCollectionChanges(); + virtual void disableAutoScoring( bool disable ); + virtual void scanUnpause(); + virtual void scanPause(); + virtual void scannerAcknowledged(); + virtual int addLabels( const QString &url, const QStringList &labels ); + virtual void removeLabels( const QString &url, const QStringList &oldLabels ); + virtual int deviceId( const QString &url ); + virtual QString relativePath( const QString &url ); + virtual QString absolutePath( int deviceid, const QString &relativePath ); +}; + + +class DcopScriptHandler : public QObject, virtual public AmarokScriptInterface +{ + Q_OBJECT + + public: + DcopScriptHandler(); + + public /* DCOP */ slots: + virtual bool runScript(const QString&); + virtual bool stopScript(const QString&); + virtual QStringList listRunningScripts(); + virtual void addCustomMenuItem(QString submenu, QString itemTitle ); + virtual void removeCustomMenuItem(QString submenu, QString itemTitle ); + virtual QString readConfig(const QString& key); + virtual QStringList readListConfig(const QString& key); + virtual QString proxyForUrl(const QString& url); + virtual QString proxyForProtocol(const QString& protocol); +}; + +class DcopDevicesHandler : public QObject, virtual public AmarokDevicesInterface +{ + Q_OBJECT + + public: + DcopDevicesHandler(); + + public /* DCOP */ slots: + virtual void mediumAdded(QString name); + virtual void mediumRemoved(QString name); + virtual void mediumChanged(QString name); + virtual QStringList showDeviceList(); +}; + +class DcopMediaBrowserHandler : public QObject, virtual public AmarokMediaBrowserInterface +{ + Q_OBJECT + + public: + DcopMediaBrowserHandler(); + + public /* DCOP */ slots: + virtual void deviceConnect(); + virtual void deviceDisconnect(); + virtual QStringList deviceList(); + virtual void deviceSwitch( QString name ); + virtual void queue( KURL url ); + virtual void queueList( KURL::List urls ); + virtual void transfer(); + virtual void transcodingFinished( QString src, QString dest ); +}; + +} // namespace Amarok + +#endif diff --git a/amarok/src/amarokcore/amarokdcopiface.h b/amarok/src/amarokcore/amarokdcopiface.h new file mode 100644 index 00000000..2e37fa00 --- /dev/null +++ b/amarok/src/amarokcore/amarokdcopiface.h @@ -0,0 +1,247 @@ +/*************************************************************************** + amarokdcopiface.h - DCOP Interface + ------------------- + begin : Sat Oct 11 2003 + copyright : (C) 2003 by Stanislav Karchebny + (C) 2005 Ian Monroe + (C) 2005 Seb Ruiz + email : berkus@users.sf.net + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef AMAROK_DCOPIFACE_H +#define AMAROK_DCOPIFACE_H + +#include +#include +#include + +/////////////////////////////////////////////////////////////////////// +// WARNING! Please ask on #amarok before modifying the DCOP interface! +/////////////////////////////////////////////////////////////////////// + + +class AmarokPlayerInterface : virtual public DCOPObject +{ + K_DCOP + +k_dcop: + virtual QString version() = 0; ///< returns amarok version string + + virtual bool dynamicModeStatus() = 0; ///< Return dynamic mode status. + virtual bool equalizerEnabled() = 0; ///< Return the equalizer status. + virtual bool osdEnabled() = 0; ///< Return the OSD display status. + virtual bool isPlaying() = 0; ///< Return true if something is playing now. + virtual bool randomModeStatus() = 0; ///< Return random mode status. + virtual bool repeatPlaylistStatus() = 0; ///< Return repeat playlist status. + virtual bool repeatTrackStatus() = 0; ///< Return repeat track status. + virtual int getVolume() = 0; ///< Return volume in range 0-100%. + virtual int sampleRate() = 0; ///< Return the sample rate of the currently playing track. + virtual float score() = 0; ///< Return the score of the currently playing track. + virtual int rating() = 0; ///< Return the rating of the currently playing track. + virtual int status() = 0; ///< Return playback status: 0 - stopped, 1 - paused, 2 - playing. < 0 - error + virtual int trackCurrentTime() = 0; ///< Return current play position in seconds. + virtual int trackCurrentTimeMs() = 0; ///< Return current play position in milliseconds. + virtual int trackPlayCounter() = 0; ///< Return play counter for current song. + virtual int trackTotalTime() = 0; ///< Return track length in seconds. + virtual QStringList labels() = 0; ///< Return the labels of the currently playing track + + + /* New player API */ + virtual QString album() = 0; ///< Return the album of the currently playing track. + virtual QString artist() = 0; ///< Return the artist of the currently playing track. + virtual QString bitrate() = 0; ///< Return the bitrate of the currently playing track (XX kbps). + virtual QString comment() = 0; ///< Return the comment of the currently playing track. + virtual QString coverImage() = 0; ///< Return the encoded URL of the current track's cover image + virtual QString currentTime() = 0; ///< Return the position of the currently playing track ([h:]mm:ss format). + virtual QString encodedURL() = 0; ///< Return the encoded URL of the currently playing track. + virtual QString engine() = 0; /// * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "amarok.h" +#include "amarokconfig.h" +#include "crashhandler.h" + +#include //invokeMailer() +#include //kdBacktrace() +#include +#include +#include + +#include +#include +#include +#include //qVersion() + +#include //popen, fread +#include +#include //pid_t +#include //waitpid +#include +#include //write, getpid + + + +#ifndef TAGLIB_PATCH_VERSION +// seems to be wheel's style +#define TAGLIB_PATCH_VERSION 0 +#endif + + +namespace Amarok +{ + #if 0 + class CrashHandlerWidget : public KDialog { + public: + CrashHandlerWidget(); + }; + #endif + + static QString + runCommand( const QCString &command ) + { + static const uint SIZE = 40960; //40 KiB + static char stdoutBuf[ SIZE ] = {0}; + + std::cout << "Running: " << command << std::endl; + + FILE *process = ::popen( command, "r" ); + if ( process ) + { + stdoutBuf[ std::fread( static_cast( stdoutBuf ), sizeof(char), SIZE-1, process ) ] = '\0'; + ::pclose( process ); + } + return QString::fromLocal8Bit( stdoutBuf ); + } + + void + Crash::crashHandler( int /*signal*/ ) + { + // we need to fork to be able to get a + // semi-decent bt - I dunno why + const pid_t pid = ::fork(); + + if( pid < 0 ) + { + std::cout << "forking crash reporter failed\n"; + // continuing now can't do no good + _exit( 1 ); + } + else if ( pid == 0 ) + { + // we are the child process (the result of the fork) + std::cout << "Amarok is crashing...\n"; + + QString subject = APP_VERSION " "; + QString body = i18n( + "Amarok has crashed! We are terribly sorry about this :(\n\n" + "But, all is not lost! You could potentially help us fix the crash. " + "Information describing the crash is below, so just click send, " + "or if you have time, write a brief description of how the crash happened first.\n\n" + "Many thanks.\n\n" ); + body += i18n( "\n\n\n\n\n\n" + "The information below is to help the developers identify the problem, " + "please do not modify it.\n\n\n\n" ); + + + body += "======== DEBUG INFORMATION =======\n" + "Version: " APP_VERSION "\n" + "Engine: %1\n" + "Build date: " __DATE__ "\n" + "CC version: " __VERSION__ "\n" //assuming we're using GCC + "KDElibs: " KDE_VERSION_STRING "\n" + "Qt: %2\n" + "TagLib: %3.%4.%5\n" + "CPU count: %6\n"; + + QString cpucount = "unknown"; +#ifdef __linux__ + QString line; + uint cpuCount = 0; + QFile cpuinfo( "/proc/cpuinfo" ); + if ( cpuinfo.open( IO_ReadOnly ) ) { + while ( cpuinfo.readLine( line, 20000 ) != -1 ) { + if ( line.startsWith( "processor" ) ) { + ++cpuCount; + } + } + } + cpucount = QString::number( cpuCount ); +#endif + + + body = body.arg( AmarokConfig::soundSystem() ) + .arg( qVersion() ) + .arg( TAGLIB_MAJOR_VERSION ) + .arg( TAGLIB_MINOR_VERSION ) + .arg( TAGLIB_PATCH_VERSION ) + .arg( cpucount ); + + #ifdef NDEBUG + body += "NDEBUG: true"; + #endif + body += '\n'; + + /// obtain the backtrace with gdb + + KTempFile temp; + temp.setAutoDelete( true ); + + const int handle = temp.handle(); + +// QCString gdb_command_string = +// "file amarokapp\n" +// "attach " + QCString().setNum( ::getppid() ) + "\n" +// "bt\n" "echo \\n\n" +// "thread apply all bt\n"; + + const QCString gdb_batch = + "bt\n" + "echo \\n\\n\n" + "bt full\n" + "echo \\n\\n\n" + "echo ==== (gdb) thread apply all bt ====\\n\n" + "thread apply all bt\n"; + + ::write( handle, gdb_batch, gdb_batch.length() ); + ::fsync( handle ); + + // so we can read stderr too + ::dup2( fileno( stdout ), fileno( stderr ) ); + + + QCString gdb; + gdb = "gdb --nw -n --batch -x "; + gdb += temp.name().latin1(); + gdb += " amarokapp "; + gdb += QCString().setNum( ::getppid() ); + + QString bt = runCommand( gdb ); + + /// clean up + bt.remove( "(no debugging symbols found)..." ); + bt.remove( "(no debugging symbols found)\n" ); + bt.replace( QRegExp("\n{2,}"), "\n" ); //clean up multiple \n characters + bt.stripWhiteSpace(); + + /// analyze usefulness + bool useful = true; + const QString fileCommandOutput = runCommand( "file `which amarokapp`" ); + + if( fileCommandOutput.find( "not stripped", false ) == -1 ) + subject += "[___stripped]"; //same length as below + else + subject += "[NOTstripped]"; + + if( !bt.isEmpty() ) { + const int invalidFrames = bt.contains( QRegExp("\n#[0-9]+\\s+0x[0-9A-Fa-f]+ in \\?\\?") ); + const int validFrames = bt.contains( QRegExp("\n#[0-9]+\\s+0x[0-9A-Fa-f]+ in [^?]") ); + const int totalFrames = invalidFrames + validFrames; + + if( totalFrames > 0 ) { + const double validity = double(validFrames) / totalFrames; + subject += QString("[validity: %1]").arg( validity, 0, 'f', 2 ); + if( validity <= 0.5 ) useful = false; + } + subject += QString("[frames: %1]").arg( totalFrames, 3 /*padding*/ ); + + if( bt.find( QRegExp(" at \\w*\\.cpp:\\d+\n") ) >= 0 ) + subject += "[line numbers]"; + } + else + useful = false; + + subject += QString("[%1]").arg( AmarokConfig::soundSystem().remove( QRegExp("-?engine") ) ); + + std::cout << subject.latin1() << std::endl; + + + //TODO -fomit-frame-pointer buggers up the backtrace, so detect it + //TODO -O optimization can rearrange execution and stuff so show a warning for the developer + //TODO pass the CXXFLAGS used with the email + + if( useful ) { + body += "==== file `which amarokapp` =======\n"; + body += fileCommandOutput + "\n\n"; + body += "==== (gdb) bt =====================\n"; + body += bt + "\n\n"; + body += "==== kdBacktrace() ================\n"; + body += kdBacktrace(); + + //TODO startup notification + kapp->invokeMailer( + /*to*/ "amarok-backtraces@lists.sf.net", + /*cc*/ QString(), + /*bcc*/ QString(), + /*subject*/ subject, + /*body*/ body, + /*messageFile*/ QString(), + /*attachURLs*/ QStringList(), + /*startup_id*/ "" ); + } + else { + std::cout << i18n( "\nAmarok has crashed! We are terribly sorry about this :(\n\n" + "But, all is not lost! Perhaps an upgrade is already available " + "which fixes the problem. Please check your distribution's software repository.\n" ).local8Bit(); + } + + //_exit() exits immediately, otherwise this + //function is called repeatedly ad finitum + ::_exit( 255 ); + } + + else { + // we are the process that crashed + + ::alarm( 0 ); + + // wait for child to exit + ::waitpid( pid, NULL, 0 ); + ::_exit( 253 ); + } + } +} + + +#if 0 + +#include +#include +#include +#include +#include +#include +#include + +Amarok::CrashHandlerWidget::CrashHandlerWidget() +{ + QBoxLayout *layout = new QHBoxLayout( this, 18, 12 ); + + { + QBoxLayout *lay = new QVBoxLayout( layout ); + QLabel *label = new QLabel( this ); + label->setPixmap( locate( "data", "drkonqi/pics/konqi.png" ) ); + label->setFrameStyle( QFrame::Plain | QFrame::Box ); + lay->add( label ); + lay->addItem( new QSpacerItem( 3, 3, QSizePolicy::Minimum, QSizePolicy::Expanding ) ); + } + + layout = new QVBoxLayout( layout, 6 ); + + layout->add( new QLabel( /*i18n*/( + "

" "Amarok has crashed! We are terribly sorry about this :(" + "

" "However you now have an opportunity to help us fix this crash so that it doesn't " + "happen again! Click Send Email and Amarok will prepare an email that you " + "can send to us that contains information about the crash, and we'll try to fix it " + "as soon as possible." + "

" "Thanks for choosing Amarok.
" ), this ) ); + + layout = new QHBoxLayout( layout, 6 ); + + layout->addItem( new QSpacerItem( 6, 6, QSizePolicy::Expanding ) ); + layout->add( new KPushButton( KGuiItem( i18n("Send Email"), "mail_send" ), this, "email" ) ); + layout->add( new KPushButton( KStdGuiItem::close(), this, "close" ) ); + + static_cast(child("email"))->setDefault( true ); + + connect( child( "email" ), SIGNAL(clicked()), SLOT(accept()) ); + connect( child( "close" ), SIGNAL(clicked()), SLOT(reject()) ); + + setCaption( i18n("Crash Handler") ); + setFixedSize( sizeHint() ); +} +#endif diff --git a/amarok/src/amarokcore/crashhandler.h b/amarok/src/amarokcore/crashhandler.h new file mode 100644 index 00000000..f196ce08 --- /dev/null +++ b/amarok/src/amarokcore/crashhandler.h @@ -0,0 +1,32 @@ +/*************************************************************************** + * Copyright (C) 2005 Max Howell * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef CRASH_H +#define CRASH_H + +#include //for main.cpp + +namespace Amarok +{ + /** + * @author Max Howell + * @short The Amarok crash-handler + * + * I'm not entirely sure why this had to be inside a class, but it + * wouldn't work otherwise *shrug* + */ + class Crash + { + public: + static void crashHandler( int signal ); + }; +} + +#endif diff --git a/amarok/src/amarokitpc.protocol b/amarok/src/amarokitpc.protocol new file mode 100644 index 00000000..6ea5041a --- /dev/null +++ b/amarok/src/amarokitpc.protocol @@ -0,0 +1,11 @@ +[Protocol] +exec=amarok "%u" +protocol=itpc +input=none +output=none +helper=true +listing= +reading=false +writing=false +makedir=false +deleting=false diff --git a/amarok/src/amaroklastfm.protocol b/amarok/src/amaroklastfm.protocol new file mode 100644 index 00000000..1e6d65dc --- /dev/null +++ b/amarok/src/amaroklastfm.protocol @@ -0,0 +1,11 @@ +[Protocol] +exec=amarok "%u" +protocol=lastfm +input=none +output=none +helper=true +listing= +reading=false +writing=false +makedir=false +deleting=false diff --git a/amarok/src/amarokpcast.protocol b/amarok/src/amarokpcast.protocol new file mode 100644 index 00000000..46666d5f --- /dev/null +++ b/amarok/src/amarokpcast.protocol @@ -0,0 +1,11 @@ +[Protocol] +exec=amarok "%u" +protocol=pcast +input=none +output=none +helper=true +listing= +reading=false +writing=false +makedir=false +deleting=false diff --git a/amarok/src/amarokrc b/amarok/src/amarokrc new file mode 100644 index 00000000..1f0f49f1 --- /dev/null +++ b/amarok/src/amarokrc @@ -0,0 +1,14 @@ +[General] +XMLFile=amarokui.rc + +[BrowserBar] +CurrentPane=ContextBrowser + +[PlaylistColumnsLayout] +#see PlaylistWidget::setColumnWidth() for explanation for below +ColumnWidths=0,200,100,100,0,0,0,0,0,80,0 + +[KNewStuff] +TargetDir=amarok/themes +Uncompress=application/x-gzip +ProvidersUrl=http://amarok.kde.org/knewstuff/amarokthemes-providers.xml diff --git a/amarok/src/amarokui.rc b/amarok/src/amarokui.rc new file mode 100644 index 00000000..da29dced --- /dev/null +++ b/amarok/src/amarokui.rc @@ -0,0 +1,20 @@ + + +Playlist Toolbar + + + + + + + + + + + + + + + + + diff --git a/amarok/src/amarokui_xmms.rc b/amarok/src/amarokui_xmms.rc new file mode 100644 index 00000000..18105ed3 --- /dev/null +++ b/amarok/src/amarokui_xmms.rc @@ -0,0 +1,13 @@ + + +Playlist Toolbar + + + + + + + + + + diff --git a/amarok/src/analyzers/Makefile.am b/amarok/src/analyzers/Makefile.am new file mode 100644 index 00000000..232fd1c4 --- /dev/null +++ b/amarok/src/analyzers/Makefile.am @@ -0,0 +1,30 @@ +noinst_LTLIBRARIES = \ + libanalyzers.la + +METASOURCES = \ + AUTO + +INCLUDES = \ + -I$(top_builddir)/amarok/src/amarokcore \ + -I$(top_builddir)/amarok/src \ + -I$(top_srcdir)/amarok/src/amarokcore \ + -I$(top_srcdir)/amarok/src/engine \ + -I$(top_srcdir)/amarok/src/plugin \ + -I$(top_srcdir)/amarok/src \ + $(all_includes) + +libanalyzers_la_SOURCES = \ + analyzerbase.cpp \ + analyzerfactory.cpp \ + baranalyzer.cpp \ + blockanalyzer.cpp \ + glanalyzer.cpp \ + glanalyzer2.cpp \ + glanalyzer3.cpp \ + sonogram.cpp \ + turbine.cpp \ + boomanalyzer.cpp + + + + diff --git a/amarok/src/analyzers/analyzerbase.cpp b/amarok/src/analyzers/analyzerbase.cpp new file mode 100644 index 00000000..04e19e26 --- /dev/null +++ b/amarok/src/analyzers/analyzerbase.cpp @@ -0,0 +1,279 @@ +/*************************************************************************** + viswidget.cpp - description + ------------------- + begin : Die Jan 7 2003 + copyright : (C) 2003 by Max Howell + email : markey@web.de + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "analyzerbase.h" +#include //interpolate() +#include "enginecontroller.h" +#include //event() + + +// INSTRUCTIONS Base2D +// 1. do anything that depends on height() in init(), Base2D will call it before you are shown +// 2. otherwise you can use the constructor to initialise things +// 3. reimplement analyze(), and paint to canvas(), Base2D will update the widget when you return control to it +// 4. if you want to manipulate the scope, reimplement transform() +// 5. for convenience are pre-included +// TODO make an INSTRUCTIONS file +//can't mod scope in analyze you have to use transform + + +//TODO for 2D use setErasePixmap Qt function insetead of m_background + +// make the linker happy only for gcc < 4.0 +#if !( __GNUC__ > 4 || ( __GNUC__ == 4 && __GNUC_MINOR__ >= 0 ) ) +template class Analyzer::Base; +#endif + + +template +Analyzer::Base::Base( QWidget *parent, uint timeout, uint scopeSize ) + : W( parent ) + , m_timeout( timeout ) + , m_fht( new FHT(scopeSize) ) +{} + +template bool +Analyzer::Base::event( QEvent *e ) +{ + switch( e->type() ) { +/* case QEvent::Paint: + if( !canvas()->isNull() ) + bitBlt( this, 0, 0, canvas() ); + return true; //no propagate event*/ + case QEvent::Hide: + m_timer.stop(); + break; + + case QEvent::Show: + m_timer.start( timeout() ); + break; + + default: + break; + } + + return QWidget::event( e ); +} + +template void +Analyzer::Base::transform( Scope &scope ) //virtual +{ + //this is a standard transformation that should give + //an FFT scope that has bands for pretty analyzers + + //NOTE resizing here is redundant as FHT routines only calculate FHT::size() values + //scope.resize( m_fht->size() ); + + float *front = static_cast( &scope.front() ); + + float* f = new float[ m_fht->size() ]; + m_fht->copy( &f[0], front ); + m_fht->logSpectrum( front, &f[0] ); + m_fht->scale( front, 1.0 / 20 ); + + scope.resize( m_fht->size() / 2 ); //second half of values are rubbish + delete [] f; +} + +template void +Analyzer::Base::drawFrame() +{ + EngineBase *engine = EngineController::engine(); + + switch( engine->state() ) + { + case Engine::Playing: + { + const Engine::Scope &thescope = engine->scope(); + static Analyzer::Scope scope( 512 ); + int i = 0; + + // convert to mono here - our built in analyzers need mono, but we the engines provide interleaved pcm + for( uint x = 0; (int)x < m_fht->size(); ++x ) + { + scope[x] = double(thescope[i] + thescope[i+1]) / (2*(1<<15)); + i += 2; + } + + transform( scope ); + analyze( scope ); + + scope.resize( m_fht->size() ); + + break; + } + case Engine::Paused: + paused(); + break; + + default: + demo(); + } +} + +template int +Analyzer::Base::resizeExponent( int exp ) +{ + if ( exp < 3 ) + exp = 3; + else if ( exp > 9 ) + exp = 9; + + if ( exp != m_fht->sizeExp() ) { + delete m_fht; + m_fht = new FHT( exp ); + } + return exp; +} + +template int +Analyzer::Base::resizeForBands( int bands ) +{ + int exp; + if ( bands <= 8 ) + exp = 4; + else if ( bands <= 16 ) + exp = 5; + else if ( bands <= 32 ) + exp = 6; + else if ( bands <= 64 ) + exp = 7; + else if ( bands <= 128 ) + exp = 8; + else + exp = 9; + + resizeExponent( exp ); + return m_fht->size() / 2; +} + +template void +Analyzer::Base::paused() //virtual +{} + +template void +Analyzer::Base::demo() //virtual +{ + static int t = 201; //FIXME make static to namespace perhaps + + if( t > 999 ) t = 1; //0 = wasted calculations + if( t < 201 ) + { + Scope s( 32 ); + + const double dt = double(t) / 200; + for( uint i = 0; i < s.size(); ++i ) + s[i] = dt * (sin( M_PI + (i * M_PI) / s.size() ) + 1.0); + + analyze( s ); + } + else analyze( Scope( 32, 0 ) ); + + ++t; +} + + + +Analyzer::Base2D::Base2D( QWidget *parent, uint timeout, uint scopeSize ) + : Base( parent, timeout, scopeSize ) +{ + setWFlags( Qt::WNoAutoErase ); //no flicker + + connect( &m_timer, SIGNAL( timeout() ), SLOT( draw() ) ); +} + +void +Analyzer::Base2D::polish() +{ + //TODO is there much point in this anymore? + + //we use polish for initialzing (instead of ctor) + //because we need to know the widget's final size + QWidget::polish(); + + init(); //virtual +} + +void +Analyzer::Base2D::resizeEvent( QResizeEvent *e ) +{ + m_background.resize( size() ); + m_canvas.resize( size() ); + m_background.fill( backgroundColor() ); + eraseCanvas(); //this is necessary + + QWidget::resizeEvent( e ); +} + +void +Analyzer::Base2D::paletteChange( const QPalette& ) +{ + m_background.fill( backgroundColor() ); + eraseCanvas(); +} + + + +#ifdef HAVE_QGLWIDGET +Analyzer::Base3D::Base3D( QWidget *parent, uint timeout, uint scopeSize ) + : Base( parent, timeout, scopeSize ) +{ + connect( &m_timer, SIGNAL( timeout() ), SLOT( draw() ) ); +} +#endif + + +void +Analyzer::interpolate( const Scope &inVec, Scope &outVec ) //static +{ + double pos = 0.0; + const double step = (double)inVec.size() / outVec.size(); + + for ( uint i = 0; i < outVec.size(); ++i, pos += step ) + { + const double error = pos - std::floor( pos ); + const unsigned long offset = (unsigned long)pos; + + unsigned long indexLeft = offset + 0; + + if ( indexLeft >= inVec.size() ) + indexLeft = inVec.size() - 1; + + unsigned long indexRight = offset + 1; + + if ( indexRight >= inVec.size() ) + indexRight = inVec.size() - 1; + + outVec[i] = inVec[indexLeft ] * ( 1.0 - error ) + + inVec[indexRight] * error; + } +} + +void +Analyzer::initSin( Scope &v, const uint size ) //static +{ + double step = ( M_PI * 2 ) / size; + double radian = 0; + + for ( uint i = 0; i < size; i++ ) + { + v.push_back( sin( radian ) ); + radian += step; + } +} + +#include "analyzerbase.moc" diff --git a/amarok/src/analyzers/analyzerbase.h b/amarok/src/analyzers/analyzerbase.h new file mode 100644 index 00000000..ba3ecb30 --- /dev/null +++ b/amarok/src/analyzers/analyzerbase.h @@ -0,0 +1,153 @@ +// Maintainer: Max Howell , (C) 2004 +// Copyright: See COPYING file that comes with this distribution + +#ifndef ANALYZERBASE_H +#define ANALYZERBASE_H + + +#include //HAVE_QGLWIDGET + +#ifdef __FreeBSD__ +#include +#endif + +#include "fht.h" //stack allocated and convenience +#include //stack allocated and convenience +#include //stack allocated +#include //baseclass +#include //included for convenience + +#ifdef HAVE_QGLWIDGET +#include //baseclass +#ifdef Q_WS_MACX +#include //included for convenience +#include //included for convenience +#else +#include //included for convenience +#include //included for convenience +#endif +#else +//this is a workaround for compile problems due to moc +#define QGLWidget QWidget +#endif + +class QEvent; +class QPaintEvent; +class QResizeEvent; + + +namespace Analyzer { + +typedef std::vector Scope; + +template class Base : public W +{ +public: + uint timeout() const { return m_timeout; } + +protected: + Base( QWidget*, uint, uint = 7 ); + ~Base() { delete m_fht; } + + void drawFrame(); + int resizeExponent( int ); + int resizeForBands( int ); + virtual void transform( Scope& ); + virtual void analyze( const Scope& ) = 0; + virtual void paused(); + virtual void demo(); + + void changeTimeout( uint newTimeout ) + { + m_timer.changeInterval( newTimeout ); + m_timeout = newTimeout; + } + +private: + bool event( QEvent* ); + +protected: + QTimer m_timer; + uint m_timeout; + FHT *m_fht; +}; + + +class Base2D : public Base +{ +Q_OBJECT +public: + const QPixmap *background() const { return &m_background; } + const QPixmap *canvas() const { return &m_canvas; } + +private slots: + void draw() { drawFrame(); bitBlt( this, 0, 0, canvas() ); } + +protected: + Base2D( QWidget*, uint timeout, uint scopeSize = 7 ); + + virtual void init() {} + + QPixmap *background() { return &m_background; } + QPixmap *canvas() { return &m_canvas; } + void eraseCanvas() { bitBlt( canvas(), 0, 0, background() ); } + + void paintEvent( QPaintEvent* ) { if( !m_canvas.isNull() ) bitBlt( this, 0, 0, canvas() ); } + void resizeEvent( QResizeEvent* ); + void paletteChange( const class QPalette& ); + + void polish(); + +private: + QPixmap m_background; + QPixmap m_canvas; +}; + + + +//This mess is because moc generates an entry for this class despite the #if block +//1. the Q_OBJECT macro must be exposed +//2. we have to define the class +//3. we have to declare a ctor (to satisfy the inheritance) +//4. the slot must also by visible (!) +//TODO find out how to stop moc generating a metaobject for this class +class Base3D : public Base +{ +Q_OBJECT +#ifdef HAVE_QGLWIDGET +protected: + Base3D( QWidget*, uint, uint = 7 ); +private slots: + void draw() { drawFrame(); } +#else +protected: + Base3D( QWidget *w, uint i1, uint i2 ) : Base( w, i1, i2 ) {} +private slots: + void draw() {} +#endif +}; + + +class Factory +{ + //Currently this is a rather small class, its only purpose + //to ensure that making changes to analyzers will not require + //rebuilding the world! + + //eventually it would be better to make analyzers pluggable + //but I can't be arsed, nor can I see much reason to do so + //yet! +public: + static QWidget* createAnalyzer( QWidget* ); + static QWidget* createPlaylistAnalyzer( QWidget *); +}; + + +void interpolate( const Scope&, Scope& ); +void initSin( Scope&, const uint = 6000 ); + +} //END namespace Analyzer + +using Analyzer::Scope; + +#endif diff --git a/amarok/src/analyzers/analyzerfactory.cpp b/amarok/src/analyzers/analyzerfactory.cpp new file mode 100644 index 00000000..91c28b01 --- /dev/null +++ b/amarok/src/analyzers/analyzerfactory.cpp @@ -0,0 +1,127 @@ +/*************************************************************************** + analyzerfactory.cpp - description + ------------------- + begin : Fre Nov 15 2002 + copyright : (C) 2002 by Mark Kretschmann + email : markey@web.de +***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include //for HAVE_QGLWIDGET macro + +#include "amarokcore/amarokconfig.h" +#include "analyzerbase.h" //declaration here + +#include "baranalyzer.h" +#include "boomanalyzer.h" +#include "sonogram.h" +#include "turbine.h" +#include "blockanalyzer.h" + +#ifdef HAVE_QGLWIDGET +#include "glanalyzer.h" +#include "glanalyzer2.h" +#include "glanalyzer3.h" +#endif + +#include +#include + +//separate from analyzerbase.cpp to save compile time + + +QWidget *Analyzer::Factory::createAnalyzer( QWidget *parent ) +{ + //new XmmsWrapper(); //toplevel + + QWidget *analyzer = 0; + + switch( AmarokConfig::currentAnalyzer() ) + { + case 2: + analyzer = new Sonogram( parent ); + break; + case 1: + analyzer = new TurbineAnalyzer( parent ); + break; + case 3: + analyzer = new BarAnalyzer( parent ); + break; + case 4: + analyzer = new BlockAnalyzer( parent ); + break; +#ifdef HAVE_QGLWIDGET + case 5: + analyzer = new GLAnalyzer( parent ); + break; + case 6: + analyzer = new GLAnalyzer2( parent ); + break; + case 7: + analyzer = new GLAnalyzer3( parent ); + break; + case 8: +#else + case 5: +#endif + analyzer = new QLabel( i18n( "Click for Analyzers" ), parent ); //blank analyzer to satisfy Grue + static_cast(analyzer)->setAlignment( Qt::AlignCenter ); + break; + + default: + AmarokConfig::setCurrentAnalyzer( 0 ); + case 0: + analyzer = new BoomAnalyzer( parent ); + } + + return analyzer; +} + +QWidget *Analyzer::Factory::createPlaylistAnalyzer( QWidget *parent) +{ + QWidget *analyzer = 0; + switch( AmarokConfig::currentPlaylistAnalyzer() ) + { + case 1: + analyzer = new TurbineAnalyzer( parent ); + break; + case 2: + analyzer = new Sonogram( parent ); + break; + case 3: + analyzer = new BoomAnalyzer( parent ); + break; + #ifdef HAVE_QGLWIDGET + case 4: + analyzer = new GLAnalyzer( parent ); + break; + case 5: + analyzer = new GLAnalyzer2( parent ); + break; + case 6: + analyzer = new GLAnalyzer3( parent ); + break; + case 7: + #else + case 4: + #endif + analyzer = new QLabel( i18n( "Click for Analyzers" ), parent ); //blank analyzer to satisfy Grue + static_cast(analyzer)->setAlignment( Qt::AlignCenter ); + break; + + default: + AmarokConfig::setCurrentPlaylistAnalyzer( 0 ); + case 0: + analyzer = new BlockAnalyzer( parent ); + break; + } + return analyzer; +} diff --git a/amarok/src/analyzers/baranalyzer.cpp b/amarok/src/analyzers/baranalyzer.cpp new file mode 100644 index 00000000..61af801b --- /dev/null +++ b/amarok/src/analyzers/baranalyzer.cpp @@ -0,0 +1,171 @@ +// +// +// C++ Implementation: $MODULE$ +// +// Description: +// +// +// Author: Mark Kretschmann , (C) 2003 +// +// Copyright: See COPYING file that comes with this distribution +// +// + +#include "baranalyzer.h" +#include //log10(), etc. +#include "debug.h" +#include + + +BarAnalyzer::BarAnalyzer( QWidget *parent ) + : Analyzer::Base2D( parent, 12, 8 ) + //, m_bands( BAND_COUNT ) + //, barVector( BAND_COUNT, 0 ) + //, roofVector( BAND_COUNT, 50 ) + //, roofVelocityVector( BAND_COUNT, ROOF_VELOCITY_REDUCTION_FACTOR ) +{ + //roof pixmaps don't depend on size() so we do in the ctor + m_bg = parent->paletteBackgroundColor(); + + QColor fg( 0xff, 0x50, 0x70 ); + #define m_bg backgroundColor() + + double dr = double(m_bg.red() - fg.red()) / (NUM_ROOFS-1); //-1 because we start loop below at 0 + double dg = double(m_bg.green() - fg.green()) / (NUM_ROOFS-1); + double db = double(m_bg.blue() - fg.blue()) / (NUM_ROOFS-1); + + for ( uint i = 0; i < NUM_ROOFS; ++i ) + { + m_pixRoof[i].resize( COLUMN_WIDTH, 1 ); + m_pixRoof[i].fill( QColor( fg.red()+int(dr*i), fg.green()+int(dg*i), fg.blue()+int(db*i) ) ); + } + + #undef m_bg +} + +void BarAnalyzer::resizeEvent( QResizeEvent * e ) +{ + debug() << "Baranalyzer Resized(" << width() << "x" << height() << ")" << endl; + Analyzer::Base2D::resizeEvent( e ); + init(); +} + +// METHODS ===================================================== + +void BarAnalyzer::init() +{ + const double MAX_AMPLITUDE = 1.0; + const double F = double(height() - 2) / (log10( 255 ) * MAX_AMPLITUDE ); + + setPaletteBackgroundColor(m_bg); + + BAND_COUNT = width() / 5; + MAX_DOWN = int(0 -((height() / 50))); + MAX_UP = int((height() / 25)); + + debug() << "BAND_COUNT = " << BAND_COUNT << " MAX_UP = " << MAX_UP << "MAX_DOWN = " << MAX_DOWN << endl; + + barVector.resize( BAND_COUNT, 0 ); + roofVector.resize( BAND_COUNT, height() -5 ); + roofVelocityVector.resize( BAND_COUNT, ROOF_VELOCITY_REDUCTION_FACTOR ); + m_roofMem.resize(BAND_COUNT); + m_scope.resize(BAND_COUNT); + + //generate a list of values that express amplitudes in range 0-MAX_AMP as ints from 0-height() on log scale + for ( uint x = 0; x < 256; ++x ) + { + m_lvlMapper[x] = uint( F * log10( x+1 ) ); + } + + m_pixBarGradient.resize( height()*COLUMN_WIDTH, height() ); + m_pixCompose.resize( size() ); + + QPainter p( &m_pixBarGradient ); + for ( int x=0, r=0x40, g=0x30, b=0xff, r2=255-r; + x < height(); ++x ) + { + for ( int y = x; y > 0; --y ) + { + const double fraction = (double)y / height(); + +// p.setPen( QColor( r + (int)(r2 * fraction), g, b - (int)(255 * fraction) ) ); + p.setPen( QColor( r + (int)(r2 * fraction), g, b ) ); + p.drawLine( x*COLUMN_WIDTH, height() - y, (x+1)*COLUMN_WIDTH, height() - y ); + } + } + + + setMinimumSize( QSize( BAND_COUNT * COLUMN_WIDTH, 10 ) ); +} + + +void BarAnalyzer::analyze( const Scope &s ) +{ + //start with a blank canvas + eraseCanvas(); + + //Analyzer::interpolate( s, m_bands ); + + Scope &v = m_scope; + Analyzer::interpolate( s, v ); + + for ( uint i = 0, x = 0, y2; i < v.size(); ++i, x+=COLUMN_WIDTH+1 ) + { + //assign pre[log10]'d value + y2 = uint(v[i] * 256); //256 will be optimised to a bitshift //no, it's a float + y2 = m_lvlMapper[ (y2 > 255) ? 255 : y2 ]; //lvlMapper is array of ints with values 0 to height() + + int change = y2 - barVector[i]; + + //using the best of Markey's, piggz and Max's ideas on the way to shift the bars + //we have the following: + // 1. don't adjust shift when doing small up movements + // 2. shift large upwards with a bias towards last value + // 3. fall downwards at a constant pace + + /*if ( change > MAX_UP ) //anything too much greater than 2 gives "jitter" + //add some dynamics - makes the value slightly closer to what it was last time + y2 = ( barVector[i] + MAX_UP ); + //y2 = ( barVector[i] * 2 + y2 ) / 3; + else*/ if ( change < MAX_DOWN ) + y2 = barVector[i] + MAX_DOWN; + + + if ( (int)y2 > roofVector[i] ) + { + roofVector[i] = (int)y2; + roofVelocityVector[i] = 1; + } + + //remember where we are + barVector[i] = y2; + + if ( m_roofMem[i].size() > NUM_ROOFS ) + m_roofMem[i].erase( m_roofMem[i].begin() ); + + //blt last n roofs, a.k.a motion blur + for ( uint c = 0; c < m_roofMem[i].size(); ++c ) + //bitBlt( m_pComposePixmap, x, m_roofMem[i]->at( c ), m_roofPixmaps[ c ] ); + bitBlt( canvas(), x, m_roofMem[i][c], &m_pixRoof[ NUM_ROOFS - 1 - c ] ); + + //blt the bar + bitBlt( canvas(), x, height() - y2, + gradient(), y2 * COLUMN_WIDTH, height() - y2, COLUMN_WIDTH, y2, Qt::CopyROP ); + + m_roofMem[i].push_back( height() - roofVector[i] - 2 ); + + //set roof parameters for the NEXT draw + if ( roofVelocityVector[i] != 0 ) + { + if ( roofVelocityVector[i] > 32 ) //no reason to do == 32 + roofVector[i] -= (roofVelocityVector[i] - 32) / 20; //trivial calculation + + if ( roofVector[i] < 0 ) + { + roofVector[i] = 0; //not strictly necessary + roofVelocityVector[i] = 0; + } + else ++roofVelocityVector[i]; + } + } +} diff --git a/amarok/src/analyzers/baranalyzer.h b/amarok/src/analyzers/baranalyzer.h new file mode 100644 index 00000000..f8869d5d --- /dev/null +++ b/amarok/src/analyzers/baranalyzer.h @@ -0,0 +1,57 @@ +// Maintainer: Max Howell +// Authors: Mark Kretschmann & Max Howell (C) 2003-4 +// Copyright: See COPYING file that comes with this distribution +// + +#ifndef BARANALYZER_H +#define BARANALYZER_H + +#include "analyzerbase.h" + +typedef std::vector aroofMemVec; + + +class BarAnalyzer : public Analyzer::Base2D +{ + public: + BarAnalyzer( QWidget* ); + + void init(); + virtual void analyze( const Scope& ); + //virtual void transform( Scope& ); + + /** + * Resizes the widget to a new geometry according to @p e + * @param e The resize-event + */ + void resizeEvent( QResizeEvent * e); + + uint BAND_COUNT; + int MAX_DOWN; + int MAX_UP; + static const uint ROOF_HOLD_TIME = 48; + static const int ROOF_VELOCITY_REDUCTION_FACTOR = 32; + static const uint NUM_ROOFS = 16; + static const uint COLUMN_WIDTH = 4; + + protected: + QPixmap m_pixRoof[NUM_ROOFS]; + //vector m_roofMem[BAND_COUNT]; + + //Scope m_bands; //copy of the Scope to prevent creating/destroying a Scope every iteration + uint m_lvlMapper[256]; + std::vector m_roofMem; + std::vector barVector; //positions of bars + std::vector roofVector; //positions of roofs + std::vector roofVelocityVector; //speed that roofs falls + + const QPixmap *gradient() const { return &m_pixBarGradient; } + + private: + QPixmap m_pixBarGradient; + QPixmap m_pixCompose; + Scope m_scope; //so we don't create a vector every frame + QColor m_bg; +}; + +#endif diff --git a/amarok/src/analyzers/blockanalyzer.cpp b/amarok/src/analyzers/blockanalyzer.cpp new file mode 100644 index 00000000..f58bc832 --- /dev/null +++ b/amarok/src/analyzers/blockanalyzer.cpp @@ -0,0 +1,451 @@ +// Author: Max Howell , (C) 2003-5 +// Mark Kretschmann , (C) 2005 +// Copyright: See COPYING file that comes with this distribution +// + +#define DEBUG_PREFIX "BlockAnalyzer" + +#include "config.h" //HAVE_LIBVISUAL definition + +#include "actionclasses.h" //mousePressEvent +#include "amarok.h" +#include "blockanalyzer.h" + +#include + +#include +#include //paletteChange() +#include //mousePressEvent +#include //mousePressEvent +#include //mousePressEvent + +#include //mousePressEvent +#include //paletteChange() +#include + + +static inline uint myMax( uint v1, uint v2 ) { return v1 > v2 ? v1 : v2; } + +namespace Amarok { extern KConfig *config( const QString& ); } + + +BlockAnalyzer::BlockAnalyzer( QWidget *parent ) + : Analyzer::Base2D( parent, 20, 9 ) + , m_columns( 0 ) //uint + , m_rows( 0 ) //uint + , m_y( 0 ) //uint + , m_barPixmap( 1, 1 ) //null qpixmaps cause crashes + , m_topBarPixmap( WIDTH, HEIGHT ) + , m_scope( MIN_COLUMNS ) //Scope + , m_store( 1 << 8, 0 ) //vector + , m_fade_bars( FADE_SIZE ) //vector + , m_fade_pos( 1 << 8, 50 ) //vector + , m_fade_intensity( 1 << 8, 32 ) //vector +{ + changeTimeout( Amarok::config( "General" )->readNumEntry( "Timeout", 20 ) ); + + setMinimumSize( MIN_COLUMNS*(WIDTH+1) -1, MIN_ROWS*(HEIGHT+1) -1 ); //-1 is padding, no drawing takes place there + setMaximumWidth( MAX_COLUMNS*(WIDTH+1) -1 ); + + // mxcl says null pixmaps cause crashes, so let's play it safe + for ( uint i = 0; i < FADE_SIZE; ++i ) + m_fade_bars[i].resize( 1, 1 ); +} + +BlockAnalyzer::~BlockAnalyzer() +{ + Amarok::config( "General" )->writeEntry( "Timeout", timeout() ); +} + +void +BlockAnalyzer::resizeEvent( QResizeEvent *e ) +{ + QWidget::resizeEvent( e ); + + canvas()->resize( size() ); + background()->resize( size() ); + + const uint oldRows = m_rows; + + //all is explained in analyze().. + //+1 to counter -1 in maxSizes, trust me we need this! + m_columns = myMax( uint(double(width()+1) / (WIDTH+1)), MAX_COLUMNS ); + m_rows = uint(double(height()+1) / (HEIGHT+1)); + + //this is the y-offset for drawing from the top of the widget + m_y = (height() - (m_rows * (HEIGHT+1)) + 2) / 2; + + m_scope.resize( m_columns ); + + if( m_rows != oldRows ) { + m_barPixmap.resize( WIDTH, m_rows*(HEIGHT+1) ); + + for ( uint i = 0; i < FADE_SIZE; ++i ) + m_fade_bars[i].resize( WIDTH, m_rows*(HEIGHT+1) ); + + m_yscale.resize( m_rows + 1 ); + + const uint PRE = 1, PRO = 1; //PRE and PRO allow us to restrict the range somewhat + + for( uint z = 0; z < m_rows; ++z ) + m_yscale[z] = 1 - (log10( PRE+z ) / log10( PRE+m_rows+PRO )); + + m_yscale[m_rows] = 0; + + determineStep(); + paletteChange( palette() ); + } + else if( width() > e->oldSize().width() || height() > e->oldSize().height() ) + drawBackground(); + + analyze( m_scope ); +} + +void +BlockAnalyzer::determineStep() +{ + // falltime is dependent on rowcount due to our digital resolution (ie we have boxes/blocks of pixels) + // I calculated the value 30 based on some trial and error + + const double fallTime = 30 * m_rows; + m_step = double(m_rows * timeout()) / fallTime; +} + +void +BlockAnalyzer::transform( Analyzer::Scope &s ) //pure virtual +{ + for( uint x = 0; x < s.size(); ++x ) + s[x] *= 2; + + float *front = static_cast( &s.front() ); + + m_fht->spectrum( front ); + m_fht->scale( front, 1.0 / 20 ); + + //the second half is pretty dull, so only show it if the user has a large analyzer + //by setting to m_scope.size() if large we prevent interpolation of large analyzers, this is good! + s.resize( m_scope.size() <= MAX_COLUMNS/2 ? MAX_COLUMNS/2 : m_scope.size() ); +} + +void +BlockAnalyzer::analyze( const Analyzer::Scope &s ) +{ + // y = 2 3 2 1 0 2 + // . . . . # . + // . . . # # . + // # . # # # # + // # # # # # # + // + // visual aid for how this analyzer works. + // y represents the number of blanks + // y starts from the top and increases in units of blocks + + // m_yscale looks similar to: { 0.7, 0.5, 0.25, 0.15, 0.1, 0 } + // if it contains 6 elements there are 5 rows in the analyzer + + Analyzer::interpolate( s, m_scope ); + + // Paint the background + bitBlt( canvas(), 0, 0, background() ); + + for( uint y, x = 0; x < m_scope.size(); ++x ) + { + // determine y + for( y = 0; m_scope[x] < m_yscale[y]; ++y ) + ; + + // this is opposite to what you'd think, higher than y + // means the bar is lower than y (physically) + if( (float)y > m_store[x] ) + y = int(m_store[x] += m_step); + else + m_store[x] = y; + + // if y is lower than m_fade_pos, then the bar has exceeded the height of the fadeout + // if the fadeout is quite faded now, then display the new one + if( y <= m_fade_pos[x] /*|| m_fade_intensity[x] < FADE_SIZE / 3*/ ) { + m_fade_pos[x] = y; + m_fade_intensity[x] = FADE_SIZE; + } + + if( m_fade_intensity[x] > 0 ) { + const uint offset = --m_fade_intensity[x]; + const uint y = m_y + (m_fade_pos[x] * (HEIGHT+1)); + bitBlt( canvas(), x*(WIDTH+1), y, &m_fade_bars[offset], 0, 0, WIDTH, height() - y ); + } + + if( m_fade_intensity[x] == 0 ) + m_fade_pos[x] = m_rows; + + //REMEMBER: y is a number from 0 to m_rows, 0 means all blocks are glowing, m_rows means none are + bitBlt( canvas(), x*(WIDTH+1), y*(HEIGHT+1) + m_y, bar(), 0, y*(HEIGHT+1) ); + } + + for( uint x = 0; x < m_store.size(); ++x ) + bitBlt( canvas(), x*(WIDTH+1), int(m_store[x])*(HEIGHT+1) + m_y, &m_topBarPixmap ); +} + + + + + +static inline void +adjustToLimits( int &b, int &f, uint &amount ) +{ + // with a range of 0-255 and maximum adjustment of amount, + // maximise the difference between f and b + + if( b < f ) { + if( b > 255 - f ) { + amount -= f; + f = 0; + } else { + amount -= (255 - f); + f = 255; + } + } + else { + if( f > 255 - b ) { + amount -= f; + f = 0; + } else { + amount -= (255 - f); + f = 255; + } + } +} + +/** + * Clever contrast function + * + * It will try to adjust the foreground color such that it contrasts well with the background + * It won't modify the hue of fg unless absolutely necessary + * @return the adjusted form of fg + */ +QColor +ensureContrast( const QColor &bg, const QColor &fg, uint _amount = 150 ) +{ + class OutputOnExit { + public: + OutputOnExit( const QColor &color ) : c( color ) {} + ~OutputOnExit() { int h,s,v; c.getHsv( &h, &s, &v ); } + private: + const QColor &c; + }; + + // hack so I don't have to cast everywhere + #define amount static_cast(_amount) +// #define STAMP debug() << (QValueList() << fh << fs << fv) << endl; +// #define STAMP1( string ) debug() << string << ": " << (QValueList() << fh << fs << fv) << endl; +// #define STAMP2( string, value ) debug() << string << "=" << value << ": " << (QValueList() << fh << fs << fv) << endl; + + OutputOnExit allocateOnTheStack( fg ); + + int bh, bs, bv; + int fh, fs, fv; + + bg.getHsv( bh, bs, bv ); + fg.getHsv( fh, fs, fv ); + + int dv = abs( bv - fv ); + +// STAMP2( "DV", dv ); + + // value is the best measure of contrast + // if there is enough difference in value already, return fg unchanged + if( dv > amount ) + return fg; + + int ds = abs( bs - fs ); + +// STAMP2( "DS", ds ); + + // saturation is good enough too. But not as good. TODO adapt this a little + if( ds > amount ) + return fg; + + int dh = abs( bh - fh ); + +// STAMP2( "DH", dh ); + + if( dh > 120 ) { + // a third of the colour wheel automatically guarentees contrast + // but only if the values are high enough and saturations significant enough + // to allow the colours to be visible and not be shades of grey or black + + // check the saturation for the two colours is sufficient that hue alone can + // provide sufficient contrast + if( ds > amount / 2 && (bs > 125 && fs > 125) ) +// STAMP1( "Sufficient saturation difference, and hues are compliemtary" ); + return fg; + else if( dv > amount / 2 && (bv > 125 && fv > 125) ) +// STAMP1( "Sufficient value difference, and hues are compliemtary" ); + return fg; + +// STAMP1( "Hues are complimentary but we must modify the value or saturation of the contrasting colour" ); + + //but either the colours are two desaturated, or too dark + //so we need to adjust the system, although not as much + ///_amount /= 2; + } + + if( fs < 50 && ds < 40 ) { + // low saturation on a low saturation is sad + const int tmp = 50 - fs; + fs = 50; + if( amount > tmp ) + _amount -= tmp; + else + _amount = 0; + } + + // test that there is available value to honor our contrast requirement + if( 255 - dv < amount ) + { + // we have to modify the value and saturation of fg + //adjustToLimits( bv, fv, amount ); + +// STAMP + + // see if we need to adjust the saturation + if( amount > 0 ) + adjustToLimits( bs, fs, _amount ); + +// STAMP + + // see if we need to adjust the hue + if( amount > 0 ) + fh += amount; // cycles around; + +// STAMP + + return QColor( fh, fs, fv, QColor::Hsv ); + } + +// STAMP + + if( fv > bv && bv > amount ) + return QColor( fh, fs, bv - amount, QColor::Hsv ); + +// STAMP + + if( fv < bv && fv > amount ) + return QColor( fh, fs, fv - amount, QColor::Hsv ); + +// STAMP + + if( fv > bv && (255 - fv > amount) ) + return QColor( fh, fs, fv + amount, QColor::Hsv ); + +// STAMP + + if( fv < bv && (255 - bv > amount ) ) + return QColor( fh, fs, bv + amount, QColor::Hsv ); + +// STAMP +// debug() << "Something went wrong!\n"; + + return Qt::blue; + + #undef amount +// #undef STAMP +} + +void +BlockAnalyzer::paletteChange( const QPalette& ) //virtual +{ + const QColor bg = palette().active().background(); + const QColor fg = ensureContrast( bg, KGlobalSettings::activeTitleColor() ); + + m_topBarPixmap.fill( fg ); + + const double dr = 15*double(bg.red() - fg.red()) / (m_rows*16); + const double dg = 15*double(bg.green() - fg.green()) / (m_rows*16); + const double db = 15*double(bg.blue() - fg.blue()) / (m_rows*16); + const int r = fg.red(), g = fg.green(), b = fg.blue(); + + bar()->fill( bg ); + + QPainter p( bar() ); + for( int y = 0; (uint)y < m_rows; ++y ) + //graduate the fg color + p.fillRect( 0, y*(HEIGHT+1), WIDTH, HEIGHT, QColor( r+int(dr*y), g+int(dg*y), b+int(db*y) ) ); + + { + const QColor bg = palette().active().background().dark( 112 ); + + //make a complimentary fadebar colour + //TODO dark is not always correct, dumbo! + int h,s,v; palette().active().background().dark( 150 ).getHsv( &h, &s, &v ); + const QColor fg( h + 120, s, v, QColor::Hsv ); + + const double dr = fg.red() - bg.red(); + const double dg = fg.green() - bg.green(); + const double db = fg.blue() - bg.blue(); + const int r = bg.red(), g = bg.green(), b = bg.blue(); + + // Precalculate all fade-bar pixmaps + for( uint y = 0; y < FADE_SIZE; ++y ) { + m_fade_bars[y].fill( palette().active().background() ); + QPainter f( &m_fade_bars[y] ); + for( int z = 0; (uint)z < m_rows; ++z ) { + const double Y = 1.0 - (log10( FADE_SIZE - y ) / log10( FADE_SIZE )); + f.fillRect( 0, z*(HEIGHT+1), WIDTH, HEIGHT, QColor( r+int(dr*Y), g+int(dg*Y), b+int(db*Y) ) ); + } + } + } + + drawBackground(); +} + +void +BlockAnalyzer::drawBackground() +{ + const QColor bg = palette().active().background(); + const QColor bgdark = bg.dark( 112 ); + + background()->fill( bg ); + + QPainter p( background() ); + for( int x = 0; (uint)x < m_columns; ++x ) + for( int y = 0; (uint)y < m_rows; ++y ) + p.fillRect( x*(WIDTH+1), y*(HEIGHT+1) + m_y, WIDTH, HEIGHT, bgdark ); + + setErasePixmap( *background() ); +} + +void +BlockAnalyzer::contextMenuEvent( QContextMenuEvent *e ) +{ + //this is hard to read in order to be compact, apologies.. + //the id of each menu item is the value of the attribute it represents, + //so mapping is concise. + + const uint ids[] = { 50, 33, 25, 20, 10 }; + + KPopupMenu menu; + menu.insertTitle( i18n( "Framerate" ) ); + + for( uint x = 0; x < 5; ++x ) + { + const uint v = ids[x]; + + menu.insertItem( i18n( "%1 fps" ).arg( 1000/v ), v ); + menu.setItemChecked( v, v == timeout() ); + } + +#if defined HAVE_LIBVISUAL + menu.insertSeparator(); + menu.insertItem( SmallIconSet( Amarok::icon( "visualizations" ) ), i18n("&Visualizations"), + 0 ); +#endif + + const int id = menu.exec( e->globalPos() ); + + if( id == 0 ) + Amarok::Menu::instance()->slotActivated( Amarok::Menu::ID_SHOW_VIS_SELECTOR ); + else if( id != -1 ) { + changeTimeout( id ); + determineStep(); + } +} diff --git a/amarok/src/analyzers/blockanalyzer.h b/amarok/src/analyzers/blockanalyzer.h new file mode 100644 index 00000000..a03c99e0 --- /dev/null +++ b/amarok/src/analyzers/blockanalyzer.h @@ -0,0 +1,62 @@ +// Maintainer: Max Howell , (C) 2003-5 +// Copyright: See COPYING file that comes with this distribution +// + +#ifndef BLOCKANALYZER_H +#define BLOCKANALYZER_H + +#include "analyzerbase.h" +#include + +class QResizeEvent; +class QMouseEvent; +class QPalette; + + +/** + * @author Max Howell + */ + +class BlockAnalyzer : public Analyzer::Base2D +{ +public: + BlockAnalyzer( QWidget* ); + ~BlockAnalyzer(); + + static const uint HEIGHT = 2; + static const uint WIDTH = 4; + static const uint MIN_ROWS = 3; //arbituary + static const uint MIN_COLUMNS = 32; //arbituary + static const uint MAX_COLUMNS = 256; //must be 2**n + static const uint FADE_SIZE = 90; + +protected: + virtual void transform( Scope& ); + virtual void analyze( const Scope& ); + virtual void resizeEvent( QResizeEvent* ); + virtual void contextMenuEvent( QContextMenuEvent* ); + virtual void paletteChange( const QPalette& ); + + void drawBackground(); + void determineStep(); + +private: + QPixmap* const bar() { return &m_barPixmap; } + + uint m_columns, m_rows; //number of rows and columns of blocks + uint m_y; //y-offset from top of widget + QPixmap m_barPixmap; + QPixmap m_topBarPixmap; + Scope m_scope; //so we don't create a vector every frame + std::vector m_store; //current bar heights + std::vector m_yscale; + + //FIXME why can't I namespace these? c++ issue? + std::vector m_fade_bars; + std::vector m_fade_pos; + std::vector m_fade_intensity; + + float m_step; //rows to fall per frame +}; + +#endif diff --git a/amarok/src/analyzers/boomanalyzer.cpp b/amarok/src/analyzers/boomanalyzer.cpp new file mode 100644 index 00000000..1f9d8c71 --- /dev/null +++ b/amarok/src/analyzers/boomanalyzer.cpp @@ -0,0 +1,157 @@ +// Author: Max Howell , (C) 2004 +// Copyright: See COPYING file that comes with this distribution + +#include "amarok.h" +#include "boomanalyzer.h" +#include +#include +#include +#include +#include +#include + +BoomAnalyzer::BoomAnalyzer( QWidget *parent ) + : Analyzer::Base2D( parent, 10, 9 ) + , K_barHeight( 1.271 )//1.471 + , F_peakSpeed( 1.103 )//1.122 + , F( 1.0 ) + , bar_height( BAND_COUNT, 0 ) + , peak_height( BAND_COUNT, 0 ) + , peak_speed( BAND_COUNT, 0.01 ) + , barPixmap( COLUMN_WIDTH, 50 ) +{ + QWidget *o, *box = new QWidget( this, 0, WType_TopLevel ); + QSpinBox *m; + int v; + + (new QGridLayout( box, 2, 3 ))->setAutoAdd( true ); + + v = int(K_barHeight*1000); + new QLabel( "Bar fall-rate:", box ); + o = new QSlider( 100, 2000, 100, v, Qt::Horizontal, box ); + (m = new QSpinBox( 100, 2000, 1, box ))->setValue( v ); + connect( o, SIGNAL(valueChanged(int)), SLOT(changeK_barHeight( int )) ); + connect( o, SIGNAL(valueChanged(int)), m, SLOT(setValue( int )) ); + + v = int(F_peakSpeed*1000); + new QLabel( "Peak acceleration: ", box ); + o = new QSlider( 1000, 1300, 50, v, Qt::Horizontal, box ); + (m = new QSpinBox( 1000, 1300, 1, box ))->setValue( v ); + connect( o, SIGNAL(valueChanged(int)), SLOT(changeF_peakSpeed( int )) ); + connect( o, SIGNAL(valueChanged(int)), m, SLOT(setValue( int )) ); + + //box->show(); +} + + +void +BoomAnalyzer::changeK_barHeight( int newValue ) +{ + K_barHeight = (double)newValue / 1000; +} + +void +BoomAnalyzer::changeF_peakSpeed( int newValue ) +{ + F_peakSpeed = (double)newValue / 1000; +} + +void +BoomAnalyzer::init() +{ + const uint HEIGHT = height() - 2; + const double h = 1.2 / HEIGHT; + + F = double(HEIGHT) / (log10( 256 ) * 1.1 /*<- max. amplitude*/); + + barPixmap.resize( COLUMN_WIDTH-2, HEIGHT ); + + QPainter p( &barPixmap ); + for( uint y = 0; y < HEIGHT; ++y ) + { + const double F = (double)y * h; + + p.setPen( QColor( 255 - int(229.0 * F), 255 - int(229.0 * F), 255 - int(191.0 * F) ) ); + p.drawLine( 0, y, COLUMN_WIDTH-2, y ); + } +} + +void +BoomAnalyzer::transform( Scope &s ) +{ + float *front = static_cast( &s.front() ); + + m_fht->spectrum( front ); + m_fht->scale( front, 1.0 / 60 ); + + Scope scope( 32, 0 ); + + const uint xscale[] = { 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,19,24,29,36,43,52,63,76,91,108,129,153,182,216,255 }; + + for( uint j, i = 0; i < 32; i++ ) + for( j = xscale[i]; j < xscale[i + 1]; j++ ) + if ( s[j] > scope[i] ) + scope[i] = s[j]; + + s = scope; +} + +void +BoomAnalyzer::analyze( const Scope &scope ) +{ + eraseCanvas(); + + QPainter p( canvas() ); + float h; + const uint MAX_HEIGHT = height() - 1; + + for( uint i = 0, x = 0, y; i < BAND_COUNT; ++i, x += COLUMN_WIDTH+1 ) + { + h = log10( scope[i]*256.0 ) * F; + + if( h > MAX_HEIGHT ) + h = MAX_HEIGHT; + + if( h > bar_height[i] ) + { + bar_height[i] = h; + + if( h > peak_height[i] ) + { + peak_height[i] = h; + peak_speed[i] = 0.01; + } + else goto peak_handling; + } + else + { + if( bar_height[i] > 0.0 ) + { + bar_height[i] -= K_barHeight; //1.4 + if( bar_height[i] < 0.0 ) bar_height[i] = 0.0; + } + + peak_handling: + + if( peak_height[i] > 0.0 ) + { + peak_height[i] -= peak_speed[i]; + peak_speed[i] *= F_peakSpeed; //1.12 + + if( peak_height[i] < bar_height[i] ) peak_height[i] = bar_height[i]; + if( peak_height[i] < 0.0 ) peak_height[i] = 0.0; + } + } + + y = height() - uint(bar_height[i]); + bitBlt( canvas(), x+1, y, &barPixmap, 0, y ); + p.setPen( Amarok::ColorScheme::Foreground ); + p.drawRect( x, y, COLUMN_WIDTH, height() - y ); + + y = height() - uint(peak_height[i]); + p.setPen( Amarok::ColorScheme::Text ); + p.drawLine( x, y, x+COLUMN_WIDTH-1, y ); + } +} + +#include "boomanalyzer.moc" diff --git a/amarok/src/analyzers/boomanalyzer.h b/amarok/src/analyzers/boomanalyzer.h new file mode 100644 index 00000000..55c5ec3a --- /dev/null +++ b/amarok/src/analyzers/boomanalyzer.h @@ -0,0 +1,52 @@ +// Author: Max Howell , (C) 2004 +// Copyright: See COPYING file that comes with this distribution +// + +#ifndef BOOMANALYZER_H +#define BOOMANALYZER_H + +#include "analyzerbase.h" + +/** +@author Max Howell +*/ + +class BoomAnalyzer : public Analyzer::Base2D +{ +Q_OBJECT +public: + BoomAnalyzer( QWidget* ); + + virtual void init(); + virtual void transform( Scope &s ); + virtual void analyze( const Scope& ); + +public slots: + void changeK_barHeight( int ); + void changeF_peakSpeed( int ); + +protected: + static const uint COLUMN_WIDTH = 4; + static const uint BAND_COUNT = 32; + + double K_barHeight, F_peakSpeed, F; + + std::vector bar_height; + std::vector peak_height; + std::vector peak_speed; + + QPixmap barPixmap; +}; + +namespace Amarok +{ + namespace ColorScheme + { + extern QColor Base; + extern QColor Text; + extern QColor Background; + extern QColor Foreground; + } +} + +#endif diff --git a/amarok/src/analyzers/glanalyzer.cpp b/amarok/src/analyzers/glanalyzer.cpp new file mode 100644 index 00000000..ec8a6bc4 --- /dev/null +++ b/amarok/src/analyzers/glanalyzer.cpp @@ -0,0 +1,342 @@ +/*************************************************************************** + gloscope.cpp - description + ------------------- + begin : Jan 17 2004 + copyright : (C) 2004 by Adam Pigg + email : adam@piggz.co.uk + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include + +#ifdef HAVE_QGLWIDGET + +#include +#include "glanalyzer.h" +#include + + +GLAnalyzer::GLAnalyzer( QWidget *parent ) + : Analyzer::Base3D(parent, 15) + , m_oldy(32, -10.0f) + , m_peaks(32) +{} + +GLAnalyzer::~GLAnalyzer() +{} + +// METHODS ===================================================== + +void GLAnalyzer::analyze( const Scope &s ) +{ + //kdDebug() << "Scope Size: " << s.size() << endl; + /* Scope t(32); + if (s.size() != 32) + { + Analyzer::interpolate(s, t); + } + else + { + t = s; + }*/ + uint offset = 0; + static float peak; + float mfactor = 0.0; + static int drawcount; + + if (s.size() == 64) + { + offset=8; + } + + glRotatef(0.25f, 0.0f, 1.0f, 0.5f); //Rotate the scene + drawFloor(); + drawcount++; + if (drawcount > 25) + { + drawcount = 0; + peak = 0.0; + } + + for ( uint i = 0; i < 32; i++ ) + { + if (s[i] > peak) + { + peak = s[i]; + } + } + + mfactor = 20 / peak; + for ( uint i = 0; i < 32; i++ ) + { + + //kdDebug() << "Scope item " << i << " value: " << s[i] << endl; + + // Calculate new horizontal position (x) depending on number of samples + x = -16.0f + i; + + // Calculating new vertical position (y) depending on the data passed by amarok + y = float(s[i+offset] * mfactor); //This make it kinda dynamically resize depending on the data + + //Some basic bounds checking + if (y > 30) + y = 30; + else if (y < 0) + y = 0; + + if((y - m_oldy[i]) < -0.6f) // Going Down Too Much + { + y = m_oldy[i] - 0.7f; + } + if (y < 0.0f) + { + y = 0.0f; + } + + m_oldy[i] = y; //Save value as last value + + //Peak Code + if (m_oldy[i] > m_peaks[i].level) + { + m_peaks[i].level = m_oldy[i]; + m_peaks[i].delay = 30; + } + + if (m_peaks[i].delay > 0) + { + m_peaks[i].delay--; + } + + if (m_peaks[i].level > 1.0f) + { + if (m_peaks[i].delay <= 0) + { + m_peaks[i].level-=0.4f; + } + } + // Draw the bar + drawBar(x,y); + drawPeak(x, m_peaks[i].level); + + } + + updateGL(); +} + +void GLAnalyzer::initializeGL() +{ + // Clear frame (next fading will be preferred to clearing) + glClearColor(0.0f, 0.0f, 0.0f, 1.0f);// Set clear color to black + glClear( GL_COLOR_BUFFER_BIT ); + + // Set the shading model + glShadeModel(GL_SMOOTH); + + // Set the polygon mode to fill + glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); + + // Enable depth testing for hidden line removal + glEnable(GL_DEPTH_TEST); + + // Set blend parameters for 'composting alpha' + glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); +} + +void GLAnalyzer::resizeGL( int w, int h ) +{ + glViewport( 0, 0, (GLint)w, (GLint)h ); + glMatrixMode( GL_PROJECTION ); + glLoadIdentity(); + glOrtho(-16.0f, 16.0f, -10.0f, 10.0f, -50.0f, 100.0f); + glMatrixMode( GL_MODELVIEW ); + glLoadIdentity(); +} + +void GLAnalyzer::paintGL() +{ + glMatrixMode( GL_MODELVIEW ); +#if 0 + glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ); +#else + glEnable( GL_DEPTH_TEST ); + glEnable( GL_BLEND ); + glPushMatrix(); + glLoadIdentity(); + glBegin( GL_TRIANGLE_STRIP ); + glColor4f( 0.0f, 0.0f, 0.1f, 0.08f ); + glVertex2f( 20.0f, 10.0f ); + glVertex2f( -20.0f, 10.0f ); + glVertex2f( 20.0f, -10.0f ); + glVertex2f( -20.0f, -10.0f ); + glEnd(); + glPopMatrix(); + glDisable( GL_BLEND ); + glEnable( GL_DEPTH_TEST ); + glClear( GL_DEPTH_BUFFER_BIT ); +#endif + //swapBuffers(); + + glFlush(); + +} + +void GLAnalyzer::drawBar(float xPos, float height) +{ + glPushMatrix(); + + //Sets color to blue + //Set the colour depending on the height of the bar + glColor3f((height/40) + 0.5f, (height/40) + 0.625f, 1.0f); + glTranslatef(xPos, -10.0f, 0.0f); + + glScalef(1.0f, height, 3.0f); + drawCube(); + + //Set colour to full blue + //glColor3f(0.0f, 0.0f, 1.0f); + //drawFrame(); + glPopMatrix(); +} + +void GLAnalyzer::drawFloor() +{ + glPushMatrix(); + + //Sets color to amarok blue + glColor3f( 0.5f, 0.625f, 1.0f); + glTranslatef(-16.0f,-11.0f, -4.0f); + + glScalef(32.0f, 1.0f, 10.0f); + drawCube(); + + //Set colour to full blue + glColor3f(0.0f, 0.0f, 1.0f); + drawFrame(); + glPopMatrix(); +} + +void GLAnalyzer::drawPeak(float xPos, float ypos) +{ + glPushMatrix(); + + //Set the colour to red + glColor3f(1.0f, 0.0f, 0.0f); + glTranslatef(xPos, ypos - 10.0f, 0.0f); + + glScalef(1.0f, 1.0f, 3.0f); + drawCube(); + + glPopMatrix(); +} + +void GLAnalyzer::drawCube() +{ + glPushMatrix(); + glBegin(GL_POLYGON); + + //This is the top face + glVertex3f(0.0f, 1.0f, 0.0f); + glVertex3f(1.0f, 1.0f, 0.0f); + glVertex3f(1.0f, 1.0f, 1.0f); + glVertex3f(0.0f, 1.0f, 1.0f); + glVertex3f(0.0f, 1.0f, 0.0f); + + //This is the front face + glVertex3f(0.0f, 0.0f, 0.0f); + glVertex3f(1.0f, 0.0f, 0.0f); + glVertex3f(1.0f, 1.0f, 0.0f); + glVertex3f(0.0f, 1.0f, 0.0f); + glVertex3f(0.0f, 0.0f, 0.0f); + + //This is the right face + glVertex3f(1.0f, 0.0f, 0.0f); + glVertex3f(1.0f, 0.0f, 1.0f); + glVertex3f(1.0f, 1.0f, 1.0f); + glVertex3f(1.0f, 1.0f, 0.0f); + glVertex3f(1.0f, 0.0f, 0.0f); + + //This is the left face + glVertex3f(0.0f, 0.0f, 0.0f); + glVertex3f(0.0f, 0.0f, 1.0f); + glVertex3f(0.0f, 1.0f, 1.0f); + glVertex3f(0.0f, 1.0f, 0.0f); + glVertex3f(0.0f, 0.0f, 0.0f); + + //This is the bottom face + glVertex3f(0.0f, 0.0f, 0.0f); + glVertex3f(1.0f, 0.0f, 0.0f); + glVertex3f(1.0f, 0.0f, 1.0f); + glVertex3f(0.0f, 0.0f, 1.0f); + glVertex3f(0.0f, 0.0f, 0.0f); + + //This is the back face + glVertex3f(0.0f, 0.0f, 1.0f); + glVertex3f(1.0f, 0.0f, 1.0f); + glVertex3f(1.0f, 1.0f, 1.0f); + glVertex3f(0.0f, 1.0f, 1.0f); + glVertex3f(0.0f, 0.0f, 1.0f); + + glEnd(); + glPopMatrix(); +} +void GLAnalyzer::drawFrame() +{ + glPushMatrix(); + glBegin(GL_LINES); + + + //This is the top face + glVertex3f(0.0f, 1.0f, 0.0f); + glVertex3f(1.0f, 1.0f, 0.0f); + glVertex3f(1.0f, 1.0f, 1.0f); + glVertex3f(0.0f, 1.0f, 1.0f); + glVertex3f(0.0f, 1.0f, 0.0f); + + //This is the front face + glVertex3f(0.0f, 0.0f, 0.0f); + glVertex3f(1.0f, 0.0f, 0.0f); + glVertex3f(1.0f, 1.0f, 0.0f); + glVertex3f(0.0f, 1.0f, 0.0f); + glVertex3f(0.0f, 0.0f, 0.0f); + + //This is the right face + glVertex3f(1.0f, 0.0f, 0.0f); + glVertex3f(1.0f, 0.0f, 1.0f); + glVertex3f(1.0f, 1.0f, 1.0f); + glVertex3f(1.0f, 1.0f, 0.0f); + glVertex3f(1.0f, 0.0f, 0.0f); + + //This is the left face + glVertex3f(0.0f, 0.0f, 0.0f); + glVertex3f(0.0f, 0.0f, 1.0f); + glVertex3f(0.0f, 1.0f, 1.0f); + glVertex3f(0.0f, 1.0f, 0.0f); + glVertex3f(0.0f, 0.0f, 0.0f); + + //This is the bottom face + glVertex3f(0.0f, 0.0f, 0.0f); + glVertex3f(1.0f, 0.0f, 0.0f); + glVertex3f(1.0f, 0.0f, 1.0f); + glVertex3f(0.0f, 0.0f, 1.0f); + glVertex3f(0.0f, 0.0f, 0.0f); + + //This is the back face + glVertex3f(0.0f, 0.0f, 1.0f); + glVertex3f(1.0f, 0.0f, 1.0f); + glVertex3f(1.0f, 1.0f, 1.0f); + glVertex3f(0.0f, 1.0f, 1.0f); + glVertex3f(0.0f, 0.0f, 1.0f); + + glEnd(); + glPopMatrix(); +} + +#endif diff --git a/amarok/src/analyzers/glanalyzer.h b/amarok/src/analyzers/glanalyzer.h new file mode 100644 index 00000000..394c4638 --- /dev/null +++ b/amarok/src/analyzers/glanalyzer.h @@ -0,0 +1,62 @@ +/*************************************************************************** + gloscope.h - description + ------------------- + begin : Jan 17 2004 + copyright : (C) 2004 by Adam Pigg + email : adam@piggz.co.uk + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef GLOSCOPE_H +#define GLOSCOPE_H + +#include +#ifdef HAVE_QGLWIDGET + +#include "analyzerbase.h" + +/** + *@author piggz + */ + +typedef struct +{ + float level; + uint delay; +} +peak_tx; + +class GLAnalyzer : public Analyzer::Base3D +{ +private: + std::vector m_oldy; + std::vector m_peaks; + + void drawCube(); + void drawFrame(); + void drawBar(float xPos, float height); + void drawPeak(float xPos, float ypos); + void drawFloor(); + + GLfloat x, y; +public: + GLAnalyzer(QWidget *); + ~GLAnalyzer(); + void analyze( const Scope & ); + +protected: + void initializeGL(); + void resizeGL( int w, int h ); + void paintGL(); +}; + +#endif +#endif diff --git a/amarok/src/analyzers/glanalyzer2.cpp b/amarok/src/analyzers/glanalyzer2.cpp new file mode 100644 index 00000000..6f3d1c83 --- /dev/null +++ b/amarok/src/analyzers/glanalyzer2.cpp @@ -0,0 +1,333 @@ +/*************************************************************************** + glanalyzer2.cpp - description + ------------------- + begin : Feb 16 2004 + copyright : (C) 2004 by Enrico Ros + email : eros.kde@email.it + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include + +#ifdef HAVE_QGLWIDGET + +#include +#include +#include "glanalyzer2.h" +#include +#include +#include +#include + + +GLAnalyzer2::GLAnalyzer2( QWidget *parent ): +Analyzer::Base3D(parent, 15) +{ + //initialize openGL context before managing GL calls + makeCurrent(); + loadTexture( locate("data","amarok/data/dot.png"), dotTexture ); + loadTexture( locate("data","amarok/data/wirl1.png"), w1Texture ); + loadTexture( locate("data","amarok/data/wirl2.png"), w2Texture ); + + show.paused = true; + show.pauseTimer = 0.0; + show.rotDegrees = 0.0; + frame.rotDegrees = 0.0; +} + +GLAnalyzer2::~GLAnalyzer2() +{ + freeTexture( dotTexture ); + freeTexture( w1Texture ); + freeTexture( w2Texture ); +} + +void GLAnalyzer2::initializeGL() +{ + // Set a smooth shade model + glShadeModel(GL_SMOOTH); + + // Disable depth test (all is drawn on a 2d plane) + glDisable(GL_DEPTH_TEST); + + // Set blend parameters for 'composting alpha' + glBlendFunc( GL_SRC_ALPHA, GL_ONE ); //superpose + //glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); //fade + glEnable( GL_BLEND ); + + // Clear frame with a black background + glClearColor(0.0f, 0.0f, 0.0f, 1.0f); + glClear( GL_COLOR_BUFFER_BIT ); +} + +void GLAnalyzer2::resizeGL( int w, int h ) +{ + // Setup screen. We're going to manually do the perspective projection + glViewport( 0, 0, (GLint)w, (GLint)h ); + glMatrixMode( GL_PROJECTION ); + glLoadIdentity(); + glOrtho( -10.0f, 10.0f, -10.0f, 10.0f, -5.0f, 5.0f ); + + // Get the aspect ratio of the screen to draw 'cicular' particles + float ratio = (float)w / (float)h, + eqPixH = 60, + eqPixW = 80; + if ( ratio >= (4.0/3.0) ) { + unitX = 10.0 / (eqPixH * ratio); + unitY = 10.0 / eqPixH; + } else { + unitX = 10.0 / eqPixW; + unitY = 10.0 / (eqPixW / ratio); + } + + // Get current timestamp. + timeval tv; + gettimeofday( &tv, NULL ); + show.timeStamp = (double)tv.tv_sec + (double)tv.tv_usec/1000000.0; +} + +void GLAnalyzer2::paused() +{ + analyze( Scope() ); +} + +void GLAnalyzer2::analyze( const Scope &s ) +{ + bool haveNoData = s.empty(); + + // if we're going into pause mode, clear timers. + if ( !show.paused && haveNoData ) + show.pauseTimer = 0.0; + + // if we have got data, interpolate it (asking myself why I'm doing it here..) + if ( !(show.paused = haveNoData) ) + { + int bands = s.size(), + lowbands = bands / 4, + hibands = bands / 3, + midbands = bands - lowbands - hibands; Q_UNUSED( midbands ); + float currentEnergy = 0, + currentMeanBand = 0, + maxValue = 0; + for ( int i = 0; i < bands; i++ ) + { + float value = s[i]; + currentEnergy += value; + currentMeanBand += (float)i * value; + if ( value > maxValue ) + maxValue = value; + } + frame.silence = currentEnergy < 0.001; + if ( !frame.silence ) + { + frame.meanBand = 100.0 * currentMeanBand / (currentEnergy * bands); + currentEnergy = 100.0 * currentEnergy / (float)bands; + frame.dEnergy = currentEnergy - frame.energy; + frame.energy = currentEnergy; +// printf( "%d [%f :: %f ]\t%f \n", bands, frame.energy, frame.meanBand, maxValue ); + } else + frame.energy = 0.0; + } + + // update the frame + updateGL(); +} + +void GLAnalyzer2::paintGL() +{ + // Compute the dT since the last call to paintGL and update timings + timeval tv; + gettimeofday( &tv, NULL ); + double currentTime = (double)tv.tv_sec + (double)tv.tv_usec/1000000.0; + show.dT = currentTime - show.timeStamp; + show.timeStamp = currentTime; + + // Clear frame + glClear( GL_COLOR_BUFFER_BIT ); + + // Shitch to MODEL matrix and reset it to default + glMatrixMode( GL_MODELVIEW ); + glLoadIdentity(); + + // Fade the previous drawings. +/* glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); + glBegin( GL_TRIANGLE_STRIP ); + glColor4f( 0.0f, 0.0f, 0.0f, 0.2f ); + glVertex2f( 10.0f, 10.0f ); + glVertex2f( -10.0f, 10.0f ); + glVertex2f( 10.0f, -10.0f ); + glVertex2f( -10.0f, -10.0f ); + glEnd();*/ + + glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); + glEnable( GL_TEXTURE_2D ); + float alphaN = show.paused ? 0.2 : (frame.energy / 10.0), + alphaP = show.paused ? 1.0 : (1 - frame.energy / 20.0); + if ( alphaN > 1.0 ) + alphaN = 1.0; + if ( alphaP < 0.1 ) + alphaP = 0.1; + glBindTexture( GL_TEXTURE_2D, w2Texture ); + setTextureMatrix( show.rotDegrees, 0.707*alphaP ); + glColor4f( 1.0f, 1.0f, 1.0f, 1.0f ); + glBegin( GL_TRIANGLE_STRIP ); + glTexCoord2f( 1.0, 1.0 ); + glVertex2f( 10.0f, 10.0f ); + glTexCoord2f( 0.0, 1.0 ); + glVertex2f( -10.0f, 10.0f ); + glTexCoord2f( 1.0, 0.0 ); + glVertex2f( 10.0f, -10.0f ); + glTexCoord2f( 0.0 , 0.0 ); + glVertex2f( -10.0f, -10.0f ); + glEnd(); + glBindTexture( GL_TEXTURE_2D, w1Texture ); + setTextureMatrix( -show.rotDegrees * 2, 0.707 ); + glColor4f( 1.0f, 1.0f, 1.0f, alphaN ); + glBegin( GL_TRIANGLE_STRIP ); + glTexCoord2f( 1.0, 1.0 ); + glVertex2f( 10.0f, 10.0f ); + glTexCoord2f( 0.0, 1.0 ); + glVertex2f( -10.0f, 10.0f ); + glTexCoord2f( 1.0, 0.0 ); + glVertex2f( 10.0f, -10.0f ); + glTexCoord2f( 0.0 , 0.0 ); + glVertex2f( -10.0f, -10.0f ); + glEnd(); + setTextureMatrix( 0.0, 0.0 ); + glDisable( GL_TEXTURE_2D ); + glBlendFunc( GL_SRC_ALPHA, GL_ONE ); + + // Here begins the real draw loop + // some updates to the show + show.rotDegrees += 40.0 * show.dT; + frame.rotDegrees += 80.0 * show.dT; + + // handle the 'pause' status + if ( show.paused ) + { + if ( show.pauseTimer > 0.5 ) + { + if ( show.pauseTimer > 0.6 ) + show.pauseTimer -= 0.6; + drawFullDot( 0.0f, 0.4f, 0.8f, 1.0f ); + drawFullDot( 0.0f, 0.4f, 0.8f, 1.0f ); + } + show.pauseTimer += show.dT; + return; + } + + if ( dotTexture ) { + glEnable( GL_TEXTURE_2D ); + glBindTexture( GL_TEXTURE_2D, dotTexture ); + } else + glDisable( GL_TEXTURE_2D ); + + glLoadIdentity(); +// glRotatef( -frame.rotDegrees, 0,0,1 ); + glBegin( GL_QUADS ); +// Particle * particle = particleList.first(); +// for (; particle; particle = particleList.next()) + { + glColor4f( 0.0f, 1.0f, 0.0f, 1.0f ); + drawDot( 0, 0, kMax(10.0,(10.0 * frame.energy)) ); + glColor4f( 1.0f, 0.0f, 0.0f, 1.0f ); + drawDot( 6, 0, kMax(10.0, (5.0 * frame.energy)) ); + glColor4f( 0.0f, 0.4f, 1.0f, 1.0f ); + drawDot( -6, 0, kMax(10.0, (5.0 * frame.energy)) ); + } + glEnd(); +} + +void GLAnalyzer2::drawDot( float x, float y, float size ) +{ + float sizeX = size * unitX, + sizeY = size * unitY, + pLeft = x - sizeX, + pTop = y + sizeY, + pRight = x + sizeX, + pBottom = y - sizeY; + glTexCoord2f( 0, 0 ); // Bottom Left + glVertex2f( pLeft, pBottom ); + glTexCoord2f( 0, 1 ); // Top Left + glVertex2f( pLeft, pTop ); + glTexCoord2f( 1, 1 ); // Top Right + glVertex2f( pRight, pTop ); + glTexCoord2f( 1, 0 ); // Bottom Right + glVertex2f( pRight, pBottom ); +} + +void GLAnalyzer2::drawFullDot( float r, float g, float b, float a ) +{ + glBindTexture( GL_TEXTURE_2D, dotTexture ); + glEnable( GL_TEXTURE_2D ); + glColor4f( r, g, b, a ); + glBegin( GL_TRIANGLE_STRIP ); + glTexCoord2f( 1.0, 1.0 ); + glVertex2f( 10.0f, 10.0f ); + glTexCoord2f( 0.0, 1.0 ); + glVertex2f( -10.0f, 10.0f ); + glTexCoord2f( 1.0, 0.0 ); + glVertex2f( 10.0f, -10.0f ); + glTexCoord2f( 0.0 , 0.0 ); + glVertex2f( -10.0f, -10.0f ); + glEnd(); + glDisable( GL_TEXTURE_2D ); +} + + +void GLAnalyzer2::setTextureMatrix( float rot, float scale ) +{ + glMatrixMode( GL_TEXTURE); + glLoadIdentity(); + if ( rot != 0.0 || scale != 0.0 ) + { + glTranslatef( 0.5f, 0.5f, 0.0f ); + glRotatef( rot, 0.0f, 0.0f, 1.0f ); + glScalef( scale, scale, 1.0f ); + glTranslatef( -0.5f, -0.5f, 0.0f ); + } + glMatrixMode( GL_MODELVIEW ); +} + +bool GLAnalyzer2::loadTexture( QString fileName, GLuint& textureID ) +{ + //reset texture ID to the default EMPTY value + textureID = 0; + + //load image + QImage tmp; + if ( !tmp.load( fileName ) ) + return false; + + //convert it to suitable format (flipped RGBA) + QImage texture = QGLWidget::convertToGLFormat( tmp ); + if ( texture.isNull() ) + return false; + + //get texture number and bind loaded image to that texture + glGenTextures( 1, &textureID ); + glBindTexture( GL_TEXTURE_2D, textureID ); + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR ); + glTexImage2D( GL_TEXTURE_2D, 0, 4, texture.width(), texture.height(), + 0, GL_RGBA, GL_UNSIGNED_BYTE, texture.bits() ); + return true; +} + + +void GLAnalyzer2::freeTexture( GLuint & textureID ) +{ + if ( textureID > 0 ) + glDeleteTextures( 1, &textureID ); + textureID = 0; +} + +#endif diff --git a/amarok/src/analyzers/glanalyzer2.h b/amarok/src/analyzers/glanalyzer2.h new file mode 100644 index 00000000..3aeb98ae --- /dev/null +++ b/amarok/src/analyzers/glanalyzer2.h @@ -0,0 +1,73 @@ +/*************************************************************************** + glanalyzer2.h - description + ------------------- + begin : Feb 16 2004 + copyright : (C) 2004 by Enrico Ros + email : eros.kde@email.it + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef GLSTARVIEW_H +#define GLSTARVIEW_H + +#include +#ifdef HAVE_QGLWIDGET + +#include "analyzerbase.h" +#include +#include + + +class GLAnalyzer2 : public Analyzer::Base3D +{ +public: + GLAnalyzer2(QWidget *); + ~GLAnalyzer2(); + void analyze( const Scope & ); + void paused(); + +protected: + void initializeGL(); + void resizeGL( int w, int h ); + void paintGL(); + +private: + struct ShowProperties { + bool paused; + double timeStamp; + double dT; + double pauseTimer; + float rotDegrees; + } show; + + struct FrameProperties { + float energy; + float dEnergy; + float meanBand; + float rotDegrees; + bool silence; + } frame; + + GLuint dotTexture; + GLuint w1Texture; + GLuint w2Texture; + float unitX, unitY; + + void drawDot( float x, float y, float size ); + void drawFullDot( float r, float g, float b, float a ); + void setTextureMatrix( float rot, float scale ); + + bool loadTexture(QString file, GLuint& textureID); + void freeTexture(GLuint& textureID); +}; + +#endif +#endif diff --git a/amarok/src/analyzers/glanalyzer3.cpp b/amarok/src/analyzers/glanalyzer3.cpp new file mode 100644 index 00000000..cc7ce4c7 --- /dev/null +++ b/amarok/src/analyzers/glanalyzer3.cpp @@ -0,0 +1,480 @@ +/*************************************************************************** + glanalyzer3.cpp - Bouncing Ballzz + ------------------- + begin : Feb 19 2004 + copyright : (C) 2004 by Enrico Ros + email : eros.kde@email.it + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include + +#ifdef HAVE_QGLWIDGET + +#include +#include +#include "glanalyzer3.h" +#include +#include +#include +#include + +#ifndef HAVE_FABSF +inline float fabsf(float f) +{ + return f < 0.f ? -f : f; +} +#endif + + +class Ball +{ + public: + Ball() : x( drand48() - drand48() ), y( 1 - 2.0 * drand48() ), + z( drand48() ), vx( 0.0 ), vy( 0.0 ), vz( 0.0 ), + mass( 0.01 + drand48()/10.0 ) + //,color( (float[3]) { 0.0, drand48()*0.5, 0.7 + drand48() * 0.3 } ) + { + //this is because GCC < 3.3 can't compile the above line, we aren't sure why though + color[0] = 0.0; color[1] = drand48()*0.5; color[2] = 0.7 + drand48() * 0.3; + }; + + float x, y, z, vx, vy, vz, mass; + float color[3]; + + void updatePhysics( float dT ) + { + x += vx * dT; // position + y += vy * dT; // position + z += vz * dT; // position + if ( y < -0.8 ) vy = fabsf( vy ); + if ( y > 0.8 ) vy = -fabsf( vy ); + if ( z < 0.1 ) vz = fabsf( vz ); + if ( z > 0.9 ) vz = -fabsf( vz ); + vx += (( x > 0 ) ? 4.94 : -4.94) * dT; // G-force + vx *= (1 - 2.9 * dT); // air friction + vy *= (1 - 2.9 * dT); // air friction + vz *= (1 - 2.9 * dT); // air friction + } +}; + +class Paddle +{ + public: + Paddle( float xPos ) : onLeft( xPos < 0 ), mass( 1.0 ), + X( xPos ), x( xPos ), vx( 0.0 ) {}; + + void updatePhysics( float dT ) + { + x += vx * dT; // posision + vx += (1300 * (X - x) / mass) * dT; // elasticity + vx *= (1 - 4.0 * dT); // air friction + } + + void renderGL() + { + glBegin( GL_TRIANGLE_STRIP ); + glColor3f( 0.0f, 0.1f, 0.3f ); + glVertex3f( x, -1.0f, 0.0 ); + glVertex3f( x, 1.0f, 0.0 ); + glColor3f( 0.1f, 0.2f, 0.6f ); + glVertex3f( x, -1.0f, 1.0 ); + glVertex3f( x, 1.0f, 1.0 ); + glEnd(); + } + + void bounce( Ball * ball ) + { + if ( onLeft && ball->x < x ) + { + ball->vx = vx * mass / (mass + ball->mass) + fabsf( ball->vx ); + ball->vy = (drand48() - drand48()) * 1.8; + ball->vz = (drand48() - drand48()) * 0.9; + ball->x = x; + } + else if ( !onLeft && ball->x > x ) + { + ball->vx = vx * mass / (mass + ball->mass) - fabsf( ball->vx ); + ball->vy = (drand48() - drand48()) * 1.8; + ball->vz = (drand48() - drand48()) * 0.9; + ball->x = x; + } + } + + void impulse( float strength ) + { + if ( (onLeft && strength > vx) || (!onLeft && strength < vx) ) + vx += strength; + } + + private: + bool onLeft; + float mass, X, x, vx; +}; + + +GLAnalyzer3::GLAnalyzer3( QWidget *parent ): +Analyzer::Base3D(parent, 15) +{ + //initialize openGL context before managing GL calls + makeCurrent(); + loadTexture( locate("data","amarok/data/ball.png"), ballTexture ); + loadTexture( locate("data","amarok/data/grid.png"), gridTexture ); + + balls.setAutoDelete( true ); + leftPaddle = new Paddle( -1.0 ); + rightPaddle = new Paddle( 1.0 ); + for ( int i = 0; i < NUMBER_OF_BALLS; i++ ) + balls.append( new Ball() ); + + show.colorK = 0.0; + show.gridScrollK = 0.0; + show.gridEnergyK = 0.0; + show.camRot = 0.0; + show.camRoll = 0.0; + show.peakEnergy = 1.0; + frame.silence = true; + frame.energy = 0.0; + frame.dEnergy = 0.0; +} + +GLAnalyzer3::~GLAnalyzer3() +{ + freeTexture( ballTexture ); + freeTexture( gridTexture ); + delete leftPaddle; + delete rightPaddle; + balls.clear(); +} + +void GLAnalyzer3::initializeGL() +{ + // Set a smooth shade model + glShadeModel(GL_SMOOTH); + + // Disable depth test (all is drawn 'z-sorted') + glDisable( GL_DEPTH_TEST ); + + // Set blending function (Alpha addition) + glBlendFunc( GL_SRC_ALPHA, GL_ONE ); + + // Clear frame with a black background + glClearColor(0.0f, 0.0f, 0.0f, 1.0f); +} + +void GLAnalyzer3::resizeGL( int w, int h ) +{ + // Setup screen. We're going to manually do the perspective projection + glViewport( 0, 0, (GLint)w, (GLint)h ); + glMatrixMode( GL_PROJECTION ); + glLoadIdentity(); + glFrustum( -0.5f, 0.5f, -0.5f, 0.5f, 0.5f, 4.5f ); + + // Get the aspect ratio of the screen to draw 'circular' particles + float ratio = (float)w / (float)h; + if ( ratio >= 1.0 ) { + unitX = 0.34 / ratio; + unitY = 0.34; + } else { + unitX = 0.34; + unitY = 0.34 * ratio; + } + + // Get current timestamp. + timeval tv; + gettimeofday( &tv, NULL ); + show.timeStamp = (double)tv.tv_sec + (double)tv.tv_usec/1000000.0; +} + +void GLAnalyzer3::paused() +{ + analyze( Scope() ); +} + +void GLAnalyzer3::analyze( const Scope &s ) +{ + // compute the dTime since the last call + timeval tv; + gettimeofday( &tv, NULL ); + double currentTime = (double)tv.tv_sec + (double)tv.tv_usec/1000000.0; + show.dT = currentTime - show.timeStamp; + show.timeStamp = currentTime; + + // compute energy integrating frame's spectrum + if ( !s.empty() ) + { + int bands = s.size(); + float currentEnergy = 0, + maxValue = 0; + // integrate spectrum -> energy + for ( int i = 0; i < bands; i++ ) + { + float value = s[i]; + currentEnergy += value; + if ( value > maxValue ) + maxValue = value; + } + currentEnergy *= 100.0 / (float)bands; + // emulate a peak detector: currentEnergy -> peakEnergy (3tau = 30 seconds) + show.peakEnergy = 1.0 + ( show.peakEnergy - 1.0 ) * exp( - show.dT / 10.0 ); + if ( currentEnergy > show.peakEnergy ) + show.peakEnergy = currentEnergy; + // check for silence + frame.silence = currentEnergy < 0.001; + // normalize frame energy against peak energy and compute frame stats + currentEnergy /= show.peakEnergy; + frame.dEnergy = currentEnergy - frame.energy; + frame.energy = currentEnergy; + } else + frame.silence = true; + + // update the frame + updateGL(); +} + +void GLAnalyzer3::paintGL() +{ + // limit max dT to 0.05 and update color and scroll constants + if ( show.dT > 0.05 ) + show.dT = 0.05; + show.colorK += show.dT * 0.4; + if ( show.colorK > 3.0 ) + show.colorK -= 3.0; + show.gridScrollK += 0.2 * show.peakEnergy * show.dT; + + // Switch to MODEL matrix and clear screen + glMatrixMode( GL_MODELVIEW ); + glLoadIdentity(); + glClear( GL_COLOR_BUFFER_BIT ); + + // Draw scrolling grid + if ( (show.gridEnergyK > 0.05) || (!frame.silence && frame.dEnergy < -0.3) ) + { + show.gridEnergyK *= exp( -show.dT / 0.1 ); + if ( -frame.dEnergy > show.gridEnergyK ) + show.gridEnergyK = -frame.dEnergy*2.0; + float gridColor[4] = { 0.0, 1.0, 0.6, show.gridEnergyK }; + drawScrollGrid( show.gridScrollK, gridColor ); + } + + // Roll camera up/down handling the beat + show.camRot += show.camRoll * show.dT; // posision + show.camRoll -= 400 * show.camRot * show.dT; // elasticity + show.camRoll *= (1 - 2.0 * show.dT); // friction + if ( !frame.silence && frame.dEnergy > 0.4 ) + show.camRoll += show.peakEnergy*2.0; + glRotatef( show.camRoll / 2.0, 1,0,0 ); + + // Translate the drawing plane + glTranslatef( 0.0f, 0.0f, -1.8f ); + + // Draw upper/lower planes and paddles + drawHFace( -1.0 ); + drawHFace( 1.0 ); + leftPaddle->renderGL(); + rightPaddle->renderGL(); + + // Draw Balls + if ( ballTexture ) { + glEnable( GL_TEXTURE_2D ); + glBindTexture( GL_TEXTURE_2D, ballTexture ); + } else + glDisable( GL_TEXTURE_2D ); + glEnable( GL_BLEND ); + Ball * ball = balls.first(); + for ( ; ball; ball = balls.next() ) + { + float color[3], + angle = show.colorK; + // Rotate the color based on 'angle' value [0,3) + if ( angle < 1.0 ) + { + color[ 0 ] = ball->color[ 0 ] * (1 - angle) + ball->color[ 1 ] * angle; + color[ 1 ] = ball->color[ 1 ] * (1 - angle) + ball->color[ 2 ] * angle; + color[ 2 ] = ball->color[ 2 ] * (1 - angle) + ball->color[ 0 ] * angle; + } + else if ( angle < 2.0 ) + { + angle -= 1.0; + color[ 0 ] = ball->color[ 1 ] * (1 - angle) + ball->color[ 2 ] * angle; + color[ 1 ] = ball->color[ 2 ] * (1 - angle) + ball->color[ 0 ] * angle; + color[ 2 ] = ball->color[ 0 ] * (1 - angle) + ball->color[ 1 ] * angle; + } + else + { + angle -= 2.0; + color[ 0 ] = ball->color[ 2 ] * (1 - angle) + ball->color[ 0 ] * angle; + color[ 1 ] = ball->color[ 0 ] * (1 - angle) + ball->color[ 1 ] * angle; + color[ 2 ] = ball->color[ 1 ] * (1 - angle) + ball->color[ 2 ] * angle; + } + // Draw the dot and update its physics also checking at bounces + glColor3fv( color ); + drawDot3s( ball->x, ball->y, ball->z, 1.0 ); + ball->updatePhysics( show.dT ); + if ( ball->x < 0 ) + leftPaddle->bounce( ball ); + else + rightPaddle->bounce( ball ); + } + glDisable( GL_BLEND ); + glDisable( GL_TEXTURE_2D ); + + // Update physics of paddles + leftPaddle->updatePhysics( show.dT ); + rightPaddle->updatePhysics( show.dT ); + if ( !frame.silence ) + { + leftPaddle->impulse( frame.energy*3.0 + frame.dEnergy*6.0 ); + rightPaddle->impulse( -frame.energy*3.0 - frame.dEnergy*6.0 ); + } +} + +void GLAnalyzer3::drawDot3s( float x, float y, float z, float size ) +{ + // Circular XY dot drawing functions + float sizeX = size * unitX, + sizeY = size * unitY, + pXm = x - sizeX, + pXM = x + sizeX, + pYm = y - sizeY, + pYM = y + sizeY; + // Draw the Dot + glBegin( GL_QUADS ); + glTexCoord2f( 0, 0 ); // Bottom Left + glVertex3f( pXm, pYm, z ); + glTexCoord2f( 0, 1 ); // Top Left + glVertex3f( pXm, pYM, z ); + glTexCoord2f( 1, 1 ); // Top Right + glVertex3f( pXM, pYM, z ); + glTexCoord2f( 1, 0 ); // Bottom Right + glVertex3f( pXM, pYm, z ); + glEnd(); + + // Shadow XZ drawing functions + float sizeZ = size / 10.0, + pZm = z - sizeZ, + pZM = z + sizeZ, + currentColor[4]; + glGetFloatv( GL_CURRENT_COLOR, currentColor ); + float alpha = currentColor[3], + topSide = (y + 1) / 4, + bottomSide = (1 - y) / 4; + // Draw the top shadow + currentColor[3] = topSide * topSide * alpha; + glColor4fv( currentColor ); + glBegin( GL_QUADS ); + glTexCoord2f( 0, 0 ); // Bottom Left + glVertex3f( pXm, 1, pZm ); + glTexCoord2f( 0, 1 ); // Top Left + glVertex3f( pXm, 1, pZM ); + glTexCoord2f( 1, 1 ); // Top Right + glVertex3f( pXM, 1, pZM ); + glTexCoord2f( 1, 0 ); // Bottom Right + glVertex3f( pXM, 1, pZm ); + glEnd(); + // Draw the bottom shadow + currentColor[3] = bottomSide * bottomSide * alpha; + glColor4fv( currentColor ); + glBegin( GL_QUADS ); + glTexCoord2f( 0, 0 ); // Bottom Left + glVertex3f( pXm, -1, pZm ); + glTexCoord2f( 0, 1 ); // Top Left + glVertex3f( pXm, -1, pZM ); + glTexCoord2f( 1, 1 ); // Top Right + glVertex3f( pXM, -1, pZM ); + glTexCoord2f( 1, 0 ); // Bottom Right + glVertex3f( pXM, -1, pZm ); + glEnd(); +} + +void GLAnalyzer3::drawHFace( float y ) +{ + glBegin( GL_TRIANGLE_STRIP ); + glColor3f( 0.0f, 0.1f, 0.2f ); + glVertex3f( -1.0f, y, 0.0 ); + glVertex3f( 1.0f, y, 0.0 ); + glColor3f( 0.1f, 0.6f, 0.5f ); + glVertex3f( -1.0f, y, 2.0 ); + glVertex3f( 1.0f, y, 2.0 ); + glEnd(); +} + +void GLAnalyzer3::drawScrollGrid( float scroll, float color[4] ) +{ + if ( !gridTexture ) + return; + glMatrixMode( GL_TEXTURE ); + glLoadIdentity(); + glTranslatef( 0.0, -scroll, 0.0 ); + glMatrixMode( GL_MODELVIEW ); + float backColor[4] = { 1.0, 1.0, 1.0, 0.0 }; + for ( int i = 0; i < 3; i++ ) + backColor[ i ] = color[ i ]; + glEnable( GL_TEXTURE_2D ); + glBindTexture( GL_TEXTURE_2D, gridTexture ); + glEnable( GL_BLEND ); + glBegin( GL_TRIANGLE_STRIP ); + glColor4fv( color ); // top face + glTexCoord2f( 0.0f, 1.0f ); + glVertex3f( -1.0f, 1.0f, -1.0f ); + glTexCoord2f( 1.0f, 1.0f ); + glVertex3f( 1.0f, 1.0f, -1.0f ); + glColor4fv( backColor ); // central points + glTexCoord2f( 0.0f, 0.0f ); + glVertex3f( -1.0f, 0.0f, -3.0f ); + glTexCoord2f( 1.0f, 0.0f ); + glVertex3f( 1.0f, 0.0f, -3.0f ); + glColor4fv( color ); // bottom face + glTexCoord2f( 0.0f, 1.0f ); + glVertex3f( -1.0f, -1.0f, -1.0f ); + glTexCoord2f( 1.0f, 1.0f ); + glVertex3f( 1.0f, -1.0f, -1.0f ); + glEnd(); + glDisable( GL_BLEND ); + glDisable( GL_TEXTURE_2D ); + glMatrixMode( GL_TEXTURE ); + glLoadIdentity(); + glMatrixMode( GL_MODELVIEW ); +} + +bool GLAnalyzer3::loadTexture( QString fileName, GLuint& textureID ) +{ + //reset texture ID to the default EMPTY value + textureID = 0; + + //load image + QImage tmp; + if ( !tmp.load( fileName ) ) + return false; + + //convert it to suitable format (flipped RGBA) + QImage texture = QGLWidget::convertToGLFormat( tmp ); + if ( texture.isNull() ) + return false; + + //get texture number and bind loaded image to that texture + glGenTextures( 1, &textureID ); + glBindTexture( GL_TEXTURE_2D, textureID ); + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR ); + glTexImage2D( GL_TEXTURE_2D, 0, 4, texture.width(), texture.height(), + 0, GL_RGBA, GL_UNSIGNED_BYTE, texture.bits() ); + return true; +} + +void GLAnalyzer3::freeTexture( GLuint& textureID ) +{ + if ( textureID > 0 ) + glDeleteTextures( 1, &textureID ); + textureID = 0; +} + +#endif diff --git a/amarok/src/analyzers/glanalyzer3.h b/amarok/src/analyzers/glanalyzer3.h new file mode 100644 index 00000000..7abd7614 --- /dev/null +++ b/amarok/src/analyzers/glanalyzer3.h @@ -0,0 +1,80 @@ +/*************************************************************************** + glanalyzer3.h - description + ------------------- + begin : Feb 16 2004 + copyright : (C) 2004 by Enrico Ros + email : eros.kde@email.it + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include +#ifdef HAVE_QGLWIDGET + +#ifndef GLBOUNCER_H +#define GLBOUNCER_H + +#include "analyzerbase.h" +#include +#include + +class QWidget; +class Ball; +class Paddle; + +class GLAnalyzer3 : public Analyzer::Base3D +{ +public: + GLAnalyzer3(QWidget *); + ~GLAnalyzer3(); + void analyze( const Scope & ); + void paused(); + +protected: + void initializeGL(); + void resizeGL( int w, int h ); + void paintGL(); + +private: + struct ShowProperties { + double timeStamp; + double dT; + float colorK; + float gridScrollK; + float gridEnergyK; + float camRot; + float camRoll; + float peakEnergy; + } show; + + struct FrameProperties { + bool silence; + float energy; + float dEnergy; + } frame; + + static const int NUMBER_OF_BALLS = 16; + + QPtrList balls; + Paddle * leftPaddle, * rightPaddle; + float unitX, unitY; + GLuint ballTexture; + GLuint gridTexture; + + void drawDot3s( float x, float y, float z, float size ); + void drawHFace( float y ); + void drawScrollGrid( float scroll, float color[4] ); + + bool loadTexture(QString file, GLuint& textureID); + void freeTexture(GLuint& textureID); +}; + +#endif +#endif diff --git a/amarok/src/analyzers/sonogram.cpp b/amarok/src/analyzers/sonogram.cpp new file mode 100644 index 00000000..b29563db --- /dev/null +++ b/amarok/src/analyzers/sonogram.cpp @@ -0,0 +1,91 @@ +// +// +// C++ Implementation: Sonogram +// +// Description: +// +// +// Author: Melchior FRANZ , (C) 2004 +// +// Copyright: See COPYING file that comes with this distribution +// +// + +#include +#include "sonogram.h" + +Sonogram::Sonogram(QWidget *parent) : + Analyzer::Base2D(parent, 16, 9) +{ +} + + +Sonogram::~Sonogram() +{ +} + + +void Sonogram::init() +{ + eraseCanvas(); +} + + +void Sonogram::resizeEvent(QResizeEvent *e) +{ + QWidget::resizeEvent(e); + canvas()->resize(size()); + background()->resize(size()); + +//only for gcc < 4.0 +#if !( __GNUC__ > 4 || ( __GNUC__ == 4 && __GNUC_MINOR__ >= 0 ) ) + resizeForBands(height() < 128 ? 128 : height()); +#endif + + background()->fill(backgroundColor()); + bitBlt(canvas(), 0, 0, background()); + bitBlt(this, 0, 0, background()); +} + + +void Sonogram::analyze(const Scope &s) +{ + int x = width() - 1; + QColor c; + QPainter p(canvas()); + + bitBlt(canvas(), 0, 0, canvas(), 1, 0, x, height()); + Scope::const_iterator it = s.begin(), end = s.end(); + for (int y = height() - 1; y;) { + if (it >= end || *it < .005) + c = backgroundColor(); + else if (*it < .05) + c.setHsv(95, 255, 255 - int(*it * 4000.0)); + else if (*it < 1.0) + c.setHsv(95 - int(*it * 90.0), 255, 255); + else + c = Qt::red; + + p.setPen(c); + p.drawPoint(x, y--); + + if (it < end) + ++it; + } +} + + +void Sonogram::transform(Scope &scope) +{ + float *front = static_cast(&scope.front()); + m_fht->power2(front); + m_fht->scale(front, 1.0 / 256); + scope.resize( m_fht->size() / 2 ); +} + + +void Sonogram::demo() +{ + analyze(Scope(m_fht->size(), 0)); +} + diff --git a/amarok/src/analyzers/sonogram.h b/amarok/src/analyzers/sonogram.h new file mode 100644 index 00000000..609c9b2c --- /dev/null +++ b/amarok/src/analyzers/sonogram.h @@ -0,0 +1,37 @@ +// +// +// C++ Interface: Sonogram +// +// Description: +// +// +// Author: Melchior FRANZ , (C) 2004 +// +// Copyright: See COPYING file that comes with this distribution +// +// + +#ifndef SONOGRAM_H +#define SONOGRAM_H + +#include "analyzerbase.h" + +/** +@author Melchior FRANZ +*/ + +class Sonogram : public Analyzer::Base2D +{ +public: + Sonogram(QWidget*); + ~Sonogram(); + +protected: + void init(); + void analyze(const Scope&); + void transform(Scope&); + void demo(); + void resizeEvent(QResizeEvent*); +}; + +#endif diff --git a/amarok/src/analyzers/turbine.cpp b/amarok/src/analyzers/turbine.cpp new file mode 100644 index 00000000..d2e75640 --- /dev/null +++ b/amarok/src/analyzers/turbine.cpp @@ -0,0 +1,78 @@ +// +// Amarok BarAnalyzer 3 - Jet Turbine: Symmetric version of analyzer 1 +// +// Author: Stanislav Karchebny , (C) 2003 +// Max Howell (I modified it to use boom analyzer code) +// +// Copyright: like rest of Amarok +// + +#include +#include + +#include "amarok.h" +#include "turbine.h" + +void TurbineAnalyzer::analyze( const Scope &scope ) +{ + eraseCanvas(); + + QPainter p( canvas() ); + float h; + const uint hd2 = height() / 2; + const uint MAX_HEIGHT = hd2 - 1; + + for( uint i = 0, x = 0, y; i < BAND_COUNT; ++i, x += COLUMN_WIDTH+1 ) + { + h = log10( scope[i]*256.0 ) * F * 0.5; + + if( h > MAX_HEIGHT ) + h = MAX_HEIGHT; + + if( h > bar_height[i] ) + { + bar_height[i] = h; + + if( h > peak_height[i] ) + { + peak_height[i] = h; + peak_speed[i] = 0.01; + } + else goto peak_handling; + } + else + { + if( bar_height[i] > 0.0 ) + { + bar_height[i] -= K_barHeight; //1.4 + if( bar_height[i] < 0.0 ) bar_height[i] = 0.0; + } + + peak_handling: + + if( peak_height[i] > 0.0 ) + { + peak_height[i] -= peak_speed[i]; + peak_speed[i] *= F_peakSpeed; //1.12 + + if( peak_height[i] < bar_height[i] ) peak_height[i] = bar_height[i]; + if( peak_height[i] < 0.0 ) peak_height[i] = 0.0; + } + } + + + y = hd2 - uint(bar_height[i]); + bitBlt( canvas(), x+1, y, &barPixmap, 0, y ); + bitBlt( canvas(), x+1, hd2, &barPixmap, 0, (int)bar_height[i] ); + + p.setPen( Amarok::ColorScheme::Foreground ); + p.drawRect( x, y, COLUMN_WIDTH, (int)bar_height[i]*2 ); + + const uint x2 = x+COLUMN_WIDTH-1; + p.setPen( Amarok::ColorScheme::Text ); + y = hd2 - uint(peak_height[i]); + p.drawLine( x, y, x2, y ); + y = hd2 + uint(peak_height[i]); + p.drawLine( x, y, x2, y ); + } +} diff --git a/amarok/src/analyzers/turbine.h b/amarok/src/analyzers/turbine.h new file mode 100644 index 00000000..d75c6053 --- /dev/null +++ b/amarok/src/analyzers/turbine.h @@ -0,0 +1,22 @@ +// +// Amarok BarAnalyzer 3 - Jet Turbine: Symmetric version of analyzer 1 +// +// Author: Stanislav Karchebny , (C) 2003 +// +// Copyright: like rest of Amarok +// + +#ifndef ANALYZER_TURBINE_H +#define ANALYZER_TURBINE_H + +#include "boomanalyzer.h" + +class TurbineAnalyzer : public BoomAnalyzer +{ + public: + TurbineAnalyzer( QWidget *parent ) : BoomAnalyzer( parent ) {} + + void analyze( const Scope& ); +}; + +#endif diff --git a/amarok/src/app.cpp b/amarok/src/app.cpp new file mode 100644 index 00000000..32f20f24 --- /dev/null +++ b/amarok/src/app.cpp @@ -0,0 +1,1487 @@ +/*************************************************************************** + app.cpp - description + ------------------- +begin : Mit Okt 23 14:35:18 CEST 2002 +copyright : (C) 2002 by Mark Kretschmann +email : markey@web.de +***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "amarok.h" +#include "amarokconfig.h" +#include "amarokdcophandler.h" +#include "app.h" +#include "atomicstring.h" +#include "config.h" +#include "configdialog.h" +#include "contextbrowser.h" +#include "collectionbrowser.h" +#include "dbsetup.h" //firstRunWizard() +#include "debug.h" +#include "devicemanager.h" +#include "mediadevicemanager.h" +#include "enginebase.h" +#include "enginecontroller.h" +#include "equalizersetup.h" +#include "firstrunwizard.h" +#include "mediabrowser.h" +#include "metabundle.h" +#include "mountpointmanager.h" +#include "osd.h" +#include "playerwindow.h" +#include "playlist.h" +#include "playlistbrowser.h" +#include "playlistwindow.h" +#include "pluginmanager.h" +#include "refreshimages.h" +#include "scriptmanager.h" +#include "scrobbler.h" +#include "statusbar.h" +#include "systray.h" +#include "threadmanager.h" +#include "tracktooltip.h" //engineNewMetaData() + +#include + +#include +#include //firstRunWizard() +#include //initCliArgs() +#include //Amarok::OverrideCursor +#include //slotConfigToolbars() +#include //initGlobalShortcuts() +#include //applyColorScheme() +#include //amarok Icon +#include //slotConfigShortcuts() +#include +#include //applySettings(), genericEventHandler() +#include //Amarok::invokeBrowser() +#include +#include //genericEventHandler() +#include +#include + +#include //genericEventHandler() +#include //applySettings() +#include +#include //applyColorScheme() +#include //applyColorScheme() +#include //QPixmap::setDefaultOptimization() +#include //genericEventHandler +#include //showHyperThreadingWarning() +#include //default tooltip for trayicon + +// For the HyperThreading fix +#ifdef __linux__ + #ifdef SCHEDAFFINITY_SUPPORT + #include + #include + #endif //SCHEDAFFINITY_SUPPORT +#endif //__linux__ + +QMutex Debug::mutex; +QMutex Amarok::globalDirsMutex; + +int App::mainThreadId = 0; + +#ifdef Q_WS_MAC +#include + +static AEEventHandlerUPP appleEventProcessorUPP = 0; + +OSStatus +appleEventProcessor(const AppleEvent *ae, AppleEvent *, long /*handlerRefCon*/) +{ + OSType aeID = typeWildCard; + OSType aeClass = typeWildCard; + AEGetAttributePtr(ae, keyEventClassAttr, typeType, 0, &aeClass, sizeof(aeClass), 0); + AEGetAttributePtr(ae, keyEventIDAttr, typeType, 0, &aeID, sizeof(aeID), 0); + + if(aeClass == kCoreEventClass) + { + if(aeID == kAEReopenApplication) + { + if( PlaylistWindow::self() ) + PlaylistWindow::self()->show(); + } + return noErr; + } + return eventNotHandledErr; +} +#endif + +LIBAMAROK_EXPORT KAboutData aboutData( "amarok", + I18N_NOOP( "Amarok" ), APP_VERSION, + I18N_NOOP( "The audio player for KDE" ), KAboutData::License_GPL, + I18N_NOOP( "(C) 2002-2003, Mark Kretschmann\n(C) 2003-2007, The Amarok Development Squad" ), + I18N_NOOP( "IRC:\nirc.freenode.net - #amarok, #amarok.de, #amarok.es\n\nFeedback:\namarok@kde.org\n\n(Build Date: " __DATE__ ")" ), + ( "http://amarok.kde.org" ) ); + +App::App() + : KApplication() + , m_pPlayerWindow( 0 ) //will be created in applySettings() +{ + DEBUG_BLOCK + +#ifdef Q_WS_MAC + // this is inspired by OpenSceneGraph: osgDB/FilePath.cpp + + // Start with the the Bundle PlugIns directory. + + // Get the main bundle first. No need to retain or release it since + // we are not keeping a reference + CFBundleRef myBundle = CFBundleGetMainBundle(); + if( myBundle ) + { + // CFBundleGetMainBundle will return a bundle ref even if + // the application isn't part of a bundle, so we need to + // check + // if the path to the bundle ends in ".app" to see if it is + // a + // proper application bundle. If it is, the plugins path is + // added + CFURLRef urlRef = CFBundleCopyBundleURL(myBundle); + if(urlRef) + { + char bundlePath[1024]; + if( CFURLGetFileSystemRepresentation( urlRef, true, (UInt8 *)bundlePath, sizeof(bundlePath) ) ) + { + QCString bp( bundlePath ); + size_t len = bp.length(); + if( len > 4 && bp.right( 4 ) == ".app" ) + { + bp.append( "/Contents/MacOS" ); + QCString path = getenv( "PATH" ); + if( path.length() > 0 ) + { + path.prepend( ":" ); + } + path.prepend( bp ); + debug() << "setting PATH=" << path << endl; + setenv("PATH", path, 1); + } + } + // docs say we are responsible for releasing CFURLRef + CFRelease(urlRef); + } + } +#endif + + QPixmap::setDefaultOptimization( QPixmap::MemoryOptim ); + + //needs to be created before the wizard + new Amarok::DcopPlayerHandler(); // Must be created first + new Amarok::DcopPlaylistHandler(); + new Amarok::DcopPlaylistBrowserHandler(); + new Amarok::DcopContextBrowserHandler(); + new Amarok::DcopCollectionHandler(); + new Amarok::DcopMediaBrowserHandler(); + new Amarok::DcopScriptHandler(); + new Amarok::DcopDevicesHandler(); + + fixHyperThreading(); + // tell AtomicString that this is the GUI thread + if ( !AtomicString::isMainThread() ) + qWarning("AtomicString was initialized from a thread other than the GUI " + "thread. This could lead to memory leaks."); + +#ifdef Q_WS_MAC + appleEventProcessorUPP = AEEventHandlerUPP(appleEventProcessor); + AEInstallEventHandler(kCoreEventClass, kAEReopenApplication, appleEventProcessorUPP, (long)this, true); +#endif + + QTimer::singleShot( 0, this, SLOT( continueInit() ) ); +} + +App::~App() +{ + DEBUG_BLOCK + + // Hiding the OSD before exit prevents crash + Amarok::OSD::instance()->hide(); + + EngineBase* const engine = EngineController::engine(); + + if ( AmarokConfig::resumePlayback() ) { + if ( engine->state() != Engine::Empty ) { + AmarokConfig::setResumeTrack( EngineController::instance()->playingURL().prettyURL() ); + AmarokConfig::setResumeTime( engine->position() ); + } + else AmarokConfig::setResumeTrack( QString::null ); //otherwise it'll play previous resume next time! + } + + EngineController::instance()->endSession(); //records final statistics + EngineController::instance()->detach( this ); + + // do even if trayicon is not shown, it is safe + Amarok::config()->writeEntry( "HiddenOnExit", mainWindow()->isHidden() ); + + CollectionDB::instance()->stopScan(); + + delete m_pPlayerWindow; //sets some XT keys + delete m_pPlaylistWindow; //sets some XT keys + + ThreadManager::deleteInstance(); //waits for jobs to finish + + // this must be deleted before the connection to the Xserver is + // severed, or we risk a crash when the QApplication is exited, + // I asked Trolltech! *smug* + delete Amarok::OSD::instance(); + + AmarokConfig::setVersion( APP_VERSION ); + AmarokConfig::writeConfig(); + + //need to unload the engine before the kapplication is destroyed + PluginManager::unload( engine ); +} + + +#include +#include + +namespace +{ + // grabbed from KsCD source, kompatctdisk.cpp + QString urlToDevice(const QString& device) + { + KURL deviceUrl(device); + if (deviceUrl.protocol() == "media" || deviceUrl.protocol() == "system") + { + DCOPRef mediamanager( "kded", "mediamanager" ); + DCOPReply reply = mediamanager.call( "properties(QString)", deviceUrl.fileName() ); + QStringList properties = reply; + + if (!reply.isValid() || properties.count() < 6) + { + debug() << "Invalid reply from mediamanager" << endl; + return QString(); + } + else + { + debug() << "Reply from mediamanager " << properties[5] << endl; + return properties[5]; + } + } + + return device; + } + +} + + +void App::handleCliArgs() //static +{ + static char cwd[PATH_MAX]; + KCmdLineArgs* const args = KCmdLineArgs::parsedArgs(); + + if ( args->isSet( "cwd" ) ) + { + strncpy(cwd, args->getOption( "cwd" ), sizeof(cwd) ); + cwd[sizeof(cwd)-1] = '\0'; + KCmdLineArgs::setCwd( cwd ); + } + + bool haveArgs = false; + if ( args->count() > 0 ) + { + haveArgs = true; + + KURL::List list; + for( int i = 0; i < args->count(); i++ ) + { + KURL url = args->url( i ); + if( url.protocol() == "itpc" || url.protocol() == "pcast" ) + PlaylistBrowser::instance()->addPodcast( url ); + else + list << url; + } + + int options = Playlist::DefaultOptions; + if( args->isSet( "queue" ) ) + options = Playlist::Queue; + else if( args->isSet( "append" ) || args->isSet( "enqueue" ) ) + options = Playlist::Append; + else if( args->isSet( "load" ) ) + options = Playlist::Replace; + + if( args->isSet( "play" ) ) + options |= Playlist::DirectPlay; + + Playlist::instance()->insertMedia( list, options ); + } + + //we shouldn't let the user specify two of these since it is pointless! + //so we prioritise, pause > stop > play > next > prev + //thus pause is the least destructive, followed by stop as brakes are the most important bit of a car(!) + //then the others seemed sensible. Feel free to modify this order, but please leave justification in the cvs log + //I considered doing some sanity checks (eg only stop if paused or playing), but decided it wasn't worth it + else if ( args->isSet( "pause" ) ) + { + haveArgs = true; + EngineController::instance()->pause(); + } + else if ( args->isSet( "stop" ) ) + { + haveArgs = true; + EngineController::instance()->stop(); + } + else if ( args->isSet( "play-pause" ) ) + { + haveArgs = true; + EngineController::instance()->playPause(); + } + else if ( args->isSet( "play" ) ) //will restart if we are playing + { + haveArgs = true; + EngineController::instance()->play(); + } + else if ( args->isSet( "next" ) ) + { + haveArgs = true; + EngineController::instance()->next(); + } + else if ( args->isSet( "previous" ) ) + { + haveArgs = true; + EngineController::instance()->previous(); + } + else if (args->isSet("cdplay")) + { + haveArgs = true; + QString device = args->getOption("cdplay"); + device = DeviceManager::instance()->convertMediaUrlToDevice( device ); + KURL::List urls; + if (EngineController::engine()->getAudioCDContents(device, urls)) { + Playlist::instance()->insertMedia( + urls, Playlist::Replace|Playlist::DirectPlay); + } else { // Default behaviour + debug() << + "Sorry, the engine doesn't support direct play from AudioCD..." + << endl; + } + } + + if ( args->isSet( "toggle-playlist-window" ) ) + { + haveArgs = true; + pApp->m_pPlaylistWindow->showHide(); + } + + static bool firstTime = true; + if( !firstTime && !haveArgs ) + pApp->m_pPlaylistWindow->activate(); + firstTime = false; + + args->clear(); //free up memory +} + + +///////////////////////////////////////////////////////////////////////////////////// +// INIT +///////////////////////////////////////////////////////////////////////////////////// + +void App::initCliArgs( int argc, char *argv[] ) //static +{ + static KCmdLineOptions options[] = + { + { "+[URL(s)]", I18N_NOOP( "Files/URLs to open" ), 0 }, + { "r", 0, 0 }, + { "previous", I18N_NOOP( "Skip backwards in playlist" ), 0 }, + { "p", 0, 0 }, + { "play", I18N_NOOP( "Start playing current playlist" ), 0 }, + { "t", 0, 0 }, + { "play-pause", I18N_NOOP( "Play if stopped, pause if playing" ), 0 }, + { "pause", I18N_NOOP( "Pause playback" ), 0 }, + { "s", 0, 0 }, + { "stop", I18N_NOOP( "Stop playback" ), 0 }, + { "f", 0, 0 }, + { "next", I18N_NOOP( "Skip forwards in playlist" ), 0 }, + { ":", I18N_NOOP("Additional options:"), 0 }, + { "a", 0, 0 }, + { "append", I18N_NOOP( "Append files/URLs to playlist" ), 0 }, + { "e", 0, 0 }, + { "enqueue", I18N_NOOP("See append, available for backwards compatability"), 0 }, + { "queue", I18N_NOOP("Queue URLs after the currently playing track"), 0 }, + { "l", 0, 0 }, + { "load", I18N_NOOP("Load URLs, replacing current playlist"), 0 }, + { "m", 0, 0 }, + { "toggle-playlist-window", I18N_NOOP("Toggle the Playlist-window"), 0 }, + { "wizard", I18N_NOOP( "Run first-run wizard" ), 0 }, + { "engine ", I18N_NOOP( "Use the engine" ), 0 }, + { "cwd ", I18N_NOOP( "Base for relative filenames/URLs" ), 0 }, + { "cdplay ", I18N_NOOP("Play an AudioCD from "), 0 }, + //FIXME: after string freeze { "cdplay ", I18N_NOOP("Play an AudioCD from or system:/media/"), 0 }, + { 0, 0, 0 } + }; + + KCmdLineArgs::reset(); + KCmdLineArgs::init( argc, argv, &::aboutData ); //calls KApplication::addCmdLineOptions() + KCmdLineArgs::addCmdLineOptions( options ); //add our own options +} + + +#include +#include +void App::initGlobalShortcuts() +{ + EngineController* const ec = EngineController::instance(); + + m_pGlobalAccel->insert( "play", i18n( "Play" ), 0, KKey("WIN+x"), 0, + ec, SLOT( play() ), true, true ); + m_pGlobalAccel->insert( "pause", i18n( "Pause" ), 0, 0, 0, + ec, SLOT( pause() ), true, true ); + m_pGlobalAccel->insert( "play_pause", i18n( "Play/Pause" ), 0, KKey("WIN+c"), 0, + ec, SLOT( playPause() ), true, true ); + m_pGlobalAccel->insert( "stop", i18n( "Stop" ), 0, KKey("WIN+v"), 0, + ec, SLOT( stop() ), true, true ); + m_pGlobalAccel->insert( "stop_after_global", i18n( "Stop Playing After Current Track" ), 0, KKey("WIN+CTRL+v"), 0, + Playlist::instance()->qscrollview(), SLOT( toggleStopAfterCurrentTrack() ), true, true ); + m_pGlobalAccel->insert( "next", i18n( "Next Track" ), 0, KKey("WIN+b"), 0, + ec, SLOT( next() ), true, true ); + m_pGlobalAccel->insert( "prev", i18n( "Previous Track" ), 0, KKey("WIN+z"), 0, + ec, SLOT( previous() ), true, true ); + m_pGlobalAccel->insert( "volup", i18n( "Increase Volume" ), 0, KKey("WIN+KP_Add"), 0, + ec, SLOT( increaseVolume() ), true, true ); + m_pGlobalAccel->insert( "voldn", i18n( "Decrease Volume" ), 0, KKey("WIN+KP_Subtract"), 0, + ec, SLOT( decreaseVolume() ), true, true ); + m_pGlobalAccel->insert( "seekforward", i18n( "Seek Forward" ), 0, KKey("WIN+Shift+KP_Add"), 0, + ec, SLOT( seekForward() ), true, true ); + m_pGlobalAccel->insert( "seekbackward", i18n( "Seek Backward" ), 0, KKey("WIN+Shift+KP_Subtract"), 0, + ec, SLOT( seekBackward() ), true, true ); + m_pGlobalAccel->insert( "playlist_add", i18n( "Add Media..." ), 0, KKey("WIN+a"), 0, + m_pPlaylistWindow, SLOT( slotAddLocation() ), true, true ); + m_pGlobalAccel->insert( "show", i18n( "Toggle Playlist Window" ), 0, KKey("WIN+p"), 0, + m_pPlaylistWindow, SLOT( showHide() ), true, true ); +#ifdef Q_WS_X11 + m_pGlobalAccel->insert( "osd", i18n( "Show OSD" ), 0, KKey("WIN+o"), 0, + Amarok::OSD::instance(), SLOT( forceToggleOSD() ), true, true ); +#endif + m_pGlobalAccel->insert( "mute", i18n( "Mute Volume" ), 0, KKey("WIN+m"), 0, + ec, SLOT( mute() ), true, true ); + + m_pGlobalAccel->insert( "rating1", i18n( "Rate Current Track: 1" ), 0, KKey("WIN+1"), 0, + this, SLOT( setRating1() ), true, true ); + m_pGlobalAccel->insert( "rating2", i18n( "Rate Current Track: 2" ), 0, KKey("WIN+2"), 0, + this, SLOT( setRating2() ), true, true ); + m_pGlobalAccel->insert( "rating3", i18n( "Rate Current Track: 3" ), 0, KKey("WIN+3"), 0, + this, SLOT( setRating3() ), true, true ); + m_pGlobalAccel->insert( "rating4", i18n( "Rate Current Track: 4" ), 0, KKey("WIN+4"), 0, + this, SLOT( setRating4() ), true, true ); + m_pGlobalAccel->insert( "rating5", i18n( "Rate Current Track: 5" ), 0, KKey("WIN+5"), 0, + this, SLOT( setRating5() ), true, true ); + + m_pGlobalAccel->setConfigGroup( "Shortcuts" ); + m_pGlobalAccel->readSettings( kapp->config() ); + m_pGlobalAccel->updateConnections(); + + //TODO fix kde accel system so that kactions find appropriate global shortcuts + // and there is only one configure shortcuts dialog + + KActionCollection* const ac = Amarok::actionCollection(); + KAccelShortcutList list( m_pGlobalAccel ); + + for( uint i = 0; i < list.count(); ++i ) + { + KAction *action = ac->action( list.name( i ).latin1() ); + + if( action ) + { + //this is a hack really, also it means there may be two calls to the slot for the shortcut + action->setShortcutConfigurable( false ); + action->setShortcut( list.shortcut( i ) ); + } + } +} + + +void App::fixHyperThreading() +{ + /** Workaround for stability issues with HyperThreading CPU's, @see BUG 99199. + * First we detect the presence of HyperThreading. If active, we bind amarokapp + * to the first CPU only (hard affinity). + * + * @see http://www-128.ibm.com/developerworks/linux/library/l-affinity.html + * @see http://www.linuxjournal.com/article/6799 + * (articles on processor affinity with the linux kernel) + */ + + DEBUG_BLOCK + + #ifdef __linux__ + QString line; + uint cpuCount = 0; + QFile cpuinfo( "/proc/cpuinfo" ); + if ( cpuinfo.open( IO_ReadOnly ) ) { + while ( cpuinfo.readLine( line, 20000 ) != -1 ) { + if ( line.startsWith( "flags" ) ) + cpuCount++; + } + } + // If multiple CPUs are listed with the HT flag, we got HyperThreading enabled + if ( cpuCount > 1 ) { + debug() << "SMP system detected. Enabling WORKAROUND.\n"; + + // If the library is new enough try and call sched_setaffinity. + #ifdef SCHEDAFFINITY_SUPPORT + cpu_set_t mask; + CPU_ZERO( &mask ); // Initializes all the bits in the mask to zero + CPU_SET( 0, &mask ); // Sets only the bit corresponding to cpu + #ifdef SCHEDAFFINITY_3PARAMS + if ( sched_setaffinity( 0, sizeof(mask), &mask ) == -1 ) + #else //SCHEDAFFINITY_3PARAMS + if ( sched_setaffinity( 0, &mask ) == -1 ) + #endif //SCHEDAFFINITY_3PARAMS + { + warning() << "sched_setaffinity() call failed with error code: " << errno << endl; + QTimer::singleShot( 0, this, SLOT( showHyperThreadingWarning() ) ); + return; + } + #else //SCHEDAFFINITY_SUPPORT + warning()<<"glibc failed checks for sched_setaffinity" << endl; + QTimer::singleShot( 0, this, SLOT( showHyperThreadingWarning() ) ); + #endif //SCHEDAFFINITY_SUPPORT + } + else { debug() << "Workaround not enabled" << endl; } + #else //__linux__ + debug() << "SCHEDAFFINITY_SUPPORT disabled since this isn't Linux" << endl; + #endif //__linux__ +} + + +void App::showHyperThreadingWarning() // SLOT +{ + const QString text = + i18n( "

You are using a system with multiple CPUs. " + "Please note that Amarok may be unstable with this " + "configuration.

" + "

If your system has hyperthreading, you can improve Amarok's stability by using the Linux kernel option 'NOHT', " + "or by disabling HyperThreading in your BIOS setup.

" + "

More information can be found in the README file. For further assistance " + "join us at #amarok on irc.freenode.net.

" ); + + KMessageBox::information( 0, text, i18n( "Warning" ), "showHyperThreadingWarning" ); +} + + +///////////////////////////////////////////////////////////////////////////////////// +// METHODS +///////////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include + +//this class is only used in this module, so I figured I may as well define it +//here and save creating another header/source file combination + +class ID3v1StringHandler : public TagLib::ID3v1::StringHandler +{ + QTextCodec *m_codec; + + virtual TagLib::String parse( const TagLib::ByteVector &data ) const + { + return QStringToTString( m_codec->toUnicode( data.data(), data.size() ) ); + } + + virtual TagLib::ByteVector render( const TagLib::String &ts ) const + { + const QCString qcs = m_codec->fromUnicode( TStringToQString(ts) ); + return TagLib::ByteVector( qcs, qcs.length() ); + } + +public: + ID3v1StringHandler( int codecIndex ) + : m_codec( QTextCodec::codecForIndex( codecIndex ) ) + { + debug() << "codec: " << m_codec << endl; + debug() << "codec-name: " << m_codec->name() << endl; + } + + ID3v1StringHandler( QTextCodec *codec ) + : m_codec( codec ) + { + debug() << "codec: " << m_codec << endl; + debug() << "codec-name: " << m_codec->name() << endl; + } +}; + +//SLOT +void App::applySettings( bool firstTime ) +{ + ///Called when the configDialog is closed with OK or Apply + + DEBUG_BLOCK + + //determine and apply colors first + applyColorScheme(); + +#ifdef Q_WS_X11 + TrackToolTip::instance()->removeFromWidget( m_pTray ); +#endif + + if( AmarokConfig::showPlayerWindow() ) + { + if( !m_pPlayerWindow ) + { + //the player Window becomes the main Window + //it is the focus for hideWithMainWindow behaviour etc. + //it gets the majestic "Amarok" caption + m_pPlaylistWindow->setCaption( kapp->makeStdCaption( i18n("Playlist") ) ); + + m_pPlayerWindow = new PlayerWidget( m_pPlaylistWindow, "PlayerWindow", firstTime && AmarokConfig::playlistWindowEnabled() ); + + //don't show PlayerWindow on firstTime, that is done below + //we need to explicately set the PL button if it's the first time + if( !firstTime ) m_pPlayerWindow->show(); + + + connect( m_pPlayerWindow, SIGNAL(playlistToggled( bool )), m_pPlaylistWindow, SLOT(showHide()) ); + +#ifdef Q_WS_X11 + //TODO get this to work! + //may work if you set no parent for the systray? + //KWin::setSystemTrayWindowFor( m_pTray->winId(), m_pPlayerWindow->winId() ); + + delete m_pTray; m_pTray = new Amarok::TrayIcon( m_pPlayerWindow ); + + //make tray icon behave properly after selecting to show or hide player window + m_pTray->engineStateChanged(EngineController::instance()->engine()->state(), EngineController::instance()->engine()->state()); + m_pTray->engineNewMetaData(EngineController::instance()->bundle(), false); +#endif + + //make player window minimal if it was last time + if( AmarokConfig::playerWindowMinimalView() ){ + m_pPlayerWindow->setMinimalView( true ); + } + } + else + //this is called in the PlayerWindow ctor, hence the else + m_pPlayerWindow->applySettings(); + + } else if( m_pPlayerWindow ) { +#ifdef Q_WS_X11 + delete m_pTray; m_pTray = new Amarok::TrayIcon( m_pPlaylistWindow ); + m_pTray->engineStateChanged(EngineController::instance()->engine()->state(), EngineController::instance()->engine()->state()); + m_pTray->engineNewMetaData(EngineController::instance()->bundle(), false); +#endif + delete m_pPlayerWindow; m_pPlayerWindow = 0; + + //Set the caption correctly. + if ( !EngineController::instance()->bundle().prettyTitle().isEmpty() ) + m_pPlaylistWindow->setCaption( i18n("Amarok - %1").arg( EngineController::instance()->bundle().veryNiceTitle() ) ); + else + m_pPlaylistWindow->setCaption( "Amarok" ); + } + + playlistWindow()->applySettings(); + Scrobbler::instance()->applySettings(); + Amarok::OSD::instance()->applySettings(); + CollectionDB::instance()->applySettings(); +#ifdef Q_WS_X11 + m_pTray->setShown( AmarokConfig::showTrayIcon() ); + TrackToolTip::instance()->addToWidget( m_pTray ); +#endif + + + //on startup we need to show the window, but only if it wasn't hidden on exit + //and always if the trayicon isn't showing + QWidget* main_window = mainWindow(); +#ifdef Q_WS_X11 + if( ( main_window && firstTime && !Amarok::config()->readBoolEntry( "HiddenOnExit", false ) ) || ( main_window && !AmarokConfig::showTrayIcon() ) ) +#endif + { + main_window->show(); + + //takes longer but feels shorter. Crazy eh? :) + kapp->eventLoop()->processEvents( QEventLoop::ExcludeUserInput ); + } + + + { // + EngineBase *engine = EngineController::engine(); + + if( firstTime || AmarokConfig::soundSystem() != + PluginManager::getService( engine )->property( "X-KDE-Amarok-name" ).toString() ) + { + //will unload engine for us first if necessary + engine = EngineController::instance()->loadEngine(); + } + + engine->setXfadeLength( AmarokConfig::crossfade() ? AmarokConfig::crossfadeLength() : 0 ); + engine->setVolume( AmarokConfig::masterVolume() ); + + engine->setEqualizerEnabled( AmarokConfig::equalizerEnabled() ); + if ( AmarokConfig::equalizerEnabled() ) + engine->setEqualizerParameters( AmarokConfig::equalizerPreamp(), AmarokConfig::equalizerGains() ); + + Amarok::actionCollection()->action("play_audiocd")->setEnabled( EngineController::hasEngineProperty( "HasKIO" ) || EngineController::hasEngineProperty("HasCDDA")); + } // + + { // + CollectionView::instance()->renderView(true); + } // + { // + ContextBrowser::instance()->renderView(); + } // + + { // delete unneeded cover images from cache + const QString size = QString::number( AmarokConfig::coverPreviewSize() ) + '@'; + const QDir cacheDir = Amarok::saveLocation( "albumcovers/cache/" ); + const QStringList obsoleteCovers = cacheDir.entryList( "*" ); + foreach( obsoleteCovers ) + if ( !(*it).startsWith( size ) && !(*it).startsWith( "50@" ) ) + QFile( cacheDir.filePath( *it ) ).remove(); + } + + //if ( !firstTime ) + // Bizarrely and ironically calling this causes crashes for + // some people! FIXME + //AmarokConfig::writeConfig(); + +} + +//SLOT +void +App::continueInit() +{ + DEBUG_BLOCK + const KCmdLineArgs* const args = KCmdLineArgs::parsedArgs(); + bool restoreSession = args->count() == 0 || args->isSet( "append" ) || args->isSet( "enqueue" ) + || Amarok::config()->readBoolEntry( "AppendAsDefault", false ); + + // Make this instance so it can start receiving signals + MoodServer::instance(); + + // Remember old folder setup, so we can detect changes after the wizard was used + //const QStringList oldCollectionFolders = MountPointManager::instance()->collectionFolders(); + + + if ( Amarok::config()->readBoolEntry( "First Run", true ) || args->isSet( "wizard" ) ) { + std::cout << "STARTUP\n" << std::flush; //hide the splashscreen + firstRunWizard(); + Amarok::config()->writeEntry( "First Run", false ); + Amarok::config()->sync(); + } + + CollectionDB::instance()->checkDatabase(); + + m_pMediaDeviceManager = MediaDeviceManager::instance(); + m_pGlobalAccel = new KGlobalAccel( this ); + m_pPlaylistWindow = new PlaylistWindow(); +#ifdef Q_WS_X11 + m_pTray = new Amarok::TrayIcon( m_pPlaylistWindow ); +#endif + m_pPlaylistWindow->init(); //creates the playlist, browsers, etc. + //init playlist window as soon as the database is guaranteed to be usable + //connect( CollectionDB::instance(), SIGNAL( databaseUpdateDone() ), m_pPlaylistWindow, SLOT( init() ) ); + initGlobalShortcuts(); + //load previous playlist in separate thread + if ( restoreSession && AmarokConfig::savePlaylist() ) + { + Playlist::instance()->restoreSession(); + //Debug::stamp(); + //p->restoreSession(); + } + if( args->isSet( "engine" ) ) { + // we correct some common errors (case issues, missing -engine off the end) + QString engine = args->getOption( "engine" ).lower(); + if( engine.startsWith( "gstreamer" ) ) engine = "gst-engine"; + if( !engine.endsWith( "engine" ) ) engine += "-engine"; + + AmarokConfig::setSoundSystem( engine ); + } + Debug::stamp(); + //create engine, show PlayerWindow, show TrayIcon etc. + applySettings( true ); + Debug::stamp(); + // Start ScriptManager. Must be created _after_ PlaylistWindow. + ScriptManager::instance(); + Debug::stamp(); + //notify loader application that we have started + std::cout << "STARTUP\n" << std::flush; + + //after this point only analyzer and temporary pixmaps will be created + QPixmap::setDefaultOptimization( QPixmap::BestOptim ); + + //do after applySettings(), or the OSD will flicker and other wierdness! + //do before restoreSession()! + EngineController::instance()->attach( this ); + + //set a default interface + engineStateChanged( Engine::Empty ); + + if ( AmarokConfig::resumePlayback() && restoreSession && !args->isSet( "stop" ) ) { + //restore session as long as the user didn't specify media to play etc. + //do this after applySettings() so OSD displays correctly + EngineController::instance()->restoreSession(); + } + + CollectionDB *collDB = CollectionDB::instance(); + //Collection scan is triggered in firstRunWizard if the colelction folder setup was changed in the wizard + + // If database version is updated, the collection needs to be rescanned. + // Works also if the collection is empty for some other reason + // (e.g. deleted collection.db) + if ( CollectionDB::instance()->isEmpty() ) + { + //connect( collDB, SIGNAL( databaseUpdateDone() ), collDB, SLOT( startScan() ) ); + collDB->startScan(); + } + else if ( AmarokConfig::monitorChanges() ) + //connect( collDB, SIGNAL( databaseUpdateDone() ), collDB, SLOT( scanModifiedDirs() ) ); + collDB->scanModifiedDirs(); + + + handleCliArgs(); +} + +void +App::applyColorScheme() +{ + QColorGroup group; + using Amarok::ColorScheme::AltBase; + int h, s, v; + QWidget* const browserBar = static_cast( playlistWindow()->child( "BrowserBar" ) ); + QWidget* const contextBrowser = static_cast( ContextBrowser::instance() ); + + if( AmarokConfig::schemeKDE() ) + { + AltBase = KGlobalSettings::alternateBackgroundColor(); + + playlistWindow()->unsetPalette(); + browserBar->unsetPalette(); + contextBrowser->unsetPalette(); + + PlayerWidget::determineAmarokColors(); + } + + else if( AmarokConfig::schemeAmarok() ) + { + group = QApplication::palette().active(); + const QColor bg( Amarok::blue ); + AltBase.setRgb( 57, 64, 98 ); + + group.setColor( QColorGroup::Text, Qt::white ); + group.setColor( QColorGroup::Link, 0xCCCCCC ); + group.setColor( QColorGroup::Base, bg ); + group.setColor( QColorGroup::Foreground, 0xd7d7ef ); + group.setColor( QColorGroup::Background, AltBase ); + + group.setColor( QColorGroup::Button, AltBase ); + group.setColor( QColorGroup::ButtonText, 0xd7d7ef ); + +// group.setColor( QColorGroup::Light, Qt::cyan /*lighter than Button color*/ ); +// group.setColor( QColorGroup::Midlight, Qt::blue /*between Button and Light*/ ); +// group.setColor( QColorGroup::Dark, Qt::green /*darker than Button*/ ); +// group.setColor( QColorGroup::Mid, Qt::red /*between Button and Dark*/ ); +// group.setColor( QColorGroup::Shadow, Qt::yellow /*a very dark color. By default, the shadow color is Qt::black*/ ); + + group.setColor( QColorGroup::Highlight, Qt::white ); + group.setColor( QColorGroup::HighlightedText, bg ); + //group.setColor( QColorGroup::BrightText, QColor( 0xff, 0x40, 0x40 ) ); //GlowColor + + AltBase.getHsv( &h, &s, &v ); + group.setColor( QColorGroup::Midlight, QColor( h, s/3, (int)(v * 1.2), QColor::Hsv ) ); //column separator in playlist + + //TODO set all colours, even button colours, that way we can change the dark, + //light, etc. colours and Amarok scheme will look much better + + using namespace Amarok::ColorScheme; + Base = Amarok::blue; + Text = Qt::white; + Background = 0x002090; + Foreground = 0x80A0FF; + + //all children() derive their palette from this + playlistWindow()->setPalette( QPalette( group, group, group ) ); + browserBar->unsetPalette(); + contextBrowser->setPalette( QPalette( group, group, group ) ); + } + + else if( AmarokConfig::schemeCustom() ) + { + // we try to be smart: this code figures out contrasting colors for + // selection and alternate background rows + group = QApplication::palette().active(); + const QColor fg( AmarokConfig::playlistWindowFgColor() ); + const QColor bg( AmarokConfig::playlistWindowBgColor() ); + + //TODO use the ensureContrast function you devised in BlockAnalyzer + + bg.hsv( &h, &s, &v ); + v += (v < 128) ? +50 : -50; + v &= 255; //ensures 0 <= v < 256 + AltBase.setHsv( h, s, v ); + + fg.hsv( &h, &s, &v ); + v += (v < 128) ? +150 : -150; + v &= 255; //ensures 0 <= v < 256 + QColor highlight( h, s, v, QColor::Hsv ); + + group.setColor( QColorGroup::Base, bg ); + group.setColor( QColorGroup::Background, bg.dark( 115 ) ); + group.setColor( QColorGroup::Text, fg ); + group.setColor( QColorGroup::Link, fg.light( 120 ) ); + group.setColor( QColorGroup::Highlight, highlight ); + group.setColor( QColorGroup::HighlightedText, Qt::white ); + group.setColor( QColorGroup::Dark, Qt::darkGray ); + + PlayerWidget::determineAmarokColors(); + + // we only colour the middle section since we only + // allow the user to choose two colours + browserBar->setPalette( QPalette( group, group, group ) ); + contextBrowser->setPalette( QPalette( group, group, group ) ); + playlistWindow()->unsetPalette(); + } + + // set the KListView alternate colours + QObjectList* const list = playlistWindow()->queryList( "KListView" ); + for( QObject *o = list->first(); o; o = list->next() ) + static_cast(o)->setAlternateBackground( AltBase ); + delete list; //heap allocated! +} + + +bool Amarok::genericEventHandler( QWidget *recipient, QEvent *e ) +{ + //this is used as a generic event handler for widgets that want to handle + //typical events in an Amarok fashion + + //to use it just pass the event eg: + // + // void Foo::barEvent( QBarEvent *e ) + // { + // Amarok::genericEventHandler( this, e ); + // } + + switch( e->type() ) + { + case QEvent::DragEnter: + #define e static_cast(e) + e->accept( KURLDrag::canDecode( e ) ); + break; + + case QEvent::Drop: + if( KURLDrag::canDecode( e ) ) + { + QPopupMenu popup; + //FIXME this isn't a good way to determine if there is a currentTrack, need playlist() function + const bool b = EngineController::engine()->loaded(); + + popup.insertItem( SmallIconSet( Amarok::icon( "add_playlist" ) ), i18n( "&Append to Playlist" ), + Playlist::Append ); + popup.insertItem( SmallIconSet( Amarok::icon( "add_playlist" ) ), i18n( "Append && &Play" ), + Playlist::DirectPlay | Playlist::Append ); + if( b ) + popup.insertItem( SmallIconSet( Amarok::icon( "fast_forward" ) ), i18n( "&Queue Track" ), + Playlist::Queue ); + popup.insertSeparator(); + popup.insertItem( i18n( "&Cancel" ), 0 ); + + const int id = popup.exec( recipient->mapToGlobal( e->pos() ) ); + KURL::List list; + KURLDrag::decode( e, list ); + + if ( id > 0 ) + Playlist::instance()->insertMedia( list, id ); + } + else return false; + #undef e + + break; + + //this like every entry in the generic event handler is used by more than one widget + //please don't remove! + case QEvent::Wheel: + { + #define e static_cast(e) + + //this behaviour happens for the systray and the player window + //to override one, override it in that class + + switch( e->state() ) + { + case Qt::ControlButton: + { + const bool up = e->delta() > 0; + + //if this seems strange to you, please bring it up on #amarok + //for discussion as we can't decide which way is best! + if( up ) EngineController::instance()->previous(); + else EngineController::instance()->next(); + break; + } + case Qt::ShiftButton: + { + EngineController::instance()->seekRelative( ( e->delta() / 120 ) * 10000 ); // 10 seconds + break; + } + default: + EngineController::instance()->increaseVolume( e->delta() / Amarok::VOLUME_SENSITIVITY ); + } + + e->accept(); + #undef e + + break; + } + + case QEvent::Close: + + //KDE policy states we should hide to tray and not quit() when the + //close window button is pushed for the main widget + + static_cast(e)->accept(); //if we don't do this the info box appears on quit()! + + if( AmarokConfig::showTrayIcon() && !e->spontaneous() && !kapp->sessionSaving() ) + { + KMessageBox::information( recipient, + i18n( "Closing the main-window will keep Amarok running in the System Tray. " + "Use Quit from the menu, or the Amarok tray-icon to exit the application." ), + i18n( "Docking in System Tray" ), "hideOnCloseInfo" ); + } + else pApp->quit(); + + break; + + default: + return false; + } + + return true; +} + + +void App::engineStateChanged( Engine::State state, Engine::State oldState ) +{ + const MetaBundle &bundle = EngineController::instance()->bundle(); + switch( state ) + { + case Engine::Empty: + if ( AmarokConfig::showPlayerWindow() ) + m_pPlaylistWindow->setCaption( kapp->makeStdCaption( i18n("Playlist") ) ); + else m_pPlaylistWindow->setCaption( "Amarok" ); + TrackToolTip::instance()->clear(); + Amarok::OSD::instance()->setImage( KIconLoader().iconPath( "amarok", -KIcon::SizeHuge ) ); + break; + + case Engine::Playing: + if ( oldState == Engine::Paused ) + Amarok::OSD::instance()->OSDWidget::show( i18n( "state, as in playing", "Play" ) ); + if ( !bundle.prettyTitle().isEmpty() ) + m_pPlaylistWindow->setCaption( i18n("Amarok - %1").arg( bundle.veryNiceTitle() ) ); + break; + + case Engine::Paused: + Amarok::OSD::instance()->OSDWidget::show( i18n("Paused") ); + break; + + case Engine::Idle: + if ( AmarokConfig::showPlayerWindow() ) + m_pPlaylistWindow->setCaption( kapp->makeStdCaption( i18n("Playlist") ) ); + else m_pPlaylistWindow->setCaption( "Amarok" ); + break; + + default: + ; + } +} + +void App::engineNewMetaData( const MetaBundle &bundle, bool /*trackChanged*/ ) +{ + Amarok::OSD::instance()->show( bundle ); + if ( !bundle.prettyTitle().isEmpty() ) + m_pPlaylistWindow->setCaption( i18n("Amarok - %1").arg( bundle.veryNiceTitle() ) ); + + TrackToolTip::instance()->setTrack( bundle ); +} + +void App::engineTrackPositionChanged( long position, bool /*userSeek*/ ) +{ + TrackToolTip::instance()->setPos( position ); +} + +void App::engineVolumeChanged( int newVolume ) +{ + Amarok::OSD::instance()->OSDWidget::volChanged( newVolume ); +} + +void App::slotConfigEqualizer() //SLOT +{ + EqualizerSetup::instance()->show(); + EqualizerSetup::instance()->raise(); +} + + +void App::slotConfigAmarok( const QCString& page ) +{ + DEBUG_THREAD_FUNC_INFO + + AmarokConfigDialog* dialog = static_cast( KConfigDialog::exists( "settings" ) ); + + if( !dialog ) + { + //KConfigDialog didn't find an instance of this dialog, so lets create it : + dialog = new AmarokConfigDialog( m_pPlaylistWindow, "settings", AmarokConfig::self() ); + + connect( dialog, SIGNAL(settingsChanged()), SLOT(applySettings()) ); + } + + //FIXME it seems that if the dialog is on a different desktop it gets lost + // what do to? detect and move it? + + if ( page.isNull() ) + dialog->showPage( AmarokConfigDialog::s_currentPage ); + else + dialog->showPageByName( page ); + + dialog->show(); + dialog->raise(); + dialog->setActiveWindow(); +} + +void App::slotConfigShortcuts() +{ + KKeyDialog::configure( Amarok::actionCollection(), m_pPlaylistWindow ); +} + +void App::slotConfigGlobalShortcuts() +{ + KKeyDialog::configure( m_pGlobalAccel, true, m_pPlaylistWindow, true ); +} + +void App::slotConfigToolBars() +{ + PlaylistWindow* const pw = playlistWindow(); + KEditToolbar dialog( pw->actionCollection(), pw->xmlFile(), true, pw ); + + dialog.showButtonApply( false ); + + if( dialog.exec() ) + { + playlistWindow()->reloadXML(); + playlistWindow()->createGUI(); + } +} + +void App::firstRunWizard() +{ + ///show firstRunWizard + DEBUG_BLOCK + + FirstRunWizard wizard; + setTopWidget( &wizard ); + KConfigDialogManager* config = new KConfigDialogManager(&wizard, AmarokConfig::self(), "wizardconfig"); + config->updateWidgets(); + // connect(config, SIGNAL(settingsChanged()), SLOT(updateSettings())); + wizard.setCaption( makeStdCaption( i18n( "First-Run Wizard" ) ) ); + + if( wizard.exec() != QDialog::Rejected ) + { + //make sure that the DB config is stored in amarokrc before calling CollectionDB's ctor + AmarokConfig::setDatabaseEngine( + QString::number( Amarok::databaseTypeCode( wizard.dbSetup7->databaseEngine->currentText() ) ) ); + config->updateSettings(); + + const QStringList oldCollectionFolders = MountPointManager::instance()->collectionFolders(); + wizard.writeCollectionConfig(); + + // If wizard is invoked at runtime, rescan collection if folder setup has changed + if ( !Amarok::config()->readBoolEntry( "First Run", true ) && + oldCollectionFolders != MountPointManager::instance()->collectionFolders() ) + CollectionDB::instance()->startScan(); + + } +} + +void App::setUseScores( bool use ) +{ + AmarokConfig::setUseScores( use ); + emit useScores( use ); +} + +void App::setUseRatings( bool use ) +{ + AmarokConfig::setUseRatings( use ); + emit useRatings( use ); +} + +void App::setMoodbarPrefs( bool show, bool moodier, int alter, bool withMusic ) +{ + AmarokConfig::setShowMoodbar( show ); + AmarokConfig::setMakeMoodier( moodier ); + AmarokConfig::setAlterMood( alter ); + AmarokConfig::setMoodsWithMusic( withMusic ); + emit moodbarPrefs( show, moodier, alter, withMusic ); +} + +KIO::Job *App::trashFiles( const KURL::List &files ) +{ +#if KDE_IS_VERSION( 3, 3, 91 ) + KIO::Job *job = KIO::trash( files, true /*show progress*/ ); + Amarok::StatusBar::instance()->newProgressOperation( job ).setDescription( i18n("Moving files to trash") ); + connect( job, SIGNAL( result( KIO::Job* ) ), this, SLOT( slotTrashResult( KIO::Job* ) ) ); + return job; +#else + KIO::Job* job = KIO::move( files, KGlobalSettings::trashPath() ); + return job; +#endif +} + +void App::setRating( int n ) +{ + if( !AmarokConfig::useRatings() ) return; + + n *= 2; + + const Engine::State s = EngineController::instance()->engine()->state(); + if( s == Engine::Playing || s == Engine::Paused || s == Engine::Idle ) + { + const QString path = EngineController::instance()->playingURL().path(); + CollectionDB::instance()->setSongRating( path, n, true ); + const int rating = CollectionDB::instance()->getSongRating( path ); + EngineController::instance()->updateBundleRating( rating ); + Amarok::OSD::instance()->OSDWidget::ratingChanged( rating ); + if( !Amarok::OSD::instance()->isShown() && !PlaylistWindow::self()->isReallyShown() ) + Amarok::OSD::instance()->forceToggleOSD(); + } + else if( PlaylistWindow::self()->isReallyShown() && Playlist::instance()->qscrollview()->hasFocus() ) + Playlist::instance()->setSelectedRatings( n ); +} + +void App::slotTrashResult( KIO::Job *job ) +{ + if( job->error() ) + job->showErrorDialog( PlaylistWindow::self() ); +} + +QWidget *App::mainWindow() const +{ + return AmarokConfig::showPlayerWindow() ? static_cast( m_pPlayerWindow ) + : static_cast( m_pPlaylistWindow ); +} + +void App::quit() +{ + emit prepareToQuit(); + if( MediaBrowser::instance()->blockQuit() ) + { + // don't quit yet, as some media devices still have to finish transferring data + QTimer::singleShot( 100, this, SLOT( quit() ) ); + return; + } + KApplication::quit(); +} + +namespace Amarok +{ + /// @see amarok.h + + QWidget *mainWindow() + { + return pApp->playlistWindow(); + } + + KActionCollection *actionCollection() + { + return pApp->playlistWindow()->actionCollection(); + } + + KConfig *config( const QString &group ) + { + //Slightly more useful config() that allows setting the group simultaneously + kapp->config()->setGroup( group ); + return kapp->config(); + } + + bool invokeBrowser( const QString& url ) + { + //URL can be in whatever forms KURL::fromPathOrURL understands - ie most. + const QString cmd = "%1 \"%2\""; + return KRun::runCommand( cmd.arg( AmarokConfig::externalBrowser(), KURL::fromPathOrURL( url ).url() ) ) > 0; + } + + namespace ColorScheme + { + QColor Base; + QColor Text; + QColor Background; + QColor Foreground; + QColor AltBase; + } + + OverrideCursor::OverrideCursor( Qt::CursorShape cursor ) + { + QApplication::setOverrideCursor( cursor == Qt::WaitCursor ? KCursor::waitCursor() : KCursor::workingCursor() ); + } + + OverrideCursor::~OverrideCursor() + { + QApplication::restoreOverrideCursor(); + } + + QString saveLocation( const QString &directory ) + { + globalDirsMutex.lock(); + QString result = KGlobal::dirs()->saveLocation( "data", QString("amarok/") + directory, true ); + globalDirsMutex.unlock(); + return result; + } + + QString cleanPath( const QString &path ) + { + QString result = path; + // german umlauts + result.replace( QChar(0x00e4), "ae" ).replace( QChar(0x00c4), "Ae" ); + result.replace( QChar(0x00f6), "oe" ).replace( QChar(0x00d6), "Oe" ); + result.replace( QChar(0x00fc), "ue" ).replace( QChar(0x00dc), "Ue" ); + result.replace( QChar(0x00df), "ss" ); + + // some strange accents + result.replace( QChar(0x00e7), "c" ).replace( QChar(0x00c7), "C" ); + result.replace( QChar(0x00fd), "y" ).replace( QChar(0x00dd), "Y" ); + result.replace( QChar(0x00f1), "n" ).replace( QChar(0x00d1), "N" ); + + // czech letters with carons + result.replace( QChar(0x0161), "s" ).replace( QChar(0x0160), "S" ); + result.replace( QChar(0x010d), "c" ).replace( QChar(0x010c), "C" ); + result.replace( QChar(0x0159), "r" ).replace( QChar(0x0158), "R" ); + result.replace( QChar(0x017e), "z" ).replace( QChar(0x017d), "Z" ); + result.replace( QChar(0x0165), "t" ).replace( QChar(0x0164), "T" ); + result.replace( QChar(0x0148), "n" ).replace( QChar(0x0147), "N" ); + result.replace( QChar(0x010f), "d" ).replace( QChar(0x010e), "D" ); + + // accented vowels + QChar a[] = { 'a', 0xe0,0xe1,0xe2,0xe3,0xe5, 0 }; + QChar A[] = { 'A', 0xc0,0xc1,0xc2,0xc3,0xc5, 0 }; + QChar e[] = { 'e', 0xe8,0xe9,0xea,0xeb,0x11b, 0 }; + QChar E[] = { 'E', 0xc8,0xc9,0xca,0xcb,0x11a, 0 }; + QChar i[] = { 'i', 0xec,0xed,0xee,0xef, 0 }; + QChar I[] = { 'I', 0xcc,0xcd,0xce,0xcf, 0 }; + QChar o[] = { 'o', 0xf2,0xf3,0xf4,0xf5,0xf8, 0 }; + QChar O[] = { 'O', 0xd2,0xd3,0xd4,0xd5,0xd8, 0 }; + QChar u[] = { 'u', 0xf9,0xfa,0xfb,0x16f, 0 }; + QChar U[] = { 'U', 0xd9,0xda,0xdb,0x16e, 0 }; + QChar nul[] = { 0 }; + QChar *replacements[] = { a, A, e, E, i, I, o, O, u, U, nul }; + + for( uint i = 0; i < result.length(); i++ ) + { + QChar c = result.ref( i ); + for( uint n = 0; replacements[n][0] != QChar(0); n++ ) + { + for( uint k=0; replacements[n][k] != QChar(0); k++ ) + { + if( replacements[n][k] == c ) + { + c = replacements[n][0]; + } + } + } + result.ref( i ) = c; + } + return result; + } + + QString asciiPath( const QString &path ) + { + QString result = path; + for( uint i = 0; i < result.length(); i++ ) + { + QChar c = result.ref( i ); + if( c > QChar(0x7f) || c == QChar(0) ) + { + c = '_'; + } + result.ref( i ) = c; + } + return result; + } + + QString vfatPath( const QString &path ) + { + QString s = path; + + for( uint i = 0; i < s.length(); i++ ) + { + QChar c = s.ref( i ); + if( c < QChar(0x20) + || c=='*' || c=='?' || c=='<' || c=='>' + || c=='|' || c=='"' || c==':' || c=='/' + || c=='\\' ) + c = '_'; + s.ref( i ) = c; + } + + uint len = s.length(); + if( len == 3 || (len > 3 && s[3] == '.') ) + { + QString l = s.left(3).lower(); + if( l=="aux" || l=="con" || l=="nul" || l=="prn" ) + s = '_' + s; + } + else if( len == 4 || (len > 4 && s[4] == '.') ) + { + QString l = s.left(3).lower(); + QString d = s.mid(3,1); + if( (l=="com" || l=="lpt") && + (d=="0" || d=="1" || d=="2" || d=="3" || d=="4" || + d=="5" || d=="6" || d=="7" || d=="8" || d=="9") ) + s = '_' + s; + } + + while( s.startsWith( "." ) ) + s = s.mid(1); + + while( s.endsWith( "." ) ) + s = s.left( s.length()-1 ); + + s = s.left(255); + len = s.length(); + if( s[len-1] == ' ' ) + s[len-1] = '_'; + + return s; + } + + QString decapitateString( const QString &input, const QString &ref ) + { + QString t = ref.upper(); + int length = t.length(); + int commonLength = 0; + while( length > 0 ) + { + if ( input.upper().startsWith( t ) ) + { + commonLength = t.length(); + t = ref.upper().left( t.length() + length/2 ); + length = length/2; + } + else + { + t = ref.upper().left( t.length() - length/2 ); + length = length/2; + } + } + QString clean = input; + if( t.endsWith( " " ) || !ref.at( t.length() ).isLetterOrNumber() ) // common part ends with a space or complete word + clean = input.right( input.length() - commonLength ).stripWhiteSpace(); + return clean; + } + + void setUseScores( bool use ) { App::instance()->setUseScores( use ); } + void setUseRatings( bool use ) { App::instance()->setUseRatings( use ); } + void setMoodbarPrefs( bool show, bool moodier, int alter, bool withMusic ) + { App::instance()->setMoodbarPrefs( show, moodier, alter, withMusic ); } + KIO::Job *trashFiles( const KURL::List &files ) { return App::instance()->trashFiles( files ); } +} + +#include "app.moc" diff --git a/amarok/src/app.h b/amarok/src/app.h new file mode 100644 index 00000000..0ed7df27 --- /dev/null +++ b/amarok/src/app.h @@ -0,0 +1,125 @@ +/*************************************************************************** + app.h - description + ------------------- + begin : Mit Okt 23 14:35:18 CEST 2002 + copyright : (C) 2002 by Mark Kretschmann + email : markey@web.de +***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef AMAROK_APP_H +#define AMAROK_APP_H + +#include +#include "amarok_export.h" +#include "engineobserver.h" //baseclass +#include //baseclass +#include + +namespace Amarok { + class TrayIcon; +} + +namespace KIO { class Job; } + +class KActionCollection; +class KConfig; +class KGlobalAccel; +class MetaBundle; +class PlayerWidget; +class Playlist; +class PlaylistWindow; +class MediaDeviceManager; + +class LIBAMAROK_EXPORT App : public KApplication, public EngineObserver +{ + Q_OBJECT + public: + App(); + ~App(); + + static App *instance() { return static_cast( qApp ); } + + static void handleCliArgs(); + static void initCliArgs( int argc, char *argv[] ); + + static int mainThreadId; + + PlaylistWindow *playlistWindow() const { return m_pPlaylistWindow; } + PlayerWidget *playerWindow() const { return m_pPlayerWindow; } + + // FRIENDS ------ + friend class PlaylistWindow; //requires access to applySettings() + + signals: + void useScores( bool use ); + void useRatings( bool use ); + void moodbarPrefs( bool show, bool moodier, int alter, bool withMusic ); + void prepareToQuit(); + protected: /* for OSD, tray, and dcop */ + void engineStateChanged( Engine::State state, Engine::State oldState = Engine::Empty ); + void engineNewMetaData( const MetaBundle &bundle, bool trackChanged ); + void engineTrackPositionChanged( long position, bool /*userSeek*/ ); + void engineVolumeChanged( int ); + + private slots: + void showHyperThreadingWarning(); + void setRating1() { setRating( 1 ); } + void setRating2() { setRating( 2 ); } + void setRating3() { setRating( 3 ); } + void setRating4() { setRating( 4 ); } + void setRating5() { setRating( 5 ); } + void continueInit(); + + + public slots: + void applySettings( bool firstTime = false ); + void slotConfigAmarok( const QCString& page = QCString() ); + void slotConfigShortcuts(); + void slotConfigGlobalShortcuts(); + void slotConfigToolBars(); + void slotConfigEqualizer(); + void setUseScores( bool use ); + void setUseRatings( bool use ); + void setMoodbarPrefs( bool show, bool moodier, int alter, bool withMusic ); + KIO::Job *trashFiles( const KURL::List &files ); + void quit(); + + private slots: + void slotTrashResult( KIO::Job *job ); + + private: + /** Workaround for HyperThreading CPU's, @see BUG 99199 */ + void fixHyperThreading(); + + void initGlobalShortcuts(); + void applyColorScheme(); + void firstRunWizard(); + + /** returns the leading window, either playerWindow or playlistWindow */ + QWidget *mainWindow() const; + + void setRating( int n ); + + // ATTRIBUTES ------ + KGlobalAccel *m_pGlobalAccel; + PlayerWidget *m_pPlayerWindow; + PlaylistWindow *m_pPlaylistWindow; +#ifdef Q_WS_X11 + Amarok::TrayIcon *m_pTray; +#endif + MediaDeviceManager *m_pMediaDeviceManager; +}; + +#define pApp static_cast(kapp) + + +#endif // AMAROK_APP_H diff --git a/amarok/src/atomicstring.cpp b/amarok/src/atomicstring.cpp new file mode 100644 index 00000000..90fb3f92 --- /dev/null +++ b/amarok/src/atomicstring.cpp @@ -0,0 +1,175 @@ +/* + Copyright (c) 2006 Gábor Lehel + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include +#ifdef HAVE_STDINT_H +#include +#endif +#include +#include +#include + +#include "atomicstring.h" + +class AtomicString::Data: public QString +{ +public: + uint refcount; + Data(): refcount( 0 ) { } + Data( const QString &s ): QString( s ), refcount( 0 ) { } +}; + +AtomicString::AtomicString(): m_string( 0 ) { } + +AtomicString::AtomicString( const AtomicString &other ) +{ + s_storeMutex.lock(); + m_string = other.m_string; + ref( m_string ); + s_storeMutex.unlock(); +} + +AtomicString::AtomicString( const QString &string ): m_string( 0 ) +{ + if( string.isEmpty() ) + return; + + Data *s = new Data( string ); // note: s is a shallow copy + s_storeMutex.lock(); + m_string = static_cast( *( s_store.insert( s ).first ) ); + ref( m_string ); + uint rc = s->refcount; + if( rc && !isMainThread()) { + // Inserted, and we are not in the main thread -- we need to make s a deep copy, + // as this copy may be refcounted by the main thread outside our locks + (QString &) (*s) = QDeepCopy( string ); + } + s_storeMutex.unlock(); + if ( !rc ) delete( s ); // already present +} + +AtomicString::~AtomicString() +{ + s_storeMutex.lock(); + deref( m_string ); + s_storeMutex.unlock(); +} + +QString AtomicString::string() const +{ + if ( !m_string ) return QString(); + // References to the stored string are only allowed to circulate in the main thread + if ( isMainThread() ) return *m_string; + else return deepCopy(); +} + +QString AtomicString::deepCopy() const +{ + if (m_string) + return QString( m_string->unicode(), m_string->length() ); + return QString(); +} + +bool AtomicString::isEmpty() const +{ + return !m_string; +} + +const QString *AtomicString::ptr() const +{ + if( m_string ) + return m_string; + return &QString::null; +} + +uint AtomicString::refcount() const +{ + if ( m_string ) { + s_storeMutex.lock(); + uint rc = m_string->refcount; + s_storeMutex.unlock(); + return rc; + } + return 0; +} + +AtomicString &AtomicString::operator=( const AtomicString &other ) +{ + if( m_string == other.m_string ) + return *this; + s_storeMutex.lock(); + deref( m_string ); + m_string = other.m_string; + ref( m_string ); + s_storeMutex.unlock(); + return *this; +} + +// needs to be called holding the lock +inline void AtomicString::deref( Data *s ) +{ + checkLazyDeletes(); // a good time to do this + if( !s ) + return; + if( !( --s->refcount ) ) + { + s_store.erase( s ); + // only the main thread is allowed to delete stored strings + if ( isMainThread() ) + delete s; + else + s_lazyDeletes.append(s); + } +} + +// needs to be called holding the lock +inline void AtomicString::ref( Data *s ) +{ + checkLazyDeletes(); // a good time to do this + if( s ) + s->refcount++; +} + +// It is not necessary to hold the store mutex here. +bool AtomicString::isMainThread() +{ + // For isMainThread(), we could use QThread::currentThread(), except the + // docs say it's unreliable. And in general QThreads don't like to be called from + // app destructors. Good old pthreads will serve us well. As for Windows, these + // two calls surely have equivalents; better yet we'll have QT4 and thread safe + // QStrings by then. + // Note that the the static local init is thread safe. + static pthread_t main_thread = pthread_self(); + return pthread_equal(pthread_self(), main_thread); +} + +// call holding the store mutex +inline void AtomicString::checkLazyDeletes() +{ + // only the main thread is allowed to delete + if ( isMainThread() ) + { + s_lazyDeletes.setAutoDelete(true); + s_lazyDeletes.clear(); + } +} + +AtomicString::set_type AtomicString::s_store; +QPtrList AtomicString::s_lazyDeletes; +QMutex AtomicString::s_storeMutex; diff --git a/amarok/src/atomicstring.h b/amarok/src/atomicstring.h new file mode 100644 index 00000000..e81ae238 --- /dev/null +++ b/amarok/src/atomicstring.h @@ -0,0 +1,191 @@ +/* + Copyright (c) 2006 Gábor Lehel + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +/** + * A thin wrapper over QString which ensures only a single copy of string data + * is stored for equivalent strings. As a side benefit, testing for equality + * is reduced to a pointer comparison. Construction is slower than a QString, + * as it must be checked for equality with existing strings. (A hash set is + * used for this purpose. According to benchmarks, Paul Hsieh's SuperFastHash + * (which is currently used -- see http://www.azillionmonkeys.com/qed/hash.html) + * can hash 5 million 256 byte strings in 1.34s on a 1.62GHz Athlon XP.) For + * other use, the overhead compared to a plain QString should be minimal. + * + * Added note: due to QString's thread unsafe refcounting, special precautions have to be + * taken to avoid memory corruption, while still maintaining some level of efficiency. + * We deepCopy strings, unless we are in the same thread that *first* used + * AtomicStrings. Also, deletions from other threads are delayed until that first thread + * calls AtomicString again. Thus, we would appear to leak memory if many AtomicStrings + * are deleted in a different thread than the main thread, and the main thread would + * never call AtomicString again. But this is unlikely since the GUI thread is the one + * manipulating AtomicStrings mostly. You can call the static method + * AtomicString::isMainString first thing in the app to make sure the GUI thread is + * identified correctly. This workaround can be removed with QT4. + * + * @author Gábor Lehel + */ + +#ifndef AMAROK_ATOMICSTRING_H +#define AMAROK_ATOMICSTRING_H + +#include "config.h" +#include +#include "amarok_export.h" + +#include +#include +#include + +class LIBAMAROK_EXPORT AtomicString +{ +public: + /** + * Constructs an empty string. + * @see isEmpty + */ + AtomicString(); + + /** + * Constructs a copy of \p string. This operation takes constant time. + * @param string the string to copy + * @see operator= + */ + AtomicString( const AtomicString &other ); + + /** + * Constructs a copy of \p string. + * @param string the string to copy + */ + AtomicString( const QString &string ); + + /** + * Destroys the string. + * Note: this isn't virtual, to halve sizeof(). + */ + ~AtomicString(); + + /** + * Makes this string a copy of \p string. This operation takes constant time. + * @param string the string to copy + */ + AtomicString &operator=( const AtomicString &other ); + + /** + * This operation takes constant time. + * @return whether this string and \p string are equivalent + */ + bool operator==( const AtomicString &other ) const { return m_string == other.m_string; } + + + bool operator<( const AtomicString &other ) const { return m_string < other.m_string; } + +// bool operator!=( const AtomicString &other ) const { return m_string != other.m_string; } + + /** + * Returns a reference to this string, avoiding copies if possible. + * + * @return the string. + */ + QString string() const; + + /** + * Implicitly casts to a QString. + * @return the string + */ + inline operator QString() const { return string(); } + + /** + * Useful for threading. + * @return a deep copy of the string + */ + QString deepCopy() const; + + /** + * For convenience. Equivalent to isNull(). + * @return whether the string is empty + * @see isNull + */ + bool isEmpty() const; + + /** + * For convenience. Equivalent to isEmpty(). + * @return whether the string is empty + * @see isEmpty + */ + inline bool isNull() const { return isEmpty(); } + + /** + * Returns the internal pointer to the string. + * Guaranteed to be equivalent for equivalent strings, and different for + * different ones. This can be useful for certain kinds of hacks, but + * shouldn't normally be used. + * + * Note: DO NOT COPY this pointer with QString() or QString=. It is not + * thread safe to do it (QString internal refcount) + * @return the internal pointer to the string + */ + const QString *ptr() const; + + /** + * For convenience, so you can do atomicstring->QStringfunction(), + * instead of atomicstring.string().QStringfunction(). The same warning + * applies as for the above ptr() function. + */ + inline const QString *operator->() const { return ptr(); } + + /** + * For debugging purposes. + * @return the number of nonempty AtomicStrings equivalent to this one + */ + uint refcount() const; + + /** + * If called first thing in the app, this makes sure that AtomicString optimizes + * string usage for the main thread. + * @return true if this thread is considered the "main thread". + */ + static bool isMainThread(); + + +private: + struct less + { + bool operator()( const QString *a, const QString *b ) const + { return *a < *b; } + }; + typedef std::set set_type; + + class Data; + friend class Data; + + void ref( Data* ); + void deref( Data* ); + + static void checkLazyDeletes(); + + Data *m_string; + + // static data + static set_type s_store; // main string store + static QPtrList s_lazyDeletes; // strings scheduled for deletion + // by main thread + static QMutex s_storeMutex; // protects the static data above +}; + +#endif diff --git a/amarok/src/atomicstring_unittest.cpp b/amarok/src/atomicstring_unittest.cpp new file mode 100644 index 00000000..1f5d93e3 --- /dev/null +++ b/amarok/src/atomicstring_unittest.cpp @@ -0,0 +1,62 @@ +// Ovidiu Gheorghioiu , (C) 2006 +// License: GNU General Public License V2 +// +// Stress-test the AtomicString class for thread safety. Run on SMP for maximum exposure. +#include +#include +#include + +#include "atomicstring.h" + +void * +Worker(void *num) { + srand( reinterpret_cast( num ) ); + QString base = "str"; + // create 5 strings, destroy them, copy them around + const int kNumStrings = 5; + AtomicString *atStrings[kNumStrings * 2]; + for( int i = 0; i < kNumStrings * 2; i++ ) atStrings[i] = NULL; + const int kIterations = 100000; + for( int i = 0; i < kIterations; i++ ) { + int k = rand() % (kNumStrings * 2); + if( atStrings[k] == NULL ) { + // the upper half are sometimes copies of the corresponding + // lower half strings + if( k >= kNumStrings && atStrings[k % kNumStrings] != NULL ) { + atStrings[k] = new AtomicString( *atStrings[k % kNumStrings] ); + } else { + atStrings[k] = new AtomicString( base + QString::number( k ) ); + } + } else { + // check the string; could be either upper or lower + QString str = atStrings[k]->string(); + if( str != base + QString::number( k ) + && str != base + QString::number( k % kNumStrings ) ) { + qFatal( "unexpected atStrings[%d]: %s", k, str.ascii() ); + } + delete atStrings[k]; + atStrings[k] = NULL; + } + } + + return NULL; +} + +int main() { + const int kWorkers = 2; + + pthread_t workers[kWorkers]; + + for( int i = 0; i < kWorkers; i++ ) { + if( pthread_create(& workers[i], NULL, + & Worker, + reinterpret_cast(i)) + != 0) + qFatal( "Could not create thread %d", i ); + } + + for( int i = 0; i < kWorkers; i++ ) { + void *thread_return; + pthread_join( workers[i], &thread_return ); + } +} diff --git a/amarok/src/atomicurl.cpp b/amarok/src/atomicurl.cpp new file mode 100644 index 00000000..620ded36 --- /dev/null +++ b/amarok/src/atomicurl.cpp @@ -0,0 +1,127 @@ +/* + Copyright (c) 2005 Gábor Lehel + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include +#include +#include "debug.h" + +#include "atomicurl.h" + +AtomicURL::AtomicURL() { } + +AtomicURL::AtomicURL( const AtomicURL &other ) +{ + m_beginning = other.m_beginning; + m_directory = other.m_directory; + m_filename = other.m_filename; + m_end = other.m_end; +} + +AtomicURL::AtomicURL( const KURL &url ) +{ + if( url.isEmpty() ) + return; + + QString s = url.protocol() + "://"; + QString host = url.host(); + if( url.hasUser() ) + { + s += url.user(); + host.prepend("@"); + } + if( url.hasPass() ) + s += ':' + url.pass(); + if( url.port() ) + host += QString(":") + QString::number( url.port() ); + + m_beginning = s + host; + m_directory = url.directory(); + m_filename = url.fileName(); + m_end = url.query(); + if( url.hasRef() ) + m_end += QString("#") + url.ref(); + if (url != this->url()) + { + debug() << "from: " << url << endl; + debug() << "to: " << this->url() << endl; + } +} + +AtomicURL::~AtomicURL() { } + +AtomicURL &AtomicURL::operator=( const AtomicURL &other ) +{ + m_beginning = other.m_beginning; + m_directory = other.m_directory; + m_filename = other.m_filename; + return *this; +} + +bool AtomicURL::operator==( const AtomicURL &other ) const +{ + return m_filename == other.m_filename + && m_directory == other.m_directory + && m_beginning == other.m_beginning + && m_end == other.m_end; +} + +QString AtomicURL::string() const +{ + return m_beginning + path() + m_end; +} + +KURL AtomicURL::url() const +{ + if( isEmpty() ) + return KURL(); + + return KURL( string(), 106 ); +} + +bool AtomicURL::isEmpty() const +{ + return m_beginning->isEmpty() + && m_directory->isEmpty() + && m_filename.isEmpty() + && m_end.isEmpty(); +} + +void AtomicURL::setPath( const QString &path ) +{ + KURL url; + url.setPath( path ); + if( m_beginning->isEmpty() ) + *this = url; + else + { + m_directory = url.directory(); + m_filename = url.fileName(); + } +} + +QString AtomicURL::path() const +{ + if( !m_filename.isEmpty() && !m_directory->endsWith("/") ) + return m_directory + '/' + m_filename; + return m_directory + m_filename; +} + +QString AtomicURL::fileName() const { return m_filename; } + +QString AtomicURL::directory() const { return m_directory; } diff --git a/amarok/src/atomicurl.h b/amarok/src/atomicurl.h new file mode 100644 index 00000000..1f45ea7f --- /dev/null +++ b/amarok/src/atomicurl.h @@ -0,0 +1,72 @@ +/* + Copyright (c) 2005 Gábor Lehel + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +/** + While having two equivalent URLs is usually rare, parts of many URLs (the directories, mostly), + are often equivalent. AtomicURL uses AtomicStrings internally for the separate parts to try and + reduce memory usage for large numbers of URLs. +**/ + +#ifndef AMAROK_ATOMICURL_H +#define AMAROK_ATOMICURL_H + +#include +#include "atomicstring.h" +#include "amarok_export.h" + +class KURL; + +class LIBAMAROK_EXPORT AtomicURL +{ + AtomicString m_beginning; + AtomicString m_directory; + QString m_filename; + QString m_end; + +public: + AtomicURL(); + + AtomicURL( const AtomicURL &other ); + + AtomicURL( const KURL &url ); + + virtual ~AtomicURL(); + + AtomicURL &operator=( const AtomicURL &other ); + + bool operator==( const AtomicURL &other ) const; + + QString string() const; + + KURL url() const; + + operator KURL() const { return url(); } + + bool isEmpty() const; + + void setPath( const QString &path ); + + QString path() const; + + QString fileName() const; + + QString directory() const; +}; + +#endif diff --git a/amarok/src/bcpp.cfg b/amarok/src/bcpp.cfg new file mode 100644 index 00000000..a580116e --- /dev/null +++ b/amarok/src/bcpp.cfg @@ -0,0 +1,7 @@ +function_spacing = 3 +use_tabs = no +indent_spacing = 4 +place_brace_on_new_line = yes +backup_file = yes +ascii_chars_only = no + diff --git a/amarok/src/browserToolBar.h b/amarok/src/browserToolBar.h new file mode 100644 index 00000000..54cafbfc --- /dev/null +++ b/amarok/src/browserToolBar.h @@ -0,0 +1,32 @@ +/*************************************************************************** + * Copyright (C) 2004, 2005 Max Howell * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef BROWSER_TOOLBAR_H +#define BROWSER_TOOLBAR_H + +#include + +namespace Browser +{ + class ToolBar : public KToolBar + { + public: + ToolBar( QWidget *parent ) + : KToolBar( parent, "NotMainToolBar" ) + { + setMovingEnabled(false); + setFlat(true); + setIconSize( 16 ); + setEnableContextMenu( false ); + } + }; +} + +#endif diff --git a/amarok/src/browserbar.cpp b/amarok/src/browserbar.cpp new file mode 100644 index 00000000..fd32be1e --- /dev/null +++ b/amarok/src/browserbar.cpp @@ -0,0 +1,408 @@ +/*************************************************************************** + * Copyright (C) 2004, 2005 Max Howell * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "amarok.h" +#include "amarokconfig.h" +#include "browserbar.h" +#include "debug.h" +#include "enginecontroller.h" +#include "multitabbar.h" //m_tabBar + +#include //kapp +#include +#include //multiTabBar icons +#include + +#include //for resize cursor +#include +#include //m_mapper +#include //Amarok::Splitter +#include + + +// we emulate a qsplitter, mostly for historic reasons, but there are still a few advantages +// mostly we can stop the browserbar getting resized too small so that switching browser looks wrong + + +namespace Amarok +{ + class Splitter : public QWidget { + public: + Splitter( BrowserBar *w ) : QWidget( w, "divider" ) + { + setCursor( QCursor(SplitHCursor) ); + styleChange( style() ); + } + + virtual void paintEvent( QPaintEvent* ) + { + QPainter p( this ); + parentWidget()->style().drawPrimitive( QStyle::PE_Splitter, &p, rect(), colorGroup(), QStyle::Style_Horizontal ); + } + + virtual void styleChange( QStyle& ) + { + setFixedWidth( style().pixelMetric( QStyle::PM_SplitterWidth, this ) ); + } + + virtual void mouseMoveEvent( QMouseEvent *e ) + { + static_cast(parent())->mouseMovedOverSplitter( e ); + } + }; +} + +BrowserBar* BrowserBar::s_instance = 0; + +BrowserBar::BrowserBar( QWidget *parent ) + : QWidget( parent, "BrowserBar" ) + , EngineObserver( EngineController::instance() ) + , m_playlistBox( new QVBox( this ) ) + , m_divider( new Amarok::Splitter( this ) ) + , m_browserBox( new QVBox( this ) ) + , m_currentIndex( -1 ) + , m_lastIndex( -1 ) + , m_mapper( new QSignalMapper( this ) ) +{ + m_tabManagementButton = new QPushButton( SmallIconSet(Amarok::icon( "configure" )), 0, this, "tab_managment_button" ); + connect (m_tabManagementButton, SIGNAL(clicked()), SLOT(showBrowserSelectionMenu())); + m_tabManagementButton->setIsMenuButton ( true ); //deprecated, but since I cannot add menu directly to button it is needed. + + QToolTip::add (m_tabManagementButton, i18n("Manage tabs")); + + + m_tabBar = new MultiTabBar( MultiTabBar::Vertical, this ); + + + m_tabManagementButton->setFixedWidth(m_tabBar->sizeHint().width()); + m_tabManagementButton->setFixedHeight(m_tabBar->sizeHint().width()); + + + s_instance = this; + m_pos = m_tabBar->sizeHint().width() + 5; //5 = esthetic spacing + + m_tabBar->setStyle( MultiTabBar::AMAROK ); + m_tabBar->setPosition( MultiTabBar::Left ); + m_tabBar->showActiveTabTexts( true ); + m_tabBar->setFixedWidth( m_pos ); + m_tabBar->move( 0, 25 ); + + QVBoxLayout *layout = new QVBoxLayout( m_browserBox ); + layout->addSpacing( 3 ); // aesthetics + layout->setAutoAdd( true ); + + m_browserBox->move( m_pos, 0 ); + m_browserBox->hide(); + m_divider->hide(); + m_playlistBox->setSpacing( 1 ); + + connect( m_mapper, SIGNAL(mapped( int )), SLOT(showHideBrowser( int )) ); + + + + + + //m_tabBar->appendButton( Amarok::icon( "configure" ), 1, 0, QString::null ); + +} + +BrowserBar::~BrowserBar() +{ + KConfig* const config = Amarok::config( "BrowserBar" ); + config->writeEntry( "CurrentPane", m_currentIndex != -1 ? QString(currentBrowser()->name()) : QString::null ); + config->writeEntry( "Width", m_browserBox->width() ); +} + +void +BrowserBar::makeDropProxy( const QString &name, DropProxyTarget *finalTarget ) +{ + int id = m_browserIds[name]; + MultiTabBarButton *button = m_tabBar->tab( id ); + if( button ) + button->proxyDrops( finalTarget ); +} + +int +BrowserBar::restoreWidth() +{ + const int index = indexForName( Amarok::config( "BrowserBar" )->readEntry( "CurrentPane" ) ); + const int width = Amarok::config( "BrowserBar" )->readNumEntry( "Width", browser( index )->sizeHint().width() ); + + m_browserBox->resize( width, height() ); + m_pos = m_browserBox->width() + m_tabBar->width(); + + return index; +} + +void +BrowserBar::polish() +{ + DEBUG_FUNC_INFO + + QWidget::polish(); + + uint M = 0; + foreachType( BrowserList, m_browsers ) { + const uint m = (*it)->minimumWidth(); + if (m > M) + M = m; + if (m > 250) { + warning() << "Browser is too large, mxcl says castrate the developer: " << (*it)->name() << ", " << M << endl; + M = 250; + } + } + + m_browserBox->setMinimumWidth( M ); + const int index = restoreWidth(); + + if (index != -1) + // if we did it for -1 it ruins the browserBox size + showHideBrowser( index ); +} + +void +BrowserBar::adjustWidgetSizes() +{ + //TODO set the geometry of the PlaylistWindow before + // the browsers are loaded so this isn't called twice + + const uint w = width(); + const uint h = height(); + const uint maxW = maxBrowserWidth(); + const uint p = (m_pos < maxW) ? m_pos : maxW; + const uint ppw = p + m_divider->width(); + const uint tbw = m_tabBar->width(); + + m_divider->move( p, 0 ); + + const uint offset = !m_divider->isHidden() ? ppw : tbw; + + m_browserBox->resize( p - tbw, h ); + m_playlistBox->setGeometry( offset, 0, w - offset, h ); +} + +void +BrowserBar::mouseMovedOverSplitter( QMouseEvent *e ) +{ + const uint oldPos = m_pos; + const uint newPos = mapFromGlobal( e->globalPos() ).x(); + const uint minWidth = m_tabBar->width() + m_browserBox->minimumWidth(); + const uint maxWidth = maxBrowserWidth(); + + if( newPos < minWidth ) + m_pos = minWidth; + + else if( newPos > maxWidth ) + m_pos = maxWidth; + + else + m_pos = newPos; + + if( m_pos != oldPos ) + adjustWidgetSizes(); +} + +bool +BrowserBar::event( QEvent *e ) +{ + switch( e->type() ) + { + case QEvent::LayoutHint: + //FIXME include browserholder width + setMinimumWidth( + m_tabBar->minimumWidth() + + m_divider->minimumWidth() + + m_browserBox->width() + + m_playlistBox->minimumWidth() ); + break; + + case QEvent::Resize: +// DEBUG_LINE_INFO + + m_divider->resize( 0, height() ); //Qt will set width + m_tabBar->resize( 0, height() ); //Qt will set width + + adjustWidgetSizes(); + + return true; + + default: + ; + } + + return QWidget::event( e ); +} + +void +BrowserBar::addBrowser( const QString &identifier, QWidget *widget, const QString &title, const QString& icon ) +{ + const int id = m_tabBar->tabs()->count(); // the next available id + const QString name( widget->name() ); + m_browserIds[name] = id; + QWidget *tab; + + widget->reparent( m_browserBox, QPoint() ); + widget->hide(); + + m_tabBar->appendTab( SmallIcon( icon ), id, title, identifier ); + tab = m_tabBar->tab( id ); + tab->setFocusPolicy( QWidget::NoFocus ); //FIXME you can focus on the tab, but they respond to no input! + + //we use a SignalMapper to show/hide the corresponding browser when tabs are clicked + connect( tab, SIGNAL(clicked()), m_mapper, SLOT(map()) ); + m_mapper->setMapping( tab, id ); + connect( tab, SIGNAL(initiateDrag ( int ) ), this, SLOT( showBrowser( int )) ); + + m_browsers.push_back( widget ); +} + +void +BrowserBar::removeMediaBrowser( QWidget *widget ) +{ + BrowserList::iterator it = qFind( m_browsers.begin(), m_browsers.end(), widget ); + if( it != m_browsers.end() ) + m_browsers.erase( it ); + QWidget *tab; + tab = m_tabBar->tab( m_browserIds["MediaBrowser"] ); + m_mapper->removeMappings( tab ); + m_tabBar->removeTab( m_browserIds["MediaBrowser"] ); +} + +void +BrowserBar::showHideBrowser( int index ) +{ + const int prevIndex = m_currentIndex; + + if( m_currentIndex != -1 ) { + ///first we need to hide the currentBrowser + + m_currentIndex = -1; //to prevent race condition, see CVS history + + m_browsers[prevIndex]->hide(); + m_tabBar->setTab( prevIndex, false ); + } + + if( index == prevIndex ) { + ///close the BrowserBar + + m_browserBox->hide(); + m_divider->hide(); + + adjustWidgetSizes(); + } + + else if( (uint)index < m_browsers.count() ) { + ///open up target + + QWidget* const target = m_browsers[index]; + m_currentIndex = index; + + m_divider->show(); + target->show(); + target->setFocus(); + m_browserBox->show(); + m_tabBar->setTab( index, true ); + + if( prevIndex == -1 ) { + // we need to show the browserBox + // m_pos dictates how everything will be sized in adjustWidgetSizes() + m_pos = m_browserBox->width() + m_tabBar->width(); + adjustWidgetSizes(); + } + } + + emit browserActivated( index ); +} + +void +BrowserBar::showHideVisibleBrowser( int index ) +{ + int realindex = -1; + QPtrList tabs = *m_tabBar->tabs(); + for( int i = 0, n = tabs.count(); i < n; ++i ) + { + if( tabs.at( i )->visible() ) + index--; + if( index < 0 ) + { + realindex = i; + break; + } + } + + if( realindex >= 0 ) + showHideBrowser( realindex ); +} + +QWidget* +BrowserBar::browser( const QString &name ) const +{ + foreachType( BrowserList, m_browsers ) + if( name == (*it)->name() ) + return *it; + + return 0; +} + +int +BrowserBar::visibleCount() const +{ + int num = 0; + QPtrList tabs = *m_tabBar->tabs(); + for( int i = 0, n = tabs.count(); i < n; ++i ) + { + if( tabs.at( i )->visible() ) + num++; + } + + return num; +} + +int +BrowserBar::indexForName( const QString &name ) const +{ + for( uint x = 0; x < m_browsers.count(); ++x ) + if( name == m_browsers[x]->name() ) + return x; + return -1; +} + +void +BrowserBar::showBrowserSelectionMenu() +{ + m_tabBar->showTabSelectionMenu(mapToGlobal(QPoint(m_tabManagementButton->pos().x(), m_tabManagementButton->pos().y() +m_tabManagementButton->height() ))); +} + +void +BrowserBar::engineStateChanged( Engine::State state, Engine::State oldState ) +{ + if( !AmarokConfig::autoShowContextBrowser() || m_currentIndex == -1 ) + return; + + switch( state ) { + case Engine::Playing: + + if( oldState != Engine::Paused && m_currentIndex != -1 ) { + m_lastIndex = m_currentIndex; + showBrowser( "ContextBrowser" ); + } + break; + + case Engine::Empty: + + if( m_lastIndex >= 0 ) + showBrowser( m_lastIndex ); + + default: + ; + } +} + +#include "browserbar.moc" diff --git a/amarok/src/browserbar.h b/amarok/src/browserbar.h new file mode 100644 index 00000000..ea2fcefd --- /dev/null +++ b/amarok/src/browserbar.h @@ -0,0 +1,103 @@ +/*************************************************************************** + * Copyright (C) 2004, 2005 Max Howell * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef BROWSERBAR_H +#define BROWSERBAR_H + +#include "amarok_export.h" //LIBAMAROK_EXPORT +#include "engineobserver.h" //baseclass + +#include //baseclass +#include //stack allocated +#include //stack allocated +#include + +typedef QValueVector BrowserList; +typedef QMap BrowserIdMap; + +class MultiTabBar; +class MultiTabBarTab; +class DropProxyTarget; +class KURL; +class QSignalMapper; +class QVBox; + + +class BrowserBar : public QWidget, public EngineObserver +{ + Q_OBJECT + +public: + BrowserBar( QWidget *parent ); + ~BrowserBar(); + + LIBAMAROK_EXPORT static BrowserBar* instance() { return s_instance; } + QVBox *container() const { return m_playlistBox; } + QVBox *browserBox() const { return m_browserBox; } + + QWidget *browser( const QString& ) const; + QWidget *browser( int index ) const { if( index < 0 ) index = 0; return m_browsers[index]; } + QWidget *currentBrowser() const { return m_currentIndex < 0 ? 0 : browser( m_currentIndex ); } + + int count() const { return m_browsers.count(); } + int visibleCount() const; + + void addBrowser( const QString &identifier, QWidget*, const QString&, const QString& ); + void removeMediaBrowser( QWidget *widget ); + int indexForName( const QString& ) const; + int restoreWidth(); + + /// for internal use + void mouseMovedOverSplitter( QMouseEvent* ); + void makeDropProxy( const QString &browserName, DropProxyTarget *finalTarget ); + +protected: + virtual bool event( QEvent* ); + virtual void polish(); + +protected: + virtual void engineStateChanged( Engine::State, Engine::State = Engine::Empty ); + +public slots: + void showBrowser( const QString& name ) { showBrowser( indexForName( name ) ); } + void showBrowser( int index ) { if( index != m_currentIndex ) showHideBrowser( index ); } + void showHideBrowser( int ); + void showHideVisibleBrowser( int ); + void closeCurrentBrowser() { showHideBrowser( m_currentIndex ); } + void showBrowserSelectionMenu(); + +signals: + void browserActivated( int ); + +private: + void adjustWidgetSizes(); + uint maxBrowserWidth() const { return width() * 2 / 3; } + + static const int DEFAULT_HEIGHT = 50; + + LIBAMAROK_EXPORT static BrowserBar *s_instance; + uint m_pos; ///the x-axis position of m_divider + QVBox *m_playlistBox; ///parent to playlist, playlist filter and toolbar + QWidget *m_divider; ///a qsplitter like widget + MultiTabBar *m_tabBar; + BrowserList m_browsers; + BrowserIdMap m_browserIds; + QVBox *m_browserBox; ///parent widget to the browsers + int m_currentIndex; + int m_lastIndex; + QSignalMapper *m_mapper; ///maps tab clicks to browsers + + QPushButton *m_tabManagementButton; + + + +}; + +#endif diff --git a/amarok/src/clicklineedit.cpp b/amarok/src/clicklineedit.cpp new file mode 100644 index 00000000..d9dc62f5 --- /dev/null +++ b/amarok/src/clicklineedit.cpp @@ -0,0 +1,104 @@ +/* + This file is part of libkdepim. + Copyright (c) 2004 Daniel Molkentin + based on code by Cornelius Schumacher + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Steet, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + + +#include "clicklineedit.h" + +#include "qpainter.h" + + +ClickLineEdit::ClickLineEdit( const QString &msg, QWidget *parent, const char* name ) : + KLineEdit( parent, name ) +{ + mDrawClickMsg = true; + setClickMessage( msg ); +} + + +///////////////////////////////////////////////////////////////////////////////////// +// PUBLIC +///////////////////////////////////////////////////////////////////////////////////// + +void ClickLineEdit::setClickMessage( const QString &msg ) +{ + mClickMessage = msg; + repaint(); +} + + +void ClickLineEdit::setText( const QString &txt ) +{ + mDrawClickMsg = txt.isEmpty(); + repaint(); + KLineEdit::setText( txt ); +} + + +///////////////////////////////////////////////////////////////////////////////////// +// PROTECTED +///////////////////////////////////////////////////////////////////////////////////// + +//#include +void ClickLineEdit::drawContents( QPainter *p ) +{ + KLineEdit::drawContents( p ); + + if ( mDrawClickMsg == true && !hasFocus() ) { + QPen tmp = p->pen(); + p->setPen( palette().color( QPalette::Disabled, QColorGroup::Text ) ); + QRect cr = contentsRect(); + + //p->drawPixmap( 3, 3, SmallIcon("filter") ); + + // Add two pixel margin on the left side + cr.rLeft() += 3; + p->drawText( cr, AlignAuto | AlignVCenter, mClickMessage ); + p->setPen( tmp ); + } +} + +void ClickLineEdit::dropEvent( QDropEvent *ev ) +{ + mDrawClickMsg = false; + KLineEdit::dropEvent( ev ); +} + + +void ClickLineEdit::focusInEvent( QFocusEvent *ev ) +{ + if ( mDrawClickMsg == true ) { + mDrawClickMsg = false; + repaint(); + } + QLineEdit::focusInEvent( ev ); +} + + +void ClickLineEdit::focusOutEvent( QFocusEvent *ev ) +{ + if ( text().isEmpty() ) { + mDrawClickMsg = true; + repaint(); + } + QLineEdit::focusOutEvent( ev ); +} + +#include "clicklineedit.moc" diff --git a/amarok/src/clicklineedit.h b/amarok/src/clicklineedit.h new file mode 100644 index 00000000..2912c087 --- /dev/null +++ b/amarok/src/clicklineedit.h @@ -0,0 +1,59 @@ +/* + This file is part of libkdepim. + Copyright (c) 2004 Daniel Molkentin + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Steet, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef CLICKLINEEDIT_H +#define CLICKLINEEDIT_H + +#include + +/** + This class provides a KLineEdit which contains a grayed-out hinting + text as long as the user didn't enter any text + + @short LineEdit with customizable "Click here" text + @author Daniel Molkentin +*/ +class ClickLineEdit : public KLineEdit +{ + Q_OBJECT + Q_PROPERTY( QString clickMessage READ clickMessage WRITE setClickMessage ) + public: + ClickLineEdit( const QString &msg, QWidget *parent, const char* name = 0 ); + + void setClickMessage( const QString &msg ); + QString clickMessage() const { return mClickMessage; } + + virtual void setText( const QString& txt ); + + protected: + virtual void drawContents( QPainter *p ); + virtual void dropEvent( QDropEvent *ev ); + virtual void focusInEvent( QFocusEvent *ev ); + virtual void focusOutEvent( QFocusEvent *ev ); + + private: + QString mClickMessage; + bool mDrawClickMsg; + +}; + +#endif // CLICKLINEEDIT_H + + diff --git a/amarok/src/collectionbrowser.cpp b/amarok/src/collectionbrowser.cpp new file mode 100644 index 00000000..57a55e0d --- /dev/null +++ b/amarok/src/collectionbrowser.cpp @@ -0,0 +1,4718 @@ +// (c) 2004 Mark Kretschmann +// (c) 2004 Christian Muehlhaeuser +// (c) 2005 GÁbor Lehel +// (c) 2005 Alexandre Pereira de Oliveira +// (c) 2005 Christan Baumgart +// (c) 2006 Joe Rabinoff +// See COPYING file for licensing information. + +#include + +#include "amarok.h" +#include "amarokconfig.h" +#include "browserbar.h" +#include "browserToolBar.h" +#include "clicklineedit.h" +#include "collectionbrowser.h" +#include "collectiondb.h" +#include "covermanager.h" +#include "debug.h" +#include "deletedialog.h" +#include "directorylist.h" +#include "editfilterdialog.h" +#include "k3bexporter.h" +#include "mediabrowser.h" +#include "metabundle.h" +#include "mountpointmanager.h" +#include "organizecollectiondialog.h" +#include "playlist.h" //insertMedia() +#include "playlistbrowser.h" +#include "starmanager.h" +#include "statusbar.h" +#include "tagdialog.h" +#include "threadmanager.h" +#include "qstringx.h" + +#include //TagLib::File::isWritable + +#include //CollectionView ctor + +#include +#include +#include +#include //infobox +#include +#include +#include +#include +#include +#include +#include +#include //QToolTip::add() +#include +#include + +#include +#include //kapp +#include +#include +#include +#include +#include +#include //renderView() +#include +#include +#include +#include //ctor +#include //dragObject() +#include +#include + +extern "C" +{ + #if KDE_VERSION < KDE_MAKE_VERSION(3,3,91) + #include //ControlMask in contentsDragMoveEvent() + #endif +} + +using namespace CollectionBrowserIds; + +namespace Amarok { extern KConfig *config( const QString& ); } + +class CoverFetcher; + +CollectionBrowser *CollectionBrowser::s_instance = 0; + +CollectionBrowser::CollectionBrowser( const char* name ) + : QVBox( 0, name ) + , m_cat1Menu( new KPopupMenu( this ) ) + , m_cat2Menu( new KPopupMenu( this ) ) + , m_cat3Menu( new KPopupMenu( this ) ) + , m_timer( new QTimer( this ) ) + , m_returnPressed( false ) +{ + s_instance = this; + + setSpacing( 4 ); + + m_toolbar = new Browser::ToolBar( this ); + + { // + KToolBarButton *button; + KToolBar* searchToolBar = new Browser::ToolBar( this ); + + button = new KToolBarButton( "locationbar_erase", 0, searchToolBar ); + m_searchEdit = new ClickLineEdit( i18n( "Enter search terms here" ), searchToolBar ); + m_searchEdit->installEventFilter( this ); + KPushButton *filterButton = new KPushButton("...", searchToolBar, "filter"); + searchToolBar->setStretchableWidget( m_searchEdit ); + + m_searchEdit->setFrame( QFrame::Sunken ); + connect( button, SIGNAL( clicked() ), SLOT( slotClearFilter() ) ); + connect( filterButton, SIGNAL( clicked() ), SLOT( slotEditFilter() ) ); + + QToolTip::add( button, i18n( "Clear search field" ) ); + QToolTip::add( m_searchEdit, i18n( "Enter space-separated terms to search in the collection" ) ); + QToolTip::add( filterButton, i18n( "Click to edit collection filter" ) ); + } // + + + // We put a little toolbar for the forward/back buttons for iPod + // navigation to the right of m_timeFilter. This toolbar is + // hidden when not in iPod browsing mode; it is shown and hidden + // in CollectionView::setViewMode(). m_ipodHbox holds m_timeFilter + // and m_ipodToolbar + m_ipodHbox = new QHBox( this ); + m_ipodHbox->setSpacing( 7 ); // looks better + + m_timeFilter = new KComboBox( m_ipodHbox, "timeFilter" ); + m_ipodHbox->setStretchFactor( m_timeFilter, 1 ); + // Allow the combobox to shrink so the iPod buttons are still visible + m_timeFilter->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Fixed ); + m_timeFilter->insertItem( i18n( "Entire Collection" ) ); + m_timeFilter->insertItem( i18n( "Added Today" ) ); + m_timeFilter->insertItem( i18n( "Added Within One Week" ) ); + m_timeFilter->insertItem( i18n( "Added Within One Month" ) ); + m_timeFilter->insertItem( i18n( "Added Within Three Months" ) ); + m_timeFilter->insertItem( i18n( "Added Within One Year" ) ); + + + // m_ipodToolbar just holds the forward and back buttons, which are + // plugged below + m_ipodToolbar = new Browser::ToolBar( m_ipodHbox ); + m_ipodHbox->setStretchFactor( m_ipodToolbar, 0 ); + m_ipodToolbar->setIconText( KToolBar::IconOnly, false ); + + + KActionCollection* ac = new KActionCollection( this ); + + m_view = new CollectionView( this ); + m_view->installEventFilter( this ); + + m_configureAction = new KAction( i18n( "Configure Folders" ), Amarok::icon( "configure" ), 0, this, SLOT( setupDirs() ), ac, "Configure" ); + m_treeViewAction = new KRadioAction( i18n( "Tree View" ), "view_tree", 0, m_view, SLOT( setTreeMode() ), ac, "Tree View" ); + m_flatViewAction = new KRadioAction( i18n( "Flat View" ), "view_detailed", 0, m_view, SLOT( setFlatMode() ), ac, "Flat View" ); + m_ipodViewAction = new KRadioAction( i18n( "iPod View" ), Amarok::icon("device"), 0, m_view, SLOT( setIpodMode() ), ac, "iPod View" ); + m_treeViewAction->setExclusiveGroup("view mode"); + m_flatViewAction->setExclusiveGroup("view mode"); + m_ipodViewAction->setExclusiveGroup("view mode"); + switch( m_view->m_viewMode ) + { + case CollectionView::modeTreeView: + m_treeViewAction->setChecked( true ); + break; + case CollectionView::modeFlatView: + m_flatViewAction->setChecked( true ); + break; + case CollectionView::modeIpodView: + m_ipodViewAction->setChecked( true ); + break; + } + + m_showDividerAction = new KToggleAction( i18n( "Show Divider" ), "leftjust", 0, this, SLOT( toggleDivider() ), ac, "Show Divider" ); + m_showDividerAction->setChecked(m_view->m_showDivider); + + + // m_ipodIncrement and m_ipodDecrement are the actions that + // correspond to moving forward / backward in the iPod collection + // browser window; see the "For iPod-style navigation" comments below. + m_ipodDecrement = new KAction( i18n( "Browse backward" ), + QIconSet( m_view->ipodDecrementIcon(), QIconSet::Small ), + 0, m_view, SLOT( decrementDepth() ), ac, + "iPod Decrement" ); + m_ipodIncrement = new KAction( i18n( "Browse forward" ), + QIconSet( m_view->ipodIncrementIcon(), QIconSet::Small ), + 0, m_view, SLOT( incrementDepth() ), ac, + "iPod Increment" ); + m_ipodDecrement->plug( m_ipodToolbar ); + m_ipodIncrement->plug( m_ipodToolbar ); + + // Show / hide m_ipodToolbar based on the view mode + ipodToolbar( m_view->m_viewMode == CollectionView::modeIpodView ); + + + m_tagfilterMenuButton = new KActionMenu( i18n( "Group By" ), "filter", ac ); + m_tagfilterMenuButton->setDelayed( false ); + // FIXME: either both or nothing + //m_tagfilterMenuButton->setEnabled( m_view->m_viewMode == CollectionView::modeTreeView ); + //connect ( m_treeViewAction, SIGNAL ( toggled(bool) ), m_tagfilterMenuButton, SLOT( setEnabled (bool) ) ); + + layoutToolbar(); + + m_categoryMenu = m_tagfilterMenuButton->popupMenu(); + m_categoryMenu->insertItem( i18n( "Artist" ), m_view, SLOT( presetMenu( int ) ), 0, IdArtist ); + m_categoryMenu->insertItem( i18n( "Artist / Album" ), m_view, SLOT( presetMenu( int ) ), 0, IdArtistAlbum ); + m_categoryMenu->insertItem( i18n( "Artist" )+" / "+ i18n( "Year" ) + i18n( " - " ) + i18n( "Album" ), m_view, SLOT( presetMenu( int ) ), 0, IdArtistVisYearAlbum ); + m_categoryMenu->insertItem( i18n( "Album" ), m_view, SLOT( presetMenu( int ) ), 0, IdAlbum ); + m_categoryMenu->insertItem( i18n( "Genre / Artist" ), m_view, SLOT( presetMenu( int ) ), 0, IdGenreArtist ); + m_categoryMenu->insertItem( i18n( "Genre / Artist / Album" ), m_view, SLOT( presetMenu( int ) ), 0, IdGenreArtistAlbum ); + + m_categoryMenu->insertSeparator(); + + m_categoryMenu->insertItem( i18n( "&First Level" ), m_cat1Menu ); + m_categoryMenu->insertItem( i18n( "&Second Level"), m_cat2Menu ); + m_categoryMenu->insertItem( i18n( "&Third Level" ), m_cat3Menu ); + + m_cat1Menu ->insertItem( i18n( "&Album" ), m_view, SLOT( cat1Menu( int ) ), 0, IdAlbum ); + m_cat1Menu ->insertItem( i18n( "(Y&ear) - Album" ), m_view, SLOT( cat1Menu( int ) ), 0, IdVisYearAlbum); + m_cat1Menu ->insertItem( i18n( "A&rtist"), m_view, SLOT( cat1Menu( int ) ), 0, IdArtist ); + m_cat1Menu ->insertItem( i18n( "&Composer"), m_view, SLOT( cat1Menu( int ) ), 0, IdComposer ); + m_cat1Menu ->insertItem( i18n( "&Genre" ), m_view, SLOT( cat1Menu( int ) ), 0, IdGenre ); + m_cat1Menu ->insertItem( i18n( "&Year" ), m_view, SLOT( cat1Menu( int ) ), 0, IdYear ); + m_cat1Menu ->insertItem( i18n( "&Label" ), m_view, SLOT( cat1Menu( int ) ), 0, IdLabel ); + + m_cat2Menu ->insertItem( i18n( "&None" ), m_view, SLOT( cat2Menu( int ) ), 0, IdNone ); + m_cat2Menu ->insertSeparator(); + m_cat2Menu ->insertItem( i18n( "&Album" ), m_view, SLOT( cat2Menu( int ) ), 0, IdAlbum ); + m_cat2Menu ->insertItem( i18n( "(Y&ear) - Album" ), m_view, SLOT( cat2Menu( int ) ), 0, IdVisYearAlbum); + m_cat2Menu ->insertItem( i18n( "A&rtist" ), m_view, SLOT( cat2Menu( int ) ), 0, IdArtist ); + m_cat2Menu ->insertItem( i18n( "&Composer"), m_view, SLOT( cat2Menu( int ) ), 0, IdComposer ); + m_cat2Menu ->insertItem( i18n( "&Genre" ), m_view, SLOT( cat2Menu( int ) ), 0, IdGenre ); + m_cat2Menu ->insertItem( i18n( "&Year" ), m_view, SLOT( cat2Menu( int ) ), 0, IdYear ); + m_cat2Menu ->insertItem( i18n( "&Label" ), m_view, SLOT( cat2Menu( int ) ), 0, IdLabel ); + + m_cat3Menu ->insertItem( i18n( "&None" ), m_view, SLOT( cat3Menu( int ) ), 0, IdNone ); + m_cat3Menu ->insertSeparator(); + m_cat3Menu ->insertItem( i18n( "A&lbum" ), m_view, SLOT( cat3Menu( int ) ), 0, IdAlbum ); + m_cat3Menu ->insertItem( i18n( "(Y&ear) - Album" ), m_view, SLOT( cat3Menu( int ) ), 0, IdVisYearAlbum); + m_cat3Menu ->insertItem( i18n( "A&rtist" ), m_view, SLOT( cat3Menu( int ) ), 0, IdArtist ); + m_cat3Menu ->insertItem( i18n( "&Composer"), m_view, SLOT( cat3Menu( int ) ), 0, IdComposer ); + m_cat3Menu ->insertItem( i18n( "&Genre" ), m_view, SLOT( cat3Menu( int ) ), 0, IdGenre ); + m_cat3Menu ->insertItem( i18n( "&Year" ), m_view, SLOT( cat3Menu( int ) ), 0, IdYear ); + m_cat3Menu ->insertItem( i18n( "&Label" ), m_view, SLOT( cat3Menu( int ) ), 0, IdLabel ); + + m_view->cat1Menu( m_view->m_cat1, false ); + m_view->cat2Menu( m_view->m_cat2, false ); + m_view->cat3Menu( m_view->m_cat3, false ); + m_view->setViewMode( m_view->m_viewMode ); + + connect( m_timer, SIGNAL( timeout() ), SLOT( slotSetFilter() ) ); + connect( m_searchEdit, SIGNAL( textChanged( const QString& ) ), SLOT( slotSetFilterTimeout() ) ); + connect( m_timeFilter, SIGNAL( activated( int ) ), SLOT( slotSetFilter() ) ); + + setFocusProxy( m_view ); //default object to get focus +} + +void +CollectionBrowser::slotClearFilter() //SLOT +{ + m_searchEdit->clear(); + kapp->processEvents(); //Let the search bar redraw fully. + QTimer::singleShot( 0, this, SLOT( slotSetFilter() ) ); //Filter instantly + QTimer::singleShot( 0, m_view, SLOT( slotEnsureSelectedItemVisible() ) ); +} + +void +CollectionBrowser::slotSetFilterTimeout() //SLOT +{ + m_returnPressed = false; + m_timer->start( 280, true ); //stops the timer for us first +} + +void +CollectionBrowser::slotSetFilter() //SLOT +{ + m_timer->stop(); + m_view->m_dirty = true; + m_view->setFilter( m_searchEdit->text() ); + m_view->setTimeFilter( m_timeFilter->currentItem() ); + m_view->renderView(); + if ( m_returnPressed ) + appendSearchResults(); + m_returnPressed = false; +} + +void +CollectionBrowser::slotSetFilter( const QString &filter ) //SLOT +{ + m_searchEdit->setText( filter ); + kapp->processEvents(); //Let the search bar redraw fully. + QTimer::singleShot( 0, this, SLOT( slotSetFilter() ) ); //Filter instantly + QTimer::singleShot( 0, m_view, SLOT( slotEnsureSelectedItemVisible() ) ); +} + +void +CollectionBrowser::slotEditFilter() //SLOT +{ + EditFilterDialog *cod = new EditFilterDialog( this, false, m_searchEdit->text() ); + connect( cod, SIGNAL(filterChanged(const QString &)), SLOT(slotSetFilter(const QString &)) ); + if( cod->exec() ) + m_searchEdit->setText( cod->filter() ); + delete cod; +} + +void +CollectionBrowser::setupDirs() //SLOT +{ + m_view->setupDirs(); +} + +void +CollectionBrowser::toggleDivider() //SLOT +{ + m_view->setShowDivider( m_showDividerAction->isChecked() ); +} + +void +CollectionBrowser::appendSearchResults() +{ + //If we are not filtering, or the search string has changed recently, do nothing + if ( m_searchEdit->text().stripWhiteSpace().isEmpty() || m_timer->isActive() ) + return; + m_view->selectAll(); + Playlist::instance()->insertMedia( m_view->listSelected(), Playlist::Unique | Playlist::Append ); + m_view->clearSelection(); + slotClearFilter(); +} + +bool +CollectionBrowser::eventFilter( QObject *o, QEvent *e ) +{ + switch( e->type() ) + { + case 6/*QEvent::KeyPress*/: + + //there are a few keypresses that we intercept + + #define e static_cast(e) + + if( o == m_searchEdit ) //the search lineedit + { + switch( e->key() ) + { + case Key_Up: + case Key_Down: + case Key_PageDown: + case Key_PageUp: + m_view->setFocus(); + QApplication::sendEvent( m_view, e ); + return true; + + case Key_Escape: + slotClearFilter(); + return true; + + case Key_Return: + case Key_Enter: + if ( m_timer->isActive() ) + { + //Immediately filter and add results + m_timer->stop(); + m_returnPressed = true; + QTimer::singleShot( 0, this, SLOT( slotSetFilter() ) ); + } + else + { + //Add current results + appendSearchResults(); + } + return true; + + default: + return false; + } + } + + // (Joe Rabinoff) the code that was here which dealt with wrapping + // the selection around when Key_Up or Key_Down was pressed was + // moved to CollectionView::keyPressEvent(). That code also + // skips dividers. + + if( ( e->key() >= Key_0 && e->key() <= Key_Z ) || e->key() == Key_Backspace || e->key() == Key_Escape ) + { + m_searchEdit->setFocus(); + QApplication::sendEvent( m_searchEdit, e ); + return true; + } + #undef e + break; + + default: + break; + } + + return QVBox::eventFilter( o, e ); +} + +void +CollectionBrowser::layoutToolbar() +{ + if ( !m_toolbar ) return; + + m_toolbar->clear(); + + m_toolbar->setIconText( KToolBar::IconTextRight, false ); + m_tagfilterMenuButton->plug( m_toolbar ); + m_toolbar->setIconText( KToolBar::IconOnly, false ); + + m_toolbar->insertLineSeparator(); + m_treeViewAction->plug( m_toolbar ); + m_flatViewAction->plug( m_toolbar ); + m_ipodViewAction->plug( m_toolbar ); + m_toolbar->insertLineSeparator(); + + m_showDividerAction->plug( m_toolbar ); + m_configureAction->plug( m_toolbar ); + + //This would break things if the toolbar is too big, see bug #121915 + //setMinimumWidth( m_toolbar->sizeHint().width() + 2 ); //set a reasonable minWidth +} + + +// (De)activate the iPod toolbar when switching into and out of +// iPod browsing mode +void +CollectionBrowser::ipodToolbar( bool activate ) +{ + if( activate ) + m_ipodToolbar->show(); + + else + m_ipodToolbar->hide(); +} + + + +////////////////////////////////////////////////////////////////////////////////////////// +// CLASS CollectionView +////////////////////////////////////////////////////////////////////////////////////////// + +CollectionView* CollectionView::m_instance = 0; + + +CollectionView::CollectionView( CollectionBrowser* parent ) + : KListView( parent ) + , m_parent( parent ) + , m_timeFilter( 0 ) + , m_currentDepth( 0 ) + , m_ipodIncremented ( 1 ) + , m_dirty( true ) + , m_organizingFileCancelled( false ) +{ + DEBUG_FUNC_INFO + m_instance = this; + + setSelectionMode( QListView::Extended ); + setItemsMovable( false ); + setSorting( 0 ); + setShowSortIndicator( true ); + setAcceptDrops( true ); + setAllColumnsShowFocus( true ); + + // + KConfig* config = Amarok::config( "Collection Browser" ); + m_cat1 = config->readNumEntry( "Category1", IdArtist ); + m_cat2 = config->readNumEntry( "Category2", IdAlbum ); + m_cat3 = config->readNumEntry( "Category3", IdNone ); + +#define saneCat(x) (x==IdAlbum||x==IdArtist||x==IdComposer||x==IdGenre||x==IdYear \ + ||x==IdNone \ + ||x==IdArtistAlbum||x==IdGenreArtist||x==IdGenreArtistAlbum||x==IdVisYearAlbum||x==IdArtistVisYearAlbum) + + if( !saneCat(m_cat1) ) + { + m_cat1 = IdArtist; + m_cat2 = IdAlbum; + m_cat2 = IdNone; + } + if( !saneCat(m_cat2) || !saneCat(m_cat3) ) + { + m_cat2 = m_cat3 = IdNone; + } +#undef saneCat + + m_viewMode = config->readNumEntry( "ViewMode", modeTreeView ); + m_showDivider = config->readBoolEntry( "ShowDivider", true); + updateTrackDepth(); + + m_flatColumnWidths.clear(); + QStringList flatWidths = config->readListEntry( "FlatColumnWidths" ); + for( QStringList::iterator it = flatWidths.begin(); + it != flatWidths.end(); + it++ ) + m_flatColumnWidths.push_back( (*it).toInt() ); + + // + KActionCollection* ac = new KActionCollection( this ); + KStdAction::selectAll( this, SLOT( selectAll() ), ac, "collectionview_select_all" ); + + connect( CollectionDB::instance(), SIGNAL( scanStarted() ), + this, SLOT( scanStarted() ) ); + connect( CollectionDB::instance(), SIGNAL( scanDone( bool ) ), + this, SLOT( scanDone( bool ) ) ); + connect( BrowserBar::instance(), SIGNAL( browserActivated( int ) ), + this, SLOT( renderView() ) ); // renderView() checks if current tab is this + connect( CollectionDB::instance(), SIGNAL( ratingChanged( const QString&, int ) ), + this, SLOT( ratingChanged( const QString&, int ) ) ); + + connect( this, SIGNAL( expanded( QListViewItem* ) ), + this, SLOT( slotExpand( QListViewItem* ) ) ); + connect( this, SIGNAL( collapsed( QListViewItem* ) ), + this, SLOT( slotCollapse( QListViewItem* ) ) ); + connect( this, SIGNAL( returnPressed( QListViewItem* ) ), + this, SLOT( invokeItem( QListViewItem* ) ) ); + connect( this, SIGNAL( doubleClicked( QListViewItem*, const QPoint&, int ) ), + this, SLOT( invokeItem( QListViewItem*, const QPoint&, int ) ) ); + connect( this, SIGNAL( clicked( QListViewItem*, const QPoint&, int ) ), + this, SLOT( ipodItemClicked( QListViewItem*, const QPoint&, int ) ) ); + connect( this, SIGNAL( contextMenuRequested( QListViewItem*, const QPoint&, int ) ), + this, SLOT( rmbPressed( QListViewItem*, const QPoint&, int ) ) ); + connect( header(), SIGNAL( sizeChange( int, int, int ) ), + this, SLOT( triggerUpdate() ) ); + + connect( MountPointManager::instance(), SIGNAL( mediumConnected( int ) ), + this, SLOT( databaseChanged() ) ); + connect( MountPointManager::instance(), SIGNAL( mediumRemoved( int ) ), + this, SLOT( databaseChanged() ) ); +} + + +CollectionView::~CollectionView() { + DEBUG_FUNC_INFO + + KConfig* const config = Amarok::config( "Collection Browser" ); + config->writeEntry( "Category1", m_cat1 ); + config->writeEntry( "Category2", m_cat2 ); + config->writeEntry( "Category3", m_cat3 ); + config->writeEntry( "ViewMode", m_viewMode ); + config->writeEntry( "ShowDivider", m_showDivider ); + + QStringList flatWidths; + for( QValueList::iterator it = m_flatColumnWidths.begin(); + it != m_flatColumnWidths.end(); + it++ ) + flatWidths.push_back( QString::number( (*it) ) ); + config->writeEntry( "FlatColumnWidths", flatWidths ); +} + +void +CollectionView::setShowDivider( bool show ) +{ + if (show != m_showDivider) { + m_showDivider = show; + renderView(true); + } +} + + +// Reimplemented for iPod-style navigation, and to skip dividers +// Specifically, this method traps the Key_Up/Down/Left/Right events. +// When Up or Down is pressed, it skips dividers and wraps around when +// necessary. When Left or Right is pressed and we are viewing in +// iPod mode, the iPod "move forward / backward" actions are activated. +void +CollectionView::keyPressEvent( QKeyEvent *e ) +{ + typedef QListViewItemIterator It; + + // Reimplement up and down to skip dividers and to loop around. + // Some of this code used to be in CollectionBrowser::eventFilter. + // This rewritten code is more faithful to the ordinary moving + // behavior, even when looping around. (For instance, it behaves + // correctly if control-up is pressed at the top of the screen.) + // It sends fake keypress events to the parent instead of programatically + // selecting items. + if( (e->key() == Key_Up || e->key() == Key_Down ) && currentItem() ) + { + // Handle both up and down at once to avoid code duplication (it's + // a delicate piece of logic, and was hard to get right) + + QListViewItem *cur = currentItem(); + + #define nextItem (e->key() == Key_Up ? cur->itemAbove() : cur->itemBelow()) + + bool wraparound = true; + + // First skip any dividers directly above / below + do + { + KListView::keyPressEvent( e ); + if( currentItem() == cur ) // Prevent infinite loops + { + if( nextItem != 0 ) + wraparound = false; + break; + } + cur = currentItem(); + + if( cur && dynamic_cast( cur ) == 0 ) + wraparound = false; // Found an item above / below + + } while( cur != NULL + && dynamic_cast(cur) != 0 + && nextItem != 0 ); + + if( cur == 0 ) return; // Shouldn't happen + + // Wrap around if necessary, by sending a Key_Home/Key_End event. + if( wraparound ) + { + QKeyEvent e2 ( e->type(), + (e->key() == Key_Up ? Key_End : Key_Home), + 0, e->state(), + QString::null, e->isAutoRepeat(), e->count() ); + QApplication::sendEvent( this, &e2 ); + cur = currentItem(); + + // The first item may also be a divider, so keep moving + // until it's not + while ( cur != 0 + && dynamic_cast(cur) != 0 + && nextItem != 0 ) + { + KListView::keyPressEvent( e ); + if( currentItem() == cur ) // Prevent infinite loops + break; + cur = currentItem(); + } + } + + #undef nextItem + + } + + // When Right/Left is pressed in iPod view mode, activate the iPod + // "move forward/backward" action. + else if( (e->key() == Key_Left || e->key() == Key_Right) + && m_viewMode == modeIpodView ) + { + if( e->key() == Key_Right ) + m_parent->m_ipodIncrement->activate(); + + else if( e->key() == Key_Left ) + m_parent->m_ipodDecrement->activate(); + + } + + else // we don't want the event + KListView::keyPressEvent( e ); +} + + +////////////////////////////////////////////////////////////////////////////////////////// +// public slots +////////////////////////////////////////////////////////////////////////////////////////// + +void +CollectionView::renderView(bool force /* = false */) //SLOT +{ + SHOULD_BE_GUI + if(!force && !m_dirty ) + return; + + if( BrowserBar::instance()->currentBrowser() != m_parent ) + { + // the collectionbrowser is intensive for sql, so we only renderView() if the tab + // is currently active. else, wait until user focuses it. +// debug() << "current browser is not collection, aborting renderView()" << endl; + m_dirty = true; + return; + } + m_dirty = false; + + // Don't cache / restore view if we're in ipod mode and we've + // just incremented or decremented, since we'll run selectIpodItems() + // below anyway. + if( childCount() && + !(m_viewMode == modeIpodView && m_ipodIncremented > 0) ) + cacheView(); + + //clear(); + safeClear(); + + if ( m_viewMode == modeFlatView ) + { + renderFlatModeView( force ); + } + + if( m_viewMode == modeIpodView ) + { + renderIpodModeView( force ); + } + + if( m_viewMode == modeTreeView ) + { + renderTreeModeView( force ); + } + + // Don't cache or restore view when we're just going to run + // selectIpodItems() below anyway. + if( !(m_viewMode == modeIpodView && m_ipodIncremented > 0) ) + restoreView(); + + else + selectIpodItems(); +} + + +////////////////////////////////////////////////////////////////////////////////////////// +// private slots +////////////////////////////////////////////////////////////////////////////////////////// + +void +CollectionView::setupDirs() //SLOT +{ + KDialogBase dialog( this, 0, false ); + kapp->setTopWidget( &dialog ); + dialog.setCaption( kapp->makeStdCaption( i18n("Configure Collection") ) ); + + CollectionSetup *setup = new CollectionSetup( &dialog ); + dialog.setMainWidget( setup ); + dialog.showButtonApply( false ); + dialog.adjustSize(); + // Make the dialog a bit bigger, default is too small to be useful + dialog.resize( dialog.width() + 50, dialog.height() + 150 ); + + if ( dialog.exec() != QDialog::Rejected ) + { + const bool rescan = ( MountPointManager::instance()->collectionFolders() != setup->dirs() ); + setup->writeConfig(); + + if ( rescan ) + CollectionDB::instance()->startScan(); + } +} + + +void +CollectionView::scanStarted() // SLOT +{ + Amarok::actionCollection()->action("update_collection")->setEnabled( false ); +} + + +void +CollectionView::scanDone( bool changed ) //SLOT +{ + if( changed ) + { + renderView(true); + } + + Amarok::actionCollection()->action("update_collection")->setEnabled( true ); +} + +void +CollectionView::slotEnsureSelectedItemVisible() //SLOT +{ + //Scroll to make sure the first selected item is visible + + //Find the first selected item + QListViewItem *r=0; + for ( QListViewItem *i = firstChild(); i && !r; i=i->nextSibling() ) + { + if ( i->isSelected() ) + r = i; + for ( QListViewItem *j = i->firstChild(); j && !r; j=j->nextSibling() ) + { + if ( j->isSelected() ) + r = j; + for ( QListViewItem *k = j->firstChild(); k && !r; k=k->nextSibling() ) + { + if ( k->isSelected() ) + r = k; + } + } + } + if ( r ) + { + //We've found the selected item. Now let's refocus on it. + //An elaborate agorithm to try to make as much as possible of the vicinity visible + + //It looks better if things end up consistently in one place. + //So, scroll to the end so that we come at items from the bottom. + if ( lastChild() ) + ensureItemVisible( lastChild() ); + + //Create a reverse list of parents, grandparents etc. + //Later we try to make the grandparents in view, then their children etc. + //This means that the selected item has the most priority as it is done last. + QValueStack parents; + while ( r ) + { + parents.push( r ); + r = r->parent(); + } + while ( !parents.isEmpty() ) + { + //We would prefer the next item to be visible. + if ( parents.top()->nextSibling() ) + ensureItemVisible( parents.top()->nextSibling() ); + //It's even more important the actual item is visible than the next one. + ensureItemVisible( parents.top() ); + parents.pop(); + } + } +} + +void +CollectionView::slotExpand( QListViewItem* item ) //SLOT +{ + if ( !item || !item->isExpandable() ) return; + + int category = 0; + QStringList values; + + QueryBuilder qb; + bool c = false; + bool SortbyTrackFirst = false; + + //Sort by track number first if album is in one of the categories, otherwise by track name first + if ( m_cat1 == IdAlbum || + m_cat2 == IdAlbum || + m_cat3 == IdAlbum ) + SortbyTrackFirst = true; + + // initialization for year - album mode + QString tmptext; + int VisYearAlbum = -1; + int VisLabel = -1; + int q_cat1=m_cat1; + int q_cat2=m_cat2; + int q_cat3=m_cat3; + if( m_cat1 == IdVisYearAlbum || + m_cat2 == IdVisYearAlbum || + m_cat3 == IdVisYearAlbum ) + { + SortbyTrackFirst = true; + if( m_cat1 == IdVisYearAlbum ) + { + VisYearAlbum = 1; + q_cat1 = IdAlbum; + } + if( m_cat2 == IdVisYearAlbum ) + { + VisYearAlbum = 2; + q_cat2 = IdAlbum; + } + if( m_cat3 == IdVisYearAlbum ) + { + VisYearAlbum = 3; + q_cat3 = IdAlbum; + } + } + if( m_cat1 == IdLabel || + m_cat2 == IdLabel || + m_cat3 == IdLabel ) + { + if( m_cat1 == IdLabel ) + VisLabel = 1; + if( m_cat2 == IdLabel ) + VisLabel = 2; + if ( m_cat3 == IdLabel ) + VisLabel = 3; + } + + if ( translateTimeFilter( timeFilter() ) > 0 ) + qb.addFilter( QueryBuilder::tabSong, QueryBuilder::valCreateDate, QString().setNum( QDateTime::currentDateTime().toTime_t() - translateTimeFilter( timeFilter() ) ), QueryBuilder::modeGreater ); + + QString itemText; + bool isUnknown; + + if ( dynamic_cast( item ) ) + { + itemText = static_cast( item )->getSQLText( 0 ); + } + else + { + debug() << "slotExpand in CollectionView of a non-CollectionItem" << endl; + itemText = item->text( 0 ); + } + + switch ( item->depth() ) + { + case 0: + tmptext = itemText; + isUnknown = tmptext.isEmpty(); + if ( !static_cast( item )->isSampler() ) + { + if ( m_cat1 == IdArtist ) + qb.setOptions( QueryBuilder::optNoCompilations ); + if( VisYearAlbum == 1 ) + { + tmptext = item->text( 0 ); + QString year = tmptext.left( tmptext.find( i18n(" - ") ) ); + yearAlbumCalc( year, tmptext ); + qb.addMatch( QueryBuilder::tabYear, year, false, true ); + if ( isUnknown ) + tmptext = ""; + } + + qb.addMatch( q_cat1, tmptext, false, true ); + } + else + { + qb.setOptions( QueryBuilder::optOnlyCompilations ); + c = true; + } + + if ( m_cat2 == QueryBuilder::tabSong ) + { + qb.addReturnValue( q_cat2, QueryBuilder::valTitle, true ); + qb.addReturnValue( q_cat2, QueryBuilder::valURL ); + if ( c ) qb.addReturnValue( QueryBuilder::tabArtist, QueryBuilder::valName, true ); + if ( SortbyTrackFirst ) { + qb.sortBy( q_cat2, QueryBuilder::valDiscNumber ); + qb.sortBy( q_cat2, QueryBuilder::valTrack ); + } + if ( c ) qb.sortBy( QueryBuilder::tabArtist, QueryBuilder::valName ); + qb.sortBy( q_cat2, QueryBuilder::valTitle ); + if ( !SortbyTrackFirst ) { + qb.sortBy( q_cat2, QueryBuilder::valDiscNumber ); + qb.sortBy( q_cat2, QueryBuilder::valTrack ); + } + qb.sortBy( q_cat2, QueryBuilder::valURL ); + } + else + { + c = false; + qb.addReturnValue( q_cat2, QueryBuilder::valName, true ); + if( VisYearAlbum == 2 ) + { + qb.addReturnValue( QueryBuilder::tabYear, QueryBuilder::valName, true ); + qb.sortBy( QueryBuilder::tabYear, QueryBuilder::valName ); + } + qb.sortBy( q_cat2, QueryBuilder::valName ); + } + + category = m_cat2; + break; + + case 1: + tmptext = dynamic_cast( item->parent() ) ? + static_cast( item->parent() )->getSQLText( 0 ) : + item->parent()->text( 0 ); + isUnknown = tmptext.isEmpty(); + + if( !static_cast( item->parent() )->isSampler() ) + { + if ( m_cat1 == IdArtist ) + qb.setOptions( QueryBuilder::optNoCompilations ); + if( VisYearAlbum == 1 ) + { + tmptext = item->parent()->text( 0 ); + QString year = tmptext.left( tmptext.find( i18n(" - ") ) ); + yearAlbumCalc( year, tmptext ); + qb.addMatch( QueryBuilder::tabYear, year, false, true ); + if ( isUnknown ) + tmptext = ""; + } + + qb.addMatch( q_cat1, tmptext, false, true ); + } + else + { + qb.setOptions( QueryBuilder::optOnlyCompilations ); + c = true; + } + + tmptext = itemText; + isUnknown = tmptext.isEmpty(); + + if( VisYearAlbum == 2 ) + { + tmptext = item->text( 0 ); + QString year = tmptext.left( tmptext.find( i18n(" - ") ) ); + yearAlbumCalc( year, tmptext ); + qb.addMatch( QueryBuilder::tabYear, year, false, true ); + if ( isUnknown ) + tmptext = ""; + } + + qb.addMatch( q_cat2, tmptext, false, true ); + + if( m_cat3 == QueryBuilder::tabSong ) + { + qb.addReturnValue( q_cat3, QueryBuilder::valTitle, true ); + qb.addReturnValue( q_cat3, QueryBuilder::valURL ); + if ( c ) qb.addReturnValue( QueryBuilder::tabArtist, QueryBuilder::valName, true ); + if ( SortbyTrackFirst ) { + qb.sortBy( q_cat3, QueryBuilder::valDiscNumber ); + qb.sortBy( q_cat3, QueryBuilder::valTrack ); + } + if ( c ) qb.sortBy( QueryBuilder::tabArtist, QueryBuilder::valName ); + qb.sortBy( q_cat3, QueryBuilder::valTitle ); + if ( !SortbyTrackFirst ) { + qb.sortBy( q_cat3, QueryBuilder::valDiscNumber ); + qb.sortBy( q_cat3, QueryBuilder::valTrack ); + } + qb.sortBy( q_cat3, QueryBuilder::valURL ); + } + else + { + c = false; + qb.addReturnValue( q_cat3, QueryBuilder::valName, true ); + if( VisYearAlbum == 3 ) + { + qb.addReturnValue( QueryBuilder::tabYear, QueryBuilder::valName ); + qb.sortBy( QueryBuilder::tabYear, QueryBuilder::valName ); + } + qb.sortBy( q_cat3, QueryBuilder::valName ); + } + + category = m_cat3; + break; + + case 2: + tmptext = dynamic_cast ( item->parent()->parent() ) ? + static_cast( item->parent()->parent() )->getSQLText( 0 ) : + item->parent()->parent()->text( 0 ); + isUnknown = tmptext.isEmpty(); + + if ( !static_cast( item->parent()->parent() )->isSampler() ) + { + if ( m_cat1 == IdArtist ) + qb.setOptions( QueryBuilder::optNoCompilations ); + if( VisYearAlbum == 1 ) + { + tmptext = item->parent()->parent()->text( 0 ); + QString year = tmptext.left( tmptext.find( i18n(" - ") ) ); + yearAlbumCalc( year, tmptext ); + qb.addMatch( QueryBuilder::tabYear, year, false, true ); + if ( isUnknown ) + tmptext = ""; + } + + qb.addMatch( q_cat1, tmptext, false, true ); + } + else + { + qb.setOptions( QueryBuilder::optOnlyCompilations ); + c = true; + } + + tmptext = dynamic_cast( item->parent() ) ? + static_cast( item->parent() )->getSQLText( 0 ) : + item->parent()->text( 0 ); + isUnknown = tmptext.isEmpty(); + + if( VisYearAlbum == 2 ) + { + tmptext = item->parent()->text( 0 ); + QString year = tmptext.left( tmptext.find( i18n(" - ") ) ); + yearAlbumCalc( year, tmptext ); + qb.addMatch( QueryBuilder::tabYear, year, false, true ); + if ( isUnknown ) + tmptext = ""; + } + + qb.addMatch( q_cat2, tmptext, false, true ); + + tmptext = itemText; + isUnknown = tmptext.isEmpty(); + + if( VisYearAlbum == 3 ) + { + tmptext = item->text( 0 ); + QString year = tmptext.left( tmptext.find( i18n(" - ") ) ); + yearAlbumCalc( year, tmptext ); + qb.addMatch( QueryBuilder::tabYear, year, false, true ); + if ( isUnknown ) + tmptext = ""; + } + + qb.addMatch( q_cat3, tmptext, false, true ); + + qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valTitle, true ); + qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valURL ); + + if( c ) + qb.addReturnValue( QueryBuilder::tabArtist, QueryBuilder::valName, true ); + if ( SortbyTrackFirst ) { + qb.sortBy( QueryBuilder::tabSong, QueryBuilder::valDiscNumber ); + qb.sortBy( QueryBuilder::tabSong, QueryBuilder::valTrack ); + } + if ( c ) qb.sortBy( QueryBuilder::tabArtist, QueryBuilder::valName ); + qb.sortBy( QueryBuilder::tabSong, QueryBuilder::valTitle ); + if ( !SortbyTrackFirst ) { + qb.sortBy( QueryBuilder::tabSong, QueryBuilder::valDiscNumber ); + qb.sortBy( QueryBuilder::tabSong, QueryBuilder::valTrack ); + } + + qb.sortBy( QueryBuilder::tabSong, QueryBuilder::valURL ); + + category = IdNone; + break; + } + + qb.setGoogleFilter( q_cat1 | q_cat2 | q_cat3 | QueryBuilder::tabSong, m_filter ); + qb.setOptions( QueryBuilder::optRemoveDuplicates ); + values = qb.run(); + uint countReturnValues = qb.countReturnValues(); + + QPixmap pixmap; + bool expandable = category != IdNone; + if ( expandable ) + pixmap = iconForCategory( category ); + + //this check avoid possible problems on database errors. FIXME: Should we add some real error handling here, + //like calling a collection update or something? + if ( values.isEmpty() ) { return; } + + for ( int i = values.count() - countReturnValues; i >= 0; i -= countReturnValues ) + { + QString text; + bool unknown=false; + + if ( category == IdVisYearAlbum ) + text += ( values[ i+1 ].isEmpty() ? "?" : values[ i+1 ] ) + i18n( " - " ); + + //show "artist - title" for compilations + if ( c ) + { + if ( values[ i + 2 ].stripWhiteSpace().isEmpty() ) + { + text += i18n( "Unknown" ) + i18n( " - " ); + unknown = true; + } + else + text = values[ i + 2 ] + i18n( " - " ); + } + + if ( values[ i ].stripWhiteSpace().isEmpty() ) + { + if ( category == IdLabel ) + text += i18n( "No Label" ); + else + text += i18n( "Unknown" ); + unknown = true; + } + else + text += values[ i ]; + + CollectionItem* child = new CollectionItem( item, category, unknown ); + child->setDragEnabled( true ); + child->setDropEnabled( false ); + child->setText( 0, text ); + if ( expandable ) + child->setPixmap( 0, pixmap ); + else + child->setUrl( values[ i + 1 ] ); + child->setExpandable( expandable ); + } + + //Display the album cover for the parent item now it is expanded + if ( dynamic_cast( item ) ) + { + CollectionItem *i = static_cast( item ); + if ( i->m_cat == IdAlbum || i->m_cat == IdVisYearAlbum ) + i->setPixmap( 0, QPixmap() ); //The pixmap given is unimportant. The cover is used. + } +} + + +void +CollectionView::slotCollapse( QListViewItem* item ) //SLOT +{ + //On collapse, go back from showing the cover to showing the icon for albums + if ( dynamic_cast( item ) ) + { + CollectionItem *i = static_cast( item ); + if ( i->m_cat == IdAlbum || i->m_cat == IdVisYearAlbum ) + i->setPixmap( 0, iconForCategory( i->m_cat ) ); + } + + QListViewItem* child = item->firstChild(); + QListViewItem* childTmp; + + //delete all children + while ( child ) + { + childTmp = child; + child = child->nextSibling(); + delete childTmp; + } +} + +void +CollectionView::ratingChanged( const QString&, int ) +{ + m_dirty = true; + QTimer::singleShot( 0, CollectionView::instance(), SLOT( renderView() ) ); +} + +void +CollectionView::presetMenu( int id ) //SLOT +{ + switch ( id ) + { + case IdArtist: + cat1Menu( IdArtist, false ); + cat2Menu( IdNone, false ); + cat3Menu( IdNone, false ); + break; + case IdAlbum: + cat1Menu( IdAlbum, false ); + cat2Menu( IdNone, false ); + cat3Menu( IdNone, false ); + break; + case IdArtistAlbum: + cat1Menu( IdArtist, false ); + cat2Menu( IdAlbum, false ); + cat3Menu( IdNone, false ); + break; + case IdArtistVisYearAlbum: + cat1Menu( IdArtist, false ); + cat2Menu( IdVisYearAlbum, false ); + cat3Menu( IdNone, false ); + break; + case IdGenreArtist: + cat1Menu( IdGenre, false ); + cat2Menu( IdArtist, false ); + cat3Menu( IdNone, false ); + break; + case IdGenreArtistAlbum: + cat1Menu( IdGenre, false ); + cat2Menu( IdArtist, false ); + cat3Menu( IdAlbum, false ); + break; + } + + renderView(true); +} + + +void +CollectionView::cat1Menu( int id, bool rerender ) //SLOT +{ + m_parent->m_cat1Menu->setItemChecked( m_cat1, false ); //uncheck old item + m_parent->m_cat2Menu->setItemEnabled( m_cat1, true ); //enable old items + m_parent->m_cat3Menu->setItemEnabled( m_cat1, true ); + m_cat1 = id; + updateColumnHeader(); + resetIpodDepth(); + m_parent->m_cat1Menu->setItemChecked( m_cat1, true ); + + //prevent choosing the same category in both menus + m_parent->m_cat2Menu->setItemEnabled( id , false ); + m_parent->m_cat3Menu->setItemEnabled( id , false ); + + //if this item is checked in second menu, uncheck it + if ( m_parent->m_cat2Menu->isItemChecked( id ) ) { + m_parent->m_cat2Menu->setItemChecked( id, false ); + m_parent->m_cat2Menu->setItemChecked( IdNone, true ); + m_cat2 = IdNone; + enableCat3Menu( false ); + } + //if this item is checked in third menu, uncheck it + if ( m_parent->m_cat3Menu->isItemChecked( id ) ) { + m_parent->m_cat3Menu->setItemChecked( id, false ); + m_parent->m_cat3Menu->setItemChecked( IdNone, true ); + m_cat3 = IdNone; + } + updateTrackDepth(); + if ( rerender ) + { + renderView(true); + } +} + + +void +CollectionView::cat2Menu( int id, bool rerender ) //SLOT +{ + m_parent->m_cat2Menu->setItemChecked( m_cat2, false ); //uncheck old item + m_parent->m_cat3Menu->setItemEnabled( m_cat3, true ); //enable old item + m_cat2 = id; + m_parent->m_cat2Menu->setItemChecked( m_cat2, true ); + updateColumnHeader(); + resetIpodDepth(); + + enableCat3Menu( id != IdNone ); + + //prevent choosing the same category in both menus + m_parent->m_cat3Menu->setItemEnabled( m_cat1 , false ); + if( id != IdNone ) + m_parent->m_cat3Menu->setItemEnabled( id , false ); + + //if this item is checked in third menu, uncheck it + if ( m_parent->m_cat3Menu->isItemChecked( id ) ) { + m_parent->m_cat3Menu->setItemChecked( id, false ); + enableCat3Menu( false ); + } + updateTrackDepth(); + if ( rerender ) + { + renderView(true); + } +} + + +void +CollectionView::cat3Menu( int id, bool rerender ) //SLOT +{ + m_parent->m_cat3Menu->setItemChecked( m_cat3, false ); //uncheck old item + m_cat3 = id; + m_parent->m_cat3Menu->setItemChecked( m_cat3, true ); + updateColumnHeader(); + resetIpodDepth(); + updateTrackDepth(); + if ( rerender ) + { + renderView(true); + } +} + + +void +CollectionView::enableCat3Menu( bool enable ) +{ + m_parent->m_cat3Menu->setItemEnabled( IdAlbum, enable ); + m_parent->m_cat3Menu->setItemEnabled( IdVisYearAlbum, enable ); + m_parent->m_cat3Menu->setItemEnabled( IdArtist, enable ); + m_parent->m_cat3Menu->setItemEnabled( IdComposer, enable ); + m_parent->m_cat3Menu->setItemEnabled( IdGenre, enable ); + m_parent->m_cat3Menu->setItemEnabled( IdYear, enable ); + m_parent->m_cat3Menu->setItemEnabled( IdLabel, enable ); + + if( !enable ) { + m_parent->m_cat3Menu->setItemChecked( m_cat3, false ); + m_parent->m_cat3Menu->setItemChecked( IdNone, true ); + m_cat3 = IdNone; + } + updateTrackDepth(); +} + + +void +CollectionView::invokeItem( QListViewItem* i, const QPoint& point, int column ) //SLOT +{ + if( column == -1 ) + return; + + QPoint p = mapFromGlobal( point ); + if ( p.x() > header()->sectionPos( header()->mapToIndex( 0 ) ) + treeStepSize() * ( i->depth() + ( rootIsDecorated() ? 1 : 0) ) + itemMargin() + || p.x() < header()->sectionPos( header()->mapToIndex( 0 ) ) ) + invokeItem( i ); +} + +void +CollectionView::invokeItem( QListViewItem* item ) //SLOT +{ + if ( !item || dynamic_cast(item) ) + return; + + item->setSelected( true ); + setCurrentItem( item ); + //append and prevent doubles in playlist + if( item->isExpandable() || m_viewMode == modeIpodView ) + Playlist::instance()->insertMedia( listSelected(), Playlist::DefaultOptions ); + else + Playlist::instance()->insertMedia( static_cast( item )->url(), Playlist::DefaultOptions ); + +} + + +// This slot is here to handle clicks on the right-arrow buttons +// in iPod browsing mode +void +CollectionView::ipodItemClicked( QListViewItem *item, const QPoint&, int c ) +{ + if( item == 0 || c == 0 ) + return; + if( m_viewMode != modeIpodView ) + return; + + // The Qt manual says NOT to delete items from within this slot + QTimer::singleShot( 0, m_parent->m_ipodIncrement, SLOT( activate() ) ); +} + + +void +CollectionView::rmbPressed( QListViewItem* item, const QPoint& point, int ) //SLOT +{ + if ( dynamic_cast( item ) ) return; + + int artistLevel = -1; + + if ( item ) { + KPopupMenu menu( this ); + + int cat = 0; + if ( m_viewMode == modeTreeView ) { + switch ( item->depth() ) + { + case 0: + cat = m_cat1; + break; + case 1: + if( m_cat1 == IdArtist ) + artistLevel = 0; + cat = m_cat2; + break; + case 2: + if( m_cat1 == IdArtist ) + artistLevel = 0; + else if( m_cat2 == IdArtist ) + artistLevel = 1; + cat = m_cat3; + break; + } + } + + else if ( m_viewMode == modeIpodView ) { + int catArr[3] = {m_cat1, m_cat2, m_cat3}; + if ( m_currentDepth < trackDepth() ) + cat = catArr[m_currentDepth]; + } + + enum Actions { APPEND, QUEUE, MAKE, SAVE, MEDIA_DEVICE, BURN_ARTIST, + BURN_COMPOSER, BURN_ALBUM, BURN_CD, FETCH, INFO, + COMPILATION_SET, COMPILATION_UNSET, ORGANIZE, DELETE, TRASH, + FILE_MENU }; + + QString trueItemText = getTrueItemText( cat, item ); + KURL::List selection = listSelected(); + QStringList siblingSelection = listSelectedSiblingsOf( cat, item ); + + menu.insertItem( SmallIconSet( Amarok::icon( "files" ) ), i18n( "&Load" ), MAKE ); + menu.insertItem( SmallIconSet( Amarok::icon( "add_playlist" ) ), i18n( "&Append to Playlist" ), APPEND ); + menu.insertItem( SmallIconSet( Amarok::icon( "queue_track" ) ), selection.count() == 1 ? i18n( "&Queue Track" ) + : i18n( "&Queue Tracks" ), QUEUE ); + + if( selection.count() > 1 || item->isExpandable() ) + menu.insertItem( SmallIconSet( Amarok::icon( "save" ) ), i18n( "&Save as Playlist..." ), SAVE ); + + menu.insertSeparator(); + + if( MediaBrowser::isAvailable() ) + menu.insertItem( SmallIconSet( Amarok::icon( "device" ) ), i18n( "&Transfer to Media Device" ), MEDIA_DEVICE ); + + if( cat == IdArtist ) + { + menu.insertItem( SmallIconSet( Amarok::icon( "burn" ) ), i18n( "&Burn All Tracks by This Artist" ), BURN_ARTIST ); + menu.setItemEnabled( BURN_ARTIST, K3bExporter::isAvailable() && siblingSelection.count() == 1 ); + } + else if( cat == IdComposer ) + { + menu.insertItem( SmallIconSet( Amarok::icon( "burn" ) ), i18n( "&Burn All Tracks by This Composer" ), BURN_COMPOSER ); + menu.setItemEnabled( BURN_COMPOSER, K3bExporter::isAvailable() && siblingSelection.count() == 1 ); + } + else if( (cat == IdAlbum || cat == IdVisYearAlbum) ) + { + menu.insertItem( SmallIconSet( Amarok::icon( "burn" ) ), i18n( "&Burn This Album" ), BURN_ALBUM ); + menu.setItemEnabled( BURN_ALBUM, K3bExporter::isAvailable() && siblingSelection.count() == 1 ); + } + // !item->isExpandable() in tree mode corresponds to + // showing tracks in iPod mode + else if( !item->isExpandable() && + (m_viewMode != modeIpodView || m_currentDepth == trackDepth()) ) + { + menu.insertItem( SmallIconSet( Amarok::icon( "burn" ) ), i18n( "B&urn to CD" ), BURN_CD ); + menu.setItemEnabled( BURN_CD, K3bExporter::isAvailable() ); + } + + menu.insertSeparator(); + + KPopupMenu fileMenu; + fileMenu.insertItem( SmallIconSet( "filesaveas" ), i18n( "&Organize File..." , "&Organize %n Files..." , selection.count() ) , ORGANIZE ); + fileMenu.insertItem( SmallIconSet( Amarok::icon( "remove" ) ), i18n( "&Delete File..." , "&Delete %n Files..." , selection.count() ) , DELETE ); + menu.insertItem( SmallIconSet( Amarok::icon( "files" ) ), i18n( "Manage &Files" ), &fileMenu, FILE_MENU ); + + if( (cat == IdAlbum || cat == IdVisYearAlbum) && siblingSelection.count() == 1 ) // cover fetch isn't multiselection capable + { + menu.insertItem( SmallIconSet( Amarok::icon( "download" ) ), i18n( "&Fetch Cover From amazon.%1" ).arg( CoverManager::amazonTld() ), this, SLOT( fetchCover() ), 0, FETCH ); + #ifndef AMAZON_SUPPORT + menu.setItemEnabled( FETCH, false ); + #endif + if( trueItemText.isEmpty() ) // disable cover fetch for unknown albums + menu.setItemEnabled( FETCH, false ); + } + + if ( ( (cat == IdAlbum || cat == IdVisYearAlbum) && siblingSelection.count() > 0 ) //album + || ( !item->isExpandable() && m_viewMode == modeTreeView ) ) //or track + { + menu.insertSeparator(); + menu.insertItem( SmallIconSet( "ok" ), i18n( "Show under &Various Artists" ), COMPILATION_SET ); + menu.insertItem( SmallIconSet( "cancel" ), i18n( "&Do not Show under Various Artists" ), COMPILATION_UNSET ); + } + + menu.insertSeparator(); + + menu.insertItem( SmallIconSet( Amarok::icon( "info" ) ) + , i18n( "Edit Track &Information...", "Edit &Information for %n Tracks...", selection.count()) + , this, SLOT( showTrackInfo() ), 0, INFO ); + + switch( menu.exec( point ) ) + { + case APPEND: + Playlist::instance()->insertMedia( selection, Playlist::Append ); + break; + case MAKE: + Playlist::instance()->insertMedia( selection, Playlist::Replace ); + break; + case SAVE: + playlistFromURLs( selection ); + break; + case QUEUE: + Playlist::instance()->insertMedia( selection, Playlist::Queue ); + break; + case MEDIA_DEVICE: + MediaBrowser::queue()->addURLs( selection ); + break; + case BURN_COMPOSER: + K3bExporter::instance()->exportComposer( trueItemText ); + break; + case BURN_ARTIST: + K3bExporter::instance()->exportArtist( trueItemText ); + break; + case BURN_ALBUM: + if( artistLevel == -1 || static_cast(item)->isSampler() ) + { + K3bExporter::instance()->exportAlbum( trueItemText ); + } + else + { + QString artist; + if( item->depth() - artistLevel == 1 ) + artist = item->parent()->text( 0 ); + else if( item->depth() - artistLevel == 2 ) + artist = item->parent()->parent()->text( 0 ); + else if( item->depth() - artistLevel == 3 ) + artist = item->parent()->parent()->parent()->text( 0 ); + K3bExporter::instance()->exportAlbum( artist, trueItemText ); + } + break; + case BURN_CD: + K3bExporter::instance()->exportTracks( selection ); + break; + case COMPILATION_SET: + setCompilation( selection, true ); + break; + case COMPILATION_UNSET: + setCompilation( selection, false ); + break; + case ORGANIZE: + organizeFiles( selection, i18n( "Organize Collection Files" ), false /* do not add to collection, just move */ ); + break; + case DELETE: + if ( DeleteDialog::showTrashDialog(this, selection) ) + { + CollectionDB::instance()->removeSongs( selection ); + foreachType( KURL::List, selection ) + CollectionDB::instance()->emitFileDeleted( (*it).path() ); + } + m_dirty = true; + QTimer::singleShot( 0, CollectionView::instance(), SLOT( renderView() ) ); + break; + } + } +} + + +void +CollectionView::setViewMode( int mode, bool rerender /*=true*/ ) +{ + if( m_viewMode == modeFlatView ) + { + m_flatColumnWidths.clear(); + for ( int c = 0; c < columns(); ++c ) + m_flatColumnWidths.push_back( columnWidth( c ) ); + } + + m_viewMode = mode; + clear(); + updateColumnHeader(); + + if( m_viewMode == modeIpodView ) + { + #if KDE_VERSION >= KDE_MAKE_VERSION(3,4,0) + setShadeSortColumn( false ); + #endif + m_parent->m_ipodDecrement->setEnabled( m_currentDepth > 0 ); + m_parent->ipodToolbar( true ); + } + else + { + #if KDE_VERSION >= KDE_MAKE_VERSION(3,4,0) + setShadeSortColumn( true ); + #endif + m_parent->ipodToolbar( false ); + } + + if ( rerender ) + { + // Pretend we just incremented the view depth so that + // renderView() will call selectIpodItems() + if( m_viewMode == modeIpodView ) + m_ipodIncremented = 1; + + renderView( true ); + } +} + +void +CollectionItem::setPixmap(int column, const QPixmap & pix) +{ + //Don't show the cover if the album isn't expanded (for speed) + if ( !isOpen() ) + { + QListViewItem::setPixmap( column, pix ); + return; + } + + //Generate Album name + QString album( text( 0 ) ), artist; + if ( m_cat == IdVisYearAlbum ) + { + QString pointlessString; + CollectionView::yearAlbumCalc( pointlessString, album ); + } + else if ( m_cat != IdAlbum ) + { + QListViewItem::setPixmap( column, pix ); + return; + } + + //Now m_cat is either IdAlbum or IdVisYearAlbum, and so this is an album as required. + + //Now work out the artist + CollectionItem *p = this; + while ( p->parent() && dynamic_cast( p->parent() ) ) + { + p = static_cast( p->parent() ); + if ( IdArtist == p->m_cat ) + { + artist = p->text( 0 ); + break; + } + } + + if ( artist.isNull() ) + { + //Try to guess artist - this will only happen if you don't have an Artist category + //above the Album category in the tree + QueryBuilder qb; + qb.addReturnValue( QueryBuilder::tabArtist, QueryBuilder::valName ); + qb.addMatch( QueryBuilder::tabAlbum, QueryBuilder::valName, album ); + + QStringList values( qb.run() ); + + if ( !values.isEmpty() ) + artist = values[ 0 ]; + else + { + //Don't bother trying to create a shadow because it won't work anyway. The + //nocover image has intial transparency, so adding the shadow doesn't work. + QListViewItem::setPixmap( column, QPixmap( CollectionDB::instance()->notAvailCover( false, 50 ) ) ); + return; + } + } + + QListViewItem::setPixmap( column, QPixmap( CollectionDB::instance()->albumImage( artist, album, true, 50 ) ) ); +} + + +void +CollectionView::fetchCover() //SLOT +{ + #ifdef AMAZON_SUPPORT + CollectionItem* item = static_cast( currentItem() ); + if ( !item ) return; + + int cat = 0; + switch ( item->depth() ) + { + case 0: + cat = m_cat1; + break; + case 1: + cat = m_cat2; + break; + case 2: + cat = m_cat3; + break; + } + + QString album = item->text(0); + if( cat == IdVisYearAlbum ) + { + // we can't use findRev since an album may have " - " within it. + QString sep = i18n(" - "); + album = album.right( album.length() - sep.length() - album.find( sep ) ); + } + + // find the first artist's name + QStringList values = + CollectionDB::instance()->query( QString ( + "SELECT DISTINCT artist.name FROM artist, album, tags " + "WHERE artist.id = tags.artist AND tags.album = album.id " + "AND album.name = '%1';" ) + .arg( CollectionDB::instance()->escapeString( album ) ) ); + + if ( !values.isEmpty() ) + CollectionDB::instance()->fetchCover( this, values[0], album, false, static_cast(item) ); + #endif +} + +void +CollectionView::showTrackInfo() //SLOT +{ + DEBUG_BLOCK + KURL::List urls = listSelected(); + int selectedTracksNumber = urls.count(); + + //If we have only one, call the full dialog. Otherwise, the multiple tracks one. + if ( selectedTracksNumber == 1 ) + { + TagDialog* dialog = new TagDialog( urls.first(), instance() ); + dialog->show(); + } + else if ( selectedTracksNumber ) + { + TagDialog* dialog = new TagDialog( urls, instance() ); + dialog->show(); + } +} + +bool +CollectionView::isOrganizingFiles() const +{ + return m_organizeURLs.count() > 0; +} + +void CollectionView::cancelOrganizingFiles() +{ + // Set the indicator + m_organizingFileCancelled = true; + + // Cancel the current underlying CollectionDB::instance()->moveFile operation + CollectionDB::instance()->cancelMovingFileJob(); +} + +void +CollectionView::organizeFiles( const KURL::List &urls, const QString &caption, bool copy ) //SLOT +{ + if( m_organizingFileCancelled ) + { + QString shortMsg = i18n( "Cannot start organize operation until jobs are aborted." ); + Amarok::StatusBar::instance()->shortMessage( shortMsg, KDE::StatusBar::Sorry ); + return; + } + + if( m_organizeURLs.count() ) + { + if( copy != m_organizeCopyMode ) + { + QString shortMsg = i18n( "Cannot start organize operation of different kind while another is in progress." ); + Amarok::StatusBar::instance()->shortMessage( shortMsg, KDE::StatusBar::Sorry ); + return; + } + else + { + m_organizeURLs += Amarok::recursiveUrlExpand( urls ); + Amarok::StatusBar::instance()->incrementProgressTotalSteps( this, urls.count() ); + return; + } + } + + QStringList folders = MountPointManager::instance()->collectionFolders(); + if( folders.isEmpty() ) + { + QString longMsg = i18n( "You need to configure at least one folder for your collection for organizing your files." ); + Amarok::StatusBar::instance()->longMessage( longMsg, KDE::StatusBar::Sorry ); + return; + } + + OrganizeCollectionDialogBase base( m_parent, "OrganizeFiles", true, caption, + KDialogBase::Ok|KDialogBase::Cancel|KDialogBase::Details ); + QVBox* page = base.makeVBoxMainWidget(); + + OrganizeCollectionDialog dialog( page ); + dialog.folderCombo->insertStringList( folders, 0 ); + dialog.folderCombo->setCurrentItem( AmarokConfig::organizeDirectory() ); + dialog.overwriteCheck->setChecked( AmarokConfig::overwriteFiles() ); + dialog.filetypeCheck->setChecked( AmarokConfig::groupByFiletype() ); + dialog.initialCheck->setChecked( AmarokConfig::groupArtists() ); + dialog.spaceCheck->setChecked( AmarokConfig::replaceSpace() ); + dialog.coverCheck->setChecked( AmarokConfig::coverIcons() ); + dialog.ignoreTheCheck->setChecked( AmarokConfig::ignoreThe() ); + dialog.vfatCheck->setChecked( AmarokConfig::vfatCompatible() ); + dialog.asciiCheck->setChecked( AmarokConfig::asciiOnly() ); + dialog.customschemeCheck->setChecked( AmarokConfig::useCustomScheme() ); + dialog.formatEdit->setText( AmarokConfig::customScheme() ); + dialog.regexpEdit->setText( AmarokConfig::replacementRegexp() ); + dialog.replaceEdit->setText( AmarokConfig::replacementString() ); + connect( &base, SIGNAL(detailsClicked()), &dialog, SLOT(slotDetails()) ); + + if( dialog.customschemeCheck->isChecked() ) + { + base.setDetails( true ); + } + else + { + dialog.slotDetails(); + } + + KURL::List previewURLs = Amarok::recursiveUrlExpand( urls.first(), 1 ); + if( previewURLs.count() ) + { + dialog.setPreviewBundle( MetaBundle( previewURLs.first() ) ); + dialog.update( 0 ); + } + + base.setInitialSize( QSize( 450, 350 ) ); + + if( base.exec() == KDialogBase::Accepted ) + { + AmarokConfig::setOrganizeDirectory( dialog.folderCombo->currentItem() ); + AmarokConfig::setOverwriteFiles( dialog.overwriteCheck->isChecked() ); + AmarokConfig::setGroupByFiletype( dialog.filetypeCheck->isChecked() ); + AmarokConfig::setGroupArtists( dialog.initialCheck->isChecked() ); + AmarokConfig::setIgnoreThe( dialog.ignoreTheCheck->isChecked() ); + AmarokConfig::setReplaceSpace( dialog.spaceCheck->isChecked() ); + AmarokConfig::setCoverIcons( dialog.coverCheck->isChecked() ); + AmarokConfig::setVfatCompatible( dialog.vfatCheck->isChecked() ); + AmarokConfig::setAsciiOnly( dialog.asciiCheck->isChecked() ); + AmarokConfig::setUseCustomScheme( dialog.customschemeCheck->isChecked() ); + AmarokConfig::setCustomScheme( dialog.formatEdit->text() ); + AmarokConfig::setReplacementRegexp( dialog.regexpEdit->text() ); + AmarokConfig::setReplacementString( dialog.replaceEdit->text() ); + KURL::List skipped; + + m_organizeURLs = Amarok::recursiveUrlExpand( urls ); + m_organizeCopyMode = copy; + CollectionDB::instance()->createTables( true ); // create temp tables + Amarok::StatusBar::instance()->newProgressOperation( this ) + .setDescription( caption ) + .setAbortSlot( this, SLOT( cancelOrganizingFiles() ) ) + .setTotalSteps( m_organizeURLs.count() ); + + while( !m_organizeURLs.empty() && !m_organizingFileCancelled ) + { + KURL &src = m_organizeURLs.first(); + + if( !CollectionDB::instance()->organizeFile( src, dialog, copy ) ) + { + skipped += src; + } + + m_organizeURLs.pop_front(); + Amarok::StatusBar::instance()->incrementProgress( this ); + + if( m_organizingFileCancelled ) m_organizeURLs.clear(); + } + + CollectionDB::instance()->sanitizeCompilations(); //queryBuilder doesn't handle unknownCompilations + CollectionDB::instance()->copyTempTables(); // copy temp table contents to permanent tables + CollectionDB::instance()->dropTables( true ); // and drop them + + // and now do an incremental scan since this was disabled while organizing files + QTimer::singleShot( 0, CollectionDB::instance(), SLOT( scanMonitor() ) ); + + if( !m_organizingFileCancelled && skipped.count() > 0 ) + { + QString longMsg = i18n( "The following file could not be organized: ", + "The following %n files could not be organized: ", skipped.count() ); + bool first = true; + for( KURL::List::iterator it = skipped.begin(); + it != skipped.end(); + it++ ) + { + if( !first ) + longMsg += i18n( ", " ); + else + first = false; + longMsg += (*it).path(); + } + longMsg += i18n( "." ); + + QString shortMsg = i18n( "Sorry, one file could not be organized.", + "Sorry, %n files could not be organized.", skipped.count() ); + Amarok::StatusBar::instance()->shortLongMessage( shortMsg, longMsg, KDE::StatusBar::Sorry ); + } + else if ( m_organizingFileCancelled ) + { + Amarok::StatusBar::instance()->shortMessage( i18n( "Aborting jobs..." ) ); + m_organizingFileCancelled = false; + } + + m_dirty = true; + QTimer::singleShot( 0, CollectionView::instance(), SLOT( renderView() ) ); + Amarok::StatusBar::instance()->endProgressOperation( this ); + } +} + +////////////////////////////////////////////////////////////////////////////////////////// +// private +////////////////////////////////////////////////////////////////////////////////////////// + +void +CollectionView::contentsDragEnterEvent( QDragEnterEvent *e ) +{ + e->accept( e->source() != viewport() && e->source() != this && KURLDrag::canDecode( e ) ); +} + +void +CollectionView::contentsDragMoveEvent( QDragMoveEvent *e ) +{ + e->accept( e->source() != viewport() && e->source() != this && KURLDrag::canDecode( e ) ); +} + +void +CollectionView::contentsDropEvent( QDropEvent *e ) +{ + KURL::List list; + if( KURLDrag::decode( e, list ) ) + { + KURL::List expandedList; + int dropped = 0; + int invalid = 0; + for( KURL::List::iterator it = list.begin(); + it != list.end(); + ++it ) + { + if( (*it).isLocalFile() && QFileInfo( (*it).path() ).isDir() ) + expandedList += Amarok::recursiveUrlExpand( *it ); + else + expandedList += *it; + } + + KURL::List cleanList; + for( KURL::List::iterator it = expandedList.begin(); + it != expandedList.end(); + ++it ) + { + QString proto = (*it).protocol(); + if( !MetaBundle::isKioUrl( *it ) ) + invalid++; + else if( (*it).isLocalFile() && CollectionDB::instance()->isFileInCollection( (*it).path() ) ) + dropped++; + else + cleanList += *it; + } + + QString msg; + if( dropped > 0 ) + msg += i18n( "One file already in collection", + "%n files already in collection", dropped ); + if( invalid > 0 ) + if( msg.isEmpty() ) + msg += i18n( "One dropped file is invalid", + "%n dropped files are invalid", invalid ); + else + msg += i18n( ", one dropped file is invalid", + ", %n dropped files are invalid", invalid ); + if( !msg.isEmpty() ) + Amarok::StatusBar::instance()->shortMessage( msg ); + if( cleanList.count() > 0 ) + organizeFiles( list, i18n( "Copy Files To Collection" ), true /* copy */ ); + } +} + +void +CollectionView::dropProxyEvent( QDropEvent *e ) +{ + contentsDropEvent( e ); +} + +void +CollectionView::safeClear() +{ + bool block = signalsBlocked(); + blockSignals( true ); + clearSelection(); + + QMap *itemCoverMap = CollectionDB::instance()->getItemCoverMap(); + QMutex* itemCoverMapMutex = CollectionDB::instance()->getItemCoverMapMutex(); + QListViewItem *c = firstChild(); + QListViewItem *n; + itemCoverMapMutex->lock(); + while( c ) { + if( itemCoverMap->contains( c ) ) + itemCoverMap->erase( c ); + n = c->nextSibling(); + delete c; + c = n; + } + itemCoverMapMutex->unlock(); + blockSignals( block ); + triggerUpdate(); +} + +void +CollectionView::updateColumnHeader() +{ + // remove all columns + for ( int i = columns() - 1; i >= 0 ; --i ) + removeColumn( i ); + + if ( m_viewMode == modeFlatView ) + { + setResizeMode( QListView::NoColumn ); + + if( m_flatColumnWidths.size() == 0 ) + { + addColumn( captionForTag( Title ) ); +#define includesArtist(cat) (((cat)&IdArtist) \ + ||((cat)&IdArtistAlbum) \ + ||((cat)&IdGenreArtist) \ + ||((cat)&IdGenreArtistAlbum) \ + ||((cat)&IdArtistVisYearAlbum)) + if( includesArtist(m_cat1)||includesArtist(m_cat2)||includesArtist(m_cat3) ) + addColumn( captionForTag( Artist ) ); + else + addColumn( captionForTag( Artist ), 0 ); +#undef includesArtist + if( m_cat1&IdComposer || m_cat2&IdComposer || m_cat3&IdComposer ) + addColumn( captionForTag( Composer ) ); + else + addColumn( captionForTag( Composer ), 0 ); +#define includesAlbum(cat) (((cat)&IdAlbum) \ + ||((cat)&IdArtistAlbum) \ + ||((cat)&IdGenreArtistAlbum) \ + ||((cat)&IdVisYearAlbum) \ + ||((cat)&IdArtistVisYearAlbum)) + if( includesAlbum(m_cat1)||includesAlbum(m_cat2)||includesAlbum(m_cat3) ) + addColumn( captionForTag( Album ) ); + else + addColumn( captionForTag( Album ), 0 ); +#undef includesAlbum +#define includesGenre(cat) (((cat)&IdGenre) \ + ||((cat)&IdGenreArtist) \ + ||((cat)&IdGenreArtistAlbum)) + if( includesGenre(m_cat1)||includesGenre(m_cat2)||includesGenre(m_cat3) ) + addColumn( captionForTag( Genre ) ); + else + addColumn( captionForTag( Genre ), 0 ); +#undef includesGenre + addColumn( captionForTag( Length ),0 ); + addColumn( captionForTag( DiscNumber ), 0 ); + addColumn( captionForTag( Track ), 0 ); +#define includesYear(cat) (((cat)&IdYear) \ + ||((cat)&IdVisYearAlbum) \ + ||((cat)&IdArtistVisYearAlbum)) + if( includesYear(m_cat1)||includesYear(m_cat2)||includesYear(m_cat3) ) + addColumn( captionForTag( Year ) ); + else + addColumn( captionForTag( Year ), 0 ); +#undef includesYear + addColumn( captionForTag( Comment ), 0 ); + addColumn( captionForTag( Playcount ), 0 ); + addColumn( captionForTag( Score ), 0 ); + addColumn( captionForTag( Rating ), 0 ); + addColumn( captionForTag( Filename ), 0 ); + addColumn( captionForTag( Firstplay ), 0 ); + addColumn( captionForTag( Lastplay ), 0 ); + addColumn( captionForTag( Modified ), 0 ); + addColumn( captionForTag( Bitrate ), 0 ); + addColumn( captionForTag( Filesize ), 0 ); + addColumn( captionForTag( BPM ), 0 ); + } + else + { + for( uint tag = 0; tag < NUM_TAGS; ++tag ) { + if( tag < m_flatColumnWidths.size() ) + addColumn( captionForTag( static_cast( tag ) ), m_flatColumnWidths[tag] ); + else + addColumn( captionForTag( static_cast( tag ) ), 0 ); + } + } + + setColumnAlignment( Track, Qt::AlignCenter ); + setColumnAlignment( DiscNumber, Qt::AlignCenter ); + setColumnAlignment( Length, Qt::AlignRight ); + setColumnAlignment( Bitrate, Qt::AlignCenter ); + setColumnAlignment( Score, Qt::AlignCenter ); + setColumnAlignment( Playcount, Qt::AlignCenter ); + setColumnAlignment( BPM, Qt::AlignRight ); + + //QListView allows invisible columns to be resized, so we disable resizing for them + for ( int i = 0; i < columns(); ++i ) { + setColumnWidthMode ( i, QListView::Manual ); + if ( columnWidth( i ) == 0 ) + header()->setResizeEnabled( false, i ); + } + setRootIsDecorated( false ); + } + else if ( m_viewMode == modeTreeView ) + { + setResizeMode( QListView::LastColumn ); + + QString caption = captionForCategory( m_cat1 ); + int catArr[2] = {m_cat2, m_cat3}; + + for(int i = 0; i < 2; i++) { + if (catArr[i] != IdNone ) { + caption += " / " + captionForCategory( catArr[i] ); + } + } + addColumn( caption ); + setRootIsDecorated( true ); + } + + // The iPod columns + // When not in track mode, there are two columns: the first + // contains the text + pixmap, and the second just has the + // right-arrow pixmap. In any case we're not in tree mode. + else if ( m_viewMode == modeIpodView ) + { + QString caption; + + if( m_currentDepth == trackDepth() ) + caption = i18n( "Tracks" ); + + else + { + int catArr[3] = {m_cat1, m_cat2, m_cat3}; + caption = captionForCategory( catArr[m_currentDepth] ); + } + + addColumn( caption ); + + if( m_currentDepth != trackDepth() ) + { + QPixmap pixmap = ipodDecrementIcon(); + // This column is for the "->" buttons. The width is just + // a guess, and will be changed once an item is added. + addColumn( "", pixmap.width() + 10 ); + } + setRootIsDecorated( false ); + + // Neither column should automatically stretch, since this tends + // to add a scrollbar when it's not needed, and anyway we manually + // stretch it in viewportResizeEvent() + header()->setStretchEnabled( false, 0 ); + if( m_currentDepth != trackDepth() ) + header()->setStretchEnabled( false, 1 ); + + } + + //manage column widths + QResizeEvent rev( size(), QSize() ); + viewportResizeEvent( &rev ); + + m_parent->m_categoryMenu->setItemChecked( IdArtist, m_cat1 == IdArtist && m_cat2 == IdNone ); + m_parent->m_categoryMenu->setItemChecked( IdAlbum, m_cat1 == IdAlbum && m_cat2 == IdNone ); + m_parent->m_categoryMenu->setItemChecked( IdArtistAlbum, m_cat1 == IdArtist && m_cat2 == IdAlbum && m_cat3 == IdNone ); + m_parent->m_categoryMenu->setItemChecked( IdArtistVisYearAlbum, m_cat1 == IdArtist && m_cat2 == IdVisYearAlbum && m_cat3 == IdNone ); + m_parent->m_categoryMenu->setItemChecked( IdGenreArtist, m_cat1 == IdGenre && m_cat2 == IdArtist && m_cat3 == IdNone ); + m_parent->m_categoryMenu->setItemChecked( IdGenreArtistAlbum, m_cat1 == IdGenre && m_cat2 == IdArtist && m_cat3 == IdAlbum ); +} + + +void +CollectionView::startDrag() +{ + KURL::List urls = listSelected(); + KURLDrag* d = new KURLDrag( urls, this ); + d->setPixmap( CollectionDB::createDragPixmap(urls), + QPoint( CollectionDB::DRAGPIXMAP_OFFSET_X, + CollectionDB::DRAGPIXMAP_OFFSET_Y ) ); + d->dragCopy(); +} + +QString +CollectionView::getTrueItemText( int cat, QListViewItem* item ) const +{ + //Work out the true name of the album ( where Unknown is "" ) , and the + QString trueItemText; + if ( item == 0 ) + { + warning() << "getTrueItemText() called for empty CollectionItem" << endl; + return QString(); + } + if ( dynamic_cast( item ) ) + { + CollectionItem* collectItem = static_cast( item ); + trueItemText = collectItem->getSQLText( 0 ); + if ( cat == IdVisYearAlbum && !collectItem->isUnknown() ) + trueItemText = trueItemText.right( trueItemText.length() - trueItemText.find( i18n( " - " ) ) - i18n( " - " ).length() ); + } + else + { + trueItemText = item->text( 0 ); + warning() << "getTrueItemText() called for non-CollectionItem with text '" << trueItemText << '\'' << endl; + } + return trueItemText; +} + +QStringList +CollectionView::listSelectedSiblingsOf( int cat, QListViewItem* item ) +{ + // notice that using the nextSibling()-axis does not work in this case as this + // would only select items below the specified item. + QStringList list; + QString trueItemText; + int depth = item->depth(); + + // go to top most item + while( item && item->itemAbove() ) + { + item = item->itemAbove(); + //debug() << "walked up to item: " << getTrueItemText( cat, item ) << endl; + } + // walk down to get all selected items in same depth + while( item ) + { + if ( item->isSelected() && item->depth() == depth ) + { + trueItemText = getTrueItemText( cat, item ); + //debug() << "selected item: " << trueItemText << endl; + list << trueItemText; + } + item = item->itemBelow(); + } + return list; +} + +KURL::List +CollectionView::listSelected() +{ + //Here we determine the URLs of all selected items. We use two passes, one for the parent items, + //and another one for the children. + + KURL::List list; + QListViewItem* item; + QStringList values; + QueryBuilder qb; + + // initialization for year - album mode + QString tmptext; + bool unknownText; + int VisYearAlbum = -1; + int q_cat1=m_cat1; + int q_cat2=m_cat2; + int q_cat3=m_cat3; + if (m_cat1 == IdVisYearAlbum || m_cat2 == IdVisYearAlbum || m_cat3 == IdVisYearAlbum) + { + if (m_cat1==IdVisYearAlbum) + { + VisYearAlbum = 1; + q_cat1 = IdAlbum; + } + if (m_cat2==IdVisYearAlbum) + { + VisYearAlbum = 2; + q_cat2 = IdAlbum; + } + if (m_cat3==IdVisYearAlbum) + { + VisYearAlbum = 3; + q_cat3 = IdAlbum; + } + } + + if ( m_viewMode == modeFlatView ) + { + for ( item = firstChild(); item; item = item->nextSibling() ) + if ( item->isSelected() ) + list << static_cast( item ) ->url(); + + return list; + } + + + // The iPod selection code is written to resemble the tree mode + // selection logic, as well as what happens on an actual iPod. If + // we're in track mode, just return the list of tracks selected. + // Otherwise select all children of all currently selected items, + // sorting first by m_cat1, then m_cat2, then m_cat3. Sort by + // track first if one of the categories is Id(VisYear)Album. + // There is a difficulty with compilation albums -- if we're + // sorting by track first then we want to group compilation albums + // separately, and not with the individual artists, even if that's + // one of the categories (e.g. if the user just adds one + // compilation album, we can't sort by artist first). In other + // words, when one of the categories is Id(VisYear)Album, + // compilation albums should behave as if the artist is Various + // Artists, for sorting purposes. This is handled by making two + // separate queries, the first for the compilations, and the + // second for everything else. + + // Note that if "All" is currently selected then the other + // selections are ignored. + if ( m_viewMode == modeIpodView ) + { + // If we're already displaying tracks, just return the selected ones + if( m_currentDepth == trackDepth() ) + { + QPtrList selected = selectedItems(); + QPtrList::iterator it = selected.begin(); + while( it != selected.end() ) + { + if( dynamic_cast(*it) != 0 ) + list << dynamic_cast(*it)->url(); + ++it; + } + return list; + } + + // We're not displaying tracks. + QueryBuilder qb; + + // Do a "fake" depth incrementation to figure out the + // correct filters at the current depth + incrementDepth( false ); + + // Copy the filter list before calling decrementDepth() below + QStringList filters[3]; + for( int i = 0; i < m_currentDepth; ++i ) + filters[i] = m_ipodFilters[i]; + QStringList filterYear = m_ipodFilterYear; + + // Undo the fake depth incrementation + decrementDepth( false ); + + int catArr[3] = {m_cat1, m_cat2, m_cat3}; + int tables = 0; + bool sortByTrackFirst = false; + for(int i = 0; i < trackDepth(); ++i) + tables |= (catArr[i] == IdVisYearAlbum + ? IdAlbum + : catArr[i]); + + // Figure out if the results will be sorted by track first + // (i.e., if one of the filters is by album). If so, we need + // to search compilations first. + if( tables & IdAlbum ) + sortByTrackFirst = true; + + if( sortByTrackFirst ) + { + // Build the query, recursively sorted. First get compilation + // albums so they'll be first, not sorted by artist + buildIpodQuery( qb, trackDepth(), filters, filterYear, true, true ); + + if ( translateTimeFilter( timeFilter() ) > 0 ) + qb.addFilter( QueryBuilder::tabSong, QueryBuilder::valCreateDate, QString().setNum( QDateTime::currentDateTime().toTime_t() - translateTimeFilter( timeFilter() ) ), QueryBuilder::modeGreater ); + + qb.setOptions( QueryBuilder::optOnlyCompilations ); + qb.setGoogleFilter( tables | QueryBuilder::tabSong, m_filter ); + qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valURL ); + + values = qb.run(); + } + + // Now build the non-compilation album query + qb.clear(); + + buildIpodQuery( qb, trackDepth(), filters, filterYear, true, false ); + + if ( translateTimeFilter( timeFilter() ) > 0 ) + qb.addFilter( QueryBuilder::tabSong, QueryBuilder::valCreateDate, QString().setNum( QDateTime::currentDateTime().toTime_t() - translateTimeFilter( timeFilter() ) ), QueryBuilder::modeGreater ); + + if( sortByTrackFirst ) + qb.setOptions( QueryBuilder::optNoCompilations ); + qb.setGoogleFilter( tables | QueryBuilder::tabSong, m_filter ); + qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valURL ); + + values += qb.run(); + + int total = values.count() / qb.countReturnValues(); + + // This shouldn't happen + if( total == 0 ) + return list; + + + QStringList::Iterator it = values.begin(); + KURL tmp; + while ( it != values.end() ) + { + tmp.setPath( (*(it++)) ); + list << tmp; + } + + return list; + } + + + //first pass: parents + for ( item = firstChild(); item; item = item->nextSibling() ) + if ( item->isSelected() ) + { + const bool sampler = static_cast( item )->isSampler(); + qb.clear(); + if ( translateTimeFilter( timeFilter() ) > 0 ) + qb.addFilter( QueryBuilder::tabSong, QueryBuilder::valCreateDate, QString().setNum( QDateTime::currentDateTime().toTime_t() - translateTimeFilter( timeFilter() ) ), QueryBuilder::modeGreater ); + + qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valURL ); + + tmptext = static_cast( item )->getSQLText( 0 ); + unknownText = tmptext.isEmpty(); + + if ( !sampler ) + { + if ( q_cat1 == IdArtist ) + qb.setOptions( QueryBuilder::optNoCompilations ); + if( VisYearAlbum == 1 ) + { + tmptext = item->text( 0 ); + QString year = tmptext.left( tmptext.find( i18n(" - ") ) ); + yearAlbumCalc( year, tmptext ); + qb.addMatch( QueryBuilder::tabYear, year, false, true ); + if ( unknownText ) + tmptext = ""; + } + + qb.addMatch( q_cat1, tmptext, false, true ); + } + else + qb.setOptions( QueryBuilder::optOnlyCompilations ); + + qb.setGoogleFilter( q_cat1 | q_cat2 | q_cat3 | QueryBuilder::tabSong, m_filter ); + + if( VisYearAlbum == 1 ) + qb.sortBy( QueryBuilder::tabYear, QueryBuilder::valName); + + if( !sampler ) qb.sortBy( q_cat1, QueryBuilder::valName ); + + if( VisYearAlbum == 2 ) + qb.sortBy( QueryBuilder::tabYear, QueryBuilder::valName); + + if( q_cat2 != QueryBuilder::tabSong ) + qb.sortBy( q_cat2, QueryBuilder::valName ); + + if( VisYearAlbum == 3 ) + qb.sortBy( QueryBuilder::tabYear, QueryBuilder::valName); + + if( q_cat3 != QueryBuilder::tabSong ) + qb.sortBy( q_cat3, QueryBuilder::valName ); + + qb.sortBy( QueryBuilder::tabSong, QueryBuilder::valDiscNumber ); + qb.sortBy( QueryBuilder::tabSong, QueryBuilder::valTrack ); + qb.sortBy( QueryBuilder::tabSong, QueryBuilder::valURL ); + + qb.setOptions( QueryBuilder::optRemoveDuplicates ); + values = qb.run(); + + for ( uint i = 0; i < values.count(); i++ ) + { + KURL tmp; + tmp.setPath( values[i] ); + list << tmp; + } + } + + //second pass: category 1 + if ( m_cat2 == IdNone ) + { + for ( item = firstChild(); item; item = item->nextSibling() ) + for ( QListViewItem* child = item->firstChild(); child; child = child->nextSibling() ) + if ( child->isSelected() && !child->parent()->isSelected() ) + list << static_cast( child ) ->url(); + } + else { + for ( item = firstChild(); item; item = item->nextSibling() ) + for ( QListViewItem* child = item->firstChild(); child; child = child->nextSibling() ) + if ( child->isSelected() && !child->parent()->isSelected() ) + { + const bool sampler = static_cast( item )->isSampler(); + qb.clear(); + if ( translateTimeFilter( timeFilter() ) > 0 ) + qb.addFilter( QueryBuilder::tabSong, QueryBuilder::valCreateDate, QString().setNum( QDateTime::currentDateTime().toTime_t() - translateTimeFilter( timeFilter() ) ), QueryBuilder::modeGreater ); + + qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valURL ); + + if ( !sampler ) + { + if ( q_cat1 == IdArtist ) + qb.setOptions( QueryBuilder::optNoCompilations ); + + tmptext = static_cast( item )->getSQLText( 0 ); + unknownText = tmptext.isEmpty(); + + if( VisYearAlbum == 1 ) + { + tmptext = item->text( 0 ); + QString year = tmptext.left( tmptext.find( i18n(" - ") ) ); + yearAlbumCalc( year, tmptext ); + qb.addMatch( QueryBuilder::tabYear, year, false, true ); + if ( unknownText ) + tmptext = ""; + } + + qb.addMatch( q_cat1, tmptext, false, true ); + } + else + qb.setOptions( QueryBuilder::optOnlyCompilations ); + + + tmptext = static_cast( child )->getSQLText( 0 ); + unknownText = tmptext.isEmpty(); + + if( VisYearAlbum == 2 ) + { + tmptext = child->text( 0 ); + QString year = tmptext.left( tmptext.find( i18n(" - ") ) ); + yearAlbumCalc( year, tmptext ); + qb.addMatch( QueryBuilder::tabYear, year, false, true ); + if ( unknownText ) + tmptext = ""; + } + + qb.addMatch( q_cat2, tmptext, false, true ); + + qb.setGoogleFilter( q_cat1 | q_cat2 | q_cat3 | QueryBuilder::tabSong, m_filter ); + + if( VisYearAlbum == 1 ) + qb.sortBy( QueryBuilder::tabYear, QueryBuilder::valName); + + if( !sampler ) + qb.sortBy( q_cat1, QueryBuilder::valName ); + + if( VisYearAlbum == 2 ) + qb.sortBy( QueryBuilder::tabYear, QueryBuilder::valName); + + qb.sortBy( q_cat2, QueryBuilder::valName ); + + if( VisYearAlbum == 3 ) + qb.sortBy( QueryBuilder::tabYear, QueryBuilder::valName); + + if( q_cat3 != QueryBuilder::tabSong ) + qb.sortBy( q_cat3, QueryBuilder::valName ); + + qb.sortBy( QueryBuilder::tabSong, QueryBuilder::valDiscNumber ); + qb.sortBy( QueryBuilder::tabSong, QueryBuilder::valTrack ); + qb.sortBy( QueryBuilder::tabSong, QueryBuilder::valURL ); + + qb.setOptions( QueryBuilder::optRemoveDuplicates ); + values = qb.run(); + + for ( uint i = 0; i < values.count(); i++ ) + { + KURL tmp; + tmp.setPath( values[i] ); + list << tmp; + } + } + } + + //third pass: category 2 + for ( item = firstChild(); item; item = item->nextSibling() ) + for ( QListViewItem* child = item->firstChild(); child; child = child->nextSibling() ) + for ( QListViewItem* grandChild = child->firstChild(); grandChild; grandChild = grandChild->nextSibling() ) + if ( grandChild->isSelected() && !grandChild->isExpandable() && !child->parent()->isSelected() && !child->isSelected() ) + list << static_cast( grandChild ) ->url(); + + //category 3 + if ( m_cat3 == IdNone ) + { + for ( item = firstChild(); item; item = item->nextSibling() ) + for ( QListViewItem* child = item->firstChild(); child; child = child->nextSibling() ) + for ( QListViewItem* grandChild = child->firstChild(); grandChild; grandChild = grandChild->nextSibling() ) + for ( QListViewItem* grandChild2 = grandChild->firstChild(); grandChild2; grandChild2 = grandChild2->nextSibling() ) + if ( grandChild2->isSelected() && !child->parent()->isSelected() && !child->isSelected() && !grandChild->isSelected() ) + list << static_cast( grandChild2 ) ->url(); + } + else { + for ( item = firstChild(); item; item = item->nextSibling() ) + for ( QListViewItem* child = item->firstChild(); child; child = child->nextSibling() ) + for ( QListViewItem* grandChild = child->firstChild(); grandChild; grandChild = grandChild->nextSibling() ) + if ( grandChild->isSelected() && !grandChild->parent()->isSelected() ) + { + const bool sampler = static_cast( item )->isSampler(); + qb.clear(); + if ( translateTimeFilter( timeFilter() ) > 0 ) + qb.addFilter( QueryBuilder::tabSong, QueryBuilder::valCreateDate, QString().setNum( QDateTime::currentDateTime().toTime_t() - translateTimeFilter( timeFilter() ) ), QueryBuilder::modeGreater ); + + qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valURL ); + + if ( !sampler ) + { + if ( q_cat1 == IdArtist ) + qb.setOptions( QueryBuilder::optNoCompilations ); + + tmptext = static_cast( item )->getSQLText( 0 ); + unknownText = tmptext.isEmpty(); + + if( VisYearAlbum == 1 ) + { + tmptext = item->text( 0 ); + QString year = tmptext.left( tmptext.find( i18n(" - ") ) ); + yearAlbumCalc( year, tmptext ); + qb.addMatch( QueryBuilder::tabYear, year, false, true ); + if ( unknownText ) + tmptext = ""; + } + + qb.addMatch( q_cat1, tmptext, false, true ); + } + else + qb.setOptions( QueryBuilder::optOnlyCompilations ); + + tmptext = static_cast( child )->getSQLText( 0 ); + unknownText = tmptext.isEmpty(); + + if( VisYearAlbum == 2 ) + { + tmptext = child->text( 0 ); + QString year = tmptext.left( tmptext.find( i18n(" - ") ) ); + yearAlbumCalc( year, tmptext ); + qb.addMatch( QueryBuilder::tabYear, year, false, true ); + if ( unknownText ) + tmptext = ""; + } + + qb.addMatch( q_cat2, tmptext, false, true ); + + tmptext = static_cast( grandChild )->getSQLText( 0 ); + unknownText = tmptext.isEmpty(); + + if( VisYearAlbum == 3 ) + { + tmptext = grandChild->text( 0 ); + QString year = tmptext.left( tmptext.find( i18n(" - ") ) ); + yearAlbumCalc( year, tmptext ); + qb.addMatch( QueryBuilder::tabYear, year, false, true ); + if ( unknownText ) + tmptext = ""; + } + + qb.addMatch( q_cat3, tmptext, false, true ); + + qb.setGoogleFilter( q_cat1 | q_cat2 | q_cat3 | QueryBuilder::tabSong, m_filter ); + + if( VisYearAlbum == 1 ) + qb.sortBy( QueryBuilder::tabYear, QueryBuilder::valName); + + if( !sampler ) + qb.sortBy( q_cat1, QueryBuilder::valName ); + + if( VisYearAlbum == 2 ) + qb.sortBy( QueryBuilder::tabYear, QueryBuilder::valName); + + qb.sortBy( q_cat2, QueryBuilder::valName ); + + if( VisYearAlbum == 3 ) + qb.sortBy( QueryBuilder::tabYear, QueryBuilder::valName); + + qb.sortBy( q_cat3, QueryBuilder::valName ); + + qb.sortBy( QueryBuilder::tabSong, QueryBuilder::valDiscNumber ); + qb.sortBy( QueryBuilder::tabSong, QueryBuilder::valTrack ); + qb.sortBy( QueryBuilder::tabSong, QueryBuilder::valURL ); + + qb.setOptions( QueryBuilder::optRemoveDuplicates ); + values = qb.run(); + + for ( uint i = 0; i < values.count(); i++ ) + { + KURL tmp; + tmp.setPath( values[i] ); + list << tmp; + } + } + } + + //category 3 + for ( item = firstChild(); item; item = item->nextSibling() ) + for ( QListViewItem* child = item->firstChild(); child; child = child->nextSibling() ) + for ( QListViewItem* grandChild = child->firstChild(); grandChild; grandChild = grandChild->nextSibling() ) + for ( QListViewItem* grandChild2 = grandChild->firstChild(); grandChild2; grandChild2 = grandChild2->nextSibling() ) + if ( grandChild2->isSelected() && !child->parent()->isSelected() && !child->isSelected() && !grandChild->isSelected() ) + list << static_cast( grandChild2 ) ->url(); + + return list; +} + + +void +CollectionView::playlistFromURLs( const KURL::List &urls ) +{ + QString suggestion; + typedef QListViewItemIterator It; + It it( this, It::Visible | It::Selected ); + if( (*it) && !(*(++it)) ) + suggestion = (*It( this, It::Visible | It::Selected ))->text( 0 ); + else + suggestion = i18n( "Untitled" ); + const QString path = PlaylistDialog::getSaveFileName( suggestion ); + + if( path.isEmpty() ) + return; + + CollectionDB* db = CollectionDB::instance(); + + QValueList titles; + QValueList lengths; + for( KURL::List::ConstIterator it = urls.constBegin(), end = urls.constEnd(); it != end; ++it ) + { + int deviceid = MountPointManager::instance()->getIdForUrl( *it ); + KURL rpath; + MountPointManager::instance()->getRelativePath( deviceid, *it, rpath ); + const QString query = QString("SELECT title, length FROM tags WHERE url = '%1' AND deviceid = %2;") + .arg( db->escapeString( rpath.path() ) ).arg( deviceid ); + debug() << "media id: " << deviceid << " rpath: " << rpath.path() << endl; + QStringList result = db->query( query ); + titles << result[0]; + lengths << result[1].toInt(); + } + + if( PlaylistBrowser::savePlaylist( path, urls, titles, lengths ) ) + PlaylistWindow::self()->showBrowser( "PlaylistBrowser" ); +} + +QPixmap +CollectionView::iconForCategory( const int cat ) const +{ + QString icon; + switch( cat ) + { + case IdAlbum: + icon = "cdrom_unmount"; + break; + case IdVisYearAlbum: + icon = "cdrom_unmount"; + break; + case IdArtist: + icon = "personal"; + break; + case IdComposer: + icon = "personal"; + break; + + case IdGenre: + icon = "kfm"; + break; + + case IdYear: + icon = "history"; + break; + + case IdLabel: + icon = "kfm"; + break; + } + + return KGlobal::iconLoader()->loadIcon( icon, KIcon::Toolbar, KIcon::SizeSmall ); +} + +QString +CollectionView::captionForCategory( const int cat ) const +{ + switch( cat ) + { + case IdAlbum: + return i18n( "Album" ); + break; + case IdVisYearAlbum: + return i18n( "Year" ) + i18n( " - " ) + i18n( "Album" ); + break; + case IdArtist: + return i18n( "Artist" ); + break; + case IdComposer: + return i18n( "Composer" ); + break; + + case IdGenre: + return i18n( "Genre" ); + break; + + case IdYear: + return i18n( "Year" ); + break; + case IdLabel: + return i18n( "Label" ); + break; + } + + return QString(); +} + + +QString +CollectionView::captionForTag( const Tag tag) const +{ + QString caption; + switch( tag ) + { + case Artist: caption = i18n( "Artist" ); break; + case Album: caption = i18n( "Album" ); break; + case Genre: caption = i18n( "Genre" ); break; + case Title: caption = i18n( "Title" ); break; + case Length: caption = i18n( "Length" ); break; + case DiscNumber:caption = i18n( "Disc Number" ); break; + case Track: caption = i18n( "Track" ); break; + case Year: caption = i18n( "Year" ); break; + case Comment: caption = i18n( "Comment" ); break; + case Composer: caption = i18n( "Composer" ); break; + case Playcount: caption = i18n( "Playcount" ); break; + case Score: caption = i18n( "Score" ); break; + case Rating: caption = i18n( "Rating" ); break; + case Filename: caption = i18n( "Filename" ); break; + case Firstplay: caption = i18n( "First Play" ); break; + case Lastplay: caption = i18n( "Last Play" ); break; + case Modified: caption = i18n( "Modified Date" ); break; + case Bitrate: caption = i18n( "Bitrate" ); break; + case Filesize: caption = i18n( "File Size" ); break; + case BPM: caption = i18n( "BPM" ); break; + default: break; + } + return caption; +} + + +////////////////////////////// +// For iPod-style navigation +////////////////////////////// + +/* + * Overview + * -------- + * + * The iPod-style navigation is a (1, 2, or) 3-tier filtering process + * (depending on the current "Group By" setting). For concreteness + * let's say the user is grouping by Genre, Artist, Album. The first + * screen he is presented with is a list of the genres, along with an + * "All genres" option (unless there is only one genre). He selects + * one or more genres, and clicks the right arrow in the toolbar. He + * is then presented with a list of albums whose genre matches one of + * the genres he has just chosen, unless he has chosen "All genres", + * in which case all albums are shown. This is repeated until he gets + * to an actual track list. If the user clicks the left arrow, he is + * taken back to the previous screen, with his previous selection + * still intact. + * + * + * Interface + * --------- + * + * The two main actions the user can perform are "browse forward" and + * "browse backward", otherwise known as increment and decrement the + * browse depth. There is a small toolbar with these two buttons + * located to the right of the time filter combobox, which is enabled + * in iPod view mode. If the user is not viewing tracks, there is + * also a small browse forward button to the right of each entry on + * his screen. If the user is viewing tracks, the browse forward + * action adds the currently selected tracks to the playlist. + * Pressing the right or left keys is an alternate way of browsing + * forward or backward. At any point the user may drag and drop, press + * return, or double-click to add the current selection to the playlist; + * the logic for this is explained in a comment in listSelected(). If + * divider mode is on, dividers are added as expected when not in track + * mode. + * + * + * Related methods + * --------------- + * + * CollectionBrowser::ipodToolbar() + * -- (de)activate the toolbar with the browse buttons + * CollectionView::keyPressEvent() + * -- handle the Left and Right keys, as well as the Up and Down keys + * to allow for wrapping around and skipping dividers + * CollectionView::renderView() + * -- logic for applying the current filter and adding the matching + * items to the screen + * CollectionView::ipodItemClicked() + * -- slot for activating the browse forward action when the right + * arrow on a listview item is clicked + * CollectionView::setViewMode() + * -- enable/disable iPod toolbar + * CollectionView::updateColumnHeader() + * CollectionView::listSelected() + * -- apply current filters and current selection to get a track + * list, and order it correctly + * CollectionView::allForCategory() + * -- return the text for the "All" item in the current category + * CollectionView::incrementDepth() + * -- save the current selection away as a filter, and remember it + * in case the user browses back so we can reselect + * CollectionView::decrementDepth() + * -- delete the current filter and reselect the saved selection + * CollectionView::resetIpodDepth() + * -- reset the iPod view mode to the first screen + * CollectionView::buildIpodQuery() + * -- adds query and sort criteria to a QueryBuilder, based on the + * saved filters + * CollectionView::selectIpodItems() + * -- if we've just browsed forward, select the All item (or the + * unique item if there's only one). If we've just browsed back, + * reselect the saved selection. + * CollectionView::ipodIncrementIcon(), CollectionView::ipodDecrementIcon() + * -- returns a QPixmap of the small version of the right, resp. left + * arrow buttons + * CollectionView::viewportResizeEvent() + * CollectionItem::compare() + * -- in iPod mode the "All" item goes first + * + */ + + +// Returns the text for the "All" filter option for the given category +// and the number of items in that category. +QString +CollectionView::allForCategory( const int cat, const int num ) const +{ + switch( cat ) + { + // The singular forms shouldn't get used + case IdAlbum: + case IdVisYearAlbum: + return i18n( "Album", "All %n Albums", num ); + break; + case IdArtist: + return i18n( "Artist", "All %n Artists", num ); + break; + case IdComposer: + return i18n( "Composer", "All %n Composers", num ); + break; + case IdGenre: + return i18n( "Genre", "All %n Genres", num ); + break; + case IdYear: + return i18n( "Year", "All %n Years", num ); + break; + case IdLabel: + return i18n( "Label", "All %n Labels", num ); + break; + } + + return QString(); +} + +// This slot is called when the "browse right" action is activated, +// and also in listSelected(). It is responsible for saving the +// current selection in two ways: first, it saves the selection as +// a list of query match criteria in m_ipodFilters and m_ipodFilterYear, +// and second, it saves the currently selected items, the current item, +// and the screen position in m_ipodSelected, m_ipodCurrent, and +// m_ipodTopItem. +// The reason there's a separate m_ipodFilterYear which is not an +// array of lists, is because if one of the categories is IdVisYearAlbum, +// we want to save the album filter separately from the year filter; +// since there's only one category that is IdVisYearAlbum, we don't need +// an array of years. +// The main reason the cache data (m_ipodSelected, etc.) is separate +// from the filter data (m_ipodFilters, etc.), apart from the fact it's +// easier to code this way, is that the filter data must be deleted +// immediately upon browsing left, whereas the cache data must not. +// If we're in track mode, this just inserts the currently selected +// tracks into the playlist. +void +CollectionView::incrementDepth( bool rerender /*= true*/ ) +{ + if( m_viewMode != modeIpodView ) + return; + if( selectedItems().count() == 0 ) + return; + + // Track mode? + if( m_currentDepth == trackDepth() ) + { + Playlist::instance()->insertMedia( listSelected(), Playlist::Unique | Playlist::Append ); + return; + } + + m_parent->m_ipodDecrement->setEnabled( true ); + + // We're not in track mode + int catArr[3] = {m_cat1, m_cat2, m_cat3}; + int cat = catArr[m_currentDepth]; + + // Clear filters and cache data at this level + m_ipodFilters[m_currentDepth].clear(); + if( cat == IdVisYearAlbum ) + m_ipodFilterYear.clear(); + + m_ipodSelected[m_currentDepth].clear(); + m_ipodCurrent[m_currentDepth] = QString::null; + m_ipodTopItem[m_currentDepth] = QString::null; + + // Save the current item + if( currentItem() ) + m_ipodCurrent[m_currentDepth] = currentItem()->text( 0 ); + + //cache viewport's top item + QListViewItem* item = itemAt( QPoint(0, 0) ); + if ( item ) + m_ipodTopItem[m_currentDepth] = item->text( 0 ); + + // Figure out the next filter, and save the current selection + QPtrList selected = selectedItems(); + QPtrList::iterator it = selected.begin(); + + while( it != selected.end() ) + { + CollectionItem *item = dynamic_cast( *it ); + ++it; + if( item == 0 ) + continue; + + // No filter if "All" is selected + if( item->isSampler() ) + { + m_ipodFilters[m_currentDepth].clear(); + if( cat == IdVisYearAlbum ) + m_ipodFilterYear.clear(); + + // If "All" is selected then don't bother saving this + // selection, since All will then be reselected automatically + // in selectIpodItems() + m_ipodSelected[m_currentDepth].clear(); + m_ipodCurrent[m_currentDepth] = QString::null; + + break; + } + + if( cat == IdVisYearAlbum ) + { + QString tmptext = item->text( 0 ); + QString year = tmptext.left( tmptext.find( i18n(" - ") ) ); + yearAlbumCalc( year, tmptext ); + if( !item->isUnknown() ) + m_ipodFilters[m_currentDepth] << tmptext; + else + m_ipodFilters[m_currentDepth] << ""; + m_ipodFilterYear << year; // May be "" + } + + else + m_ipodFilters[m_currentDepth] << item->getSQLText( 0 ); + + // Save the selection + m_ipodSelected[m_currentDepth] << item->text( 0 ); + } + + m_currentDepth++; + + if( rerender ) + { + updateColumnHeader(); + m_ipodIncremented = 1; + renderView( true ); + } +} + + +// This slot is basically responsible for undoing whatever +// incrementDepth did. Namely, it deletes the filter at +// the previous level; selectIpodItems() will then be called +// from renderView() to reselect the remembered selection. +void +CollectionView::decrementDepth ( bool rerender /*= true*/ ) +{ + if( m_viewMode != modeIpodView ) + return; + if( m_currentDepth <= 0 ) + return; + + m_currentDepth--; + m_parent->m_ipodDecrement->setEnabled( m_currentDepth > 0 ); + m_ipodFilters[m_currentDepth].clear(); + int catArr[3] = {m_cat1, m_cat2, m_cat3}; + int cat = catArr[m_currentDepth]; + if( cat == IdVisYearAlbum ) + m_ipodFilterYear.clear(); + + // Clear the selection on higher levels + for( int i = m_currentDepth + 1; i < 3; ++i ) + { + m_ipodSelected[i].clear(); + m_ipodCurrent[i] = QString::null; + m_ipodTopItem[i] = QString::null; + } + + if( rerender ) + { + m_ipodIncremented = 2; + updateColumnHeader(); + renderView( true ); + } +} + + +// This resets the ipod view mode to the first screen. +// Call updateColumnHeader() as well whenever you run this +void +CollectionView::resetIpodDepth ( void ) +{ + m_currentDepth = 0; + m_ipodFilterYear.clear(); + m_ipodFilters[0].clear(); + m_ipodFilters[1].clear(); + m_ipodFilters[2].clear(); + m_ipodIncremented = 1; + m_parent->m_ipodDecrement->setEnabled( false ); +} + + + +// This method is the querying workhorse for the iPod browsing code. +// It is used both to populate the content browser (in renderView()) +// and to generate track lists (in listSelected()). This method only +// runs qb.addMatch() and qb.sortBy() (as well as one qb.addFilter() +// below); the caller should run qb.setGoogleFilter(), +// qb.addReturnValue(), etc. +// The sorting is as follows: if recursiveSort is true (for +// listSelected()), then it sorts first by m_cat1, then by m_cat2, +// then m_cat3, then track; if recursiveSort is false (for +// renderView()), it only sorts by the category at m_currentDepth. +// Tracks are sorted by track number first if either of the two +// following conditions hold: +// (i) recursiveSort is true and one of the categories is (Year +) Album +// (ii) recursiveSort is false and only one album is selected +// This most closely mimics the behavior of the list view, as well as +// an actual iPod. +// The compilationsOnly argument does *not* set the onlyCompilations +// option (setting options is up to the caller); all it does is disable +// sorting by artist if recursiveSort is on. This is because when doing +// a compilations-only search, all tracks should behave as if the artist +// were Various Artists for sorting purposes. +void +CollectionView::buildIpodQuery ( QueryBuilder &qb, int depth, QStringList filters[3], + QStringList filterYear, bool recursiveSort /*= false*/, bool compilationsOnly /*= false*/) +{ + int catArr[3] = {m_cat1, m_cat2, m_cat3}; + int q_cat; + bool stillFiltering = (depth < trackDepth()); + bool SortbyTrackFirst = false; + + // First apply the filters from previous screens + for( int i = 0; i < depth; ++i ) + { + q_cat = catArr[i]; + + if( q_cat == IdVisYearAlbum ) + { + q_cat = IdAlbum; + + if( filters[i].count() > 0 ) + { + // This is very annoying -- we have to do an OR of queries + // of the form (album = ? AND year = ??) + QStringList::iterator album = filters[i].begin(); + QStringList::iterator year = filterYear.begin(); + + qb.beginOR(); + + while( album != filters[i].end() ) + { + qb.beginAND(); + qb.addMatch( QueryBuilder::tabAlbum, *album, false, true ); + qb.addMatch( QueryBuilder::tabYear, *year, false, true ); + qb.endAND(); + + ++album; + ++year; + } + + qb.endOR(); + } + + if( recursiveSort ) + qb.sortBy( QueryBuilder::tabYear, QueryBuilder::valName ); + + } + + else + { + if( filters[i].count() > 0 ) + qb.addMatches( q_cat, filters[i], false, true ); + } + + // Don't sort by artist if we're getting compilations + if( recursiveSort + && !(compilationsOnly && q_cat == IdArtist) ) + qb.sortBy( q_cat, QueryBuilder::valName ); + + // Sort by track first subject to the conditions described above + if( q_cat == IdAlbum && + (filters[i].count() == 1 || recursiveSort) ) + SortbyTrackFirst = true; + } + + + // Now add the non-recursive sort-by + if( stillFiltering ) // Are we showing a category? + { + q_cat = catArr[depth]; + if( q_cat == IdVisYearAlbum ) + { + q_cat = IdAlbum; + qb.sortBy( QueryBuilder::tabYear, QueryBuilder::valName ); + } + + qb.sortBy( q_cat, QueryBuilder::valName ); + + // ensure we don't get empty genres/albums/etc due to tag changes + qb.addFilter( QueryBuilder::tabSong, QString::null ); + + } + + // ... or are we showing tracks? + else + { + if ( SortbyTrackFirst ) { + qb.sortBy( QueryBuilder::tabSong, QueryBuilder::valDiscNumber ); + qb.sortBy( QueryBuilder::tabSong, QueryBuilder::valTrack ); + } + qb.sortBy( QueryBuilder::tabSong, QueryBuilder::valTitle ); + if ( !SortbyTrackFirst ) { + qb.sortBy( QueryBuilder::tabSong, QueryBuilder::valDiscNumber ); + qb.sortBy( QueryBuilder::tabSong, QueryBuilder::valTrack ); + } + qb.sortBy( QueryBuilder::tabSong, QueryBuilder::valURL ); + } + +} + + +// This method is responsible for either selecting the "All" item +// if the iPod depth has been incremented, or selecting the previously +// remembered items if the depth has been decremented, depending on +// m_ipodIncremented, m_ipodSelected, m_ipodCurrent, and m_ipodTopItem. +// Note that if the previously selected items have disappeared (due to +// a GoogleFilter being applied, e.g.) then we select the "All" item by +// default. +// Note that if there is only one item in the list then there is no +// All option, so we select the unique item. +void +CollectionView::selectIpodItems ( void ) +{ + if( m_viewMode != modeIpodView || + m_ipodIncremented == 0 ) + { + m_ipodIncremented = 0; + return; + } + + // If we've just decremented the iPod view depth then remember + // the selection and the current item last time we incremented. + // Note that a filter or something may have happened between then + // and now, so we should allow for those items no longer being + // here (in which case we pass through to the code below) + if( m_ipodIncremented == 2 ) + { + // Pedantry -- presumably we're not at track depth! + if( m_currentDepth == trackDepth() ) + { + m_ipodIncremented = 0; + return; + } + + // If there's no selection or the selected items have + // disappeared, pass through to the code below + if( m_ipodSelected[m_currentDepth].count() == 0 ) + m_ipodIncremented = 1; + + else + { + KListView::selectAll( false ); + int selected = 0; + QStringList::iterator it = m_ipodSelected[m_currentDepth].begin(); + while( it != m_ipodSelected[m_currentDepth].end() ) + { + QListViewItem *item = findItem( *it, 0 ); + ++it; + + if( !item ) + continue; + + selected++; + // If the saved currentItem has disappeared, it's more + // intuitive if the last selected item is current. + setCurrentItem( item ); + item->setSelected( true ); + setSelectionAnchor( item ); + } + + // Pass through to below + if( selected == 0 ) + m_ipodIncremented = 1; + + else + { + // Remember the current item and scroll position + if( !m_ipodTopItem[m_currentDepth].isEmpty() && + !m_ipodTopItem[m_currentDepth].isNull() ) + { + //scroll to previous viewport top item + QListViewItem* item = findItem( m_ipodTopItem[m_currentDepth], 0 ); + if ( item ) + setContentsPos( 0, itemPos( item ) ); + } + + if( !m_ipodCurrent[m_currentDepth].isEmpty() && + !m_ipodCurrent[m_currentDepth].isNull() ) + { + QListViewItem *item = findItem( m_ipodCurrent[m_currentDepth], 0); + if( item ) + setCurrentItem( item ); + } + } + } + } + + // If we've just incremented the iPod view depth (or are displaying + // the iPod window for the first time) then automatically select the + // All option (or the only element of the list) for keyboard + // navigation + if( m_ipodIncremented == 1 ) + { + KListView::selectAll( false ); + QListViewItem *item = firstChild(); + + // There will be a divider in the first slot if there is only + // one item in the list and m_showDivider is on + while( item && dynamic_cast( item ) ) + item = item->itemBelow(); + + if( item ) + { + setCurrentItem( item ); + item->setSelected( true ); + setSelectionAnchor( item ); + setContentsPos( 0, itemPos( item ) ); + } + } + + m_ipodIncremented = 0; +} + + +// Convenience methods for returning the correct (small version of) +// the browse forward / backward buttons + +QPixmap +CollectionView::ipodIncrementIcon ( void ) +{ + return SmallIcon( Amarok::icon( "fastforward" ) ); +} + +QPixmap +CollectionView::ipodDecrementIcon ( void ) +{ + return SmallIcon( Amarok::icon( "rewind" ) ); +} + +void +CollectionView::setCompilation( const KURL::List &urls, bool compilation ) +{ + //visual feedback + QApplication::setOverrideCursor( KCursor::waitCursor() ); + + //Set it in the DB. We don't need to update the view now as we do it at the end. + CollectionDB::instance()->setCompilation( urls, compilation, false ); + + foreachType( KURL::List, urls ) { + if ( !TagLib::File::isWritable( QFile::encodeName( ( *it ).path() ) ) ) + continue; + + MetaBundle mb( *it ); + + mb.setCompilation( compilation ? MetaBundle::CompilationYes : MetaBundle::CompilationNo ); + + if( mb.save() ) { + mb.updateFilesize(); + //update the collection db, since filesize might have changed + CollectionDB::instance()->updateTags( mb.url().path(), mb, false ); + } + } + //visual feedback + QApplication::restoreOverrideCursor(); + if ( !urls.isEmpty() ) renderView(true); +} + +void +CollectionView::cacheView() +{ + //free cache + m_cacheOpenItemPaths.clear(); + + //Store the current item + m_cacheCurrentItem = makeStructuredNameList( currentItem() ); + + //cache expanded/open items + if ( m_viewMode == modeTreeView ) { + QListViewItemIterator it( this ); + while ( it.current() ) { + QListViewItem *item = it.current(); + if ( item->isOpen() ) { + //construct path to item + QStringList itemPath; + for( const QListViewItem *i = item; i; i = i->parent() ) + itemPath.prepend( i->text( 0 ) ); + + m_cacheOpenItemPaths.append ( itemPath ); + } + ++it; + } + } + + //cache viewport's top item + m_cacheViewportTopItem = makeStructuredNameList( itemAt( QPoint(0, 0) ) ); +} + + +void +CollectionView::restoreView() +{ + //expand cached items + if ( m_viewMode == modeTreeView ) { + QValueList::const_iterator it; + for ( it = m_cacheOpenItemPaths.begin(); it != m_cacheOpenItemPaths.end(); ++it ) { + QListViewItem* item = findItem( (*it)[0], 0 ); + if ( item ) + item->setOpen ( true ); + + for ( uint i = 1; i < (*it).count() && item; ++i ) { + item = item->firstChild(); + while ( item ) { + if ( item->text(0) == (*it)[ i ] ) + item->setOpen ( true ); + item = item->nextSibling(); + } + } + } + } + + //scroll to previous viewport top item + QListViewItem* item = findFromStructuredNameList( m_cacheViewportTopItem ); + if ( item ) + setContentsPos( 0, itemPos(item) ); + + //Restore a selected item (all levels of the tree stored to fully specify which item) + item = findFromStructuredNameList( m_cacheCurrentItem ); + if ( item ) + { + setCurrentItem( item ); + item->setSelected( true ); + // More intuitive if shift-click selects from current selection + setSelectionAnchor( item ); + } + + //free cache + m_cacheOpenItemPaths.clear(); + m_cacheViewportTopItem = QStringList(); + m_cacheCurrentItem = QStringList(); +} + +QStringList +CollectionView::makeStructuredNameList( QListViewItem *item ) const +{ + QStringList nameList; + for ( QListViewItem *current = item; current; current = current->parent() ) + nameList.push_front( current->text( 0 ) ); + return nameList; +} + +QListViewItem* +CollectionView::findFromStructuredNameList( const QStringList &nameList ) const +{ + QListViewItem *item( firstChild() ); + bool firstTime = true; + foreach( nameList ) + { + if ( !firstTime ) + item = item->firstChild(); + else + firstTime = false; + + while ( item && item->text( 0 ) != *it ) + item = item->nextSibling(); + + if ( !item ) + { + debug() << "Could not find expected element to select: " << nameList << endl; + break; + } + } + return nameList.isEmpty() ? 0 : item; +} + + +// Small function aimed to convert Eagles, The -> The Eagles (and back again) +// TODO Internationlise +void +CollectionView::manipulateThe( QString &str, bool reverse ) +{ + if( reverse ) + { + QString begin = str.left( 3 ); + str = str.append( ", %1" ).arg( begin ); + str = str.mid( 4 ); + return; + } + + if( !endsInThe( str ) ) + return; + + QString end = str.right( 3 ); + str = str.prepend( "%1 " ).arg( end ); + + uint newLen = str.length() - end.length() - 2; + + str.truncate( newLen ); +} + +bool +CollectionView::endsInThe( const QString & text ) +{ + return text.endsWith( ", the", false ); +} + +// avoid code duplication +void +CollectionView::yearAlbumCalc( QString &year, QString &text ) +{ + if( year == "\?" ) + year = ""; + + text = text.right( text.length() - + text.find( i18n(" - ") ) - + i18n(" - ").length() ); +} + +void +CollectionView::viewportPaintEvent( QPaintEvent *e ) +{ + KListView::viewportPaintEvent( e ); + + // Superimpose bubble help for Flat-View mode: + + if ( m_viewMode == modeFlatView && childCount() == 0 ) + { + QPainter p( viewport() ); + + QSimpleRichText t( i18n( + "
" + "

Flat-View Mode

" + "To enable the Flat-View mode, please enter search terms in the search line above." + "
" ), QApplication::font() ); + + t.setWidth( width() - 50 ); + + const uint w = t.width() + 20; + const uint h = t.height() + 20; + + p.setBrush( colorGroup().background() ); + p.drawRoundRect( 15, 15, w, h, (8*200)/w, (8*200)/h ); + t.draw( &p, 20, 20, QRect(), colorGroup() ); + } +} + + +void +CollectionView::updateTrackDepth() { + bool m3 = (m_cat3 == IdNone); + bool m2 = (m_cat2 == IdNone); + bool m1 = (m_cat1 == IdNone); + if ( m3 || m2 || m1) { + //The wanted depth, is the lowest IdNone + if (m3) + m_trackDepth = 2; + if (m2) + m_trackDepth = 1; + if (m1) + m_trackDepth = 0; + } + else // If there's no IdNone, then it's 3 + m_trackDepth = 3; +} + +void +CollectionView::viewportResizeEvent( QResizeEvent* e) +{ + if( m_viewMode != modeIpodView ) + { + header()->blockSignals( true ); + + const double width = e->size().width(); + int visibleColumns = 0; + for ( int i = 0; i < columns(); ++i ) + if ( columnWidth( i ) != 0 ) + visibleColumns ++; + int correct = e->size().width() - (e->size().width() / visibleColumns) * visibleColumns; + + if( m_viewMode == modeFlatView ) + m_flatColumnWidths.clear(); + + if ( visibleColumns != 0 ) { + for ( int c = 0; c < columns(); ++c ) { + int w = columnWidth( c ) ? static_cast( width/visibleColumns ) : 0; + if ( w > 0 ) + { + w += correct; + correct = 0; + setColumnWidth( c, w ); + } + if( m_viewMode == modeFlatView ) + m_flatColumnWidths.push_back( w ); + } + } + + header()->blockSignals( false ); + } + + // iPod-mode header adjustment code + else + { + // Don't use header()->adjustHeaderSize(), since that doesn't + // do a very good job. Instead we treat the browse-forward-button + // column as rigid, and stretch the text column to exactly fit + // the width. + + int width = visibleWidth(); + int col1width = 0; + // No column 1 for tracks + if( m_currentDepth != trackDepth() ) + col1width = columnWidth(1); + setColumnWidth( 0, width - col1width ); + } + + // Needed for correct redraw of bubble help + triggerUpdate(); +} + +bool +CollectionView::eventFilter( QObject* o, QEvent* e ) +{ + if( o == header() + && e->type() == QEvent::MouseButtonPress + && static_cast( e )->button() == Qt::RightButton + && m_viewMode == modeFlatView ) + { + KPopupMenu popup; + popup.setCheckable( true ); + popup.insertTitle( i18n( "Flat View Columns" ), /*id*/ -1, /*index*/ 1 ); + + for ( int i = 0; i < columns(); ++i ) + { + popup.insertItem( captionForTag( static_cast( i ) ), i ); + popup.setItemChecked( i, ( columnWidth(i) != 0 ) ); + } + + //title column should always be shown + popup.setItemEnabled( Title, false ); + popup.setItemVisible( Score, AmarokConfig::useScores() ); + popup.setItemVisible( Rating, AmarokConfig::useRatings() ); + + const int returnID = popup.exec( static_cast(e)->globalPos() ); + + if ( returnID != -1 ) + { + if ( columnWidth( returnID ) == 0 ) { + adjustColumn( returnID ); // show column + header()->setResizeEnabled( true, returnID ); + renderView(true); + } + else { + setColumnWidth ( returnID, 0 ); // hide column + header()->setResizeEnabled( false, returnID ); + } + //manage column widths + QResizeEvent rev ( size(), QSize() ); + viewportResizeEvent( &rev ); + } + + m_flatColumnWidths.clear(); + for ( int c = 0; c < columns(); ++c ) + m_flatColumnWidths.push_back( columnWidth( c ) ); + + return true; + } + + return KListView::eventFilter( o, e ); +} + +uint CollectionView::translateTimeFilter( uint filterMode ) +{ + uint filterSecs = 0; + switch ( filterMode ) + { + case 1: + // added today + filterSecs = 60 * 60 * 24; + break; + + case 2: + // added within one week + filterSecs = 60 * 60 * 24 * 7; + break; + + case 3: + // added within one month + filterSecs = 60 * 60 * 24 * 30; + break; + + case 4: + // added within three months + filterSecs = 60 * 60 * 24 * 91; + break; + + case 5: + // added within one year + filterSecs = 60 * 60 * 24 * 365; + break; + } + + return filterSecs; +} + +void +CollectionView::renderFlatModeView( bool /*=false*/ ) +{ + QStringList values; + QueryBuilder qb; + + if ( translateTimeFilter( timeFilter() ) > 0 ) + qb.addFilter( QueryBuilder::tabSong, QueryBuilder::valCreateDate, QString().setNum( QDateTime::currentDateTime().toTime_t() - translateTimeFilter( timeFilter() ) ), QueryBuilder::modeGreater ); + + if ( translateTimeFilter( timeFilter() ) <= 0 + && (m_filter.length() < 3 || (!m_filter.contains( " " ) && m_filter.endsWith( ":" ) ) ) ) + { + // Redraw bubble help + triggerUpdate(); + return; + } + + QValueList visibleColumns; + for ( int c = 0; c < columns(); ++c ) + if ( columnWidth( c ) != 0 ) + { + visibleColumns.append( static_cast( c ) ); + } + + //always fetch URL + qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valURL ); + //device automatically added + + int filterTables = 0; + for ( QValueList::ConstIterator it = visibleColumns.constBegin(); it != visibleColumns.constEnd(); ++it ) + { + switch ( *it ) + { + case Artist: + qb.addReturnValue( QueryBuilder::tabArtist, QueryBuilder::valName, true ); + filterTables |= QueryBuilder::tabArtist; + break; + case Composer: + qb.addReturnValue ( QueryBuilder::tabComposer, QueryBuilder::valName, true ); + filterTables |= QueryBuilder::tabComposer; + break; + case Album: + qb.addReturnValue( QueryBuilder::tabAlbum, QueryBuilder::valName, true ); + filterTables |= QueryBuilder::tabAlbum; + break; + case Genre: + qb.addReturnValue( QueryBuilder::tabGenre, QueryBuilder::valName, true ); + filterTables |= QueryBuilder::tabGenre; + break; + case Title: + qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valTitle, true ); + filterTables |= QueryBuilder::tabSong; + break; + case Length: + qb.addReturnValue ( QueryBuilder::tabSong, QueryBuilder::valLength ); + filterTables |= QueryBuilder::tabSong; + break; + case DiscNumber: + qb.addReturnValue ( QueryBuilder::tabSong, QueryBuilder::valDiscNumber ); + filterTables |= QueryBuilder::tabSong; + break; + case Track: + qb.addReturnValue ( QueryBuilder::tabSong, QueryBuilder::valTrack ); + filterTables |= QueryBuilder::tabSong; + break; + case Year: + qb.addReturnValue ( QueryBuilder::tabYear, QueryBuilder::valName ); + filterTables |= QueryBuilder::tabYear; + break; + case Comment: + qb.addReturnValue ( QueryBuilder::tabSong, QueryBuilder::valComment ); + filterTables |= QueryBuilder::tabSong; + break; + case Playcount: + qb.addReturnValue ( QueryBuilder::tabStats, QueryBuilder::valPlayCounter ); + break; + case Score: + qb.addReturnValue( QueryBuilder::tabStats, QueryBuilder::valScore ); + break; + case Rating: + qb.addReturnValue( QueryBuilder::tabStats, QueryBuilder::valRating ); + break; + case Filename: + qb.addReturnValue ( QueryBuilder::tabSong, QueryBuilder::valRelativePath ); + break; + case Firstplay: + qb.addReturnValue ( QueryBuilder::tabStats, QueryBuilder::valCreateDate ); + break; + case Lastplay: + qb.addReturnValue ( QueryBuilder::tabStats, QueryBuilder::valAccessDate ); + break; + case Modified: + qb.addReturnValue ( QueryBuilder::tabSong, QueryBuilder::valCreateDate ); + break; + case Bitrate: + qb.addReturnValue ( QueryBuilder::tabSong, QueryBuilder::valBitrate ); + break; + case Filesize: + qb.addReturnValue ( QueryBuilder::tabSong, QueryBuilder::valFilesize ); + break; + case BPM: + qb.addReturnValue ( QueryBuilder::tabSong, QueryBuilder::valBPM ); + filterTables |= QueryBuilder::tabSong; + break; + default: + qb.addReturnValue( QueryBuilder::tabDummy, QueryBuilder::valDummy ); + break; + } + } + + qb.setGoogleFilter( filterTables, m_filter ); + qb.sortBy( QueryBuilder::tabSong, QueryBuilder::valTitle ); + qb.setOptions( QueryBuilder::optRemoveDuplicates ); + + //we leftjoin the query so it can return mysql NULL cells, i.e. for score and playcount + //this is an ugly hack - should be integrated in querybuilder itself instead. + QString leftQuery = qb.query(); + leftQuery.replace( "INNER JOIN", "LEFT JOIN" ); + values = CollectionDB::instance()->query( leftQuery ); + + //construct items + QStringList::ConstIterator it = values.constBegin(); + QStringList::ConstIterator end = values.constEnd(); + while ( it != end ) + { + CollectionItem* item = new CollectionItem( this ); + item->setDragEnabled( true ); + item->setDropEnabled( false ); + QString rpath = *it; + item->setUrl( MountPointManager::instance()->getAbsolutePath( (*++it).toInt(), rpath ) ); + ++it; + + for ( QValueList::ConstIterator it_c = visibleColumns.constBegin(); it_c != visibleColumns.constEnd(); ++it_c ) + { + switch ( *it_c ) + { + case Length: + item->setText( *it_c, MetaBundle::prettyLength( (*it).toInt(), false) ); + break; + case Bitrate: + item->setText( *it_c, MetaBundle::prettyBitrate( (*it).toInt() ) ); + break; + case Firstplay: + case Lastplay: + case Modified: + { + QDateTime time = QDateTime(); + time.setTime_t( (*it).toUInt() ); + item->setText( *it_c, time.date().toString( Qt::LocalDate ) ); + break; + } + case Playcount: + case Score: + item->setText( *it_c, (*it).isNull() ? "0" : (*it) ); + break; + case Rating: + item->setText( *it_c, (*it).isNull() ? "0" : (*it) ); + break; + case Filename: + item->setText( *it_c, KURL::fromPathOrURL( (*it).right( (*it).length() -1 ) ).filename() ); + break; + case Filesize: + item->setText( *it_c, MetaBundle::prettyFilesize( (*it).toInt() ) ); + break; + default: + item->setText( *it_c, (*it) ); + break; + } + ++it; + } + } +} + +void +CollectionView::renderTreeModeView( bool /*=false*/ ) +{ + QStringList values; + QueryBuilder qb; + + if ( translateTimeFilter( timeFilter() ) > 0 ) + qb.addFilter( QueryBuilder::tabSong, QueryBuilder::valCreateDate, QString().setNum( QDateTime::currentDateTime().toTime_t() - translateTimeFilter( timeFilter() ) ), QueryBuilder::modeGreater ); + + setSorting( 0 ); + int VisYearAlbum = -1; + int VisLabel = -1; + int q_cat1=m_cat1; + int q_cat2=m_cat2; + int q_cat3=m_cat3; + if( m_cat1 == IdVisYearAlbum || + m_cat2 == IdVisYearAlbum || + m_cat3 == IdVisYearAlbum ) + { + if( m_cat1==IdVisYearAlbum ) + { + VisYearAlbum = 1; + q_cat1 = IdAlbum; + } + if( m_cat2==IdVisYearAlbum ) + { + VisYearAlbum = 2; + q_cat2 = IdAlbum; + } + if( m_cat3==IdVisYearAlbum ) + { + VisYearAlbum = 3; + q_cat3 = IdAlbum; + } + } + if ( m_cat1 == IdLabel || + m_cat2 == IdLabel || + m_cat3 == IdLabel ) + { + if ( m_cat1 == IdLabel ) + VisLabel = 1; + else if ( m_cat2 == IdLabel ) + VisLabel = 2; + else + VisLabel = 3; + } + QPixmap pixmap = iconForCategory( m_cat1 ); + + qb.addReturnValue( q_cat1, QueryBuilder::valName, true ); + + if( VisYearAlbum == 1 ) + qb.addReturnValue( QueryBuilder::tabYear, QueryBuilder::valName, true ); + + qb.setGoogleFilter( q_cat1 | q_cat2 | q_cat3 | QueryBuilder::tabSong, m_filter ); + + if( VisYearAlbum == 1 ) + qb.sortBy( QueryBuilder::tabYear, QueryBuilder::valName ); + + qb.sortBy( q_cat1, QueryBuilder::valName ); + qb.setOptions( QueryBuilder::optRemoveDuplicates ); + + if( q_cat1 == QueryBuilder::tabArtist ) + qb.setOptions( QueryBuilder::optNoCompilations ); + + // ensure we don't get empty genres/albums/etc due to tag changes + qb.addFilter( QueryBuilder::tabSong, QString::null ); + + values = qb.run(); + + //add items to the view + + uint dividerCount = 0; + if( values.count() ) + { + //keep track of headers already added + QMap containedDivider; + + for ( QStringList::Iterator it = values.fromLast(), begin = values.begin(); true; --it ) + { + bool unknown = false; + + //For Year-Album + if ( VisYearAlbum == 1 ) + { + ( *it ) = ( *it ).isEmpty() ? "?" : ( *it ) + i18n( " - " ); + QStringList::Iterator album = it; + --album; + if ( (*album).isEmpty() ) + { + unknown = true; + ( *it ) += i18n( "Unknown" ); + } + else + ( *it ) += *album; + } + + if ( (*it).stripWhiteSpace().isEmpty() ) + { + if ( VisLabel == 1 ) + (*it) = i18n( "No Label" ); + else + (*it) = i18n( "Unknown" ); + unknown = true; + } + else if (m_showDivider) + { + //Dividers for "The Who" should be created as "W", not "T", because + //that's how we sort it + QString actualStr = *it; + if ( m_cat1 == IdArtist && actualStr.startsWith( "the ", false ) ) + manipulateThe( actualStr, true ); + + QString headerStr = DividerItem::createGroup( actualStr, m_cat1); + + if ( !containedDivider[headerStr] && !headerStr.isEmpty() ) + { + containedDivider[headerStr] = true; + (void)new DividerItem(this, headerStr, m_cat1/*, m_sortYearsInverted*/); + dividerCount++; + } + } + + CollectionItem* item = new CollectionItem( this, m_cat1, unknown ); + item->setExpandable( true ); + item->setDragEnabled( true ); + item->setDropEnabled( false ); + item->setText( 0, *it ); + item->setPixmap( 0, pixmap ); + + //The structure of the return is different if Year - Album is format + if ( VisYearAlbum == 1 ) + --it; + + if ( it == begin ) + break; + } + } + + //check if we need to add a Various Artists node + if ( q_cat1 == QueryBuilder::tabArtist ) + { + qb.clear(); + if ( translateTimeFilter( timeFilter() ) > 0 ) + qb.addFilter( QueryBuilder::tabSong, QueryBuilder::valCreateDate, + QString().setNum( QDateTime::currentDateTime().toTime_t() - + translateTimeFilter( timeFilter() ) ), QueryBuilder::modeGreater ); + + qb.addReturnValue( q_cat1, QueryBuilder::valName, true ); + qb.setGoogleFilter( q_cat1 | q_cat2 | q_cat3 | QueryBuilder::tabSong, m_filter ); + qb.setOptions( QueryBuilder::optOnlyCompilations | QueryBuilder::optRemoveDuplicates ); + qb.setLimit( 0, 1 ); + values = qb.run(); + + if ( values.count() ) + { + // KListViewItem* x = new DividerItem(this, i18n( "Various" ), m_cat1); + // x->setExpandable(false); + // x->setDropEnabled( false ); + // x->setSelectable(false); + CollectionItem* item = new CollectionItem( this, m_cat1, false, true ); + item->setExpandable( true ); + item->setDragEnabled( true ); + item->setDropEnabled( false ); + item->setText( 0, i18n( "Various Artists" ) ); + item->setPixmap( 0, pixmap ); + } + } + + //Algorithm to expand some items after a search in a pretty/useful way: + //Aim to expand all items with one child, but with a maximum limit to how many items + //should be shown in the listview ( maxRows) after which we give up. If an item has + //more than one child and we haven't reached the limit, we may end up expanding it + //later. + QValueList couldOpen; + int totalCount = childCount() - dividerCount; + const int maxRows = 20; //This seems like a fair limit for a 1024x768 screen + if ( totalCount < maxRows ) + { + //Generate initial list of top list items to look at + for ( QListViewItem* top = firstChild(); top; top = top->nextSibling() ) + { + if ( !dynamic_cast( top ) ) + continue; + couldOpen.push_back( top ); + } + //Expand suggested items and expand or enqueue their children until we run out of + //rows or have expanded everything + for ( QValueList::iterator it = couldOpen.begin(); it != couldOpen.end() && totalCount < maxRows; ++it ) + { + if ( !( *it )->isOpen() ) + ( *it )->setOpen( true ); + totalCount += ( *it )->childCount(); + if ( ( *it )->firstChild()->isExpandable() ) //Check we have not reached the bottom + { + for ( QListViewItem *j = ( *it )->firstChild(); j && totalCount < maxRows; j = j->nextSibling() ) + { + j->setOpen( true ); + if ( j->childCount() > 1 ) //More than one child - maybe later + { + j->setOpen( false ); + couldOpen.push_back( j ); + } + else + { + //Prioritize expanding its children - add it immediately next + QValueList::iterator next = it; + ++next; + couldOpen.insert( next, j ); + ++totalCount; + } + } + } + } + } + + //If the tree has only one branch, at least at the top, make the lowest child + //which has no siblings (other branches) become selected. + //Rationale: If you search for something and then clear the search bar, this way it + //will stay in view, assuming you only had one real result. + if ( childCount() - dividerCount == 1 ) + { + QListViewItem *item = firstChild(); + if ( dynamic_cast( item ) ) //Skip a divider, if present + item = item->nextSibling(); + for ( ; item ; item = item->firstChild() ) + if ( !item->isOpen() || item->childCount() > 1 ) + break; + if ( item ) + { + setCurrentItem( item ); + item->setSelected( true ); + setSelectionAnchor( item ); + } + } + + removeDuplicatedHeaders(); +} + +void +CollectionView::removeDuplicatedHeaders() +{ + /* Following code depends on the order! */ + sort(); + + QValueList toDelete; + DividerItem *current=0, *last=0; + bool empty; + QListViewItem *item; + /* If we have two consecutive headers, one of them is useless, and should be removed */ + for( item = firstChild(),empty=false; item; item=item->nextSibling() ) + { + if ( (current = dynamic_cast( item )) ) + { + if ( empty ) + { + if ( !current->text(0).at(0).isLetterOrNumber() + || ( last->text(0).at(0).isLetterOrNumber() + && current->text(0).at(0).unicode() > last->text(0).at(0).unicode() ) ) + toDelete += current; + else + { + toDelete += last; + last=current; + } + } + else + last=current; + empty=true; + } + else + empty=false; + } + + for ( QValueList::iterator it = toDelete.begin(); it != toDelete.end(); ++it ) + delete *it; +} + + +// MODE IPODVIEW This is the heart of the iPod view mode code. It +// applies the current filters (as defined by previous "move +// forward" actions). If we're not viewing tracks (stillFiltering +// == true), then display the results in the standard order, with +// dividers if applicable, with an "All" (i.e., no filter) item if +// there is more than one result, and with "Unknown" items if +// there are any. Note that the "All" item is a CollectionItem +// with the Sampler bit set, since it behaves similar to the +// Various Artists node. +// If we are viewing tracks (stillFiltering == +// false), then just apply all of the filters and show the +// selected tracks. The track ordering is similar to in list view +// mode; see the comments in buildIpodQuery() for details. +void +CollectionView::renderIpodModeView( bool /*=false*/ ) +{ + QStringList values; + QueryBuilder qb; + + if ( translateTimeFilter( timeFilter() ) > 0 ) + qb.addFilter( QueryBuilder::tabSong, QueryBuilder::valCreateDate, QString().setNum( QDateTime::currentDateTime().toTime_t() - translateTimeFilter( timeFilter() ) ), QueryBuilder::modeGreater ); + + int catArr[3] = {m_cat1, m_cat2, m_cat3}; + // stillFiltering is true when we're not viewing tracks + bool stillFiltering = (m_currentDepth < trackDepth()); + // q_cat is the "query category" -- it's undefined if + // stillFiltering is true; otherwise it's the category + // at the current iPod viewing depth (m_currentDepth), unless + // that category is IdVisYearAlbum, in which case it's IdAlbum. + int q_cat = (stillFiltering ? catArr[m_currentDepth] : catArr[0]); + // m_cat is the current actual category -- it agrees with q_cat + // if and only if m_cat != IdVisYearAlbum + int m_cat = q_cat; + // This is set to true if the current category is IdVisYearAlbum + // It is only used when stillFiltering == true. + bool VisYearAlbum = false; + //This is set to true if the current category is IdLabel + bool VisLabel = false; + + if( q_cat == IdVisYearAlbum && stillFiltering ) + { + VisYearAlbum = true; + q_cat = IdAlbum; + } + if ( q_cat == IdLabel && stillFiltering ) + VisLabel = true; + + // If we're viewing tracks, we don't want them to be sorted + // alphabetically, since we take great pains in + // buildIpodQuery() to have them returned to us from the + // database in the correct order. + setSorting( stillFiltering ? 0 : -1 ); + + // Do the grunt work of building the query (this method is also + // called by listSelected() ) + buildIpodQuery( qb, m_currentDepth, m_ipodFilters, m_ipodFilterYear ); + if(stillFiltering) + qb.setOptions( QueryBuilder::optRemoveDuplicates ); + + int tables = 0; + for( int i = 0; i < trackDepth(); ++i ) + tables |= (catArr[i] == IdVisYearAlbum + ? IdAlbum + : catArr[i]); + qb.setGoogleFilter( tables | QueryBuilder::tabSong, m_filter ); + + // Return values + if( stillFiltering ) + { + qb.addReturnValue( q_cat, QueryBuilder::valName, true ); + if( VisYearAlbum ) + qb.addReturnValue( QueryBuilder::tabYear, QueryBuilder::valName, true ); + } + else + { + qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valURL ); + qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valTitle ); + } + + values = qb.run(); + int total = values.count() / qb.countReturnValues(); + + // This can happen -- with the filter it might be empty + if( total == 0 ) + return; + + // We want to load the pixmap only once if we're still filtering + // Otherwise just load a dummy pixmap + QPixmap pixmap = iconForCategory( q_cat ); + QPixmap incPixmap = ipodIncrementIcon(); + int width = incPixmap.width() + 10; // Set the column width below + // Keep track of the dividers we've created. + QMap containedDivider; + + QStringList::Iterator itStep = values.end(); + QStringList::Iterator begin = values.begin(); + itStep -= qb.countReturnValues(); + // It's an awkward business stepping through a list backward + // when the elements are in tuples, going forward. + // This loop breaks at the bottom -- don't put a continue in here! + while( 1 ) + { + CollectionItem* item; + QStringList::Iterator it = itStep; + + // Add non-track items + if( stillFiltering ) + { + bool unknown = false; + + if( (*it).isEmpty() ) + { + unknown = true; + if ( VisLabel ) + *it = i18n( "No Label" ); + else + *it = i18n( "Unknown" ); + } + + item = new CollectionItem( this, m_cat, unknown ); + + if( VisYearAlbum ) + { + QString album = *it; + QString year = *(++it); + if( year.isEmpty() ) + year = "?"; + item->setText( 0, year + i18n(" - ") + album ); + } + else + item->setText( 0, *it ); + + item->setPixmap( 0, pixmap ); + item->setPixmap( 1, incPixmap ); + item->setText( 1, "" ); + // Calculate width + width = item->width( fontMetrics(), this, 1 ); + + // Only do dividers if we're not showing tracks since + // dividers don't really make sense in a track-only view + if( !unknown && m_showDivider ) + { + //Dividers for "The Who" should be created as "W", not "T", + //because that's how we sort it + QString actualStr = item->text( 0 ); + if ( m_cat == IdArtist && + actualStr.startsWith( "the ", false ) ) + manipulateThe( actualStr, true ); + + QString headerStr = DividerItem::createGroup( actualStr, m_cat ); + + if ( !containedDivider[headerStr] && !headerStr.isEmpty() ) + { + containedDivider[headerStr] = true; + (void)new DividerItem( this, headerStr, m_cat ); + } + } + } + + else // Add tracks + { + item = new CollectionItem( this ); + item->setUrl( *it ); + item->setText( 0, *(++it) ); + } + + item->setDragEnabled( true ); + item->setDropEnabled( false ); + + if( itStep == begin ) + break; + + itStep -= qb.countReturnValues(); + } + + // Add the "All" filter if necessary + if( stillFiltering ) + { + if( total > 1 ) + { + // The "All" filter has much the same behavior as the + // "Various Artists" item, so we use the isSampler bit + CollectionItem* item = new CollectionItem( this, m_cat, false, true ); + item->setDragEnabled( true ); + item->setDropEnabled( false ); + item->setPixmap( 0, pixmap ); + item->setText( 0, allForCategory( q_cat, total ) ); + item->setPixmap( 1, incPixmap ); + item->setText( 1, "" ); + + sort(); + } + setColumnWidth( 1, width ); + QResizeEvent rev( size(), QSize() ); + viewportResizeEvent( &rev ); + } + + removeDuplicatedHeaders(); +} + +////////////////////////////////////////////////////////////////////////////////////////// +// CLASS CollectionItem +////////////////////////////////////////////////////////////////////////////////////////// +void +CollectionItem::paintCell ( QPainter * painter, const QColorGroup & cg, + int column, int width, int align ) +{ + if( static_cast(column) == CollectionView::Rating ) + { + QPixmap buf( width, height() ); + QPainter p( &buf, true ); + + const QColorGroup _cg = listView()->palette().active(); + + QColor bg = isSelected() ? _cg.highlight() + : isAlternate() ? listView()->alternateBackground() + : listView()->viewport()->backgroundColor(); +#if KDE_IS_VERSION( 3, 3, 91 ) + if( listView()->shadeSortColumn() && !isSelected() && listView()->columnSorted() == column ) + { + /* from klistview.cpp + Copyright (C) 2000 Reginald Stadlbauer + Copyright (C) 2000,2003 Charles Samuels + Copyright (C) 2000 Peter Putzer */ + if ( bg == Qt::black ) + bg = QColor(55, 55, 55); // dark gray + else + { + int h,s,v; + bg.hsv(&h, &s, &v); + if ( v > 175 ) + bg = bg.dark(104); + else + bg = bg.light(120); + } + } +#endif + + buf.fill( bg ); + + int rating = text(column).toInt(); + int i = 1, x = 1; + const int y = height() / 2 - StarManager::instance()->getGreyStar()->height() / 2; + bool half = rating%2; + for(; i <= rating/2; ++i ) + { + bitBlt( p.device(), x, y, StarManager::instance()->getStar( half ? rating/2 + 1 : rating/2 ) ); + x += StarManager::instance()->getGreyStar()->width() + listView()->itemMargin(); + } + if( half ) + { + bitBlt( p.device(), x, y, StarManager::instance()->getHalfStar( rating/2 + 1 ) ); + x += StarManager::instance()->getGreyStar()->width() + listView()->itemMargin(); + } + + p.end(); + painter->drawPixmap( 0, 0, buf ); + } + else + { + KListViewItem::paintCell( painter, cg, column, width, align ); + } +} + +int +CollectionItem::compare( QListViewItem* i, int col, bool ascending ) const +{ + QString a = text( col ); + QString b = i->text( col ); + int ia, ib; + + //Special cases go first to take priority + + // Sampler is the first one in iPod view + CollectionView* view = static_cast( listView() ); + if( view->viewMode() == CollectionView::modeIpodView ) + { + if ( m_isSampler ) + return -1; + if ( dynamic_cast( i ) && static_cast( i )->m_isSampler ) + return 1; + } + else if( view->viewMode() == CollectionView::modeFlatView ) + { + ia = ib = 0; + // correctly treat numeric values + switch( col ) + { + case CollectionView::Track: + case CollectionView::DiscNumber: + case CollectionView::Bitrate: + case CollectionView::Score: + case CollectionView::Rating: + case CollectionView::Playcount: + case CollectionView::BPM: + ia = a.toInt(); + ib = b.toInt(); + break; + case CollectionView::Length: + ia = a.section( ':', 0, 0 ).toInt() * 60 + a.section( ':', 1, 1 ).toInt(); + ib = b.section( ':', 0, 0 ).toInt() * 60 + b.section( ':', 1, 1 ).toInt(); + break; + } + + if( ia || ib ) + { + if( ia < ib ) + return 1; + if( ia > ib ) + return -1; + return 0; + } + } + + // Unknown is always the first one unless we're doing iPod view, but if the two items to be compared are Unknown, + // then compare the normal way + if ( !( m_isUnknown && dynamic_cast( i ) && static_cast( i )->m_isUnknown ) ) + { + if ( m_isUnknown ) + return -1; + if ( dynamic_cast( i ) && static_cast( i )->m_isUnknown ) + return 1; + } + + // Various Artists is always after unknown + if ( m_isSampler ) + return -1; + if ( dynamic_cast( i ) && static_cast( i )->m_isSampler ) + return 1; + + //Group heading should go above the items in that group + if (dynamic_cast(i) && DividerItem::shareTheSameGroup(a, b, m_cat)) { + return ascending == false ? -1 : 1; + } + + switch( m_cat ) { + case IdVisYearAlbum: + a = a.left( a.find( i18n(" - ") ) ); + b = b.left( b.find( i18n(" - ") ) ); + // "?" are the last ones + if ( a == "?" ) + return 1; + if ( b == "?" ) + return -1; + //fall through + case IdYear: + ia = a.toInt(); + ib = b.toInt(); + if (ia==ib) + return QString::localeAwareCompare( text( col ).lower(), i->text( col ).lower() ); + if (ia(i) ) + a += a; + + // No special case, then fall on default + return QString::localeAwareCompare( a.lower(), b.lower() ); +} + +void +CollectionItem::sortChildItems ( int column, bool ascending ) { + CollectionView* view = static_cast( listView() ); + // Sort only if it's not the tracks + if ( depth() + 1 < view->trackDepth()) + QListViewItem::sortChildItems( column, ascending ); +} + +// +// DividerItem + +DividerItem::DividerItem( QListView* parent, QString txt, int cat/*, bool sortYearsInverted*/) +: KListViewItem( parent), m_blockText(false), m_text(txt), m_cat(cat)/*, m_sortYearsInverted(sortYearsInverted)*/ +{ + setExpandable(false); + setDropEnabled(false); + setSelectable(false); +} + +void +DividerItem::paintCell ( QPainter * p, const QColorGroup & cg, + int column, int width, int align ) +{ + p->save(); + + // be sure, that KListViewItem::paintCell() does not draw its text + setBlockText( true ); + KListViewItem::paintCell(p, cg, column, width, align); + setBlockText( false ); + + //use bold font for the divider + QFont f = p->font(); + f.setBold( true ); + p->setFont( f ); + + // draw the text offset a little bit + if( column == 0 ) // For iPod viewing mode + { + QFontMetrics fm( p->fontMetrics() ); + int x = !QApplication::reverseLayout() ? 25 : width - 25; + int y = fm.ascent() + (height() - fm.height())/2; + p->drawText( x, y, m_text ); + } + + //draw the baseline + p->setPen( QPen(Qt::gray, 2) ); + p->drawLine(0, height() -2 , width, height() -2 ); + + p->restore(); +} + +void +DividerItem::paintFocus ( QPainter* /*p*/, const QColorGroup& /*cg*/, const QRect& /*r*/ ) +{ + //not implemented, we don't to show focus +} + +//to draw the text on my own I have to be able to block the text, otherwise I could +// not use QListViewItem::paintCell() to draw the basic cell +QString +DividerItem::text(int column) const +{ + if (column == 0) { + return m_blockText ? "" : m_text; + } + return KListViewItem::text(column); +} + +int +DividerItem::compare( QListViewItem* i, int col, bool ascending ) const +{ + if (!i) { + return QString::localeAwareCompare( text(col).lower(), QString("") ); + } + if (dynamic_cast(i)) { + return -1 * i->compare(const_cast(this), col, ascending); + } + + if (m_cat == IdYear || + m_cat == IdVisYearAlbum) + { + bool ok_a, ok_b; + int ia = text(col).toInt(&ok_a); + int ib = i->text(col).toInt(&ok_b); + + if (ok_a && ok_b) + { + if (ia == ib) return 0; + else if (ia < ib) return 1; + else return -1; + } + } + return QString::localeAwareCompare( text(col).lower(), i->text(col).lower() ); +} + +QString +DividerItem::createGroup(const QString& src, int cat) +{ + QString ret; + switch (cat) { + case IdVisYearAlbum: { + ret = src.left( src.find(" - ") ); + break; + } + case IdYear: { + ret = src; + if (ret.length() == 2 || ret.length() == 4) { + ret = ret.left(ret.length() - 1) + '0'; + } + break; + } + default: + ret = src.stripWhiteSpace(); + // (Joe Rabinoff) deleted. The bug this fixes is as follows. + // If the category is Album and the album name is "Heroes" (by + // David Bowie -- the quotes are part of the title), it gets + // put under the H group, but then gets sorted under '"'. + // What I've done is the wrong fix -- albums should be sorted + // by their first alphanumeric character. + /* + while ( !ret.isEmpty() && !ret.at(0).isLetterOrNumber() ) { + ret = ret.right( ret.length()-1 ); + } + */ + if ( !ret.isEmpty() && ret.at(0).isLetterOrNumber() ) + ret = ret.left( 1 ).upper(); + else + return ""; + /*else + ret = i18n( "Others" );*/ + /* By returning an empty string, no header is created. */ + + if (QChar(ret.at(0)).isDigit()) { + ret = "0-9"; + } + } + + return ret; +} + +bool +DividerItem::shareTheSameGroup(const QString& itemStr, const QString& divStr, int cat) +{ + bool inGroup = false; + QString tmp = itemStr.stripWhiteSpace(); + + switch (cat) { + case IdVisYearAlbum: { + QString sa = itemStr.left( itemStr.find( i18n(" - ") ) ); + QString sb = divStr.left( divStr.find( i18n(" - ") ) ); + if (sa == sb) { + inGroup = true; + } + break; + } + case IdYear: { + int ia = itemStr.toInt(); + int ib = divStr.toInt(); + // they share one group if: + // o they are < 100 (short years '98') + // o they are > 1000 (long years '1998') + // o their 'century' is the same + // o are the same + if (((ia < 100 || ia > 1000) && ia/10 == ib/10) || + (ia == ib)) { + inGroup = true; + } + break; + } + case IdArtist: + //"The Who" should count as being in "W" and not "T" + if ( tmp.startsWith( "the ", false ) ) + CollectionView::manipulateThe( tmp, true ); + //Fall through + default: + if ( !tmp.isEmpty() ) { + if (divStr == "0-9" && QChar(tmp.at(0)).isDigit()) { + inGroup = true; + } + else if (tmp.startsWith(divStr, 0)) { + inGroup = true; + } + } + } + + return inGroup; +} + +#include "collectionbrowser.moc" diff --git a/amarok/src/collectionbrowser.h b/amarok/src/collectionbrowser.h new file mode 100644 index 00000000..407c4f7c --- /dev/null +++ b/amarok/src/collectionbrowser.h @@ -0,0 +1,397 @@ +// (c) 2004 Mark Kretschmann +// (c) 2004 Christian Muehlhaeuser +// (c) 2005 Gábor Lehel +// (c) 2005 Christan Baumgart +// See COPYING file for licensing information. + +#ifndef AMAROK_COLLECTIONBROWSER_H +#define AMAROK_COLLECTIONBROWSER_H + +#include +#include //stack allocated +#include //baseclass + +#include //baseclass +#include //stack allocated +#include //stack allocated +#include //baseclass +#include + +#include "multitabbar.h" //baseclass +#include "collectiondb.h" +#include "amarok_export.h" + +class ClickLineEdit; +class CollectionDB; + +class QCString; +class QDragObject; +class QPixmap; +class QPoint; +class QStringList; + +class KAction; +class KComboBox; +class KPopupMenu; +class KRadioAction; +class KTabBar; +class KToolBar; +class KToggleAction; + +class CollectionView; +class CollectionItem; +class DividerItem; +class OrganizeCollectionDialog; + +namespace CollectionBrowserIds +{ + enum CatMenuId { IdAlbum = QueryBuilder::tabAlbum, + IdArtist = QueryBuilder::tabArtist, + IdComposer = QueryBuilder::tabComposer, + IdGenre = QueryBuilder::tabGenre, + IdYear = QueryBuilder::tabYear , + IdScan = 32, IdNone = 64, + IdArtistAlbum = 128, IdGenreArtist = 256, IdGenreArtistAlbum = 512, IdVisYearAlbum = 1024, IdArtistVisYearAlbum = 2048, + IdLabel = QueryBuilder::tabLabels //=8192 + }; +} + +class CollectionBrowser: public QVBox +{ + Q_OBJECT + friend class CollectionView; + + public: + CollectionBrowser( const char* name ); + virtual bool eventFilter( QObject*, QEvent* ); + KToolBar* getToolBar() const { return m_toolbar; } + static CollectionBrowser *instance() { return s_instance; } + + public slots: + void setupDirs(); + void toggleDivider(); + + private slots: + void slotClearFilter(); + void slotSetFilterTimeout(); + void slotSetFilter(); + void slotSetFilter( const QString &filter ); + void slotEditFilter(); + + private: + void layoutToolbar(); + void ipodToolbar( bool activate ); + void appendSearchResults(); + + //attributes: + KTabBar* m_tabs; //tree-view, flat-view tabs + class KToolBar *m_toolbar; + KAction *m_configureAction; + // For iPod-style browsing + KAction *m_ipodIncrement, *m_ipodDecrement; + class KToolBar *m_ipodToolbar; + class QHBox *m_ipodHbox; + + KToggleAction *m_showDividerAction; + KRadioAction *m_treeViewAction; + KRadioAction *m_flatViewAction; + KRadioAction *m_ipodViewAction; + class KActionMenu *m_tagfilterMenuButton; + + KPopupMenu* m_categoryMenu; + KPopupMenu* m_cat1Menu; + KPopupMenu* m_cat2Menu; + KPopupMenu* m_cat3Menu; + KLineEdit* m_searchEdit; + KComboBox* m_timeFilter; + CollectionView* m_view; + QTimer* m_timer; + + bool m_returnPressed; + + static CollectionBrowser *s_instance; + + // for CatMenuId + friend class CollectionItem; + friend class DividerItem; +}; + +class DividerItem : public KListViewItem +{ +public: + static QString createGroup(const QString& src, int cat); + static bool shareTheSameGroup(const QString& a, const QString& b, int cat); + +public: + DividerItem( QListView* parent, QString txt, int cat); + + virtual void paintCell ( QPainter * p, const QColorGroup & cg, int column, int width, int align ); + virtual void paintFocus ( QPainter * p, const QColorGroup & cg, const QRect & r ); + + virtual QString text(int column) const; + + void setBlockText(bool block) { m_blockText = block; } + +private: + virtual int compare( QListViewItem*, int, bool ) const; + +private: + bool m_blockText; + QString m_text; + int m_cat; +}; + + + +class CollectionItem : public KListViewItem { + public: + CollectionItem( QListView* parent, int cat = 0, bool unknown = false, bool sampler=false ) + : KListViewItem( parent ) + , m_cat( cat ) + , m_isUnknown( unknown ) + , m_isSampler( sampler ) {}; + CollectionItem( QListViewItem* parent, int cat = 0, bool unknown = false, bool sampler=false ) + : KListViewItem( parent ) + , m_cat( cat ) + , m_isUnknown( unknown ) + , m_isSampler( sampler ) {}; + void setUrl( const QString& url ) { m_url.setPath( url ); } + const KURL& url() const { return m_url; } + + virtual void sortChildItems ( int column, bool ascending ); //reimplemented + + inline QString getSQLText( int column ) + { + return ( !column && m_isUnknown ) ? "" : text( column ); + } + + inline bool isUnknown() {return m_isUnknown;} + inline bool isSampler() {return m_isSampler;} + + virtual void setPixmap(int column, const QPixmap & pix); + + /// convenience functions + CollectionView *listView() const { return reinterpret_cast( KListViewItem::listView() ); } + + private: + friend class CollectionView; + virtual void paintCell ( QPainter * painter, const QColorGroup & cg, int column, int width, int align ); + + //for sorting + virtual int compare( QListViewItem*, int, bool ) const; //reimplemented + + //attributes: + KURL m_url; + int m_cat; + bool m_isUnknown; + bool m_isSampler; +}; + + +class CollectionView : public KListView, public DropProxyTarget +{ + Q_OBJECT + friend class CollectionBrowser; + + public: + enum ViewMode { modeTreeView, modeFlatView, modeIpodView }; + + friend class CollectionItem; // for access to m_cat2 + friend class ContextBrowser; // for setupDirs() + + CollectionView( CollectionBrowser* parent ); + ~CollectionView(); + + LIBAMAROK_EXPORT static CollectionView* instance() { return m_instance; } + + void setFilter( const QString &filter ) { m_filter = filter; } + void setTimeFilter( const uint timeFilter ) { m_timeFilter = timeFilter; } + QString filter() { return m_filter; } + uint timeFilter() { return m_timeFilter; } + CollectionItem* currentItem() { return static_cast( KListView::currentItem() ); } + + int trackDepth() { return m_trackDepth; } + int viewMode() const { return m_viewMode; } + + // Transform "The Who" -> "Who, The" or the other way + static void manipulateThe( QString &str, bool reverse ); + + void setShowDivider(bool show); + + bool isOrganizingFiles() const; + + //Useful helper function to avoid duplicating code + static inline void yearAlbumCalc( QString &year, QString &text ); + + protected: + // Reimplemented for iPod-style navigation, etc. + virtual void keyPressEvent( QKeyEvent *e ); + + + public slots: + /** Rebuilds and displays the treeview by querying the database. */ + void renderView(bool force = false); + + void databaseChanged() { m_dirty = true; renderView(); }; + + void setTreeMode() { setViewMode( modeTreeView ); }; + void setFlatMode() { setViewMode( modeFlatView ); }; + void setIpodMode() { setViewMode( modeIpodView ); }; + + void presetMenu( int id ); + void cat1Menu( int id, bool rerender = true ); + void cat2Menu( int id, bool rerender = true ); + void cat3Menu( int id, bool rerender = true ); + void organizeFiles( const KURL::List &list, const QString &caption, bool addToCollection=false ) LIBAMAROK_EXPORT; + + private slots: + void setupDirs(); + + void slotEnsureSelectedItemVisible(); + + void renderFlatModeView(bool force = false); + void renderTreeModeView(bool force = false); + void renderIpodModeView(bool force = false); + + void scanStarted(); + void scanDone( bool changed = true ); + + void slotExpand( QListViewItem* ); + void slotCollapse( QListViewItem* ); + void enableCat3Menu( bool ); + void invokeItem( QListViewItem*, const QPoint &, int column ); + void invokeItem( QListViewItem* ); + + // ipod-style navigation slots + void ipodItemClicked( QListViewItem*, const QPoint&, int ); + void incrementDepth ( bool rerender = true ); + void decrementDepth ( bool rerender = true ); + + void rmbPressed( QListViewItem*, const QPoint&, int ); + void selectAll() {QListView::selectAll(true); } + /** Tries to download the cover image from Amazon.com */ + void fetchCover(); + /** Shows dialog with information on selected track */ + void showTrackInfo(); + + /** + * Cancel Organizing files + */ + void cancelOrganizingFiles(); + + void ratingChanged( const QString&, int ); + + private: + enum Tag { Title = 0, Artist, Composer, Album, Genre, Length, DiscNumber, Track, Year, + Comment, Playcount, Score, Rating, Filename, Firstplay, Lastplay, Modified, + Bitrate, Filesize, BPM, NUM_TAGS }; + + void setViewMode( int mode, bool rerender = true ); + void removeDuplicatedHeaders(); + + void startDrag(); + QString getTrueItemText( int, QListViewItem* ) const; + QStringList listSelectedSiblingsOf( int, QListViewItem* ); + KURL::List listSelected(); + + void playlistFromURLs( const KURL::List &urls ); + QPixmap iconForCategory( const int cat ) const; + QString captionForCategory( const int cat ) const; + inline QString captionForTag( const Tag ) const; + + // For iPod-style navigation + QString allForCategory( const int cat, const int num ) const; + void resetIpodDepth ( void ); + void buildIpodQuery ( QueryBuilder &qb, int depth, QStringList filters[3], QStringList filterYear, bool recursiveSort = false, bool compilationsOnly = false ); + void selectIpodItems ( void ); + QPixmap ipodIncrementIcon ( void ); + QPixmap ipodDecrementIcon ( void ); + + void setCompilation( const KURL::List &urls, bool compilation ); + + /** Rebuild selections, viewport and expanded items after reloads */ + void cacheView(); + void restoreView(); + + //Used to store the name of an item (and its parents), so it can be recalled later + //even if the pointer to the item has been invalidated. + QStringList makeStructuredNameList( QListViewItem* ) const; + QListViewItem* findFromStructuredNameList( const QStringList& ) const; + + // avoid duplicated code + static inline bool endsInThe( const QString & text ); + inline void updateTrackDepth(); + + uint translateTimeFilter( uint filterMode ); + + /**Call when a category has changed **/ + void updateColumnHeader(); + // Reimplemented from KListView + void viewportPaintEvent( QPaintEvent* ); + void viewportResizeEvent( QResizeEvent* ); + bool eventFilter( QObject*, QEvent* ); + void contentsDragEnterEvent( QDragEnterEvent* ); + void contentsDragMoveEvent( QDragMoveEvent* ); + void contentsDropEvent( QDropEvent *e ); + // Reimplemented from DropProxyTarget + void dropProxyEvent( QDropEvent *e ); + + void safeClear(); + + //attributes: + LIBAMAROK_EXPORT static CollectionView* m_instance; + + CollectionBrowser* m_parent; + + QString m_filter; + uint m_timeFilter; + int m_cat1; + int m_cat2; + int m_cat3; + int m_trackDepth; + int m_viewMode; + + // The iPod-style viewing attributes + int m_currentDepth; // Current viewing depth + QStringList m_ipodFilters[3]; // Selections at each stage + QStringList m_ipodFilterYear; // See the comment for incrementDepth() + // For auto-selection + int m_ipodIncremented; // 0 = nothing, 1 = just incremented, 2 = just decremented + QStringList m_ipodSelected[3]; // Saved selections at lower levels + QString m_ipodCurrent[3]; // Saved current selections + QString m_ipodTopItem[3]; // Saved viewport positions + + bool m_dirty; // we use this to avoid re-rendering the view when unnecessary (eg, browser is not visible) + + QValueList m_cacheOpenItemPaths; + QStringList m_cacheViewportTopItem; + QStringList m_cacheCurrentItem; + KURL::List m_organizeURLs; + bool m_organizeCopyMode; + + bool m_organizingFileCancelled; + + bool m_showDivider; + QValueList m_flatColumnWidths; +}; + +// why is signal detailsClicked() missing from KDialogBase? +class OrganizeCollectionDialogBase : public KDialogBase +{ + Q_OBJECT + public: + OrganizeCollectionDialogBase( QWidget *parent=0, const char *name=0, bool modal=true, + const QString &caption=QString::null, + int buttonMask=Ok|Apply|Cancel ) + : KDialogBase( parent, name, modal, caption, buttonMask ) + { + } + + signals: + void detailsClicked(); + public slots: + void slotDetails() { KDialogBase::slotDetails(); emit detailsClicked(); adjustSize(); } +}; + + +#endif /* AMAROK_COLLECTIONBROWSER_H */ diff --git a/amarok/src/collectiondb.cpp b/amarok/src/collectiondb.cpp new file mode 100644 index 00000000..0fb4ca7f --- /dev/null +++ b/amarok/src/collectiondb.cpp @@ -0,0 +1,8074 @@ +// (c) 2004 Mark Kretschmann +// (c) 2004 Christian Muehlhaeuser +// (c) 2004 Sami Nieminen +// (c) 2005 Ian Monroe +// (c) 2005 Jeff Mitchell +// (c) 2005 Isaiah Damron +// (c) 2005-2006 Alexandre Pereira de Oliveira +// (c) 2006 Jonas Hurrelmann +// (c) 2006 Shane King +// (c) 2006 Peter C. Ndikuwera +// (c) 2006 Stanislav Nikolov +// See COPYING file for licensing information. + +#define DEBUG_PREFIX "CollectionDB" + +#include "app.h" +#include "amarok.h" +#include "amarokconfig.h" +#include "config.h" +#include "debug.h" +#include "collectionbrowser.h" //updateTags() +#include "collectiondb.h" +#include "coverfetcher.h" +#include "enginecontroller.h" +#include "expression.h" +#include "mediabrowser.h" +#include "metabundle.h" //updateTags() +#include "mountpointmanager.h" //buildQuery() +#include "organizecollectiondialog.h" +#include "playlist.h" +#include "playlistloader.h" +#include "playlistbrowser.h" +#include "podcastbundle.h" //addPodcast +#include "qstringx.h" +#include "scancontroller.h" +#include "scriptmanager.h" +#include "scrobbler.h" +#include "statusbar.h" +#include "threadmanager.h" + +#include +#include +#include +#include +#include +#include +#include //setHTMLLyrics() +#include +#include //createDragPixmap() +#include +#include //debugging, can be removed later + +#include //setHTMLLyrics() +#include +#include +#include //checkDatabase() +#include +#include //setupCoverFetcher() +#include //setupCoverFetcher() +#include +#include +#include +#include +#include +#include +#include + +#include //DbConnection::sqlite_power() +#include //query() +#include //exit() +#include //usleep() + +#include + +#include "sqlite/sqlite3.h" + +#ifdef USE_MYSQL + #include + #include +#endif + +#ifdef USE_POSTGRESQL + #include +#endif + +#undef HAVE_INOTIFY // NOTE Disabled for now, due to stability issues + +#ifdef HAVE_INOTIFY + #include + #include "inotify/inotify-syscalls.h" +#endif + +using Amarok::QStringx; + +#define DEBUG 0 + +////////////////////////////////////////////////////////////////////////////////////////// +// CLASS INotify +////////////////////////////////////////////////////////////////////////////////////////// + +INotify* INotify::s_instance = 0; + +INotify::INotify( CollectionDB *parent, int fd ) + : DependentJob( parent, "INotify" ) + , m_parent( parent ) + , m_fd( fd ) +{ + s_instance = this; +} + + +INotify::~INotify() +{} + + +bool +INotify::watchDir( const QString directory ) +{ +#ifdef HAVE_INOTIFY + int wd = inotify_add_watch( m_fd, directory.local8Bit(), IN_CLOSE_WRITE | IN_DELETE | IN_MOVE | + IN_MODIFY | IN_ATTRIB ); + if ( wd < 0 ) + debug() << "Could not add INotify watch for: " << directory << endl; + + return ( wd >= 0 ); +#else + Q_UNUSED(directory); +#endif + + return false; +} + + +bool +INotify::doJob() +{ +#ifdef HAVE_INOTIFY + DEBUG_BLOCK + + IdList list = MountPointManager::instance()->getMountedDeviceIds(); + QString deviceIds; + foreachType( IdList, list ) + { + if ( !deviceIds.isEmpty() ) deviceIds += ','; + deviceIds += QString::number(*it); + } + const QStringList values = m_parent->query( QString( "SELECT dir, deviceid FROM directories WHERE deviceid IN (%1);" ) + .arg( deviceIds ) ); + foreach( values ) + { + QString rpath = *it; + int deviceid = (*(++it)).toInt(); + QString abspath = MountPointManager::instance()->getAbsolutePath( deviceid, rpath ); + watchDir( abspath ); + } + + /* size of the event structure, not counting name */ + const int EVENT_SIZE = ( sizeof( struct inotify_event ) ); + /* reasonable guess as to size of 1024 events */ + const int BUF_LEN = 1024 * ( EVENT_SIZE + 16 ); + + while ( 1 ) + { + char buf[BUF_LEN]; + int len, i = 0; + len = read( m_fd, buf, BUF_LEN ); + if ( len < 0 ) + { + debug() << "Read from INotify failed" << endl; + return false; + } + else + { + if ( !len ) + { + /* BUF_LEN too small? */ + } + else + { + while ( i < len ) + { + struct inotify_event *event; + event = (struct inotify_event *) &buf[i]; + + i += EVENT_SIZE + event->len; + } + + QTimer::singleShot( 0, m_parent, SLOT( scanMonitor() ) ); + } + } + } +#endif + + // this shouldn't happen + return false; +} + + +////////////////////////////////////////////////////////////////////////////////////////// +// CLASS CollectionDB +////////////////////////////////////////////////////////////////////////////////////////// + +QMutex* CollectionDB::connectionMutex = new QMutex(); +QMutex* CollectionDB::itemCoverMapMutex = new QMutex(); +//we don't have to worry about this map leaking memory since ThreadManager limits the total +//number of QThreads ever created +QMap *CollectionDB::threadConnections = new QMap(); +QMap *CollectionDB::itemCoverMap = new QMap(); + +CollectionDB* CollectionDB::instance() +{ + static CollectionDB db; + return &db; +} + + +CollectionDB::CollectionDB() + : EngineObserver( EngineController::instance() ) + , m_autoScoring( true ) + , m_noCover( locate( "data", "amarok/images/nocover.png" ) ) + , m_shadowImage( locate( "data", "amarok/images/shadow_albumcover.png" ) ) + , m_scanInProgress( false ) + , m_rescanRequired( false ) + , m_aftEnabledPersistentTables() + , m_moveFileJobCancelled( false ) +{ + DEBUG_BLOCK + +#ifdef USE_MYSQL + if ( AmarokConfig::databaseEngine().toInt() == DbConnection::mysql ) + m_dbConnType = DbConnection::mysql; + else +#endif +#ifdef USE_POSTGRESQL + if ( AmarokConfig::databaseEngine().toInt() == DbConnection::postgresql ) + m_dbConnType = DbConnection::postgresql; + else +#endif + m_dbConnType = DbConnection::sqlite; + + //perform all necessary operations to allow MountPointManager to access the devices table here + //there is a recursive dependency between CollectionDB and MountPointManager and this is the workaround + //checkDatabase has to be able to access MountPointManager + + // + initialize(); + // + + // Remove cached "nocover" images, so that a new version actually gets shown + // The asterisk is for also deleting the shadow-caches. + const QStringList entryList = cacheCoverDir().entryList( "*nocover.png*", QDir::Files ); + foreach( entryList ) + cacheCoverDir().remove( *it ); + + + connect( this, SIGNAL(fileMoved(const QString&, const QString&, const QString&)), + this, SLOT(aftMigratePermanentTablesUrl(const QString&, const QString&, const QString&)) ); + connect( this, SIGNAL(uniqueIdChanged(const QString&, const QString&, const QString&)), + this, SLOT(aftMigratePermanentTablesUniqueId(const QString&, const QString&, const QString&)) ); + + connect( qApp, SIGNAL( aboutToQuit() ), this, SLOT( disableAutoScoring() ) ); + + connect( this, SIGNAL( coverRemoved( const QString&, const QString& ) ), + SIGNAL( coverChanged( const QString&, const QString& ) ) ); + connect( Scrobbler::instance(), SIGNAL( similarArtistsFetched( const QString&, const QStringList& ) ), + this, SLOT( similarArtistsFetched( const QString&, const QStringList& ) ) ); + + // If we're supposed to monitor dirs for changes, make sure we run it once + // on startup, since inotify can't inform us about old events +// QTimer::singleShot( 0, this, SLOT( scanMonitor() ) ) + initDirOperations(); + m_aftEnabledPersistentTables << "lyrics" << "statistics" << "tags_labels"; +} + + +CollectionDB::~CollectionDB() +{ + DEBUG_BLOCK + +#ifdef HAVE_INOTIFY + if ( INotify::instance()->fd() >= 0 ) + close( INotify::instance()->fd() ); +#endif + + destroy(); +} + + +inline QString +CollectionDB::exactCondition( const QString &right ) +{ + if ( DbConnection::mysql == instance()->getDbConnectionType() ) + return QString( "= BINARY '" + instance()->escapeString( right ) + '\'' ); + else + return QString( "= '" + instance()->escapeString( right ) + '\'' ); +} + + +QString +CollectionDB::likeCondition( const QString &right, bool anyBegin, bool anyEnd ) +{ + QString escaped = right; + escaped.replace( '/', "//" ).replace( '%', "/%" ).replace( '_', "/_" ); + escaped = instance()->escapeString( escaped ); + + QString ret; + if ( DbConnection::postgresql == instance()->getDbConnectionType() ) + ret = " ILIKE "; //case-insensitive according to locale + else + ret = " LIKE "; + + ret += '\''; + if ( anyBegin ) + ret += '%'; + ret += escaped; + if ( anyEnd ) + ret += '%'; + ret += '\''; + + //Use / as the escape character + ret += " ESCAPE '/' "; + + return ret; +} + +////////////////////////////////////////////////////////////////////////////////////////// +// PUBLIC +////////////////////////////////////////////////////////////////////////////////////////// + +void +CollectionDB::initDirOperations() +{ + //this code was originally part of the ctor. It has to call MountPointManager to + //generate absolute paths from deviceids and relative paths. MountPointManager's ctor + //absolutely has to access the database, which resulted in a recursive ctor call. To + //solve this problem, the directory access code was moved into its own method, which can + //only be called when the CollectionDB object already exists. + + //FIXME max: make sure we check additional directories if we connect a new device +#ifdef HAVE_INOTIFY + // Try to initialize inotify, if not available use the old timer approach. + int inotify_fd = inotify_init(); + if ( inotify_fd < 0 ) +#endif + { +// debug() << "INotify not available, using QTimer!" << endl; + startTimer( MONITOR_INTERVAL * 1000 ); + } +#ifdef HAVE_INOTIFY + else + { + debug() << "INotify enabled!" << endl; + ThreadManager::instance()->onlyOneJob( new INotify( this, inotify_fd ) ); + } +#endif + +} + + +/** + * Executes a SQL query on the already opened database + * @param statement SQL program to execute. Only one SQL statement is allowed. + * @return The queried data, or QStringList() on error. + */ +QStringList +CollectionDB::query( const QString& statement, bool suppressDebug ) +{ + m_mutex.lock(); + clock_t start; + if ( DEBUG ) + { + debug() << "Query-start: " << statement << endl; + start = clock(); + } + if ( statement.stripWhiteSpace().isEmpty() ) + { + m_mutex.unlock(); + return QStringList(); + } + + DbConnection *dbConn; + dbConn = getMyConnection(); + + QStringList values = dbConn->query( statement, suppressDebug ); + if ( DEBUG ) + { + clock_t finish = clock(); + const double duration = (double) (finish - start) / CLOCKS_PER_SEC; + debug() << "SQL-query (" << duration << "s): " << statement << endl; + } + m_mutex.unlock(); + return values; +} + + +/** + * Executes a SQL insert on the already opened database + * @param statement SQL statement to execute. Only one SQL statement is allowed. + * @return The rowid of the inserted item. + */ +int +CollectionDB::insert( const QString& statement, const QString& table ) +{ + m_mutex.lock(); + clock_t start; + if ( DEBUG ) + { + debug() << "insert-start: " << statement << endl; + start = clock(); + } + + DbConnection *dbConn; + dbConn = getMyConnection(); + + int id = dbConn->insert( statement, table ); + + if ( DEBUG ) + { + clock_t finish = clock(); + const double duration = (double) (finish - start) / CLOCKS_PER_SEC; + debug() << "SQL-insert (" << duration << "s): " << statement << endl; + } + m_mutex.unlock(); + return id; +} + +QString +CollectionDB::deviceidSelection( const bool showAll ) +{ + if ( !showAll ) + { + IdList list = MountPointManager::instance()->getMountedDeviceIds(); + QString deviceIds = ""; + foreachType( IdList, list ) + { + if ( it != list.begin() ) deviceIds += ','; + deviceIds += QString::number(*it); + } + return " AND tags.deviceid IN (" + deviceIds + ')'; + } + else return ""; +} + +QStringList +CollectionDB::URLsFromQuery( const QStringList &result ) const +{ + QStringList values; + foreach( result ) + { + const int id = (*it).toInt(); + values << MountPointManager::instance()->getAbsolutePath( id, *(++it) ); + } + return values; +} + +KURL::List +CollectionDB::URLsFromSqlDrag( const QStringList &values ) const +{ + KURL::List urls; + for( QStringList::const_iterator it = values.begin(); + it != values.end(); + it++ ) + { + const QString &rel = *it; + it++; + int id = (*it).toInt(); + urls += KURL::fromPathOrURL( MountPointManager::instance()->getAbsolutePath( id, rel ) ); + for( int i = 0; + i < QueryBuilder::dragFieldCount-1 && it != values.end(); + i++ ) + it++; + } + + return urls; +} + +bool +CollectionDB::isEmpty( ) +{ + QStringList values; + + values = query( "SELECT COUNT( url ) FROM tags LIMIT 1 OFFSET 0;" ); + + return values.isEmpty() ? true : values.first() == "0"; +} + + +bool +CollectionDB::isValid( ) +{ + QStringList values1; + QStringList values2; + QStringList values3; + QStringList values4; + QStringList values5; + + values1 = query( "SELECT COUNT( url ) FROM tags LIMIT 1 OFFSET 0;" ); + values2 = query( "SELECT COUNT( url ) FROM statistics LIMIT 1 OFFSET 0;" ); + values3 = query( "SELECT COUNT( url ) FROM podcastchannels LIMIT 1 OFFSET 0;" ); + values4 = query( "SELECT COUNT( url ) FROM podcastepisodes LIMIT 1 OFFSET 0;" ); + values5 = query( "SELECT COUNT( id ) FROM devices LIMIT 1 OFFSET 0;" ); + + //It's valid as long as we've got _some_ tables that have something in. + return !( values1.isEmpty() && values2.isEmpty() && values3.isEmpty() && values4.isEmpty() && values5.isEmpty() ); +} + + +QString +CollectionDB::adminValue( QString noption ) { + QStringList values; + values = query ( + QString( "SELECT value FROM admin WHERE noption = '%1';").arg(noption) + ); + return values.isEmpty() ? "" : values.first(); +} + + +void +CollectionDB::setAdminValue( QString noption, QString value ) { + + QStringList values = query( QString( "SELECT value FROM admin WHERE noption = '%1';").arg( noption )); + if(values.count() > 0) + { + query( QString( "UPDATE admin SET value = '%1' WHERE noption = '%2';" ).arg( value, noption ) ); + } + else + { + insert( QString( "INSERT INTO admin (value, noption) values ( '%1', '%2' );" ).arg( value, noption ), + NULL ); + } +} + + + +void +CollectionDB::createTables( const bool temporary ) +{ + DEBUG_BLOCK + + //create tag table + query( QString( "CREATE %1 TABLE tags%2 (" + "url " + exactTextColumnType() + "," + "dir " + exactTextColumnType() + "," + "createdate INTEGER," + "modifydate INTEGER," + "album INTEGER," + "artist INTEGER," + "composer INTEGER," + "genre INTEGER," + "title " + textColumnType() + "," + "year INTEGER," + "comment " + longTextColumnType() + "," + "track NUMERIC(4)," + "discnumber INTEGER," + "bitrate INTEGER," + "length INTEGER," + "samplerate INTEGER," + "filesize INTEGER," + "filetype INTEGER," + "sampler BOOL," + "bpm FLOAT," + "deviceid INTEGER);" ) + .arg( temporary ? "TEMPORARY" : "" ) + .arg( temporary ? "_temp" : "" ) ); + + QString albumAutoIncrement = ""; + QString artistAutoIncrement = ""; + QString composerAutoIncrement = ""; + QString genreAutoIncrement = ""; + QString yearAutoIncrement = ""; + if ( getDbConnectionType() == DbConnection::postgresql ) + { + if(!temporary) + { + query( QString( "CREATE SEQUENCE album_seq;" ) ); + query( QString( "CREATE SEQUENCE artist_seq;" ) ); + query( QString( "CREATE SEQUENCE composer_seq;" ) ); + query( QString( "CREATE SEQUENCE genre_seq;" ) ); + query( QString( "CREATE SEQUENCE year_seq;" ) ); + } + + albumAutoIncrement = QString("DEFAULT nextval('album_seq')"); + artistAutoIncrement = QString("DEFAULT nextval('artist_seq')"); + composerAutoIncrement = QString("DEFAULT nextval('composer_seq')"); + genreAutoIncrement = QString("DEFAULT nextval('genre_seq')"); + yearAutoIncrement = QString("DEFAULT nextval('year_seq')"); + } + else if ( getDbConnectionType() == DbConnection::mysql ) + { + albumAutoIncrement = "AUTO_INCREMENT"; + artistAutoIncrement = "AUTO_INCREMENT"; + composerAutoIncrement = "AUTO_INCREMENT"; + genreAutoIncrement = "AUTO_INCREMENT"; + yearAutoIncrement = "AUTO_INCREMENT"; + } + + //create album table + query( QString( "CREATE %1 TABLE album%2 (" + "id INTEGER PRIMARY KEY %3," + "name " + textColumnType() + ");" ) + .arg( temporary ? "TEMPORARY" : "" ) + .arg( temporary ? "_temp" : "" ) + .arg( albumAutoIncrement ) ); + + //create artist table + query( QString( "CREATE %1 TABLE artist%2 (" + "id INTEGER PRIMARY KEY %3," + "name " + textColumnType() + ");" ) + .arg( temporary ? "TEMPORARY" : "" ) + .arg( temporary ? "_temp" : "" ) + .arg( artistAutoIncrement ) ); + + //create composer table + query( QString( "CREATE %1 TABLE composer%2 (" + "id INTEGER PRIMARY KEY %3," + "name " + textColumnType() + ");" ) + .arg( temporary ? "TEMPORARY" : "" ) + .arg( temporary ? "_temp" : "" ) + .arg( composerAutoIncrement ) ); + + //create genre table + query( QString( "CREATE %1 TABLE genre%2 (" + "id INTEGER PRIMARY KEY %3," + "name " + textColumnType() +");" ) + .arg( temporary ? "TEMPORARY" : "" ) + .arg( temporary ? "_temp" : "" ) + .arg( genreAutoIncrement ) ); + + //create year table + query( QString( "CREATE %1 TABLE year%2 (" + "id INTEGER PRIMARY KEY %3," + "name " + textColumnType() + ");" ) + .arg( temporary ? "TEMPORARY" : "" ) + .arg( temporary ? "_temp" : "" ) + .arg( yearAutoIncrement ) ); + + //create images table + query( QString( "CREATE %1 TABLE images%2 (" + "path " + exactTextColumnType() + "," + "deviceid INTEGER," + "artist " + textColumnType() + "," + "album " + textColumnType() + ");" ) + .arg( temporary ? "TEMPORARY" : "" ) + .arg( temporary ? "_temp" : "" ) ); + + //create embed table + query( QString( "CREATE %1 TABLE embed%2 (" + "url " + exactTextColumnType() + "," + "deviceid INTEGER," + "hash " + exactTextColumnType() + "," + "description " + textColumnType() + ");" ) + .arg( temporary ? "TEMPORARY" : "" ) + .arg( temporary ? "_temp" : "" ) ); + + // create directory statistics table + query( QString( "CREATE %1 TABLE directories%2 (" + "dir " + exactTextColumnType() + "," + "deviceid INTEGER," + "changedate INTEGER);" ) + .arg( temporary ? "TEMPORARY" : "" ) + .arg( temporary ? "_temp" : "" ) ); + + //create uniqueid table + query( QString( "CREATE %1 TABLE uniqueid%2 (" + "url " + exactTextColumnType() + "," + "deviceid INTEGER," + "uniqueid " + exactTextColumnType(32) + " UNIQUE," + "dir " + exactTextColumnType() + ");" ) + .arg( temporary ? "TEMPORARY" : "" ) + .arg( temporary ? "_temp" : "" ) ); + + //create indexes + query( QString( "CREATE INDEX album_idx%1 ON album%2( name );" ) + .arg( temporary ? "_temp" : "" ).arg( temporary ? "_temp" : "" ) ); + query( QString( "CREATE INDEX artist_idx%1 ON artist%2( name );" ) + .arg( temporary ? "_temp" : "" ).arg( temporary ? "_temp" : "" ) ); + query( QString( "CREATE INDEX composer_idx%1 ON composer%2( name );" ) + .arg( temporary ? "_temp" : "" ).arg( temporary ? "_temp" : "" ) ); + query( QString( "CREATE INDEX genre_idx%1 ON genre%2( name );" ) + .arg( temporary ? "_temp" : "" ).arg( temporary ? "_temp" : "" ) ); + query( QString( "CREATE INDEX year_idx%1 ON year%2( name );" ) + .arg( temporary ? "_temp" : "" ).arg( temporary ? "_temp" : "" ) ); + + if ( !temporary ) + { + //create admin table -- holds the db version, put here other stuff if necessary + query( QString( "CREATE TABLE admin (" + "noption " + textColumnType() + ", " + "value " + textColumnType() + ");" ) ); + + // create related artists cache + query( QString( "CREATE TABLE related_artists (" + "artist " + textColumnType() + "," + "suggestion " + textColumnType() + "," + "changedate INTEGER );" ) ); + + createIndices(); + } + else + { + query( "CREATE UNIQUE INDEX url_tagtemp ON tags_temp( url, deviceid );" ); + query( "CREATE UNIQUE INDEX embed_urltemp ON embed_temp( url, deviceid );" ); + query( "CREATE UNIQUE INDEX dir_temp_dir ON directories_temp( dir, deviceid );" ); + query( "CREATE INDEX album_tagtemp ON tags_temp( album );" ); + query( "CREATE INDEX artist_tagtemp ON tags_temp( artist );" ); + query( "CREATE INDEX sampler_tagtemp ON tags_temp( sampler );" ); + query( "CREATE INDEX uniqueidtemp_uniqueid ON uniqueid_temp( uniqueid );"); + query( "CREATE INDEX uniqueidtemp_url ON uniqueid_temp( url, deviceid );"); + } +} + +void +CollectionDB::createIndices() +{ + //This creates the indices for tables created in createTables. It should not refer to + //tables which are not created in that function. + debug() << "Creating indices, ignore errors about already existing indices" << endl; + + query( "CREATE UNIQUE INDEX url_tag ON tags( url, deviceid );" ); + query( "CREATE INDEX album_tag ON tags( album );" ); + query( "CREATE INDEX artist_tag ON tags( artist );" ); + query( "CREATE INDEX composer_tag ON tags( composer );" ); + query( "CREATE INDEX genre_tag ON tags( genre );" ); + query( "CREATE INDEX year_tag ON tags( year );" ); + query( "CREATE INDEX sampler_tag ON tags( sampler );" ); + + query( "CREATE INDEX images_album ON images( album );" ); + query( "CREATE INDEX images_artist ON images( artist );" ); + + query( "CREATE INDEX images_url ON images( path, deviceid );" ); + + query( "CREATE UNIQUE INDEX embed_url ON embed( url, deviceid );" ); + query( "CREATE INDEX embed_hash ON embed( hash );" ); + + query( "CREATE UNIQUE INDEX directories_dir ON directories( dir, deviceid );" ); + query( "CREATE INDEX uniqueid_uniqueid ON uniqueid( uniqueid );"); + query( "CREATE INDEX uniqueid_url ON uniqueid( url, deviceid );"); + + query( "CREATE INDEX album_idx ON album( name );" ); + query( "CREATE INDEX artist_idx ON artist( name );" ); + query( "CREATE INDEX composer_idx ON composer( name );" ); + query( "CREATE INDEX genre_idx ON genre( name );" ); + query( "CREATE INDEX year_idx ON year( name );" ); + + query( "CREATE INDEX tags_artist_index ON tags( artist );" ); + query( "CREATE INDEX tags_album_index ON tags( album );" ); + query( "CREATE INDEX tags_deviceid_index ON tags( deviceid ); "); + query( "CREATE INDEX tags_url_index ON tags( url ); "); + + query( "CREATE INDEX embed_deviceid_index ON embed( deviceid ); "); + query( "CREATE INDEX embed_url_index ON embed( url ); "); + + query( "CREATE INDEX related_artists_artist ON related_artists( artist );" ); + + debug() << "Finished creating indices, stop ignoring errors" << endl; +} + +void +CollectionDB::createPermanentIndices() +{ + //this method creates all indices which are not referred to in createTables + //this method is called on each startup of amarok + //until we figure out a way to handle this better it produces SQL errors if the indices + //already exist, but these can be ignored + debug() << "Creating permanent indices, ignore errors about already existing indices" << endl; + + query( "CREATE UNIQUE INDEX lyrics_url ON lyrics( url, deviceid );" ); + query( "CREATE INDEX lyrics_uniqueid ON lyrics( uniqueid );" ); + query( "CREATE INDEX playlist_playlists ON playlists( playlist );" ); + query( "CREATE INDEX url_playlists ON playlists( url );" ); + query( "CREATE UNIQUE INDEX labels_name ON labels( name, type );" ); + query( "CREATE INDEX tags_labels_uniqueid ON tags_labels( uniqueid );" ); //m:n relationship, DO NOT MAKE UNIQUE! + query( "CREATE INDEX tags_labels_url ON tags_labels( url, deviceid );" ); //m:n relationship, DO NOT MAKE UNIQUE! + query( "CREATE INDEX tags_labels_labelid ON tags_labels( labelid );" ); //m:n relationship, DO NOT MAKE UNIQUE! + + query( "CREATE UNIQUE INDEX url_stats ON statistics( deviceid, url );" ); + query( "CREATE INDEX percentage_stats ON statistics( percentage );" ); + query( "CREATE INDEX rating_stats ON statistics( rating );" ); + query( "CREATE INDEX playcounter_stats ON statistics( playcounter );" ); + query( "CREATE INDEX uniqueid_stats ON statistics( uniqueid );" ); + + query( "CREATE INDEX url_podchannel ON podcastchannels( url );" ); + query( "CREATE INDEX url_podepisode ON podcastepisodes( url );" ); + query( "CREATE INDEX localurl_podepisode ON podcastepisodes( localurl );" ); + query( "CREATE INDEX url_podfolder ON podcastfolders( id );" ); + + debug() << "Finished creating permanent indices, stop ignoring errors" << endl; +} + + +void +CollectionDB::dropTables( const bool temporary ) +{ + query( QString( "DROP TABLE tags%1;" ).arg( temporary ? "_temp" : "" ) ); + query( QString( "DROP TABLE album%1;" ).arg( temporary ? "_temp" : "" ) ); + query( QString( "DROP TABLE artist%1;" ).arg( temporary ? "_temp" : "" ) ); + query( QString( "DROP TABLE composer%1;" ).arg( temporary ? "_temp" : "" ) ); + query( QString( "DROP TABLE genre%1;" ).arg( temporary ? "_temp" : "" ) ); + query( QString( "DROP TABLE year%1;" ).arg( temporary ? "_temp" : "" ) ); + query( QString( "DROP TABLE images%1;" ).arg( temporary ? "_temp" : "" ) ); + query( QString( "DROP TABLE embed%1;" ).arg( temporary ? "_temp" : "" ) ); + query( QString( "DROP TABLE directories%1;" ).arg( temporary ? "_temp" : "" ) ); + query( QString( "DROP TABLE uniqueid%1;" ).arg( temporary ? "_temp" : "" ) ); + if ( !temporary ) + { + query( QString( "DROP TABLE related_artists;" ) ); + debug() << "Dropping media table" << endl; + } + + if ( getDbConnectionType() == DbConnection::postgresql ) + { + if (temporary == false) { + query( QString( "DROP SEQUENCE album_seq;" ) ); + query( QString( "DROP SEQUENCE artist_seq;" ) ); + query( QString( "DROP SEQUENCE composer_seq;" ) ); + query( QString( "DROP SEQUENCE genre_seq;" ) ); + query( QString( "DROP SEQUENCE year_seq;" ) ); + } + } +} + + +void +CollectionDB::clearTables( const bool temporary ) +{ + QString clearCommand = "DELETE FROM"; + if ( getDbConnectionType() == DbConnection::mysql || getDbConnectionType() == DbConnection::postgresql) + { + // TRUNCATE TABLE is faster than DELETE FROM TABLE, so use it when supported. + clearCommand = "TRUNCATE TABLE"; + } + + query( QString( "%1 tags%2;" ).arg( clearCommand ).arg( temporary ? "_temp" : "" ) ); + query( QString( "%1 album%2;" ).arg( clearCommand ).arg( temporary ? "_temp" : "" ) ); + query( QString( "%1 artist%2;" ).arg( clearCommand ).arg( temporary ? "_temp" : "" ) ); + query( QString( "%1 composer%2;" ).arg( clearCommand ).arg( temporary ? "_temp" : "" ) ); + query( QString( "%1 genre%2;" ).arg( clearCommand ).arg( temporary ? "_temp" : "" ) ); + query( QString( "%1 year%2;" ).arg( clearCommand ).arg( temporary ? "_temp" : "" ) ); + query( QString( "%1 images%2;" ).arg( clearCommand ).arg( temporary ? "_temp" : "" ) ); + query( QString( "%1 embed%2;" ).arg( clearCommand ).arg( temporary ? "_temp" : "" ) ); + query( QString( "%1 directories%2;" ).arg( clearCommand ).arg( temporary ? "_temp" : "" ) ); + query( QString( "%1 uniqueid%2;" ).arg( clearCommand ).arg( temporary ? "_temp" : "" ) ); + if ( !temporary ) + { + query( QString( "%1 related_artists;" ).arg( clearCommand ) ); + //debug() << "Clearing media table" << endl; + //query( QString( "%1 media;" ).arg( clearCommand ) ); + } +} + + +void +CollectionDB::copyTempTables( ) +{ + DEBUG_BLOCK + + insert( "INSERT INTO tags SELECT * FROM tags_temp;", NULL ); + + //mysql 5 supports subqueries with IN, mysql 4 doesn't. this way will work for all SQL servers + QStringList albumIdList = query( "SELECT album.id FROM album;" ); + //in an empty database, albumIdList is empty. This would result in a SQL query like NOT IN ( ) without + //the -1 below which is invalid SQL. The auto generated values start at 1 so this is fine + QString albumIds = "-1"; + foreach( albumIdList ) + { + albumIds += ','; + albumIds += *it; + } + insert( QString ( "INSERT INTO album SELECT * FROM album_temp WHERE album_temp.id NOT IN ( %1 );" ).arg( albumIds ), NULL ); + + QStringList artistIdList = query( "SELECT artist.id FROM artist;" ); + QString artistIds = "-1"; + foreach( artistIdList ) + { + artistIds += ','; + artistIds += *it; + } + insert( QString ( "INSERT INTO artist SELECT * FROM artist_temp WHERE artist_temp.id NOT IN ( %1 );" ).arg( artistIds ), NULL ); + + QStringList composerIdList = query( "SELECT composer.id FROM composer;" ); + QString composerIds = "-1"; + foreach( composerIdList ) + { + composerIds += ','; + composerIds += *it; + } + insert( QString ( "INSERT INTO composer SELECT * FROM composer_temp WHERE composer_temp.id NOT IN ( %1 );" ).arg( composerIds ), NULL ); + + QStringList genreIdList = query( "SELECT genre.id FROM genre;" ); + QString genreIds = "-1"; + foreach( genreIdList ) + { + genreIds += ','; + genreIds += *it; + } + insert( QString ( "INSERT INTO genre SELECT * FROM genre_temp WHERE genre_temp.id NOT IN ( %1 );" ).arg( genreIds ), NULL ); + + QStringList yearIdList = query( "SELECT year.id FROM year;" ); + QString yearIds = "-1"; + foreach( yearIdList ) + { + yearIds += ','; + yearIds += *it; + } + insert( QString ( "INSERT INTO year SELECT * FROM year_temp WHERE year_temp.id NOT IN ( %1 );" ).arg( yearIds ), NULL ); + + insert( "INSERT INTO images SELECT * FROM images_temp;", NULL ); + insert( "INSERT INTO embed SELECT * FROM embed_temp;", NULL ); + insert( "INSERT INTO directories SELECT * FROM directories_temp;", NULL ); + insert( "INSERT INTO uniqueid SELECT * FROM uniqueid_temp;", NULL ); +} + +void +CollectionDB::prepareTempTables() +{ + DEBUG_BLOCK + insert( "INSERT INTO album_temp SELECT * from album;", 0 ); + insert( "INSERT INTO artist_temp SELECT * from artist;", 0 ); + insert( "INSERT INTO composer_temp SELECT * from composer;", 0 ); + insert( "INSERT INTO genre_temp SELECT * from genre;", 0 ); + insert( "INSERT INTO year_temp SELECT * from year;", 0 ); +} + +void +CollectionDB::createDevicesTable() +{ + debug() << "Creating DEVICES table" << endl; + QString deviceAutoIncrement = ""; + if ( getDbConnectionType() == DbConnection::postgresql ) + { + query( QString( "CREATE SEQUENCE devices_seq;" ) ); + deviceAutoIncrement = QString("DEFAULT nextval('devices_seq')"); + } + else if ( getDbConnectionType() == DbConnection::mysql ) + { + deviceAutoIncrement = "AUTO_INCREMENT"; + } + query( QString( "CREATE TABLE devices (" + "id INTEGER PRIMARY KEY %1," + "type " + textColumnType() + "," + "label " + textColumnType() + "," + "lastmountpoint " + textColumnType() + "," + "uuid " + textColumnType() + "," + "servername " + textColumnType() + "," + "sharename " + textColumnType() + ");" ) + .arg( deviceAutoIncrement ) ); + query( "CREATE INDEX devices_type ON devices( type );" ); + query( "CREATE INDEX devices_uuid ON devices( uuid );" ); + query( "CREATE INDEX devices_rshare ON devices( servername, sharename );" ); +} + +void +CollectionDB::createStatsTable() +{ + // create music statistics database + query( QString( "CREATE TABLE statistics (" + "url " + exactTextColumnType() + "," + "deviceid INTEGER," + "createdate INTEGER," + "accessdate INTEGER," + "percentage FLOAT," + "rating INTEGER DEFAULT 0," + "playcounter INTEGER," + "uniqueid " + exactTextColumnType(32) + " UNIQUE," + "deleted BOOL DEFAULT " + boolF() + "," + "PRIMARY KEY(url, deviceid) );" ) ); + +} + +//Old version, used in upgrade code. This should never be changed. +void +CollectionDB::createStatsTableV8() +{ + // create music statistics database - old form, for upgrade code. + query( QString( "CREATE TABLE statistics (" + "url " + textColumnType() + " UNIQUE," + "createdate INTEGER," + "accessdate INTEGER," + "percentage FLOAT," + "rating INTEGER DEFAULT 0," + "playcounter INTEGER," + "uniqueid " + textColumnType(8) + " UNIQUE," + "deleted BOOL DEFAULT " + boolF() + ");" ) ); + + query( "CREATE INDEX url_stats ON statistics( url );" ); + query( "CREATE INDEX percentage_stats ON statistics( percentage );" ); + query( "CREATE INDEX rating_stats ON statistics( rating );" ); + query( "CREATE INDEX playcounter_stats ON statistics( playcounter );" ); + query( "CREATE INDEX uniqueid_stats ON statistics( uniqueid );" ); +} + +//Old version, used in upgrade code +void +CollectionDB::createStatsTableV10( bool temp ) +{ + // create music statistics database + query( QString( "CREATE %1 TABLE statistics%2 (" + "url " + exactTextColumnType() + "," + "deviceid INTEGER," + "createdate INTEGER," + "accessdate INTEGER," + "percentage FLOAT," + "rating INTEGER DEFAULT 0," + "playcounter INTEGER," + "uniqueid " + exactTextColumnType(32) + " UNIQUE," + "deleted BOOL DEFAULT " + boolF() + "," + "PRIMARY KEY(url, deviceid) );" + ).arg( temp ? "TEMPORARY" : "" ) + .arg( temp ? "_fix_ten" : "" ) ); + + if ( !temp ) + { + query( "CREATE UNIQUE INDEX url_stats ON statistics( deviceid, url );" ); + query( "CREATE INDEX percentage_stats ON statistics( percentage );" ); + query( "CREATE INDEX rating_stats ON statistics( rating );" ); + query( "CREATE INDEX playcounter_stats ON statistics( playcounter );" ); + query( "CREATE INDEX uniqueid_stats ON statistics( uniqueid );" ); + } +} + + +void +CollectionDB::dropStatsTable() +{ + query( "DROP TABLE statistics;" ); +} + +void +CollectionDB::dropStatsTableV1() +{ + query( "DROP TABLE statistics;" ); +} + +void +CollectionDB::createPersistentTables() +{ + // create amazon table + query( "CREATE TABLE amazon ( " + "asin " + textColumnType(20) + ", " + "locale " + textColumnType(2) + ", " + "filename " + exactTextColumnType(33) + ", " + "refetchdate INTEGER );" ); + + // create lyrics table + query( QString( "CREATE TABLE lyrics (" + "url " + exactTextColumnType() + ", " + "deviceid INTEGER," + "lyrics " + longTextColumnType() + ", " + "uniqueid " + exactTextColumnType(32) + ");" ) ); + + query( QString( "CREATE TABLE playlists (" + "playlist " + textColumnType() + ", " + "url " + exactTextColumnType() + ", " + "tracknum INTEGER );" ) ); + + QString labelsAutoIncrement = ""; + if ( getDbConnectionType() == DbConnection::postgresql ) + { + query( QString( "CREATE SEQUENCE labels_seq;" ) ); + + labelsAutoIncrement = QString("DEFAULT nextval('labels_seq')"); + } + else if ( getDbConnectionType() == DbConnection::mysql ) + { + labelsAutoIncrement = "AUTO_INCREMENT"; + } + + //create labels tables + query( QString( "CREATE TABLE labels (" + "id INTEGER PRIMARY KEY " + labelsAutoIncrement + ", " + "name " + textColumnType() + ", " + "type INTEGER);" ) ); + + query( QString( "CREATE TABLE tags_labels (" + "deviceid INTEGER," + "url " + exactTextColumnType() + ", " + "uniqueid " + exactTextColumnType(32) + ", " //m:n relationship, DO NOT MAKE UNIQUE! + "labelid INTEGER REFERENCES labels( id ) ON DELETE CASCADE );" ) ); +} + +void +CollectionDB::createPersistentTablesV12() +{ + // create amazon table + query( "CREATE TABLE amazon ( " + "asin " + textColumnType(20) + ", " + "locale " + textColumnType(2) + ", " + "filename " + textColumnType(33) + ", " + "refetchdate INTEGER );" ); + + // create lyrics table + query( QString( "CREATE TABLE lyrics (" + "url " + textColumnType() + ", " + "lyrics " + longTextColumnType() + ");" ) ); + + // create labels table + query( QString( "CREATE TABLE label (" + "url " + textColumnType() + "," + "label " + textColumnType() + ");" ) ); + + query( QString( "CREATE TABLE playlists (" + "playlist " + textColumnType() + ", " + "url " + textColumnType() + ", " + "tracknum INTEGER );" ) ); + + query( "CREATE INDEX url_label ON label( url );" ); + query( "CREATE INDEX label_label ON label( label );" ); + query( "CREATE INDEX playlist_playlists ON playlists( playlist );" ); + query( "CREATE INDEX url_playlists ON playlists( url );" ); +} + +void +CollectionDB::createPersistentTablesV14( bool temp ) +{ + const QString a( temp ? "TEMPORARY" : "" ); + const QString b( temp ? "_fix" : "" ); + + // create amazon table + query( QString( "CREATE %1 TABLE amazon%2 ( " + "asin " + textColumnType(20) + ", " + "locale " + textColumnType(2) + ", " + "filename " + exactTextColumnType(33) + ", " + "refetchdate INTEGER );" ).arg( a,b ) ); + + // create lyrics table + query( QString( "CREATE %1 TABLE lyrics%2 (" + "url " + exactTextColumnType() + ", " + "deviceid INTEGER," + "lyrics " + longTextColumnType() + ");" ).arg( a,b ) ); + + query( QString( "CREATE %1 TABLE playlists%2 (" + "playlist " + textColumnType() + ", " + "url " + exactTextColumnType() + ", " + "tracknum INTEGER );" ).arg( a,b ) ); + + if ( !temp ) + { + query( "CREATE UNIQUE INDEX lyrics_url ON lyrics( url, deviceid );" ); + query( "CREATE INDEX playlist_playlists ON playlists( playlist );" ); + query( "CREATE INDEX url_playlists ON playlists( url );" ); + } +} + +void +CollectionDB::createPodcastTables() +{ + QString podcastAutoIncrement = ""; + QString podcastFolderAutoInc = ""; + if ( getDbConnectionType() == DbConnection::postgresql ) + { + query( QString( "CREATE SEQUENCE podcastepisode_seq;" ) ); + + query( QString( "CREATE SEQUENCE podcastfolder_seq;" ) ); + + podcastAutoIncrement = QString("DEFAULT nextval('podcastepisode_seq')"); + podcastFolderAutoInc = QString("DEFAULT nextval('podcastfolder_seq')"); + } + else if ( getDbConnectionType() == DbConnection::mysql ) + { + podcastAutoIncrement = "AUTO_INCREMENT"; + podcastFolderAutoInc = "AUTO_INCREMENT"; + } + + // create podcast channels table + query( QString( "CREATE TABLE podcastchannels (" + "url " + exactTextColumnType() + " UNIQUE," + "title " + textColumnType() + "," + "weblink " + exactTextColumnType() + "," + "image " + exactTextColumnType() + "," + "comment " + longTextColumnType() + "," + "copyright " + textColumnType() + "," + "parent INTEGER," + "directory " + textColumnType() + "," + "autoscan BOOL, fetchtype INTEGER, " + "autotransfer BOOL, haspurge BOOL, purgecount INTEGER );" ) ); + + // create podcast episodes table + query( QString( "CREATE TABLE podcastepisodes (" + "id INTEGER PRIMARY KEY %1, " + "url " + exactTextColumnType() + " UNIQUE," + "localurl " + exactTextColumnType() + "," + "parent " + exactTextColumnType() + "," + "guid " + exactTextColumnType() + "," + "title " + textColumnType() + "," + "subtitle " + textColumnType() + "," + "composer " + textColumnType() + "," + "comment " + longTextColumnType() + "," + "filetype " + textColumnType() + "," + "createdate " + textColumnType() + "," + "length INTEGER," + "size INTEGER," + "isNew BOOL );" ) + .arg( podcastAutoIncrement ) ); + + // create podcast folders table + query( QString( "CREATE TABLE podcastfolders (" + "id INTEGER PRIMARY KEY %1, " + "name " + textColumnType() + "," + "parent INTEGER, isOpen BOOL );" ) + .arg( podcastFolderAutoInc ) ); + + query( "CREATE INDEX url_podchannel ON podcastchannels( url );" ); + query( "CREATE INDEX url_podepisode ON podcastepisodes( url );" ); + query( "CREATE INDEX localurl_podepisode ON podcastepisodes( localurl );" ); + query( "CREATE INDEX url_podfolder ON podcastfolders( id );" ); +} + +void +CollectionDB::createPodcastTablesV2( bool temp ) +{ + const QString a( temp ? "TEMPORARY" : "" ); + const QString b( temp ? "_fix" : "" ); + + QString podcastAutoIncrement = ""; + QString podcastFolderAutoInc = ""; + if ( getDbConnectionType() == DbConnection::postgresql ) + { + query( QString( "CREATE SEQUENCE podcastepisode_seq;" ) ); + + query( QString( "CREATE SEQUENCE podcastfolder_seq;" ) ); + + podcastAutoIncrement = QString("DEFAULT nextval('podcastepisode_seq')"); + podcastFolderAutoInc = QString("DEFAULT nextval('podcastfolder_seq')"); + } + else if ( getDbConnectionType() == DbConnection::mysql ) + { + podcastAutoIncrement = "AUTO_INCREMENT"; + podcastFolderAutoInc = "AUTO_INCREMENT"; + } + + // create podcast channels table + query( QString( "CREATE %1 TABLE podcastchannels%2 (" + "url " + exactTextColumnType() + " UNIQUE," + "title " + textColumnType() + "," + "weblink " + exactTextColumnType() + "," + "image " + exactTextColumnType() + "," + "comment " + longTextColumnType() + "," + "copyright " + textColumnType() + "," + "parent INTEGER," + "directory " + textColumnType() + "," + "autoscan BOOL, fetchtype INTEGER, " + "autotransfer BOOL, haspurge BOOL, purgecount INTEGER );" ).arg( a,b ) ); + + // create podcast episodes table + query( QString( "CREATE %2 TABLE podcastepisodes%3 (" + "id INTEGER PRIMARY KEY %1, " + "url " + exactTextColumnType() + " UNIQUE," + "localurl " + exactTextColumnType() + "," + "parent " + exactTextColumnType() + "," + "guid " + exactTextColumnType() + "," + "title " + textColumnType() + "," + "subtitle " + textColumnType() + "," + "composer " + textColumnType() + "," + "comment " + longTextColumnType() + "," + "filetype " + textColumnType() + "," + "createdate " + textColumnType() + "," + "length INTEGER," + "size INTEGER," + "isNew BOOL );" ) + .arg( podcastAutoIncrement, a, b ) ); + + // create podcast folders table + query( QString( "CREATE %2 TABLE podcastfolders%3 (" + "id INTEGER PRIMARY KEY %1, " + "name " + textColumnType() + "," + "parent INTEGER, isOpen BOOL );" ) + .arg( podcastFolderAutoInc, a, b ) ); + + if ( !temp ) + { + query( "CREATE INDEX url_podchannel ON podcastchannels( url );" ); + query( "CREATE INDEX url_podepisode ON podcastepisodes( url );" ); + query( "CREATE INDEX localurl_podepisode ON podcastepisodes( localurl );" ); + query( "CREATE INDEX url_podfolder ON podcastfolders( id );" ); + } +} + + +void +CollectionDB::dropPersistentTables() +{ + query( "DROP TABLE amazon;" ); + query( "DROP TABLE lyrics;" ); + query( "DROP TABLE playlists;" ); + query( "DROP TABLE tags_labels;" ); + query( "DROP TABLE labels;" ); +} + +void +CollectionDB::dropPersistentTablesV14() +{ + query( "DROP TABLE amazon;" ); + query( "DROP TABLE lyrics;" ); + query( "DROP TABLE label;" ); + query( "DROP TABLE playlists;" ); +} + +void +CollectionDB::dropPodcastTables() +{ + query( "DROP TABLE podcastchannels;" ); + query( "DROP TABLE podcastepisodes;" ); + query( "DROP TABLE podcastfolders;" ); +} + +void +CollectionDB::dropPodcastTablesV2() +{ + query( "DROP TABLE podcastchannels;" ); + query( "DROP TABLE podcastepisodes;" ); + query( "DROP TABLE podcastfolders;" ); +} + +void +CollectionDB::dropDevicesTable() +{ + query( "DROP TABLE devices;" ); +} + +uint +CollectionDB::artistID( QString value, bool autocreate, const bool temporary, bool exact /* = true */ ) +{ + // lookup cache + if ( m_validArtistCache && m_cacheArtist[(int)temporary] == value ) + return m_cacheArtistID[(int)temporary]; + + uint id; + if ( exact ) + id = IDFromExactValue( "artist", value, autocreate, temporary ).toUInt(); + else + id = IDFromValue( "artist", value, autocreate, temporary ); + + // cache values + m_cacheArtist[(int)temporary] = value; + m_cacheArtistID[(int)temporary] = id; + m_validArtistCache = 1; + + return id; +} + + +QString +CollectionDB::artistValue( uint id ) +{ + // lookup cache + if ( m_cacheArtistID[0] == id ) + return m_cacheArtist[0]; + + QString value = valueFromID( "artist", id ); + + // cache values + m_cacheArtist[0] = value; + m_cacheArtistID[0] = id; + + return value; +} + + +uint +CollectionDB::composerID( QString value, bool autocreate, const bool temporary, bool exact /* = true */ ) +{ + // lookup cache + if ( m_validComposerCache && m_cacheComposer[(int)temporary] == value ) + return m_cacheComposerID[(int)temporary]; + + uint id; + if ( exact ) + id = IDFromExactValue( "composer", value, autocreate, temporary ).toUInt(); + else + id = IDFromValue( "composer", value, autocreate, temporary ); + + // cache values + m_cacheComposer[(int)temporary] = value; + m_cacheComposerID[(int)temporary] = id; + m_validComposerCache = 1; + + return id; +} + + +QString +CollectionDB::composerValue( uint id ) +{ + // lookup cache + if ( m_cacheComposerID[0] == id ) + return m_cacheComposer[0]; + + QString value = valueFromID( "composer", id ); + + // cache values + m_cacheComposer[0] = value; + m_cacheComposerID[0] = id; + + return value; +} + + +uint +CollectionDB::albumID( QString value, bool autocreate, const bool temporary, bool exact /* = true */ ) +{ + // lookup cache + if ( m_validAlbumCache && m_cacheAlbum[(int)temporary] == value ) + return m_cacheAlbumID[(int)temporary]; + + uint id; + if ( exact ) + id = IDFromExactValue( "album", value, autocreate, temporary ).toUInt(); + else + id = IDFromValue( "album", value, autocreate, temporary ); + + // cache values + m_cacheAlbum[(int)temporary] = value; + m_cacheAlbumID[(int)temporary] = id; + m_validAlbumCache = 1; + + return id; +} + +QString +CollectionDB::albumValue( uint id ) +{ + // lookup cache + if ( m_cacheAlbumID[0] == id ) + return m_cacheAlbum[0]; + + QString value = valueFromID( "album", id ); + + // cache values + m_cacheAlbum[0] = value; + m_cacheAlbumID[0] = id; + + return value; +} + +uint +CollectionDB::genreID( QString value, bool autocreate, const bool temporary, bool exact /* = true */ ) +{ + return exact ? + IDFromExactValue( "genre", value, autocreate, temporary ).toUInt() : + IDFromValue( "genre", value, autocreate, temporary ); +} + +QString +CollectionDB::genreValue( uint id ) +{ + return valueFromID( "genre", id ); +} + + +uint +CollectionDB::yearID( QString value, bool autocreate, const bool temporary, bool exact /* = true */ ) +{ + return exact ? + IDFromExactValue( "year", value, autocreate, temporary ).toUInt() : + IDFromValue( "year", value, autocreate, temporary ); +} + + +QString +CollectionDB::yearValue( uint id ) +{ + return valueFromID( "year", id ); +} + + +uint +CollectionDB::IDFromValue( QString name, QString value, bool autocreate, const bool temporary ) +{ + if ( temporary ) + name.append( "_temp" ); +// what the hell is the reason for this? +// else +// conn = NULL; + + QStringList values = + query( QString( + "SELECT id, name FROM %1 WHERE name %2;" ) + .arg( name ) + .arg( CollectionDB::likeCondition( value ) ) ); + + //check if item exists. if not, should we autocreate it? + uint id; + if ( values.isEmpty() && autocreate ) + { + id = insert( QString( "INSERT INTO %1 ( name ) VALUES ( '%2' );" ) + .arg( name ) + .arg( CollectionDB::instance()->escapeString( value ) ), name ); + + return id; + } + + return values.isEmpty() ? 0 : values.first().toUInt(); +} + + +QString +CollectionDB::valueFromID( QString table, uint id ) +{ + QStringList values = + query( QString( + "SELECT name FROM %1 WHERE id=%2;" ) + .arg( table ) + .arg( id ) ); + + + return values.isEmpty() ? 0 : values.first(); +} + + +QString +CollectionDB::albumSongCount( const QString &artist_id, const QString &album_id ) +{ + QStringList values = + query( QString( + "SELECT COUNT( url ) FROM tags WHERE album = %1 AND artist = %2;" ) + .arg( album_id ) + .arg( artist_id ) ); + return values.first(); +} + +bool +CollectionDB::albumIsCompilation( const QString &album_id ) +{ + QStringList values = + query( QString( + "SELECT sampler FROM tags WHERE sampler=%1 AND album=%2" ) + .arg( CollectionDB::instance()->boolT() ) + .arg( album_id ) ); + + return (values.count() != 0); +} + +QStringList +CollectionDB::albumTracks( const QString &artist_id, const QString &album_id ) +{ + QueryBuilder qb; + qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valURL ); + qb.addMatch( QueryBuilder::tabAlbum, QueryBuilder::valID, album_id ); + const bool isCompilation = albumIsCompilation( album_id ); + if( !isCompilation ) + qb.addMatch( QueryBuilder::tabArtist, QueryBuilder::valID, artist_id ); + qb.sortBy( QueryBuilder::tabSong, QueryBuilder::valDiscNumber ); + qb.sortBy( QueryBuilder::tabSong, QueryBuilder::valTrack ); + QStringList ret = qb.run(); + + uint returnValues = qb.countReturnValues(); + if ( returnValues > 1 ) + { + QStringList ret2; + for ( QStringList::size_type i = 0; i < ret.size(); i += returnValues ) + ret2 << ret[ i ]; + return ret2; + } + else + return ret; +} + +QStringList +CollectionDB::albumDiscTracks( const QString &artist_id, const QString &album_id, const QString &discNumber) +{ + QStringList rs; + rs = query( QString( "SELECT tags.deviceid, tags.url FROM tags, year WHERE tags.album = %1 AND " + "tags.artist = %2 AND year.id = tags.year AND tags.discnumber = %3 " + + deviceidSelection() + " ORDER BY tags.track;" ) + .arg( album_id ) + .arg( artist_id ) + .arg( discNumber ) ); + QStringList result; + foreach( rs ) + { + const int id = (*it).toInt(); + result << MountPointManager::instance()->getAbsolutePath( id, *(++it) ); + } + return result; +} + +QStringList +CollectionDB::artistTracks( const QString &artist_id ) +{ + QStringList rs = query( QString( "SELECT tags.deviceid, tags.url FROM tags, album " + "WHERE tags.artist = '%1' AND album.id = tags.album " + deviceidSelection() + + "ORDER BY album.name, tags.discnumber, tags.track;" ) + .arg( artist_id ) ); + QStringList result = QStringList(); + foreach( rs ) + { + const int id = (*it).toInt(); + result << MountPointManager::instance()->getAbsolutePath( id, *(++it) ); + } + return result; +} + + +void +CollectionDB::addImageToAlbum( const QString& image, QValueList< QPair > info, const bool temporary ) +{ + int deviceid = MountPointManager::instance()->getIdForUrl( image ); + QString rpath = MountPointManager::instance()->getRelativePath( deviceid, image ); + for ( QValueList< QPair >::ConstIterator it = info.begin(); it != info.end(); ++it ) + { + if ( (*it).first.isEmpty() || (*it).second.isEmpty() ) + continue; + + QString sql = QString( "INSERT INTO images%1 ( path, deviceid, artist, album ) VALUES ( '%3', %2" ) + .arg( temporary ? "_temp" : "" ) + .arg( deviceid ) + .arg( escapeString( rpath ) ); + sql += QString( ", '%1'" ).arg( escapeString( (*it).first ) ); + sql += QString( ", '%1' );" ).arg( escapeString( (*it).second ) ); + +// debug() << "Added image for album: " << (*it).first << " - " << (*it).second << ": " << image << endl; + insert( sql, NULL ); + } +} + +void +CollectionDB::addEmbeddedImage( const QString& path, const QString& hash, const QString& description ) +{ +// debug() << "Added embedded image hash " << hash << " for file " << path << endl; + //TODO: figure out what this embedded table does and then add the necessary code + //what are embedded images anyway? + int deviceid = MountPointManager::instance()->getIdForUrl( path ); + QString rpath = MountPointManager::instance()->getRelativePath(deviceid, path ); + insert( QString( "INSERT INTO embed_temp ( url, deviceid, hash, description ) VALUES ( '%2', %1, '%3', '%4' );" ) + .arg( deviceid ) + .arg( escapeString( rpath ), escapeString( hash ), escapeString( description ) ), NULL ); +} + +void +CollectionDB::removeOrphanedEmbeddedImages() +{ + //TODO refactor + // do it the hard way, since a delete subquery wont work on MySQL + QStringList orphaned = query( "SELECT embed.deviceid, embed.url FROM embed LEFT JOIN tags ON embed.url = tags.url AND embed.deviceid = tags.deviceid WHERE tags.url IS NULL;" ); + foreach( orphaned ) { + QString deviceid = *it; + QString rpath = *(++it); + query( QString( "DELETE FROM embed WHERE embed.deviceid = %1 AND embed.url = '%2';" ) + .arg( deviceid, escapeString( rpath ) ) ); + } +} + +QPixmap +CollectionDB::createDragPixmapFromSQL( const QString &sql, QString textOverRide ) +{ + // it is too slow to check if the url is actually in the colleciton. + //TODO mountpointmanager: figure out what has to be done here + QStringList values = instance()->query( sql ); + KURL::List list; + foreach( values ) + { + KURL u = KURL::fromPathOrURL( *it ); + if( u.isValid() ) + list += u; + } + return createDragPixmap( list, textOverRide ); +} + +QPixmap +CollectionDB::createDragPixmap( const KURL::List &urls, QString textOverRide ) +{ + // settings + const int maxCovers = 4; // maximum number of cover images to show + const int coverSpacing = 20; // spacing between stacked covers + const int fontSpacing = 5; // spacing between covers and info text + const int coverW = AmarokConfig::coverPreviewSize() > 100 ? 100 : AmarokConfig::coverPreviewSize(); + const int coverH = coverW; + const int margin = 2; //px margin + + int covers = 0; + int songs = 0; + int pixmapW = 0; + int pixmapH = 0; + int remoteUrls = 0; + int playlists = 0; + + QMap albumMap; + QPixmap coverPm[maxCovers]; + + QString song, album; + + + // iterate urls, get covers and count artist/albums + bool correctAlbumCount = true; + KURL::List::ConstIterator it = urls.begin(); + for ( ; it != urls.end(); ++it ) + { + if( PlaylistFile::isPlaylistFile( *it ) + || (*it).protocol() == "playlist" || (*it).protocol() == "smartplaylist" + || (*it).protocol() == "dynamic" ) + { + playlists++; + } + else if( (*it).isLocalFile() ) + { + songs++; + + if( covers >= maxCovers ) + { + correctAlbumCount = false; + continue; + } + + MetaBundle mb( *it ); + + song = mb.title(); + album = mb.album(); + + QString artist = mb.artist(); + if( mb.compilation() == MetaBundle::CompilationYes ) + artist = QString( "Various_AMAROK_Artists" ); // magic key for the albumMap! + + if( !albumMap.contains( artist + album ) ) + { + albumMap[ artist + album ] = 1; + QString coverName = CollectionDB::instance()->albumImage( mb.artist(), album, false, coverW ); + + if ( !coverName.endsWith( "@nocover.png" ) ) + coverPm[covers++].load( coverName ); + } + } + else + { + MetaBundle mb( *it ); + if( !albumMap.contains( mb.artist() + mb.album() ) ) + { + albumMap[ mb.artist() + mb.album() ] = 1; + QString coverName = CollectionDB::instance()->podcastImage( mb, false, coverW ); + + if ( covers < maxCovers && !coverName.endsWith( "@nocover.png" ) ) + coverPm[covers++].load( coverName ); + } + remoteUrls++; + } + } + + // make better text... + int albums = albumMap.count(); + QString text; + + if( !textOverRide.isEmpty() ) + { + text = textOverRide; + } + else if( ( songs && remoteUrls ) || + ( songs && playlists ) || + ( playlists && remoteUrls ) ) + { + text = i18n( "One item", "%n items", songs + remoteUrls + playlists ); + } + else if( songs > 0 ) + { + if( correctAlbumCount ) { + text = i18n( "X songs from X albums", "%2 from %1" ); + text = text.arg( albums == 1 && !album.isEmpty() ? album : i18n( "one album", "%n albums",albums ) ); + } + else + text = "%1"; + text = text.arg( songs == 1 && !song.isEmpty() ? song : i18n( "One song", "%n songs", songs ) ); + } + else if( playlists > 0 ) + text = i18n( "One playlist", "%n playlists", playlists ); + else if ( remoteUrls > 0 ) + text = i18n( "One remote file", "%n remote files", remoteUrls ); + else + text = i18n( "Unknown item" ); + + QFont font; + QFontMetrics fm( font ); + int fontH = fm.height() + margin; + int minWidth = fm.width( text ) + margin*2; //margin either side + + if ( covers > 0 ) + { + // insert "..." cover as first image if appropriate + if ( covers < albums ) + { + if ( covers < maxCovers ) covers++; + for ( int i = maxCovers-1; i > 0; i-- ) + coverPm[i] = coverPm[i-1]; + + QImage im( locate( "data","amarok/images/more_albums.png" ) ); + coverPm[0].convertFromImage( im.smoothScale( coverW, coverH, QImage::ScaleMin ) ); + } + + pixmapH = coverPm[0].height(); + pixmapW = coverPm[0].width(); + + // caluclate pixmap height + int dW, dH; + for ( int i = 1; i < covers; i++ ) + { + dW = coverPm[i].width() - coverPm[i-1].width() + coverSpacing; + dH = coverPm[i].height() - coverPm[i-1].height() + coverSpacing; + if ( dW > 0 ) pixmapW += dW; + if ( dH > 0 ) pixmapH += dH; + } + pixmapH += fontSpacing + fontH; + + if ( pixmapW < minWidth ) + pixmapW = minWidth; + } + else + { + pixmapW = minWidth; + pixmapH = fontH; + } + + QPixmap pmdrag( pixmapW, pixmapH ); + QPixmap pmtext( pixmapW, fontH ); + + QPalette palette = QToolTip::palette(); + + QPainter p; + p.begin( &pmtext ); + p.fillRect( 0, 0, pixmapW, fontH, QBrush( Qt::black ) ); // border + p.fillRect( 1, 1, pixmapW-margin, fontH-margin, palette.brush( QPalette::Normal, QColorGroup::Background ) ); + p.setBrush( palette.color( QPalette::Normal, QColorGroup::Text ) ); + p.setFont( font ); + p.drawText( margin, fm.ascent() + 1, text ); + p.end(); + + QBitmap pmtextMask(pixmapW, fontH); + pmtextMask.fill( Qt::color1 ); + + // when we have found no covers, just display the text message + if( !covers ) + { + pmtext.setMask(pmtextMask); + return pmtext; + } + + // compose image + p.begin( &pmdrag ); + p.setBackgroundMode( Qt::TransparentMode ); + for ( int i = 0; i < covers; i++ ) + bitBlt( &pmdrag, i * coverSpacing, i * coverSpacing, &coverPm[i], 0, Qt::CopyROP ); + + bitBlt( &pmdrag, 0, pixmapH - fontH, &pmtext, 0, Qt::CopyROP ); + p.end(); + + QBitmap pmdragMask( pmdrag.size(), true ); + for ( int i = 0; i < covers; i++ ) + { + QBitmap coverMask( coverPm[i].width(), coverPm[i].height() ); + coverMask.fill( Qt::color1 ); + bitBlt( &pmdragMask, i * coverSpacing, i * coverSpacing, &coverMask, 0, Qt::CopyROP ); + } + bitBlt( &pmdragMask, 0, pixmapH - fontH, &pmtextMask, 0, Qt::CopyROP ); + pmdrag.setMask( pmdragMask ); + + return pmdrag; +} + +QImage +CollectionDB::fetchImage( const KURL& url, QString &/*tmpFile*/ ) +{ + if ( url.protocol() != "file" ) + { + QString tmpFile; + KIO::NetAccess::download( url, tmpFile, 0 ); //TODO set 0 to the window, though it probably doesn't really matter + return QImage( tmpFile ); + } + else + { + return QImage( url.path() ); + } + +} +bool +CollectionDB::setAlbumImage( const QString& artist, const QString& album, const KURL& url ) +{ + QString tmpFile; + bool success = setAlbumImage( artist, album, fetchImage(url, tmpFile) ); + KIO::NetAccess::removeTempFile( tmpFile ); //only removes file if it was created with NetAccess + return success; +} + + +bool +CollectionDB::setAlbumImage( const QString& artist, const QString& album, QImage img, const QString& amazonUrl, const QString& asin ) +{ + //show a wait cursor for the duration + Amarok::OverrideCursor keep; + + const bool isCompilation = albumIsCompilation( QString::number( albumID( album, false, false, true ) ) ); + const QString artist_ = isCompilation ? "" : artist; + + // remove existing album covers + removeAlbumImage( artist_, album ); + + QCString key = md5sum( artist_, album ); + newAmazonReloadDate(asin, AmarokConfig::amazonLocale(), key); + // Save Amazon product page URL as embedded string, for later retreival + if ( !amazonUrl.isEmpty() ) + img.setText( "amazon-url", 0, amazonUrl ); + + const bool b = img.save( largeCoverDir().filePath( key ), "PNG"); + emit coverChanged( artist_, album ); + return b; +} + + +QString +CollectionDB::podcastImage( const MetaBundle &bundle, const bool withShadow, uint width ) +{ + PodcastEpisodeBundle peb; + PodcastChannelBundle pcb; + + KURL url = bundle.url().url(); + + if( getPodcastEpisodeBundle( url, &peb ) ) + { + url = peb.parent().url(); + } + + if( getPodcastChannelBundle( url, &pcb ) ) + { + if( pcb.imageURL().isValid() ) + return podcastImage( pcb.imageURL().url(), withShadow, width ); + } + + return notAvailCover( withShadow, width ); +} + + +QString +CollectionDB::podcastImage( const QString &remoteURL, const bool withShadow, uint width ) +{ + // we aren't going to need a 1x1 size image. this is just a quick hack to be able to show full size images. + // width of 0 == full size + if( width == 1 ) + width = AmarokConfig::coverPreviewSize(); + + QString s = findAmazonImage( "Podcast", remoteURL, width ); + + if( s.isEmpty() ) + { + s = notAvailCover( withShadow, width ); + + const KURL url = KURL::fromPathOrURL( remoteURL ); + if( url.isValid() ) //KIO crashes with invalid URLs + { + KIO::Job *job = KIO::storedGet( url, false, false ); + m_podcastImageJobs[job] = remoteURL; + connect( job, SIGNAL( result( KIO::Job* ) ), SLOT( podcastImageResult( KIO::Job* ) ) ); + } + } + + if ( withShadow ) + s = makeShadowedImage( s ); + + return s; +} + +void +CollectionDB::podcastImageResult( KIO::Job *gjob ) +{ + QString url = m_podcastImageJobs[gjob]; + m_podcastImageJobs.remove( gjob ); + + KIO::StoredTransferJob *job = dynamic_cast( gjob ); + if( !job ) + { + debug() << "connected to wrong job type" << endl; + return; + } + + if( job->error() ) + { + debug() << "job finished with error" << endl; + return; + } + + if( job->isErrorPage() ) + { + debug() << "error page" << endl; + return; + } + + QImage image( job->data() ); + if( !image.isNull() ) + { + if( url.isEmpty() ) + url = job->url().url(); + + QCString key = md5sum( "Podcast", url ); + if( image.save( largeCoverDir().filePath( key ), "PNG") ) + emit imageFetched( url ); + } +} + + +QString +CollectionDB::albumImage( const QString &artist, const QString &album, bool withShadow, uint width, bool* embedded ) +{ + QString s; + // we aren't going to need a 1x1 size image. this is just a quick hack to be able to show full size images. + // width of 0 == full size + if( width == 1 ) + width = AmarokConfig::coverPreviewSize(); + if( embedded ) + *embedded = false; + + s = findAmazonImage( artist, album, width ); + + if( s.isEmpty() ) + s = findAmazonImage( "", album, width ); // handle compilations + + if( s.isEmpty() ) + s = findDirectoryImage( artist, album, width ); + + if( s.isEmpty() ) + { + s = findEmbeddedImage( artist, album, width ); + if( embedded && !s.isEmpty() ) + *embedded = true; + } + + if( s.isEmpty() ) + s = notAvailCover( withShadow, width ); + + if ( withShadow ) + s = makeShadowedImage( s ); + + return s; +} + + +QString +CollectionDB::albumImage( const uint artist_id, const uint album_id, bool withShadow, uint width, bool* embedded ) +{ + return albumImage( artistValue( artist_id ), albumValue( album_id ), withShadow, width, embedded ); +} + + +QString +CollectionDB::albumImage( const MetaBundle &trackInformation, bool withShadow, uint width, bool* embedded ) +{ + QString s; + if( width == 1 ) + width = AmarokConfig::coverPreviewSize(); + + QString album = trackInformation.album(); + QString artist = trackInformation.artist(); + + // this art is per track, so should check for it first + s = findMetaBundleImage( trackInformation, width ); + if( embedded ) + *embedded = !s.isEmpty(); + + if( s.isEmpty() ) + s = findAmazonImage( artist, album, width ); + if( s.isEmpty() ) + s = findAmazonImage( "", album, width ); // handle compilations + if( s.isEmpty() ) + s = findDirectoryImage( artist, album, width ); + if( s.isEmpty() ) + s = notAvailCover( withShadow, width ); + if ( withShadow ) + s = makeShadowedImage( s ); + return s; +} + +QString +CollectionDB::makeShadowedImage( const QString& albumImage, bool cache ) +{ + qApp->lock(); + const QImage original( albumImage, "PNG" ); + qApp->unlock(); + + if( original.hasAlphaBuffer() ) + return albumImage; + + const QFileInfo fileInfo( albumImage ); + const uint shadowSize = static_cast( original.width() / 100.0 * 6.0 ); + const QString cacheFile = fileInfo.fileName() + "@shadow"; + + if ( !cache && cacheCoverDir().exists( cacheFile ) ) + return cacheCoverDir().filePath( cacheFile ); + + QImage shadow; + + const QString folder = Amarok::saveLocation( "covershadow-cache/" ); + const QString file = QString( "shadow_albumcover%1x%2.png" ).arg( original.width() + shadowSize ).arg( original.height() + shadowSize ); + if ( QFile::exists( folder + file ) ) { + qApp->lock(); + shadow.load( folder + file, "PNG" ); + qApp->unlock(); + } + else { + shadow = QDeepCopy(instance()->m_shadowImage); + shadow = shadow.smoothScale( original.width() + shadowSize, original.height() + shadowSize ); + shadow.save( folder + file, "PNG" ); + } + + QImage target(shadow); + bitBlt( &target, 0, 0, &original ); + + if ( cache ) { + target.save( cacheCoverDir().filePath( cacheFile ), "PNG" ); + return cacheCoverDir().filePath( cacheFile ); + } + + target.save( albumImage, "PNG" ); + return albumImage; +} + + +// Amazon Image +QString +CollectionDB::findAmazonImage( const QString &artist, const QString &album, uint width ) +{ + QCString widthKey = makeWidthKey( width ); + + if ( artist.isEmpty() && album.isEmpty() ) + return QString(); + + QCString key = md5sum( artist, album ); + + // check cache for existing cover + if ( cacheCoverDir().exists( widthKey + key ) ) + return cacheCoverDir().filePath( widthKey + key ); + + // we need to create a scaled version of this cover + QDir imageDir = largeCoverDir(); + if ( imageDir.exists( key ) ) + { + if ( width > 1 ) + { + QImage img( imageDir.filePath( key ) ); + img.smoothScale( width, width, QImage::ScaleMin ).save( cacheCoverDir().filePath( widthKey + key ), "PNG" ); + + return cacheCoverDir().filePath( widthKey + key ); + } + else + return imageDir.filePath( key ); + } + + return QString(); +} + + +QString +CollectionDB::findDirectoryImage( const QString& artist, const QString& album, uint width ) +{ + if ( width == 1 ) + width = AmarokConfig::coverPreviewSize(); + QCString widthKey = makeWidthKey( width ); + if ( album.isEmpty() ) + return QString(); + + IdList list = MountPointManager::instance()->getMountedDeviceIds(); + QString deviceIds; + foreachType( IdList, list ) + { + if ( !deviceIds.isEmpty() ) deviceIds += ','; + deviceIds += QString::number(*it); + } + + QStringList rs; + if ( artist == i18n( "Various Artists" ) || artist.isEmpty() ) + { + rs = query( QString( + "SELECT distinct images.deviceid,images.path FROM images, artist, tags " + "WHERE images.artist = artist.name " + "AND artist.id = tags.artist " + "AND tags.sampler = %1 " + "AND images.album %2 " + "AND images.deviceid IN (%3) " ) + .arg( boolT() ) + .arg( CollectionDB::likeCondition( album ) ) + .arg( deviceIds ) ); + } + else + { + rs = query( QString( + "SELECT distinct images.deviceid,images.path FROM images WHERE artist %1 AND album %2 AND deviceid IN (%3) ORDER BY path;" ) + .arg( CollectionDB::likeCondition( artist ) ) + .arg( CollectionDB::likeCondition( album ) ) + .arg( deviceIds ) ); + } + QStringList values = URLsFromQuery( rs ); + if ( !values.isEmpty() ) + { + QString image( values.first() ); + uint matches = 0; + uint maxmatches = 0; + QRegExp iTunesArt( "^AlbumArt_.*Large" ); + for ( uint i = 0; i < values.count(); i++ ) + { + matches = values[i].contains( "front", false ) + values[i].contains( "cover", false ) + values[i].contains( "folder", false ) + values[i].contains( iTunesArt ); + if ( matches > maxmatches ) + { + image = values[i]; + maxmatches = matches; + } + } + + QCString key = md5sum( artist, album, image ); + + if ( width > 1 ) + { + QString path = cacheCoverDir().filePath( widthKey + key ); + if ( !QFile::exists( path ) ) + { + QImage img( image ); + img.smoothScale( width, width, QImage::ScaleMin ).save( path, "PNG" ); + } + return path; + } + else //large image + return image; + } + return QString(); +} + + +QString +CollectionDB::findEmbeddedImage( const QString& artist, const QString& album, uint width ) +{ + // In the case of multiple embedded images, we arbitrarily choose one from the newest file + // could potentially select multiple images within a file based on description, although a + // lot of tagging software doesn't fill in that field, so we just get whatever the DB + // happens to return for us + QStringList rs; + if ( artist == i18n("Various Artists") || artist.isEmpty() ) { + // VAs need special handling to not match on artist name but instead check for sampler flag + rs = query( QString( + "SELECT embed.hash, embed.deviceid, embed.url FROM " + "tags INNER JOIN embed ON tags.url = embed.url " + "INNER JOIN album ON tags.album = album.id " + "WHERE " + "album.name = '%1' " + "AND tags.sampler = %2 " + "ORDER BY modifydate DESC LIMIT 1;" ) + .arg( escapeString( album ) ) + .arg( boolT() ) ); + } else { + rs = query( QString( + "SELECT embed.hash, embed.deviceid, embed.url FROM " + "tags INNER JOIN embed ON tags.url = embed.url " + "INNER JOIN artist ON tags.artist = artist.id " + "INNER JOIN album ON tags.album = album.id " + "WHERE " + "artist.name = '%1' " + "AND album.name = '%2' " + "ORDER BY modifydate DESC LIMIT 1;" ) + .arg( escapeString( artist ) ) + .arg( escapeString( album ) ) ); + } + + QStringList values = QStringList(); + if ( rs.count() == 3 ) { + values += rs.first(); + values += MountPointManager::instance()->getAbsolutePath( rs[1].toInt(), rs[2] ); + } + + if ( values.count() == 2 ) { + QCString hash = values.first().utf8(); + QString result = loadHashFile( hash, width ); + if ( result.isEmpty() ) { + // need to get original from file first + MetaBundle mb( KURL::fromPathOrURL( values.last() ) ); + if ( extractEmbeddedImage( mb, hash ) ) { + // try again, as should be possible now + result = loadHashFile( hash, width ); + } + } + return result; + } + return QString(); +} + + +QString +CollectionDB::findMetaBundleImage( const MetaBundle& trackInformation, uint width ) +{ + int deviceid = MountPointManager::instance()->getIdForUrl( trackInformation.url() ); + QString rpath = MountPointManager::instance()->getRelativePath( deviceid, trackInformation.url().path() ); + QStringList values = + query( QString( + "SELECT embed.hash FROM tags LEFT JOIN embed ON tags.url = embed.url " + " AND tags.deviceid = embed.deviceid WHERE tags.url = '%2' AND tags.deviceid = %1 ORDER BY hash DESC LIMIT 1;" ) + .arg( deviceid ).arg( escapeString( rpath ) ) ); + + if ( values.empty() || !values.first().isEmpty() ) { + QCString hash; + QString result; + if( !values.empty() ) { // file in collection, so we know the hash + hash = values.first().utf8(); + result = loadHashFile( hash, width ); + } + if ( result.isEmpty() ) { + // need to get original from file first + if ( extractEmbeddedImage( trackInformation, hash ) ) { + // try again, as should be possible now + result = loadHashFile( hash, width ); + } + } + return result; + } + return QString(); +} + + +QCString +CollectionDB::makeWidthKey( uint width ) +{ + return QString::number( width ).local8Bit() + '@'; +} + + +bool +CollectionDB::removeAlbumImage( const QString &artist, const QString &album ) +{ + DEBUG_BLOCK + + QCString widthKey = "*@"; + QCString key = md5sum( artist, album ); + query( "DELETE FROM amazon WHERE filename='" + key + '\'' ); + + // remove scaled versions of images (and add the asterisk for the shadow-caches) + QStringList scaledList = cacheCoverDir().entryList( widthKey + key + '*' ); + if ( scaledList.count() > 0 ) + for ( uint i = 0; i < scaledList.count(); i++ ) + QFile::remove( cacheCoverDir().filePath( scaledList[ i ] ) ); + + bool deleted = false; + // remove large, original image + if ( largeCoverDir().exists( key ) && QFile::remove( largeCoverDir().filePath( key ) ) ) + deleted = true; + + QString hardImage = findDirectoryImage( artist, album ); + debug() << "hardImage: " << hardImage << endl; + + if( !hardImage.isEmpty() ) + { + int id = MountPointManager::instance()->getIdForUrl( hardImage ); + QString rpath = MountPointManager::instance()->getRelativePath( id, hardImage ); + query( "DELETE FROM images WHERE path='" + escapeString( hardImage ) + "' AND deviceid = " + QString::number( id ) + ';' ); + deleted = true; + } + + if ( deleted ) + { + emit coverRemoved( artist, album ); + return true; + } + + return false; +} + + +bool +CollectionDB::removeAlbumImage( const uint artist_id, const uint album_id ) +{ + return removeAlbumImage( artistValue( artist_id ), albumValue( album_id ) ); +} + + +QString +CollectionDB::notAvailCover( const bool withShadow, int width ) +{ + if ( width <= 1 ) + width = AmarokConfig::coverPreviewSize(); + QString widthKey = QString::number( width ) + '@'; + QString s; + + if( cacheCoverDir().exists( widthKey + "nocover.png" ) ) + s = cacheCoverDir().filePath( widthKey + "nocover.png" ); + else + { + m_noCover.smoothScale( width, width, QImage::ScaleMin ).save( cacheCoverDir().filePath( widthKey + "nocover.png" ), "PNG" ); + s = cacheCoverDir().filePath( widthKey + "nocover.png" ); + } + + if ( withShadow ) + s = makeShadowedImage( s ); + + return s; +} + + +QStringList +CollectionDB::artistList( bool withUnknowns, bool withCompilations ) +{ + QueryBuilder qb; + qb.addReturnValue( QueryBuilder::tabArtist, QueryBuilder::valName ); + + if ( !withUnknowns ) + qb.excludeMatch( QueryBuilder::tabArtist, i18n( "Unknown" ) ); + if ( !withCompilations ) + qb.setOptions( QueryBuilder::optNoCompilations ); + + qb.groupBy( QueryBuilder::tabArtist, QueryBuilder::valName ); + qb.setOptions( QueryBuilder::optShowAll ); + qb.sortBy( QueryBuilder::tabArtist, QueryBuilder::valName ); + return qb.run(); +} + + +QStringList +CollectionDB::composerList( bool withUnknowns, bool withCompilations ) +{ + DEBUG_BLOCK + QueryBuilder qb; + qb.addReturnValue( QueryBuilder::tabComposer, QueryBuilder::valName ); + + if ( !withUnknowns ) + qb.excludeMatch( QueryBuilder::tabComposer, i18n( "Unknown" ) ); + if ( !withCompilations ) + qb.setOptions( QueryBuilder::optNoCompilations ); + + qb.groupBy( QueryBuilder::tabComposer, QueryBuilder::valName ); + qb.setOptions( QueryBuilder::optShowAll ); + qb.sortBy( QueryBuilder::tabComposer, QueryBuilder::valName ); + return qb.run(); +} + + +QStringList +CollectionDB::albumList( bool withUnknowns, bool withCompilations ) +{ + QueryBuilder qb; + qb.addReturnValue( QueryBuilder::tabAlbum, QueryBuilder::valName ); + + if ( !withUnknowns ) + qb.excludeMatch( QueryBuilder::tabAlbum, i18n( "Unknown" ) ); + if ( !withCompilations ) + qb.setOptions( QueryBuilder::optNoCompilations ); + + qb.groupBy( QueryBuilder::tabAlbum, QueryBuilder::valName ); + qb.setOptions( QueryBuilder::optShowAll ); + qb.sortBy( QueryBuilder::tabAlbum, QueryBuilder::valName ); + return qb.run(); +} + + +QStringList +CollectionDB::genreList( bool withUnknowns, bool withCompilations ) +{ + QueryBuilder qb; + qb.addReturnValue( QueryBuilder::tabGenre, QueryBuilder::valName ); + + //Only report genres that currently have at least one song + qb.addFilter( QueryBuilder::tabSong, "" ); + + if ( !withUnknowns ) + qb.excludeMatch( QueryBuilder::tabGenre, i18n( "Unknown" ) ); + if ( !withCompilations ) + qb.setOptions( QueryBuilder::optNoCompilations ); + + qb.groupBy( QueryBuilder::tabGenre, QueryBuilder::valName ); + qb.setOptions( QueryBuilder::optShowAll ); + qb.sortBy( QueryBuilder::tabGenre, QueryBuilder::valName ); + return qb.run(); +} + + +QStringList +CollectionDB::yearList( bool withUnknowns, bool withCompilations ) +{ + QueryBuilder qb; + qb.addReturnValue( QueryBuilder::tabYear, QueryBuilder::valName ); + + if ( !withUnknowns ) + qb.excludeMatch( QueryBuilder::tabYear, i18n( "Unknown" ) ); + if ( !withCompilations ) + qb.setOptions( QueryBuilder::optNoCompilations ); + + qb.groupBy( QueryBuilder::tabYear, QueryBuilder::valName ); + qb.setOptions( QueryBuilder::optShowAll ); + qb.sortBy( QueryBuilder::tabYear, QueryBuilder::valName ); + return qb.run(); +} + +QStringList +CollectionDB::labelList() +{ + QueryBuilder qb; + qb.addReturnValue( QueryBuilder::tabLabels, QueryBuilder::valName ); + qb.groupBy( QueryBuilder::tabLabels, QueryBuilder::valName ); + qb.setOptions( QueryBuilder::optShowAll ); + qb.sortBy( QueryBuilder::tabLabels, QueryBuilder::valName ); + return qb.run(); +} + +QStringList +CollectionDB::albumListOfArtist( const QString &artist, bool withUnknown, bool withCompilations ) +{ + if (getDbConnectionType() == DbConnection::postgresql) + { + return query( "SELECT DISTINCT album.name, lower( album.name ) AS __discard FROM tags, album, artist WHERE " + "tags.album = album.id AND tags.artist = artist.id " + "AND lower(artist.name) = lower('" + escapeString( artist ) + "') " + + ( withUnknown ? QString::null : "AND album.name <> '' " ) + + ( withCompilations ? QString::null : "AND tags.sampler = " + boolF() ) + deviceidSelection() + + " ORDER BY lower( album.name );" ); + } + // mysql is case insensitive and lower() is very slow + else if (getDbConnectionType() == DbConnection::mysql) + { + return query( "SELECT DISTINCT album.name FROM tags, album, artist WHERE " + "tags.album = album.id AND tags.artist = artist.id " + "AND artist.name = '" + escapeString( artist ) + "' " + + ( withUnknown ? QString::null : "AND album.name <> '' " ) + + ( withCompilations ? QString::null : "AND tags.sampler = " + boolF() ) + deviceidSelection() + + " ORDER BY album.name;" ); + } + else // sqlite + { + return query( "SELECT DISTINCT album.name FROM tags, album, artist WHERE " + "tags.album = album.id AND tags.artist = artist.id " + "AND lower(artist.name) = lower('" + escapeString( artist ) + "') " + + ( withUnknown ? QString::null : "AND album.name <> '' " ) + + ( withCompilations ? QString::null : "AND tags.sampler = " + boolF() ) + deviceidSelection() + + " ORDER BY lower( album.name );" ); + } +} + + +QStringList +CollectionDB::artistAlbumList( bool withUnknown, bool withCompilations ) +{ + if (getDbConnectionType() == DbConnection::postgresql) + { + return query( "SELECT DISTINCT artist.name, album.name, lower( album.name ) AS __discard FROM tags, album, artist WHERE " + "tags.album = album.id AND tags.artist = artist.id " + + ( withUnknown ? QString::null : "AND album.name <> '' AND artist.name <> '' " ) + + ( withCompilations ? QString::null : "AND tags.sampler = " + boolF() ) + deviceidSelection() + + " ORDER BY lower( album.name );" ); + } + else + { + return query( "SELECT DISTINCT artist.name, album.name FROM tags, album, artist WHERE " + "tags.album = album.id AND tags.artist = artist.id " + + ( withUnknown ? QString::null : "AND album.name <> '' AND artist.name <> '' " ) + + ( withCompilations ? QString::null : "AND tags.sampler = " + boolF() ) + deviceidSelection() + + " ORDER BY lower( album.name );" ); + } +} + +bool +CollectionDB::addPodcastChannel( const PodcastChannelBundle &pcb, const bool &replace ) +{ + QString command; + if( replace ) { + command = "REPLACE INTO podcastchannels " + "( url, title, weblink, image, comment, copyright, parent, directory" + ", autoscan, fetchtype, autotransfer, haspurge, purgecount ) " + "VALUES ("; + } else { + command = "INSERT INTO podcastchannels " + "( url, title, weblink, image, comment, copyright, parent, directory" + ", autoscan, fetchtype, autotransfer, haspurge, purgecount ) " + "VALUES ("; + } + + QString title = pcb.title(); + KURL link = pcb.link(); + KURL image = pcb.imageURL(); + QString description = pcb.description(); + QString copyright = pcb.copyright(); + + if( title.isEmpty() ) + title = pcb.url().prettyURL(); + + command += '\'' + escapeString( pcb.url().url() ) + "',"; + command += ( title.isEmpty() ? "NULL" : '\'' + escapeString( title ) + '\'' ) + ','; + command += ( link.isEmpty() ? "NULL" : '\'' + escapeString( link.url() ) + '\'' ) + ','; + command += ( image.isEmpty() ? "NULL" : '\'' + escapeString( image.url() ) + '\'' ) + ','; + command += ( description.isEmpty() ? "NULL" : '\'' + escapeString( description ) + '\'' ) + ','; + command += ( copyright.isEmpty() ? "NULL" : '\'' + escapeString( copyright ) + '\'' ) + ','; + command += QString::number( pcb.parentId() ) + ",'"; + command += escapeString( pcb.saveLocation() ) + "',"; + command += pcb.autoscan() ? boolT() + ',' : boolF() + ','; + command += QString::number( pcb.fetchType() ) + ','; + command += pcb.autotransfer() ? boolT() + ',' : boolF() + ','; + command += pcb.hasPurge() ? boolT() + ',' : boolF() + ','; + command += QString::number( pcb.purgeCount() ) + ");"; + + //FIXME: currently there's no way to check if an INSERT query failed or not - always return true atm. + // Now it might be possible as insert returns the rowid. + insert( command, NULL ); + return true; +} + +int +CollectionDB::addPodcastEpisode( const PodcastEpisodeBundle &episode, const int idToUpdate ) +{ + QString command; + + if( idToUpdate ) { + command = "REPLACE INTO podcastepisodes " + "( id, url, localurl, parent, title, subtitle, composer, comment, filetype, createdate, guid, length, size, isNew ) " + "VALUES ("; + } else { + command = "INSERT INTO podcastepisodes " + "( url, localurl, parent, title, subtitle, composer, comment, filetype, createdate, guid, length, size, isNew ) " + "VALUES ("; + } + + QString localurl = episode.localUrl().url(); + QString title = episode.title(); + QString subtitle = episode.subtitle(); + QString author = episode.author(); + QString description = episode.description(); + QString type = episode.type(); + QString date = episode.date(); + QString guid = episode.guid(); + int duration = episode.duration(); + uint size = episode.size(); + + if( title.isEmpty() ) + title = episode.url().prettyURL(); + + if( idToUpdate ) + command += QString::number( idToUpdate ) + ','; + + command += '\'' + escapeString( episode.url().url() ) + "',"; + command += ( localurl.isEmpty() ? "NULL" : '\'' + escapeString( localurl ) + '\'' ) + ','; + command += '\'' + escapeString( episode.parent().url()) + "',"; + command += ( title.isEmpty() ? "NULL" : '\'' + escapeString( title ) + '\'' ) + ','; + command += ( subtitle.isEmpty() ? "NULL" : '\'' + escapeString( subtitle ) + '\'' ) + ','; + command += ( author.isEmpty() ? "NULL" : '\'' + escapeString( author ) + '\'' ) + ','; + command += ( description.isEmpty() ? "NULL" : '\'' + escapeString( description ) + '\'' ) + ','; + command += ( type.isEmpty() ? "NULL" : '\'' + escapeString( type ) + '\'' ) + ','; + command += ( date.isEmpty() ? "NULL" : '\'' + escapeString( date ) + '\'' ) + ','; + command += ( guid.isEmpty() ? "NULL" : '\'' + escapeString( guid ) + '\'' ) + ','; + command += QString::number( duration ) + ','; + command += QString::number( size ) + ','; + command += episode.isNew() ? boolT() + " );" : boolF() + " );"; + + insert( command, NULL ); + + if( idToUpdate ) return idToUpdate; + //This is a bit of a hack. We have just inserted an item, so it is going to be the one with the + //highest id. Change this if threaded insertions are used in the future. + QStringList values = query( QString("SELECT id FROM podcastepisodes WHERE url='%1' ORDER BY id DESC;") + .arg( escapeString( episode.url().url() ) ) ); + if( values.isEmpty() ) return -1; + + return values[0].toInt(); +} + +QValueList +CollectionDB::getPodcastChannels() +{ + QString command = "SELECT url, title, weblink, image, comment, copyright, parent, directory " + ", autoscan, fetchtype, autotransfer, haspurge, purgecount FROM podcastchannels;"; + + QStringList values = query( command ); + QValueList bundles; + + foreach( values ) + { + PodcastChannelBundle pcb; + pcb.setURL ( KURL::fromPathOrURL(*it) ); + pcb.setTitle ( *++it ); + pcb.setLink ( KURL::fromPathOrURL(*++it) ); + pcb.setImageURL ( KURL::fromPathOrURL(*++it) ); + pcb.setDescription ( *++it ); + pcb.setCopyright ( *++it ); + pcb.setParentId ( (*++it).toInt() ); + pcb.setSaveLocation( *++it ); + pcb.setAutoScan ( boolFromSql( *++it ) ); + pcb.setFetchType ( (*++it).toInt() ); + pcb.setAutoTransfer( boolFromSql( *++it ) ); + pcb.setPurge ( boolFromSql( *++it ) ); + pcb.setPurgeCount ( (*++it).toInt() ); + + bundles.append( pcb ); + } + + return bundles; +} + +QValueList +CollectionDB::getPodcastEpisodes( const KURL &parent, bool onlyNew, int limit ) +{ + QString command = QString( "SELECT id, url, localurl, parent, guid, title, subtitle, composer, comment, filetype, createdate, length, size, isNew FROM podcastepisodes WHERE ( parent='%1'" ).arg( parent.url() ); + if( onlyNew ) + command += QString( " AND isNew='%1'" ).arg( boolT() ); + command += " ) ORDER BY id"; + if( limit != -1 ) + command += QString( " DESC LIMIT %1 OFFSET 0" ).arg( limit ); + command += ';'; + + QStringList values = query( command ); + QValueList bundles; + + foreach( values ) + { + PodcastEpisodeBundle peb; + peb.setDBId ( (*it).toInt() ); + peb.setURL ( KURL::fromPathOrURL(*++it) ); + if( *++it != "NULL" ) + peb.setLocalURL ( KURL::fromPathOrURL(*it) ); + peb.setParent ( KURL::fromPathOrURL(*++it) ); + peb.setGuid ( *++it ); + peb.setTitle ( *++it ); + if( *++it != NULL ) + peb.setSubtitle( *it ); + peb.setAuthor ( *++it ); + peb.setDescription ( *++it ); + peb.setType ( *++it ); + peb.setDate ( *++it ); + peb.setDuration ( (*++it).toInt() ); + if( *++it == NULL ) + peb.setSize ( 0 ); + else + peb.setSize ( (*it).toInt() ); + peb.setNew ( boolFromSql( *++it ) ); + + bundles.append( peb ); + } + + return bundles; +} + +PodcastEpisodeBundle +CollectionDB::getPodcastEpisodeById( int id ) +{ + QString command = QString( "SELECT url, localurl, parent, guid, title, subtitle, composer, comment, filetype, createdate, length, size, isNew FROM podcastepisodes WHERE id=%1;").arg( id ); + + QStringList values = query( command ); + PodcastEpisodeBundle peb; + foreach( values ) + { + peb.setDBId ( id ); + peb.setURL ( KURL::fromPathOrURL(*it) ); + if( *++it != "NULL" ) + peb.setLocalURL( KURL::fromPathOrURL(*it) ); + peb.setParent ( KURL::fromPathOrURL(*++it) ); + peb.setGuid ( *++it ); + peb.setTitle ( *++it ); + peb.setSubtitle ( *++it ); + peb.setAuthor ( *++it ); + peb.setDescription ( *++it ); + peb.setType ( *++it ); + peb.setDate ( *++it ); + peb.setDuration ( (*++it).toInt() ); + if( *++it == NULL ) + peb.setSize ( 0 ); + else + peb.setSize ( (*it).toInt() ); + peb.setNew ( boolFromSql( *++it ) ); + } + + return peb; +} + +bool +CollectionDB::getPodcastEpisodeBundle( const KURL &url, PodcastEpisodeBundle *peb ) +{ + int id = 0; + if( url.isLocalFile() ) + { + QStringList values = + query( QString( "SELECT id FROM podcastepisodes WHERE localurl = '%1';" ) + .arg( escapeString( url.url() ) ) ); + if( !values.isEmpty() ) + id = values[0].toInt(); + } + else + { + QStringList values = + query( QString( "SELECT id FROM podcastepisodes WHERE url = '%1';" ) + .arg( escapeString( url.url() ) ) ); + if( !values.isEmpty() ) + id = values[0].toInt(); + } + + if( id ) + { + *peb = getPodcastEpisodeById( id ); + return true; + } + + return false; +} + +bool +CollectionDB::getPodcastChannelBundle( const KURL &url, PodcastChannelBundle *pcb ) +{ + QStringList values = query( QString( + "SELECT url, title, weblink, image, comment, copyright, parent, directory " + ", autoscan, fetchtype, autotransfer, haspurge, purgecount FROM podcastchannels WHERE url = '%1';" + ).arg( escapeString( url.url() ) ) ); + + foreach( values ) + { + pcb->setURL ( KURL::fromPathOrURL(*it) ); + pcb->setTitle ( *++it ); + pcb->setLink ( KURL::fromPathOrURL(*++it) ); + if( *++it != "NULL" ) + pcb->setImageURL( KURL::fromPathOrURL(*it) ); + pcb->setDescription ( *++it ); + pcb->setCopyright ( *++it ); + pcb->setParentId ( (*++it).toInt() ); + pcb->setSaveLocation( *++it ); + pcb->setAutoScan ( boolFromSql( *++it ) ); + pcb->setFetchType ( (*++it).toInt() ); + pcb->setAutoTransfer( boolFromSql( *++it ) ); + pcb->setPurge ( boolFromSql( *++it ) ); + pcb->setPurgeCount ( (*++it).toInt() ); + } + + return !values.isEmpty(); +} + +// return newly created folder id +int +CollectionDB::addPodcastFolder( const QString &name, const int parent_id, const bool isOpen ) +{ + QString command = QString( "INSERT INTO podcastfolders ( name, parent, isOpen ) VALUES ('" ); + command += escapeString( name ) + "',"; + command += QString::number( parent_id ) + ","; + command += isOpen ? boolT() + ");" : boolF() + ");"; + + insert( command, NULL ); + + command = QString( "SELECT id FROM podcastfolders WHERE name = '%1' AND parent = '%2';" ) + .arg( name, QString::number(parent_id) ); + QStringList values = query( command ); + + return values[0].toInt(); +} + +void +CollectionDB::updatePodcastChannel( const PodcastChannelBundle &b ) +{ + if( getDbConnectionType() == DbConnection::postgresql ) + { + query( QStringx( "UPDATE podcastchannels SET title='%1', weblink='%2', comment='%3', " + "copyright='%4', parent=%5, directory='%6', autoscan=%7, fetchtype=%8, " + "autotransfer=%9, haspurge=%10, purgecount=%11 WHERE url='%12';" ) + .args ( QStringList() + << escapeString( b.title() ) + << escapeString( b.link().url() ) + << escapeString( b.description() ) + << escapeString( b.copyright() ) + << QString::number( b.parentId() ) + << escapeString( b.saveLocation() ) + << ( b.autoscan() ? boolT() : boolF() ) + << QString::number( b.fetchType() ) + << (b.hasPurge() ? boolT() : boolF() ) + << (b.autotransfer() ? boolT() : boolF() ) + << QString::number( b.purgeCount() ) + << escapeString( b.url().url() ) + ) + ); + } + else { + addPodcastChannel( b, true ); //replace the already existing row + } +} + +void +CollectionDB::updatePodcastEpisode( const int id, const PodcastEpisodeBundle &b ) +{ + if( getDbConnectionType() == DbConnection::postgresql ) + { + query( QStringx( "UPDATE podcastepisodes SET url='%1', localurl='%2', parent='%3', title='%4', subtitle='%5', composer='%6', comment='%7', " + "filetype='%8', createdate='%9', guid='%10', length=%11, size=%12, isNew=%13 WHERE id=%14;" ) + .args( QStringList() + << escapeString( b.url().url() ) + << ( b.localUrl().isValid() ? escapeString( b.localUrl().url() ) : "NULL" ) + << escapeString( b.parent().url() ) + << escapeString( b.title() ) + << escapeString( b.subtitle() ) + << escapeString( b.author() ) + << escapeString( b.description() ) + << escapeString( b.type() ) + << escapeString( b.date() ) + << escapeString( b.guid() ) + << QString::number( b.duration() ) + << escapeString( QString::number( b.size() ) ) + << ( b.isNew() ? boolT() : boolF() ) + << QString::number( id ) + ) + ); + } + else { + addPodcastEpisode( b, id ); + } +} + +void +CollectionDB::updatePodcastFolder( const int folder_id, const QString &name, const int parent_id, const bool isOpen ) +{ + if( getDbConnectionType() == DbConnection::postgresql ) { + query( QStringx( "UPDATE podcastfolders SET name='%1', parent=%2, isOpen=%3 WHERE id=%4;" ) + .args( QStringList() + << escapeString(name) + << QString::number(parent_id) + << ( isOpen ? boolT() : boolF() ) + << QString::number(folder_id) + ) + ); + } + else { + query( QStringx( "REPLACE INTO podcastfolders ( id, name, parent, isOpen ) " + "VALUES ( %1, '%2', %3, %4 );" ) + .args( QStringList() + << QString::number(folder_id) + << escapeString(name) + << QString::number(parent_id) + << ( isOpen ? boolT() : boolF() ) + ) + ); + } +} + +void +CollectionDB::removePodcastChannel( const KURL &url ) +{ + //remove channel + query( QString( "DELETE FROM podcastchannels WHERE url = '%1';" ) + .arg( escapeString( url.url() ) ) ); + //remove all children + query( QString( "DELETE FROM podcastepisodes WHERE parent = '%1';" ) + .arg( escapeString( url.url() ) ) ); +} + + +/// Try not to delete by url, since some podcast feeds have all the same url +void +CollectionDB::removePodcastEpisode( const int id ) +{ + if( id < 0 ) return; + query( QString( "DELETE FROM podcastepisodes WHERE id = '%1';" ) + .arg( QString::number(id) ) ); +} + +void +CollectionDB::removePodcastFolder( const int id ) +{ + if( id < 0 ) return; + query( QString("DELETE FROM podcastfolders WHERE id=%1;") + .arg( QString::number(id) ) ); +} + +bool +CollectionDB::addSong( MetaBundle* bundle, const bool incremental ) +{ + if ( !QFileInfo( bundle->url().path() ).isReadable() ) return false; + + QString command = "INSERT INTO tags_temp " + "( url, dir, deviceid, createdate, modifydate, album, artist, composer, genre, year, title, " + "comment, track, discnumber, bpm, sampler, length, bitrate, " + "samplerate, filesize, filetype ) " + "VALUES ('"; + + QString artist = bundle->artist(); + QString title = bundle->title(); + if ( title.isEmpty() ) + { + title = bundle->url().fileName(); + if ( bundle->url().fileName().find( '-' ) > 0 ) + { + if ( artist.isEmpty() ) + { + artist = bundle->url().fileName().section( '-', 0, 0 ).stripWhiteSpace(); + bundle->setArtist( artist ); + } + title = bundle->url().fileName().section( '-', 1 ).stripWhiteSpace(); + title = title.left( title.findRev( '.' ) ).stripWhiteSpace(); + if ( title.isEmpty() ) title = bundle->url().fileName(); + } + bundle->setTitle( title ); + } + + int deviceId = MountPointManager::instance()->getIdForUrl( bundle->url() ); + KURL relativePath; + MountPointManager::instance()->getRelativePath( deviceId, bundle->url(), relativePath ); + //debug() << "File has deviceId " << deviceId << ", relative path " << relativePath.path() << ", absolute path " << bundle->url().path() << endl; + + command += escapeString( relativePath.path() ) + "','"; + command += escapeString( relativePath.directory() ) + "',"; + command += QString::number( deviceId ) + ','; + command += QString::number( QFileInfo( bundle->url().path() ).created().toTime_t() ) + ','; + command += QString::number( QFileInfo( bundle->url().path() ).lastModified().toTime_t() ) + ','; + + command += escapeString( QString::number( albumID( bundle->album(), true, !incremental, true ) ) ) + ','; + command += escapeString( QString::number( artistID( bundle->artist(), true, !incremental, true ) ) ) + ','; + command += escapeString( QString::number( composerID( bundle->composer(), true, !incremental, true ) ) ) + ','; + command += escapeString( QString::number( genreID( bundle->genre(), true, !incremental, true ) ) ) + ",'"; + command += escapeString( QString::number( yearID( QString::number( bundle->year() ), true, !incremental, true ) ) ) + "','"; + + command += escapeString( bundle->title() ) + "','"; + command += escapeString( bundle->comment() ) + "', "; + command += escapeString( QString::number( bundle->track() ) ) + " , "; + command += escapeString( QString::number( bundle->discNumber() ) ) + " , "; + command += escapeString( QString::number( bundle->bpm() ) ) + " , "; + switch( bundle->compilation() ) { + case MetaBundle::CompilationNo: + command += boolF(); + break; + + case MetaBundle::CompilationYes: + command += boolT(); + break; + + case MetaBundle::CompilationUnknown: + default: + command += "NULL"; + } + command += ','; + + // NOTE any of these may be -1 or -2, this is what we want + // see MetaBundle::Undetermined + command += QString::number( bundle->length() ) + ','; + command += QString::number( bundle->bitrate() ) + ','; + command += QString::number( bundle->sampleRate() ) + ','; + command += QString::number( bundle->filesize() ) + ','; + command += QString::number( bundle->fileType() ) + ')'; + + //FIXME: currently there's no way to check if an INSERT query failed or not - always return true atm. + // Now it might be possible as insert returns the rowid. + insert( command, NULL ); + + doAFTStuff( bundle, true ); + + return true; +} + +void +CollectionDB::doAFTStuff( MetaBundle* bundle, const bool tempTables ) +{ + if( bundle->uniqueId().isEmpty() || bundle->url().path().isEmpty() ) + return; + + MountPointManager *mpm = MountPointManager::instance(); + //const to make sure one isn't later modified without the other being changed + const int deviceIdInt = mpm->getIdForUrl( bundle->url().path() ); + const QString currdeviceid = QString::number( deviceIdInt ); + QString currid = escapeString( bundle->uniqueId() ); + QString currurl = escapeString( mpm->getRelativePath( deviceIdInt, bundle->url().path() ) ); + QString currdir = escapeString( mpm->getRelativePath( deviceIdInt, bundle->url().directory() ) ); + //debug() << "Checking currid = " << currid << ", currdir = " << currdir << ", currurl = " << currurl << endl; + //debug() << "tempTables = " << (tempTables?"true":"false") << endl; + + + QStringList urls = query( QString( + "SELECT url, uniqueid " + "FROM uniqueid%1 " + "WHERE deviceid = %2 AND url = '%3';" ) + .arg( tempTables ? "_temp" : "" + , currdeviceid + , currurl ) ); + + QStringList uniqueids = query( QString( + "SELECT url, uniqueid, deviceid " + "FROM uniqueid%1 " + "WHERE uniqueid = '%2';" ) + .arg( tempTables ? "_temp" : "" + , currid ) ); + + QStringList nonTempIDs = query( QString( + "SELECT url, uniqueid, deviceid " + "FROM uniqueid " + "WHERE uniqueid = '%1';" ) + .arg( currid ) ); + + QStringList nonTempURLs = query( QString( + "SELECT url, uniqueid " + "FROM uniqueid " + "WHERE deviceid = %1 AND url = '%2';" ) + .arg( currdeviceid + , currurl ) ); + + bool tempTablesAndInPermanent = false; + bool permanentFullMatch = false; + + //if we're not using temp tables here, i.e. tempTables is false, + //then the results from both sets of queries above should be equal, + //so behavior should be the same + if( tempTables && ( nonTempURLs.count() > 0 || nonTempIDs.count() > 0 ) ) + tempTablesAndInPermanent = true; + if( tempTablesAndInPermanent && nonTempURLs.count() > 0 && nonTempIDs.count() > 0 ) + permanentFullMatch = true; + + //debug() << "tempTablesAndInPermanent = " << (tempTablesAndInPermanent?"true":"false") << endl; + //debug() << "permanentFullMatch = " << (permanentFullMatch?"true":"false") << endl; + + //debug() << "Entering checks" << endl; + //first case: not in permanent table or temporary table + if( !tempTablesAndInPermanent && urls.empty() && uniqueids.empty() ) + { + //debug() << "first case" << endl; + QString insertline = QStringx( "INSERT INTO uniqueid%1 (deviceid, url, uniqueid, dir) " + "VALUES ( %2,'%3', '%4', '%5');" ) + .args( QStringList() + << ( tempTables ? "_temp" : "" ) + << currdeviceid + << currurl + << currid + << currdir ); + insert( insertline, NULL ); + //debug() << "aftCheckPermanentTables #1" << endl; + aftCheckPermanentTables( currdeviceid, currid, currurl ); + return; + } + + //next case: not in permanent table, but a match on one or the other in the temporary table + //OR, we are using permanent tables (and not considering temp ones) + if( !tempTablesAndInPermanent ) + { + if( urls.empty() ) //uniqueid already found in temporary table but not url; check the old URL + { + //stat the original URL + QString absPath = mpm->getAbsolutePath( uniqueids[2].toInt(), uniqueids[0] ); + //debug() << "At doAFTStuff, stat-ing file " << absPath << endl; + bool statSuccessful = false; + bool pathsSame = absPath == bundle->url().path(); + if( !pathsSame ) + statSuccessful = QFile::exists( absPath ); + if( statSuccessful ) //if true, new one is a copy + warning() << "Already-scanned file at " << absPath << " has same UID as new file at " << bundle->url().path() << endl; + else //it's a move, not a copy, or a copy and then both files were moved...can't detect that + { + //debug() << "stat was NOT successful, updating tables with: " << endl; + //debug() << QString( "UPDATE uniqueid%1 SET url='%2', dir='%3' WHERE uniqueid='%4';" ).arg( ( tempTables ? "_temp" : "" ), currurl, currdir, currid ) << endl; + query( QStringx( "UPDATE uniqueid%1 SET deviceid = %2, url='%3', dir='%4' WHERE uniqueid='%5';" ) + .args( QStringList() + << ( tempTables ? "_temp" : "" ) + << currdeviceid + << currurl + << currdir + << currid ) ); + if( !pathsSame ) + emit fileMoved( absPath, bundle->url().path(), bundle->uniqueId() ); + } + } + //okay then, url already found in temporary table but different uniqueid + //a file exists in the same place as before, but new uniqueid...assume + //that this is desired user behavior + //NOTE: this should never happen during an incremental scan with temporary tables...! + else if( uniqueids.empty() ) + { + //debug() << "file exists in same place as before, new uniqueid" << endl; + query( QString( "UPDATE uniqueid%1 SET uniqueid='%2' WHERE deviceid = %3 AND url='%4';" ) + .arg( tempTables ? "_temp" : "" + , currid + , currdeviceid + , currurl ) ); + emit uniqueIdChanged( bundle->url().path(), urls[1], bundle->uniqueId() ); + } + //else uniqueid and url match; nothing happened, so safely exit + return; + } + //okay...being here means, we are using temporary tables, AND it exists in the permanent table + else + { + //first case...full match exists in permanent table, should then be no match in temp table + //(since code below deleted from permanent table after changes) + //in this case, just insert into temp table + if( permanentFullMatch ) + { + QString insertline = QString( "INSERT INTO uniqueid_temp (deviceid, url, uniqueid, dir) " + "VALUES ( %1, '%2'" ) + .arg( currdeviceid + , currurl ); + insertline += QString( ", '%1', '%2');" ).arg( currid ).arg( currdir ); + //debug() << "running command: " << insertline << endl; + insert( insertline, NULL ); + //debug() << "aftCheckPermanentTables #2" << endl; + aftCheckPermanentTables( currdeviceid, currid, currurl ); + return; + } + + //second case...full match exists in permanent table, but path is different + if( nonTempURLs.empty() ) + { + //stat the original URL + QString absPath = mpm->getAbsolutePath( nonTempIDs[2].toInt(), nonTempIDs[0] ); + //debug() << "At doAFTStuff part 2, stat-ing file " << absPath << endl; + bool statSuccessful = false; + bool pathsSame = absPath == bundle->url().path(); + if( !pathsSame ) + statSuccessful = QFile::exists( absPath ); + if( statSuccessful ) //if true, new one is a copy + warning() << "Already-scanned file at " << absPath << " has same UID as new file at " << currurl << endl; + else //it's a move, not a copy, or a copy and then both files were moved...can't detect that + { + //debug() << "stat part 2 was NOT successful, updating tables with: " << endl; + query( QString( "DELETE FROM uniqueid WHERE uniqueid='%1';" ) + .arg( currid ) ); + query( QString( "INSERT INTO uniqueid_temp (deviceid, url, uniqueid, dir) " + "VALUES ( %1, '%2', '%3', '%4')" ) + .arg( currdeviceid + , currurl + , currid + , currdir ) ); + if( !pathsSame ) + emit fileMoved( absPath, bundle->url().path(), bundle->uniqueId() ); + } + } + else if( nonTempIDs.empty() ) + { + //debug() << "file exists in same place as before, part 2, new uniqueid" << endl; + query( QString( "DELETE FROM uniqueid WHERE deviceid = %1 AND url='%2';" ) + .arg( currdeviceid ) + .arg( currurl ) ); + query( QString( "INSERT INTO uniqueid_temp (deviceid, url, uniqueid, dir) VALUES ( %1, '%2', '%3', '%4')" ) + .arg( currdeviceid + , currurl + , currid + , currdir ) ); + emit uniqueIdChanged( bundle->url().path(), nonTempURLs[1], bundle->uniqueId() ); + } + //else do nothing...really this case should never happen + return; + } +} + +void +CollectionDB::emitFileDeleted( const QString &absPath, const QString &uniqueid ) +{ + if( uniqueid.isEmpty() ) + emit fileDeleted( absPath ); + else + emit fileDeleted( absPath, uniqueid ); +} + +void +CollectionDB::emitFileAdded( const QString &absPath, const QString &uniqueid ) +{ + if( uniqueid.isEmpty() ) + emit fileAdded( absPath ); + else + emit fileAdded( absPath, uniqueid ); +} + +QString +CollectionDB::urlFromUniqueId( const QString &id ) +{ + bool scanning = ( ScanController::instance() && ScanController::instance()->tablesCreated() ); + QStringList urls = query( QString( + "SELECT deviceid, url " + "FROM uniqueid%1 " + "WHERE uniqueid = '%2';" ) + .arg( scanning ? "_temp" : QString::null ) + .arg( id ), true ); + + if( urls.empty() && scanning ) + urls = query( QString( + "SELECT deviceid, url " + "FROM uniqueid " + "WHERE uniqueid = '%1';" ) + .arg( id ) ); + + if( urls.empty() ) + return QString(); + + return MountPointManager::instance()->getAbsolutePath( urls[0].toInt(), urls[1] ); +} + +QString +CollectionDB::uniqueIdFromUrl( const KURL &url ) +{ + MountPointManager *mpm = MountPointManager::instance(); + int currdeviceid = mpm->getIdForUrl( url.path() ); + QString currurl = escapeString( mpm->getRelativePath( currdeviceid, url.path() ) ); + + bool scanning = ( ScanController::instance() && ScanController::instance()->tablesCreated() ); + QStringList uid = query( QString( + "SELECT uniqueid " + "FROM uniqueid%1 " + "WHERE deviceid = %2 AND url = '%3';" ) + .arg( scanning ? "_temp" : QString::null ) + .arg( currdeviceid ) + .arg( currurl ), true ); + + if( uid.empty() && scanning ) + uid = query( QString( + "SELECT uniqueid " + "FROM uniqueid " + "WHERE deviceid = %1 AND url = '%2';" ) + .arg( currdeviceid ) + .arg( currurl ) ); + + if( uid.empty() ) + return QString(); + + return uid[0]; +} + +QString +CollectionDB::getURL( const MetaBundle &bundle ) +{ + uint artID = artistID( bundle.artist(), false ); + if( !artID ) + return QString(); + + uint albID = albumID( bundle.album(), false ); + if( !albID ) + return QString(); + + QString q = QString( "SELECT tags.deviceid, tags.url " + "FROM tags " + "WHERE tags.album = '%1' AND tags.artist = '%2' AND tags.track = '%3' AND tags.title = '%4'" + + deviceidSelection() + ';' ) + .arg( albID ) + .arg( artID ) + .arg( bundle.track() ) + .arg( escapeString( bundle.title() ) ); + + QStringList urls = URLsFromQuery( query( q ) ); + + if( urls.empty() ) + return QString(); + + if( urls.size() == 1 ) + { + return urls.first(); + } + + QString url = urls.first(); + int maxPlayed = -1; + for( QStringList::iterator it = urls.begin(); + it != urls.end(); + it++ ) + { + int pc = getPlayCount( *it ); + if( pc > maxPlayed ) + { + maxPlayed = pc; + url = *it; + } + } + + return url; +} + +// Helper function to convert the "tags.sampler" column to a MetaBundle::Collection value +// +// We use the first char of boolT / boolF as not all DBs store true/false as +// numerics (and it's only a single-char column) +static int +samplerToCompilation( const QString &it ) +{ + if( it == CollectionDB::instance()->boolT().mid( 0, 1 ) ) + { + return MetaBundle::CompilationYes; + } + else if( it == CollectionDB::instance()->boolF().mid( 0, 1 ) ) + { + return MetaBundle::CompilationNo; + } + return MetaBundle::CompilationUnknown; +} + +MetaBundle +CollectionDB::bundleFromQuery( QStringList::const_iterator *iter ) +{ + QStringList::const_iterator &it = *iter; + MetaBundle b; + //QueryBuilder automatically inserts the deviceid as return value if asked for the path + QString rpath = *it; + int deviceid = (*++it).toInt(); + b.setPath ( MountPointManager::instance()->getAbsolutePath( deviceid, rpath ) ); + b.setAlbum ( *++it ); + b.setArtist ( *++it ); + b.setComposer ( *++it ); + b.setGenre ( *++it ); + b.setTitle ( *++it ); + b.setYear ( (*++it).toInt() ); + b.setComment ( *++it ); + b.setTrack ( (*++it).toInt() ); + b.setBitrate ( (*++it).toInt() ); + b.setDiscNumber( (*++it).toInt() ); + b.setLength ( (*++it).toInt() ); + b.setSampleRate( (*++it).toInt() ); + b.setFilesize ( (*++it).toInt() ); + + b.setCompilation( samplerToCompilation( *it ) ); + ++it; + b.setFileType( (*++it).toInt() ); + b.setBpm ( (*++it).toFloat() ); + + b.setScore ( (*++it).toFloat() ); + b.setRating ( (*++it).toInt() ); + b.setPlayCount ( (*++it).toInt() ); + b.setLastPlay ( (*++it).toInt() ); + + if( false && b.length() <= 0 ) { + // we try to read the tags, despite the slow-down + debug() << "Audioproperties not known for: " << b.url().fileName() << endl; + b.readTags( TagLib::AudioProperties::Fast); + } + + return b; +} + +static void +fillInBundle( QStringList values, MetaBundle &bundle ) +{ + //TODO use this whenever possible + + // crash prevention + while( values.count() < 16 ) + values += "IF YOU CAN SEE THIS THERE IS A BUG!"; + + QStringList::ConstIterator it = values.begin(); + + bundle.setAlbum ( *it ); ++it; + bundle.setArtist ( *it ); ++it; + bundle.setComposer ( *it ); ++it; + bundle.setGenre ( *it ); ++it; + bundle.setTitle ( *it ); ++it; + bundle.setYear ( (*it).toInt() ); ++it; + bundle.setComment ( *it ); ++it; + bundle.setDiscNumber( (*it).toInt() ); ++it; + bundle.setTrack ( (*it).toInt() ); ++it; + bundle.setBitrate ( (*it).toInt() ); ++it; + bundle.setLength ( (*it).toInt() ); ++it; + bundle.setSampleRate( (*it).toInt() ); ++it; + bundle.setFilesize ( (*it).toInt() ); ++it; + bundle.setFileType ( (*it).toInt() ); ++it; + bundle.setBpm ( (*it).toFloat() ); ++it; + + bundle.setCompilation( samplerToCompilation( *it ) ); + ++it; + + bundle.setUniqueId(*it); +} + +bool +CollectionDB::bundleForUrl( MetaBundle* bundle ) +{ + int deviceid = MountPointManager::instance()->getIdForUrl( bundle->url() ); + KURL rpath; + MountPointManager::instance()->getRelativePath( deviceid, bundle->url(), rpath ); + QStringList values = query( QString( + "SELECT album.name, artist.name, composer.name, genre.name, tags.title, " + "year.name, tags.comment, tags.discnumber, " + "tags.track, tags.bitrate, tags.length, tags.samplerate, " + "tags.filesize, tags.filetype, tags.bpm, tags.sampler, uniqueid.uniqueid " + "FROM tags LEFT OUTER JOIN uniqueid ON tags.url = uniqueid.url AND tags.deviceid = uniqueid.deviceid," + "album, artist, composer, genre, year " + "WHERE album.id = tags.album AND artist.id = tags.artist AND composer.id = tags.composer AND " + "genre.id = tags.genre AND year.id = tags.year AND tags.url = '%2' AND tags.deviceid = %1;" ) + .arg( deviceid ) + .arg( escapeString( rpath.path( ) ) ) ); + + bool valid = false; + + if ( !values.empty() ) + { + fillInBundle( values, *bundle ); + valid = true; + } + else if( MediaBrowser::instance() && MediaBrowser::instance()->getBundle( bundle->url(), bundle ) ) + { + valid = true; + } + else + { + // check if it's a podcast + PodcastEpisodeBundle peb; + if( getPodcastEpisodeBundle( bundle->url(), &peb ) ) + { + if( bundle->url().protocol() == "file" && QFile::exists( bundle->url().path() ) ) + { + MetaBundle mb( bundle->url(), true /* avoid infinite recursion */ ); + *bundle = mb; + } + bundle->copyFrom( peb ); + valid = true; + } + } + + return valid; +} + + +QValueList +CollectionDB::bundlesByUrls( const KURL::List& urls ) +{ + BundleList bundles; + QStringList paths; + QueryBuilder qb; + + for( KURL::List::ConstIterator it = urls.begin(), end = urls.end(), last = urls.fromLast(); it != end; ++it ) + { + // non file stuff won't exist in the db, but we still need to + // re-insert it into the list we return, just with no tags assigned + paths += (*it).protocol() == "file" ? (*it).path() : (*it).url(); + + if( paths.count() == 50 || it == last ) + { + qb.clear(); + + qb.addReturnValue( QueryBuilder::tabAlbum, QueryBuilder::valName ); + qb.addReturnValue( QueryBuilder::tabArtist, QueryBuilder::valName ); + qb.addReturnValue( QueryBuilder::tabComposer, QueryBuilder::valName ); + qb.addReturnValue( QueryBuilder::tabGenre, QueryBuilder::valName ); + qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valTitle ); + qb.addReturnValue( QueryBuilder::tabYear, QueryBuilder::valName ); + qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valComment ); + qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valTrack ); + qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valBitrate ); + qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valDiscNumber ); + qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valLength ); + qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valSamplerate ); + qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valFilesize ); + qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valFileType ); + qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valBPM ); + qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valURL ); + qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valIsCompilation ); + + qb.addURLFilters( paths ); + qb.setOptions( QueryBuilder::optRemoveDuplicates ); + + const QStringList values = qb.run(); + + BundleList buns50; + MetaBundle b; + foreach( values ) + { + b.setAlbum ( *it ); + b.setArtist ( *++it ); + b.setComposer ( *++it ); + b.setGenre ( *++it ); + b.setTitle ( *++it ); + b.setYear ( (*++it).toInt() ); + b.setComment ( *++it ); + b.setTrack ( (*++it).toInt() ); + b.setBitrate ( (*++it).toInt() ); + b.setDiscNumber( (*++it).toInt() ); + b.setLength ( (*++it).toInt() ); + b.setSampleRate( (*++it).toInt() ); + b.setFilesize ( (*++it).toInt() ); + b.setFileType ( (*++it).toInt() ); + b.setBpm ( (*++it).toFloat() ); + b.setPath ( *++it ); + + b.setCompilation( samplerToCompilation( *it ) ); + ++it; + + b.checkExists(); + + buns50.append( b ); + } + + // we get no guarantee about the order that the database + // will return our values, and sqlite indeed doesn't return + // them in the desired order :( (MySQL does though) + foreach( paths ) + { + for( BundleList::Iterator jt = buns50.begin(), end = buns50.end(); jt != end; ++jt ) + { + if ( ( *jt ).url().path() == ( *it ) ) + { + bundles += *jt; + buns50.remove( jt ); + goto success; + } + } + + // if we get here, we didn't find an entry + { + KURL url = KURL::fromPathOrURL( *it ); + + if( !MediaBrowser::instance()->getBundle( url, &b ) ) + { + if( url.isLocalFile() ) + { + b = MetaBundle( url ); + } + else + { + b = MetaBundle(); + b.setUrl( url ); + // FIXME: more context for i18n after string freeze + b.setTitle( QString( "%1 %2 %3%4" ) + .arg( url.filename(), + i18n( "from" ), + url.hasHost() ? url.host() : QString(), + url.directory( false ) ) ); + } + + // check if it's a podcast + PodcastEpisodeBundle peb; + if( getPodcastEpisodeBundle( url, &peb ) ) + { + b.copyFrom( peb ); + } + else if( b.url().protocol() == "audiocd" || b.url().protocol() == "cdda" ) + { + // try to see if the engine has some info about the + // item (the intended behaviour should be that if the + // item is an AudioCD track, the engine can return + // CDDB data for it) + Engine::SimpleMetaBundle smb; + if ( EngineController::engine()->metaDataForUrl( b.url(), smb ) ) + { + b.setTitle( smb.title ); + b.setArtist( smb.artist ); + b.setAlbum( smb.album ); + b.setComment( smb.comment ); + b.setGenre( smb.genre ); + b.setBitrate( smb.bitrate.toInt() ); + b.setSampleRate( smb.samplerate.toInt() ); + b.setLength( smb.length.toInt() ); + b.setYear( smb.year.toInt() ); + b.setTrack( smb.tracknr.toInt() ); + } + } + + } + } + bundles += b; + +success: ; + } + + paths.clear(); + } + } + + return bundles; +} + + +void +CollectionDB::addAudioproperties( const MetaBundle& bundle ) +{ + int deviceid = MountPointManager::instance()->getIdForUrl( bundle.url() ); + KURL rpath; + MountPointManager::instance()->getRelativePath( deviceid, bundle.url(), rpath ); + query( QString( "UPDATE tags SET bitrate='%1', length='%2', samplerate='%3' WHERE url='%5' AND deviceid = %4;" ) + .arg( bundle.bitrate() ) + .arg( bundle.length() ) + .arg( bundle.sampleRate() ) + .arg( deviceid ) + .arg( escapeString( rpath.path() ) ) ); +} + + +void +CollectionDB::addSongPercentage( const QString &url, float percentage, + const QString &reason, const QDateTime *playtime ) +{ + //the URL must always be inserted last! an escaped URL can contain Strings like %1->bug + int deviceid = MountPointManager::instance()->getIdForUrl( url ); + QString rpath = MountPointManager::instance()->getRelativePath( deviceid, url ); + //statistics table might not have those values, but we need them later, so keep them + int statDevId = deviceid; + QString statRPath = rpath; + QStringList values = + query( QString( + "SELECT playcounter, createdate, percentage, rating FROM statistics " + "WHERE url = '%2' AND deviceid = %1;" ) + .arg( statDevId ).arg( escapeString( statRPath ) ) ); + + //handle corner case: deviceid!=-1 but there is a statistics row for that song with deviceid -1 + if ( values.isEmpty() ) + { + QString rpath2 = '.' + url; + values = query( QString( + "SELECT playcounter, createdate, percentage, rating FROM statistics " + "WHERE url = '%1' AND deviceid = -1;" ) + .arg( escapeString( rpath2 ) ) ); + if ( !values.isEmpty() ) + { + statRPath = rpath2; + statDevId = -1; + } + } + + uint atime = playtime ? playtime->toTime_t() : QDateTime::currentDateTime().toTime_t(); + + // check boundaries + if ( percentage > 100.f ) percentage = 100.f; + if ( percentage < 1.f ) percentage = 1.f; + + if ( !values.isEmpty() ) + { + + // increment playcounter and update accesstime + query( QString( "UPDATE statistics SET playcounter=%1, accessdate=%2 WHERE url='%4' AND deviceid= %3;" ) + .arg( values[0] + " + 1" ) + .arg( atime ) + .arg( statDevId ) + .arg( escapeString( statRPath ) ) ); + } + else + { + insert( QString( "INSERT INTO statistics ( url, deviceid, createdate, accessdate, percentage, playcounter, rating, uniqueid, deleted ) " + "VALUES ( '%6', %5, %1, %2, 0, 1, 0, %3, %4 );" ) + .arg( atime ) + .arg( atime ) + .arg( ( getUniqueId( url ).isNull() ? "NULL" : '\'' + escapeString( getUniqueId( url ) ) + '\'' ) ) + .arg( boolF() ) + .arg( statDevId ) + .arg( escapeString( statRPath ) ), 0 ); + } + + double prevscore = 50; + int playcount = 0; + if( !values.isEmpty() ) + { + playcount = values[ 0 ].toInt(); + // This stops setting the Rating (which creates a row) from affecting the + // prevscore of an unplayed track. See bug 127475 + if ( playcount ) + prevscore = values[ 2 ].toDouble(); + } + const QStringList v = query( QString( "SELECT length FROM tags WHERE url = '%2' AND deviceid = %1;" ) + .arg( deviceid ).arg( escapeString( rpath ) ) ); + const int length = v.isEmpty() ? 0 : v.first().toInt(); + + ScriptManager::instance()->requestNewScore( url, prevscore, playcount, length, percentage, reason ); +} + + +float +CollectionDB::getSongPercentage( const QString &url ) +{ + QueryBuilder qb; + qb.addReturnValue( QueryBuilder::tabStats, QueryBuilder::valScore ); + qb.addMatch( QueryBuilder::tabStats, QueryBuilder::valURL, url ); + + QStringList values = qb.run(); + + if( !values.isEmpty() ) + return values.first().toFloat(); + + return 0; +} + +int +CollectionDB::getSongRating( const QString &url ) +{ + QueryBuilder qb; + qb.addReturnValue( QueryBuilder::tabStats, QueryBuilder::valRating ); + qb.addMatch( QueryBuilder::tabStats, QueryBuilder::valURL, url ); + + QStringList values = qb.run(); + + if( values.count() ) + return kClamp( values.first().toInt(), 0, 10 ); + + return 0; +} + +void +CollectionDB::setSongPercentage( const QString &url , float percentage) +{ + int deviceid = MountPointManager::instance()->getIdForUrl( url ); + QString rpath = MountPointManager::instance()->getRelativePath( deviceid, url ); + QStringList values = + query( QString( + "SELECT playcounter, createdate, accessdate, rating FROM statistics WHERE url = '%2' AND deviceid = %1;" ) + .arg( deviceid ).arg( escapeString( rpath ) ) ); + + //handle corner case: deviceid!=-1 but there is a statistics row for that song with deviceid -1 + if ( values.isEmpty() ) + { + QString rpath2 = '.' + url; + values = query( QString( + "SELECT playcounter, createdate, accessdate, rating FROM statistics " + "WHERE url = '%1' AND deviceid = -1;" ) + .arg( escapeString( rpath2 ) ) ); + if ( !values.isEmpty() ) + { + rpath = rpath2; + deviceid = -1; + } + } + + // check boundaries + if ( percentage > 100.f ) percentage = 100.f; + if ( percentage < 0.f ) percentage = 0.f; + + if ( !values.isEmpty() ) + { + query( QString( "UPDATE statistics SET percentage=%1 WHERE url='%3' AND deviceid = %2;" ) + .arg( percentage ) + .arg( deviceid ).arg( escapeString( rpath ) ) ); + } + else + { + insert( QString( "INSERT INTO statistics ( url, deviceid, createdate, accessdate, percentage, playcounter, rating, uniqueid, deleted ) " + "VALUES ( '%7', %6, %2, %3, %1, 0, 0, %3, %4 );" ) + .arg( percentage ) + .arg( QDateTime::currentDateTime().toTime_t() ) + .arg( 0 ) + .arg( ( getUniqueId( url ).isNull() ? "NULL" : '\'' + escapeString( getUniqueId( url ) ) + '\'' ) ) + .arg( boolF() ) + .arg( deviceid ) + .arg( escapeString( rpath ) ),0 ); + } + + emit scoreChanged( url, percentage ); +} + +void +CollectionDB::setSongRating( const QString &url, int rating, bool toggleHalf ) +{ + int deviceid = MountPointManager::instance()->getIdForUrl( url ); + QString rpath = MountPointManager::instance()->getRelativePath( deviceid, url ); + QStringList values = + query( QString( + "SELECT playcounter, createdate, accessdate, percentage, rating FROM statistics WHERE url = '%2' AND deviceid = %1;" ) + .arg( deviceid ) + .arg( escapeString( rpath ) ) ); + + //handle corner case: deviceid!=-1 but there is a statistics row for that song with deviceid -1 + if( values.isEmpty() ) + { + QString rpath2 = '.' + url; + values = query( QString( + "SELECT playcounter, createdate, accessdate, percentage, rating FROM statistics " + "WHERE url = '%1' AND deviceid = -1;" ) + .arg( escapeString( rpath2 ) ) ); + if ( !values.isEmpty() ) + { + rpath = rpath2; + deviceid = -1; + } + } + + bool ok = true; + if( !values.isEmpty() ) + { + int prev = values[4].toInt( &ok ); + if( ok && toggleHalf && ( prev == rating || ( prev == 1 && rating == 2 ) ) ) + { + if( prev == 1 && rating == 2 ) + rating = 0; + else if( rating % 2 ) //.5 + rating++; + else + rating--; + } + } + + // check boundaries + if ( rating > 10 ) rating = 10; + if ( rating < 0 /*|| rating == 1*/ ) rating = 0; //ratings are 1-5 + + if ( !values.isEmpty() ) + { + query( QString( "UPDATE statistics SET rating=%1 WHERE url='%3' AND deviceid = %2;" ) + .arg( rating ) + .arg( deviceid ) + .arg( escapeString( rpath ) ) ); + } + else + { + insert( QString( "INSERT INTO statistics ( url, deviceid, createdate, accessdate, percentage, rating, playcounter, uniqueid, deleted ) " + "VALUES ( '%7', %6, %2, %3, 0, %1, 0, %4, %5 );" ) + .arg( rating ) + .arg( QDateTime::currentDateTime().toTime_t() ) + .arg( 0 ) + .arg( ( getUniqueId( url ).isNull() ? "NULL" : '\'' + escapeString( getUniqueId( url ) ) + '\'' ) ) + .arg( boolF() ) + .arg( deviceid ) + .arg( escapeString( rpath ) ), NULL ); + } + + emit ratingChanged( url, rating ); +} + +int +CollectionDB::getPlayCount( const QString &url ) +{ + //queryBuilder is good + QueryBuilder qb; + qb.addReturnValue( QueryBuilder::tabStats, QueryBuilder::valPlayCounter ); + qb.addMatch( QueryBuilder::tabStats, QueryBuilder::valURL, url ); + QStringList values = qb.run(); + if( values.count() ) + return values.first().toInt(); + return 0; +} + +QDateTime +CollectionDB::getFirstPlay( const QString &url ) +{ + QueryBuilder qb; + qb.addReturnValue( QueryBuilder::tabStats, QueryBuilder::valCreateDate ); + qb.addMatch( QueryBuilder::tabStats, QueryBuilder::valURL, url ); + QStringList values = qb.run(); + QDateTime dt; + if( values.count() ) + dt.setTime_t( values.first().toUInt() ); + return dt; +} + +QDateTime +CollectionDB::getLastPlay( const QString &url ) +{ + QueryBuilder qb; + qb.addReturnValue( QueryBuilder::tabStats, QueryBuilder::valAccessDate ); + qb.addMatch( QueryBuilder::tabStats, QueryBuilder::valURL, url ); + QStringList values = qb.run(); + QDateTime dt; + if( values.count() ) + dt.setTime_t( values.first().toUInt() ); + else + dt.setTime_t( 0 ); + return dt; +} +/*! + * @short: exchange url references in the database for a particular file + * @note: deletes all items for newURL, changes oldURL->newURL, deletes oldURL. + * FIXME: should we check if lyrics etc exist in the newURL and keep them if necessary? + */ +void +CollectionDB::migrateFile( const QString &oldURL, const QString &newURL ) +{ + int oldMediaid = MountPointManager::instance()->getIdForUrl( oldURL ); + QString oldRpath = MountPointManager::instance()->getRelativePath( oldMediaid, oldURL ); + + int newMediaid = MountPointManager::instance()->getIdForUrl( newURL ); + QString newRpath = MountPointManager::instance()->getRelativePath( newMediaid, newURL ); + + // Ensure destination is clear. + query( QString( "DELETE FROM tags WHERE url = '%2' AND deviceid = %1;" ) + .arg( newMediaid ).arg( escapeString( newRpath ) ) ); + + query( QString( "DELETE FROM statistics WHERE url = '%2' AND deviceid = %1;" ) + .arg( newMediaid ).arg( escapeString( newRpath ) ) ); + + query( QString( "DELETE FROM tags_labels WHERE url = '%2' and deviceid = %1;" ) + .arg( newMediaid).arg( escapeString( newRpath ) ) ); + + if ( !getLyrics( oldURL ).isEmpty() ) + query( QString( "DELETE FROM lyrics WHERE url = '%2' AND deviceid = %1;" ) + .arg( newMediaid ).arg( escapeString( newRpath ) ) ); + // Migrate + //code looks ugly but prevents problems when the URL contains HTTP escaped characters + query( QString( "UPDATE tags SET url = '%3', deviceid = %1" ) + .arg( newMediaid ).arg( escapeString( newRpath ) ) + + QString( " WHERE deviceid=%1 AND url = '%2';" ) + .arg( oldMediaid ).arg( escapeString( oldRpath ) ) ); + + query( QString( "UPDATE statistics SET url = '%2', deviceid = %1" ) + .arg( newMediaid ).arg( escapeString( newRpath ) ) + + QString( " WHERE deviceid=%1 AND url = '%2';" ) + .arg( oldMediaid ).arg( escapeString( oldRpath ) ) ); + + query( QString( "UPDATE lyrics SET url = '%2', deviceid = %1" ) + .arg( newMediaid ).arg( escapeString( newRpath ) ) + + QString( " WHERE deviceid=%1 AND url = '%2';" ) + .arg( oldMediaid ).arg( escapeString( oldRpath ) ) ); + + query( QString( "UPDATE tags_labels SET url = '%2', deviceid = %1 WHERE deviceid = %3 AND url = '%4';" ) + .arg( QString::number( newMediaid ), escapeString( newRpath ), QString::number( oldMediaid ), escapeString( oldRpath ) ) ); + + query( QString( "UPDATE uniqueid SET url = '%1', deviceid = %2 WHERE url = '%3' AND deviceid = %4;" ) + .arg( escapeString( newRpath ), QString::number( newMediaid ), + escapeString( oldRpath ), QString::number( oldMediaid ) ) ); + + query( QString( "UPDATE playlists SET url = '%1' WHERE url = '%2';" ) + .arg( escapeString( newURL ), + escapeString( oldURL ) ) ); +} + +void +CollectionDB::fileOperationResult( KIO::Job *job ) // slot +{ + if(job->error()) + { + m_fileOperationFailed = true; + debug() << "file operation failed: " << job->errorText() << endl; + } + else + { + m_fileOperationFailed = false; + } + + m_waitForFileOperation = false; +} + +void CollectionDB::cancelMovingFileJob() +{ + m_moveFileJobCancelled = true; +} + +bool +CollectionDB::organizeFile( const KURL &src, const OrganizeCollectionDialog &dialog, bool copy ) +{ + if( !MetaBundle::isKioUrl( src ) ) + return false; + + bool overwrite = dialog.overwriteCheck->isChecked(); + bool localFile = src.isLocalFile(); + KURL tmpSrc = src; + if( !localFile ) + { + QString tmp; + QString extension = src.url().section( '.', -1 ); + extension = extension.section("?", 0, 0); // remove trailling stuff lead by ?, if any + + int count = 0; + do + { + tmp = QString( dialog.folderCombo->currentText() + "/amarok-tmp-%1." + extension ).arg( count ); + count++; + } while( QFile::exists( tmp ) ); + tmpSrc = KURL::fromPathOrURL( tmp ); + + KIO::FileCopyJob *job = 0; + if( copy ) + { + job = KIO::file_copy( src, tmpSrc, -1, false, false, false ); + } + else + { + job = KIO::file_move( src, tmpSrc, -1, false, false, false ); + } + connect( job, SIGNAL(result( KIO::Job * )), SLOT(fileOperationResult( KIO::Job * )) ); + m_waitForFileOperation = true; + while( m_waitForFileOperation ) + { + if( m_moveFileJobCancelled ) + { + disconnect( job, SIGNAL(result( KIO::Job * )), this, SLOT(fileOperationResult( KIO::Job * )) ); + + QString partFile = QString( "%1.part" ).arg( (job->destURL()).path() ); + job->kill(); + QFile file( partFile ); + if( file.exists() ) file.remove(); + + m_waitForFileOperation = false; + m_fileOperationFailed = true; + continue; + } + + usleep( 10000 ); + kapp->processEvents( 100 ); + } + + if( m_fileOperationFailed ) + { + debug() << "failed to transfer " << src.url() << " to " << tmpSrc << endl; + + m_moveFileJobCancelled = false; + return false; + } + } + + //Building destination here. + MetaBundle mb( tmpSrc ); + QString dest = dialog.buildDestination( dialog.buildFormatString(), mb ); + + debug() << "Destination: " << dest << endl; + + if( !m_moveFileJobCancelled && tmpSrc.path() != dest ) //suppress error warning that file couldn't be moved + { + if( !CollectionDB::instance()->moveFile( tmpSrc.url(), dest, overwrite, copy && localFile ) ) + { + if( !localFile ) + QFile::remove( tmpSrc.path() ); + + m_moveFileJobCancelled = false; + return false; + } + } + + //Use cover image for folder icon + if( !m_moveFileJobCancelled && dialog.coverCheck->isChecked() && !mb.artist().isEmpty() && !mb.album().isEmpty() ) + { + KURL dstURL = KURL::fromPathOrURL( dest ); + dstURL.cleanPath(); + + QString path = dstURL.directory(); + QString cover = CollectionDB::instance()->albumImage( mb.artist(), mb.album(), false, 1 ); + + if( !QFile::exists(path + "/.directory") && !cover.endsWith( "nocover.png" ) ) + { + //QPixmap thumb; //Not amazon nice. + //if ( thumb.load( cover ) ){ + //thumb.save(path + "/.front.png", "PNG", -1 ); //hide files + + KSimpleConfig config(path + "/.directory"); + config.setGroup("Desktop Entry"); + + if( !config.hasKey("Icon") ) + { + config.writeEntry( "Icon", cover ); + config.sync(); + } + //} //Not amazon nice. + } + } + + if( localFile && isDirInCollection( src.directory() ) && QDir().rmdir( src.directory() ) ) + { + debug() << "removed: " << src.directory() << endl; + } + + m_moveFileJobCancelled = false; + + return true; +} + +bool +CollectionDB::moveFile( const QString &src, const QString &dest, bool overwrite, bool copy ) +{ + DEBUG_BLOCK + if(src == dest){ + debug() << "Source and destination URLs are the same, aborting." << endl; + return false; + } + + // Escape URL. + KURL srcURL = KURL::fromPathOrURL( src ); + KURL dstURL = KURL::fromPathOrURL( dest ); + + // Clean it. + srcURL.cleanPath(); + dstURL.cleanPath(); + + // Make sure it is valid. + if(!srcURL.isValid() || !dstURL.isValid()) + debug() << "Invalid URL " << endl; + + // Get just the directory. + KURL dir = dstURL; + dir.setFileName(QString::null); + + // Create the directory. + if(!KStandardDirs::exists(dir.path())) + if(!KStandardDirs::makeDir(dir.path())) { + debug() << "Unable to create directory " << dir.path() << endl; + } + + m_fileOperationFailed = false; + KIO::FileCopyJob *job = 0; + if( copy ) + { + job = KIO::file_copy( srcURL, dstURL, -1, overwrite, false, false ); + } + else + { + job = KIO::file_move( srcURL, dstURL, -1, overwrite, false, false ); + } + connect( job, SIGNAL(result( KIO::Job * )), SLOT(fileOperationResult( KIO::Job * )) ); + m_waitForFileOperation = true; + while( m_waitForFileOperation ) + { + if( m_moveFileJobCancelled ) + { + disconnect( job, SIGNAL(result( KIO::Job * )), this, SLOT(fileOperationResult( KIO::Job * )) ); + + QString partFile = QString( "%1.part" ).arg( (job->destURL()).path() ); + job->kill(); + QFile file( partFile ); + if( file.exists() ) file.remove(); + + m_waitForFileOperation = false; + m_fileOperationFailed = true; + continue; + } + + usleep( 10000 ); + kapp->processEvents( 100 ); + } + + if( !m_fileOperationFailed ) + { + if( copy ) + { + MetaBundle bundle( dstURL ); + if( bundle.isValidMedia() ) + { + addSong( &bundle, true ); + return true; + } + } + else + { + emit fileMoved( src, dest ); + migrateFile( srcURL.path(), dstURL.path() ); + + if( isFileInCollection( srcURL.path() ) ) + { + return true; + } + else + { + MetaBundle bundle( dstURL ); + if( bundle.isValidMedia() ) + { + addSong( &bundle, true ); + return true; + } + } + } + } + + return false; +} + + +void +CollectionDB::updateDirStats( QString path, const long datetime, const bool temporary ) +{ + if ( path.endsWith( "/" ) ) + path = path.left( path.length() - 1 ); + + int deviceid = MountPointManager::instance()->getIdForUrl( path ); + QString rpath = MountPointManager::instance()->getRelativePath( deviceid, path ); + + if (getDbConnectionType() == DbConnection::postgresql) + { + // REPLACE INTO is not valid SQL for postgres, so we need to check whether we + // should UPDATE() or INSERT() + QStringList values = query( QString("SELECT * FROM directories%1 WHERE dir='%3' AND deviceid=%2;") + .arg( temporary ? "_temp" : "") + .arg( deviceid ) + .arg( escapeString( rpath ) ) ); + + if(values.count() > 0 ) + { + query( QString( "UPDATE directories%1 SET changedate=%2 WHERE dir='%4'AND deviceid=%3;") + .arg( temporary ? "_temp" : "" ) + .arg( datetime ) + .arg( deviceid ) + .arg( escapeString( rpath ) ) ); + } + else + { + + query( QString( "INSERT INTO directories%1 (dir, deviceid,changedate) VALUES ('%4', %3, '%2');") + .arg( temporary ? "_temp" : "") + .arg( datetime ) + .arg( deviceid ) + .arg( escapeString( rpath ) ) ); + } + } + else + { + query( QString( "REPLACE INTO directories%1 ( dir, deviceid, changedate ) VALUES ( '%4', %3, %2 );" ) + .arg( temporary ? "_temp" : "" ) + .arg( datetime ) + .arg( deviceid ) + .arg( escapeString( rpath ) ) ); + } + + INotify::instance()->watchDir( path ); +} + + +void +CollectionDB::removeSongsInDir( QString path, QMap *tagsRemoved ) +{ + if ( path.endsWith( "/" ) ) + path = path.left( path.length() - 1 ); + int deviceid = MountPointManager::instance()->getIdForUrl( path ); + QString rpath = MountPointManager::instance()->getRelativePath( deviceid, path ); + + // Pass back the list of tags we actually delete if requested. + if( tagsRemoved ) + { + QStringList result + = query( QString( "SELECT tags.deviceid, tags.url, uniqueid.uniqueid FROM tags " + "LEFT JOIN uniqueid ON uniqueid.url = tags.url " + "AND uniqueid.deviceid = tags.deviceid " + "WHERE tags.dir = '%2' AND tags.deviceid = %1" ) + .arg( deviceid ) + .arg( escapeString( rpath ) ) ); + QStringList::ConstIterator it = result.begin(), end = result.end(); + while( it != end ) + { + int deviceid2 = (*(it++)).toInt(); + QString rpath2 = *(it++); + QString uniqueid = *(it++); + (*tagsRemoved)[uniqueid] = MountPointManager::instance()->getAbsolutePath( + deviceid2, rpath2 ); + } + } + + query( QString( "DELETE FROM tags WHERE dir = '%2' AND deviceid = %1;" ) + .arg( deviceid ) + .arg( escapeString( rpath ) ) ); + + query( QString( "DELETE FROM uniqueid WHERE dir = '%2' AND deviceid = %1;" ) + .arg( deviceid ) + .arg( escapeString( rpath ) ) ); +} + + +bool +CollectionDB::isDirInCollection( QString path ) +{ + if ( path.endsWith( "/" ) ) + path = path.left( path.length() - 1 ); + int deviceid = MountPointManager::instance()->getIdForUrl( path ); + QString rpath = MountPointManager::instance()->getRelativePath( deviceid, path ); + + QStringList values = + query( QString( "SELECT changedate FROM directories WHERE dir = '%2' AND deviceid = %1;" ) + .arg( deviceid ) + .arg( escapeString( rpath ) ) ); + + return !values.isEmpty(); +} + + +bool +CollectionDB::isFileInCollection( const QString &url ) +{ + int deviceid = MountPointManager::instance()->getIdForUrl( url ); + QString rpath = MountPointManager::instance()->getRelativePath( deviceid, url ); + + QString sql = QString( "SELECT url FROM tags WHERE url = '%2' AND deviceid = %1" ) + .arg( deviceid ) + .arg( escapeString( rpath ) ); + if ( deviceid == -1 ) + { + sql += ';'; + } + else + { + QString rpath2 = '.' + url; + sql += QString( " OR url = '%1' AND deviceid = -1;" ) + .arg( escapeString( rpath2 ) ); + } + QStringList values = query( sql ); + + return !values.isEmpty(); +} + + +void +CollectionDB::removeSongs( const KURL::List& urls ) +{ + for( KURL::List::ConstIterator it = urls.begin(), end = urls.end(); it != end; ++it ) + { + int deviceid = MountPointManager::instance()->getIdForUrl( *it ); + QString rpath = MountPointManager::instance()->getRelativePath( deviceid, (*it).path() ); + + query( QString( "DELETE FROM tags WHERE url = '%2' AND deviceid = %1;" ) + .arg( deviceid ) + .arg( escapeString( rpath ) ) ); + query( QString( "DELETE FROM uniqueid WHERE url = '%2' AND deviceid = %1;" ) + .arg( deviceid ) + .arg( escapeString( rpath ) ) ); + query( QString( "UPDATE statistics SET deleted = %1 WHERE url = '%3' AND deviceid = %2;" ) + .arg( boolT() ) + .arg( deviceid ) + .arg( escapeString( rpath ) ) ); + } +} + + +QStringList +CollectionDB::similarArtists( const QString &artist, uint count ) +{ + QStringList values; + + values = query( QString( "SELECT suggestion FROM related_artists WHERE artist = '%1' ORDER BY %2 LIMIT %3 OFFSET 0;" ) + .arg( escapeString( artist ), randomFunc(), QString::number( count ) ) ); + + if ( values.isEmpty() ) + Scrobbler::instance()->similarArtists( artist ); + + return values; +} + + +void +CollectionDB::sanitizeCompilations() +{ + query( QString( "UPDATE tags_temp SET sampler = %1 WHERE sampler IS NULL;").arg( boolF() ) ); +} + +void +CollectionDB::checkCompilations( const QString &path, const bool temporary ) +{ + QStringList albums; + QStringList artists; + QStringList dirs; + + int deviceid = MountPointManager::instance()->getIdForUrl( path ); + QString rpath = MountPointManager::instance()->getRelativePath( deviceid, path ); + + albums = query( QString( "SELECT DISTINCT album.name FROM tags_temp, album%1 AS album WHERE tags_temp.dir = '%3' AND tags_temp.deviceid = %2 AND album.id = tags_temp.album AND tags_temp.sampler IS NULL;" ) + .arg( temporary ? "_temp" : "" ) + .arg( deviceid ) + .arg( escapeString( rpath ) ) ); + + for ( uint i = 0; i < albums.count(); i++ ) + { + if ( albums[ i ].isEmpty() ) continue; + + const uint album_id = albumID( albums[ i ], false, temporary, true ); + artists = query( QString( "SELECT DISTINCT artist.name FROM tags_temp, artist%1 AS artist WHERE tags_temp.album = '%2' AND tags_temp.artist = artist.id;" ) + .arg( temporary ? "_temp" : "" ) + .arg( album_id ) ); + dirs = query( QString( "SELECT DISTINCT dir FROM tags_temp WHERE album = '%1';" ) + .arg( album_id ) ); + + if ( artists.count() > dirs.count() ) + { + debug() << "Detected compilation: " << albums[ i ] << " - " << artists.count() << ':' << dirs.count() << endl; + } + query( QString( "UPDATE tags_temp SET sampler = %1 WHERE album = '%2' AND sampler IS NULL;" ) + .arg(artists.count() > dirs.count() ? boolT() : boolF()).arg( album_id ) ); + } +} + +void +CollectionDB::setCompilation( const KURL::List &urls, bool enabled, bool updateView ) +{ + for ( KURL::List::const_iterator it = urls.begin(); it != urls.end(); ++it ) + { + QString url( ( *it ).path() ); + + int deviceid = MountPointManager::instance()->getIdForUrl( url ); + QString rpath = MountPointManager::instance()->getRelativePath( deviceid, url ); + + query( QString( "UPDATE tags SET sampler = %1 WHERE tags.url = '%2' AND tags.deviceid = %3;" ) + .arg( ( enabled ? boolT() : boolF() ), escapeString( rpath ), QString::number( deviceid ) ) ); + } + + // Update the Collection-Browser view, + // using QTimer to make sure we don't manipulate the GUI from a thread + if ( updateView ) + QTimer::singleShot( 0, CollectionView::instance(), SLOT( renderView() ) ); +} + + +void +CollectionDB::removeDirFromCollection( QString path ) +{ + //if ( path.endsWith( "/" ) ) + // path = path.left( path.length() - 1 ); + int deviceid = MountPointManager::instance()->getIdForUrl( path ); + QString rpath = MountPointManager::instance()->getRelativePath( deviceid, path ); + + query( QString( "DELETE FROM directories WHERE dir = '%2' AND deviceid = %1;" ) + .arg( deviceid ) + .arg( escapeString( rpath ) ) ); +} + + +QString +CollectionDB::IDFromExactValue( QString table, QString value, bool autocreate, bool temporary /* = false */ ) +{ + if ( temporary ) + { + table.append( "_temp" ); + } + + QString querystr( QString( "SELECT id FROM %1 WHERE name " ).arg( table ) ); + querystr += exactCondition( value ) + ';'; + QStringList result = query( querystr ); + if ( result.isEmpty() ) + { + if ( autocreate ) + return QString::number( insert( QString( "INSERT INTO %1 ( name ) VALUES ( '%2' );" ) + .arg( table, escapeString( value ) ), + table ) ); + else + return 0; + } + else + { + if ( result.count() > 1 ) + debug() << "More than one entry in the " << table << " database for '" << value << '\'' << endl; + return result.first(); + } +} + +void +CollectionDB::deleteRedundantName( const QString &table, const QString &id ) +{ + QString querystr( QString( "SELECT %1 FROM tags WHERE tags.%1 = %2 LIMIT 1;" ).arg( table, id ) ); + QStringList result = query( querystr ); + if ( result.isEmpty() ) + query( QString( "DELETE FROM %1 WHERE id = %2;" ).arg( table,id ) ); +} + +void +CollectionDB::deleteAllRedundant( const QString &table ) +{ + //This works with MySQL4. I thought it might not do, due to the comment in copyTempTables + query( QString( "DELETE FROM %1 WHERE id NOT IN ( SELECT %2 FROM tags )" ).arg( table, table ) ); +} + + +void +CollectionDB::updateTags( const QString &url, const MetaBundle &bundle, const bool updateView ) +{ + DEBUG_BLOCK + QueryBuilder qb; + qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valTitle ); + qb.addReturnValue( QueryBuilder::tabArtist, QueryBuilder::valName ); + qb.addReturnValue( QueryBuilder::tabComposer, QueryBuilder::valName ); + qb.addReturnValue( QueryBuilder::tabAlbum, QueryBuilder::valName ); + qb.addReturnValue( QueryBuilder::tabGenre, QueryBuilder::valName ); + qb.addReturnValue( QueryBuilder::tabYear, QueryBuilder::valName ); + qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valTrack ); + qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valComment ); + qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valDiscNumber ); + qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valFilesize ); + qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valFileType ); + // [10] is above. [11] is below. + qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valBPM ); + qb.addReturnValue( QueryBuilder::tabArtist, QueryBuilder::valID ); + qb.addReturnValue( QueryBuilder::tabComposer, QueryBuilder::valID ); + qb.addReturnValue( QueryBuilder::tabAlbum, QueryBuilder::valID ); + qb.addReturnValue( QueryBuilder::tabGenre, QueryBuilder::valID ); + qb.addReturnValue( QueryBuilder::tabYear, QueryBuilder::valID ); + + qb.addURLFilters ( QStringList( url ) ); + qb.setOptions( QueryBuilder::optRemoveDuplicates ); + QStringList values = qb.run(); + + if ( values.count() > 17 ) + { + error() << "Query returned more than 1 song. Aborting updating metadata" << endl; + return; + } + + if ( !values.isEmpty() ) + { + bool art=false, comp=false, alb=false, gen=false, year=false; + + QString command = "UPDATE tags SET "; + if ( values[ 0 ] != bundle.title() ) + command += "title = '" + escapeString( bundle.title() ) + "', "; + if ( values[ 1 ] != bundle.artist() ) + { + art = true; + command += "artist = " + IDFromExactValue( "artist", bundle.artist() ) + ", "; + } + if ( values[ 2 ] != bundle.composer() ) + { + comp = true; + command += "composer = " + IDFromExactValue( "composer", bundle.composer() ) + ", "; + } + if ( values[ 3 ] != bundle.album() ) + { + alb = true; + command += "album = " + IDFromExactValue( "album", bundle.album() ) + ", "; + } + if ( values[ 4 ] != bundle.genre() ) + { + gen = true; + command += "genre = " + IDFromExactValue( "genre", bundle.genre() ) + ", "; + } + if ( values[ 5 ] != QString::number( bundle.year() ) ) + { + year = false; + command += "year = " + IDFromExactValue( "year", QString::number( bundle.year() ) ) + ", "; + } + if ( values[ 6 ] != QString::number( bundle.track() ) ) + command += "track = " + QString::number( bundle.track() ) + ", "; + if ( values[ 7 ] != bundle.comment() ) + command += "comment = '" + escapeString( bundle.comment() ) + "', "; + if ( values[ 8 ] != QString::number( bundle.discNumber() ) ) + command += "discnumber = '" + QString::number( bundle.discNumber() ) + "', "; + if ( values[ 9 ] != QString::number( bundle.filesize() ) ) + command += "filesize = '" + QString::number( bundle.filesize() ) + "', "; + if ( values[ 10 ] != QString::number( bundle.fileType() ) ) + command += "filetype = '" + QString::number( bundle.fileType() ) + "', "; + if ( values[ 11 ] != QString::number( bundle.bpm() ) ) + command += "bpm = '" + QString::number( bundle.bpm() ) + "', "; + + if ( "UPDATE tags SET " == command ) + { + debug() << "No tags selected to be changed" << endl; + } + else + { + int deviceid = MountPointManager::instance()->getIdForUrl( url ); + QString rpath = MountPointManager::instance()->getRelativePath( deviceid, url ); + //We have to remove the trailing comma from command + query( command.left( command.length() - 2 ) + " WHERE url = '" + escapeString( rpath ) + + "' AND deviceid = " + QString::number( deviceid ) + ';' ); + } + + //Check to see if we use the entry anymore. If not, delete it + if ( art ) + deleteRedundantName( "artist", values[ 12 ] ); + if ( comp ) + deleteRedundantName( "composer", values[ 13 ] ); + if ( alb ) + deleteRedundantName( "album", values[ 14 ] ); + if ( gen ) + deleteRedundantName( "genre", values[ 15 ] ); + if ( year ) + deleteRedundantName( "year", values[ 16 ] ); + + // Update the Collection-Browser view, + // using QTimer to make sure we don't manipulate the GUI from a thread + if ( updateView ) + QTimer::singleShot( 0, CollectionView::instance(), SLOT( databaseChanged() ) ); + + if( art || alb ) + emit tagsChanged( values[12], values[14] ); + } + + if ( EngineController::instance()->bundle().url() == bundle.url() ) + { + debug() << "Current song edited, updating widgets: " << bundle.title() << endl; + EngineController::instance()->currentTrackMetaDataChanged( bundle ); + } + + emit tagsChanged( bundle ); +} + + +void +CollectionDB::updateURL( const QString &url, const bool updateView ) +{ + // don't use the KURL ctor as it checks the db first + MetaBundle bundle; + bundle.setPath( url ); + bundle.readTags( TagLib::AudioProperties::Fast ); + + updateTags( url, bundle, updateView); + doAFTStuff( &bundle, false ); +} + +QString +CollectionDB::getUniqueId( const QString &url ) +{ + int deviceid = MountPointManager::instance()->getIdForUrl( url ); + QString rpath = MountPointManager::instance()->getRelativePath( deviceid, url ); + QStringList values = query( QString( "SELECT uniqueid FROM uniqueid WHERE deviceid = %1 AND url = '%2';" ) + .arg( deviceid ) + .arg( escapeString( rpath ) )); + if( !values.empty() ) + return values[0]; + else + return QString(); +} + +void +CollectionDB::setLyrics( const QString &url, const QString &lyrics, const QString &uniqueid ) +{ + int deviceid = MountPointManager::instance()->getIdForUrl( url ); + QString rpath = MountPointManager::instance()->getRelativePath( deviceid, url ); + + QStringList values = query(QString("SELECT lyrics FROM lyrics WHERE url = '%2' AND deviceid = %1;") + .arg( deviceid ).arg( escapeString( rpath ) ) ); + if(values.count() > 0) + { + if ( !lyrics.isEmpty() ) + query( QString( "UPDATE lyrics SET lyrics = '%1' WHERE url = '%3' AND deviceid = %2;" ) + .arg( escapeString( lyrics), QString::number(deviceid), escapeString( rpath ) ) ); + else + query( QString( "DELETE FROM lyrics WHERE url = '%2' AND deviceid = %1;" ) + .arg( deviceid).arg( escapeString( rpath ) ) ); + } + else + { + insert( QString( "INSERT INTO lyrics (deviceid, url, lyrics, uniqueid) values ( %1, '%2', '%3', '%4' );" ) + .arg( QString::number(deviceid), escapeString( rpath ), escapeString( lyrics ), escapeString( uniqueid ) ), NULL); + } +} + + +QString +CollectionDB::getLyrics( const QString &url ) +{ + int deviceid = MountPointManager::instance()->getIdForUrl( url ); + QString rpath = MountPointManager::instance()->getRelativePath( deviceid, url ); + QStringList values = query( QString( "SELECT lyrics FROM lyrics WHERE url = '%2' AND deviceid = %1;" ) + .arg( deviceid ).arg( escapeString( rpath ) ) ); + return values[0]; +} + +void CollectionDB::removeInvalidAmazonInfo( const QString& md5sum ) +{ + query( QString( "DELETE FROM amazon WHERE filename='%1'" ).arg( md5sum ) ); +} + +void CollectionDB::newAmazonReloadDate( const QString& asin, const QString& locale, const QString& md5sum) +{ + QStringList values = query(QString("SELECT filename FROM amazon WHERE filename = '%1'") + .arg(md5sum)); + if(values.count() > 0) + { + query( QString("UPDATE amazon SET asin = '%1', locale = '%2', refetchdate = '%3' WHERE filename = '%4'") + .arg(asin) + .arg(locale) + .arg(QDateTime::currentDateTime().addDays(80).toTime_t()) + .arg(md5sum)); + } + else + { + insert( QString( "INSERT INTO amazon ( asin, locale, filename, refetchdate ) VALUES ( '%1', '%2', '%3', '%4');" ) + .arg(asin) + .arg(locale) + .arg(md5sum) + .arg(QDateTime::currentDateTime().addDays(80).toTime_t()), NULL ); + } +} + +QStringList CollectionDB::staleImages() +{ + return query(QString("SELECT asin, locale, filename FROM amazon WHERE refetchdate < %1 ;") + .arg(QDateTime::currentDateTime().toTime_t() )); +} + +void +CollectionDB::applySettings() +{ + bool recreateConnections = false; + if ( AmarokConfig::databaseEngine().toInt() != getDbConnectionType() ) + { + if ( AmarokConfig::databaseEngine().toInt() == DbConnection::mysql ) + m_dbConnType = DbConnection::mysql; + else if ( AmarokConfig::databaseEngine().toInt() == DbConnection::postgresql ) + m_dbConnType = DbConnection::postgresql; + else m_dbConnType = DbConnection::sqlite; + recreateConnections = true; + } + else if ( AmarokConfig::databaseEngine().toInt() == DbConnection::mysql ) + { + // Using MySQL, so check if MySQL settings were changed + const MySqlConfig *config = + static_cast ( m_dbConfig ); + if ( AmarokConfig::mySqlHost() != config->host() ) + { + recreateConnections = true; + } + else if ( AmarokConfig::mySqlPort() != config->port() ) + { + recreateConnections = true; + } + else if ( AmarokConfig::mySqlDbName() != config->database() ) + { + recreateConnections = true; + } + else if ( AmarokConfig::mySqlUser() != config->username() ) + { + recreateConnections = true; + } + else if ( AmarokConfig::mySqlPassword2() != config->password() ) + { + recreateConnections = true; + } + } + else if ( AmarokConfig::databaseEngine().toInt() == DbConnection::postgresql ) + { + const PostgresqlConfig *config = + static_cast ( m_dbConfig ); + if ( AmarokConfig::postgresqlHost() != config->host() ) + { + recreateConnections = true; + } + else if ( AmarokConfig::postgresqlPort() != config->port() ) + { + recreateConnections = true; + } + else if ( AmarokConfig::postgresqlDbName() != config->database() ) + { + recreateConnections = true; + } + else if ( AmarokConfig::postgresqlUser() != config->username() ) + { + recreateConnections = true; + } + else if ( AmarokConfig::postgresqlPassword() != config->password() ) + { + recreateConnections = true; + } + } + + if ( recreateConnections ) + { + debug() + << "Database engine settings changed: " + << "recreating DbConnections" << endl; + // If Database engine was changed, recreate DbConnections. + destroy(); + initialize(); + CollectionView::instance()->renderView(); + PlaylistBrowser::instance()->loadPodcastsFromDatabase(); + + emit databaseEngineChanged(); + } +} + +DbConnection * CollectionDB::getMyConnection() +{ + //after some thought, to be thread-safe, must lock at the beginning of this function, + //not only if a new connection is made + connectionMutex->lock(); + + DbConnection *dbConn; + QThread *currThread = ThreadManager::Thread::getRunning(); + + if (threadConnections->contains(currThread)) + { + QMap::Iterator it = threadConnections->find(currThread); + dbConn = it.data(); + connectionMutex->unlock(); + return dbConn; + } + +#ifdef USE_MYSQL + if ( m_dbConnType == DbConnection::mysql ) + { + dbConn = new MySqlConnection( static_cast( m_dbConfig ) ); + } + else +#endif +#ifdef USE_POSTGRESQL + if ( m_dbConnType == DbConnection::postgresql ) + { + dbConn = new PostgresqlConnection( static_cast( m_dbConfig ) ); + } + else +#endif + { + dbConn = new SqliteConnection( static_cast( m_dbConfig ) ); + } + + threadConnections->insert(currThread, dbConn); + + connectionMutex->unlock(); + return dbConn; +} + + +void +CollectionDB::releasePreviousConnection(QThread *currThread) +{ + //if something already exists, delete the object, and erase knowledge of it from the QMap. + connectionMutex->lock(); + DbConnection *dbConn; + if (threadConnections->contains(currThread)) + { + QMap::Iterator it = threadConnections->find(currThread); + dbConn = it.data(); + delete dbConn; + threadConnections->erase(currThread); + } + connectionMutex->unlock(); +} + +bool +CollectionDB::isConnected() +{ + return getMyConnection()->isConnected(); +} + +////////////////////////////////////////////////////////////////////////////////////////// +// PROTECTED +////////////////////////////////////////////////////////////////////////////////////////// + +QCString +CollectionDB::md5sum( const QString& artist, const QString& album, const QString& file ) +{ + KMD5 context( artist.lower().local8Bit() + album.lower().local8Bit() + file.local8Bit() ); +// debug() << "MD5 SUM for " << artist << ", " << album << ": " << context.hexDigest() << endl; + return context.hexDigest(); +} + + +void CollectionDB::engineTrackEnded( int finalPosition, int trackLength, const QString &reason ) +{ + //This is where percentages are calculated + //TODO statistics are not calculated when currentTrack doesn't exist + + // Don't update statistics if song has been played for less than 15 seconds + // if ( finalPosition < 15000 ) return; + + //below check is necessary because if stop after current track is selected, + //the url's path will be empty, so check the previous URL for the path that + //had just played + const KURL url = EngineController::instance()->bundle().url().path().isEmpty() ? + EngineController::instance()->previousURL() : + EngineController::instance()->bundle().url(); + PodcastEpisodeBundle peb; + if( getPodcastEpisodeBundle( url.url(), &peb ) ) + { + PodcastEpisode *p = PlaylistBrowser::instance()->findPodcastEpisode( peb.url(), peb.parent() ); + if ( p ) + p->setListened(); + + if( !url.isLocalFile() ) + return; + } + + if ( url.path().isEmpty() || !m_autoScoring ) return; + + // sanity check + if ( finalPosition > trackLength || finalPosition <= 0 ) + finalPosition = trackLength; + + int pct = (int) ( ( (double) finalPosition / (double) trackLength ) * 100 ); + + // increase song counter & calculate new statistics + addSongPercentage( url.path(), pct, reason ); +} + + +void +CollectionDB::timerEvent( QTimerEvent* ) +{ + scanMonitor(); +} + + +////////////////////////////////////////////////////////////////////////////////////////// +// PUBLIC SLOTS +////////////////////////////////////////////////////////////////////////////////////////// + +void +CollectionDB::fetchCover( QWidget* parent, const QString& artist, const QString& album, bool noedit, QListViewItem* item ) //SLOT +{ + #ifdef AMAZON_SUPPORT + debug() << "Fetching cover for " << artist << " - " << album << endl; + + const bool isCompilation = albumIsCompilation( QString::number( albumID( album, false, false, true ) ) ); + CoverFetcher* fetcher; + if( isCompilation ) + // avoid putting various artists in front of album title. this causes problems for locales other than US. + fetcher = new CoverFetcher( parent, "", album ); + else + fetcher = new CoverFetcher( parent, artist, album ); + if( item ) + { + itemCoverMapMutex->lock(); + itemCoverMap->insert( item, fetcher ); + itemCoverMapMutex->unlock(); + } + connect( fetcher, SIGNAL(result( CoverFetcher* )), SLOT(coverFetcherResult( CoverFetcher* )) ); + fetcher->setUserCanEditQuery( !noedit ); + fetcher->startFetch(); + #endif +} + +void +CollectionDB::scanMonitor() //SLOT +{ + if ( AmarokConfig::monitorChanges() ) + scanModifiedDirs(); +} + + +void +CollectionDB::startScan() //SLOT +{ + QStringList folders = MountPointManager::instance()->collectionFolders(); + + if ( folders.isEmpty() ) + { + //dropTables( false ); + //createTables( false ); + clearTables( false ); + emit scanDone( true ); + } + else if( PlaylistBrowser::instance() ) + { + emit scanStarted(); + ThreadManager::instance()->queueJob( new ScanController( this, false, folders ) ); + } +} + + +void +CollectionDB::stopScan() //SLOT +{ + ThreadManager::instance()->abortAllJobsNamed( "CollectionScanner" ); +} + + +////////////////////////////////////////////////////////////////////////////////////////// +// PRIVATE SLOTS +////////////////////////////////////////////////////////////////////////////////////////// + +void +CollectionDB::dirDirty( const QString& path ) +{ + debug() << k_funcinfo << "Dirty: " << path << endl; + + ThreadManager::instance()->queueJob( new ScanController( this, false, path ) ); +} + + +void +CollectionDB::coverFetcherResult( CoverFetcher *fetcher ) +{ + if( fetcher->wasError() ) { + error() << fetcher->errors() << endl; + emit coverFetcherError( fetcher->errors().front() ); + } + + else { + setAlbumImage( fetcher->artist(), fetcher->album(), fetcher->image(), fetcher->amazonURL(), fetcher->asin() ); + emit coverFetched( fetcher->artist(), fetcher->album() ); + } + + //check the validity of the CollectionItem as it may have been deleted e.g. by a + //collection scan while fetching the cover + itemCoverMapMutex->lock(); + QMap::Iterator it; + for( it = itemCoverMap->begin(); it != itemCoverMap->end(); ++it ) + { + if( it.data() == fetcher ) + { + if( it.key()->isOpen() ) + static_cast(it.key())->setPixmap( 0, QPixmap() ); + itemCoverMap->erase( it ); + } + } + itemCoverMapMutex->unlock(); +} + +/** + * This query is fairly slow with sqlite, and often happens just + * after the OSD is shown. Threading it restores responsivity. + */ +class SimilarArtistsInsertionJob : public ThreadManager::DependentJob +{ + virtual bool doJob() + { + CollectionDB::instance()->query( QString( "DELETE FROM related_artists WHERE artist = '%1';" ).arg( escapedArtist ) ); + + const QString sql = "INSERT INTO related_artists ( artist, suggestion, changedate ) VALUES ( '%1', '%2', 0 );"; + foreach( suggestions ) + CollectionDB::instance()->insert( sql + .arg( escapedArtist, + CollectionDB::instance()->escapeString( *it ) ), NULL); + + return true; + } + + virtual void completeJob() { emit CollectionDB::instance()->similarArtistsFetched( artist ); } + + const QString artist; + const QString escapedArtist; + const QStringList suggestions; + +public: + SimilarArtistsInsertionJob( CollectionDB *parent, const QString &s, const QStringList &list ) + : ThreadManager::DependentJob( parent, "SimilarArtistsInsertionJob" ) + , artist( QDeepCopy(s) ) + , escapedArtist( parent->escapeString( QDeepCopy(s) ) ) + , suggestions( QDeepCopy(list) ) + {} +}; + +void +CollectionDB::similarArtistsFetched( const QString& artist, const QStringList& suggestions ) +{ + debug() << "Received similar artists\n"; + + ThreadManager::instance()->queueJob( new SimilarArtistsInsertionJob( this, artist, suggestions ) ); +} + +void +CollectionDB::aftCheckPermanentTables( const QString &currdeviceid, const QString &currid, const QString &currurl ) +{ + //DEBUG_BLOCK + //debug() << "deviceid = " << currdeviceid << endl << "url = " << currurl << endl << "uid = " << currid << endl; + + QStringList check1, check2; + + foreach( m_aftEnabledPersistentTables ) + { + //debug() << "Checking " << (*it) << endl;; + check1 = query( QString( + "SELECT url, deviceid " + "FROM %1 " + "WHERE uniqueid = '%2';" ) + .arg( escapeString( *it ) ) + .arg( currid ) ); + + check2 = query( QString( + "SELECT url, uniqueid " + "FROM %1 " + "WHERE deviceid = %2 AND url = '%3';" ) + .arg( escapeString( *it ) ) + .arg( currdeviceid + , currurl ) ); + + if( !check1.empty() ) + { + //debug() << "uniqueid found, updating url" << endl; + query( QString( "UPDATE %1 SET deviceid = %2, url = '%4' WHERE uniqueid = '%3';" ) + .arg( escapeString( *it ) ) + .arg( currdeviceid + , currid + , currurl ) ); + } + else if( !check2.empty() ) + { + //debug() << "url found, updating uniqueid" << endl; + query( QString( "UPDATE %1 SET uniqueid = '%2' WHERE deviceid = %3 AND url = '%4';" ) + .arg( escapeString( *it ) ) + .arg( currid + , currdeviceid + , currurl ) ); + } + } +} + +void +CollectionDB::aftMigratePermanentTablesUrl( const QString& /*oldUrl*/, const QString& newUrl, const QString& uniqueid ) +{ + //DEBUG_BLOCK + int deviceid = MountPointManager::instance()->getIdForUrl( newUrl ); + QString rpath = MountPointManager::instance()->getRelativePath( deviceid, newUrl ); + //NOTE: if ever do anything with "deleted" in the statistics table, set deleted to false in query + //below; will need special case. + //debug() << "deviceid = " << deviceid << endl << "newurl = " << newUrl << endl << "uid = " << uniqueid << endl; + foreach( m_aftEnabledPersistentTables ) + { + query( QString( "DELETE FROM %1 WHERE deviceid = %2 AND url = '%3';" ) + .arg( escapeString( *it ) ) + .arg( deviceid ) + .arg( escapeString( rpath ) ) ); + query( QString( "UPDATE %1 SET deviceid = %2, url = '%4' WHERE uniqueid = '%3';" ) + .arg( escapeString( *it ) ) + .arg( deviceid ) + .arg( escapeString( uniqueid ) ) + .arg( escapeString( rpath ) ) ); + } +} + +void +CollectionDB::aftMigratePermanentTablesUniqueId( const QString& /*url*/, const QString& oldid, const QString& newid ) +{ + //DEBUG_BLOCK + //debug() << "oldid = " << oldid << endl << "newid = " << newid << endl; + //NOTE: if ever do anything with "deleted" in the statistics table, set deleted to false in query + //below; will need special case. + foreach( m_aftEnabledPersistentTables ) + { + query( QString( "DELETE FROM %1 WHERE uniqueid = '%2';" ) + .arg( escapeString( *it ) ) + .arg( escapeString( newid ) ) ); + query( QString( "UPDATE %1 SET uniqueid = '%1' WHERE uniqueid = '%2';" ) + .arg( escapeString( *it ) ) + .arg( escapeString( newid ) ) + .arg( escapeString( oldid ) ) ); + } +} + + +////////////////////////////////////////////////////////////////////////////////////////// +// PRIVATE +////////////////////////////////////////////////////////////////////////////////////////// + +void +CollectionDB::initialize() +{ + DEBUG_BLOCK + + /// Create DBConfig instance: + +#ifdef USE_MYSQL + if ( m_dbConnType == DbConnection::mysql ) + { + QString appVersion = Amarok::config( "General Options" )->readEntry( "Version" ); + QString passwd = AmarokConfig::mySqlPassword2(); // stored as string type + + if( passwd.isEmpty() ) + { + if( appVersion.startsWith( "1.3" ) ) + { + /// This is because of the encrypted -> plaintext conversion + passwd = AmarokConfig::mySqlPassword(); // stored as password type + AmarokConfig::setMySqlPassword2( passwd ); + } + else if( appVersion.startsWith( "1.4" ) ) + { + passwd = Amarok::config( "MySql" )->readEntry( "MySqlPassword" ); //read the field as plaintext + AmarokConfig::setMySqlPassword2( passwd ); // store it in plaintext field + } + } + + m_dbConfig = new MySqlConfig( + AmarokConfig::mySqlHost(), + AmarokConfig::mySqlPort(), + AmarokConfig::mySqlDbName(), + AmarokConfig::mySqlUser(), + passwd ); + } + else +#endif +#ifdef USE_POSTGRESQL + if ( m_dbConnType == DbConnection::postgresql ) + { + QString appVersion = Amarok::config( "General Options" )->readEntry( "Version" ); + QString passwd = AmarokConfig::postgresqlPassword2(); + + if( passwd.isEmpty() ) + { + if( appVersion.startsWith( "1.3" ) ) + { + /// This is because of the encrypted -> plaintext conversion + passwd = AmarokConfig::postgresqlPassword(); // stored as password type + AmarokConfig::setPostgresqlPassword2( passwd ); + } + else if( appVersion.startsWith( "1.4" ) && + ( appVersion.contains( "beta", false ) || + appVersion.contains( "svn", false ) ) ) + { + passwd = Amarok::config( "Postgresql" )->readEntry( "PostgresqlPassword" ); + AmarokConfig::setPostgresqlPassword2( passwd ); + } + } + + m_dbConfig = new PostgresqlConfig( + AmarokConfig::postgresqlHost(), + AmarokConfig::postgresqlPort(), + AmarokConfig::postgresqlDbName(), + AmarokConfig::postgresqlUser(), + passwd ); + } + else +#endif + { + m_dbConfig = new SqliteConfig( + Amarok::config( "Sqlite" )->readPathEntry( "location", + Amarok::saveLocation() + "collection.db" ) ); + } + + DbConnection *dbConn = getMyConnection(); + + if ( !dbConn->isConnected() || !dbConn->isInitialized() ) + { + error() << "Failed to connect to or initialise database!" << endl; + Amarok::MessageQueue::instance()->addMessage( dbConn->lastError() ); + } + else + { + if ( !isValid() ) + { + //No tables seem to exist (as doing a count(url) didn't even return any number, even 0). + warning() << "Tables seem to not exist." << endl; + warning() << "Attempting to create tables (this should be safe; ignore any errors)..." << endl; + createTables(false); + createPersistentTables(); + createPodcastTables(); + createStatsTable(); + warning() << "Tables should now definitely exist. (Stop ignoring errors)" << endl; + + //Since we have created the tables, we need to make sure the version numbers are + //set to the correct values. If this is not done now, the database update code may + //run, which could corrupt things. + Amarok::config( "Collection Browser" )->writeEntry( "Database Version", DATABASE_VERSION ); + Amarok::config( "Collection Browser" )->writeEntry( "Database Stats Version", DATABASE_STATS_VERSION ); + Amarok::config( "Collection Browser" )->writeEntry( "Database Persistent Tables Version", DATABASE_PERSISTENT_TABLES_VERSION ); + Amarok::config( "Collection Browser" )->writeEntry( "Database Podcast Tables Version", DATABASE_PODCAST_TABLES_VERSION ); Amarok::config( "Collection Browser" )->writeEntry( "Database AFT Version", DATABASE_AFT_VERSION ); + + setAdminValue( "Database Version", QString::number( DATABASE_VERSION ) ); + setAdminValue( "Database Stats Version", QString::number( DATABASE_STATS_VERSION ) ); + setAdminValue( "Database Persistent Tables Version", QString::number( DATABASE_PERSISTENT_TABLES_VERSION ) ); + setAdminValue( "Database Podcast Tables Version", QString::number( DATABASE_PODCAST_TABLES_VERSION ) ); + setAdminValue( "Database AFT Version", QString::number( DATABASE_AFT_VERSION ) ); + } + + + // Due to a bug in our sqllite handling code, we have to recreate the indices. + // We should rmeove this before 1.4.5 + if ( m_dbConnType == DbConnection::sqlite ) { + QStringList indices = query( "SELECT name FROM sqlite_master WHERE type='index' ORDER BY name;" ); + if (!indices.contains("url_tag")) { + createIndices(); + } + } + + + //updates for the Devices table go here + //put all other update code into checkDatabase() + //make sure that there is no call to MountPointManager in CollectionDB's ctor + //or in methods called from the ctor. + if ( adminValue( "Database Devices Version" ).isEmpty() + && Amarok::config( "CollectionBrowser" )->readNumEntry( "Database Devices Version", 0 ) == 0 ) + { + createDevicesTable(); + } + else if ( adminValue( "Database Devices Version" ).toInt() != DATABASE_DEVICES_VERSION + || Amarok::config( "Collection Browser" )->readNumEntry( "Database Devices Version", 0 ) != DATABASE_DEVICES_VERSION ) + { + int prev = adminValue( "Database Devices Version" ).toInt(); + + if ( prev > DATABASE_DEVICES_VERSION || prev < 0 ) + { + error() << "Database devices version too new for this version of Amarok" << endl; + exit( 1 ); + //dropDevicesTable(); + } + else + { + debug() << "Updating DEVICES table" << endl; + //add future Devices update code here + } + } + Amarok::config( "Collection Browser" )->writeEntry( "Database Devices Version", DATABASE_DEVICES_VERSION ); + setAdminValue( "Database Devices Version", QString::number( DATABASE_DEVICES_VERSION ) ); + + //make sure that all indices exist + createIndices(); + createPermanentIndices(); + } + +} + +void +CollectionDB::checkDatabase() +{ + DEBUG_BLOCK + if ( isValid() ) + { + //Inform the user that he should attach as many devices with music as possible + //Hopefully this won't be necessary soon. + // + //Currently broken, so disabled - seems to cause crashes as events are sent to + //the Playlist - maybe it's not fully initialised? + /* + QString text = i18n( "Amarok has to update your database to be able to use the new Dynamic Collection(insert link) feature. Amarok now has to determine on which physical devices your collection is stored. Please attach all removable devices which contain part of your collection and continue. Cancelling will exit Amarok." ); + int result = KMessageBox::warningContinueCancel( 0, text, "Database migration" ); + if ( result != KMessageBox::Continue ) + { + error() << "Dynamic Collection migration was aborted by user...exiting" << endl; + exit( 1 ); + } + */ + + bool needsUpdate = ( adminValue( "Database Stats Version" ).toInt() != DATABASE_STATS_VERSION + || Amarok::config( "Collection Browser" )->readNumEntry( "Database Stats Version", 0 ) != DATABASE_STATS_VERSION + || Amarok::config( "Collection Browser" )->readNumEntry( "Database Version", 0 ) != DATABASE_VERSION + || adminValue( "Database Version" ).toInt() != DATABASE_VERSION + || Amarok::config( "Collection Browser" )->readNumEntry( "Database Persistent Tables Version", 0 ) != DATABASE_PERSISTENT_TABLES_VERSION + || adminValue( "Database Persistent Tables Version" ).toInt() != DATABASE_PERSISTENT_TABLES_VERSION + || Amarok::config( "Collection Browser" )->readNumEntry( "Database Podcast Tables Version", 0 ) != DATABASE_PODCAST_TABLES_VERSION + || adminValue( "Database Podcast Tables Version" ).toInt() != DATABASE_PODCAST_TABLES_VERSION + || Amarok::config( "Collection Browser" )->readNumEntry( "Database AFT Version", 0 ) != DATABASE_AFT_VERSION + || adminValue( "Database AFT Version" ).toInt() != DATABASE_AFT_VERSION ); + + if ( needsUpdate ) + { + + KDialogBase *dialog = new KDialogBase( KDialogBase::Swallow, + Qt::WType_TopLevel|Qt::WStyle_Customize|Qt::WStyle_DialogBorder, + 0, + "Update database warning dialog", + false, + i18n( "Updating database" ), + 0 ); + /* TODO: remove the standard window controls from the dialog window, the user should not be able + to close, minimize, maximize the dialog + add additional text, e.g. Amarok is currently updating your database. This may take a while. + Please wait. + + Consider using a ProgressBarDialog + */ + QLabel *label = new QLabel( i18n( "Updating database" ), dialog ); + dialog->setMainWidget( label ); + label->show(); + QTimer::singleShot( 0, dialog, SLOT( show() ) ); + //process events in the main event loop so that the dialog actually gets shown + kapp->processEvents(); + debug() << "Beginning database update" << endl; + + updateStatsTables(); + + updatePersistentTables(); + + updatePodcastTables(); + + //This is really a one-off call that fixes a Collection Browser glitch + updateGroupBy(); + + //remove database file if version is incompatible + if ( Amarok::config( "Collection Browser" )->readNumEntry( "Database Version", 0 ) != DATABASE_VERSION + || adminValue( "Database Version" ).toInt() != DATABASE_VERSION ) + { + debug() << "Rebuilding database!" << endl; + dropTables(false); + createTables(false); + } + delete dialog; + } + emit databaseUpdateDone(); + } + + // TODO Should write to config in dtor, but it crashes... + Amarok::config( "Collection Browser" )->writeEntry( "Database Version", DATABASE_VERSION ); + Amarok::config( "Collection Browser" )->writeEntry( "Database Stats Version", DATABASE_STATS_VERSION ); + Amarok::config( "Collection Browser" )->writeEntry( "Database Persistent Tables Version", DATABASE_PERSISTENT_TABLES_VERSION ); + Amarok::config( "Collection Browser" )->writeEntry( "Database Podcast Tables Version", DATABASE_PODCAST_TABLES_VERSION ); + Amarok::config( "Collection Browser" )->writeEntry( "Database AFT Version", DATABASE_AFT_VERSION ); + + setAdminValue( "Database Version", QString::number( DATABASE_VERSION ) ); + setAdminValue( "Database Stats Version", QString::number( DATABASE_STATS_VERSION ) ); + setAdminValue( "Database Persistent Tables Version", QString::number( DATABASE_PERSISTENT_TABLES_VERSION ) ); + setAdminValue( "Database Podcast Tables Version", QString::number( DATABASE_PODCAST_TABLES_VERSION ) ); + setAdminValue( "Database AFT Version", QString::number( DATABASE_AFT_VERSION ) ); + + initDirOperations(); +} + +void +CollectionDB::updateGroupBy() +{ + //This ugly bit of code makes sure the Group BY setting is preserved, after the + //meanings of the values were changed due to the addition of the Composer table. + int version = adminValue( "Database Version" ).toInt(); + if (!version) // an even older update + version = Amarok::config( "Collection Browser" )->readNumEntry( "Database Version", 0 ); + + if ( version && version < 32 ) + { + KConfig* config = Amarok::config( "Collection Browser" ); + int m_cat1 = config->readNumEntry( "Category1" ); + int m_cat2 = config->readNumEntry( "Category2" ); + int m_cat3 = config->readNumEntry( "Category3" ); + m_cat1 = m_cat1 ? ( m_cat1 > 2 ? m_cat1 << 1 : m_cat1 ) : CollectionBrowserIds::IdArtist; + m_cat2 = m_cat2 ? ( m_cat2 > 2 ? m_cat2 << 1 : m_cat2 ) : CollectionBrowserIds::IdAlbum; + m_cat3 = m_cat3 ? ( m_cat3 > 2 ? m_cat3 << 1 : m_cat3 ) : CollectionBrowserIds::IdNone; + config->writeEntry( "Category1", m_cat1 ); + config->writeEntry( "Category2", m_cat2 ); + config->writeEntry( "Category3", m_cat3 ); + } +} + +void +CollectionDB::updateStatsTables() +{ + if ( adminValue( "Database Stats Version" ).toInt() != DATABASE_STATS_VERSION + || Amarok::config( "Collection Browser" )->readNumEntry( "Database Stats Version", 0 ) != DATABASE_STATS_VERSION ) + { + debug() << "Different database stats version detected! Stats table will be updated or rebuilt." << endl; + + #if 0 // causes mysterious crashes + if( getType() == DbConnection::sqlite && QFile::exists( Amarok::saveLocation()+"collection.db" ) ) + { + debug() << "Creating a backup of the database in " + << Amarok::saveLocation()+"collection-backup.db" << '.' << endl; + + bool copied = KIO::NetAccess::file_copy( Amarok::saveLocation()+"collection.db", + Amarok::saveLocation()+"collection-backup.db", + -1 /*perms*/, true /*overwrite*/, false /*resume*/ ); + + if( !copied ) + { + debug() << "Backup failed! Perhaps the volume is not writable." << endl; + debug() << "Error was: " << KIO::NetAccess::lastErrorString() << endl; + } + } + #endif + + int prev = adminValue( "Database Stats Version" ).toInt(); + + /* If config returns 3 or lower, it came from an Amarok version that was not aware of + admin table, so we can't trust this table at all */ + if( !prev || ( Amarok::config( "Collection Browser" )->readNumEntry( "Database Stats Version", 0 ) + && Amarok::config( "Collection Browser" )->readNumEntry( "Database Stats Version", 0 ) <= 3 ) ) + prev = Amarok::config( "Collection Browser" )->readNumEntry( "Database Stats Version", 0 ); + + //pre somewhere in the 1.3-1.4 timeframe, the version wasn't stored in the DB, so try to guess it + const QString q = "SELECT COUNT( %1 ) FROM statistics;"; + if( !prev && query( q.arg( "url" ) ).first().toInt() + && query( q.arg( "createdate" ) ).first().toInt() + && query( q.arg( "accessdate" ) ).first().toInt() + && query( q.arg( "percentage" ) ).first().toInt() + && query( q.arg( "playcounter" ) ).first().toInt() ) + { + prev = 3; + } + + if ( prev < 3 ) //it is from before 1.2, or our poor user is otherwise fucked + { + debug() << "Rebuilding stats-database!" << endl; + dropStatsTableV1(); + createStatsTable(); + } + else //Incrementally update the stats table to reach the present version + { + if( prev < 4 ) //every version from 1.2 forward had a stats version of 3 + { + debug() << "Updating stats-database!" << endl; + query( "ALTER TABLE statistics ADD rating INTEGER DEFAULT 0;" ); + query( "CREATE INDEX rating_stats ON statistics( rating );" ); + query( "UPDATE statistics SET rating=0 WHERE " + boolT() + ';' ); + } + if( prev < 5 ) + { + debug() << "Updating stats-database!" << endl; + query( "UPDATE statistics SET rating = rating * 2;" ); + } + if( prev < 8 ) //Versions 6, 7 and 8 all were all attempts to add columns for ATF. his code should do it all. + { + query( QString( "CREATE TABLE statistics_fix (" + "url " + textColumnType() + " UNIQUE," + "createdate INTEGER," + "accessdate INTEGER," + "percentage FLOAT," + "rating INTEGER DEFAULT 0," + "playcounter INTEGER);" ) ); + + insert( "INSERT INTO statistics_fix (url, createdate, accessdate, percentage, playcounter, rating)" + "SELECT url, createdate, accessdate, percentage, playcounter, rating FROM statistics;" + , NULL ); + + dropStatsTableV1(); + createStatsTableV8(); + + insert( "INSERT INTO statistics (url, createdate, accessdate, percentage, playcounter, rating)" + "SELECT url, createdate, accessdate, percentage, playcounter, rating FROM statistics_fix;" + , NULL ); + query( "DROP TABLE statistics_fix" ); + } + if( prev < 9 ) + { + //Update for Dynamic Collection: + + //This is not technically for the stats table, but it is part of the same + //update, so put it here anyway. + MountPointManager::instance()->setCollectionFolders( Amarok::config( "Collection" )->readPathListEntry( "Collection Folders" ) ); + + query( "ALTER TABLE statistics ADD deviceid INTEGER;" ); + + //FIXME: (max) i know this is bad but its fast + QStringList oldURLs = query( "SELECT url FROM statistics;" ); + //it might be necessary to use batch updates to improve speed + debug() << "Updating " << oldURLs.count() << " rows in statistics" << endl; + foreach( oldURLs ) + { + bool exists = QFile::exists( *it ); + int deviceid = exists ? MountPointManager::instance()->getIdForUrl( *it ) : -2; + QString rpath = exists ? MountPointManager::instance()->getRelativePath( deviceid, *it ) : *it; + QString update = QString( "UPDATE statistics SET deviceid = %1, url = '%2' WHERE " ) + .arg( deviceid ) + .arg( escapeString( rpath ) ); + update += QString( "url = '%1';" ).arg( escapeString( *it ) ); + query ( update ); + } + } + if ( prev < 12 ) + { + //re-using old method cause just a slight change to one column... + //if people are upgrading from earlier than 11, just get the new column + //earlier :-) + createStatsTableV10( true ); + query( "INSERT INTO statistics_fix_ten SELECT url,deviceid,createdate," + "accessdate,percentage,rating,playcounter,uniqueid,deleted FROM " + "statistics;" ); + dropStatsTableV1(); + createStatsTableV10( false ); + query( "INSERT INTO statistics SELECT * FROM statistics_fix_ten;" ); + query( "UPDATE statistics SET uniqueid=NULL;" ); + } + else if( prev > DATABASE_STATS_VERSION ) + { + error() << "Database statistics version too new for this version of Amarok. Quitting..." << endl; + exit( 1 ); + } + } + } +} + +void +CollectionDB::updatePersistentTables() +{ + QString PersistentVersion = adminValue( "Database Persistent Tables Version" ); + if ( PersistentVersion.isEmpty() ) + { + /* persistent tables didn't have a version on 1.3X and older, but let's be nice and try to + copy/keep the good information instead of just deleting the tables */ + debug() << "Detected old schema for tables with important data. Amarok will convert the tables, ignore any \"table already exists\" errors." << endl; + createPersistentTables(); + /* Copy lyrics */ + debug() << "Trying to get lyrics from old db schema." << endl; + QStringList Lyrics = query( "SELECT url, lyrics FROM tags where tags.lyrics IS NOT NULL;" ); + for (uint i=0; igetIdForUrl( *it ); + QString rpath = MountPointManager::instance()->getRelativePath( deviceid, *it ); + QString update = QString( "UPDATE lyrics SET deviceid = %1, url = '%2' WHERE " ) + .arg( deviceid ) + .arg( escapeString( rpath ) ); + update += QString( "url = '%1';" ).arg( escapeString( *it ) ); + query ( update ); + } + } + if ( PersistentVersion.toInt() < 15 ) + { + createPersistentTablesV14( true ); + query( "INSERT INTO amazon_fix SELECT asin,locale,filename,refetchdate FROM amazon;" ); + query( "INSERT INTO lyrics_fix SELECT url,deviceid,lyrics FROM lyrics;" ); + query( "INSERT INTO playlists_fix SELECT playlist,url,tracknum FROM playlists;" ); + dropPersistentTablesV14(); + createPersistentTablesV14( false ); + query( "INSERT INTO amazon SELECT * FROM amazon_fix;" ); + query( "INSERT INTO lyrics SELECT * FROM lyrics_fix;" ); + query( "INSERT INTO playlists SELECT * FROM playlists_fix;" ); + } + if ( PersistentVersion.toInt() < 17 ) + { + //drop old labels and label tables, they were never used anyway and just confuse things + query( "DROP TABLE label;" ); + query( "DROP TABLE labels;" ); + query( "DROP TABLE tags_labels;" ); + //update for label support + QString labelsAutoIncrement = ""; + if ( getDbConnectionType() == DbConnection::postgresql ) + { + query( QString( "CREATE SEQUENCE labels_seq;" ) ); + + labelsAutoIncrement = QString("DEFAULT nextval('labels_seq')"); + } + else if ( getDbConnectionType() == DbConnection::mysql ) + { + labelsAutoIncrement = "AUTO_INCREMENT"; + } + + query( QString( "CREATE TABLE labels (" + "id INTEGER PRIMARY KEY " + labelsAutoIncrement + ", " + "name " + textColumnType() + ", " + "type INTEGER);" ) ); + + query( QString( "CREATE TABLE tags_labels (" + "deviceid INTEGER," + "url " + exactTextColumnType() + ", " + "uniqueid " + exactTextColumnType(32) + ", " //m:n relationship, DO NOT MAKE UNIQUE! + "labelid INTEGER REFERENCES labels( id ) ON DELETE CASCADE );" ) ); + + query( "CREATE UNIQUE INDEX labels_name ON labels( name, type );" ); + query( "CREATE INDEX tags_labels_uniqueid ON tags_labels( uniqueid );" ); //m:n relationship, DO NOT MAKE UNIQUE! + query( "CREATE INDEX tags_labels_url ON tags_labels( url, deviceid );" ); //m:n relationship, DO NOT MAKE UNIQUE! + } + if ( PersistentVersion.toInt() < 18 ) + { + query( "ALTER TABLE lyrics ADD uniqueid " + exactTextColumnType(32) + ';' ); + query( "CREATE INDEX lyrics_uniqueid ON lyrics( uniqueid );" ); + } + if ( PersistentVersion.toInt() < 19 ) + { + query( "CREATE INDEX tags_labels_labelid ON tags_labels( labelid );" ); //m:n relationship, DO NOT MAKE UNIQUE! + } + //Up to date. Keep this number \/ in sync! + if ( PersistentVersion.toInt() > 19 || PersistentVersion.toInt() < 0 ) + { + //Something is horribly wrong + if ( adminValue( "Database Persistent Tables Version" ).toInt() != DATABASE_PERSISTENT_TABLES_VERSION ) + { + error() << "There is a bug in Amarok: instead of destroying your valuable" + << " database tables, I'm quitting" << endl; + exit( 1 ); + + debug() << "Rebuilding persistent tables database!" << endl; + dropPersistentTables(); + createPersistentTables(); + } + } + } +} + +void +CollectionDB::updatePodcastTables() +{ + QString PodcastVersion = adminValue( "Database Podcast Tables Version" ); + if ( PodcastVersion.toInt() < 2 ) + { + createPodcastTablesV2( true ); + query( "INSERT INTO podcastchannels_fix SELECT url,title,weblink,image,comment," + "copyright,parent,directory,autoscan,fetchtype,autotransfer,haspurge," + "purgecount FROM podcastchannels;" ); + query( "INSERT INTO podcastepisodes_fix SELECT id,url,localurl,parent,guid,title," + "subtitle,composer,comment,filetype,createdate,length,size,isNew FROM " + "podcastepisodes;" ); + query( "INSERT INTO podcastfolders_fix SELECT id,name,parent,isOpen FROM podcastfolders;" ); + dropPodcastTablesV2(); + createPodcastTablesV2( false ); + query( "INSERT INTO podcastchannels SELECT * FROM podcastchannels_fix;" ); + query( "INSERT INTO podcastepisodes SELECT * FROM podcastepisodes_fix;" ); + query( "INSERT INTO podcastfolders SELECT * FROM podcastfolders_fix;" ); + } + + //Keep this number in sync \/ + if ( PodcastVersion.toInt() > 2 ) + { + error() << "Something is very wrong with the Podcast Tables. Aborting" << endl; + exit( 1 ); + dropPodcastTables(); + createPodcastTables(); + } +} + +void +CollectionDB::vacuum() +{ + if ( DbConnection::sqlite == getDbConnectionType() || + DbConnection::postgresql == getDbConnectionType() ) + { + //Clean up DB and free unused space. + debug() << "Running VACUUM" << endl; + query( "VACUUM;" ); + } +} + +void +CollectionDB::destroy() +{ + //do we need or want to delete the actual connection objects as well as clearing them from the QMap? + //or does QMap's clear function delete them? + //this situation is not at all likely to happen often, so leaving them might be okay to prevent a + //thread from having its connection torn out from under it...not likely, but possible + //and leaving it should not end up eating much memory at all + + connectionMutex->lock(); + + threadConnections->clear(); + delete m_dbConfig; + + connectionMutex->unlock(); +} + +void +CollectionDB::scanModifiedDirs() +{ + if ( !m_scanInProgress + && ( !CollectionView::instance() || !CollectionView::instance()->isOrganizingFiles() ) + && ( !MediaBrowser::instance() || !MediaBrowser::instance()->isTranscoding() ) ) + { + //we check if a job is pending because we don't want to abort incremental collection readings + if ( !ThreadManager::instance()->isJobPending( "CollectionScanner" ) && PlaylistBrowser::instance() ) + { + m_scanInProgress = true; + m_rescanRequired = false; + emit scanStarted(); + + ThreadManager::instance()->onlyOneJob( new ScanController( this, true ) ); + } + } + else + m_rescanRequired = true; +} + + +void +CollectionDB::customEvent( QCustomEvent *e ) +{ + if ( e->type() == (int)ScanController::JobFinishedEvent ) + { + ScanController* s = static_cast( e ); + m_scanInProgress = false; + + if ( s->isIncremental() ) + { + debug() << "JobFinishedEvent from Incremental ScanController received.\n"; + emit scanDone( s->hasChanged() ); + + // check if something changed while we were scanning. in this case we should + // rescan again, now. + if ( m_rescanRequired ) + QTimer::singleShot( 0, CollectionDB::instance(), SLOT( scanMonitor() ) ); + } + else + { + debug() << "JobFinishedEvent from ScanController received.\n"; + emit scanDone( s->wasSuccessful() ); + } + } +} + + +QString +CollectionDB::loadHashFile( const QCString& hash, uint width ) +{ + //debug() << "loadHashFile: " << hash << " - " << width << endl; + + QString full = tagCoverDir().filePath( hash ); + + if ( width == 0 ) { + if ( QFileInfo( full ).isReadable() ) { + //debug() << "loadHashFile: fullsize: " << full << endl; + return full; + } + } else { + if ( width == 1 ) width = AmarokConfig::coverPreviewSize(); + QCString widthKey = makeWidthKey( width ); + + QString path = cacheCoverDir().filePath( widthKey + hash ); + if ( QFileInfo( path ).isReadable() ) { + //debug() << "loadHashFile: scaled: " << path << endl; + return path; + } else if ( QFileInfo( full ).isReadable() ) { + //debug() << "loadHashFile: scaling: " << full << endl; + QImage image( full ); + if ( image.smoothScale( width, width, QImage::ScaleMin ).save( path, "PNG" ) ) { + //debug() << "loadHashFile: scaled: " << path << endl; + return path; + } + } + } + return QString(); +} + + +bool +CollectionDB::extractEmbeddedImage( const MetaBundle &trackInformation, QCString& hash ) +{ + //debug() << "extractEmbeddedImage: " << hash << " - " << trackInformation.url().path() << endl; + + MetaBundle::EmbeddedImageList images; + trackInformation.embeddedImages( images ); + foreachType ( MetaBundle::EmbeddedImageList, images ) { + if ( hash.isEmpty() || (*it).hash() == hash ) { + if ( (*it).save( tagCoverDir() ) ) { + //debug() << "extractEmbeddedImage: saved to " << tagCoverDir().path() << endl; + hash = (*it).hash(); + return true; + } + } + } + return false; +} + +QStringList +CollectionDB::getLabels( const QString &url, const uint type ) +{ + int deviceid = MountPointManager::instance()->getIdForUrl( url ); + QString rpath = MountPointManager::instance()->getRelativePath( deviceid, url ); + return query( QString( "SELECT labels.name FROM labels " + "LEFT JOIN tags_labels ON labels.id = tags_labels.labelid " + "WHERE labels.type = %1 AND tags_labels.deviceid = %2 AND tags_labels.url = '%3';" ) + .arg( type ).arg( deviceid ).arg( escapeString( rpath ) ) ); +} + +void +CollectionDB::cleanLabels() +{ + DEBUG_BLOCK + QStringList labelIds = query( "select labels.id " + "from labels left join tags_labels on labels.id = tags_labels.labelid " + "where tags_labels.labelid is NULL;" ); + if ( !labelIds.isEmpty() ) + { + QString ids; + foreach( labelIds ) + { + if ( !ids.isEmpty() ) + ids += ','; + ids += *it; + } + query( QString( "DELETE FROM labels " + "WHERE labels.id IN ( %1 );" ) + .arg( ids ) ); + } +} + +void +CollectionDB::setLabels( const QString &url, const QStringList &labels, const QString &uid, const uint type ) +{ + DEBUG_BLOCK + int deviceid = MountPointManager::instance()->getIdForUrl( url ); + QString rpath = escapeString( MountPointManager::instance()->getRelativePath( deviceid, url ) ); + QStringList labelIds = query( QString( "SELECT id FROM labels WHERE type = %1;" ).arg( type ) ); + QString ids; + if ( !labelIds.isEmpty() ) + { + foreach( labelIds ) + { + if ( !ids.isEmpty() ) + ids += ','; + ids += *it; + } + //TODO: max: add uniqueid handling + query( QString( "DELETE FROM tags_labels " + "WHERE tags_labels.labelid IN (%1) AND tags_labels.deviceid = %2 AND tags_labels.url = '%3';" ) + .arg( ids, QString::number(deviceid), rpath ) ); + } + + foreach( labels ) + { + int id = query( QString( "SELECT id FROM labels WHERE type = %1 AND name = '%2';" ) + .arg( type ).arg( escapeString( *it ) ) ).first().toInt(); + if ( !id ) + { + id = insert( QString( "INSERT INTO labels( name, type ) VALUES ( '%2', %1 );" ) + .arg( type ).arg( escapeString( *it ) ), "labels" ); + } + insert( QString( "INSERT INTO tags_labels( labelid, deviceid, url, uniqueid ) VALUES ( %1, %2, '%3', '%4' );" ) + .arg( QString::number(id), QString::number(deviceid), rpath, escapeString( uid ) ), 0 ); + } + + emit labelsChanged( url ); +} + +void +CollectionDB::removeLabels( const QString &url, const QStringList &labels, const uint type ) +{ + DEBUG_BLOCK + int deviceid = MountPointManager::instance()->getIdForUrl( url ); + QString rpath = escapeString( MountPointManager::instance()->getRelativePath( deviceid, url ) ); + QString sql = QString( "DELETE FROM tags_labels " + "FROM tags_labels AS t LEFT JOIN labels AS l ON t.labelid = l.id " + "WHERE l.type = %1 AND t.deviceid = %2 AND t.url = '%3' AND ( 0" ) + .arg( type ).arg( deviceid ).arg( rpath ); + foreach( labels ) + { + sql += QString( " OR l.name = '%1'" ).arg( escapeString( *it ) ); + } + sql += ");"; + query( sql ); + + emit labelsChanged( url ); +} + +bool +CollectionDB::addLabel( const QString &url, const QString &label, const QString &uid, const uint type ) +{ + DEBUG_BLOCK + int deviceid = MountPointManager::instance()->getIdForUrl( url ); + QString rpath = escapeString( MountPointManager::instance()->getRelativePath( deviceid, url ) ); + + int id = query( QString( "SELECT id FROM labels WHERE type = %1 AND name = '%2';" ) + .arg( type ).arg( escapeString( label ) ) ).first().toInt(); + bool labelAlreadyExists = id > 0; + if ( !id ) + { + id = insert( QString( "INSERT INTO labels( name, type ) VALUES ( '%2', %1 );" ) + .arg( type ).arg( escapeString( label ) ), "labels" ); + } + if ( labelAlreadyExists ) + { + //we can return if the link between the tags row and the labels row already exists + int count = query( QString( "SELECT COUNT(*) FROM tags_labels WHERE labelid = %1 AND deviceid = %2 AND url = '%3';" ) + .arg( id ).arg( deviceid ).arg( rpath ) ).first().toInt(); + if ( count ) + return false; + } + insert( QString( "INSERT INTO tags_labels( labelid, deviceid, url, uniqueid ) VALUES ( %1, %2, '%3', '%4' );" ) + .arg( QString::number(id), QString::number(deviceid), rpath, escapeString( uid ) ), "tags_labels" ); + + emit labelsChanged( url ); + return true; +} + +QStringList +CollectionDB::favoriteLabels( int type, int count ) +{ + return query( QString( "SELECT labels.name, count( tags_labels.labelid ) " + "FROM labels LEFT JOIN tags_labels ON labels.id = tags_labels.labelid " + "WHERE labels.type = %1 GROUP BY labels.name " + "ORDER BY count(tags_labels.labelid) DESC LIMIT %2;" ) + .arg( QString::number( type ), QString::number( count ) ) ); +} + +QDir +CollectionDB::largeCoverDir() //static +{ + return QDir( Amarok::saveLocation( "albumcovers/large/" ) ); +} + + +QDir +CollectionDB::tagCoverDir() //static +{ + return QDir( Amarok::saveLocation( "albumcovers/tagcover/" ) ); +} + + +QDir +CollectionDB::cacheCoverDir() //static +{ + return QDir( Amarok::saveLocation( "albumcovers/cache/" ) ); +} + + +////////////////////////////////////////////////////////////////////////////////////////// +// CLASS DbConnection +////////////////////////////////////////////////////////////////////////////////////////// + +DbConnection::DbConnection() + : m_initialized( false ) +{} + + +////////////////////////////////////////////////////////////////////////////////////////// +// CLASS SqliteConnection +////////////////////////////////////////////////////////////////////////////////////////// + +SqliteConnection::SqliteConnection( const SqliteConfig* config ) + : DbConnection() + , m_db( 0 ) +{ + + DEBUG_BLOCK + + const QCString path = QFile::encodeName( config->dbFile() ); + + // Open database file and check for correctness + QFile file( path ); + if ( file.open( IO_ReadOnly ) ) + { + QString format; + file.readLine( format, 50 ); + if ( !format.startsWith( "SQLite format 3" ) ) + { + warning() << "Database versions incompatible. Removing and rebuilding database.\n"; + } + else if ( sqlite3_open( path, &m_db ) != SQLITE_OK ) + { + warning() << "Database file corrupt. Removing and rebuilding database.\n"; + sqlite3_close( m_db ); + } + else + m_initialized = true; + } + + if ( !m_initialized ) + { + // Remove old db file; create new + QFile::remove( path ); + if ( sqlite3_open( path, &m_db ) == SQLITE_OK ) + { + m_initialized = true; + } + } + if ( m_initialized ) + { + if( sqlite3_create_function(m_db, "rand", 0, SQLITE_UTF8, NULL, sqlite_rand, NULL, NULL) != SQLITE_OK ) + m_initialized = false; + if( sqlite3_create_function(m_db, "power", 2, SQLITE_UTF8, NULL, sqlite_power, NULL, NULL) != SQLITE_OK ) + m_initialized = false; + if ( sqlite3_create_function(m_db, "like", 2, SQLITE_UTF8, NULL, sqlite_like_new, NULL, NULL) != SQLITE_OK ) + m_initialized = false; + if ( sqlite3_create_function(m_db, "like", 3, SQLITE_UTF8, NULL, sqlite_like_new, NULL, NULL) != SQLITE_OK ) + m_initialized = false; + } + + //optimization for speeding up SQLite + query( "PRAGMA default_synchronous = OFF;" ); +} + + +SqliteConnection::~SqliteConnection() +{ + if ( m_db ) sqlite3_close( m_db ); +} + + +QStringList SqliteConnection::query( const QString& statement, bool /*suppressDebug*/ ) +{ + + QStringList values; + int error; + int rc = 0; + const char* tail; + sqlite3_stmt* stmt; + int busyCnt = 0; + int retryCnt = 0; + + do { + //compile SQL program to virtual machine, reattempting if busy + do { + if ( busyCnt ) + { + ::usleep( 100000 ); // Sleep 100 msec + debug() << "sqlite3_prepare: BUSY counter: " << busyCnt << endl; + } + error = sqlite3_prepare( m_db, statement.utf8(), -1, &stmt, &tail ); + } + while ( SQLITE_BUSY==error && busyCnt++ < 120 ); + + if ( error != SQLITE_OK ) + { + if ( SQLITE_BUSY==error ) + Debug::error() << "Gave up waiting for lock to clear" << endl; + Debug::error() << k_funcinfo << " sqlite3_compile error:" << endl; + Debug::error() << sqlite3_errmsg( m_db ) << endl; + Debug::error() << "on query: " << statement << endl; + values = QStringList(); + break; + } + else + { + busyCnt = 0; + int number = sqlite3_column_count( stmt ); + //execute virtual machine by iterating over rows + while ( true ) + { + error = sqlite3_step( stmt ); + + if ( error == SQLITE_BUSY ) + { + if ( busyCnt++ > 120 ) { + Debug::error() << "Busy-counter has reached maximum. Aborting this sql statement!\n"; + break; + } + ::usleep( 100000 ); // Sleep 100 msec + debug() << "sqlite3_step: BUSY counter: " << busyCnt << endl; + continue; + } + if ( error == SQLITE_MISUSE ) + debug() << "sqlite3_step: MISUSE" << endl; + if ( error == SQLITE_DONE || error == SQLITE_ERROR ) + break; + + //iterate over columns + for ( int i = 0; i < number; i++ ) + { + values << QString::fromUtf8( reinterpret_cast( sqlite3_column_text( stmt, i ) ) ); + } + } + //deallocate vm resources + rc = sqlite3_finalize( stmt ); + + if ( error != SQLITE_DONE && rc != SQLITE_SCHEMA ) + { + Debug::error() << k_funcinfo << "sqlite_step error.\n"; + Debug::error() << sqlite3_errmsg( m_db ) << endl; + Debug::error() << "on query: " << statement << endl; + values = QStringList(); + } + if ( rc == SQLITE_SCHEMA ) + { + retryCnt++; + debug() << "SQLITE_SCHEMA error occurred on query: " << statement << endl; + if ( retryCnt < 10 ) + debug() << "Retrying now." << endl; + else + { + Debug::error() << "Retry-Count has reached maximum. Aborting this SQL statement!" << endl; + Debug::error() << "SQL statement: " << statement << endl; + values = QStringList(); + } + } + } + } + while ( rc == SQLITE_SCHEMA && retryCnt < 10 ); + + return values; +} + + +int SqliteConnection::insert( const QString& statement, const QString& /* table */ ) +{ + int error; + int rc = 0; + const char* tail; + sqlite3_stmt* stmt; + int busyCnt = 0; + int retryCnt = 0; + + do { + //compile SQL program to virtual machine, reattempting if busy + do { + if ( busyCnt ) + { + ::usleep( 100000 ); // Sleep 100 msec + debug() << "sqlite3_prepare: BUSY counter: " << busyCnt << endl; + } + error = sqlite3_prepare( m_db, statement.utf8(), -1, &stmt, &tail ); + } + while ( SQLITE_BUSY==error && busyCnt++ < 120 ); + + if ( error != SQLITE_OK ) + { + if ( SQLITE_BUSY==error ) + Debug::error() << "Gave up waiting for lock to clear" << endl; + Debug::error() << k_funcinfo << " sqlite3_compile error:" << endl; + Debug::error() << sqlite3_errmsg( m_db ) << endl; + Debug::error() << "on insert: " << statement << endl; + break; + } + else + { + busyCnt = 0; + //execute virtual machine by iterating over rows + while ( true ) + { + error = sqlite3_step( stmt ); + + if ( error == SQLITE_BUSY ) + { + if ( busyCnt++ > 120 ) { + Debug::error() << "Busy-counter has reached maximum. Aborting this sql statement!\n"; + break; + } + ::usleep( 100000 ); // Sleep 100 msec + debug() << "sqlite3_step: BUSY counter: " << busyCnt << endl; + } + if ( error == SQLITE_MISUSE ) + debug() << "sqlite3_step: MISUSE" << endl; + if ( error == SQLITE_DONE || error == SQLITE_ERROR ) + break; + } + //deallocate vm resources + rc = sqlite3_finalize( stmt ); + + if ( error != SQLITE_DONE && rc != SQLITE_SCHEMA) + { + Debug::error() << k_funcinfo << "sqlite_step error.\n"; + Debug::error() << sqlite3_errmsg( m_db ) << endl; + Debug::error() << "on insert: " << statement << endl; + } + if ( rc == SQLITE_SCHEMA ) + { + retryCnt++; + debug() << "SQLITE_SCHEMA error occurred on insert: " << statement << endl; + if ( retryCnt < 10 ) + debug() << "Retrying now." << endl; + else + { + Debug::error() << "Retry-Count has reached maximum. Aborting this SQL insert!" << endl; + Debug::error() << "SQL statement: " << statement << endl; + } + } + } + } + while ( SQLITE_SCHEMA == rc && retryCnt < 10 ); + return sqlite3_last_insert_rowid( m_db ); +} + + +// this implements a RAND() function compatible with the MySQL RAND() (0-param-form without seed) +void SqliteConnection::sqlite_rand(sqlite3_context *context, int /*argc*/, sqlite3_value ** /*argv*/) +{ + sqlite3_result_double( context, static_cast(KApplication::random()) / (RAND_MAX+1.0) ); +} + +// this implements a POWER() function compatible with the MySQL POWER() +void SqliteConnection::sqlite_power(sqlite3_context *context, int argc, sqlite3_value **argv) +{ + Q_ASSERT( argc==2 ); + if( sqlite3_value_type(argv[0])==SQLITE_NULL || sqlite3_value_type(argv[1])==SQLITE_NULL ) { + sqlite3_result_null(context); + return; + } + double a = sqlite3_value_double(argv[0]); + double b = sqlite3_value_double(argv[1]); + sqlite3_result_double( context, pow(a,b) ); +} + +// this implements a LIKE() function that overrides the default string comparison function +// Reason: default function is case-sensitive for utf8 strings (BUG: 116458, ...) +void SqliteConnection::sqlite_like_new( sqlite3_context *context, int argc, sqlite3_value **argv ) +{ + + const unsigned char *zA = sqlite3_value_text( argv[0] ); + const unsigned char *zB = sqlite3_value_text( argv[1] ); + + QString pattern = QString::fromUtf8( (const char*)zA ); + QString text = QString::fromUtf8( (const char*)zB ); + + int begin = pattern.startsWith( "%" ), end = pattern.endsWith( "%" ); + if (begin) + pattern = pattern.right( pattern.length() - 1 ); + if (end) + pattern = pattern.left( pattern.length() - 1 ); + + if( argc == 3 ) // The function is given an escape character. In likeCondition() it defaults to '/' + pattern.replace( "/%", "%" ).replace( "/_", "_" ).replace( "//", "/" ); + + int result = 0; + if ( begin && end ) result = ( text.find( pattern, 0, 0 ) != -1); + else if ( begin ) result = text.endsWith( pattern, 0 ); + else if ( end ) result = text.startsWith( pattern, 0 ); + else result = ( text.lower() == pattern.lower() ); + + sqlite3_result_int( context, result ); +} + +////////////////////////////////////////////////////////////////////////////////////////// +// CLASS MySqlConnection +////////////////////////////////////////////////////////////////////////////////////////// + +#ifdef USE_MYSQL +MySqlConnection::MySqlConnection( const MySqlConfig* config ) + : DbConnection() + , m_connected( false ) +{ + DEBUG_BLOCK + + debug() << k_funcinfo << endl; + m_db = mysql_init(NULL); + if (m_db) + { +// if ( config->username().isEmpty() ) +// pApp->slotConfigAmarok("MySql"); + + if ( mysql_real_connect( m_db, config->host().latin1(), + config->username().latin1(), + config->password().latin1(), + config->database().latin1(), + config->port(), + NULL, CLIENT_COMPRESS ) ) + { + m_initialized = true; + +#if MYSQL_VERSION_ID >= 40113 + // now set the right charset for the connection + QStringList my_qslist = query( "SHOW VARIABLES LIKE 'character_set_database'" ); + if( !my_qslist.isEmpty() && !mysql_set_character_set( m_db, const_cast( my_qslist[1].latin1() ) ) ) + //charset was updated + debug() << "Connection Charset is now: " << my_qslist[1].latin1() << endl; + else + error() << "Failed to set database charset\n"; +#endif + + m_db->reconnect = 1; //setting reconnect flag for newer mysqld + m_connected = true; + } + else + { + + if ( mysql_real_connect( + m_db, + config->host().latin1(), + config->username().latin1(), + config->password().latin1(), + NULL, + config->port(), + NULL, CLIENT_COMPRESS)) + { + if ( mysql_query(m_db, + QString( "CREATE DATABASE " + config->database() ).latin1() ) ) + { m_connected = true; m_initialized = true; } + else + { setMysqlError(); } + } + else + setMysqlError(); + + } + } + else + error() << "Failed to allocate/initialize MySql struct\n"; +} + + +MySqlConnection::~MySqlConnection() +{ + if ( m_db ) mysql_close( m_db ); +} + + +QStringList MySqlConnection::query( const QString& statement, bool suppressDebug ) +{ + QStringList values; + + if ( !mysql_query( m_db, statement.utf8() ) ) + { + MYSQL_RES* result; + if ( ( result = mysql_use_result( m_db ) ) ) + { + int number = mysql_field_count( m_db ); + MYSQL_ROW row; + while ( ( row = mysql_fetch_row( result ) ) ) + { + for ( int i = 0; i < number; i++ ) + { + values << QString::fromUtf8( (const char*)row[i] ); + } + } + } + else + { + if ( mysql_field_count( m_db ) != 0 ) + { + if ( !suppressDebug ) + debug() << "MYSQL QUERY FAILED: " << mysql_error( m_db ) << "\n" << "FAILED QUERY: " << statement << "\n"; + values = QStringList(); + } + } + mysql_free_result( result ); + } + else + { + if ( !suppressDebug ) + debug() << "MYSQL QUERY FAILED: " << mysql_error( m_db ) << "\n" << "FAILED QUERY: " << statement << "\n"; + values = QStringList(); + } + + return values; +} + + +int MySqlConnection::insert( const QString& statement, const QString& /* table */ ) +{ + mysql_query( m_db, statement.utf8() ); + if ( mysql_errno( m_db ) ) + { + debug() << "MYSQL INSERT FAILED: " << mysql_error( m_db ) << "\n" << "FAILED INSERT: " << statement << endl; + } + return mysql_insert_id( m_db ); +} + + +void +MySqlConnection::setMysqlError() +{ + m_error = i18n("MySQL reported the following error:
") + mysql_error(m_db) + + i18n("

You can configure MySQL in the Collection section under Settings->Configure Amarok

"); +} +#endif + + +////////////////////////////////////////////////////////////////////////////////////////// +// CLASS PostgresqlConnection +////////////////////////////////////////////////////////////////////////////////////////// + +#ifdef USE_POSTGRESQL +PostgresqlConnection::PostgresqlConnection( const PostgresqlConfig* config ) + : DbConnection() + , m_connected( false ) +{ + QString conninfo; + debug() << k_funcinfo << endl; + +// if ( config->username().isEmpty() ) +// pApp->slotConfigAmarok("Postgresql"); + + conninfo = "host='" + config->host() + + "' port=" + QString::number( config->port() ) + + " dbname='" + config->database() + + "' user='" + config->username() + + "' password='" + config->password() + '\''; + + m_db = PQconnectdb( conninfo.utf8() ); + + if (!m_db) + { + debug() << "POSTGRESQL CONNECT FAILED: " << PQerrorMessage( m_db ) << "\n"; + error() << "Failed to allocate/initialize Postgresql struct\n"; + setPostgresqlError(); + return; + } + + if (PQstatus(m_db) != CONNECTION_OK) + { + debug() << "POSTGRESQL CONNECT FAILED: " << PQerrorMessage( m_db ) << "\n"; + error() << "Failed to allocate/initialize Postgresql struct\n"; + setPostgresqlError(); + PQfinish(m_db); + m_db = NULL; + return; + } + + m_initialized = true; + m_connected = true; +} + + +PostgresqlConnection::~PostgresqlConnection() +{ + if ( m_db ) PQfinish( m_db ); +} + + +QStringList PostgresqlConnection::query( const QString& statement, bool suppressDebug ) +{ + QStringList values; + PGresult* result; + ExecStatusType status; + + result = PQexec(m_db, statement.utf8()); + if (result == NULL) + { + if ( !suppressDebug ) + debug() << "POSTGRESQL QUERY FAILED: " << PQerrorMessage( m_db ) << "\n" << "FAILED QUERY: " << statement << "\n"; + return values; + } + + status = PQresultStatus(result); + if ((status != PGRES_COMMAND_OK) && (status != PGRES_TUPLES_OK)) + { + if ( !suppressDebug ) + debug() << "POSTGRESQL QUERY FAILED: " << PQerrorMessage( m_db ) << "\n" << "FAILED QUERY: " << statement << "\n"; + PQclear(result); + return values; + } + + int cols = PQnfields( result ); + int rows = PQntuples( result ); + QMap discardCols; + for(int col=0; col< cols; col++) { + if (QString(PQfname(result, col)) == QString("__discard") || QString(PQfname(result, col)) == QString("__random")) + { + discardCols[col] = true; + } + } + + for(int row=0; row< rows; row++) + { + for(int col=0; col< cols; col++) + { + if (discardCols[col]) continue; + + values << QString::fromUtf8(PQgetvalue(result, row, col)); + } + } + + PQclear(result); + + return values; +} + + +int PostgresqlConnection::insert( const QString& statement, const QString& table ) +{ + PGresult* result; + ExecStatusType status; + QString curvalSql; + int id; + + result = PQexec(m_db, statement.utf8()); + if (result == NULL) + { + debug() << "POSTGRESQL INSERT FAILED: " << PQerrorMessage( m_db ) << "\n" << "FAILED SQL: " << statement << "\n"; + return 0; + } + + status = PQresultStatus(result); + if (status != PGRES_COMMAND_OK) + { + debug() << "POSTGRESQL INSERT FAILED: " << PQerrorMessage( m_db ) << "\n" << "FAILED SQL: " << statement << "\n"; + PQclear(result); + return 0; + } + PQclear(result); + + if (table == NULL) return 0; + + QString _table = table; + if (table.find("_temp") > 0) _table = table.left(table.find("_temp")); + + curvalSql = QString("SELECT currval('%1_seq');").arg(_table); + result = PQexec(m_db, curvalSql.utf8()); + if (result == NULL) + { + debug() << "POSTGRESQL INSERT FAILED: " << PQerrorMessage( m_db ) << "\n" << "FAILED SQL: " << curvalSql << "\n"; + return 0; + } + + status = PQresultStatus(result); + if (status != PGRES_TUPLES_OK) + { + debug() << "POSTGRESQL INSERT FAILED: " << PQerrorMessage( m_db ) << "\n" << "FAILED SQL: " << curvalSql << "\n"; + PQclear(result); + return 0; + } + + if ((PQnfields( result ) != 1) || (PQntuples( result ) != 1)) + { + debug() << "POSTGRESQL INSERT FAILED: " << PQerrorMessage( m_db ) << "\n" << "FAILED SQL: " << curvalSql << "\n"; + PQclear(result); + return 0; + } + + id = QString::fromUtf8(PQgetvalue(result, 0, 0)).toInt(); + PQclear(result); + + return id; +} + + +void PostgresqlConnection::setPostgresqlError() +{ + m_error = i18n("Postgresql reported the following error:
") + PQerrorMessage(m_db) + + i18n("

You can configure Postgresql in the Collection section under Settings->Configure Amarok

"); +} +#endif + + + +////////////////////////////////////////////////////////////////////////////////////////// +// CLASS SqliteConfig +////////////////////////////////////////////////////////////////////////////////////////// + +SqliteConfig::SqliteConfig( const QString& dbfile ) + : m_dbfile( dbfile ) +{} + + +////////////////////////////////////////////////////////////////////////////////////////// +// CLASS MySqlConfig +////////////////////////////////////////////////////////////////////////////////////////// + +MySqlConfig::MySqlConfig( + const QString& host, + const int port, + const QString& database, + const QString& username, + const QString& password ) + : m_host( host ), + m_port( port ), + m_database( database ), + m_username( username ), + m_password( password ) +{} + + +////////////////////////////////////////////////////////////////////////////////////////// +// CLASS PostgresqlConfig +////////////////////////////////////////////////////////////////////////////////////////// + +PostgresqlConfig::PostgresqlConfig( + const QString& host, + const int port, + const QString& database, + const QString& username, + const QString& password ) + : m_host( host ), + m_port( port ), + m_database( database ), + m_username( username ), + m_password( password ) +{} + +////////////////////////////////////////////////////////////////////////////////////////// +// CLASS QueryBuilder +////////////////////////////////////////////////////////////////////////////////////////// + +QueryBuilder::QueryBuilder() +{ + m_OR.push(false); + clear(); + // there are a few string members with a large number of appends. to + // avoid reallocations, pre-reserve 1024 bytes and try never to assign + // it, instead doing setLength(0) and appends + // Note: unfortunately, QT3's setLength(), which is also called from append, + // squeezes the string if it's less than 4x the length. So this is useless. + // Uncomment after porting to QT4 if it's smarter about this, as the docs say. +// m_query.reserve(1024); +// m_values.reserve(1024); +// m_tables.reserve(1024); +} + + +void +QueryBuilder::linkTables( int tables ) +{ + m_tables.setLength(0); + + m_tables += tableName( tabSong ); + + if ( !(tables & tabSong ) ) + { + // check if only one table is selected (does somebody know a better way to check that?) + if (tables == tabAlbum || tables==tabArtist || tables==tabGenre || tables == tabYear || tables == tabStats || tables == tabPodcastEpisodes || tables == tabPodcastFolders || tables == tabPodcastChannels || tables == tabLabels) { + m_tables.setLength( 0 ); + m_tables += tableName(tables); + } + else + tables |= tabSong; + } + + if ( tables & tabSong ) + { + if ( tables & tabAlbum ) + ((m_tables += " LEFT JOIN ") += tableName( tabAlbum)) += " ON album.id=tags.album"; + if ( tables & tabArtist ) + ((m_tables += " LEFT JOIN ") += tableName( tabArtist)) += " ON artist.id=tags.artist"; + if ( tables & tabComposer ) + ((m_tables += " LEFT JOIN ") += tableName( tabComposer)) += " ON composer.id=tags.composer"; + if ( tables & tabGenre ) + ((m_tables += " LEFT JOIN ") += tableName( tabGenre)) += " ON genre.id=tags.genre"; + if ( tables & tabYear ) + ((m_tables += " LEFT JOIN ") += tableName( tabYear)) += " ON year.id=tags.year"; + if ( tables & tabStats ) + { + ((m_tables += " LEFT JOIN ") += tableName( tabStats)) + += " ON statistics.url=tags.url AND statistics.deviceid = tags.deviceid"; + //if ( !m_url.isEmpty() ) { + // QString url = QString( '.' ) + m_url; + // m_tables += QString( " OR statistics.deviceid = -1 AND statistics.url = '%1'" ) + // .arg( CollectionDB::instance()->escapeString( url ) ); + //} + } + if ( tables & tabLyrics ) + ((m_tables += " LEFT JOIN ") += tableName( tabLyrics)) + += " ON lyrics.url=tags.url AND lyrics.deviceid = tags.deviceid"; + + if ( tables & tabDevices ) + ((m_tables += " LEFT JOIN ") += tableName( tabDevices )) += " ON tags.deviceid = devices.id"; + if ( tables & tabLabels ) + ( m_tables += " LEFT JOIN tags_labels ON tags.url = tags_labels.url AND tags.deviceid = tags_labels.deviceid" ) + += " LEFT JOIN labels ON tags_labels.labelid = labels.id"; + } +} + + +void +QueryBuilder::addReturnValue( int table, Q_INT64 value, bool caseSensitive /* = false, unless value refers to a string */ ) +{ + caseSensitive |= value == valName || value == valTitle || value == valComment; + + if ( !m_values.isEmpty() && m_values != "DISTINCT " ) m_values += ','; + + if ( value == valDummy ) + m_values += "''"; + else + { + if ( caseSensitive && CollectionDB::instance()->getType() == DbConnection::mysql ) + m_values += "BINARY "; + m_values += tableName( table ) + '.'; + m_values += valueName( value ); + } + + m_linkTables |= table; + m_returnValues++; + if ( value & valURL ) + { + // make handling of deviceid transparent to calling code + m_deviceidPos = m_returnValues + 1; //the return value after the url is the deviceid + m_values += ','; + m_values += tableName( table ); + m_values += '.'; + m_values += valueName( valDeviceId ); + } +} + +void +QueryBuilder::addReturnFunctionValue( int function, int table, Q_INT64 value) +{ + // translate NULL and 0 values into the default value for percentage/rating + // First translate 0 to NULL via NULLIF, then NULL to default via COALESCE + bool defaults = function == funcAvg && ( value & valScore || value & valRating ); + + if ( !m_values.isEmpty() && m_values != "DISTINCT " ) m_values += ','; + m_values += functionName( function ) + '('; + if ( defaults ) + m_values += "COALESCE(NULLIF("; + m_values += tableName( table ) + '.'; + m_values += valueName( value ); + if ( defaults ) + { + m_values += ", 0), "; + if ( value & valScore ) + m_values += "50"; + else + m_values += '6'; + m_values += ')'; + } + m_values += ") AS "; + m_values += functionName( function )+tableName( table )+valueName( value ); + + m_linkTables |= table; + if ( !m_showAll ) m_linkTables |= tabSong; + m_returnValues++; +} + +uint +QueryBuilder::countReturnValues() +{ + return m_returnValues; +} + +void +QueryBuilder::addURLFilters( const QStringList& filter ) +{ + if ( !filter.isEmpty() ) + { + m_where += ANDslashOR() + " ( " + CollectionDB::instance()->boolF() + ' '; + + for ( uint i = 0; i < filter.count(); i++ ) + { + int deviceid = MountPointManager::instance()->getIdForUrl( filter[i] ); + QString rpath = MountPointManager::instance()->getRelativePath( deviceid , filter[i] ); + m_where += "OR (tags.url = '" + CollectionDB::instance()->escapeString( rpath ) + "' "; + m_where += QString( "AND tags.deviceid = %1 ) " ).arg( QString::number( deviceid ) ); + //TODO MountPointManager fix this + } + + m_where += " ) "; + } + + m_linkTables |= tabSong; +} + +void +QueryBuilder::setGoogleFilter( int defaultTables, QString query ) +{ + //TODO MountPointManager fix google syntax + //no clue about what needs to be done atm + ParsedExpression parsed = ExpressionParser::parse( query ); + + for( uint i = 0, n = parsed.count(); i < n; ++i ) //check each part for matchiness + { + beginOR(); + for( uint ii = 0, nn = parsed[i].count(); ii < nn; ++ii ) + { + const expression_element &e = parsed[i][ii]; + QString s = e.text; + int mode; + switch( e.match ) + { + case expression_element::More: mode = modeGreater; break; + case expression_element::Less: mode = modeLess; break; + case expression_element::Contains: + default: mode = modeNormal; break; + } + bool exact = false; // enable for numeric values + + int table = -1; + Q_INT64 value = -1; + if( e.field == "artist" ) + table = tabArtist; + else if( e.field == "composer" ) + table = tabComposer; + else if( e.field == "album" ) + table = tabAlbum; + else if( e.field == "title" ) + table = tabSong; + else if( e.field == "genre" ) + table = tabGenre; + else if( e.field == "year" ) + { + table = tabYear; + value = valName; + exact = true; + } + else if( e.field == "score" ) + { + table = tabStats; + value = valScore; + exact = true; + } + else if( e.field == "rating" ) + { + table = tabStats; + value = valRating; + exact = true; + s = QString::number( int( s.toFloat() * 2 ) ); + } + else if( e.field == "directory" ) + { + table = tabSong; + value = valDirectory; + } + else if( e.field == "length" ) + { + table = tabSong; + value = valLength; + exact = true; + } + else if( e.field == "playcount" ) + { + table = tabStats; + value = valPlayCounter; + exact = true; + } + else if( e.field == "samplerate" ) + { + table = tabSong; + value = valSamplerate; + exact = true; + } + else if( e.field == "track" ) + { + table = tabSong; + value = valTrack; + exact = true; + } + else if( e.field == "disc" || e.field == "discnumber" ) + { + table = tabSong; + value = valDiscNumber; + exact = true; + } + else if( e.field == "size" || e.field == "filesize" ) + { + table = tabSong; + value = valFilesize; + exact = true; + if( s.lower().endsWith( "m" ) ) + s = QString::number( s.left( s.length()-1 ).toLong() * 1024 * 1024 ); + else if( s.lower().endsWith( "k" ) ) + s = QString::number( s.left( s.length()-1 ).toLong() * 1024 ); + } + else if( e.field == "filename" || e.field == "url" ) + { + table = tabSong; + value = valURL; + } + else if( e.field == "filetype" || e.field == "type" ) + { + table = tabSong; + value = valURL; + mode = modeEndMatch; + s.prepend( '.' ); + } + else if( e.field == "bitrate" ) + { + table = tabSong; + value = valBitrate; + exact = true; + } + else if( e.field == "comment" ) + { + table = tabSong; + value = valComment; + } + else if( e.field == "bpm" ) + { + table = tabSong; + value = valBPM; + exact = true; + } + else if( e.field == "lyrics" ) + { + table = tabLyrics; + value = valLyrics; + } + else if( e.field == "device" ) + { + table = tabDevices; + value = valDeviceLabel; + } + else if( e.field == "mountpoint" ) + { + table = tabDevices; + value = valMountPoint; + } + else if( e.field == "label" ) + { + table = tabLabels; + value = valName; + } + + if( e.negate ) + { + if( value >= 0 ) + excludeFilter( table, value, s, mode, exact ); + else + excludeFilter( table >= 0 ? table : defaultTables, s ); + } + else + { + if( value >= 0 ) + addFilter( table, value, s, mode, exact ); + else + addFilter( table >= 0 ? table : defaultTables, s ); + } + } + endOR(); + } +} + +void +QueryBuilder::addFilter( int tables, const QString& filter ) +{ + if ( !filter.isEmpty() ) + { + m_where += ANDslashOR() + " ( " + CollectionDB::instance()->boolF() + ' '; + + if ( tables & tabAlbum ) + m_where += "OR album.name " + CollectionDB::likeCondition( filter, true, true ); + if ( tables & tabArtist ) + m_where += "OR artist.name " + CollectionDB::likeCondition( filter, true, true ); + if ( tables & tabComposer ) + m_where += "OR composer.name " + CollectionDB::likeCondition( filter, true, true ); + if ( tables & tabGenre ) + m_where += "OR genre.name " + CollectionDB::likeCondition( filter, true, true ); + if ( tables & tabYear ) + m_where += "OR year.name " + CollectionDB::likeCondition( filter, false, false ); + if ( tables & tabSong ) + m_where += "OR tags.title " + CollectionDB::likeCondition( filter, true, true ); + if ( tables & tabLabels ) + m_where += "OR labels.name " + CollectionDB::likeCondition( filter, true, true ); + + if ( i18n( "Unknown" ).contains( filter, false ) ) + { + if ( tables & tabAlbum ) + m_where += "OR album.name = '' "; + if ( tables & tabArtist ) + m_where += "OR artist.name = '' "; + if ( tables & tabComposer ) + m_where += "OR composer.name = '' "; + if ( tables & tabGenre ) + m_where += "OR genre.name = '' "; + if ( tables & tabYear ) + m_where += "OR year.name = '' "; + if ( tables & tabSong ) + m_where += "OR tags.title = '' "; + } + if ( ( tables & tabArtist ) && i18n( "Various Artists" ).contains( filter, false ) ) + m_where += QString( "OR tags.sampler = %1 " ).arg( CollectionDB::instance()->boolT() ); + m_where += " ) "; + } + + m_linkTables |= tables; +} + +void +QueryBuilder::addFilter( int tables, Q_INT64 value, const QString& filter, int mode, bool exact ) +{ + //true for INTEGER fields (see comment of coalesceField(int, Q_INT64) + bool useCoalesce = coalesceField( tables, value ); + m_where += ANDslashOR() + " ( "; + + QString m, s; + if (mode == modeLess || mode == modeGreater) + { + QString escapedFilter; + if (useCoalesce && DbConnection::sqlite == CollectionDB::instance()->getDbConnectionType()) + escapedFilter = CollectionDB::instance()->escapeString( filter ); + else + escapedFilter = "'" + CollectionDB::instance()->escapeString( filter ) + "' "; + s = ( mode == modeLess ? "< " : "> " ) + escapedFilter; + } + else + { + if (exact) + if (useCoalesce && DbConnection::sqlite == CollectionDB::instance()->getDbConnectionType()) + s = " = " +CollectionDB::instance()->escapeString( filter ) + ' '; + else + s = " = '" + CollectionDB::instance()->escapeString( filter ) + "' "; + else + s = CollectionDB::likeCondition( filter, mode != modeBeginMatch, mode != modeEndMatch ); + } + + if( coalesceField( tables, value ) ) + m_where += QString( "COALESCE(%1.%2,0) " ).arg( tableName( tables ) ).arg( valueName( value ) ) + s; + else + m_where += QString( "%1.%2 " ).arg( tableName( tables ) ).arg( valueName( value ) ) + s; + + if ( !exact && (value & valName) && mode == modeNormal && i18n( "Unknown").contains( filter, false ) ) + m_where += QString( "OR %1.%2 = '' " ).arg( tableName( tables ) ).arg( valueName( value ) ); + + m_where += " ) "; + + m_linkTables |= tables; +} + +void +QueryBuilder::addFilters( int tables, const QStringList& filter ) +{ + if ( !filter.isEmpty() ) + { + m_where += ANDslashOR() + " ( " + CollectionDB::instance()->boolT() + ' '; + + for ( uint i = 0; i < filter.count(); i++ ) + { + m_where += ANDslashOR() + " ( " + CollectionDB::instance()->boolF() + ' '; + + if ( tables & tabAlbum ) + m_where += "OR album.name " + CollectionDB::likeCondition( filter[i], true, true ); + if ( tables & tabArtist ) + m_where += "OR artist.name " + CollectionDB::likeCondition( filter[i], true, true ); + if ( tables & tabComposer ) + m_where += "OR composer.name " + CollectionDB::likeCondition( filter[i], true, true ); + if ( tables & tabGenre ) + m_where += "OR genre.name " + CollectionDB::likeCondition( filter[i], true, true ); + if ( tables & tabYear ) + m_where += "OR year.name " + CollectionDB::likeCondition( filter[i], false, false ); + if ( tables & tabSong ) + m_where += "OR tags.title " + CollectionDB::likeCondition( filter[i], true, true ); + if ( tables & tabLabels ) + m_where += "OR labels.name " + CollectionDB::likeCondition( filter[i], true, true ); + + if ( i18n( "Unknown" ).contains( filter[i], false ) ) + { + if ( tables & tabAlbum ) + m_where += "OR album.name = '' "; + if ( tables & tabArtist ) + m_where += "OR artist.name = '' "; + if ( tables & tabComposer ) + m_where += "OR composer.name = '' "; + if ( tables & tabGenre ) + m_where += "OR genre.name = '' "; + if ( tables & tabYear ) + m_where += "OR year.name = '' "; + if ( tables & tabSong ) + m_where += "OR tags.title = '' "; + } + if ( i18n( "Various Artists" ).contains( filter[ i ], false ) && ( tables & tabArtist ) ) + m_where += "OR tags.sampler = " + CollectionDB::instance()->boolT() + ' '; + m_where += " ) "; + } + + m_where += " ) "; + } + + m_linkTables |= tables; +} + +void +QueryBuilder::excludeFilter( int tables, const QString& filter ) +{ + if ( !filter.isEmpty() ) + { + m_where += ANDslashOR() + " ( " + CollectionDB::instance()->boolT() + ' '; + + + if ( tables & tabAlbum ) + m_where += "AND album.name NOT " + CollectionDB::likeCondition( filter, true, true ); + if ( tables & tabArtist ) + m_where += "AND artist.name NOT " + CollectionDB::likeCondition( filter, true, true ); + if ( tables & tabComposer ) + m_where += "AND composer.name NOT " + CollectionDB::likeCondition( filter, true, true ); + if ( tables & tabGenre ) + m_where += "AND genre.name NOT " + CollectionDB::likeCondition( filter, true, true ); + if ( tables & tabYear ) + m_where += "AND year.name NOT " + CollectionDB::likeCondition( filter, false, false ); + if ( tables & tabSong ) + m_where += "AND tags.title NOT " + CollectionDB::likeCondition( filter, true, true ); + if ( tables & tabLabels ) + m_where += "AND labels.name NOT " + CollectionDB::likeCondition( filter, true, true ); + + if ( i18n( "Unknown" ).contains( filter, false ) ) + { + if ( tables & tabAlbum ) + m_where += "AND album.name <> '' "; + if ( tables & tabArtist ) + m_where += "AND artist.name <> '' "; + if ( tables & tabComposer ) + m_where += "AND composer.name <> '' "; + if ( tables & tabGenre ) + m_where += "AND genre.name <> '' "; + if ( tables & tabYear ) + m_where += "AND year.name <> '' "; + if ( tables & tabSong ) + m_where += "AND tags.title <> '' "; + } + + if ( i18n( "Various Artists" ).contains( filter, false ) && ( tables & tabArtist ) ) + m_where += "AND tags.sampler = " + CollectionDB::instance()->boolF() + ' '; + + + m_where += " ) "; + } + + m_linkTables |= tables; +} + +void +QueryBuilder::excludeFilter( int tables, Q_INT64 value, const QString& filter, int mode, bool exact ) +{ + m_where += ANDslashOR() + " ( "; + + QString m, s; + if (mode == modeLess || mode == modeGreater) + s = ( mode == modeLess ? ">= '" : "<= '" ) + CollectionDB::instance()->escapeString( filter ) + "' "; + else + { + if (exact) + { + bool isNumber; + filter.toInt( &isNumber ); + if (isNumber) + s = " <> " + CollectionDB::instance()->escapeString( filter ) + " "; + else + s = " <> '" + CollectionDB::instance()->escapeString( filter ) + "' "; + } + else + s = "NOT " + CollectionDB::instance()->likeCondition( filter, mode != modeBeginMatch, mode != modeEndMatch ) + ' '; + } + + if( coalesceField( tables, value ) ) + m_where += QString( "COALESCE(%1.%2,0) " ).arg( tableName( tables ) ).arg( valueName( value ) ) + s; + else + m_where += QString( "%1.%2 " ).arg( tableName( tables ) ).arg( valueName( value ) ) + s; + + if ( !exact && (value & valName) && mode == modeNormal && i18n( "Unknown").contains( filter, false ) ) + m_where += QString( "AND %1.%2 <> '' " ).arg( tableName( tables ) ).arg( valueName( value ) ); + + m_where += " ) "; + + m_linkTables |= tables; +} + +void +QueryBuilder::addMatch( int tables, const QString& match, bool interpretUnknown /* = true */, bool caseSensitive /* = true */ ) +{ + QString matchCondition = caseSensitive ? CollectionDB::exactCondition( match ) : CollectionDB::likeCondition( match ); + + (((m_where += ANDslashOR()) += " ( ") += CollectionDB::instance()->boolF()) += ' '; + if ( tables & tabAlbum ) + (m_where += "OR album.name ") += matchCondition; + if ( tables & tabArtist ) + (m_where += "OR artist.name ") += matchCondition; + if ( tables & tabComposer ) + (m_where += "OR composer.name ") += matchCondition; + if ( tables & tabGenre ) + (m_where += "OR genre.name ") += matchCondition; + if ( tables & tabYear ) + (m_where += "OR year.name ") += matchCondition; + if ( tables & tabSong ) + (m_where += "OR tags.title ") += matchCondition; + if ( tables & tabLabels ) + (m_where += "OR labels.name ") += matchCondition; + + static QString i18nUnknown = i18n("Unknown"); + + if ( interpretUnknown && match == i18nUnknown ) + { + if ( tables & tabAlbum ) m_where += "OR album.name = '' "; + if ( tables & tabArtist ) m_where += "OR artist.name = '' "; + if ( tables & tabComposer ) m_where += "OR composer.name = '' "; + if ( tables & tabGenre ) m_where += "OR genre.name = '' "; + if ( tables & tabYear ) m_where += "OR year.name = '' "; + } + if ( tables & tabLabels && match.isEmpty() ) + m_where += " OR labels.name IS NULL "; + m_where += " ) "; + + m_linkTables |= tables; +} + + +void +QueryBuilder::addMatch( int tables, Q_INT64 value, const QString& match, bool interpretUnknown /* = true */, bool caseSensitive /* = true */ ) +{ + m_where += ANDslashOR() + " ( " + CollectionDB::instance()->boolF() + ' '; + if ( value & valURL ) + m_url = match; + //FIXME max: doesn't work yet if we are querying for the mount point part of a directory + if ( value & valURL || value & valDirectory ) + { + int deviceid = MountPointManager::instance()->getIdForUrl( match ); + QString rpath = MountPointManager::instance()->getRelativePath( deviceid, match ); + //we are querying for a specific path, so we don't need the tags.deviceid IN (...) stuff + //which is automatially appended if m_showAll = false + m_showAll = true; + m_where += QString( "OR %1.%2 " ) + .arg( tableName( tables ) ) + .arg( valueName( value ) ); + m_where += caseSensitive ? CollectionDB::exactCondition( rpath ) : CollectionDB::likeCondition( rpath ); + m_where += QString( " AND %1.deviceid = %2 " ).arg( tableName( tables ) ).arg( deviceid ); + if ( deviceid != -1 ) + { + //handle corner case + QString rpath2( '.' + match ); + m_where += QString( " OR %1.%2 " ).arg( tableName( tables ) ).arg( valueName( value ) ); + m_where += caseSensitive ? CollectionDB::exactCondition( rpath2 ) : CollectionDB::likeCondition( rpath2 ); + m_where += QString( " AND %1.deviceid = -1 " ).arg( tableName( tables ) ); + } + } + else + { + m_where += QString( "OR %1.%2 " ).arg( tableName( tables ) ).arg( valueName( value ) ); + m_where += caseSensitive ? CollectionDB::exactCondition( match ) : CollectionDB::likeCondition( match ); + } + + if ( ( value & valName ) && interpretUnknown && match == i18n( "Unknown" ) ) + m_where += QString( "OR %1.%2 = '' " ).arg( tableName( tables ) ).arg( valueName( value ) ); + + m_where += " ) "; + + m_linkTables |= tables; +} + + +void +QueryBuilder::addMatches( int tables, const QStringList& match, bool interpretUnknown /* = true */, bool caseSensitive /* = true */ ) +{ + QStringList matchConditions; + for ( uint i = 0; i < match.count(); i++ ) + matchConditions << ( caseSensitive ? CollectionDB::exactCondition( match[i] ) : CollectionDB::likeCondition( match[i] ) ); + + m_where += ANDslashOR() + " ( " + CollectionDB::instance()->boolF() + ' '; + + for ( uint i = 0; i < match.count(); i++ ) + { + if ( tables & tabAlbum ) + m_where += "OR album.name " + matchConditions[ i ]; + if ( tables & tabArtist ) + m_where += "OR artist.name " + matchConditions[ i ]; + if ( tables & tabComposer ) + m_where += "OR composer.name " + matchConditions[ i ]; + if ( tables & tabGenre ) + m_where += "OR genre.name " + matchConditions[ i ]; + if ( tables & tabYear ) + m_where += "OR year.name " + matchConditions[ i ]; + if ( tables & tabSong ) + m_where += "OR tags.title " + matchConditions[ i ]; + if ( tables & tabStats ) + m_where += "OR statistics.url " + matchConditions[ i ]; + if ( tables & tabLabels ) + (m_where += "OR labels.name ") += matchConditions[ i ]; + + + if ( interpretUnknown && match[i] == i18n( "Unknown" ) ) + { + if ( tables & tabAlbum ) m_where += "OR album.name = '' "; + if ( tables & tabArtist ) m_where += "OR artist.name = '' "; + if ( tables & tabComposer ) m_where += "OR composer.name = '' "; + if ( tables & tabGenre ) m_where += "OR genre.name = '' "; + if ( tables & tabYear ) m_where += "OR year.name = '' "; + } + if ( tables & tabLabels && match[i].isEmpty() ) + m_where += " OR labels.name IS NULL "; + } + + m_where += " ) "; + m_linkTables |= tables; +} + +void +QueryBuilder::excludeMatch( int tables, const QString& match ) +{ + m_where += ANDslashOR() + " ( " + CollectionDB::instance()->boolT() + ' '; + if ( tables & tabAlbum ) m_where += "AND album.name <> '" + CollectionDB::instance()->escapeString( match ) + "' "; + if ( tables & tabArtist ) m_where += "AND artist.name <> '" + CollectionDB::instance()->escapeString( match ) + "' "; + if ( tables & tabComposer ) m_where += "AND composer.name <> '" + CollectionDB::instance()->escapeString( match ) + "' "; + if ( tables & tabGenre ) m_where += "AND genre.name <> '" + CollectionDB::instance()->escapeString( match ) + "' "; + if ( tables & tabYear ) m_where += "AND year.name <> '" + CollectionDB::instance()->escapeString( match ) + "' "; + if ( tables & tabSong ) m_where += "AND tags.title <> '" + CollectionDB::instance()->escapeString( match ) + "' "; + if ( tables & tabLabels ) m_where += "AND labels.name <> '" + CollectionDB::instance()->escapeString( match ) + "' "; + + if ( match == i18n( "Unknown" ) ) + { + if ( tables & tabAlbum ) m_where += "AND album.name <> '' "; + if ( tables & tabArtist ) m_where += "AND artist.name <> '' "; + if ( tables & tabComposer ) m_where += "AND composer.name <> '' "; + if ( tables & tabGenre ) m_where += "AND genre.name <> '' "; + if ( tables & tabYear ) m_where += "AND year.name <> '' "; + } + m_where += " ) "; + + m_linkTables |= tables; +} + + +void +QueryBuilder::exclusiveFilter( int tableMatching, int tableNotMatching, Q_INT64 value ) +{ + m_where += " AND "; + m_where += tableName( tableNotMatching ) + '.'; + m_where += valueName( value ); + m_where += " IS null "; + + m_linkTables |= tableMatching; + m_linkTables |= tableNotMatching; +} + + +void +QueryBuilder::addNumericFilter(int tables, Q_INT64 value, const QString &n, + int mode /* = modeNormal */, + const QString &endRange /* = QString::null */ ) +{ + m_where.append( ANDslashOR() ).append( " ( " ); + + if ( coalesceField( tables, value) ) + m_where.append("COALESCE("); + + m_where.append( tableName( tables ) ).append( '.' ).append( valueName( value ) ); + + if ( coalesceField( tables, value) ) + m_where.append(",0)"); + + switch (mode) { + case modeNormal: + m_where.append( " = " ); break; + case modeLess: + m_where.append( " < " ); break; + case modeGreater: + m_where.append( " > " ); break; + case modeBetween: + m_where.append( " BETWEEN " ); break; + case modeNotBetween: + m_where.append(" NOT BETWEEN "); break; + default: + qWarning( "Unhandled mode in addNumericFilter, using equals: %d", mode ); + m_where.append( " = " ); + } + + m_where.append( n ); + if ( mode == modeBetween || mode == modeNotBetween ) + m_where.append( " AND " ).append( endRange ); + + m_where.append( " ) " ); + m_linkTables |= tables; +} + + + +void +QueryBuilder::setOptions( int options ) +{ + if ( options & optNoCompilations || options & optOnlyCompilations ) + m_linkTables |= tabSong; + + if ( options & optNoCompilations ) m_where += QString("AND tags.sampler = %1 ").arg(CollectionDB::instance()->boolF()); + if ( options & optOnlyCompilations ) m_where += QString("AND tags.sampler = %1 ").arg(CollectionDB::instance()->boolT()); + + if (CollectionDB::instance()->getType() == DbConnection::postgresql && options & optRemoveDuplicates && options & optRandomize) + { + m_values = "DISTINCT " + CollectionDB::instance()->randomFunc() + " AS __random "+ m_values; + if ( !m_sort.isEmpty() ) + m_sort += ','; + m_sort += CollectionDB::instance()->randomFunc() + ' '; + } + else + { + if ( options & optRemoveDuplicates ) + m_values = "DISTINCT " + m_values; + if ( options & optRandomize ) + { + if ( !m_sort.isEmpty() ) m_sort += ','; + m_sort += CollectionDB::instance()->randomFunc() + ' '; + } + } + + if ( options & optShowAll ) m_showAll = true; +} + + +void +QueryBuilder::sortBy( int table, Q_INT64 value, bool descending ) +{ + //shall we sort case-sensitively? (not for integer columns!) + bool b = true; + if ( value & valID || value & valTrack || value & valScore || value & valRating || value & valLength || value & valBitrate || + value & valSamplerate || value & valPlayCounter || value & valAccessDate || value & valCreateDate || + value & valFilesize || value & valDiscNumber || + table & tabYear ) + b = false; + + // only coalesce for certain columns + bool c = false; + if ( value & valScore || value & valRating || value & valPlayCounter || value & valAccessDate || value & valCreateDate ) + c = true; + + if ( !m_sort.isEmpty() ) m_sort += ','; + if ( b ) m_sort += "LOWER( "; + if ( c ) m_sort += "COALESCE( "; + + m_sort += tableName( table ) + '.'; + m_sort += valueName( value ); + + if ( c ) m_sort += ", 0 )"; + + if ( b ) m_sort += " ) "; + if ( descending ) m_sort += " DESC "; + + if (CollectionDB::instance()->getType() == DbConnection::postgresql) + { + if (!m_values.isEmpty()) m_values += ','; + if ( b ) m_values += "LOWER( "; + m_values += tableName( table ) + '.'; + m_values += valueName( value ); + if ( b ) m_values += ')'; + m_values += " as __discard "; + } + + m_linkTables |= table; +} + +void +QueryBuilder::sortByFunction( int function, int table, Q_INT64 value, bool descending ) +{ + // This function should be used with the equivalent addReturnFunctionValue (with the same function on same values) + // since it uses the "func(table.value) AS functablevalue" definition. + + // this column is already coalesced, but need to reconstruct for postgres + bool defaults = function == funcAvg && ( value & valScore || value & valRating ); + + //shall we sort case-sensitively? (not for integer columns!) + bool b = true; + if ( value & valID || value & valTrack || value & valScore || value & valRating || value & valLength || value & valBitrate || + value & valSamplerate || value & valPlayCounter || value & valAccessDate || value & valCreateDate || + value & valFilesize || value & valDiscNumber || + table & tabYear ) + b = false; + + // only coalesce for certain columns + bool c = false; + if ( !defaults && ( value & valScore || value & valRating || value & valPlayCounter || value & valAccessDate || value & valCreateDate ) ) + c = true; + + if ( !m_sort.isEmpty() ) m_sort += ','; + //m_sort += functionName( function ) + '('; + if ( b ) m_sort += "LOWER( "; + if ( c && CollectionDB::instance()->getType() != DbConnection::mysql) m_sort += "COALESCE( "; + + QString columnName; + + if (CollectionDB::instance()->getType() == DbConnection::postgresql) + { + columnName = functionName( function ) + '('; + if ( defaults ) + columnName += "COALESCE(NULLIF("; + columnName += tableName( table )+'.'+valueName( value ); + if ( defaults ) + { + columnName += ", 0), "; + if ( value & valScore ) + columnName += "50"; + else + columnName += '6'; + columnName += ')'; + } + columnName += ')'; + } + else + columnName = functionName( function )+tableName( table )+valueName( value ); + + m_sort += columnName; + + if ( c && CollectionDB::instance()->getType() != DbConnection::mysql) m_sort += ", 0 )"; + + if ( b ) m_sort += " ) "; + //m_sort += " ) "; + if ( descending ) m_sort += " DESC "; + + m_linkTables |= table; +} + +void +QueryBuilder::groupBy( int table, Q_INT64 value ) +{ + if ( !m_group.isEmpty() ) m_group += ','; + + //Do case-sensitive comparisons for MySQL too. See also QueryBuilder::addReturnValue + if ( DbConnection::mysql == CollectionDB::instance()->getDbConnectionType() && + ( value == valName || value == valTitle || value == valComment ) ) + { + m_group += "BINARY "; + } + + m_group += tableName( table ) + '.'; + m_group += valueName( value ); + + m_linkTables |= table; +} + +void +QueryBuilder::having( int table, Q_INT64 value, int function, int mode, const QString& match ) +{ + if( !m_having.isEmpty() ) m_having += " AND "; + + QString fn = functionName( function ); + fn.isEmpty() ? + m_having += tableName( table ) + '.' + valueName( value ) : + m_having += functionName( function )+'('+tableName( table )+'.'+valueName( value )+')'; + + switch( mode ) + { + case modeNormal: + m_having += '=' + match; + break; + + case modeLess: + m_having += '<' + match; + break; + + case modeGreater: + m_having += '>' + match; + + default: + break; + } +} + +void +QueryBuilder::setLimit( int startPos, int length ) +{ + m_limit = QString( " LIMIT %2 OFFSET %1 " ).arg( startPos ).arg( length ); +} + +void +QueryBuilder::shuffle( int table, Q_INT64 value ) +{ + if ( !m_sort.isEmpty() ) m_sort += " , "; + if ( table == 0 || value == 0 ) { + // simple random + m_sort += CollectionDB::instance()->randomFunc(); + } else { + // This is the score weighted random order. + + // The RAND() function returns random values equally distributed between 0.0 + // (inclusive) and 1.0 (exclusive). The obvious way to get this order is to + // put every track times into a list, sort the list by RAND() + // (i.e. shuffle it) and discard every occurrence of every track but the very + // first of each. By putting every track into the list only once but applying + // a transfer function T_s(x) := 1-(1-x)^(1/s) where s is the score, to RAND() + // before sorting the list, exactly the same distribution of tracks can be + // achieved (for a proof write to Stefan Siegel ) + + // In the query below a simplified function is used: The score is incremented + // by one to prevent division by zero, RAND() is used instead of 1-RAND() + // because it doesn't matter if it becomes zero (the exponent is always + // non-zero), and finally POWER(...) is used instead of 1-POWER(...) because it + // only changes the order type. + m_sort += QString("POWER( %1, 1.0 / (%2.%3 + 1) ) DESC") + .arg( CollectionDB::instance()->randomFunc() ) + .arg( tableName( table ) ) + .arg( valueName( value ) ); + + m_linkTables |= table; + } +} + + +/* NOTE: It's important to keep these two functions and the const in sync! */ +/* NOTE: It's just as important to keep tags.url first! */ +const int +QueryBuilder::dragFieldCount = 21; + +QString +QueryBuilder::dragSQLFields() +{ + return "tags.url, tags.deviceid, album.name, artist.name, composer.name, " + "genre.name, tags.title, year.name, " + "tags.comment, tags.track, tags.bitrate, tags.discnumber, " + "tags.length, tags.samplerate, tags.filesize, " + "tags.sampler, tags.filetype, tags.bpm, " + "statistics.percentage, statistics.rating, statistics.playcounter, " + "statistics.accessdate"; +} + +void +QueryBuilder::initSQLDrag() +{ + clear(); + addReturnValue( QueryBuilder::tabSong, QueryBuilder::valURL ); + addReturnValue( QueryBuilder::tabAlbum, QueryBuilder::valName ); + addReturnValue( QueryBuilder::tabArtist, QueryBuilder::valName ); + addReturnValue( QueryBuilder::tabComposer, QueryBuilder::valName ); + addReturnValue( QueryBuilder::tabGenre, QueryBuilder::valName ); + addReturnValue( QueryBuilder::tabSong, QueryBuilder::valTitle ); + addReturnValue( QueryBuilder::tabYear, QueryBuilder::valName ); + addReturnValue( QueryBuilder::tabSong, QueryBuilder::valComment ); + addReturnValue( QueryBuilder::tabSong, QueryBuilder::valTrack ); + addReturnValue( QueryBuilder::tabSong, QueryBuilder::valBitrate ); + addReturnValue( QueryBuilder::tabSong, QueryBuilder::valDiscNumber ); + addReturnValue( QueryBuilder::tabSong, QueryBuilder::valLength ); + addReturnValue( QueryBuilder::tabSong, QueryBuilder::valSamplerate ); + addReturnValue( QueryBuilder::tabSong, QueryBuilder::valFilesize ); + addReturnValue( QueryBuilder::tabSong, QueryBuilder::valIsCompilation ); + addReturnValue( QueryBuilder::tabSong, QueryBuilder::valFileType ); + addReturnValue( QueryBuilder::tabSong, QueryBuilder::valBPM ); + addReturnValue( QueryBuilder::tabStats, QueryBuilder::valScore ); + addReturnValue( QueryBuilder::tabStats, QueryBuilder::valRating ); + addReturnValue( QueryBuilder::tabStats, QueryBuilder::valPlayCounter ); + addReturnValue( QueryBuilder::tabStats, QueryBuilder::valAccessDate ); +} + + +void +QueryBuilder::buildQuery( bool withDeviceidPlaceholder ) +{ + if ( m_query.isEmpty() ) + { + linkTables( m_linkTables ); + m_query += "SELECT "; + m_query += m_values; + m_query += " FROM "; + m_query += m_tables; + m_query += ' '; + m_query += m_join; + m_query += " WHERE "; + m_query += CollectionDB::instance()->boolT(); + m_query += ' '; + m_query += m_where; + if ( !m_showAll && ( m_linkTables & tabSong || m_tables.contains( tableName( tabSong) ) ) ) //Only stuff on mounted devices, unless you use optShowAll + { + if ( withDeviceidPlaceholder ) + m_query += "(*MountedDeviceSelection*)"; + else + { + IdList list = MountPointManager::instance()->getMountedDeviceIds(); + //debug() << "number of device ids " << list.count() << endl; + m_query += " AND tags.deviceid IN ("; + foreachType( IdList, list ) + { + if ( it != list.begin() ) m_query += ','; + m_query += QString::number( *it ); + } + m_query += ')'; + } + } + // GROUP BY must be before ORDER BY for sqlite + // HAVING must be between GROUP BY and ORDER BY + if ( !m_group.isEmpty() ) { m_query += " GROUP BY "; m_query += m_group; } + if ( !m_having.isEmpty() ) { m_query += " HAVING "; m_query += m_having; } + if ( !m_sort.isEmpty() ) { m_query += " ORDER BY "; m_query += m_sort; } + m_query += m_limit; + m_query += ';'; + } +} + +// get the builded SQL-Query (used in smartplaylisteditor soon) +QString +QueryBuilder::getQuery() +{ + if ( m_query.isEmpty()) + { + buildQuery(); + } + return m_query; +} + +QStringList +QueryBuilder::run() +{ + buildQuery(); + //debug() << m_query << endl; + QStringList rs = CollectionDB::instance()->query( m_query ); + //calling code is unaware of the dynamic collection implementation, it simply expects an URL + if( m_deviceidPos > 0 ) + return cleanURL( rs ); + else + return rs; +} + + +void +QueryBuilder::clear() +{ + m_query.setLength(0); + m_values.setLength(0); + m_tables.setLength(0); + m_join.setLength(0); + m_where.setLength(0); + m_sort.setLength(0); + m_group.setLength(0); + m_limit.setLength(0); + m_having.setLength(0); + + m_linkTables = 0; + m_returnValues = 0; + + m_showAll = false; + m_deviceidPos = 0; +} + + +Q_INT64 +QueryBuilder::valForFavoriteSorting() { + Q_INT64 favSortBy = valRating; + if ( !AmarokConfig::useScores() && !AmarokConfig::useRatings() ) + favSortBy = valPlayCounter; + else if( !AmarokConfig::useRatings() ) + favSortBy = valScore; + return favSortBy; +} + +void +QueryBuilder::sortByFavorite() { + if ( AmarokConfig::useRatings() ) + sortBy(tabStats, valRating, true ); + if ( AmarokConfig::useScores() ) + sortBy(tabStats, valScore, true ); + sortBy(tabStats, valPlayCounter, true ); + +} + +void +QueryBuilder::sortByFavoriteAvg() { + // Due to MySQL4 weirdness, we need to add the function we're using to sort + // as return values as well. + if ( AmarokConfig::useRatings() ) { + sortByFunction(funcAvg, tabStats, valRating, true ); + addReturnFunctionValue( funcAvg, tabStats, valRating ); + } + if ( AmarokConfig::useScores() ) { + sortByFunction(funcAvg, tabStats, valScore, true ); + addReturnFunctionValue( funcAvg, tabStats, valScore ); + } + sortByFunction(funcAvg, tabStats, valPlayCounter, true ); + addReturnFunctionValue( funcAvg, tabStats, valPlayCounter ); + + //exclude unrated and unplayed + if( !m_having.isEmpty() ) + m_having += " AND "; + m_having += " ("; + if (AmarokConfig::useRatings() ) + m_having += QString("%1(%2.%3) > 0 OR ") + .arg( functionName( funcAvg ), tableName(tabStats), valueName(valRating) ); + m_having += QString("%1(%2.%3) > 0") + .arg( functionName( funcAvg ), tableName(tabStats), valueName(valPlayCounter) ); + m_having += ")"; +} + +// Helper method -- given a value, returns the index of the bit that is +// set, if only one, otherwise returns -1 +// Binsearch seems appropriate since the values enum has 40 members +template +static inline int +searchBit( ValueType value, int numBits ) { + int low = 0, high = numBits - 1; + while( low <= high ) { + int mid = (low + high) / 2; + ValueType compare = static_cast( 1 ) << mid; + if ( value == compare ) return mid; + else if ( value < compare ) high = mid - 1; + else low = mid + 1; + } + + return -1; +} + +QString +QueryBuilder::tableName( int table ) +{ + // optimize for 1 table which is by far the most frequent case + static const char tabNames[][16] = { + "album", + "artist", + "composer", + "genre", + "year", + "", // 32 is missing from the enum + "tags", + "statistics", + "lyrics", + "podcastchannels", + "podcastepisodes", + "podcasttables", + "devices", + "labels" + }; + + int oneBit = searchBit( table, sizeof( tabNames ) / sizeof( QString ) ); + if ( oneBit >= 0 ) return tabNames[oneBit]; + + // slow path: multiple tables. This seems to be unneeded at the moment, + // but leaving it here since it appears to be intended usage + QString tables; + + if ( CollectionDB::instance()->getType() != DbConnection::postgresql ) + { + if ( table & tabSong ) tables += ",tags"; + } + if ( table & tabArtist ) tables += ",artist"; + if ( table & tabComposer ) tables += ",composer"; + if ( table & tabAlbum ) tables += ",album"; + if ( table & tabGenre ) tables += ",genre"; + if ( table & tabYear ) tables += ",year"; + if ( table & tabStats ) tables += ",statistics"; + if ( table & tabLyrics ) tables += ",lyrics"; + if ( table & tabPodcastChannels ) tables += ",podcastchannels"; + if ( table & tabPodcastEpisodes ) tables += ",podcastepisodes"; + if ( table & tabPodcastFolders ) tables += ",podcasttables"; + if ( CollectionDB::instance()->getType() == DbConnection::postgresql ) + { + if ( table & tabSong ) tables += ",tags"; + } + + if ( table & tabDevices ) tables += ",devices"; + if ( table & tabLabels ) tables += ",labels"; + // when there are multiple tables involved, we always need table tags for linking them + return tables.mid( 1 ); +} + + +const QString & +QueryBuilder::valueName( Q_INT64 value ) +{ + static const QString values[] = { + "id", + "name", + "url", + "title", + "track", + "percentage", + "comment", + "bitrate", + "length", + "samplerate", + "playcounter", + "createdate", + "accessdate", + "percentage", + "artist", + "album", + "year", + "genre", + "dir", + "lyrics", + "rating", + "composer", + "discnumber", + "filesize", + "filetype", + "sampler", + "bpm", + "copyright", + "parent", + "weblink", + "autoscan", + "fetchtype", + "autotransfer", + "haspurge", + "purgeCount", + "isNew", + "deviceid", + "url", + "label", + "lastmountpoint", + "type" + }; + + int oneBit = searchBit( value, sizeof( values ) / sizeof( QString ) ); + if ( oneBit >= 0 ) return values[oneBit]; + + static const QString error( "" ); + return error; +} + +/* + * Return true if we should call COALESCE(..,0) for this DB field + * (field names sourced from the old smartplaylistbrowser.cpp code) + * Warning: addFilter( int, Q_INT64, const QString&, int bool ) + * expects this method to return true for all statistics table clomuns of type INTEGER + * Sqlite doesn't like comparing strings to an INTEGER column. + */ +bool +QueryBuilder::coalesceField( int table, Q_INT64 value ) +{ + if( tableName( table ) == "statistics" && + ( valueName( value ) == "playcounter" || + valueName( value ) == "rating" || + valueName( value ) == "percentage" || + valueName( value ) == "accessdate" || + valueName( value ) == "createdate" + ) + ) + return true; + return false; +} + +QString +QueryBuilder::functionName( int function ) +{ + QString functions; + + if ( function & funcCount ) functions += "Count"; + if ( function & funcMax ) functions += "Max"; + if ( function & funcMin ) functions += "Min"; + if ( function & funcAvg ) functions += "Avg"; + if ( function & funcSum ) functions += "Sum"; + + return functions; +} + +// FIXME: the two functions below are inefficient, but this patch is getting too +// big already. They are not on any critical path right now. Ovy +int +QueryBuilder::getTableByName(const QString &name) +{ + for ( int i = 1; i <= tabLabels; i <<= 1 ) + { + if (tableName(i) == name) return i; + } + return -1; +} + +Q_INT64 +QueryBuilder::getValueByName(const QString &name) +{ + for ( Q_INT64 i = 1; i <= valType; i <<= 1 ) { + if (valueName(i) == name) return i; + } + + return -1; +} + +bool +QueryBuilder::getField(const QString &tableValue, int *table, Q_INT64 *value) +{ + int dotIndex = tableValue.find( '.' ) ; + if ( dotIndex < 0 ) return false; + int tmpTable = getTableByName( tableValue.left(dotIndex) ); + Q_UINT64 tmpValue = getValueByName( tableValue.mid( dotIndex + 1 ) ); + if ( tmpTable >= 0 && value ) { + *table = tmpTable; + *value = tmpValue; + return true; + } + else + { + qFatal("invalid table.value: %s", tableValue.ascii()); + return false; + } +} + + + +QStringList +QueryBuilder::cleanURL( QStringList result ) +{ + //this method replaces the fields for relative path and devive/media id with a + //single field containing the absolute path for each row + //TODO Max improve this code + int count = 1; + for( QStringList::Iterator it = result.begin(), end = result.end(); it != end; ) + { + QString rpath; + if ( (count % (m_returnValues + 1)) + 1== m_deviceidPos ) + { + //this block is reached when the iterator points at the relative path + //deviceid is next + QString rpath = *it; + int deviceid = (*(++it)).toInt(); + QString abspath = MountPointManager::instance()->getAbsolutePath( deviceid, rpath ); + it = result.remove(--it); + result.insert( it, abspath ); + it = result.remove( it ); + //we advanced the iterator over two fields in this iteration + ++count; + } + else + ++it; + ++count; + } + return result; +} + + +#include "collectiondb.moc" diff --git a/amarok/src/collectiondb.h b/amarok/src/collectiondb.h new file mode 100644 index 00000000..a86fd2fa --- /dev/null +++ b/amarok/src/collectiondb.h @@ -0,0 +1,868 @@ +// (c) 2004 Mark Kretschmann +// (c) 2004 Christian Muehlhaeuser +// (c) 2004 Sami Nieminen +// (c) 2005 Ian Monroe +// (c) 2005 Jeff Mitchell +// (c) 2005 Isaiah Damron +// (c) 2005 Alexandre Pereira de Oliveira +// (c) 2006 Jonas Hurrelmann +// (c) 2006 Shane King +// (c) 2006 Peter C. Ndikuwera +// See COPYING file for licensing information. + +#ifndef AMAROK_COLLECTIONDB_H +#define AMAROK_COLLECTIONDB_H + +#include "engineobserver.h" +#include "threadmanager.h" //baseclass +#include "amarok_export.h" + +#include +#include //stack allocated +#include +#include +#include +#include //baseclass +#include //baseclass +#include //stack allocated +#include //stack allocated +#include +#include +#include + +namespace KIO { class Job; } + +class DbConnection; +class CoverFetcher; +class MetaBundle; +class OrganizeCollectionDialog; +class PodcastChannelBundle; +class PodcastEpisodeBundle; +class QListViewItem; +class Scrobbler; + +class DbConfig +{}; + + +class SqliteConfig : public DbConfig +{ + public: + SqliteConfig( const QString& /* dbfile */ ); + + QString dbFile() const { return m_dbfile; } + + private: + QString m_dbfile; +}; + + +class MySqlConfig : public DbConfig +{ + public: + MySqlConfig( + const QString& /* host */, + const int /* port */, + const QString& /* database */, + const QString& /* username */, + const QString& /* password */); + + QString host() const { return m_host; } + int port() const { return m_port; } + QString database() const { return m_database; } + QString username() const { return m_username; } + QString password() const { return m_password; } + + private: + QString m_host; + int m_port; + QString m_database; + QString m_username; + QString m_password; +}; + + +class PostgresqlConfig : public DbConfig +{ + public: + PostgresqlConfig( + const QString& /* host */, + const int /* port */, + const QString& /* database */, + const QString& /* username */, + const QString& /* password */); + + QString host() const { return m_host; } + int port() const { return m_port; } + QString database() const { return m_database; } + QString username() const { return m_username; } + QString password() const { return m_password; } + + private: + QString m_host; + int m_port; + QString m_database; + QString m_username; + QString m_password; +}; + + +class DbConnection +{ + public: + enum DbConnectionType { sqlite = 0, mysql = 1, postgresql = 2 }; + + DbConnection(); + virtual ~DbConnection() {} + + virtual QStringList query( const QString& /* statement */, bool suppressDebug ) = 0; + virtual int insert( const QString& /* statement */, const QString& /* table */ ) = 0; + bool isInitialized() const { return m_initialized; } + virtual bool isConnected() const = 0; + virtual QString lastError() const { return "None"; } + + protected: + bool m_initialized; +}; + + +typedef struct sqlite3 sqlite3; +typedef struct sqlite3_context sqlite3_context; +typedef struct Mem sqlite3_value; + +class SqliteConnection : public DbConnection +{ + public: + SqliteConnection( const SqliteConfig* /* config */ ); + ~SqliteConnection(); + + QStringList query( const QString& /* statement */, bool suppressDebug = false ); + int insert( const QString& /* statement */, const QString& /* table */ ); + bool isConnected()const { return true; } + private: + static void sqlite_rand( sqlite3_context *context, int /*argc*/, sqlite3_value ** /*argv*/ ); + static void sqlite_power( sqlite3_context *context, int argc, sqlite3_value **argv ); + static void sqlite_like_new( sqlite3_context *context, int argc, sqlite3_value **argv ); + + sqlite3* m_db; +}; + + +#ifdef USE_MYSQL +typedef struct st_mysql MYSQL; + +class MySqlConnection : public DbConnection +{ + public: + MySqlConnection( const MySqlConfig* /* config */ ); + ~MySqlConnection(); + + QStringList query( const QString& /* statement */, bool suppressDebug = false ); + int insert( const QString& /* statement */, const QString& /* table */ ); + bool isConnected()const { return m_connected; } + QString lastError() const { return m_error; } + private: + void setMysqlError(); + MYSQL* m_db; + bool m_connected; + QString m_error; +}; +#endif + + +#ifdef USE_POSTGRESQL +typedef struct pg_conn PGconn; + +class PostgresqlConnection : public DbConnection +{ + public: + PostgresqlConnection( const PostgresqlConfig* /* config */ ); + ~PostgresqlConnection(); + + QStringList query( const QString& /* statement */, bool suppressDebug = false ); + int insert( const QString& /* statement */, const QString& /* table */ ); + bool isConnected()const { return m_connected; } + QString lastError() const { return m_error; } + private: + void setPostgresqlError(); + PGconn* m_db; + bool m_connected; + QString m_error; +}; +#endif + + +class LIBAMAROK_EXPORT CollectionDB : public QObject, public EngineObserver +{ + Q_OBJECT + + friend class SimilarArtistsInsertionJob; + + signals: + void scanStarted(); + void scanDone( bool changed ); + void databaseEngineChanged(); + + void databaseUpdateDone(); + + void scoreChanged( const QString &url, float score ); + void ratingChanged( const QString &url, int rating ); + void labelsChanged( const QString &url ); + void fileMoved( const QString &srcUrl, const QString &dstUrl ); + void fileMoved( const QString &srcUrl, const QString &dstUrl, const QString &uniqueid ); + void fileDeleted( const QString &absPath ); + void fileDeleted( const QString &absPath, const QString &uniqueid ); + void fileAdded( const QString &absPath ); + void fileAdded( const QString &absPath, const QString &uniqueid ); + void filesAdded( const QMap &map ); + void uniqueIdChanged( const QString &url, const QString &originalid, const QString &newid ); + void coverChanged( const QString &artist, const QString &album ); //whenever a cover changes + void coverFetched( const QString &artist, const QString &album ); //only when fetching from amazon + void coverRemoved( const QString &artist, const QString &album ); + void coverFetcherError( const QString &error ); + + void similarArtistsFetched( const QString &artist ); + void tagsChanged( const MetaBundle &bundle ); + void tagsChanged( const QString &oldArtist, const QString &oldAlbum ); + void imageFetched( const QString &remoteURL ); //for fetching remote podcast images + + public: + CollectionDB(); + ~CollectionDB(); + + static CollectionDB *instance(); + + /** + * performs all initializations which require directory or URL data stored in the + * database. + */ + void initDirOperations(); + + enum labelTypes { typeUser = 1 }; //add new types add the end! + + QString escapeString(QString string ) const + { + return + #ifdef USE_MYSQL + // We have to escape "\" for mysql, but can't do so for sqlite + ( m_dbConnType == DbConnection::mysql ) + ? string.replace("\\", "\\\\").replace( '\'', "''" ) : + #endif + string.replace( '\'', "''" ); + } + + QString boolT() const { if (getDbConnectionType() == DbConnection::postgresql) return "true"; else return "1"; } + QString boolF() const { if (getDbConnectionType() == DbConnection::postgresql) return "false"; else return "0"; } + inline bool boolFromSql( const QString &b ) { return ( b == boolT() || b == "t" ); } + //textColumnType should be used for normal strings, which need to be compared + //either case-sensitively or -insensitively + QString textColumnType( int length=255 ) const { if ( getDbConnectionType() == DbConnection::postgresql ) return "TEXT"; else return QString("VARCHAR(%1)").arg(length); } + //exactTextColumnType should be used for strings that must be stored exactly, such + //as URLs (necessary for holding control chars etc. if present in URL), except for + //trailing spaces. Comparisions should always be done case-sensitively. + //As we create indices on these columns, we have to restrict them to + //<= 255 chars for mysql < 5.0.3 + QString exactTextColumnType( int length=1024 ) const { if ( getDbConnectionType() == DbConnection::mysql ) return QString( "VARBINARY(%1)" ).arg( length>255 ? 255 : length ); else return textColumnType( length ); } + // We might consider using LONGTEXT type, as some lyrics could be VERY long..??? + QString longTextColumnType() const { if ( getDbConnectionType() == DbConnection::postgresql ) return "TEXT"; else return "TEXT"; } + QString randomFunc() const { if ( getDbConnectionType() == DbConnection::postgresql ) return "random()"; else return "RAND()"; } + + inline static QString exactCondition( const QString &right ); + static QString likeCondition( const QString &right, bool anyBegin=false, bool anyEnd=false ); + int getType() { return getDbConnectionType(); } + + //sql helper methods + QStringList query( const QString& statement, bool suppressDebug = false ); + int insert( const QString& statement, const QString& table ); + + /** + * TODO: write doc + * @param showAll + * @return a string which can be appended to an existing sql where statement + */ + QString deviceidSelection( const bool showAll = false ); + + /** + * converts the result of a query which contains a deviceid and a relative path + * to a list of absolute paths. the order of entries in each result row must be + * deviceid first, relative path second. + * @param result the result of the sql query, deviceid first, relative path second + * @return a list of urls + */ + QStringList URLsFromQuery( const QStringList &result ) const; + + /** + * converts the result list of a amarok-sql query to a list of urls + */ + KURL::List URLsFromSqlDrag( const QStringList &values ) const; + + //table management methods + bool isEmpty(); + bool isValid(); + QString adminValue( QString noption ); + void setAdminValue( QString noption, QString value ); + void createTables( const bool temporary = false ); + void createIndices( ); + void createPermanentIndices(); + void dropTables( const bool temporary = false); + void clearTables( const bool temporary = false); + void copyTempTables( ); + void prepareTempTables(); + + uint artistID( QString value, bool autocreate = true, const bool temporary = false, bool exact = true ); + uint composerID( QString value, bool autocreate = true, const bool temporary = false, bool exact = true ); + uint albumID( QString value, bool autocreate = true, const bool temporary = false, bool exact = true ); + uint genreID( QString value, bool autocreate = true, const bool temporary = false, bool exact = true ); + uint yearID( QString value, bool autocreate = true, const bool temporary = false, bool exact = true ); + + bool isDirInCollection( QString path ); + bool isFileInCollection( const QString &url ); + QString getURL( const MetaBundle &bundle ); + void removeDirFromCollection( QString path ); + void removeSongsInDir( QString path, QMap *tagsRemoved = 0 ); + void removeSongs( const KURL::List& urls ); + void updateDirStats( QString path, const long datetime, const bool temporary = false ); + + //song methods + bool addSong( MetaBundle* bundle, const bool incremental = false ); + void aftCheckPermanentTables( const QString &currdeviceid, const QString &currid, const QString &currurl ); + void doAFTStuff( MetaBundle *bundle, const bool tempTables = true ); + void emitFileAdded( const QString &absPath, + const QString &uniqueid = QString::null ); + void emitFilesAdded( const QMap &map ) { emit filesAdded( map ); } + void emitFileDeleted( const QString &absPath, + const QString &uniqueid = QString::null ); + bool newUniqueIdForFile( const QString &path ); + bool removeUniqueIdFromFile( const QString &path ); + QString urlFromUniqueId( const QString &id ); + QString uniqueIdFromUrl( const KURL &url ); + + //podcast methods + /// Insert a podcast channel into the database. If @param replace is true, replace the row + /// use updatePodcastChannel() always in preference + bool addPodcastChannel( const PodcastChannelBundle &pcb, const bool &replace=false ); + /// Insert a podcast episode into the database. If @param idToUpdate is provided, replace the row + /// use updatePodcastEpisode() always in preference + int addPodcastEpisode( const PodcastEpisodeBundle &episode, const int idToUpdate=0 ); + int addPodcastFolder( const QString &name, const int parent_id=0, const bool isOpen=false ); + QValueList getPodcastChannels(); + PodcastEpisodeBundle getPodcastEpisodeById( int id ); + QValueList getPodcastEpisodes( const KURL &parent, bool newOnly=false, int limit=-1 ); + void removePodcastChannel( const KURL &url ); // will remove all episodes too + void removePodcastEpisode( const int id ); + void removePodcastFolder( const int id ); + void updatePodcastChannel( const PodcastChannelBundle &b ); + void updatePodcastEpisode( const int id, const PodcastEpisodeBundle &b ); + void updatePodcastFolder( const int folder_id, const QString &name, const int parent_id=0, const bool isOpen=false ); + // these return false when no bundle was available + bool getPodcastChannelBundle( const KURL &url, PodcastChannelBundle *channel ); + bool getPodcastEpisodeBundle( const KURL &url, PodcastEpisodeBundle *channel ); + + MetaBundle bundleFromQuery( QStringList::const_iterator *iter ); + /** + * The @p bundle parameter's url() will be looked up in the Collection + * @param bundle this will be filled in with tags for you + * @return true if in the collection + */ + bool bundleForUrl( MetaBundle* bundle ); + QValueList bundlesByUrls( const KURL::List& urls ); + void addAudioproperties( const MetaBundle& bundle ); + + //Helper function for updateTags + void deleteRedundantName( const QString &table, const QString &id ); + + void deleteAllRedundant( const QString &table ); + + void updateTags( const QString &url, const MetaBundle &bundle, const bool updateView = true); + void updateURL( const QString &url, const bool updateView = true ); + QString getUniqueId( const QString &url ); + + //statistics methods + void addSongPercentage( const QString &url, float percentage, + const QString &reason, const QDateTime *playtime = 0 ); + float getSongPercentage( const QString &url ); + int getSongRating( const QString &url ); + void setSongPercentage( const QString &url, float percentage ); + void setSongRating( const QString &url, int percentage, bool toggleHalf = false ); + int getPlayCount( const QString &url ); + QDateTime getFirstPlay( const QString &url ); + QDateTime getLastPlay( const QString &url ); + void migrateFile( const QString &oldURL, const QString &newURL ); + bool moveFile( const QString &src, const QString &dest, bool overwrite, bool copy = false ); + bool organizeFile( const KURL &src, const OrganizeCollectionDialog &dialog, bool copy ); + + //artist methods + QStringList similarArtists( const QString &artist, uint count ); + + //album methods + void checkCompilations( const QString &path, const bool temporary = false ); + void setCompilation( const KURL::List &urls, bool enabled, bool updateView ); + QString albumSongCount( const QString &artist_id, const QString &album_id ); + bool albumIsCompilation( const QString &album_id ); + void sanitizeCompilations(); + + //label methods + QStringList getLabels( const QString &url, const uint type ); + void removeLabels( const QString &url, const QStringList &labels, const uint type ); + bool addLabel( const QString &url, const QString &label, const QString &uid, const uint type ); + void setLabels( const QString &url, const QStringList &labels, const QString &uid, const uint type ); + + void cleanLabels(); + + QStringList favoriteLabels( int type = CollectionDB::typeUser, int count = 10 ); + + //list methods + QStringList artistList( bool withUnknowns = true, bool withCompilations = true ); + QStringList composerList( bool withUnknowns = true, bool withCompilations = true ); + QStringList albumList( bool withUnknowns = true, bool withCompilations = true ); + QStringList genreList( bool withUnknowns = true, bool withCompilations = true ); + QStringList yearList( bool withUnknowns = true, bool withCompilations = true ); + QStringList labelList(); + + QStringList albumListOfArtist( const QString &artist, bool withUnknown = true, bool withCompilations = true ); + QStringList artistAlbumList( bool withUnknown = true, bool withCompilations = true ); + + QStringList albumTracks( const QString &artist_id, const QString &album_id ); + QStringList albumDiscTracks( const QString &artist_id, const QString &album_id, const QString &discNumber ); + QStringList artistTracks( const QString &artist_id ); + + //cover management methods + /** Returns the image from a given URL, network-transparently. + * You must run KIO::NetAccess::removeTempFile( tmpFile ) when you are finished using the image; + **/ + static QImage fetchImage( const KURL& url, QString &tmpFile ); + /** Saves images located on the user's filesystem */ + bool setAlbumImage( const QString& artist, const QString& album, const KURL& url ); + /** Saves images obtained from CoverFetcher */ + bool setAlbumImage( const QString& artist, const QString& album, QImage img, const QString& amazonUrl = QString::null, const QString& asin = QString::null ); + + QString findAmazonImage( const QString &artist, const QString &album, const uint width = 1 ); + QString findDirectoryImage( const QString& artist, const QString& album, uint width = 0 ); + QString findEmbeddedImage( const QString& artist, const QString& album, uint width = 1 ); + QString findMetaBundleImage( const MetaBundle &trackInformation, const uint = 1 ); + + /// ensure the sql only return urls to tracks for efficiency + static QPixmap createDragPixmapFromSQL( const QString &sql, QString textOverRide=QString::null ); + static QPixmap createDragPixmap( const KURL::List &urls, QString textOverRide=QString::null ); + static const int DRAGPIXMAP_OFFSET_X = -12; + static const int DRAGPIXMAP_OFFSET_Y = -28; + + /* + * Retrieves the path to the local copy of the image pointed to by url, + * initiates fetching of the remote image if necessary. + * @param width the size of the image. 0 == full size, 1 == preview size + */ + QString podcastImage( const MetaBundle &bundle, const bool withShadow = false, uint width = 1 ); + QString podcastImage( const QString &remoteURL, const bool withShadow = false, uint width = 1 ); + + /** + * Retrieves the path to the image for the album of the requested item + * @param width the size of the image. 0 == full size, 1 == preview size + * @param embedded if not NULL, sets a bool indicating whether the path is an embedded image + */ + QString albumImage( const MetaBundle &trackInformation, const bool withShadow = false, uint width = 1, bool* embedded = 0 ); + QString albumImage( const uint artist_id, const uint album_id, const bool withShadow = false, uint width = 1, bool* embedded = 0 ); + QString albumImage( const QString &artist, const QString &album, const bool withShadow = false, uint width = 1, bool* embedded = 0 ); + QMap * getItemCoverMap() { return itemCoverMap; } + QMutex * getItemCoverMapMutex() { return itemCoverMapMutex; } + + bool removeAlbumImage( const uint artist_id, const uint album_id ); + bool removeAlbumImage( const QString &artist, const QString &album ); + + static QString makeShadowedImage( const QString& albumImage, bool cache = true ); + + //local cover methods + void addImageToAlbum( const QString& image, QValueList< QPair > info, const bool temporary ); + QString notAvailCover( const bool withShadow = false, int width = 1 ); + + //embedded cover methods + void addEmbeddedImage( const QString& path, const QString& hash, const QString& description ); + void removeOrphanedEmbeddedImages(); + + void applySettings(); + + void setLyrics( const QString& url, const QString& lyrics, const QString &uniqueid = QString::null ); + QString getLyrics( const QString& url ); + + /** Remove from the amazon table the item with the specified md5sum **/ + void removeInvalidAmazonInfo( const QString& md5sum ); + void newAmazonReloadDate( const QString& asin, const QString& locale, const QString& md5sum ); + QStringList staleImages(); + + DbConnection::DbConnectionType getDbConnectionType() const { return m_dbConnType; } + bool isConnected(); + void releasePreviousConnection(QThread *currThread); + + void invalidateArtistAlbumCache() { m_validArtistCache=false; m_validComposerCache=false; m_validAlbumCache=false; }; + + void vacuum(); + + /** + * Cancel the underlying move/copy file action + */ + void cancelMovingFileJob(); + + protected: + QCString md5sum( const QString& artist, const QString& album, const QString& file = QString::null ); + void engineTrackEnded( int finalPosition, int trackLength, const QString &reason ); + /** Manages regular folder monitoring scan */ + void timerEvent( QTimerEvent* e ); + + public slots: + void fetchCover( QWidget* parent, const QString& artist, const QString& album, bool noedit, QListViewItem* item = 0 ); + void scanMonitor(); + void startScan(); + void stopScan(); + void scanModifiedDirs(); + void disableAutoScoring( bool disable = true ) { m_autoScoring = !disable; } + + void checkDatabase(); + + private slots: + void dirDirty( const QString& path ); + void coverFetcherResult( CoverFetcher* ); + void similarArtistsFetched( const QString& artist, const QStringList& suggestions ); + void fileOperationResult( KIO::Job *job ); // moveFile depends on it + void podcastImageResult( KIO::Job *job ); //for fetching remote podcast images + void aftMigratePermanentTablesUrl( const QString& oldUrl, const QString& newUrl, const QString& uniqueid ); //AFT-enable stats + void aftMigratePermanentTablesUniqueId( const QString& url, const QString& oldid, const QString& newid ); + + private: + //bump DATABASE_VERSION whenever changes to the table structure are made. + // This erases tags, album, artist, composer, genre, year, images, embed, directory and related_artists tables. + static const int DATABASE_VERSION = 35; + // Persistent Tables hold data that is somehow valuable to the user, and can't be erased when rescaning. + // When bumping this, write code to convert the data! + static const int DATABASE_PERSISTENT_TABLES_VERSION = 19; + // Bumping this erases stats table. If you ever need to, write code to convert the data! + static const int DATABASE_STATS_VERSION = 12; + // When bumping this, you should provide code to convert the data. + static const int DATABASE_PODCAST_TABLES_VERSION = 2; + static const int DATABASE_AFT_VERSION = 2; + // persistent table. you should provide code to convert the data when bumping this + static const int DATABASE_DEVICES_VERSION = 1; + + static const int MONITOR_INTERVAL = 60; //sec + + static QDir largeCoverDir(); + static QDir tagCoverDir(); + static QDir cacheCoverDir(); + + void initialize(); + void destroy(); + DbConnection* getMyConnection(); + + //helper methods which perform updates of amarok's database + void updateStatsTables(); + void updatePersistentTables(); + void updatePodcastTables(); + + //A dirty hack to preserve Group By settings in Collection Browser after addition + //of Composer table + void updateGroupBy(); + + void customEvent( QCustomEvent * ); + + // helpers for embedded images + QString loadHashFile( const QCString& hash, uint width ); + bool extractEmbeddedImage( const MetaBundle &trackInformation, QCString& hash ); + + //general management methods + void createStatsTable(); + void dropStatsTable(); + void createPersistentTables(); + void dropPersistentTables(); + void createPodcastTables(); + void dropPodcastTables(); + void createDevicesTable(); + void dropDevicesTable(); + + //Archived forms of the above. useful for providing a linear upgrade routine that + //stays the same + void createStatsTableV8(); + void createStatsTableV10( bool temp ); + void dropStatsTableV1(); + void createPersistentTablesV12(); + void createPersistentTablesV14( bool temp ); + void dropPersistentTablesV14(); + void createPodcastTablesV2( bool temp ); + void dropPodcastTablesV2(); + + + QCString makeWidthKey( uint width ); + QString artistValue( uint id ); + QString composerValue( uint id ); + QString albumValue( uint id ); + QString genreValue( uint id ); + QString yearValue( uint id ); + + //These should be avoided as they will be slow and potentially unsafe. + //Use the Exact version where possible (faster and safer). + //To convert output from Exact version from QString to uint, use .toUInt() + uint IDFromValue( QString name, QString value, bool autocreate = true, const bool temporary = false ); + QString IDFromExactValue( QString table, QString value, bool autocreate = true, bool temporary = false ); + QString valueFromID( QString table, uint id ); + + //member variables + QString m_amazonLicense; + bool m_validArtistCache; + bool m_validComposerCache; + bool m_validAlbumCache; + QString m_cacheArtist[2]; + uint m_cacheArtistID[2]; + QString m_cacheComposer[2]; + uint m_cacheComposerID[2]; + QString m_cacheAlbum[2]; + uint m_cacheAlbumID[2]; + + bool m_monitor; + bool m_autoScoring; + + static QMap *itemCoverMap; + static QMutex *itemCoverMapMutex; + QImage m_noCover, m_shadowImage; + + static QMap *threadConnections; + static QMutex *connectionMutex; + DbConnection::DbConnectionType m_dbConnType; + DbConfig *m_dbConfig; + + //organize files stuff + bool m_waitForFileOperation; + bool m_fileOperationFailed; + bool m_scanInProgress; + bool m_rescanRequired; + + QStringList m_aftEnabledPersistentTables; + + // Cancel move/copy job + bool m_moveFileJobCancelled; + + // for handling podcast image url redirects + QMap m_podcastImageJobs; + + // protect against multiple simultaneous queries/inserts + QMutex m_mutex; +}; + +class INotify : public ThreadManager::DependentJob +{ + Q_OBJECT + + public: + INotify( CollectionDB *parent, int fd ); + ~INotify(); + + static INotify *instance() { return s_instance; } + + bool watchDir( const QString directory ); + int fd() { return m_fd; } + + private: + virtual bool doJob(); + + CollectionDB* m_parent; + int m_fd; + + static INotify* s_instance; +}; + + +class QueryBuilder +{ + public: + //attributes: + enum qBuilderTables { tabAlbum = 1, tabArtist = 2, tabComposer = 4, tabGenre = 8, tabYear = 16, tabSong = 64, + tabStats = 128, tabLyrics = 256, tabPodcastChannels = 512, + tabPodcastEpisodes = 1024, tabPodcastFolders = 2048, + tabDevices = 4096, tabLabels = 8192 + /* dummy table for filtering */, tabDummy = 0 }; + enum qBuilderOptions { optNoCompilations = 1, optOnlyCompilations = 2, optRemoveDuplicates = 4, + optRandomize = 8, + optShowAll = 16 /* get all songs, not just mounted ones */ }; + /* This has been an enum in the past, but 32 bits wasn't enough anymore :-( */ + static const Q_INT64 valDummy = 0; + static const Q_INT64 valID = 1LL << 0; + static const Q_INT64 valName = 1LL << 1; + static const Q_INT64 valURL = 1LL << 2; + static const Q_INT64 valTitle = 1LL << 3; + static const Q_INT64 valTrack = 1LL << 4; + static const Q_INT64 valScore = 1LL << 5; + static const Q_INT64 valComment = 1LL << 6; + static const Q_INT64 valBitrate = 1LL << 7; + static const Q_INT64 valLength = 1LL << 8; + static const Q_INT64 valSamplerate = 1LL << 9; + static const Q_INT64 valPlayCounter = 1LL << 10; + static const Q_INT64 valCreateDate = 1LL << 11; + static const Q_INT64 valAccessDate = 1LL << 12; + //static const Q_INT64 valPercentage = 1LL << 13; // same as valScore + static const Q_INT64 valArtistID = 1LL << 14; + static const Q_INT64 valAlbumID = 1LL << 15; + static const Q_INT64 valYearID = 1LL << 16; + static const Q_INT64 valGenreID = 1LL << 17; + static const Q_INT64 valDirectory = 1LL << 18; + static const Q_INT64 valLyrics = 1LL << 19; + static const Q_INT64 valRating = 1LL << 20; + static const Q_INT64 valComposerID = 1LL << 21; + static const Q_INT64 valDiscNumber = 1LL << 22; + static const Q_INT64 valFilesize = 1LL << 23; + static const Q_INT64 valFileType = 1LL << 24; + static const Q_INT64 valIsCompilation = 1LL << 25; + static const Q_INT64 valBPM = 1LL << 26; + // podcast relevant: + static const Q_INT64 valCopyright = 1LL << 27; + static const Q_INT64 valParent = 1LL << 28; + static const Q_INT64 valWeblink = 1LL << 29; + static const Q_INT64 valAutoscan = 1LL << 30; + static const Q_INT64 valFetchtype = 1LL << 31; + static const Q_INT64 valAutotransfer = 1LL << 32; + static const Q_INT64 valPurge = 1LL << 33; + static const Q_INT64 valPurgeCount = 1LL << 34; + static const Q_INT64 valIsNew = 1LL << 35; + // dynamic collection relevant: + static const Q_INT64 valDeviceId = 1LL << 36; + static const Q_INT64 valRelativePath = 1LL << 37; + static const Q_INT64 valDeviceLabel = 1LL << 38; + static const Q_INT64 valMountPoint = 1LL << 39; + //label relevant + static const Q_INT64 valType = 1LL << 40; + + static Q_INT64 valForFavoriteSorting(); + void sortByFavorite(); + + // sortByFavoriteAvg() add the average rating, if enabled, the average score, if enabled, + // and the average playcounter as return values! + void sortByFavoriteAvg(); + + enum qBuilderFunctions { funcNone = 0, funcCount = 1, funcMax = 2, funcMin = 4, funcAvg = 8, funcSum = 16 }; + + // Note: modes beginMatch, endMatch are only supported for string filters + // Likewise, modes between and notBetween are only supported for numeric filters + enum qBuilderFilter { modeNormal = 0, modeLess = 1, modeGreater = 2, modeEndMatch = 3, modeBeginMatch = 4, modeBetween = 5, modeNotBetween = 6}; + + QueryBuilder(); + + void addReturnValue( int table, Q_INT64 value, bool caseSensitive = false /* unless value refers to a string */ ); + void addReturnFunctionValue( int function, int table, Q_INT64 value); + uint countReturnValues(); + + // Note: the filter chain begins in AND mode + void beginOR(); //filters will be ORed instead of ANDed + void endOR(); //don't forget to end it! + void beginAND(); // These do the opposite; for recursive and/or + void endAND(); + + void setGoogleFilter( int defaultTables, QString query ); + + void addURLFilters( const QStringList& filter ); + + void addFilter( int tables, const QString& filter); + void addFilter( int tables, Q_INT64 value, const QString& filter, int mode = modeNormal, bool exact = false ); + void addFilters( int tables, const QStringList& filter ); + void excludeFilter( int tables, const QString& filter ); + void excludeFilter( int tables, Q_INT64 value, const QString& filter, int mode = modeNormal, bool exact = false ); + + void addMatch( int tables, const QString& match, bool interpretUnknown = true, bool caseSensitive = true ); + void addMatch( int tables, Q_INT64 value, const QString& match, bool interpretUnknown = true, bool caseSensitive = true ); + void addMatches( int tables, const QStringList& match, bool interpretUnknown = true, bool caseSensitive = true ); + void excludeMatch( int tables, const QString& match ); + void having( int table, Q_INT64 value, int function, int mode, const QString& match ); + + void exclusiveFilter( int tableMatching, int tableNotMatching, Q_INT64 value ); + + // For numeric filters: + // modeNormal means strict equality; modeBeginMatch and modeEndMatch are not + // allowed; modeBetween needs a second value endRange + void addNumericFilter(int tables, Q_INT64 value, const QString &n, + int mode = modeNormal, + const QString &endRange = QString::null); + + void setOptions( int options ); + void sortBy( int table, Q_INT64 value, bool descending = false ); + void sortByFunction( int function, int table, Q_INT64 value, bool descending = false ); + void groupBy( int table, Q_INT64 value ); + void setLimit( int startPos, int length ); + + // Returns the results in random order. + // If a \p table and \p value are specified, uses weighted random order on + // that field. + // The shuffle is cumulative with other sorts, but any sorts after this are + // pointless because of the precision of the random function. + void shuffle( int table = 0, Q_INT64 value = 0 ); + + static const int dragFieldCount; + static QString dragSQLFields(); + void initSQLDrag(); + + void buildQuery( bool withDeviceidPlaceholder = false ); + QString getQuery(); + //use withDeviceidPlaceholder = false if the query isn't run immediately (*CurrentTimeT*) + //and replace (*MountedDeviceSelection*) with CollectionDB::instance()->deviceIdSelection() + QString query( bool withDeviceidPlaceholder = false ) { buildQuery( withDeviceidPlaceholder ); return m_query; }; + void clear(); + + QStringList run(); + + // Transform a string table.value "field" into enum values + // @return true if we succeeded + bool getField(const QString &tableValue, int *table, Q_INT64 *value); + + private: + QString tableName( int table ); + const QString &valueName( Q_INT64 value ); + QString functionName( int functions ); + bool coalesceField( int table, Q_INT64 value ); + + int getTableByName(const QString &name); + Q_INT64 getValueByName(const QString &field); + + QStringList cleanURL( QStringList result ); + + void linkTables( int tables ); + + QValueStack m_OR; + bool m_showAll; + uint m_deviceidPos; + + QString ANDslashOR() const; + + QString m_query; + QString m_values; + QString m_tables; + QString m_join; + QString m_where; + QString m_sort; + QString m_group; + QString m_limit; + QString m_having; + + QString m_url; //url is used as primary key and linkTables needs to do some special stuff with it + + int m_linkTables; + uint m_returnValues; +}; + +inline void QueryBuilder::beginOR() +{ + m_where += ANDslashOR() + " ( " + CollectionDB::instance()->boolF() + ' '; + m_OR.push(true); +} +inline void QueryBuilder::endOR() +{ + m_where += " ) "; + m_OR.pop(); +} +inline void QueryBuilder::beginAND() +{ + m_where += ANDslashOR() + " ( " + CollectionDB::instance()->boolT() + ' '; + m_OR.push(false); +} +inline void QueryBuilder::endAND() +{ + m_where += " ) "; + m_OR.pop(); +} +inline QString QueryBuilder::ANDslashOR() const { return m_OR.top() ? "OR" : "AND"; } + + +#endif /* AMAROK_COLLECTIONDB_H */ diff --git a/amarok/src/collectionscanner/Makefile.am b/amarok/src/collectionscanner/Makefile.am new file mode 100644 index 00000000..51c2f386 --- /dev/null +++ b/amarok/src/collectionscanner/Makefile.am @@ -0,0 +1,28 @@ +bin_PROGRAMS = amarokcollectionscanner + +METASOURCES = AUTO + +INCLUDES = \ + -I$(top_srcdir)/amarok/src \ + $(TAGLIB_CFLAGS) \ + $(all_includes) + +noinst_HEADERS = \ + collectionscannerdcophandler.h + +amarokcollectionscanner_SOURCES = \ + main.cpp \ + collectionscannerdcopiface.skel \ + collectionscannerdcophandler.cpp \ + collectionscanner.cpp + +amarokcollectionscanner_LDADD = \ + $(top_builddir)/amarok/src/metadata/libmetadata.la \ + $(top_builddir)/amarok/src/libamarok.la \ + $(LIB_QT) \ + $(LIB_KDECORE) \ + $(TAGLIB_LIBS) + +amarokcollectionscanner_LDFLAGS = \ + $(all_libraries) \ + $(KDE_RPATH) diff --git a/amarok/src/collectionscanner/collectionscanner.cpp b/amarok/src/collectionscanner/collectionscanner.cpp new file mode 100644 index 00000000..50e10e48 --- /dev/null +++ b/amarok/src/collectionscanner/collectionscanner.cpp @@ -0,0 +1,480 @@ +/*************************************************************************** + * Copyright (C) 2003-2005 by The Amarok Developers * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Steet, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#define DEBUG_PREFIX "CollectionScanner" + +#include "amarok.h" +#include "collectionscanner.h" +#include "collectionscannerdcophandler.h" +#include "debug.h" + +#include +#include + +#include //stat +#include //PATH_MAX +#include //realpath + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + + +CollectionScanner::CollectionScanner( const QStringList& folders, + bool recursive, + bool incremental, + bool importPlaylists, + bool restart ) + : KApplication( /*allowStyles*/ false, /*GUIenabled*/ false ) + , m_importPlaylists( importPlaylists ) + , m_folders( folders ) + , m_recursively( recursive ) + , m_incremental( incremental ) + , m_restart( restart ) + , m_logfile( Amarok::saveLocation( QString::null ) + "collection_scan.log" ) + , m_pause( false ) +{ + DcopCollectionScannerHandler* dcsh = new DcopCollectionScannerHandler(); + connect( dcsh, SIGNAL(pauseRequest()), this, SLOT(pause()) ); + connect( dcsh, SIGNAL(unpauseRequest()), this, SLOT(resume()) ); + kapp->setName( QString( "amarokcollectionscanner" ).ascii() ); + if( !restart ) + QFile::remove( m_logfile ); + + QTimer::singleShot( 0, this, SLOT( doJob() ) ); +} + + +CollectionScanner::~CollectionScanner() +{ + DEBUG_BLOCK +} + +void +CollectionScanner::pause() +{ + DEBUG_BLOCK + m_pause = true; +} + +void +CollectionScanner::resume() +{ + DEBUG_BLOCK + m_pause = false; +} + + + +void +CollectionScanner::doJob() //SLOT +{ + std::cout << ""; + std::cout << ""; + + + QStringList entries; + + if( m_restart ) { + QFile logFile( m_logfile ); + QString lastFile; + if ( !logFile.open( IO_ReadOnly ) ) + warning() << "Failed to open log file " << logFile.name() << " read-only" + << endl; + else { + QTextStream logStream; + logStream.setDevice(&logFile); + logStream.setEncoding(QTextStream::UnicodeUTF8); + lastFile = logStream.read(); + logFile.close(); + } + + QFile folderFile( Amarok::saveLocation( QString::null ) + "collection_scan.files" ); + if ( !folderFile.open( IO_ReadOnly ) ) + warning() << "Failed to open folder file " << folderFile.name() + << " read-only" << endl; + else { + QTextStream folderStream; + folderStream.setDevice(&folderFile); + folderStream.setEncoding(QTextStream::UnicodeUTF8); + entries = QStringList::split( "\n", folderStream.read() ); + } + + for( int count = entries.findIndex( lastFile ) + 1; count; --count ) + entries.pop_front(); + + } + else { + foreachType( QStringList, m_folders ) { + if( (*it).isEmpty() ) + //apparently somewhere empty strings get into the mix + //which results in a full-system scan! Which we can't allow + continue; + + QString dir = *it; + if( !dir.endsWith( "/" ) ) + dir += '/'; + + readDir( dir, entries ); + } + + QFile folderFile( Amarok::saveLocation( QString::null ) + "collection_scan.files" ); + if ( !folderFile.open( IO_WriteOnly ) ) + warning() << "Failed to open folder file " << folderFile.name() + << " read-only" << endl; + else { + QTextStream stream( &folderFile ); + stream.setEncoding(QTextStream::UnicodeUTF8); + stream << entries.join( "\n" ); + folderFile.close(); + } + } + + if( !entries.isEmpty() ) { + if( !m_restart ) { + AttributeMap attributes; + attributes["count"] = QString::number( entries.count() ); + writeElement( "itemcount", attributes ); + } + + scanFiles( entries ); + } + + std::cout << "" << std::endl; + + quit(); +} + + +void +CollectionScanner::readDir( const QString& dir, QStringList& entries ) +{ + static DCOPRef dcopRef( "amarok", "collection" ); + + // linux specific, but this fits the 90% rule + if( dir.startsWith( "/dev" ) || dir.startsWith( "/sys" ) || dir.startsWith( "/proc" ) ) + return; + + const QCString dir8Bit = QFile::encodeName( dir ); + DIR *d = opendir( dir8Bit ); + if( d == NULL ) { + warning() << "Skipping, " << strerror(errno) << ": " << dir << endl; + return; + } +#ifdef USE_SOLARIS + int dfd = d->d_fd; +#else + int dfd = dirfd(d); +#endif + if (dfd == -1) { + warning() << "Skipping, unable to obtain file descriptor: " << dir << endl; + closedir(d); + return; + } + + struct stat statBuf; + struct stat statBuf_symlink; + fstat( dfd, &statBuf ); + + struct direntry de; + memset(&de, 0, sizeof(struct direntry)); + de.dev = statBuf.st_dev; + de.ino = statBuf.st_ino; + + int f = -1; +#if __GNUC__ < 4 + for( unsigned int i = 0; i < m_processedDirs.size(); ++i ) + if( memcmp( &m_processedDirs[i], &de, sizeof( direntry ) ) == 0 ) { + f = i; break; + } +#else + f = m_processedDirs.find( de ); +#endif + + if ( ! S_ISDIR( statBuf.st_mode ) || f != -1 ) { + debug() << "Skipping, already scanned: " << dir << endl; + closedir(d); + return; + } + + AttributeMap attributes; + attributes["path"] = dir; + writeElement( "folder", attributes ); + + m_processedDirs.resize( m_processedDirs.size() + 1 ); + m_processedDirs[m_processedDirs.size() - 1] = de; + + for( dirent *ent; ( ent = readdir( d ) ); ) { + QCString entry (ent->d_name); + QCString entryname (ent->d_name); + + if ( entry == "." || entry == ".." ) + continue; + + entry.prepend( dir8Bit ); + + if ( stat( entry, &statBuf ) != 0 ) + continue; + if ( lstat( entry, &statBuf_symlink ) != 0 ) + continue; + + // loop protection + if ( ! ( S_ISDIR( statBuf.st_mode ) || S_ISREG( statBuf.st_mode ) ) ) + continue; + + if ( S_ISDIR( statBuf.st_mode ) && m_recursively && entry.length() && entryname[0] != '.' ) + { + if ( S_ISLNK( statBuf_symlink.st_mode ) ) { + char nosymlink[PATH_MAX]; + if ( realpath( entry, nosymlink ) ) { + debug() << entry << " is a symlink. Using: " << nosymlink << endl; + entry = nosymlink; + } + } + const QString file = QFile::decodeName( entry ); + + bool isInCollection = false; + if( m_incremental ) + dcopRef.call( "isDirInCollection", file ).get( isInCollection ); + + if( !m_incremental || !isInCollection ) + // we MUST add a '/' after the dirname + readDir( file + '/', entries ); + } + + else if( S_ISREG( statBuf.st_mode ) ) + entries.append( QFile::decodeName( entry ) ); + } + + closedir( d ); +} + + +void +CollectionScanner::scanFiles( const QStringList& entries ) +{ + DEBUG_BLOCK + + typedef QPair CoverBundle; + + QStringList validImages; validImages << "jpg" << "png" << "gif" << "jpeg"; + QStringList validPlaylists; validPlaylists << "m3u" << "pls"; + + QValueList covers; + QStringList images; + + int itemCount = 0; + + DCOPRef dcopRef( "amarok", "collection" ); + + foreachType( QStringList, entries ) { + const QString path = *it; + const QString ext = extension( path ); + const QString dir = directory( path ); + + itemCount++; + + // Write path to logfile + if( !m_logfile.isEmpty() ) { + QFile log( m_logfile ); + if( log.open( IO_WriteOnly ) ) { + QCString cPath = path.utf8(); + log.writeBlock( cPath, cPath.length() ); + log.close(); + } + } + + if( validImages.contains( ext ) ) + images += path; + + else if( m_importPlaylists && validPlaylists.contains( ext ) ) { + AttributeMap attributes; + attributes["path"] = path; + writeElement( "playlist", attributes ); + } + + else { + MetaBundle::EmbeddedImageList images; + MetaBundle mb( KURL::fromPathOrURL( path ), true, TagLib::AudioProperties::Fast, &images ); + const AttributeMap attributes = readTags( mb ); + + if( !attributes.empty() ) { + writeElement( "tags", attributes ); + + CoverBundle cover( attributes["artist"], attributes["album"] ); + + if( !covers.contains( cover ) ) + covers += cover; + + foreachType( MetaBundle::EmbeddedImageList, images ) { + AttributeMap attributes; + attributes["path"] = path; + attributes["hash"] = (*it).hash(); + attributes["description"] = (*it).description(); + writeElement( "embed", attributes ); + } + } + } + + // Update Compilation-flag, when this is the last loop-run + // or we're going to switch to another dir in the next run + QStringList::ConstIterator itTemp( it ); + ++itTemp; + if( path == entries.last() || dir != directory( *itTemp ) ) + { + // we entered the next directory + foreachType( QStringList, images ) { + // Serialize CoverBundle list with AMAROK_MAGIC as separator + QString string; + + for( QValueList::ConstIterator it2 = covers.begin(); it2 != covers.end(); ++it2 ) + string += (*it2).first + "AMAROK_MAGIC" + (*it2).second + "AMAROK_MAGIC"; + + AttributeMap attributes; + attributes["path"] = *it; + attributes["list"] = string; + writeElement( "image", attributes ); + } + + AttributeMap attributes; + attributes["path"] = dir; + writeElement( "compilation", attributes ); + + // clear now because we've processed them + covers.clear(); + images.clear(); + } + + if( itemCount % 20 == 0 ) + { + kapp->processEvents(); // let DCOP through! + if( m_pause ) + { + dcopRef.send( "scannerAcknowledged" ); + while( m_pause ) + { + sleep( 1 ); + kapp->processEvents(); + } + dcopRef.send( "scannerAcknowledged" ); + } + } + + } +} + + +AttributeMap +CollectionScanner::readTags( const MetaBundle& mb ) +{ + // Tests reveal the following: + // + // TagLib::AudioProperties Relative Time Taken + // + // No AudioProp Reading 1 + // Fast 1.18 + // Average Untested + // Accurate Untested + + AttributeMap attributes; + + if ( !mb.isValidMedia() ) { + std::cout << ""; + return attributes; + } + + attributes["path"] = mb.url().path(); + attributes["title"] = mb.title(); + attributes["artist"] = mb.artist(); + attributes["composer"]= mb.composer(); + attributes["album"] = mb.album(); + attributes["comment"] = mb.comment(); + attributes["genre"] = mb.genre(); + attributes["year"] = mb.year() ? QString::number( mb.year() ) : QString(); + attributes["track"] = mb.track() ? QString::number( mb.track() ) : QString(); + attributes["discnumber"] = mb.discNumber() ? QString::number( mb.discNumber() ) : QString(); + attributes["bpm"] = mb.bpm() ? QString::number( mb.bpm() ) : QString(); + attributes["filetype"] = QString::number( mb.fileType() ); + attributes["uniqueid"] = mb.uniqueId(); + attributes["compilation"] = QString::number( mb.compilation() ); + + if ( mb.audioPropertiesUndetermined() ) + attributes["audioproperties"] = "false"; + else { + attributes["audioproperties"] = "true"; + attributes["bitrate"] = QString::number( mb.bitrate() ); + attributes["length"] = QString::number( mb.length() ); + attributes["samplerate"] = QString::number( mb.sampleRate() ); + } + + if ( mb.filesize() >= 0 ) + attributes["filesize"] = QString::number( mb.filesize() ); + + return attributes; +} + + +void +CollectionScanner::writeElement( const QString& name, const AttributeMap& attributes ) +{ + QDomDocument doc; // A dummy. We don't really use DOM, but SAX2 + QDomElement element = doc.createElement( name ); + + foreachType( AttributeMap, attributes ) + { + // There are at least some characters that Qt cannot categorize which make the resulting + // xml document ill-formed and prevent the parser from processing the remaining document. + // Because of this we skip attributes containing characters not belonging to any category. + QString data = it.data(); + const unsigned len = data.length(); + bool nonPrint = false; + for( unsigned i = 0; i < len; i++ ) + { + if( data.ref( i ).category() == QChar::NoCategory ) + { + nonPrint = true; + break; + } + } + + if( nonPrint ) + continue; + + element.setAttribute( it.key(), it.data() ); + } + + QString text; + QTextStream stream( &text, IO_WriteOnly ); + element.save( stream, 0 ); + + std::cout << text.utf8() << std::endl; +} + + +#include "collectionscanner.moc" + diff --git a/amarok/src/collectionscanner/collectionscanner.h b/amarok/src/collectionscanner/collectionscanner.h new file mode 100644 index 00000000..a6a17a2f --- /dev/null +++ b/amarok/src/collectionscanner/collectionscanner.h @@ -0,0 +1,116 @@ +/*************************************************************************** + * Copyright (C) 2003-2005 by The Amarok Developers * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Steet, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef COLLECTIONSCANNER_H +#define COLLECTIONSCANNER_H + +#include "metabundle.h" + +#include +#include +#include + +#include +#include + +#include + +typedef QMap AttributeMap; + + +/** + * @class CollectionScanner + * @short Scans directories and builds the Collection + */ + +class CollectionScanner : public KApplication +{ + Q_OBJECT + +public: + CollectionScanner( const QStringList& folders, + bool recursive, + bool incremental, + bool importPlaylists, + bool restart ); + + ~CollectionScanner(); + int newInstance() { return 0; } + +public slots: + void pause(); + void resume(); + +private slots: + void doJob(); + +private: + void readDir( const QString& dir, QStringList& entries ); + void scanFiles( const QStringList& entries ); + + /** + * Read metadata tags of a given file. + * @mb MetaBundle for the file. + * @return QMap containing tags, or empty QMap on failure. + */ + AttributeMap readTags( const MetaBundle& mb ); + + /** + * Helper method for writing XML elements to stdout. + * @name Name of the element. + * @attributes Key/value map of attributes. + */ + void writeElement( const QString& name, const AttributeMap& attributes ); + + + /** + * @return the LOWERCASE file extension without the preceding '.', or "" if there is none + */ + inline QString extension( const QString &fileName ) + { + return fileName.contains( '.' ) ? fileName.mid( fileName.findRev( '.' ) + 1 ).lower() : ""; + } + + /** + * @return the last directory in @param fileName + */ + inline QString directory( const QString &fileName ) + { + return fileName.section( '/', 0, -2 ); + } + + + const bool m_importPlaylists; + QStringList m_folders; + const bool m_recursively; + const bool m_incremental; + const bool m_restart; + const QString m_logfile; + bool m_pause; + + struct direntry { + dev_t dev; + ino_t ino; + } KDE_PACKED; + + QMemArray m_processedDirs; +}; + + +#endif // COLLECTIONSCANNER_H diff --git a/amarok/src/collectionscanner/collectionscannerdcophandler.cpp b/amarok/src/collectionscanner/collectionscannerdcophandler.cpp new file mode 100644 index 00000000..c758d571 --- /dev/null +++ b/amarok/src/collectionscanner/collectionscannerdcophandler.cpp @@ -0,0 +1,49 @@ +/*************************************************************************** + collectionscannerdcophandler.cpp - DCOP Implementation + ------------------- + begin : 16/08/05 + copyright : (C) 2006 by Jeff Mitchell + email : kde-dev@emailgoeshere.com + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "collectionscannerdcophandler.h" + +#include + +///////////////////////////////////////////////////////////////////////////////////// +// class DcopCollectionScannerHandler +///////////////////////////////////////////////////////////////////////////////////// + + DcopCollectionScannerHandler::DcopCollectionScannerHandler() + : DCOPObject( "scanner" ) + , QObject( kapp ) + { + // Register with DCOP + if ( !kapp->dcopClient()->isRegistered() ) { + kapp->dcopClient()->registerAs( "amarokcollectionscanner", false ); + kapp->dcopClient()->setDefaultObject( objId() ); + } + } + + void DcopCollectionScannerHandler::pause() + { + //do nothing for now + emit pauseRequest(); + } + + void DcopCollectionScannerHandler::unpause() + { + //do nothing for now + emit unpauseRequest(); + } + +#include "collectionscannerdcophandler.moc" diff --git a/amarok/src/collectionscanner/collectionscannerdcophandler.h b/amarok/src/collectionscanner/collectionscannerdcophandler.h new file mode 100644 index 00000000..e4133212 --- /dev/null +++ b/amarok/src/collectionscanner/collectionscannerdcophandler.h @@ -0,0 +1,43 @@ +/*************************************************************************** + collectionscannerdcophandler.h - DCOP Interface + ------------------- + begin : 16/08/05 + copyright : (C) 2006 by Jeff Mitchell + email : kde-dev@emailgoeshere.com + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef COLLECTIONSCANNER_DCOP_HANDLER_H +#define COLLECTIONSCANNER_DCOP_HANDLER_H + +#include "collectionscannerdcopiface.h" + +#include + +#include + +class DcopCollectionScannerHandler : public QObject, virtual public CollectionScannerInterface +{ + Q_OBJECT + + public: + DcopCollectionScannerHandler(); + + signals: + void pauseRequest(); + void unpauseRequest(); + + public: + virtual void pause(); + virtual void unpause(); +}; + +#endif diff --git a/amarok/src/collectionscanner/collectionscannerdcopiface.h b/amarok/src/collectionscanner/collectionscannerdcopiface.h new file mode 100644 index 00000000..b0872686 --- /dev/null +++ b/amarok/src/collectionscanner/collectionscannerdcopiface.h @@ -0,0 +1,37 @@ +/*************************************************************************** + collectionscannerdcopiface.h - DCOP Interface + ------------------- + begin : 16/08/05 + copyright : (C) 2006 by Jeff Mitchell + email : kde-dev@emailgoeshere.com + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef COLLECTIONSCANNER_DCOPIFACE_H +#define COLLECTIONSCANNER_DCOPIFACE_H + +#include + +/////////////////////////////////////////////////////////////////////// +// WARNING! Please ask on #amarok before modifying the DCOP interface! +/////////////////////////////////////////////////////////////////////// + + +class CollectionScannerInterface : virtual public DCOPObject +{ + K_DCOP + +k_dcop: + virtual void pause() = 0; ///< Pause the scanner + virtual void unpause() = 0; ///< Unpause the scanner +}; + +#endif diff --git a/amarok/src/collectionscanner/main.cpp b/amarok/src/collectionscanner/main.cpp new file mode 100644 index 00000000..8ca61a6d --- /dev/null +++ b/amarok/src/collectionscanner/main.cpp @@ -0,0 +1,80 @@ +/*************************************************************************** + * Copyright (C) 2003-2006 by The Amarok Developers * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Steet, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "collectionscanner.h" +#include "metadata/tplugins.h" + +#include + +#include +#include +#include +#include + +int main( int argc, char *argv[] ) +{ + const KAboutData about( "amarokcollectionscanner", + I18N_NOOP( "Amarok Collection Scanner\n\nNote: For debugging purposes this application can be invoked from the command line, but it will not actually build a collection this way." ), "0.1", + I18N_NOOP( "Collection Scanner for Amarok" ), KAboutData::License_GPL, + I18N_NOOP( "(C) 2003-2006, The Amarok Developers" ), + I18N_NOOP( "IRC:\nserver: irc.freenode.net / channels: #amarok #amarok.de #amarok.es\n\nFeedback:\namarok@kde.org" ), + I18N_NOOP( "http://amarok.kde.org" ) ); + + + static KCmdLineOptions options[] = + { + { "+Folder(s)", I18N_NOOP( "Folders to scan" ), 0 }, + { "r", 0, 0 }, + { "recursive", I18N_NOOP( "Scan folders recursively" ), 0 }, + { "i", 0, 0 }, + { "incremental", I18N_NOOP( "Incremental Scan (modified folders only)" ), 0 }, + { "p", 0, 0 }, + { "importplaylists", I18N_NOOP( "Import playlist" ), 0 }, + { "s", 0, 0 }, + { "restart", I18N_NOOP( "Restart the scanner at last position, after a crash" ), "" }, + { 0, 0, 0 } + }; + + + KCmdLineArgs::reset(); + KCmdLineArgs::init( argc, argv, &about ); //calls KApplication::addCmdLineOptions() + KCmdLineArgs::addCmdLineOptions( options ); //add our own options + + const KCmdLineArgs* const args = KCmdLineArgs::parsedArgs(); + + // Parse list of folder arguments + QStringList folders; + for( int i = 0; i < args->count(); i++ ) + folders << QFile::decodeName( args->arg( i ) ); + + const bool recursive = args->isSet( "recursive" ); + const bool incremental = args->isSet( "incremental" ); + const bool importplaylists = args->isSet( "importplaylists" ); + const bool restart = args->isSet( "restart" ); + + KApplication::disableAutoDcopRegistration(); + + CollectionScanner scanner( folders, recursive, incremental, importplaylists, restart ); + + registerTaglibPlugins(); + + return scanner.exec(); +} + + diff --git a/amarok/src/colorgenerator.h b/amarok/src/colorgenerator.h new file mode 100644 index 00000000..d209012f --- /dev/null +++ b/amarok/src/colorgenerator.h @@ -0,0 +1,81 @@ +/*************************************************************************** + copyright : (C) 2004 by amarok squad + email : amarok@kde.org + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Steet, Fifth Floor, Boston, MA 02110-1301 * + * USA * + ***************************************************************************/ + +#ifndef COLORGENERATOR_H +#define COLORGENERATOR_H + +#include "debug.h" + +namespace Amarok { + + +class Color : public QColor +{ + static const int CONTRAST = 130; + static const int SATURATION_TARGET = 30; + +public: + Color( const QColor &c ) : QColor( c ) + { + DEBUG_BLOCK + + int h,s1,s,v1,v; + getHsv( &h, &s1, &v1 ); + + debug() << "Initial Color Properties: s:" << s1 << " v:" << v1 << endl; + + //we want the new colour to be low saturation + //TODO what if s is less than SATURATION_TARGET to start with + s = s1 - CONTRAST; + v = v1; + + if ( s < SATURATION_TARGET ) { + int remainingContrast = SATURATION_TARGET - s; + s = SATURATION_TARGET; + + debug() << "Unapplied Contrast: " << remainingContrast << endl; + + //we only add to the value to avoid the dreaded "grey-gradient" + v += remainingContrast; + + if ( v > 255 ) { + int error = v - 255; + debug() << "Over-compensation: " << error << endl; + + //if the error is significant then this must be a pretty bright colour + //it would look better if the gradient was dark + if( error > CONTRAST/2 ) + v = v1 - error; + else + v = 255; + } + } + + setHsv( h, s, v ); + + debug() << "Final Colour Properties: s:" << s << " v:" << v << endl; + } +}; + +} + +#endif + diff --git a/amarok/src/columnlist.cpp b/amarok/src/columnlist.cpp new file mode 100644 index 00000000..21d2ffee --- /dev/null +++ b/amarok/src/columnlist.cpp @@ -0,0 +1,226 @@ +/* + Copyright (c) 2006 Gábor Lehel + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include + +#include +#include +#include +#include +#include +#include +#include "amarokconfig.h" +#include "metabundle.h" +#include "playlist.h" + +#include "columnlist.h" + +class MyCheckListItem: public QCheckListItem +{ + typedef QCheckListItem super; + ColumnList *m_list; +public: + int column; + MyCheckListItem( int c, QListView *v, const QString &s, Type t, ColumnList *list ): + super( v, s, t ), m_list( list ), column( c ) { } + virtual void paintCell( QPainter * p, const QColorGroup &cg, int c, int w, int a ) + { + QFont f = p->font(); + if( isOn() ) + f.setBold( !f.bold() ); + p->setFont( f ); + super::paintCell( p, cg, c, w, a ); + } + virtual void stateChange( bool b ) + { + super::stateChange( b ); + m_list->setChanged(); + } + MyCheckListItem *itemAbove() { return static_cast( QCheckListItem::itemAbove() ); } + MyCheckListItem *itemBelow() { return static_cast( QCheckListItem::itemBelow() ); } +}; + +ColumnList::ColumnList( QWidget *parent, const char *name ) + : QHBox( parent, name ), m_changed( true ) +{ + setSpacing( 5 ); + + QVBox *buttonbox = new QVBox( this ); + + m_up = new KPushButton( KGuiItem( QString::null, "up" ), buttonbox ); + QToolTip::add( m_up, i18n( "Move column up" ) ); + connect( m_up, SIGNAL( clicked() ), this, SLOT( moveUp() ) ); + + m_down = new KPushButton( KGuiItem( QString::null, "down" ), buttonbox ); + QToolTip::add( m_down, i18n( "Move column down" ) ); + connect( m_down, SIGNAL( clicked() ), this, SLOT( moveDown() ) ); + + m_list = new KListView( this ); + m_list->addColumn(""); + m_list->header()->hide(); + m_list->setSelectionMode( QListView::Single ); + m_list->setResizeMode( QListView::LastColumn ); + m_list->setSorting( -1 ); + m_list->setAcceptDrops( true ); + m_list->setDragEnabled( true ); + m_list->setDropVisualizer( true ); + m_list->setDropVisualizerWidth( 3 ); + connect( m_list, SIGNAL( moved() ), this, SLOT( updateUI() ) ); + connect( m_list, SIGNAL( moved() ), this, SLOT( setChanged() ) ); + connect( m_list, SIGNAL( currentChanged( QListViewItem* ) ), this, SLOT( updateUI() ) ); + + QHeader* const h = Playlist::instance()->header(); + for( int i = h->count() - 1; i >= 0; --i ) + { + const int s = h->mapToSection( i ); + if( ( s != MetaBundle::Rating || AmarokConfig::useRatings() ) && + ( s != MetaBundle::Mood || AmarokConfig::showMoodbar() ) && + ( s != MetaBundle::Score || AmarokConfig::useScores() ) ) + { + ( new MyCheckListItem( s, m_list, MetaBundle::prettyColumnName( s ), QCheckListItem::CheckBox, this ) ) + ->setOn( h->sectionSize( s ) ); + } + } + + m_list->setCurrentItem( m_list->firstChild() ); + updateUI(); + resetChanged(); +} + +QValueList ColumnList::visibleColumns() const +{ + QValueList v; + for( MyCheckListItem *item = static_cast( m_list->firstChild() ); item; item = item->itemBelow() ) + if( item->isOn() ) + v.append( item->column ); + return v; +} + +QValueList ColumnList::columnOrder() const +{ + QValueList v; + for( MyCheckListItem *item = static_cast( m_list->firstChild() ); item; item = item->itemBelow() ) + v.append( item->column ); + return v; +} + +bool ColumnList::isChanged() const +{ + return m_changed; +} + +void ColumnList::resetChanged() +{ + m_changed = false; +} + +void ColumnList::moveUp() +{ + if( QListViewItem *item = m_list->currentItem() ) + if( item->itemAbove() ) + { + m_list->moveItem( item, 0, item->itemAbove()->itemAbove() ); + m_list->setCurrentItem( item ); + m_list->ensureItemVisible( item ); + updateUI(); + setChanged(); + } +} + +void ColumnList::moveDown() +{ + if( QListViewItem *item = m_list->currentItem() ) + { + m_list->moveItem( item, 0, item->itemBelow() ); + m_list->setCurrentItem( item ); + m_list->ensureItemVisible( item ); + updateUI(); + setChanged(); + } +} + +void ColumnList::updateUI() +{ + m_up->setEnabled( m_list->currentItem() && m_list->currentItem()->itemAbove() ); + m_down->setEnabled( m_list->currentItem() && m_list->currentItem()->itemBelow() ); +} + +void ColumnList::setChanged() //slot +{ + if( !m_changed ) + { + m_changed = true; + emit changed(); + } +} + +ColumnsDialog::ColumnsDialog() + : KDialogBase( PlaylistWindow::self(), 0, false, i18n( "Playlist Columns" ) ), + m_list( new ColumnList( this ) ) +{ + setMainWidget( m_list ); + enableButtonApply( false ); + connect( m_list, SIGNAL( changed() ), this, SLOT( slotChanged() ) ); +} + +ColumnsDialog::~ColumnsDialog() +{ + s_instance = 0; +} + +void ColumnsDialog::slotApply() +{ + apply(); + KDialogBase::slotApply(); +} + +void ColumnsDialog::slotOk() +{ + apply(); + KDialogBase::slotOk(); +} + +void ColumnsDialog::hide() +{ + KDialogBase::hide(); + delete this; +} + +void ColumnsDialog::apply() +{ + Playlist::instance()->setColumns( m_list->columnOrder(), m_list->visibleColumns() ); + m_list->resetChanged(); + enableButtonApply( false ); +} + +void ColumnsDialog::display() //static +{ + if( !s_instance ) + s_instance = new ColumnsDialog; + s_instance->show(); +} + +void ColumnsDialog::slotChanged() //slot +{ + enableButtonApply( true ); +} + +ColumnsDialog *ColumnsDialog::s_instance = 0; + +#include "columnlist.moc" diff --git a/amarok/src/columnlist.h b/amarok/src/columnlist.h new file mode 100644 index 00000000..dd32e7da --- /dev/null +++ b/amarok/src/columnlist.h @@ -0,0 +1,76 @@ +/* + Copyright (c) 2006 Gábor Lehel + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef AMAROK_COLUMNLIST_H +#define AMAROK_COLUMNLIST_H + +#include +#include + +class KListView; +class KPushButton; +template class QValueList; + +class ColumnList: public QHBox +{ + Q_OBJECT +public: + ColumnList( QWidget *parent = 0, const char *name = 0 ); + QValueList visibleColumns() const; + QValueList columnOrder() const; + bool isChanged() const; + void resetChanged(); + +signals: + void changed(); + +private slots: + void moveUp(); + void moveDown(); + void updateUI(); + void setChanged(); + +private: + friend class MyCheckListItem; + KListView *m_list; + KPushButton *m_up, *m_down; + bool m_changed; +}; + +class ColumnsDialog: public KDialogBase +{ + Q_OBJECT +public: + static void display(); + +private: + ColumnList *m_list; + static ColumnsDialog *s_instance; + ColumnsDialog(); + ~ColumnsDialog(); +public: + virtual void slotApply(); + virtual void slotOk(); + virtual void hide(); + void apply(); +public slots: + void slotChanged(); +}; + +#endif diff --git a/amarok/src/configdialog.cpp b/amarok/src/configdialog.cpp new file mode 100644 index 00000000..d39d329e --- /dev/null +++ b/amarok/src/configdialog.cpp @@ -0,0 +1,449 @@ +/*************************************************************************** +begin : 2004/02/07 +copyright : (C) Mark Kretschmann +email : markey@web.de +***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "amarok.h" +#include "amarokconfig.h" +#include "app.h" +#include "collectiondb.h" +#include "config.h" // Has USE_MYSQL +#include "configdialog.h" +#include "contextbrowser.h" +#include "dbsetup.h" +#include "debug.h" +#include "directorylist.h" +#include "enginecontroller.h" +#include "mediabrowser.h" +#include "mediumpluginmanager.h" +#include "Options1.h" +#include "Options2.h" +#include "Options4.h" +#include "Options5.h" +#include "Options7.h" +#include "Options8.h" +#include "osd.h" +#include "playlistwindow.h" +#include "playerwindow.h" +#include "plugin/pluginconfig.h" +#include "pluginmanager.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include //kapp +#include +#include +#include +#include +#include +#include + +namespace Amarok { + int databaseTypeCode( const QString type ) + { + // can't use kconfigxt for the database comboxbox since we need the DBConnection id and not the index + int dbType = DbConnection::sqlite; + if ( type == "MySQL") + dbType = DbConnection::mysql; + else if ( type == "Postgresql" ) + dbType = DbConnection::postgresql; + return dbType; + } +} + + +int AmarokConfigDialog::s_currentPage = 0; + +////////////////////////////////////////////////////////////////////////////////////////// +// PUBLIC +////////////////////////////////////////////////////////////////////////////////////////// + +AmarokConfigDialog::AmarokConfigDialog( QWidget *parent, const char* name, KConfigSkeleton *config ) + : KConfigDialog( parent, name, config ) + , m_engineConfig( 0 ) + , m_opt4( 0 ) +{ + setWFlags( WDestructiveClose ); + + // IMPORTANT Don't simply change the page names, they are used as identifiers in other parts of the app. + m_opt1 = new Options1( 0, "General" ); +#ifdef Q_WS_MAC + m_opt1->kcfg_ShowSplashscreen->setEnabled(false); + m_opt1->kcfg_ShowTrayIcon->setEnabled(false); + m_opt1->kcfg_AnimateTrayIcon->setEnabled(false); + m_opt1->kcfg_ShowPlayerWindow->setEnabled(false); +#endif + m_opt2 = new Options2( 0, "Appearance" ); + m_opt4 = new Options4( 0, "Playback" ); +#ifdef Q_WS_X11 + Options5 *opt5 = new Options5( 0, "OSD" ); +#endif + QVBox *opt6 = new QVBox; + m_opt7 = new Options7( 0, "Collection" ); + Options8 *opt8 = new Options8( 0, "Scrobbler" ); + QVBox *opt9 = new QVBox; + + // Sound System + opt6->setName( "Engine" ); + opt6->setSpacing( KDialog::spacingHint() ); + QWidget *groupBox, *aboutEngineButton; + groupBox = new QGroupBox( 2, Qt::Horizontal, i18n("Sound System"), opt6 ); + m_engineConfigFrame = new QGroupBox( 1, Qt::Horizontal, opt6 ); + m_soundSystem = new QComboBox( false, groupBox ); + aboutEngineButton = new QPushButton( i18n("About"), groupBox ); + + QToolTip::add( m_soundSystem, i18n("Click to select the sound system to use for playback.") ); + QToolTip::add( aboutEngineButton, i18n("Click to get the plugin information.") ); + + /// Populate the engine selection combo box + KTrader::OfferList offers = PluginManager::query( "[X-KDE-Amarok-plugintype] == 'engine'" ); + KTrader::OfferList::ConstIterator end( offers.end() ); + for( KTrader::OfferList::ConstIterator it = offers.begin(); it != end; ++it ) { + // Don't list the (void engine) entry if it's not currently active, + // cause there's no point in choosing this engine (it's a dummy, after all). + if( (*it)->property( "X-KDE-Amarok-name" ).toString() == "void-engine" + && AmarokConfig::soundSystem() != "void-engine" ) continue; + + m_soundSystem->insertItem( (*it)->name() ); + // Save name properties in QMap for lookup + m_pluginName[(*it)->name()] = (*it)->property( "X-KDE-Amarok-name" ).toString(); + m_pluginAmarokName[(*it)->property( "X-KDE-Amarok-name" ).toString()] = (*it)->name(); + } + + // Collection +#if !defined(USE_MYSQL) && !defined(USE_POSTGRESQL) + m_opt7->databaseBox->hide(); +#endif + +#ifndef USE_MYSQL + //FIXME we do this because this widget breaks the Apply button (always enabled). + //It breaks because it is set to type="password" in the .kcfg file. Setting to + //type="string" also fixes this bug, but means the password is stored in plain + //text. This is a temporary fix so that the majority of users get a fixed Apply + //button. + delete m_opt7->dbSetupFrame->kcfg_MySqlPassword2; +#endif + m_opt7->collectionFoldersBox->setColumns( 1 ); + new CollectionSetup( m_opt7->collectionFoldersBox ); //TODO this widget doesn't update the apply/ok buttons + + // Media Devices + opt9->setName( "Media Devices" ); + opt9->setSpacing( KDialog::spacingHint() ); + QVBox *topbox = new QVBox( opt9 ); + topbox->setSpacing( KDialog::spacingHint() ); + QGroupBox *mediaBox = new QGroupBox( 2, Qt::Horizontal, i18n("Media Devices"), topbox ); + mediaBox->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Minimum ); + QVBox *vbox = new QVBox( mediaBox ); + vbox->setSpacing( KDialog::spacingHint() ); + m_deviceManager = new MediumPluginManager( vbox ); + + QHBox *hbox = new QHBox( topbox ); + hbox->setSpacing( KDialog::spacingHint() ); + hbox->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Minimum ); + KPushButton *autodetect = new KPushButton( i18n( "Autodetect Devices" ), hbox ); + autodetect->setSizePolicy( QSizePolicy( QSizePolicy::Expanding, QSizePolicy::Fixed ) ); + connect( autodetect, SIGNAL(clicked()), m_deviceManager, SLOT(redetectDevices()) ); + KPushButton *add = new KPushButton( i18n( "Add Device..." ), hbox ); + add->setSizePolicy( QSizePolicy( QSizePolicy::Expanding, QSizePolicy::Fixed ) ); + connect( add, SIGNAL(clicked()), m_deviceManager, SLOT(newDevice()) ); + + QFrame *frame = new QFrame( topbox ); + frame->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Expanding ); + + // add pages + addPage( m_opt1, i18n( "General" ), Amarok::icon( "settings_general" ), i18n( "Configure General Options" ) ); + addPage( m_opt2, i18n( "Appearance" ), Amarok::icon( "settings_view" ), i18n( "Configure Amarok's Appearance" ) ); + addPage( m_opt4, i18n( "Playback" ), Amarok::icon( "settings_playback" ), i18n( "Configure Playback" ) ); +#ifdef Q_WS_X11 + addPage( opt5, i18n( "OSD" ), Amarok::icon( "settings_indicator" ), i18n( "Configure On-Screen-Display" ) ); +#endif + addPage( opt6, i18n( "Engine" ), Amarok::icon( "settings_engine" ), i18n( "Configure Engine" ) ); + addPage( m_opt7, i18n( "Collection" ), Amarok::icon( "collection" ), i18n( "Configure Collection" ) ); + addPage( opt8, i18n( "last.fm" ), Amarok::icon( "audioscrobbler" ), i18n( "Configure last.fm Support" ) ); + addPage( opt9, i18n( "Media Devices" ), Amarok::icon( "device" ), i18n( "Configure Portable Player Support" ) ); + + // Show information labels (must be done after insertions) + QObjectList *list = queryList( "QLabel", "infoPixmap" ); + QPixmap const info = KGlobal::iconLoader()->iconPath( "messagebox_info", -KIcon::SizeHuge ); + for( QObject *label = list->first(); label; label = list->next() ) + static_cast(label)->setPixmap( info ); + delete list; + + //stop KFont Requesters getting stupidly large + list = queryList( "QLabel", "m_sampleLabel" ); + for( QObject *label = list->first(); label; label = list->next() ) + static_cast(label)->setMaximumWidth( 250 ); + delete list; + + connect( m_deviceManager, SIGNAL(changed()), SLOT(updateButtons()) ); + connect( m_soundSystem, SIGNAL(activated( int )), SLOT(updateButtons()) ); + connect( aboutEngineButton, SIGNAL(clicked()), SLOT(aboutEngine()) ); +#ifdef Q_WS_X11 + connect( opt5, SIGNAL(settingsChanged()), SLOT(updateButtons()) ); //see options5.ui.h +#endif + connect( m_opt2->styleComboBox, SIGNAL( activated( int ) ), SLOT( updateButtons() ) ); + connect( m_opt7->dbSetupFrame->databaseEngine, SIGNAL( activated( int ) ), SLOT( updateButtons() ) ); + connect( m_opt1->kComboBox_browser, SIGNAL( activated( int ) ), SLOT( updateButtons() ) ); + connect( m_opt1->kLineEdit_customBrowser, SIGNAL( textChanged( const QString& ) ), SLOT( updateButtons() ) ); +} + +AmarokConfigDialog::~AmarokConfigDialog() +{ + DEBUG_FUNC_INFO + + s_currentPage = activePageIndex(); + + delete m_engineConfig; + delete m_deviceManager; +} + + +/** Reimplemented from KConfigDialog */ +void AmarokConfigDialog::addPage( QWidget *page, const QString &itemName, const QString &pixmapName, const QString &header, bool manage ) +{ + // Add the widget pointer to our list, for later reference + m_pageList << page; + + KConfigDialog::addPage( page, itemName, pixmapName, header, manage ); +} + + +/** Show page by object name */ +void AmarokConfigDialog::showPageByName( const QCString& page ) +{ + for( uint index = 0; index < m_pageList.count(); index++ ) { + if ( m_pageList[index]->name() == page ) { + KConfigDialog::showPage( index ); + return; + } + } +} + + +////////////////////////////////////////////////////////////////////////////////////////// +// PROTECTED SLOTS +////////////////////////////////////////////////////////////////////////////////////////// + +/** + * Update the buttons. + * REIMPLEMENTED + */ + +void AmarokConfigDialog::updateButtons() +{ + KConfigDialog::updateButtons(); +} + +/** + * Update the settings from the dialog. + * Example use: User clicks Ok or Apply button in a configure dialog. + * REIMPLEMENTED + */ +void AmarokConfigDialog::updateSettings() +{ +#ifdef Q_WS_X11 + OSDPreviewWidget *osd = static_cast( child( "osdpreview" ) ); + AmarokConfig::setOsdAlignment( osd->alignment() ); + AmarokConfig::setOsdYOffset( osd->y() ); + Amarok::OSD::instance()->applySettings(); +#endif + + static_cast(child("CollectionSetup"))->writeConfig(); + + if ( m_engineConfig ) m_engineConfig->save(); + + AmarokConfig::setExternalBrowser( externalBrowser() ); + + // When sound system has changed, update engine config page + if ( m_soundSystem->currentText() != m_pluginAmarokName[AmarokConfig::soundSystem()] ) { + AmarokConfig::setSoundSystem( m_pluginName[m_soundSystem->currentText()] ); + emit settingsChanged(); + soundSystemChanged(); + } + + if ( m_opt2->styleComboBox->currentText() != AmarokConfig::contextBrowserStyleSheet() ) { + //can't use kconfigxt for the style comboxbox's since we need the string, not the index + AmarokConfig::setContextBrowserStyleSheet( m_opt2->styleComboBox->currentText() ); + ContextBrowser::instance()->reloadStyleSheet(); + } + + int dbType = Amarok::databaseTypeCode( m_opt7->dbSetupFrame->databaseEngine->currentText() ); + if ( dbType != AmarokConfig::databaseEngine().toInt() ) { + AmarokConfig::setDatabaseEngine( QString::number( dbType ) ); + emit settingsChanged(); + } + + m_deviceManager->finished(); + + if( MediaBrowser::isAvailable() ) + { + PlaylistWindow::self()->addBrowser( "MediaBrowser", MediaBrowser::instance(), i18n( "Media Device" ), Amarok::icon( "device" ) ); + //to re-enable mediabrowser hiding, uncomment this: + //connect( MediaBrowser::instance(), SIGNAL( availabilityChanged( bool ) ), + // PlaylistWindow::self(), SLOT( mbAvailabilityChanged( bool ) ) ); + + } + + Amarok::setUseScores( m_opt1->kcfg_UseScores->isChecked() ); + Amarok::setUseRatings( m_opt1->kcfg_UseRatings->isChecked() ); + + // The following makes everything with a moodbar redraw itself. + Amarok::setMoodbarPrefs( m_opt1->kcfg_ShowMoodbar->isChecked(), + m_opt1->kcfg_MakeMoodier->isChecked(), + m_opt1->kcfg_AlterMood->currentItem(), + m_opt1->kcfg_MoodsWithMusic->isChecked() ); +} + + +/** + * Update the configuration-widgets in the dialog based on Amarok's current settings. + * Example use: Initialisation of dialog. + * Example use: User clicks Reset button in a configure dialog. + * REIMPLEMENTED + */ +void AmarokConfigDialog::updateWidgets() +{ + m_soundSystem->setCurrentText( m_pluginAmarokName[AmarokConfig::soundSystem()] ); + soundSystemChanged(); +} + + +/** + * Update the configuration-widgets in the dialog based on the default values for Amarok's settings. + * Example use: User clicks Defaults button in a configure dialog. + * REIMPLEMENTED + */ +void AmarokConfigDialog::updateWidgetsDefault() +{ + m_soundSystem->setCurrentItem( 0 ); +} + + +////////////////////////////////////////////////////////////////////////////////////////// +// PROTECTED +////////////////////////////////////////////////////////////////////////////////////////// + +/** + * @return true if any configuration items we are managing changed from Amarok's stored settings + * We manage the engine combo box and some of the OSD settings + * REIMPLEMENTED + */ +bool AmarokConfigDialog::hasChanged() +{ +#ifdef Q_WS_X11 + OSDPreviewWidget *osd = static_cast( child( "osdpreview" ) ); +#endif + + return m_soundSystem->currentText() != m_pluginAmarokName[AmarokConfig::soundSystem()] || +#ifdef Q_WS_X11 + osd->alignment() != AmarokConfig::osdAlignment() || + osd->alignment() != OSDWidget::Center && osd->y() != AmarokConfig::osdYOffset() || +#endif + m_opt2->styleComboBox->currentText() != AmarokConfig::contextBrowserStyleSheet() || + Amarok::databaseTypeCode( m_opt7->dbSetupFrame->databaseEngine->currentText() ) != AmarokConfig::databaseEngine().toInt() || + m_engineConfig && m_engineConfig->hasChanged() || + m_deviceManager && m_deviceManager->hasChanged() || + externalBrowser() != AmarokConfig::externalBrowser(); +} + + +/** REIMPLEMENTED */ +bool AmarokConfigDialog::isDefault() +{ + AMAROK_NOTIMPLEMENTED + + //TODO hard to implement - what are default settings for OSD etc? + + return false; +} + + +////////////////////////////////////////////////////////////////////////////////////////// +// PRIVATE SLOTS +////////////////////////////////////////////////////////////////////////////////////////// + +void AmarokConfigDialog::aboutEngine() //SLOT +{ + PluginManager::showAbout( QString( "Name == '%1'" ).arg( m_soundSystem->currentText() ) ); +} + + +////////////////////////////////////////////////////////////////////////////////////////// +// PRIVATE +////////////////////////////////////////////////////////////////////////////////////////// + +void AmarokConfigDialog::soundSystemChanged() +{ + ///A new sound system has been LOADED + ///If only the sound system widget has been changed don't call this! + + // remove old engine config widget + // will delete the view if implementation is done correctly + delete m_engineConfig; + + if( EngineController::hasEngineProperty( "HasConfigure" ) ) + { + m_engineConfig = EngineController::engine()->configure(); + m_engineConfig->view()->reparent( m_engineConfigFrame, QPoint() ); + m_engineConfig->view()->show(); + m_engineConfigFrame->setTitle( i18n( "to change settings", "Configure %1" ).arg( m_soundSystem->currentText() ) ); + m_engineConfigFrame->show(); + + connect( m_engineConfig, SIGNAL(viewChanged()), SLOT(updateButtons()) ); + } + else { + m_engineConfig = 0; + m_engineConfigFrame->hide(); + } + + const bool hasCrossfade = EngineController::hasEngineProperty( "HasCrossfade" ); + const bool crossfadeOn = m_opt4->kcfg_Crossfade->isOn(); + // Enable crossfading option when available + m_opt4->kcfg_Crossfade->setEnabled( hasCrossfade ); + m_opt4->kcfg_CrossfadeLength->setEnabled( hasCrossfade && crossfadeOn ); + m_opt4->crossfadeLengthLabel->setEnabled( hasCrossfade && crossfadeOn ); + m_opt4->kcfg_CrossfadeType->setEnabled( hasCrossfade && crossfadeOn ); + + if (!hasCrossfade) + { + m_opt4->radioButtonNormalPlayback->setChecked( true ); + } +} + +QString AmarokConfigDialog::externalBrowser() const +{ + return m_opt1->kComboBox_browser->isEnabled() ? +#ifdef Q_WS_MAC + m_opt1->kComboBox_browser->currentText() == i18n( "Default Browser" ) ? + "open" : +#else + m_opt1->kComboBox_browser->currentText() == i18n( "Default KDE Browser" ) ? + "kfmclient openURL" : +#endif + m_opt1->kComboBox_browser->currentText().lower() : + m_opt1->kLineEdit_customBrowser->text().lower(); +} + + +#include "configdialog.moc" diff --git a/amarok/src/configdialog.h b/amarok/src/configdialog.h new file mode 100644 index 00000000..2ffd50dc --- /dev/null +++ b/amarok/src/configdialog.h @@ -0,0 +1,81 @@ +/*************************************************************************** +begin : 2004/02/07 +copyright : (C) Mark Kretschmann +email : markey@web.de +***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef AMAROKCONFIGDIALOG_H +#define AMAROKCONFIGDIALOG_H + +#include +#include + +#include + +class QComboBox; +class QGroupBox; +class QVBox; + +namespace Amarok { + class PluginConfig; +} + +class MediumPluginManager; + +class AmarokConfigDialog : public KConfigDialog +{ + Q_OBJECT + + public: + AmarokConfigDialog( QWidget *parent, const char* name, KConfigSkeleton *config ); + ~AmarokConfigDialog(); + + void addPage( QWidget *page, const QString &itemName, const QString &pixmapName, + const QString &header=QString::null, bool manage=true); + + void showPageByName( const QCString& page ); + + static int s_currentPage; + + protected slots: + void updateButtons(); + void updateSettings(); + void updateWidgets(); + void updateWidgetsDefault(); + + private slots: + void aboutEngine(); + + protected: + bool hasChanged(); + bool isDefault(); + + private: + void soundSystemChanged(); + QString externalBrowser() const; + + QComboBox* m_soundSystem; + Amarok::PluginConfig *m_engineConfig; + QGroupBox *m_engineConfigFrame; + class Options1 *m_opt1; + class Options2 *m_opt2; + class Options4 *m_opt4; + class Options7 *m_opt7; + MediumPluginManager *m_deviceManager; + + QValueList m_pageList; + QMap m_pluginName; + QMap m_pluginAmarokName; +}; + + +#endif // AMAROKCONFIGDIALOG_H diff --git a/amarok/src/contextbrowser.cpp b/amarok/src/contextbrowser.cpp new file mode 100644 index 00000000..63cfa614 --- /dev/null +++ b/amarok/src/contextbrowser.cpp @@ -0,0 +1,4501 @@ +// (c) 2004 Christian Muehlhaeuser +// (c) 2005 Reigo Reinmets +// (c) 2005 Mark Kretschmann +// (c) 2006 Peter C. Ndikuwera +// (c) 2006 Alexandre Pereira de Oliveira +// (c) 2006 Maximilian Kossick +// License: GNU General Public License V2 + + +#define DEBUG_PREFIX "ContextBrowser" + +#include "amarok.h" +#include "amarokconfig.h" +#include "app.h" +#include "browserToolBar.h" +#include "debug.h" +#include "clicklineedit.h" +#include "collectiondb.h" +#include "collectionbrowser.h" +#include "colorgenerator.h" +#include "contextbrowser.h" +#include "coverfetcher.h" +#include "covermanager.h" +#include "cuefile.h" +#include "enginecontroller.h" +#include "htmlview.h" +#include "lastfm.h" +#include "mediabrowser.h" +#include "metabundle.h" +#include "mountpointmanager.h" +#include "playlist.h" //appendMedia() +#include "podcastbundle.h" +#include "qstringx.h" +#include "scriptmanager.h" +#include "starmanager.h" +#include "statusbar.h" +#include "tagdialog.h" +#include "threadmanager.h" + +#include +#include +#include +#include +#include +#include +#include // External CSS reading +#include //wiki tab +#include +#include +#include +#include +#include +#include + +#include +#include //kapp +#include // for Amarok::verboseTimeSince() +#include // suggested/related/favorite box visibility +#include +#include +#include +#include +#include +#include +#include // for data: URLs +#include +#include +#include +#include +#include + +#include //usleep() + +namespace Amarok +{ + QString escapeHTML( const QString &s ) + { + return QString(s).replace( "&", "&" ).replace( "<", "<" ).replace( ">", ">" ); + // .replace( "%", "%25" ) has to be the first(!) one, otherwise we would do things like converting spaces into %20 and then convert them into %25%20 + } + + QString escapeHTMLAttr( const QString &s ) + { + return QString(s).replace( "%", "%25" ).replace( "'", "%27" ).replace( "\"", "%22" ).replace( "#", "%23" ).replace( "?", "%3F" ); + } + + QString unescapeHTMLAttr( const QString &s ) + { + return QString(s).replace( "%3F", "?" ).replace( "%23", "#" ).replace( "%22", "\"" ).replace( "%27", "'" ).replace( "%25", "%" ); + } + + QString verboseTimeSince( const QDateTime &datetime ) + { + const QDateTime now = QDateTime::currentDateTime(); + const int datediff = datetime.daysTo( now ); + + if( datediff >= 6*7 /*six weeks*/ ) { // return absolute month/year + const KCalendarSystem *cal = KGlobal::locale()->calendar(); + const QDate date = datetime.date(); + return i18n( "monthname year", "%1 %2" ).arg( cal->monthName(date), cal->yearString(date, false) ); + } + + //TODO "last week" = maybe within 7 days, but prolly before last sunday + + if( datediff >= 7 ) // return difference in weeks + return i18n( "One week ago", "%n weeks ago", (datediff+3)/7 ); + + if( datediff == -1 ) + return i18n( "Tomorrow" ); + + const int timediff = datetime.secsTo( now ); + + if( timediff >= 24*60*60 /*24 hours*/ ) // return difference in days + return datediff == 1 ? + i18n( "Yesterday" ) : + i18n( "One day ago", "%n days ago", (timediff+12*60*60)/(24*60*60) ); + + if( timediff >= 90*60 /*90 minutes*/ ) // return difference in hours + return i18n( "One hour ago", "%n hours ago", (timediff+30*60)/(60*60) ); + + //TODO are we too specific here? Be more fuzzy? ie, use units of 5 minutes, or "Recently" + + if( timediff >= 0 ) // return difference in minutes + return timediff/60 ? + i18n( "One minute ago", "%n minutes ago", (timediff+30)/60 ) : + i18n( "Within the last minute" ); + + return i18n( "The future" ); + } + + QString verboseTimeSince( uint time_t ) + { + if( !time_t ) + return i18n( "Never" ); + + QDateTime dt; + dt.setTime_t( time_t ); + return verboseTimeSince( dt ); + } + + extern KConfig *config( const QString& ); + + /** + * Function that must be used when separating contextBrowser escaped urls + * detail can contain track/discnumber + */ + void albumArtistTrackFromUrl( QString url, QString &artist, QString &album, QString &detail ) + { + if ( !url.contains("@@@") ) return; + //KHTML removes the trailing space! + if ( url.endsWith( " @@@" ) ) + url += ' '; + + const QStringList list = QStringList::split( " @@@ ", url, true ); + + int size = list.count(); + + Q_ASSERT( size>0 ); + + artist = size > 0 ? unescapeHTMLAttr( list[0] ) : ""; + album = size > 1 ? unescapeHTMLAttr( list[1] ) : ""; + detail = size > 2 ? unescapeHTMLAttr( list[2] ) : ""; + } + +} + + +using Amarok::QStringx; +using Amarok::escapeHTML; +using Amarok::escapeHTMLAttr; +using Amarok::unescapeHTMLAttr; + + +static +QString albumImageTooltip( const QString &albumImage, int size ) +{ + if ( albumImage == CollectionDB::instance()->notAvailCover( false, size ) ) + return escapeHTMLAttr( i18n( "Click to fetch cover from amazon.%1, right-click for menu." ).arg( CoverManager::amazonTld() ) ); + + return escapeHTMLAttr( i18n( "Click for information from Amazon, right-click for menu." ) ); +} + + +ContextBrowser *ContextBrowser::s_instance = 0; +QString ContextBrowser::s_wikiLocale = "en"; + + +ContextBrowser::ContextBrowser( const char *name ) + : KTabWidget( 0, name ) + , EngineObserver( EngineController::instance() ) + , m_dirtyCurrentTrackPage( true ) + , m_dirtyLyricsPage( true ) + , m_dirtyWikiPage( true ) + , m_emptyDB( CollectionDB::instance()->isEmpty() ) + , m_wikiBackPopup( new KPopupMenu( this ) ) + , m_wikiForwardPopup( new KPopupMenu( this ) ) + , m_wikiJob( NULL ) + , m_wikiConfigDialog( NULL ) + , m_relatedOpen( true ) + , m_suggestionsOpen( true ) + , m_favoritesOpen( true ) + , m_labelsOpen( true ) + , m_showFreshPodcasts( true ) + , m_showFavoriteAlbums( true ) + , m_showNewestAlbums( true ) + , m_browseArtists( false ) + , m_browseLabels( false ) + , m_cuefile( NULL ) +{ + s_instance = this; + s_wikiLocale = AmarokConfig::wikipediaLocale(); + + m_contextTab = new QVBox(this, "context_tab"); + + m_currentTrackPage = new HTMLView( m_contextTab, "current_track_page", true /* DNDEnabled */, + true /*JScriptEnabled*/ ); + + m_lyricsTab = new QVBox(this, "lyrics_tab"); + + m_lyricsToolBar = new Browser::ToolBar( m_lyricsTab ); + m_lyricsToolBar->setIconText( KToolBar::IconTextRight, false ); + m_lyricsToolBar->insertButton( Amarok::icon( "refresh" ), LYRICS_REFRESH, true, i18n("Refresh") ); + m_lyricsToolBar->insertButton( Amarok::icon( "add_lyrics" ), LYRICS_ADD, true, i18n("Add") ); + m_lyricsToolBar->insertButton( Amarok::icon( "edit" ), LYRICS_EDIT, true, i18n("Edit") ); + m_lyricsToolBar->setToggle( LYRICS_EDIT, true ); + m_lyricsToolBar->insertButton( Amarok::icon( "search" ), LYRICS_SEARCH, true, i18n("Search") ); + m_lyricsToolBar->setIconText( KToolBar::IconOnly, false ); + m_lyricsToolBar->insertButton( Amarok::icon( "external" ), LYRICS_BROWSER, true, i18n("Open in external browser") ); + + { //Search text inside lyrics. Code inspired/copied from playlistwindow.cpp + m_lyricsTextBar = new KToolBar( m_lyricsTab, "NotMainToolBar" ); + m_lyricsTextBar->hide(); + m_lyricsTextBarShowed=false; + + m_lyricsTextBar->setIconSize( 22, false ); //looks more sensible + m_lyricsTextBar->setFlat( true ); //removes the ugly frame + m_lyricsTextBar->setMovingEnabled( false ); //removes the ugly frame + + m_lyricsTextBar->boxLayout()->addStretch(); + + QWidget *button = new KToolBarButton( "locationbar_erase", 1, m_lyricsTextBar ); + QLabel *filter_label = new QLabel( i18n("S&earch:") + ' ', m_lyricsTextBar ); + m_lyricsSearchText = new ClickLineEdit( i18n( "Search in lyrics" ), m_lyricsTextBar ); + filter_label->setBuddy( m_lyricsSearchText ); + + m_lyricsTextBar->setStretchableWidget(m_lyricsSearchText ); + + m_lyricsSearchText->setFrame( QFrame::Sunken ); + m_lyricsSearchText->installEventFilter( this ); //we intercept keyEvents + + connect( button, SIGNAL(clicked()), m_lyricsSearchText, SLOT(clear()) ); + + QToolTip::add( button, i18n( "Clear search" ) ); + QString filtertip = i18n( "Enter text to search for. Press enter to advance to the next match." ); + + QToolTip::add( m_lyricsSearchText, filtertip ); + + connect ( button, SIGNAL(clicked()), m_lyricsSearchText, SLOT(clear()) ); + connect ( m_lyricsSearchText, SIGNAL(textChanged(const QString &)), this, SLOT(lyricsSearchText(const QString & )) ); + connect ( m_lyricsSearchText, SIGNAL(returnPressed()), this, (SLOT(lyricsSearchTextNext())) ); + Amarok::actionCollection()->setAutoConnectShortcuts ( true ); + new KAction( i18n("Search text in lyrics"), KShortcut("/"), this,SLOT( lyricsSearchTextShow() ), Amarok::actionCollection(), "search_text_lyric"); + Amarok::actionCollection()->setAutoConnectShortcuts ( false ); + } + + + + m_lyricsPage = new HTMLView( m_lyricsTab, "lyrics_page", true /* DNDEnabled */, false /* JScriptEnabled*/ ); + m_lyricsTextEdit = new KTextEdit ( m_lyricsTab, "lyrics_text_edit"); + m_lyricsTextEdit->setTextFormat( Qt::PlainText ); + m_lyricsTextEdit->hide(); + + m_wikiTab = new QVBox(this, "wiki_tab"); + + m_wikiToolBar = new Browser::ToolBar( m_wikiTab ); + m_wikiToolBar->insertButton( "back", WIKI_BACK, false, i18n("Back") ); + m_wikiToolBar->insertButton( "forward", WIKI_FORWARD, false, i18n("Forward") ); + m_wikiToolBar->insertLineSeparator(); + m_wikiToolBar->insertButton( Amarok::icon( "artist" ), WIKI_ARTIST, false, i18n("Artist Page") ); + m_wikiToolBar->insertButton( Amarok::icon( "album" ), WIKI_ALBUM, false, i18n("Album Page") ); + m_wikiToolBar->insertButton( Amarok::icon( "track" ), WIKI_TITLE, false, i18n("Title Page") ); + m_wikiToolBar->insertLineSeparator(); + m_wikiToolBar->insertButton( Amarok::icon( "external" ), WIKI_BROWSER, true, i18n("Open in external browser") ); + m_wikiToolBar->insertButton( Amarok::icon( "change_language" ), WIKI_CONFIG, true, i18n("Change Locale") ); + + m_wikiToolBar->setDelayedPopup( WIKI_BACK, m_wikiBackPopup ); + m_wikiToolBar->setDelayedPopup( WIKI_FORWARD, m_wikiForwardPopup ); + + m_wikiPage = new HTMLView( m_wikiTab, "wiki_page", true /* DNDEnabled */, false /* JScriptEnabled */ ); + + m_cuefile = CueFile::instance(); + connect( m_cuefile, SIGNAL(metaData( const MetaBundle& )), + EngineController::instance(), SLOT(currentTrackMetaDataChanged( const MetaBundle& )) ); + connect( m_cuefile, SIGNAL(newCuePoint( long, long, long )), + Scrobbler::instance(), SLOT(subTrack( long, long, long )) ); + + addTab( m_contextTab, SmallIconSet( Amarok::icon( "music" ) ), i18n( "Music" ) ); + addTab( m_lyricsTab, SmallIconSet( Amarok::icon( "lyrics" ) ), i18n( "Lyrics" ) ); + addTab( m_wikiTab, SmallIconSet( Amarok::icon( "artist" ) ), i18n( "Artist" ) ); + + setTabEnabled( m_lyricsTab, false ); + setTabEnabled( m_wikiTab, false ); + + m_showRelated = Amarok::config( "ContextBrowser" )->readBoolEntry( "ShowRelated", true ); + m_showSuggested = Amarok::config( "ContextBrowser" )->readBoolEntry( "ShowSuggested", true ); + m_showFaves = Amarok::config( "ContextBrowser" )->readBoolEntry( "ShowFaves", true ); + m_showLabels = Amarok::config( "ContextBrowser" )->readBoolEntry( "ShowLabels", true ); + + m_showFreshPodcasts = Amarok::config( "ContextBrowser" )->readBoolEntry( "ShowFreshPodcasts", true ); + m_showNewestAlbums = Amarok::config( "ContextBrowser" )->readBoolEntry( "ShowNewestAlbums", true ); + m_showFavoriteAlbums = Amarok::config( "ContextBrowser" )->readBoolEntry( "ShowFavoriteAlbums", true ); + + // Delete folder with the cached coverimage shadow pixmaps + KIO::del( KURL::fromPathOrURL( Amarok::saveLocation( "covershadow-cache/" ) ), false, false ); + + connect( this, SIGNAL( currentChanged( QWidget* ) ), SLOT( tabChanged( QWidget* ) ) ); + + connect( m_currentTrackPage->browserExtension(), SIGNAL( openURLRequest( const KURL &, const KParts::URLArgs & ) ), + this, SLOT( openURLRequest( const KURL & ) ) ); + connect( m_lyricsPage->browserExtension(), SIGNAL( openURLRequest( const KURL &, const KParts::URLArgs & ) ), + this, SLOT( openURLRequest( const KURL & ) ) ); + connect( m_wikiPage->browserExtension(), SIGNAL( openURLRequest( const KURL &, const KParts::URLArgs & ) ), + this, SLOT( openURLRequest( const KURL & ) ) ); + + connect( m_currentTrackPage, SIGNAL( popupMenu( const QString&, const QPoint& ) ), + this, SLOT( slotContextMenu( const QString&, const QPoint& ) ) ); + connect( m_lyricsPage, SIGNAL( popupMenu( const QString&, const QPoint& ) ), + this, SLOT( slotContextMenu( const QString&, const QPoint& ) ) ); + connect( m_wikiPage, SIGNAL( popupMenu( const QString&, const QPoint& ) ), + this, SLOT( slotContextMenu( const QString&, const QPoint& ) ) ); + + connect( m_lyricsToolBar->getButton( LYRICS_ADD ), SIGNAL(clicked( int )), SLOT(lyricsAdd()) ); + connect( m_lyricsToolBar->getButton( LYRICS_EDIT ), SIGNAL(toggled( int )), SLOT(lyricsEditToggle()) ); + connect( m_lyricsToolBar->getButton( LYRICS_SEARCH ), SIGNAL(clicked( int )), SLOT(lyricsSearch()) ); + connect( m_lyricsToolBar->getButton( LYRICS_REFRESH ), SIGNAL(clicked( int )), SLOT(lyricsRefresh()) ); + connect( m_lyricsToolBar->getButton( LYRICS_BROWSER ), SIGNAL(clicked( int )), SLOT(lyricsExternalPage()) ); + + connect( m_wikiToolBar->getButton( WIKI_BACK ), SIGNAL(clicked( int )), SLOT(wikiHistoryBack()) ); + connect( m_wikiToolBar->getButton( WIKI_FORWARD ), SIGNAL(clicked( int )), SLOT(wikiHistoryForward()) ); + connect( m_wikiToolBar->getButton( WIKI_ARTIST ), SIGNAL(clicked( int )), SLOT(wikiArtistPage()) ); + connect( m_wikiToolBar->getButton( WIKI_ALBUM ), SIGNAL(clicked( int )), SLOT(wikiAlbumPage()) ); + connect( m_wikiToolBar->getButton( WIKI_TITLE ), SIGNAL(clicked( int )), SLOT(wikiTitlePage()) ); + connect( m_wikiToolBar->getButton( WIKI_BROWSER ), SIGNAL(clicked( int )), SLOT(wikiExternalPage()) ); + connect( m_wikiToolBar->getButton( WIKI_CONFIG ), SIGNAL(clicked( int )), SLOT(wikiConfig()) ); + + connect( m_wikiBackPopup, SIGNAL(activated( int )), SLOT(wikiBackPopupActivated( int )) ); + connect( m_wikiForwardPopup, SIGNAL(activated( int )), SLOT(wikiForwardPopupActivated( int )) ); + + connect( CollectionDB::instance(), SIGNAL( scanStarted() ), SLOT( collectionScanStarted() ) ); + connect( CollectionDB::instance(), SIGNAL( scanDone( bool ) ), SLOT( collectionScanDone( bool ) ) ); + connect( CollectionDB::instance(), SIGNAL( databaseEngineChanged() ), SLOT( renderView() ) ); + connect( CollectionDB::instance(), SIGNAL( coverFetched( const QString&, const QString& ) ), + this, SLOT( coverFetched( const QString&, const QString& ) ) ); + connect( CollectionDB::instance(), SIGNAL( coverChanged( const QString&, const QString& ) ), + this, SLOT( coverRemoved( const QString&, const QString& ) ) ); + connect( CollectionDB::instance(), SIGNAL( similarArtistsFetched( const QString& ) ), + this, SLOT( similarArtistsFetched( const QString& ) ) ); + connect( CollectionDB::instance(), SIGNAL( tagsChanged( const MetaBundle& ) ), + this, SLOT( tagsChanged( const MetaBundle& ) ) ); + connect( CollectionDB::instance(), SIGNAL( tagsChanged( const QString&, const QString& ) ), + this, SLOT( tagsChanged( const QString&, const QString& ) ) ); + connect( CollectionDB::instance(), SIGNAL( ratingChanged( const QString&, int ) ), + this, SLOT( ratingOrScoreOrLabelsChanged( const QString& ) ) ); + connect( StarManager::instance(), SIGNAL( ratingsColorsChanged() ), + this, SLOT( ratingOrScoreOrLabelsChanged( const QString& ) ) ); + connect( CollectionDB::instance(), SIGNAL( scoreChanged( const QString&, float ) ), + this, SLOT( ratingOrScoreOrLabelsChanged( const QString& ) ) ); + connect( CollectionDB::instance(), SIGNAL( labelsChanged( const QString& ) ), + this, SLOT( ratingOrScoreOrLabelsChanged( const QString& ) ) ); + connect( CollectionDB::instance(), SIGNAL( imageFetched( const QString& ) ), + this, SLOT( imageFetched( const QString& ) ) ); + + connect( App::instance(), SIGNAL( useScores( bool ) ), + this, SLOT( refreshCurrentTrackPage() ) ); + connect( App::instance(), SIGNAL( useRatings( bool ) ), + this, SLOT( refreshCurrentTrackPage() ) ); + + connect( MountPointManager::instance(), SIGNAL( mediumConnected( int ) ), + this, SLOT( renderView() ) ); + connect( MountPointManager::instance(), SIGNAL( mediumRemoved( int ) ), + this, SLOT( renderView() ) ); + + showContext( KURL( "current://track" ) ); + +// setMinimumHeight( AmarokConfig::coverPreviewSize() + (fontMetrics().height()+2)*5 + tabBar()->height() ); +} + + +ContextBrowser::~ContextBrowser() +{ + DEBUG_BLOCK + + ThreadManager::instance()->abortAllJobsNamed( "CurrentTrackJob" ); + + // Ensure the KHTMLPart dies before its KHTMLView dies, + // because KHTMLPart's dtoring relies on its KHTMLView still being alive + // (see bug 130494). + delete m_currentTrackPage; + delete m_lyricsPage; + delete m_wikiPage; + m_cuefile->clear(); +} + + +////////////////////////////////////////////////////////////////////////////////////////// +// PUBLIC METHODS +////////////////////////////////////////////////////////////////////////////////////////// + +void ContextBrowser::setFont( const QFont &newFont ) +{ + QWidget::setFont( newFont ); + reloadStyleSheet(); +} + + +////////////////////////////////////////////////////////////////////////////////////////// +// PUBLIC SLOTS +////////////////////////////////////////////////////////////////////////////////////////// + +void ContextBrowser::openURLRequest( const KURL &url ) +{ + QString artist, album, track; + Amarok::albumArtistTrackFromUrl( url.path(), artist, album, track ); + + // All http links should be loaded inside wikipedia tab, as that is the only tab that should contain them. + // Streams should use stream:// protocol. + if ( url.protocol() == "http" ) + { + if ( url.hasHTMLRef() ) + { + KURL base = url; + base.setRef(QString::null); + // Wikipedia also has links to otherpages with Anchors, so we have to check if it's for the current one + if ( m_wikiCurrentUrl == base.url() ) { + m_wikiPage->gotoAnchor( url.htmlRef() ); + return; + } + } + // new page + m_dirtyWikiPage = true; + m_wikiCurrentEntry = QString::null; + showWikipedia( url.url() ); + } + else if ( url.protocol() == "show" ) + { + if ( url.path().contains( "suggestLyric-" ) ) + { + QString _url = url.url().mid( url.url().find( QString( "-" ) ) +1 ); + debug() << "Clicked lyrics URL: " << _url << endl; + m_dirtyLyricsPage = true; + showLyrics( _url ); + } + else if ( url.path() == "collectionSetup" ) + { + CollectionView::instance()->setupDirs(); + } + else if ( url.path() == "scriptmanager" ) + { + ScriptManager::instance()->show(); + ScriptManager::instance()->raise(); + } + else if ( url.path() == "editLabels" ) + { + showLabelsDialog(); + } + // Konqueror sidebar needs these + if (url.path() == "context") { m_dirtyCurrentTrackPage=true; showContext( KURL( "current://track" ) ); saveHtmlData(); } + if (url.path() == "wiki") { m_dirtyWikiPage=true; showWikipedia(); saveHtmlData(); } + if (url.path() == "lyrics") { m_dirtyLyricsPage=true; m_wikiJob=false; showLyrics(); saveHtmlData(); } + } + + else if ( url.protocol() == "runscript" ) + { + ScriptManager::instance()->runScript( url.path() ); + } + + // When left-clicking on cover image, open browser with amazon site + else if ( url.protocol() == "fetchcover" ) + { + QString albumPath = CollectionDB::instance()->albumImage(artist, album, false, 0 ); + if ( albumPath == CollectionDB::instance()->notAvailCover( false, 0 ) ) + { + CollectionDB::instance()->fetchCover( this, artist, album, false ); + return; + } + + QImage img( albumPath ); + const QString amazonUrl = img.text( "amazon-url" ); + + if ( amazonUrl.isEmpty() ) + KMessageBox::information( this, i18n( "

There is no product information available for this image.

Right-click on image for menu." ) ); + else + Amarok::invokeBrowser( amazonUrl ); + } + + /* open konqueror with musicbrainz search result for artist-album */ + else if ( url.protocol() == "musicbrainz" ) + { + const QString url = "http://www.musicbrainz.org/taglookup.html?artist=%1&album=%2&track=%3"; + Amarok::invokeBrowser( url.arg( KURL::encode_string_no_slash( artist, 106 /*utf-8*/ ), + KURL::encode_string_no_slash( album, 106 /*utf-8*/ ), + KURL::encode_string_no_slash( track, 106 /*utf-8*/ ) ) ); + } + + else if ( url.protocol() == "externalurl" ) + Amarok::invokeBrowser( url.url().replace( QRegExp( "^externalurl:" ), "http:") ); + + else if ( url.protocol() == "lastfm" ) + { + LastFm::WebService *lfm = LastFm::Controller::instance()->getService(); + if ( url.path() == "skip" ) lfm->skip(); + else if ( url.path() == "love" ) lfm->love(); + else if ( url.path() == "ban" ) lfm->ban(); + } + else if ( url.protocol() == "togglebox" ) + { + if ( url.path() == "ra" ) m_relatedOpen ^= true; + else if ( url.path() == "ss" ) m_suggestionsOpen ^= true; + else if ( url.path() == "ft" ) m_favoritesOpen ^= true; + else if ( url.path() == "sl" ) m_labelsOpen ^= true; + } + + else if ( url.protocol() == "seek" ) + { + EngineController::instance()->seek(url.path().toLong()); + } + + // browse albums of a related artist. Don't do this if we are viewing Home tab + else if ( url.protocol() == "artist" || url.protocol() == "current" || url.protocol() == "showlabel") + { + if( EngineController::engine()->loaded() ) // song must be active + showContext( url ); + } + + else if( url.protocol() == "artistback" ) + { + contextHistoryBack(); + } + + else if ( url.protocol() == "wikipedia" ) + { + m_dirtyWikiPage = true; + QString entry = unescapeHTMLAttr( url.path() ); + showWikipediaEntry( entry ); + } + + else if( url.protocol() == "ggartist" ) + { + const QString url2 = QString( "http://www.google.com/musicsearch?q=%1&res=artist" ) + .arg( KURL::encode_string_no_slash( unescapeHTMLAttr( url.path() ).replace( " ", "+" ), 106 /*utf-8*/ ) ); + Amarok::invokeBrowser( url2 ); + } + + else if( url.protocol() == "file" ) + { + Playlist::instance()->insertMedia( url, Playlist::DefaultOptions ); + } + + else if( url.protocol() == "stream" ) + { + Playlist::instance()->insertMedia( KURL::fromPathOrURL( url.url().replace( QRegExp( "^stream:" ), "http:" ) ), Playlist::DefaultOptions ); + } + + else if( url.protocol() == "compilationdisc" || url.protocol() == "albumdisc" ) + { + Playlist::instance()->insertMedia( expandURL( url ) , Playlist::DefaultOptions ); + } + + else + HTMLView::openURLRequest( url ); +} + + +void ContextBrowser::collectionScanStarted() +{ + m_emptyDB = CollectionDB::instance()->isEmpty(); + if( m_emptyDB && currentPage() == m_contextTab ) + showCurrentTrack(); +} + + +void ContextBrowser::collectionScanDone( bool changed ) +{ + if ( CollectionDB::instance()->isEmpty() ) + { + m_emptyDB = true; + if ( currentPage() == m_contextTab ) + showCurrentTrack(); + } + else if ( m_emptyDB ) + { + m_emptyDB = false; + PlaylistWindow::self()->showBrowser("CollectionBrowser"); + } + else if( changed && currentPage() == m_contextTab ) + { + m_dirtyCurrentTrackPage = true; + showCurrentTrack(); + } +} + + +void ContextBrowser::renderView() +{ + m_dirtyCurrentTrackPage = true; + m_dirtyLyricsPage = true; + m_dirtyWikiPage = true; + m_emptyDB = CollectionDB::instance()->isEmpty(); + + showCurrentTrack(); +} + + +void ContextBrowser::lyricsChanged( const QString &url ) { + if ( url == EngineController::instance()->bundle().url().path() ) { + m_dirtyLyricsPage = true; + if ( currentPage() == m_lyricsTab ) + showLyrics(); + } +} + +void ContextBrowser::lyricsScriptChanged() { + m_dirtyLyricsPage = true; + if ( currentPage() == m_lyricsTab ) + showLyrics(); +} + +////////////////////////////////////////////////////////////////////////////////////////// +// PROTECTED +////////////////////////////////////////////////////////////////////////////////////////// + +void ContextBrowser::engineNewMetaData( const MetaBundle& bundle, bool trackChanged ) +{ + bool newMetaData = false; + m_dirtyCurrentTrackPage = true; + m_dirtyLyricsPage = true; + m_wikiJob = 0; //New metadata, so let's forget previous wiki-fetching jobs + + if ( MetaBundle( m_currentURL ).artist() != bundle.artist() ) + m_dirtyWikiPage = true; + // Prepend stream metadata history item to list + if ( !m_metadataHistory.first().contains( bundle.prettyTitle() ) ) + { + newMetaData = true; + const QString timeString = KGlobal::locale()->formatTime( QTime::currentTime() ).replace(" ", " "); // don't break over lines + m_metadataHistory.prepend( QString( "" + timeString + " " + escapeHTML( bundle.prettyTitle() ) + "" ) ); + } + + if ( currentPage() == m_contextTab && ( bundle.url() != m_currentURL || newMetaData || !trackChanged ) ) + showCurrentTrack(); + else if ( currentPage() == m_lyricsTab ) + { + EngineController::engine()->isStream() ? + lyricsRefresh() : // can't call showLyrics() because the url hasn't changed + showLyrics() ; + } + else if ( CollectionDB::instance()->isEmpty() || !CollectionDB::instance()->isValid() ) + showCurrentTrack(); + + if (trackChanged) + { + m_cuefile->clear(); + + if (bundle.url().isLocalFile()) + { + + /** The cue file that is provided with the media might have different name than the + * media file itself, hence simply cutting the media extension and adding ".cue" + * is not always enough to find the matching cue file. In such cases we have + * to search for all the cue files in the directory and have a look inside them for + * the matching FILE="" stanza. However the FILE="" stanza does not always + * point at the corresponding media file (e.g. it is quite often set to the misleading + * FILE="audio.wav" WAV). Therfore we also have to check blindly if there is a cue + * file having the same name as the media file played, as described above. + */ + + // look for the cue file that matches the media file played first + QString path = bundle.url().path(); + QString cueFile = path.left( path.findRev('.') ) + ".cue"; + + m_cuefile->setCueFileName( cueFile ); + + if( m_cuefile->load( bundle.length() ) ) + debug() << "[CUEFILE]: " << cueFile << " - Shoot blindly, found and loaded. " << endl; + + // if unlucky, let's have a look inside cue files, if any + else + { + debug() << "[CUEFILE]: " << cueFile << " - Shoot blindly and missed, searching for other cue files." << endl; + + bool foundCueFile = false; + QDir dir ( bundle.directory() ); + dir.setFilter( QDir::Files ) ; + dir.setNameFilter( "*.cue *.CUE" ) ; + + QStringList cueFilesList = dir.entryList(); + + if ( !cueFilesList.empty() ) + for ( QStringList::Iterator it = cueFilesList.begin(); it != cueFilesList.end() && !foundCueFile; ++it ) + { + QFile file ( dir.filePath(*it) ); + if( file.open( IO_ReadOnly ) ) + { + debug() << "[CUEFILE]: " << *it << " - Opened, looking for the matching FILE stanza." << endl; + QTextStream stream( &file ); + QString line; + + while ( !stream.atEnd() && !foundCueFile) + { + line = stream.readLine().simplifyWhiteSpace(); + + if( line.startsWith( "file", false ) ) + { + line = line.mid( 5 ).remove( '"' ); + + if ( line.contains( bundle.filename(), false ) ) + { + cueFile = dir.filePath(*it); + foundCueFile = true; + m_cuefile->setCueFileName( cueFile ); + if( m_cuefile->load( bundle.length() ) ) + debug() << "[CUEFILE]: " << cueFile << " - Looked inside cue files, found and loaded proper one" << endl; + } + } + } + + file.close(); + } + } + + if ( !foundCueFile ) + debug() << "[CUEFILE]: - Didn't find any matching cue file." << endl; + } + } + } +} + + +void ContextBrowser::engineStateChanged( Engine::State state, Engine::State oldState ) +{ + DEBUG_BLOCK + + if( state != Engine::Paused /*pause*/ && oldState != Engine::Paused /*resume*/ + || state == Engine::Empty ) + { + // Pause shouldn't clear everything (but stop should, even when paused) + m_dirtyCurrentTrackPage = true; + m_dirtyLyricsPage = true; + m_wikiJob = 0; //let's forget previous wiki-fetching jobs + } + + switch( state ) + { + case Engine::Empty: + m_metadataHistory.clear(); + if ( currentPage() == m_contextTab || currentPage() == m_lyricsTab ) + { + showCurrentTrack(); + } + blockSignals( true ); + setTabEnabled( m_lyricsTab, false ); + if ( currentPage() != m_wikiTab ) { + setTabEnabled( m_wikiTab, false ); + m_dirtyWikiPage = true; + } + else // current tab is wikitab, disable some buttons. + { + m_wikiToolBar->setItemEnabled( WIKI_ARTIST, false ); + m_wikiToolBar->setItemEnabled( WIKI_ALBUM, false ); + m_wikiToolBar->setItemEnabled( WIKI_TITLE, false ); + } + blockSignals( false ); + break; + + case Engine::Playing: + if ( oldState != Engine::Paused ) + m_metadataHistory.clear(); + blockSignals( true ); + setTabEnabled( m_lyricsTab, true ); + setTabEnabled( m_wikiTab, true ); + m_wikiToolBar->setItemEnabled( WIKI_ARTIST, true ); + m_wikiToolBar->setItemEnabled( WIKI_ALBUM, true ); + m_wikiToolBar->setItemEnabled( WIKI_TITLE, true ); + blockSignals( false ); + break; + + default: + ; + } +} + +void ContextBrowser::saveHtmlData() +{ + QFile exportedDocument( Amarok::saveLocation() + "contextbrowser.html" ); + if ( !exportedDocument.open( IO_WriteOnly ) ) + warning() << "Failed to open file " << exportedDocument.name() + << " write-only" << endl; + else { + QTextStream stream( &exportedDocument ); + stream.setEncoding( QTextStream::UnicodeUTF8 ); + stream << m_HTMLSource // the pure html data.. + .replace( "", + QString( "" ) + .arg( HTMLView::loadStyleSheet() ) ); // and the + // stylesheet + // code + exportedDocument.close(); + } +} + + +void ContextBrowser::paletteChange( const QPalette& /* pal */ ) +{ +// KTabWidget::paletteChange( pal ); + HTMLView::paletteChange(); + reloadStyleSheet(); +} + +void ContextBrowser::reloadStyleSheet() +{ + m_currentTrackPage->setUserStyleSheet( HTMLView::loadStyleSheet() ); + m_lyricsPage->setUserStyleSheet( HTMLView::loadStyleSheet() ); + m_wikiPage->setUserStyleSheet( HTMLView::loadStyleSheet() ); +} + +////////////////////////////////////////////////////////////////////////////////////////// +// PROTECTED SLOTS +////////////////////////////////////////////////////////////////////////////////////////// + +//parts of this function from ktabwidget.cpp, copyright (C) 2003 Zack Rusin and Stephan Binner +//fucking setCurrentTab() isn't virtual so we have to override this instead =( +void ContextBrowser::wheelDelta( int delta ) +{ + if ( count() < 2 || delta == 0 ) + return; + + int index = currentPageIndex(), start = index; + do + { + if( delta < 0 ) + index = (index + 1) % count(); + else + { + index = index - 1; + if( index < 0 ) + index = count() - 1; + } + if( index == start ) // full circle, none enabled + return; + } while( !isTabEnabled( page( index ) ) ); + setCurrentPage( index ); +} + + +////////////////////////////////////////////////////////////////////////////////////////// +// PRIVATE SLOTS +////////////////////////////////////////////////////////////////////////////////////////// + +void ContextBrowser::tabChanged( QWidget *page ) +{ +DEBUG_FUNC_INFO + setFocusProxy( page ); //so focus is given to a sensible widget when the tab is opened + + if ( page == m_contextTab ) + showCurrentTrack(); + else if ( page == m_lyricsTab ) + showLyrics(); + else if ( page == m_wikiTab ) + showWikipedia(); +} + +void ContextBrowser::slotContextMenu( const QString& urlString, const QPoint& point ) +{ + enum { APPEND, ASNEXT, MAKE, MEDIA_DEVICE, INFO, TITLE, RELATED, SUGGEST, FAVES, FRESHPODCASTS, NEWALBUMS, FAVALBUMS, LABELS }; + debug() << "url string: " << urlString << endl; + + if( urlString.startsWith( "musicbrainz" ) || + urlString.startsWith( "externalurl" ) || + urlString.startsWith( "show:suggest" ) || + urlString.startsWith( "http" ) || + urlString.startsWith( "wikipedia" ) || + urlString.startsWith( "seek" ) || + urlString.startsWith( "ggartist" ) || + urlString.startsWith( "artistback" ) || + urlString.startsWith( "current" ) || + urlString.startsWith( "lastfm" ) || + urlString.startsWith( "showlabel" ) || + urlString.startsWith( "show:editLabels" ) || + currentPage() != m_contextTab ) + return; + + KURL url( urlString ); + + KPopupMenu menu; + KURL::List urls( url ); + QString artist, album, track; // track unused here + Amarok::albumArtistTrackFromUrl( url.path(), artist, album, track ); + + if( urlString.isEmpty() ) + { + debug() << "url string empty. loaded?" << EngineController::engine()->loaded() << endl; + if( EngineController::engine()->loaded() ) + { + menu.setCheckable( true ); + menu.insertItem( i18n("Show Labels"), LABELS ); + menu.insertItem( i18n("Show Related Artists"), RELATED ); + menu.insertItem( i18n("Show Suggested Songs"), SUGGEST ); + menu.insertItem( i18n("Show Favorite Tracks"), FAVES ); + + menu.setItemChecked( RELATED, m_showRelated ); + menu.setItemChecked( SUGGEST, m_showSuggested ); + menu.setItemChecked( FAVES, m_showFaves ); + menu.setItemChecked( LABELS, m_showLabels ); + } else { + // the home info page + menu.setCheckable( true ); + menu.insertItem( i18n("Show Fresh Podcasts"), FRESHPODCASTS ); + menu.insertItem( i18n("Show Newest Albums"), NEWALBUMS ); + menu.insertItem( i18n("Show Favorite Albums"), FAVALBUMS ); + + menu.setItemChecked( FRESHPODCASTS, m_showFreshPodcasts ); + menu.setItemChecked( NEWALBUMS, m_showNewestAlbums ); + menu.setItemChecked( FAVALBUMS, m_showFavoriteAlbums ); + } + } + else if( url.protocol() == "fetchcover" ) + { + Amarok::coverContextMenu( this, point, artist, album ); + return; + } + else if( url.protocol() == "stream" ) + { + url = KURL::fromPathOrURL( url.url().replace( QRegExp( "^stream:" ), "http:" ) ); + urls = KURL::List( url ); + menu.insertTitle( i18n("Podcast"), TITLE ); + menu.insertItem( SmallIconSet( Amarok::icon( "files" ) ), i18n( "&Load" ), MAKE ); + menu.insertItem( SmallIconSet( Amarok::icon( "add_playlist" ) ), i18n( "&Append to Playlist" ), APPEND ); + menu.insertItem( SmallIconSet( Amarok::icon( "queue_track" ) ), i18n( "&Queue Podcast" ), ASNEXT ); + //menu.insertSeparator(); + //menu.insertItem( SmallIconSet( "down" ), i18n( "&Download" ), DOWNLOAD ); + } + else if( url.protocol() == "file" || url.protocol() == "artist" || url.protocol() == "album" || url.protocol() == "compilation" || url.protocol() == "albumdisc" || url.protocol() == "compilationdisc") + { + //TODO it would be handy and more usable to have this menu under the cover one too + + menu.insertTitle( i18n("Track"), TITLE ); + menu.insertItem( SmallIconSet( Amarok::icon( "files" ) ), i18n( "&Load" ), MAKE ); + menu.insertItem( SmallIconSet( Amarok::icon( "add_playlist" ) ), i18n( "&Append to Playlist" ), APPEND ); + menu.insertItem( SmallIconSet( Amarok::icon( "queue_track" ) ), i18n( "&Queue Track" ), ASNEXT ); + if( MediaBrowser::isAvailable() ) + menu.insertItem( SmallIconSet( Amarok::icon( "device" ) ), i18n( "&Transfer to Media Device" ), MEDIA_DEVICE ); + + menu.insertSeparator(); + menu.insertItem( SmallIconSet( Amarok::icon( "info" ) ), i18n( "Edit Track &Information..." ), INFO ); + + if ( url.protocol() == "artist" ) + { + urls = expandURL( url ); + + menu.changeTitle( TITLE, i18n("Artist") ); + menu.changeItem( INFO, i18n("Edit Artist &Information..." ) ); + menu.changeItem( ASNEXT, i18n("&Queue Artist's Songs") ); + } + if ( url.protocol() == "album" ) + { + urls = expandURL( url ); + + menu.changeTitle( TITLE, i18n("Album") ); + menu.changeItem( INFO, i18n("Edit Album &Information..." ) ); + menu.changeItem( ASNEXT, i18n("&Queue Album") ); + } + if ( url.protocol() == "albumdisc" ) + { + urls = expandURL( url ); + + menu.changeTitle( TITLE, i18n("Album Disc") ); + menu.changeItem( INFO, i18n("Edit Album Disc &Information..." ) ); + menu.changeItem( ASNEXT, i18n("&Queue Album Disc") ); + } + if ( url.protocol() == "compilation" ) + { + urls = expandURL( url ); + + menu.changeTitle( TITLE, i18n("Compilation") ); + menu.changeItem( INFO, i18n("Edit Album &Information..." ) ); + menu.changeItem( ASNEXT, i18n("&Queue Album") ); + } + if ( url.protocol() == "compilationdisc" ) + { + urls = expandURL( url ); + + menu.changeTitle( TITLE, i18n("Compilation Disc") ); + menu.changeItem( INFO, i18n("Edit Compilation Disc &Information..." ) ); + menu.changeItem( ASNEXT, i18n("&Queue Compilation Disc") ); + } + + if( urls.count() == 0 ) + { + menu.setItemEnabled( MAKE, false ); + menu.setItemEnabled( APPEND, false ); + menu.setItemEnabled( ASNEXT, false ); + menu.setItemEnabled( MEDIA_DEVICE, false ); + menu.setItemEnabled( INFO, false ); + } + } + + //Not all these are used in the menu, it depends on the context + switch( menu.exec( point ) ) + { + + case RELATED: + m_showRelated = !menu.isItemChecked( RELATED ); + Amarok::config( "ContextBrowser" )->writeEntry( "ShowRelated", m_showRelated ); + m_dirtyCurrentTrackPage = true; + showCurrentTrack(); + break; + + case SUGGEST: + m_showSuggested = !menu.isItemChecked( SUGGEST ); + Amarok::config( "ContextBrowser" )->writeEntry( "ShowSuggested", m_showSuggested ); + m_dirtyCurrentTrackPage = true; + showCurrentTrack(); + break; + + case FAVES: + m_showFaves = !menu.isItemChecked( FAVES ); + Amarok::config( "ContextBrowser" )->writeEntry( "ShowFaves", m_showFaves ); + m_dirtyCurrentTrackPage = true; + showCurrentTrack(); + break; + + case LABELS: + m_showLabels = !menu.isItemChecked( LABELS ); + Amarok::config( "ContextBrowser" )->writeEntry( "ShowLabels", m_showLabels ); + m_dirtyCurrentTrackPage = true; + showCurrentTrack(); + break; + + case FRESHPODCASTS: + m_showFreshPodcasts = !menu.isItemChecked( FRESHPODCASTS ); + Amarok::config( "ContextBrowser" )->writeEntry( "ShowFreshPodcasts", m_showFreshPodcasts ); + m_dirtyCurrentTrackPage = true; + showCurrentTrack(); + break; + + case NEWALBUMS: + m_showNewestAlbums = !menu.isItemChecked( NEWALBUMS ); + Amarok::config( "ContextBrowser" )->writeEntry( "ShowNewestAlbums", m_showNewestAlbums ); + m_dirtyCurrentTrackPage = true; + showCurrentTrack(); + break; + + case FAVALBUMS: + m_showFavoriteAlbums = !menu.isItemChecked( FAVALBUMS ); + Amarok::config( "ContextBrowser" )->writeEntry( "ShowFavoriteAlbums", m_showFavoriteAlbums ); + m_dirtyCurrentTrackPage = true; + showCurrentTrack(); + break; + + case ASNEXT: + Playlist::instance()->insertMedia( urls, Playlist::Queue ); + break; + + case INFO: + { + if ( urls.count() > 1 ) + { + TagDialog* dialog = new TagDialog( urls, instance() ); + dialog->show(); + } + else if ( !urls.isEmpty() ) + { + TagDialog* dialog = new TagDialog( urls.first(), instance() ); + dialog->show(); + } + break; + } + + case MAKE: + Playlist::instance()->clear(); + //FALL_THROUGH + + case APPEND: + Playlist::instance()->insertMedia( urls, Playlist::Append ); + break; + + case MEDIA_DEVICE: + MediaBrowser::queue()->addURLs( urls ); + break; + + } +} + + + +////////////////////////////////////////////////////////////////////////////////////////// +// Current-Tab +////////////////////////////////////////////////////////////////////////////////////////// + +/** This is the slowest part of track change, so we thread it */ +class CurrentTrackJob : public ThreadManager::DependentJob +{ +public: + CurrentTrackJob( ContextBrowser *parent ) + : ThreadManager::DependentJob( parent, "CurrentTrackJob" ) + , b( parent ) + , m_currentTrack( QDeepCopy( EngineController::instance()->bundle() ) ) + , m_isStream( EngineController::engine()->isStream() ) + { + for( QStringList::iterator it = b->m_metadataHistory.begin(); + it != b->m_metadataHistory.end(); + ++it ) + { + m_metadataHistory += QDeepCopy( *it ); + } + + + m_amarokIconPath = QDeepCopy(KGlobal::iconLoader()->iconPath( "amarok", + -KIcon::SizeEnormous ) ); + m_musicBrainIconPath = QDeepCopy(locate( "data", "amarok/images/musicbrainz.png" ) + ); + m_lastfmIcon = "file://" + locate( "data","amarok/images/lastfm.png" ); + } + +private: + virtual bool doJob(); + void addMetaHistory(); + void showLastFm( const MetaBundle ¤tTrack ); + void showStream( const MetaBundle ¤tTrack ); + void showPodcast( const MetaBundle ¤tTrack ); + void showBrowseArtistHeader( const QString &artist ); + void showBrowseLabelHeader( const QString &label ); + void showCurrentArtistHeader( const MetaBundle ¤tTrack ); + void showRelatedArtists( const QString &artist, const QStringList &relArtists ); + void showSuggestedSongs( const QStringList &relArtists ); + void showSongsWithLabel( const QString &label ); + void showArtistsFaves( const QString &artistName, uint artist_id ); + void showArtistsAlbums( const QString &artist, uint artist_id, uint album_id ); + void showArtistsCompilations( const QString &artist, uint artist_id, uint album_id ); + void showHome(); + void showUserLabels( const MetaBundle ¤tTrack ); + QString fetchLastfmImage( const QString& url ); + QStringList showHomeByAlbums(); + void constructHTMLAlbums( const QStringList &albums, QString &htmlCode, const QString &idPrefix ); + static QString statsHTML( int score, int rating, bool statsbox = true ); // meh. + + virtual void completeJob() + { + // are we still showing the currentTrack page? +// if( b->currentPage() != b->m_contextTab ) +// return; + + b->m_shownAlbums.clear(); + for( QStringList::iterator it = m_shownAlbums.begin(); + it != m_shownAlbums.end(); + ++it ) + b->m_shownAlbums.append( QDeepCopy( *it ) ); + b->m_HTMLSource = QDeepCopy( m_HTMLSource ); + b->m_currentTrackPage->set( m_HTMLSource ); + b->m_dirtyCurrentTrackPage = false; + b->saveHtmlData(); // Send html code to file + } + QString m_HTMLSource; + QString m_amarokIconPath; + QString m_musicBrainIconPath; + QString m_lastfmIcon; + + ContextBrowser *b; + MetaBundle m_currentTrack; + bool m_isStream; + QStringList m_shownAlbums; + QStringList m_metadataHistory; +}; + +void +ContextBrowser::showContext( const KURL &url, bool fromHistory ) +{ + if ( currentPage() != m_contextTab ) + { + blockSignals( true ); + showPage( m_contextTab ); + blockSignals( false ); + } + + m_dirtyCurrentTrackPage = true; + m_contextURL = url.url(); + + if( url.protocol() == "current" ) + { + m_browseArtists = false; + m_browseLabels = false; + m_label = QString::null; + m_artist = QString::null; + m_contextBackHistory.clear(); + m_contextBackHistory.push_back( "current://track" ); + } + else if( url.protocol() == "artist" ) + { + m_browseArtists = true; + m_browseLabels = false; + m_label = QString::null; + m_artist = unescapeHTMLAttr( url.path() ); + } + else if( url.protocol() == "showlabel" ) + { + m_browseLabels = true; + m_browseArtists = false; + m_artist = QString::null; + m_label = unescapeHTMLAttr( url.path() ); + } + + // Append new URL to history + if ( !fromHistory ) { + m_contextBackHistory += m_contextURL.url(); + } + // Limit number of items in history + if ( m_contextBackHistory.count() > CONTEXT_MAX_HISTORY ) + m_contextBackHistory.pop_front(); + + showCurrentTrack(); +} + +void +ContextBrowser::contextHistoryBack() //SLOT +{ + if( m_contextBackHistory.size() > 0 ) + { + m_contextBackHistory.pop_back(); + + m_dirtyCurrentTrackPage = true; + + showContext( KURL( m_contextBackHistory.last() ), true ); + } +} + + +void ContextBrowser::showCurrentTrack() //SLOT +{ +#if 0 + if( BrowserBar::instance()->currentBrowser() != this ) + { + debug() << "current browser is not context, aborting showCurrentTrack()" << endl; + m_dirtyCurrentTrackPage = true; + m_currentTrackPage->set( QString( "

%1
" ) + .arg( i18n( "Updating..." ) ) ); + return; + } +#endif + if ( currentPage() != m_contextTab ) { + blockSignals( true ); + showPage( m_contextTab ); + blockSignals( false ); + } + + // TODO: Show CurrentTrack or Lyric tab if they were selected + // If it's not a streaming, check for a collection + if ( !EngineController::engine()->isStream() ) + { + if ( m_emptyDB && CollectionDB::instance()->isValid() && !MountPointManager::instance()->collectionFolders().isEmpty() ) + { + showScanning(); + return; + } + else if ( CollectionDB::instance()->isEmpty() || !CollectionDB::instance()->isValid() ) + { + showIntroduction(); + return; + } + } + if( !m_dirtyCurrentTrackPage ) + return; + m_currentURL = EngineController::instance()->bundle().url(); + m_currentTrackPage->write( QString::null ); + ThreadManager::instance()->onlyOneJob( new CurrentTrackJob( this ) ); +} + + + +////////////////////////////////////////////////////////////////////////////////////////// +// Shows the statistics summary when no track is playing +////////////////////////////////////////////////////////////////////////////////////////// + +void CurrentTrackJob::showHome() +{ + QueryBuilder qb; + + qb.clear(); //Song count + qb.addReturnFunctionValue( QueryBuilder::funcCount, QueryBuilder::tabSong, QueryBuilder::valURL ); + qb.setOptions( QueryBuilder::optRemoveDuplicates ); + QStringList a = qb.run(); + QString songCount = a[0]; + + qb.clear(); //Artist count + //qb.addReturnFunctionValue( QueryBuilder::funcCount, QueryBuilder::tabArtist, QueryBuilder::valID ); + //qb.setOptions( QueryBuilder::optRemoveDuplicates ); + //a = qb.run(); + //QString artistCount = a[0]; + qb.setOptions( QueryBuilder::optRemoveDuplicates ); + qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valArtistID ); + //I can't get the correct value w/o suing a subquery, and querybuilder doesn't support those + QString artistCount = QString::number( qb.run().count() ); + + qb.clear(); //Album count + //qb.addReturnFunctionValue( QueryBuilder::funcCount, QueryBuilder::tabAlbum, QueryBuilder::valID ); + //qb.setOptions( QueryBuilder::optRemoveDuplicates ); + //a = qb.run(); + //QString albumCount = a[0]; + qb.setOptions( QueryBuilder::optRemoveDuplicates ); + qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valAlbumID ); + QString albumCount = QString::number( qb.run().count() ); + + qb.clear(); //Genre count + //qb.addReturnFunctionValue( QueryBuilder::funcCount, QueryBuilder::tabGenre, QueryBuilder::valID ); + //qb.setOptions( QueryBuilder::optRemoveDuplicates ); + //a = qb.run(); + //QString genreCount = a[0]; + qb.setOptions( QueryBuilder::optRemoveDuplicates ); + qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valGenreID ); + QString genreCount = QString::number( qb.run().count() ); + + qb.clear(); //Total Playtime + qb.addReturnFunctionValue( QueryBuilder::funcSum, QueryBuilder::tabSong, QueryBuilder::valLength ); + a = qb.run(); + QString playTime = MetaBundle::fuzzyTime( a[0].toInt() ); + + m_HTMLSource.append( + QStringx( + "
\n" + "
\n" + "\n" + + i18n( "No Track Playing" ) + + "\n" + "
\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "
\n" + "\n" + "\n" + "%3
\n" + "%4
\n" + "%5
\n" + "%6
\n" + "%7
\n" + "
\n" + "
\n" ) + .args( QStringList() + << escapeHTMLAttr( "externalurl://amarok.kde.org" ) + << escapeHTMLAttr( m_amarokIconPath ) + << i18n( "1 Track", "%n Tracks", songCount.toInt() ) + << i18n( "1 Artist", "%n Artists", artistCount.toInt() ) + << i18n( "1 Album", "%n Albums", albumCount.toInt() ) + << i18n( "1 Genre", "%n Genres", genreCount.toInt() ) + << i18n( "%1 Play-time" ).arg ( playTime ) ) ); + + m_shownAlbums = showHomeByAlbums(); + + m_HTMLSource.append( + "\n"); +} + + +void +CurrentTrackJob::constructHTMLAlbums( const QStringList &reqResult, QString &htmlCode, const QString &stID ) +{ + // This function create the html code used to display a list of albums. Each album + // is a 'toggleable' block. + // Parameter stID is used to differentiate same albums in different album list. So if this function + // is called multiple time in the same HTML code, stID must be different. + for ( uint i = 0; i < reqResult.count(); i += 4 ) + { + QueryBuilder qb; + qb.clear(); + qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valTitle ); + qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valURL ); + qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valTrack ); + qb.addReturnValue( QueryBuilder::tabYear, QueryBuilder::valName ); + qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valLength ); + qb.addReturnValue( QueryBuilder::tabArtist, QueryBuilder::valName ); + qb.addReturnValue( QueryBuilder::tabArtist, QueryBuilder::valID ); + qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valDiscNumber ); + qb.addMatch( QueryBuilder::tabSong, QueryBuilder::valAlbumID, reqResult[i+1] ); + qb.addMatch( QueryBuilder::tabSong, QueryBuilder::valArtistID, reqResult[i+3] ); + qb.sortBy( QueryBuilder::tabSong, QueryBuilder::valDiscNumber ); + qb.sortBy( QueryBuilder::tabSong, QueryBuilder::valTrack ); + qb.sortBy( QueryBuilder::tabSong, QueryBuilder::valTitle ); + qb.setOptions( QueryBuilder::optNoCompilations ); // samplers __need__ to be handled differently + QStringList albumValues = qb.run(); + + QString albumYear; + if ( !albumValues.isEmpty() ) + { + albumYear = albumValues[ 3 ]; + for ( uint j = 0; j < albumValues.count(); j += qb.countReturnValues()) + if ( albumValues[j + 3] != albumYear || albumYear == "0" ) + { + albumYear = QString::null; + break; + } + } + + uint i_albumLength = 0; + for ( uint j = 0; j < albumValues.count(); j += qb.countReturnValues() ) + i_albumLength += QString(albumValues[j + 4]).toInt(); + + QString albumLength = ( i_albumLength==0 ? i18n( "Unknown" ) : MetaBundle::prettyTime( i_albumLength, true ) ); + + htmlCode.append( QStringx ( + "\n" + "\n" + "
\n" + "\n" + "\n") + .args( QStringList() + << stID + reqResult[i+1] )); + + QString albumName = escapeHTML( reqResult[ i ].isEmpty() ? i18n( "Unknown album" ) : reqResult[ i ] ); + + QString artistName = albumValues[5].isEmpty() ? i18n( "Unknown artist" ) : albumValues[5]; + + QString albumImage = ContextBrowser::getEncodedImage( CollectionDB::instance()->albumImage( albumValues[5], reqResult[ i ], true, 50 ) ); + QString albumImageTitleAttr = albumImageTooltip( albumImage, 50 ); + + // Album image + htmlCode.append( QStringx ( + "\n" + "\n") + .args( QStringList() + << i18n( "Single", "%n Tracks", albumValues.count() / qb.countReturnValues() ) + << albumYear + << albumLength) ); + + // Begining of the 'toggleable div' that contains the songs + htmlCode.append( QStringx ( + "\n" + "
\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "%6\n" + "\n" + " - \n" + "\n" + "%9\n" + "\n" ) + .args( QStringList() + << escapeHTMLAttr( albumValues[5] ) // artist name + << escapeHTMLAttr( reqResult[ i ].isEmpty() ? i18n( "Unknown" ) : reqResult[ i ] ) // album.name + << albumImageTitleAttr + << escapeHTMLAttr( albumImage ) + << escapeHTMLAttr( artistName ) + << escapeHTML( artistName ) + << albumValues[6] + << reqResult[ i + 1 ] //album.id + << albumName ) ); + + // Tracks number, year and length + htmlCode.append( QStringx ( + "%1 " + "
\n" + "%2\n" + "%3\n" + "
\n" + "
\n" + "
\n" ) + .args( QStringList() + << "none" /* shows it if it's the current track album */ + << stID + reqResult[ i + 1 ] ) ); + + QString discNumber; + + if ( !albumValues.isEmpty() ) + { + for ( uint j = 0; j < albumValues.count(); j += qb.countReturnValues() ) + { + QString newDiscNumber = albumValues[ j + 7 ].stripWhiteSpace(); + if( discNumber != newDiscNumber && newDiscNumber.toInt() > 0) + { + discNumber = newDiscNumber; + htmlCode.append( QStringx ( + "
\n" + "\n" + "%4" + "\n" + "
\n" ) + .args( QStringList() + << albumValues[6] + << reqResult[ i + 1 ] //album.id + << escapeHTMLAttr( discNumber ) + << i18n( "Disc %1" ).arg( discNumber ) ) ); + } + QString track = albumValues[j + 2].stripWhiteSpace(); + if( track.length() > 0 ) + { + if( track.length() == 1 ) + track.prepend( "0" ); + + track = "\n" + track + " \n"; + } + + QString length; + if( albumValues[j + 4] != "0" ) + length = "(" + MetaBundle::prettyTime( QString(albumValues[j + 4]).toInt(), true ) + ")\n"; + + htmlCode.append( + "\n" ); + } + } + + htmlCode.append( + "
\n" + "\n" + "\n" ); + } +} + + +// return list of albums shown +QStringList +CurrentTrackJob::showHomeByAlbums() +{ + QueryBuilder qb; + + m_HTMLSource.append( "\n" ); + + // + if( ContextBrowser::instance()->m_showFreshPodcasts ) + { + qb.clear(); + qb.addReturnValue( QueryBuilder::tabPodcastEpisodes, QueryBuilder::valParent ); + qb.addFilter( QueryBuilder::tabPodcastEpisodes, QueryBuilder::valIsNew, CollectionDB::instance()->boolT(), QueryBuilder::modeNormal, true ); + qb.sortBy( QueryBuilder::tabPodcastEpisodes, QueryBuilder::valID, true ); + qb.setOptions( QueryBuilder::optRemoveDuplicates ); + qb.setLimit( 0, 5 ); + QStringList channels = qb.run(); + + if( channels.count() > 0 ) + { + m_HTMLSource.append( + "\n" + "\n" + "\n" ); + } + } + // + + QStringList albums; + // + if( ContextBrowser::instance()->m_showNewestAlbums ) + { + qb.clear(); + qb.addReturnValue( QueryBuilder::tabAlbum, QueryBuilder::valName ); + qb.addReturnValue( QueryBuilder::tabAlbum, QueryBuilder::valID ); + qb.addReturnFunctionValue( QueryBuilder::funcMax, QueryBuilder::tabSong, QueryBuilder::valCreateDate ); + qb.addReturnValue( QueryBuilder::tabArtist, QueryBuilder::valID ); + qb.sortByFunction( QueryBuilder::funcMax, QueryBuilder::tabSong, QueryBuilder::valCreateDate, true ); + qb.excludeMatch( QueryBuilder::tabAlbum, i18n( "Unknown" ) ); + qb.groupBy( QueryBuilder::tabAlbum, QueryBuilder::valID ); + qb.groupBy( QueryBuilder::tabArtist, QueryBuilder::valID ); + qb.groupBy( QueryBuilder::tabAlbum, QueryBuilder::valName ); + qb.setOptions( QueryBuilder::optNoCompilations ); // samplers __need__ to be handled differently + qb.setLimit( 0, 5 ); + QStringList recentAlbums = qb.run(); + + foreach( recentAlbums ) + { + albums += *it; + it++; + it++; + it++; + } + // toggle html here so we get correct albums + m_HTMLSource.append( + "\n" + "\n" + "\n" ); + } + // + + // + if( ContextBrowser::instance()->m_showFavoriteAlbums ) + { + qb.clear(); + qb.addReturnValue( QueryBuilder::tabAlbum, QueryBuilder::valName ); + qb.addReturnValue( QueryBuilder::tabAlbum, QueryBuilder::valID ); + qb.sortByFavoriteAvg(); // this function adds return values! + qb.addReturnValue( QueryBuilder::tabArtist, QueryBuilder::valID ); + // only albums with more than 3 tracks + qb.having( QueryBuilder::tabAlbum, QueryBuilder::valID, QueryBuilder::funcCount, QueryBuilder::modeGreater, "3" ); + qb.excludeMatch( QueryBuilder::tabAlbum, i18n( "Unknown" ) ); + qb.groupBy( QueryBuilder::tabAlbum, QueryBuilder::valID ); + qb.groupBy( QueryBuilder::tabArtist, QueryBuilder::valID ); + qb.groupBy( QueryBuilder::tabAlbum, QueryBuilder::valName ); + qb.setOptions( QueryBuilder::optNoCompilations ); // samplers __need__ to be handled differently + qb.setLimit( 0, 5 ); + QStringList faveAlbums = qb.run(); + QStringList faveResults; + + bool ratings = AmarokConfig::useRatings(); + bool scores = AmarokConfig::useScores(); + + foreach( faveAlbums ) { + albums += *it; + faveResults += *(it++); + faveResults += *(it++); + faveResults += *(it++); + // sortByFavoriteAvg add some return values, and constructHTMLAlbums expects + // a specific set of return values, so we might need to skip some values + if ( ratings ) + it++; + if ( scores ) + it++; + faveResults += *(it); + } + + m_HTMLSource.append( + "\n" ); + } + // + + m_HTMLSource.append( "
\n" + "
\n" + "\n" + + i18n( "Fresh Podcast Episodes" ) + + "\n" + "
\n" + "\n" ); + + uint i = 0; + for( QStringList::iterator it = channels.begin(); + it != channels.end(); + it++ ) + { + PodcastChannelBundle pcb; + if( !CollectionDB::instance()->getPodcastChannelBundle( *it, &pcb ) ) + continue; + + QValueList episodes = CollectionDB::instance()->getPodcastEpisodes( *it, true /* only new */, 1 ); + if( !episodes.isEmpty() ) + { + PodcastEpisodeBundle &ep = *episodes.begin(); + + QString date; + ep.dateTime().isNull() ? + date = ep.date() : + date = ep.dateTime().toString(); + + QString image = CollectionDB::instance()->podcastImage( pcb.imageURL().url(), true, 50 ); + QString imageAttr = escapeHTMLAttr( i18n( "Click to go to podcast website: %1." ).arg( pcb.link().prettyURL() ) ); + + m_HTMLSource.append( QStringx ( + "\n" + "\n" + "\n" ); + i++; + } + } + m_HTMLSource.append( + "
\n" + "
\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "
\n" + "\n" + "\n" + "\n" + "\n" + "%5 \n" + "%7\n" + "
\n" + "%8\n" + "
\n" + "
\n" + "
\n" ) + .args( QStringList() + << QString::number( i ) + << pcb.link().url().replace( QRegExp( "^http:" ), "externalurl:" ) + << escapeHTMLAttr( imageAttr ) + << escapeHTMLAttr( image ) + << escapeHTML( ep.duration() ? MetaBundle::prettyTime( ep.duration() ) : QString( "" ) ) + << ( ep.localUrl().isValid() + ? ep.localUrl().url() + : ep.url().url().replace( QRegExp( "^http:" ), "stream:" ) ) + << escapeHTML( pcb.title() + ": " + ep.title() ) + << escapeHTML( date ) + << "none" + << QString::number( i ) + ) + ); + + m_HTMLSource.append( QStringx ( "

%1

\n" ).arg( ep.description() ) ); + + m_HTMLSource.append( + "
\n" + "
\n" + "
\n" + "
\n" + "
\n" + "
\n" + "\n" + + i18n( "Your Newest Albums" ) + + "\n" + "
\n" + "\n" ); + constructHTMLAlbums( recentAlbums, m_HTMLSource, "1" ); + + m_HTMLSource.append( + "
\n" + "
\n" + "
\n" + "
\n" + "
\n" + "\n" + + i18n( "Favorite Albums" ) + + "\n" + "
\n" + "\n" ); + + if ( faveAlbums.count() == 0 ) + { + m_HTMLSource.append( + "

\n" + + (QueryBuilder::valForFavoriteSorting() == QueryBuilder::valRating + ? i18n( "A list of your favorite albums will appear here, once you have rated a few of your songs." ) + : i18n( "A list of your favorite albums will appear here, once you have played a few of your songs." ) ) + + "

\n" ); + } + else + { + constructHTMLAlbums( faveResults, m_HTMLSource, "2" ); + } + + m_HTMLSource.append( + "
\n" ); + + return albums; +} + + +void CurrentTrackJob::showLastFm( const MetaBundle ¤tTrack ) +{ + if( !LastFm::Controller::instance()->isPlaying() ) return; + + const LastFm::Bundle *lastFmInfo = currentTrack.lastFmBundle(); + if ( !lastFmInfo ) return; + + const QString username = AmarokConfig::scrobblerUsername(); + const QString userpage = "www.last.fm/user/" + username; //no http + const QString albumUrl = lastFmInfo->albumUrl(); + const QString artistUrl = lastFmInfo->artistUrl(); + const QString titleUrl = lastFmInfo->titleUrl(); + + const QString coverImage = ContextBrowser::getEncodedImage( lastFmInfo->imageUrl() ); + + QPtrList newUrls; + newUrls.append( &albumUrl ); + newUrls.append( &artistUrl ); + newUrls.append( &titleUrl ); + + for ( QString* url = newUrls.first(); url; url = newUrls.next() ) + url->replace( QRegExp( "^http:" ), "externalurl:" ); + + const QString skipIcon = KGlobal::iconLoader()->iconPath( Amarok::icon("next"), -KIcon::SizeSmallMedium ); + const QString loveIcon = KGlobal::iconLoader()->iconPath( Amarok::icon("love"), -KIcon::SizeSmallMedium ); + const QString banIcon = KGlobal::iconLoader()->iconPath( Amarok::icon("remove"), -KIcon::SizeSmallMedium ); + + + m_HTMLSource.append( QStringx( + "
\n" + "
\n" + "%1 " + "
\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "
\n" + "%3 - %5" + "
\n" + "%7" + "
\n" + "" + "\n" + "\n" + "
\n" + "\n" + "\n" + "\n" + "
\n" + "\n" + "\n" + "\n" + "\n" + "
\n" + "%14\n" + "\n" + "\n" + "
\n" + "%16\n" + "\n" + "\n" + "
\n" + "%18\n" + "\n" + "\n" + "
\n" + "
\n" + "
\n" ) + .args( QStringList() + << escapeHTML( LastFm::Controller::stationDescription() ) //1 + << artistUrl //2 + << escapeHTML( currentTrack.artist() ) //3 + << titleUrl //4 + << escapeHTML( currentTrack.title() ) //5 + << albumUrl //6 + << escapeHTML( currentTrack.album() ) //7 + << albumUrl //8 + << coverImage //9 + << escapeHTMLAttr( currentTrack.album() )//10 + << escapeHTMLAttr( userpage ) //11 + << escapeHTMLAttr( userpage ) //12 + << escapeHTMLAttr( m_lastfmIcon ) //13 + << escapeHTML( i18n( "Skip" ) ) //14 + << escapeHTMLAttr( skipIcon ) //15 + << escapeHTML( i18n( "Love" ) ) //16 + << escapeHTMLAttr( loveIcon ) //17 + << escapeHTML( i18n( "Ban" ) ) //18 + << escapeHTMLAttr( banIcon ) //19 + ) ); + + addMetaHistory(); + + if( ContextBrowser::instance()->m_showRelated || ContextBrowser::instance()->m_showSuggested ) + { + QStringList relArtists = CollectionDB::instance()->similarArtists( currentTrack.artist(), 10 ); + if ( !relArtists.isEmpty() ) + { + if( ContextBrowser::instance()->m_showRelated ) + showRelatedArtists( currentTrack.artist(), relArtists ); + + if( ContextBrowser::instance()->m_showSuggested ) + showSuggestedSongs( relArtists ); + } + } + + const uint artist_id = CollectionDB::instance()->artistID( currentTrack.artist(), false /* don't autocreate */ ); + if( artist_id ) + { + if( ContextBrowser::instance()->m_showFaves ) + showArtistsFaves( currentTrack.artist(), artist_id ); + + const uint album_id = CollectionDB::instance()->albumID ( currentTrack.album(), false /* don't autocreate */ ); + showArtistsAlbums( currentTrack.artist(), artist_id, album_id ); + showArtistsCompilations( currentTrack.artist(), artist_id, album_id ); + } + + m_HTMLSource.append( "\n" ); +} + + +void CurrentTrackJob::showStream( const MetaBundle ¤tTrack ) +{ + m_HTMLSource.append( QStringx( + "
\n" + "
\n" + "%1 " + "
\n" + "\n" + "\n" + "\n" + "\n" + "
\n" + "%2\n" + "
\n" + "
\n" + "%3" + "
\n" + "
\n" + "%4" + "
\n" + "%5 kbps" + "
\n" + "%6" + "
\n" + "%7" + "
\n" + "
\n" ) + .args( QStringList() + << i18n( "Stream Details" ) + << escapeHTML( currentTrack.prettyTitle() ) + << escapeHTML( currentTrack.streamName() ) + << escapeHTML( currentTrack.genre() ) + << escapeHTML( currentTrack.prettyBitrate() ) + << escapeHTML( currentTrack.streamUrl() ) + << escapeHTML( currentTrack.prettyURL() ) ) ); + + addMetaHistory(); + + m_HTMLSource.append( "\n" ); +} + +void CurrentTrackJob::addMetaHistory() +{ + if ( m_metadataHistory.count() > 0 ) + { + m_HTMLSource.append( + "
\n" + "
\n" + i18n( "Metadata History" ) + "
\n" + "\n" ); + + for ( uint i = 0; i < m_metadataHistory.count(); ++i ) + { + const QString &str = m_metadataHistory[i]; + m_HTMLSource.append( QStringx( "\n" ).arg( str ) ); + } + + m_HTMLSource.append( + "
%1
\n" + "
\n" ); + } +} + +void CurrentTrackJob::showPodcast( const MetaBundle ¤tTrack ) +{ + if( !currentTrack.podcastBundle() ) + return; + + PodcastEpisodeBundle peb = *currentTrack.podcastBundle(); + PodcastChannelBundle pcb; + bool channelInDB = true; + if( !CollectionDB::instance()->getPodcastChannelBundle( peb.parent(), &pcb ) ) + { + pcb.setTitle( i18n( "Unknown Channel (not in Database)" ) ); + channelInDB = false; + } + + QString image; + if( pcb.imageURL().isValid() ) + image = CollectionDB::instance()->podcastImage( pcb.imageURL().url(), true ); + else + image = CollectionDB::instance()->notAvailCover( true ); + + QString imageAttr = escapeHTMLAttr( pcb.link().isValid() + ? i18n( "Click to go to podcast website: %1." ).arg( pcb.link().prettyURL() ) + : i18n( "No podcast website." ) + ); + + m_HTMLSource.append( QStringx( + "
\n" + "
\n" + "%1 " + "
\n" + "%2\n" + "
\n" + + "\n" + "\n" + "\n" + "\n" + "
\n" + "\n" + "\n" + "\n" + "\n" + "%6" + "%7" + "
\n" + "
\n" ) + .args( QStringList() + << escapeHTML( pcb.title() ) + << escapeHTML( peb.title() ) + << ( pcb.link().isValid() + ? pcb.link().url().replace( QRegExp( "^http:" ), "externalurl:" ) + : "current://track" ) + << image + << imageAttr + << escapeHTML( peb.author().isEmpty() + ? i18n( "Podcast" ) + : i18n( "Podcast by %1" ).arg( peb.author() ) ) + << ( peb.localUrl().isValid() + ? "
\n" + escapeHTML( i18n( "(Cached)" ) ) + : "" ) + ) + ); + + if ( m_isStream && m_metadataHistory.count() > 1 ) + { + m_HTMLSource.append( + "
\n" + "
\n" + i18n( "Metadata History" ) + "
\n" + "\n" ); + + for ( uint i = 0; i < m_metadataHistory.count(); ++i ) + { + const QString &str = m_metadataHistory[i]; + m_HTMLSource.append( QStringx( "\n" ).arg( str ) ); + } + + m_HTMLSource.append( + "
%1
\n" + "
\n" ); + } + + m_HTMLSource.append( + "
\n" + "
\n" + "\n" + + ( channelInDB + ? i18n( "Episodes from %1" ).arg( escapeHTML( pcb.title() ) ) + : i18n( "Episodes from this Channel" ) + ) + + "\n" + "
\n" + "\n" ); + + uint i = 0; + QValueList episodes = CollectionDB::instance()->getPodcastEpisodes( peb.parent() ); + while( !episodes.isEmpty() ) + { + PodcastEpisodeBundle &ep = episodes.back(); + QString date; + + ep.dateTime().isNull() ? + date = ep.date() : + date = ep.dateTime().toString(); + + m_HTMLSource.append( QStringx ( + "\n" + "\n" + "\n" ); + i++; + episodes.pop_back(); + } + + m_HTMLSource.append("\n" ); +} + +void CurrentTrackJob::showBrowseArtistHeader( const QString &artist ) +{ + // + bool linkback = ( b->m_contextBackHistory.size() > 0 ); + QString back = ( linkback + ? "\n" + + escapeHTML( i18n( "<- Back" ) ) + + "\n" + : QString( "" ) + ); + m_HTMLSource.append( + QString( + "
\n" + "
\n" + "%1\n" + "
\n" + "
\n" + "
\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "
\n" + "%2 " + "%4\n" + "
\n" + "%5\n" + "
\n" + "
\n" + "
\n" ) + .args( QStringList() + << QString::number( i ) + << escapeHTML( ep.duration() ? MetaBundle::prettyTime( ep.duration() ) : QString( "" ) ) + << ( ep.localUrl().isValid() + ? ep.localUrl().url() + : ep.url().url().replace( QRegExp( "^http:" ), "stream:" ) ) + << escapeHTML( ep.title() ) + << escapeHTML( date ) + << (peb.url() == ep.url() ? "block" : "none" ) + << QString::number( i ) + ) + ); + + m_HTMLSource.append( QStringx ( "

%1

\n" ).arg( ep.description() ) ); + + m_HTMLSource.append( + "
\n" + "
\n" + "\n" + "\n" + "
%2
%3
\n" + "
\n" ) + .arg( escapeHTML( artist ) ) + .arg( escapeHTML( i18n( "Browse Artist" ) ) ) + .arg( back ) ); + m_HTMLSource.append( + "\n" + ); + + m_HTMLSource.append( + "\n" + "\n" + "\n" + ); + + m_HTMLSource.append( + "\n" + "\n" + "\n"); + m_HTMLSource.append( + "\n" + "\n" + "\n" + ); + + m_HTMLSource.append( + "\n" + "\n" + "
\n" + + QString( "\n" ) + + i18n( "Information for Current Track" ) + + "\n" + "
\n" + + QString( "\n" ).arg( escapeHTMLAttr( artist + b->wikiArtistPostfix() ) ) + + i18n( "Wikipedia Information for %1" ).arg( escapeHTML( artist ) ) + + "\n" + "
\n" + + QString( "\n" ).arg( escapeHTMLAttr( artist ) ) + + i18n( "Google Musicsearch for %1" ).arg( escapeHTML( artist ) ) + + "\n" + "
\n" + "\n" ); + // +} + +void +CurrentTrackJob::showBrowseLabelHeader( const QString &label ) +{ + bool linkback = ( b->m_contextBackHistory.size() > 0 ); + QString back = ( linkback + ? "\n" + + escapeHTML( i18n( "<- Back" ) ) + + "\n" + : QString( "" ) + ); + m_HTMLSource.append( + QString( + "
\n" + "
\n" + "%1\n" + "
\n" + "\n" + "\n" + "\n" + "
%2
%3
\n" + "
\n" ) + .arg( escapeHTML( label ) ) + .arg( escapeHTML( i18n( "Browse Label" ) ) ) + .arg( back ) ); + m_HTMLSource.append( + "\n" + ); + + m_HTMLSource.append( + "\n" + "\n" + "\n" + ); + + m_HTMLSource.append( + "\n" + "\n" + "\n"); + + m_HTMLSource.append( + "\n" + "\n" + "
\n" + + QString( "\n" ) + + i18n( "Information for Current Track" ) + + "\n" + "
\n" + + QString( "\n" ).arg( escapeHTMLAttr( label ) ) + + i18n( "Last.fm Information for %1" ).arg( escapeHTML( label ) ) + + "\n" + "
\n" + "
\n" ); +} + +void CurrentTrackJob::showCurrentArtistHeader( const MetaBundle ¤tTrack ) +{ + QueryBuilder qb; + QStringList values; + // + qb.clear(); + qb.addReturnValue( QueryBuilder::tabStats, QueryBuilder::valCreateDate ); + qb.addReturnValue( QueryBuilder::tabStats, QueryBuilder::valAccessDate ); + qb.addReturnValue( QueryBuilder::tabStats, QueryBuilder::valPlayCounter ); + qb.addReturnValue( QueryBuilder::tabStats, QueryBuilder::valScore ); + qb.addReturnValue( QueryBuilder::tabStats, QueryBuilder::valRating ); + qb.addMatch( QueryBuilder::tabStats, QueryBuilder::valURL, currentTrack.url().path() ); + values = qb.run(); + usleep( 10000 ); + + //making 2 tables is most probably not the cleanest way to do it, but it works. + QString albumImage = ContextBrowser::getEncodedImage( CollectionDB::instance()->albumImage( currentTrack, true, 1 ) ); + QString albumImageTitleAttr = albumImageTooltip( albumImage, 0 ); + + bool isCompilation = false; + if( !currentTrack.album().isEmpty() ) + { + isCompilation = CollectionDB::instance()->albumIsCompilation( + QString::number( CollectionDB::instance()->albumID( currentTrack.album() ) ) + ); + } + + m_HTMLSource.append( + "
\n" + "
\n" + // Show "Title - Artist \n Album", or only "PrettyTitle" if there's no title tag + + ( !currentTrack.title().isEmpty() + ? QStringx( + "%1 " + "- " + "%2\n" + "
\n" + "%3\n" + "
\n" + "\n" + "\n" + "\n" + "\n" ).arg( i18n( "Score: %1" ).arg( score ) ) + + "\n" + "\n" + "\n"; + + if( AmarokConfig::useRatings() ) + { + contents += QString( "\n" ).arg( i18n( "Rating: %1" ) + .arg( MetaBundle::ratingDescription( rating ) ) ) + + "\n" + "\n"; + } + + return table.arg( contents ); +} + +bool CurrentTrackJob::doJob() +{ + m_HTMLSource.append( "\n" + "\n" ); + + if( !b->m_browseArtists ) + { + if( !EngineController::engine()->loaded() ) + { + showHome(); + return true; + } + MetaBundle mb( m_currentTrack.url() ); + if( mb.podcastBundle() ) + { + showPodcast( mb ); + return true; + } + + if( m_currentTrack.url().protocol() == "lastfm" ) + { + showLastFm( m_currentTrack ); + return true; + } + + if( m_isStream && m_currentTrack.url().protocol() != "daap" ) + { + showStream( m_currentTrack ); + return true; + } + } + + QString artist; + if( b->m_browseArtists ) + { + artist = b->m_artist; + if( artist == m_currentTrack.artist() ) + { + b->m_browseArtists = false; + b->m_artist = QString::null; + b->m_contextBackHistory.clear(); + b->m_contextBackHistory.push_back( "current://track" ); + } + } + else + artist = m_currentTrack.artist(); + + const uint artist_id = CollectionDB::instance()->artistID( artist ); + const uint album_id = CollectionDB::instance()->albumID ( m_currentTrack.album() ); + QueryBuilder qb; + QStringList values; + if( b->m_browseArtists ) + showBrowseArtistHeader( artist ); + else if( b->m_browseLabels ) + { + showBrowseLabelHeader( b->m_label ); + showSongsWithLabel( b->m_label ); + m_HTMLSource.append( "\n" ); + + return true; + } + else + showCurrentArtistHeader( m_currentTrack ); + + if ( ContextBrowser::instance()->m_showLabels && !b->m_browseArtists ) + showUserLabels( m_currentTrack ); + + if( ContextBrowser::instance()->m_showRelated || ContextBrowser::instance()->m_showSuggested ) + { + QStringList relArtists = CollectionDB::instance()->similarArtists( artist, 10 ); + if ( !relArtists.isEmpty() ) + { + if( ContextBrowser::instance()->m_showRelated ) + showRelatedArtists( artist, relArtists ); + + if( ContextBrowser::instance()->m_showSuggested ) + showSuggestedSongs( relArtists ); + } + } + + QString artistName = artist.isEmpty() ? i18n( "This Artist" ) : artist ; + if ( !artist.isEmpty() ) + { + if( ContextBrowser::instance()->m_showFaves ) + showArtistsFaves( artistName, artist_id ); + + showArtistsAlbums( artist, artist_id, album_id ); + showArtistsCompilations( artist, artist_id, album_id ); + } + m_HTMLSource.append( "\n" ); + + return true; +} + + +void ContextBrowser::showIntroduction() +{ + if ( currentPage() != m_contextTab ) + { + blockSignals( true ); + showPage( m_contextTab ); + blockSignals( false ); + } + + // Do we have to rebuild the page? I don't care + m_HTMLSource = QString::null; + m_HTMLSource.append( + "\n" + "
\n" + "
\n" + "\n" + + i18n( "Hello Amarok user!" ) + + "\n" + "
\n" + "
\n" + "

\n" + + i18n( "This is the Context Browser: " + "it shows you contextual information about the currently playing track. " + "In order to use this feature of Amarok, you need to build a Collection." + ) + + "

\n" + "
\n" + "

\n" + "
\n" + "
\n" + "\n" + ); + + m_currentTrackPage->set( m_HTMLSource ); + saveHtmlData(); // Send html code to file +} + + +void ContextBrowser::showScanning() +{ + if ( currentPage() != m_contextTab ) + { + blockSignals( true ); + showPage( m_contextTab ); + blockSignals( false ); + } + + // Do we have to rebuild the page? I don't care + m_HTMLSource=""; + m_HTMLSource.append( + "\n" + "
\n" + "
\n" + "\n" + + i18n( "Building Collection Database..." ) + + "\n" + "
\n" + "
\n" + "

\n" + i18n( "Please be patient while Amarok scans your music collection. You can watch the progress of this activity in the statusbar." ) + "

\n" + "
\n" + "
\n" + "\n" + ); + + m_currentTrackPage->set( m_HTMLSource ); + saveHtmlData(); // Send html code to file +} + +QString +ContextBrowser::getEncodedImage( const QString &imageUrl ) +{ + // Embed cover image in html (encoded string), to get around khtml's caching + //debug() << "Encoding imageUrl: " << imageUrl << endl; + qApp->lock(); + const QImage img( imageUrl, "PNG" ); + qApp->unlock(); + QByteArray ba; + QBuffer buffer( ba ); + buffer.open( IO_WriteOnly ); + qApp->lock(); + img.save( &buffer, "PNG" ); // writes image into ba in PNG format + qApp->unlock(); + const QString coverImage = QString( "data:image/png;base64,%1" ).arg( KCodecs::base64Encode( ba ) ); + //debug() << "Encoded imageUrl: " << coverImage << endl; + return coverImage; +} + +////////////////////////////////////////////////////////////////////////////////////////// +// Lyrics-Tab +////////////////////////////////////////////////////////////////////////////////////////// + +void ContextBrowser::showLyrics( const QString &url ) +{ + #if 0 + if( BrowserBar::instance()->currentBrowser() != this ) + { + debug() << "current browser is not context, aborting showLyrics()" << endl; + m_dirtyLyricsPage = true; + return; + } + #endif + + DEBUG_BLOCK + + if ( currentPage() != m_lyricsTab ) + { + blockSignals( true ); + showPage( m_lyricsTab ); + blockSignals( false ); + } + if ( !m_dirtyLyricsPage ) return; + + QString lyrics = CollectionDB::instance()->getLyrics( EngineController::instance()->bundle().url().path() ); + // don't rely on caching for streams + const bool cached = !lyrics.isEmpty() && !EngineController::engine()->isStream(); + QString title = EngineController::instance()->bundle().title(); + QString artist = EngineController::instance()->bundle().artist(); + + if( title.contains("PREVIEW: buy it at www.magnatune.com", true) >= 1 ) + title = title.remove(" (PREVIEW: buy it at www.magnatune.com)"); + if( artist.contains("PREVIEW: buy it at www.magnatune.com", true) >= 1 ) + artist = artist.remove(" (PREVIEW: buy it at www.magnatune.com)"); + + if ( title.isEmpty() ) { + /* If title is empty, try to use pretty title. + The fact that it often (but not always) has artist name together, can be bad, + but at least the user will hopefully get nice suggestions. */ + QString prettyTitle = EngineController::instance()->bundle().prettyTitle(); + int h = prettyTitle.find( '-' ); + if ( h != -1 ) + { + title = prettyTitle.mid( h+1 ).stripWhiteSpace(); + if( title.contains("PREVIEW: buy it at www.magnatune.com", true) >= 1 ) + title = title.remove(" (PREVIEW: buy it at www.magnatune.com)"); + if ( artist.isEmpty() ) { + artist = prettyTitle.mid( 0, h ).stripWhiteSpace(); + if( artist.contains("PREVIEW: buy it at www.magnatune.com", true) >= 1 ) + artist = artist.remove(" (PREVIEW: buy it at www.magnatune.com)"); + } + + } + } + + m_lyricSearchUrl = QString( "http://www.google.com/search?ie=UTF-8&q=lyrics+%1+%2" ) + .arg( KURL::encode_string_no_slash( '"' + artist + '"', 106 /*utf-8*/ ), + KURL::encode_string_no_slash( '"' + title + '"', 106 /*utf-8*/ ) ); + + m_lyricsToolBar->getButton( LYRICS_BROWSER )->setEnabled(false); + + if( ( !cached || url == "reload" ) && !ScriptManager::instance()->lyricsScriptRunning() ) { + const QStringList scripts = ScriptManager::instance()->lyricsScripts(); + lyrics = + i18n( "Sorry, no lyrics script running.") + "
\n" + + "
\n"+ + i18n( "Available Lyrics Scripts:" ) + "
\n"; + foreach ( scripts ) { + lyrics += QString( "%2
\n" ).arg( *it, *it ); + } + lyrics += "
\n" + i18n( "Click on one of the scripts to run it, or use the Script Manager, to be able" + " to see all the scripts, and download new ones from the Web." ); + lyrics += "
\n" + "

\n"; + + m_HTMLSource = QString ( + "\n" + "
\n" + "
\n" + "\n" + + ( cached ? i18n( "Cached Lyrics" ) : i18n( "Lyrics" ) ) + + "\n" + "
\n" + "
\n" + + lyrics + + "
\n" + "
\n" + "\n" + ); + m_lyricsPage->set( m_HTMLSource ); + + m_dirtyLyricsPage = false; + saveHtmlData(); // Send html code to file + + return; + } + + if( cached && url.isEmpty() ) + { + lyricsResult( lyrics.utf8(), true ); + } + else + { + m_HTMLSource = QString ( + "\n" + "
\n" + "
\n" + "\n" + + i18n( "Fetching Lyrics" ) + + "\n" + "
\n" + "
\n" + "

\n" + i18n( "Fetching Lyrics..." ) + "

\n" + "
\n" + "
\n" + "\n" + ); + m_lyricsPage->set( m_HTMLSource ); + saveHtmlData(); // Send html code to file + + + if( url.isNull() || url == "reload" ) + ScriptManager::instance()->notifyFetchLyrics( artist, title ); + else + ScriptManager::instance()->notifyFetchLyricsByUrl( url ); + } +} + + +void +ContextBrowser::lyricsResult( QCString cXmlDoc, bool cached ) //SLOT +{ + QDomDocument doc; + QString xmldoc = QString::fromUtf8( cXmlDoc ); + if( !doc.setContent( xmldoc ) ) + { + m_HTMLSource=""; + m_HTMLSource.append( + "\n" + "
\n" + "
\n" + "\n" + + i18n( "Error" ) + + "\n" + "
\n" + "

\n" + + i18n( "Lyrics could not be retrieved because the server was not reachable." ) + + "

\n" + "
\n" + "\n" + ); + m_lyricsPage->set( m_HTMLSource ); + saveHtmlData(); // Send html code to file + + m_dirtyLyricsPage = false; + + return; + } + + QString lyrics; + + QDomElement el = doc.documentElement(); + m_lyricCurrentUrl = el.attribute( "page_url" ); + + ScriptManager* const sm = ScriptManager::instance(); + KConfig spec( sm->specForScript( sm->lyricsScriptRunning() ), true, false ); + spec.setGroup( "Lyrics" ); + + if ( el.attribute( "add_url" ).isEmpty() ) + { + m_lyricAddUrl = spec.readPathEntry( "add_url" ); + m_lyricAddUrl.replace( "MAGIC_ARTIST", KURL::encode_string_no_slash( EngineController::instance()->bundle().artist() ) ); + m_lyricAddUrl.replace( "MAGIC_TITLE", KURL::encode_string_no_slash( EngineController::instance()->bundle().title() ) ); + m_lyricAddUrl.replace( "MAGIC_ALBUM", KURL::encode_string_no_slash( EngineController::instance()->bundle().album() ) ); + m_lyricAddUrl.replace( "MAGIC_YEAR", KURL::encode_string_no_slash( QString::number( EngineController::instance()->bundle().year() ) ) ); + } + else + m_lyricAddUrl = el.attribute( "add_url" ); + + if ( el.tagName() == "suggestions" ) + { + + + const QDomNodeList l = doc.elementsByTagName( "suggestion" ); + + if( l.length() ==0 ) + { + lyrics = i18n( "Lyrics for track not found" ); + } + else + { + lyrics = i18n( "Lyrics for track not found, here are some suggestions:" ) + "

\n"; + for( uint i = 0; i < l.length(); ++i ) { + const QString url = l.item( i ).toElement().attribute( "url" ); + const QString artist = l.item( i ).toElement().attribute( "artist" ); + const QString title = l.item( i ).toElement().attribute( "title" ); + + lyrics += "\n" + i18n("%1 - %2").arg( artist, title ); + lyrics += "
\n"; + } + } + lyrics += i18n( "

You can search for the lyrics on the Web.

" ) + .arg( QString( m_lyricSearchUrl ).replace( QRegExp( "^http:" ), "externalurl:" ) ); + } + else { + lyrics = el.text(); + lyrics.replace( "\n", "
\n" ); // Plaintext -> HTML + + const QString title = el.attribute( "title" ); + const QString artist = el.attribute( "artist" ); + const QString site = el.attribute( "site" ).isEmpty() ? spec.readEntry( "site" ) : el.attribute( "site" ); + const QString site_url = el.attribute( "site_url" ).isEmpty() ? spec.readEntry( "site_url" ) : el.attribute( "site_url" ); + + lyrics.prepend( "\n" + title + "
\n" + artist+ "

\n" ); + + if( !cached ) { + lyrics.append( "

\n" + i18n( "Powered by %1 (%2)" ).arg( site, site_url ) + "\n" ); + CollectionDB::instance()->setLyrics( EngineController::instance()->bundle().url().path(), xmldoc, EngineController::instance()->bundle().uniqueId() ); + } + } + + m_HTMLSource=""; + m_HTMLSource.append( + "\n" + "
\n" + "
\n" + "\n" + + ( cached ? i18n( "Cached Lyrics" ) : i18n( "Lyrics" ) ) + + "\n" + "
\n" + "
\n" + + lyrics + + "
\n" + "
\n" + "\n" + ); + + + m_lyricsPage->set( m_HTMLSource ); + //Reset scroll + m_lyricsPage->view()->setContentsPos(0, 0); + saveHtmlData(); // Send html code to file + + m_lyricsToolBar->getButton( LYRICS_BROWSER )->setEnabled( !m_lyricCurrentUrl.isEmpty() ); + m_dirtyLyricsPage = false; +} + + +void +ContextBrowser::lyricsExternalPage() //SLOT +{ + Amarok::invokeBrowser( m_lyricCurrentUrl ); +} + + +void +ContextBrowser::lyricsAdd() //SLOT +{ + Amarok::invokeBrowser( m_lyricAddUrl ); +} + +void +ContextBrowser::lyricsEditToggle() //SLOT +{ + if ( m_lyricsToolBar->getButton( LYRICS_EDIT )->isOn() ) + { + m_lyricsBeingEditedUrl = EngineController::instance()->bundle().url().path(); + m_lyricsBeingEditedArtist = EngineController::instance()->bundle().artist(); + m_lyricsBeingEditedTitle = EngineController::instance()->bundle().title(); + QString xml = CollectionDB::instance()->getLyrics( m_lyricsBeingEditedUrl ), lyrics; + QDomDocument doc; + if( doc.setContent( xml ) ) + lyrics = doc.documentElement().text(); + else + lyrics = QString::null; + m_lyricsTextEdit->setText( lyrics ); + m_lyricsPage->hide(); + m_lyricsTextEdit->show(); + } + else + { + m_lyricsTextEdit->hide(); + + QDomDocument doc; + QDomElement e = doc.createElement( "lyrics" ); + e.setAttribute( "artist", m_lyricsBeingEditedArtist ); + e.setAttribute( "title", m_lyricsBeingEditedTitle ); + QDomText t = doc.createTextNode( m_lyricsTextEdit->text() ); + e.appendChild( t ); + doc.appendChild( e ); + CollectionDB::instance()->setLyrics( m_lyricsBeingEditedUrl, doc.toString(), CollectionDB::instance()->uniqueIdFromUrl( KURL( m_lyricsBeingEditedUrl) ) ); + m_lyricsPage->show(); + lyricsChanged( m_lyricsBeingEditedUrl ); + } +} + +void +ContextBrowser::lyricsSearch() //SLOT +{ + Amarok::invokeBrowser( m_lyricSearchUrl ); +} + + +void +ContextBrowser::lyricsRefresh() //SLOT +{ + m_dirtyLyricsPage = true; + showLyrics( "reload" ); +} + +void +ContextBrowser::lyricsSearchText(QString const &text) //SLOT +{ + m_lyricsPage->findText( text, 0 ); + lyricsSearchTextNext(); +} + +void +ContextBrowser::lyricsSearchTextNext() //SLOT +{ + m_lyricsPage->findTextNext(); +} + +void +ContextBrowser::lyricsSearchTextShow() //SLOT +{ + m_lyricsSearchText->setEnabled( true ); + m_lyricsTextBar->show(); + m_lyricsTextBarShowed = true; + m_lyricsSearchText->setFocus(); +} + + +void +ContextBrowser::lyricsSearchTextHide() //SLOT +{ + m_lyricsSearchText->setText(""); + m_lyricsSearchText->setEnabled( false ); + m_lyricsTextBar->hide(); + m_lyricsTextBarShowed=false; +} + + +void +ContextBrowser::lyricsSearchTextToggle() //SLOT +{ + if ( m_lyricsTextBarShowed ) + { + lyricsSearchTextHide(); + } + else + { + lyricsSearchTextShow(); + } +} + +// Wikipedia-Tab +////////////////////////////////////////////////////////////////////////////////////////// + +QString +ContextBrowser::wikiArtistPostfix() const +{ + if( wikiLocale() == "en" ) + return " (band)"; + else if( wikiLocale() == "de" ) + return " (Band)"; + else + return ""; +} + +QString +ContextBrowser::wikiAlbumPostfix() const +{ + if( wikiLocale() == "en" ) + return " (album)"; + else + return ""; +} + +QString +ContextBrowser::wikiTrackPostfix() const +{ + if( wikiLocale() == "en" ) + return " (song)"; + else + return ""; +} + +void +ContextBrowser::wikiConfigChanged( int /*activeItem*/ ) // SLOT +{ + // keep in sync with localeList in wikiConfig + QString text = m_wikiLocaleCombo->currentText(); + + m_wikiLocaleEdit->setEnabled( text == i18n("Other...") ); + + if( text == i18n("English") ) + m_wikiLocaleEdit->setText( "en" ); + + else if( text == i18n("German") ) + m_wikiLocaleEdit->setText( "de" ); + + else if( text == i18n("French") ) + m_wikiLocaleEdit->setText( "fr" ); + + else if( text == i18n("Polish") ) + m_wikiLocaleEdit->setText( "pl" ); + + else if( text == i18n("Japanese") ) + m_wikiLocaleEdit->setText( "ja" ); + + else if( text == i18n("Spanish") ) + m_wikiLocaleEdit->setText( "es" ); +} + +void +ContextBrowser::wikiConfigApply() // SLOT +{ + const bool changed = m_wikiLocaleEdit->text() != wikiLocale(); + setWikiLocale( m_wikiLocaleEdit->text() ); + + if ( changed && currentPage() == m_wikiTab && !m_wikiCurrentEntry.isNull() ) + { + m_dirtyWikiPage = true; + showWikipediaEntry( m_wikiCurrentEntry ); + } + + showWikipedia(); +} + +void +ContextBrowser::wikiConfig() // SLOT +{ + QStringList localeList; + localeList + << i18n( "English" ) + << i18n( "German" ) + << i18n( "French" ) + << i18n( "Polish" ) + << i18n( "Japanese" ) + << i18n( "Spanish" ) + << i18n( "Other..." ); + + int index; + + if( wikiLocale() == "en" ) + index = 0; + else if( wikiLocale() == "de" ) + index = 1; + else if( wikiLocale() == "fr" ) + index = 2; + else if( wikiLocale() == "pl" ) + index = 3; + else if( wikiLocale() == "ja" ) + index = 4; + else if( wikiLocale() == "es" ) + index = 5; + else // other + index = 6; + + m_wikiConfigDialog = new KDialogBase( this, 0, true, 0, KDialogBase::Ok|KDialogBase::Apply|KDialogBase::Cancel ); + kapp->setTopWidget( m_wikiConfigDialog ); + m_wikiConfigDialog->setCaption( kapp->makeStdCaption( i18n( "Wikipedia Locale" ) ) ); + QVBox *box = m_wikiConfigDialog->makeVBoxMainWidget(); + + m_wikiLocaleCombo = new QComboBox( box ); + m_wikiLocaleCombo->insertStringList( localeList ); + + QHBox *hbox = new QHBox( box ); + QLabel *otherLabel = new QLabel( i18n( "Locale: " ), hbox ); + m_wikiLocaleEdit = new QLineEdit( "en", hbox ); + + otherLabel->setBuddy( m_wikiLocaleEdit ); + QToolTip::add( m_wikiLocaleEdit, i18n( "2-letter language code for your Wikipedia locale" ) ); + + connect( m_wikiLocaleCombo, SIGNAL( activated(int) ), SLOT( wikiConfigChanged(int) ) ); + connect( m_wikiConfigDialog, SIGNAL( applyClicked() ), SLOT( wikiConfigApply() ) ); + + m_wikiLocaleEdit->setText( wikiLocale() ); + m_wikiLocaleCombo->setCurrentItem( index ); + wikiConfigChanged( index ); // a little redundant, but saves ugly code, and ensures the lineedit enabled status is correct + + m_wikiConfigDialog->setInitialSize( QSize( 240, 100 ) ); + const int result = m_wikiConfigDialog->exec(); + + + if( result == QDialog::Accepted ) + wikiConfigApply(); + + delete m_wikiConfigDialog; +} + +QString +ContextBrowser::wikiLocale() +{ + if( s_wikiLocale.isEmpty() ) + return QString( "en" ); + + return s_wikiLocale; +} + +void +ContextBrowser::setWikiLocale( const QString &locale ) +{ + AmarokConfig::setWikipediaLocale( locale ); + s_wikiLocale = locale; +} + +QString +ContextBrowser::wikiURL( const QString &item ) +{ + // add any special characters to be replaced here + QString wStr = QString(item).replace( "/", " " ); + + return QString( "http://%1.wikipedia.org/wiki/" ).arg( wikiLocale() ) + + KURL::encode_string_no_slash( wStr, 106 /*utf-8*/ ); +} + +void +ContextBrowser::reloadWikipedia() +{ + m_wikiJob = NULL; + showWikipediaEntry( m_wikiCurrentEntry, true ); +} + +void +ContextBrowser::showWikipediaEntry( const QString &entry, bool replaceHistory ) +{ + m_wikiCurrentEntry = entry; + showWikipedia( wikiURL( entry ), false, replaceHistory ); +} + +void +ContextBrowser::showLabelsDialog() +{ + DEBUG_BLOCK + KURL currentUrl = EngineController::instance()->bundle().url(); + QStringList allLabels = CollectionDB::instance()->labelList(); + QStringList trackLabels = CollectionDB::instance()->getLabels( currentUrl.path(), CollectionDB::typeUser ); + debug() << "Showing add label dialog" << endl; + KDialogBase *dialog = new KDialogBase( this, 0, false, QString::null, KDialogBase::Ok|KDialogBase::Cancel ); + dialog->makeVBoxMainWidget(); + + QLabel *labelText = new QLabel( i18n( + "

Add a new label in the field below and press Enter, or choose labels from the list

"), + dialog->mainWidget() ); + m_addLabelEdit = new ClickLineEdit( i18n( "Add new label" ), dialog->mainWidget() ); + m_addLabelEdit->installEventFilter( this ); + m_addLabelEdit->setFrame( QFrame::Sunken ); + QToolTip::add( m_addLabelEdit, i18n( "Enter a new label and press Return to add it" ) ); + dialog->setFocusProxy( m_addLabelEdit ); + labelText->setBuddy( m_addLabelEdit ); + + m_labelListView = new QListView( dialog->mainWidget() ); + m_labelListView->addColumn( i18n( "Label" ) ); + m_labelListView->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Expanding ); + m_labelListView->setColumnWidthMode( 0, QListView::Maximum ); + + foreach( allLabels ) + { + QCheckListItem *item = new QCheckListItem( m_labelListView, *it, QCheckListItem::CheckBox ); + item->setOn( trackLabels.contains( *it ) ); + } + if( dialog->exec() == QDialog::Accepted ) + { + debug() << "Dialog closed, updating labels" << endl; + QStringList newTrackLabels; + QListViewItemIterator iter( m_labelListView ); + while( iter.current() ) + { + QCheckListItem *item = static_cast( iter.current() ); + if( item->isOn() ) + newTrackLabels.append( item->text() ); + iter++; + } + CollectionDB::instance()->setLabels( currentUrl.path(), + newTrackLabels, + CollectionDB::instance()->uniqueIdFromUrl( currentUrl ), + CollectionDB::typeUser ); + CollectionDB::instance()->cleanLabels(); + if( newTrackLabels != trackLabels + && currentUrl == EngineController::instance()->bundle().url() ) + { + m_dirtyCurrentTrackPage = true; + showCurrentTrack(); + } + } + delete dialog; //deletes children + m_addLabelEdit = 0; + m_labelListView = 0; +} + +bool +ContextBrowser::eventFilter( QObject *o, QEvent *e ) +{ + switch( e->type() ) + { + case 6/*QEvent::KeyPress*/: + #define e static_cast(e) + + if( o == m_addLabelEdit ) //the add label lineedit + { + switch( e->key() ) + { + case Key_Return: + case Key_Enter: + { + QCheckListItem *item = new QCheckListItem( m_labelListView, m_addLabelEdit->text(), QCheckListItem::CheckBox ); + item->setOn( true ); + m_addLabelEdit->setText( QString() ); + return true; + } + + default: + return false; + } + } + if (o == m_lyricsSearchText) + { + switch ( e->key() ) + { + case Key_Escape: + { + lyricsSearchTextHide(); + return true; + } + default: + return false; + } + } + + default: + break; + } + + return KTabWidget::eventFilter( o, e ); +} + +void ContextBrowser::showWikipedia( const QString &url, bool fromHistory, bool replaceHistory ) +{ + #if 0 + if( BrowserBar::instance()->currentBrowser() != this ) + { + debug() << "current browser is not context, aborting showWikipedia()" << endl; + m_dirtyWikiPage = true; + return; + } + #endif + + if ( currentPage() != m_wikiTab ) + { + blockSignals( true ); + showPage( m_wikiTab ); + blockSignals( false ); + } + if ( !m_dirtyWikiPage || m_wikiJob ) return; + + // Disable the Open in a Browser button, because while loading it would open wikipedia main page. + m_wikiToolBar->setItemEnabled( WIKI_BROWSER, false ); + + m_HTMLSource=""; + m_HTMLSource.append( + "\n" + "
\n" + "
\n" + "\n" + + i18n( "Wikipedia" ) + + "\n" + "
\n" + "
\n" + "

\n" + i18n( "Fetching Wikipedia Information" ) + " ...

\n" + "
\n" + "
\n" + "\n" + ); + + m_wikiPage->set( m_HTMLSource ); + saveHtmlData(); // Send html code to file + + if ( url.isEmpty() ) + { + QString tmpWikiStr; + + if ( (EngineController::instance()->bundle().url().protocol() == "lastfm") || + (EngineController::instance()->bundle().url().protocol() == "daap") || + !EngineController::engine()->isStream() ) + { + if ( !EngineController::instance()->bundle().artist().isEmpty() ) + { + tmpWikiStr = EngineController::instance()->bundle().artist(); + tmpWikiStr += wikiArtistPostfix(); + } + else if ( !EngineController::instance()->bundle().title().isEmpty() ) + { + tmpWikiStr = EngineController::instance()->bundle().title(); + } + else + { + tmpWikiStr = EngineController::instance()->bundle().prettyTitle(); + } + } + else + { + tmpWikiStr = EngineController::instance()->bundle().prettyTitle(); + } + + //Hack to make wiki searches work with magnatune preview tracks + if (tmpWikiStr.contains( "PREVIEW: buy it at www.magnatune.com" ) >= 1 ) { + tmpWikiStr = tmpWikiStr.remove(" (PREVIEW: buy it at www.magnatune.com)" ); + int index = tmpWikiStr.find( '-' ); + if ( index != -1 ) { + tmpWikiStr = tmpWikiStr.left (index - 1); + } + + } + m_wikiCurrentEntry = tmpWikiStr; + + m_wikiCurrentUrl = wikiURL( tmpWikiStr ); + } + else + { + m_wikiCurrentUrl = url; + } + + // Append new URL to history + if ( replaceHistory ) + { + m_wikiBackHistory.back() = m_wikiCurrentUrl; + } + else if ( !fromHistory ) { + m_wikiBackHistory += m_wikiCurrentUrl; + m_wikiForwardHistory.clear(); + } + // Limit number of items in history + if ( m_wikiBackHistory.count() > WIKI_MAX_HISTORY ) + m_wikiBackHistory.pop_front(); + + // Remove all items from the button-menus + m_wikiBackPopup->clear(); + m_wikiForwardPopup->clear(); + + // Populate button menus with URLs from the history + QStringList::ConstIterator it; + uint count; + // Reverse iterate over both lists + count = m_wikiBackHistory.count()-1; + it = m_wikiBackHistory.fromLast(); + if( count > 0 ) + it--; + for ( uint i=0; iinsertItem( SmallIconSet( "wiki" ), *it, i ); + count = m_wikiForwardHistory.count(); + it = m_wikiForwardHistory.fromLast(); + for ( uint i=0; iinsertItem( SmallIconSet( "wiki" ), *it, i ); + + m_wikiToolBar->setItemEnabled( WIKI_BACK, m_wikiBackHistory.size() > 1 ); + m_wikiToolBar->setItemEnabled( WIKI_FORWARD, m_wikiForwardHistory.size() > 0 ); + + m_wikiBaseUrl = m_wikiCurrentUrl.mid(0 , m_wikiCurrentUrl.find("wiki/")); + m_wikiJob = KIO::storedGet( m_wikiCurrentUrl, false, false ); + + Amarok::StatusBar::instance()->newProgressOperation( m_wikiJob ) + .setDescription( i18n( "Fetching Wikipedia Information" ) ); + + connect( m_wikiJob, SIGNAL( result( KIO::Job* ) ), SLOT( wikiResult( KIO::Job* ) ) ); +} + + +void +ContextBrowser::wikiHistoryBack() //SLOT +{ + //Disable the button as history may be empty. Reenabled later by showWikipedia. + m_wikiToolBar->setItemEnabled( WIKI_BACK, false ); + m_wikiToolBar->setItemEnabled( WIKI_FORWARD, false ); + + m_wikiForwardHistory += m_wikiBackHistory.last(); + m_wikiBackHistory.pop_back(); + + m_dirtyWikiPage = true; + m_wikiCurrentEntry = QString::null; + showWikipedia( m_wikiBackHistory.last(), true ); +} + + +void +ContextBrowser::wikiHistoryForward() //SLOT +{ + //Disable the button as history may be empty. Reenabled later by showWikipedia. + m_wikiToolBar->setItemEnabled( WIKI_FORWARD, false ); + m_wikiToolBar->setItemEnabled( WIKI_BACK, false ); + + m_wikiBackHistory += m_wikiForwardHistory.last(); + m_wikiForwardHistory.pop_back(); + + m_dirtyWikiPage = true; + m_wikiCurrentEntry = QString::null; + showWikipedia( m_wikiBackHistory.last(), true ); +} + + +void +ContextBrowser::wikiBackPopupActivated( int id ) //SLOT +{ + do + { + m_wikiForwardHistory += m_wikiBackHistory.last(); + m_wikiBackHistory.pop_back(); + if ( m_wikiForwardHistory.count() > WIKI_MAX_HISTORY ) + m_wikiForwardHistory.pop_front(); + id--; + } while( id >= 0 ); + + m_dirtyWikiPage = true; + m_wikiCurrentEntry = QString::null; + showWikipedia( m_wikiBackHistory.last(), true ); +} + +void +ContextBrowser::wikiForwardPopupActivated( int id ) //SLOT +{ + do + { + m_wikiBackHistory += m_wikiForwardHistory.last(); + m_wikiForwardHistory.pop_back(); + if ( m_wikiBackHistory.count() > WIKI_MAX_HISTORY ) + m_wikiBackHistory.pop_front(); + + id--; + } while( id >= 0 ); + + m_dirtyWikiPage = true; + m_wikiCurrentEntry = QString::null; + showWikipedia( m_wikiBackHistory.last(), true ); +} + + +void +ContextBrowser::wikiArtistPage() //SLOT +{ + m_dirtyWikiPage = true; + showWikipedia(); // Will fall back to title, if artist is empty(streams!). +} + + +void +ContextBrowser::wikiAlbumPage() //SLOT +{ + m_dirtyWikiPage = true; + showWikipediaEntry( EngineController::instance()->bundle().album() + wikiAlbumPostfix() ); +} + + +void +ContextBrowser::wikiTitlePage() //SLOT +{ + m_dirtyWikiPage = true; + showWikipediaEntry( EngineController::instance()->bundle().title() + wikiTrackPostfix() ); +} + + +void +ContextBrowser::wikiExternalPage() //SLOT +{ + Amarok::invokeBrowser( m_wikiCurrentUrl ); +} + + +void +ContextBrowser::wikiResult( KIO::Job* job ) //SLOT +{ + DEBUG_BLOCK + + if ( !job->error() == 0 ) + { + m_HTMLSource=""; + m_HTMLSource.append( + "
\n" + "
\n" + "\n" + + i18n( "Error" ) + + "\n" + "
\n" + "

\n" + + i18n( "Artist information could not be retrieved because the server was not reachable." ) + + "

\n" + "
\n" + ); + m_wikiPage->set( m_HTMLSource ); + + m_dirtyWikiPage = false; + //m_wikiPage = NULL; // FIXME: what for? leads to crashes + saveHtmlData(); // Send html code to file + + warning() << "[WikiFetcher] KIO error! errno: " << job->error() << endl; + return; + } + if ( job != m_wikiJob ) + return; //not the right job, so let's ignore it + + KIO::StoredTransferJob* const storedJob = static_cast( job ); + m_wiki = QString( storedJob->data() ); + + // Enable the Open in a Brower button, Disabled while loading, guz it would open wikipedia main page. + m_wikiToolBar->setItemEnabled( WIKI_BROWSER, true ); + + // FIXME: Get a safer Regexp here, to match only inside of at least. + if ( m_wiki.contains( "charset=utf-8" ) ) { + m_wiki = QString::fromUtf8( storedJob->data().data(), storedJob->data().size() ); + } + + if( m_wiki.find( "var wgArticleId = 0" ) != -1 ) + { + debug() << "Article not found." << endl; + + // article was not found + if( !wikiArtistPostfix().isEmpty() && m_wikiCurrentEntry.endsWith( wikiArtistPostfix() ) ) + { + m_wikiCurrentEntry = m_wikiCurrentEntry.left( m_wikiCurrentEntry.length() - wikiArtistPostfix().length() ); + reloadWikipedia(); + return; + } + else if( !wikiAlbumPostfix().isEmpty() && m_wikiCurrentEntry.endsWith( wikiAlbumPostfix() ) ) + { + m_wikiCurrentEntry = m_wikiCurrentEntry.left( m_wikiCurrentEntry.length() - wikiAlbumPostfix().length() ); + reloadWikipedia(); + return; + } + else if( !wikiTrackPostfix().isEmpty() && m_wikiCurrentEntry.endsWith( wikiTrackPostfix() ) ) + { + m_wikiCurrentEntry = m_wikiCurrentEntry.left( m_wikiCurrentEntry.length() - wikiTrackPostfix().length() ); + reloadWikipedia(); + return; + } + } + + //remove the new-lines and tabs(replace with spaces IS needed). + m_wiki.replace( "\n", " " ); + m_wiki.replace( "\t", " " ); + + m_wikiLanguages = QString::null; + // Get the available language list + if ( m_wiki.find("
") != -1 ) + { + m_wikiLanguages = m_wiki.mid( m_wiki.find("
") ); + m_wikiLanguages = m_wikiLanguages.mid( m_wikiLanguages.find("
    ") ); + m_wikiLanguages = m_wikiLanguages.mid( 0, m_wikiLanguages.find( "
" ) ); + } + + QString copyright; + QString copyrightMark = "
  • "; + if ( m_wiki.find( copyrightMark ) != -1 ) + { + copyright = m_wiki.mid( m_wiki.find(copyrightMark) + copyrightMark.length() ); + copyright = copyright.mid( 0, copyright.find( "
  • " ) ); + copyright.replace( "
    ", QString::null ); + //only one br at the beginning + copyright.prepend( "
    " ); + } + + // Ok lets remove the top and bottom parts of the page + m_wiki = m_wiki.mid( m_wiki.find( "

    " ) ); + m_wiki = m_wiki.mid( 0, m_wiki.find( "
    " ) ); + // Adding back license information + m_wiki += copyright; + m_wiki.append( "
    " ); + m_wiki.replace( QRegExp("

    [^<]*

    "), QString::null ); + + m_wiki.replace( QRegExp( "]*>[^<]*<[^>]*>[^<]*<[^>]*>[^<]*" ), QString::null ); + + m_wiki.replace( QRegExp( "]*>([^<]*)" ), "\\1" ); + + // Remove anything inside of a class called urlexpansion, as it's pointless for us + m_wiki.replace( QRegExp( "[^(]*[(][^)]*[)]" ), QString::null ); + + // Remove hidden table rows as well + QRegExp hidden( "
    .*", false ); + hidden.setMinimal( true ); //greedy behaviour wouldn't be any good! + m_wiki.replace( hidden, QString::null ); + + // we want to keep our own style (we need to modify the stylesheet a bit to handle things nicely) + m_wiki.replace( QRegExp( "style= *\"[^\"]*\"" ), QString::null ); + m_wiki.replace( QRegExp( "class= *\"[^\"]*\"" ), QString::null ); + // let's remove the form elements, we don't want them. + m_wiki.replace( QRegExp( "]*>" ), QString::null ); + m_wiki.replace( QRegExp( "]*>" ), QString::null ); + m_wiki.replace( "\n" , QString::null ); + m_wiki.replace( QRegExp( "]*>" ), QString::null ); + m_wiki.replace( "\n" , QString::null ); + m_wiki.replace( QRegExp( "]*>" ), QString::null ); + m_wiki.replace( "" , QString::null ); + + //first we convert all the links with protocol to external, as they should all be External Links. + m_wiki.replace( QRegExp( "href= *\"http:" ), "href=\"externalurl:" ); + m_wiki.replace( QRegExp( "href= *\"/" ), "href=\"" +m_wikiBaseUrl ); + m_wiki.replace( QRegExp( "href= *\"#" ), "href=\"" +m_wikiCurrentUrl + '#' ); + + m_HTMLSource = "\n"; + m_HTMLSource.append( + "
    \n" + "
    \n" + "\n" + + i18n( "Wikipedia Information" ) + + "\n" + "
    \n" + "
    \n" + + m_wiki + + "
    \n" + "
    \n" + ); + if ( !m_wikiLanguages.isEmpty() ) + { + m_HTMLSource.append( + "
    \n" + "
    \n" + "\n" + + i18n( "Wikipedia Other Languages" ) + + "\n" + "
    \n" + "
    \n" + + m_wikiLanguages + + "
    \n" + "
    \n" + ); + } + m_HTMLSource.append( "\n" ); + m_wikiPage->set( m_HTMLSource ); + + m_dirtyWikiPage = false; + saveHtmlData(); // Send html code to file + m_wikiJob = NULL; +} + + +void +ContextBrowser::coverFetched( const QString &artist, const QString &album ) //SLOT +{ + if ( currentPage() == m_contextTab && + !EngineController::engine()->loaded() && + !m_browseArtists ) + { + m_dirtyCurrentTrackPage = true; + if( m_shownAlbums.contains( album ) ) + showCurrentTrack(); + return; + } + + const MetaBundle ¤tTrack = EngineController::instance()->bundle(); + if ( currentTrack.artist().isEmpty() && currentTrack.album().isEmpty() ) + return; + + if ( currentPage() == m_contextTab && + ( currentTrack.artist().string() == artist || m_artist == artist || currentTrack.album().string() == album ) ) // this is for compilations or when artist is empty + { + m_dirtyCurrentTrackPage = true; + showCurrentTrack(); + } +} + + +void +ContextBrowser::coverRemoved( const QString &artist, const QString &album ) //SLOT +{ + if ( currentPage() == m_contextTab && + !EngineController::engine()->loaded() && + !m_browseArtists ) + { + m_dirtyCurrentTrackPage = true; + if( m_shownAlbums.contains( album ) ) + showCurrentTrack(); + return; + } + + const MetaBundle ¤tTrack = EngineController::instance()->bundle(); + if ( currentTrack.artist().isEmpty() && currentTrack.album().isEmpty() && m_artist.isNull() ) + return; + + if ( currentPage() == m_contextTab && + ( currentTrack.artist().string() == artist || m_artist == artist || currentTrack.album().string() == album ) ) // this is for compilations or when artist is empty + { + m_dirtyCurrentTrackPage = true; + showCurrentTrack(); + } +} + + +void +ContextBrowser::similarArtistsFetched( const QString &artist ) //SLOT +{ + if( artist == m_artist || EngineController::instance()->bundle().artist().string() == artist ) { + m_dirtyCurrentTrackPage = true; + if ( currentPage() == m_contextTab ) + showCurrentTrack(); + } +} + +void +ContextBrowser::imageFetched( const QString &url ) //SLOT +{ + const MetaBundle ¤tTrack = EngineController::instance()->bundle(); + PodcastEpisodeBundle peb; + if( CollectionDB::instance()->getPodcastEpisodeBundle( currentTrack.url(), &peb ) ) + { + PodcastChannelBundle pcb; + if( CollectionDB::instance()->getPodcastChannelBundle( peb.parent(), &pcb ) ) + { + if( pcb.imageURL().url() == url ) + { + m_dirtyCurrentTrackPage = true; + showCurrentTrack(); + } + } + } +} + +void ContextBrowser::ratingOrScoreOrLabelsChanged( const QString &path ) //SLOT +{ + const MetaBundle ¤tTrack = EngineController::instance()->bundle(); + + //Always refresh if using ratings, otherwise suggested songs and other songs by artist that + //have their ratings changed in the playlist won't be reflected until the context browser refreshes + //which can be confusing, and looks less polished/professional + //This can be changed if it slows things down too much... + if( m_browseLabels || ( currentTrack.isFile() && ( currentTrack.url().path() == path || AmarokConfig::useRatings() ) ) ) + m_dirtyCurrentTrackPage = true; // will be reloaded when viewed (much faster) + + if( currentPage() == m_contextTab ) + refreshCurrentTrackPage(); +} + +void ContextBrowser::tagsChanged( const MetaBundle &bundle ) //SLOT +{ + const MetaBundle ¤tTrack = EngineController::instance()->bundle(); + + if( !m_shownAlbums.contains( bundle.album() ) && m_artist != bundle.artist() ) + { + if( currentTrack.artist().isEmpty() && currentTrack.album().isEmpty() ) + return; + + if( bundle.artist() != currentTrack.artist() && bundle.album() != currentTrack.album() ) + return; + } + + refreshCurrentTrackPage(); +} + +void ContextBrowser::tagsChanged( const QString &oldArtist, const QString &oldAlbum ) //SLOT +{ + const MetaBundle ¤tTrack = EngineController::instance()->bundle(); + + if( !m_shownAlbums.contains( oldAlbum ) && m_artist != oldArtist ) + { + if( currentTrack.artist().isEmpty() && currentTrack.album().isEmpty() ) + return; + + if( oldArtist != currentTrack.artist() && oldAlbum != currentTrack.album() ) + return; + } + + refreshCurrentTrackPage(); +} + + +void ContextBrowser::refreshCurrentTrackPage() //SLOT +{ + if ( currentPage() == m_contextTab ) // this is for compilations or when artist is empty + { + m_dirtyCurrentTrackPage = true; + showCurrentTrack(); + } +} + + +bool +ContextBrowser::hasContextProtocol( const KURL &url ) +{ + QString protocol = url.protocol(); + return protocol == "album" + || protocol == "artist" + || protocol == "stream" + || protocol == "compilation" + || protocol == "albumdisc" + || protocol == "compilationdisc" + || protocol == "fetchcover"; +} + +KURL::List +ContextBrowser::expandURL( const KURL &url ) +{ + KURL::List urls; + QString protocol = url.protocol(); + + if( protocol == "artist" ) { + uint artist_id = CollectionDB::instance()->artistID( url.path(), false ); + if( artist_id ) + { + QStringList trackUrls = CollectionDB::instance()->artistTracks( QString::number( artist_id ) ); + foreach( trackUrls ) + urls += KURL::fromPathOrURL( *it ); + } + } + else if( protocol == "album" ) { + QString artist, album, track; // track unused here + Amarok::albumArtistTrackFromUrl( url.path(), artist, album, track ); + + QStringList trackUrls = CollectionDB::instance()->albumTracks( artist, album ); + foreach( trackUrls ) { + urls += KURL::fromPathOrURL( *it ); + } + } + else if( protocol == "albumdisc" ) { + QString artist, album, discnumber; // discnumber is returned in track number field + Amarok::albumArtistTrackFromUrl( url.path(), artist, album, discnumber ); + + QStringList trackUrls = CollectionDB::instance()->albumDiscTracks( artist, album, discnumber ); + foreach( trackUrls ) { + urls += KURL::fromPathOrURL( *it ); + } + } + else if( protocol == "compilation" ) { + QueryBuilder qb; + qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valURL ); + qb.addMatch( QueryBuilder::tabSong, QueryBuilder::valAlbumID, url.path() ); + qb.sortBy( QueryBuilder::tabSong, QueryBuilder::valDiscNumber ); + qb.sortBy( QueryBuilder::tabSong, QueryBuilder::valTrack ); + qb.setOptions( QueryBuilder::optOnlyCompilations ); + QStringList values = qb.run(); + + for( QStringList::ConstIterator it = values.begin(), end = values.end(); it != end; ++it ) { + urls += KURL::fromPathOrURL( *it ); + } + } + else if( protocol == "compilationdisc") { + QString artist, album, discnumber; // artist is unused + Amarok::albumArtistTrackFromUrl( url.path(), artist, album, discnumber ); + + QueryBuilder qb; + qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valURL ); + qb.addMatch( QueryBuilder::tabSong, QueryBuilder::valAlbumID, album ); + qb.addMatch( QueryBuilder::tabSong, QueryBuilder::valDiscNumber, discnumber ); + qb.sortBy( QueryBuilder::tabSong, QueryBuilder::valTrack ); + qb.setOptions( QueryBuilder::optOnlyCompilations ); + QStringList values = qb.run(); + + for( QStringList::ConstIterator it = values.begin(), end = values.end(); it != end; ++it ) { + urls += KURL::fromPathOrURL( *it ); + } + } + + else if( protocol == "fetchcover" ) { + QString artist, album, track; // track unused here + Amarok::albumArtistTrackFromUrl( url.path(), artist, album, track ); + + QString artistID = QString::number( CollectionDB::instance()->artistID( artist ) ); + QString albumID = QString::number( CollectionDB::instance()->albumID( album ) ); + + QStringList trackUrls = CollectionDB::instance()->albumTracks( artistID, albumID ); + foreach( trackUrls ) { + urls += KURL::fromPathOrURL( *it ); + } + } + + else if( protocol == "stream" ) { + urls += KURL::fromPathOrURL( url.url().replace( QRegExp( "^stream:" ), "http:" ) ); + } + + return urls; +} + + +#include "contextbrowser.moc" diff --git a/amarok/src/contextbrowser.h b/amarok/src/contextbrowser.h new file mode 100644 index 00000000..f99304a9 --- /dev/null +++ b/amarok/src/contextbrowser.h @@ -0,0 +1,222 @@ +// (c) 2004 Christian Muehlhaeuser +// (c) 2005 Reigo Reinmets +// (c) 2005 Mark Kretschmann +// (c) 2006 Peter C. Ndikuwera +// License: GNU General Public License V2 + + +#ifndef AMAROK_CONTEXTBROWSER_H +#define AMAROK_CONTEXTBROWSER_H + +#include "amarokdcophandler.h" +#include "clicklineedit.h" +#include "engineobserver.h" + +#include +#include +#include + +class ClickLineEdit; +class CollectionDB; +class Color; +class HTMLView; +class KPopupMenu; +class MetaBundle; +class QPalette; +class QVBox; +class QLineEdit; +class QComboBox; +class KDialogBase; +class KTabBar; +class KTextEdit; + +class CueFile; + +namespace Browser { class ToolBar; } +namespace KIO { class Job; class TransferJob; } + + +class ContextBrowser : public KTabWidget, public EngineObserver +{ + Q_OBJECT + + friend class CurrentTrackJob; + friend class Amarok::DcopContextBrowserHandler; + + static ContextBrowser *s_instance; + + public: + ContextBrowser( const char *name ); + ~ContextBrowser(); + + static ContextBrowser *instance() { return s_instance; } + + void setFont( const QFont& ); + void reloadStyleSheet(); + static KURL::List expandURL( const KURL &url ); // expand urls (album, compilation, ...) + static bool hasContextProtocol( const KURL &url ); // is url expandable by context browser? + + virtual bool eventFilter( QObject *o, QEvent *e ); //required by the labels dialog + + public slots: + void openURLRequest(const KURL &url ); + void collectionScanStarted(); + void collectionScanDone( bool changed ); + void renderView(); + void lyricsChanged( const QString& ); + void lyricsScriptChanged(); + void lyricsResult( QCString cXmlDoc, bool cached = false ); + + protected: + void engineNewMetaData( const MetaBundle&, bool ); + void engineStateChanged( Engine::State, Engine::State = Engine::Empty ); + void paletteChange( const QPalette& ); + + protected slots: + void wheelDelta( int delta ); + + private slots: + void tabChanged( QWidget *page ); + void slotContextMenu( const QString& urlString, const QPoint& point ); + void showContext( const KURL& url, bool fromHistory = false ); + void showCurrentTrack(); + void showLyrics( const QString& url = QString::null ); + void showWikipedia( const QString& url = QString::null, bool fromHistory = false, bool replaceHistory = false ); + void showWikipediaEntry( const QString& entry, bool replaceHistory = false ); + void reloadWikipedia(); + void showLabelsDialog(); + + void coverFetched( const QString &artist, const QString &album ); + void coverRemoved( const QString &artist, const QString &album ); + void similarArtistsFetched( const QString &artist ); + void imageFetched( const QString &remoteURL ); + void tagsChanged( const MetaBundle &bundle ); + void tagsChanged( const QString &oldArtist, const QString &oldAlbum ); + void ratingOrScoreOrLabelsChanged( const QString &path ); + void refreshCurrentTrackPage(); + + void contextHistoryBack(); + + void lyricsAdd(); + void lyricsEditToggle(); + void lyricsSearch(); + void lyricsRefresh(); + void lyricsExternalPage(); + + void lyricsSearchText( const QString &text ); + void lyricsSearchTextNext(); + void lyricsSearchTextHide(); + void lyricsSearchTextShow(); + void lyricsSearchTextToggle(); + + void wikiHistoryBack(); + void wikiHistoryForward(); + void wikiBackPopupActivated( int id ); + void wikiForwardPopupActivated( int id ); + void wikiArtistPage(); + void wikiAlbumPage(); + void wikiTitlePage(); + void wikiExternalPage(); + void wikiResult( KIO::Job* job ); + void wikiConfigApply(); + void wikiConfig(); + void wikiConfigChanged( int activeItem ); + + private: + enum { CONTEXT_BACK, CONTEXT_FORWARD, CONTEXT_CURRENT, CONTEXT_HOME, CONTEXT_SEARCH }; + enum { LYRICS_ADD, LYRICS_EDIT, LYRICS_SEARCH, LYRICS_REFRESH, LYRICS_BROWSER }; + enum { WIKI_BACK, WIKI_FORWARD, WIKI_ARTIST, WIKI_ALBUM, WIKI_TITLE, WIKI_BROWSER, WIKI_CONFIG }; + typedef enum {SHOW_ALBUM_NORMAL, SHOW_ALBUM_SCORE, SHOW_ALBUM_LEAST_PLAY} T_SHOW_ALBUM_TYPE; + static const uint WIKI_MAX_HISTORY = 20; + static const uint CONTEXT_MAX_HISTORY = 20; + + void showIntroduction(); + void saveHtmlData(); + void showScanning(); + + static QString getEncodedImage( const QString &imageUrl ); + + static QString wikiLocale(); + static void setWikiLocale( const QString &locale ); + static QString wikiURL( const QString &item ); + QString wikiArtistPostfix() const; + QString wikiAlbumPostfix() const; + QString wikiTrackPostfix() const; + + HTMLView *m_currentTrackPage; + HTMLView *m_lyricsPage; + HTMLView *m_wikiPage; + + QVBox *m_contextTab; + QVBox *m_lyricsTab; + QVBox *m_wikiTab; + // These controls are used to dictate whether the page should be rebuilt + // true -> need rebuild + bool m_dirtyCurrentTrackPage; + bool m_dirtyLyricsPage; + bool m_dirtyWikiPage; + + QStringList m_contextBackHistory; + KURL m_contextURL; + + QString m_styleSheet; + bool m_emptyDB; + QString m_lyricAddUrl; + QString m_lyricSearchUrl; + QString m_lyricCurrentUrl; + Browser::ToolBar* m_lyricsToolBar; + KTextEdit* m_lyricsTextEdit; + QString m_lyricsBeingEditedUrl; + QString m_lyricsBeingEditedArtist; + QString m_lyricsBeingEditedTitle; + ClickLineEdit* m_lyricsSearchText; + KToolBar* m_lyricsTextBar; + bool m_lyricsTextBarShowed; + + + QString m_wiki; + QString m_wikiLanguages; + static QString s_wikiLocale; + QString m_wikiBaseUrl; + QString m_wikiCurrentUrl; + QString m_wikiCurrentEntry; + QStringList m_wikiBackHistory; + QStringList m_wikiForwardHistory; + KPopupMenu* m_wikiBackPopup; + KPopupMenu* m_wikiForwardPopup; + KIO::TransferJob* m_wikiJob; + Browser::ToolBar* m_wikiToolBar; + QLineEdit* m_wikiLocaleEdit; + QComboBox* m_wikiLocaleCombo; + KDialogBase* m_wikiConfigDialog; + + QString m_HTMLSource; + QStringList m_metadataHistory; + KURL m_currentURL; + + bool m_relatedOpen; + bool m_suggestionsOpen; + bool m_favoritesOpen; + bool m_labelsOpen; + bool m_showRelated; + bool m_showSuggested; + bool m_showFaves; + bool m_showLabels; + + bool m_showFreshPodcasts; + bool m_showFavoriteAlbums; + bool m_showNewestAlbums; + + bool m_browseArtists; + QString m_artist; + QStringList m_shownAlbums; + + bool m_browseLabels; + QString m_label; + ClickLineEdit* m_addLabelEdit; + QListView* m_labelListView; + + CueFile *m_cuefile; +}; + +#endif /* AMAROK_CONTEXTBROWSER_H */ diff --git a/amarok/src/coverfetcher.cpp b/amarok/src/coverfetcher.cpp new file mode 100644 index 00000000..40e79e7c --- /dev/null +++ b/amarok/src/coverfetcher.cpp @@ -0,0 +1,678 @@ +// (C) 2004 Mark Kretschmann +// (C) 2004 Stefan Bogner +// (C) 2004 Max Howell +// See COPYING file for licensing information. + +#include "amarok.h" +#include "amarokconfig.h" +#include "collectiondb.h" +#include "config.h" //for AMAZON_SUPPORT +#include "covermanager.h" +#include "coverfetcher.h" +#include "debug.h" +#include "statusbar.h" + +#include +#include +#include +#include +#include + +#include +#include +#include //waiting cursor +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +void +Amarok::coverContextMenu( QWidget *parent, QPoint point, const QString &artist, const QString &album, bool showCoverManager ) +{ + KPopupMenu menu; + enum { SHOW, FETCH, CUSTOM, DELETE, MANAGER }; + + menu.insertTitle( i18n( "Cover Image" ) ); + + menu.insertItem( SmallIconSet( Amarok::icon( "zoom" ) ), i18n( "&Show Fullsize" ), SHOW ); + menu.insertItem( SmallIconSet( Amarok::icon( "download" ) ), i18n( "&Fetch From amazon.%1" ).arg( CoverManager::amazonTld() ), FETCH ); + menu.insertItem( SmallIconSet( Amarok::icon( "files" ) ), i18n( "Set &Custom Cover" ), CUSTOM ); + bool disable = !album.isEmpty(); // disable setting covers for unknown albums + menu.setItemEnabled( FETCH, disable ); + menu.setItemEnabled( CUSTOM, disable ); + menu.insertSeparator(); + + menu.insertItem( SmallIconSet( Amarok::icon( "remove" ) ), i18n( "&Unset Cover" ), DELETE ); + if ( showCoverManager ) { + menu.insertSeparator(); + menu.insertItem( SmallIconSet( Amarok::icon( "covermanager" ) ), i18n( "Cover &Manager" ), MANAGER ); + } + #ifndef AMAZON_SUPPORT + menu.setItemEnabled( FETCH, false ); + #endif + disable = !CollectionDB::instance()->albumImage( artist, album, 0 ).contains( "nocover" ); + menu.setItemEnabled( SHOW, disable ); + menu.setItemEnabled( DELETE, disable ); + + switch( menu.exec( point ) ) + { + case SHOW: + CoverManager::viewCover( artist, album, parent ); + break; + + case DELETE: + { + const int button = KMessageBox::warningContinueCancel( parent, + i18n( "Are you sure you want to remove this cover from the Collection?" ), + QString::null, + KStdGuiItem::del() ); + + if ( button == KMessageBox::Continue ) + CollectionDB::instance()->removeAlbumImage( artist, album ); + break; + } + + case FETCH: + #ifdef AMAZON_SUPPORT + CollectionDB::instance()->fetchCover( parent, artist, album, false ); + break; + #endif + + case CUSTOM: + { + QString artist_id; artist_id.setNum( CollectionDB::instance()->artistID( artist ) ); + QString album_id; album_id.setNum( CollectionDB::instance()->albumID( album ) ); + QStringList values = CollectionDB::instance()->albumTracks( artist_id, album_id ); + QString startPath = ":homedir"; + + if ( !values.isEmpty() ) { + KURL url; + url.setPath( values.first() ); + startPath = url.directory(); + } + + KURL file = KFileDialog::getImageOpenURL( startPath, parent, i18n("Select Cover Image File") ); + if ( !file.isEmpty() ) + CollectionDB::instance()->setAlbumImage( artist, album, file ); + break; + } + + case MANAGER: + CoverManager::showOnce( album ); + break; + } +} + + + +CoverLabel::CoverLabel ( QWidget * parent, const char * name, WFlags f ) + : QLabel( parent, name, f) +{} + + +void CoverLabel::mouseReleaseEvent(QMouseEvent *pEvent) { + if (pEvent->button() == LeftButton || pEvent->button() == RightButton) + { + Amarok::coverContextMenu( this, pEvent->globalPos(), m_artist, m_album, false ); + } +} + + +CoverFetcher::CoverFetcher( QWidget *parent, const QString &artist, QString album ) + : QObject( parent, "CoverFetcher" ) + , m_artist( artist ) + , m_album( album ) + , m_size( 2 ) + , m_success( true ) +{ + DEBUG_FUNC_INFO + + QStringList extensions; + extensions << i18n("disc") << i18n("disk") << i18n("remaster") << i18n("cd") << i18n("single") << i18n("soundtrack") << i18n("part") + << "disc" << "disk" << "remaster" << "cd" << "single" << "soundtrack" << "part" << "cds" /*cd single*/; + + //we do several queries, one raw ie, without the following modifications + //the others have the above strings removed with the following regex, as this can increase hit-rate + const QString template1 = " ?-? ?[(^{]* ?%1 ?\\d*[)^}\\]]* *$"; //eg album - [disk 1] -> album + foreach( extensions ) { + QRegExp regexp( template1.arg( *it ) ); + regexp.setCaseSensitive( false ); + album.remove( regexp ); + } + + //TODO try queries that remove anything in album after a " - " eg Les Mis. - Excerpts + + /** + * We search for artist - album, and just album, using the exact album text and the + * manipulated album text. + */ + + //search on our modified term, then the original + if ( !m_artist.isEmpty() ) + m_userQuery = m_artist + " - "; + m_userQuery += m_album; + + m_queries += m_artist + " - " + album; + m_queries += m_userQuery; + m_queries += album; + m_queries += m_album; + + //don't do the same searches twice in a row + if( m_album == album ) { + m_queries.pop_front(); + m_queries.pop_back(); + } + + /** + * Finally we do a search for just the artist, just in case as this often + * turns up a cover, and it might just be the right one! Also it would be + * the only valid search if m_album.isEmpty() + */ + m_queries += m_artist; + + QApplication::setOverrideCursor( KCursor::workingCursor() ); +} + +CoverFetcher::~CoverFetcher() +{ + DEBUG_FUNC_INFO + + QApplication::restoreOverrideCursor(); +} + +void +CoverFetcher::startFetch() +{ + DEBUG_FUNC_INFO + + // Static license Key. Thanks hydrogen ;-) + const QString LICENSE( "11ZKJS8X1ETSTJ6MT802" ); + + // reset all values + m_coverAmazonUrls.clear(); + m_coverAsins.clear(); + m_coverUrls.clear(); + m_coverNames.clear(); + m_xml = QString::null; + m_size = 2; + + if ( m_queries.isEmpty() ) { + debug() << "m_queries is empty" << endl; + finishWithError( i18n("No cover found") ); + return; + } + QString query = m_queries.front(); + m_queries.pop_front(); + + // '&' breaks searching + query.remove('&'); + + QString locale = AmarokConfig::amazonLocale(); + QString tld; + + if( locale == "us" ) + tld = "com"; + else if( locale =="uk" ) + tld = "co.uk"; + else + tld = locale; + + int mibenum = 106; // utf-8 + + QString url; + url = "http://ecs.amazonaws." + tld + + "/onca/xml?Service=AWSECommerceService&Version=2007-10-29&Operation=ItemSearch&AssociateTag=webservices-20&AWSAccessKeyId=" + LICENSE + + "&Keywords=" + KURL::encode_string_no_slash( query, mibenum ) + + "&SearchIndex=Music&ResponseGroup=Small,Images"; + debug() << url << endl; + + KIO::TransferJob* job = KIO::storedGet( url, false, false ); + connect( job, SIGNAL(result( KIO::Job* )), SLOT(finishedXmlFetch( KIO::Job* )) ); + + Amarok::StatusBar::instance()->newProgressOperation( job ); +} + + +////////////////////////////////////////////////////////////////////////////////////////// +// PRIVATE SLOTS +////////////////////////////////////////////////////////////////////////////////////////// + +void +CoverFetcher::finishedXmlFetch( KIO::Job *job ) //SLOT +{ + DEBUG_BLOCK + + // NOTE: job can become 0 when this method is called from attemptAnotherFetch() + + if( job && job->error() ) { + finishWithError( i18n("There was an error communicating with Amazon."), job ); + return; + } + if ( job ) { + KIO::StoredTransferJob* const storedJob = static_cast( job ); + m_xml = QString::fromUtf8( storedJob->data().data(), storedJob->data().size() ); + } + + QDomDocument doc; + if( !doc.setContent( m_xml ) ) { + m_errors += i18n("The XML obtained from Amazon is invalid."); + startFetch(); + return; + } + + m_coverAsins.clear(); + m_coverAmazonUrls.clear(); + m_coverUrls.clear(); + m_coverNames.clear(); + + // the url for the Amazon product info page + const QDomNodeList list = doc.documentElement().namedItem( "Items" ).childNodes(); + + for(int i = 0; i < list.count(); i++ ) + { + QDomNode n = list.item( i ); + if( n.isElement() && n.nodeName() == "IsValid" ) + { + if( n.toElement().text() == "False" ) + { + warning() << "The XML Is Invalid!"; + return; + } + } + else if( list.item( i ).nodeName() == "Item" ) + { + const QDomNode node = list.item( i ); + parseItemNode( node ); + } + } + attemptAnotherFetch(); +} + +void CoverFetcher::parseItemNode( const QDomNode &node ) +{ + QDomNode it = node.firstChild(); + + QString size; + switch( m_size ) + { + case 0: size = "Small"; break; + case 1: size = "Medium"; break; + default: size = "Large"; break; + } + size += "Image"; + + while ( !it.isNull() ) { + if ( it.isElement() ) { + QDomElement e = it.toElement(); + if(e.tagName()=="ASIN") + { + m_asin = e.text(); + m_coverAsins += m_asin; + } + else if(e.tagName() == "DetailPageURL" ) + { + m_amazonURL = e.text(); + m_coverAmazonUrls += m_amazonURL; + } + else if( e.tagName() == size ) + { + QDomNode subIt = e.firstChild(); + while( !subIt.isNull() ) + { + if( subIt.isElement() ) + { + QDomElement subE = subIt.toElement(); + if( subE.tagName() == "URL" ) + { + const QString coverUrl = subE.text(); + m_coverUrls += coverUrl; + break; + } + } + subIt = subIt.nextSibling(); + } + } + else if( e.tagName() == "ItemAttributes" ) + { + QDomNodeList nodes = e.childNodes(); + QDomNode iter; + QString artist; + QString album; + for( int i = 0; i < nodes.count(); i++ ) + { + iter = nodes.item( i ); + + if( iter.isElement() ) + { + if( iter.nodeName() == "Artist" ) + { + artist = iter.toElement().text(); + } + else if( iter.nodeName() == "Title" ) + { + album = iter.toElement().text(); + } + } + } + m_coverNames += QString( artist + " - " + album ); + } + } + it = it.nextSibling(); + } +} + +void +CoverFetcher::finishedImageFetch( KIO::Job *job ) //SLOT +{ + if( job->error() ) { + debug() << "finishedImageFetch(): KIO::error(): " << job->error() << endl; + + m_errors += i18n("The cover could not be retrieved."); + + attemptAnotherFetch(); + return; + } + + m_image.loadFromData( static_cast( job )->data() ); + + if( m_image.width() <= 1 ) { + //Amazon seems to offer images of size 1x1 sometimes + //Amazon has nothing to offer us for the requested image size + m_errors += i18n("The cover-data produced an invalid image."); + attemptAnotherFetch(); + } + + else if( m_userCanEditQuery ) + //yay! image found :) + //lets see if the user wants it + showCover(); + + else + //image loaded successfully yay! + finish(); +} + + +void +CoverFetcher::attemptAnotherFetch() +{ + DEBUG_BLOCK + + if( !m_coverUrls.isEmpty() ) { + // Amazon suggested some more cover URLs to try before we + // try a different query + + KIO::TransferJob* job = KIO::storedGet( KURL(m_coverUrls.front()), false, false ); + connect( job, SIGNAL(result( KIO::Job* )), SLOT(finishedImageFetch( KIO::Job* )) ); + + Amarok::StatusBar::instance()->newProgressOperation( job ); + + m_coverUrls.pop_front(); + + m_currentCoverName = m_coverNames.front(); + m_coverNames.pop_front(); + + m_amazonURL = m_coverAmazonUrls.front(); + m_coverAmazonUrls.pop_front(); + + m_asin = m_coverAsins.front(); + m_coverAsins.pop_front(); + } + + else if( !m_xml.isEmpty() && m_size > 0 ) { + // we need to try smaller sizes, this often is + // fruitless, but does work out sometimes. + m_size--; + + finishedXmlFetch( 0 ); + } + + else if( !m_queries.isEmpty() ) { + // we have some queries left in the pot + startFetch(); + } + + else if( m_userCanEditQuery ) { + // we have exhausted all the predetermined queries + // so lets let the user give it a try + getUserQuery( i18n("You have seen all the covers Amazon returned using the query below. Perhaps you can refine it:") ); + m_coverAmazonUrls.clear(); + m_coverAsins.clear(); + m_coverUrls.clear(); + m_coverNames.clear(); + } + else + finishWithError( i18n("No cover found") ); +} + + +// Moved outside the only function that uses it because +// gcc 2.95 doesn't like class declarations there. + class EditSearchDialog : public KDialog + { + public: + EditSearchDialog( QWidget* parent, const QString &text, const QString &keyword, CoverFetcher *fetcher ) + : KDialog( parent ) + { + setCaption( i18n( "Amazon Query Editor" ) ); + + // amazon combo box + KComboBox* amazonLocale = new KComboBox( this ); + amazonLocale->insertItem( i18n("International"), CoverFetcher::International ); + amazonLocale->insertItem( i18n("Canada"), CoverFetcher::Canada ); + amazonLocale->insertItem( i18n("France"), CoverFetcher::France ); + amazonLocale->insertItem( i18n("Germany"), CoverFetcher::Germany ); + amazonLocale->insertItem( i18n("Japan"), CoverFetcher::Japan); + amazonLocale->insertItem( i18n("United Kingdom"), CoverFetcher::UK ); + if( CoverManager::instance() ) + connect( amazonLocale, SIGNAL( activated(int) ), + CoverManager::instance(), SLOT( changeLocale(int) ) ); + else + connect( amazonLocale, SIGNAL( activated(int) ), + fetcher, SLOT( changeLocale(int) ) ); + QHBoxLayout *hbox1 = new QHBoxLayout( 8 ); + hbox1->addWidget( new QLabel( i18n( "Amazon Locale: " ), this ) ); + hbox1->addWidget( amazonLocale ); + + int currentLocale = CoverFetcher::localeStringToID( AmarokConfig::amazonLocale() ); + amazonLocale->setCurrentItem( currentLocale ); + + KPushButton* cancelButton = new KPushButton( KStdGuiItem::cancel(), this ); + KPushButton* searchButton = new KPushButton( i18n("&Search"), this ); + + QHBoxLayout *hbox2 = new QHBoxLayout( 8 ); + hbox2->addItem( new QSpacerItem( 160, 8, QSizePolicy::Expanding, QSizePolicy::Minimum ) ); + hbox2->addWidget( searchButton ); + hbox2->addWidget( cancelButton ); + + QVBoxLayout *vbox = new QVBoxLayout( this, 8, 8 ); + vbox->addLayout( hbox1 ); + vbox->addWidget( new QLabel( "" + text, this ) ); + vbox->addWidget( new KLineEdit( keyword, this, "Query" ) ); + vbox->addLayout( hbox2 ); + + searchButton->setDefault( true ); + + adjustSize(); + setFixedHeight( height() ); + + connect( searchButton, SIGNAL(clicked()), SLOT(accept()) ); + connect( cancelButton, SIGNAL(clicked()), SLOT(reject()) ); + } + + QString query() { return static_cast(child( "Query" ))->text(); } + }; + +QString +CoverFetcher::localeIDToString( int id )//static +{ + switch ( id ) + { + case International: + return "us"; + case Canada: + return "ca"; + case France: + return "fr"; + case Germany: + return "de"; + case Japan: + return "jp"; + case UK: + return "uk"; + } + + return "us"; +} + +int +CoverFetcher::localeStringToID( const QString &s ) +{ + int id = International; + if( s == "fr" ) id = France; + else if( s == "de" ) id = Germany; + else if( s == "jp" ) id = Japan; + else if( s == "uk" ) id = UK; + else if( s == "ca" ) id = Canada; + + return id; +} + +void +CoverFetcher::changeLocale( int id )//SLOT +{ + QString locale = localeIDToString( id ); + AmarokConfig::setAmazonLocale( locale ); +} + + +void +CoverFetcher::getUserQuery( QString explanation ) +{ + if( explanation.isEmpty() ) + explanation = i18n("Ask Amazon for covers using this query:"); + + EditSearchDialog dialog( + static_cast( parent() ), + explanation, + m_userQuery, + this ); + + switch( dialog.exec() ) + { + case QDialog::Accepted: + m_userQuery = dialog.query(); + m_queries = m_userQuery; + startFetch(); + break; + default: + finishWithError( i18n( "Aborted." ) ); + break; + } +} + + class CoverFoundDialog : public KDialog + { + public: + CoverFoundDialog( QWidget *parent, const QImage &cover, const QString &productname ) + : KDialog( parent ) + { + // Gives the window a small title bar, and skips a taskbar entry + KWin::setType( winId(), NET::Utility ); + KWin::setState( winId(), NET::SkipTaskbar ); + + (new QVBoxLayout( this ))->setAutoAdd( true ); + + QLabel *labelPix = new QLabel( this ); + QLabel *labelName = new QLabel( this ); + QHBox *buttons = new QHBox( this ); + KPushButton *save = new KPushButton( KStdGuiItem::save(), buttons ); + KPushButton *newsearch = new KPushButton( i18n( "Ne&w Search..." ), buttons, "NewSearch" ); + KPushButton *nextcover = new KPushButton( i18n( "&Next Cover" ), buttons, "NextCover" ); + KPushButton *cancel = new KPushButton( KStdGuiItem::cancel(), buttons ); + + labelPix ->setAlignment( Qt::AlignHCenter ); + labelName->setAlignment( Qt::AlignHCenter ); + labelPix ->setPixmap( cover ); + labelName->setText( productname ); + + save->setDefault( true ); + this->setFixedSize( sizeHint() ); + this->setCaption( i18n("Cover Found") ); + + connect( save, SIGNAL(clicked()), SLOT(accept()) ); + connect( newsearch, SIGNAL(clicked()), SLOT(accept()) ); + connect( nextcover, SIGNAL(clicked()), SLOT(accept()) ); + connect( cancel, SIGNAL(clicked()), SLOT(reject()) ); + } + + virtual void accept() + { + if( qstrcmp( sender()->name(), "NewSearch" ) == 0 ) + done( 1000 ); + else if( qstrcmp( sender()->name(), "NextCover" ) == 0 ) + done( 1001 ); + else + KDialog::accept(); + } + }; + + +void +CoverFetcher::showCover() +{ + CoverFoundDialog dialog( static_cast( parent() ), m_image, m_currentCoverName ); + + switch( dialog.exec() ) + { + case KDialog::Accepted: + finish(); + break; + case 1000: //showQueryEditor() + getUserQuery(); + m_coverAmazonUrls.clear(); + m_coverAsins.clear(); + m_coverUrls.clear(); + m_coverNames.clear(); + break; + case 1001: //nextCover() + attemptAnotherFetch(); + break; + default: + finishWithError( i18n( "Aborted." ) ); + break; + } +} + + +void +CoverFetcher::finish() +{ + emit result( this ); + + deleteLater(); +} + +void +CoverFetcher::finishWithError( const QString &message, KIO::Job *job ) +{ + if( job ) + warning() << message << " KIO::error(): " << job->errorText() << endl; + + m_errors += message; + m_success = false; + + emit result( this ); + + deleteLater(); +} + +#include "coverfetcher.moc" diff --git a/amarok/src/coverfetcher.h b/amarok/src/coverfetcher.h new file mode 100644 index 00000000..a24761d3 --- /dev/null +++ b/amarok/src/coverfetcher.h @@ -0,0 +1,121 @@ +// (c) 2004 Mark Kretschmann +// (c) 2004 Stefan Bogner +// See COPYING file for licensing information. + +#ifndef AMAROK_COVERFETCHER_H +#define AMAROK_COVERFETCHER_H + +#include //baseclass +#include //stack allocated +#include //baseclass +#include //stack allocated +#include //stack allocated + +namespace Amarok { + void coverContextMenu( QWidget *parent, QPoint point, const QString &artist, const QString &album, bool showCoverManager = true ); +} + + +class CoverLabel : public QLabel { + public: + CoverLabel ( QWidget * parent, const char * name = 0, WFlags f = 0 ); + + void setInformation( const QString artist, const QString album ) { + m_artist = artist; + m_album = album; + } + + protected: + virtual void mouseReleaseEvent(QMouseEvent *pEvent); + + private: + QString m_artist; + QString m_album; +}; + + + +namespace KIO { class Job; } + +class CoverFetcher : public QObject +{ + friend class EditSearchDialog; + Q_OBJECT + + static const uint MAX_COVERS_CHOICE = 10; + +public: + CoverFetcher( QWidget *parent, const QString &artist, QString album ); + ~CoverFetcher(); + + /// allow the user to edit the query? + void setUserCanEditQuery( bool b ) { m_userCanEditQuery = b; } + + /// starts the fetch + void startFetch(); + + QString artist() const { return m_artist; } + QString album() const { return m_album; } + QString amazonURL() const { return m_amazonURL; } + QString asin() const { return m_asin; } + QImage image() const { return m_image; } + + bool wasError() const { return !m_success; } + QStringList errors() const { return m_errors; } + + enum Locale { International=0, France, Germany, Japan, UK, Canada }; + static QString localeIDToString( int id ); + static int localeStringToID( const QString &locale ); + +signals: + /// The CollectionDB can get the cover information using the pointer + void result( CoverFetcher* ); + +private slots: + void finishedXmlFetch( KIO::Job* job ); + void finishedImageFetch( KIO::Job* job ); + void changeLocale( int id ); + +private: + const QString m_artist; + const QString m_album; + + bool m_userCanEditQuery; + QString m_userQuery; /// the query from the query edit dialog + QString m_xml; + QImage m_image; + QString m_amazonURL; + QString m_asin; + int m_size; + + QStringList m_queries; + QStringList m_coverAsins; + QStringList m_coverAmazonUrls; + QStringList m_coverUrls; + QStringList m_coverNames; + QString m_currentCoverName; + QStringList m_errors; + + bool m_success; + +private: + /// The fetch was successful! + void finish(); + + /// Parse one QDomNode and append results. + void parseItemNode( const QDomNode &node ); + + /// The fetch failed, finish up and log an error message + void finishWithError( const QString &message, KIO::Job *job = 0 ); + + /// Prompt the user for a query + void getUserQuery( QString explanation = QString::null ); + + /// Will try all available queries, and then prompt the user, if allowed + void attemptAnotherFetch(); + + /// Show the cover that has been found + void showCover(); +}; + +#endif /* AMAROK_COVERFETCHER_H */ diff --git a/amarok/src/covermanager.cpp b/amarok/src/covermanager.cpp new file mode 100644 index 00000000..d4996beb --- /dev/null +++ b/amarok/src/covermanager.cpp @@ -0,0 +1,1067 @@ +// (c) Pierpaolo Di Panfilo 2004 +// (c) 2005 Isaiah Damron +// See COPYING file for licensing information + +#include "amarok.h" +#include "amarokconfig.h" +#include "browserToolBar.h" +#include "clicklineedit.h" +#include "debug.h" +#include "collectionbrowser.h" //manipulateThe() +#include "collectiondb.h" +#include "config.h" +#include "coverfetcher.h" +#include "covermanager.h" +#include "pixmapviewer.h" +#include "playlist.h" + +#include //ctor: desktop size +#include +#include //paintItem() +#include +#include +#include +#include //used to delete all cover fetchers +#include //paintItem() +#include //paintItem() +#include +#include +#include +#include +#include +#include +#include //search filter timer +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include //showCoverMenu() +#include +#include +#include //showCoverMenu() +#include +#include +#include //status label +#include +#include //paintItem +#include +#include //clear filter button +#include +#include +#include + +static QString artistToSelectInInitFunction; +CoverManager *CoverManager::s_instance = 0; + +class ArtistItem : public KListViewItem +{ + public: + ArtistItem(QListView *view, QListViewItem *item, const QString &text) + : KListViewItem(view, item, text) {} + protected: + int compare( QListViewItem* i, int col, bool ascending ) const + { + Q_UNUSED(col); + Q_UNUSED(ascending); + + QString a = text(0); + QString b = i->text(0); + + if ( a.startsWith( "the ", false ) ) + CollectionView::manipulateThe( a, true ); + if ( b.startsWith( "the ", false ) ) + CollectionView::manipulateThe( b, true ); + + return QString::localeAwareCompare( a.lower(), b.lower() ); + } +}; + +CoverManager::CoverManager() + : QSplitter( 0, "TheCoverManager" ) + , m_timer( new QTimer( this ) ) //search filter timer + , m_fetchCounter( 0 ) + , m_fetchingCovers( 0 ) + , m_coversFetched( 0 ) + , m_coverErrors( 0 ) +{ + DEBUG_BLOCK + + s_instance = this; + + // Sets caption and icon correctly (needed e.g. for GNOME) + kapp->setTopWidget( this ); + setCaption( kapp->makeStdCaption( i18n("Cover Manager") ) ); + setWFlags( WDestructiveClose ); + setMargin( 4 ); + + //artist listview + m_artistView = new KListView( this ); + m_artistView->addColumn(i18n( "Albums By" )); + m_artistView->setFullWidth( true ); + m_artistView->setSorting( 0 ); + m_artistView->setMinimumWidth( 180 ); + ArtistItem *item = 0; + + //load artists from the collection db + const QStringList artists = CollectionDB::instance()->artistList( false, false ); + foreach( artists ) + { + QString artist = *it; + item = new ArtistItem( m_artistView, item, artist ); + item->setPixmap( 0, SmallIcon( Amarok::icon( "artist" ) ) ); + } + m_artistView->sort(); + + m_artistView->setSorting( -1 ); + ArtistItem *last = static_cast(m_artistView->lastItem()); + item = new ArtistItem( m_artistView, 0, i18n( "All Albums" ) ); + item->setPixmap( 0, SmallIcon( Amarok::icon( "album" ) ) ); + + QueryBuilder qb; + qb.addReturnValue( QueryBuilder::tabAlbum, QueryBuilder::valName ); + qb.groupBy( QueryBuilder::tabAlbum, QueryBuilder::valName ); + qb.setOptions( QueryBuilder::optOnlyCompilations ); + qb.setLimit( 0, 1 ); + if ( qb.run().count() ) { + item = new ArtistItem( m_artistView, last, i18n( "Various Artists" ) ); + item->setPixmap( 0, SmallIcon("personal") ); + } + + QVBox *vbox = new QVBox( this ); + QHBox *hbox = new QHBox( vbox ); + + vbox->setSpacing( 4 ); + hbox->setSpacing( 4 ); + + { // + QHBox *searchBox = new QHBox( hbox ); + KToolBar* searchToolBar = new Browser::ToolBar( searchBox ); + KToolBarButton *button = new KToolBarButton( "locationbar_erase", 0, searchToolBar ); + m_searchEdit = new ClickLineEdit( i18n( "Enter search terms here" ), searchToolBar ); + m_searchEdit->setFrame( QFrame::Sunken ); + + searchToolBar->setStretchableWidget( m_searchEdit ); + connect( button, SIGNAL(clicked()), m_searchEdit, SLOT(clear()) ); + + QToolTip::add( button, i18n( "Clear search field" ) ); + QToolTip::add( m_searchEdit, i18n( "Enter space-separated terms to search in the albums" ) ); + + hbox->setStretchFactor( searchBox, 1 ); + } // + + // view menu + m_viewMenu = new KPopupMenu( this ); + m_viewMenu->insertItem( i18n("All Albums"), AllAlbums ); + m_viewMenu->insertItem( i18n("Albums With Cover"), AlbumsWithCover ); + m_viewMenu->insertItem( i18n("Albums Without Cover"), AlbumsWithoutCover ); + m_viewMenu->setItemChecked( AllAlbums, true ); + connect( m_viewMenu, SIGNAL( activated(int) ), SLOT( changeView(int) ) ); + + #ifdef AMAZON_SUPPORT + // amazon locale menu + m_amazonLocaleMenu = new KPopupMenu( this ); + m_amazonLocaleMenu->insertItem( i18n("International"), CoverFetcher::International ); + m_amazonLocaleMenu->insertItem( i18n("Canada"), CoverFetcher::Canada ); + m_amazonLocaleMenu->insertItem( i18n("France"), CoverFetcher::France ); + m_amazonLocaleMenu->insertItem( i18n("Germany"), CoverFetcher::Germany ); + m_amazonLocaleMenu->insertItem( i18n("Japan"), CoverFetcher::Japan); + m_amazonLocaleMenu->insertItem( i18n("United Kingdom"), CoverFetcher::UK ); + connect( m_amazonLocaleMenu, SIGNAL( activated(int) ), SLOT( changeLocale(int) ) ); + #endif + + KToolBar* toolBar = new KToolBar( hbox ); + toolBar->setIconText( KToolBar::IconTextRight ); + toolBar->setFrameShape( QFrame::NoFrame ); + toolBar->insertButton( "view_choose", 1, m_viewMenu, true, i18n( "View" ) ); + #ifdef AMAZON_SUPPORT + toolBar->insertButton( "babelfish", 2, m_amazonLocaleMenu, true, i18n( "Amazon Locale" ) ); + + QString locale = AmarokConfig::amazonLocale(); + m_currentLocale = CoverFetcher::localeStringToID( locale ); + m_amazonLocaleMenu->setItemChecked( m_currentLocale, true ); + + //fetch missing covers button + m_fetchButton = new KPushButton( KGuiItem( i18n("Fetch Missing Covers"), Amarok::icon( "download" ) ), hbox ); + connect( m_fetchButton, SIGNAL(clicked()), SLOT(fetchMissingCovers()) ); + #endif + + //cover view + m_coverView = new CoverView( vbox ); + + //status bar + KStatusBar *m_statusBar = new KStatusBar( vbox ); + m_statusBar->addWidget( m_statusLabel = new KSqueezedTextLabel( m_statusBar ), 4 ); + m_statusLabel->setIndent( 3 ); + m_statusBar->addWidget( m_progressBox = new QHBox( m_statusBar ), 1, true ); + KPushButton *stopButton = new KPushButton( KGuiItem(i18n("Abort"), "stop"), m_progressBox ); + connect( stopButton, SIGNAL(clicked()), SLOT(stopFetching()) ); + m_progress = new KProgress( m_progressBox ); + m_progress->setCenterIndicator( true ); + + const int h = m_statusLabel->height() + 3; + m_statusLabel->setFixedHeight( h ); + m_progressBox->setFixedHeight( h ); + m_progressBox->hide(); + + + // signals and slots connections + connect( m_artistView, SIGNAL(selectionChanged( QListViewItem* ) ), + SLOT(slotArtistSelected( QListViewItem* )) ); + connect( m_coverView, SIGNAL(contextMenuRequested( QIconViewItem*, const QPoint& )), + SLOT(showCoverMenu( QIconViewItem*, const QPoint& )) ); + connect( m_coverView, SIGNAL(executed( QIconViewItem* )), + SLOT(coverItemExecuted( QIconViewItem* )) ); + connect( m_timer, SIGNAL(timeout()), + SLOT(slotSetFilter()) ); + connect( m_searchEdit, SIGNAL(textChanged( const QString& )), + SLOT(slotSetFilterTimeout()) ); + + #ifdef AMAZON_SUPPORT + connect( CollectionDB::instance(), SIGNAL(coverFetched( const QString&, const QString& )), + SLOT(coverFetched( const QString&, const QString& )) ); + connect( CollectionDB::instance(), SIGNAL(coverRemoved( const QString&, const QString& )), + SLOT(coverRemoved( const QString&, const QString& )) ); + connect( CollectionDB::instance(), SIGNAL(coverFetcherError( const QString& )), + SLOT(coverFetcherError()) ); + #endif + + m_currentView = AllAlbums; + + QSize size = QApplication::desktop()->screenGeometry( this ).size() / 1.5; + resize( Amarok::config( "Cover Manager" )->readSizeEntry( "Window Size", &size ) ); + + show(); + + QTimer::singleShot( 0, this, SLOT(init()) ); +} + + +CoverManager::~CoverManager() +{ + DEBUG_BLOCK + + Amarok::config( "Cover Manager" )->writeEntry( "Window Size", size() ); + + s_instance = 0; +} + + +void CoverManager::init() +{ + DEBUG_BLOCK + + QListViewItem *item = 0; + + if ( !artistToSelectInInitFunction.isEmpty() ) + for ( item = m_artistView->firstChild(); item; item = item->nextSibling() ) + if ( item->text( 0 ) == artistToSelectInInitFunction ) + break; + + if ( item == 0 ) + item = m_artistView->firstChild(); + + m_artistView->setSelected( item, true ); +} + + +CoverViewDialog::CoverViewDialog( const QString& artist, const QString& album, QWidget *parent ) + : QDialog( parent, 0, false, WDestructiveClose | WType_TopLevel | WNoAutoErase ) + , m_pixmap( CollectionDB::instance()->albumImage( artist, album, false, 0 ) ) +{ + KWin::setType( winId(), NET::Utility ); + kapp->setTopWidget( this ); + setCaption( kapp->makeStdCaption( i18n("%1 - %2").arg( artist, album ) ) ); + + m_layout = new QHBoxLayout( this ); + m_layout->setAutoAdd( true ); + m_pixmapViewer = new PixmapViewer( this, m_pixmap ); + + setFixedSize( m_pixmapViewer->maximalSize() ); +} + + +void CoverManager::viewCover( const QString& artist, const QString& album, QWidget *parent ) //static +{ + //QDialog means "escape" works as expected + QDialog *dialog = new CoverViewDialog( artist, album, parent ); + dialog->show(); +} + + +QString CoverManager::amazonTld() //static +{ + if (AmarokConfig::amazonLocale() == "us") + return "com"; + else if (AmarokConfig::amazonLocale()== "jp") + return "co.jp"; + else if (AmarokConfig::amazonLocale() == "uk") + return "co.uk"; + else if (AmarokConfig::amazonLocale() == "ca") + return "ca"; + else + return AmarokConfig::amazonLocale(); +} + + +void CoverManager::fetchMissingCovers() //SLOT +{ + #ifdef AMAZON_SUPPORT + + DEBUG_BLOCK + + for ( QIconViewItem *item = m_coverView->firstItem(); item; item = item->nextItem() ) { + CoverViewItem *coverItem = static_cast( item ); + if( !coverItem->hasCover() ) { + m_fetchCovers += coverItem->artist() + " @@@ " + coverItem->album(); + m_fetchingCovers++; + } + } + + if( !m_fetchCounter ) //loop isn't started yet + fetchCoversLoop(); + + updateStatusBar(); + m_fetchButton->setEnabled( false ); + + #endif +} + + +void CoverManager::fetchCoversLoop() //SLOT +{ + #ifdef AMAZON_SUPPORT + + if( m_fetchCounter < m_fetchCovers.count() ) { + //get artist and album from keyword + const QStringList values = QStringList::split( " @@@ ", m_fetchCovers[m_fetchCounter], true ); + + if( values.count() > 1 ) + CollectionDB::instance()->fetchCover( this, values[0], values[1], m_fetchCovers.count() != 1); //edit mode when fetching 1 cover + + m_fetchCounter++; + + // Wait 1 second, since amazon caps the number of accesses per client + QTimer::singleShot( 1000, this, SLOT( fetchCoversLoop() ) ); + } + else { + m_fetchCovers.clear(); + m_fetchCounter = 0; + } + + #endif +} + + +void CoverManager::showOnce( const QString &artist ) +{ + if ( !s_instance ) { + artistToSelectInInitFunction = artist; + new CoverManager(); //shows itself + } + else { + s_instance->setActiveWindow(); + s_instance->raise(); + } +} + +void CoverManager::slotArtistSelected( QListViewItem *item ) //SLOT +{ + if( item->depth() ) //album item + return; + + QString artist = item->text(0); + + if( artist.endsWith( ", The" ) ) + CollectionView::instance()->manipulateThe( artist, false ); + + m_coverView->clear(); + m_coverItems.clear(); + + // reset current view mode state to "AllAlbum" which is the default on artist change in left panel + m_currentView = AllAlbums; + m_viewMenu->setItemChecked( AllAlbums, true ); + m_viewMenu->setItemChecked( AlbumsWithCover, false ); + m_viewMenu->setItemChecked( AlbumsWithoutCover, false ); + + QProgressDialog progress( this, 0, true ); + progress.setLabelText( i18n("Loading Thumbnails...") ); + progress.QDialog::setCaption( i18n("...") ); + + //NOTE we MUST show the dialog, otherwise the closeEvents get processed + // in the processEvents() calls below, GRUMBLE! Qt sux0rs + progress.show(); + progress.repaint( false ); //ensures the dialog isn't blank + + //this is an extra processEvent call for the sake of init() and aesthetics + //it isn't necessary + kapp->processEvents(); + + //this can be a bit slow + QApplication::setOverrideCursor( KCursor::waitCursor() ); + QueryBuilder qb; + QStringList albums; + + qb.addReturnValue( QueryBuilder::tabArtist, QueryBuilder::valName ); + qb.addReturnValue( QueryBuilder::tabAlbum, QueryBuilder::valName ); + + qb.excludeMatch( QueryBuilder::tabAlbum, i18n( "Unknown" ) ); + qb.sortBy( QueryBuilder::tabAlbum, QueryBuilder::valName ); + qb.setOptions( QueryBuilder::optRemoveDuplicates ); + qb.setOptions( QueryBuilder::optNoCompilations ); + + if ( item != m_artistView->firstChild() ) + qb.addMatches( QueryBuilder::tabArtist, artist ); + + albums = qb.run(); + + //also retrieve compilations when we're showing all items (first treenode) or + //"Various Artists" (last treenode) + if ( item == m_artistView->firstChild() || item == m_artistView->lastChild() ) + { + QStringList cl; + + qb.clear(); + qb.addReturnValue( QueryBuilder::tabAlbum, QueryBuilder::valName ); + + qb.excludeMatch( QueryBuilder::tabAlbum, i18n( "Unknown" ) ); + qb.sortBy( QueryBuilder::tabAlbum, QueryBuilder::valName ); + qb.setOptions( QueryBuilder::optRemoveDuplicates ); + qb.setOptions( QueryBuilder::optOnlyCompilations ); + cl = qb.run(); + + for ( uint i = 0; i < cl.count(); i++ ) { + albums.append( i18n( "Various Artists" ) ); + albums.append( cl[ i ] ); + } + } + + QApplication::restoreOverrideCursor(); + + progress.setTotalSteps( (albums.count()/2) + (albums.count()/10) ); + + //insert the covers first because the list view is soooo paint-happy + //doing it in the second loop looks really bad, unfortunately + //this is the slowest step in the bit that we can't process events + uint x = 0; + foreach( albums ) + { + const QString artist = *it; + const QString album = *(++it); + m_coverItems.append( new CoverViewItem( m_coverView, m_coverView->lastItem(), artist, album ) ); + + if ( ++x % 50 == 0 ) { + progress.setProgress( x / 5 ); // we do it less often due to bug in Qt, ask Max + kapp->processEvents(); // QProgressDialog also calls this, but not always due to Qt bug! + + //only worth testing for after processEvents() is called + if( progress.wasCancelled() ) + break; + } + } + + //now, load the thumbnails + for( QIconViewItem *item = m_coverView->firstItem(); item; item = item->nextItem() ) { + progress.setProgress( progress.progress() + 1 ); + kapp->processEvents(); + + if( progress.wasCancelled() ) + break; + + static_cast(item)->loadCover(); + } + + updateStatusBar(); +} + +void CoverManager::showCoverMenu( QIconViewItem *item, const QPoint &p ) //SLOT +{ + #define item static_cast(item) + if( !item ) return; + + enum { SHOW, FETCH, CUSTOM, DELETE, APPEND }; + + KPopupMenu menu; + + menu.insertTitle( i18n( "Cover Image" ) ); + + QPtrList selected = selectedItems(); + if( selected.count() > 1 ) { + menu.insertItem( SmallIconSet( Amarok::icon( "download" ) ), i18n( "&Fetch Selected Covers" ), FETCH ); + menu.insertItem( SmallIconSet( Amarok::icon( "files" ) ), i18n( "Set &Custom Cover for Selected Albums" ), CUSTOM ); + menu.insertItem( SmallIconSet( Amarok::icon( "remove" ) ), i18n( "&Unset Selected Covers" ), DELETE ); + menu.insertItem( SmallIconSet( Amarok::icon( "add_playlist" ) ), i18n( "&Append to Playlist" ), APPEND ); + } + else { + menu.insertItem( SmallIconSet( Amarok::icon( "zoom" ) ), i18n( "&Show Fullsize" ), SHOW ); + menu.insertItem( SmallIconSet( Amarok::icon( "download" ) ), i18n( "&Fetch From amazon.%1" ).arg( CoverManager::amazonTld() ), FETCH ); + menu.insertItem( SmallIconSet( Amarok::icon( "files" ) ), i18n( "Set &Custom Cover" ), CUSTOM ); + menu.insertItem( SmallIconSet( Amarok::icon( "add_playlist" ) ), i18n( "&Append to Playlist" ), APPEND ); + menu.insertSeparator(); + + menu.insertItem( SmallIconSet( Amarok::icon( "remove" ) ), i18n( "&Unset Cover" ), DELETE ); + menu.setItemEnabled( SHOW, item->hasCover() ); + menu.setItemEnabled( DELETE, item->canRemoveCover() ); + } + #ifndef AMAZON_SUPPORT + menu.setItemEnabled( FETCH, false ); + #endif + + switch( menu.exec(p) ) { + case SHOW: + viewCover( item->artist(), item->album(), this ); + break; + + #ifdef AMAZON_SUPPORT + case FETCH: + fetchSelectedCovers(); + break; + #endif + + case CUSTOM: + { + setCustomSelectedCovers(); + break; + } + + case DELETE: + deleteSelectedCovers(); + break; + + case APPEND: + { + CoverViewItem* sel; + for ( sel = selected.first(); sel; sel = selected.next() ) + { + QString artist_id; + QString album_id; + artist_id.setNum( CollectionDB::instance()->artistID( sel->artist() ) ); + album_id.setNum( CollectionDB::instance()->albumID( sel->album() ) ); + Playlist::instance()->insertMedia( CollectionDB::instance()->albumTracks( artist_id, album_id ), Playlist::Append ); + } + break; + } + + default: ; + } + + #undef item +} + +void CoverManager::coverItemExecuted( QIconViewItem *item ) //SLOT +{ + #define item static_cast(item) + + if( !item ) return; + + item->setSelected( true ); + if ( item->hasCover() ) + viewCover( item->artist(), item->album(), this ); + else + fetchSelectedCovers(); + + #undef item +} + + +void CoverManager::slotSetFilter() //SLOT +{ + m_filter = m_searchEdit->text(); + + m_coverView->selectAll( false); + QIconViewItem *item = m_coverView->firstItem(); + while ( item ) + { + QIconViewItem *tmp = item->nextItem(); + m_coverView->takeItem( item ); + item = tmp; + } + + m_coverView->setAutoArrange( false ); + for( QIconViewItem *item = m_coverItems.first(); item; item = m_coverItems.next() ) + { + CoverViewItem *coverItem = static_cast(item); + if( coverItem->album().contains( m_filter, false ) || coverItem->artist().contains( m_filter, false ) ) + m_coverView->insertItem( item, m_coverView->lastItem() ); + } + m_coverView->setAutoArrange( true ); + + m_coverView->arrangeItemsInGrid(); + updateStatusBar(); +} + + +void CoverManager::slotSetFilterTimeout() //SLOT +{ + if ( m_timer->isActive() ) m_timer->stop(); + m_timer->start( 180, true ); +} + + +void CoverManager::changeView( int id ) //SLOT +{ + if( m_currentView == id ) return; + + //clear the iconview without deleting items + m_coverView->selectAll( false); + QIconViewItem *item = m_coverView->firstItem(); + while ( item ) { + QIconViewItem *tmp = item->nextItem(); + m_coverView->takeItem( item ); + item = tmp; + } + + m_coverView->setAutoArrange(false ); + for( QIconViewItem *item = m_coverItems.first(); item; item = m_coverItems.next() ) { + bool show = false; + CoverViewItem *coverItem = static_cast(item); + if( !m_filter.isEmpty() ) { + if( !coverItem->album().contains( m_filter, false ) && !coverItem->artist().contains( m_filter, false ) ) + continue; + } + + if( id == AllAlbums ) //show all albums + show = true; + else if( id == AlbumsWithCover && coverItem->hasCover() ) //show only albums with cover + show = true; + else if( id == AlbumsWithoutCover && !coverItem->hasCover() ) //show only albums without cover + show = true; + + if( show ) m_coverView->insertItem( item, m_coverView->lastItem() ); + } + m_coverView->setAutoArrange( true ); + + m_viewMenu->setItemChecked( m_currentView, false ); + m_viewMenu->setItemChecked( id, true ); + + m_coverView->arrangeItemsInGrid(); + m_currentView = id; +} + +void CoverManager::changeLocale( int id ) //SLOT +{ + QString locale = CoverFetcher::localeIDToString( id ); + AmarokConfig::setAmazonLocale( locale ); + m_amazonLocaleMenu->setItemChecked( m_currentLocale, false ); + m_amazonLocaleMenu->setItemChecked( id, true ); + m_currentLocale = id; +} + + +void CoverManager::coverFetched( const QString &artist, const QString &album ) //SLOT +{ + loadCover( artist, album ); + m_coversFetched++; + updateStatusBar(); +} + + +void CoverManager::coverRemoved( const QString &artist, const QString &album ) //SLOT +{ + loadCover( artist, album ); + m_coversFetched--; + updateStatusBar(); +} + + +void CoverManager::coverFetcherError() +{ + DEBUG_FUNC_INFO + + m_coverErrors++; + updateStatusBar(); +} + + +void CoverManager::stopFetching() +{ + Debug::Block block( __PRETTY_FUNCTION__ ); + + m_fetchCovers.clear(); + m_fetchCounter = 0; + + //delete all cover fetchers + QObjectList* list = queryList( "CoverFetcher" ); + for( QObject *obj = list->first(); obj; obj = list->next() ) + obj->deleteLater(); + + delete list; + + m_fetchingCovers = 0; + updateStatusBar(); +} + +// PRIVATE + +void CoverManager::loadCover( const QString &artist, const QString &album ) +{ + for( QIconViewItem *item = m_coverItems.first(); item; item = m_coverItems.next() ) + { + CoverViewItem *coverItem = static_cast(item); + if ( album == coverItem->album() && ( artist == coverItem->artist() || ( artist.isEmpty() && coverItem->artist().isEmpty() ) ) ) + { + coverItem->loadCover(); + return; + } + } +} + +void CoverManager::setCustomSelectedCovers() +{ + //function assumes something is selected + QPtrList selected = selectedItems(); + CoverViewItem* first = selected.getFirst(); + + QString artist_id; artist_id.setNum( CollectionDB::instance()->artistID( first->artist() ) ); + QString album_id; album_id.setNum( CollectionDB::instance()->albumID( first->album() ) ); + QStringList values = CollectionDB::instance()->albumTracks( artist_id, album_id ); + + QString startPath = ":homedir"; + if ( !values.isEmpty() ) { + KURL url; + url.setPath( values.first() ); + startPath = url.directory(); + } + KURL file = KFileDialog::getImageOpenURL( startPath, this, i18n( "Select Cover Image File" ) ); + if ( !file.isEmpty() ) { + qApp->processEvents(); //it may takes a while so process pending events + QString tmpFile; + QImage image = CollectionDB::fetchImage(file, tmpFile); + for ( CoverViewItem* item = selected.first(); item; item = selected.next() ) { + CollectionDB::instance()->setAlbumImage( item->artist(), item->album(), image ); + item->loadCover(); + } + KIO::NetAccess::removeTempFile( tmpFile ); + } +} + +void CoverManager::fetchSelectedCovers() +{ + QPtrList selected = selectedItems(); + for ( CoverViewItem* item = selected.first(); item; item = selected.next() ) + m_fetchCovers += item->artist() + " @@@ " + item->album(); + + m_fetchingCovers += selected.count(); + + if( !m_fetchCounter ) //loop isn't started yet + fetchCoversLoop(); + + updateStatusBar(); +} + + +void CoverManager::deleteSelectedCovers() +{ + QPtrList selected = selectedItems(); + + int button = KMessageBox::warningContinueCancel( this, + i18n( "Are you sure you want to remove this cover from the Collection?", + "Are you sure you want to delete these %n covers from the Collection?", + selected.count() ), + QString::null, + KStdGuiItem::del() ); + + if ( button == KMessageBox::Continue ) { + for ( CoverViewItem* item = selected.first(); item; item = selected.next() ) { + qApp->processEvents(); + if ( CollectionDB::instance()->removeAlbumImage( item->artist(), item->album() ) ) //delete selected cover + coverRemoved( item->artist(), item->album() ); + } + } +} + + +QPtrList CoverManager::selectedItems() +{ + QPtrList selectedItems; + for ( QIconViewItem* item = m_coverView->firstItem(); item; item = item->nextItem() ) + if ( item->isSelected() ) + selectedItems.append( static_cast(item) ); + + return selectedItems; +} + + +void CoverManager::updateStatusBar() +{ + QString text; + + //cover fetching info + if( m_fetchingCovers ) { + //update the progress bar + m_progress->setTotalSteps( m_fetchingCovers ); + m_progress->setProgress( m_coversFetched + m_coverErrors ); + if( m_progressBox->isHidden() ) + m_progressBox->show(); + + //update the status text + if( m_coversFetched + m_coverErrors >= m_progress->totalSteps() ) { + //fetching finished + text = i18n( "Finished." ); + if( m_coverErrors ) + text += i18n( " Cover not found", " %n covers not found", m_coverErrors ); + //reset counters + m_fetchingCovers = 0; + m_coversFetched = 0; + m_coverErrors = 0; + QTimer::singleShot( 2000, this, SLOT( updateStatusBar() ) ); + } + + if( m_fetchingCovers == 1 ) { + QStringList values = QStringList::split( " @@@ ", m_fetchCovers[0], true ); //get artist and album name + if ( values.count() >= 2 ) + { + if( values[0].isEmpty() ) + text = i18n( "Fetching cover for %1..." ).arg( values[1] ); + else + text = i18n( "Fetching cover for %1 - %2..." ).arg( values[0], values[1] ); + } + } + else if( m_fetchingCovers ) { + text = i18n( "Fetching 1 cover: ", "Fetching %n covers... : ", m_fetchingCovers ); + if( m_coversFetched ) + text += i18n( "1 fetched", "%n fetched", m_coversFetched ); + if( m_coverErrors ) { + if( m_coversFetched ) text += i18n(" - "); + text += i18n( "1 not found", "%n not found", m_coverErrors ); + } + if( m_coversFetched + m_coverErrors == 0 ) + text += i18n( "Connecting..." ); + } + } + else { + m_coversFetched = 0; + m_coverErrors = 0; + + uint totalCounter = 0, missingCounter = 0; + + if( m_progressBox->isShown() ) + m_progressBox->hide(); + + //album info + for( QIconViewItem *item = m_coverView->firstItem(); item; item = item->nextItem() ) { + totalCounter++; + if( !static_cast( item )->hasCover() ) + missingCounter++; //counter for albums without cover + } + + if( !m_filter.isEmpty() ) + text = i18n( "1 result for \"%1\"", "%n results for \"%1\"", totalCounter ).arg( m_filter ); + else if( m_artistView->selectedItem() ) { + text = i18n( "1 album", "%n albums", totalCounter ); + if( m_artistView->selectedItem() != m_artistView->firstChild() ) //showing albums by an artist + { + QString artist = m_artistView->selectedItem()->text(0); + if( artist.endsWith( ", The" ) ) + CollectionView::instance()->manipulateThe( artist, false ); + text += i18n( " by " ) + artist; + } + } + + if( missingCounter ) + text += i18n(" - ( %1 without cover )" ).arg( missingCounter ); + + #ifdef AMAZON_SUPPORT + m_fetchButton->setEnabled( missingCounter ); + #endif + } + + m_statusLabel->setText( text ); +} + +void CoverManager::setStatusText( QString text ) +{ + m_oldStatusText = m_statusLabel->text(); + m_statusLabel->setText( text ); +} + +////////////////////////////////////////////////////////////////////// +// CLASS CoverView +///////////////////////////////////////////////////////////////////// + +CoverView::CoverView( QWidget *parent, const char *name, WFlags f ) + : KIconView( parent, name, f ) +{ + Debug::Block block( __PRETTY_FUNCTION__ ); + + setArrangement( QIconView::LeftToRight ); + setResizeMode( QIconView::Adjust ); + setSelectionMode( QIconView::Extended ); + arrangeItemsInGrid(); + setAutoArrange( true ); + setItemsMovable( false ); + + // as long as QIconView only shows tooltips when the cursor is over the + // icon (and not the text), we have to create our own tooltips + setShowToolTips( false ); + + connect( this, SIGNAL( onItem( QIconViewItem * ) ), SLOT( setStatusText( QIconViewItem * ) ) ); + connect( this, SIGNAL( onViewport() ), CoverManager::instance(), SLOT( updateStatusBar() ) ); +} + + +QDragObject *CoverView::dragObject() +{ + CoverViewItem *item = static_cast( currentItem() ); + if( !item ) + return 0; + + const QString sql = "SELECT tags.url FROM tags, album WHERE album.name %1 AND tags.album = album.id ORDER BY tags.track;"; + const QStringList values = CollectionDB::instance()->query( sql.arg( CollectionDB::likeCondition( item->album() ) ) ); + + KURL::List urls; + for( QStringList::ConstIterator it = values.begin(), end = values.end(); it != end; ++it ) + urls += *it; + + QString imagePath = CollectionDB::instance()->albumImage( item->artist(), item->album(), false, 1 ); + KMultipleDrag *drag = new KMultipleDrag( this ); + drag->setPixmap( item->coverPixmap() ); + drag->addDragObject( new QIconDrag( this ) ); + drag->addDragObject( new QImageDrag( QImage( imagePath ) ) ); + drag->addDragObject( new KURLDrag( urls ) ); + + return drag; +} + +void CoverView::setStatusText( QIconViewItem *item ) +{ + #define item static_cast( item ) + if ( !item ) + return; + + bool sampler = false; + //compilations have valDummy for artist. see QueryBuilder::addReturnValue(..) for explanation + //FIXME: Don't rely on other independent code, use an sql query + if( item->artist().isEmpty() ) sampler = true; + + QString tipContent = i18n( "%1 - %2" ).arg( sampler ? i18n("Various Artists") : item->artist() ) + .arg( item->album() ); + + CoverManager::instance()->setStatusText( tipContent ); + + #undef item +} + +////////////////////////////////////////////////////////////////////// +// CLASS CoverViewItem +///////////////////////////////////////////////////////////////////// + +CoverViewItem::CoverViewItem( QIconView *parent, QIconViewItem *after, const QString &artist, const QString &album ) + : KIconViewItem( parent, after, album ) + , m_artist( artist ) + , m_album( album ) + , m_coverImagePath( CollectionDB::instance()->albumImage( m_artist, m_album, false, 0, &m_embedded ) ) + , m_coverPixmap( 0 ) +{ + setDragEnabled( true ); + setDropEnabled( true ); + calcRect(); +} + +bool CoverViewItem::hasCover() const +{ + return !m_coverImagePath.endsWith( "nocover.png" ) && QFile::exists( m_coverImagePath ); +} + +void CoverViewItem::loadCover() +{ + m_coverImagePath = CollectionDB::instance()->albumImage( m_artist, m_album, false, 1, &m_embedded ); + m_coverPixmap = QPixmap( m_coverImagePath ); //create the scaled cover + + repaint(); +} + + +void CoverViewItem::calcRect( const QString& ) +{ + int thumbWidth = AmarokConfig::coverPreviewSize(); + + QFontMetrics fm = iconView()->fontMetrics(); + QRect itemPixmapRect( 5, 1, thumbWidth, thumbWidth ); + QRect itemRect = rect(); + itemRect.setWidth( thumbWidth + 10 ); + itemRect.setHeight( thumbWidth + fm.lineSpacing() + 2 ); + QRect itemTextRect( 0, thumbWidth+2, itemRect.width(), fm.lineSpacing() ); + + setPixmapRect( itemPixmapRect ); + setTextRect( itemTextRect ); + setItemRect( itemRect ); +} + + +void CoverViewItem::paintItem(QPainter* p, const QColorGroup& cg) +{ + QRect itemRect = rect(); + + p->save(); + p->translate( itemRect.x(), itemRect.y() ); + + // draw the border + p->setPen( cg.mid() ); + p->drawRect( 0, 0, itemRect.width(), pixmapRect().height()+2 ); + + // draw the cover image + if( !m_coverPixmap.isNull() ) + p->drawPixmap( pixmapRect().x() + (pixmapRect().width() - m_coverPixmap.width())/2, + pixmapRect().y() + (pixmapRect().height() - m_coverPixmap.height())/2, m_coverPixmap ); + + //justify the album name + QString str = text(); + QFontMetrics fm = p->fontMetrics(); + int nameWidth = fm.width( str ); + if( nameWidth > textRect().width() ) + { + str = KStringHandler::rPixelSqueeze( str, p->fontMetrics(), textRect().width() ); + } + p->setPen( cg.text() ); + p->drawText( textRect(), Qt::AlignCenter, str ); + + if( isSelected() ) { + p->setPen( cg.highlight() ); + p->drawRect( pixmapRect() ); + p->drawRect( pixmapRect().left()+1, pixmapRect().top()+1, pixmapRect().width()-2, pixmapRect().height()-2); + p->drawRect( pixmapRect().left()+2, pixmapRect().top()+2, pixmapRect().width()-4, pixmapRect().height()-4); + } + + p->restore(); +} + + +void CoverViewItem::dropped( QDropEvent *e, const QValueList & ) +{ + if( QImageDrag::canDecode( e ) ) { + if( hasCover() ) { + int button = KMessageBox::warningContinueCancel( iconView(), + i18n( "Are you sure you want to overwrite this cover?"), + i18n("Overwrite Confirmation"), + i18n("&Overwrite") ); + if( button == KMessageBox::Cancel ) + return; + } + + QImage img; + QImageDrag::decode( e, img ); + CollectionDB::instance()->setAlbumImage( artist(), album(), img ); + m_coverImagePath = CollectionDB::instance()->albumImage( m_artist, m_album, false, 0 ); + loadCover(); + } +} + + +void CoverViewItem::dragEntered() +{ + setSelected( true ); +} + + +void CoverViewItem::dragLeft() +{ + setSelected( false ); +} + +#include "covermanager.moc" diff --git a/amarok/src/covermanager.h b/amarok/src/covermanager.h new file mode 100644 index 00000000..bba3f910 --- /dev/null +++ b/amarok/src/covermanager.h @@ -0,0 +1,166 @@ + +// (c) Pierpaolo Di Panfilo 2004 +// See COPYING file for licensing information + +#ifndef COVERMANAGER_H +#define COVERMANAGER_H + +#include +#include +#include +#include +#include + +class QListViewItem; +class CoverViewItem; +class ClickLineEdit; +class KPushButton; +class KPopupMenu; +class QToolButton; +class QLabel; +class KListView; +class CoverView; +class QHBox; +class KProgress; +class QHBoxLayout; +class PixmapViewer; + +class CoverManager : public QSplitter +{ + Q_OBJECT + + static CoverManager *s_instance; + + public: + CoverManager(); + ~CoverManager(); + + static CoverManager *instance() { return s_instance; } + + static void showOnce( const QString &artist = QString::null ); + static void viewCover( const QString& artist, const QString& album, QWidget *parent=0 ); + + void setStatusText( QString text ); + + /** + * Return the top level domain for the current locale + **/ + static QString amazonTld(); + public slots: + void updateStatusBar(); + void changeLocale( int id ); + + private slots: + void slotArtistSelected( QListViewItem* ); + void coverItemExecuted( QIconViewItem *item ); + void showCoverMenu( QIconViewItem *item, const QPoint& ); + void slotSetFilter(); + void slotSetFilterTimeout(); + void changeView( int id ); + void fetchMissingCovers(); + void fetchCoversLoop(); + void coverFetched( const QString&, const QString& ); + void coverRemoved( const QString&, const QString& ); + void coverFetcherError(); + void stopFetching(); + + void init(); + + private: + enum View { AllAlbums=0, AlbumsWithCover, AlbumsWithoutCover }; + + void loadCover( const QString &, const QString & ); + void setCustomSelectedCovers(); + void fetchSelectedCovers(); + void deleteSelectedCovers(); + QPtrList selectedItems(); + + KListView *m_artistView; + CoverView *m_coverView; + ClickLineEdit *m_searchEdit; + KPushButton *m_fetchButton; + KPopupMenu *m_amazonLocaleMenu; + KPopupMenu *m_viewMenu; + QToolButton *m_amazonLocaleButton; + QToolButton *m_viewButton; + int m_currentLocale; + int m_currentView; + + //status bar widgets + QLabel *m_statusLabel; + QHBox *m_progressBox; + KProgress *m_progress; + QString m_oldStatusText; + + QTimer *m_timer; //search filter timer + QPtrList m_coverItems; //used for filtering + QString m_filter; + + + // Used by fetchCoversLoop() for temporary storage + QStringList m_fetchCovers; + uint m_fetchCounter; + + //used to display information about cover fetching in the status bar + int m_fetchingCovers; + int m_coversFetched; + int m_coverErrors; +}; + +class CoverView : public KIconView +{ + Q_OBJECT + + public: + CoverView( QWidget *parent = 0, const char *name = 0, WFlags f = 0 ); + + protected: + QDragObject *dragObject(); + + private slots: + void setStatusText( QIconViewItem *item ); +}; + +class CoverViewItem : public KIconViewItem +{ + public: + CoverViewItem( QIconView *parent, QIconViewItem *after, const QString &artist, const QString &album ); + + void loadCover(); + bool hasCover() const; + bool canRemoveCover() const { return !m_embedded && hasCover(); } + QString artist() const { return m_artist; } + QString album() const { return m_album; } + QPixmap coverPixmap() const { return m_coverPixmap; } + + protected: + void paintItem(QPainter* painter, const QColorGroup& colorGroup); + void paintFocus(QPainter *, const QColorGroup &) { } + void dropped( QDropEvent *, const QValueList & ); + void dragEntered(); + void dragLeft(); + void calcRect( const QString& text_=QString::null ); + + private: + QString m_artist; + QString m_album; + QString m_coverImagePath; + QPixmap m_coverPixmap; + bool m_embedded; +}; + + +class CoverViewDialog : public QDialog { + Q_OBJECT + + public: + CoverViewDialog(const QString& artist, const QString& album, QWidget *parent); + + private: + QHBoxLayout *m_layout; + QPixmap m_pixmap; + PixmapViewer *m_pixmapViewer; + QLabel *m_label; +}; + +#endif diff --git a/amarok/src/cuefile.cpp b/amarok/src/cuefile.cpp new file mode 100644 index 00000000..d8eb4a60 --- /dev/null +++ b/amarok/src/cuefile.cpp @@ -0,0 +1,268 @@ +// (c) 2005 Martin Ehmke +// License: GNU General Public License V2 + +#define DEBUG_PREFIX "CueFile" + +#include +#include +#include + +#include + +#include "cuefile.h" +#include "metabundle.h" +#include "enginecontroller.h" +#include "debug.h" + +CueFile *CueFile::instance() +{ + static CueFile *s_instance = 0; + + if(!s_instance) + { + s_instance = new CueFile(EngineController::instance()); // FIXME berkus: le grand borkage (if engine is changed, e.g.)? + } + + return s_instance; +} + +CueFile::~CueFile() +{ + debug() << "shmack! destructed" << endl; +} + + +/* ++ - set and continue in the same state +x - cannot happen +track - switch to new state + +state/next token > + v PERFORMER TITLE FILE TRACK INDEX PREGAP +begin + + file x x x +file x x file track x x +track + + file x + + +index x x file track + x + +1. Ignore FILE completely. +2. INDEX 00 is gap abs position in cue formats we care about (use it to calc length of prev track and then drop on the floor). +3. Ignore subsequent INDEX entries (INDEX 02, INDEX 03 etc). - FIXME? this behavior is different from state chart above. +4. For a valid cuefile at least TRACK and INDEX are required. +*/ +enum { + BEGIN = 0, + TRACK_FOUND, // track found, index not yet found + INDEX_FOUND +}; + + +/** +* @return true if the cuefile could be successfully loaded +*/ +bool CueFile::load(int mediaLength) +{ + clear(); + m_lastSeekPos = -1; + + if( QFile::exists( m_cueFileName ) ) + { + QFile file( m_cueFileName ); + int track = 0; + QString defaultArtist = QString::null; + QString defaultAlbum = QString::null; + QString artist = QString::null; + QString title = QString::null; + long length = 0; + long prevIndex = -1; + bool index00Present = false; + long index = -1; + + int mode = BEGIN; + if( file.open( IO_ReadOnly ) ) + { + QTextStream stream( &file ); + QString line; + + while ( !stream.atEnd() ) + { + line = stream.readLine().simplifyWhiteSpace(); + + if( line.startsWith( "title", false ) ) + { + title = line.mid( 6 ).remove( '"' ); + if( mode == BEGIN ) + { + defaultAlbum = title; + title = QString::null; + debug() << "Album: " << defaultAlbum << endl; + } + else + debug() << "Title: " << title << endl; + } + + else if( line.startsWith( "performer", false )) + { + artist = line.mid( 10 ).remove( '"' ); + if( mode == BEGIN ) + { + defaultArtist = artist; + artist = QString::null; + debug() << "Album Artist: " << defaultArtist << endl; + } + else + debug() << "Artist: " << artist << endl; + } + + else if( line.startsWith( "track", false ) ) + { + if( mode == TRACK_FOUND ) + { + // not valid, because we have to have an index for the previous track + file.close(); + debug() << "Mode is TRACK_FOUND, abort." << endl; + return false; + } + if( mode == INDEX_FOUND ) + { + if(artist.isNull()) + artist = defaultArtist; + + debug() << "Inserting item: " << title << " - " << artist << " on " << defaultAlbum << " (" << track << ")" << endl; + // add previous entry to map + insert( index, CueFileItem( title, artist, defaultAlbum, track, index ) ); + prevIndex = index; + title = QString::null; + artist = QString::null; + track = 0; + } + track = line.section (' ',1,1).toInt(); + debug() << "Track: " << track << endl; + mode = TRACK_FOUND; + } + else if( line.startsWith( "index", false ) ) + { + if( mode == TRACK_FOUND) + { + int indexNo = line.section(' ',1,1).toInt(); + + if( indexNo == 1 ) + { + QStringList time = QStringList::split( QChar(':'),line.section (' ',-1,-1) ); + + index = time[0].toLong()*60*1000 + time[1].toLong()*1000 + time[2].toLong()*1000/75; //75 frames per second + + if( prevIndex != -1 && !index00Present ) // set the prev track's length if there is INDEX01 present, but no INDEX00 + { + length = index - prevIndex; + debug() << "Setting length of track " << (*this)[prevIndex].getTitle() << " to " << length << " msecs." << endl; + (*this)[prevIndex].setLength(length); + } + + index00Present = false; + mode = INDEX_FOUND; + length = 0; + } + + else if( indexNo == 0 ) // gap, use to calc prev track length + { + QStringList time = QStringList::split( QChar(':'),line.section (' ',-1,-1) ); + length = time[0].toLong()*60*1000 + time[1].toLong()*1000 + time[2].toLong()*1000/75; //75 frames per second + + if( prevIndex != -1 ) + { + length -= prevIndex; //this[prevIndex].getIndex(); + debug() << "Setting length of track " << (*this)[prevIndex].getTitle() << " to " << length << " msecs." << endl; + (*this)[prevIndex].setLength(length); + index00Present = true; + } + else + length = 0; + } + else + { + debug() << "Skipping unsupported INDEX " << indexNo << endl; + } + } + else + { + // not valid, because we don't have an associated track + file.close(); + debug() << "Mode is not TRACK_FOUND but encountered INDEX, abort." << endl; + return false; + } + debug() << "index: " << index << endl; + } + } + + if(artist.isNull()) + artist = defaultArtist; + + debug() << "Inserting item: " << title << " - " << artist << " on " << defaultAlbum << " (" << track << ")" << endl; + // add previous entry to map + insert( index, CueFileItem( title, artist, defaultAlbum, track, index ) ); + file.close(); + } + + /** + * Because there is no way to set the length for the last track in a normal way, + * we have to do some magic here. Having the total length of the media file given + * we can set the lenth for the last track after all the cue file was loaded into array. + */ + + (*this)[index].setLength(mediaLength*1000 - index); + debug() << "Setting length of track " << (*this)[index].getTitle() << " to " << mediaLength*1000 - index << " msecs." << endl; + + return true; + } + + else + return false; +} + +void CueFile::engineTrackPositionChanged( long position, bool userSeek ) +{ + position /= 1000; + if(userSeek || position > m_lastSeekPos) + { + CueFile::Iterator it = end(); + while( it != begin() ) + { + --it; +// debug() << "Checking " << position << " against pos " << it.key()/1000 << " title " << it.data().getTitle() << endl; + if(it.key()/1000 <= position) + { + MetaBundle bundle = EngineController::instance()->bundle(); // take current one and modify it + if(it.data().getTitle() != bundle.title() + || it.data().getArtist() != bundle.artist() + || it.data().getAlbum() != bundle.album() + || it.data().getTrackNumber() != bundle.track()) + { + bundle.setTitle(it.data().getTitle()); + bundle.setArtist(it.data().getArtist()); + bundle.setAlbum(it.data().getAlbum()); + bundle.setTrack(it.data().getTrackNumber()); + emit metaData(bundle); + + long length = it.data().getLength(); + if ( length == -1 ) // need to calculate + { + ++it; + long nextKey = it == end() ? bundle.length() * 1000 : it.key(); + --it; + length = kMax( nextKey - it.key(), 0L ); + } + emit newCuePoint( position, it.key() / 1000, ( it.key() + length ) / 1000 ); + } + break; + } + } + } + + m_lastSeekPos = position; +} + + +#include "cuefile.moc" + + + diff --git a/amarok/src/cuefile.h b/amarok/src/cuefile.h new file mode 100644 index 00000000..621830ab --- /dev/null +++ b/amarok/src/cuefile.h @@ -0,0 +1,82 @@ +// (c) 2005 Martin Ehmke +// License: GNU General Public License V2 + +#ifndef CUEFILE_H +#define CUEFILE_H + +#include +#include + +#include +#include "engineobserver.h" + +class CueFileItem { + public: + CueFileItem (const QString& title, const QString& artist, const QString& album, const int trackNumber, const long index) + : m_title( title ) + , m_artist( artist ) + , m_album( album ) + , m_trackNumber( trackNumber ) + , m_index( index ) + , m_length( -1 ) + {} + + CueFileItem() + : m_title( ) + , m_artist( ) + , m_album( ) + , m_trackNumber( -1 ) + , m_index( -1 ) + , m_length( -1 ) + {} + + void setLength(const long length) { m_length = length; } + + const QString getTitle () const { return m_title; } + const QString getArtist () const { return m_artist; } + const QString getAlbum () const { return m_album; } + const int getTrackNumber () const { return m_trackNumber; } + const long getIndex () const { return m_index; } + const long getLength () const { return m_length; } + + private: + QString m_title; + QString m_artist; + QString m_album; + int m_trackNumber; + long m_index; + long m_length; +}; + +// <> +class CueFile : public QObject, public QMap, public EngineObserver +{ + Q_OBJECT + + public: + static CueFile *instance(); + + void setCueFileName( QString name ) { m_cueFileName = name; }; + bool load(int mediaLength); + + // EngineObserver + virtual void engineTrackPositionChanged( long /*position*/ , bool /*userSeek*/ ); + + signals: + /** Transmits new metadata bundle */ + void metaData( const MetaBundle& ); + /** Transmits new length information associated with current cue */ + void newCuePoint( long currentPos, long startPos, long endPos ); + + protected: + CueFile() : EngineObserver(), m_lastSeekPos(-1) { }; + CueFile(EngineSubject *s) : EngineObserver(s), m_lastSeekPos(-1) { }; + ~CueFile(); + + private: + QString m_cueFileName; + int m_lastSeekPos; // in seconds +}; + + +#endif diff --git a/amarok/src/data/Amarok_1.4_Welcome.ogg b/amarok/src/data/Amarok_1.4_Welcome.ogg new file mode 100644 index 00000000..a59cc3d3 Binary files /dev/null and b/amarok/src/data/Amarok_1.4_Welcome.ogg differ diff --git a/amarok/src/data/Cool-Streams.xml b/amarok/src/data/Cool-Streams.xml new file mode 100644 index 00000000..51fd5072 --- /dev/null +++ b/amarok/src/data/Cool-Streams.xml @@ -0,0 +1,81 @@ + + + + http://www.ah.fm/192k.m3u + + + http://www.bassdrive.com/v2/streams/BassDrive.m3u + + + http://www.digitalgunfire.com/playlist.pls + + + http://di.fm/mp3/chillout.pls + + + http://di.fm/mp3/classictechno.pls + + + http://di.fm/mp3/trance.pls + + + http://dnbradio.com/hi.pls + + + http://www.shouted.fm/tunein/electro-dsl.m3u + + + http://streams.frequence3.net/hd-mp3.m3u + + + http://www.somafm.com/groovesalad.pls + + + http://somafm.com/dronezone.pls + + + http://somafm.com/tagstrance.pls + + + http://www.somafm.com/indiepop.pls + + + http://la.campus.ltu.se:8000/stream.ogg.m3u + + + http://www.sky.fm/mp3/classical.pls + + + http://stream.mth-house.de:8500/listen.pls + + + http://nectarine.sik.fi:8002/live.mp3.m3u + + + http://philosomatika.com/Philosomatika.pls + + + http://protonradio.com/proton.m3u + + + http://www.puredj.com/etc/pls/128K.pls + + + http://www.radioparadise.com/musiclinks/rp_128.m3u + + + http://www.raggakings.net/listen.m3u + + + http://somafm.com/secretagent.pls + + + http://sc.slayradio.org:8000/listen.pls + + + http://www.smgradio.com/core/audio/mp3/live.pls?service=vrbb + + + http://stream.xtcradio.com:8069/listen.pls + + diff --git a/amarok/src/data/Makefile.am b/amarok/src/data/Makefile.am new file mode 100644 index 00000000..afb5f56e --- /dev/null +++ b/amarok/src/data/Makefile.am @@ -0,0 +1,14 @@ +amarokdatadir = \ + $(kde_datadir)/amarok/data + +amarokdata_DATA = \ + Cool-Streams.xml \ + Amarok_1.4_Welcome.ogg \ + ball.png \ + dot.png \ + equalizer_presets.xml \ + firstrun.m3u \ + grid.png \ + wirl1.png \ + wirl2.png \ + magnatune_logo.png diff --git a/amarok/src/data/ball.png b/amarok/src/data/ball.png new file mode 100644 index 00000000..2e406b21 Binary files /dev/null and b/amarok/src/data/ball.png differ diff --git a/amarok/src/data/dot.png b/amarok/src/data/dot.png new file mode 100644 index 00000000..11e3b58d Binary files /dev/null and b/amarok/src/data/dot.png differ diff --git a/amarok/src/data/equalizer_presets.xml b/amarok/src/data/equalizer_presets.xml new file mode 100644 index 00000000..9cac9a08 --- /dev/null +++ b/amarok/src/data/equalizer_presets.xml @@ -0,0 +1,219 @@ + + + + 0 + 0 + 0 + 0 + 0 + 0 + -40 + -40 + -40 + -50 + + + 0 + 0 + 20 + 30 + 30 + 30 + 20 + 0 + 0 + 0 + + + 50 + 35 + 10 + 0 + 0 + -30 + -40 + -40 + 0 + 0 + + + 70 + 70 + 70 + 40 + 20 + -20 + -45 + -50 + -55 + -55 + + + -50 + -50 + -50 + -25 + 15 + 55 + 80 + 80 + 80 + 85 + + + 35 + 30 + 0 + -40 + -25 + 10 + 45 + 55 + 60 + 60 + + + 25 + 50 + 25 + -20 + 0 + -30 + -40 + -40 + 0 + 0 + + + 50 + 50 + 30 + 30 + 0 + -25 + -25 + -25 + 0 + 0 + + + -25 + 0 + 20 + 25 + 30 + 30 + 20 + 15 + 15 + 10 + + + 35 + 35 + 0 + 0 + 0 + 0 + 0 + 0 + 35 + 35 + + + -10 + 25 + 35 + 40 + 25 + -5 + -15 + -15 + -10 + -10 + + + 0 + 0 + -5 + -30 + 0 + -35 + -35 + 0 + 0 + 0 + + + 40 + 25 + -30 + -40 + -20 + 20 + 45 + 55 + 55 + 55 + + + 25 + 10 + -5 + -15 + -5 + 20 + 45 + 50 + 55 + 60 + + + -15 + -25 + -25 + -5 + 20 + 30 + 45 + 50 + 55 + 50 + + + 20 + 20 + 10 + -5 + -25 + -30 + -20 + -5 + 15 + 45 + + + 40 + 30 + 0 + -30 + -25 + 0 + 40 + 50 + 50 + 45 + + + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + + diff --git a/amarok/src/data/firstrun.m3u b/amarok/src/data/firstrun.m3u new file mode 100644 index 00000000..a19f59c6 --- /dev/null +++ b/amarok/src/data/firstrun.m3u @@ -0,0 +1,2 @@ +#EXTM3U +Amarok_1.4_Welcome.ogg diff --git a/amarok/src/data/grid.png b/amarok/src/data/grid.png new file mode 100644 index 00000000..3dbbc25a Binary files /dev/null and b/amarok/src/data/grid.png differ diff --git a/amarok/src/data/magnatune_logo.png b/amarok/src/data/magnatune_logo.png new file mode 100644 index 00000000..f92003b3 Binary files /dev/null and b/amarok/src/data/magnatune_logo.png differ diff --git a/amarok/src/data/wirl1.png b/amarok/src/data/wirl1.png new file mode 100644 index 00000000..e6af564d Binary files /dev/null and b/amarok/src/data/wirl1.png differ diff --git a/amarok/src/data/wirl2.png b/amarok/src/data/wirl2.png new file mode 100644 index 00000000..1307c5e4 Binary files /dev/null and b/amarok/src/data/wirl2.png differ diff --git a/amarok/src/database_refactor/README b/amarok/src/database_refactor/README new file mode 100644 index 00000000..4bd89d81 --- /dev/null +++ b/amarok/src/database_refactor/README @@ -0,0 +1,9 @@ + DATABASE +========== + +This folder contains a draft of the planned plugin-based database redesign. If you are +interested in helping, please contact our mailing list amarok-devel@lists.sf.net + + +WORK IN PROGRESS + diff --git a/amarok/src/database_refactor/_Makefile.am b/amarok/src/database_refactor/_Makefile.am new file mode 100644 index 00000000..b6c32c96 --- /dev/null +++ b/amarok/src/database_refactor/_Makefile.am @@ -0,0 +1,35 @@ +noinst_LTLIBRARIES = libdbengine.la + +INCLUDES = \ + -I$(top_srcdir)/amarok/src/plugin \ + -I$(top_srcdir)/amarok/src \ + -I$(top_srcdir)/amarok/src/engine \ + -I$(top_srcdir)/amarok/src/amarokcore \ + -I$(top_srcdir)/amarok/src/statusbar \ + $(all_includes) + +#if enable_sqlite + SQLITE_DBENGINE_SUBDIR = sqlite +#endif + +if enable_mysql + MYSQL_DBENGINE_SUBDIR = mysql +endif + +if enable_postgresql + POSTGRESQL_DBENGINE_SUBDIR = postgresql +endif + +libdbengine_la_SOURCES = \ + dbenginebase.cpp + +noinst_HEADERS = dbenginebase.h + +METASOURCES = \ + AUTO + +SUBDIRS = . \ + $(SQLITE_DBENGINE_SUBDIR) \ + $(MYSQL_DBENGINE_SUBDIR) \ + $(POSTGRESQL_DBENGINE_SUBDIR) + diff --git a/amarok/src/database_refactor/collectiondb.cpp b/amarok/src/database_refactor/collectiondb.cpp new file mode 100644 index 00000000..32a5a237 --- /dev/null +++ b/amarok/src/database_refactor/collectiondb.cpp @@ -0,0 +1,1975 @@ +// (c) 2004 Mark Kretschmann +// (c) 2004 Christian Muehlhaeuser +// (c) 2004 Sami Nieminen +// (c) 2005 Ian Monroe +// See COPYING file for licensing information. + +#define DEBUG_PREFIX "CollectionDB" + +#include "app.h" +#include "amarok.h" +#include "amarokconfig.h" +#include "config.h" +#include "debug.h" +#include "collectionbrowser.h" //updateTags() +#include "collectiondb.h" +#include "collectionreader.h" +#include "coverfetcher.h" +#include "enginecontroller.h" +#include "metabundle.h" //updateTags() +#include "playlist.h" +#include "playlistbrowser.h" +#include "pluginmanager.h" +#include "scrobbler.h" +#include "statusbar.h" +#include "threadweaver.h" + +#include +#include +#include + +#include +#include +#include +#include //setupCoverFetcher() +#include +#include //setupCoverFetcher() +#include +#include +#include +#include +#include + +#include //DbConnection::sqlite_power() +#include //query() +#include //usleep() + +#include +#include +#include +#include +#include + + +////////////////////////////////////////////////////////////////////////////////////////// +// CLASS CollectionDB +////////////////////////////////////////////////////////////////////////////////////////// + +CollectionDB* CollectionDB::instance() +{ + static CollectionDB db; + return &db; +} + + +CollectionDB::CollectionDB() + : EngineObserver( EngineController::instance() ) + , m_cacheDir( amaroK::saveLocation() ) + , m_coverDir( amaroK::saveLocation() ) +{ + DEBUG_BLOCK + + // create cover dir, if it doesn't exist. + if( !m_coverDir.exists( "albumcovers", false ) ) + m_coverDir.mkdir( "albumcovers", false ); + m_coverDir.cd( "albumcovers" ); + + // create image cache dir, if it doesn't exist. + if( !m_cacheDir.exists( "albumcovers/cache", false ) ) + m_cacheDir.mkdir( "albumcovers/cache", false ); + m_cacheDir.cd( "albumcovers/cache" ); + + // Load DBEngine plugin + QString query = "[X-KDE-Amarok-plugintype] == 'dbengine' and [X-KDE-Amarok-name] != '%1'"; + KTrader::OfferList offers = PluginManager::query( query.arg( "sqlite-dbengine" ) ); + m_dbEngine = (DBEngine*) PluginManager::createFromService( offers.first() ); + + // + initialize(); + // + + // TODO: Should write to config in dtor, but it crashes... + KConfig* config = amaroK::config( "Collection Browser" ); + config->writeEntry( "Database Version", DATABASE_VERSION ); + config->writeEntry( "Database Stats Version", DATABASE_STATS_VERSION ); + + startTimer( MONITOR_INTERVAL * 1000 ); + + connect( Scrobbler::instance(), SIGNAL( similarArtistsFetched( const QString&, const QStringList& ) ), + this, SLOT( similarArtistsFetched( const QString&, const QStringList& ) ) ); +} + + +CollectionDB::~CollectionDB() +{ + DEBUG_FUNC_INFO + + destroy(); + +// This crashes so it's done at the end of ctor. +// KConfig* const config = amaroK::config( "Collection Browser" ); +// config->writeEntry( "Database Version", DATABASE_VERSION ); +// config->writeEntry( "Database Stats Version", DATABASE_STATS_VERSION ); +} + + +////////////////////////////////////////////////////////////////////////////////////////// +// PUBLIC +////////////////////////////////////////////////////////////////////////////////////////// + + +DbConnection +*CollectionDB::getStaticDbConnection() +{ + return m_dbConnPool->getDbConnection(); +} + + +void +CollectionDB::returnStaticDbConnection( DbConnection *conn ) +{ + m_dbConnPool->putDbConnection( conn ); +} + + +/** + * Executes a SQL query on the already opened database + * @param statement SQL program to execute. Only one SQL statement is allowed. + * @return The queried data, or QStringList() on error. + */ +QStringList +CollectionDB::query( const QString& statement, DbConnection *conn ) +{ + if ( DEBUG ) + debug() << "Query-start: " << statement << endl; + + clock_t start = clock(); + + DbConnection *dbConn; + if ( conn != NULL ) + { + dbConn = conn; + } + else + { + dbConn = m_dbConnPool->getDbConnection(); + } + + QStringList values = dbConn->query( statement ); + + if ( conn == NULL ) + { + m_dbConnPool->putDbConnection( dbConn ); + } + + if ( DEBUG ) + { + clock_t finish = clock(); + const double duration = (double) (finish - start) / CLOCKS_PER_SEC; + debug() << "SQL-query (" << duration << "s): " << statement << endl; + } + return values; +} + + +/** + * Executes a SQL insert on the already opened database + * @param statement SQL statement to execute. Only one SQL statement is allowed. + * @return The rowid of the inserted item. + */ +int +CollectionDB::insert( const QString& statement, const QString& table, DbConnection *conn ) +{ + if ( DEBUG ) + debug() << "insert-start: " << statement << endl; + + clock_t start = clock(); + + DbConnection *dbConn; + if ( conn != NULL ) + { + dbConn = conn; + } + else + { + dbConn = m_dbConnPool->getDbConnection(); + } + + int id = dbConn->insert( statement, table ); + + if ( conn == NULL ) + { + m_dbConnPool->putDbConnection( dbConn ); + } + + if ( DEBUG ) + { + clock_t finish = clock(); + const double duration = (double) (finish - start) / CLOCKS_PER_SEC; + debug() << "SQL-insert (" << duration << "s): " << statement << endl; + } + return id; +} + + +bool +CollectionDB::isEmpty() +{ + QStringList values; + + if (m_dbConnPool->getDbConnectionType() == DbConnection::postgresql) + { + values = query( "SELECT COUNT( url ) FROM tags OFFSET 0 LIMIT 1;" ); + } + else + { + values = query( "SELECT COUNT( url ) FROM tags LIMIT 0, 1;" ); + } + + return values.isEmpty() ? true : values.first() == "0"; +} + + +bool +CollectionDB::isValid() +{ + QStringList values1; + QStringList values2; + + if (m_dbConnPool->getDbConnectionType() == DbConnection::postgresql) { + values1 = query( "SELECT COUNT( url ) FROM tags OFFSET 0 LIMIT 1;" ); + values2 = query( "SELECT COUNT( url ) FROM statistics OFFSET 0 LIMIT 1;" ); + } + else + { + values1 = query( "SELECT COUNT( url ) FROM tags LIMIT 0, 1;" ); + values2 = query( "SELECT COUNT( url ) FROM statistics LIMIT 0, 1;" ); + } + + //TODO? this returns true if value1 or value2 is not empty. Shouldn't this be and (&&)??? + return !values1.isEmpty() || !values2.isEmpty(); +} + + +void +CollectionDB::createTables( DbConnection *conn ) +{ + DEBUG_FUNC_INFO + + //create tag table + query( QString( "CREATE %1 TABLE tags%2 (" + "url " + textColumnType() + "," + "dir " + textColumnType() + "," + "createdate INTEGER," + "album INTEGER," + "artist INTEGER," + "genre INTEGER," + "title " + textColumnType() + "," + "year INTEGER," + "comment " + textColumnType() + "," + "track NUMERIC(4)," + "bitrate INTEGER," + "length INTEGER," + "samplerate INTEGER," + "sampler BOOL );" ) + .arg( conn ? "TEMPORARY" : "" ) + .arg( conn ? "_temp" : "" ), conn ); + + QString albumAutoIncrement = ""; + QString artistAutoIncrement = ""; + QString genreAutoIncrement = ""; + QString yearAutoIncrement = ""; + if ( m_dbConnPool->getDbConnectionType() == DbConnection::postgresql ) + { + query( QString( "CREATE SEQUENCE album_seq;" ), conn ); + query( QString( "CREATE SEQUENCE artist_seq;" ), conn ); + query( QString( "CREATE SEQUENCE genre_seq;" ), conn ); + query( QString( "CREATE SEQUENCE year_seq;" ), conn ); + + albumAutoIncrement = QString("DEFAULT nextval('album_seq')"); + artistAutoIncrement = QString("DEFAULT nextval('artist_seq')"); + genreAutoIncrement = QString("DEFAULT nextval('genre_seq')"); + yearAutoIncrement = QString("DEFAULT nextval('year_seq')"); + } + else if ( m_dbConnPool->getDbConnectionType() == DbConnection::mysql ) + { + albumAutoIncrement = "AUTO_INCREMENT"; + artistAutoIncrement = "AUTO_INCREMENT"; + genreAutoIncrement = "AUTO_INCREMENT"; + yearAutoIncrement = "AUTO_INCREMENT"; + } + //create album table + query( QString( "CREATE %1 TABLE album%2 (" + "id INTEGER PRIMARY KEY %3," + "name " + textColumnType() + ");" ) + .arg( conn ? "TEMPORARY" : "" ) + .arg( conn ? "_temp" : "" ) + .arg( albumAutoIncrement ), conn ); + + //create artist table + query( QString( "CREATE %1 TABLE artist%2 (" + "id INTEGER PRIMARY KEY %3," + "name " + textColumnType() + ");" ) + .arg( conn ? "TEMPORARY" : "" ) + .arg( conn ? "_temp" : "" ) + .arg( artistAutoIncrement ), conn ); + + //create genre table + query( QString( "CREATE %1 TABLE genre%2 (" + "id INTEGER PRIMARY KEY %3," + "name " + textColumnType() +");" ) + .arg( conn ? "TEMPORARY" : "" ) + .arg( conn ? "_temp" : "" ) + .arg( genreAutoIncrement ), conn ); + + //create year table + query( QString( "CREATE %1 TABLE year%2 (" + "id INTEGER PRIMARY KEY %3," + "name " + textColumnType() + ");" ) + .arg( conn ? "TEMPORARY" : "" ) + .arg( conn ? "_temp" : "" ) + .arg( yearAutoIncrement ), conn ); + + //create images table + query( QString( "CREATE %1 TABLE images%2 (" + "path " + textColumnType() + "," + "artist " + textColumnType() + "," + "album " + textColumnType() + ");" ) + .arg( conn ? "TEMPORARY" : "" ) + .arg( conn ? "_temp" : "" ), conn ); + + // create directory statistics table + query( QString( "CREATE %1 TABLE directories%2 (" + "dir " + textColumnType() + " UNIQUE," + "changedate INTEGER );" ) + .arg( conn ? "TEMPORARY" : "" ) + .arg( conn ? "_temp" : "" ), conn ); + + + //create indexes + query( QString( "CREATE INDEX album_idx%1 ON album%2( name );" ) + .arg( conn ? "_temp" : "" ).arg( conn ? "_temp" : "" ), conn ); + query( QString( "CREATE INDEX artist_idx%1 ON artist%2( name );" ) + .arg( conn ? "_temp" : "" ).arg( conn ? "_temp" : "" ), conn ); + query( QString( "CREATE INDEX genre_idx%1 ON genre%2( name );" ) + .arg( conn ? "_temp" : "" ).arg( conn ? "_temp" : "" ), conn ); + query( QString( "CREATE INDEX year_idx%1 ON year%2( name );" ) + .arg( conn ? "_temp" : "" ).arg( conn ? "_temp" : "" ), conn ); + + if ( !conn ) + { + // create related artists cache + query( QString( "CREATE TABLE related_artists (" + "artist " + textColumnType() + "," + "suggestion " + textColumnType() + "," + "changedate INTEGER );" ) ); + + query( "CREATE INDEX url_tag ON tags( url );" ); + query( "CREATE INDEX album_tag ON tags( album );" ); + query( "CREATE INDEX artist_tag ON tags( artist );" ); + query( "CREATE INDEX genre_tag ON tags( genre );" ); + query( "CREATE INDEX year_tag ON tags( year );" ); + query( "CREATE INDEX sampler_tag ON tags( sampler );" ); + + query( "CREATE INDEX images_album ON images( album );" ); + query( "CREATE INDEX images_artist ON images( artist );" ); + + query( "CREATE INDEX directories_dir ON directories( dir );" ); + query( "CREATE INDEX related_artists_artist ON related_artists( artist );" ); + } +} + + +void +CollectionDB::dropTables( DbConnection *conn ) +{ + DEBUG_FUNC_INFO + + query( QString( "DROP TABLE tags%1;" ).arg( conn ? "_temp" : "" ), conn ); + query( QString( "DROP TABLE album%1;" ).arg( conn ? "_temp" : "" ), conn ); + query( QString( "DROP TABLE artist%1;" ).arg( conn ? "_temp" : "" ), conn ); + query( QString( "DROP TABLE genre%1;" ).arg( conn ? "_temp" : "" ), conn ); + query( QString( "DROP TABLE year%1;" ).arg( conn ? "_temp" : "" ), conn ); + query( QString( "DROP TABLE images%1;" ).arg( conn ? "_temp" : "" ), conn ); + query( QString( "DROP TABLE directories%1;" ).arg( conn ? "_temp" : "" ), conn ); + if ( !conn ) + { + query( QString( "DROP TABLE related_artists;" ) ); + } + + if ( m_dbConnPool->getDbConnectionType() == DbConnection::postgresql ) + { + if (conn == NULL) { + query( QString( "DROP SEQUENCE album_seq;" ), conn ); + query( QString( "DROP SEQUENCE artist_seq;" ), conn ); + query( QString( "DROP SEQUENCE genre_seq;" ), conn ); + query( QString( "DROP SEQUENCE year_seq;" ), conn ); + } + } +} + + +void +CollectionDB::clearTables( DbConnection *conn ) +{ + DEBUG_FUNC_INFO + + QString clearCommand = "DELETE FROM"; + if ( m_dbConnPool->getDbConnectionType() == DbConnection::mysql ) + { + // TRUNCATE TABLE is faster than DELETE FROM TABLE, so use it when supported. + clearCommand = "TRUNCATE TABLE"; + } + + query( QString( "%1 tags%2;" ).arg( clearCommand ).arg( conn ? "_temp" : "" ), conn ); + query( QString( "%1 album%2;" ).arg( clearCommand ).arg( conn ? "_temp" : "" ), conn ); + query( QString( "%1 artist%2;" ).arg( clearCommand ).arg( conn ? "_temp" : "" ), conn ); + query( QString( "%1 genre%2;" ).arg( clearCommand ).arg( conn ? "_temp" : "" ), conn ); + query( QString( "%1 year%2;" ).arg( clearCommand ).arg( conn ? "_temp" : "" ), conn ); + query( QString( "%1 images%2;" ).arg( clearCommand ).arg( conn ? "_temp" : "" ), conn ); + query( QString( "%1 directories%2;" ).arg( clearCommand ).arg( conn ? "_temp" : "" ), conn ); + if ( !conn ) + { + query( QString( "%1 related_artists;" ).arg( clearCommand ) ); + } +} + + +void +CollectionDB::moveTempTables( DbConnection *conn ) +{ + insert( "INSERT INTO tags SELECT * FROM tags_temp;", NULL, conn ); + insert( "INSERT INTO album SELECT * FROM album_temp;", NULL, conn ); + insert( "INSERT INTO artist SELECT * FROM artist_temp;", NULL, conn ); + insert( "INSERT INTO genre SELECT * FROM genre_temp;", NULL, conn ); + insert( "INSERT INTO year SELECT * FROM year_temp;", NULL, conn ); + insert( "INSERT INTO images SELECT * FROM images_temp;", NULL, conn ); + insert( "INSERT INTO directories SELECT * FROM directories_temp;", NULL, conn ); +} + + +void +CollectionDB::createStatsTable() +{ + DEBUG_FUNC_INFO + + // create music statistics database + query( QString( "CREATE TABLE statistics (" + "url " + textColumnType() + " UNIQUE," + "createdate INTEGER," + "accessdate INTEGER," + "percentage FLOAT," + "playcounter INTEGER );" ) ); + + query( "CREATE INDEX url_stats ON statistics( url );" ); + query( "CREATE INDEX percentage_stats ON statistics( percentage );" ); + query( "CREATE INDEX playcounter_stats ON statistics( playcounter );" ); +} + + +void +CollectionDB::dropStatsTable() +{ + DEBUG_FUNC_INFO + + query( "DROP TABLE statistics;" ); +} + + +uint +CollectionDB::artistID( QString value, bool autocreate, const bool temporary, const bool updateSpelling, DbConnection *conn ) +{ + // lookup cache + if ( m_cacheArtist == value ) + return m_cacheArtistID; + + uint id = IDFromValue( "artist", value, autocreate, temporary, updateSpelling, conn ); + + // cache values + m_cacheArtist = value; + m_cacheArtistID = id; + + return id; +} + + +QString +CollectionDB::artistValue( uint id ) +{ + // lookup cache + if ( m_cacheArtistID == id ) + return m_cacheArtist; + + QString value = valueFromID( "artist", id ); + + // cache values + m_cacheArtist = value; + m_cacheArtistID = id; + + return value; +} + + + +uint +CollectionDB::albumID( QString value, bool autocreate, const bool temporary, const bool updateSpelling, DbConnection *conn ) +{ + // lookup cache + if ( m_cacheAlbum == value ) + return m_cacheAlbumID; + + uint id = IDFromValue( "album", value, autocreate, temporary, updateSpelling, conn ); + + // cache values + m_cacheAlbum = value; + m_cacheAlbumID = id; + + return id; +} + + +QString +CollectionDB::albumValue( uint id ) +{ + // lookup cache + if ( m_cacheAlbumID == id ) + return m_cacheAlbum; + + QString value = valueFromID( "album", id ); + + // cache values + m_cacheAlbum = value; + m_cacheAlbumID = id; + + return value; +} + + +uint +CollectionDB::genreID( QString value, bool autocreate, const bool temporary, const bool updateSpelling, DbConnection *conn ) +{ + return IDFromValue( "genre", value, autocreate, temporary, updateSpelling, conn ); +} + + +QString +CollectionDB::genreValue( uint id ) +{ + return valueFromID( "genre", id ); +} + + +uint +CollectionDB::yearID( QString value, bool autocreate, const bool temporary, const bool updateSpelling, DbConnection *conn ) +{ + return IDFromValue( "year", value, autocreate, temporary, updateSpelling, conn ); +} + + +QString +CollectionDB::yearValue( uint id ) +{ + return valueFromID( "year", id ); +} + + +uint +CollectionDB::IDFromValue( QString name, QString value, bool autocreate, const bool temporary, const bool updateSpelling, DbConnection *conn ) +{ + if ( temporary ) + name.append( "_temp" ); + else + conn = NULL; + + QStringList values = + query( QString( + "SELECT id, name FROM %1 WHERE name LIKE '%2';" ) + .arg( name ) + .arg( CollectionDB::instance()->escapeString( value ) ), conn ); + + if ( updateSpelling && !values.isEmpty() && ( values[1] != value ) ) + { + query( QString( "UPDATE %1 SET id = %2, name = '%3' WHERE id = %4;" ) + .arg( name ) + .arg( values.first() ) + .arg( CollectionDB::instance()->escapeString( value ) ) + .arg( values.first() ), conn ); + } + + //check if item exists. if not, should we autocreate it? + uint id; + if ( values.isEmpty() && autocreate ) + { + id = insert( QString( "INSERT INTO %1 ( name ) VALUES ( '%2' );" ) + .arg( name ) + .arg( CollectionDB::instance()->escapeString( value ) ), name, conn ); + + return id; + } + + return values.isEmpty() ? 0 : values.first().toUInt(); +} + + +QString +CollectionDB::valueFromID( QString table, uint id ) +{ + QStringList values = + query( QString( + "SELECT name FROM %1 WHERE id=%2;" ) + .arg( table ) + .arg( id ) ); + + + return values.isEmpty() ? 0 : values.first(); +} + + +QString +CollectionDB::albumSongCount( const QString &artist_id, const QString &album_id ) +{ + QStringList values = + query( QString( + "SELECT COUNT( url ) FROM tags WHERE album = %1 AND artist = %2;" ) + .arg( album_id ) + .arg( artist_id ) ); + return values.first(); +} + +bool +CollectionDB::albumIsCompilation( const QString &album_id ) +{ + QStringList values = + query( QString( + "SELECT sampler FROM tags WHERE sampler=%1 AND album=%2" ) + .arg( CollectionDB::instance()->boolT() ) + .arg( album_id ) ); + + return (values.count() != 0); +} + +QStringList +CollectionDB::albumTracks( const QString &artist_id, const QString &album_id ) +{ + if (m_dbConnPool->getDbConnectionType() == DbConnection::postgresql) { + return query( QString( "SELECT tags.url, tags.track AS __discard FROM tags, year WHERE tags.album = %1 AND " + "( tags.sampler = %2 OR tags.artist = %3 ) AND year.id = tags.year " + "ORDER BY tags.track;" ) + .arg( album_id ) + .arg( boolT() ) + .arg( artist_id ) ); + } + else + { + return query( QString( "SELECT tags.url FROM tags, year WHERE tags.album = %1 AND " + "( tags.sampler = 1 OR tags.artist = %2 ) AND year.id = tags.year " + "ORDER BY tags.track;" ) + .arg( album_id ) + .arg( artist_id ) ); + } +} + + +void +CollectionDB::addImageToAlbum( const QString& image, QValueList< QPair > info, DbConnection *conn ) +{ + for ( QValueList< QPair >::ConstIterator it = info.begin(); it != info.end(); ++it ) + { + if ( (*it).first.isEmpty() || (*it).second.isEmpty() ) + continue; + + debug() << "Added image for album: " << (*it).first << " - " << (*it).second << ": " << image << endl; + insert( QString( "INSERT INTO images%1 ( path, artist, album ) VALUES ( '%1', '%2', '%3' );" ) + .arg( conn ? "_temp" : "" ) + .arg( escapeString( image ) ) + .arg( escapeString( (*it).first ) ) + .arg( escapeString( (*it).second ) ), NULL, conn ); + } +} + +QImage +CollectionDB::fetchImage(const KURL& url, QString &/*tmpFile*/) +{ + if(url.protocol() != "file") + { + QString tmpFile; + KIO::NetAccess::download( url, tmpFile, 0); //TODO set 0 to the window, though it probably doesn't really matter + return QImage(tmpFile); + } + else + { + return QImage( url.path() ); + } + +} +bool +CollectionDB::setAlbumImage( const QString& artist, const QString& album, const KURL& url ) +{ + QString tmpFile; + bool success = setAlbumImage( artist, album, fetchImage(url, tmpFile) ); + KIO::NetAccess::removeTempFile( tmpFile ); //only removes file if it was created with NetAccess + return success; +} + + +bool +CollectionDB::setAlbumImage( const QString& artist, const QString& album, QImage img, const QString& amazonUrl ) +{ + debug() << "Saving cover for: " << artist << " - " << album << endl; + + //show a wait cursor for the duration + amaroK::OverrideCursor keep; + + // remove existing album covers + removeAlbumImage( artist, album ); + + QDir largeCoverDir( amaroK::saveLocation( "albumcovers/large/" ) ); + QCString key = md5sum( artist, album ); + + // Save Amazon product page URL as embedded string, for later retreival + if ( !amazonUrl.isEmpty() ) + img.setText( "amazon-url", 0, amazonUrl ); + + return img.save( largeCoverDir.filePath( key ), "PNG"); +} + + +QString +CollectionDB::findImageByMetabundle( MetaBundle trackInformation, uint width ) +{ + if( width == 1 ) width = AmarokConfig::coverPreviewSize(); + + QCString widthKey = makeWidthKey( width ); + QCString tagKey = md5sum( trackInformation.artist(), trackInformation.album() ); + QDir tagCoverDir( amaroK::saveLocation( "albumcovers/tagcover/" ) ); + + //FIXME: the cached versions will never be refreshed + if ( tagCoverDir.exists( widthKey + tagKey ) ) + { + // cached version + return tagCoverDir.filePath( widthKey + tagKey ); + } else + { + // look into the tag + TagLib::MPEG::File f( QFile::encodeName( trackInformation.url().path() ) ); + TagLib::ID3v2::Tag *tag = f.ID3v2Tag(); + + if ( tag ) + { + TagLib::ID3v2::FrameList l = f.ID3v2Tag()->frameListMap()[ "APIC" ]; + if ( !l.isEmpty() ) + { + debug() << "Found APIC frame(s)" << endl; + TagLib::ID3v2::Frame *f = l.front(); + TagLib::ID3v2::AttachedPictureFrame *ap = (TagLib::ID3v2::AttachedPictureFrame*)f; + + const TagLib::ByteVector &imgVector = ap->picture(); + debug() << "Size of image: " << imgVector.size() << " byte" << endl; + + // ignore APIC frames without picture and those with obviously bogus size + if( imgVector.size() == 0 || imgVector.size() > 10000000 /*10MB*/ ) + return QString(); + + QImage image; + if( image.loadFromData((const uchar*)imgVector.data(), imgVector.size()) ) + { + if ( width > 1 ) + { + image.smoothScale( width, width, QImage::ScaleMin ).save( m_cacheDir.filePath( widthKey + tagKey ), "PNG" ); + return m_cacheDir.filePath( widthKey + tagKey ); + } else + { + image.save( tagCoverDir.filePath( tagKey ), "PNG" ); + return tagCoverDir.filePath( tagKey ); + } + } // image.isNull + } // apic list is empty + } // tag is empty + } // caching + + return QString(); +} + + +QString +CollectionDB::findImageByArtistAlbum( const QString &artist, const QString &album, uint width ) +{ + QCString widthKey = makeWidthKey( width ); + + if ( artist.isEmpty() && album.isEmpty() ) + return notAvailCover( width ); + else + { + QCString key = md5sum( artist, album ); + + // check cache for existing cover + if ( m_cacheDir.exists( widthKey + key ) ) + return m_cacheDir.filePath( widthKey + key ); + else + { + // we need to create a scaled version of this cover + QDir largeCoverDir( amaroK::saveLocation( "albumcovers/large/" ) ); + if ( largeCoverDir.exists( key ) ) + if ( width > 1 ) + { + QImage img( largeCoverDir.filePath( key ) ); + img.smoothScale( width, width, QImage::ScaleMin ).save( m_cacheDir.filePath( widthKey + key ), "PNG" ); + + return m_cacheDir.filePath( widthKey + key ); + } + else + return largeCoverDir.filePath( key ); + } + + // no amazon cover found, let's try to find a cover in the song's directory + return getImageForAlbum( artist, album, width ); + } +} + + +QString +CollectionDB::albumImage( const QString &artist, const QString &album, uint width ) +{ + QString s; + // we aren't going to need a 1x1 size image. this is just a quick hack to be able to show full size images. + if ( width == 1 ) width = AmarokConfig::coverPreviewSize(); + + s = findImageByArtistAlbum( artist, album, width ); + if ( s == notAvailCover( width ) ) + return findImageByArtistAlbum( "", album, width ); + + return s; +} + + +QString +CollectionDB::albumImage( const uint artist_id, const uint album_id, const uint width ) +{ + return albumImage( artistValue( artist_id ), albumValue( album_id ), width ); +} + + +QString +CollectionDB::albumImage( MetaBundle trackInformation, uint width ) +{ + QString path = findImageByMetabundle( trackInformation, width ); + if( path.isEmpty() ) + path =albumImage( trackInformation.artist(), trackInformation.album(), width ); + + return path; +} + + +QCString +CollectionDB::makeWidthKey( uint width ) +{ + return QString::number( width ).local8Bit() + "@"; +} + +// get image from path +QString +CollectionDB::getImageForAlbum( const QString& artist, const QString& album, uint width ) +{ + if ( width == 1 ) width = AmarokConfig::coverPreviewSize(); + QCString widthKey = QString::number( width ).local8Bit() + "@"; + + if ( album.isEmpty() ) + return notAvailCover( width ); + + QStringList values = + query( QString( + "SELECT path FROM images WHERE artist LIKE '%1' AND album LIKE '%2' ORDER BY path;" ) + .arg( escapeString( artist ) ) + .arg( escapeString( album ) ) ); + + if ( !values.isEmpty() ) + { + QString image( values.first() ); + uint matches = 0; + uint maxmatches = 0; + for ( uint i = 0; i < values.count(); i++ ) + { + matches = values[i].contains( "front", false ) + values[i].contains( "cover", false ) + values[i].contains( "folder", false ); + if ( matches > maxmatches ) + { + image = values[i]; + maxmatches = matches; + } + } + + QCString key = md5sum( artist, album, image ); + + if ( width > 1 ) + { + if ( !m_cacheDir.exists( widthKey + key ) ) + { + QImage img = QImage( image ); + img.smoothScale( width, width, QImage::ScaleMin ).save( m_cacheDir.filePath( widthKey + key ), "PNG" ); + } + + return m_cacheDir.filePath( widthKey + key ); + } + else //large image + { + return image; + } + } + + return notAvailCover( width ); +} + + +bool +CollectionDB::removeAlbumImage( const QString &artist, const QString &album ) +{ + QCString widthKey = "*@"; + QCString key = md5sum( artist, album ); + + // remove scaled versions of images + QStringList scaledList = m_cacheDir.entryList( widthKey + key ); + if ( scaledList.count() > 0 ) + for ( uint i = 0; i < scaledList.count(); i++ ) + QFile::remove( m_cacheDir.filePath( scaledList[ i ] ) ); + + // remove large, original image + QDir largeCoverDir( amaroK::saveLocation( "albumcovers/large/" ) ); + + if ( largeCoverDir.exists( key ) && QFile::remove( largeCoverDir.filePath( key ) ) ) { + emit coverRemoved( artist, album ); + return true; + } + + return false; +} + +bool +CollectionDB::removeAlbumImage( const uint artist_id, const uint album_id ) +{ + return removeAlbumImage( artistValue( artist_id ), albumValue( album_id ) ); +} + + +QString +CollectionDB::notAvailCover( int width ) +{ + if ( !width ) width = AmarokConfig::coverPreviewSize(); + QString widthKey = QString::number( width ) + "@"; + + if( m_cacheDir.exists( widthKey + "nocover.png" ) ) + return m_cacheDir.filePath( widthKey + "nocover.png" ); + else + { + QImage nocover( locate( "data", "amarok/images/nocover.png" ) ); + nocover.smoothScale( width, width, QImage::ScaleMin ).save( m_cacheDir.filePath( widthKey + "nocover.png" ), "PNG" ); + return m_cacheDir.filePath( widthKey + "nocover.png" ); + } +} + + +QStringList +CollectionDB::artistList( bool withUnknowns, bool withCompilations ) +{ + QueryBuilder qb; + qb.addReturnValue( QueryBuilder::tabArtist, QueryBuilder::valName ); + + if ( !withUnknowns ) + qb.excludeMatch( QueryBuilder::tabArtist, i18n( "Unknown" ) ); + if ( !withCompilations ) + qb.setOptions( QueryBuilder::optNoCompilations ); + + qb.setOptions( QueryBuilder::optRemoveDuplicates ); + qb.sortBy( QueryBuilder::tabArtist, QueryBuilder::valName ); + return qb.run(); +} + + +QStringList +CollectionDB::albumList( bool withUnknowns, bool withCompilations ) +{ + QueryBuilder qb; + qb.addReturnValue( QueryBuilder::tabAlbum, QueryBuilder::valName ); + + if ( !withUnknowns ) + qb.excludeMatch( QueryBuilder::tabAlbum, i18n( "Unknown" ) ); + if ( !withCompilations ) + qb.setOptions( QueryBuilder::optNoCompilations ); + + qb.setOptions( QueryBuilder::optRemoveDuplicates ); + qb.sortBy( QueryBuilder::tabAlbum, QueryBuilder::valName ); + return qb.run(); +} + + +QStringList +CollectionDB::genreList( bool withUnknowns, bool withCompilations ) +{ + QueryBuilder qb; + qb.addReturnValue( QueryBuilder::tabGenre, QueryBuilder::valName ); + + if ( !withUnknowns ) + qb.excludeMatch( QueryBuilder::tabGenre, i18n( "Unknown" ) ); + if ( !withCompilations ) + qb.setOptions( QueryBuilder::optNoCompilations ); + + qb.setOptions( QueryBuilder::optRemoveDuplicates ); + qb.sortBy( QueryBuilder::tabGenre, QueryBuilder::valName ); + return qb.run(); +} + + +QStringList +CollectionDB::yearList( bool withUnknowns, bool withCompilations ) +{ + QueryBuilder qb; + qb.addReturnValue( QueryBuilder::tabYear, QueryBuilder::valName ); + + if ( !withUnknowns ) + qb.excludeMatch( QueryBuilder::tabYear, i18n( "Unknown" ) ); + if ( !withCompilations ) + qb.setOptions( QueryBuilder::optNoCompilations ); + + qb.setOptions( QueryBuilder::optRemoveDuplicates ); + qb.sortBy( QueryBuilder::tabYear, QueryBuilder::valName ); + return qb.run(); +} + + +QStringList +CollectionDB::albumListOfArtist( const QString &artist, bool withUnknown, bool withCompilations ) +{ + if (m_dbConnPool->getDbConnectionType() == DbConnection::postgresql) + { + return query( "SELECT DISTINCT album.name, lower( album.name ) AS __discard FROM tags, album, artist WHERE " + "tags.album = album.id AND tags.artist = artist.id " + "AND artist.name = '" + escapeString( artist ) + "' " + + ( withUnknown ? QString::null : "AND album.name <> '' " ) + + ( withCompilations ? QString::null : "AND tags.sampler = " + boolF() ) + + " ORDER BY lower( album.name );" ); + } + else + { + return query( "SELECT DISTINCT album.name FROM tags, album, artist WHERE " + "tags.album = album.id AND tags.artist = artist.id " + "AND artist.name = '" + escapeString( artist ) + "' " + + ( withUnknown ? QString::null : "AND album.name <> '' " ) + + ( withCompilations ? QString::null : "AND tags.sampler = " + boolF() ) + + " ORDER BY lower( album.name );" ); + } +} + + +QStringList +CollectionDB::artistAlbumList( bool withUnknown, bool withCompilations ) +{ + if (m_dbConnPool->getDbConnectionType() == DbConnection::postgresql) + { + return query( "SELECT DISTINCT artist.name, album.name, lower( album.name ) AS __discard FROM tags, album, artist WHERE " + "tags.album = album.id AND tags.artist = artist.id " + + ( withUnknown ? QString::null : "AND album.name <> '' AND artist.name <> '' " ) + + ( withCompilations ? QString::null : "AND tags.sampler = " + boolF() ) + + " ORDER BY lower( album.name );" ); + } + else + { + return query( "SELECT DISTINCT artist.name, album.name FROM tags, album, artist WHERE " + "tags.album = album.id AND tags.artist = artist.id " + + ( withUnknown ? QString::null : "AND album.name <> '' AND artist.name <> '' " ) + + ( withCompilations ? QString::null : "AND tags.sampler = " + boolF() ) + + " ORDER BY lower( album.name );" ); + } +} + + +bool +CollectionDB::addSong( MetaBundle* bundle, const bool incremental, DbConnection *conn ) +{ + if ( !QFileInfo( bundle->url().path() ).isReadable() ) return false; + + QString command = "INSERT INTO tags_temp " + "( url, dir, createdate, album, artist, genre, year, title, comment, track, sampler, length, bitrate, samplerate ) " + "VALUES ('"; + + QString artist = bundle->artist(); + QString title = bundle->title(); + if ( title.isEmpty() ) + { + title = bundle->url().fileName(); + if ( bundle->url().fileName().find( '-' ) > 0 ) + { + if ( artist.isEmpty() ) artist = bundle->url().fileName().section( '-', 0, 0 ).stripWhiteSpace(); + title = bundle->url().fileName().section( '-', 1 ).stripWhiteSpace(); + title = title.left( title.findRev( '.' ) ).stripWhiteSpace(); + if ( title.isEmpty() ) title = bundle->url().fileName(); + } + } + bundle->setArtist( artist ); + bundle->setTitle( title ); + + command += escapeString( bundle->url().path() ) + "','"; + command += escapeString( bundle->url().directory() ) + "',"; + command += QString::number( QFileInfo( bundle->url().path() ).lastModified().toTime_t() ) + ","; + + command += escapeString( QString::number( albumID( bundle->album(), true, !incremental, false, conn ) ) ) + ","; + command += escapeString( QString::number( artistID( bundle->artist(), true, !incremental, false, conn ) ) ) + ","; + command += escapeString( QString::number( genreID( bundle->genre(), true, !incremental, false, conn ) ) ) + ",'"; + command += escapeString( QString::number( yearID( bundle->year(), true, !incremental, false, conn ) ) ) + "','"; + + command += escapeString( bundle->title() ) + "','"; + command += escapeString( bundle->comment() ) + "', "; + command += ( bundle->track().isEmpty() ? "NULL" : escapeString( bundle->track() ) ) + " , "; + command += artist == i18n( "Various Artists" ) ? boolT() + "," : boolF() + ","; + + // NOTE any of these may be -1 or -2, this is what we want + // see MetaBundle::Undetermined + command += QString::number( bundle->length() ) + ","; + command += QString::number( bundle->bitrate() ) + ","; + command += QString::number( bundle->sampleRate() ) + ")"; + + //FIXME: currently there's no way to check if an INSERT query failed or not - always return true atm. + // Now it might be possible as insert returns the rowid. + insert( command, NULL, conn ); + return true; +} + + +static void +fillInBundle( QStringList values, MetaBundle &bundle ) +{ + //TODO use this whenever possible + + // crash prevention + while( values.count() != 10 ) + values += "IF YOU CAN SEE THIS THERE IS A BUG!"; + + QStringList::ConstIterator it = values.begin(); + + bundle.setAlbum ( *it ); ++it; + bundle.setArtist ( *it ); ++it; + bundle.setGenre ( *it ); ++it; + bundle.setTitle ( *it ); ++it; + bundle.setYear ( *it ); ++it; + bundle.setComment ( *it ); ++it; + bundle.setTrack ( *it ); ++it; + bundle.setBitrate ( (*it).toInt() ); ++it; + bundle.setLength ( (*it).toInt() ); ++it; + bundle.setSampleRate( (*it).toInt() ); +} + +bool +CollectionDB::bundleForUrl( MetaBundle* bundle ) +{ + QStringList values = query( QString( + "SELECT album.name, artist.name, genre.name, tags.title, " + "year.name, tags.comment, tags.track, tags.bitrate, tags.length, " + "tags.samplerate " + "FROM tags, album, artist, genre, year " + "WHERE album.id = tags.album AND artist.id = tags.artist AND " + "genre.id = tags.genre AND year.id = tags.year AND tags.url = '%1';" ) + .arg( escapeString( bundle->url().path() ) ) ); + + if ( !values.empty() ) + fillInBundle( values, *bundle ); + + return !values.isEmpty(); +} + + +QValueList +CollectionDB::bundlesByUrls( const KURL::List& urls ) +{ + typedef QValueList BundleList; + BundleList bundles; + QStringList paths; + QueryBuilder qb; + + for( KURL::List::ConstIterator it = urls.begin(), end = urls.end(), last = urls.fromLast(); it != end; ++it ) + { + // non file stuff won't exist in the db, but we still need to + // re-insert it into the list we return, just with no tags assigned + paths += (*it).protocol() == "file" ? (*it).path() : (*it).url(); + + if( paths.count() == 50 || it == last ) + { + qb.clear(); + + qb.addReturnValue( QueryBuilder::tabAlbum, QueryBuilder::valName ); + qb.addReturnValue( QueryBuilder::tabArtist, QueryBuilder::valName ); + qb.addReturnValue( QueryBuilder::tabGenre, QueryBuilder::valName ); + qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valTitle ); + qb.addReturnValue( QueryBuilder::tabYear, QueryBuilder::valName ); + qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valComment ); + qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valTrack ); + qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valBitrate ); + qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valLength ); + qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valSamplerate ); + qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valURL ); + + qb.addURLFilters( paths ); + qb.setOptions( QueryBuilder::optRemoveDuplicates ); + + const QStringList values = qb.run(); + + BundleList buns50; + MetaBundle b; + foreach( values ) + { + b.setAlbum ( *it ); + b.setArtist ( *++it ); + b.setGenre ( *++it ); + b.setTitle ( *++it ); + b.setYear ( *++it ); + b.setComment ( *++it ); + b.setTrack ( *++it ); + b.setBitrate ( (*++it).toInt() ); + b.setLength ( (*++it).toInt() ); + b.setSampleRate( (*++it).toInt() ); + b.setPath ( *++it ); + + buns50.append( b ); + } + + // we get no guarantee about the order that the database + // will return our values, and sqlite indeed doesn't return + // them in the desired order :( (MySQL does though) + foreach( paths ) { + for( BundleList::Iterator jt = buns50.begin(), end = buns50.end(); jt != end; ++jt ) + if ( (*jt).url().path() == (*it) ) { + bundles += *jt; + buns50.remove( jt ); + goto success; + } + + // if we get here, we didn't find an entry + debug() << "No bundle recovered for: " << *it << endl; + b = MetaBundle(); + b.setUrl( KURL::fromPathOrURL(*it) ); + bundles += b; + + success: ; + } + + paths.clear(); + } + } + + return bundles; +} + + +void +CollectionDB::addAudioproperties( const MetaBundle& bundle ) +{ + query( QString( "UPDATE tags SET bitrate='%1', length='%2', samplerate='%3' WHERE url='%4';" ) + .arg( bundle.bitrate() ) + .arg( bundle.length() ) + .arg( bundle.sampleRate() ) + .arg( escapeString( bundle.url().path() ) ) ); +} + + +int +CollectionDB::addSongPercentage( const QString &url, int percentage ) +{ + float score; + QStringList values = + query( QString( + "SELECT playcounter, createdate, percentage FROM statistics " + "WHERE url = '%1';" ) + .arg( escapeString( url ) ) ); + + // check boundaries + if ( percentage > 100 ) percentage = 100; + if ( percentage < 1 ) percentage = 1; + + if ( !values.isEmpty() ) + { + // entry exists, increment playcounter and update accesstime + score = ( ( values[2].toDouble() * values.first().toInt() ) + percentage ) / ( values.first().toInt() + 1 ); + + if (m_dbConnPool->getDbConnectionType() == DbConnection::postgresql) { + query( QString( "UPDATE statistics SET percentage=%1, playcounter=%2+1 WHERE url='%3';" ) + .arg( score ) + .arg( values[0] + " + 1" ) + .arg( escapeString( url ) ) ); + } + else + { + query( QString( "REPLACE INTO statistics ( url, createdate, accessdate, percentage, playcounter ) " + "VALUES ( '%1', %2, %3, %4, %5 );" ) + .arg( escapeString( url ) ) + .arg( values[1] ) + .arg( QDateTime::currentDateTime().toTime_t() ) + .arg( score ) + .arg( values[0] + " + 1" ) ); + } + } + else + { + // entry didn't exist yet, create a new one + score = ( ( 50 + percentage ) / 2 ); + + insert( QString( "INSERT INTO statistics ( url, createdate, accessdate, percentage, playcounter ) " + "VALUES ( '%1', %2, %3, %4, 1 );" ) + .arg( escapeString( url ) ) + .arg( QDateTime::currentDateTime().toTime_t() ) + .arg( QDateTime::currentDateTime().toTime_t() ) + .arg( score ), NULL ); + } + + int iscore = getSongPercentage( url ); + emit scoreChanged( url, iscore ); + return iscore; +} + + +int +CollectionDB::getSongPercentage( const QString &url ) +{ + QStringList values = query( QString( "SELECT round( percentage + 0.4 ) FROM statistics WHERE url = '%1';" ) + .arg( escapeString( url ) ) ); + + if( values.count() ) + return values.first().toInt(); + + return 0; +} + + +void +CollectionDB::setSongPercentage( const QString &url , int percentage ) +{ + QStringList values = + query( QString( + "SELECT playcounter, createdate, accessdate FROM statistics WHERE url = '%1';" ) + .arg( escapeString( url ) ) ); + + // check boundaries + if ( percentage > 100 ) percentage = 100; + if ( percentage < 1 ) percentage = 1; + + if ( !values.isEmpty() ) + { + if (m_dbConnPool->getDbConnectionType() == DbConnection::postgresql) { + query( QString( "UPDATE statistics SET percentage=%1 WHERE url='%2';" ) + .arg( percentage ) + .arg( escapeString( url ) ) ); + } + else + { + // entry exists + query( QString( "REPLACE INTO statistics ( url, createdate, accessdate, percentage, playcounter ) " + "VALUES ( '%1', '%2', '%3', %4, %5 );" ) + .arg( escapeString( url ) ) + .arg( values[1] ) + .arg( values[2] ) + .arg( percentage ) + .arg( values[0] ) ); + } + } + else + { + insert( QString( "INSERT INTO statistics ( url, createdate, accessdate, percentage, playcounter ) " + "VALUES ( '%1', %2, %3, %4, 0 );" ) + .arg( escapeString( url ) ) + .arg( QDateTime::currentDateTime().toTime_t() ) + .arg( QDateTime::currentDateTime().toTime_t() ) + .arg( percentage ), NULL ); + } + + emit scoreChanged( url, percentage ); +} + + +void +CollectionDB::updateDirStats( QString path, const long datetime, DbConnection *conn ) +{ + if ( path.endsWith( "/" ) ) + path = path.left( path.length() - 1 ); + + if (m_dbConnPool->getDbConnectionType() == DbConnection::postgresql) { + query( QString( "UPDATE directories%1 SET changedate=%2 WHERE dir='%3';") + .arg( conn ? "_temp" : "" ) + .arg( datetime ) + .arg( escapeString( path ) ), conn ); + } + else + { + query( QString( "REPLACE INTO directories%1 ( dir, changedate ) VALUES ( '%3', %2 );" ) + .arg( conn ? "_temp" : "" ) + .arg( datetime ) + .arg( escapeString( path ) ), + conn ); + } +} + + +void +CollectionDB::removeSongsInDir( QString path ) +{ + if ( path.endsWith( "/" ) ) + path = path.left( path.length() - 1 ); + + query( QString( "DELETE FROM tags WHERE dir = '%1';" ) + .arg( escapeString( path ) ) ); +} + + +bool +CollectionDB::isDirInCollection( QString path ) +{ + if ( path.endsWith( "/" ) ) + path = path.left( path.length() - 1 ); + + QStringList values = + query( QString( "SELECT changedate FROM directories WHERE dir = '%1';" ) + .arg( escapeString( path ) ) ); + + return !values.isEmpty(); +} + + +bool +CollectionDB::isFileInCollection( const QString &url ) +{ + QStringList values = + query( QString( "SELECT url FROM tags WHERE url = '%1';" ) + .arg( escapeString( url ) ) ); + + return !values.isEmpty(); +} + + +void +CollectionDB::removeSongs( const KURL::List& urls ) +{ + for( KURL::List::ConstIterator it = urls.begin(), end = urls.end(); it != end; ++it ) + { + query( QString( "DELETE FROM tags WHERE url = '%1';" ) + .arg( escapeString( (*it).path() ) ) ); + } +} + + +QStringList +CollectionDB::similarArtists( const QString &artist, uint count ) +{ + QStringList values; + + if (m_dbConnPool->getDbConnectionType() == DbConnection::postgresql) { + values = query( QString( "SELECT suggestion FROM related_artists WHERE artist = '%1' OFFSET 0 LIMIT %2;" ) + .arg( escapeString( artist ) ).arg( count ) ); + } + else + { + values = query( QString( "SELECT suggestion FROM related_artists WHERE artist = '%1' LIMIT 0, %2;" ) + .arg( escapeString( artist ) ).arg( count ) ); + } + + if ( values.isEmpty() ) + Scrobbler::instance()->similarArtists( artist ); + + return values; +} + + +void +CollectionDB::checkCompilations( const QString &path, const bool temporary, DbConnection *conn ) +{ + QStringList albums; + QStringList artists; + QStringList dirs; + + albums = query( QString( "SELECT DISTINCT album.name FROM tags_temp, album%1 AS album WHERE tags_temp.dir = '%2' AND album.id = tags_temp.album;" ) + .arg( temporary ? "_temp" : "" ) + .arg( escapeString( path ) ), conn ); + + for ( uint i = 0; i < albums.count(); i++ ) + { + if ( albums[ i ].isEmpty() ) continue; + + const uint album_id = albumID( albums[ i ], false, temporary, false, conn ); + artists = query( QString( "SELECT DISTINCT artist.name FROM tags_temp, artist%1 AS artist WHERE tags_temp.album = '%2' AND tags_temp.artist = artist.id;" ) + .arg( temporary ? "_temp" : "" ) + .arg( album_id ), conn ); + dirs = query( QString( "SELECT DISTINCT dir FROM tags_temp WHERE album = '%1';" ) + .arg( album_id ), conn ); + + if ( artists.count() > dirs.count() ) + { + debug() << "Detected compilation: " << albums[ i ] << " - " << artists.count() << ":" << dirs.count() << endl; + query( QString( "UPDATE tags_temp SET sampler = %1 WHERE album = '%2';" ) + .arg(boolT()).arg( album_id ), conn ); + } + } +} + + +void +CollectionDB::setCompilation( const QString &album, const bool enabled, const bool updateView ) +{ + query( QString( "UPDATE tags, album SET tags.sampler = %1 WHERE tags.album = album.id AND album.name = '%2';" ) + .arg( enabled ? "1" : "0" ) + .arg( escapeString( album ) ) ); + + // Update the Collection-Browser view, + // using QTimer to make sure we don't manipulate the GUI from a thread + if ( updateView ) + QTimer::singleShot( 0, CollectionView::instance(), SLOT( renderView() ) ); +} + + +void +CollectionDB::removeDirFromCollection( QString path ) +{ + if ( path.endsWith( "/" ) ) + path = path.left( path.length() - 1 ); + + query( QString( "DELETE FROM directories WHERE dir = '%1';" ) + .arg( escapeString( path ) ) ); +} + + +void +CollectionDB::updateTags( const QString &url, const MetaBundle &bundle, const bool updateView ) +{ + QString command = "UPDATE tags SET "; + command += "title = '" + escapeString( bundle.title() ) + "', "; + command += "artist = " + QString::number( artistID( bundle.artist(), true, false, true ) ) + ", "; + command += "album = " + QString::number( albumID( bundle.album(), true, false, true ) ) + ", "; + command += "genre = " + QString::number( genreID( bundle.genre(), true, false, true ) ) + ", "; + command += "year = " + QString::number( yearID( bundle.year(), true, false, true ) ) + ", "; + if ( !bundle.track().isEmpty() ) + command += "track = " + bundle.track() + ", "; + command += "comment = '" + escapeString( bundle.comment() ) + "' "; + command += "WHERE url = '" + escapeString( url ) + "';"; + + query( command ); + + if ( EngineController::instance()->bundle().url() == bundle.url() ) + { + debug() << "Current song edited, updating widgets: " << bundle.title() << endl; + EngineController::instance()->currentTrackMetaDataChanged( bundle ); + } + + // Update the Collection-Browser view, + // using QTimer to make sure we don't manipulate the GUI from a thread + if ( updateView ) + QTimer::singleShot( 0, CollectionView::instance(), SLOT( renderView() ) ); +} + + +void +CollectionDB::updateURL( const QString &url, const bool updateView ) +{ + // don't use the KURL ctor as it checks the db first + MetaBundle bundle; + bundle.setPath( url ); + bundle.readTags( TagLib::AudioProperties::Fast ); + + updateTags( url, bundle, updateView ); +} + + +void +CollectionDB::applySettings() +{ + bool recreateConnections = false; + if ( AmarokConfig::databaseEngine().toInt() != m_dbConnPool->getDbConnectionType() ) + { + recreateConnections = true; + } + else if ( AmarokConfig::databaseEngine().toInt() == DbConnection::mysql ) + { + // Using MySQL, so check if MySQL settings were changed + const MySqlConfig *config = + static_cast ( m_dbConnPool->getDbConfig() ); + if ( AmarokConfig::mySqlHost() != config->host() ) + { + recreateConnections = true; + } + else if ( AmarokConfig::mySqlPort() != config->port() ) + { + recreateConnections = true; + } + else if ( AmarokConfig::mySqlDbName() != config->database() ) + { + recreateConnections = true; + } + else if ( AmarokConfig::mySqlUser() != config->username() ) + { + recreateConnections = true; + } + else if ( AmarokConfig::mySqlPassword() != config->password() ) + { + recreateConnections = true; + } + } + else if ( AmarokConfig::databaseEngine().toInt() == DbConnection::postgresql ) + { + const PostgresqlConfig *config = + static_cast ( m_dbConnPool->getDbConfig() ); + if ( AmarokConfig::postgresqlConninfo() != config->conninfo() ) + { + recreateConnections = true; + } + } + if ( recreateConnections ) + { + debug() + << "Database engine settings changed: " + << "recreating DbConnections" << endl; + // If Database engine was changed, recreate DbConnections. + destroy(); + initialize(); + CollectionView::instance()->renderView(); + emit databaseEngineChanged(); + } +} + + +////////////////////////////////////////////////////////////////////////////////////////// +// PROTECTED +////////////////////////////////////////////////////////////////////////////////////////// + +QCString +CollectionDB::md5sum( const QString& artist, const QString& album, const QString& file ) +{ + KMD5 context( artist.lower().local8Bit() + album.lower().local8Bit() + file.local8Bit() ); +// debug() << "MD5 SUM for " << artist << ", " << album << ": " << context.hexDigest() << endl; + return context.hexDigest(); +} + + +void CollectionDB::engineTrackEnded( int finalPosition, int trackLength ) +{ + //This is where percentages are calculated + //TODO statistics are not calculated when currentTrack doesn't exist + + // Don't update statistics if song has been played for less than 15 seconds + // if ( finalPosition < 15000 ) return; + + const KURL url = EngineController::instance()->bundle().url(); + if ( url.path().isEmpty() ) return; + + // sanity check + if ( finalPosition > trackLength || finalPosition <= 0 ) + finalPosition = trackLength; + + int pct = (int) ( ( (double) finalPosition / (double) trackLength ) * 100 ); + + // increase song counter & calculate new statistics + addSongPercentage( url.path(), pct ); +} + + +void +CollectionDB::timerEvent( QTimerEvent* ) +{ + if ( AmarokConfig::monitorChanges() ) + scanMonitor(); +} + + +////////////////////////////////////////////////////////////////////////////////////////// +// PUBLIC SLOTS +////////////////////////////////////////////////////////////////////////////////////////// + +void +CollectionDB::fetchCover( QWidget* parent, const QString& artist, const QString& album, bool noedit ) //SLOT +{ + #ifdef AMAZON_SUPPORT + debug() << "Fetching cover for " << artist << " - " << album << endl; + + CoverFetcher* fetcher = new CoverFetcher( parent, artist, album ); + connect( fetcher, SIGNAL(result( CoverFetcher* )), SLOT(coverFetcherResult( CoverFetcher* )) ); + fetcher->setUserCanEditQuery( !noedit ); + fetcher->startFetch(); + #endif +} + + +void +CollectionDB::scanMonitor() //SLOT +{ + scanModifiedDirs(); +} + + +void +CollectionDB::startScan() //SLOT +{ + QStringList folders = MountPointManager::instance()->collectionFolders(); + + if ( folders.isEmpty() ) { + dropTables(); + createTables(); + } + else if( PlaylistBrowser::instance() ) + { + emit scanStarted(); + + ThreadWeaver::instance()->queueJob( new CollectionReader( this, folders ) ); + } +} + + +void +CollectionDB::stopScan() //SLOT +{ + ThreadWeaver::instance()->abortAllJobsNamed( "CollectionReader" ); +} + + +////////////////////////////////////////////////////////////////////////////////////////// +// PRIVATE SLOTS +////////////////////////////////////////////////////////////////////////////////////////// + +void +CollectionDB::dirDirty( const QString& path ) +{ + debug() << k_funcinfo << "Dirty: " << path << endl; + + ThreadWeaver::instance()->queueJob( new CollectionReader( this, path ) ); +} + + +void +CollectionDB::coverFetcherResult( CoverFetcher *fetcher ) +{ + if( fetcher->wasError() ) { + error() << fetcher->errors() << endl; + emit coverFetcherError( fetcher->errors().front() ); + } + + else { + setAlbumImage( fetcher->artist(), fetcher->album(), fetcher->image(), fetcher->amazonURL() ); + emit coverFetched( fetcher->artist(), fetcher->album() ); + } +} + +/** + * This query is fairly slow with sqlite, and often happens just + * after the OSD is shown. Threading it restores responsivity. + */ +class SimilarArtistsInsertionJob : public ThreadWeaver::DependentJob +{ + virtual bool doJob() + { + CollectionDB::instance()->query( QString( "DELETE FROM related_artists WHERE artist = '%1';" ).arg( escapedArtist ) ); + + const QString sql = "INSERT INTO related_artists ( artist, suggestion, changedate ) VALUES ( '%1', '%2', 0 );"; + foreach( suggestions ) + CollectionDB::instance()->insert( sql + .arg( escapedArtist ) + .arg( CollectionDB::instance()->escapeString( *it ) ), NULL ); + + return true; + } + + virtual void completeJob() { emit CollectionDB::instance()->similarArtistsFetched( artist ); } + + const QString artist; + const QString escapedArtist; + const QStringList suggestions; + +public: + SimilarArtistsInsertionJob( CollectionDB *parent, const QString &s, const QStringList &list ) + : ThreadWeaver::DependentJob( parent, "SimilarArtistsInsertionJob" ) + , artist( s ) + , escapedArtist( parent->escapeString( s ) ) + , suggestions( list ) + {} +}; + +void +CollectionDB::similarArtistsFetched( const QString& artist, const QStringList& suggestions ) +{ + debug() << "Received similar artists\n"; + + ThreadWeaver::instance()->queueJob( new SimilarArtistsInsertionJob( this, artist, suggestions ) ); +} + + +////////////////////////////////////////////////////////////////////////////////////////// +// PRIVATE +////////////////////////////////////////////////////////////////////////////////////////// + + +void +CollectionDB::initialize() +{ + m_dbConnPool = new DbConnectionPool(); + DbConnection *dbConn = m_dbConnPool->getDbConnection(); + m_dbConnPool->putDbConnection( dbConn ); + + KConfig* config = amaroK::config( "Collection Browser" ); + if(!dbConn->isConnected()) + amaroK::MessageQueue::instance()->addMessage(dbConn->lastError()); + if ( !dbConn->isInitialized() || !isValid() ) + { + createTables(); + createStatsTable(); + } + else + { + //remove database file if version is incompatible + if ( config->readNumEntry( "Database Version", 0 ) != DATABASE_VERSION ) + { + debug() << "Rebuilding database!" << endl; + dropTables(); + createTables(); + } + if ( config->readNumEntry( "Database Stats Version", 0 ) != DATABASE_STATS_VERSION ) + { + debug() << "Rebuilding stats-database!" << endl; + dropStatsTable(); + createStatsTable(); + } + } + + m_dbConnPool->createDbConnections(); +} + + +void +CollectionDB::destroy() +{ + delete m_dbConnPool; +} + + +void +CollectionDB::scanModifiedDirs() +{ + //we check if a job is pending because we don't want to abort incremental collection readings + if ( !ThreadWeaver::instance()->isJobPending( "CollectionReader" ) && PlaylistBrowser::instance() ) { + emit scanStarted(); + ThreadWeaver::instance()->onlyOneJob( new IncrementalCollectionReader( this ) ); + } +} + + +void +CollectionDB::customEvent( QCustomEvent *e ) +{ + if ( e->type() == (int)CollectionReader::JobFinishedEvent ) + emit scanDone( static_cast(e)->wasSuccessful() ); +} + + +////////////////////////////////////////////////////////////////////////////////////////// +// CLASS DbConnectionPool +////////////////////////////////////////////////////////////////////////////////////////// + +DbConnectionPool::DbConnectionPool() : m_semaphore( POOL_SIZE ) +{ +#ifdef USE_MYSQL + if ( AmarokConfig::databaseEngine().toInt() == DbConnection::mysql ) + m_dbConnType = DbConnection::mysql; + else +#endif +#ifdef USE_POSTGRESQL + if ( AmarokConfig::databaseEngine().toInt() == DbConnection::postgresql ) + m_dbConnType = DbConnection::postgresql; + else +#endif + m_dbConnType = DbConnection::sqlite; + + m_semaphore += POOL_SIZE; + DbConnection *dbConn; + +#ifdef USE_MYSQL + if ( m_dbConnType == DbConnection::mysql ) + { + m_dbConfig = + new MySqlConfig( + AmarokConfig::mySqlHost(), + AmarokConfig::mySqlPort(), + AmarokConfig::mySqlDbName(), + AmarokConfig::mySqlUser(), + AmarokConfig::mySqlPassword() ); + dbConn = new MySqlConnection( static_cast ( m_dbConfig ) ); + } + else +#endif +#ifdef USE_POSTGRESQL + if ( m_dbConnType == DbConnection::postgresql ) + { + m_dbConfig = + new PostgresqlConfig( + AmarokConfig::postgresqlConninfo() ); + dbConn = new PostgresqlConnection( static_cast ( m_dbConfig ) ); + } + else +#endif + { + m_dbConfig = new SqliteConfig( "collection.db" ); + dbConn = new SqliteConnection( static_cast ( m_dbConfig ) ); + } + enqueue( dbConn ); + m_semaphore--; + debug() << "Available db connections: " << m_semaphore.available() << endl; +} + + +DbConnectionPool::~DbConnectionPool() +{ + m_semaphore += POOL_SIZE; + DbConnection *conn; + bool vacuum = true; + + while ( ( conn = dequeue() ) != 0 ) + { + if ( m_dbConnType == DbConnection::sqlite && vacuum ) + { + vacuum = false; + debug() << "Running VACUUM" << endl; + conn->query( "VACUUM; "); + } + + delete conn; + } + + delete m_dbConfig; +} + + +void DbConnectionPool::createDbConnections() +{ + for ( int i = 0; i < POOL_SIZE - 1; i++ ) + { + DbConnection *dbConn; + +#ifdef USE_MYSQL + if ( m_dbConnType == DbConnection::mysql ) + dbConn = new MySqlConnection( static_cast ( m_dbConfig ) ); + else +#endif +#ifdef USE_POSTGRESQL + if ( m_dbConnType == DbConnection::postgresql ) + dbConn = new PostgresqlConnection( static_cast ( m_dbConfig ) ); + else +#endif + dbConn = new SqliteConnection( static_cast ( m_dbConfig ) ); + enqueue( dbConn ); + m_semaphore--; + } + debug() << "Available db connections: " << m_semaphore.available() << endl; +} + + +DbConnection *DbConnectionPool::getDbConnection() +{ + m_semaphore++; + return dequeue(); +} + + +void DbConnectionPool::putDbConnection( const DbConnection *conn ) +{ + enqueue( conn ); + m_semaphore--; +} + + +#include "collectiondb.moc" diff --git a/amarok/src/database_refactor/collectiondb.h b/amarok/src/database_refactor/collectiondb.h new file mode 100644 index 00000000..7d0f6d12 --- /dev/null +++ b/amarok/src/database_refactor/collectiondb.h @@ -0,0 +1,239 @@ +// (c) 2004 Mark Kretschmann +// (c) 2004 Christian Muehlhaeuser +// (c) 2004 Sami Nieminen +// See COPYING file for licensing information. + +#ifndef AMAROK_COLLECTIONDB_H +#define AMAROK_COLLECTIONDB_H + +#include "engineobserver.h" +#include "dbenginebase.h" +#include +#include //stack allocated +#include //baseclass +#include //baseclass +#include //stack allocated +#include //stack allocated + +class CoverFetcher; +class MetaBundle; +class Scrobbler; + + +class DbConnectionPool : QPtrQueue +{ + public: + DbConnectionPool(); + ~DbConnectionPool(); + + const DbConnection::DbConnectionType getDbConnectionType() const { return m_dbConnType; } + const DbConfig *getDbConfig() const { return m_dbConfig; } + void createDbConnections(); + + DbConnection *getDbConnection(); + void putDbConnection( const DbConnection* /* conn */ ); + + private: + static const int POOL_SIZE = 5; + QSemaphore m_semaphore; + DbConnection::DbConnectionType m_dbConnType; + DbConfig *m_dbConfig; +}; + + +class CollectionDB : public QObject, public EngineObserver +{ + Q_OBJECT + + friend class SimilarArtistsInsertionJob; + + signals: + void scanStarted(); + void scanDone( bool changed ); + void databaseEngineChanged(); + + void scoreChanged( const QString &url, int score ); + + void coverFetched( const QString &artist, const QString &album ); + void coverRemoved( const QString &artist, const QString &album ); + void coverFetcherError( const QString &error ); + + void similarArtistsFetched( const QString &artist ); + + public: + static CollectionDB *instance(); + + QString escapeString( QString string ) { return m_dbConnPool->escapeString(string); } + int getType() { return m_dbConnPool->getDbConnectionType(); } + + + /** + * This method returns a static DbConnection for components that want to use + * the same connection for the whole time. Should not be used anywhere else + * but in CollectionReader. + * + * @return static DbConnection + */ + DbConnection *getStaticDbConnection(); + + /** + * Returns the DbConnection back to connection pool. + * + * @param conn DbConnection to be returned + */ + void returnStaticDbConnection( DbConnection *conn ); + + //sql helper methods + QStringList query( const QString& statement, DbConnection *conn = NULL ); + int insert( const QString& statement, const QString& table, DbConnection *conn = NULL ); + + //table management methods + bool isEmpty(); + bool isValid(); + void createTables( DbConnection *conn = NULL ); + void dropTables( DbConnection *conn = NULL ); + void clearTables( DbConnection *conn = NULL ); + void moveTempTables( DbConnection *conn ); + + uint artistID( QString value, bool autocreate = true, const bool temporary = false, const bool updateSpelling = false, DbConnection *conn = NULL ); + uint albumID( QString value, bool autocreate = true, const bool temporary = false, const bool updateSpelling = false, DbConnection *conn = NULL ); + uint genreID( QString value, bool autocreate = true, const bool temporary = false, const bool updateSpelling = false, DbConnection *conn = NULL ); + uint yearID( QString value, bool autocreate = true, const bool temporary = false, const bool updateSpelling = false, DbConnection *conn = NULL ); + + bool isDirInCollection( QString path ); + bool isFileInCollection( const QString &url ); + void removeDirFromCollection( QString path ); + void removeSongsInDir( QString path ); + void removeSongs( const KURL::List& urls ); + void updateDirStats( QString path, const long datetime, DbConnection *conn = NULL ); + + //song methods + bool addSong( MetaBundle* bundle, const bool incremental = false, DbConnection *conn = NULL ); + + /** + * The @p bundle parameter's url() will be looked up in the Collection + * @param bundle this will be filled in with tags for you + * @return true if in the collection + */ + bool bundleForUrl( MetaBundle* bundle ); + QValueList bundlesByUrls( const KURL::List& urls ); + void addAudioproperties( const MetaBundle& bundle ); + + void updateTags( const QString &url, const MetaBundle &bundle, const bool updateView = true ); + void updateURL( const QString &url, const bool updateView = true ); + + //statistics methods + int addSongPercentage( const QString &url, int percentage ); + int getSongPercentage( const QString &url ); + void setSongPercentage( const QString &url , int percentage ); + + //artist methods + QStringList similarArtists( const QString &artist, uint count ); + + //album methods + void checkCompilations( const QString &path, const bool temporary = false, DbConnection *conn = NULL ); + void setCompilation( const QString &album, const bool enabled, const bool updateView = true ); + QString albumSongCount( const QString &artist_id, const QString &album_id ); + bool albumIsCompilation( const QString &album_id ); + + //list methods + QStringList artistList( bool withUnknowns = true, bool withCompilations = true ); + QStringList albumList( bool withUnknowns = true, bool withCompilations = true ); + QStringList genreList( bool withUnknowns = true, bool withCompilations = true ); + QStringList yearList( bool withUnknowns = true, bool withCompilations = true ); + + QStringList albumListOfArtist( const QString &artist, bool withUnknown = true, bool withCompilations = true ); + QStringList artistAlbumList( bool withUnknown = true, bool withCompilations = true ); + + QStringList albumTracks( const QString &artist_id, const QString &album_id ); + + //cover management methods + /** Returns the image from a given URL, network-transparently. + * You must run KIO::NetAccess::removeTempFile( tmpFile ) when you are finished using the image; + **/ + static QImage fetchImage(const KURL& url, QString &tmpFile); + /** Saves images located on the user's filesystem */ + bool setAlbumImage( const QString& artist, const QString& album, const KURL& url ); + /** Saves images obtained from CoverFetcher */ + bool setAlbumImage( const QString& artist, const QString& album, QImage img, const QString& amazonUrl = QString::null ); + + QString findImageByMetabundle( MetaBundle trackInformation, const uint = 1 ); + QString findImageByArtistAlbum( const QString &artist, const QString &album, const uint width = 1 ); + QString albumImage( MetaBundle trackInformation, const uint width = 1 ); + QString albumImage( const uint artist_id, const uint album_id, const uint width = 1 ); + QString albumImage( const QString &artist, const QString &album, const uint width = 1 ); + + bool removeAlbumImage( const uint artist_id, const uint album_id ); + bool removeAlbumImage( const QString &artist, const QString &album ); + + //local cover methods + void addImageToAlbum( const QString& image, QValueList< QPair > info, DbConnection *conn = NULL ); + QString getImageForAlbum( const QString& artist, const QString& album, uint width = 0 ); + QString notAvailCover( int width = 0 ); + + void applySettings(); + + protected: + CollectionDB(); + ~CollectionDB(); + + QCString md5sum( const QString& artist, const QString& album, const QString& file = QString::null ); + void engineTrackEnded( int finalPosition, int trackLength ); + /** Manages regular folder monitoring scan */ + void timerEvent( QTimerEvent* e ); + + public slots: + void fetchCover( QWidget* parent, const QString& artist, const QString& album, bool noedit ); + void scanMonitor(); + void startScan(); + void stopScan(); + + private slots: + void dirDirty( const QString& path ); + void coverFetcherResult( CoverFetcher* ); + void similarArtistsFetched( const QString& artist, const QStringList& suggestions ); + + private: + //bump DATABASE_VERSION whenever changes to the table structure are made. will remove old db file. + static const int DATABASE_VERSION = 18; + static const int DATABASE_STATS_VERSION = 3; + static const int MONITOR_INTERVAL = 60; //sec + static const bool DEBUG = false; + + void initialize(); + void destroy(); + void customEvent( QCustomEvent* ); + + //general management methods + void createStatsTable(); + void dropStatsTable(); + void scanModifiedDirs(); + + QCString makeWidthKey( uint width ); + QString artistValue( uint id ); + QString albumValue( uint id ); + QString genreValue( uint id ); + QString yearValue( uint id ); + + uint IDFromValue( QString name, QString value, bool autocreate = true, const bool temporary = false, + const bool updateSpelling = false, DbConnection *conn = NULL ); + + QString valueFromID( QString table, uint id ); + + //member variables + QString m_amazonLicense; + QString m_cacheArtist; + uint m_cacheArtistID; + QString m_cacheAlbum; + uint m_cacheAlbumID; + + DBEngine* m_dbEngine; + DbConnectionPool *m_dbConnPool; + + bool m_monitor; + QDir m_cacheDir; + QDir m_coverDir; +}; + + +#endif /* AMAROK_COLLECTIONDB_H */ diff --git a/amarok/src/database_refactor/dbenginebase.cpp b/amarok/src/database_refactor/dbenginebase.cpp new file mode 100644 index 00000000..42f40366 --- /dev/null +++ b/amarok/src/database_refactor/dbenginebase.cpp @@ -0,0 +1,544 @@ +/*************************************************************************** + * Copyright (C) 2004-2005 Mark Kretschmann * + * 2004 Christian Muehlhaeuser * + * 2004 Sami Nieminen * + * 2005 Ian Monroe * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Steet, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include +#include + +#include + + +////////////////////////////////////////////////////////////////////////////////////////// +// CLASS DBConnection +////////////////////////////////////////////////////////////////////////////////////////// + +DbConnection::DbConnection( DbConfig* config ) + : m_config( config ) +{} + + +DbConnection::~DbConnection() +{} + + +////////////////////////////////////////////////////////////////////////////////////////// +// CLASS QueryBuilder +////////////////////////////////////////////////////////////////////////////////////////// + +QueryBuilder::QueryBuilder() +{ + clear(); +} + + +void +QueryBuilder::linkTables( int tables ) +{ + + m_tables = tableName( tabSong ); + + if ( !(tables & tabSong ) ) + { + // check if only one table is selected (does somebody know a better way to check that?) + if (tables == tabAlbum || tables==tabArtist || tables==tabGenre || tables == tabYear || tables == tabStats) + m_tables = tableName(tables); + else + tables |= tabSong; + } + + + if ( tables & tabSong ) + { + if ( tables & tabAlbum ) + m_tables += " INNER JOIN " + tableName( tabAlbum) + " ON album.id=tags.album"; + if ( tables & tabArtist ) + m_tables += " INNER JOIN " + tableName( tabArtist) + " ON artist.id=tags.artist"; + if ( tables & tabGenre ) + m_tables += " INNER JOIN " + tableName( tabGenre) + " ON genre.id=tags.genre"; + if ( tables & tabYear ) + m_tables += " INNER JOIN " + tableName( tabYear) + " ON year.id=tags.year"; + if ( tables & tabStats ) + m_tables += " INNER JOIN " + tableName( tabStats) + " ON statistics.url=tags.url"; + } +} + + +void +QueryBuilder::addReturnValue( int table, int value ) +{ + if ( !m_values.isEmpty() && m_values != "DISTINCT " ) m_values += ","; + if ( table & tabStats && value & valScore ) m_values += "round("; + + if ( value == valDummy ) + m_values += "''"; + else + { + m_values += tableName( table ) + "."; + m_values += valueName( value ); + } + + if ( table & tabStats && value & valScore ) m_values += " + 0.4 )"; + + m_linkTables |= table; + m_returnValues++; +} + +void +QueryBuilder::addReturnFunctionValue( int function, int table, int value) +{ + if ( !m_values.isEmpty() && m_values != "DISTINCT " ) m_values += ","; + m_values += functionName( function ) + "("; + m_values += tableName( table ) + "."; + m_values += valueName( value )+ ")"; + m_values += " AS "; + m_values += functionName( function )+tableName( table )+valueName( value ); + + m_linkTables |= table; + m_returnValues++; +} + +uint +QueryBuilder::countReturnValues() +{ + return m_returnValues; +} + + +void +QueryBuilder::addURLFilters( const QStringList& filter ) +{ + if ( !filter.isEmpty() ) + { + m_where += "AND ( true "; + + for ( uint i = 0; i < filter.count(); i++ ) + { + m_where += "OR tags.url = '" + escapeString( filter[i] ) + "' "; + } + + m_where += " ) "; + } + + m_linkTables |= tabSong; +} + + +void +QueryBuilder::addFilter( int tables, const QString& filter, int /*mode*/ ) +{ + if ( !filter.isEmpty() ) + { + m_where += "AND ( true "; + if ( tables & tabAlbum ) m_where += "OR album.name LIKE '%" + escapeString( filter ) + "%' "; + if ( tables & tabArtist ) m_where += "OR artist.name LIKE '%" + escapeString( filter ) + "%' "; + if ( tables & tabGenre ) m_where += "OR genre.name LIKE '%" + escapeString( filter ) + "%' "; + if ( tables & tabYear ) m_where += "OR year.name LIKE '%" + escapeString( filter ) + "%' "; + if ( tables & tabSong ) m_where += "OR tags.title LIKE '%" + escapeString( filter ) + "%' "; + m_where += " ) "; + } + + m_linkTables |= tables; +} + + +void +QueryBuilder::addFilters( int tables, const QStringList& filter ) +{ + if ( !filter.isEmpty() ) + { + m_where += "AND ( true "; + + for ( uint i = 0; i < filter.count(); i++ ) + { + m_where += " AND ( true "; + if ( tables & tabAlbum ) m_where += "OR album.name LIKE '%" + escapeString( filter[i] ) + "%' "; + if ( tables & tabArtist ) m_where += "OR artist.name LIKE '%" + escapeString( filter[i] ) + "%' "; + if ( tables & tabGenre ) m_where += "OR genre.name LIKE '%" + escapeString( filter[i] ) + "%' "; + if ( tables & tabYear ) m_where += "OR year.name LIKE '%" + escapeString( filter[i] ) + "%' "; + if ( tables & tabSong ) m_where += "OR tags.title LIKE '%" + escapeString( filter[i] ) + "%' "; + m_where += " ) "; + } + + m_where += " ) "; + } + + m_linkTables |= tables; +} + + +void +QueryBuilder::addMatch( int tables, const QString& match ) +{ + if ( !match.isEmpty() ) + { + m_where += "AND ( true "; + if ( tables & tabAlbum ) m_where += "OR album.name LIKE '" + escapeString( match ) + "' "; + if ( tables & tabArtist ) m_where += "OR artist.name LIKE '" + escapeString( match ) + "' "; + if ( tables & tabGenre ) m_where += "OR genre.name LIKE '" + escapeString( match ) + "' "; + if ( tables & tabYear ) m_where += "OR year.name LIKE '" + escapeString( match ) + "' "; + if ( tables & tabSong ) m_where += "OR tags.title LIKE '" + escapeString( match ) + "' "; + + if ( match == i18n( "Unknown" ) ) + { + if ( tables & tabAlbum ) m_where += "OR album.name = '' "; + if ( tables & tabArtist ) m_where += "OR artist.name = '' "; + if ( tables & tabGenre ) m_where += "OR genre.name = '' "; + if ( tables & tabYear ) m_where += "OR year.name = '' "; + } + m_where += " ) "; + } + + m_linkTables |= tables; +} + + +void +QueryBuilder::addMatch( int tables, int value, const QString& match ) +{ + if ( !match.isEmpty() ) + { + m_where += "AND ( true "; + m_where += QString( "OR %1.%2 LIKE '" ).arg( tableName( tables ) ).arg( valueName( value ) ) + escapeString( match ) + "' "; + + if ( ( value & valName ) && match == i18n( "Unknown" ) ) + m_where += QString( "OR %1.%2 = '' " ).arg( tableName( tables ) ).arg( valueName( value ) ); + + m_where += " ) "; + } + + m_linkTables |= tables; +} + + +void +QueryBuilder::addMatches( int tables, const QStringList& match ) +{ + if ( !match.isEmpty() ) + { + m_where += "AND ( true "; + + for ( uint i = 0; i < match.count(); i++ ) + { + if ( tables & tabAlbum ) m_where += "OR album.name LIKE '" + escapeString( match[i] ) + "' "; + if ( tables & tabArtist ) m_where += "OR artist.name LIKE '" + escapeString( match[i] ) + "' "; + if ( tables & tabGenre ) m_where += "OR genre.name LIKE '" + escapeString( match[i] ) + "' "; + if ( tables & tabYear ) m_where += "OR year.name LIKE '" + escapeString( match[i] ) + "' "; + if ( tables & tabSong ) m_where += "OR tags.title LIKE '" + escapeString( match[i] ) + "' "; + if ( tables & tabStats ) m_where += "OR statistics.url LIKE '" + escapeString( match[i] ) + "' "; + + if ( match[i] == i18n( "Unknown" ) ) + { + if ( tables & tabAlbum ) m_where += "OR album.name = '' "; + if ( tables & tabArtist ) m_where += "OR artist.name = '' "; + if ( tables & tabGenre ) m_where += "OR genre.name = '' "; + if ( tables & tabYear ) m_where += "OR year.name = '' "; + } + } + + m_where += " ) "; + } + + m_linkTables |= tables; +} + + +void +QueryBuilder::excludeFilter( int tables, const QString& filter ) +{ + if ( !filter.isEmpty() ) + { + m_where += "AND ( true "; + if ( tables & tabAlbum ) m_where += "AND album.name <> '%" + escapeString( filter ) + "%' "; + if ( tables & tabArtist ) m_where += "AND artist.name <> '%" + escapeString( filter ) + "%' "; + if ( tables & tabGenre ) m_where += "AND genre.name <> '%" + escapeString( filter ) + "%' "; + if ( tables & tabYear ) m_where += "AND year.name <> '%" + escapeString( filter ) + "%' "; + if ( tables & tabSong ) m_where += "AND tags.title <> '%" + escapeString( filter ) + "%' "; + m_where += " ) "; + } + + m_linkTables |= tables; +} + + +void +QueryBuilder::excludeMatch( int tables, const QString& match ) +{ + if ( !match.isEmpty() ) + { + m_where += "AND ( true "; + if ( tables & tabAlbum ) m_where += "AND album.name <> '" + escapeString( match ) + "' "; + if ( tables & tabArtist ) m_where += "AND artist.name <> '" + escapeString( match ) + "' "; + if ( tables & tabGenre ) m_where += "AND genre.name <> '" + escapeString( match ) + "' "; + if ( tables & tabYear ) m_where += "AND year.name <> '" + escapeString( match ) + "' "; + if ( tables & tabSong ) m_where += "AND tags.title <> '" + escapeString( match ) + "' "; + + if ( match == i18n( "Unknown" ) ) + { + if ( tables & tabAlbum ) m_where += "AND album.name <> '' "; + if ( tables & tabArtist ) m_where += "AND artist.name <> '' "; + if ( tables & tabGenre ) m_where += "AND genre.name <> '' "; + if ( tables & tabYear ) m_where += "AND year.name <> '' "; + } + m_where += " ) "; + } + + m_linkTables |= tables; +} + + +void +QueryBuilder::exclusiveFilter( int tableMatching, int tableNotMatching, int value ) +{ + m_join += " LEFT JOIN "; + m_join += tableName( tableNotMatching ); + m_join += " ON "; + + m_join += tableName( tableMatching ) + "."; + m_join += valueName( value ); + m_join+= " = "; + m_join += tableName( tableNotMatching ) + "."; + m_join += valueName( value ); + + m_where += " AND "; + m_where += tableName( tableNotMatching ) + "."; + m_where += valueName( value ); + m_where += " IS null "; +} + + +void +QueryBuilder::setOptions( int options ) +{ + if ( options & optNoCompilations || options & optOnlyCompilations ) + m_linkTables |= tabSong; + + if ( options & optNoCompilations ) m_where += "AND tags.sampler = 0 "; + if ( options & optOnlyCompilations ) m_where += "AND tags.sampler = 1 "; + + if ( options & optRemoveDuplicates ) m_values = "DISTINCT " + m_values; + if ( options & optRandomize ) + { + if ( !m_sort.isEmpty() ) m_sort += ","; + m_sort += "RAND() "; + } +} + + +void +QueryBuilder::sortBy( int table, int value, bool descending ) +{ + //shall we sort case-sensitively? (not for integer columns!) + bool b = true; + if ( value & valID || value & valTrack || value & valScore || value & valLength || value & valBitrate || + value & valSamplerate || value & valPlayCounter || value & valAccessDate || value & valCreateDate || value & valPercentage || + table & tabYear ) + b = false; + + if ( !m_sort.isEmpty() ) m_sort += ","; + if ( b ) m_sort += "LOWER( "; + if ( table & tabYear ) m_sort += "("; + + m_sort += tableName( table ) + "."; + m_sort += valueName( value ); + + if ( table & tabYear ) m_sort += "+0)"; + + if ( b ) m_sort += " ) "; + if ( descending ) m_sort += " DESC "; + + m_linkTables |= table; +} + +void +QueryBuilder::sortByFunction( int function, int table, int value, bool descending ) +{ + // This function should be used with the equivalent addReturnFunctionValue (with the same function on same values) + // since it uses the "func(table.value) AS functablevalue" definition. + + //shall we sort case-sensitively? (not for integer columns!) + bool b = true; + if ( value & valID || value & valTrack || value & valScore || value & valLength || value & valBitrate || + value & valSamplerate || value & valPlayCounter || value & valAccessDate || value & valCreateDate || value & valPercentage || + table & tabYear ) + b = false; + + if ( !m_sort.isEmpty() ) m_sort += ","; + //m_sort += functionName( function ) + "("; + if ( b ) m_sort += "LOWER( "; + if ( table & tabYear ) m_sort += "("; + + QString columnName = functionName( function )+tableName( table )+valueName( value ); + m_sort += columnName; + + if ( table & tabYear ) m_sort += "+0)"; + if ( b ) m_sort += " ) "; + //m_sort += " ) "; + if ( descending ) m_sort += " DESC "; + + m_linkTables |= table; +} + +void +QueryBuilder::groupBy( int table, int value ) +{ + if ( !m_group.isEmpty() ) m_group += ","; + m_group += tableName( table ) + "."; + m_group += valueName( value ); + + m_linkTables |= table; +} + + +void +QueryBuilder::setLimit( int startPos, int length ) +{ + m_limit = QString( " LIMIT %1, %2 " ).arg( startPos ).arg( length ); +} + + +void +QueryBuilder::initSQLDrag() +{ + clear(); + addReturnValue( QueryBuilder::tabAlbum, QueryBuilder::valName ); + addReturnValue( QueryBuilder::tabArtist, QueryBuilder::valName ); + addReturnValue( QueryBuilder::tabGenre, QueryBuilder::valName ); + addReturnValue( QueryBuilder::tabSong, QueryBuilder::valTitle ); + addReturnValue( QueryBuilder::tabYear, QueryBuilder::valName ); + addReturnValue( QueryBuilder::tabSong, QueryBuilder::valComment ); + addReturnValue( QueryBuilder::tabSong, QueryBuilder::valTrack ); + addReturnValue( QueryBuilder::tabSong, QueryBuilder::valBitrate ); + addReturnValue( QueryBuilder::tabSong, QueryBuilder::valLength ); + addReturnValue( QueryBuilder::tabSong, QueryBuilder::valSamplerate ); + addReturnValue( QueryBuilder::tabSong, QueryBuilder::valURL ); +} + + +void +QueryBuilder::buildQuery() +{ + if ( m_query.isEmpty() ) + { + linkTables( m_linkTables ); + + m_query = "SELECT " + m_values + " FROM " + m_tables + " " + m_join + " WHERE true " + m_where; + // GROUP BY must be before ORDER BY for sqlite + if ( !m_group.isEmpty() ) m_query += " GROUP BY " + m_group; + if ( !m_sort.isEmpty() ) m_query += " ORDER BY " + m_sort; + m_query += m_limit; + } +} + +// get the builded SQL-Query (used in smartplaylisteditor soon) +QString +QueryBuilder::getQuery() +{ + if ( m_query.isEmpty()) + { + buildQuery(); + } + return m_query; +} + +QStringList +QueryBuilder::run() +{ + buildQuery(); + //debug() << m_query << endl; +// return query( m_query ); +} + + +void +QueryBuilder::clear() +{ + m_query = ""; + m_values = ""; + m_tables = ""; + m_join = ""; + m_where = ""; + m_sort = ""; + m_group = ""; + m_limit = ""; + + m_linkTables = 0; + m_returnValues = 0; +} + + +QString +QueryBuilder::tableName( int table ) +{ + QString tables; + + if ( table & tabSong ) tables += ",tags"; + if ( table & tabArtist ) tables += ",artist"; + if ( table & tabAlbum ) tables += ",album"; + if ( table & tabGenre ) tables += ",genre"; + if ( table & tabYear ) tables += ",year"; + if ( table & tabStats ) tables += ",statistics"; + + // when there are multiple tables involved, we always need table tags for linking them + return tables.mid( 1 ); +} + + +QString +QueryBuilder::valueName( int value ) +{ + QString values; + + if ( value & valID ) values += "id"; + if ( value & valName ) values += "name"; + if ( value & valURL ) values += "url"; + if ( value & valTitle ) values += "title"; + if ( value & valTrack ) values += "track"; + if ( value & valScore ) values += "percentage"; + if ( value & valComment ) values += "comment"; + if ( value & valBitrate ) values += "bitrate"; + if ( value & valLength ) values += "length"; + if ( value & valSamplerate ) values += "samplerate"; + if ( value & valPlayCounter ) values += "playcounter"; + if ( value & valAccessDate ) values += "accessdate"; + if ( value & valCreateDate ) values += "createdate"; + if ( value & valPercentage ) values += "percentage"; + if ( value & valArtistID ) values += "artist"; + if ( value & valAlbumID ) values += "album"; + if ( value & valGenreID ) values += "genre"; + if ( value & valYearID ) values += "year"; + + return values; +} + +QString +QueryBuilder::functionName( int value ) +{ + QString function; + + if ( value & funcCount ) function += "Count"; + if ( value & funcMax ) function += "Max"; + if ( value & funcMin ) function += "Min"; + if ( value & funcAvg ) function += "Avg"; + if ( value & funcSum ) function += "Sum"; + + return function; +} + diff --git a/amarok/src/database_refactor/dbenginebase.h b/amarok/src/database_refactor/dbenginebase.h new file mode 100644 index 00000000..880a34bd --- /dev/null +++ b/amarok/src/database_refactor/dbenginebase.h @@ -0,0 +1,133 @@ +/*************************************************************************** + * Copyright (C) 2004-2005 Mark Kretschmann * + * 2004 Christian Muehlhaeuser * + * 2004 Sami Nieminen * + * 2005 Ian Monroe * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Steet, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef AMAROK_DBENGINEBASE_H +#define AMAROK_DBENGINEBASE_H + +#include "plugin/plugin.h" //baseclass +#include //baseclass + + +class DbConfig +{}; + +class DbConnection : public QObject, public amaroK::Plugin +{ + public: + enum DbConnectionType { sqlite = 0, mysql = 1, postgresql = 2 }; + + DbConnection( DbConfig* /* config */ ); + virtual ~DbConnection() = 0; + + virtual QStringList query( const QString& /* statement */ ) = 0; + virtual int insert( const QString& /* statement */, const QString& /* table */ ) = 0; + const bool isInitialized() const { return m_initialized; } + virtual bool isConnected() const = 0; + virtual const QString lastError() const { return "None"; } + + protected: + bool m_initialized; + DbConfig *m_config; +}; + + +class QueryBuilder +{ + public: + //attributes: + enum qBuilderTables { tabAlbum = 1, tabArtist = 2, tabGenre = 4, tabYear = 8, tabSong = 32, tabStats = 64, tabDummy = 0 }; + enum qBuilderOptions { optNoCompilations = 1, optOnlyCompilations = 2, optRemoveDuplicates = 4, optRandomize = 8 }; + enum qBuilderValues { valID = 1, valName = 2, valURL = 4, valTitle = 8, valTrack = 16, valScore = 32, valComment = 64, + valBitrate = 128, valLength = 256, valSamplerate = 512, valPlayCounter = 1024, + valCreateDate = 2048, valAccessDate = 4096, valPercentage = 8192, valArtistID = 16384, valAlbumID = 32768, + valYearID = 65536, valGenreID = 131072, valDummy = 0 }; + enum qBuilderFunctions { funcCount = 1, funcMax = 2, funcMin = 4, funcAvg = 8, funcSum = 16 }; + + enum qBuilderFilter { modeNormal = 0, modeFuzzy = 1 }; + + QueryBuilder(); + + QString escapeString( QString string ) + { + return + #ifdef USE_MYSQL + // We have to escape "\" for mysql, but can't do so for sqlite + (m_dbConnType == DbConnection::mysql) + ? string.replace("\\", "\\\\").replace( '\'', "''" ) + : + #endif + string.replace( '\'', "''" ); + } + + void addReturnValue( int table, int value ); + void addReturnFunctionValue( int function, int table, int value); + uint countReturnValues(); + + void addURLFilters( const QStringList& filter ); + + void addFilter( int tables, const QString& filter, int mode = modeNormal ); + void addFilters( int tables, const QStringList& filter ); + void excludeFilter( int tables, const QString& filter ); + + void addMatch( int tables, const QString& match ); + void addMatch( int tables, int value, const QString& match ); + void addMatches( int tables, const QStringList& match ); + void excludeMatch( int tables, const QString& match ); + + void exclusiveFilter( int tableMatching, int tableNotMatching, int value ); + + void setOptions( int options ); + void sortBy( int table, int value, bool descending = false ); + void sortByFunction( int function, int table, int value, bool descending = false ); + void groupBy( int table, int value ); + void setLimit( int startPos, int length ); + + void initSQLDrag(); + void buildQuery(); + QString getQuery(); + QString query() { buildQuery(); return m_query; }; + void clear(); + + QStringList run(); + + private: + QString tableName( int table ); + QString valueName( int value ); + QString functionName( int value ); + + void linkTables( int tables ); + + QString m_query; + QString m_values; + QString m_tables; + QString m_join; + QString m_where; + QString m_sort; + QString m_group; + QString m_limit; + + int m_linkTables; + uint m_returnValues; +}; + + +#endif /*AMAROK_DBENGINEBASE_H*/ diff --git a/amarok/src/database_refactor/sqlite/_Makefile.am b/amarok/src/database_refactor/sqlite/_Makefile.am new file mode 100644 index 00000000..bc58791a --- /dev/null +++ b/amarok/src/database_refactor/sqlite/_Makefile.am @@ -0,0 +1,38 @@ +kde_module_LTLIBRARIES = \ + libamarok_sqlite_dbengine_plugin.la + +SUBDIRS = \ + sqlite + +INCLUDES = \ + -I$(top_srcdir)/amarok/src/database/sqlite/sqlite \ + -I$(top_srcdir)/amarok/src/database \ + -I$(top_srcdir)/amarok/src/plugin \ + -I$(top_srcdir)/amarok/src/engine \ + -I$(top_srcdir)/amarok/src/amarokcore \ + -I$(top_srcdir)/amarok/src/statusbar \ + -I$(top_srcdir)/amarok/src \ + $(all_includes) $(taglib_includes) + +libamarok_sqlite_dbengine_plugin_la_LIBADD = \ + $(top_builddir)/amarok/src/database/sqlite/sqlite/libsqlite.la \ + $(top_builddir)/amarok/src/database/libdbengine.la \ + $(top_builddir)/amarok/src/plugin/libplugin.la \ + $(LIB_KDECORE) + +libamarok_sqlite_dbengine_plugin_la_SOURCES = \ + sqlite_dbengine.cpp + +libamarok_sqlite_dbengine_plugin_la_LDFLAGS = \ + -module \ + -no-undefined \ + $(KDE_PLUGIN) \ + $(all_libraries) + +METASOURCES = \ + AUTO + +kde_services_DATA = \ + amarok_sqlite_dbengine_plugin.desktop + + diff --git a/amarok/src/database_refactor/sqlite/amarok_sqlite_dbengine_plugin.desktop b/amarok/src/database_refactor/sqlite/amarok_sqlite_dbengine_plugin.desktop new file mode 100644 index 00000000..89fce118 --- /dev/null +++ b/amarok/src/database_refactor/sqlite/amarok_sqlite_dbengine_plugin.desktop @@ -0,0 +1,103 @@ +[Desktop Entry] +Encoding=UTF-8 +Type=Service +Name=SQLite DBEngine +Name[af]=SQLite DBEnjin +Name[ar]= محرك SQLite DBEngine +Name[bn]=এসকিউ-লাইট ডিবি-ইঞ্জিন +Name[br]=Keflusker DB SQLite +Name[da]=SQLite DB-motor +Name[de]=SQLite +Name[eo]=SQLite DBIlo +Name[es]=Motor de base de datos SQLite +Name[et]=SQLite'i andmebaasimootor +Name[fi]=SQLite-tietokantajärjestelmä +Name[fr]=Moteur de base de données SQLite +Name[ga]=Inneall SQLite +Name[gl]=Motor de BBDD SQLite +Name[he]=מנוע מסד נתונים SQLite +Name[hu]=SQLite adatbázis-alrendszer +Name[is]=SQLite gagnagrunnur +Name[it]=Motore DB SQLite +Name[ja]=SQLite DB エンジン +Name[lt]=SQLite duomenų bazės variklis +Name[nds]=SQLite +Name[ne]=एसक्यु लाइट डीबी इन्जिन +Name[nn]=SQLite-databasemotor +Name[pl]=Baza danych SQLite +Name[pt]=Motor de BD SQLite +Name[pt_BR]=Mecanismo do Banco de Dados SQLite +Name[ru]=SQLite +Name[sq]=Motor SQLite DB +Name[sr]=Мотор SQLite DB +Name[sr@Latn]=Motor SQLite DB +Name[ss]=Motor SQLite DB +Name[sv]=SQLite-databasgränssnitt +Name[tg]=Муҳаррики-DB барои SQLite +Name[tr]=SQLite DBMotoru +Name[uk]=Рушій бази даних SQLite +Name[uz]=SQLite maʼlumot bazasi +Name[uz@cyrillic]=SQLite маълумот базаси +Name[wa]=Éndjin d' BD SQLite +Name[zh_CN]=SQLite 数据引擎 +Name[zh_TW]=SQLite 資料庫引擎 +X-KDE-Library=libamarok_sqlite_dbengine_plugin +Comment=Plugin for Amarok +Comment[af]=Inprop module vir Amarok +Comment[ar]= قابس ( برنامج مضاف الى) AmaroK +Comment[bg]=Приставка за Amarok +Comment[bn]=আমারক-এর জন্য প্লাগিন +Comment[br]=Lugent evit Amarok +Comment[ca]=Connector per l'Amarok +Comment[cs]=Modul pro AmaroK +Comment[de]=Modul für Amarok +Comment[el]=Πρόσθετο για το AmaroK +Comment[eo]=Kromaĵo por Amarok +Comment[es]=Extensión para Amarok +Comment[et]=Amaroki plugin +Comment[fa]=وصله برای amaroK +Comment[fi]=Amarok-liitännäinen +Comment[fr]=Module pour Amarok +Comment[ga]=Breiseán AmaroK +Comment[gl]=Extensión para Amarok +Comment[hu]=Bővítőmodul az Amarokhoz +Comment[is]=Íforrit fyrir Amarok +Comment[it]=Plugin per Amarok +Comment[ja]=Amarok のためのプラグイン +Comment[ka]=მოდული Amarok-ისთვის +Comment[km]=កម្មវិធី​ជំនួយ​សម្រាប់ Amarok +Comment[lt]=Amarok įskiepis +Comment[mk]=Приклучок за Амарок +Comment[nb]=Programtillegg for Amarok +Comment[nds]=Moduul för Amarok +Comment[ne]=अमारोकका लागि प्लगइन +Comment[nl]=Plugin voor Amarok +Comment[nn]=Programtillegg for Amarok +Comment[pa]=ਅਮਰੋਕ ਲਈ ਪਲੱਗਇਨ +Comment[pl]=Wtyczka Amaroka +Comment[pt]='Plugin' para o Amarok +Comment[pt_BR]=Plugin para o Amarok +Comment[ru]=Модуль amaroK +Comment[se]=Lassemoduvla Amarok:ii +Comment[sk]=Amarok modul +Comment[sr]=Прикључак за Amarok +Comment[sr@Latn]=Priključak za Amarok +Comment[sv]=Insticksprogram för Amarok +Comment[th]=โปรแกรมเสริมสำหรับ Amarok +Comment[tr]=Amarok için Eklenti +Comment[uk]=Втулок для Amarok +Comment[uz]=Amarok uchun plagin +Comment[uz@cyrillic]=Amarok учун плагин +Comment[wa]=Tchôke-divins po Amarok +Comment[zh_CN]=Amarok 插件 +Comment[zh_TW]=amaroK 插件 +ServiceTypes=amaroK/Plugin + +X-KDE-amaroK-plugintype=dbengine +X-KDE-amaroK-name=sqlite-dbengine +X-KDE-amaroK-authors=Mark Kretschmann, Christian Muehlhaeuser +X-KDE-amaroK-email=markey@web.de +X-KDE-amaroK-rank=255 +X-KDE-amaroK-version=1 +X-KDE-amaroK-framework-version=5 + diff --git a/amarok/src/database_refactor/sqlite/sqlite_dbengine.cpp b/amarok/src/database_refactor/sqlite/sqlite_dbengine.cpp new file mode 100644 index 00000000..3d93a84c --- /dev/null +++ b/amarok/src/database_refactor/sqlite/sqlite_dbengine.cpp @@ -0,0 +1,227 @@ +// (c) 2004 Mark Kretschmann +// (c) 2004 Christian Muehlhaeuser +// (c) 2004 Sami Nieminen +// (c) 2005 Ian Monroe +// See COPYING file for licensing information. + +#define DEBUG_PREFIX "SQLite-DBEngine" + +#include "app.h" +#include "amarok.h" +#include "amarokconfig.h" +#include "debug.h" +#include "sqlite_dbengine.h" + +#include + +#include +#include +#include + +#include //DbConnection::sqlite_power() +#include //query() +#include //usleep() + +#include "sqlite/sqlite3.h" + +AMAROK_EXPORT_PLUGIN( SqliteDbEngine ) + + +////////////////////////////////////////////////////////////////////////////////////////// +// CLASS SqliteConnection +////////////////////////////////////////////////////////////////////////////////////////// + +SqliteDbEngine::SqliteDbEngine() + : DbConnection( new SqliteConfig( "collection.db" ) ) +{ + const QCString path = QString(/*amaroK::saveLocation()+*/"collection.db").local8Bit(); + + // Open database file and check for correctness + m_initialized = false; + QFile file( path ); + if ( file.open( IO_ReadOnly ) ) + { + QString format; + file.readLine( format, 50 ); + if ( !format.startsWith( "SQLite format 3" ) ) + { + warning() << "Database versions incompatible. Removing and rebuilding database.\n"; + } + else if ( sqlite3_open( path, &m_db ) != SQLITE_OK ) + { + warning() << "Database file corrupt. Removing and rebuilding database.\n"; + sqlite3_close( m_db ); + } + else + m_initialized = true; + } + + if ( !m_initialized ) + { + // Remove old db file; create new + QFile::remove( path ); + if ( sqlite3_open( path, &m_db ) == SQLITE_OK ) + { + m_initialized = true; + } + } + if ( m_initialized ) + { + if( sqlite3_create_function(m_db, "rand", 0, SQLITE_UTF8, NULL, sqlite_rand, NULL, NULL) != SQLITE_OK ) + m_initialized = false; + if( sqlite3_create_function(m_db, "power", 2, SQLITE_UTF8, NULL, sqlite_power, NULL, NULL) != SQLITE_OK ) + m_initialized = false; + } + + //optimization for speeding up SQLite + query( "PRAGMA default_synchronous = OFF;" ); +} + + +SqliteDbEngine::~SqliteDbEngine() +{ + if ( m_db ) sqlite3_close( m_db ); +} + + +QStringList SqliteDbEngine::query( const QString& statement ) +{ + QStringList values; + int error; + const char* tail; + sqlite3_stmt* stmt; + + //compile SQL program to virtual machine + error = sqlite3_prepare( m_db, statement.utf8(), statement.length(), &stmt, &tail ); + + if ( error != SQLITE_OK ) + { + Debug::error() << k_funcinfo << " sqlite3_compile error:" << endl; + Debug::error() << sqlite3_errmsg( m_db ) << endl; + Debug::error() << "on query: " << statement << endl; + values = QStringList(); + } + else + { + int busyCnt = 0; + int number = sqlite3_column_count( stmt ); + //execute virtual machine by iterating over rows + while ( true ) + { + error = sqlite3_step( stmt ); + + if ( error == SQLITE_BUSY ) + { + if ( busyCnt++ > 20 ) { + Debug::error() << "Busy-counter has reached maximum. Aborting this sql statement!\n"; + break; + } + ::usleep( 100000 ); // Sleep 100 msec + debug() << "sqlite3_step: BUSY counter: " << busyCnt << endl; + } + if ( error == SQLITE_MISUSE ) + debug() << "sqlite3_step: MISUSE" << endl; + if ( error == SQLITE_DONE || error == SQLITE_ERROR ) + break; + + //iterate over columns + for ( int i = 0; i < number; i++ ) + { + values << QString::fromUtf8( (const char*) sqlite3_column_text( stmt, i ) ); + } + } + //deallocate vm resources + sqlite3_finalize( stmt ); + + if ( error != SQLITE_DONE ) + { + Debug::error() << k_funcinfo << "sqlite_step error.\n"; + Debug::error() << sqlite3_errmsg( m_db ) << endl; + Debug::error() << "on query: " << statement << endl; + values = QStringList(); + } + } + + return values; +} + + +int SqliteDbEngine::insert( const QString& statement, const QString& /* table */ ) +{ + int error; + const char* tail; + sqlite3_stmt* stmt; + + //compile SQL program to virtual machine + error = sqlite3_prepare( m_db, statement.utf8(), statement.length(), &stmt, &tail ); + + if ( error != SQLITE_OK ) + { + Debug::error() << k_funcinfo << " sqlite3_compile error:" << endl; + Debug::error() << sqlite3_errmsg( m_db ) << endl; + Debug::error() << "on insert: " << statement << endl; + } + else + { + int busyCnt = 0; + //execute virtual machine by iterating over rows + while ( true ) + { + error = sqlite3_step( stmt ); + + if ( error == SQLITE_BUSY ) + { + if ( busyCnt++ > 20 ) { + Debug::error() << "Busy-counter has reached maximum. Aborting this sql statement!\n"; + break; + } + ::usleep( 100000 ); // Sleep 100 msec + debug() << "sqlite3_step: BUSY counter: " << busyCnt << endl; + } + if ( error == SQLITE_MISUSE ) + debug() << "sqlite3_step: MISUSE" << endl; + if ( error == SQLITE_DONE || error == SQLITE_ERROR ) + break; + } + //deallocate vm resources + sqlite3_finalize( stmt ); + + if ( error != SQLITE_DONE ) + { + Debug::error() << k_funcinfo << "sqlite_step error.\n"; + Debug::error() << sqlite3_errmsg( m_db ) << endl; + Debug::error() << "on insert: " << statement << endl; + } + } + return sqlite3_last_insert_rowid( m_db ); +} + + +// this implements a RAND() function compatible with the MySQL RAND() (0-param-form without seed) +void SqliteDbEngine::sqlite_rand(sqlite3_context *context, int /*argc*/, sqlite3_value ** /*argv*/) +{ + //sqlite3_result_double( context, static_cast(KApplication::random()) / (RAND_MAX+1.0) ); +} + +// this implements a POWER() function compatible with the MySQL POWER() +void SqliteDbEngine::sqlite_power(sqlite3_context *context, int argc, sqlite3_value **argv) +{ + Q_ASSERT( argc==2 ); + if( sqlite3_value_type(argv[0])==SQLITE_NULL || sqlite3_value_type(argv[1])==SQLITE_NULL ) { + sqlite3_result_null(context); + return; + } + double a = sqlite3_value_double(argv[0]); + double b = sqlite3_value_double(argv[1]); + sqlite3_result_double( context, pow(a,b) ); +} + + +////////////////////////////////////////////////////////////////////////////////////////// +// CLASS SqliteConfig +////////////////////////////////////////////////////////////////////////////////////////// + +SqliteConfig::SqliteConfig( const QString& dbfile ) + : m_dbfile( dbfile ) +{} + diff --git a/amarok/src/database_refactor/sqlite/sqlite_dbengine.h b/amarok/src/database_refactor/sqlite/sqlite_dbengine.h new file mode 100644 index 00000000..d19263e0 --- /dev/null +++ b/amarok/src/database_refactor/sqlite/sqlite_dbengine.h @@ -0,0 +1,58 @@ +// (c) 2004 Mark Kretschmann +// (c) 2004 Christian Muehlhaeuser +// (c) 2004 Sami Nieminen +// See COPYING file for licensing information. + +#ifndef AMAROK_SQLITE_DBENGINE_H +#define AMAROK_SQLITE_DBENGINE_H + +#include "dbenginebase.h" +#include +#include //stack allocated +#include //baseclass +#include //baseclass +#include //stack allocated +#include //stack allocated + +class DbConfig; +class DbConnection; +class DbConnectionPool; +class CoverFetcher; +class MetaBundle; +class Scrobbler; + + +class SqliteConfig : public DbConfig +{ + public: + SqliteConfig( const QString& /* dbfile */ ); + + const QString dbFile() const { return m_dbfile; } + + private: + QString m_dbfile; +}; + + +typedef struct sqlite3 sqlite3; +typedef struct sqlite3_context sqlite3_context; +typedef struct Mem sqlite3_value; + +class SqliteDbEngine : public DbConnection +{ + public: + SqliteDbEngine(); + ~SqliteDbEngine(); + + QStringList query( const QString& /* statement */ ); + int insert( const QString& /* statement */, const QString& /* table */ ); + bool isConnected()const { return true; } + private: + static void sqlite_rand(sqlite3_context *context, int /*argc*/, sqlite3_value ** /*argv*/); + static void sqlite_power(sqlite3_context *context, int argc, sqlite3_value **argv); + + sqlite3* m_db; +}; + + +#endif /*SQLITE_DBENGINE_H*/ diff --git a/amarok/src/dbsetup.ui b/amarok/src/dbsetup.ui new file mode 100644 index 00000000..9a30eb75 --- /dev/null +++ b/amarok/src/dbsetup.ui @@ -0,0 +1,468 @@ + +DbSetup + + + DbSetup + + + + 0 + 0 + 385 + 155 + + + + Database Setup + + + + unnamed + + + 0 + + + + configStack + + + Plain + + + + SQLLite + + + 0 + + + + + MySQL + + + 1 + + + + unnamed + + + 0 + + + + mySqlFrame + + + NoFrame + + + Raised + + + 0 + + + + unnamed + + + 0 + + + + mysqlConfig + + + MySQL Configuration + + + + unnamed + + + + textLabel1 + + + Hostname: + + + + + textLabel5 + + + Database: + + + + + kcfg_MySqlPort + + + + 100 + 32767 + + + + 65535 + + + Which port mysql should connect to. + + + Which port mysql should connect to. + + + + + textLabel2 + + + Port: + + + + + kcfg_MySqlHost + + + Hostname where database lives. + + + Hostname where database lives. + + + + + kcfg_MySqlDbName + + + Name of the database. + + + Name of the database. + + + + + line1 + + + HLine + + + Sunken + + + Horizontal + + + + + layout4 + + + + unnamed + + + + textLabel3 + + + Username: + + + + + kcfg_MySqlUser + + + Username with which to connect to. + + + Username with which to connect to. + + + + + textLabel4 + + + Password: + + + + + kcfg_MySqlPassword2 + + + Password + + + Password with which to connect to. + + + Password with which to connect to. + + + + + + + + + + + + + PostgreSQL + + + 2 + + + + unnamed + + + 0 + + + + postgreSqlFrame + + + NoFrame + + + Raised + + + + unnamed + + + 0 + + + + postgresqlConfig + + + PostgreSQL Configuration + + + + unnamed + + + + textLabel1 + + + Hostname: + + + + + textLabel5 + + + Database: + + + + + kcfg_PostgresqlPort + + + + 100 + 32767 + + + + 65535 + + + Which port postgresql should connect to. + + + Which port postgresql should connect to. + + + + + textLabel2 + + + Port: + + + + + kcfg_PostgresqlHost + + + Hostname where database lives. + + + Hostname where database lives. + + + + + kcfg_PostgresqlDbName + + + Name of the database. + + + Name of the database. + + + + + line1 + + + HLine + + + Sunken + + + Horizontal + + + + + layout4 + + + + unnamed + + + + textLabel3 + + + Username: + + + + + kcfg_PostgresqlUser + + + Username with which to connect to. + + + Username with which to connect to. + + + + + textLabel4 + + + Password: + + + + + kcfg_PostgresqlPassword2 + + + Password + + + Password with which to connect to. + + + Password with which to connect to. + + + + + + + + + + + + + + textLabel1_2 + + + Database: + + + + + + SQLite + + + + databaseEngine + + + + 7 + 0 + 0 + 0 + + + + + + + + + + databaseEngine + activated(int) + configStack + raiseWidget(int) + + + databaseEngine + activated(int) + DbSetup + databaseEngine_activated(int) + + + + amarokconfig.h + config.h + debug.h + dbsetup.ui.h + + + databaseEngine_activated( int ) + + + init() + + + + knuminput.h + klineedit.h + klineedit.h + klineedit.h + klineedit.h + klineedit.h + kcombobox.h + + diff --git a/amarok/src/dbsetup.ui.h b/amarok/src/dbsetup.ui.h new file mode 100644 index 00000000..5b305e0e --- /dev/null +++ b/amarok/src/dbsetup.ui.h @@ -0,0 +1,55 @@ +#//(c) 2005 Ian Monroe see COPYING +/**************************************************************************** +** ui.h extension file, included from the uic-generated form implementation. +** +** If you want to add, delete, or rename functions or slots, use +** Qt Designer to update this file, preserving your code. +** +** You should not define a constructor or destructor in this file. +** Instead, write your code in functions called init() and destroy(). +** These will automatically be called by the form's constructor and +** destructor. +*****************************************************************************/ +#include "config.h" +#include "amarokconfig.h" +#include "collectiondb.h" + +void DbSetup::init() +{ + configStack->raiseWidget( 0 ); +#ifdef USE_MYSQL + databaseEngine->insertItem( "MySQL", -1 ); + if (AmarokConfig::databaseEngine() == QString::number(DbConnection::mysql)) + { + databaseEngine->setCurrentItem("MySQL"); + configStack->raiseWidget( 1 ); + } +#endif + +#ifdef USE_POSTGRESQL + databaseEngine->insertItem( "Postgresql", -1 ); + if (AmarokConfig::databaseEngine() == QString::number(DbConnection::postgresql)) + { + databaseEngine->setCurrentItem("Postgresql"); + configStack->raiseWidget( 2 ); + } +#endif +} + +void DbSetup::databaseEngine_activated( int item ) +{ + if( item == 0 ) + configStack->raiseWidget( 0 ); + + // If built with MySQL support, the PostgreSQL config widget is #2 + // Without MySQL it's #1 +#ifdef USE_MYSQL + else if( item == 1 ) + configStack->raiseWidget( 1 ); + else if( item == 2 ) + configStack->raiseWidget( 2 ); +#elif defined(USE_POSTGRESQL) + else if( item == 1 ) + configStack->raiseWidget( 2 ); +#endif +} diff --git a/amarok/src/debug.h b/amarok/src/debug.h new file mode 100644 index 00000000..2c83ad79 --- /dev/null +++ b/amarok/src/debug.h @@ -0,0 +1,245 @@ +// Author: Max Howell , (C) 2003-5 +// Copyright: See COPYING file that comes with this distribution +// + +#ifndef AMAROK_DEBUG_H +#define AMAROK_DEBUG_H + +#include +#include +#include +#include +#include +#include + +class QApplication; +extern QApplication *qApp; ///@see Debug::Indent + + +/** + * @namespace Debug + * @short kdebug with indentation functionality and convenience macros + * @author Max Howell + * + * Usage: + * + * #define DEBUG_PREFIX "Blah" + * #include "debug.h" + * + * void function() + * { + * Debug::Block myBlock( __PRETTY_FUNCTION__ ); + * + * debug() << "output1" << endl; + * debug() << "output2" << endl; + * } + * + * Will output: + * + * app: BEGIN: void function() + * app: [Blah] output1 + * app: [Blah] output2 + * app: END: void function(): Took 0.1s + * + * @see Block + * @see CrashHelper + * @see ListStream + */ + +namespace Debug +{ + extern QMutex mutex; // defined in app.cpp + + // we can't use a statically instantiated QCString for the indent, because + // static namespaces are unique to each dlopened library. So we piggy back + // the QCString on the KApplication instance + + #define qApp reinterpret_cast(qApp) + class Indent : QObject + { + friend QCString &modifieableIndent(); + Indent() : QObject( qApp, "DEBUG_indent" ) {} + QCString m_string; + }; + + inline QCString &modifieableIndent() + { + QObject *o = qApp ? qApp->child( "DEBUG_indent" ) : 0; + QCString &ret = (o ? static_cast( o ) : new Indent)->m_string; + return ret; + } + + inline QCString indent() + { + return QDeepCopy( modifieableIndent() ); + } + #undef qApp + + + #ifdef NDEBUG + static inline kndbgstream debug() { return kndbgstream(); } + static inline kndbgstream warning() { return kndbgstream(); } + static inline kndbgstream error() { return kndbgstream(); } + static inline kndbgstream fatal() { return kndbgstream(); } + + typedef kndbgstream DebugStream; + #else + #ifndef DEBUG_PREFIX + #define AMK_PREFIX "" + #else + #define AMK_PREFIX "[" DEBUG_PREFIX "] " + #endif + + //from kdebug.h + enum DebugLevels { + KDEBUG_INFO = 0, + KDEBUG_WARN = 1, + KDEBUG_ERROR = 2, + KDEBUG_FATAL = 3 + }; + + static inline kdbgstream debug() { mutex.lock(); QCString ind = indent(); mutex.unlock(); return kdbgstream( ind, 0, KDEBUG_INFO ) << AMK_PREFIX; } + static inline kdbgstream warning() { mutex.lock(); QCString ind = indent(); mutex.unlock(); return kdbgstream( ind, 0, KDEBUG_WARN ) << AMK_PREFIX << "[WARNING!] "; } + static inline kdbgstream error() { mutex.lock(); QCString ind = indent(); mutex.unlock(); return kdbgstream( ind, 0, KDEBUG_ERROR ) << AMK_PREFIX << "[ERROR!] "; } + static inline kdbgstream fatal() { mutex.lock(); QCString ind = indent(); mutex.unlock(); return kdbgstream( ind, 0, KDEBUG_FATAL ) << AMK_PREFIX; } + + typedef kdbgstream DebugStream; + + #undef AMK_PREFIX + #endif + + typedef kndbgstream NoDebugStream; +} + +using Debug::debug; +using Debug::warning; +using Debug::error; +using Debug::fatal; +using Debug::DebugStream; + +/// Standard function announcer +#define DEBUG_FUNC_INFO { Debug::mutex.lock(); kdDebug() << Debug::indent() << k_funcinfo << endl; Debug::mutex.unlock(); } + +/// Announce a line +#define DEBUG_LINE_INFO { Debug::mutex.lock(); kdDebug() << Debug::indent() << k_funcinfo << "Line: " << __LINE__ << endl; Debug::mutex.unlock(); } + +/// Convenience macro for making a standard Debug::Block +#define DEBUG_BLOCK Debug::Block uniquelyNamedStackAllocatedStandardBlock( __PRETTY_FUNCTION__ ); + +/// Use this to remind yourself to finish the implementation of a function +#define AMAROK_NOTIMPLEMENTED warning() << "NOT-IMPLEMENTED: " << __PRETTY_FUNCTION__ << endl; + +/// Use this to alert other developers to stop using a function +#define AMAROK_DEPRECATED warning() << "DEPRECATED: " << __PRETTY_FUNCTION__ << endl; + + +namespace Debug +{ + /** + * @class Debug::Block + * @short Use this to label sections of your code + * + * Usage: + * + * void function() + * { + * Debug::Block myBlock( "section" ); + * + * debug() << "output1" << endl; + * debug() << "output2" << endl; + * } + * + * Will output: + * + * app: BEGIN: section + * app: [prefix] output1 + * app: [prefix] output2 + * app: END: section - Took 0.1s + * + */ + + class Block + { + timeval m_start; + const char *m_label; + + public: + Block( const char *label ) + : m_label( label ) + { + mutex.lock(); + gettimeofday( &m_start, 0 ); + + kdDebug() << "BEGIN: " << label << "\n"; + Debug::modifieableIndent() += " "; + mutex.unlock(); + } + + ~Block() + { + mutex.lock(); + timeval end; + gettimeofday( &end, 0 ); + + end.tv_sec -= m_start.tv_sec; + if( end.tv_usec < m_start.tv_usec) { + // Manually carry a one from the seconds field. + end.tv_usec += 1000000; + end.tv_sec--; + } + end.tv_usec -= m_start.tv_usec; + + double duration = double(end.tv_sec) + (double(end.tv_usec) / 1000000.0); + + Debug::modifieableIndent().truncate( Debug::indent().length() - 2 ); + kdDebug() << "END__: " << m_label + << " - Took " << QString::number( duration, 'g', 2 ) << "s\n"; + mutex.unlock(); + } + }; + + + /** + * @name Debug::stamp() + * @short To facilitate crash/freeze bugs, by making it easy to mark code that has been processed + * + * Usage: + * + * { + * Debug::stamp(); + * function1(); + * Debug::stamp(); + * function2(); + * Debug::stamp(); + * } + * + * Will output (assuming the crash occurs in function2() + * + * app: Stamp: 1 + * app: Stamp: 2 + * + */ + + inline void stamp() + { + static int n = 0; + debug() << "| Stamp: " << ++n << endl; + } +} + + +#include + +namespace Debug +{ + /** + * @class Debug::List + * @short You can pass anything to this and it will output it as a list + * + * debug() << (Debug::List() << anInt << aString << aQStringList << aDouble) << endl; + */ + + typedef QValueList List; +} + +#endif diff --git a/amarok/src/deletedialog.cpp b/amarok/src/deletedialog.cpp new file mode 100644 index 00000000..1c34e7d1 --- /dev/null +++ b/amarok/src/deletedialog.cpp @@ -0,0 +1,157 @@ +/*************************************************************************** + begin : Tue Aug 31 21:59:58 EST 2004 + copyright : (C) 2004 by Michael Pyne + (C) 2006 by Ian Monroe +***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "amarok.h" +#include "playlist.h" +#include "collectiondb.h" +#include "deletedialog.h" +#include "statusbar.h" + +////////////////////////////////////////////////////////////////////////////// +// DeleteWidget implementation +////////////////////////////////////////////////////////////////////////////// + +DeleteWidget::DeleteWidget(QWidget *parent, const char *name) + : DeleteDialogBase(parent, name) +{ + KConfigGroup messageGroup(KGlobal::config(), "FileRemover"); + + bool deleteInstead = messageGroup.readBoolEntry("deleteInsteadOfTrash", false); + slotShouldDelete(deleteInstead); + ddShouldDelete->setChecked(deleteInstead); +} + +void DeleteWidget::setFiles(const KURL::List &files) +{ + ddFileList->clear(); +// ddFileList->insertStringList(files); + for( KURL::List::ConstIterator it = files.begin(); it != files.end(); it++) + { + if( (*it).isLocalFile() ) //path is nil for non-local + ddFileList->insertItem( (*it).path() ); + else + ddFileList->insertItem( (*it).url() ); + } + ddNumFiles->setText(i18n("1 file selected.", "%n files selected.", files.count())); +} + +void DeleteWidget::slotShouldDelete(bool shouldDelete) +{ + if(shouldDelete) { + ddDeleteText->setText(i18n("These items will be permanently " + "deleted from your hard disk.")); + ddWarningIcon->setPixmap(KGlobal::iconLoader()->loadIcon("messagebox_warning", + KIcon::Desktop, KIcon::SizeLarge)); + } + else { + ddDeleteText->setText(i18n("These items will be moved to the Trash Bin.")); + ddWarningIcon->setPixmap(KGlobal::iconLoader()->loadIcon("trashcan_full", + KIcon::Desktop, KIcon::SizeLarge)); + } +} + +////////////////////////////////////////////////////////////////////////////// +// DeleteDialog implementation +////////////////////////////////////////////////////////////////////////////// + +DeleteDialog::DeleteDialog(QWidget *parent, const char *name) : + KDialogBase(Swallow, WStyle_DialogBorder, parent, name, + true /* modal */, i18n("About to delete selected files"), + Ok | Cancel, Cancel /* Default */, true /* separator */), + m_trashGuiItem(i18n("&Send to Trash"), "trashcan_full") +{ + m_widget = new DeleteWidget(this, "delete_dialog_widget"); + setMainWidget(m_widget); + + m_widget->setMinimumSize(400, 300); + setMinimumSize(410, 326); + adjustSize(); + + slotShouldDelete(shouldDelete()); + connect(m_widget->ddShouldDelete, SIGNAL(toggled(bool)), SLOT(slotShouldDelete(bool))); + +} + +bool DeleteDialog::confirmDeleteList(const KURL::List& condemnedFiles) +{ + m_widget->setFiles(condemnedFiles); + + return exec() == QDialog::Accepted; +} + +void DeleteDialog::setFiles(const KURL::List &files) +{ + m_widget->setFiles(files); +} + +void DeleteDialog::accept() +{ + KConfigGroup messageGroup(KGlobal::config(), "FileRemover"); + + // Save user's preference + + messageGroup.writeEntry("deleteInsteadOfTrash", shouldDelete()); + messageGroup.sync(); + + KDialogBase::accept(); +} + +void DeleteDialog::slotShouldDelete(bool shouldDelete) +{ + setButtonGuiItem(Ok, shouldDelete ? KStdGuiItem::del() : m_trashGuiItem); +} + +bool DeleteDialog::showTrashDialog(QWidget* parent, const KURL::List& files) +{ + DeleteDialog dialog(parent); + bool doDelete = dialog.confirmDeleteList(files); + + if( doDelete ) + { + KIO::Job* job = 0; + bool shouldDelete = dialog.shouldDelete(); + if ( ( shouldDelete && (job = KIO::del( files )) ) || + ( job = Amarok::trashFiles( files ) ) ) + { + if(shouldDelete) //amarok::trashFiles already does the progress operation + Amarok::StatusBar::instance()->newProgressOperation( job ) + .setDescription( i18n("Deleting files") ); + } + + } + + return doDelete; +} +#include "deletedialog.moc" + +// vim: set et ts=4 sw=4: diff --git a/amarok/src/deletedialog.h b/amarok/src/deletedialog.h new file mode 100644 index 00000000..6127f1fb --- /dev/null +++ b/amarok/src/deletedialog.h @@ -0,0 +1,67 @@ +/*************************************************************************** + begin : Tue Aug 31 21:54:20 EST 2004 + copyright : (C) 2004 by Michael Pyne + (C) 2006 by Ian Monroe +***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef _DELETEDIALOG_H +#define _DELETEDIALOG_H + + +#include +#include +#include +#include "deletedialogbase.h" + +class QStringList; +class KListBox; +class KGuiItem; +class QLabel; +class QWidgetStack; + +class DeleteWidget : public DeleteDialogBase +{ + Q_OBJECT + +public: + DeleteWidget(QWidget *parent = 0, const char *name = 0); + + void setFiles(const KURL::List &files); + +protected slots: + virtual void slotShouldDelete(bool shouldDelete); +}; + +class DeleteDialog : public KDialogBase +{ + Q_OBJECT + +public: + DeleteDialog(QWidget *parent, const char *name = "delete_dialog"); + static bool showTrashDialog(QWidget* parent, const KURL::List &files); + + bool confirmDeleteList(const KURL::List &condemnedFiles); + void setFiles(const KURL::List &files); + bool shouldDelete() const { return m_widget->ddShouldDelete->isChecked(); } + +protected slots: + virtual void accept(); + void slotShouldDelete(bool shouldDelete); + +private: + DeleteWidget *m_widget; + KGuiItem m_trashGuiItem; +}; + +#endif + +// vim: set et ts=4 sw=4: diff --git a/amarok/src/deletedialogbase.ui b/amarok/src/deletedialogbase.ui new file mode 100644 index 00000000..768050bc --- /dev/null +++ b/amarok/src/deletedialogbase.ui @@ -0,0 +1,135 @@ + +DeleteDialogBase + + + DeleteDialogBase + + + + 0 + 0 + 542 + 374 + + + + + 420 + 320 + + + + + unnamed + + + 0 + + + + layout4 + + + + unnamed + + + + ddWarningIcon + + + + 4 + 4 + 0 + 0 + + + + Icon Placeholder, not in GUI + + + + + layout3 + + + + unnamed + + + + ddDeleteText + + + Deletion method placeholder, never shown to user. + + + WordBreak|AlignCenter + + + + + + + + + ddFileList + + + NoSelection + + + List of files that are about to be deleted. + + + This is the list of items that are about to be deleted. + + + + + ddNumFiles + + + Placeholder for number of files, not in GUI + + + AlignVCenter|AlignRight + + + + + ddShouldDelete + + + &Delete files instead of moving them to the trash + + + If checked, files will be permanently removed instead of being placed in the Trash Bin + + + <qt><p>If this box is checked, files will be <b>permanently removed</b> instead of being placed in the Trash Bin.</p> + +<p><em>Use this option with caution</em>: Most filesystems are unable to reliably undelete deleted files.</p></qt> + + + + + + + + + ddShouldDelete + toggled(bool) + DeleteDialogBase + slotShouldDelete(bool) + + + + slotShouldDelete(bool) + + + + klistbox.h + + diff --git a/amarok/src/device/Makefile.am b/amarok/src/device/Makefile.am new file mode 100644 index 00000000..9d38aa8f --- /dev/null +++ b/amarok/src/device/Makefile.am @@ -0,0 +1,3 @@ +METASOURCES = AUTO + +SUBDIRS = massstorage nfs smb diff --git a/amarok/src/device/massstorage/Makefile.am b/amarok/src/device/massstorage/Makefile.am new file mode 100644 index 00000000..69ff41a5 --- /dev/null +++ b/amarok/src/device/massstorage/Makefile.am @@ -0,0 +1,28 @@ +kde_module_LTLIBRARIES = libamarok_massstorage-device.la +kde_services_DATA = amarok_massstorage-device.desktop + +INCLUDES = \ + -I$(top_builddir)/amarok/src/amarokcore \ + -I$(top_srcdir)/amarok/src/amarokcore \ + -I$(top_builddir)/amarok/src/amarokcore \ + -I$(top_srcdir)/amarok/src/plugin \ + -I$(top_builddir)/amarok/src \ + -I$(top_srcdir)/amarok/src \ + $(all_includes) + +METASOURCES = AUTO + +libamarok_massstorage_device_la_LIBADD = \ + $(top_builddir)/amarok/src/libamarok.la \ + $(LIB_KDECORE) $(LIB_QT) + +libamarok_massstorage_device_la_LDFLAGS = \ + $(KDE_PLUGIN) \ + $(all_libraries) + +libamarok_massstorage_device_la_SOURCES = \ + massstoragedevicehandler.cpp + +noinst_HEADERS = \ + massstoragedevicehandler.h + diff --git a/amarok/src/device/massstorage/amarok_massstorage-device.desktop b/amarok/src/device/massstorage/amarok_massstorage-device.desktop new file mode 100644 index 00000000..ef48dee5 --- /dev/null +++ b/amarok/src/device/massstorage/amarok_massstorage-device.desktop @@ -0,0 +1,110 @@ +[Desktop Entry] +Encoding=UTF-8 +Type=Service +Name=Mass Storage Device +Name[af]=Massa Stoor Toestel +Name[ar]=جهاز حفظ ذو الذاكرة الضخمة +Name[bg]=Устройство за съхранение +Name[bn]=মাস স্টোরেজ ডিভাইস +Name[ca]=Dispositiu d'emmagatzemament massiu +Name[cs]=Souborové mediální zařízení +Name[da]=Masseopbevaringsenhed +Name[de]=Wechsellaufwerk +Name[el]=Συσκευή μαζικής αποθήκευσης +Name[eo]=Amasmemora Ekipaĵo +Name[es]=Dispositivo de almacenamiento masivo +Name[et]=Mass-salvestusseade +Name[fa]=دستگاه ذخیره‌گاه حجم +Name[fi]=Massamuistilaite +Name[fr]=Périphérique de stockage +Name[gl]=Dispositivo de Armacenaxe +Name[hu]=Tárolóeszköz +Name[is]=Harðir diskar o.þ.h +Name[it]=Dispositivo di memorizzazione di massa +Name[ja]=マスストレージデバイス +Name[ka]=არქივის შესანახი მოწყობილობა +Name[km]=ឧបករណ៍​ផ្ទុក​ធំ +Name[lt]=Duomenų saugojimo įrenginys +Name[mk]=Уред за складирање податоци +Name[nb]=Masselagerenhet +Name[nds]=Bültspieker-Reedschap +Name[ne]=बृहत भण्डारण यन्त्र +Name[nl]=Massa-opslag-apparaat +Name[nn]=Masselagringseining +Name[pa]=ਵੱਡਾ ਸਟੋਰੇਜ਼ ਜੰਤਰ +Name[pl]=Urządzenie przechowywania danych +Name[pt]=Dispositivo Multimédia com Armazenamento +Name[pt_BR]=Dispositivo de Armazenamento em Massa +Name[se]=Vurkenovttadat +Name[sk]=Zariadenie na masové ukladanie +Name[sr]=Уређај за масовно складиштење +Name[sr@Latn]=Uređaj za masovno skladištenje +Name[sv]=Masslagringsenhet +Name[th]=อุปกรณ์สื่อบันทึกข้อมูล +Name[tr]=Yığın Depolama Aygıtı +Name[uk]=Пристрій масового накопичувача +Name[uz]=Saqlash uskunasi +Name[uz@cyrillic]=Сақлаш ускунаси +Name[wa]=Éndjin d' wårdaedje di masse +Name[zh_CN]=大容量存储设备 +Name[zh_TW]=大型儲存裝置 +Comment=Device plugin for Amarok +Comment[af]=Inprop module vir Amarok +Comment[ar]= قابس ( برنامج مضاف الى) AmaroK +Comment[bg]=Приставка за устройство за Amarok +Comment[bn]=আমারক-এর জন্য ডিভাইস প্লাগিন +Comment[ca]=Connector de dispositiu per a l'Amarok +Comment[cs]=Modul zařízení pro AmaroK +Comment[da]=Enheds-plugin for Amarok +Comment[de]=Geräte-Modul für Amarok +Comment[el]=Πρόσθετο συσκευής για το AmaroK +Comment[eo]=Ekipaĵa kromaĵo por Amarok +Comment[es]=Plugin de dispositivos para Amarok +Comment[et]=Amaroki seadmeplugin +Comment[fa]=وصلۀ دستگاه برای Amarok +Comment[fi]=Amarok-laiteliitännäinen +Comment[fr]=Plugin de périphérique pour Amarok +Comment[ga]=Breiseán gléis le haghaidh AmaroK +Comment[gl]=Extensión de dispositivos para Amarok +Comment[hu]=Eszköz-bővítőmodul az Amarokhoz +Comment[is]=Tækja íforrit fyrir Amarok +Comment[it]=Plugin di dispositivo per Amarok +Comment[ja]=Amarok のためのデバイスプラグイン +Comment[ka]=მოწყობილობის ძრავი Amarok-ისთვის +Comment[km]=កម្មវិធី​ជំនួយ​ឧបករណ៍​សម្រាប់ Amarok +Comment[lt]=Amarok įrenginio priedas +Comment[mk]=Приклучок за уред за Амарок +Comment[ms]=Plugin peranti untuk Amarok +Comment[nb]=Amarok programtillegg for masselagerenhet +Comment[nds]=Reedschapmoduul för Amarok +Comment[ne]=अमारोकका लागि यन्त्र प्लगइन +Comment[nl]=Apparaatplugin voor Amarok +Comment[nn]=Einingstillegg for Amarok +Comment[pa]=ਅਮਰੋਕ ਲਈ ਜੰਤਰ ਪਲੱਗਇਨ +Comment[pl]=Wtyczka urządzenia dla Amaroka +Comment[pt]='Plugin' de dispositivo para o Amarok +Comment[pt_BR]=Plugin de dispositivo para o Amarok +Comment[se]=Ovttadatlassemoduvla Amarok:ii +Comment[sk]=Modul zariadenia pre Amarok +Comment[sr]=Уређајски прикључак за Amarok +Comment[sr@Latn]=Uređajski priključak za Amarok +Comment[sv]=Enhetsinsticksprogram för Amarok +Comment[th]=โปรแกรมเสริมจัดการอุปกรณ์สำหรับ Amarok +Comment[tr]=Amarok için aygıt eklentisi +Comment[uk]=Втулок пристроїв для Amarok +Comment[uz]=Amarok uchun uskuna plagini +Comment[uz@cyrillic]=Amarok учун ускуна плагини +Comment[wa]=Tchôke-divins d' éndjin po Amarok +Comment[zh_CN]=Amarok 的设备插件 +Comment[zh_TW]=amaroK 裝置插件 +ServiceTypes=Amarok/Plugin + +X-KDE-Library=libamarok_massstorage-device + +X-KDE-Amarok-authors=Maximilian Kossick +X-KDE-Amarok-email=maximilian.kossick@googlemail.com +X-KDE-Amarok-framework-version=32 +X-KDE-Amarok-name=massstorage-device +X-KDE-Amarok-plugintype=device +X-KDE-Amarok-rank=100 +X-KDE-Amarok-version=1 diff --git a/amarok/src/device/massstorage/massstoragedevicehandler.cpp b/amarok/src/device/massstorage/massstoragedevicehandler.cpp new file mode 100644 index 00000000..76ff777b --- /dev/null +++ b/amarok/src/device/massstorage/massstoragedevicehandler.cpp @@ -0,0 +1,162 @@ +/* + * Copyright (c) 2006-2007 Maximilian Kossick + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU 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. + */ +#define DEBUG_PREFIX "MassStorageDeviceHandler" + +#include "massstoragedevicehandler.h" + +AMAROK_EXPORT_PLUGIN( MassStorageDeviceHandlerFactory ) + +#include "collectiondb.h" +#include "debug.h" + +#include +#include + +#include + +MassStorageDeviceHandler::MassStorageDeviceHandler() + : DeviceHandler() + , m_deviceID( -1 ) +{ +} + +MassStorageDeviceHandler::MassStorageDeviceHandler( int deviceId, const QString &mountPoint, const QString &uuid ) + : DeviceHandler() + , m_deviceID( deviceId ) + , m_mountPoint( mountPoint ) + , m_uuid( uuid ) +{ +} + +MassStorageDeviceHandler::~MassStorageDeviceHandler() +{ +} + +bool MassStorageDeviceHandler::isAvailable() const +{ + return true; +} + + +QString MassStorageDeviceHandler::type() const +{ + return "uuid"; +} + +int MassStorageDeviceHandler::getDeviceID() +{ + return m_deviceID; +} + +const QString &MassStorageDeviceHandler::getDevicePath() const +{ + return m_mountPoint; +} + +void MassStorageDeviceHandler::getURL( KURL &absolutePath, const KURL &relativePath ) +{ + absolutePath.setPath( m_mountPoint ); + absolutePath.addPath( relativePath.path() ); + absolutePath.cleanPath(); +} + +void MassStorageDeviceHandler::getPlayableURL( KURL &absolutePath, const KURL &relativePath ) +{ + getURL( absolutePath, relativePath ); +} + +bool MassStorageDeviceHandler::deviceIsMedium( const Medium * m ) const +{ + return m_uuid == m->id(); +} + +/////////////////////////////////////////////////////////////////////////////// +// class MassStorageDeviceHandlerFactory +/////////////////////////////////////////////////////////////////////////////// + +QString MassStorageDeviceHandlerFactory::type( ) const +{ + return "uuid"; +} + +bool MassStorageDeviceHandlerFactory::canCreateFromMedium( ) const +{ + return true; +} + +bool MassStorageDeviceHandlerFactory::canCreateFromConfig( ) const +{ + return false; +} + +bool MassStorageDeviceHandlerFactory::canHandle( const Medium * m ) const +{ + return m && !m->id().isEmpty() && !excludedFilesystem( m->fsType() ); +} + +MassStorageDeviceHandlerFactory::MassStorageDeviceHandlerFactory( ) +{ +} + +MassStorageDeviceHandlerFactory::~MassStorageDeviceHandlerFactory( ) +{ +} + +DeviceHandler * MassStorageDeviceHandlerFactory::createHandler( const KConfig* ) const +{ + return 0; +} + +DeviceHandler * MassStorageDeviceHandlerFactory::createHandler( const Medium * m ) const +{ + QStringList ids = CollectionDB::instance()->query( QString( "SELECT id, label, lastmountpoint " + "FROM devices WHERE type = 'uuid' " + "AND uuid = '%1';" ).arg( m->id() ) ); + if ( ids.size() == 3 ) + { + debug() << "Found existing UUID config for ID " << ids[0] << " , uuid " << m->id() << endl; + CollectionDB::instance()->query( QString( "UPDATE devices SET lastmountpoint = '%2' WHERE " + "id = %1;" ).arg( ids[0] ).arg( m->mountPoint() ) ); + return new MassStorageDeviceHandler( ids[0].toInt(), m->mountPoint(), m->id() ); + } + else + { + int id = CollectionDB::instance()->insert( QString( "INSERT INTO devices( type, uuid, lastmountpoint ) " + "VALUES ( 'uuid', '%1', '%2' );" ) + .arg( m->id() ) + .arg( m->mountPoint() ), "devices" ); + if ( id == 0 ) + { + warning() << "Inserting into devices failed for type=uuid, uuid=" << m->id() << endl; + return 0; + } + debug() << "Created new UUID device with ID " << id << " , uuid " << m->id() << endl; + return new MassStorageDeviceHandler( id, m->mountPoint(), m->id() ); + } +} + +bool +MassStorageDeviceHandlerFactory::excludedFilesystem( const QString &fstype ) const +{ + return fstype.isEmpty() || + fstype.find( "smb" ) != -1 || + fstype.find( "cifs" ) != -1 || + fstype.find( "nfs" ) != -1 || + fstype == "udf" || + fstype == "iso9660" ; +} diff --git a/amarok/src/device/massstorage/massstoragedevicehandler.h b/amarok/src/device/massstorage/massstoragedevicehandler.h new file mode 100644 index 00000000..cb3bdf92 --- /dev/null +++ b/amarok/src/device/massstorage/massstoragedevicehandler.h @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2006-2007 Maximilian Kossick + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU 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 MASSSTORAGEDEVICEHANDLER_H +#define MASSSTORAGEDEVICEHANDLER_H + +#include + +class MassStorageDeviceHandlerFactory : public DeviceHandlerFactory +{ +public: + MassStorageDeviceHandlerFactory(); + virtual ~MassStorageDeviceHandlerFactory(); + + virtual bool canHandle( const Medium* m ) const; + + virtual bool canCreateFromMedium() const; + + virtual DeviceHandler* createHandler( const Medium* m ) const; + + virtual bool canCreateFromConfig() const; + + virtual DeviceHandler* createHandler( const KConfig* c ) const; + + virtual QString type() const; + +private: + bool excludedFilesystem( const QString &fstype ) const; +}; + +/** + @author Maximilian Kossick +*/ +class MassStorageDeviceHandler : public DeviceHandler +{ +public: + MassStorageDeviceHandler(); + MassStorageDeviceHandler(int deviceId, const QString &mountPoint, const QString &uuid ); + + virtual ~MassStorageDeviceHandler(); + + virtual bool isAvailable() const; + virtual QString type() const; + virtual int getDeviceID( ); + virtual const QString &getDevicePath() const; + virtual void getURL( KURL &absolutePath, const KURL &relativePath ); + virtual void getPlayableURL( KURL &absolutePath, const KURL &relativePath ); + virtual bool deviceIsMedium( const Medium *m ) const; + +private: + + int m_deviceID; + const QString m_mountPoint; + QString m_uuid; + +}; + +#endif diff --git a/amarok/src/device/nfs/Makefile.am b/amarok/src/device/nfs/Makefile.am new file mode 100644 index 00000000..7b7ab9cb --- /dev/null +++ b/amarok/src/device/nfs/Makefile.am @@ -0,0 +1,30 @@ +kde_module_LTLIBRARIES = libamarok_nfs-device.la +kde_services_DATA = amarok_nfs-device.desktop + +INCLUDES = \ + -I$(top_builddir)/amarok/src/amarokcore \ + -I$(top_srcdir)/amarok/src/amarokcore \ + -I$(top_builddir)/amarok/src/amarokcore \ + -I$(top_srcdir)/amarok/src/plugin \ + -I$(top_builddir)/amarok/src \ + -I$(top_srcdir)/amarok/src \ + $(all_includes) + +METASOURCES = AUTO + +libamarok_nfs_device_la_LIBADD = \ + $(top_builddir)/amarok/src/libamarok.la \ + $(LIB_KDECORE) $(LIB_QT) + +libamarok_nfs_device_la_LDFLAGS = \ + $(KDE_PLUGIN) \ + $(all_libraries) + + + + + + + +noinst_HEADERS = nfsdevicehandler.h +libamarok_nfs_device_la_SOURCES = nfsdevicehandler.cpp diff --git a/amarok/src/device/nfs/amarok_nfs-device.desktop b/amarok/src/device/nfs/amarok_nfs-device.desktop new file mode 100644 index 00000000..d513ea95 --- /dev/null +++ b/amarok/src/device/nfs/amarok_nfs-device.desktop @@ -0,0 +1,107 @@ +[Desktop Entry] +Encoding=UTF-8 +Type=Service +Name=NFS Device +Name[af]=NFS Toestel +Name[ar]=جهاز نظام الملفات على الشبكة NFS +Name[bg]=NFS устройство +Name[bn]=এন-এফ-এস ডিভাইস +Name[ca]=Dispositiu NFS +Name[cs]=NFS zařízení +Name[da]=NFS-enhed +Name[de]=NFS-Gerät +Name[el]=Συσκευή NFS +Name[eo]=NFS Aranĝaĵo +Name[es]=Dispositivo NFS +Name[et]=NFS-seade +Name[fa]=دستگاه NFS +Name[fi]=NFS-laite +Name[fr]=Périphérique NFS +Name[ga]=Gléas NFS +Name[gl]=Dispositivo NFS +Name[hu]=NFS-eszköz +Name[is]=NFS tæki +Name[it]=Dispositivo NFS +Name[ja]=NFS デバイス +Name[ka]=NFS მოწყობილობა +Name[km]=ឧបករណ៍ NFS +Name[lt]=NFS įrenginys +Name[mk]=NFS-уред +Name[ms]=Peranti NFS +Name[nb]=NFS-enhet +Name[nds]=NFS-Reedschap +Name[ne]=NFS यन्त्र +Name[nl]=NFS-apparaat +Name[nn]=NFS-eining +Name[pa]=NFS ਜੰਤਰ +Name[pl]=Urządzenie NFS +Name[pt]=Dispositivo NFS +Name[pt_BR]=Dispositivo NFS +Name[se]=NFS-ovttadat +Name[sk]=NFS zariadenie +Name[sr]=NFS уређај +Name[sr@Latn]=NFS uređaj +Name[sv]=NFS-enhet +Name[tr]=NFS Aygıtı +Name[uk]=Пристрій NFS +Name[uz]=NFS uskunasi +Name[uz@cyrillic]=NFS ускунаси +Name[wa]=Éndjin NFS +Name[zh_CN]=NFS 设备 +Name[zh_TW]=NFS 裝置 +Comment=Device plugin for Amarok which supports NFS +Comment[af]=Toestel inprop module vir Amarok wat NFS ondersteuning bied +Comment[bg]=Приставка за устройство за Amarok, поддържаща NFS +Comment[bn]=আমারক-এর জন্য ডিভাইস প্লাগিন যা এন-এফ-এস সমর্থন করে +Comment[ca]=Connector de dispositiu per a l'Amarok que accepta NFS +Comment[cs]=Modul zařízení pro AmaroK, které podporuje NFS +Comment[da]=Enhedsplugin for Amarok som understøtter NFS +Comment[de]=Geräte-Modul für Amarok mit Unterstützung für NFS +Comment[el]=Πρόσθετο συσκευής για το AmaroK με υποστήριξη NFS +Comment[eo]=Aranĝaĵa kromaĵo por Amarok kiu subtenas NFS-n +Comment[es]=Plugin de dispositivo con soporte NFS para Amarok +Comment[et]=Amaroki seadmeplugin NFS-i toega +Comment[fa]=وصلۀ دستگاه برای Amarok که از NFS پشتیبانی می‌کند +Comment[fi]=Amarok-liitännäinen, joka tukee NFS:ää (Network File System) +Comment[fr]=Plugin de périphérique pour Amarok qui supporte NFS +Comment[gl]=Extensión de dispositivos que soporten NFS para Amarok +Comment[hu]=NFS-t támogató eszköz-bővítőmodul az Amarokhoz +Comment[is]=Tækja íforrit fyrir Amarok með stuðning við NFS +Comment[it]=Plugin di dispositivo per Amarok con supporto NFS +Comment[ja]=NFS をサポートする Amarok のためのデバイスプラグイン +Comment[ka]=მოწყობილობის მოდული Amarok-ისთვის NFS მხარდაჭერით +Comment[km]=កម្មវិធី​ជំនួយ​ឧបករណ៍ សម្រាប់ Amarok ដែល​គាំទ្រ NFS +Comment[lt]=Amarok įrenginio priedas, palaikantis NFS +Comment[mk]=Приклучок за уред за Амарок што поддржува NFS +Comment[ms]=Plugin peranti untuk Amarok yang menyokong NFS +Comment[nb]=Amarok programtillegg for enhet som støtter NFS +Comment[nds]=Reedschapmoduul för Amarok, dat NFS ünnerstütt +Comment[ne]=अमारोकका लागि यन्त्र प्लगइन जसले NFS समर्थन गर्दछ +Comment[nl]=Apparaatplugin voor Amarok met ondersteuning voor NFS +Comment[nn]=Einingstillegg for Amarok som støttar NFS +Comment[pa]=ਅਮਰੋਕ ਲਈ ਜੰਤਰ ਪਲੱਗਇਨ, ਜੋ ਕਿ NFS ਲਈ ਸਹਾਇਕ ਹੈ +Comment[pl]=Wtyczka urządzenia dla Amaroka z obsługą NFS +Comment[pt]=Um 'plugin' de dispositivo para o Amarok que suporta o NFS +Comment[pt_BR]=Plugin de dispositivo para o Amarok, que suporta NFS +Comment[se]=Ovttadatlassemoduvla Amarokii mii doarju NFS +Comment[sk]=Modul zariadenia pre Amarok, ktorý podporuje NFS +Comment[sr]=Уређајски прикључак за Amarok који подржава NFS +Comment[sr@Latn]=Uređajski priključak za Amarok koji podržava NFS +Comment[sv]=Enhetsinsticksprogram för Amarok som stöder NFS +Comment[th]=โปรแกรมเสริมจัดการอุปกรณ์สำหรับ Amarok ที่รองรับการใช้งาน NFS +Comment[tr]=Amarok için NFS destekleyen aygıt eklentisi +Comment[uk]=Втулок пристроїв для Amarok, який підтримує NFS +Comment[wa]=Tchôke-divins d' éndjin po Amarok ki sopoite NFS +Comment[zh_CN]=Amarok 支持 NFS 的设备插件 +Comment[zh_TW]=支援 NFS 的 amaroK 裝置插件 +ServiceTypes=Amarok/Plugin + +X-KDE-Library=libamarok_nfs-device + +X-KDE-Amarok-authors=Maximilian Kossick +X-KDE-Amarok-email=maximilian.kossick@googlemail.com +X-KDE-Amarok-framework-version=32 +X-KDE-Amarok-name=nfs-device +X-KDE-Amarok-plugintype=device +X-KDE-Amarok-rank=100 +X-KDE-Amarok-version=1 diff --git a/amarok/src/device/nfs/nfsdevicehandler.cpp b/amarok/src/device/nfs/nfsdevicehandler.cpp new file mode 100644 index 00000000..274f76e5 --- /dev/null +++ b/amarok/src/device/nfs/nfsdevicehandler.cpp @@ -0,0 +1,164 @@ +/* + * Copyright (c) 2006-2007 Maximilian Kossick + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU 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 "nfsdevicehandler.h" + +AMAROK_EXPORT_PLUGIN( NfsDeviceHandlerFactory ) + +#include "debug.h" + +#include +#include + +#include + + +NfsDeviceHandler::NfsDeviceHandler( int deviceId, QString server, QString dir, QString mountPoint ) + : DeviceHandler() + , m_deviceID( deviceId ) + , m_mountPoint( mountPoint ) + , m_server( server ) + , m_dir( dir ) +{ +} + +NfsDeviceHandler::~NfsDeviceHandler() +{ +} + +bool +NfsDeviceHandler::isAvailable() const +{ + return true; +} + + +QString +NfsDeviceHandler::type() const +{ + return "nfs"; +} + +int +NfsDeviceHandler::getDeviceID() +{ + return m_deviceID; +} + +const QString & +NfsDeviceHandler::getDevicePath() const +{ + return m_mountPoint; +} + +void +NfsDeviceHandler::getURL( KURL &absolutePath, const KURL &relativePath ) +{ + absolutePath.setPath( m_mountPoint ); + absolutePath.addPath( relativePath.path() ); + absolutePath.cleanPath(); +} + +void +NfsDeviceHandler::getPlayableURL( KURL &absolutePath, const KURL &relativePath ) +{ + getURL( absolutePath, relativePath ); +} + +bool +NfsDeviceHandler::deviceIsMedium( const Medium * m ) const +{ + return m->deviceNode() == m_server + ':' + m_dir; +} + +/////////////////////////////////////////////////////////////////////////////// +// class NfsDeviceHandlerFactory +/////////////////////////////////////////////////////////////////////////////// + +QString +NfsDeviceHandlerFactory::type( ) const +{ + return "nfs"; +} + +bool +NfsDeviceHandlerFactory::canCreateFromMedium( ) const +{ + return true; +} + +bool +NfsDeviceHandlerFactory::canCreateFromConfig( ) const +{ + return false; +} + +bool +NfsDeviceHandlerFactory::canHandle( const Medium * m ) const +{ + return m && m->fsType() == "nfs" && m->isMounted(); +} + +NfsDeviceHandlerFactory::NfsDeviceHandlerFactory( ) +{ +} + +NfsDeviceHandlerFactory::~NfsDeviceHandlerFactory( ) +{ +} + +DeviceHandler * +NfsDeviceHandlerFactory::createHandler( const KConfig* ) const +{ + return 0; +} + +DeviceHandler * +NfsDeviceHandlerFactory::createHandler( const Medium * m ) const +{ + QString server = m->deviceNode().section( ":", 0, 0 ); + QString share = m->deviceNode().section( ":", 1, 1 ); + QStringList ids = CollectionDB::instance()->query( QString( "SELECT id, label, lastmountpoint " + "FROM devices WHERE type = 'nfs' " + "AND servername = '%1' AND sharename = '%2';" ) + .arg( server ) + .arg( share ) ); + if ( ids.size() == 3 ) + { + debug() << "Found existing NFS config for ID " << ids[0] << " , server " << server << " ,share " << share << endl; + CollectionDB::instance()->query( QString( "UPDATE devices SET lastmountpoint = '%2' WHERE " + "id = %1;" ).arg( ids[0] ).arg( m->mountPoint() ) ); + return new NfsDeviceHandler( ids[0].toInt(), server, share, m->mountPoint() ); + } + else + { + int id = CollectionDB::instance()->insert( QString( "INSERT INTO devices" + "( type, servername, sharename, lastmountpoint ) " + "VALUES ( 'nfs', '%1', '%2', '%3' );" ) + .arg( server ) + .arg( share ) + .arg( m->mountPoint() ), "devices" ); + if ( id == 0 ) + { + warning() << "Inserting into devices failed for type=nfs, server=" << server << ", share=" << share << endl; + return 0; + } + debug() << "Created new NFS device with ID " << id << " , server " << server << " ,share " << share << endl; + return new NfsDeviceHandler( id, server, share, m->mountPoint() ); + } +} + diff --git a/amarok/src/device/nfs/nfsdevicehandler.h b/amarok/src/device/nfs/nfsdevicehandler.h new file mode 100644 index 00000000..a17af7aa --- /dev/null +++ b/amarok/src/device/nfs/nfsdevicehandler.h @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2006-2007 Maximilian Kossick + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU 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 NFSDEVICEHANDLER_H +#define NFSDEVICEHANDLER_H + +#include + +class NfsDeviceHandlerFactory : public DeviceHandlerFactory +{ +public: + NfsDeviceHandlerFactory(); + virtual ~NfsDeviceHandlerFactory(); + + virtual bool canHandle( const Medium* m ) const; + + virtual bool canCreateFromMedium() const; + + virtual DeviceHandler* createHandler( const Medium* m ) const; + + virtual bool canCreateFromConfig() const; + + virtual DeviceHandler* createHandler( const KConfig* c ) const; + + virtual QString type() const; +}; + +/** + @author Maximilian Kossick +*/ +class NfsDeviceHandler : public DeviceHandler +{ +public: + NfsDeviceHandler(int deviceId, QString server, QString dir, QString mountPoint ); + + virtual ~NfsDeviceHandler(); + + virtual bool isAvailable() const; + virtual QString type() const; + virtual int getDeviceID( ); + virtual const QString &getDevicePath() const; + virtual void getURL( KURL &absolutePath, const KURL &relativePath ); + virtual void getPlayableURL( KURL &absolutePath, const KURL &relativePath ); + virtual bool deviceIsMedium( const Medium *m ) const; + +private: + + int m_deviceID; + const QString m_mountPoint; + QString m_server; + QString m_dir; + +}; + +#endif diff --git a/amarok/src/device/smb/Makefile.am b/amarok/src/device/smb/Makefile.am new file mode 100644 index 00000000..d5598a8a --- /dev/null +++ b/amarok/src/device/smb/Makefile.am @@ -0,0 +1,24 @@ +kde_module_LTLIBRARIES = libamarok_smb-device.la +kde_services_DATA = amarok_smb-device.desktop + +INCLUDES = \ + -I$(top_builddir)/amarok/src/amarokcore \ + -I$(top_srcdir)/amarok/src/amarokcore \ + -I$(top_builddir)/amarok/src/amarokcore \ + -I$(top_srcdir)/amarok/src/plugin \ + -I$(top_builddir)/amarok/src \ + -I$(top_srcdir)/amarok/src \ + $(all_includes) + +METASOURCES = AUTO + +libamarok_smb_device_la_LIBADD = \ + $(top_builddir)/amarok/src/libamarok.la \ + $(LIB_KDECORE) $(LIB_QT) + +libamarok_smb_device_la_LDFLAGS = \ + $(KDE_PLUGIN) \ + $(all_libraries) + +noinst_HEADERS = smbdevicehandler.h +libamarok_smb_device_la_SOURCES = smbdevicehandler.cpp diff --git a/amarok/src/device/smb/amarok_smb-device.desktop b/amarok/src/device/smb/amarok_smb-device.desktop new file mode 100644 index 00000000..57ee4590 --- /dev/null +++ b/amarok/src/device/smb/amarok_smb-device.desktop @@ -0,0 +1,106 @@ +[Desktop Entry] +Encoding=UTF-8 +Type=Service +Name=SMB Device +Name[af]=SMB Toestel +Name[ar]=جهاز SMB +Name[bg]=SMB устройство +Name[bn]=এস-এম‌-বি ডিভাইস +Name[ca]=Dispositiu SMB +Name[cs]=SMB zařízení +Name[da]=SMB-enhed +Name[de]=SMB-Gerät +Name[el]=Συσκευή SMB +Name[eo]=SMB Aranĝaĵo +Name[es]=Dispositivo SMB +Name[et]=SMB-seade +Name[fa]=دستگاه SMB +Name[fi]=SMB-laite +Name[fr]=Périphérique SMB +Name[ga]=Gléas SMB +Name[gl]=Dispositivo SMB +Name[hu]=SMB-eszköz +Name[is]=SMB tæki +Name[it]=Dispositivo SMB +Name[ja]=SMB デバイス +Name[ka]=SMB მოწყობილობა +Name[km]=ឧបករណ៍ SMB +Name[lt]=SMB įrenginys +Name[mk]=SMB-уред +Name[ms]=Peranti SMB +Name[nb]=SMB-enhet +Name[nds]=SMB-Reedschap +Name[ne]=SMB यन्त्र +Name[nl]=SMB-apparaat +Name[nn]=SMB-eining +Name[pa]=SMB ਜੰਤਰ +Name[pl]=Urządzenie SMB +Name[pt]=Dispositivo SMB +Name[pt_BR]=Dispositivo SMB +Name[se]=SMB-ovttadat +Name[sk]=SMB zariadenie +Name[sr]=SMB уређај +Name[sr@Latn]=SMB uređaj +Name[sv]=SMB-enhet +Name[tr]=SMB Aygıtı +Name[uk]=Пристрій SMB +Name[uz]=SMB uskunasi +Name[uz@cyrillic]=SMB ускунаси +Name[wa]=Éndjin SMB +Name[zh_CN]=SMB 设备 +Name[zh_TW]=MSB 裝置 +Comment=Device plugin for Amarok which supports SMBFS +Comment[af]=Toestel inprop module vir Amarok wat SMBFS ondersteuning bied +Comment[bg]=Приставка за устройство за Amarok, поддържаща SMBFS +Comment[bn]=আমারক-এর জন্য ডিভাইস প্লাগিন যা এসএম‌বিএফএস সমর্থন করে +Comment[ca]=Connector de dispositiu per a l'Amarok que accepta SMBFS +Comment[cs]=Modul zařízení pro AmaroK, které podporuje SMBFS +Comment[da]=Enhedsplugin for Amarok som understøtter SMBFS +Comment[de]=Geräte-Modul für Amarok mit Unterstützung für SMB-Dateisystem +Comment[el]=Πρόσθετο συσκευής για το AmaroK με υποστήριξη SMBFS +Comment[eo]=Aranĝaĵa kromaĵo por Amarok kiu subtenas SMBFS-n +Comment[es]=Plugin de dispositivo con soporte SMBFS para Amarok +Comment[et]=Amaroki seadmeplugin SMBFS-i toega +Comment[fa]=وصلۀ دستگاه برای Amarok که از SMBFS پشتیبانی می‌کند +Comment[fi]=Amarok-liitännäinen, joka tukee SMBFS:ää +Comment[fr]=Plugin de périphérique pour Amarok qui supporte SMBFS +Comment[gl]=Extensión de dispositivos que soporten SMBFS para Amarok +Comment[hu]=SMBFS-t támogató eszköz-bővítőmodul az Amarokhoz +Comment[is]=Tækja íforrit fyrir Amarok með stuðning við SMBFS +Comment[it]=Plugin di dispositivo per Amarok con supporto SMBFS +Comment[ja]=SMBFS をサポートする Amarok のためのデバイスプラグイン +Comment[km]=កម្មវិធី​ជំនួយ​ឧបករណ៍​សម្រាប់ Amarok ដែល​គាំទ្រ SMBFS +Comment[lt]=Amarok įrenginio priedas, palaikantis SMBFS +Comment[mk]=Приклучок за уред за Амарок што поддржува SMBFS +Comment[ms]=Plugmasuk peranti untuk Amarok yang menyokong SMBFS +Comment[nb]=Amarok programtillegg for enhet som støtter SMBFS +Comment[nds]=Reedschapmoduul för Amarok, dat SMBFS ünnerstütt +Comment[ne]=अमारोकका लागि यन्त्र प्लगइन जसले SMBFS समर्थन गर्दछ +Comment[nl]=Apparaatplugin voor Amarok met ondersteuning voor SMBFS +Comment[nn]=Einingstillegg for Amarok som støttar SMBFS +Comment[pa]=ਅਮਰੋਕ ਲਈ ਜੰਤਰ ਪਲੱਗਇਨ, ਜੋ ਕਿ SMBFS ਲਈ ਸਹਾਇਕ ਹੈ +Comment[pl]=Wtyczka urządzenia dla Amaroka obsługująca SMBFS +Comment[pt]=Um 'plugin' de dispositivo para o Amarok que suporta o SMBFS +Comment[pt_BR]=Plugin de dispositivo para o Amarok, que suporta SMBFS +Comment[se]=Ovttadatlassemoduvla Amarokii mii doarju SMBFS +Comment[sk]=Modul zariadenia pre Amarok, ktorý podporuje SMBFS +Comment[sr]=Уређајски прикључак за Amarok који подржава SMBFS +Comment[sr@Latn]=Uređajski priključak za Amarok koji podržava SMBFS +Comment[sv]=Enhetsinsticksprogram för Amarok som stöder SMBFS +Comment[th]=โปรแกรมเสริมจัดการอุปกรณ์สำหรับ Amarok ที่รองรับการใช้งาน SMBFS +Comment[tr]=Amarok için SMBFS destekleyen aygıt eklentisi +Comment[uk]=Втулок пристроїв для Amarok, який підтримує SMBFS +Comment[wa]=Tchôke-divins d' éndjin po Amarok ki sopoite SMBFS +Comment[zh_CN]=Amarok 支持 SMBFS 的设备插件 +Comment[zh_TW]=支援 SMBFS 的 amaroK 裝置插件 +ServiceTypes=Amarok/Plugin + +X-KDE-Library=libamarok_smb-device + +X-KDE-Amarok-authors=Maximilian Kossick +X-KDE-Amarok-email=maximilian.kossick@googlemail.com +X-KDE-Amarok-framework-version=32 +X-KDE-Amarok-name=smb-device +X-KDE-Amarok-plugintype=device +X-KDE-Amarok-rank=100 +X-KDE-Amarok-version=1 diff --git a/amarok/src/device/smb/smbdevicehandler.cpp b/amarok/src/device/smb/smbdevicehandler.cpp new file mode 100644 index 00000000..2e778fcd --- /dev/null +++ b/amarok/src/device/smb/smbdevicehandler.cpp @@ -0,0 +1,166 @@ +/* + * Copyright (c) 2006-2007 Maximilian Kossick + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU 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 "smbdevicehandler.h" + +AMAROK_EXPORT_PLUGIN( SmbDeviceHandlerFactory ) + +#include "debug.h" + +#include +#include + +#include + + +SmbDeviceHandler::SmbDeviceHandler( int deviceId, QString server, QString dir, QString mountPoint ) + : DeviceHandler() + , m_deviceID( deviceId ) + , m_mountPoint( mountPoint ) + , m_server( server ) + , m_dir( dir ) +{ +} + +SmbDeviceHandler::~SmbDeviceHandler() +{ +} + +bool +SmbDeviceHandler::isAvailable() const +{ + return true; +} + + +QString +SmbDeviceHandler::type() const +{ + return "smb"; +} + +int +SmbDeviceHandler::getDeviceID() +{ + return m_deviceID; +} + +const QString & +SmbDeviceHandler::getDevicePath() const +{ + return m_mountPoint; +} + +void +SmbDeviceHandler::getURL( KURL &absolutePath, const KURL &relativePath ) +{ + absolutePath.setPath( m_mountPoint ); + absolutePath.addPath( relativePath.path() ); + absolutePath.cleanPath(); +} + +void +SmbDeviceHandler::getPlayableURL( KURL &absolutePath, const KURL &relativePath ) +{ + getURL( absolutePath, relativePath ); +} + +bool +SmbDeviceHandler::deviceIsMedium( const Medium * m ) const +{ + return m->deviceNode() == m_server + ':' + m_dir; +} + +/////////////////////////////////////////////////////////////////////////////// +// class SmbDeviceHandlerFactory +/////////////////////////////////////////////////////////////////////////////// + +QString +SmbDeviceHandlerFactory::type( ) const +{ + return "smb"; +} + +bool +SmbDeviceHandlerFactory::canCreateFromMedium( ) const +{ + return true; +} + +bool +SmbDeviceHandlerFactory::canCreateFromConfig( ) const +{ + return false; +} + +bool +SmbDeviceHandlerFactory::canHandle( const Medium * m ) const +{ + return m && ( m->fsType().find( "smb" ) != -1 || + m->fsType().find( "cifs" ) != -1 ) + && m->isMounted(); +} + +SmbDeviceHandlerFactory::SmbDeviceHandlerFactory( ) +{ +} + +SmbDeviceHandlerFactory::~SmbDeviceHandlerFactory( ) +{ +} + +DeviceHandler * +SmbDeviceHandlerFactory::createHandler( const KConfig* ) const +{ + return 0; +} + +DeviceHandler * +SmbDeviceHandlerFactory::createHandler( const Medium * m ) const +{ + QString server = m->deviceNode().section( "/", 2, 2 ); + QString share = m->deviceNode().section( "/", 3, 3 ); + QStringList ids = CollectionDB::instance()->query( QString( "SELECT id, label, lastmountpoint " + "FROM devices WHERE type = 'smb' " + "AND servername = '%1' AND sharename = '%2';" ) + .arg( server ) + .arg( share ) ); + if ( ids.size() == 3 ) + { + debug() << "Found existing SMB config for ID " << ids[0] << " , server " << server << " ,share " << share << endl; + CollectionDB::instance()->query( QString( "UPDATE devices SET lastmountpoint = '%2' WHERE " + "id = %1;" ).arg( ids[0] ).arg( m->mountPoint() ) ); + return new SmbDeviceHandler( ids[0].toInt(), server, share, m->mountPoint() ); + } + else + { + int id = CollectionDB::instance()->insert( QString( "INSERT INTO devices" + "( type, servername, sharename, lastmountpoint ) " + "VALUES ( 'smb', '%1', '%2', '%3' );" ) + .arg( server ) + .arg( share ) + .arg( m->mountPoint() ), "devices" ); + if ( id == 0 ) + { + warning() << "Inserting into devices failed for type=smb, server=" << server << ", share=" << share << endl; + return 0; + } + debug() << "Created new SMB device with ID " << id << " , server " << server << " ,share " << share << endl; + return new SmbDeviceHandler( id, server, share, m->mountPoint() ); + } +} + diff --git a/amarok/src/device/smb/smbdevicehandler.h b/amarok/src/device/smb/smbdevicehandler.h new file mode 100644 index 00000000..e3b04cb0 --- /dev/null +++ b/amarok/src/device/smb/smbdevicehandler.h @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2006-2007 Maximilian Kossick + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU 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 SMBDEVICEHANDLER_H +#define SMBDEVICEHANDLER_H + +#include + +class SmbDeviceHandlerFactory : public DeviceHandlerFactory +{ +public: + SmbDeviceHandlerFactory(); + virtual ~SmbDeviceHandlerFactory(); + + virtual bool canHandle( const Medium* m ) const; + + virtual bool canCreateFromMedium() const; + + virtual DeviceHandler* createHandler( const Medium* m ) const; + + virtual bool canCreateFromConfig() const; + + virtual DeviceHandler* createHandler( const KConfig* c ) const; + + virtual QString type() const; +}; + +/** + @author Maximilian Kossick +*/ +class SmbDeviceHandler : public DeviceHandler +{ +public: + SmbDeviceHandler(int deviceId, QString server, QString dir, QString mountPoint ); + + virtual ~SmbDeviceHandler(); + + virtual bool isAvailable() const; + virtual QString type() const; + virtual int getDeviceID( ); + virtual const QString &getDevicePath() const; + virtual void getURL( KURL &absolutePath, const KURL &relativePath ); + virtual void getPlayableURL( KURL &absolutePath, const KURL &relativePath ); + virtual bool deviceIsMedium( const Medium *m ) const; + +private: + + int m_deviceID; + const QString m_mountPoint; + QString m_server; + QString m_dir; + +}; + +#endif diff --git a/amarok/src/deviceconfiguredialog.cpp b/amarok/src/deviceconfiguredialog.cpp new file mode 100644 index 00000000..c9781fe1 --- /dev/null +++ b/amarok/src/deviceconfiguredialog.cpp @@ -0,0 +1,158 @@ +// +// C++ Implementation: deviceconfiguredialog.cpp +// +// Description: +// +// +// Author: Jeff Mitchell , (C) 2006 +// Martin Aumueller , (C) 2005 +// +// Copyright: See COPYING file that comes with this distribution +// +// +#include "amarok.h" +#include "debug.h" +#include "deviceconfiguredialog.h" +#include "hintlineedit.h" +#include "mediabrowser.h" +#include "medium.h" +#include "plugin/pluginconfig.h" +#include "pluginmanager.h" +#include "scriptmanager.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +DeviceConfigureDialog::DeviceConfigureDialog( const Medium &medium ) + : KDialogBase( Amarok::mainWindow(), "deviceconfiguredialog", true, QString("Select Plugin for " + medium.name()), Ok|Cancel, Ok, false ) +{ + m_medium = new Medium( medium ); + kapp->setTopWidget( this ); + setCaption( kapp->makeStdCaption( i18n( "Configure Media Device" ) ) ); + showButtonApply( false ); + + QVBox* vbox = makeVBoxMainWidget(); + vbox->setSpacing( KDialog::spacingHint() ); + + QLabel *connectLabel = 0; + m_connectEdit = 0; + QLabel *disconnectLabel = 0; + m_disconnectEdit = 0; + m_transcodeCheck = 0; + QButtonGroup *transcodeGroup = 0; + m_transcodeAlways = 0; + m_transcodeWhenNecessary = 0; + m_transcodeRemove = 0; + + MediaDevice* device = MediaBrowser::instance()->deviceFromId( m_medium->id() ); + + if( device ) + { + device->loadConfig(); + + // pre-connect/post-disconnect (mount/umount) + connectLabel = new QLabel( vbox ); + connectLabel->setText( i18n( "Pre-&connect command:" ) ); + m_connectEdit = new HintLineEdit( device->m_preconnectcmd, vbox ); + m_connectEdit->setHint( i18n( "Example: mount %d" ) ); + connectLabel->setBuddy( m_connectEdit ); + QToolTip::add( m_connectEdit, i18n( "Set a command to be run before connecting to your device (e.g. a mount command) here.\n%d is replaced by the device node, %m by the mount point.\nEmpty commands are not executed." ) ); + + disconnectLabel = new QLabel( vbox ); + disconnectLabel->setText( i18n( "Post-&disconnect command:" ) ); + m_disconnectEdit = new HintLineEdit( device->m_postdisconnectcmd, vbox ); + disconnectLabel->setBuddy( m_disconnectEdit ); + m_disconnectEdit->setHint( i18n( "Example: eject %d" ) ); + QToolTip::add( m_disconnectEdit, i18n( "Set a command to be run after disconnecting from your device (e.g. an eject command) here.\n%d is replaced by the device node, %m by the mount point.\nEmpty commands are not executed." ) ); + + // transcode + m_transcodeCheck = new QCheckBox( vbox ); + m_transcodeCheck->setText( i18n( "&Transcode before transferring to device" ) ); + m_transcodeCheck->setChecked( device->m_transcode ); + + transcodeGroup = new QVButtonGroup( vbox ); + QString format = "mp3"; + if( !device->supportedFiletypes().isEmpty() ) + format = device->supportedFiletypes().first(); + transcodeGroup->setTitle( i18n( "Transcode to preferred format (%1) for device" ).arg( format ) ); + m_transcodeAlways = new QRadioButton( transcodeGroup ); + m_transcodeAlways->setText( i18n( "Whenever possible" ) ); + m_transcodeAlways->setChecked( device->m_transcodeAlways ); + m_transcodeWhenNecessary = new QRadioButton( transcodeGroup ); + m_transcodeWhenNecessary->setText( i18n( "When necessary" ) ); + m_transcodeWhenNecessary->setChecked( !device->m_transcodeAlways ); + connect( m_transcodeCheck, SIGNAL(toggled( bool )), + transcodeGroup, SLOT(setEnabled( bool )) ); + transcodeGroup->insert( m_transcodeAlways ); + transcodeGroup->insert( m_transcodeWhenNecessary ); + m_transcodeRemove = new QCheckBox( transcodeGroup ); + m_transcodeRemove->setText( i18n( "Remove transcoded files after transfer" ) ); + m_transcodeRemove->setChecked( device->m_transcodeRemove ); + + const ScriptManager *sm = ScriptManager::instance(); + m_transcodeCheck->setEnabled( sm->transcodeScriptRunning() != QString::null ); + transcodeGroup->setEnabled( sm->transcodeScriptRunning() != QString::null && device->m_transcode ); + if( sm->transcodeScriptRunning().isNull() ) + { + QToolTip::add( m_transcodeCheck, i18n( "For this feature, a script of type \"Transcode\" has to be running" ) ); + QToolTip::add( transcodeGroup, i18n( "For this feature, a script of type \"Transcode\" has to be running" ) ); + } + + device->addConfigElements( vbox ); + } + + m_accepted = false; +} + +DeviceConfigureDialog::~DeviceConfigureDialog() +{ + delete m_connectEdit; + delete m_disconnectEdit; + delete m_medium; +} + +void +DeviceConfigureDialog::slotCancel() +{ + KDialogBase::slotCancel( ); +} + +void +DeviceConfigureDialog::slotOk() +{ + m_accepted = true; + MediaDevice* device = MediaBrowser::instance()->deviceFromId( m_medium->id() ); + + if( device ) + { + device->m_preconnectcmd = m_connectEdit->text(); + device->setConfigString( "PreConnectCommand", device->m_preconnectcmd ); + device->m_postdisconnectcmd = m_disconnectEdit->text(); + device->setConfigString( "PostDisconnectCommand", device->m_postdisconnectcmd ); + device->setConfigBool( "Transcode", device->m_transcode ); + device->m_transcode = m_transcodeCheck->isChecked(); + device->setConfigBool( "Transcode", device->m_transcode ); + device->m_transcodeAlways = m_transcodeAlways->isChecked(); + device->setConfigBool( "TranscodeAlways", device->m_transcodeAlways ); + device->m_transcodeRemove = m_transcodeRemove->isChecked(); + device->setConfigBool( "TranscodeRemove", device->m_transcodeRemove ); + device->applyConfig(); + } + + MediaBrowser::instance()->updateButtons(); + MediaBrowser::instance()->updateStats(); + MediaBrowser::instance()->updateDevices(); + + KDialogBase::slotOk(); +} + +#include "deviceconfiguredialog.moc" diff --git a/amarok/src/deviceconfiguredialog.h b/amarok/src/deviceconfiguredialog.h new file mode 100644 index 00000000..920829e9 --- /dev/null +++ b/amarok/src/deviceconfiguredialog.h @@ -0,0 +1,55 @@ +// +// C++ Interface: deviceconfiguredialog.h +// +// Description: +// +// +// Author: Jeff Mitchell , (C) 2006 +// Martin Aumueller , (C) 2005 +// +// Copyright: See COPYING file that comes with this distribution +// +// +#ifndef DEVICECONFIGUREDIALOG_H +#define DEVICECONFIGUREDIALOG_H + +#include "hintlineedit.h" + +#include +#include + +#include + +class MediaBrowser; +class Medium; + +/** + @author Jeff Mitchell +*/ +class DeviceConfigureDialog : public KDialogBase +{ + Q_OBJECT + + public: + DeviceConfigureDialog( const Medium &medium ); + ~DeviceConfigureDialog(); + bool successful() { return m_accepted; }; + + private slots: + void slotOk(); + void slotCancel(); + + private: + bool m_accepted; + Medium* m_medium; + + HintLineEdit *m_connectEdit; + HintLineEdit *m_disconnectEdit; + QCheckBox *m_transcodeCheck; + QRadioButton *m_transcodeAlways; + QRadioButton *m_transcodeWhenNecessary; + QCheckBox *m_transcodeRemove; + +}; + +#endif diff --git a/amarok/src/devicemanager.cpp b/amarok/src/devicemanager.cpp new file mode 100644 index 00000000..e97e1274 --- /dev/null +++ b/amarok/src/devicemanager.cpp @@ -0,0 +1,284 @@ +// +// C++ Implementation: devicemanager +// +// Description: Controls device/medium object handling, providing +// helper functions for other objects +// +// +// Author: Jeff Mitchell , (C) 2006 +// Maximilian Kossick , (C) 2006 +// +// Copyright: See COPYING file that comes with this distribution +// +// + +#include "amarok.h" +#include "amarokconfig.h" +#include "debug.h" +#include "devicemanager.h" +#include "medium.h" +#include "mediumpluginmanager.h" + +#include +#include + +#include +#include +#include +#include + +typedef Medium::List MediumList; +typedef QMap::Iterator MediumIterator; + +DeviceManager* DeviceManager::instance() +{ + static DeviceManager dw; + return &dw; +} + +DeviceManager::DeviceManager() +{ + DEBUG_BLOCK + m_dc = KApplication::dcopClient(); + m_dc->setNotifications(true); + m_valid = false; + + if (!m_dc->isRegistered()) + { + debug() << "DeviceManager: DCOP Client not registered!" << endl; + } + else + { + if (!m_dc->connectDCOPSignal("kded", "mediamanager", "mediumAdded(QString)", "devices", "mediumAdded(QString)", false) || + !m_dc->connectDCOPSignal("kded", "mediamanager", "mediumRemoved(QString)", "devices", "mediumRemoved(QString)", false) || + !m_dc->connectDCOPSignal("kded", "mediamanager", "mediumChanged(QString)", "devices", "mediumChanged(QString)", false)) + { + debug() << "DeviceManager: Could not connect to signal mediumAdded/Removed/Changed!" << endl; + } + else + { + m_valid = true; + //run the DCOP query here because apparently if you don't run KDE as a DM the first call will fail + //...go figure + QByteArray data, replyData; + QCString replyType; + QDataStream arg(data, IO_WriteOnly); + QStringList result; + arg << 5; + if (!m_dc->call("kded", "mediamanager", "fullList()", data, replyType, replyData, false, -1)) + { + debug() << "During DeviceManager init, error during DCOP call" << endl; + } + reconcileMediumMap(); + debug() << "DeviceManager: connectDCOPSignal returned successfully!" << endl; + } + } +} + +DeviceManager::~DeviceManager() +{ + for( MediumIterator it = m_mediumMap.begin(); it != m_mediumMap.end(); it++ ) + delete (*it); +} + +void +DeviceManager::mediumAdded( const QString name ) +{ + DEBUG_BLOCK + if ( !m_valid ) + return; + Medium* addedMedium = getDevice(name); + if ( addedMedium != 0 ) + debug() << "[DeviceManager::mediumAdded] Obtained medium name is " << name << ", id is: " << addedMedium->id() << endl; + else + debug() << "[DeviceManager::mediumAdded] Obtained medium is null; name was " << name << endl; + emit mediumAdded( addedMedium, name ); +} + + +void +DeviceManager::mediumRemoved( const QString name ) +{ + DEBUG_BLOCK + if ( !m_valid ) + return; + Medium* removedMedium = 0; + if ( m_mediumMap.contains(name) ) + removedMedium = m_mediumMap[name]; + if ( removedMedium != 0 ) + debug() << "[DeviceManager::mediumRemoved] Obtained medium name is " << name << ", id is: " << removedMedium->id() << endl; + else + debug() << "[DeviceManager::mediumRemoved] Medium was unknown and is null; name was " << name << endl; + //if you get a null pointer from this signal, it means we did not know about the device + //before it was removed, i.e. the removal was the first event for the device received while amarok + //has been running + //There is no point in calling getDevice, since it will not be in the list anyways + emit mediumRemoved( removedMedium, name ); + if ( m_mediumMap.contains(name) ) + { + delete removedMedium; //If we are to remove it from the map, delete it first + m_mediumMap.remove(name); + } +} + + +void +DeviceManager::mediumChanged( const QString name ) +{ + DEBUG_BLOCK + if ( !m_valid ) + return; + Medium *changedMedium = getDevice(name); + if ( changedMedium != 0 ) + debug() << "[DeviceManager::mediumChanged] Obtained medium name is " << name << ", id is: " << changedMedium->id() << endl; + else + debug() << "[DeviceManager::mediumChanged] Obtained medium is null; name was " << name << endl; + emit mediumChanged( changedMedium, name ); +} + + +/* +BIG FAT WARNING: +Values returned from the below function should not be counted on being unique! +For instance, there may be a Medium object in the QMap that can be accessed through +other functions that has the same data as the Medium object returned, but is a different object. +As you should not be writing to this object, this is okay, however: + +Use the Medium's name or id, not the pointer value, for equality comparison!!! + +This function does rebuild the map every time it is called, however this should be rare enough +that it is not a problem. +*/ +Medium::List +DeviceManager::getDeviceList() +{ + return Medium::createList( getDeviceStringList() ); +} + +QStringList +DeviceManager::getDeviceStringList() +{ + DEBUG_BLOCK + MediumList currMediumList; + + if ( !m_valid ) + { + QStringList blah; + return blah; + } + + //normal kded Medium doesn't have autodetect, so decrease by 1 + int autodetect_insert = Medium::PROPERTIES_COUNT - 1; + + QByteArray data, replyData; + QCString replyType; + QDataStream arg(data, IO_WriteOnly); + QStringList result; + arg << 5; + if (!m_dc->call("kded", "mediamanager", "fullList()", data, replyType, replyData)) + { + debug() << "Error during DCOP call" << endl; + } + else + { + QDataStream reply(replyData, IO_ReadOnly); + while(!reply.atEnd()) + { + reply >> result; + } + QStringList::Iterator it; + for( it = result.begin(); it != result.end(); ++it ) + { + if (autodetect_insert == Medium::PROPERTIES_COUNT - 1) + result.insert(it, QString("true")); + autodetect_insert--; + if (autodetect_insert == -1) + autodetect_insert = Medium::PROPERTIES_COUNT - 1; + } + } + + return result; +} + +Medium* +DeviceManager::getDevice( const QString name ) +{ + DEBUG_BLOCK + if ( !m_valid ) + return 0; + debug() << "DeviceManager: getDevice called with name argument = " << name << endl; + Medium* returnedMedium = 0; + MediumList currMediumList = getDeviceList(); + + for ( Medium::List::iterator it = currMediumList.begin(); it != currMediumList.end(); ++it ) + { + if ( (*it).name() == name ) + { + MediumIterator secIt; + if ( (secIt = m_mediumMap.find( name )) != m_mediumMap.end() ) + { + //Refresh the Medium by reconstructing then copying it over. + returnedMedium = *secIt; + *returnedMedium = Medium( *it ); + } + else + { + //No previous version of this Medium - create it + returnedMedium = new Medium( *it ); + m_mediumMap[ name ] = returnedMedium; + } + break; + } + } + return returnedMedium; +} + +void +DeviceManager::reconcileMediumMap() +{ + DEBUG_BLOCK + if ( !m_valid ) + return; + + MediumList currMediumList = getDeviceList(); + + Medium::List::iterator it; + for ( it = currMediumList.begin(); it != currMediumList.end(); ++it ) + { + MediumIterator locIt; + if ( (locIt = m_mediumMap.find( (*it).name() )) != m_mediumMap.end() ) + { + Medium* mediumHolder = (*locIt); + *mediumHolder = Medium( *it ); + } + else + m_mediumMap[ (*it).name() ] = new Medium(*it); + } + + //Sanity check + if ( currMediumList.size() != m_mediumMap.size() ) + warning() << "Number of devices does not equal expected number" << endl; +} + +QString DeviceManager::convertMediaUrlToDevice( QString url ) +{ + QString device; + if ( url.startsWith( "media:" ) || url.startsWith( "system:" ) ) + { + KURL devicePath( url ); + DCOPRef mediamanager( "kded", "mediamanager" ); + DCOPReply reply = mediamanager.call( "properties(QString)", devicePath.fileName() ); + if ( reply.isValid() ) { + QStringList properties = reply; + device = properties[ 5 ]; + //kdDebug() << "DeviceManager::convertMediaUrlToDevice() munged to: " << device << "\n"; + } else + device = QString(); + } + else + device = url; + + return device; +} + +#include "devicemanager.moc" diff --git a/amarok/src/devicemanager.h b/amarok/src/devicemanager.h new file mode 100644 index 00000000..74470390 --- /dev/null +++ b/amarok/src/devicemanager.h @@ -0,0 +1,79 @@ +// +// C++ Interface: devicemanager +// +// Description: Controls device/medium object handling, providing +// helper functions for other objects +// +// +// Author: Jeff Mitchell , (C) 2006 +// Maximilian Kossick , (C) 2006 +// +// Copyright: See COPYING file that comes with this distribution +// +// + + +#ifndef AMAROK_DEVICE_MANAGER_H +#define AMAROK_DEVICE_MANAGER_H + +#include "medium.h" + +#include + +#include + +typedef QMap MediumMap; + + +//this class provides support for MountPointManager and MediaDeviceManager +//the latter is responsible for handling mediadevices (e.g. ipod) +//unless you have special requirements you should use either MountPointManager or +//MediaDeviceManager instead of this class. +class DeviceManager : public QObject +{ + + Q_OBJECT + public: + DeviceManager(); + ~DeviceManager(); + static DeviceManager *instance(); + + void mediumAdded( const QString name ); + void mediumChanged( const QString name); + void mediumRemoved( const QString name); + + MediumMap getMediumMap() { return m_mediumMap; } + Medium* getDevice( const QString name ); + // reconciles m_mediumMap to whatever kded has in it. + void reconcileMediumMap(); + + bool isValid() { return m_valid; } + + //only use getDeviceList to initialise clients + Medium::List getDeviceList(); + + //public so can be called from DCOP...but don't use this, see the + //warning about getDeviceList() + QStringList getDeviceStringList(); + + // Converts a media://media/hdc URL as provided by the KDE media + // manager on CD insert to /dev/hdc so amarok can play it. + // This method is safe to call with a device path, it returns it + // unchanged. + QString convertMediaUrlToDevice( QString url ); + + signals: + void mediumAdded( const Medium*, QString ); + void mediumChanged( const Medium*, QString ); + void mediumRemoved( const Medium*, QString ); + + private: + + DCOPClient *m_dc; + bool m_valid; + MediumMap m_mediumMap; + +}; + +#endif + diff --git a/amarok/src/directorylist.cpp b/amarok/src/directorylist.cpp new file mode 100644 index 00000000..30ae5bf5 --- /dev/null +++ b/amarok/src/directorylist.cpp @@ -0,0 +1,318 @@ +/*************************************************************************** + directorylist.cpp + ------------------- + begin : Tue Feb 4 2003 + copyright : (C) 2003 Scott Wheeler + : (C) 2004 Max Howell + : (C) 2004 Mark Kretschmann +***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "amarokconfig.h" +#include "directorylist.h" +#include "mountpointmanager.h" + +#include +#include + +#include +#include +#include +#include + + +CollectionSetup* CollectionSetup::s_instance; + + +CollectionSetup::CollectionSetup( QWidget *parent ) + : QVBox( parent, "CollectionSetup" ) +{ + s_instance = this; + + (new QLabel( i18n( + "These folders will be scanned for " + "media to make up your collection:"), this ))->setAlignment( Qt::WordBreak ); + + m_view = new QFixedListView( this ); + m_recursive = new QCheckBox( i18n("&Scan folders recursively"), this ); + m_monitor = new QCheckBox( i18n("&Watch folders for changes"), this ); + + QToolTip::add( m_recursive, i18n( "If selected, Amarok will read all subfolders." ) ); + QToolTip::add( m_monitor, i18n( "If selected, folders will automatically get rescanned when the content is modified, e.g. when a new file was added." ) ); + + // Read config values + //we have to detect if this is the actual first run and not get the collectionFolders in that case + //there won't be any anyway and accessing them creates a Sqlite database, even if the user wants to + //use another database + //bug 131719 131724 + if( !Amarok::config()->readBoolEntry( "First Run", true ) ) + m_dirs = MountPointManager::instance()->collectionFolders(); + + m_recursive->setChecked( AmarokConfig::scanRecursively() ); + m_monitor->setChecked( AmarokConfig::monitorChanges() ); + + m_view->addColumn( QString::null ); + m_view->setRootIsDecorated( true ); + m_view->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Expanding ); + m_view->setResizeMode( QListView::LastColumn ); + + reinterpret_cast(m_view->header())->hide(); + new Collection::Item( m_view ); + + setSpacing( 6 ); +} + + +void +CollectionSetup::writeConfig() +{ + //If we are in recursive mode then we don't need to store the names of the + //subdirectories of the selected directories + if ( recursive() ) + { + for ( QStringList::iterator it=m_dirs.begin(); it!=m_dirs.end(); ++it ) + { + QStringList::iterator jt=m_dirs.begin(); + while ( jt!=m_dirs.end() ) + { + if ( it==jt ) + { + ++jt; + continue; + } + //Note: all directories except "/" lack a trailing '/'. + //If (*jt) is a subdirectory of (*it) it is redundant. + //As all directories are subdirectories of "/", if "/" is selected, we + //can delete everything else. + if ( ( *jt ).startsWith( *it + '/' ) || *it=="/" ) + jt = m_dirs.remove( jt ); + else + ++jt; + } + } + } + + MountPointManager::instance()->setCollectionFolders( m_dirs ); + AmarokConfig::setScanRecursively( recursive() ); + AmarokConfig::setMonitorChanges( monitor() ); +} + + +////////////////////////////////////////////////////////////////////////////////////////// +// CLASS Item +////////////////////////////////////////////////////////////////////////////////////////// + +namespace Collection { + +Item::Item( QListView *parent ) + : QCheckListItem( parent, "/", QCheckListItem::CheckBox ) + , m_lister( true ) + , m_url( "file:/" ) + , m_listed( false ) + , m_fullyDisabled( false ) +{ + //Since we create the "/" checklistitem here, we need to enable it if needed + if ( CollectionSetup::instance()->m_dirs.contains( "/" ) ) + static_cast( this )->setOn(true); + m_lister.setDirOnlyMode( true ); + connect( &m_lister, SIGNAL(newItems( const KFileItemList& )), SLOT(newItems( const KFileItemList& )) ); + setOpen( true ); + setVisible( true ); +} + + +Item::Item( QListViewItem *parent, const KURL &url , bool full_disable /* default=false */ ) + : QCheckListItem( parent, url.fileName(), QCheckListItem::CheckBox ) + , m_lister( true ) + , m_url( url ) + , m_listed( false ) + , m_fullyDisabled( full_disable ) +{ + m_lister.setDirOnlyMode( true ); + setExpandable( true ); + connect( &m_lister, SIGNAL(newItems( const KFileItemList& )), SLOT(newItems( const KFileItemList& )) ); + connect( &m_lister, SIGNAL(completed()), SLOT(completed()) ); + connect( &m_lister, SIGNAL(canceled()), SLOT(completed()) ); +} + + +QString +Item::fullPath() const +{ + QString path; + + for( const QListViewItem *item = this; item != listView()->firstChild(); item = item->parent() ) + { + path.prepend( item->text( 0 ) ); + path.prepend( '/' ); + } + + return path; +} + + +void +Item::setOpen( bool b ) +{ + if ( !m_listed ) + { + m_lister.openURL( m_url, true ); + m_listed = true; + } + + QListViewItem::setOpen( b ); +} + + +void +Item::stateChange( bool b ) +{ + QStringList &cs_m_dirs = CollectionSetup::instance()->m_dirs; + + if ( isFullyDisabled() ) + return; + + if( CollectionSetup::instance()->recursive() ) + for( QListViewItem *item = firstChild(); item; item = item->nextSibling() ) + if ( dynamic_cast( item ) && !dynamic_cast( item )->isFullyDisabled() ) + static_cast(item)->QCheckListItem::setOn( b ); + + //If it is disabled, allow us to change its appearance (above code) but not add it + //to the list of folders (code below) + if ( isDisabled() ) + return; + + // Update folder list + QStringList::Iterator it = cs_m_dirs.find( m_url.path() ); + if ( isOn() ) { + if ( it == cs_m_dirs.end() ) + cs_m_dirs << m_url.path(); + + // Deselect subdirectories if we are in recursive mode as they are redundant + if ( CollectionSetup::instance()->recursive() ) + { + QStringList::Iterator diriter = cs_m_dirs.begin(); + while ( diriter != cs_m_dirs.end() ) + { + // Since the dir "/" starts with '/', we need a hack to stop it removing + // itself (it being the only path with a trailing '/') + if ( (*diriter).startsWith( m_url.path(1) ) && *diriter != "/" ) + diriter = cs_m_dirs.erase(diriter); + else + ++diriter; + } + } + } + else { + //Deselect item and recurse through children but only deselect children if they + //do not exist unless we are in recursive mode (where no children should be + //selected if the parent is being unselected) + //Note this does not do anything to the checkboxes, but they should be doing + //the same thing as we are (hopefully) + //Note: all paths lack a trailing '/' except for "/", which must be handled as a + //special case + if ( it != cs_m_dirs.end() ) + cs_m_dirs.erase( it ); + QStringList::Iterator diriter = cs_m_dirs.begin(); + while ( diriter != cs_m_dirs.end() ) + { + if ( (*diriter).startsWith( m_url.path(1) ) ) //path(1) adds a trailing '/' + { + if ( CollectionSetup::instance()->recursive() || + !QFile::exists( *diriter ) ) + { + diriter = cs_m_dirs.erase(diriter); + } + else + ++diriter; + } + else + ++diriter; + } + } + + // Redraw parent items + listView()->triggerUpdate(); +} + + +void +Item::activate() +{ + if( !isDisabled() ) + QCheckListItem::activate(); +} + + +void +Item::newItems( const KFileItemList &list ) //SLOT +{ + for( KFileItemListIterator it( list ); *it; ++it ) + { + //Fully disable (always appears off and grayed-out) if it is "/proc", "/sys" or + //"/dev" or one of their children. This is because we will never scan them, so we + //might as well show that. + //These match up with the skipped dirs in CollectionScanner::readDir. + bool fully_disable=false; + + if ( this->m_url.fileName().isEmpty() && ( ( *it )->url().fileName()=="proc" + || ( *it )->url().fileName()=="dev" || ( *it )->url().fileName()=="sys" ) ) + { + fully_disable=true; + } + + Item *item = new Item( this, (*it)->url() , fully_disable || this->isFullyDisabled() ); + + if ( !item->isFullyDisabled() ) + { + if( CollectionSetup::instance()->recursive() && isOn() || + CollectionSetup::instance()->m_dirs.contains( item->fullPath() ) ) + { + item->setOn( true ); + } + } + + item->setPixmap( 0, (*it)->pixmap( KIcon::SizeSmall ) ); + } +} + + +void +Item::paintCell( QPainter * p, const QColorGroup & cg, int column, int width, int align ) +{ + bool dirty = false; + QStringList &cs_m_dirs = CollectionSetup::instance()->m_dirs; + + // Figure out if a child folder is activated + for ( QStringList::const_iterator iter = cs_m_dirs.begin(); iter != cs_m_dirs.end(); + ++iter ) + if ( ( *iter ).startsWith( m_url.path(1) ) ) + if ( *iter != "/" ) // "/" should not match as a child of "/" + dirty = true; + + // Use a different color if this folder has an activated child folder + const QFont f = p->font(); + QColorGroup _cg = cg; + if ( dirty ) + { + _cg.setColor( QColorGroup::Text, listView()->colorGroup().link() ); + QFont font = p->font(); + font.setBold( !font.bold() ); + p->setFont( font ); + } + + QCheckListItem::paintCell( p, isDisabled() ? listView()->palette().disabled() : _cg, column, width, align ); + p->setFont( f ); +} + +} //namespace Collection + +#include "directorylist.moc" diff --git a/amarok/src/directorylist.h b/amarok/src/directorylist.h new file mode 100644 index 00000000..d6954d98 --- /dev/null +++ b/amarok/src/directorylist.h @@ -0,0 +1,100 @@ +/*************************************************************************** + directorylist.h + ------------------- + begin : Tue Feb 4 2003 + copyright : (C) 2003 Scott Wheeler + : (C) 2004 Max Howell + : (C) 2004 Mark Kretschmann +***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef AMAROK_DIRECTORYLIST_H +#define AMAROK_DIRECTORYLIST_H + +#include //inlined functions +#include //baseclass +#include //baseclass + +#include //stack allocated +#include //stack allocated + + +namespace Collection { class Item; } + +class QFixedListView : public QListView +// Reimplement sizeHint to have directorylist not being too big for "low" (1024x768 is not exactly low) resolutions +{ +public: + QFixedListView ( QWidget * parent = 0, const char * name = 0, WFlags f = 0 ) + :QListView(parent, name, f) {}; + QSize sizeHint() const + { + return QSize(400, 100); + } + +}; + +class CollectionSetup : public QVBox +{ + friend class Collection::Item; + +public: + static CollectionSetup* instance() { return s_instance; } + + CollectionSetup( QWidget* ); + void writeConfig(); + + QStringList dirs() const { return m_dirs; } + bool recursive() const { return m_recursive->isChecked(); } + bool monitor() const { return m_monitor->isChecked(); } + +private: + static CollectionSetup* s_instance; + + QFixedListView *m_view; + QStringList m_dirs; + QCheckBox *m_recursive; + QCheckBox *m_monitor; +}; + + +namespace Collection { //just to keep it out of the global namespace + +class Item : public QObject, public QCheckListItem +{ +Q_OBJECT +public: + Item( QListView *parent ); + Item( QListViewItem *parent, const KURL &url , bool full_disable=false ); + + QCheckListItem *parent() const { return static_cast( QListViewItem::parent() ); } + bool isFullyDisabled() const { return m_fullyDisabled; } + bool isDisabled() const { return isFullyDisabled() || ( CollectionSetup::instance()->recursive() && parent() && parent()->isOn() ); } + QString fullPath() const; + + void setOpen( bool b ); // reimpl. + void stateChange( bool ); // reimpl. + void activate(); // reimpl. + void paintCell( QPainter * p, const QColorGroup & cg, int column, int width, int align ); // reimpl. + +public slots: + void newItems( const KFileItemList& ); + void completed() { if( childCount() == 0 ) { setExpandable( false ); repaint(); } } + +private: + KDirLister m_lister; + KURL m_url; + bool m_listed; + bool m_fullyDisabled; +}; +} + +#endif diff --git a/amarok/src/dynamicmode.cpp b/amarok/src/dynamicmode.cpp new file mode 100644 index 00000000..e7950e49 --- /dev/null +++ b/amarok/src/dynamicmode.cpp @@ -0,0 +1,399 @@ +/*************************************************************************** + * copyright : (C) 2005 Seb Ruiz * + * copyright : (C) 2006 Gábor Lehel * + * copyright : (C) 2006 Bonne Eggleston // random func + +#include +#include + +///////////////////////////////////////////////////////////////////////////// +/// CLASS DynamicMode +//////////////////////////////////////////////////////////////////////////// + +DynamicMode::DynamicMode( const QString &name ) + : m_title( name ) + , m_cycle( true ) + , m_upcoming( 20 ) + , m_previous( 5 ) + , m_appendType( RANDOM ) +{ +} + +DynamicMode::~DynamicMode() +{} + +void +DynamicMode::deleting() +{ + if( this == Playlist::instance()->dynamicMode() ) + Playlist::instance()->disableDynamicMode(); +} + +void +DynamicMode::edit() +{ + if( this == Playlist::instance()->dynamicMode() ) + Playlist::instance()->editActiveDynamicMode(); //so the changes get noticed + else + ConfigDynamic::editDynamicPlaylist( PlaylistWindow::self(), this ); +} + +QStringList DynamicMode::items() const { return m_items; } + +QString DynamicMode::title() const { return m_title; } +bool DynamicMode::cycleTracks() const { return m_cycle; } +int DynamicMode::upcomingCount() const { return m_upcoming; } +int DynamicMode::previousCount() const { return m_previous; } +int DynamicMode::appendType() const { return m_appendType; } + +void DynamicMode::setItems( const QStringList &list ) { m_items = list; } +void DynamicMode::setCycleTracks( bool e ) { m_cycle = e; } +void DynamicMode::setUpcomingCount( int c ) { m_upcoming = c; } +void DynamicMode::setPreviousCount( int c ) { m_previous = c; } +void DynamicMode::setAppendType( int type ) { m_appendType = type; } +void DynamicMode::setTitle( const QString& title ) { m_title = title; } + +void DynamicMode::setDynamicItems( QPtrList& newList ) +{ +DEBUG_BLOCK + + QStringList strListEntries; + PlaylistBrowserEntry* entry; + QPtrListIterator it( newList ); + + while( (entry = it.current()) != 0 ) + { + ++it; + strListEntries << entry->text(0); + } + + setItems( strListEntries ); + PlaylistBrowser::instance()->saveDynamics(); + + rebuildCachedItemSet(); +} + +void DynamicMode::rebuildCachedItemSet() +{ +DEBUG_BLOCK + + m_cachedItemSet.clear(); + + if( appendType() == RANDOM || appendType() == SUGGESTION ) + { + QueryBuilder qb; + qb.setOptions( QueryBuilder::optRandomize | QueryBuilder::optRemoveDuplicates ); + qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valURL ); + + if( appendType() == SUGGESTION ) + { + // TODO some clever stuff here for spanning across artists + QString artist = EngineController::instance()->bundle().artist(); + + if( artist.isEmpty() ) + { + PlaylistItem *currentItem = Playlist::instance()->currentItem(); + if( currentItem != 0 ) + artist = currentItem->artist(); + } + + debug() << "seeding from: " << artist << endl; + + QStringList suggestions = CollectionDB::instance()->similarArtists( artist, 16 ); + // for this artist, choose 4 suggested artists at random, to get further suggestions from + QStringList newChosen; + for( uint suggestCount = 0; suggestCount < 4; ++suggestCount ) + { + if( suggestions.isEmpty() ) + break; + + QString chosen = suggestions[ KApplication::random() % suggestions.count() ]; + + debug() << "found similar artist: " << chosen << endl; + + QStringList newSuggestions = CollectionDB::instance()->similarArtists( chosen, 10 ); + for( uint c = 0; c < 4; ++c ) // choose another 4 artists + { + if( newSuggestions.isEmpty() ) + break; + + QString s = newSuggestions[ KApplication::random() % newSuggestions.count() ]; + + debug() << "found extended similar artist: " << s << endl; + newChosen += s; + newSuggestions.remove( s ); + } + suggestions.remove( chosen ); + } + if ( !newChosen.isEmpty() ) + suggestions += newChosen; + qb.addMatches( QueryBuilder::tabArtist, suggestions ); + } + + qb.setLimit( 0, CACHE_SIZE ); + debug() << "Using SQL: " << qb.query() << endl; + + QStringList urls = qb.run(); + + foreach( urls ) //we have to run setPath on all raw paths + { + KURL current; + current.setPath( *it ); + m_cachedItemSet += current; + } + } + + else + { + PlaylistBrowser *pb = PlaylistBrowser::instance(); + QPtrList dynamicEntries = pb->dynamicEntries(); + if( !dynamicEntries.count() ) + { + Amarok::StatusBar::instance()->longMessage( i18n( "This dynamic playlist has no sources set." ), + KDE::StatusBar::Sorry ); + return; + } + // Create an array of the sizes of each of the playlists + QValueVector trackCount(dynamicEntries.count()) ; + int trackCountTotal = 0; + + for( uint i=0; i < dynamicEntries.count(); i++ ){ + trackCount[i] = 0; + + if ( QListViewItem *item = dynamicEntries.at( i ) ){ + if( item->rtti() == PlaylistEntry::RTTI ) + trackCount[i] = static_cast(item)->tracksURL().count(); + else if( item->rtti() == SmartPlaylist::RTTI ) + trackCount[i] = static_cast(item)->length(); + + trackCountTotal += trackCount[i]; + } + } + + + PlaylistBrowserEntry* entry; + QPtrListIterator it( dynamicEntries ); + + //const int itemsPerSource = CACHE_SIZE / dynamicEntries.count() != 0 ? CACHE_SIZE / dynamicEntries.count() : 1; + + int i = 0; + while( (entry = it.current()) != 0 ) + { + ++it; + //trackCountTotal might be 0 + int itemsForThisSource = trackCountTotal ? CACHE_SIZE * trackCount[i] / trackCountTotal : 1; + if (itemsForThisSource == 0) + itemsForThisSource = 1; + debug() << "this source will return " << itemsForThisSource << " entries" << endl; + + if( entry->rtti() == PlaylistEntry::RTTI ) + { + KURL::List t = tracksFromStaticPlaylist( static_cast(entry), itemsForThisSource); + m_cachedItemSet += t; + } + + else if( entry->rtti() == SmartPlaylist::RTTI ) + { + KURL::List t = tracksFromSmartPlaylist( static_cast(entry), itemsForThisSource); + m_cachedItemSet += t; + } + i++; + } + } +} + +KURL::List DynamicMode::tracksFromStaticPlaylist( PlaylistEntry *item, uint songCount ) +{ +DEBUG_BLOCK + + KURL::List trackList = item->tracksURL(); + KURL::List returnList; + + for( uint i=0; i < songCount; ) + { + if( trackList.isEmpty() ) + break; + + KURL::List::Iterator urlIt = trackList.at( KApplication::random() % trackList.count() ); + if( (*urlIt).isValid() ) + { + returnList << (*urlIt).path(); + ++i; + } + trackList.remove( urlIt ); + } + + debug() << "Returning " << returnList.count() << " tracks from " << item->text(0) << endl; + + return returnList; +} + +KURL::List DynamicMode::tracksFromSmartPlaylist( SmartPlaylist *item, uint songCount ) +{ +DEBUG_BLOCK + if( !item || !songCount ) + return KURL::List(); + + bool useDirect = true; + const bool hasTimeOrder = item->isTimeOrdered(); + debug() << "The smart playlist: " << item->text(0) << ", time order? " << hasTimeOrder << endl; + + QString sql = item->query(); + + // FIXME: All this SQL magic out of collectiondb is not a good thing + + // if there is no ordering, add random ordering + if ( sql.find( QString("ORDER BY"), false ) == -1 ) + { + QRegExp limit( "(LIMIT.*)?;$" ); + sql.replace( limit, QString(" ORDER BY %1 LIMIT %2 OFFSET 0;") + .arg( CollectionDB::instance()->randomFunc() ) + .arg( songCount ) ); + } + else + { + uint limit=0, offset=0; + + QRegExp limitSearch( "LIMIT.*(\\d+).*OFFSET.*(\\d+)" ); + int findLocation = limitSearch.search( sql, 0 ); + if( findLocation == -1 ) //not found, let's find out the higher limit the hard way + { + QString counting( sql ); + counting.replace( QRegExp( "SELECT.*FROM" ), "SELECT COUNT(*) FROM" ); + // Postgres' grouping rule doesn't like the following clause + counting.replace( QRegExp( "ORDER BY.*" ), "" ); + QStringList countingResult = CollectionDB::instance()->query( counting ); + limit = countingResult[0].toInt(); + } + else + { // There's a Limit, we have to respect it. + // capturedTexts() gives us the strings that were matched by each subexpression + offset = limitSearch.capturedTexts()[2].toInt(); + limit = limitSearch.capturedTexts()[1].toInt(); + } + + // we must be ordering by some other arbitrary query. + // we can scrap it, since it won't affect our result + if( !hasTimeOrder ) + { + // We can mess with the limits if the smart playlist is not orderd by a time criteria + // Why? We can have a smart playlist which is ordered by name or by some other quality which + // is meaningless in dynamic mode + QRegExp orderLimit( "(ORDER BY.*)?;$" ); + + sql.replace( orderLimit, QString(" ORDER BY %1 LIMIT %2 OFFSET 0;") + .arg( CollectionDB::instance()->randomFunc() ) + .arg( songCount ) ); + } + else // time ordered criteria, only mess with the limits + { + debug() << "time based criteria used!" << endl; + if ( limit <= songCount ) // The list is even smaller than the number of songs we want :-( + songCount = limit; + else + // Let's get a random limit, repecting the original one. + offset += KApplication::random() % (limit - songCount); + + if( findLocation == -1 ) // there is no limit + { + QRegExp queryEnd( ";$" ); // find the end of the query an add a limit + sql.replace( queryEnd, QString(" LIMIT %1 OFFSET %2;" ).arg( songCount*5 ).arg( offset ) ); + useDirect = false; + } + else // there is a limit, so find it and replace it + sql.replace( limitSearch, QString(" LIMIT %1 OFFSET %2;" ).arg( songCount ).arg( offset ) ); + } + } + + // only return the fields that we need + sql.replace( QRegExp( "SELECT.*FROM" ), "SELECT tags.url, tags.deviceid FROM" ); + QStringList queryResult = CollectionDB::instance()->query( sql ); + QStringList items; + + debug() << "Smart Playlist: adding urls from query: " << sql << endl; + if ( !item->query().isEmpty() ) + //We have to filter all the un-needed results from query( sql ) + for( uint x=0; x < queryResult.count() ; x += 2 ) + items << MountPointManager::instance()->getAbsolutePath( queryResult[x+1].toInt(), queryResult[x] ); + else + items = queryResult; + + + KURL::List urls; + foreach( items ) //we have to run setPath on all raw paths + { + KURL tmp; + tmp.setPath( *it ); + urls << tmp; + } + KURL::List addMe; + + // we have to randomly select tracks from the returned query since we can't have + // ORDER BY RAND() for some statements + if( !useDirect ) + { + for( uint i=0; i < songCount && urls.count(); i++ ) + { + KURL::List::iterator newItem = urls.at( KApplication::random() % urls.count() ); + addMe << (*newItem); + urls.remove( newItem ); + } + } + + useDirect ? + debug() << "Returning " << urls.count() << " tracks from " << item->text(0) << endl: + debug() << "Returning " << addMe.count() << " tracks from " << item->text(0) << endl; + + return useDirect ? urls : addMe; +} + + +KURL::List DynamicMode::retrieveTracks( const uint trackCount ) +{ +DEBUG_BLOCK + KURL::List retrieval; + + // always rebuild with suggested mode since the artists will be changing + if( m_cachedItemSet.count() <= trackCount || appendType() == SUGGESTION ) + rebuildCachedItemSet(); + + for( uint i=0; i < trackCount; i++ ) + { + if( m_cachedItemSet.isEmpty() ) + break; + const int pos = KApplication::random() % m_cachedItemSet.count(); + KURL::List::iterator newItem = m_cachedItemSet.at( pos ); + if( QFile::exists( (*newItem).path() ) ) + retrieval << (*newItem); + m_cachedItemSet.remove( newItem ); + } + + return retrieval; +} diff --git a/amarok/src/dynamicmode.h b/amarok/src/dynamicmode.h new file mode 100644 index 00000000..8ae8a544 --- /dev/null +++ b/amarok/src/dynamicmode.h @@ -0,0 +1,124 @@ +/*************************************************************************** + * copyright : (C) 2005 Seb Ruiz * + * copyright : (C) 2006 Gábor Lehel * + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +/*************************************************************************** + * Infiltrations of Dynamic Mode * + * Dynamic mode is a complex playlist handling mechanism - acting * + * basically on the concept of a 'rotating' playlist. The playlist can * + * be modelled as a queuing system, FIFO. As a track is advanced, * + * the first track in the playlist is removed, and another appended to * + * the end. The type of addition is selected by the user during * + * configuration. * + * * + * Due to the nature of this type of handling, the status of dynamicmode * + * must be determined, as many function require alternate handling. * + * Examples include: * + * - Context Menus * + * - Double clicking on an item -> requires moving the item to * + * front of the queue * + * - Undo/Redo states, to reinit history items * + * Please be aware of these when working with dynamic mode. * + ***************************************************************************/ + +#ifndef AMAROK_DYNAMIC_H +#define AMAROK_DYNAMIC_H + +#include //KURL::List + +class QString; +class QStringList; +template class QPtrList; +class PlaylistBrowserEntry; +class PlaylistEntry; +class SmartPlaylist; + +class DynamicMode +{ + public: + DynamicMode( const QString &name ); + virtual ~DynamicMode(); + enum Type { RANDOM=0, SUGGESTION=1, CUSTOM=2 }; + + void edit(); + void deleting(); + void setDynamicItems( QPtrList& newList ); + + /** + * Retrieves \p tracks from the cache, \p m_cachedItemSet + */ + KURL::List retrieveTracks( const uint trackCount ); + + /** + * Creates a list of \p CACHE_SIZE urls, stored in \p m_cachedItemSet in order + * to increase efficiency of dynamic mode population. This should be called + * when the dynamic sources are changed, or the cache runs out of items + */ + void rebuildCachedItemSet(); + + QString title() const; + QStringList items() const; + bool cycleTracks() const; + int upcomingCount() const; + int previousCount() const; + int appendType() const; + + void setAppendType( int type ); + void setCycleTracks( bool cycle ); + void setItems( const QStringList &list ); + void setUpcomingCount( int count ); + void setPreviousCount( int count ); + void setTitle( const QString& title ); + + private: + static const int CACHE_SIZE = 200; ///< the number of items to store in the cached set + + /** + * Returns a list of \p songCount urls from \p item - to be stored as part of + * the dynamic element cache, \p m_cachedItemSet + * + * This function will alter the sql statement of the item in order to return an + * adequate subset of data after execution. Limits and ordering attributes + * within the statement will be respected (to a certain extent). + */ + KURL::List tracksFromSmartPlaylist( SmartPlaylist *item, uint songCount ); + + /** + * Returns a list of \p songCount urls from \p item - to be stored as part of + * the dynamic element cache, \p m_cachedItemSet + * + * This function will return a random selection of elements from within the + * playlist given, in order to give some diversity when rebuilding the cache. + */ + KURL::List tracksFromStaticPlaylist( PlaylistEntry *item, uint songCount ); + + /** + * A list of urls which satisfy at least one of the dynamic mode sources. As tracks + * are added to the playlist, they are removed from the cache. When the cache expires, + * it should be rebuilt. + * + * The cache is used to reduce the number of database queries required when adding and + * removing tracks from the playlist when using dynamic mode. + */ + KURL::List m_cachedItemSet; + + QStringList m_items; + + QString m_title; + bool m_cycle; + int m_upcoming; + int m_previous; + int m_appendType; +}; + +#endif //AMAROK_DYNAMIC_H diff --git a/amarok/src/editfilterdialog.cpp b/amarok/src/editfilterdialog.cpp new file mode 100644 index 00000000..75764317 --- /dev/null +++ b/amarok/src/editfilterdialog.cpp @@ -0,0 +1,791 @@ +// (c) 2006 Giovanni Venturi +// See COPYING file for licensing information. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "amarokcore/amarokconfig.h" +#include "collectiondb.h" +#include "debug.h" +#include "editfilterdialog.h" +#include "metabundle.h" + +EditFilterDialog::EditFilterDialog( QWidget* parent, bool metaBundleKeywords, const QString &text ) + : KDialogBase( Plain, i18n("Edit Filter"), User1|User2|Default|Ok|Cancel, + Cancel, parent, "editfilter", /*modal*/true, /*separator*/false ), + m_minMaxRadio(0), + m_filterText(text) +{ + // Redefine "Default" button + KGuiItem defaultButton( i18n("&Append"), "add" ); + setButtonWhatsThis( Default, i18n( "

    By clicking here you can add the defined condition. The \"OK\" button will " + "close the dialog and apply the defined filter. With this button you can add more than " + "one condition to create a more complex filtering condition.

    " ) ); + setButtonTip(Default, i18n( "Add this filter condition to the list" ) ); + setButtonGuiItem( Default, defaultButton ); + + // define "User1" button + KGuiItem user1Button( i18n("&Clear"), "remove" ); + setButtonWhatsThis( User1, i18n( "

    By clicking here you will clear the filter. If you intend to " + "undo the last appending just click on the \"Undo\" button.

    " ) ); + setButtonTip(User1, i18n( "Clear the filter" ) ); + setButtonGuiItem( User1, user1Button ); + + // define "User2" button + KGuiItem user2Button( i18n("this \"undo\" will undo the last appended filter... be careful how you will translate it " + "to avoid two buttons (\"Cancel\" and \"Undo\") with same label in the same dialog", "&Undo"), "undo" ); + setButtonWhatsThis( User2, i18n( "

    Clicking here will remove the last appended filter. " + "You cannot undo more than one action.

    " ) ); + setButtonTip(User2, i18n( "Remove last appended filter" ) ); + setButtonGuiItem( User2, user2Button ); + + m_mainLay = new QVBoxLayout( plainPage() ); + m_mainLay->activate(); + + // no filter rule available + m_appended = false; + + // text explanation of this dialog + QLabel *label1 = new QLabel( plainPage(), "label1" ); + label1->setText( i18n("

    Edit the filter for finding tracks with specific attributes" + ", e.g. you can look for a track that has a length of three minutes.

    ") ); + m_mainLay->addWidget( label1 ); + m_mainLay->addItem( new QSpacerItem( 10, 10, QSizePolicy::Expanding, QSizePolicy::Minimum ) ); + + // choosing keyword filtering + QHBoxLayout *keywordLayout = new QHBoxLayout( plainPage() ); + QLabel *label3 = new QLabel( i18n("Attribute:"), plainPage(), "label3" ); + QWhatsThis::add( label3, + i18n("you can translate the keyword as you will do for the combobox", + "

    Here you can choose to Simple Search directly or to use " + "some keywords to specify some attributes, such as the artist name " + "and so on. The keywords selectable are divided by their specific value. " + "Some keywords are numeric and others are alphanumeric. You do not need " + "to know it directly. When a keyword is numeric it will be used to search " + "the numeric data for each track.

    The alphanumeric " + "keywords are the following: album, artist, filename " + " (including path), mountpoint (e.g. /home/user1), filetype " + " (you can specify mp3, ogg, flac, ... and the file extensions will be matched), " + "genre, comment, composer, directory, lyrics, " + "title, and label.

    " + "

    The numeric keywords are: bitrate, disc/discnumber, " + "length (expressed in seconds), playcount, rating, " + "samplerate, score, size/filesize (expressed in bytes, " + "kbytes, and megabytes as specified in the unit for the filesize keyword), " + "track (i.e. the track number), and year.

    ") ); + keywordLayout->addWidget( label3 ); + keywordLayout->addItem( new QSpacerItem( 5, 10, QSizePolicy::Minimum, QSizePolicy::Minimum ) ); + m_comboKeyword = new QComboBox( plainPage(), "keywordComboBox"); + QToolTip::add( m_comboKeyword, i18n("Select an attribute for the filter") ); + label3->setBuddy( m_comboKeyword ); + + m_comboKeyword->insertItem( i18n("Simple Search") ); + m_vector.push_back("Simple Search"); + if( metaBundleKeywords ) + { + for( int i=0; i < MetaBundle::NUM_COLUMNS; ++i ) + { + if( i == MetaBundle::Mood ) + continue; + if( !AmarokConfig::useRatings() && i == MetaBundle::Rating ) + continue; + if( !AmarokConfig::useScores() && i == MetaBundle::Score ) + continue; + + m_comboKeyword->insertItem( MetaBundle::prettyColumnName( i ) ); + m_vector.push_back( MetaBundle::exactColumnName( i ).lower() ); + } + } + else + { + m_comboKeyword->insertItem( i18n("Album") ); + m_vector.push_back( "album" ); + m_comboKeyword->insertItem( i18n("Artist") ); + m_vector.push_back( "artist" ); + m_comboKeyword->insertItem( i18n("Bitrate") ); + m_vector.push_back( "bitrate" ); + m_comboKeyword->insertItem( i18n("BPM") ); + m_vector.push_back( "bpm" ); + m_comboKeyword->insertItem( i18n("Comment") ); + m_vector.push_back( "comment" ); + m_comboKeyword->insertItem( i18n("Composer") ); + m_vector.push_back( "composer" ); + m_comboKeyword->insertItem( i18n("Directory") ); + m_vector.push_back( "directory" ); + m_comboKeyword->insertItem( i18n("Disc Number") ); + m_vector.push_back( "disc" ); + m_comboKeyword->insertItem( i18n("Filename") ); + m_vector.push_back( "filename" ); + m_comboKeyword->insertItem( i18n("Mount Point") ); + m_vector.push_back( "mountpoint" ); + m_comboKeyword->insertItem( i18n("Filetype") ); + m_vector.push_back( "filetype" ); + m_comboKeyword->insertItem( i18n("Genre") ); + m_vector.push_back( "genre" ); + m_comboKeyword->insertItem( i18n("Length") ); + m_vector.push_back( "length" ); + m_comboKeyword->insertItem( i18n("Label") ); + m_vector.push_back( "label" ); + m_comboKeyword->insertItem( i18n("Lyrics") ); + m_vector.push_back( "lyrics" ); + m_comboKeyword->insertItem( i18n("Play Count") ); + m_vector.push_back( "playcount" ); + if( AmarokConfig::useRatings() ) + { + m_comboKeyword->insertItem( i18n("Rating") ); + m_vector.push_back( "rating" ); + } + m_comboKeyword->insertItem( i18n("Sample Rate") ); + m_vector.push_back( "samplerate" ); + if( AmarokConfig::useScores() ) + { + m_comboKeyword->insertItem( i18n("Score") ); + m_vector.push_back( "score" ); + } + m_comboKeyword->insertItem( i18n("File Size") ); + m_vector.push_back( "size" ); + m_comboKeyword->insertItem( i18n("Title") ); + m_vector.push_back( "title" ); + m_comboKeyword->insertItem( i18n("Track") ); + m_vector.push_back( "track" ); + m_comboKeyword->insertItem( i18n("Year") ); + m_vector.push_back( "year" ); + } + + // the "Simple Search" text is selected in the comboKeyword + m_selectedIndex = 0; + + keywordLayout->addWidget( m_comboKeyword ); + keywordLayout->addItem( new QSpacerItem( 5, 10, QSizePolicy::Minimum, QSizePolicy::Minimum ) ); + m_editKeyword = new KLineEdit( plainPage(), "editKeywordBox" ); + QWhatsThis::add( m_editKeyword, i18n("

    Type the attribute value or the text to look for here.

    ") ); + keywordLayout->addWidget( m_editKeyword ); + m_mainLay->addLayout( keywordLayout ); + m_mainLay->addItem( new QSpacerItem( 10, 10, QSizePolicy::Expanding, QSizePolicy::Minimum ) ); + connect(m_comboKeyword, SIGNAL(activated(int)), this, SLOT(selectedKeyword(int))); + + // group of options on numeric attribute keywords: a value <,>,= ... or a value between Min and Max + m_groupBox = new QGroupBox( plainPage(), "groupBox" ); + m_groupBox->setTitle( i18n( "Attribute value is" ) ); + m_mainLay->addWidget( m_groupBox ); + m_mainLay->addItem( new QSpacerItem( 10, 10, QSizePolicy::Minimum, QSizePolicy::Minimum ) ); + + QVBoxLayout *vertLayout = new QVBoxLayout( m_groupBox, 15, 5 ); + + // choose other keyword parameters: smaller than, greater than, equal to... + QHBoxLayout *paramLayout = new QHBoxLayout( vertLayout ); + + m_comboCondition = new QComboBox( m_groupBox, "valuecondition"); + m_comboCondition->insertItem( i18n("smaller than") ); + m_comboCondition->insertItem( i18n("larger than") ); + m_comboCondition->insertItem( i18n("equal to") ); + m_comboCondition->insertItem( i18n("between") ); + paramLayout->addWidget( m_comboCondition ); + paramLayout->addItem( new QSpacerItem( 5, 10, QSizePolicy::Fixed, QSizePolicy::Minimum ) ); + + m_spinMin1 = new QSpinBox( m_groupBox, "minimum1" ); + paramLayout->addWidget( m_spinMin1 ); + paramLayout->addItem( new QSpacerItem( 5, 10, QSizePolicy::Minimum, QSizePolicy::Minimum ) ); + + m_spinMin2 = new QSpinBox( m_groupBox, "minimum2" ); + paramLayout->addWidget( m_spinMin2 ); + paramLayout->addItem( new QSpacerItem( 5, 10, QSizePolicy::Minimum, QSizePolicy::Minimum ) ); + + connect(m_spinMin1, SIGNAL(valueChanged(int)), this, SLOT(minSpinChanged(int))); + + m_andLabel = new QLabel( i18n("and"), m_groupBox, "andLabel"); + paramLayout->addWidget( m_andLabel ); + paramLayout->addItem( new QSpacerItem( 5, 10, QSizePolicy::Minimum, QSizePolicy::Minimum ) ); + + m_spinMax1 = new QSpinBox( m_groupBox, "maximum1" ); + paramLayout->addWidget( m_spinMax1 ); + paramLayout->addItem( new QSpacerItem( 5, 10, QSizePolicy::Minimum, QSizePolicy::Minimum ) ); + + m_spinMax2 = new QSpinBox( m_groupBox, "maximum2" ); + paramLayout->addWidget( m_spinMax2 ); + + connect(m_spinMax1, SIGNAL(valueChanged(int)), this, SLOT(maxSpinChanged(int))); + + QHBoxLayout *filesizeLayout = new QHBoxLayout( vertLayout ); + filesizeLayout->setAlignment( AlignLeft ); + m_filesizeLabel = new QLabel( i18n("Unit:"), m_groupBox, "filesizeLabel"); + filesizeLayout->addWidget( m_filesizeLabel ); + filesizeLayout->addItem( new QSpacerItem( 5, 10, QSizePolicy::Fixed, QSizePolicy::Minimum ) ); + m_comboUnitSize = new QComboBox( m_groupBox, "comboUnitSize" ); + m_filesizeLabel->setBuddy( m_comboUnitSize ); + m_comboUnitSize->insertItem( i18n("B (1 Byte)") ); + m_comboUnitSize->insertItem( i18n("KB (1024 Bytes)") ); + m_comboUnitSize->insertItem( i18n("MB (1024 KB)") ); + filesizeLayout->addWidget( m_comboUnitSize ); + + // type text selected + textWanted(); + + // check the "One Value Choosing" by default + chooseOneValue(); + + connect( m_comboCondition, SIGNAL(activated(int)), SLOT(chooseCondition(int)) ); + + QHBoxLayout *otherOptionsLayout = new QHBoxLayout( plainPage() ); + otherOptionsLayout->setAlignment( AlignHCenter ); + m_mainLay->addLayout( otherOptionsLayout ); + + // the groupbox to select the action filter + m_groupBox2 = new QGroupBox( plainPage(), "groupBox2" ); + m_groupBox2->setTitle( i18n( "Filter action" ) ); + otherOptionsLayout->addWidget( m_groupBox2 ); + + QVBoxLayout* ratioLay = new QVBoxLayout( m_groupBox2, 15, 0 ); + + m_checkALL = new QRadioButton( i18n("Match all words"), m_groupBox2, "checkall" ); + QToolTip::add( m_checkALL, + i18n("

    Check this box to look for the tracks that contain all the words you typed " + "in the related Simple Search edit box

    ")); + ratioLay->addWidget( m_checkALL ); + + m_checkAtLeastOne = new QRadioButton( i18n("Match any word"), m_groupBox2, "checkor"); + QToolTip::add( m_checkAtLeastOne, + i18n("

    Check this box to look for the tracks that contain at least one of the words " + "you typed in the related Simple Search edit box

    ")); + ratioLay->addWidget( m_checkAtLeastOne ); + + m_checkExactly = new QRadioButton( i18n("Exact match"), m_groupBox2, "checkexactly"); + QToolTip::add( m_checkExactly, + i18n("

    Check this box to look for all the tracks that contain exactly the words you typed " + "in the related Simple Search edit box

    ")); + ratioLay->addWidget( m_checkExactly ); + + m_checkExclude = new QRadioButton( i18n("Exclude"), m_groupBox2, "checkexclude"); + QToolTip::add( m_checkExclude, + i18n("

    Check this box to look for all the tracks that do not contain the words you typed " + "in the related Simple Search edit box

    ")); + ratioLay->addWidget( m_checkExclude ); + + m_actionCheck << m_checkALL; + m_actionCheck << m_checkAtLeastOne; + m_actionCheck << m_checkExactly; + m_actionCheck << m_checkExclude; + + connect( m_checkALL, SIGNAL(clicked()), this, SLOT(slotCheckAll()) ); + connect( m_checkAtLeastOne, SIGNAL(clicked()), this, SLOT(slotCheckAtLeastOne()) ); + connect( m_checkExactly, SIGNAL(clicked()), this, SLOT(slotCheckExactly()) ); + connect( m_checkExclude, SIGNAL(clicked()), this, SLOT(slotCheckExclude()) ); + + // check "select all words" as default + slotCheckAll(); + + // some vertical space + otherOptionsLayout->addItem( new QSpacerItem( 50, 5, QSizePolicy::Minimum, QSizePolicy::Minimum ) ); + + QVBoxLayout* verticalCondLay = new QVBoxLayout( otherOptionsLayout, 15, 0 ); + + m_groupBox3 = new QGroupBox( plainPage(), "groupBox3" ); + m_groupBox3->setTitle( i18n( "Appending condition" ) ); + verticalCondLay->addWidget( m_groupBox3 ); + + QVBoxLayout* ratioLay2 = new QVBoxLayout( m_groupBox3, 15, 0 ); + + m_checkAND = new QRadioButton( i18n("AND logic condition", "AND"), m_groupBox3, "checkAND" ); + QToolTip::add( m_checkAND, + i18n("

    Check this box if you want to add another condition and you want that the filter " + "to match both the previous conditions and this new one

    ")); + ratioLay2->addWidget( m_checkAND ); + + m_checkOR = new QRadioButton( i18n("OR logic condition", "OR"), m_groupBox3, "checkOR" ); + QToolTip::add( m_checkOR, + i18n("

    Check this box if you want to add another condition and you want that the filter " + "to match either the previous conditions or this new one

    ")); + ratioLay2->addWidget( m_checkOR ); + + otherOptionsLayout->addItem( new QSpacerItem( 10, 10, QSizePolicy::Minimum, QSizePolicy::Minimum ) ); + + m_prefixNOT = new QCheckBox( i18n("Invert condition"), plainPage(), "prefixNOT" ); + QToolTip::add( m_prefixNOT, + i18n("Check this box to negate the defined filter condition")); + QWhatsThis::add( m_prefixNOT, + i18n("

    If this option is checked the defined filter condition will be negated. " + "This means that, for example, you can define a filter that looks for all " + "tracks that are not of a specific album, artist, and so on.

    ")); + verticalCondLay->addWidget( m_prefixNOT ); + m_prefixNOT->setEnabled( false ); + + connect(m_prefixNOT, SIGNAL(clicked()), SLOT(assignPrefixNOT())); + + m_mainLay->addItem( new QSpacerItem( 10, 20, QSizePolicy::Minimum, QSizePolicy::Minimum ) ); + + // you need to append at least one filter condition to specify if do + // an "AND" or an "OR" with the next condition if the filter is empty + if (m_filterText.isEmpty()) + m_groupBox3->setEnabled( false ); + + connect( m_checkAND, SIGNAL(clicked()), SLOT(slotCheckAND()) ); + connect( m_checkOR, SIGNAL(clicked()), SLOT(slotCheckOR()) ); + + // check "AND" condition as default + slotCheckAND(); + + // setup Min Max Value spin + setMinMaxValueSpins(); +} + +EditFilterDialog::~EditFilterDialog() +{ + delete m_editKeyword; +} + +QString EditFilterDialog::filter() const +{ + return m_filterText; +} + +void EditFilterDialog::exclusiveSelectOf( int which ) +{ + int size = static_cast( m_actionCheck.count() ); + + for ( int i = 0; i < size; i++ ) + if ( i != which ) + m_actionCheck[i]->setChecked( false ); + else + m_actionCheck[i]->setChecked( true ); +} + +QString EditFilterDialog::keywordConditionString(const QString& keyword) const +{ + // this member is called when there is a keyword that needs numeric attributes + QString result, unit; + + if (m_vector.at(m_selectedIndex) == "size") + switch (m_comboUnitSize->currentItem()) + { + case 1: + // kbytes + unit = "k"; + break; + case 2: + // mbytes + unit = "m"; + break; + } + + switch(m_comboCondition->currentItem()) + { + case 0: + // less than... + result = m_strPrefixNOT + keyword + ":<"; + if (keyword == "length") + result += QString::number( m_spinMin1->value() * 60 + m_spinMin2->value() ) + unit; + else + result += m_spinMin1->text() + unit; + break; + case 1: + // greater than... + result = m_strPrefixNOT + keyword + ":>"; + if (keyword == "length") + result += QString::number( m_spinMin1->value() * 60 + m_spinMin2->value() ) + unit; + else + result += m_spinMin1->text() + unit; + break; + case 2: + // equal to... + if (keyword == "length") + result = m_strPrefixNOT + "length:" + QString::number( m_spinMin1->value() * 60 + + m_spinMin2->value() ) + unit; + else + { + if (m_strPrefixNOT.isEmpty()) + result = keyword + ":>" + QString::number(m_spinMin1->value() - 1) + unit + + " " + keyword + ":<" + QString::number(m_spinMin1->value() + 1) + unit; + else + result = keyword + ":<" + QString::number(m_spinMin1->value()) + unit + + " OR " + keyword + ":>" + QString::number(m_spinMin1->value()) + unit; + } + break; + case 3: + // between... + if (keyword == "length") + { + if (m_strPrefixNOT.isEmpty()) + result = "length:>" + QString::number( m_spinMin1->value() * 60 + m_spinMin2->value() - 1) + unit + + " length:<" + QString::number( m_spinMax1->value() * 60 + m_spinMax2->value() + 1) + unit; + else + result = "length:<" + QString::number( m_spinMin1->value() * 60 + m_spinMin2->value()) + unit + + " OR length:>" + QString::number( m_spinMax1->value() * 60 + m_spinMax2->value()) + unit; + } + else + { + if (m_strPrefixNOT.isEmpty()) + result = keyword + ":>" + QString::number(m_spinMin1->value() - 1) + unit + + " " + keyword + ":<" + QString::number(m_spinMax1->value() + 1) + unit; + else + result = keyword + ":<" + QString::number(m_spinMin1->value() - 1) + unit + + " OR " + keyword + ":>" + QString::number(m_spinMax1->value() + 1) + unit; + } + break; + } + + return result; +} + +void EditFilterDialog::setMinMaxValueSpins() +{ + // setting some spin box options and limit values + m_spinMin1->setValue( 0 ); + m_spinMin1->setMinValue( 0 ); + m_spinMin1->setMaxValue( 100000000 ); + + m_spinMin2->setMinValue( 0 ); + m_spinMin2->setMaxValue( 59 ); + m_spinMin2->hide(); + + m_spinMax1->setValue( 0 ); + m_spinMax1->setMinValue( 0 ); + m_spinMax1->setMaxValue( 100000000 ); + + m_spinMax2->setMinValue( 0 ); + m_spinMax2->setMaxValue( 59 ); + m_spinMax2->hide(); + + // fix tooltip + QToolTip::add( m_spinMin1, "" ); + QToolTip::add( m_spinMin2, i18n("Seconds") ); + + QToolTip::add( m_spinMax1, "" ); + QToolTip::add( m_spinMax2, i18n("Seconds") ); +} + +// SLOTS +void EditFilterDialog::selectedKeyword(int index) // SLOT +{ + debug() << "you selected index " << index << ": '" << m_comboKeyword->text(index) << "'" << endl; + m_groupBox2->setEnabled( false ); + m_comboUnitSize->setEnabled( false ); + m_filesizeLabel->setEnabled( false ); + m_prefixNOT->setEnabled( true ); + + setMinMaxValueSpins(); + + const QString key = m_vector[index]; + if( index == 0 ) + { + // Simple Search + m_groupBox2->setEnabled( true ); + m_prefixNOT->setEnabled( false ); + textWanted(); + } + else if( key=="bitrate" ) + { + // bitrate: set useful values for the spinboxes + m_spinMin1->setValue( 128 ); + m_spinMax1->setValue( 384 ); + valueWanted(); + } + else if( key=="samplerate" ) + { + // samplerate: set useful values for the spinboxes + m_spinMin1->setValue( 8000 ); + m_spinMax1->setValue( 48000 ); + valueWanted(); + } + else if( key=="length" ) + { + // length: set useful values for the spinboxes + m_spinMin2->show(); + m_spinMax2->show(); + m_spinMin1->setValue( 1 ); + m_spinMax1->setValue( 5 ); + QToolTip::add( m_spinMin1, i18n("Minutes") ); + QToolTip::add( m_spinMax1, i18n("Minutes") ); + + // fix the maximum values to reduce spinboxes size + m_spinMin1->setMaxValue( 240 ); + m_spinMax1->setMaxValue( 240 ); + + valueWanted(); + } + else if( key=="size" || key=="filesize" ) + { + // size: set useful values for the spinboxes + m_filesizeLabel->setEnabled( true ); + m_comboUnitSize->setEnabled( true ); + m_spinMin1->setValue( 1 ); + m_spinMax1->setValue( 3 ); + m_comboUnitSize->setCurrentItem( 2 ); + valueWanted(); + } + else if( key=="year" ) + { + // year: set useful values for the spinboxes + m_spinMin1->setValue( 1900 ); + m_spinMax1->setValue( QDate::currentDate().year() ); + valueWanted(); + } + else if( key=="track" || key=="disc" || key=="discnumber" ) + { + // track/disc: set useful values for the spinboxes + m_spinMin1->setValue( 1 ); + m_spinMax1->setValue( 15 ); + valueWanted(); + } + else if( key=="playcount" + || key=="lastplayed" + || key=="rating" + || key=="score" + || key=="bpm" ) + { + valueWanted(); + } + else if( key=="label" ) + textWanted( CollectionDB::instance()->labelList() ); + else if( key=="album" ) + textWanted( CollectionDB::instance()->albumList() ); + else if( key=="artist" ) + textWanted( CollectionDB::instance()->artistList() ); + else if( key=="composer" ) + textWanted( CollectionDB::instance()->composerList() ); + else if( key=="genre" ) + textWanted( CollectionDB::instance()->genreList() ); + else if( key=="type" || key=="filetype" ) + { + QStringList types; + types << "mp3" << "flac" << "ogg" << "aac" << "m4a" << "mp4" << "mp2" << "ac3" + << "wav" << "asf" << "wma"; + textWanted( types ); + } + else + textWanted(); + + // assign the correct value to the m_strPrefixNOT + assignPrefixNOT(); + + // assign the right index + m_selectedIndex = index; +} + +void EditFilterDialog::minSpinChanged(int value) // SLOT +{ + if (value > m_spinMax1->value()) + m_spinMax1->setValue(value); +} + +void EditFilterDialog::maxSpinChanged(int value) // SLOT +{ + if (m_spinMin1->value() > value) + m_spinMin1->setValue(value); +} + +void EditFilterDialog::textWanted() // SLOT +{ + m_editKeyword->setEnabled( true ); + m_groupBox->setEnabled( false ); + + m_editKeyword->completionObject()->clear(); +} + +void EditFilterDialog::textWanted( const QStringList &completion ) // SLOT +{ + m_editKeyword->setEnabled( true ); + m_groupBox->setEnabled( false ); + + m_editKeyword->completionObject()->clear(); + m_editKeyword->completionObject()->insertItems( completion ); + m_editKeyword->completionObject()->setIgnoreCase( true ); + m_editKeyword->setCompletionMode( KGlobalSettings::CompletionPopup ); +} + +void EditFilterDialog::valueWanted() // SLOT +{ + m_editKeyword->setEnabled( false ); + m_groupBox->setEnabled( true ); +} + +void EditFilterDialog::chooseCondition( int condition ) // SLOT +{ + if( condition == 3 ) // included between + chooseMinMaxValue(); + else + chooseOneValue(); +} + +void EditFilterDialog::chooseOneValue() // SLOT +{ + m_andLabel->setEnabled( false); + m_spinMax1->setEnabled( false ); + m_spinMax2->setEnabled( false ); +} + +void EditFilterDialog::chooseMinMaxValue() // SLOT +{ + m_andLabel->setEnabled( true ); + m_spinMax1->setEnabled( true ); + m_spinMax2->setEnabled( true ); +} + +void EditFilterDialog::slotCheckAll() // SLOT +{ + exclusiveSelectOf( 0 ); +} + +void EditFilterDialog::slotCheckAtLeastOne() // SLOT +{ + exclusiveSelectOf( 1 ); +} + +void EditFilterDialog::slotCheckExactly() // SLOT +{ + exclusiveSelectOf( 2 ); +} + +void EditFilterDialog::slotCheckExclude() // SLOT +{ + exclusiveSelectOf( 3 ); +} + +void EditFilterDialog::slotCheckAND() // SLOT +{ + m_checkAND->setChecked( true ); + m_checkOR->setChecked( false ); +} + +void EditFilterDialog::slotCheckOR() // SLOT +{ + m_checkAND->setChecked( false ); + m_checkOR->setChecked( true ); +} + +void EditFilterDialog::assignPrefixNOT() // SLOT +{ + if (m_prefixNOT->isChecked()) + m_strPrefixNOT = "-"; + else + m_strPrefixNOT = ""; +} + +void EditFilterDialog::slotDefault() // SLOT +{ + // now append the filter rule if not empty + if (m_editKeyword->text().isEmpty() && (m_selectedIndex == 0)) + { + KMessageBox::sorry( 0, i18n("

    Sorry but the filter rule cannot be set. The text field is empty. " + "Please type something into it and retry.

    "), i18n("Empty Text Field")); + m_editKeyword->setFocus(); + return; + } + if (!m_appended) + { + // it's the first rule + m_appended = true; + m_groupBox3->setEnabled( true ); + } + + m_previousFilterText = m_filterText; + if (!m_filterText.isEmpty()) + { + m_filterText += " "; + if (m_checkOR->isChecked()) + m_filterText += "OR "; + } + QStringList list = QStringList::split( " ", m_editKeyword->text() ); + const QString key = m_vector[m_selectedIndex]; + if( m_selectedIndex == 0 ) + { + // Simple Search + debug() << "selected text: '" << m_editKeyword->text() << "'" << endl; + if (m_actionCheck[0]->isChecked()) + { + // all words + m_filterText += m_editKeyword->text(); + } + else if (m_actionCheck[1]->isChecked()) + { + // at least one word + m_filterText += *(list.begin()); + for ( QStringList::Iterator it = ++list.begin(); it != list.end(); ++it ) + m_filterText += " OR " + *it; + } + else if (m_actionCheck[2]->isChecked()) + { + // exactly the words + m_filterText += "\"" + m_editKeyword->text() + "\""; + } + else if (m_actionCheck[3]->isChecked()) + { + // exclude words + for ( QStringList::Iterator it = list.begin(); it != list.end(); ++it ) + m_filterText += " -" + *it; + } + } + else if( key=="bitrate" + || key=="disc" || key=="discnumber" + || key=="length" + || key=="playcount" + || key=="rating" + || key=="samplerate" + || key=="score" + || key=="filesize" || key=="size" + || key=="track" + || key=="year" ) + { + m_filterText += keywordConditionString( m_vector[m_selectedIndex] ); + } + else + { + m_filterText += m_vector[m_selectedIndex] + ":\"" + m_editKeyword->text() + "\""; + } + emit filterChanged( m_filterText ); + + m_editKeyword->clear(); +} + +void EditFilterDialog::slotUser1() // SLOT +{ + m_previousFilterText = m_filterText; + m_filterText = ""; + + // no filter appended cause all cleared + m_appended = false; + m_groupBox3->setEnabled( false ); + + emit filterChanged( m_filterText ); +} + +void EditFilterDialog::slotUser2() // SLOT +{ + m_filterText = m_previousFilterText; + if (m_filterText.isEmpty()) + { + // no filter appended cause all cleared + m_appended = false; + m_groupBox3->setEnabled( false ); + } + emit filterChanged( m_filterText ); +} + +void EditFilterDialog::slotOk() // SLOT +{ + // If there's a filter typed in but unadded, add it. + // This makes it easier to just add one condition - you only need to press OK. + if ( !m_editKeyword->text().isEmpty() ) + slotDefault(); + + // Don't let OK do anything if they haven't set any filters. + if (m_appended) + accept(); +} + +#include "editfilterdialog.moc" diff --git a/amarok/src/editfilterdialog.h b/amarok/src/editfilterdialog.h new file mode 100644 index 00000000..c6a0fabd --- /dev/null +++ b/amarok/src/editfilterdialog.h @@ -0,0 +1,107 @@ +// (c) 2006 Giovanni Venturi +// See COPYING file for licensing information. + +#ifndef AMAROK_EDITFILTERDIALOG_H +#define AMAROK_EDITFILTERDIALOG_H + +#include +#include + +#include + +class QWidget; +class QVBoxLayout; +class QComboBox; +class QCheckBox; +class QLineEdit; +class QRadioButton; +class QGroupBox; +class QSpinBox; +class QStringList; +class KComboBox; + +class EditFilterDialog : public KDialogBase +{ + Q_OBJECT + public: + EditFilterDialog( QWidget* parent, bool metaBundleKeywords, const QString &text = "" ); + ~EditFilterDialog(); + + QString filter() const; + + signals: + void filterChanged( const QString &filter ); + + private: + QVBoxLayout *m_mainLay; + + QCheckBox *m_prefixNOT; + QComboBox *m_comboKeyword; + KLineEdit *m_editKeyword; + + QGroupBox *m_groupBox; + + QComboBox *m_comboCondition; + QLabel *m_filesizeLabel; + QComboBox *m_comboUnitSize; + + QRadioButton *m_minMaxRadio; + QSpinBox *m_spinMin1, *m_spinMin2; + QLabel *m_andLabel; + QSpinBox *m_spinMax1, *m_spinMax2; + + QGroupBox *m_groupBox2; + QRadioButton *m_checkALL; + QRadioButton *m_checkAtLeastOne; + QRadioButton *m_checkExactly; + QRadioButton *m_checkExclude; + QValueList m_actionCheck; + + QGroupBox *m_groupBox3; + QRadioButton *m_checkAND; + QRadioButton *m_checkOR; + + bool m_appended; // true if a filter appended + int m_selectedIndex; // the position of the selected keyword in the combobox + QValueVector m_vector; // the vector of the amarok filter keyword + QString m_filterText; // the resulting filter string + QString m_previousFilterText; // the previous resulting filter string + QString m_strPrefixNOT; // is empty if no NOT prefix is needed else it's "-" + + private: + void exclusiveSelectOf( int which ); + QString keywordConditionString(const QString& keyword) const; + void setMinMaxValueSpins(); + + private slots: + void selectedKeyword(int index); + + void minSpinChanged(int value); + void maxSpinChanged(int value); + + void textWanted(); + void textWanted( const QStringList &completions ); + void valueWanted(); + + void chooseCondition(int index); + void chooseOneValue(); + void chooseMinMaxValue(); + + void slotCheckAll(); + void slotCheckAtLeastOne(); + void slotCheckExactly(); + void slotCheckExclude(); + + void slotCheckAND(); + void slotCheckOR(); + + void assignPrefixNOT(); + + protected slots: + virtual void slotDefault(); + virtual void slotUser1(); + virtual void slotUser2(); + virtual void slotOk(); +}; + +#endif /* AMAROK_EDITFILTERDIALOG_H */ diff --git a/amarok/src/engine/ENGINE_TODO b/amarok/src/engine/ENGINE_TODO new file mode 100644 index 00000000..0755bf28 --- /dev/null +++ b/amarok/src/engine/ENGINE_TODO @@ -0,0 +1,13 @@ +TODO for the next engine framework: + + * emit stateChanged() with no parameter and force controller to read the state from + EngineBase::state() instead, as this makes things more likely to be consistent + * combine load() and play() as it just complicates things for engine development (eg + crossfade is a bitch for me due to the separation + * setVolumeSW( double ), to give more resolution for setting volume + * Make EngineObserver system use engineStateChanged( oldState, newState ) + * Don't do above function for Play on every track? Is this ever useful? + * Would be useful if engineNewMetaData and stateChanged for Play weren't separate? + * setVolumeSW uses a log function, volume() doesn't so you get different values back to + what you expect. Really we should provide a volumeToLog( int ) function for engines to + use internally as some engines may already apply a log function anyway. diff --git a/amarok/src/engine/Makefile.am b/amarok/src/engine/Makefile.am new file mode 100644 index 00000000..926d54ae --- /dev/null +++ b/amarok/src/engine/Makefile.am @@ -0,0 +1,27 @@ +#if with_gst10 +# GST10_ENGINE_SUBDIR = gst10 +#endif + +if with_nmm + NMM_ENGINE_SUBDIR = nmm +endif + +if with_xine + XINE_ENGINE_SUBDIR = xine +endif + +if with_helix + HELIX_ENGINE_SUBDIR = helix +endif + +if with_yauap + YAUAP_ENGINE_SUBDIR = yauap +endif + +SUBDIRS = . \ + void \ + $(XINE_ENGINE_SUBDIR) \ + $(NMM_ENGINE_SUBDIR) \ + $(HELIX_ENGINE_SUBDIR) \ + $(YAUAP_ENGINE_SUBDIR) + diff --git a/amarok/src/engine/akode/Makefile.am b/amarok/src/engine/akode/Makefile.am new file mode 100644 index 00000000..8414cd64 --- /dev/null +++ b/amarok/src/engine/akode/Makefile.am @@ -0,0 +1,15 @@ +kde_module_LTLIBRARIES = libamarok_aKode-engine.la +kde_services_DATA = amarok_aKode-engine.desktop + +INCLUDES = -I$(top_srcdir)/amarok/src $(CFLAGS_AKODE) $(all_includes) + +libamarok_aKode_engine_la_LIBADD = \ + $(top_builddir)/amarok/src/libamarok.la \ + $(top_builddir)/amarok/src/plugin/libplugin.la \ + -lkdecore \ + $(LIBS_AKODE) + +libamarok_aKode_engine_la_SOURCES = akode-engine.cpp akode-scope.cpp +libamarok_aKode_engine_la_LDFLAGS = -module $(KDE_PLUGIN) $(all_libraries) + +METASOURCES = AUTO diff --git a/amarok/src/engine/akode/akode-engine.cpp b/amarok/src/engine/akode/akode-engine.cpp new file mode 100644 index 00000000..429f0172 --- /dev/null +++ b/amarok/src/engine/akode/akode-engine.cpp @@ -0,0 +1,196 @@ +/*************************************************************************** + * Copyright (C) 2005 Max Howell * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include +#include +#include +#include +#include + +AMAROK_EXPORT_PLUGIN( AkodeEngine ) + + +namespace Amarok +{ + class Manager : public aKode::Player::Manager + { + AkodeEngine *m_engine; + + /// Called for all stateChanges + virtual void stateChangeEvent( aKode::Player::State ) + { + QApplication::postEvent( m_engine, new QCustomEvent( 3000 ) ); + } + + /// Called when a decoder reaches end of file + virtual void eofEvent() + { + QApplication::postEvent( m_engine, new QCustomEvent( 3001 ) ); + } + + /// Called when a decoder encounters a fatal error + virtual void errorEvent() + { + QApplication::postEvent( m_engine, new QCustomEvent( 3002 ) ); + } + + public: + Manager( AkodeEngine *engine ) : m_engine( engine ) {} + }; +} + + +AkodeEngine::AkodeEngine() + : m_player( 0 ) +{} + +AkodeEngine::~AkodeEngine() +{ + if( m_player ) + m_player->close(); +} + +bool +AkodeEngine::init() +{ +// startTimer( 20 ); + + m_player = new aKode::Player(); + m_player->setManager( new Amarok::Manager( this ) ); + m_player->setMonitor( &m_scope ); + + return m_player->open( "auto" ); +} + +bool +AkodeEngine::load( const KURL &url, bool isStream ) +{ + Engine::Base::load( url, isStream ); + + return m_player->load( url.path().local8Bit().data() ); +} + +bool +AkodeEngine::play( uint /*offset*/ ) +{ + //FIXME this seemed to crash Amarok + //m_player->decoder()->seek( offset ); + m_player->play(); + + return true; +} + +void +AkodeEngine::unpause() +{ + m_player->play(); +} + +bool +AkodeEngine::canDecode( const KURL &url ) const +{ + const QString ext = url.path().right( 4 ).lower(); + + return ext == ".mp3" || ext == ".ogg" || ext == ".wav" || ext ==".mpc" || ext == "flac"; +} + +uint +AkodeEngine::position() const +{ + if( !m_player->decoder() ) + return 0; + + const int pos = m_player->decoder()->position(); + + return pos >= 0 ? pos : 0; +} + +void +AkodeEngine::stop() +{ + m_player->stop(); + m_player->unload(); +} + +void +AkodeEngine::pause() +{ + switch( m_player->state() ) { + case aKode::Player::Playing: m_player->pause(); break; + case aKode::Player::Paused: m_player->play(); break; + default: ; + } +} + +void +AkodeEngine::setVolumeSW( uint v ) +{ + m_player->setVolume( (float)v / 100.0 ); +} + +void +AkodeEngine::seek( uint ms ) +{ + m_player->decoder()->seek( ms ); +} + +Engine::State +AkodeEngine::state() const +{ + switch( m_player->state() ) + { + case aKode::Player::Open: + case aKode::Player::Closed: return Engine::Empty; + default: + case aKode::Player::Loaded: return Engine::Idle; + case aKode::Player::Playing: return Engine::Playing; + case aKode::Player::Paused: return Engine::Paused; + } +} + +bool +AkodeEngine::event( QEvent *e ) +{ + switch( e->type() ) + { + /* + case QEvent::Timer: + if( m_player->decoder() && m_player->decoder()->eof() ) { + m_player->stop(); + emit trackEnded(); + } + break; + */ + case 3000: + emit stateChanged( state() ); + break; + + case 3001: + m_player->stop(); + emit trackEnded(); + break; + + case 3002: + m_player->stop(); + emit trackEnded(); + emit infoMessage( i18n("Unable to decode %1").arg( m_url.prettyURL()) ); + break; + + default: + return false; + } + + return true; +} + +const Engine::Scope& AkodeEngine::scope() +{ + return m_scope.scope(); +} diff --git a/amarok/src/engine/akode/akode-engine.h b/amarok/src/engine/akode/akode-engine.h new file mode 100644 index 00000000..a5fc41be --- /dev/null +++ b/amarok/src/engine/akode/akode-engine.h @@ -0,0 +1,42 @@ +/*************************************************************************** + * Copyright (C) 2005 Max Howell * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "enginebase.h" +#include "akode-scope.h" + +namespace aKode { class Player; } + +class AkodeEngine : public Engine::Base +{ + virtual bool init(); + virtual bool canDecode( const KURL& ) const; + virtual uint position() const; + virtual bool load( const KURL&, bool ); + virtual bool play( uint ); + virtual void stop(); + virtual void pause(); + virtual void unpause(); + virtual void setVolumeSW( uint ); + virtual void seek( uint ); + + virtual Engine::State state() const; + virtual const Engine::Scope &scope(); + + virtual bool event( QEvent* ); + + aKode::Player *m_player; + aKodeScope m_scope; + +protected: + ~AkodeEngine(); + +public: + AkodeEngine(); +}; diff --git a/amarok/src/engine/akode/akode-scope.cpp b/amarok/src/engine/akode/akode-scope.cpp new file mode 100644 index 00000000..41b80891 --- /dev/null +++ b/amarok/src/engine/akode/akode-scope.cpp @@ -0,0 +1,91 @@ +/* aKode: aKodeScope + + Copyright (C) 2005 Allan Sandfeld Jensen + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Steet, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include + +#include +#include +#include "akode-scope.h" + +#include +//#include +using aKode::AudioFrame; + +// Copies fromFrame to toFrame fast (Actually a swap happens to save malloc and frees). +static inline void takeover(AudioFrame* toFrame, AudioFrame* fromFrame) +{ + AudioFrame tmpFrame; + + tmpFrame = *toFrame; + *toFrame = *fromFrame; + *fromFrame = tmpFrame; + tmpFrame.data = 0; +} + +struct aKodeScope::private_data +{ + private_data() : length(512), convert(16) {}; + int length; + aKode::AudioFrame frame; + aKode::AudioFrame frame1; + aKode::Converter convert; + std::vector scope; +}; + +aKodeScope::aKodeScope() +{ + d = new private_data; +} + +aKodeScope::~aKodeScope() +{ + delete d; +} + +void aKodeScope::writeFrame(AudioFrame* frame) +{ + takeover(&d->frame, frame); +} +/* +void aKodeScope::setLength(length) +{ + d->length = length; +} + +int aKodeScope::length() const +{ + return d->length; +}*/ + +const Engine::Scope& aKodeScope::scope() +{ + d->convert.doFrame(&d->frame, &d->frame1); + + int length = d->frame1.length; + int channels = d->frame1.channels; + if (length > 512) length = 512; + d->scope.resize(length*channels); + int16_t **data = (int16_t**)d->frame1.data; + for(int j=0 ; j < length; j++) + for (int i = 0; i < channels; i++) + d->scope[j*channels + i] = data[i][j]; + + return d->scope; +} diff --git a/amarok/src/engine/akode/akode-scope.h b/amarok/src/engine/akode/akode-scope.h new file mode 100644 index 00000000..940af5c7 --- /dev/null +++ b/amarok/src/engine/akode/akode-scope.h @@ -0,0 +1,45 @@ +/* aKode: scope + + Copyright (C) 2005 Allan Sandfeld Jensen + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Steet, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef _AKODE_SCOPE_H +#define _AKODE_SCOPE_H + +#include +#include "enginebase.h" + +namespace aKode { +class AudioFrame; +} + +class aKodeScope : public aKode::Player::Monitor +{ +public: + aKodeScope(); + ~aKodeScope(); + + void writeFrame(aKode::AudioFrame *frame); + + const Engine::Scope& scope(); + struct private_data; +private: + private_data *d; +}; + +#endif diff --git a/amarok/src/engine/akode/amarok_aKode-engine.desktop b/amarok/src/engine/akode/amarok_aKode-engine.desktop new file mode 100644 index 00000000..8c87826f --- /dev/null +++ b/amarok/src/engine/akode/amarok_aKode-engine.desktop @@ -0,0 +1,120 @@ +[Desktop Entry] +Encoding=UTF-8 +Type=Service +Name=aKode Engine +Name[af]=aKode Enjin +Name[ar]=محرك aKode +Name[bg]=aKode +Name[bn]=অ্যাকোড ইঞ্জিন +Name[br]=Keflusker aKode +Name[ca]=Motor aKode +Name[cs]=aKode +Name[da]=aKode-motor +Name[de]=aKode +Name[el]=Μηχανή aKode +Name[eo]=aKode Ilo +Name[es]=Motor aKode +Name[et]=aKode mootor +Name[fa]=موتور aKode +Name[fi]=aKode +Name[fr]=Moteur aKode +Name[ga]=Inneall aKode +Name[gl]=Motor aKode +Name[he]=מנוע שמע aKode +Name[hu]=aKode alrendszer +Name[is]=aKode vél +Name[it]=Motore aKode +Name[ja]=aKode エンジン +Name[ka]=aKode ძრავა +Name[km]=ម៉ាស៊ីន aKode +Name[lt]=aKode variklis +Name[mk]=aKode-машина +Name[ms]=Enjin aKode +Name[nb]=aKode-motor +Name[nds]=aKode +Name[ne]=एकोड इन्जिन +Name[nl]=aKode-engine +Name[nn]=aKode-motor +Name[pa]=aKode ਇੰਜਣ +Name[pl]=Wyjście aKode +Name[pt]=Motor aKode +Name[pt_BR]=Mecanismo aKode +Name[ru]=aKode +Name[se]=aKode-mohtor +Name[sl]=Pogon aKode +Name[sq]=Motor aKode +Name[sr]=Мотор aKode +Name[sr@Latn]=Motor aKode +Name[ss]=Motor aKode +Name[sv]=aKode-gränssnitt +Name[ta]=aKode என்ஜின் +Name[tg]=Муҳаррики aKode +Name[tr]=aKode Motoru +Name[uk]=Рушій aKode +Name[uz]=aKode tizimi +Name[uz@cyrillic]=aKode тизими +Name[wa]=Éndjin aKode +Name[zh_CN]=aKode 引擎 +Name[zh_TW]=aKode 解碼引擎 +ServiceTypes=Amarok/Plugin +X-KDE-Library=libamarok_aKode-engine +Comment=aKode audio-engine for Amarok +Comment[af]=aKode oudio enjin vir Amarok +Comment[ar]=محرك صوتي aKode ل- AmaroK +Comment[bg]=Аудио система aKode за Amarok +Comment[bn]=আমারক-এর জন্য অ্যাকোড অডিও ইঞ্জিন +Comment[br]=Keflusker klevet aKode evit Amarok +Comment[ca]=Motor de so aKode per l'Amarok +Comment[cs]=Zvukový modul aKode pro Amarok +Comment[da]=aKode lyd-motor for Amarok +Comment[de]=aKode-Ausgabe-Modul für Amarok +Comment[el]=Μηχανή ήχου aKode για το AmaroK +Comment[eo]=aKode aŭdmotoro por Amarok +Comment[es]=Motor de audio aKode para Amarok +Comment[et]=Amaroki aKode audiomootor +Comment[fa]=موتور صوتی aKode برای Amarok +Comment[fi]=aKode-toistojärjestelmä Amarok-liitännäinen +Comment[fr]=Moteur audio aKode pour Amarok +Comment[ga]=Inneall fuaime aKode le haghaidh AmaroK +Comment[gl]=Motor de áudio aKode para Amarok +Comment[hu]=aKode hang-alrendszer az Amarokhoz +Comment[is]=aKode hljóðvél fyrir Amarok +Comment[it]=motore audio aKode per Amarok +Comment[ja]=Amarok のための aKode オーディオエンジン +Comment[ka]=aKode აუდიო ძრავი Amarok-ისთვის +Comment[km]=ម៉ាស៊ីន​អូឌីយ៉ូ aKode សម្រាប់ Amarok +Comment[lt]=Amarok skirtas aKode audio variklis +Comment[mk]=aKode аудио-машина за Амарок +Comment[ms]=Enjin-audio aKode untuk Amarok +Comment[nb]=aKode lydmotor for Amarok +Comment[nds]=aKode-Klangmaschien för Amarok +Comment[ne]=अमारोकका लागि एकोड अडियो इन्जिन +Comment[nl]=aKode audio-engine voor Amarok +Comment[nn]=aKode lydmotor for Amarok +Comment[pa]=ਅਮਰੋਕ ਲਈ aKode ਆਡੀਓ-ਇੰਜਣ +Comment[pl]=Wtyczka wyjścia audio aKode dla Amaroka +Comment[pt]=Motor de áudio do aKode para o Amarok +Comment[pt_BR]=Mecanismo de áudio aKode para o Amarok +Comment[ru]=Модуль вывода aKode для amaroK +Comment[se]=aKode jietnamohtor Amarok:ii +Comment[sk]=aKode audio-engine pre Amarok +Comment[sr]=Аудио мотор aKode за Amarok +Comment[sr@Latn]=Audio motor aKode za Amarok +Comment[sv]=aKode ljudgränssnitt för Amarok +Comment[th]=โปรแกรมประมวลผลเสียง aKode สำหรับ Amarok +Comment[tr]=Amarok için aKode ses-motoru eklentisi +Comment[uk]=Аудіо-рушій aKode для Amarok +Comment[uz]=Amarok uchun aKode audio-tizimi +Comment[uz@cyrillic]=Amarok учун aKode аудио-тизими +Comment[wa]=Éndjin d' son aKode po-z Amarok +Comment[zh_CN]=Amarok 的 aKode 音频引擎 +Comment[zh_TW]=AmaroK 的 aKode 解碼引擎 + +X-KDE-Amarok-plugintype=engine +X-KDE-Amarok-name=akode-engine +X-KDE-Amarok-authors=Allan Sandfeld, Max Howell +X-KDE-Amarok-email=kde@carewolf.com +X-KDE-Amarok-rank=0 +X-KDE-Amarok-version=1 +X-KDE-Amarok-framework-version=32 + diff --git a/amarok/src/engine/helix/COPYING b/amarok/src/engine/helix/COPYING new file mode 100644 index 00000000..0fc8a215 --- /dev/null +++ b/amarok/src/engine/helix/COPYING @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 51 Franklin Steet, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) 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 +this service 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 make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. 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. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute 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 and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +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 +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the 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 a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, 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. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE 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. + + 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 +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Steet, Fifth Floor, Boston, MA 02110-1301 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision 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, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This 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 Library General +Public License instead of this License. diff --git a/amarok/src/engine/helix/Makefile.am b/amarok/src/engine/helix/Makefile.am new file mode 100644 index 00000000..f48927b9 --- /dev/null +++ b/amarok/src/engine/helix/Makefile.am @@ -0,0 +1,44 @@ +kde_module_LTLIBRARIES = \ + libamarok_helixengine_plugin.la + +SUBDIRS = \ + helix-sp \ + config + +INCLUDES = \ + -I$(top_srcdir)/amarok/src \ + -I$(top_builddir)/amarok/src/amarokcore \ + $(all_includes) + +AM_CPPFLAGS = \ + -D_UNIX \ + -Wall -Wreturn-type \ + -fno-exceptions \ + -I$(top_srcdir)/amarok/src/engine/helix/helix-sp \ + -I$(top_srcdir)/amarok/src/engine/helix/config \ + -I$(top_srcdir)/amarok/src/ \ + -include $(top_srcdir)/amarok/src/engine/helix/helix-sp/helixdefines.h \ + $(all_includes) + +libamarok_helixengine_plugin_la_SOURCES = \ + helix-engine.cpp \ + helix-errors.cpp \ + helix-configdialog.cpp \ + hxplayercontrol.cpp + +libamarok_helixengine_plugin_la_LIBADD = \ + $(top_builddir)/amarok/src/libamarok.la \ + $(top_builddir)/amarok/src/engine/helix/helix-sp/libhelix-sp.la \ + $(top_builddir)/amarok/src/engine/helix/config/libhelixconfig.la \ + $(top_builddir)/amarok/src/plugin/libplugin.la \ + -lkdeui -lkdecore $(ALSALIB_LIBS) + +libamarok_helixengine_plugin_la_LDFLAGS = -module $(KDE_PLUGIN) $(all_libraries) + +METASOURCES = \ + AUTO + +kde_services_DATA = \ + amarok_helixengine_plugin.desktop + +KDE_OPTIONS=nofinal diff --git a/amarok/src/engine/helix/Makefile.test b/amarok/src/engine/helix/Makefile.test new file mode 100644 index 00000000..0fc64129 --- /dev/null +++ b/amarok/src/engine/helix/Makefile.test @@ -0,0 +1,119 @@ +DEFINES=-DTEST_APP -D_REENTRANT_ -DQT_THREAD_SUPPORT + +CXX=g++ +MOC=/usr/lib/qt-3.3/bin/moc + +CXXFLAGS=-g -fpic -pipe -Wall -Wreturn-type -fno-exceptions --permissive -fno-rtti -Wno-ctor-dtor-privacy -march=pentium -mcpu=pentium -O2 $(INCLUDES) $(DEFINES) + +SRCS=main.cpp hxsplay.cpp +LIBSRCS=helix-engine.cpp helix-config.cpp hxsplay.cpp +MOCSRCS=helix-engine.moc helix-config.moc +MOCHDRS=$(MOCSRCS:.moc=.h) + +OBJS=$(SRCS:.cpp=.o) +LIBOBJS=$(LIBSRCS:.cpp=.o) + +INCLUDES=\ + -I/usr/lib/qt-3.3/include \ + -I/usr/include/kde \ + -I./helix-sp \ + -I../.. \ + -I. \ + -I/usr/X11R6/include \ + -Ihelix-sp/helix-include/runtime \ + -Ihelix-sp/helix-include/common/include \ + -Ihelix-sp/helix-include/client/include \ + -Ihelix-sp/helix-include/common/container \ + -Ihelix-sp/helix-include/common/system \ + -Ihelix-sp/helix-include/common/dbgtool \ + -Ihelix-sp/helix-include/common/util + +LIBRARIES=-Lhelix-sp -lhelix-sp --lstdc++ -ldl -lm -L/usr/lib/qt-3.3/lib -lqt-mt + +.SUFFIXES: .cpp .so .h .moc + +.c.o: + $(CC) $(CCFLAGS) -o $@ -c $< + +.cpp.o: + $(CXX) $(CXXFLAGS) -o $@ -c $< + +.h.moc: + $(MOC) $< -o $@ + + +PROGRAM=splay +SLIBRARY=libamarok_helixengine_plugin.so + +all: $(MOCSRCS) $(SLIBRARY) $(OBJS) $(LIBOBJS) $(PROGRAM) + +$(PROGRAM): $(OBJS) + $(CXX) -o $(PROGRAM) $(OBJS) $(LIBRARIES) + +$(SLIBRARY): $(LIBOBJS) + $(CXX) -shared -o $(SLIBRARY) $(LIBOBJS) -L/usr/X11R6/lib $(LIBRARIES) + +$(OBJS): $(SRCS) + +$(MOCSRCS): $(MOCHDRS) + +install: + install -p -C -m 755 $(SLIBRARY) /usr/lib/kde3/$(SLIBRARY) + install -p -C -m 755 $(SLIBRARY:.so=.la) /usr/lib/kde3/$(SLIBRARY:.so=.la) + install -p -C -m 644 amarok_helixengine_plugin.desktop /usr/share/services/amarok_helixengine_plugin.desktop +# kbuildsycoca + +uninstall: + rm -f /usr/lib/kde3/$(SLIBRARY:.so=.la) + rm -f /usr/lib/kde3/$(SLIBRARY) + rm -f /usr/share/services/amarok_helixengine_plugin.desktop + +clean: + rm -f $(PROGRAM) $(SLIBRARY) $(LIBOBJS) $(OBJS) helix-engine.moc *~ + +depend: + makedepend $(DEFINES) $(INCLUDES) -o.o -- $(SRCS) + +# DO NOT DELETE THIS LINE -- make depend depends on it + +main.o: /usr/include/dlfcn.h /usr/include/features.h /usr/include/sys/cdefs.h +main.o: /usr/include/gnu/stubs.h +main.o: /usr/lib/gcc/i386-redhat-linux/3.4.3/include/stddef.h +main.o: /usr/include/bits/dlfcn.h hxsplay.h /usr/lib/qt-3.3/include/qthread.h +main.o: /usr/lib/qt-3.3/include/qwindowdefs.h +main.o: /usr/lib/qt-3.3/include/qobjectdefs.h +main.o: /usr/lib/qt-3.3/include/qglobal.h /usr/lib/qt-3.3/include/qstring.h +main.o: /usr/lib/qt-3.3/include/qcstring.h +main.o: /usr/lib/qt-3.3/include/qmemarray.h /usr/lib/qt-3.3/include/qgarray.h +main.o: /usr/lib/qt-3.3/include/qshared.h +main.o: /usr/lib/qt-3.3/include/qwinexport.h /usr/include/string.h +main.o: /usr/include/limits.h +main.o: /usr/lib/gcc/i386-redhat-linux/3.4.3/include/limits.h +main.o: /usr/lib/qt-3.3/include/qnamespace.h /usr/lib/qt-3.3/include/qmutex.h +main.o: /usr/lib/qt-3.3/include/qsemaphore.h +main.o: /usr/lib/qt-3.3/include/qwaitcondition.h +hxsplay.o: /usr/include/dlfcn.h /usr/include/features.h +hxsplay.o: /usr/include/sys/cdefs.h /usr/include/gnu/stubs.h +hxsplay.o: /usr/lib/gcc/i386-redhat-linux/3.4.3/include/stddef.h +hxsplay.o: /usr/include/bits/dlfcn.h /usr/include/sys/param.h +hxsplay.o: /usr/include/limits.h +hxsplay.o: /usr/lib/gcc/i386-redhat-linux/3.4.3/include/limits.h +hxsplay.o: /usr/include/linux/limits.h /usr/include/linux/param.h +hxsplay.o: /usr/include/asm/param.h /usr/include/unistd.h +hxsplay.o: /usr/include/bits/posix_opt.h /usr/include/bits/types.h +hxsplay.o: /usr/include/bits/wordsize.h /usr/include/bits/typesizes.h +hxsplay.o: /usr/include/bits/confname.h /usr/include/sys/types.h +hxsplay.o: /usr/include/time.h /usr/lib/qt-3.3/include/qthread.h +hxsplay.o: /usr/lib/qt-3.3/include/qwindowdefs.h +hxsplay.o: /usr/lib/qt-3.3/include/qobjectdefs.h +hxsplay.o: /usr/lib/qt-3.3/include/qglobal.h +hxsplay.o: /usr/lib/qt-3.3/include/qstring.h +hxsplay.o: /usr/lib/qt-3.3/include/qcstring.h +hxsplay.o: /usr/lib/qt-3.3/include/qmemarray.h +hxsplay.o: /usr/lib/qt-3.3/include/qgarray.h +hxsplay.o: /usr/lib/qt-3.3/include/qshared.h +hxsplay.o: /usr/lib/qt-3.3/include/qwinexport.h /usr/include/string.h +hxsplay.o: /usr/lib/qt-3.3/include/qnamespace.h +hxsplay.o: /usr/lib/qt-3.3/include/qmutex.h +hxsplay.o: /usr/lib/qt-3.3/include/qsemaphore.h +hxsplay.o: /usr/lib/qt-3.3/include/qwaitcondition.h hxsplay.h hxsplay.moc diff --git a/amarok/src/engine/helix/TODO b/amarok/src/engine/helix/TODO new file mode 100644 index 00000000..c940a4e0 --- /dev/null +++ b/amarok/src/engine/helix/TODO @@ -0,0 +1,3 @@ +- fix timesync with my alsa device implementation, so scope looks better when using it +- support esd as someone actually asked for it on the channel +- fade on stop diff --git a/amarok/src/engine/helix/amarok_helixengine_plugin.desktop b/amarok/src/engine/helix/amarok_helixengine_plugin.desktop new file mode 100644 index 00000000..705749b0 --- /dev/null +++ b/amarok/src/engine/helix/amarok_helixengine_plugin.desktop @@ -0,0 +1,118 @@ +[Desktop Entry] +Encoding=UTF-8 +Type=Service +Name=Helix Engine +Name[af]=Helix Enjin +Name[ar]=محرك Helix +Name[bg]=Helix +Name[bn]=হেলিক্স ইঞ্জিন +Name[br]=Keflusker Helix +Name[ca]=Motor Helix +Name[da]=Helix motor +Name[de]=Helix +Name[el]=Μηχανή Helix +Name[eo]=Helix Ilo +Name[es]=Motor Helix +Name[et]=Helixi mootor +Name[eu]=Helix motorea +Name[fa]=موتور Helix +Name[fi]=Helix +Name[fr]=Moteur Helix +Name[ga]=Inneall Helix +Name[gl]=Motor Helix +Name[he]=מנוע שמע Helix +Name[hu]=Helix alrendszer +Name[is]=Helix vél +Name[it]=Motore Helix +Name[ja]=Helix エンジン +Name[ka]=Helix dრავა +Name[km]=ម៉ាស៊ីន Helix +Name[lt]=Helix variklis +Name[mk]=Helix-машина +Name[ms]=Enjin Helix +Name[nb]=Helix-motor +Name[nds]=Helix +Name[ne]=हेलिक्स इन्जिन +Name[nl]=Helix-engine +Name[nn]=Helix-motor +Name[pa]=ਹੀਲਿਕਸ ਇੰਜਣ +Name[pl]=Wyjście Helix +Name[pt]=Motor Helix +Name[pt_BR]=Mecanismo Helix +Name[ru]=Helix +Name[se]=Helix-mohtor +Name[sq]=Motor Helix +Name[sr]=Мотор Helix +Name[sr@Latn]=Motor Helix +Name[ss]=Motor Helix +Name[sv]=Helix-gränssnitt +Name[tg]=Муҳаррики Helix +Name[tr]=Helix Motoru +Name[uk]=Рушій Helix +Name[uz]=Helix tizimi +Name[uz@cyrillic]=Helix тизими +Name[wa]=Éndjin Helix +Name[zh_CN]=Helix 引擎 +Name[zh_TW]=Helix 解碼引擎 +X-KDE-Library=libamarok_helixengine_plugin +Comment=Plugin for Amarok +Comment[af]=Inprop module vir Amarok +Comment[ar]= قابس ( برنامج مضاف الى) AmaroK +Comment[bg]=Приставка за Amarok +Comment[bn]=আমারক-এর জন্য প্লাগিন +Comment[br]=Lugent evit Amarok +Comment[ca]=Connector per l'Amarok +Comment[cs]=Modul pro AmaroK +Comment[de]=Modul für Amarok +Comment[el]=Πρόσθετο για το AmaroK +Comment[eo]=Kromaĵo por Amarok +Comment[es]=Extensión para Amarok +Comment[et]=Amaroki plugin +Comment[fa]=وصله برای amaroK +Comment[fi]=Amarok-liitännäinen +Comment[fr]=Module pour Amarok +Comment[ga]=Breiseán AmaroK +Comment[gl]=Extensión para Amarok +Comment[hu]=Bővítőmodul az Amarokhoz +Comment[is]=Íforrit fyrir Amarok +Comment[it]=Plugin per Amarok +Comment[ja]=Amarok のためのプラグイン +Comment[ka]=მოდული Amarok-ისთვის +Comment[km]=កម្មវិធី​ជំនួយ​សម្រាប់ Amarok +Comment[lt]=Amarok įskiepis +Comment[mk]=Приклучок за Амарок +Comment[nb]=Programtillegg for Amarok +Comment[nds]=Moduul för Amarok +Comment[ne]=अमारोकका लागि प्लगइन +Comment[nl]=Plugin voor Amarok +Comment[nn]=Programtillegg for Amarok +Comment[pa]=ਅਮਰੋਕ ਲਈ ਪਲੱਗਇਨ +Comment[pl]=Wtyczka Amaroka +Comment[pt]='Plugin' para o Amarok +Comment[pt_BR]=Plugin para o Amarok +Comment[ru]=Модуль amaroK +Comment[se]=Lassemoduvla Amarok:ii +Comment[sk]=Amarok modul +Comment[sr]=Прикључак за Amarok +Comment[sr@Latn]=Priključak za Amarok +Comment[sv]=Insticksprogram för Amarok +Comment[th]=โปรแกรมเสริมสำหรับ Amarok +Comment[tr]=Amarok için Eklenti +Comment[uk]=Втулок для Amarok +Comment[uz]=Amarok uchun plagin +Comment[uz@cyrillic]=Amarok учун плагин +Comment[wa]=Tchôke-divins po Amarok +Comment[zh_CN]=Amarok 插件 +Comment[zh_TW]=amaroK 插件 +ServiceTypes=Amarok/Plugin + +X-KDE-Amarok-plugintype=engine +X-KDE-Amarok-name=helix-engine +X-KDE-Amarok-authors=Paul Cifarelli +X-KDE-Amarok-email=paul@cifarelli.net +X-KDE-Amarok-rank=75 +X-KDE-Amarok-version=1 +X-KDE-Amarok-framework-version=32 + + + diff --git a/amarok/src/engine/helix/config/Makefile.am b/amarok/src/engine/helix/config/Makefile.am new file mode 100644 index 00000000..fb077794 --- /dev/null +++ b/amarok/src/engine/helix/config/Makefile.am @@ -0,0 +1,15 @@ +noinst_LTLIBRARIES = \ + libhelixconfig.la + +libhelixconfig_la_SOURCES = \ + dummy.cpp \ + helixconfig.kcfgc + +INCLUDES = \ + $(all_includes) + +kde_kcfg_DATA = \ + helixconfig.kcfg + + + diff --git a/amarok/src/engine/helix/config/dummy.cpp b/amarok/src/engine/helix/config/dummy.cpp new file mode 100644 index 00000000..e69de29b diff --git a/amarok/src/engine/helix/config/helixconfig.kcfg b/amarok/src/engine/helix/config/helixconfig.kcfg new file mode 100644 index 00000000..f94bbfe9 --- /dev/null +++ b/amarok/src/engine/helix/config/helixconfig.kcfg @@ -0,0 +1,44 @@ + + + + + config.h + + + + + This is the directory where clntcore.so is located + HELIX_LIBS "/common" + + + + This is the directory where, for example, vorbisrend.so is located + HELIX_LIBS "/plugins" + + + + This is the directory where, for example, cvt1.so is located + HELIX_LIBS "/codecs" + + + + OSS vs ALSA + "oss" + + + + ALSA Device + "default" + + + + Is the device selected + false + + + + + diff --git a/amarok/src/engine/helix/config/helixconfig.kcfgc b/amarok/src/engine/helix/config/helixconfig.kcfgc new file mode 100644 index 00000000..c0f6079d --- /dev/null +++ b/amarok/src/engine/helix/config/helixconfig.kcfgc @@ -0,0 +1,7 @@ +# Code generation options for kconfig_compiler +File=helixconfig.kcfg +ClassName=HelixConfig +Singleton=true +Mutators=true +MemberVariables=private + diff --git a/amarok/src/engine/helix/helix-configdialog.cpp b/amarok/src/engine/helix/helix-configdialog.cpp new file mode 100644 index 00000000..15895db4 --- /dev/null +++ b/amarok/src/engine/helix/helix-configdialog.cpp @@ -0,0 +1,482 @@ +/*************************************************************************** + * Copyright (C) 2005 Paul Cifarelli * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "helix-configdialog.h" +#include "helix-engine.h" + +#include "config/helixconfig.h" +#include + +#include + +#define DEBUG_PREFIX "helix-engine" +#define indent helix_indent + +#include "debug.h" + +using namespace std; + +HelixConfigDialogBase *HelixConfigDialog::instance = NULL; + +HelixConfigEntry::HelixConfigEntry( QWidget *parent, + Amarok::PluginConfig *pluginConfig, + int row, + const QString & description, + const char *defaultvalue, + const QString & tooltip) + : m_w(0) + , m_valueChanged( false ) + , m_stringValue( defaultvalue ) +{ + QGridLayout *grid = (QGridLayout*)parent->layout(); + + m_w = new KLineEdit( m_stringValue, parent ); + connect( (QWidget *) m_w, SIGNAL(textChanged( const QString& )), this, SLOT(slotStringChanged( const QString& )) ); + connect( (QWidget *) m_w, SIGNAL(textChanged( const QString& )), pluginConfig, SIGNAL(viewChanged()) ); + + QToolTip::add( (QWidget *) m_w, "" + tooltip ); + + QLabel* d = new QLabel( description + ':', parent ); + d->setAlignment( QLabel::WordBreak | QLabel::AlignVCenter ); + + grid->addWidget( (QWidget *) m_w, row, 1 ); + grid->addWidget( d, row, 0 ); +} + +HelixConfigEntry::HelixConfigEntry( QWidget *parent, + QString &str, + Amarok::PluginConfig *pluginConfig, + int row, + const QString & description, + const char *defaultvalue, + const QString & tooltip) + : m_w(0) + , m_valueChanged( false ) + , m_stringValue( defaultvalue ) +{ + QGridLayout *grid = (QGridLayout*)parent->layout(); + + m_key = str; + + m_w = new KLineEdit( str, parent ); + connect( m_w, SIGNAL(textChanged( const QString& )), this, SLOT(slotStringChanged( const QString& )) ); + connect( m_w, SIGNAL(textChanged( const QString& )), pluginConfig, SIGNAL(viewChanged()) ); + + QToolTip::add( m_w, "" + tooltip ); + + QLabel* d = new QLabel( description + ':', parent ); + d->setAlignment( QLabel::WordBreak | QLabel::AlignVCenter ); + + grid->addWidget( m_w, row, 1 ); + grid->addWidget( d, row, 0 ); +} + + +inline void +HelixConfigEntry::slotStringChanged( const QString& ) +{ + m_stringValue = m_w->text(); + m_valueChanged = true; +} + +HelixSoundDevice::HelixSoundDevice( QWidget *parent, + Amarok::PluginConfig *pluginConfig, + int &row, + HelixEngine *engine ) + : deviceComboBox(0), checkBox_outputDevice(0), lineEdit_outputDevice(0), m_changed(false), m_engine(engine) +{ + QGridLayout *grid = (QGridLayout*)parent->layout(); + + deviceComboBox = new KComboBox( false, parent, "deviceComboBox" ); + deviceComboBox->insertItem("oss"); // I believe these are not subject to translation (they don't seem to be in xine, +#ifdef USE_HELIX_ALSA + deviceComboBox->insertItem("alsa"); // and neither are the equivalents in gst (osssink and alsasink) +#endif + deviceComboBox->setCurrentItem(HelixConfig::outputplugin()); + QLabel* op = new QLabel( i18n("Output plugin:"), parent ); + op->setAlignment( QLabel::WordBreak | QLabel::AlignVCenter ); + grid->addWidget( op, row, 0 ); + grid->addWidget( deviceComboBox, row, 1); + connect( (QWidget *)deviceComboBox, SIGNAL( activated( const QString& ) ), this, SLOT( slotNewDevice( const QString& )) ); + connect( (QWidget *)deviceComboBox, SIGNAL( activated( const QString& )), pluginConfig, SIGNAL(viewChanged()) ); + + ++row; + + checkBox_outputDevice = new QCheckBox( parent, "checkBox_outputDevice" ); + checkBox_outputDevice->setSizePolicy( QSizePolicy( (QSizePolicy::SizeType)5, (QSizePolicy::SizeType)5, 0, 0, checkBox_outputDevice->sizePolicy().hasHeightForWidth() ) ); + grid->addWidget( checkBox_outputDevice, row, 0 ); + checkBox_outputDevice->setText( i18n( "Device:" ) ); + + lineEdit_outputDevice = new KLineEdit( HelixConfig::device(), parent ); + connect( (QWidget *) lineEdit_outputDevice, SIGNAL(textChanged( const QString& )), this, SLOT(slotStringChanged( const QString& )) ); + connect( (QWidget *) lineEdit_outputDevice, SIGNAL( textChanged( const QString& )), pluginConfig, SIGNAL(viewChanged()) ); + connect( checkBox_outputDevice, SIGNAL( toggled(bool) ), lineEdit_outputDevice, SLOT( setEnabled(bool) ) ); + connect( checkBox_outputDevice, SIGNAL( toggled(bool) ), pluginConfig, SIGNAL(viewChanged()) ); + + connect( checkBox_outputDevice, SIGNAL( toggled(bool) ), this, SLOT( slotDeviceChecked(bool) ) ); + grid->addWidget( (QWidget *) lineEdit_outputDevice, row, 1 ); + + if (HelixConfig::deviceenabled()) + { + checkBox_outputDevice->setChecked( true ); + lineEdit_outputDevice->setEnabled( true ); + } + else + { + checkBox_outputDevice->setChecked( false ); + lineEdit_outputDevice->setEnabled( false ); + } + + if (HelixConfig::outputplugin() == "oss") + { + checkBox_outputDevice->setEnabled( false ); + lineEdit_outputDevice->setEnabled( false ); + } +} + +void +HelixSoundDevice::slotNewDevice( const QString &dev ) +{ + if (dev == "oss") + { + checkBox_outputDevice->setEnabled( false ); + lineEdit_outputDevice->setEnabled(false); + } + else + { + checkBox_outputDevice->setEnabled( true ); + if (checkBox_outputDevice->isChecked()) + lineEdit_outputDevice->setEnabled( true ); + else + lineEdit_outputDevice->setEnabled( false ); + } + + m_changed = true; +} + +void +HelixSoundDevice::slotStringChanged( const QString& ) +{ + m_changed = true; +} + +void +HelixSoundDevice::slotDeviceChecked( bool checked ) +{ + checkBox_outputDevice->setChecked( checked ); + if (checked) + lineEdit_outputDevice->setEnabled( true ); + else + lineEdit_outputDevice->setEnabled( false ); + m_changed = true; +} + +bool +HelixSoundDevice::save() +{ + if (m_changed) + { + HelixConfig::setOutputplugin(deviceComboBox->currentText()); + if (deviceComboBox->currentText() == "oss") + m_engine->setOutputSink(HelixSimplePlayer::OSS); + else + m_engine->setOutputSink(HelixSimplePlayer::ALSA); + + HelixConfig::setDevice( lineEdit_outputDevice->text() ); + if (checkBox_outputDevice->isChecked()) + m_engine->setDevice( lineEdit_outputDevice->text().utf8() ); + else + m_engine->setDevice("default"); + HelixConfig::setDeviceenabled( checkBox_outputDevice->isChecked() ); + } + + return m_changed; +} + +void HelixSoundDevice::setSoundSystem( int api ) +{ + switch (api) + { + case HelixSimplePlayer::OSS: + deviceComboBox->setCurrentItem("oss"); + checkBox_outputDevice->setEnabled( false ); + lineEdit_outputDevice->setEnabled(false); + break; + + case HelixSimplePlayer::ALSA: + deviceComboBox->setCurrentItem("alsa"); + checkBox_outputDevice->setEnabled( true ); + if (checkBox_outputDevice->isChecked()) + lineEdit_outputDevice->setEnabled( true ); + else + lineEdit_outputDevice->setEnabled( false ); + break; + }; + HelixConfig::setOutputplugin(deviceComboBox->currentText()); + HelixConfig::writeConfig(); +} + +void HelixConfigDialogBase::setSoundSystem( int api ) +{ + m_device->setSoundSystem(api); +} + + +HelixConfigDialogBase::HelixConfigDialogBase( HelixEngine *engine, Amarok::PluginConfig *config, QWidget *p ) + : QTabWidget( p ) + , m_core(0) + , m_plugin(0) + , m_codec(0) + , m_device(0) + , m_engine( engine ) +{ + int row = 0; + QString currentPage; + QWidget *parent = 0; + QGridLayout *grid = 0; + QScrollView *sv = 0; + + QString pageName( i18n("Main") ); + + addTab( sv = new QScrollView, pageName ); + parent = new QWidget( sv->viewport() ); + + sv->setResizePolicy( QScrollView::AutoOneFit ); + sv->setHScrollBarMode( QScrollView::AlwaysOff ); + sv->setFrameShape( QFrame::NoFrame ); + sv->addChild( parent ); + + grid = new QGridLayout( parent, /*rows*/20, /*cols*/2, /*margin*/10, /*spacing*/10 ); + grid->setColStretch( 0, 1 ); + grid->setColStretch( 1, 1 ); + + if( sv ) + sv->setMinimumWidth( grid->sizeHint().width() + 20 ); + + engine->m_coredir = HelixConfig::coreDirectory(); + m_core = new HelixConfigEntry( parent, engine->m_coredir, + config, row, + i18n("Helix/Realplay core directory"), + HelixConfig::coreDirectory().utf8(), + i18n("This is the directory where clntcore.so is located")); + ++row; + engine->m_pluginsdir = HelixConfig::pluginDirectory(); + m_plugin = new HelixConfigEntry( parent, engine->m_pluginsdir, + config, row, + i18n("Helix/Realplay plugins directory"), + HelixConfig::pluginDirectory().utf8(), + i18n("This is the directory where, for example, vorbisrend.so is located")); + ++row; + engine->m_codecsdir = HelixConfig::codecsDirectory(); + m_codec = new HelixConfigEntry( parent, engine->m_codecsdir, + config, row, + i18n("Helix/Realplay codecs directory"), + HelixConfig::codecsDirectory().utf8(), + i18n("This is the directory where, for example, cvt1.so is located")); + ++row; + grid->addMultiCellWidget( new KSeparator( KSeparator::Horizontal, parent ), row, row, 0, 1 ); + + ++row; + m_device = new HelixSoundDevice( parent, config, row, engine ); + + // lets find the logo if we can + QPixmap *pm = 0; + QString logo = HelixConfig::coreDirectory(); + if (logo.isEmpty()) + logo = HELIX_LIBS "/common"; + + logo.append("/../share/"); + + QString tmp = logo; + tmp.append("hxplay/logo.png"); + if (QFileInfo(tmp).exists()) + { + logo = tmp; + pm = new QPixmap(logo); + } + else + { + tmp = logo; + tmp.append("realplay/logo.png"); + if (QFileInfo(tmp).exists()) + { + logo = tmp; + pm = new QPixmap(logo); + } + } + + if (pm) + { + QLabel *l = new QLabel(parent); + l->setPixmap(*pm); + grid->addMultiCellWidget( l, 20, 20, 1, 1, Qt::AlignRight ); + } + + entries.setAutoDelete( true ); + + pageName = i18n("Plugins"); + + addTab( sv = new QScrollView, pageName ); + parent = new QWidget( sv->viewport() ); + + sv->setResizePolicy( QScrollView::AutoOneFit ); + sv->addChild( parent ); + + QTextEdit *le = new QTextEdit( parent ); + if( sv ) + sv->setMinimumWidth( le->sizeHint().width() ); + + grid = new QGridLayout( parent, /*rows*/1, /*cols*/1, /*margin*/2, /*spacing*/1 ); + grid->addMultiCellWidget( le, 0, 1, 0, 1, 0 ); + le->setWordWrap(QTextEdit::NoWrap); + + int n = engine->numPlugins(); + const char *description, *copyright, *moreinfourl; + row = 0; + for (int i=0; igetPluginInfo(i, description, copyright, moreinfourl)) + { + le->append(QString(description)); + le->append(QString(copyright)); + le->append(QString(moreinfourl)); + le->append(QString(" ")); + } + } + + le->setReadOnly(true); + le->setContentsPos(0,0); +} + +HelixConfigDialogBase::~HelixConfigDialogBase() +{ + delete m_core; + delete m_plugin; + delete m_codec; + delete m_device; +} + +bool +HelixConfigDialogBase::hasChanged() const +{ + for( QPtrListIterator it( entries ); *it != 0; ++it ) + if ( (*it)->isChanged() ) + return true; + if (m_core->isChanged() || m_plugin->isChanged() || m_codec->isChanged() || m_device->isChanged()) + return true; + + return false; +} + +bool +HelixConfigDialogBase::isDefault() const +{ + return false; +} + +void +HelixConfigDialogBase::save() +{ + bool writeIt = false; + + if (m_core->isChanged()) + { + m_engine->m_coredir = m_core->stringValue(); + HelixConfig::setCoreDirectory(m_engine->m_coredir); + writeIt = true; + } + + if (m_plugin->isChanged()) + { + m_engine->m_pluginsdir = m_plugin->stringValue(); + HelixConfig::setPluginDirectory(m_engine->m_pluginsdir); + writeIt = true; + } + + if (m_codec->isChanged()) + { + m_engine->m_codecsdir = m_codec->stringValue(); + HelixConfig::setCodecsDirectory(m_engine->m_codecsdir); + writeIt = true; + } + + writeIt |= m_device->save(); + + // not really doing anything here yet + for( HelixConfigEntry *entry = entries.first(); entry; entry = entries.next() ) + { + if( entry->isChanged() ) + { + entry->setUnchanged(); + } + } + + if (m_device->isChanged()) + { + m_device->setUnchanged(); + writeIt = true; + } + + if (writeIt) + { + HelixConfig::writeConfig(); + + // reinit... + m_engine->init(); + } + +} + +HelixConfigDialog::HelixConfigDialog( HelixEngine *engine, QWidget *p ) : Amarok::PluginConfig() +{ + if (!instance) + instance = new HelixConfigDialogBase( engine, this, p ); +} + +HelixConfigDialog::~HelixConfigDialog() +{ + delete instance; + instance = 0; +} + +int HelixConfigDialog::setSoundSystem( int api ) +{ + if (instance) + { + instance->setSoundSystem(api); + return 0; + } + else + { + HelixConfig::setOutputplugin(api ? "alsa" : "oss"); + HelixConfig::writeConfig(); + return 1; + } +} + +#include "helix-configdialog.moc" diff --git a/amarok/src/engine/helix/helix-configdialog.h b/amarok/src/engine/helix/helix-configdialog.h new file mode 100644 index 00000000..0e9487c7 --- /dev/null +++ b/amarok/src/engine/helix/helix-configdialog.h @@ -0,0 +1,121 @@ +/*************************************************************************** + * Copyright (C) 2005 Paul Cifarelli * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef _HELIX_CONFIG_H_ +#define _HELIX_CONFIG_H_ + +#include "plugin/pluginconfig.h" +#include +#include +#include + +class QGridLayout; +class KComboBox; +class QCheckBox; +class KLineEdit; +class HelixEngine; + +// since many preferences can be set in Helix, I'm planning on more config items later +// for now I'll just get the location of the Helix core/plugins for initializing +// the Helix core +class HelixConfigEntry : public QObject +{ +Q_OBJECT +public: + HelixConfigEntry( QWidget *parent, Amarok::PluginConfig*, + int row, const QString & description, const char *defaultvalue, const QString & tooltip ); + HelixConfigEntry( QWidget *parent, QString &str, Amarok::PluginConfig*, + int row, const QString & description, const char *defaultvalue, const QString & tooltip ); + + bool isChanged() const { return m_valueChanged; } + void setUnchanged() { m_valueChanged = false; } + const QString& key() const { return m_key; } + QString stringValue() const { return m_stringValue; } + int numValue() const { return m_numValue; } + +private slots: + void slotStringChanged( const QString& ); + +private: + KLineEdit *m_w; + bool m_valueChanged; + int m_numValue; + QString m_key; + QString m_stringValue; +}; + +class HelixSoundDevice : public QObject +{ +Q_OBJECT +public: + HelixSoundDevice( QWidget *parent, Amarok::PluginConfig *config, int &row, HelixEngine *engine ); + bool save(); + void setSoundSystem( int api ); + bool isChanged() const { return m_changed; } + void setUnchanged() { m_changed = false; } + +private slots: + void slotNewDevice( const QString& ); + void slotStringChanged( const QString& ); + void slotDeviceChecked( bool ); + +private: + KComboBox* deviceComboBox; + QCheckBox* checkBox_outputDevice; + KLineEdit* lineEdit_outputDevice; + bool m_changed; + HelixEngine *m_engine; +}; + + +class HelixConfigDialogBase : public QTabWidget +{ +public: + HelixConfigDialogBase( HelixEngine *engine, Amarok::PluginConfig *config, QWidget *parent = 0 ); + ~HelixConfigDialogBase(); + + virtual QWidget *view() { return this; } + virtual bool hasChanged() const; + virtual bool isDefault() const; + + /** Save view state into configuration */ + virtual void save(); + + void setSoundSystem( int api ); + void setEngine(HelixEngine *e) { m_engine = e; } + +private: + QPtrList entries; + HelixConfigEntry *m_core; + HelixConfigEntry *m_plugin; + HelixConfigEntry *m_codec; + HelixSoundDevice *m_device; + HelixEngine *m_engine; +}; + +class HelixConfigDialog : public Amarok::PluginConfig +{ +public: + HelixConfigDialog( HelixEngine *engine, QWidget *parent = 0 ); + ~HelixConfigDialog(); + + virtual QWidget *view() { return instance->view(); } + virtual bool hasChanged() const { return instance->hasChanged(); } + virtual bool isDefault() const { return instance->isDefault(); } + + virtual void save() { instance->save(); } + static int setSoundSystem( int api ); + +private: + static HelixConfigDialogBase *instance; +}; + + +#endif diff --git a/amarok/src/engine/helix/helix-engine.cpp b/amarok/src/engine/helix/helix-engine.cpp new file mode 100644 index 00000000..f8c8b26a --- /dev/null +++ b/amarok/src/engine/helix/helix-engine.cpp @@ -0,0 +1,897 @@ +/*************************************************************************** + * Copyright (C) 2005 Paul Cifarelli * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include "debug.h" + +#include +#include + +#include +#include +#include + +#include "helix-engine.h" +#include "helix-configdialog.h" +#include "config/helixconfig.h" +#include "helix-errors.h" +#include "helix-sp.h" +#include "hxplayercontrol.h" +#include "amarokconfig.h" + +AMAROK_EXPORT_PLUGIN( HelixEngine ) + +#define DEBUG_PREFIX "helix-engine" + +using namespace std; + +extern "C" +{ + #include +} + +#define HELIX_ENGINE_TIMER 10 // 10 ms timer +#define SCOPE_MAX_BEHIND 200 // 200 postmix buffers + + +#ifndef LLONG_MAX +#define LLONG_MAX 9223372036854775807LL +#endif + + +///returns the configuration we will use +static inline QCString configPath() { return QFile::encodeName( QDir::homeDirPath() + "/.helix/config" ); } + + +HelixEngine::HelixEngine() + : EngineBase(), PlayerControl(), + m_state(Engine::Empty), + m_coredir(HELIX_LIBS "/common"), + m_pluginsdir(HELIX_LIBS "/plugins"), + m_codecsdir(HELIX_LIBS "/codecs"), + m_inited(false), + m_scopeplayerlast(0), + m_sfps(0.0), + m_scopedelta(0), + m_sframes(0), + m_lframes(0) +{ + addPluginProperty( "HasConfigure", "true" ); + addPluginProperty( "HasEqualizer", "true" ); + addPluginProperty( "HasCrossfade", "true" ); + addPluginProperty( "HasCDDA", "false"); + + memset(&m_md, 0, sizeof(m_md)); + memset(hscope, 0, 2*sizeof(HelixScope)); + memset(&m_scopetm, 0, sizeof(struct timeval)); + memset(m_pfade, 0, 2*sizeof(FadeTrack)); +} + +HelixEngine::~HelixEngine() +{ + m_mimes.clear(); +} + +int HelixEngine::print2stdout(const char *fmt, ...) +{ + va_list args; + char buf[1024]; + + va_start(args, fmt); + + int ret = vsprintf(buf, fmt, args); + debug() << buf; + + va_end(args); + + return ret; +} + + +int HelixEngine::print2stderr(const char *fmt, ...) +{ + va_list args; + char buf[1024]; + + va_start(args, fmt); + + int ret = vsprintf(buf, fmt, args); + debug() << buf; + + va_end(args); + + return ret; +} + + +void HelixEngine::notifyUser(unsigned long code, const char *moreinfo, const char *moreinfourl) +{ + QString *err = HelixErrors::errorText(code); + if (err) + emit statusText(i18n("Helix Core returned error: %1 %2 %3").arg(QString(*err)).arg(QString(moreinfo)).arg(QString(moreinfourl))); + else + emit statusText(i18n("Helix Core returned error: ")); +} + +void HelixEngine::interruptUser(unsigned long code, const char *moreinfo, const char *moreinfourl) +{ + QString *err = HelixErrors::errorText(code); + if (err) + emit infoMessage(i18n("Helix Core returned error: %1 %1 %1").arg(QString(*err)).arg(QString(moreinfo)).arg(QString(moreinfourl))); + else + emit infoMessage(i18n("Helix Core returned error: ")); + + // since this is a serious error, emit trackEnded so amarok knows to move on + play_finished( m_current ); +} + + +void HelixEngine::onContacting(const char *host) +{ + emit statusText( i18n("Contacting: %1").arg( QString(host) ) ); +} + +void HelixEngine::onBuffering(int pcnt) +{ + if (pcnt != 100) // let's not report that... + emit statusText( i18n( "Buffering %1%" ).arg( pcnt ) ); +} + + +Amarok::PluginConfig* +HelixEngine::configure() const +{ + debug() << "Starting HelixConfigDialog\n"; + return new HelixConfigDialog( (HelixEngine *)this ); +} + +int HelixEngine::fallbackToOSS() +{ + KMessageBox::information( 0, i18n("The helix library you have configured does not support ALSA, the helix-engine has fallen back to OSS") ); + debug() << "Falling back to OSS\n"; + return (HelixConfigDialog::setSoundSystem( (int) HelixSimplePlayer::OSS )); +} + +bool +HelixEngine::init() +{ + debug() << "Initializing HelixEngine\n"; + struct stat s; + bool exists = false; + stop(); + m_state = Engine::Empty; + + m_numPlayers = 2; + m_current = 1; + + m_coredir = HelixConfig::coreDirectory(); + if (m_coredir.isEmpty()) + m_coredir = HELIX_LIBS "/common"; + + m_pluginsdir = HelixConfig::pluginDirectory(); + if (m_pluginsdir.isEmpty()) + m_pluginsdir = HELIX_LIBS "/plugins"; + + m_codecsdir = HelixConfig::codecsDirectory(); + if (m_codecsdir.isEmpty()) + m_codecsdir = HELIX_LIBS "/codecs"; + + if (HelixConfig::outputplugin() == "oss") + setOutputSink( HelixSimplePlayer::OSS ); + else + { + setOutputSink( HelixSimplePlayer::ALSA ); + if (HelixConfig::deviceenabled()) + setDevice( HelixConfig::device().utf8() ); + else + setDevice("default"); + } + + + if (!stat(m_coredir.utf8(), &s) && !stat(m_pluginsdir.utf8(), &s) && !stat(m_codecsdir.utf8(), &s)) + { + long vol=0; + bool eqenabled=false; + int savedpreamp=0; + QValueList savedequalizerGains; + + if (m_inited) + { + vol = PlayerControl::getVolume(); + eqenabled = PlayerControl::isEQenabled(); + for (unsigned int i=0; i < m_equalizerGains.size(); i++) + savedequalizerGains.append(m_equalizerGains[i]); + savedpreamp = m_preamp; + PlayerControl::tearDown(); + } + + PlayerControl::init(m_coredir.utf8(), m_pluginsdir.utf8(), m_codecsdir.utf8(), 2); + if (PlayerControl::initDirectSS()) + { + fallbackToOSS(); + + PlayerControl::initDirectSS(); + } + + if (m_inited) + { + PlayerControl::setVolume(vol); + setEqualizerEnabled(eqenabled); + setEqualizerParameters(savedpreamp, savedequalizerGains); + } + + m_inited = exists = true; + } + + + if (!exists || PlayerControl::getError()) + { + KMessageBox::error( 0, i18n("The Helix Engine requires the RealPlayer(tm) or HelixPlayer libraries to be installed. Please make sure one is installed, and adjust the paths in \"Amarok Settings\" -> \"Engine\"") ); + // we need to return true here so that the user has an oppportunity to change the directory + //return false; + return true; + } + + // create a list of mime types and ext for use in canDecode() + m_mimes.resize( getMimeListLen() ); + int i = 0; + const MimeList *ml = getMimeList(); + MimeEntry *entry; + while (ml) + { + QString mt = ml->mimetypes; + QString me = ml->mimeexts; + + entry = new MimeEntry; + entry->type = QStringList::split('|', mt); + entry->ext = QStringList::split('|', me); + m_mimes[i] = *entry; + + debug() << ml->mimetypes << endl; + + i++; + ml = ml->fwd; + } + + debug() << "Succussful init\n"; + + return true; +} + + +bool +HelixEngine::load( const KURL &url, bool isStream ) +{ + debug() << "In load " << url.url() << endl; + + if (!m_inited) + return false; + + if (!canDecode(url)) + { + const QString path = url.path(); + const QString ext = path.mid( path.findRev( '.' ) + 1 ).lower(); + emit statusText( i18n("No plugin found for the %1 format").arg(ext) ); + return false; + } + + debug() << "xfadeLength is " << m_xfadeLength << endl; + if( m_xfadeLength > 0 && m_state == Engine::Playing && !isStream && + ( m_xfadeNextTrack || //set by engine controller when switching tracks automatically + (uint) AmarokConfig::crossfadeType() == 0 || //crossfade always + (uint) AmarokConfig::crossfadeType() == 2 ) ) //crossfade when switching tracks manually) + { + //set m_xfadeNextTrack true here regardless to play() will work correctly; disable in there + m_xfadeNextTrack = true; + int nextPlayer = m_current ? 0 : 1; + + // prepare the next player + PlayerControl::stop(nextPlayer); + resetScope(nextPlayer); + memset(&hscope[nextPlayer], 0, sizeof(HelixScope)); + memset(&m_pfade[nextPlayer], 0, sizeof(FadeTrack)); + + if (isPlaying(m_current)) + { + m_pfade[m_current].m_fadeactive = true; + m_pfade[m_current].m_startfadetime = PlayerControl::where(m_current); + setFadeout(true, m_xfadeLength, m_current); + } + Engine::Base::load( url, false ); // we don't crossfade streams ?? do we load the base here ?? + PlayerControl::setURL( QFile::encodeName( url.url() ), nextPlayer, !isStream ); + m_isStream = false; + } + else + cleanup(); + + m_isStream = isStream; + int nextPlayer; + + nextPlayer = m_current ? 0 : 1; + + Engine::Base::load( url, isStream || url.protocol() == "http" ); + m_state = Engine::Idle; + emit stateChanged( Engine::Idle ); + m_url = url; + + if (url.isLocalFile()) + PlayerControl::setURL( QFile::encodeName( url.url() ), nextPlayer, !m_isStream ); + else + { + m_isStream = true; + PlayerControl::setURL( QFile::encodeName( url.url() ), nextPlayer, !m_isStream ); + } + + return true; +} + +bool +HelixEngine::play( uint offset ) +{ + debug() << "In play" << endl; + int nextPlayer; + + if (!m_inited) + return false; + + if (m_state != Engine::Playing) + { + struct timezone tz; + memset(&tz, 0, sizeof(struct timezone)); + gettimeofday(&m_scopetm, &tz); + startTimer(HELIX_ENGINE_TIMER); + } + + nextPlayer = m_current ? 0 : 1; + + if (m_xfadeLength && m_xfadeNextTrack && !offset && isPlaying(m_current)) + { + m_xfadeNextTrack = false; + PlayerControl::start(nextPlayer, true, m_xfadeLength); + } + else + PlayerControl::start(nextPlayer); + + if (offset) + PlayerControl::seek( offset, nextPlayer ); + + if (!PlayerControl::getError()) + { + if (m_state != Engine::Playing) + { + m_state = Engine::Playing; + emit stateChanged( Engine::Playing ); + } + + m_current = nextPlayer; + return true; + } + + cleanup(); + m_state = Engine::Empty; + emit stateChanged( Engine::Empty ); + + return false; +} + +void +HelixEngine::cleanup() +{ + if (!m_inited) + return; + + m_url = KURL(); + PlayerControl::stop(); // stop all players + resetScope(0); + resetScope(1); + killTimers(); + m_isStream = false; + memset(&m_md, 0, sizeof(m_md)); + memset(hscope, 0, 2*sizeof(HelixScope)); + memset(m_pfade, 0, 2*sizeof(FadeTrack)); +} + +void +HelixEngine::stop() +{ + if (!m_inited) + return; + + debug() << "In stop where=" << where(m_current) << " duration=" << duration(m_current) << endl; + + if( AmarokConfig::fadeout() && !m_pfade[m_current].m_fadeactive && state() == Engine::Playing ) + { + debug() << "fading out...\n"; + m_state = Engine::Empty; + emit stateChanged( Engine::Empty ); // tell the controller not to bother you anymore + + m_pfade[m_current].m_fadeactive = true; + m_pfade[m_current].m_stopfade = true; + m_pfade[m_current].m_startfadetime = PlayerControl::where(m_current); + setFadeout(true, AmarokConfig::fadeoutLength(), m_current); + } + else + { + debug() << "Stopping immediately\n"; + cleanup(); + cleanUpStream(m_current); + m_state = Engine::Empty; + emit stateChanged( m_state ); + } +} + + +void HelixEngine::play_finished(int playerIndex) +{ + debug() << "Ok, finished playing the track\n"; + cleanUpStream(playerIndex); + resetScope(playerIndex); + memset(&hscope[playerIndex], 0, sizeof(HelixScope)); + memset(&m_pfade[playerIndex], 0, sizeof(FadeTrack)); + if (playerIndex == m_current && !m_pfade[playerIndex].m_stopfade && !m_pfade[playerIndex].m_fadeactive) + { + m_state = Engine::Idle; + emit stateChanged( m_state ); + emit trackEnded(); + } +} + +void +HelixEngine::pause() +{ + if (!m_inited) + return; + + // TODO: PAUSE in XFADE + debug() << "In pause\n"; + if( m_state == Engine::Playing ) + { + PlayerControl::pause(m_current); + m_state = Engine::Paused; + emit stateChanged( Engine::Paused ); + } +} + +void +HelixEngine::unpause() +{ + if (!m_inited) + return; + + // TODO: PAUSE in XFADE + debug() << "In unpause\n"; + if ( m_state == Engine::Paused ) + { + PlayerControl::resume(m_current); + m_state = Engine::Playing; + emit stateChanged( Engine::Playing ); + } +} + +Engine::State +HelixEngine::state() const +{ + //debug() << "In state, state is " << m_state << endl; + + if (!m_inited || m_url.isEmpty()) + return (Engine::Empty); + + return m_state; +} + +uint +HelixEngine::position() const +{ + if (!m_inited) + return 0; + + return PlayerControl::where(m_current); +} + +uint +HelixEngine::length() const +{ + if (!m_inited) + return 0; + + return PlayerControl::duration(m_current); +} + +void +HelixEngine::seek( uint ms ) +{ + if (!m_inited) + return; + + debug() << "In seek\n"; + resetScope(0); + resetScope(1); + PlayerControl::seek(ms, m_current); +} + +void +HelixEngine::setVolumeSW( uint vol ) +{ + if (!m_inited) + return; + + debug() << "In setVolumeSW\n"; + PlayerControl::setVolume(vol); // set the volume in all players! +} + + +bool +HelixEngine::canDecode( const KURL &url ) const +{ + if (!m_inited) + return false; + + debug() << "In canDecode " << url.prettyURL() << endl; + + if (url.protocol() == "http" || url.protocol() == "rtsp") + return true; + + const QString path = url.path(); + const QString ext = path.mid( path.findRev( '.' ) + 1 ).lower(); + + if (ext != "txt") + for (int i=0; i<(int)m_mimes.size(); i++) + { + if (m_mimes[i].type.grep("audio").count() || + m_mimes[i].type.grep("video").count() || + m_mimes[i].type.grep("application").count()) + if (m_mimes[i].ext.grep(ext).count()) + { + return true; + } + } + + return false; +} + +void +HelixEngine::timerEvent( QTimerEvent * ) +{ + PlayerControl::dispatch(); // dispatch the players + if ( m_xfadeLength <= 0 && m_state == Engine::Playing && PlayerControl::done(m_current) ) + play_finished(m_current); + else if ( m_xfadeLength > 0 || AmarokConfig::fadeout() ) + { + if ( m_state == Engine::Playing && isPlaying(m_current?0:1) && PlayerControl::done(m_current?0:1) ) + hscope[m_current?0:1].m_lasttime = 0; + + // fade on stop finished + if ( m_pfade[m_current].m_stopfade && m_pfade[m_current].m_fadeactive && + (PlayerControl::where(m_current) > m_pfade[m_current].m_startfadetime + (unsigned)AmarokConfig::fadeoutLength() || + PlayerControl::done(m_current)) ) + { + debug() << "Stop fade end\n"; + stop(); + } + + // crossfade finished + if ( m_pfade[m_current?0:1].m_fadeactive && + PlayerControl::where(m_current?0:1) > m_pfade[m_current?0:1].m_startfadetime + (unsigned)m_xfadeLength) + play_finished(m_current?0:1); + } + + // prune the scope(s) + prune(); + + struct timeval tm; + struct timezone tz; + memset(&tz, 0, sizeof(struct timezone)); + gettimeofday(&tm, &tz); + m_scopedelta = (tm.tv_sec - m_scopetm.tv_sec) * 1000 + (tm.tv_usec - m_scopetm.tv_usec) / 1000; // ms + m_scopetm.tv_sec = tm.tv_sec; + m_scopetm.tv_usec = tm.tv_usec; + hscope[m_current].m_lasttime += m_scopedelta; + + HelixSimplePlayer::metaData *md = getMetaData(m_current); + if (m_isStream && + (strcmp(m_md.title, md->title) || strcmp(m_md.artist, md->artist))) + { + memcpy(&m_md, md, sizeof(m_md)); + + debug() << "{Title}: " << md->title << " {Artist}: " << md->artist << " {Bitrate}: " << md->bitrate << endl; + + /* Real Radio One (and Rhapsody?) streams have their own format, where title is: + * clipinfo:title=|artist name=<artist>|Album name=<album>|Artist:Next artist=<next artist>| \ + * ordinal=<some number>|duration=<in secs>|Track:Rhapsody Track Id=<some number> + * + * for all other streams helix sends the title of the song in the artist string. + * this prevents context lookup, so we split it here (the artist and title are separated by a '-' + * we'll put the 'title' in album instead... + */ + Engine::SimpleMetaBundle bndl; + bndl.album = QString::fromUtf8( m_md.title ); + if ( bndl.album.startsWith( QString("clipinfo:") ) ) + { + bndl.album = bndl.album.remove(0, 9); + QStringList sl = QStringList::split('|', bndl.album); + for ( QStringList::Iterator it = sl.begin(); it != sl.end(); ++it ) + { + if ((*it).startsWith("title=")) + bndl.title = (*it).section('=', 1, 1); + if ((*it).startsWith("artist name=")) + bndl.artist = (*it).section('=', 1, 1); + if ((*it).startsWith("Album name=")) + bndl.album = (*it).section('=', 1, 1); + if ((*it).startsWith("duration=")) + bndl.length = (*it).section('=', 1, 1); + } + + //debug() << "Title: " << bndl.title << endl; + //debug() << "Artist: " << bndl.artist << endl; + //debug() << "Album: " << bndl.album << endl; + //debug() << "length: " << bndl.length << endl; + } + else + { + char c,*tmp = strchr(m_md.artist, '-'); + if (tmp) + { + tmp--; + c = *tmp; + *tmp = '\0'; + bndl.artist = QString::fromUtf8( m_md.artist ); + *tmp = c; + tmp+=3; + bndl.title = QString::fromUtf8( tmp ); + bndl.album = QString::fromUtf8( m_md.title ); + } + else // just copy them as is... + { + bndl.title = QString::fromUtf8( m_md.title ); + bndl.artist = QString::fromUtf8( m_md.artist ); + } + } + bndl.bitrate = QString::number( m_md.bitrate / 1000 ); + emit EngineBase::metaData( bndl ); + } +} + + +int HelixEngine::prune() +{ + int err = 0; + + err |= prune(0); + err |= prune(1); + + return err; +} + +int HelixEngine::prune(int playerIndex) +{ + // + // this bit is to help us keep more accurate time than helix provides + ///////////////////////////////////////////////////////////////////// + unsigned long hpos = PlayerControl::where(playerIndex); + + if (hpos != hscope[playerIndex].m_lastpos + && hpos - hscope[playerIndex].m_lastpos < hscope[playerIndex].m_lasttime - hscope[playerIndex].m_lastpos) + hscope[playerIndex].m_lasttime = hpos; + + if (hpos > hscope[playerIndex].m_lasttime) + { + hscope[playerIndex].m_w = hpos; + hscope[playerIndex].m_lasttime = hpos; + } + else + hscope[playerIndex].m_w = hscope[playerIndex].m_lasttime; + + hscope[playerIndex].m_lastpos = hpos; + + if ( getScopeCount(playerIndex) > SCOPE_MAX_BEHIND ) // protect against naughty streams + { + resetScope(playerIndex); + return 0; + } + + if (!hscope[playerIndex].m_w || !hscope[playerIndex].m_item) + return 0; + + // prune, unless the player is still starting + while (hpos && hscope[playerIndex].m_item && hscope[playerIndex].m_w > hscope[playerIndex].m_item->etime) + { + //debug() << "pruning " << hpos << "," << hscope[playerIndex].m_w << "," << hscope[playerIndex].m_lasttime + // << "," << hscope[playerIndex].m_item->time << ":" << hscope[playerIndex].m_item->etime << endl; + + if (hscope[playerIndex].m_item && hscope[playerIndex].m_item->allocd) + delete hscope[playerIndex].m_item; + hscope[playerIndex].m_item = getScopeBuf(playerIndex); + } + + if (!hscope[playerIndex].m_item) + return 0; + + if (hscope[playerIndex].m_w < hscope[playerIndex].m_item->time) // wait for the player to catchup + { + //debug() << "waiting for player to catchup " << hpos << "," << hscope[playerIndex].m_w << "," << hscope[playerIndex].m_lasttime + // << "," << hscope[playerIndex].m_item->time << ":" << hscope[playerIndex].m_item->etime << endl; + return 0; + } + + return 1; +} + +const Engine::Scope &HelixEngine::scope() +{ + if (isPlaying(0) && isPlaying(1)) // crossfading + { + if (m_scopeplayerlast) + scope(m_current); + else + scope(m_current?0:1); + + m_scopeplayerlast = !m_scopeplayerlast; + } + else + scope(m_current); + + return m_scope; +} + +int HelixEngine::scope(int playerIndex) +{ + int i; + unsigned long t; + + if (!m_inited) + return 0; + + if (!hscope[playerIndex].m_item && !peekScopeTime(t, playerIndex)) + hscope[playerIndex].m_item = getScopeBuf(playerIndex); + + if (!prune(playerIndex)) + return 0; + + if (hscope[playerIndex].m_item->nchan > 2) + return 0; + + int j,k=0; + short int *pint; + unsigned char b[4]; + + // calculate the starting offset into the buffer + int off = (hscope[playerIndex].m_item->spb * (hscope[playerIndex].m_w - hscope[playerIndex].m_item->time) / + (hscope[playerIndex].m_item->etime - hscope[playerIndex].m_item->time)) * + hscope[playerIndex].m_item->nchan * hscope[playerIndex].m_item->bps; + k = off; + while (hscope[playerIndex].m_item && hscope[playerIndex].m_scopeindex < SCOPESIZE) + { + while (k < (int) hscope[playerIndex].m_item->len) + { + for (j=0; j<hscope[playerIndex].m_item->nchan; j++) + { + switch (hscope[playerIndex].m_item->bps) + { + case 1: + b[1] = 0; + b[0] = hscope[playerIndex].m_item->buf[k]; + break; + case 2: + b[1] = hscope[playerIndex].m_item->buf[k+1]; + b[0] = hscope[playerIndex].m_item->buf[k]; + break; + } + + pint = (short *) &b[0]; + + if (hscope[playerIndex].m_item->nchan == 1) // duplicate mono samples + { + hscope[playerIndex].m_currentScope[hscope[playerIndex].m_scopeindex] = *pint; + hscope[playerIndex].m_scopeindex++; + hscope[playerIndex].m_currentScope[hscope[playerIndex].m_scopeindex] = *pint; + hscope[playerIndex].m_scopeindex++; + } + else + { + hscope[playerIndex].m_currentScope[hscope[playerIndex].m_scopeindex] = *pint; + hscope[playerIndex].m_scopeindex++; + } + + k += hscope[playerIndex].m_item->bps; + } + + if (hscope[playerIndex].m_scopeindex >= SCOPESIZE) + { + hscope[playerIndex].m_scopeindex = SCOPESIZE; + break; + } + } + // as long as we know there's another buffer...otherwise we need to wait for another + if (hscope[playerIndex].m_scopeindex < SCOPESIZE) + { + if (hscope[playerIndex].m_item && hscope[playerIndex].m_item->allocd) + delete hscope[playerIndex].m_item; + hscope[playerIndex].m_item = getScopeBuf(playerIndex); + + k = 0; + + if (!hscope[playerIndex].m_item) + return 0; // wait until there are some more buffers available + } + else + { + if (k >= (int) hscope[playerIndex].m_item->len) + { + if (hscope[playerIndex].m_item && hscope[playerIndex].m_item->allocd) + delete hscope[playerIndex].m_item; + hscope[playerIndex].m_item = getScopeBuf(playerIndex); + } + break; // done with the scope buffer, so hand it off + } + } + + // ok, we must have a full buffer here, give it to the scope + for (i=0; i<SCOPESIZE; i++) + m_scope[i] = hscope[playerIndex].m_currentScope[i]; + hscope[playerIndex].m_scopeindex = 0; + + return 1; +} + +void +HelixEngine::resetScope(int playerIndex) +{ + if (playerIndex >=0 && playerIndex < numPlayers()) + { + // make sure the scope is clear of old buffers + clearScopeQ(playerIndex); + hscope[playerIndex].m_scopeindex = 0; + if (hscope[playerIndex].m_item && hscope[playerIndex].m_item->allocd) + delete hscope[playerIndex].m_item; + hscope[playerIndex].m_w = 0; + hscope[playerIndex].m_item = 0; + } +} + + +void +HelixEngine::setEqualizerEnabled( bool enabled ) //SLOT +{ + enableEQ(enabled); +} + + +// ok, this is lifted from gst... but why mess with what works? +void +HelixEngine::setEqualizerParameters( int preamp, const QValueList<int>& bandGains ) //SLOT +{ + m_preamp = ( preamp + 100 ) / 2; + + m_equalizerGains.resize( bandGains.count() ); + for ( uint i = 0; i < bandGains.count(); i++ ) + m_equalizerGains[i] = ( *bandGains.at( i ) + 100 ) / 2; + + updateEQgains(); +} + + +namespace Debug +{ + #undef helix_indent + QCString helix_indent; +} + +#include "helix-engine.moc" diff --git a/amarok/src/engine/helix/helix-engine.h b/amarok/src/engine/helix/helix-engine.h new file mode 100644 index 00000000..e7282d3b --- /dev/null +++ b/amarok/src/engine/helix/helix-engine.h @@ -0,0 +1,131 @@ +/*************************************************************************** + * Copyright (C) 2005-2006 Paul Cifarelli <paul@cifarelli.net> * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef _HELIX_ENGINE_H_ +#define _HELIX_ENGINE_H_ + +#include "enginebase.h" +#include <qobject.h> +#include <sys/types.h> +#include <hxplayercontrol.h> + +class QStringList; +struct timeval; + +class HelixEngine : public Engine::Base, public PlayerControl +{ + Q_OBJECT + +public: + HelixEngine(); + ~HelixEngine(); + + virtual bool init(); + virtual bool canDecode( const KURL& ) const; + virtual uint position() const; + virtual uint length() const; + virtual Engine::State state() const; + + virtual void play_finished(int playerIndex); + virtual const Engine::Scope &scope(); + + virtual Amarok::PluginConfig *configure() const; + + virtual void onContacting(const char *host); + virtual void onBuffering(int pcnt); + + virtual int fallbackToOSS(); + +public slots: + virtual bool load( const KURL &url, bool stream ); + virtual bool play( uint = 0 ); + virtual void stop(); + virtual void pause(); + virtual void unpause(); + virtual void seek( uint ); + + virtual void setEqualizerEnabled( bool ); + virtual void setEqualizerParameters( int preamp, const QValueList<int>& ); + + +protected: + virtual void setVolumeSW( uint ); + +private: + Engine::State m_state; + KURL m_url; + + QString m_coredir; + QString m_pluginsdir; + QString m_codecsdir; + bool m_inited; + + int m_numPlayers; + int m_current; // the current player + + bool m_isStream; + HelixSimplePlayer::metaData m_md; + + int scope(int playerIndex); + int prune(); + int prune(int playerIndex); + bool m_scopeplayerlast; + float m_sfps; + struct timeval m_scopetm; + unsigned long m_scopedelta; + int m_sframes; + int m_lframes; + struct HelixScope + { + DelayQueue *m_item; + unsigned long m_lasttime; + unsigned long m_lastpos; + unsigned short m_currentScope[SCOPESIZE]; + int m_scopeindex; + unsigned long m_w; // more accurate position estimate for the player + } hscope[2]; + + typedef struct MimeEntry + { + QStringList type; + QStringList ext; + }; + + std::vector<MimeEntry> m_mimes; + + struct FadeTrack + { + unsigned long m_startfadetime; + bool m_fadeactive; + bool m_stopfade; + } m_pfade[2]; + + void cleanup(); + void timerEvent( QTimerEvent * ); + void resetScope(int playerIndex); + + int print2stdout(const char *fmt, ...) +#ifdef __GNUC__ + __attribute__ ((format (printf, 2, 3))) +#endif + ; + int print2stderr(const char *fmt, ...) +#ifdef __GNUC__ + __attribute__ ((format (printf, 2, 3))) +#endif + ; + void notifyUser(unsigned long code, const char *moreinfo, const char *moreinfourl); + void interruptUser(unsigned long code, const char *moreinfo, const char *moreinfourl); + + friend class HelixConfigDialogBase; +}; + + +#endif diff --git a/amarok/src/engine/helix/helix-errors.cpp b/amarok/src/engine/helix/helix-errors.cpp new file mode 100644 index 00000000..e4412c7b --- /dev/null +++ b/amarok/src/engine/helix/helix-errors.cpp @@ -0,0 +1,457 @@ +/*************************************************************************** + * Copyright (C) 2005 Paul Cifarelli * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +#include <qdict.h> +#include <config.h> +#include <iostream> +#include "debug.h" +#include <klocale.h> +#include <map> + +using namespace std; + +#include "helix-errors.h" + +struct HelixCoreErrors +{ + unsigned long code; + QString error_string; +} helixErrors[500] = + +{ {0x80040004, i18n("Invalid Operation")}, + {0x80040005, i18n("Invalid Version")}, + {0x80040006, i18n("Invalid Revision")}, + {0x80040007, i18n("Not Initialized")}, + {0x80040008, i18n("Doc Missing")}, + {0x80040009, i18n("Unexpected")}, + {0x8004000c, i18n("Incomplete")}, + {0x8004000d, i18n("Buffertoosmall")}, + {0x8004000e, i18n("Unsupported Video")}, + {0x8004000f, i18n("Unsupported Audio")}, + {0x80040010, i18n("Invalid Bandwidth")}, + {0x80040011, i18n("No Fileformat")}, + {0x80040011, i18n("No Fileformat")}, + {0x80040011, i18n("Missing Components")}, + {0x00040012, i18n("Element Not Found")}, + {0x00040013, i18n("Noclass")}, + {0x00040014, i18n("Class Noaggregation")}, + {0x80040015, i18n("Not Licensed")}, + {0x80040016, i18n("No Filesystem")}, + {0x80040017, i18n("Request Upgrade")}, + {0x80040018, i18n("Check Rights")}, + {0x80040019, i18n("Restore Server Denied")}, + {0x8004001a, i18n("Debugger Detected")}, + {0x8004005c, i18n("Restore Server Connect")}, + {0x8004005d, i18n("Restore Server Timeout")}, + {0x8004005e, i18n("Revoke Server Connect")}, + {0x8004005f, i18n("Revoke Server Timeout")}, + {0x800401cd, i18n("View Rights Nodrm")}, + {0x800401d3, i18n("Vsrc Nodrm")}, + {0x80040024, i18n("Wm Opl Not Supported")}, + {0x8004001b, i18n("Restoration Complete")}, + {0x8004001c, i18n("Backup Complete")}, + {0x8004001d, i18n("Tlc Not Certified")}, + {0x8004001e, i18n("Corrupted Backup File")}, + {0x8004001f, i18n("Awaiting License")}, + {0x80040020, i18n("Already Initialized")}, + {0x80040021, i18n("Not Supported")}, + {0x00040022, i18n("False")}, + {0x00040023, i18n("Warning")}, + {0x00040040, i18n("Buffering")}, + {0x00040041, i18n("Paused")}, + {0x00040042, i18n("No Data")}, + {0x00040043, i18n("Stream Done")}, + {0x80040043, i18n("Net Socket Invalid")}, + {0x80040044, i18n("Net Connect")}, + {0x80040045, i18n("Bind")}, + {0x80040046, i18n("Socket Create")}, + {0x80040047, i18n("Invalid Host")}, + {0x80040048, i18n("Net Read")}, + {0x80040049, i18n("Net Write")}, + {0x8004004a, i18n("Net Udp")}, + {0x8004004b, i18n("Retry")}, + {0x8004004c, i18n("Server Timeout")}, + {0x8004004d, i18n("Server Disconnected")}, + {0x8004004e, i18n("Would Block")}, + {0x8004004f, i18n("General Nonet")}, + {0x80040050, i18n("Block Canceled")}, + {0x80040051, i18n("Multicast Join")}, + {0x80040052, i18n("General Multicast")}, + {0x80040053, i18n("Multicast Udp")}, + {0x80040054, i18n("At Interrupt")}, + {0x80040055, i18n("Msg Toolarge")}, + {0x80040056, i18n("Net Tcp")}, + {0x80040057, i18n("Try Autoconfig")}, + {0x80040058, i18n("Notenough Bandwidth")}, + {0x80040059, i18n("Http Connect")}, + {0x8004005a, i18n("Port In Use")}, + {0x8004005b, i18n("Loadtest Not Supported")}, + {0x00040060, i18n("Tcp Connect")}, + {0x00040061, i18n("Tcp Reconnect")}, + {0x80040062, i18n("Tcp Failed")}, + {0x80040063, i18n("Authentication Socket Create Failure")}, + {0x80040064, i18n("Authentication Tcp Connect Failure")}, + {0x80040065, i18n("Authentication Tcp Connect Timeout")}, + {0x80040066, i18n("Authentication Failure")}, + {0x80040067, i18n("Authentication Required Parameter Missing")}, + {0x80040068, i18n("Dns Resolve Failure")}, + {0x00040068, i18n("Authentication Succeeded")}, + {0x80040069, i18n("Pull Authentication Failed")}, + {0x8004006a, i18n("Bind Error")}, + {0x8004006b, i18n("Pull Ping Timeout")}, + {0x8004006c, i18n("Authentication Tcp Failed")}, + {0x8004006d, i18n("Unexpected Stream End")}, + {0x8004006e, i18n("Authentication Read Timeout")}, + {0x8004006f, i18n("Authentication Connection Failure")}, + {0x80040070, i18n("Blocked")}, + {0x80040071, i18n("Notenough Predecbuf")}, + {0x80040072, i18n("End With Reason")}, + {0x80040073, i18n("Socket Nobufs")}, + {0x00040080, i18n("At End")}, + {0x80040081, i18n("Invalid File")}, + {0x80040082, i18n("Invalid Path")}, + {0x80040083, i18n("Record")}, + {0x80040084, i18n("Record Write")}, + {0x80040085, i18n("Temporary File")}, + {0x80040086, i18n("Already Open")}, + {0x80040087, i18n("Seek Pending")}, + {0x80040088, i18n("Cancelled")}, + {0x80040089, i18n("File Not Found")}, + {0x8004008a, i18n("Write Error")}, + {0x8004008b, i18n("File Exists")}, + {0x8004008c, i18n("File Not Open")}, + {0x0004008d, i18n("Advise Prefer Linear")}, + {0x8004008e, i18n("Parse Error")}, + {0x0004008f, i18n("Advise Noasync Seek")}, + {0x80040090, i18n("Header Parse Error")}, + {0x80040091, i18n("Corrupt File")}, + {0x800400c0, i18n("Bad Server")}, + {0x800400c1, i18n("Advanced Server")}, + {0x800400c2, i18n("Old Server")}, + {0x000400c3, i18n("Redirection")}, + {0x800400c4, i18n("Server Alert")}, + {0x800400c5, i18n("Proxy")}, + {0x800400c6, i18n("Proxy Response")}, + {0x800400c7, i18n("Advanced Proxy")}, + {0x800400c8, i18n("Old Proxy")}, + {0x800400c9, i18n("Invalid Protocol")}, + {0x800400ca, i18n("Invalid Url Option")}, + {0x800400cb, i18n("Invalid Url Host")}, + {0x800400cc, i18n("Invalid Url Path")}, + {0x800400cd, i18n("Http Content Not Found")}, + {0x800400ce, i18n("Not Authorized")}, + {0x800400cf, i18n("Unexpected Msg")}, + {0x800400d0, i18n("Bad Transport")}, + {0x800400d1, i18n("No Session Id")}, + {0x800400d2, i18n("Proxy Dnr")}, + {0x800400d3, i18n("Proxy Net Connect")}, + {0x800400d4, i18n("Aggregate Operation Not Allowed")}, + {0x800400d5, i18n("Rights Expired")}, + {0x800400d6, i18n("Not Modified")}, + {0x800400d7, i18n("Forbidden")}, + {0x80040100, i18n("Audio Driver Error")}, + {0x80040101, i18n("Late Packet")}, + {0x80040102, i18n("Overlapped Packet")}, + {0x80040103, i18n("Outoforder Packet")}, + {0x80040104, i18n("Noncontiguous Packet")}, + {0x80040140, i18n("Open Not Processed")}, + {0x80040141, i18n("Windraw Exception")}, + {0x80040180, i18n("Expired")}, + {0x80040fc0, i18n("Invalid Interleaver")}, + {0x80040fc1, i18n("Bad Format")}, + {0x80040fc2, i18n("Chunk Missing")}, + {0x80040fc3, i18n("Invalid Stream")}, + {0x80040fc4, i18n("Dnr")}, + {0x80040fc5, i18n("Open Driver")}, + {0x80040fc6, i18n("Upgrade")}, + {0x80040fc7, i18n("Notification")}, + {0x80040fc8, i18n("Not Notified")}, + {0x80040fc9, i18n("Stopped")}, + {0x80040fca, i18n("Closed")}, + {0x80040fcb, i18n("Invalid Wav File")}, + {0x80040fcc, i18n("No Seek")}, + {0x80040200, i18n("Decode Inited")}, + {0x80040201, i18n("Decode Not Found")}, + {0x80040202, i18n("Decode Invalid")}, + {0x80040203, i18n("Decode Type Mismatch")}, + {0x80040204, i18n("Decode Init Failed")}, + {0x80040205, i18n("Decode Not Inited")}, + {0x80040206, i18n("Decode Decompress")}, + {0x80040207, i18n("Obsolete Version")}, + {0x00040208, i18n("Decode At End")}, + {0x80040240, i18n("Encode File Too Small")}, + {0x80040241, i18n("Encode Unknown File")}, + {0x80040242, i18n("Encode Bad Channels")}, + {0x80040243, i18n("Encode Bad Sampsize")}, + {0x80040244, i18n("Encode Bad Samprate")}, + {0x80040245, i18n("Encode Invalid")}, + {0x80040246, i18n("Encode No Output File")}, + {0x80040247, i18n("Encode No Input File")}, + {0x80040248, i18n("Encode No Output Permissions")}, + {0x80040249, i18n("Encode Bad Filetype")}, + {0x8004024a, i18n("Encode Invalid Video")}, + {0x8004024b, i18n("Encode Invalid Audio")}, + {0x8004024c, i18n("Encode No Video Capture")}, + {0x8004024d, i18n("Encode Invalid Video Capture")}, + {0x8004024e, i18n("Encode No Audio Capture")}, + {0x8004024f, i18n("Encode Invalid Audio Capture")}, + {0x80040250, i18n("Encode Too Slow For Live")}, + {0x80040251, i18n("Encode Engine Not Initialized")}, + {0x80040252, i18n("Encode Codec Not Found")}, + {0x80040253, i18n("Encode Codec Not Initialized")}, + {0x80040254, i18n("Encode Invalid Input Dimensions")}, + {0x80040255, i18n("Encode Message Ignored")}, + {0x80040256, i18n("Encode No Settings")}, + {0x80040257, i18n("Encode No Output Types")}, + {0x80040258, i18n("Encode Improper State")}, + {0x80040259, i18n("Encode Invalid Server")}, + {0x8004025a, i18n("Encode Invalid Temp Path")}, + {0x8004025b, i18n("Encode Merge Fail")}, + {0x0004025c, i18n("Binary Data Not Found")}, + {0x0004025d, i18n("Binary End Of Data")}, + {0x8004025e, i18n("Binary Data Purged")}, + {0x8004025f, i18n("Binary Full")}, + {0x80040260, i18n("Binary Offset Past End")}, + {0x80040261, i18n("Encode No Encoded Data")}, + {0x80040262, i18n("Encode Invalid Dll")}, + {0x80040263, i18n("Not Indexable")}, + {0x80040264, i18n("Encode No Browser")}, + {0x80040265, i18n("Encode No File To Server")}, + {0x80040266, i18n("Encode Insufficient Disk Space")}, + {0x00040267, i18n("Encode Sample Discarded")}, + {0x80040268, i18n("Encode Rv10 Frame Too Large")}, + {0x00040269, i18n("Not Handled")}, + {0x0004026a, i18n("End Of Stream")}, + {0x0004026b, i18n("Jobfile Incomplete")}, + {0x0004026c, i18n("Nothing To Serialize")}, + {0x8004026d, i18n("Sizenotset")}, + {0x8004026e, i18n("Already Committed")}, + {0x8004026f, i18n("Buffers Outstanding")}, + {0x80040270, i18n("Not Committed")}, + {0x80040271, i18n("Sample Time Not Set")}, + {0x80040272, i18n("Timeout")}, + {0x80040273, i18n("Wrongstate")}, + {0x800403c1, i18n("Remote Usage Error")}, + {0x800403c2, i18n("Remote Invalid Endtime")}, + {0x800403c3, i18n("Remote Missing Input File")}, + {0x800403c4, i18n("Remote Missing Output File")}, + {0x800403c5, i18n("Remote Input Equals Output File")}, + {0x800403c6, i18n("Remote Unsupported Audio Version")}, + {0x800403c7, i18n("Remote Different Audio")}, + {0x800403c8, i18n("Remote Different Video")}, + {0x800403c9, i18n("Remote Paste Missing Stream")}, + {0x800403ca, i18n("Remote End Of Stream")}, + {0x800403cb, i18n("Remote Image Map Parse Error")}, + {0x800403cc, i18n("Remote Invalid Imagemap File")}, + {0x800403cd, i18n("Remote Event Parse Error")}, + {0x800403ce, i18n("Remote Invalid Event File")}, + {0x800403cf, i18n("Remote Invalid Output File")}, + {0x800403d0, i18n("Remote Invalid Duration")}, + {0x800403d1, i18n("Remote No Dump Files")}, + {0x800403d2, i18n("Remote No Event Dump File")}, + {0x800403d3, i18n("Remote No Imap Dump File")}, + {0x800403d4, i18n("Remote No Data")}, + {0x800403d5, i18n("Remote Empty Stream")}, + {0x800403d6, i18n("Remote Read Only File")}, + {0x800403d7, i18n("Remote Paste Missing Audio Stream")}, + {0x800403d8, i18n("Remote Paste Missing Video Stream")}, + {0x800403d9, i18n("Remote Encrypted Content")}, + {0x80040281, i18n("Property Not Found")}, + {0x80040282, i18n("Property Not Composite")}, + {0x80040283, i18n("Property Duplicate")}, + {0x80040284, i18n("Property Type Mismatch")}, + {0x80040285, i18n("Property Active")}, + {0x80040286, i18n("Property Inactive")}, + {0x80040287, i18n("Property Value Underflow")}, + {0x80040288, i18n("Property Value Overflow")}, + {0x80040289, i18n("Property Value less than Lower bound")}, + {0x8004028a, i18n("Property Value greater than Upper bound")}, + {0x0004028b, i18n("Property Delete Pending")}, + {0x800401c1, i18n("Could not initialize core")}, + {0x800401c2, i18n("Perfectplay Not Supported")}, + {0x800401c3, i18n("No Live Perfectplay")}, + {0x800401c4, i18n("Perfectplay Not Allowed")}, + {0x800401c5, i18n("No Codecs")}, + {0x800401c6, i18n("Slow Machine")}, + {0x800401c7, i18n("Force Perfectplay")}, + {0x800401c8, i18n("Invalid Http Proxy Host")}, + {0x800401c9, i18n("Invalid Metafile")}, + {0x800401ca, i18n("Browser Launch")}, + {0x800401cb, i18n("View Source Noclip")}, + {0x800401cc, i18n("View Source Disabled")}, + {0x800401ce, i18n("Timeline Suspended")}, + {0x800401cf, i18n("Buffer Not Available")}, + {0x800401d0, i18n("Could Not Display")}, + {0x800401d1, i18n("Vsrc Disabled")}, + {0x800401d2, i18n("Vsrc Noclip")}, + {0x80040301, i18n("Resource Not Cached")}, + {0x80040302, i18n("Resource Not Found")}, + {0x80040303, i18n("Resource Close File First")}, + {0x80040304, i18n("Resource Nodata")}, + {0x80040305, i18n("Resource Badfile")}, + {0x80040306, i18n("Resource Partialcopy")}, + {0x800402c0, i18n("PayPerView No User")}, + {0x800402c1, i18n("PayPerView Guid Read Only")}, + {0x800402c2, i18n("PayPerView Guid Collision")}, + {0x800402c3, i18n("Register Guid Exists")}, + {0x800402c4, i18n("PayPerView Authorization Failed")}, + {0x800402c5, i18n("PayPerView Old Player")}, + {0x800402c6, i18n("PayPerView Account Locked")}, + {0x800402c8, i18n("Xr PayPerView Protocol Ignores")}, + {0x800402c9, i18n("PayPerView User Already Exists")}, + {0x80040340, i18n("Upg Auth Failed")}, + {0x80040341, i18n("Upg Cert Auth Failed")}, + {0x80040342, i18n("Upg Cert Expired")}, + {0x80040343, i18n("Upg Cert Revoked")}, + {0x80040344, i18n("Upg Rup Bad")}, + {0x80040345, i18n("Upg System Busy")}, + {0x80041800, i18n("Autocfg Success")}, + {0x80041901, i18n("No Error")}, + {0x80041902, i18n("Invalid Version")}, + {0x80041903, i18n("Invalid Format")}, + {0x80041904, i18n("Invalid Bandwidth")}, + {0x80041905, i18n("Invalid Path")}, + {0x80041906, i18n("Unknown Path")}, + {0x80041907, i18n("Invalid Protocol")}, + {0x80041908, i18n("Invalid Player Addr")}, + {0x80041909, i18n("Local Streams Prohibited")}, + {0x8004190a, i18n("Server Full")}, + {0x8004190b, i18n("Remote Streams Prohibited")}, + {0x8004190c, i18n("Event Streams Prohibited")}, + {0x8004190d, i18n("Invalid Host")}, + {0x8004190e, i18n("No Codec")}, + {0x8004190f, i18n("Livefile Invalid Bwn")}, + {0x80041910, i18n("Unable To Fulfill")}, + {0x80041911, i18n("Multicast Delivery Only")}, + {0x80041912, i18n("License Exceeded")}, + {0x80041913, i18n("License Unavailable")}, + {0x80041914, i18n("Invalid Loss Correction")}, + {0x80041915, i18n("Protocol Failure")}, + {0x80041916, i18n("Realvideo Streams Prohibited")}, + {0x80041917, i18n("Realaudio Streams Prohibited")}, + {0x80041918, i18n("Datatype Unsupported")}, + {0x80041919, i18n("Datatype Unlicensed")}, + {0x8004191a, i18n("Restricted Player")}, + {0x8004191b, i18n("Stream Initializing")}, + {0x8004191c, i18n("Invalid Player")}, + {0x8004191d, i18n("Player Plus Only")}, + {0x8004191e, i18n("No Embedded Players")}, + {0x8004191f, i18n("Pna Prohibited")}, + {0x80041920, i18n("Authentication Unsupported")}, + {0x80041921, i18n("Max Failed Authentications")}, + {0x80041922, i18n("Authentication Access Denied")}, + {0x80041923, i18n("Authentication Uuid Read Only")}, + {0x80041924, i18n("Authentication Uuid Not Unique")}, + {0x80041925, i18n("Authentication No Such User")}, + {0x80041926, i18n("Authentication Registration Succeeded")}, + {0x80041927, i18n("Authentication Registration Failed")}, + {0x80041928, i18n("Authentication Registration Guid Required")}, + {0x80041929, i18n("Authentication Unregistered Player")}, + {0x8004192a, i18n("Authentication Time Expired")}, + {0x8004192b, i18n("Authentication No Time Left")}, + {0x8004192c, i18n("Authentication Account Locked")}, + {0x8004192d, i18n("Authentication Invalid Server Cfg")}, + {0x8004192e, i18n("No Mobile Download")}, + {0x8004192f, i18n("No More Multi Addr")}, + {0x80041930, i18n("Proxy Max Connections")}, + {0x80041931, i18n("Proxy Max Gw Bandwidth")}, + {0x80041932, i18n("Proxy Max Bandwidth")}, + {0x80041933, i18n("Bad Loadtest Password")}, + {0x80041934, i18n("Pna Not Supported")}, + {0x80041935, i18n("Proxy Origin Disconnected")}, + {0x80041936, i18n("Internal Error")}, + {0x80041937, i18n("Max Value")}, + {0x80040600, i18n("Socket Intr")}, + {0x80040601, i18n("Socket Badf")}, + {0x80040602, i18n("Socket Acces")}, + {0x80040603, i18n("Socket Fault")}, + {0x80040604, i18n("Socket Inval")}, + {0x80040605, i18n("Socket Mfile")}, + {0x80040606, i18n("Socket Wouldblock")}, + {0x80040607, i18n("Socket Inprogress")}, + {0x80040608, i18n("Socket Already")}, + {0x80040609, i18n("Socket Notsock")}, + {0x8004060a, i18n("Socket Destaddrreq")}, + {0x8004060b, i18n("Socket Msgsize")}, + {0x8004060c, i18n("Socket Prototype")}, + {0x8004060d, i18n("Socket Noprotoopt")}, + {0x8004060e, i18n("Socket Protonosupport")}, + {0x8004060f, i18n("Socket Socktnosupport")}, + {0x80040610, i18n("Socket Opnotsupp")}, + {0x80040611, i18n("Socket Pfnosupport")}, + {0x80040612, i18n("Socket Afnosupport")}, + {0x80040613, i18n("Socket Addrinuse")}, + {0x80040614, i18n("Socket Address Not Available")}, + {0x80040615, i18n("Socket Net Down")}, + {0x80040616, i18n("Socket Net Unreachable")}, + {0x80040617, i18n("Socket Net Reset")}, + {0x80040618, i18n("Socket Connection Aborted")}, + {0x80040619, i18n("Socket Connection Reset")}, + {0x8004061a, i18n("Socket No buffers")}, + {0x8004061b, i18n("Socket Isconnected")}, + {0x8004061c, i18n("Socket Notconn")}, + {0x8004061d, i18n("Socket Shutdown")}, + {0x8004061e, i18n("Socket Too Many References")}, + {0x8004061f, i18n("Socket Timedout")}, + {0x80040620, i18n("Socket Connection Refused")}, + {0x80040621, i18n("Socket Loop")}, + {0x80040622, i18n("Socket Name too long")}, + {0x80040623, i18n("Socket Hostdown")}, + {0x80040624, i18n("Socket Hostunreach")}, + {0x80040625, i18n("Socket Pipe")}, + {0x80040626, i18n("Socket Endstream")}, + {0x00040627, i18n("Socket Buffered")}, + {0x80040640, i18n("Resolve Noname")}, + {0x80040641, i18n("Resolve Nodata")}, + {0, 0} + }; + + +class HelixErrorsBase +{ +public: + HelixErrorsBase(); + ~HelixErrorsBase(); + + QString *errorText(unsigned long code); + +private: + std::map<unsigned long, QString *> m_errors; + int m_nerrors; +}; + + +HelixErrorsBase *HelixErrors::m_base = new HelixErrorsBase(); + +QString *HelixErrors::errorText(unsigned long code) +{ + return m_base->errorText(code); +} + +HelixErrorsBase::HelixErrorsBase() : m_nerrors(0) +{ + while (helixErrors[m_nerrors].code) m_nerrors++; + + for (int i=0; i<m_nerrors; i++) + m_errors[helixErrors[i].code] = new QString(helixErrors[i].error_string); +} + +HelixErrorsBase::~HelixErrorsBase() +{ + for (int i=0; i<m_nerrors; i++) + delete m_errors[helixErrors[i].code]; +} + +QString *HelixErrorsBase::errorText(unsigned long code) +{ + if (m_errors.count(code)) + return m_errors[code]; + else + return 0; +} diff --git a/amarok/src/engine/helix/helix-errors.h b/amarok/src/engine/helix/helix-errors.h new file mode 100644 index 00000000..f53f6deb --- /dev/null +++ b/amarok/src/engine/helix/helix-errors.h @@ -0,0 +1,27 @@ +/*************************************************************************** + * Copyright (C) 2005 Paul Cifarelli * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +#ifndef _HELIX_ERRORS_INCLUDED +#define _HELIX_ERRORS_INCLUDED + +class QString; +class HelixErrorsBase; + +class HelixErrors +{ +public: + static QString *errorText(unsigned long code); + +private: + HelixErrors(); + static HelixErrorsBase *m_base; +}; + + +#endif diff --git a/amarok/src/engine/helix/helix-sp/Makefile.am b/amarok/src/engine/helix/helix-sp/Makefile.am new file mode 100644 index 00000000..436410a9 --- /dev/null +++ b/amarok/src/engine/helix/helix-sp/Makefile.am @@ -0,0 +1,38 @@ +noinst_LTLIBRARIES = libhelix-sp.la + +# must disable HX_LOG_SUBSYSTEM before commiting!! +# -DHX_LOG_SUBSYSTEM + +AM_CPPFLAGS = \ + -D_UNIX \ + -Wall -Wreturn-type -DHELIX_CONFIG_DISABLE_ATOMIC_OPERATORS \ + -I$(top_srcdir)/amarok/src/engine \ + -I$(top_srcdir)/amarok/src/ \ + -I$(srcdir)/helix-include/runtime \ + -I$(srcdir)/helix-include/audio/fixptutil \ + -I$(srcdir)/helix-include/common/include \ + -I$(srcdir)/helix-include/client/include \ + -I$(srcdir)/helix-include/common/container \ + -I$(srcdir)/helix-include/common/system \ + -I$(srcdir)/helix-include/common/dbgtool \ + -I$(srcdir)/helix-include/common/util \ + -I$(srcdir)/helix-include/common/log \ + -include $(srcdir)/helixdefines.h \ + $(all_includes) + +libhelix_sp_la_SOURCES = \ + hspcontext.cpp \ + hspadvisesink.cpp \ + hsperror.cpp \ + hspauthmgr.cpp \ + hsphook.cpp \ + iids.cpp \ + utils.cpp \ + gain.cpp \ + hspalsadevice.cpp \ + helix-sp.cpp + +METASOURCES = \ + AUTO + +KDE_OPTIONS=nofinal \ No newline at end of file diff --git a/amarok/src/engine/helix/helix-sp/Makefile.test b/amarok/src/engine/helix/helix-sp/Makefile.test new file mode 100644 index 00000000..6c49f458 --- /dev/null +++ b/amarok/src/engine/helix/helix-sp/Makefile.test @@ -0,0 +1,388 @@ +DEFINES=-D_UNIX -DTEST_APP + +RM=rm -f + +RM_DIR=rm -rf + +MAKE_DEP= + +MAKE_DEP_FLAGS=$(INCLUDES) $(DEFINES) + +RANLIB=ranlib + +CP=cp + +MAKE=make + +CC=gcc + +AR=ar + +CCFLAGS=-pipe -Wall -Wreturn-type -fno-exceptions -march=pentium -mcpu=pentium -O2 $(INCLUDES) $(DEFINES) + +CXX=g++ + +CXXFLAGS=-g -fpic -pipe -Wall -Wreturn-type -fno-exceptions --permissive -fno-rtti -Wno-ctor-dtor-privacy -march=pentium -mcpu=pentium -O2 $(INCLUDES) $(DEFINES) + +COMMON_SRCS=hspcontext.cpp hspadvisesink.cpp hsperror.cpp hspauthmgr.cpp iids.cpp print.cpp utils.cpp +LIB_SRCS=helix-sp.cpp $(COMMON_SRCS) +TESTAPP_SRCS =helix-sp.cpp $(COMMON_SRCS) + +# sources for make depend: +SRC=$(LIB_SRCS) + +COMMON_OBJS=$(COMMON_SRCS:.cpp=.o) +LIB_OBJS=$(LIB_SRCS:.cpp=.o) +TESTAPP_OBJS=splay.o + +COMPILED_OBJS=splay.o helix-sp.o $(COMMON_SRCS:.cpp=.o) + +INCLUDES= \ + -I/usr/X11R6/include \ + -Ihelix-include/runtime \ + -Ihelix-include/common/include \ + -Ihelix-include/client/include \ + -Ihelix-include/common/container \ + -Ihelix-include/common/system \ + -Ihelix-include/common/dbgtool \ + -Ihelix-include/common/util \ + -I. -include helixdefines.h + + +DYNAMIC_LIBS=-lstdc++ -lX11 -ldl -lm -lpthread + +.SUFFIXES: .cpp .so + +.c.o: + $(CC) $(CCFLAGS) -o $@ -c $< + +.cpp.o: + $(CXX) $(CXXFLAGS) -o $@ -c $< + + +PROGRAM=splay +LIBRARY=libhelix-sp.a +#SLIBRARY=libhelix-simpleplayer.so + +all: $(LIBRARY) $(PROGRAM) $(COMPILED_OBJS) + +#$(SLIBRARY): $(LIB_OBJS) $(STATIC_LIBS) +# $(CXX) -shared -o $(SLIBRARY) $(LIB_OBJS) $(STATIC_LIBS) -L/usr/X11R6/lib $(DYNAMIC_LIBS) + +$(LIBRARY): $(LIB_OBJS) $(STATIC_LIBS) + $(AR) r $(LIBRARY) $(LIB_OBJS) + +$(PROGRAM): $(TESTAPP_OBJS) $(STATIC_LIBS) + $(CXX) -o $(PROGRAM) $(TESTAPP_OBJS) $(LIBRARY) -L/usr/X11R6/lib $(DYNAMIC_LIBS) + +helix-sp.o: helix-sp.cpp + $(CXX) $(CXXFLAGS) -o helix-sp.o -c helix-sp.cpp + +splay.o: helix-sp.cpp + $(CXX) $(CXXFLAGS) -DTEST_APP -o splay.o -c helix-sp.cpp + +$(COMMON_OBJ): $(COMMON_SRC) + +clean: + $(RM) $(PROGRAM) $(SLIBRARY) $(LIBRARY) $(COMPILED_OBJS) *~ + +depend: + makedepend $(DEFINES) $(INCLUDES) -o.o -- $(SRC) + +# DO NOT DELETE THIS LINE -- make depend depends on it + +helix-sp.o: helixdefines.h /usr/include/stdlib.h /usr/include/features.h +helix-sp.o: /usr/include/sys/cdefs.h /usr/include/gnu/stubs.h +helix-sp.o: /usr/lib/gcc/i386-redhat-linux/3.4.3/include/stddef.h +helix-sp.o: /usr/include/bits/waitflags.h /usr/include/bits/waitstatus.h +helix-sp.o: /usr/include/endian.h /usr/include/bits/endian.h +helix-sp.o: /usr/include/xlocale.h /usr/include/sys/types.h +helix-sp.o: /usr/include/bits/types.h /usr/include/bits/wordsize.h +helix-sp.o: /usr/include/bits/typesizes.h /usr/include/time.h +helix-sp.o: /usr/include/sys/select.h /usr/include/bits/select.h +helix-sp.o: /usr/include/bits/sigset.h /usr/include/bits/time.h +helix-sp.o: /usr/include/sys/sysmacros.h /usr/include/bits/pthreadtypes.h +helix-sp.o: /usr/include/bits/sched.h /usr/include/alloca.h +helix-sp.o: helix-include/common/include/hxcomm.h +helix-sp.o: helix-include/common/include/hxengin.h +helix-sp.o: helix-include/common/include/hxcom.h +helix-sp.o: helix-include/common/include/hxtypes.h /usr/include/sys/param.h +helix-sp.o: /usr/include/limits.h +helix-sp.o: /usr/lib/gcc/i386-redhat-linux/3.4.3/include/limits.h +helix-sp.o: /usr/include/bits/posix1_lim.h /usr/include/bits/local_lim.h +helix-sp.o: /usr/include/linux/limits.h /usr/include/bits/posix2_lim.h +helix-sp.o: /usr/include/bits/xopen_lim.h /usr/include/bits/stdio_lim.h +helix-sp.o: /usr/include/linux/param.h /usr/include/asm/param.h +helix-sp.o: /usr/include/unistd.h /usr/include/bits/posix_opt.h +helix-sp.o: /usr/include/bits/environments.h /usr/include/bits/confname.h +helix-sp.o: /usr/include/getopt.h helix-include/common/include/hxresult.h +helix-sp.o: helix-include/runtime/hlxclib/memory.h /usr/include/memory.h +helix-sp.o: /usr/include/string.h helix-include/common/include/atomicbase.h +helix-sp.o: helix-include/common/include/ihxpckts.h +helix-sp.o: helix-include/common/include/hxvalue.h +helix-sp.o: helix-include/common/include/hxccf.h +helix-sp.o: helix-include/common/include/hxcore.h /usr/include/sys/time.h +helix-sp.o: helix-include/common/include/hxwin.h +helix-sp.o: helix-include/client/include/hxclsnk.h +helix-sp.o: helix-include/common/include/hxerror.h +helix-sp.o: helix-include/common/include/hxauth.h +helix-sp.o: helix-include/common/include/hxprefs.h +helix-sp.o: helix-include/common/util/hxstrutl.h +helix-sp.o: helix-include/runtime/hlxclib/string.h /usr/include/strings.h +helix-sp.o: helix-include/runtime/hlxclib/stdlib.h +helix-sp.o: helix-include/runtime/safestring.h /usr/include/ctype.h +helix-sp.o: helix-include/common/include/hxvsrc.h hspadvisesink.h hsperror.h +helix-sp.o: hspauthmgr.h hspcontext.h print.h /usr/X11R6/include/X11/Xlib.h +helix-sp.o: /usr/X11R6/include/X11/X.h /usr/X11R6/include/X11/Xfuncproto.h +helix-sp.o: /usr/X11R6/include/X11/Xosdefs.h /usr/include/dlfcn.h +helix-sp.o: /usr/include/bits/dlfcn.h /usr/include/errno.h +helix-sp.o: /usr/include/bits/errno.h /usr/include/linux/errno.h +helix-sp.o: /usr/include/asm/errno.h helix-include/common/include/hxausvc.h +helix-sp.o: helix-include/common/system/dllpath.h +helix-sp.o: helix-include/common/container/hxmap.h +helix-sp.o: helix-include/common/container/chxmapptrtoptr.h +helix-sp.o: helix-include/common/container/carray.h +helix-sp.o: helix-include/common/dbgtool/hxassert.h +helix-sp.o: helix-include/runtime/hlxclib/assert.h /usr/include/assert.h +helix-sp.o: helix-include/runtime/hlxclib/limits.h +helix-sp.o: helix-include/runtime/hlxclib/stdio.h /usr/include/stdio.h +helix-sp.o: /usr/include/libio.h /usr/include/_G_config.h +helix-sp.o: /usr/include/wchar.h /usr/include/bits/wchar.h +helix-sp.o: /usr/include/gconv.h +helix-sp.o: /usr/lib/gcc/i386-redhat-linux/3.4.3/include/stdarg.h +helix-sp.o: /usr/include/bits/sys_errlist.h /usr/include/signal.h +helix-sp.o: /usr/include/bits/signum.h /usr/include/bits/siginfo.h +helix-sp.o: /usr/include/bits/sigaction.h /usr/include/bits/sigcontext.h +helix-sp.o: /usr/include/asm/sigcontext.h /usr/include/bits/sigstack.h +helix-sp.o: /usr/include/sys/ucontext.h /usr/include/bits/sigthread.h +helix-sp.o: helix-include/common/container/hxstring.h +helix-sp.o: helix-include/common/container/hxmaputils.h +helix-sp.o: helix-include/common/container/chxmapbuckets.h +helix-sp.o: helix-include/common/container/chxmapstringtoob.h +helix-sp.o: helix-include/common/container/chxmapstringtostring.h +helix-sp.o: helix-include/common/container/chxmaplongtoobj.h helix-sp.h +helix-sp.o: hspvoladvise.h utils.h +hspcontext.o: helixdefines.h helix-include/common/container/hxbuffer.h +hspcontext.o: helix-include/common/include/ihxpckts.h +hspcontext.o: helix-include/common/include/hxvalue.h +hspcontext.o: helix-include/common/include/hxcom.h +hspcontext.o: helix-include/common/include/hxtypes.h /usr/include/sys/param.h +hspcontext.o: /usr/include/limits.h /usr/include/features.h +hspcontext.o: /usr/include/sys/cdefs.h /usr/include/gnu/stubs.h +hspcontext.o: /usr/lib/gcc/i386-redhat-linux/3.4.3/include/limits.h +hspcontext.o: /usr/include/bits/posix1_lim.h /usr/include/bits/local_lim.h +hspcontext.o: /usr/include/linux/limits.h /usr/include/bits/posix2_lim.h +hspcontext.o: /usr/include/bits/xopen_lim.h /usr/include/bits/stdio_lim.h +hspcontext.o: /usr/include/linux/param.h /usr/include/asm/param.h +hspcontext.o: /usr/include/unistd.h /usr/include/bits/posix_opt.h +hspcontext.o: /usr/include/bits/environments.h /usr/include/bits/types.h +hspcontext.o: /usr/include/bits/wordsize.h +hspcontext.o: /usr/lib/gcc/i386-redhat-linux/3.4.3/include/stddef.h +hspcontext.o: /usr/include/bits/typesizes.h /usr/include/bits/confname.h +hspcontext.o: /usr/include/getopt.h /usr/include/sys/types.h +hspcontext.o: /usr/include/time.h /usr/include/endian.h +hspcontext.o: /usr/include/bits/endian.h /usr/include/sys/select.h +hspcontext.o: /usr/include/bits/select.h /usr/include/bits/sigset.h +hspcontext.o: /usr/include/bits/time.h /usr/include/sys/sysmacros.h +hspcontext.o: /usr/include/bits/pthreadtypes.h /usr/include/bits/sched.h +hspcontext.o: helix-include/common/include/hxresult.h +hspcontext.o: helix-include/runtime/hlxclib/memory.h /usr/include/memory.h +hspcontext.o: /usr/include/string.h /usr/include/xlocale.h +hspcontext.o: helix-include/common/include/atomicbase.h +hspcontext.o: helix-include/common/container/hxstring.h +hspcontext.o: helix-include/common/dbgtool/hxassert.h +hspcontext.o: helix-include/runtime/hlxclib/assert.h /usr/include/assert.h +hspcontext.o: helix-include/runtime/hlxclib/limits.h +hspcontext.o: helix-include/runtime/hlxclib/stdio.h /usr/include/stdio.h +hspcontext.o: /usr/include/libio.h /usr/include/_G_config.h +hspcontext.o: /usr/include/wchar.h /usr/include/bits/wchar.h +hspcontext.o: /usr/include/gconv.h +hspcontext.o: /usr/lib/gcc/i386-redhat-linux/3.4.3/include/stdarg.h +hspcontext.o: /usr/include/bits/sys_errlist.h /usr/include/signal.h +hspcontext.o: /usr/include/bits/signum.h /usr/include/bits/siginfo.h +hspcontext.o: /usr/include/bits/sigaction.h /usr/include/bits/sigcontext.h +hspcontext.o: /usr/include/asm/sigcontext.h /usr/include/bits/sigstack.h +hspcontext.o: /usr/include/sys/ucontext.h /usr/include/bits/sigthread.h +hspcontext.o: /usr/include/stdlib.h /usr/include/bits/waitflags.h +hspcontext.o: /usr/include/bits/waitstatus.h /usr/include/alloca.h +hspcontext.o: helix-include/runtime/hlxclib/string.h /usr/include/strings.h +hspcontext.o: helix-include/common/util/hxmangle.h +hspcontext.o: helix-include/client/include/hxclsnk.h +hspcontext.o: helix-include/common/include/hxerror.h +hspcontext.o: helix-include/common/include/hxprefs.h hspadvisesink.h +hspcontext.o: hsperror.h hspauthmgr.h helix-include/common/include/hxauth.h +hspcontext.o: hspcontext.h helix-include/common/include/hxausvc.h helix-sp.h +hspcontext.o: utils.h +hspadvisesink.o: helixdefines.h /usr/include/stdio.h /usr/include/features.h +hspadvisesink.o: /usr/include/sys/cdefs.h /usr/include/gnu/stubs.h +hspadvisesink.o: /usr/lib/gcc/i386-redhat-linux/3.4.3/include/stddef.h +hspadvisesink.o: /usr/include/bits/types.h /usr/include/bits/wordsize.h +hspadvisesink.o: /usr/include/bits/typesizes.h /usr/include/libio.h +hspadvisesink.o: /usr/include/_G_config.h /usr/include/wchar.h +hspadvisesink.o: /usr/include/bits/wchar.h /usr/include/gconv.h +hspadvisesink.o: /usr/lib/gcc/i386-redhat-linux/3.4.3/include/stdarg.h +hspadvisesink.o: /usr/include/bits/stdio_lim.h +hspadvisesink.o: /usr/include/bits/sys_errlist.h +hspadvisesink.o: helix-include/common/include/hxcomm.h +hspadvisesink.o: helix-include/common/include/hxengin.h +hspadvisesink.o: helix-include/common/include/hxcom.h +hspadvisesink.o: helix-include/common/include/hxtypes.h +hspadvisesink.o: /usr/include/sys/param.h /usr/include/limits.h +hspadvisesink.o: /usr/lib/gcc/i386-redhat-linux/3.4.3/include/limits.h +hspadvisesink.o: /usr/include/bits/posix1_lim.h /usr/include/bits/local_lim.h +hspadvisesink.o: /usr/include/linux/limits.h /usr/include/bits/posix2_lim.h +hspadvisesink.o: /usr/include/bits/xopen_lim.h /usr/include/linux/param.h +hspadvisesink.o: /usr/include/asm/param.h /usr/include/unistd.h +hspadvisesink.o: /usr/include/bits/posix_opt.h +hspadvisesink.o: /usr/include/bits/environments.h +hspadvisesink.o: /usr/include/bits/confname.h /usr/include/getopt.h +hspadvisesink.o: /usr/include/sys/types.h /usr/include/time.h +hspadvisesink.o: /usr/include/endian.h /usr/include/bits/endian.h +hspadvisesink.o: /usr/include/sys/select.h /usr/include/bits/select.h +hspadvisesink.o: /usr/include/bits/sigset.h /usr/include/bits/time.h +hspadvisesink.o: /usr/include/sys/sysmacros.h +hspadvisesink.o: /usr/include/bits/pthreadtypes.h /usr/include/bits/sched.h +hspadvisesink.o: helix-include/common/include/hxresult.h +hspadvisesink.o: helix-include/runtime/hlxclib/memory.h /usr/include/memory.h +hspadvisesink.o: /usr/include/string.h /usr/include/xlocale.h +hspadvisesink.o: helix-include/common/include/atomicbase.h +hspadvisesink.o: helix-include/common/include/ihxpckts.h +hspadvisesink.o: helix-include/common/include/hxvalue.h +hspadvisesink.o: helix-include/common/include/hxccf.h +hspadvisesink.o: helix-include/common/include/hxmon.h +hspadvisesink.o: helix-include/runtime/hlxclib/limits.h +hspadvisesink.o: helix-include/common/include/hxcore.h +hspadvisesink.o: /usr/include/sys/time.h helix-include/common/include/hxwin.h +hspadvisesink.o: helix-include/client/include/hxclsnk.h hspadvisesink.h +hspadvisesink.o: print.h helix-include/common/include/hxausvc.h helix-sp.h +hspadvisesink.o: utils.h +hsperror.o: helixdefines.h helix-include/common/include/hxcomm.h +hsperror.o: helix-include/common/include/hxengin.h +hsperror.o: helix-include/common/include/hxcom.h +hsperror.o: helix-include/common/include/hxtypes.h /usr/include/sys/param.h +hsperror.o: /usr/include/limits.h /usr/include/features.h +hsperror.o: /usr/include/sys/cdefs.h /usr/include/gnu/stubs.h +hsperror.o: /usr/lib/gcc/i386-redhat-linux/3.4.3/include/limits.h +hsperror.o: /usr/include/bits/posix1_lim.h /usr/include/bits/local_lim.h +hsperror.o: /usr/include/linux/limits.h /usr/include/bits/posix2_lim.h +hsperror.o: /usr/include/bits/xopen_lim.h /usr/include/bits/stdio_lim.h +hsperror.o: /usr/include/linux/param.h /usr/include/asm/param.h +hsperror.o: /usr/include/unistd.h /usr/include/bits/posix_opt.h +hsperror.o: /usr/include/bits/environments.h /usr/include/bits/types.h +hsperror.o: /usr/include/bits/wordsize.h +hsperror.o: /usr/lib/gcc/i386-redhat-linux/3.4.3/include/stddef.h +hsperror.o: /usr/include/bits/typesizes.h /usr/include/bits/confname.h +hsperror.o: /usr/include/getopt.h /usr/include/sys/types.h +hsperror.o: /usr/include/time.h /usr/include/endian.h +hsperror.o: /usr/include/bits/endian.h /usr/include/sys/select.h +hsperror.o: /usr/include/bits/select.h /usr/include/bits/sigset.h +hsperror.o: /usr/include/bits/time.h /usr/include/sys/sysmacros.h +hsperror.o: /usr/include/bits/pthreadtypes.h /usr/include/bits/sched.h +hsperror.o: helix-include/common/include/hxresult.h +hsperror.o: helix-include/runtime/hlxclib/memory.h /usr/include/memory.h +hsperror.o: /usr/include/string.h /usr/include/xlocale.h +hsperror.o: helix-include/common/include/atomicbase.h +hsperror.o: helix-include/common/include/ihxpckts.h +hsperror.o: helix-include/common/include/hxvalue.h +hsperror.o: helix-include/common/include/hxccf.h +hsperror.o: helix-include/common/include/hxerror.h +hsperror.o: helix-include/common/include/hxcore.h /usr/include/sys/time.h +hsperror.o: helix-include/common/include/hxwin.h +hsperror.o: helix-include/common/container/hxbuffer.h +hsperror.o: helix-include/common/container/hxstring.h +hsperror.o: helix-include/common/dbgtool/hxassert.h +hsperror.o: helix-include/runtime/hlxclib/assert.h /usr/include/assert.h +hsperror.o: helix-include/runtime/hlxclib/limits.h +hsperror.o: helix-include/runtime/hlxclib/stdio.h /usr/include/stdio.h +hsperror.o: /usr/include/libio.h /usr/include/_G_config.h +hsperror.o: /usr/include/wchar.h /usr/include/bits/wchar.h +hsperror.o: /usr/include/gconv.h +hsperror.o: /usr/lib/gcc/i386-redhat-linux/3.4.3/include/stdarg.h +hsperror.o: /usr/include/bits/sys_errlist.h /usr/include/signal.h +hsperror.o: /usr/include/bits/signum.h /usr/include/bits/siginfo.h +hsperror.o: /usr/include/bits/sigaction.h /usr/include/bits/sigcontext.h +hsperror.o: /usr/include/asm/sigcontext.h /usr/include/bits/sigstack.h +hsperror.o: /usr/include/sys/ucontext.h /usr/include/bits/sigthread.h +hsperror.o: /usr/include/stdlib.h /usr/include/bits/waitflags.h +hsperror.o: /usr/include/bits/waitstatus.h /usr/include/alloca.h +hsperror.o: helix-include/runtime/hlxclib/string.h /usr/include/strings.h +hsperror.o: hsperror.h print.h helix-include/common/include/hxausvc.h +hsperror.o: helix-sp.h utils.h +hspauthmgr.o: helixdefines.h /usr/include/stdio.h /usr/include/features.h +hspauthmgr.o: /usr/include/sys/cdefs.h /usr/include/gnu/stubs.h +hspauthmgr.o: /usr/lib/gcc/i386-redhat-linux/3.4.3/include/stddef.h +hspauthmgr.o: /usr/include/bits/types.h /usr/include/bits/wordsize.h +hspauthmgr.o: /usr/include/bits/typesizes.h /usr/include/libio.h +hspauthmgr.o: /usr/include/_G_config.h /usr/include/wchar.h +hspauthmgr.o: /usr/include/bits/wchar.h /usr/include/gconv.h +hspauthmgr.o: /usr/lib/gcc/i386-redhat-linux/3.4.3/include/stdarg.h +hspauthmgr.o: /usr/include/bits/stdio_lim.h /usr/include/bits/sys_errlist.h +hspauthmgr.o: helix-include/common/include/hxcom.h +hspauthmgr.o: helix-include/common/include/hxtypes.h /usr/include/sys/param.h +hspauthmgr.o: /usr/include/limits.h +hspauthmgr.o: /usr/lib/gcc/i386-redhat-linux/3.4.3/include/limits.h +hspauthmgr.o: /usr/include/bits/posix1_lim.h /usr/include/bits/local_lim.h +hspauthmgr.o: /usr/include/linux/limits.h /usr/include/bits/posix2_lim.h +hspauthmgr.o: /usr/include/bits/xopen_lim.h /usr/include/linux/param.h +hspauthmgr.o: /usr/include/asm/param.h /usr/include/unistd.h +hspauthmgr.o: /usr/include/bits/posix_opt.h /usr/include/bits/environments.h +hspauthmgr.o: /usr/include/bits/confname.h /usr/include/getopt.h +hspauthmgr.o: /usr/include/sys/types.h /usr/include/time.h +hspauthmgr.o: /usr/include/endian.h /usr/include/bits/endian.h +hspauthmgr.o: /usr/include/sys/select.h /usr/include/bits/select.h +hspauthmgr.o: /usr/include/bits/sigset.h /usr/include/bits/time.h +hspauthmgr.o: /usr/include/sys/sysmacros.h /usr/include/bits/pthreadtypes.h +hspauthmgr.o: /usr/include/bits/sched.h +hspauthmgr.o: helix-include/common/include/hxresult.h +hspauthmgr.o: helix-include/runtime/hlxclib/memory.h /usr/include/memory.h +hspauthmgr.o: /usr/include/string.h /usr/include/xlocale.h +hspauthmgr.o: helix-include/common/include/atomicbase.h +hspauthmgr.o: helix-include/common/include/hxauth.h hspauthmgr.h +hspauthmgr.o: /usr/include/ctype.h print.h +hspauthmgr.o: helix-include/common/include/hxausvc.h helix-sp.h utils.h +iids.o: helixdefines.h helix-include/common/include/hxtypes.h +iids.o: /usr/include/sys/param.h /usr/include/limits.h +iids.o: /usr/include/features.h /usr/include/sys/cdefs.h +iids.o: /usr/include/gnu/stubs.h +iids.o: /usr/lib/gcc/i386-redhat-linux/3.4.3/include/limits.h +iids.o: /usr/include/bits/posix1_lim.h /usr/include/bits/local_lim.h +iids.o: /usr/include/linux/limits.h /usr/include/bits/posix2_lim.h +iids.o: /usr/include/bits/xopen_lim.h /usr/include/bits/stdio_lim.h +iids.o: /usr/include/linux/param.h /usr/include/asm/param.h +iids.o: /usr/include/unistd.h /usr/include/bits/posix_opt.h +iids.o: /usr/include/bits/environments.h /usr/include/bits/types.h +iids.o: /usr/include/bits/wordsize.h +iids.o: /usr/lib/gcc/i386-redhat-linux/3.4.3/include/stddef.h +iids.o: /usr/include/bits/typesizes.h /usr/include/bits/confname.h +iids.o: /usr/include/getopt.h /usr/include/sys/types.h /usr/include/time.h +iids.o: /usr/include/endian.h /usr/include/bits/endian.h +iids.o: /usr/include/sys/select.h /usr/include/bits/select.h +iids.o: /usr/include/bits/sigset.h /usr/include/bits/time.h +iids.o: /usr/include/sys/sysmacros.h /usr/include/bits/pthreadtypes.h +iids.o: /usr/include/bits/sched.h helix-include/common/include/hxcom.h +iids.o: helix-include/common/include/hxresult.h +iids.o: helix-include/runtime/hlxclib/memory.h /usr/include/memory.h +iids.o: /usr/include/string.h /usr/include/xlocale.h +iids.o: helix-include/common/include/atomicbase.h +iids.o: helix-include/common/include/hxiids.h +iids.o: helix-include/common/include/hxpiids.h +print.o: helixdefines.h /usr/lib/gcc/i386-redhat-linux/3.4.3/include/stdarg.h +print.o: /usr/include/stdio.h /usr/include/features.h +print.o: /usr/include/sys/cdefs.h /usr/include/gnu/stubs.h +print.o: /usr/lib/gcc/i386-redhat-linux/3.4.3/include/stddef.h +print.o: /usr/include/bits/types.h /usr/include/bits/wordsize.h +print.o: /usr/include/bits/typesizes.h /usr/include/libio.h +print.o: /usr/include/_G_config.h /usr/include/wchar.h +print.o: /usr/include/bits/wchar.h /usr/include/gconv.h +print.o: /usr/include/bits/stdio_lim.h /usr/include/bits/sys_errlist.h +print.o: print.h +utils.o: helixdefines.h /usr/include/stdio.h /usr/include/features.h +utils.o: /usr/include/sys/cdefs.h /usr/include/gnu/stubs.h +utils.o: /usr/lib/gcc/i386-redhat-linux/3.4.3/include/stddef.h +utils.o: /usr/include/bits/types.h /usr/include/bits/wordsize.h +utils.o: /usr/include/bits/typesizes.h /usr/include/libio.h +utils.o: /usr/include/_G_config.h /usr/include/wchar.h +utils.o: /usr/include/bits/wchar.h /usr/include/gconv.h +utils.o: /usr/lib/gcc/i386-redhat-linux/3.4.3/include/stdarg.h +utils.o: /usr/include/bits/stdio_lim.h /usr/include/bits/sys_errlist.h +utils.o: /usr/include/string.h /usr/include/xlocale.h utils.h diff --git a/amarok/src/engine/helix/helix-sp/gain.cpp b/amarok/src/engine/helix/helix-sp/gain.cpp new file mode 100644 index 00000000..c219955b --- /dev/null +++ b/amarok/src/engine/helix/helix-sp/gain.cpp @@ -0,0 +1,517 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * + * This software is released under the provisions of the GPL version 2. + * see file "COPYING". If that file is not available, the full statement + * of the license can be found at + * + * http://www.fsf.org/licensing/licenses/gpl.txt + * + * Portions Copyright (c) 1995-2004 RealNetworks, Inc. All Rights Reserved. + * Copyright (c) 2005 Paul Cifarelli All Rights Reserved. + * + * + * This file is part of the Helix DNA Technology. RealNetworks is the + * developer of the Original Code and owns the copyrights in the + * portions it created. + * + * This file, and the files included with this file, is distributed + * and made available on an 'AS IS' basis, WITHOUT WARRANTY OF ANY + * KIND, EITHER EXPRESS OR IMPLIED, AND REALNETWORKS HEREBY DISCLAIMS + * ALL SUCH WARRANTIES, INCLUDING WITHOUT LIMITATION, ANY WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, QUIET + * ENJOYMENT OR NON-INFRINGEMENT. + * + * Technology Compatibility Kit Test Suite(s) Location: + * http://www.helixcommunity.org/content/tck + * + * ***** END LICENSE BLOCK ***** */ + +#include <stdlib.h> +#include <math.h> +#include <string.h> +#include <iostream> +#include "hxassert.h" + +#include "gain.h" + +using namespace std; + +#define INT8_CEILING 255 +#define INT16_CEILING 32767 +#define INT32_CEILING 65535 + +struct GAIN_STATE +{ + int sampleRate; + int nChannels; + int bytesPerSample; + bool isMute; + float instGain; /* gain applied right now */ + float tgtGain; /* in a smooth gain change, the gain we are aiming for */ + float decay; +}; + +GAIN_STATE* gainInit(int sampleRate, int nChannels, int bytesPerSample) +{ + GAIN_STATE* g = (GAIN_STATE*) calloc(1,sizeof(GAIN_STATE)) ; + if (g) + { + g->sampleRate = sampleRate; + g->nChannels = nChannels; + g->bytesPerSample = bytesPerSample; + gainSetTimeConstant(0.1f, g); + } + + return g ; +} + +void gainFree(GAIN_STATE* g) +{ + if (g) free(g) ; +} + +float gainSetSmoothdB(float dB, GAIN_STATE* g) +{ + float gain = pow(10.0, 0.05*dB) ; + + if (g) + { + g->isMute = false; + g->tgtGain = gain ; + } + + return dB ; +} + +float gainSetImmediatedB(float dB, GAIN_STATE* g) +{ + dB = gainSetSmoothdB(dB, g) ; + + if (g) + g->instGain = g->tgtGain ; // make it instantaneous + + return dB ; +} + +float gainSetSmooth(float percent, GAIN_STATE* g) +{ + float gaintop = pow(10.0, 0.05*GAIN_MAX_dB) ; + float gainbottom = pow(10.0, 0.05*GAIN_MIN_dB) ; + float gain = percent * (gaintop - gainbottom) + gainbottom; + + if (g) + { + g->isMute = false; + g->tgtGain = gain ; + } + + return gain; +} + +float gainSetImmediate(float percent, GAIN_STATE* g) +{ + float gain = gainSetSmooth(percent, g) ; + + if (g) + g->instGain = g->tgtGain ; // make it instantaneous + + return gain; +} + +void gainSetMute(GAIN_STATE* g) +{ + if (g) + { + g->isMute = true; + g->instGain = g->tgtGain = 0.0; // mute is immediate + } +} + +int gainSetTimeConstant(float millis, GAIN_STATE* g) +{ + if (!g) + return 0; + + // we define the time constant millis so that the signal has decayed to 1/2 (-6dB) after + // millis milliseconds have elapsed. + // Let T[sec] = millis/1000 = time constant in units of seconds + // + // => (1-2^-s)^(T[sec]*sr) = 1/2 + // => 1-2^-s = (1/2)^(1/(T[sec]*sr)) + // => 2^-s = 1 - (1/2)^(1/(T[sec]*sr)) + // => s = -log2(1 - (1/2)^(1 / (T[sec]*sr))) + + // first 0.5 is rounding constant + int shift; + shift = (int)(0.5 - 1.0/log(2.0)*log(1.0 - pow(0.5, 1000.0/(millis * g->sampleRate)))) ; + if (shift < 1) + shift = 1 ; + if (shift > 31) + shift = 31 ; + + g->decay = ::pow(2.0, (float) shift); + + return 1 ; // OK +} + +static void gainFeedMono(unsigned char* signal, unsigned char *outsignal, int len, GAIN_STATE *g) +{ + if (!g) + return; + + float tgtGain = g->tgtGain ; + float gain = g->instGain ; + unsigned char *bufferEnd = signal + len; + + if (gain == tgtGain) + { // steady state + while (signal < bufferEnd) + { + switch (g->bytesPerSample) + { + case 1: + { + short int res; + char *s = (char *) signal; + char *o = (char *) outsignal; + res = (short int) (*s * gain); + *o = (char) (res > INT8_CEILING ? INT8_CEILING : res); + } + break; + case 2: + { + long res; + short int *s = (short int *) signal; + short int *o = (short int *) outsignal; + res = (long) (*s * gain); + *o = (short int) (res > INT16_CEILING ? INT16_CEILING : res); + } + break; + case 4: + { + long long res; + long *s = (long *) signal; + long *o = (long *) outsignal; + res = (long long) (*s * gain); + *o = (long) (res > INT32_CEILING ? INT32_CEILING : res); + } + break; + default: + return; + } + signal += g->bytesPerSample; + outsignal += g->bytesPerSample; + } + } + else + { // while we are still ramping the gain + while (signal < bufferEnd) + { + switch (g->bytesPerSample) + { + case 1: + { + short int res; + char *s = (char *) signal; + char *o = (char *) outsignal; + res = (short int) (*s * gain); + *o = (char) (res > INT8_CEILING ? INT8_CEILING : res); + } + break; + case 2: + { + long res; + short int *s = (short int *) signal; + short int *o = (short int *) outsignal; + res = (long) (*s * gain); + *o = (short int) (res > INT16_CEILING ? INT16_CEILING : res); + } + break; + case 4: + { + long long res; + long *s = (long *) signal; + long *o = (long *) outsignal; + res = (long long) (*s * gain); + *o = (long) (res > INT32_CEILING ? INT32_CEILING : res); + } + break; + default: + return; + } + signal += g->bytesPerSample; + outsignal += g->bytesPerSample; + gain += ((tgtGain-gain) / g->decay); + } + g->instGain = gain ; + } +} + +static void gainFeedStereo(unsigned char* signal, unsigned char *outsignal, int len, GAIN_STATE *g) +{ + if (!g) + return; + + float tgtGain = g->tgtGain ; + float gain = g->instGain ; + unsigned char *bufferEnd = signal + len; + + if (gain == tgtGain) + { // steady state + while (signal < bufferEnd) + { + switch (g->bytesPerSample) + { + case 1: + { + short int res; + char *s = (char *) signal; + char *o = (char *) outsignal; + res = (short int) (*s * gain); + *o = (char) (res > INT8_CEILING ? INT8_CEILING : res); + s++; + o++; + res = (short int) (*s * gain); + *o = (char) (res > INT8_CEILING ? INT8_CEILING : res); + } + break; + case 2: + { + long res; + short int *s = (short int *) signal; + short int *o = (short int *) outsignal; + res = (long) (*s * gain); + *o = (short int) (res > INT16_CEILING ? INT16_CEILING : res); + s++; + o++; + res = (long) (*s * gain); + *o = (short int) (res > INT16_CEILING ? INT16_CEILING : res); + } + break; + case 4: + { + long long res; + long *s = (long *) signal; + long *o = (long *) outsignal; + res = (long long) (*s * gain); + *o = (long) (res > INT32_CEILING ? INT32_CEILING : res); + s++; + o++; + res = (long long) (*s * gain); + *o = (long) (res > INT32_CEILING ? INT32_CEILING : res); + } + break; + default: + return; + } + signal += 2 * g->bytesPerSample; + outsignal += 2 * g->bytesPerSample; + } + } + else + { // while we are still ramping the gain + while (signal < bufferEnd) + { + switch (g->bytesPerSample) + { + case 1: + { + short int res; + char *s = (char *) signal; + char *o = (char *) outsignal; + res = (short int) (*s * gain); + *o = (char) (res > INT8_CEILING ? INT8_CEILING : res); + s++; + o++; + res = (short int) (*s * gain); + *o = (char) (res > INT8_CEILING ? INT8_CEILING : res); + } + break; + case 2: + { + long res; + short int *s = (short int *) signal; + short int *o = (short int *) outsignal; + res = (long) (*s * gain); + *o = (short int) (res > INT16_CEILING ? INT16_CEILING : res); + s++; + o++; + res = (long) (*s * gain); + *o = (short int) (res > INT16_CEILING ? INT16_CEILING : res); + } + break; + case 4: + { + long long res; + long *s = (long *) signal; + long *o = (long *) outsignal; + res = (long long) (*s * gain); + *o = (long) (res > INT32_CEILING ? INT32_CEILING : res); + s++; + o++; + res = (long long) (*s * gain); + *o = (long) (res > INT32_CEILING ? INT32_CEILING : res); + } + break; + default: + return; + } + signal += 2 * g->bytesPerSample; + outsignal += 2 * g->bytesPerSample; + gain += ((tgtGain-gain) / g->decay); + } + g->instGain = gain ; + } +} + +static void gainFeedMulti(unsigned char* signal, unsigned char *outsignal, int len, GAIN_STATE *g) +{ + if (!g) + return; + + float tgtGain = g->tgtGain ; + float gain = g->instGain ; + unsigned char *bufferEnd = signal + len; + + if (gain == tgtGain) + { // steady state + while (signal < bufferEnd) + { + switch (g->bytesPerSample) + { + case 1: + { + short int res; + int i ; + char *s = (char *) signal; + char *o = (char *) outsignal; + for (i = 0 ; i < g->nChannels ; i++) + { + res = (short int) (*s * gain); + *o = (char) (res > INT8_CEILING ? INT8_CEILING : res); + s++; + o++; + } + } + break; + case 2: + { + long res; + int i ; + short int *s = (short int *) signal; + short int *o = (short int *) outsignal; + for (i = 0 ; i < g->nChannels ; i++) + { + res = (long) (*s * gain); + *o = (short int) (res > INT16_CEILING ? INT16_CEILING : res); + s++; + o++; + } + } + break; + case 4: + { + long long res; + int i ; + long *s = (long *) signal; + long *o = (long *) outsignal; + for (i = 0 ; i < g->nChannels ; i++) + { + res = (long long) (*s * gain); + *o = (long) (res > INT32_CEILING ? INT32_CEILING : res); + s++; + o++; + } + } + break; + default: + return; + } + signal += g->nChannels * g->bytesPerSample; + outsignal += g->nChannels * g->bytesPerSample; + } + } + else + { // while we are still ramping the gain + while (signal < bufferEnd) + { + int i ; + + switch (g->bytesPerSample) + { + case 1: + { + short int res; + char *s = (char *) signal; + char *o = (char *) outsignal; + for (i = 0 ; i < g->nChannels ; i++) + { + res = (short int) (*s * gain); + *o = (char) (res > INT8_CEILING ? INT8_CEILING : res); + s++; + o++; + } + } + break; + case 2: + { + long res; + short int *s = (short int *) signal; + short int *o = (short int *) outsignal; + for (i = 0 ; i < g->nChannels ; i++) + { + res = (long) (*s * gain); + *o = (short int) (res > INT16_CEILING ? INT16_CEILING : res); + s++; + o++; + } + } + case 4: + { + long long res; + long *s = (long *) signal; + long *o = (long *) outsignal; + for (i = 0 ; i < g->nChannels ; i++) + { + res = (long long) (*s * gain); + *o = (long) (res > INT32_CEILING ? INT32_CEILING : res); + s++; + o++; + } + } + break; + } + signal += g->nChannels * g->bytesPerSample; + outsignal += g->nChannels * g->bytesPerSample; + gain += ((tgtGain-gain) / g->decay); + } + g->instGain = gain ; + } +} + +void gainFeed(unsigned char* signal, unsigned char *outsignal, int len, GAIN_STATE* g) +{ + if (!g) + return; + + /* if the gain is 0dB, and we are not currently ramping, shortcut. */ + if (g->instGain == 1.0 && g->instGain == g->tgtGain) + { + if (signal != outsignal) + memcpy(outsignal, signal, len); + + return ; + } + switch (g->nChannels) + { + case 1: + gainFeedMono(signal, outsignal, len, g) ; + break ; + case 2: + gainFeedStereo(signal, outsignal, len, g) ; + break ; + default: + gainFeedMulti(signal, outsignal, len, g) ; + break ; + } +} diff --git a/amarok/src/engine/helix/helix-sp/gain.h b/amarok/src/engine/helix/helix-sp/gain.h new file mode 100644 index 00000000..d120a134 --- /dev/null +++ b/amarok/src/engine/helix/helix-sp/gain.h @@ -0,0 +1,58 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * + * This software is released under the provisions of the GPL version 2. + * see file "COPYING". If that file is not available, the full statement + * of the license can be found at + * + * http://www.fsf.org/licensing/licenses/gpl.txt + * + * Portions Copyright (c) 1995-2004 RealNetworks, Inc. All Rights Reserved. + * Copyright (c) 2005 Paul Cifarelli All Rights Reserved. + * + * + * This file is part of the Helix DNA Technology. RealNetworks is the + * developer of the Original Code and owns the copyrights in the + * portions it created. + * + * This file, and the files included with this file, is distributed + * and made available on an 'AS IS' basis, WITHOUT WARRANTY OF ANY + * KIND, EITHER EXPRESS OR IMPLIED, AND REALNETWORKS HEREBY DISCLAIMS + * ALL SUCH WARRANTIES, INCLUDING WITHOUT LIMITATION, ANY WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, QUIET + * ENJOYMENT OR NON-INFRINGEMENT. + * + * Technology Compatibility Kit Test Suite(s) Location: + * http://www.helixcommunity.org/content/tck + * + * ***** END LICENSE BLOCK ***** */ + +#ifndef _GAIN_H_ +#define _GAIN_H_ + +#include "hxtypes.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define GAIN_MAX_dB 0.0 +#define GAIN_MIN_dB -27.0 + +struct GAIN_STATE; +typedef struct GAIN_STATE GAIN_STATE; + +GAIN_STATE* gainInit(int sampleRate, int nChannels, int bytePerSample) ; +void gainFree(GAIN_STATE*) ; +float gainSetImmediatedB(float dB, GAIN_STATE*) ; +float gainSetSmoothdB(float dB, GAIN_STATE*); +float gainSetImmediate(float dB, GAIN_STATE*) ; +float gainSetSmooth(float dB, GAIN_STATE*); +void gainSetMute(GAIN_STATE* g); +int gainSetTimeConstant(float millis, GAIN_STATE*) ; +void gainFeed(unsigned char* signal, unsigned char *outsignal, int len, GAIN_STATE* g) ; + +#ifdef __cplusplus +} +#endif + +#endif /* _GAIN_H_ */ diff --git a/amarok/src/engine/helix/helix-sp/helix-include/client/include/hxclsnk.h b/amarok/src/engine/helix/helix-sp/helix-include/client/include/hxclsnk.h new file mode 100644 index 00000000..e79f7e90 --- /dev/null +++ b/amarok/src/engine/helix/helix-sp/helix-include/client/include/hxclsnk.h @@ -0,0 +1,230 @@ + +/* + * + * This software is released under the provisions of the GPL version 2. + * see file "COPYING". If that file is not available, the full statement + * of the license can be found at + * + * http://www.fsf.org/licensing/licenses/gpl.txt + * + * Portions Copyright (c) 1995-2004 RealNetworks, Inc. All Rights Reserved. + * + */ + +#ifndef _HXCLSNK_H_ +#define _HXCLSNK_H_ + +/* + * Forward declarations of some interfaces defined or used here-in. + */ +typedef _INTERFACE IHXClientAdviseSink IHXClientAdviseSink; +typedef _INTERFACE IHXRequest IHXRequest; + + +/**************************************************************************** + * + * Interface: + * + * IHXClientAdviseSink + * + * Purpose: + * + * Interface supplied by client to core to receive notifications of + * status changes. + * + * IID_IHXClientAdviseSink: + * + * {00000B00-0901-11d1-8B06-00A024406D59} + * + */ + +DEFINE_GUID(IID_IHXClientAdviseSink, 0x00000B00, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXClientAdviseSink + +DECLARE_INTERFACE_(IHXClientAdviseSink, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXClientAdviseSink methods + */ + + /************************************************************************ + * Method: + * IHXClientAdviseSink::OnPosLength + * Purpose: + * Called to advise the client that the position or length of the + * current playback context has changed. + */ + STDMETHOD(OnPosLength) (THIS_ + UINT32 ulPosition, + UINT32 ulLength) PURE; + + /************************************************************************ + * Method: + * IHXClientAdviseSink::OnPresentationOpened + * Purpose: + * Called to advise the client a presentation has been opened. + */ + STDMETHOD(OnPresentationOpened) (THIS) PURE; + + /************************************************************************ + * Method: + * IHXClientAdviseSink::OnPresentationClosed + * Purpose: + * Called to advise the client a presentation has been closed. + */ + STDMETHOD(OnPresentationClosed) (THIS) PURE; + + /************************************************************************ + * Method: + * IHXClientAdviseSink::OnStatisticsChanged + * Purpose: + * Called to advise the client that the presentation statistics + * have changed. + */ + STDMETHOD(OnStatisticsChanged) (THIS) PURE; + + /************************************************************************ + * Method: + * IHXClientAdviseSink::OnPreSeek + * Purpose: + * Called by client engine to inform the client that a seek is + * about to occur. The render is informed the last time for the + * stream's time line before the seek, as well as the first new + * time for the stream's time line after the seek will be completed. + * + */ + STDMETHOD(OnPreSeek) (THIS_ + ULONG32 ulOldTime, + ULONG32 ulNewTime) PURE; + + /************************************************************************ + * Method: + * IHXClientAdviseSink::OnPostSeek + * Purpose: + * Called by client engine to inform the client that a seek has + * just occurred. The render is informed the last time for the + * stream's time line before the seek, as well as the first new + * time for the stream's time line after the seek. + * + */ + STDMETHOD(OnPostSeek) (THIS_ + ULONG32 ulOldTime, + ULONG32 ulNewTime) PURE; + + /************************************************************************ + * Method: + * IHXClientAdviseSink::OnStop + * Purpose: + * Called by client engine to inform the client that a stop has + * just occurred. + * + */ + STDMETHOD(OnStop) (THIS) PURE; + + /************************************************************************ + * Method: + * IHXClientAdviseSink::OnPause + * Purpose: + * Called by client engine to inform the client that a pause has + * just occurred. The render is informed the last time for the + * stream's time line before the pause. + * + */ + STDMETHOD(OnPause) (THIS_ + ULONG32 ulTime) PURE; + + /************************************************************************ + * Method: + * IHXClientAdviseSink::OnBegin + * Purpose: + * Called by client engine to inform the client that a begin or + * resume has just occurred. The render is informed the first time + * for the stream's time line after the resume. + * + */ + STDMETHOD(OnBegin) (THIS_ + ULONG32 ulTime) PURE; + + /************************************************************************ + * Method: + * IHXClientAdviseSink::OnBuffering + * Purpose: + * Called by client engine to inform the client that buffering + * of data is occurring. The render is informed of the reason for + * the buffering (start-up of stream, seek has occurred, network + * congestion, etc.), as well as percentage complete of the + * buffering process. + * + */ + STDMETHOD(OnBuffering) (THIS_ + ULONG32 ulFlags, + UINT16 unPercentComplete) PURE; + + /************************************************************************ + * Method: + * IHXClientAdviseSink::OnContacting + * Purpose: + * Called by client engine to inform the client is contacting + * hosts(s). + * + */ + STDMETHOD(OnContacting) (THIS_ + const char* pHostName) PURE; +}; + + +// $Private: + +/**************************************************************************** + * + * Interface: + * + * IHXClientRequestSink + * + * Purpose: + * + * Enables top level clients to get notified of new URLs + * + * + * IID_IHXClientRequestSink + * + * {00000B01-0901-11d1-8B06-00A024406D59} + * + */ + +DEFINE_GUID(IID_IHXClientRequestSink, 0x00000B01, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXClientRequestSink + +DECLARE_INTERFACE_(IHXClientRequestSink, IUnknown) +{ + /************************************************************************ + * Method: + * IHXClientRequestSink::OnNewRequest + * Purpose: + * Inform TLC of the new request. The TLC may choose to + * modify RequestHeaders at this time. + */ + + STDMETHOD(OnNewRequest) (THIS_ IHXRequest* pNewRequest) PURE; +}; + +// $EndPrivate. + +#endif /* _HXCLSNK_H_ */ diff --git a/amarok/src/engine/helix/helix-sp/helix-include/common/container/carray.h b/amarok/src/engine/helix/helix-sp/helix-include/common/container/carray.h new file mode 100644 index 00000000..962efe2b --- /dev/null +++ b/amarok/src/engine/helix/helix-sp/helix-include/common/container/carray.h @@ -0,0 +1,358 @@ + +/* + * + * This software is released under the provisions of the GPL version 2. + * see file "COPYING". If that file is not available, the full statement + * of the license can be found at + * + * http://www.fsf.org/licensing/licenses/gpl.txt + * + * Portions Copyright (c) 1995-2004 RealNetworks, Inc. All Rights Reserved. + * + */ + +#ifndef CARRAY_H_ +#define CARRAY_H_ + +#include "hxcom.h" +#include "hxassert.h" + +class HXEXPORT_CLASS CHXPtrArray { +public: + CHXPtrArray(); + ~CHXPtrArray(); + + // return num elements == 0 + HXBOOL IsEmpty() const; + // return number of elements + int GetSize() const; + // return largest index + int GetUpperBound() const; + // set size and grow by size + void SetSize(int nelems, int growSize=-1); + + + // free un-assigned slots + void FreeExtra(); + // free the entire array + void RemoveAll(); + // return the value at the given index + void* GetAt(int index) const; + // set the value at the given index + void SetAt(int index, void* value); + // return reference to value at given index + void*& ElementAt(int index); + // set value, grow array if needed + void SetAtGrow(int index, void* value); + // add the element, ret index of added element + int Add(void* value); + // append another array + void Append(const CHXPtrArray& other); + + // add the element if not already in array + HXBOOL AddIfUnique(void* value); + // same as GetAt() + void* operator[](int index) const; + // same as ElementAt() + void*& operator[](int index); + + // insert value at index + void InsertAt(int index, void* value, int repeat=1); + // insert array at index + void InsertAt(int index, CHXPtrArray* pPtrArray); + // remove value(s) at index + void RemoveAt(int index, int repeat=1); + + // search for value in array + inline HXBOOL Find(void* value, int* index=NULL); + // search for and remove first occurrence + HXBOOL FindAndRemoveOne(void* value); + // search for and remove all occurrences + HXBOOL FindAndRemoveAll(void* value); + + class Iterator + { + friend class CHXPtrArray; + public: + Iterator(); + Iterator& operator++(); + HXBOOL operator==(const Iterator& iter) const; + HXBOOL operator!=(const Iterator& iter) const; + void* operator*(); + + private: + Iterator(CHXPtrArray* pArray, int idx); + + CHXPtrArray* m_pArray; + int m_idx; + }; + + Iterator Begin(); + Iterator End(); + +private: + // not implemented + CHXPtrArray(const CHXPtrArray&); + // not implemented + void operator=(const CHXPtrArray&); + + // resize the array to given size + void Resize(int size); + // get the size to grow array by + int GetGrowSize(int newSize); + // common code for insertions + void InsertCommon(int index, int len); + + + // total slots allocated + int m_size; + // number of elements in array + int m_nelems; + // use set grow size for resizing ops + int m_userGrowSize; + // default grow size if user does not set + int m_defGrowSize; + // data array + void** m_pData; + +}; + +/// +/// IsEmpty() const +/// +/// return num elements == 0 +/// +inline HXBOOL +CHXPtrArray::IsEmpty() const +{ + return m_nelems == 0; +} + +/// +/// GetSize() const +/// +/// return size of the array +/// +inline int +CHXPtrArray::GetSize() const +{ + return m_nelems; +} + +/// +/// GetUpperBound() const +/// +/// return largest index +/// +inline int +CHXPtrArray::GetUpperBound() const +{ + return m_nelems - 1; +} + +/// +/// GetAt(int index) const +/// +/// return the value at the given index +/// +inline void* +CHXPtrArray::GetAt(int index) const +{ + HX_ASSERT(index >= 0 && index < m_nelems); + return m_pData[index]; +} + +/// +/// SetAt(int index, void* value) +/// +/// set the value at the given index +/// +inline void +CHXPtrArray::SetAt(int index, void* value) +{ + HX_ASSERT(index >= 0 && index < m_nelems); + m_pData[index] = value; +} + +/// +/// ElementAt(int index) +/// +/// return reference to value at given index +/// +inline void*& +CHXPtrArray::ElementAt(int index) +{ + HX_ASSERT(index >= 0 && index < m_nelems); + return m_pData[index]; +} + +/// +/// Add(void* value) +/// +/// append the element to the array +/// +inline int +CHXPtrArray::Add(void* value) +{ + int ret = m_nelems; + SetAtGrow(m_nelems, value); + return ret; +} + +/// +/// void* operator[] +/// +/// same as GetAt() +/// +inline void* +CHXPtrArray::operator[] (int index) const +{ + return GetAt(index); +} + +/// +/// void*& operator[] +/// +/// same as ElementAt() +/// +inline void*& +CHXPtrArray::operator[] (int index) +{ + return ElementAt(index); +} + +/// +/// AddIfUnique(void* value) +/// +/// add the element if not already in array +/// +inline HXBOOL +CHXPtrArray::AddIfUnique(void* value) +{ + int index; + if (Find(value, &index)) return FALSE; + Add(value); + return TRUE; +} + +/// +/// Find(void* value, int* index=NULL) +/// +/// search for value in array +/// +inline HXBOOL +CHXPtrArray::Find(void* value, int* index) +{ + int i = 0; + for (void** cur = m_pData; i < m_nelems; ++i, ++cur) + { + if (*cur == value) + { + if (index) *index = i; + return TRUE; + } + } + return FALSE; +} + +/// +/// FindAndRemoveOne(void* value) +/// +/// search for and remove first occurrence +/// +inline HXBOOL +CHXPtrArray::FindAndRemoveOne(void* value) +{ + int index = -1; + if (Find(value, &index) && index >= 0) + { + RemoveAt(index, 1); + return TRUE; + } + return FALSE; +} + +/// +/// FindAndRemoveAll(void* value) +/// +/// search for and remove all occurrences +/// +inline HXBOOL +CHXPtrArray::FindAndRemoveAll(void* value) +{ + void** src = m_pData; + void** dest = m_pData; + + for (int i = 0; i < m_nelems; ++i, ++src) + if (value == *src) *dest++ = *src; + + if (src != dest) + { + SetSize(dest - m_pData); + return TRUE; + } + + return FALSE; +} + +inline CHXPtrArray::Iterator +CHXPtrArray::Begin() +{ + return Iterator(this, 0); +} + +inline CHXPtrArray::Iterator +CHXPtrArray::End() +{ + return Iterator(this, GetSize()); +} + +/// +/// CHXPtrArray::Iterator methods +/// +inline +CHXPtrArray::Iterator::Iterator() + : m_pArray(0), + m_idx(0) +{ +} + +inline +CHXPtrArray::Iterator::Iterator(CHXPtrArray* pArray, int idx) + : m_pArray(pArray), + m_idx(idx) +{ +} + +inline CHXPtrArray::Iterator& +CHXPtrArray::Iterator::operator++() +{ + HX_ASSERT(m_pArray); + HX_ASSERT(!m_pArray->IsEmpty()); + HX_ASSERT(m_idx < m_pArray->GetSize()); + ++m_idx; + return *this; +} + +inline HXBOOL +CHXPtrArray::Iterator::operator==(const Iterator& iter) const +{ + return m_pArray == iter.m_pArray && m_idx == iter.m_idx; +} + +inline HXBOOL +CHXPtrArray::Iterator::operator!=(const Iterator& iter) const +{ + return !operator==(iter); +} + +inline void* +CHXPtrArray::Iterator::operator*() +{ + HX_ASSERT(m_pArray); + HX_ASSERT(!m_pArray->IsEmpty()); + HX_ASSERT(m_idx < m_pArray->GetSize()); + return m_pArray->GetAt(m_idx); +} + +#endif /* CARRAY_H_ */ diff --git a/amarok/src/engine/helix/helix-sp/helix-include/common/container/chxmapbuckets.h b/amarok/src/engine/helix/helix-sp/helix-include/common/container/chxmapbuckets.h new file mode 100644 index 00000000..d52e0cd0 --- /dev/null +++ b/amarok/src/engine/helix/helix-sp/helix-include/common/container/chxmapbuckets.h @@ -0,0 +1,69 @@ + +/* + * + * This software is released under the provisions of the GPL version 2. + * see file "COPYING". If that file is not available, the full statement + * of the license can be found at + * + * http://www.fsf.org/licensing/licenses/gpl.txt + * + * Portions Copyright (c) 1995-2004 RealNetworks, Inc. All Rights Reserved. + * + */ + +#ifndef _CHLXMAPBUCKETS_H_ +#define _CHLXMAPBUCKETS_H_ + +#include "hxmaputils.h" + +class CHlxMapBuckets +{ +public: + typedef HlxMap::IntVec_t ITEM; + + CHlxMapBuckets() : m_items(0), m_size(0) + { + } + + CHlxMapBuckets(UINT16 num) : m_items(0), m_size(0) + { + // Nasty new in constructor makes it hard to detect OOM errors, + // but I don't think this constructor is actually used by anybody. + m_items = new ITEM[num]; + m_size = num; + } + + ~CHlxMapBuckets() { HX_VECTOR_DELETE(m_items); } + + inline ITEM& operator[] (int idx) + { + return m_items[idx]; + } + + inline const ITEM& operator[] (int idx) const + { + return m_items[idx]; + } + + inline bool empty () const { return !m_items; } + + inline UINT16 size() const { return m_size; } + + inline HX_RESULT Init (UINT16 num) + { + HX_VECTOR_DELETE(m_items); + m_items = new ITEM[num]; + if( !m_items ) + { + return HXR_OUTOFMEMORY; + } + m_size = num; + return HXR_OK; + } + +private: + ITEM* m_items; + UINT16 m_size; +}; + +#endif // _CHLXMAPBUCKETS_H_ diff --git a/amarok/src/engine/helix/helix-sp/helix-include/common/container/chxmaplongtoobj.h b/amarok/src/engine/helix/helix-sp/helix-include/common/container/chxmaplongtoobj.h new file mode 100644 index 00000000..18a2a8b1 --- /dev/null +++ b/amarok/src/engine/helix/helix-sp/helix-include/common/container/chxmaplongtoobj.h @@ -0,0 +1,286 @@ + +/* + * + * This software is released under the provisions of the GPL version 2. + * see file "COPYING". If that file is not available, the full statement + * of the license can be found at + * + * http://www.fsf.org/licensing/licenses/gpl.txt + * + * Portions Copyright (c) 1995-2004 RealNetworks, Inc. All Rights Reserved. + * + */ + +#ifndef _CHXMAPLONGTOOBJ_H_ +#define _CHXMAPLONGTOOBJ_H_ + +// Notes... +// +// Since we aren't using templates, we get to copy the same basic code all +// over the place. So, if you change something in this class, chances are +// that the other CHXMap*To* classes may need the change as well. +// XXXSAB: Need to better abstract out the common code... +// +// This implementation has a few dynamically resized vectors - their +// "chunk sizes" (number of elements added to size when a new element +// addition requires a reallocation) can be adjusted via the following +// accessors. +// +// m_items - This is the vector of actual key/value pairs (along with a +// boolean "free" flag) where the data for the map is stored. It's +// chunk size is controlled via the optional argument to the map +// ctor. And the default value for that is controlled by the +// static SetDefaultChunkSize() method. +// +// m_buckets - This is the vector of hash buckets. Each hash bucket is +// a vector of int indices into the m_items vector. The number of +// buckets doesn't change over time and is controlled via the +// InitHashTable() method (which has the effect of resetting the +// map) and it defaults to z_defaultNumBuckets (101 at the moment). +// The chunk size of the individual hash buckets is set by the +// SetBucketChunkSize() method and the default for that is set by +// the SetDefaultBucketChunkSize() method. +// + +#include "hxtypes.h" +#include "carray.h" +#include "hxstring.h" + +#include "hxmaputils.h" +#include "chxmapbuckets.h" + +class CHXMapLongToObj +{ +public: + typedef LONG32 key_type; + typedef LONG32 key_arg_type; + typedef LONG32 key_ref_type; + inline static key_type key_nil() { return 0; } + + typedef void* value_type; + typedef void* value_arg_type; + typedef void*& value_ref_type; + typedef void* value_const_ref_type; + inline static value_ref_type val_nil() { static const value_type p = 0; return (value_ref_type)p; } + + struct Item + { + Item (key_arg_type key_ = key_nil(), + value_arg_type val_ = val_nil(), + bool bFree_ = true) : + key(key_), val(val_), bFree(bFree_) + {} + + key_type key; + value_type val; + bool bFree; + }; + DECLARE_ITEMVEC(ItemVec_t,Item,Item(),0,0); + + class Iterator + { + public: + typedef key_type iter_key_type; + friend class CHXMapLongToObj; + + // NOTE: (item == -1) is used to mean "set to end of pItems". + Iterator(ItemVec_t* pItems = NULL, + int item = -1); + + // NOTE: Values of 'next' copied into iterator...since this + // iterator is caching key/value and doesn't return a + // value_type&, it can't be used to modify the values in the + // map. + Iterator& operator++(); + Iterator operator++(int); // XXXSAB: tested? + + HXBOOL operator==(const Iterator&) const; + HXBOOL operator!=(const Iterator&) const; + value_type operator*(); // returns the 'value' + iter_key_type get_key (); // returns the 'key' + + private: + void GotoValid(); + + ItemVec_t* m_pItems; + int m_item; + + // cached key/value + iter_key_type m_key; + value_type m_val; + }; + +private: + +#if defined(HELIX_CONFIG_NOSTATICS) + static const ULONG32 z_defaultNumBuckets; + static const ULONG32 z_defaultChunkSize; + static const ULONG32 z_defaultBucketChunkSize; +#else + static ULONG32 z_defaultNumBuckets; + static ULONG32 z_defaultChunkSize; + static ULONG32 z_defaultBucketChunkSize; +#endif + + +public: + + // Construction + + // NOTE: Chunk size is the number of key/value pairs to grow by each + // time one of the hash buckets needs to be grown. + CHXMapLongToObj(int chunkSize = z_defaultChunkSize); + ~CHXMapLongToObj(); + + // Attributes + inline int GetCount() const; + inline HXBOOL IsEmpty() const; + + HXBOOL Lookup(key_arg_type key, value_arg_type& value) const; + POSITION Lookup(key_arg_type key) const; + + // XXXSAB: I added GetKeyAt() and GetAt() since there was previously + // no easy way to get those data without advancing the + // POSITION. + key_ref_type GetKeyAt(POSITION pos) const; + value_const_ref_type GetAt(POSITION pos) const; + value_ref_type GetAt(POSITION pos); + + // Lookup & add if not there + value_ref_type operator[](key_arg_type key); + + // add a new (key, value) pair + POSITION SetAt(key_arg_type key, value_arg_type value); + + // remove existing (key, ?) pair + POSITION Remove(key_arg_type key); + + HXBOOL RemoveKey(key_arg_type key); + + void RemoveAll(); + + // Iteration + POSITION GetStartPosition() const; + void GetNextAssoc (POSITION& pos, key_arg_type& key, value_arg_type& value) const; + + Iterator Begin(); + Iterator End(); + Iterator Erase(Iterator it); + // XXXSAB: Added Find() command to parallel STL style method + Iterator Find(key_arg_type key); + + // Returns the number of hash buckets + inline ULONG32 GetHashTableSize() const; + + // This will reset the internal storage so that any the map will be + // empty when this returns. + HX_RESULT InitHashTable(ULONG32 numBuckets = z_defaultNumBuckets, + HXBOOL bAlloc = TRUE); + + typedef ULONG32 (*HashFunc_t) (key_arg_type key); + static ULONG32 DefaultHashFunc (key_arg_type key); + inline HashFunc_t SetHashFunc (HashFunc_t hf = DefaultHashFunc); // XXXSAB: tested??? + + // Overrideables: special non-virtual (XXXSAB: Huh?) + inline ULONG32 HashKey(key_arg_type key) const; + + inline static void SetDefaultNumBuckets (ULONG32 numBuckets); + inline static void SetDefaultChunkSize (ULONG32 chunkSize); + inline static void SetDefaultBucketChunkSize (ULONG32 chunkSize); + inline void SetBucketChunkSize (ULONG32 chunkSize); + + // In _DEBUG mode, this does a bunch of DPRINTF's... + void Dump() const; + +private: + inline HXBOOL Lookup(key_arg_type key, int& retItem) const; + HXBOOL LookupInBucket(ULONG32 bucket, key_arg_type key, int& retItem) const; + Item* LookupItem(ULONG32 bucket, key_arg_type key); + inline const Item* LookupItem(ULONG32 bucket, key_arg_type key) const + { + return ((CHXMapLongToObj*)this)->LookupItem(bucket, key); + } + + // Internal function - key already verified not to exist + HXBOOL AddToBucket(ULONG32 bucket, key_arg_type key, value_arg_type value, int& retItem); + + + inline POSITION Item2Pos(int item) const; + inline int Pos2Item(POSITION pos) const; + +private: + + HashFunc_t m_hf; + + ItemVec_t m_items; + HlxMap::IntVec_t m_free; + + CHlxMapBuckets m_buckets; + ULONG32 m_numBuckets; + ULONG32 m_chunkSize; + ULONG32 m_bucketChunkSize; + + // Members specific to the type of key and/or value goes below here. + void ConstructTypeSpecifics(); + inline HXBOOL IsKeyMatch (key_arg_type k1, key_arg_type k2) const + { + return (k1 == k2) ? TRUE : FALSE; + } +}; + +int CHXMapLongToObj::GetCount() const +{ + return m_items.size() - m_free.size(); +} + +HXBOOL CHXMapLongToObj::IsEmpty() const +{ + return GetCount() == 0; +} + +ULONG32 CHXMapLongToObj::GetHashTableSize() const +{ + return m_numBuckets; +} + +CHXMapLongToObj::HashFunc_t CHXMapLongToObj::SetHashFunc ( + CHXMapLongToObj::HashFunc_t hf) +{ + HashFunc_t old = m_hf; + m_hf = hf; + return old; +} + +ULONG32 CHXMapLongToObj::HashKey (key_arg_type key) const +{ + if (m_hf) return m_hf(key); + return DefaultHashFunc(key); +} + +void CHXMapLongToObj::SetDefaultNumBuckets (ULONG32 numBuckets) +{ +#if !defined(HELIX_CONFIG_NOSTATICS) + z_defaultNumBuckets = numBuckets; +#endif +} + +void CHXMapLongToObj::SetDefaultChunkSize (ULONG32 chunkSize) +{ +#if !defined(HELIX_CONFIG_NOSTATICS) + z_defaultChunkSize = chunkSize; +#endif +} + +void CHXMapLongToObj::SetDefaultBucketChunkSize (ULONG32 chunkSize) +{ +#if !defined(HELIX_CONFIG_NOSTATICS) + z_defaultBucketChunkSize = chunkSize; +#endif +} + +void CHXMapLongToObj::SetBucketChunkSize (ULONG32 chunkSize) +{ + m_bucketChunkSize = chunkSize; +} + +#endif // _CHXMAPLONGTOOBJ_H_ diff --git a/amarok/src/engine/helix/helix-sp/helix-include/common/container/chxmapptrtoptr.h b/amarok/src/engine/helix/helix-sp/helix-include/common/container/chxmapptrtoptr.h new file mode 100644 index 00000000..186dd50b --- /dev/null +++ b/amarok/src/engine/helix/helix-sp/helix-include/common/container/chxmapptrtoptr.h @@ -0,0 +1,285 @@ + +/* + * + * This software is released under the provisions of the GPL version 2. + * see file "COPYING". If that file is not available, the full statement + * of the license can be found at + * + * http://www.fsf.org/licensing/licenses/gpl.txt + * + * Portions Copyright (c) 1995-2004 RealNetworks, Inc. All Rights Reserved. + * + */ + +#ifndef _CHXMAPPTRTOPTR_H_ +#define _CHXMAPPTRTOPTR_H_ + +// Notes... +// +// Since we aren't using templates, we get to copy the same basic code all +// over the place. So, if you change something in this class, chances are +// that the other CHXMap*To* classes may need the change as well. +// XXXSAB: Need to better abstract out the common code... +// +// This implementation has a few dynamically resized vectors - their +// "chunk sizes" (number of elements added to size when a new element +// addition requires a reallocation) can be adjusted via the following +// accessors. +// +// m_items - This is the vector of actual key/value pairs (along with a +// boolean "free" flag) where the data for the map is stored. It's +// chunk size is controlled via the optional argument to the map +// ctor. And the default value for that is controlled by the +// static SetDefaultChunkSize() method. +// +// m_buckets - This is the vector of hash buckets. Each hash bucket is +// a vector of int indices into the m_items vector. The number of +// buckets doesn't change over time and is controlled via the +// InitHashTable() method (which has the effect of resetting the +// map) and it defaults to z_defaultNumBuckets (101 at the moment). +// The chunk size of the individual hash buckets is set by the +// SetBucketChunkSize() method and the default for that is set by +// the SetDefaultBucketChunkSize() method. +// + +#include "hxtypes.h" +#include "carray.h" +#include "hxstring.h" + +#include "hxmaputils.h" +#include "chxmapbuckets.h" + +class CHXMapPtrToPtr +{ +public: + typedef void* key_type; + typedef void* key_arg_type; + typedef void* key_ref_type; + inline static key_type key_nil() { return NULL; } + + typedef void* value_type; + typedef void* value_arg_type; + typedef void*& value_ref_type; + typedef void* value_const_ref_type; + inline static value_ref_type val_nil() { static const value_type p = 0; return (value_ref_type)p; } + + struct Item + { + Item (key_arg_type key_ = key_nil(), + value_arg_type val_ = val_nil(), + bool bFree_ = true) : + key(key_), val(val_), bFree(bFree_) + {} + + key_type key; + value_type val; + bool bFree; + }; + DECLARE_ITEMVEC(ItemVec_t,Item,Item(),0,0); + + class Iterator + { + public: + typedef key_type iter_key_type; + friend class CHXMapPtrToPtr; + + // NOTE: (item == -1) is used to mean "set to end of pItems". + Iterator(ItemVec_t* pItems = NULL, + int item = -1); + + // NOTE: Values of 'next' copied into iterator...since this + // iterator is caching key/value and doesn't return a + // value_type&, it can't be used to modify the values in the + // map. + Iterator& operator++(); + Iterator operator++(int); // XXXSAB: tested? + + HXBOOL operator==(const Iterator&) const; + HXBOOL operator!=(const Iterator&) const; + value_type operator*(); // returns the 'value' + iter_key_type get_key (); // returns the 'key' + + private: + void GotoValid(); + + ItemVec_t* m_pItems; + int m_item; + + // cached key/value + iter_key_type m_key; + value_type m_val; + }; + +private: + +#if defined(HELIX_CONFIG_NOSTATICS) + static const ULONG32 z_defaultNumBuckets; + static const ULONG32 z_defaultChunkSize; + static const ULONG32 z_defaultBucketChunkSize; +#else + static ULONG32 z_defaultNumBuckets; + static ULONG32 z_defaultChunkSize; + static ULONG32 z_defaultBucketChunkSize; +#endif + +public: + + // Construction + + // NOTE: Chunk size is the number of key/value pairs to grow by each + // time one of the hash buckets needs to be grown. + CHXMapPtrToPtr(int chunkSize = z_defaultChunkSize); + ~CHXMapPtrToPtr(); + + // Attributes + inline int GetCount() const; + inline HXBOOL IsEmpty() const; + + HXBOOL Lookup(key_arg_type key, value_arg_type& value) const; + POSITION Lookup(key_arg_type key) const; + + // XXXSAB: I added GetKeyAt() and GetAt() since there was previously + // no easy way to get those data without advancing the + // POSITION. + key_ref_type GetKeyAt(POSITION pos) const; + value_const_ref_type GetAt(POSITION pos) const; + value_ref_type GetAt(POSITION pos); + + // Lookup & add if not there + value_ref_type operator[](key_arg_type key); + + // add a new (key, value) pair + POSITION SetAt(key_arg_type key, value_arg_type value); + + // remove existing (key, ?) pair + POSITION Remove(key_arg_type key); + + HXBOOL RemoveKey(key_arg_type key); + + void RemoveAll(); + + // Iteration + POSITION GetStartPosition() const; + void GetNextAssoc (POSITION& pos, key_arg_type& key, value_arg_type& value) const; + + Iterator Begin(); + Iterator End(); + Iterator Erase(Iterator it); + // XXXSAB: Added Find() command to parallel STL style method + Iterator Find(key_arg_type key); + + // Returns the number of hash buckets + inline ULONG32 GetHashTableSize() const; + + // This will reset the internal storage so that any the map will be + // empty when this returns. + HX_RESULT InitHashTable(ULONG32 numBuckets = z_defaultNumBuckets, + HXBOOL bAlloc = TRUE); + + typedef ULONG32 (*HashFunc_t) (key_arg_type key); + static ULONG32 DefaultHashFunc (key_arg_type key); + inline HashFunc_t SetHashFunc (HashFunc_t hf = DefaultHashFunc); // XXXSAB: tested??? + + // Overrideables: special non-virtual (XXXSAB: Huh?) + inline ULONG32 HashKey(key_arg_type key) const; + + inline static void SetDefaultNumBuckets (ULONG32 numBuckets); + inline static void SetDefaultChunkSize (ULONG32 chunkSize); + inline static void SetDefaultBucketChunkSize (ULONG32 chunkSize); + inline void SetBucketChunkSize (ULONG32 chunkSize); + + // In _DEBUG mode, this does a bunch of DPRINTF's... + void Dump() const; + +private: + inline HXBOOL Lookup(key_arg_type key, int& retItem) const; + HXBOOL LookupInBucket(ULONG32 bucket, key_arg_type key, int& retItem) const; + Item* LookupItem(ULONG32 bucket, key_arg_type key); + inline const Item* LookupItem(ULONG32 bucket, key_arg_type key) const + { + return ((CHXMapPtrToPtr*)this)->LookupItem(bucket, key); + } + + // Internal function - key already verified not to exist + HXBOOL AddToBucket(ULONG32 bucket, key_arg_type key, value_arg_type value, int& retItem); + + + inline POSITION Item2Pos(int item) const; + inline int Pos2Item(POSITION pos) const; + +private: + + HashFunc_t m_hf; + + ItemVec_t m_items; + HlxMap::IntVec_t m_free; + + CHlxMapBuckets m_buckets; + ULONG32 m_numBuckets; + ULONG32 m_chunkSize; + ULONG32 m_bucketChunkSize; + + // Members specific to the type of key and/or value goes below here. + void ConstructTypeSpecifics(); + inline HXBOOL IsKeyMatch (key_arg_type k1, key_arg_type k2) const + { + return (k1 == k2) ? TRUE : FALSE; + } +}; + +int CHXMapPtrToPtr::GetCount() const +{ + return m_items.size() - m_free.size(); +} + +HXBOOL CHXMapPtrToPtr::IsEmpty() const +{ + return GetCount() == 0; +} + +ULONG32 CHXMapPtrToPtr::GetHashTableSize() const +{ + return m_numBuckets; +} + +CHXMapPtrToPtr::HashFunc_t CHXMapPtrToPtr::SetHashFunc ( + CHXMapPtrToPtr::HashFunc_t hf) +{ + HashFunc_t old = m_hf; + m_hf = hf; + return old; +} + +ULONG32 CHXMapPtrToPtr::HashKey (key_arg_type key) const +{ + if (m_hf) return m_hf(key); + return DefaultHashFunc(key); +} + +void CHXMapPtrToPtr::SetDefaultNumBuckets (ULONG32 numBuckets) +{ +#if !defined(HELIX_CONFIG_NOSTATICS) + z_defaultNumBuckets = numBuckets; +#endif +} + +void CHXMapPtrToPtr::SetDefaultChunkSize (ULONG32 chunkSize) +{ +#if !defined(HELIX_CONFIG_NOSTATICS) + z_defaultChunkSize = chunkSize; +#endif +} + +void CHXMapPtrToPtr::SetDefaultBucketChunkSize (ULONG32 chunkSize) +{ +#if !defined(HELIX_CONFIG_NOSTATICS) + z_defaultBucketChunkSize = chunkSize; +#endif +} + +void CHXMapPtrToPtr::SetBucketChunkSize (ULONG32 chunkSize) +{ + m_bucketChunkSize = chunkSize; +} + +#endif // _CHXMAPPTRTOPTR_H_ diff --git a/amarok/src/engine/helix/helix-sp/helix-include/common/container/chxmapstringtoob.h b/amarok/src/engine/helix/helix-sp/helix-include/common/container/chxmapstringtoob.h new file mode 100644 index 00000000..5d3ff12e --- /dev/null +++ b/amarok/src/engine/helix/helix-sp/helix-include/common/container/chxmapstringtoob.h @@ -0,0 +1,322 @@ + +/* + * + * This software is released under the provisions of the GPL version 2. + * see file "COPYING". If that file is not available, the full statement + * of the license can be found at + * + * http://www.fsf.org/licensing/licenses/gpl.txt + * + * Portions Copyright (c) 1995-2004 RealNetworks, Inc. All Rights Reserved. + * + */ + +#ifndef _CHXMAPSTRINGTOOB_H_ +#define _CHXMAPSTRINGTOOB_H_ + +// Notes... +// +// Since we aren't using templates, we get to copy the same basic code all +// over the place. So, if you change something in this class, chances are +// that the other CHXMap*To* classes may need the change as well. +// XXXSAB: Need to better abstract out the common code... +// +// This implementation has a few dynamically resized vectors - their +// "chunk sizes" (number of elements added to size when a new element +// addition requires a reallocation) can be adjusted via the following +// accessors. +// +// m_items - This is the vector of actual key/value pairs (along with a +// boolean "free" flag) where the data for the map is stored. It's +// chunk size is controlled via the optional argument to the map +// ctor. And the default value for that is controlled by the +// static SetDefaultChunkSize() method. +// +// m_buckets - This is the vector of hash buckets. Each hash bucket is +// a vector of int indices into the m_items vector. The number of +// buckets doesn't change over time and is controlled via the +// InitHashTable() method (which has the effect of resetting the +// map) and it defaults to z_defaultNumBuckets (101 at the moment). +// The chunk size of the individual hash buckets is set by the +// SetBucketChunkSize() method and the default for that is set by +// the SetDefaultBucketChunkSize() method. +// + +#include "hxtypes.h" +#include "carray.h" +#include "hxstring.h" + +#include "hxmaputils.h" +#include "chxmapbuckets.h" + +#include "hlxclib/string.h" // strcasecmp() + +class CHXMapStringToOb +{ +public: + typedef const char* key_type; + typedef const char* key_arg_type; + typedef const char* key_ref_type; + + inline static CHXString& key_nil() { return (CHXString&)HXEmptyString; } + + typedef void* value_type; + typedef void* value_arg_type; + typedef void*& value_ref_type; + typedef void* value_const_ref_type; + inline static value_ref_type val_nil() { static const value_type p = 0; return (value_ref_type)p; } + + struct Item + { + Item (key_arg_type key_ = key_nil(), + value_arg_type val_ = val_nil(), + bool bFree_ = true) : + key(key_), val(val_), bFree(bFree_) + {} + + CHXString key; + value_type val; + bool bFree; + }; + DECLARE_ITEMVEC(ItemVec_t,Item,Item(),0,0); + + class Iterator + { + public: + typedef key_type iter_key_type; + friend class CHXMapStringToOb; + + // NOTE: (item == -1) is used to mean "set to end of pItems". + Iterator(ItemVec_t* pItems = NULL, + int item = -1); + + // NOTE: Values of 'next' copied into iterator...since this + // iterator is caching key/value and doesn't return a + // value_type&, it can't be used to modify the values in the + // map. + Iterator& operator++(); + Iterator operator++(int); // XXXSAB: tested? + + HXBOOL operator==(const Iterator&) const; + HXBOOL operator!=(const Iterator&) const; + value_type operator*(); // returns the 'value' + iter_key_type get_key (); // returns the 'key' + + private: + void GotoValid(); + + ItemVec_t* m_pItems; + int m_item; + + // cached key/value + CHXString m_key; + value_type m_val; + }; + +private: + +#if defined(HELIX_CONFIG_NOSTATICS) + static const ULONG32 z_defaultNumBuckets; + static const ULONG32 z_defaultChunkSize; + static const ULONG32 z_defaultBucketChunkSize; +#else + static ULONG32 z_defaultNumBuckets; + static ULONG32 z_defaultChunkSize; + static ULONG32 z_defaultBucketChunkSize; +#endif + + +public: + // Construction + + // NOTE: Chunk size is the number of key/value pairs to grow by each + // time one of the hash buckets needs to be grown. + CHXMapStringToOb(int chunkSize = z_defaultChunkSize); + ~CHXMapStringToOb(); + + // Attributes + inline int GetCount() const; + inline HXBOOL IsEmpty() const; + + HXBOOL Lookup(key_arg_type key, value_arg_type& value) const; + POSITION Lookup(key_arg_type key) const; + + // XXXSAB: I added GetKeyAt() and GetAt() since there was previously + // no easy way to get those data without advancing the + // POSITION. + key_ref_type GetKeyAt(POSITION pos) const; + value_const_ref_type GetAt(POSITION pos) const; + value_ref_type GetAt(POSITION pos); + + // Lookup & add if not there + value_ref_type operator[](key_arg_type key); + + // add a new (key, value) pair + POSITION SetAt(key_arg_type key, value_arg_type value); + + // remove existing (key, ?) pair + POSITION Remove(key_arg_type key); + + HXBOOL RemoveKey(key_arg_type key); + + void RemoveAll(); + + // Iteration + POSITION GetStartPosition() const; + void GetNextAssoc (POSITION& pos, key_arg_type& key, value_arg_type& value) const; + + // CHXString Helper... + void GetNextAssoc (POSITION& pos, CHXString& key, value_arg_type& value) const + { + const char* sz = NULL; + GetNextAssoc (pos, sz, value); + if (sz) key = sz; + } + + Iterator Begin(); + Iterator End(); + Iterator Erase(Iterator it); + // XXXSAB: Added Find() command to parallel STL style method + Iterator Find(key_arg_type key); + + // Returns the number of hash buckets + inline ULONG32 GetHashTableSize() const; + + // This will reset the internal storage so that any the map will be + // empty when this returns. + // NOTE: This function always allocates some data - the bAlloc flag is + // for compatibility with an old interface and is ignored. + HX_RESULT InitHashTable(ULONG32 numBuckets = z_defaultNumBuckets, + HXBOOL bAlloc = TRUE); + + // defaults to on. Set this before you add anything! And make sure + // that if you set a hash function that it's behavior matches the case + // sensitivity flag. + void SetCaseSensitive(HXBOOL bCaseSens) { m_bCaseSens = bCaseSens ? true : false; } + + typedef ULONG32 (*HashFunc_t) (key_arg_type key); + static ULONG32 DefaultHashFunc (key_arg_type key) + { + return HlxMap::StrHashFunc(key, true); + } + static ULONG32 DefaultNoCaseHashFunc (key_arg_type key) + { + return HlxMap::StrHashFunc(key, false); + } + inline HashFunc_t SetHashFunc (HashFunc_t hf = DefaultHashFunc); // XXXSAB: tested??? + + // Overrideables: special non-virtual (XXXSAB: Huh?) + inline ULONG32 HashKey(key_arg_type key) const; + + inline static void SetDefaultNumBuckets (ULONG32 numBuckets); + inline static void SetDefaultChunkSize (ULONG32 chunkSize); + inline static void SetDefaultBucketChunkSize (ULONG32 chunkSize); + inline void SetBucketChunkSize (ULONG32 chunkSize); + + // In _DEBUG mode, this does a bunch of DPRINTF's... + void Dump() const; + +private: + inline HXBOOL Lookup(key_arg_type key, int& retItem) const; + HXBOOL LookupInBucket(ULONG32 bucket, key_arg_type key, int& retItem) const; + Item* LookupItem(ULONG32 bucket, key_arg_type key); + inline const Item* LookupItem(ULONG32 bucket, key_arg_type key) const + { + return ((CHXMapStringToOb*)this)->LookupItem(bucket, key); + } + + // Internal function - key already verified not to exist + HXBOOL AddToBucket(ULONG32 bucket, key_arg_type key, value_arg_type value, int& retItem); + + inline POSITION Item2Pos(int item) const; + inline int Pos2Item(POSITION pos) const; + +private: + + HashFunc_t m_hf; + + ItemVec_t m_items; + HlxMap::IntVec_t m_free; + + CHlxMapBuckets m_buckets; + ULONG32 m_numBuckets; + ULONG32 m_chunkSize; + ULONG32 m_bucketChunkSize; + + // Members specific to the type of key and/or value goes below here. + void ConstructTypeSpecifics(); + inline HXBOOL IsKeyMatch (key_arg_type k1, key_arg_type k2) const + { + HXBOOL bRet; + + if (m_bCaseSens) bRet = (strcmp(k1, k2) == 0); + else bRet = (strcasecmp(k1, k2) == 0); + +#ifdef XXXSAB + printf ("IsKeyMatch(\"%s\", \"%s\") -> %s\n", + k1, k2, + bRet ? "true" : "false"); +#endif /* XXXSAB */ + + return bRet; + } + + bool m_bCaseSens; +}; + +int CHXMapStringToOb::GetCount() const +{ + return m_items.size() - m_free.size(); +} + +HXBOOL CHXMapStringToOb::IsEmpty() const +{ + return GetCount() == 0; +} + +ULONG32 CHXMapStringToOb::GetHashTableSize() const +{ + return m_numBuckets; +} + +CHXMapStringToOb::HashFunc_t CHXMapStringToOb::SetHashFunc ( + CHXMapStringToOb::HashFunc_t hf) +{ + HashFunc_t old = m_hf; + m_hf = hf; + return old; +} + +ULONG32 CHXMapStringToOb::HashKey (key_arg_type key) const +{ + if (m_hf) return m_hf(key); + return m_bCaseSens ? DefaultHashFunc(key) : DefaultNoCaseHashFunc(key); +} + +void CHXMapStringToOb::SetDefaultNumBuckets (ULONG32 numBuckets) +{ +#if !defined(HELIX_CONFIG_NOSTATICS) + z_defaultNumBuckets = numBuckets; +#endif +} + +void CHXMapStringToOb::SetDefaultChunkSize (ULONG32 chunkSize) +{ +#if !defined(HELIX_CONFIG_NOSTATICS) + z_defaultChunkSize = chunkSize; +#endif +} + +void CHXMapStringToOb::SetDefaultBucketChunkSize (ULONG32 chunkSize) +{ +#if !defined(HELIX_CONFIG_NOSTATICS) + z_defaultBucketChunkSize = chunkSize; +#endif +} + +void CHXMapStringToOb::SetBucketChunkSize (ULONG32 chunkSize) +{ + m_bucketChunkSize = chunkSize; +} + +#endif // _CHXMAPSTRINGTOOB_H_ diff --git a/amarok/src/engine/helix/helix-sp/helix-include/common/container/chxmapstringtostring.h b/amarok/src/engine/helix/helix-sp/helix-include/common/container/chxmapstringtostring.h new file mode 100644 index 00000000..5d679d77 --- /dev/null +++ b/amarok/src/engine/helix/helix-sp/helix-include/common/container/chxmapstringtostring.h @@ -0,0 +1,316 @@ + +/* + * + * This software is released under the provisions of the GPL version 2. + * see file "COPYING". If that file is not available, the full statement + * of the license can be found at + * + * http://www.fsf.org/licensing/licenses/gpl.txt + * + * Portions Copyright (c) 1995-2004 RealNetworks, Inc. All Rights Reserved. + * + */ + +#ifndef _CHXMAPSTRINGTOSTRING_H_ +#define _CHXMAPSTRINGTOSTRING_H_ + +// Notes... +// +// Since we aren't using templates, we get to copy the same basic code all +// over the place. So, if you change something in this class, chances are +// that the other CHXMap*To* classes may need the change as well. +// XXXSAB: Need to better abstract out the common code... +// +// This implementation has a few dynamically resized vectors - their +// "chunk sizes" (number of elements added to size when a new element +// addition requires a reallocation) can be adjusted via the following +// accessors. +// +// m_items - This is the vector of actual key/value pairs (along with a +// boolean "free" flag) where the data for the map is stored. It's +// chunk size is controlled via the optional argument to the map +// ctor. And the default value for that is controlled by the +// static SetDefaultChunkSize() method. +// +// m_buckets - This is the vector of hash buckets. Each hash bucket is +// a vector of int indices into the m_items vector. The number of +// buckets doesn't change over time and is controlled via the +// InitHashTable() method (which has the effect of resetting the +// map) and it defaults to z_defaultNumBuckets (101 at the moment). +// The chunk size of the individual hash buckets is set by the +// SetBucketChunkSize() method and the default for that is set by +// the SetDefaultBucketChunkSize() method. +// + +#include "hxtypes.h" +#include "carray.h" +#include "hxstring.h" + +#include "hxmaputils.h" +#include "chxmapbuckets.h" + +#include "hlxclib/string.h" // strcasecmp() + +class CHXMapStringToString +{ +public: + typedef const char* key_type; + typedef const char* key_arg_type; + typedef const char* key_ref_type; + + inline static CHXString& key_nil() { return (CHXString&)HXEmptyString; } + + typedef const char* value_type; + typedef const char* value_arg_type; + typedef CHXString& value_ref_type; + typedef const CHXString& value_const_ref_type; + + inline static CHXString& val_nil() { return (CHXString&)HXEmptyString; } + + struct Item + { + Item (key_arg_type key_ = key_nil(), + value_arg_type val_ = val_nil(), + bool bFree_ = true) : + key(key_), val(val_), bFree(bFree_) + {} + + CHXString key; + CHXString val; + bool bFree; + }; + DECLARE_ITEMVEC(ItemVec_t,Item,Item(),0,0);//HXEmptyString); + + class Iterator + { + public: + typedef key_type iter_key_type; + friend class CHXMapStringToString; + + // NOTE: (item == -1) is used to mean "set to end of pItems". + Iterator(ItemVec_t* pItems = NULL, + int item = -1); + + // NOTE: Values of 'next' copied into iterator...since this + // iterator is caching key/value and doesn't return a + // value_type&, it can't be used to modify the values in the + // map. + Iterator& operator++(); + Iterator operator++(int); // XXXSAB: tested? + + HXBOOL operator==(const Iterator&) const; + HXBOOL operator!=(const Iterator&) const; + value_type operator*(); // returns the 'value' + iter_key_type get_key (); // returns the 'key' + + private: + void GotoValid(); + + ItemVec_t* m_pItems; + int m_item; + + // cached key/value + CHXString m_key; + CHXString m_val; + }; + +private: + +#if defined(HELIX_CONFIG_NOSTATICS) + static const ULONG32 z_defaultNumBuckets; + static const ULONG32 z_defaultChunkSize; + static const ULONG32 z_defaultBucketChunkSize; +#else + static ULONG32 z_defaultNumBuckets; + static ULONG32 z_defaultChunkSize; + static ULONG32 z_defaultBucketChunkSize; +#endif + + +public: + + // Construction + + // NOTE: Chunk size is the number of key/value pairs to grow by each + // time one of the hash buckets needs to be grown. + CHXMapStringToString(int chunkSize = z_defaultChunkSize); + ~CHXMapStringToString(); + + // Attributes + inline int GetCount() const; + inline HXBOOL IsEmpty() const; + + HXBOOL Lookup(key_arg_type key, CHXString& value) const; + POSITION Lookup(key_arg_type key) const; + + // XXXSAB: I added GetKeyAt() and GetAt() since there was previously + // no easy way to get those data without advancing the + // POSITION. + key_ref_type GetKeyAt(POSITION pos) const; + value_const_ref_type GetAt(POSITION pos) const; + value_ref_type GetAt(POSITION pos); + + // Lookup & add if not there + value_ref_type operator[](key_arg_type key); + + // add a new (key, value) pair + POSITION SetAt(key_arg_type key, value_arg_type value); + + // remove existing (key, ?) pair + POSITION Remove(key_arg_type key); + + HXBOOL RemoveKey(key_arg_type key); + + void RemoveAll(); + + // Iteration + POSITION GetStartPosition() const; + void GetNextAssoc (POSITION& pos, CHXString& key, CHXString& value) const; + + Iterator Begin(); + Iterator End(); + Iterator Erase(Iterator it); + // XXXSAB: Added Find() command to parallel STL style method + Iterator Find(key_arg_type key); + + // Returns the number of hash buckets + inline ULONG32 GetHashTableSize() const; + + // This will reset the internal storage so that any the map will be + // empty when this returns. + // NOTE: This function always allocates some data - the bAlloc flag is + // for compatibility with an old interface and is ignored. + HX_RESULT InitHashTable(ULONG32 numBuckets = z_defaultNumBuckets, + HXBOOL bAlloc = TRUE); + + // defaults to on. Set this before you add anything! And make sure + // that if you set a hash function that it's behavior matches the case + // sensitivity flag. + void SetCaseSensitive(HXBOOL bCaseSens) { m_bCaseSens = bCaseSens ? true : false; } + + typedef ULONG32 (*HashFunc_t) (key_arg_type key); + static ULONG32 DefaultHashFunc (key_arg_type key) + { + return HlxMap::StrHashFunc(key, true); + } + static ULONG32 DefaultNoCaseHashFunc (key_arg_type key) + { + return HlxMap::StrHashFunc(key, false); + } + inline HashFunc_t SetHashFunc (HashFunc_t hf = DefaultHashFunc); // XXXSAB: tested??? + + // Overrideables: special non-virtual (XXXSAB: Huh?) + inline ULONG32 HashKey(key_arg_type key) const; + + inline static void SetDefaultNumBuckets (ULONG32 numBuckets); + inline static void SetDefaultChunkSize (ULONG32 chunkSize); + inline static void SetDefaultBucketChunkSize (ULONG32 chunkSize); + inline void SetBucketChunkSize (ULONG32 chunkSize); + + // In _DEBUG mode, this does a bunch of DPRINTF's... + void Dump() const; + +private: + inline HXBOOL Lookup(key_arg_type key, int& retItem) const; + HXBOOL LookupInBucket(ULONG32 bucket, key_arg_type key, int& retItem) const; + Item* LookupItem(ULONG32 bucket, key_arg_type key); + inline const Item* LookupItem(ULONG32 bucket, key_arg_type key) const + { + return ((CHXMapStringToString*)this)->LookupItem(bucket, key); + } + + // Internal function - key already verified not to exist + HXBOOL AddToBucket(ULONG32 bucket, key_arg_type key, value_arg_type value, int& retItem); + + inline POSITION Item2Pos(int item) const; + inline int Pos2Item(POSITION pos) const; + +private: + + HashFunc_t m_hf; + + ItemVec_t m_items; + HlxMap::IntVec_t m_free; + + CHlxMapBuckets m_buckets; + ULONG32 m_numBuckets; + ULONG32 m_chunkSize; + ULONG32 m_bucketChunkSize; + + // Members specific to the type of key and/or value goes below here. + void ConstructTypeSpecifics(); + inline HXBOOL IsKeyMatch (key_arg_type k1, key_arg_type k2) const + { + HXBOOL bRet; + + if (m_bCaseSens) bRet = (strcmp(k1, k2) == 0); + else bRet = (strcasecmp(k1, k2) == 0); + +#ifdef XXXSAB + printf ("IsKeyMatch(\"%s\", \"%s\") -> %s\n", + k1, k2, + bRet ? "true" : "false"); +#endif /* XXXSAB */ + + return bRet; + } + + bool m_bCaseSens; +}; + +int CHXMapStringToString::GetCount() const +{ + return m_items.size() - m_free.size(); +} + +HXBOOL CHXMapStringToString::IsEmpty() const +{ + return GetCount() == 0; +} + +ULONG32 CHXMapStringToString::GetHashTableSize() const +{ + return m_numBuckets; +} + +CHXMapStringToString::HashFunc_t CHXMapStringToString::SetHashFunc ( + CHXMapStringToString::HashFunc_t hf) +{ + HashFunc_t old = m_hf; + m_hf = hf; + return old; +} + +ULONG32 CHXMapStringToString::HashKey (key_arg_type key) const +{ + if (m_hf) return m_hf(key); + return m_bCaseSens ? DefaultHashFunc(key) : DefaultNoCaseHashFunc(key); +} + +void CHXMapStringToString::SetDefaultNumBuckets (ULONG32 numBuckets) +{ +#if !defined(HELIX_CONFIG_NOSTATICS) + z_defaultNumBuckets = numBuckets; +#endif +} + +void CHXMapStringToString::SetDefaultChunkSize (ULONG32 chunkSize) +{ +#if !defined(HELIX_CONFIG_NOSTATICS) + z_defaultChunkSize = chunkSize; +#endif +} + +void CHXMapStringToString::SetDefaultBucketChunkSize (ULONG32 chunkSize) +{ +#if !defined(HELIX_CONFIG_NOSTATICS) + z_defaultBucketChunkSize = chunkSize; +#endif +} + +void CHXMapStringToString::SetBucketChunkSize (ULONG32 chunkSize) +{ + m_bucketChunkSize = chunkSize; +} + +#endif // _CHXMAPSTRINGTOSTRING_H_ diff --git a/amarok/src/engine/helix/helix-sp/helix-include/common/container/hxbuffer.h b/amarok/src/engine/helix/helix-sp/helix-include/common/container/hxbuffer.h new file mode 100644 index 00000000..bcda2595 --- /dev/null +++ b/amarok/src/engine/helix/helix-sp/helix-include/common/container/hxbuffer.h @@ -0,0 +1,206 @@ + +/* + * + * This software is released under the provisions of the GPL version 2. + * see file "COPYING". If that file is not available, the full statement + * of the license can be found at + * + * http://www.fsf.org/licensing/licenses/gpl.txt + * + * Portions Copyright (c) 1995-2004 RealNetworks, Inc. All Rights Reserved. + * + */ + +#ifndef _HXBUFFER_H_ +#define _HXBUFFER_H_ + +#include "ihxpckts.h" +#include "hxvalue.h" +//#include "hxheap.h" +#include "hxstring.h" + +typedef struct +{ + UCHAR* m_pData; + ULONG32 m_ulLength; + unsigned char m_FreeWithMallocInterfaceIfAvail; +} _BigData; + +// This determines the length of the built in buffer that is used if the +// data length is small enough, to save us from allocating so many little +// pieces of data. +#define PnBufferShort +#ifdef PnBufferShort +// XXXNH: This value was originally 15 and was chosen after some research as +// an optimal size for bulk of the small strings we deal with in our buffers. +// However, since the size of the structure is larger than 15 bytes on +// 64-bit systems we are now using this compile-time size calculation to +// ensure that the structure is of sufficient size. +const int MaxPnbufShortDataLen = HX_MAX(sizeof(_BigData), 15); +#endif + +#define NUM_ALLOCATION_EACH_TIME 25 +/**************************************************************************** + * + * Class: + * + * CHXBuffer + * + * Purpose: + * + * PN implementation of a basic buffer. + * + */ +class CHXBuffer + : public IHXBuffer +{ +protected: + + LONG32 m_lRefCount; + ULONG32 m_ulAllocLength; + HXBOOL m_bJustPointToExistingData; + +#if !defined(HELIX_CONFIG_NOSTATICS) + // Interface for optional allocator + static IMalloc* m_zMallocInterface; +#endif + + // number of CHXBuffer allocated at a time to be placed in freeStore + static CHXBuffer* s_pFreeStore; + static const int s_iBufferChunk; + + virtual ~CHXBuffer(); + + HXBOOL FreeWithMallocInterface() const; + +#ifdef PnBufferShort + // buffer for small amounts of data + //UCHAR m_ShortData[MaxPnbufShortDataLen + 1]; +#endif + + enum { BigDataTag = 0xEE }; + + union + { + _BigData m_BigData; + + UCHAR m_ShortData[MaxPnbufShortDataLen + 1]; + }; + + bool IsShort() const; + HX_RESULT SetSize(ULONG32 ulLength, HXBOOL copyExistingData); + + UCHAR* Allocate(UINT32 size) const; + UCHAR* Reallocate(UCHAR*, UINT32 oldSize, UINT32 newSize) const; + void Deallocate(UCHAR*) const; + + +public: + CHXBuffer(); + CHXBuffer(UCHAR* pData, UINT32 ulLength, HXBOOL bOwnBuffer = TRUE); + +#if 0 +#ifndef __MWERKS__ +#if defined (_DEBUG) && defined (_WIN32) && 0 + void * operator new( + unsigned int, + int, + const char *, + int + ); +#else + void * operator new (size_t size); +#endif /*defined (_DEBUG) && defined (_WIN32) */ + void operator delete(void *p, size_t size); +#endif /*__MWERKS__*/ +#endif /*0*/ + + inline CHXBuffer& operator=(const char* psz); + inline CHXBuffer& operator=(const unsigned char* psz); + inline CHXBuffer& operator=(const CHXString &str); + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj); + + STDMETHOD_(ULONG32,AddRef) (THIS); + + STDMETHOD_(ULONG32,Release) (THIS); + + /* + * IHXBuffer methods + */ + STDMETHOD(Get) (THIS_ + REF(UCHAR*) pData, + REF(ULONG32) ulLength); + + STDMETHOD(Set) (THIS_ + const UCHAR* pData, + ULONG32 ulLength); + + STDMETHOD(SetSize) (THIS_ + ULONG32 ulLength); + + STDMETHOD_(ULONG32,GetSize) (THIS); + + STDMETHOD_(UCHAR*,GetBuffer) + (THIS); + + +public: + static HX_RESULT FromCharArray + ( + const char* szIn, + IHXBuffer** ppbufOut + ); + static HX_RESULT FromCharArray + ( + const char* szIn, + UINT32 ulLength, + IHXBuffer** ppbufOut + ); + static void SetAllocator(IMalloc* pMalloc); + static void ReleaseAllocator(); +}; + +CHXBuffer& CHXBuffer::operator=(const char* psz) +{ + Set((const unsigned char*)psz, strlen(psz)+1); + return(*this); +} + +CHXBuffer& CHXBuffer::operator=(const unsigned char* psz) +{ + Set(psz, strlen((const char*)psz)+1); + return(*this); +} + +CHXBuffer& CHXBuffer::operator=(const CHXString& str) +{ + Set((const unsigned char*)(const char *)str, str.GetLength()+1); + return(*this); +} + + +// This class was created in order to be able to have a buffer that consists of +// a subset of another existing buffer without allocating any new data or +// copying data over. The way to use this class is to instantiate it with 3 +// parameters: +// 1) A pointer to the superset buffer, +// 2) The pointer to the point in the the buffer that represents the start of +// the subset buffer, and +// 3) The length of the subset buffer. +// +class CHXBufferFragment : public CHXBuffer +{ +public : + CHXBufferFragment(IHXBuffer * pWrappedBuffer, UCHAR* pModFrameStart, ULONG32 ulFragLen) : CHXBuffer( pModFrameStart, ulFragLen, FALSE ), m_pHXBufferPointedTo(pWrappedBuffer){ if(pWrappedBuffer) {pWrappedBuffer->AddRef();} }; + ~CHXBufferFragment(){ HX_RELEASE(m_pHXBufferPointedTo);} + +protected : + IHXBuffer * m_pHXBufferPointedTo; +}; + +#endif diff --git a/amarok/src/engine/helix/helix-sp/helix-include/common/container/hxmap.h b/amarok/src/engine/helix/helix-sp/helix-include/common/container/hxmap.h new file mode 100644 index 00000000..797b04ae --- /dev/null +++ b/amarok/src/engine/helix/helix-sp/helix-include/common/container/hxmap.h @@ -0,0 +1,22 @@ + +/* + * + * This software is released under the provisions of the GPL version 2. + * see file "COPYING". If that file is not available, the full statement + * of the license can be found at + * + * http://www.fsf.org/licensing/licenses/gpl.txt + * + * Portions Copyright (c) 1995-2004 RealNetworks, Inc. All Rights Reserved. + * + */ + +#ifndef _HXMAP_H_ +#define _HXMAP_H_ + +#include "chxmapptrtoptr.h" +#include "chxmapstringtoob.h" +#include "chxmapstringtostring.h" +#include "chxmaplongtoobj.h" + +#endif // _HXMAP_H_ diff --git a/amarok/src/engine/helix/helix-sp/helix-include/common/container/hxmaputils.h b/amarok/src/engine/helix/helix-sp/helix-include/common/container/hxmaputils.h new file mode 100644 index 00000000..48faa7c3 --- /dev/null +++ b/amarok/src/engine/helix/helix-sp/helix-include/common/container/hxmaputils.h @@ -0,0 +1,211 @@ + +/* + * + * This software is released under the provisions of the GPL version 2. + * see file "COPYING". If that file is not available, the full statement + * of the license can be found at + * + * http://www.fsf.org/licensing/licenses/gpl.txt + * + * Portions Copyright (c) 1995-2004 RealNetworks, Inc. All Rights Reserved. + * + */ + +#ifndef _HLXMAPUTILS_H_ +#define _HLXMAPUTILS_H_ + +#include "hxtypes.h" +#include "hxassert.h" + +typedef void* POSITION; // XXXSAB: where does this belong? + +#if defined(HELIX_CONFIG_LOW_HEAP_HASH_TABLE) +// The default values to the hash class result in terrible heap consumption. +// Using absolute minimal values here. Not much of a hash table now though! +#define CHUNK_INIT 1 +#else +#define CHUNK_INIT 16 +#endif + +#define DECLARE_ITEMVEC(CLASS,ITEM,NIL,CHUNK,INIT) \ + class CLASS \ + { \ + public: \ + CLASS(); \ + CLASS(int num);\ + CLASS(int num, const ITEM& item); \ + CLASS(const CLASS& from); \ + CLASS& operator= (const CLASS& from); \ + ~CLASS(); \ + \ + inline ITEM& operator[] (int idx) \ + { \ + return m_items[idx]; \ + /* return (idx >= 0 && idx < m_used ? m_items[idx] : nil()); */ \ + } \ + \ + inline const ITEM& operator[] (int idx) const \ + { \ + return m_items[idx]; \ + /* return (idx >= 0 && idx < m_used ? m_items[idx] : nil()); */ \ + } \ + \ + inline bool empty () const { return m_used <= 0; } \ + inline int size () const { return m_used; } \ + inline void resize (int s) \ + { \ + resize(s, INIT); \ + } \ + \ + void resize (int s, const ITEM& item); \ + \ + inline int capacity () const { return m_alloc; } \ +\ + void reserve (int s); \ +\ + inline void SetChunkSize (int chunk) { m_chunkSize = chunk; } \ + void GrowBy (int by); \ + \ + CLASS& push_back (const ITEM& item); \ + \ + inline CLASS& pop_back () \ + { \ + HX_ASSERT (m_used > 0); \ + if (m_used > 0) --m_used; \ + return *this; \ + } \ + \ + inline ITEM& back() \ + { \ + HX_ASSERT (m_items); HX_ASSERT (m_used > 0); \ + return m_items[m_used-1]; \ + } \ + \ + void zap (int idx, int numToZap = 1); \ + \ + private: \ + ITEM* m_items; \ + int m_alloc; \ + int m_used; \ + UINT16 m_chunkSize; \ + } + +#define DECLARE_ITEMVEC_IMP(PARENT, CLASS,ITEM,NIL,CHUNK,INIT) \ + PARENT::CLASS::CLASS() : \ + m_items(0), m_alloc(0), m_used(0), m_chunkSize(CHUNK) \ + { \ + } \ + \ + PARENT::CLASS::CLASS(int num) : \ + m_items(0), m_alloc(0), m_used(0), m_chunkSize(CHUNK) \ + { \ + if (num > 0) \ + { \ + m_items = new ITEM[num]; \ + m_used = m_alloc = num; \ + for (int i = 0; i < num; ++i) m_items[i] = INIT; \ + } \ + } \ + \ + PARENT::CLASS::CLASS(int num, const ITEM& item) : \ + m_items(0), m_alloc(0), m_used(0), m_chunkSize(CHUNK) \ + { \ + if (num > 0) \ + { \ + m_items = new ITEM[num]; \ + m_used = m_alloc = num; \ + for (int i = 0; i < num; ++i) m_items[i] = item; \ + } \ + } \ + \ + PARENT::CLASS::CLASS(const PARENT::CLASS& from) : \ + m_items(0), m_alloc(0), m_used(0), m_chunkSize(CHUNK) \ + { \ + m_used = from.m_used; \ + m_alloc = from.m_alloc; \ + m_items = new ITEM[m_alloc]; \ + for (int i = 0; i < m_used; ++i) m_items[i] = from.m_items[i]; \ + } \ + \ + PARENT::CLASS& PARENT::CLASS::operator= (const PARENT::CLASS& from) \ + { \ + if (m_items != from.m_items) \ + { \ + HX_VECTOR_DELETE(m_items); \ + m_used = from.m_used; \ + m_alloc = from.m_alloc; \ + m_items = new ITEM[m_alloc]; \ + for (int i = 0; i < m_used; ++i) m_items[i] = from.m_items[i]; \ + } \ + return *this; \ + } \ + \ + PARENT::CLASS::~CLASS() { HX_VECTOR_DELETE(m_items); } \ + \ + \ + void PARENT::CLASS::resize (int s, const ITEM& item) \ + { \ + reserve(s); \ + for (int i = m_used; i < s; ++i) m_items[i] = item; \ + m_used = s; \ + } \ + \ + void PARENT::CLASS::reserve (int s) \ + { \ + if (s > m_alloc) \ + { \ + ITEM* newItems = new ITEM[s]; \ + if( newItems ){ \ + for (int i = 0; i < m_used; ++i) newItems[i] = m_items[i]; \ + HX_VECTOR_DELETE (m_items); \ + m_items = newItems; \ + m_alloc = s; \ + } \ + } \ + } \ + \ + void PARENT::CLASS::GrowBy (int by) \ + { \ + /* If no chunkSize specified, \ + use the larger of 16 and the currently allocated amount. \ + */ \ + \ + int chunk = m_chunkSize > 0 ? m_chunkSize : MAX (m_alloc, CHUNK_INIT); \ + int newAlloc = m_alloc + ((by + chunk - 1) / chunk) * chunk; \ + reserve (newAlloc); \ + } \ + \ + PARENT::CLASS& PARENT::CLASS::push_back (const ITEM& item) \ + { \ + if (m_used == m_alloc) GrowBy (1); \ + HX_ASSERT (m_used < m_alloc); \ + m_items[m_used++] = item; \ + return *this; \ + } \ + \ + void PARENT::CLASS::zap (int idx, int numToZap) \ + { \ + HX_ASSERT (idx >= 0 && idx < m_used); \ + \ + if ((idx + numToZap) >= m_used) \ + { \ + m_used = idx; \ + } \ + else \ + { \ + int src = idx + numToZap; \ + int dest = idx; \ + for (; src < m_used; ++src, ++dest) \ + m_items[dest] = m_items[src]; \ + m_used -= numToZap; \ + } \ + } + +struct HlxMap +{ + DECLARE_ITEMVEC(IntVec_t, int, 0, 0, 0); + + static ULONG32 StrHashFunc (const char* key, bool bCaseSens); +}; + +#endif // _HLXMAPUTILS_H_ diff --git a/amarok/src/engine/helix/helix-sp/helix-include/common/container/hxstring.h b/amarok/src/engine/helix/helix-sp/helix-include/common/container/hxstring.h new file mode 100644 index 00000000..6a0657c3 --- /dev/null +++ b/amarok/src/engine/helix/helix-sp/helix-include/common/container/hxstring.h @@ -0,0 +1,559 @@ + +/* + * + * This software is released under the provisions of the GPL version 2. + * see file "COPYING". If that file is not available, the full statement + * of the license can be found at + * + * http://www.fsf.org/licensing/licenses/gpl.txt + * + * Portions Copyright (c) 1995-2004 RealNetworks, Inc. All Rights Reserved. + * + */ + +#ifndef HXSTRING_H +#define HXSTRING_H + +#include "hxtypes.h" +#include "hxassert.h" +#include "hlxclib/limits.h" +#include "hlxclib/string.h" + +#if defined(HELIX_CONFIG_NOSTATICS) +# include "globals/hxglobalchxstring.h" +#endif + + +typedef INT32 (*StringGrowthFunc)(INT32 currentSize, INT32 sizeNeeded); + +class CHXStringRep +{ +public: + CHXStringRep(INT32 bufSize = 1, bool bSetLength = false); + CHXStringRep(const char* pStr); + CHXStringRep(const char* pStr, INT32 bufSize); + CHXStringRep(char ch, INT32 bufSize); + ~CHXStringRep(); + + void AddRef(); + void Release(); + + char* GetBuffer(); + INT32 GetStringSize() const; + INT32 GetBufferSize() const; + + void SetStringSize(INT32 strSize); + void Resize(INT32 newSize); + void ResizeAndCopy(INT32 newSize, bool bSetLength = false); + + void Copy(const char* pStr, INT32 size); + + bool IsShared() const; + +private: + CHXStringRep(const CHXStringRep&); + CHXStringRep& operator=(const CHXStringRep&); + + INT32 m_refCount; + INT32 m_strSize; + INT32 m_bufSize; + char* m_pData; +}; + +class HXEXPORT_CLASS CHXString +{ +public: + CHXString(StringGrowthFunc pGrowthFunc = 0); + CHXString(const CHXString& rhs); + CHXString(char ch, int length = 1, StringGrowthFunc pGrowthFunc = 0); + CHXString(const char* pStr, StringGrowthFunc pGrowthFunc = 0); + CHXString(const char* pStr, int length, StringGrowthFunc pGrowthFunc = 0); + CHXString(const unsigned char* pStr, StringGrowthFunc pGrowthFunc = 0); + ~CHXString(); + + // Attributes & Operations + // as an array of characters + UINT32 GetLength() const; + HXBOOL IsEmpty() const; + void Empty(); + + char GetAt(INT32 i) const; + char operator[](short i) const; + char operator[](unsigned short i) const; + char operator[](int i) const; + char operator[](unsigned int i) const; + char operator[](long i) const; + char operator[](unsigned long i) const; + + void SetAt(INT32 i, char ch); + operator const char*() const; + + bool operator>(const CHXString& rhs) const; + bool operator>(const char* pStr) const; + bool operator>(const unsigned char* pStr) const; + bool operator>=(const CHXString& rhs) const; + bool operator>=(const char* pStr) const; + bool operator>=(const unsigned char* pStr) const; + bool operator==(const CHXString& rhs) const; + bool operator==(const char* pStr) const; + bool operator==(const unsigned char* pStr) const; + bool operator!=(const CHXString& rhs) const; + bool operator!=(const char* pStr) const; + bool operator!=(const unsigned char* pStr) const; + bool operator<=(const CHXString& rhs) const; + inline bool operator<=(const char* pStr) const; + inline bool operator<=(const unsigned char* pStr) const; + bool operator<(const CHXString& rhs) const; + inline bool operator<(const char* pStr) const; + inline bool operator<(const unsigned char* pStr) const; + + const CHXString& operator=(const CHXString& rhs); + const CHXString& operator=(char ch); + const CHXString& operator=(const char* pStr); + const CHXString& operator=(const unsigned char* pStr); + + const CHXString& operator+=(const CHXString& rhs); + const CHXString& operator+=(char ch); + const CHXString& operator+=(const char* pStr); + + friend CHXString operator+(const CHXString& strA, + const CHXString& strB); + + friend CHXString operator+(const CHXString& str, char ch); + friend CHXString operator+(char ch , const CHXString& str); + friend CHXString operator+(const CHXString& strA, const char* pStrB); + friend CHXString operator+(const char* pStrA, const CHXString& strB); + + INT32 Compare(const char* pStr) const; + INT32 CompareNoCase(const char* pStr) const; + + void Center(short length); + CHXString Mid(INT32 i, INT32 length) const; + CHXString Mid(INT32 i) const; + CHXString Left(INT32 length) const; + CHXString Right(INT32 length) const; + + ULONG32 CountFields(char delim) const; + CHXString GetNthField(char delim, ULONG32 i, UINT64& state) const; + CHXString NthField(char delim, ULONG32 i) const; + + CHXString SpanIncluding(const char* pCharSet) const; + CHXString SpanExcluding(const char* pCharSet) const; + + void MakeUpper(); + void MakeLower(); + + void TrimRight(); + void TrimLeft(); + + INT32 Find(char ch) const; + INT32 ReverseFind(char ch) const; + HXBOOL FindAndReplace(const char* pSearch , const char* pReplace, + HXBOOL bReplaceAll = FALSE); + + INT32 Find(const char* pStr) const; + + void Format(const char* pFmt, ...); + + void AppendULONG(ULONG32 value); + + void AppendEndOfLine(); + + char* GetBuffer(INT32 minSize); + void ReleaseBuffer(INT32 newSize = -1); + char* GetBufferSetLength(INT32 newSize); + void FreeExtra(); + + INT32 GetAllocLength() const; + INT32 SetMinBufSize(INT32 minSize); + +#if defined(_MACINTOSH) || defined(_MAC_UNIX) + const CHXString& SetFromStr255(const Str255 ); + + const CHXString& AppendFromStr255(const Str255 ); + const CHXString& InsertFromStr255(const Str255 ); + + const CHXString& SetFromIndString(short , short ); + + const CHXString& operator =(FSSpec ); + operator const FSSpec(void); + + HX_RESULT MakeStr255(Str255& ) const; + +#if !defined(_CARBON) && !defined(_MAC_UNIX) + operator Str255* (void); + operator const Str255* (void) const; + operator ConstStr255Param (void) const; +#else + const CHXString& operator =(const FSRef& ); + // ConstStr255Param operator disallowed since + // it's not implemented in Carbon + //operator ConstStr255Param (void) const; + operator const FSRef(void); + + const CHXString& operator =(CFStringRef ); + + HX_RESULT SetFromCFString(CFStringRef , CFStringEncoding ); + + HX_RESULT SetFromHFSUniStr255(const HFSUniStr255& , CFStringEncoding ); + HX_RESULT MakeHFSUniStr255(HFSUniStr255& , CFStringEncoding ) const; +#endif /* !defined(_CARBON) */ + +#endif /* _MACINTOSH */ + +protected: + + void Init(const char* pStr, UINT32 size = UINT_MAX ); + void Nuke(); + void ConcatInPlace(const char* pStr, const UINT32 size); + void EnsureUnique(); // Make sure that m_pRep is not shared + void Release(); + static UINT32 SafeStrlen(const char* ); + +//#define CHXSMEMCHK +#ifdef CHXSMEMCHK + INT32 m_memchkCopied; + INT32 m_memchkChanged; + INT32 m_memchkDataLength; + static INT32 g_memchkCopiedNotChanged; + static INT32 g_memchkCopiedChanged; + static INT32 g_memchkCounter; + static INT32 g_memchkHighCount; + static INT32 g_memchkTotalBytesNotChanged; + char* m_memchkData; + static memchkWhatever Dummy; + + static void memchkLogStats(void); +#endif /* CHXSMEMCHK */ + +private: + static INT32 MinimalGrowth(INT32 currentSize, INT32 sizeNeeded); + static INT32 DoublingGrowth(INT32 currentSize, INT32 sizeNeeded); + + void Append(const char* pStr, INT32 size); + void Grow(INT32 newSize); + + CHXStringRep* m_pRep; + StringGrowthFunc m_pGrowthFunc; +}; + +#if !defined(HELIX_CONFIG_NOSTATICS) +extern const CHXString HXEmptyString; +#else +extern const char* const _g_emptyString; +#define HXEmptyString HXGlobalCHXString::Get(&_g_emptyString) +#endif + +inline +char* CHXStringRep::GetBuffer() +{ + return m_pData; +} + +inline +INT32 CHXStringRep::GetStringSize() const +{ + return m_strSize; +} + +inline +INT32 CHXStringRep::GetBufferSize() const +{ + return m_bufSize; +} + +inline +void CHXStringRep::SetStringSize(INT32 strSize) +{ + HX_ASSERT(strSize >= 0); + HX_ASSERT(strSize < m_bufSize); + HX_ASSERT((size_t)strSize == strlen(m_pData)); + m_strSize = strSize; +} + +inline +bool CHXStringRep::IsShared() const +{ + return (m_refCount > 1); +} + +inline +UINT32 CHXString::GetLength() const +{ + return (m_pRep) ? m_pRep->GetStringSize() : 0; +} + +inline +HXBOOL CHXString::IsEmpty() const +{ + return (GetLength() == 0); +} + +inline +char CHXString::GetAt(INT32 i) const +{ + HX_ASSERT(m_pRep); + HX_ASSERT(i >= 0); + HX_ASSERT(i < m_pRep->GetBufferSize()); + return m_pRep->GetBuffer()[i]; +} + +inline +char CHXString::operator[](short i) const +{ + HX_ASSERT(m_pRep); + HX_ASSERT(i >= 0); + HX_ASSERT(i < m_pRep->GetBufferSize()); + return m_pRep->GetBuffer()[i]; +} + +inline +char CHXString::operator[](unsigned short i) const +{ + HX_ASSERT(m_pRep); + HX_ASSERT((INT32)i < m_pRep->GetBufferSize()); + return m_pRep->GetBuffer()[i]; +} + +inline +char CHXString::operator[](int i) const +{ + HX_ASSERT(m_pRep); + HX_ASSERT(i >= 0); + HX_ASSERT(i < m_pRep->GetBufferSize()); + return m_pRep->GetBuffer()[i]; +} + +inline +char CHXString::operator[](unsigned int i) const +{ + HX_ASSERT(m_pRep); + HX_ASSERT(i < (unsigned int)m_pRep->GetBufferSize()); + return m_pRep->GetBuffer()[i]; +} + +inline +char CHXString::operator[](long i) const +{ + HX_ASSERT(m_pRep); + HX_ASSERT(i >= 0); + HX_ASSERT(i < m_pRep->GetBufferSize()); + return m_pRep->GetBuffer()[i]; +} + +inline +char CHXString::operator[](unsigned long i) const +{ + HX_ASSERT(m_pRep); + HX_ASSERT(i < (unsigned long)m_pRep->GetBufferSize()); + return m_pRep->GetBuffer()[i]; +} + +inline +CHXString::operator const char*() const +{ + return (m_pRep) ? m_pRep->GetBuffer() : (const char*)(&m_pRep); +} + +inline +INT32 CHXString::Compare(const char* pStr) const +{ + return strcmp((const char*)(*this), pStr); +} + +inline +INT32 CHXString::CompareNoCase(const char* pStr) const +{ + return strcasecmp((const char*)(*this), pStr); +} + +inline +bool CHXString::operator>(const char* pStr) const +{ + return (Compare(pStr) > 0); +} + +inline +bool CHXString::operator>(const CHXString& rhs) const +{ + return (*this > ((const char*)rhs)); +} + +inline +bool CHXString::operator>(const unsigned char* pStr) const +{ + return (*this > ((const char*)pStr)); +} + +inline +bool operator>(const char* pA, const CHXString& b) +{ + return (b < pA); +} + +inline +bool operator>(const unsigned char* pA, const CHXString& b) +{ + return (b < pA); +} + +inline +bool CHXString::operator>=(const char* pStr) const +{ + return (Compare(pStr) >= 0); +} + +inline +bool CHXString::operator>=(const CHXString& rhs) const +{ + return (*this >= ((const char*)rhs)); +} + +inline +bool CHXString::operator>=(const unsigned char* pStr) const +{ + return (*this >= ((const char*)pStr)); +} + +inline +bool operator>=(const char* pA, const CHXString& b) +{ + return (b <= pA); +} + +inline +bool operator>=(const unsigned char* pA, const CHXString& b) +{ + return (b <= pA); +} + +inline +bool CHXString::operator==(const char* pStr) const +{ + return (strcmp(((const char*)*this), pStr) == 0); +} + +inline +bool CHXString::operator==(const CHXString& rhs) const +{ + return ((m_pRep == rhs.m_pRep) || + ((GetLength() == rhs.GetLength()) && + (*this == ((const char*)rhs)))); +} + +inline +bool CHXString::operator==(const unsigned char* pStr) const +{ + return (*this == ((const char*)pStr)); +} + +inline +bool operator==(const char* pA, const CHXString& b) +{ + return (b == pA); +} + +inline +bool operator==(const unsigned char* pA, const CHXString& b) +{ + return (b == pA); +} + +inline +bool CHXString::operator!=(const char* pStr) const +{ + return (strcmp(((const char*)*this), pStr) != 0); +} + +inline +bool CHXString::operator!=(const CHXString& rhs) const +{ + return ((m_pRep != rhs.m_pRep) && + ((GetLength() != rhs.GetLength()) || + (*this != ((const char*)rhs)))); +} + +inline +bool CHXString::operator!=(const unsigned char* pStr) const +{ + return (*this != ((const char*)pStr)); +} + +inline +bool operator!=(const char* pA, const CHXString& b) +{ + return (b != pA); +} + +inline +bool operator!=(const unsigned char* pA, const CHXString& b) +{ + return (b != pA); +} + +inline +bool CHXString::operator<=(const char* pStr) const +{ + return (Compare(pStr) <= 0); +} + +inline +bool CHXString::operator<=(const CHXString& rhs) const +{ + return (*this <= ((const char*)rhs)); +} + +inline +bool CHXString::operator<=(const unsigned char* pStr) const +{ + return (*this <= ((const char*)pStr)); +} + +inline +bool operator<=(const char* pA, const CHXString& b) +{ + return (b >= pA); +} + +inline +bool operator<=(const unsigned char* pA, const CHXString& b) +{ + return (b >= pA); +} + +inline +bool CHXString::operator<(const char* pStr) const +{ + return (Compare(pStr) < 0); +} + +inline +bool CHXString::operator<(const CHXString& rhs) const +{ + return (*this < ((const char*)rhs)); +} + +inline +bool CHXString::operator<(const unsigned char* pStr) const +{ + return (*this < ((const char*)pStr)); +} + +inline +bool operator<(const char* pA, const CHXString& b) +{ + return (b > pA); +} + +inline +bool operator<(const unsigned char* pA, const CHXString& b) +{ + return (b > pA); +} + +inline +UINT32 CHXString::SafeStrlen(const char* pStr) +{ + return (pStr) ? strlen(pStr) : 0; +} +#endif /* HXSTRING_H */ diff --git a/amarok/src/engine/helix/helix-sp/helix-include/common/dbgtool/hxassert.h b/amarok/src/engine/helix/helix-sp/helix-include/common/dbgtool/hxassert.h new file mode 100644 index 00000000..dd841fe2 --- /dev/null +++ b/amarok/src/engine/helix/helix-sp/helix-include/common/dbgtool/hxassert.h @@ -0,0 +1,727 @@ + +/* + * + * This software is released under the provisions of the GPL version 2. + * see file "COPYING". If that file is not available, the full statement + * of the license can be found at + * + * http://www.fsf.org/licensing/licenses/gpl.txt + * + * Portions Copyright (c) 1995-2004 RealNetworks, Inc. All Rights Reserved. + * + */ + +///////////////////////////////////////////////////////////////////////////// +// HXASSERT.H +// +// Debugging support header. +// +// HX_ASSERT() - asserts an expression is TRUE. Compiles to no-ops in +// retail builds. Provides message box or other UI when +// expression fails. +// +// HX_ASSERT_VALID_PTR() - asserts that a pointer is valid. Performs more +// rigid verification specifically appropriate for pointers. +// +// HX_VERIFY() - verifies an expression is TRUE. Expression or code DOES NOT +// compile away in retail builds, but UI of failure is removed. +// In debug builds provides message box or other UI when +// expression fails. +// +// HX_TRACE() - Similar to DEBUGPRINTF() but no buffer is required. +// Compiles to no-ops in retail builds. +// + +#ifndef _HXASSERT_H_ +#define _HXASSERT_H_ + +#include "hlxclib/assert.h" + +#ifndef ASSERT +#if defined (DEBUG) || defined (_DEBUG) +#if defined (_OSF1) && defined (_NATIVE_COMPILER) +# define ASSERT(x) assert(((long)(x)) != 0L) +#else +# define ASSERT(x) assert(x) +#endif +#else +# define ASSERT(x) /* x */ +#endif /* DEBUG */ +#endif /* ndef ASSERT */ + +#include "hlxclib/limits.h" +#include "hxtypes.h" +#include "hxresult.h" // for HX_RESULT +#include "hlxclib/stdio.h" // for sprintf + +#ifdef _SUN +#include <stddef.h> // for size_t +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#if defined (_SOLARIS) || defined (_IRIX) || defined (_DECALPHA) +#ifndef __inline +#define __inline inline +#endif +#endif + +#if defined (_OSF1) && defined (_NATIVE_COMPILER) +#if defined __cplusplus +#define __inline inline +#else +#define __inline static +#endif /* __cplusplus */ +#endif + +#ifdef _HPUX +#if defined __cplusplus +#define __inline inline +#else +#define __inline +#endif /* __cplusplus */ +#endif + +#if defined _AIX +#if defined __cplusplus +#define __inline inline +#else +#define __inline +#endif /* __cplusplus */ +#endif /* _AIX */ + +#ifdef _UNIX +#include <signal.h> /* For kill() */ +#include <unistd.h> /* For getpid() */ +#include <stdio.h> +void HXUnixDebugBreak(); +#endif + + +///////////////////////////////////////////////////////////////////////////// +// Diagnostic support + +// For _MAX_PATH +#if defined ( _MACINTOSH ) +#include "platform/mac/maclibrary.h" +#elif defined (_UNIX) +#include <stdlib.h> +#if !defined(_VXWORKS) +#include <sys/param.h> +#endif +#define _MAX_PATH MAXPATHLEN +#elif defined (_WINDOWS) +#include <stdlib.h> +#endif + +#ifdef _SYMBIAN +# include <unistd.h> +# define _MAX_PATH MAXPATHLEN +#endif + +#ifdef _OPENWAVE +#ifndef _MAX_PATH +#define _MAX_PATH 256 +#endif +#endif + +#if defined (DEBUG) || defined (_DEBUG) + +#ifdef _MACINTOSH +# define MAX_TRACE_OUTPUT 255 +#else +# ifdef _UNIX +# define MAX_TRACE_OUTPUT 255 +# elif _WIN16 +# define MAX_TRACE_OUTPUT 255 +# elif _SYMBIAN +# define MAX_TRACE_OUTPUT 255 +# else +# define MAX_TRACE_OUTPUT (_MAX_PATH*2 + 20) +# endif +#endif + + +///////////////////////////////////////////////////////////////////////////// +// +// HXDebugOptionEnabled: +// Determine if the given debug option is enabled. +// A lookup is done to the registry key, and if it's present +// and set to '1', TRUE is returned otherwise FALSE +// Similar to HXWantTraceMessages, except not specific to one option. +#ifdef _WIN16 +// The STDMETHODCALLTYPE includes the _export keyword, even though this is +// a static, not dll, library. Seems to work on win32 ok, but not a win16 +// .exe. rpapp would see this as duplicate symbol, until the _export was +// removed, and all the libraries that rpapp linked with rebuilt. There +// may be a define for STDMETHODCALLTYPE without the _export that should be +// used here. XXXTW. april 98. +HXBOOL far _cdecl HXDebugOptionEnabled(const char* szOption); +#else +HXBOOL STDMETHODCALLTYPE HXDebugOptionEnabled(const char* szOption); +#endif + +///////////////////////////////////////////////////////////////////////////// +// +// HXWantTraceMessages: +// Helper function used to determine if the system has asked for trace +// messages. +// +HXBOOL STDMETHODCALLTYPE HXWantTraceMessages(); + +///////////////////////////////////////////////////////////////////////////// +// +// HXOutputDebugString: +// Helper function used by DEBUGOUTSTR(). This is better than +// OutputDebugString, because it will check to see if output +// tracing is turned off in the registry. This prevents the massive +// slew of output messages. +// +void STDMETHODCALLTYPE HXOutputDebugString(const char* pString); + +///////////////////////////////////////////////////////////////////////////// +// +// HXTrace: Helper function used by HX_TRACE() +// +void STDMETHODVCALLTYPE HXTrace(const char* pszFormat, ...); + + +///////////////////////////////////////////////////////////////////////////// +// +// HXAssertFailedLine: Helper function used by HX_ASSERT() +// +#ifdef _WIN16 +// The STDMETHODCALLTYPE includes the _export keyword, even though this is +// a static, not dll, library. Seems to work on win32 ok, but not a win16 +// .exe. rpapp would see this as duplicate symbol, until the _export was +// removed, and all the libraries that rpapp linked with rebuilt. There +// may be a define for STDMETHODCALLTYPE without the _export that should be +// used here. XXXTW. april 98. +HXBOOL far _cdecl HXAssertFailedLine(const char* pszExpression, const char* pszFileName, int nLine); +#else +HXBOOL STDMETHODCALLTYPE HXAssertFailedLine(const char* pszExpression, const char* pszFileName, int nLine); +#endif + +///////////////////////////////////////////////////////////////////////////// +// +// HXAssertValidPointer, HXIsValidAddress: Helper functions used by +// HX_ASSERT_VALID_PTR() +// +void STDMETHODCALLTYPE HXAssertValidPointer(const void* pVoid, const char* pszFileName, int nLine); +#ifdef __cplusplus +#ifdef _WIN16 + // see note above on the problem with STDMETHODCALLTYPE on win16 + HXBOOL far _cdecl HXIsValidAddress(const void* lp, ULONG32 nBytes = 1, HXBOOL bReadWrite = TRUE); +#else + HXBOOL STDMETHODCALLTYPE HXIsValidAddress(const void* lp, ULONG32 nBytes = 1, HXBOOL bReadWrite = TRUE); +#endif +#else +#ifdef _WIN16 + // see note above on the problem with STDMETHODCALLTYPE on win16 + HXBOOL far _cdecl HXIsValidAddress(const void* lp, ULONG32 nBytes, HXBOOL bReadWrite); +#else + HXBOOL STDMETHODCALLTYPE HXIsValidAddress(const void* lp, ULONG32 nBytes, HXBOOL bReadWrite); +#endif +#endif + +#ifdef _WIN16 +HXBOOL far _cdecl HXIsValidString(const char* lpsz, int nLength); +#else +HXBOOL STDMETHODCALLTYPE HXIsValidString(const char* lpsz, int nLength); +#endif + +///////////////////////////////////////////////////////////////////////////// +// +// HXDebugBreak: used to break into debugger at critical times +// +#ifndef HXDebugBreak +// by default, debug break is asm int 3, or a call to DebugBreak, or nothing + +#if defined(_WINDOWS) +#if !defined(_M_IX86) +#include "windows.h" +#define HXDebugBreak() DebugBreak() +#else +#define HXDebugBreak() _asm { int 3 } +#endif // _M_ALPHA +#elif defined( _MACINTOSH ) +#define HXDebugBreak() Debugger() +#elif defined(_OPENWAVE) +#if defined(_OPENWAVE_SIMULATOR) // windows app... +#define HXDebugBreak() _asm { int 3 } +#else +void HXDebugBreak(); +#endif +#elif defined(_SYMBIAN) +#if defined(__WINS__) +#define HXDebugBreak() _asm { int 3 } +#else +void HXDebugBreak(); +#endif //_SYMBIAN +#elif defined(_UNIX) +void HXDebugBreak(); +#elif defined(_VXWORKS) +#include <taskLib.h> +#define HXDebugBreak() (taskSuspend(taskIdSelf())) + +#endif // end of#if defined(_WIN32) || defined(_WINDOWS) + +#endif // end of#ifndef HXDebugBreak + +#ifdef _UNIX +#include "signal.h" +#include "stdlib.h" + +#define HX_ENABLE_JIT_DEBUGGING() \ +do { \ + signal (SIGSEGV, (void (*)(int))HXDebugBreak); \ + signal (SIGBUS, (void (*)(int))HXDebugBreak); \ + signal (SIGFPE, (void (*)(int))HXDebugBreak); \ + if (!getenv("PROCESS_NAME")) \ + { \ + char *progname = new char[strlen(argv[0]) + 30]; \ + sprintf(progname, "PROCESS_NAME=%s", argv[0]); /* Flawfinder: ignore */ \ + putenv(progname); \ + } \ +} while (0); +#else +#define HX_ENABLE_JIT_DEBUGGING() +#endif + +///////////////////////////////////////////////////////////////////////////// +// +// HXAbort: used to shut down the application at critical times +// +#ifndef HXAbort + +#if (defined(_WIN32) || defined(_WINDOWS)) && !defined(WIN32_PLATFORM_PSPC) +# define HXAbort() abort() +#elif defined(WIN32_PLATFORM_PSPC) +# define HXAbort() exit(1) +#elif defined(_SYMBIAN) +# define HXAbort() exit(1) +#elif defined(_OPENWAVE) +// XXXSAB is this right?? +# define HXAbort() exit(1) +#elif defined ( _MACINTOSH ) +# define HXAbort() DebugStr("\pHXAbort: Please exit this program.") +#elif defined ( _UNIX ) +# define HXAbort() printf("\npnabort: Please exit this program.\n") +#endif // end of#if defined(_WIN32) || defined(_WINDOWS) + +#endif // end of#ifndef HXAbort + + +///////////////////////////////////////////////////////////////////////////// +// +// HX_TRACE: see above. +// +#define HX_TRACE ::HXTrace + +///////////////////////////////////////////////////////////////////////////// +// +// HX_ASSERT: see above. +// +#define HX_ASSERT(f) \ + do \ + { \ + if (!(f) && HXAssertFailedLine(#f, __FILE__, __LINE__)) \ + HXDebugBreak(); \ + } while (0) \ + + +#define REQUIRE_REPORT(targ,file,line) HXAssertFailedLine(targ,file,line) + + + + +///////////////////////////////////////////////////////////////////////////// +// +// Macros Defined for logging messges in the player. +// +// STARTLOGSINK: Query for an IHXErrorSink +// +// LOGINFO: Report the error to the error sink +// +// STOPLOGSINK: Shutdown and Release the error sink. +// +///////////////////////////////////////////////////////////////////////////// +#ifndef _GOLD + +#ifndef _INTERFACE +#define _INTERFACE struct +#endif + +typedef _INTERFACE IHXErrorMessages IHXErrorMessages; + + const char* FileAndLine(const char* pFile, UINT32 ulLine); + HX_RESULT HXLogf(IHXErrorMessages *pErr, UINT8 sev, HX_RESULT err, UINT32 usr, const char* pURL, const char* pUsrStr, ...); + HX_RESULT HXLog(IHXErrorMessages *pErr, UINT8 sev, HX_RESULT err, UINT32 usr, const char* pStr, const char* pURL); + +#define THIS_LINE() FileAndLine(__FILE__, __LINE__) + +#define HX_STARTLOG(__IUnknown, __ErrorMessages) \ + __IUnknown->QueryInterface(IID_IHXErrorMessages, (void **)&__ErrorMessages); \ + HX_ASSERT(__ErrorMessages != NULL && "Must have IHXErrorMessages interface to start logging") + +#define HX_LOG HXLog +#define HX_LOGF HXLogf + +#define HX_STOPLOG(__ErrorMessages) HX_RELEASE(__ErrorMessages) + +#else // _GOLD +#define HX_STARTLOG(x,y) (void)0 +#define HX_LOG 1 ? (void)0 : HXLog +#define HX_LOGF 1 ? (void)0 : HXLogf +#define HX_STOPLOG(x) (void)0 +#endif + +///////////////////////////////////////////////////////////////////////////// +// +// HX_SAFESIZE_T +// +#define HX_SAFESIZE_T(f) \ + ( ((ULONG32)(f) <= (size_t)-1)?((size_t)(f)): \ + (HXAssertFailedLine("size_t overflow",__FILE__, __LINE__), (size_t)(f)) ) \ + +///////////////////////////////////////////////////////////////////////////// +// +// HX_SAFEINT +// +#ifndef _VXWORKS +#define HX_SAFEINT(f) \ + ( ((LONG32)(f) <= LONG_MAX && ((LONG32)(f)) >= ((LONG32)(LONG_MIN)))?((int)(f)): \ + (HXAssertFailedLine("Integer Size Overflow",__FILE__, __LINE__), (int)(f)) ) \ + +#else + +#ifdef __cplusplus +extern "C" +#endif +int safe_int_func_call(LONG32 f); +#define HX_SAFEINT(f) safe_int_func_call((LONG32)(f)) +#endif + +#define HX_SAFEUINT(f) \ + ( ((ULONG32)(f) <= ULONG_MAX && (LONG32)(f) >= 0)?((unsigned int)(f)): \ + (HXAssertFailedLine("Unsigned Integer Size Overflow",__FILE__, __LINE__), (unsigned int)(f)) ) \ + +#define HX_SAFEINT16(f) \ + ( ((LONG32)(f) <= SHRT_MAX && (LONG32)(f) >= SHRT_MIN)?((short)(f)): \ + (HXAssertFailedLine("Short Integer Size Overflow",__FILE__, __LINE__), (short)(f)) ) \ + +#define HX_SAFEUINT16(f) \ + ( ((ULONG32)(f) <= USHRT_MAX && (LONG32)(f) >= 0)?((unsigned short)(f)): \ + (HXAssertFailedLine("Unsigned Short Integer Size Overflow",__FILE__, __LINE__), (unsigned short)(f)) ) \ + +#define HX_SAFEINT8(f) \ + ( ((LONG32)(f) <= SCHAR_MAX && (LONG32)(f) >= SCHAR_MIN)?((char)(f)): \ + (HXAssertFailedLine("Signed Char Size Overflow",__FILE__, __LINE__), (char)(f)) ) \ + +#define HX_SAFEUINT8(f) \ + ( ((ULONG32)(f) <= UCHAR_MAX && (LONG32)(f) >= 0)?((unsigned char)(f)): \ + (HXAssertFailedLine("Unsigned Char Size Overflow",__FILE__, __LINE__), (unsigned char)(f)) ) \ + + + + +///////////////////////////////////////////////////////////////////////////// +// +// HX_SAFE_VOID2HANDLE +// +#if defined(_WINDOWS) + +#ifdef _WIN32 +#define HX_SAFE_VOID2HANDLE(f) ((HANDLE)(f)) +#else // !_WIN23 +#define HX_SAFE_VOID2HANDLE(f) ((HANDLE)LOWORD(f)) +// this doesn't work most of the time since the assignment of a handle (near *) to a +// void * sets the high word to the SS. +// ( ( (0xFFFF0000 & ((ULONG32)(f))) == 0)?((HANDLE)LOWORD(f)): +// (HXAssertFailedLine("HANDLE HIWORD NOT NULL",__FILE__, __LINE__), (HANDLE)LOWORD(f)) ) + +#endif + +#elif defined ( __MWERKS__ ) +#define HX_SAFE_VOID2HANDLE(f) (f) +#elif defined ( _UNIX ) +#define HX_SAFE_VOID2HANDLE(f) (f) +#endif // end of#if defined(_WIN32) || defined(_WINDOWS) + +///////////////////////////////////////////////////////////////////////////// +// +// HX_VERIFY: see above. +// +#define HX_VERIFY(f) HX_ASSERT(f) + +///////////////////////////////////////////////////////////////////////////// +// +// HX_ASSERT_VALID_PTR: see above. +// +#define HX_ASSERT_VALID_PTR(pOb) (::HXAssertValidPointer((const void*)pOb, __FILE__, __LINE__)) +#define HX_ASSERT_VALID_READ_PTR(pOb) HX_ASSERT(::HXIsValidAddress(pOb, 1, FALSE)) + +///////////////////////////////////////////////////////////////////////////// +// +// _SAFESTRING: Similar to HX_TRACE() except that it assume you are +// using a single format string, with a single parameter string, this will +// ensure that the length of the resulting format is less than the maximum +// trace string, and gracefully handle the situation... +// +#define HX_TRACE_SAFESTRING(f,s)\ + if ((strlen(f) + strlen(s)) < (size_t)MAX_TRACE_OUTPUT)\ + {\ + HX_TRACE(f,s);\ + }\ + else\ + {\ + HX_TRACE(f,"Some really big URL");\ + }\ + +#else // _DEBUG + +#ifndef _DEBUG + +#ifndef HX_ENABLE_JIT_DEBUGGING +#define HX_ENABLE_JIT_DEBUGGING() +#endif + +#ifdef HXAbort +#undef HXAbort +#endif // end of#ifdef HXAbort + +#define HXAbort() + +#endif // _DEBUG + +#ifndef _DEBUG + +#ifdef HXDebugBreak +#undef HXDebugBreak +#endif // end of#ifdef HXDebugBreak + +#define HXDebugBreak() + +#endif // _DEBUG || DEBUG + +///////////////////////////////////////////////////////////////////////////// +// +// HX_SAFEINT +// +#define HX_SAFEINT(f) ((int)(f)) +#define HX_SAFEUINT(f) ((unsigned int)(f)) +#define HX_SAFEINT16(f) ((short)(f)) +#define HX_SAFEUINT16(f) ((unsigned short)(f)) +#define HX_SAFEINT8(f) ((char)(f)) +#define HX_SAFEUINT8(f) ((unsigned char)(f)) + +#define HX_ASSERT(f) ((void)0) +#define HX_SAFESIZE_T(f) ((size_t)(f)) +#define HX_SAFEINT(f) ((int)(f)) +#define HX_SAFE_VOID2HANDLE(f) ((HANDLE)(ULONG32)(f)) +#define HX_ASSERT_VALID_PTR(pOb) ((void)0) +#define HX_ASSERT_VALID_READ_PTR(pOb) ((void)0) +#define HXOutputDebugString(f) ((void)0) + +#define REQUIRE_REPORT(targ,file,line) ((int)0) + +#if defined (_MACINTOSH) +// this is the proper release version of HX_VERIFY that preserves the syntactic +// role of the debug version; it's necessary to quiet warnings on the Mac +#define HX_VERIFY(f) do { if (!(f)) {} } while (0) +#else +#define HX_VERIFY(f) ((void)(f)) +#endif + +#if defined (_WINDOWS) +__inline void __cdecl HXTrace(const char* x, ...) { } +#else +static __inline void HXTrace(const char* /* x */, ...) {} +#endif + +#define HX_TRACE 1 ? (void)0 : ::HXTrace +#define HX_TRACE_SAFESTRING HX_TRACE + +#endif // !_DEBUG + +#ifdef __cplusplus +} // end extern "C" +#endif + +#ifdef __cplusplus +///////////////////////////////////////////////////////////////////////////// +// +// Helper for loginfo for LOGINFO() +// +#ifndef _INTERFACE +#define _INTERFACE struct +#endif + +typedef _INTERFACE IHXErrorSink IHXErrorSink; + +class LogInfo +{ + public: + static void STDMETHODCALLTYPE FileandLine(const char* file, int nLine); + static void STDMETHODCALLTYPE Report(IHXErrorSink* mySink, const char* pUserInfo, ...); + private: + static char m_pFile[_MAX_PATH]; /* Flawfinder: ignore */ +}; + +#endif + + +///////////////////////////////////////////////////////////////////////////// +// CHECK/REQUIRE MACROS +// +// These macros are always valid (debug and release builds) +// +// + +// +// REQUIRE and REQUIRE_ACTION _always_ emit code for the goto if the statement is false +// +// REQUIRE_REPORT only generates code in Debug builds +// +// The do {} while (0) construct ensures this is legal wherever a statement is legal +// + +#define CHECK(stmt) HX_ASSERT(stmt) + +#define REQUIRE_VOID_RETURN(stmt) \ + do { if ((stmt) == 0) { if (REQUIRE_REPORT(#stmt,__FILE__,__LINE__)) HXDebugBreak(); return; } } while (0) +#define REQUIRE_RETURN(stmt,returned) \ + do { if ((stmt) == 0) { if (REQUIRE_REPORT(#stmt,__FILE__,__LINE__)) HXDebugBreak(); return (returned); } } while (0) +#define REQUIRE_VOID_RETURN_QUIET(stmt) \ + do { if ((stmt) == 0) { return; } } while (0) +#define REQUIRE_RETURN_QUIET(stmt,returned) \ + do { if ((stmt) == 0) { return (returned); } } while (0) +#define REQUIRE(stmt,target) \ + do { if ((stmt) == 0) { REQUIRE_REPORT(#target,__FILE__,__LINE__); goto target; } } while (0) +#define REQUIRE_ACTION(stmt,target,action) \ + do { if ((stmt) == 0) { REQUIRE_REPORT(#target,__FILE__,__LINE__); {{action;} goto target;} } } while (0) +#define REQUIRE_QUIET(stmt,target) \ + do { if ((stmt) == 0) goto target; } while (0) +#define REQUIRE_ACTION_QUIET(stmt,target,action) \ + do { if ((stmt) == 0) {{action;} goto target;} } while (0) +#define PRE_REQUIRE_RETURN(stmt,returned) \ + REQUIRE_RETURN(stmt,returned) +#define PRE_REQUIRE_VOID_RETURN(stmt) \ + REQUIRE_VOID_RETURN(stmt) +#define POST_REQUIRE_RETURN(stmt,returned) \ + REQUIRE_RETURN(stmt,returned) +#define POST_REQUIRE_VOID_RETURN(stmt) \ + REQUIRE_VOID_RETURN(stmt) + +#define REQUIRE_SUCCESS_RETURN_QUIET(expr) \ + do { register HX_RESULT const res = expr; if (FAILED (res)) return res; } while (0) +#define REQUIRE_SUCCESS_RETURN(expr) \ + do { register HX_RESULT const res = expr; if (FAILED (res)) { REQUIRE_REPORT("False condition, Aborting...",__FILE__,__LINE__); return res; } } while (0) + +// +// REQUIRE_SUCCESS reports the error if an expected result failed +// Ideally, this should report the status value as well +// + +#define CHECK_SUCCESS(stat) HX_ASSERT(((unsigned long)(stat)>>31) == 0) + +#define REQUIRE_SUCCESS(stat,target) \ + do { if (((unsigned long)(stat)>>31) != 0) { REQUIRE_REPORT(#target,__FILE__,__LINE__); goto target; } } while (0) +#define REQUIRE_SUCCESS_ACTION(stat,target,action) \ + do { if (((unsigned long)(stat)>>31) != 0) { REQUIRE_REPORT(#target,__FILE__,__LINE__); {{action;} goto target;} } } while (0) +#define REQUIRE_SUCCESS_QUIET(stat,target) \ + do { if (((unsigned long)(stat)>>31) != 0) goto target; } while (0) +#define REQUIRE_SUCCESS_ACTION_QUIET(stat,target,action) \ + do { if (((unsigned long)(stat)>>31) != 0) {{action;} goto target;} } while (0) + +// +// REQUIRE_NOERR reports the error if the error value is non-zero +// Ideally, this should report the error value as well +// + +#define CHECK_NOERR(err) HX_ASSERT((err) == 0) + +#define REQUIRE_NOERR_RETURN(err,returned) \ + do { if ((err) != 0) { REQUIRE_REPORT("Toolbox error, Aborting...",__FILE__,__LINE__); return (returned); } } while (0) +#define REQUIRE_NOERR(err,target) \ + do { if ((err) != 0) { REQUIRE_REPORT(#target,__FILE__,__LINE__); goto target; } } while (0) +#define REQUIRE_NOERR_ACTION(err,target,action) \ + do { if ((err) != 0) { REQUIRE_REPORT(#target,__FILE__,__LINE__); {{action;} goto target;} } } while (0) +#define REQUIRE_NOERR_QUIET(err,target) \ + do { if ((err) != 0) goto target; } while (0) +#define REQUIRE_NOERR_ACTION_QUIET(err,target,action) \ + do { if ((err) != 0) {{action;} goto target;} } while (0) + +// +// REQUIRE_NONNULL reports the error if the ptr value is null +// Ideally, this should report the error value as well +// +#define CHECK_NONNULL(ptr) HX_ASSERT((ptr) != 0L) +#define CHECK_NULL(ptr) HX_ASSERT((ptr) == 0L) + +#define REQUIRE_NONNULL_VOID_RETURN(ptr) \ + do { if ((ptr) == 0L) { REQUIRE_REPORT(#ptr" is nil, Aborting...",__FILE__,__LINE__); return; } } while (0) +#define REQUIRE_NONNULL_RETURN(ptr,returned) \ + do { if ((ptr) == 0L) { REQUIRE_REPORT(#ptr" is nil, Aborting...",__FILE__,__LINE__); return (returned); } } while (0) +#define REQUIRE_NONNULL(ptr,target) \ + do { if ((ptr) == 0L) { REQUIRE_REPORT(#target,__FILE__,__LINE__); goto target; } } while (0) +#define REQUIRE_NONNULL_ACTION(ptr,target,action) \ + do { if ((ptr) == 0L) { REQUIRE_REPORT(#target,__FILE__,__LINE__); {{action;} goto target;} } } while (0) +#define REQUIRE_NONNULL_QUIET(ptr,target) \ + do { if ((ptr) == 0L) goto target; } while (0) +#define REQUIRE_NONNULL_ACTION_QUIET(ptr,target,action) \ + do { if ((ptr) == 0L) {{action;} goto target;} } while (0) +// lower case versions make source code more readable + +#if defined(_CARBON) || defined(_MAC_MACHO) +#undef check +#undef require +#undef require_action +#undef require_quiet +#undef check_noerr +#undef require_action_quiet +#undef require_noerr +#undef require_noerr_action +#undef require_noerr_quiet +#undef require_noerr_action_quiet +#endif + +#define check(stmt) CHECK(stmt) + +#define require_void_return(stmt) REQUIRE_VOID_RETURN(stmt) +#define require_return_void(stmt) REQUIRE_VOID_RETURN(stmt) +#define require_return(stmt,returned) REQUIRE_RETURN(stmt,returned) +#define require(stmt,target) REQUIRE(stmt,target) +#define require_action(stmt,target,action) REQUIRE_ACTION(stmt,target,action) +#define require_quiet(stmt,target) REQUIRE_QUIET(stmt,target) +#define require_action_quiet(stmt,target,action) REQUIRE_ACTION_QUIET(stmt,target,action) + +#define check_success(stat) CHECK_SUCCESS(stat) + +#define require_success(stat,target) REQUIRE_SUCCESS(stat,target) +#define require_success_action(stat,target,action) REQUIRE_SUCCESS_ACTION(stat,target,action) +#define require_success_quiet(stat,target) REQUIRE_SUCCESS_QUIET(stat,target) +#define require_success_action_quiet(stat,target,action) REQUIRE_SUCCESS_ACTION_QUIET(stat,target,action) + +#define check_noerr(err) CHECK_NOERR(err) + +#define require_noerr_return(err,returned) REQUIRE_NOERR_RETURN(err,returned) +#define require_noerr(err,target) REQUIRE_NOERR(err,target) +#define require_noerr_action(err,target,action) REQUIRE_NOERR_ACTION(err,target,action) +#define require_noerr_quiet(err,target) REQUIRE_NOERR_QUIET(err,target) +#define require_noerr_action_quiet(err,target,action) REQUIRE_NOERR_ACTION_QUIET(err,target,action) + +#define check_nonnull(ptr) CHECK_NONNULL(ptr) +#define check_null(ptr) CHECK_NULL(ptr) + +#define require_nonnull_void_return(ptr) REQUIRE_NONNULL_VOID_RETURN(ptr) +#define require_nonnull_return(ptr,returned) REQUIRE_NONNULL_RETURN(ptr,returned) +#define require_nonnull(ptr,target) REQUIRE_NONNULL(ptr,target) +#define require_nonnull_action(ptr,target,action) REQUIRE_NONNULL_ACTION(ptr,target,action) +#define require_nonnull_quiet(ptr,target) REQUIRE_NONNULL_QUIET(ptr,target) +#define require_nonnull_action_quiet(ptr,target,action) REQUIRE_NONNULL_ACTION_QUIET(ptr,target,action) + + +#endif // !_HXASSERT_H_ diff --git a/amarok/src/engine/helix/helix-sp/helix-include/common/include/atomicbase.h b/amarok/src/engine/helix/helix-sp/helix-include/common/include/atomicbase.h new file mode 100644 index 00000000..ba2748f7 --- /dev/null +++ b/amarok/src/engine/helix/helix-sp/helix-include/common/include/atomicbase.h @@ -0,0 +1,1566 @@ + +/* + * + * This software is released under the provisions of the GPL version 2. + * see file "COPYING". If that file is not available, the full statement + * of the license can be found at + * + * http://www.fsf.org/licensing/licenses/gpl.txt + * + * Portions Copyright (c) 1995-2004 RealNetworks, Inc. All Rights Reserved. + * + */ + + +/*********************************************************************** + * THIS CODE IS HIGHLY CRITICAL TO THE SERVER'S STABILITY!!! + * DO NOT MAKE CHANGES TO THE ATOMIC OPERATORS OR TO THE + * MUTEX CODE WITHOUT A SERVER TEAM CODE-REVIEW! (dev@helix-server) + */ + + +/**************************************************************************** + * $Id: atomicbase.h 587223 2006-09-21 23:14:40Z aoliveira $ + * + * atomicbase.h - Defines several atomic operations + * + * See server/common/util/pub/servatomic.h for broader platform support. + * Also conditionally overrides InterlockedIncrement/Decrement + * via USE_HX_ATOMIC_INTERLOCKED_INC_DEC. + * + * + *********************************************************************** + * + * Defines: + * + * void HXAtomicIncINT32(INT32* p) -- Increment *p + * void HXAtomicDecINT32(INT32* p) -- Decrement *p + * void HXAtomicAddINT32(INT32* p, INT32 n) -- Increment *p by n + * void HXAtomicSubINT32(INT32* p, INT32 n) -- Decrement *p by n + * INT32 HXAtomicIncRetINT32(INT32* p) -- Increment *p and return it + * INT32 HXAtomicDecRetINT32(INT32* p) -- Decrement *p and return it + * INT32 HXAtomicAddRetINT32(INT32* p, INT32 n)-- Increment *p by n, return it + * INT32 HXAtomicSubRetINT32(INT32* p, INT32 n)-- Increment *p by n, return it + * + * + * There are also UINT32 versions: + * + * void HXAtomicIncUINT32(UINT32* p) + * void HXAtomicDecUINT32(UINT32* p) + * void HXAtomicAddUINT32(UINT32* p, UINT32 n) + * void HXAtomicSubUINT32(UINT32* p, UINT32 n) + * UINT32 HXAtomicIncRetUINT32(UINT32* p) + * UINT32 HXAtomicDecRetUINT32(UINT32* p) + * UINT32 HXAtomicAddRetUINT32(UINT32* p, UINT32 n) + * UINT32 HXAtomicSubRetUINT32(UINT32* p, UINT32 n) + * + *********************************************************************** + * + * TODO: + * Add INT64 versions + * Obsolete the 0x80000000-based Solaris implementation entirely. + * + ***********************************************************************/ +#ifndef _ATOMICBASE_H_ +#define _ATOMICBASE_H_ + + +/*********************************************************************** + * Sun Solaris / SPARC (Native compiler) + * + * Implementation Notes: + * This uses inline assembly from server/common/util/platform/solaris/atomicops.il + * Note: Sparc/gcc is in include/atomicbase.h + */ +#if defined (_SOLARIS) && !defined (__GNUC__) + +#if defined(__cplusplus) +extern "C" { +#endif + //UINT32 _HXAtomicIncRetUINT32 (UINT32* pNum); + //UINT32 _HXAtomicDecRetUINT32 (UINT32* pNum); + UINT32 _HXAtomicAddRetUINT32 (UINT32* pNum, UINT32 ulNum); + UINT32 _HXAtomicSubRetUINT32 (UINT32* pNum, UINT32 ulNum); +#if defined(__cplusplus) +} +#endif + + +#define HXAtomicIncUINT32(p) _HXAtomicAddRetUINT32((p),(UINT32)1) +#define HXAtomicDecUINT32(p) _HXAtomicSubRetUINT32((p),(UINT32)1) +#define HXAtomicIncRetUINT32(p) _HXAtomicAddRetUINT32((p),(UINT32)1) +#define HXAtomicDecRetUINT32(p) _HXAtomicSubRetUINT32((p),(UINT32)1) +#define HXAtomicAddUINT32(p,n) _HXAtomicAddRetUINT32((p),(n)) +#define HXAtomicSubUINT32(p,n) _HXAtomicSubRetUINT32((p),(n)) +#define HXAtomicAddRetUINT32(p,n) _HXAtomicAddRetUINT32((p),(n)) +#define HXAtomicSubRetUINT32(p,n) _HXAtomicSubRetUINT32((p),(n)) + +inline void HXAtomicIncINT32(INT32* p) { HXAtomicIncUINT32((UINT32*)p); } +inline void HXAtomicDecINT32(INT32* p) { HXAtomicDecUINT32((UINT32*)p); } +inline void HXAtomicAddINT32(INT32* p, INT32 n) { HXAtomicAddUINT32((UINT32*)p, (UINT32)n); } +inline void HXAtomicSubINT32(INT32* p, INT32 n) { HXAtomicSubUINT32((UINT32*)p, (UINT32)n); } +inline INT32 HXAtomicIncRetINT32(INT32* p) { return HXAtomicIncRetUINT32((UINT32*)p); } +inline INT32 HXAtomicDecRetINT32(INT32* p) { return HXAtomicDecRetUINT32((UINT32*)p); } +inline INT32 HXAtomicAddRetINT32(INT32* p, INT32 n) { return HXAtomicAddRetUINT32((UINT32*)p, (UINT32)n); } +inline INT32 HXAtomicSubRetINT32(INT32* p, INT32 n) { return HXAtomicSubRetUINT32((UINT32*)p, (UINT32)n); } + + + +/*********************************************************************** + * Sun Solaris / SPARC (gcc) + * + * Implementation Notes: + * The sparc method of pipelining and use of "delay slots" requires + * the nop's. Be extra careful modifying these routines! + * + * This implementation sacrifices being able to store the value + * 0x800000000 in the INT32 value, which is a special "busy" marker value. + * Since these are intended for use primarily with AddRef/Release and + * resource usage counters, this should be acceptable for now. If a counter + * is incremented to the point it would conflict with the flag, it is + * incremented one more to hop over it. The same in reverse for decrement. + * This is far from ideal, however... See the inline-assembly file + * server/common/util/platform/solaris/mutex_setbit.il for *much* + * better implementations using newer sparc assembly operators. + * + * Basic design of the flag-based implementation: + * 1. Load a register with 0x80000000 + * 2. _atomically_ swap it with the INT32 (critical!) + * 3. Compare what we got with 0x80000000 + * 4. Branch if equal to #2 + * 5. Increment (or decrement) the result + * 6. Compare to 0x80000000 + * 7. Branch if equal to #5 + * 8. Save the new value to the INT32's location in memory + * 9. Return new INT32 result if required + * + * This implementation primarily exists due to limitations in the ancient + * version of gcc we used to use on Solaris (2.7.2.3), and more modern + * gcc's can probably handle assembly more like what's used in Sun's + * Native compiler version. + * + */ +#elif defined (__sparc__) && defined (__GNUC__) + +/* Increment by 1 */ +inline void +HXAtomicIncUINT32(UINT32* pNum) +{ + __asm__ __volatile__(\ +"1: swap [%0], %2; ! Swap *pNum and %2\n" +" nop; ! delay slot...\n" +" cmp %2, %1; ! Is someone else using pNum?\n" +" be 1b; ! If so, retry...\n" +" nop; ! delay slot...yawn\n" +"2: inc %2; ! Increment %2\n" +" cmp %2, %1; ! check for overflow\n" +" be 2b; ! if so, inc again\n" +" nop; ! but this means a delay, sigh\n" +" st %2, [%0]; ! Save new value into *pNum\n" + : /* no output */ + : "r" (pNum), "r" (0x80000000), "r" (0x80000000) + : "cc", "memory" + ); +} + +/* Decrement by 1 */ +inline void +HXAtomicDecUINT32(UINT32* pNum) +{ + __asm__ __volatile__( +"1: swap [%0], %2; ! Swap *pNum and %2\n" +" nop; ! delay slot...\n" +" cmp %2, %1; ! Is someone else using pNum?\n" +" be 1b; ! If so, retry...\n" +" nop; ! delay slot...yawn\n" +"2: dec %2; ! Increment %2\n" +" cmp %2, %1; ! check for overflow\n" +" be 2b; ! if so, dec again\n" +" nop; ! but this means a delay, sigh\n" +" st %2, [%0]; ! Save new value into *pNum\n" + : /* no output */ + : "r" (pNum), "r" (0x80000000), "r" (0x80000000) + : "cc", "memory" + ); +} + +/* Increment by 1 and return new value */ +inline UINT32 +HXAtomicIncRetUINT32(UINT32* pNum) +{ + volatile UINT32 ulRet; + __asm__ __volatile__( +" mov %2, %0; ! Copy %2 to %0 \n" +"1: swap [%1], %0; ! Swap *pNum and %0\n" +" nop; ! delay slot...\n" +" cmp %0, %2; ! Is someone else using pNum?\n" +" be 1b; ! If so, retry...\n" +" nop; ! delay slot...yawn\n" +"2: inc %0; ! Increment %0\n" +" cmp %0, %2; ! check for overflow\n" +" be 2b; ! if so, inc again\n" +" nop; ! but this means a delay, sigh\n" +" st %0, [%1]; ! Save new value into *pNum\n" + : "=r" (ulRet) + : "r" (pNum), "r" (0x80000000), "0" (ulRet) + : "cc", "memory" + ); + return ulRet; +} + +/* Decrement by 1 and return new value */ +inline UINT32 +HXAtomicDecRetUINT32(UINT32* pNum) +{ volatile UINT32 ulRet; + __asm__ __volatile__( +" mov %2, %0; ! Copy %2 to %0 \n" +"1: swap [%1], %0; ! Swap *pNum and %0\n" +" nop; ! delay slot...\n" +" cmp %0, %2; ! Is someone else using pNum?\n" +" be 1b; ! If so, retry...\n" +" nop; ! delay slot...yawn\n" +"2: dec %0; ! Decrement %0\n" +" cmp %0, %2; ! check for overflow\n" +" be 2b; ! if so, dec again\n" +" nop; ! but this means a delay, sigh\n" +" st %0, [%1]; ! Save new value into *pNum\n" + : "=r" (ulRet) + : "r" (pNum), "r" (0x80000000), "0" (ulRet) + : "cc", "memory" + ); + return ulRet; +} + +/* Add n */ +inline void +HXAtomicAddUINT32(UINT32* pNum, UINT32 ulNum) +{ + __asm__ __volatile__( +"1: swap [%0], %2; ! Swap *pNum and %2\n" +" nop; ! delay slot...\n" +" cmp %2, %1; ! Is someone else using pNum?\n" +" be 1b; ! If so, retry...\n" +" nop; ! delay slot...yawn\n" +" add %2, %3, %2; ! Add ulNum to %2\n" +" cmp %2, %1; ! check for overflow\n" +" bne 2f; ! if not, skip to the end\n" +" nop; ! but this means a delay, sigh\n" +" inc %2; ! skip marker value\n" +"2: st %2, [%0]; ! Save new value into *pNum\n" + : /* no output */ + : "r" (pNum), "r" (0x80000000), "r" (0x80000000), "r" (ulNum) + : "cc", "memory" + ); +} + +/* Subtract n */ +inline void +HXAtomicSubUINT32(UINT32* pNum, UINT32 ulNum) +{ + __asm__ __volatile__( +"1: swap [%0], %2; ! Swap *pNum and %2\n" +" nop; ! delay slot...\n" +" cmp %2, %1; ! Is someone else using pNum?\n" +" be 1b; ! If so, retry...\n" +" nop; ! delay slot...yawn\n" +" sub %2, %3, %2; ! Subtract ulNum to %2\n" +" cmp %2, %1; ! check for overflow\n" +" bne 2f; ! if not, skip to the end\n" +" nop; ! but this means a delay, sigh\n" +" inc %2; ! skip marker value\n" +"2: st %2, [%0]; ! Save new value into *pNum\n" + : /* no output */ + : "r" (pNum), "r" (0x80000000), "r" (0x80000000), "r" (ulNum) + : "cc", "memory" + ); +} + +/* Add n and return new value */ +inline UINT32 +HXAtomicAddRetUINT32(UINT32* pNum, UINT32 ulNum) +{ + volatile UINT32 ulRet; \ + __asm__ __volatile__( +" mov %2, %0 ! Copy %2 to %0 \n" +"1: swap [%1], %0; ! Swap *pNum and %0\n" +" nop; ! delay slot...\n" +" cmp %0, %2; ! Is someone else using pNum?\n" +" be 1b; ! If so, retry...\n" +" nop; ! delay slot...yawn\n" +" add %0, %3, %0; ! Add ulNum to %0\n" +" cmp %0, %2; ! check for overflow\n" +" bne 2f; ! if not, skip to the end\n" +" nop; ! but this means a delay, sigh\n" +" inc %0; ! skip marker value\n" +"2: st %0, [%1]; ! Save new value into *pNum\n" + : "=r" (ulRet) + : "r" (pNum), "r" (0x80000000), "r" (ulNum), "0" (ulRet) + : "cc", "memory" + ); + return ulRet; +} + +/* Subtract n and return new value */ +inline UINT32 +HXAtomicSubRetUINT32(UINT32* pNum, UINT32 ulNum) +{ volatile UINT32 ulRet; + __asm__ __volatile__( +" mov %2, %0 ! Copy %2 to %0 \n" +"1: swap [%1], %0; ! Swap *pNum and %0\n" +" nop; ! delay slot...\n" +" cmp %0, %2; ! Is someone else using pNum?\n" +" be 1b; ! If so, retry...\n" +" nop; ! delay slot...yawn\n" +" sub %0, %3, %0; ! Sub ulNum from %0\n" +" cmp %0, %2; ! check for overflow\n" +" bne 2f; ! if not, skip to the end\n" +" nop; ! but this means a delay, sigh\n" +" dec %0; ! skip marker value\n" +"2: st %0, [%1]; ! Save new value into *pNum\n" + : "=r" (ulRet) + : "r" (pNum), "r" (0x80000000), "r" (ulNum), "0" (ulRet) + : "cc", "memory" + ); + return ulRet; +} + +inline void HXAtomicIncINT32(INT32* p) { HXAtomicIncUINT32((UINT32*)p); } +inline void HXAtomicDecINT32(INT32* p) { HXAtomicDecUINT32((UINT32*)p); } +inline void HXAtomicAddINT32(INT32* p, INT32 n) { HXAtomicAddUINT32((UINT32*)p, (UINT32)n); } +inline void HXAtomicSubINT32(INT32* p, INT32 n) { HXAtomicSubUINT32((UINT32*)p, (UINT32)n); } +inline INT32 HXAtomicIncRetINT32(INT32* p) { return HXAtomicIncRetUINT32((UINT32*)p); } +inline INT32 HXAtomicDecRetINT32(INT32* p) { return HXAtomicDecRetUINT32((UINT32*)p); } +inline INT32 HXAtomicAddRetINT32(INT32* p, INT32 n) { return HXAtomicAddRetUINT32((UINT32*)p, (UINT32)n); } +inline INT32 HXAtomicSubRetINT32(INT32* p, INT32 n) { return HXAtomicSubRetUINT32((UINT32*)p, (UINT32)n); } + + + +/*********************************************************************** + * Windows / x86 (Visual C/C++) + * + * Implementation Notes: + * 'xadd' is only available in the 486 series and later, not the 386. + * There is no 'xsub' counterpart, you have to negate the operand + * and use 'xadd'. Note the use of the 'lock' prefix to ensure + * certain operations occur atomically. + */ +#elif defined (_M_IX86) /* && _M_IX86 > 300 XXX wschildbach: disabled until the build system delivers the correct value */ + +/* Increment by 1 */ +static __inline void +HXAtomicIncUINT32(UINT32* pNum) +{ + // register usage summary: + // eax - pointer to the value we're modifying + _asm + { + mov eax, pNum ; Load the pointer into a register + lock inc dword ptr [eax] ; Atomically increment *pNum + } +} + +/* Decrement by 1 */ +static __inline void +HXAtomicDecUINT32(UINT32* pNum) +{ + // register usage summary: + // eax - pointer to the value we're modifying + _asm + { + mov eax, pNum ; Load the pointer into a register + lock dec dword ptr [eax] ; Atomically decrement *pNum + } +} + +/* Increment by 1 and return new value */ +static __inline UINT32 +HXAtomicIncRetUINT32(UINT32* pNum) +{ + volatile UINT32 ulRet; + // register usage summary: + // eax - pointer to the value we're modifying + // ebx - work register + _asm + { + mov eax, pNum ; Load the pointer into a register + mov ebx, 0x1 ; Load increment amount into a register + lock xadd dword ptr [eax], ebx ; Increment *pNum; ebx gets old value + inc ebx ; Increment old value + mov ulRet, ebx ; Set the return value + } + return ulRet; +} + +/* Decrement by 1 and return new value */ +static __inline UINT32 +HXAtomicDecRetUINT32(UINT32* pNum) +{ + volatile UINT32 ulRet; + // register usage summary: + // eax - pointer to the value we're modifying + // ebx - work register + // note: we increment by 0xffffffff to decrement by 1 + _asm + { + mov eax, pNum ; Load the pointer into a register + mov ebx, 0xffffffff ; Load decrement amount into a register + lock xadd dword ptr [eax], ebx ; Decrement *pNum; ebx gets old value + dec ebx ; decrement old value + mov ulRet, ebx ; Set the return value + } + return ulRet; +} + +/* Add n */ +static __inline void +HXAtomicAddUINT32(UINT32* pNum, UINT32 ulNum) +{ + // register usage summary: + // eax - pointer to the value we're modifying + // ebx - work register + _asm + { + mov eax, pNum ; Load the pointer into a register + mov ebx, ulNum ; Load increment amount into a register + lock add dword ptr [eax], ebx ; Increment *pNum by ulNum + } +} + +/* Subtract n */ +static __inline void +HXAtomicSubUINT32(UINT32* pNum, UINT32 ulNum) +{ + // register usage summary: + // eax - pointer to the value we're modifying + // ebx - work register + _asm + { + mov eax, pNum ; Load the pointer into a register + mov ebx, ulNum ; Load increment amount into a register + lock sub dword ptr [eax], ebx ; Atomically decrement *pNum by ulNum + } +} + +/* Add n and return new value */ +static __inline UINT32 +HXAtomicAddRetUINT32(UINT32* pNum, UINT32 ulNum) +{ + volatile UINT32 ulRet; + // register usage summary: + // eax - pointer to the value we're modifying + // ebx - work register + // ecx - work register #2 + _asm + { + mov eax, pNum ; Load the pointer into a register + mov ebx, ulNum ; Load increment amount into a register + mov ecx, ebx ; copy ebx into ecx + lock xadd dword ptr [eax], ecx ; Increment *pNum; ecx gets old value + add ecx, ebx ; Add ulNum to it + mov ulRet, ecx ; save result in ulRet + } + return ulRet; +} + +/* Subtract n and return new value */ +static __inline UINT32 +HXAtomicSubRetUINT32(UINT32* pNum, UINT32 ulNum) +{ + volatile UINT32 ulRet; + // register usage summary: + // eax - pointer to the value we're modifying + // ebx - work register + // ecx - work register #2 + _asm + { + mov eax, pNum ; Load the pointer into a register + mov ebx, ulNum ; Load increment amount into a register + mov ecx, 0x0 ; zero out ecx + sub ecx, ebx ; compute -(ulNum), saving in ecx + lock xadd dword ptr [eax], ecx ; Decrement *pNum; ecx gets old value + sub ecx, ebx ; subtract ulNum from it + mov ulRet, ecx ; save result in ulRet + } + return ulRet; +} + +static __inline void HXAtomicIncINT32(INT32* p) { HXAtomicIncUINT32((UINT32*)p); } +static __inline void HXAtomicDecINT32(INT32* p) { HXAtomicDecUINT32((UINT32*)p); } +static __inline void HXAtomicAddINT32(INT32* p, INT32 n) { HXAtomicAddUINT32((UINT32*)p, (UINT32)n); } +static __inline void HXAtomicSubINT32(INT32* p, INT32 n) { HXAtomicSubUINT32((UINT32*)p, (UINT32)n); } +static __inline INT32 HXAtomicIncRetINT32(INT32* p) { return HXAtomicIncRetUINT32((UINT32*)p); } +static __inline INT32 HXAtomicDecRetINT32(INT32* p) { return HXAtomicDecRetUINT32((UINT32*)p); } +static __inline INT32 HXAtomicAddRetINT32(INT32* p, INT32 n) { return HXAtomicAddRetUINT32((UINT32*)p, (UINT32)n); } +static __inline INT32 HXAtomicSubRetINT32(INT32* p, INT32 n) { return HXAtomicSubRetUINT32((UINT32*)p, (UINT32)n); } + + + +/*********************************************************************** + * Intel x86 (gcc) / Unix -- i486 and higher - 32-bit + * + * Implementation Notes: + * 'xadd' is only available in the 486 series and later, not the 386. + * There is no 'xsub' counterpart, you have to negate the operand + * and use 'xadd'. Note the use of the 'lock' prefix to ensure + * certain operations occur atomically. + * + * OpenBSD is excluded since the standard assembler on x86 systems + * can't handle the xadd instruction. + * + */ +#elif defined(__GNUC__) && !defined(_OPENBSD) && \ + (__GNUC__>2 || (__GNUC__==2 && __GNUC_MINOR__>=95)) && \ + ( defined (__i486__) || defined (__i586__) || defined (__i686__) || \ + defined (__pentium__) || defined (__pentiumpro__)) + +/* Increment by 1 */ +static __inline__ void +HXAtomicIncUINT32(UINT32* pNum) +{ + __asm__ __volatile__( + "lock incl (%0);" // atomically add 1 to *pNum + : /* no output */ + : "r" (pNum) + : "cc", "memory" + ); +} + +/* Decrement by 1 */ +static __inline__ void +HXAtomicDecUINT32(UINT32* pNum) +{ + __asm__ __volatile__( + "lock decl (%0);" // atomically add -1 to *pNum + : /* no output */ + : "r" (pNum) + : "cc", "memory" + ); +} + +/* Increment by 1 and return new value */ +static __inline__ UINT32 +HXAtomicIncRetUINT32(UINT32* pNum) +{ + volatile UINT32 ulRet; + __asm__ __volatile__( + "lock xaddl %0, (%1);" // atomically add 1 to *pNum + " inc %0;" // old value in %0, increment it + : "=r" (ulRet) + : "r" (pNum), "0" (0x1) + : "cc", "memory" + ); + return ulRet; +} + +/* Decrement by 1 and return new value */ +static __inline__ UINT32 +HXAtomicDecRetUINT32(UINT32* pNum) +{ + volatile UINT32 ulRet; + __asm__ __volatile__( + "lock xaddl %0, (%1);" // atomically add -1 to *pNum + " dec %0;" // old value in %0, decrement it + : "=r" (ulRet) + : "r" (pNum), "0" (-1) + : "cc", "memory" + ); + return ulRet; +} + +/* Add n */ +static __inline__ void +HXAtomicAddUINT32(UINT32* pNum, UINT32 ulNum) +{ + __asm__ __volatile__( + "lock addl %1, (%0);" // atomically add ulNum to *pNum + : /* no output */ + : "r" (pNum), "r" (ulNum) + : "cc", "memory" + ); +} + +/* Subtract n */ +static __inline__ void +HXAtomicSubUINT32(UINT32* pNum, UINT32 ulNum) +{ + __asm__ __volatile__( + "lock subl %1, (%0);" // atomically add ulNum to *pNum + : /* no output */ + : "r" (pNum), "r" (ulNum) + : "cc", "memory" + ); +} + +/* Add n and return new value */ +static __inline__ UINT32 +HXAtomicAddRetUINT32(UINT32* pNum, UINT32 ulNum) +{ + volatile UINT32 ulRet; + __asm__ __volatile__( + " mov %2, %0;" // copy ulNum into %0 + "lock xaddl %0, (%1);" // atomically add ulNum to *pNum + " add %2, %0;" // old value in %0, add ulNum + : "=r" (ulRet) + : "r" (pNum), "r" (ulNum), "0" (0) + : "cc", "memory" + ); + return ulRet; +} + +/* Subtract n and return new value */ +static __inline__ UINT32 +HXAtomicSubRetUINT32(UINT32* pNum, UINT32 ulNum) +{ + volatile UINT32 ulRet; + __asm__ __volatile__( + " sub %2, %0;" // negate ulNum, saving in %0 + "lock xaddl %0, (%1);" // atomically add -(ulNum) to *pNum + " sub %2, %0;" // old value in %0, subtract ulNum + : "=r" (ulRet) + : "r" (pNum), "r" (ulNum), "0" (0) + : "cc", "memory" + ); + return ulRet; +} + + +static __inline__ void HXAtomicIncINT32(INT32* p) { HXAtomicIncUINT32((UINT32*)p); } +static __inline__ void HXAtomicDecINT32(INT32* p) { HXAtomicDecUINT32((UINT32*)p); } +static __inline__ void HXAtomicAddINT32(INT32* p, INT32 n) { HXAtomicAddUINT32((UINT32*)p, (UINT32)n); } +static __inline__ void HXAtomicSubINT32(INT32* p, INT32 n) { HXAtomicSubUINT32((UINT32*)p, (UINT32)n); } +static __inline__ INT32 HXAtomicIncRetINT32(INT32* p) { return HXAtomicIncRetUINT32((UINT32*)p); } +static __inline__ INT32 HXAtomicDecRetINT32(INT32* p) { return HXAtomicDecRetUINT32((UINT32*)p); } +static __inline__ INT32 HXAtomicAddRetINT32(INT32* p, INT32 n) { return HXAtomicAddRetUINT32((UINT32*)p, (UINT32)n); } +static __inline__ INT32 HXAtomicSubRetINT32(INT32* p, INT32 n) { return HXAtomicSubRetUINT32((UINT32*)p, (UINT32)n); } + + + +/*********************************************************************** + * Intel x86/amd64/x86_64 (gcc) / Unix -- 64-bit + * + * Implementation Notes: + * + */ +#elif defined(__GNUC__) && (defined (__amd64__) || defined (__x86_64__)) + +/* Increment by 1 */ +static __inline__ void +HXAtomicIncUINT32(UINT32* pNum) +{ + __asm__ __volatile__( + "lock incl (%%rax);" // atomically add 1 to *pNum + : /* no output */ + : "a" (pNum) + : "cc", "memory" + ); +} + +/* Decrement by 1 */ +static __inline__ void +HXAtomicDecUINT32(UINT32* pNum) +{ + __asm__ __volatile__( + "lock decl (%%rax);" // atomically add -1 to *pNum + : /* no output */ + : "a" (pNum) + : "cc", "memory" + ); +} + +/* Increment by 1 and return new value */ +static __inline__ UINT32 +HXAtomicIncRetUINT32(UINT32* pNum) +{ + volatile UINT32 ulRet; + __asm__ __volatile__( + "lock xaddl %%ebx, (%%rax);" // atomically add 1 to *pNum + " incl %%ebx;" // old value in %%ebx, increment it + : "=b" (ulRet) + : "a" (pNum), "b" (0x1) + : "cc", "memory" + ); + return ulRet; +} + +/* Decrement by 1 and return new value */ +static __inline__ UINT32 +HXAtomicDecRetUINT32(UINT32* pNum) +{ + volatile UINT32 ulRet; + __asm__ __volatile__( + "lock xaddl %%ebx, (%%rax);" // atomically add -1 to *pNum + " decl %%ebx;" // old value in %%ebx, decrement it + : "=b" (ulRet) + : "a" (pNum), "b" (-1) + : "cc", "memory" + ); + return ulRet; +} + +/* Add n */ +static __inline__ void +HXAtomicAddUINT32(UINT32* pNum, UINT32 ulNum) +{ + __asm__ __volatile__( + "lock addl %%ebx, (%%rax);" // atomically add ulNum to *pNum + : /* no output */ + : "a" (pNum), "b" (ulNum) + : "cc", "memory" + ); +} + +/* Subtract n */ +static __inline__ void +HXAtomicSubUINT32(UINT32* pNum, UINT32 ulNum) +{ + __asm__ __volatile__( + "lock subl %%ebx, (%%rax);" // atomically add ulNum to *pNum + : /* no output */ + : "a" (pNum), "b" (ulNum) + : "cc", "memory" + ); +} + +/* Add n and return new value */ +static __inline__ UINT32 +HXAtomicAddRetUINT32(UINT32* pNum, UINT32 ulNum) +{ + volatile UINT32 ulRet; + __asm__ __volatile__( + " movl %%ebx, %%ecx;" // copy ulNum into %0 + "lock xaddl %%ecx, (%%rax);" // atomically add ulNum to *pNum + " addl %%ebx, %%ecx;" // old value in %%ecx, add ulNum + : "=c" (ulRet) + : "a" (pNum), "b" (ulNum), "c" (0) + : "cc", "memory" + ); + return ulRet; +} + +/* Subtract n and return new value */ +static __inline__ UINT32 +HXAtomicSubRetUINT32(UINT32* pNum, UINT32 ulNum) +{ + volatile UINT32 ulRet; + __asm__ __volatile__( + " subl %%ebx, %%ecx;" // negate ulNum, saving in %0 + "lock xaddl %%ecx, (%%rax);" // atomically add -(ulNum) to *pNum + " subl %%ebx, %%ecx;" // old value in %%ecx, subtract ulNum + : "=c" (ulRet) + : "a" (pNum), "b" (ulNum), "c" (0) + : "cc", "memory" + ); + return ulRet; +} + + +static __inline__ void HXAtomicIncINT32(INT32* p) { HXAtomicIncUINT32((UINT32*)p); } +static __inline__ void HXAtomicDecINT32(INT32* p) { HXAtomicDecUINT32((UINT32*)p); } +static __inline__ void HXAtomicAddINT32(INT32* p, INT32 n) { HXAtomicAddUINT32((UINT32*)p, (UINT32)n); } +static __inline__ void HXAtomicSubINT32(INT32* p, INT32 n) { HXAtomicSubUINT32((UINT32*)p, (UINT32)n); } +static __inline__ INT32 HXAtomicIncRetINT32(INT32* p) { return HXAtomicIncRetUINT32((UINT32*)p); } +static __inline__ INT32 HXAtomicDecRetINT32(INT32* p) { return HXAtomicDecRetUINT32((UINT32*)p); } +static __inline__ INT32 HXAtomicAddRetINT32(INT32* p, INT32 n) { return HXAtomicAddRetUINT32((UINT32*)p, (UINT32)n); } +static __inline__ INT32 HXAtomicSubRetINT32(INT32* p, INT32 n) { return HXAtomicSubRetUINT32((UINT32*)p, (UINT32)n); } + + + +/*********************************************************************** + * HP-UX / IA64 (Native compiler) + * + * Implementation Notes: + * A work-in-progress... + */ +#elif defined(_HPUX) && defined(_IA64) + +#if defined(__cplusplus) +extern "C" { +#endif + UINT32 _HXAtomicIncRetUINT32 (UINT32* pNum); + UINT32 _HXAtomicDecRetUINT32 (UINT32* pNum); + UINT32 _HXAtomicAddRetUINT32 (UINT32* pNum, UINT32 ulNum); + UINT32 _HXAtomicSubRetUINT32 (UINT32* pNum, UINT32 ulNum); +#if defined(__cplusplus) +} +#endif + +#define HXAtomicIncINT32(p) _HXAtomicIncRetUINT32((UINT32*)(p)) +#define HXAtomicDecINT32(p) _HXAtomicDecRetUINT32((UINT32*)(p)) +#define HXAtomicIncRetINT32(p) _HXAtomicIncRetUINT32((UINT32*)(p)) +#define HXAtomicDecRetINT32(p) _HXAtomicDecRetUINT32((UINT32*)(p)) +#define HXAtomicAddINT32(p,n) _HXAtomicAddRetUINT32((UINT32*)(p),(INT32)(n)) +#define HXAtomicSubINT32(p,n) _HXAtomicSubRetUINT32((UINT32*)(p),(INT32)(n)) +#define HXAtomicAddRetINT32(p,n) _HXAtomicAddRetUINT32((UINT32*)(p),(INT32)(n)) +#define HXAtomicSubRetINT32(p,n) _HXAtomicSubRetUINT32((UINT32*)(p),(INT32)(n)) + +#define HXAtomicIncUINT32(p) _HXAtomicIncRetUINT32((p)) +#define HXAtomicDecUINT32(p) _HXAtomicDecRetUINT32((p)) +#define HXAtomicIncRetUINT32(p) _HXAtomicIncRetUINT32((p)) +#define HXAtomicDecRetUINT32(p) _HXAtomicDecRetUINT32((p)) +#define HXAtomicAddUINT32(p,n) _HXAtomicAddRetUINT32((p),(n)) +#define HXAtomicSubUINT32(p,n) _HXAtomicSubRetUINT32((p),(n)) +#define HXAtomicAddRetUINT32(p,n) _HXAtomicAddRetUINT32((p),(n)) +#define HXAtomicSubRetUINT32(p,n) _HXAtomicSubRetUINT32((p),(n)) + + + +/*********************************************************************** + * Tru64 (OSF1) / Alpha (Native compiler) + * + * Implementation Notes: + * + * The Alpha CPU provides instructions to load-lock a value, + * modify it, and attempt to write it back. If the value has + * been modified by someone else since the load-lock occurred, + * the write will fail and you can check the status code to + * know whether you need to retry or not. + * + */ +#elif defined (__alpha) + +#include <c_asm.h> + +/* Increment by 1 and return new value */ +inline INT32 +HXAtomicIncRetINT32(INT32* pNum) +{ + return asm ( + "10: ldl_l %t0, (%a0);" // Load-lock value into a register + " addl %t0, 1, %t0;" // Increment value + " or %t0, %zero, %v0;" // set new value for return. + " stl_c %t0, (%a0);" // Save new value into *pNum + " beq %t0, 10b;" // Retry if sequence failed + , pNum); +} + +/* Decrement by 1 and return new value */ +inline INT32 +HXAtomicDecRetINT32(INT32* pNum) +{ + return asm ( + "10: ldl_l %t0, (%a0);" // Load-lock value into a register + " subl %t0, 1, %t0;" // Decrement value + " or %t0, %zero, %v0;" // set new value for return. + " stl_c %t0, (%a0);" // Save new value into *pNum + " beq %t0, 10b;" // Retry if sequence failed + , pNum); +} + +/* Add n and return new value */ +inline INT32 +HXAtomicAddRetINT32(INT32* pNum, INT32 n) +{ + return asm ( + "10: ldl_l %t0, (%a0);" // Load-lock value into a register + " addl %t0, %a1, %t0;" // Add n to value + " or %t0, %zero, %v0;" // set new value for return. + " stl_c %t0, (%a0);" // Save new value into *pNum + " beq %t0, 10b;" // Retry if sequence failed + , pNum, n); +} + +/* Subtract n and return new value */ +inline INT32 +HXAtomicSubRetINT32(INT32* pNum, INT32 n) +{ + return asm ( + "10: ldl_l %t0, (%a0);" // Load-lock value into a register + " subl %t0, %a1, %t0;" // Subtract n from value + " or %t0, %zero, %v0;" // set new value for return. + " stl_c %t0, (%a0);" // Save new value into *pNum + " beq %t0, 10b;" // Retry if sequence failed + , pNum, n); +} + +/* Increment by 1 and return new value */ +inline UINT32 +HXAtomicIncRetUINT32(UINT32* pNum) +{ + return asm ( + "10: ldl_l %t0, (%a0);" // Load-lock value into a register + " addl %t0, 1, %t0;" // Increment value + " or %t0, %zero, %v0;" // set new value for return. + " stl_c %t0, (%a0);" // Save new value into *pNum + " beq %t0, 10b;" // Retry if sequence failed + , pNum); +} + +/* Decrement by 1 and return new value */ +inline UINT32 +HXAtomicDecRetUINT32(UINT32* pNum) +{ + return asm ( + "10: ldl_l %t0, (%a0);" // Load-lock value into a register + " subl %t0, 1, %t0;" // Decrement value + " or %t0, %zero, %v0;" // set new value for return. + " stl_c %t0, (%a0);" // Save new value into *pNum + " beq %t0, 10b;" // Retry if sequence failed + , pNum); +} + +/* Add n and return new value */ +inline UINT32 +HXAtomicAddRetUINT32(UINT32* pNum, UINT32 n) +{ + return asm ( + "10: ldl_l %t0, (%a0);" // Load-lock value into a register + " addl %t0, %a1, %t0;" // Add n to value + " or %t0, %zero, %v0;" // set new value for return. + " stl_c %t0, (%a0);" // Save new value into *pNum + " beq %t0, 10b;" // Retry if sequence failed + , pNum, n); +} + +/* Subtract n and return new value */ +inline UINT32 +HXAtomicSubRetUINT32(UINT32* pNum, UINT32 n) +{ + return asm ( + "10: ldl_l %t0, (%a0);" // Load-lock value into a register + " subl %t0, %a1, %t0;" // Subtract n from value + " or %t0, %zero, %v0;" // set new value for return. + " stl_c %t0, (%a0);" // Save new value into *pNum + " beq %t0, 10b;" // Retry if sequence failed + , pNum, n); +} + +#define HXAtomicIncINT32(p) HXAtomicIncRetINT32((p)) +#define HXAtomicDecINT32(p) HXAtomicDecRetINT32((p)) +#define HXAtomicAddINT32(p,n) HXAtomicAddRetINT32((p),(n)) +#define HXAtomicSubINT32(p,n) HXAtomicSubRetINT32((p),(n)) + +#define HXAtomicIncUINT32(p) HXAtomicIncRetUINT32((p)) +#define HXAtomicDecUINT32(p) HXAtomicDecRetUINT32((p)) +#define HXAtomicAddUINT32(p,n) HXAtomicAddRetUINT32((p),(n)) +#define HXAtomicSubUINT32(p,n) HXAtomicSubRetUINT32((p),(n)) + + + +/*********************************************************************** + * AIX / PowerPC (Native compiler) + * + * Implementation Notes: + * + * XXXDC: The xlc compiler is able to do inline asm for C but when I do + * it for C++ it crashes, so for now I have resorted to putting + * the asm in a separate assembler routine. The way you inline with + * xlc/xlC is difficult to use, requiring the use of "#pragma mc_func". + */ +#elif defined (_AIX) + +//defined in common/util/platform/aix/atomicops.s +#if defined(__cplusplus) +extern "C" { +#endif + INT32 _HXAtomicAddRetINT32 (INT32* pNum, INT32 lNum); + INT32 _HXAtomicSubRetINT32 (INT32* pNum, INT32 lNum); + UINT32 _HXAtomicAddRetUINT32 (UINT32* pNum, UINT32 ulNum); + UINT32 _HXAtomicSubRetUINT32 (UINT32* pNum, UINT32 ulNum); +#if defined(__cplusplus) +} +#endif + +#define HXAtomicIncINT32(p) _HXAtomicAddRetINT32((p),(INT32)1) +#define HXAtomicDecINT32(p) _HXAtomicSubRetINT32((p),(INT32)1) +#define HXAtomicIncRetINT32(p) _HXAtomicAddRetINT32((p),(INT32)1) +#define HXAtomicDecRetINT32(p) _HXAtomicSubRetINT32((p),(INT32)1) +#define HXAtomicAddINT32(p,n) _HXAtomicAddRetINT32((p),(n)) +#define HXAtomicSubINT32(p,n) _HXAtomicSubRetINT32((p),(n)) +#define HXAtomicAddRetINT32(p,n) _HXAtomicAddRetINT32((p),(n)) +#define HXAtomicSubRetINT32(p,n) _HXAtomicSubRetINT32((p),(n)) + +#define HXAtomicIncUINT32(p) _HXAtomicAddRetUINT32((p),(UINT32)1) +#define HXAtomicDecUINT32(p) _HXAtomicSubRetUINT32((p),(UINT32)1) +#define HXAtomicIncRetUINT32(p) _HXAtomicAddRetUINT32((p),(UINT32)1) +#define HXAtomicDecRetUINT32(p) _HXAtomicSubRetUINT32((p),(UINT32)1) +#define HXAtomicAddUINT32(p,n) _HXAtomicAddRetUINT32((p),(n)) +#define HXAtomicSubUINT32(p,n) _HXAtomicSubRetUINT32((p),(n)) +#define HXAtomicAddRetUINT32(p,n) _HXAtomicAddRetUINT32((p),(n)) +#define HXAtomicSubRetUINT32(p,n) _HXAtomicSubRetUINT32((p),(n)) + + +/*********************************************************************** + * MAC / PowerPC (CW) + * + * Implementation Notes: + * + * This will need to be rewritten, probably, once we move away from CW to PB. + * + * Note: This is an imcompletely-defined platform, be aware that + * not all standard HXAtomic operators are defined! + * + */ +#elif defined(_MACINTOSH) && defined(__MWERKS__) + +inline UINT32 +HXAtomicIncRetUINT32(register UINT32* pNum) +{ + register UINT32 zeroOffset = 0; + register UINT32 temp; + + asm + { + again: + lwarx temp, zeroOffset, pNum + addi temp, temp, 1 + stwcx. temp, zeroOffset, pNum + bne- again + } + + return temp; +} + +inline UINT32 +HXAtomicDecRetUINT32(register UINT32* pNum) +{ + register UINT32 zeroOffset = 0; + register UINT32 temp; + + asm + { + again: + lwarx temp, zeroOffset, pNum + subi temp, temp, 1 + stwcx. temp, zeroOffset, pNum + bne- again + } + + return temp; +} + + +/*********************************************************************** + * MAC - PowerPC (PB or XCode) / Linux - PowerPC + * + * Implementation Notes: + * + * Use PowerPC load exclusive and store exclusive instructions + * + */ +#elif defined(_MAC_UNIX) || (defined(_LINUX) && defined(__powerpc__)) + +// could also probably be defined(__GNUC__) && defined(__powerpc) + +static inline UINT32 +HXAtomicIncRetUINT32(UINT32* pNum) +{ + volatile UINT32 result; + + __asm__ __volatile__ ( +"1: lwarx %0, %3, %2;\n" +" addi %0, %0, 1;\n" +" stwcx. %0, %3, %2;\n" +" bne- 1b;" + : "=b" (result) + : "0" (result), "b" (pNum), "b" (0x0) + : "cc", "memory" + ); + + return result; +} + +static inline UINT32 +HXAtomicDecRetUINT32(UINT32* pNum) +{ + volatile UINT32 result; + + __asm__ __volatile__ ( +"1: lwarx %0, %3, %2;\n" +" subi %0, %0, 1;\n" +" stwcx. %0, %3, %2;\n" +" bne- 1b;" + : "=b" (result) + : "0" (result), "b" (pNum), "b" (0x0) + : "cc", "memory" + ); + + return result; +} + + +static inline UINT32 +HXAtomicAddRetUINT32(UINT32* pNum, UINT32 ulNum) +{ + volatile UINT32 result; + + __asm__ __volatile__ ( +"1: lwarx %0, %3, %2;\n" +" add %0, %0, %4;\n" +" stwcx. %0, %3, %2;\n" +" bne- 1b;" + : "=b" (result) + : "0" (result), "b" (pNum), "b" (0x0), "b" (ulNum) + : "cc", "memory" + ); + + return result; +} + + +static inline UINT32 +HXAtomicSubRetUINT32(UINT32* pNum, UINT32 ulNum) +{ + volatile UINT32 result; + + __asm__ __volatile__ ( +"1: lwarx %0, %3, %2;\n" +" sub %0, %0, %4;\n" +" stwcx. %0, %3, %2;\n" +" bne- 1b;" + : "=b" (result) + : "0" (result), "b" (pNum), "b" (0x0), "b" (ulNum) + : "cc", "memory" + ); + + return result; +} + +// the rest of these atomic operations can be implemented in terms of the four above. + +static inline void HXAtomicIncINT32(INT32* p) { (void)HXAtomicIncRetUINT32((UINT32*)p); } +static inline void HXAtomicDecINT32(INT32* p) { (void)HXAtomicDecRetUINT32((UINT32*)p); } +static inline void HXAtomicAddINT32(INT32* p, INT32 n) { (void)HXAtomicAddRetUINT32((UINT32*)p, (UINT32)n); } +static inline void HXAtomicSubINT32(INT32* p, INT32 n) { (void)HXAtomicSubRetUINT32((UINT32*)p, (UINT32)n); } +static inline INT32 HXAtomicIncRetINT32(INT32* p) { return (INT32)HXAtomicIncRetUINT32((UINT32*)p); } +static inline INT32 HXAtomicDecRetINT32(INT32* p) { return (INT32)HXAtomicDecRetUINT32((UINT32*)p); } +static inline INT32 HXAtomicAddRetINT32(INT32* p, INT32 n) { return (INT32)HXAtomicAddRetUINT32((UINT32*)p, (UINT32)n); } +static inline INT32 HXAtomicSubRetINT32(INT32* p, INT32 n) { return (INT32)HXAtomicSubRetUINT32((UINT32*)p, (UINT32)n); } +static inline void HXAtomicIncUINT32(UINT32* p) { (void)HXAtomicIncRetUINT32(p); } +static inline void HXAtomicDecUINT32(UINT32* p) { (void)HXAtomicDecRetUINT32(p); } +static inline void HXAtomicAddUINT32(UINT32* p, UINT32 n) { (void)HXAtomicAddRetUINT32(p, n); } +static inline void HXAtomicSubUINT32(UINT32* p, UINT32 n) { (void)HXAtomicSubRetUINT32(p, n); } + + +/*********************************************************************** + * Generic + * + * Implementation Notes: + * + * This should work on any platform with a HXMutex-style mutex. + * It allocates a pool of mutexes and hashes the int pointers + * to one of the mutexes. Since the mutexes are held for + * such a short time, only long enough to increment an int, + * collisions should be extremely rare and this should work fine, + * although it is probably less fast than the extra-high-performance + * atomic operators provided above. You need to link in atomic.cpp + * to get HXAtomic::m_pLocks defined. + * + * Basic design of the mutex-based lock-pool implementation: + * At startup, allocate an array of N mutexes (where N is a power of 2). + * When a method is called, hash the int pointer to one of the locks. + * Lock this mutex. + * Modify the value. + * Unlock this mutex. + * + * + * Platform-specific notes: + * Any platforms that use this should be documented here! + * Why are you using the generic operators for this platform? + * + * HP-UX / HP-PA: + * This is used on the HP-PA processor since it doesn't provide the + * necessary assembler operators to implement proper atomic updates + * of ints. HP's mutex primitive seems pretty fast however, resulting + * in a workable solution. + * + * OpenBSD: + * The standard assembler on x86 can't handle the gcc/asm operators + * defined above, so we're using the lock-pool approach for now. + * This approach also makes it possible to support non-x86 OpenBSD + * builds more easily (someday). + * + */ +#elif defined(_HPUX) || defined(_OPENBSD) + +#if defined(__cplusplus) +#include "microsleep.h" +#include "hxcom.h" +#include "hxmutexlock.h" + +class HXAtomic +{ +public: + HXAtomic(); + ~HXAtomic(); + void InitLockPool(); + + /* Users of the HXAtomic routines should *NEVER* call these directly. + * They should *ALWAYS* use the HXAtomicAddRetINT32-style macros instead. + */ + INT32 _AddRetINT32 (INT32* pNum, INT32 nNum); + UINT32 _AddRetUINT32 (UINT32* pNum, UINT32 ulNum); + INT32 _SubRetINT32 (INT32* pNum, INT32 nNum); + UINT32 _SubRetUINT32 (UINT32* pNum, UINT32 ulNum); + +private: + void Lock (HX_MUTEX pLock); + void Unlock (HX_MUTEX pLock); + + HX_MUTEX* m_pLocks; +}; + +extern HXAtomic g_AtomicOps; //in common/util/atomicops.cpp + +#define HXAtomicIncINT32(p) g_AtomicOps._AddRetINT32((p),(INT32)1) +#define HXAtomicDecINT32(p) g_AtomicOps._SubRetINT32((p),(INT32)1) +#define HXAtomicIncRetINT32(p) g_AtomicOps._AddRetINT32((p),(INT32)1) +#define HXAtomicDecRetINT32(p) g_AtomicOps._SubRetINT32((p),(INT32)1) + +#define HXAtomicAddRetINT32(p,n) g_AtomicOps._AddRetINT32((p),(n)) +#define HXAtomicSubRetINT32(p,n) g_AtomicOps._SubRetINT32((p),(n)) +#define HXAtomicAddINT32(p,n) g_AtomicOps._AddRetINT32((p),(n)) +#define HXAtomicSubINT32(p,n) g_AtomicOps._SubRetINT32((p),(n)) + +#define HXAtomicIncUINT32(p) g_AtomicOps._AddRetUINT32((p),(UINT32)1) +#define HXAtomicDecUINT32(p) g_AtomicOps._SubRetUINT32((p),(UINT32)1) +#define HXAtomicIncRetUINT32(p) g_AtomicOps._AddRetUINT32((p),(UINT32)1) +#define HXAtomicDecRetUINT32(p) g_AtomicOps._SubRetUINT32((p),(UINT32)1) + +#define HXAtomicAddRetUINT32(p,n) g_AtomicOps._AddRetUINT32((p),(n)) +#define HXAtomicSubRetUINT32(p,n) g_AtomicOps._SubRetUINT32((p),(n)) +#define HXAtomicAddUINT32(p,n) g_AtomicOps._AddRetUINT32((p),(n)) +#define HXAtomicSubUINT32(p,n) g_AtomicOps._SubRetUINT32((p),(n)) +#endif + + + +/*********************************************************************** + * SYMBIAN + * + * Implementation Notes: + * + * Note: This is an imcompletely-defined platform, be aware that + * not all standard HXAtomic operators are defined! + * + */ +#elif defined(_SYMBIAN) + +/* Increment by 1 and return new value */ +inline INT32 +HXAtomicIncRetINT32(INT32* pNum) +{ + return User::LockedInc(*((TInt*)pNum)) + 1; +} + +/* Decrement by 1 and return new value */ +inline INT32 +HXAtomicDecRetINT32(INT32* pNum) +{ + return User::LockedDec(*((TInt*)pNum)) - 1; +} + +/* Increment by 1 and return new value */ +inline UINT32 +HXAtomicIncRetUINT32(UINT32* pNum) +{ + return ((UINT32)User::LockedInc(*((TInt*)pNum))) + 1; +} + +/* Decrement by 1 and return new value */ +inline UINT32 +HXAtomicDecRetUINT32(UINT32* pNum) +{ + return ((UINT32)User::LockedDec(*((TInt*)pNum))) - 1; +} + +#define HXAtomicIncINT32(p) HXAtomicIncRetINT32((p)) +#define HXAtomicDecINT32(p) HXAtomicDecRetINT32((p)) +#define HXAtomicIncUINT32(p) HXAtomicIncRetUINT32((p)) +#define HXAtomicDecUINT32(p) HXAtomicDecRetUINT32((p)) + +#if 0 + +/* + * Add and subtract operations are not implemented + * at this time because there isn't an easy way to + * do it using the facilities provided by Symbian. + * Assembly will likely be needed. + */ + +/* Add n and return new value */ +inline INT32 +HXAtomicAddRetINT32(INT32* pNum, INT32 n) +{ + +} + +/* Subtract n and return new value */ +inline INT32 +HXAtomicSubRetINT32(INT32* pNum, INT32 n) +{ + +} + +/* Add n and return new value */ +inline UINT32 +HXAtomicAddRetUINT32(UINT32* pNum, UINT32 n) +{ + +} + +/* Subtract n and return new value */ +inline UINT32 +HXAtomicSubRetUINT32(UINT32* pNum, UINT32 n) +{ + +} + +#define HXAtomicAddINT32(p,n) HXAtomicAddRetINT32((p),(n)) +#define HXAtomicSubINT32(p,n) HXAtomicSubRetINT32((p),(n)) + +#define HXAtomicAddUINT32(p,n) HXAtomicAddRetUINT32((p),(n)) +#define HXAtomicSubUINT32(p,n) HXAtomicSubRetUINT32((p),(n)) + +#endif + +/*********************************************************************** + * Linux / ARM (gcc) + * + * Implementation Notes: + * + * This implementation sacrifices being able to store the value + * 0x800000000 in the INT32 value, which is a special "busy" marker value. + * Since these are intended for use primarily with AddRef/Release and + * resource usage counters, this should be acceptable for now. If a counter + * is incremented to the point it would conflict with the flag, it is + * incremented one more to hop over it. The same in reverse for decrement. + * + * Basic design of the flag-based implementation: + * 1. Load a register with 0x80000000 + * 2. _atomically_ swap it with the INT32 (critical!) + * 3. Compare what we got with 0x80000000 + * 4. Branch if equal to #2 + * 5. Increment (or decrement) the result + * 6. Compare to 0x80000000 + * 7. Increment (or decrement) again if equal + * 8. Save the new value to the INT32's location in memory + * 9. Return new INT32 result if required + * + */ +#elif defined (_ARM) && defined (__GNUC__) + +/* Increment by 1 */ +inline void +HXAtomicIncUINT32(UINT32* pNum) +{ + UINT32 ulTmp; + __asm__ __volatile__( +" mov %0, #0x80000000;\n" /* Set ulTmp to 0x800000000 */ +"1: swp %0, %0, [%1];\n" /* Swap *pNum and ulTmp */ +" cmp %0, #0x80000000;\n" /* Is someone else using pNum? */ +" beq 1;\n" /* If so, retry... */ +" add %0, %0, #1;\n" /* Increment ulTmp */ +" cmp %0, #0x80000000;\n" /* check for overflow */ +" addeq %0, %0, #1;\n" /* if so, increment again */ +" str %0, [%1];\n" /* Save new value into *pNum */ + : /* no output */ + : "r" (ulTmp), "r" (pNum) + : "cc", "memory" + ); +} + +/* Decrement by 1 */ +inline void +HXAtomicDecUINT32(UINT32* pNum) +{ + UINT32 ulTmp; + __asm__ __volatile__( +" mov %0, #0x80000000;\n" /* Set ulTmp to 0x800000000 */ +"1: swp %0, %0, [%1];\n" /* Swap *pNum and ulTmp */ +" cmp %0, #0x80000000;\n" /* Is someone else using pNum? */ +" beq 1;\n" /* If so, retry... */ +" sub %0, %0, #1;\n" /* Decrement ulTmp */ +" cmp %0, #0x80000000;\n" /* check for overflow */ +" subeq %0, %0, #1;\n" /* if so, decrement again */ +" str %0, [%1];\n" /* Save new value into *pNum */ + : /* no output */ + : "r" (ulTmp), "r" (pNum) + : "cc", "memory" + ); +} + +/* Increment by 1 and return new value */ +inline UINT32 +HXAtomicIncRetUINT32(UINT32* pNum) +{ + volatile UINT32 ulRet; + __asm__ __volatile__( +" mov %0, #0x80000000;\n" /* Set ulRet to 0x80000000 */ +"1: swp %0, %0, [%1];\n" /* Swap *pNum and ulRet */ +" cmp %0, #0x80000000;\n" /* Is someone else using pNum? */ +" beq 1;\n" /* If so, retry... */ +" add %0, %0, #1;\n" /* Increment ulRet */ +" cmp %0, #0x80000000;\n" /* check for overflow */ +" addeq %0, %0, #1;\n" /* if so, increment again */ +" str %0, [%1];\n" /* Save new value into *pNum */ + : "=&r" (ulRet) + : "r" (pNum) + : "cc", "memory" + ); + return ulRet; +} + +/* Decrement by 1 and return new value */ +inline UINT32 +HXAtomicDecRetUINT32(UINT32* pNum) +{ + volatile UINT32 ulRet; + __asm__ __volatile__( +" mov %0, #0x80000000;\n" /* Set ulRet to 0x80000000 */ +"1: swp %0, %0, [%1];\n" /* Swap *pNum and ulRet */ +" cmp %0, #0x80000000;\n" /* Is someone else using pNum? */ +" beq 1;\n" /* If so, retry... */ +" sub %0, %0, #1;\n" /* Decrement ulRet */ +" cmp %0, #0x80000000;\n" /* check for overflow */ +" subeq %0, %0, #1;\n" /* if so, decrement again */ +" str %0, [%1];\n" /* Save new value into *pNum */ + : "=&r" (ulRet) + : "r" (pNum) + : "cc", "memory" + ); + return ulRet; +} + +/* Add n */ +inline void +HXAtomicAddUINT32(UINT32* pNum, UINT32 ulNum) +{ + UINT32 ulTmp; + __asm__ __volatile__( +" mov %0, #0x80000000;\n" /* Set ulTmp to 0x800000000 */ +"1: swp %0, %0, [%1];\n" /* Swap *pNum and ulTmp */ +" cmp %0, #0x80000000;\n" /* Is someone else using pNum? */ +" beq 1;\n" /* If so, retry... */ +" add %0, %0, %2;\n" /* Add ulNum to ulTmp */ +" cmp %0, #0x80000000;\n" /* check for overflow */ +" addeq %0, %0, #1;\n" /* if so, increment again */ +" str %0, [%1];\n" /* Save new value into *pNum */ + : /* no output */ + : "r" (ulTmp), "r" (pNum), "r" (ulNum) + : "cc", "memory" + ); +} + +/* Subtract n */ +inline void +HXAtomicSubUINT32(UINT32* pNum, UINT32 ulNum) +{ + UINT32 ulTmp; + __asm__ __volatile__( +" mov %0, #0x80000000;\n" /* Set ulTmp to 0x800000000 */ +"1: swp %0, %0, [%1];\n" /* Swap *pNum and ulTmp */ +" cmp %0, #0x80000000;\n" /* Is someone else using pNum? */ +" beq 1;\n" /* If so, retry... */ +" sub %0, %0, %2;\n" /* Subtract ulNum from ulTmp */ +" cmp %0, #0x80000000;\n" /* check for overflow */ +" subeq %0, %0, #1;\n" /* if so, decrement again */ +" str %0, [%1];\n" /* Save new value into *pNum */ + : /* no output */ + : "r" (ulTmp), "r" (pNum), "r" (ulNum) + : "cc", "memory" + ); +} + +/* Add n and return new value */ +inline UINT32 +HXAtomicAddRetUINT32(UINT32* pNum, UINT32 ulNum) +{ + volatile UINT32 ulRet; + __asm__ __volatile__( +" mov %0, #0x80000000;\n" /* Set ulRet to 0x80000000 */ +"1: swp %0, %0, [%1];\n" /* Swap *pNum and ulRet */ +" cmp %0, #0x80000000;\n" /* Is someone else using pNum? */ +" beq 1;\n" /* If so, retry... */ +" add %0, %0, %2;\n" /* Add ulNum to ulRet */ +" cmp %0, #0x80000000;\n" /* check for overflow */ +" addeq %0, %0, #1;\n" /* if so, increment again */ +" str %0, [%1];\n" /* Save new value into *pNum */ + : "=&r" (ulRet) + : "r" (pNum) , "r" (ulNum) + : "cc", "memory" + ); + return ulRet; +} + +/* Subtract n and return new value */ +inline UINT32 +HXAtomicSubRetUINT32(UINT32* pNum, UINT32 ulNum) +{ + volatile UINT32 ulRet; + __asm__ __volatile__( +" mov %0, #0x80000000;\n" /* Set ulRet to 0x80000000 */ +"1: swp %0, %0, [%1];\n" /* Swap *pNum and ulRet */ +" cmp %0, #0x80000000;\n" /* Is someone else using pNum? */ +" beq 1;\n" /* If so, retry... */ +" sub %0, %0, %2;\n" /* Subtract ulNum from ulRet */ +" cmp %0, #0x80000000;\n" /* check for overflow */ +" subeq %0, %0, #1;\n" /* if so, decrement again */ +" str %0, [%1];\n" /* Save new value into *pNum */ + : "=&r" (ulRet) + : "r" (pNum), "r" (ulNum) + : "cc", "memory" + ); + return ulRet; +} + +inline void HXAtomicIncINT32(INT32* p) { HXAtomicIncUINT32((UINT32*)p); } +inline void HXAtomicDecINT32(INT32* p) { HXAtomicDecUINT32((UINT32*)p); } +inline void HXAtomicAddINT32(INT32* p, INT32 n) { HXAtomicAddUINT32((UINT32*)p, (UINT32)n); } +inline void HXAtomicSubINT32(INT32* p, INT32 n) { HXAtomicSubUINT32((UINT32*)p, (UINT32)n); } +inline INT32 HXAtomicIncRetINT32(INT32* p) { return HXAtomicIncRetUINT32((UINT32*)p); } +inline INT32 HXAtomicDecRetINT32(INT32* p) { return HXAtomicDecRetUINT32((UINT32*)p); } +inline INT32 HXAtomicAddRetINT32(INT32* p, INT32 n) { return HXAtomicAddRetUINT32((UINT32*)p, (UINT32)n); } +inline INT32 HXAtomicSubRetINT32(INT32* p, INT32 n) { return HXAtomicSubRetUINT32((UINT32*)p, (UINT32)n); } + +/*********************************************************************** + * Add new platforms above here + */ +#else + +// +// Unsupported platform +// + +#ifndef HELIX_CONFIG_DISABLE_ATOMIC_OPERATORS +// Defining HELIX_CONFIG_DISABLE_ATOMIC_OPERATORS will use the ++ and -- +// operators in place of atomic operators in some places in the code. These +// operators are not thread-safe, and should only be used in the intermediary +// stages of porting. +# error "You need to create atomic dec/inc opers for your platform or #define HELIX_CONFIG_DISABLE_ATOMIC_OPERATORS" +#endif + +#endif + + + +/*************************************************************************/ + +/* + * Conditional override of InterlockedIncrement/Decrement + * + * Place this in your Umakefil/.pcf file to turn off atomic + * InterlockedIncrement/Decrement on a per-module basis, + * or place it in your umake profile for system-wide scope. + * If this is defined you'll still have access to the underlying + * HXAtomicxxx operators (if they exist for your platform), + * just that the specific InterlockedIncrement/InterlockedDecrement + * macros won't be defined to use them. + */ +#if !defined (HELIX_CONFIG_DISABLE_ATOMIC_OPERATORS) + +#undef InterlockedIncrement +#undef InterlockedDecrement + +// Since many classes (incorrectly) implement their refcount using LONG32 +// rather than the proper ULONG32, we have to use the typecast for things +// to build on many platforms. +#define InterlockedIncrement(p) HXAtomicIncRetUINT32((UINT32*)(p)) +#define InterlockedDecrement(p) HXAtomicDecRetUINT32((UINT32*)(p)) + +#if !defined(HAVE_INTERLOCKED_INCREMENT) +#define HAVE_INTERLOCKED_INCREMENT //so hxcom.h doesn't redefine these to ++/-- +#endif // /HAVE_INTERLOCKED_INCREMENT. + +#endif /* !defined(HELIX_CONFIG_DISABLE_ATOMIC_OPERATORS) */ + +#endif /* _ATOMICBASE_H_ */ diff --git a/amarok/src/engine/helix/helix-sp/helix-include/common/include/hxausvc.h b/amarok/src/engine/helix/helix-sp/helix-include/common/include/hxausvc.h new file mode 100644 index 00000000..a366ca3d --- /dev/null +++ b/amarok/src/engine/helix/helix-sp/helix-include/common/include/hxausvc.h @@ -0,0 +1,1729 @@ + +/* + * + * This software is released under the provisions of the GPL version 2. + * see file "COPYING". If that file is not available, the full statement + * of the license can be found at + * + * http://www.fsf.org/licensing/licenses/gpl.txt + * + * Portions Copyright (c) 1995-2004 RealNetworks, Inc. All Rights Reserved. + * + */ + +#ifndef _HXAUSVC_H_ +#define _HXAUSVC_H_ + +#define HX_MAX_VOLUME 100 +#define HX_INIT_VOLUME 50 +#define HX_MIN_VOLUME 0 + +/**************************************************************************** + * + * Forward declarations of some interfaces defined here-in. + */ +typedef _INTERFACE IHXAudioPlayer IHXAudioPlayer; +typedef _INTERFACE IHXAudioPlayerResponse IHXAudioPlayerResponse; +typedef _INTERFACE IHXAudioStream IHXAudioStream; +typedef _INTERFACE IHXAudioStream2 IHXAudioStream2; +typedef _INTERFACE IHXAudioDevice IHXAudioDevice; +typedef _INTERFACE IHXAudioDeviceResponse IHXAudioDeviceResponse; +typedef _INTERFACE IHXAudioHook IHXAudioHook; +typedef _INTERFACE IHXAudioDeviceHookManager IHXAudioDeviceHookManager; +typedef _INTERFACE IHXAudioStreamInfoResponse IHXAudioStreamInfoResponse; +// $Private: +typedef _INTERFACE IHXMultiPlayPauseSupport IHXMultiPlayPauseSupport; +typedef _INTERFACE IHXAudioDeviceManager2 IHXAudioDeviceManager2; +typedef _INTERFACE IHXAudioResampler IHXAudioResampler; +typedef _INTERFACE IHXAudioResamplerManager IHXAudioResamplerManager; +typedef _INTERFACE IHXAudioPushdown2 IHXAudioPushdown2; +// $EndPrivate. +typedef _INTERFACE IHXVolume IHXVolume; +typedef _INTERFACE IHXVolumeAdviseSink IHXVolumeAdviseSink; +typedef _INTERFACE IHXDryNotification IHXDryNotification; +typedef _INTERFACE IHXBuffer IHXBuffer; +typedef _INTERFACE IHXValues IHXValues; + +/**************************************************************************** + * + * Audio Services Data Structures + */ +typedef struct _HXAudioFormat +{ + UINT16 uChannels; /* Num. of Channels (1=Mono, 2=Stereo, etc. */ + UINT16 uBitsPerSample; /* 8 or 16 */ + UINT32 ulSamplesPerSec;/* Sampling Rate */ + UINT16 uMaxBlockSize; /* Max Blocksize */ +} HXAudioFormat; + +typedef enum _AudioStreamType +{ + STREAMING_AUDIO = 0, + INSTANTANEOUS_AUDIO = 1, + TIMED_AUDIO = 2, + STREAMING_INSTANTANEOUS_AUDIO = 3 +} AudioStreamType; + +typedef struct _HXAudioData +{ + IHXBuffer* pData; /* Audio data */ + ULONG32 ulAudioTime; /* Start time in milliseconds */ + AudioStreamType uAudioStreamType; +} HXAudioData; + +typedef enum _AudioDeviceHookType +{ + READ_ONLY_EARLY = 0, + WRITABLE = 127, + READ_ONLY_LATE = 255 +} AudioDeviceHookType; + +/**************************************************************************** + * + * Interface: + * + * IHXAudioPlayer + * + * Purpose: + * + * This interface provides access to the Audio Player services. Use this + * interface to create audio streams, "hook" post-mixed audio data, and to + * control volume levels. + * + * IID_IHXAudioPlayer: + * + * {00000700-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXAudioPlayer, 0x00000700, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + + +#undef INTERFACE +#define INTERFACE IHXAudioPlayer + +DECLARE_INTERFACE_(IHXAudioPlayer, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXAudioPlayer methods + */ + /************************************************************************ + * Method: + * IHXAudioPlayer::CreateAudioStream + * Purpose: + * Call this to create an audio stream. + */ + STDMETHOD(CreateAudioStream) (THIS_ + IHXAudioStream** /*OUT*/ pAudioStream + ) PURE; + + /************************************************************************ + * Method: + * IHXAudioPlayer::AddPostMixHook + * Purpose: + * Call this to hook audio data after all audio streams in this + * have been mixed. + */ + STDMETHOD(AddPostMixHook) (THIS_ + IHXAudioHook* /*IN*/ pHook, + const HXBOOL /*IN*/ bDisableWrite, + const HXBOOL /*IN*/ bFinal + ) PURE; + + /************************************************************************ + * Method: + * IHXAudioPlayer::RemovePostMixHook + * Purpose: + * Call this to remove an already added post hook. + */ + STDMETHOD(RemovePostMixHook) (THIS_ + IHXAudioHook* /*IN*/ pHook + ) PURE; + + /************************************************************************ + * Method: + * IHXAudioPlayer::GetAudioStreamCount + * Purpose: + * Get the number of audio streams currently active in the + * audio player. Since streams can be added mid-presentation + * this function may return different values on different calls. + * If the user needs to know about all the streams as they get + * get added to the player, IHXAudioStreamInfoResponse should + * be implemented and passed in SetStreamInfoResponse. + */ + STDMETHOD_(UINT16,GetAudioStreamCount) (THIS) PURE; + + /************************************************************************ + * Method: + * IHXAudioPlayer::GetAudioStream + * Purpose: + * Get an audio stream at position given. + */ + STDMETHOD_(IHXAudioStream*,GetAudioStream) (THIS_ + UINT16 /*IN*/ uIndex + ) PURE; + + /************************************************************************ + * Method: + * IHXAudioPlayer::SetStreamInfoResponse + * Purpose: + * Set a stream info response interface. A client must implement + * an IHXAudioStreamInfoResponse and then call this method with + * the IHXAudioStreamInfoResponse as the parameter. The audio + * player will call IHXAudioStreamInfoResponse::OnStreamsReady + * with the total number of audio streams associated with this + * audio player. + */ + STDMETHOD(SetStreamInfoResponse) (THIS_ + IHXAudioStreamInfoResponse* /*IN*/ pResponse + ) PURE; + + /************************************************************************ + * Method: + * IHXAudioPlayer::RemoveStreamInfoResponse + * Purpose: + * Remove stream info response that was added earlier + */ + STDMETHOD(RemoveStreamInfoResponse) (THIS_ + IHXAudioStreamInfoResponse* /*IN*/ pResponse + ) PURE; + + /************************************************************************ + * Method: + * IHXAudioPlayer::GetAudioVolume + * Purpose: + * Get the audio player's volume interface. This volume controls + * the volume level of all the mixed audio streams for this + * audio player. + */ + STDMETHOD_(IHXVolume*,GetAudioVolume) (THIS) PURE; + + /************************************************************************ + * Method: + * IHXAudioPlayer::GetDeviceVolume + * Purpose: + * Get the audio device volume interface. This volume controls + * the audio device volume levels. + */ + STDMETHOD_(IHXVolume*,GetDeviceVolume) (THIS) PURE; + +}; + +/**************************************************************************** + * + * Interface: + * + * IHXAudioPlayerResponse + * + * Purpose: + * + * This interface provides access to the Audio Player Response. Use this + * to receive audio player playback notifications. Your implementation of + * OnTimeSync() is called with the current audio playback time (millisecs). + * This interface is currently to be used ONLY by the RMA engine internally. + * + * IID_IHXAudioPlayerResponse: + * + * {00000701-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXAudioPlayerResponse, 0x00000701, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + + +#undef INTERFACE +#define INTERFACE IHXAudioPlayerResponse + +DECLARE_INTERFACE_(IHXAudioPlayerResponse, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXAudioPlayerResponse methods + */ + + /************************************************************************ + * Method: + * IHXAudioPlayerResponse::OnTimeSync + * Purpose: + * This method is called with the current audio playback time. + */ + STDMETHOD(OnTimeSync) (THIS_ + ULONG32 /*IN*/ ulTimeEnd + ) PURE; + +}; + + +/**************************************************************************** + * + * Interface: + * + * IHXAudioStream + * + * Purpose: + * + * This interface provides access to an Audio Stream. Use this to play + * audio, "hook" audio stream data, and to get audio stream information. + * + * IID_IHXAudioStream: + * + * {00000702-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXAudioStream, 0x00000702, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); +#undef INTERFACE +#define INTERFACE IHXAudioStream + +DECLARE_INTERFACE_(IHXAudioStream, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXAudioStream methods + */ + + /************************************************************************ + * Method: + * IHXAudioStream::Init + * Purpose: + * Initialize an audio stream with the given audio format. The + * IHXValues contains stream identification information. + */ + STDMETHOD(Init) (THIS_ + const HXAudioFormat* /*IN*/ pAudioFormat, + IHXValues* /*IN*/ pValues + ) PURE; + + /************************************************************************ + * Method: + * IHXAudioStream::Write + * Purpose: + * Write audio data to Audio Services. + * + * NOTE: If the renderer loses packets and there is no loss + * correction, then the renderer should write the next packet + * using a meaningful start time. Audio Services will play + * silence where packets are missing. + */ + STDMETHOD(Write) (THIS_ + HXAudioData* /*IN*/ pAudioData + ) PURE; + + /************************************************************************ + * Method: + * IHXAudioStream::AddPreMixHook + * Purpose: + * Use this to "hook" audio stream data prior to the mixing. + * Set bDisableWrite to TRUE to prevent this audio stream data + * from being mixed with other audio stream data associated + * with this audio player. + */ + STDMETHOD(AddPreMixHook) (THIS_ + IHXAudioHook* /*IN*/ pHook, + const HXBOOL /*IN*/ bDisableWrite + ) PURE; + + /************************************************************************ + * Method: + * IHXAudioStream::RemovePreMixHook + * Purpose: + * Use this to remove an already added "hook". + */ + STDMETHOD(RemovePreMixHook) (THIS_ + IHXAudioHook* /*IN*/ pHook + ) PURE; + + /************************************************************************ + * Method: + * IHXAudioStream::AddDryNotification + * Purpose: + * Use this to add a notification response object to get + * notifications when audio stream is running dry. + */ + STDMETHOD(AddDryNotification) (THIS_ + IHXDryNotification* /*IN*/ pNotification + ) PURE; + + /************************************************************************ + * Method: + * IHXAudioStream::GetStreamInfo + * Purpose: + * Use this to get information specific to this audio stream. + */ + STDMETHOD_(IHXValues*,GetStreamInfo) (THIS) PURE; + + /************************************************************************ + * Method: + * IHXAudioStream::GetAudioVolume + * Purpose: + * Get the audio stream's volume interface. This volume controls + * the volume level for this audio stream. + */ + STDMETHOD_(IHXVolume*,GetAudioVolume) (THIS) PURE; +}; + +/**************************************************************************** + * + * Interface: + * + * IHXAudioDevice + * + * Purpose: + * + * Object that exports audio device API + * This interface is currently to be used ONLY by the RMA engine + * internally. + * + * IID_IHXAudioDevice: + * + * {00000703-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXAudioDevice, 0x00000703, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXAudioDevice + +DECLARE_INTERFACE_(IHXAudioDevice, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXAudioDevice methods + */ + + /************************************************************************ + * Method: + * IHXAudioDevice::Open + * Purpose: + * The caller calls this to open the audio device using the audio + * format given. + */ + STDMETHOD(Open) (THIS_ + const HXAudioFormat* /*IN*/ pAudioFormat, + IHXAudioDeviceResponse* /*IN*/ pStreamResponse) PURE; + + /************************************************************************ + * Method: + * IHXAudioDevice::Close + * Purpose: + * The caller calls this to close the audio device. + */ + STDMETHOD(Close) (THIS_ + const HXBOOL /*IN*/ bFlush ) PURE; + + /************************************************************************ + * Method: + * IHXAudioDevice::Resume + * Purpose: + * The caller calls this to start or resume audio playback. + */ + STDMETHOD(Resume) (THIS) PURE; + + /************************************************************************ + * Method: + * IHXAudioDevice::Pause + * Purpose: + * The caller calls this to pause the audio device. If bFlush is + * TRUE, any buffers in the audio device will be flushed; otherwise, + * the buffers are played. + */ + STDMETHOD(Pause) (THIS) PURE; + + /************************************************************************ + * Method: + * IHXAudioDevice::Write + * Purpose: + * The caller calls this to write an audio buffer. + */ + STDMETHOD(Write) (THIS_ + const HXAudioData* /*IN*/ pAudioData) PURE; + + /************************************************************************ + * Method: + * IHXAudioDevice::InitVolume + * Purpose: + * The caller calls this to inform the audio stream of the client's + * volume range. The audio stream maps the client's volume range + * into the audio device volume range. + * NOTE: This function returns TRUE if volume is supported by this + * audio device. + */ + STDMETHOD_(HXBOOL,InitVolume) (THIS_ + const UINT16 /*IN*/ uMinVolume, + const UINT16 /*IN*/ uMaxVolume) PURE; + + /************************************************************************ + * Method: + * IHXAudioDevice::SetVolume + * Purpose: + * The caller calls this to set the audio device volume level. + */ + STDMETHOD(SetVolume) (THIS_ + const UINT16 /*IN*/ uVolume) PURE; + + /************************************************************************ + * Method: + * IHXAudioDevice::GetVolume + * Purpose: + * The caller calls this to get the audio device volume level. + */ + STDMETHOD_(UINT16,GetVolume) (THIS) PURE; + + /************************************************************************ + * Method: + * IHXAudioDevice::Reset + * Purpose: + * The caller calls this to reset the audio device. + */ + STDMETHOD(Reset) (THIS) PURE; + + /************************************************************************ + * Method: + * IHXAudioDevice::Drain + * Purpose: + * The caller calls this to drain the audio device. + */ + STDMETHOD(Drain) (THIS) PURE; + + /************************************************************************ + * Method: + * IHXAudioDevice::CheckFormat + * Purpose: + * The caller calls this to check the input format with the + * audio device format. + */ + STDMETHOD(CheckFormat) (THIS_ + const HXAudioFormat* /*IN*/ pAudioFormat ) PURE; + + /************************************************************************ + * Method: + * IHXAudioDevice::GetCurrentAudioTime + * Purpose: + * The caller calls this to get current system audio time. + */ + STDMETHOD(GetCurrentAudioTime) (THIS_ + REF(ULONG32) /*OUT*/ ulCurrentTime) PURE; +}; + +/**************************************************************************** + * + * Interface: + * IHXAudioDeviceResponse + * + * Purpose: + * + * Object that exports audio device Response API + * This interface is currently to be used ONLY by the RMA engine + * internally. + * + * IID_IHXAudioDeviceResponse: + * + * {00000704-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXAudioDeviceResponse, 0x00000704, 0x901, 0x11d1, 0x8b, 0x6, + 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXAudioDeviceResponse + +DECLARE_INTERFACE_(IHXAudioDeviceResponse, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXAudioDeviceResponse methods + */ + + /************************************************************************ + * Method: + * IHXAudioDeviceResponse::OnTimeSync + * Purpose: + * Notification interface provided by users of the IHXAudioDevice + * interface. This method is called by the IHXAudioDevice when + * audio playback occurs. + */ + STDMETHOD(OnTimeSync) (THIS_ + ULONG32 /*IN*/ ulTimeEnd) PURE; +}; + +/**************************************************************************** + * + * Interface: + * + * IHXAudioHook + * + * Purpose: + * + * Clients must implement this interface to access pre- or post-mixed + * audio data. Use this interface to get post processed audio buffers and + * their associated audio format. + * + * IID_IHXAudioHook: + * + * {00000705-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXAudioHook, 0x00000705, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXAudioHook + +DECLARE_INTERFACE_(IHXAudioHook, IUnknown) +{ + /* + * IUnknown methods! + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXAudioHook methods + */ + /************************************************************************ + * Method: + * IHXAudioHook::OnInit + * Purpose: + * Audio Services calls OnInit() with the audio data format of the + * audio data that will be provided in the OnBuffer() method. + */ + STDMETHOD(OnInit) (THIS_ + HXAudioFormat* /*IN*/ pFormat) PURE; + + /************************************************************************ + * Method: + * IHXAudioHook::OnBuffer + * Purpose: + * Audio Services calls OnBuffer() with audio data packets. The + * renderer should not modify the data in the IHXBuffer part of + * pAudioInData. If the renderer wants to write a modified + * version of the data back to Audio Services, then it should + * create its own IHXBuffer, modify the data and then associate + * this buffer with the pAudioOutData->pData member. + */ + STDMETHOD(OnBuffer) (THIS_ + HXAudioData* /*IN*/ pAudioInData, + HXAudioData* /*OUT*/ pAudioOutData) PURE; +}; + +/**************************************************************************** + * + * Interface: + * + * IHXAudioDeviceHookManager + * + * Purpose: + * + * Allows setting audio hooks in the audio device itself. + * + * IID_IHXAudioDeviceHookManager: + * + * {00000715-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXAudioDeviceHookManager, 0x00000715, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXAudioDeviceHookManager + +DECLARE_INTERFACE_(IHXAudioDeviceHookManager, IUnknown) +{ + /* + * IUnknown methods! + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXAudioDeviceHookManager methods + */ + /************************************************************************ + * Method: + * IHXAudioDeviceHookManager::AddAudioDeviceHook + * Purpose: + * Last chance to modify data being written to the audio device. + */ + STDMETHOD(AddAudioDeviceHook) (THIS_ + IHXAudioHook* /*IN*/ pHook, + AudioDeviceHookType /*IN*/ type + ) PURE; + + /************************************************************************ + * Method: + * IHXAudioDeviceHookManager::RemoveAudioDeviceHook + * Purpose: + * Removes the audio device hook that was set with AddAudioDeviceHook. + */ + STDMETHOD(RemoveAudioDeviceHook) (THIS_ + IHXAudioHook* /*IN*/ pHook + ) PURE; + + /************************************************************************ + * Method: + * IHXAudioDeviceHookManager::ProcessHooks + * Purpose: + * Called by audio device implementations to process the hooks on a + * given audio buffer + */ + STDMETHOD(ProcessAudioDeviceHooks) (THIS_ + IHXBuffer*& /*IN/OUT*/ pBuffer, + HXBOOL& /*OUT*/ bChanged + ) PURE; +}; + +/**************************************************************************** + * + * Interface: + * + * IHXAudioStreamInfoResponse + * + * Purpose: + * + * Clients must implement this interface when interested in receiving + * notification of the total number of streams associated with this + * audio player. + * + * IID_IHXAudioStreamInfoResponse: + * + * {00000706-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXAudioStreamInfoResponse, 0x00000706, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXAudioStreamInfoResponse + +DECLARE_INTERFACE_(IHXAudioStreamInfoResponse, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXAudioStreamInfoResponse methods + */ + + /************************************************************************ + * Method: + * IHXAudioStreamInfoResponse::OnStream + * Purpose: + * The client implements this to get notification of streams + * associated with this player. Use + * AudioPlayer::SetStreamInfoResponse() to register your + * implementation with the AudioPlayer. Once player has been + * initialized, it will call OnStream() multiple times to pass all + * the streams. Since a stream can be added mid-presentation, + * IHXAudioStreamInfoResponse object should be written to handle + * OnStream() in the midst of the presentation as well. + */ + STDMETHOD(OnStream) (THIS_ + IHXAudioStream* /*IN*/ pAudioStream) PURE; +}; + +/**************************************************************************** + * + * Interface: + * + * IHXVolume + * + * Purpose: + * + * This interface provides access to Audio Services volume control. Use this + * interface to get, set, or receive notifications of volume changes. Audio + * Services implements IHXVolume for IHXAudioPlayer, IHXAudioStream and + * for the audio device. Clients can use the IHXVolume interface to get/set + * volume levels of each audio stream, to get/set volume levels for the + * audio player's mixed data, or to get/set the volume levels of the audio + * device. See AudioStream::GetStreamVolume() (TBD), AudioPlayer:: + * GetAudioVolume() and AudioPlayer::GetDeviceVolume(). + * + * IID_IHXVolume: + * + * {00000707-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXVolume, 0x00000707, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXVolume + +DECLARE_INTERFACE_(IHXVolume, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXVolume methods + */ + /************************************************************************ + * Method: + * IHXVolume::SetVolume + * Purpose: + * Call this to set the volume level. + */ + STDMETHOD(SetVolume) (THIS_ + const UINT16 /*IN*/ uVolume ) PURE; + + /************************************************************************ + * Method: + * IHXVolume::GetVolume + * Purpose: + * Call this to get the current volume level. + */ + STDMETHOD_(UINT16,GetVolume) (THIS) PURE; + + /************************************************************************ + * Method: + * IHXVolume::SetMute + * Purpose: + * Call this to mute the volume. + */ + STDMETHOD(SetMute) (THIS_ + const HXBOOL /*IN*/ bMute ) PURE; + + /************************************************************************ + * Method: + * IHXVolume::GetMute + * Purpose: + * Call this to determine if the volume is muted. + * + */ + STDMETHOD_(HXBOOL,GetMute) (THIS) PURE; + + /************************************************************************ + * Method: + * IHXVolume::AddAdviseSink + * Purpose: + * Call this to register an IHXVolumeAdviseSink. The advise sink + * methods: OnVolumeChange() and OnMuteChange() are called when + * ever IHXVolume::SetVolume() and IHXVolume::SetMute() are + * called. + */ + STDMETHOD(AddAdviseSink) (THIS_ + IHXVolumeAdviseSink* /*IN*/ pSink + ) PURE; + + /************************************************************************ + * Method: + * IHXVolume::RemoveAdviseSink + * Purpose: + * Call this to unregister an IHXVolumeAdviseSink. Use this when + * you are no longer interested in receiving volume or mute change + * notifications. + */ + STDMETHOD(RemoveAdviseSink) (THIS_ + IHXVolumeAdviseSink* /*IN*/ pSink + ) PURE; +}; + +/**************************************************************************** + * + * Interface: + * + * IHXVolumeAdviseSink + * + * Purpose: + * + * This interface provides access to notifications of volume changes. A + * client must implement this interface if they are interested in receiving + * notifications of volume level changes or mute state changes. A client must + * register their volume advise sink using IHXVolume::AddAdviseSink(). + * See the IHXVolume interface. + * + * IID_IHXVolumeAdviseSink: + * + * {00000708-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXVolumeAdviseSink, 0x00000708, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXVolumeAdviseSink + +DECLARE_INTERFACE_(IHXVolumeAdviseSink, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXVolumeAdviseSink methods + */ + + /************************************************************************ + * Method: + * IHXVolumeAdviseSink::OnVolumeChange + * Purpose: + * This interface is called whenever the associated IHXVolume + * SetVolume() is called. + */ + STDMETHOD(OnVolumeChange) (THIS_ + const UINT16 uVolume + ) PURE; + + /************************************************************************ + * Method: + * IHXVolumeAdviseSink::OnMuteChange + * Purpose: + * This interface is called whenever the associated IHXVolume + * SetMute() is called. + * + */ + STDMETHOD(OnMuteChange) (THIS_ + const HXBOOL bMute + ) PURE; +}; + +/**************************************************************************** + * + * Interface: + * + * IHXAudioLevelNormalization + * + * IID_IHXAudioLevelNormalization: + * + * {00000716-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXAudioLevelNormalization, 0x00000716, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXAudioLevelNormalization + +DECLARE_INTERFACE_(IHXAudioLevelNormalization, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXAudioLevelNormalization methods + */ + STDMETHOD(SetSoundLevelOffset) (THIS_ INT16 nOffset) PURE; + STDMETHOD_(INT16, GetSoundLevelOffset)(THIS) PURE; +}; + +/**************************************************************************** + * + * Interface: + * + * IHXDryNotification + * + * Purpose: + * + * Audio Renderer should implement this if it needs notification when the + * audio stream is running dry. + * + * IID_IHXDryNotification: + * + * {00000709-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXDryNotification, 0x00000709, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXDryNotification + +DECLARE_INTERFACE_(IHXDryNotification, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXDryNotification methods + */ + + /************************************************************************ + * Method: + * IHXDryNotification::OnDryNotification + * Purpose: + * This function is called when it is time to write to audio device + * and there is not enough data in the audio stream. The renderer can + * then decide to add more data to the audio stream. This should be + * done synchronously within the call to this function. + * It is OK to not write any data. Silence will be played instead. + */ + STDMETHOD(OnDryNotification) (THIS_ + UINT32 /*IN*/ ulCurrentStreamTime, + UINT32 /*IN*/ ulMinimumDurationRequired + ) PURE; +}; + + +/**************************************************************************** + * + * Interface: + * + * IHXAudioDeviceManager + * + * Purpose: + * + * Allows the default audio device to be replaced. + * + * IID_IHXAudioDeviceManager: + * + * {0000070A-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXAudioDeviceManager, 0x0000070A, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXAudioDeviceManager + +DECLARE_INTERFACE_(IHXAudioDeviceManager, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXAudioDeviceManager methods + */ + + /********************************************************************** + * Method: + * IHXAudioDeviceManager::Replace + * Purpose: + * This is used to replace the default implementation of the audio + * device by the given audio device interface. + */ + STDMETHOD(Replace) (THIS_ + IHXAudioDevice* /*IN*/ pAudioDevice) PURE; + + /********************************************************************** + * Method: + * IHXAudioDeviceManager::Remove + * Purpose: + * This is used to remove the audio device given to the manager in + * the earlier call to Replace. + */ + STDMETHOD(Remove) (THIS_ + IHXAudioDevice* /*IN*/ pAudioDevice) PURE; + + /************************************************************************ + * Method: + * IHXAudioDeviceManager::AddFinalHook + * Purpose: + * One last chance to modify data being written to the audio device. + * This hook allows the user to change the audio format that + * is to be written to the audio device. This can be done in call + * to OnInit() in IHXAudioHook. + */ + STDMETHOD(SetFinalHook) (THIS_ + IHXAudioHook* /*IN*/ pHook + ) PURE; + + /************************************************************************ + * Method: + * IHXAudioDeviceManager::RemoveFinalHook + * Purpose: + * Remove final hook + */ + STDMETHOD(RemoveFinalHook) (THIS_ + IHXAudioHook* /*IN*/ pHook + ) PURE; + + /************************************************************************ + * Method: + * IHXAudioDeviceManager::GetAudioFormat + * Purpose: + * Returns the audio format in which the audio device is opened. + * This function will fill in the pre-allocated HXAudioFormat + * structure passed in. + */ + STDMETHOD(GetAudioFormat) (THIS_ + HXAudioFormat* /*IN/OUT*/pAudioFormat) PURE; +}; + +/**************************************************************************** + * + * Interface: + * + * IHXAudioCrossFade + * + * Purpose: + * + * This interface can be used to cross-fade two audio streams. It is exposed + * by IHXAudioPlayer + * + * IID_IHXAudioCrossFade: + * + * {0000070B-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXAudioCrossFade, 0x0000070B, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXAudioCrossFade + +DECLARE_INTERFACE_(IHXAudioCrossFade, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXAudioCrossFade methods + */ + + /************************************************************************ + * Method: + * IHXAudioCrossFade::CrossFade + * Purpose: + * Cross-fade two audio streams. + * pStreamFrom - Stream to be cross faded from + * pStreamTo - Stream to be cross faded to + * ulFromCrossFadeStartTime- "From" Stream time when cross fade is + * to be started + * ulToCrossFadeStartTime - "To" Stream time when cross fade is to + * be started + * ulCrossFadeDuration - Duration over which cross-fade needs + * to be done + * + */ + STDMETHOD(CrossFade) (THIS_ + IHXAudioStream* pStreamFrom, + IHXAudioStream* pStreamTo, + UINT32 ulFromCrossFadeStartTime, + UINT32 ulToCrossFadeStartTime, + UINT32 ulCrossFadeDuration) PURE; +}; + +/**************************************************************************** + * + * Interface: + * + * IHXAudioStream2 + * + * Purpose: + * + * This interface contains some last-minute added audio stream functions + * + * IID_IHXAudioStream2: + * + * {0000070C-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXAudioStream2, 0x0000070C, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXAudioStream2 + +DECLARE_INTERFACE_(IHXAudioStream2, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXAudioStream2 methods + */ + /************************************************************************ + * Method: + * IHXAudioStream2::RemoveDryNotification + * Purpose: + * Use this to remove itself from the notification response object + * during the stream switching. + */ + STDMETHOD(RemoveDryNotification) (THIS_ + IHXDryNotification* /*IN*/ pNotification + ) PURE; + + /************************************************************************ + * Method: + * IHXAudioStream2::GetAudioFormat + * Purpose: + * Returns the input audio format of the data written by the + * renderer. This function will fill in the pre-allocated + * HXAudioFormat structure passed in. + */ + STDMETHOD(GetAudioFormat) (THIS_ + HXAudioFormat* /*IN/OUT*/pAudioFormat) PURE; +}; + + +/**************************************************************************** + * + * Interface: + * + * IHXAudioPushdown + * + * Purpose: + * + * This interface can be used to setup the audio pushdown time. + * + * IID_IHXAudioPushdown: + * + * {0000070D-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXAudioPushdown, 0x0000070D, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXAudioPushdown + +DECLARE_INTERFACE_(IHXAudioPushdown, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXAudioPushdown methods + */ + /************************************************************************ + * Method: + * IHXAudioPushdown::SetAudioPushdown + * Purpose: + * Use this to set the minimum audio pushdown value in ms. + * This is the amount of audio data that is being written + * to the audio device before starting playback. + */ + STDMETHOD(SetAudioPushdown) (THIS_ + UINT32 /*IN*/ ulAudioPushdown + ) PURE; +}; + + +/**************************************************************************** + * + * Interface: + * + * IHXAudioHookManager + * + * Purpose: + * + * This interface can be used to add a hook at the audio device layer. + * + * IID_IHXAudioHookManager: + * + * {0000070E-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXAudioHookManager, 0x0000070E, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXAudioHookManager + +DECLARE_INTERFACE_(IHXAudioHookManager, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXAudioHookManager methods + */ + /************************************************************************ + * Method: + * IHXAudioHookManager::AddHook + * Purpose: + * Use this to add a hook + */ + STDMETHOD(AddHook) (THIS_ + IHXAudioHook* /*IN*/ pHook + ) PURE; + + /************************************************************************ + * Method: + * IHXAudioHookManager::RemoveHook + * Purpose: + * Use this to remove a hook + */ + STDMETHOD(RemoveHook) (THIS_ + IHXAudioHook* /*IN*/ pHook + ) PURE; +}; + +// $Private: +/**************************************************************************** + * + * Interface: + * + * IHXMultiPlayPauseSupport + * + * Purpose: + * + * This interface can be used to control whether audio services handles multi-player pause / rewind support + * + * IID_IHXMultiPlayPauseSupport: + * + * {0000070F-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXMultiPlayPauseSupport, + 0x0000070F, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXMultiPlayPauseSupport + +DECLARE_INTERFACE_(IHXMultiPlayPauseSupport, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXMultiPlayPauseSupport methods + */ + STDMETHOD_(HXBOOL,GetDisableMultiPlayPauseSupport) (THIS) PURE; +}; + +/**************************************************************************** + * + * Interface: + * + * IHXAudioDeviceManager2 + * + * Purpose: + * + * Audio Device Manager extension + * + * IID_IHXAudioDeviceManager2: + * + * {00000710-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXAudioDeviceManager2, 0x00000710, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXAudioDeviceManager2 + +DECLARE_INTERFACE_(IHXAudioDeviceManager2, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXAudioDeviceManager2 methods + */ + + /********************************************************************** + * Method: + * IHXAudioDeviceManager2::IsReplacedDevice + * Purpose: + * This is used to determine if the audio device has been replaced. + */ + STDMETHOD_(HXBOOL, IsReplacedDevice) (THIS) PURE; + +}; + + +/**************************************************************************** + * + * Interface: + * + * IHXAudioResampler + * + * Purpose: + * + * Audio Resampler + * + * IID_IHXAudioResampler: + * + * {00000711-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXAudioResampler, 0x00000711, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXAudioResampler + +DECLARE_INTERFACE_(IHXAudioResampler, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXAudioResampler methods + */ + + /********************************************************************** + * Method: + * IHXAudioResampler::Resample + * Purpose: + * Will produce 1 output frame for every (upFactor/downFactor) inputs + * frames, straddling if not an integer. Works down to 1 sample/call. + * + * Returns actual number of output frames. + ***********************************************************************/ + + STDMETHOD_(UINT32, Resample) ( THIS_ + UINT16* pInput, + UINT32 ulInputBytes, + UINT16* pOutput) PURE; + + /********************************************************************** + * Method: + * IHXAudioResampler::Requires + * Purpose: + * Returns number of input frames required to produce this number + * of output frames, given the current state of the filter. + */ + + STDMETHOD_(UINT32, Requires) ( THIS_ + UINT32 ulOutputFrames) PURE; +}; + +/**************************************************************************** + * + * Interface: + * + * IHXAudioResamplerManager + * + * Purpose: + * + * Audio Resampler Manager + * + * IID_IHXAudioResamplerManager: + * + * {00000712-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXAudioResamplerManager, 0x00000712, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXAudioResamplerManager + +DECLARE_INTERFACE_(IHXAudioResamplerManager, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXAudioResamplerManager methods + * + */ + STDMETHOD(CreateResampler) (THIS_ + HXAudioFormat inAudioFormat, + REF(HXAudioFormat) outAudioFormat, + REF(IHXAudioResampler*) pResampler) PURE; +}; + +/**************************************************************************** + * + * Interface: + * + * IHXAudioPushdown2 + * + * Purpose: + * + * Audio PushDown access methods + * + * IID_IHXAudioPushdown2: + * + * {00000713-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXAudioPushdown2, 0x00000713, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXAudioPushdown2 + +DECLARE_INTERFACE_(IHXAudioPushdown2, IHXAudioPushdown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /************************************************************************ + * Method: + * IHXAudioPushdown::SetAudioPushdown + * Purpose: + * Use this to set the minimum audio pushdown value in ms. + * This is the amount of audio data that is being written + * to the audio device before starting playback. + */ + STDMETHOD(SetAudioPushdown) (THIS_ + UINT32 /*IN */ ulAudioPushdown) PURE; + + /************************************************************************ + * Method: + * IHXAudioPushdown2::GetAudioPushdown + * Purpose: + * Use this to get the minimum audio pushdown value in ms. + * This is the amount of audio data that is being written + * to the audio device before starting playback. + */ + STDMETHOD(GetAudioPushdown) (THIS_ + REF(UINT32) /*OUT*/ ulAudioPushdown) PURE; + + /************************************************************************ + * Method: + * IHXAudioPushdown2::GetCurrentAudioDevicePushdown + * Purpose: + * Use this to get the audio pushed down to the audio device and haven't + * been played yet + */ + STDMETHOD(GetCurrentAudioDevicePushdown) (THIS_ + REF(UINT32) /*OUT*/ ulAudioPusheddown) PURE; +}; + +/**************************************************************************** + * + * Interface: + * + * IHXAudioMultiChannel + * + * Purpose: + * + * Multi-channel audio support + * + * IID_IHXAudioMultiChannel: + * + * {00000714-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXAudioMultiChannel, 0x00000714, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXAudioMultiChannel + +DECLARE_INTERFACE_(IHXAudioMultiChannel, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXAudioMultiChannel methods + */ + STDMETHOD_(HXBOOL,GetMultiChannelSupport) (THIS) PURE; +}; +// $EndPrivate. + + +#if defined(HELIX_FEATURE_TIMELINE_WATCHER) + +// Timeline watcher. +// {211A3CAE-F1DA-4678-84D5-0F12E7B1D8C6} +DEFINE_GUID(IID_IHXTimelineWatcher, + 0x211a3cae, 0xf1da, 0x4678, 0x84, 0xd5, 0xf, 0x12, 0xe7, 0xb1, 0xd8, 0xc6); +#undef INTERFACE +#define INTERFACE IHXTimelineWatcher +DECLARE_INTERFACE_(IHXTimelineWatcher, IUnknown) +{ + STDMETHOD(QueryInterface) (THIS_ REFIID riid, void** ppvObj) PURE; + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + STDMETHOD(OnPause) (THIS) PURE; + STDMETHOD(OnResume) (THIS) PURE; + STDMETHOD(OnClose) (THIS) PURE; + STDMETHOD(OnTimeSync) (THIS_ UINT32 currentTime ) PURE; +}; + +// TimelineManager +// {9ED91BC3-9E92-46bb-A094-6C8B9416CFB6} +DEFINE_GUID(IID_IHXTimelineManager, + 0x9ed91bc3, 0x9e92, 0x46bb, 0xa0, 0x94, 0x6c, 0x8b, 0x94, 0x16, 0xcf, 0xb6); +#undef INTERFACE +#define INTERFACE IHXTimelineManager +DECLARE_INTERFACE_(IHXTimelineManager, IUnknown) +{ + STDMETHOD(QueryInterface) (THIS_ REFIID riid, void** ppvObj) PURE; + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + STDMETHOD(AddTimelineWatcher) (THIS_ IHXTimelineWatcher* ) PURE; + STDMETHOD(RemoveTimelineWatcher) (THIS_ IHXTimelineWatcher* ) PURE; +}; +#endif /* #if defined(HELIX_FEATURE_TIMELINE_WATCHER) */ + + +#endif /* _HXAUSVC_H_ */ diff --git a/amarok/src/engine/helix/helix-sp/helix-include/common/include/hxauth.h b/amarok/src/engine/helix/helix-sp/helix-include/common/include/hxauth.h new file mode 100644 index 00000000..903c835f --- /dev/null +++ b/amarok/src/engine/helix/helix-sp/helix-include/common/include/hxauth.h @@ -0,0 +1,385 @@ + +/* + * + * This software is released under the provisions of the GPL version 2. + * see file "COPYING". If that file is not available, the full statement + * of the license can be found at + * + * http://www.fsf.org/licensing/licenses/gpl.txt + * + * Portions Copyright (c) 1995-2004 RealNetworks, Inc. All Rights Reserved. + * + */ + +#ifndef _HXAUTH_H_ +#define _HXAUTH_H_ + +/* + * Forward declarations of some interfaces defined or used here-in. + */ +typedef _INTERFACE IHXAuthenticator IHXAuthenticator; +typedef _INTERFACE IHXAuthenticatorResponse IHXAuthenticatorResponse; +typedef _INTERFACE IHXAuthenticatorRequest IHXAuthenticatorRequest; +typedef _INTERFACE IHXPassword IHXPassword; +typedef _INTERFACE IHXAuthenticationManagerResponse IHXAuthenticationManagerResponse; +typedef _INTERFACE IHXValues IHXValues; +typedef _INTERFACE IHXBuffer IHXBuffer; + +/**************************************************************************** + * + * Interface: + * + * IHXAuthenticator + * + * Purpose: + * + * Provide a means of authenticating users. + * + * IID_IHXAuthenticator: + * + * {00001800-0901-11d1-8B06-00A024406D59} + * + */ + +DEFINE_GUID(IID_IHXAuthenticator, 0x00001800, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#define CLSID_IHXAuthenticator IID_IHXAuthenticator + +#undef INTERFACE +#define INTERFACE IHXAuthenticator + +DECLARE_INTERFACE_(IHXAuthenticator, IUnknown) +{ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * InitAuthenticator is called by the creator of the Authenticator + * object in order to pass it an IHXAuthenticatorRequest object, + * usually implemented by the creator itself. + */ + STDMETHOD(InitAuthenticator) (THIS_ + IHXAuthenticatorRequest* pRequest) PURE; + /* + * Authenticate is called by a file object (and others??) + * when it wants to ask the creator, presumably an FS Manager, + * for authorization to open it's file. + * + * Authenticate will call IHXAuthenticateResponse::AuthenticateDone + * when done with HXR_OK or an error. + * + * File objects will presumably perform the Authenticate response as + * part of their Init() call, and not call InitDone until they receive + * a response one way or the other. + */ + STDMETHOD(Authenticate) (THIS_ + IHXValues* pValues, + IHXAuthenticatorResponse* pResponse) PURE; + + /* GenerateAuthRequest is called by the creator of this object + * when they want to send an authentication request to someone. + */ + STDMETHOD(GenerateAuthRequest) (THIS_ + UINT32 authType, + REF(IHXValues*) pValues) PURE; + /* + * AuthValuesReady is called by IHXAuthenticatorRequest when it + * is ready to respond to a GetAuthValues request. + */ + STDMETHOD(AuthValuesReady) (THIS_ + HX_RESULT result, + IHXValues* pValues) PURE; +}; + + +/**************************************************************************** + * + * Interface: + * + * IHXAuthenticatorResponse + * + * Purpose: + * + * Response object for the Authenticator class. + * + * IID_IHXAuthenticatorResponse: + * + * {00001801-0901-11d1-8B06-00A024406D59} + * + */ + +DEFINE_GUID(IID_IHXAuthenticatorResponse, 0x00001801, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXAuthenticatorResponse + +DECLARE_INTERFACE_(IHXAuthenticatorResponse, IUnknown) +{ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* AuthenticateDone is called by an IHXAuthenticator when it has + * finished it's authorization steps. If the result is HXR_OK, + * then the values contain authorization information as generated by + * IHXPassword. + */ + STDMETHOD(AuthenticateDone) (THIS_ HX_RESULT result, + IHXValues* pAuthResponseValues) PURE; +}; + + +/**************************************************************************** + * + * Interface: + * + * IHXAuthenticatorRequest + * + * Purpose: + * + * Request object for the Authenticator class. + * + * IID_IHXAuthenticatorRequest: + * + * {00001802-0901-11d1-8B06-00A024406D59} + * + */ + +DEFINE_GUID(IID_IHXAuthenticatorRequest, 0x00001802, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXAuthenticatorRequest + +DECLARE_INTERFACE_(IHXAuthenticatorRequest, IUnknown) +{ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* GetAuthValues is called by the Authenticator object when it + * needs to know the authorization info for this transaction. + * + * This object should call AuthValuesReady when ready. + */ + STDMETHOD(GetAuthValues) (THIS_ IHXValues* pOrigValues) PURE; +}; + + +/**************************************************************************** + * + * Interface: + * + * IHXPassword + * + * Purpose: + * + * Provides a general password facility for storing of passwords in + * an encrypted form and a facility for verifying passwords securely + * over the network. + * + * IID_IHXPassword: + * + * {00001700-0901-11d1-8B06-00A024406D59} + * + */ + +#define HX_AUTH_BASIC 1 +#define HX_AUTH_DIGEST 2 +#define PN_AUTH_HX5 3 +#define HX_AUTH_NTLM 4 + +DEFINE_GUID(IID_IHXPassword, 0x00001700, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXPassword + +DECLARE_INTERFACE_(IHXPassword, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXPassword methods + */ + + STDMETHOD(Crypt) (THIS_ IHXValues* pAuthentication) PURE; + STDMETHOD(Verify) (THIS_ IHXValues* pAuth1, IHXValues* pAuth2) PURE; + + STDMETHOD(AsString) (THIS_ IHXValues* pAuth, REF(IHXBuffer*) pBuffer) PURE; + STDMETHOD(AsValues) (THIS_ const char* str, IHXValues* pValues) PURE; + + /* + * CreateBuffer is provided for the convenince of external users, + * who would otherwise have to get a context and common class factory + * just to create IHXBuffers. This method can be used instead, but + * is not advisable if other means are available. + */ + STDMETHOD(CreateBuffer) (THIS_ REF(IHXBuffer*) pBuffer) PURE; + + /* + * Ditto for CreateValues + */ + STDMETHOD(CreateValues) (THIS_ REF(IHXValues*) pValues) PURE; +}; + + +/**************************************************************************** + * + * Interface: + * + * IHXAuthenticationManager + * + * Purpose: + * + * Provide a means of authenticating users. + * + * IID_IHXAuthenticator: + * + * {00001a00-0901-11d1-8B06-00A024406D59} + * + */ + +DEFINE_GUID(IID_IHXAuthenticationManager, 0x00001a00, 0x901, 0x11d1, + 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXAuthenticationManager + +DECLARE_INTERFACE_(IHXAuthenticationManager, IUnknown) +{ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* HandleAuthenticationRequest is called when the core wants us to get + * a username and password. + */ + STDMETHOD(HandleAuthenticationRequest) ( + THIS_ IHXAuthenticationManagerResponse* pResponse) PURE; +}; + + +/**************************************************************************** + * + * Interface: + * + * IHXAuthenticationManager2 + * + * Purpose: + * + * Provide a means of authenticating users. + * + * Includes sending an IHXValues list to the + * authentication manager, for support of proxy + * authentication, for example, which may include + * a "pseudonym" header or something. + * + * IID_IHXAuthenticator2: + * + * {34e171d2-a8f0-4832-bc7d-06dfe3ae58fd} + * + */ + +DEFINE_GUID(IID_IHXAuthenticationManager2, + 0x34e171d2, 0xa8f0, 0x4832, 0xbc, 0x7d, 0x06, + 0xdf, 0xe3, 0xae, 0x58, 0xfd); + +#undef INTERFACE +#define INTERFACE IHXAuthenticationManager2 + +DECLARE_INTERFACE_(IHXAuthenticationManager2, IUnknown) +{ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* HandleAuthenticationRequest2 is called when the core wants us to get + * a username and password. + */ + STDMETHOD(HandleAuthenticationRequest2) ( + THIS_ + IHXAuthenticationManagerResponse* pResponse, + IHXValues* pHeader) PURE; +}; + + +/**************************************************************************** + * + * Interface: + * + * IHXAuthenticationManagerResponse + * + * Purpose: + * + * Response object for IHXAuthenticationManager. + * + * IID_IHXAuthenticator: + * + * {00001a01-0901-11d1-8B06-00A024406D59} + * + */ + +DEFINE_GUID(IID_IHXAuthenticationManagerResponse, 0x00001a01, 0x901, 0x11d1, + 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59); + + +#undef INTERFACE +#define INTERFACE IHXAuthenticationManagerResponse + +DECLARE_INTERFACE_(IHXAuthenticationManagerResponse, IUnknown) +{ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* HandleAuthenticationRequest is called when the core wants us to get + * a username and password. + */ + STDMETHOD(AuthenticationRequestDone) (THIS_ + HX_RESULT result, + const char* pUserName, + const char* pPassword) PURE; +}; + +#ifdef _MACINTOSH +#pragma export on +#endif + +STDAPI CreatePassword(IUnknown** /* OUT */ ppIUnknown); + +#ifdef _MACINTOSH +#pragma export off +#endif + +#endif /* _HXAUTH_H_ */ diff --git a/amarok/src/engine/helix/helix-sp/helix-include/common/include/hxccf.h b/amarok/src/engine/helix/helix-sp/helix-include/common/include/hxccf.h new file mode 100644 index 00000000..b99d4bdb --- /dev/null +++ b/amarok/src/engine/helix/helix-sp/helix-include/common/include/hxccf.h @@ -0,0 +1,106 @@ + +/* + * + * This software is released under the provisions of the GPL version 2. + * see file "COPYING". If that file is not available, the full statement + * of the license can be found at + * + * http://www.fsf.org/licensing/licenses/gpl.txt + * + * Portions Copyright (c) 1995-2004 RealNetworks, Inc. All Rights Reserved. + * + */ + +#ifndef _HXCCF_H_ +#define _HXCCF_H_ + +/* + * Forward declarations of some interfaces defined here-in. + */ + +typedef _INTERFACE IHXCommonClassFactory IHXCommonClassFactory; + + +/**************************************************************************** + * + * Interface: + * + * IHXCommonClassFactory + * + * Purpose: + * + * RMA interface that manages the creation of common RMA classes. + * + * IID_IHXCommonClassFactory: + * + * {00000000-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXCommonClassFactory, 0x00000000, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXCommonClassFactory + +DECLARE_INTERFACE_(IHXCommonClassFactory, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXCommonClassFactory methods + */ + + /************************************************************************ + * Method: + * IHXCommonClassFactory::CreateInstance + * Purpose: + * Creates instances of common objects supported by the system, + * like IHXBuffer, IHXPacket, IHXValues, etc. + * + * This method is similar to Window's CoCreateInstance() in its + * purpose, except that it only creates objects of a well known + * types. + * + * NOTE: Aggregation is never used. Therefore and outer unknown is + * not passed to this function, and you do not need to code for this + * situation. + */ + STDMETHOD(CreateInstance) (THIS_ + REFCLSID /*IN*/ rclsid, + void** /*OUT*/ ppUnknown) PURE; + + /************************************************************************ + * Method: + * IHXController::CreateInstanceAggregatable + * Purpose: + * Creates instances of common objects that can be aggregated + * supported by the system, like IHXSiteWindowed + * + * This method is similar to Window's CoCreateInstance() in its + * purpose, except that it only creates objects of a well known + * types. + * + * NOTE 1: Unlike CreateInstance, this method will create internal + * objects that support Aggregation. + * + * NOTE 2: The output interface is always the non-delegating + * IUnknown. + */ + STDMETHOD(CreateInstanceAggregatable) + (THIS_ + REFCLSID /*IN*/ rclsid, + REF(IUnknown*) /*OUT*/ ppUnknown, + IUnknown* /*IN*/ pUnkOuter) PURE; +}; + + +#endif /*_HXCCF_H_*/ diff --git a/amarok/src/engine/helix/helix-sp/helix-include/common/include/hxcom.h b/amarok/src/engine/helix/helix-sp/helix-include/common/include/hxcom.h new file mode 100644 index 00000000..9a5f6891 --- /dev/null +++ b/amarok/src/engine/helix/helix-sp/helix-include/common/include/hxcom.h @@ -0,0 +1,862 @@ + +/* + * + * This software is released under the provisions of the GPL version 2. + * see file "COPYING". If that file is not available, the full statement + * of the license can be found at + * + * http://www.fsf.org/licensing/licenses/gpl.txt + * + * Portions Copyright (c) 1995-2004 RealNetworks, Inc. All Rights Reserved. + * + */ + +#ifndef _HXCOM_H_ +#define _HXCOM_H_ + +#include "hxtypes.h" /* Needed for most type definitions */ + +// have to use the double expansion to get the prescan level + +#define STATCONCAT1(w,x,y,z) STATCONCAT2(w,x,y,z) +#define STATCONCAT2(w,x,y,z) w##x##y##z + +#ifdef _STATICALLY_LINKED +#ifndef _PLUGINNAME +#define ENTRYPOINT(func) STATCONCAT1(entrypoint_error_symbol_should_not_be_needed,_PLUGINNAME,_,func) +#else /* _PLUGINNAME */ +#define ENTRYPOINT(func) STATCONCAT1(entrypoint_for_,_PLUGINNAME,_,func) +#endif +#else /* _STATICALLY_LINKED */ +#define ENTRYPOINT(func) func +#endif + +/* + * We include objbase.h when building for windows so that hxcom.h can + * easily be used in any windows code. + */ +#ifdef _WIN32 +#include "hlxclib/sys/socket.h" +#include <objbase.h> +#endif /* _WIN32 */ + + +#include "hxresult.h" + +/* + * REF: + * Use this for reference parameters, so that C users can + * use the interface as well. + */ +#if defined(__cplusplus) +#define REF(type) type& +#else +#define REF(type) const type * const +#endif + +/* + * CONSTMETHOD: + * Use this for constant methods in an interface + * Compiles away under C + */ +#if !defined( CONSTMETHOD ) + +#if defined(__cplusplus) +#define CONSTMETHOD const +#else +#define CONSTMETHOD +#endif + +#endif +/* + * CALL: + * + * Used by C users to easily call a function through an interface + * + * EXAMPLE: + * + * pIFooObject->CALL(IFoo,DoSomething)(bar); + * + */ +#if !defined(__cplusplus) || defined(CINTERFACE) +#define CALL(iface, func) iface##Vtbl->func +#endif + + +#define _INTERFACE struct + +/* + * If using windows.h or the windows implementation of COM + * these defines are not needed. + */ +#if !defined( _OBJBASE_H_ ) + +#ifdef _WIN16 +typedef unsigned int MMRESULT; +#define FAR _far +#else +#define FAR +#endif /* WIN16 */ +#define PASCAL _pascal +#define CDECL _cdecl + +/* + * EXTERN_C + */ +#ifndef EXTERN_C +#ifdef __cplusplus +#define EXTERN_C extern "C" +#else +#define EXTERN_C extern +#endif +#endif + +#ifdef OLDERRORCODES +#ifndef MAKE_HRESULT +#define MAKE_HRESULT(sev,fac,code) ((HRESULT) (((unsigned long)(sev)<<31) | ((unsigned long)(fac)<<16) | ((unsigned long)(code)))) +#endif /*MAKE_HRESULT*/ +#endif /* OLDERRORCODES */ + +/* + * STDMETHODCALLTYPE + */ +#ifndef STDMETHODCALLTYPE +#if defined(_WIN32) || defined(_MPPC_) +#ifdef _MPPC_ +#define STDMETHODCALLTYPE __cdecl +#else +#define STDMETHODCALLTYPE __stdcall +#endif +#elif defined(_WIN16) +#define STDMETHODCALLTYPE __export FAR CDECL +#else +#define STDMETHODCALLTYPE +#endif +#endif + +/* + * STDMETHODVCALLTYPE + */ +#ifndef STDMETHODVCALLTYPE +#if defined(_WINDOWS) || defined(_MPPC_) +#define STDMETHODVCALLTYPE __cdecl +#else +#define STDMETHODVCALLTYPE +#endif +#endif + +/* + * STDAPICALLTYPE + */ +#ifndef STDAPICALLTYPE +#if defined(_WIN32) || defined(_MPPC_) +#define STDAPICALLTYPE __stdcall +#elif defined(_WIN16) +#define STDAPICALLTYPE __export FAR PASCAL +#else +#define STDAPICALLTYPE +#endif +#endif + +/* + * STDAPIVCALLTYPE + */ +#ifndef STDAPIVCALLTYPE +#if defined(_WINDOWS) || defined(_MPPC_) +#define STDAPIVCALLTYPE __cdecl +#else +#define STDAPIVCALLTYPE +#endif +#endif + +/* + * Standard API defines: + * + * NOTE: the 'V' versions allow Variable Argument lists. + * + * STDAPI + * STDAPI_(type) + * STDAPIV + * STDAPIV_(type) + */ +#ifndef STDAPI +#define STDAPI EXTERN_C HX_RESULT STDAPICALLTYPE +#endif +#ifndef STDAPI_ +#define STDAPI_(type) EXTERN_C type STDAPICALLTYPE +#endif +#ifndef STDAPIV +#define STDAPIV EXTERN_C HX_RESULT STDAPIVCALLTYPE +#endif +#ifndef STDAPIV_ +#define STDAPIV_(type) EXTERN_C type STDAPIVCALLTYPE +#endif + + +/* + * Standard Interface Method defines: + * + * NOTE: the 'V' versions allow Variable Argument lists. + * + * STDMETHODIMP + * STDMETHODIMP_(type) + * STDMETHODIMPV + * STDMETHODIMPV_(type) + */ +#ifndef STDMETHODIMP +#define STDMETHODIMP HX_RESULT STDMETHODCALLTYPE +#endif +#ifndef STDMETHODIMP_ +#define STDMETHODIMP_(type) type STDMETHODCALLTYPE +#endif +#ifndef STDMETHODIMPV +#define STDMETHODIMPV HX_RESULT STDMETHODVCALLTYPE +#endif +#ifndef STDMETHODIMPV_ +#define STDMETHODIMPV_(type) type STDMETHODVCALLTYPE +#endif + + +#if defined(__cplusplus) && !defined(CINTERFACE) +#define _INTERFACE struct +#define STDMETHOD(method) virtual HX_RESULT STDMETHODCALLTYPE method +#define STDMETHOD_(type,method) virtual type STDMETHODCALLTYPE method +#define PURE = 0 +#define THIS_ +#define THIS void + +#if defined(_WINDOWS) && defined(EXPORT_CLASSES) +#define DECLARE_INTERFACE(iface) _INTERFACE HXEXPORT_CLASS iface +#define DECLARE_INTERFACE_(iface, baseiface) _INTERFACE HXEXPORT_CLASS iface : public baseiface +#else +#define DECLARE_INTERFACE(iface) _INTERFACE iface +#define DECLARE_INTERFACE_(iface, baseiface) _INTERFACE iface : public baseiface +#endif // defined(_WINDOWS) && defined(EXPORT_CLASSES) + +#if !defined(BEGIN_INTERFACE) +#if defined(_MPPC_) && \ + ( (defined(_MSC_VER) || defined(__SC__) || defined(__MWERKS__)) && \ + !defined(NO_NULL_VTABLE_ENTRY) ) + #define BEGIN_INTERFACE virtual void a() {} + #define END_INTERFACE +#else + #define BEGIN_INTERFACE + #define END_INTERFACE +#endif +#endif + + +#else + +#define _INTERFACE struct + +#define STDMETHOD(method) HX_RESULT (STDMETHODCALLTYPE * method) +#define STDMETHOD_(type,method) type (STDMETHODCALLTYPE * method) + +#if !defined(BEGIN_INTERFACE) +#if defined(_MPPC_) + #define BEGIN_INTERFACE void *b; + #define END_INTERFACE +#else + #define BEGIN_INTERFACE + #define END_INTERFACE +#endif +#endif + + +#define PURE +#define THIS_ INTERFACE FAR* This, +#define THIS INTERFACE FAR* This +#ifdef CONST_VTABLE +#undef CONST_VTBL +#define CONST_VTBL const +#define DECLARE_INTERFACE(iface) typedef _INTERFACE iface { \ + const struct iface##Vtbl FAR* lpVtbl; \ + } iface; \ + typedef const struct iface##Vtbl iface##Vtbl; \ + const struct iface##Vtbl +#else +#undef CONST_VTBL +#define CONST_VTBL +#define DECLARE_INTERFACE(iface) typedef _INTERFACE iface { \ + struct iface##Vtbl FAR* lpVtbl; \ + } iface; \ + typedef struct iface##Vtbl iface##Vtbl; \ + struct iface##Vtbl +#endif +#define DECLARE_INTERFACE_(iface, baseiface) DECLARE_INTERFACE(iface) + +#endif + +/* + * COMMON TYPES + */ + +#if !defined(HELIX_FEATURE_FULLGUID) + +#define DEFINE_GUID(name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) + +#define DEFINE_GUID_ENUM(name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) \ + name = l + w1 * 3 + w2 * 5 + b1 * 7 + b2 * 11 + b3 * 13 + b4 * 17 + \ + b5 * 19 + b6 * 23 + b7 * 29 + b8 * 31, + +#ifndef GUID_DEFINED +#define GUID_DEFINED +typedef enum _GUIDNoConflict +{ + GUID_NULL, + DEFINE_GUID_ENUM(IID_IUnknown, 0x00000000, 0x0000, 0x0000, 0xC0, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46) + DEFINE_GUID_ENUM(IID_IMalloc, 0x00000002, 0x0000, 0x0000, 0xC0, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46) +#include "hxiids.h" +#include "hxpiids.h" + NUM_GUIDS +} GUID; +#endif + +#else /* #if !defined(HELIX_FEATURE_FULLGUID) */ +#ifndef GUID_DEFINED +#define GUID_DEFINED +typedef struct _GUID + { + ULONG32 Data1; + UINT16 Data2; + UINT16 Data3; + UCHAR Data4[ 8 ]; + } GUID; +#endif +#endif /* #if !defined(HELIX_FEATURE_FULLGUID) #else */ + +#if !defined( __IID_DEFINED__ ) +#define __IID_DEFINED__ +typedef GUID IID; +#define IID_NULL GUID_NULL +typedef GUID CLSID; +#define CLSID_NULL GUID_NULL + +#if defined(__cplusplus) +#ifndef _REFGUID_DEFINED +#define _REFGUID_DEFINED +#define REFGUID const GUID & +#endif + +#ifndef _REFIID_DEFINED +#define _REFIID_DEFINED +#define REFIID const IID & +#endif + +#ifndef _REFCLSID_DEFINED +#define _REFCLSID_DEFINED +#define REFCLSID const CLSID & +#endif + +#else +#ifndef _REFGUID_DEFINED +#define _REFGUID_DEFINED +#define REFGUID const GUID * const +#endif +#ifndef _REFIID_DEFINED +#define _REFIID_DEFINED +#define REFIID const IID * const +#endif +#ifndef _REFCLSID_DEFINED +#define _REFCLSID_DEFINED +#define REFCLSID const CLSID * const +#endif +#endif +#endif + + +#if defined(HELIX_FEATURE_FULLGUID) + +#if !defined (INITGUID) || (defined (_STATICALLY_LINKED) && !defined(NCIHACK)) + +#define DEFINE_GUID(name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) \ + EXTERN_C const GUID FAR name + +#define DEFINE_GUID_ENUM(name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) \ + EXTERN_C const GUID FAR name; + +#else /* #if !defined (INITGUID) || (defined (_STATICALLY_LINKED) && !defined(NCIHACK)) */ + +#define DEFINE_GUID(name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) \ + EXTERN_C const GUID name \ + = { l, w1, w2, { b1, b2, b3, b4, b5, b6, b7, b8 } } + +#define DEFINE_GUID_ENUM(name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) \ + EXTERN_C const GUID name \ + = { l, w1, w2, { b1, b2, b3, b4, b5, b6, b7, b8 } }; + +#endif /* #if !defined (INITGUID) || (defined (_STATICALLY_LINKED) && !defined(NCIHACK)) #else */ + +#endif /* #if defined(HELIX_FEATURE_FULLGUID) */ + +#include "hlxclib/memory.h" /* for memcmp */ + +#ifdef __cplusplus +inline HXBOOL IsEqualGUID(REFGUID rguid1, REFGUID rguid2) +{ +#if defined(HELIX_FEATURE_FULLGUID) + return !memcmp(&rguid1, &rguid2, sizeof(GUID)); +#else // HELIX_FEATURE_FULLGUID + return (rguid1 == rguid2); +#endif // HELIX_FEATURE_FULLGUID + +} + +inline void SetGUID(GUID& rguid1, REFGUID rguid2) +{ +#if defined(HELIX_FEATURE_FULLGUID) + memcpy(&rguid1, &rguid2, sizeof(GUID)); /* Flawfinder: ignore */ +#else // HELIX_FEATURE_FULLGUID + rguid1 = rguid2; +#endif // HELIX_FEATURE_FULLGUID +} +#else // __cplusplus +#if defined(HELIX_FEATURE_FULLGUID) +#define IsEqualGUID(rguid1, rguid2) (!memcmp(rguid1, rguid2, sizeof(GUID))) +#define SetGUID(rguid1, rguid2) (memcpy(rguid1, rguid2, sizeof(GUID))) /* Flawfinder: ignore */ +#else // HELIX_FEATURE_FULLGUID +#define IsEqualGUID(rguid1, rguid2) ((rguid1) == (rguid2)) +#define SetGUID(rguid1, rguid2) ((rguid1) = (rguid2)) +#endif // HELIX_FEATURE_FULLGUID +#endif // __cplusplus + +#define IsEqualIID(riid1, riid2) IsEqualGUID(riid1, riid2) +#define IsEqualCLSID(rclsid1, rclsid2) IsEqualGUID(rclsid1, rclsid2) + +#define SetIID(riid1, riid2) SetGUID(riid1, riid2) +#define SetCLSID(rclsid1, rclsid2) SetGUID(rclsid1, rclsid2) + +#ifdef __cplusplus + +/* + * Because GUID is defined elsewhere in WIN32 land, the operator == and != + * are moved outside the class to global scope. + */ +#if defined(HELIX_FEATURE_FULLGUID) +inline HXBOOL operator==(const GUID& guidOne, const GUID& guidOther) +{ + return !memcmp(&guidOne,&guidOther,sizeof(GUID)); +} + +inline HXBOOL operator!=(const GUID& guidOne, const GUID& guidOther) +{ + return !(guidOne == guidOther); +} +#endif // HELIX_FEATURE_FULLGUID + +#endif + + +/**************************************************************************** + * + * Interface: + * + * IUnknown + * + * Purpose: + * + * Base class of all interfaces. Defines life time management and + * support for dynamic cast. + * + * IID_IUnknown: + * + * {00000000-0000-0000-C000000000000046} + * + */ +DEFINE_GUID(IID_IUnknown, 0x00000000, 0x0000, 0x0000, 0xC0, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x46); + +#undef INTERFACE +#define INTERFACE IUnknown + +DECLARE_INTERFACE(IUnknown) +{ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; +}; + + +/**************************************************************************** + * + * Interface: + * + * IMalloc + * + * Purpose: + * + * Basic memory management interface. + * + * IID_IMalloc: + * + * {00000002-0000-0000-C000000000000046} + * + */ +DEFINE_GUID(IID_IMalloc, 00000002, 0x0000, 0x0000, 0xC0, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x46); + +#undef INTERFACE +#define INTERFACE IMalloc + +DECLARE_INTERFACE_(IMalloc, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IMalloc methods + */ + STDMETHOD_(void*,Alloc) (THIS_ + UINT32 /*IN*/ count) PURE; + + STDMETHOD_(void*,Realloc) (THIS_ + void* /*IN*/ pMem, + UINT32 /*IN*/ count) PURE; + + STDMETHOD_(void,Free) (THIS_ + void* /*IN*/ pMem) PURE; + + STDMETHOD_(UINT32,GetSize) (THIS_ + void* /*IN*/ pMem) PURE; + + STDMETHOD_(HXBOOL,DidAlloc) (THIS_ + void* /*IN*/ pMem) PURE; + + STDMETHOD_(void,HeapMinimize) (THIS) PURE; +}; + +#else /* else case of !defined( _OBJBASE_H_ ) && !defined( _COMPOBJ_H_ ) */ + +// For now, we always use full guids on Windows +#ifndef HELIX_FEATURE_FULLGUID +#define HELIX_FEATURE_FULLGUID +#endif /* HELIX_FEATURE_FULLGUID */ + +#ifdef DEFINE_GUID +#undef DEFINE_GUID +#endif + +#if !defined (INITGUID) || (defined (_STATICALLY_LINKED) && !defined(NCIHACK)) +#define DEFINE_GUID(name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) \ + EXTERN_C const GUID FAR name +#define DEFINE_GUID_ENUM(name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) \ + EXTERN_C const GUID FAR name; +#else +#define DEFINE_GUID(name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) \ + EXTERN_C const GUID name \ + = { l, w1, w2, { b1, b2, b3, b4, b5, b6, b7, b8 } } +#define DEFINE_GUID_ENUM(name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) \ + EXTERN_C const GUID name \ + = { l, w1, w2, { b1, b2, b3, b4, b5, b6, b7, b8 } }; +#endif + + +/* Even in windows we want these GUID's defined... */ + +#if !(defined(INITGUID) && defined(USE_IUNKNOWN_AND_IMALLOC_FROM_UUID_LIB)) +DEFINE_GUID(IID_IUnknown, 0x00000000, 0x0000, 0x0000, 0xC0, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x46); + +DEFINE_GUID(IID_IMalloc, 00000002, 0x0000, 0x0000, 0xC0, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x46); +#endif + +#include <memory.h> /* for memcmp */ + +#ifdef __cplusplus +inline void SetGUID(REFGUID rguid1, REFGUID rguid2) +{ + memcpy((void*)&rguid1, (void*)&rguid2, sizeof(GUID)); /* Flawfinder: ignore */ +} +#else +#define SetGUID(rguid1, rguid2) (memcpy((void*)rguid1, (void*)rguid2, sizeof(GUID))) /* Flawfinder: ignore */ +#endif +#define SetIID(riid1, riid2) SetGUID(riid1, riid2) +#define SetCLSID(rclsid1, rclsid2) SetGUID(rclsid1, rclsid2) + +#endif /* !defined( _OBJBASE_H_ ) && !defined( _COMPOBJ_H_ )*/ + +#ifdef __cplusplus + +/* + * This operator is defined for all platforms + */ +#if defined(HELIX_FEATURE_FULLGUID) +inline HXBOOL operator<(const GUID& lhs, const GUID& rhs) +{ + return memcmp(&lhs, &rhs, sizeof(GUID)) < 0; +} +#endif // HELIX_FEATURE_FULLGUID + +#endif // __cplusplus + + +#ifdef IsEqualIID +#undef IsEqualIID +#endif + +#ifdef IsEqualCLSID +#undef IsEqualCLSID +#endif + +#define IsEqualIID(riid1, riid2) HXIsEqualGUID(riid1, riid2) +#define IsEqualCLSID(rclsid1, rclsid2) HXIsEqualGUID(rclsid1, rclsid2) + +#ifdef __cplusplus +inline HXBOOL HXIsEqualGUID(REFGUID rguid1, REFGUID rguid2) +{ +#if defined(HELIX_FEATURE_FULLGUID) + return (((UINT32*) &rguid1)[0] == ((UINT32*) &rguid2)[0] && + ((UINT32*) &rguid1)[1] == ((UINT32*) &rguid2)[1] && + ((UINT32*) &rguid1)[2] == ((UINT32*) &rguid2)[2] && + ((UINT32*) &rguid1)[3] == ((UINT32*) &rguid2)[3]); +#else + return( (rguid1) == (rguid2) ); +#endif +} +#else +#if defined(HELIX_FEATURE_FULLGUID) +#define HXIsEqualGUID(rguid1, rguid2) \ + (((UINT32*) &rguid1)[0] == ((UINT32*) &rguid2)[0] && \ + ((UINT32*) &rguid1)[1] == ((UINT32*) &rguid2)[1] && \ + ((UINT32*) &rguid1)[2] == ((UINT32*) &rguid2)[2] && \ + ((UINT32*) &rguid1)[3] == ((UINT32*) &rguid2)[3]); +#else +#define HXIsEqualGUID(rguid1, rguid2) \ + ( (rguid1) == (rguid2) ); +#endif +#endif + +/**************************************************************************** + * + * QueryInterface size reduction structs and functions. + * + * Example Usage: + * + * QInterfaceList qiList[] = + * { + * { GET_IIDHANDLE(IID_IUnknown), (IUnknown*) (IHXPlugin*) this}, + * { GET_IIDHANDLE(IID_IHXPlugin), (IHXPlugin*) this}, + * { GET_IIDHANDLE(IID_IHXFileFormatObject), (IHXFileFormatObject*) this}, + * { GET_IIDHANDLE(IID_IHXAtomizerResponse), (IHXAtomizerResponse*) this}, + * { GET_IIDHANDLE(IID_IHXAtomizationCommander), (IHXAtomizationCommander*) this}, + * { GET_IIDHANDLE(IID_IHXASMSource), (IHXASMSource*) this}, + * { GET_IIDHANDLE(IID_IHXPacketFormat), (IHXPacketFormat*) this}, + * { GET_IIDHANDLE(IID_IHXFileSwitcher), (IHXFileSwitcher*) m_pFileSwitcher}, + * { GET_IIDHANDLE(IID_IHXCommonClassFactory), m_pClassFactory}, + * { GET_IIDHANDLE(IID_IHXScheduler), m_pScheduler} + * }; + * + * return QIFind(qiList, QILISTSIZE(qiList), riid, ppvObj); + */ +#if !defined(HELIX_FEATURE_FULLGUID) + +#define IIDHANDLE IID +#define GET_IIDHANDLE(x) (x) +#define DREF_IIDHANDLE(x) (x) + +#else // HELIX_FEATURE_FULLGUID + +#define IIDHANDLE const IID* +#define GET_IIDHANDLE(x) (&(x)) +#define DREF_IIDHANDLE(x) (*(x)) + +#endif // HELIX_FEATURE_FULLGUID + +typedef struct +{ + IIDHANDLE hiid; + void* pIFace; +} QInterfaceList; + +#define QILISTSIZE(x) sizeof(x)/sizeof(QInterfaceList) + +#if !defined(INITGUID) || (defined(_STATICALLY_LINKED) && !defined(NCIHACK)) +EXTERN_C HX_RESULT QIFind(QInterfaceList* qiList, UINT32 ulqiListSize, + REFIID riid, void** ppvObj); +#else // !INITGUID || (_STATICALLY_LINKED && NCIHACK) +EXTERN_C HX_RESULT QIFind(QInterfaceList* qiList, UINT32 ulqiListSize, + REFIID riid, void** ppvObj) +{ + do + { + if (IsEqualIID(DREF_IIDHANDLE(qiList->hiid), riid)) + { + *ppvObj = (qiList->pIFace); + if (*ppvObj) + { + ((IUnknown*) (*ppvObj))->AddRef(); + return HXR_OK; + } + return HXR_NOINTERFACE; + } + qiList++; + } while ((--ulqiListSize) != 0); + + *ppvObj = NULL; + return HXR_NOINTERFACE; +} +#endif // !INITGUID || (_STATICALLY_LINKED && NCIHACK) + + +/**************************************************************************** + * + * Putting the following macro in the definition of your class will overload + * new and delete for that object. New will then take an IMalloc* from + * which to allocate memory from and store it in the beginning of the + * memory which it will return. Delete will grab this IMalloc* from + * the beginning of the mem and use this pointer to deallocate the mem. + * + * Example useage: + * class A + * { + * public: + * A(int); + * ~A(); + * + * IMALLOC_MEM + * }; + * + * IMalloc* pMalloc; + * m_pContext->QueryInterface(IID_IMalloc, (void**)&pMalloc); + * A* p = new(pMalloc) A(0); + * pMalloc->Release(); + * delete p; + */ + +#define IMALLOC_MEM\ + void* operator new(size_t size, IMalloc* pMalloc)\ + {\ + void* pMem = pMalloc->Alloc(size + sizeof(IMalloc*));\ + *(IMalloc**)pMem = pMalloc;\ + pMalloc->AddRef();\ + return ((unsigned char*)pMem + sizeof(IMalloc*));\ + }\ +\ + void operator delete(void* pMem)\ + {\ + pMem = (unsigned char*)pMem - sizeof(IMalloc*);\ + IMalloc* pMalloc = *(IMalloc**)pMem;\ + pMalloc->Free(pMem);\ + pMalloc->Release();\ + }\ + + +/**************************************************************************** + * + * By default, we attempt to use atomic InterlockedIncrement/Decrement + * implementations. Add HELIX_FEATURE_DISABLE_INTERLOCKED_INC_DEC to + * your profile's .pf or to your Umakefil/.pcf file to use non-threadsafe + * implementations. + */ +#include "atomicbase.h" + +#if !defined HAVE_INTERLOCKED_INCREMENT +#undef InterlockedIncrement +#undef InterlockedDecrement +#define InterlockedIncrement(plong) (++(*(plong))) +#define InterlockedDecrement(plong) (--(*(plong))) +#endif /* !defined HAVE_INTERLOCKED_INCREMENT */ + + +#if defined(HELIX_CONFIG_COMPACT_COM_MACROS) + +EXTERN_C void HX_AddRefFunc(IUnknown** pUnk); +EXTERN_C void HX_ReleaseFunc(IUnknown** pUnk); + +#if defined(INITGUID) && (!defined(_STATICALLY_LINKED) || defined(NCIHACK)) + +void HX_AddRefFunc(IUnknown** pUnk) +{ + if (*pUnk) + { + (*pUnk)->AddRef(); + } +} + +void HX_ReleaseFunc(IUnknown** pUnk) +{ + if (*pUnk) + { + (*pUnk)->Release(); + *pUnk = 0; + } +} + +#endif // INITGUID && (!_STATICALLY_LINKED || NCIHACK) + +#if defined(HELIX_CONFIG_TYPE_CHECK_COM_MACROS) + +#if defined(HELIX_CONFIG_TYPE_CHECK_COM_MACROS_IMPLICIT_CAST) + +#define HX_RELEASE(x) (HX_ReleaseFunc(&x)) +#define HX_ADDREF(x) (HX_AddRefFunc(&x)) + +#else // defined(HELIX_CONFIG_TYPE_CHECK_COM_MACROS_IMPLICIT_CAST) + +// we don't want to build with this; this should be compiled only +// to ensure that we have a COM object in the code base + +#define HX_RELEASE(x) \ +{ \ + if (x) \ + { \ + IUnknown* pUnk; \ + (x)->QueryInterface(IID_IUnknown, (void**)&pUnk); \ + if (pUnk) \ + { \ + pUnk->Release(); \ + } \ + (x)->Release(); \ + (x) = 0; \ + } \ +} \ + +#define HX_ADDREF(x) \ +{ \ + if (x) \ + { \ + IUnknown* pUnk; \ + (x)->QueryInterface(IID_IUnknown, (void**)&pUnk); \ + if (pUnk) \ + { \ + pUnk->Release(); \ + } \ + (x)->AddRef(); \ + } \ +} \ + +#endif // defined(HELIX_CONFIG_TYPE_CHECK_COM_MACROS_IMPLICIT_CAST) + +#else // defined(HELIX_CONFIG_TYPE_CHECK_COM_MACROS) + +#define HX_RELEASE(x) (HX_ReleaseFunc((IUnknown**)&(x))) +#define HX_ADDREF(x) (HX_AddRefFunc((IUnknown**)&(x))) + +#endif // defined(HELIX_CONFIG_TYPE_CHECK_COM_MACROS) + +#else // defined(HELIX_CONFIG_COMPACT_COM_MACROS) + +#define HX_RELEASE(x) ((x) ? ((x)->Release(), (x) = 0) : 0) +#define HX_ADDREF(x) ((x) ? ((x)->AddRef()) : 0) + +#endif // defined(HELIX_CONFIG_COMPACT_COM_MACROS) + + + +#endif /* _HXCOM_H_ */ diff --git a/amarok/src/engine/helix/helix-sp/helix-include/common/include/hxcomm.h b/amarok/src/engine/helix/helix-sp/helix-include/common/include/hxcomm.h new file mode 100644 index 00000000..dc4c8ea4 --- /dev/null +++ b/amarok/src/engine/helix/helix-sp/helix-include/common/include/hxcomm.h @@ -0,0 +1,625 @@ + +/* + * + * This software is released under the provisions of the GPL version 2. + * see file "COPYING". If that file is not available, the full statement + * of the license can be found at + * + * http://www.fsf.org/licensing/licenses/gpl.txt + * + * Portions Copyright (c) 1995-2004 RealNetworks, Inc. All Rights Reserved. + * + */ + +#ifndef _HXCOMM_H_ +#define _HXCOMM_H_ + +#include "hxengin.h" /* For HXTimeval */ +#include "hxccf.h" /* For IHXCommonClassFactory. Formerly declared in this file. */ + +/* + * Forward declarations of some interfaces defined here-in. + */ + +typedef _INTERFACE IHXStatistics IHXStatistics; +typedef _INTERFACE IHXRegistryID IHXRegistryID; +typedef _INTERFACE IHXServerFork IHXServerFork; +typedef _INTERFACE IHXServerControl IHXServerControl; +typedef _INTERFACE IHXReconfigServerResponse IHXReconfigServerResponse; +typedef _INTERFACE IHXBuffer IHXBuffer; +typedef _INTERFACE IHXWantServerReconfigNotification + IHXWantServerReconfigNotification; +typedef _INTERFACE IHXFastAlloc IHXFastAlloc; + + +/**************************************************************************** + * + * Interface: + * + * IHXStatistics + * + * Purpose: + * + * This interface allows update of the client statistics. + * + * IID_IHXStatistics: + * + * {00000001-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXStatistics, 0x00000001, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXStatistics + +DECLARE_INTERFACE_(IHXStatistics, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXStatistics methods + */ + + /************************************************************************ + * Method: + * IHXStatistics::Init + * Purpose: + * Pass registry ID to the caller + * + */ + STDMETHOD(InitializeStatistics) (THIS_ + UINT32 /*IN*/ ulRegistryID) PURE; + + /************************************************************************ + * Method: + * IHXStatistics::Update + * Purpose: + * Notify the client to update its statistics stored in the registry + * + */ + STDMETHOD(UpdateStatistics) (THIS) PURE; +}; + + +/**************************************************************************** + * + * Interface: + * + * IHXRegistryID + * + * Purpose: + * + * This interface is implemented by IHXPlayer, IHXStreamSource, + * and IHXStream. It allows the user to get the registry Base ID, + * for an object that you have a pointer to. + * + * IID_IHXRegistryID: + * + * {00000002-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXRegistryID, 0x00000002, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXRegistryID + +DECLARE_INTERFACE_(IHXRegistryID, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXRegistryID methods + */ + + /************************************************************************ + * Method: + * IHXRegistryID::GetID + * Purpose: + * Get the registry ID of the object. + * + */ + STDMETHOD(GetID) (THIS_ + REF(UINT32) /*OUT*/ ulRegistryID) PURE; +}; + +/**************************************************************************** + * + * Interface: + * + * IHXServerFork + * + * Purpose: + * + * This interface is implemented by the server context on Unix + * platforms. This interface allows your plugin to fork off a + * process. Note that the process that is forked off cannot use + * any RMA APIs. The fork() system call is prohibited from within + * a RMA plugin. + * + * IID_IHXServerFork: + * + * {00000003-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXServerFork, 0x00000003, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXServerFork + +DECLARE_INTERFACE_(IHXServerFork, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXServerFork methods + */ + + /************************************************************************ + * Method: + * IHXServerFork::Fork + * Purpose: + * Fork off a child process. The child process cannot use any RMA + * APIs. Upon successful completion, Fork returns 0 to the child + * process and the PID of the child to the parent. A return value + * of -1 indicates an error. + * + * Note: The child process should *NOT* Release any interfaces. + * The cleanup of the IHXServerFork() interface and other + * RMA interfaces is done by the parent. + * + */ + STDMETHOD_(INT32, Fork) (THIS) PURE; +}; + +/* + * + * Interface: + * + * IHXServerControl + * + * Purpose: + * + * This interface provides access to the RealMedia server's controls + * for shutting down (for now). + * + * Note: This registry is not related to the Windows system registry. + * + * IID_IHXServerControl: + * + * {00000004-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXServerControl, 0x00000004, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#define CLSID_IHXServerControl IID_IHXServerControl + +#undef INTERFACE +#define INTERFACE IHXServerControl + +DECLARE_INTERFACE_(IHXServerControl, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXServerControl methods + */ + + /************************************************************************ + * Method: + * IHXServerControl::ShutdownServer + * Purpose: + * Shutdown the server. + */ + STDMETHOD(ShutdownServer) (THIS_ + UINT32 status) PURE; +}; + +/* + * + * Interface: + * + * IHXServerControl2 + * + * Purpose: + * + * Interface for extended server control methods. + * + * + * IID_IHXServerControl2: + * + * {00000005-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXServerControl2, 0x00000005, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + + +#undef INTERFACE +#define INTERFACE IHXServerControl2 + +DECLARE_INTERFACE_(IHXServerControl2, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXServerControl2 methods + */ + + /************************************************************************ + * IHXServerControl2::RestartServer + * + * Purpose: + * + * Completely shutdown the server, then restart. Mainly used to + * cause not hot setting config var changes to take effect. + */ + STDMETHOD(RestartServer) (THIS) PURE; + + /************************************************************************ + * IHXServerControl2::ReconfigServer + * + * Purpose: + * + * Used to cause the server to re-read in config from file or registry + * (however it was started) and attempt to use the values. + */ + STDMETHOD(ReconfigServer) (THIS_ IHXReconfigServerResponse* pResp) PURE; + +}; + +/* + * + * Interface: + * + * IHXReconfigServerResponse + * + * Purpose: + * + * Response interface for IHXServerControl2::ReconfigServer + * + * + * IID_IHXReconfigServerResponse: + * + * {00000006-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXReconfigServerResponse, 0x00000006, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + + +#undef INTERFACE +#define INTERFACE IHXReconfigServerResponse + +DECLARE_INTERFACE_(IHXReconfigServerResponse, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /************************************************************************ + * IHXReconfigServerResponse::ReconfigServerDone + * + * Purpose: + * + * Notification that reconfiguring the server is done. + */ + STDMETHOD(ReconfigServerDone) (THIS_ + HX_RESULT res, + IHXBuffer** pInfo, + UINT32 ulNumInfo) PURE; +}; + +/* + * + * Interface: + * + * IHXServerReconfigNotification + * + * Purpose: + * + * Register with the server that you want notification when a reconfig + * request comes in and want/need to take part in the reconfiguration. This + * is used when you have configuration info outside the server config file + * which needs to be re-initialized. + * + * + * IID_IHXServerReconfigNotification: + * + * {00000007-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXServerReconfigNotification, 0x00000007, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + + +#undef INTERFACE +#define INTERFACE IHXServerReconfigNotification + +DECLARE_INTERFACE_(IHXServerReconfigNotification, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /************************************************************************ + * IHXServerReconfigNotification::WantReconfigNotification + * + * Purpose: + * + * Tell the server that you want reconfig notification. + */ + STDMETHOD(WantReconfigNotification) (THIS_ + IHXWantServerReconfigNotification* pResponse) PURE; + + /************************************************************************ + * IHXServerReconfigNotification::CancelReconfigNotification + * + * Purpose: + * + * Tell the server that you no longer want reconfig notification. + */ + STDMETHOD(CancelReconfigNotification) (THIS_ + IHXWantServerReconfigNotification* pResponse) PURE; + +}; + +/* + * + * Interface: + * + * IHXWantServerReconfigNotification + * + * Purpose: + * + * Tell user that the server got a reconfig request and it is time to + * do your reconfiguration. NOTE: You should not need this if all of your + * configuration is stored in the config file; that is taken care of through + * IHXActiveRegistry. + * + * IID_IHXWantServerReconfigNotification: + * + * {00000008-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXWantServerReconfigNotification, 0x00000008, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + + +#undef INTERFACE +#define INTERFACE IHXWantServerReconfigNotification + +DECLARE_INTERFACE_(IHXWantServerReconfigNotification, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /************************************************************************ + * IHXWantServerReconfigNotification::ServerReconfig + * + * Purpose: + * + * Notify user that a server reconfig request had come in and it + * is now your turn to do external (not server config) reconfiguration.* + */ + STDMETHOD(ServerReconfig) (THIS_ + IHXReconfigServerResponse* pResponse) PURE; + +}; + +// $Private: + +DEFINE_GUID(IID_IHXResolverExec, 0x00000009, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + + +#undef INTERFACE +#define INTERFACE IHXResolverExec + +DECLARE_INTERFACE_(IHXResolverExec, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + STDMETHOD(ResolverExec) (THIS_ int readfd, int writefd) PURE; + +}; + + +/**************************************************************************** + * + * Interface: + * + * IHXFastAlloc + * + * Purpose: + * + * Basic memory management interface. + * + * IID_IHXFastAlloc: + * + * {0000000a-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXFastAlloc, 0x0000000a, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXFastAlloc + +DECLARE_INTERFACE_(IHXFastAlloc, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXFastAlloc methods + */ + STDMETHOD_(void*,FastAlloc) (THIS_ + UINT32 /*IN*/ count) PURE; + + STDMETHOD_(void,FastFree) (THIS_ + void* /*IN*/ pMem) PURE; +}; + +#define FAST_CACHE_MEM\ + void* operator new(size_t size, IHXFastAlloc* pMalloc = NULL)\ + {\ + void* pMem;\ + if (pMalloc)\ + {\ + pMem = pMalloc->FastAlloc(size + sizeof(IHXFastAlloc*));\ + }\ + else\ + {\ + pMem = (void *)::new char [size + sizeof(IHXFastAlloc*)];\ + }\ + *(IHXFastAlloc**)pMem = pMalloc;\ + return ((unsigned char*)pMem + sizeof(IHXFastAlloc*));\ + }\ +\ + void operator delete(void* pMem)\ + {\ + pMem = (unsigned char*)pMem - sizeof(IHXFastAlloc*);\ + IHXFastAlloc* pMalloc = *(IHXFastAlloc**)pMem;\ + if (pMalloc)\ + {\ + pMalloc->FastFree((char *)pMem);\ + }\ + else\ + {\ + delete[] (char *)pMem;\ + }\ + }\ + + +/**************************************************************************** + * + * Interface: + * + * IHXAccurateClock + * + * Purpose: + * + * High Accuracy, Cheap (no system-call) gettimeofday() + * [ Only available on some Unix platforms, except QI can fail!! ] + * + * IID_IHXAccurateClock: + * + * {0000000b-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXAccurateClock, 0x0000000b, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXAccurateClock + +DECLARE_INTERFACE_(IHXAccurateClock, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXAccurateClock methods + */ + STDMETHOD_(HXTimeval,GetTimeOfDay) (THIS) PURE; +}; + +// $EndPrivate. + +#endif /*_HXCOMM_H_*/ diff --git a/amarok/src/engine/helix/helix-sp/helix-include/common/include/hxcore.h b/amarok/src/engine/helix/helix-sp/helix-include/common/include/hxcore.h new file mode 100644 index 00000000..e76f85c4 --- /dev/null +++ b/amarok/src/engine/helix/helix-sp/helix-include/common/include/hxcore.h @@ -0,0 +1,2110 @@ + +/* + * + * This software is released under the provisions of the GPL version 2. + * see file "COPYING". If that file is not available, the full statement + * of the license can be found at + * + * http://www.fsf.org/licensing/licenses/gpl.txt + * + * Portions Copyright (c) 1995-2004 RealNetworks, Inc. All Rights Reserved. + * + */ + +#ifndef _HXCORE_H_ +#define _HXCORE_H_ + +/* + * Forward declarations of some interfaces defined or used here-in. + */ +typedef _INTERFACE IUnknown IUnknown; +typedef _INTERFACE IHXStream IHXStream; +typedef _INTERFACE IHXStream2 IHXStream2; +typedef _INTERFACE IHXStreamSource IHXStreamSource; +typedef _INTERFACE IHXPlayer IHXPlayer; +typedef _INTERFACE IHXClientEngine IHXClientEngine; +typedef _INTERFACE IHXScheduler IHXScheduler; +typedef _INTERFACE IHXClientAdviseSink IHXClientAdviseSink; +typedef _INTERFACE IHXValues IHXValues; +typedef _INTERFACE IHXBuffer IHXBuffer; +typedef _INTERFACE IHXPacket IHXPacket; +// $Private: +typedef _INTERFACE IHXPersistenceManager IHXPersistenceManager; +typedef _INTERFACE IHXRendererAdviseSink IHXRendererAdviseSink; +typedef _INTERFACE IHXLayoutSite IHXLayoutSite; +typedef _INTERFACE IHXProtocolValidator IHXProtocolValidator; +typedef _INTERFACE IHXUpdateProperties IHXUpdateProperties; +typedef _INTERFACE IHXUpdateProperties2 IHXUpdateProperties2; +typedef _INTERFACE IHXPersistentComponentManager IHXPersistentComponentManager; +typedef _INTERFACE IHXPersistentComponent IHXPersistentComponent; +typedef _INTERFACE IHXPersistentRenderer IHXPersistentRenderer; +typedef _INTERFACE IHXCoreMutex IHXCoreMutex; +typedef _INTERFACE IHXMacBlitMutex IHXMacBlitMutex; +// $EndPrivate. +typedef _INTERFACE IHXRenderer IHXRenderer; +typedef _INTERFACE IHXPlayer2 IHXPlayer2; +typedef _INTERFACE IHXRequest IHXrequest; +typedef _INTERFACE IHXPlayerNavigator IHXPlayerNavigator; +typedef _INTERFACE IHXGroupSink IHXGroupSink; + +typedef struct _PersistentComponentInfo PersistentComponentInfo; +typedef struct _HXxEvent HXxEvent; + + +#ifdef _MAC_CFM +#pragma export on +#endif + +#if defined _UNIX && !(defined _VXWORKS) +/* Includes needed for select() stuff */ +#include <sys/time.h> +#include <sys/types.h> +#include <unistd.h> +#endif + +#ifdef _BEOS // fd_set stuff +#include <net/socket.h> +#endif + +#include "hxwin.h" + +/* Used in renderer and advise sink interface */ +enum BUFFERING_REASON +{ + BUFFERING_START_UP = 0, + BUFFERING_SEEK, + BUFFERING_CONGESTION, + BUFFERING_LIVE_PAUSE +}; + +/**************************************************************************** + * + * Function: + * + * CreateEngine() + * + * Purpose: + * + * Function implemented by the RMA core to return a pointer to the + * client engine. This function would be run by top level clients. + */ +STDAPI CreateEngine + ( + IHXClientEngine** /*OUT*/ ppEngine + ); + +/**************************************************************************** + * + * Function: + * + * CloseEngine() + * + * Purpose: + * + * Function implemented by the RMA core to close the engine which + * was returned in CreateEngine(). + */ +STDAPI CloseEngine + ( + IHXClientEngine* /*IN*/ pEngine + ); + +#ifdef _MAC_CFM +#pragma export off +#endif + +/* + * Definitions of Function Pointers to CreateEngine() and Close Engine(). + * These types are provided as a convenince to authors of top level clients. + */ +typedef HX_RESULT (HXEXPORT_PTR FPRMCREATEENGINE)(IHXClientEngine** ppEngine); +typedef HX_RESULT (HXEXPORT_PTR FPRMCLOSEENGINE) (IHXClientEngine* pEngine); + + +/**************************************************************************** + * + * Interface: + * + * IHXStream + * + * Purpose: + * + * Interface provided by the client engine to the renderers. This + * interface allows access to stream related information and properties. + * + * IID_IHXStream: + * + * {00000400-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXStream, 0x00000400, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXStream + +DECLARE_INTERFACE_(IHXStream, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXStream methods + */ + + /************************************************************************ + * Method: + * IHXStream::GetSource + * Purpose: + * Get the interface to the source object of which the stream is + * a part of. + * + */ + STDMETHOD(GetSource) (THIS_ + REF(IHXStreamSource*) pSource) PURE; + + /************************************************************************ + * Method: + * IHXStream::GetStreamNumber + * Purpose: + * Get the stream number for this stream relative to the source + * object of which the stream is a part of. + * + */ + STDMETHOD_(UINT16,GetStreamNumber) (THIS) PURE; + + /************************************************************************ + * Method: + * IHXStream::GetStreamType + * Purpose: + * Get the MIME type for this stream. NOTE: The returned string is + * assumed to be valid for the life of the IHXStream from which it + * was returned. + * + */ + STDMETHOD_(const char*,GetStreamType) (THIS) PURE; + + /************************************************************************ + * Method: + * IHXStream::GetHeader + * Purpose: + * Get the header for this stream. + * + */ + STDMETHOD_(IHXValues*,GetHeader) (THIS) PURE; + + /************************************************************************ + * Method: + * IHXStream::ReportQualityOfService + * Purpose: + * Call this method to report to the playback context that the + * quality of service for this stream has changed. The unQuality + * should be on a scale of 0 to 100, where 100 is the best possible + * quality for this stream. Although the transport engine can + * determine lost packets and report these through the user + * interface, only the renderer of this stream can determine the + * "real" perceived damage associated with this loss. + * + * NOTE: The playback context may use this value to indicate loss + * in quality to the user interface. When the effects of a lost + * packet are eliminated the renderer should call this method with + * a unQuality of 100. + * + */ + STDMETHOD(ReportQualityOfService) (THIS_ + UINT8 unQuality) PURE; + + /************************************************************************ + * Method: + * IHXStream::ReportRebufferStatus + * Purpose: + * Call this method to report to the playback context that the + * available data has dropped to a critically low level, and that + * rebuffering should occur. The renderer should call back into this + * interface as it receives additional data packets to indicate the + * status of its rebuffering effort. + * + * NOTE: The values of unNeeded and unAvailable are used to indicate + * the general status of the rebuffering effort. For example, if a + * renderer has "run dry" and needs 5 data packets to play smoothly + * again, it should call ReportRebufferStatus() with 5,0 then as + * packet arrive it should call again with 5,1; 5,2... and eventually + * 5,5. + * + */ + STDMETHOD(ReportRebufferStatus) (THIS_ + UINT8 unNeeded, + UINT8 unAvailable) PURE; + + /************************************************************************ + * Method: + * IHXStream::SetGranularity + * Purpose: + * Sets the desired Granularity for this stream. The actual + * granularity will be the lowest granularity of all streams. + * Valid to call before stream actually begins. Best to call during + * IHXRenderer::OnHeader(). + */ + STDMETHOD(SetGranularity) (THIS_ + ULONG32 ulGranularity) PURE; + + /************************************************************************ + * Method: + * IHXStream::GetRendererCount + * Purpose: + * Returns the current number of renderer instances supported by + * this stream instance. + */ + STDMETHOD_(UINT16, GetRendererCount)(THIS) PURE; + + /************************************************************************ + * Method: + * IHXStream::GetRenderer + * Purpose: + * Returns the Nth renderer instance supported by this stream. + */ + STDMETHOD(GetRenderer) (THIS_ + UINT16 nIndex, + REF(IUnknown*) pUnknown) PURE; +}; + +/**************************************************************************** + * + * Interface: + * + * IHXStream2 + * + * Purpose: + * + * Interface provided by the client engine to the renderers. This + * interface allows access to stream related information and properties. + * + * IID_IHXStream: + * + * {00000400-0901-11d1-8B06-00A024406D5a} + * + */ +DEFINE_GUID(IID_IHXStream2, 0x00000400, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x5a); + +#undef INTERFACE +#define INTERFACE IHXStream2 + +DECLARE_INTERFACE_(IHXStream2, IHXStream) +{ + /************************************************************************ + * Method: + * IHXStream2::ReportAudioRebufferStatus + * Purpose: + * For audio only, when it's called, the rebuffer will only occur when + * there aren't any packets in the transport and the amount of audio in + * audio device falls below the minimum startup audio pushdown(1000ms + * by default) + * + * Non-audio renderers should still call ReportRebufferStatus(), the + * rebuffer will occur when the core drains out all the packets from + * the transport buffer + * + * The rest semantic are the same between the 2 calls. + */ + STDMETHOD(ReportAudioRebufferStatus) (THIS_ + UINT8 unNeeded, + UINT8 unAvailable) PURE; +}; + +/**************************************************************************** + * + * Interface: + * + * IHXStreamSource + * + * Purpose: + * + * Interface provided by the client engine to the renderers. This + * interface allows access to source related information and properties. + * + * IID_IHXStreamSource: + * + * {00000401-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXStreamSource, 0x00000401, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXStreamSource + +DECLARE_INTERFACE_(IHXStreamSource, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXStreamSource methods + */ + + /************************************************************************ + * Method: + * IHXStreamSource::IsLive + * Purpose: + * Ask the source whether it is live + * + */ + STDMETHOD_ (HXBOOL,IsLive) (THIS) PURE; + + /************************************************************************ + * Method: + * IHXStreamSource::GetPlayer + * Purpose: + * Get the interface to the player object of which the source is + * a part of. + * + */ + STDMETHOD(GetPlayer) (THIS_ + REF(IHXPlayer*) pPlayer) PURE; + + /************************************************************************ + * Method: + * IHXStreamSource::GetURL + * Purpose: + * Get the URL for this source. NOTE: The returned string is + * assumed to be valid for the life of the IHXStreamSource from which + * it was returned. + * + */ + STDMETHOD_(const char*,GetURL) (THIS) PURE; + + /************************************************************************ + * Method: + * IHXStreamSource::GetStreamCount + * Purpose: + * Returns the current number of stream instances supported by + * this source instance. + */ + STDMETHOD_(UINT16, GetStreamCount)(THIS) PURE; + + /************************************************************************ + * Method: + * IHXStreamSource::GetStream + * Purpose: + * Returns the Nth stream instance supported by this source. + */ + STDMETHOD(GetStream) (THIS_ + UINT16 nIndex, + REF(IUnknown*) pUnknown) PURE; +}; + + +/**************************************************************************** + * + * Interface: + * + * IHXPlayer + * + * Purpose: + * + * Interface provided by the client engine to the renderers. This + * interface allows access to player related information, properties, + * and operations. + * + * IID_IHXPlayer: + * + * {00000402-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXPlayer, 0x00000402, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXPlayer + +DECLARE_INTERFACE_(IHXPlayer, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXPlayer methods + */ + + /************************************************************************ + * Method: + * IHXPlayer::GetClientEngine + * Purpose: + * Get the interface to the client engine object of which the + * player is a part of. + * + */ + STDMETHOD(GetClientEngine) (THIS_ + REF(IHXClientEngine*) pEngine) PURE; + + /************************************************************************ + * Method: + * IHXPlayer::IsDone + * Purpose: + * Ask the player if it is done with the current presentation + * + */ + STDMETHOD_(HXBOOL,IsDone) (THIS) PURE; + + /************************************************************************ + * Method: + * IHXPlayer::IsLive + * Purpose: + * Ask the player whether it contains the live source + * + */ + STDMETHOD_(HXBOOL,IsLive) (THIS) PURE; + + /************************************************************************ + * Method: + * IHXPlayer::GetCurrentPlayTime + * Purpose: + * Get the current time on the Player timeline + * + */ + STDMETHOD_(ULONG32,GetCurrentPlayTime) (THIS) PURE; + + /************************************************************************ + * Method: + * IHXPlayer::OpenURL + * Purpose: + * Tell the player to begin playback of all its sources. + * + */ + STDMETHOD(OpenURL) (THIS_ + const char* pURL) PURE; + + /************************************************************************ + * Method: + * IHXPlayer::Begin + * Purpose: + * Tell the player to begin playback of all its sources. + * + */ + STDMETHOD(Begin) (THIS) PURE; + + /************************************************************************ + * Method: + * IHXPlayer::Stop + * Purpose: + * Tell the player to stop playback of all its sources. + * + */ + STDMETHOD(Stop) (THIS) PURE; + + /************************************************************************ + * Method: + * IHXPlayer::Pause + * Purpose: + * Tell the player to pause playback of all its sources. + * + */ + STDMETHOD(Pause) (THIS) PURE; + + /************************************************************************ + * Method: + * IHXPlayer::Seek + * Purpose: + * Tell the player to seek in the playback timeline of all its + * sources. + * + */ + STDMETHOD(Seek) (THIS_ + ULONG32 ulTime) PURE; + + /************************************************************************ + * Method: + * IHXPlayer::GetSourceCount + * Purpose: + * Returns the current number of source instances supported by + * this player instance. + */ + STDMETHOD_(UINT16, GetSourceCount)(THIS) PURE; + + /************************************************************************ + * Method: + * IHXPlayer::GetSource + * Purpose: + * Returns the Nth source instance supported by this player. + */ + STDMETHOD(GetSource) (THIS_ + UINT16 nIndex, + REF(IUnknown*) pUnknown) PURE; + + /************************************************************************ + * Method: + * IHXPlayer::SetClientContext + * Purpose: + * Called by the client to install itself as the provider of client + * services to the core. This is traditionally called by the top + * level client application. + */ + STDMETHOD(SetClientContext) (THIS_ + IUnknown* pUnknown) PURE; + + /************************************************************************ + * Method: + * IHXPlayer::GetClientContext + * Purpose: + * Called to get the client context for this player. This is + * set by the top level client application. + */ + STDMETHOD(GetClientContext) (THIS_ + REF(IUnknown*) pUnknown) PURE; + + /************************************************************************ + * Method: + * IHXPlayer::AddAdviseSink + * Purpose: + * Call this method to add a client advise sink. + * + */ + STDMETHOD(AddAdviseSink) (THIS_ + IHXClientAdviseSink* pAdviseSink) PURE; + + /************************************************************************ + * Method: + * IHXPlayer::RemoveAdviseSink + * Purpose: + * Call this method to remove a client advise sink. + */ + STDMETHOD(RemoveAdviseSink) (THIS_ + IHXClientAdviseSink* pAdviseSink) PURE; +}; + + +/**************************************************************************** + * + * Interface: + * + * IHXClientEngine + * + * Purpose: + * + * Interface to the basic client engine. Provided to the renderers and + * other client side components. + * + * IID_IHXClientEngine: + * + * {00000403-0901-11d1-8B06-00A024406D59} + */ +DEFINE_GUID(IID_IHXClientEngine, 0x00000403, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXClientEngine + +DECLARE_INTERFACE_(IHXClientEngine, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXClientEngine methods + */ + + /************************************************************************ + * Method: + * IHXClientEngine::CreatePlayer + * Purpose: + * Creates a new IHXPlayer instance. + * + */ + STDMETHOD(CreatePlayer) (THIS_ + REF(IHXPlayer*) pPlayer) PURE; + + /************************************************************************ + * Method: + * IHXClientEngine::ClosePlayer + * Purpose: + * Called by the client when it is done using the player... + * + */ + STDMETHOD(ClosePlayer) (THIS_ + IHXPlayer* pPlayer) PURE; + + /************************************************************************ + * Method: + * IHXClientEngine::GetPlayerCount + * Purpose: + * Returns the current number of IHXPlayer instances supported by + * this client engine instance. + */ + STDMETHOD_(UINT16, GetPlayerCount)(THIS) PURE; + + /************************************************************************ + * Method: + * IHXClientEngine::GetPlayer + * Purpose: + * Returns the Nth IHXPlayer instances supported by this client + * engine instance. + */ + STDMETHOD(GetPlayer) (THIS_ + UINT16 nPlayerNumber, + REF(IUnknown*) pUnknown) PURE; + + /************************************************************************ + * Method: + * IHXClientEngine::EventOccurred + * Purpose: + * Clients call this to pass OS events to all players. HXxEvent + * defines a cross-platform event. + */ + STDMETHOD(EventOccurred) (THIS_ + HXxEvent* /*IN*/ pEvent) PURE; + +}; + +// $Private: +/**************************************************************************** + * + * Interface: + * + * IHXClientEngineMapper + * + * Purpose: + * + * Interface to the basic client engine. Provided to the renderers and + * other client side components. + * + * IID_IHXClientEngineMapper: + * + * {0000040A-0901-11d1-8B06-00A024406D59} + */ +DEFINE_GUID(IID_IHXClientEngineMapper, 0x0000040A, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXClientEngineMapper + +DECLARE_INTERFACE_(IHXClientEngineMapper, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXClientEngineMapper methods + */ + + /************************************************************************ + * Method: + * IHXClientEngineMapper::GetPlayerBySite + * Purpose: + * Returns the IHXPlayer instance supported by this client + * engine instance that contains the specified IHXSite. + */ + STDMETHOD(GetPlayerBySite) (THIS_ + IHXSite* pSite, + REF(IUnknown*) pUnknown) PURE; +}; +// $EndPrivate: + +#if defined _UNIX && !defined (_VXWORKS) +DEFINE_GUID(IID_IHXClientEngineSelector, 0x00000404, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXClientEngineSelector + +DECLARE_INTERFACE_(IHXClientEngineSelector, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /************************************************************************ + * Method: + * IHXClientEngine::Select + * Purpose: + * Top level clients under Unix should use this instead of + * select() to select for events. + */ + STDMETHOD_(INT32, Select) (THIS_ + INT32 n, + fd_set *readfds, + fd_set *writefds, + fd_set *exceptfds, + struct timeval* timeout) PURE; +}; +#endif /* _UNIX */ + +/**************************************************************************** + * + * Interface: + * + * IHXClientEngineSetup + * + * Purpose: + * + * Interface to the basic client engine. Provided to the renderers and + * other client side components. + * + * IID_IHXClientEngineSetup: + * + * {00000405-0901-11d1-8B06-00A024406D59} + */ +DEFINE_GUID(IID_IHXClientEngineSetup, 0x00000405, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXClientEngineSetup + +DECLARE_INTERFACE_(IHXClientEngineSetup, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXClientEngineSetup methods + */ + + /************************************************************************ + * Method: + * IHXClientEngineSetup::Setup + * Purpose: + * Top level clients use this interface to over-ride certain basic + * interfaces implemented by the core. Current over-ridable + * interfaces are: IHXPreferences, IHXHyperNavigate + */ + STDMETHOD(Setup) (THIS_ + IUnknown* pContext) PURE; +}; + + +/**************************************************************************** + * + * Interface: + * + * IHXInfoLogger + * + * Purpose: + * + * Interface to send any logging information back to the server. + * This information will appear in the server's access log. + * + * IID_IHXInfoLogger: + * + * {00000409-0901-11d1-8B06-00A024406D59} + */ +DEFINE_GUID(IID_IHXInfoLogger, 0x00000409, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXInfoLogger + +DECLARE_INTERFACE_(IHXInfoLogger, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXInfoLogger methods + */ + + /************************************************************************ + * Method: + * IHXInfoLogger::LogInformation + * Purpose: + * Logs any user defined information in form of action and + * associated data. + */ + STDMETHOD(LogInformation) (THIS_ + const char* /*IN*/ pAction, + const char* /*IN*/ pData) PURE; +}; + + +// $Private: +/**************************************************************************** + * + * Interface: + * + * IHXPersistenceManager + * + * Purpose: + * + * + * IID_IHXPersistenceManager: + * + * {0000040B-0901-11d1-8B06-00A024406D59} + */ +DEFINE_GUID(IID_IHXPersistenceManager, 0x0000040B, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXPersistenceManager + +DECLARE_INTERFACE_(IHXPersistenceManager, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXPersistenceManager methods + */ + /************************************************************************ + * Method: + * IHXPersistenceManager::AddPersistentComponent + * Purpose: + * We currently allow ONLY IHXRenderer to register itself as a + * persistent component. + * Renderer registers itself as a persistent renderer if it wants + * to live across group boundaries. + * It will not be unloaded until + * a) Someone opens the next URL. + * b) All the groups within the current presentation have been + * played. + */ + STDMETHOD(AddPersistentComponent) (THIS_ + IUnknown* /*IN*/ pComponent) PURE; + + /************************************************************************ + * Method: + * IHXPersistenceManager::RemovePersistentComponent + * Purpose: + * Remove an earlier registered persistent component. + */ + STDMETHOD(RemovePersistentComponent) (THIS_ + IUnknown* /*IN*/ pComponent) PURE; + + /************************************************************************ + * Method: + * IHXPersistenceManager::GetPersistentComponent + * Purpose: + * Return an earlier registered persistent component. + */ + STDMETHOD(GetPersistentComponent) (THIS_ + REF(IUnknown*) /*OUT*/ pComponent) PURE; + +}; + +/**************************************************************************** + * + * Interface: + * + * IHXDriverStreamManager + * + * Purpose: + * Methods to notify/update driver stream renderer + * + * + * IID_IHXDriverStreamManager + * + * {0000040C-0901-11d1-8B06-00A024406D59} + */ +DEFINE_GUID(IID_IHXDriverStreamManager, 0x0000040C, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXDriverStreamManager + +DECLARE_INTERFACE_(IHXDriverStreamManager, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXDriverStreamManager methods + */ + /************************************************************************ + * Method: + * IHXDriverStreamManager::AddRendererAdviseSink + * Purpose: + * Add a renderer advise sink + * + */ + STDMETHOD(AddRendererAdviseSink) (THIS_ + IHXRendererAdviseSink* pSink) PURE; + + /************************************************************************ + * Method: + * IHXDriverStreamManager::SetDriverStreamManager + * Purpose: + * Remove an advise sink + * + */ + STDMETHOD(RemoveRendererAdviseSink) (THIS_ + IHXRendererAdviseSink* pSink) PURE; +}; + + +/**************************************************************************** + * + * Interface: + * IHXRendererAdviseSink + * + * Purpose: + * Provides access to notifications of initialization/changes to + * renderers in the player. + * + * IID_IHXRendererAdviseSink + * + * {0000040D-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXRendererAdviseSink, 0x0000040D, 0x901, + 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXRendererAdviseSink + +DECLARE_INTERFACE_(IHXRendererAdviseSink, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXRendererAdviseSink methods + */ + + STDMETHOD(TrackDurationSet) (THIS_ + UINT32 ulGroupIndex, + UINT32 ulTrackIndex, + UINT32 ulDuration, + UINT32 ulDelay, + HXBOOL bIsLive) PURE; + + STDMETHOD(RepeatedTrackDurationSet) (THIS_ + const char* pID, + UINT32 ulDuration, + HXBOOL bIsLive) PURE; + + STDMETHOD(TrackUpdated) (THIS_ + UINT32 ulGroupIndex, + UINT32 ulTrackIndex, + IHXValues* pValues) PURE; + + /************************************************************************ + * Method: + * IHXRendererAdviseSink::RendererInitialized + * Purpose: + * Notification of renderer initialization + * + */ + STDMETHOD(RendererInitialized) (THIS_ + IHXRenderer* pRenderer, + IUnknown* pStream, + IHXValues* pInfo) PURE; + + /************************************************************************ + * Method: + * IHXRendererAdviseSink::RendererClosed + * Purpose: + * Notification of renderer close + * + */ + STDMETHOD(RendererClosed) (THIS_ + IHXRenderer* pRenderer, + IHXValues* pInfo) PURE; +}; + + +/**************************************************************************** + * + * Interface: + * + * IHXLayoutStream + * + * Purpose: + * + * Interface that allows access/updates to stream properties + * + * IID_IHXLayoutStream: + * + * {0000040E-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXLayoutStream, 0x0000040E, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXLayoutStream + +DECLARE_INTERFACE_(IHXLayoutStream, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXLayoutStream methods + */ + + /************************************************************************ + * Method: + * IHXLayoutStream::GetProperty + * Purpose: + * Get layout stream property + * + * + */ + STDMETHOD(GetProperties) (THIS_ + REF(IHXValues*) pValue) PURE; + + /************************************************************************ + * Method: + * IHXLayoutStream::SetProperty + * Purpose: + * Set layout stream property + * + */ + STDMETHOD(SetProperties) (THIS_ + IHXValues* pValue) PURE; +}; + +/**************************************************************************** + * + * Interface: + * + * IHXRendererUpgrade + * + * Purpose: + * + * Interface that tells the player to upgrade a particular set of renderers + * + * IID_IHXRendererUpgrade: + * + * {00000410-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXRendererUpgrade, 0x00000410, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXRendererUpgrade + +DECLARE_INTERFACE_(IHXRendererUpgrade, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXRendererUpgrade methods + */ + + /************************************************************************ + * Method: + * IHXRendererUpgrade::IsRendererAvailable + * Purpose: + * Find out if the renderer is already loaded + * + * + */ + STDMETHOD_(HXBOOL,IsRendererAvailable) (THIS_ + const char* pMimeType) PURE; + + /************************************************************************ + * Method: + * IHXRendererUpgrade::ForceUpgrade + * Purpose: + * Force an upgrade for any unloaded renderers + * + */ + STDMETHOD(ForceUpgrade) (THIS) PURE; +}; + +/**************************************************************************** + * + * Interface: + * + * IHXValidator + * + * Purpose: + * + * Interface that provides validation + * + * IID_IHXValidator: + * + * {00000412-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXValidator, 0x00000412, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXValidator + +DECLARE_INTERFACE_(IHXValidator, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXValidator methods + */ + + /************************************************************************ + * Method: + * IHXValidator::ValidateProtocol + * Purpose: + * Find out if the protocol is valid + * + * + */ + STDMETHOD_(HXBOOL,ValidateProtocol) (THIS_ + char* pProtocol) PURE; + + /************************************************************************ + * Method: + * IHXValidator::ValidateMetaFile + * Purpose: + * Find out if it is a meta file + * + * + */ + STDMETHOD(ValidateMetaFile) (THIS_ + IHXRequest* pRequest, + IHXBuffer* pContent) PURE; +}; + +/**************************************************************************** + * + * Interface: + * + * IHXPrivateStreamSource + * + * Purpose: + * This interface is being added for the sole purpose of implementing + * IsSaveAllowed on our stream source objects. We need to get this in + * for alpha-3 of the player, since it would be very bad to put out a + * player version that allowed recording of content that was supposed to + * be unrecordable. This method should be moved into IHXStreamSource + * as soon as is convenient. + * + * IHXPrivateStreamSource: + * + * {57DFD0E2-C76E-11d1-8B5C-006008065552} + * + */ + +DEFINE_GUID(IID_IHXPrivateStreamSource, 0x57dfd0e2, 0xc76e, 0x11d1, 0x8b, 0x5c, + 0x0, 0x60, 0x8, 0x6, 0x55, 0x52); + +#undef INTERFACE +#define INTERFACE IHXPrivateStreamSource + +DECLARE_INTERFACE_(IHXPrivateStreamSource, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXPrivateStreamSource methods + */ + + + STDMETHOD_ (HXBOOL,IsSaveAllowed) (THIS) PURE; + +}; + +// $EndPrivate. + +/**************************************************************************** + * + * Interface: + * + * IHXPlayer2 + * + * Purpose: + * + * Extra methods in addition to IHXPlayer + * + * IID_IHXPlayer2: + * + * {00000411-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXPlayer2, 0x00000411, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXPlayer2 + +DECLARE_INTERFACE_(IHXPlayer2, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /************************************************************************ + * Method: + * IID_IHXPlayer2::SetMinimumPreroll + * Purpose: + * Call this method to set the minimum preroll of this clip + */ + STDMETHOD(SetMinimumPreroll) (THIS_ + UINT32 ulMinPreroll) PURE; + + /************************************************************************ + * Method: + * IID_IHXPlayer2::GetMinimumPreroll + * Purpose: + * Call this method to get the minimum preroll of this clip + */ + STDMETHOD(GetMinimumPreroll) (THIS_ + REF(UINT32) ulMinPreroll) PURE; + + /************************************************************************ + * Method: + * IID_IHXPlayer2::OpenRequest + * Purpose: + * Call this method to open the IHXRequest + */ + STDMETHOD(OpenRequest) (THIS_ + IHXRequest* pRequest) PURE; + + /************************************************************************ + * Method: + * IID_IHXPlayer2::GetRequest + * Purpose: + * Call this method to get the IHXRequest + */ + STDMETHOD(GetRequest) (THIS_ + REF(IHXRequest*) pRequest) PURE; +}; + +// $Private: +/**************************************************************************** + * + * Interface: + * + * IHXUpdateProperties + * + * Purpose: + * + * update any offset related stuff + * + * IID_IHXUpdateProperties: + * + * {00000413-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXUpdateProperties, 0x00000413, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXUpdateProperties + +DECLARE_INTERFACE_(IHXUpdateProperties, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /************************************************************************ + * Method: + * IHXUpdateProperties::UpdatePacketTimeOffset + * Purpose: + * Call this method to update the timestamp offset of cached packets + */ + STDMETHOD(UpdatePacketTimeOffset) (THIS_ + INT32 lTimeOffset) PURE; + + /************************************************************************ + * Method: + * IHXUpdateProperties::UpdatePlayTimes + * Purpose: + * Call this method to update properties + */ + STDMETHOD(UpdatePlayTimes) (THIS_ + IHXValues* pProps) PURE; +}; + +/**************************************************************************** + * + * Interface: + * + * IHXUpdateProperties + * + * Purpose: + * + * update any offset related stuff + * + * IID_IHXUpdateProperties: + * + * {00000413-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXUpdateProperties2, 0x00000413, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x5a); + +#undef INTERFACE +#define INTERFACE IHXUpdateProperties2 + +DECLARE_INTERFACE_(IHXUpdateProperties2, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /************************************************************************ + * Method: + * IHXUpdateProperties::UpdateHeader + * Purpose: + * Call this method to update the stream header + */ + STDMETHOD(UpdateHeader) (THIS_ + IHXValues* pProps) PURE; +}; +// $EndPrivate. + +/**************************************************************************** + * + * Interface: + * + * IHXPlayerNavigator + * + * Purpose: + * + * navigate player objects + * + * IID_IHXPlayerNavigator: + * + * {00000414-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXPlayerNavigator, 0x00000414, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXPlayerNavigator + +DECLARE_INTERFACE_(IHXPlayerNavigator, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /************************************************************************ + * Method: + * IHXPlayerNavigator::AddChildPlayer + * Purpose: + * Add child player to the current player + */ + STDMETHOD(AddChildPlayer) (THIS_ + IHXPlayer* pPlayer) PURE; + + /************************************************************************ + * Method: + * IHXPlayerNavigator::RemoveChildPlayer + * Purpose: + * Remove child player from the current player + */ + STDMETHOD(RemoveChildPlayer) (THIS_ + IHXPlayer* pPlayer) PURE; + + /************************************************************************ + * Method: + * IHXPlayerNavigator::GetNumChildPlayer + * Purpose: + * Get number of the child players + */ + STDMETHOD_(UINT16, GetNumChildPlayer) (THIS) PURE; + + /************************************************************************ + * Method: + * IHXPlayerNavigator::GetChildPlayer + * Purpose: + * Get Nth child player + */ + STDMETHOD(GetChildPlayer) (THIS_ + UINT16 uPlayerIndex, + REF(IHXPlayer*) pPlayer) PURE; + + /************************************************************************ + * Method: + * IHXPlayerNavigator::SetParentPlayer + * Purpose: + * Set the parent player + */ + STDMETHOD(SetParentPlayer) (THIS_ + IHXPlayer* pPlayer) PURE; + + /************************************************************************ + * Method: + * IHXPlayerNavigator::RemoveParentPlayer + * Purpose: + * Remove the parent player + */ + STDMETHOD(RemoveParentPlayer) (THIS_ + IHXPlayer* pPlayer) PURE; + + /************************************************************************ + * Method: + * IHXPlayerNavigator::GetParentPlayer + * Purpose: + * Get the parent player + */ + STDMETHOD(GetParentPlayer) (THIS_ + REF(IHXPlayer*) pPlayer) PURE; +}; + +// $Private: +/**************************************************************************** + * + * Interface: + * + * IHXPersistentComponentManager + * + * Purpose: + * + * persistent component manager + * + * IID_IHXPersistentComponentManager + * + * {00000415-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXPersistentComponentManager, 0x00000415, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXPersistentComponentManager + +DECLARE_INTERFACE_(IHXPersistentComponentManager, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /************************************************************************ + * Method: + * IHXPersistentComponentManager::CreatePersistentComponent + * Purpose: + * create persistent component + */ + STDMETHOD(CreatePersistentComponent) (THIS_ + REF(IHXPersistentComponent*) pPersistentComponent) PURE; + + + /************************************************************************ + * Method: + * IHXPersistentComponentManager::AddPersistentComponent + * Purpose: + * add persistent component + */ + STDMETHOD(AddPersistentComponent) (THIS_ + IHXPersistentComponent* pPersistentComponent) PURE; + + /************************************************************************ + * Method: + * IHXPersistentComponentManager::RemovePersistentComponent + * Purpose: + * remove persistent component + */ + STDMETHOD(RemovePersistentComponent) (THIS_ + UINT32 ulPersistentComponentID) PURE; + + /************************************************************************ + * Method: + * IHXPersistentComponentManager::GetPersistentComponentInfo + * Purpose: + * get persistent component information + */ + STDMETHOD(GetPersistentComponent) (THIS_ + UINT32 ulPersistentComponentID, + REF(IHXPersistentComponent*) pPersistentComponent) PURE; + + /************************************************************************ + * Method: + * IHXPersistentComponentManager::AttachPersistentComponentLayout + * Purpose: + * get persistent component information + */ + STDMETHOD(AttachPersistentComponentLayout) (THIS_ + IUnknown* pLSG, + IHXValues* pProps) PURE; +}; + +/**************************************************************************** + * + * Interface: + * + * IHXPersistentComponent + * + * Purpose: + * + * persistent component + * + * IID_IHXPersistentComponent + * + * {00000416-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXPersistentComponent, 0x00000416, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXPersistentComponent + +DECLARE_INTERFACE_(IHXPersistentComponent, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /************************************************************************ + * Method: + * IHXPersistentComponent::Init + * Purpose: + * initialize persistent component + */ + STDMETHOD(Init) (THIS_ + IHXPersistentRenderer* pPersistentRenderer) PURE; + + /************************************************************************ + * Method: + * IHXPersistentComponent::AddRendererAdviseSink + * Purpose: + * add renderer advise sink + */ + STDMETHOD(AddRendererAdviseSink) (THIS_ + IHXRendererAdviseSink* pSink) PURE; + + /************************************************************************ + * Method: + * IHXPersistentComponent::RemoveRendererAdviseSink + * Purpose: + * remove renderer advise sink + */ + STDMETHOD(RemoveRendererAdviseSink) (THIS_ + IHXRendererAdviseSink* pSink) PURE; + + /************************************************************************ + * Method: + * IHXPersistentComponent::AddGroupSink + * Purpose: + * add renderer advise sink + */ + STDMETHOD(AddGroupSink) (THIS_ + IHXGroupSink* pSink) PURE; + + /************************************************************************ + * Method: + * IHXPersistentComponent::RemoveGroupSink + * Purpose: + * remove renderer advise sink + */ + STDMETHOD(RemoveGroupSink) (THIS_ + IHXGroupSink* pSink) PURE; + + /************************************************************************ + * Method: + * IHXPersistentComponent::GetPersistentRenderer + * Purpose: + * get persistent renderer + */ + STDMETHOD(GetPersistentRenderer) (THIS_ + REF(IHXPersistentRenderer*) pPersistentRenderer) PURE; + + /************************************************************************ + * Method: + * IHXPersistentComponent::GetPersistentProperties + * Purpose: + * get persistent component properties + */ + STDMETHOD(GetPersistentProperties) (THIS_ + REF(IHXValues*) pProperties) PURE; +}; + +/**************************************************************************** + * + * Interface: + * + * IID_IHXClientStatisticsGranularity + * + * Purpose: + * + * Enables users to set the Granularity at which statistics are + * gathered. This allows machines under high load to still run + * efficiently. + * + * + * IID_IHXClientStatisticsGranularity + * + * {00000416-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXClientStatisticsGranularity, 0x00000417, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXClientStatisticsGranularity + +DECLARE_INTERFACE_(IHXClientStatisticsGranularity, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /************************************************************************ + * Method: + * IHXClientStatisticsGranularity::SetStatsGranularity + * Purpose: + * Set the granularity + */ + STDMETHOD(SetStatsGranularity) (THIS_ ULONG32 ulGranularity) PURE; +}; + +// $EndPrivate. + +// $Private: + +/**************************************************************************** + * + * Interface: + * + * IID_IHXSourceBufferingStats + * + * Purpose: + * + * Enables users to get the current buffering status of the + * given source. + * + * + * IID_IHXSourceBufferingStats + * + * {00000418-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXSourceBufferingStats, 0x00000418, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXSourceBufferingStats + +DECLARE_INTERFACE_(IHXSourceBufferingStats, IUnknown) +{ + /************************************************************************ + * Method: + * IHXSourceBufferingStats::GetCurrentBuffering + * Purpose: + * Get the current buffering information + */ + + STDMETHOD(GetCurrentBuffering) (THIS_ UINT16 uStreamNumber, + REF(INT64) llLowestTimestamp, + REF(INT64) llHighestTimestamp, + REF(UINT32) ulNumBytes, + REF(HXBOOL) bDone) PURE; +}; + + +/**************************************************************************** + * + * Interface: + * + * IID_IHXSourceBufferingStats2 + * + * Purpose: + * + * Enables users to get the current buffering status of the + * given source. + * + * + * IID_IHXSourceBufferingStats2 + * + * {00000418-0901-11d1-8B06-00A024406D5A} + * + */ +DEFINE_GUID(IID_IHXSourceBufferingStats2, 0x00000418, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x5a); + +#undef INTERFACE +#define INTERFACE IHXSourceBufferingStats2 + +DECLARE_INTERFACE_(IHXSourceBufferingStats2, IHXSourceBufferingStats) +{ + /************************************************************************ + * Method: + * IHXSourceBufferingStats2::GetCurrentBuffering + * Purpose: + * Get the amount of buffering in the transport + */ + STDMETHOD(GetCurrentBuffering) (THIS_ UINT16 uStreamNumber, + REF(INT64) llLowestTimestamp, + REF(INT64) llHighestTimestamp, + REF(UINT32) ulNumBytes, + REF(HXBOOL) bDone) PURE; + + /************************************************************************ + * Method: + * IHXSourceBufferingStats2::GetTotalBuffering + * Purpose: + * Get the total amount of data buffered for a stream. + * This includes what is in the transport buffer and in + * the renderer + */ + + STDMETHOD(GetTotalBuffering) (THIS_ UINT16 uStreamNumber, + REF(INT64) llLowestTimestamp, + REF(INT64) llHighestTimestamp, + REF(UINT32) ulNumBytes, + REF(HXBOOL) bDone) PURE; +}; + +// $EndPrivate. + +/**************************************************************************** + * + * Interface: + * + * IID_IHXSourceLatencyStats + * + * Purpose: + * + * + * + * IHXSourceLatencyStats + * + * {7A4D7872-E5A9-11D8-ABE7-000A95BEFE6C} + * + */ +DEFINE_GUID(IID_IHXSourceLatencyStats, 0x7A4D7872, 0xE5A9, 0x11D8, 0xAB, 0xE7, 0x00, + 0x0A, 0x95, 0xBE, 0xFE, 0x6C); + +#undef INTERFACE +#define INTERFACE IHXSourceLatencyStats + +DECLARE_INTERFACE_(IHXSourceLatencyStats, IUnknown) +{ + /************************************************************************ + * Method: + * IHXSourceLatencyStats::SetLiveSyncOffset + * Purpose: + * set the live sync start time + */ + STDMETHOD(SetLiveSyncOffset) (THIS_ UINT32 ulLiveSyncStartTime) PURE; + + /************************************************************************ + * Method: + * IHXSourceLatencyStats::NewPacketTimeStamp + * Purpose: + * call this for each arriving packet! + */ + + STDMETHOD(NewPacketTimeStamp) (THIS_ UINT32 ulDueTimeStamp) PURE; + + /************************************************************************ + * Method: + * IHXSourceLatencyStats::GetLatencyStats + * Purpose: + * call this for each arriving packet! + */ + + STDMETHOD(GetLatencyStats) (THIS_ + REF(UINT32) ulAverageLatency, + REF(UINT32) ulMinimumLatency, + REF(UINT32) ulMaximumLatency ) PURE; + + + /************************************************************************ + * Method: + * IHXSourceLatencyStats::ResetLatencyStats + * Purpose: + * call this for each arriving packet! + */ + + STDMETHOD(ResetLatencyStats) (THIS_ ) PURE; + + +}; + + +// $Private: + +/**************************************************************************** + * + * Interface: + * + * IID_IHXPlayerPresentation + * + * Purpose: + * + * Control over the player's current presentation + * + * IID_IHXPlayerPresentation + * + * {6DE011A7-EF05-417b-9367-6FE0E54302D3} + * + */ +DEFINE_GUID(IID_IHXPlayerPresentation, 0x6de011a7, 0xef05, 0x417b, 0x93, 0x67, 0x6f, 0xe0, 0xe5, 0x43, 0x2, 0xd3); + +#undef INTERFACE +#define INTERFACE IHXPlayerPresentation + +DECLARE_INTERFACE_(IHXPlayerPresentation, IUnknown) +{ + /************************************************************************ + * Method: + * IHXPlayerPresentation::ClosePresentation + * Purpose: + * Call this method to close the player's current presentation. This will free + * all resources associated with the current presentation. + */ + STDMETHOD(ClosePresentation) (THIS) PURE; +}; + +// $EndPrivate. + + + +// $Private: + +/**************************************************************************** + * + * Interface: + * + * IID_IHXCoreMutex + * + * Purpose: + * + * Access the core mutex + * + * IID_IHXCoreMutex + * + * {6DE011A7-EF05-417b-9367-6FE0E44404D4} + * + */ +DEFINE_GUID(IID_IHXCoreMutex, 0x6de011a7, 0xef05, 0x417b, 0x93, 0x67, 0x6f, 0xe0, 0xe4, 0x44, 0x4, 0xd4); + +#undef INTERFACE +#define INTERFACE IHXCoreMutex + +DECLARE_INTERFACE_(IHXCoreMutex, IUnknown) +{ + /************************************************************************ + * Method: + * IHXCoreMutex::LockCoreMutex + * Purpose: + * Call this method to lock the client engine's core mutex. + */ + STDMETHOD(LockCoreMutex) (THIS) PURE; + + /************************************************************************ + * Method: + * IHXCoreMutex::UnlockCoreMutex + * Purpose: + * Call this method to unlock the client engine's core mutex. + */ + STDMETHOD(UnlockCoreMutex) (THIS) PURE; +}; + + +/**************************************************************************** + * + * Interface: + * + * IID_IHXMacBlitMutex + * + * Purpose: + * + * Access the Mac blitting mutex + * + * Used for all Mac drawing, in both the core and the tlc. This + * mutex prevents quickdraw (and/or the image compression manager) + * from re-entering which causes crashes on OS X. + * + * IID_IHXCoreMutex + * + * {294e6de4-fbc6-4c06-bb94-95a969373b4d} + * + */ +DEFINE_GUID(IID_IHXMacBlitMutex, 0x294e6de4, 0xfbc6, 0x4c06, 0xbb, 0x94, 0x95, 0xa9, 0x69, 0x37, 0x3b, 0x4d); + +#undef INTERFACE +#define INTERFACE IHXMacBlitMutex + +DECLARE_INTERFACE_(IHXMacBlitMutex, IUnknown) +{ + /************************************************************************ + * Method: + * IHXMacBlitMutex::LockMacBlitMutex + * Purpose: + * Call this method to lock the client engine's core mutex. + */ + STDMETHOD(LockMacBlitMutex) (THIS) PURE; + + /************************************************************************ + * Method: + * IHXMacBlitMutex::UnlockMacBlitMutex + * Purpose: + * Call this method to unlock the client engine's core mutex. + */ + STDMETHOD(UnlockMacBlitMutex) (THIS) PURE; +}; + +// $EndPrivate. + + + +#endif /* _HXCORE_H_ */ + diff --git a/amarok/src/engine/helix/helix-sp/helix-include/common/include/hxengin.h b/amarok/src/engine/helix/helix-sp/helix-include/common/include/hxengin.h new file mode 100644 index 00000000..cc4d1b49 --- /dev/null +++ b/amarok/src/engine/helix/helix-sp/helix-include/common/include/hxengin.h @@ -0,0 +1,2812 @@ + +/* + * + * This software is released under the provisions of the GPL version 2. + * see file "COPYING". If that file is not available, the full statement + * of the license can be found at + * + * http://www.fsf.org/licensing/licenses/gpl.txt + * + * Portions Copyright (c) 1995-2004 RealNetworks, Inc. All Rights Reserved. + * + */ + +#ifndef _HXENGIN_H_ +#define _HXENGIN_H_ + +#include "hxcom.h" +#include "ihxpckts.h" + +/* + * Forward declarations of some interfaces used here-in. + */ + +typedef _INTERFACE IHXValues IHXValues; +typedef _INTERFACE IHXBuffer IHXBuffer; +typedef _INTERFACE IHXCallback IHXCallback; +typedef _INTERFACE IHXScheduler IHXScheduler; +typedef _INTERFACE IHXTCPResponse IHXTCPResponse; +typedef _INTERFACE IHXLBoundTCPSocket IHXLBoundTCPSocket; +typedef _INTERFACE IHXTCPSocket IHXTCPSocket; +typedef _INTERFACE IHXListenResponse IHXListenResponse; +typedef _INTERFACE IHXListenSocket IHXListenSocket; +typedef _INTERFACE IHXNetworkServices IHXNetworkServices; +typedef _INTERFACE IHXNetworkServices2 IHXNetworkServices2; +typedef _INTERFACE IHXUDPResponse IHXUDPResponse; +typedef _INTERFACE IHXUDPSocket IHXUDPSocket; +typedef _INTERFACE IHXResolver IHXResolver; +typedef _INTERFACE IHXResolverResponse IHXResolverResponse; +typedef _INTERFACE IHXInterruptSafe IHXInterruptSafe; +typedef _INTERFACE IHXAsyncIOSelection IHXAsyncIOSelection; +typedef _INTERFACE IHXUDPMulticastInit IHXUDPMulticastInit; +typedef _INTERFACE IHXInterruptState IHXInterruptState; +typedef _INTERFACE IHXOptimizedScheduler IHXOptimizedScheduler; +// $Private: +typedef _INTERFACE IHXThreadSafeScheduler IHXThreadSafeScheduler; +typedef _INTERFACE IHXBufferedSocket IHXBufferedSocket; +typedef _INTERFACE IHXNetInterfaces IHXNetInterfaces; +typedef _INTERFACE IHXNetInterfacesAdviseSink IHXNetInterfacesAdviseSink; +// $EndPrivate. +typedef _INTERFACE IHXNetworkInterfaceEnumerator IHXNetworkInterfaceEnumerator; +typedef _INTERFACE IHXUDPConnectedSocket IHXUDPConnectedSocket; +typedef _INTERFACE IHXAutoBWDetection IHXAutoBWDetection; +typedef _INTERFACE IHXAutoBWDetectionAdviseSink IHXAutoBWDetectionAdviseSink; +typedef _INTERFACE IHXAutoBWCalibration IHXAutoBWCalibration; +typedef _INTERFACE IHXAutoBWCalibrationAdviseSink IHXAutoBWCalibrationAdviseSink; +typedef _INTERFACE IHXPreferredTransport IHXPreferredTransport; + +/* + * Address flags starting with PNR are deprecated. + */ +#define HXR_INADDR_ANY (UINT32)0x00000000 //THIS FLAG IS DEPRECATED +#define HX_INADDR_ANY (UINT32)0x00000000 + +/* + * 255.255.255.254 + * + * Bind to all ports in IPBindings list from + * server config. + */ +#define HXR_INADDR_IPBINDINGS (UINT32)0xfffffffe //THIS FLAG IS DEPRECATED +#define HX_INADDR_IPBINDINGS (UINT32)0xfffffffe + + +/* Async IO Selection Type (Unix Only) */ + +#define PNAIO_READ 1 +#define PNAIO_WRITE 2 +#define PNAIO_EXCEPTION 4 + +/**************************************************************************** + * + * Interface: + * + * IHXCallback + * + * Purpose: + * + * This interface defines a simple callback which will be used in + * various interfaces such as IHXScheduler. + * + * IID_IHXCallback: + * + * {00000100-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXCallback, 0x00000100, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXCallback + +DECLARE_INTERFACE_(IHXCallback, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXCallback methods + */ + + /************************************************************************ + * Method: + * IHXCallback::Func + * Purpose: + * This is the function that will be called when a callback is + * to be executed. + */ + STDMETHOD(Func) (THIS) PURE; +}; + + +/**************************************************************************** + * + * Interface: + * + * IHXScheduler + * + * Purpose: + * + * This interface provides the user with a way of scheduling callbacks + * that will be executed at some time in the future. + * + * IID_IHXScheduler: + * + * {00000101-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXScheduler, 0x00000101, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXScheduler + +typedef ULONG32 CallbackHandle; + +typedef struct _HXTimeval +{ + UINT32 tv_sec; + UINT32 tv_usec; +} HXTimeval; + +DECLARE_INTERFACE_(IHXScheduler, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXScheduler methods + */ + + /************************************************************************ + * Method: + * IHXScheduler::RelativeEnter + * Purpose: + * Schedule a callback to be executed "ms" milliseconds from now + * This function is less percise then AbsoluteEnter and should only + * be used when accurate timing is not critical. + */ + STDMETHOD_(CallbackHandle,RelativeEnter) (THIS_ + IHXCallback* pCallback, + UINT32 ms) PURE; + + /************************************************************************ + * Method: + * IHXScheduler::AbsoluteEnter + * Purpose: + * Schedule a callback to be executed at time "tVal". + */ + STDMETHOD_(CallbackHandle,AbsoluteEnter) (THIS_ + IHXCallback* pCallback, + HXTimeval tVal) PURE; + + /************************************************************************ + * Method: + * IHXScheduler::Remove + * Purpose: + * Remove a callback from the scheduler. + */ + STDMETHOD(Remove) (THIS_ + CallbackHandle Handle) PURE; + + /************************************************************************ + * Method: + * IHXScheduler::GetCurrentSchedulerTime + * Purpose: + * Gives the current time (in the timeline of the scheduler). + */ + STDMETHOD_(HXTimeval,GetCurrentSchedulerTime) (THIS) PURE; +}; + + +/**************************************************************************** + * + * Interface: + * + * IHXTCPResponse + * + * Purpose: + * + * This is the response interface for the asynchronous TCP networking + * interface. + * + * IID_IHXTCPResponse: + * + * {00000102-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXTCPResponse, 0x00000102, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXTCPResponse + +DECLARE_INTERFACE_(IHXTCPResponse, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXTCPResponse methods + */ + + /************************************************************************ + * Method: + * IHXTCPResponse::ConnectDone + * Purpose: + * A Connect operation has been completed or an error has occurred. + */ + STDMETHOD(ConnectDone) (THIS_ + HX_RESULT status) PURE; + + /************************************************************************ + * Method: + * IHXTCPResponse::ReadDone + * Purpose: + * A Read operation has been completed or an error has occurred. + * The data is returned in the IHXBuffer. + */ + STDMETHOD(ReadDone) (THIS_ + HX_RESULT status, + IHXBuffer* pBuffer) PURE; + + /************************************************************************ + * Method: + * IHXTCPResponse::WriteReady + * Purpose: + * This is the response method for WantWrite. + * If HX_RESULT is ok, then the TCP channel is ok to Write to. + */ + STDMETHOD(WriteReady) (THIS_ + HX_RESULT status) PURE; + + /************************************************************************ + * Method: + * IHXTCPResponse::Closed + * Purpose: + * This method is called to inform you that the TCP channel has + * been closed by the peer or closed due to error. + */ + STDMETHOD(Closed) (THIS_ + HX_RESULT status) PURE; +}; + + +/**************************************************************************** + * + * Interface: + * + * IHXTCPSocket + * + * Purpose: + * + * Provides the user with an asynchronous TCP networking interface. + * + * IID_IHXTCPSocket: + * + * {00000103-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXTCPSocket, 0x00000103, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXTCPSocket + +DECLARE_INTERFACE_(IHXTCPSocket, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXTCPSocket methods + * + * Network addresses and ports are in native byte order + * + */ + + STDMETHOD(Init) (THIS_ + IHXTCPResponse* /*IN*/ pTCPResponse) PURE; + + STDMETHOD(SetResponse) (THIS_ + IHXTCPResponse* pTCPResponse) PURE; + + STDMETHOD(Bind) (THIS_ + UINT32 ulLocalAddr, + UINT16 nPort) PURE; + + /* + * pDestination is a string containing host name or dotted-ip notation + */ + STDMETHOD(Connect) (THIS_ + const char* pDestination, + UINT16 nPort) PURE; + + STDMETHOD(Read) (THIS_ + UINT16 Size) PURE; + + STDMETHOD(Write) (THIS_ + IHXBuffer* pBuffer) PURE; + + /************************************************************************ + * Method: + * IHXTCPSocket::WantWrite + * Purpose: + * This method is called when you wish to write a large amount of + * data. If you are only writing small amounts of data, you can + * just call Write (all data not ready to be transmitted will be + * buffered on your behalf). When the TCP channel is ready to be + * written to, the response interfaces WriteReady method will be + * called. + */ + STDMETHOD(WantWrite) (THIS) PURE; + + /************************************************************************ + * Method: + * IHXTCPSocket::GetForeignAddress + * Purpose: + * Returns the address of the other end of the TCP socket as a + * ULONG32 in local host order + */ + STDMETHOD(GetForeignAddress) (THIS_ + REF(ULONG32) lAddress) PURE; + + STDMETHOD(GetLocalAddress) (THIS_ + REF(ULONG32) lAddress) PURE; + + /************************************************************************ + * Method: + * IHXTCPSocket::GetForeignPort + * Purpose: + * Returns the port of the other end of the TCP socket in local + * host order. + */ + STDMETHOD(GetForeignPort) (THIS_ + REF(UINT16) port) PURE; + + STDMETHOD(GetLocalPort) (THIS_ + REF(UINT16) port) PURE; +}; + +// $Private: +/**************************************************************************** + * + * Interface: + * + * IHXTCPSecureSocket + * + * Purpose: + * + * When an IHXTCPSocket also supports this interface, + * it allows you to say it's secure so it tries to use + * SSL. + * + */ +DEFINE_GUID(IID_IHXTCPSecureSocket, 0x00000203, 0x911, 0x21d1, 0x8c, 0x4, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x54); + +#undef INTERFACE +#define INTERFACE IHXTCPSecureSocket + +DECLARE_INTERFACE_(IHXTCPSecureSocket, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXTCPSecureSocket method + */ + STDMETHOD(SetSecure) (THIS_ + HXBOOL bSecure) PURE; +}; +// $EndPrivate. + + + +// $Private: +/**************************************************************************** + * + * Interface: + * + * IHXSSL + * + * Purpose: + * + * This is the interface to an SSL library. + * + */ +DEFINE_GUID(IID_IHXSSL, 0x34e171d4, 0xa8f0, + 0x4832, 0xbc, 0x7d, 0x06, 0xdf, 0xe3, 0xae, 0x58, 0xfd); + +DECLARE_INTERFACE_(IHXSSL, IUnknown) +{ + /* + * IUnknown methods + */ + + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32, AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32, Release) (THIS) PURE; + + /* + * + */ + + STDMETHOD(Initialize) (THIS) PURE; + + STDMETHOD(Shutdown) (THIS) PURE; + + STDMETHOD(PostConnect) (THIS_ + LONG32 nSocketNumber) PURE; + + STDMETHOD_(LONG32, Read) (THIS_ + LONG32 nSocketNumber, + void* buff, + LONG32 buffLen) PURE; + + STDMETHOD_(LONG32, Write) (THIS_ + LONG32 nSocketNumber, + void* buff, + LONG32 buffLen) PURE; + + STDMETHOD(Close) (THIS_ + LONG32 nSocketNumber) PURE; + + STDMETHOD(SetCallbacks) (THIS_ + void* readCallback, + void* writeCallback, + void* closeCallback) PURE; + + +}; +// $EndPrivate. + + + + + +// $Private: +/**************************************************************************** + * + * Interface: + * + * IHXBufferedSocket + * + * Purpose: + * + * This provdies a method for doing for doing more optimal + * TCP delivery using desired packet size and writev. + * + * IID_IHXTCPSocket: + * + * {00001402-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXBufferedSocket, + 0x00001402, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXBufferedSocket + +DECLARE_INTERFACE_(IHXBufferedSocket, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXTCPSocket methods + */ + + STDMETHOD(BufferedWrite) (THIS_ + IHXBuffer* pBuffer) PURE; + + STDMETHOD(FlushWrite) (THIS) PURE; + + STDMETHOD(SetDesiredPacketSize) (THIS_ + UINT32 ulPacketSize) PURE; + +}; +// $EndPrivate. + + +/**************************************************************************** + * + * Interface: + * + * IHXListenResponse + * + * Purpose: + * + * This is the response interface for the asynchronous TCP listening + * socket interface. + * + * IID_IHXListenResponse: + * + * {00000104-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXListenResponse, 0x00000104, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXListenResponse + +DECLARE_INTERFACE_(IHXListenResponse, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXListenResponse methods + */ + + STDMETHOD(NewConnection) (THIS_ + HX_RESULT status, + IHXTCPSocket* pTCPSocket) PURE; +}; + +/**************************************************************************** + * + * Interface: + * + * IHXListenSocket + * + * Purpose: + * + * This interfaces allows you to asynchronously listen on a port for + * TCP connections. + * + * IID_IHXListenSocket: + * + * {00000105-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXListenSocket, 0x00000105, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXListenSocket + +DECLARE_INTERFACE_(IHXListenSocket, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXListenSocket methods + */ + + STDMETHOD(Init) (THIS_ + UINT32 ulLocalAddr, + UINT16 port, + IHXListenResponse* /*IN*/ pListenResponse + ) PURE; +}; + + +/**************************************************************************** + * + * Interface: + * + * IHXNetworkServices + * + * Purpose: + * + * This is a factory interface for the various types of networking + * interfaces described above. + * + * IID_IHXNetworkServices: + * + * {00000106-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXNetworkServices, 0x00000106, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXNetworkServices + +DECLARE_INTERFACE_(IHXNetworkServices, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXNetworkServices methods + */ + + /************************************************************************ + * Method: + * IHXNetworkServices::CreateTCPSocket + * Purpose: + * Create a new TCP socket. + */ + STDMETHOD(CreateTCPSocket) (THIS_ + IHXTCPSocket** /*OUT*/ ppTCPSocket) PURE; + + /************************************************************************ + * Method: + * IHXNetworkServices::CreateUDPSocket + * Purpose: + * Create a new UDP socket. + */ + STDMETHOD(CreateUDPSocket) (THIS_ + IHXUDPSocket** /*OUT*/ ppUDPSocket) PURE; + + /************************************************************************ + * Method: + * IHXNetworkServices::CreateListenSocket + * Purpose: + * Create a new TCP socket that will listen for connections on a + * particular port. + */ + STDMETHOD(CreateListenSocket) (THIS_ + IHXListenSocket** /*OUT*/ ppListenSocket + ) PURE; + + /************************************************************************ + * Method: + * IHXNetworkServices::CreateResolver + * Purpose: + * Create a new resolver that can lookup host names + */ + STDMETHOD(CreateResolver) (THIS_ + IHXResolver** /*OUT*/ ppResolver) PURE; +}; + + +/**************************************************************************** + * + * Interface: + * + * IHXNetworkServices2 + * + * Purpose: + * + * This is a factory interface for the various types of networking + * interfaces described above. + * + * IID_IHXNetworkServices: + * + * {17951551-5683-11d3-B6BA-00C0F031C237} + * + */ + +// {17951551-5683-11d3-B6BA-00C0F031C237} +DEFINE_GUID(IID_IHXNetworkServices2, 0x17951551, 0x5683, 0x11d3, 0xb6, 0xba, 0x0, 0xc0, 0xf0, 0x31, 0xc2, 0x37); + +#undef INTERFACE +#define INTERFACE IHXNetworkServices2 + +DECLARE_INTERFACE_(IHXNetworkServices2, IHXNetworkServices) +{ + /************************************************************************ + * Method: + * IHXNetworkServices2::CreateLBoundTCPSocket + * Purpose: + * Create a new local bound TCP socket. + */ + STDMETHOD(CreateLBoundTCPSocket) (THIS_ + IHXTCPSocket** /*OUT*/ ppTCPSocket) PURE; +}; + + + +/**************************************************************************** + * + * Interface: + * + * IHXUDPResponse + * + * Purpose: + * + * This is the response interface for the asynchronous UDP networking + * interface. + * + * IID_IHXUDPResponse: + * + * {00000107-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXUDPResponse, 0x00000107, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXUDPResponse + +DECLARE_INTERFACE_(IHXUDPResponse, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXUDPResponse methods + */ + + STDMETHOD(ReadDone) (THIS_ + HX_RESULT status, + IHXBuffer* pBuffer, + ULONG32 ulAddr, + UINT16 nPort) PURE; +}; + + +/**************************************************************************** + * + * Interface: + * + * IHXUDPSocket + * + * Purpose: + * + * Provides the user with an asynchronous UDP networking interface. + * + * IID_IHXUDPSocket: + * + * {00000108-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXUDPSocket, 0x00000108, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXUDPSocket + +DECLARE_INTERFACE_(IHXUDPSocket, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXUDPSocket methods + * + * Network addresses and ports are in native byte order + */ + + STDMETHOD(Init) (THIS_ + ULONG32 ulAddr, + UINT16 nPort, + IHXUDPResponse* pUDPResponse) PURE; + + STDMETHOD(Bind) (THIS_ + UINT32 ulLocalAddr, + UINT16 nPort) PURE; + + STDMETHOD(Read) (THIS_ + UINT16 Size) PURE; + + STDMETHOD(Write) (THIS_ + IHXBuffer* pBuffer) PURE; + + STDMETHOD(WriteTo) (THIS_ + ULONG32 ulAddr, + UINT16 nPort, + IHXBuffer* pBuffer) PURE; + + STDMETHOD(GetLocalPort) (THIS_ + REF(UINT16) port) PURE; + + STDMETHOD(JoinMulticastGroup) (THIS_ + ULONG32 ulMulticastAddr, + ULONG32 ulInterfaceAddr) PURE; + + STDMETHOD(LeaveMulticastGroup) (THIS_ + ULONG32 ulMulticastAddr, + ULONG32 ulInterfaceAddr) PURE; +}; + + +/**************************************************************************** + * + * Interface: + * + * IHXResolver + * + * Purpose: + * + * This interface allows you to asynchronously resolve hostnames. + * + * IID_IHXResolver: + * + * {00000109-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXResolver, 0x00000109, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXResolver + +DECLARE_INTERFACE_(IHXResolver, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXResolver methods + */ + + STDMETHOD(Init) (THIS_ + IHXResolverResponse* pResponse) PURE; + + STDMETHOD(GetHostByName) (THIS_ + const char* pHostName) PURE; +}; + + +/**************************************************************************** + * + * Interface: + * + * IHXResolverResponse + * + * Purpose: + * + * This is the response interface for the asynchronous DNS hostname + * resolver. + * + * IID_IHXResolverResponse: + * + * {0000010A-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXResolverResponse, 0x0000010A, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXResolverResponse + +DECLARE_INTERFACE_(IHXResolverResponse, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXResolverResponse methods + */ + + STDMETHOD(GetHostByNameDone) (THIS_ + HX_RESULT status, + ULONG32 ulAddr) PURE; +}; + + +/**************************************************************************** + * + * Interface: + * + * IHXInterruptSafe + * + * Purpose: + * + * This interface is used in Macintosh implementations of callback + * functions, renderers, etc... to determine if interrupt time execution + * is supported. If this interface is not implemented then it is assumed + * that interrupt time execution is NOT supported. There are restrictions + * on what may be executed at interrupt time; please consult the Macintosh + * Deferred Task Manager tech notes from Apple. + * + * IID_IHXInterruptSafe: + * + * {0000010B-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXInterruptSafe, 0x0000010B, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXInterruptSafe + +DECLARE_INTERFACE_(IHXInterruptSafe, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXInterruptSafe methods + */ + + /************************************************************************ + * Method: + * IHXInterruptSafe::IsInterruptSafe + * Purpose: + * This is the function that will be called to determine if + * interrupt time execution is supported. + */ + STDMETHOD_(HXBOOL,IsInterruptSafe) (THIS) PURE; +}; + + +/**************************************************************************** + * + * Interface: + * + * IHXAsyncIOSelection + * + * Purpose: + * + * This interface is implemented by the server/player context on Unix + * platforms. This interface allows your plugin to get callbacks based + * I/O events that are normally handled by select(). This interface + * allows you to setup callbacks which will be executed when a file + * descriptor is ready for reading, writing, or has an exception. + * + * IID_IHXAsyncIOSelection: + * + * {0000010C-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXAsyncIOSelection, 0x0000010C, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXAsyncIOSelection + +DECLARE_INTERFACE_(IHXAsyncIOSelection, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXAsyncIOSelection methods + */ + + /************************************************************************ + * Method: + * IHXAsyncIOSelection::Add + * Purpose: + * This function will allow you to receive a callback when the + * given descriptor is ready for read, write, or has an + * exception. This function is only available on Unix, and is + * intended to replace the functionality of select(). + */ + STDMETHOD(Add) (THIS_ + IHXCallback* pCallback, + INT32 lFileDescriptor, + UINT32 ulType) PURE; + + /************************************************************************ + * Method: + * IHXAsyncIOSelection::Remove + * Purpose: + * This function will allow you remove the callback associated + * with the given descriptor from the event handler. + * This function is only available on Unix, and is intended to + * replace the functionality of select(). + */ + STDMETHOD(Remove) (THIS_ + INT32 lFileDescriptor, + UINT32 ulType) PURE; +}; + + +/**************************************************************************** + * + * Interface: + * + * IHXUDPMulticastInit + * + * Purpose: + * + * Provides the user with a way to set the TTL for outgoing multicast + * UDP packets. Usually shared with IHXUDPSocket. + * + * IID_IHXUDPMulticastInit: + * + * {0000010D-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXUDPMulticastInit, 0x0000010D, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXUDPMulticastInit + +DECLARE_INTERFACE_(IHXUDPMulticastInit, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXUDPMulticastInit methods + * + */ + + /************************************************************************ + * Method: + * IHXUDPMulticastInit::InitMulticast + * Purpose: + * This function will set the TTL (time to live) for the UDP socket + * so it can be used as a multicast socket, sending packets across + * the number of routers specified in the ulTTL parameter. + */ + + STDMETHOD(InitMulticast) (THIS_ + UINT8 chTTL) PURE; +}; + +/**************************************************************************** + * + * Interface: + * + * IHXInterruptState + * + * Purpose: + * + * This interface is used in Macintosh implementations to inform the + * the client engine when entering & leaving an interupt task. It is + * also used to determine if it is currently at interrupt time. + * Please consult the Macintosh Deferred Task Manager tech notes from Apple + * for information on interrupt tasks. + * + * IID_IHXInterruptState: + * + * {0000010E-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXInterruptState, 0x0000010E, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXInterruptState + +DECLARE_INTERFACE_(IHXInterruptState, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXInterruptState methods + */ + + /************************************************************************ + * Method: + * IHXInterruptState::AtInterruptTime + * Purpose: + * This function is called to determine if we are currently at + * interrupt task time. + */ + STDMETHOD_(HXBOOL,AtInterruptTime) (THIS) PURE; + + /************************************************************************ + * Method: + * IHXInterruptState::EnterInterruptState + * Purpose: + * This function is called when starting a deferred/interrupt task + */ + STDMETHOD(EnterInterruptState) (THIS) PURE; + + /************************************************************************ + * Method: + * IHXInterruptState::LeaveInterruptState + * Purpose: + * This function is called when leaving a deferred/interrupt task + */ + STDMETHOD(LeaveInterruptState) (THIS) PURE; + + /************************************************************************ + * Method: + * IHXInterruptState::EnableInterrupt + * Purpose: + * This function can be called to enable/disable interrupt time + * processsing + */ + STDMETHOD(EnableInterrupt) (THIS_ + HXBOOL bEnable) PURE; + + /************************************************************************ + * Method: + * IHXInterruptState::IsInterruptEnabled + * Purpose: + * This function can be called to find if the core is currently + * interrupt enabled. + */ + STDMETHOD_(HXBOOL, IsInterruptEnabled) (THIS) PURE; +}; + + +/**************************************************************************** + * + * Interface: + * + * IHXOptimizedScheduler + * + * Purpose: + * + * This interface provides the user with a way of scheduling callbacks + * that will be executed at some time in the future. + * + * This interface should ONLY be used if you need accurately timed + * callbacks. These callbacks should be efficient and should not consume + * much time/CPU. This is not a thread safe interface. The user has to + * take care of synchronization in their callbacks. + * + * IID_IHXOptimizedScheduler: + * + * {0000010F-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXOptimizedScheduler, 0x0000010F, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXOptimizedScheduler + +DECLARE_INTERFACE_(IHXOptimizedScheduler, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXOptimizedScheduler methods + */ + + /************************************************************************ + * Method: + * IHXOptimizedScheduler::RelativeEnter + * Purpose: + * Schedule a callback to be executed "ms" milliseconds from now + * This function is less percise then AbsoluteEnter and should only + * be used when accurate timing is not critical. + */ + STDMETHOD_(CallbackHandle,RelativeEnter) (THIS_ + IHXCallback* pCallback, + UINT32 ms) PURE; + + /************************************************************************ + * Method: + * IHXOptimizedScheduler::AbsoluteEnter + * Purpose: + * Schedule a callback to be executed at time "tVal". + */ + STDMETHOD_(CallbackHandle,AbsoluteEnter) (THIS_ + IHXCallback* pCallback, + HXTimeval tVal) PURE; + + /************************************************************************ + * Method: + * IHXOptimizedScheduler::Remove + * Purpose: + * Remove a callback from the scheduler. + */ + STDMETHOD(Remove) (THIS_ + CallbackHandle Handle) PURE; + + /************************************************************************ + * Method: + * IHXOptimizedScheduler::GetCurrentSchedulerTime + * Purpose: + * Gives the current time (in the timeline of the scheduler). + */ + STDMETHOD_(HXTimeval,GetCurrentSchedulerTime) (THIS) PURE; +}; + +/**************************************************************************** + * + * Interface: + * + * IHXThreadSafeScheduler + * + * Purpose: + * + * This interface provides the user with a way of scheduling callbacks + * that will be executed at some time in the future. This is identical + * to IHXScheduler except the scheduler events are considered thread-safe. + * + * IID_IHXThreadSafeScheduler: + * + * {00000120-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXThreadSafeScheduler, 0x00000120, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXThreadSafeScheduler + +DECLARE_INTERFACE_(IHXThreadSafeScheduler, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXThreadSafeScheduler methods + */ + + /************************************************************************ + * Method: + * IHXThreadSafeScheduler::RelativeEnter + * Purpose: + + * Schedule a callback to be executed "ms" milliseconds from now + * This function is less percise then AbsoluteEnter and should only + * be used when accurate timing is not critical. + */ + STDMETHOD_(CallbackHandle,RelativeEnter) (THIS_ + IHXCallback* pCallback, + UINT32 ms) PURE; + + /************************************************************************ + * Method: + * IHXThreadSafeScheduler::AbsoluteEnter + * Purpose: + * Schedule a callback to be executed at time "tVal". + */ + STDMETHOD_(CallbackHandle,AbsoluteEnter) (THIS_ + IHXCallback* pCallback, + HXTimeval tVal) PURE; + + /************************************************************************ + * Method: + * IHXThreadSafeScheduler::Remove + * Purpose: + * Remove a callback from the scheduler. + */ + STDMETHOD(Remove) (THIS_ + CallbackHandle Handle) PURE; +}; + +/**************************************************************************** + * + * Interface: + * + * IHXProcessEntryPoint + * + * Purpose: + * + * This interface is the entry point for an IHXProcess + * + * IID_IHXProcessEntryPoint + * + * {00000123-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXProcessEntryPoint, 0x00000123, 0x901, 0x11d1, 0x8b, 0x6, + 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXProcessEntryPoint + +DECLARE_INTERFACE_(IHXProcessEntryPoint, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + STDMETHOD(Func) (THIS_ + IUnknown* pContext) PURE; +}; + +/**************************************************************************** + * + * Interface: + * + * IHXProcess + * + * Purpose: + * + * This interface allows you to create new server processes and specify + * an entry point. It is queried off the context. + * + * IID_IHXProcess + * + * {00000122-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXProcess, 0x00000122, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXProcess + +DECLARE_INTERFACE_(IHXProcess, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + STDMETHOD(Start) (THIS_ + const char* pProcessName, + IHXProcessEntryPoint* pEntryPoint) PURE; +}; + +/**************************************************************************** + * + * Interface: + * + * IHXLoadBalancedListen + * + * Purpose: + * + * This interface is queried off of IHXListenSocket. It allows + * a plugin to specify that it wants the server to load balance + * multiple instances of itself. The server will instantiate multiple + * instances of the plugin as needed based on socket / descriptor limits. + * Each plugin instance should attempt to listen on the same port as + * other instances (they will share the port). + * + * IID_IHXLoadBalancedListen: + * + * {00000110-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXLoadBalancedListen, 0x00000110, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXLoadBalancedListen + +DECLARE_INTERFACE_(IHXLoadBalancedListen, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXLoadBalancedListen methods + */ + + /************************************************************************ + * Method: + * IHXLoadBalancedListen::SetID + * Purpose: + * This function set's the unique ID for this listen socket. This + * ID is used to determine whether or not different instances of + * a plugin trying to listen on a single port are actually the + * same plugin. Without this function, it would be possible for + * two completely different plugins to listen on the same port using + * the load balanced listener. + */ + STDMETHOD(SetID) (THIS_ + REFIID ID) PURE; + + /************************************************************************ + * Method: + * IHXLoadBalancedListen::SetReserveLimit + * Purpose: + * Sets the reserve limit for descriptors / sockets. If less + * than reserve limit descriptors / sockets are left then a new + * instance of the plugin will be created. + */ + STDMETHOD(SetReserveLimit) (THIS_ + UINT32 ulDescriptors, + UINT32 ulSockets) PURE; +}; + + +/**************************************************************************** + * + * Interface: + * + * IHXOverrideDefaultServices + * + * Purpose: + * + * This interface is queried off of the context. It allows + * a plugin to override any default services provided by the G2 system. + * Currently, it is supported only on the client side. + * You may currently override IHXNetworkServices using this interface + * You can use the same interface to later restore back the overridden services. + * This is done by calling the same OverrideServices() function with the + * original service QIed before the initial override. + * + * IID_IHXOverrideDefaultServices: + * + * {00000111-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXOverrideDefaultServices, 0x00000111, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXOverrideDefaultServices + +DECLARE_INTERFACE_(IHXOverrideDefaultServices, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXOverrideDefaultServices methods + */ + + /************************************************************************ + * Method: + * IHXOverrideDefaultServices::OverrideServices + * Purpose: + * Override default services provided by the G2 system. + * + */ + STDMETHOD(OverrideServices) (THIS_ + IUnknown* pContext) PURE; +}; + +typedef enum _HX_SOCKET_OPTION +{ + HX_SOCKOPT_REUSE_ADDR, + HX_SOCKOPT_REUSE_PORT, + HX_SOCKOPT_BROADCAST, + HX_SOCKOPT_SET_RECVBUF_SIZE, + HX_SOCKOPT_SET_SENDBUF_SIZE, + HX_SOCKOPT_MULTICAST_IF, + HX_SOCKOPT_IP_TOS +} HX_SOCKET_OPTION; + +/**************************************************************************** + * + * Interface: + * + * IHXSetSocketOption + * + * Purpose: + * + * Set sockt option + * + * IID_IHXSetSocketOption: + * + * IID_IHXSetSocketOption: {00000114-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXSetSocketOption, + 0x00000114, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXSetSocketOption +DECLARE_INTERFACE_(IHXSetSocketOption, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXListenSocket methods + */ + + STDMETHOD(SetOption) (THIS_ + HX_SOCKET_OPTION option, + UINT32 ulValue) PURE; +}; + +#define HX_THREADSAFE_METHOD_FF_GETPACKET 0x00000001 +/* + * FileFormat::GetPacket() only calls: + * CCF->CI(Buffer), CCF->CI(Packet), CCF->CI(Values), *Alloc, *Free, + * FS->Read(), FS->Close(), FS->Seek(), + * FFR->PacketReady(), FFR->StreamDone() + * Context->Scheduler->*, + * CCF->CI(Mutex), Mutex->* + * Context->ErrorMessages + * + * XXXSMPNOW + */ + +#define HX_THREADSAFE_METHOD_FS_READ 0x00000002 +/* + * FileSystem::Read()/Seek()/Close() only calls: + * CCF->CI(Buffer), CCF->CI(Packet), CCF->CI(Values), *Alloc, *Free, + * FS->Read(), FS->Close(), FS->Seek(), + * Context->Scheduler->*, + * CCF->CI(Mutex), Mutex->* + * Context->ErrorMessages + * + * XXXSMPNOW + */ +#define HX_THREADSAFE_METHOD_FSR_READDONE 0x00000004 +/* + * FileFormat::ReadDone()/SeekDone()/CloseDone() only calls: + * CCF->CI(Buffer), CCF->CI(Packet), CCF->CI(Values), *Alloc, *Free, + * FS->Read(), FS->Close(), FS->Seek(), + * FFR->PacketReady(), FFR->StreamDone() + * Context->Scheduler->*, + * CCF->CI(Mutex), Mutex->* + * Context->ErrorMessages + * + * XXXSMPNOW + */ +#define HX_THREADSAFE_METHOD_CACHE_FILE 0x00000008 +/* + * FileSystem::Read()/Seek()/Close() only calls: + * CCF->CI(Buffer), CCF->CI(Packet), CCF->CI(Values), *Alloc, *Free, + * FS->Read(), FS->Close(), FS->Seek(), + * IHXCacheFile->*, IHXCacheFileResponse->*, + * Context->Scheduler->*, + * CCF->CI(Mutex), Mutex->* + * Context->ErrorMessages + * + * XXXSMPNOW + */ +#define HX_THREADSAFE_METHOD_CACHE_FILE_RESPONSE 0x00000010 +/* + * FileSystem::Read()/Seek()/Close() only calls: + * CCF->CI(Buffer), CCF->CI(Packet), CCF->CI(Values), *Alloc, *Free, + * FS->Read(), FS->Close(), FS->Seek(), + * IHXCacheFile->*, IHXCacheFileResponse->*, + * Context->Scheduler->*, + * CCF->CI(Mutex), Mutex->* + * Context->ErrorMessages + * + * XXXSMPNOW + */ + +/* + * Thread Safe flags for IHXDataConvert + */ +#define HX_THREADSAFE_METHOD_CONVERT_HEADERS 0x00000020 +/* + * IHXDataConvert::ConvertXXX()/CtrlBufferReady() only calls: + * CCF->CI(Buffer), CCF->CI(Packet), CCF->CI(Values), *Alloc, *Free, + * IHXDataConvertResponse->* + * Context->Scheduler->*, + * CCF->CI(Mutex), Mutex->* + * Context->ErrorMessages + * + * XXXSMPNOW + */ +#define HX_THREADSAFE_METHOD_CONVERT_DATA 0x00000040 +/* + * IHXDataConvert::ConvertXXX()/CtrlBufferReady() only calls: + * CCF->CI(Buffer), CCF->CI(Packet), CCF->CI(Values), *Alloc, *Free, + * IHXDataConvertResponse->* + * Context->Scheduler->*, + * CCF->CI(Mutex), Mutex->* + * Context->ErrorMessages + * + * XXXSMPNOW + */ +#define HX_THREADSAFE_METHOD_CONVERT_CTRL_BUFFER_READY 0x00000080 +/* + * IHXDataConvert::ConvertXXX()/CtrlBufferReady() only calls: + * CCF->CI(Buffer), CCF->CI(Packet), CCF->CI(Values), *Alloc, *Free, + * IHXDataConvertResponse->* + * Context->Scheduler->*, + * CCF->CI(Mutex), Mutex->* + * Context->ErrorMessages + * + * XXXSMPNOW + */ +#define HX_THREADSAFE_METHOD_SOCKET_READDONE 0x00000100 + +#define HX_THREADSAFE_METHOD_ALL (~0) + +/**************************************************************************** + * + * Interface: + * + * IHXThreadSafeMethods + * + * Purpose: + * + * XXXSMPNOW + * + * IID_IHXThreadSafeMethods: + * + * {00000115-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXThreadSafeMethods, 0x00000115, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXThreadSafeMethods + +DECLARE_INTERFACE_(IHXThreadSafeMethods, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXThreadSafeMethods methods + */ + + /************************************************************************ + * Method: + * IHXThreadSafeMethods::IsThreadSafe + * Purpose: + * XXXSMPNOW + */ + STDMETHOD_(UINT32,IsThreadSafe) (THIS) PURE; +}; + + +/**************************************************************************** + * + * Interface: + * + * IHXMutex + * + * Purpose: + * + * XXXSMPNOW + * + * IID_IHXMutex: + * + * {00000116-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXMutex, 0x00000116, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXMutex + +/* + * The IHXCommonClassFactory supports creating an instance + * of this object. + */ +#define CLSID_IHXMutex IID_IHXMutex + +DECLARE_INTERFACE_(IHXMutex, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXMutex methods + */ + + /* XXXSMPNOW Comments */ + STDMETHOD(Lock) (THIS) PURE; + + STDMETHOD(TryLock) (THIS) PURE; + + STDMETHOD(Unlock) (THIS) PURE; +}; + +// $Private: +/**************************************************************************** + * + * Interface: + * + * IHXFastPathNetWrite + * + * Purpose: + * + * Private interface for high speed UDP output. + * + * IID_IHXFastPathNetWrite: + * + * {00000117-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXFastPathNetWrite, 0x00000117, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXFastPathNetWrite + +DECLARE_INTERFACE_(IHXFastPathNetWrite, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXFastPathNetWrite methods + * + */ + + STDMETHOD(FastWrite) (THIS_ + const UINT8* pBuffer, UINT32 ulLen) PURE; +}; + +/**************************************************************************** + * + * Interface: + * + * IHXWouldBlockResponse + * + * Purpose: + * + * Get notifications of EWOULDBLOCK conditions. + * + * IID_IHXWouldBlockResponse: + * + * {00000118-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXWouldBlockResponse, 0x00000118, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXWouldBlockResponse + +DECLARE_INTERFACE_(IHXWouldBlockResponse, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXWouldBlockResponse methods + * + */ + + + /* + * WouldBlock + * + * Return HXR_OK to go into blocked mode, causing a future + * WouldBlockCleared call. HXR_anythingelse to ignore. + */ + + STDMETHOD(WouldBlock) (THIS_ UINT32 id) PURE; + STDMETHOD(WouldBlockCleared)(THIS_ UINT32 id) PURE; + +}; + +/**************************************************************************** + * + * Interface: + * + * IHXWouldBlock + * + * Purpose: + * + * Notifier for EWOULDBLOCK conditions. + * + * IID_IHXWouldBlock: + * + * {00000119-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXWouldBlock, 0x00000119, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXWouldBlock + +DECLARE_INTERFACE_(IHXWouldBlock, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXWouldBlock methods + * + */ + + STDMETHOD(WantWouldBlock) (THIS_ + IHXWouldBlockResponse*, UINT32 id) PURE; +}; + +/**************************************************************************** + * + * Interface: + * + * IHXSharedUDPServices + * + * Purpose: + * + * Private interface for tying a UDP socket (via IHXUDPSocketContext) to + * a shared UDP resend port. Used to send UDP packet resend requests to one + * shared UDP port per streamer. + * + * IID_IHXSharedUDPServices + * + * {00000123-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXSharedUDPServices, 0x00000124, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXSharedUDPServices + +DECLARE_INTERFACE_(IHXSharedUDPServices, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXSharedUDPServices methods + */ + STDMETHOD(RegisterSharedResponse) (THIS_ + IHXUDPResponse* response, + UINT16 sPortEnum) PURE; + + STDMETHOD(UnregisterSharedResponse) (THIS) PURE; + + STDMETHOD_(UINT16, GetSharedPort) (THIS) PURE; + +}; + +/**************************************************************************** + * + * Interface: + * + * IHXThreadLocal + * + * Purpose: + * + * Thread-local information, namely the procnum. + * + * IID_IHXThreadLocal + * + * {00000125-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXThreadLocal, 0x00000125, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXThreadLocal + +#define CLSID_IHXThreadLocal IID_IHXThreadLocal + +DECLARE_INTERFACE_(IHXThreadLocal, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXThreadLocal methods + */ + + /* + * IHXThreadLocal::GetMaxThreads() + * + * Maximum number of threads on the system (MAX_THREADS on server) + */ + STDMETHOD_(int, GetMaxThreads) (THIS) PURE; + + /* + * IHXThreadLocal::GetThreadNumber() + * + * This thread's number (< MAX_THREADS) + */ + STDMETHOD_(int, GetThreadNumber) (THIS) PURE; +}; + +/**************************************************************************** + * + * Interface: + * + * IHXMemoryServices + * + * Purpose: + * + * Exposes server memory functions + * + * IID_IHXMemoryServices + * + * {00000126-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXMemoryServices, 0x00000126, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXMemoryServices + +#define CLSID_IHXMemoryServices IID_IHXMemoryServices + +DECLARE_INTERFACE_(IHXMemoryServices, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXMemoryServices methods + */ + + /* + * IHXMemoryServices::ValidateMemory() + * + * Make consistency checks on the server shared memory space. + * + * lStartPage, lPages + * + * Specifies a page range to allow smaller searches, if you have a + * repro case and want to fail as soon as possible after the scribble. + * Use lPages == 0 to check to the last page. (Pages in the shared + * space are numbered starting from zero. You can decide on a range + * based on the printout of previous errors.) + * + * ulFlags + * + * 0x00000001 fail -- abort() -- on finding an error. + * 0x00000002 do rudimentary checks on internal SharedMemory data arrays. + * + */ + STDMETHOD(ValidateMemory) (THIS_ + INT32 lStartPage, + INT32 lPages, + UINT32 ulFlags) PURE; +}; + + +typedef enum _HX_PRIVATE_SOCKET_OPTION +{ + HX_PRIVATE_SOCKOPT_IGNORE_WSAECONNRESET +} HX_PRIVATE_SOCKET_OPTION; + +/**************************************************************************** + * + * Interface: + * + * IHXSetPrivateSocketOption + * + * Purpose: + * + * Set private sockt option + * + * IID_IHXSetPrivateSocketOption: + * + * {00000127-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXSetPrivateSocketOption, + 0x00000127, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXSetPrivateSocketOption +DECLARE_INTERFACE_(IHXSetPrivateSocketOption, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXSetPrivateSocketOption methods + */ + + STDMETHOD(SetOption) (THIS_ + HX_PRIVATE_SOCKET_OPTION option, + UINT32 ulValue) PURE; +}; + +/**************************************************************************** + * + * Interface: + * + * IHXNetInterfaces + * + * Purpose: + * + * Network Interfaces + * + * IID_IHXNetInterfaces: + * + * {00000128-0901-11d1-8B06-00A024406D59} + * + */ +typedef enum +{ + NI_UNKNOWN, + NI_ETHERNET, + NI_TOKENRING, + NI_FDDI, + NI_PPP, + NI_LOOPBACK, + NI_SLIP, + NI_TUNNEL +} NIType; + +// Operational status of the interface using the +// values defined in RFC 2863. +typedef enum +{ + NI_OPER_STATUS_NON_OPERATIONAL= 0, + NI_OPER_STATUS_UNREACHABLE, + NI_OPER_STATUS_DISCONNECTED, + NI_OPER_STATUS_CONNECTING, + NI_OPER_STATUS_CONNECTED, + NI_OPER_STATUS_OPERATIONAL +} NIStatus; + +typedef enum +{ + NI_ADDR_UNKNOWN, + NI_ADDR_IPv4, + NI_ADDR_IPv6 +} NIAddressType; + +struct NIAddressInfo +{ + NIAddressType type; + IHXBuffer* pAddress; + IHXBuffer* pSubnet; + UINT32 ulSubnetPrefix; + NIAddressInfo* next; + + NIAddressInfo() + { + type = NI_ADDR_UNKNOWN; + pAddress = NULL; + pSubnet = NULL; + ulSubnetPrefix = 0; + next = NULL; + }; + + ~NIAddressInfo() + { + HX_RELEASE(pAddress); + HX_RELEASE(pSubnet); + next = NULL; + }; +}; + +struct NIInfo +{ + NIType type; + IHXBuffer* pDescription; + NIStatus status; + UINT32 ulIPv4Index; + UINT32 ulIPv6Index; + UINT32 ulMTU; + NIAddressInfo* pAddressInfo; + + NIInfo() + { + type = NI_UNKNOWN; + pDescription = NULL; + status = NI_OPER_STATUS_NON_OPERATIONAL; + ulIPv4Index = 0; + ulIPv6Index = 0; + ulMTU = 0; + pAddressInfo = NULL; + }; + + ~NIInfo() + { + HX_RELEASE(pDescription); + + NIAddressInfo* pTemp = pAddressInfo; + while (pTemp) + { + pAddressInfo = pAddressInfo->next; + HX_DELETE(pTemp); + pTemp = pAddressInfo; + } + }; +}; + +DEFINE_GUID(IID_IHXNetInterfaces, + 0x00000128, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXNetInterfaces +DECLARE_INTERFACE_(IHXNetInterfaces, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXNetworkInterfaces methods + */ + STDMETHOD(UpdateNetInterfaces) (THIS) PURE; + + STDMETHOD_(UINT32, GetNumOfNetInterfaces) (THIS) PURE; + + STDMETHOD(GetNetInterfaces) (THIS_ + UINT16 lIndex, + REF(NIInfo*) pNIInfo) PURE; + + STDMETHOD(AddAdviseSink) (THIS_ + IHXNetInterfacesAdviseSink* pSink) PURE; + + STDMETHOD(RemoveAdviseSink) (THIS_ + IHXNetInterfacesAdviseSink* pSink) PURE; +}; + +/**************************************************************************** + * + * Interface: + * + * IHXNetInterfacesAdviseSink + * + * Purpose: + * + * Network Interfaces Advise Sink + * + * IID_IHXNetInterfaces: + * + * {00000129-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXNetInterfacesAdviseSink, + 0x00000129, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXNetInterfacesAdviseSink +DECLARE_INTERFACE_(IHXNetInterfacesAdviseSink, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXNetInterfacesAdviseSink methods + */ + STDMETHOD(NetInterfacesUpdated) (THIS) PURE; +}; + +// $EndPrivate. + +/**************************************************************************** + * + * Interface: + * + * IHXNetworkInterfaceEnumerator + * + * Purpose: + * + * Enumerate interfaces on a box. + * + * IID_IHXNetworkInterfaceEnumerator; + * + * {00000121-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXNetworkInterfaceEnumerator, 0x00000121, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXNetworkInterfaceEnumerator + +DECLARE_INTERFACE_(IHXNetworkInterfaceEnumerator, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + + /************************************************************************ + * Method: + * IHXNetworkInterfaceEnumerator::EnumerateInterfaces + * Purpose: + * returns a list of local interfaces + * Usage: + * If a buffer passed in is too small, it will return + * HXR_BUFFERTOOSMALL with ulNumInterfaces updated. + */ + STDMETHOD(EnumerateInterfaces) (THIS_ + REF(UINT32*) pulInterfaces, REF(UINT32) ulNumInterfaces) PURE; +}; + +/**************************************************************************** + * + * Interface: + * + * IHXUDPConnectedSocket + * + * Purpose: + * + * Connect and disconnect a UDP socket + * + * IID_IHXUDPConnectedSocket; + * + * {0000012A-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXUDPConnectedSocket, 0x0000012a, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXUDPConnectedSocket + +DECLARE_INTERFACE_(IHXUDPConnectedSocket, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + + /************************************************************************ + * Method: + * IHXUDPConnectedSocket::UDPConnect + * Purpose: + * Connect the udp socket + * Usage: + * Connect to the foreign addr and port the socket already knows + * about; this is implementation-dependent. + */ + STDMETHOD(UDPConnect) (THIS) PURE; + + /************************************************************************ + * Method: + * IHXUDPConnectedSocket::UDPConnect(ULONG32 ulAddr, UINT16 nPort); + * Purpose: + * Connect the udp socket + * Usage: + * Specify the host-ordered foreign addr and port to connect to. + */ + STDMETHOD(UDPConnect) (THIS_ ULONG32 ulAddr, UINT16 nPort) PURE; + + /************************************************************************ + * Method: + * IHXUDPConnectedSocket::UDPDisconnect + * Purpose: + * Disconnect the udp socket + */ + STDMETHOD(UDPDisconnect) (THIS) PURE; + + /************************************************************************ + * Method: + * IHXUDPConnectedSocket::IsUDPConnected + * Purpose: + * Return whether the socket is connected. + */ + STDMETHOD_(HXBOOL, IsUDPConnected) (THIS) PURE; + + /************************************************************************ + * Method: + * IHXUDPConnectedSocket::IsUDPConnected(REF(ULONG32) ulAddr, + * REF(UINT16) nPort) + * Purpose: + * Return whether the socket is connected, and the connected addr/port. + * Usage: + * Return the foreign addr/port the socket knows about, regardless of + * whether it's connected. This is the foreign addr that is connected + * to (if TRUE) or that will be used if Connect(void) called (if FALSE). + */ + STDMETHOD_(HXBOOL, IsUDPConnected) (THIS_ REF(ULONG32) ulAddr, + REF(UINT16) nPort) PURE; +}; + +/**************************************************************************** + * + * Interface: + * + * IHXAutoBWDetection + * + * Purpose: + * + * Auto Bandwidth Detection interface + * + * IID_IHXAutoBWDetection; + * + * {0000012b-0901-11d1-8B06-00A024406D59} + * + */ + +DEFINE_GUID(IID_IHXAutoBWDetection, + 0x0000012b, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXAutoBWDetection +DECLARE_INTERFACE_(IHXAutoBWDetection, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXAutoBWDetection methods + */ + STDMETHOD(InitAutoBWDetection) (THIS_ + HXBOOL bEnabled) PURE; + STDMETHOD(AddAutoBWDetectionSink) (THIS_ + IHXAutoBWDetectionAdviseSink* pSink) PURE; + STDMETHOD(RemoveAutoBWDetectionSink) (THIS_ + IHXAutoBWDetectionAdviseSink* pSink) PURE; +}; + +/**************************************************************************** + * + * Interface: + * + * IHXAutoBWDetectionAdviseSink + * + * Purpose: + * + * Auto Bandwidth Detection callback interface + * + * IID_IHXAutoBWDetectionAdviseSink: + * + * {0000012c-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXAutoBWDetectionAdviseSink, + 0x0000012c, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXAutoBWDetectionAdviseSink +DECLARE_INTERFACE_(IHXAutoBWDetectionAdviseSink, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXAutoBWDetectionAdviseSink methods + */ + STDMETHOD(AutoBWDetectionDone) (THIS_ + HX_RESULT status, + UINT32 ulBW) PURE; +}; + +/**************************************************************************** + * + * Interface: + * + * IHXAutoBWCalibration + * + * Purpose: + * + * Auto Bandwidth Calibration interface + * + * IID_IHXAutoBWDetection; + * + * {0000012d-0901-11d1-8B06-00A024406D59} + * + */ + +DEFINE_GUID(IID_IHXAutoBWCalibration, + 0x0000012d, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXAutoBWCalibration +DECLARE_INTERFACE_(IHXAutoBWCalibration, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXAutoBWCalibration methods + */ + STDMETHOD(InitAutoBWCalibration) (THIS_ + IHXValues* pValues) PURE; + STDMETHOD(StartAutoBWCalibration) (THIS) PURE; + STDMETHOD(StopAutoBWCalibration) (THIS) PURE; + STDMETHOD(AddAutoBWCalibrationSink) (THIS_ + IHXAutoBWCalibrationAdviseSink* pSink) PURE; + STDMETHOD(RemoveAutoBWCalibrationSink) (THIS_ + IHXAutoBWCalibrationAdviseSink* pSink) PURE; +}; + +/**************************************************************************** + * + * Interface: + * + * IHXAutoBWCalibrationAdviseSink + * + * Purpose: + * + * Auto Bandwidth Calibration callback interface + * + * IID_IHXAutoBWCalibrationAdviseSink: + * + * {0000012e-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXAutoBWCalibrationAdviseSink, + 0x0000012e, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXAutoBWCalibrationAdviseSink +DECLARE_INTERFACE_(IHXAutoBWCalibrationAdviseSink, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXAutoBWCalibrationAdviseSink methods + */ + STDMETHOD(AutoBWCalibrationStarted) (THIS_ + const char* pszServer) PURE; + STDMETHOD(AutoBWCalibrationDone) (THIS_ + HX_RESULT status, + UINT32 ulBW) PURE; +}; + +/**************************************************************************** + * + * Interface: + * + * IHXConnectionBWAdviseSink + * + * Purpose: + * + * Manages + * + * IID_IHXConnectionBWAdviseSink + * + * {7568B47F-0C1A-4099-B84B-D425C9746737} + * + */ +DEFINE_GUID(IID_IHXConnectionBWAdviseSink, +0x7568b47f, 0xc1a, 0x4099, 0xb8, 0x4b, 0xd4, 0x25, 0xc9, 0x74, 0x67, 0x37); +#undef INTERFACE +#define INTERFACE IHXConnectionBWAdviseSink +DECLARE_INTERFACE_(IHXConnectionBWAdviseSink, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXConnectionBWAdviseSink methods + */ + STDMETHOD(NewConnectionBW)(THIS_ UINT32 uConnectionBW) PURE; +}; + + +/**************************************************************************** + * + * Interface: + * + * IHXConnectionBWInfo + * + * Purpose: + * + * Manages + * + * IID_IHXConnectionBWInfo + * + * {9D1EDFB0-7A10-43f1-B008-8D0E00CA279F} + * + */ +DEFINE_GUID(IID_IHXConnectionBWInfo, +0x9d1edfb0, 0x7a10, 0x43f1, 0xb0, 0x8, 0x8d, 0xe, 0x0, 0xca, 0x27, 0x9f); + +#undef INTERFACE +#define INTERFACE IHXConnectionBWInfo +DECLARE_INTERFACE_(IHXConnectionBWInfo, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXConnectionBWInfo methods + */ + STDMETHOD(AddABDInfo)(THIS_ IHXAutoBWDetection* pABD, + IHXPreferredTransport* pPrefTransport) PURE; + STDMETHOD(RemoveABDInfo)(THIS_ IHXAutoBWDetection* pABD) PURE; + + /* + * IHXConnectionBWInfo::GetConnectionBW() + * + * Gets the current connection bandwidth estimate. + * + * Parameters: + * uBW : The connection BW is stored in this parameter on return + * bDetectedBWOnly : Specifies that only values derived from BW + * detection are considered OK. + * + * Returns: + * HXR_OK : uBW is set to the connection BW + * HXR_INCOMPLETE : This is returned if bDetectedBWOnly is set + * and there isn't any BW detection data available. + * uBW will contain a connection BW based on user + * preferences + * HXR_FAILED : No connection BW is available at this time + * + */ + STDMETHOD(GetConnectionBW)(THIS_ REF(UINT32) uBw, HXBOOL bDetectedBWOnly) PURE; + + STDMETHOD(AddSink)(THIS_ IHXConnectionBWAdviseSink* pSink) PURE; + STDMETHOD(RemoveSink)(THIS_ IHXConnectionBWAdviseSink* pSink) PURE; +}; + +#endif /* _HXENGIN_H_ */ diff --git a/amarok/src/engine/helix/helix-sp/helix-include/common/include/hxerror.h b/amarok/src/engine/helix/helix-sp/helix-include/common/include/hxerror.h new file mode 100644 index 00000000..b3d318c7 --- /dev/null +++ b/amarok/src/engine/helix/helix-sp/helix-include/common/include/hxerror.h @@ -0,0 +1,272 @@ + +/* + * + * This software is released under the provisions of the GPL version 2. + * see file "COPYING". If that file is not available, the full statement + * of the license can be found at + * + * http://www.fsf.org/licensing/licenses/gpl.txt + * + * Portions Copyright (c) 1995-2004 RealNetworks, Inc. All Rights Reserved. + * + */ + +#ifndef _HXERROR_H_ +#define _HXERROR_H_ + +/* + * Forward declarations of some interfaces defined here-in. + */ +typedef _INTERFACE IHXBuffer IHXBuffer; +typedef _INTERFACE IHXErrorSinkControl IHXErrorSinkControl; + + +/* Message Severity values */ + +enum { + HXLOG_EMERG = 0, /* A panic condition. Server / Player will halt or + restart. */ + + HXLOG_ALERT = 1, /* A condition that should be corrected immediately. + Needs user intervention to prevent problems. */ + + HXLOG_CRIT = 2, /* Critical conditions. */ + + HXLOG_ERR = 3, /* Errors. */ + + HXLOG_WARNING = 4, /* Warning messages. */ + + HXLOG_NOTICE = 5, /* Conditions that are not error conditions, but + should possibly be handled specially. */ + + HXLOG_INFO = 6, /* Informational messages. */ + + HXLOG_DEBUG = 7 /* Messages that contain information normally of use + only when debugging a program. */ +}; + + +/**************************************************************************** + * + * Interface: + * + * IHXErrorMessages + * + * Purpose: + * + * Error, event, and status message reporting interface + * + * IID_IHXErrorMessages: + * + * {00000800-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXErrorMessages, 0x00000800, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXErrorMessages + +DECLARE_INTERFACE_(IHXErrorMessages, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXErrorMessages methods + */ + + /************************************************************************ + * Method: + * IHXErrorMessages::Report + * Purpose: + * Call this method to report an error, event, or status message. + * Parameters: + * + * const UINT8 unSeverity + * Type of report. This value will impact how the player, tool, or + * server will react to the report. Possible values are described + * above. Depending on the error type, an error message with the + * HXR code, anda string translation of that code will be displayed. + * The error dialog includes a "more info" section that displays the + * user code and string, and a link to the more info URL. In the + * server these messages are logged to the log file. + * + * const ULONG32 ulHXCode + * Well known HXR error code. This will be translated to a text + * representation for display in an error dialog box or log file. + * + * const ULONG32 ulUserCode + * User specific error code. This will NOT be translated to a text + * representation. This can be any value the caller wants, it will + * be logged or displayed but not interpretted. + * + * const char* pUserString + * User specific error string. This will NOT be translated or + * modified. This can be any value the caller wants, it will + * be logged or displayed but not interpretted. + * + * const char* pMoreInfoURL + * User specific more info URL string. + * + */ + STDMETHOD(Report) (THIS_ + const UINT8 unSeverity, + HX_RESULT ulHXCode, + const ULONG32 ulUserCode, + const char* pUserString, + const char* pMoreInfoURL) PURE; + + /************************************************************************ + * Method: + * IHXErrorMessages::GetErrorText + * Purpose: + * Call this method to get the text description of a HXR error code. + * Parameters: + * HX_RESULT ulHXCode (A HXR error code) + * Return Value: + * IHXBuffer* containing error text. + */ + STDMETHOD_(IHXBuffer*, GetErrorText) (THIS_ + HX_RESULT ulHXCode) PURE; +}; + + +/**************************************************************************** + * + * Interface: + * + * IHXErrorSink + * + * Purpose: + * + * Error Sink Interface + * + * IID_IHXErrorSink: + * + * {00000801-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXErrorSink, 0x00000801, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXErrorSink + +DECLARE_INTERFACE_(IHXErrorSink, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXErrorSink methods + */ + + /************************************************************************ + * Method: + * IHXErrorSink::ErrorOccurred + * Purpose: + * After you have registered your error sink with an + * IHXErrorSinkControl (either in the server or player core) this + * method will be called to report an error, event, or status message. + * + * The meaning of the arguments is exactly as described in + * hxerror.h + */ + STDMETHOD(ErrorOccurred) (THIS_ + const UINT8 unSeverity, + const ULONG32 ulHXCode, + const ULONG32 ulUserCode, + const char* pUserString, + const char* pMoreInfoURL + ) PURE; +}; + + +/**************************************************************************** + * + * Interface: + * + * IHXErrorSinkControl + * + * Purpose: + * + * Error Sink Control Interface + * + * IID_IHXErrorSinkControl: + * + * {00000802-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXErrorSinkControl, 0x00000802, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXErrorSinkControl + + +DECLARE_INTERFACE_(IHXErrorSinkControl, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXErrorSinkControl methods + */ + + /************************************************************************ + * Method: + * IHXErrorSinkControl::AddErrorSink + * Purpose: + * Call this method to tell the sink controller to handle an error + * sink. + * + * This method also allows you to set a range of severity levels which + * you will receive reports for. + * + * Note: You should specify an invalid range (Low = 1, High = 0 for + * example) if you don't want to receive any errors. + * + * The default severity range is HXLOG_EMERG to HXLOG_INFO (0-6). + */ + STDMETHOD(AddErrorSink) (THIS_ + IHXErrorSink* pErrorSink, + const UINT8 unLowSeverity, + const UINT8 unHighSeverity) PURE; + + /************************************************************************ + * Method: + * IHXErrorSinkControl::AddErrorSink + * Purpose: + * Call this method to remove an error sink. + */ + STDMETHOD(RemoveErrorSink) (THIS_ + IHXErrorSink* pErrorSink) PURE; + +}; + +#endif /* _HXERROR_H_ */ diff --git a/amarok/src/engine/helix/helix-sp/helix-include/common/include/hxfiles.h b/amarok/src/engine/helix/helix-sp/helix-include/common/include/hxfiles.h new file mode 100644 index 00000000..f516af6b --- /dev/null +++ b/amarok/src/engine/helix/helix-sp/helix-include/common/include/hxfiles.h @@ -0,0 +1,2573 @@ + +/* + * + * This software is released under the provisions of the GPL version 2. + * see file "COPYING". If that file is not available, the full statement + * of the license can be found at + * + * http://www.fsf.org/licensing/licenses/gpl.txt + * + * Portions Copyright (c) 1995-2004 RealNetworks, Inc. All Rights Reserved. + * + */ + +#ifndef _HXFILES_H_ +#define _HXFILES_H_ + +/* + * Forward declarations of some interfaces defined here-in. + */ +typedef _INTERFACE IHXFileObject IHXFileObject; +typedef _INTERFACE IHXFileObjectExt IHXFileObjectExt; +typedef _INTERFACE IHXFileResponse IHXFileResponse; +typedef _INTERFACE IHXFileSystemObject IHXFileSystemObject; +typedef _INTERFACE IHXFileStat IHXFileStat; +typedef _INTERFACE IHXFileStatResponse IHXFileStatResponse; + +typedef _INTERFACE IHXFileSystemManager IHXFileSystemManager; +typedef _INTERFACE IHXFileSystemManagerResponse IHXFileSystemManagerResponse; +typedef _INTERFACE IHXFileExists IHXFileExists; +typedef _INTERFACE IHXFileExistsResponse IHXFileExistsResponse; +typedef _INTERFACE IHXFileMimeMapper IHXFileMimeMapper; +typedef _INTERFACE IHXFileMimeMapperResponse IHXFileMimeMapperResponse; +typedef _INTERFACE IHXBroadcastMapper IHXBroadcastMapper; +typedef _INTERFACE IHXBroadcastMapperResponse IHXBroadcastMapperResponse; +typedef _INTERFACE IHXGetFileFromSamePoolResponse IHXGetFileFromSamePoolResponse; +typedef _INTERFACE IHXBuffer IHXBuffer; +typedef _INTERFACE IHXPacket IHXPacket; +typedef _INTERFACE IHXValues IHXValues; +typedef _INTERFACE IHXMetaCreation IHXMetaCreation; + +typedef _INTERFACE IHXAuthenticator IHXAuthenticator; +typedef _INTERFACE IHXRequest IHXRequest; +typedef _INTERFACE IHXFileRename IHXFileRename; +typedef _INTERFACE IHXFileMove IHXFileMove; +typedef _INTERFACE IHXDirHandler IHXDirHandler; +typedef _INTERFACE IHXDirHandlerResponse IHXDirHandlerResponse; +typedef _INTERFACE IHXFileRemove IHXFileRemove; +// $Private: +typedef _INTERFACE IHXFastFileFactory IHXFastFileFactory; +typedef _INTERFACE IHXHTTPPostObject IHXHTTPPostObject; +typedef _INTERFACE IHXHTTPPostResponse IHXHTTPPostResponse; +typedef _INTERFACE IHXHTTPRedirect IHXHTTPRedirect; +typedef _INTERFACE IHXHTTPRedirectResponse IHXHTTPRedirectResponse; +typedef _INTERFACE IHXRM2Converter2 IHXRM2Converter2; +typedef _INTERFACE IHXRM2Converter2Response IHXRM2Converter2Response; +typedef _INTERFACE IHXPoolPathAdjustment IHXPoolPathAdjustment; +typedef _INTERFACE IHXPostDataHandler IHXPostDataHandler; +// $EndPrivate. + +/**************************************************************************** + * Defines: + * HX_FILE_XXXX + * Purpose: + * Flags for opening file objects + */ +#define HX_FILE_READ 1 +#define HX_FILE_WRITE 2 +#define HX_FILE_BINARY 4 +#define HX_FILE_NOTRUNC 8 +#define HX_FILE_NOPAC 16 + +/**************************************************************************** + * Defines: + * HX_FILEADVISE_XXXX + * Purpose: + * Flags for file object Advise method + */ +#define HX_FILEADVISE_RANDOMACCESS 1 +#define HX_FILEADVISE_SYNCACCESS 2 +#define HX_FILEADVISE_ASYNCACCESS 3 +#define HX_FILEADVISE_RANDOMACCESSONLY 4 +#define HX_FILEADVISE_ANYACCESS 5 + +/**************************************************************************** + * Defines: + * HX_FILERESPONSEADVISE_XXXX + * Purpose: + * Flags for file response Advise method + */ +#define HX_FILERESPONSEADVISE_REQUIREFULLREAD 1 + + +#if defined(_UNIX) || defined(_WINDOWS) +#include "hlxclib/sys/stat.h" +/* + * This is a subset of standard stat()/fstat() values that both Unix and + * Windows support (or at least define). + * + * These flags are returned from IHXFileStatResponse::StatDone() in the + * ulMode argument. + */ +#define HX_S_IFMT S_IFMT +#define HX_S_IFDIR S_IFDIR +#define HX_S_IFCHR S_IFCHR +#define HX_S_IFIFO S_IFIFO +#define HX_S_IFREG S_IFREG +#else +/* Macintosh */ +#define HX_S_IFMT 0170000 +#define HX_S_IFDIR 0040000 +#define HX_S_IFCHR 0020000 +#define HX_S_IFIFO 0010000 +#define HX_S_IFREG 0100000 +#endif + + +/**************************************************************************** + * + * Interface: + * + * IHXFileObject + * + * Purpose: + * + * Object that exports file control API + * + * IID_IHXFileObject: + * + * {00000200-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXFileObject, 0x00000200, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXFileObject + +DECLARE_INTERFACE_(IHXFileObject, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXFileObject methods + */ + + /************************************************************************ + * Method: + * IHXFileObject::Init + * Purpose: + * Associates a file object with the file response object it should + * notify of operation completness. This method should also check + * for validity of the object (for example by opening it if it is + * a local file). + */ + STDMETHOD(Init) (THIS_ + ULONG32 /*IN*/ ulFlags, + IHXFileResponse* /*IN*/ pFileResponse) PURE; + + /************************************************************************ + * Method: + * IHXFileObject::GetFilename + * Purpose: + * Returns the filename (without any path information) associated + * with a file object. + * + * Note: The returned pointer's lifetime expires as soon as the + * caller returns from a function which was called from the RMA + * core (i.e. when you return control to the RMA core) + * + */ + STDMETHOD(GetFilename) (THIS_ + REF(const char*) /*OUT*/ pFilename) PURE; + + /************************************************************************ + * Method: + * IHXFileObject::Close + * Purpose: + * Closes the file resource and releases all resources associated + * with the object. + */ + STDMETHOD(Close) (THIS) PURE; + + /************************************************************************ + * Method: + * IHXFileObject::Read + * Purpose: + * Reads a buffer of data of the specified length from the file + * and asynchronously returns it to the caller via the + * IHXFileResponse interface passed in to Init. + */ + STDMETHOD(Read) (THIS_ + ULONG32 ulCount) PURE; + + /************************************************************************ + * Method: + * IHXFileObject::Write + * Purpose: + * Writes a buffer of data to the file and asynchronously notifies + * the caller via the IHXFileResponse interface passed in to Init, + * of the completeness of the operation. + */ + STDMETHOD(Write) (THIS_ + IHXBuffer* pBuffer) PURE; + + /************************************************************************ + * Method: + * IHXFileObject::Seek + * Purpose: + * Seeks to an offset in the file and asynchronously notifies + * the caller via the IHXFileResponse interface passed in to Init, + * of the completeness of the operation. + * If the bRelative flag is TRUE, it is a relative seek; else + * an absolute seek. + */ + STDMETHOD(Seek) (THIS_ + ULONG32 ulOffset, + HXBOOL bRelative) PURE; + + /************************************************************************ + * Method: + * IHXFileObject::Advise + * Purpose: + * To pass information to the File Object advising it about usage + * heuristics. + */ + STDMETHOD(Advise) (THIS_ + ULONG32 ulInfo) PURE; +}; + + +/**************************************************************************** + * + * Interface: + * + * IHXFileObjectExt + * + * Purpose: + * + * Object that exports file control API + * + * IID_IHXFileObjectExt: + * + * {96DD5EB5-7EFD-4084-95CD-4D192A9036AF} + * + */ + DEFINE_GUID(IID_IHXFileObjectExt, 0x96dd5eb5, 0x7efd, 0x4084, 0x95, 0xcd, 0x4d, + 0x19, 0x2a, 0x90, 0x36, 0xaf); + +#undef INTERFACE +#define INTERFACE IHXFileObjectExt + +DECLARE_INTERFACE_(IHXFileObjectExt, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /************************************************************************ + * Method: + * IHXFileObjectExt::GetFullFilename + * Purpose: + * Returns the filename, with path information, associated + * with a file object. + * + * Note: The returned pointer's lifetime expires as soon as the + * caller returns from a function which was called from the RMA + * core (i.e. when you return control to the RMA core) + * + */ + STDMETHOD(GetFullFilename) (THIS_ + REF(IHXBuffer*) /*OUT*/ pFullFilename) PURE; +}; + + +/**************************************************************************** + * + * Interface: + * + * IHXFileResponse + * + * Purpose: + * + * Object that exports file response API + * + * IID_IHXFileResponse: + * + * {00000201-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXFileResponse, 0x00000201, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXFileResponse + +DECLARE_INTERFACE_(IHXFileResponse, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXFileResponse methods + */ + + /************************************************************************ + * Method: + * IHXFileResponse::InitDone + * Purpose: + * Notification interface provided by users of the IHXFileObject + * interface. This method is called by the IHXFileObject when the + * initialization of the file is complete. If the file is not valid + * for the file system, the status HXR_FAILED should be + * returned. + */ + STDMETHOD(InitDone) (THIS_ + HX_RESULT status) PURE; + + /************************************************************************ + * Method: + * IHXFileResponse::CloseDone + * Purpose: + * Notification interface provided by users of the IHXFileObject + * interface. This method is called by the IHXFileObject when the + * close of the file is complete. + */ + STDMETHOD(CloseDone) (THIS_ + HX_RESULT status) PURE; + + /************************************************************************ + * Method: + * IHXFileResponse::ReadDone + * Purpose: + * Notification interface provided by users of the IHXFileObject + * interface. This method is called by the IHXFileObject when the + * last read from the file is complete and a buffer is available. + */ + STDMETHOD(ReadDone) (THIS_ + HX_RESULT status, + IHXBuffer* pBuffer) PURE; + + /************************************************************************ + * Method: + * IHXFileResponse::WriteDone + * Purpose: + * Notification interface provided by users of the IHXFileObject + * interface. This method is called by the IHXFileObject when the + * last write to the file is complete. + */ + STDMETHOD(WriteDone) (THIS_ + HX_RESULT status) PURE; + + /************************************************************************ + * Method: + * IHXFileResponse::SeekDone + * Purpose: + * Notification interface provided by users of the IHXFileObject + * interface. This method is called by the IHXFileObject when the + * last seek in the file is complete. + */ + STDMETHOD(SeekDone) (THIS_ + HX_RESULT status) PURE; +}; + +/**************************************************************************** + * + * Interface: + * + * IHXAdvise + * + * Purpose: + * + * Allow IHXFileObject to query its IHXFileResponse interface + * + * IID_IHXAdvise: + * + * {43C3A3B8-8F76-4394-A4F8-07AA9091A0CA} + * + */ +DEFINE_GUID(IID_IHXAdvise, 0x43c3a3b8, 0x8f76, 0x4394, 0xa4, 0xf8, 0x7, + 0xaa, 0x90, 0x91, 0xa0, 0xca); + +#undef INTERFACE +#define INTERFACE IHXAdvise + +DECLARE_INTERFACE_(IHXAdvise, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXAdvise methods + */ + + /************************************************************************ + * Method: + * IHXAdvise::Advise + * Purpose: + * Allows IHXFileObject to query its IHXFileResponse about + * usage heuristics. It should pass HX_FILERESPONSEADVISE_xxx + * flags into this method. + */ + STDMETHOD(Advise) (THIS_ ULONG32 ulInfo) PURE; +}; + + +/**************************************************************************** + * + * Interface: + * + * IHXFileSystemObject + * + * Purpose: + * + * Object that allows a Controller to communicate with a specific + * File System plug-in session + * + * IID_IHXFileSystemObject: + * + * {00000202-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXFileSystemObject, 0x00000202, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXFileSystemObject + +DECLARE_INTERFACE_(IHXFileSystemObject, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXFileSystemObject methods + */ + + /************************************************************************ + * Method: + * IHXFileSystemObject::GetFileSystemInfo + * Purpose: + * Returns information vital to the instantiation of file system + * plugin. + * + * pShortName should be a short, human readable name in the form + * of "company-fsname". For example: pShortName = "pn-local". + */ + STDMETHOD(GetFileSystemInfo) (THIS_ + REF(const char*) /*OUT*/ pShortName, + REF(const char*) /*OUT*/ pProtocol) PURE; + + STDMETHOD(InitFileSystem) (THIS_ + IHXValues* pOptions) PURE; + + STDMETHOD(CreateFile) (THIS_ + IUnknown** /*OUT*/ ppFileObject) PURE; + + /* + * The following method is deprecated and should return HXR_NOTIMPL + */ + + STDMETHOD(CreateDir) (THIS_ + IUnknown** /*OUT*/ ppDirObject) PURE; + +}; + + +/**************************************************************************** + * + * Interface: + * + * IHXFileStat + * + * Purpose: + * + * Gets information about a specific File object + * + * IID_IHXFileStat: + * + * {00000205-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXFileStat, 0x00000205, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXFileStat + +DECLARE_INTERFACE_(IHXFileStat, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXFileStat methods + */ + + STDMETHOD(Stat) (THIS_ + IHXFileStatResponse* pFileStatResponse + ) PURE; +}; + + +/**************************************************************************** + * + * Interface: + * + * IHXFileStatResponse + * + * Purpose: + * + * Returns information about a specific File object + * + * IID_IHXFileStatResponse: + * + * {00000206-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXFileStatResponse, 0x00000206, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXFileStatResponse + +DECLARE_INTERFACE_(IHXFileStatResponse, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXFileStat methods + */ + + STDMETHOD(StatDone) (THIS_ + HX_RESULT status, + UINT32 ulSize, + UINT32 ulCreationTime, + UINT32 ulAccessTime, + UINT32 ulModificationTime, + UINT32 ulMode) PURE; +}; + +/**************************************************************************** + * + * Interface: + * + * IHXFileSystemManager + * + * Purpose: + * + * Gives out File Objects based on URLs + * + * IID_IHXFileSystemManager: + * + * {00000207-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXFileSystemManager, 0x00000207, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXFileSystemManager + +#define CLSID_IHXFileSystemManager IID_IHXFileSystemManager + +DECLARE_INTERFACE_(IHXFileSystemManager, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXFileSystemManager methods + */ + + STDMETHOD(Init) (THIS_ + IHXFileSystemManagerResponse* /*IN*/ pFileManagerResponse + ) PURE; + + /* GetFileObject attempts to locate an existing file via the DoesExist + * method in each file system's objects, and returns that object through + * FSManagerResponse->FileObjectReady + */ + STDMETHOD(GetFileObject) (THIS_ + IHXRequest* pRequest, + IHXAuthenticator* pAuthenticator) PURE; + + /* GetNewFileObject is similar to GetFileObject except that no DoesExist + * checks are done. The first file system that matches the mount point + * or protocol for the path in the request object creates the file + * which is then returned through FileObjectReady. This is especially + * useful for those who wish to open a brand new file for writing. + */ + STDMETHOD(GetNewFileObject) (THIS_ + IHXRequest* pRequest, + IHXAuthenticator* pAuthenticator) PURE; + + STDMETHOD(GetRelativeFileObject) (THIS_ + IUnknown* pOriginalObject, + const char* pPath) PURE; + + /* + * The following method is deprecated and should return HXR_NOTIMPL + */ + + STDMETHOD(GetDirObjectFromURL) (THIS_ + const char* pURL) PURE; +}; + + +/**************************************************************************** + * + * Interface: + * + * IHXFileSystemManagerResponse + * + * Purpose: + * + * Gives out File System objects based on URLs + * + * IID_IHXFileSystemManagerResponse: + * + * {00000208-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXFileSystemManagerResponse, 0x00000208, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXFileSystemManagerResponse + +DECLARE_INTERFACE_(IHXFileSystemManagerResponse, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXFileSystemManagerResponse methods + */ + + /************************************************************************ + * Method: + * IHXFileSystemManagerResponse::InitDone + * Purpose: + */ + STDMETHOD(InitDone) (THIS_ + HX_RESULT status) PURE; + + STDMETHOD(FileObjectReady) (THIS_ + HX_RESULT status, + IUnknown* pObject) PURE; + + /* + * The following method is deprecated and should return HXR_NOTIMPL + */ + + STDMETHOD(DirObjectReady) (THIS_ + HX_RESULT status, + IUnknown* pDirObject) PURE; +}; + + +/**************************************************************************** + * + * Interface: + * + * IHXFileExists + * + * Purpose: + * + * Checks for the existense of a file. Must be implemented. + * + * IID_IHXFileExists: + * + * {00000209-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXFileExists, 0x00000209, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXFileExists + +DECLARE_INTERFACE_(IHXFileExists, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXFileExists methods + */ + + /************************************************************************ + * Method: + * IHXFileExists::DoesExist + * Purpose: + */ + STDMETHOD(DoesExist) (THIS_ + const char* /*IN*/ pPath, + IHXFileExistsResponse* /*IN*/ pFileResponse) PURE; +}; + + +/**************************************************************************** + * + * Interface: + * + * IHXFileExistsResponse + * + * Purpose: + * + * Response interface for IHXFileExists. Must be implemented. + * + * IID_IHXFileExistsResponse: + * + * {0000020A-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXFileExistsResponse, 0x0000020a, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXFileExists + +DECLARE_INTERFACE_(IHXFileExistsResponse, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXFileExistsResponse methods + */ + + STDMETHOD(DoesExistDone) (THIS_ + HXBOOL bExist) PURE; +}; + + +/**************************************************************************** + * + * Interface: + * + * IHXFileMimeMapper + * + * Purpose: + * + * Allows you to specify a mime type for a specific file. + * Optional interface. + * + * IID_IHXFileMimeMapper: + * + * {0000020B-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXFileMimeMapper, 0x0000020b, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXFileMimeMapper + +DECLARE_INTERFACE_(IHXFileMimeMapper, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXFileMimeMapper methods + */ + + /************************************************************************ + * Method: + * IHXFileMimeMapper::FindMimeType + * Purpose: + */ + STDMETHOD(FindMimeType) (THIS_ + const char* /*IN*/ pURL, + IHXFileMimeMapperResponse* /*IN*/ pMimeMapperResponse + ) PURE; +}; + + +/**************************************************************************** + * + * Interface: + * + * IHXFileMimeMapperResponse + * + * Purpose: + * + * Response interface for IHXFileMimeMapper. + * Optional interface. + * + * IID_IHXFileMimeMapperResponse: + * + * {0000020C-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXFileMimeMapperResponse, 0x0000020c, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXFileMimeMapperResponse + +DECLARE_INTERFACE_(IHXFileMimeMapperResponse, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXFileMimeMapperResponse methods + */ + + /************************************************************************ + * Method: + * IHXFileMimeMapperResponse::MimeTypeFound + * Purpose: + * Notification interface provided by users of the IHXFileMimeMapper + * interface. This method is called by the IHXFileObject when the + * initialization of the file is complete, and the Mime type is + * available for the request file. If the file is not valid for the + * file system, the status HXR_FAILED should be returned, + * with a mime type of NULL. If the file is valid but the mime type + * is unknown, then the status HXR_OK should be returned with + * a mime type of NULL. + * + */ + STDMETHOD(MimeTypeFound) (THIS_ + HX_RESULT status, + const char* pMimeType) PURE; +}; + + +/**************************************************************************** + * + * Interface: + * + * IHXBroadcastMapper + * + * Purpose: + * + * Associates a file with a broadcast format plugin. + * Implementation only required by broadcast plugin file systems. + * + * IID_IHXBroadcastMapper: + * + * {0000020D-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXBroadcastMapper, 0x0000020d, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXBroadcastMapper + +DECLARE_INTERFACE_(IHXBroadcastMapper, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXBroadcastMapper methods + */ + + /************************************************************************ + * Method: + * IHXBroadcastMapper::FindBroadcastType + * Purpose: + */ + STDMETHOD(FindBroadcastType) (THIS_ + const char* /*IN*/ pURL, + IHXBroadcastMapperResponse* /*IN*/ pBroadcastMapperResponse) PURE; +}; + + +/**************************************************************************** + * + * Interface: + * + * IHXBroadcastMapperResponse + * + * Purpose: + * + * Response interface for IHXBroadcastMapper. + * Implementation only required by broadcast plugin file systems. + * + * IID_IHXBroadcastMapperResponse: + * + * {0000020E-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXBroadcastMapperResponse, 0x0000020e, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXBroadcastMapperResponse + +DECLARE_INTERFACE_(IHXBroadcastMapperResponse, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXBroadcastMapperResponse methods + */ + + /************************************************************************ + * Method: + * IHXBroadcastMapperResponse::BroadcastTypeFound + * Purpose: + * Notification interface provided by users of the IHXBroadcastMapper + * interface. This method is called by the File Object when the + * initialization of the file is complete, and the broadcast type is + * available for the request file. If the file is not valid for the + * file system, the status HXR_FAILED should be returned, + * with the broadcast type set to NULL. + * + */ + STDMETHOD(BroadcastTypeFound) (THIS_ + HX_RESULT status, + const char* pBroadcastType) PURE; +}; + + +/**************************************************************************** + * + * Interface: + * + * IHXGetFileFromSamePool + * + * Purpose: + * + * Gives out File Objects based on filenames and relative "paths" + * + * IID_IHXGetFileFromSamePool: + * + * {0000020f-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXGetFileFromSamePool, 0x0000020f, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); +#undef INTERFACE +#define INTERFACE IHXGetFileFromSamePool + +#define CLSID_IHXGetFileFromSamePool IID_IHXGetFileFromSamePool + +DECLARE_INTERFACE_(IHXGetFileFromSamePool, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + + /* + * IHXGetFileFromSamePool method + */ + /************************************************************************ + * Method: + * IHXGetFileFromSamePool::GetFileObjectFromPool + * Purpose: + * To get another FileObject from the same pool. + */ + STDMETHOD(GetFileObjectFromPool) (THIS_ + IHXGetFileFromSamePoolResponse*) PURE; +}; + + +/**************************************************************************** + * + * Interface: + * + * IHXGetFileFromSamePoolResponse + * + * Purpose: + * + * Gives out File Objects based on filenames and relative "paths" + * + * IID_IHXGetFileFromSamePoolResponse: + * + * {00000210-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXGetFileFromSamePoolResponse, 0x00000210, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); +#undef INTERFACE +#define INTERFACE IHXGetFileFromSamePoolResponse + +#define CLSID_IHXGetFileFromSamePoolResponse IID_IHXGetFileFromSamePoolResponse + +DECLARE_INTERFACE_(IHXGetFileFromSamePoolResponse, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXGetFileFromSamePoolResponse method + */ + /************************************************************************ + * Method: + * IHXGetFileFromSamePoolResponse::FileObjectReady + * Purpose: + * To return another FileObject from the same pool. + */ + STDMETHOD(FileObjectReady) (THIS_ + HX_RESULT status, + IUnknown* ppUnknown) PURE; +}; + + +/**************************************************************************** + * + * Interface: + * + * IHXFileAuthenticator + * + * Purpose: + * + * Set and Get a file object's authenticator object. + * + * IID_IHXFileAuthenticator: + * + * {00000211-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXFileAuthenticator, 0x00000211, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); +#undef INTERFACE +#define INTERFACE IHXFileAuthenticator + +#define CLSID_IHXFileAuthenticator IID_IHXFileAuthenticator + +DECLARE_INTERFACE_(IHXFileAuthenticator, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXFileAuthenticator methods + */ + STDMETHOD(SetAuthenticator) (THIS_ + IHXAuthenticator* pAuthenticator) PURE; + + STDMETHOD(GetAuthenticator) (THIS_ + REF(IHXAuthenticator*) pAuthenticator) PURE; +}; + + +/**************************************************************************** + * + * Interface: + * + * IHXRequestHandler + * + * Purpose: + * + * Object to manage IHXRequest objects + * + * IID_IHXRequestHandler: + * + * {00000212-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXRequestHandler, 0x00000212, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); +#undef INTERFACE +#define INTERFACE IHXRequestHandler + +#define CLSID_IHXRequestHandler IID_IHXRequestHandler + +DECLARE_INTERFACE_(IHXRequestHandler, IUnknown) +{ + /* + * IUnknown methods + */ + + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /************************************************************************ + * Method: + * IHXRequestHandler::SetRequest + * Purpose: + * Associates an IHXRequest with an object + */ + STDMETHOD(SetRequest) (THIS_ + IHXRequest* /*IN*/ pRequest) PURE; + + /************************************************************************ + * Method: + * IHXRequestHandler::GetRequest + * Purpose: + * Gets the IHXRequest object associated with an object + */ + STDMETHOD(GetRequest) (THIS_ + REF(IHXRequest*) /*OUT*/ pRequest) PURE; + +}; + + +/**************************************************************************** + * + * Interface: + * + * IHXRequestContext + * + * Purpose: + * + * Object to manage the context of the Request + * + * IID_IHXRequestContext: + * + * {00000217-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXRequestContext, 0x00000217, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); +#undef INTERFACE +#define INTERFACE IHXRequestContext + +#define CLSID_IHXRequestContext IID_IHXRequestContext + +DECLARE_INTERFACE_(IHXRequestContext, IUnknown) +{ + /* + * IUnknown methods + */ + + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXRequestContext methods + */ + + /************************************************************************ + * Method: + * IHXRequestContext::SetUserContext + * Purpose: + * Sets the Authenticated users Context. + */ + STDMETHOD(SetUserContext) + ( + THIS_ + IUnknown* pIUnknownNewContext + ) PURE; + + /************************************************************************ + * Method: + * IHXRequestContext::GetUserContext + * Purpose: + * Gets the Authenticated users Context. + */ + STDMETHOD(GetUserContext) + ( + THIS_ + REF(IUnknown*) pIUnknownCurrentContext + ) PURE; + + /************************************************************************ + * Method: + * IHXRequestContext::SetRequester + * Purpose: + * Sets the Object that made the request. + */ + STDMETHOD(SetRequester) + ( + THIS_ + IUnknown* pIUnknownNewRequester + ) PURE; + + /************************************************************************ + * Method: + * IHXRequestContext::GetRequester + * Purpose: + * Gets the Object that made the request. + */ + STDMETHOD(GetRequester) + ( + THIS_ + REF(IUnknown*) pIUnknownCurrentRequester + ) PURE; +}; + +/**************************************************************************** + * + * Interface: + * + * IHXRequest + * + * Purpose: + * + * Object to manage the RFC822 headers sent by the client + * + * IID_IHXRequest: + * + * {00000213-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXRequest, 0x00000213, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); +#undef INTERFACE +#define INTERFACE IHXRequest + +#define CLSID_IHXRequest IID_IHXRequest + +DECLARE_INTERFACE_(IHXRequest, IUnknown) +{ + /* + * IUnknown methods + */ + + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXRequest methods + */ + + /************************************************************************ + * Method: + * IHXRequest::SetRequestHeaders + * Purpose: + * Sets the headers that will be sent in the RFC822 header section + * of the request message + */ + STDMETHOD(SetRequestHeaders) (THIS_ + IHXValues* pRequestHeaders) PURE; + + /************************************************************************ + * Method: + * IHXRequest::GetRequestHeaders + * Purpose: + * Gets the headers that were sent in the RFC822 header section + * of the request message + */ + STDMETHOD(GetRequestHeaders) (THIS_ + REF(IHXValues*) pRequestHeaders) PURE; + + /************************************************************************ + * Method: + * IHXRequest::SetResponseHeaders + * Purpose: + * Sets the headers that will be returned in the RFC822 header + * section of the response message + */ + STDMETHOD(SetResponseHeaders) (THIS_ + IHXValues* pResponseHeaders) PURE; + + /************************************************************************ + * Method: + * IHXRequest::GetResponseHeaders + * Purpose: + * Gets the headers that were returned in the RFC822 header section + * of the response message + */ + STDMETHOD(GetResponseHeaders) (THIS_ + REF(IHXValues*) pResponseHeaders) PURE; + + /************************************************************************ + * Method: + * IHXRequest::SetURL + * Purpose: + * Sets the fully qualified path associated with a file object. + * Note: On the server, this path does not include the file system + * mount point. + */ + STDMETHOD(SetURL) (THIS_ + const char* pURL) PURE; + + /************************************************************************ + * Method: + * IHXRequest::GetURL + * Purpose: + * Returns the fully qualified path associated with a file object. + * Note: On the server, this path does not include the file system + * mount point. + * + * Note: The returned pointer's lifetime expires as soon as the + * caller returns from a function which was called from the RMA + * core (i.e. when you return control to the RMA core) + */ + STDMETHOD(GetURL) (THIS_ + REF(const char*) pURL) PURE; +}; + +/**************************************************************************** + * + * Interface: + * + * IHXFileRename + * + * Purpose: + * + * Interface to allow renaming of files. Query off of the File Object. + * Not all filesystem plugins implement this feature. + * + * IID_IHXFileRename: + * + * {00000214-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXFileRename, 0x00000214, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); +#undef INTERFACE +#define INTERFACE IHXFileRename + +DECLARE_INTERFACE_(IHXFileRename, IUnknown) +{ + /* + * IUnknown methods + */ + + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXFileRename methods + */ + + /************************************************************************ + * Method: + * IHXFileRename::Rename + * Purpose: + * Renames a file to a new name. + */ + STDMETHOD(Rename) (THIS_ + const char* pNewFileName) PURE; +}; + + +/**************************************************************************** + * + * Interface: + * + * IHXFileMove + * + * Purpose: + * + * Interface to allow removing of files. Query off of the File Object. + * Not all filesystem plugins implement this feature. + * + * IID_IHXFileMove: + * + * {23E72FB0-DE0E-11d5-AA9A-00010251B340} + * + */ +DEFINE_GUID(IID_IHXFileMove, 0x23e72fb0, 0xde0e, 0x11d5, 0xaa, 0x9a, 0x0, + 0x1, 0x2, 0x51, 0xb3, 0x40); + +#undef INTERFACE +#define INTERFACE IHXFileMove + +DECLARE_INTERFACE_(IHXFileMove, IUnknown) +{ + /* + * IUnknown methods + */ + + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXFileMove methods + */ + + /************************************************************************ + * Method: + * IHXFileMove::Move + * Purpose: + * Moves a file to a different location in the file system + */ + STDMETHOD(Move) (THIS_ + const char* pNewFilePathName) PURE; +}; + + +/**************************************************************************** + * + * Interface: + * + * IHXDirHandler + * + * Purpose: + * + * Object that exports directory handler API + * + * IID_IHXDirHandler: + * + * {00000215-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXDirHandler, 0x00000215, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXDirHandler + +DECLARE_INTERFACE_(IHXDirHandler, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXDirHandler methods + */ + + /************************************************************************ + * Method: + * IHXDirHandler::InitDirHandler + * Purpose: + * Associates a directory handler with the directory handler + * response, it should notify of operation completness. + */ + STDMETHOD(InitDirHandler) (THIS_ + IHXDirHandlerResponse* /*IN*/ pDirResponse) PURE; + + /************************************************************************ + * Method: + * IHXDirHandler::CloseDirHandler + * Purpose: + * Closes the directory handler resource and releases all resources + * associated with the object. + */ + STDMETHOD(CloseDirHandler) (THIS) PURE; + + /************************************************************************ + * Method: + * IHXDirHandler::MakeDir + * Purpose: + * Create the directory + */ + STDMETHOD(MakeDir) (THIS) PURE; + + /************************************************************************ + * Method: + * IHXDirHandler::ReadDir + * Purpose: + * Get a dump of the directory + */ + STDMETHOD(ReadDir) (THIS) PURE; +}; + + +/**************************************************************************** + * + * Interface: + * + * IHXDirHandlerResponse + * + * Purpose: + * + * Object that exports the directory handler response API + * + * IID_IHXDirHandlerResponse: + * + * {00000216-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXDirHandlerResponse, 0x00000216, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXDirHandlerResponse + +DECLARE_INTERFACE_(IHXDirHandlerResponse, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXDirHandlerResponse methods + */ + + /************************************************************************ + * Method: + * IHXDirHandlerResponse::InitDirHandlerDone + * Purpose: + * Notification interface provided by users of the IHXDirHandler + * interface. This method is called by the IHXDirHandler when the + * initialization of the object is complete. + */ + STDMETHOD(InitDirHandlerDone) (THIS_ + HX_RESULT status) PURE; + + /************************************************************************ + * Method: + * IHXDirHandlerResponse::CloseDirHandlerDone + * Purpose: + * Notification interface provided by users of the IHXDirHandler + * interface. This method is called by the IHXDirHandler when the + * close of the directory is complete. + */ + STDMETHOD(CloseDirHandlerDone) (THIS_ + HX_RESULT status) PURE; + + /************************************************************************ + * Method: + * IHXDirHandler::MakeDirDone + * Purpose: + * Notification interface provided by users of the IHXDirHandler + * interface. This method is called by the IHXDirHandler when the + * attempt to create the directory is complete. + */ + STDMETHOD(MakeDirDone) (THIS_ + HX_RESULT status) PURE; + + /************************************************************************ + * Method: + * IHXDirHandler::ReadDirDone + * Purpose: + * Notification interface provided by users of the IHXDirHandler + * interface. This method is called by the IHXDirHandler when the + * read from the directory is complete and a buffer is available. + */ + STDMETHOD(ReadDirDone) (THIS_ + HX_RESULT status, + IHXBuffer* pBuffer) PURE; +}; + + +// $Private: +/**************************************************************************** + * + * Interface: + * + * IHXGetRecursionLevel + * + * Purpose: + * + * Set's Recursion Level + * + * IID_GetRecursionLevel: + * + * {00000218-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXGetRecursionLevel, 0x00000218, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXGetRecursionLevel + +DECLARE_INTERFACE_(IHXGetRecursionLevel, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXGetRecursionLevel methods + */ + + STDMETHOD_(UINT32, GetRecursionLevel) (THIS) PURE; +}; + +/**************************************************************************** + * + * Interface: + * + * IHXFileRestrictor + * + * Purpose: + * + * Allows restrictions on per file basis. This will only get called for + * HTTP and it will only send in the localport. Maybe some day..... + * + * IID_IHXFileRestrictor: + * + * {00000219-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXFileRestrictor, 0x00000219, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXFileRestrictor + +DECLARE_INTERFACE_(IHXFileRestrictor, IUnknown) +{ + /* IUnknown methods */ + STDMETHOD(QueryInterface) (THIS_ REFIID riid, void** ppvObj) PURE; + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* IHXFileRestrictor methods */ + STDMETHOD_(HXBOOL, IsAllowed) (THIS_ const char* url, + const char* pLocalAddr, + const char* pLocalPort, + const char* pPeerAddr, + const char* pPeerPort) PURE; +}; + +// $EndPrivate. + +/**************************************************************************** + * + * Interface: + * + * IHXFileRemove + * + * Purpose: + * + * Interface to allow removing of files. Query off of the File Object. + * Not all filesystem plugins implement this feature. + * + * IID_IHXFileRemove: + * + * {0000021A-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXFileRemove, 0x0000021A, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); +#undef INTERFACE +#define INTERFACE IHXFileRemove + +DECLARE_INTERFACE_(IHXFileRemove, IUnknown) +{ + /* + * IUnknown methods + */ + + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXFileRemove methods + */ + + /************************************************************************ + * Method: + * IHXFileRemove::Remove + * Purpose: + * Removes a file from the file system. + */ + STDMETHOD(Remove) (THIS) PURE; +}; + + +// $Private: +/**************************************************************************** + * + * Interface: + * + * IHXFastFileFactory + * + * Purpose: + * + * Interface to allow wrapping file objects with a buffer and block-sharing + * sceme. Query off the server class factory. + * + * IID_IHXFastFileFactory: + * + * {0000021C-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXFastFileFactory, 0x0000021C, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#define CLSID_IHXFastFileFactory IID_IHXFastFileFactory + +#undef INTERFACE +#define INTERFACE IHXFastFileFactory + +DECLARE_INTERFACE_(IHXFastFileFactory, IUnknown) +{ + /* + * IUnknown methods + */ + + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXFastFileFactory methods + */ + + /************************************************************************ + * Method: + * IHXFastFileFactory::Wrap + * Purpose: + * Return an instantiated wrapper around an existing (but + * uninitialized) file object. + * + */ + STDMETHOD(Wrap) (THIS_ + REF(IUnknown*) /*OUT*/ pWrapper, + IUnknown* /*IN*/ pFileObj, + UINT32 /*IN*/ ulBlockSize, + HXBOOL /*IN*/ bAlignReads, + HXBOOL /*IN*/ bCacheStats) PURE; +}; + + +/**************************************************************************** + * + * Interface: + * + * IHXFastFileFactory2 + * + * Purpose: + * + * Interface to allow wrapping file objects with a buffer and block-sharing + * sceme. Query off the server class factory. + * + * IID_IHXFastFileFactory2: + * + * {0000021F-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXFastFileFactory2, 0x0000021F, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#define CLSID_IHXFastFileFactory2 IID_IHXFastFileFactory2 + +#undef INTERFACE +#define INTERFACE IHXFastFileFactory2 + +DECLARE_INTERFACE_(IHXFastFileFactory2, IUnknown) +{ + /************************************************************************ + * Method: + * IHXFastFileFactory2::Wrap + * Purpose: + * Return an instantiated wrapper around an existing (but + * uninitialized) file object. + * + */ + STDMETHOD(Wrap) (THIS_ + REF(IUnknown*) /*OUT*/ pWrapper, + IUnknown* /*IN*/ pFileObj, + UINT32 /*IN*/ ulBlockSize, + HXBOOL /*IN*/ bAlignReads, + HXBOOL /*IN*/ bCacheStats, + UINT32 /*IN*/ ulMaxBlockSize) PURE; +}; + + +/**************************************************************************** + * + * Interface: + * + * IHXFilePlacementRead + * + * Purpose: + * + * Reads into the passed buffer + * + * IID_IHXFilePlacementRead + * + * {0000021D-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXFilePlacementRead, 0x0000021D, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXFilePlacementRead + +DECLARE_INTERFACE_(IHXFilePlacementRead, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXFilePlacementRead methods + */ + STDMETHOD(Read) (THIS_ + ULONG32 ulAmount, + ULONG32 ulOffset, + char* pBuffer, + HXBOOL bOffsetBuffer) PURE; + + STDMETHOD_(ULONG32,AlignmentBoundary) (THIS) PURE; +}; + +// $EndPrivate. + +/**************************************************************************** + * + * Interface: + * + * IHXFastFileStats + * + * Purpose: + * + * Interface to allow file objects to request that at close they be + * informed how many bytes have been "fast cached" on their behalf if any. + * + * ulFastFileBytesSaved is the number of bytes that would have been read + * from the file object had FastFile not been in use (this includes some + * lookahead + * + * ulFastFileBytesNeeded is the amount of data actually read by the file + * system. + * + * IID_IHXFastFileStats: + * + * {0000021E-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXFastFileStats, 0x0000021E, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#define CLSID_IHXFastFileStats IID_IHXFastFileStats + +#undef INTERFACE +#define INTERFACE IHXFastFileStats + +DECLARE_INTERFACE_(IHXFastFileStats, IUnknown) +{ + /* + * IUnknown methods + */ + + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXFastFileStats methods + */ + + STDMETHOD(UpdateFileObjectStats) (THIS_ + UINT32 /*IN*/ ulFastFileBytesSaved, + UINT32 /*IN*/ ulFastFileBytesNeeded) PURE; +}; + +// $Private: +/**************************************************************************** + * + * Interface: + * + * IHXHTTPPostObject + * + * Purpose: + * + * Object that exports file control API + * + * IID_IHXHTTPPostObject: + * + * {00000112-0901-11d1-8B06-00A024406D59} + * + */ + +DEFINE_GUID(IID_IHXHTTPPostObject, 0x00000112, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#define CLSID_IHXHTTPPostObject IID_IHXHTTPPostObject + +#undef INTERFACE +#define INTERFACE IHXHTTPPostObject + +DECLARE_INTERFACE_(IHXHTTPPostObject, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXHTTPPostObject methods + */ + + /************************************************************************ + * Method: + * IHXHTTPPostObject::Init + * Purpose: + * Associates a file object with the file response object it should + * notify of operation completness. This method should also check + * for validity of the object (for example by opening it if it is + * a local file). + */ + STDMETHOD(Init) (THIS_ + ULONG32 /*IN*/ ulFlags, + IHXHTTPPostResponse* /*IN*/ pFileResponse) PURE; + + /************************************************************************ + * Method: + * IHXHTTPPostObject::Close + * Purpose: + * Closes the file resource and releases all resources associated + * with the object. + */ + STDMETHOD(Close) (THIS) PURE; + + /************************************************************************ + * Method: + * IHXHTTPPostObject::GetResponse + * Purpose: + * Tells the object to retrieve any response data from the POST. + * Calls IHXHTTPPostResponse with ResponseReady(IHXValues*). + */ + STDMETHOD(GetResponse) (THIS) PURE; + + + /************************************************************************ + * Method: + * IHXHTTPPostObject::Post + * Purpose: + * Writes a buffer of data to the HTTP URL and asynchronously notifies + * the caller via the IHXFileResponse interface passed in to Init, + * of the completeness of the operation. + */ + STDMETHOD(Post) (THIS_ + IHXBuffer* pBuffer) PURE; + + /************************************************************************ + * Method: + * IHXHTTPPostObject::SetSize + * Purpose: + * Set the total size of the Post(s) about to be made. + */ + STDMETHOD(SetSize) (THIS_ + ULONG32 ulLength) PURE; +}; + + +/**************************************************************************** + * + * Interface: + * + * IHXHTTPPostResponse + * + * Purpose: + * + * Object that exports file response API + * + * IID_IHXHTTPPostResponse: + * + * {00000113-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXHTTPPostResponse, 0x00000113, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXHTTPPostResponse + +DECLARE_INTERFACE_(IHXHTTPPostResponse, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXHTTPPostResponse methods + */ + + /************************************************************************ + * Method: + * IHXHTTPPostResponse::InitDone + * Purpose: + * Notification interface provided by users of the IHXHTTPPostObject + * interface. This method is called by the IHXHTTPPostObject when the + * initialization of the file is complete. If the file is not valid + * for the file system, the status HXR_FAILED should be + * returned. + */ + STDMETHOD(InitDone) (THIS_ + HX_RESULT status) PURE; + + /************************************************************************ + * Method: + * IHXHTTPPostResponse::CloseDone + * Purpose: + * Notification interface provided by users of the IHXHTTPPostObject + * interface. This method is called by the IHXHTTPPostObject when the + * close of the file is complete. + */ + STDMETHOD(CloseDone) (THIS_ + HX_RESULT status) PURE; + + /************************************************************************ + * Method: + * IHXHTTPPostResponse::ResponseReady + * Purpose: + * Notification interface provided by users of the IHXHTTPPostObject + * interface. This method is called by the IHXHTTPPostObject when the + * POST response data has been completely read. + */ + STDMETHOD(ResponseReady) (THIS_ + HX_RESULT status, + IHXBuffer* pContentBuffer) PURE; + + /************************************************************************ + * Method: + * IHXHTTPPostResponse::WriteDone + * Purpose: + * Notification interface provided by users of the IHXHTTPPostObject + * interface. This method is called by the IHXHTTPPostObject when the + * last write to the file is complete. + */ + STDMETHOD(PostDone) (THIS_ + HX_RESULT status) PURE; +}; +// $EndPrivate. + + +// $Private: +/**************************************************************************** + * + * Interface: + * + * IHXHTTPRedirect + * + * Purpose: + * + * Allows you to get redirect URL + * + * IID_IHXHTTPRedirect: + * + * {00002E00-0901-11d1-8B06-00A024406D59} + * + */ + +DEFINE_GUID(IID_IHXHTTPRedirect, 0x21eae0b9, 0x2e0c, 0x4003, 0xbb, 0x79, + 0xbc, 0x8c, 0xc5, 0x67, 0x2c, 0x2d); + +#undef INTERFACE +#define INTERFACE IHXHTTPRedirect + +DECLARE_INTERFACE_(IHXHTTPRedirect, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXHTTPRedirect methods + */ + + /************************************************************************ + * Method: + * IHXHTTPRedirect::Init + * Purpose: + * Initialize the response object + */ + STDMETHOD(Init) (THIS_ + IHXHTTPRedirectResponse* pRedirectResponse) PURE; + + /************************************************************************ + * Method: + * IHXHTTPRedirect::SetResponseObject + * Purpose: + * Initialize the response object w/o calling Init + */ + STDMETHOD(SetResponseObject) (THIS_ + IHXHTTPRedirectResponse* pRedirectResponse) PURE; + +}; + +/**************************************************************************** + * + * Interface: + * + * IHXHTTPRedirectResponse + * + * Purpose: + * + * Allows you to get redirect URL + * + * IID_IHXHTTPRedirectResponse: + * + * {00002E01-0901-11d1-8B06-00A024406D59} + * + */ + +DEFINE_GUID(IID_IHXHTTPRedirectResponse, 0x125a63b1, 0x669c, 0x42f9, 0xb1, + 0xf9, 0x1b, 0x53, 0xe9, 0x95, 0x82, 0x95); + +#undef INTERFACE +#define INTERFACE IHXHTTPRedirectResponse + +DECLARE_INTERFACE_(IHXHTTPRedirectResponse, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXHTTPRedirectResponse methods + */ + + /************************************************************************ + * Method: + * IHXHTTPRedirectResponse::RedirectDone + * Purpose: + * return the redirect URL + */ + STDMETHOD(RedirectDone) (THIS_ + IHXBuffer* pURL) PURE; + +}; + +/**************************************************************************** + * + * Interface: + * + * IHXRM2Converter2 + * + * Purpose: + * + * Interface to RM1->RM2 merge/converter module. This is a new improved + * interface which contains all of the functionality of the old one. + * However, this one is asynchronous where necessary and takes standard + * IHXFileObjects instead of pathnames for the required files. + * + * IHXRM2Converter + * + * {00002F00-0901-11D1-8B06-00A024406D59} + */ + +DEFINE_GUID(IID_IHXRM2Converter2, 0x00002F00, 0x901, 0x11d1, 0x8b, + 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#define CLSID_IRMARM2Converter2 IID_IHXRM2Converter2 + +#undef INTERFACE +#define INTERFACE IHXRM2Converter2 + +DECLARE_INTERFACE_(IHXRM2Converter2, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXRM2Converter2 methods + */ + STDMETHOD(Init) (THIS_ + IHXRM2Converter2Response* pResponse) PURE; + // + // AddStream takes a stream from an RM1 file and + // adds it to the new RM2 multirate section + // + STDMETHOD(AddStream) (THIS_ + const char* pFilename, + IHXFileObject* pFileObject, + UINT16 nStreamNumber, + HXBOOL bTagAsBackwardCompatible) PURE; + + // + // AddInterleavedStream takes a stream from an + // RM1 file and adds it to the initial interleaved + // backward compatibility section + // + STDMETHOD(AddInterleavedStream) (THIS_ + const char* pFilename, + IHXFileObject* pFileObject, + UINT16 nStreamNumber) PURE; + + // + // Add file slurps down all of the streams in + // an RM1 file and adds them to the new RM2 + // multirate section. + // + STDMETHOD(AddFile) (THIS_ + const char* pFilename, + IHXFileObject* pFileObject) PURE; + + // + // SetTitle,Author,Copyright, and Comment + // should all be pretty self explanatory... + // + STDMETHOD(SetTitle) (THIS_ + const char* pTitle) PURE; + STDMETHOD(SetAuthor) (THIS_ + const char* pAuthor) PURE; + STDMETHOD(SetCopyright) (THIS_ + const char* pCopyright) PURE; + STDMETHOD(SetComment) (THIS_ + const char* pComment) PURE; + + // + // PairStreams lets you pair two different + // streams together to play at a particular + // bandwidth (i.e. the sum of the stream + // bandwidths involved). + // + STDMETHOD(PairStreams) (THIS_ + const char* pFilename1, + UINT16 nStreamNumber1, + const char* pFilename2, + UINT16 nStreamNumber2) PURE; + + STDMETHOD(Merge) (THIS_ + const char* pFilename, + IHXFileObject* pOutputFile, + UINT32 ulBytesToWrite) PURE; + + STDMETHOD(Reset) (THIS) PURE; + STDMETHOD(Done) (THIS) PURE; +}; + + +/**************************************************************************** + * + * Interface: + * + * IHXRM2Converter2Response + * + * Purpose: + * + * Response Interface for IHXRM2Converter2. + * + * IHXRM2Converter + * + * {00002F01-0901-11D1-8B06-00A024406D59} + */ + +DEFINE_GUID(IID_IHXRM2Converter2Response, 0x00002F01, 0x901, 0x11d1, 0x8b, + 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59); + + +#undef INTERFACE +#define INTERFACE IHXRM2Converter2Response + +DECLARE_INTERFACE_(IHXRM2Converter2Response, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXRM2Converter2Response methods + */ + STDMETHOD(InitDone) (THIS_ + HX_RESULT status) PURE; + STDMETHOD(AddStreamDone) (THIS_ + HX_RESULT status) PURE; + STDMETHOD(AddInterleavedStreamDone) (THIS_ + HX_RESULT status) PURE; + STDMETHOD(AddFileDone) (THIS_ + HX_RESULT status) PURE; + STDMETHOD(MergeDone) (THIS_ + HX_RESULT status) PURE; +}; + + +/**************************************************************************** + * + * Interface: + * + * IHXPoolPathAdjustment + * + * Purpose: + * + * For file systems that use GetFileObjectFromPool to properly + * adjust absolute local URLs so they will be accessed relative + * to the pool filesystem mountpoint. + * + * IID_IHXPoolPathAdjustment: + * + * {00002F02-0901-11d1-8b06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXPoolPathAdjustment, 0x00002F02, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXPoolPathAdjustment + +DECLARE_INTERFACE_(IHXPoolPathAdjustment, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXPoolPathAdjustment Methods. + */ + STDMETHOD(AdjustAbsolutePath) (THIS_ + IHXBuffer* /*IN*/ pOldPath, + REF(IHXBuffer*) /*OUT*/ pNewPath) PURE; + +}; + +/* + * Interface: + * IHXPostDataHandler + * + * Purpose: + * + * Allows a file object to receive out of band POST data + * from an HTTP request. If this interface is not implemented + * data will be placed in request header as "PostData" + * + * When there is no more Data remaining, PostData will be + * called with a NULL argument. + * + * IID_IHXPostDataHandler: + * + * {0x0222a1b5-49fc-47e2-b69098befa0a588e} + * + */ +DEFINE_GUID(IID_IHXPostDataHandler, 0x0222a1b5, 0x49fc, 0x47e2, 0xb6, + 0x90, 0x98, 0xbe, 0xfa, 0x0a, 0x58, 0x8e); + +#undef INTERFACE +#define INTERFACE IHXPostDataHandler + +DECLARE_INTERFACE_(IHXPostDataHandler, IUnknown) +{ + /* + * IUnknownMethods + */ + + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + /* + * IHXPostDataHandler methods + */ + STDMETHOD(PostData) (THIS_ + IHXBuffer* pBuf) PURE; +}; + +// $EndPrivate. + +#endif /* _HXFILES_H_ */ diff --git a/amarok/src/engine/helix/helix-sp/helix-include/common/include/hxiids.h b/amarok/src/engine/helix/helix-sp/helix-include/common/include/hxiids.h new file mode 100644 index 00000000..ddcf9b33 --- /dev/null +++ b/amarok/src/engine/helix/helix-sp/helix-include/common/include/hxiids.h @@ -0,0 +1,1899 @@ + +/* + * + * This software is released under the provisions of the GPL version 2. + * see file "COPYING". If that file is not available, the full statement + * of the license can be found at + * + * http://www.fsf.org/licensing/licenses/gpl.txt + * + * Portions Copyright (c) 1995-2004 RealNetworks, Inc. All Rights Reserved. + * + */ + +/**************************************************************************** + * + * Exhaustive list of IID's used in IHX interfaces + * + * Note: These IIDs generally are duplicated in the headers that are specific + * to each interface, so if you change this file, change the other file(s) as + * well. Having all these IIDS in one files is convenient to some folks, but + * not everyone includes this file, hence the need to keep them in individual + * files as well. + */ + +#ifndef _HXIIDS_H_ +#define _HXIIDS_H_ + +/* + * File: + * hxcom.h + * Description: + * Interfaces defined by COM. + * Interfaces: + * IID_IUnknown: {00000000-0000-0000-C000000000000046} + * IID_IMalloc: {00000002-0000-0000-C000000000000046} + */ + +/* + * These GUIDs are defined in hxcom.h: + * + * DEFINE_GUID_ENUM(IID_IUnknown, 0x00000000, 0x0000, 0x0000, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46) + * DEFINE_GUID_ENUM(IID_IMalloc, 0x00000002, 0x0000, 0x0000, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46) + * + */ + +/* + * File: + * hxcomm.h + * Description: + * RealMedia Common Utility interfaces + * Interfaces: + * IID_IHXCommonClassFactory: {00000000-0901-11d1-8B06-00A024406D59} + * IID_IHXStatistics: {00000001-0901-11d1-8B06-00A024406D59} + * IID_IHXRegistryID: {00000002-0901-11d1-8B06-00A024406D59} + * IID_IHXServerFork: {00000003-0901-11d1-8B06-00A024406D59} + * IID_IHXServerControl: {00000004-0901-11d1-8B06-00A024406D59} + * IID_IHXServerControl2: {00000005-0901-11d1-8B06-00A024406D59} + * IID_IHXReconfigServerResponse: {00000006-0901-11d1-8B06-00A024406D59} +// $Private: + * IID_IHXFastAlloc: {0000000a-0901-11d1-8B06-00A024406D59} + * IID_IHXAccurateClock: {0000000b-0901-11d1-8B06-00A024406D59} +// $EndPrivate. + */ + +#ifndef _HXCCF_H_ +DEFINE_GUID_ENUM(IID_IHXCommonClassFactory, 0x00000000, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +#endif /* _HXCCF_H_ */ + +#ifndef _HXCOMM_H_ +DEFINE_GUID_ENUM(IID_IHXStatistics, 0x00000001, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXRegistryID, 0x00000002, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXServerFork, 0x00000003, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXServerControl, 0x00000004, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXServerControl2, 0x00000005, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXReconfigServerResponse, 0x00000006, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXServerReconfigNotification, 0x00000007, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXWantServerReconfigNotification, 0x00000008, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +// $Private: +DEFINE_GUID_ENUM(IID_IHXResolverExec, 0x00000009, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +// $Private: +DEFINE_GUID_ENUM(IID_IHXFastAlloc, 0x0000000a, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXAccurateClock, 0x0000000b, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +// $EndPrivate. +#endif + +/* + * File: + * hxengin.h + * Description: + * Interfaces related to callbacks, networking, and scheduling. + * Interfaces: + * IID_IHXCallback: {00000100-0901-11d1-8B06-00A024406D59} + * IID_IHXScheduler: {00000101-0901-11d1-8B06-00A024406D59} + * IID_IHXTCPResponse: {00000102-0901-11d1-8B06-00A024406D59} + * IID_IHXTCPSocket: {00000103-0901-11d1-8B06-00A024406D59} + * IID_IHXListenResponse: {00000104-0901-11d1-8B06-00A024406D59} + * IID_IHXListenSocket: {00000105-0901-11d1-8B06-00A024406D59} + * IID_IHXNetworkServices: {00000106-0901-11d1-8B06-00A024406D59} + * IID_IHXUDPResponse: {00000107-0901-11d1-8B06-00A024406D59} + * IID_IHXUDPSocket: {00000108-0901-11d1-8B06-00A024406D59} + * IID_IHXResolver: {00000109-0901-11d1-8B06-00A024406D59} + * IID_IHXResolverResponse: {0000010A-0901-11d1-8B06-00A024406D59} + * IID_IHXInterruptSafe: {0000010B-0901-11d1-8B06-00A024406D59} + * IID_IHXAsyncIOSelection: {0000010C-0901-11d1-8B06-00A024406D59} + * IID_IHXUDPMulticastInit: {0000010D-0901-11d1-8B06-00A024406D59} + * IID_IHXInterruptState: {0000010E-0901-11d1-8B06-00A024406D59} + * IID_IHXOptimizedScheduler: {0000010F-0901-11d1-8B06-00A024406D59} + * IID_IHXLoadBalancedListen: {00000110-0901-11d1-8B06-00A024406D59} + * IID_IHXOverrideDefaultServices: {00000111-0901-11d1-8B06-00A024406D59} + * IID_IHXHTTPPostObject: {00000112-0901-11d1-8B06-00A024406D59} + * IID_IHXHTTPPostResponse: {00000113-0901-11d1-8B06-00A024406D59} + * IID_IHXSetSocketOption: {00000114-0901-11d1-8B06-00A024406D59} + * IID_IHXThreadSafeMethods: {00000115-0901-11d1-8B06-00A024406D59} + * IID_IHXMutex: {00000116-0901-11d1-8B06-00A024406D59} + * IID_IHXNetworkInterfaceEnumerator{00000121-0901-11d1-8B06-00A024406D59} + * IID_IHXUDPConnectedSocket {0000012A-0901-11d1-8B06-00A024406D59} +// $Private: + * IID_IHXFastPathNetWrite: {00000117-0901-11d1-8B06-00A024406D59} + * IID_IHXWouldBlockResponse: {00000118-0901-11d1-8B06-00A024406D59} + * IID_IHXWouldBlock: {00000119-0901-11d1-8B06-00A024406D59} + * IID_IHXThreadSafeScheduler:{00000120-0901-11d1-8B06-00A024406D59} + * IID_IHXProcess: {00000122-0901-11d1-8B06-00A024406D59} + * IID_IHXProcessEntryPoint: {00000123-0901-11d1-8B06-00A024406D59} + * IID_IHXSharedUDPServices: {00000124-0901-11d1-8B06-00A024406D59} + * IID_IHXThreadLocal: {00000125-0901-11d1-8B06-00A024406D59} + * IID_IHXMemoryServices: {00000126-0901-11d1-8B06-00A024406D59} + * IID_IHXNetInterfaces: {00000128-0901-11d1-8B06-00A024406D59} + * IID_IHXNetInterfacesAdviseSink: {00000129-0901-11d1-8B06-00A024406D59} +// $EndPrivate. + * IID_IHXAutoBWDetection: {0000012b-0901-11d1-8B06-00A024406D59} + * IID_IHXAutoBWDetectionAdviseSink: {0000012c-0901-11d1-8B06-00A024406D59} + * + */ +#ifndef _HXENGIN_H_ +DEFINE_GUID_ENUM(IID_IHXCallback, 0x00000100, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXScheduler, 0x00000101, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXTCPResponse, 0x00000102, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXTCPSocket, 0x00000103, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXListenResponse, 0x00000104, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXListenSocket, 0x00000105, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXNetworkServices, 0x00000106, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXNetworkServices2, 0x17951551, 0x5683, 0x11d3, 0xb6, 0xba, 0x0, 0xc0, 0xf0, 0x31, 0xc2, 0x37) +DEFINE_GUID_ENUM(IID_IHXUDPResponse, 0x00000107, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXUDPSocket, 0x00000108, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXResolver, 0x00000109, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXResolverResponse, 0x0000010A, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXInterruptSafe, 0x0000010B, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXAsyncIOSelection, 0x0000010C, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXUDPMulticastInit, 0x0000010D, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXInterruptState, 0x0000010E, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXOptimizedScheduler, 0x0000010F, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXLoadBalancedListen, 0x00000110, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXOverrideDefaultServices, 0x00000111, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXSetSocketOption, 0x00000114, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXThreadSafeMethods, 0x00000115, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXMutex, 0x00000116, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXNetworkInterfaceEnumerator,0x00000121, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXUDPConnectedSocket, 0x0000012a, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +// $Private: +DEFINE_GUID_ENUM(IID_IHXFastPathNetWrite, 0x00000117, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXWouldBlockResponse, 0x00000118, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXWouldBlock, 0x00000119, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXThreadSafeScheduler,0x00000120, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXProcess, 0x00000122, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXProcessEntryPoint, 0x00000123, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXSharedUDPServices, 0x00000124, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXThreadLocal, 0x00000125, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXMemoryServices, 0x00000126, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXSetPrivateSocketOption,0x00000127, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXNetInterfaces, 0x00000128, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXNetInterfacesAdviseSink, 0x00000129, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXAutoBWDetection, 0x0000012b, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXAutoBWDetectionAdviseSink, 0x0000012c, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXAutoBWCalibration, 0x0000012d, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXAutoBWCalibrationAdviseSink, 0x0000012e, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXConnectionBWInfo, 0x9d1edfb0, 0x7a10, 0x43f1, 0xb0, 0x8, 0x8d, 0xe, 0x0, 0xca, 0x27, 0x9f) +DEFINE_GUID_ENUM(IID_IHXConnectionBWAdviseSink, 0x7568b47f, 0xc1a, 0x4099, 0xb8, 0x4b, 0xd4, 0x25, 0xc9, 0x74, 0x67, 0x37) +DEFINE_GUID_ENUM(IID_IHXSSL, 0x34e171d4, 0xa8f0, 0x4832, 0xbc, 0x7d, 0x06, 0xdf, 0xe3, 0xae, 0x58, 0xfd) +DEFINE_GUID_ENUM(IID_IHXTCPSecureSocket, 0x00000203, 0x911, 0x21d1, 0x8c, 0x4, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x54) +// $EndPrivate. +#endif + + +/* + * File: + * hxfiles.h + * Description: + * Interfaces related to file systems. + * Interfaces: + * IID_IHXFileObject: {00000200-0901-11d1-8B06-00A024406D59} + * IID_IHXFileObjectExt: {96dd5eb5-7efd-4084-95cd-4d192a9036af} + * IID_IHXFileResponse: {00000201-0901-11d1-8B06-00A024406D59} + * IID_IHXAdvise: {43C3A3B8-8F76-4394-A4F8-07AA9091A0CA} + * IID_IHXFileSystemObject: {00000202-0901-11d1-8B06-00A024406D59} + * IID_IHXDirObject: {00000203-0901-11d1-8B06-00A024406D59} + * IID_IHXDirResponse: {00000204-0901-11d1-8B06-00A024406D59} + * IID_IHXFileStat: {00000205-0901-11d1-8B06-00A024406D59} + * IID_IHXFileStatResponse: {00000206-0901-11d1-8B06-00A024406D59} + * IID_IHXFileSystemManager: {00000207-0901-11d1-8B06-00A024406D59} + * IID_IHXFileSystemManagerResponse: + * {00000208-0901-11d1-8B06-00A024406D59} + * IID_IHXFileExists: {00000209-0901-11d1-8B06-00A024406D59} + * IID_IHXFileExistsResponse: {0000020A-0901-11d1-8B06-00A024406D59} + * IID_IHXFileMimeMapper: {0000020B-0901-11d1-8B06-00A024406D59} + * IID_IHXFileMimeMapperResponse: {0000020C-0901-11d1-8B06-00A024406D59} + * IID_IHXBroadcastMapper: {0000020D-0901-11d1-8B06-00A024406D59} + * IID_BroadcastMimeMapperResponse:{0000020E-0901-11d1-8B06-00A024406D59} + * IID_IHXGetFileFromSamePool: {0000020F-0901-11d1-8B06-00A024406D59} + * IID_GetFileFromSamePoolResponse:{00000210-0901-11d1-8B06-00A024406D59} + * IID_IHXFileAuthenticator: {00000211-0901-11d1-8B06-00A024406D59} + * IID_IHXRequestHandler: {00000212-0901-11d1-8B06-00A024406D59} + * IID_IHXRequest: {00000213-0901-11d1-8B06-00A024406D59} + * IID_IHXFileRename: {00000214-0901-11d1-8B06-00A024406D59} + * IID_IHXFileMove: {23E72FB0-DE0E-11d5-AA9A-00010251B340} + * IID_IHXDirHandler: {00000215-0901-11d1-8B06-00A024406D59} + * IID_IHXDirHandlerResponse: {00000216-0901-11d1-8B06-00A024406D59} + * IID_IHXRequestContext {00000217-0901-11d1-8B06-00A024406D59} +// $Private: + * IID_IHXGetRecursionLevel {00000218-0901-11d1-8B06-00A024406D59} + * IID_IHXFileRestrictor {00000219-0901-11d1-8B06-00A024406D59} +// $EndPrivate. + * IID_IHXFileRemove: {0000021A-0901-11d1-8B06-00A024406D59} + * DEPRECATED DEPRECATED {0000021B-0901-11d1-8B06-00A024406D59} +// $Private: + * IID_IHXFastFileFactory {0000021C-0901-11d1-8B06-00A024406D59} + * IID_IHXFilePlacementRead {0000021D-0901-11d1-8B06-00A024406D59} + * IID_IHXFastFileStats {0000021E-0901-11d1-8B06-00A024406D59} + * IID_IHXFastFileFactory2 {0000021F-0901-11d1-8B06-00A024406D59} +// $EndPrivate. + * + */ +#ifndef _HXFILES_H_ +DEFINE_GUID_ENUM(IID_IHXHTTPPostObject, 0x00000112, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXHTTPPostResponse, 0x00000113, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXFileObject, 0x00000200, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXFileObjectExt, 0x96dd5eb5, 0x7efd, 0x4084, 0x95, 0xcd, 0x4d, 0x19, 0x2a, 0x90, 0x36, 0xaf) +DEFINE_GUID_ENUM(IID_IHXFileResponse, 0x00000201, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXAdvise, 0x43c3a3b8, 0x8f76, 0x4394, 0xa4, 0xf8, 0x7, 0xaa, 0x90, 0x91, 0xa0, 0xca) +DEFINE_GUID_ENUM(IID_IHXFileSystemObject, 0x00000202, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXDirObject, 0x00000203, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) //NOTE, use is deprecated +DEFINE_GUID_ENUM(IID_IHXDirResponse, 0x00000204, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) //NOTE, use is deprecated +DEFINE_GUID_ENUM(IID_IHXFileStat, 0x00000205, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXFileStatResponse, 0x00000206, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXFileSystemManager, 0x00000207, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXFileSystemManagerResponse, 0x00000208, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXFileExists, 0x00000209, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXFileExistsResponse, 0x0000020a, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXFileMimeMapper, 0x0000020b, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXFileMimeMapperResponse, 0x0000020c, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXBroadcastMapper, 0x0000020d, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXBroadcastMapperResponse, 0x0000020e, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXGetFileFromSamePool, 0x0000020f, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXGetFileFromSamePoolResponse,0x00000210, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXFileAuthenticator, 0x00000211, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXRequestHandler, 0x00000212, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXRequest, 0x00000213, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXFileRename, 0x00000214, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXFileMove, 0x23e72fb0, 0xde0e, 0x11d5, 0xaa, 0x9a, 0x0, 0x1, 0x2, 0x51, 0xb3, 0x40) +DEFINE_GUID_ENUM(IID_IHXDirHandler, 0x00000215, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXDirHandlerResponse, 0x00000216, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXRequestContext, 0x00000217, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +// $Private: +DEFINE_GUID_ENUM(IID_IHXGetRecursionLevel, 0x00000218, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXFileRestrictor, 0x00000219, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +// $EndPrivate. +DEFINE_GUID_ENUM(IID_IHXFileRemove, 0x0000021a, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +// $Private: +DEFINE_GUID_ENUM(IID_IHXFastFileFactory, 0x0000021c, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXFilePlacementRead, 0x0000021d, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXFastFileStats, 0x0000021e, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXFastFileFactory2, 0x0000021f, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +// $EndPrivate. +DEFINE_GUID_ENUM(IID_IHXFileRecognizer, 0x00000220, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXFileRecognizerResponse, 0x00000221, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +#endif +/* + * File: + * hxrendr.h + * Description: + * Interfaces related to renderers. + * Interfaces: + * IID_IHXRenderer: {00000300-0901-11d1-8B06-00A024406D59} + * IID_IHXPersistentRenderer: {00000301-0901-11d1-8B06-00A024406D59} + * IID_IHXUntimedRenderer: {00000303-0901-11d1-8B06-00A024406D59} + */ +DEFINE_GUID_ENUM(IID_IHXRenderer, 0x00000300, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +// $Private: +DEFINE_GUID_ENUM(IID_IHXPersistentRenderer, 0x00000301, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +// $EndPrivate. +DEFINE_GUID_ENUM(IID_IHXUntimedRenderer, 0x00000303, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) + + +/* + * File: + * hxcore.h + * Description: + * Interfaces related to the client core services. + * Interfaces: + * IID_IHXStream: {00000400-0901-11d1-8B06-00A024406D59} + * IID_IHXStreamSource {00000401-0901-11d1-8B06-00A024406D59} + * IID_IHXPlayer: {00000402-0901-11d1-8B06-00A024406D59} + * IID_IHXClientEngine: {00000403-0901-11d1-8B06-00A024406D59} + * IID_IHXClientEngineSelector{00000404-0901-11d1-8B06-00A024406D59} + * IID_IHXClientEngineSetup: {00000405-0901-11d1-8B06-00A024406D59} + * : {00000406-0901-11d1-8B06-00A024406D59} -- Deprecated + * IID_IHXInfoLogger: {00000409-0901-11d1-8B06-00A024406D59} +// $Private: + * IID_IHXPersistenceManager: {0000040B-0901-11d1-8B06-00A024406D59} + * IID_IHXDriverStreamManager:{0000040C-0901-11d1-8B06-00A024406D59} + * IID_IHXRendererAdviseSink: {0000040D-0901-11d1-8B06-00A024406D59} + * IID_IHXLayoutStream: {0000040E-0901-11d1-8B06-00A024406D59} + * IID_IHXValidator: {00000412-0901-11d1-8B06-00A024406D59} +// $EndPrivate. + * {0000040F-0901-11d1-8B06-00A024406D59} -- Deprecated + * IID_IHXPlayer2: {00000411-0901-11d1-8B06-00A024406D59} + * IID_IHXPlayerNavigator: {00000414-0901-11d1-8B06-00A024406D59} + * IID_IHXPersistentComponentManager: {00000415-0901-11d1-8B06-00A024406D59} + * IID_IHXPersistentComponent: {00000416-0901-11d1-8B06-00A024406D59} + */ +#ifndef _HXCORE_H_ +DEFINE_GUID_ENUM(IID_IHXStream, 0x00000400, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXStream2, 0x00000400, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x5a) +DEFINE_GUID_ENUM(IID_IHXStreamSource, 0x00000401, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXPlayer, 0x00000402, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXClientEngine, 0x00000403, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +#ifdef _UNIX +DEFINE_GUID_ENUM(IID_IHXClientEngineSelector, 0x00000404, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +#endif +DEFINE_GUID_ENUM(IID_IHXClientEngineSetup, 0x00000405, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXInfoLogger, 0x00000409, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +// $Private: +DEFINE_GUID_ENUM(IID_IHXClientEngineMapper, 0x0000040A, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXPersistenceManager, 0x0000040B, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXDriverStreamManager, 0x0000040C, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXPrivateStreamSource, 0x57dfd0e2, 0xc76e, 0x11d1, 0x8b, 0x5c, 0x0, 0x60, 0x8, 0x6, 0x55, 0x52) +DEFINE_GUID_ENUM(IID_IHXRendererAdviseSink, 0x0000040D, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXLayoutStream, 0x0000040E, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXRendererUpgrade, 0x00000410, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXValidator, 0x00000412, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +// $EndPrivate. +DEFINE_GUID_ENUM(IID_IHXPlayer2, 0x00000411, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXUpdateProperties, 0x00000413, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXUpdateProperties2, 0x00000413, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x5a) +DEFINE_GUID_ENUM(IID_IHXPlayerNavigator, 0x00000414, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXPersistentComponentManager, 0x00000415, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXPersistentComponent, 0x00000416, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +// $Private: +DEFINE_GUID_ENUM(IID_IHXSourceBufferingStats, 0x00000418, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXSourceBufferingStats2, 0x00000418, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x5a) +// $EndPrivate. +DEFINE_GUID_ENUM(IID_IHXSourceLatencyStats, 0x7A4D7872, 0xE5A9, 0x11D8, 0xAB, 0xE7, 0x00, 0x0A, 0x95, 0xBE, 0xFE, 0x6C) +#endif +/* + * File: + * hxprefs.h + * Description: + * Interfaces related to persistent preferences services. + * Interfaces: + * IID_IHXPreferences: {00000500-0901-11d1-8B06-00A024406D59} + */ +#ifndef _HXPREFS_H_ +DEFINE_GUID_ENUM(IID_IHXPreferences, 0x00000500, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXPreferences2, 0x00000503, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXPreferenceEnumerator, 0x00000504, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59) +#endif + +/* + * File: + * hxmon.h + * Description: + * Interfaces related to Monitor plugins. + * Interfaces: + * IID_IHXRegistry: {00000600-0901-11d1-8B06-00A024406D59} + * IID_IHXPropWatch: {00000601-0901-11d1-8B06-00A024406D59} + * IID_IHXPropWatchResponse: {00000602-0901-11d1-8B06-00A024406D59} + * IID_IHXActiveRegistry: {00000603-0901-11d1-8B06-00A024406D59} + * IID_IHXActivePropUser: {00000604-0901-11d1-8B06-00A024406D59} + * IID_IHXActivePropUserResponse: {00000605-0901-11d1-8B06-00A024406D59} + * IID_IHXCopyRegistry: {00000606-0901-11d1-8B06-00A024406D59} + * IID_IHXRegistryAltStringHandling: {00000607-0901-11d1-8B06-00A024406D59} + * IID_IHXRegistry2: {00000608-0901-11d1-8B06-00A024406D59} +// $Private: + * IID_IHXDeletePropResponse: {00000609-0901-11d1-8B06-00A024406D59} +// $EndPrivate. + */ +#ifndef _HXMON_H_ +DEFINE_GUID_ENUM(IID_IHXRegistry, 0x00000600, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXPropWatch, 0x00000601, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXPropWatchResponse, 0x00000602, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXActiveRegistry, 0x00000603, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXActivePropUser, 0x00000604, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXActivePropUserResponse, 0x00000605, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXCopyRegistry, 0x00000606, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXRegistryAltStringHandling,0x00000607, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXRegistry2, 0x00000608, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +// $Private: +DEFINE_GUID_ENUM(IID_IHXDeletedPropResponse, 0x00000609, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +// $EndPrivate. +#endif +/* + * File: + * hxausvc.h + * Description: + * Interfaces related to audio services. + * Interfaces: + * IID_IHXAudioPlayer: {00000700-0901-11d1-8B06-00A024406D59} + * IID_IHXAudioPlayerResponse: {00000701-0901-11d1-8B06-00A024406D59} + * IID_IHXAudioStream: {00000702-0901-11d1-8B06-00A024406D59} + * IID_IHXAudioDevice: {00000703-0901-11d1-8B06-00A024406D59} + * IID_IHXAudioDeviceResponse: {00000704-0901-11d1-8B06-00A024406D59} + * IID_IHXAudioHook: {00000705-0901-11d1-8B06-00A024406D59} + * IID_IHXAudioStreamInfoResponse: {00000706-0901-11d1-8B06-00A024406D59} + * IID_IHXVolume: {00000707-0901-11d1-8B06-00A024406D59} + * IID_IHXVolumeAdviseSink: {00000708-0901-11d1-8B06-00A024406D59} + * IID_IHXDryNotification: {00000709-0901-11d1-8B06-00A024406D59} + * IID_IHXAudioDeviceManager: {0000070A-0901-11d1-8B06-00A024406D59} + * IID_IHXAudioCrossFade: {0000070B-0901-11d1-8B06-00A024406D59} + * IID_IHXAudioStream2: {0000070C-0901-11d1-8B06-00A024406D59} + * IID_IHXAudioPushdown: {0000070D-0901-11d1-8B06-00A024406D59} + * IID_IHXAudioHookManager: {0000070E-0901-11d1-8B06-00A024406D59} + * IID_IHXAudioDeviceHookManager: {00000715-0901-11d1-8B06-00A024406D59} + * IID_IHXAudioLevelNormalization: {00000716-0901-11d1-8B06-00A024406D59} +// $Private: + * IID_IHXMultiPlayPauseSupport: {0000070F-0901-11d1-8B06-00A024406D59} + * IID_IHXAudioDeviceManager2: {00000710-0901-11d1-8B06-00A024406D59} + * IID_IHXAudioResampler: {00000711-0901-11d1-8b06-00A024406d59} + * IID_IHXAudioResamplerManager: {00000712-0901-11d1-8b06-00A024406d59} + * IID_IHXAudioPushdown2: {00000713-0901-11d1-8b06-00A024406d59} + * IID_IHXAudioMultiChannel: {00000714-0901-11d1-8b06-00A024406d59} +// $EndPrivate. + */ +DEFINE_GUID_ENUM(IID_IHXAudioPlayer, 0x00000700, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXAudioPlayerResponse, 0x00000701, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXAudioStream, 0x00000702, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXAudioDevice, 0x00000703, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXAudioDeviceResponse, 0x00000704, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXAudioHook, 0x00000705, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXAudioStreamInfoResponse, 0x00000706, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXVolume, 0x00000707, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXVolumeAdviseSink, 0x00000708, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXDryNotification, 0x00000709, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXAudioDeviceManager, 0x0000070A, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXAudioCrossFade, 0x0000070B, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXAudioStream2, 0x0000070C, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXAudioPushdown, 0x0000070D, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXAudioHookManager, 0x0000070E, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXAudioDeviceHookManager, 0x00000715, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXAudioLevelNormalization, 0x00000716, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) + +#if defined(HELIX_FEATURE_TIMELINE_WATCHER) +DEFINE_GUID_ENUM(IID_IHXTimelineWatcher, 0x211a3cae, 0xf1da, 0x4678, 0x84, 0xd5, 0xf, 0x12, 0xe7, 0xb1, 0xd8, 0xc6) +DEFINE_GUID_ENUM(IID_IHXTimelineManager, 0x9ed91bc3, 0x9e92, 0x46bb, 0xa0, 0x94, 0x6c, 0x8b, 0x94, 0x16, 0xcf, 0xb6) +#endif + +// $Private: +DEFINE_GUID_ENUM(IID_IHXMultiPlayPauseSupport, 0x0000070F, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXAudioDeviceManager2, 0x00000710, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXAudioResampler, 0x00000711, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXAudioResamplerManager, 0x00000712, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXAudioPushdown2, 0x00000713, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXAudioMultiChannel, 0x00000714, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +// $EndPrivate. + +/* + * File: + * hxerror.h + * Description: + * Interfaces related to error reporting and receiving notification of errors. + * Interfaces: + * IID_IHXErrorMessages: {00000800-0901-11d1-8B06-00A024406D59} + * IID_IHXErrorSink: {00000801-0901-11d1-8B06-00A024406D59} + * IID_IHXErrorSinkControl: {00000802-0901-11d1-8B06-00A024406D59} + */ +#ifndef _HXERROR_H_ +DEFINE_GUID_ENUM(IID_IHXErrorMessages, 0x00000800, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXErrorSink, 0x00000801, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXErrorSinkControl, 0x00000802, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +#endif +/* + * File: + * hxhyper.h + * Description: + * Simple Hyper Navigation Interfaces + * Interfaces: + * IID_IHXHyperNavigate: {00000900-0901-11d1-8B06-00A024406D59} + * IID_IHXHyperNavigate2: {00000901-0901-11d1-8B06-00A024406D59} + * IID_IHXHyperNavigateWithContext: {00000902-0901-11d1-8B06-00A024406D59} + */ +DEFINE_GUID_ENUM(IID_IHXHyperNavigate, 0x00000900, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +// $Private: +// will be made public post redstone beta 1 XXXRA +DEFINE_GUID_ENUM(IID_IHXHyperNavigate2, 0x00000901, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXHyperNavigateWithContext, 0x00000902, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXHyperNavigateHint, 0xd6507709, 0xf344, 0x4011, 0x94, 0xee, 0x57, 0x37, 0xd3, 0x78, 0xec, 0x4a) +// $EndPrivate. + +/* + * File: + * hxclsnk.h + * Description: + * Client Advise Sink Interfaces + * Interfaces: + * IID_IHXClientAdviseSink: {00000B00-0901-11d1-8B06-00A024406D59} +// $Private: + * IID_IHXClientRequestSink: {00000B01-0901-11d1-8B06-00A024406D59} +// $EndPrivate. + */ +DEFINE_GUID_ENUM(IID_IHXClientAdviseSink, 0x00000B00, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +// $Private: +DEFINE_GUID_ENUM(IID_IHXClientRequestSink, 0x00000B01, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +// $EndPrivate. + +/* + * File: + * hxplugn.h + * Description: + * Plugin inspector interface + * Interfaces: + * IID_IHXPlugin: {00000C00-0901-11d1-8B06-00A024406D59} + * IID_IHXPluginEnumerator {00000C01-0901-11d1-8B06-00A024406D59} + * IID_IHXPluginGroupEnumerator {00000C02-0901-11d1-8B06-00A024406D59} + * IID_IHXPluginReloader {00000C03-0901-11d1-8B06-00A024406D59} + * IID_IHXPluginFactory {00000C04-0901-11d1-8B06-00A024406D59} +// $Private: + * IID_IHXPluginChallenger {00000C05-0901-11d1-8B06-00A024406D59} + * IID_IHXPluginQuery {00000C06-0901-11d1-8B06-00A024406D59} + * : {00000C07-0901-11d1-8B06-00A024406D59} -- Deprecated + * : {00000C08-0901-11d1-8B06-00A024406D59} -- Deprecated + * IID_IHXGenericPlugin {00000C09-0901-11d1-8B06-00A024406D59} + * IID_IHXProxiedPlugin {00000C0A-0901-11d1-8B06-00A024406D59} +// $EndPrivate. + * IID_IHXPersistentComponent {00000C0B-0901-11d1-8B06-00A024406D59} + */ +#ifndef _HXPLUGN_H_ +DEFINE_GUID_ENUM(IID_IHXPlugin, 0x00000C00, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXPluginEnumerator, 0x00000C01, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXPluginGroupEnumerator, 0x00000C02, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXPluginSearchEnumerator, 0x3244b391, 0x42d4, 0x11d4, 0x95, 0x3, 0x0, 0x90, 0x27, 0x90, 0x29, 0x9c) +DEFINE_GUID_ENUM(IID_IHXPluginReloader, 0x00000C03, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXPluginFactory, 0x00000C04, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +// $Private: +DEFINE_GUID_ENUM(IID_IHXPluginChallenger, 0x00000C05, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXPluginQuery, 0x00000C06, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXGenericPlugin, 0x00000C09, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXProxiedPlugin, 0x00000C0A, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) + +DEFINE_GUID_ENUM(IID_IHXPluginHandler3, 0x32b19771, 0x2299, 0x11d4, 0x95, 0x3, 0x0, 0x90, 0x27, 0x90, 0x29, 0x9c) +DEFINE_GUID_ENUM(IID_IHXComponentPlugin, 0xf8a31571, 0x22ac, 0x11d4, 0x95, 0x3, 0x0, 0x90, 0x27, 0x90, 0x29, 0x9c) +DEFINE_GUID_ENUM(IID_IHXPluginNamespace, 0xf09e8891, 0x8e2d, 0x11d4, 0x82, 0xdb, 0x0, 0xd0, 0xb7, 0x4c, 0x2d, 0x25) +DEFINE_GUID_ENUM( IID_IHXPluginDatabase, 0xc2c65401, 0xa478, 0x11d4, 0x95, 0x18, 0x0, 0x90, 0x27, 0x90, 0x29, 0x9c) +// $EndPrivate. +#endif +/* + * File: + * hxwin.h + * Description: + * Site interfaces + * Interfaces: + * IID_IHXSiteWindowed: {00000D01-0901-11d1-8B06-00A024406D59} + * IID_IHXSiteWindowless: {00000D02-0901-11d1-8B-6-00A024406D59} + * IID_IHXSite: {00000D03-0901-11d1-8B-6-00A024406D59} + * IID_IHXSiteUser: {00000D04-0901-11d1-8B-6-00A024406D59} + * IID_IHXSiteWatcher: {00000D05-0901-11d1-8B-6-00A024406D59} + * IID_IHXSiteUserSupplier: {00000D06-0901-11d1-8B-6-00A024406D59} + * IID_IHXSiteSupplier: {00000D07-0901-11d1-8B-6-00A024406D59} + * IID_IHXSiteManager: {00000D08-0901-11d1-8B-6-00A024406D59} + * IID_IHXMultiInstanceSiteUserSupplier: {00000D09-0901-11d1-8B-6-00A024406D59} + * IID_IHXSite2: {00000D0A-0901-11d1-8B-6-00A024406D59} + * IID_IHXSiteTreeNavigation: {b52abc41-a919-11d8-b8a3-0003939ba95e} + * IID_IHXSiteFullScreen {00000D0B-0901-11d1-8B-6-00A024406D59} + // $Private: + * IID_IHXLayoutSiteGroupManager {00000D0C-0901-11d1-8B-6-00A024406D59} + // $EndPrivate. + * IID_IHXEventHookMgr {00000D0D-0901-11d1-8B-6-00A024406D59} + * IID_IHXEventHook {00000D0E-0901-11d1-8B-6-00A024406D59} + * IID_IHXPassiveSiteWatcher {00000D0F-0901-11d1-8B-6-00A024406D59} + * IID_IHXStatusMessage {00000D10-0901-11d1-8B-6-00A024406D59} + // $Private: + * IID_IHXGetImplementation {00000D11-0901-11d1-8B-6-00A024406D59} + * IID_IHXSiteEventHandler {00000D12-0901-11d1-8B-6-00A024406D59} + * IID_IHXSiteTransition {00000D13-0901-11d1-8B-6-00A024406D59} + * IID_IHXRegion {00000D14-0901-11d1-8B-6-00A024406D59} + * IID_IHXColorConverterManager {00000D15-0901-11d1-8B-6-00A024406D59} + * IID_IHXColorConverter {00000D16-0901-11d1-8B-6-00A024406D59} + * IID_IHXColorConverterHandler {00000D17-0901-11d1-8B-6-00A024406D59} + // $EndPrivate. + */ +#ifndef _HXWIN_H_ +DEFINE_GUID_ENUM(IID_IHXSiteWindowed, 0x00000D01, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXSiteWindowless, 0x00000D02, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXSite, 0x00000D03, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +// $Private: +DEFINE_GUID_ENUM(IID_IHXSiteComposition, 0x00000D19, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +// $EndPrivate. +DEFINE_GUID_ENUM(IID_IHXSiteUser, 0x00000D04, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXSiteWatcher, 0x00000D05, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXSiteUserSupplier, 0x00000D06, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXSiteSupplier, 0x00000D07, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXSiteManager, 0x00000D08, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXMultiInstanceSiteUserSupplier, 0x00000D09, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXSite2, 0x00000D0A, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXSiteTreeNavigation, 0xb52abc41, 0xa919, 0x11d8, 0xb8, 0xa3, 0x0, 0x03, 0x93, 0x9b, 0xa9, 0x5e) +// $Private: +DEFINE_GUID_ENUM(IID_IHXSiteControl, 0xdd25ca2e, 0x73a5, 0x4811, 0x99, 0x6f, 0x7e, 0x67, 0x26, 0xe7, 0x66, 0x8f) +// $EndPrivate. + +DEFINE_GUID_ENUM(IID_IHXSiteFullScreen, 0x00000D0B, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +// $Private: +DEFINE_GUID_ENUM(IID_IHXLayoutSiteGroupManager, 0x00000D0C, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +// $EndPrivate. +DEFINE_GUID_ENUM(IID_IHXEventHookMgr, 0x00000D0D, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXEventHook, 0x00000D0E, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXPassiveSiteWatcher, 0x00000D0F, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXStatusMessage, 0x00000D10, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +// $Private: +DEFINE_GUID_ENUM(IID_IHXGetImplementation, 0x00000D11, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXSiteEventHandler, 0x00000D12, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXSiteTransition, 0x00000D13, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXRegion, 0x00000D14, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXColorConverterManager, 0x00000D15, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXColorConverter, 0x00000D16, 0x903, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXColorConverterHandler, 0x00000D17, 0x903, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXColorConverterPlugin, 0x00000D18, 0x903, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXSiteManager2, 0x00000D20, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXSiteEnumerator, 0x67f8c5bd, 0x4b1d, 0x4c09, 0x8f, 0xb7, 0x8a, 0xc7, 0xc2, 0x0d, 0x29, 0xc7) +DEFINE_GUID_ENUM(IID_IHXOverlayResponse, 0x00000D22, 0x903, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXOverlayManager, 0x00000D21, 0x903, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +// $EndPrivate. +DEFINE_GUID_ENUM(IID_IHXKeyBoardFocus, 0x00000D23, 0x903, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXDrawFocus, 0x00000D24, 0x903, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXSubRectSite, 0x00000D25, 0x903, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +#endif +/* + * File: + * hxformt.h + * Description: + * Interfaces related to file and broadcast format plugins. + * Interfaces: + * + * IID_IHXFileFormatObject: {00000F00-0901-11d1-8B06-00A024406D59} + * IID_IHXBroadcastFormatObject: {00000F01-0901-11d1-8B06-00A024406D59} + * IID_IHXFormatResponse: {00000F02-0901-11d1-8B06-00A024406D59} + * IID_IHXFormatReuse: {e55077c4-a299-11d7-864c-0002b3658720} + * IID_IHXPacketFormat: {00000F03-0901-11d1-8B06-00A024406D59} + * IID_IHXPacketTimeOffsetHandler {00000F04-0901-11d1-8B06-00A024406D59} + * IID_IHXPacketTimeOffsetHandlerResponse {00000F05-0901-11d1-8B06-00A024406D59} + * IID_IHXLiveFileFormatInfo {00000F06-0901-11d1-8B06-00A024406D59} +// $Private: + * IID_IHXBroadcastLatency {00000F08-0901-11d1-8B06-00A024406D59} + * IID_IHXPayloadFormatObject {00000F07-0901-11d1-8B06-00A024406D59} + * IID_IHXBlockFormatObject {00000F09-0901-11d1-8B06-00A024406D59} + * IID_IHXFileFormatHeaderAdvise {00000F0A-0901-11d1-8B06-00A024406D59} + * IID_IHXFileFormatHeaderAdviseResponse {00000F0B-0901-11d1-8B06-00A024406D59} +// $EndPrivate. + */ +#ifndef _HXFORMT_H_ +DEFINE_GUID_ENUM(IID_IHXFileFormatObject, 0x00000F00, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXBroadcastFormatObject, 0x00000F01, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXFormatResponse, 0x00000F02, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXFormatReuse, 0xe55077c4, 0xa299, 0x11d7, 0x86, 0x4c, 0x0, 0x2, 0xb3, 0x65, 0x87, 0x20) +DEFINE_GUID_ENUM(IID_IHXPacketFormat, 0x00000F03, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXPacketTimeOffsetHandler, 0x00000F04, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXPacketTimeOffsetHandlerResponse, 0x00000F05, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXLiveFileFormatInfo, 0x00000F06, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +// $Private: +DEFINE_GUID_ENUM(IID_IHXPayloadFormatObject, 0x00000F07, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXBroadcastLatency, 0x00000F08, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXBlockFormatObject, 0x00000F09, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXFileFormatHeaderAdvise, 0x00000F0A, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXFileFormatHeaderAdviseResponse, 0x00000F0B, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +// $EndPrivate. + +DEFINE_GUID_ENUM(IID_IHXSyncFileFormatObject, 0x00000F0C, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) + +#endif + +#ifndef _HXFWRTR_H_ +// $Private: +DEFINE_GUID_ENUM(IID_IHXFileWriterMonitor, 0xb5615de1, 0x42a6, 0x11d5, 0xa9, 0xc, 0x0, 0x1, 0x2, 0x51, 0xb3, 0x40) +DEFINE_GUID_ENUM(IID_IHXPropertyAdviser, 0x264fd2f0, 0x432b, 0x11d5, 0xa9, 0xd, 0x0, 0x1, 0x2, 0x51, 0xb3, 0x40) +DEFINE_GUID_ENUM(IID_IHXFileWriter, 0xb5615de0, 0x42a6, 0x11d5, 0xa9, 0xc, 0x0, 0x1, 0x2, 0x51, 0xb3, 0x40) +// $EndPrivate. +#endif + +#ifndef _HXSRCIN_H_ +DEFINE_GUID_ENUM(IID_IHXSourceInput, 0xebf8d220, 0x40f7, 0x11d6, 0xab, 0x3f, 0x0, 0x1, 0x2, 0x51, 0xb3, 0x40) +DEFINE_GUID_ENUM(IID_IHXSourceHandler, 0xb2646da0, 0x410a, 0x11d6, 0xab, 0x3f, 0x0, 0x1, 0x2, 0x51, 0xb3, 0x40) +#endif + +/* + * File: + * hxpends.h + * Description: + * Interfaces related to get pending status from objects + * Interfaces: + * IHXPendingStatus: {00001100-0901-11d1-8B06-00A024406D59} + */ +#ifndef _HXPENDS_H_ +DEFINE_GUID_ENUM(IID_IHXPendingStatus, 0x00001100, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +#endif +/* + * File: + * ihxpckts.h + * Description: + * Interfaces related to buffers, packets, streams, etc. + * Interfaces: + * IID_IHXBuffer: {00001300-0901-11d1-8B06-00A024406D59} + * IID_IHXPacket: {00001301-0901-11d1-8B06-00A024406D59} + * IID_IHXRTPPacket {0169A731-1ED0-11d4-952B-00902742C923} + * IID_IHXRTPPacketInfo {0169A731-1ED0-11d4-952B-00902742C923} + * IID_IHXValues: {00001302-0901-11d1-8B06-00A024406D59} + * IID_IHXValuesRemove: {00001303-0901-11d1-8B06-00A024406D59} + * IID_IHXClientPacket: {00001304-0901-11d1-8B06-00A024406D59} + * IID_IHXBroadcastDistPktExt: {3B022922-94A1-4be5-BD25-216DA27BD8FC} + */ +#ifndef _IHXPCKTS_H_ +DEFINE_GUID_ENUM(IID_IHXBuffer, 0x00001300, 0x0901, 0x11d1, 0x8b, 0x06, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXPacket, 0x00001301, 0x0901, 0x11d1, 0x8b, 0x06, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXRTPPacket, 0x0169a731, 0x1ed0, 0x11d4, 0x95, 0x2b, 0x0, 0x90, 0x27, 0x42, 0xc9, 0x23) +DEFINE_GUID_ENUM(IID_IHXRTPPacketInfo, 0xec7d67bb, 0x2e79, 0x49c3, 0xb6, 0x67, 0xba, 0x8a, 0x93, 0x8d, 0xbc, 0xe0) +DEFINE_GUID_ENUM(IID_IHXValues, 0x00001302, 0x0901, 0x11d1, 0x8b, 0x06, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXValues2, +0x7ae64d81, 0xc5ab, 0x4b0a, 0x94, 0xf2, 0xb4, 0xd6, 0xdd, 0x2b, 0xda, 0x7a) +DEFINE_GUID_ENUM(IID_IHXValuesRemove, 0x00001303, 0x0901, 0x11d1, 0x8b, 0x06, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +// $Private: +DEFINE_GUID_ENUM(IID_IHXClientPacket, 0x00001304, 0x0901, 0x11d1, 0x8b, 0x06, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXBroadcastDistPktExt, 0x3b022922, 0x94a1, 0x4be5, 0xbd, 0x25, 0x21, 0x6d, 0xa2, 0x7b, 0xd8, 0xfc) +// $EndPrivate. +#endif +/* + * File: + * hxasm.h + * Description: + * Interfaces related to abm and back channel support. + * + * Interfaces: + * IID_IHXBackChannel: {00001500-0901-11d1-8B06-00A024406D59} + * IID_IHXASMSource: {00001501-0901-11d1-8B06-00A024406D59} + * IID_IHXASMStream: {00001502-0901-11d1-8B06-00A024406D59} + * IID_IHXASMStream2: {30d39e2c-1e56-11b2-8618-0002b39a94d0} + * IID_IHXASMStreamSink: {00001503-0901-11d1-8B06-00A024406D59} + */ +#ifndef _HXASM_H_ +DEFINE_GUID_ENUM(IID_IHXBackChannel, 0x00001500, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXASMSource, 0x00001501, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXASMStream, 0x00001502, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXASMStream2, 0x00001504, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXASMStreamSink, 0x00001503, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +#endif +/* + * File: + * hxencod.h + * Description: + * Interfaces related to superencoders. + * + * Interaces: + * IID_IHXEncoderResponse {00001600-0901-11d1-8B06-00A024406D59} + * IID_IHXEncoder {00001601-0901-11d1-8B06-00A024406D59} + * IID_IHXEncoderCompletion {00001602-0901-11d1-8B06-00A024406D59} + * IID_IHXConnectionlessControl + {00001603-0901-11d1-8B06-00A024406D59} + * IID_IHXEncoderResponseCompletion + {00001604-0901-11d1-8B06-00A024406D59} + * IID_IHXTransportControl {00001605-0901-11d1-8B06-00A024406D59} + */ +#ifndef _HXENCOD_H_ +DEFINE_GUID_ENUM(IID_IHXEncoderResponse, 0x00001600, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXEncoder, 0x00001601, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXEncoderCompletion, 0x00001602, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXConnectionlessControl, + 0x00001603, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXEncoderResponseCompletion, + 0x00001604, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXTransportControl, 0x00001605, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +#endif + +/* + * File: + * hxbrdcst.h + * Description: + * + * RealSystem iQ remote broadcast functionality + * + * Interfaces: + * + * IID_IHXRemoteBroadcastServices: {8F933081-27B6-11d5-9569-00902742E832} + * IID_IHXRemoteBroadcastConfiguration: {8F933083-27B6-11d5-9569-00902742E832} + * IID_IHXRemoteBroadcastConfigurationResponse: {67C1BA10-39BC-11d5-956A-00902742E832} + * IID_IHXRemoteBroadcastStatisticsReport: {CEE9CC1E-DCDF-4159-A315-8A3B86EE9A86} + */ + +#ifndef _HXBRDCST_H_ +DEFINE_GUID_ENUM(IID_IHXRemoteBroadcastServices, 0x8f933081, 0x27b6, 0x11d5, 0x95, 0x69, 0x0, 0x90, 0x27, 0x42, 0xe8, 0x32) +DEFINE_GUID_ENUM(IID_IHXRemoteBroadcastConfiguration, 0x8f933083, 0x27b6, 0x11d5, 0x95, 0x69, 0x0, 0x90, 0x27, 0x42, 0xe8, 0x32) +DEFINE_GUID_ENUM(IID_IHXRemoteBroadcastConfigurationResponse, 0x67c1ba10, 0x39bc, 0x11d5, 0x95, 0x6a, 0x0, 0x90, 0x27, 0x42, 0xe8, 0x32) +DEFINE_GUID_ENUM(IID_IHXRemoteBroadcastStatisticsReport, 0xcee9cc1e, 0xdcdf, 0x4159, 0xa3, 0x15, 0x8a, 0x3b, 0x86, 0xee, 0x9a, 0x86) +#endif + +/* + * File: + * hxauth.h + * Description: + * Password handling API + * Interfaces: + * IID_IHXPassword {00001700-0901-11d1-8B06-00A024406D59} + */ + +/* + * 000017**-0901-11d1-8B06-00A024406D59 is reserved for interfaces in hxauth.h (below) + */ + +/* + * File: + * hxauth.h + * Description: + * Authentication API + * Interfaces: + * IID_IHXAuthenticator {00001800-0901-11d1-8B06-00A024406D59} + * IID_IHXAuthenticatorResponse {00001801-0901-11d1-8B06-00A024406D59} + * IID_IHXAuthenticatorRequest {00001802-0901-11d1-8B06-00A024406D59} + * IID_IHXPassword {00001700-0901-11d1-8B06-00A024406D59} + * IID_IHXAuthenticationManager {00001A00-0901-11d1-8B06-00A024406D59} + * IID_IHXAuthenticationManager2 + * {34e171d2-a8f0-4832-bc7d-06dfe3ae58fd} + * IID_IHXAuthenticationManagerResponse + * {00001A01-0901-11d1-8B06-00A024406D59} + */ +#ifndef _HXAUTH_H_ +DEFINE_GUID_ENUM(IID_IHXAuthenticator, 0x00001800, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXAuthenticatorResponse, 0x00001801, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXAuthenticatorRequest, 0x00001802, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXPassword, 0x00001700, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXAuthenticationManager, 0x00001a00, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXAuthenticationManager2, 0x34e171d2, 0xa8f0, 0x4832, 0xbc, 0x7d, 0x06, 0xdf, 0xe3, 0xae, 0x58, 0xfd) +DEFINE_GUID_ENUM(IID_IHXAuthenticationManagerResponse, 0x00001a01, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +#endif +/* + * File: + * hxsdesc.h + * Description: + * Stream description API + * Interfaces: + * IID_IHXStreamDescription {00001900-0901-11d1-8B06-00A024406D59} + * IID_IHXRTPPayloadInfo {00001901-0901-11d1-8B06-00A024406D59} + * IID_IHXStreamDescriptionSettings {00001902-0901-11d1-8B06-00A024406D59} + */ +#ifndef _HXSDESC_ +DEFINE_GUID_ENUM(IID_IHXStreamDescription, 0x00001900, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXRTPPayloadInfo, 0x00001901, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXStreamDescriptionSettings, 0x00001902, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +#endif +/* + * 00001A**-0901-11d1-8B06-00A024406D59 is reserved for interfaces in hxauth.h (above) + */ + +/* + * File: + * hxlvtxt.h + * Description: + * Interfaces related to live text superencoder. + * + * Interaces: + * IID_IHXLiveText {00001b00-0901-11d1-8B06-00A024406D59} + * IID_IHXLiveText2 {00001b01-0901-11d1-8B06-00A024406D59} + */ +#ifndef _HXLVTXT_H_ +DEFINE_GUID_ENUM(IID_IHXLiveText, 0x00001b00, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXLiveText2, 0x00001b01, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +#endif +/* + * File: + * hxcfg.h + * Description: + * Interfaces used by server configuration tools. + * + * Interfaces: + * IID_IHXConfigFile {00001c00-0901-11d1-8B06-00A024406D59} + * IID_IHXRegConfig {00001c01-0901-11d1-8B06-00A024406D59} + * + */ +#ifndef _HXCFG_H_ +DEFINE_GUID_ENUM(IID_IHXConfigFile, 0x00001c00, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXRegConfig, 0x00001c01, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59) +#endif + +/* + * File: + * rmappv.h + * Description: + * Interfaces related to Pay Per View Database Plugins + * Interfaces: + * IID_IHXPPVDatabase {00001d00-0901-11d1-8B06-00A024406D59} + */ +DEFINE_GUID_ENUM(IID_IHXPPVDatabase, + 0x00001d00, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) + + +/* + * 00001e**-0901-11d1-8B06-00A024406D59 is reserved for an interface which + * has been deprecated. + */ + +/* + * File: + * rmacmenu.h + * Description: + * Interfaces used by renderers for context menus. + * + * Interfaces: + * IID_IHXContextMenu {00001f00-0901-11d1-8B06-00A024406D59} + * IID_IHXContextMenuResponse {00001f01-0901-11d1-8B06-00A024406D59} + */ +#ifndef _HXCMENU_H_ +DEFINE_GUID_ENUM(IID_IHXContextMenu, 0x00001f00, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXContextMenuResponse, 0x00001f01, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59) + +#endif +/* + * File: + * hxphook.h + * Description: + * Interfaces used by the top level client. client core and renderer to + * support Selective Record. + * + * Interfaces: + * IID_IHXPacketHook {00002000-0901-11d1-8B06-00A024406D59} + * IID_IHXPacketHookManager {00002001-0901-11d1-8B06-00A024406D59} + * IID_IHXPacketHookHelper {00002002-0901-11d1-8B06-00A024406D59} + * IID_IHXPacketHookHelperResponse {00002003-0901-11d1-8B06-00A024406D59} + */ + +#ifndef _HXPHOOK_H_ + +DEFINE_GUID_ENUM(IID_IHXPacketHook, 0x00002000, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXPacketHookManager, 0x00002001, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXPacketHookHelper, 0x00002002, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXPacketHookHelperResponse, 0x00002003, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) + +#endif + +/* + * File: + * hxpsink.h + * Description: + * Interfaces used by the top level client or renderers to determine + * that a player has been created or closed. + * + * Interfaces: + * IID_IHXPlayerCreationSink {00002100-0901-11d1-8B06-00A024406D59} + * IID_IHXPlayerSinkControl {00002101-0901-11d1-8B06-00A024406D59} + */ +DEFINE_GUID_ENUM(IID_IHXPlayerCreationSink, 0x00002100, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXPlayerSinkControl, 0x00002101, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) + +/* + * File: + * hxvsurf.h + * Description: + * Interface used by renderers to blt data to the screen (when in + * full screen mode). + * + * Interfaces: + * IID_IHXVideoSurface {00002200-0901-11d1-8B06-00A024406D59} + */ +DEFINE_GUID_ENUM(IID_IHXVideoSurface, 0x00002200, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXVideoHookSink, 0x00002201, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXVideoHook, 0x00002202, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXVideoSurface2, 0x00002203, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXRenderTimeLine, 0x00002204, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXSubRectVideoSurface, 0x00002205, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) + + +// $Private: +/* + * File: + * ihxfgbuf.h + * Description: + * Interfaces related to fragmented buffers + * Interfaces: + * IID_IHXFragmentedBuffer: {00002300-0901-11d1-8B06-00A024406D59} + * IID_IHXEnumFragmentedBuffer: {00002301-0901-11d1-8B06-00A024406D59} + */ +#ifndef _IHXFGBUF_H_ +DEFINE_GUID_ENUM(IID_IHXFragmentedBuffer, 0x00002300, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXEnumFragmentedBuffer, 0x00002301, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +#endif +// $EndPrivate. + +/* + * File: + * hxgroup.h + * Description: + * Client side Group related interfaces + * + * Interfaces: + * IID_IHXGroup {00002400-0901-11d1-8B06-00A024406D59} + * IID_IHXGroupManager {00002401-0901-11d1-8B06-00A024406D59} + * IID_IHXGroupSink {00002402-0901-11d1-8B06-00A024406D59} + * IID_IHXTrack {00002404-0901-11d1-8B06-00A024406D59} + */ +DEFINE_GUID_ENUM(IID_IHXGroup, 0x00002400, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXGroupManager, 0x00002401, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXGroupSink, 0x00002402, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +// $Private: +DEFINE_GUID_ENUM(IID_IHXGroup2, 0x00002403, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +// $EndPrivate. +DEFINE_GUID_ENUM(IID_IHXTrack, 0x00002404, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXTrackSink, 0x00002405, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXPrefetchSink, 0x00002406, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXGroupSink2, 0x00002407, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXPrefetch, 0x00002408, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) + +/* + * File: + * hxupgrd.h + * Description: + * Interfaces used by player for auto-upgrade. + * + * Interfaces: + * IID_IHXUpgradeCollection {00002500-0901-11d1-8B06-00A024406D59} + * IID_IHXUpgradeHandler {00002501-0901-11d1-8B06-00A024406D59} +// $Private: + * IID_IHXSystemRequired {00002502-0901-11d1-8B06-00A024406D59} +// $EndPrivate. + */ +DEFINE_GUID_ENUM(IID_IHXUpgradeCollection, + 0x00002500, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXUpgradeHandler, + 0x00002501, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +// $Private: +DEFINE_GUID_ENUM(IID_IHXSystemRequired, + 0x00002502, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +// $EndPrivate. + +DEFINE_GUID_ENUM(IID_IHXUpgradeCollection2, + 0x00002503, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) + +/* + * File: + * hxallow.h + * Description: + * Interfaces related to Allowance plugins + * Interfaces: + * IID_IHXPlayerConnectionAdviseSink {00002600-0901-11d1-8B06-00A024406D59} + * IID_IHXPlayerConnectionResponse {00002601-0901-11d1-8B06-00A024406D59} + * IID_IHXPlayerController {00002602-0901-11d1-8B06-00A024406D59} + * IID_IHXPlayerConnectionAdviseSinkManager + {00002603-0901-11d1-8B06-00A024406D59} + * IID_IHXPlayerConnectionAdviseEvents + {8fe78da6-a828-11d7-939c-00601df0ce4c} + * IID_IHXProxyConnectionAdviseSink {00002604-0901-11d1-8B06-00A024406D59} + * IID_IHXProxyConnectionResponse {00002605-0901-11d1-8B06-00A024406D59} + * IID_IHXProxyController {00002605-0901-11d1-8B06-00A024406D59} + * IID_IHXPlayerControllerProxyRedirect {00002607-0901-11d1-8B06-00A024406D59} + * IID_IHXMidBoxNotify {f8c5dcaf-9a5f-4d1b-a061-22fa0d038848} + */ +DEFINE_GUID_ENUM(IID_IHXPlayerConnectionAdviseSink, + 0x00002600, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXPlayerConnectionResponse, + 0x00002601, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXPlayerController, + 0x00002602, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXPlayerConnectionAdviseSinkManager, + 0x00002603, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXPlayerConnectionAdviseEvents, + 0x8fe78da6, 0xa828, 0x11d7, 0x93, 0x9c, 0x0, 0x60, 0x1d, 0xf0, 0xce, 0x4c) + +DEFINE_GUID_ENUM(IID_IHXProxyConnectionAdviseSink, + 0x00002604, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXProxyConnectionResponse, + 0x00002605, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXProxyController, + 0x00002606, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXPlayerControllerProxyRedirect, 0x00002607, 0x901, 0x11d1, + 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXMidBoxNotify, 0xf8c5dcaf, 0x9a5f, 0x4d1b, + 0xa0, 0x61, 0x22, 0xfa, 0x0d, 0x03, 0x88, 0x48) + +/* + * File: + * rmaaconf.h + * Description: + * Interfaces used by the top level client. client core to + * support Auto. Transport Configuration + * + * Interfaces: + * IID_IHXAutoConfig {00002700-0901-11d1-8B06-00A024406D59} + * IID_IHXAutoConfigResponse {00002701-0901-11d1-8B06-00A024406D59} + */ + +DEFINE_GUID_ENUM(IID_IHXAutoConfig, 0x00002700, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXAutoConfigResponse, 0x00002701, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) + +/* + * File: + * hxauthn.h + * Description: + * Interfaces used to validate a users access to content. + * + * Interfaces: + * IID_IHXCredRequestResponse, {00002800-0901-11d1-8B06-00A024406D59} + * IID_IHXCredRequest, {00002801-0901-11d1-8B06-00A024406D59} + * IID_IHXClientAuthResponse, {00002802-0901-11d1-8B06-00A024406D59} + * IID_IHXClientAuthConversation, {00002803-0901-11d1-8B06-00A024406D59} + * IID_IHXServerAuthResponse, {00002804-0901-11d1-8B06-00A024406D59} + * IID_IHXServerAuthConversation, {00002805-0901-11d1-8B06-00A024406D59} + * IID_IHXUserContext, {00002806-0901-11d1-8B06-00A024406D59} + * IID_IHXUserProperties, {00002807-0901-11d1-8B06-00A024406D59} + * IID_IHXUserImpersonation, {00002808-0901-11d1-8B06-00A024406D59} + * IID_IHXUserDB, {00002809-0901-11d1-8B06-00A024406D59} + * IID_IHXChallengeResponse, {0000280A-0901-11d1-8B06-00A024406D59} + * IID_IHXChallenge, {0000280B-0901-11d1-8B06-00A024406D59} + */ + +DEFINE_GUID_ENUM(IID_IHXCredRequestResponse, 0x00002800, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXCredRequest, 0x00002801, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXClientAuthResponse, 0x00002802, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXClientAuthConversation, 0x00002803, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXServerAuthResponse, 0x00002804, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXServerAuthConversation, 0x00002805, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXUserContext, 0x00002806, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXUserProperties, 0x00002807, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXUserImpersonation, 0x00002808, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXChallengeResponse, 0x00002809, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXChallenge, 0x0000280A, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) + + +/* + * File: + * hxplgns.h + * + * Description: + * Interfaces for Plugins: + * IHXObjectConfiguration - Consistant configuration. + * IHXPluginProperties - Consistant property retrival. + * + * Interfaces: + * IID_IHXObjectConfiguration: {00002900-0901-11d1-8B06-00A024406D59} + * IID_IHXPluginProperties: {00002901-0901-11d1-8B06-00A024406D59} + */ +DEFINE_GUID_ENUM(IID_IHXObjectConfiguration, 0x00002900, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXPluginProperties, 0x00002901, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) + +/* + * File: + * hxdb.h + * + * Description: + * Interfaces for Plugins: + * IHXDatabaseManager - Creates Configured Database Instances + * IHXAuthenticationDBManagerResponse - Provides Callbacks for IHXAuthenticationDBManager + * IHXAuthenticationDBManager - Functions to add and remove users from a database + * IHXAsyncEnumAuthenticationDBResponse - Provides Callbacks for IHXAsyncEnumAuthenticationDB + * IHXAsyncEnumAuthenticationDB - Functions to enumerate the list of users in a database + * IHXAuthenticationDBAccessResponse - Provides Callbacks for IHXAuthenticationDBAccess + * IHXAuthenticationDBAccess - Functions to access a users info in the database + * IHXGUIDDBManagerResponse - Provides Callbacks for IHXGUIDDBManager + * IHXGUIDDBManager - Functions to add and remove GUID's from a database + * IHXPPVDBManagerResponse - Provides Callbacks for IHXPPVDBManager + * IHXPPVDBManager - Functions to add, remove, and adjust a user's permissions from a database + * IHXRedirectDBManagerResponse - Provides Callbacks for IHXRedirectDBManager + * IHXRedirectDBManager - Functions to add and remove URL Redirects from a database + * IHXRegistrationLoggerResponse - Provides Callbacks for IHXRegistrationLogger + * IHXRegistrationLogger - Functions to Log registration Activity. + * + * Interfaces: + * IID_IHXDatabaseManager: {00002A00-0901-11d1-8B06-00A024406D59} + * IID_IHXAuthenticationDBManagerResponse: {00002A01-0901-11d1-8B06-00A024406D59} + * IID_IHXAuthenticationDBManager: {00002A02-0901-11d1-8B06-00A024406D59} + * IID_IHXAsyncEnumAuthenticationDBResponse:{00002A03-0901-11d1-8B06-00A024406D59} + * IID_IHXAsyncEnumAuthenticationDB: {00002A04-0901-11d1-8B06-00A024406D59} + * IID_IHXAuthenticationDBAccessResponse: {00002A05-0901-11d1-8B06-00A024406D59} + * IID_IHXAuthenticationDBAccess: {00002A06-0901-11d1-8B06-00A024406D59} + * IID_IHXGUIDDBManagerResponse: {00002A07-0901-11d1-8B06-00A024406D59} + * IID_IHXGUIDDBManager: {00002A08-0901-11d1-8B06-00A024406D59} + * IID_IHXPPVDBManagerResponse: {00002A09-0901-11d1-8B06-00A024406D59} + * IID_IHXPPVDBManager: {00002A0A-0901-11d1-8B06-00A024406D59} + * IID_IHXRedirectDBManagerResponse: {00002A0B-0901-11d1-8B06-00A024406D59} + * IID_IHXRedirectDBManager: {00002A0C-0901-11d1-8B06-00A024406D59} + * IID_IHXRegistrationLoggerResponse: {00002A0D-0901-11d1-8B06-00A024406D59} + * IID_IHXRegistrationLogger: {00002A0E-0901-11d1-8B06-00A024406D59} + */ +DEFINE_GUID_ENUM(IID_IHXDatabaseManager, 0x00002A00, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXAuthenticationDBManagerResponse, 0x00002A01, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXAuthenticationDBManager, 0x00002A02, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXAsyncEnumAuthenticationDBResponse, 0x00002A03, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXAsyncEnumAuthenticationDB, 0x00002A04, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXAuthenticationDBAccessResponse, 0x00002A05, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXAuthenticationDBAccess, 0x00002A06, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXGUIDDBManagerResponse, 0x00002A07, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXGUIDDBManager, 0x00002A08, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXPPVDBManagerResponse, 0x00002A09, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXPPVDBManager, 0x00002A0A, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXRedirectDBManagerResponse, 0x00002A0B, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXRedirectDBManager, 0x00002A0C, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXRegistrationLoggerResponse, 0x00002A0D, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXRegistrationLogger, 0x00002A0E, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) + +// $Private: +/* + * File: + * ihxostrm.h + * Description: + */ + +DEFINE_GUID_ENUM(IID_IHXStreamableObj, 0x47533471, 0xbece, 0x11d1, 0x8f, 0x9, 0x0, 0x60, 0x8, 0x3b, 0xe5, 0x61) +DEFINE_GUID_ENUM(IID_IHXObjOutStream, 0xb3a156d1, 0xbedd, 0x11d1, 0x8f, 0xa, 0x0, 0x60, 0x8, 0x3b, 0xe5, 0x61) +DEFINE_GUID_ENUM(IID_IHXObjInStream, 0xb3a156d2, 0xbedd, 0x11d1, 0x8f, 0xa, 0x0, 0x60, 0x8, 0x3b, 0xe5, 0x61) +// $EndPrivate. + +/* + * File: + * hxxmltg.h + * + * Description: + * Interfaces for Plugins: + * IHXXMLTagHandler: Interface for registering for a specific tag + * and providing an IHXXMLTagObject to tagfsys. + * (Works like IHXFileSystemObject) + * + * IHXXMLTagObject: Interface for receiving the contents of a tag + * for which the creating IHXXMLTagHandler has registerd. + * + * IHXXMLTagObjectResponse: Interface for IHXXMLTagObject to return + * the replacement for the tag. This is implemented by tagfsys. + * + * + * Interfaces: + * IID_IHXXMLTagObjectResponse: {00002C02-0901-11d1-8B06-00A024406D59} + * IID_IHXXMLTagHandler: {00002C03-0901-11d1-8B06-00A024406D59} + * IID_IHXXMLTagObject: {00002C04-0901-11d1-8B06-00A024406D59} + */ +#ifndef _HXXMLTG_H +DEFINE_GUID_ENUM(IID_IHXXMLTagObjectResponse, 0x00002C02, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXXMLTagHandler, 0x00002C03, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXXMLTagObject, 0x00002C04, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +#endif +// $Private: +/* + * File: + * archplin/flcreatr.h + */ +DEFINE_GUID_ENUM(IID_IHXFileCreatorResponse, 0x00002D00, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +// $EndPrivate. + +// $Private: +/* + * File: + * hxfiles.h + */ +#ifndef _HXFILES_H_ +DEFINE_GUID_ENUM(IID_IHXHTTPRedirect, 0x21eae0b9, 0x2e0c, 0x4003, 0xbb, 0x79, 0xbc, 0x8c, 0xc5, 0x67, 0x2c, 0x2d) +DEFINE_GUID_ENUM(IID_IHXHTTPRedirectResponse, 0x125a63b1, 0x669c, 0x42f9, 0xb1, 0xf9, 0x1b, 0x53, 0xe9, 0x95, 0x82, 0x95) +DEFINE_GUID_ENUM(IID_IHXRM2Converter2, 0x00002F00, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXRM2Converter2Response, 0x00002F01, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXPoolPathAdjustment, 0x00002F02, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +#endif +// $EndPrivate. + +/* + * File: + * hxcache.h + * + * Description: + * Interfaces for caching services: + * IHXCache - Creates IHXCacheFiles + * IHXCacheResponse - Response object for IHXCache + * IHXCacheFile - Persistant store object for caching + * IHXCacheFileResponse - Response object for IHXCacheFile + * +// $Private: +// cdist interfaces private for development, expect to make public when finalized. + * IHXContentDistribution - Content distribution services + * IHXContentDistributionResponse - Content distribution service replies + * IHXContentDistributionAdvise - advise sink for cdist + * IHXContentDistributionAdviseResponse - cdist advise sink responses + * IHXMIIPullEntire - MII will pull the entire file + * from the next read. + * IHXMIIPullEntireResponse + * IHXCacheFileSetVersion - Get specific version from cache. + * IHXMIIReadStatCollection - Collect read data for MII +// $EndPrivate. + * + * Interfaces: + * IID_IHXCache: {00002E00-0901-11d1-8B06-00A024406D59} + * IID_IHXCacheResponse: {00002E01-0901-11d1-8B06-00A024406D59} + * IID_IHXCacheFile: {00002E02-0901-11d1-8B06-00A024406D59} + * IID_IHXCacheFileResponse: {00002E03-0901-11d1-8B06-00A024406D59} + * IID_IHXMIIFetch: {00002E04-0901-11d1-8B06-00A024406D59} + * IID_IHXFIFOCache: {00002E05-0901-11d1-8B06-00A024406D59} +// $Private: + * IID_IHXContentDistribution {00002E06-0901-11d1-8B06-00A024406D59} + * IID_IHXContentDistributionResponse {00002E07-0901-11d1-8B06-00A024406D59} + * IID_IHXContentDistributionAdvise {00002E08-0901-11d1-8B06-00A024406D59} + * IID_IHXContentDistributionAdviseResponse {00002E09-0901-11d1-8B06-00A024406D59} + * IID_IHXMIIPullEntire {00002E0A-0901-11d1-8B06-00A024406D59} + * IID_IHXMIIPullEntireResponse {00002E0B-0901-11d1-8B06-00A024406D59} + * IID_IHXCacheFileSetVersion {00002E0C-0901-11d1-8B06-00A024406D59} + * IID_IHXMIIReadStatCollection {00002E0D-0901-11d1-8B06-00A024406D59} +// $EndPrivate. + */ + +DEFINE_GUID_ENUM(IID_IHXCache, 0x00002E00, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXCacheResponse, 0x00002E01, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXCacheFile, 0x00002E02, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXCacheFileResponse, 0x00002E03, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXMIIFetch, 0x00002E04, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXFIFOCache, 0x00002E05, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +// $Private: +DEFINE_GUID_ENUM(IID_IHXContentDistribution, 0x00002E06, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXContentDistributionResponse,0x00002E07, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXContentDistributionAdvise, 0x00002E08, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXContentDistributionAdviseResponse, + 0x00002E09, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXMIIPullEntire, 0x00002E0A, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXMIIPullEntireResponse, 0x00002E0B, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXCacheFileSetVersion, 0x00002E0C, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXMIIReadStatCollection, 0x00002E0D, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +// $EndPrivate. + +/* + * File: intrpm.h + * + * IID_IHXInterPluginMessenger: {00003000-0901-11d1-8B06-00A024406D59} + */ +DEFINE_GUID_ENUM(IID_IHXInterPluginMessenger, 0x00003000, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) + +/* + * File: intrpm.h + * + * IID_IHXInterPluginMessenger2: {00003001-0901-11d1-8B06-00A024406D59} + */ +DEFINE_GUID_ENUM(IID_IHXInterPluginMessenger2, 0x00003001, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) + +/* + * File: hxvalue.h + * + * DEPRECATED: {00003100-0901-11d1-8B06-00A024406D59} + * DEPRECATED: {00003101-0901-11d1-8B06-00A024406D59} + * DEPRECATED: {00003102-0901-11d1-8B06-00A024406D59} + * DEPRECATED: {00003103-0901-11d1-8B06-00A024406D59} + * DEPRECATED: {00003104-0901-11d1-8B06-00A024406D59} + * DEPRECATED: {00003105-0901-11d1-8B06-00A024406D59} + * DEPRECATED: {00003106-0901-11d1-8B06-00A024406D59} + * DEPRECATED: {00003107-0901-11d1-8B06-00A024406D59} + * + * IID_IHXKeyValueList: {00003108-0901-11d1-8B06-00A024406D59} + * IID_IHXKeyValueListIter: {00003109-0901-11d1-8B06-00A024406D59} + * IID_IHXKeyValueListIterOneKey: {00003110-0901-11d1-8B06-00A024406D59} + * IID_IHXOptions: {00003111-0901-11d1-8B06-00A024406D59} + */ +#ifndef _HXVALUE_H_ +/* DEPRECATED 3100 - 3107 */ +DEFINE_GUID_ENUM(IID_IHXKeyValueList, 0x00003108, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXKeyValueListIter, 0x00003109, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXKeyValueListIterOneKey, 0x00003110, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXOptions, 0x00003111, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +#endif + +/* + * File: + * ihxcookies.h + * + * Description: + * Interfaces for Plugins: + * IHXCookies - Cookie database management APIs + * IHXCookiesHelper - Cookie output helper APIs + * + * Interfaces: + * IID_IHXCookies: {00003200-0901-11d1-8B06-00A024406D59} + * IID_IHXCookiesHelper: {00003201-0901-11d1-8B06-00A024406D59} + */ +DEFINE_GUID_ENUM(IID_IHXCookies, 0x00003200, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXCookiesHelper, 0x00003201, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) + +/* + * File: addrpool.h + * + * IID_IHXMulticastAddressPool: {00003300-0901-11d1-8B06-00A024406D59} + */ +DEFINE_GUID_ENUM(IID_IHXMulticastAddressPool, 0x00003300, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) + +/* + * File: sapmgr.h + * + * IID_IHXSapManager: {00003400-0901-11d1-8B06-00A024406D59} + */ +DEFINE_GUID_ENUM(IID_IHXSapManager, 0x00003400, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) + +/* + * File: namedlock.h + * + * IID_IHXNamedLock: {A5814CDC-232A-448D-8204-98E4D66445B5} + */ +DEFINE_GUID_ENUM(IID_IHXNamedLock, 0xa5814cdc, 0x232a, 0x448d, 0x82, 0x4, + 0x98, 0xe4, 0xd6, 0x64, 0x45, 0xb5) + + +/* + * File: + * hxvsrc.h + * + * Description: + * Interfaces for Plugins: + * IHXFileViewSource - Interface so file formats can support view source. + * IHXFileViewSourceResponse - Response interface. + * + * Interfaces: + * IID_IHXFileViewSource: {00003500-0901-11d1-8B06-00A024406D59} + * IID_IHXFileViewSourceResponse: {00003501-0901-11d1-8B06-00A024406D59} +// $Private: + * IID_IHXClientViewSource: {00003502-0901-11d1-8B06-00A024406D59} + * IID_IHXClientViewSourceSink: {00003503-0901-11d1-8B06-00A024406D59} +// $EndPrivate. + * IID_IHXViewSourceCommand: {00003504-0901-11d1-8B06-00A024406D59} + * IID_IHXViewSourceURLResponse {00003505-0901-11d1-8B06-00A024406D59} +// $Private: + * IID_IHXClientViewRights: {00003506-0901-11d1-8B06-00A024406D59} +// $EndPrivate. + */ +#ifndef _HXVSRC_H_ +DEFINE_GUID_ENUM(IID_IHXFileViewSource, 0x00003500, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXFileViewSourceResponse, 0x00003501, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +// $Private: +DEFINE_GUID_ENUM(IID_IHXClientViewSource, 0x00003502, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXClientViewSourceSink, 0x00003503, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +// $EndPrivate: +DEFINE_GUID_ENUM(IID_IHXViewSourceCommand, 0x00003504, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXViewSourceURLResponse, 0x00003505, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) + +// $Private: +DEFINE_GUID_ENUM(IID_IHXClientViewRights, 0x00003506, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +// $EndPrivate: + +#endif + +/* + * File: + * hxdllaccess.h + * Description: + * Interfaces to Dll Access Service + * Interfaces: + * IID_IHXDllAccess: {D5A71AA1-A6ED-479f-9FC6-F06B99142691} + */ +#ifndef _HXDLLACCESS_H_ +DEFINE_GUID_ENUM(IID_IHXDllAccess, 0xd5a71aa1, 0xa6ed, 0x479f, 0x9f, 0xc6, 0xf0, 0x6b, 0x99, 0x14, 0x26, 0x91) +#endif // _HXDLLACCESS_H_ + +// $Private: +/* + * File: + * hxpxymgr.h + * + * Description: + * + * IHXProxyManager - proxy APIs + * + * Interfaces: + * IID_IHXProxyManager: {00003600-0901-11d1-8B06-00A024406D59} + */ +DEFINE_GUID_ENUM(IID_IHXProxyManager, 0x00003600, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +// $EndPrivate. + +/* File: + * hxdtcvt.h + * + * Description + * IHXDataConvertSystemObject - RMA Stream data conversion creator + * IHXDataConvert - RMA Stream data conversion + * IHXDataConvertResponse - response for above + * IHXDataRevert - RMA Stream data reversion + * IHXDataRevertResponse - response for above + */ +DEFINE_GUID_ENUM(IID_IHXDataConvertSystemObject, + 0x00003900, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXDataConvert, + 0x00003901, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXDataConvertResponse, + 0x00003902, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXDataRevert, + 0x00003903, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXDataRevertResponse, + 0x00003904, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) + +/* File: + * hxvport.h + * + * Description + * IHXViewPortManager - RMA ViewportManager + * IHXViewPort - RMA Viewport + * IHXViewPortSink - RMA Viewport sink + * IHXViewPortSupplier - RMA Viewport supplier + */ +DEFINE_GUID_ENUM(IID_IHXViewPortManager, + 0x00004000, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXViewPort, + 0x00004001, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXViewPortSink, + 0x00004002, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXViewPortSupplier, + 0x00004003, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) + +/* File: + * hxmmrkr.h + * + * Description + * IHXMediaMarkerManager - RMA Media Marker Manager + * IHXMediaMarkerSink - RMA Media Marker Sink + */ +DEFINE_GUID_ENUM(IID_IHXMediaMarkerManager, + 0x46679d62, 0xf7ac, 0x4b0e, 0x80, 0x0, 0xf4, 0xee, 0x90, 0xf7, 0x85, 0xb1) +DEFINE_GUID_ENUM(IID_IHXMediaMarkerSink, + 0xa4643c85, 0x5b52, 0x4b07, 0xa7, 0x61, 0x32, 0xcf, 0xb2, 0xf2, 0x84, 0xfe) + +/* + * File: + * hxinter.h + * + * Description + * IHXEventManager - RMA Inter-datatype event manager + * IHXEventSink - RMA Inter-datatype event sink + * + */ +DEFINE_GUID_ENUM(IID_IHXEventManager, + 0xf932b582, 0x517, 0x4ca4, 0x84, 0x4c, 0x26, 0xa4, 0xe8, 0xe9, 0x69, 0x83) +DEFINE_GUID_ENUM(IID_IHXEventSink, + 0x76cf54bc, 0x9fce, 0x45e7, 0x90, 0xd1, 0x3, 0x46, 0x5, 0xf8, 0xdd, 0x14) + +/* + * File: + * hxslta.h + * + * Description: + * + * IHXSLTA - RMA version of slta. Simulates a live stream from file format. + * + * IHXSltaEvent - Allows events to be sent in an SLTA stream + */ +DEFINE_GUID_ENUM(IID_IHXSLTA, + 0x00000D00, 0xb4c8, 0x11d0, 0x99, 0x95, 0x0, 0xa0, 0x24, 0x8d, 0xa5, 0xf0) + +DEFINE_GUID_ENUM(IID_IHXSltaEvent, + 0x00000D01, 0xb4c8, 0x11d0, 0x99, 0x95, 0x0, 0xa0, 0x24, 0x8d, 0xa5, 0xf0) + + + +/* + * File: + * ihxcookies2.h + * + * Description: + * + * IHXCookies2 - Lets you get expired cookies + */ +#ifndef _ihxcookies2_h_ +DEFINE_GUID_ENUM(IID_IHXCookies2, 0x5634537f, 0x162f, 0x41b0, 0x93, 0xfc, 0x6e, 0xf, 0x77, 0x10, 0x71, 0x81) +#endif + + +/* + * File: + * rmasm2sm.h + * + * Description: + * + * IHXSmilToSmilRendererCommunicator - Lets you communicate from SMIL 1.0 + * renderer to SMIL 2.0 renderer that SMIL 2 stream is being proxied. + */ +#ifndef _HXSM2SM_H_ +// $Private: +DEFINE_GUID_ENUM(IID_IHXSmilToSmilRendererCommunicator, + 0x00004300, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +// $EndPrivate. +#endif + +/* + * File: + * hxproductversion.h + * + * Description: + * + * Provide version information regarding the executable core. + */ +DEFINE_GUID_ENUM(IID_IHXProductVersion, 0xA0991C6E, 0xD51B, 0x4549, + 0xA0, 0xD7, 0xBE, 0xD5, 0xED, 0x18, 0xF1, 0xC7) + + +/* + * File: + * progsink.h + * + * Description: + * + * Enables subscription for notification on the progress of merging files. + */ +#ifndef _PROGSINK_H_ + +DEFINE_GUID_ENUM(IID_IHXProgressSink, + 0x6f8c5fb0, 0xc1d3, 0x11d2, 0x87, 0x1b, 0x0, 0xc0, 0xf0, 0x31, 0xc2, 0x66) + +DEFINE_GUID_ENUM(IID_IHXProgressSinkControl, + 0xdc464800, 0xc1d3, 0x11d2, 0x87, 0x1b, 0x0, 0xc0, 0xf0, 0x31, 0xc2, 0x66) + +#endif + +/* + * File: + * rmflsnk.h + * + * Description: + * + * Enables receipt of header and packets from the rmeditor, prior to writing to file. + */ + +#ifndef _RMFLSNK_H_ + +DEFINE_GUID_ENUM(IID_IHXRMFileSink, + 0x19137680, 0x377b, 0x11d2, 0xa1, 0xc4, 0x0, 0x60, 0x8, 0x3b, 0xe5, 0x63) + +DEFINE_GUID_ENUM(IID_IHXRMFileSinkControl, + 0xcb88b91, 0xa444, 0x11d2, 0x87, 0x92, 0x0, 0xc0, 0xf0, 0x31, 0x93, 0x8b) + +#endif + +/* + * File: + * hxqos.h + * + * Description: + * + * Server QoS subsystem profile selection, and QoS Signalling mechanism + */ +#ifndef _HX_QOS_H_ + +DEFINE_GUID_ENUM(IID_IHXQoSProfileSelector, + 0x75db043b, 0xc5a8, 0x49b2, 0x8d, 0x3f, 0x8c, 0xf9, 0x9f, 0x9e, 0x64, 0x44) + +DEFINE_GUID_ENUM(IID_IHXQoSSignalSourceResponse, + 0xb6154b09, 0xbbc3, 0x4239, 0xbe, 0x8b, 0x81, 0x60, 0x7c, 0xa0, 0xbe, 0x9) + +DEFINE_GUID_ENUM(IID_IHXQoSSignalSource, + 0x42aeddae, 0x3c4a, 0x498c, 0x86, 0x3e, 0xe6, 0xed, 0xee, 0xd4, 0x2, 0xa5) + +DEFINE_GUID_ENUM(IID_IHXQoSSignalSink, + 0x8b94c9cf, 0x48e2, 0x4384, 0xbc, 0x39, 0x70, 0x1b, 0x92, 0x4f, 0x55, 0x6f) + +DEFINE_GUID_ENUM(IID_IHXQoSSignalBus, + 0x8003507e, 0x453f, 0x4439, 0xbf, 0x8, 0x7f, 0x8a, 0xe, 0x8, 0x3d, 0x9e) + +DEFINE_GUID_ENUM(IID_IHXQoSSignal, + 0x32126bdc, 0x74, 0x4f43, 0x8d, 0x2c, 0x65, 0xd7, 0x6d, 0x60, 0xb5, 0xcb) + +DEFINE_GUID_ENUM(IID_IHXQoSClassFactory, + 0xc1316a78, 0x2960, 0x4f5c, 0xa2, 0xa9, 0x46, 0xd9, 0xc9, 0x25, 0xe8, 0x8f) + +DEFINE_GUID_ENUM(IID_IHXQoSCongestionControl, + 0x99c0a316, 0xfbbc, 0x41a9, 0x96, 0x28, 0xee, 0xcc, 0xcc, 0xf2, 0x20, 0x3f) + +DEFINE_GUID_ENUM(IID_IHXQoSCongestionEquation, + 0xce97019f, 0x6a3d, 0x4c26, 0x81, 0x55, 0xc8, 0xae, 0xba, 0xdc, 0x77, 0x62) + +DEFINE_GUID_ENUM(IID_IHXQoSDiffServConfigurator, + 0x5fb79e3a, 0x9bef, 0x4676, 0x89, 0x77, 0x8, 0xff, 0x6b, 0x2d, 0x72, 0x70) + +DEFINE_GUID_ENUM(IID_IHXQoSProfileConfigurator, + 0x75db043b, 0xc5a8, 0x49b2, 0x8d, 0x3f, 0x8c, 0xf9, 0x9f, 0x9e, 0x64, 0x48) + +DEFINE_GUID_ENUM(IID_IHXQoSRateShapeAggregator, + 0x18496bea, 0xf2a7, 0x45fc, 0xb3, 0x12, 0x88, 0x52, 0x14, 0x11, 0x9a, 0xb2) + +DEFINE_GUID_ENUM(IID_IHXQoSRateShaper, + 0x2a287694, 0xef37, 0x4266, 0x85, 0x8f, 0xe8, 0x4f, 0xe3, 0x6a, 0xbd, 0x90) + +#endif /* _HX_QOS_H_ */ + +#ifndef _HXQOSSESS_H + +DEFINE_GUID_ENUM(IID_IHXQoSClientBufferVerifier, + 0x51d547a0, 0xf019, 0x49e8, 0xb4, 0x98, 0x50, 0xdf, 0x22, 0xf7, 0xeb, 0x6b) + +DEFINE_GUID_ENUM(IID_IHXQoSRateManager, + 0x8fce6b44, 0x6067, 0x4060, 0x86, 0x75, 0xd8, 0xb5, 0x6d, 0x5, 0x95, 0x92) + +DEFINE_GUID_ENUM(IID_IHXClientBufferInfo, + 0xa7a1ab2, 0x6e3e, 0x4ed4, 0x80, 0xf, 0x97, 0xd5, 0x4e, 0x67, 0xee, 0x3c) +#endif + +#ifndef _HXQOSBUFFERUPDATE_H_ +DEFINE_GUID_ENUM(IID_IHXBufferDepthUpdate, + 0xbdeaf826, 0x1cd5, 0x4200, 0xb1, 0xb2, 0x48, 0x53, 0x9d, 0xdf, 0xd4, 0x58) +#endif + +#ifndef _HXPCKTFLWCTRL_H_ +DEFINE_GUID_ENUM(IID_IHXPacketFlowControl, + 0x00000130, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) + +#endif + +#ifndef _HX_STREAMADAPT_H +DEFINE_GUID_ENUM(IID_IHXStreamAdaptationSetup, + 0xad4ebf5, 0xf125, 0x42ca, 0xb4, 0x77, 0xb4, 0xae, 0x82, 0x8a, 0xc5, 0xf) + +DEFINE_GUID_ENUM(IID_IHXQoSLinkCharSetup, + 0x1a668eb, 0xdc9e, 0x414f, 0xa8, 0x68, 0xc9, 0x48, 0x9, 0xa1, 0x3a, 0x6b) + +DEFINE_GUID_ENUM(IID_IHXQoSRateMgrClassFactory, + 0xcc46e2f4, 0xf985, 0x49b7, 0x9b, 0x7f, 0x83, 0x44, 0x72, 0xba, 0x0, 0xe2) +#endif +/* + * Server-specific IID's required for capability exchange + */ + +#ifndef _HX_CLIENT_PROFILE_H_ +DEFINE_GUID_ENUM(IID_IHXClientProfileManager, + 0x00004400, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) + +DEFINE_GUID_ENUM(IID_IHXClientProfileManagerResponse, + 0x00004401, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) + +DEFINE_GUID_ENUM(IID_IHXClientProfileInfo, + 0x00004402, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) + +DEFINE_GUID_ENUM(IID_IHXClientProfile, + 0x00004403, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) + +DEFINE_GUID_ENUM(IID_IHXCPAttribute, + 0x00004404, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +#endif + +#ifndef _HX_PSS_PROFILE_H +DEFINE_GUID_ENUM(IID_IHXPSSProfileData, + 0x00004410, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) + +DEFINE_GUID_ENUM(IID_IHXPSSPTAgent, + 0x00004411, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) + +DEFINE_GUID_ENUM(IID_IHXPSSPTAgentResponse, + 0x00004412, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +#endif + +#ifndef _HXRDFPARSER_H_ +DEFINE_GUID_ENUM(IID_IHXRDFParser, + 0x00004420, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) + +DEFINE_GUID_ENUM(IID_IHXRDFGraph, + 0x00004421, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) + +DEFINE_GUID_ENUM(IID_IHXRDFGraphNode, + 0x00004422, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) + +DEFINE_GUID_ENUM(IID_IHXRDFGraphEdge, + 0x00004423, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +#endif + +#ifndef _HXPROFILECACHE_H_ +DEFINE_GUID_ENUM(IID_IHXProfileCache, + 0x00004430, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +#endif + +#ifndef _HXPRDNLD_H_ +DEFINE_GUID_ENUM(IID_IHXPDStatusMgr, + 0x00004500, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXPDStatusObserver, + 0x00004501, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXMediaBytesToMediaDur, + 0x00004510, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +#endif /* _HXPRDNLD_H_ */ + +#ifndef _ISIFS_H_ +/* file server/engine/inputsource/pub/isifs.h */ +DEFINE_GUID_ENUM(IID_IHXRateDescription, + 0x4b5b28f6, 0xf6ff, 0x4f60, 0xad, 0x66, 0xac, 0x6d, 0xe9, 0xe7, 0x5c, 0xdf) +DEFINE_GUID_ENUM(IID_IHXRateDescManager, + 0x8467996c, 0x9097, 0x4721, 0x9d, 0x4d, 0xd8, 0xa6, 0xd4, 0xbb, 0x03, 0x03) +DEFINE_GUID_ENUM(IID_IHXRateDescResponse, + 0x1e8e5cb1, 0x36ee, 0x4209, 0x9d, 0xcc, 0x4b, 0x66, 0x1f, 0x7b, 0xaa, 0x59) +DEFINE_GUID_ENUM(IID_IHXUberStreamManager, + 0xe7903a70, 0x43f5, 0x4aee, 0xa3, 0x6b, 0xeb, 0xfe, 0x3c, 0x9b, 0x49, 0xea) +DEFINE_GUID_ENUM(IID_IHXUberStreamManagerInit, + 0xb9db235b, 0xdef7, 0x4e22, 0xb9, 0x4e, 0x6e, 0xcd, 0xce, 0xb7, 0xb3, 0x1b) +DEFINE_GUID_ENUM(IID_IHXUberStreamManagerConfig, + 0x280d65ea, 0x7b81, 0x42b0, 0xaa, 0xc6, 0x15, 0x1a, 0x94, 0xe7, 0xbb, 0x2e) +DEFINE_GUID_ENUM(IID_IHXStreamSelector, + 0x246e42b4, 0xecad, 0x4064, 0xab, 0x3a, 0x6f, 0x0, 0x60, 0x94, 0x46, 0x8c) +DEFINE_GUID_ENUM(IID_IHXRateDescEnumerator, + 0x9a32b0bb, 0xea2c, 0x47d3, 0x8f, 0x44, 0xb1, 0x80, 0xca, 0x6b, 0xa2, 0xbb) +DEFINE_GUID_ENUM(IID_IHXStreamRateDescManager, + 0x4d56585b, 0xe47a, 0x46e0, 0xba, 0xf6, 0x14, 0x62, 0x13, 0xba, 0x94, 0xab) +DEFINE_GUID_ENUM(IID_IHXStreamRateDescResponse, + 0x544d3cd9, 0x8ed8, 0x4473, 0xbd, 0x83, 0xec, 0x52, 0x90, 0xec, 0x8, 0x39) +#endif + +#ifndef _ISPIFS_H_ +/* file server/engine/inputsource/pub/ispifs.h */ +DEFINE_GUID_ENUM(IID_IHXSyncHeaderSource, + 0x5e5ed607, 0x79f0, 0x4b8d, 0xa0, 0xf6, 0x70, 0xd6, 0x57, 0x8d, 0x63, 0x99) +DEFINE_GUID_ENUM(IID_IHXRateDescVerifier, + 0xec48325f, 0x4f89, 0x4542, 0x94, 0x51, 0xf8, 0x8f, 0xd3, 0x74, 0x03, 0xef) + +#endif + +#ifndef _HXNET_H +/* common/include/hxnet.h */ +DEFINE_GUID_ENUM(IID_IHXSockAddrNative, 0x9a419062, 0xdb35, 0x11d8, 0xb8, 0x60, 0x00, + 0x02, 0xb3, 0x65, 0x87, 0x20) +DEFINE_GUID_ENUM(IID_IHXSockAddr, 0xa954f190, 0x7c47, 0x11d8, 0x8b, 0xcb, 0x00, + 0x02, 0xb3, 0x65, 0x87, 0x20) +DEFINE_GUID_ENUM(IID_IHXSockAddrLocal, 0xa9552beb, 0x7c47, 0x11d8, 0x8b, 0xcb, 0x00, + 0x02, 0xb3, 0x65, 0x87, 0x20) +DEFINE_GUID_ENUM(IID_IHXSockAddrIN4, 0xa9552bec, 0x7c47, 0x11d8, 0x8b, 0xcb, 0x00, + 0x02, 0xb3, 0x65, 0x87, 0x20) +DEFINE_GUID_ENUM(IID_IHXSockAddrIN6, 0xa95566de, 0x7c47, 0x11d8, 0x8b, 0xcb, 0x00, + 0x02, 0xb3, 0x65, 0x87, 0x20) +DEFINE_GUID_ENUM(IID_IHXSocketAccessControl, 0x831bc2f2, 0xd236, 0x11d8, 0x97, 0x45, 0x00, + 0x02, 0xb3, 0x65, 0x87, 0x20) +DEFINE_GUID_ENUM(IID_IHXSocketResponse, 0xa955da88, 0x7c47, 0x11d8, 0x8b, 0xcb, 0x00, + 0x02, 0xb3, 0x65, 0x87, 0x20) +DEFINE_GUID_ENUM(IID_IHXSocket, 0xa955a090, 0x7c47, 0x11d8, 0x8b, 0xcb, 0x00, + 0x02, 0xb3, 0x65, 0x87, 0x20) +DEFINE_GUID_ENUM(IID_IHXMulticastSocket, 0xa9561368, 0x7c47, 0x11d8, 0x8b, 0xcb, 0x00, + 0x02, 0xb3, 0x65, 0x87, 0x20) +DEFINE_GUID_ENUM(IID_IHXListeningSocketResponse, 0xa95686b8, 0x7c47, 0x11d8, 0x8b, 0xcb, 0x00, + 0x02, 0xb3, 0x65, 0x87, 0x20) +DEFINE_GUID_ENUM(IID_IHXListeningSocket, 0xa9564d38, 0x7c47, 0x11d8, 0x8b, 0xcb, 0x00, + 0x02, 0xb3, 0x65, 0x87, 0x20) +DEFINE_GUID_ENUM(IID_IHXAddrInfo, 0xa956c0ba, 0x7c47, 0x11d8, 0x8b, 0xcb, 0x00, + 0x02, 0xb3, 0x65, 0x87, 0x20) +DEFINE_GUID_ENUM(IID_IHXResolve, 0xa956fa80, 0x7c47, 0x11d8, 0x8b, 0xcb, 0x00, + 0x02, 0xb3, 0x65, 0x87, 0x20) +DEFINE_GUID_ENUM(IID_IHXResolveResponse, 0xa9574904, 0x7c47, 0x11d8, 0x8b, 0xcb, 0x00, + 0x02, 0xb3, 0x65, 0x87, 0x20) +DEFINE_GUID_ENUM(IID_IHXNetServices, 0xa9578e5a, 0x7c47, 0x11d8, 0x8b, 0xcb, 0x00, + 0x02, 0xb3, 0x65, 0x87, 0x20) +#endif + +#ifndef _IHXTLOGCONTEXTOBSERVER_H +DEFINE_GUID_ENUM(IID_IHXTLogContextObserver, + 0xd7bf2f2c, 0x19a, 0x4642, 0x89, 0x3c, 0x9b, 0x8c, 0xb7, 0x54, 0x29, 0x45) +#endif /* #ifndef _IHXTLOGCONTEXTOBSERVER_H */ + +#ifndef IHXTLOGSYSTEM_H +DEFINE_GUID_ENUM(IID_IHXTLogObserver, + 0xea6abcf4, 0x66eb, 0x11d4, 0x93, 0x1a, 0x0, 0xd0, 0xb7, 0x49, 0xde, 0x42) +DEFINE_GUID_ENUM(IID_IHXTLogObserver2, + 0x68afe313, 0xbe30, 0x4b46, 0xbf, 0xad, 0x6f, 0x3, 0x5e, 0x62, 0x4c, 0x8a) +DEFINE_GUID_ENUM(IID_IHXTFuncAreaEnum, + 0x938f4a21, 0x1327, 0x11d5, 0x93, 0x49, 0x0, 0xd0, 0xb7, 0x49, 0xde, 0x42) +DEFINE_GUID_ENUM(IID_IHXTLogObserverManager, + 0xea6abcdc, 0x66eb, 0x11d4, 0x93, 0x1a, 0x0, 0xd0, 0xb7, 0x49, 0xde, 0x42) +DEFINE_GUID_ENUM(IID_IHXTLogObserverManager2, + 0x0e38953f, 0x25ad, 0x4efb, 0x9a, 0xd4, 0x2c, 0xbb, 0xc9, 0xd6, 0x2a, 0xb0) +DEFINE_GUID_ENUM(IID_IHXTLogWriter, + 0xea6abcd9, 0x66eb, 0x11d4, 0x93, 0x1a, 0x0, 0xd0, 0xb7, 0x49, 0xde, 0x42) +DEFINE_GUID_ENUM(IID_IHXTInternalLogWriter, + 0xe7adc1b7, 0x7b6e, 0x4e54, 0x98, 0x78, 0xaa, 0x81, 0xe, 0xcc, 0x6d, 0xe6) +DEFINE_GUID_ENUM(IID_IHXTLogSystem, + 0xe50f7e51, 0x4640, 0x11d5, 0x93, 0x5b, 0x0, 0xd0, 0xb7, 0x49, 0xde, 0x42) +#endif /* #ifndef IHXTLOGSYSTEM_H */ + +#ifndef _IHXTLOGSYSTEMCONTEXT_H +DEFINE_GUID_ENUM(IID_IHXTLogSystemContext, + 0x1afceebd, 0x17b1, 0x4698, 0xa4, 0xa4, 0x1c, 0x8d, 0x84, 0x56, 0x55, 0x12) +#endif /* #ifndef _IHXTLOGSYSTEMCONTEXT_H */ + +#ifndef _IHXTFILEOBSERVER_H + +DEFINE_GUID_ENUM(IID_IHXTFileObserver, +0x478d6411, 0xa712, 0x11d5, 0x93, 0x74, 0x0, 0xd0, 0xb7, 0x49, 0xde, 0x42) + +#endif //_IHXTFILEOBSERVER_H + +#ifndef _RTP_INFO_SYNC_H_ +/* file protocol/transport/rtp/include/rtpinfosync.h */ +DEFINE_GUID_ENUM(IID_IHXRTPInfoSynch, + 0x1d9df3ad, 0x7429, 0x4efc, 0xb5, 0x4d, 0xd6, 0x7e, 0x25, 0x63, 0x4, 0xc4) +#endif + +#ifndef HXPLAYVELOCITY_H +DEFINE_GUID_ENUM(IID_IHXPlaybackVelocityCaps, + 0xdadb9abf, 0x549e, 0x4a6a, 0xa3, 0x6a, 0x59, 0x7e, 0xe2, 0x70, 0xc6, 0xa0) +DEFINE_GUID_ENUM(IID_IHXPlaybackVelocityResponse, + 0xa4f87cbb, 0x8ec5, 0x4d87, 0x90, 0x8d, 0x1a, 0x98, 0xdb, 0xbd, 0xe4, 0xdf) +DEFINE_GUID_ENUM(IID_IHXPlaybackVelocity, + 0x42bd6e35, 0x20ac, 0x4f7e, 0x9d, 0x9b, 0xb0, 0xbb, 0x3e, 0xcb, 0x1b, 0xe9) +DEFINE_GUID_ENUM(IID_IHXPlaybackVelocityTimeRegulator, + 0xa0a5d241, 0xc387, 0x4f85, 0x9b, 0xe6, 0x9b, 0xa4, 0xa0, 0xf1, 0x8c, 0x94) +#endif /* #ifndef HXPLAYVELOCITY_H */ + +DEFINE_GUID_ENUM(IID_IHXPacketOrderer, + 0x132bceaf, 0xbe58, 0x4090, 0x92, 0x9c, 0x50, 0x73, 0x6d, 0xb3, 0xe, 0x68) + +#endif /* _HXIIDS_H_ */ + diff --git a/amarok/src/engine/helix/helix-sp/helix-include/common/include/hxmon.h b/amarok/src/engine/helix/helix-sp/helix-include/common/include/hxmon.h new file mode 100644 index 00000000..d2f9f8af --- /dev/null +++ b/amarok/src/engine/helix/helix-sp/helix-include/common/include/hxmon.h @@ -0,0 +1,2356 @@ + +/* + * + * This software is released under the provisions of the GPL version 2. + * see file "COPYING". If that file is not available, the full statement + * of the license can be found at + * + * http://www.fsf.org/licensing/licenses/gpl.txt + * + * Portions Copyright (c) 1995-2004 RealNetworks, Inc. All Rights Reserved. + * + */ + +#ifndef _HXMON_H_ +#define _HXMON_H_ + +#include "hlxclib/limits.h" + +typedef _INTERFACE IUnknown IUnknown; +typedef _INTERFACE IHXPlugin IHXPlugin; +typedef _INTERFACE IHXBuffer IHXBuffer; +typedef _INTERFACE IHXValues IHXValues; +typedef _INTERFACE IHXPropWatch IHXPropWatch; +typedef _INTERFACE IHXPropWatchResponse IHXPropWatchResponse; +typedef _INTERFACE IHXActiveRegistry IHXActiveRegistry; +typedef _INTERFACE IHXActivePropUser IHXActivePropUser; +typedef _INTERFACE IHXActivePropUserResponse IHXActivePropUserResponse; +typedef _INTERFACE IHXRegistryAltStringHandling IHXRegistryAltStringHandling; + +/* + * Types of the values stored in the registry. + */ +typedef enum _HXPropType +{ + PT_UNKNOWN, + PT_COMPOSITE, /* Contains other values (elements) */ + PT_INTEGER, /* 32-bit signed value */ + PT_INTREF, /* Integer reference object -- 32-bit signed integer */ + PT_STRING, /* Signed char* value */ + PT_BUFFER, /* IHXBuffer object */ + + /*IHXRegistry2: */ + PT_INTEGER64, /* 64-bit signed value */ + PT_INT64REF /* Integer reference object -- 64-bit signed integer */ + +} HXPropType; + + +/* + * + * Interface: + * + * IHXRegistry + * + * Purpose: + * + * This interface provides access to the "Registry" in the server and + * client. The "Registry" is a hierarchical structure of Name/Value + * pairs (properties) which is capable of storing many different types + * of data including strings, buffers, and integers. The registry + * provides various types of information including statistics, + * configuration information, and system status. + * + * Note: This registry is not related to the Windows system registry. + * + * IID_IHXRegistry: + * + * {00000600-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXRegistry, 0x00000600, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#define CLSID_IHXRegistry IID_IHXRegistry + +#undef INTERFACE +#define INTERFACE IHXRegistry + +DECLARE_INTERFACE_(IHXRegistry, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXRegistry methods + */ + + /************************************************************************ + * Method: + * IHXRegistry::CreatePropWatch + * Purpose: + * Create a new IHXPropWatch object which can then be queried for + * the right kind of IHXPropWatch object. + * + * pPropWatch - OUT - returns a new addref'ed IHXPropWatch object + */ + STDMETHOD(CreatePropWatch) (THIS_ + REF(IHXPropWatch*) pPropWatch) PURE; + + /************************************************************************ + * Method: + * IHXRegistry::AddComp + * Purpose: + * Add a COMPOSITE property to the registry and return its ID + * if successful. It returns ZERO (0) if an error occurred + * during the operation. + * + * pName - IN - name of the Property that is going to be added to + * the registry + */ + STDMETHOD_(UINT32, AddComp) (THIS_ + const char* pName) PURE; + + /************************************************************************ + * Method: + * IHXRegistry::AddInt + * Purpose: + * Add an INTEGER property with name in "pName" and value in + * "iValue" to the registry. The return value is the id to + * the newly added Property or ZERO if there was an error. + * + * pName - IN - name of the Property that is going to be added to + * the registry + * nValue - IN - integer value of the Property that is going to be + * added to the registry + */ + STDMETHOD_(UINT32, AddInt) (THIS_ + const char* pName, + const INT32 nValue) PURE; + + /************************************************************************ + * Method: + * IHXRegistry::GetIntByName + * Purpose: + * Retreive an INTEGER value from the registry given its Property + * name "pName". If the Property is found, it will return HXR_OK, + * otherwise it returns HXR_FAIL. + * + * pName - IN - name of the Property whose value is to be retrieved + * nValue - OUT - parameter into which the value of the Property is + * going to be returned + */ + STDMETHOD(GetIntByName) (THIS_ + const char* pName, + REF(INT32) nValue) const PURE; + + /************************************************************************ + * Method: + * IHXRegistry::GetIntById + * Purpose: + * Retreive an INTEGER value from the registry given its id "ulId". + * If the Property is found, it will return HXR_OK, otherwise it + * returns HXR_FAIL. + * + * ulId - IN - unique id of the Property whose value is to be retrieved + * nValue - OUT - parameter into which the value of the Property is + * going to be returned + */ + STDMETHOD(GetIntById) (THIS_ + const UINT32 ulId, + REF(INT32) nValue) const PURE; + + /************************************************************************ + * Method: + * IHXRegistry::SetIntByName + * Purpose: + * Modify a Property's INTEGER value in the registry given the + * Property's name "pName". If the value was set, it will return HXR_OK, + * otherwise it returns HXR_FAIL. + * + * pName - IN - name of the Property whose value is to be set + * nValue - IN - the new value of the Property which is going to be set + */ + STDMETHOD(SetIntByName) (THIS_ + const char* pName, + const INT32 nValue) PURE; + + /************************************************************************ + * Method: + * IHXRegistry::SetIntById + * Purpose: + * Modify a Property's INTEGER value in the registry given the + * its id "id". If the value was set, it will return HXR_OK, otherwise + * it returns HXR_FAIL. + * + * ulId - IN - unique id of the Property whose value is to be set + * nValue - IN - the new value of the Property which is going to be set + */ + STDMETHOD(SetIntById) (THIS_ + const UINT32 id, + const INT32 nValue) PURE; + + /************************************************************************ + * Method: + * IHXRegistry::AddStr + * Purpose: + * Add an STRING property with name in "pName" and value in + * "pValue" to the registry. + * + * pName - IN - name of the Property that is going to be added to + * the registry + * pValue - IN - buffer value of the Property that is going to be + * added to the registry + */ + STDMETHOD_(UINT32, AddStr) (THIS_ + const char* pName, + IHXBuffer* pValue) PURE; + + /************************************************************************ + * Method: + * IHXRegistry::GetStrByName + * Purpose: + * Retreive an STRING value from the registry given its Property + * name "pName". If the Property is found, it will return HXR_OK, + * otherwise it returns HXR_FAIL. + * + * pName - IN - name of the Property whose value is to be retrieved + * pValue - OUT - parameter into which the value of the Property is + * going to be returned + */ + STDMETHOD(GetStrByName) (THIS_ + const char* pName, + REF(IHXBuffer*) pValue) const PURE; + + /************************************************************************ + * Method: + * IHXRegistry::GetStrById + * Purpose: + * Retreive an STRING value from the registry given its id "ulId". + * If the Property is found, it will return HXR_OK, otherwise it + * returns HXR_FAIL. + * + * ulId - IN - unique id of the Property whose value is to be retrieved + * pValue - OUT - parameter into which the value of the Property is + * going to be returned + */ + STDMETHOD(GetStrById) (THIS_ + const UINT32 ulId, + REF(IHXBuffer*) pValue) const PURE; + + /************************************************************************ + * Method: + * IHXRegistry::SetStrByName + * Purpose: + * Modify a Property's STRING value in the registry given the + * Property's name "pName". If the value was set, it will return + * HXR_OK, otherwise it returns HXR_FAIL. + * + * pName - IN - name of the Property whose value is to be set + * pValue - IN - the new value of the Property which is going to be set + */ + STDMETHOD(SetStrByName) (THIS_ + const char* pName, + IHXBuffer* pValue) PURE; + + /************************************************************************ + * Method: + * IHXRegistry::SetStrById + * Purpose: + * Modify a Property's STRING value in the registry given the + * its id "ulId". If the value was set, it will return HXR_OK, + * otherwise it returns HXR_FAIL. + * + * ulId - IN - unique id of the Property whose value is to be set + * pValue - IN - the new value of the Property which is going to be set + */ + STDMETHOD(SetStrById) (THIS_ + const UINT32 ulId, + IHXBuffer* pValue) PURE; + + /************************************************************************ + * Method: + * IHXRegistry::AddBuf + * Purpose: + * Add an BUFFER property with name in "pName" and value in + * "pValue" to the registry. + * + * pName - IN - name of the Property that is going to be added to + * the registry + * pValue - IN - buffer value of the Property that is going to be + * added to the registry + */ + STDMETHOD_(UINT32, AddBuf) (THIS_ + const char* pName, + IHXBuffer* pValue) PURE; + + /************************************************************************ + * Method: + * IHXRegistry::GetBufByName + * Purpose: + * Retreive the BUFFER from the registry given its Property name + * "pName". If the Property is found, it will return HXR_OK, otherwise + * it returns HXR_FAIL. + * + * pName - IN - name of the Property whose value is to be retrieved + * pValue - OUT - parameter into which the value of the Property is + * going to be returned + */ + STDMETHOD(GetBufByName) (THIS_ + const char* pName, + REF(IHXBuffer*) pValue) const PURE; + + /************************************************************************ + * Method: + * IHXRegistry::GetBufById + * Purpose: + * Retreive the BUFFER from the registry given its id "ulId". If the + * Property is found, it will return HXR_OK, otherwise it returns + * HXR_FAIL. + * + * ulId - IN - unique id of the Property whose value is to be retrieved + * pValue - OUT - parameter into which the value of the Property is + * going to be returned + */ + STDMETHOD(GetBufById) (THIS_ + const UINT32 ulId, + REF(IHXBuffer*) pValue) const PURE; + + /************************************************************************ + * Method: + * IHXRegistry::SetBufByName + * Purpose: + * Modify a Property's BUFFER in the registry given the + * Property's name "pName". If the value was set, it will return + * HXR_OK, otherwise it returns HXR_FAIL. + * + * pName - IN - name of the Property whose value is to be set + * pValue - IN - the new value of the Property which is going to be set + */ + STDMETHOD(SetBufByName) (THIS_ + const char* pName, + IHXBuffer* pValue) PURE; + + /************************************************************************ + * Method: + * IHXRegistry::SetBufById + * Purpose: + * Modify a Property's BUFFER in the registry given its id "ulId". + * If the value was set, it will return HXR_OK, otherwise it returns + * HXR_FAIL. + * + * ulId - IN - unique id of the Property whose value is to be set + * pValue - IN - the new value of the Property which is going to be set + */ + STDMETHOD(SetBufById) (THIS_ + const UINT32 ulId, + IHXBuffer* pValue) PURE; + + /************************************************************************ + * Method: + * IHXRegistry::AddIntRef + * Purpose: + * Add an INTEGER REFERENCE property with name in "pName" and + * value in "iValue" to the registry. This property allows the user + * to modify its contents directly, without having to go through the + * registry. + * + * pName - IN - name of the Property that is going to be added to + * the registry + * pValue - IN - the pointer of the integer value is what gets stored + * in the registry as the Interger Reference Property's + * value + */ + STDMETHOD_(UINT32, AddIntRef) (THIS_ + const char* pName, + INT32* pValue) PURE; + + /************************************************************************ + * Method: + * IHXRegistry::DeleteByName + * Purpose: + * Delete a Property from the registry using its name "pName". + * + * pName - IN - name of the Property that is going to be deleted + */ + STDMETHOD_(UINT32, DeleteByName) (THIS_ + const char* pName) PURE; + + /************************************************************************ + * Method: + * IHXRegistry::DeleteById + * Purpose: + * Delete a Property from the registry using its id "ulId". + * + * ulId - IN - unique id of the Property that is going to be deleted + */ + STDMETHOD_(UINT32, DeleteById) (THIS_ + const UINT32 ulId) PURE; + + /************************************************************************ + * Method: + * IHXRegistry::GetTypeByName + * Purpose: + * Returns the datatype of the Property given its name "pName". + * + * pName - IN - name of the Property whose type is to be retrieved + */ + STDMETHOD_(HXPropType, GetTypeByName) (THIS_ + const char* pName) const PURE; + + /************************************************************************ + * Method: + * IHXRegistry::GetTypeById + * Purpose: + * Returns the datatype of the Property given its its id "ulId". + * + * ulId - IN - unique id of the Property whose type is to be retrieved + */ + STDMETHOD_(HXPropType, GetTypeById) (THIS_ + const UINT32 ulId) const PURE; + + /************************************************************************ + * Method: + * IHXRegistry::FindParentIdByName + * Purpose: + * Returns the id value of the parent node of the Property whose + * name "pName" has been passed in. If it fails, a ZERO value is + * returned. + * + * pName - IN - name of the Property whose parent's unique id is to be + * retrieved + */ + STDMETHOD_(UINT32, FindParentIdByName) (THIS_ + const char* pName) const PURE; + + /************************************************************************ + * Method: + * IHXRegistry::FindParentIdById + * Purpose: + * Returns the id value of the parent node of the Property whose + * id "ulId" has been passed in. If it fails, a ZERO value is returned. + * + * ulId - IN - unique id of the Property whose parent's id is to be + * retrieved + */ + STDMETHOD_(UINT32, FindParentIdById) (THIS_ + const UINT32 ulId) const PURE; + + /************************************************************************ + * Method: + * HXRegistry::GetPropName + * Purpose: + * Returns the Property name in the pName char buffer passed + * as a parameter, given the Property's id "ulId". + * + * ulId - IN - unique id of the Property whose name is to be retrieved + * pName - OUT - parameter into which the Property name is going to be + * returned + */ + STDMETHOD(GetPropName) (THIS_ + const UINT32 ulId, + REF(IHXBuffer*) pName) const PURE; + + /************************************************************************ + * Method: + * HXRegistry::GetId + * Purpose: + * Returns the Property's id given the Property name. + * + * pName - IN - name of the Property whose unique id is to be + * retrieved + */ + STDMETHOD_(UINT32, GetId) (THIS_ + const char* pName) const PURE; + + /************************************************************************ + * Method: + * IHXRegistry::GetPropListOfRoot + * Purpose: + * Returns an array of a Properties under the root level of the + * registry's hierarchy. + * + * pValues - OUT - list of property name and unique id at the + * highest level (root) in the registry + */ + STDMETHOD(GetPropListOfRoot) (THIS_ + REF(IHXValues*) pValues) const PURE; + + /************************************************************************ + * Method: + * IHXRegistry::GetPropListByName + * Purpose: + * Returns an array of Properties immediately under the one whose + * name is passed in "pName". + * + * pName - IN - name of the Property whose child property list is to be + * retrieved + * pValues - OUT - list of property name and unique id under the + * Property whose name is in "pName" + */ + STDMETHOD(GetPropListByName) (THIS_ + const char* pName, + REF(IHXValues*) pValues) const PURE; + + /************************************************************************ + * Method: + * IHXRegistry::GetPropListById + * Purpose: + * Returns an array of Properties immediately under the one whose + * id is passed in "ulId". + * + * ulId - IN - unique id of the Property whose child property list is + * to be retrieved + * pValues - OUT - list of property name and unique id under the + * Property whose is is in "ulId" + */ + STDMETHOD(GetPropListById) (THIS_ + const UINT32 ulId, + REF(IHXValues*) pValues) const PURE; + + /************************************************************************ + * Method: + * IHXRegistry::GetNumPropsAtRoot + * Purpose: + * Returns the number of Properties at the root of the registry. + */ + STDMETHOD_(INT32, GetNumPropsAtRoot) (THIS) const PURE; + + /************************************************************************ + * Method: + * IHXRegistry::GetNumPropsByName + * Purpose: + * Returns the count of the number of Properties under the one + * whose name is specified in "pName". + * + * pName - IN - name of the Property whose number of children is to be + * retrieved + */ + STDMETHOD_(INT32, GetNumPropsByName) (THIS_ + const char* pName) const PURE; + + /************************************************************************ + * Method: + * IHXRegistry::GetNumPropsById + * Purpose: + * Returns the count of the number of Properties under the one + * whose unique id is specified in "ulId". + * + * ulId - IN - unique id of the Property whose number of children is + * to be retrieved + */ + STDMETHOD_(INT32, GetNumPropsById) (THIS_ + const UINT32 ulId) const PURE; +}; + + +/* + * + * Interface: + * + * IHXPropWatch + * + * Purpose: + * + * This interface allows the user to watch properties so that when + * changes happen to the properties the plugins receive notification via + * the IHXPropWatchResponse API. + * + * IID_IHXPropWatch: + * + * {00000601-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXPropWatch, 0x00000601, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXPropWatch + +DECLARE_INTERFACE_(IHXPropWatch, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXPropWatch methods + */ + + /************************************************************************ + * Method: + * IHXPropWatch::Init + * Purpose: + * Initialize with the response object so that the Watch + * notifications can be sent back to the respective plugins. + * + * pResponse - IN - pointer to the response object which gets used to + * initialize the IHXPropWatch object. the response + * object gets AddRef'd in the Init method. + */ + STDMETHOD(Init) (THIS_ + IHXPropWatchResponse* pResponse) PURE; + + /************************************************************************ + * Method: + * IHXPropWatch::SetWatchOnRoot + * Purpose: + * The SetWatch method puts a watch at the highest level of + * the registry hierarchy. It notifies ONLY IF properties at THIS LEVEL + * get added/modified/deleted. + */ + STDMETHOD_(UINT32, SetWatchOnRoot) (THIS) PURE; + + /************************************************************************ + * Method: + * IHXPropWatch::SetWatchByName + * Purpose: + * Sets a watch-point on the Property whose name is passed in. + * In case the mentioned Property gets modified or deleted a + * notification of that will be sent to the object which set the + * watch-point. + * + * pName - IN - name of Property on which a watch point is to be added + */ + STDMETHOD_(UINT32, SetWatchByName) (THIS_ + const char* pName) PURE; + + /************************************************************************ + * Method: + * IHXPropWatch::SetWatchById + * Purpose: + * Sets a watch-point on the Property whose name is passed in. + * In case the mentioned Property gets modified or deleted a + * notification of that will be sent to the object which set the + * watch-point. + * + * ulId - IN - unique id of Property on which a watch point is to be + * added + */ + STDMETHOD_(UINT32, SetWatchById) (THIS_ + const UINT32 ulId) PURE; + + /************************************************************************ + * Method: + * IHXPropWatch::ClearWatchOnRoot + * Purpose: + * It clears the watch on the root of the registry. + */ + STDMETHOD(ClearWatchOnRoot) (THIS) PURE; + + /************************************************************************ + * Method: + * IHXPropWatch::ClearWatchByName + * Purpose: + * Clears a watch-point based on the Property's name. + * + * pName - IN - name of Property whose watch point is to be cleared + */ + STDMETHOD(ClearWatchByName) (THIS_ + const char* pName) PURE; + + /************************************************************************ + * Method: + * IHXPropWatch::ClearWatchById + * Purpose: + * Clears a watch-point based on the Property's id. + * + * ulId - IN - unique id of Property whose watch point is to be cleared + */ + STDMETHOD(ClearWatchById) (THIS_ + const UINT32 ulId) PURE; +}; + + +/* + * + * Interface: + * + * IHXPropWatchResponse + * + * Purpose: + * + * Interface for notification of additions/modifications/deletions of + * properties in the registry which are being watched. + * + * IID_IHXPropWatchResponse: + * + * {00000602-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXPropWatchResponse, 0x00000602, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXPropWatchResponse + +DECLARE_INTERFACE_(IHXPropWatchResponse, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXPropWatchResponse methods + */ + + /************************************************************************ + * Method: + * IHXPropWatchResponse::AddedProp + * Purpose: + * Gets called when a new Property gets added under the Property + * on which the Watch was set. It passes the id of the Property just + * added, its datatype and the id of its immediate parent COMPOSITE + * property. + */ + STDMETHOD(AddedProp) (THIS_ + const UINT32 ulId, + const HXPropType propType, + const UINT32 ulParentID) PURE; + + /************************************************************************ + * Method: + * IHXPropWatchResponse::ModifiedProp + * Purpose: + * Gets called when a watched Property gets modified. It passes + * the id of the Property just modified, its datatype and the + * id of its immediate parent COMPOSITE property. + */ + STDMETHOD(ModifiedProp) (THIS_ + const UINT32 ulId, + const HXPropType propType, + const UINT32 ulParentID) PURE; + + /************************************************************************ + * Method: + * IHXPropWatchResponse::DeletedProp + * Purpose: + * Gets called when a watched Property gets deleted. As can be + * seen, it returns the id of the Property just deleted and + * its immediate parent COMPOSITE property. + */ + STDMETHOD(DeletedProp) (THIS_ + const UINT32 ulId, + const UINT32 ulParentID) PURE; +}; + +/* + * + * Interface: + * + * IHXActiveRegistry + * + * Purpose: + * + * Interface to get IHXActiveUser responsible for a particular property + * from the registry. + * + * IID_IHXActiveRegistry: + * + * {00000603-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXActiveRegistry, 0x00000603, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXActiveRegistry + +DECLARE_INTERFACE_(IHXActiveRegistry, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /************************************************************************ + * IHXActiveRegistry::SetAsActive + * + * Method to set prop pName to active and register pUser as + * the active prop user. + */ + STDMETHOD(SetAsActive) (THIS_ + const char* pName, + IHXActivePropUser* pUser) PURE; + + /************************************************************************ + * IHXActiveRegistry::SetAsInactive + * + * Method to remove an IHXActiveUser from Prop activation. + */ + STDMETHOD(SetAsInactive) (THIS_ + const char* pName, + IHXActivePropUser* pUser) PURE; + + /************************************************************************ + * IHXActiveRegistry::IsActive + * + * Tells if prop pName has an active user that must be queried to + * change the value, or if it can just be set. + */ + STDMETHOD_(HXBOOL, IsActive) (THIS_ + const char* pName) PURE; + + /************************************************************************ + * IHXActiveRegistry::SetActiveInt + * + * Async request to set int pName to ul. + */ + STDMETHOD(SetActiveInt) (THIS_ + const char* pName, + UINT32 ul, + IHXActivePropUserResponse* pResponse) PURE; + + /************************************************************************ + * IHXActiveRegistry::SetActiveStr + * + * Async request to set string pName to string in pBuffer. + */ + STDMETHOD(SetActiveStr) (THIS_ + const char* pName, + IHXBuffer* pBuffer, + IHXActivePropUserResponse* pResponse) PURE; + + /************************************************************************ + * IHXActiveRegistry::SetActiveBuf + * + * Async request to set buffer pName to buffer in pBuffer. + */ + STDMETHOD(SetActiveBuf) (THIS_ + const char* pName, + IHXBuffer* pBuffer, + IHXActivePropUserResponse* pResponse) PURE; + + /************************************************************************ + * IHXActiveRegistry::DeleteActiveProp + * + * Async request to delete the active property. + */ + STDMETHOD(DeleteActiveProp) (THIS_ + const char* pName, + IHXActivePropUserResponse* pResponse) PURE; + + +}; + + +/* + * + * Interface: + * + * IHXActivePropUser + * + * Purpose: + * + * An IHXActivePropUser can be set as the active user of a property in + * an IHXActiveRegistry. This causes the IHXActivePropUser to be consulted + * every time someone wants to change a property. The difference between this + * and a prop watch is that this is async, and can call a done method with + * failure to cause the prop to not be set, and this get called instead of + * calling into the IHXReg. + * + * IID_IHXActivePropUser: + * + * {00000604-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXActivePropUser, 0x00000604, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXActivePropUser + +DECLARE_INTERFACE_(IHXActivePropUser, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /************************************************************************ + * IHXActivePropUser::SetActiveInt + * + * Async request to set int pName to ul. + */ + STDMETHOD(SetActiveInt) (THIS_ + const char* pName, + UINT32 ul, + IHXActivePropUserResponse* pResponse) PURE; + + /************************************************************************ + * IHXActivePropUser::SetActiveStr + * + * Async request to set string pName to string in pBuffer. + */ + STDMETHOD(SetActiveStr) (THIS_ + const char* pName, + IHXBuffer* pBuffer, + IHXActivePropUserResponse* pResponse) PURE; + + /************************************************************************ + * IHXActivePropUser::SetActiveBuf + * + * Async request to set buffer pName to buffer in pBuffer. + */ + STDMETHOD(SetActiveBuf) (THIS_ + const char* pName, + IHXBuffer* pBuffer, + IHXActivePropUserResponse* pResponse) PURE; + + /************************************************************************ + * IHXActivePropUser::DeleteActiveProp + * + * Async request to delete the active property. + */ + STDMETHOD(DeleteActiveProp) (THIS_ + const char* pName, + IHXActivePropUserResponse* pResponse) PURE; + +}; + +/* + * + * Interface: + * + * IHXActivePropUserResponse + * + * Purpose: + * + * Gets responses from IHXActivePropUser for queries to set properties + * in the IHXActiveRegistry. + * + * + * IID_IHXActivePropUserResponse: + * + * {00000605-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXActivePropUserResponse, 0x00000605, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXActivePropUserResponse + +DECLARE_INTERFACE_(IHXActivePropUserResponse, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /************************************************************************ + * Called with status result on completion of set request. + */ + STDMETHOD(SetActiveIntDone) (THIS_ + HX_RESULT res, + const char* pName, + UINT32 ul, + IHXBuffer* pInfo[], + UINT32 ulNumInfo) PURE; + + STDMETHOD(SetActiveStrDone) (THIS_ + HX_RESULT res, + const char* pName, + IHXBuffer* pBuffer, + IHXBuffer* pInfo[], + UINT32 ulNumInfo) PURE; + + STDMETHOD(SetActiveBufDone) (THIS_ + HX_RESULT res, + const char* pName, + IHXBuffer* pBuffer, + IHXBuffer* pInfo[], + UINT32 ulNumInfo) PURE; + + STDMETHOD(DeleteActivePropDone) (THIS_ + HX_RESULT res, + const char* pName, + IHXBuffer* pInfo[], + UINT32 ulNumInfo) PURE; + +}; + +/* + * + * Interface: + * + * IHXCopyRegistry + * + * Purpose: + * + * Allows copying from one registry key to another. + * + * + * IID_IHXCopyRegistry + * + * {00000606-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXCopyRegistry, 0x00000606, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXCopyRegistry + +DECLARE_INTERFACE_(IHXCopyRegistry, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /************************************************************************ + * IHXCopyRegistry::Copy + * + * Here it is! The "Copy" method! + */ + STDMETHOD (CopyByName) (THIS_ + const char* pFrom, + const char* pTo) PURE; +}; + + +/* + * + * Interface: + * + * IHXRegistryAltStringHandling + * + * Purpose: + * + * Tells the registry about alternate handling of PT_STRING types. + * + * + * IID_IHXRegistryAltStringHandling + * + * {00000607-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXRegistryAltStringHandling, 0x00000607, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXRegistryAltStringHandling + +DECLARE_INTERFACE_(IHXRegistryAltStringHandling, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /************************************************************************ + * IHXRegistryAltStringHandling::SetStringAccessAsBufferById + * + * For those times when you added a property as a buffer, but wish it + * were a string (and of course, people now rely on the fact that it's + * a buffer)... Create the property as a string and then pass this + * method it's ID. The property will now be accessible/setable as a, + * but it will still be a string! + */ + STDMETHOD (SetStringAccessAsBufferById) (THIS_ + UINT32 ulId) PURE; +}; + + + +// $Private: +/* + * + * Interface: + * + * IHXRegistry2 + * + * Purpose: + * + * 1) Provide atomic update methods + * 2) Provide INT64 support + * 3) Provide access to INTREF pointers + * + * All operations occur atomically, ensuring that multiple users of the + * registry do not interfere with each other, even on multi-CPU systems. + * Note, this is essentially a superset of IHXRegistry. + * + * IID_IHXRegistry2 + * + * {00000608-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXRegistry2, 0x00000608, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXRegistry2 + +DECLARE_INTERFACE_(IHXRegistry2, IUnknown) +{ + + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXRegistry2 methods + */ + + /************************************************************************ + * Method: + * IHXRegistry2::CreatePropWatch + * Purpose: + * Create a new IHXPropWatch object which can then be queried for + * the right kind of IHXPropWatch object. + * + * pPropWatch - OUT - returns a new addref'ed IHXPropWatch object + */ + STDMETHOD(CreatePropWatch) (THIS_ + REF(IHXPropWatch*) pPropWatch) PURE; + + /************************************************************************ + * Method: + * IHXRegistry2::AddComp + * Purpose: + * Add a COMPOSITE property to the registry and return its ID + * if successful. It returns ZERO (0) if an error occurred + * during the operation. + * + * pName - IN - name of the Property that is going to be added to + * the registry + */ + STDMETHOD_(UINT32, AddComp) (THIS_ + const char* pName) PURE; + + /************************************************************************ + * Method: + * IHXRegistry2::AddInt + * Purpose: + * Add an INTEGER property with name in "pName" and value in + * "iValue" to the registry. The return value is the id to + * the newly added Property or ZERO if there was an error. + * + * pName - IN - name of the Property that is going to be added to + * the registry + * nValue - IN - integer value of the Property that is going to be + * added to the registry + */ + STDMETHOD_(UINT32, AddInt) (THIS_ + const char* pName, + const INT32 nValue) PURE; + + /************************************************************************ + * Method: + * IHXRegistry2::GetIntByName + * Purpose: + * Retreive an INTEGER value from the registry given its Property + * name "pName". If the Property is found, it will return HXR_OK, + * otherwise it returns HXR_FAIL. + * + * pName - IN - name of the Property whose value is to be retrieved + * nValue - OUT - parameter into which the value of the Property is + * going to be returned + */ + STDMETHOD(GetIntByName) (THIS_ + const char* pName, + REF(INT32) nValue) const PURE; + + /************************************************************************ + * Method: + * IHXRegistry2::GetIntById + * Purpose: + * Retreive an INTEGER value from the registry given its id "ulId". + * If the Property is found, it will return HXR_OK, otherwise it + * returns HXR_FAIL. + * + * ulId - IN - unique id of the Property whose value is to be retrieved + * nValue - OUT - parameter into which the value of the Property is + * going to be returned + */ + STDMETHOD(GetIntById) (THIS_ + const UINT32 ulId, + REF(INT32) nValue) const PURE; + + /************************************************************************ + * Method: + * IHXRegistry2::SetIntByName + * Purpose: + * Modify a Property's INTEGER value in the registry given the + * Property's name "pName". If the value was set, it will return HXR_OK, + * otherwise it returns HXR_FAIL. + * + * pName - IN - name of the Property whose value is to be set + * nValue - IN - the new value of the Property which is going to be set + */ + STDMETHOD(SetIntByName) (THIS_ + const char* pName, + const INT32 nValue) PURE; + + /************************************************************************ + * Method: + * IHXRegistry2::SetIntById + * Purpose: + * Modify a Property's INTEGER value in the registry given the + * its id "id". If the value was set, it will return HXR_OK, otherwise + * it returns HXR_FAIL. + * + * ulId - IN - unique id of the Property whose value is to be set + * nValue - IN - the new value of the Property which is going to be set + */ + STDMETHOD(SetIntById) (THIS_ + const UINT32 id, + const INT32 nValue) PURE; + + /************************************************************************ + * Method: + * IHXRegistry2::AddStr + * Purpose: + * Add an STRING property with name in "pName" and value in + * "pValue" to the registry and return its ID if successful. + * It returns ZERO (0) if an error occurred during the operation. + * + * pName - IN - name of the Property that is going to be added to + * the registry + * pValue - IN - buffer value of the Property that is going to be + * added to the registry + */ + STDMETHOD_(UINT32, AddStr) (THIS_ + const char* pName, + IHXBuffer* pValue) PURE; + + /************************************************************************ + * Method: + * IHXRegistry2::GetStrByName + * Purpose: + * Retreive an STRING value from the registry given its Property + * name "pName". If the Property is found, it will return HXR_OK, + * otherwise it returns HXR_FAIL. + * + * pName - IN - name of the Property whose value is to be retrieved + * pValue - OUT - parameter into which the value of the Property is + * going to be returned + */ + STDMETHOD(GetStrByName) (THIS_ + const char* pName, + REF(IHXBuffer*) pValue) const PURE; + + /************************************************************************ + * Method: + * IHXRegistry2::GetStrById + * Purpose: + * Retreive an STRING value from the registry given its id "ulId". + * If the Property is found, it will return HXR_OK, otherwise it + * returns HXR_FAIL. + * + * ulId - IN - unique id of the Property whose value is to be retrieved + * pValue - OUT - parameter into which the value of the Property is + * going to be returned + */ + STDMETHOD(GetStrById) (THIS_ + const UINT32 ulId, + REF(IHXBuffer*) pValue) const PURE; + + /************************************************************************ + * Method: + * IHXRegistry2::SetStrByName + * Purpose: + * Modify a Property's STRING value in the registry given the + * Property's name "pName". If the value was set, it will return + * HXR_OK, otherwise it returns HXR_FAIL. + * + * pName - IN - name of the Property whose value is to be set + * pValue - IN - the new value of the Property which is going to be set + */ + STDMETHOD(SetStrByName) (THIS_ + const char* pName, + IHXBuffer* pValue) PURE; + + /************************************************************************ + * Method: + * IHXRegistry2::SetStrById + * Purpose: + * Modify a Property's STRING value in the registry given the + * its id "ulId". If the value was set, it will return HXR_OK, + * otherwise it returns HXR_FAIL. + * + * ulId - IN - unique id of the Property whose value is to be set + * pValue - IN - the new value of the Property which is going to be set + */ + STDMETHOD(SetStrById) (THIS_ + const UINT32 ulId, + IHXBuffer* pValue) PURE; + + /************************************************************************ + * Method: + * IHXRegistry2::AddBuf + * Purpose: + * Add an BUFFER property with name in "pName" and value in + * "pValue" to the registry and return its ID if successful. + * It returns ZERO (0) if an error occurred during the operation. + * + * pName - IN - name of the Property that is going to be added to + * the registry + * pValue - IN - buffer value of the Property that is going to be + * added to the registry + */ + STDMETHOD_(UINT32, AddBuf) (THIS_ + const char* pName, + IHXBuffer* pValue) PURE; + + /************************************************************************ + * Method: + * IHXRegistry2::GetBufByName + * Purpose: + * Retreive the BUFFER from the registry given its Property name + * "pName". If the Property is found, it will return HXR_OK, otherwise + * it returns HXR_FAIL. + * + * pName - IN - name of the Property whose value is to be retrieved + * pValue - OUT - parameter into which the value of the Property is + * going to be returned + */ + STDMETHOD(GetBufByName) (THIS_ + const char* pName, + REF(IHXBuffer*) pValue) const PURE; + + /************************************************************************ + * Method: + * IHXRegistry2::GetBufById + * Purpose: + * Retreive the BUFFER from the registry given its id "ulId". If the + * Property is found, it will return HXR_OK, otherwise it returns + * HXR_FAIL. + * + * ulId - IN - unique id of the Property whose value is to be retrieved + * pValue - OUT - parameter into which the value of the Property is + * going to be returned + */ + STDMETHOD(GetBufById) (THIS_ + const UINT32 ulId, + REF(IHXBuffer*) pValue) const PURE; + + /************************************************************************ + * Method: + * IHXRegistry2::SetBufByName + * Purpose: + * Modify a Property's BUFFER in the registry given the + * Property's name "pName". If the value was set, it will return + * HXR_OK, otherwise it returns HXR_FAIL. + * + * pName - IN - name of the Property whose value is to be set + * pValue - IN - the new value of the Property which is going to be set + */ + STDMETHOD(SetBufByName) (THIS_ + const char* pName, + IHXBuffer* pValue) PURE; + + /************************************************************************ + * Method: + * IHXRegistry2::SetBufById + * Purpose: + * Modify a Property's BUFFER in the registry given its id "ulId". + * If the value was set, it will return HXR_OK, otherwise it returns + * HXR_FAIL. + * + * ulId - IN - unique id of the Property whose value is to be set + * pValue - IN - the new value of the Property which is going to be set + */ + STDMETHOD(SetBufById) (THIS_ + const UINT32 ulId, + IHXBuffer* pValue) PURE; + + /************************************************************************ + * Method: + * IHXRegistry2::AddIntRef + * Purpose: + * Add an INTEGER REFERENCE property with name in "pName" and + * value in "iValue" to the registry. This property allows the user + * to modify its contents directly, without having to go through the + * registry. The Property's id is returned if successful. + * It returns ZERO (0) if an error occurred during the operation. + * + * pName - IN - name of the Property that is going to be added to + * the registry + * pValue - IN - the pointer of the integer value is what gets stored + * in the registry as the Interger Reference Property's + * value + */ + STDMETHOD_(UINT32, AddIntRef) (THIS_ + const char* pName, + INT32* pValue) PURE; + + /************************************************************************ + * Method: + * IHXRegistry2::DeleteByName + * Purpose: + * Delete a Property from the registry using its name "pName". + * + * pName - IN - name of the Property that is going to be deleted + */ + STDMETHOD_(UINT32, DeleteByName) (THIS_ + const char* pName) PURE; + + /************************************************************************ + * Method: + * IHXRegistry2::DeleteById + * Purpose: + * Delete a Property from the registry using its id "ulId". + * + * ulId - IN - unique id of the Property that is going to be deleted + */ + STDMETHOD_(UINT32, DeleteById) (THIS_ + const UINT32 ulId) PURE; + + /************************************************************************ + * Method: + * IHXRegistry2::GetTypeByName + * Purpose: + * Returns the datatype of the Property given its name "pName". + * + * pName - IN - name of the Property whose type is to be retrieved + */ + STDMETHOD_(HXPropType, GetTypeByName) (THIS_ + const char* pName) const PURE; + + /************************************************************************ + * Method: + * IHXRegistry2::GetTypeById + * Purpose: + * Returns the datatype of the Property given its its id "ulId". + * + * ulId - IN - unique id of the Property whose type is to be retrieved + */ + STDMETHOD_(HXPropType, GetTypeById) (THIS_ + const UINT32 ulId) const PURE; + + /************************************************************************ + * Method: + * IHXRegistry2::FindParentIdByName + * Purpose: + * Returns the id value of the parent node of the Property whose + * name "pName" has been passed in. If it fails, a ZERO value is + * returned. + * + * pName - IN - name of the Property whose parent's unique id is to be + * retrieved + */ + STDMETHOD_(UINT32, FindParentIdByName) (THIS_ + const char* pName) const PURE; + + /************************************************************************ + * Method: + * IHXRegistry2::FindParentIdById + * Purpose: + * Returns the id value of the parent node of the Property whose + * id "ulId" has been passed in. If it fails, a ZERO value is returned. + * + * ulId - IN - unique id of the Property whose parent's id is to be + * retrieved + */ + STDMETHOD_(UINT32, FindParentIdById) (THIS_ + const UINT32 ulId) const PURE; + + /************************************************************************ + * Method: + * IHXRegistry2::GetPropName + * Purpose: + * Returns the Property name in the pName char buffer passed + * as a parameter, given the Property's id "ulId". + * + * ulId - IN - unique id of the Property whose name is to be retrieved + * pName - OUT - parameter into which the Property name is going to be + * returned + */ + STDMETHOD(GetPropName) (THIS_ + const UINT32 ulId, + REF(IHXBuffer*) pName) const PURE; + + /************************************************************************ + * Method: + * IHXRegistry2::GetId + * Purpose: + * Returns the Property's id given the Property name. + * + * pName - IN - name of the Property whose unique id is to be + * retrieved + */ + STDMETHOD_(UINT32, GetId) (THIS_ + const char* pName) const PURE; + + /************************************************************************ + * Method: + * IHXRegistry2::GetPropListOfRoot + * Purpose: + * Returns an array of a Properties under the root level of the + * registry's hierarchy. + * + * pValues - OUT - list of property name and unique id at the + * highest level (root) in the registry + */ + STDMETHOD(GetPropListOfRoot) (THIS_ + REF(IHXValues*) pValues) const PURE; + + /************************************************************************ + * Method: + * IHXRegistry2::GetPropListByName + * Purpose: + * Returns an array of Properties immediately under the one whose + * name is passed in "pName". + * + * pName - IN - name of the Property whose child property list is to be + * retrieved + * pValues - OUT - list of property name and unique id under the + * Property whose name is in "pName" + */ + STDMETHOD(GetPropListByName) (THIS_ + const char* pName, + REF(IHXValues*) pValues) const PURE; + + /************************************************************************ + * Method: + * IHXRegistry2::GetPropListById + * Purpose: + * Returns an array of Properties immediately under the one whose + * id is passed in "ulId". + * + * ulId - IN - unique id of the Property whose child property list is + * to be retrieved + * pValues - OUT - list of property name and unique id under the + * Property whose is is in "ulId" + */ + STDMETHOD(GetPropListById) (THIS_ + const UINT32 ulId, + REF(IHXValues*) pValues) const PURE; + + /************************************************************************ + * Method: + * IHXRegistry2::GetNumPropsAtRoot + * Purpose: + * Returns the number of Properties at the root of the registry. + * + */ + STDMETHOD_(INT32, GetNumPropsAtRoot) (THIS) const PURE; + + /************************************************************************ + * Method: + * IHXRegistry2::GetNumPropsByName + * Purpose: + * Returns the count of the number of Properties under the one + * whose name is specified in "pName". + * + * pName - IN - name of the Property whose number of children is to be + * retrieved + */ + STDMETHOD_(INT32, GetNumPropsByName) (THIS_ + const char* pName) const PURE; + + /************************************************************************ + * Method: + * IHXRegistry2::GetNumPropsById + * Purpose: + * Returns the count of the number of Properties under the one + * whose unique id is specified in "ulId". + * + * ulId - IN - unique id of the Property whose number of children is + * to be retrieved + */ + STDMETHOD_(INT32, GetNumPropsById) (THIS_ + const UINT32 ulId) const PURE; + + + /************************************************************************ + * Method: + * IHXRegistry2::ModifyIntByName + * Purpose: + * Changes the INTEGER value in the registry given its Property + * name "pName" and an amount to change it by. Modifies the value + * of the integer in the registry by the amount specified by "nDelta", + * setting nValue equal to the value after modification. If the + * Property is found, it will return HXR_OK, otherwise it + * returns HXR_FAIL. + * + * pName - IN - name of the Property whose value is to be retrieved + * nDelta - IN - amount to modify the named property by + * nValue - OUT - parameter into which the value of the Property is + * going to be returned, after modification + */ + STDMETHOD(ModifyIntByName) (THIS_ + const char* pName, + INT32 nDelta, + REF(INT32) nValue) PURE; + + /************************************************************************ + * Method: + * IHXRegistry2::ModifyIntById + * Purpose: + * Changes the INTEGER value in the registry given its id "ulID" + * and an amount to change it by. Modifies the value of the + * integer in the registry by the amount specified by "nDelta", + * setting nValue equal to the value after modification. If the + * Property is found, it will return HXR_OK, otherwise it + * returns HXR_FAIL. + * + * ulId - IN - unique id of the Property whose value is to be modified + * nDelta - IN - amount to modify the specified property by + * nValue - OUT - parameter into which the value of the Property is + * going to be returned, after modification + */ + STDMETHOD(ModifyIntById) (THIS_ + const UINT32 id, + INT32 nDelta, + REF(INT32) nValue) PURE; + + /************************************************************************ + * Method: + * IHXRegistry2::BoundedModifyIntByName + * Purpose: + * Changes the INTEGER value in the registry given its Property name + * "pName" and an amount to change it by and keeps the modified value + * within the bounds of the nMin and nMax values. Modifies the value + * of the integer in the registry by the amount specified by "nDelta", + * setting nValue equal to the value after modification, if the modified + * value is >= nMin and <= nMax. If either of these limits are violated + * the the resulting value stored in the registry is the value of the + * limit just violated. If the Property is found, it will return HXR_OK, + * otherwise it returns HXR_FAIL. + * + * pName - IN - name of the Property whose value is to be retrieved + * nDelta - IN - amount to modify the named property by + * nMin - IN - min value that the modified registry prop can have + * if the modified registry value < nMin then + * registry value = nMin + * nMax - IN - min value that the modified registry prop can have + * if the modified registry value > nMax then + * registry value = nMax + * nValue - OUT - parameter into which the value of the Property is + * going to be returned, after modification + */ + STDMETHOD(BoundedModifyIntByName) (THIS_ + const char* pName, + INT32 nDelta, + REF(INT32) nValue, + INT32 nMin=INT_MIN, + INT32 nMax=INT_MAX) PURE; + + /************************************************************************ + * Method: + * IHXRegistry2::BoundedModifyIntById + * Purpose: + * Changes the INTEGER value in the registry given its id "ulID" + * and an amount to change it by and keeps the modified value within + * the bounds of the nMin and nMax values. Modifies the value of the + * integer in the registry by the amount specified by "nDelta", + * setting nValue equal to the value after modification, if the modified + * value is >= nMin and <= nMax. If either of these limits are violated + * the the resulting value stored in the registry is the value of the + * limit just violated. If the Property is found, it will return HXR_OK, + * otherwise it returns HXR_FAIL. + * + * ulId - IN - unique id of the Property whose value is to be modified + * nDelta - IN - amount to modify the specified property by + * nMin - IN - min value that the modified registry prop can have + * if the modified registry value < nMin then + * registry value = nMin + * nMax - IN - min value that the modified registry prop can have + * if the modified registry value > nMax then + * registry value = nMax + * nValue - OUT - parameter into which the value of the Property is + * going to be returned, after modification + */ + STDMETHOD(BoundedModifyIntById) (THIS_ + const UINT32 id, + INT32 nDelta, + REF(INT32) nValue, + INT32 nMin=INT_MIN, + INT32 nMax=INT_MAX) PURE; + + /************************************************************************ + * Method: + * IHXRegistry2::SetAndReturnIntByName + * Purpose: + * Modify a Property's INTEGER value in the registry given the + * Property's name "pName". If the Property is found, the previous + * value, prior to setting it, will be assigned to nOldValue. + * If the Property is found, it will return HXR_OK, otherwise it + * returns HXR_FAIL. + * + * pName - IN - name of the Property whose value is to be retrieved + * nValue - IN - the new value of the Property which is going to be set + * nOldValue - OUT - parameter into which the previous value of the + * Property is returned + */ + STDMETHOD(SetAndReturnIntByName) (THIS_ + const char* pName, + INT32 nValue, + REF(INT32) nOldValue) PURE; + + /************************************************************************ + * Method: + * IHXRegistry2::SetAndReturnIntById + * Purpose: + * Modify a Property's INTEGER value in the registry given the + * Property's id "ulId". If the id is found, the previous + * value, prior to setting it, will be assigned to nOldValue. + * If the Property is found, it will return HXR_OK, otherwise it + * returns HXR_FAIL. + * + * pName - IN - name of the Property whose value is to be retrieved + * nValue - IN - the new value of the Property which is going to be set + * nOldValue - OUT - parameter into which the previous value of the + * Property is returned + */ + STDMETHOD(SetAndReturnIntById) (THIS_ + const UINT32 id, + INT32 nValue, + REF(INT32) nOldValue) PURE; + + /************************************************************************ + * Method: + * IHXRegistry2::GetIntRefByName + * Purpose: + * Retrieve an INTEGER REFERENCE property from the registry given + * its Property name "pName". If the Property is found it will return + * HXR_OK and pValue will be assigned the address of the integer + * (not the value of the integer, which can be obtained even for + * INTREFs via the GetIntByxxx methods.) Otherwise, it returns HXR_FAIL. + * + * pName - IN - name of the Property whose value is to be retrieved + * pValue - OUT - the address of the integer value + */ + STDMETHOD(GetIntRefByName) (THIS_ + const char* pName, + REF(INT32*) pValue) const PURE; + + /************************************************************************ + * Method: + * IHXRegistry2::GetIntRefById + * Purpose: + * Retrieve an INTEGER REFERENCE property from the registry given + * its id "ulId". If the Property is found it will return + * HXR_OK and pValue will be assigned the address of the integer + * (not the value of the integer, which can be obtained even for + * INTREFs via the GetIntByxxx methods.) Otherwise, it returns HXR_FAIL. + * + * pName - IN - name of the Property whose value is to be retrieved + * pValue - OUT - the address of the integer value + */ + STDMETHOD(GetIntRefById) (THIS_ + const UINT32 id, + REF(INT32*) pValue) const PURE; + + + + /************************************************************************ + * Method: + * IHXRegistry2::AddInt64 + * Purpose: + * Add an INTEGER property with name in "pName" and value in + * "iValue" to the registry. The return value is the id to + * the newly added Property or ZERO if there was an error. + * + * pName - IN - name of the Property that is going to be added to + * the registry + * nValue - IN - integer value of the Property that is going to be + * added to the registry + */ + STDMETHOD_(UINT32, AddInt64) (THIS_ + const char* pName, + const INT64 nValue) PURE; + + /************************************************************************ + * Method: + * IHXRegistry2::GetInt64ByName + * Purpose: + * Retrieve a 64-bit INTEGER value from the registry given its + * Property name "pName". If the Property is found, it will return + * HXR_OK, otherwise it returns HXR_FAIL. + * + * pName - IN - name of the Property whose value is to be retrieved + * nValue - OUT - parameter into which the value of the Property is + * going to be returned + */ + STDMETHOD(GetInt64ByName) (THIS_ + const char* pName, + REF(INT64) nValue) const PURE; + + /************************************************************************ + * Method: + * IHXRegistry2::GetInt64ById + * Purpose: + * Retrieve a 64-bit INTEGER value from the registry given its id + * "ulId". If the Property is found, it will return HXR_OK, otherwise + * it returns HXR_FAIL. + * + * ulId - IN - unique id of the Property whose value is to be retrieved + * nValue - OUT - parameter into which the value of the Property is + * going to be returned + */ + STDMETHOD(GetInt64ById) (THIS_ + const UINT32 ulId, + REF(INT64) nValue) const PURE; + + /************************************************************************ + * Method: + * IHXRegistry2::SetInt64ByName + * Purpose: + * Modify a Property's INTEGER value in the registry given the + * Property's name "pName". If the value was set, it will return HXR_OK, + * otherwise it returns HXR_FAIL. + * + * pName - IN - name of the Property whose value is to be set + * nValue - IN - the new value of the Property which is going to be set + */ + STDMETHOD(SetInt64ByName) (THIS_ + const char* pName, + const INT64 nValue) PURE; + + /************************************************************************ + * Method: + * IHXRegistry2::SetInt64ById + * Purpose: + * Modify a Property's 64-bit INTEGER value in the registry given the + * its id "id". If the value was set, it will return HXR_OK, otherwise + * it returns HXR_FAIL. + * + * ulId - IN - unique id of the Property whose value is to be set + * nValue - IN - the new value of the Property which is going to be set + */ + STDMETHOD(SetInt64ById) (THIS_ + const UINT32 id, + const INT64 nValue) PURE; + + /************************************************************************ + * Method: + * IHXRegistry2::ModifyInt64ByName + * Purpose: + * Changes the 64-bit INTEGER value in the registry given its Property + * name "pName" and an amount to change it by. Modifies the value + * of the integer in the registry by the amount specified by "nDelta", + * setting nValue equal to the value after modification. If the + * Property is found, it will return HXR_OK, otherwise it + * returns HXR_FAIL. + * + * pName - IN - name of the Property whose value is to be retrieved + * nDelta - IN - amount to modify the named property by + * nValue - OUT - parameter into which the value of the Property is + * going to be returned, after modification + */ + STDMETHOD(ModifyInt64ByName) (THIS_ + const char* pName, + INT64 nDelta, + REF(INT64) nValue) PURE; + + /************************************************************************ + * Method: + * IHXRegistry2::ModifyInt64ById + * Purpose: + * Changes the 64-bit INTEGER value in the registry given its id + * "ulID and an amount to change it by. Modifies the value of the + * integer in the registry by the amount specified by "nDelta", + * setting nValue equal to the value after modification. If the + * Property is found, it will return HXR_OK, otherwise it + * returns HXR_FAIL. + * + * ulId - IN - unique id of the Property whose value is to be modified + * nDelta - IN - amount to modify the specified property by + * nValue - OUT - parameter into which the value of the Property is + * going to be returned, after modification + */ + STDMETHOD(ModifyInt64ById) (THIS_ + const UINT32 id, + INT64 nDelta, + REF(INT64) nValue) PURE; + + /************************************************************************ + * Method: + * IHXRegistry2::BoundedModifyInt64ByName + * Purpose: + * Changes the 64-bit INT val in the registry given its Property name + * "pName" and an amount to change it by and keeps the modified value + * within the bounds of the nMin and nMax values. Modifies the value + * of the integer in the registry by the amount specified by "nDelta", + * setting nValue equal to the value after modification, if the modified + * value is >= nMin and <= nMax. If either of these limits are violated + * the the resulting value stored in the registry is the value of the + * limit just violated. If the Property is found, it will return HXR_OK, + * otherwise it returns HXR_FAIL. + * + * pName - IN - name of the Property whose value is to be retrieved + * nDelta - IN - amount to modify the named property by + * nMin - IN - min value that the modified registry prop can have + * if the modified registry value < nMin then + * registry value = nMin + * nMax - IN - min value that the modified registry prop can have + * if the modified registry value > nMax then + * registry value = nMax + * nValue - OUT - parameter into which the value of the Property is + * going to be returned, after modification + * + * NOTE: + * the default values should b changed from INT_MIN/MAX to their + * appropriate 64-bit values + */ + STDMETHOD(BoundedModifyInt64ByName) (THIS_ + const char* pName, + INT64 nDelta, + REF(INT64) nValue, + INT64 nMin=INT64(INT_MIN), + INT64 nMax=INT64(INT_MAX)) PURE; + + /************************************************************************ + * Method: + * IHXRegistry2::BoundedModifyInt64ById + * Purpose: + * Changes the 64-bit INT val in the registry given its id "ulID" + * and an amount to change it by and keeps the modified value within + * the bounds of the nMin and nMax values. Modifies the value of the + * integer in the registry by the amount specified by "nDelta", + * setting nValue equal to the value after modification, if the modified + * value is >= nMin and <= nMax. If either of these limits are violated + * the the resulting value stored in the registry is the value of the + * limit just violated. If the Property is found, it will return HXR_OK, + * otherwise it returns HXR_FAIL. + * + * ulId - IN - unique id of the Property whose value is to be modified + * nDelta - IN - amount to modify the specified property by + * nMin - IN - min value that the modified registry prop can have + * if the modified registry value < nMin then + * registry value = nMin + * nMax - IN - min value that the modified registry prop can have + * if the modified registry value > nMax then + * registry value = nMax + * nValue - OUT - parameter into which the value of the Property is + * going to be returned, after modification + * + * NOTE: + * the default values should b changed from INT_MIN/MAX to their + * appropriate 64-bit values + */ + STDMETHOD(BoundedModifyInt64ById) (THIS_ + const UINT32 id, + INT64 nDelta, + REF(INT64) nValue, + INT64 nMin=INT64(INT_MIN), + INT64 nMax=INT64(INT_MAX)) PURE; + + /************************************************************************ + * Method: + * IHXRegistry2::SetAndReturnInt64ByName + * Purpose: + * Modify a Property's 64-bit INTEGER value in the registry given + * the Property's name "pName". If the Property is found, the previous + * value, prior to setting it, will be assigned to nOldValue. + * If the Property is found, it will return HXR_OK, otherwise it + * returns HXR_FAIL. + * + * pName - IN - name of the Property whose value is to be retrieved + * nValue - IN - the new value of the Property which is going to be set + * nOldValue - OUT - parameter into which the previous value of the + * Property is returned + */ + STDMETHOD(SetAndReturnInt64ByName) (THIS_ + const char* pName, + INT64 nValue, + REF(INT64) nOldValue) PURE; + + /************************************************************************ + * Method: + * IHXRegistry2::SetAndReturnInt64ById + * Purpose: + * Modify a Property's 64-bit INTEGER value in the registry given + * the Property's id "ulId". If the id is found, the previous + * value, prior to setting it, will be assigned to nOldValue. + * If the Property is found, it will return HXR_OK, otherwise it + * returns HXR_FAIL. + * + * pName - IN - name of the Property whose value is to be retrieved + * nValue - IN - the new value of the Property which is going to be set + * nOldValue - OUT - parameter into which the previous value of the + * Property is returned + */ + STDMETHOD(SetAndReturnInt64ById) (THIS_ + const UINT32 id, + INT64 nValue, + REF(INT64) nOldValue) PURE; + + + /************************************************************************ + * Method: + * IHXRegistry2::AddInt64Ref + * Purpose: + * Add a 64-bit INTEGER REFERENCE property with name in "pName" + * and value in "iValue" to the registry. This property allows the user + * to modify its contents directly, without having to go through the + * registry. + * + * pName - IN - name of the Property that is going to be added to + * the registry + * pValue - IN - the pointer of the integer value is what gets stored + * in the registry as the Integer Reference Property's + * value + */ + STDMETHOD_(UINT32, AddInt64Ref) (THIS_ + const char* pName, + INT64* pValue) PURE; + + /************************************************************************ + * Method: + * IHXRegistry2::GetInt64RefByName + * Purpose: + * Retrieve a 64-bit INTEGER REFERENCE property from the registry + * given its Property name "pName". If the Property is found it will + * return HXR_OK and pValue will be assigned the address of the integer + * (not the value of the integer, which can be obtained even for + * INTREFs via the GetIntByxxx methods.) Otherwise, it returns HXR_FAIL. + * + * pName - IN - name of the Property whose value is to be retrieved + * pValue - OUT - the address of the integer value + */ + STDMETHOD(GetInt64RefByName) (THIS_ + const char* pName, + REF(INT64*) pValue) const PURE; + + + /************************************************************************ + * Method: + * IHXRegistry2::GetInt64RefById + * Purpose: + * Retrieve a 64-bit INTEGER REFERENCE property from the registry + * given its id "ulId". If the Property is found it will return + * HXR_OK and pValue will be assigned the address of the integer + * (not the value of the integer, which can be obtained even for + * INTREFs via the GetIntByxxx methods.) Otherwise, it returns HXR_FAIL. + * + * pName - IN - name of the Property whose value is to be retrieved + * pValue - OUT - the address of the integer value + */ + STDMETHOD(GetInt64RefById) (THIS_ + const UINT32 id, + REF(INT64*) pValue) const PURE; + + + /************************************************************************ + * Method: + * IHXRegistry2::GetChildIdListByName + * Purpose: + * Get a array which enumerates all of the children under a + * property by id. + * + * pName - IN - name of the Property whose children are to be enumerated. + * pValues - OUT - array of unique Property id's. + * ulCount - OUT - size of the returned pValues array. + * + * Note: The array must be deleted by the user. + */ + STDMETHOD(GetChildIdListByName) (THIS_ + const char* pName, + REF(UINT32*) pValues, + REF(UINT32) ulCount) const PURE; + + + /************************************************************************ + * Method: + * IHXRegistry2::GetChildIdListById + * Purpose: + * Get a array which enumerates all of the children under a + * property by id. + * + * ulId - IN - unique id of the Property whose children are to be enumerated. + * pValues - OUT - array of unique Property id's. + * ulCount - OUT - size of the returned pValues array. + * + * Note: The pValues array must be deleted by the user. + */ + STDMETHOD(GetChildIdListById) (THIS_ + const UINT32 ulId, + REF(UINT32*) pValues, + REF(UINT32) ulCount) const PURE; + + + /************************************************************************ + * Method: + * IHXRegistry2::GetPropStatusById + * Purpose: + * Gets status of a property by id. + * + * ulId - IN - id of property to get child ids for. + * + * Returns: + * HXR_OK if property exists. + * HXR_PROP_DELETE_PENDING if property exists, but is delete-pending. + * HXR_FAIL if property does not exist. + */ + + STDMETHOD(GetPropStatusById) (THIS_ + const UINT32 ulId) const PURE; + + + /************************************************************************ + * Method: + * IHXRegistry2::GetPropStatusByName + * Purpose: + * Gets status of a property by name. + * + * szPropName - IN - name of property to get child ids for. + * + * Returns: + * HXR_OK if property exists. + * HXR_PROP_DELETE_PENDING if property exists, but is delete-pending. + * HXR_FAIL if property does not exist. + */ + + STDMETHOD(GetPropStatusByName) (THIS_ + const char* pName) const PURE; + +}; +// $EndPrivate. + + + +// $Private: +/* + * + * Interface: + * + * IHXDeletedPropResponse + * + * Purpose: + * + * Provides an alternative way to be notified about the deletion + * of objects from the registry. + * + * IID_IHXDeletePropResponse + * + * {00000609-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXDeletedPropResponse, 0x00000609, 0x901, 0x11d1, + 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXDeletedPropResponse + +DECLARE_INTERFACE_(IHXDeletedPropResponse, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXDeletedPropResponse methods + */ + + /************************************************************************ + * Method: + * IHXDeletedPropResponse::DeletedComposite + * Purpose: + * Provide notification that a COMPOSITE Property has been deleted + * from the registry. + * + * ulId - IN - unique id of the Property which has been deleted + * ulParentID - IN - unique id of the parent Property of the deleted item + * bIsParentNotify - IN - TRUE if this is a parent notification. + * pName - IN - name of the deleted Property (null-terminated) + */ + STDMETHOD(DeletedComposite)(THIS_ + const UINT32 ulId, + const UINT32 ulParentID, + const HXBOOL bIsParentNotify, + IHXBuffer* pName) PURE; + + /************************************************************************ + * Method: + * IHXDeletedPropResponse::DeletedInt + * Purpose: + * Provide notification that a INTEGER Property has been deleted. + * + * ulId - IN - unique id of the Property which has been deleted + * ulParentID - IN - unique id of the parent Property of the deleted item + * bIsParentNotify - IN - TRUE if this is a parent notification. + * pName - IN - name of the Property being deleted (null-terminated) + * nValue - IN - integer value of the Property which has been deleted + */ + STDMETHOD(DeletedInt) (THIS_ + const UINT32 ulId, + const UINT32 ulParentID, + const HXBOOL bIsParentNotify, + IHXBuffer* pName, + const INT32 nValue) PURE; + + /************************************************************************ + * Method: + * IHXDeletedPropResponse::DeletedIntRef + * Purpose: + * Provide notification that an INTEGER reference Property has + * been deleted from the registry. + * + * ulId - IN - unique id of the Property which has been deleted + * ulParentID - IN - unique id of the parent Property of the deleted item + * bIsParentNotify - IN - TRUE if this is a parent notification. + * pName - IN - name of the deleted Property (null-terminated) + * nValue - IN - integer value of the Property which has been deleted + * pValue - IN - the pointer of the integer reference Property + * which has been deleted + */ + STDMETHOD(DeletedIntRef) (THIS_ + const UINT32 ulId, + const UINT32 ulParentID, + const HXBOOL bIsParentNotify, + IHXBuffer* pName, + const INT32 nValue, + const INT32* pValue) PURE; + + /************************************************************************ + * Method: + * IHXDeletedPropResponse::DeletedString + * Purpose: + * Provide notification that a String Property has been deleted + * from the registry. + * + * ulId - IN - unique id of the deleted Property which has been deleted + * ulParentID - IN - unique id of the parent Property of the deleted item + * bIsParentNotify - IN - TRUE if this is a parent notification. + * pName - IN - name of the deleted Property (null-terminated) + * pValue - IN - value of the deleted Property + */ + STDMETHOD(DeletedString) (THIS_ + const UINT32 ulId, + const UINT32 ulParentID, + const HXBOOL bIsParentNotify, + IHXBuffer* pName, + IHXBuffer* pValue) PURE; + + /************************************************************************ + * Method: + * IHXDeletedPropResponse::DeletedBuffer + * Purpose: + * Provide notification that a Buffer Property has been deleted + * from the registry. + * + * ulId - IN - unique id of the Property which has been deleted + * ulParentID - IN - unique id of the parent Property of the deleted item + * bIsParentNotify - IN - TRUE if this is a parent notification. + * pName - IN - name of the deleted Property (null-terminated) + * pValue - IN - buffer value of the deleted Property + */ + STDMETHOD(DeletedBuffer) (THIS_ + const UINT32 ulId, + const UINT32 ulParentID, + const HXBOOL bIsParentNotify, + IHXBuffer* pName, + IHXBuffer* pValue) PURE; + + /************************************************************************ + * Method: + * IHXDeletedPropResponse::DeletedInt64 + * Purpose: + * Provide notification that a 64-bit integer Property has been + * deleted from the registry. + * + * Note: This is not used for IHXRegistry, but for IHXRegistry2. + * + * ulId - IN - unique id of the Property which has been deleted + * ulParentID - IN - unique id of the parent Property of the deleted item + * bIsParentNotify - IN - TRUE if this is a parent notification. + * pName - IN - name of the deleted Property (null-terminated) + * nValue - IN - 64-bit integer value of the deleted Property + */ + STDMETHOD(DeletedInt64) (THIS_ + const UINT32 ulId, + const UINT32 ulParentID, + const HXBOOL bIsParentNotify, + IHXBuffer* pName, + const INT64 nValue) PURE; + + /************************************************************************ + * Method: + * IHXDeletedPropResponse::DeletedInt64Ref + * Purpose: + * Provide notification that a 64-bit integer reference Property + * has been deleted from the registry. + * + * Note: This is not used for IHXRegistry, but for IHXRegistry2. + * + * ulId - IN - unique id of the Property which has been deleted + * ulParentID - IN - unique id of the parent Property of the deleted item + * bIsParentNotify - IN - TRUE if this is a parent notification. + * pName - IN - name of the deleted Property (null-terminated) + * nValue - IN - 64-bit integer value of the deleted Property + * pValue - IN - the pointer of the 64-bit integer reference Property + * which has been deleted + */ + STDMETHOD(DeletedInt64Ref) (THIS_ + const UINT32 ulId, + const UINT32 ulParentID, + const HXBOOL bIsParentNotify, + IHXBuffer* pName, + const INT64 nValue, + const INT64* pValue) PURE; + +}; +// $EndPrivate. + +#endif /* _HXMON_H_ */ diff --git a/amarok/src/engine/helix/helix-sp/helix-include/common/include/hxpiids.h b/amarok/src/engine/helix/helix-sp/helix-include/common/include/hxpiids.h new file mode 100644 index 00000000..b3921e04 --- /dev/null +++ b/amarok/src/engine/helix/helix-sp/helix-include/common/include/hxpiids.h @@ -0,0 +1,1055 @@ + +/* + * + * This software is released under the provisions of the GPL version 2. + * see file "COPYING". If that file is not available, the full statement + * of the license can be found at + * + * http://www.fsf.org/licensing/licenses/gpl.txt + * + * Portions Copyright (c) 1995-2004 RealNetworks, Inc. All Rights Reserved. + * + */ + +#ifndef _HXPRIVATEIIDS_H_ +#define _HXPRIVATEIIDS_H_ + +/* + * File: + * hxcorgui.h + * Description: + * Interfaces used by gui to get info from core + * Interfaces: + * IID_IHXCoreGuiHook: {00000000-b4c8-11d0-9995-00a0248da5f0} + + */ +DEFINE_GUID_ENUM(IID_IHXCoreGuiHook, 0x00000000, 0xb4c8, 0x11d0, 0x99, 0x95, +0x0, 0xa0, 0x24, 0x8d, 0xa5, 0xf0) +DEFINE_GUID_ENUM(IID_IHXInternalReset, 0x00000001, 0xb4c8, 0x11d0, 0x99, 0x95, +0x0, 0xa0, 0x24, 0x8d, 0xa5, 0xf0) + +/* + * File: + * hxbdwdth.h + * Description: + * Interface used by raffplin and rmffplin to support 3.0/4.0 style + * Bandwidth negotiation. + * Interfaces: + * IID_IHXBandwidthNegotiator: {00000100-b4c8-11d0-9995-00a0248da5f0} + */ + +DEFINE_GUID_ENUM(IID_IHXBandwidthNegotiator, 0x00000100, 0xb4c8, 0x11d0, 0x99, 0x95, 0x0, 0xa0, 0x24, 0x8d, 0xa5, 0xf0) +DEFINE_GUID_ENUM(IID_IHXBandwidthLister, 0x00000101, 0xb4c8, 0x11d0, 0x99, 0x95, 0x0, 0xa0, 0x24, 0x8d, 0xa5, 0xf0) + +/* + * File: + * hxphand.h + * + * Description: + * Interface for PluginHandler - non-IHX, just gimme the pointer + * Bandwidth negotiation. + * Interfaces: + * IID_IHXPluginHandler: {00000200-b4c8-11d0-9995-00a0248da5f0} + * IID_IHXPlugin2Handler:{00000201-b4c8-11d0-9995-00a0248da5f0} + */ + + +#ifndef _HXPLUGN_H_ +DEFINE_GUID_ENUM(IID_IHXPluginHandler, 0x00000200, 0xb4c8, 0x11d0, 0x99, 0x95, 0x0, 0xa0, 0x24, 0x8d, 0xa5, 0xf0) +DEFINE_GUID_ENUM(IID_IHXPlugin2Handler, 0x00000201, 0xb4c8, 0x11d0, 0x99, 0x95, 0x0, 0xa0, 0x24, 0x8d, 0xa5, 0xf0) +#endif +#ifndef _HXPHAND_H_ +DEFINE_GUID_ENUM(IID_IHXPlugin2HandlerEnumeratorInterface, 0x00000202, 0xb4c8, 0x11d0, 0x99, 0x95, 0x0, 0xa0, 0x24, 0x8d, 0xa5, 0xf0) +#endif + +/* + * File: + * hxrendr.h + * Description: + * Interfaces related to renderers. + * Interfaces: + * IID_IHXMediaPushdown {00000302-0901-11d1-8B06-00A024406D59} + */ + +DEFINE_GUID_ENUM(IID_IHXMediaPushdown, 0x00000302, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) + +/* + * File: + * rtspif.h + * Description: + * Interface for resend handling. + * Interfaces: + * IID_IHXPacketResend: {00000400-b4c8-11d0-9995-00a0248da5f0} + */ + +DEFINE_GUID_ENUM(IID_IHXPacketResend, 0x00000400, 0xb4c8, 0x11d0, 0x99, 0x95, 0x0, 0xa0, 0x24, 0x8d, 0xa5, 0xf0) + +/* + * File: + * rtspif.h + * Description: + * Interface for Context. + * Interfaces: + * IID_IHXRTSPContext: {00000401-b4c8-11d0-9995-00a0248da5f0} + */ +DEFINE_GUID_ENUM(IID_IHXRTSPContext, 0x00000401, 0xb4c8, 0x11d0, 0x99, 0x95, 0x0, 0xa0, 0x24, 0x8d, 0xa5, 0xf0) + +/* + * File: + * rtspif.h + * Description: + * Interface for communicating timestamp. + * Interfaces: + * IID_IHXTimeStampSync: {00000402-b4c8-11d0-9995-00a0248da5f0} + */ +DEFINE_GUID_ENUM(IID_IHXTimeStampSync, 0x00000402, 0xb4c8, 0x11d0, 0x99, 0x95, 0x0, 0xa0, 0x24, 0x8d, 0xa5, 0xf0) + +/* + * File: + * rtspif.h + * Description: + * Interface for servicing synchronization across transport streams + * Interfaces: + * IID_IHXTransportSyncServer: {16b420d0-f4d0-11d5-aac0-00102051b340} + */ +DEFINE_GUID_ENUM(IID_IHXTransportSyncServer, 0x16b420d0, 0xf4d0, 0x11d5, 0xaa, 0xc0, 0x0, 0x1, 0x2, 0x51, 0xb3, 0x40) + +/* + * File: altserv.h + * + * IID_IHXAlternateServerProxy: {00000403-b4c8-11d0-9995-00a0248da5f0} + */ +DEFINE_GUID_ENUM(IID_IHXAlternateServerProxy, 0x00000403, 0xb4c8, 0x11d0, 0x99, 0x95, 0x0, 0xa0, 0x24, 0x8d, 0xa5, 0xf0) + +/* + * File: altserv.h + * + * IID_IHXAlternateServerProxyResponse: {00000404-b4c8-11d0-9995-00a0248da5f0} + */ +DEFINE_GUID_ENUM(IID_IHXAlternateServerProxyResponse, 0x00000404, 0xb4c8, 0x11d0, 0x99, 0x95, 0x0, 0xa0, 0x24, 0x8d, 0xa5, 0xf0) + +/* + * File: + * pnupdate.h + * Description: + * Interface for getting file objects to the RealUpdate renderer + * Interfaces: + * IID_IHXUpdateRenderer: {00000500-b4c8-11d0-9995-00a0248da5f0} + */ +DEFINE_GUID_ENUM(IID_IHXUpdateRenderer, 0x00000500, 0xb4c8, 0x11d0, 0x99, 0x95, 0x0, 0xa0, 0x24, 0x8d, 0xa5, 0xf0) +/* From file pnrup/pub/pnrup.h, but related nonetheless. */ +DEFINE_GUID_ENUM(IID_IHXCHXRealUpdateResponse, 0x00000501, 0xb4c8, 0x11d0, 0x99, 0x95, 0x0, 0xa0, 0x24, 0x8d, 0xa5, 0xf0) +DEFINE_GUID_ENUM(IID_IHXUpdateRendererResponse, 0x00000502, 0xb4c8, 0x11d0, 0x99, 0x95, 0x0, 0xa0, 0x24, 0x8d, 0xa5, 0xf0) + +/* + * File: + * hxprefs.h + * Description: + * Interface for deleting prefernces + * Interfaces: + * IID_IHXPreferences3: {0x00000505-9011-11d1-8b60-a024406d59} + */ + +#ifndef _HXPREFS_H_ +DEFINE_GUID_ENUM(IID_IHXPreferences3, 0x00000505, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +#endif + +/* + * File: + * hxpnets.h + * Description: + * Cloaked HTTP Network Services. Creation of cloaked Client and + * Server sockets. + * Interfaces: + * IID_IHXCloakedNetworkServices {00000600-b4c8-11d0-9995-00a0248da5f0} + * IID_IHXHTTPProxy {00000601-b4c8-11d0-9995-00a0248da5f0} + * IID_IHXCloakedTCPSocket {00000602-b4c8-11d0-9995-00a0248da5f0} + */ +DEFINE_GUID_ENUM(IID_IHXCloakedNetworkServices, 0x00000600, 0xb4c8, 0x11d0, 0x99, 0x95, 0x0, 0xa0, 0x24, 0x8d, 0xa5, 0xf0) +DEFINE_GUID_ENUM(IID_IHXHTTPProxy, 0x00000601, 0xb4c8, 0x11d0, 0x99, 0x95, 0x0, 0xa0, 0x24, 0x8d, 0xa5, 0xf0) +DEFINE_GUID_ENUM(IID_IHXCloakedTCPSocket, 0x00000602, 0xb4c8, 0x11d0, 0x99, 0x95, 0x0, 0xa0, 0x24, 0x8d, 0xa5, 0xf0) + +/* + * File: + * hxmeta.h + * Description: + * Metafile creation & navigation Interfaces + * Interfaces: + * IID_IHXMetaTrack: {00000E01-0901-11d1-8B06-00A024406D59} + * IID_IHXMetaGroup: {00000E02-0901-11d1-8B06-00A024406D59} + * IID_IHXMetaLayout: {00000E03-0901-11d1-8B06-00A024406D59} + * IID_IHXMetaTuner: {00000E04-0901-11d1-8B06-00A024406D59} + * IID_IHXMetaFileFormatObject: {00000E05-0901-11d1-8B06-00A024406D59} + * IID_IHXMetaFileFormatResponse: {00000E06-0901-11d1-8B06-00A024406D59} + * IID_IHXSiteLayout: {00000E07-0901-11d1-8B06-00A024406D59} + */ +DEFINE_GUID_ENUM(IID_IHXMetaTrack, 0x00000E01, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXMetaGroup, 0x00000E02, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXMetaLayout, 0x00000E03, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXMetaTuner, 0x00000E04, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXMetaFileFormatObject, 0x00000E05, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXMetaFileFormatResponse, 0x00000E06, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXSiteLayout, 0x00000E07, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) + +/* + * File: + * hxsrc.h + * Description: + * Interfaces related to raw sources and sinks + * Interfaces: + * IID_IHXRawSourceObject: {00001000-0901-11d1-8B06-00A024406D59} + * IID_IHXRawSinkObject: {00001001-0901-11d1-8B06-00A024406D59} + * IID_IHXSourceFinderObject: {00001002-0901-11d1-8B06-00A024406D59} + * IID_IHXSourceFinderResponse: {00001003-0901-11d1-8B06-00A024406D59} + * IID_IHXSourceFinderFileResponse: {00001004-0901-11d1-8B06-00A024406D59} + */ +DEFINE_GUID_ENUM(IID_IHXRawSourceObject, 0x00001000, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXRawSinkObject, 0x00001001, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXSourceFinderObject, 0x00001002, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXSourceFinderResponse, 0x00001003, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXSourceFinderFileResponse, 0x00001004, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) + +/* + * File: + * hxtbuf.h + * Description: + * Interface related TimeStamped IHXBuffers + * Interfaces: + * IID_IHXTimeStampedBuffer: {00000700-b4c8-11d0-9995-00a0248da5f0} + */ +DEFINE_GUID_ENUM(IID_IHXTimeStampedBuffer, 0x00000700, 0xb4c8, 0x11d0, 0x99, 0x95, 0x0, 0xa0, 0x24, 0x8d, 0xa5, 0xf0) + +/* + * File: + * hxsmbw.h + * Description: + * Interface related the ASM Bandwidth Manager + * Interfaces: + * IID_IHXBandwidthManager: {00000800-b4c8-11d0-9995-00a0248da5f0} + * IID_IHXSourceBandwidthInfo: {00000801-b4c8-11d0-9995-00a0248da5f0} + * IID_IHXBandwidthManagerInput: {00000802-b4c8-11d0-9995-00a0248da5f0} + * IID_IHXStreamBandwidthNegotiator: {00000803-b4c8-11d0-9995-00a0248da5f0} + * IID_IHXStreamBandwidthBias: {00000804-b4c8-11d0-9995-00a0248da5f0} + * IID_IHXThinnableSource: {00000805-b4c8-11d0-9995-00a0248da5f0} + * IID_IHXBandwidthNudger: {00000806-b4c8-11d0-9995-00a0248da5f0} + * IID_IHXASMProps: {00000807-b4c8-11d0-9995-00a0248da5f0} + * IID_IHXAtomicRuleChange: {00000808-b4c8-11d0-9995-00a0248da5f0} + * IID_IHXAtomicRuleGather: {00000809-b4c8-11d0-9995-00a0248da5f0} + * IID_IHXPlayerState: {0000080A-b4c8-11d0-9995-00a0248da5f0} + */ +DEFINE_GUID_ENUM(IID_IHXBandwidthManager, 0x00000800, 0xb4c8, 0x11d0, 0x99, 0x95, 0x0, 0xa0, 0x24, 0x8d, 0xa5, 0xf0) +DEFINE_GUID_ENUM(IID_IHXSourceBandwidthInfo, 0x00000801, 0xb4c8, 0x11d0, 0x99, 0x95, 0x0, 0xa0, 0x24, 0x8d, 0xa5, 0xf0) +DEFINE_GUID_ENUM(IID_IHXBandwidthManagerInput, 0x00000802, 0xb4c8, 0x11d0, 0x99, 0x95, 0x0, 0xa0, 0x24, 0x8d, 0xa5, 0xf0) +DEFINE_GUID_ENUM(IID_IHXStreamBandwidthNegotiator, 0x00000803, 0xb4c8, 0x11d0, 0x99, 0x95, 0x0, 0xa0, 0x24, 0x8d, 0xa5, 0xf0) +DEFINE_GUID_ENUM(IID_IHXStreamBandwidthBias, 0x00000804, 0xb4c8, 0x11d0, 0x99, 0x95, 0x0, 0xa0, 0x24, 0x8d, 0xa5, 0xf0) +DEFINE_GUID_ENUM(IID_IHXThinnableSource, 0x00000805, 0xb4c8, 0x11d0, 0x99, 0x95, 0x0, 0xa0, 0x24, 0x8d, 0xa5, 0xf0) +DEFINE_GUID_ENUM(IID_IHXBandwidthNudger, 0x00000806, 0xb4c8, 0x11d0, 0x99, 0x95, 0x0, 0xa0, 0x24, 0x8d, 0xa5, 0xf0) +DEFINE_GUID_ENUM(IID_IHXASMProps, 0x00000807, 0xb4c8, 0x11d0, 0x99, 0x95, 0x0, 0xa0, 0x24, 0x8d, 0xa5, 0xf0) +DEFINE_GUID_ENUM(IID_IHXAtomicRuleChange, 0x00000808, 0xb4c8, 0x11d0, 0x99, 0x95, 0x0, 0xa0, 0x24, 0x8d, 0xa5, 0xf0) +DEFINE_GUID_ENUM(IID_IHXAtomicRuleGather, 0x00000809, 0xb4c8, 0x11d0, 0x99, 0x95, 0x0, 0xa0, 0x24, 0x8d, 0xa5, 0xf0) +DEFINE_GUID_ENUM(IID_IHXPlayerState, 0x0000080A, 0xb4c8, 0x11d0, 0x99, 0x95, 0x0, 0xa0, 0x24, 0x8d, 0xa5, 0xf0) + +/* + * File: + * rmavctrl.c + * Description: + * Video Control Interface + * Interface: + * IID_IHXVideoControl: {00000900-b4c8-11d0-9995-00a0248da5f0} + * + */ +DEFINE_GUID_ENUM(IID_IHXVideoControl, 0x00000900, 0xb4c8, 0x11d0, 0x99, 0x95, 0x0, 0xa0, 0x24, 0x8d, 0xa5, 0xf0) + +/* + * File: + * hxxres.h + * Description: + * Cross platform resource reading class. Reads resources directly + * from Win32 DLL's and EXEs on any platform. + * + * Interfaces: + * IID_IHXXResFile {00000A00-b4c8-11d0-9995-00a0248da5f0} + * IID_IHXXResource {00000A01-b4c8-11d0-9995-00a0248da5f0} + */ +DEFINE_GUID_ENUM(IID_IHXXResFile, 0x00000A00, 0xb4c8, 0x11d0, 0x99, 0x95, 0x0, 0xa0, 0x24, 0x8d, 0xa5, 0xf0) +DEFINE_GUID_ENUM(IID_IHXXResource, 0x00000A01, 0xb4c8, 0x11d0, 0x99, 0x95, 0x0, 0xa0, 0x24, 0x8d, 0xa5, 0xf0) + +/* + * File: + * hxrasyn.h + * Description: + * RealAudio Synchronization interface + * + * Interfaces: + * IID_IHXRealAudioSync {00000B00-b4c8-11d0-9995-00a0248da5f0} + */ +DEFINE_GUID_ENUM(IID_IHXRealAudioSync, 0x00000B00, 0xb4c8, 0x11d0, 0x99, 0x95, 0x0, 0xa0, 0x24, 0x8d, 0xa5, 0xf0) + +/* + * File: + * hxshtdn.h + * Description: + * Shut down all the plugins + * + * Interfaces: + * IID_IHXShutDownEverything {00000C00-b4c8-11d0-9995-00a0248da5f0} + */ +DEFINE_GUID_ENUM(IID_IHXShutDownEverything, 0x00000C00, 0xb4c8, 0x11d0, 0x99, + 0x95, 0x0, 0xa0, 0x24, 0x8d, 0xa5, 0xf0) + +/* + * File: + * hxgroup.h + * Description: + * Interface for precache manager + * Interfaces: + * IID_IHXPreCacheMgr: {00000E00-b4c8-11d0-9995-00a0248da5f0} + */ + +DEFINE_GUID_ENUM(IID_IHXPreCacheGroupMgr, 0x00000E00, 0xb4c8, 0x11d0, 0x99, 0x95, 0x0, 0xa0, 0x24, 0x8d, 0xa5, 0xf0) + +/* + * File: + * hxdataf.h + * Description: + * Interface for basic data file operations + * Interfaces: + * IID_IHXDataFileFactory: {00000F00-b4c8-11d0-9995-00a0248da5f0} + * IID_IHXDataFile: {00000F01-b4c8-11d0-9995-00a0248da5f0} + */ + +DEFINE_GUID_ENUM(IID_IHXDataFileFactory, 0x00000F00, 0xb4c8, 0x11d0, 0x99, 0x95, 0x0, 0xa0, 0x24, 0x8d, 0xa5, 0xf0) +DEFINE_GUID_ENUM(IID_IHXDataFile, 0x00000F01, 0xb4c8, 0x11d0, 0x99, 0x95, 0x0, 0xa0, 0x24, 0x8d, 0xa5, 0xf0) +DEFINE_GUID_ENUM(IID_IHXAsyncDataFile, 0x972bacc0, 0xaff, 0x11d7, 0xac, 0x45, 0x0, 0x1, 0x2, 0x51, 0xb3, 0x40) +#ifdef _SYMBIAN +DEFINE_GUID_ENUM(IID_IHXSymbFileSessionManager, 0x8a5c6080, 0xb16, 0x11d7, 0xac, 0x45, 0x0, 0x1, 0x2, 0x51, 0xb3, 0x40) +#endif // _SYMBIAN + +/* + * File: + * memfsys.h + * Description: + * Interface for basic data file operations + * Interfaces: + * IID_IHXMemoryFileContext: {00001000-b4c8-11d0-9995-00a0248da5f0} + * IID_IHXMemoryFileSystem: {00001001-b4c8-11d0-9995-00a0248da5f0} + */ + +DEFINE_GUID_ENUM(IID_IHXMemoryFileContext, 0x00001000, 0xb4c8, 0x11d0, 0x99, 0x95, 0x0, 0xa0, 0x24, 0x8d, 0xa5, 0xf0) +DEFINE_GUID_ENUM(IID_IHXMemoryFileSystem, 0x00001001, 0xb4c8, 0x11d0, 0x99, 0x95, 0x0, 0xa0, 0x24, 0x8d, 0xa5, 0xf0) +DEFINE_GUID_ENUM(IID_IHXMemoryFileSystem2, 0x00001002, 0xb4c8, 0x11d0, 0x99, 0x95, 0x0, 0xa0, 0x24, 0x8d, 0xa5, 0xf0) + +/* + * File: + * hxrsdbf.h + * Description: + * Interface for resend buffer management + * Interfaces: + * IID_IHXResendBufferControl: {00002B00-b4c8-11d0-9995-00a0248da5f0} + * IID_IHXResendBufferControl2: {D3103F1E-738F-4161-9B39-1DE7BC60E0E3} + */ + +DEFINE_GUID_ENUM(IID_IHXResendBufferControl, 0x00002B00, 0xb4c8, 0x11d0, 0x99, 0x95, 0x0, 0xa0, 0x24, 0x8d, 0xa5, 0xf0) +DEFINE_GUID_ENUM(IID_IHXResendBufferControl2, 0xd3103f1e, 0x738f, 0x4161, 0x9b, 0x39, 0x1d, 0xe7, 0xbc, 0x60, 0xe0, 0xe3) + +/* + * File: + * hxtset.h + * Description: + * Timeout settings interface + * Interfaces: + * IID_IHXTimeoutSettings: {950A6ED6-36D5-11d2-8F78-0060083BE561} + */ +#ifndef _HXTSET_H_ +DEFINE_GUID_ENUM(IID_IHXTimeoutSettings, 0x950a6ed6, 0x36d5, 0x11d2, 0x8f, 0x78, 0x0, 0x60, 0x8, 0x3b, 0xe5, 0x61) +#endif +/* + * File: + * hxspriv.h + * Description: + * Interface for descriptor registration + * Interfaces: + * IID_IHXDescriptorRegistration: {00001100-b4c8-11d0-9995-00a0248da5f0} + */ +DEFINE_GUID_ENUM(IID_IHXDescriptorRegistration, 0x00001100, 0xb4c8, 0x11d0, 0x99, 0x95, 0x0, 0xa0, 0x24, 0x8d, 0xa5, 0xf0) + +/* + * File: + * hxxrsmg.h + * Description: + * Interface for external resource manager + * Interfaces: + * IID_IHXExternalResourceManager: {00001200-b4c8-11d0-9995-00a0248da5f0} + * IID_IHXExternalResourceReader: {00001201-b4c8-11d0-9995-00a0248da5f0} + */ +DEFINE_GUID_ENUM(IID_IHXExternalResourceManager, 0x00001200, 0xb4c8, 0x11d0, 0x99, 0x95, 0x0, 0xa0, 0x24, 0x8d, 0xa5, 0xf0) +DEFINE_GUID_ENUM(IID_IHXExternalResourceReader, 0x00001201, 0xb4c8, 0x11d0, 0x99, 0x95, 0x0, 0xa0, 0x24, 0x8d, 0xa5, 0xf0) + +/* + * File: + * hxcredc.h + * Description: + * Interface for credential(username/password) cache + * + * Interfaces: + * IID_IHXCredentialsCache {00002B00-0901-11d1-8B06-00A024406D59} + */ + +DEFINE_GUID_ENUM(IID_IHXCredentialsCache, 0x00002B00, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) + + +/* + * File: + * hxxml.h + * Description: + * Interface for XML parser + * + * Interfaces: + * IID_IHXXMLParser {00002D00-0901-11d1-8B06-00A024406D59} + * IID_IHXXMLParserResponse {00002D01-0901-11d1-8B06-00A024406D59} + * IID_IHXXMLNamespaceParser {00002D02-0901-11d1-8B06-00A024406D59} + * IID_IHXXMLNamespaceResponse {00002D03-0901-11d1-8B06-00A024406D59} + */ +DEFINE_GUID_ENUM(IID_IHXXMLParser, 0x1a39e773, 0xfe28, 0x4ca9, 0x93, 0x18, 0x9d, 0x21, 0xee, 0x85, 0xe4, 0x7a) +DEFINE_GUID_ENUM(IID_IHXXMLParserResponse, 0x00002D01, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXXMLNamespaceParser, 0x00002D02, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXXMLNamespaceResponse,0x00002D03, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) + +/* + * {0000150*-***************************} DEPRECATED + * {0000150*-***************************} DEPRECATED + */ + + +/* + * File: + * clbwcont.h + * Description: + * Interface for controlling client bandwidth usage from the server + * + * Interfaces: + * IID_IHXClientBandwidthController: {00001600-b4c8-11d0-9995-00a0248da5f0 +} + */ + +DEFINE_GUID_ENUM(IID_IHXClientBandwidthController, 0x00001600, 0xb4c8, 0x11d0, 0x99 , 0x95, 0x0, 0xa0, 0x24, 0x8d, 0xa5, 0xf0) + + +/* File: + * embdsvcs.h + * + * Description: + * + * IRCAEmbeddedServices - RCA embedded player services + */ + +DEFINE_GUID_ENUM(IID_IRCAEmbeddedServices, + 0x00003807, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) + + +/* This IID is just reserved here, there is no interface for it */ +DEFINE_GUID_ENUM(IID_ServerPacket, 0x00001700, 0xb4c8, 0x11d0, 0x99, 0x95, 0x0, 0xa0, 0x24, 0x8d, 0xa5, 0xf0) + +#ifndef _HXPHOOK_H_ + +DEFINE_GUID_ENUM(IID_IHXPacketHookSink, 0x00002004, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXRecordTimeline, 0x00002005, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) + +#endif + +/* File: + * Used by licrequest.cpp + * + * Description: + * + * IHXLicenseRequestResponse - Response interface for async license + * request + */ +// {b8676e90-625c-11d4-968500c0f031f80f} +DEFINE_GUID_ENUM( IID_IHXLicenseRequestResponse, + 0xb8676e90, 0x625c, 0x11d4, 0x96, 0x85, 0x00, 0xc0, 0xf0, 0x31, 0xf8, 0x0f) + +/* File: + * Used by inetwork.cpp + * + * Description: + * + * IHXBufferedSocket - buffered writes for higher performing + * TCP + * + */ + +#ifndef _HXENGIN_H_ +DEFINE_GUID_ENUM(IID_IHXBufferedSocket, + 0x00001402, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +#endif /* _HXENGIN_H_ */ +/* File: + * Used by propreadonly.*(server) + * + * Description: + * + * IHXInternalSetPropReadOnly - Set/Unset registry properties to be read only + */ +// {b8676e90-625c-11d4-968500c0f0320910} +DEFINE_GUID_ENUM(IID_IHXInternalSetPropReadOnly, + 0xb8676e90, 0x625c, 0x11d4, 0x96, 0x85, 0x00, 0xc0, 0xf0, 0x32, 0x09, 0x10) + + + +/* File: + * hxcore.h + * + * Description: + * + * IHXClientStatisticsGranularity - Experimental interface to allow setting the rate at which statictics are generated + * IHXPlayerPresentation - Control over the player's current presentation + * IHXCoreMutex - Control the core mutex + * IHXMacBlitMutex - control Mac blitting to ensure no reentrancy + */ +// {b8676e90-625c-11d4-968500c0f0320910} +#ifndef _HXCORE_H_ +DEFINE_GUID_ENUM(IID_IHXClientStatisticsGranularity, 0x00000417, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXPlayerPresentation, 0x6de011a7, 0xef05, 0x417b, 0x93, 0x67, 0x6f, 0xe0, 0xe5, 0x43, 0x2, 0xd3) +DEFINE_GUID_ENUM(IID_IHXCoreMutex, 0x6de011a7, 0xef05, 0x417b, 0x93, 0x67, 0x6f, 0xe0, 0xe4, 0x44, 0x4, 0xd4) +DEFINE_GUID_ENUM(IID_IHXMacBlitMutex, 0x294e6de4, 0xfbc6, 0x4c06, 0xbb, 0x94, 0x95, 0xa9, 0x69, 0x37, 0x3b, 0x4d) +#endif /* _HXCORE_H_ */ + +/* + * File: + * hxpreftr.h + * + * Description: + * + * IHXPreferredTransportManager + * IHXPreferredTransport + * IHXPreferredTransportSink + * + * Interfaces: + * IID_IHXPreferredTransportManager: {00003700-0901-11d1-8B06-00A024406D59} + * IID_IHXPreferredTransport: {00003701-0901-11d1-8B06-00A024406D59} + * IID_IHXPreferredTransportSink: {00003702-0901-11d1-8B06-00A024406D59} + */ +DEFINE_GUID_ENUM(IID_IHXPreferredTransportManager, 0x00003700, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXPreferredTransport, 0x00003701, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXPreferredTransportSink, 0x00003702, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) + +/* + * File: + * used by inetwork.cpp, rtspserv.cpp + * + * Description: + An IID to distiguish LocalBoundSocket from IHXTCPSocket + */ +DEFINE_GUID_ENUM(IID_IHXPLocalBoundSocket, 0xc1f00ad1, 0x61e7, 0x11d5, 0xa5, 0x30, + 0x0, 0x2, 0xb3, 0x6, 0xdc, 0x93) + +/* + * File: + * http.cpp, adminfo.cpp + * + * Description: + * File object interface to support out of band http post data + */ +#ifndef _HXFILES_H_ +DEFINE_GUID_ENUM(IID_IHXPostDataHandler, 0x0222a1b5, 0x49fc, 0x47e2, 0xb6, + 0x90, 0x98, 0xbe, 0xfa, 0x0a, 0x58, 0x8e) +#endif + +/* + * File: + * ihxlist.h - COM list containers + * + * Description: + * + * Interfaces: + * IID_IHXList + * IID_IHXListIterator + */ +#ifndef _IHXLIST_H_ +DEFINE_GUID_ENUM(IID_IHXList, 0x1599cb17, 0x9db4, 0x4f8a, 0x86, 0x5a, 0x78, 0xa5, 0x4e, 0xff, 0xbc, 0x60) +DEFINE_GUID_ENUM(IID_IHXListIterator, 0xe7ad1443, 0xf6bf, 0x4b0e, 0xbc, 0x00, 0x8f, 0x03, 0xc0, 0xb1, 0x27, 0x24) +#endif /* _IHXLIST_H_ */ + + +/* + * File: + * hxrtsp2.h - New RTSP stuff (tommy's new parser, etc.) + * + * Description: + * The faster and more efficient (memcpy-wise) RTSP parser, MIME header + * handler and related clases. + * + * Interfaces: + * IID_IHXMIMEParameter + * IID_IHXMIMEField + * IID_IHXMIMEHeader + * IID_IHXRTSPMessage + * IID_IHXRTSPRequestMessage + * IID_IHXRTSPResponseMessage + * IID_IHXRTSPConsumer + * IID_IHXRTSPProtocolResponse + * IID_IHXRTSPProtocol + */ +#ifndef _HXRTSP2_H_ +DEFINE_GUID_ENUM(IID_IHXMIMEParameter, 0x8ae57afa, 0x902c, 0x4327, 0x8c, 0x00, 0x31, 0x57, 0x85, 0xcd, 0xc2, 0x43) +DEFINE_GUID_ENUM(IID_IHXMIMEField, 0x946eed6, 0x0501, 0x4fc3, 0x94, 0xbb, 0x30, 0x23, 0xa0, 0xe5, 0x23, 0xc7) +DEFINE_GUID_ENUM(IID_IHXMIMEHeader, 0x97e681a3, 0xbd71, 0x4b81, 0x8f, 0xa0, 0x81, 0x19, 0x9e, 0x79, 0x9a, 0xe7) +DEFINE_GUID_ENUM(IID_IHXRTSPMessage, 0x1bff98ab, 0xe5c9, 0x459d, 0x80, 0xee, 0xb8, 0x0d, 0x20, 0xe4, 0xf3, 0x0e) +DEFINE_GUID_ENUM(IID_IHXRTSPRequestMessage, 0xddb0e73f, 0x0d5a, 0x4fd1, 0xbd, 0xc8, 0x95, 0x7f, 0x0d, 0x87, 0x2a, 0x33) +DEFINE_GUID_ENUM(IID_IHXRTSPResponseMessage, 0x876baec2, 0xec9e, 0x41dc, 0x8c, 0xb6, 0xe8, 0x74, 0xb6, 0x0f, 0xba, 0xd6) +DEFINE_GUID_ENUM(IID_IHXRTSPInterleavedPacket, 0x4d737eff, 0x8218, 0x4762, 0xac, 0xe3, 0xfc, 0xf2, 0x7c, 0x08, 0xf9, 0x16) +DEFINE_GUID_ENUM(IID_IHXRTSPConsumer, 0xda62eb99, 0x2120, 0x410a, 0x98, 0x66, 0x90, 0xf7, 0xec, 0x9c, 0xc1, 0x5d) +DEFINE_GUID_ENUM(IID_IHXRTSPProtocol, 0x29d8eebf, 0x5597, 0x410b, 0xa2, 0x90, 0x81, 0x81, 0xbe, 0x1e, 0x24, 0x30) +DEFINE_GUID_ENUM(IID_IHXRTSPProtocolResponse, 0xbf646cd4, 0x922c, 0x4b9c, 0xac, 0x92, 0x96, 0xe7, 0x74, 0xde, 0x56, 0x39) +#endif + +/* + * File: + * hxsdp.h - New SDP stuff + * + * Description: + * The faster and more efficient (memcpy-wise) SDP parser + * handler and related clases. + * + * Interfaces: + * IID_IHXSDPAttrib + * IID_IHXSDPTimeDesc + * IID_IHXSDPMedia + * IID_IHXSDP + */ +#ifndef _HXSDP_H_ +DEFINE_GUID_ENUM(IID_IHXSDPAttrib, 0x538d82b4, 0xa284, 0x4527, 0xae, 0xf5, 0x47, 0xce, 0x87, 0xd8, 0x31, 0xcc) +DEFINE_GUID_ENUM(IID_IHXSDPTimeDesc, 0xffc46a97, 0x965d, 0x41cb, 0xa3, 0x50, 0xde, 0x2c, 0x48, 0x6e, 0x72, 0x90) +DEFINE_GUID_ENUM(IID_IHXSDPMedia, 0x30069090, 0xa6dc, 0x47c5, 0x89, 0x1f, 0x9e, 0xea, 0x9c, 0x86, 0x0a, 0x6b) +DEFINE_GUID_ENUM(IID_IHXSDP, 0x6fe2a32b, 0x36fa, 0x4fd4, 0x88, 0x09, 0x1c, 0x5a, 0xda, 0x9b, 0xea, 0x15) +#endif + +#ifndef _HXLISTP_H_ +DEFINE_GUID_ENUM(IID_IHXListIteratorPrivate, 0x2d3f1b24, 0x6e4e, 0x4fdc, 0x9e, 0x53, 0x4b, 0x9b, 0xd7, 0x3f, 0x3f, 0xed) +DEFINE_GUID_ENUM(IID_IHXVectorIteratorPrivate, 0x58b7d31f, 0x2261, 0x4608, 0xa6, 0x34, 0x4e, 0x98, 0xdc, 0x8f, 0xa8, 0x4f) +DEFINE_GUID_ENUM(IID_IHXRingBufferIteratorPrivate, 0x87d7ca44, 0x7a75, 0x11d7, 0x8b, 0x4a, 0x00, 0xd0, 0xb7, 0x10, 0x35, 0xfe) +#endif + +/* + * File: + * hxmms.h - MMS protocol + * + * Description: + * MMS (Windows Media) protocol. + * + * Interfaces: + * IID_ + */ +#ifndef _HXMMS2_H_ +DEFINE_GUID_ENUM(IID_IHXMMSMessage, 0x3cb3f6dc, 0xcb9a, 0x4a37, 0x99, 0x3a, 0xd1, 0x81, 0x7a, 0xdd, 0x91, 0x00) +DEFINE_GUID_ENUM(IID_IHXMMSConsumer, 0x3cb3f6dc, 0xcb9a, 0x4a37, 0x99, 0x3a, 0xd1, 0x81, 0x7a, 0xdd, 0x91, 0x01) +DEFINE_GUID_ENUM(IID_IHXMMSMessageClientHello, 0x3cb3f6dc, 0xcb9a, 0x4a37, 0x99, 0x3a, 0xd1, 0x81, 0x7a, 0xdd, 0x91, 0x02) +DEFINE_GUID_ENUM(IID_IHXMMSMessageServerHello, 0x3cb3f6dc, 0xcb9a, 0x4a37, 0x99, 0x3a, 0xd1, 0x81, 0x7a, 0xdd, 0x91, 0x03) +DEFINE_GUID_ENUM(IID_IHXMMSMessageSetup1, 0x3cb3f6dc, 0xcb9a, 0x4a37, 0x99, 0x3a, 0xd1, 0x81, 0x7a, 0xdd, 0x91, 0x04) +DEFINE_GUID_ENUM(IID_IHXMMSMessageSetup2, 0x3cb3f6dc, 0xcb9a, 0x4a37, 0x99, 0x3a, 0xd1, 0x81, 0x7a, 0xdd, 0x91, 0x05) +DEFINE_GUID_ENUM(IID_IHXMMSMessageSetup3, 0x3cb3f6dc, 0xcb9a, 0x4a37, 0x99, 0x3a, 0xd1, 0x81, 0x7a, 0xdd, 0x91, 0x06) +DEFINE_GUID_ENUM(IID_IHXMMSMessageSetup4, 0x3cb3f6dc, 0xcb9a, 0x4a37, 0x99, 0x3a, 0xd1, 0x81, 0x7a, 0xdd, 0x91, 0x07) +DEFINE_GUID_ENUM(IID_IHXMMSMessageSetup5, 0x3cb3f6dc, 0xcb9a, 0x4a37, 0x99, 0x3a, 0xd1, 0x81, 0x7a, 0xdd, 0x91, 0x08) +DEFINE_GUID_ENUM(IID_IHXMMSMessageSetup6, 0x3cb3f6dc, 0xcb9a, 0x4a37, 0x99, 0x3a, 0xd1, 0x81, 0x7a, 0xdd, 0x91, 0x09) +DEFINE_GUID_ENUM(IID_IHXMMSMessageSetup7, 0x3cb3f6dc, 0xcb9a, 0x4a37, 0x99, 0x3a, 0xd1, 0x81, 0x7a, 0xdd, 0x91, 0x0a) +DEFINE_GUID_ENUM(IID_IHXMMSMessageSetup8, 0x3cb3f6dc, 0xcb9a, 0x4a37, 0x99, 0x3a, 0xd1, 0x81, 0x7a, 0xdd, 0x91, 0x0b) +DEFINE_GUID_ENUM(IID_IHXMMSMessageSubscribe, 0x3cb3f6dc, 0xcb9a, 0x4a37, 0x99, 0x3a, 0xd1, 0x81, 0x7a, 0xdd, 0x91, 0x0c) +DEFINE_GUID_ENUM(IID_IHXMMSMessageSubscribeAck, 0x3cb3f6dc, 0xcb9a, 0x4a37, 0x99, 0x3a, 0xd1, 0x81, 0x7a, 0xdd, 0x91, 0x0d) +DEFINE_GUID_ENUM(IID_IHXMMSMessagePlay, 0x3cb3f6dc, 0xcb9a, 0x4a37, 0x99, 0x3a, 0xd1, 0x81, 0x7a, 0xdd, 0x91, 0x0e) +DEFINE_GUID_ENUM(IID_IHXMMSMessagePlayAck, 0x3cb3f6dc, 0xcb9a, 0x4a37, 0x99, 0x3a, 0xd1, 0x81, 0x7a, 0xdd, 0x91, 0x0f) +DEFINE_GUID_ENUM(IID_IHXMMSMessageStop, 0x3cb3f6dc, 0xcb9a, 0x4a37, 0x99, 0x3a, 0xd1, 0x81, 0x7a, 0xdd, 0x91, 0x10) +DEFINE_GUID_ENUM(IID_IHXMMSMessageEndData, 0x3cb3f6dc, 0xcb9a, 0x4a37, 0x99, 0x3a, 0xd1, 0x81, 0x7a, 0xdd, 0x91, 0x11) +DEFINE_GUID_ENUM(IID_IHXMMSMessageFastPlay, 0x3cb3f6dc, 0xcb9a, 0x4a37, 0x99, 0x3a, 0xd1, 0x81, 0x7a, 0xdd, 0x91, 0x12) +DEFINE_GUID_ENUM(IID_IHXMMSMessageFastPlayAck, 0x3cb3f6dc, 0xcb9a, 0x4a37, 0x99, 0x3a, 0xd1, 0x81, 0x7a, 0xdd, 0x91, 0x13) +DEFINE_GUID_ENUM(IID_IHXMMSMessageStats, 0x3cb3f6dc, 0xcb9a, 0x4a37, 0x99, 0x3a, 0xd1, 0x81, 0x7a, 0xdd, 0x91, 0x14) +DEFINE_GUID_ENUM(IID_IHXMMSMessageGoodbye, 0x3cb3f6dc, 0xcb9a, 0x4a37, 0x99, 0x3a, 0xd1, 0x81, 0x7a, 0xdd, 0x91, 0x15) +DEFINE_GUID_ENUM(IID_IHXMMSMessagePing, 0x3cb3f6dc, 0xcb9a, 0x4a37, 0x99, 0x3a, 0xd1, 0x81, 0x7a, 0xdd, 0x91, 0x16) +DEFINE_GUID_ENUM(IID_IHXMMSFileHeader, 0x3cb3f6dc, 0xcb9a, 0x4a37, 0x99, 0x3a, 0xd1, 0x81, 0x7a, 0xdd, 0x91, 0x17) +DEFINE_GUID_ENUM(IID_IHXMMSInterleavedPacket, 0x3cb3f6dc, 0xcb9a, 0x4a37, 0x99, 0x3a, 0xd1, 0x81, 0x7a, 0xdd, 0x91, 0x18) +DEFINE_GUID_ENUM(IID_IHXMMSProtocol, 0x3cb3f6dc, 0xcb9a, 0x4a37, 0x99, 0x3a, 0xd1, 0x81, 0x7a, 0xdd, 0x91, 0x19) +DEFINE_GUID_ENUM(IID_IHXMMSProtocolResponse, 0x3cb3f6dc, 0xcb9a, 0x4a37, 0x99, 0x3a, 0xd1, 0x81, 0x7a, 0xdd, 0x91, 0x1a) +DEFINE_GUID_ENUM(IID_IHXWM60PlayerLiveHack, 0x3cb3f6dc, 0xcb9a, 0x4a37, 0x99, 0x3a, 0xd1, 0x81, 0x7a, 0xdd, 0x91, 0x1b) +DEFINE_GUID_ENUM(IID_IHXMMSMessageLiveHeader, 0x3cb3f6dc, 0xcb9a, 0x4a37, 0x99, 0x3a, 0xd1, 0x81, 0x7a, 0xdd, 0x91, 0x1c) +#endif + +/* + * + * File: + * hxstktrc.h -- Stack Trace interface for debugging + * + * Description: + * This interface provides the user the ability to get the stack + * trace at any point in the program by a call to GetTrace(). + */ +DEFINE_GUID_ENUM(IID_IHXStackTrace, 0x00004702, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) + +/* + * File: + * hxformt.h + * Description: + * Interfaces related to file and broadcast format plugins. + * Interfaces: + * IID_IHXSetPlayParam {0x503c212c-413f-478b-9fc8daa7b145b8a9} + * IID_IHXSetPlayParamResponse {0x750008af-5588-4838-85faaa203a32c799} + * IID_IHXSeekByPacket {0x171c3c4e-c4ea-46fd-b47b-c3b82dbb9517} + * IID_IHXSeekByPacketResponse {0xe978476d-6c99-4dc6-9279-7525c693dc34} + */ +#ifndef _HXFORMT_H_ +DEFINE_GUID_ENUM(IID_IHXSetPlayParam, 0x503c212c, 0x413f, 0x478b, 0x9f, 0xc8, 0xda, 0xa7, 0xb1, 0x45, 0xb8, 0xa9) +DEFINE_GUID_ENUM(IID_IHXSetPlayParamResponse, 0x750008af, 0x5588, 0x4838, 0x85, 0xfa, 0xaa, 0x20, 0x3a, 0x32, 0xc7, 0x99) + +DEFINE_GUID_ENUM(IID_IHXSeekByPacket, 0x171c3c4e, 0xc4ea, 0x46fd, 0xb4, 0x7b, 0xc3, 0xb8, 0x2d, 0xbb, 0x95, 0x17) +DEFINE_GUID_ENUM(IID_IHXSeekByPacketResponse, 0xe978476d, 0x6c99, 0x4dc6, 0x92, 0x79, 0x75, 0x25, 0xc6, 0x93, 0xdc, 0x34) +#endif + +/* + * + * File: + * hxpac.h + * + */ +DEFINE_GUID_ENUM(IID_IHXProxyAutoConfig, 0x00004800, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXProxyAutoConfigCallback, 0x00004801, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXProxyAutoConfigAdviseSink, 0x00004802, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) + +/* + * + * File: + * hxfwmgr.h + * + */ +DEFINE_GUID_ENUM(IID_IHXFirewallControlManager, 0x00004900, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) + +/* + * + * File: + * mmsrsend.h + * + */ +#ifndef MMSRSEND_H +DEFINE_GUID_ENUM(IID_IHXMMSResendManager, 0x4676c245, 0xad4e, 0x45b9, 0xa8, 0xdd, 0x60, 0xa8, 0xe4, 0x7b, 0xf6, 0x05) +DEFINE_GUID_ENUM(IID_IHXMMSResendResponse, 0xa581f3a4, 0xc508, 0x4800, 0xba, 0xdc, 0xb4, 0xf1, 0xcd, 0x11, 0x07, 0xb6) +#endif + + +/* + * + * File: + * hxdist_lic_requester.h + * + */ +DEFINE_GUID_ENUM(IID_IHXDistributedLicenseRequester, 0x00005702, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXDistributedLicenseRequestStatus, 0x00005703, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) + +/* + * File: hxauto.h + */ +DEFINE_GUID_ENUM(IID_IHXHTTPAutoStream, + 0x60b68af1, 0x9f0, 0x11d3, 0x8b, 0x57, 0x0, 0x90, 0x27, 0x42, 0xc8, 0xa7) + +/* + * File: hxflche.h + */ +DEFINE_GUID_ENUM(IID_IHXFileSystemCache, + 0x18d8a780, 0xf90d, 0x11d2, 0xad, 0x55, 0x0, 0xc0, 0xf0, 0x31, 0xc2, 0x36) + +/* + * File: ihxident.h + */ +DEFINE_GUID_ENUM(IID_IHXProductIdentity, + 0xae7eb8a0, 0x32dc, 0x11d2, 0x8a, 0xc0, 0x0, 0xc0, 0x4f, 0xee, 0x3a, 0x97) +DEFINE_GUID_ENUM(IID_IHXProductIdentity2, + 0xae7eb8a0, 0x32dc, 0x11d2, 0x7a, 0xc0, 0x0, 0xc0, 0x4f, 0xee, 0x3a, 0x98) + +/* + * File: ihxperplex.h + */ +DEFINE_GUID_ENUM(IID_IHXPerplex, + 0xb0f17ee1, 0xdd86, 0x11d2, 0xb3, 0x39, 0x0, 0xc0, 0xf0, 0x31, 0x87, 0x98) + +/* + * File: ihxdefpackethookhlp.h + */ +DEFINE_GUID_ENUM(IID_IHXDefaultPacketHookHelper, + 0x50540475, 0x79e8, 0x471e, 0xb6, 0x89, 0x3, 0xc4, 0x20, 0xea, 0xc5, 0xe1) + +/* + * File: rvsink.h + */ +DEFINE_GUID_ENUM(IID_IRVSinkMgr, + 0xa57fd431, 0x4599, 0x11d4, 0xb4, 0x23, 0x0, 0x90, 0x27, 0x43, 0xf, 0x4c) + +/* + * File: hxpcmkr.h + */ +DEFINE_GUID_ENUM(IID_IHXPaceMaker, + 0x1223eff0, 0x4dad, 0x11d5, 0xa9, 0x38, 0x0, 0x1, 0x2, 0x51, 0xb3, 0x40) + +DEFINE_GUID_ENUM(IID_IHXPaceMakerResponse, + 0xb4d81b50, 0x4dac, 0x11d5, 0xa9, 0x38, 0x0, 0x1, 0x2, 0x51, 0xb3, 0x40) + + +/* + * File: hxfswch.h + */ +DEFINE_GUID_ENUM(IID_IHXFileSwitcher, + 0x55cc55b0, 0x1ba, 0x11d4, 0x95, 0x23, 0x0, 0x90, 0x27, 0x42, 0xc9, 0x23) + +/* + * File: hxatmzr.h + */ +DEFINE_GUID_ENUM(IID_IHXAtomizationCommander, + 0xafdcd230, 0x4b, 0x11d4, 0x95, 0x23, 0x0, 0x90, 0x27, 0x42, 0xc9, 0x23) + +DEFINE_GUID_ENUM(IID_IHXAtomizerResponse, + 0x72bc0330, 0x41, 0x11d4, 0x95, 0x23, 0x0, 0x90, 0x27, 0x42, 0xc9, 0x23) + +#if defined(_STATICALLY_LINKED) && defined(WIN32) && !defined(_NO_COM) +/* + * File: ddraw.h + */ +//DEFINE_GUID_ENUM(IID_IDirectDraw2, +// 0xB3A6F3E0, 0x2B43, 0x11CF, 0xA2, 0xDE, 0x00, 0xAA, 0x00, 0xB9, 0x33, 0x56) + +#endif + +/* + * File: hxacodec.h + */ +DEFINE_GUID_ENUM(IID_IHXAudioDecoder, + 0x26139eda, 0x98d8, 0x437b, 0x92, 0x29, 0x94, 0x9d, 0x3b, 0xef, 0x25, 0x51) + +DEFINE_GUID_ENUM(IID_IHXAudioEncoder, + 0x56ff66f4, 0xa6c5, 0x42aa, 0xb2, 0x67, 0xc2, 0x96, 0xdd, 0x7, 0x5d, 0xa3) + +DEFINE_GUID_ENUM(IID_IHXAudioEncoderConfigurator, + 0xb9919b52, 0x54ff, 0x4099, 0x98, 0x4a, 0x53, 0x3e, 0x75, 0xb5, 0x78, 0xb9) + +DEFINE_GUID_ENUM(IID_IHXCodecOldStyleAuthenticate, + 0xeae2fec7, 0xac4b, 0x4d15, 0xb1, 0xdc, 0xa, 0x94, 0x54, 0x9c, 0xf2, 0xc9) + +DEFINE_GUID_ENUM(IID_IQueryDecoderUnit, + 0x5720a4df, 0x7b79, 0x47ae, 0x84, 0x94, 0x6b, 0x46, 0x52, 0xf2, 0xd4, 0xad) + +/* + * File: ihxsimpleencryptionservice.h + */ +DEFINE_GUID_ENUM(IID_IHXSimpleEncryptionService, + 0x2a3e75dd, 0xe43e, 0x41f3, 0xad, 0xe3, 0xac, 0x9b, 0x95, 0xc2, 0x79, 0xd1) + +/* + * File: rmacrptg.h + */ +DEFINE_GUID_ENUM(IID_IHXCryptograph, + 0x78b12021, 0x9250, 0x11d4, 0x8f, 0x56, 0xe0, 0xde, 0x51, 0xc1, 0x0, 0x0) + + +#ifdef HELIX_FEATURE_HTTP_SERVICE +/* + * File: hxhttp.h + */ +DEFINE_GUID_ENUM(IID_IHXHttp, + 0xe7ddb0b0, 0x9846, 0x11d1, 0xa5, 0xfe, 0x0, 0x60, 0x97, 0xe5, 0x7c, 0x78); +DEFINE_GUID_ENUM(IID_IHXHttp2, + 0xe7ddb0b0, 0x9846, 0x11d1, 0xb5, 0xfe, 0x0, 0x60, 0x97, 0xe5, 0x7c, 0x87); +DEFINE_GUID_ENUM(IID_IHXHttpResponse, + 0xbdf0bb0, 0x9847, 0x11d1, 0xa5, 0xfe, 0x0, 0x60, 0x97, 0xe5, 0x7c, 0x78); +DEFINE_GUID_ENUM(IID_IHXHttpResponse2, + 0xe7ddb0b0, 0x9846, 0x11d1, 0xb5, 0xfe, 0x0, 0x60, 0x95, 0xe5, 0x7d, 0x87); +DEFINE_GUID_ENUM(IID_IHXHttpInitialize, + 0x083912d6, 0x6c54, 0x4e13, 0xa6, 0x99, 0xc5, 0xb3, 0x06, 0xd7, 0x61, 0xae); +#endif + + +/* + * File: ihxhurl.h + */ +DEFINE_GUID_ENUM(IID_IHXHurl, + 0xb16c0330, 0xb2ec, 0x11d1, 0x8e, 0xfd, 0x0, 0x60, 0x8, 0x3b, 0xe5, 0x61) + +/* + * File: icmdbcst.h + */ +DEFINE_GUID_ENUM(IID_IHXCmdBroadcaster, + 0x00000700, 0x6050, 0x1450, 0x7c, 0xea, 0x7, 0x0b, 0x18, 0xf8, 0x6a, 0x71) + +DEFINE_GUID_ENUM(IID_IHXCmdObserver, + 0x00000700, 0x6050, 0x1450, 0x7c, 0xea, 0x7, 0x0b, 0x18, 0xa8, 0x6a, 0x72) + +/* + * File: dunitprvt.h + */ +DEFINE_GUID_ENUM(IID_IPrepareDecoderUnit, + 0x655dd923, 0x3ab, 0x45b2, 0xb9, 0x1e, 0xf, 0x46, 0x57, 0xec, 0x9d, 0xc3) + +/* + * pxffmcod.h + */ +DEFINE_GUID_ENUM(IID_IHXRealPixFileFormatCodec, + 0x309f2d21, 0xcc0a, 0x11d2, 0x8a, 0x53, 0x10, 0xf, 0xf0, 0x0, 0x0, 0x0) + +/* + * pxrndcod.h + */ +DEFINE_GUID_ENUM(IID_IHXRealPixRendererCodec, + 0x10552e61, 0xc6f1, 0x11d2, 0x8a, 0x4f, 0x28, 0x90, 0x9a, 0x0, 0x0, 0x0) + +/* + * File: hxrtsp2.h + */ +DEFINE_GUID_ENUM(IID_IHXRTSPAggregateEventStats, 0x4ed8aabe, 0x5597, 0x410b, 0xa2, 0x90, 0x81, 0x81, 0xbe, 0x1e, 0x24, 0x01) +DEFINE_GUID_ENUM(IID_IHXRTSPEventsSink, 0x26a03092, 0x49d6, 0x483a, 0xa1, 0x2d, 0x4d, 0x69, 0xe2, 0x88, 0x56, 0xd0) +DEFINE_GUID_ENUM(IID_IHXRTSPEventsManager, 0x84988f28, 0x9264, 0x46ba, 0x8b, 0x5a, 0xb2, 0x6b, 0xd6, 0xf1, 0x63, 0x72) + +/* + * File: rtspif.h + */ +DEFINE_GUID_ENUM(IID_IHXRTSPServerPauseResponse, 0xbf646cd4, 0x922c, 0x4b9c, 0xac, 0x92, 0x96, 0xe7, 0x74, 0xde, 0x56, 0x02) + +/* + * File: hxsdp.h + */ +DEFINE_GUID_ENUM(IID_IHXSDPAggregateStats, 0x4ed8aabe, 0x5597, 0x410b, 0xa2, 0x90, 0x81, 0x81, 0xbe, 0x1e, 0x24, 0x03) + +/* + * File: hxrecord.h + */ +DEFINE_GUID_ENUM(IID_IHXRecordManager, 0x9b7854dd, 0x92c8, 0x42c6, 0x93, 0x6c, 0x56, 0x5e, 0xc3, 0x73, 0xe2, 0xad) +DEFINE_GUID_ENUM(IID_IHXRecordService, 0xf2f8c09a, 0xa607, 0x40c9, 0x9c, 0x26, 0x48, 0x3b, 0xbb, 0xc4, 0xa0, 0x86) +DEFINE_GUID_ENUM(IID_IHXRecordSource, 0xe007f531, 0x4ec9, 0x4555, 0x8e, 0xc5, 0x1d, 0x58, 0x49, 0x99, 0x4, 0xdf) + + +/* + * File: hxstats.h + */ + +DEFINE_GUID_ENUM(IID_IHXSessionStats, 0x3c02c47f, 0x6f44, 0x47fd, 0xb6, 0x25, 0xc8, 0x1a, 0x2b, 0xf0, 0x5d, 0x4f) +DEFINE_GUID_ENUM(IID_IHXClientStats, 0x83ce47e8, 0x3ebe, 0x450a, 0xbf, 0x49, 0x66, 0xd8, 0x80, 0x94, 0xe5, 0x16) +DEFINE_GUID_ENUM(IID_IHXClientStats2, 0x5dad23df, 0xa442, 0x4fe9, 0x89, 0xcd, 0x39, 0xb1, 0x3d, 0xa5, 0xca, 0x51) +DEFINE_GUID_ENUM(IID_IHXClientStatsSink, 0xfdc6d1aa, 0xd78e, 0x40b0, 0xa3, 0xe6, 0xb7, 0xd6, 0xdf, 0x30, 0x38, 0x3b) +DEFINE_GUID_ENUM(IID_IHXClientStatsManager, 0xe0ece2b8, 0xa94b, 0x4eb4, 0xaf, 0xd, 0x17, 0x7d, 0x5, 0x3b, 0x2e, 0xd6) + +/* + * File: server_stats.h + */ + +DEFINE_GUID_ENUM(IID_IHXClientStatsTimerControl, 0x793ab631, 0xb654, 0x4aae, 0x99, 0xa3, 0xd, 0xf8, 0x72, 0x58, 0xc3, 0x63) + +/* + * File: ihxplayerstateobserver.h + */ + +DEFINE_GUID_ENUM(IID_IHXPlayerStateObserver, 0x466c7e42, 0x81d1, 0x4746, 0xa6, 0xd4, 0x9e, 0x9f, 0x99, 0xba, 0x7b, 0xb5) + + +/* + * File: hxqosinfo.h + */ + +DEFINE_GUID_ENUM(IID_IHXQoSTransportAdaptationInfo, 0x213645c5, 0x3a56, 0x4945, 0xa8, 0xa9, 0x67, 0x2, 0xbb, 0x56, 0x4, 0xb6) +DEFINE_GUID_ENUM(IID_IHXQoSSessionAdaptationInfo, 0xaed09295, 0xa71, 0x4520, 0x9d, 0x7f, 0xbf, 0xa9, 0xb5, 0xa9, 0x72, 0x45) +DEFINE_GUID_ENUM(IID_IHXQoSApplicationAdaptationInfo, 0x207e23e5, 0xf71f, 0x4a18, 0xb7, 0xd0, 0xf4, 0xf8, 0x65, 0xa2, 0x5, 0x8b) + +/* + * File: hxcache2.h + */ + +DEFINE_GUID_ENUM(IID_IHXCacheObjectResponse, + 0x00002E11, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXCache2, + 0x00002E0E, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +DEFINE_GUID_ENUM(IID_IHXCacheObject, + 0x00002E10, 0x901, 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59) +/* + * File: hxrssmgr.h + */ + +DEFINE_GUID_ENUM(IID_IHXRSSReport, 0xbd2f1e35, 0x83e4, 0x4459, 0x94, 0x30, 0x2e, 0xb6, 0x37, 0xad, 0xbe, 0x17) +DEFINE_GUID_ENUM(IID_IHXRSSManager, 0x66fb8dc5, 0xd3d4, 0x4aaf, 0x8f, 0x88, 0x56, 0xb3, 0xc8, 0xa7, 0xd3, 0x1f) + +/* + * File: hxlogoutputs.h + */ +DEFINE_GUID_ENUM(IID_IHXLogOutput, 0x88ec448c, 0x136c, 0x4733, 0x94, 0x3, 0xdc, 0x43, 0x40, 0x9d, 0xca, 0x29) +DEFINE_GUID_ENUM(IID_IHXLogFileOutput, 0xc5c9c037, 0x53fa, 0x4478, 0x8b, 0xfd, 0xe4, 0x9e, 0x58, 0x8b, 0xf2, 0x1f) + +/* File: + * hxbufctl.h + * + * Description: + * Client buffer control interfaces + */ +DEFINE_GUID_ENUM(IID_IHXBufferControl, 0x68b2aef9, 0x1384, 0x46ec, 0xa4, 0xd0, 0x0, 0x68, 0xa, 0x7d, 0xbb, 0xae) +DEFINE_GUID_ENUM(IID_IHXWatermarkBufferControl, 0x68b2aef9, 0x1384, 0x46ec, 0xa4, 0xd0, 0x0, 0x68, 0xa, 0x7d, 0xbb, 0xaf) +DEFINE_GUID_ENUM(IID_IHXTransportBufferLimit, 0x68b2aef9, 0x1384, 0x46ec, 0xa4,0xd0, 0x0, 0x68, 0xa, 0x7d, 0xbb, 0xb0) + +/* File: + * ihxaccesspoint.h + * + * Description: + * Access Point interfaces + */ +DEFINE_GUID_ENUM(IID_IHXAccessPointConnectResponse, 0x9e9ca2d6, 0xcbfe, 0x40f8, 0x94, + 0xfd, 0x38, 0xf4, 0xeb, 0x5d, 0xf8, 0xf) +DEFINE_GUID_ENUM(IID_IHXAccessPointManager, 0x9e9ca2d6, 0xcbfe, 0x40f8, 0x94, + 0xfd, 0x38, 0xf4, 0xeb, 0x5d, 0xf8, 0x10) +DEFINE_GUID_ENUM(IID_IHXAccessPointSelectorResponse, + 0x9e9ca2d6, 0xcbfe, 0x40f8, 0x94, + 0xfd, 0x38, 0xf4, 0xeb, 0x5d, 0xf8, 0x11) +DEFINE_GUID_ENUM(IID_IHXAccessPointSelector, 0x9e9ca2d6, 0xcbfe, 0x40f8, 0x94, + 0xfd, 0x38, 0xf4, 0xeb, 0x5d, 0xf8, 0x12) + +/* File: + * ihxrateadaptctl.h + * + * Description: + * Rate adaptation control interfaces + */ +DEFINE_GUID_ENUM(IID_IHXClientRateAdaptControl, 0x44f5ac8c, 0x654c, 0x414e, 0x9d, + 0x18, 0xe7, 0xa4, 0x80, 0x90, 0x70, 0x9) + +/* File: + * ihxtranstime.h + * + * Description: + * Transport time interfaces + */ +DEFINE_GUID_ENUM(IID_IHXTransportTimeSink, +0xdb838ab3, 0x4637, 0x41e0, 0xbc, 0x3f, 0xed, 0x3, 0xb6, 0xa6, 0x84, 0xc1) + +DEFINE_GUID_ENUM(IID_IHXTransportTimeManager, +0xd0a5ba01, 0xedfb, 0x4741, 0xb7, 0x9, 0x88, 0x4e, 0x68, 0x5d, 0x2d, 0x8) + +/* File: + * ihx3gpp.h + * + * Description: + * Interfaces for 3GPP functionality + */ +DEFINE_GUID_ENUM(IID_IHX3gppNADU, +0x8069baaf, 0x777a, 0x4cf0, 0x90, 0x3, 0xd2, 0x6d, 0x68, 0xb8, 0xd7, 0x86) + +/* File: + * hxservnet.h + * + * Description: + * Interfaces for server-specific networking services. + */ + +DEFINE_GUID_ENUM(IID_IHXLocalBoundNetServices, + 0x669b1aaa, 0xdda3, 0x4892, 0x8d, 0x58, 0x6d, 0xc, 0xd5, 0xea, 0xcb, 0x19) + + +/* File: + * hxprivstats.h + * + * Description: + * Private client stats functionality interfaces. + */ +DEFINE_GUID_ENUM(IID_IHXPrivateClientStats, +0x2399cc75, 0x3c38, 0x4680, 0xac, 0xa5, 0xcd, 0x66, 0x3a, 0x64, 0x70, 0x1a) + +#endif /* _HXPRIVATEIIDS_H_ */ + + diff --git a/amarok/src/engine/helix/helix-sp/helix-include/common/include/hxplugn.h b/amarok/src/engine/helix/helix-sp/helix-include/common/include/hxplugn.h new file mode 100644 index 00000000..80461212 --- /dev/null +++ b/amarok/src/engine/helix/helix-sp/helix-include/common/include/hxplugn.h @@ -0,0 +1,1424 @@ + +/* + * + * This software is released under the provisions of the GPL version 2. + * see file "COPYING". If that file is not available, the full statement + * of the license can be found at + * + * http://www.fsf.org/licensing/licenses/gpl.txt + * + * Portions Copyright (c) 1995-2004 RealNetworks, Inc. All Rights Reserved. + * + */ + +#ifndef _HXPLUGN_H_ +#define _HXPLUGN_H_ + +#include "hxcom.h" +#include "hxplugncompat.h" + +/* + * Forward declarations of some interfaces defined or used here-in. + */ +typedef _INTERFACE IUnknown IUnknown; +typedef _INTERFACE IHXPlugin IHXPlugin; +typedef _INTERFACE IHXPluginEnumerator IHXPluginEnumerator; +// $Private: +typedef _INTERFACE IHXPluginSearchEnumerator IHXPluginSearchEnumerator; +typedef _INTERFACE IHXPluginChallenger IHXPluginChallenger; +// $EndPrivate. +typedef _INTERFACE IHXBuffer IHXBuffer; +typedef _INTERFACE IHXValues IHXValues; +typedef _INTERFACE IHXPreferences IHXPreferences; + +// Plugin Types. +#define PLUGIN_FILESYSTEM_TYPE "PLUGIN_FILE_SYSTEM" +#define PLUGIN_FILEFORMAT_TYPE "PLUGIN_FILE_FORMAT" +#define PLUGIN_FILEWRITER_TYPE "PLUGIN_FILE_WRITER" +#define PLUGIN_METAFILEFORMAT_TYPE "PLUGIN_METAFILE_FORMAT" +#define PLUGIN_RENDERER_TYPE "PLUGIN_RENDERER" +#define PLUGIN_DEPACKER_TYPE "PLUGIN_DEPACKER" +#define PLUGIN_REVERTER_TYPE "PLUGIN_REVERTER" +#define PLUGIN_BROADCAST_TYPE "PLUGIN_BROADCAST" +#define PLUGIN_STREAM_DESC_TYPE "PLUGIN_STREAM_DESC" +#define PLUGIN_ALLOWANCE_TYPE "PLUGIN_ALLOWANCE" +#define PLUGIN_PAC_TYPE "PLUGIN_PAC" +#define PLUGIN_CLASS_FACTORY_TYPE "PLUGIN_CLASS_FACT" + +#define PLUGIN_CLASS "PluginType" +#define PLUGIN_FILENAME "PluginFilename" +#define PLUGIN_REGKEY_ROOT "PluginHandlerData" +#define PLUGIN_PLUGININFO "PluginInfo" +#define PLUGIN_GUIDINFO "GUIDInfo" +#define PLUGIN_NONHXINFO "NonHXDLLs" +#define PLUGIN_IDENTIFIER "Plugin#" +// This may no longer be needed... +#define PLUGINDIRECTORYHASH "DirHash" +// XXXAH WHO is defining this ... I think I know.. +#define PLUGIN_DESCRIPTION2 "Description" +// XXXAH WHO is defining this ... I think I know.. +#define PLUGIN_FILE_HASH "FileHash" +#define PLUGIN_INDEX "IndexNumber" +#define PLUGIN_FILENAMES "FileInfo" +#define PLUGIN_COPYRIGHT2 "Copyright" +#define PLUGIN_LOADMULTIPLE "LoadMultiple" +#define PLUGIN_VERSION "Version" +#define PLUGIN_FILESYSTEMSHORT "FileShort" +#define PLUGIN_FILESYSTEMPROTOCOL "FileProtocol" +#define PLUGIN_FILEMIMETYPES "FileMime" +#define PLUGIN_FILEEXTENSIONS "FileExtensions" +#define PLUGIN_FILEOPENNAMES "FileOpenNames" +#define PLUGIN_RENDERER_MIME "RendererMime" +#define PLUGIN_RENDERER_GRANULARITY "Renderer_Granularity" +#define PLUGIN_DEPACKER_MIME "DepackerMime" +#define PLUGIN_REVERTER_MIME "ReverterMime" +#define PLUGIN_BROADCASTTYPE "BroadcastType" +#define PLUGIN_STREAMDESCRIPTION "StreamDescription" + +// +#define PLUGIN_GUID_RESPONSE "MainGuid" +#define PLUGIN_FACTORY_GUIDS "" // These are comma delimited. + +// + +#define PLUGIN_NUM_PLUGINS "NumPlugins" +#define PLUGIN_FILE_CHECKSUM "DLLCheckSum" +#define PLUGIN_DLL_SIZE "DLLSize" +#define PLUGIN_HAS_FACTORY "DLLHasFactory" + +/**************************************************************************** + * + * Function: + * + * HXCreateInstance() + * + * Purpose: + * + * Function implemented by all plugin DLL's to create an instance of + * any of the objects supported by the DLL. This method is similar to + * Window's CoCreateInstance() in its purpose, except that it only + * creates objects from this plugin DLL. + * + * NOTE: Aggregation is never used. Therefore an outer unknown is + * not passed to this function, and you do not need to code for this + * situation. + * + */ +#ifdef _MAC_CFM +#pragma export on +#endif + +STDAPI HXCreateInstance + ( + IUnknown** /*OUT*/ ppIUnknown + ); + +#ifdef _MAC_CFM +#pragma export off +#endif + + +/**************************************************************************** + * + * Function: + * + * HXShutdown() + * + * Purpose: + * + * Function implemented by all plugin DLL's to free any *global* + * resources. This method is called just before the DLL is unloaded. + * + */ +#ifdef _MAC_CFM +#pragma export on +#endif + +STDAPI HXShutdown(void); + +#ifdef _MAC_CFM +#pragma export off +#endif + + +/**************************************************************************** + * + * Interface: + * + * IHXPlugin + * + * Purpose: + * + * Interface exposed by a plugin DLL to allow inspection of objects + * supported by the plugin DLL. + * + * IID_IHXPlugin: + * + * {00000C00-0901-11d1-8B06-00A024406D59} + * + */ + +DEFINE_GUID(IID_IHXPlugin, 0x00000C00, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXPlugin + +DECLARE_INTERFACE_(IHXPlugin, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXPlugin methods + */ + + /************************************************************************ + * Method: + * IHXPlugin::GetPluginInfo + * Purpose: + * Returns the basic information about this plugin. Including: + * + * bMultipleLoad Whether or not this plugin can be instantiated + * multiple times. All File Formats must set + * this value to TRUE. The only other type of + * plugin that can specify bMultipleLoad=TRUE is + * a filesystem plugin. Any plugin that sets + * this flag to TRUE must not use global variables + * of any type. + * + * Setting this flag to TRUE implies that you + * accept that your plugin may be instantiated + * multiple times (possibly in different + * address spaces). Plugins are instantiated + * multiple times only in the server (for + * performance reasons). + * + * An example of a plugin, that must set this + * flag to FALSE is a filesystem plugin that + * uses a single TCP connection to communicate + * with a database. + * + * pDescription which is used in about UIs (can be NULL) + * pCopyright which is used in about UIs (can be NULL) + * pMoreInfoURL which is used in about UIs (can be NULL) + * ulVersionNumber The version of this plugin. + */ + STDMETHOD(GetPluginInfo) (THIS_ + REF(HXBOOL) /*OUT*/ bMultipleLoad, + REF(const char*) /*OUT*/ pDescription, + REF(const char*) /*OUT*/ pCopyright, + REF(const char*) /*OUT*/ pMoreInfoURL, + REF(ULONG32) /*OUT*/ ulVersionNumber) PURE; + + /************************************************************************ + * Method: + * IHXPlugin::InitPlugin + * Purpose: + * Initializes the plugin for use. This interface must always be + * called before any other method is called. This is primarily needed + * so that the plugin can have access to the context for creation of + * IHXBuffers and IMalloc. + */ + STDMETHOD(InitPlugin) (THIS_ + IUnknown* /*IN*/ pContext) PURE; + +}; + + +/**************************************************************************** + * + * Interface: + * + * IHXPluginEnumerator + * + * Purpose: + * + * provide methods to enumerate through all the plugins installed + * + * IID_IHXPluginEnumerator: + * + * {00000C01-0901-11d1-8B06-00A024406D59} + * + */ + +DEFINE_GUID(IID_IHXPluginEnumerator, 0x00000C01, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXPluginEnumerator + +DECLARE_INTERFACE_(IHXPluginEnumerator, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXPluginEnumerator methods + */ + + /************************************************************************ + * Method: + * IHXPluginEnumerator::GetNumOfPlugins + * + * Purpose: + * return the number of plugins available + * + */ + STDMETHOD_(ULONG32,GetNumOfPlugins) (THIS) PURE; + + /************************************************************************ + * Method: + * IHXPluginEnumerator::GetPlugin + * Purpose: + * Return an instance (IUnknown) of the plugin + * + */ + STDMETHOD(GetPlugin) (THIS_ + ULONG32 /*IN*/ ulIndex, + REF(IUnknown*) /*OUT*/ pPlugin) PURE; + +}; + +/**************************************************************************** + * + * Interface: + * + * IHXPluginGroupEnumerator + * + * Purpose: + * + * Provide a way to enumerate through all of the plugins which + * implement a specific interface. + * + * IID_IHXPluginGroupEnumerator: + * + * {00000C02-0901-11d1-8B06-00A024406D59} + * + */ + +DEFINE_GUID(IID_IHXPluginGroupEnumerator, 0x00000C02, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXPluginGroupEnumerator + +#define CLSID_IHXPluginGroupEnumerator IID_IHXPluginGroupEnumerator + +DECLARE_INTERFACE_(IHXPluginGroupEnumerator, IUnknown) +{ + /* + * IUnknown methods + */ + + /* + * IHXPluginGroupEnumerator methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /****************************************************************** + * Method: + * IHXPluginGroupEnumerator::Init + * + * Purpose: + * tell the group enumerator which interface to group the plugins + * into, this method must be called before the other methods can + * be called. + * + */ + STDMETHOD(Init) (THIS_ + REFIID iid) PURE; + + + /****************************************************************** + * Method: + * IHXPluginGroupEnumerator::GetNumOfPlugins + * + * Purpose: + * return the number of plugins available that support a + * particular interface. + * + */ + STDMETHOD_(ULONG32,GetNumOfPlugins) (THIS) PURE; + + + /****************************************************************** + * Method: + * IHXPluginGroupEnumerator::GetPlugin + * Purpose: + * Return an instance (IUnknown) of the plugin + * + */ + STDMETHOD(GetPlugin) (THIS_ + UINT32 /*IN*/ ulIndex, + REF(IUnknown*) /*OUT*/ pPlugin) PURE; + +}; + + + +// $Private: +/**************************************************************************** + * + * Interface: + * + * IHXPluginSearchEnumerator + * + * Purpose: + * Walk through the result set of a plugin search + * + * {3244B391-42D4-11d4-9503-00902790299C} + * + */ + +DEFINE_GUID( IID_IHXPluginSearchEnumerator, + 0x3244b391, 0x42d4, 0x11d4, 0x95, 0x3, 0x0, 0x90, 0x27, 0x90, 0x29, 0x9c); + +#undef INTERFACE +#define INTERFACE IHXPluginSearchEnumerator + +DECLARE_INTERFACE_( IHXPluginSearchEnumerator, IUnknown ) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface)(THIS_ REFIID riid, void** ppvObj) PURE; + STDMETHOD_(ULONG32,AddRef)(THIS) PURE; + STDMETHOD_(ULONG32,Release)(THIS) PURE; + + + /* + * IHXPluginSearchEnumerator methods + */ + + /************************************************************************ + * Method: + * IHXPluginSearchEnumerator::GetNumPlugins + * + * Purpose: + * Returns numbers of plugins found during search + * + */ + STDMETHOD_( UINT32, GetNumPlugins)(THIS) PURE; + + /************************************************************************ + * Method: + * IHXPluginSearchEnumerator::GoHead + * + * Purpose: + * Moves the iterator to the beginning of the collection + * + */ + STDMETHOD_(void, GoHead)(THIS) PURE; + + /************************************************************************ + * Method: + * IHXPluginSearchEnumerator::GetNextPlugin + * + * Purpose: + * Returns an instance of the next plugin in the collection + * + */ + STDMETHOD(GetNextPlugin)( THIS_ REF(IUnknown*) pIUnkResult, + IUnknown* pIUnkOuter ) PURE; + + /************************************************************************ + * Method: + * IHXPluginSearchEnumerator::GetNextPluginInfo + * + * Purpose: + * Gets information about the next plugin in the list + * + */ + STDMETHOD(GetNextPluginInfo)( THIS_ REF(IHXValues*) pRetValues ) PURE; + + + /************************************************************************ + * Method: + * IHXPluginSearchEnumerator::GetPluginAt + * + * Purpose: + * Returns an instance of a plugin at a specific index in the list + * + */ + STDMETHOD(GetPluginAt)( THIS_ UINT32 index, + REF(IUnknown*) pIUnkResult, + IUnknown* pIUnkOuter ) PURE; + + /************************************************************************ + * Method: + * IHXPluginSearchEnumerator:: + * + * Purpose: + * Returns information about a plugin at a specific index in the list + * + */ + STDMETHOD(GetPluginInfoAt)( THIS_ UINT32 index, + REF(IHXValues*) pRetValues ) PURE; + +}; +// $EndPrivate. + + +/**************************************************************************** + * + * Interface: + * + * IHXPluginReloader + * + * Purpose: + * + * Tells the client core to reload all plugins. + * + * IID_IHXPluginReloader: + * + * {00000C03-0901-11d1-8B06-00A024406D59} + * + */ + +DEFINE_GUID(IID_IHXPluginReloader, 0x00000C03, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXPluginReloader + +DECLARE_INTERFACE_(IHXPluginReloader, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXPluginReloader methods + */ + + /************************************************************************ + * Method: + * IHXPluginReloader::ReloadPlugins + * Purpose: + * Causes the client core to reload all plugins. + * + */ + STDMETHOD(ReloadPlugins) (THIS) PURE; +}; + + + +/**************************************************************************** + * + * Interface: + * + * IHXPluginFactory + * + * Purpose: + * + * This interface is implemented by a plugin in order to have more then + * one "RMA plugin" in a single DLL. I.e., a plugin author could + * use this interface to have 3 different file format plugins in + * a single DLL. + * + * IID_IHXPluginFactory: + * + * {00000C04-0901-11d1-8B06-00A024406D59} + * + */ + +DEFINE_GUID(IID_IHXPluginFactory, 0x00000C04, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXPluginFactory + +DECLARE_INTERFACE_(IHXPluginFactory, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXPluginFactory methods + */ + + /***************************************************************** + * Method: + * IHXPluginFactory::GetNumPlugins + * Purpose: + * Report the number of Plugins within the DLL. + * + * Parameters: + */ + STDMETHOD_(UINT16, GetNumPlugins) (THIS) PURE; + + /***************************************************************** + * Method: + * IHXPluginFactory::GetPlugin + * Purpose: + * Returns an IUnknown interface to the requested plugin. + * + * Parameters: + */ + + STDMETHOD(GetPlugin) (THIS_ + UINT16 uIndex, + IUnknown** pPlugin) PURE; +}; + +// $Private: +/**************************************************************************** + * + * Interface: + * + * IHXPluginChallenger + * + * Purpose: + * + * This interface is implemented by a plugin in order to allow + * verification of a plugin's authenticity, by issuing a challenge + * and receiving a challenge response. + * + * IID_IHXPluginChallenger: + * + * {00000C05-0901-11d1-8B06-00A024406D59} + * + */ + +DEFINE_GUID(IID_IHXPluginChallenger, 0x00000C05, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXPluginChallenger + +typedef struct _HXTimeval HXTimeval; + +DECLARE_INTERFACE_(IHXPluginChallenger, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXPluginChallenger methods + */ + + /***************************************************************** + * Method: + * IHXPluginChallenger::Challenge + * Purpose: + * Challenge the plugin's authenticity. Returns a challenge + * response which the caller can use to verify that the + * plugin is authentic. + * + * Parameters: + * tVal A time value which may be used to create the + * challenge response. + */ + STDMETHOD(Challenge) (THIS_ + HXTimeval /*IN*/ tVal, + REF(IHXBuffer*) /*OUT*/ pResponse) PURE; +}; + + +/**************************************************************************** + * + * Interface: + * + * IHXPluginQuery + * + * Purpose: + * + * Queries the plugin handler for information on plugins. + * + * IID_IHXPluginQuery: + * + * {00000C06-0901-11d1-8B06-00A024406D59} + * + */ + +#define PLUGIN_FILE_PATH "PluginFilePath" + +#define PLUGIN_PATH "PlgPath" +#define PLUGIN_DESCRIPTION "PlgDesc" +#define PLUGIN_COPYRIGHT "PlgCopy" +#define PLUGIN_MOREINFO "PlgMore" +#define PLUGIN_MIMETYPES "PlgMime" +#define PLUGIN_EXTENSIONS "PlgExt" +#define PLUGIN_OPENNAME "PlgOpen" +#define PLUGIN_MULTIPLE "PlgMult" + +DEFINE_GUID(IID_IHXPluginQuery, 0x00000C06, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXPluginQuery + +DECLARE_INTERFACE_(IHXPluginQuery, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXPluginQuery methods + */ + + /************************************************************************ + * Method: + * IHXPluginQuery::GetNumPluginsGivenGroup + * + * Purpose: + * Gets the number of plugins associated with a particular class id. + * + */ + STDMETHOD(GetNumPluginsGivenGroup) (THIS_ REFIID riid, + REF(UINT32) /*OUT*/ unNumPlugins) PURE; + + /************************************************************************ + * Method: + * IHXPluginQuery::GetPluginInfo + * + * Purpose: + * Gets the info of a particular plugin. + * + */ + STDMETHOD(GetPluginInfo) (THIS_ REFIID riid, + UINT32 unIndex, REF(IHXValues*) /*OUT*/ Values) PURE; +}; +// $EndPrivate. + + +/**************************************************************************** + * + * Interface: + * + * IHXGenericPlugin + * + * Purpose: + * + * Interface exposed by a plugin DLL to inform the client / server core + * that your plugin wishes to have InitPlugin called immediately. + * + * IID_IHXGenericPlugin: + * + * {00000C09-0901-11d1-8B06-00A024406D59} + * + */ + +DEFINE_GUID(IID_IHXGenericPlugin, 0x00000C09, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXGenericPlugin + +DECLARE_INTERFACE_(IHXGenericPlugin, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXGenericPlugin methods + */ + + STDMETHOD(IsGeneric) (THIS_ + REF(HXBOOL) /*OUT*/ bIsGeneric) PURE; +}; + + +DEFINE_GUID(IID_IHXPluginHandler, 0x00000200, 0xb4c8, 0x11d0, 0x99, 0x95, 0x0, 0xa0, 0x24, 0x8d, 0xa5, 0xf0); + +DEFINE_GUID(IID_IHXPlugin2Handler, 0x00000201, 0xb4c8, 0x11d0, 0x99, 0x95, 0x0, 0xa0, 0x24, 0x8d, 0xa5, 0xf0); + +#undef INTERFACE +#define INTERFACE IHXPlugin2Handler + +DECLARE_INTERFACE_(IHXPlugin2Handler, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXPlugin2Handler Methods + */ + + /************************************************************************ + * Method: + * IHXPlugin2Handler::Init + * + * Purpose: + * Specifies the context and sets the pluginhandler in motion. + * + */ + STDMETHOD(Init) (THIS_ IUnknown* pContext) PURE; + + /************************************************************************ + * Method: + * IHXPlugin2Handler::GetNumPlugins2 + * + * Purpose: + * Gets the info of a particular plugin. + * + */ + STDMETHOD_(ULONG32,GetNumOfPlugins2) (THIS) PURE; + + /************************************************************************ + * Method: + * IHXPlugin2Handler::GetPluginInfo + * + * Purpose: + * Gets the info of a particular plugin. + * + */ + STDMETHOD(GetPluginInfo) (THIS_ + UINT32 unIndex, + REF(IHXValues*) /*OUT*/ Values) PURE; + + /************************************************************************ + * Method: + * IHXPlugin2Handler::FlushCache() + * + * Purpose: + * Flushes the LRU cache -- Unloads all DLLs from memory + * which currenltly have a refcount of 0. + */ + + STDMETHOD(FlushCache) (THIS) PURE; + + /************************************************************************ + * Method: + * IHXPlugin2Handler::SetCacheSize + * + * Purpose: + * This function sets the size of the Cache. The cache is + * initally set to 1000KB. To disable the cache simply set + * the size to 0.If the cache is disabled a DLL will be + * unloaded whenever it's refcount becomes zero. Which MAY + * cause performance problems. + */ + + STDMETHOD(SetCacheSize) (THIS_ ULONG32 nSizeKB) PURE; + + /************************************************************************ + * Method: + * IHXPlugin2Handler::GetInstance + * + * Purpose: + * + * This function will return a plugin instance given a plugin index. + * + */ + + STDMETHOD(GetInstance) (THIS_ UINT32 index, REF(IUnknown*) pUnknown) PURE; + + /************************************************************************ + * Method: + * IHXPlugin2Handler::FindIndexUsingValues + * + * Purpose: + * Finds a plugin which matches the set of values given. An index + * is returned which can be used to either get the values (using + * GetPluginInfo) or an instance can be created using GetPluing(). + * + */ + + STDMETHOD(FindIndexUsingValues) (THIS_ IHXValues*, + REF(UINT32) unIndex) PURE; + + /************************************************************************ + * Method: + * IHXPlugin2Handler::FindPluginUsingValues + * + * Purpose: + * Finds a plugin which matches the set of values given. A Plugin + * instance is returned. + * + */ + + STDMETHOD(FindPluginUsingValues) (THIS_ IHXValues*, + REF(IUnknown*) pUnk) PURE; + + /************************************************************************ + * Method: + * IHXPlugin2Handler::FindIndexUsingStrings + * + * Purpose: + * Finds a plugin which matches the set of values given. An index + * is returned which can be used to either get the values (using + * GetPluginInfo) or an instance can be created using GetPluing(). + * NOTE: that a max of two values may be given. + */ + + STDMETHOD(FindIndexUsingStrings) (THIS_ char* PropName1, + char* PropVal1, + char* PropName2, + char* PropVal2, + char* PropName3, + char* PropVal3, + REF(UINT32) unIndex) PURE; + + /************************************************************************ + * Method: + * IHXPlugin2Handler::FindPluginUsingStrings + * + * Purpose: + * Finds a plugin which matches the set of values given. A Plugin + * instance is returned. + * NOTE: that a max of two values may be given. + */ + + STDMETHOD(FindPluginUsingStrings) (THIS_ char* PropName1, + char* PropVal1, + char* PropName2, + char* PropVal2, + char* PropName3, + char* PropVal3, + REF(IUnknown*) pUnk) PURE; + + /************************************************************************ + * Method: + * IHXPlugin2Handler::FindImplementationFromClassID + * + * Purpose: + * Finds a CommonClassFactory plugin which supports the + * ClassID given. An instance of the Class is returned. + */ + + STDMETHOD(FindImplementationFromClassID) + ( + THIS_ + REFGUID GUIDClassID, + REF(IUnknown*) pIUnknownInstance + ) PURE; + + /************************************************************************ + * Method: + * IHXPlugin2Handler::Close + * + * Purpose: + * A function which performs all of the functions of delete. + * + * + */ + + STDMETHOD(Close) (THIS) PURE; + + /************************************************************************ + * Method: + * IHXPlugin2Handler::SetRequiredPlugins + * + * Purpose: + * This function sets the required plugin list + * + * + */ + + STDMETHOD(SetRequiredPlugins) (THIS_ const char** ppszRequiredPlugins) PURE; + + +}; + + +// $Private: +/**************************************************************************** + * + * Interface: + * + * IHXPluginHandler3 + * + * Purpose: + * + * Extensions to the IHXPlugin2Handler so we can interact with the + * Gemini Object Broker + * + * IID_IHXPluginHandler3: + * + * {32B19771-2299-11d4-9503-00902790299C} + * + */ +DEFINE_GUID( IID_IHXPluginHandler3, 0x32b19771, 0x2299, 0x11d4, 0x95, 0x3, 0x0, 0x90, 0x27, 0x90, 0x29, 0x9c); + +#undef INTERFACE +#define INTERFACE IHXPluginHandler3 + +DECLARE_INTERFACE_(IHXPluginHandler3, IUnknown) +{ + /************************************************************************ + * Method: + * IHXPluginHandler3::RegisterContext + * + * Purpose: + * Sets up the context without loading any plugin info + * + */ + STDMETHOD( RegisterContext )( THIS_ IUnknown* pContext ) PURE; + + /************************************************************************ + * Method: + * IHXPluginHandler3::AddPluginMountPoint + * + * Purpose: + * Sets up the plugins stored in this preferences object + * + */ + STDMETHOD( AddPluginMountPoint )( THIS_ const char* pName, UINT32 majorVersion, + UINT32 minorVersion, IHXBuffer* pPath ) PURE; + + + /************************************************************************ + * Method: + * IHXPluginHandler3::RefreshPluginMountPoint + * + * Purpose: + * Refreshes plugin information associated with this + * preferences object + */ + STDMETHOD( RefreshPluginMountPoint )( THIS_ const char* pName ) PURE; + + + /************************************************************************ + * Method: + * IHXPluginHandler3::RemovePluginMountPoint + * + * Purpose: + * Removes plugins associated with this preferences object + */ + STDMETHOD( RemovePluginMountPoint )( THIS_ const char* pName ) PURE; + + + /************************************************************************ + * Method: + * IHXPluginHandler3::FindImplementationFromClassID + * + * Purpose: + * Finds a CommonClassFactory plugin which supports the + * ClassID given. An instance of the Class is returned. + * The plugin instance is initialized with the specified + * context + */ + + STDMETHOD( FindImplementationFromClassID )( THIS_ REFGUID GUIDClassID, + REF(IUnknown*) pIUnknownInstance, IUnknown* pIUnkOuter, IUnknown* pContext ) PURE; + + + /************************************************************************ + * Method: + * IHXPluginHandler3::FindCLSIDFromName + * + * Purpose: + * + * Maps a text name to a CLSID based on information from + * component plugins + */ + STDMETHOD( FindCLSIDFromName )( THIS_ const char* pName, REF(IHXBuffer*) pCLSID ) PURE; + + + /************************************************************************ + * Method: + * IHXPluginHandler3::FindGroupOfPluginsUsingValues + * + * Purpose: + * Builds a collection of plugins that match the criteria + * + */ + STDMETHOD(FindGroupOfPluginsUsingValues)(THIS_ IHXValues* pValues, + REF(IHXPluginSearchEnumerator*) pIEnumerator) PURE; + + /************************************************************************ + * Method: + * IHXPluginHandler3::FindGroupOfPluginsUsingStrings + * + * Purpose: + * Builds a collection of plugins that match the criteria + * + */ + STDMETHOD(FindGroupOfPluginsUsingStrings)(THIS_ char* PropName1, + char* PropVal1, + char* PropName2, + char* PropVal2, + char* PropName3, + char* PropVal3, + REF(IHXPluginSearchEnumerator*) pIEnumerator) PURE; + + + /************************************************************************ + * Method: + * IHXPluginHandler3::GetPlugin + * + * Purpose: + * Allocates a plugin based on index. Supports aggregation + * + */ + STDMETHOD(GetPlugin)(THIS_ ULONG32 ulIndex, + REF(IUnknown*) pIUnkResult, + IUnknown* pIUnkOuter ) PURE; + + /************************************************************************ + * Method: + * IHXPluginHandler3::FindPluginUsingValues + * + * Purpose: + * Allocates a plugin based on criteria. Supports aggregation + * + */ + STDMETHOD(FindPluginUsingValues)(THIS_ IHXValues*, + REF(IUnknown*) pIUnkResult, + IUnknown* pIUnkOuter ) PURE; + + + /************************************************************************ + * Method: + * IHXPluginHandler3::FindPluginUsingStrings + * + * Purpose: + * Allocates a plugin based on criteria. Supports aggregation + * + */ + STDMETHOD(FindPluginUsingStrings)(THIS_ char* PropName1, + char* PropVal1, + char* PropName2, + char* PropVal2, + char* PropName3, + char* PropVal3, + REF(IUnknown*) pIUnkResult, + IUnknown* pIUnkOuter ) PURE; + + /************************************************************************ + * Method: + * IHXPluginHandler3::UnloadPluginFromClassID + * + * Purpose: + * Finds a plugin from the classID and unloads it if it supports CanUnload2 + * and returns TRUE in response to query + */ + + STDMETHOD( UnloadPluginFromClassID )( THIS_ REFGUID GUIDClassID ) PURE; + + /************************************************************************ + * Method: + * IHXPluginHandler3::UnloadPackageByName + * + * Purpose: + * finds a package from the name passed in and attempts to unload it. + */ + STDMETHOD (UnloadPackageByName) (char const* pName) PURE; + +}; +// $EndPrivate. + +// $Private: +/**************************************************************************** + * + * Interface: + * + * IHXPluginDatabase + * + * Purpose: + * + * Extensions to the plugin handler for optimized searching + * on a single key + * + * IID_IHXPluginDatabase: + * + * {C2C65401-A478-11d4-9518-00902790299C} + * + */ + +enum EPluginIndexType +{ + kIndex_StringType, + kIndex_BufferType, + kIndex_GUIDType, + kIndex_MVStringType, + + kIndex_NumTypes +}; + +DEFINE_GUID( IID_IHXPluginDatabase, 0xc2c65401, 0xa478, 0x11d4, 0x95, 0x18, 0x0, 0x90, 0x27, 0x90, 0x29, 0x9c); + +#undef INTERFACE +#define INTERFACE IHXPluginDatabase + +DECLARE_INTERFACE_(IHXPluginDatabase, IUnknown) +{ + /************************************************************************ + * Method: + * IHXPluginDatabase::AddPluginIndex + * + * Purpose: + * Create a new index in the plugin-handler + * + */ + STDMETHOD( AddPluginIndex ) ( THIS_ const char* pKeyName, EPluginIndexType indexType, HXBOOL bScanExisting ) PURE; + + /************************************************************************ + * Method: + * IHXPluginDatabase::RemovePluginIndex + * + * Purpose: + * Remove an index from the plugin handler + * + */ + STDMETHOD( RemovePluginIndex )( THIS_ const char* pKeyName ) PURE; + + + /************************************************************************ + * Method: + * IHXPluginDatabase::FindPluginInfoViaIndex + * + * Purpose: + * Look up a plugin's info based on a single attribute. Use an index if + * possible, otherwise defer to a linear search + * + */ + STDMETHOD( FindPluginInfoViaIndex )( THIS_ const char* pKeyName, const void* pValue, IHXValues** ppIInfo ) PURE; + + /************************************************************************ + * Method: + * IHXPluginDatabase::FindPluginSetViaIndex + * + * Purpose: + * // XXXND Should this take pValue? Should it just return a list from an index? + * Find a set of plugins matching a single attribute + * + */ + STDMETHOD( FindPluginSetViaIndex )( THIS_ const char* pKeyName, const void* pValue, IHXPluginSearchEnumerator** ppIEnumerator ) PURE; + + /************************************************************************ + * Method: + * IHXPluginDatabase::FindPluginViaIndex + * + * Purpose: + * Create a plugin based on a simple attribute. + * + */ + STDMETHOD( CreatePluginViaIndex )( THIS_ const char* pKeyName, const void* pValue, IUnknown** ppIUnkPlugin, IUnknown* pIUnkOuter ) PURE; +}; + +// $EndPrivate. + + + +// $Private: +/**************************************************************************** + * + * Interface: + * + * IHXProxiedPugin + * + * Purpose: + * + * Provides The IHXPlugin actually being used. + * + * IID_IHXProxiedPlugin: + * + * {00000C10-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXProxiedPlugin, 0x00000C0A, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXProxiedPlugin + +DECLARE_INTERFACE_(IHXProxiedPlugin, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXProxiedPlugin methods + */ + + /************************************************************************ + * Method: + * IHXProxiedPlugin::GetProxiedPlugin + * Purpose: + * Gets the Actual Plugin being used... + */ + STDMETHOD(GetProxiedPlugin)(THIS_ + REF(IHXPlugin*) /*OUT*/ pPlugin) PURE; + +}; +// $EndPrivate. + + +// $Private: +/**************************************************************************** + * + * Component plugin property names + * + * These attribute names are standard. + * + * ComponentCLSID maps to an IHXBuffer value that contains the + * binary CLSID for the component + * + * ComponentName maps to a string value that contains the tag or + * actor name for the component + * + */ +#define PLUGIN_COMPONENT_CLSID "ComponentCLSID" +#define PLUGIN_COMPONENT_NAME "ComponentName" + + +/**************************************************************************** + * + * Interface: + * + * IHXComponentPlugin + * + * Purpose: + * + * Allows the plugin handler to iterator over multiple plugins in a DLL + * + * {F8A31571-22AC-11d4-9503-00902790299C} + * + */ +DEFINE_GUID( IID_IHXComponentPlugin, 0xf8a31571, 0x22ac, 0x11d4, 0x95, 0x3, 0x0, 0x90, 0x27, 0x90, 0x29, 0x9c); + +#undef INTERFACE +#define INTERFACE IHXComponentPlugin + +DECLARE_INTERFACE_(IHXComponentPlugin, IUnknown) +{ + /* + * IHXComponentPlugin methods + */ + + /************************************************************************ + * Method: + * IHXComponentPlugin::GetNumberComponents + * Purpose: + */ + STDMETHOD_(UINT32, GetNumComponents)(THIS) PURE; + + /************************************************************************ + * Method: + * IHXComponentPlugin::GetPackageName + * Purpose: + */ + STDMETHOD_(char const*, GetPackageName)(THIS) CONSTMETHOD PURE; + + /************************************************************************ + * Method: + * IHXComponentPlugin::GetComponentInfoAtIndex + * Purpose: + */ + STDMETHOD(GetComponentInfoAtIndex) (THIS_ + UINT32 /*IN*/ nIndex, + REF(IHXValues*) /*OUT*/ pInfo) PURE; + + /************************************************************************ + * Method: + * IHXComponentPlugin::CreateComponentInstance + * Purpose: + */ + STDMETHOD(CreateComponentInstance)(THIS_ + REFCLSID /*IN*/ rclsid, + REF(IUnknown*) /*OUT*/ ppUnknown, + IUnknown* /*IN*/ pUnkOuter) PURE; +}; + +/**************************************************************************** + * + * Interface: + * + * IHXPluginNamespace + * + * Purpose: + * + * Allows the plugin handler to retrieve a plugin's namespace + * + * {F09E8891-8E2D-11d4-82DB-00D0B74C2D25} + * + */ +DEFINE_GUID(IID_IHXPluginNamespace, 0xf09e8891, 0x8e2d, 0x11d4, 0x82, 0xdb, 0x0, 0xd0, 0xb7, 0x4c, 0x2d, 0x25); + +#undef INTERFACE +#define INTERFACE IHXPluginNamespace + +DECLARE_INTERFACE_(IHXPluginNamespace, IUnknown) +{ + /************************************************************************ + * Method: + * IHXPluginNamespace::GetPluginNamespace + * Purpose: + */ + STDMETHOD(GetPluginNamespace) (THIS_ + REF(IHXBuffer*) /*OUT*/ pBuffer) PURE; + +}; +// $EndPrivate. + +#endif /* _HXPLUGN_H_ */ diff --git a/amarok/src/engine/helix/helix-sp/helix-include/common/include/hxplugncompat.h b/amarok/src/engine/helix/helix-sp/helix-include/common/include/hxplugncompat.h new file mode 100644 index 00000000..a4d1244e --- /dev/null +++ b/amarok/src/engine/helix/helix-sp/helix-include/common/include/hxplugncompat.h @@ -0,0 +1,38 @@ + +/* + * + * This software is released under the provisions of the GPL version 2. + * see file "COPYING". If that file is not available, the full statement + * of the license can be found at + * + * http://www.fsf.org/licensing/licenses/gpl.txt + * + * Portions Copyright (c) 1995-2004 RealNetworks, Inc. All Rights Reserved. + * + */ + +#ifndef _HXPLUGNCOMPAT_H_ +#define _HXPLUGNCOMPAT_H_ + +// In order to maintain interoperability with existing software that uses +// plugins, redefine these symbols to their old names. + +#ifndef HXCreateInstance +#define HXCreateInstance RMACreateInstance +#endif /* HXCreateInstance */ + +#ifndef HXCREATEINSTANCE +#define HXCREATEINSTANCESTR "RMACreateInstance" +#define HXCREATEINSTANCE RMACreateInstance +#endif /* HXCREATEINSTANCE */ + +#ifndef HXShutdown +#define HXShutdown RMAShutdown +#endif /* HXShutdown */ + +#ifndef HXSHUTDOWN +#define HXSHUTDOWNSTR "RMAShutdown" +#define HXSHUTDOWN RMAShutdown +#endif /* HXSHUTDOWN */ + +#endif // _HXPLUGNCOMPAT_H_ diff --git a/amarok/src/engine/helix/helix-sp/helix-include/common/include/hxprefs.h b/amarok/src/engine/helix/helix-sp/helix-include/common/include/hxprefs.h new file mode 100644 index 00000000..4750c4c3 --- /dev/null +++ b/amarok/src/engine/helix/helix-sp/helix-include/common/include/hxprefs.h @@ -0,0 +1,348 @@ + +/* + * + * This software is released under the provisions of the GPL version 2. + * see file "COPYING". If that file is not available, the full statement + * of the license can be found at + * + * http://www.fsf.org/licensing/licenses/gpl.txt + * + * Portions Copyright (c) 1995-2004 RealNetworks, Inc. All Rights Reserved. + * + */ + +/**************************************************************************** + * + * + * Persistent Preferences Interfaces + * + * Here are the preference entries set by the client core and renderers: + * KEY DEFAULT VALUES + * ================= ==================== + * AttemptMulticast 1 + * AttemptTCP 1 + * AttemptUDP 1 + * AudioQuality 0 + * AutoTransport 1 + * Bandwidth 28800 + * BitsPerSample 16 + * BroadcastPluginInfo {dllpath;description;copyright;moreinfo;loadmultiple;type}{ ... } + * ClientLicenseKey 7FF7FF00 + * EndScan 10000 + * FactoryPluginInfo + * FileFormatPluginInfo {dllpath;description;copyright;moreinfo;loadmultiple;mimetype1|mimetype2;extension1|extension2}{ ... } + * FileSystemPluginInfo {dllpath;description;copyright;moreinfo;loadmultiple;protocol;shortname}{ ... } + * GeneralPluginInfo {dllpath;description;copyright;moreinfo;loadmultiple}{ ... } + * PNAProxyHost + * PNAProxyPort 1090 + * RTSPProxyHost + * RTSPProxyPort 554 + * HTTPProxyHost + * HTTPProxyPort 1092 + * HurledURL 0 + * InfoandVolume 1 + * LastURL + * MaxClipCount 4 + * MetaFormatPluginInfo {dllpath;description;copyright;moreinfo;loadmultiple;mimetype1|mimetype2;extension1|extension2}{ ... } + * MiscPluginInfo {dllpath;description;copyright;moreinfo;loadmultiple}{ ... } + * MulticastTimeout 2000 + * NotProxy + * OnTop 0 + * PerfectPlayMode 0 + * PerfectPlayTime 60 + * PerfPlayEntireClip 1 + * PluginDirectory + * Presets# + * ProxySupport 0 + * RendererPluginInfo {dllpath;description;copyright;moreinfo;loadmultiple;mimetype1|mimetype2}{ ... } + * SamplingRate 8000 + * SeekPage 40 + * SendStatistics 1 + * ServerTimeOut 90 + * ShowPresets 0 + * StatusBar 1 + * StreamDescriptionPluginInfo {dllpath;description;copyright;moreinfo;loadmultiple;mimetype}{ ... } + * SyncMultimedia 1 + * UDPPort 7070 + * UDPTimeout 10000 + * UpgradeAvailable 0 + * UseUDPPort 0 + * Volume 50 + * x:Pref_windowPositionX + * y:Pref_WindowPositionY + */ + +#ifndef _HXPREFS_H_ +#define _HXPREFS_H_ + +#define HXREGISTRY_PREFPROPNAME "ApplicationData" +/* + * Forward declarations of some interfaces defined or used here-in. + */ +typedef _INTERFACE IHXBuffer IHXBuffer; + + +// CLSID for creating a preferences objects via a CCF +// {EC5C2B01-D105-11d4-951F-00902790299C} +#define CLSID_HXPreferences IID_IHXPreferences + +/**************************************************************************** + * + * Interface: + * + * IHXPreferences + * + * Purpose: + * + * This interface allows you to store persistant preferences in the + * server or player's config / registry. + * + * IID_IHXPreferences: + * + * {00000500-0901-11d1-8B06-00A024406D59} + * + */ + +DEFINE_GUID(IID_IHXPreferences, 0x00000500, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXPreferences + +DECLARE_INTERFACE_(IHXPreferences, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXPreferences methods + */ + + /************************************************************************ + * Method: + * IHXPreferences::ReadPref + * Purpose: + * Read a preference from the registry or configuration. + */ + STDMETHOD(ReadPref) (THIS_ + const char* pPrekKey, REF(IHXBuffer*) pBuffer) PURE; + + /************************************************************************ + * Method: + * IHXPreferences::WritePref + * Purpose: + * TBD + */ + STDMETHOD(WritePref) (THIS_ + const char* pPrekKey, IHXBuffer* pBuffer) PURE; +}; + + +/**************************************************************************** + * + * Interface: + * + * IHXPreferenceEnumerator + * + * Purpose: + * + * Allows preference Enumeration + * + * + * IHXPreferenceEnumerator: + * + * {00000504-0901-11d1-8B06-00A024406D59} + * + */ + +DEFINE_GUID(IID_IHXPreferenceEnumerator, 0x00000504, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXPreferenceEnumerator + +DECLARE_INTERFACE_(IHXPreferenceEnumerator, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXPreferenceEnumerator methods + */ + + /************************************************************************ + * Method: + * IHXPreferenceEnumerator::EndSubPref + * Purpose: + * TBD + */ + + STDMETHOD(BeginSubPref) (THIS_ const char* szSubPref) PURE; + + + /************************************************************************ + * Method: + * IHXPreferenceEnumerator::EndSubPref + * Purpose: + * TBD + */ + + STDMETHOD(EndSubPref) (THIS) PURE; + + /************************************************************************ + * Method: + * IHXPreferenceEnumerator::GetPrefKey + * Purpose: + * TBD + */ + + STDMETHOD(GetPrefKey) (THIS_ UINT32 nIndex, REF(IHXBuffer*) pBuffer) PURE; + + /************************************************************************ + * Method: + * IHXPreferenceEnumerator::ReadPref + * Purpose: + * TBD + */ + STDMETHOD(ReadPref) (THIS_ + const char* pPrefKey, IHXBuffer*& pBuffer) PURE; + +}; + + + +/**************************************************************************** + * + * Interface: + * + * IHXPreferences2 + * + * Purpose: + * + * New interface which gives sub-preference options abilities. + * + * + * IID_IHXPreferences2: + * + * {00000503-0901-11d1-8B06-00A024406D59} + * + */ + +DEFINE_GUID(IID_IHXPreferences2, 0x00000503, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXPreferences2 + +DECLARE_INTERFACE_(IHXPreferences2, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXPreferences2 methods + */ + + /************************************************************************ + * Method: + * IHXPreferences2::GetPreferenceEnumerator + * Purpose: + * Read a preference from the registry or configuration. + */ + + STDMETHOD(GetPreferenceEnumerator)(THIS_ REF(IHXPreferenceEnumerator*) /*OUT*/ pEnum) PURE; + + /************************************************************************ + * Method: + * IHXPreferences2::ResetRoot + * Purpose: + * Reset the root of the preferences + */ + + STDMETHOD(ResetRoot)(THIS_ const char* pCompanyName, const char* pProductName, + int nProdMajorVer, int nProdMinorVer) PURE; +}; + + +// $Private: +/**************************************************************************** + * + * Interface: + * + * IHXPreferences3 + * + * Purpose: + * + * New interface for deleting preferences, and whatever we might think of next! + * + * + * IID_IHXPreferences3: + * + * {00000505-0901-11d1-8B06-00A024406D59} + * + */ + +DEFINE_GUID(IID_IHXPreferences3, 0x00000505, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXPreferences3 + +DECLARE_INTERFACE_(IHXPreferences3, IUnknown) +{ + /************************************************************************ + * Method: + * IHXPreferences3::Open + * Purpose: + * Open a specified collection of preferences + */ + + STDMETHOD( Open )(THIS_ const char* pCompanyName, const char* pProductName, + ULONG32 nProdMajorVer, ULONG32 nProdMinorVer) PURE; + + /************************************************************************ + * Method: + * IHXPreferences3::OpenShared + * Purpose: + * Have this preference object read/write from the company wide + * shared location for all products + */ + STDMETHOD( OpenShared )( THIS_ const char* pCompanyName) PURE; + + /************************************************************************ + * Method: + * IHXPreferences3::DeletePref + * Purpose: + * Delete a preference + */ + + STDMETHOD(DeletePref)( THIS_ const char* pPrekKey ) PURE; + +}; +// $EndPrivate. + +#endif /* _HXPREFS_H_ */ diff --git a/amarok/src/engine/helix/helix-sp/helix-include/common/include/hxresult.h b/amarok/src/engine/helix/helix-sp/helix-include/common/include/hxresult.h new file mode 100644 index 00000000..c2697da7 --- /dev/null +++ b/amarok/src/engine/helix/helix-sp/helix-include/common/include/hxresult.h @@ -0,0 +1,583 @@ + +/* + * + * This software is released under the provisions of the GPL version 2. + * see file "COPYING". If that file is not available, the full statement + * of the license can be found at + * + * http://www.fsf.org/licensing/licenses/gpl.txt + * + * Portions Copyright (c) 1995-2004 RealNetworks, Inc. All Rights Reserved. + * + */ + +#ifndef _HXRESULT_H_ +#define _HXRESULT_H_ + +/* Some files include this before pntypes.h. */ +#include "hxtypes.h" + +typedef LONG32 HX_RESULT; + +#ifndef _WIN32 + typedef HX_RESULT HRESULT; +# undef NOERROR +# define NOERROR 0 +# define FACILITY_ITF 4 +# define MAKE_HRESULT(sev,fac,code) \ + ((HRESULT) (((unsigned long)(sev)<<31) | ((unsigned long)(fac)<<16) | \ + ((unsigned long)(code))) ) +# define SUCCEEDED(Status) (((unsigned long)(Status)>>31) == 0) +# define FAILED(Status) (((unsigned long)(Status)>>31) != 0) +#else +# ifndef _HRESULT_DEFINED + typedef LONG32 HRESULT; +# endif /* _HRESULT_DEFINED */ +# include <winerror.h> +#endif /* _WIN32 */ + +#define MAKE_HX_RESULT(sev,fac,code) MAKE_HRESULT(sev, FACILITY_ITF, \ + ((fac << 6) | (code))) + +#define SS_GLO 0 /* General errors */ +#define SS_NET 1 /* Networking errors */ +#define SS_FIL 2 /* File errors */ +#define SS_PRT 3 /* Protocol Error */ +#define SS_AUD 4 /* Audio error */ +#define SS_INT 5 /* General internal errors */ +#define SS_USR 6 /* The user is broken. */ +#define SS_MSC 7 /* Miscellaneous */ +#define SS_DEC 8 /* Decoder errors */ +#define SS_ENC 9 /* Encoder errors */ +#define SS_REG 10 /* Registry (not Windows registry ;) errors */ +#define SS_PPV 11 /* Pay Per View errors */ +#define SS_RSC 12 /* Errors for HXXRES */ +#define SS_UPG 13 /* Auto-upgrade & Certificate Errors */ +#define SS_PLY 14 /* RealPlayer/Plus specific errors (USE ONLY IN /rpmisc/pub/rpresult.h) */ +#define SS_RMT 15 /* RMTools Errors */ +#define SS_CFG 16 /* AutoConfig Errors */ +#define SS_RPX 17 /* RealPix-related Errors */ +#define SS_XML 18 /* XML-related Errors */ +// $Private: +#define SS_TKO 19 /* Taiko specific errors (USE ONLY IN /taiko/tresult.h) */ +#define SS_SEC 20 /* Security (key handling, encrypt,decrypt, CSP,...) errors */ +// $EndPrivate. +#define SS_RCA 21 /* RCA errors */ +#define SS_ENC_AX 22 /* Encoder Active x error */ +#define SS_SOCK 24 /* Socket errors */ +#define SS_RSLV 25 /* Resolver errors */ + +#define SS_DPR 63 /* Deprecated errors */ +#define SS_SAM 100 /* ServerAlert errors */ + + +#define HXR_NOTIMPL MAKE_HRESULT(1,0,0x4001) // 80004001 +#define HXR_OUTOFMEMORY MAKE_HRESULT(1,7,0x000e) // 8007000e +#define HXR_INVALID_PARAMETER MAKE_HRESULT(1,7,0x0057) // 80070057 +#define HXR_NOINTERFACE MAKE_HRESULT(1,0,0x4002) // 80004002 +#define HXR_POINTER MAKE_HRESULT(1,0,0x4003) // 80004003 +#define HXR_HANDLE MAKE_HRESULT(1,7,0x0006) // 80070006 +#define HXR_ABORT MAKE_HRESULT(1,0,0x4004) // 80004004 +#define HXR_FAIL MAKE_HRESULT(1,0,0x4005) // 80004005 +#define HXR_ACCESSDENIED MAKE_HRESULT(1,7,0x0005) // 80070005 +#define HXR_IGNORE MAKE_HRESULT(1,0,0x0006) // 80000006 +#define HXR_OK MAKE_HRESULT(0,0,0) // 00000000 + + +#define HXR_INVALID_OPERATION MAKE_HX_RESULT(1,SS_GLO,4) // 80040004 +#define HXR_INVALID_VERSION MAKE_HX_RESULT(1,SS_GLO,5) // 80040005 +#define HXR_INVALID_REVISION MAKE_HX_RESULT(1,SS_GLO,6) // 80040006 +#define HXR_NOT_INITIALIZED MAKE_HX_RESULT(1,SS_GLO,7) // 80040007 +#define HXR_DOC_MISSING MAKE_HX_RESULT(1,SS_GLO,8) // 80040008 +#define HXR_UNEXPECTED MAKE_HX_RESULT(1,SS_GLO,9) // 80040009 +#define HXR_INCOMPLETE MAKE_HX_RESULT(1,SS_GLO,12) // 8004000c +#define HXR_BUFFERTOOSMALL MAKE_HX_RESULT(1,SS_GLO,13) // 8004000d +#define HXR_UNSUPPORTED_VIDEO MAKE_HX_RESULT(1,SS_GLO,14) // 8004000e +#define HXR_UNSUPPORTED_AUDIO MAKE_HX_RESULT(1,SS_GLO,15) // 8004000f +#define HXR_INVALID_BANDWIDTH MAKE_HX_RESULT(1,SS_GLO,16) // 80040010 +/* HXR_NO_RENDERER and HXR_NO_FILEFORMAT old value is being deprecated +#define HXR_NO_FILEFORMAT MAKE_HX_RESULT(1,SS_GLO,10) +#define HXR_NO_RENDERER MAKE_HX_RESULT(1,SS_GLO,11)*/ +#define HXR_NO_RENDERER MAKE_HX_RESULT(1,SS_GLO,17) // 80040011 +#define HXR_NO_FILEFORMAT MAKE_HX_RESULT(1,SS_GLO,17) // 80040011 +#define HXR_MISSING_COMPONENTS MAKE_HX_RESULT(1,SS_GLO,17) // 80040011 +#define HXR_ELEMENT_NOT_FOUND MAKE_HX_RESULT(0,SS_GLO,18) // 00040012 +#define HXR_NOCLASS MAKE_HX_RESULT(0,SS_GLO,19) // 00040013 +#define HXR_CLASS_NOAGGREGATION MAKE_HX_RESULT(0,SS_GLO,20) // 00040014 +#define HXR_NOT_LICENSED MAKE_HX_RESULT(1,SS_GLO,21) // 80040015 +#define HXR_NO_FILESYSTEM MAKE_HX_RESULT(1,SS_GLO,22) // 80040016 +#define HXR_REQUEST_UPGRADE MAKE_HX_RESULT(1,SS_GLO,23) // 80040017 + +#define HXR_CHECK_RIGHTS MAKE_HX_RESULT(1,SS_GLO,24) // 80040018 +#define HXR_RESTORE_SERVER_DENIED MAKE_HX_RESULT(1,SS_GLO,25) // 80040019 +#define HXR_DEBUGGER_DETECTED MAKE_HX_RESULT(1,SS_GLO,26) // 8004001a +#define HXR_RESTORE_SERVER_CONNECT MAKE_HX_RESULT(1,SS_NET,28) // 8004005c +#define HXR_RESTORE_SERVER_TIMEOUT MAKE_HX_RESULT(1,SS_NET,29) // 8004005d +#define HXR_REVOKE_SERVER_CONNECT MAKE_HX_RESULT(1,SS_NET,30) // 8004005e +#define HXR_REVOKE_SERVER_TIMEOUT MAKE_HX_RESULT(1,SS_NET,31) // 8004005f +#define HXR_VIEW_RIGHTS_NODRM MAKE_HX_RESULT(1,SS_MSC,13) // 800401cd +#define HXR_VSRC_NODRM MAKE_HX_RESULT(1,SS_MSC,19) // 800401d3 +#define HXR_WM_OPL_NOT_SUPPORTED MAKE_HX_RESULT(1,SS_GLO,36) // 80040024 + +// $Private: +/* Status Code for backup/restore*/ +#define HXR_RESTORATION_COMPLETE MAKE_HX_RESULT(1,SS_GLO,27) // 8004001b +#define HXR_BACKUP_COMPLETE MAKE_HX_RESULT(1,SS_GLO,28) // 8004001c +#define HXR_TLC_NOT_CERTIFIED MAKE_HX_RESULT(1,SS_GLO,29) // 8004001d +#define HXR_CORRUPTED_BACKUP_FILE MAKE_HX_RESULT(1,SS_GLO,30) // 8004001e +// $EndPrivate. +#define HXR_AWAITING_LICENSE MAKE_HX_RESULT(1,SS_GLO,31) // 8004001f +#define HXR_ALREADY_INITIALIZED MAKE_HX_RESULT(1,SS_GLO,32) // 80040020 +#define HXR_NOT_SUPPORTED MAKE_HX_RESULT(1,SS_GLO,33) // 80040021 +#define HXR_S_FALSE MAKE_HX_RESULT(0,SS_GLO,34) // 00040022 +#define HXR_WARNING MAKE_HX_RESULT(0,SS_GLO,35) // 00040023 + +#define HXR_BUFFERING MAKE_HX_RESULT(0,SS_NET,0) // 00040040 +#define HXR_PAUSED MAKE_HX_RESULT(0,SS_NET,1) // 00040041 +#define HXR_NO_DATA MAKE_HX_RESULT(0,SS_NET,2) // 00040042 +#define HXR_STREAM_DONE MAKE_HX_RESULT(0,SS_NET,3) // 00040043 +#define HXR_NET_SOCKET_INVALID MAKE_HX_RESULT(1,SS_NET,3) // 80040043 +#define HXR_NET_CONNECT MAKE_HX_RESULT(1,SS_NET,4) // 80040044 +#define HXR_BIND MAKE_HX_RESULT(1,SS_NET,5) // 80040045 +#define HXR_SOCKET_CREATE MAKE_HX_RESULT(1,SS_NET,6) // 80040046 +#define HXR_INVALID_HOST MAKE_HX_RESULT(1,SS_NET,7) // 80040047 +#define HXR_NET_READ MAKE_HX_RESULT(1,SS_NET,8) // 80040048 +#define HXR_NET_WRITE MAKE_HX_RESULT(1,SS_NET,9) // 80040049 +#define HXR_NET_UDP MAKE_HX_RESULT(1,SS_NET,10) // 8004004a +#define HXR_RETRY MAKE_HX_RESULT(1,SS_NET,11) /* XXX */ // 8004004b +#define HXR_SERVER_TIMEOUT MAKE_HX_RESULT(1,SS_NET,12) // 8004004c +#define HXR_SERVER_DISCONNECTED MAKE_HX_RESULT(1,SS_NET,13) // 8004004d +#define HXR_WOULD_BLOCK MAKE_HX_RESULT(1,SS_NET,14) // 8004004e +#define HXR_GENERAL_NONET MAKE_HX_RESULT(1,SS_NET,15) // 8004004f +#define HXR_BLOCK_CANCELED MAKE_HX_RESULT(1,SS_NET,16) /* XXX */ // 80040050 +#define HXR_MULTICAST_JOIN MAKE_HX_RESULT(1,SS_NET,17) // 80040051 +#define HXR_GENERAL_MULTICAST MAKE_HX_RESULT(1,SS_NET,18) // 80040052 +#define HXR_MULTICAST_UDP MAKE_HX_RESULT(1,SS_NET,19) // 80040053 +#define HXR_AT_INTERRUPT MAKE_HX_RESULT(1,SS_NET,20) // 80040054 +#define HXR_MSG_TOOLARGE MAKE_HX_RESULT(1,SS_NET,21) // 80040055 +#define HXR_NET_TCP MAKE_HX_RESULT(1,SS_NET,22) // 80040056 +#define HXR_TRY_AUTOCONFIG MAKE_HX_RESULT(1,SS_NET,23) // 80040057 +#define HXR_NOTENOUGH_BANDWIDTH MAKE_HX_RESULT(1,SS_NET,24) // 80040058 +#define HXR_HTTP_CONNECT MAKE_HX_RESULT(1,SS_NET,25) // 80040059 +#define HXR_PORT_IN_USE MAKE_HX_RESULT(1,SS_NET,26) // 8004005a +#define HXR_LOADTEST_NOT_SUPPORTED MAKE_HX_RESULT(1,SS_NET,27) // 8004005b +#define HXR_TCP_CONNECT MAKE_HX_RESULT(0,SS_NET,32) // 00040060 +#define HXR_TCP_RECONNECT MAKE_HX_RESULT(0,SS_NET,33) // 00040061 +#define HXR_TCP_FAILED MAKE_HX_RESULT(1,SS_NET,34) // 80040062 +#define HXR_AUTH_SOCKET_CREATE_FAILURE MAKE_HX_RESULT(1,SS_NET,35) // 80040063 +#define HXR_AUTH_TCP_CONNECT_FAILURE MAKE_HX_RESULT(1,SS_NET,36) // 80040064 +#define HXR_AUTH_TCP_CONNECT_TIMEOUT MAKE_HX_RESULT(1,SS_NET,37) // 80040065 +#define HXR_AUTH_FAILURE MAKE_HX_RESULT(1,SS_NET,38) // 80040066 +#define HXR_AUTH_REQ_PARAMETER_MISSING MAKE_HX_RESULT(1,SS_NET,39) // 80040067 +#define HXR_DNS_RESOLVE_FAILURE MAKE_HX_RESULT(1,SS_NET,40) // 80040068 +#define HXR_AUTH_SUCCEEDED MAKE_HX_RESULT(0,SS_NET,40) // 00040068 +#define HXR_PULL_AUTHENTICATION_FAILED MAKE_HX_RESULT(1,SS_NET,41) // 80040069 +#define HXR_BIND_ERROR MAKE_HX_RESULT(1,SS_NET,42) // 8004006a +#define HXR_PULL_PING_TIMEOUT MAKE_HX_RESULT(1,SS_NET,43) // 8004006b +#define HXR_AUTH_TCP_FAILED MAKE_HX_RESULT(1,SS_NET,44) // 8004006c +#define HXR_UNEXPECTED_STREAM_END MAKE_HX_RESULT(1,SS_NET,45) // 8004006d +#define HXR_AUTH_READ_TIMEOUT MAKE_HX_RESULT(1,SS_NET,46) // 8004006e +#define HXR_AUTH_CONNECTION_FAILURE MAKE_HX_RESULT(1,SS_NET,47) // 8004006f +#define HXR_BLOCKED MAKE_HX_RESULT(1,SS_NET,48) // 80040070 +#define HXR_NOTENOUGH_PREDECBUF MAKE_HX_RESULT(1,SS_NET,49) // 80040071 +#define HXR_END_WITH_REASON MAKE_HX_RESULT(1,SS_NET,50) // 80040072 +#define HXR_SOCKET_NOBUFS MAKE_HX_RESULT(1,SS_NET,51) // 80040073 + +#define HXR_AT_END MAKE_HX_RESULT(0,SS_FIL,0) // 00040080 +#define HXR_INVALID_FILE MAKE_HX_RESULT(1,SS_FIL,1) // 80040081 +#define HXR_INVALID_PATH MAKE_HX_RESULT(1,SS_FIL,2) // 80040082 +#define HXR_RECORD MAKE_HX_RESULT(1,SS_FIL,3) // 80040083 +#define HXR_RECORD_WRITE MAKE_HX_RESULT(1,SS_FIL,4) // 80040084 +#define HXR_TEMP_FILE MAKE_HX_RESULT(1,SS_FIL,5) // 80040085 +#define HXR_ALREADY_OPEN MAKE_HX_RESULT(1,SS_FIL,6) // 80040086 +#define HXR_SEEK_PENDING MAKE_HX_RESULT(1,SS_FIL,7) // 80040087 +#define HXR_CANCELLED MAKE_HX_RESULT(1,SS_FIL,8) // 80040088 +#define HXR_FILE_NOT_FOUND MAKE_HX_RESULT(1,SS_FIL,9) // 80040089 +#define HXR_WRITE_ERROR MAKE_HX_RESULT(1,SS_FIL,10) // 8004008a +#define HXR_FILE_EXISTS MAKE_HX_RESULT(1,SS_FIL,11) // 8004008b +#define HXR_FILE_NOT_OPEN MAKE_HX_RESULT(1,SS_FIL,12) // 8004008c +#define HXR_ADVISE_PREFER_LINEAR MAKE_HX_RESULT(0,SS_FIL,13) // 0004008d +#define HXR_PARSE_ERROR MAKE_HX_RESULT(1,SS_FIL,14) // 8004008e +#define HXR_ADVISE_NOASYNC_SEEK MAKE_HX_RESULT(0,SS_FIL,15) // 0004008f +#define HXR_HEADER_PARSE_ERROR MAKE_HX_RESULT(1,SS_FIL,16) // 80040090 +#define HXR_CORRUPT_FILE MAKE_HX_RESULT(1,SS_FIL,17) // 80040091 + +#define HXR_BAD_SERVER MAKE_HX_RESULT(1,SS_PRT,0) // 800400c0 +#define HXR_ADVANCED_SERVER MAKE_HX_RESULT(1,SS_PRT,1) // 800400c1 +#define HXR_OLD_SERVER MAKE_HX_RESULT(1,SS_PRT,2) // 800400c2 +#define HXR_REDIRECTION MAKE_HX_RESULT(0,SS_PRT,3) /* XXX */ // 000400c3 +#define HXR_SERVER_ALERT MAKE_HX_RESULT(1,SS_PRT,4) // 800400c4 +#define HXR_PROXY MAKE_HX_RESULT(1,SS_PRT,5) // 800400c5 +#define HXR_PROXY_RESPONSE MAKE_HX_RESULT(1,SS_PRT,6) // 800400c6 +#define HXR_ADVANCED_PROXY MAKE_HX_RESULT(1,SS_PRT,7) // 800400c7 +#define HXR_OLD_PROXY MAKE_HX_RESULT(1,SS_PRT,8) // 800400c8 +#define HXR_INVALID_PROTOCOL MAKE_HX_RESULT(1,SS_PRT,9) // 800400c9 +#define HXR_INVALID_URL_OPTION MAKE_HX_RESULT(1,SS_PRT,10) // 800400ca +#define HXR_INVALID_URL_HOST MAKE_HX_RESULT(1,SS_PRT,11) // 800400cb +#define HXR_INVALID_URL_PATH MAKE_HX_RESULT(1,SS_PRT,12) // 800400cc +#define HXR_HTTP_CONTENT_NOT_FOUND MAKE_HX_RESULT(1,SS_PRT,13) // 800400cd +#define HXR_NOT_AUTHORIZED MAKE_HX_RESULT(1,SS_PRT,14) // 800400ce +#define HXR_UNEXPECTED_MSG MAKE_HX_RESULT(1,SS_PRT,15) // 800400cf +#define HXR_BAD_TRANSPORT MAKE_HX_RESULT(1,SS_PRT,16) // 800400d0 +#define HXR_NO_SESSION_ID MAKE_HX_RESULT(1,SS_PRT,17) // 800400d1 +#define HXR_PROXY_DNR MAKE_HX_RESULT(1,SS_PRT,18) // 800400d2 +#define HXR_PROXY_NET_CONNECT MAKE_HX_RESULT(1,SS_PRT,19) // 800400d3 +#define HXR_AGGREGATE_OP_NOT_ALLOWED MAKE_HX_RESULT(1,SS_PRT,20) // 800400d4 +#define HXR_RIGHTS_EXPIRED MAKE_HX_RESULT(1,SS_PRT,21) // 800400d5 +#define HXR_NOT_MODIFIED MAKE_HX_RESULT(1,SS_PRT,22) // 800400d6 +#define HXR_FORBIDDEN MAKE_HX_RESULT(1,SS_PRT,23) // 800400d7 + +#define HXR_AUDIO_DRIVER MAKE_HX_RESULT(1,SS_AUD,0) // 80040100 +#define HXR_LATE_PACKET MAKE_HX_RESULT(1,SS_AUD,1) // 80040101 +#define HXR_OVERLAPPED_PACKET MAKE_HX_RESULT(1,SS_AUD,2) // 80040102 +#define HXR_OUTOFORDER_PACKET MAKE_HX_RESULT(1,SS_AUD,3) // 80040103 +#define HXR_NONCONTIGUOUS_PACKET MAKE_HX_RESULT(1,SS_AUD,4) // 80040104 + +#define HXR_OPEN_NOT_PROCESSED MAKE_HX_RESULT(1,SS_INT,0) // 80040140 +#define HXR_WINDRAW_EXCEPTION MAKE_HX_RESULT(1,SS_INT,1) // 80040141 + +#define HXR_EXPIRED MAKE_HX_RESULT(1,SS_USR,0) // 80040180 + +#define HXR_INVALID_INTERLEAVER MAKE_HX_RESULT(1,SS_DPR,0) // 80040fc0 +#define HXR_BAD_FORMAT MAKE_HX_RESULT(1,SS_DPR,1) // 80040fc1 +#define HXR_CHUNK_MISSING MAKE_HX_RESULT(1,SS_DPR,2) // 80040fc2 +#define HXR_INVALID_STREAM MAKE_HX_RESULT(1,SS_DPR,3) // 80040fc3 +#define HXR_DNR MAKE_HX_RESULT(1,SS_DPR,4) // 80040fc4 +#define HXR_OPEN_DRIVER MAKE_HX_RESULT(1,SS_DPR,5) // 80040fc5 +#define HXR_UPGRADE MAKE_HX_RESULT(1,SS_DPR,6) // 80040fc6 +#define HXR_NOTIFICATION MAKE_HX_RESULT(1,SS_DPR,7) // 80040fc7 +#define HXR_NOT_NOTIFIED MAKE_HX_RESULT(1,SS_DPR,8) // 80040fc8 +#define HXR_STOPPED MAKE_HX_RESULT(1,SS_DPR,9) // 80040fc9 +#define HXR_CLOSED MAKE_HX_RESULT(1,SS_DPR,10) // 80040fca +#define HXR_INVALID_WAV_FILE MAKE_HX_RESULT(1,SS_DPR,11) // 80040fcb +#define HXR_NO_SEEK MAKE_HX_RESULT(1,SS_DPR,12) // 80040fcc + +#define HXR_DEC_INITED MAKE_HX_RESULT(1,SS_DEC,0) // 80040200 +#define HXR_DEC_NOT_FOUND MAKE_HX_RESULT(1,SS_DEC,1) // 80040201 +#define HXR_DEC_INVALID MAKE_HX_RESULT(1,SS_DEC,2) // 80040202 +#define HXR_DEC_TYPE_MISMATCH MAKE_HX_RESULT(1,SS_DEC,3) // 80040203 +#define HXR_DEC_INIT_FAILED MAKE_HX_RESULT(1,SS_DEC,4) // 80040204 +#define HXR_DEC_NOT_INITED MAKE_HX_RESULT(1,SS_DEC,5) // 80040205 +#define HXR_DEC_DECOMPRESS MAKE_HX_RESULT(1,SS_DEC,6) // 80040206 +#define HXR_OBSOLETE_VERSION MAKE_HX_RESULT(1,SS_DEC,7) // 80040207 +#define HXR_DEC_AT_END MAKE_HX_RESULT(0,SS_DEC,8) // 00040208 + +#define HXR_ENC_FILE_TOO_SMALL MAKE_HX_RESULT(1,SS_ENC,0) // 80040240 +#define HXR_ENC_UNKNOWN_FILE MAKE_HX_RESULT(1,SS_ENC,1) // 80040241 +#define HXR_ENC_BAD_CHANNELS MAKE_HX_RESULT(1,SS_ENC,2) // 80040242 +#define HXR_ENC_BAD_SAMPSIZE MAKE_HX_RESULT(1,SS_ENC,3) // 80040243 +#define HXR_ENC_BAD_SAMPRATE MAKE_HX_RESULT(1,SS_ENC,4) // 80040244 +#define HXR_ENC_INVALID MAKE_HX_RESULT(1,SS_ENC,5) // 80040245 +#define HXR_ENC_NO_OUTPUT_FILE MAKE_HX_RESULT(1,SS_ENC,6) // 80040246 +#define HXR_ENC_NO_INPUT_FILE MAKE_HX_RESULT(1,SS_ENC,7) // 80040247 +#define HXR_ENC_NO_OUTPUT_PERMISSIONS MAKE_HX_RESULT(1,SS_ENC,8) // 80040248 +#define HXR_ENC_BAD_FILETYPE MAKE_HX_RESULT(1,SS_ENC,9) // 80040249 +#define HXR_ENC_INVALID_VIDEO MAKE_HX_RESULT(1,SS_ENC,10) // 8004024a +#define HXR_ENC_INVALID_AUDIO MAKE_HX_RESULT(1,SS_ENC,11) // 8004024b +#define HXR_ENC_NO_VIDEO_CAPTURE MAKE_HX_RESULT(1,SS_ENC,12) // 8004024c +#define HXR_ENC_INVALID_VIDEO_CAPTURE MAKE_HX_RESULT(1,SS_ENC,13) // 8004024d +#define HXR_ENC_NO_AUDIO_CAPTURE MAKE_HX_RESULT(1,SS_ENC,14) // 8004024e +#define HXR_ENC_INVALID_AUDIO_CAPTURE MAKE_HX_RESULT(1,SS_ENC,15) // 8004024f +#define HXR_ENC_TOO_SLOW_FOR_LIVE MAKE_HX_RESULT(1,SS_ENC,16) // 80040250 +#define HXR_ENC_ENGINE_NOT_INITIALIZED MAKE_HX_RESULT(1,SS_ENC,17) // 80040251 +#define HXR_ENC_CODEC_NOT_FOUND MAKE_HX_RESULT(1,SS_ENC,18) // 80040252 +#define HXR_ENC_CODEC_NOT_INITIALIZED MAKE_HX_RESULT(1,SS_ENC,19) // 80040253 +#define HXR_ENC_INVALID_INPUT_DIMENSIONS MAKE_HX_RESULT(1,SS_ENC,20) // 80040254 +#define HXR_ENC_MESSAGE_IGNORED MAKE_HX_RESULT(1,SS_ENC,21) // 80040255 +#define HXR_ENC_NO_SETTINGS MAKE_HX_RESULT(1,SS_ENC,22) // 80040256 +#define HXR_ENC_NO_OUTPUT_TYPES MAKE_HX_RESULT(1,SS_ENC,23) // 80040257 +#define HXR_ENC_IMPROPER_STATE MAKE_HX_RESULT(1,SS_ENC,24) // 80040258 +#define HXR_ENC_INVALID_SERVER MAKE_HX_RESULT(1,SS_ENC,25) // 80040259 +#define HXR_ENC_INVALID_TEMP_PATH MAKE_HX_RESULT(1,SS_ENC,26) // 8004025a +#define HXR_ENC_MERGE_FAIL MAKE_HX_RESULT(1,SS_ENC,27) // 8004025b +#define HXR_BIN_DATA_NOT_FOUND MAKE_HX_RESULT(0,SS_ENC,28) // 0004025c +#define HXR_BIN_END_OF_DATA MAKE_HX_RESULT(0,SS_ENC,29) // 0004025d +#define HXR_BIN_DATA_PURGED MAKE_HX_RESULT(1,SS_ENC,30) // 8004025e +#define HXR_BIN_FULL MAKE_HX_RESULT(1,SS_ENC,31) // 8004025f +#define HXR_BIN_OFFSET_PAST_END MAKE_HX_RESULT(1,SS_ENC,32) // 80040260 +#define HXR_ENC_NO_ENCODED_DATA MAKE_HX_RESULT(1,SS_ENC,33) // 80040261 +#define HXR_ENC_INVALID_DLL MAKE_HX_RESULT(1,SS_ENC,34) // 80040262 +#define HXR_NOT_INDEXABLE MAKE_HX_RESULT(1,SS_ENC,35) // 80040263 +#define HXR_ENC_NO_BROWSER MAKE_HX_RESULT(1,SS_ENC,36) // 80040264 +#define HXR_ENC_NO_FILE_TO_SERVER MAKE_HX_RESULT(1,SS_ENC,37) // 80040265 +#define HXR_ENC_INSUFFICIENT_DISK_SPACE MAKE_HX_RESULT(1,SS_ENC,38) // 80040266 +#define HXR_ENC_SAMPLE_DISCARDED MAKE_HX_RESULT(0,SS_ENC,39) // 00040267 +#define HXR_ENC_RV10_FRAME_TOO_LARGE MAKE_HX_RESULT(1,SS_ENC,40) // 80040268 +#define HXR_S_NOT_HANDLED MAKE_HX_RESULT(0,SS_ENC,41) // 00040269 +#define HXR_S_END_OF_STREAM MAKE_HX_RESULT(0,SS_ENC,42) // 0004026a +#define HXR_S_JOBFILE_INCOMPLETE MAKE_HX_RESULT(0, SS_ENC, 43 ) // 0004026b +#define HXR_S_NOTHING_TO_SERIALIZE MAKE_HX_RESULT(0, SS_ENC, 44 ) // 0004026c +#define HXR_SIZENOTSET MAKE_HX_RESULT(1,SS_ENC,45) // 8004026d +#define HXR_ALREADY_COMMITTED MAKE_HX_RESULT(1,SS_ENC,46) // 8004026e +#define HXR_BUFFERS_OUTSTANDING MAKE_HX_RESULT(1,SS_ENC,47) // 8004026f +#define HXR_NOT_COMMITTED MAKE_HX_RESULT(1,SS_ENC,48) // 80040270 +#define HXR_SAMPLE_TIME_NOT_SET MAKE_HX_RESULT(1,SS_ENC,49) // 80040271 +#define HXR_TIMEOUT MAKE_HX_RESULT(1,SS_ENC,50) // 80040272 +#define HXR_WRONGSTATE MAKE_HX_RESULT(1,SS_ENC,51) // 80040273 + +#define HXR_RMT_USAGE_ERROR MAKE_HX_RESULT(1,SS_RMT,1) // 800403c1 +#define HXR_RMT_INVALID_ENDTIME MAKE_HX_RESULT(1,SS_RMT,2) // 800403c2 +#define HXR_RMT_MISSING_INPUT_FILE MAKE_HX_RESULT(1,SS_RMT,3) // 800403c3 +#define HXR_RMT_MISSING_OUTPUT_FILE MAKE_HX_RESULT(1,SS_RMT,4) // 800403c4 +#define HXR_RMT_INPUT_EQUALS_OUTPUT_FILE MAKE_HX_RESULT(1,SS_RMT,5) // 800403c5 +#define HXR_RMT_UNSUPPORTED_AUDIO_VERSION MAKE_HX_RESULT(1,SS_RMT,6) // 800403c6 +#define HXR_RMT_DIFFERENT_AUDIO MAKE_HX_RESULT(1,SS_RMT,7) // 800403c7 +#define HXR_RMT_DIFFERENT_VIDEO MAKE_HX_RESULT(1,SS_RMT,8) // 800403c8 +#define HXR_RMT_PASTE_MISSING_STREAM MAKE_HX_RESULT(1,SS_RMT,9) // 800403c9 +#define HXR_RMT_END_OF_STREAM MAKE_HX_RESULT(1,SS_RMT,10) // 800403ca +#define HXR_RMT_IMAGE_MAP_PARSE_ERROR MAKE_HX_RESULT(1,SS_RMT,11) // 800403cb +#define HXR_RMT_INVALID_IMAGEMAP_FILE MAKE_HX_RESULT(1,SS_RMT,12) // 800403cc +#define HXR_RMT_EVENT_PARSE_ERROR MAKE_HX_RESULT(1,SS_RMT,13) // 800403cd +#define HXR_RMT_INVALID_EVENT_FILE MAKE_HX_RESULT(1,SS_RMT,14) // 800403ce +#define HXR_RMT_INVALID_OUTPUT_FILE MAKE_HX_RESULT(1,SS_RMT,15) // 800403cf +#define HXR_RMT_INVALID_DURATION MAKE_HX_RESULT(1,SS_RMT,16) // 800403d0 +#define HXR_RMT_NO_DUMP_FILES MAKE_HX_RESULT(1,SS_RMT,17) // 800403d1 +#define HXR_RMT_NO_EVENT_DUMP_FILE MAKE_HX_RESULT(1,SS_RMT,18) // 800403d2 +#define HXR_RMT_NO_IMAP_DUMP_FILE MAKE_HX_RESULT(1,SS_RMT,19) // 800403d3 +#define HXR_RMT_NO_DATA MAKE_HX_RESULT(1,SS_RMT,20) // 800403d4 +#define HXR_RMT_EMPTY_STREAM MAKE_HX_RESULT(1,SS_RMT,21) // 800403d5 +#define HXR_RMT_READ_ONLY_FILE MAKE_HX_RESULT(1,SS_RMT,22) // 800403d6 +#define HXR_RMT_PASTE_MISSING_AUDIO_STREAM MAKE_HX_RESULT(1,SS_RMT,23) // 800403d7 +#define HXR_RMT_PASTE_MISSING_VIDEO_STREAM MAKE_HX_RESULT(1,SS_RMT,24) // 800403d8 +#define HXR_RMT_ENCRYPTED_CONTENT MAKE_HX_RESULT(1,SS_RMT,25) // 800403d9 + +#define HXR_PROP_NOT_FOUND MAKE_HX_RESULT(1,SS_REG,1) // 80040281 +#define HXR_PROP_NOT_COMPOSITE MAKE_HX_RESULT(1,SS_REG,2) // 80040282 +#define HXR_PROP_DUPLICATE MAKE_HX_RESULT(1,SS_REG,3) // 80040283 +#define HXR_PROP_TYPE_MISMATCH MAKE_HX_RESULT(1,SS_REG,4) // 80040284 +#define HXR_PROP_ACTIVE MAKE_HX_RESULT(1,SS_REG,5) // 80040285 +#define HXR_PROP_INACTIVE MAKE_HX_RESULT(1,SS_REG,6) // 80040286 +#define HXR_PROP_VAL_UNDERFLOW MAKE_HX_RESULT(1,SS_REG,7) // 80040287 +#define HXR_PROP_VAL_OVERFLOW MAKE_HX_RESULT(1,SS_REG,8) // 80040288 +#define HXR_PROP_VAL_LT_LBOUND MAKE_HX_RESULT(1,SS_REG,9) // 80040289 +#define HXR_PROP_VAL_GT_UBOUND MAKE_HX_RESULT(1,SS_REG,10) // 8004028a +#define HXR_PROP_DELETE_PENDING MAKE_HX_RESULT(0,SS_REG,11) // 0004028b + +#define HXR_COULDNOTINITCORE MAKE_HX_RESULT(1,SS_MSC,1) // 800401c1 +#define HXR_PERFECTPLAY_NOT_SUPPORTED MAKE_HX_RESULT(1,SS_MSC,2) // 800401c2 +#define HXR_NO_LIVE_PERFECTPLAY MAKE_HX_RESULT(1,SS_MSC,3) // 800401c3 +#define HXR_PERFECTPLAY_NOT_ALLOWED MAKE_HX_RESULT(1,SS_MSC,4) // 800401c4 +#define HXR_NO_CODECS MAKE_HX_RESULT(1,SS_MSC,5) // 800401c5 +#define HXR_SLOW_MACHINE MAKE_HX_RESULT(1,SS_MSC,6) // 800401c6 +#define HXR_FORCE_PERFECTPLAY MAKE_HX_RESULT(1,SS_MSC,7) // 800401c7 +#define HXR_INVALID_HTTP_PROXY_HOST MAKE_HX_RESULT(1,SS_MSC,8) // 800401c8 +#define HXR_INVALID_METAFILE MAKE_HX_RESULT(1,SS_MSC,9) // 800401c9 +#define HXR_BROWSER_LAUNCH MAKE_HX_RESULT(1,SS_MSC,10) // 800401ca +#define HXR_VIEW_SOURCE_NOCLIP MAKE_HX_RESULT(1,SS_MSC,11) // 800401cb +#define HXR_VIEW_SOURCE_DISSABLED MAKE_HX_RESULT(1,SS_MSC,12) // 800401cc +#define HXR_TIMELINE_SUSPENDED MAKE_HX_RESULT(1,SS_MSC,14) // 800401ce +#define HXR_BUFFER_NOT_AVAILABLE MAKE_HX_RESULT(1,SS_MSC,15) // 800401cf +#define HXR_COULD_NOT_DISPLAY MAKE_HX_RESULT(1,SS_MSC,16) // 800401d0 +#define HXR_VSRC_DISABLED MAKE_HX_RESULT(1,SS_MSC,17) // 800401d1 +#define HXR_VSRC_NOCLIP MAKE_HX_RESULT(1,SS_MSC,18) // 800401d2 + +#define HXR_RESOURCE_NOT_CACHED MAKE_HX_RESULT(1,SS_RSC,1) // 80040301 +#define HXR_RESOURCE_NOT_FOUND MAKE_HX_RESULT(1,SS_RSC,2) // 80040302 +#define HXR_RESOURCE_CLOSE_FILE_FIRST MAKE_HX_RESULT(1,SS_RSC,3) // 80040303 +#define HXR_RESOURCE_NODATA MAKE_HX_RESULT(1,SS_RSC,4) // 80040304 +#define HXR_RESOURCE_BADFILE MAKE_HX_RESULT(1,SS_RSC,5) // 80040305 +#define HXR_RESOURCE_PARTIALCOPY MAKE_HX_RESULT(1,SS_RSC,6) // 80040306 + +#define HXR_PPV_NO_USER MAKE_HX_RESULT(1,SS_PPV,0) // 800402c0 +#define HXR_PPV_GUID_READ_ONLY MAKE_HX_RESULT(1,SS_PPV,1) // 800402c1 +#define HXR_PPV_GUID_COLLISION MAKE_HX_RESULT(1,SS_PPV,2) // 800402c2 +#define HXR_REGISTER_GUID_EXISTS MAKE_HX_RESULT(1,SS_PPV,3) // 800402c3 +#define HXR_PPV_AUTHORIZATION_FAILED MAKE_HX_RESULT(1,SS_PPV,4) // 800402c4 +#define HXR_PPV_OLD_PLAYER MAKE_HX_RESULT(1,SS_PPV,5) // 800402c5 +#define HXR_PPV_ACCOUNT_LOCKED MAKE_HX_RESULT(1,SS_PPV,6) // 800402c6 +// #define HXR_PPV_PROTOCOL_IGNORES MAKE_HX_RESULT(1,SS_PPV,7) +#define HXR_PPV_DBACCESS_ERROR MAKE_HX_RESULT(1,SS_PPV,8) // 800402c8 +#define HXR_PPV_USER_ALREADY_EXISTS MAKE_HX_RESULT(1,SS_PPV,9) // 800402c9 + +// auto-upgrade (RealUpdate) errors +#define HXR_UPG_AUTH_FAILED MAKE_HX_RESULT(1,SS_UPG,0) // 80040340 +#define HXR_UPG_CERT_AUTH_FAILED MAKE_HX_RESULT(1,SS_UPG,1) // 80040341 +#define HXR_UPG_CERT_EXPIRED MAKE_HX_RESULT(1,SS_UPG,2) // 80040342 +#define HXR_UPG_CERT_REVOKED MAKE_HX_RESULT(1,SS_UPG,3) // 80040343 +#define HXR_UPG_RUP_BAD MAKE_HX_RESULT(1,SS_UPG,4) // 80040344 +#define HXR_UPG_SYSTEM_BUSY MAKE_HX_RESULT(1,SS_UPG,5) // 80040345 + +// auto-config errors +#define HXR_AUTOCFG_SUCCESS MAKE_HX_RESULT(1,SS_CFG,0) +#define HXR_AUTOCFG_FAILED MAKE_HX_RESULT(1,SS_CFG,1) +#define HXR_AUTOCFG_ABORT MAKE_HX_RESULT(1,SS_CFG,2) + +//producer activex errors. +#define HXR_ENC_AX_INIT_FAILED MAKE_HX_RESULT(1,SS_ENC_AX,0) +#define HXR_ENC_AX_NOTVALID_WHILE_ENCODING MAKE_HX_RESULT(1,SS_ENC_AX,1) +#define HXR_ENC_AX_REALMEDIAEVENTS_DISABLED MAKE_HX_RESULT(1,SS_ENC_AX,2) +#define HXR_ENC_AX_EVENT_START_TIME_DECREASING MAKE_HX_RESULT(1,SS_ENC_AX,3) +#define HXR_ENC_AX_FAILED_MEDIASINK_INPUT MAKE_HX_RESULT(1,SS_ENC_AX,4) +#define HXR_ENC_AX_INVALID_EVENT_TYPE MAKE_HX_RESULT(1,SS_ENC_AX,5) +#define HXR_ENC_AX_JOB_NOT_SET MAKE_HX_RESULT(1,SS_ENC_AX,6) +#define HXR_ENC_AX_NOTVALID_WHILE_NOTENCODING MAKE_HX_RESULT(1,SS_ENC_AX,7) +#define HXR_ENC_AX_NO_AUDIO_GAIN_SET MAKE_HX_RESULT(1,SS_ENC_AX,8) +#define HXR_ENC_AX_UPDATE_CODECS_FAILED MAKE_HX_RESULT(1,SS_ENC_AX,9) +#define HXR_ENC_AX_EVENT_LATE MAKE_HX_RESULT(1,SS_ENC_AX,10) +#define HXR_ENC_AX_EVENT_INVALID_START_TIME MAKE_HX_RESULT(1,SS_ENC_AX,11) +#define HXR_ENC_AX_JOB_START_TIME_NOT_SET MAKE_HX_RESULT(1,SS_ENC_AX,12) +#define HXR_ENC_AX_INVALID_JOB_FILE MAKE_HX_RESULT(1,SS_ENC_AX,13) +#define HXR_ENC_AX_INVALID_JOB_XML MAKE_HX_RESULT(1,SS_ENC_AX,14) +#define HXR_ENC_AX_INVALID_ARGUMENTS MAKE_HX_RESULT(1,SS_ENC_AX,15) + +// RealPix errors +#define HXR_UNKNOWN_IMAGE MAKE_HX_RESULT(1,SS_RPX,0) +#define HXR_UNKNOWN_EFFECT MAKE_HX_RESULT(1,SS_RPX,1) +#define HXR_SENDIMAGE_ABORTED MAKE_HX_RESULT(0,SS_RPX,2) +#define HXR_SENDEFFECT_ABORTED MAKE_HX_RESULT(0,SS_RPX,3) + +// server alert errors +#define HXR_SE_MIN_VALUE MAKE_HX_RESULT(1, SS_SAM, 0) // 80041800 +#define HXR_SE_NO_ERROR MAKE_HX_RESULT(1, SS_SAM, 1) // 80041901 +#define HXR_SE_INVALID_VERSION MAKE_HX_RESULT(1, SS_SAM, 2) // 80041902 +#define HXR_SE_INVALID_FORMAT MAKE_HX_RESULT(1, SS_SAM, 3) // 80041903 +#define HXR_SE_INVALID_BANDWIDTH MAKE_HX_RESULT(1, SS_SAM, 4) // 80041904 +#define HXR_SE_INVALID_PATH MAKE_HX_RESULT(1, SS_SAM, 5) // 80041905 +#define HXR_SE_UNKNOWN_PATH MAKE_HX_RESULT(1, SS_SAM, 6) // 80041906 +#define HXR_SE_INVALID_PROTOCOL MAKE_HX_RESULT(1, SS_SAM, 7) // 80041907 +#define HXR_SE_INVALID_PLAYER_ADDR MAKE_HX_RESULT(1, SS_SAM, 8) // 80041908 +#define HXR_SE_LOCAL_STREAMS_PROHIBITED MAKE_HX_RESULT(1, SS_SAM, 9) // 80041909 +#define HXR_SE_SERVER_FULL MAKE_HX_RESULT(1, SS_SAM, 10) // 8004190a +#define HXR_SE_REMOTE_STREAMS_PROHIBITED MAKE_HX_RESULT(1, SS_SAM, 11) // 8004190b +#define HXR_SE_EVENT_STREAMS_PROHIBITED MAKE_HX_RESULT(1, SS_SAM, 12) // 8004190c +#define HXR_SE_INVALID_HOST MAKE_HX_RESULT(1, SS_SAM, 13) // 8004190d +#define HXR_SE_NO_CODEC MAKE_HX_RESULT(1, SS_SAM, 14) // 8004190e +#define HXR_SE_LIVEFILE_INVALID_BWN MAKE_HX_RESULT(1, SS_SAM, 15) // 8004190f +#define HXR_SE_UNABLE_TO_FULFILL MAKE_HX_RESULT(1, SS_SAM, 16) // 80041910 +#define HXR_SE_MULTICAST_DELIVERY_ONLY MAKE_HX_RESULT(1, SS_SAM, 17) // 80041911 +#define HXR_SE_LICENSE_EXCEEDED MAKE_HX_RESULT(1, SS_SAM, 18) // 80041912 +#define HXR_SE_LICENSE_UNAVAILABLE MAKE_HX_RESULT(1, SS_SAM, 19) // 80041913 +#define HXR_SE_INVALID_LOSS_CORRECTION MAKE_HX_RESULT(1, SS_SAM, 20) // 80041914 +#define HXR_SE_PROTOCOL_FAILURE MAKE_HX_RESULT(1, SS_SAM, 21) // 80041915 +#define HXR_SE_REALVIDEO_STREAMS_PROHIBITED MAKE_HX_RESULT(1, SS_SAM, 22) // 80041916 +#define HXR_SE_REALAUDIO_STREAMS_PROHIBITED MAKE_HX_RESULT(1, SS_SAM, 23) // 80041917 +#define HXR_SE_DATATYPE_UNSUPPORTED MAKE_HX_RESULT(1, SS_SAM, 24) // 80041918 +#define HXR_SE_DATATYPE_UNLICENSED MAKE_HX_RESULT(1, SS_SAM, 25) // 80041919 +#define HXR_SE_RESTRICTED_PLAYER MAKE_HX_RESULT(1, SS_SAM, 26) // 8004191a +#define HXR_SE_STREAM_INITIALIZING MAKE_HX_RESULT(1, SS_SAM, 27) // 8004191b +#define HXR_SE_INVALID_PLAYER MAKE_HX_RESULT(1, SS_SAM, 28) // 8004191c +#define HXR_SE_PLAYER_PLUS_ONLY MAKE_HX_RESULT(1, SS_SAM, 29) // 8004191d +#define HXR_SE_NO_EMBEDDED_PLAYERS MAKE_HX_RESULT(1, SS_SAM, 30) // 8004191e +#define HXR_SE_PNA_PROHIBITED MAKE_HX_RESULT(1, SS_SAM, 31) // 8004191f +#define HXR_SE_AUTHENTICATION_UNSUPPORTED MAKE_HX_RESULT(1, SS_SAM, 32) // 80041920 +#define HXR_SE_MAX_FAILED_AUTHENTICATIONS MAKE_HX_RESULT(1, SS_SAM, 33) // 80041921 +#define HXR_SE_AUTH_ACCESS_DENIED MAKE_HX_RESULT(1, SS_SAM, 34) // 80041922 +#define HXR_SE_AUTH_UUID_READ_ONLY MAKE_HX_RESULT(1, SS_SAM, 35) // 80041923 +#define HXR_SE_AUTH_UUID_NOT_UNIQUE MAKE_HX_RESULT(1, SS_SAM, 36) // 80041924 +#define HXR_SE_AUTH_NO_SUCH_USER MAKE_HX_RESULT(1, SS_SAM, 37) // 80041925 +#define HXR_SE_AUTH_REGISTRATION_SUCCEEDED MAKE_HX_RESULT(1, SS_SAM, 38) // 80041926 +#define HXR_SE_AUTH_REGISTRATION_FAILED MAKE_HX_RESULT(1, SS_SAM, 39) // 80041927 +#define HXR_SE_AUTH_REGISTRATION_GUID_REQUIRED MAKE_HX_RESULT(1, SS_SAM, 40) // 80041928 +#define HXR_SE_AUTH_UNREGISTERED_PLAYER MAKE_HX_RESULT(1, SS_SAM, 41) // 80041929 +#define HXR_SE_AUTH_TIME_EXPIRED MAKE_HX_RESULT(1, SS_SAM, 42) // 8004192a +#define HXR_SE_AUTH_NO_TIME_LEFT MAKE_HX_RESULT(1, SS_SAM, 43) // 8004192b +#define HXR_SE_AUTH_ACCOUNT_LOCKED MAKE_HX_RESULT(1, SS_SAM, 44) // 8004192c +#define HXR_SE_AUTH_INVALID_SERVER_CFG MAKE_HX_RESULT(1, SS_SAM, 45) // 8004192d +#define HXR_SE_NO_MOBILE_DOWNLOAD MAKE_HX_RESULT(1, SS_SAM, 46) // 8004192e +#define HXR_SE_NO_MORE_MULTI_ADDR MAKE_HX_RESULT(1, SS_SAM, 47) // 8004192f +#define HXR_PE_PROXY_MAX_CONNECTIONS MAKE_HX_RESULT(1, SS_SAM, 48) // 80041930 +#define HXR_PE_PROXY_MAX_GW_BANDWIDTH MAKE_HX_RESULT(1, SS_SAM, 49) // 80041931 +#define HXR_PE_PROXY_MAX_BANDWIDTH MAKE_HX_RESULT(1, SS_SAM, 50) // 80041932 +#define HXR_SE_BAD_LOADTEST_PASSWORD MAKE_HX_RESULT(1, SS_SAM, 51) // 80041933 +#define HXR_SE_PNA_NOT_SUPPORTED MAKE_HX_RESULT(1, SS_SAM, 52) // 80041934 +#define HXR_PE_PROXY_ORIGIN_DISCONNECTED MAKE_HX_RESULT(1, SS_SAM, 53) // 80041935 +#define HXR_SE_INTERNAL_ERROR MAKE_HX_RESULT(1, SS_SAM, 54) // 80041936 +#define HXR_SE_MAX_VALUE MAKE_HX_RESULT(1, SS_SAM, 55) // 80041937 + + +#define HXR_SOCK_INTR MAKE_HX_RESULT(1, SS_SOCK, 0) // 80040600 +#define HXR_SOCK_BADF MAKE_HX_RESULT(1, SS_SOCK, 1) // 80040601 +#define HXR_SOCK_ACCES MAKE_HX_RESULT(1, SS_SOCK, 2) // 80040602 +#define HXR_SOCK_FAULT MAKE_HX_RESULT(1, SS_SOCK, 3) // 80040603 +#define HXR_SOCK_INVAL MAKE_HX_RESULT(1, SS_SOCK, 4) // 80040604 +#define HXR_SOCK_MFILE MAKE_HX_RESULT(1, SS_SOCK, 5) // 80040605 +#define HXR_SOCK_WOULDBLOCK MAKE_HX_RESULT(1, SS_SOCK, 6) // 80040606 +#define HXR_SOCK_INPROGRESS MAKE_HX_RESULT(1, SS_SOCK, 7) // 80040607 +#define HXR_SOCK_ALREADY MAKE_HX_RESULT(1, SS_SOCK, 8) // 80040608 +#define HXR_SOCK_NOTSOCK MAKE_HX_RESULT(1, SS_SOCK, 9) // 80040609 +#define HXR_SOCK_DESTADDRREQ MAKE_HX_RESULT(1, SS_SOCK, 10) // 8004060a +#define HXR_SOCK_MSGSIZE MAKE_HX_RESULT(1, SS_SOCK, 11) // 8004060b +#define HXR_SOCK_PROTOTYPE MAKE_HX_RESULT(1, SS_SOCK, 12) // 8004060c +#define HXR_SOCK_NOPROTOOPT MAKE_HX_RESULT(1, SS_SOCK, 13) // 8004060d +#define HXR_SOCK_PROTONOSUPPORT MAKE_HX_RESULT(1, SS_SOCK, 14) // 8004060e +#define HXR_SOCK_SOCKTNOSUPPORT MAKE_HX_RESULT(1, SS_SOCK, 15) // 8004060f +#define HXR_SOCK_OPNOTSUPP MAKE_HX_RESULT(1, SS_SOCK, 16) // 80040610 +#define HXR_SOCK_PFNOSUPPORT MAKE_HX_RESULT(1, SS_SOCK, 17) // 80040611 +#define HXR_SOCK_AFNOSUPPORT MAKE_HX_RESULT(1, SS_SOCK, 18) // 80040612 +#define HXR_SOCK_ADDRINUSE MAKE_HX_RESULT(1, SS_SOCK, 19) // 80040613 +#define HXR_SOCK_ADDRNOTAVAIL MAKE_HX_RESULT(1, SS_SOCK, 20) // 80040614 +#define HXR_SOCK_NETDOWN MAKE_HX_RESULT(1, SS_SOCK, 21) // 80040615 +#define HXR_SOCK_NETUNREACH MAKE_HX_RESULT(1, SS_SOCK, 22) // 80040616 +#define HXR_SOCK_NETRESET MAKE_HX_RESULT(1, SS_SOCK, 23) // 80040617 +#define HXR_SOCK_CONNABORTED MAKE_HX_RESULT(1, SS_SOCK, 24) // 80040618 +#define HXR_SOCK_CONNRESET MAKE_HX_RESULT(1, SS_SOCK, 25) // 80040619 +#define HXR_SOCK_NOBUFS MAKE_HX_RESULT(1, SS_SOCK, 26) // 8004061a +#define HXR_SOCK_ISCONN MAKE_HX_RESULT(1, SS_SOCK, 27) // 8004061b +#define HXR_SOCK_NOTCONN MAKE_HX_RESULT(1, SS_SOCK, 28) // 8004061c +#define HXR_SOCK_SHUTDOWN MAKE_HX_RESULT(1, SS_SOCK, 29) // 8004061d +#define HXR_SOCK_TOOMANYREFS MAKE_HX_RESULT(1, SS_SOCK, 30) // 8004061e +#define HXR_SOCK_TIMEDOUT MAKE_HX_RESULT(1, SS_SOCK, 31) // 8004061f +#define HXR_SOCK_CONNREFUSED MAKE_HX_RESULT(1, SS_SOCK, 32) // 80040620 +#define HXR_SOCK_LOOP MAKE_HX_RESULT(1, SS_SOCK, 33) // 80040621 +#define HXR_SOCK_NAMETOOLONG MAKE_HX_RESULT(1, SS_SOCK, 34) // 80040622 +#define HXR_SOCK_HOSTDOWN MAKE_HX_RESULT(1, SS_SOCK, 35) // 80040623 +#define HXR_SOCK_HOSTUNREACH MAKE_HX_RESULT(1, SS_SOCK, 36) // 80040624 +#define HXR_SOCK_PIPE MAKE_HX_RESULT(1, SS_SOCK, 37) // 80040625 +#define HXR_SOCK_ENDSTREAM MAKE_HX_RESULT(1, SS_SOCK, 38) // 80040626 +#define HXR_SOCK_BUFFERED MAKE_HX_RESULT(0, SS_SOCK, 39) // 00040627 + + +#define HXR_RSLV_NONAME MAKE_HX_RESULT(1, SS_RSLV, 0) // 80040640 +#define HXR_RSLV_NODATA MAKE_HX_RESULT(1, SS_RSLV, 1) // 80040641 + +#define SA_OFFSET 2 +#define MAKE_SA(sa) HXR_SE_MIN_VALUE+sa+SA_OFFSET +#define IS_SERVER_ALERT(sa) ((HXR_SE_MIN_VALUE < sa && sa < HXR_SE_MAX_VALUE) || sa == HXR_SERVER_ALERT) + + +#define HXR_FAILED HXR_FAIL + +#ifdef _WIN16 +/*typedef UINT MMRESULT;*/ +#else +#ifdef _WIN32 + +#if defined(WIN32_PLATFORM_PSPC) +#undef _HRESULT_TYPEDEF_ +#undef E_NOTIMPL +#undef E_OUTOFMEMORY +#undef E_INVALIDARG +#undef E_NOINTERFACE +#undef E_POINTER +#undef E_HANDLE +#undef E_ABORT +#undef E_FAIL +#undef E_ACCESSDENIED +#endif /* defined(WIN32_PLATFORM_PSPC) */ +#define _HRESULT_TYPEDEF_(_sc) ((HRESULT)_sc) +#define E_NOTIMPL _HRESULT_TYPEDEF_(0x80004001L) +#define E_OUTOFMEMORY _HRESULT_TYPEDEF_(0x8007000EL) +#define E_INVALIDARG _HRESULT_TYPEDEF_(0x80070057L) +#define E_NOINTERFACE _HRESULT_TYPEDEF_(0x80004002L) +#define E_POINTER _HRESULT_TYPEDEF_(0x80004003L) +#define E_HANDLE _HRESULT_TYPEDEF_(0x80070006L) +#define E_ABORT _HRESULT_TYPEDEF_(0x80004004L) +#define E_FAIL _HRESULT_TYPEDEF_(0x80004005L) +#define E_ACCESSDENIED _HRESULT_TYPEDEF_(0x80070005L) +#else +#define S_OK HXR_OK +#define E_NOTIMPL HXR_NOTIMPL +#define E_INVALIDARG HXR_INVALID_PARAMETER +#define E_NOINTERFACE HXR_NOINTERFACE +#define E_POINTER HXR_POINTER +#define E_HANDLE HXR_HANDLE +#define E_ABORT HXR_ABORT +#define E_FAIL HXR_FAIL +#define E_ACCESSDENIES HXR_ACCESSDENIED +#endif /* _WIN32 */ +#endif /* _WIN16 */ + +#define HX_STATUS_OK HXR_OK +#define HX_STATUS_FAILED E_FAIL + +#endif /* _HXRESULT_H_ */ diff --git a/amarok/src/engine/helix/helix-sp/helix-include/common/include/hxtbuf.h b/amarok/src/engine/helix/helix-sp/helix-include/common/include/hxtbuf.h new file mode 100644 index 00000000..d7e1b92c --- /dev/null +++ b/amarok/src/engine/helix/helix-sp/helix-include/common/include/hxtbuf.h @@ -0,0 +1,64 @@ + +/* + * + * This software is released under the provisions of the GPL version 2. + * see file "COPYING". If that file is not available, the full statement + * of the license can be found at + * + * http://www.fsf.org/licensing/licenses/gpl.txt + * + * Portions Copyright (c) 1995-2004 RealNetworks, Inc. All Rights Reserved. + * + */ + +#ifndef _HXTBUF_H_ +#define _HXTBUF_H_ + +/**************************************************************************** + * + * Interface: + * + * IHXTimeStampedBuffer + * + * Purpose: + * + * Basic opaque data storage buffer. Used in interfaces where + * object ownership is best managed through COM style reference + * counting. + * + * IID_IHXTimeStampedBuffer: + * + * {00000700-b4c8-11d0-9995-00a0248da5f0} + * + */ +DEFINE_GUID(IID_IHXTimeStampedBuffer, 0x00000700, 0xb4c8, 0x11d0, + 0x99, 0x95, 0x0, 0xa0, 0x24, 0x8d, 0xa5, 0xf0); + +#define CLSID_IHXTimeStampedBuffer IID_IHXTimeStampedBuffer + +#undef INTERFACE +#define INTERFACE IHXTimeStampedBuffer + +DECLARE_INTERFACE_(IHXTimeStampedBuffer, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXTimeStampedBuffer methods + */ + STDMETHOD_(UINT32,GetTimeStamp)(THIS) PURE; + + STDMETHOD(SetTimeStamp)(THIS_ + UINT32 ulTimeStamp) PURE; +}; + +#endif /* _HXTBUF_H_ */ diff --git a/amarok/src/engine/helix/helix-sp/helix-include/common/include/hxtypes.h b/amarok/src/engine/helix/helix-sp/helix-include/common/include/hxtypes.h new file mode 100644 index 00000000..32c19701 --- /dev/null +++ b/amarok/src/engine/helix/helix-sp/helix-include/common/include/hxtypes.h @@ -0,0 +1,730 @@ + +/* + * + * This software is released under the provisions of the GPL version 2. + * see file "COPYING". If that file is not available, the full statement + * of the license can be found at + * + * http://www.fsf.org/licensing/licenses/gpl.txt + * + * Portions Copyright (c) 1995-2004 RealNetworks, Inc. All Rights Reserved. + * + */ + +#if defined(_SYMBIAN) +# include <e32def.h> +# include <e32std.h> +# include <platform/symbian/symbiantypes.h> /* For our TInt64 impl */ +#endif + +#ifdef _VXWORKS +#include "types/vxTypesOld.h" +#include "vxWorks.h" + /* md3 - added to override SENS macro. net/mbuf.h */ +# ifdef m_flags +# undef m_flags +# endif /* m_flags */ + /* md3 - added to override SENS macro, net/radix.h */ +# ifdef Free +# undef Free +# endif /* Free */ +#endif + +#ifdef _MACINTOSH +#pragma once +#endif + +#ifndef _HXTYPES_H_ +#define _HXTYPES_H_ + +// this is essential to make sure that new is not #define'd before the C++ version is included. +#if (defined(_MSC_VER) && defined(_DEBUG) && defined(__cplusplus) && !defined(WIN32_PLATFORM_PSPC) && !defined(_SYMBIAN) && !defined(_OPENWAVE)) +#include <memory> +#endif + +// disable the "debug info truncated at 255" warning. +#if defined _MSC_VER +#pragma warning (disable: 4786) +#endif + +#if (defined(_MSC_VER) && (_MSC_VER > 1100) && defined(_BASETSD_H_)) +#error For VC++ 6.0 or higher you must include hxtypes.h before other windows header files. +#endif + +#if defined(_SYMBIAN) +typedef TInt8 INT8; +typedef TUint8 UINT8; +typedef TInt16 INT16; +typedef TUint16 UINT16; +typedef TInt32 INT32; +typedef TUint32 UINT32; +typedef TUint32 UINT; /* Its unclear, but UINT is suppose to be 32 bits. */ +typedef TBool BOOL; +#else +# ifndef _VXWORKS + +# if defined(QWS) && !defined(QT_CLEAN_NAMESPACE) +#error "You need to define QT_CLEAN_NAMESPACE when using Qt with Helix. If you don't you'll have conflicts with the Helix INT32, UINT32, and UINT definitions" +# endif + + typedef char INT8; /* signed 8 bit value */ + typedef unsigned char UINT8; /* unsigned 8 bit value */ + typedef short int INT16; /* signed 16 bit value */ + typedef unsigned short int UINT16; /* unsigned 16 bit value */ +# if (defined _UNIX && defined _LONG_IS_64) + typedef int INT32; /* signed 32 bit value */ + typedef unsigned int UINT32; /* unsigned 32 bit value */ + typedef unsigned int UINT; +# elif defined _VXWORKS + typedef int INT32; /* signed 32 bit value */ + typedef unsigned int UINT32; /* unsigned 32 bit value */ + typedef unsigned int UINT; +# else + typedef long int INT32; /* signed 32 bit value */ + typedef unsigned long int UINT32; /* unsigned 32 bit value */ + typedef unsigned int UINT; +# endif /* (defined _UNIX && (defined _ALPHA || OSF1)) */ + +# if (defined _UNIX && defined _IRIX) +# ifdef __LONG_MAX__ +# undef __LONG_MAX__ +# endif +# define __LONG_MAX__ 2147483647 +# endif + + +#endif /* _VXWORKS */ + +#endif /* _SYMBIAN */ + + +#if defined(HELIX_CONFIG_AVOID_BOOL) + typedef int HXBOOL; +# if defined(BOOL) +# undef BOOL +# endif +#else +# if defined(BOOL) + typedef BOOL HXBOOL; +# else + typedef int HXBOOL; + typedef HXBOOL BOOL; +# endif +#endif + + +#define ARE_BOOLS_EQUAL(a,b) (((a) && (b)) || (!(a) && !(b))) + +#ifndef HX_BITFIELD +typedef unsigned char HX_BITFIELD; +#endif + +typedef INT32 LONG32; /* signed 32 bit value */ +typedef UINT32 ULONG32; /* unsigned 32 bit value */ + + +#ifdef _LONG_IS_64 +typedef long int INT64; +typedef unsigned long int UINT64; +#elif defined(_WINDOWS) || defined(_OPENWAVE_SIMULATOR) +typedef __int64 INT64; +typedef unsigned __int64 UINT64; +#elif defined(_SYMBIAN) && !defined (_SYMBIAN_81_) +typedef SymInt64 INT64; +typedef SymInt64 UINT64; +#else +typedef long long INT64; +typedef unsigned long long UINT64; +#endif /* _WINDOWS */ + + +/* define the float and double type for all platforms */ +#define HXFLOAT float +#define HXDOUBLE double + +typedef ULONG32 HX_MOFTAG; + + + +/* Some platforms have native 64 bit int types, others don't + * so, we provide these casting macros that have to be used + * to cast 64-bit ints to smaller datatypes + */ +#if defined(_SYMBIAN) && !defined (_SYMBIAN_81_) +#define INT64_TO_ULONG32(a) ((ULONG32)((a).Low())) +#define INT64_TO_UINT32(a) ((UINT32)((a).Low())) +#define INT64_TO_INT32(a) ((INT32)((a).Low())) +#define INT64_TO_DOUBLE(a) ((a).GetTReal()) +#define INT64_TO_FLOAT(a) ((a).GetTReal()) +#define UINT32_TO_DOUBLE(a) (SymbianUINT32toDouble(a)) +#else +#define INT64_TO_ULONG32(a) ((ULONG32)(a)) +#define INT64_TO_UINT32(a) ((UINT32) (a)) +#define INT64_TO_INT32(a) ((INT32) (a)) +#define INT64_TO_DOUBLE(a) ((double) (a)) +#define INT64_TO_FLOAT(a) ((float) (a)) +#define UINT32_TO_DOUBLE(a) ((double) (a)) +#endif + + +#ifdef _MACINTOSH + #ifdef powerc + #define _MACPPC + #else + #define _MAC68K + #endif +#endif + +#if defined(_SYMBIAN) +#define PATH_MAX KMaxPath +#endif + +#ifdef __cplusplus +extern "C" { /* Assume C declarations for C++ */ +#endif /* __cplusplus */ + +#define LANGUAGE_CODE "EN" + +#ifdef _WIN16 +#define MAX_PATH 260 +#define PRODUCT_ID "play16" +#define PLUS_PRODUCT_ID "plus16" +#else +#define PRODUCT_ID "play32" +#define PLUS_PRODUCT_ID "plus32" +#endif + +// $Private: +#define DEFAULT_CONN_TIMEOUT 20 // in seconds +#define MAX_TIMESTAMP_GAP 0x2fffffff +#if !defined(MAX_UINT32) +#define MAX_UINT32 0xffffffff +#endif /* MAX_UINT32 */ +#if defined(_MACINTOSH) +#define kLetInterruptsFinishBeforeQuittingGestalt 'RN$~' +#endif +// $EndPrivate. + +#define MAX_DISPLAY_NAME 256 +#define HX_INVALID_VALUE (ULONG32)0xffffffff + +#define HX_FREE(x) ((x) ? (free (x), (x) = 0) : 0) + +#if defined(HELIX_CONFIG_NULL_DELETE_UNSAFE) + +#define HX_DELETE(x) ((x) ? (delete (x), (x) = 0) : 0) +#define HX_VECTOR_DELETE(x) ((x) ? (delete [] (x), (x) = 0) : 0) + +#else // defined(HELIX_CONFIG_NULL_DELETE_UNSAFE) + +#define HX_DELETE(x) (delete (x), (x) = 0) +#define HX_VECTOR_DELETE(x) (delete [] (x), (x) = 0) + +#endif // defined(HELIX_CONFIG_NULL_DELETE_UNSAFE) + +#define RA_FILE_MAGIC_NUMBER 0x2E7261FDL /* RealAudio File Identifier */ +#define RM_FILE_MAGIC_NUMBER 0x2E524D46L /* RealMedia File Identifier */ +#define RIFF_FILE_MAGIC_NUMBER 0x52494646L /* RIFF (AVI etc.) File Identifier */ + +#ifndef _VXWORKS +typedef UINT8 UCHAR; /* unsigned 8 bit value */ +#endif +typedef INT8 CHAR; /* signed 8 bit value */ + +typedef UINT8 BYTE; + +typedef INT32 long32; +typedef UINT32 u_long32; + +#ifndef _MACINTOSH +typedef INT8 Int8; +#endif +typedef UINT8 u_Int8; +typedef INT16 Int16; +typedef UINT16 u_Int16; +typedef INT32 Int32; +typedef UINT32 u_Int32; + +/* + * XXXGo + * deprecated...now that we need UFIXED and FIXED, this name is confusing... + * use the ones below. + */ +typedef ULONG32 UFIXED32; /* FIXED point value */ +#define FLOAT_TO_FIXED(x) ((UFIXED32) ((x) * (1L << 16) + 0.5)) +#define FIXED_TO_FLOAT(x) ((float) ((((float)x)/ (float)(1L <<16)))) + +/* + * float and fixed point value conversion + */ +#define HX_FLOAT_TO_UFIXED(x) ((UFIXED32) ((x) * (1L << 16) + 0.5)) +#define HX_UFIXED_TO_FLOAT(x) ((float) ((((float)x)/ (float)(1L <<16)))) + +typedef LONG32 FIXED32; /* FIXED point value */ +#define HX_FLOAT_TO_FIXED(x) ((FIXED32) ((x) * (1L << 16) + 0.5)) +#define HX_FIXED_TO_FLOAT(x) ((float) ((((float)x)/ (float)(1L <<16)))) + + +/* + * UFIXED32 is a 32 value where the upper 16 bits are the unsigned integer + * portion of value, and the lower 16 bits are the fractional part of the + * value + */ + +typedef const char* PCSTR; + +/* + * FOURCC's are 32bit codes used in Tagged File formats like + * the RealMedia file format. + */ +#ifndef FOURCC +typedef UINT32 FOURCC; +#endif + +#ifndef HX_FOURCC +#define HX_FOURCC( ch0, ch1, ch2, ch3 ) \ + ( (UINT32)(UINT8)(ch0) | ( (UINT32)(UINT8)(ch1) << 8 ) | \ + ( (UINT32)(UINT8)(ch2) << 16 ) | ( (UINT32)(UINT8)(ch3) << 24 ) ) +#endif + +typedef UINT16 PrefKey; + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#ifdef TRUE +#undef TRUE +#endif + +#ifdef FALSE +#undef FALSE +#endif + +#ifndef TRUE +#define TRUE 1 +#endif + +#ifndef FALSE +#define FALSE 0 +#endif + +#ifndef NULL +#ifdef __cplusplus +#define NULL 0 +#else +#define NULL ((void *)0) +#endif +#endif + +#ifndef _WINDOWS /* defined in windef.h on Windows platform */ +#ifndef HIWORD +#define HIWORD(x) ((x) >> 16) +#endif +#ifndef LOWORD +#define LOWORD(x) ((x) & 0xffff) +#endif +#endif + +#ifndef NOMINMAX +/* Always use macro versions of these! */ +#ifndef max +#define max(a, b) (((a) > (b)) ? (a) : (b)) +#endif +#ifndef min +#define min(a, b) (((a) < (b)) ? (a) : (b)) +#endif +#endif + +/* Should use capitalized macro versions of these, as the lowercase + versions conflict with the STL spec */ +//get these from sys/param.h to avoid tons of warnings about redefining them: +#ifdef _UNIX +#include <sys/param.h> +#endif +#ifndef MAX +#define MAX(a, b) (((a) > (b)) ? (a) : (b)) +#endif +#ifndef MIN +#define MIN(a, b) (((a) < (b)) ? (a) : (b)) +#endif + +#define HX_MAX(a, b) (((a) > (b)) ? (a) : (b)) +#define HX_MIN(a, b) (((a) < (b)) ? (a) : (b)) + +/*-------------------------------------------------------------------------- +| ZeroInit - initializes a block of memory with zeros +--------------------------------------------------------------------------*/ +#define ZeroInit(pb) memset((void *)pb,0,sizeof(*(pb))) + +#ifndef __MACTYPES__ +typedef unsigned char Byte; +#endif + +/* +///////////////////////////////////////////////////////////////////////////// +// HXEXPORT needed for RA.H and RAGUI.H, should be able to be defined +// and used in cross platform code... +///////////////////////////////////////////////////////////////////////////// +*/ +#if defined(_WIN32) || defined(_WINDOWS) +#ifdef _WIN32 +#define HXEXPORT __declspec(dllexport) __stdcall +#define HXEXPORT_PTR __stdcall * +#else /* Windows, but not 32 bit... */ +#define HXEXPORT _pascal __export +#define HXEXPORT_PTR _pascal * +#define WAVE_FORMAT_PCM 1 +#define LPCTSTR LPCSTR +#endif +#else /* Not Windows... */ + + + + +#define HXEXPORT +#define HXEXPORT_PTR * +#endif + +#if defined(_WIN32) || defined(_WINDOWS) || defined (_MACINTOSH)|| defined (_UNIX) +typedef void (*RANOTIFYPROC)( void* ); +#endif + +#if defined(EXPORT_CLASSES) && defined(_WINDOWS) +#ifdef _WIN32 +#define HXEXPORT_CLASS __declspec(dllexport) +#else +#define HXEXPORT_CLASS __export +#endif // _WIN32 +#else +#define HXEXPORT_CLASS +#endif // EXPORT_CLASSES + + +/* + * STDMETHODCALLTYPE + */ +#ifndef STDMETHODCALLTYPE +#if defined(_WIN32) || defined(_MPPC_) +#ifdef _MPPC_ +#define STDMETHODCALLTYPE __cdecl +#else +#define STDMETHODCALLTYPE __stdcall +#endif +#elif defined(_WIN16) +// XXXTW I made the change below on 5/18/98. The __export was causing +// conflicts with duplicate CHXBuffer methods in being linked into +// rpupgrd and rpdestpn. Also, the warning was "export imported". +// This was fixed by removing the __export. The __export is also +// causing the same problem in pndebug methods. +//#define STDMETHODCALLTYPE __export far _cdecl +#define STDMETHODCALLTYPE far _cdecl +#else +#define STDMETHODCALLTYPE +#endif +#endif + +/* + * STDMETHODVCALLTYPE (V is for variable number of arguments) + */ +#ifndef STDMETHODVCALLTYPE +#if defined(_WINDOWS) || defined(_MPPC_) +#define STDMETHODVCALLTYPE __cdecl +#else +#define STDMETHODVCALLTYPE +#endif +#endif + +/* + * STDAPICALLTYPE + */ +#ifndef STDAPICALLTYPE +#if defined(_WIN32) || defined(_MPPC_) +#define STDAPICALLTYPE __stdcall +#elif defined(_WIN16) +#define STDAPICALLTYPE __export FAR PASCAL +#else +#define STDAPICALLTYPE +#endif +#endif + +/* + * STDAPIVCALLTYPE (V is for variable number of arguments) + */ +#ifndef STDAPIVCALLTYPE +#if defined(_WINDOWS) || defined(_MPPC_) +#define STDAPIVCALLTYPE __cdecl +#else +#define STDAPIVCALLTYPE +#endif +#endif + +/* +///////////////////////////////////////////////////////////////////////////// +// +// Macro: +// +// HX_GET_MAJOR_VERSION() +// +// Purpose: +// +// Returns the Major version portion of the encoded product version +// of the RealAudio application interface DLL previously returned from +// a call to RaGetProductVersion(). +// +// Parameters: +// +// prodVer +// The encoded product version of the RealAudio application interface +// DLL previously returned from a call to RaGetProductVersion(). +// +// Return: +// +// The major version number of the RealAudio application interface DLL +// +// +*/ +#define HX_GET_MAJOR_VERSION(prodVer) ((prodVer >> 28) & 0xF) + +/* +///////////////////////////////////////////////////////////////////////////// +// +// Macro: +// +// HX_GET_MINOR_VERSION() +// +// Purpose: +// +// Returns the minor version portion of the encoded product version +// of the RealAudio application interface DLL previously returned from +// a call to RaGetProductVersion(). +// +// Parameters: +// +// prodVer +// The encoded product version of the RealAudio application interface +// DLL previously returned from a call to RaGetProductVersion(). +// +// Return: +// +// The minor version number of the RealAudio application interface DLL +// +// +*/ +#define HX_GET_MINOR_VERSION(prodVer) ((prodVer >> 20) & 0xFF) + +/* +///////////////////////////////////////////////////////////////////////////// +// +// Macro: +// +// HX_GET_RELEASE_NUMBER() +// +// Purpose: +// +// Returns the release number portion of the encoded product version +// of the RealAudio application interface DLL previously returned from +// a call to RaGetProductVersion(). +// +// Parameters: +// +// prodVer +// The encoded product version of the RealAudio application interface +// DLL previously returned from a call to RaGetProductVersion(). +// +// Return: +// +// The release number of the RealAudio application interface DLL +// +// +*/ +#define HX_GET_RELEASE_NUMBER(prodVer) ((prodVer >> 12) & 0xFF) + +/* +///////////////////////////////////////////////////////////////////////////// +// +// Macro: +// +// HX_GET_BUILD_NUMBER() +// +// Purpose: +// +// Returns the build number portion of the encoded product version +// of the RealAudio application interface DLL previously returned from +// a call to RaGetProductVersion(). +// +// Parameters: +// +// prodVer +// The encoded product version of the RealAudio application interface +// DLL previously returned from a call to RaGetProductVersion(). +// +// Return: +// +// The build number of the RealAudio application interface DLL +// +// +*/ +#define HX_GET_BUILD_NUMBER(prodVer) (prodVer & 0xFFF) + +/* +///////////////////////////////////////////////////////////////////////////// +// +// Macro: +// +// HX_ENCODE_PROD_VERSION() +// +// Purpose: +// +// Encodes a major version, minor version, release number, and build +// number into a product version for testing against the product version +// of the RealAudio application interface DLL returned from a call to +// RaGetProductVersion(). +// +// Parameters: +// +// major +// The major version number to encode. +// +// mimor +// The minor version number to encode. +// +// release +// The release number to encode. +// +// build +// The build number to encode. +// +// Return: +// +// The encoded product version. +// +// NOTES: +// +// Macintosh DEVELOPERS especially, make sure when using the HX_ENCODE_PROD_VERSION +// that you are passing a ULONG32 or equivalent for each of the parameters. +// By default a number passed in as a constant is a short unless it requires more room, +// so designate the constant as a long by appending a L to the end of it. +// Example: +// WORKS: +// HX_ENCODE_VERSION(2L,1L,1L,0L); +// +// DOES NOT WORK: +// HX_ENCODE_VERSION(2,1,1,0); +// +*/ + +#define HX_ENCODE_PROD_VERSION(major,minor,release,build) \ + ((ULONG32)((ULONG32)major << 28) | ((ULONG32)minor << 20) | \ + ((ULONG32)release << 12) | (ULONG32)build) + +#define HX_ENCODE_ADD_PRIVATE_FIELD(ulversion,ulprivate) \ + ((ULONG32)((ULONG32)(ulversion) & (UINT32)0xFFFFFF00) | (ULONG32)(ulprivate) ) + +#define HX_EXTRACT_PRIVATE_FIELD(ulversion)(ulversion & (UINT32)0xFF) + +#define HX_EXTRACT_MAJOR_VERSION(ulversion) ((ulversion)>>28) +#define HX_EXTRACT_MINOR_VERSION(ulversion) (((ulversion)>>20) & (UINT32)0xFF) + +#ifdef _AIX + typedef int tv_sec_t; + typedef int tv_usec_t; +#elif (defined _HPUX) + typedef UINT32 tv_sec_t; + typedef INT32 tv_usec_t; +#else + typedef INT32 tv_sec_t; + typedef INT32 tv_usec_t; +#endif /* _AIX */ + +#ifndef VOLATILE +#define VOLATILE volatile +#endif + +typedef ULONG32 HXXRESOURCE; +typedef ULONG32 HXXHANDLE; +typedef ULONG32 HXXIMAGE; + +// Macro which indicates that a particular variable is unused. Use this to +// avoid compiler warnings. +#define UNUSED(x) + +/* + * For VC++ 6.0 and higher we need to include this substitute header file + * in place of the standard header file basetsd.h, since this standard + * header file conflicts with our definitions. + */ +#if defined(_MSC_VER) && (_MSC_VER > 1100) && !defined(_SYMBIAN) +#include "hxbastsd.h" +#ifdef WIN32_PLATFORM_PSPC +#define _TYPES_H_ +#endif +#endif + +#ifdef _VXWORKS +/* throw in some defines for VXWORKS */ +#define MAXPATHLEN 255 + + +#endif + +#ifdef _MACINTOSH +// xxxbobclark with CWPro 7, there is a code generation bug in the long long +// casting code. It can be avoided by first casting to an unsigned long long. +// This is supposedly fixed in the upcoming (as of this writing) CWPro 8, but +// for now we're setting up the casting using a macro to change it easily. +#define CAST_TO_INT64 (INT64)(UINT64) +#else +#define CAST_TO_INT64 (INT64) +#endif + + +#if defined _LONG_IS_64 && (defined _OSF1 || defined _SOLARIS || defined _HPUX) +typedef unsigned long PTR_INT; +#else +typedef unsigned int PTR_INT; +#endif + + +/* +///////////////////////////////////////////////////////////////////////////// +// +// Macro: +// +// DEFINE_CONSTANT_STRING() +// +// Purpose: +// +// declare a constant string as "const char *" or "extern const char *" depending on whether or +// not INITGUID is defined. This allows constant strings to be safely added +// to a header file without risk of multiply defined symbols if that header is included in a DLL +// and in a library that the DLL links to. While INITGUID doesn't have anything to do with constant +// strings, it is a great constant to switch off of because it is already used to denote when a GUID +// should be defined as external. +// +// Parameters: +// +// name +// The name of the variable to which the constant string will be assigned +// +// string +// the constant string (in quotes). +// +// +// +// NOTES: +// There is currently no way to use DEFINE_CONSTANT_STRING() to declare an "extern const char*" when +// INITGUID is defined. This functionality could be added, but until there is a use case, it would just +// make things more complicated. +// +*/ +#if defined (INITGUID) +#define DEFINE_CONSTANT_STRING(name, string) \ + const char *name = string; +#else +#define DEFINE_CONSTANT_STRING(name, string) \ + extern const char* name; +#endif + + +#endif /* _HXTYPES_H_ */ diff --git a/amarok/src/engine/helix/helix-sp/helix-include/common/include/hxvalue.h b/amarok/src/engine/helix/helix-sp/helix-include/common/include/hxvalue.h new file mode 100644 index 00000000..6d201641 --- /dev/null +++ b/amarok/src/engine/helix/helix-sp/helix-include/common/include/hxvalue.h @@ -0,0 +1,379 @@ + +/* + * + * This software is released under the provisions of the GPL version 2. + * see file "COPYING". If that file is not available, the full statement + * of the license can be found at + * + * http://www.fsf.org/licensing/licenses/gpl.txt + * + * Portions Copyright (c) 1995-2004 RealNetworks, Inc. All Rights Reserved. + * + */ + +#ifndef _HXVALUE_H_ +#define _HXVALUE_H_ + +#include "hxcom.h" + +/* + * Forward declarations of some interfaces defined or used here-in. + */ +typedef _INTERFACE IUnknown IUnknown; +typedef _INTERFACE IHXBuffer IHXBuffer; +typedef _INTERFACE IHXKeyValueList IHXKeyValueList; +typedef _INTERFACE IHXKeyValueListIter IHXKeyValueListIter; +typedef _INTERFACE IHXKeyValueListIterOneKey IHXKeyValueListIterOneKey; +typedef _INTERFACE IHXValues IHXValues; +typedef _INTERFACE IHXOptions IHXOptions; + +/* Note : GUIDS 3101 - 3107 are deprecated. */ + +/**************************************************************************** + * + * Interface: + * + * IHXKeyValueList + * + * Purpose: + * + * Stores a list of strings, where strings are keyed by not necessarily + * unique keys. + * + * + * IHXKeyValueList: + * + * {0x00003108-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXKeyValueList, 0x00003108, 0x901, 0x11d1, + 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59); +#define CLSID_IHXKeyValueList IID_IHXKeyValueList + +#undef INTERFACE +#define INTERFACE IHXKeyValueList + +DECLARE_INTERFACE_(IHXKeyValueList, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * Regular methods + */ + + /************************************************************************ + * Method: + * IHXKeyValueList::AddKeyValue + * Purpose: + * Add a new key/value tuple to our list of strings. You can have + * multiple strings for the same key. + */ + STDMETHOD(AddKeyValue) (THIS_ + const char* pKey, + IHXBuffer* pStr) PURE; + + /************************************************************************ + * Method: + * IHXKeyValueList::GetIter + * Purpose: + * Return an iterator that allows you to iterate through all the + * key/value tuples in our list of strings. + */ + STDMETHOD(GetIter) (THIS_ + REF(IHXKeyValueListIter*) pIter) PURE; + + + /************************************************************************ + * Method: + * IHXKeyValueList::GetIterOneKey + * Purpose: + * Return an iterator that allows you to iterate through all the + * strings for a particular key. + */ + STDMETHOD(GetIterOneKey) (THIS_ + const char* pKey, + REF(IHXKeyValueListIterOneKey*) pIter) PURE; + + /************************************************************************ + * Method: + * IHXKeyValueList::AppendAllListItems + * Purpose: + * Append all the key/string tuples from another list to this list. + * (You can have duplicate keys.) + */ + STDMETHOD(AppendAllListItems) (THIS_ + IHXKeyValueList* pList) PURE; + /************************************************************************ + * Method: + * IHXKeyValueList::KeyExists + * Purpose: + * See whether any strings exist for a particular key. + */ + STDMETHOD_(HXBOOL,KeyExists) (THIS_ + const char* pKey) PURE; + + /************************************************************************ + * Method: + * IHXKeyValueList::CreateObject + * Purpose: + * Create an empty object that is the same class as the current object. + */ + STDMETHOD(CreateObject) (THIS_ + REF(IHXKeyValueList*) pNewList) PURE; + + /************************************************************************ + * Method: + * IHXKeyValueList::ImportValues. + * Purpose: + * Import all the strings from an IHXValues object into this object. + * If this object also supports IHXValues, it should also import the + * ULONGs and Buffers. You can have duplicate keys, and old data is + * left untouched. + */ + STDMETHOD(ImportValues) (THIS_ + IHXValues* pValues) PURE; +}; + + +/**************************************************************************** + * + * Interface: + * + * IHXKeyValueListIter + * + * Purpose: + * + * Iterate over all the items in a CKeyValueList. + * Call IHXKeyValueList::GetIter to create an iterator. + * + * + * IHXKeyValueListIter: + * + * {0x00003109-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXKeyValueListIter, 0x00003109, 0x901, 0x11d1, + 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#define CLSID_IHXKeyValueListIter IID_IHXKeyValueListIter + +#undef INTERFACE +#define INTERFACE IHXKeyValueListIter + +DECLARE_INTERFACE_(IHXKeyValueListIter, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + + /* + * Regular methods + */ + + /************************************************************************ + * Method: + * IHXKeyValueListIter::GetNextPair + * Purpose: + * Each call to this method returns one key/value tuple from your + * list of strings. Strings are returned in same order that they + * were inserted. + */ + STDMETHOD(GetNextPair) (THIS_ + REF(const char*) pKey, + REF(IHXBuffer*) pStr) PURE; + + /************************************************************************ + * Method: + * IHXKeyValueListIter::ReplaceCurr + * Purpose: + * Replaces the value in the key/value tuple that was returned + * in the last call to GetNextPair with a new string. + */ + STDMETHOD(ReplaceCurr) (THIS_ + IHXBuffer* pStr) PURE; +}; + + +/**************************************************************************** + * + * Interface: + * + * IHXKeyValueListIterOneKey + * + * Purpose: + * + * Iterate over all the items in a CKeyValueList that match a particular key. + * Call IHXKeyValueList::GetIterOneKey to create an iterator. + * + * + * IHXKeyValueListIterOneKey: + * + * {0x00003110-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXKeyValueListIterOneKey, 0x00003110, 0x901, 0x11d1, + 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#define CLSID_IHXKeyValueListIterOneKey IID_IHXKeyValueListIterOneKey + +#undef INTERFACE +#define INTERFACE IHXKeyValueListIterOneKey + +DECLARE_INTERFACE_(IHXKeyValueListIterOneKey, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + + /* + * Regular methods + */ + + /************************************************************************ + * Method: + * IHXKeyValueListIterOneKey::GetNextString + * Purpose: + * Each call to this method returns one string that matches the + * key for this iterator. Strings are returned in same order that they + * were inserted. + * + */ + STDMETHOD(GetNextString) (THIS_ + REF(IHXBuffer*) pStr) PURE; + + /************************************************************************ + * Method: + * IHXKeyValueListIterOneKey::ReplaceCurr + * Purpose: + * Replaces the value in the key/value tuple that was referenced + * in the last call to GetNextString with a new string. + * + */ + STDMETHOD(ReplaceCurr) (THIS_ + IHXBuffer* pStr) PURE; +}; + + +/**************************************************************************** + * + * Interface: + * + * IHXOptions + * + * Purpose: + * + * This is a generic options interface, implemented by any object to + * allow its options to be read and set by another component of the + * system. + * + * + * IHXOptions: + * + * {0x00003111-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXOptions, 0x00003111, 0x901, 0x11d1, + 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#define CLSID_IHXOptions IID_IHXOptions + +#undef INTERFACE +#define INTERFACE IHXOptions + +DECLARE_INTERFACE_(IHXOptions, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + + /* + * Regular methods + */ + + /************************************************************************ + * Method: + * IHXOptions::GetOptions + * Purpose: + * This method returns a list of the options supported by this + * particular object, along with the value currently set for each + * option. Enumerate the members of the returned IHXValues object + * to discover what options a component supports and the type of + * each of those options. The value for each name-value pair is + * the current setting for that option. + * + */ + STDMETHOD(GetOptions) (THIS_ + REF(IHXValues*) pOptions) PURE; + + /************************************************************************ + * Method: + * IHXOptions::SetOptionULONG32 + * Purpose: + * Sets the value of a ULONG32 option. The return value indicates + * whether or not the SetOptionULONG32 call succeeded. + * + */ + STDMETHOD(SetOptionULONG32) (THIS_ + const char* pName, + ULONG32 ulValue) PURE; + + /************************************************************************ + * Method: + * IHXOptions::SetOptionCString + * Purpose: + * Sets the value of a CString option. The return value indicates + * whether or not the SetOptionCString call succeeded. + * + */ + STDMETHOD(SetOptionCString) (THIS_ + const char* pName, + IHXBuffer* pValue) PURE; + + /************************************************************************ + * Method: + * IHXOptions::SetOptionBuffer + * Purpose: + * Sets the value of a Buffer option. The return value indicates + * whether or not the SetOptionBuffer call succeeded. + * + */ + STDMETHOD(SetOptionBuffer) (THIS_ + const char* pName, + IHXBuffer* pValue) PURE; +}; + + +#endif /* !_HXVALUE_H_ */ diff --git a/amarok/src/engine/helix/helix-sp/helix-include/common/include/hxvsrc.h b/amarok/src/engine/helix/helix-sp/helix-include/common/include/hxvsrc.h new file mode 100644 index 00000000..747aa319 --- /dev/null +++ b/amarok/src/engine/helix/helix-sp/helix-include/common/include/hxvsrc.h @@ -0,0 +1,297 @@ + +/* + * + * This software is released under the provisions of the GPL version 2. + * see file "COPYING". If that file is not available, the full statement + * of the license can be found at + * + * http://www.fsf.org/licensing/licenses/gpl.txt + * + * Portions Copyright (c) 1995-2004 RealNetworks, Inc. All Rights Reserved. + * + */ + +#ifndef _HXVSRC_H +#define _HXVSRC_H + +typedef _INTERFACE IHXStreamSource IHXStreamSource; +typedef _INTERFACE IHXFileObject IHXFileObject; + +// Interfaces definded in this file +typedef _INTERFACE IHXFileViewSource IHXFileViewSource; +typedef _INTERFACE IHXFileViewSourceResponse IHXFileViewSourceResponse; +typedef _INTERFACE IHXViewSourceCommand IHXViewSourceCommand; +typedef _INTERFACE IHXViewSourceURLResponse IHXViewSourceURLResponse; + +// $Private: +typedef _INTERFACE IHXClientViewSource IHXClientViewSource; +typedef _INTERFACE IHXClientViewSourceSink IHXClientViewSourceSink; +// $EndPrivate. + + + +/**************************************************************************** + * + * Interface: + * + * IHXFileViewSource + * + * IID_IHXFileViewSource: + * + * {00003500-0901-11d1-8B06-00A024406D59} + * + */ + +enum SOURCE_TYPE +{ + RAW_SOURCE, + HTML_SOURCE +}; + +DEFINE_GUID(IID_IHXFileViewSource, 0x00003500, 0x901, 0x11d1, 0x8b, 0x6, + 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXFileViewSource + +DECLARE_INTERFACE_(IHXFileViewSource, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ REFIID riid, void** ppvObj) PURE; + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /************************************************************************ + * IHXFileViewSource + */ + STDMETHOD(InitViewSource) (THIS_ + IHXFileObject* /*IN*/ pFileObject, + IHXFileViewSourceResponse* /*IN*/ pResp, + SOURCE_TYPE /*IN*/ sourceType, + IHXValues* /*IN*/ pOptions) PURE; + STDMETHOD(GetSource) (THIS) PURE; + STDMETHOD(Close) (THIS) PURE; +}; + +/**************************************************************************** + * + * Interface: + * + * IHXFileViewSourceResponse + * + * IID_IHXFileViewSourceResponse: + * + * {00003501-0901-11d1-8B06-00A024406D59} + * + */ + +DEFINE_GUID(IID_IHXFileViewSourceResponse, 0x00003501, 0x901, 0x11d1, 0x8b, + 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXFileViewSourceResponse + +DECLARE_INTERFACE_(IHXFileViewSourceResponse, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ REFIID riid, void** ppvObj) PURE; + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /************************************************************************ + * IHXFileViewSourceResoponse + */ + STDMETHOD(InitDone) (THIS_ HX_RESULT status ) PURE; + STDMETHOD(SourceReady) (THIS_ HX_RESULT status, + IHXBuffer* pSource ) PURE; + STDMETHOD(CloseDone) (THIS_ HX_RESULT) PURE; +}; + +/**************************************************************************** + * + * Interface: + * + * IHXViewSourceCommand + * + * IID_IHXViewSourceCommand: + * + * {00003504-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXViewSourceCommand, 0x00003504, 0x901, 0x11d1, 0x8b, 0x6, + 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXViewSourceCommand + +DECLARE_INTERFACE_(IHXViewSourceCommand, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ REFIID riid, void** ppvObj) PURE; + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /************************************************************************ + * IHXViewSourceCommand + */ + STDMETHOD_(HXBOOL, CanViewSource) (THIS_ + IHXStreamSource* pStream) PURE; + STDMETHOD(DoViewSource) (THIS_ + IHXStreamSource* pStream) PURE; + STDMETHOD(GetViewSourceURL) (THIS_ + IHXStreamSource* pSource, + IHXViewSourceURLResponse* pResp) PURE; +}; + +/**************************************************************************** + * + * Interface: + * + * IHXViewSourceURLResponse + * + * IID_IHXViewSourceURLResponse: + * + * {00003505-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXViewSourceURLResponse, 0x00003505, 0x901, 0x11d1, 0x8b, 0x6, + 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXViewSourceURLResponse + +DECLARE_INTERFACE_(IHXViewSourceURLResponse, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ REFIID riid, void** ppvObj) PURE; + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /************************************************************************ + * IHXViewSourceURLResponse + */ + STDMETHOD(ViewSourceURLReady) (THIS_ + const char* /*out*/ pUrl) PURE; +}; + +// $Private: +/**************************************************************************** + * + * Interface: + * + * IHXClientViewSource + * + * IID_IHXClientViewSource: + * + * {00003502-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXClientViewSource, 0x00003502, 0x901, 0x11d1, 0x8b, 0x6, + 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXClientViewSource + +DECLARE_INTERFACE_(IHXClientViewSource, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ REFIID riid, void** ppvObj) PURE; + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /************************************************************************ + * IHXClientViewSource + */ + STDMETHOD(DoViewSource) (THIS_ + IUnknown* /*IN*/ pPlayerContext, + IHXStreamSource* /*IN*/ pSource) PURE; + STDMETHOD_(HXBOOL, CanViewSource) (THIS_ + IHXStreamSource* /*IN*/ pSource) PURE; + + STDMETHOD(GetViewSourceURL) (THIS_ + IUnknown* pPlayerContext, + IHXStreamSource* pSource, + IHXViewSourceURLResponse* pResp) PURE; + +}; + +/**************************************************************************** + * + * Interface: + * + * IHXClientViewSourceSink + * + * IID_IHXClientViewSourceSink: + * + * {00003503-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXClientViewSourceSink, 0x00003503, 0x901, 0x11d1, 0x8b, 0x6, + 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXClientViewSourceSink + +DECLARE_INTERFACE_(IHXClientViewSourceSink, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ REFIID riid, void** ppvObj) PURE; + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /************************************************************************ + * IHXClientViewSourceSink + */ + STDMETHOD(RegisterViewSourceHdlr) (THIS_ + IHXClientViewSource* /*in*/ pViewSourceHdlr) PURE; +}; + +/**************************************************************************** + * + * Interface: + * + * IHXClientViewRights + * + * IID_IHXClientViewRights: + * + * {00003506-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXClientViewRights, 0x00003506, 0x901, 0x11d1, 0x8b, 0x6, + 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXClientViewRights + +DECLARE_INTERFACE_(IHXClientViewRights, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ REFIID riid, void** ppvObj) PURE; + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /************************************************************************ + * IHXClientViewRights + */ + STDMETHOD(ViewRights) (THIS_ + IUnknown* /*IN*/ pPlayerContext) PURE; + STDMETHOD_(HXBOOL, CanViewRights) (THIS) PURE; + +}; +// $EndPrivate. + +#endif diff --git a/amarok/src/engine/helix/helix-sp/helix-include/common/include/hxwin.h b/amarok/src/engine/helix/helix-sp/helix-include/common/include/hxwin.h new file mode 100644 index 00000000..d7cd38fd --- /dev/null +++ b/amarok/src/engine/helix/helix-sp/helix-include/common/include/hxwin.h @@ -0,0 +1,1703 @@ + +/* + * + * This software is released under the provisions of the GPL version 2. + * see file "COPYING". If that file is not available, the full statement + * of the license can be found at + * + * http://www.fsf.org/licensing/licenses/gpl.txt + * + * Portions Copyright (c) 1995-2004 RealNetworks, Inc. All Rights Reserved. + * + */ + +#ifndef _HXWIN_H_ +#define _HXWIN_H_ + +/* + * Forward declarations of some interfaces defined or used here-in. + */ +typedef _INTERFACE IHXSite IHXSite; +typedef _INTERFACE IHXSiteUser IHXSiteUser; +typedef _INTERFACE IHXSiteWindowed IHXSiteWindowed; +typedef _INTERFACE IHXSiteEventHandler IHXSiteEventHandler; +typedef _INTERFACE IHXSiteWindowless IHXSiteWindowless; +typedef _INTERFACE IHXSiteWatcher IHXSiteWatcher; +typedef _INTERFACE IHXValues IHXValues; +typedef _INTERFACE IHXSiteFullScreen IHXSiteFullScreen; +typedef _INTERFACE IHXLayoutSiteGroupManager IHXLayoutSiteGroupManager; +typedef _INTERFACE IHXEventHook IHXEventHook; +typedef _INTERFACE IHXColorConverter IHXColorConverter; +typedef _INTERFACE IHXSubRectVideoSurface IHXSubRectVideoSurface; + +typedef struct _HXBitmapInfoHeader HXBitmapInfoHeader; +typedef struct _HXxWindow HXxWindow; +typedef struct _HXxRegion HXxBoxRegion; +typedef struct _HXxSize HXxSize; +typedef struct _HXxPoint HXxPoint; +typedef struct _HXxRect HXxRect; +typedef void* HXxRegion; + + +/* + * Styles for IHXDrawFocus + */ +#define HX_SOLID_LINE 1 +#define HX_DASHED_LINE HX_SOLID_LINE<<1 +#define HX_DOTTED_LINE HX_SOLID_LINE<<2 +#define HX_CUSTOM_LINE HX_SOLID_LINE<<3 + +/* + * Focus Navigation + */ +typedef enum _HXFocusContext +{ + HXFirstFocus, + HXUpFocus, + HXDownFocus, + HXLeftFocus, + HXRightFocus, + HXNextFocus, + HXPrevFocus, + HXLastFocus +} HXFocusContext; + +typedef enum _HXFocusState +{ + HXNoFocus, + HXFocused +} HXFocusState; + +/**************************************************************************** + * + * Interface: + * + * IHXSiteWindowed + * + * Purpose: + * + * Interface for IHXSite objects which are associated with platform + * specific window objects on Microsoft Windows and X-Windows. + * + * IID_IHXSiteWindowed: + * + * {00000D01-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXSiteWindowed, 0x00000D01, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#define CLSID_IHXSiteWindowed IID_IHXSiteWindowed + +// $Private: +DEFINE_GUID(IID_IHXGetImplementation, 0x00000D11, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); +// $EndPrivate. + +#undef INTERFACE +#define INTERFACE IHXSiteWindowed + +DECLARE_INTERFACE_(IHXSiteWindowed, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXSiteWindowed methods called by site suppliers + * when they want the site associated with a + * previously created (and externally managed) window. + * This method will "sub-class" that window (Win32). + * On Unix, the site supplier must pass events from + * the externally managed window to the core via + * IHXClientEngine::EventOccurred(). Please note that + * The HXxWindow ptr must remain in scope for the life + * of Site. + * + */ + STDMETHOD(AttachWindow) (THIS_ + HXxWindow* /*IN*/ pWindow) PURE; + + STDMETHOD(DetachWindow) (THIS) PURE; + + /* + * IHXSiteWindowed methods called by Owners of the site + * in the event that want a default top level window created + * for the site. + */ + STDMETHOD(Create) (THIS_ + void* ParentWindow, + UINT32 style) PURE; + + STDMETHOD(Destroy) (THIS) PURE; + + /* + * IHXSiteWindowed method. Returns actual window of the site. + */ + STDMETHOD_(HXxWindow*,GetWindow)(THIS) PURE; +}; + +// $Private: +/**************************************************************************** + * + * Interface: + * + * IHXSiteEventHandler + * + * Purpose: + * + * Interface for allowing client core engine to pass events to a site imeplementor + * which it implemented as a factory plugin. + * + * IID_IHXSiteEventHandler + * + * {00000D12-0901-11d1-8B-6-00A024406D59} + * + */ + +DEFINE_GUID(IID_IHXSiteEventHandler, 0x00000D12, 0x901, 0x11d1, 0x8b, 0x6, + 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#define CLSID_IHXSiteEventHandler IID_IHXSiteEventHandler + +#undef INTERFACE +#define INTERFACE IHXSiteEventHandler + +DECLARE_INTERFACE_(IHXSiteEventHandler, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * This method is called from Site Manager EventOccured(). + * The imeplementation of this interface must pass the events + * on to the individual CHXSiteWindowed sites. + */ + STDMETHOD(EventOccurred) (THIS_ HXxEvent* pEvent) PURE; +}; +// $EndPrivate. + + +/**************************************************************************** + * + * Interface: + * + * IHXSiteWindowless + * + * Purpose: + * + * Interface for IHXSite objects which are "windowless" or not + * associated with platform specific window objects. + * + * IID_IHXSiteWindowless: + * + * {00000D02-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXSiteWindowless, 0x00000D02, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXSiteWindowless + +#define CLSID_IHXSiteWindowless IID_IHXSiteWindowless + +DECLARE_INTERFACE_(IHXSiteWindowless, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXSiteWindowless methods called by owners of the site. + */ + STDMETHOD(EventOccurred) (THIS_ + HXxEvent* /*IN*/ pEvent) PURE; + + /* + * IHXSiteWindowless method. Returns some parent window that + * owns the windowless site. Useful for right-click menus and + * dialog box calls. + */ + STDMETHOD_(HXxWindow*,GetParentWindow)(THIS) PURE; +}; + + +/**************************************************************************** + * + * Interface: + * + * IHXSite + * + * Purpose: + * + * Interface for IHXSite objects. + * + * IID_IHXSite: + * + * {00000D03-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXSite, 0x00000D03, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXSite + +DECLARE_INTERFACE_(IHXSite, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXSite methods usually called by the "context" to + * associate users with the site, and to create child sites + * as appropriate. + */ + STDMETHOD(AttachUser) (THIS_ + IHXSiteUser* /*IN*/ pUser) PURE; + + STDMETHOD(DetachUser) (THIS) PURE; + + + STDMETHOD(GetUser) (THIS_ + REF(IHXSiteUser*) /*OUT*/ pUser) PURE; + + STDMETHOD(CreateChild) (THIS_ + REF(IHXSite*) /*OUT*/ pChildSite) PURE; + + STDMETHOD(DestroyChild) (THIS_ + IHXSite* /*IN*/ pChildSite) PURE; + + /* + * IHXSite methods called by the the "context" in which the site + * is displayed in order to manage its position. Site users should + * not generally call these methods. + */ + STDMETHOD(AttachWatcher) (THIS_ + IHXSiteWatcher* /*IN*/ pWatcher) PURE; + + STDMETHOD(DetachWatcher) (THIS) PURE; + + STDMETHOD(SetPosition) (THIS_ + HXxPoint position) PURE; + + STDMETHOD(GetPosition) (THIS_ + REF(HXxPoint) position) PURE; + + /* + * IHXSite methods called by the user of the site to get + * information about the site, and to manipulate the site. + */ + STDMETHOD(SetSize) (THIS_ + HXxSize size) PURE; + + STDMETHOD(GetSize) (THIS_ + REF(HXxSize) size) PURE; + + STDMETHOD(DamageRect) (THIS_ + HXxRect rect) PURE; + + STDMETHOD(DamageRegion) (THIS_ + HXxRegion region) PURE; + + STDMETHOD(ForceRedraw) (THIS) PURE; +}; + + +// $Private +/**************************************************************************** + * + * Interface: + * + * IHXSiteComposition + * + * Purpose: + * + * Interface for IHXSite objects to let them compose composition + * frames and display them on a regular basis rather than many + * discrete blts. + * + * IID_IHXSiteComposition: + * + * {00000D03-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXSiteComposition, 0x00000D19, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXSiteComposition + +DECLARE_INTERFACE_(IHXSiteComposition, IUnknown) +{ + /* IUnknown methods */ + STDMETHOD(QueryInterface) (THIS_ REFIID riid, void** ppvObj) PURE; + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* IHXSiteComposition methods. */ + STDMETHOD(LockComposition) (THIS) PURE; + STDMETHOD(UnlockComposition) (THIS) PURE; + STDMETHOD(BltComposition) (THIS) PURE; + STDMETHOD(SetCompositionMode) (THIS_ HXBOOL OnOrOff) PURE; + STDMETHOD_(HXBOOL, IsCompositionLocked) (THIS) PURE; + STDMETHOD_(HXBOOL, IsCompositionMode) (THIS) PURE; +}; +// $EndPrivate. + + +/**************************************************************************** + * + * Interface: + * + * IHXSiteUser + * + * Purpose: + * + * Interface for the user of the IHXSite objects. + * + * IID_IHXSiteUser: + * + * {00000D04-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXSiteUser, 0x00000D04, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXSiteUser + +DECLARE_INTERFACE_(IHXSiteUser, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXSiteUser methods usually called by the "context" to + * associate users with the site. + */ + STDMETHOD(AttachSite) (THIS_ + IHXSite* /*IN*/ pSite) PURE; + + STDMETHOD(DetachSite) (THIS) PURE; + + /* + * IHXSiteUser methods called to inform user of an event. + */ + STDMETHOD(HandleEvent) (THIS_ + HXxEvent* /*IN*/ pEvent) PURE; + + STDMETHOD_(HXBOOL,NeedsWindowedSites) (THIS) PURE; +}; + +/**************************************************************************** + * + * Interface: + * + * IHXSiteWatcher + * + * Purpose: + * + * Interface for IHXSiteWatcher objects. + * + * IID_IHXSite: + * + * {00000D05-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXSiteWatcher, 0x00000D05, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXSiteWatcher + +DECLARE_INTERFACE_(IHXSiteWatcher, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXSiteWatcher methods called by the site when a watcher + * is attached to or detached from it. + */ + STDMETHOD(AttachSite) (THIS_ + IHXSite* /*IN*/ pSite) PURE; + + STDMETHOD(DetachSite) (THIS) PURE; + + /* + * IHXSiteWatcher methods called by the site an attempt is + * made to change it's position or size. The site watcher must + * return HXR_OK for the change to occur. If the site watcher + * returns any value other than HXR_OK then the size or position + * will not change. The site watcher can also modify the new + * size of position. + */ + STDMETHOD(ChangingPosition) (THIS_ + HXxPoint posOld, + REF(HXxPoint)/*IN-OUT*/ posNew) PURE; + + STDMETHOD(ChangingSize) (THIS_ + HXxSize sizeOld, + REF(HXxSize) /*IN-OUT*/ sizeNew) PURE; +}; + +/**************************************************************************** + * + * Interface: + * + * IHXSiteUserSupplier + * + * Purpose: + * + * Interface implemented by renderers and objects with provide layouts to + * the client core. This interface is called by the core when it needs a + * new IHXSiteUser, or when it is done using an IHXSiteUser. + * + * IID_IHXSiteUserSupplier: + * + * {00000D06-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXSiteUserSupplier, 0x00000D06, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXSiteUserSupplier + +DECLARE_INTERFACE_(IHXSiteUserSupplier, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXSiteUserSupplier methods usually called by the + * "context" to ask for additional or to release previously + * created site users. + */ + STDMETHOD(CreateSiteUser) (THIS_ + REF(IHXSiteUser*)/*OUT*/ pSiteUser) PURE; + + STDMETHOD(DestroySiteUser) (THIS_ + IHXSiteUser* /*IN*/ pSiteUser) PURE; + + STDMETHOD_(HXBOOL,NeedsWindowedSites) (THIS) PURE; +}; + + +/**************************************************************************** + * + * Interface: + * + * IHXSiteSupplier + * + * Purpose: + * + * Interface implemented by users of the client core. This interface is + * called by the core when it needs a new IHXSite, or when it is done + * using an IHXSite. + * + * IID_IHXSiteSupplier: + * + * {00000D07-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXSiteSupplier, 0x00000D07, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXSiteSupplier + +DECLARE_INTERFACE_(IHXSiteSupplier, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXSiteSupplier methods + */ + + /************************************************************************ + * Method: + * IHXSiteSupplier::SitesNeeded + * Purpose: + * Called to inform the site supplier that a site with a particular + * set of characteristics is needed. If the site supplier can + * fulfill the request it should call the site manager and add one + * or more new sites. + * Note that the request for sites is associated with a Request ID + * the client core will inform the site supplier when this requested + * site is no longer needed. + */ + STDMETHOD(SitesNeeded) (THIS_ + UINT32 uReqestID, + IHXValues* pSiteProps) PURE; + + /************************************************************************ + * Method: + * IHXSiteSupplier::SitesNotNeeded + * Purpose: + * Called to inform the site supplier that all sites from a previos + * site request are no longer needed. If the site supplier had + * previously created non-persistant sites (like popup windows) + * to fulfill a request for sites it should call the site manager + * and remove those sites. + */ + STDMETHOD(SitesNotNeeded) (THIS_ + UINT32 uReqestID) PURE; + + + /************************************************************************ + * Method: + * IHXSiteSupplier::BeginChangeLayout + * Purpose: + * Called to inform the site supplier a layout change has beginning + * it can expect to receive SitesNeeded() and SitesNotNeeded() calls + * while a layout change is in progress, + */ + STDMETHOD(BeginChangeLayout) (THIS) PURE; + + /************************************************************************ + * Method: + * IHXSiteSupplier::DoneChangeLayout + * Purpose: + * Called to inform the site supplier the layout change has been + * completed. + */ + STDMETHOD(DoneChangeLayout) (THIS) PURE; + +}; + + +/**************************************************************************** + * + * Interface: + * + * IHXSiteManager + * + * Purpose: + * + * Interface implemented by the client core. This interface is called + * by users of the client core to inform it of IHXSite's which are + * available for layout of renderers + * + * IID_IHXSiteManager: + * + * {00000D08-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXSiteManager, 0x00000D08, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXSiteManager + +DECLARE_INTERFACE_(IHXSiteManager, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXSiteManager methods + */ + + /************************************************************************ + * Method: + * IHXSiteManager::AddSite + * Purpose: + * Called to inform the site manager of the existence of a site. + */ + STDMETHOD(AddSite) (THIS_ + IHXSite* pSite) PURE; + + /************************************************************************ + * Method: + * IHXSiteManager::RemoveSite + * Purpose: + * Called to inform the site manager that a site is no longer + * available. + */ + STDMETHOD(RemoveSite) (THIS_ + IHXSite* pSite) PURE; +}; + + +// $Private: +/**************************************************************************** + * + * Interface: + * + * IHXSiteManager2 + * + * Purpose: + * + * Interface implemented by the client core. This interface is called + * by users of the client core to iterate over the sites known by this + * site manager. + * + * IID_IHXSiteManager: + * + * {00000D20-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXSiteManager2, 0x00000D20, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXSiteManager2 + +DECLARE_INTERFACE_(IHXSiteManager2, IUnknown) +{ + /* + * IHXSiteManager2 methods + */ + + /************************************************************************ + * Method: + * IHXSiteManager2::GetNumberOfSites + * Purpose: + * Called to get the number of sites that the site mananger currently + * knows about. + */ + STDMETHOD(GetNumberOfSites) (THIS_ REF(UINT32) nNumSites ) PURE; + + /************************************************************************ + * Method: + * IHXSiteManager2::GetSiteAt + * Purpose: + * Used to iterate over the sites. + * + */ + STDMETHOD(GetSiteAt) (THIS_ UINT32 nIndex, REF(IHXSite*) pSite) PURE; +}; +// $EndPrivate. + + +/**************************************************************************** + * + * Interface: + * + * IHXMultiInstanceSiteUserSupplier + * + * Purpose: + * + * This is the interface for a special default object which is available + * from the common class factory. This object will act as a site user + * supplier for any renderer (or other site user object) that wants + * default support for multiple instances. The site user must work as + * a windowless site for this default implementation to work. The + * default object also implements the IHXSite interfave to allow + * the site user object to control all the sites through a single + * interface instance. + * + * IID_IHXMultiInstanceSiteUserSupplier: + * + * {00000D09-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXMultiInstanceSiteUserSupplier, 0x00000D09, 0x901, + 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#define CLSID_IHXMultiInstanceSiteUserSupplier \ + IID_IHXMultiInstanceSiteUserSupplier + +#undef INTERFACE +#define INTERFACE IHXMultiInstanceSiteUserSupplier + +DECLARE_INTERFACE_(IHXMultiInstanceSiteUserSupplier, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXMultiInstanceSiteUserSupplier methods called by site users. + */ + STDMETHOD(SetSingleSiteUser) (THIS_ + IUnknown* pUnknown) PURE; + + STDMETHOD(ReleaseSingleSiteUser) (THIS) PURE; + +}; + + +/**************************************************************************** + * + * Interface: + * + * IHXSiteEnumerator + * + * Purpose: + * + * Provides an interface to enumerate sites. Currently implemented + * in the IHXMultiInstanceSiteUserSupplier supplied by the core player, + * it can be used to render to MISUS sites outside of an HX_SURFACE_UPDATE; + * this is especially useful when using IHXVideoSurface2. + * + * IID_IHXSiteEnumerator: + * + * {67f8c5bd-4b1d-4c09-8fb7-8ac7c20d29c7} + * + */ +DEFINE_GUID(IID_IHXSiteEnumerator, 0x67f8c5bd, 0x4b1d, + 0x4c09, 0x8f, 0xb7, 0x8a, 0xc7, 0xc2, 0x0d, 0x29, 0xc7); + +#undef INTERFACE +#define INTERFACE IHXSiteEnumerator + +DECLARE_INTERFACE_(IHXSiteEnumerator, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + typedef void* SitePosition; + + /* + * HXSiteEnumerator methods + */ + + + /************************************************************************ + * Method: + * HXSiteEnumerator::GetFirstSite + * Purpose: + * Retrieves both the first site in the enumeration and initializes + * sitePosition with the position of the next site (if any). + * Returns HXR_OK if the first site is available, HXR_FAIL if not. + */ + STDMETHOD(GetFirstSite) (THIS_ + REF(IHXSite*) /* OUT */ pFirstSite, + REF(SitePosition) /* OUT */ nextPosition) PURE; + + /************************************************************************ + * Method: + * HXSiteEnumerator::GetNextSite + * Purpose: + * Retrieves both the next site in the enumeration (as specified by + * nextSite) and initializes sitePosition with the position of the + * following site (if any). + * Returns HXR_OK if the first site is available, HXR_FAIL if not. + */ + STDMETHOD(GetNextSite) (THIS_ + REF(IHXSite*) pNextSite, + REF(SitePosition) /* IN/OUT */ nextPosition) PURE; +}; + + +/**************************************************************************** + * + * Interface: + * IHXSiteFullScreen + * + * Purpose: + * + * This is the interface for turning on/off the full screen mode + * + * IID_IHXSiteFullScreen: + * + * {00000D0B-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXSiteFullScreen, 0x00000D0B, 0x901, + 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXSiteFullScreen + +DECLARE_INTERFACE_(IHXSiteFullScreen, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXSiteFullScreen methods + */ + STDMETHOD(EnterFullScreen) (THIS) PURE; + + STDMETHOD(ExitFullScreen) (THIS) PURE; + + STDMETHOD(TestFullScreen) (THIS_ + void* hTestBitmap,const char* pszStatusText) PURE; + + STDMETHOD_(HXBOOL, IsFullScreen) (THIS) PURE; +}; + + +// $Private: +/**************************************************************************** + * + * Interface: + * IHXLayoutSiteGroupManager + * + * Purpose: + * + * Allow layout site groups to be added and removed + * + * IID_IHXLayoutSiteGroupManager: + * + * {00000D0C-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXLayoutSiteGroupManager, 0x00000D0C, 0x901, + 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXLayoutSiteGroupManager + +DECLARE_INTERFACE_(IHXLayoutSiteGroupManager, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXLayoutSiteGroupManager methods + */ + STDMETHOD(AddLayoutSiteGroup) (THIS_ + IUnknown* pLSG) PURE; + + STDMETHOD(RemoveLayoutSiteGroup) (THIS_ + IUnknown* pLSG) PURE; +}; +// $EndPrivate. + + +/**************************************************************************** + * + * Interface: + * IHXEventHookMgr + * + * Purpose: + * + * Add ability to hook events from a named region + * + * IID_IHXEventHookMgr: + * + * {00000D0D-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXEventHookMgr, 0x00000D0D, 0x901, + 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXEventHookMgr + +DECLARE_INTERFACE_(IHXEventHookMgr, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXEventHookMgr methods + */ + STDMETHOD(AddHook) (THIS_ + IHXEventHook* pHook, + const char* pRegionName, + UINT16 uLayer) PURE; + + STDMETHOD(RemoveHook) (THIS_ + IHXEventHook* pHook, + const char* pRegionName, + UINT16 uLayer) PURE; +}; + +/**************************************************************************** + * + * Interface: + * IHXEventHook + * + * Purpose: + * + * Object that gets hooked events sent by IHXEventHookMgr + * + * IID_IHXEventHookMgr: + * + * {00000D0E-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXEventHook, 0x00000D0E, 0x901, + 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXEventHook + +DECLARE_INTERFACE_(IHXEventHook, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXEventHook methods + */ + STDMETHOD(SiteAdded) (THIS_ + IHXSite* pSite) PURE; + STDMETHOD(HandleEvent) (THIS_ + IHXSite* pSite, + HXxEvent* pEvent) PURE; + STDMETHOD(SiteRemoved) (THIS_ + IHXSite* pSite) PURE; +}; + + +/**************************************************************************** + * + * Interface: + * IHXStatusMessage + * + * Purpose: + * + * This is the interface for setting the status text. + * + * IID_IHXStatusMessage: + * + * {00000D10-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXStatusMessage, 0x00000D10, 0x901, + 0x11d1, 0x8b, 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXStatusMessage + +DECLARE_INTERFACE_(IHXStatusMessage, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXStatusMessage methods + */ + + STDMETHOD(SetStatus) (THIS_ const char* pText) PURE; +}; + +// $Private: +/**************************************************************************** + * + * Interface: + * + * IHXSiteTransition + * + * Purpose: + * + * Interface for transitioning between IHXSites. + * + * IID_IHXSiteTransition: + * + * {00000D01-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXSiteTransition, 0x00000D13, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#define CLSID_IHXSiteTransition IID_IHXSiteTransition + +#undef INTERFACE +#define INTERFACE IHXSiteTransition + +DECLARE_INTERFACE_(IHXSiteTransition, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + STDMETHOD(Initialize) (THIS_ + IHXValues* pParams) PURE; + + STDMETHOD(SetPercentage) (THIS_ + UINT32 nThousandnthsComplete) PURE; + +}; + + +/**************************************************************************** + * + * Interface: + * + * IHXRegion + * + * Purpose: + * + * Interface for managing HXRegions. + * + * IHXRegion: + * + * {00002200-0903-11d1-8B06-00A024406D59} + * + */ + + +DEFINE_GUID(IID_IHXRegion, 0x00000D14, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#define CLSID_IHXRegion IID_IHXRegion + +#undef INTERFACE +#define INTERFACE IHXRegion + +DECLARE_INTERFACE_(IHXRegion, IUnknown) +{ + /************************************************************************ + * Method: + * IHXRegion::SetRect + * Purpose: + * This function creates a rectangular region. + * + */ + STDMETHOD(SetRect) (THIS_ HXxRect* pRect) PURE; + + /************************************************************************ + * Method: + * IHXRegion::SetRect + * Purpose: + * This function creates a rectangular region. + * + */ + STDMETHOD(SetRect) (THIS_ int x, int y, int x1, int y1) PURE; + + /************************************************************************ + * Method: + * IHXRegion::SetPoly + * Purpose: + * This function creates a region defined by an arbitrary polygon. + * + */ + + STDMETHOD(SetPoly) (THIS_ HXxPoint** pRect, HXBOOL bUseWinding) PURE; + + /************************************************************************ + * Method: + * IHXRegion::IsEqual + * Purpose: + * This function determines if two regions are equal. + * + */ + + STDMETHOD_(HXBOOL,IsEqual) (THIS_ IHXRegion* pRegion) PURE; + + /************************************************************************ + * Method: + * IHXRegion::GetExtents + * Purpose: + * This function allows the user to determine the extents of the region + * + */ + + STDMETHOD(GetExtents) (THIS_ REF(HXxRect) rExtents) PURE; + + /************************************************************************ + * Method: + * IHXRegion::Offset + * Purpose: + * This function offsets the region by the spectified origin + * + */ + + STDMETHOD(Offset) (THIS_ HXxPoint* pOrigin) PURE; + + /************************************************************************ + * Method: + * IHXRegion::PointHitTest + * Purpose: + * This function returns if TRUE if the specified point is in the region + * + */ + + STDMETHOD_(HXBOOL,PointHitTest) (THIS_ HXxPoint* pPoint) PURE; + + /************************************************************************ + * Method: + * IHXRegion::RectHitTest + * Purpose: + * This function returns + * HX_CONTAINED if the rect is fully contained within the region + * HX_PART if part of the rect is within the region + * HX_OUT if no part of the rect is within the region + * + */ + + STDMETHOD_(INT32,RectHitTest) (THIS_ HXxRect* pRect) PURE; + + /************************************************************************ + * Method: + * IHXRegion::GetNumRects + * Purpose: + * This function gets the number of rects which describe the + * region + * + */ + + STDMETHOD_(UINT32, GetNumRects) (THIS) PURE; + + /************************************************************************ + * Method: + * IHXRegion::GetRectAtIndex + * Purpose: + * This function gets the RECT at index nRectIndex + * + */ + + STDMETHOD(GetRectAtIndex) (THIS_ UINT32 nRectIndex, REF(HXxRect) rRect) PURE; + + /************************************************************************ + * Method: + * IHXRegion::UnionRegion + * Purpose: + * Union Region -- this operator like all of the operators will create an IHXRegion + * if pDest is NULL. + * + */ + + STDMETHOD(UnionRegion) (THIS_ REF(IHXRegion*) pDest, IHXRegion* pSrc1) PURE; + + /************************************************************************ + * Method: + * IHXRegion::GetRectAtIndex + * Purpose: + * Copy Region + * + */ + + STDMETHOD(CopyRegion) (THIS_ REF(IHXRegion*) pDest) PURE; + + /************************************************************************ + * Method: + * IHXRegion::GetRectAtIndex + * Purpose: + * Diff Region + * + */ + + STDMETHOD(DiffRegion) (THIS_ REF(IHXRegion*) pDest, IHXRegion* pSrc1) PURE; + + /************************************************************************ + * Method: + * IHXRegion::GetRectAtIndex + * Purpose: + * And Region + * + */ + + STDMETHOD(AndRegion) (THIS_ REF(IHXRegion*) pDest, IHXRegion* pSrc1) PURE; + + /************************************************************************ + * Method: + * IHXRegion::GetRectAtIndex + * Purpose: + * XOR Region + * + */ + + STDMETHOD(XORRegion) (THIS_ REF(IHXRegion*) pDest, IHXRegion* pSrc1) PURE; + + /************************************************************************ + * Method: + * IHXRegion::GetRegion + * Purpose: + * Bad hack for the moment to obtain the REGION pointer of an HXRegion. + * Will have to remove this later. + * + */ + + STDMETHOD_(void*, GetRegion) (THIS) PURE; + +}; + +/**************************************************************************** + * + * Interface: + * + * IHXColorConverterManager + * + * Purpose: + * + * Interface for obtaining IHXColorConverters + * + * IHXColorConverterManager: + * + * {00000D15-0902-11d1-8B06-00A024406D59} + * + */ + +DEFINE_GUID(IID_IHXColorConverterManager, 0x00000D15, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXColorConverterManager + +DECLARE_INTERFACE_(IHXColorConverterManager, IUnknown) +{ + /* + * Get ColorConverter is called to obtain a color converter to convert + * from a particular bitmap to another bitmap. + */ + STDMETHOD(GetColorConverter) (THIS_ + HXBitmapInfoHeader* /*IN*/ pBitmapInfoIn, + HXBitmapInfoHeader* /*IN*/ pBitmapInfoOut, + REF(IHXColorConverter*) /*OUT*/ pConverter) PURE; + +}; + +/**************************************************************************** + * + * Interface: + * + * IHXColorConverter + * + * Purpose: + * + * Interface for converting between two bitmaps of different color formats. + * + * IHXColorConverterManager: + * + * {00000D16-0902-11d1-8B06-00A024406D59} + * + */ + +DEFINE_GUID(IID_IHXColorConverter, 0x00000D16, 0x903, 0x11d1, 0x8b, + 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXColorConverter + +DECLARE_INTERFACE_(IHXColorConverter, IUnknown) +{ + /* + * ColorConvert converts the pBitsIn from one color format to + * the format of pBitsOut + */ + STDMETHOD(ColorConvert) (THIS_ + UCHAR* pBitsIn, + UCHAR* pBitsOut, + HXxRect* pRectIn, + HXxRect* pRectOut + ) PURE; + +}; + +/**************************************************************************** + * + * Interface: + * + * IHXOverlayResponse + * + * Purpose: + * + * Interface for reporting/computing the current statistics relevant to + * Video Presentations. + * + * IHXOverlayResponse: + * + * {00000D22-0902-11d1-8B06-00A024406D59} + * + */ + +DEFINE_GUID(IID_IHXOverlayResponse, 0x00000D22, 0x903, 0x11d1, 0x8b, + 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXOverlayResponse + +DECLARE_INTERFACE_(IHXOverlayResponse, IUnknown) +{ + STDMETHOD(OverlayGranted) (THIS ) PURE; + STDMETHOD(OverlayRevoked) (THIS ) PURE; +}; + + +/**************************************************************************** + * + * Interface: + * + * IHXOverlayManager + * + * Purpose: + * + * Interface for reporting/computing the current statistics relevant to + * Video Presentations. + * + * IHXOverlayManager: + * + * {00000D21-0902-11d1-8B06-00A024406D59} + * + */ + +DEFINE_GUID(IID_IHXOverlayManager, 0x00000D21, 0x903, 0x11d1, 0x8b, + 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXOverlayManager + +DECLARE_INTERFACE_(IHXOverlayManager, IUnknown) +{ + STDMETHOD(HasOverlay) (THIS_ + IHXOverlayResponse* pResp + ) PURE; + + STDMETHOD(AddStats) (THIS_ + IHXOverlayResponse* pResp, + UINT32 ulNumPixels + ) PURE; + + + STDMETHOD(RemoveOverlayRequest)(THIS_ IHXOverlayResponse* pResp ) PURE; +}; + + +// $EndPrivate. + +/**************************************************************************** + * + * Interface: + * + * IHXKeyBoardFocus + * + * Purpose: + * + * Interface for setting/getting the keyboard focus for a particular siteuser + * + * IHXKeyBoardFocus: + * + * {00000D23-0902-11d1-8B06-00A024406D59} + * + */ + +DEFINE_GUID(IID_IHXKeyBoardFocus, 0x00000D23, 0x903, 0x11d1, 0x8b, + 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXKeyBoardFocus + +DECLARE_INTERFACE_(IHXKeyBoardFocus, IUnknown) +{ + STDMETHOD(SetKeyboardFocus)(THIS_ IHXSiteUser* pSiteUser ) PURE; + STDMETHOD(GetKeyboardFocus)(THIS_ IHXSiteUser* &pSiteUser ) PURE; + +}; + +/**************************************************************************** + * + * Interface: + * + * IHXDrawFocus + * + * Purpose: + * + * Interface for displaying the site that has the keyboard focus + * + * IHXDrawFocus: + * + * {00000D24-0902-11d1-8B06-00A024406D59} + * + */ + +DEFINE_GUID(IID_IHXDrawFocus, 0x00000D24, 0x903, 0x11d1, 0x8b, + 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXDrawFocus + +DECLARE_INTERFACE_(IHXDrawFocus, IUnknown) +{ + /************************************************************************ + * Method: + * IHXDrawFocus::SetStyle + * Purpose: + * Ask the site to set the focus style. + * + * Syles: + * ULONG32 Properties: + * + * LINE_STYLE = HX_SOLID_LINE, HX_DASHED_LINE, HX_DOTTED_LINE, + * HX_CUSTOM_LINE + * LINE_WIDTH = Width of the line in pixels + * RED = 0-255 color of the primary pixel + * GREEN = 0-255 color of the primary pixel + * BLUE = 0-255 color of the primary pixel + * RED_OFF = 0-255 color of the secondary pixel + * GREEN_OFF = 0-255 color of the secondary pixel + * BLUE_OFF = 0-255 color of the secondary pixel + * CUSTOM_LINE_ENTRIES number of ULONG32s in CUSTOM_LINE_PATTERN + * + * IHXBuffer Properties: + * + * CUSTOM_LINE_PATTERN list of ULONG32s describing the number + * of primary and secondary pixels (eq 4241 = "----..----." where + * - is a primary pixel and . is a secondary pixel) and + * CUSTOM_LINE_ENTRIES equals 4. Secondary pixels are not + * drawn if RED_OFF, GREEN_OFF, and BLUE_OFF are not set. + */ + STDMETHOD(SetStyle) (THIS_ IHXValues* pProperties) PURE; + + /************************************************************************ + * Method: + * IHXDrawFocus::ClearFocus + * Purpose: + * Ask the site to clear the current focus drawing. + */ + STDMETHOD(ClearFocus)(THIS) PURE; + + /************************************************************************ + * Method: + * IHXDrawFocus::SetFocusPolygon + * Purpose: + * Ask the site to draw polygon around focus + */ + STDMETHOD(SetFocusPolygon)(THIS_ HXxPoint* pPoints, ULONG32 numPoints) PURE; + + /************************************************************************ + * Method: + * IHXDrawFocus::SetFocusRect + * Purpose: + * Ask the site to draw rectangle around focus + */ + STDMETHOD(SetFocusRect) (THIS_ HXxRect* pRect) PURE; + + /************************************************************************ + * Method: + * IHXDrawFocus::SetFocusEllipse + * Purpose: + * Ask the site to draw ellipse around focus + */ + STDMETHOD(SetFocusEllipse) (THIS_ HXxRect* pRect) PURE; +}; + +// $Private: + +/**************************************************************************** + * + * Interface: + * + * IHXSubRectSite + * + * Purpose: + * + * Interface to determine if a site support sub rect BLT'ing via + * the HX_SURFACE_UPDATE2 message. If the site does support sub + * rect BLT'ing you can tell the site to send you the + * HX_SURFACE_UPDATE2 messages via this interface. + * + * IHXSubRectSite: + * + * {00000D25-0902-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXSubRectSite, 0x00000D25, 0x903, 0x11d1, 0x8b, + 0x6, 0x0, 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXSubRectSite + +DECLARE_INTERFACE_(IHXSubRectSite, IHXSite) +{ + /* + * Tells the site to send/not-send HX_SURFACE_UPDATE2 messages. + * These messages contain actuall dirty rects so that the renderer + * does not need to BLT the entire frame. + */ + STDMETHOD(SendSubRectMessages) (THIS_ HXBOOL bRet ) PURE; + /* + * New damage region call that takes the cross platfrom region + * defined in hxwintyp.h and invalidates the rects in it + */ + STDMETHOD(SubRectDamageRegion) (THIS_ HXxBoxRegion* pRegion ) PURE; + /* + * Method to get the new video surface that comes with the sub + * rect BLT'ing support. + */ + STDMETHOD(GetSubRectVideoSurface) (THIS_ + REF(IHXSubRectVideoSurface*) pSurface + ) PURE; +}; + +// $EndPrivate. + +/**************************************************************************** + * + * Interface: + * + * IHXFocusNavigation + * + * Purpose: + * + * Interface for navigating between and within keyboard focus sites + * + * IHXFocusNavigation: + * + * {B42B7677-F605-438e-9002-E2AAB7784B43} + * + */ + +DEFINE_GUID(IID_IHXFocusNavigation, 0xb42b7677, 0xf605, 0x438e, 0x90, + 0x2, 0xe2, 0xaa, 0xb7, 0x78, 0x4b, 0x43); + +#undef INTERFACE +#define INTERFACE IHXFocusNavigation + +DECLARE_INTERFACE_(IHXFocusNavigation, IUnknown) +{ + /************************************************************************ + * Method: + * IHXFocusNavigation::SetFocus + * Purpose: + * Ask the renderer to set the focus to the given item. + */ + STDMETHOD(SetFocus) (THIS_ HXFocusContext eFocus) PURE; + + /************************************************************************ + * Method: + * IHXFocusNavigation::ClearFocus + * Purpose: + * Ask the renderer to clear the current focus. + */ + STDMETHOD(ClearFocus) (THIS) PURE; + + /************************************************************************ + * Method: + * IHXFocusNavigation::ActivateFocus + * Purpose: + * Ask the renderer to activate the focused link. Do nothing if + * there is no focus point. + */ + STDMETHOD(ActivateFocus) (THIS) PURE; + + /************************************************************************ + * Method: + * IHXFocusNavigation::GetFocusState + * Purpose: + * Obtain the current focus state + */ + STDMETHOD_(HXFocusState,GetFocusState) (THIS) PURE; +}; + +#endif /* _HXWIN_H_ */ diff --git a/amarok/src/engine/helix/helix-sp/helix-include/common/include/hxwintyp.h b/amarok/src/engine/helix/helix-sp/helix-include/common/include/hxwintyp.h new file mode 100644 index 00000000..4316cc2f --- /dev/null +++ b/amarok/src/engine/helix/helix-sp/helix-include/common/include/hxwintyp.h @@ -0,0 +1,374 @@ + +/* + * + * This software is released under the provisions of the GPL version 2. + * see file "COPYING". If that file is not available, the full statement + * of the license can be found at + * + * http://www.fsf.org/licensing/licenses/gpl.txt + * + * Portions Copyright (c) 1995-2004 RealNetworks, Inc. All Rights Reserved. + * + */ + +#ifndef _HXWINTYP_H_ +#define _HXWINTYP_H_ + +#include "hxtypes.h" /* Needed at least for various defines and types. */ + +#ifdef _WIN16 +#define BI_BITFIELDS 3L +#endif + +#ifdef _SYMBIAN +#include <coemain.h> +#include <w32std.h> +#endif + +/**************************************************************************** + * + * Structure: + * + * HXxSize + * + * Purpose: + * + * Cross Platform definition of a size. + * + */ +typedef struct HXEXPORT_CLASS _HXxSize +{ + INT32 cx; + INT32 cy; +} HXxSize; + +#ifdef __cplusplus + +inline HXBOOL operator ==( const HXxSize& a, const HXxSize& b ) +{ + return ( a.cx == b.cx ) && ( a.cy == b.cy ); +} + +inline HXBOOL operator !=( const HXxSize& a, const HXxSize& b ) +{ + return !( a == b ); +} + +#endif // __cplusplus + +/**************************************************************************** + * + * Structure: + * + * HXxPoint + * + * Purpose: + * + * Cross Platform definition of a point. + * + */ +typedef struct HXEXPORT_CLASS _HXxPoint +{ + INT32 x; + INT32 y; +} HXxPoint; + +#ifdef __cplusplus + +inline HXBOOL operator ==( const HXxPoint& a, const HXxPoint& b ) +{ + return ( a.x == b.x ) && ( a.y == b.y ); +} + +inline HXBOOL operator !=( const HXxPoint& a, const HXxPoint& b ) +{ + return !( a == b ); +} + +#endif // __cplusplus + + + +/**************************************************************************** + * + * Structure: + * + * HXxRect + * + * Purpose: + * + * Cross Platform definition of a rectangle. + * + */ +typedef struct HXEXPORT_CLASS _HXxRect +{ + INT32 left; + INT32 top; + INT32 right; + INT32 bottom; +} HXxRect; + +#define HXxRECT_WIDTH(r) ((r).right - (r).left) +#define HXxRECT_HEIGHT(r) ((r).bottom - (r).top) + +#ifdef __cplusplus + +inline HXBOOL operator ==( const HXxRect& a, const HXxRect& b ) +{ + return ( a.left == b.left ) && + ( a.top == b.top ) && + ( a.right == b.right ) && + ( a.bottom == b.bottom ); +} + +inline HXBOOL operator !=( const HXxRect& a, const HXxRect& b ) +{ + return !( a == b ); +} + +inline HXBOOL HXxRect_IsEmpty( const HXxRect& rect ) +{ + return ( rect.left >= rect.right ) || + ( rect.top >= rect.bottom ); +} + +inline void HXxRect_Intersection( const HXxRect& r1, const HXxRect& r2, HXxRect* result ) +{ + result->left = ( r1.left > r2.left ) ? r1.left : r2.left; + result->top = ( r1.top > r2.top ) ? r1.top : r2.top; + result->right = ( r1.right < r2.right ) ? r1.right : r2.right; + result->bottom = ( r1.bottom < r2.bottom ) ? r1.bottom : r2.bottom; +} + +#endif // __cplusplus + +/**************************************************************************** + * + * Structure: + * + * HXxWindow + * + * Purpose: + * + * Cross Platform definition of a window. This struct is sufficiently + * wide to describe parent or child windows in Windows, MacOS, and + * various flavors of X-Windows. + * + * Data Members: + * + * void* window + * platform specific window handle + * + * ULONG32 x, y + * position of top left corner relative to a client page + * + * ULONG32 width, height + * maximum window size + * + * HXxRect clipRect; + * clipping rectangle in port coordinates + * + */ +typedef struct HXEXPORT_CLASS _HXxWindow +{ + /* NOTE: The window parameter is NOT guaranteed to be unique for every + corresponding CHXWindow. Use HXxWindowID if this is desired. */ + void* window; + ULONG32 x; + ULONG32 y; + ULONG32 width; + ULONG32 height; + HXxRect clipRect; +#ifdef _UNIX + void * display; +#endif +#ifdef _SYMBIAN + CDirectScreenAccess* iDSA; +#endif +} HXxWindow; + +typedef void* HXxWindowID; + +/**************************************************************************** + * + * Structure: + * + * HXxEvent + * + * Purpose: + * + * Cross Platform definition of a event. This struct is sufficiently + * wide to describe an event in Windows, MacOS, and various flavors of + * X-Windows. + * + * Data Members: + * + * void* event + * platform specific event ID, can also be one of the several HXxMSG_* + * event IDs which map onto existing platform specific event IDs + * UNIX: X Event Type + * + * void* window + * platform specific window handle + * UNIX: X Window ID + * + * void* param1 + * message specific parameter + * UNIX: Display* + * + * void* param2 + * Mac: for UpdateEvt, either NULL or RgnHandle to be filled with updated area + * UNIX: Native XEvent* + * HX_SURFACE_UPDATE HXxWindow* + * + */ +typedef struct HXEXPORT_CLASS _HXxEvent +{ + ULONG32 event; /* IN */ + void* window; /* IN */ + void* param1; /* IN */ + void* param2; /* IN */ + + UINT32 result; /* OUT */ + HXBOOL handled; /* OUT */ +} HXxEvent; + + +/**************************************************************************** + * + * typedef: + * + * HXxRegion + * + * Purpose: + * + * Cross Platform definition of a region. This typedef is redefined as + * appropriate to describe a region in Windows, MacOS, and various + * flavors of X-Windows. + * + */ +typedef void* HXxRegion; + +/**************************************************************************** + * + * typedef: + * + * HXxDC + * + * Purpose: + * + * Cross Platform definition of a device context. This typedef is redefined as + * appropriate to describe a device context in Windows, MacOS, and various + * flavors of X-Windows. + * + */ +typedef void* HXxDC; + +/**************************************************************************** + * + * typedef: + * + * HXxFont + * + * Purpose: + * + * Cross Platform definition of a font. This typedef is redefined as + * appropriate to describe a font in Windows, MacOS, and various + * flavors of X-Windows. + * + */ +typedef void* HXxFont; + +/**************************************************************************** + * + * typedef: + * + * HXxColor + * + * Purpose: + * + * Cross Platform definition of a color. This typedef is redefined as + * appropriate to describe a font in Windows, MacOS, and various + * flavors of X-Windows. + * + */ +typedef ULONG32 HXxColor; + +/**************************************************************************** + * + * typedef: + * + * HXxIcon + * + * Purpose: + * + * Cross Platform definition of a icon. This typedef is redefined as + * appropriate to describe a font in Windows, MacOS, and various + * flavors of X-Windows. + * + */ +typedef void* HXxIcon; + +/**************************************************************************** + * + * typedef: + * + * HXxMenu + * + * Purpose: + * + * Cross Platform definition of a menu. This typedef is redefined as + * appropriate to describe a font in Windows, MacOS, and various + * flavors of X-Windows. + * + */ +typedef void* HXxMenu; + +/**************************************************************************** + * + * typedef: + * + * HXxCursor + * + * Purpose: + * + * Cross Platform definition of a cursor. This typedef is redefined as + * appropriate to describe a cursor in Windows, MacOS, and various + * flavors of X-Windows. + * + */ +typedef void* HXxCursor; + + +/**************************************************************************** + * + * Structure: + * + * HXREGION + * + * Purpose: + * + * Cross Platform Region definition. + */ +typedef struct HXEXPORT_CLASS _HXBox +{ + short x1, x2, y1, y2; +} HXBOX, *HXBoxPtr; + +typedef struct HXEXPORT_CLASS _HXxRegion +{ + HXBOX* rects; + long numRects; +} HXxBoxRegion, *HXxRegionPtr; + +//Definition of the ExposeInfo structure pass with HX_SURFACE_UPDATE2. +typedef struct HXEXPORT_CLASS _HXxExposeInfo +{ + HXxRect extents; //The bounding rect of all dirty rects. + HXxBoxRegion* pRegion; //Pointer to dirty region. DO NOT MODIFY. + HXxWindow* pWindow; //Pointer to the HXxWindow for this site. + void* pParam1; //Reserved + void* pParam2; //Reserved +} HXxExposeInfo; + +#endif /* _HXWINTYP_H_ */ diff --git a/amarok/src/engine/helix/helix-sp/helix-include/common/include/ihxpckts.h b/amarok/src/engine/helix/helix-sp/helix-include/common/include/ihxpckts.h new file mode 100644 index 00000000..c4a927a8 --- /dev/null +++ b/amarok/src/engine/helix/helix-sp/helix-include/common/include/ihxpckts.h @@ -0,0 +1,660 @@ + +/* + * + * This software is released under the provisions of the GPL version 2. + * see file "COPYING". If that file is not available, the full statement + * of the license can be found at + * + * http://www.fsf.org/licensing/licenses/gpl.txt + * + * Portions Copyright (c) 1995-2004 RealNetworks, Inc. All Rights Reserved. + * + */ + +#ifndef _IHXPCKTS_H_ +#define _IHXPCKTS_H_ + +// Define IHXUtilities +// $Private +#include "hxvalue.h" +// $EndPrivate + +/* ASMFlags in IHXPacket */ +#define HX_ASM_SWITCH_ON 0x01 +#define HX_ASM_SWITCH_OFF 0x02 +#define HX_ASM_DROPPED_PKT 0x04 + + +/**************************************************************************** + * + * Interface: + * + * IHXBuffer + * + * Purpose: + * + * Basic opaque data storage buffer. Used in interfaces where + * object ownership is best managed through COM style reference + * counting. + * + * IID_IHXBuffer: + * + * {00001300-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXBuffer, 0x00001300, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +/* + * The IHXCommonClassFactory supports creating an instance + * of this object. + */ +#define CLSID_IHXBuffer IID_IHXBuffer + +#undef INTERFACE +#define INTERFACE IHXBuffer + +DECLARE_INTERFACE_(IHXBuffer, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXBuffer methods + */ + STDMETHOD(Get) (THIS_ + REF(UCHAR*) pData, + REF(ULONG32) ulLength) PURE; + + STDMETHOD(Set) (THIS_ + const UCHAR* pData, + ULONG32 ulLength) PURE; + + STDMETHOD(SetSize) (THIS_ + ULONG32 ulLength) PURE; + + STDMETHOD_(ULONG32,GetSize) (THIS) PURE; + + STDMETHOD_(UCHAR*,GetBuffer)(THIS) PURE; +}; + + +/**************************************************************************** + * + * Interface: + * + * IHXPacket + * + * Purpose: + * + * Basic data packet in the RealMedia system. + * + * IID_IHXPacket: + * + * {00001301-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXPacket, 0x00001301, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +/* + * The IHXCommonClassFactory supports creating an instance + * of this object. + */ +#define CLSID_IHXPacket IID_IHXPacket + +#undef INTERFACE +#define INTERFACE IHXPacket + +DECLARE_INTERFACE_(IHXPacket, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXPacket methods + */ + STDMETHOD(Get) (THIS_ + REF(IHXBuffer*) pBuffer, + REF(UINT32) ulTime, + REF(UINT16) unStreamNumber, + REF(UINT8) unASMFlags, + REF(UINT16) unASMRuleNumber + ) PURE; + + STDMETHOD_(IHXBuffer*,GetBuffer) (THIS) PURE; + + STDMETHOD_(ULONG32,GetTime) (THIS) PURE; + + STDMETHOD_(UINT16,GetStreamNumber) (THIS) PURE; + + STDMETHOD_(UINT8,GetASMFlags) (THIS) PURE; + + STDMETHOD_(UINT16,GetASMRuleNumber) (THIS) PURE; + + STDMETHOD_(HXBOOL,IsLost) (THIS) PURE; + + STDMETHOD(SetAsLost) (THIS) PURE; + + STDMETHOD(Set) (THIS_ + IHXBuffer* pBuffer, + UINT32 ulTime, + UINT16 uStreamNumber, + UINT8 unASMFlags, + UINT16 unASMRuleNumber + ) PURE; +}; + + +/**************************************************************************** + * + * Interface: + * + * IHXPacket + * + * Purpose: + * + * RTP data packet in the RealMedia system. + * + * IID_IHXRTPPacket: + * + * {0169A731-1ED0-11d4-952B-00902742C923} + * + */ +DEFINE_GUID(IID_IHXRTPPacket, 0x169a731, 0x1ed0, 0x11d4, 0x95, 0x2b, 0x0, + 0x90, 0x27, 0x42, 0xc9, 0x23); + +/* + * The IHXCommonClassFactory supports creating an instance + * of this object. + */ +#define CLSID_IHXRTPPacket IID_IHXRTPPacket + +#undef INTERFACE +#define INTERFACE IHXRTPPacket + +DECLARE_INTERFACE_(IHXRTPPacket, IHXPacket) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXPacket methods + */ + STDMETHOD(Get) (THIS_ + REF(IHXBuffer*) pBuffer, + REF(UINT32) ulTime, + REF(UINT16) unStreamNumber, + REF(UINT8) unASMFlags, + REF(UINT16) unASMRuleNumber + ) PURE; + + STDMETHOD_(IHXBuffer*,GetBuffer) (THIS) PURE; + + STDMETHOD_(ULONG32,GetTime) (THIS) PURE; + + STDMETHOD_(UINT16,GetStreamNumber) (THIS) PURE; + + STDMETHOD_(UINT8,GetASMFlags) (THIS) PURE; + + STDMETHOD_(UINT16,GetASMRuleNumber) (THIS) PURE; + + STDMETHOD_(HXBOOL,IsLost) (THIS) PURE; + + STDMETHOD(SetAsLost) (THIS) PURE; + + STDMETHOD(Set) (THIS_ + IHXBuffer* pBuffer, + UINT32 ulTime, + UINT16 uStreamNumber, + UINT8 unASMFlags, + UINT16 unASMRuleNumber + ) PURE; + + /* + * IHXRTPPacket methods + */ + STDMETHOD_(ULONG32,GetRTPTime) (THIS) PURE; + + STDMETHOD(GetRTP) (THIS_ + REF(IHXBuffer*) pBuffer, + REF(UINT32) ulTime, + REF(UINT32) ulRTPTime, + REF(UINT16) unStreamNumber, + REF(UINT8) unASMFlags, + REF(UINT16) unASMRuleNumber + ) PURE; + + STDMETHOD(SetRTP) (THIS_ + IHXBuffer* pBuffer, + UINT32 ulTime, + UINT32 ulRTPTime, + UINT16 uStreamNumber, + UINT8 unASMFlags, + UINT16 unASMRuleNumber + ) PURE; +}; + +/**************************************************************************** + * + * Interface: + * + * IHXRTPPacketInfo + * + * Purpose: + * + * Provides complete RTP packet header info (RFC 1889) + * + * IID_IHXPacket: + * + * {EC7D67BB-2E79-49c3-B667-BA8A938DBCE0} + * + */ +DEFINE_GUID(IID_IHXRTPPacketInfo, + 0xec7d67bb, 0x2e79, 0x49c3, 0xb6, 0x67, 0xba, 0x8a, 0x93, 0x8d, 0xbc, 0xe0); + +#undef INTERFACE +#define INTERFACE IHXRTPPacketInfo + +DECLARE_INTERFACE_(IHXRTPPacketInfo, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ REFIID riid, void** ppvObj) PURE; + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXRTPPacketInfo methods + */ + STDMETHOD_(UINT8, GetVersion) (THIS) PURE; + + STDMETHOD(GetPaddingBit) (THIS_ REF(HXBOOL)bPadding) PURE; + STDMETHOD(SetPaddingBit) (THIS_ HXBOOL bPadding) PURE; + + STDMETHOD(GetExtensionBit) (THIS_ REF(HXBOOL)bExtension) PURE; + STDMETHOD(SetExtensionBit) (THIS_ HXBOOL bExtension) PURE; + + STDMETHOD(GetCSRCCount) (THIS_ REF(UINT8)unCSRCCount) PURE; + STDMETHOD(SetCSRCCount) (THIS_ UINT8 unCSRCCount) PURE; + + STDMETHOD(GetMarkerBit) (THIS_ REF(HXBOOL)bMarker) PURE; + STDMETHOD(SetMarkerBit) (THIS_ HXBOOL bMarker) PURE; + + STDMETHOD(GetPayloadType) (THIS_ REF(UINT8)unPayloadType) PURE; + STDMETHOD(SetPayloadType) (THIS_ UINT8 unPayloadType) PURE; + + STDMETHOD(GetSequenceNumber) (THIS_ REF(UINT16)unSeqNo) PURE; + STDMETHOD(SetSequenceNumber) (THIS_ UINT16 unSeqNo) PURE; + + STDMETHOD(GetTimeStamp) (THIS_ REF(UINT32)ulTS) PURE; + STDMETHOD(SetTimeStamp) (THIS_ UINT32 ulTS) PURE; + + STDMETHOD(GetSSRC) (THIS_ REF(UINT32)ulSSRC) PURE; + STDMETHOD(SetSSRC) (THIS_ UINT32 ulSSRC) PURE; + + + STDMETHOD(GetCSRCList) (THIS_ REF(const char*) pulCSRC) PURE; + STDMETHOD(SetCSRCList) (THIS_ const char* pCSRCList, UINT32 ulSize) PURE; + STDMETHOD(GetPadding) (THIS_ REF(const char*) pPadding) PURE; + STDMETHOD(SetPadding) (THIS_ const char* pPadding, UINT32 ulSize) PURE; + STDMETHOD(GetExtension) (THIS_ REF(const char*) pExtension) PURE; + STDMETHOD(SetExtension) (THIS_ const char* pExtension, UINT32 ulSize) PURE; +}; + + +/**************************************************************************** + * + * Interface: + * + * IHXValues + * + * Purpose: + * + * This is an interface to a generic name-value pair facility. This + * is used in various places (such as stream headers). + * + * IID_IHXValues: + * + * {00001302-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXValues, 0x00001302, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +/* + * The IHXCommonClassFactory supports creating an instance + * of this object. + */ +#define CLSID_IHXValues IID_IHXValues + +#undef INTERFACE +#define INTERFACE IHXValues + +DECLARE_INTERFACE_(IHXValues, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXValues methods + */ + + /* + * Note: That strings returned as references should be copied or + * used immediately because their lifetime is only as long as the + * IHXValues's objects lifetime. + * + * Note: Your iterator will be reset once you give up control to the + * RMA core (i.e. you exit whatever function gave you a time slice). + */ + + STDMETHOD(SetPropertyULONG32) (THIS_ + const char* pPropertyName, + ULONG32 uPropertyValue) PURE; + + STDMETHOD(GetPropertyULONG32) (THIS_ + const char* pPropertyName, + REF(ULONG32) uPropertyName) PURE; + + STDMETHOD(GetFirstPropertyULONG32) (THIS_ + REF(const char*) pPropertyName, + REF(ULONG32) uPropertyValue) PURE; + + STDMETHOD(GetNextPropertyULONG32) (THIS_ + REF(const char*) pPropertyName, + REF(ULONG32) uPropertyValue) PURE; + + STDMETHOD(SetPropertyBuffer) (THIS_ + const char* pPropertyName, + IHXBuffer* pPropertyValue) PURE; + + STDMETHOD(GetPropertyBuffer) (THIS_ + const char* pPropertyName, + REF(IHXBuffer*) pPropertyValue) PURE; + + STDMETHOD(GetFirstPropertyBuffer) (THIS_ + REF(const char*) pPropertyName, + REF(IHXBuffer*) pPropertyValue) PURE; + + STDMETHOD(GetNextPropertyBuffer) (THIS_ + REF(const char*) pPropertyName, + REF(IHXBuffer*) pPropertyValue) PURE; + + STDMETHOD(SetPropertyCString) (THIS_ + const char* pPropertyName, + IHXBuffer* pPropertyValue) PURE; + + STDMETHOD(GetPropertyCString) (THIS_ + const char* pPropertyName, + REF(IHXBuffer*) pPropertyValue) PURE; + + STDMETHOD(GetFirstPropertyCString) (THIS_ + REF(const char*) pPropertyName, + REF(IHXBuffer*) pPropertyValue) PURE; + + STDMETHOD(GetNextPropertyCString) (THIS_ + REF(const char*) pPropertyName, + REF(IHXBuffer*) pPropertyValue) PURE; +}; + +/**************************************************************************** + * + * Interface: + * + * IHXValues2 + * + * Purpose: + * This is an extension to the IHXValues interface. This extension + * let's store IUnknown properties and remove values + * + * IID_IHXValues2: + * + * {7AE64D81-C5AB-4b0a-94F2-B4D6DD2BDA7A} + * + */ +DEFINE_GUID(IID_IHXValues2, +0x7ae64d81, 0xc5ab, 0x4b0a, 0x94, 0xf2, 0xb4, 0xd6, 0xdd, 0x2b, 0xda, 0x7a); + +#define CLSID_IHXValues2 IID_IHXValues2 + +#undef INTERFACE +#define INTERFACE IHXValues2 + +DECLARE_INTERFACE_(IHXValues2, IHXValues) +{ + /* + * IHXValues2 methods + */ + + STDMETHOD(SetPropertyObject) (THIS_ + const char* pPropertyName, + IUnknown* pPropertyValue) PURE; + + STDMETHOD(GetPropertyObject) (THIS_ + const char* pPropertyName, + REF(IUnknown*) pPropertyValue) PURE; + + STDMETHOD(GetFirstPropertyObject) (THIS_ + REF(const char*) pPropertyName, + REF(IUnknown*) pPropertyValue) PURE; + + STDMETHOD(GetNextPropertyObject) (THIS_ + REF(const char*) pPropertyName, + REF(IUnknown*) pPropertyValue) PURE; + + /************************************************************************ + * Method: + * IHXValues2::Remove + * Purpose: + * Remove all items matching pKey. (If you know what datatype you saved + * the key as, use the specific method.) + */ + STDMETHOD(Remove) (const char* pKey) PURE; + + /************************************************************************ + * Method: + * IHXValues2::RemoveULONG32 + * Purpose: + * Remove all ULONG32 items matching pKey. + */ + STDMETHOD(RemoveULONG32) (const char* pKey) PURE; + + /************************************************************************ + * Method: + * IHXValues2::RemoveBuffer + * Purpose: + * Remove all Buffer items matching pKey. + */ + STDMETHOD(RemoveBuffer) (const char* pKey) PURE; + + /************************************************************************ + * Method: + * IHXValues2::RemoveCString + * Purpose: + * Remove all CString items matching pKey. + */ + STDMETHOD(RemoveCString) (const char* pKey) PURE; + + /************************************************************************ + * Method: + * IHXValues2::RemoveObject + * Purpose: + * Remove all IUnknown items matching pKey. + */ + STDMETHOD(RemoveObject) (const char* pKey) PURE; +}; + +/**************************************************************************** + * + * Interface: + * + * IHXValuesRemove + * + * Purpose: + * + * This interface is to add Remove methods to a class that supports + * IHXValues. All classes that support this interface will also + * support IHXValues. + * + * + * + * IID_IHXValuesRemove: + * + * {00001303-0901-11d1-8B06-00A024406D59} + * + */ +DEFINE_GUID(IID_IHXValuesRemove, 0x00001303, 0x901, 0x11d1, 0x8b, 0x6, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +/* + * The IHXCommonClassFactory does not support creating an instance + * of this object. + */ + +#undef INTERFACE +#define INTERFACE IHXValuesRemove + +DECLARE_INTERFACE_(IHXValuesRemove, IUnknown) +{ + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + /* + * IHXValuesRemove methods + */ + + /************************************************************************ + * Method: + * IHXKeyValuesRemove::Remove + * Purpose: + * Remove all items matching pKey. (If you know what datatype you saved + * the key as, use the specific method.) + */ + STDMETHOD(Remove) (const char* pKey) PURE; + + /************************************************************************ + * Method: + * IHXKeyValuesRemove::RemoveULONG32 + * Purpose: + * Remove all ULONG32 items matching pKey. + */ + STDMETHOD(RemoveULONG32) (const char* pKey) PURE; + + /************************************************************************ + * Method: + * IHXKeyValuesRemove::RemoveBuffer + * Purpose: + * Remove all Buffer items matching pKey. + */ + STDMETHOD(RemoveBuffer) (const char* pKey) PURE; + + /************************************************************************ + * Method: + * IHXKeyValuesRemove::RemoveCString + * Purpose: + * Remove all CString items matching pKey. + */ + STDMETHOD(RemoveCString) (const char* pKey) PURE; +}; + +// $Private: +DEFINE_GUID(IID_IHXClientPacket, 0x00001304, 0x0901, 0x11d1, 0x8b, 0x06, 0x0, + 0xa0, 0x24, 0x40, 0x6d, 0x59); + +#undef INTERFACE +#define INTERFACE IHXClientPacket + +DECLARE_INTERFACE_(IHXClientPacket, IUnknown) +{ + /* + * IUnknown methods + */ + + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; +}; + +DEFINE_GUID(IID_IHXBroadcastDistPktExt, 0x3b022922, 0x94a1, 0x4be5, 0xbd, 0x25, 0x21, + 0x6d, 0xa2, 0x7b, 0xd8, 0xfc); + +#undef INTERFACE +#define INTERFACE IHXBroadcastDistPktExt + +DECLARE_INTERFACE_(IHXBroadcastDistPktExt, IUnknown) +{ + /* + * IUnknown methods + */ + + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj) PURE; + + STDMETHOD_(ULONG32,AddRef) (THIS) PURE; + + STDMETHOD_(ULONG32,Release) (THIS) PURE; + + STDMETHOD_(UINT32,GetSeqNo) (THIS) PURE; + STDMETHOD_(UINT32,GetStreamSeqNo) (THIS) PURE; + STDMETHOD_(HXBOOL,GetIsLostRelaying) (THIS) PURE; + STDMETHOD_(HXBOOL,SupportsLowLatency) (THIS) PURE; + STDMETHOD_(UINT16,GetRuleSeqNoArraySize) (THIS) PURE; + STDMETHOD_(UINT16*,GetRuleSeqNoArray) (THIS) PURE; + + STDMETHOD(SetSeqNo) (THIS_ UINT32 ulSeqNo) PURE; + STDMETHOD(SetStreamSeqNo) (THIS_ UINT32 ulStreamSeqNo) PURE; + STDMETHOD(SetIsLostRelaying) (THIS_ HXBOOL bLostRelay) PURE; + STDMETHOD(SetRuleSeqNoArray) (THIS_ UINT16* pRuleSeqNoArray, UINT16 uSize) PURE; +}; + +// $EndPrivate. + +#endif /* _IHXPCKTS_H_ */ + diff --git a/amarok/src/engine/helix/helix-sp/helix-include/common/system/dllpath.h b/amarok/src/engine/helix/helix-sp/helix-include/common/system/dllpath.h new file mode 100644 index 00000000..604c5448 --- /dev/null +++ b/amarok/src/engine/helix/helix-sp/helix-include/common/system/dllpath.h @@ -0,0 +1,238 @@ + +/* + * + * This software is released under the provisions of the GPL version 2. + * see file "COPYING". If that file is not available, the full statement + * of the license can be found at + * + * http://www.fsf.org/licensing/licenses/gpl.txt + * + * Portions Copyright (c) 1995-2004 RealNetworks, Inc. All Rights Reserved. + * + */ + +#ifndef _DLL_PATH +#define _DLL_PATH + +#include "hxcom.h" +#include "hxmap.h" +#include "hxstring.h" + +#ifdef _MACINTOSH +#pragma export on +STDAPI SetDLLAccessPath(const char* pPathDescriptor); +#pragma export off +#endif + +#if defined(HELIX_CONFIG_NOSTATICS) +#include "globals/hxglobals.h" +#endif + +/* + * Used to identify dll types. + */ +typedef enum dll_types +{ + DLLTYPE_NOT_DEFINED = 0, // Arbitrary DLLs (no predefined path used) + DLLTYPE_PLUGIN, // Plug-ins + DLLTYPE_CODEC, // Codecs + DLLTYPE_ENCSDK, // Encoder SDK DLLs + DLLTYPE_COMMON, // Common libraries + DLLTYPE_UPDATE, // Setup/Upgrade libraries + DLLTYPE_OBJBROKR, // Special entry for the object broker + DLLTYPE_RCAPLUGIN, // Gemini plugins + DLLTYPE_NUMBER // Not a type, used as number of predefined types. +} DLLTYPES; + +typedef HX_RESULT (HXEXPORT_PTR FPSETDLLACCESSPATH) (const char*); + + +class DLLAccessPath +{ +public: + DLLAccessPath(); + virtual ~DLLAccessPath(); + + // This class is only ref-counted if it is used as such. + // Most of the system uses this as a non-refcounted class. + STDMETHOD_(ULONG32,AddRef) (THIS); + STDMETHOD_(ULONG32,Release) (THIS); + + HX_RESULT SetAccessPaths(const char* pPathDescriptor); + HX_RESULT SetPath(UINT16 nLibType, const char* szPath); + HX_RESULT SetPath(const char* szLibType, const char* szPath); + + const char* GetPath(UINT16 nLibType); + const char* GetPath(const char* szLibType); + const char* GetLibTypeName(UINT16 nLibType); + + HX_RESULT PassDLLAccessPath(FPSETDLLACCESSPATH pSetDLLAccessPath); + + HX_RESULT AddPathToEnvironment(const char* szPath); + HX_RESULT RestoreEnvironment(); + UINT32 GetNumPaths() {return m_mapPathes.GetCount();} + +protected: + + static const char* const zm_pszDllTypeNames[DLLTYPE_NUMBER]; + +private: + + LONG32 m_lRefCount; + + CHXMapStringToString m_mapPathes; + CHXString m_strPathEnvVar; +}; + +extern DLLAccessPath* GetDLLAccessPath(); + +class DLLAccessDestructor +{ +public: + DLLAccessDestructor() {}; + ~DLLAccessDestructor() + { +#ifndef _VXWORKS + if (GetDLLAccessPath()) + { + GetDLLAccessPath()->Release(); + } +#endif + } +}; + + +// +// Macros for setting DLL loading paths +// +#ifndef _VXWORKS + +#if defined(_STATICALLY_LINKED) && !defined(HELIX_FEATURE_SERVER) + +// We need this since many DLLs have this listed +// as an export, so we have to have it defined +#define ENABLE_DLLACCESS_PATHS(GLOBAL) \ +STDAPI ENTRYPOINT(SetDLLAccessPath)(const char* pPathDescriptor) \ +{ \ + return HXR_OK; \ +} + +#define ENABLE_MULTILOAD_DLLACCESS_PATHS(GLOBAL) \ +STDAPI ENTRYPOINT(SetDLLAccessPath)(const char* pPathDescriptor) \ +{ \ + return HXR_OK; \ +} + +#elif defined(HELIX_CONFIG_NOSTATICS) + +#define ENABLE_DLLACCESS_PATHS(GLOBAL) \ + static const DLLAccessPath* const _g_##GLOBAL = NULL; \ + \ + DLLAccessPath* ENTRYPOINT(GetDLLAccessPath)() \ + { \ + return &HXGlobalDLLAccessPath::Get(&_g_##GLOBAL); \ + } \ + \ + STDAPI ENTRYPOINT(SetDLLAccessPath)(const char* pPathDescriptor) \ + { \ + return (GetDLLAccessPath()->SetAccessPaths(pPathDescriptor)); \ + } + +#else /* #if defined(_STATICALLY_LINKED) && !defined(HELIX_FEATURE_SERVER) */ + +#define ENABLE_DLLACCESS_PATHS(GLOBAL) \ + DLLAccessPath GLOBAL; \ + \ + DLLAccessPath* ENTRYPOINT(GetDLLAccessPath)() \ + { \ + return &GLOBAL; \ + } \ + \ + STDAPI ENTRYPOINT(SetDLLAccessPath)(const char* pPathDescriptor) \ + { \ + return (GetDLLAccessPath()->SetAccessPaths(pPathDescriptor)); \ + } + +#ifdef _UNIX + +#define ENABLE_MULTILOAD_DLLACCESS_PATHS(GLOBAL) \ + DLLAccessPath* GLOBAL = NULL; \ + \ + DLLAccessPath* GetDLLAccessPath() \ + { \ + if (!GLOBAL) \ + { \ + GLOBAL = new DLLAccessPath(); \ + GLOBAL->AddRef(); \ + } \ + return GLOBAL; \ + } \ + \ + STDAPI ENTRYPOINT(SetDLLAccessPath)(const char* pPathDescriptor) \ + { \ + if (!GLOBAL) \ + { \ + GLOBAL = new DLLAccessPath(); \ + GLOBAL->AddRef(); \ + } \ + \ + return (GLOBAL->SetAccessPaths(pPathDescriptor)); \ + } + +#else /* #ifdef _UNIX */ + +#define ENABLE_MULTILOAD_DLLACCESS_PATHS(GLOBAL) \ + DLLAccessPath* GLOBAL = NULL; \ + DLLAccessDestructor GLOBALDestructor; \ + \ + DLLAccessPath* GetDLLAccessPath() \ + { \ + if (!GLOBAL) \ + { \ + GLOBAL = new DLLAccessPath(); \ + GLOBAL->AddRef(); \ + } \ + return GLOBAL; \ + } \ + \ + STDAPI ENTRYPOINT(SetDLLAccessPath)(const char* pPathDescriptor) \ + { \ + if (!GLOBAL) \ + { \ + GLOBAL = new DLLAccessPath(); \ + GLOBAL->AddRef(); \ + } \ + \ + return (GLOBAL->SetAccessPaths(pPathDescriptor)); \ + } + +#endif /* #ifdef _UNIX #else */ + +#endif /* #if defined(_STATICALLY_LINKED) #else */ + +#else /* #ifndef _VXWORKS */ + +#define ENABLE_DLLACCESS_PATHS(GLOBAL) \ + STDAPI ENTRYPOINT(SetDLLAccessPath)(const char* pPathDescriptor) \ + { \ + return 0; \ + } + +#ifdef _SERVER +#define ENABLE_MULTILOAD_DLLACCESS_PATHS(GLOBAL) \ + STDAPI ENTRYPOINT(SetDLLAccessPath)(const char* pPathDescriptor) \ + { \ + return 0; \ + } +#else +#define ENABLE_MULTILOAD_DLLACCESS_PATHS(GLOBAL) \ + STDAPI ENTRYPOINT(SetDLLAccessPath)(const char* pPathDescriptor) \ + { \ + return 0; \ + } +#endif + +#endif /* #ifndef _VXWORKS #else */ + +#endif /* #ifndef _DLL_PATH */ + diff --git a/amarok/src/engine/helix/helix-sp/helix-include/common/util/hxmangle.h b/amarok/src/engine/helix/helix-sp/helix-include/common/util/hxmangle.h new file mode 100644 index 00000000..dd7b8845 --- /dev/null +++ b/amarok/src/engine/helix/helix-sp/helix-include/common/util/hxmangle.h @@ -0,0 +1,30 @@ + +/* + * + * This software is released under the provisions of the GPL version 2. + * see file "COPYING". If that file is not available, the full statement + * of the license can be found at + * + * http://www.fsf.org/licensing/licenses/gpl.txt + * + * Portions Copyright (c) 1995-2004 RealNetworks, Inc. All Rights Reserved. + * + */ + +#ifndef _HXMANGLE_H_ +#define _HXMANGLE_H_ + +#define CLIENT_GUID_REGNAME "Rotuma" +#define CLIENT_ID_REGNAME "Futuna" + +static const char CLIENT_ZERO_GUID[] = "00000000-0000-0000-0000-000000000000"; + +// given an input buffer, mangle it and return it back +// The caller has to call delete[] on the returned pointer +char* Cipher(const char* pszBuffer); + +// given an input buffer, de-mangle it and return it back +// The caller has to call delete[] on the returned pointer +char* DeCipher(const char* pszBuffer); + +#endif // _HXMANGLE_H_ diff --git a/amarok/src/engine/helix/helix-sp/helix-include/common/util/hxstrutl.h b/amarok/src/engine/helix/helix-sp/helix-include/common/util/hxstrutl.h new file mode 100644 index 00000000..d9df49cc --- /dev/null +++ b/amarok/src/engine/helix/helix-sp/helix-include/common/util/hxstrutl.h @@ -0,0 +1,143 @@ + +/* + * + * This software is released under the provisions of the GPL version 2. + * see file "COPYING". If that file is not available, the full statement + * of the license can be found at + * + * http://www.fsf.org/licensing/licenses/gpl.txt + * + * Portions Copyright (c) 1995-2004 RealNetworks, Inc. All Rights Reserved. + * + */ + +#ifndef _HXSTRUTL_H_ +#define _HXSTRUTL_H_ + +#include "hlxclib/string.h" /* for strxxx functions */ +#include "hlxclib/stdlib.h" /* for atoi64() and itoa() functionallity */ + +#include "safestring.h" + +#if !defined(_VXWORKS) +#ifdef _UNIX +#include <strings.h> +#include <ctype.h> +#endif +#endif +#ifdef _MACINTOSH +#include <ctype.h> +#endif + +#include "hxresult.h" + +#if defined (_MACINTOSH) + +#define isascii isprint + +inline const char *AnsiNext(const char* pcPtr) { return( pcPtr + 1 ); } +inline const char *AnsiPrev(const char * /* pcStart */, const char* pcPtr) { return (pcPtr - 1 ); } + +int CopyP2CString(ConstStr255Param inSource, char* outDest, int inDestLength); +void CopyC2PString(const char* inSource, Str255 outDest); +char WINToMacCharacter( char inWINChar ); +// these functions are used to convert Windows extended chars (used in non-English Roman languages) +// to Mac extended chars & vice-versa +void StripWinChars( char* pChars); +void StripMacChars( char* pChars); + +inline void pstrcpy(Str255 dst, ConstStr255Param src) { BlockMoveData(src, dst, 1+src[0]); } + +#ifndef _CARBON +inline void PStrCopy(StringPtr dest, ConstStr255Param src) { BlockMoveData(src, dest, 1+src[0]); } +inline void p2cstrcpy(char *dst, ConstStr255Param src) { CopyP2CString(src, dst, 255); } +inline void c2pstrcpy(Str255 dst, const char * src) { CopyC2PString(src, dst); } +#endif + +#endif /* _MACINTOSH */ + +#define CR (CHAR) '\r' +#define LF (CHAR) '\n' +#define CRLF "\r\n" + +#ifdef _WIN32 + #define LINEBREAK "\015\012" + #define LINEBREAK_LEN 2 +#else + #define LINEBREAK "\012" + #define LINEBREAK_LEN 1 +#endif /* _WIN32 */ + +#define LINE_BUFFER_SIZE 4096 +#define MAX_BYTES_PER_COOKIE 4096 +#define MAX_NUMBER_OF_COOKIES 300 +#define MAX_COOKIES_PER_SERVER 20 + +/* +According to C99 7.4/1: +--------------- +The header <ctype.h> declares several functions useful for +classifying and mapping characters. In all cases the argument is an +int, the value of which shall be representable as an unsigned char or +shall equal the value of the macro EOF. If the argument has any other +value, the behavior is undefined. +--------------- +Typecast the value to an (unsigned char) before passing it to isspace() to ensure that +if the value is a signed char it doesn't get bit extended on certain (VC) compilers. +*/ +#define IS_SPACE(x) (isspace((unsigned char) x)) + +#ifdef __cplusplus +void StrAllocCopy(char*& pDest, const char* pSrc); +#else +void StrAllocCopy(char** pDest, const char* pSrc); +#endif +char* StripLine(char* pLine); + +#include "hxtypes.h" +#include "hxcom.h" +typedef _INTERFACE IHXValues IHXValues; +HX_RESULT SaveStringToHeader(IHXValues* /* IN OUT */ pHeader, + const char* /* IN */ pszKey, + const char* /* IN */ pszValue); + +char* StrStrCaseInsensitive(const char* str1, const char* str2); +char* StrNStr(const char* str1, const char* str2, size_t depth1, size_t depth2); +char *StrNChr(const char *str, int c, size_t depth); +char *StrNRChr(const char *str, int c, size_t depth); +size_t StrNSpn(const char *str1, const char *str2, size_t depth1, size_t depth2); +size_t StrNCSpn(const char *str1, const char *str2, size_t depth1, size_t depth2); + +char* StrToUpper(char *pString); + +#if defined( _SYMBIAN) +#define NEW_FAST_TEMP_STR(NAME, EstimatedBiggestSize, LenNeeded) \ + char* NAME = new char[(LenNeeded)]; + +#define DELETE_FAST_TEMP_STR(NAME) \ + delete[] NAME; + +#else +/* XXXSMP We can use alloca() on platforms that support it for more speed! */ +#define NEW_FAST_TEMP_STR(NAME, EstimatedBiggestSize, LenNeeded) \ + char __##NAME##__StaticVersion[EstimatedBiggestSize]; \ + char* NAME; \ + UINT32 ulNeeded##NAME##Len = (LenNeeded); \ + \ + if (ulNeeded##NAME##Len <= EstimatedBiggestSize) \ + { \ + NAME = __##NAME##__StaticVersion; \ + } \ + else \ + { \ + NAME = new char[ulNeeded##NAME##Len]; \ + } + +#define DELETE_FAST_TEMP_STR(NAME) \ + if (NAME != __##NAME##__StaticVersion) \ + { \ + delete[] NAME; \ + } +#endif /* defined(_SYMBIAN) */ + +#endif /* _HXSTRUTL_H_ */ diff --git a/amarok/src/engine/helix/helix-sp/helix-include/runtime/hlxclib/assert.h b/amarok/src/engine/helix/helix-sp/helix-include/runtime/hlxclib/assert.h new file mode 100644 index 00000000..f1982a22 --- /dev/null +++ b/amarok/src/engine/helix/helix-sp/helix-include/runtime/hlxclib/assert.h @@ -0,0 +1,51 @@ + +/* + * + * This software is released under the provisions of the GPL version 2. + * see file "COPYING". If that file is not available, the full statement + * of the license can be found at + * + * http://www.fsf.org/licensing/licenses/gpl.txt + * + * Portions Copyright (c) 1995-2004 RealNetworks, Inc. All Rights Reserved. + * + */ + +#ifndef HLXSYS_ASSERT_H +#define HLXSYS_ASSERT_H + +#if defined(_OPENWAVE) +#include "platform/openwave/hx_op_debug.h" +#define assert(x) OpASSERT(x) +#elif !defined(WIN32_PLATFORM_PSPC) +#include <assert.h> +#endif /* !defined(WIN32_PLATFORM_PSPC) */ + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + +void __helix_assert(const char* pExpression, + const char* pFilename, int lineNum); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + + +#if defined(WIN32_PLATFORM_PSPC) + +#include "hxtypes.h" +#include <winbase.h> +#include <dbgapi.h> + +#ifdef _DEBUG +#define assert(x) if(!(x)) __helix_assert(#x, __FILE__, __LINE__); +#else /* _DEBUG */ +#define assert(x) +#endif /* _DEBUG */ + +#endif /* defined(WIN32_PLATFORM_PSPC) */ + +#endif /* HLXSYS_ASSERT_H */ diff --git a/amarok/src/engine/helix/helix-sp/helix-include/runtime/hlxclib/limits.h b/amarok/src/engine/helix/helix-sp/helix-include/runtime/hlxclib/limits.h new file mode 100644 index 00000000..fbd2dc06 --- /dev/null +++ b/amarok/src/engine/helix/helix-sp/helix-include/runtime/hlxclib/limits.h @@ -0,0 +1,31 @@ + +/* + * + * This software is released under the provisions of the GPL version 2. + * see file "COPYING". If that file is not available, the full statement + * of the license can be found at + * + * http://www.fsf.org/licensing/licenses/gpl.txt + * + * Portions Copyright (c) 1995-2004 RealNetworks, Inc. All Rights Reserved. + * + */ + +#ifndef HLXSYS_LIMITS_H +#define HLXSYS_LIMITS_H + +#ifdef _OPENWAVE_SIMULATOR +#ifndef _WIN32 +#define _WIN32 +#define LIMITS_UNDEF_WIN32 +#endif /* _WIN32 */ +#endif /* _OPENWAVE_SIMULATOR */ + +#include <limits.h> + +#ifdef LIMITS_UNDEF_WIN32 +#undef _WIN32 +#undef LIMITS_UNDEF_WIN32 +#endif /* LIMITS_UNDEF_WIN32 */ + +#endif /* HLXSYS_LIMITS_H */ diff --git a/amarok/src/engine/helix/helix-sp/helix-include/runtime/hlxclib/memory.h b/amarok/src/engine/helix/helix-sp/helix-include/runtime/hlxclib/memory.h new file mode 100644 index 00000000..2cc9d506 --- /dev/null +++ b/amarok/src/engine/helix/helix-sp/helix-include/runtime/hlxclib/memory.h @@ -0,0 +1,29 @@ + +/* + * + * This software is released under the provisions of the GPL version 2. + * see file "COPYING". If that file is not available, the full statement + * of the license can be found at + * + * http://www.fsf.org/licensing/licenses/gpl.txt + * + * Portions Copyright (c) 1995-2004 RealNetworks, Inc. All Rights Reserved. + * + */ + +#ifndef HLXSYS_MEMORY_H +#define HLXSYS_MEMORY_H + +#if defined(_SYMBIAN) +#include <string.h> +#elif defined(_OPENWAVE) +#include "platform/openwave/hx_op_stdc.h" +#elif !defined(__TCS__) && !defined(_VXWORKS) +#include <memory.h> +#endif + +#if defined(_SOLARIS) || defined(_MAC_CFM) +#include <string.h> +#endif + +#endif /* HLXSYS_MEMORY_H */ diff --git a/amarok/src/engine/helix/helix-sp/helix-include/runtime/hlxclib/stdio.h b/amarok/src/engine/helix/helix-sp/helix-include/runtime/hlxclib/stdio.h new file mode 100644 index 00000000..117f9ec5 --- /dev/null +++ b/amarok/src/engine/helix/helix-sp/helix-include/runtime/hlxclib/stdio.h @@ -0,0 +1,117 @@ + +/* + * + * This software is released under the provisions of the GPL version 2. + * see file "COPYING". If that file is not available, the full statement + * of the license can be found at + * + * http://www.fsf.org/licensing/licenses/gpl.txt + * + * Portions Copyright (c) 1995-2004 RealNetworks, Inc. All Rights Reserved. + * + */ + +#ifndef HLXSYS_STDIO_H +#define HLXSYS_STDIO_H + +#if defined(_OPENWAVE) +#include "platform/openwave/hx_op_debug.h" +#include "platform/openwave/hx_op_stdc.h" +#include "platform/openwave/hx_op_fs.h" +#include "hlxclib/sys/types.h" +#else +#include <stdio.h> +#include <stdarg.h> +#endif + +#if __cplusplus +extern "C" { +#endif +/* Make sure vsnprintf is defined for all platforms */ + +int __helix_snprintf(char *str, size_t size, const char *format, ...); +int __helix_vsnprintf(char *str, size_t size, const char *format, va_list ap); + + +#if defined(_OPENWAVE) +int __helix_printf(const char* format, ...); +int __helix_vprintf(const char *format, va_list ap); + +int __helix_sscanf(const char *buffer, const char *format, ...); + +#define printf __helix_printf +#define vprintf __helix_vprintf +#define snprintf op_snprintf +#define vsnprintf __helix_vsnprintf +#define _vsnprintf __helix_vsnprintf +#define sscanf __helix_sscanf +#define unlink OpFsRemove + +typedef void* FILE; +#define stdin (FILE*)0 +#define stdout (FILE*)1 +#define stderr (FILE*)2 + +#ifndef EOF +#define EOF ((size_t)-1) + +FILE* __helix_fopen(const char *, const char *); +size_t __helix_fread(void *, size_t, size_t, FILE *); +size_t __helix_fwrite(const void *, size_t, size_t, FILE *); +int __helix_fseek(FILE *, long, int); +int __helix_fclose(FILE *); +int __helix_feof(FILE *); +long __helix_ftell(FILE *); +char* __helix_fgets(char*, int, FILE *); +int __helix_fputc(int, FILE *); +int __helix_ferror(FILE *); + +int __helix_fflush(FILE *); +int __helix_rename(const char *oldname, const char *newname); + +FILE* __helix_fdopen(int, const char *); +int __helix_fileno(FILE* ); + +int __helix_fprintf(FILE* f, const char *format, ...); +int __helix_vfprintf(FILE* f, const char *format, va_list ap); +#define puts(x) printf("%s\n", (x)) + +#define fopen __helix_fopen +#define fread __helix_fread +#define fseek __helix_fseek +#define fwrite __helix_fwrite +#define fclose __helix_fclose +#define feof __helix_feof +#define ftell __helix_ftell +#define fgets __helix_fgets +#define fputc __helix_fputc +#define putc __helix_fputc +#define ferror __helix_ferror +#define rewind(fp) __helix_fseek(fp, 0, SEEK_SET) + +#define fprintf __helix_fprintf +#define vfprintf __helix_fprintf + +#define fflush __helix_fflush +#define rename __helix_rename + +#define _fdopen __helix_fdopen + +#define fileno __helix_fileno + +#endif // end of _OPENWAVE + +#elif defined(_WINDOWS) +#define snprintf _snprintf +#define vsnprintf _vsnprintf + +#elif defined(_SYMBIAN) || defined(_WINCE) || defined(_IRIX) +#define snprintf __helix_snprintf +#define vsnprintf __helix_vsnprintf +#endif + +#if __cplusplus +} +#endif + +#endif /* HLXSYS_STDIO_H */ diff --git a/amarok/src/engine/helix/helix-sp/helix-include/runtime/hlxclib/stdlib.h b/amarok/src/engine/helix/helix-sp/helix-include/runtime/hlxclib/stdlib.h new file mode 100644 index 00000000..c9c427a2 --- /dev/null +++ b/amarok/src/engine/helix/helix-sp/helix-include/runtime/hlxclib/stdlib.h @@ -0,0 +1,136 @@ + +/* + * + * This software is released under the provisions of the GPL version 2. + * see file "COPYING". If that file is not available, the full statement + * of the license can be found at + * + * http://www.fsf.org/licensing/licenses/gpl.txt + * + * Portions Copyright (c) 1995-2004 RealNetworks, Inc. All Rights Reserved. + * + */ + +#ifndef HLXSYS_STDLIB_H +#define HLXSYS_STDLIB_H + +#include "hxtypes.h" + +#if defined(_OPENWAVE) +// XXXSAB Include compiler <stdlib.h> so we can modify it??? +#ifdef _OPENWAVE_SIMULATOR +#ifndef _WIN32 +#define STDLIB_UNDEF_WIN32 +#define _WIN32 +#endif /* _WIN32 */ +#endif /* _OPENWAVE_SIMULATOR */ + +#include <stdlib.h> +#undef itoa // just in case + +#ifdef STDLIB_UNDEF_WIN32 +#undef _WIN32 +#undef STDLIB_UNDEF_WIN32 +#endif /* STDLIB_UNDEF_WIN32 */ + +// XXXSAB Define malloc()/free() wrappers for Openwave in here??? +#else +#include <stdlib.h> +#endif + +char* __helix_itoa(int val, char *str, int radix); +char* __helix_i64toa(INT64 val, char *str, int radix); +INT64 __helix_atoi64(char* str); +void* __helix_bsearch( const void *key, const void *base, size_t num, + size_t width, + int ( *compare ) ( const void *elem1, + const void *elem2 ) ); +int __helix_remove(const char* pPath); +int __helix_putenv(const char* pStr); +char* __helix_getenv(const char* pName); + +#if defined(_WINDOWS) && !defined(_OPENWAVE) + +#if !defined(WIN32_PLATFORM_PSPC) +_inline char* +i64toa(INT64 val, char* str, int radix) +{ + return _i64toa(val, str, radix); +} + +#else /* !defined(WIN32_PLATFORM_PSPC) */ + +_inline +int remove(const char* pPath) +{ + return __helix_remove(pPath); +} + +_inline +char* getenv(const char* pName) +{ + return __helix_getenv(pName); +} + +#define i64toa __helix_i64toa +#define itoa __helix_itoa + +_inline +void* bsearch( const void *key, const void *base, size_t num, + size_t width, + int ( *compare ) ( const void *elem1, + const void *elem2 ) ) +{ + return __helix_bsearch(key, base, num, width, compare); +} +#endif /* !defined(WIN32_PLATFORM_PSPC) */ + +_inline INT64 +atoi64(const char* str) +{ + return _atoi64(str); +} +#endif /* _WINDOWS */ + +#if defined (_MACINTOSH) + +#define itoa __helix_itoa +#define i64toa __helix_i64toa +#define atoi64 __helix_atoi64 + +#endif /* _MACINTOSH */ + +#if defined (_UNIX) && !defined (__QNXNTO__) + +// Convert integer to string + +#define itoa __helix_itoa +#define i64toa __helix_i64toa +#define atoi64 __helix_atoi64 + +#endif /* _UNIX */ + +#if defined(_SYMBIAN) + +#define itoa __helix_itoa +#define i64toa __helix_i64toa +#define atoi64 __helix_atoi64 +#define putenv __helix_putenv + +#endif + +#if defined(_OPENWAVE) + +#define itoa(v,s,r) __helix_itoa((v),(s),(r)) +#define i64toa(v,s,r) __helix_i64toa((v),(s),(r)) +#define atoi64(s) __helix_atoi64((s)) +#define putenv __helix_putenv + +__inline int remove(const char* pPath) +{ + return __helix_remove(pPath); +} + +#endif // _OPENWAVE + +#endif /* HLXSYS_STDLIB_H */ diff --git a/amarok/src/engine/helix/helix-sp/helix-include/runtime/hlxclib/string.h b/amarok/src/engine/helix/helix-sp/helix-include/runtime/hlxclib/string.h new file mode 100644 index 00000000..5da00a5a --- /dev/null +++ b/amarok/src/engine/helix/helix-sp/helix-include/runtime/hlxclib/string.h @@ -0,0 +1,254 @@ + +/* + * + * This software is released under the provisions of the GPL version 2. + * see file "COPYING". If that file is not available, the full statement + * of the license can be found at + * + * http://www.fsf.org/licensing/licenses/gpl.txt + * + * Portions Copyright (c) 1995-2004 RealNetworks, Inc. All Rights Reserved. + * + */ + +#ifndef HLXSYS_STRING_H +#define HLXSYS_STRING_H + +#if defined(_OPENWAVE) +#include "platform/openwave/hx_op_stdc.h" +#else +#include <string.h> +#endif /* !_OPENWAVE */ + +#ifdef _SYMBIAN +//on symbian we have stuff scattered all about. +# include <stdlib.h> +# include <ctype.h> +#endif +#if !defined(_VXWORKS) +#ifdef _UNIX +#include <strings.h> +#endif /* _UNIX */ +#endif /* !defined(_VXWORKS) */ + +/* If we are on Windows and are compiling + * a .c file and are using Visual C++, + * then use __inline instead of inline. + */ +#if (defined(_WINDOWS) || defined(_OPENWAVE)) && \ + !defined(__cplusplus) && defined(_MSC_VER) +#define HLX_INLINE __inline +#else +#define HLX_INLINE inline +#endif + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +char * __helix_strrev(char * str); +void __helix_strlwr(char *s); +void __helix_strupr(char *s); + +const char* __helix_strnchr(const char* sc, const char c, size_t n); +const char* __helix_strnstr(const char* sc, const char* str, size_t n); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#define strnchr __helix_strnchr +#define strnstr __helix_strnstr + +#ifdef _WINDOWS +#ifdef _WINCE + int strcasecmp(const char* str1, const char* str2); + #else +HLX_INLINE int +strcasecmp(const char* str1, const char* str2) +{ + return _stricmp(str1, str2); +} +#endif //_WINCE +HLX_INLINE int +strncasecmp(const char* str1, const char* str2, int len) +{ + return _strnicmp(str1, str2, (size_t) len); +} + +#if defined(WIN32_PLATFORM_PSPC) +#define strrev __helix_strrev +#define stricmp strcasecmp +#define strcmpi strcasecmp +#define strnicmp strncasecmp +#define strlwr __helix_strlwr +#define strupr __helix_strupr +#endif /* defined(WIN32_PLATFORM_PSPC) */ + +#endif /* _WINDOWS */ + + +#if defined(_SYMBIAN) +unsigned long __helix_strtoul(const char*s, char**end, int base); +#define strtoul __helix_strtoul +#define strrev __helix_strrev +#define stricmp strcasecmp +#define strnicmp strncasecmp +#define strlwr __helix_strlwr +#define strupr __helix_strupr + +#endif /* _SYMBIAN */ + +#if defined(_OPENWAVE) +#define strcmpi stricmp +#define strrev __helix_strrev +#define strlwr __helix_strlwr +#undef stricmp +#undef strnicmp +#define stricmp strcasecmp +#define strnicmp strncasecmp +#endif /* _OPENWAVE_ARMULATOR */ + +#if defined (_MACINTOSH) + +#ifdef _MAC_MACHO + +#define strlwr __helix_strlwr +#define stricmp strcasecmp +#define strnicmp strncasecmp +#define strrev __helix_strrev + +#else + +int strnicmp(const char *first, const char *last, size_t count); +int stricmp(const char *first, const char *last); +char * strrev(char * str); + +#endif + +#define strcmpi stricmp + +#ifndef _MAC_MACHO + +HLX_INLINE int +strcasecmp(const char* str1, const char* str2) +{ + return stricmp(str1, str2); +} + +#endif + + +#endif /* _MACINTOSH */ + +#if defined (_UNIX) && !defined (__QNXNTO__) + +/* strcasecmp, strncasecmp are defined in strings.h */ +#define stricmp strcasecmp +#define strcmpi strcasecmp +#define strnicmp strncasecmp + +// Convert integer to string + +// reverse a string in place + +#define strrev __helix_strrev +#define strlwr __helix_strlwr +#define strupr __helix_strupr + +#endif /* _UNIX */ + + +#if defined (_MACINTOSH) || defined (_UNIX) +#define _tcsspn strspn +#define _tcscspn strcspn +#define _tcsrchr strchr +#define _tcsstr strstr +#endif + +#ifdef _VXWORKS +extern "C" { +int strncasecmp(const char *first, const char *last, size_t count); +int strcasecmp(const char *first, const char *last); +} +#endif /* _VXWORKS */ + + +#if defined(_WINDOWS) && !defined(__cplusplus) && defined(_MSC_VER) +#include "hlxclib/stdlib.h" // malloc() + +#define NEW_STRING_BUFFER new_string_buffer +#define HLX_ALLOC(x) ((char*) malloc((x))) +#define HLX_DEALLOC(x) free((x)) + +#elif defined(__cplusplus) +#define NEW_STRING_BUFFER new_string +#define HLX_ALLOC(x) (new char[(x)]) +#define HLX_DEALLOC(x) delete [] (x) +#endif /* #elif defined(__cplusplus) */ + +#ifdef HLX_ALLOC +HLX_INLINE char* +NEW_STRING_BUFFER(const void* mem, int len) +{ + char* str = HLX_ALLOC(len+1); + if (str) + { + memcpy((void*)str, mem, len); /* Flawfinder: ignore */ + str[len] = '\0'; + } + return str; +} + +HLX_INLINE char* +new_string(const char* str) +{ + char* pTmp = HLX_ALLOC(strlen(str)+1); + + return pTmp ? strcpy(pTmp, str) : NULL; /* Flawfinder: ignore */ +} + +HLX_INLINE char* +new_path_string(const char* str) +{ + char* pnew = HLX_ALLOC(strlen(str) + 1); + const char* psrc = str; + char* pdst = pnew; + if (!pnew) return NULL; + + while (*psrc) + { + if (*psrc == '/' || *psrc == '\\') + { +#if defined _WIN32 || defined(_SYMBIAN) + *pdst = '\\'; +#elif defined _UNIX || defined _OPENWAVE + *pdst = '/'; +#elif defined __MWERKS__ + *pdst = ':'; +#else + *pdst = *psrc; +#endif + } + else + { + *pdst = *psrc; + } + psrc++; + pdst++; + } + *pdst = '\0'; + return pnew; +} + +HLX_INLINE void +delete_string(char* str) +{ + if (str) + { + HLX_DEALLOC(str); + } +} +#endif /* HLX_ALLOC */ + +#endif /* HLXSYS_STRING_H */ diff --git a/amarok/src/engine/helix/helix-sp/helix-include/runtime/hlxclib/sys/stat.h b/amarok/src/engine/helix/helix-sp/helix-include/runtime/hlxclib/sys/stat.h new file mode 100644 index 00000000..3c2cc1f2 --- /dev/null +++ b/amarok/src/engine/helix/helix-sp/helix-include/runtime/hlxclib/sys/stat.h @@ -0,0 +1,67 @@ + +/* + * + * This software is released under the provisions of the GPL version 2. + * see file "COPYING". If that file is not available, the full statement + * of the license can be found at + * + * http://www.fsf.org/licensing/licenses/gpl.txt + * + * Portions Copyright (c) 1995-2004 RealNetworks, Inc. All Rights Reserved. + * + */ + +#ifndef HLXSYS_SYS_STAT_H +#define HLXSYS_SYS_STAT_H + +#if !defined(WIN32_PLATFORM_PSPC) && !defined(_OPENWAVE) && !defined(_WINCE) +#if defined(_MACINTOSH) && !defined(_MAC_MACHO) +#include <stat.h> +#else +#include <sys/stat.h> +#endif +#endif /* !defined(WIN32_PLATFORM_PSPC) */ + +#include "hlxclib/sys/types.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/* Helix implementations */ +int __helix_fstat(int filedes, struct stat *buf); +int __helix_stat(const char* pFilename, struct stat *buf); + +#if defined(WIN32_PLATFORM_PSPC) || defined(_OPENWAVE) + +#ifdef _OPENWAVE +#define S_IFDIR 0x1000 /* specify a directory*/ +#endif + +inline +int fstat(int filedes, struct stat *buf) +{ + return __helix_fstat(filedes, buf); +} + +inline +int _stat(const char* pFilename, struct _stat *buf) +{ + return __helix_stat(pFilename, (struct stat *)buf); +} + +inline +int stat(const char* pFilename, struct stat *buf) +{ + return __helix_stat(pFilename, buf); +} + +#endif /* defined(WIN32_PLATFORM_PSPC) */ + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + + +#endif /* HLXSYS_SYS_STAT_H */ diff --git a/amarok/src/engine/helix/helix-sp/helix-include/runtime/hlxclib/sys/types.h b/amarok/src/engine/helix/helix-sp/helix-include/runtime/hlxclib/sys/types.h new file mode 100644 index 00000000..4fbb554e --- /dev/null +++ b/amarok/src/engine/helix/helix-sp/helix-include/runtime/hlxclib/sys/types.h @@ -0,0 +1,66 @@ + +/* + * + * This software is released under the provisions of the GPL version 2. + * see file "COPYING". If that file is not available, the full statement + * of the license can be found at + * + * http://www.fsf.org/licensing/licenses/gpl.txt + * + * Portions Copyright (c) 1995-2004 RealNetworks, Inc. All Rights Reserved. + * + */ + +#ifndef HLXSYS_SYS_TYPES_H +#define HLXSYS_SYS_TYPES_H + +#if !defined(WIN32_PLATFORM_PSPC) && !defined(_MACINTOSH) && !defined(_OPENWAVE) +#include <sys/types.h> +#endif /* !defined(WIN32_PLATFORM_PSPC) */ + +#if defined (_WINDOWS) || (defined (_MACINTOSH) && !defined(_MAC_MACHO)) +typedef long off_t; +#endif /* defined (_WINDOWS) || defined (_MACINTOSH) */ + +#if defined(_OPENWAVE) +#include "hlxclib/time.h" +#include "platform/openwave/hx_op_fs.h" +typedef unsigned short mode_t; +typedef OpFsSize off_t; // XXXSAB??? + +#ifndef SEEK_SET +#define SEEK_SET kOpFsSeekSet +#define SEEK_CUR kOpFsSeekCur +#define SEEK_END kOpFsSeekEnd +#endif + +struct stat +{ + // XXXSAB fill this in... + mode_t st_mode; + off_t st_size; + time_t st_atime; + time_t st_ctime; + time_t st_mtime; + short st_nlink; +}; + +#elif defined(WIN32_PLATFORM_PSPC) +#include "hlxclib/windows.h" + +typedef unsigned short mode_t; + +#define S_IFDIR 0040000 + +struct stat { + mode_t st_mode; + off_t st_size; + time_t st_atime; + time_t st_ctime; + time_t st_mtime; + short st_nlink; +}; + +#endif /* defined(WIN32_PLATFORM_PSPC) */ + +#endif /* HLXSYS_SYS_TYPES_H */ diff --git a/amarok/src/engine/helix/helix-sp/helix-include/runtime/hlxclib/time.h b/amarok/src/engine/helix/helix-sp/helix-include/runtime/hlxclib/time.h new file mode 100644 index 00000000..f0009d0a --- /dev/null +++ b/amarok/src/engine/helix/helix-sp/helix-include/runtime/hlxclib/time.h @@ -0,0 +1,148 @@ + +/* + * + * This software is released under the provisions of the GPL version 2. + * see file "COPYING". If that file is not available, the full statement + * of the license can be found at + * + * http://www.fsf.org/licensing/licenses/gpl.txt + * + * Portions Copyright (c) 1995-2004 RealNetworks, Inc. All Rights Reserved. + * + */ + +#ifndef HLXSYS_TIME_H +#define HLXSYS_TIME_H + +#if defined(_SYMBIAN) +# include <sys/time.h> +#endif + +#if defined(WIN32_PLATFORM_PSPC) +# include "hxtypes.h" +# include "hlxclib/windows.h" +#elif !defined(WIN32_PLATFORM_PSPC) && !defined(_OPENWAVE) +# include <time.h> +#endif /* !defined(WIN32_PLATFORM_PSPC) && !defined(_OPENWAVE) */ + +#if defined(_OPENWAVE) +# include "platform/openwave/hx_op_timeutil.h" +#endif + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + +/******************************* + * Types + */ + +#if defined(_OPENWAVE) + +#define NO_TM_ISDST +typedef U32 time_t; +#define tm op_tm // XXXSAB any other way for 'struct tm' to + // work in a C-includeable file? +struct timeval { + time_t tv_sec; + time_t tv_usec; +}; + + +#elif defined(WIN32_PLATFORM_PSPC) + +struct tm { + int tm_sec; + int tm_min; + int tm_hour; + int tm_mday; + int tm_mon; + int tm_year; + int tm_wday; + int tm_yday; + int tm_isdst; +}; + +#define timezone _timezone +extern long _timezone; + +#endif /* defined(WIN32_PLATFORM_PSPC) */ + + +/******************************* + * Helix declarations + */ +long __helix_time(long *t); +struct tm* __helix_localtime(long* timep); +void __helix_tzset(); +long __helix_mktime(struct tm* tm); +struct tm *__helix_gmtime(long *timep); +int __helix_gettimeofday(struct timeval *tv, void *tz); +char * __helix_ctime(long *timer); + +#if defined(_WINCE) +char * __helix_asctime (struct tm *tm); + +/******************************* + * platform specifics declarations + */ + +_inline char * ctime(time_t *timp) +{ + return __helix_ctime((long*)timp); +} + +_inline char * asctime (struct tm *tm) +{ + return __helix_asctime(tm); +} + +_inline +void _tzset() +{ + __helix_tzset(); +} + +_inline +struct tm* localtime(time_t* timep) +{ + return __helix_localtime((long *)timep); +} + +_inline +long time(time_t *t) +{ + return __helix_time((long *)t); +} + + +_inline +long mktime(struct tm* tm) +{ + return __helix_mktime(tm); +} + +_inline +struct tm* gmtime(time_t *timep) +{ + return __helix_gmtime((long*)timep); +} + +#elif defined(_OPENWAVE) +#define time(t) __helix_time(t) +#define ctime(t) __helix_ctime(t) +#define gmtime(t) __helix_gmtime(t) +#define localtime(t) __helix_gmtime(t) // XXXSAB is there a _local_ time call? +#define mktime(tm) __helix_mktime(tm) +#define gettimeofday __helix_gettimeofday + +#define strftime op_strftime + +#endif /* defined(WIN32_PLATFORM_PSPC) */ + +#ifdef __cplusplus +}; +#endif /* __cplusplus */ + +#endif /* HLXSYS_TIME_H */ diff --git a/amarok/src/engine/helix/helix-sp/helix-include/runtime/safestring.h b/amarok/src/engine/helix/helix-sp/helix-include/runtime/safestring.h new file mode 100644 index 00000000..55df7220 --- /dev/null +++ b/amarok/src/engine/helix/helix-sp/helix-include/runtime/safestring.h @@ -0,0 +1,37 @@ + +/* + * + * This software is released under the provisions of the GPL version 2. + * see file "COPYING". If that file is not available, the full statement + * of the license can be found at + * + * http://www.fsf.org/licensing/licenses/gpl.txt + * + * Portions Copyright (c) 1995-2004 RealNetworks, Inc. All Rights Reserved. + * + */ + +#ifndef _SAFESTRING_H_ +#define _SAFESTRING_H_ + +#include "hxtypes.h" /* UINT32 */ +#include "hlxclib/string.h" /* for strxxx functions */ + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +char* SafeStrCpy(char* pDestStr, const char* pSourceStr, UINT32 ulBufferSize); +char* SafeStrCat(char* pDestStr, const char* pSourceStr, UINT32 ulBufferSize); + +#ifdef _OPENWAVE_ARMULATOR +#define SafeSprintf op_snprintf +#else +int SafeSprintf(char* pBuffer, UINT32 ulBufferSize, const char* pFormatStr, ...); +#endif + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* _SAFESTRING_H_ */ diff --git a/amarok/src/engine/helix/helix-sp/helix-sp.cpp b/amarok/src/engine/helix/helix-sp/helix-sp.cpp new file mode 100644 index 00000000..c7ac5943 --- /dev/null +++ b/amarok/src/engine/helix/helix-sp/helix-sp.cpp @@ -0,0 +1,2108 @@ +/* ********** + * + * This software is released under the provisions of the GPL version 2. + * see file "COPYING". If that file is not available, the full statement + * of the license can be found at + * + * http://www.fsf.org/licensing/licenses/gpl.txt + * + * Portions Copyright (c) 1995-2002 RealNetworks, Inc. All Rights Reserved. + * Portions Copyright (c) Paul Cifarelli 2005 + * + * ********** */ +#include <stdlib.h> +#include <stdarg.h> + +#include "hxcomm.h" +#include "hxcore.h" +#include "hxclsnk.h" +#include "hxerror.h" +#include "hxauth.h" +#include "hxprefs.h" +#include "hxstrutl.h" +#include "hxvsrc.h" +#include "hxresult.h" +#include "hxplugn.h" + +#include "hspadvisesink.h" +#include "hsperror.h" +#include "hspauthmgr.h" +#include "hspcontext.h" +#include <X11/Xlib.h> +#include <dlfcn.h> +#include <errno.h> +#include <fcntl.h> +#include <sys/ioctl.h> + +#include "hxausvc.h" + +#include "dllpath.h" + +#include <config.h> + +#include "helix-sp.h" +#include "hspvoladvise.h" +#include "utils.h" +#include "hsphook.h" +#include "hxfiles.h" + +#ifdef USE_HELIX_ALSA +#include <alsa/asoundlib.h> +#include "hspalsadevice.h" +#endif + +#ifdef HX_LOG_SUBSYSTEM +#include "hxtlogutil.h" +#endif + +#ifdef __FreeBSD__ +#define PTHREAD_MUTEX_FAST_NP PTHREAD_MUTEX_NORMAL +#endif + +#if !defined(__NetBSD__) && !defined(__OpenBSD__) + #include <sys/soundcard.h> +#else + #include <soundcard.h> +#endif + +typedef HX_RESULT (HXEXPORT_PTR FPRMSETDLLACCESSPATH) (const char*); + +class HelixSimplePlayerAudioStreamInfoResponse : public IHXAudioStreamInfoResponse +{ +public: + HelixSimplePlayerAudioStreamInfoResponse(HelixSimplePlayer *player, int playerIndex) : + m_Player(player), m_index(playerIndex), m_lRefCount(0) {} + virtual ~HelixSimplePlayerAudioStreamInfoResponse() {} + + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj); + + STDMETHOD_(ULONG32,AddRef) (THIS); + + STDMETHOD_(ULONG32,Release) (THIS); + + /* + * IHXAudioStreamInfoResponse methods + */ + STDMETHOD(OnStream) (THIS_ + IHXAudioStream *pAudioStream + ); +private: + HelixSimplePlayer *m_Player; + IHXAudioStream *m_Stream; + int m_index; + LONG32 m_lRefCount; + HXAudioFormat m_audiofmt; +}; + +STDMETHODIMP +HelixSimplePlayerAudioStreamInfoResponse::QueryInterface(REFIID riid, void**ppvObj) +{ + if(IsEqualIID(riid, IID_IUnknown)) + { + AddRef(); + *ppvObj = (IUnknown*)(IHXAudioStreamInfoResponse *)this; + return HXR_OK; + } + else if(IsEqualIID(riid, IID_IHXAudioStreamInfoResponse)) + { + AddRef(); + *ppvObj = (IHXAudioStreamInfoResponse*)this; + return HXR_OK; + } + *ppvObj = NULL; + return HXR_NOINTERFACE; +} + +STDMETHODIMP_(UINT32) +HelixSimplePlayerAudioStreamInfoResponse::AddRef() +{ + return InterlockedIncrement(&m_lRefCount); +} + +STDMETHODIMP_(UINT32) +HelixSimplePlayerAudioStreamInfoResponse::Release() +{ + if (InterlockedDecrement(&m_lRefCount) > 0) + { + return m_lRefCount; + } + + delete this; + return 0; +} + +STDMETHODIMP HelixSimplePlayerAudioStreamInfoResponse::OnStream(IHXAudioStream *pAudioStream) +{ + m_Player->print2stderr("Stream Added on player %d, stream duration %ld, sources %d\n", m_index, + m_Player->duration(m_index), m_Player->ppctrl[m_index]->pPlayer->GetSourceCount()); + + m_Player->ppctrl[m_index]->pStream = pAudioStream; + m_Player->ppctrl[m_index]->pPreMixHook = new HSPPreMixAudioHook(m_Player, m_index, pAudioStream, + m_Player->ppctrl[m_index]->bFadeIn, + m_Player->ppctrl[m_index]->ulFadeLength); + + // addpremixhook adds another ref + pAudioStream->AddPreMixHook(m_Player->ppctrl[m_index]->pPreMixHook, false); + m_Player->ppctrl[m_index]->pPreMixHook->Release(); // release the ref added in the premixhook constructor + + m_Player->ppctrl[m_index]->bStarting = false; + + return HXR_OK; +} + +// Constants +const int DEFAULT_TIME_DELTA = 2000; +const int DEFAULT_STOP_TIME = -1; +const int SLEEP_TIME = 10; +const int GUID_LEN = 64; + +// *** IUnknown methods *** + +///////////////////////////////////////////////////////////////////////// +// Method: +// IUnknown::QueryInterface +// Purpose: +// Implement this to export the interfaces supported by your +// object. +// +STDMETHODIMP HelixSimplePlayerVolumeAdvice::QueryInterface(REFIID riid, void** ppvObj) +{ + if (IsEqualIID(riid, IID_IUnknown)) + { + AddRef(); + *ppvObj = (IUnknown*)(IHXClientAdviseSink*)this; + return HXR_OK; + } + else if (IsEqualIID(riid, IID_IHXVolumeAdviseSink)) + { + AddRef(); + *ppvObj = (IHXVolumeAdviseSink*)this; + return HXR_OK; + } + + *ppvObj = NULL; + return HXR_NOINTERFACE; +} + +///////////////////////////////////////////////////////////////////////// +// Method: +// IUnknown::AddRef +// Purpose: +// Everyone usually implements this the same... feel free to use +// this implementation. +// +STDMETHODIMP_(ULONG32) HelixSimplePlayerVolumeAdvice::AddRef() +{ + return InterlockedIncrement(&m_lRefCount); +} + +///////////////////////////////////////////////////////////////////////// +// Method: +// IUnknown::Release +// Purpose: +// Everyone usually implements this the same... feel free to use +// this implementation. +// +STDMETHODIMP_(ULONG32) HelixSimplePlayerVolumeAdvice::Release() +{ + if (InterlockedDecrement(&m_lRefCount) > 0) + { + return m_lRefCount; + } + + delete this; + return 0; +} + +STDMETHODIMP HelixSimplePlayerVolumeAdvice::OnVolumeChange(const UINT16 /*uVolume*/) +{ + m_Player->onVolumeChange(m_index); +#ifdef HELIX_SW_VOLUME_INTERFACE + m_Player->ppctrl[m_index]->volume = uVolume; +#endif + return HXR_OK; +} + +STDMETHODIMP HelixSimplePlayerVolumeAdvice::OnMuteChange(const BOOL bMute) +{ + m_Player->onMuteChange(m_index); + m_Player->ppctrl[m_index]->ismute = bMute; + return HXR_OK; +} + + +int HelixSimplePlayer::print2stdout(const char *fmt, ...) +{ + va_list args; + char buf[1024]; + + va_start(args, fmt); + + int ret = vsprintf(buf, fmt, args); + std::cout << buf; + + va_end(args); + + return ret; +} + +int HelixSimplePlayer::print2stderr(const char *fmt, ...) +{ + va_list args; + char buf[1024]; + + va_start(args, fmt); + + int ret = vsprintf(buf, fmt, args); + std::cerr << buf; + + va_end(args); + + return ret; +} + + +void HelixSimplePlayer::setFadeout(bool fadeout, unsigned long fadelength, int playerIndex ) +{ + if (playerIndex == ALL_PLAYERS) + { + for (int i = 0; i<nNumPlayers; i++) + setFadeout(fadeout, fadelength, i); + } + else + { + if (playerIndex >=0 && playerIndex < nNumPlayers && ppctrl[playerIndex]->pPreMixHook) + { + ppctrl[playerIndex]->ulFadeLength = fadelength; + ((HSPPreMixAudioHook *)ppctrl[playerIndex]->pPreMixHook)->setFadelength(ppctrl[playerIndex]->ulFadeLength); + ((HSPPreMixAudioHook *)ppctrl[playerIndex]->pPreMixHook)->setFadeout(fadeout); + } + } +} + + +void HelixSimplePlayer::cleanUpStream(int playerIndex) +{ + //print2stderr("CLEANUPSTREAM\n"); + stop(playerIndex); +} + + +void HelixSimplePlayer::updateEQgains() +{ + for (int i = 0; i<nNumPlayers; i++) + if (pFinalAudioHook && isEQenabled()) + ((HSPFinalAudioHook *)pFinalAudioHook)->updateEQgains(m_preamp, m_equalizerGains); +} + +/* + * handle one event + */ +void HelixSimplePlayer::DoEvent() +{ + struct _HXxEvent *pNothing = 0x0; + struct timeval mtime; + + mtime.tv_sec = 0; + mtime.tv_usec = SLEEP_TIME * 1000; + usleep(SLEEP_TIME*1000); + pEngine->EventOccurred(pNothing); +} + +/* + * handle events for at most nTimeDelta milliseconds + */ +void HelixSimplePlayer::DoEvents(int) +{ + DoEvent(); +} + +/* + * return the number of milliseconds since the epoch + */ +UINT32 HelixSimplePlayer::GetTime() +{ + timeval t; + gettimeofday(&t, NULL); + + // FIXME: + // the fact that the result is bigger than a UINT32 is really irrelevant; + // we can still play a stream for many many years... + return (UINT32)((t.tv_sec * 1000) + (t.tv_usec / 1000)); +} + +char* HelixSimplePlayer::RemoveWrappingQuotes(char* str) +{ + int len = strlen(str); + if (len > 0) + { + if (str[len-1] == '"') str[--len] = 0; + if (str[0] == '"') {int i = 0; do { str[i] = str[i+1]; ++i; } while(--len); } + } + return str; +} + + +HelixSimplePlayer::HelixSimplePlayer() : + theErr(HXR_FAILED), + pErrorSink(NULL), + pErrorSinkControl(NULL), + pPluginE(0), + pPlugin2Handler(0), + ppctrl(NULL), + bURLFound(false), + nNumPlayers(0), + nNumPlayRepeats(1), + nTimeDelta(DEFAULT_TIME_DELTA), + nStopTime(DEFAULT_STOP_TIME), + bStopTime(true), + bStopping(false), + nPlay(0), + bEnableAdviceSink(false), + bEnableVerboseMode(false), + pEngine(NULL), + pEngineContext(NULL), + m_pszUsername(NULL), + m_pszPassword(NULL), + m_pszGUIDFile(NULL), + m_pszGUIDList(NULL), + m_Error(0), + m_ulNumSecondsPlayed(0), + mimehead(0), + mimelistlen(0), + m_preamp(0), + m_outputsink(OSS), + m_device(0), +#ifdef USE_HELIX_ALSA + m_direct(ALSA), // TODO: out why my alsa direct HW reader doesn't pickup changes in kmix (the whole purpose of this!) +#else + m_direct(OSS), +#endif + m_AlsaCapableCore(false), + m_nDevID(-1), + m_pAlsaMixerHandle(NULL), + m_pAlsaMasterMixerElem(NULL), + m_pAlsaPCMMixerElem(NULL), + m_alsaDevice("default"), + m_urlchanged(0), + m_volBefore(-1), + m_volAtStart(-1), + m_MvolBefore(-1), + m_MvolAtStart(-1) +{ + + pthread_mutexattr_t ma; + + pthread_mutexattr_init(&ma); + pthread_mutexattr_settype(&ma, PTHREAD_MUTEX_FAST_NP); // note this is not portable outside linux and a few others + pthread_mutex_init(&m_engine_m, &ma); +} + +void HelixSimplePlayer::init(const char *corelibhome, const char *pluginslibhome, const char *codecshome, int numPlayers) +{ + int i; + + theErr = HXR_OK; + + FPRMCREATEENGINE fpCreateEngine; + FPRMSETDLLACCESSPATH fpSetDLLAccessPath; + + SafeSprintf(mCoreLibPath, MAX_PATH, "%s/%s", corelibhome, "clntcore.so"); + + // Allocate arrays to keep track of players and client + // context pointers + ppctrl = new struct playerCtrl *[MAX_PLAYERS]; + memset(ppctrl, 0, sizeof(struct playerCtrl *) * MAX_PLAYERS); + + if (!ppctrl) + { + print2stderr("Error: Out of Memory.\n"); + theErr = HXR_UNEXPECTED; + return; + } + + fpCreateEngine = NULL; + + // prepare/load the HXCore module + //print2stdout("Simpleplayer is looking for the client core at %s\n", mCoreLibPath ); + + core_handle = dlopen(mCoreLibPath, RTLD_LAZY | RTLD_GLOBAL); + if (!core_handle) + { + print2stderr("splayer: failed to open corelib, errno %d\n", errno); + theErr = HXR_FAILED; + return; + } + fpCreateEngine = (FPRMCREATEENGINE) dlsym(core_handle, "CreateEngine"); + fpSetDLLAccessPath = (FPRMSETDLLACCESSPATH) dlsym(core_handle, "SetDLLAccessPath"); + + if (fpCreateEngine == NULL || + fpSetDLLAccessPath == NULL ) + { + theErr = HXR_FAILED; + return; + } + + //Now tell the client core where to find the plugins and codecs it + //will be searching for. + if (NULL != fpSetDLLAccessPath) + { + //Create a null delimited, double-null terminated string + //containing the paths to the encnet library (DT_Common) and + //the sdpplin library (DT_Plugins)... + char pPaths[256]; /* Flawfinder: ignore */ + char* pPathNextPosition = pPaths; + memset(pPaths, 0, 256); + UINT32 ulBytesLeft = 256; + + char* pNextPath = new char[256]; + memset(pNextPath, 0, 256); + + SafeSprintf(pNextPath, 256, "DT_Common=%s", corelibhome); + //print2stderr("Common DLL path %s\n", pNextPath ); + UINT32 ulBytesToCopy = strlen(pNextPath) + 1; + if (ulBytesToCopy <= ulBytesLeft) + { + memcpy(pPathNextPosition, pNextPath, ulBytesToCopy); + pPathNextPosition += ulBytesToCopy; + ulBytesLeft -= ulBytesToCopy; + } + + SafeSprintf(pNextPath, 256, "DT_Plugins=%s", pluginslibhome); + //print2stderr("Plugin path %s\n", pNextPath ); + ulBytesToCopy = strlen(pNextPath) + 1; + if (ulBytesToCopy <= ulBytesLeft) + { + memcpy(pPathNextPosition, pNextPath, ulBytesToCopy); + pPathNextPosition += ulBytesToCopy; + ulBytesLeft -= ulBytesToCopy; + } + + SafeSprintf(pNextPath, 256, "DT_Codecs=%s", codecshome); + //print2stderr("Codec path %s\n", pNextPath ); + ulBytesToCopy = strlen(pNextPath) + 1; + if (ulBytesToCopy <= ulBytesLeft) + { + memcpy(pPathNextPosition, pNextPath, ulBytesToCopy); + pPathNextPosition += ulBytesToCopy; + ulBytesLeft -= ulBytesToCopy; + *pPathNextPosition='\0'; + } + + fpSetDLLAccessPath((char*)pPaths); + + HX_VECTOR_DELETE(pNextPath); + } + + // create client engine + if (HXR_OK != fpCreateEngine((IHXClientEngine**)&pEngine)) + { + theErr = HXR_FAILED; + return; + } + + pCommonClassFactory = 0; + // get the common class factory + pEngine->QueryInterface(IID_IHXCommonClassFactory, (void **) &pCommonClassFactory); + if (!pCommonClassFactory) + print2stderr("no CommonClassFactory\n"); + + // get the engine setup interface + IHXClientEngineSetup *pEngineSetup = 0; + pEngine->QueryInterface(IID_IHXClientEngineSetup, (void **) &pEngineSetup); + if (!pEngineSetup) + print2stderr("no engine setup interface\n"); + else + { + pEngineContext = new HSPEngineContext(this, pCommonClassFactory); + pEngineContext->AddRef(); +#ifdef HX_LOG_SUBSYSTEM + HX_ENABLE_LOGGING(pEngineContext); +#endif + pEngineSetup->Setup(pEngineContext); + pEngineSetup->Release(); + } + + // get the client engine selector + pCEselect = 0; + pEngine->QueryInterface(IID_IHXClientEngineSelector, (void **) &pCEselect); + if (!pCEselect) + print2stderr("no CE selector\n"); + + pPluginE = 0; + // get the plugin enumerator + pEngine->QueryInterface(IID_IHXPluginEnumerator, (void **) &pPluginE); + if (!pPluginE) + print2stderr("no plugin enumerator\n"); + + pPlugin2Handler = 0; + // get the plugin2handler + pEngine->QueryInterface(IID_IHXPlugin2Handler, (void **) &pPlugin2Handler); + if (!pPlugin2Handler) + print2stderr("no plugin enumerator\n"); + + pAudioDeviceManager = 0; + // get the audio device mananger + pEngine->QueryInterface(IID_IHXAudioDeviceManager, (void **) &pAudioDeviceManager); + if (!pAudioDeviceManager) + print2stderr("no audio device manager\n"); + + // create players + for (i = 0; i < numPlayers; i++) + { + addPlayer(); + } + +#ifdef USE_HELIX_ALSA + pAudioDevice = 0; + if ( m_outputsink == ALSA && pAudioDeviceManager && !m_AlsaCapableCore) + { + pAudioDevice = new HSPAudioDevice(this, m_device); + + // change the AudioDevice if we are using alsa (player 0's audion device is special...) + pAudioDeviceManager->Replace(pAudioDevice); + } +#endif + + pAudioDeviceResponse = 0; + pEngine->QueryInterface(IID_IHXAudioDeviceResponse, (void**) &pAudioDeviceResponse); + + pAudioHookManager = 0; + pFinalAudioHook = 0; + pEngine->QueryInterface(IID_IHXAudioHookManager, (void **) &pAudioHookManager); + if (!pAudioHookManager) + print2stderr("no audio device hook manager\n"); + + + // install hook for visualizations, equalizer, and volume control - for use with streams + // the time in the packets is the presentation time - which maps better to streams + HSPFinalAudioHook *pPMAH = new HSPFinalAudioHook(this); + pAudioHookManager->AddHook(pPMAH); + pFinalAudioHook = pPMAH; + + if (pPlugin2Handler) + { + IHXValues* pPluginProps; + const char* szPropName; + IHXBuffer* pPropValue; + char *value; + HX_RESULT ret; + MimeList *ml; + mimehead = 0; + char mime[1024]; + char ext[1024]; + bool hasmime, hasexts; + + pPluginProps = 0; + int n = pPlugin2Handler->GetNumOfPlugins2(); + m_numPlugins = n; + print2stderr("Got the plugin2 handler: numplugins = %d\n", n); + m_pluginInfo = new pluginInfo* [n]; + for (i=0; i<n; i++) + { + m_pluginInfo[i] = new pluginInfo; + m_pluginInfo[i]->description = ""; + m_pluginInfo[i]->copyright = ""; + m_pluginInfo[i]->moreinfourl = ""; + hasmime = hasexts = false; + pPlugin2Handler->GetPluginInfo(i, pPluginProps); + if (pPluginProps) + { + ret = pPluginProps->GetFirstPropertyCString(szPropName, pPropValue); + while(SUCCEEDED(ret)) + { + value = (char*)pPropValue->GetBuffer(); + if (!strcmp(szPropName, "FileMime")) + { + strcpy(mime, value); + hasmime = true; + } + + if (!strcmp(szPropName, "FileExtensions")) + { + strcpy(ext, value); + hasexts = true; + } + + if (!strcmp(szPropName, "Description")) + { + m_pluginInfo[i]->description = new char[ strlen(value) + 1 ]; + strcpy(m_pluginInfo[i]->description, value); + } + + if (!strcmp(szPropName, "Copyright")) + { + m_pluginInfo[i]->copyright = new char[ strlen(value) + 1 ]; + strcpy(m_pluginInfo[i]->copyright, value); + } + + + if (!strcmp(szPropName, "PlgCopy")) + { + m_pluginInfo[i]->moreinfourl = new char[ strlen(value) + 1 ]; + strcpy(m_pluginInfo[i]->moreinfourl, value); + } + + ret = pPluginProps->GetNextPropertyCString(szPropName, pPropValue); + } + HX_RELEASE(pPluginProps); + if (hasmime && hasexts) + { + mimelistlen++; + ml = new MimeList(mime, ext); + ml->fwd = mimehead; + mimehead = ml; + } + } + } + } +} + +int HelixSimplePlayer::initDirectSS() +{ + if (m_outputsink == ALSA) + { + closeAudioDevice(); + m_direct = ALSA; + openAudioDevice(); + } + else + { + closeAudioDevice(); + m_direct = OSS; + openAudioDevice(); + } + +#ifdef USE_HELIX_ALSA + m_MvolBefore = m_MvolAtStart = getDirectMasterVolume(); + print2stderr("***Master VolAtStart is %d\n", m_MvolAtStart); + setDirectMasterVolume(m_MvolAtStart); +#endif + + m_volBefore = m_volAtStart = getDirectPCMVolume(); + print2stderr("***VolAtStart is %d\n", m_volAtStart); + setDirectPCMVolume(m_volAtStart); + return 0; +} + +int HelixSimplePlayer::addPlayer() +{ + if ((nNumPlayers+1) == MAX_PLAYERS) + { + print2stderr("MAX_PLAYERS: %d nNumPlayers: %d\n", MAX_PLAYERS, nNumPlayers); + return -1; + } + + ppctrl[nNumPlayers] = new struct playerCtrl; + memset(ppctrl[nNumPlayers], 0, sizeof(struct playerCtrl)); + + pthread_mutexattr_t ma; + + pthread_mutexattr_init(&ma); + pthread_mutexattr_settype(&ma, PTHREAD_MUTEX_FAST_NP); // note this is not portable outside linux and a few others + pthread_mutex_init(&ppctrl[nNumPlayers]->m_scope_m, &ma); + + ppctrl[nNumPlayers]->bPlaying = false; + ppctrl[nNumPlayers]->bStarting = false; + ppctrl[nNumPlayers]->bFadeIn = false; + ppctrl[nNumPlayers]->bFadeOut = false; + ppctrl[nNumPlayers]->ulFadeLength = 0; + ppctrl[nNumPlayers]->pStream = 0; + ppctrl[nNumPlayers]->pszURL = 0; + + memset(&ppctrl[nNumPlayers]->md, 0, sizeof(ppctrl[nNumPlayers]->md)); + + ppctrl[nNumPlayers]->pHSPContext = new HSPClientContext(nNumPlayers, this); + if (!ppctrl[nNumPlayers]->pHSPContext) + { + print2stdout("Error: Out of Memory. num players is %d\n", nNumPlayers); + theErr = HXR_UNEXPECTED; + return -1; + } + ppctrl[nNumPlayers]->pHSPContext->AddRef(); + + //initialize the example context + + char pszGUID[GUID_LEN + 1]; /* Flawfinder: ignore */ // add 1 for terminator + //char* token = NULL; + IHXPreferences* pPreferences = NULL; + + if (HXR_OK != pEngine->CreatePlayer(ppctrl[nNumPlayers]->pPlayer)) + { + theErr = HXR_FAILED; + return -1; + } + + pszGUID[0] = '\0'; + +// disable for now - I don't know what I was thinking +#ifdef _DISABLE_CUZ_I_MUST_HAVE_BEEN_NUTS_ + if (m_pszGUIDList) + { + // Get next GUID from the GUID list + if (nNumPlayers == 0) + { + token = strtok(m_pszGUIDList, "\n\0"); + } + else + { + token = strtok(NULL, "\n\0"); + } + if (token) + { + strncpy(pszGUID, token, GUID_LEN); /* Flawfinder: ignore */ + pszGUID[GUID_LEN] = '\0'; + } + } +#endif + + ppctrl[nNumPlayers]->pPlayer->QueryInterface(IID_IHXPreferences, (void**) &pPreferences); + ppctrl[nNumPlayers]->pHSPContext->Init(ppctrl[nNumPlayers]->pPlayer, pPreferences, pszGUID); + ppctrl[nNumPlayers]->pPlayer->SetClientContext(ppctrl[nNumPlayers]->pHSPContext); + HX_RELEASE(pPreferences); + + ppctrl[nNumPlayers]->pPlayer->QueryInterface(IID_IHXErrorSinkControl, (void**) &pErrorSinkControl); + if (pErrorSinkControl) + { + ppctrl[nNumPlayers]->pHSPContext->QueryInterface(IID_IHXErrorSink, (void**) &pErrorSink); + if (pErrorSink) + pErrorSinkControl->AddErrorSink(pErrorSink, HXLOG_EMERG, HXLOG_INFO); + HX_RELEASE(pErrorSink); + } + + HX_RELEASE(pErrorSinkControl); + + // Get the Player2 interface + ppctrl[nNumPlayers]->pPlayer->QueryInterface(IID_IHXPlayer2, (void**) &ppctrl[nNumPlayers]->pPlayer2); + if (!ppctrl[nNumPlayers]->pPlayer2) + print2stderr("no player2 device\n"); + + // Get the Audio Player + ppctrl[nNumPlayers]->pPlayer->QueryInterface(IID_IHXAudioPlayer, (void**) &ppctrl[nNumPlayers]->pAudioPlayer); + if (ppctrl[nNumPlayers]->pAudioPlayer) + { + // ...and now the volume interface + //ppctrl[nNumPlayers]->pVolume = ppctrl[nNumPlayers]->pAudioPlayer->GetAudioVolume(); + //ppctrl[nNumPlayers]->pVolume = ppctrl[nNumPlayers]->pAudioPlayer->GetDeviceVolume(); + if (ppctrl[nNumPlayers]->pVolume) + { +#ifndef HELIX_SW_VOLUME_INTERFACE + HelixSimplePlayerVolumeAdvice *pVA = new HelixSimplePlayerVolumeAdvice(this, nNumPlayers); + pVA->AddRef(); + ppctrl[nNumPlayers]->pVolume->AddAdviseSink((IHXVolumeAdviseSink *)pVA); + ppctrl[nNumPlayers]->pVolumeAdvise = pVA; + ppctrl[nNumPlayers]->volume = 50; // should get volume advise, which will set this properly +#else + // set the helix sw interface volume to 100, we'll control the volume either ourselves post equalization or by + // amaorok using direct hardware volume + //ppctrl[nNumPlayers]->pVolume->SetVolume(100); + ppctrl[nNumPlayers]->pVolume->SetVolume(100); + ppctrl[nNumPlayers]->pVolumeAdvise = 0; +#endif + } + + // add the IHXAudioStreamInfoResponse it the AudioPlayer + HelixSimplePlayerAudioStreamInfoResponse *pASIR = new HelixSimplePlayerAudioStreamInfoResponse(this, nNumPlayers); + ppctrl[nNumPlayers]->pAudioPlayer->SetStreamInfoResponse(pASIR); + ppctrl[nNumPlayers]->pStreamInfoResponse = pASIR; + + // ...and get the CrossFader + ppctrl[nNumPlayers]->pAudioPlayer->QueryInterface(IID_IHXAudioCrossFade, (void **) &(ppctrl[nNumPlayers]->pCrossFader)); + if (!ppctrl[nNumPlayers]->pCrossFader) + print2stderr("CrossFader not available\n"); + + // install hook for visualizations, equalizer, and volume control - for use with local files + // a FinalAudioHook is used for streams + HSPPostMixAudioHook *pPMAH = new HSPPostMixAudioHook(this, nNumPlayers); + ppctrl[nNumPlayers]->pAudioPlayer->AddPostMixHook(pPMAH, false, true); + ppctrl[nNumPlayers]->pPostMixHook = pPMAH; + } + else + print2stderr("No AudioPlayer Found - how can we play music!!\n"); + + ++nNumPlayers; + + //print2stderr("Added player, total is %d\n",nNumPlayers); + return 0; +} + +HelixSimplePlayer::~HelixSimplePlayer() +{ + tearDown(); + + // only now invalidate the device, not whenever we teardown + delete [] m_device; +} + +void HelixSimplePlayer::tearDown() +{ + int i; + FPRMCLOSEENGINE fpCloseEngine; + + if (theErr != HXR_OK) // failed to initialize properly + return; + + // make sure all players are stopped, + stop(); + + print2stderr("TEARDOWN\n"); + + for (i=nNumPlayers-1; i>=0; i--) + { + if (ppctrl[i]->pCrossFader) + ppctrl[i]->pCrossFader->Release(); + + if (ppctrl[i]->pAudioPlayer) + { + ppctrl[i]->pAudioPlayer->RemovePostMixHook( ppctrl[i]->pPostMixHook ); + ppctrl[i]->pPostMixHook->Release(); + + ppctrl[i]->pAudioPlayer->RemoveStreamInfoResponse((IHXAudioStreamInfoResponse *) ppctrl[i]->pStreamInfoResponse); + + if (ppctrl[i]->pVolume) + { + if (ppctrl[i]->pVolumeAdvise) + { + ppctrl[i]->pVolume->RemoveAdviseSink(ppctrl[i]->pVolumeAdvise); + ppctrl[i]->pVolumeAdvise->Release(); + } + ppctrl[i]->pVolume->Release(); + } + + ppctrl[i]->pAudioPlayer->Release(); + } + + if ( ppctrl[i]->pszURL ) + delete [] ppctrl[i]->pszURL; + + if (ppctrl[i]->pHSPContext) + ppctrl[i]->pHSPContext->Release(); + + if (ppctrl[i]->pPlayer2) + ppctrl[i]->pPlayer2->Release(); + + if (ppctrl[i]->pPlayer && pEngine) + { + pEngine->ClosePlayer(ppctrl[i]->pPlayer); + ppctrl[i]->pPlayer->Release(); + } + + delete ppctrl[i]; + } + + if (pAudioDevice) + pAudioDevice->Release(); + + if (pAudioDeviceResponse) + pAudioDeviceResponse->Release(); + + delete [] ppctrl; + + if (pCommonClassFactory) + pCommonClassFactory->Release(); + if (pCEselect) + pCEselect->Release(); + if (pPluginE) + pPluginE->Release(); + if (pPlugin2Handler) + pPlugin2Handler->Release(); + if (pAudioDeviceManager) + pAudioDeviceManager->Release(); + if (pAudioHookManager) + { + pAudioHookManager->RemoveHook(pFinalAudioHook); + pFinalAudioHook->Release(); + pAudioHookManager->Release(); + } + if (pEngineContext) + pEngineContext->Release(); + + fpCloseEngine = (FPRMCLOSEENGINE) dlsym(core_handle, "CloseEngine"); + if (fpCloseEngine && pEngine) + { + fpCloseEngine(pEngine); + pEngine = NULL; + } + + dlclose(core_handle); + + if (m_pszUsername) + { + delete [] m_pszUsername; + } + if (m_pszPassword) + { + delete [] m_pszPassword; + } + if (m_pszGUIDFile) + { + delete [] m_pszGUIDFile; + } + if (m_pszGUIDList) + { + delete [] m_pszGUIDList; + } + + for (i=0; i<m_numPlugins; i++) + delete m_pluginInfo[i]; + delete [] m_pluginInfo; + + if (bEnableVerboseMode) + { + print2stdout("\nDone.\n"); + } + + MimeList *ml = mimehead, *mh; + while (ml) + { + mh = ml->fwd; + delete ml; + ml = mh; + } + + closeAudioDevice(); + + theErr = HXR_FAILED; + pErrorSink = NULL; + pErrorSinkControl = NULL; + pPluginE = 0; + pPlugin2Handler = 0; + ppctrl = NULL; + bURLFound = false; + nNumPlayers = 0; + nNumPlayRepeats = 1; + nTimeDelta = DEFAULT_TIME_DELTA; + nStopTime = DEFAULT_STOP_TIME; + bStopTime = true; + bStopping = false; + nPlay = 0; + bEnableAdviceSink = false; + bEnableVerboseMode = false; + pEngine = NULL; + m_pszUsername = NULL; + m_pszPassword = NULL; + m_pszGUIDFile = NULL; + m_pszGUIDList = NULL; + m_Error = 0; + m_ulNumSecondsPlayed = 0; + mimehead = 0; + m_preamp = 0; +} + + +void HelixSimplePlayer::setOutputSink( HelixSimplePlayer::AUDIOAPI out ) +{ +#ifdef USE_HELIX_ALSA + m_outputsink = out; +#else + m_outputsink = OSS; +#endif +} + +HelixSimplePlayer::AUDIOAPI HelixSimplePlayer::getOutputSink() +{ + return m_outputsink; +} + +void HelixSimplePlayer::setDevice( const char *dev ) +{ + delete [] m_device; + + int len = strlen(dev); + m_device = new char [len + 1]; + strcpy(m_device, dev); +} + +const char *HelixSimplePlayer::getDevice() +{ + return m_device; +} + + +#define MAX_DEV_NAME 255 +#define HX_VOLUME SOUND_MIXER_PCM +void HelixSimplePlayer::openAudioDevice() +{ + switch (m_direct) + { + case OSS: + { + //Check the environmental variable to let user overide default device. + char *pszOverrideName = getenv( "AUDIO" ); /* Flawfinder: ignore */ + char szDevName[MAX_DEV_NAME]; /* Flawfinder: ignore */ + + // Use defaults if no environment variable is set. + if ( pszOverrideName && strlen(pszOverrideName)>0 ) + { + SafeStrCpy( szDevName, pszOverrideName, MAX_DEV_NAME ); + } + else + { + SafeStrCpy( szDevName, "/dev/mixer", MAX_DEV_NAME ); + } + + // Open the audio device if it isn't already open + if ( m_nDevID < 0 ) + { + m_nDevID = ::open( szDevName, O_WRONLY ); + } + + if ( m_nDevID < 0 ) + { + print2stderr("Failed to open audio(%s)!!!!!!! Code is: %d errno: %d\n", + szDevName, m_nDevID, errno ); + + //Error opening device. + } + } + break; + + case ALSA: + { +#ifdef USE_HELIX_ALSA + int err; + + print2stderr("Opening ALSA mixer device PCM\n"); + + err = snd_mixer_open(&m_pAlsaMixerHandle, 0); + if (err < 0) + print2stderr("snd_mixer_open: %s\n", snd_strerror (err)); + + if (err == 0) + { + err = snd_mixer_attach(m_pAlsaMixerHandle, m_alsaDevice); + if (err < 0) + print2stderr("snd_mixer_attach: %s\n", snd_strerror (err)); + } + + if (err == 0) + { + err = snd_mixer_selem_register(m_pAlsaMixerHandle, NULL, NULL); + if (err < 0) + print2stderr("snd_mixer_selem_register: %s\n", snd_strerror (err)); + } + + if (err == 0) + { + err = snd_mixer_load(m_pAlsaMixerHandle); + if(err < 0 ) + print2stderr("snd_mixer_load: %s\n", snd_strerror (err)); + } + + if (err == 0) + { + /* Find the mixer element */ + snd_mixer_elem_t* elem = snd_mixer_first_elem(m_pAlsaMixerHandle); + snd_mixer_elem_type_t type; + const char* elem_name = NULL; + snd_mixer_selem_id_t *sid = NULL; + + snd_mixer_selem_id_alloca(&sid); + + while (elem) + { + type = snd_mixer_elem_get_type(elem); + if (type == SND_MIXER_ELEM_SIMPLE) + { + snd_mixer_selem_get_id(elem, sid); + + /* We're only interested in playback volume controls */ + if(snd_mixer_selem_has_playback_volume(elem) && !snd_mixer_selem_has_common_volume(elem) ) + { + elem_name = snd_mixer_selem_id_get_name(sid); + if (!m_pAlsaPCMMixerElem && strcmp(elem_name, "Master") == 0) + m_pAlsaMasterMixerElem = elem; + + if (!m_pAlsaPCMMixerElem && strcmp(elem_name, "PCM") == 0) + m_pAlsaPCMMixerElem = elem; + + if (m_pAlsaMasterMixerElem && m_pAlsaPCMMixerElem) + break; + } + } + elem = snd_mixer_elem_next(elem); + } + + if (!elem) + { + print2stderr("Could not find a usable mixer element\n"); + err = -1; + } + } + + + if (err != 0) + { + if(m_pAlsaMixerHandle) + { + snd_mixer_close(m_pAlsaMixerHandle); + m_pAlsaMixerHandle = NULL; + } + } +#endif + } + break; + + default: + print2stderr("Unknown audio interface in openAudioDevice()\n"); + } +} + +void HelixSimplePlayer::closeAudioDevice() +{ + switch (m_direct) + { + case OSS: + { + if( m_nDevID >= 0 ) + { + ::close( m_nDevID ); + m_nDevID = -1; + } + } + break; + + case ALSA: + { +#ifdef USE_HELIX_ALSA + int err = 0; + + if (m_pAlsaMixerHandle && m_pAlsaMasterMixerElem) + { + err = snd_mixer_detach(m_pAlsaMixerHandle, "Master"); + if (err < 0) + print2stderr("snd_mixer_detach: %s\n", snd_strerror(err)); + } + + if (m_pAlsaMixerHandle && m_pAlsaPCMMixerElem) + { + err = snd_mixer_detach(m_pAlsaMixerHandle, "PCM"); + if (err < 0) + print2stderr("snd_mixer_detach: %s\n", snd_strerror(err)); + } + + + if (m_pAlsaMixerHandle) + { + if(err == 0) + { + err = snd_mixer_close(m_pAlsaMixerHandle); + if(err < 0) + print2stderr("snd_mixer_close: %s\n", snd_strerror (err)); + } + + if(err == 0) + { + m_pAlsaMixerHandle = NULL; + m_pAlsaPCMMixerElem = NULL; + } + } +#endif + } + break; + + default: + print2stderr("Unknown audio interface in closeAudioDevice()\n"); + } +} + +// it seems the master volume only gets reset on track change when using ALSA +// sheez, I thought Amarok wasnt supposed to be a mixer?? +// all this code is so that you can actually *use* a mixer and have it work +// the way you expect... +#ifdef USE_HELIX_ALSA +int HelixSimplePlayer::getDirectMasterVolume() +{ + int nRetVolume = 0; + + switch (m_direct) + { + case ALSA: + { + if (!m_pAlsaMasterMixerElem) + return nRetVolume; + + snd_mixer_elem_type_t type; + int err = 0; + type = snd_mixer_elem_get_type(m_pAlsaMasterMixerElem); + + if (type == SND_MIXER_ELEM_SIMPLE) + { + long volumeL, volumeR, min_volume, max_volume; + + if(snd_mixer_selem_has_playback_volume(m_pAlsaMasterMixerElem) || + snd_mixer_selem_has_playback_volume_joined(m_pAlsaMasterMixerElem)) + { + err = snd_mixer_selem_get_playback_volume(m_pAlsaMasterMixerElem, + SND_MIXER_SCHN_FRONT_LEFT, + &volumeL); + if (err < 0) + print2stderr("snd_mixer_selem_get_playback_volume (L): %s\n", snd_strerror (err)); + else + { + if ( snd_mixer_selem_is_playback_mono ( m_pAlsaMasterMixerElem )) + volumeR = volumeL; + else + { + err = snd_mixer_selem_get_playback_volume(m_pAlsaMasterMixerElem, + SND_MIXER_SCHN_FRONT_RIGHT, + &volumeR); + if (err < 0) + print2stderr("snd_mixer_selem_get_playback_volume (R): %s\n", snd_strerror (err)); + } + } + + if (err == 0) + { + snd_mixer_selem_get_playback_volume_range(m_pAlsaMasterMixerElem, + &min_volume, + &max_volume); + + if(max_volume > min_volume) + nRetVolume = (UINT16) (0.5 + (100.0 * (double)(volumeL + volumeR) / (2.0 * (max_volume - min_volume)))); + } + } + } + } + break; + + default: + print2stderr("Unknown audio interface in getDirectMasterVolume()\n"); + } + + return nRetVolume; +} + +void HelixSimplePlayer::setDirectMasterVolume(int vol) +{ + switch (m_direct) + { + case ALSA: + { + if (!m_pAlsaMasterMixerElem) + return; + + snd_mixer_elem_type_t type; + int err = 0; + type = snd_mixer_elem_get_type(m_pAlsaMasterMixerElem); + + + if (type == SND_MIXER_ELEM_SIMPLE) + { + long volume, min_volume, max_volume, range; + + if(snd_mixer_selem_has_playback_volume(m_pAlsaMasterMixerElem) || + snd_mixer_selem_has_playback_volume_joined(m_pAlsaMasterMixerElem)) + { + snd_mixer_selem_get_playback_volume_range(m_pAlsaMasterMixerElem, + &min_volume, + &max_volume); + + range = max_volume - min_volume; + volume = (long) (((double)vol / 100) * range + min_volume); + + + err = snd_mixer_selem_set_playback_volume( m_pAlsaMasterMixerElem, + SND_MIXER_SCHN_FRONT_LEFT, + volume); + if (err < 0) + print2stderr("snd_mixer_selem_set_playback_volume: %s\n", snd_strerror (err)); + + if (!snd_mixer_selem_is_playback_mono (m_pAlsaMasterMixerElem)) + { + /* Set the right channel too */ + err = snd_mixer_selem_set_playback_volume( m_pAlsaMasterMixerElem, + SND_MIXER_SCHN_FRONT_RIGHT, + volume); + if (err < 0) + print2stderr("snd_mixer_selem_set_playback_volume: %s\n", snd_strerror (err)); + } + } + } + } + break; + + default: + print2stderr("Unknown audio interface in setDirectMasterVolume()\n"); + } +} +#endif + +int HelixSimplePlayer::getDirectPCMVolume() +{ + int nRetVolume = 0; + + switch (m_direct) + { + case OSS: + { + int nVolume = 0; + int nLeftVolume = 0; + int nRightVolume = 0; + + if (m_nDevID < 0 || (::ioctl( m_nDevID, MIXER_READ(HX_VOLUME), &nVolume) < 0)) + { + print2stderr("ioctl fails when reading HW volume: mnDevID=%d, errno=%d\n", m_nDevID, errno); + nRetVolume = 50; // sensible default + } + else + { + nLeftVolume = (nVolume & 0x000000ff); + nRightVolume = (nVolume & 0x0000ff00) >> 8; + + //Which one to use? Average them? + nRetVolume = nLeftVolume ; + } + } + break; + + case ALSA: + { +#ifdef USE_HELIX_ALSA + if (!m_pAlsaPCMMixerElem) + return nRetVolume; + + snd_mixer_elem_type_t type; + int err = 0; + type = snd_mixer_elem_get_type(m_pAlsaPCMMixerElem); + + if (type == SND_MIXER_ELEM_SIMPLE) + { + long volumeL, volumeR, min_volume, max_volume; + + if(snd_mixer_selem_has_playback_volume(m_pAlsaPCMMixerElem) || + snd_mixer_selem_has_playback_volume_joined(m_pAlsaPCMMixerElem)) + { + err = snd_mixer_selem_get_playback_volume(m_pAlsaPCMMixerElem, + SND_MIXER_SCHN_FRONT_LEFT, + &volumeL); + if (err < 0) + print2stderr("snd_mixer_selem_get_playback_volume (L): %s\n", snd_strerror (err)); + else + { + if ( snd_mixer_selem_is_playback_mono ( m_pAlsaPCMMixerElem )) + volumeR = volumeL; + else + { + err = snd_mixer_selem_get_playback_volume(m_pAlsaPCMMixerElem, + SND_MIXER_SCHN_FRONT_RIGHT, + &volumeR); + if (err < 0) + print2stderr("snd_mixer_selem_get_playback_volume (R): %s\n", snd_strerror (err)); + } + } + + if (err == 0) + { + snd_mixer_selem_get_playback_volume_range(m_pAlsaPCMMixerElem, + &min_volume, + &max_volume); + + if(max_volume > min_volume) + nRetVolume = (UINT16) (0.5 + (100.0 * (double)(volumeL + volumeR) / (2.0 * (max_volume - min_volume)))); + } + } + } +#endif + } + break; + + default: + print2stderr("Unknown audio interface in getDirectPCMVolume()\n"); + } + + return nRetVolume; +} + +void HelixSimplePlayer::setDirectPCMVolume(int vol) +{ + switch (m_direct) + { + case OSS: + { + int nNewVolume=0; + + //Set both left and right volumes. + nNewVolume = (vol & 0xff) | ((vol & 0xff) << 8); + + if (::ioctl( m_nDevID, MIXER_WRITE(HX_VOLUME), &nNewVolume) < 0) + print2stderr("Unable to set direct HW volume\n"); + } + break; + + case ALSA: + { +#ifdef USE_HELIX_ALSA + if (!m_pAlsaPCMMixerElem) + return; + + snd_mixer_elem_type_t type; + int err = 0; + type = snd_mixer_elem_get_type(m_pAlsaPCMMixerElem); + + + if (type == SND_MIXER_ELEM_SIMPLE) + { + long volume, min_volume, max_volume, range; + + if(snd_mixer_selem_has_playback_volume(m_pAlsaPCMMixerElem) || + snd_mixer_selem_has_playback_volume_joined(m_pAlsaPCMMixerElem)) + { + snd_mixer_selem_get_playback_volume_range(m_pAlsaPCMMixerElem, + &min_volume, + &max_volume); + + range = max_volume - min_volume; + volume = (long) (((double)vol / 100) * range + min_volume); + + + err = snd_mixer_selem_set_playback_volume( m_pAlsaPCMMixerElem, + SND_MIXER_SCHN_FRONT_LEFT, + volume); + if (err < 0) + print2stderr("snd_mixer_selem_set_playback_volume: %s\n", snd_strerror (err)); + + if (!snd_mixer_selem_is_playback_mono (m_pAlsaPCMMixerElem)) + { + /* Set the right channel too */ + err = snd_mixer_selem_set_playback_volume( m_pAlsaPCMMixerElem, + SND_MIXER_SCHN_FRONT_RIGHT, + volume); + if (err < 0) + print2stderr("snd_mixer_selem_set_playback_volume: %s\n", snd_strerror (err)); + } + } + } +#endif + } + break; + + default: + print2stderr("Unknown audio interface in setDirectPCMVolume()\n"); + } +} + + +int HelixSimplePlayer::setURL(const char *file, int playerIndex, bool islocal) +{ + if (playerIndex == ALL_PLAYERS) + { + int i; + + for (i=0; i<nNumPlayers; i++) + setURL(file, i); + } + else + { + int len = strlen(file); + if (len >= MAXPATHLEN) + return -1;; + + print2stderr("SETURL MASTER VOL: %d\n",getDirectMasterVolume()); + + if (ppctrl[playerIndex]->pszURL) + delete [] ppctrl[playerIndex]->pszURL; + + // see if the file is already in the form of a url + char *tmp = strstr(file, "://"); + if (!tmp) + { + char pszURLOrig[MAXPATHLEN]; + const char* pszAddOn; + + strcpy(pszURLOrig, file); + RemoveWrappingQuotes(pszURLOrig); + pszAddOn = "file://"; + + ppctrl[playerIndex]->pszURL = new char[strlen(pszURLOrig)+strlen(pszAddOn)+1]; + if ( (len + strlen(pszAddOn)) < MAXPATHLEN ) + { + sprintf( ppctrl[playerIndex]->pszURL, "%s%s", pszAddOn, pszURLOrig ); + islocal = true; // diesnt matter what we were told... + } + else + return -1; + } + else + { + ppctrl[playerIndex]->pszURL = new char[len + 1]; + if (ppctrl[playerIndex]->pszURL) + strcpy(ppctrl[playerIndex]->pszURL, file); + else + return -1; + } + + ppctrl[playerIndex]->isLocal = islocal; + + print2stderr("opening %s on player %d, src cnt %d\n", + ppctrl[playerIndex]->pszURL, playerIndex, ppctrl[playerIndex]->pPlayer->GetSourceCount()); + +#ifdef __NOCROSSFADER__ + + if (HXR_OK == ppctrl[playerIndex]->pPlayer->OpenURL(ppctrl[playerIndex]->pszURL)) + { + print2stderr("opened player on %d src cnt %d\n", playerIndex, ppctrl[playerIndex]->pPlayer->GetSourceCount()); + m_urlchanged = true; + } +#else + /* try OpenRequest instead... */ + IHXRequest *ireq = 0; + pthread_mutex_lock(&m_engine_m); + pCommonClassFactory->CreateInstance(CLSID_IHXRequest, (void **)&ireq); + if (ireq) + { + //print2stderr("GOT THE IHXRequest Interface!!\n"); + ireq->SetURL(ppctrl[playerIndex]->pszURL); + ppctrl[playerIndex]->pPlayer2->OpenRequest(ireq); + m_urlchanged = true; + ireq->Release(); + } + pthread_mutex_unlock(&m_engine_m); + +#endif + + } + + return 0; +} + +int HelixSimplePlayer::numPlugins() const +{ + if (pPluginE) + { + return ((int)pPluginE->GetNumOfPlugins()); + } + + return 0; +} + +int HelixSimplePlayer::getPluginInfo(int index, + const char *&description, + const char *©right, + const char *&moreinfourl) const +{ + if (index < m_numPlugins) + { + description = m_pluginInfo[index]->description; + copyright = m_pluginInfo[index]->copyright; + moreinfourl = m_pluginInfo[index]->moreinfourl; + + return 0; + } + return -1; +} + + +void HelixSimplePlayer::play(const char *file, int playerIndex, bool fadein, bool fadeout, unsigned long fadetime) +{ + if (!setURL(file, playerIndex)) + play(playerIndex, fadein, fadeout, fadetime); +} + +void HelixSimplePlayer::play(int playerIndex, bool fadein, bool fadeout, unsigned long fadetime) +{ + int i; + int firstPlayer = playerIndex == ALL_PLAYERS ? 0 : playerIndex; + int lastPlayer = playerIndex == ALL_PLAYERS ? nNumPlayers : playerIndex + 1; + + nPlay = 0; + nNumPlayRepeats=1; + while(nPlay < nNumPlayRepeats) + { + nPlay++; + if (bEnableVerboseMode) + { + print2stdout("Starting play #%d...\n", nPlay); + } + //print2stderr("firstplayer = %d lastplayer=%d\n",firstPlayer,lastPlayer); + + UINT32 starttime=0, endtime=0, now=0; + for (i = firstPlayer; i < lastPlayer; i++) + { + // start is already protected... + start(i, fadein, fadetime); + + starttime = GetTime(); + endtime = starttime + nTimeDelta; + while (1) + { + pthread_mutex_lock(&m_engine_m); + DoEvents(nTimeDelta); + pthread_mutex_unlock(&m_engine_m); + now = GetTime(); + if (now >= endtime) + break; + if (fadeout && !ppctrl[i]->bFadeOut && now > endtime - fadetime) + { + ppctrl[i]->bFadeOut = true; + ((HSPPreMixAudioHook *)ppctrl[i]->pPreMixHook)->setFadelength(fadetime); + ((HSPPreMixAudioHook *)ppctrl[i]->pPreMixHook)->setFadeout(true); + } + } + } + + starttime = GetTime(); + if (nStopTime == -1) + { + bStopTime = false; + } + else + { + endtime = starttime + nStopTime; + } + bStopping = false; + // Handle events coming from all of the players + while (!done(playerIndex)) + { + now = GetTime(); + if (!bStopping && bStopTime && now >= endtime) + { + // Stop all of the players, as they should all be done now + if (bEnableVerboseMode) + { + print2stdout("\nEnd (Stop) time reached. Stopping...\n"); + } + stop(playerIndex); + bStopping = true; + } + pthread_mutex_lock(&m_engine_m); + DoEvent(); + pthread_mutex_unlock(&m_engine_m); + } + + // Stop all of the players, as they should all be done now + if (bEnableVerboseMode) + { + print2stdout("\nPlayback complete. Stopping all players...\n"); + } + stop(playerIndex); + + // repeat until nNumRepeats + } +} + +void HelixSimplePlayer::start(int playerIndex, bool fadein, unsigned long fadetime) +{ + if (playerIndex == ALL_PLAYERS) + { + int i; + for (i=0; i<nNumPlayers; i++) + start(i, fadein, fadetime); + } + else + { + if (!ppctrl[playerIndex]->pszURL) + return; + + print2stderr("START MASTER VOL: %d\n",getDirectMasterVolume()); + + if (bEnableVerboseMode) + { + print2stdout("Starting player %d...\n", playerIndex); + } + + ppctrl[playerIndex]->bFadeIn = fadein; + ppctrl[playerIndex]->bFadeOut = false; // assume we'll only fade out if we have another track + ppctrl[playerIndex]->ulFadeLength = fadetime; + if (!ppctrl[playerIndex]->bPlaying) + { + pthread_mutex_lock(&m_engine_m); + ppctrl[playerIndex]->pPlayer->Begin(); + pthread_mutex_unlock(&m_engine_m); + + ppctrl[playerIndex]->bPlaying = true; + ppctrl[playerIndex]->bStarting = true; + //print2stderr("Begin player %d\n", playerIndex); + } + } +} + + +void HelixSimplePlayer::start(const char *file, int playerIndex, bool fadein, unsigned long fadetime) +{ + setURL(file, playerIndex); + start(playerIndex, fadein, fadetime); +} + + + +bool HelixSimplePlayer::done(int playerIndex) +{ + BOOL bAllDone = true; + + if (playerIndex == ALL_PLAYERS) + // Start checking at the end of the array since those players + // were started last and are therefore more likely to not be + // finished yet. + for (int i = nNumPlayers - 1; i >= 0 && bAllDone; i--) + { + pthread_mutex_lock(&m_engine_m); + if (ppctrl[i]->bStarting || !ppctrl[i]->pPlayer->IsDone()) + ppctrl[i]->bPlaying = (bAllDone = false); + pthread_mutex_unlock(&m_engine_m); + } + else + { + if (playerIndex < nNumPlayers) + { + pthread_mutex_lock(&m_engine_m); + if ((bAllDone = (!ppctrl[playerIndex]->bStarting && ppctrl[playerIndex]->pPlayer->IsDone()))) + ppctrl[playerIndex]->bPlaying = false; + pthread_mutex_unlock(&m_engine_m); + } + } + + return bAllDone; +} + +void HelixSimplePlayer::stop(int playerIndex) +{ + if (playerIndex == ALL_PLAYERS) + for (int i = 0; i < nNumPlayers; i++) + { + pthread_mutex_lock(&m_engine_m); + ppctrl[i]->pPlayer->Stop(); + pthread_mutex_unlock(&m_engine_m); + + ppctrl[i]->bPlaying = false; + ppctrl[i]->bStarting = false; + ppctrl[i]->isLocal = false; + } + else + { + if (playerIndex < nNumPlayers) + { + pthread_mutex_lock(&m_engine_m); + ppctrl[playerIndex]->pPlayer->Stop(); + pthread_mutex_unlock(&m_engine_m); + ppctrl[playerIndex]->bPlaying = false; + ppctrl[playerIndex]->bStarting = false; + ppctrl[playerIndex]->isLocal = false; + memset(&ppctrl[playerIndex]->md, 0, sizeof(ppctrl[playerIndex]->md)); + } + } +} + +HelixSimplePlayer::metaData *HelixSimplePlayer::getMetaData(int playerIndex) +{ + return &ppctrl[playerIndex]->md; +} + + +void HelixSimplePlayer::dispatch() +{ + struct _HXxEvent *pNothing = 0x0; + struct timeval tv; + int volAfter = 0; + + tv.tv_sec = 0; + tv.tv_usec = SLEEP_TIME*1000; + + if (m_urlchanged) + { +#ifdef USE_HELIX_ALSA + m_MvolBefore = getDirectMasterVolume(); + print2stderr("Master Volume is: %d\n", m_MvolBefore); +#endif + m_volBefore = getDirectPCMVolume(); + m_urlchanged = false; + print2stderr("Volume is: %d\n", m_volBefore); + } + pEngine->EventOccurred(pNothing); +#ifdef USE_HELIX_ALSA + if (m_MvolBefore > 0 && m_MvolAtStart != m_MvolBefore && (volAfter = getDirectMasterVolume()) != m_MvolBefore) + { + print2stderr("RESETTING MASTER VOLUME TO: %d\n", m_MvolBefore); + setDirectMasterVolume(m_volBefore); + print2stderr("Now Master Volume is %d\n", getDirectMasterVolume()); + m_MvolBefore = -1; + } +#endif + if (m_volBefore > 0 && m_volAtStart != m_volBefore && (volAfter = getDirectPCMVolume()) != m_volBefore) + { + print2stderr("RESETTING VOLUME TO: %d\n", m_volBefore); + setDirectPCMVolume(m_volBefore); + print2stderr("Now Volume is %d\n", getDirectPCMVolume()); + m_volBefore = -1; + } +} + + +bool HelixSimplePlayer::isPlaying(int playerIndex) const +{ + if (playerIndex < nNumPlayers) + return ppctrl[playerIndex]->bPlaying; + else + return false; +} + +bool HelixSimplePlayer::isLocal(int playerIndex) const +{ + if (playerIndex < nNumPlayers) + return (ppctrl[playerIndex]->isLocal && duration(playerIndex)); + else + return false; +} + +void HelixSimplePlayer::pause(int playerIndex) +{ + int i; + + if (playerIndex == ALL_PLAYERS) + for (i=0; i<nNumPlayers; i++) + pause(i); + else + if (playerIndex < nNumPlayers) + { + pthread_mutex_lock(&m_engine_m); + ppctrl[playerIndex]->pPlayer->Pause(); + pthread_mutex_unlock(&m_engine_m); + ppctrl[playerIndex]->bPlaying = false; + } +} + +void HelixSimplePlayer::resume(int playerIndex) +{ + int i; + + if (playerIndex == ALL_PLAYERS) + for (i=0; i<nNumPlayers; i++) + resume(i); + else + if (playerIndex < nNumPlayers) + { + pthread_mutex_lock(&m_engine_m); + ppctrl[playerIndex]->pPlayer->Begin(); + pthread_mutex_unlock(&m_engine_m); + ppctrl[playerIndex]->bPlaying = true; + } +} + + +void HelixSimplePlayer::seek(unsigned long pos, int playerIndex) +{ + int i; + + if (playerIndex == ALL_PLAYERS) + for (i=0; i<nNumPlayers; i++) + seek(pos, i); + else + if (playerIndex < nNumPlayers) + { + pthread_mutex_lock(&m_engine_m); + ppctrl[playerIndex]->pPlayer->Seek(pos); + pthread_mutex_unlock(&m_engine_m); + } +} + +unsigned long HelixSimplePlayer::where(int playerIndex) const +{ + if (playerIndex < nNumPlayers && ppctrl[playerIndex]->pHSPContext) + //return ppctrl[playerIndex]->pHSPContext->position(); + return ppctrl[playerIndex]->pPlayer->GetCurrentPlayTime(); + else + return 0; +} + +unsigned long HelixSimplePlayer::duration(int playerIndex) const +{ + if (playerIndex < nNumPlayers && ppctrl[playerIndex]->pHSPContext) + return ppctrl[playerIndex]->pHSPContext->duration(); + else + return 0; +} + +unsigned long HelixSimplePlayer::getVolume(int playerIndex) +{ + unsigned long vol; + + if (playerIndex < nNumPlayers) + { + pthread_mutex_lock(&m_engine_m); + vol = ppctrl[playerIndex]->volume; + //if (ppctrl[playerIndex]->pVolume) + //pVolume->GetVolume(); + pthread_mutex_unlock(&m_engine_m); + + return (vol); + } + else + return 0; +} + +void HelixSimplePlayer::setVolume(unsigned long vol, int playerIndex) +{ + int i; + + if (playerIndex == ALL_PLAYERS) + { + for (i=0; i<nNumPlayers; i++) + setVolume(vol, i); + } + else + if (playerIndex < nNumPlayers) + { + pthread_mutex_lock(&m_engine_m); +#ifndef HELIX_SW_VOLUME_INTERFACE + ppctrl[playerIndex]->volume = vol; + ((HSPFinalAudioHook *)pFinalAudioHook)->setGain(vol); +#else + ppctrl[playerIndex]->pVolume->SetVolume(vol); +#endif + pthread_mutex_unlock(&m_engine_m); + } +} + +void HelixSimplePlayer::setMute(bool mute, int playerIndex) +{ + int i; + + if (playerIndex == ALL_PLAYERS) + { + for (i=0; i<nNumPlayers; i++) + setMute(mute, i); + } + else + if (playerIndex < nNumPlayers) + { + pthread_mutex_lock(&m_engine_m); + ppctrl[playerIndex]->pVolume->SetMute(mute); + pthread_mutex_unlock(&m_engine_m); + } +} + + +bool HelixSimplePlayer::getMute(int playerIndex) +{ + bool ismute; + + if (playerIndex < nNumPlayers) + { + pthread_mutex_lock(&m_engine_m); + ismute = ppctrl[playerIndex]->ismute; +//pVolume->GetMute(); + pthread_mutex_unlock(&m_engine_m); + + return ismute; + } + else + return false; +} + +bool HelixSimplePlayer::ReadGUIDFile() +{ + BOOL bSuccess = false; + FILE* pFile = NULL; + int nNumRead = 0; + int readSize = 10000; + char* pszBuffer = new char[readSize]; + + if (m_pszGUIDFile) + { + if((pFile = fopen(m_pszGUIDFile, "r")) != NULL) + { + // Read in the entire file + nNumRead = fread(pszBuffer, sizeof(char), readSize, pFile); + pszBuffer[nNumRead] = '\0'; + + // Store it for later parsing + m_pszGUIDList = new char[nNumRead + 1]; + strcpy(m_pszGUIDList, pszBuffer); /* Flawfinder: ignore */ + + fclose(pFile); + pFile = NULL; + + if (nNumRead > 0) + { + bSuccess = true; + } + } + } + + delete [] pszBuffer; + + return bSuccess; +} + + +void HelixSimplePlayer::addScopeBuf(struct DelayQueue *item, int playerIndex) +{ + if (playerIndex >=0 && playerIndex < nNumPlayers) + { + pthread_mutex_lock(&ppctrl[playerIndex]->m_scope_m); + + if (ppctrl[playerIndex]->scopebuftail) + { + item->fwd = 0; + ppctrl[playerIndex]->scopebuftail->fwd = item; + ppctrl[playerIndex]->scopebuftail = item; + ppctrl[playerIndex]->scopecount++; + } + else + { + item->fwd = 0; + ppctrl[playerIndex]->scopebufhead = item; + ppctrl[playerIndex]->scopebuftail = item; + ppctrl[playerIndex]->scopecount = 1; + } + pthread_mutex_unlock(&ppctrl[playerIndex]->m_scope_m); + } +} + +struct DelayQueue *HelixSimplePlayer::getScopeBuf(int playerIndex) +{ + if (playerIndex >=0 && playerIndex < nNumPlayers) + { + pthread_mutex_lock(&ppctrl[playerIndex]->m_scope_m); + + struct DelayQueue *item = ppctrl[playerIndex]->scopebufhead; + + if (item) + { + ppctrl[playerIndex]->scopebufhead = item->fwd; + ppctrl[playerIndex]->scopecount--; + if (!ppctrl[playerIndex]->scopebufhead) + ppctrl[playerIndex]->scopebuftail = 0; + } + + pthread_mutex_unlock(&ppctrl[playerIndex]->m_scope_m); + + return item; + } + else + return 0; +} + +int HelixSimplePlayer::peekScopeTime(unsigned long &t, int playerIndex) +{ + if (playerIndex >=0 && playerIndex < nNumPlayers) + { + if (ppctrl[playerIndex]->scopebufhead) + t = ppctrl[playerIndex]->scopebufhead->time; + else + return -1; + return 0; + } + return -1; +} + +void HelixSimplePlayer::clearScopeQ(int playerIndex) +{ + if (playerIndex < 0) + { + for (int i=0; i<nNumPlayers; i++) + clearScopeQ(i); + } + else + { + struct DelayQueue *item; + while ((item = getScopeBuf(playerIndex))) + delete item; + } +} + diff --git a/amarok/src/engine/helix/helix-sp/helix-sp.h b/amarok/src/engine/helix/helix-sp/helix-sp.h new file mode 100644 index 00000000..1b758b5f --- /dev/null +++ b/amarok/src/engine/helix/helix-sp/helix-sp.h @@ -0,0 +1,373 @@ +/* + * + * This software is released under the provisions of the GPL version 2. + * see file "COPYING". If that file is not available, the full statement + * of the license can be found at + * + * http://www.fsf.org/licensing/licenses/gpl.txt + * + * Copyright (c) Paul Cifarelli 2005 + * Portions Copyright (c) 1995-2002 RealNetworks, Inc. All Rights Reserved. + * + * at this point, this class should be HelixNotSoSimplePlayer... + */ + +#ifndef _HELIX_SIMPLEPLAYER_LIB_H_INCLUDED_ +#define _HELIX_SIMPLEPLAYER_LIB_H_INCLUDED_ + +class HSPClientAdviceSink; +class HSPClientContext; +class IHXErrorSinkControl; +class HelixSimplePlayerAudioStreamInfoResponse; + +#include <limits.h> +#include <sys/param.h> +#include <pthread.h> +#include <vector> +#include <config.h> +#include <iostream> +using std::vector; + +#define MAX_PATH PATH_MAX + +#define MAX_PLAYERS 100 // that should do it... +#define MAX_SCOPE_SAMPLES 5120 + +class HelixSimplePlayer; +class CHXURL; +class IHXVolumeAdviseSink; +class IHXAudioPlayer; +class IHXAudioStream; +class IHXAudioCrossFade; +class IHXPlayer; +class IHXPlayer2; +class IHXErrorSink; +class IHXErrorSinkControl; +class IHXAudioPlayer; +class IHXVolume; +class IHXPlayerNavigator; +class IHXClientEngineSelector; +class IHXClientEngine; +class IHXAudioHook; +class IHXAudioStreamInfoResponse; +class IHXCommonClassFactory; +class IHXPluginEnumerator; +class IHXPlugin2Handler; +class IHXAudioDeviceManager; +class IHXAudioHookManager; +class IHXPreferences; +class HSPAudioDevice; +class IHXAudioDeviceResponse; +#ifdef USE_HELIX_ALSA +struct _snd_mixer; +struct _snd_mixer_elem; +#endif + +// scope delay queue +class DelayQueue +{ +public: + DelayQueue() : fwd(0), len(0), time(0), etime(0), nchan(0), bps(0), allocd(false), buf(0) {} + DelayQueue(int bufsize) : fwd(0), len(bufsize), time(0), etime(0), nchan(0), bps(0), allocd(true), buf(0) + { buf = new unsigned char [ bufsize ]; } + DelayQueue(DelayQueue &src) : fwd(0), len(src.len), time(src.time), etime(src.etime), nchan(src.nchan), bps(src.bps), allocd(true), buf(0) + { buf = new unsigned char [ len ]; memcpy( (void *) buf, (void *) src.buf, src.len ); } + ~DelayQueue() { if (allocd) delete [] buf; } + struct DelayQueue *fwd; + int len; // len of the buffer + unsigned long time; // start time of the buffer + unsigned long etime; // end time of the buffer + int nchan; // number of channels + int bps; // bytes per sample + double tps; // time per sample + int spb; // samples per buffer + bool allocd; // did we allocate the memory? + unsigned char *buf; +}; + + +// simple list of supported mime type +struct MimeList +{ + MimeList(char *mimestr, char *ext) : mimetypes(0), mimeexts(0) + { mimetypes = new char[strlen(mimestr)+1]; strcpy(mimetypes,mimestr); mimeexts = new char[strlen(ext)+1]; strcpy(mimeexts,ext); } + ~MimeList() { delete [] mimetypes; delete [] mimeexts; } + struct MimeList *fwd; + char *mimetypes; + char *mimeexts; +}; + +class HelixSimplePlayer +{ +public: + enum { ALL_PLAYERS = -1 }; + + HelixSimplePlayer(); + virtual ~HelixSimplePlayer(); + + void init(const char *corelibpath, const char *pluginslibpath, const char *codecspath, int numPlayers = 1); + void tearDown(); + int initDirectSS(); + int addPlayer(); // add another player + void play(int playerIndex = ALL_PLAYERS, + bool fadein = false, bool fadout = false, + unsigned long fadetime = 0); // play the current url, waiting for it to finish + void play(const char *url, int playerIndex = ALL_PLAYERS, + bool fadein = false, bool fadeout = false, + unsigned long fadetime = 0); // play the file, setting it as the current url; wait for it to finish + int setURL(const char *url, + int playerIndex = ALL_PLAYERS, + bool islocal = true); // set the current url + bool done(int playerIndex = ALL_PLAYERS); // test to see if the player(s) is(are) done + void start(int playerIndex = ALL_PLAYERS, + bool fadein = false, + unsigned long fadetime = 0); // start the player + void start(const char *file, int playerIndex = ALL_PLAYERS, + bool fadein = false, + unsigned long fadetime = 0); // start the player, setting the current url first + void stop(int playerIndex = ALL_PLAYERS); // stop the player(s) + void pause(int playerIndex = ALL_PLAYERS); // pause the player(s) + void resume(int playerIndex = ALL_PLAYERS); // pause the player(s) + void seek(unsigned long pos, int playerIndex = ALL_PLAYERS); // seek to the pos + unsigned long where(int playerIndex) const; // where is the player in the playback + unsigned long duration(int playerIndex) const; // how long (ms) is this clip? + unsigned long getVolume(int playerIndex); // get the current volume + void setVolume(unsigned long vol, int playerIndex = ALL_PLAYERS); // set the volume + void setMute(bool mute, int playerIndex = ALL_PLAYERS); // set mute: mute = true to mute the volume, false to unmute + bool getMute(int playerIndex); // get the mute state of the player + void dispatch(); // dispatch the player(s) + + void cleanUpStream(int playerIndex); // cleanup after stream complete + bool isPlaying(int playerIndex) const; // is the player currently playing? + bool isLocal(int playerIndex) const; // playing a local file + int numPlayers() const { return nNumPlayers; } // return the number of players + + + virtual void onVolumeChange(int) {} // called when the volume is changed + virtual void onMuteChange(int) {} // called when mute is changed + virtual void onContacting(const char */*host*/) {} // called when contacting a host + virtual void onBuffering(int /*percentage*/) {} // called when buffering + + int getError() const { return theErr; } + + static char* RemoveWrappingQuotes(char* str); + //void setUsername(const char *username) { m_pszUsername = username; } + //void setPassword(const char *password) { m_pszPassword = password; } + //void setGUIDFile(const char *file) { m_pszGUIDFile = file; } + bool ReadGUIDFile(); + + typedef struct + { + char title[512]; + char artist[512]; + unsigned long bitrate; + } metaData; + +private: + void DoEvent(); + void DoEvents(int nTimeDelta); + unsigned long GetTime(); + + char mCoreLibPath[MAXPATHLEN]; + char mPluginLibPath[MAXPATHLEN]; + int theErr; + IHXErrorSink* pErrorSink; + IHXErrorSinkControl* pErrorSinkControl; + IHXClientEngineSelector* pCEselect; + IHXCommonClassFactory* pCommonClassFactory; + IHXPluginEnumerator* pPluginE; + IHXPlugin2Handler* pPlugin2Handler; + IHXAudioDeviceManager* pAudioDeviceManager; + IHXAudioHookManager* pAudioHookManager; + IHXAudioHook* pFinalAudioHook; + HSPAudioDevice* pAudioDevice; + + struct playerCtrl + { + bool bPlaying; + bool bStarting; + bool bFadeIn; + bool bFadeOut; + unsigned long ulFadeLength; + IHXAudioStream* pStream; + HSPClientContext* pHSPContext; + IHXPlayer* pPlayer; + IHXPlayer2* pPlayer2; + IHXAudioPlayer* pAudioPlayer; + IHXAudioCrossFade* pCrossFader; + IHXVolume* pVolume; + IHXVolumeAdviseSink* pVolumeAdvise; + IHXAudioStreamInfoResponse* pStreamInfoResponse; + IHXAudioHook* pPreMixHook; + IHXAudioHook* pPostMixHook; + metaData md; + char* pszURL; + bool isLocal; + unsigned short volume; + bool ismute; + // scope + int scopecount; + struct DelayQueue *scopebufhead; + struct DelayQueue *scopebuftail; + pthread_mutex_t m_scope_m; + } **ppctrl; + + IHXAudioDeviceResponse *pAudioDeviceResponse; + bool bURLFound; + int nNumPlayers; + int nNumPlayRepeats; + int nTimeDelta; + int nStopTime; + bool bStopTime; + void* core_handle; + bool bStopping; + int nPlay; + +public: + const IHXAudioPlayer *getAudioPlayer(int playerIndex) const { return ppctrl[playerIndex]->pAudioPlayer; } + const IHXAudioCrossFade *getCrossFader(int playerIndex) const { return ppctrl[playerIndex]->pCrossFader; } + + // crossfade + void setFadeout(bool fadeout, unsigned long fadelength, int playerIndex = ALL_PLAYERS); + + // scope + void addScopeBuf(struct DelayQueue *item, int playerIndex); + DelayQueue *getScopeBuf(int playerIndex); + int getScopeCount(int playerIndex) { return playerIndex >= 0 && playerIndex < nNumPlayers ? ppctrl[playerIndex]->scopecount : 0; } + int peekScopeTime(unsigned long &t, int playerIndex); + void clearScopeQ(int playerIndex = ALL_PLAYERS); + + // equalizer + void enableEQ(bool enabled) { m_eq_enabled = enabled; } + bool isEQenabled() { return m_eq_enabled; } + void updateEQgains(); + + int numPlugins() const; + int getPluginInfo(int index, + const char *&description, + const char *©right, + const char *&moreinfourl) const; + + + metaData *getMetaData(int playerIndex); + + const MimeList *getMimeList() const { return mimehead; } + int getMimeListLen() const { return mimelistlen; } + +private: + + bool bEnableAdviceSink; + bool bEnableVerboseMode; + IHXClientEngine* pEngine; + IHXPreferences* pEngineContext; + char* m_pszUsername; + char* m_pszPassword; + char* m_pszGUIDFile; + char* m_pszGUIDList; + int m_Error; + unsigned long m_ulNumSecondsPlayed; + + pthread_mutex_t m_engine_m; + + // supported mime type list + MimeList *mimehead; + int mimelistlen; + + // equalizer + bool m_eq_enabled; + +public: + // 0 = OSS + // 1 = OldOSSsupport + // 2 = ESound + // 3 = Alsa + // 4 = USound + enum AUDIOAPI + { + OSS, + OLDOSS, + ESOUND, + ALSA, + USOUND + }; + + void setOutputSink( AUDIOAPI out ); + AUDIOAPI getOutputSink(); + void setDevice( const char *dev ); + const char *getDevice(); + void setAlsaCapableCore() { m_AlsaCapableCore = true; } + virtual int fallbackToOSS() { return 0; } + + int m_preamp; + vector<int> m_equalizerGains; + +private: + AUDIOAPI m_outputsink; + char *m_device; + + // work around the annoying problem of the core reseting the PCM volume on every url change +protected: + void openAudioDevice(); + void closeAudioDevice(); +#ifdef USE_HELIX_ALSA + int getDirectMasterVolume(); + void setDirectMasterVolume(int vol); +#endif + int getDirectPCMVolume(); + void setDirectPCMVolume(int vol); + +private: + AUDIOAPI m_direct; + bool m_AlsaCapableCore; + int m_nDevID; +#ifdef USE_HELIX_ALSA + struct _snd_mixer* m_pAlsaMixerHandle; + struct _snd_mixer_elem* m_pAlsaMasterMixerElem; + struct _snd_mixer_elem* m_pAlsaPCMMixerElem; +#endif + char *m_alsaDevice; + bool m_urlchanged; + int m_volBefore; + int m_volAtStart; +#ifdef USE_HELIX_ALSA + int m_MvolBefore; + int m_MvolAtStart; +#endif + + int m_numPlugins; + struct pluginInfo + { + char *description; + char *copyright; + char *moreinfourl; + } **m_pluginInfo; + + +protected: + virtual int print2stdout(const char *fmt, ...) +#ifdef __GNUC__ + __attribute__ ((format (printf, 2, 3))) +#endif + ; + virtual int print2stderr(const char *fmt, ...) +#ifdef __GNUC__ + __attribute__ ((format (printf, 2, 3))) +#endif + ; + virtual void notifyUser(unsigned long/*code*/, const char */*moreinfo*/, const char */*moreinfourl*/) {} + virtual void interruptUser(unsigned long/*code*/, const char */*moreinfo*/, const char */*moreinfourl*/) {} + + friend class HSPClientAdviceSink; + friend class HSPErrorSink; + friend class HSPAuthenticationManager; + friend class HelixSimplePlayerAudioStreamInfoResponse; + friend class HSPPreMixAudioHook; + friend class HSPPostProcessor; + friend class HSPPostMixAudioHook; + friend class HSPFinalAudioHook; + friend class HelixSimplePlayerVolumeAdvice; + friend class HSPEngineContext; + friend class HSPAudioDevice; +}; + +#endif diff --git a/amarok/src/engine/helix/helix-sp/helixdefines.h b/amarok/src/engine/helix/helix-sp/helixdefines.h new file mode 100644 index 00000000..243d682d --- /dev/null +++ b/amarok/src/engine/helix/helix-sp/helixdefines.h @@ -0,0 +1,87 @@ +#ifndef HELIX_CONFIG_XVIDEO +#define HELIX_CONFIG_XVIDEO 1 +#endif +#ifndef HELIX_FEATURE_ADVANCEDGROUPMGR +#define HELIX_FEATURE_ADVANCEDGROUPMGR 1 +#endif +#ifndef HELIX_FEATURE_AUDIO +#define HELIX_FEATURE_AUDIO 1 +#endif +#ifndef HELIX_FEATURE_AUDIO_CODEC_14_4 +#define HELIX_FEATURE_AUDIO_CODEC_14_4 1 +#endif +#ifndef HELIX_FEATURE_AUDIO_CODEC_28_8 +#define HELIX_FEATURE_AUDIO_CODEC_28_8 1 +#endif +#ifndef HELIX_FEATURE_AUDIO_CODEC_GECKO +#define HELIX_FEATURE_AUDIO_CODEC_GECKO 1 +#endif +#ifndef HELIX_FEATURE_AUDIO_CODEC_INTERLEAVE_ALL +#define HELIX_FEATURE_AUDIO_CODEC_INTERLEAVE_ALL 1 +#endif +#ifndef HELIX_FEATURE_AUDIO_CODEC_O5_6 +#define HELIX_FEATURE_AUDIO_CODEC_O5_6 1 +#endif +#ifndef HELIX_FEATURE_AUDIO_CODEC_SIPRO +#define HELIX_FEATURE_AUDIO_CODEC_SIPRO 1 +#endif +#ifndef HELIX_FEATURE_AUDIO_CODEC_TOKYO +#define HELIX_FEATURE_AUDIO_CODEC_TOKYO 1 +#endif +#ifndef HELIX_FEATURE_AUDIO_MPA_LAYER1 +#define HELIX_FEATURE_AUDIO_MPA_LAYER1 1 +#endif +#ifndef HELIX_FEATURE_AUDIO_MPA_LAYER2 +#define HELIX_FEATURE_AUDIO_MPA_LAYER2 1 +#endif +#ifndef HELIX_FEATURE_AUDIO_MPA_LAYER3 +#define HELIX_FEATURE_AUDIO_MPA_LAYER3 1 +#endif +#ifndef HELIX_FEATURE_AUDIO_REAL +#define HELIX_FEATURE_AUDIO_REAL 1 +#endif +#ifndef HELIX_FEATURE_BASICGROUPMGR +#define HELIX_FEATURE_BASICGROUPMGR 1 +#endif +#ifndef HELIX_FEATURE_FULLGUID +#define HELIX_FEATURE_FULLGUID 1 +#endif +#ifndef HELIX_FEATURE_PLAYBACK_LOCAL +#define HELIX_FEATURE_PLAYBACK_LOCAL 1 +#endif +#ifndef HELIX_FEATURE_PLUGINHANDLER2 +#define HELIX_FEATURE_PLUGINHANDLER2 1 +#endif +#ifndef RIBOSOME_TEST_BRANCH +#define RIBOSOME_TEST_BRANCH 1 +#endif +#ifndef THREADS_SUPPORTED +#define THREADS_SUPPORTED 1 +#endif +#ifndef USE_XWINDOWS +#define USE_XWINDOWS 1 +#endif +#ifndef _GNU_SOURCE +#define _GNU_SOURCE 1 +#endif +#ifndef _LINUX +#define _LINUX 1 +#endif +#ifndef _RED_HAT_5_X_ +#define _RED_HAT_5_X_ 1 +#endif +#ifndef _REENTRANT +#define _REENTRANT 1 +#endif +#ifndef _TIMEDWAITS_RECURSIVE_MUTEXES +#define _TIMEDWAITS_RECURSIVE_MUTEXES 1 +#endif +#ifndef _UNIX +#define _UNIX 1 +#endif +#ifndef _UNIX_THREADED_NETWORK_IO +#define _UNIX_THREADED_NETWORK_IO 1 +#endif +#ifndef _UNIX_THREADS_SUPPORTED +#define _UNIX_THREADS_SUPPORTED 1 +#endif diff --git a/amarok/src/engine/helix/helix-sp/hspadvisesink.cpp b/amarok/src/engine/helix/helix-sp/hspadvisesink.cpp new file mode 100644 index 00000000..f81e9c01 --- /dev/null +++ b/amarok/src/engine/helix/helix-sp/hspadvisesink.cpp @@ -0,0 +1,668 @@ + +/* + * + * This software is released under the provisions of the GPL version 2. + * see file "COPYING". If that file is not available, the full statement + * of the license can be found at + * + * http://www.fsf.org/licensing/licenses/gpl.txt + * + * Portions Copyright (c) 1995-2002 RealNetworks, Inc. All Rights Reserved. + * Portions (c) Paul Cifarelli 2005 + */ + +#include <stdio.h> + +#include "hxcomm.h" +#include "hxmon.h" +#include "hxcore.h" +#include "hxengin.h" +#include "hxclsnk.h" +#include "ihxpckts.h" + +#include "hxausvc.h" +#include "helix-sp.h" +#include "hspadvisesink.h" +#include "utils.h" + +#if defined(__cplusplus) +extern "C" { +#endif /* defined(__cplusplus) */ + +#if defined(__cplusplus) +} +#endif /* defined(__cplusplus) */ + +HSPClientAdviceSink::HSPClientAdviceSink(IUnknown* pUnknown, LONG32 lClientIndex, HelixSimplePlayer *pSplay) + : m_splayer(pSplay) + , m_lRefCount (0) + , m_lClientIndex (lClientIndex) + , m_pUnknown (NULL) + , m_pRegistry (NULL) + , m_pScheduler (NULL) + , m_position(0) + , m_duration(0) + , m_lCurrentBandwidth(0) + , m_lAverageBandwidth(0) + , m_bOnStop(0) +{ + //m_splayer->bEnableAdviceSink = true; + + if (pUnknown) + { + m_pUnknown = pUnknown; + m_pUnknown->AddRef(); + + if (HXR_OK != m_pUnknown->QueryInterface(IID_IHXRegistry, (void**)&m_pRegistry)) + { + m_pRegistry = NULL; + } + + if (HXR_OK != m_pUnknown->QueryInterface(IID_IHXScheduler, (void**)&m_pScheduler)) + { + m_pScheduler = NULL; + } + + IHXPlayer* pPlayer; + if(HXR_OK == m_pUnknown->QueryInterface(IID_IHXPlayer, + (void**)&pPlayer)) + { + pPlayer->AddAdviseSink(this); + pPlayer->Release(); + } + } +} + +HSPClientAdviceSink::~HSPClientAdviceSink(void) +{ + if (m_pScheduler) + { + m_pScheduler->Release(); + m_pScheduler = NULL; + } + + if (m_pRegistry) + { + m_pRegistry->Release(); + m_pRegistry = NULL; + } + + if (m_pUnknown) + { + m_pUnknown->Release(); + m_pUnknown = NULL; + } +} + + +// *** IUnknown methods *** + +///////////////////////////////////////////////////////////////////////// +// Method: +// IUnknown::QueryInterface +// Purpose: +// Implement this to export the interfaces supported by your +// object. +// +STDMETHODIMP HSPClientAdviceSink::QueryInterface(REFIID riid, void** ppvObj) +{ + if (IsEqualIID(riid, IID_IUnknown)) + { + AddRef(); + *ppvObj = (IUnknown*)(IHXClientAdviseSink*)this; + return HXR_OK; + } + else if (IsEqualIID(riid, IID_IHXClientAdviseSink)) + { + AddRef(); + *ppvObj = (IHXClientAdviseSink*)this; + return HXR_OK; + } + + *ppvObj = NULL; + return HXR_NOINTERFACE; +} + +///////////////////////////////////////////////////////////////////////// +// Method: +// IUnknown::AddRef +// Purpose: +// Everyone usually implements this the same... feel free to use +// this implementation. +// +STDMETHODIMP_(ULONG32) HSPClientAdviceSink::AddRef() +{ + return InterlockedIncrement(&m_lRefCount); +} + +///////////////////////////////////////////////////////////////////////// +// Method: +// IUnknown::Release +// Purpose: +// Everyone usually implements this the same... feel free to use +// this implementation. +// +STDMETHODIMP_(ULONG32) HSPClientAdviceSink::Release() +{ + if (InterlockedDecrement(&m_lRefCount) > 0) + { + return m_lRefCount; + } + + delete this; + return 0; +} + +/* + * IHXClientAdviseSink methods + */ + +/************************************************************************ + * Method: + * IHXClientAdviseSink::OnPosLength + * Purpose: + * Called to advise the client that the position or length of the + * current playback context has changed. + */ +STDMETHODIMP +HSPClientAdviceSink::OnPosLength(UINT32 ulPosition, + UINT32 ulLength) +{ + if (m_splayer->bEnableAdviceSink) + { + m_splayer->print2stdout("OnPosLength(%ld, %ld)\n", ulPosition, ulLength); + } + m_position = ulPosition; + m_duration = ulLength; + + return HXR_OK; +} + +/************************************************************************ + * Method: + * IHXClientAdviseSink::OnPresentationOpened + * Purpose: + * Called to advise the client a presentation has been opened. + */ +STDMETHODIMP HSPClientAdviceSink::OnPresentationOpened() +{ +/* + if (m_splayer && m_splayer->xf().crossfading && m_lClientIndex == m_splayer->xf().toIndex) + { + m_splayer->print2stderr("Crossfading...\n"); + m_splayer->xf().toStream = 0; + m_splayer->xf().toStream = m_splayer->getAudioPlayer(m_lClientIndex)->GetAudioStream(0); + if (m_splayer->xf().toStream) + { + m_splayer->print2stderr("Got Stream 2\n"); + m_splayer->startCrossFade(); + } + else + m_splayer->stop(m_lClientIndex); + } +*/ + //m_splayer->bEnableAdviceSink = true; + + if (m_splayer->bEnableAdviceSink) + { + m_splayer->print2stdout("OnPresentationOpened()\n"); + } + + return HXR_OK; +} + + +/************************************************************************ + * Method: + * IHXClientAdviseSink::OnPresentationClosed + * Purpose: + * Called to advise the client a presentation has been closed. + */ +STDMETHODIMP HSPClientAdviceSink::OnPresentationClosed() +{ + if (m_splayer->bEnableAdviceSink) + { + m_splayer->print2stdout("OnPresentationClosed()\n"); + } + + return HXR_OK; +} + +void HSPClientAdviceSink::GetStatistics (char* pszRegistryKey) +{ + char szRegistryValue[MAX_DISPLAY_NAME] = {0}; /* Flawfinder: ignore */ + INT32 lValue = 0; + INT32 i = 0; + INT32 lStatistics = 8; + UINT32 *plValue; + + // collect statistic + for (i = 0; i < lStatistics; i++) + { + plValue = NULL; + switch (i) + { + case 0: + SafeSprintf(szRegistryValue, MAX_DISPLAY_NAME, "%s.Normal", pszRegistryKey); + break; + case 1: + SafeSprintf(szRegistryValue, MAX_DISPLAY_NAME, "%s.Recovered", pszRegistryKey); + break; + case 2: + SafeSprintf(szRegistryValue, MAX_DISPLAY_NAME, "%s.Received", pszRegistryKey); + break; + case 3: + SafeSprintf(szRegistryValue, MAX_DISPLAY_NAME, "%s.Lost", pszRegistryKey); + break; + case 4: + SafeSprintf(szRegistryValue, MAX_DISPLAY_NAME, "%s.Late", pszRegistryKey); + break; + case 5: + SafeSprintf(szRegistryValue, MAX_DISPLAY_NAME, "%s.ClipBandwidth", pszRegistryKey); + break; + case 6: + SafeSprintf(szRegistryValue, MAX_DISPLAY_NAME, "%s.AverageBandwidth", pszRegistryKey); + plValue = &m_lAverageBandwidth; + break; + case 7: + SafeSprintf(szRegistryValue, MAX_DISPLAY_NAME, "%s.CurrentBandwidth", pszRegistryKey); + plValue = &m_lCurrentBandwidth; + break; + default: + break; + } + + m_pRegistry->GetIntByName(szRegistryValue, lValue); + if (plValue) + { + if (m_bOnStop || lValue == 0) + { + lValue = *plValue; + } + else + { + *plValue = lValue; + } + } + if (m_splayer->bEnableAdviceSink || (m_splayer->bEnableVerboseMode && m_bOnStop)) + { + m_splayer->print2stdout("%s = %ld\n", szRegistryValue, lValue); + } + } +} + +void HSPClientAdviceSink::GetAllStatistics(void) +{ + UINT32 unPlayerIndex = 0; + UINT32 unSourceIndex = 0; + UINT32 unStreamIndex = 0; + + const char* pszRegistryPrefix = "Statistics"; + char szRegistryName[MAX_DISPLAY_NAME] = {0}; /* Flawfinder: ignore */ + + // display the content of whole statistic registry + if (m_pRegistry) + { + // ok, let's start from the top (player) + SafeSprintf(szRegistryName, MAX_DISPLAY_NAME, "%s.Player%ld", pszRegistryPrefix, m_lClientIndex); + if (PT_COMPOSITE == m_pRegistry->GetTypeByName(szRegistryName)) + { + // display player statistic + GetStatistics(szRegistryName); + + SafeSprintf(szRegistryName, MAX_DISPLAY_NAME, "%s.Source%ld", szRegistryName, unSourceIndex); + while (PT_COMPOSITE == m_pRegistry->GetTypeByName(szRegistryName)) + { + // display source statistic + GetStatistics(szRegistryName); + + SafeSprintf(szRegistryName, MAX_DISPLAY_NAME, "%s.Stream%ld", szRegistryName, unStreamIndex); + while (PT_COMPOSITE == m_pRegistry->GetTypeByName(szRegistryName)) + { + // display stream statistic + GetStatistics(szRegistryName); + + unStreamIndex++; + + SafeSprintf(szRegistryName, MAX_DISPLAY_NAME, "%s.Player%ld.Source%ld.Stream%ld", + pszRegistryPrefix, unPlayerIndex, unSourceIndex, unStreamIndex); + } + + unSourceIndex++; + + SafeSprintf(szRegistryName, MAX_DISPLAY_NAME, "%s.Player%ld.Source%ld", + pszRegistryPrefix, unPlayerIndex, unSourceIndex); + } + + unPlayerIndex++; + + SafeSprintf(szRegistryName, MAX_DISPLAY_NAME, "%s.Player%ld", pszRegistryPrefix, unPlayerIndex); + } + } +} + +/************************************************************************ + * Method: + * IHXClientAdviseSink::OnStatisticsChanged + * Purpose: + * Called to advise the client that the presentation statistics + * have changed. + */ +STDMETHODIMP HSPClientAdviceSink::OnStatisticsChanged(void) +{ + char szBuff[1024]; /* Flawfinder: ignore */ + HX_RESULT res = HXR_OK; + UINT16 uPlayer = 0; + + //if(m_splayer->bEnableAdviceSink) + { + if(m_splayer->bEnableAdviceSink) + m_splayer->print2stdout("OnStatisticsChanged():\n"); + + SafeSprintf(szBuff, 1024, "Statistics.Player%u", uPlayer ); + while( HXR_OK == res ) + { + res = DumpRegTree( szBuff, uPlayer ); + if ( HXR_OK == res ) + { + //m_splayer->print2stderr("%d Title: %s\n", uPlayer, m_splayer->ppctrl[uPlayer]->md.title); + //m_splayer->print2stderr("%d Author: %s\n", uPlayer, m_splayer->ppctrl[uPlayer]->md.artist); + //m_splayer->print2stderr("%d Bitrate: %ld\n", uPlayer, m_splayer->ppctrl[uPlayer]->md.bitrate); + } + uPlayer++; + SafeSprintf(szBuff, 1024, "Statistics.Player%u", uPlayer ); + } + } + + return HXR_OK; +} + +HX_RESULT HSPClientAdviceSink::DumpRegTree(const char* pszTreeName, UINT16 index ) +{ + const char* pszName = NULL; + ULONG32 ulRegID = 0; + HX_RESULT res = HXR_OK; + INT32 nVal = 0; + IHXBuffer* pBuff = NULL; + IHXValues* pValues = NULL; + + int len; + bool title = false; + bool bw = false; + bool author = false; + + //See if the name exists in the reg tree. + res = m_pRegistry->GetPropListByName( pszTreeName, pValues); + if( HXR_OK!=res || !pValues ) + return HXR_FAIL; + + //make sure this is a PT_COMPOSITE type reg entry. + if( PT_COMPOSITE != m_pRegistry->GetTypeByName(pszTreeName)) + return HXR_FAIL; + + //Print out the value of each member of this tree. + res = pValues->GetFirstPropertyULONG32( pszName, ulRegID ); + while( HXR_OK == res ) + { + title = false; + bw = false; + author = false; + + len = strlen(pszName); + len -= strlen("Title"); + if (len > 0 && !strcmp(&pszName[len], "Title")) + title = true; + len += strlen("Title"); + len -= strlen("Author"); + if (len > 0 && !strcmp(&pszName[len], "Author")) + author = true; + len += strlen("Author"); + len -= strlen("AverageBandwidth"); + if (len > 0 && !strcmp(&pszName[len], "AverageBandwidth")) + bw = true; + + //We have at least one entry. See what type it is. + HXPropType pt = m_pRegistry->GetTypeById(ulRegID); + switch(pt) + { + case PT_COMPOSITE: + DumpRegTree(pszName, index); + break; + case PT_INTEGER : + nVal = 0; + m_pRegistry->GetIntById( ulRegID, nVal ); + if(m_splayer->bEnableAdviceSink) + m_splayer->print2stdout("%s : %ld\n", pszName, nVal ); + if (bw) + m_splayer->ppctrl[index]->md.bitrate = nVal; + break; + case PT_INTREF : + nVal = 0; + m_pRegistry->GetIntById( ulRegID, nVal ); + if(m_splayer->bEnableAdviceSink) + m_splayer->print2stdout("%s : %ld\n", pszName, nVal ); + if (bw) + m_splayer->ppctrl[index]->md.bitrate = nVal; + break; + case PT_STRING : + pBuff = NULL; + m_pRegistry->GetStrById( ulRegID, pBuff ); + if(m_splayer->bEnableAdviceSink) + m_splayer->print2stdout("%s : \"", pszName ); + if( pBuff ) + if(m_splayer->bEnableAdviceSink) + m_splayer->print2stdout("%s", (const char *)(pBuff->GetBuffer()) ); + if(m_splayer->bEnableAdviceSink) + m_splayer->print2stdout("\"\n" ); + + if (title && pBuff) + { + strncpy(m_splayer->ppctrl[index]->md.title, (const char *) (pBuff->GetBuffer()), 512); + m_splayer->ppctrl[index]->md.title[511] = '\0'; + } + if (author && pBuff) + { + strncpy(m_splayer->ppctrl[index]->md.artist, (const char *) (pBuff->GetBuffer()), 512); + m_splayer->ppctrl[index]->md.artist[511] = '\0'; + } + HX_RELEASE(pBuff); + break; + case PT_BUFFER : + if(m_splayer->bEnableAdviceSink) + m_splayer->print2stdout("%s %ld : BUFFER TYPE NOT SHOWN\n", + pszName, nVal ); + break; + case PT_UNKNOWN: + if(m_splayer->bEnableAdviceSink) + m_splayer->print2stdout("%s Unkown registry type entry\n", pszName ); + break; + default: + if(m_splayer->bEnableAdviceSink) + m_splayer->print2stdout("%s Unkown registry type entry\n", pszName ); + break; + } + res = pValues->GetNextPropertyULONG32( pszName, ulRegID); + } + + HX_RELEASE( pValues ); + + return HXR_OK; +} + + +/************************************************************************ + * Method: + * IHXClientAdviseSink::OnPreSeek + * Purpose: + * Called by client engine to inform the client that a seek is + * about to occur. The render is informed the last time for the + * stream's time line before the seek, as well as the first new + * time for the stream's time line after the seek will be completed. + * + */ +STDMETHODIMP HSPClientAdviceSink::OnPreSeek( ULONG32 ulOldTime, + ULONG32 ulNewTime) +{ +#if !defined(__TCS__) + if (m_splayer->bEnableAdviceSink) + { + m_splayer->print2stdout("OnPreSeek(%ld, %ld)\n", ulOldTime, ulNewTime); + } +#endif + + return HXR_OK; +} + + +/************************************************************************ + * Method: + * IHXClientAdviseSink::OnPostSeek + * Purpose: + * Called by client engine to inform the client that a seek has + * just occurred. The render is informed the last time for the + * stream's time line before the seek, as well as the first new + * time for the stream's time line after the seek. + * + */ +STDMETHODIMP HSPClientAdviceSink::OnPostSeek( ULONG32 ulOldTime, + ULONG32 ulNewTime) +{ + if (m_splayer->bEnableAdviceSink) + { + m_splayer->print2stdout("OnPostSeek(%ld, %ld)\n", ulOldTime, ulNewTime); + } + + return HXR_OK; +} + + +/************************************************************************ + * Method: + * IHXClientAdviseSink::OnStop + * Purpose: + * Called by client engine to inform the client that a stop has + * just occurred. + * + */ +STDMETHODIMP HSPClientAdviceSink::OnStop(void) +{ + HXTimeval now; + + if (m_splayer->bEnableAdviceSink) + { + m_splayer->print2stdout("OnStop()\n"); + } + + if (m_splayer->bEnableVerboseMode) + { + m_splayer->print2stdout("Player %ld stopped.\n", m_lClientIndex); + m_bOnStop = true; + GetAllStatistics(); + } + + // Find out the current time and subtract the beginning time to + // figure out how many seconds we played + now = m_pScheduler->GetCurrentSchedulerTime(); + m_ulStopTime = now.tv_sec; + + m_splayer->m_ulNumSecondsPlayed = m_ulStopTime - m_ulStartTime; + + m_position = m_duration = 0; + return HXR_OK; +} + +/************************************************************************ + * Method: + * IHXClientAdviseSink::OnPause + * Purpose: + * Called by client engine to inform the client that a pause has + * just occurred. The render is informed the last time for the + * stream's time line before the pause. + * + */ +STDMETHODIMP HSPClientAdviceSink::OnPause(ULONG32 ulTime) +{ + if (m_splayer->bEnableAdviceSink) + { + m_splayer->print2stdout("OnPause(%ld)\n", ulTime); + } + + return HXR_OK; +} + + +/************************************************************************ + * Method: + * IHXClientAdviseSink::OnBegin + * Purpose: + * Called by client engine to inform the client that a begin or + * resume has just occurred. The render is informed the first time + * for the stream's time line after the resume. + * + */ +STDMETHODIMP HSPClientAdviceSink::OnBegin(ULONG32 ulTime) +{ + HXTimeval now; + +#if !defined(__TCS__) + if (m_splayer->bEnableAdviceSink) + { + m_splayer->print2stdout("OnBegin(%ld)\n", ulTime); + } + + if (m_splayer->bEnableVerboseMode) + { + m_splayer->print2stdout("Player %ld beginning playback...\n", m_lClientIndex); + } +#endif + + // Record the current time, so we can figure out many seconds we played + now = m_pScheduler->GetCurrentSchedulerTime(); + m_ulStartTime = now.tv_sec; + + return HXR_OK; +} + + +/************************************************************************ + * Method: + * IHXClientAdviseSink::OnBuffering + * Purpose: + * Called by client engine to inform the client that buffering + * of data is occurring. The render is informed of the reason for + * the buffering (start-up of stream, seek has occurred, network + * congestion, etc.), as well as percentage complete of the + * buffering process. + * + */ +STDMETHODIMP HSPClientAdviceSink::OnBuffering(ULONG32 ulFlags, + UINT16 unPercentComplete) +{ + if (m_splayer->bEnableAdviceSink) + { + m_splayer->print2stdout("OnBuffering(%ld, %d)\n", ulFlags, unPercentComplete); + } + m_splayer->onBuffering(unPercentComplete); + + return HXR_OK; +} + + +/************************************************************************ + * Method: + * IHXClientAdviseSink::OnContacting + * Purpose: + * Called by client engine to inform the client is contacting + * hosts(s). + * + */ +STDMETHODIMP HSPClientAdviceSink::OnContacting(const char* pHostName) +{ + if (m_splayer->bEnableAdviceSink) + { + m_splayer->print2stdout("OnContacting(\"%s\")\n", pHostName); + } + m_splayer->onContacting(pHostName); + return HXR_OK; +} + diff --git a/amarok/src/engine/helix/helix-sp/hspadvisesink.h b/amarok/src/engine/helix/helix-sp/hspadvisesink.h new file mode 100644 index 00000000..6816e75a --- /dev/null +++ b/amarok/src/engine/helix/helix-sp/hspadvisesink.h @@ -0,0 +1,203 @@ +/* + * + * This software is released under the provisions of the GPL version 2. + * see file "COPYING". If that file is not available, the full statement + * of the license can be found at + * + * http://www.fsf.org/licensing/licenses/gpl.txt + * + * Portions Copyright (c) 1995-2002 RealNetworks, Inc. All Rights Reserved. + * Portions (c) Paul Cifarelli 2005 + * + */ + +#ifndef _HSPADVISESINK_ +#define _HSPADVISESINK_ + +struct IHXClientAdviseSink; +struct IUnknown; +struct IHXRegistry; +struct IHXScheduler; +class HelixSimplePlayer; + +class HSPClientAdviceSink : public IHXClientAdviseSink +{ + private: + HelixSimplePlayer *m_splayer; + LONG32 m_lRefCount; + LONG32 m_lClientIndex; + + IUnknown* m_pUnknown; + IHXRegistry* m_pRegistry; + IHXScheduler* m_pScheduler; + + UINT32 m_ulStartTime; + UINT32 m_ulStopTime; + + UINT32 m_position; + UINT32 m_duration; + + UINT32 m_lCurrentBandwidth; + UINT32 m_lAverageBandwidth; + BOOL m_bOnStop; + + HX_RESULT DumpRegTree(const char* pszTreeName, UINT16 index ); + + //PRIVATE_DESTRUCTORS_ARE_NOT_A_CRIME + + void GetStatistics (char* pszRegistryKey); + void GetAllStatistics (void); + + public: + + HSPClientAdviceSink(IUnknown* pUnknown, LONG32 lClientIndex, HelixSimplePlayer *pSplay); + virtual ~HSPClientAdviceSink(); + + UINT32 position() { return m_position; } + UINT32 duration() { return m_duration; } + UINT32 currentBW() { return m_lCurrentBandwidth; } + UINT32 averageBW() { return m_lAverageBandwidth; } + + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj); + + STDMETHOD_(ULONG32,AddRef) (THIS); + + STDMETHOD_(ULONG32,Release) (THIS); + + /* + * IHXClientAdviseSink methods + */ + + /************************************************************************ + * Method: + * IHXClientAdviseSink::OnPosLength + * Purpose: + * Called to advise the client that the position or length of the + * current playback context has changed. + */ + STDMETHOD(OnPosLength) (THIS_ + UINT32 ulPosition, + UINT32 ulLength); + + /************************************************************************ + * Method: + * IHXClientAdviseSink::OnPresentationOpened + * Purpose: + * Called to advise the client a presentation has been opened. + */ + STDMETHOD(OnPresentationOpened) (THIS); + + /************************************************************************ + * Method: + * IHXClientAdviseSink::OnPresentationClosed + * Purpose: + * Called to advise the client a presentation has been closed. + */ + STDMETHOD(OnPresentationClosed) (THIS); + + /************************************************************************ + * Method: + * IHXClientAdviseSink::OnStatisticsChanged + * Purpose: + * Called to advise the client that the presentation statistics + * have changed. + */ + STDMETHOD(OnStatisticsChanged) (THIS); + + /************************************************************************ + * Method: + * IHXClientAdviseSink::OnPreSeek + * Purpose: + * Called by client engine to inform the client that a seek is + * about to occur. The render is informed the last time for the + * stream's time line before the seek, as well as the first new + * time for the stream's time line after the seek will be completed. + * + */ + STDMETHOD (OnPreSeek) (THIS_ + ULONG32 ulOldTime, + ULONG32 ulNewTime); + + /************************************************************************ + * Method: + * IHXClientAdviseSink::OnPostSeek + * Purpose: + * Called by client engine to inform the client that a seek has + * just occurred. The render is informed the last time for the + * stream's time line before the seek, as well as the first new + * time for the stream's time line after the seek. + * + */ + STDMETHOD (OnPostSeek) (THIS_ + ULONG32 ulOldTime, + ULONG32 ulNewTime); + + /************************************************************************ + * Method: + * IHXClientAdviseSink::OnStop + * Purpose: + * Called by client engine to inform the client that a stop has + * just occurred. + * + */ + STDMETHOD (OnStop) (THIS); + + /************************************************************************ + * Method: + * IHXClientAdviseSink::OnPause + * Purpose: + * Called by client engine to inform the client that a pause has + * just occurred. The render is informed the last time for the + * stream's time line before the pause. + * + */ + STDMETHOD (OnPause) (THIS_ + ULONG32 ulTime); + + /************************************************************************ + * Method: + * IHXClientAdviseSink::OnBegin + * Purpose: + * Called by client engine to inform the client that a begin or + * resume has just occurred. The render is informed the first time + * for the stream's time line after the resume. + * + */ + STDMETHOD (OnBegin) (THIS_ + ULONG32 ulTime); + + /************************************************************************ + * Method: + * IHXClientAdviseSink::OnBuffering + * Purpose: + * Called by client engine to inform the client that buffering + * of data is occurring. The render is informed of the reason for + * the buffering (start-up of stream, seek has occurred, network + * congestion, etc.), as well as percentage complete of the + * buffering process. + * + */ + STDMETHOD (OnBuffering) (THIS_ + ULONG32 ulFlags, + UINT16 unPercentComplete); + + + /************************************************************************ + * Method: + * IHXClientAdviseSink::OnContacting + * Purpose: + * Called by client engine to inform the client is contacting + * hosts(s). + * + */ + STDMETHOD (OnContacting) (THIS_ + const char* pHostName); + +}; + +#endif /* _EXAMPLECLSNK_ */ diff --git a/amarok/src/engine/helix/helix-sp/hspalsadevice.cpp b/amarok/src/engine/helix/helix-sp/hspalsadevice.cpp new file mode 100644 index 00000000..38eadd79 --- /dev/null +++ b/amarok/src/engine/helix/helix-sp/hspalsadevice.cpp @@ -0,0 +1,2204 @@ +/****************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin St, 5th fl, Boston, MA 02110-1301, * + * USA, or check http://www.fsf.org/about/contact.html * + * * + * Portions Copyright (c) 1995-2004 RealNetworks, Inc. All Rights Reserved. * + * Portions Copyright (c) 2005 Paul Cifarelli * + * * + ******************************************************************************/ + +#include <unistd.h> +#include <fcntl.h> +#include <stdlib.h> +#include <errno.h> +#include <sys/ioctl.h> + +#include <stdio.h> +#include <math.h> +#include <time.h> +#include <sys/time.h> +#include <unistd.h> + +#include <config.h> + +#include "hxcomm.h" +#include "hxcore.h" +#include "hxprefs.h" +#include "hxstrutl.h" +#include "hxvsrc.h" +#include "hxresult.h" +#include "hxausvc.h" +#include "helix-sp.h" + +#include "ihxpckts.h" +#include "hxprefs.h" +#include "hspalsadevice.h" + +#ifdef HX_LOG_SUBSYSTEM +#include "hxtlogutil.h" +#include "ihxtlogsystem.h" +#endif + +#include "dllpath.h" + +#include "hxbuffer.h" + +#ifdef USE_HELIX_ALSA + +IHXPreferences* z_pIHXPrefs = 0; +#define RA_AOE_NOERR 0 +#define RA_AOE_GENERAL -1 +#define RA_AOE_DEVNOTOPEN -2 +#define RA_AOE_NOTENABLED -3 +#define RA_AOE_BADFORMAT -4 +#define RA_AOE_NOTSUPPORTED -5 +#define RA_AOE_DEVBUSY -6 +#define RA_AOE_BADOPEN -7 + +#ifdef __FreeBSD__ +#define PTHREAD_MUTEX_FAST_NP PTHREAD_MUTEX_NORMAL +#endif + +#if !defined(__NetBSD__) && !defined(__OpenBSD__) + #include <sys/soundcard.h> +#else + #include <soundcard.h> +#endif + +typedef HX_RESULT (HXEXPORT_PTR FPRMSETDLLACCESSPATH) (const char*); + + +AudioQueue::AudioQueue( const HXAudioData *buf) : fwd(0) +{ + ad = *buf; + ad.pData->AddRef(); +} + +AudioQueue::~AudioQueue() +{ + ad.pData->Release(); +} + + +STDMETHODIMP +HSPAudioDevice::QueryInterface(REFIID riid, void**ppvObj) +{ + if(IsEqualIID(riid, IID_IUnknown)) + { + AddRef(); + *ppvObj = (IUnknown*)(IHXAudioDevice *)this; + return HXR_OK; + } + else if(IsEqualIID(riid, IID_IHXAudioDevice)) + { + AddRef(); + *ppvObj = (IHXAudioDevice *)this; + return HXR_OK; + } + *ppvObj = NULL; + return HXR_NOINTERFACE; +} + +STDMETHODIMP_(UINT32) +HSPAudioDevice::AddRef() +{ + return InterlockedIncrement(&m_lRefCount); +} + +STDMETHODIMP_(UINT32) +HSPAudioDevice::Release() +{ + if (InterlockedDecrement(&m_lRefCount) > 0) + { + return m_lRefCount; + } + + delete this; + return 0; +} + +STDMETHODIMP +HSPAudioDevice::CheckFormat( const HXAudioFormat* pAudioFormat ) +{ + m_Player->print2stderr("########## Got to HSPAudioDevice::CheckFormat\n"); + + return (_CheckFormat(pAudioFormat)); +} + +STDMETHODIMP +HSPAudioDevice::Close( const BOOL bFlush ) +{ + m_Player->print2stderr("########## Got to HSPAudioDevice::Close flush %d\n", bFlush); + + pthread_mutex_lock(&m_m); + + if (bFlush) + { + clearQueue(); + _Drain(); + } + + _Reset(); + _CloseAudio(); + _CloseMixer(); + + m_closed = true; + + m_ulCurrentTime = m_ulQTime = 0; + + if (m_pStreamResponse) + m_pStreamResponse->Release(); + + + pthread_mutex_unlock(&m_m); + + return 0; +} + +STDMETHODIMP +HSPAudioDevice::Drain() +{ + m_Player->print2stderr("########## Got to HSPAudioDevice::Drain\n"); + pthread_mutex_lock(&m_m); + + LONG32 err = _Drain(); + clearQueue(); + pthread_mutex_unlock(&m_m); + return err; +} + +STDMETHODIMP +HSPAudioDevice::GetCurrentAudioTime( REF(ULONG32) ulCurrentTime ) +{ + //m_Player->print2stderr("########## Got to HSPAudioDevice::GetCurrentTime = %d\n", m_ulCurrentTime); + + int err = 0; + snd_pcm_sframes_t frame_delay = 0; + + pthread_mutex_lock(&m_m); + if (!m_closed) + { + err = snd_pcm_delay (m_pAlsaPCMHandle, &frame_delay); + if (err < 0) + { +#ifdef HX_LOG_SUBSYSTEM + HXLOGL1 ( HXLOG_ADEV, "snd_pcm_status: %s", snd_strerror(err)); +#endif + m_Player->print2stderr("########## HSPAudioDevice::GetCurrentAudioTime error getting frame_delay: %s\n", snd_strerror(err)); + pthread_mutex_unlock(&m_m); + return -1; + } + + ulCurrentTime = m_ulCurrentTime - (ULONG32)(((double)frame_delay * 1000.0) / (double)m_unSampleRate); + + //m_Player->print2stderr("########## HSPAudioDevice::GetCurrentAudioTime %d %d\n", ulCurrentTime, m_ulCurrentTime); + } + pthread_mutex_unlock(&m_m); + + return 0; +} + +STDMETHODIMP_(UINT16) +HSPAudioDevice::GetVolume() +{ + m_Player->print2stderr("########## Got to HSPAudioDevice::GetVolume\n"); + return 0; +} + +STDMETHODIMP_(BOOL) + HSPAudioDevice::InitVolume(const UINT16 /*uMinVolume*/, const UINT16 /*uMaxVolume*/) +{ + m_Player->print2stderr("########## Got to HSPAudioDevice::InitVolume\n"); + return true; +} + +STDMETHODIMP +HSPAudioDevice::Open(const HXAudioFormat* pAudioFormat, IHXAudioDeviceResponse* pStreamResponse) +{ + int err; + + m_Player->print2stderr("########## Got to HSPAudioDevice::Open\n"); + if (pStreamResponse) + pStreamResponse->AddRef(); + + pthread_mutex_lock(&m_m); + + m_drain = false; + m_closed = false; + m_ulTotalWritten = 0; + m_ulCurrentTime = 0; + m_SWPause = false; + m_pStreamResponse = pStreamResponse; + if (!m_pAlsaPCMHandle) + { + err = _OpenAudio(); + if (err) m_Player->print2stderr("########## HSPAudioDevice::Open error (device) %d\n", err); + err = SetDeviceConfig(pAudioFormat); + if (err) m_Player->print2stderr("########## HSPAudioDevice::Open error (config) %d\n", err); + m_ulCurrentTime = m_ulLastTime = m_ulQTime = 0; + } + + if (m_pAlsaMixerHandle != NULL) + { + err = _OpenMixer(); + if (err) m_Player->print2stderr("########## HSPAudioDevice::Open error (mixer) %d\n", err); + } + + pthread_mutex_unlock(&m_m); + + return 0; +} + +STDMETHODIMP +HSPAudioDevice::Pause() +{ + m_Player->print2stderr("########## Got to HSPAudioDevice::Pause %d\n", m_bHasHardwarePauseAndResume); + _Pause(); + return 0; +} + +STDMETHODIMP +HSPAudioDevice::Reset() +{ + m_Player->print2stderr("########## Got to HSPAudioDevice::Reset\n"); + return (_Reset()); +} + +STDMETHODIMP +HSPAudioDevice::Resume() +{ + m_Player->print2stderr("########## Got to HSPAudioDevice::Resume\n"); + _Resume(); + + return 0; +} + +STDMETHODIMP +HSPAudioDevice::SetVolume( const UINT16 /*uVolume*/ ) +{ + m_Player->print2stderr("########## Got to HSPAudioDevice::SetVolume\n"); + return 0; +} + +STDMETHODIMP +HSPAudioDevice::Write( const HXAudioData* pAudioData ) +{ + addBuf( new AudioQueue( pAudioData ) ); + return 0; +} + +int HSPAudioDevice::sync() +{ + if (m_pStreamResponse) + { + ULONG32 curtime; + if (!GetCurrentAudioTime(curtime) && curtime) + return m_pStreamResponse->OnTimeSync(curtime); + else + { + // probably a seek occurred + //clearQueue(); + _Reset(); + } + } + return -1; +} + + +HX_RESULT HSPAudioDevice::OnTimeSync() +{ + HX_RESULT err; + + if (!(err = sync())) + return HXR_OK; + + return err; +} + +int +HSPAudioDevice::_Write( const HXAudioData* pAudioData ) +{ + unsigned long len; + long bytes; + unsigned char *data; + int err = 0; + + pAudioData->pData->Get(data, len); + + // if the time of this buf is earlier than the last, or the time between this buf and the last is > 1 buffer's worth, this was a seek + if ( pAudioData->ulAudioTime < m_ulCurrentTime || + pAudioData->ulAudioTime - m_ulCurrentTime > (1000 * len) / (m_unNumChannels * m_unSampleRate) + 1 ) + { + m_Player->print2stderr("########## seek detected %ld %ld, len = %ld %d\n", m_ulCurrentTime, pAudioData->ulAudioTime, len, + abs(pAudioData->ulAudioTime - (m_ulCurrentTime + (1000 * len) / (m_unNumChannels * m_unSampleRate)))); + //_Reset(); + //clearQueue(); + } + + if (!err) + { + err = WriteBytes(data, len, bytes); + m_ulCurrentTime = pAudioData->ulAudioTime; + } + err = sync(); + + //m_Player->print2stderr("########## %d %d\n", m_ulCurrentTime,pAudioData->ulAudioTime); + + //m_Player->print2stderr("########## Got to HSPAudioDevice::Write len=%d byteswriten=%d err=%d time=%d\n", + // len,bytes,err,m_ulCurrentTime); + + return err; +} + + +//------------------------------------------ +// Ctors and Dtors. +//------------------------------------------ +HSPAudioDevice::HSPAudioDevice(HelixSimplePlayer *player, const char *device) : + m_pAlsaPCMHandle (NULL), + m_pAlsaMixerHandle (NULL), + m_pAlsaMixerElem (NULL), + + m_pPCMDeviceName (NULL), + m_pMixerDeviceName (NULL), + m_pMixerElementName (NULL), + + m_bHasHardwarePauseAndResume (FALSE), + m_nBytesPlayedBeforeLastTrigger(0), + + m_nLastBytesPlayed(0), + + m_bGotInitialTrigger(FALSE), + m_bUseMMAPTStamps(TRUE), + m_lRefCount(0), + m_wLastError(0), + m_SWPause(false), + m_Player(player), + m_done(false), + m_drain(false), + m_closed(true), + m_head(0), + m_tail(0) +{ + pthread_mutexattr_t ma; + + pthread_mutexattr_init(&ma); + pthread_mutexattr_settype(&ma, PTHREAD_MUTEX_FAST_NP); // note this is not portable outside linux and a few others + pthread_mutex_init(&m_m, &ma); + + pthread_cond_init(&m_cv, NULL); + + // create thread that will wait for buffers to appear to send to the device + pthread_create(&m_thrid, 0, writerThread, this); + + if (device) + { + int len = strlen( device ); + m_Player->pCommonClassFactory->CreateInstance(CLSID_IHXBuffer, (void **) &m_pPCMDeviceName); + if (m_pPCMDeviceName) + m_pPCMDeviceName->Set( (const unsigned char*) device, len + 1 ); + } +} + +HSPAudioDevice::~HSPAudioDevice() +{ + pthread_mutex_lock(&m_m); + m_done = true; + pthread_mutex_unlock(&m_m); + pthread_cond_signal(&m_cv); + void *tmp; + pthread_join(m_thrid, &tmp); + + + if(m_pPCMDeviceName) + { + HX_RELEASE(m_pPCMDeviceName); + } + + if(m_pMixerDeviceName) + { + HX_RELEASE(m_pMixerDeviceName); + } + + if(m_pMixerElementName) + { + HX_RELEASE(m_pMixerElementName); + } + + pthread_cond_destroy(&m_cv); + pthread_mutex_destroy(&m_m); +} + +void HSPAudioDevice::addBuf(struct AudioQueue *item) +{ + pthread_mutex_lock(&m_m); + + m_ulQTime = item->ad.ulAudioTime; + if (m_tail) + { + item->fwd = 0; + m_tail->fwd = item; + m_tail = item; + } + else + { + item->fwd = 0; + m_head = item; + m_tail = item; + } + + pthread_mutex_unlock(&m_m); + pthread_cond_signal(&m_cv); +} + +AudioQueue *HSPAudioDevice::getBuf() +{ + pthread_mutex_lock(&m_m); + + AudioQueue *item = m_head; + + if (item) + { + m_head = item->fwd; + if (!m_head) + m_tail = 0; + } + + pthread_mutex_unlock(&m_m); + + return item; +} + +// NOTE THAT THIS IS NOT UNDER LOCK, AND SHOULD ONLY BE CALLED WITH THE MUTEX LOCKED +void HSPAudioDevice::clearQueue() +{ + AudioQueue *item; + + if (!m_tail) + return; + + while (m_tail) + { + item = m_head; + m_head = item->fwd; + if (!m_head) + m_tail = 0; + delete item; + } +} + + +void *HSPAudioDevice::writerThread( void *arg ) +{ + HSPAudioDevice *thisObj = (HSPAudioDevice *) arg; + AudioQueue *item; + + pthread_mutex_lock(&thisObj->m_m); + while (!thisObj->m_done) + { + pthread_mutex_unlock(&thisObj->m_m); + item = thisObj->getBuf(); + + if (item) + thisObj->_Write(&item->ad); + + delete item; + + pthread_mutex_lock(&thisObj->m_m); + if (!thisObj->m_tail) + pthread_cond_wait(&thisObj->m_cv, &thisObj->m_m); + } + pthread_mutex_unlock(&thisObj->m_m); + + thisObj->m_Player->print2stderr("############ writerThread exit\n"); + return 0; +} + + +// These Device Specific methods must be implemented +// by the platform specific sub-classes. +INT16 HSPAudioDevice::GetAudioFd(void) +{ + //Not implemented. + return -1; +} + + +//Device specific methods to open/close the mixer and audio devices. +HX_RESULT HSPAudioDevice::_OpenAudio() +{ + int err = 0; + const char* szDevice; + + HX_ASSERT (m_pAlsaPCMHandle == NULL); + if (m_pAlsaPCMHandle) + { + m_wLastError = RA_AOE_BADOPEN; + return m_wLastError; + } + + if(z_pIHXPrefs) + { + HX_RELEASE(m_pPCMDeviceName); + z_pIHXPrefs->ReadPref("AlsaPCMDeviceName", m_pPCMDeviceName); + } + + if(!m_pPCMDeviceName) + { + const char szDefaultDevice[] = "default"; + + m_Player->pCommonClassFactory->CreateInstance(CLSID_IHXBuffer, (void **) &m_pPCMDeviceName); + if (m_pPCMDeviceName) + m_pPCMDeviceName->Set( (const unsigned char*) szDefaultDevice, sizeof(szDefaultDevice) ); + } + + szDevice = (const char*) m_pPCMDeviceName->GetBuffer(); + m_Player->print2stderr("########### Opening ALSA PCM device %s\n", szDevice); + +#ifdef HX_LOG_SUBSYSTEM + HXLOGL2 (HXLOG_ADEV, "Opening ALSA PCM device %s", + szDevice); +#endif + + err = snd_pcm_open( &m_pAlsaPCMHandle, + szDevice, + SND_PCM_STREAM_PLAYBACK, + 0); + if(err < 0) + { + m_Player->print2stderr("########### snd_pcm_open: %s %s\n", szDevice, snd_strerror (err)); +#ifdef HX_LOG_SUBSYSTEM + HXLOGL1 ( HXLOG_ADEV, "snd_pcm_open: %s", + szDevice, snd_strerror (err)); +#endif + + m_wLastError = RA_AOE_BADOPEN; + } + + if(err == 0) + { + err = snd_pcm_nonblock(m_pAlsaPCMHandle, TRUE); + if(err < 0) + { + m_Player->print2stderr("########## snd_pcm_nonblock: %s\n", snd_strerror (err)); +#ifdef HX_LOG_SUBSYSTEM + HXLOGL1 ( HXLOG_ADEV, "snd_pcm_nonblock: %s", + snd_strerror (err)); +#endif + m_wLastError = RA_AOE_BADOPEN; + } + } + + if(err == 0) + { + m_Player->print2stderr("########## return from OpenAudio\n"); + m_wLastError = RA_AOE_NOERR; + } + else + { + if(m_pAlsaPCMHandle) + { + snd_pcm_close(m_pAlsaPCMHandle); + m_pAlsaPCMHandle = NULL; + } + } + + return m_wLastError; +} + + +HX_RESULT HSPAudioDevice::_CloseAudio() +{ + if (!m_pAlsaPCMHandle) + { + m_wLastError = RA_AOE_DEVNOTOPEN; + return m_wLastError; + } + +#ifdef HX_LOG_SUBSYSTEM + HXLOGL2 (HXLOG_ADEV, "Closing ALSA PCM device"); +#endif + + snd_pcm_close(m_pAlsaPCMHandle); + m_pAlsaPCMHandle = NULL; + m_wLastError = RA_AOE_NOERR; + + return m_wLastError; +} + + +HX_RESULT HSPAudioDevice::_OpenMixer() +{ + int err; + const char* szDeviceName = NULL; + const char* szElementName = NULL; + int nElementIndex = 0; + + HX_ASSERT (m_pAlsaMixerHandle == NULL); + if (m_pAlsaMixerHandle != NULL) + { + m_wLastError = RA_AOE_BADOPEN; + return m_wLastError; + } + + HX_ASSERT(m_pAlsaMixerElem == NULL); + if (m_pAlsaMixerElem != NULL) + { + m_wLastError = RA_AOE_BADOPEN; + return m_wLastError; + } + + if(z_pIHXPrefs) + { + HX_RELEASE(m_pMixerDeviceName); + z_pIHXPrefs->ReadPref("AlsaMixerDeviceName", m_pMixerDeviceName); + } + + if(!m_pMixerDeviceName) + { + const char szDefaultDevice[] = "default"; + + m_Player->pCommonClassFactory->CreateInstance(CLSID_IHXBuffer, (void **) &m_pMixerDeviceName); + if (m_pMixerDeviceName) + m_pMixerDeviceName->Set( (const unsigned char*) szDefaultDevice, sizeof(szDefaultDevice) ); + } + + if(z_pIHXPrefs) + { + HX_RELEASE(m_pMixerElementName); + z_pIHXPrefs->ReadPref("AlsaMixerElementName", m_pMixerElementName); + } + + if(!m_pMixerElementName) + { + const char szDefaultElement[] = "PCM"; + + m_Player->pCommonClassFactory->CreateInstance(CLSID_IHXBuffer, (void **) &m_pMixerElementName); + if (m_pMixerElementName) + m_pMixerElementName->Set( (const unsigned char*) szDefaultElement, sizeof(szDefaultElement) ); + } + + if(z_pIHXPrefs) + { + IHXBuffer* pElementIndex = NULL; + z_pIHXPrefs->ReadPref("AlsaMixerElementIndex", pElementIndex); + if(pElementIndex) + { + const char* szElementIndex = (const char*) pElementIndex->GetBuffer(); + nElementIndex = atoi(szElementIndex); + + HX_RELEASE(pElementIndex); + } + } + + szDeviceName = (const char*) m_pMixerDeviceName->GetBuffer();; + szElementName = (const char*) m_pMixerElementName->GetBuffer(); + +#ifdef HX_LOG_SUBSYSTEM + HXLOGL2 (HXLOG_ADEV, "Opening ALSA mixer device %s", + szDeviceName); +#endif + + err = snd_mixer_open(&m_pAlsaMixerHandle, 0); + if (err < 0) + { +#ifdef HX_LOG_SUBSYSTEM + HXLOGL1 ( HXLOG_ADEV, "snd_mixer_open: %s", + snd_strerror (err)); +#endif + + m_wLastError = RA_AOE_BADOPEN; + } + + if (err == 0) + { + err = snd_mixer_attach(m_pAlsaMixerHandle, szDeviceName); + if (err < 0) + { +#ifdef HX_LOG_SUBSYSTEM + HXLOGL1 ( HXLOG_ADEV, "snd_mixer_attach: %s", + snd_strerror (err)); +#endif + + m_wLastError = RA_AOE_BADOPEN; + } + } + + if (err == 0) + { + err = snd_mixer_selem_register(m_pAlsaMixerHandle, NULL, NULL); + if (err < 0) + { +#ifdef HX_LOG_SUBSYSTEM + HXLOGL1 ( HXLOG_ADEV, "snd_mixer_selem_register: %s", + snd_strerror (err)); +#endif + m_wLastError = RA_AOE_BADOPEN; + } + } + + if (err == 0) + { + err = snd_mixer_load(m_pAlsaMixerHandle); + if(err < 0 ) + { +#ifdef HX_LOG_SUBSYSTEM + HXLOGL1 ( HXLOG_ADEV, "snd_mixer_load: %s", + snd_strerror (err)); +#endif + + m_wLastError = RA_AOE_NOTENABLED; + } + } + + if (err == 0) + { + /* Find the mixer element */ + snd_mixer_elem_t* fallback_elem = NULL; + snd_mixer_elem_t* elem = snd_mixer_first_elem (m_pAlsaMixerHandle); + snd_mixer_elem_type_t type; + const char* elem_name = NULL; + snd_mixer_selem_id_t *sid = NULL; + int index; + + snd_mixer_selem_id_alloca(&sid); + + while (elem) + { + type = snd_mixer_elem_get_type(elem); + if (type == SND_MIXER_ELEM_SIMPLE) + { + snd_mixer_selem_get_id(elem, sid); + + /* We're only interested in playback volume controls */ + if(snd_mixer_selem_has_playback_volume(elem) && + !snd_mixer_selem_has_common_volume(elem)) + { + if (!fallback_elem) + { + fallback_elem = elem; + } + + elem_name = snd_mixer_selem_id_get_name (sid); + index = snd_mixer_selem_id_get_index(sid); + if (strcmp(elem_name, szElementName) == 0 && + index == nElementIndex) + { + break; + } + } + } + + elem = snd_mixer_elem_next(elem); + } + + if (!elem && fallback_elem) + { + elem = fallback_elem; + elem_name = NULL; + type = snd_mixer_elem_get_type(elem); + + if (type == SND_MIXER_ELEM_SIMPLE) + { + snd_mixer_selem_get_id(elem, sid); + elem_name = snd_mixer_selem_id_get_name (sid); + } + +#ifdef HX_LOG_SUBSYSTEM + HXLOGL1 ( HXLOG_ADEV, "Could not find element %s, using element %s instead", + m_pMixerElementName, elem_name? elem_name: "unknown"); +#endif + } + else if (!elem) + { +#ifdef HX_LOG_SUBSYSTEM + HXLOGL1 ( HXLOG_ADEV, "Could not find a usable mixer element", + snd_strerror (err)); +#endif + m_wLastError = RA_AOE_BADOPEN; + err = -1; + } + + m_pAlsaMixerElem = elem; + } + + if(err == 0) + { + if (m_pAlsaMixerHandle) + { + m_bMixerPresent = 1; + _GetVolume(); + } + else + { + m_bMixerPresent = 0; + } + + m_wLastError = RA_AOE_NOERR; + } + else + { + if(m_pAlsaMixerHandle) + { + snd_mixer_close(m_pAlsaMixerHandle); + m_pAlsaMixerHandle = NULL; + } + } + + return m_wLastError; +} + +HX_RESULT HSPAudioDevice::_CloseMixer() +{ + int err; + const char* szMixerDeviceName = NULL; + + if (!m_pAlsaMixerHandle) + { + m_wLastError = RA_AOE_DEVNOTOPEN; + return m_wLastError; + } + + if (!m_pMixerDeviceName) + { + m_wLastError = RA_AOE_DEVNOTOPEN; + return m_wLastError; + } + + szMixerDeviceName = (const char*) m_pMixerDeviceName->GetBuffer(); + err = snd_mixer_detach(m_pAlsaMixerHandle, szMixerDeviceName); + if (err < 0) + { +#ifdef HX_LOG_SUBSYSTEM + HXLOGL1 ( HXLOG_ADEV, "snd_mixer_detach: %s", + snd_strerror (err)); +#endif + m_wLastError = RA_AOE_GENERAL; + } + + if(err == 0) + { + err = snd_mixer_close(m_pAlsaMixerHandle); + if(err < 0) + { +#ifdef HX_LOG_SUBSYSTEM + HXLOGL1 ( HXLOG_ADEV, "snd_mixer_close: %s", + snd_strerror (err)); +#endif + m_wLastError = RA_AOE_GENERAL; + } + } + + if(err == 0) + { + m_pAlsaMixerHandle = NULL; + m_pAlsaMixerElem = NULL; + m_wLastError = RA_AOE_NOERR; + } + + return m_wLastError; +} + + +//Device specific method to set the audio device characteristics. Sample rate, +//bits-per-sample, etc. +//Method *must* set member vars. m_unSampleRate and m_unNumChannels. +HX_RESULT HSPAudioDevice::SetDeviceConfig( const HXAudioFormat* pFormat ) +{ + snd_pcm_state_t state; + + HX_ASSERT(m_pAlsaPCMHandle != NULL); + if (!m_pAlsaPCMHandle) + { + m_wLastError = RA_AOE_DEVNOTOPEN; + return m_wLastError; + } + + state = snd_pcm_state(m_pAlsaPCMHandle); + if (state != SND_PCM_STATE_OPEN) + { +#ifdef HX_LOG_SUBSYSTEM + HXLOGL1 ( HXLOG_ADEV, "Device is not in open state in HSPAudioDevice::SetDeviceConfig (%d)", (int) state); +#endif + m_wLastError = RA_AOE_DEVNOTOPEN; + return m_wLastError; + } + + /* Translate from HXAudioFormat to ALSA-friendly values */ + snd_pcm_format_t fmt; + unsigned int sample_rate = 0; + unsigned int channels = 0; + unsigned int buffer_time = 500000; /* 0.5 seconds */ + unsigned int period_time = buffer_time / 4; /* 4 interrupts per buffer */ + + switch (pFormat->uBitsPerSample) + { + case 8: + fmt = SND_PCM_FORMAT_S8; + break; + + case 16: + fmt = SND_PCM_FORMAT_S16_LE; + break; + + case 24: + fmt = SND_PCM_FORMAT_S24_LE; + break; + + case 32: + fmt = SND_PCM_FORMAT_S32_LE; + break; + + default: + fmt = SND_PCM_FORMAT_UNKNOWN; + break; + } + + if (fmt == SND_PCM_FORMAT_UNKNOWN) + { +#ifdef HX_LOG_SUBSYSTEM + HXLOGL1 ( HXLOG_ADEV, "Unknown bits per sample: %d", pFormat->uBitsPerSample); +#endif + m_wLastError = RA_AOE_NOTENABLED; + return m_wLastError; + } + sample_rate = pFormat->ulSamplesPerSec; + channels = pFormat->uChannels; + + /* Apply to ALSA */ + int err = 0; + snd_pcm_hw_params_t *hwparams; + snd_pcm_sw_params_t *swparams; + + snd_pcm_hw_params_alloca(&hwparams); + snd_pcm_sw_params_alloca(&swparams); + + /* Hardware parameters */ + err = snd_pcm_hw_params_any(m_pAlsaPCMHandle, hwparams); + if (err < 0) + { +#ifdef HX_LOG_SUBSYSTEM + HXLOGL1 ( HXLOG_ADEV, "snd_pcm_hw_params_any: %s", snd_strerror(err)); +#endif + m_wLastError = RA_AOE_NOTENABLED; + } + + if (err == 0) + { + err = snd_pcm_hw_params_set_access(m_pAlsaPCMHandle, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED); + if (err < 0) + { +#ifdef HX_LOG_SUBSYSTEM + HXLOGL1 ( HXLOG_ADEV, "snd_pcm_hw_params_set_access: %s", snd_strerror(err)); +#endif + m_wLastError = RA_AOE_NOTENABLED; + } + } + + if (err == 0) + { + err = snd_pcm_hw_params_set_format(m_pAlsaPCMHandle, hwparams, fmt); + if (err < 0) + { +#ifdef HX_LOG_SUBSYSTEM + HXLOGL1 ( HXLOG_ADEV, "snd_pcm_hw_params_set_format: %s", snd_strerror(err)); +#endif + m_wLastError = RA_AOE_NOTENABLED; + } + } + + if (err == 0) + { + err = snd_pcm_hw_params_set_channels(m_pAlsaPCMHandle, hwparams, channels); + if (err < 0) + { +#ifdef HX_LOG_SUBSYSTEM + HXLOGL1 ( HXLOG_ADEV, "snd_pcm_hw_params_set_channels: %s", snd_strerror(err)); +#endif + m_wLastError = RA_AOE_NOTENABLED; + } + } + + if (err == 0) + { + unsigned int sample_rate_out; + sample_rate_out = sample_rate; + + err = snd_pcm_hw_params_set_rate_near(m_pAlsaPCMHandle, hwparams, &sample_rate_out, 0); + if (err < 0) + { +#ifdef HX_LOG_SUBSYSTEM + HXLOGL1 ( HXLOG_ADEV, "snd_pcm_hw_params_set_channels: %s", snd_strerror(err)); +#endif + m_wLastError = RA_AOE_NOTENABLED; + } + + if (sample_rate_out != sample_rate) + { +#ifdef HX_LOG_SUBSYSTEM + HXLOGL2 ( HXLOG_ADEV, "Requested a sample rate of %d, got a rate of %d", + sample_rate, sample_rate_out); +#endif + + sample_rate = sample_rate_out; + } + } + + if (err == 0) + { + unsigned int buffer_time_out; + buffer_time_out = buffer_time; + + err = snd_pcm_hw_params_set_buffer_time_near(m_pAlsaPCMHandle, hwparams, &buffer_time_out, 0); + if (err < 0) + { +#ifdef HX_LOG_SUBSYSTEM + HXLOGL1 ( HXLOG_ADEV, "snd_pcm_hw_params_set_buffer_time_near: %s", + snd_strerror(err)); +#endif + m_wLastError = RA_AOE_NOTENABLED; + } + + if (buffer_time_out != buffer_time) + { +#ifdef HX_LOG_SUBSYSTEM + HXLOGL2 ( HXLOG_ADEV, "Requested a buffering time of %d, got a time of %d", + buffer_time, buffer_time_out); +#endif + + buffer_time = buffer_time_out; + } + } + + if (err == 0) + { + unsigned int period_time_out; + period_time_out = period_time; + + err = snd_pcm_hw_params_set_period_time_near(m_pAlsaPCMHandle, hwparams, &period_time_out, 0); + if (err < 0) + { +#ifdef HX_LOG_SUBSYSTEM + HXLOGL1 ( HXLOG_ADEV, "snd_pcm_hw_params_set_period_time_near: %s", + snd_strerror(err)); +#endif + m_wLastError = RA_AOE_NOTENABLED; + } + + if (period_time_out != period_time) + { +#ifdef HX_LOG_SUBSYSTEM + HXLOGL2 ( HXLOG_ADEV, "Requested a period time of %d, got a period of %d", + period_time, period_time_out); +#endif + period_time = period_time_out; + } + } + + /* Apply parameters */ + err = snd_pcm_hw_params(m_pAlsaPCMHandle, hwparams); + if (err < 0) + { +#ifdef HX_LOG_SUBSYSTEM + HXLOGL1 ( HXLOG_ADEV, "snd_pcm_hw_params: %s", + snd_strerror(err)); +#endif + m_wLastError = RA_AOE_NOTENABLED; + } + + /* read buffer & period sizes */ + snd_pcm_uframes_t buffer_size = 0; + snd_pcm_uframes_t period_size = 0; + + if (err == 0) + { + err = snd_pcm_hw_params_get_buffer_size(hwparams, &buffer_size); + if (err < 0) + { +#ifdef HX_LOG_SUBSYSTEM + HXLOGL1 ( HXLOG_ADEV, "snd_pcm_hw_params_get_buffer_size: %s", + snd_strerror(err)); +#endif + m_wLastError = RA_AOE_NOTENABLED; + } + else + { + HX_ASSERT (buffer_size > 0); + } + } + + if (err == 0) + { + err = snd_pcm_hw_params_get_period_size(hwparams, &period_size, 0); + if (err < 0) + { +#ifdef HX_LOG_SUBSYSTEM + HXLOGL1 ( HXLOG_ADEV, "snd_pcm_hw_params_get_period_size: %s", + snd_strerror(err)); +#endif + m_wLastError = RA_AOE_NOTENABLED; + } + } + + /* Get hardware pause */ + if (err == 0) + { + int can_pause = 0; + int can_resume = 0; + + can_pause = snd_pcm_hw_params_can_pause(hwparams); + can_resume = snd_pcm_hw_params_can_resume(hwparams); + + // could we really have one without the other? + m_bHasHardwarePauseAndResume = (can_pause && can_resume); + m_Player->print2stderr("########## can_pause %d can_resume %d\n", can_pause, can_resume); + } + + /* Software parameters */ + if (err == 0) + { + err = snd_pcm_sw_params_current(m_pAlsaPCMHandle, swparams); + if (err < 0) + { +#ifdef HX_LOG_SUBSYSTEM + HXLOGL1 ( HXLOG_ADEV, "snd_pcm_sw_params_current: %s", + snd_strerror(err)); +#endif + m_wLastError = RA_AOE_NOTENABLED; + } + } + + snd_pcm_uframes_t start_threshold = ((buffer_size - 1) / period_size) * period_size; + + if (err == 0) + { + err = snd_pcm_sw_params_set_start_threshold(m_pAlsaPCMHandle, swparams, start_threshold); + if (err < 0) + { +#ifdef HX_LOG_SUBSYSTEM + HXLOGL1 ( HXLOG_ADEV, "snd_pcm_sw_params_set_start_threshold: %s", + snd_strerror(err)); +#endif + m_wLastError = RA_AOE_NOTENABLED; + } + } + + if (err == 0) + { + err = snd_pcm_sw_params_set_avail_min(m_pAlsaPCMHandle, swparams, period_size); + if (err < 0) + { +#ifdef HX_LOG_SUBSYSTEM + HXLOGL1 ( HXLOG_ADEV, "snd_pcm_sw_params_set_avail_min: %s", + snd_strerror(err)); +#endif + m_wLastError = RA_AOE_NOTENABLED; + } + } + + if (err == 0) + { + err = snd_pcm_sw_params_set_xfer_align(m_pAlsaPCMHandle, swparams, 1); + if (err < 0) + { +#ifdef HX_LOG_SUBSYSTEM + HXLOGL1 ( HXLOG_ADEV, "snd_pcm_sw_params_set_xfer_align: %s", + snd_strerror(err)); +#endif + m_wLastError = RA_AOE_NOTENABLED; + } + } + + if (err == 0) + { + err = snd_pcm_sw_params_set_tstamp_mode(m_pAlsaPCMHandle, swparams, SND_PCM_TSTAMP_MMAP); + if (err < 0) + { +#ifdef HX_LOG_SUBSYSTEM + HXLOGL1 ( HXLOG_ADEV, "snd_pcm_sw_params_set_xfer_align: %s", + snd_strerror(err)); +#endif + m_wLastError = RA_AOE_NOTENABLED; + } + } + + if (err == 0) + { + err = snd_pcm_sw_params_set_stop_threshold(m_pAlsaPCMHandle, swparams, ~0U); + if (err < 0) + { +#ifdef HX_LOG_SUBSYSTEM + HXLOGL1 ( HXLOG_ADEV, "snd_pcm_sw_params_set_stop_threshold: %s", + snd_strerror(err)); +#endif + m_wLastError = RA_AOE_NOTENABLED; + } + } + + if (err == 0) + { + err = snd_pcm_sw_params(m_pAlsaPCMHandle, swparams); + if (err < 0) + { +#ifdef HX_LOG_SUBSYSTEM + HXLOGL1 ( HXLOG_ADEV, "snd_pcm_sw_params: %s", + snd_strerror(err)); +#endif + m_wLastError = RA_AOE_NOTENABLED; + } + } + + /* If all the calls to this point have succeeded, move to the PREPARE state. + We will enter the RUNNING state when we've buffered enough for our start theshold. */ + if (err == 0) + { + err = snd_pcm_prepare (m_pAlsaPCMHandle); + if (err < 0) + { +#ifdef HX_LOG_SUBSYSTEM + HXLOGL1 ( HXLOG_ADEV, "snd_pcm_prepare: %s", + snd_strerror(err)); +#endif + m_wLastError = RA_AOE_NOTENABLED; + } + } + + /* Sanity check: See if we're now in the PREPARE state */ + if (err == 0) + { + snd_pcm_state_t state; + state = snd_pcm_state (m_pAlsaPCMHandle); + if (state != SND_PCM_STATE_PREPARED) + { +#ifdef HX_LOG_SUBSYSTEM + HXLOGL1 ( HXLOG_ADEV, "Expected to be in PREPARE state, actually in state %d", + (int) state); +#endif + m_wLastError = RA_AOE_NOTENABLED; + } + } + + /* Use avail to get the alsa buffer size, which is distinct from the hardware buffer + size. This will match what GetRoomOnDevice uses. */ + int alsa_buffer_size = 0; + err = snd_pcm_avail_update(m_pAlsaPCMHandle); + if(err < 0) + { +#ifdef HX_LOG_SUBSYSTEM + HXLOGL1 ( HXLOG_ADEV, "snd_pcm_avail_update: %s", snd_strerror(err)); +#endif + } + else + { + alsa_buffer_size = snd_pcm_frames_to_bytes(m_pAlsaPCMHandle, err); + err = 0; + } + + if (err == 0) + { + m_wLastError = RA_AOE_NOERR; + + m_unSampleRate = sample_rate; + m_unNumChannels = channels; + m_wBlockSize = m_ulBytesPerGran; + m_ulDeviceBufferSize = alsa_buffer_size; + m_uSampFrameSize = snd_pcm_frames_to_bytes(m_pAlsaPCMHandle, 1) / channels; + +#ifdef HX_LOG_SUBSYSTEM + HXLOGL2 ( HXLOG_ADEV, "Device Configured:\n"); + HXLOGL2 ( HXLOG_ADEV, " Sample Rate: %d", m_unSampleRate); + HXLOGL2 ( HXLOG_ADEV, " Sample Width: %d", m_uSampFrameSize); + HXLOGL2 ( HXLOG_ADEV, " Num channels: %d", m_unNumChannels); + HXLOGL2 ( HXLOG_ADEV, " Block size: %d", m_wBlockSize); + HXLOGL2 ( HXLOG_ADEV, " Device buffer size: %lu", m_ulDeviceBufferSize); + HXLOGL2 ( HXLOG_ADEV, " Supports HW Pause: %d", m_bHasHardwarePauseAndResume); + HXLOGL2 ( HXLOG_ADEV, " Start threshold: %d", start_threshold); +#endif + + } + else + { + m_unSampleRate = 0; + m_unNumChannels = 0; + + if (m_pAlsaPCMHandle) + { + _CloseAudio(); + } + } + + return m_wLastError; +} + +//Device specific method to write bytes out to the audiodevice and return a +//count of bytes written. +HX_RESULT HSPAudioDevice::WriteBytes( UCHAR* buffer, ULONG32 ulBuffLength, LONG32& lCount ) +{ + int err = 0, count = 0; + unsigned int frames_written = 0; + snd_pcm_sframes_t num_frames = 0; + ULONG32 ulBytesToWrite = ulBuffLength; + ULONG32 ulBytesWrote = 0; + + lCount = 0; + + HX_ASSERT(m_pAlsaPCMHandle); + if (!m_pAlsaPCMHandle) + { + m_wLastError = RA_AOE_DEVNOTOPEN; + return m_wLastError; + } + + m_wLastError = RA_AOE_NOERR; + + if (ulBuffLength == 0) + { + lCount = ulBuffLength; + return m_wLastError; + } + + + do + { + pthread_mutex_lock(&m_m); + if (!m_closed) + { + if (!m_SWPause) + { + num_frames = snd_pcm_bytes_to_frames(m_pAlsaPCMHandle, ulBytesToWrite); + err = snd_pcm_writei( m_pAlsaPCMHandle, buffer, num_frames ); + } + else + err = -EAGAIN; + } + else + { + pthread_mutex_unlock(&m_m); + return 0; + } + pthread_mutex_unlock(&m_m); + count++; + if (err >= 0) + { + frames_written = err; + + pthread_mutex_lock(&m_m); + if (!m_closed) + ulBytesWrote = snd_pcm_frames_to_bytes (m_pAlsaPCMHandle, frames_written); + pthread_mutex_unlock(&m_m); + buffer += ulBytesWrote; + ulBytesToWrite -= ulBytesWrote; + lCount += ulBytesWrote; + + m_ulTotalWritten += ulBytesWrote; + + } + else + { + switch (err) + { + case -EAGAIN: + usleep(10000); + break; + + case -EPIPE: + HandleXRun(); + lCount = (LONG32) ulBuffLength; + break; + + case -ESTRPIPE: + HandleSuspend(); + lCount = (LONG32) ulBuffLength; + break; + + default: + m_Player->print2stderr("########### snd_pcm_writei: %s num_frames=%ld\n", snd_strerror(err), num_frames); +#ifdef HX_LOG_SUBSYSTEM + HXLOGL1 ( HXLOG_ADEV, "snd_pcm_writei: %s", snd_strerror(err)); +#endif + m_wLastError = RA_AOE_DEVBUSY; + } + } + } while (err == -EAGAIN || (err>0 && ulBytesToWrite>0)); + + //m_Player->print2stderr("############## count = %d\n", count); + + return m_wLastError; +} + +/* Subtract the `struct timeval' values X and Y, + storing the result in RESULT. + Return 1 if the difference is negative, otherwise 0. */ + +int +timeval_subtract (struct timeval *result, + const struct timeval *x, + const struct timeval *y_orig) +{ + struct timeval y = *y_orig; + + /* Perform the carry for the later subtraction by updating Y. */ + if (x->tv_usec < y.tv_usec) + { + int nsec = (y.tv_usec - x->tv_usec) / 1000000 + 1; + y.tv_usec -= 1000000 * nsec; + y.tv_sec += nsec; + } + if ((x->tv_usec - y.tv_usec) > 1000000) + { + int nsec = (x->tv_usec - y.tv_usec) / 1000000; + y.tv_usec += 1000000 * nsec; + y.tv_sec -= nsec; + } + + /* Compute the time remaining to wait. + `tv_usec' is certainly positive. */ + result->tv_sec = x->tv_sec - y.tv_sec; + result->tv_usec = x->tv_usec - y.tv_usec; + + /* Return 1 if result is negative. */ + return x->tv_sec < y.tv_sec; +} + +HX_RESULT HSPAudioDevice::GetBytesActuallyPlayedUsingTStamps(UINT64 &nBytesPlayed) const +{ + HX_RESULT retVal = HXR_FAIL; + + int err = 0; + + snd_timestamp_t trigger_tstamp, now_tstamp, diff_tstamp; + snd_pcm_status_t* status; + + snd_pcm_status_alloca(&status); + + err = snd_pcm_status(m_pAlsaPCMHandle, status); + if (err < 0) + { +#ifdef HX_LOG_SUBSYSTEM + HXLOGL1 ( HXLOG_ADEV, "snd_pcm_status: %s", snd_strerror(err)); +#endif + } + + if (err == 0) + { + snd_pcm_status_get_tstamp(status, &now_tstamp); + snd_pcm_status_get_trigger_tstamp(status, &trigger_tstamp); + + if(!m_bGotInitialTrigger && now_tstamp.tv_sec == 0 && now_tstamp.tv_usec == 0) + { + /* Our first "now" timestamp appears to be invalid (or the user is very unlucky, and + happened to start playback as the timestamp rolls over). Fall back to using + snd_pcm_delay. + + XXXRGG: Is there a better way to figure out if the driver supports mmap'd + timestamps? */ + + m_bUseMMAPTStamps = FALSE; + } + else + { + /* Timestamp seems to be valid */ + if(!m_bGotInitialTrigger) + { + m_bGotInitialTrigger = TRUE; + memcpy(&m_tstampLastTrigger, &trigger_tstamp, sizeof(m_tstampLastTrigger)); + } + else + { + if(memcmp(&m_tstampLastTrigger, &trigger_tstamp, sizeof(m_tstampLastTrigger)) != 0) + { + /* There's been a trigger since last time -- restart the timestamp counter + XXXRGG: What if there's been multiple triggers? */ + m_nBytesPlayedBeforeLastTrigger = m_nLastBytesPlayed; + memcpy(&m_tstampLastTrigger, &trigger_tstamp, sizeof(m_tstampLastTrigger)); + +#ifdef HX_LOG_SUBSYSTEM + HXLOGL1 ( HXLOG_ADEV, "Retriggered..."); +#endif + } + } + + timeval_subtract (&diff_tstamp, &now_tstamp, &m_tstampLastTrigger); + + double fTimePlayed = (double) diff_tstamp.tv_sec + + ((double) diff_tstamp.tv_usec / 1e6); + + nBytesPlayed = (UINT64) ((fTimePlayed * (double) m_unSampleRate * m_uSampFrameSize * m_unNumChannels) + m_nBytesPlayedBeforeLastTrigger); + retVal = HXR_OK; + } + } + + return retVal; +} + +HX_RESULT HSPAudioDevice::GetBytesActuallyPlayedUsingDelay (UINT64 &nBytesPlayed) const +{ + HX_RESULT retVal = HXR_FAIL; + int err = 0; + snd_pcm_sframes_t frame_delay = 0; + + err = snd_pcm_delay (m_pAlsaPCMHandle, &frame_delay); + if (err < 0) + { +#ifdef HX_LOG_SUBSYSTEM + HXLOGL1 ( HXLOG_ADEV, "snd_pcm_status: %s", snd_strerror(err)); +#endif + } + else + { + int bytes_delay; + bytes_delay = snd_pcm_frames_to_bytes (m_pAlsaPCMHandle, frame_delay); + + nBytesPlayed = m_ulTotalWritten - bytes_delay; + retVal = HXR_OK; + } + +#ifdef HX_LOG_SUBSYSTEM +// HXLOGL4 ( HXLOG_ADEV, "nBytesPlayed: %llu, m_ulTotalWritten: %llu\n", nBytesPlayed, m_ulTotalWritten); +#endif + + return retVal; +} + +HX_RESULT HSPAudioDevice::GetBytesActuallyPlayedUsingAvail(UINT64 &nBytesPlayed) const +{ + /* Try this the hwsync way. This method seems to crash & burn with dmix, + as avail seems to come from the device, and varies depending on what other + dmix clients are writing to the slave device. Currently not used for that reason. */ + + HX_RESULT retVal = HXR_FAIL; + int err = 0; + + err = snd_pcm_hwsync(m_pAlsaPCMHandle); + if(err < 0) + { +#ifdef HX_LOG_SUBSYSTEM + HXLOGL1 ( HXLOG_ADEV, "snd_pcm_hwsync: %s", snd_strerror(err)); +#endif + } + + err = snd_pcm_avail_update(m_pAlsaPCMHandle); + if(err < 0) + { +#ifdef HX_LOG_SUBSYSTEM + HXLOGL1 ( HXLOG_ADEV, "snd_pcm_avail_update: %s", snd_strerror(err)); +#endif + } + else + { + snd_pcm_sframes_t avail = err; + int bytes_avail; + bytes_avail = snd_pcm_frames_to_bytes (m_pAlsaPCMHandle, avail); + + nBytesPlayed = m_ulTotalWritten - (m_ulDeviceBufferSize - bytes_avail); + retVal = HXR_OK; + } + + return retVal; +} + +HX_RESULT HSPAudioDevice::GetBytesActuallyPlayedUsingTimer(UINT64 &/*nBytesPlayed*/) const +{ + /* Look at the alsa timer api, and how we can lock onto it as a timer source. */ + + return HXR_FAIL; +} + +UINT64 HSPAudioDevice::GetBytesActualyPlayed(void) const +{ + HX_ASSERT(m_pAlsaPCMHandle); + if (!m_pAlsaPCMHandle) + { + return 0; + } + + HX_RESULT retVal = HXR_OK; + UINT64 nBytesPlayed = 0; + snd_pcm_state_t state; + + for(;;) + { + state = snd_pcm_state(m_pAlsaPCMHandle); + switch(state) + { + case SND_PCM_STATE_OPEN: + case SND_PCM_STATE_SETUP: + case SND_PCM_STATE_PREPARED: + /* If we're in one of these states, written and played should match. */ + m_nLastBytesPlayed = m_ulTotalWritten; + return m_nLastBytesPlayed; + + case SND_PCM_STATE_XRUN: + HandleXRun(); + continue; + + case SND_PCM_STATE_RUNNING: + break; + + case SND_PCM_STATE_PAUSED: + // return m_nLastBytesPlayed; + break; + + case SND_PCM_STATE_DRAINING: + case SND_PCM_STATE_SUSPENDED: + case SND_PCM_STATE_DISCONNECTED: + HX_ASSERT(!"Not reached"); + break; + } + + break; + } + + // XXXRGG: Always use the delay method for now. + m_bUseMMAPTStamps = FALSE; + + if (m_bUseMMAPTStamps) + { + retVal = GetBytesActuallyPlayedUsingTStamps(nBytesPlayed); + } + + if (!m_bUseMMAPTStamps || FAILED(retVal)) + { + /* MMAP'd timestamps are fishy. Try using snd_pcm_delay. */ + retVal = GetBytesActuallyPlayedUsingDelay(nBytesPlayed); + } + + m_nLastBytesPlayed = nBytesPlayed; + return nBytesPlayed; +} + + +//this must return the number of bytes that can be written without blocking. +HX_RESULT HSPAudioDevice::GetRoomOnDevice(ULONG32& ulBytes) const +{ + ulBytes = 0; + + HX_ASSERT(m_pAlsaPCMHandle); + if (!m_pAlsaPCMHandle) + { + m_wLastError = RA_AOE_DEVNOTOPEN; + return m_wLastError; + } + + int err = 0; + err = snd_pcm_avail_update(m_pAlsaPCMHandle); + if(err > 0) + { + ulBytes = snd_pcm_frames_to_bytes(m_pAlsaPCMHandle, err); + } + else + { + switch (err) + { + case -EAGAIN: + break; + + case -EPIPE: + HandleXRun(); + break; + + case -ESTRPIPE: + HandleSuspend(); + break; + + default: +#ifdef HX_LOG_SUBSYSTEM + HXLOGL1 ( HXLOG_ADEV, "snd_pcm_avail_update: %s", snd_strerror(err)); +#endif + m_wLastError = RA_AOE_DEVBUSY; + } + } + +#ifdef HX_LOG_SUBSYSTEM +// HXLOGL4 ( HXLOG_ADEV, "RoomOnDevice: %d", ulBytes); +#endif + + return m_wLastError; +} + + +//Device specific method to get/set the devices current volume. +UINT16 HSPAudioDevice::_GetVolume() const +{ + HX_ASSERT(m_pAlsaMixerElem); + if (!m_pAlsaMixerElem) + { + return 0; + } + + UINT16 nRetVolume = 0; + + snd_mixer_elem_type_t type; + int err = 0; + type = snd_mixer_elem_get_type(m_pAlsaMixerElem); + + if (type == SND_MIXER_ELEM_SIMPLE) + { + long volume, min_volume, max_volume; + + if(snd_mixer_selem_has_playback_volume(m_pAlsaMixerElem) || + snd_mixer_selem_has_playback_volume_joined(m_pAlsaMixerElem)) + { + err = snd_mixer_selem_get_playback_volume(m_pAlsaMixerElem, + SND_MIXER_SCHN_MONO, + &volume); + if (err < 0) + { +#ifdef HX_LOG_SUBSYSTEM + HXLOGL1 ( HXLOG_ADEV, "snd_mixer_selem_get_playback_volume: %s", + snd_strerror (err)); +#endif + } + + if (err == 0) + { + snd_mixer_selem_get_playback_volume_range(m_pAlsaMixerElem, + &min_volume, + &max_volume); + + if(max_volume > min_volume) + { + nRetVolume = (UINT16) (100 * volume / (max_volume - min_volume)); + } + } + } + } + + return nRetVolume; +} + + +HX_RESULT HSPAudioDevice::_SetVolume(UINT16 unVolume) +{ + m_wLastError = RA_AOE_NOERR; + + HX_ASSERT(m_pAlsaMixerElem); + if (!m_pAlsaMixerElem) + { + m_wLastError = RA_AOE_DEVNOTOPEN; + return m_wLastError; + } + + snd_mixer_elem_type_t type; + int err = 0; + type = snd_mixer_elem_get_type(m_pAlsaMixerElem); + + if (type == SND_MIXER_ELEM_SIMPLE) + { + long volume, min_volume, max_volume, range; + + if(snd_mixer_selem_has_playback_volume(m_pAlsaMixerElem) || + snd_mixer_selem_has_playback_volume_joined(m_pAlsaMixerElem)) + { + snd_mixer_selem_get_playback_volume_range(m_pAlsaMixerElem, + &min_volume, + &max_volume); + + range = max_volume - min_volume; + volume = (long) ((unVolume / 100) * range + min_volume); + + err = snd_mixer_selem_set_playback_volume( m_pAlsaMixerElem, + SND_MIXER_SCHN_FRONT_LEFT, + volume); + if (err < 0) + { +#ifdef HX_LOG_SUBSYSTEM + HXLOGL1 ( HXLOG_ADEV, "snd_mixer_selem_set_playback_volume: %s", + snd_strerror (err)); +#endif + m_wLastError = RA_AOE_GENERAL; + } + + if (!snd_mixer_selem_is_playback_mono (m_pAlsaMixerElem)) + { + /* Set the right channel too */ + err = snd_mixer_selem_set_playback_volume( m_pAlsaMixerElem, + SND_MIXER_SCHN_FRONT_RIGHT, + volume); + if (err < 0) + { +#ifdef HX_LOG_SUBSYSTEM + HXLOGL1 ( HXLOG_ADEV, "snd_mixer_selem_set_playback_volume: %s", + snd_strerror (err)); +#endif + m_wLastError = RA_AOE_GENERAL; + } + } + } + } + + return m_wLastError; +} + +//Device specific method to drain a device. This should play the remaining +//bytes in the devices buffer and then return. +HX_RESULT HSPAudioDevice::_Drain() +{ + m_wLastError = RA_AOE_NOERR; + + HX_ASSERT(m_pAlsaPCMHandle); + if (!m_pAlsaPCMHandle) + { + m_wLastError = RA_AOE_DEVNOTOPEN; + return m_wLastError; + } + + int err = 0; + + err = snd_pcm_drain(m_pAlsaPCMHandle); + if (err < 0) + { +#ifdef HX_LOG_SUBSYSTEM + HXLOGL1 ( HXLOG_ADEV, "snd_pcm_drain: %s", + snd_strerror (err)); +#endif + m_wLastError = RA_AOE_GENERAL; + } + + err = snd_pcm_prepare(m_pAlsaPCMHandle); + if (err < 0) + { +#ifdef HX_LOG_SUBSYSTEM + HXLOGL1 ( HXLOG_ADEV, "snd_pcm_prepare: %s", + snd_strerror (err)); +#endif + m_wLastError = RA_AOE_GENERAL; + } + + return m_wLastError; +} + + +//Device specific method to reset device and return it to a state that it +//can accept new sample rates, num channels, etc. +HX_RESULT HSPAudioDevice::_Reset() +{ + if (!m_pAlsaPCMHandle) + { + m_wLastError = RA_AOE_DEVNOTOPEN; + return m_wLastError; + } + + m_wLastError = RA_AOE_NOERR; + + m_nLastBytesPlayed = 0; + + int err = 0; + + err = snd_pcm_drop(m_pAlsaPCMHandle); + if (err < 0) + { +#ifdef HX_LOG_SUBSYSTEM + HXLOGL1 ( HXLOG_ADEV, "snd_pcm_drop: %s", + snd_strerror (err)); +#endif + m_wLastError = RA_AOE_GENERAL; + } + + err = snd_pcm_prepare(m_pAlsaPCMHandle); + if (err < 0) + { +#ifdef HX_LOG_SUBSYSTEM + HXLOGL1 ( HXLOG_ADEV, "snd_pcm_prepare: %s", + snd_strerror (err)); +#endif + m_wLastError = RA_AOE_GENERAL; + } + + return m_wLastError; +} + +HX_RESULT HSPAudioDevice::_CheckFormat( const HXAudioFormat* pFormat ) +{ + HX_ASSERT(m_pAlsaPCMHandle == NULL); + + m_wLastError = _OpenAudio(); + if(m_wLastError != RA_AOE_NOERR) + { + return m_wLastError; + } + + m_wLastError = RA_AOE_NOERR; + + snd_pcm_format_t fmt; + unsigned int sample_rate = 0; + unsigned int channels = 0; + + switch (pFormat->uBitsPerSample) + { + case 8: + fmt = SND_PCM_FORMAT_S8; + break; + + case 16: + fmt = SND_PCM_FORMAT_S16_LE; + break; + + case 24: + fmt = SND_PCM_FORMAT_S24_LE; + break; + + case 32: + fmt = SND_PCM_FORMAT_S32_LE; + break; + + default: + fmt = SND_PCM_FORMAT_UNKNOWN; + break; + } + + if (fmt == SND_PCM_FORMAT_UNKNOWN) + { +#ifdef HX_LOG_SUBSYSTEM + HXLOGL1 ( HXLOG_ADEV, "Unknown bits per sample: %d", pFormat->uBitsPerSample); +#endif + m_wLastError = RA_AOE_NOTENABLED; + return m_wLastError; + } + sample_rate = pFormat->ulSamplesPerSec; + channels = pFormat->uChannels; + + /* Apply to ALSA */ + int err = 0; + snd_pcm_hw_params_t *hwparams; + + snd_pcm_hw_params_alloca(&hwparams); + + err = snd_pcm_hw_params_any(m_pAlsaPCMHandle, hwparams); + if (err < 0) + { +#ifdef HX_LOG_SUBSYSTEM + HXLOGL1 ( HXLOG_ADEV, "snd_pcm_hw_params_any: %s", snd_strerror(err)); +#endif + m_wLastError = RA_AOE_NOTENABLED; + } + + if (err == 0) + { + err = snd_pcm_hw_params_test_rate (m_pAlsaPCMHandle, hwparams, sample_rate, 0); + if (err < 0) + { + m_wLastError = RA_AOE_BADFORMAT; + } + } + + if (err == 0) + { + err = snd_pcm_hw_params_test_channels (m_pAlsaPCMHandle, hwparams, channels); + if (err < 0) + { + m_wLastError = RA_AOE_BADFORMAT; + } + } + + if (err == 0) + { + err = snd_pcm_hw_params_test_format (m_pAlsaPCMHandle, hwparams, fmt); + if (err < 0) + { + m_wLastError = RA_AOE_BADFORMAT; + } + } + + _CloseAudio(); + + return m_wLastError; +} + + +HX_RESULT HSPAudioDevice::CheckSampleRate( ULONG32 ulSampleRate ) +{ + HX_ASSERT(m_pAlsaPCMHandle == NULL); + bool shouldclose = false; + + if (!m_pAlsaPCMHandle) + { + m_wLastError = _OpenAudio(); + if(m_wLastError != RA_AOE_NOERR) + { + return m_wLastError; + } + shouldclose = true; + } + + int err = 0; + snd_pcm_hw_params_t *hwparams; + + snd_pcm_hw_params_alloca(&hwparams); + + m_wLastError = RA_AOE_NOERR; + + err = snd_pcm_hw_params_any(m_pAlsaPCMHandle, hwparams); + if (err < 0) + { +#ifdef HX_LOG_SUBSYSTEM + HXLOGL1 ( HXLOG_ADEV, "snd_pcm_hw_params_any: %s", snd_strerror(err)); +#endif + m_wLastError = RA_AOE_NOTENABLED; + } + + if (err == 0) + { + err = snd_pcm_hw_params_test_rate (m_pAlsaPCMHandle, hwparams, ulSampleRate, 0); + if (err < 0) + { + m_wLastError = RA_AOE_BADFORMAT; + } + } + + if (shouldclose) + _CloseAudio(); + + return m_wLastError; +} + + +HX_RESULT HSPAudioDevice::_Pause() +{ + HX_ASSERT(m_pAlsaPCMHandle); + if (!m_pAlsaPCMHandle) + { + m_wLastError = RA_AOE_DEVNOTOPEN; + return m_wLastError; + } + + if (m_bHasHardwarePauseAndResume) + { + snd_pcm_state_t state; + + state = snd_pcm_state(m_pAlsaPCMHandle); + if (state == SND_PCM_STATE_RUNNING) + { + int err = 0; + err = snd_pcm_pause(m_pAlsaPCMHandle, 1); + if (err < 0) + { +#ifdef HX_LOG_SUBSYSTEM + HXLOGL1 ( HXLOG_ADEV, "snd_pcm_pause: %s", + snd_strerror (err)); +#endif + + m_wLastError = RA_AOE_NOTSUPPORTED; + } + } + } + else + { + pthread_mutex_lock(&m_m); + m_SWPause = true; + _Drain(); + _Reset(); + pthread_mutex_unlock(&m_m); + } + + return m_wLastError; +} + +HX_RESULT HSPAudioDevice::_Resume() +{ + HX_ASSERT(m_pAlsaPCMHandle); + if (!m_pAlsaPCMHandle) + { + m_wLastError = RA_AOE_DEVNOTOPEN; + return m_wLastError; + } + + if (m_bHasHardwarePauseAndResume) + { + snd_pcm_state_t state; + + state = snd_pcm_state(m_pAlsaPCMHandle); + if (state == SND_PCM_STATE_PAUSED) + { + int err = 0; + err = snd_pcm_pause(m_pAlsaPCMHandle, 0); + + if (err < 0) + { +#ifdef HX_LOG_SUBSYSTEM + HXLOGL1 ( HXLOG_ADEV, "snd_pcm_pause: %s", + snd_strerror (err)); +#endif + + m_wLastError = RA_AOE_NOTSUPPORTED; + } + } + } + else + { + pthread_mutex_lock(&m_m); + m_SWPause = false; + _Reset(); + pthread_mutex_unlock(&m_m); + } + + return m_wLastError; +} + +BOOL HSPAudioDevice::HardwarePauseSupported() const +{ + HX_ASSERT(m_pAlsaPCMHandle != NULL); + + return m_bHasHardwarePauseAndResume; +} + + +void HSPAudioDevice::HandleXRun(void) const +{ + int err = 0; + +#ifdef HX_LOG_SUBSYSTEM + HXLOGL2 ( HXLOG_ADEV, "Handling XRun"); +#endif + + err = snd_pcm_prepare(m_pAlsaPCMHandle); + if (err < 0) + { +#ifdef HX_LOG_SUBSYSTEM + HXLOGL1 ( HXLOG_ADEV, "snd_pcm_resume: %s (xrun)", + snd_strerror (err)); +#endif + } + + /* Catch up to the write position of the audio device so we get new data. + XXXRGG: Is there some way we, the device, can force a rewind? */ + m_nLastBytesPlayed = m_ulTotalWritten; +} + +void HSPAudioDevice::HandleSuspend(void) const +{ + int err = 0; + + do + { + err = snd_pcm_resume(m_pAlsaPCMHandle); + if (err == 0) + { + break; + } + else if (err == -EAGAIN) + { + usleep(1000); + } + } while (err == -EAGAIN); + + if (err < 0) + { + HandleXRun(); + } +} + +#endif // HELIX_USE_ALSA diff --git a/amarok/src/engine/helix/helix-sp/hspalsadevice.h b/amarok/src/engine/helix/helix-sp/hspalsadevice.h new file mode 100644 index 00000000..b237e322 --- /dev/null +++ b/amarok/src/engine/helix/helix-sp/hspalsadevice.h @@ -0,0 +1,248 @@ +/****************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin St, 5th fl, Boston, MA 02110-1301, * + * USA, or check http://www.fsf.org/about/contact.html * + * * + * Portions Copyright (c) 1995-2004 RealNetworks, Inc. All Rights Reserved. * + * Portions Copyright (c) 2005 Paul Cifarelli * + * * + ******************************************************************************/ + +#ifndef _AUDLINUXALSA +#define _AUDLINUXALSA + +#ifdef USE_HELIX_ALSA + +#define ALSA_PCM_NEW_HW_PARAMS_API +#define ALSA_PCM_NEW_SW_PARAMS_API + +#include <alsa/asoundlib.h> + +class HelixSimplePlayer; +class AudioQueue +{ +public: + AudioQueue( const HXAudioData *buf ); + ~AudioQueue(); + + AudioQueue *fwd; + HXAudioData ad; + LONG32 bytes; +}; + +class HSPAudioDevice : public IHXAudioDevice +{ +public: + HSPAudioDevice(HelixSimplePlayer *player, const char *device); + virtual ~HSPAudioDevice(); + + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj); + STDMETHOD_(ULONG32,AddRef) (THIS); + STDMETHOD_(ULONG32,Release) (THIS); + /* + * IHXAudioDevice methods + */ + STDMETHOD(CheckFormat) ( + THIS_ + const HXAudioFormat* pAudioFormat + ); + + STDMETHOD(Close) ( + THIS_ + const BOOL bFlush + ); + + STDMETHOD(Drain) ( + THIS + ); + + STDMETHOD(GetCurrentAudioTime) ( + THIS_ + REF(ULONG32) ulCurrentTime + ); + + STDMETHOD_(UINT16,GetVolume) ( + THIS + ); + + STDMETHOD_(BOOL,InitVolume) ( + THIS_ + const UINT16 uMinVolume, + const UINT16 uMaxVolume + ); + + STDMETHOD(Open) ( + THIS_ + const HXAudioFormat* pAudioFormat, + IHXAudioDeviceResponse* pStreamResponse + ); + + STDMETHOD(Pause) ( + THIS + ); + + STDMETHOD(Reset) ( + THIS + ); + + STDMETHOD(Resume) ( + THIS + ); + + STDMETHOD(SetVolume) ( + THIS_ + const UINT16 uVolume + ); + + STDMETHOD(Write) ( + THIS_ + const HXAudioData* pAudioData + ); + + HX_RESULT OnTimeSync(); + + void setDevice( const char *device ); + +protected: + virtual INT16 GetAudioFd(void); + + //This ones important. + virtual UINT64 GetBytesActualyPlayed(void) const; + + //Device specific method to set the audio device characteristics. Sample rate, + //bits-per-sample, etc. + //Method *must* set member vars. m_unSampleRate and m_unNumChannels. + virtual HX_RESULT SetDeviceConfig( const HXAudioFormat* pFormat ); + + //Device specific method to test wether or not the device supports the + //give sample rate. If the device can not be opened, or otherwise tested, + //it should return RA_AOE_DEVBUSY. + virtual HX_RESULT CheckSampleRate( ULONG32 ulSampleRate ); + virtual HX_RESULT _CheckFormat( const HXAudioFormat* pFormat ); + + //Device specific method to write bytes out to the audiodevice and return a + //count of bytes written. + virtual HX_RESULT WriteBytes( UCHAR* buffer, ULONG32 ulBuffLength, LONG32& lCount ); + + //Device specific methods to open/close the mixer and audio devices. + virtual HX_RESULT _OpenAudio(); + virtual HX_RESULT _CloseAudio(); + virtual HX_RESULT _OpenMixer(); + virtual HX_RESULT _CloseMixer(); + + //Device specific method to reset device and return it to a state that it + //can accept new sample rates, num channels, etc. + virtual HX_RESULT _Reset(); + virtual HX_RESULT _Pause(); + virtual HX_RESULT _Resume(); + + //Device specific method to get/set the devices current volume. + virtual UINT16 _GetVolume() const; + virtual HX_RESULT _SetVolume(UINT16 volume); + + //Device specific method to drain a device. This should play the remaining + //bytes in the devices buffer and then return. + virtual HX_RESULT _Drain(); + + //Device specific method to return the amount of room available on the + //audio device that can be written without blocking. + virtual HX_RESULT GetRoomOnDevice( ULONG32& ulBytes) const; + + //A method to let us know if the hardware supports puase/resume. + //We can use this to remove unneeded memcpys and other expensive + //operations. The default implementation is 'No, not supported'. + virtual BOOL HardwarePauseSupported() const; + + int _Write( const HXAudioData *pAudioData ); + + int sync(); + +private: + HSPAudioDevice(); + //protect the unintentional copy ctor. + HSPAudioDevice( const HSPAudioDevice & ); //Not implemented. + + /* The constness imposed by the base class is a lost cause here -- + make all functions const, all members mutable. */ + void HandleXRun(void) const; + void HandleSuspend(void) const; + + HX_RESULT GetBytesActuallyPlayedUsingTStamps (UINT64 &nBytesPlayed) const; + HX_RESULT GetBytesActuallyPlayedUsingDelay (UINT64 &nBytesPlayed) const; + HX_RESULT GetBytesActuallyPlayedUsingAvail (UINT64 &nBytesPlayed) const; + HX_RESULT GetBytesActuallyPlayedUsingTimer (UINT64 &nBytesPlayed) const; + + mutable snd_pcm_t* m_pAlsaPCMHandle; + mutable snd_mixer_t* m_pAlsaMixerHandle; + mutable snd_mixer_elem_t* m_pAlsaMixerElem; + + mutable IHXBuffer* m_pPCMDeviceName; + mutable IHXBuffer* m_pMixerDeviceName; + mutable IHXBuffer* m_pMixerElementName; + + mutable BOOL m_bHasHardwarePauseAndResume; + + mutable UINT64 m_nBytesPlayedBeforeLastTrigger; + + mutable UINT64 m_nLastBytesPlayed; + + mutable snd_timestamp_t m_tstampLastTrigger; + mutable BOOL m_bGotInitialTrigger; + + mutable BOOL m_bUseMMAPTStamps; + + LONG32 m_lRefCount; + mutable LONG32 m_wLastError; + BOOL m_bMixerPresent; + UINT32 m_unSampleRate; + UINT16 m_unNumChannels; + UINT32 m_wBlockSize; + UINT32 m_ulBytesPerGran; + UINT32 m_ulDeviceBufferSize; + UINT16 m_uSampFrameSize; + UINT32 m_ulTotalWritten; + UINT32 m_ulCurrentTime; + UINT32 m_ulQTime; + UINT32 m_ulLastTime; + BOOL m_SWPause; + + HelixSimplePlayer *m_Player; + IHXAudioDeviceResponse *m_pStreamResponse; + + bool m_done; + bool m_drain; + bool m_closed; + AudioQueue *m_head; + AudioQueue *m_tail; + pthread_t m_thrid; + pthread_mutex_t m_m; + pthread_cond_t m_cv; + + void addBuf(struct AudioQueue *item); + void pushBuf(struct AudioQueue *item); + AudioQueue *getBuf(); + void clearQueue(); + + static void *writerThread( void *arg ); +}; + +#endif // USE_HELIX_ALSA + +#endif //_AUDIOOUTLINUXALSA diff --git a/amarok/src/engine/helix/helix-sp/hspauthmgr.cpp b/amarok/src/engine/helix/helix-sp/hspauthmgr.cpp new file mode 100644 index 00000000..db2b1f81 --- /dev/null +++ b/amarok/src/engine/helix/helix-sp/hspauthmgr.cpp @@ -0,0 +1,112 @@ + +/* + * + * This software is released under the provisions of the GPL version 2. + * see file "COPYING". If that file is not available, the full statement + * of the license can be found at + * + * http://www.fsf.org/licensing/licenses/gpl.txt + * + * Portions Copyright (c) 1995-2002 RealNetworks, Inc. All Rights Reserved. + * Portions (c) Paul Cifarelli 2005 + */ + +#include <stdio.h> +#include "hxcom.h" +#include "hxauth.h" +#include "hspauthmgr.h" +#include <ctype.h> + +#include "hxausvc.h" +#include "helix-sp.h" +#include "utils.h" + + +HSPAuthenticationManager::HSPAuthenticationManager(HelixSimplePlayer *pSplay) : + m_lRefCount(0), + m_bSentPassword(false), + m_splayer(pSplay) +{ +} + +HSPAuthenticationManager::~HSPAuthenticationManager() +{ +} + +STDMETHODIMP +HSPAuthenticationManager::QueryInterface(REFIID riid, void**ppvObj) +{ + if(IsEqualIID(riid, IID_IUnknown)) + { + AddRef(); + *ppvObj = (IUnknown*)(IHXAuthenticationManager*)this; + return HXR_OK; + } + else if(IsEqualIID(riid, IID_IHXAuthenticationManager)) + { + AddRef(); + *ppvObj = (IHXAuthenticationManager*)this; + return HXR_OK; + } + *ppvObj = NULL; + return HXR_NOINTERFACE; +} + +STDMETHODIMP_(UINT32) +HSPAuthenticationManager::AddRef() +{ + return InterlockedIncrement(&m_lRefCount); +} + +STDMETHODIMP_(UINT32) +HSPAuthenticationManager::Release() +{ + if (InterlockedDecrement(&m_lRefCount) > 0) + { + return m_lRefCount; + } + + delete this; + return 0; +} + +STDMETHODIMP +HSPAuthenticationManager::HandleAuthenticationRequest(IHXAuthenticationManagerResponse* pResponse) +{ + char username[1024] = ""; /* Flawfinder: ignore */ + char password[1024] = ""; /* Flawfinder: ignore */ + HX_RESULT res = HXR_FAIL; + + if( !m_bSentPassword ) + { + res = HXR_OK; + if (m_splayer->bEnableVerboseMode) + m_splayer->print2stdout("\nSending Username and Password...\n"); + + SafeStrCpy(username, m_splayer->m_pszUsername, 1024); + SafeStrCpy(password, m_splayer->m_pszPassword, 1024); + + //strip trailing whitespace + char* c; + for(c = username + strlen(username) - 1; + c > username && isspace(*c); + c--) + ; + *(c+1) = 0; + + for(c = password + strlen(password) - 1; + c > password && isspace(*c); + c--) + ; + *(c+1) = 0; + + m_bSentPassword = true; + } + + if (m_splayer->bEnableVerboseMode && FAILED(res) ) + m_splayer->print2stdout("\nInvalid Username and/or Password.\n"); + + pResponse->AuthenticationRequestDone(res, username, password); + return res; +} + diff --git a/amarok/src/engine/helix/helix-sp/hspauthmgr.h b/amarok/src/engine/helix/helix-sp/hspauthmgr.h new file mode 100644 index 00000000..55d95cb7 --- /dev/null +++ b/amarok/src/engine/helix/helix-sp/hspauthmgr.h @@ -0,0 +1,37 @@ +/* + * + * This software is released under the provisions of the GPL version 2. + * see file "COPYING". If that file is not available, the full statement + * of the license can be found at + * + * http://www.fsf.org/licensing/licenses/gpl.txt + * + * Portions Copyright (c) 1995-2002 RealNetworks, Inc. All Rights Reserved. + * Portions (c) Paul Cifarelli 2005 + * + */ + +#ifndef _HSPAUTHMGR_H_ +#define _HSPAUTHMGR_H_ + +#include "hxauth.h" + +class HelixSimplePlayer; + +class HSPAuthenticationManager : public IHXAuthenticationManager +{ +private: + INT32 m_lRefCount; + BOOL m_bSentPassword; + HelixSimplePlayer *m_splayer; + +public: + HSPAuthenticationManager(HelixSimplePlayer *pSplay); + virtual ~HSPAuthenticationManager(); + STDMETHOD(QueryInterface) (THIS_ REFIID riid, void** ppvObj); + STDMETHOD_(UINT32,AddRef) (THIS); + STDMETHOD_(UINT32,Release) (THIS); + + STDMETHOD(HandleAuthenticationRequest) (IHXAuthenticationManagerResponse* pResponse); +}; +#endif diff --git a/amarok/src/engine/helix/helix-sp/hspcontext.cpp b/amarok/src/engine/helix/helix-sp/hspcontext.cpp new file mode 100644 index 00000000..f87a9141 --- /dev/null +++ b/amarok/src/engine/helix/helix-sp/hspcontext.cpp @@ -0,0 +1,472 @@ + +/* + * + * This software is released under the provisions of the GPL version 2. + * see file "COPYING". If that file is not available, the full statement + * of the license can be found at + * + * http://www.fsf.org/licensing/licenses/gpl.txt + * + * Portions Copyright (c) 1995-2002 RealNetworks, Inc. All Rights Reserved. + * Portions (c) Paul Cifarelli 2005 + * + * + */ +#include "hxcomm.h" +#include "hxcore.h" +#include "hxbuffer.h" +#include "hxmangle.h" + +#include "hxclsnk.h" +#include "hxerror.h" +#include "hxprefs.h" + +#include "hspadvisesink.h" +#include "hsperror.h" +#include "hspauthmgr.h" +#include "hspcontext.h" + +#include "hxausvc.h" +#include "helix-sp.h" +#include "utils.h" + +extern BOOL bEnableAdviceSink; + +HSPEngineContext::HSPEngineContext(HelixSimplePlayer *splayer, IHXCommonClassFactory *pCommonClassFactory) : m_lRefCount(0), m_CommonClassFactory(pCommonClassFactory), m_splayer(splayer) +{ +} + +HSPEngineContext::~HSPEngineContext() +{ + Close(); +} + +void HSPEngineContext::Close() +{ + // you don't own the common class factory, so don't even think about it... +} + +// *** IUnknown methods *** + +///////////////////////////////////////////////////////////////////////// +// Method: +// IUnknown::QueryInterface +// Purpose: +// Implement this to export the interfaces supported by your +// object. +// +STDMETHODIMP HSPEngineContext::QueryInterface(REFIID riid, void** ppvObj) +{ + if (IsEqualIID(riid, IID_IUnknown)) + { + AddRef(); + *ppvObj = this; + return HXR_OK; + } + else if (IsEqualIID(riid, IID_IHXPreferences)) + { + AddRef(); + *ppvObj = (IHXPreferences*)this; + return HXR_OK; + } + *ppvObj = NULL; + return HXR_NOINTERFACE; +} + +///////////////////////////////////////////////////////////////////////// +// Method: +// IUnknown::AddRef +// Purpose: +// Everyone usually implements this the same... feel free to use +// this implementation. +// +STDMETHODIMP_(ULONG32) HSPEngineContext::AddRef() +{ + return InterlockedIncrement(&m_lRefCount); +} + +///////////////////////////////////////////////////////////////////////// +// Method: +// IUnknown::Release +// Purpose: +// Everyone usually implements this the same... feel free to use +// this implementation. +// +STDMETHODIMP_(ULONG32) HSPEngineContext::Release() +{ + if (InterlockedDecrement(&m_lRefCount) > 0) + { + return m_lRefCount; + } + + delete this; + return 0; +} + + +// *** IHXPreference methods *** +void HSPEngineContext::Init(IUnknown* /*pUnknown*/) +{ + // nothing to do yet... +} + +///////////////////////////////////////////////////////////////////////// +// Method: +// IHXPreferences::ReadPref +// Purpose: +// Read a Preference from the registry. +// +STDMETHODIMP +HSPEngineContext::ReadPref(const char* pref_key, IHXBuffer*& buffer) +{ + HX_RESULT hResult = HXR_OK; + unsigned char *outbuf; + IHXBuffer *ibuf; + + + m_splayer->print2stderr("in engine context, key is <%s>\n", pref_key); + if (0 == (stricmp(pref_key, "OpenAudioDeviceOnPlayback"))) + { + m_CommonClassFactory->CreateInstance(CLSID_IHXBuffer, (void **) &ibuf); + if (ibuf) + { + ibuf->SetSize(2); + outbuf = ibuf->GetBuffer(); + strcpy((char *)outbuf, "0"); + buffer = ibuf; + //m_splayer->print2stderr("value = %d\n",atol((const char*) buffer->GetBuffer())); + } + } + else if (0 == (stricmp(pref_key, "SoundDriver"))) + { + m_CommonClassFactory->CreateInstance(CLSID_IHXBuffer, (void **) &ibuf); + if (ibuf) + { + ibuf->SetSize(2); + outbuf = ibuf->GetBuffer(); + + // 0 = OSS + // 1 = OldOSSsupport + // 2 = ESound + // 3 = Alsa + // 4 = USound + + if (m_splayer->getOutputSink() == HelixSimplePlayer::ALSA) + strcpy((char *)outbuf, "3"); // set SoundDriver = kALSA (ie 3) for Alsa native support + else if (m_splayer->getOutputSink() == HelixSimplePlayer::OSS) + strcpy((char *)outbuf, "0"); // set SoundDriver = kOSS (ie 0) for OSS + buffer = ibuf; + + if (m_splayer->getOutputSink() == HelixSimplePlayer::ALSA || m_splayer->getOutputSink() == HelixSimplePlayer::OSS) + m_splayer->print2stderr("Setting Sound System to %s\n", m_splayer->getOutputSink() == HelixSimplePlayer::ALSA ? "ALSA" : "OSS"); + else + m_splayer->print2stderr("Setting Sound System to UNKNOWN: %d\n", m_splayer->getOutputSink()); + } + } + // maybe also need to allow setting of "AlsaMixerDeviceName"? + else if (0 == (stricmp(pref_key, "AlsaMixerElementName"))) + { + m_splayer->setAlsaCapableCore(); // this just lets everyone know that this helix core is Alsa-capable + m_CommonClassFactory->CreateInstance(CLSID_IHXBuffer, (void **) &ibuf); + if (ibuf) + { + ibuf->SetSize(11); + outbuf = ibuf->GetBuffer(); + strcpy((char *)outbuf, "PC Speaker"); + buffer = ibuf; + m_splayer->print2stderr("Setting Mixer Element to use default mixer\n"); + } + } + else if (0 == (stricmp(pref_key, "AlsaMixerDeviceName"))) + { + m_splayer->setAlsaCapableCore(); // this just lets everyone know that this helix core is Alsa-capable + m_CommonClassFactory->CreateInstance(CLSID_IHXBuffer, (void **) &ibuf); + if (ibuf) + { + ibuf->SetSize(8); + outbuf = ibuf->GetBuffer(); + strcpy((char *)outbuf, "default"); + buffer = ibuf; + m_splayer->print2stderr("Setting Mixer Device to use the \"default\" mixer\n"); + } + } + else if (0 == (stricmp(pref_key, "AlsaPCMDeviceName"))) + { + m_splayer->setAlsaCapableCore(); // this just lets everyone know that this helix core is Alsa-capable + m_CommonClassFactory->CreateInstance(CLSID_IHXBuffer, (void **) &ibuf); + if (ibuf) + { + int len = strlen(m_splayer->getDevice()); + m_splayer->print2stderr("Setting Sound Device to \"%s\", %d\n", m_splayer->getDevice(), len); + ibuf->SetSize(len + 1); + outbuf = ibuf->GetBuffer(); + strcpy((char *)outbuf, m_splayer->getDevice()); + buffer = ibuf; + m_splayer->print2stderr("Setting Sound Device to \"%s\"\n", m_splayer->getDevice()); + } + } + else if (0 == (stricmp(pref_key, "ThreadedAudio"))) + { + m_CommonClassFactory->CreateInstance(CLSID_IHXBuffer, (void **) &ibuf); + if (ibuf) + { + ibuf->SetSize(2); + outbuf = ibuf->GetBuffer(); + strcpy((char *)outbuf, "1"); + buffer = ibuf; + + m_splayer->print2stderr("setting ThreadedAudio to value = %ld\n",atol((const char*) buffer->GetBuffer())); + } + } + else if (0 == (stricmp(pref_key, "UseCoreThread"))) + { + m_CommonClassFactory->CreateInstance(CLSID_IHXBuffer, (void **) &ibuf); + if (ibuf) + { + ibuf->SetSize(2); + outbuf = ibuf->GetBuffer(); + strcpy((char *)outbuf, "1"); + buffer = ibuf; + + m_splayer->print2stderr("setting initial UseCoreThread to value = %ld\n",atol((const char*) buffer->GetBuffer())); + } + } + else if (0 == (stricmp(pref_key, "NetworkThreading"))) + { + m_CommonClassFactory->CreateInstance(CLSID_IHXBuffer, (void **) &ibuf); + if (ibuf) + { + ibuf->SetSize(2); + outbuf = ibuf->GetBuffer(); + strcpy((char *)outbuf, "1"); + buffer = ibuf; + + m_splayer->print2stderr("setting initial NetworkTheading to value = %ld\n",atol((const char*) buffer->GetBuffer())); + } + } + else + { + hResult = HXR_NOTIMPL; + } + + return hResult; +} + +///////////////////////////////////////////////////////////////////////// +// Method: +// IHXPreferences::WritePref +// Purpose: +// Write a Preference to the registry. +// +STDMETHODIMP +HSPEngineContext::WritePref(const char* /*pref_key*/, IHXBuffer* /*buffer*/) +{ + //m_splayer->print2stderr("In EngineContext, WritePref, key %s\n", pref_key); + return HXR_OK; // for now, no one allowed to change it +} + + + +HSPClientContext::HSPClientContext(LONG32 lClientIndex, HelixSimplePlayer *pSplay) + : m_lRefCount(0) + , m_lClientIndex(lClientIndex) + , m_pClientSink(NULL) + , m_pErrorSink(NULL) + , m_pAuthMgr(NULL) + , m_pDefaultPrefs(NULL) + , m_splayer(pSplay) +{ +} + + +HSPClientContext::~HSPClientContext() +{ + Close(); +} + +void HSPClientContext::Init(IUnknown* pUnknown, + IHXPreferences* pPreferences, + char* pszGUID) +{ + //char* pszCipher = NULL; + + + m_pClientSink = new HSPClientAdviceSink(pUnknown, m_lClientIndex, m_splayer); + m_pErrorSink = new HSPErrorSink(pUnknown, m_splayer); + m_pAuthMgr = new HSPAuthenticationManager(m_splayer); + + if (m_pClientSink) + { + m_pClientSink->AddRef(); + } + + if (m_pErrorSink) + { + m_pErrorSink->AddRef(); + } + + if(m_pAuthMgr) + { + m_pAuthMgr->AddRef(); + } + + if (pPreferences) + { + m_pDefaultPrefs = pPreferences; + m_pDefaultPrefs->AddRef(); + } + + if (pszGUID && *pszGUID) + { + // Encode GUID + // TODO: find/implement Cipher + //pszCipher = Cipher(pszGUID); + //SafeStrCpy(m_pszGUID, pszCipher, 256); + } + else + { + m_pszGUID[0] = '\0'; + } +} + +void HSPClientContext::Close() +{ + HX_RELEASE(m_pClientSink); + HX_RELEASE(m_pErrorSink); + HX_RELEASE(m_pAuthMgr); + HX_RELEASE(m_pDefaultPrefs); +} + + + +// *** IUnknown methods *** + +///////////////////////////////////////////////////////////////////////// +// Method: +// IUnknown::QueryInterface +// Purpose: +// Implement this to export the interfaces supported by your +// object. +// +STDMETHODIMP HSPClientContext::QueryInterface(REFIID riid, void** ppvObj) +{ + if (IsEqualIID(riid, IID_IUnknown)) + { + AddRef(); + *ppvObj = this; + return HXR_OK; + } + else if (IsEqualIID(riid, IID_IHXPreferences)) + { + AddRef(); + *ppvObj = (IHXPreferences*)this; + return HXR_OK; + } + else if (m_pClientSink && + m_pClientSink->QueryInterface(riid, ppvObj) == HXR_OK) + { + return HXR_OK; + } + else if (m_pErrorSink && + m_pErrorSink->QueryInterface(riid, ppvObj) == HXR_OK) + { + return HXR_OK; + } + else if(m_pAuthMgr && + m_pAuthMgr->QueryInterface(riid, ppvObj) == HXR_OK) + { + return HXR_OK; + } + *ppvObj = NULL; + return HXR_NOINTERFACE; +} + +///////////////////////////////////////////////////////////////////////// +// Method: +// IUnknown::AddRef +// Purpose: +// Everyone usually implements this the same... feel free to use +// this implementation. +// +STDMETHODIMP_(ULONG32) HSPClientContext::AddRef() +{ + return InterlockedIncrement(&m_lRefCount); +} + +///////////////////////////////////////////////////////////////////////// +// Method: +// IUnknown::Release +// Purpose: +// Everyone usually implements this the same... feel free to use +// this implementation. +// +STDMETHODIMP_(ULONG32) HSPClientContext::Release() +{ + if (InterlockedDecrement(&m_lRefCount) > 0) + { + return m_lRefCount; + } + + delete this; + return 0; +} + + +// *** IHXPreference methods *** + +///////////////////////////////////////////////////////////////////////// +// Method: +// IHXPreferences::ReadPref +// Purpose: +// Read a Preference from the registry. +// +STDMETHODIMP +HSPClientContext::ReadPref(const char* pref_key, IHXBuffer*& buffer) +{ + HX_RESULT hResult = HXR_OK; + //char* pszCipher = NULL; + + if ((stricmp(pref_key, CLIENT_GUID_REGNAME) == 0) && + (*m_pszGUID)) + { + // Create a Buffer + //TODO: Implement an IHXBuffer + +// buffer = new CHXBuffer(); +// buffer->AddRef(); + + // Copy the encoded GUID into the buffer +// buffer->Set((UCHAR*)m_pszGUID, strlen(m_pszGUID) + 1); + } + else if (m_pDefaultPrefs) + { + hResult = m_pDefaultPrefs->ReadPref(pref_key, buffer); + } + else + { + hResult = HXR_NOTIMPL; + } + + return hResult; +} + +///////////////////////////////////////////////////////////////////////// +// Method: +// IHXPreferences::WritePref +// Purpose: +// Write a Preference to the registry. +// +STDMETHODIMP +HSPClientContext::WritePref(const char* pref_key, IHXBuffer* buffer) +{ + if (m_pDefaultPrefs) + { + return m_pDefaultPrefs->WritePref(pref_key, buffer); + } + else + { + return HXR_OK; + } +} + + diff --git a/amarok/src/engine/helix/helix-sp/hspcontext.h b/amarok/src/engine/helix/helix-sp/hspcontext.h new file mode 100644 index 00000000..2d4f5000 --- /dev/null +++ b/amarok/src/engine/helix/helix-sp/hspcontext.h @@ -0,0 +1,101 @@ +/* + * + * This software is released under the provisions of the GPL version 2. + * see file "COPYING". If that file is not available, the full statement + * of the license can be found at + * + * http://www.fsf.org/licensing/licenses/gpl.txt + * + * Portions Copyright (c) 1995-2002 RealNetworks, Inc. All Rights Reserved. + * Portions (c) Paul Cifarelli 2005 + * + */ +#ifndef _HSPCONTEXT_ +#define _HSPCONTEXT_ + +struct IUnknown; +struct IHXPreferences; +struct IHXVolume; +class IHXCommonClassFactory; +class HSPClientAdviceSink; +class HSPErrorMessages; +class HSPAuthenticationManager; +class HelixSimplePlayer; + +class HSPEngineContext : public IHXPreferences +{ +public: + HSPEngineContext(HelixSimplePlayer *splayer, IHXCommonClassFactory *pCommonClassFactory); + virtual ~HSPEngineContext(); + void Init(IUnknown* /*IN*/ pUnknown); + void Close(); + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj); + + STDMETHOD_(ULONG32,AddRef) (THIS); + STDMETHOD_(ULONG32,Release) (THIS); + + /* + * IHXPreferences methods + */ + STDMETHOD(ReadPref) (THIS_ const char* pref_key, + IHXBuffer*& buffer); + STDMETHOD(WritePref) (THIS_ const char* pref_key, + IHXBuffer* buffer); + +private: + LONG32 m_lRefCount; + IHXCommonClassFactory *m_CommonClassFactory; + HelixSimplePlayer *m_splayer; +}; + +class HSPClientContext : public IHXPreferences +{ +private: + LONG32 m_lRefCount; + LONG32 m_lClientIndex; + + HSPClientAdviceSink* m_pClientSink; + HSPErrorSink* m_pErrorSink; + HSPAuthenticationManager* m_pAuthMgr; + IHXPreferences* m_pDefaultPrefs; + char m_pszGUID[256]; + HelixSimplePlayer *m_splayer; + +public: + + HSPClientContext(LONG32 lClientIndex, HelixSimplePlayer *pSplay); + virtual ~HSPClientContext(); + + unsigned long position() { return m_pClientSink ? m_pClientSink->position() : 0; } + unsigned long duration() { return m_pClientSink ? m_pClientSink->duration() : 0; } + + void Init(IUnknown* /*IN*/ pUnknown, + IHXPreferences* /*IN*/ pPreferences, + char* /*IN*/ pszGUID); + void Close(); + + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj); + + STDMETHOD_(ULONG32,AddRef) (THIS); + STDMETHOD_(ULONG32,Release) (THIS); + + /* + * IHXPreferences methods + */ + STDMETHOD(ReadPref) (THIS_ const char* pref_key, + IHXBuffer*& buffer); + STDMETHOD(WritePref) (THIS_ const char* pref_key, + IHXBuffer* buffer); +}; + +#endif diff --git a/amarok/src/engine/helix/helix-sp/hsperror.cpp b/amarok/src/engine/helix/helix-sp/hsperror.cpp new file mode 100644 index 00000000..3042ebf8 --- /dev/null +++ b/amarok/src/engine/helix/helix-sp/hsperror.cpp @@ -0,0 +1,194 @@ +/* + * + * This software is released under the provisions of the GPL version 2. + * see file "COPYING". If that file is not available, the full statement + * of the license can be found at + * + * http://www.fsf.org/licensing/licenses/gpl.txt + * + * Portions Copyright (c) 1995-2002 RealNetworks, Inc. All Rights Reserved. + * Portions (c) Paul Cifarelli 2005 + * + */ +#include "hxcomm.h" +#include "hxerror.h" +#include "hxcore.h" +#include "hxbuffer.h" + +#include "hsperror.h" + +#include <stdio.h> + +#include "hxausvc.h" +#include "helix-sp.h" +#include "utils.h" + +HSPErrorSink::HSPErrorSink(IUnknown* pUnknown, HelixSimplePlayer *pSplay) + : m_lRefCount(0), + m_pPlayer(NULL), + m_splayer(pSplay) +{ + IHXClientEngine* pEngine = NULL; + pUnknown->QueryInterface(IID_IHXClientEngine, (void**)&pEngine ); + if( pEngine ) + { + IUnknown* pTmp = NULL; + pEngine->GetPlayer(0, pTmp); + m_pPlayer = (IHXPlayer*)pTmp; + } + + HX_RELEASE( pEngine ); + HX_ASSERT(m_pPlayer); +} + +HSPErrorSink::~HSPErrorSink() +{ + HX_RELEASE(m_pPlayer); +} + +// *** IUnknown methods *** + +///////////////////////////////////////////////////////////////////////// +// Method: +// IUnknown::QueryInterface +// Purpose: +// Implement this to export the interfaces supported by your +// object. +// +STDMETHODIMP HSPErrorSink::QueryInterface(REFIID riid, void** ppvObj) +{ + if (IsEqualIID(riid, IID_IUnknown)) + { + AddRef(); + *ppvObj = (IUnknown*)(IHXErrorSink*)this; + return HXR_OK; + } + else if (IsEqualIID(riid, IID_IHXErrorSink)) + { + AddRef(); + *ppvObj = (IHXErrorSink*) this; + return HXR_OK; + } + + *ppvObj = NULL; + return HXR_NOINTERFACE; +} + +///////////////////////////////////////////////////////////////////////// +// Method: +// IUnknown::AddRef +// Purpose: +// Everyone usually implements this the same... feel free to use +// this implementation. +// +STDMETHODIMP_(ULONG32) HSPErrorSink::AddRef() +{ + return InterlockedIncrement(&m_lRefCount); +} + +///////////////////////////////////////////////////////////////////////// +// Method: +// IUnknown::Release +// Purpose: +// Everyone usually implements this the same... feel free to use +// this implementation. +// +STDMETHODIMP_(ULONG32) HSPErrorSink::Release() +{ + if (InterlockedDecrement(&m_lRefCount) > 0) + { + return m_lRefCount; + } + + delete this; + return 0; +} + +/* + * IHXErrorSink methods + */ + +STDMETHODIMP +HSPErrorSink::ErrorOccurred(const UINT8 unSeverity, + const ULONG32 ulHXCode, + const ULONG32 ulUserCode, + const char* pUserString, + const char* pMoreInfoURL + ) +{ + char HXDefine[256]; /* Flawfinder: ignore */ + + // Store the code, so we can return it from main() + m_splayer->m_Error = ulHXCode; + + switch (unSeverity) + { + case HXLOG_NOTICE: + case HXLOG_INFO: + m_splayer->notifyUser(ulHXCode, + (pUserString && *pUserString) ? pUserString : "", + (pMoreInfoURL && *pMoreInfoURL) ? pMoreInfoURL : "" ); + break; + case HXLOG_WARNING: + case HXLOG_ERR: + case HXLOG_CRIT: + case HXLOG_ALERT: + case HXLOG_EMERG: + m_splayer->interruptUser(ulHXCode, + (pUserString && *pUserString) ? pUserString : "", + (pMoreInfoURL && *pMoreInfoURL) ? pMoreInfoURL : "" ); + break; + } + ConvertErrorToString(ulHXCode, HXDefine, 256); + + m_splayer->print2stdout("Report(%d, %ld, \"%s\", %ld, \"%s\", \"%s\")\n", + unSeverity, + ulHXCode, + (pUserString && *pUserString) ? pUserString : "(NULL)", + ulUserCode, + (pMoreInfoURL && *pMoreInfoURL) ? pMoreInfoURL : "(NULL)", + HXDefine); + + return HXR_OK; +} + +void +HSPErrorSink::ConvertErrorToString(const ULONG32 ulHXCode, char* pszBuffer, UINT32 ulBufLen) +{ + IHXErrorMessages* pErrMsg = NULL; + + if( !pszBuffer) + return; + + pszBuffer[0]='\0'; + + + HX_ASSERT(m_pPlayer); + if( m_pPlayer) + { + m_pPlayer->QueryInterface(IID_IHXErrorMessages, (void**)&pErrMsg); + if( pErrMsg ) + { + IHXBuffer* pMessage = pErrMsg->GetErrorText(ulHXCode); + if( pMessage ) + { + SafeStrCpy( pszBuffer, (const char*)pMessage->GetBuffer(), (int)ulBufLen); + pMessage->Release(); + } + else + m_splayer->print2stderr("NO expansion of error message available\n"); + + } + else + m_splayer->print2stderr("Unable to get Error Messages\n"); + } + + HX_RELEASE(pErrMsg); + + if( strlen(pszBuffer)==0 ) + { + SafeSprintf( pszBuffer, (int) ulBufLen, "Can't convert error code %lu - please find corresponding HXR code in common/include/hxresult.h", (unsigned long)ulHXCode ); + } + +} + diff --git a/amarok/src/engine/helix/helix-sp/hsperror.h b/amarok/src/engine/helix/helix-sp/hsperror.h new file mode 100644 index 00000000..f50e494d --- /dev/null +++ b/amarok/src/engine/helix/helix-sp/hsperror.h @@ -0,0 +1,70 @@ +/* + * + * This software is released under the provisions of the GPL version 2. + * see file "COPYING". If that file is not available, the full statement + * of the license can be found at + * + * http://www.fsf.org/licensing/licenses/gpl.txt + * + * Portions Copyright (c) 1995-2002 RealNetworks, Inc. All Rights Reserved. + * Portions (c) Paul Cifarelli 2005 + * + */ + +#ifndef _HSPERROR_ +#define _HSPERROR_ + +struct IUnknown; +struct IHXErrorMessages; +struct IHXPlayer; +class HelixSimplePlayer; + +class HSPErrorSink : public IHXErrorSink +{ +public: + + HSPErrorSink(IUnknown* pUnknown, HelixSimplePlayer *pSplay); + virtual ~HSPErrorSink(); + + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj); + + STDMETHOD_(ULONG32,AddRef) (THIS); + + STDMETHOD_(ULONG32,Release) (THIS); + + /* + * IHXErrorSink methods + */ + + /************************************************************************ + * Method: + * IHXErrorSink::ErrorOccurred + * Purpose: + * After you have registered your error sink with an IHXErrorSinkControl + * (either in the server or player core) this method will be called to + * report an error, event, or status message. + * + * The meaning of the arguments is exactly as described in + * hxerror.h + */ + STDMETHOD(ErrorOccurred) (THIS_ + const UINT8 unSeverity, + const ULONG32 ulHXCode, + const ULONG32 ulUserCode, + const char* pUserString, + const char* pMoreInfoURL + ); + +protected: + LONG32 m_lRefCount; + IHXPlayer* m_pPlayer; + HelixSimplePlayer *m_splayer; + + void ConvertErrorToString (const ULONG32 ulHXCode, char* pszBuffer, UINT32 ulBufLen); +}; +#endif diff --git a/amarok/src/engine/helix/helix-sp/hsphook.cpp b/amarok/src/engine/helix/helix-sp/hsphook.cpp new file mode 100644 index 00000000..3434a77c --- /dev/null +++ b/amarok/src/engine/helix/helix-sp/hsphook.cpp @@ -0,0 +1,758 @@ +/* ********** + * + * This software is released under the provisions of the GPL version 2. + * see file "COPYING". If that file is not available, the full statement + * of the license can be found at + * + * http://www.fsf.org/licensing/licenses/gpl.txt + * + * Copyright (c) Paul Cifarelli 2005 + * Portions Copyright (c) 1995-2002 RealNetworks, Inc. All Rights Reserved. + * PCM time-domain equalizer: + * (c) 2002 Felipe Rivera <liebremx at users sourceforge net> + * (c) 2004 Mark Kretschmann <markey@web.de> + * + * ********** */ +#include <math.h> +#include <stdlib.h> + +#include "hxcomm.h" +#include "hxcore.h" +#include "hxprefs.h" +#include "hxstrutl.h" +#include "hxvsrc.h" +#include "hxresult.h" +#include "hxausvc.h" +#include "helix-sp.h" +#ifndef HELIX_SW_VOLUME_INTERFACE +#include "gain.h" +#endif +#include "hsphook.h" +#include "iir_cf.h" // IIR filter coefficients +#include "hspalsadevice.h" + +#define SCOPESIZE 512 + +HSPPreMixAudioHook::HSPPreMixAudioHook(HelixSimplePlayer *player, int playerIndex, IHXAudioStream *pAudioStream, + bool fadein, unsigned long fadelength) : + m_Player(player), m_lRefCount(0), m_index(playerIndex), m_stream(pAudioStream), m_count(0), + m_gaintool(0), m_gaindb(0), m_fadein(fadein), m_fadeout(false), m_fadelength(fadelength) +{ + AddRef(); +} + +HSPPreMixAudioHook::~HSPPreMixAudioHook() +{ + if (m_gaintool) + gainFree(m_gaintool); +} + +STDMETHODIMP +HSPPreMixAudioHook::QueryInterface(REFIID riid, void**ppvObj) +{ + if(IsEqualIID(riid, IID_IUnknown)) + { + AddRef(); + *ppvObj = (IUnknown*)(IHXAudioHook *)this; + return HXR_OK; + } + else if(IsEqualIID(riid, IID_IHXAudioHook)) + { + AddRef(); + *ppvObj = (IHXAudioHook *)this; + return HXR_OK; + } + *ppvObj = NULL; + return HXR_NOINTERFACE; +} + +STDMETHODIMP_(UINT32) +HSPPreMixAudioHook::AddRef() +{ + return InterlockedIncrement(&m_lRefCount); +} + +STDMETHODIMP_(UINT32) +HSPPreMixAudioHook::Release() +{ + if (InterlockedDecrement(&m_lRefCount) > 0) + { + return m_lRefCount; + } + + delete this; + return 0; +} + +int HSPPreMixAudioHook::volumeize(unsigned char *data, unsigned char *outbuf, size_t len) +{ + gainFeed(data, outbuf, len, m_gaintool); + + return len; +} + +void HSPPreMixAudioHook::setFadeout(bool fadeout) +{ + m_fadeout = fadeout; + if (m_fadeout) + { + // the "time constant" (ms) is the time it takes to reach +/- 6db of the original + gainSetTimeConstant((float) m_fadelength / 8.0, m_gaintool); + gainSetSmoothdB(FADE_MIN_dB, m_gaintool); + } +} + +STDMETHODIMP HSPPreMixAudioHook::OnBuffer(HXAudioData *pAudioInData, HXAudioData *pAudioOutData) +{ + m_count++; + +#ifdef DEBUG_PURPOSES_ONLY + if (!(m_count % 100)) + { + m_Player->print2stderr("PRE: time: %d ", pAudioInData->ulAudioTime); + switch (pAudioInData->uAudioStreamType) + { + case INSTANTANEOUS_AUDIO: + m_Player->print2stderr(" INSTANTANEOUS_AUDIO "); + break; + case STREAMING_AUDIO: + m_Player->print2stderr(" STREAMING_AUDIO "); + break; + case TIMED_AUDIO: + m_Player->print2stderr(" TIMED_AUDIO "); + break; + case STREAMING_INSTANTANEOUS_AUDIO: + m_Player->print2stderr(" STREAMING_INSTANTANEOUS_AUDIO "); + break; + } + m_Player->print2stderr("pAudioOutData %lx, data %lx\n", pAudioOutData, pAudioOutData->pData); + } +#endif + + unsigned char *outbuf; + IHXBuffer *ibuf; + unsigned long len; + unsigned char *data; + + pAudioInData->pData->Get(data, len); + + // provide a little margin to prevent a slight but noticeable jump in vol when the fadein ends + if ((m_fadein && pAudioInData->ulAudioTime < 2*m_fadelength) || m_fadeout) + { + m_Player->pCommonClassFactory->CreateInstance(CLSID_IHXBuffer, (void **) &ibuf); + if (ibuf) + { + ibuf->SetSize(len); + outbuf = ibuf->GetBuffer(); + + len = volumeize(data, outbuf, len); + + pAudioOutData->pData = ibuf; + pAudioOutData->ulAudioTime = pAudioInData->ulAudioTime; + pAudioOutData->uAudioStreamType = pAudioInData->uAudioStreamType; + } + } + + return 0; +} + +STDMETHODIMP HSPPreMixAudioHook::OnInit(HXAudioFormat *pFormat) +{ + m_Player->print2stderr("PRE MIX HOOK OnInit AudioFormat: ch %d, bps %d, sps %ld, mbs %d\n", pFormat->uChannels, + pFormat->uBitsPerSample, + pFormat->ulSamplesPerSec, + pFormat->uMaxBlockSize); + + m_format = *pFormat; + + int bps = pFormat->uBitsPerSample / 8; + m_gaintool = gainInit(pFormat->ulSamplesPerSec, pFormat->uChannels, bps); + gainSetImmediatedB(0, m_gaintool); + + if (m_fadein) + { + gainSetImmediatedB(FADE_MIN_dB, m_gaintool); + // the "time constant" (ms) is the time it takes to reach -6db of the target + gainSetTimeConstant((float) m_fadelength / 2.0, m_gaintool); + gainSetSmoothdB(0, m_gaintool); + } + + return 0; +} + + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +HSPPostProcessor::HSPPostProcessor(HelixSimplePlayer *player, int playerIndex) : + m_Player(player), m_lRefCount(0), m_index(playerIndex), m_count(0), m_item(0), + m_current(0), m_prevtime(0), m_i(0), m_j(2), m_k(1) +#ifndef HELIX_SW_VOLUME_INTERFACE + , m_gaintool(0), m_gaindB(0.0) +#endif +{ + AddRef(); + memset(&m_format, 0, sizeof(m_format)); + + // zero the data_history, to eliminate the buzz on playing the first track after enabling the equalizer + memset(&data_history, 0, sizeof(data_history)); +} + +HSPPostProcessor::~HSPPostProcessor() +{ +#ifndef HELIX_SW_VOLUME_INTERFACE + if (m_gaintool) + gainFree(m_gaintool); +#endif +} + +STDMETHODIMP +HSPPostProcessor::QueryInterface(REFIID riid, void**ppvObj) +{ + if(IsEqualIID(riid, IID_IUnknown)) + { + AddRef(); + *ppvObj = (IUnknown*)(IHXAudioHook *)this; + return HXR_OK; + } + else if(IsEqualIID(riid, IID_IHXAudioHook)) + { + AddRef(); + *ppvObj = (IHXAudioHook *)this; + return HXR_OK; + } + *ppvObj = NULL; + return HXR_NOINTERFACE; +} + +STDMETHODIMP_(UINT32) +HSPPostProcessor::AddRef() +{ + return InterlockedIncrement(&m_lRefCount); +} + +STDMETHODIMP_(UINT32) +HSPPostProcessor::Release() +{ + if (InterlockedDecrement(&m_lRefCount) > 0) + { + return m_lRefCount; + } + + delete this; + return 0; +} + +STDMETHODIMP HSPPostProcessor::OnBuffer(HXAudioData *pAudioInData, HXAudioData *pAudioOutData) +{ + unsigned long len; + unsigned char *data; + + pAudioInData->pData->Get(data, len); + + m_count++; + +#ifdef DEBUG_PURPOSES_ONLY + if (!(m_count % 100)) + { + m_Player->print2stderr("POST: time: %d ", pAudioInData->ulAudioTime); + switch (pAudioInData->uAudioStreamType) + { + case INSTANTANEOUS_AUDIO: + m_Player->print2stderr(" INSTANTANEOUS_AUDIO "); + break; + case STREAMING_AUDIO: + m_Player->print2stderr(" STREAMING_AUDIO "); + break; + case TIMED_AUDIO: + m_Player->print2stderr(" TIMED_AUDIO "); + break; + case STREAMING_INSTANTANEOUS_AUDIO: + m_Player->print2stderr(" STREAMING_INSTANTANEOUS_AUDIO "); + break; + } + m_Player->print2stderr("len %d\n", len); + m_Player->print2stderr("pAudioOutData %lx, data %lx\n", pAudioOutData, pAudioOutData->pData); + + m_Player->print2stderr("Volume is %d\n",m_Player->getDirectHWVolume()); + } + +#endif + + +#ifndef HELIX_SW_VOLUME_INTERFACE + unsigned char *outbuf; + IHXBuffer *ibuf; + + m_Player->pCommonClassFactory->CreateInstance(CLSID_IHXBuffer, (void **) &ibuf); + if (ibuf) + { + ibuf->SetSize(len); + outbuf = ibuf->GetBuffer(); + + // equalize + if (m_Player->ppctrl[m_index]->volume && m_Player->isEQenabled() && m_format.uBitsPerSample == 16) + { + equalize(data, outbuf, len); + + // finally adjust the volume + len = volumeize(outbuf, len); + } + else + // finally adjust the volume + len = volumeize(data, outbuf, len); + + pAudioOutData->pData = ibuf; + pAudioOutData->ulAudioTime = pAudioInData->ulAudioTime; + pAudioOutData->uAudioStreamType = pAudioInData->uAudioStreamType; + } +#else + // equalize + if (m_Player->ppctrl[m_index]->volume && m_Player->isEQenabled() && m_format.uBitsPerSample == 16) + { + unsigned char *outbuf; + IHXBuffer *ibuf; + + m_Player->pCommonClassFactory->CreateInstance(CLSID_IHXBuffer, (void **) &ibuf); + if (ibuf) + { + ibuf->SetSize(len); + outbuf = ibuf->GetBuffer(); + equalize(data, outbuf, len); + pAudioOutData->pData = ibuf; + pAudioOutData->ulAudioTime = pAudioInData->ulAudioTime; + pAudioOutData->uAudioStreamType = pAudioInData->uAudioStreamType; + pAudioInData->pData->Release(); + } + } +#endif + + return 0; +} + +STDMETHODIMP HSPPostProcessor::OnInit(HXAudioFormat *pFormat) +{ + m_format = *pFormat; + m_count = 0; + m_prevtime = 0; + + // set the filter coefficients, in case we need to use the equalizer + switch(pFormat->ulSamplesPerSec) + { + case 8000: + //iir_cf = iir_cf10_8000; <-- doesn't work + iir_cf = iir_cf10_11k_11025; // works + break; + + case 11025: + //iir_cf = iir_cf10_11025; <-- not tested (cant get an encoder to give me this sfreq) + iir_cf = iir_cf10_11k_11025; // not tested, but works for 8k + break; + + case 16000: + //iir_cf = iir_cf10_16000; <-- doesn't work + iir_cf = iir_cf10_22k_22050; // works + break; + + case 22050: + //iir_cf = iir_cf10_22050; + iir_cf = iir_cf10_22k_22050; // this set actually works... + break; + + case 32000: + iir_cf = iir_cf10_32000; // works + break; + + case 48000: + iir_cf = iir_cf10_48000; // not tested + break; + + case 44100: + default: + iir_cf = iir_cf10_44100; // works + break; + } + + m_i = 0; + m_j = 2; + m_k = 1; + + memset(&data_history, 0, sizeof(data_history)); + +#ifndef HELIX_SW_VOLUME_INTERFACE + // setup the gain tool for volume + if (m_gaintool) + gainFree(m_gaintool); + + int bps = pFormat->uBitsPerSample / 8; + m_gaintool = gainInit(pFormat->ulSamplesPerSec, pFormat->uChannels, bps); + setGain(m_Player->ppctrl[m_index]->volume); +#endif + + return 0; +} + + +#ifndef HELIX_SW_VOLUME_INTERFACE +void HSPPostProcessor::setGain(int volume) +{ + if (m_gaintool) + { + if (volume == 0) + gainSetMute(m_gaintool); + else + { + //m_gaindB = GAIN_MIN_dB + (GAIN_MAX_dB - GAIN_MIN_dB) * (float) volume / 100.0; + //m_Player->print2stderr("GAIN set to %f\n", m_gaindB); + //gainSetImmediatedB(m_gaindB, m_gaintool); + + gainSetImmediate( (float) volume / 100.0, m_gaintool ); + } + } +} +#endif + + +void HSPPostProcessor::scopeify(unsigned long time, unsigned char *data, size_t len) +{ + int bytes_per_sample = m_format.uBitsPerSample / 8; + + // TODO: 32 bit samples + if ( (bytes_per_sample != 1 && bytes_per_sample != 2) ) + return; // no scope + + unsigned long scopebuf_timeinc = (unsigned long)(1000.0 * (double)len / ((double)m_format.ulSamplesPerSec * (double)bytes_per_sample)); + DelayQueue *item = new DelayQueue(len); + memcpy(item->buf, data, len); + item->len = len; + item->time = time; + item->etime = time + scopebuf_timeinc; + + m_prevtime = item->etime; + + item->nchan = m_format.uChannels; + item->bps = bytes_per_sample; + item->spb = len / item->nchan; + item->spb /= bytes_per_sample; + item->tps = (double) scopebuf_timeinc / (double) item->spb; + m_Player->addScopeBuf(item, m_index); +} + + +void HSPPostProcessor::updateEQgains(int pamp, vector<int> &equalizerGains) +{ + for (int i=0; i<EQ_CHANNELS; i++) + { + preamp[i] = (float) pamp * 0.01; + + + for (int j=0; j<EQ_MAX_BANDS; j++) + gain[j][i] = (float)(equalizerGains[j]) * 0.012 - 0.2; + } +} + + +void HSPPostProcessor::equalize(unsigned char *inbuf, unsigned char *outbuf, size_t length) +{ + int index, band, channel; + int tempint, halflength; + float out[EQ_CHANNELS], pcm[EQ_CHANNELS]; + short int *data = (short int *) inbuf; + short int *dataout = (short int *) outbuf; + + /** + * IIR filter equation is + * y[n] = 2 * (alpha*(x[n]-x[n-2]) + gamma*y[n-1] - beta*y[n-2]) + * + * NOTE: The 2 factor was introduced in the coefficients to save + * a multiplication + * + * This algorithm cascades two filters to get nice filtering + * at the expense of extra CPU cycles + */ + /* 16bit, 2 bytes per sample, so divide by two the length of + * the buffer (length is in bytes) + */ + halflength = (length >> 1); + for (index = 0; index < halflength; index+=m_format.uChannels) + { + /* For each channel */ + for (channel = 0; channel < m_format.uChannels; channel++) + { + pcm[channel] = (float) data[index+channel]; + + /* Preamp gain */ + pcm[channel] *= preamp[channel]; + + out[channel] = 0.; + /* For each band */ + for (band = 0; band < BAND_NUM; band++) + { + /* Store Xi(n) */ + data_history[band][channel].x[m_i] = pcm[channel]; + /* Calculate and store Yi(n) */ + data_history[band][channel].y[m_i] = + ( + /* = alpha * [x(n)-x(n-2)] */ + iir_cf[band].alpha * ( data_history[band][channel].x[m_i] + - data_history[band][channel].x[m_k]) + /* + gamma * y(n-1) */ + + iir_cf[band].gamma * data_history[band][channel].y[m_j] + /* - beta * y(n-2) */ + - iir_cf[band].beta * data_history[band][channel].y[m_k] + ); + /* + * The multiplication by 2.0 was 'moved' into the coefficients to save + * CPU cycles here */ + /* Apply the gain */ + out[channel] += data_history[band][channel].y[m_i]*gain[band][channel]; // * 2.0; + } /* For each band */ + + /* Volume stuff + Scale down original PCM sample and add it to the filters + output. This substitutes the multiplication by 0.25 + Go back to use the floating point multiplication before the + conversion to give more dynamic range + */ + out[channel] += pcm[channel]*0.25; + + /* Round and convert to integer */ + tempint = lrintf(out[channel]); + + /* Limit the output */ + if (tempint < -32768) + dataout[index+channel] = -32768; + else if (tempint > 32767) + dataout[index+channel] = 32767; + else + dataout[index+channel] = tempint; + } /* For each channel */ + + m_i++; m_j++; m_k++; + + /* Wrap around the indexes */ + if (m_i == 3) m_i = 0; + else if (m_j == 3) m_j = 0; + else m_k = 0; + }/* For each pair of samples */ +} + + +#ifndef HELIX_SW_VOLUME_INTERFACE +int HSPPostProcessor::volumeize(unsigned char *data, size_t len) +{ + gainFeed(data, data, len, m_gaintool); + + return len; +} + + +int HSPPostProcessor::volumeize(unsigned char *data, unsigned char *outbuf, size_t len) +{ + gainFeed(data, outbuf, len, m_gaintool); + + return len; +} +#endif + +HSPPostMixAudioHook::HSPPostMixAudioHook(HelixSimplePlayer *player, int playerIndex) : + m_Player(player), m_index(playerIndex), m_lRefCount(0), m_processor(0) +{ + AddRef(); + + m_Player->print2stderr("POST MIX HOOK CTOR\n"); + + m_processor = new HSPPostProcessor(player, playerIndex); +} + + +HSPPostMixAudioHook::~HSPPostMixAudioHook() +{ + m_processor->Release(); +} + +/* + * IUnknown methods + */ +STDMETHODIMP +HSPPostMixAudioHook::QueryInterface(REFIID riid, void** ppvObj) +{ + if(IsEqualIID(riid, IID_IUnknown)) + { + AddRef(); + *ppvObj = (IUnknown*)(IHXAudioHook *)this; + return HXR_OK; + } + else if(IsEqualIID(riid, IID_IHXAudioHook)) + { + AddRef(); + *ppvObj = (IHXAudioHook *)this; + return HXR_OK; + } + *ppvObj = NULL; + return HXR_NOINTERFACE; +} + + +STDMETHODIMP_(UINT32) +HSPPostMixAudioHook::AddRef() +{ + return InterlockedIncrement(&m_lRefCount); +} + +STDMETHODIMP_(UINT32) +HSPPostMixAudioHook::Release() +{ + if (InterlockedDecrement(&m_lRefCount) > 0) + { + return m_lRefCount; + } + + m_Player->print2stderr("DELETING POST MIX HOOK index %d\n", m_index); + delete this; + return 0; +} + +STDMETHODIMP HSPPostMixAudioHook::OnBuffer(HXAudioData *pAudioInData, HXAudioData */*pAudioOutData*/) +{ + unsigned long len; + unsigned char *data; + + pAudioInData->pData->Get(data, len); + + // feed the visualizations, if this is a local file (otherwise do it in the FinalHook) + if (m_Player->isLocal(m_index)) + m_processor->scopeify(pAudioInData->ulAudioTime, data, len); + + return 0; +} + +STDMETHODIMP HSPPostMixAudioHook::OnInit(HXAudioFormat *pFormat) +{ + m_Player->print2stderr("POST MIX HOOK OnInit AudioFormat: idx %d ch %d, bps %d, sps %ld, mbs %d\n", m_index, pFormat->uChannels, + pFormat->uBitsPerSample, + pFormat->ulSamplesPerSec, + pFormat->uMaxBlockSize); + + + return (m_processor->OnInit(pFormat)); +} + +void HSPPostMixAudioHook::updateEQgains(int preamp, vector<int> &equalizerGains) +{ + m_processor->updateEQgains(preamp, equalizerGains); +} + + +#ifndef HELIX_SW_VOLUME_INTERFACE + +void HSPPostMixAudioHook::setGain(int volume) +{ + m_processor->setGain(volume); +} + +#endif + + +HSPFinalAudioHook::HSPFinalAudioHook(HelixSimplePlayer *player) : m_Player(player), m_lRefCount(0), m_processor(0) +{ + AddRef(); + m_processor = new HSPPostProcessor(player, 0); +} + + +HSPFinalAudioHook::~HSPFinalAudioHook() +{ + m_processor->Release(); +} + +/* + * IUnknown methods + */ +STDMETHODIMP +HSPFinalAudioHook::QueryInterface(REFIID riid, void** ppvObj) +{ + if(IsEqualIID(riid, IID_IUnknown)) + { + AddRef(); + *ppvObj = (IUnknown*)(IHXAudioHook *)this; + return HXR_OK; + } + else if(IsEqualIID(riid, IID_IHXAudioHook)) + { + AddRef(); + *ppvObj = (IHXAudioHook *)this; + return HXR_OK; + } + *ppvObj = NULL; + return HXR_NOINTERFACE; +} + + +STDMETHODIMP_(UINT32) +HSPFinalAudioHook::AddRef() +{ + return InterlockedIncrement(&m_lRefCount); +} + +STDMETHODIMP_(UINT32) +HSPFinalAudioHook::Release() +{ + if (InterlockedDecrement(&m_lRefCount) > 0) + { + return m_lRefCount; + } + + delete this; + return 0; +} + +STDMETHODIMP HSPFinalAudioHook::OnBuffer(HXAudioData *pAudioInData, HXAudioData *pAudioOutData) +{ + unsigned long len; + unsigned char *data; + + pAudioInData->pData->Get(data, len); + + // feed the visualizations, if this is a live stream + bool anyLocal = false; + int i = 0; + while (i< m_Player->numPlayers()) + { + if (m_Player->isPlaying(i)) + m_processor->setIndex(i); // put the buffers on the queue of the player that's playing + if (anyLocal = m_Player->isLocal(i)) + break; + i++; + } + if (!anyLocal) + m_processor->scopeify(pAudioInData->ulAudioTime, data, len); + + return (m_processor->OnBuffer(pAudioInData, pAudioOutData)); +} + +STDMETHODIMP HSPFinalAudioHook::OnInit(HXAudioFormat *pFormat) +{ + m_Player->print2stderr("FINAL HOOK OnInit AudioFormat: ch %d, bps %d, sps %ld, mbs %d\n", pFormat->uChannels, + pFormat->uBitsPerSample, + pFormat->ulSamplesPerSec, + pFormat->uMaxBlockSize); + + return (m_processor->OnInit(pFormat)); +} + +void HSPFinalAudioHook::updateEQgains(int preamp, vector<int> &equalizerGains) +{ + m_processor->updateEQgains(preamp, equalizerGains); +} + + +#ifndef HELIX_SW_VOLUME_INTERFACE + +void HSPFinalAudioHook::setGain(int volume) +{ + m_processor->setGain(volume); +} + +#endif + + diff --git a/amarok/src/engine/helix/helix-sp/hsphook.h b/amarok/src/engine/helix/helix-sp/hsphook.h new file mode 100644 index 00000000..6983124f --- /dev/null +++ b/amarok/src/engine/helix/helix-sp/hsphook.h @@ -0,0 +1,250 @@ +/* ********** + * + * This software is released under the provisions of the GPL version 2. + * see file "COPYING". If that file is not available, the full statement + * of the license can be found at + * + * http://www.fsf.org/licensing/licenses/gpl.txt + * + * Copyright (c) Paul Cifarelli 2005 + * Portions Copyright (c) 1995-2002 RealNetworks, Inc. All Rights Reserved. + * PCM time-domain equalizer: + * (c) 2002 Felipe Rivera <liebremx at users sourceforge net> + * (c) 2004 Mark Kretschmann <markey@web.de> + * + * ********** */ +#ifndef _HSPHOOK_H_INCLUDED_ +#define _HSPHOOK_H_INCLUDED_ + +struct GAIN_STATE; + +#define FADE_MIN_dB -120 + +class HSPPreMixAudioHook : public IHXAudioHook +{ +public: + HSPPreMixAudioHook(HelixSimplePlayer *player, int playerIndex, IHXAudioStream *pAudioStream, + bool fadein = false, unsigned long fadelength = 0); + virtual ~HSPPreMixAudioHook(); + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj); + STDMETHOD_(ULONG32,AddRef) (THIS); + STDMETHOD_(ULONG32,Release) (THIS); + /* + * IHXAudioHook methods + */ + STDMETHOD(OnBuffer) (THIS_ + HXAudioData *pAudioInData, + HXAudioData *pAudioOutData); + STDMETHOD(OnInit) (THIS_ + HXAudioFormat *pFormat); + + void setFadeout(bool fadeout); + void setFadelength(unsigned long fadelength) { m_fadelength = fadelength; } + +private: + HSPPreMixAudioHook(); + + HelixSimplePlayer *m_Player; + LONG32 m_lRefCount; + int m_index; + IHXAudioStream *m_stream; + HXAudioFormat m_format; + int m_count; + + GAIN_STATE *m_gaintool; + float m_gaindb; + bool m_fadein; + bool m_fadeout; + unsigned long m_fadelength; + + int volumeize(unsigned char *data, unsigned char *outbuf, size_t len); +}; + + + +#define BAND_NUM 10 +#define EQ_MAX_BANDS 10 +#define EQ_CHANNELS 2 // Helix DNA currently only supports stereo + +// Floating point +typedef struct +{ + float beta; + float alpha; + float gamma; +} sIIRCoefficients; + +/* Coefficient history for the IIR filter */ +typedef struct +{ + float x[3]; /* x[n], x[n-1], x[n-2] */ + float y[3]; /* y[n], y[n-1], y[n-2] */ +} sXYData; + + +struct DelayQueue; + +class HSPPostProcessor : public IHXAudioHook +{ +public: + HSPPostProcessor(HelixSimplePlayer *player, int playerIndex); + virtual ~HSPPostProcessor(); + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj); + STDMETHOD_(ULONG32,AddRef) (THIS); + STDMETHOD_(ULONG32,Release) (THIS); + /* + * IHXAudioHook methods + */ + STDMETHOD(OnBuffer) (THIS_ + HXAudioData *pAudioInData, + HXAudioData *pAudioOutData); + STDMETHOD(OnInit) (THIS_ + HXAudioFormat *pFormat); + + void updateEQgains(int preamp, vector<int> &equalizerGains); + + void scopeify(unsigned long time, unsigned char *data, size_t len); + +#ifndef HELIX_SW_VOLUME_INTERFACE + void setGain(int volume); +#endif + + void setIndex(int playerIndex) { m_index = playerIndex; } + +private: + HSPPostProcessor(); + + void equalize(unsigned char *datain, unsigned char *dataout, size_t len); +#ifndef HELIX_SW_VOLUME_INTERFACE + int volumeize(unsigned char *data, size_t len); + int volumeize(unsigned char *data, unsigned char *outbuf, size_t len); + // returns samples (not bytes) + int unpack(unsigned char *data, size_t len, /*out*/ INT32 *signal); + // returns bytes (siglen is in samples) + int pack(INT32 *signal, size_t siglen, /*out*/ unsigned char *data); +#endif + + HelixSimplePlayer *m_Player; + LONG32 m_lRefCount; + int m_index; + HXAudioFormat m_format; + int m_count; + + // scope + struct DelayQueue *m_item; + int m_current; + unsigned long m_prevtime; + + // equalizer + + // Gain for each band + // values should be between -0.2 and 1.0 + float gain[EQ_MAX_BANDS][EQ_CHANNELS] __attribute__((aligned)); + // Volume gain + // values should be between 0.0 and 1.0 + float preamp[EQ_CHANNELS] __attribute__((aligned)); + // Coefficients + sIIRCoefficients* iir_cf; + sXYData data_history[EQ_MAX_BANDS][EQ_CHANNELS] __attribute__((aligned)); + // history indices + int m_i; + int m_j; + int m_k; + +#ifndef HELIX_SW_VOLUME_INTERFACE + // volume stuff + GAIN_STATE *m_gaintool; + float m_gaindB; +#endif +}; + + +class HSPPostMixAudioHook : public IHXAudioHook +{ +public: + HSPPostMixAudioHook(HelixSimplePlayer *player, int playerIndex); + virtual ~HSPPostMixAudioHook(); + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj); + STDMETHOD_(ULONG32,AddRef) (THIS); + STDMETHOD_(ULONG32,Release) (THIS); + /* + * IHXAudioHook methods + */ + STDMETHOD(OnBuffer) (THIS_ + HXAudioData *pAudioInData, + HXAudioData *pAudioOutData); + STDMETHOD(OnInit) (THIS_ + HXAudioFormat *pFormat); + + void updateEQgains(int preamp, vector<int> &equalizerGains); + +#ifndef HELIX_SW_VOLUME_INTERFACE + void setGain(int volume); +#endif + +private: + HSPPostMixAudioHook(); + + HelixSimplePlayer *m_Player; + int m_index; + LONG32 m_lRefCount; + + HSPPostProcessor *m_processor; +}; + + +class HSPFinalAudioHook : public IHXAudioHook +{ +public: + HSPFinalAudioHook(HelixSimplePlayer *player); + virtual ~HSPFinalAudioHook(); + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj); + STDMETHOD_(ULONG32,AddRef) (THIS); + STDMETHOD_(ULONG32,Release) (THIS); + /* + * IHXAudioHook methods + */ + STDMETHOD(OnBuffer) (THIS_ + HXAudioData *pAudioInData, + HXAudioData *pAudioOutData); + STDMETHOD(OnInit) (THIS_ + HXAudioFormat *pFormat); + + + void updateEQgains(int preamp, vector<int> &equalizerGains); + +#ifndef HELIX_SW_VOLUME_INTERFACE + void setGain(int volume); +#endif + +private: + HSPFinalAudioHook(); + + HelixSimplePlayer *m_Player; + LONG32 m_lRefCount; + + HSPPostProcessor *m_processor; +}; + + +#endif diff --git a/amarok/src/engine/helix/helix-sp/hspvoladvise.h b/amarok/src/engine/helix/helix-sp/hspvoladvise.h new file mode 100644 index 00000000..20b0572b --- /dev/null +++ b/amarok/src/engine/helix/helix-sp/hspvoladvise.h @@ -0,0 +1,52 @@ +/* + * + * This software is released under the provisions of the GPL version 2. + * see file "COPYING". If that file is not available, the full statement + * of the license can be found at + * + * http://www.fsf.org/licensing/licenses/gpl.txt + * + * Portions Copyright (c) 1995-2002 RealNetworks, Inc. All Rights Reserved. + * Portions (c) Paul Cifarelli 2005 + * + */ + +#ifndef _HSPVOLADVISE_INCLUDED_ +#define _HSPVOLADVISE_INCLUDED_ + +class HelixSimplePlayerVolumeAdvice : public IHXVolumeAdviseSink +{ +public: + HelixSimplePlayerVolumeAdvice(HelixSimplePlayer *player, int playerIndex) : m_Player(player),m_index(playerIndex),m_lRefCount(0) {} + virtual ~HelixSimplePlayerVolumeAdvice() {} + + /* + * IUnknown methods + */ + STDMETHOD(QueryInterface) (THIS_ + REFIID riid, + void** ppvObj); + + STDMETHOD_(ULONG32,AddRef) (THIS); + + STDMETHOD_(ULONG32,Release) (THIS); + + /* + * IHXVolumeAdviceSink methods + */ + STDMETHOD(OnVolumeChange) (THIS_ + const UINT16 uVolume + ); + STDMETHOD(OnMuteChange) (THIS_ + const BOOL bMute + ); + +private: + HelixSimplePlayerVolumeAdvice(); + HelixSimplePlayer *m_Player; + int m_index; + LONG32 m_lRefCount; + LONG32 m_lClientIndex; +}; + +#endif diff --git a/amarok/src/engine/helix/helix-sp/iids.cpp b/amarok/src/engine/helix/helix-sp/iids.cpp new file mode 100644 index 00000000..c199cc2f --- /dev/null +++ b/amarok/src/engine/helix/helix-sp/iids.cpp @@ -0,0 +1,21 @@ + +/* + * + * This software is released under the provisions of the GPL version 2. + * see file "COPYING". If that file is not available, the full statement + * of the license can be found at + * + * http://www.fsf.org/licensing/licenses/gpl.txt + * + * Portions Copyright (c) 1995-2002 RealNetworks, Inc. All Rights Reserved. + * Portions (c) Paul Cifarelli 2005 + */ + +// define all guids here once... +#define INITGUID +#define NCIHACK +#include "hxtypes.h" +#include "hxcom.h" +#include "hxiids.h" +#include "hxpiids.h" + diff --git a/amarok/src/engine/helix/helix-sp/iir_cf.h b/amarok/src/engine/helix/helix-sp/iir_cf.h new file mode 100644 index 00000000..a4958922 --- /dev/null +++ b/amarok/src/engine/helix/helix-sp/iir_cf.h @@ -0,0 +1,546 @@ +// IIR filter coefficient tables + +/* BETA, ALPHA, GAMMA */ +static sIIRCoefficients iir_cf10_11k_11025[] __attribute__((aligned)) = { + /* 31 Hz*/ +{ 9.8758524689e-01, 6.2073765555e-03, 1.9872750693e+00 }, + /* 62 Hz*/ +{ 9.7532461998e-01, 1.2337690008e-02, 1.9740916593e+00 }, + /* 125 Hz*/ +{ 9.5087485437e-01, 2.4562572817e-02, 1.9459267562e+00 }, + /* 250 Hz*/ +{ 9.0416308662e-01, 4.7918456688e-02, 1.8848691023e+00 }, + /* 500 Hz*/ +{ 8.1751373987e-01, 9.1243130064e-02, 1.7442229115e+00 }, + /* 1k Hz*/ +{ 6.6840529852e-01, 1.6579735074e-01, 1.4047189863e+00 }, + /* 2k Hz*/ +{ 4.4858358977e-01, 2.7570820511e-01, 6.0517475334e-01 }, + /* 3k Hz*/ +{ 3.1012671838e-01, 3.4493664081e-01, -1.8141012760e-01 }, + /* 4k Hz*/ +{ 2.4198119087e-01, 3.7900940457e-01, -8.0845085113e-01 }, + /* 5.5k Hz*/ +{ 3.3453245058e-01, 3.3273377471e-01, -1.3344985880e+00 }, +}; +static sIIRCoefficients iir_cf10_22k_22050[] __attribute__((aligned)) = { + /* 31 Hz*/ +{ 9.9377323686e-01, 3.1133815717e-03, 1.9936954495e+00 }, + /* 62 Hz*/ +{ 9.8758524689e-01, 6.2073765555e-03, 1.9872750693e+00 }, + /* 125 Hz*/ +{ 9.7512812040e-01, 1.2435939802e-02, 1.9738753198e+00 }, + /* 250 Hz*/ +{ 9.5087485437e-01, 2.4562572817e-02, 1.9459267562e+00 }, + /* 500 Hz*/ +{ 9.0416308662e-01, 4.7918456688e-02, 1.8848691023e+00 }, + /* 1k Hz*/ +{ 8.1751373987e-01, 9.1243130064e-02, 1.7442229115e+00 }, + /* 2k Hz*/ +{ 6.6840529852e-01, 1.6579735074e-01, 1.4047189863e+00 }, + /* 4k Hz*/ +{ 4.4858358977e-01, 2.7570820511e-01, 6.0517475334e-01 }, + /* 8k Hz*/ +{ 2.4198119087e-01, 3.7900940457e-01, -8.0845085113e-01 }, + /* 11k Hz*/ +{ 3.3453245058e-01, 3.3273377471e-01, -1.3344985880e+00 }, +}; +#ifdef _UNUSED_ +static sIIRCoefficients iir_cforiginal10_44100[] __attribute__((aligned)) = { + /* 60 Hz*/ +{ 9.9397349481e-01, 3.0132525945e-03, 1.9939006377e+00 }, + /* 170 Hz*/ +{ 9.8301906957e-01, 8.4904652142e-03, 1.9824374272e+00 }, + /* 310 Hz*/ +{ 9.6925150511e-01, 1.5374247445e-02, 1.9673310395e+00 }, + /* 600 Hz*/ +{ 9.4134330314e-01, 2.9328348430e-02, 1.9342541736e+00 }, + /* 1k Hz*/ +{ 9.0416308662e-01, 4.7918456688e-02, 1.8848691023e+00 }, + /* 3k Hz*/ +{ 7.3918401009e-01, 1.3040799496e-01, 1.5827185140e+00 }, + /* 6k Hz*/ +{ 5.4688945509e-01, 2.2655527245e-01, 1.0152665639e+00 }, + /* 12k Hz*/ +{ 3.1012671838e-01, 3.4493664081e-01, -1.8141012760e-01 }, + /* 14k Hz*/ +{ 2.6712322292e-01, 3.6643838854e-01, -5.2115143966e-01 }, + /* 16k Hz*/ +{ 2.4198119087e-01, 3.7900940457e-01, -8.0845085113e-01 }, +}; +static sIIRCoefficients iir_cforiginal10_48000[] __attribute__((aligned)) = { + /* 60 Hz*/ +{ 9.9446178985e-01, 2.7691050731e-03, 1.9944002760e+00 }, + /* 170 Hz*/ +{ 9.8438794122e-01, 7.8060293913e-03, 1.9838966333e+00 }, + /* 310 Hz*/ +{ 9.7171413384e-01, 1.4142933081e-02, 1.9700909975e+00 }, + /* 600 Hz*/ +{ 9.4597793866e-01, 2.7011030668e-02, 1.9399791381e+00 }, + /* 1k Hz*/ +{ 9.1159452679e-01, 4.4202736607e-02, 1.8952405706e+00 }, + /* 3k Hz*/ +{ 7.5755317065e-01, 1.2122341468e-01, 1.6237674017e+00 }, + /* 6k Hz*/ +{ 5.7422402554e-01, 2.1288798723e-01, 1.1131444836e+00 }, + /* 12k Hz*/ +{ 3.3730698905e-01, 3.3134650547e-01, 8.1883731790e-17 }, + /* 14k Hz*/ +{ 2.8947322018e-01, 3.5526338991e-01, -3.3374022753e-01 }, + /* 16k Hz*/ +{ 2.5620076154e-01, 3.7189961923e-01, -6.2810038077e-01 }, +}; +static sIIRCoefficients iir_cf10_8000[] __attribute__((aligned)) = { + /* 31 Hz*/ +{ 9.8293118010e-01, 8.5344099512e-03, 1.9823434752e+00 }, + /* 62 Hz*/ +{ 9.6615370528e-01, 1.6923147362e-02, 1.9638231211e+00 }, + /* 125 Hz*/ +{ 9.3293473908e-01, 3.3532630460e-02, 1.9236271300e+00 }, + /* 250 Hz*/ +{ 8.7036769704e-01, 6.4816151479e-02, 1.8344291062e+00 }, + /* 500 Hz*/ +{ 7.5755317065e-01, 1.2122341468e-01, 1.6237674017e+00 }, + /* 1k Hz*/ +{ 5.7422402554e-01, 2.1288798723e-01, 1.1131444836e+00 }, + /* 2k Hz*/ +{ 3.3730698905e-01, 3.3134650547e-01, 8.1883731790e-17 }, + /* 4k Hz*/ +{ 3.3730698905e-01, 3.3134650547e-01, -1.3373069891e+00 }, + /* 8k Hz*/ +{ -7.3760117555e+00, 4.1880058777e+00, -6.3760117555e+00 }, + /* 16k Hz*/ +{ -1.7632951026e+00, 1.3816475513e+00, -7.6329510259e-01 }, +}; +static sIIRCoefficients iir_cf10_11025[] __attribute__((aligned)) = { + /* 31 Hz*/ +{ 9.8758524689e-01, 6.2073765555e-03, 1.9872750693e+00 }, + /* 62 Hz*/ +{ 9.7532461998e-01, 1.2337690008e-02, 1.9740916593e+00 }, + /* 125 Hz*/ +{ 9.5087485437e-01, 2.4562572817e-02, 1.9459267562e+00 }, + /* 250 Hz*/ +{ 9.0416308662e-01, 4.7918456688e-02, 1.8848691023e+00 }, + /* 500 Hz*/ +{ 8.1751373987e-01, 9.1243130064e-02, 1.7442229115e+00 }, + /* 1k Hz*/ +{ 6.6840529852e-01, 1.6579735074e-01, 1.4047189863e+00 }, + /* 2k Hz*/ +{ 4.4858358977e-01, 2.7570820511e-01, 6.0517475334e-01 }, + /* 4k Hz*/ +{ 2.4198119087e-01, 3.7900940457e-01, -8.0845085113e-01 }, + /* 8k Hz*/ +{ -1.2157171596e+00, 1.1078585798e+00, 3.2910548979e-02 }, + /* 16k Hz*/ +{ -1.1844379135e+00, 1.0922189568e+00, 1.7585210768e-01 }, +}; +static sIIRCoefficients iir_cf10_16000[] __attribute__((aligned)) = { + /* 31 Hz*/ +{ 9.9142885790e-01, 4.2855710504e-03, 1.9912812966e+00 }, + /* 62 Hz*/ +{ 9.8293118010e-01, 8.5344099512e-03, 1.9823434752e+00 }, + /* 125 Hz*/ +{ 9.6588546081e-01, 1.7057269597e-02, 1.9635174657e+00 }, + /* 250 Hz*/ +{ 9.3293473908e-01, 3.3532630460e-02, 1.9236271300e+00 }, + /* 500 Hz*/ +{ 8.7036769704e-01, 6.4816151479e-02, 1.8344291062e+00 }, + /* 1k Hz*/ +{ 7.5755317065e-01, 1.2122341468e-01, 1.6237674017e+00 }, + /* 2k Hz*/ +{ 5.7422402554e-01, 2.1288798723e-01, 1.1131444836e+00 }, + /* 4k Hz*/ +{ 3.3730698905e-01, 3.3134650547e-01, 8.1883731790e-17 }, + /* 8k Hz*/ +{ 3.3730698905e-01, 3.3134650547e-01, -1.3373069891e+00 }, + /* 16k Hz*/ +{ -7.3760117555e+00, 4.1880058777e+00, -6.3760117555e+00 }, +}; +static sIIRCoefficients iir_cf10_22050[] __attribute__((aligned)) = { + /* 31 Hz*/ +{ 9.9377323686e-01, 3.1133815717e-03, 1.9936954495e+00 }, + /* 62 Hz*/ +{ 9.8758524689e-01, 6.2073765555e-03, 1.9872750693e+00 }, + /* 125 Hz*/ +{ 9.7512812040e-01, 1.2435939802e-02, 1.9738753198e+00 }, + /* 250 Hz*/ +{ 9.5087485437e-01, 2.4562572817e-02, 1.9459267562e+00 }, + /* 500 Hz*/ +{ 9.0416308662e-01, 4.7918456688e-02, 1.8848691023e+00 }, + /* 1k Hz*/ +{ 8.1751373987e-01, 9.1243130064e-02, 1.7442229115e+00 }, + /* 2k Hz*/ +{ 6.6840529852e-01, 1.6579735074e-01, 1.4047189863e+00 }, + /* 4k Hz*/ +{ 4.4858358977e-01, 2.7570820511e-01, 6.0517475334e-01 }, + /* 8k Hz*/ +{ 2.4198119087e-01, 3.7900940457e-01, -8.0845085113e-01 }, + /* 16k Hz*/ +{ -1.2157171596e+00, 1.1078585798e+00, 3.2910548979e-02 }, +}; +#endif +static sIIRCoefficients iir_cf10_32000[] __attribute__((aligned)) = { + /* 31 Hz*/ +{ 9.9570520792e-01, 2.1473960411e-03, 1.9956682380e+00 }, + /* 62 Hz*/ +{ 9.9142885790e-01, 4.2855710504e-03, 1.9912812966e+00 }, + /* 125 Hz*/ +{ 9.8279471928e-01, 8.6026403580e-03, 1.9821975386e+00 }, + /* 250 Hz*/ +{ 9.6588546081e-01, 1.7057269597e-02, 1.9635174657e+00 }, + /* 500 Hz*/ +{ 9.3293473908e-01, 3.3532630460e-02, 1.9236271300e+00 }, + /* 1k Hz*/ +{ 8.7036769704e-01, 6.4816151479e-02, 1.8344291062e+00 }, + /* 2k Hz*/ +{ 7.5755317065e-01, 1.2122341468e-01, 1.6237674017e+00 }, + /* 4k Hz*/ +{ 5.7422402554e-01, 2.1288798723e-01, 1.1131444836e+00 }, + /* 8k Hz*/ +{ 3.3730698905e-01, 3.3134650547e-01, 8.1883731790e-17 }, + /* 16k Hz*/ +{ 3.3730698905e-01, 3.3134650547e-01, -1.3373069891e+00 }, +}; +static sIIRCoefficients iir_cf10_44100[] __attribute__((aligned)) = { + /* 31 Hz*/ +{ 9.9688176273e-01, 1.5591186337e-03, 1.9968622855e+00 }, + /* 62 Hz*/ +{ 9.9377323686e-01, 3.1133815717e-03, 1.9936954495e+00 }, + /* 125 Hz*/ +{ 9.8748575691e-01, 6.2571215431e-03, 1.9871705722e+00 }, + /* 250 Hz*/ +{ 9.7512812040e-01, 1.2435939802e-02, 1.9738753198e+00 }, + /* 500 Hz*/ +{ 9.5087485437e-01, 2.4562572817e-02, 1.9459267562e+00 }, + /* 1k Hz*/ +{ 9.0416308662e-01, 4.7918456688e-02, 1.8848691023e+00 }, + /* 2k Hz*/ +{ 8.1751373987e-01, 9.1243130064e-02, 1.7442229115e+00 }, + /* 4k Hz*/ +{ 6.6840529852e-01, 1.6579735074e-01, 1.4047189863e+00 }, + /* 8k Hz*/ +{ 4.4858358977e-01, 2.7570820511e-01, 6.0517475334e-01 }, + /* 16k Hz*/ +{ 2.4198119087e-01, 3.7900940457e-01, -8.0845085113e-01 }, +}; +static sIIRCoefficients iir_cf10_48000[] __attribute__((aligned)) = { + /* 31 Hz*/ +{ 9.9713475915e-01, 1.4326204244e-03, 1.9971183163e+00 }, + /* 62 Hz*/ +{ 9.9427771143e-01, 2.8611442874e-03, 1.9942120343e+00 }, + /* 125 Hz*/ +{ 9.8849666727e-01, 5.7516663664e-03, 1.9882304829e+00 }, + /* 250 Hz*/ +{ 9.7712566171e-01, 1.1437169144e-02, 1.9760670839e+00 }, + /* 500 Hz*/ +{ 9.5477456091e-01, 2.2612719547e-02, 1.9505892385e+00 }, + /* 1k Hz*/ +{ 9.1159452679e-01, 4.4202736607e-02, 1.8952405706e+00 }, + /* 2k Hz*/ +{ 8.3100647694e-01, 8.4496761532e-02, 1.7686164442e+00 }, + /* 4k Hz*/ +{ 6.9062328809e-01, 1.5468835596e-01, 1.4641227157e+00 }, + /* 8k Hz*/ +{ 4.7820368352e-01, 2.6089815824e-01, 7.3910184176e-01 }, + /* 16k Hz*/ +{ 2.5620076154e-01, 3.7189961923e-01, -6.2810038077e-01 }, +}; +#ifdef _UNUSED_ +static sIIRCoefficients iir_cf15_44100[] __attribute__((aligned)) = { + /* 25 Hz*/ +{ 9.9834072702e-01, 8.2963648917e-04, 1.9983280505e+00 }, + /* 40 Hz*/ +{ 9.9734652663e-01, 1.3267366865e-03, 1.9973140908e+00 }, + /* 63 Hz*/ +{ 9.9582396353e-01, 2.0880182333e-03, 1.9957435641e+00 }, + /* 100 Hz*/ +{ 9.9337951306e-01, 3.3102434709e-03, 1.9931771947e+00 }, + /* 160 Hz*/ +{ 9.8942832039e-01, 5.2858398053e-03, 1.9889114258e+00 }, + /* 250 Hz*/ +{ 9.8353109588e-01, 8.2344520610e-03, 1.9822729654e+00 }, + /* 400 Hz*/ +{ 9.7378088082e-01, 1.3109559588e-02, 1.9705764276e+00 }, + /* 630 Hz*/ +{ 9.5901979676e-01, 2.0490101620e-02, 1.9511333590e+00 }, + /* 1k Hz*/ +{ 9.3574903986e-01, 3.2125480071e-02, 1.9161350100e+00 }, + /* 1.6k Hz*/ +{ 8.9923630641e-01, 5.0381846793e-02, 1.8501014162e+00 }, + /* 2.5k Hz*/ +{ 8.4722457681e-01, 7.6387711593e-02, 1.7312785699e+00 }, + /* 4k Hz*/ +{ 7.6755471307e-01, 1.1622264346e-01, 1.4881981417e+00 }, + /* 6.3k Hz*/ +{ 6.6125377473e-01, 1.6937311263e-01, 1.0357747868e+00 }, + /* 10k Hz*/ +{ 5.2683267950e-01, 2.3658366025e-01, 2.2218349322e-01 }, + /* 16k Hz*/ +{ 4.0179628792e-01, 2.9910185604e-01, -9.1248032613e-01 }, +}; +static sIIRCoefficients iir_cf15_48000[] __attribute__((aligned)) = { + /* 25 Hz*/ +{ 9.9847546664e-01, 7.6226668143e-04, 1.9984647656e+00 }, + /* 40 Hz*/ +{ 9.9756184654e-01, 1.2190767289e-03, 1.9975344645e+00 }, + /* 63 Hz*/ +{ 9.9616261379e-01, 1.9186931041e-03, 1.9960947369e+00 }, + /* 100 Hz*/ +{ 9.9391578543e-01, 3.0421072865e-03, 1.9937449618e+00 }, + /* 160 Hz*/ +{ 9.9028307215e-01, 4.8584639242e-03, 1.9898465702e+00 }, + /* 250 Hz*/ +{ 9.8485897264e-01, 7.5705136795e-03, 1.9837962543e+00 }, + /* 400 Hz*/ +{ 9.7588512657e-01, 1.2057436715e-02, 1.9731772447e+00 }, + /* 630 Hz*/ +{ 9.6228521814e-01, 1.8857390928e-02, 1.9556164694e+00 }, + /* 1k Hz*/ +{ 9.4080933132e-01, 2.9595334338e-02, 1.9242054384e+00 }, + /* 1.6k Hz*/ +{ 9.0702059196e-01, 4.6489704022e-02, 1.8653476166e+00 }, + /* 2.5k Hz*/ +{ 8.5868004289e-01, 7.0659978553e-02, 1.7600401337e+00 }, + /* 4k Hz*/ +{ 7.8409610788e-01, 1.0795194606e-01, 1.5450725522e+00 }, + /* 6.3k Hz*/ +{ 6.8332861002e-01, 1.5833569499e-01, 1.1426447155e+00 }, + /* 10k Hz*/ +{ 5.5267518228e-01, 2.2366240886e-01, 4.0186190803e-01 }, + /* 16k Hz*/ +{ 4.1811888447e-01, 2.9094055777e-01, -7.0905944223e-01 }, +}; +static sIIRCoefficients iir_cf25_44100[] __attribute__((aligned)) = { + /* 20 Hz*/ +{ 9.9934037157e-01, 3.2981421662e-04, 1.9993322545e+00 }, + /* 31.5 Hz*/ +{ 9.9896129025e-01, 5.1935487310e-04, 1.9989411587e+00 }, + /* 40 Hz*/ +{ 9.9868118265e-01, 6.5940867495e-04, 1.9986487252e+00 }, + /* 50 Hz*/ +{ 9.9835175161e-01, 8.2412419683e-04, 1.9983010452e+00 }, + /* 80 Hz*/ +{ 9.9736411067e-01, 1.3179446674e-03, 1.9972343673e+00 }, + /* 100 Hz*/ +{ 9.9670622662e-01, 1.6468866919e-03, 1.9965035707e+00 }, + /* 125 Hz*/ +{ 9.9588448566e-01, 2.0577571681e-03, 1.9955679690e+00 }, + /* 160 Hz*/ +{ 9.9473519326e-01, 2.6324033689e-03, 1.9942169198e+00 }, + /* 250 Hz*/ +{ 9.9178600786e-01, 4.1069960678e-03, 1.9905226414e+00 }, + /* 315 Hz*/ +{ 9.8966154150e-01, 5.1692292513e-03, 1.9876580847e+00 }, + /* 400 Hz*/ +{ 9.8689036168e-01, 6.5548191616e-03, 1.9836646251e+00 }, + /* 500 Hz*/ +{ 9.8364027156e-01, 8.1798642207e-03, 1.9786090689e+00 }, + /* 800 Hz*/ +{ 9.7395577681e-01, 1.3022111597e-02, 1.9611472340e+00 }, + /* 1k Hz*/ +{ 9.6755437936e-01, 1.6222810321e-02, 1.9476180811e+00 }, + /* 1.25k Hz*/ +{ 9.5961458750e-01, 2.0192706249e-02, 1.9286193446e+00 }, + /* 1.6k Hz*/ +{ 9.4861481164e-01, 2.5692594182e-02, 1.8982024567e+00 }, + /* 2.5k Hz*/ +{ 9.2095325455e-01, 3.9523372724e-02, 1.8003794694e+00 }, + /* 3.15k Hz*/ +{ 9.0153642498e-01, 4.9231787512e-02, 1.7132251201e+00 }, + /* 4k Hz*/ +{ 8.7685876255e-01, 6.1570618727e-02, 1.5802270232e+00 }, + /* 5k Hz*/ +{ 8.4886734822e-01, 7.5566325889e-02, 1.3992391376e+00 }, + /* 8k Hz*/ +{ 7.7175298860e-01, 1.1412350570e-01, 7.4018523020e-01 }, + /* 10k Hz*/ +{ 7.2627049462e-01, 1.3686475269e-01, 2.5120552756e-01 }, + /* 12.5k Hz*/ +{ 6.7674787974e-01, 1.6162606013e-01, -3.4978377639e-01 }, + /* 16k Hz*/ +{ 6.2482197550e-01, 1.8758901225e-01, -1.0576558797e+00 }, + /* 20k Hz*/ +{ 6.1776148240e-01, 1.9111925880e-01, -1.5492465594e+00 }, +}; +static sIIRCoefficients iir_cf25_48000[] __attribute__((aligned)) = { + /* 20 Hz*/ +{ 9.9939388451e-01, 3.0305774630e-04, 1.9993870327e+00 }, + /* 31.5 Hz*/ +{ 9.9904564663e-01, 4.7717668529e-04, 1.9990286528e+00 }, + /* 40 Hz*/ +{ 9.9878827195e-01, 6.0586402557e-04, 1.9987608731e+00 }, + /* 50 Hz*/ +{ 9.9848556942e-01, 7.5721528829e-04, 1.9984427652e+00 }, + /* 80 Hz*/ +{ 9.9757801538e-01, 1.2109923088e-03, 1.9974684869e+00 }, + /* 100 Hz*/ +{ 9.9697343933e-01, 1.5132803374e-03, 1.9968023538e+00 }, + /* 125 Hz*/ +{ 9.9621823598e-01, 1.8908820086e-03, 1.9959510180e+00 }, + /* 160 Hz*/ +{ 9.9516191728e-01, 2.4190413595e-03, 1.9947243453e+00 }, + /* 250 Hz*/ +{ 9.9245085008e-01, 3.7745749576e-03, 1.9913840669e+00 }, + /* 315 Hz*/ +{ 9.9049749914e-01, 4.7512504310e-03, 1.9888056233e+00 }, + /* 400 Hz*/ +{ 9.8794899744e-01, 6.0255012789e-03, 1.9852245824e+00 }, + /* 500 Hz*/ +{ 9.8495930023e-01, 7.5203498850e-03, 1.9807093500e+00 }, + /* 800 Hz*/ +{ 9.7604570090e-01, 1.1977149551e-02, 1.9652207158e+00 }, + /* 1k Hz*/ +{ 9.7014963927e-01, 1.4925180364e-02, 1.9532947360e+00 }, + /* 1.25k Hz*/ +{ 9.6283181641e-01, 1.8584091793e-02, 1.9366149237e+00 }, + /* 1.6k Hz*/ +{ 9.5268463224e-01, 2.3657683878e-02, 1.9100137880e+00 }, + /* 2.5k Hz*/ +{ 9.2711765003e-01, 3.6441174983e-02, 1.8248457659e+00 }, + /* 3.15k Hz*/ +{ 9.0912548757e-01, 4.5437256213e-02, 1.7491177803e+00 }, + /* 4k Hz*/ +{ 8.8619860800e-01, 5.6900696000e-02, 1.6334959111e+00 }, + /* 5k Hz*/ +{ 8.6010264114e-01, 6.9948679430e-02, 1.4757186436e+00 }, + /* 8k Hz*/ +{ 7.8757448309e-01, 1.0621275845e-01, 8.9378724155e-01 }, + /* 10k Hz*/ +{ 7.4415362476e-01, 1.2792318762e-01, 4.5142017567e-01 }, + /* 12.5k Hz*/ +{ 6.9581428034e-01, 1.5209285983e-01, -1.1091156053e-01 }, + /* 16k Hz*/ +{ 6.4120506488e-01, 1.7939746756e-01, -8.2060253244e-01 }, + /* 20k Hz*/ +{ 6.0884213704e-01, 1.9557893148e-01, -1.3932981614e+00 }, +}; +static sIIRCoefficients iir_cf31_44100[] __attribute__((aligned)) = { + /* 20 Hz*/ +{ 9.9934037157e-01, 3.2981421662e-04, 1.9993322545e+00 }, + /* 25 Hz*/ +{ 9.9917555233e-01, 4.1222383516e-04, 1.9991628705e+00 }, + /* 31.5 Hz*/ +{ 9.9896129025e-01, 5.1935487310e-04, 1.9989411587e+00 }, + /* 40 Hz*/ +{ 9.9868118265e-01, 6.5940867495e-04, 1.9986487252e+00 }, + /* 50 Hz*/ +{ 9.9835175161e-01, 8.2412419683e-04, 1.9983010452e+00 }, + /* 63 Hz*/ +{ 9.9792365217e-01, 1.0381739160e-03, 1.9978431682e+00 }, + /* 80 Hz*/ +{ 9.9736411067e-01, 1.3179446674e-03, 1.9972343673e+00 }, + /* 100 Hz*/ +{ 9.9670622662e-01, 1.6468866919e-03, 1.9965035707e+00 }, + /* 125 Hz*/ +{ 9.9588448566e-01, 2.0577571681e-03, 1.9955679690e+00 }, + /* 160 Hz*/ +{ 9.9473519326e-01, 2.6324033689e-03, 1.9942169198e+00 }, + /* 200 Hz*/ +{ 9.9342335280e-01, 3.2883236020e-03, 1.9926141028e+00 }, + /* 250 Hz*/ +{ 9.9178600786e-01, 4.1069960678e-03, 1.9905226414e+00 }, + /* 315 Hz*/ +{ 9.8966154150e-01, 5.1692292513e-03, 1.9876580847e+00 }, + /* 400 Hz*/ +{ 9.8689036168e-01, 6.5548191616e-03, 1.9836646251e+00 }, + /* 500 Hz*/ +{ 9.8364027156e-01, 8.1798642207e-03, 1.9786090689e+00 }, + /* 630 Hz*/ +{ 9.7943153305e-01, 1.0284233476e-02, 1.9714629236e+00 }, + /* 800 Hz*/ +{ 9.7395577681e-01, 1.3022111597e-02, 1.9611472340e+00 }, + /* 1k Hz*/ +{ 9.6755437936e-01, 1.6222810321e-02, 1.9476180811e+00 }, + /* 1.25k Hz*/ +{ 9.5961458750e-01, 2.0192706249e-02, 1.9286193446e+00 }, + /* 1.6k Hz*/ +{ 9.4861481164e-01, 2.5692594182e-02, 1.8982024567e+00 }, + /* 2k Hz*/ +{ 9.3620971896e-01, 3.1895140519e-02, 1.8581325022e+00 }, + /* 2.5k Hz*/ +{ 9.2095325455e-01, 3.9523372724e-02, 1.8003794694e+00 }, + /* 3.15k Hz*/ +{ 9.0153642498e-01, 4.9231787512e-02, 1.7132251201e+00 }, + /* 4k Hz*/ +{ 8.7685876255e-01, 6.1570618727e-02, 1.5802270232e+00 }, + /* 5k Hz*/ +{ 8.4886734822e-01, 7.5566325889e-02, 1.3992391376e+00 }, + /* 6.3k Hz*/ +{ 8.1417575446e-01, 9.2912122771e-02, 1.1311200817e+00 }, + /* 8k Hz*/ +{ 7.7175298860e-01, 1.1412350570e-01, 7.4018523020e-01 }, + /* 10k Hz*/ +{ 7.2627049462e-01, 1.3686475269e-01, 2.5120552756e-01 }, + /* 12.5k Hz*/ +{ 6.7674787974e-01, 1.6162606013e-01, -3.4978377639e-01 }, + /* 16k Hz*/ +{ 6.2482197550e-01, 1.8758901225e-01, -1.0576558797e+00 }, + /* 20k Hz*/ +{ 6.1776148240e-01, 1.9111925880e-01, -1.5492465594e+00 }, +}; +static sIIRCoefficients iir_cf31_48000[] __attribute__((aligned)) = { + /* 20 Hz*/ +{ 9.9939388451e-01, 3.0305774630e-04, 1.9993870327e+00 }, + /* 25 Hz*/ +{ 9.9924247917e-01, 3.7876041632e-04, 1.9992317740e+00 }, + /* 31.5 Hz*/ +{ 9.9904564663e-01, 4.7717668529e-04, 1.9990286528e+00 }, + /* 40 Hz*/ +{ 9.9878827195e-01, 6.0586402557e-04, 1.9987608731e+00 }, + /* 50 Hz*/ +{ 9.9848556942e-01, 7.5721528829e-04, 1.9984427652e+00 }, + /* 63 Hz*/ +{ 9.9809219264e-01, 9.5390367779e-04, 1.9980242502e+00 }, + /* 80 Hz*/ +{ 9.9757801538e-01, 1.2109923088e-03, 1.9974684869e+00 }, + /* 100 Hz*/ +{ 9.9697343933e-01, 1.5132803374e-03, 1.9968023538e+00 }, + /* 125 Hz*/ +{ 9.9621823598e-01, 1.8908820086e-03, 1.9959510180e+00 }, + /* 160 Hz*/ +{ 9.9516191728e-01, 2.4190413595e-03, 1.9947243453e+00 }, + /* 200 Hz*/ +{ 9.9395607757e-01, 3.0219612131e-03, 1.9932727986e+00 }, + /* 250 Hz*/ +{ 9.9245085008e-01, 3.7745749576e-03, 1.9913840669e+00 }, + /* 315 Hz*/ +{ 9.9049749914e-01, 4.7512504310e-03, 1.9888056233e+00 }, + /* 400 Hz*/ +{ 9.8794899744e-01, 6.0255012789e-03, 1.9852245824e+00 }, + /* 500 Hz*/ +{ 9.8495930023e-01, 7.5203498850e-03, 1.9807093500e+00 }, + /* 630 Hz*/ +{ 9.8108651246e-01, 9.4567437704e-03, 1.9743538683e+00 }, + /* 800 Hz*/ +{ 9.7604570090e-01, 1.1977149551e-02, 1.9652207158e+00 }, + /* 1k Hz*/ +{ 9.7014963927e-01, 1.4925180364e-02, 1.9532947360e+00 }, + /* 1.25k Hz*/ +{ 9.6283181641e-01, 1.8584091793e-02, 1.9366149237e+00 }, + /* 1.6k Hz*/ +{ 9.5268463224e-01, 2.3657683878e-02, 1.9100137880e+00 }, + /* 2k Hz*/ +{ 9.4122788957e-01, 2.9386055213e-02, 1.8750821533e+00 }, + /* 2.5k Hz*/ +{ 9.2711765003e-01, 3.6441174983e-02, 1.8248457659e+00 }, + /* 3.15k Hz*/ +{ 9.0912548757e-01, 4.5437256213e-02, 1.7491177803e+00 }, + /* 4k Hz*/ +{ 8.8619860800e-01, 5.6900696000e-02, 1.6334959111e+00 }, + /* 5k Hz*/ +{ 8.6010264114e-01, 6.9948679430e-02, 1.4757186436e+00 }, + /* 6.3k Hz*/ +{ 8.2760520925e-01, 8.6197395374e-02, 1.2405797786e+00 }, + /* 8k Hz*/ +{ 7.8757448309e-01, 1.0621275845e-01, 8.9378724155e-01 }, + /* 10k Hz*/ +{ 7.4415362476e-01, 1.2792318762e-01, 4.5142017567e-01 }, + /* 12.5k Hz*/ +{ 6.9581428034e-01, 1.5209285983e-01, -1.1091156053e-01 }, + /* 16k Hz*/ +{ 6.4120506488e-01, 1.7939746756e-01, -8.2060253244e-01 }, + /* 20k Hz*/ +{ 6.0884213704e-01, 1.9557893148e-01, -1.3932981614e+00 }, +}; + +#endif diff --git a/amarok/src/engine/helix/helix-sp/utils.cpp b/amarok/src/engine/helix/helix-sp/utils.cpp new file mode 100644 index 00000000..6605ad04 --- /dev/null +++ b/amarok/src/engine/helix/helix-sp/utils.cpp @@ -0,0 +1,36 @@ +/* + * + * This software is released under the provisions of the GPL version 2. + * see file "COPYING". If that file is not available, the full statement + * of the license can be found at + * + * http://www.fsf.org/licensing/licenses/gpl.txt + * + * Portions Copyright (c) 1995-2002 RealNetworks, Inc. All Rights Reserved. + * Portions (c) Paul Cifarelli 2005 + */ + +#include <stdio.h> +#include <stdarg.h> +#include <string.h> +#include "utils.h" + + +int SafeSprintf(char *str, int max, const char *fmt, ...) +{ + int ret; + va_list ap; + va_start(ap, fmt); + ret = vsnprintf(str,max,fmt,ap); + va_end(ap); + return ret; +} + +char *SafeStrCpy(char *str1, const char *str2, int sz) +{ + int len = strlen(str2); + if (len > sz) + return 0; + + return (strcpy(str1,str2)); +} diff --git a/amarok/src/engine/helix/helix-sp/utils.h b/amarok/src/engine/helix/helix-sp/utils.h new file mode 100644 index 00000000..e636d442 --- /dev/null +++ b/amarok/src/engine/helix/helix-sp/utils.h @@ -0,0 +1,22 @@ +/* + * + * This software is released under the provisions of the GPL version 2. + * see file "COPYING". If that file is not available, the full statement + * of the license can be found at + * + * http://www.fsf.org/licensing/licenses/gpl.txt + * + * Portions Copyright (c) 1995-2002 RealNetworks, Inc. All Rights Reserved. + * Portions (c) Paul Cifarelli 2005 + */ +#ifndef _HELIXUTILS_ +#define _HELIXUTILS_ + +int SafeSprintf(char *str, int max, const char *fmt, ...) +#ifdef __GNUC__ +__attribute__ ((format (printf, 3, 4))) +#endif +; +char *SafeStrCpy(char *str1, const char *str2, int sz); + +#endif diff --git a/amarok/src/engine/helix/hxplayercontrol.cpp b/amarok/src/engine/helix/hxplayercontrol.cpp new file mode 100644 index 00000000..6c76eac2 --- /dev/null +++ b/amarok/src/engine/helix/hxplayercontrol.cpp @@ -0,0 +1,1296 @@ +/*************************************************************************** + * Copyright (C) 2006 Paul Cifarelli <paul@cifarelli.net> * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +/*************************************************************************** + basically this implements a rudamentary rpc mechanism so the we can have + each player in a separate process. this is solely done to implement a + reliable crossfade (or more precicely, put the crossfade in the hands of + the alsa guys + ***************************************************************************/ + +#include <sys/time.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <unistd.h> +#include <iostream> +#include <cstring> +#include <sys/mman.h> + +using namespace std; + +#include "hxplayercontrol.h" + +class HSPPlayerControlled : public HelixSimplePlayer +{ +public: + HSPPlayerControlled(PlayerControl *pcntl, int index) : m_pcntl(pcntl), m_index(index) {} + virtual ~HSPPlayerControlled() {} + + virtual void notifyUser(unsigned long code, const char *moreinfo, const char *moreinfourl); + virtual void interruptUser(unsigned long code, const char *moreinfo, const char *moreinfourl); + virtual void onContacting(const char *host); + virtual void onBuffering(int percentage); + +private: + PlayerControl *m_pcntl; + int m_index; +}; + + +void HSPPlayerControlled::notifyUser(unsigned long code, const char *moreinfo, const char *moreinfourl) +{ + m_pcntl->sendnotifyuser(code, moreinfo, moreinfourl); +} + +void HSPPlayerControlled::interruptUser(unsigned long code, const char *moreinfo, const char *moreinfourl) +{ + m_pcntl->sendinterruptuser(code, moreinfo, moreinfourl); +} + +void HSPPlayerControlled::onContacting(const char *host) +{ + m_pcntl->sendcontacting(host); +} + +void HSPPlayerControlled::onBuffering(int percentage) +{ + m_pcntl->sendbuffering(percentage); +} + + +PlayerControl::PlayerControl() : m_eq_enabled(false), m_preamp(0), m_err(0), iamparent(0), m_index(0), nNumPlayers(0), + m_inited(false), m_api( HelixSimplePlayer::OSS ), m_device(0), mimehead(0), mimelistlen(0), + m_numPlugins(0), m_pluginInfo(0) +{ + memset(m_children, 0, sizeof(m_children)); +} + +PlayerControl::~PlayerControl() +{ + tearDown(); + + print2stderr("In PlayerControl::~PlayerControl(), m_index=%d\n", m_index); + + delete m_device; + + if (pmapped) + munmap(pmapped, sizeof(stateStuff) * 2); +} + +// init functions +void PlayerControl::init(const char *corelibpath, const char *pluginslibpath, const char *codecspath, int numPlayers) +{ + int err; + iamparent = 0; + nNumPlayers = numPlayers; + m_err = 0; + + print2stderr("In PlayerControl::init(), m_api=%d, m_device=%s\n", m_api, m_device ? m_device : "DEVICE NOT SET"); + + if (numPlayers > 2) // it's impossible + { + m_err = -1; + return; + } + + memset(&m_children, 0, numPlayers * sizeof(struct playerChildren)); + + m_inited = false; + + // create a shared memory region for state like stuff + if ( MAP_FAILED == (pmapped = (stateStuff *) mmap( (void *) statestuff, + sizeof(stateStuff) * 2, + PROT_READ | PROT_WRITE, +#ifdef __linux__ + MAP_SHARED | MAP_ANONYMOUS, +#else + MAP_SHARED | MAP_ANON, +#endif + -1, 0)) ) + pmapped = 0; + + // we do this the old fashioned way, so that we don't have to include an executable with our plugin... + for (int i = 0; i < numPlayers; i++) + { + if (pmapped) + { + m_children[i].current_time = &(pmapped[i].current_time); + m_children[i].duration = &(pmapped[i].duration); + m_children[i].md = &(pmapped[i].md); + + m_children[i].m_current = &(pmapped[i].m_current); + m_children[i].m_consumed = &(pmapped[i].m_consumed); + //m_children[i].q = pmapped[i].q; + //for (int j=0; j<NUM_SCOPEBUFS; j++) + //{ + // m_children[i].q[j].allocd = false; + // m_children[i].q[j].buf = pmapped[i].b[j]; + //} + } + err = pipe(m_children[i].m_pipeA); + err |= pipe(m_children[i].m_pipeB); + if ( !err && (iamparent = fork()) ) + { + // parent + print2stderr("%%%%%% parent initializes player %d\n", i); + + // parent's m_pid remains 0 + m_children[i].m_pid = iamparent; + close(m_children[i].m_pipeA[1]); // parent uses A for reading + close(m_children[i].m_pipeB[0]); // and B for writing + } + else if (!err) + { + // child + + cerr << "%%%%%% child initializes as player " << i << endl;; + + m_index = i; // child's index is saved + close(m_children[i].m_pipeA[0]); // child uses A for writing + close(m_children[i].m_pipeB[1]); // and B for reading + break; + } + } + + + if (!iamparent) // children stay here, parents return + { + int rfd = m_children[m_index].m_pipeB[0]; + int wfd = m_children[m_index].m_pipeA[1]; + int n; + struct timeval timeout; + HSPPlayerControlled *player = 0; + bool playing = false; + + player = new HSPPlayerControlled(this, m_index); + + timeout.tv_sec = 0; + timeout.tv_usec = 10000; + fd_set rdset, wrset; + FD_ZERO(&rdset); + FD_ZERO(&wrset); + FD_SET(rfd, &rdset); + FD_SET(wfd, &wrset); // really should check to see if we can write, but not gonna + + while ( 1 ) + { + FD_SET(rfd, &rdset); + + n = select(rfd + 1, &rdset, 0, 0, &timeout); + if ( FD_ISSET(rfd, &rdset) ) + { + msgid m; + unsigned char buf[65536]; + int sz = 0; + + if (getmessage(rfd, m, buf, sz) && player) + { + switch (m) + { + case INIT: + cerr << "INIT\n"; + if (!sz) + { + player->setOutputSink(m_api); + if (m_device) + player->setDevice(m_device); + player->init(corelibpath, pluginslibpath, codecspath, 1); + m_inited = true; + if (!m_index) // only player 0 need send the mime and plugin stuff + { + sendplugins(wfd, player); + sendmimetypes(wfd, player); + } + cerr << "%%%%%% player " << m_index << " child sends ready\n"; + sendready(wfd); + } + else + cerr << "CHILD " << m_index << " sz not right in INIT, sz=" << sz << endl; + break; + case SETURL: + { + bool islocal = (bool) buf[0]; + int len = strlen((const char *)&buf[1]); // not that this would prevent a crash... + if (m_inited) + { + cerr << "CHILD " << m_index << " setURL for " << (const char *)&buf[1] << + ",islocal=" << islocal << ",len=" << len << endl; + if (sz == len + 2) + player->setURL((const char *)&buf[1], 0, islocal); // remember, we sent the null... + else + cerr << "CHILD " << m_index << " sz not right in SETURL, sz=" << sz << endl; + } + } + break; + case START: + { + bool fadein = (bool) buf[0]; + unsigned long fadetime; + + if (m_inited) + { + if (pmapped) + *m_children[m_index].current_time = 0; + + if (sz == sizeof(unsigned long) + 1) + { + cerr << "CHILD " << m_index << " gets START\n"; + memcpy((void *)&fadetime, (void *)&buf[1], sizeof(unsigned long)); + playing = true; + player->start(0, fadein, fadetime); + } + else + cerr << "CHILD " << m_index << " sz not right in START, sz=" << sz << endl; + } + } + break; + case STOP: + if (m_inited) + if (!sz) + { + player->stop(); + playing = false; + cerr << "CHILD " << m_index << " gets STOP\n"; + } + else + cerr << "CHILD " << m_index << " sz not right in STOP, sz=" << sz << endl; + break; + case PAUSE: + if (m_inited) + if (!sz) + player->pause(); + else + cerr << "CHILD " << m_index << " sz not right in PAUSE, sz=" << sz << endl; + break; + case RESUME: + if (m_inited) + if (!sz) + player->resume(); + else + cerr << "CHILD " << m_index << " sz not right in RESUME, sz=" << sz << endl; + break; + case SEEK: + if (m_inited) + if (sz == sizeof(unsigned long)) + { + unsigned long pos; + memcpy( (void *) &pos, (void *) buf, sizeof(unsigned long) ); + if (pos < player->duration(0)) + player->seek(pos, 0); + } + else + cerr << "CHILD " << m_index << " sz not right in SEEK, sz=" << sz << endl; + break; + case SETVOLUME: + { + if (m_inited) + if (sz == sizeof(unsigned long)) + { + memcpy( (void *) &m_volume, (void *) buf, sizeof(unsigned long)); + cerr << "CHILD: received setvolume request " << m_volume <<endl;; + player->setVolume(m_volume); + } + else + cerr << "CHILD " << m_index << " sz not right in SETVOLUME, sz=" << sz << endl; + } + break; + case OUTPUTSINK: + if (m_inited) + if (sz == 1) + { + m_api = (HelixSimplePlayer::AUDIOAPI) buf[0]; + cerr << "CHILD: received OUTPUTSINK: " << m_api <<endl;; + } + else + cerr << "CHILD " << m_index << " sz not right in OUTPUTSINK, sz=" << sz << endl; + break; + case DEVICE: + { + char* dev = strdup((const char*) buf); + if (m_inited) + if ((unsigned)sz == strlen(dev) + 1) + { + cerr << "CHILD " << m_index << " gets device " << dev << endl; + setDevice( dev ); + } + else + cerr << "CHILD " << m_index << " sz not right in DEVICE, sz=" << sz << endl; + free(dev); + } + break; + case SETFADE: + { + if (m_inited) + if (sz == sizeof(unsigned long) + 1) + { + bool fadeout; + unsigned long fadelength; + fadeout = (bool) buf[0]; + memcpy((void *) &fadelength, (void *) &buf[1], sizeof(unsigned long)); + player->setFadeout(fadeout, fadelength, 0); + } + else + cerr << "CHILD " << m_index << " sz not right in SETFADE, sz=" << sz << endl; + } + break; + case ENABLEEQ: + { + if (m_inited) + if (sz == 1) + { + m_eq_enabled = (bool) buf[0]; + player->enableEQ(m_eq_enabled); + cerr << "CHILD " << m_index << " enables EQ\n"; + } + else + cerr << "CHILD " << m_index << " sz not right in ENABLEEQ, sz=" << sz << endl; + } + break; + case UPDATEEQGAINS: + { + int i, n, k; + + cerr << "UPDATEGAINS\n"; + if (m_inited) + { + if (sz >= 2) + { + memcpy( (void *) &m_preamp, (void *) buf, sizeof(m_preamp) ); + memcpy( (void *) &n, (void *) &buf[ sizeof(m_preamp) ], sizeof(int) ); + player->m_preamp = m_preamp; + } + else + cerr << "CHILD " << m_index << " sz not right in UPDATEEQGAINS, sz=" << sz << endl; + + if ((unsigned)sz == sizeof(m_preamp) + sizeof(int) + n * sizeof(int)) + { + if (n > 0) + { + m_equalizerGains.resize(n); + cerr << "CHILD " << m_index << " receives " << n << " equalizer gains\n"; + for (i=0; i<n; i++) + { + memcpy( (void *) &k, (void *) &buf[ sizeof(m_preamp) + (i+1)*sizeof(int) ], sizeof(int) ); + m_equalizerGains[i] = k; + } + player->m_equalizerGains = m_equalizerGains; + if (!m_eq_enabled) + player->enableEQ(true); + player->updateEQgains(); + player->enableEQ(m_eq_enabled); + } + } + else + cerr << "CHILD " << m_index << " sz not right in UPDATEEQGAINS, sz=" << sz << endl; + } + } + break; + case SCOPECLEAR: + if (m_inited) + { + player->clearScopeQ(0); + if (pmapped) + *m_children[m_index].m_consumed = *m_children[m_index].m_current = 0; + } + break; + case TEARDOWN: + if (!sz) + { + cerr << "CHILD: " << m_index << " received shutdown request\n"; + player->stop(0); + delete player; + raise(15); + exit(0); + } + else + cerr << "CHILD " << m_index << " sz not right in TEARDOWN, sz=" << sz << endl; + break; + + default: // send an error to the parent + cerr << "CHILD " << m_index << " received unhandled message, sz=" << sz << endl; + break; + } + } + else + { + cerr << "CHILD " << m_index << " gets EOD\n"; + break; + } + } + + if (m_inited) + { + player->dispatch(); + + if (playing && player->done(0)) + { + player->stop(0); + player->clearScopeQ(0); + senddone(wfd); + playing = false; + if (pmapped) + { + *m_children[m_index].current_time = 0; + *m_children[m_index].duration = 0; + } + } + + if (pmapped) + { + *m_children[m_index].current_time = player->where(0); + *m_children[m_index].duration = player->duration(0); + + HelixSimplePlayer::metaData *md = player->getMetaData(0); + if (md) + memcpy((void *) m_children[m_index].md, (void *) md, sizeof(HelixSimplePlayer::metaData)); + + struct DelayQueue *item; + //int j; + while ((item = player->getScopeBuf(0))) + { + //j = (*m_children[m_index].m_current + 1) % NUM_SCOPEBUFS; + + //cerr << "player:" << m_index << " j=" << j << " time=" << item->time << " etime=" << item->etime << " len=" << item->len << endl; + //m_children[m_index].q[j].len = item->len; + //m_children[m_index].q[j].time = item->time; + //m_children[m_index].q[j].etime = item->etime; + //m_children[m_index].q[j].nchan = item->nchan; + //m_children[m_index].q[j].bps = item->bps; + //m_children[m_index].q[j].tps = item->tps; + //m_children[m_index].q[j].spb = item->spb; + //memcpy((void *)m_children[m_index].q[j].buf, (void *) item->buf, item->len ); + //*m_children[m_index].m_current = j; + //cerr << "player:" << m_index << " time=" << item->time << " etime=" << item->etime << endl; + sendscopebuf(wfd, item); + delete item; + } + } + } + + timeout.tv_sec = 0; + timeout.tv_usec = 10000; + } + cerr << "CHILD " << m_index << " will exit!\n"; + } + else + { + int i; + bool done = false, dead = false; + + sendsetoutputsink(); + sendsetdevice(); + sendinit(); + + // wait for ready from children + while (!done && !dead) + { + dispatch(); + done = true; + for (i=0; i<numPlayers; i++) + { + done &= m_children[i].isready; + dead |= m_children[i].isdead; + } + } + + if (dead) + { + m_err = -1; + return; + } + } + m_inited = true; +} + + +void PlayerControl::setOutputSink( HelixSimplePlayer::AUDIOAPI out ) +{ + print2stderr("%%%% In PlayerControl::setOutputSink:%d\n", out); + m_api = out; +} + +void PlayerControl::setDevice( const char *dev ) +{ + delete [] m_device; + + int len = strlen(dev); + m_device = new char [len + 1]; + strcpy(m_device, dev); + print2stderr("%%%% In PlayerControl::setDevice:%s\n", dev); +} + +int PlayerControl::initDirectSS() +{ + return 0; +} + +void PlayerControl::tearDown() +{ + int tmp; + if (iamparent) + { + for (int i = 0; i < nNumPlayers; i++) + { + if (m_inited) + { + sendteardown(m_children[i].m_pipeB[1]); + close(m_children[i].m_pipeB[1]); + close(m_children[i].m_pipeA[0]); + cerr << "About to waitpid for pid " << m_children[i].m_pid << endl; + kill(m_children[i].m_pid, SIGTERM); + waitpid(m_children[i].m_pid, &tmp, 0); + } + } + } +} + +void PlayerControl::start(int playerIndex, bool fadein, unsigned long fadetime) +{ + m_children[playerIndex].isplaying = true; + if (pmapped) + *m_children[playerIndex].m_consumed = *m_children[playerIndex].m_current = 0; + sendstart(m_children[playerIndex].m_pipeB[1], fadein, fadetime); +} + +int PlayerControl::setURL(const char *url, int playerIndex, bool islocal) +{ + m_children[playerIndex].islocal = islocal; + if (sendsetURL(m_children[playerIndex].m_pipeB[1], url, islocal)) + return 0; + + return -1; +} + +bool PlayerControl::done(int playerIndex) +{ + return (!m_children[playerIndex].isplaying); +} + +void PlayerControl::stop(int playerIndex) +{ + if (playerIndex == HelixSimplePlayer::ALL_PLAYERS) + { + for (int i=0; i<nNumPlayers; i++) + stop(i); + } + else + { + m_children[playerIndex].isplaying = false; + sendstop(m_children[playerIndex].m_pipeB[1]); + } +} + +void PlayerControl::pause(int playerIndex) +{ + sendpause(m_children[playerIndex].m_pipeB[1]); +} + +void PlayerControl::resume(int playerIndex) +{ + sendresume(m_children[playerIndex].m_pipeB[1]); +} + +void PlayerControl::seek(unsigned long pos, int playerIndex) +{ + sendmessage(m_children[playerIndex].m_pipeB[1], SEEK, (unsigned char *) &pos, sizeof(unsigned long)); +} + +unsigned long PlayerControl::where(int playerIndex) const +{ + if (pmapped) + return *m_children[playerIndex].current_time; + else + return 0; +} + +unsigned long PlayerControl::duration(int playerIndex) const +{ + if (pmapped) + return *m_children[playerIndex].duration; + else + return 0; +} + +unsigned long PlayerControl::getVolume() +{ + return m_volume; +} + +void PlayerControl::setVolume(unsigned long vol) +{ + m_volume = vol; + for (int i = 0; i < nNumPlayers; i++) + sendsetvolume(m_children[i].m_pipeB[1], vol); +} + +void PlayerControl::dispatch() +{ + int i; + struct timeval timeout; + int n = -1, ntot; + int rfd; + int wfd; + + timeout.tv_sec = 0; + timeout.tv_usec = 0; + fd_set rdset, wrset; + + FD_ZERO(&rdset); + FD_ZERO(&wrset); + + for (i=0; i<nNumPlayers; i++) + { + rfd = m_children[i].m_pipeA[0]; + wfd = m_children[i].m_pipeB[1]; + FD_SET(rfd, &rdset); + FD_SET(wfd, &wrset); // really should check to see if we can write, but not gonna + if (rfd > n) + n = rfd; + } + + if (n < 0) + return; + + ntot = select(n + 1, &rdset, 0, 0, &timeout); + for (i=0; ntot && i < nNumPlayers; i++) + { + rfd = m_children[i].m_pipeA[0]; + wfd = m_children[i].m_pipeB[1]; + if ( FD_ISSET(rfd, &rdset) ) + { + msgid m; + unsigned char buf[65536]; + int sz = 0; + + if (getmessage(rfd, m, buf, sz)) + { + switch (m) + { + case READY: + print2stderr("CHILD %d is READY\n", i); + m_children[i].isready = true;; + break; + + case DONE: + print2stderr("CHILD %d is DONE\n", i); + if (!sz) + { + m_children[i].isplaying = false; + clearScopeQ(i); + play_finished(i); + } + else + print2stderr("PARENT: sz does not agree in DONE\n"); + break; + + case MIMETYPES: + { + int len, slen; + MimeList *entry; + char *tmp; + char tmpbuf[65536]; + + mimehead = 0; + memcpy( (void *) &mimelistlen, (void *) buf, sizeof(mimelistlen) ); + len = sizeof(mimelistlen); + + print2stderr("%%%%%%% Received %d mimetypes\n", mimelistlen); + for (int j = 0; j < mimelistlen; j++) + { + tmp = (char *) &buf[len]; + slen = strlen(tmp); + strcpy(tmpbuf, tmp); + tmp += slen + 1; + len += slen + 1; + entry = new MimeList(tmpbuf, tmp); + slen = strlen(tmp); + len += slen + 1; + + entry->fwd = mimehead; + mimehead = entry; + } + + if (sz != len) // sanity check + cerr << "PARENT: sz not = len in MIMETYPES " << sz << " " << len << endl; + } + break; + + case PLUGINS: + { + int len, slen; + int nplugins; + char *tmp; + + memcpy( (void *) &nplugins, (void *) buf, sizeof(nplugins) ); + len = sizeof(nplugins); + m_pluginInfo = new pluginInfo* [nplugins]; + m_numPlugins = nplugins; + + print2stderr("%%%%%%% Received %d plugins\n", nplugins); + for (int j = 0; j < nplugins; j++) + { + m_pluginInfo[j] = new pluginInfo; + + tmp = (char *) &buf[len]; + slen = strlen(tmp); + m_pluginInfo[j]->description = new char[ slen + 1 ]; + strcpy(m_pluginInfo[j]->description, tmp); + len += slen + 1; + + tmp = (char *) &buf[len]; + slen = strlen(tmp); + m_pluginInfo[j]->copyright = new char[ slen + 1 ]; + strcpy(m_pluginInfo[j]->copyright, tmp); + len += slen + 1; + + tmp = (char *) &buf[len]; + slen = strlen(tmp); + m_pluginInfo[j]->moreinfourl = new char[ slen + 1 ]; + strcpy(m_pluginInfo[j]->moreinfourl, tmp); + len += slen + 1; + } + + if (sz != len) // sanity check + cerr << "PARENT: sz not = len in PLUGINS " << sz << " " << len << endl; + } + break; + + case CONTACTING: + { + int len = strlen((const char *)buf); + if (sz == len + 1) + onContacting((const char *)buf); + else + cerr << "PARENT: sz not right in CONTACTING sz=" << sz << endl; + } + break; + + case BUFFERING: + { + unsigned long percent; + if (sz == sizeof(unsigned long)) + { + memcpy((void *)&percent, (void *) buf, sizeof(unsigned long)); + onBuffering(percent); + } + else + cerr << "PARENT: sz not right in BUFFERING sz=" << sz << endl; + } + break; + + case NOTIFYUSER: + { + int len1, len2; + unsigned long code; + const char *moreinfo, *moreinfourl; + memcpy((void *) &code, (void *) buf, sizeof(unsigned long)); + moreinfo = (const char *) &buf[sizeof(unsigned long)]; + len1 = strlen(moreinfo); + moreinfourl = (const char *) &moreinfo[ len1 + 1]; + len2 = strlen(moreinfourl); + // sanity check + if ((unsigned) sz != sizeof(unsigned long) + len1 + len2 + 2) + cerr << "PARENT: sz not right in NOTIFYUSER sz=" << sz << endl; + + notifyUser(code, moreinfo, moreinfourl); + } + break; + + case INTERRUPTUSER: + { + int len1, len2; + unsigned long code; + const char *moreinfo, *moreinfourl; + memcpy((void *) &code, (void *) buf, sizeof(unsigned long)); + moreinfo = (const char *) &buf[sizeof(unsigned long)]; + len1 = strlen(moreinfo); + moreinfourl = (const char *) &moreinfo[ len1 + 1]; + len2 = strlen(moreinfourl); + // sanity check + if ((unsigned) sz != sizeof(unsigned long) + len1 + len2 + 2) + cerr << "PARENT: sz not right in INTERRUPTUSER sz=" << sz << endl; + + interruptUser(code, moreinfo, moreinfourl); + } + break; + + case SCOPEBUF: + { + DelayQueue *item; + int bufsz, len = 0; + memcpy( (void *) &bufsz, (void *) buf, sizeof(int) ); len += sizeof(int); + if ((int) (bufsz + 2 * sizeof(unsigned long) + 4 * sizeof(int) + sizeof(double)) == sz) + { + item = new DelayQueue(bufsz); + memcpy( (void *) &item->time, (void *) &buf[len], sizeof(unsigned long) ); + len += sizeof(unsigned long); + memcpy( (void *) &item->etime, (void *) &buf[len], sizeof(unsigned long) ); + len += sizeof(unsigned long); + memcpy( (void *) &item->nchan, (void *) &buf[len], sizeof(int) ); len += sizeof(int); + memcpy( (void *) &item->bps, (void *) &buf[len], sizeof(int) ); len += sizeof(int); + memcpy( (void *) &item->tps, (void *) &buf[len], sizeof(double) ); len += sizeof(double); + memcpy( (void *) &item->spb, (void *) &buf[len], sizeof(int) ); len += sizeof(int); + memcpy( (void *) item->buf, (void *) &buf[len], item->len ); len += item->len; + + addScopeBuf(item, i); + } + else + cerr << "PARENT: sz not right in SCOPEBUF sz=" << sz << endl; + } + break; + default: + print2stderr("PARENT recvd unhandled message %d\n", m); + break; + } + } + else + { + m_children[i].isdead = true; + return; // should never happen + } + } + } + + for (i=0; pmapped && i<nNumPlayers; i++) + { + while (*m_children[i].m_consumed != *m_children[i].m_current) + { + addScopeBuf(&m_children[i].q[*m_children[i].m_consumed], i); + + //cerr << "j=" << *m_children[i].m_consumed << + // " time=" << m_children[i].q[*m_children[i].m_consumed].time << + // " etime=" << m_children[i].q[*m_children[i].m_consumed].etime << + // " len=" << m_children[i].q[*m_children[i].m_consumed].len << endl; + + *m_children[i].m_consumed = (*m_children[i].m_consumed + 1) % NUM_SCOPEBUFS; + } + } +} + +void PlayerControl::cleanUpStream(int playerIndex) +{ + stop(playerIndex); +} + +bool PlayerControl::isPlaying(int playerIndex) const +{ + return m_children[playerIndex].isplaying; +} + +bool PlayerControl::isLocal(int playerIndex) const +{ + return m_children[playerIndex].islocal; +} + + +void PlayerControl::setFadeout(bool fadeout, unsigned long fadelength, int playerIndex) +{ + sendsetfade(m_children[playerIndex].m_pipeB[1], fadeout, fadelength); +} + +void PlayerControl::addScopeBuf(struct DelayQueue *item, int playerIndex) +{ + if (playerIndex >=0 && playerIndex < nNumPlayers) + { + if (m_children[playerIndex].scopebuftail) + { + item->fwd = 0; + m_children[playerIndex].scopebuftail->fwd = item; + m_children[playerIndex].scopebuftail = item; + m_children[playerIndex].scopecount++; + } + else + { + item->fwd = 0; + m_children[playerIndex].scopebufhead = item; + m_children[playerIndex].scopebuftail = item; + m_children[playerIndex].scopecount = 1; + } + } +} + +DelayQueue *PlayerControl::getScopeBuf(int playerIndex) +{ + if (playerIndex >=0 && playerIndex < nNumPlayers) + { + struct DelayQueue *item = m_children[playerIndex].scopebufhead; + + if (item) + { + m_children[playerIndex].scopebufhead = item->fwd; + m_children[playerIndex].scopecount--; + if (!m_children[playerIndex].scopebufhead) + m_children[playerIndex].scopebuftail = 0; + } + return item; + } + else + return 0; +} + +int PlayerControl::getScopeCount(int playerIndex) +{ + return (playerIndex >= 0 && playerIndex < nNumPlayers ? m_children[playerIndex].scopecount : 0); +} + +int PlayerControl::peekScopeTime(unsigned long &t, int playerIndex) +{ + if (playerIndex >=0 && playerIndex < nNumPlayers) + { + if (m_children[playerIndex].scopebufhead) + t = m_children[playerIndex].scopebufhead->time; + else + return -1; + return 0; + } + return -1; +} + +void PlayerControl::clearScopeQ(int playerIndex) +{ + if (playerIndex < 0) + { + for (int i=0; i<nNumPlayers; i++) + clearScopeQ(i); + } + else + { + sendscopeclear(m_children[playerIndex].m_pipeB[1]); + struct DelayQueue *item; + while ((item = getScopeBuf(playerIndex))) + if (item->allocd) + delete item; + } +} + +void PlayerControl::enableEQ(bool enabled) +{ + int i; + unsigned char c = (char) enabled; + + for (i=0; i<nNumPlayers; i++) + sendmessage(m_children[i].m_pipeB[1], ENABLEEQ, (unsigned char *) &c, 1); + + m_eq_enabled = enabled; +} + +bool PlayerControl::isEQenabled() +{ + return m_eq_enabled; +} + +void PlayerControl::updateEQgains() +{ + sendupdateeqgains(); +} + +int PlayerControl::numPlugins() const +{ + return m_numPlugins; +} + +int PlayerControl::getPluginInfo(int index, const char *&description, const char *©right, const char *&moreinfourl) const +{ + if (m_pluginInfo && index < m_numPlugins) + { + description = m_pluginInfo[index]->description; + copyright = m_pluginInfo[index]->copyright; + moreinfourl = m_pluginInfo[index]->moreinfourl; + + return 0; + } + return -1; +} + +const MimeList *PlayerControl::getMimeList() const +{ + return mimehead; +} + +int PlayerControl::getMimeListLen() const +{ + return mimelistlen; +} + +HelixSimplePlayer::metaData *PlayerControl::getMetaData(int playerIndex ) +{ + return m_children[playerIndex].md; +} + +bool PlayerControl::sendsetoutputsink() +{ + int i; + char c = (char) m_api; + bool ok = false; + + for (i=0; i<nNumPlayers; i++) + ok |= sendmessage(m_children[i].m_pipeB[1], OUTPUTSINK, (unsigned char *) &c, 1); + + return ok; +} + +bool PlayerControl::sendsetdevice() +{ + if (!m_device) + return false; + + int i, len = strlen( m_device ); + bool ok = false; + + for (i=0; i<nNumPlayers; i++) + ok |= sendmessage(m_children[i].m_pipeB[1], DEVICE, (unsigned char *) m_device, len + 1); + + return ok; +} + +bool PlayerControl::sendinit() +{ + int i; + bool ok = false; + + for (i=0; i<nNumPlayers; i++) + ok |= sendrequest(m_children[i].m_pipeB[1], INIT); + + return ok; +} + +bool PlayerControl::sendupdateeqgains() +{ + unsigned char buf[ 65535 ]; + int bandGain; + uint i; + bool ok = false; + + memcpy((void *) buf, (void *) &m_preamp, sizeof(m_preamp)); + i = m_equalizerGains.size(); + memcpy( (void *) &buf[ sizeof(m_preamp) ], (void *) &i, sizeof(int) ); + for ( i = 0; i < m_equalizerGains.size(); i++ ) + { + bandGain = m_equalizerGains[i]; + memcpy((void *)&buf[ sizeof(m_preamp) + (i+1) * sizeof(int) ], (void *) &bandGain, sizeof(int)); + } + for ( i = 0; i < (uint) nNumPlayers; i++ ) + ok |= sendmessage(m_children[i].m_pipeB[1], UPDATEEQGAINS, buf, sizeof(m_preamp) + (m_equalizerGains.size()+1) * sizeof(int)); + + return ok; +} + +// children send this! +bool PlayerControl::sendnotifyuser(unsigned long code, const char *moreinfo, const char *moreinfourl) +{ + int len1 = strlen(moreinfo), len2 = strlen(moreinfourl), len; + unsigned char buf[65536]; + + memcpy( (void *) buf, (void *) &code, sizeof(unsigned long) ); + len = sizeof(unsigned long); + memcpy( (void *) &buf[ len ], (void *) moreinfo, len1 + 1); + len += len1 + 1; + memcpy( (void *) &buf[ len ], (void *) moreinfourl, len2 + 1); + len += len2 + 1; + + return (sendmessage(m_children[m_index].m_pipeA[1], NOTIFYUSER, buf, len)); +} + +// children send this! +bool PlayerControl::sendinterruptuser(unsigned long code, const char *moreinfo, const char *moreinfourl) +{ + int len1 = strlen(moreinfo), len2 = strlen(moreinfourl), len; + unsigned char buf[65536]; + + memcpy( (void *) buf, (void *) &code, sizeof(unsigned long) ); + len = sizeof(unsigned long); + memcpy( (void *) &buf[ len ], (void *) moreinfo, len1 + 1); + len += len1 + 1; + memcpy( (void *) &buf[ len ], (void *) moreinfourl, len2 + 1); + len += len2 + 1; + + return (sendmessage(m_children[m_index].m_pipeA[1], INTERRUPTUSER, buf, len)); +} + +// children send this! +bool PlayerControl::sendcontacting(const char *host) +{ + int len = strlen(host); + + return (sendmessage(m_children[m_index].m_pipeA[1], CONTACTING, (unsigned char *) host, len + 1)); +} + +// children send this! +bool PlayerControl::sendbuffering(int percentage) +{ + return (sendmessage(m_children[m_index].m_pipeA[1], BUFFERING, (unsigned char *) &percentage, sizeof(unsigned long))); +} + + +///////////// statics ////////////// + +bool PlayerControl::getmessage(int fd, msgid &m, unsigned char *buf, int &sz) +{ + int nbytes = 0, bytes = 0; + unsigned char mm; + + bytes = read(fd, (void *) &mm, 1); + if (bytes <= 0) + return false; + + m = (msgid) mm; + + nbytes = bytes; + + nbytes = 0; + char *tmp = (char *) &sz; + while ( bytes > 0 && 4 != nbytes ) + { + bytes = read(fd, (void *) &tmp[ nbytes ], 4 - nbytes); + nbytes += bytes; + } + + if (sz) + { + nbytes = 0; + while ( bytes > 0 && sz != nbytes ) + { + bytes = read(fd, (void *) &buf[ nbytes ], sz - nbytes); + nbytes += bytes; + } + } + + return (nbytes > 0); +} + +bool PlayerControl::sendmessage(int fd, msgid m, unsigned char *buf, int sz) +{ + unsigned char hdr[5]; + hdr[0] = (unsigned char) m; + int ret = 0; + + memcpy(&hdr[1], (void *) &sz, 4); + ret = write(fd, (void *) hdr, 5); + if (sz) + ret += write(fd, (void *) buf, sz); + + return (ret == (sz + 5)); +} + +bool PlayerControl::sendsetURL(int fd, const char *url, bool islocal) +{ + int len = strlen(url); + unsigned char* buf = new unsigned char[ len + 2 ]; + + buf[0] = (unsigned char) islocal; + memcpy((void *) &buf[1], (void *) url, len + 1); // go ahead and send the null, what the hell + bool r = sendmessage(fd, SETURL, (unsigned char *)buf, len + 2); + delete [] buf; + return r; +} + +bool PlayerControl::sendstart(int fd, bool fadin, unsigned long fadetime) +{ + unsigned char buf[32]; + + buf[0] = (unsigned char) fadin; + memcpy( (void *) &buf[1], (void *) &fadetime, sizeof(unsigned long) ); + + return sendmessage(fd, START, buf, sizeof(unsigned long) + 1); +} + +bool PlayerControl::sendsetvolume(int fd, unsigned long volume) +{ + return sendmessage(fd, SETVOLUME, (unsigned char *) &volume, sizeof(unsigned long)); +} + +bool PlayerControl::sendvolume(int fd, unsigned long volume) +{ + return sendmessage(fd, VOLUME, (unsigned char *) &volume, sizeof(unsigned long)); +} + +bool PlayerControl::sendsetfade(int fd, bool fadeout, unsigned long fadelength) +{ + unsigned char buf[ sizeof(bool) + sizeof(unsigned long) ]; + + buf[0] = (char )fadeout; + memcpy((void *) &buf[1], (void *) &fadelength, sizeof(unsigned long)); + return sendmessage(fd, SETFADE, buf, 1 + sizeof(unsigned long)); +} + +bool PlayerControl::sendplugins(int fd, HelixSimplePlayer *player) +{ + unsigned char buf[65536]; + int sz, slen; + int nplugins = player->numPlugins(); + const char *description, *copyright, *moreinfourl; + + memcpy( (void *) buf, (void *) &nplugins, sizeof(nplugins) ); + sz = sizeof(nplugins); + + for (int i = 0; i < nplugins; i++) + { + player->getPluginInfo(i, description, copyright, moreinfourl); + + slen = strlen(description); + memcpy( (void *) &buf[sz], (void *) description, slen + 1 ); // expecting the null + sz += slen + 1; + slen = strlen(copyright); + memcpy( (void *) &buf[sz], (void *) copyright, slen + 1 ); // expecting the null + sz += slen + 1; + slen = strlen(moreinfourl); + memcpy( (void *) &buf[sz], (void *) moreinfourl, slen + 1 ); // expecting the null + sz += slen + 1; + } + + cerr << "CHILD: nplugins " << nplugins << " sz " << sz << endl; + return sendmessage(fd, PLUGINS, buf, sz); +} + +bool PlayerControl::sendmimetypes(int fd, HelixSimplePlayer *player) +{ + unsigned char buf[65536]; + int sz, slen; + int mimelistlen = player->getMimeListLen(); + const MimeList *mimelisthead = player->getMimeList(); + + memcpy( (void *) buf, (void *) &mimelistlen, sizeof(mimelistlen) ); + sz = sizeof(mimelistlen); + + while (mimelisthead) + { + slen = strlen(mimelisthead->mimetypes); + memcpy( (void *) &buf[sz], (void *) mimelisthead->mimetypes, slen + 1 ); // expecting the null + sz += slen + 1; + slen = strlen(mimelisthead->mimeexts); + memcpy( (void *) &buf[sz], (void *) mimelisthead->mimeexts, slen + 1 ); // expecting the null + sz += slen + 1; + + mimelisthead = mimelisthead->fwd; + } + + return sendmessage(fd, MIMETYPES, buf, sz); +} + +bool PlayerControl::sendscopebuf(int fd, DelayQueue *item) +{ + unsigned char buf[65536]; + int len = 0; + + if (len + 2 * sizeof(unsigned long) + 4 * sizeof(int) + sizeof(double) < 65536) + { + memcpy( (void *) buf, (void *) &item->len, sizeof(int) ); len += sizeof(int); + memcpy( (void *) &buf[len], (void *) &item->time, sizeof(unsigned long) ); len += sizeof(unsigned long); + memcpy( (void *) &buf[len], (void *) &item->etime, sizeof(unsigned long) ); len += sizeof(unsigned long); + memcpy( (void *) &buf[len], (void *) &item->nchan, sizeof(int) ); len += sizeof(int); + memcpy( (void *) &buf[len], (void *) &item->bps, sizeof(int) ); len += sizeof(int); + memcpy( (void *) &buf[len], (void *) &item->tps, sizeof(double) ); len += sizeof(double); + memcpy( (void *) &buf[len], (void *) &item->spb, sizeof(int) ); len += sizeof(int); + memcpy( (void *) &buf[len], (void *) item->buf, item->len ); len += item->len; + + return sendmessage(fd, SCOPEBUF, buf, len); + } + return false; +} diff --git a/amarok/src/engine/helix/hxplayercontrol.h b/amarok/src/engine/helix/hxplayercontrol.h new file mode 100644 index 00000000..760284de --- /dev/null +++ b/amarok/src/engine/helix/hxplayercontrol.h @@ -0,0 +1,221 @@ +/*************************************************************************** + * Copyright (C) 2006 Paul Cifarelli <paul@cifarelli.net> * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef _HXSPLAY_INCLUDED_ +#define _HXSPLAY_INCLUDED_ + +#include "helix-sp.h" + +class PlayerControl +{ +public: + PlayerControl(); + virtual ~PlayerControl(); + + int getError() const { return m_err; } + + // init functions + void init(const char *corelibpath, const char *pluginslibpath, const char *codecspath, int numPlayers = 1); + void setOutputSink( HelixSimplePlayer::AUDIOAPI out ); + void setDevice( const char *dev ); + int initDirectSS(); + void tearDown(); + + // player functions + int setURL(const char *url, + int playerIndex, + bool islocal = true); + bool done(int playerIndex); + void start(int playerIndex, + bool fadein = false, + unsigned long fadetime = 0); + void stop(int playerIndex = HelixSimplePlayer::ALL_PLAYERS); + void pause(int playerIndex); + void resume(int playerIndex); + void seek(unsigned long pos, int playerIndex); + unsigned long where(int playerIndex) const; + unsigned long duration(int playerIndex) const; + unsigned long getVolume(); + void setVolume(unsigned long vol); + void dispatch(); + void cleanUpStream(int playerIndex); + bool isPlaying(int playerIndex) const; + bool isLocal(int playerIndex) const; + int numPlayers() const { return nNumPlayers; } + + // crossfade + void setFadeout(bool fadeout, unsigned long fadelength, int playerIndex); + + // scope + void addScopeBuf(struct DelayQueue *item, int playerIndex); + DelayQueue *getScopeBuf(int playerIndex); + int getScopeCount(int playerIndex); + int peekScopeTime(unsigned long &t, int playerIndex); + void clearScopeQ(int playerIndex); + + // equalizer + void enableEQ(bool enabled); + bool isEQenabled(); + void updateEQgains(); + + // config stuff + int numPlugins() const; + int getPluginInfo(int index, + const char *&description, + const char *©right, + const char *&moreinfourl) const; + const MimeList *getMimeList() const; + int getMimeListLen() const; + + virtual void play_finished(int /*playerIndex*/) {} + + // stream meta data + HelixSimplePlayer::metaData *getMetaData(int playerIndex); + + // need to simulate these + virtual void notifyUser(unsigned long/*code*/, const char */*moreinfo*/, const char */*moreinfourl*/) {} + virtual void interruptUser(unsigned long/*code*/, const char */*moreinfo*/, const char */*moreinfourl*/) {} + virtual int print2stdout(const char */*fmt*/, ...) { return 0; } + virtual int print2stderr(const char */*fmt*/, ...) { return 0; } + virtual void onContacting(const char */*host*/) {} + virtual void onBuffering(int /*percentage*/) {} + + // children send functions + bool sendnotifyuser(unsigned long code, const char *moreinfo, const char *moreinfourl); + bool sendinterruptuser(unsigned long code, const char *moreinfo, const char *moreinfourl); + bool sendcontacting(const char *host); + bool sendbuffering(int percentage); + +protected: + bool m_eq_enabled; + int m_preamp; + vector<int> m_equalizerGains; + +private: + int m_err; + pid_t iamparent; + int m_index; + int nNumPlayers; + bool m_inited; + + // not yet initd when these are set + HelixSimplePlayer::AUDIOAPI m_api; + char *m_device; + + static const int NUM_SCOPEBUFS = 50; + + struct playerChildren + { + int m_pipeA[2]; + int m_pipeB[2]; + pid_t m_pid; + bool isplaying; + bool islocal; + bool isready; + bool isdead; + HelixSimplePlayer::metaData *md; + int scopecount; + struct DelayQueue *scopebufhead; + struct DelayQueue *scopebuftail; + unsigned long *current_time; + unsigned long *duration; + DelayQueue *q; + int *m_current; + int *m_consumed; + } m_children[2]; + + unsigned long m_volume; + + // supported mime type list + MimeList *mimehead; + int mimelistlen; + + int m_numPlugins; + struct pluginInfo + { + char *description; + char *copyright; + char *moreinfourl; + } **m_pluginInfo; + + // for sharing + struct stateStuff + { + unsigned long current_time; + unsigned long duration; + HelixSimplePlayer::metaData md; + //DelayQueue q[NUM_SCOPEBUFS]; + //unsigned char b[NUM_SCOPEBUFS][65535]; + int m_current; + int m_consumed; + } statestuff[2], *pmapped; + + // msgs are a 1 byte code follows by a 4 byte sz. sz does NOT include the 5 bytes of this hdr + enum msgid + { + READY, + INIT, + SETURL, + START, + STOP, + PAUSE, + RESUME, + SEEK, + DONE, + SETVOLUME, + VOLUME, + OUTPUTSINK, + DEVICE, + SETFADE, + ENABLEEQ, + UPDATEEQGAINS, + SCOPEBUF, + SCOPECLEAR, + METADATAREQ, + METADATA, + PLUGINS, + MIMETYPES, + CONTACTING, + BUFFERING, + NOTIFYUSER, + INTERRUPTUSER, + TEARDOWN, + ERRORCODE + }; + + // utility functions + bool sendsetoutputsink(); + bool sendsetdevice(); + bool sendinit(); + bool sendupdateeqgains(); + + static bool getmessage(int fd, msgid &m, unsigned char *buf, int &sz); + static bool sendmessage(int fd, msgid m, unsigned char *buf, int sz); + static bool sendrequest(int fd, msgid m) { return (sendmessage(fd, m, 0, 0)); } + static bool sendready(int fd) { return (sendrequest(fd, READY)); } + static bool sendsetURL(int fd, const char *url, bool islocal); + static bool sendstart(int fd, bool fadin, unsigned long fadetime); + static bool sendstop(int fd) { return (sendrequest(fd, STOP)); } + static bool sendpause(int fd) { return (sendrequest(fd, PAUSE)); } + static bool sendresume(int fd) { return (sendrequest(fd, RESUME)); } + static bool senddone(int fd) { return (sendrequest(fd, DONE)); } + static bool sendsetvolume(int fd, unsigned long volume); + static bool sendvolume(int fd, unsigned long volume); + static bool sendsetfade(int fd, bool fadeout, unsigned long fadelength); + static bool sendteardown(int fd) { return (sendrequest(fd, TEARDOWN)); } + static bool sendscopeclear(int fd) { return (sendrequest(fd, SCOPECLEAR)); } + + static bool sendplugins(int fd, HelixSimplePlayer *player); + static bool sendmimetypes(int fd, HelixSimplePlayer *player); + static bool sendscopebuf(int fd, DelayQueue *item); +}; + + +#endif diff --git a/amarok/src/engine/kdemm/Makefile.am b/amarok/src/engine/kdemm/Makefile.am new file mode 100644 index 00000000..b040116e --- /dev/null +++ b/amarok/src/engine/kdemm/Makefile.am @@ -0,0 +1,28 @@ +kde_module_LTLIBRARIES = libamarok_kdemmengine_plugin.la +kde_services_DATA = amarok_kdemmengine_plugin.desktop + +INCLUDES = \ + -I$(top_srcdir)/amarok/src/engine \ + -I$(top_srcdir)/amarok/src/plugin \ + -I$(top_srcdir)/amarok/src \ + $(CFLAGS_MAS) $(all_includes) + +libamarok_kdemmengine_plugin_la_LIBADD = \ + $(top_builddir)/amarok/src/libamarok.la \ + $(top_builddir)/amarok/src/plugin/libplugin.la \ + $(LIB_KFILE) $(LIB_KDEUI) $(LIB_KDECORE) \ + $(LIBS_KDEMM) -lkdemm + +libamarok_kdemmengine_plugin_la_SOURCES = \ + kdemmengine.cpp + +libamarok_kdemmengine_plugin_la_LDFLAGS = \ + -module \ + -no-undefined \ + $(KDE_PLUGIN) \ + $(all_libraries) + +METASOURCES = AUTO + + + diff --git a/amarok/src/engine/kdemm/amarok_kdemmengine_plugin.desktop b/amarok/src/engine/kdemm/amarok_kdemmengine_plugin.desktop new file mode 100644 index 00000000..c28b5b85 --- /dev/null +++ b/amarok/src/engine/kdemm/amarok_kdemmengine_plugin.desktop @@ -0,0 +1,119 @@ +[Desktop Entry] +Encoding=UTF-8 +Type=Service +Name=KDEMM Engine +Name[af]=KDEMM Enjin +Name[ar]=محرك KDEMM +Name[bg]=KDEMM +Name[bn]=কেডিই-এমএম ইঞ্জিন +Name[br]=Keflusker KDEMM +Name[ca]=Motor KDEMM +Name[cs]=KDEMM systém +Name[da]=KDEMM-motor +Name[de]=KDEMM +Name[el]=Μηχανή KDEMM +Name[eo]=KDEMM Ilo +Name[es]=Motor KDEMM +Name[et]=KDEMM mootor +Name[eu]=KDEMM motorea +Name[fa]=موتور KDEMM +Name[fi]=KDEMM +Name[fr]=Moteur KDEMM +Name[ga]=Inneall KDEMM +Name[gl]=Motor KDEMM +Name[he]=מנוע שמע KDEMM +Name[hi]=केडीईएमएम इंजिन +Name[hu]=KDEMM alrendszer +Name[is]=KDEMM vél +Name[it]=Motore KDEMM +Name[ja]=KDEMM エンジン +Name[ka]=KDEMM dრავა +Name[km]=ម៉ាស៊ីន KDEMM +Name[lt]=KDEMM variklis +Name[mk]=KDEMM-машина +Name[ms]=Enjin KDEMM +Name[nb]=KDEMM-motor +Name[nds]=KDEMM +Name[ne]=KDEMM इन्जिन +Name[nn]=KDEMM-motor +Name[pa]=KDEMM ਇੰਜਣ +Name[pl]=Wyjście KDEMM +Name[pt]=Motor KDEMM +Name[pt_BR]=Mecanismo KDEMM +Name[ru]=KDEMM +Name[se]=KDEMM-mohtor +Name[sl]=Pogon KDEMM +Name[sq]=Motor KDEMM +Name[sr]=Мотор KDEMM +Name[sr@Latn]=Motor KDEMM +Name[ss]=Motor KDEMM +Name[sv]=KDEMM-gränssnitt +Name[ta]=KDEMM என்ஜின் +Name[tg]=Муҳаррики KDEMM +Name[tr]=KDEMM Motoru +Name[uk]=Рушій KDEMM +Name[uz]=KDEMM tizimi +Name[uz@cyrillic]=KDEMM тизими +Name[wa]=Éndjin KDEMM +Name[zh_CN]=KDEMM 引擎 +Name[zh_TW]=KDEMM 引擎 +Comment=Plugin for Amarok +Comment[af]=Inprop module vir Amarok +Comment[ar]= قابس ( برنامج مضاف الى) AmaroK +Comment[bg]=Приставка за Amarok +Comment[bn]=আমারক-এর জন্য প্লাগিন +Comment[br]=Lugent evit Amarok +Comment[ca]=Connector per l'Amarok +Comment[cs]=Modul pro AmaroK +Comment[de]=Modul für Amarok +Comment[el]=Πρόσθετο για το AmaroK +Comment[eo]=Kromaĵo por Amarok +Comment[es]=Extensión para Amarok +Comment[et]=Amaroki plugin +Comment[fa]=وصله برای amaroK +Comment[fi]=Amarok-liitännäinen +Comment[fr]=Module pour Amarok +Comment[ga]=Breiseán AmaroK +Comment[gl]=Extensión para Amarok +Comment[hu]=Bővítőmodul az Amarokhoz +Comment[is]=Íforrit fyrir Amarok +Comment[it]=Plugin per Amarok +Comment[ja]=Amarok のためのプラグイン +Comment[ka]=მოდული Amarok-ისთვის +Comment[km]=កម្មវិធី​ជំនួយ​សម្រាប់ Amarok +Comment[lt]=Amarok įskiepis +Comment[mk]=Приклучок за Амарок +Comment[nb]=Programtillegg for Amarok +Comment[nds]=Moduul för Amarok +Comment[ne]=अमारोकका लागि प्लगइन +Comment[nl]=Plugin voor Amarok +Comment[nn]=Programtillegg for Amarok +Comment[pa]=ਅਮਰੋਕ ਲਈ ਪਲੱਗਇਨ +Comment[pl]=Wtyczka Amaroka +Comment[pt]='Plugin' para o Amarok +Comment[pt_BR]=Plugin para o Amarok +Comment[ru]=Модуль amaroK +Comment[se]=Lassemoduvla Amarok:ii +Comment[sk]=Amarok modul +Comment[sr]=Прикључак за Amarok +Comment[sr@Latn]=Priključak za Amarok +Comment[sv]=Insticksprogram för Amarok +Comment[th]=โปรแกรมเสริมสำหรับ Amarok +Comment[tr]=Amarok için Eklenti +Comment[uk]=Втулок для Amarok +Comment[uz]=Amarok uchun plagin +Comment[uz@cyrillic]=Amarok учун плагин +Comment[wa]=Tchôke-divins po Amarok +Comment[zh_CN]=Amarok 插件 +Comment[zh_TW]=amaroK 插件 + +ServiceTypes=Amarok/Plugin +X-KDE-Library=libamarok_kdemmengine_plugin +X-KDE-Amarok-name=KDEMM Engine +X-KDE-Amarok-plugintype=engine +X-KDE-Amarok-authors=Roland Gigler +X-KDE-Amarok-email=rolandg@web.de +X-KDE-Amarok-rank=0 +X-KDE-Amarok-version=1 +X-KDE-Amarok-framework-version=32 + diff --git a/amarok/src/engine/kdemm/kdemmengine.cpp b/amarok/src/engine/kdemm/kdemmengine.cpp new file mode 100644 index 00000000..6c4174de --- /dev/null +++ b/amarok/src/engine/kdemm/kdemmengine.cpp @@ -0,0 +1,261 @@ +/*************************************************************************** + kdemmengine.cpp - KDE Multimedia (KDEMM) interface + ------------------- +begin : 2004-10-01 +copyright : (C) 2004 by Roland Gigler +email : rolandg@web.de +what : interface to the KDE Multimedia interface (KDEMM) +***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "enginebase.h" +#include "engineobserver.h" + +#include <assert.h> +#include <math.h> //setVolume(), timerEvent() +#include <string> +#include <vector> + +#include <qdir.h> +#include <qdom.h> +#include <qfile.h> +#include <qlayout.h> +#include <qstring.h> +#include <qtextstream.h> +#include <qtimer.h> + +#include <kapplication.h> +#include <kconfig.h> +#include <kdebug.h> +#include <kfileitem.h> +#include <kgenericfactory.h> +#include <klocale.h> +#include <kmessagebox.h> +#include <kmimetype.h> +#include <kstandarddirs.h> +#include <kurl.h> + +#include <kdemm/simpleplayer.h> + +#include "kdemmengine.h" + +#define KDEMM_TIMER 250 + +AMAROK_EXPORT_PLUGIN( KDEMMEngine ) + +using namespace KDE::Multimedia; + +KDEMMEngine::KDEMMEngine( ) + : Engine::Base() +// , m_scopeId( 0 ) +// , m_xfadeFadeout( false ) +// , m_xfadeValue( 0.0 ) +// , m_xfadeCurrent( "invalue2" ) + , m_state( Engine::Empty ) + , m_pPlayingTimer( new QTimer( this ) ) +{ + kdDebug() << k_funcinfo << endl; + m_player = new SimplePlayer(); +} + + +KDEMMEngine::~KDEMMEngine() +{ + kdDebug() << "BEGIN " << k_funcinfo << endl; + m_pPlayingTimer->stop(); + delete m_player; + kdDebug() << "END " << k_funcinfo << endl; +} + + +bool KDEMMEngine::init() +{ + kdDebug() << "BEGIN " << k_funcinfo << endl; + /* + m_scopeSize = 1 << scopeSize; + m_restoreEffects = restoreEffects; + m_mixerHW = -1; //initialize + */ + + connect ( m_pPlayingTimer, SIGNAL( timeout() ), this, SLOT( playingTimeout() ) ); + + kdDebug() << "END " << k_funcinfo << endl; + return true; +} + + +//////////////////////////////////////////////////////////////////////////////// +// PUBLIC METHODS +//////////////////////////////////////////////////////////////////////////////// + +bool KDEMMEngine::canDecode( const KURL &url ) const +{ + static QStringList list; + + kdDebug() << "BEGIN " << k_funcinfo << endl; + kdDebug() << " Param: url: " << url << endl; + //kdDebug() << " url.protocol() >" << url.protocol() <<"<"<< endl; + + if (url.protocol() == "http" ) return false; + + // TODO determine list of supported MimeTypes/Extensions from KDEMM + list += QString("audio/x-mp3"); + + KFileItem fileItem( KFileItem::Unknown, KFileItem::Unknown, url, false ); //false = determineMimeType straight away + KMimeType::Ptr mimetype = fileItem.determineMimeType(); + kdDebug() << "mimetype: " << mimetype->name().latin1() << endl; + + return list.contains( mimetype->name().latin1() ); +} // canDecode + + +bool KDEMMEngine::load( const KURL& url, bool stream ) +{ + kdDebug() << "BEGIN " << k_funcinfo << endl; + + m_isStream = stream; + kdDebug() << " m_url: " << m_url << endl; + kdDebug() << " Param: stream: " << stream << endl; + kdDebug() << " Param: url " << url << endl; + + if ( !url.isLocalFile() ) { // for now + return false; + } + + if ( m_url == url ) { + return true; + } else { + stop(); + } + m_url = url; + // the KDEMM SimplePlayer dows the loading in the play method + + m_state = Engine::Idle; + + kdDebug() << "END " << k_funcinfo << endl; + return true; +} // load + + +bool KDEMMEngine::play( unsigned int offset) +{ + kdDebug() << "BEGIN " << k_funcinfo << endl; + kdDebug() << " param: offset " << offset << endl; + + m_player->play(m_url); + m_pPlayingTimer->start(KDEMM_TIMER, false); + m_state = Engine::Playing; + emit stateChanged( Engine::Playing ); + + kdDebug() << "END " << k_funcinfo << endl; + return true; +} // play + + +/* + * return current position in milli seconds +*/ +uint KDEMMEngine::position() const +{ + uint pos=0; + //kdDebug() << "BEGIN " << k_funcinfo << endl; + + //kdDebug() << " totalTime: " << m_player->totalTime() << endl; + //kdDebug() << " currentTime: " << m_player->currentTime() << endl; + + pos = m_player->currentTime(); + + //kdDebug() << "END " << k_funcinfo << endl; + return (pos); +} // position + + +////////////////////////////////////////////////////////////////////// + +void KDEMMEngine::stop() +{ + kdDebug() << "BEGIN " << k_funcinfo << endl; + + //switch xfade channels +/* m_xfadeCurrent = ( m_xfadeCurrent == "invalue1" ) ? "invalue2" : "invalue1"; + + if ( m_xfadeValue == 0.0 ) + m_xfadeValue = 1.0; +*/ + + m_player->stop(); + m_pPlayingTimer->stop(); + + m_state = Engine::Idle; // Empty + emit stateChanged( m_state ); + kdDebug() << "END " << k_funcinfo << endl; +} + + +void KDEMMEngine::pause() +{ + kdDebug() << "BEGIN " << k_funcinfo << endl; + + // KDEMM: pause() cannot do un-pause, do it manually + if (m_state == Engine::Paused){ + m_player->play(m_url); + m_pPlayingTimer->start(KDEMM_TIMER, false); + m_state = Engine::Playing; + } else { + m_player->pause(); + m_pPlayingTimer->stop(); + m_state = Engine::Paused; + } + + emit stateChanged( m_state ); + kdDebug() << "END " << k_funcinfo << endl; +} // pause + + +void KDEMMEngine::seek( unsigned int ms ) +{ + kdDebug() << "BEGIN " << k_funcinfo << endl; + kdDebug() << " param: ms " << ms << endl; + m_player->seek(ms); + kdDebug() << "END " << k_funcinfo << endl; +} // seek + + +void KDEMMEngine::setVolumeSW( unsigned int percent ) +{ + kdDebug() << "BEGIN " << k_funcinfo << endl; + kdDebug() << " Param: percent " << percent << endl; + + + float vol = percent*0.01; + kdDebug() << " setting vol to " << vol << endl; + m_player->setVolume(vol); + + kdDebug() << "END " << k_funcinfo << endl; +} // setVolumeSW + +//////////////////////////////////////////////////////////////////////////////// +// PRIVATE METHODS +//////////////////////////////////////////////////////////////////////////////// + +void KDEMMEngine::playingTimeout() //SLOT +{ + //kdDebug() << "BEGIN " << k_funcinfo << endl; + if( !m_player->isPlaying() ) { + m_pPlayingTimer->stop(); + m_state = Engine::Idle; + emit trackEnded(); + } + //kdDebug() << "END " << k_funcinfo << endl; +} // playingTimeout + +#include "kdemmengine.moc" + diff --git a/amarok/src/engine/kdemm/kdemmengine.h b/amarok/src/engine/kdemm/kdemmengine.h new file mode 100644 index 00000000..42bb23fb --- /dev/null +++ b/amarok/src/engine/kdemm/kdemmengine.h @@ -0,0 +1,83 @@ +/*************************************************************************** + kdemmengine.h - KDEMM audio interface + ------------------- +begin : Jul 04 2004 +copyright : (C) 2004 by Roland Gigler +email : rolandg@web.de +what : interface to KDE Multimedia (KDEMM) +***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef AMAROK_KDEMMENGINE_H +#define AMAROK_KDEMMENGINE_H + +#include "enginebase.h" + +#include <vector> + +#include <qguardedptr.h> +#include <qmap.h> +#include <qstringlist.h> +#include <qwidget.h> + + +class QTimer; +class KURL; + +namespace KDE { namespace Multimedia { class SimplePlayer; } } + +class KDEMMEngine : public Engine::Base +{ + Q_OBJECT + + public: + KDEMMEngine(); + ~KDEMMEngine(); + + bool init(); + + bool initMixer( bool hardware ); + bool canDecode( const KURL& ) const; + uint position() const; + Engine::State state() const {return m_state;} +// std::vector<float>* scope(); +// bool decoderConfigurable(); +// void configureDecoder(); + bool supportsXFade() const { return false; } + + public slots: + bool load( const KURL&, bool stream ); + bool play( unsigned int offset = 0); + void stop(); + void pause(); + void seek( unsigned int ms ); + void setVolumeSW( unsigned int percent ); + private slots: + void playingTimeout(); + private: + //void startXfade(); + ///////////////////////////////////////////////////////////////////////////////////// + // ATTRIBUTES + ///////////////////////////////////////////////////////////////////////////////////// + Engine::State m_state; + //long m_scopeId; + //int m_scopeSize; + //bool m_xfadeFadeout; + //float m_xfadeValue; + //QString m_xfadeCurrent; + KURL m_url; + KDE::Multimedia::SimplePlayer *m_player; + + QTimer* m_pPlayingTimer; +}; + +#endif // AMAROK_KDEMM_ENGINE + diff --git a/amarok/src/engine/mas/HOWTO b/amarok/src/engine/mas/HOWTO new file mode 100644 index 00000000..f5977805 --- /dev/null +++ b/amarok/src/engine/mas/HOWTO @@ -0,0 +1,29 @@ +HOWTO set up the MAS support + + +donwload MAS code from their website www.mediaapplicationserver.net + - mas-0.6.3.tar.gz + - mas-devtools-0.6.3.tar.gz + - mas-control-apps-0.6.3.tar.gz + or get them from their CVS + + - mas-codec_mp1a_mad-0.6.3.tar.gz (!not in CVS ??) + + +compile, install + +add /usr/local/mas/lib to /etc/ld.so.conf +call ldconfig +edit /usr/local/mas/mas-launch + fix 1st line: #!/bin/sh + move line 12 to line 25: export LD_LIBRARY_PATH # somehow i need this + +start mas-launch (superuser) + +you can find the logfile in /usr/local/mas/log + + +start masplayer for testing + + + diff --git a/amarok/src/engine/mas/Makefile.am b/amarok/src/engine/mas/Makefile.am new file mode 100644 index 00000000..2a56d4e6 --- /dev/null +++ b/amarok/src/engine/mas/Makefile.am @@ -0,0 +1,28 @@ +kde_module_LTLIBRARIES = libamarok_masengine_plugin.la +kde_services_DATA = amarok_masengine_plugin.desktop + +INCLUDES = \ + -I$(top_srcdir)/amarok/src/engine \ + -I$(top_srcdir)/amarok/src/plugin \ + -I$(top_srcdir)/amarok/src \ + $(CFLAGS_MAS) $(all_includes) + +libamarok_masengine_plugin_la_LIBADD = \ + $(top_builddir)/amarok/src/engine/libengine.la \ + $(top_builddir)/amarok/src/plugin/libplugin.la \ + $(LIB_KFILE) $(LIB_KDEUI) $(LIB_KDECORE) \ + $(LIBS_MAS) + +libamarok_masengine_plugin_la_SOURCES = \ + masengine.cpp + +libamarok_masengine_plugin_la_LDFLAGS = \ + -module \ + -no-undefined \ + $(KDE_PLUGIN) \ + $(all_libraries) + +METASOURCES = AUTO + + + diff --git a/amarok/src/engine/mas/TODO b/amarok/src/engine/mas/TODO new file mode 100644 index 00000000..a717754a --- /dev/null +++ b/amarok/src/engine/mas/TODO @@ -0,0 +1,12 @@ +TODO + +- ask MAS which mimetypes it can play (MAS) +- get/set position on source device (MAS) + (position is workarounded in the Engine. Grrgh) +- more decoders (OGG, ...) (MAS) + +- CrossFading ? +- scope ? +- configuration ? +- network ? + diff --git a/amarok/src/engine/mas/amarok_masengine_plugin.desktop b/amarok/src/engine/mas/amarok_masengine_plugin.desktop new file mode 100644 index 00000000..b2fdcdd9 --- /dev/null +++ b/amarok/src/engine/mas/amarok_masengine_plugin.desktop @@ -0,0 +1,119 @@ +[Desktop Entry] +Encoding=UTF-8 +Type=Service +Name=MAS Engine +Name[af]=MAS Enjin +Name[ar]=محرك MAS +Name[bg]=MAS +Name[bn]=এম-এ-এস ইঞ্জিন +Name[br]=Keflusker MAS +Name[ca]=Motor MAS +Name[cs]=MAS systém +Name[da]=MAS-motor +Name[de]=MAS +Name[el]=Μηχανή MAS +Name[eo]=MAS Ilo +Name[es]=Motor MAS +Name[et]=MAS mootor +Name[eu]=MAS motorea +Name[fa]=موتور MAS +Name[fi]=MAS +Name[fr]=Moteur MAS +Name[ga]=Inneall MAS +Name[gl]=Motor MAS +Name[he]=מנוע שמע MAS +Name[hi]=एमएएस इंजिन +Name[hu]=MAS alrendszer +Name[is]=MAS vél +Name[it]=Motore MAS +Name[ja]=MAS エンジン +Name[ka]=MAS dრავა +Name[km]=ម៉ាស៊ីន MAS +Name[lt]=MAS variklis +Name[mk]=MAS-машина +Name[ms]=Enjin MAS +Name[nb]=MAS-motor +Name[nds]=MAS +Name[ne]=मास इन्जिन +Name[nn]=MAS-motor +Name[pa]=MAS ਇੰਜਣ +Name[pl]=Wyjście MAS +Name[pt]=Motor MAS +Name[pt_BR]=Mecanismo MAS +Name[ru]=MAS +Name[se]=MAS-mohtor +Name[sl]=Pogon MAS +Name[sq]=Motor MAS +Name[sr]=Мотор MAS +Name[sr@Latn]=Motor MAS +Name[ss]=Motor MAS +Name[sv]=MAS-gränssnitt +Name[ta]=MAS என்ஜின் +Name[tg]=Муҳаррики MAS +Name[tr]=MAS Motoru +Name[uk]=Рушій MAS +Name[uz]=MAS tizimi +Name[uz@cyrillic]=MAS тизими +Name[wa]=Éndjin MAS +Name[zh_CN]=MAS 引擎 +Name[zh_TW]=MAS 解碼引擎 +Comment=Plugin for Amarok +Comment[af]=Inprop module vir Amarok +Comment[ar]= قابس ( برنامج مضاف الى) AmaroK +Comment[bg]=Приставка за Amarok +Comment[bn]=আমারক-এর জন্য প্লাগিন +Comment[br]=Lugent evit Amarok +Comment[ca]=Connector per l'Amarok +Comment[cs]=Modul pro AmaroK +Comment[de]=Modul für Amarok +Comment[el]=Πρόσθετο για το AmaroK +Comment[eo]=Kromaĵo por Amarok +Comment[es]=Extensión para Amarok +Comment[et]=Amaroki plugin +Comment[fa]=وصله برای amaroK +Comment[fi]=Amarok-liitännäinen +Comment[fr]=Module pour Amarok +Comment[ga]=Breiseán AmaroK +Comment[gl]=Extensión para Amarok +Comment[hu]=Bővítőmodul az Amarokhoz +Comment[is]=Íforrit fyrir Amarok +Comment[it]=Plugin per Amarok +Comment[ja]=Amarok のためのプラグイン +Comment[ka]=მოდული Amarok-ისთვის +Comment[km]=កម្មវិធី​ជំនួយ​សម្រាប់ Amarok +Comment[lt]=Amarok įskiepis +Comment[mk]=Приклучок за Амарок +Comment[nb]=Programtillegg for Amarok +Comment[nds]=Moduul för Amarok +Comment[ne]=अमारोकका लागि प्लगइन +Comment[nl]=Plugin voor Amarok +Comment[nn]=Programtillegg for Amarok +Comment[pa]=ਅਮਰੋਕ ਲਈ ਪਲੱਗਇਨ +Comment[pl]=Wtyczka Amaroka +Comment[pt]='Plugin' para o Amarok +Comment[pt_BR]=Plugin para o Amarok +Comment[ru]=Модуль amaroK +Comment[se]=Lassemoduvla Amarok:ii +Comment[sk]=Amarok modul +Comment[sr]=Прикључак за Amarok +Comment[sr@Latn]=Priključak za Amarok +Comment[sv]=Insticksprogram för Amarok +Comment[th]=โปรแกรมเสริมสำหรับ Amarok +Comment[tr]=Amarok için Eklenti +Comment[uk]=Втулок для Amarok +Comment[uz]=Amarok uchun plagin +Comment[uz@cyrillic]=Amarok учун плагин +Comment[wa]=Tchôke-divins po Amarok +Comment[zh_CN]=Amarok 插件 +Comment[zh_TW]=amaroK 插件 + +ServiceTypes=Amarok/Plugin +X-KDE-Library=libamarok_masengine_plugin +X-KDE-Amarok-name=MAS Engine +X-KDE-Amarok-plugintype=engine +X-KDE-Amarok-authors=Roland Gigler +X-KDE-Amarok-email=rolandg@web.de +X-KDE-Amarok-rank=0 +X-KDE-Amarok-version=1 +X-KDE-Amarok-framework-version=32 + diff --git a/amarok/src/engine/mas/masengine.cpp b/amarok/src/engine/mas/masengine.cpp new file mode 100644 index 00000000..3abe6f9c --- /dev/null +++ b/amarok/src/engine/mas/masengine.cpp @@ -0,0 +1,591 @@ +/*************************************************************************** + masengine.cpp - MAS audio interface + ------------------- +begin : 2004-07-20 +copyright : (C) 2004-05 by Roland Gigler +email : rolandg@web.de +what : interface to the Media Application Server (MAS) +***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#define DEBUG_PREFIX "MAS-Engine" + +#include "debug.h" +#include "enginebase.h" +#include "engineobserver.h" + +#include <assert.h> +#include <math.h> //setVolume(), timerEvent() +#include <string> +//#include <vector> + +#include <qtimer.h> +#include <qfile.h> + +#include <kapplication.h> +#include <kconfig.h> +#include <kfileitem.h> +#include <kgenericfactory.h> +#include <klocale.h> +#include <kmessagebox.h> +#include <kmimetype.h> +#include <kstandarddirs.h> +#include <kurl.h> + +#include "masengine.h" +//#define DB_CUTOFF -40.0 +#define BUFFER_TIME_MS 300 +#define POSTOUT_TIME_MS 100 +//#define QUERY_MIX_VOLUME 0 +//#define QUERY_MIX_EPSILON 5 + + + +AMAROK_EXPORT_PLUGIN( MasEngine ) + + +MasEngine::MasEngine() + : Engine::Base() + , m_inited (false) +// , m_scopeId( 0 ) +// , m_xfadeFadeout( false ) +// , m_xfadeValue( 0.0 ) +// , m_xfadeCurrent( "invalue2" ) + , m_lastKnownPosition( 0 ) + , m_state( Engine::Empty ) + , m_pPlayingTimer( new QTimer( this ) ) + +{ + DEBUG_FUNC_INFO + + // NOT SUPPORTED + //addPluginProperty( "HasConfigure", "true" ); + //addPluginProperty( "HasCrossfading", "true" ); + //addPluginProperty( "HasEqualizer", "true" ); +} + + +MasEngine::~MasEngine() +{ + DEBUG_BLOCK + + if ( m_inited ) stop(); + m_pPlayingTimer->stop(); + killTimers(); +} + + +bool MasEngine::init() +{ + DEBUG_BLOCK + + if (!masinit() ) { + KMessageBox::error( 0, i18n("<h3>Amarok could not initialise MAS.</h3>" + "<p>Check for a running mas daemon.</p>") ); + error() << " connecting to MAS daemon failed. Aborting. " << endl; + debug() << " Please restart amarok." << endl; + debug() << k_funcinfo << " returns false !" << endl; + return false; + } + m_inited=true; // we connected to MAS + + connect ( m_pPlayingTimer, SIGNAL( timeout() ), this, SLOT( playingTimeout() ) ); + + emit statusText( "MAS Engine inited :-)"); + return true; +} + + +//////////////////////////////////////////////////////////////////////////////// +// PUBLIC METHODS +//////////////////////////////////////////////////////////////////////////////// + +bool MasEngine::canDecode( const KURL &url ) const +{ + DEBUG_BLOCK + + QStringList list; + bool playable; + + debug() << " Param: url: " << url << endl; + //debug() << " url.protocol() >" << url.protocol() <<"<"<< endl; + + if (url.protocol() == "http" ) return false; + + // TODO determine list of supported MimeTypes/Extensions from MAS + list += QString("audio/x-mp3"); + + KFileItem fileItem( KFileItem::Unknown, KFileItem::Unknown, url, false ); //false = determineMimeType straight away + KMimeType::Ptr mimetype = fileItem.determineMimeType(); + debug() << "mimetype: " << mimetype->name().latin1() << endl; + + playable = list.contains( mimetype->name().latin1() ); + if ( !playable ) + warning() << "Mimetype is not playable by MAS (" << url << ")" << endl; + + return playable; +} // canDecode + + +bool MasEngine::load( const KURL& url, bool stream ) +{ + DEBUG_BLOCK + struct mas_package pkg; + char pbuf[10240]; + int pos = 0; + + m_isStream = stream; + debug() << " m_url: " << m_url << endl; + debug() << " Param: stream: " << stream << endl; + debug() << " Param: url " << url << endl; + + if ( !url.isLocalFile() ) { // for now + debug() << " only local files are supported (for now)" << endl; + return false; + } + + if ( !canDecode(url) ) { + debug() << " cannot decode!" << endl; + return false; + } + + if ( m_url == url ) { + return true; + } else { + stop(); + } + m_url = url; + + /* send fresh data to MAS */; + masc_setup_package( &pkg, pbuf, sizeof pbuf, MASC_PACKAGE_STATIC ); + masc_pushk_int16( &pkg, (char*)"pos", pos ); + //masc_push_string( &pkg, (char *)m_url.path().latin1() ); + + QCString cs= QFile::encodeName( m_url.path()); + const char *pcs = cs; + masc_push_string( &pkg, (char *)pcs); + masc_finalize_package( &pkg ); + + mas_set( m_mp1a_source_device, (char*)"playlist", &pkg ); + masc_strike_package( &pkg ); + + mas_dev_show_state (m_mp1a_source_device); + mas_source_flush( m_codec ); + + m_lastKnownPosition = 0; + m_state = Engine::Idle; + + return true; +} // load + + +bool MasEngine::play( unsigned int offset) +{ + DEBUG_BLOCK + struct mas_package pkg; + char pbuf[10240]; + + debug() << " param: offset " << offset << endl; + if ( m_state != Engine::Playing ) { + /* change the track */ + masc_setup_package( &pkg, pbuf, sizeof pbuf, MASC_PACKAGE_STATIC ); + masc_pushk_int16( &pkg, (char*)"pos", 1 ); + masc_finalize_package( &pkg ); + mas_set( m_mp1a_source_device, (char*)"ctrack", &pkg ); + masc_finalize_package( &pkg ); + + mas_source_flush( m_codec ); + mas_source_play( m_mp1a_source_device ); + mas_source_play_on_mark( m_sbuf ); + + debug() << "mas_source_play()" << endl; + } + + m_pPlayingTimer->start(MAS_TIMER, false); + + m_state = Engine::Playing; + emit stateChanged( Engine::Playing ); + + return true; +} // play +uint +MasEngine::length() const +{ + DEBUG_BLOCK + char pbuf[128]; + struct mas_package pkg; + struct mas_package nugget; + float trklen; + + masc_setup_package( &pkg, pbuf, sizeof pbuf, MASC_PACKAGE_STATIC ); + masc_pushk_int16( &pkg, (char *)"pos", 1 ); + masc_finalize_package( &pkg ); + + mas_get( m_mp1a_source_device, (char *)"trklen", &pkg, &nugget ); + masc_strike_package( &pkg ); + + masc_pullk_float( &nugget, (char *)"trklen", &trklen ); + masc_strike_package( &nugget ); + + debug() << "trklen: " << trklen << endl; + return uint(trklen*1000); +} // position + +/* + * return current position in milli seconds +*/ +uint MasEngine::position() const +{ + return m_lastKnownPosition; +} // position + + +////////////////////////////////////////////////////////////////////// + + +void MasEngine::playingTimeout() //SLOT +{ + //DEBUG_BLOCK + m_lastKnownPosition += MAS_TIMER; + + // ask MAS if it's still playing + struct mas_package nugget; + int16 pos; + mas_get( m_mp1a_source_device, (char *)"ctrack", NULL, &nugget ); + masc_pullk_int16( &nugget, (char *)"pos", &pos ); + masc_strike_package( &nugget ); + + if ( pos == 0 ) { + m_pPlayingTimer->stop(); + + m_state = Engine::Idle; + emit trackEnded(); + } + +} // playingTimeout //SLOT + + +void MasEngine::stop() +{ + DEBUG_BLOCK + + //switch xfade channels +/* m_xfadeCurrent = ( m_xfadeCurrent == "invalue1" ) ? "invalue2" : "invalue1"; + + if ( m_xfadeValue == 0.0 ) + m_xfadeValue = 1.0; +*/ + mas_source_stop( m_mp1a_source_device ); + mas_source_stop( m_sbuf ); + debug() << "performed: mas_source_stop()" << endl; + + m_pPlayingTimer->stop(); + m_state = Engine::Empty; + m_lastKnownPosition = 0; + + //emit stateChanged( m_state ); +} + + +void MasEngine::pause() +{ + DEBUG_BLOCK + + if(m_state == Engine::Paused) { + mas_source_play( m_mp1a_source_device ); + mas_source_play( m_sbuf ); + debug() << "performed: mas_source_play()" << endl; + + m_state = Engine::Playing; + m_pPlayingTimer->start(MAS_TIMER, false); + + } else { + mas_source_pause( m_mp1a_source_device ); + mas_source_pause( m_sbuf ); + debug() << "performed: mas_source_pause()" << endl; + + m_state = Engine::Paused; + m_pPlayingTimer->stop(); + } + + emit stateChanged( m_state ); +} // pause + + +// TODO depends on MAS support +void MasEngine::seek( unsigned int ms ) +{ + //DEBUG_BLOCK + AMAROK_NOTIMPLEMENTED + //debug() << " param: ms " << ms << endl; +} // seek + + +void MasEngine::setVolumeSW( unsigned int percent ) +{ + DEBUG_BLOCK + debug() << " Param: percent " << percent << endl; + +#ifdef USE_MIX_VOLUME_GENERIC + // MAS takes values from 0 to 128 + int16 vol = (int16)(percent*1.28); + debug() << " setting vol to " << vol << endl; + + // the more generic way + struct mas_package pkg; + char buffer[128]; + + masc_setup_package( &pkg, buffer, sizeof buffer, MASC_PACKAGE_STATIC ); + masc_push_int32( &pkg, m_mix_sink->portnum ); + masc_push_uint16( &pkg, vol ); + masc_finalize_package( &pkg ); + + mas_set( m_mix_device, (char *)"multiplier", &pkg ); + masc_strike_package( &pkg ); + debug() << " after mas_set" << endl; +#else + // use the mix api + double vol = percent*0.01; + debug() << " setting vol to " << vol << endl; + mas_mix_attenuate_linear( m_mix_device, m_mix_sink, vol); +#endif + +} // setVolumeSW + +//////////////////////////////////////////////////////////////////////////////// +// PRIVATE METHODS +//////////////////////////////////////////////////////////////////////////////// +/* +void MasEngine::timerEvent( QTimerEvent* ) +{ + if ( m_xfadeValue > 0.0 ) + { + m_xfadeValue -= ( m_xfadeLength ) ? 1.0 / m_xfadeLength * ARTS_TIMER : 1.0; + + if ( m_xfadeValue <= 0.0 ) + { + m_xfadeValue = 0.0; + } + float value; + if ( m_xfadeFadeout ) + value = 1.0 - log10( ( 1.0 - m_xfadeValue ) * 9.0 + 1.0 ); + else + value = log10( m_xfadeValue * 9.0 + 1.0 ); + + } +} +*/ + +bool MasEngine::masinit() +{ + DEBUG_BLOCK + int32 err; + + masc_log_verbosity( MAS_VERBLVL_DEBUG ); + masc_log_message( 0, (char*)"amarok/MASengine" ); + masc_log_message( 0, (char*)"tries to plays audio using MAS ;-)"); + masc_log_message( 0, (char*)"" ); + + err = mas_init(); + debug() << " mas_init err:" << err << endl; + if (err < 0) + { + warning() << "MAS daemon not running. Starting! Please provide password..." << endl; + // masd seems not to be running, let's try to run it + QCString cmdline; + cmdline = QFile::encodeName( KStandardDirs::findExe( QString::fromLatin1( "kdesu" ) ) ); + // TODO !!!hardcoded path + cmdline += " -n -f /usr/local/mas/log/mas-1.log -c "; + cmdline += "/usr/local/mas/bin/mas-launch"; + + debug() << " cmdline: " << cmdline << endl; + int status = ::system( cmdline ); + debug() << " status: " << status << endl; + + debug() << " give the MAS daemon some time (3s) ... " << endl; + ::sleep( 3 ); + + if ( status != -1 && WIFEXITED( status ) ) + { + int time = 0; + do + { + debug() << " time: " << time << endl; + // every time it fails, we should wait a little longer + // between tries + ::sleep( 1 + time / 2 ); + err = mas_init(); + debug() << " mas_init err: " << err << endl; + } + while ( ++time < 5 && ( err < 0 ) ); + } + } + + if (err < 0) + { + QString text = "Connection to MAS daemon FAILED. Please check your installation."; + error() << text << endl; + emit statusText( text ); + return false; + } + debug() << "Connection to MAS daemon is up :-)" << endl; + + struct mas_data_characteristic* dc; + struct mas_package nugget; + mas_device_t anx; + mas_port_t tmp_source; + mas_channel_t local; + + err = mas_get_local_control_channel( &local ); + if ( err < 0 ) masc_logerror( err, "getting local control channel" ); + debug() << " after mas_get_local_control_channel" << endl; + + /* Get the id of the sample clock provided by the anx device -- if + we can! */ + err = mas_asm_get_device_by_name( (char*)"anx", &anx ); + mas_assert( err >= 0, (char*)"Couldn't get anx device" ); + debug() << " after mas_asm_get_device_by_name" << endl; + + m_sink_clkid = 0; + err = mas_get( anx, (char*)"mc_clkid", NULL, &nugget ); + if ( err >= 0 ) + { + masc_pull_int32( &nugget, &m_sink_clkid ); + masc_strike_package( &nugget ); + } + debug() <<" after mas_get (mc_clkid)" << endl; + + /* CODEC */ + err = mas_asm_instantiate_device( (char*)"codec_mp1a_mad", 0, 0, &m_codec ); + if (err < 0 ) + { + masc_log_message( MAS_VERBLVL_INFO, (char*)"Couldn't instantiate mp1a_mad codec, trying mp1a codec."); + err = mas_asm_instantiate_device( (char*)"codec_mp1a", 0, 0, &m_codec ); + mas_assert( err >= 0, (char*)"Couldn't instantiate MPEG codec"); + } + debug() << " after mas_asm_instantiate_device (codec_mp1a_mad)" << endl; + + /* source - instantiated on the local MAS server */ + err = mas_asm_instantiate_device_on_channel( (char*)"source_mp1a", 0, 0, &m_mp1a_source_device, local ); + mas_assert( err >= 0, (char*)"Couldn't instantiate source_mp1a" ); + + /* buffer */ + err = mas_asm_instantiate_device( (char*)"sbuf", 0, 0, &m_sbuf ); + mas_assert( err >= 0, (char*)"Couldn't instantiate sbuf device" ); + debug() << " after mas_asm_instantiate_device (sbuf)" << endl; + + m_visual = NULL; +#ifdef USE_VISUAL + err = mas_asm_instantiate_device( (char*)"visual", 0, 0, &m_visual ); + if ( err < 0 ) + { + masc_log_message( 0, (char*)"Couldn't instantiate visual device. It's okay, I just won't use it." ); + m_visual = NULL; + } + debug() << " after mas_asm_instantiate_device (visual)" << endl; +#endif + + + /* get a handle to the mixer */ + err = mas_asm_get_device_by_name( (char*)"mix", &m_mix_device ); + mas_assert( err >= 0, (char*)"Couldn't get mixer device" ); + debug() << " after mas_asm_get_device_by_name (mix)" << endl; + + /* start making connections: + * + * source->codec->visual->mix + */ + + err = mas_asm_connect_devices( m_mp1a_source_device, m_codec, (char*)"source", (char*)"sink" ); + mas_assert( err >= 0, "Couldn't connect MPEG source to MPEG codec" ); + + dc = masc_make_audio_basic_dc( MAS_LINEAR_FMT, 44100, 20, 2, MAS_HOST_ENDIAN_FMT ); + mas_assert( dc, "Couldn't create audio data characteristic." ); + + err = mas_asm_get_port_by_name( m_mix_device, (char*)"default_mix_sink", &m_mix_sink ); + mas_assert( err >= 0, "Couldn't get default mixer sink" ); + debug() << " m_mix_sink:" << m_mix_sink << endl; + + err = mas_asm_connect_devices_dc( m_codec, m_sbuf, (char*)"source", (char*)"sink", dc ); + mas_assert( err >= 0, "Couldn't connect MPEG codec to sbuf" ); + debug() << " after mas_asm_connect_device_dc (source, sink)" << endl; + + if (m_visual != NULL) + { + err = mas_asm_connect_devices_dc( m_sbuf, m_visual, (char*)"source", (char*)"sink", dc ); + mas_assert( err >= 0, "Couldn't connect sbuf to visual device." ); + + err = mas_asm_get_port_by_name( m_visual, (char*)"source", &tmp_source ); + mas_assert( err >= 0, "Couldn't get visual source port" ); + + err = mas_asm_connect_source_sink( tmp_source, m_mix_sink, dc ); + mas_assert( err >= 0, "Couldn't connect visual device to mixer sink." ); + } + else + { + err = mas_asm_get_port_by_name( m_sbuf, (char*)"source", &tmp_source ); + mas_assert( err >= 0, "Couldn't get sbuf source port" ); + + err = mas_asm_connect_source_sink( tmp_source, m_mix_sink, dc ); + mas_assert( err >= 0, "Couldn't connect sbuf to mix device." ); + } + debug() << " after ... visual ..." << endl; + + /* set the buffer time */ + masc_setup_package( &nugget, NULL, 0, 0 ); + masc_pushk_uint32( &nugget, (char*)"buftime_ms", BUFFER_TIME_MS ); + masc_finalize_package( &nugget ); + mas_set( m_sbuf, (char*)"buftime_ms", &nugget ); + masc_strike_package( &nugget ); + + debug() << " after ... masc_strike_package ..." << endl; + + masc_setup_package( &nugget, NULL, 0, 0 ); + masc_pushk_uint32( &nugget, "postout_time_ms", POSTOUT_TIME_MS ); + masc_finalize_package( &nugget ); + mas_set( m_sbuf, "postout_time_ms", &nugget ); + masc_strike_package( &nugget ); + + /* If we can use a sample clock, let's do it... */ + if ( m_sink_clkid > 0 ) + { + mas_get_mc_device( &m_sink_mc ); + mas_get_mc_device_on_channel( &m_source_mc, local ); + debug() << " after ... masc_get_mc_device_on_channel ..." << endl; + + //get_measured_sample_freq(); +/* + m_source_clkid = mas_mc_add_fixed_clock( "player", m_measured_sample_freq, local ); + printf (" after ... masc_mc_add_fixed_clock ...\n"); + masc_log_message( 0, "got clock: %d", m_source_clkid ); + */ + /* the file source device uses the file clock */ +/* masc_setup_package( &nugget, NULL, 0, 0 ); + masc_pushk_int32( &nugget, "mc_clkid", m_source_clkid ); + masc_finalize_package( &nugget ); + mas_set( mp1a_source_device, "mc_clkid", &nugget ); + masc_strike_package( &nugget ); +*/ + /* and the sbuf, which could be on a different machine, uses + the sample clock from that machine's anx device. */ +/* masc_setup_package( &nugget, NULL, 0, 0 ); + masc_pushk_int32( &nugget, "mc_clkid", m_sink_clkid ); + masc_finalize_package( &nugget ); + mas_set( m_sbuf, "mc_clkid", &nugget ); + masc_strike_package( &nugget ); +*/ + } + + return true; +} // masinit + +#include "masengine.moc" + diff --git a/amarok/src/engine/mas/masengine.h b/amarok/src/engine/mas/masengine.h new file mode 100644 index 00000000..15c4f6ca --- /dev/null +++ b/amarok/src/engine/mas/masengine.h @@ -0,0 +1,108 @@ +/*************************************************************************** + masengine.h - Media Application Server (MAS) audio interface + ------------------- +begin : Jul 04 2004 +copyright : (C) 2004 by Roland Gigler +email : rolandg@web.de +what : interface to the Media Application Server (MAS) +***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef AMAROK_MASENGINE_H +#define AMAROK_MASENGINE_H + +#include "enginebase.h" + +//#include <vector> + +#ifdef __cplusplus +extern "C" { +#endif + #include <mas/mas.h> + #include <mas/mas_core.h> +#ifdef __cplusplus +} +#endif + + +class QTimer; +class KURL; + +class MasEngine : public Engine::Base +{ + Q_OBJECT + + public: + MasEngine(); + ~MasEngine(); + + bool init(); + + bool initMixer( bool hardware ); + bool canDecode( const KURL& ) const; + uint position() const; + uint length() const; + Engine::State state() const {return m_state;} +/* + const Engine::Scope& scope(); + bool decoderConfigurable(); + void configureDecoder(); + */ + bool supportsXFade() const { return false; } + + public slots: + bool load( const KURL&, bool stream ); + bool play( unsigned int offset = 0); + void stop(); + void pause(); + + void seek( unsigned int ms ); + void setVolumeSW( unsigned int percent ); + private slots: + void playingTimeout(); + + private: + //void startXfade(); + //void timerEvent( QTimerEvent* ); + bool masinit(); + ///////////////////////////////////////////////////////////////////////////////////// + // ATTRIBUTES + ///////////////////////////////////////////////////////////////////////////////////// + static const int MAS_TIMER = 250; //ms + //static const int TIMEOUT = 4000; //ms FIXME make option? + bool m_inited; + uint m_lastKnownPosition; + + Engine::State m_state; + //long m_scopeId; + //int m_scopeSize; + //bool m_xfadeFadeout; + //float m_xfadeValue; + //QString m_xfadeCurrent; + QTimer* m_pPlayingTimer; + + KURL m_url; + + mas_device_t m_mp1a_source_device; + mas_device_t m_visual; + mas_device_t m_sbuf; + mas_device_t m_codec; + mas_device_t m_mix_device; + mas_port_t m_mix_sink; + mas_device_t m_sink_mc; + mas_device_t m_source_mc; + int32 m_sink_clkid; + //int32 m_source_clkid; + //double m_measured_sample_freq; +}; + +#endif // AMAROK_MASENGINE_H + diff --git a/amarok/src/engine/nmm/HostList.cpp b/amarok/src/engine/nmm/HostList.cpp new file mode 100644 index 00000000..c4a0dbf7 --- /dev/null +++ b/amarok/src/engine/nmm/HostList.cpp @@ -0,0 +1,156 @@ +/* NMM - Network-Integrated Multimedia Middleware + * + * Copyright (C) 2006 + * NMM work group, + * Computer Graphics Lab, + * Saarland University, Germany + * http://www.networkmultimedia.org + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Steet, Fifth Floor, Boston, MA 02110-1301 + * USA + */ + +#include "HostList.h" + +#include <qcursor.h> +#include <qheader.h> +#include <klocale.h> + +#include "debug.h" +#include "HostListItem.h" + +HostList::HostList( QWidget *parent, const char *name ) + : KListView( parent, name ), + m_read_only( false ), + m_hoveredVolume(0) +{ + // TODO: item should be activated on mouse click + setMouseTracking( true ); + setAllColumnsShowFocus( true ); + + addColumn( i18n("Hostname") ); + addColumn( i18n("Video" ) ); + addColumn( i18n("Audio" ) ); + addColumn( i18n("Volume" ), 113 ); + header()->setResizeEnabled(false, 3); + addColumn( i18n("Status" ) ); + addColumn( i18n("Playback" ) ); + + setColumnAlignment( HostListItem::Hostname, Qt::AlignCenter ); + setColumnAlignment( HostListItem::Video, Qt::AlignCenter ); + setColumnAlignment( HostListItem::Audio, Qt::AlignCenter ); + setColumnAlignment( HostListItem::Volume, Qt::AlignCenter ); + setColumnAlignment( HostListItem::Status, Qt::AlignLeft ); +} + +HostList::~HostList() +{} + +void HostList::notifyHostError( QString hostname, int error) +{ + QListViewItemIterator it( this ); + HostListItem *host; + while( it.current() ) { + host = static_cast<HostListItem*>( it.current() ); + if( host->text(HostListItem::Hostname) == hostname ) + { + host->setText( HostListItem::Hostname, hostname ); + host->setStatus( error ); + host->repaint(); + return; + } + ++it; + } +} + +void HostList::contentsMousePressEvent( QMouseEvent *e) +{ + HostListItem *item = static_cast<HostListItem*>( itemAt( contentsToViewport( e->pos() ) ) ); + if( !( e->state() & Qt::ControlButton || e->state() & Qt::ShiftButton ) && ( e->button() & Qt::LeftButton ) && item) + { + // video column + if( !m_read_only && + e->pos().x() > header()->sectionPos( HostListItem::Video ) && + e->pos().x() < header()->sectionPos( HostListItem::Video ) + header()->sectionSize( HostListItem::Video ) ) + { + item->toggleVideo(); + item->updateColumn( HostListItem::Video ); + emit viewChanged(); + } + // audio column + else + if( !m_read_only && + e->pos().x() > header()->sectionPos( HostListItem::Audio ) && + e->pos().x() < header()->sectionPos( HostListItem::Audio ) + header()->sectionSize( HostListItem::Audio ) ) + { + item->toggleAudio(); + item->updateColumn( HostListItem::Audio ); + emit viewChanged(); + } + // status column + else + if( e->pos().x() > header()->sectionPos( HostListItem::Status ) && + e->pos().x() < header()->sectionPos( HostListItem::Status ) + header()->sectionSize( HostListItem::Status ) ) + { + item->statusToolTip(); + } + else // set new volume for item + if( e->pos().x() > header()->sectionPos( HostListItem::Volume ) && + e->pos().x() < header()->sectionPos( HostListItem::Volume ) + header()->sectionSize( HostListItem::Volume ) ) + { + int vol = e->pos().x(); + vol -= header()->sectionPos( HostListItem::Volume ); + item->setVolume( item->volumeAtPosition( vol ) ); + } + else + KListView::contentsMousePressEvent( e ); + } + else + KListView::contentsMousePressEvent( e ); +} + +void HostList::contentsMouseMoveEvent( QMouseEvent *e ) +{ + if( e ) + KListView::contentsMouseMoveEvent( e ); + + HostListItem *prev = m_hoveredVolume; + const QPoint pos = e ? e->pos() : viewportToContents( viewport()->mapFromGlobal( QCursor::pos() ) ); + + HostListItem *item = static_cast<HostListItem*>( itemAt( contentsToViewport( pos ) ) ); + if( item && pos.x() > header()->sectionPos( HostListItem::Volume ) && + pos.x() < header()->sectionPos( HostListItem::Volume ) + header()->sectionSize( HostListItem::Volume ) ) + { + m_hoveredVolume = item; + m_hoveredVolume->updateColumn( HostListItem::Volume ); + } + else + m_hoveredVolume = 0; + + if( prev ) + prev->updateColumn( HostListItem::Volume ); +} + +void HostList::leaveEvent( QEvent *e ) +{ + KListView::leaveEvent( e ); + + HostListItem *prev = m_hoveredVolume; + m_hoveredVolume = 0; + if( prev ) + prev->updateColumn( HostListItem::Volume ); +} + +#include "HostList.moc" diff --git a/amarok/src/engine/nmm/HostList.h b/amarok/src/engine/nmm/HostList.h new file mode 100644 index 00000000..45d0a590 --- /dev/null +++ b/amarok/src/engine/nmm/HostList.h @@ -0,0 +1,63 @@ +/* NMM - Network-Integrated Multimedia Middleware + * + * Copyright (C) 2006 + * NMM work group, + * Computer Graphics Lab, + * Saarland University, Germany + * http://www.networkmultimedia.org + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Steet, Fifth Floor, Boston, MA 02110-1301 + * USA + */ + +#ifndef HOSTLIST_H +#define HOSTLIST_H + +#include <klistview.h> + +class HostListItem; + +class HostList : public KListView +{ + Q_OBJECT + + public: + HostList( QWidget*, const char* ); + ~HostList(); + + void setReadOnly( bool read_only ) { m_read_only = read_only; } + bool readOnly() const { return m_read_only; } + + void notifyHostError( QString, int); + + friend class HostListItem; + + signals: + /** + * Emitted when audio of video toggle changes. + */ + void viewChanged(); + + protected slots: + void contentsMousePressEvent( QMouseEvent *e ); + void contentsMouseMoveEvent( QMouseEvent *e = 0 ); + void leaveEvent( QEvent *e ); + + private: + bool m_read_only; + HostListItem *m_hoveredVolume; //if the mouse is hovering over the volume of an item +}; + +#endif diff --git a/amarok/src/engine/nmm/HostListItem.cpp b/amarok/src/engine/nmm/HostListItem.cpp new file mode 100644 index 00000000..915632e7 --- /dev/null +++ b/amarok/src/engine/nmm/HostListItem.cpp @@ -0,0 +1,266 @@ +/* NMM - Network-Integrated Multimedia Middleware + * + * Copyright (C) 2005-2006 + * NMM work group, + * Computer Graphics Lab, + * Saarland University, Germany + * http://www.networkmultimedia.org + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Steet, Fifth Floor, Boston, MA 02110-1301 + * USA + */ + +#include "HostListItem.h" + +#include <qbitmap.h> +#include <qfont.h> +#include <qheader.h> +#include <qpainter.h> +#include <qwhatsthis.h> + +#include <kglobal.h> +#include <kiconloader.h> +#include <klocale.h> +#include <kpixmap.h> +#include <kpixmapeffect.h> +#include <kstandarddirs.h> + +#include "debug.h" +#include "HostList.h" +#include "nmm_engine.h" + +HostListItem::HostListItem( QListView *parent, QString hostname, bool audio, bool video, int volume, int status, bool read_only ) + : KListViewItem( parent ), + m_audio( audio ), + m_video( video ), + m_volume( volume ), + m_status( status ), + m_read_only( read_only ) +{ + setText( HostListItem::Hostname, hostname); + + setPixmap( HostListItem::Status, SmallIcon("info") ); + setText( HostListItem::Status, i18n("Unknown") ); + setPixmap( HostListItem::Playback, SmallIcon("info") ); + setText( HostListItem::Playback, i18n("Unknown") ); + + if( 24 /*m_pixmapInset.height()*/ > height() ) + this->setHeight( 24 /*m_pixmapInset.height()*/ ); +} + +HostListItem::~HostListItem() +{ +} + +int HostListItem::volumeAtPosition( int x ) +{ + if( x > 106 ) + return 100; + else if ( x < 6 ) + return -100; + else + return (x - 56) * 2; +} + + +void HostListItem::updateColumn( int column ) const +{ + const QRect r = listView()->itemRect( this ); + if( !r.isValid() ) + return; + + listView()->viewport()->update( listView()->header()->sectionPos( column ) - listView()->contentsX() + 1, + r.y() + 1, + listView()->header()->sectionSize( column ) - 2, height() - 2 ); +} + +void HostListItem::statusToolTip() +{ + QWhatsThis::display( prettyStatus( m_status ) ); +} + +QString HostListItem::prettyStatus( int error ) +{ + QString st; + + debug() << "### ERROR code : " << error << endl; + + st = "<html><body>"; + + if(!error) + st += i18n("So far no status available for this host entry.<br/>Probably this means the host has not been used yet for playback."); + + + if( error & NmmEngine::ERROR_PLAYBACKNODE ) + // TODO distinguish between ALSAPlaybackNode and PlaybackNode + st += i18n("An error appeared during audio playback initialization. Make sure the <b>PlaybackNode</b> is present on your system. If it is present, the command <b>serverregistry -s</b> in a console will list <b>PlaybackNode</b> as <b>available</b>.<br/>"); + + if( error & NmmEngine::ERROR_DISPLAYNODE ) + st += i18n("An error appeared during video playback initialization. Make sure the <b>XDisplayNode</b> is present on your system. If it is present, the command <b>serverregistry -s</b> in a console will list <b>XDisplayNode</b> as <b>available</b>.<br/>"); + + if( error ) + st += i18n("In general have a look at the <a href=\"http://www.networkmultimedia.org/Download/Binary/index.html#configure\">Configuration and tests</a> instructions."); + + st += "</body></html>"; + return st; +} + +void HostListItem::paintCell(QPainter * p, const QColorGroup & cg, int column, int width, int align ) +{ + QColorGroup m_cg( cg ); + + // TODO: reuse icons? + if( column == HostListItem::Video ) + { + if( m_video ) { // video ? + if( m_read_only ) + setPixmap( HostListItem::Video, SmallIcon("nmm_option_on_readonly") ); + else + setPixmap( HostListItem::Video, SmallIcon("nmm_option_on") ); + } + else + if( ! m_read_only) + setPixmap( HostListItem::Video, SmallIcon("nmm_option_off") ); + } + else if( column == HostListItem::Audio ) + { + if( m_audio ) {// audio ? + if( m_read_only ) + setPixmap( HostListItem::Audio, SmallIcon("nmm_option_on_readonly") ); + else + setPixmap( HostListItem::Audio, SmallIcon("nmm_option_on") ); + } + else + if( ! m_read_only) + setPixmap( HostListItem::Audio, SmallIcon("nmm_option_off") ); + } + else if( column == HostListItem::Status ) + { + QFont font( p->font() ); + if( ! m_status ) // Unknown + { + font.setBold( false ); + setText( HostListItem::Status , i18n("Unknown") ); + } + else if( m_status == NmmEngine::STATUS_OK ) + { + font.setBold( false ); + m_cg.setColor( QColorGroup::Text, Qt::darkGreen ); + setText( HostListItem::Status , i18n("OK") ); + } + else { // error + font.setBold( true ); + m_cg.setColor( QColorGroup::Text, Qt::red ); + setText( HostListItem::Status , i18n("Failed") ); + } + p->setFont( font ); + } + else if( column == HostListItem::Volume ) + { + QPixmap buf( width, height() ); + QColor bg = listView()->viewport()->backgroundColor(); + buf.fill( bg ); + + bitBlt( &buf, 0, 0, pixmapVolume( PixInset ) ); + + // Draw gradient + static int padding = 7; + static int vol; // pixelposition + if( this == ((HostList*)listView())->m_hoveredVolume ) + { + vol = listView()->viewportToContents( listView()->viewport()->mapFromGlobal( QCursor::pos() ) ).x(); + vol -= listView()->header()->sectionPos( HostListItem::Volume ); + } + else + vol = (m_volume / 2) + 56; + + //std::cerr << "rel vol = " << vol << std::endl; + + static int center = 56; + if( vol > center ) { + bitBlt( &buf, 0, 0, pixmapVolume( PixRight ), 0, 0, vol + 1 /* TODO: why + 1??? */ ); + } + else if ( vol < center ) { + bitBlt( &buf, vol, 0, pixmapVolume( PixLeft ), vol, 0, 56 ); + } + else + {} + + // Calculate actual volume string from pixelposition + vol = volumeAtPosition( vol ); + QString vol_text; + if( vol > 0 ) + vol_text = "+"; + vol_text += QString::number( vol ); + vol_text += '%'; + + // Draw relative volume number + QPainter p_number(&buf); + p_number.setPen( cg.buttonText() ); + QFont font; + font.setPixelSize( 9 ); + p_number.setFont( font ); + const QRect rect( 40, 0, 34, 15 ); + p_number.drawText( rect, Qt::AlignRight | Qt::AlignVCenter, vol_text ); + p_number.end(); + //bitBlt( p_number.device(), 0, 0, &buf ); + + p->drawPixmap( 0, 0, buf ); + return; + } + + KListViewItem::paintCell(p, m_cg, column, width, align); +} + +QPixmap* HostListItem::pixmapVolume( int type ) +{ + if( type == PixInset ) + { + static QPixmap m_pixmapInset( locate( "data", "amarok/images/nmm-volume-inset.png" ) ); + return &m_pixmapInset; + } + else if( type == PixRight ) + { + static QPixmap m_pixmapGradientRight = generateGradient( PixRight ); + return &m_pixmapGradientRight; + } + else if ( type == PixLeft ) + { + static QPixmap m_pixmapGradientLeft = generateGradient( PixLeft ); + return &m_pixmapGradientLeft; + } + + return 0; +} + +QPixmap HostListItem::generateGradient( int type ) +{ + QPixmap temp; + + if( type == PixRight ) + temp = QPixmap( locate( "data", "amarok/images/nmm-gradient-right.png" ) ); + else // PixLeft + temp = QPixmap( locate( "data", "amarok/images/nmm-gradient-left.png" ) ); + const QBitmap mask( temp.createHeuristicMask() ); + + KPixmap result = QPixmap( 113, 24 ); + if( type == PixRight) + KPixmapEffect::gradient( result, listView()->colorGroup().background(), listView()->colorGroup().highlight(), KPixmapEffect::HorizontalGradient ); + else + KPixmapEffect::gradient( result, listView()->colorGroup().highlight(), listView()->colorGroup().background(), KPixmapEffect::HorizontalGradient ); + + result.setMask( mask); + return result; +} diff --git a/amarok/src/engine/nmm/HostListItem.h b/amarok/src/engine/nmm/HostListItem.h new file mode 100644 index 00000000..9149fd2c --- /dev/null +++ b/amarok/src/engine/nmm/HostListItem.h @@ -0,0 +1,90 @@ +/* NMM - Network-Integrated Multimedia Middleware + * + * Copyright (C) 2005-2006 + * NMM work group, + * Computer Graphics Lab, + * Saarland University, Germany + * http://www.networkmultimedia.org + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Steet, Fifth Floor, Boston, MA 02110-1301 + * USA + */ + +#ifndef HOSTLISTITEM_H +#define HOSTLISTITEM_H + +#include <klistview.h> +#include <qpixmap.h> + +class HostListItem : public KListViewItem { + public: + enum Column + { + Hostname = 0, + Video, + Audio, + Volume, + Status, + Playback + }; + + HostListItem( QListView*, QString hostname, bool audio = true, bool video = true, int volume = 0, int status = 0, bool read_only = false); + ~HostListItem(); + + bool isVideoEnabled() const { return m_video; } + void toggleVideo() { m_video = !m_video; } + + bool isAudioEnabled() const { return m_audio; } + void toggleAudio() { m_audio = !m_audio; } + + void setStatus( int s ) { m_status = s; } + int status() const { return m_status; } + + void setVolume( int v ) { m_volume = v; } + int volume() const { return m_volume; } + int volumeAtPosition( int ); + + void updateColumn( int column ) const; + + /** + * Shows extended status text in a QWhatsThis widget. + * \todo handle different error scenarios + */ + void statusToolTip(); + + /** + * Create detailed status message. + * \todo make it user friendly/understandable + * \todo right place for this method? + */ + static QString prettyStatus( int ); + + protected: + void paintCell( QPainter * painter, const QColorGroup & cg, int column, int width, int align ); + + private: + enum { PixInset, PixLeft, PixRight }; + QPixmap* pixmapVolume( int ); + QPixmap generateGradient( int ); + + bool m_audio; + bool m_video; + int m_volume; + int m_status; + bool m_read_only; + +}; + +#endif diff --git a/amarok/src/engine/nmm/Makefile.am b/amarok/src/engine/nmm/Makefile.am new file mode 100644 index 00000000..a0748ed8 --- /dev/null +++ b/amarok/src/engine/nmm/Makefile.am @@ -0,0 +1,47 @@ +imagesdir = $(kde_datadir)/amarok/images +images_DATA = \ + nmm-gradient-left.png \ + nmm-gradient-right.png \ + nmm-volume-inset.png + +kde_module_LTLIBRARIES = \ + libamarok_nmmengine_plugin.la + +INCLUDES = \ + -I$(top_srcdir)/amarok/src \ + $(CFLAGS_NMM) \ + $(all_includes) + +libamarok_nmmengine_plugin_la_LIBADD = \ + $(top_builddir)/amarok/src/libamarok.la \ + $(top_builddir)/amarok/src/plugin/libplugin.la \ + $(LIB_KDECORE) $(LIB_KFILE) \ + -lnmmbase -lnmmgraphmgr -lnmmiprogress -lnmmiaudiodevice -lnmmigeneral + +libamarok_nmmengine_plugin_la_SOURCES = \ + nmm_engine.cpp \ + nmm_configdialog.cpp \ + nmm_configdialogbase.ui \ + NmmLocation.cpp \ + HostList.cpp \ + HostListItem.cpp \ + ServerregistryPing.cpp \ + nmm_kdeconfig.kcfgc + +libamarok_nmmengine_plugin_la_LDFLAGS = \ + -module \ + $(KDE_PLUGIN) \ + $(LDFLAGS_NMM) \ + $(all_libraries) + +METASOURCES = \ + AUTO + +KDE_CXXFLAGS = $(USE_EXCEPTIONS) + +kde_services_DATA = \ + amarok_nmmengine_plugin.desktop + +kde_kcfg_DATA = nmm_kdeconfig.kcfg + +SUBDIRS = icons diff --git a/amarok/src/engine/nmm/NmmLocation.cpp b/amarok/src/engine/nmm/NmmLocation.cpp new file mode 100644 index 00000000..6ccd3123 --- /dev/null +++ b/amarok/src/engine/nmm/NmmLocation.cpp @@ -0,0 +1,59 @@ +/* NMM - Network-Integrated Multimedia Middleware + * + * Copyright (C) 2006 + * NMM work group, + * Computer Graphics Lab, + * Saarland University, Germany + * http://www.networkmultimedia.org + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Steet, Fifth Floor, Boston, MA 02110-1301 + * USA + */ + +#include "NmmLocation.h" + +NmmLocation::NmmLocation() +{} + +NmmLocation::NmmLocation( QString hostname, bool audio, bool video, int volume, int status ) + : m_hostname(hostname), + m_audio(audio), + m_video(video), + m_volume(volume), + m_status( status) +{ +} + +NmmLocation::NmmLocation( const NmmLocation &old ) +{ + m_hostname = old.hostname(); + m_audio = old.audio(); + m_video = old.video(); + m_volume = old.volume(); + m_status = old.status(); +} + +NmmLocation::~NmmLocation() +{} + +QString NmmLocation::hostname() const +{ + return m_hostname; +} + +void NmmLocation::setHostname(QString hostname) +{ + m_hostname = hostname; +} diff --git a/amarok/src/engine/nmm/NmmLocation.h b/amarok/src/engine/nmm/NmmLocation.h new file mode 100644 index 00000000..3782ba1b --- /dev/null +++ b/amarok/src/engine/nmm/NmmLocation.h @@ -0,0 +1,60 @@ +/* NMM - Network-Integrated Multimedia Middleware + * + * Copyright (C) 2006 + * NMM work group, + * Computer Graphics Lab, + * Saarland University, Germany + * http://www.networkmultimedia.org + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Steet, Fifth Floor, Boston, MA 02110-1301 + * USA + */ + +#ifndef NMMLOCATION_H +#define NMMLOCATION_H + +#include <qstring.h> + +class NmmLocation { + public: + NmmLocation(); + NmmLocation(QString hostname, bool audio, bool video, int volume, int status ); + NmmLocation(const NmmLocation&); + ~NmmLocation(); + + QString hostname() const; + void setHostname(QString); + + bool audio() const { return m_audio; } + void setAudio( bool audio ) { m_audio = audio; } + + bool video() const { return m_video; } + void setVideo( bool video ) { m_video = video; } + + int volume() const { return m_volume; } + void setVolume( int v ) { m_volume = v; } + + void setStatus( int s ) { m_status = s; } + int status() const { return m_status; } + + private: + QString m_hostname; + bool m_audio; + bool m_video; + int m_volume; + int m_status; +}; + +#endif diff --git a/amarok/src/engine/nmm/ServerregistryPing.cpp b/amarok/src/engine/nmm/ServerregistryPing.cpp new file mode 100644 index 00000000..7a6d2684 --- /dev/null +++ b/amarok/src/engine/nmm/ServerregistryPing.cpp @@ -0,0 +1,60 @@ +/* NMM - Network-Integrated Multimedia Middleware + * + * Copyright (C) 2002-2006 + * NMM work group, + * Computer Graphics Lab, + * Saarland University, Germany + * http://www.networkmultimedia.org + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Steet, Fifth Floor, Boston, MA 02110-1301 + * USA + */ + +#include "ServerregistryPing.h" + +#include "debug.h" + +ServerregistryPing::ServerregistryPing(const QString &host, Q_UINT16 port) + : QSocket() +{ + connect( this, SIGNAL(connected()), + SLOT(socketConnected()) ); + connect( this, SIGNAL(connectionClosed()), + SLOT(socketConnectionClosed()) ); + connect( this, SIGNAL(error(int)), + SLOT(socketError(int)) ); + + connectToHost(host, port); +} + +void ServerregistryPing::socketConnected() +{ + DEBUG_FUNC_INFO + emit registryAvailable( true ); +} + +void ServerregistryPing::socketConnectionClosed() +{ + DEBUG_FUNC_INFO + emit registryAvailable( false ); +} + +void ServerregistryPing::socketError( int ) +{ + DEBUG_FUNC_INFO + emit registryAvailable( false ); +} + +#include "ServerregistryPing.moc" diff --git a/amarok/src/engine/nmm/ServerregistryPing.h b/amarok/src/engine/nmm/ServerregistryPing.h new file mode 100644 index 00000000..06313d48 --- /dev/null +++ b/amarok/src/engine/nmm/ServerregistryPing.h @@ -0,0 +1,55 @@ +/* NMM - Network-Integrated Multimedia Middleware + * + * Copyright (C) 2002-2006 + * NMM work group, + * Computer Graphics Lab, + * Saarland University, Germany + * http://www.networkmultimedia.org + * + * Maintainer: Robert Gogolok <gogo@graphics.cs.uni-sb.de> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Steet, Fifth Floor, Boston, MA 02110-1301 + * USA + */ + +#ifndef SERVERREGISTRYPING_H +#define SERVERREGISTRYPING_H + +#include <qsocket.h> + +/** + * Connects to a remote host on the default NMM serverregistry port. + */ +class ServerregistryPing + : public QSocket +{ + Q_OBJECT + + public: + ServerregistryPing(const QString & host, Q_UINT16 port = 22801); + + private slots: + void socketConnected(); + void socketConnectionClosed(); + void socketError(int); + + signals: + /** + * This signal is emitted when the serverregistry gets available/unavailable. + */ + void registryAvailable(bool); +}; + +#endif diff --git a/amarok/src/engine/nmm/amarok_nmmengine_plugin.desktop b/amarok/src/engine/nmm/amarok_nmmengine_plugin.desktop new file mode 100644 index 00000000..c088d3a0 --- /dev/null +++ b/amarok/src/engine/nmm/amarok_nmmengine_plugin.desktop @@ -0,0 +1,122 @@ +[Desktop Entry] +Encoding=UTF-8 +Type=Service +Name=NMM Engine +Name[af]=NMM Enjin +Name[ar]=محرك NMM +Name[bg]=NMM +Name[bn]=এনএমএম ইঞ্জিন +Name[br]=Keflusker NMM +Name[ca]=Motor NMM +Name[cs]=NMM +Name[da]=NMM-motor +Name[de]=NMM +Name[el]=Μηχανή NMM +Name[eo]=NMM Ilo +Name[es]=Motor NMM +Name[et]=NMM mootor +Name[eu]=NMM motorea +Name[fa]=موتور NMM +Name[fi]=NMM +Name[fr]=Moteur NMM +Name[ga]=Inneall NMM +Name[gl]=Motor NMM +Name[he]=מנוע שמע NMM +Name[hi]=एनएमएम इंजिन +Name[hu]=NMM alrendszer +Name[is]=NMM vél +Name[it]=Motore NMM +Name[ja]=NMM エンジン +Name[ka]=NMM dრავა +Name[km]=ម៉ាស៊ីន NMM +Name[lt]=NMM variklis +Name[mk]=NMM-машина +Name[ms]=Enjin NMM +Name[nb]=NMM-motor +Name[nds]=NMM +Name[ne]=NMM इन्जिन +Name[nn]=NMM-motor +Name[pa]=NMM ਇੰਜਣ +Name[pl]=Wyjście NMM +Name[pt]=Motor NMM +Name[pt_BR]=Mecanismo NMM +Name[ru]=NMM +Name[se]=NMM-mohtor +Name[sl]=Pogon NMM +Name[sq]=Motor NMM +Name[sr]=Мотор NMM +Name[sr@Latn]=Motor NMM +Name[ss]=Motor NMM +Name[sv]=NMM-gränssnitt +Name[ta]=NMM பொறி +Name[tg]=Муҳаррики NMM +Name[tr]=NMM Motoru +Name[uk]=Рушій NMM +Name[uz]=NMM tizimi +Name[uz@cyrillic]=NMM тизими +Name[wa]=Éndjin NMM +Name[zh_CN]=NMM 引擎 +Name[zh_TW]=NMM 解碼引擎 +X-KDE-Library=libamarok_nmmengine_plugin +Comment=Amarok plugin +Comment[af]=Amarok inprop module +Comment[ar]=قابس ( برنامج مضاف الى ) AmaroK +Comment[bg]=Приставка за Amarok +Comment[bn]=আমারক প্লাগিন +Comment[br]=Lugent Amarok +Comment[ca]=Connector de l'Amarok +Comment[cs]=Amarok modul +Comment[da]=Plugin for Amarok +Comment[de]=Amarok-Modul +Comment[el]=Πρόσθετο AmaroK +Comment[eo]=Amarok kromaĵo +Comment[es]=Extensión de Amarok +Comment[et]=Amaroki plugin +Comment[fa]=وصلۀ amaroK +Comment[fi]=Amarok-liitännäinen +Comment[fr]=Module Amarok +Comment[ga]=Breiseán AmaroK +Comment[gl]=Extensión para Amarok +Comment[hu]=Amarok-bővítőmodul +Comment[is]=Amarok íforrit +Comment[it]=Plugin di Amarok +Comment[ja]=Amarok プラグイン +Comment[ka]=Amarok მოდული +Comment[km]=កម្មវិធី​ជំនួយ Amarok +Comment[lt]=Amarok priedas +Comment[mk]=Приклучок за Амарок +Comment[ms]=Plugin Amarok +Comment[nb]=Programtillegg for Amarok +Comment[nds]=Amarok-Moduul +Comment[ne]=अमारोक प्लगइन +Comment[nl]=Amarok-plugin +Comment[nn]=Amarok-tillegg +Comment[pa]=ਅਮਰੋਕ ਪਲੱਗਇਨ +Comment[pl]=Wtyczka Amaroka +Comment[pt]='Plugin' para o Amarok +Comment[pt_BR]=Plugin do Amarok +Comment[ru]=Модуль amaroK +Comment[se]=Amarok-lassemoduvla +Comment[sk]=Amarok modul +Comment[sr]=Прикључак за Amarok +Comment[sr@Latn]=Priključak za Amarok +Comment[sv]=Insticksprogram för Amarok +Comment[th]=โปรแกรมเสริมของ Amarok +Comment[tr]=Amarok eklentisi +Comment[uk]=Втулок Amarok +Comment[uz]=Amarok plagini +Comment[uz@cyrillic]=Amarok плагини +Comment[wa]=Tchôke-divins Amarok +Comment[zh_CN]=Amarok 插件 +Comment[zh_TW]=AmaroK 插件 +ServiceTypes=Amarok/Plugin + +X-KDE-Amarok-plugintype=engine +X-KDE-Amarok-name=nmm-engine +X-KDE-Amarok-authors=NMM work group http://www.networkmultimedia.org +X-KDE-Amarok-email=nmm-dev@graphics.cs.uni-sb.de +X-KDE-Amarok-rank=50 +X-KDE-Amarok-version=1 +X-KDE-Amarok-framework-version=32 + + diff --git a/amarok/src/engine/nmm/icons/Makefile.am b/amarok/src/engine/nmm/icons/Makefile.am new file mode 100644 index 00000000..0969cf7c --- /dev/null +++ b/amarok/src/engine/nmm/icons/Makefile.am @@ -0,0 +1,2 @@ +nmmicondir = $(kde_datadir)/amarok/icons +nmmicon_ICON = AUTO diff --git a/amarok/src/engine/nmm/icons/hi16-action-nmm_option_off.png b/amarok/src/engine/nmm/icons/hi16-action-nmm_option_off.png new file mode 100644 index 00000000..678edcb2 Binary files /dev/null and b/amarok/src/engine/nmm/icons/hi16-action-nmm_option_off.png differ diff --git a/amarok/src/engine/nmm/icons/hi16-action-nmm_option_on.png b/amarok/src/engine/nmm/icons/hi16-action-nmm_option_on.png new file mode 100644 index 00000000..799b88d8 Binary files /dev/null and b/amarok/src/engine/nmm/icons/hi16-action-nmm_option_on.png differ diff --git a/amarok/src/engine/nmm/icons/hi16-action-nmm_option_on_readonly.png b/amarok/src/engine/nmm/icons/hi16-action-nmm_option_on_readonly.png new file mode 100644 index 00000000..c5715bf8 Binary files /dev/null and b/amarok/src/engine/nmm/icons/hi16-action-nmm_option_on_readonly.png differ diff --git a/amarok/src/engine/nmm/nmm-gradient-left.png b/amarok/src/engine/nmm/nmm-gradient-left.png new file mode 100644 index 00000000..08c98701 Binary files /dev/null and b/amarok/src/engine/nmm/nmm-gradient-left.png differ diff --git a/amarok/src/engine/nmm/nmm-gradient-right.png b/amarok/src/engine/nmm/nmm-gradient-right.png new file mode 100644 index 00000000..043852d7 Binary files /dev/null and b/amarok/src/engine/nmm/nmm-gradient-right.png differ diff --git a/amarok/src/engine/nmm/nmm-volume-inset.png b/amarok/src/engine/nmm/nmm-volume-inset.png new file mode 100644 index 00000000..324018b7 Binary files /dev/null and b/amarok/src/engine/nmm/nmm-volume-inset.png differ diff --git a/amarok/src/engine/nmm/nmm_configdialog.cpp b/amarok/src/engine/nmm/nmm_configdialog.cpp new file mode 100644 index 00000000..6c13b203 --- /dev/null +++ b/amarok/src/engine/nmm/nmm_configdialog.cpp @@ -0,0 +1,258 @@ +/* NMM - Network-Integrated Multimedia Middleware + * + * Copyright (C) 2005-2006 + * NMM work group, + * Computer Graphics Lab, + * Saarland University, Germany + * http://www.networkmultimedia.org + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Steet, Fifth Floor, Boston, MA 02110-1301 + * USA + */ + +#include "nmm_configdialog.h" +#include "nmm_engine.h" +#include "nmm_kdeconfig.h" +#include "HostList.h" +#include "HostListItem.h" +#include "debug.h" + +#include <qbuttongroup.h> +#include <qinputdialog.h> +#include <qlayout.h> +#include <qlistbox.h> +#include <qpushbutton.h> +#include <qradiobutton.h> +#include <qwidgetstack.h> +#include <kcombobox.h> + +NmmConfigDialog::NmmConfigDialog() + : PluginConfig(), + current_audio_group_selection(-1), + m_environment_list(NULL), + m_user_list(NULL), + m_host_list_modified(false) +{ + kdDebug() << k_funcinfo << endl; + + + m_view = new NmmConfigDialogBase(); + + /* create host list widget stack */ + m_environment_list = new HostList( m_view->audioGroup, "TheEnvironmentList" ); + m_environment_list->setReadOnly( true ); + m_environment_list->setSelectionMode( QListView::NoSelection ); + + m_user_list = new HostList( m_view->audioGroup, "TheUserList" ); + m_user_list->setSelectionMode( QListView::Single ); + connect( m_user_list, SIGNAL( viewChanged() ), this, SLOT( hostListModified() ) ); + + m_view->hostListStack->addWidget( m_environment_list ); + m_view->hostListStack->addWidget( m_user_list ); + // show disabled user list by default if 'localhost only' selected + m_view->hostListStack->raiseWidget( m_user_list ); + + /* restore saved output plugin */ + if ( NmmKDEConfig::audioOutputPlugin() == "ALSAPlaybackNode" ) + m_view->audioPlaybackNode->setCurrentItem( 1 ); + + /* restore selected audioGroup selection */ + m_view->audioGroup->setButton( NmmKDEConfig::location() ); + clickedAudioGroup( NmmKDEConfig::location() ); + createHostLists(); + + /* connect 'Add...' and 'Remove' buttons */ + connect( m_view->addLocationButton, SIGNAL( released() ), SLOT( addHost() ) ); + connect( m_view->removeHostButton, SIGNAL( released() ), SLOT( removeHost() ) ); + connect( m_user_list, SIGNAL( selectionChanged() ) , this, SLOT( enableRemoveButton() ) ); + + /* connect audioGroup selection */ + connect( m_view->audioGroup, SIGNAL( released(int) ), SLOT( clickedAudioGroup(int) ) ); + + PluginConfig::connect( m_view->audioPlaybackNode, SIGNAL( activated( int ) ), SIGNAL( viewChanged() ) ); + PluginConfig::connect( m_view->audioGroup, SIGNAL( released( int ) ), SIGNAL( viewChanged() ) ); + +// connect( this, SIGNAL( viewChanged() ) ) +} + +NmmConfigDialog::~NmmConfigDialog() +{ + kdDebug() << k_funcinfo << endl; + delete m_view; +} + + +bool NmmConfigDialog::hasChanged() const +{ + return NmmKDEConfig::audioOutputPlugin() != m_view->audioPlaybackNode->currentText() || + NmmKDEConfig::location() != m_view->audioGroup->selectedId() || + m_host_list_modified; +} + +bool NmmConfigDialog::isDefault() const +{ + return false; +} + +void NmmConfigDialog::save() +{ + DEBUG_BLOCK + + if( hasChanged() ) + { + NmmKDEConfig::setAudioOutputPlugin( m_view->audioPlaybackNode->currentText() ); + debug() << "saved audio output plugin" << endl; + NmmKDEConfig::setLocation( m_view->audioGroup->selectedId() ); + debug() << "saved current location selection" << endl; + + /* store volume for AUDIO_HOSTS */ + //NmmEngine::instance()->setEnvironmentHostList( tmp_environment_list ); + + /* save user host list and toggle states for audio, video */ + QValueList<NmmLocation> tmp_user_list; + + QListViewItemIterator it( m_user_list ); + HostListItem *host; + while( it.current() ) { + host = static_cast<HostListItem*>( it.current() ); + tmp_user_list.append( NmmLocation(host->text(HostListItem::Hostname), host->isAudioEnabled(), host->isVideoEnabled(), /* TODO: host->volume()*/ 0, host->status() ) ); + ++it; + } + + NmmEngine::instance()->setUserHostList( tmp_user_list ); + + QStringList hosts; + QStringList audio_hosts; + QStringList video_hosts; + QValueList<NmmLocation>::iterator it_n; + for( it_n = tmp_user_list.begin(); it_n != tmp_user_list.end(); ++it_n ) + { + debug() << "saved user host" << endl; + hosts.append( (*it_n).hostname() ); + if( (*it_n).audio() ) + audio_hosts.append( "1" ); + else + audio_hosts.append( "0" ); + if( (*it_n).video() ) + video_hosts.append( "1" ); + else + video_hosts.append( "0" ); + // TODO: save volume + } + NmmKDEConfig::setHostList( hosts ); + NmmKDEConfig::setAudioToggle( audio_hosts ); + NmmKDEConfig::setVideoToggle( video_hosts ); + debug() << "saved user host list with toggle states for audio and video" << endl; + + NmmKDEConfig::writeConfig(); + + m_host_list_modified = false; + } +} + +void NmmConfigDialog::addHost() +{ + bool ok; + QString hostname = QInputDialog::getText( + "New NMM sink host", "Enter hostname to add:", QLineEdit::Normal, + QString::null, &ok, NULL); + if( ok && !hostname.isEmpty() ) + { + new HostListItem( m_user_list, hostname ); + if( m_user_list->currentItem() ) + m_view->removeHostButton->setEnabled( true ); + hostListModified(); + } +} + +void NmmConfigDialog::removeHost() +{ + m_user_list->takeItem( m_user_list->currentItem() ); + if( !m_user_list->currentItem() ) + m_view->removeHostButton->setEnabled( false ); + hostListModified(); +} + +void NmmConfigDialog::clickedAudioGroup( int new_selection ) +{ + if( current_audio_group_selection == new_selection || new_selection > 2 ) + return; + + /* localhost only, disable host list */ + if( new_selection == 0 ) { + m_view->hostListStack->setDisabled(true); + m_view->addLocationButton->setEnabled( false ); + m_view->removeHostButton->setEnabled( false ); + } + /* environment host list + * disable 'Add...' and 'Remove' buttons, load environment host list + */ + else if( new_selection == 1 ) { + m_view->hostListStack->setEnabled( true ); + m_view->addLocationButton->setEnabled( false ); + m_view->removeHostButton->setEnabled( false ); + m_view->hostListStack->raiseWidget( m_environment_list ); + } + /* user host list + * enable all widgets for host list, load user host list + */ + else if( new_selection == 2 ) { + m_view->hostListStack->setEnabled( true ); + m_view->addLocationButton->setEnabled( true ); + if( !m_user_list->currentItem() ) + m_view->removeHostButton->setEnabled( false ); + else + m_view->removeHostButton->setEnabled( true ); + m_view->hostListStack->raiseWidget( m_user_list ); + } + + current_audio_group_selection = new_selection; +} + +void NmmConfigDialog::notifyHostError( QString hostname, int error ) +{ + m_user_list->notifyHostError( hostname, error ); + m_environment_list->notifyHostError( hostname, error ); +} + +void NmmConfigDialog::enableRemoveButton() +{ + + m_view->removeHostButton->setEnabled( true ); +} + +void NmmConfigDialog::hostListModified() +{ + m_host_list_modified = true; + emit viewChanged(); +} + +void NmmConfigDialog::createHostLists() +{ + DEBUG_BLOCK + + QValueList<NmmLocation>::iterator it; + QValueList<NmmLocation> list; + + list = NmmEngine::instance()->environmentHostList(); + for( it = list.begin(); it != list.end(); ++it ) + new HostListItem( m_environment_list, (*it).hostname(), (*it).audio(), (*it).video(), 0, (*it).status(), true ); + + list = NmmEngine::instance()->userHostList(); + for( it = list.begin(); it != list.end(); ++it ) + new HostListItem( m_user_list, (*it).hostname(), (*it).audio(), (*it).video(), 0, (*it).status() ); +} + +#include "nmm_configdialog.moc" diff --git a/amarok/src/engine/nmm/nmm_configdialog.h b/amarok/src/engine/nmm/nmm_configdialog.h new file mode 100644 index 00000000..97ffd1a1 --- /dev/null +++ b/amarok/src/engine/nmm/nmm_configdialog.h @@ -0,0 +1,125 @@ +/* NMM - Network-Integrated Multimedia Middleware + * + * Copyright (C) 2005-2006 + * NMM work group, + * Computer Graphics Lab, + * Saarland University, Germany + * http://www.networkmultimedia.org + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Steet, Fifth Floor, Boston, MA 02110-1301 + * USA + */ + + +#ifndef NMMCONFIGDIALOG_H +#define NMMCONFIGDIALOG_H + +#include "nmm_configdialogbase.h" +#include "plugin/pluginconfig.h" + +#include "qobject.h" + +class HostList; +class HostListItem; +class NmmLocation; + +class NmmConfigDialog : public Amarok::PluginConfig +{ + Q_OBJECT + + public: + NmmConfigDialog(); + ~NmmConfigDialog(); + + QWidget* view() { return m_view; } + + // \todo doesn't work the intended way + bool hasChanged() const; + + bool isDefault() const; + + public slots: + + void save(); + + /** + * Adds a host to the user list. + */ + void addHost(); + + /** + * Removes current selected host entry from user list. + */ + void removeHost(); + + /** + * Called when a radio button in audioGroup was clicked. + */ + void clickedAudioGroup( int ); + + /** + * Updates status column for m_user_list and m_environment_list + * to reflect that an error occurred for a host. + * \param hostname host the error is related to + * \param error error identification, see NMMEngineException::Error + */ + void notifyHostError( QString hostname, int error ); + + private slots: + /** + * Enables 'Remove ' host button if a HostListItem is selected. + */ + void enableRemoveButton(); + + /** + * Called when user host list gets modified. + * So either a host entry has been deleted/added + * or the audio/video toggle has changed. + */ + void hostListModified(); + + private: + /** + * Fills user and environment host on config dialog init. + */ + void createHostLists(); + + /** + * Designer ui configuration dialog. + */ + NmmConfigDialogBase* m_view; + + /** + * Current audio group selection. + */ + int current_audio_group_selection; + + /** + * Host list showing read-only environment list. + */ + HostList *m_environment_list; + + /** + * Host list create by the user. + */ + HostList *m_user_list; + + /** + * True if user host list was modified. + */ + bool m_host_list_modified; +}; + +#endif diff --git a/amarok/src/engine/nmm/nmm_configdialogbase.ui b/amarok/src/engine/nmm/nmm_configdialogbase.ui new file mode 100644 index 00000000..99440241 --- /dev/null +++ b/amarok/src/engine/nmm/nmm_configdialogbase.ui @@ -0,0 +1,227 @@ +<!DOCTYPE UI><UI version="3.3" stdsetdef="1"> +<class>NmmConfigDialogBase</class> +<widget class="QWidget"> + <property name="name"> + <cstring>NmmConfigDialogBase</cstring> + </property> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>581</width> + <height>362</height> + </rect> + </property> + <property name="caption"> + <string>NMM Engine Configuration - Amarok</string> + </property> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>layout6</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLabel"> + <property name="name"> + <cstring>textLabel2</cstring> + </property> + <property name="text"> + <string>Audio plugin:</string> + </property> + <property name="buddy" stdset="0"> + <cstring>deviceComboBox</cstring> + </property> + </widget> + <widget class="QComboBox"> + <item> + <property name="text"> + <string>Playback node</string> + </property> + </item> + <item> + <property name="text"> + <string>ALSA playback node</string> + </property> + </item> + <property name="name"> + <cstring>audioPlaybackNode</cstring> + </property> + <property name="whatsThis" stdset="0"> + <string>Selects audio output plugin. PlaybackNode uses the Open Sound System (<b>OSS</b>). ALSAPlaybackNode uses the Advanced Linux Sound Architecture (<b>ALSA</b>).</string> + </property> + </widget> + </hbox> + </widget> + <spacer> + <property name="name"> + <cstring>spacer2</cstring> + </property> + <property name="orientation"> + <enum>Vertical</enum> + </property> + <property name="sizeType"> + <enum>Fixed</enum> + </property> + <property name="sizeHint"> + <size> + <width>16</width> + <height>16</height> + </size> + </property> + </spacer> + <widget class="QButtonGroup"> + <property name="name"> + <cstring>audioGroup</cstring> + </property> + <property name="title"> + <string>Video,Audio Location</string> + </property> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QRadioButton"> + <property name="name"> + <cstring>localhostButton</cstring> + </property> + <property name="text"> + <string>Localhost only</string> + </property> + <property name="whatsThis" stdset="0"> + <string>Plays audio and video on the machine running Amarok.</string> + </property> + </widget> + <widget class="QRadioButton"> + <property name="name"> + <cstring>environmentButton</cstring> + </property> + <property name="text"> + <string>Environment variables</string> + </property> + <property name="toolTip" stdset="0"> + <string>Environment variables are AUDIO_HOSTS and VIDEO_HOSTS.</string> + </property> + <property name="whatsThis" stdset="0"> + <string>Reads the environment variables <b>AUDIO_HOSTS</b> and <b>VIDEO_HOSTS</b> to determine the audio and video playback locations. The playback locations will be shown in the host list below. The list is <b>read-only</b>. + +<h3>Example</h3> +AUDIO_HOSTS=desktop:laptop:kitchen<br> +VIDEO_HOSTS=laptop<br> +<br> +This setting will enable audio on the three hosts desktop, laptop and kitchen, and video only on host laptop.</string> + </property> + </widget> + <widget class="QRadioButton"> + <property name="name"> + <cstring>hostListButton</cstring> + </property> + <property name="text"> + <string>Host list</string> + </property> + <property name="whatsThis" stdset="0"> + <string>If selected you can add and remove hosts in the list below and enable audio and video for each host.</string> + </property> + </widget> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>layout5</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QWidgetStack"> + <property name="name"> + <cstring>hostListStack</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>5</hsizetype> + <vsizetype>5</vsizetype> + <horstretch>1</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <widget class="QWidget"> + <property name="name"> + <cstring>WStackPage</cstring> + </property> + <attribute name="id"> + <number>0</number> + </attribute> + </widget> + </widget> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>layout13</cstring> + </property> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QPushButton"> + <property name="name"> + <cstring>addLocationButton</cstring> + </property> + <property name="text"> + <string>Add...</string> + </property> + </widget> + <widget class="QPushButton"> + <property name="name"> + <cstring>removeHostButton</cstring> + </property> + <property name="text"> + <string>R&emove</string> + </property> + </widget> + <spacer> + <property name="name"> + <cstring>spacer1</cstring> + </property> + <property name="orientation"> + <enum>Vertical</enum> + </property> + <property name="sizeType"> + <enum>Expanding</enum> + </property> + <property name="sizeHint"> + <size> + <width>20</width> + <height>1</height> + </size> + </property> + </spacer> + </vbox> + </widget> + </hbox> + </widget> + </vbox> + </widget> + <spacer> + <property name="name"> + <cstring>spacer3</cstring> + </property> + <property name="orientation"> + <enum>Vertical</enum> + </property> + <property name="sizeType"> + <enum>Minimum</enum> + </property> + <property name="sizeHint"> + <size> + <width>20</width> + <height>16</height> + </size> + </property> + </spacer> + </vbox> +</widget> +<layoutdefaults spacing="6" margin="11"/> +</UI> diff --git a/amarok/src/engine/nmm/nmm_engine.cpp b/amarok/src/engine/nmm/nmm_engine.cpp new file mode 100644 index 00000000..b2ef176d --- /dev/null +++ b/amarok/src/engine/nmm/nmm_engine.cpp @@ -0,0 +1,712 @@ +/* NMM - Network-Integrated Multimedia Middleware + * + * Copyright (C) 2002-2006 + * NMM work group, + * Computer Graphics Lab, + * Saarland University, Germany + * http://www.networkmultimedia.org + * + * Maintainer: Robert Gogolok <gogo@graphics.cs.uni-sb.de> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Steet, Fifth Floor, Boston, MA 02110-1301 + * USA + */ + +#include "nmm_engine.h" + +#include "nmm_kdeconfig.h" +#include "nmm_configdialog.h" +#include "HostListItem.h" +#include "debug.h" +#include "plugin/plugin.h" + +#include <nmm/base/graph/GraphBuilder2.hpp> +#include <nmm/base/registry/NodeDescription.hpp> +#include <nmm/base/ProxyApplication.hpp> +#include <nmm/interfaces/base/sync/ISynchronizedSink.hpp> +#include <nmm/interfaces/file/ISeekable.hpp> +#include <nmm/interfaces/file/ITrack.hpp> +#include <nmm/interfaces/file/IBufferSize.hpp> +#include <nmm/interfaces/general/progress/IProgressListener.hpp> +#include <nmm/interfaces/general/progress/IProgress.hpp> +#include <nmm/interfaces/general/ITrackDuration.hpp> +#include <nmm/interfaces/device/audio/IAudioDevice.hpp> +#include <nmm/base/ProxyObject.hpp> +#include <nmm/utils/NMMConfig.hpp> + +#include <qapplication.h> +#include <qtimer.h> + +#include <kfileitem.h> +#include <klocale.h> +#include <kmessagebox.h> +#include <kmimetype.h> +#include <iostream> +#include <kurl.h> + +NmmEngine* NmmEngine::s_instance; + +AMAROK_EXPORT_PLUGIN( NmmEngine ) + +NmmEngine::NmmEngine() + : Engine::Base(), + __position(0), + __track_length(0), + __state(Engine::Empty), + __app(NULL), + __endTrack_listener(this, &NmmEngine::endTrack), + __syncReset_listener(this, &NmmEngine::syncReset), + __setProgress_listener(this, &NmmEngine::setProgress), + __trackDuration_listener(this, &NmmEngine::trackDuration), + __composite(NULL), + __playback(NULL), + __display(NULL), + __av_sync(NULL), + __synchronizer(NULL), + __with_video(false), + __seeking(false), + m_localhostonly_errordialog(false) +{ + addPluginProperty( "HasConfigure", "true" ); +} + +bool NmmEngine::init() +{ + DEBUG_BLOCK + + s_instance = this; + + // disable debug and warning streams + NamedObject::getGlobalInstance().setDebugStream(NULL, NamedObject::ALL_LEVELS); + NamedObject::getGlobalInstance().setWarningStream(NULL, NamedObject::ALL_LEVELS); + + // create new NMM application object + __app = ProxyApplication::getApplication(0, 0); + + createEnvironmentHostList(); + createUserHostList(); + + connect( this, SIGNAL( hostError( QString, int ) ), SLOT( notifyHostError( QString, int ) ) ); + + return true; +} + +NmmEngine::~NmmEngine() +{ + // stop all nodes + stop(); + + // delete application object + if (__app) + delete __app; +} + +void NmmEngine::checkSecurity() +{ + const char* home(getenv("HOME")); + NMMConfig nmmconfig( string(home) + string("/.nmmrc") ); + + bool readpaths_set = false; + bool writepaths_set = false; + string optionvalue(""); + + nmmconfig.getValue("allowedreadpaths", optionvalue); + if( !optionvalue.empty() ) + readpaths_set = true; + + optionvalue = ""; + nmmconfig.getValue("allowedwritepaths", optionvalue); + if( !optionvalue.empty() ) + writepaths_set = true; + + QString str; + str += "<html><body>"; + str += "Your current NMM setup is insecure.<br/><br/>"; + str += "The file <b>.nmmrc</b> in your home directory restricts read and write access for NMM to certain paths.<br/><br/>"; + + if( !readpaths_set ) + str += "<b>allowedreadpaths option is not set</b>. NMM plugins are therefore allowed to read every file the process running NMM is allowed.<br/>"; + + if( !writepaths_set ) + str += "<b>allowedwritepaths option is not set</b>. NMM plugins are therefore allowed to write every file or directory the process running NMM is allowed.<br/>"; + + str += "<br/>See <a href=\"http://www.networkmultimedia.org/Download/\">http://www.networkmultimedia.org/Download/</a> for general security instructions in the correspoding <i>configure and test NMM</i> section depending on your chosen installation method."; + str += "</body></html>"; + + if( !writepaths_set || !readpaths_set ) + KMessageBox::information(0, str, i18n( "Insecure NMM setup" ), "insecureNmmSetup", KMessageBox::AllowLink ); +} + +void NmmEngine::notifyHostError( QString hostname, int error ) +{ + DEBUG_BLOCK + + for( QValueList<NmmLocation>::Iterator it = tmp_user_list.begin(); it != tmp_user_list.end(); ++it ) { + if( (*it).hostname() == hostname ) { + (*it).setStatus( error ); + break; + } + } + + for( QValueList<NmmLocation>::Iterator it = tmp_environment_list.begin(); it != tmp_environment_list.end(); ++it ) { + if( (*it).hostname() == hostname ) { + (*it).setStatus( error ); + break; + } + } + +} + +Engine::State NmmEngine::state() const +{ + return __state; +} + +Amarok::PluginConfig* NmmEngine::configure() const +{ + NmmConfigDialog* dialog = new NmmConfigDialog(); + connect( this, SIGNAL( hostError( QString, int ) ), dialog, SLOT( notifyHostError(QString, int ) ) ); + return dialog; +} + +bool NmmEngine::load(const KURL& url, bool stream) +{ + DEBUG_BLOCK + + static int error; + error = STATUS_UNKNOWN; + + // check security options + static bool already_checked = false; + if( !already_checked) { + QTimer::singleShot(100, this, SLOT( checkSecurity() ) ); + already_checked = true; + } + + // Don't play a track if 'localhost only' error dialog is being shown + if( m_localhostonly_errordialog ) + return false; + + // play only local files + if( !url.isLocalFile() ) { + debug() << "Currently NMM engine can only play local files!" << endl; + return false; + } + + Engine::Base::load(url, stream); + + cleanup(); + + // make the GraphBuilder construct an appropriate graph for the given URL + try { + QStringList hosts; + + // node for audio playback + NodeDescription playback_nd("PlaybackNode"); + // ALSA or OSS + if( NmmKDEConfig::audioOutputPlugin() == "ALSAPlaybackNode" ) + playback_nd = NodeDescription("ALSAPlaybackNode"); + + // TODO: currently we only support one host for audio playback + if( !(hosts = getSinkHosts()).empty() ) + playback_nd.setLocation( hosts.first().ascii() ); + + // node for video playback + NodeDescription display_nd("XDisplayNode"); + + // TODO: currently we only support one host for video playback + if( !(hosts = getSinkHosts( false )).empty() ) + display_nd.setLocation( hosts.first().ascii() ); + + GraphBuilder2 gb; + + // convert the URL to a valid NMM url + if(!gb.setURL("file://" + string(url.path().ascii()))) + throw Exception("Invalid URL given"); + + ClientRegistry& registry = __app->getRegistry(); + // requst playback and audio node {{{ + { + //debug() << "##############> ClientRegistry " << endl; + RegistryLock lock(registry); + + // get a playback node interface from the registry + try { + list<Response> playback_response = registry.initRequest(playback_nd); + if (playback_response.empty()) // playback node not available + throw( NMMEngineException( playback_nd.getLocation(), NmmEngine::ERROR_PLAYBACKNODE ) ); + + __playback = registry.requestNode( playback_response.front() ); + } + catch( RegistryException ) { + error = NmmEngine::ERROR_PLAYBACKNODE; + throw( NMMEngineException( playback_nd.getLocation(), NmmEngine::ERROR_PLAYBACKNODE ) ); + } + catch(...) { + error = NmmEngine::ERROR_PLAYBACKNODE; + throw; + } + + // get a display node interface from the registry + try { + list<Response> display_response = registry.initRequest(display_nd); + if (display_response.empty()) // Display Node not available + throw NMMEngineException( display_nd.getLocation(), NmmEngine::ERROR_DISPLAYNODE ); + + __display = registry.requestNode(display_response.front()); + } + catch( RegistryException ) { + error = NmmEngine::ERROR_DISPLAYNODE; + throw NMMEngineException( display_nd.getLocation(), NmmEngine::ERROR_DISPLAYNODE ); + } + catch(...) { + error = NmmEngine::ERROR_DISPLAYNODE; + throw; + } + + //debug() << "##############< ClientRegistry " << endl; + }//}}} + + __av_sync = new MultiAudioVideoSynchronizer(); + __synchronizer = __av_sync->getCheckedInterface<IMultiAudioVideoSynchronizer>(); + + // initialize the GraphBuilder + gb.setMultiAudioVideoSynchronizer(__synchronizer); + gb.setAudioSink(__playback); + gb.setVideoSink(__display); + gb.setDemuxAudioJackTag("audio"); + gb.setDemuxVideoJackTag("video"); + + // create the graph represented by a composite node + __composite = gb.createGraph(*__app); + + // if the display node is connected we know we will play a video TODO: what about video without audio? + __with_video = __display->isInputConnected(); + debug() << "NMM video playback? " << __with_video << endl; + + // set volume for playback node + setVolume( m_volume ); + + // register the needed event listeners at the display node if video enabled + if(__with_video) { + __display->getParentObject()->registerEventListener(ISyncReset::syncReset_event, &__syncReset_listener); + __display->getParentObject()->registerEventListener(IProgressListener::setProgress_event, &__setProgress_listener); + __display->getParentObject()->registerEventListener(ITrack::endTrack_event, &__endTrack_listener); + + } + else { // in other case at the playback node + __playback->getParentObject()->registerEventListener(ISyncReset::syncReset_event, &__syncReset_listener); + __playback->getParentObject()->registerEventListener(IProgressListener::setProgress_event, &__setProgress_listener); + __playback->getParentObject()->registerEventListener(ITrack::endTrack_event, &__endTrack_listener); + + + (__playback->getParentObject()->getCheckedInterface<ISynchronizedSink>())->setSynchronized(false); + } + + __playback->getParentObject()->registerEventListener(ITrackDuration::trackDuration_event, &__trackDuration_listener); + __display->getParentObject()->registerEventListener(ITrackDuration::trackDuration_event, &__trackDuration_listener); + + // Tell the node that implements the IProgress interface to send progress events frequently. + IProgress_var progress(__composite->getInterface<IProgress>()); + if (progress.get()) { + progress->sendProgressInformation(true); + progress->setProgressInterval(1); + } + + // minimize the buffer size to increase the frequency of progress events + IBufferSize_var buffer_size(__composite->getInterface<IBufferSize>()); + if (buffer_size.get()) { + buffer_size->setBufferSize(1000); + } + + // we don't know the track length yet - we have to wait for the trackDuration event + __track_length = 0; + + __seeking = false; + + // finally start the graph + if(__playback->isActivated()) + __playback->reachStarted(); + if(__display->isActivated()) + __display->reachStarted(); + + __composite->reachStarted(); + + return true; + } + catch ( const NMMEngineException e) { + QString host = e.hostname.c_str(); + emit hostError(host, error); + emit statusText( i18n("NMM engine: Stopping playback...") ); + } + catch (const Exception& e) { + cerr << e << endl; + QString status = e.getComment().c_str() ; + emit statusText( QString( i18n("NMM engine: ") ) + status ); + } + catch(...) { + emit statusText( i18n("NMM engine: Something went wrong...") ); + } + + // loading failed, clean up + cleanup(); + + // if 'Localhost only' playback, show user an error message + // and explanation how to test current NMM setup + if( NmmKDEConfig::location() == NmmKDEConfig::EnumLocation::LocalhostOnly ) + { + m_localhostonly_errordialog = true; + QString detailed_status = HostListItem::prettyStatus( error ); + KMessageBox::detailedError( 0, i18n("Local NMM playback failed."), detailed_status, i18n("Error"), KMessageBox::AllowLink ); + m_localhostonly_errordialog = false; + } + + return false; +} + +bool NmmEngine::play(uint) +{ + DEBUG_BLOCK + + if (!__composite) + return false; + + // TODO: seek to the last position if 'resume playback on startup' is enabled + + __synchronizer->wakeup(); + __state = Engine::Playing; + emit stateChanged(Engine::Playing); + + return true; +} + +void NmmEngine::cleanup() +{ + DEBUG_BLOCK + + // remove all event listeners + if(__display && __with_video ) { + __display->getParentObject()->removeEventListener(&__setProgress_listener); + __display->getParentObject()->removeEventListener(&__endTrack_listener); + __display->getParentObject()->removeEventListener(&__syncReset_listener); + + if( __playback ) + __playback->getParentObject()->removeEventListener(&__trackDuration_listener); + __display->getParentObject()->removeEventListener(&__trackDuration_listener); + + debug() << "removed event listener for __display" << endl; + } + else if (__playback ) { + __playback->getParentObject()->removeEventListener(&__setProgress_listener); + __playback->getParentObject()->removeEventListener(&__endTrack_listener); + __playback->getParentObject()->removeEventListener(&__syncReset_listener); + + __playback->getParentObject()->removeEventListener(&__trackDuration_listener); + if( __display ) + __display->getParentObject()->removeEventListener(&__trackDuration_listener); + + debug() << "removed event listener for __playback" << endl; + } + + if( __composite && __composite->isStarted() ) { + __composite->reachActivated(); + debug() << "__composite STARTED -> ACTIVATED" << endl; + } + + if( __playback && __playback->isStarted() ) { + __playback->reachActivated(); + debug() << "__playback STARTED -> ACTIVATED " << endl; + } + + if( __display && __display->isStarted() ) { + __display->reachActivated(); + debug() << "__display STARTED -> ACTIVATED " << endl; + } + + if( __composite && __composite->isActivated() ) { + __composite->flush(); + __composite->reachConstructed(); + debug() << "__composite ACTIVATED -> CONSTRUCTED " << endl; + } + + if( __playback && __playback->isActivated() ) { + __playback->flush(); + __playback->reachConstructed(); + debug() << "__playback ACTIVATED -> CONSTRUCTED " << endl; + } + + if( __display && __display->isActivated() ) { + __display->flush(); + __display->reachConstructed(); + debug() << "__display ACTIVATED -> CONSTRUCTED " << endl; + } + + // release the playback and video node + ClientRegistry& registry = __app->getRegistry(); + { + RegistryLock lock(registry); + if(__playback) { + registry.releaseNode(*__playback); + debug() << "RELEASED __playback node" << endl; + } + if(__display) { + registry.releaseNode(*__display); + debug() << "RELEASED __display node" << endl; + } + } + + delete __composite; + __composite = NULL; + delete __playback; + __playback = NULL; + delete __display; + __display = NULL; + + __with_video = false; + + delete __synchronizer; + __synchronizer = NULL; + delete __av_sync; + __av_sync = NULL; + + __position = 0; + __state = Engine::Idle; +} + +void NmmEngine::createEnvironmentHostList() +{ + QString hosts = getenv("AUDIO_HOSTS"); + QStringList list = QStringList::split(":", hosts ); + + /* merge audio hosts */ + for( QStringList::Iterator it = list.begin(); it != list.end(); ++it ) { + tmp_environment_list.append( NmmLocation( (*it), true, false, 0, NmmEngine::STATUS_UNKNOWN ) ); + } + + /* merge video hosts */ + hosts = getenv("VIDEO_HOSTS"); + list = QStringList::split(":", hosts ); + bool found = false; + for( QStringList::Iterator it = list.begin(); it != list.end(); ++it ) { + + found = false; + for( QValueList<NmmLocation>::Iterator it_t = tmp_environment_list.begin(); it_t != tmp_environment_list.end(); ++it_t ) { + if( (*it_t).hostname() == *it ) { + (*it_t).setVideo(true); + found = true; + break; + } + } + + if( !found ) + tmp_environment_list.append( NmmLocation( (*it), false, true, 0, NmmEngine::STATUS_UNKNOWN ) ); + } + + //debug() << "### ENVIRONMENT" << endl; + //for( QValueList<NmmLocation>::Iterator it = tmp_environment_list.begin(); it != tmp_environment_list.end(); ++it ) { + //debug() << "### hostname " << (*it).hostname() << endl; + //debug() << "### audio " << (*it).audio() << endl; + //debug() << "### video " << (*it).video() << endl; + //debug() << "#########################" << endl; + //} +} + +void NmmEngine::createUserHostList() +{ + QStringList hosts = NmmKDEConfig::hostList(); + QStringList audio_list = NmmKDEConfig::audioToggle(); + QStringList video_list = NmmKDEConfig::videoToggle(); + + bool audio = false; + bool video = false; + + unsigned int size = hosts.size(); + for(unsigned int i = 0; i < size; i++ ) { + if( audio_list[i] == "1") + audio = true; + else + audio = false; + + if( video_list[i] == "1") + video = true; + else + video = false; + + tmp_user_list.append( NmmLocation( hosts[i], audio, video, /* TODO: volume */0, NmmEngine::STATUS_UNKNOWN ) ); + } +} + +void NmmEngine::stop() +{ + DEBUG_BLOCK + + cleanup(); + + __state = Engine::Empty; + emit stateChanged(Engine::Empty); +} + +void NmmEngine::pause() +{ + if (!__composite) + return; + + debug() << "pause()" << endl; + if( __state == Engine::Playing ) { + __synchronizer->pause(); + __state = Engine::Paused; + emit stateChanged(Engine::Paused); + } + else if ( __state == Engine::Paused ) { + __synchronizer->wakeup(); + __state = Engine::Playing; + emit stateChanged(Engine::Playing); + } +} + +void NmmEngine::seek(uint ms) +{ + if (!__track_length) + return; + + __seeking = true; + __position = ms; + + ISeekable_var seek(__composite->getCheckedInterface<ISeekable>()); + if (seek.get()) + seek->seekPercentTo(Rational(ms, __track_length)); +} + +void NmmEngine::endOfStreamReached() +{ + DEBUG_BLOCK + emit trackEnded(); +} + +uint NmmEngine::position() const +{ + return __position; +} + +uint NmmEngine::length() const +{ + return __track_length; +} + +bool NmmEngine::canDecode(const KURL& url) const +{ + static QStringList types; + + if (url.protocol() == "http" ) return false; + + // the following MIME types can be decoded + types += QString("audio/x-mp3"); + types += QString("audio/x-wav"); + types += QString("audio/ac3"); + types += QString("audio/vorbis"); + types += QString("video/mpeg"); + types += QString("video/x-msvideo"); + types += QString("video/x-ogm"); + + KFileItem fileItem( KFileItem::Unknown, KFileItem::Unknown, url, false ); //false = determineMimeType straight away + KMimeType::Ptr mimetype = fileItem.determineMimeType(); + + return types.contains(mimetype->name()); +} + + +void NmmEngine::setVolumeSW(uint percent) +{ + if( __playback ) + { + IAudioDevice_var audio(__playback->getParentObject()->getCheckedInterface<IAudioDevice>()); + audio->setVolume( percent ); + } +} + +QStringList NmmEngine::getSinkHosts( bool audio ) +{ + QStringList hosts; + // TODO: redundant code... + + // read locations from environment variable + if( NmmKDEConfig::location() == NmmKDEConfig::EnumLocation::EnvironmentVariable ) + { + for( QValueList<NmmLocation>::Iterator it = tmp_environment_list.begin(); it != tmp_environment_list.end(); ++it ) { + if( audio && (*it).audio() ) + hosts.append( (*it).hostname() ); + else if( !audio && (*it).video() ) + hosts.append( (*it).hostname() ); + } + //debug() << "locations from environment variable are => " << hosts << endl; + return hosts; + + } + // read locations from host list + else if( NmmKDEConfig::location() == NmmKDEConfig::EnumLocation::HostList ) + { + for( QValueList<NmmLocation>::Iterator it = tmp_user_list.begin(); it != tmp_user_list.end(); ++it ) { + if( audio && (*it).audio() ) + hosts.append( (*it).hostname() ); + else if( !audio && (*it).video() ) + hosts.append( (*it).hostname() ); + } + + return hosts; + } + + // localhost only + return hosts; +} + +Result NmmEngine::setProgress(u_int64_t& numerator, u_int64_t& denominator) +{ + // compute the track position in milliseconds + u_int64_t position = numerator * __track_length / denominator; + + if (__seeking) + return SUCCESS; + + __position = position; + + return SUCCESS; +} + +Result NmmEngine::endTrack() +{ + __state = Engine::Idle; + __position = 0; + + // cleanup after this method returned + QTimer::singleShot( 0, instance(), SLOT( endOfStreamReached() ) ); + + return SUCCESS; +} + +Result NmmEngine::syncReset() +{ + __seeking = false; + return SUCCESS; +} + +Result NmmEngine::trackDuration(Interval& duration) +{ + // we got the duration of the track, so let's convert it to milliseconds + __track_length = duration.sec * 1000 + duration.nsec / 1000; + kdDebug() << "NmmEngine::trackDuration " << __track_length << endl; + return SUCCESS; +} + +#include "nmm_engine.moc" diff --git a/amarok/src/engine/nmm/nmm_engine.h b/amarok/src/engine/nmm/nmm_engine.h new file mode 100644 index 00000000..dad6887c --- /dev/null +++ b/amarok/src/engine/nmm/nmm_engine.h @@ -0,0 +1,264 @@ +/* NMM - Network-Integrated Multimedia Middleware + * + * Copyright (C) 2002-2006 + * NMM work group, + * Computer Graphics Lab, + * Saarland University, Germany + * http://www.networkmultimedia.org + * + * Maintainer: Robert Gogolok <gogo@graphics.cs.uni-sb.de> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Steet, Fifth Floor, Boston, MA 02110-1301 + * USA + */ + + +#ifndef NMM_ENGINE_H +#define NMM_ENGINE_H + +#include <config.h> + +#include "enginebase.h" +#include "NmmLocation.h" +#include <nmm/base/graph/CompositeNode.hpp> +#include <nmm/base/NMMApplication.hpp> +#include <nmm/base/EDObject.hpp> +#include <nmm/base/sync/MultiAudioVideoSynchronizer.hpp> + +using namespace NMM; + +/** + * \todo currently every song/video change means a stop and restart of the NMM environment. + */ +class NmmEngine : public Engine::Base +{ +Q_OBJECT +public: + NmmEngine(); + ~NmmEngine(); + + bool init(); + bool canDecode(const KURL&) const; + + uint position() const; + + uint length() const; + + Engine::State state() const; + + Amarok::PluginConfig* configure() const; + + QValueList<NmmLocation> environmentHostList() const {return tmp_environment_list;} + void setEnvironmentHostList(QValueList<NmmLocation> list) { tmp_environment_list = list;} + + QValueList<NmmLocation> userHostList() const {return tmp_user_list;} + void setUserHostList(QValueList<NmmLocation> list) { tmp_user_list = list;} + + + static NmmEngine* instance() { return s_instance; } + +public slots: + bool load(const KURL&, bool stream = false); + bool play(unsigned int offset = 0); + void stop(); + void pause(); + void seek(uint); + +private slots: + void endOfStreamReached(); + + /** + * Checks for local NMM security options. + * Warns user if ~/.nmmrc doesn't have + * allowedwritepaths or allowedreadpaths set. + * TODO: Should be called on NMM engine load and not from ::load. + */ + void checkSecurity(); + + /** + * Updates error type in tmp_environment_list and tmp_user_list for a host. + * \param hostname host the error is related to + * \param error error identification, see NMMEngineException::Error + */ + void notifyHostError( QString hostname, int error ); + +signals: + /** + * Emitted when an error occurred during NMM setup. + * \param hostname host the error is related to + * \param error error identification, see NMMEngineException::Error + */ + void hostError( QString hostname, int error ); + +protected: + void setVolumeSW( uint ); + +private: + + /** + * Resets current NMM environment and other configurations. + */ + void cleanup(); + + /** + * \todo document + */ + void createEnvironmentHostList(); + + /** + * \todo document + */ + void createUserHostList(); + + /** + * Returns sink locations for audio/video playback. + * + * \param audio return audio locations, else video locations + * + * \return the audio/video locations + */ + QStringList getSinkHosts( bool audio = true ); + + /** + * This method is called when a setProgress event is received. The two parameters represent a rational number + * (numerator and denominator) containing the amount of progress as a value between 0 and 1. + */ + Result setProgress(u_int64_t&, u_int64_t&); + + /** + * This method is called when an endTrack event is received. + */ + Result endTrack(); + + /** + * This method is called when a syncReset event is received. When an NMM source node has finished seeking, such an event is + * sent. Here, it is used to prevent the engine from updating the track position while receiving setProgress events before seeking + * is done, since these setProgress events may contain old progress information. Otherwise, the progress slider would jump + * back and forth... + */ + Result syncReset(); + + /** + * This method is called when a trackDuration event is received. The duration of a track is represented by + * an Interval that contains the time in seconds and nanoseconds. + */ + Result trackDuration(Interval& duration); + + /** + * The current track position in milliseconds. + */ + u_int64_t __position; + + /** + * The length of the track in milliseconds. + */ + u_int64_t __track_length; + + /** + * The current engine state + */ + Engine::State __state; + + /** + * The NMM application object. + */ + NMMApplication* __app; + + /** + * Event listeners for various NMM events. + */ + TEDObject0<NmmEngine> __endTrack_listener; + TEDObject0<NmmEngine> __syncReset_listener; + TEDObject2<NmmEngine, u_int64_t, u_int64_t> __setProgress_listener; + TEDObject1<NmmEngine, Interval> __trackDuration_listener; + + /** + * The composite node that contains the graph created by the GraphBuilder. + */ + CompositeNode* __composite; + + /** + * The node for audio playback + * where the various events like endTrack, setProgress etc. are caught + * if video is disabled. + */ + INode* __playback; + + /** + * The display node for video playback + * where the various events like endTrack, setProgress etc. are caught + * if video is enabled. + */ + INode* __display; + + /** + * synchronizer for graph builder + */ + MultiAudioVideoSynchronizer* __av_sync; + + /** + * synchronizer interface + */ + IMultiAudioVideoSynchronizer* __synchronizer; + + /** + * Indicates whether we are playing a video. + */ + bool __with_video; + + /** + * This flag is set during seeking. + */ + bool __seeking; + + /** + * Used to determine whether an errorDialog is being displayed + * in 'localhost only' mode. No track should be played till + * the user clicked 'Ok'. + */ + bool m_localhostonly_errordialog; + + /** + * Environment variables host list. + * Only read on startup, volume can be changed via settings dialog. + */ + QValueList<NmmLocation> tmp_environment_list; + + /** + * User host list. + */ + QValueList<NmmLocation> tmp_user_list; + + static NmmEngine* s_instance; + +public: + enum HostStatus { + STATUS_UNKNOWN = 0, + STATUS_OK = 1 << 0, + ERROR_PLAYBACKNODE = 1 << 1, + ERROR_DISPLAYNODE = 1 << 2, + }; +}; + +class NMMEngineException { + public: + NMMEngineException(std::string _hostname, int _error) + : hostname( _hostname ), error( _error ) {} + + std::string hostname; + int error; +}; + +#endif diff --git a/amarok/src/engine/nmm/nmm_kdeconfig.kcfg b/amarok/src/engine/nmm/nmm_kdeconfig.kcfg new file mode 100644 index 00000000..114d3b8b --- /dev/null +++ b/amarok/src/engine/nmm/nmm_kdeconfig.kcfg @@ -0,0 +1,42 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- Authors: Joerg Bakker, joerg@hakker.de --> +<!-- Robert Gogolok, gogo@graphics.cs.uni-sb.de --> +<kcfg xmlns="http://www.kde.org/standards/kcfg/1.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://www.kde.org/standards/kcfg/1.0 + http://www.kde.org/standards/kcfg/1.0/kcfg.xsd" > + <kcfgfile name="amarokrc"/> + + <group name="NMM-Engine"> + <entry key="Audio Output Plugin" type="String"> + <label>Audio output method to use</label> + <whatsthis>Select the audio output plugin.</whatsthis> + <default>PlaybackNode</default> + </entry> + <entry key="Location" type="Enum"> + <label>Type of source for audio and video location</label> + <whatsthis>Type of location of audio and video sink: environment variable, fixed host name or localhost only.</whatsthis> + <choices> + <choice name="LocalhostOnly"/> + <choice name="EnvironmentVariable"/> + <choice name="HostList"/> + </choices> + <default>LocalhostOnly</default> + </entry> + <entry key="Host List" type="StringList"> + <label>Hostnames of audio and video sinks</label> + <whatsthis>Names of hosts where your audio and video sink can be located if Location equals SinkHostName.</whatsthis> + <default></default> + </entry> + <entry key="Audio Toggle" type="StringList"> + <label>Toggle for audio playback</label> + <whatsthis>Indicates for every host in Host List whether audio is enabled/disabled.</whatsthis> + <default></default> + </entry> + <entry key="Video Toggle" type="StringList"> + <label>Toggle for video playback</label> + <whatsthis>Indicates for every host in Host List whether video is enabled/disabled.</whatsthis> + <default></default> + </entry> + </group> +</kcfg> diff --git a/amarok/src/engine/nmm/nmm_kdeconfig.kcfgc b/amarok/src/engine/nmm/nmm_kdeconfig.kcfgc new file mode 100644 index 00000000..8db0f8c5 --- /dev/null +++ b/amarok/src/engine/nmm/nmm_kdeconfig.kcfgc @@ -0,0 +1,7 @@ +# Code generation options for kconfig_compiler +File=nmm_kdeconfig.kcfg +ClassName=NmmKDEConfig +Singleton=true +Mutators=true +MemberVariables=private +GlobalEnums=tru diff --git a/amarok/src/engine/void/Makefile.am b/amarok/src/engine/void/Makefile.am new file mode 100644 index 00000000..ea3b5b0c --- /dev/null +++ b/amarok/src/engine/void/Makefile.am @@ -0,0 +1,25 @@ +kde_module_LTLIBRARIES = \ + libamarok_void-engine_plugin.la + +INCLUDES = \ + -I$(top_srcdir)/amarok/src/plugin \ + -I$(top_srcdir)/amarok/src \ + $(all_includes) + +libamarok_void_engine_plugin_la_LIBADD = \ + $(top_builddir)/amarok/src/libamarok.la \ + $(top_builddir)/amarok/src/plugin/libplugin.la \ + $(LIB_KDECORE) $(LIB_QT) + +libamarok_void_engine_plugin_la_SOURCES = \ + void-engine.cpp + +libamarok_void_engine_plugin_la_LDFLAGS = \ + $(KDE_PLUGIN) \ + $(all_libraries) + +METASOURCES = \ + AUTO + +kde_services_DATA = \ + amarok_void-engine_plugin.desktop diff --git a/amarok/src/engine/void/amarok_void-engine_plugin.desktop b/amarok/src/engine/void/amarok_void-engine_plugin.desktop new file mode 100644 index 00000000..c3338528 --- /dev/null +++ b/amarok/src/engine/void/amarok_void-engine_plugin.desktop @@ -0,0 +1,118 @@ +[Desktop Entry] +Encoding=UTF-8 +Type=Service +Name=<no engine> +Name[af]=<geen enjin> +Name[ar]= <ليس هناك محرك صوتي > +Name[bg]=<без система> +Name[bn]=<কোনও ইঞ্জিন নয়> +Name[br]=<keflusker ebet> +Name[ca]=<sense motor> +Name[cs]=<žádný systém> +Name[da]=<ingen motor> +Name[de]=Keine Audio-Ausgabe +Name[el]=<χωρίς μηχανή> +Name[eo]=<neniu ilo> +Name[et]=<mootor puudub> +Name[eu]=<motorerik ez> +Name[fa]=<بدون موتور> +Name[fi]=<ei järjestelmää> +Name[fr]=<aucun moteur> +Name[ga]=<gan inneall> +Name[gl]=<sen motor> +Name[he]=<ללא מנוע שמע> +Name[hi]=<कोई इंजिन नहीं> +Name[hu]=<nincs alrendszer> +Name[is]=<engin vél> +Name[it]=<nessun motore> +Name[ja]=<エンジンなし> +Name[ka]=<ძრავის გარეშე> +Name[km]=<គ្មាន​ម៉ាស៊ីន> +Name[lt]=<nėra> +Name[mk]=<нема машина> +Name[mn]=<нема машина> +Name[ms]=<tiada enjin> +Name[nb]=<ingen motor> +Name[nds]=<keen> +Name[nl]=<geen engine> +Name[nn]=<ingen motor> +Name[pl]=<brak wyjścia> +Name[pt]=<sem motor> +Name[pt_BR]=<nenhum mecanismo> +Name[ru]=<нет> +Name[se]=<ii makkárge mohtor> +Name[sk]=<žiadny engine> +Name[sl]=<brez pogona> +Name[sq]=<nema motora> +Name[sr]=<нема мотора> +Name[sr@Latn]=<nema motora> +Name[ss]=<nema motora> +Name[sv]=<inget gränssnitt> +Name[th]=<ไม่มีโปรแกรมประมวลผลเสียง> +Name[tr]=<motor yok> +Name[uk]=<немає рушія> +Name[uz]=<yoʻq> +Name[uz@cyrillic]=<йўқ> +Name[zh_CN]=<无引擎> +Name[zh_TW]=<不使用解碼引擎> +X-KDE-Library=libamarok_void-engine_plugin +Comment=Plugin for Amarok +Comment[af]=Inprop module vir Amarok +Comment[ar]= قابس ( برنامج مضاف الى) AmaroK +Comment[bg]=Приставка за Amarok +Comment[bn]=আমারক-এর জন্য প্লাগিন +Comment[br]=Lugent evit Amarok +Comment[ca]=Connector per l'Amarok +Comment[cs]=Modul pro AmaroK +Comment[de]=Modul für Amarok +Comment[el]=Πρόσθετο για το AmaroK +Comment[eo]=Kromaĵo por Amarok +Comment[es]=Extensión para Amarok +Comment[et]=Amaroki plugin +Comment[fa]=وصله برای amaroK +Comment[fi]=Amarok-liitännäinen +Comment[fr]=Module pour Amarok +Comment[ga]=Breiseán AmaroK +Comment[gl]=Extensión para Amarok +Comment[hu]=Bővítőmodul az Amarokhoz +Comment[is]=Íforrit fyrir Amarok +Comment[it]=Plugin per Amarok +Comment[ja]=Amarok のためのプラグイン +Comment[ka]=მოდული Amarok-ისთვის +Comment[km]=កម្មវិធី​ជំនួយ​សម្រាប់ Amarok +Comment[lt]=Amarok įskiepis +Comment[mk]=Приклучок за Амарок +Comment[nb]=Programtillegg for Amarok +Comment[nds]=Moduul för Amarok +Comment[ne]=अमारोकका लागि प्लगइन +Comment[nl]=Plugin voor Amarok +Comment[nn]=Programtillegg for Amarok +Comment[pa]=ਅਮਰੋਕ ਲਈ ਪਲੱਗਇਨ +Comment[pl]=Wtyczka Amaroka +Comment[pt]='Plugin' para o Amarok +Comment[pt_BR]=Plugin para o Amarok +Comment[ru]=Модуль amaroK +Comment[se]=Lassemoduvla Amarok:ii +Comment[sk]=Amarok modul +Comment[sr]=Прикључак за Amarok +Comment[sr@Latn]=Priključak za Amarok +Comment[sv]=Insticksprogram för Amarok +Comment[th]=โปรแกรมเสริมสำหรับ Amarok +Comment[tr]=Amarok için Eklenti +Comment[uk]=Втулок для Amarok +Comment[uz]=Amarok uchun plagin +Comment[uz@cyrillic]=Amarok учун плагин +Comment[wa]=Tchôke-divins po Amarok +Comment[zh_CN]=Amarok 插件 +Comment[zh_TW]=amaroK 插件 +ServiceTypes=Amarok/Plugin + +X-KDE-Amarok-plugintype=engine +X-KDE-Amarok-name=void-engine +X-KDE-Amarok-authors=Max Howell,Mark Kretschmann +X-KDE-Amarok-email=max.howell@methylblue.com +X-KDE-Amarok-rank=1 +X-KDE-Amarok-version=1 +X-KDE-Amarok-framework-version=32 + + diff --git a/amarok/src/engine/void/void-engine.cpp b/amarok/src/engine/void/void-engine.cpp new file mode 100644 index 00000000..394c8310 --- /dev/null +++ b/amarok/src/engine/void/void-engine.cpp @@ -0,0 +1,33 @@ +/*************************************************************************** + void-engine.h - Dummy engine plugin + +copyright : (C) 2003 by Max Howell <max.howell@methylblue.com> +copyright : (C) 2004 by Mark Kretschmann <markey@web.de> +***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "void-engine.h" + +#include <klocale.h> + + +AMAROK_EXPORT_PLUGIN( VoidEngine ) + + +bool +VoidEngine::load( const KURL& url, bool stream ) +{ + Engine::Base::load( url, stream ); + emit statusText( i18n( "Error: No engine loaded, cannot start playback." ) ); + + return false; +} + diff --git a/amarok/src/engine/void/void-engine.h b/amarok/src/engine/void/void-engine.h new file mode 100644 index 00000000..b5d311d0 --- /dev/null +++ b/amarok/src/engine/void/void-engine.h @@ -0,0 +1,39 @@ +/*************************************************************************** + void-engine.cpp - Dummy engine plugin + +copyright : (C) 2003 by Max Howell <max.howell@methylblue.com> +copyright : (C) 2004 by Mark Kretschmann <markey@web.de> +***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "enginebase.h" + +class VoidEngine : public Engine::Base +{ + //Does nothing, just here to prevent crashes on startup + //and in case no engines are found + + virtual bool init() { return true; } + virtual bool canDecode( const KURL& ) const { return false; } + virtual uint position() const { return 0; } + virtual bool load( const KURL&, bool ); + virtual bool play( uint ) { return false; } + virtual void stop() {} + virtual void pause() {} + virtual void unpause() {} + virtual void setVolumeSW( uint ) {} + virtual void seek( uint ) {} + + virtual Engine::State state() const { return Engine::Empty; } + +public: VoidEngine() : EngineBase() {} +}; + diff --git a/amarok/src/engine/xine/Makefile.am b/amarok/src/engine/xine/Makefile.am new file mode 100644 index 00000000..2a551f7f --- /dev/null +++ b/amarok/src/engine/xine/Makefile.am @@ -0,0 +1,31 @@ +kde_module_LTLIBRARIES = libamarok_xine-engine.la +kde_services_DATA = amarok_xine-engine.desktop + +INCLUDES = \ + -I$(top_srcdir)/amarok/src \ + -I$(top_srcdir)/amarok/src/amarokcore \ + -I$(top_builddir)/amarok/src/amarokcore \ + $(XINE_CFLAGS) \ + $(all_includes) + +libamarok_xine_engine_la_LIBADD = \ + $(top_builddir)/amarok/src/libamarok.la \ + $(top_builddir)/amarok/src/plugin/libplugin.la \ + -lkdeui -lkdecore \ + $(XINE_LIBS) $(LIB_QT) + +libamarok_xine_engine_la_SOURCES = \ + xine-scope.c \ + xinecfg.kcfgc \ + xine-engine.cpp \ + xineconfigbase.ui \ + xine-config.cpp + +libamarok_xine_engine_la_LDFLAGS = \ + $(KDE_PLUGIN) \ + $(all_libraries) + +kde_kcfg_DATA = \ + xinecfg.kcfg + +METASOURCES = AUTO diff --git a/amarok/src/engine/xine/amarok_xine-engine.desktop b/amarok/src/engine/xine/amarok_xine-engine.desktop new file mode 100644 index 00000000..ee732da9 --- /dev/null +++ b/amarok/src/engine/xine/amarok_xine-engine.desktop @@ -0,0 +1,121 @@ +[Desktop Entry] +Encoding=UTF-8 +Type=Service +Name=xine Engine +Name[af]=XINE enjin +Name[ar]=محرك xine +Name[bg]=Xine +Name[bn]=জাইন ইঞ্জিন +Name[br]=Keflusker xine +Name[ca]=Motor xine +Name[cs]=xine +Name[da]=xine-motor +Name[de]=xine +Name[el]=Μηχανή xine +Name[eo]=xine Ilo +Name[es]=Motor xine +Name[et]=xine mootor +Name[eu]=xine motorea +Name[fa]=موتور xine +Name[fi]=xine +Name[fr]=Moteur xine +Name[ga]=Inneall xine +Name[gl]=Motor Xine +Name[he]=מנוע שמע xine +Name[hi]=एक्जाइन इंजिन +Name[hu]=Xine alrendszer +Name[is]=xine vél +Name[it]=Motore xine +Name[ja]=xine エンジン +Name[ka]=xine dრავა +Name[km]=ម៉ាស៊ីន xine +Name[lt]=Xine variklis +Name[mk]=xine-машина +Name[ms]=Enjin xine +Name[nb]=xine-motor +Name[nds]=Xine +Name[ne]=जाइन इन्जिन +Name[nl]=xine-engine +Name[nn]=xine-motor +Name[pa]=xine ਇੰਜਣ +Name[pl]=Wyjście xine +Name[pt]=Motor Xine +Name[pt_BR]=Mecanismo Xine +Name[ru]=Xine +Name[se]=xine-mohtor +Name[sl]=Pogon xine +Name[sq]=Motor Xine +Name[sr]=Мотор Xine +Name[sr@Latn]=Motor Xine +Name[ss]=Motor Xine +Name[sv]=Xine-gränssnitt +Name[ta]=xine என்ஜின் +Name[tg]=Муҳаррики xine +Name[tr]=xine Motoru +Name[uk]=Рушій xine +Name[uz]=xine tizimi +Name[uz@cyrillic]=xine тизими +Name[wa]=Éndjin xine +Name[zh_CN]=xine 引擎 +Name[zh_TW]=xine 解碼引擎 +X-KDE-Library=libamarok_xine-engine +Comment=Plugin for Amarok +Comment[af]=Inprop module vir Amarok +Comment[ar]= قابس ( برنامج مضاف الى) AmaroK +Comment[bg]=Приставка за Amarok +Comment[bn]=আমারক-এর জন্য প্লাগিন +Comment[br]=Lugent evit Amarok +Comment[ca]=Connector per l'Amarok +Comment[cs]=Modul pro AmaroK +Comment[de]=Modul für Amarok +Comment[el]=Πρόσθετο για το AmaroK +Comment[eo]=Kromaĵo por Amarok +Comment[es]=Extensión para Amarok +Comment[et]=Amaroki plugin +Comment[fa]=وصله برای amaroK +Comment[fi]=Amarok-liitännäinen +Comment[fr]=Module pour Amarok +Comment[ga]=Breiseán AmaroK +Comment[gl]=Extensión para Amarok +Comment[hu]=Bővítőmodul az Amarokhoz +Comment[is]=Íforrit fyrir Amarok +Comment[it]=Plugin per Amarok +Comment[ja]=Amarok のためのプラグイン +Comment[ka]=მოდული Amarok-ისთვის +Comment[km]=កម្មវិធី​ជំនួយ​សម្រាប់ Amarok +Comment[lt]=Amarok įskiepis +Comment[mk]=Приклучок за Амарок +Comment[nb]=Programtillegg for Amarok +Comment[nds]=Moduul för Amarok +Comment[ne]=अमारोकका लागि प्लगइन +Comment[nl]=Plugin voor Amarok +Comment[nn]=Programtillegg for Amarok +Comment[pa]=ਅਮਰੋਕ ਲਈ ਪਲੱਗਇਨ +Comment[pl]=Wtyczka Amaroka +Comment[pt]='Plugin' para o Amarok +Comment[pt_BR]=Plugin para o Amarok +Comment[ru]=Модуль amaroK +Comment[se]=Lassemoduvla Amarok:ii +Comment[sk]=Amarok modul +Comment[sr]=Прикључак за Amarok +Comment[sr@Latn]=Priključak za Amarok +Comment[sv]=Insticksprogram för Amarok +Comment[th]=โปรแกรมเสริมสำหรับ Amarok +Comment[tr]=Amarok için Eklenti +Comment[uk]=Втулок для Amarok +Comment[uz]=Amarok uchun plagin +Comment[uz@cyrillic]=Amarok учун плагин +Comment[wa]=Tchôke-divins po Amarok +Comment[zh_CN]=Amarok 插件 +Comment[zh_TW]=amaroK 插件 +ServiceTypes=Amarok/Plugin + +X-KDE-Amarok-plugintype=engine +X-KDE-Amarok-name=xine-engine +X-KDE-Amarok-authors=Max Howell +X-KDE-Amarok-email=max.howell@methylblue.com +X-KDE-Amarok-rank=255 +X-KDE-Amarok-version=1 +X-KDE-Amarok-framework-version=32 + + diff --git a/amarok/src/engine/xine/amarok_xine-mp3_install.desktop b/amarok/src/engine/xine/amarok_xine-mp3_install.desktop new file mode 100644 index 00000000..172d49ea --- /dev/null +++ b/amarok/src/engine/xine/amarok_xine-mp3_install.desktop @@ -0,0 +1,9 @@ +[Desktop Entry] +Encoding=UTF-8 +Type=Service +ServiceTypes=Amarok/CodecInstall +X-KDE-Amarok-codec=mp3 +X-KDE-Amarok-engine=xine-engine +Exec=/opt/kde3/lib/amarok/install-mp3 + + diff --git a/amarok/src/engine/xine/xine-config.cpp b/amarok/src/engine/xine/xine-config.cpp new file mode 100644 index 00000000..95295d3f --- /dev/null +++ b/amarok/src/engine/xine/xine-config.cpp @@ -0,0 +1,310 @@ +//(C) 2005 Ian Monroe <ian@monroe.nu> +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "debug.h" +#include "xine-config.h" +#include "xinecfg.h" + +#include <kcombobox.h> +#include <klocale.h> +#include <knuminput.h> +#include <kstandarddirs.h> + +#include <qgroupbox.h> +#include <qlabel.h> +#include <qpixmap.h> + +//////////////////// +/// XineGeneralEntry +//////////////////// +XineGeneralEntry::XineGeneralEntry(const QString& key, xine_t *xine, XineConfigDialog* xcf) + : m_valueChanged(false) + , m_key(key) + , m_xine(xine) +{ + debug() << "new entry " << m_key << endl; + connect(this, SIGNAL(viewChanged()), xcf, SIGNAL(viewChanged() ) ); +} + + +void XineGeneralEntry::entryChanged() +{ + m_valueChanged = true; + emit viewChanged(); +} + +///////////////// +/// Function saveXineEntry +///////////////// +template<class T, class Functor> +void saveXineEntry(Functor& storeEntry, T val, const QString& key, xine_t *xine) +{ + if(xine) debug() << "its not null " << key << ' ' << val << endl; + xine_cfg_entry_t ent; + if(xine_config_lookup_entry(xine, key.ascii(), &ent)) + { + storeEntry(&ent, val); + xine_config_update_entry(xine, &ent); + } + else + debug()<<"Error saving " << val << " with key " << key; + +} +////////////////// +/// Functors +////////////////// +void +XineIntFunctor::operator()( xine_cfg_entry_t* ent, int val ) +{ + ent->num_value = val; +} + + +void +XineStrFunctor::operator()( xine_cfg_entry_t* ent, const QString& val ) +{ + ent->str_value = const_cast<char*>(val.ascii()); +} + +//////////////////// +/// XineStrEntry +//////////////////// +XineStrEntry::XineStrEntry(QLineEdit* input, const QCString & key, xine_t *xine, XineConfigDialog* xcf) + : XineGeneralEntry(key,xine,xcf) +{ + xine_cfg_entry_t ent; + if( xine_config_lookup_entry(m_xine, m_key.ascii(), &ent) ) + { + input->setText(ent.str_value); + m_val = ent.str_value; + } + connect( input, SIGNAL( textChanged( const QString & ) ), this, SLOT( entryChanged(const QString &) ) ); +} + + +void +XineStrEntry::save() +{ + XineStrFunctor func; + saveXineEntry(func, m_val, m_key, m_xine); + m_valueChanged = false; +} + + +void +XineStrEntry::entryChanged(const QString & val) +{ + m_val = val; + XineGeneralEntry::entryChanged(); +} + +//////////////////// +/// XineIntEntry +//////////////////// +XineIntEntry::XineIntEntry(KIntSpinBox* input, const QCString & key, xine_t *xine, XineConfigDialog* xcf) + : XineGeneralEntry(key,xine,xcf) +{ + xine_cfg_entry_t ent; + if(xine_config_lookup_entry(m_xine, m_key.ascii(), &ent)) + { + input->setValue(ent.num_value); + m_val = ent.num_value; + } + connect( input, SIGNAL( valueChanged( int ) ), this, SLOT( entryChanged( int ) ) ); +} + + +XineIntEntry::XineIntEntry(const QString& key, xine_t *xine, XineConfigDialog* xcf) + : XineGeneralEntry(key,xine,xcf) +{ } + + +void +XineIntEntry::save() +{ + XineIntFunctor func; + saveXineEntry(func, m_val, m_key, m_xine); + m_valueChanged = false; +} + + +void +XineIntEntry::entryChanged(int val) +{ + m_val = val; + XineGeneralEntry::entryChanged(); +} + +//////////////////// +/// XineEnumEntry +//////////////////// +XineEnumEntry::XineEnumEntry(QComboBox* input, const QCString & key, xine_t *xine, XineConfigDialog* xcf) + : XineIntEntry(key,xine,xcf) +{ + input->clear(); + xine_cfg_entry_t ent; + if(xine_config_lookup_entry(m_xine, m_key.ascii(), &ent)) + { + for( int i = 0; ent.enum_values[i]; ++i ) + { + input->insertItem( QString::fromLocal8Bit( ent.enum_values[i] ) ); + input->setCurrentItem( ent.num_value ); + m_val = ent.num_value; + } + } + connect( input, SIGNAL( activated( int ) ), this, SLOT( entryChanged( int ) ) ); +} + +/////////////////////// +/// XineConfigDialog +/////////////////////// + +XineConfigDialog::XineConfigDialog( const xine_t* const xine) + : PluginConfig() + , m_xine (const_cast<xine_t*>(xine)) +{ + m_view = new XineConfigBase(); + m_view->xineLogo->setPixmap( QPixmap( locate( "data", "amarok/images/xine_logo.png" ) ) ); + //sound output combo box + m_view->deviceComboBox->insertItem(i18n("Autodetect")); + const char* const* drivers = xine_list_audio_output_plugins(m_xine); + for(int i =0; drivers[i]; ++i) + { + if(qstrcmp(drivers[i],"none") != 0) //returns 0 if equal + m_view->deviceComboBox->insertItem(drivers[i]); + } + + connect( m_view->deviceComboBox, SIGNAL( activated( int ) ), SIGNAL( viewChanged() ) ); + m_entries.setAutoDelete(true); + m_view->deviceComboBox->setCurrentItem( (XineCfg::outputPlugin() == "auto" ) ? "Autodetect" : XineCfg::outputPlugin() ); + init(); + showHidePluginConfigs(); +} + + +XineConfigDialog::~XineConfigDialog() +{ + XineCfg::writeConfig(); + delete m_view; +} + + +void +XineConfigDialog::init() +{ + #define add(X) m_entries.append(X) + add(new XineStrEntry(m_view->hostLineEdit, "media.network.http_proxy_host", m_xine, this)); + add(new XineIntEntry(m_view->portIntBox,"media.network.http_proxy_port", m_xine, this)); + add(new XineStrEntry(m_view->userLineEdit, "media.network.http_proxy_user", m_xine, this)); + add(new XineStrEntry(m_view->passLineEdit, "media.network.http_proxy_password", m_xine, this)); + //alsaGroupBox + add(new XineStrEntry(m_view->monoLineEdit, "audio.device.alsa_default_device", m_xine,this)); + add(new XineStrEntry(m_view->stereoLineEdit, "audio.device.alsa_front_device", m_xine,this)); + add(new XineStrEntry(m_view->chan4LineEdit, "audio.device.alsa_surround40_device", m_xine, this)); + add(new XineStrEntry(m_view->chan5LineEdit, "audio.device.alsa_surround51_device", m_xine, this)); + //ossGroupBox + add(new XineEnumEntry(m_view->ossDeviceComboBox, "audio.device.oss_device_name", m_xine,this)); + add(new XineEnumEntry(m_view->speakerComboBox, "audio.output.speaker_arrangement", m_xine, this)); + // audiocdGroupBox + add(new XineStrEntry(m_view->audiocd_device, "media.audio_cd.device", m_xine, this)); + add(new XineStrEntry(m_view->cddb_server, "media.audio_cd.cddb_server", m_xine, this)); + add(new XineIntEntry(m_view->cddb_port, "media.audio_cd.cddb_port", m_xine, this)); + add(new XineStrEntry(m_view->cddb_cache_dir, "media.audio_cd.cddb_cachedir", m_xine, this)); + #undef add +} + + +void +XineConfigDialog::showHidePluginConfigs() const +{ + if(m_view->deviceComboBox->currentText() == "alsa") + { + m_view->alsaGroupBox->show(); + m_view->ossGroupBox->hide(); + if(XineCfg::outputPlugin() == "alsa") + m_view->alsaGroupBox->setEnabled(true); + else + m_view->alsaGroupBox->setEnabled(false); + } + else if(m_view->deviceComboBox->currentText() == "oss") + { + m_view->alsaGroupBox->hide(); + m_view->ossGroupBox->show(); + if(XineCfg::outputPlugin() == "oss") + m_view->ossGroupBox->setEnabled(true); + else + m_view->ossGroupBox->setEnabled(false); + } + else + { + m_view->alsaGroupBox->hide(); + m_view->ossGroupBox->hide(); + m_view->alsaGroupBox->setEnabled(false); + m_view->ossGroupBox->setEnabled(false); + } +} + + +bool +XineConfigDialog::hasChanged() const +{ + showHidePluginConfigs(); + if(XineCfg::outputPlugin() != ((m_view->deviceComboBox->currentItem() == 0) ? "auto" : m_view->deviceComboBox->currentText())) + return true; + + QPtrListIterator<XineGeneralEntry> it( m_entries ); + XineGeneralEntry* entry; + while( (entry = it.current()) != 0 ) + { + ++it; + if(entry->hasChanged()) + return true; + } + return false; +} + + +bool +XineConfigDialog::isDefault() const +{ + return false; +} + + +void +XineConfigDialog::reset(xine_t* xine) //SLOT +{ + debug() << &m_xine << " " << &xine << endl; + m_entries.clear(); + m_xine = xine; + debug() << "m_entries now empty " << m_entries.isEmpty() << endl; + init(); +} + + +void +XineConfigDialog::save()//SLOT +{ + if(hasChanged()) + { + //its not Autodetect really, its just auto + XineCfg::setOutputPlugin((m_view->deviceComboBox->currentItem() == 0) + ? "auto" : m_view->deviceComboBox->currentText()); + XineGeneralEntry* entry; + for(entry = m_entries.first(); entry; entry=m_entries.next()) + { + if(entry->hasChanged()) + entry->save(); + } + emit settingsSaved(); + } +} + +#include "xine-config.moc" diff --git a/amarok/src/engine/xine/xine-config.h b/amarok/src/engine/xine/xine-config.h new file mode 100644 index 00000000..789b4942 --- /dev/null +++ b/amarok/src/engine/xine/xine-config.h @@ -0,0 +1,131 @@ +//Copyright: (C) 2004 Max Howell, <max.howell@methylblue.com> +//Copyright: (C) 2003-2004 J. Kofler, <kaffeine@gmx.net> +//Copyright: (C) 2005 Ian Monroe +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef XINECONFIG_H +#define XINECONFIG_H + +#include "plugin/pluginconfig.h" +#include "xineconfigbase.h" + +#include <xine.h> + +class XineConfigDialog; +class KLineEdit; + +class XineGeneralEntry : public QObject +{ + Q_OBJECT + + public: + virtual void save() = 0; + bool hasChanged() const { return m_valueChanged; }; + + signals: + void viewChanged(); + + protected: + XineGeneralEntry(const QString& key, xine_t *xine, XineConfigDialog* xcf); + void entryChanged(); + + bool m_valueChanged; + QString m_key; + xine_t *m_xine; +}; + + +class XineStrFunctor +{ + public: + void operator()( xine_cfg_entry_t* ent, const QString& val ); +}; + + +class XineIntFunctor +{ + public: + void operator()( xine_cfg_entry_t* ent, int val ); +}; + + +template<class T, class Functor> +void saveXineEntry(Functor& storeEntry, T val, const QString& key, xine_t *xine); + + +class XineStrEntry : public XineGeneralEntry +{ + Q_OBJECT + + public: + XineStrEntry(QLineEdit* input, const QCString & key, xine_t *m_xine, XineConfigDialog* xcf); + void save(); + + private slots: + void entryChanged(const QString& newEntry); + + private: + QString m_val; +}; + + +class XineIntEntry : public XineGeneralEntry +{ + Q_OBJECT + + public: + XineIntEntry(KIntSpinBox* input, const QCString & key, xine_t *xine, XineConfigDialog* xcf); + XineIntEntry(const QString& key, xine_t *xine, XineConfigDialog* xcf); + void save(); + + protected slots: + void entryChanged(int newEntry); + + protected: + int m_val; +}; + + +class XineEnumEntry : public XineIntEntry +{ + Q_OBJECT +public: + XineEnumEntry(QComboBox* input, const QCString & key, xine_t *xine, XineConfigDialog* xcf); +}; + + +class XineConfigDialog : public Amarok::PluginConfig +{ + Q_OBJECT + + public: + XineConfigDialog( const xine_t* const xine); + ~XineConfigDialog(); + QWidget* view() { return m_view; } + /** Return true if any of the view settings are different to the currently saved state */ + bool hasChanged() const; + /** Return true if all view settings are in their default states */ + bool isDefault() const; + + public slots: + /** Save view state using, eg KConfig */ + void save(); + void reset(xine_t *xine); + + private: + /** All data structures with m_xine initiated **/ + void init(); + void showHidePluginConfigs() const; + xine_t *m_xine; + QPtrList<XineGeneralEntry> m_entries; + XineConfigBase* m_view; +}; + +#endif diff --git a/amarok/src/engine/xine/xine-engine.cpp b/amarok/src/engine/xine/xine-engine.cpp new file mode 100644 index 00000000..05412aca --- /dev/null +++ b/amarok/src/engine/xine/xine-engine.cpp @@ -0,0 +1,1342 @@ +/*************************************************************************** + * Copyright (C) 2005 Christophe Thommeret <hftom@free.fr> * + * (C) 2005 Ian Monroe <ian@monroe.nu> * + * (C) 2005,6 Mark Kretschmann <markey@web.de> * + * (C) 2004,5 Max Howell <max.howell@methylblue.com> * + * (C) 2003,4 J. Kofler <kaffeine@gmx.net> * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#define DEBUG_PREFIX "xine-engine" + +#include "xine-config.h" +#include "xinecfg.h" +#include "xine-engine.h" +#include "amarok.h" +#include "amarokconfig.h" +//these files are from libamarok +#include "playlist.h" +#include "enginecontroller.h" + +AMAROK_EXPORT_PLUGIN( XineEngine ) + +#include <climits> +#include <cstdlib> +#include <cmath> +#include "debug.h" + +#include <klocale.h> +#include <kmessagebox.h> +#include <kstandarddirs.h> + +#include <qapplication.h> +#include <qdir.h> + +extern "C" +{ + #include <unistd.h> + #include "xine-scope.h" +} + +#ifndef LLONG_MAX +#define LLONG_MAX 9223372036854775807LL +#endif + + +//define this to use xine in a more standard way +//#define XINE_SAFE_MODE + + +///some logging static globals +namespace Log +{ + static uint bufferCount = 0; + static uint scopeCallCount = 1; //prevent divideByZero + static uint noSuitableBuffer = 0; +} + +///returns the configuration we will use. there is no KInstance, so using this hacked up method. +//static inline QCString configPath() { return QFile::encodeName(KStandardDirs().localkdedir() + KStandardDirs::kde_default("data") + "amarok/xine-config"); } +static inline QCString configPath() { return QFile::encodeName(locate( "data", "amarok/") + "xine-config" ); } +static Fader *s_fader = 0; +static OutFader *s_outfader = 0; + + +XineEngine::XineEngine() + : EngineBase() + , m_xine( 0 ) + , m_stream( 0 ) + , m_audioPort( 0 ) + , m_eventQueue( 0 ) + , m_post( 0 ) + , m_preamp( 1.0 ) + , m_stopFader( false ) + , m_fadeOutRunning ( false ) + , m_equalizerEnabled( false ) +{ + addPluginProperty( "HasConfigure", "true" ); + addPluginProperty( "HasEqualizer", "true" ); + #ifndef __NetBSD__ // NetBSD does not offer audio mixing + addPluginProperty( "HasCrossfade", "true" ); + #endif + addPluginProperty("HasCDDA", "true"); // new property + debug() << "hello" << endl; + +} + +XineEngine::~XineEngine() +{ + // Wait until the fader thread is done + if( s_fader ) { + m_stopFader = true; + s_fader->resume(); // safety call if the engine is in the pause state + s_fader->wait(); + } + + delete s_fader; + delete s_outfader; + + if( AmarokConfig::fadeoutOnExit() ) { + bool terminateFader = false; + fadeOut( AmarokConfig::fadeoutLength(), &terminateFader, true ); // true == exiting + } + + if( m_xine ) xine_config_save( m_xine, configPath() ); + + if( m_stream ) xine_close( m_stream ); + if( m_eventQueue ) xine_event_dispose_queue( m_eventQueue ); + if( m_stream ) xine_dispose( m_stream ); + if( m_audioPort ) xine_close_audio_driver( m_xine, m_audioPort ); + if( m_post ) xine_post_dispose( m_xine, m_post ); + if( m_xine ) xine_exit( m_xine ); + + debug() << "xine closed\n"; + + debug() << "Scope statistics:\n" + << " Average list size: " << Log::bufferCount / Log::scopeCallCount << endl + << " Buffer failure: " << double(Log::noSuitableBuffer*100) / Log::scopeCallCount << "%\n"; +} + +bool +XineEngine::init() +{ + DEBUG_BLOCK + + debug() << "'Bringing joy to small mexican gerbils, a few weeks at a time.'\n"; + + m_xine = xine_new(); + + if( !m_xine ) { + KMessageBox::error( 0, i18n("Amarok could not initialize xine.") ); + return false; + } + + #ifdef XINE_SAFE_MODE + xine_engine_set_param( m_xine, XINE_ENGINE_PARAM_VERBOSITY, 99 ); + #endif + + xine_config_load( m_xine, configPath() ); + debug() << "w00t" << configPath() << endl; + + xine_init( m_xine ); + + makeNewStream(); + + #ifndef XINE_SAFE_MODE + startTimer( 200 ); //prunes the scope + #endif + + return true; +} + +bool +XineEngine::makeNewStream() +{ + m_currentAudioPlugin = XineCfg::outputPlugin(); + + m_audioPort = xine_open_audio_driver( m_xine, XineCfg::outputPlugin().local8Bit(), NULL ); + if( !m_audioPort ) { + //TODO make engine method that is the same but parents the dialog for us + KMessageBox::error( 0, i18n("xine was unable to initialize any audio drivers.") ); + return false; + } + + m_stream = xine_stream_new( m_xine, m_audioPort, NULL ); + if( !m_stream ) { + xine_close_audio_driver( m_xine, m_audioPort ); + m_audioPort = NULL; + KMessageBox::error( 0, i18n("Amarok could not create a new xine stream.") ); + return false; + } + + if( m_eventQueue ) + xine_event_dispose_queue( m_eventQueue ); + + xine_event_create_listener_thread( + m_eventQueue = xine_event_new_queue( m_stream ), + &XineEngine::XineEventListener, + (void*)this ); + + #ifndef XINE_SAFE_MODE + //implemented in xine-scope.h + m_post = scope_plugin_new( m_xine, m_audioPort ); + + xine_set_param( m_stream, XINE_PARAM_METRONOM_PREBUFFER, 6000 ); + xine_set_param( m_stream, XINE_PARAM_IGNORE_VIDEO, 1 ); + #endif +#ifdef XINE_PARAM_EARLY_FINISHED_EVENT + if ( xine_check_version(1,1,1) && !(m_xfadeLength > 0) ) { + // enable gapless playback + debug() << "gapless playback enabled." << endl; + //xine_set_param(m_stream, XINE_PARAM_EARLY_FINISHED_EVENT, 1 ); + } +#endif + return true; +} + +// Makes sure an audio port and a stream exist. +bool +XineEngine::ensureStream() +{ + if( !m_stream ) + return makeNewStream(); + + return true; +} + +bool +XineEngine::load( const KURL &url, bool isStream ) +{ + DEBUG_BLOCK + + if( !ensureStream() ) + return false; + + Engine::Base::load( url, isStream ); + + if( s_outfader ) { + s_outfader->finish(); + delete s_outfader; + } + + if( m_xfadeLength > 0 && xine_get_status( m_stream ) == XINE_STATUS_PLAY && + url.isLocalFile() && + xine_get_param( m_stream, XINE_PARAM_SPEED ) != XINE_SPEED_PAUSE && + ( m_xfadeNextTrack || //set by engine controller when switching tracks automatically + (uint) AmarokConfig::crossfadeType() == 0 || //crossfade always + (uint) AmarokConfig::crossfadeType() == 2 ) ) //crossfade when switching tracks manually + { + m_xfadeNextTrack = false; + // Stop a probably running fader + if( s_fader ) { + m_stopFader = true; + s_fader->finish(); // makes the fader stop abruptly + delete s_fader; + } + s_fader = new Fader( this, m_xfadeLength ); + setEqualizerParameters( m_intPreamp, m_equalizerGains ); + } + + // for users who stubbonly refuse to use DMIX or buy a good soundcard + // why doesn't xine do this? I cannot say. + xine_close( m_stream ); + + debug() << "Before xine_open() *****" << endl; + + if( xine_open( m_stream, QFile::encodeName( url.url() ) ) ) + { + debug() << "After xine_open() *****" << endl; + + #ifndef XINE_SAFE_MODE + //we must ensure the scope is pruned of old buffers + timerEvent( 0 ); + + xine_post_out_t *source = xine_get_audio_source( m_stream ); + xine_post_in_t *target = (xine_post_in_t*)xine_post_input( m_post, const_cast<char*>("audio in") ); + xine_post_wire( source, target ); + #endif + + playlistChanged(); + + return true; + } + else + { + #ifdef XINE_PARAM_GAPLESS_SWITCH + if ( xine_check_version(1,1,1) && !(m_xfadeLength > 0) ) + xine_set_param( m_stream, XINE_PARAM_GAPLESS_SWITCH, 0); + #endif + } + + // FAILURE to load! + //s_fader will delete itself + determineAndShowErrorMessage(); + + return false; +} + +bool +XineEngine::play( uint offset ) +{ + DEBUG_BLOCK + + if( !ensureStream() ) + return false; + + const bool has_audio = xine_get_stream_info( m_stream, XINE_STREAM_INFO_HAS_AUDIO ); + const bool audio_handled = xine_get_stream_info( m_stream, XINE_STREAM_INFO_AUDIO_HANDLED ); + + if (has_audio && audio_handled && xine_play( m_stream, 0, offset )) + { + if( s_fader ) + s_fader->start( QThread::LowestPriority ); + + emit stateChanged( Engine::Playing ); + + return true; + } + + //we need to stop the track that is prepped for crossfade + delete s_fader; + + emit stateChanged( Engine::Empty ); + + determineAndShowErrorMessage(); + + xine_close( m_stream ); + + return false; +} + +#include "statusbar/statusbar.h" + +void +XineEngine::determineAndShowErrorMessage() +{ + DEBUG_BLOCK + + QString body; + + debug() << "xine_get_error()\n"; + switch (xine_get_error( m_stream )) { + case XINE_ERROR_NO_INPUT_PLUGIN: + body = i18n("No suitable input plugin. This often means that the url's protocol is not supported. Network failures are other possible causes."); + break; + + case XINE_ERROR_NO_DEMUX_PLUGIN: + body = i18n("No suitable demux plugin. This often means that the file format is not supported."); + break; + + case XINE_ERROR_DEMUX_FAILED: + body = i18n("Demuxing failed."); + break; + + case XINE_ERROR_INPUT_FAILED: + body = i18n("Could not open file."); + break; + + case XINE_ERROR_MALFORMED_MRL: + body = i18n("The location is malformed."); + break; + + case XINE_ERROR_NONE: + // xine is thick. xine doesn't think there is an error + // but there may be! We check for other errors below. + + default: + if (!xine_get_stream_info( m_stream, XINE_STREAM_INFO_AUDIO_HANDLED )) + { + // xine can read the plugin but it didn't find any codec + // THUS xine=daft for telling us it could handle the format in canDecode! + body = i18n("There is no available decoder."); + QString const ext = Amarok::extension( m_url.url() ).lower(); + if (ext == "mp3" && EngineController::installDistroCodec( "xine-engine" )) + return; + } + else if (!xine_get_stream_info( m_stream, XINE_STREAM_INFO_HAS_AUDIO )) + body = i18n("There is no audio channel!"); + break; + } + + Amarok::StatusBar::instance()->longMessage( + "<b>" + i18n("Error Loading Media") + "</b><p>" + body + "<p>" + m_url.prettyURL(), + KDE::StatusBar::Error ); +} + +void +XineEngine::stop() +{ + if( s_fader && s_fader->running() ) + s_fader->resume(); // safety call if the engine is in the pause state + + if ( !m_stream ) + return; + + if( AmarokConfig::fadeout() && !m_fadeOutRunning || state() == Engine::Paused ) + { + s_outfader = new OutFader( this, AmarokConfig::fadeoutLength() ); + s_outfader->start(); + ::usleep( 100 ); //to be sure engine state won't be changed before it is checked in fadeOut() + m_url = KURL(); //to ensure we return Empty from state() + + std::fill( m_scope.begin(), m_scope.end(), 0 ); + } + else if( !m_fadeOutRunning ) + { + xine_stop( m_stream ); + xine_close( m_stream ); + xine_set_param( m_stream, XINE_PARAM_AUDIO_CLOSE_DEVICE, 1); + } + + emit stateChanged( Engine::Empty ); +} + +void +XineEngine::pause() +{ + if ( !m_stream ) + return; + + if( xine_get_param( m_stream, XINE_PARAM_SPEED ) != XINE_SPEED_PAUSE ) + { + if( s_fader && s_fader->running() ) + s_fader->pause(); + + xine_set_param( m_stream, XINE_PARAM_SPEED, XINE_SPEED_PAUSE ); + xine_set_param( m_stream, XINE_PARAM_AUDIO_CLOSE_DEVICE, 1); + emit stateChanged( Engine::Paused ); + + } +} + +void +XineEngine::unpause() +{ + if ( !m_stream ) + return; + + if( xine_get_param( m_stream, XINE_PARAM_SPEED ) == XINE_SPEED_PAUSE ) + { + if( s_fader && s_fader->running() ) + s_fader->resume(); + + xine_set_param( m_stream, XINE_PARAM_SPEED, XINE_SPEED_NORMAL ); + emit stateChanged( Engine::Playing ); + } +} + +Engine::State +XineEngine::state() const +{ + if ( !m_stream || m_fadeOutRunning ) + return Engine::Empty; + + switch( xine_get_status( m_stream ) ) + { + case XINE_STATUS_PLAY: return xine_get_param( m_stream, XINE_PARAM_SPEED ) != XINE_SPEED_PAUSE ? Engine::Playing : Engine::Paused; + case XINE_STATUS_IDLE: return Engine::Empty; + case XINE_STATUS_STOP: + default: return m_url.isEmpty() ? Engine::Empty : Engine::Idle; + } +} + +uint +XineEngine::position() const +{ + if ( state() == Engine::Empty ) + return 0; + + int pos; + int time = 0; + int length; + + // Workaround for problems when you seek too quickly, see BUG 99808 + int tmp = 0, i = 0; + while( ++i < 4 ) + { + xine_get_pos_length( m_stream, &pos, &time, &length ); + if( time > tmp ) break; + usleep( 100000 ); + } + + // Here we check for new metadata periodically, because xine does not emit an event + // in all cases (e.g. with ogg streams). See BUG 122505 + if ( state() != Engine::Idle && state() != Engine::Empty ) + { + const Engine::SimpleMetaBundle bundle = fetchMetaData(); + if( bundle.title != m_currentBundle.title || bundle.artist != m_currentBundle.artist ) { + debug() << "Metadata received." << endl; + m_currentBundle = bundle; + + XineEngine* p = const_cast<XineEngine*>( this ); + p->emit metaData( bundle ); + } + } + + return time; +} + +uint +XineEngine::length() const +{ + if ( !m_stream ) + return 0; + + // xine often delivers nonsense values for VBR files and such, so we only + // use the length for remote files + + if( m_url.isLocalFile() ) + return 0; + + else { + int pos; + int time; + int length = 0; + + xine_get_pos_length( m_stream, &pos, &time, &length ); + if( length < 0 ) + length=0; + + return length; + } +} + +void +XineEngine::seek( uint ms ) +{ + if( !ensureStream() ) + return; + + if( xine_get_param( m_stream, XINE_PARAM_SPEED ) == XINE_SPEED_PAUSE ) { + // FIXME this is a xine API issue really, they need to add a seek function + xine_play( m_stream, 0, (int)ms ); + xine_set_param( m_stream, XINE_PARAM_SPEED, XINE_SPEED_PAUSE ); + } + else + xine_play( m_stream, 0, (int)ms ); +} + +void +XineEngine::setVolumeSW( uint vol ) +{ + if ( !m_stream ) + return; + if( !s_fader ) + xine_set_param( m_stream, XINE_PARAM_AUDIO_AMP_LEVEL, static_cast<uint>( vol * m_preamp ) ); +} + +void +XineEngine::fadeOut( uint fadeLength, bool* terminate, bool exiting ) +{ + if( m_fadeOutRunning ) //Let us not start another fadeout... + return; + + m_fadeOutRunning = !m_fadeOutRunning; + const bool isPlaying = m_stream && ( xine_get_status( m_stream ) == XINE_STATUS_PLAY ); + const float originalVol = Engine::Base::makeVolumeLogarithmic( m_volume ) * m_preamp; + + // On shutdown, limit fadeout to 3 secs max, so that we don't risk getting killed + const int length = exiting ? QMIN( fadeLength, 3000 ) : fadeLength; + + if( length > 0 && isPlaying ) + { + // fader-class doesn't work in this spot as is, so some parts need to be copied here... (ugly) + uint stepsCount = length < 1000 ? length / 10 : 100; + uint stepSizeUs = (int)( 1000.0 * (float)length / (float)stepsCount ); + + ::usleep( stepSizeUs ); + QTime t; + t.start(); + float mix = 0.0; + while ( mix < 1.0 ) + { + if( *terminate ) break; + + ::usleep( stepSizeUs ); + float vol = Engine::Base::makeVolumeLogarithmic( m_volume ) * m_preamp; + float mix = (float)t.elapsed() / (float)length; + if ( mix > 1.0 ) + { + break; + } + if ( m_stream ) + { + float v = 4.0 * (1.0 - mix) / 3.0; + xine_set_param( m_stream, XINE_PARAM_AUDIO_AMP_LEVEL, (uint)( v < 1.0 ? vol * v : vol ) ); + } + } + } + if( m_fadeOutRunning && m_stream ) + xine_set_param( m_stream, XINE_PARAM_AUDIO_AMP_LEVEL, (uint) originalVol ); + m_fadeOutRunning = !m_fadeOutRunning; +} + +void +XineEngine::setEqualizerEnabled( bool enable ) +{ + if ( !m_stream ) + return; + + m_equalizerEnabled = enable; + + if( !enable ) { + QValueList<int> gains; + for( uint x = 0; x < 10; x++ ) + gains << -101; // sets eq gains to zero. + + setEqualizerParameters( 0, gains ); + } +} + +/* + sets the eq params for xine engine - have to rescale eq params to fitting range (adapted from kaffeine and xfmedia) + + preamp + pre: (-100..100) + post: (0.1..1.9) - this is not really a preamp but we use the xine preamp parameter for our normal volume. so we make a postamp. + + gains + pre: (-100..100) + post: (1..200) - (1 = down, 100 = middle, 200 = up, 0 = off) + */ +void +XineEngine::setEqualizerParameters( int preamp, const QValueList<int> &gains ) +{ + if ( !m_stream ) + return; + + m_equalizerGains = gains; + m_intPreamp = preamp; + QValueList<int>::ConstIterator it = gains.begin(); + + xine_set_param( m_stream, XINE_PARAM_EQ_30HZ, int( (*it )*0.995 + 100 ) ); + xine_set_param( m_stream, XINE_PARAM_EQ_60HZ, int( (*++it)*0.995 + 100 ) ); + xine_set_param( m_stream, XINE_PARAM_EQ_125HZ, int( (*++it)*0.995 + 100 ) ); + xine_set_param( m_stream, XINE_PARAM_EQ_250HZ, int( (*++it)*0.995 + 100 ) ); + xine_set_param( m_stream, XINE_PARAM_EQ_500HZ, int( (*++it)*0.995 + 100 ) ); + xine_set_param( m_stream, XINE_PARAM_EQ_1000HZ, int( (*++it)*0.995 + 100 ) ); + xine_set_param( m_stream, XINE_PARAM_EQ_2000HZ, int( (*++it)*0.995 + 100 ) ); + xine_set_param( m_stream, XINE_PARAM_EQ_4000HZ, int( (*++it)*0.995 + 100 ) ); + xine_set_param( m_stream, XINE_PARAM_EQ_8000HZ, int( (*++it)*0.995 + 100 ) ); + xine_set_param( m_stream, XINE_PARAM_EQ_16000HZ, int( (*++it)*0.995 + 100 ) ); + + m_preamp = ( preamp - 0.1 * preamp + 100 ) / 100.0; + setVolume( m_volume ); +} + +bool +XineEngine::canDecode( const KURL &url ) const +{ + static QStringList list; + if(list.isEmpty()) + { + char* exts = xine_get_file_extensions( m_xine ); + list = QStringList::split( ' ', exts ); + free( exts ); exts = NULL; + //images + list.remove("png"); + list.remove("jpg"); + list.remove("jpeg"); + list.remove("gif"); + list.remove("ilbm"); + list.remove("iff"); + //subtitles + list.remove("asc"); + list.remove("txt"); + list.remove("sub"); + list.remove("srt"); + list.remove("smi"); + list.remove("ssa"); +//HACK we also check for m4a because xine plays them but +//for some reason doesn't return the extension + if(!list.contains("m4a")) + list << "m4a"; + } + + if (url.protocol() == "cdda") + // play audio CDs pls + return true; + + QString path = url.path(); + + // partial downloads from Konqi and other browsers + // tend to have a .part extension + if (path.endsWith( ".part" )) + path = path.left( path.length() - 5 ); + + const QString ext = path.mid( path.findRev( '.' ) + 1 ).lower(); + + return list.contains( ext ); +} + +const Engine::Scope& +XineEngine::scope() +{ + if( !m_post || !m_stream || xine_get_status( m_stream ) != XINE_STATUS_PLAY ) + return m_scope; + + MyNode* const myList = scope_plugin_list( m_post ); + metronom_t* const myMetronom = scope_plugin_metronom( m_post ); + const int myChannels = scope_plugin_channels( m_post ); + int scopeidx = 0; + + if (myChannels > 2) + return m_scope; + + //prune the buffer list and update m_currentVpts + timerEvent( 0 ); + + for( int n, frame = 0; frame < 512; ) + { + MyNode *best_node = 0; + + for( MyNode *node = myList->next; node != myList; node = node->next, Log::bufferCount++ ) + if( node->vpts <= m_currentVpts && (!best_node || node->vpts > best_node->vpts) ) + best_node = node; + + if( !best_node || best_node->vpts_end < m_currentVpts ) { + Log::noSuitableBuffer++; break; } + + int64_t + diff = m_currentVpts; + diff -= best_node->vpts; + diff *= 1<<16; + diff /= myMetronom->pts_per_smpls; + + const int16_t* + data16 = best_node->mem; + data16 += diff; + + diff += diff % myChannels; //important correction to ensure we don't overflow the buffer + diff /= myChannels; //use units of frames, not samples + + //calculate the number of available samples in this buffer + n = best_node->num_frames; + n -= diff; + n += frame; //clipping for # of frames we need + + if( n > 512 ) + n = 512; //we don't want more than 512 frames + + for( int a, c; frame < n; ++frame, data16 += myChannels ) { + for( a = c = 0; c < myChannels; ++c ) + { + // we now give interleaved pcm to the scope + m_scope[scopeidx++] = data16[c]; + if (myChannels == 1) // duplicate mono samples + m_scope[scopeidx++] = data16[c]; + } + } + + m_currentVpts = best_node->vpts_end; + m_currentVpts++; //FIXME needs to be done for some reason, or you get situations where it uses same buffer again and again + } + + Log::scopeCallCount++; + + return m_scope; +} + +void +XineEngine::timerEvent( QTimerEvent* ) +{ + if ( !m_stream ) + return; + + //here we prune the buffer list regularly + + MyNode *myList = scope_plugin_list( m_post ); + + if ( ! myList ) return; + + //we operate on a subset of the list for thread-safety + MyNode * const first_node = myList->next; + MyNode const * const list_end = myList; + + m_currentVpts = (xine_get_status( m_stream ) == XINE_STATUS_PLAY) + ? xine_get_current_vpts( m_stream ) + : LLONG_MAX; //if state is not playing OR paused, empty the list + //: std::numeric_limits<int64_t>::max(); //TODO don't support crappy gcc 2.95 + + for( MyNode *prev = first_node, *node = first_node->next; node != list_end; node = node->next ) + { + //we never delete first_node + //this maintains thread-safety + if( node->vpts_end < m_currentVpts ) { + prev->next = node->next; + + free( node->mem ); + free( node ); + + node = prev; + } + + prev = node; + } +} + +Amarok::PluginConfig* +XineEngine::configure() const +{ + XineConfigDialog* xcf = new XineConfigDialog( m_xine ); + connect(xcf, SIGNAL( settingsSaved() ), this, SLOT( configChanged() )); + connect(this, SIGNAL( resetConfig(xine_t*) ), xcf, SLOT( reset(xine_t*) )); + return xcf; +} + +void +XineEngine::customEvent( QCustomEvent *e ) +{ + #define message static_cast<QString*>(e->data()) + + switch( e->type() ) + { + case 3000: //XINE_EVENT_UI_PLAYBACK_FINISHED + emit trackEnded(); + break; + + case 3001: + emit infoMessage( (*message).arg( m_url.prettyURL() ) ); + delete message; + break; + + case 3002: + emit statusText( *message ); + delete message; + break; + + case 3003: { //meta info has changed + debug() << "Metadata received." << endl; + const Engine::SimpleMetaBundle bundle = fetchMetaData(); + m_currentBundle = bundle; + emit metaData( bundle ); + } break; + + case 3004: + emit statusText( i18n("Redirecting to: ").arg( *message ) ); + load( KURL( *message ), false ); + play(); + delete message; + break; + case 3005: + emit lastFmTrackChange(); + break; + default: + ; + } + + #undef message +} +//SLOT +void XineEngine::configChanged() +{ + //reset xine to load new audio plugin + if( m_currentAudioPlugin != XineCfg::outputPlugin() ) + { + stop(); + xine_config_save( m_xine, configPath() ); + if( m_stream ) xine_close( m_stream ); + if( m_eventQueue ) xine_event_dispose_queue( m_eventQueue ); + m_eventQueue = NULL; + if( m_stream ) xine_dispose( m_stream ); + m_stream = NULL; + if( m_audioPort ) xine_close_audio_driver( m_xine, m_audioPort ); + m_audioPort = NULL; + if( m_post ) xine_post_dispose( m_xine, m_post ); + m_post = NULL; + if( m_xine ) xine_exit( m_xine ); + m_xine = NULL; + init(); + setEqualizerEnabled( m_equalizerEnabled ); + if( m_equalizerEnabled ) + setEqualizerParameters( m_intPreamp, m_equalizerGains ); + emit resetConfig(m_xine); + } +} + +//SLOT +void +XineEngine::playlistChanged() +{ + #ifdef XINE_PARAM_EARLY_FINISHED_EVENT + #ifdef XINE_PARAM_GAPLESS_SWITCH + if ( xine_check_version(1,1,1) && !(m_xfadeLength > 0) + && m_url.isLocalFile() && Playlist::instance()->isTrackAfter() ) + { + xine_set_param(m_stream, XINE_PARAM_EARLY_FINISHED_EVENT, 1 ); + debug() << "XINE_PARAM_EARLY_FINISHED_EVENT enabled" << endl; + } + else + { + //we don't want an early finish event if there is no track after the current one + xine_set_param(m_stream, XINE_PARAM_EARLY_FINISHED_EVENT, 0 ); + debug() << "XINE_PARAM_EARLY_FINISHED_EVENT disabled" << endl; + } + #endif + #endif +} + +static time_t last_error_time = 0; // hysteresis on xine errors +static int last_error = XINE_MSG_NO_ERROR; + +void +XineEngine::XineEventListener( void *p, const xine_event_t* xineEvent ) +{ + time_t current; + + if( !p ) return; + + #define xe static_cast<XineEngine*>(p) + + switch( xineEvent->type ) + { + case XINE_EVENT_UI_SET_TITLE: + + debug() << "XINE_EVENT_UI_SET_TITLE\n"; + + QApplication::postEvent( xe, new QCustomEvent( 3003 ) ); + + break; + + case XINE_EVENT_UI_PLAYBACK_FINISHED: + debug() << "XINE_EVENT_UI_PLAYBACK_FINISHED\n"; + + #ifdef XINE_PARAM_GAPLESS_SWITCH + if ( xine_check_version(1,1,1) && xe->m_url.isLocalFile() //Remote media break with gapless + //don't prepare for a track that isn't coming + && Playlist::instance() + && Playlist::instance()->isTrackAfter() + && !AmarokConfig::crossfade() ) + xine_set_param( xe->m_stream, XINE_PARAM_GAPLESS_SWITCH, 1); + #endif + //emit signal from GUI thread + QApplication::postEvent( xe, new QCustomEvent(3000) ); + break; + + case XINE_EVENT_PROGRESS: { + xine_progress_data_t* pd = (xine_progress_data_t*)xineEvent->data; + + QString + msg = "%1 %2%"; + msg = msg.arg( QString::fromUtf8( pd->description ) ) + .arg( KGlobal::locale()->formatNumber( pd->percent, 0 ) ); + + QCustomEvent *e = new QCustomEvent( 3002 ); + e->setData( new QString( msg ) ); + + QApplication::postEvent( xe, e ); + + } break; + + case XINE_EVENT_MRL_REFERENCE: { + /// xine has read the stream and found it actually links to something else + /// so we need to play that instead + + QString message = QString::fromUtf8( static_cast<xine_mrl_reference_data_t*>(xineEvent->data)->mrl ); + QCustomEvent *e = new QCustomEvent( 3004 ); + e->setData( new QString( message ) ); + + QApplication::postEvent( xe, e ); + + } break; + + case XINE_EVENT_UI_MESSAGE: + { + debug() << "message received from xine\n"; + + xine_ui_message_data_t *data = (xine_ui_message_data_t *)xineEvent->data; + QString message; + + switch( data->type ) + { + case XINE_MSG_NO_ERROR: + { + //series of \0 separated strings, terminated with a \0\0 + char str[2000]; + char *p = str; + for( char *msg = data->messages; !(*msg == '\0' && *(msg+1) == '\0'); ++msg, ++p ) + *p = *msg == '\0' ? '\n' : *msg; + *p = '\0'; + + debug() << str << endl; + + break; + } + + case XINE_MSG_ENCRYPTED_SOURCE: + break; + + case XINE_MSG_UNKNOWN_HOST: + message = i18n("The host is unknown for the URL: <i>%1</i>"); goto param; + case XINE_MSG_UNKNOWN_DEVICE: + message = i18n("The device name you specified seems invalid."); goto param; + case XINE_MSG_NETWORK_UNREACHABLE: + message = i18n("The network appears unreachable."); goto param; + case XINE_MSG_AUDIO_OUT_UNAVAILABLE: + message = i18n("Audio output unavailable; the device is busy."); goto param; + case XINE_MSG_CONNECTION_REFUSED: + message = i18n("The connection was refused for the URL: <i>%1</i>"); goto param; + case XINE_MSG_FILE_NOT_FOUND: + message = i18n("xine could not find the URL: <i>%1</i>"); goto param; + case XINE_MSG_PERMISSION_ERROR: + message = i18n("Access was denied for the URL: <i>%1</i>"); goto param; + case XINE_MSG_READ_ERROR: + message = i18n("The source cannot be read for the URL: <i>%1</i>"); goto param; + case XINE_MSG_LIBRARY_LOAD_ERROR: + message = i18n("A problem occurred while loading a library or decoder."); goto param; + + case XINE_MSG_GENERAL_WARNING: + message = i18n("General Warning"); goto explain; + case XINE_MSG_SECURITY: + message = i18n("Security Warning"); goto explain; + default: + message = i18n("Unknown Error"); goto explain; + + + explain: + + // Don't flood the user with error messages + if( (last_error_time + 10) > time( ¤t ) && + data->type == last_error ) + { + last_error_time = current; + return; + } + last_error_time = current; + last_error = data->type; + + if( data->explanation ) + { + message.prepend( "<b>" ); + message += "</b>:<p>"; + message += QString::fromUtf8( (char*)data + data->explanation ); + } + else break; //if no explanation then why bother! + + //FALL THROUGH + + param: + + // Don't flood the user with error messages + if((last_error_time + 10) > time(¤t) && + data->type == last_error) + { + last_error_time = current; + return; + } + last_error_time = current; + last_error = data->type; + + message.prepend( "<p>" ); + message += "<p>"; + + if(data->explanation) + { + message += "xine parameters: <i>"; + message += QString::fromUtf8( (char*)data + data->parameters ); + message += "</i>"; + } + else message += i18n("Sorry, no additional information is available."); + + QApplication::postEvent( xe, new QCustomEvent(QEvent::Type(3001), new QString(message)) ); + } + + } //case + case XINE_EVENT_UI_CHANNELS_CHANGED: //Flameeyes used this for last.fm track changes + QApplication::postEvent( xe, new QCustomEvent(QEvent::Type(3005) ) ); + break; + } //switch + + #undef xe +} + +Engine::SimpleMetaBundle +XineEngine::fetchMetaData() const +{ + Engine::SimpleMetaBundle bundle; + bundle.title = QString::fromUtf8( xine_get_meta_info( m_stream, XINE_META_INFO_TITLE ) ); + bundle.artist = QString::fromUtf8( xine_get_meta_info( m_stream, XINE_META_INFO_ARTIST ) ); + bundle.album = QString::fromUtf8( xine_get_meta_info( m_stream, XINE_META_INFO_ALBUM ) ); + bundle.comment = QString::fromUtf8( xine_get_meta_info( m_stream, XINE_META_INFO_COMMENT ) ); + bundle.genre = QString::fromUtf8( xine_get_meta_info( m_stream, XINE_META_INFO_GENRE ) ); + bundle.bitrate = QString::number( xine_get_stream_info( m_stream, XINE_STREAM_INFO_AUDIO_BITRATE ) / 1000 ); + bundle.samplerate = QString::number( xine_get_stream_info( m_stream, XINE_STREAM_INFO_AUDIO_SAMPLERATE ) ); + bundle.year = QString::fromUtf8( xine_get_meta_info( m_stream, XINE_META_INFO_YEAR ) ); + bundle.tracknr = QString::fromUtf8( xine_get_meta_info( m_stream, XINE_META_INFO_TRACK_NUMBER ) ); + + return bundle; +} + +bool XineEngine::metaDataForUrl(const KURL &url, Engine::SimpleMetaBundle &b) +{ + bool result = false; + xine_stream_t* tmpstream = xine_stream_new(m_xine, NULL, NULL); + if (xine_open(tmpstream, QFile::encodeName(url.url()))) { + QString audioCodec = QString::fromUtf8(xine_get_meta_info(tmpstream, XINE_META_INFO_SYSTEMLAYER)); + + if (audioCodec == "CDDA") { + QString title = QString::fromUtf8( + xine_get_meta_info(tmpstream, XINE_META_INFO_TITLE)); + if ((!title.isNull()) && (!title.isEmpty())) { //no meta info + b.title = title; + b.artist = + QString::fromUtf8( + xine_get_meta_info(tmpstream, XINE_META_INFO_ARTIST)); + b.album = + QString::fromUtf8( + xine_get_meta_info(tmpstream, XINE_META_INFO_ALBUM)); + b.genre = + QString::fromUtf8( + xine_get_meta_info(tmpstream, XINE_META_INFO_GENRE)); + b.year = + QString::fromUtf8( + xine_get_meta_info(tmpstream, XINE_META_INFO_YEAR)); + b.tracknr = + QString::fromUtf8( + xine_get_meta_info(tmpstream, XINE_META_INFO_TRACK_NUMBER)); + if( b.tracknr.isEmpty() ) + b.tracknr = url.filename(); + } else { + b.title = QString(i18n("Track %1")).arg(url.filename()); + b.album = i18n("AudioCD"); + } + } + + if (audioCodec == "CDDA" || audioCodec == "WAV") { + result = true; + int samplerate = xine_get_stream_info( tmpstream, XINE_STREAM_INFO_AUDIO_SAMPLERATE ); + + // xine would provide a XINE_STREAM_INFO_AUDIO_BITRATE, but unfortunately not for CDDA or WAV + // so we calculate the bitrate by our own + int bitsPerSample = xine_get_stream_info( tmpstream, XINE_STREAM_INFO_AUDIO_BITS ); + int nbrChannels = xine_get_stream_info( tmpstream, XINE_STREAM_INFO_AUDIO_CHANNELS ); + int bitrate = (samplerate * bitsPerSample * nbrChannels) / 1000; + + b.bitrate = QString::number(bitrate); + b.samplerate = QString::number(samplerate); + int pos, time, length = 0; + xine_get_pos_length(tmpstream, &pos, &time, &length); + b.length = QString::number(length / 1000); + } + xine_close(tmpstream); + } + xine_dispose(tmpstream); + return result; +} + +bool XineEngine::getAudioCDContents(const QString &device, KURL::List &urls) +{ + char **xine_urls = NULL; + int num; + int i = 0; + + if (!device.isNull()) { + debug() << "xine-engine setting CD Device to: " << device << endl; + xine_cfg_entry_t config; + if (!xine_config_lookup_entry(m_xine, "input.cdda_device", &config)) { + emit statusText(i18n("Failed CD device lookup in xine engine")); + return false; + } + config.str_value = (char *)device.latin1(); + xine_config_update_entry(m_xine, &config); + } + + emit statusText(i18n("Getting AudioCD contents...")); + + xine_urls = xine_get_autoplay_mrls(m_xine, "CD", &num); + + if (xine_urls) { + while (xine_urls[i]) { + urls << KURL(xine_urls[i]); + ++i; + } + } + else emit statusText(i18n("Could not read AudioCD")); + + return true; +} + +bool XineEngine::flushBuffer() +{ + return false; +} + +bool XineEngine::lastFmProxyRequired() +{ + return !( xine_check_version(1,1,9) ); +} + +////////////////////////////////////////////////////////////////////////////// +/// class Fader +////////////////////////////////////////////////////////////////////////////// + +Fader::Fader( XineEngine *engine, uint fadeMs ) + : QObject( engine ) + , QThread() + , m_engine( engine ) + , m_xine( engine->m_xine ) + , m_decrease( engine->m_stream ) + , m_increase( 0 ) + , m_port( engine->m_audioPort ) + , m_post( engine->m_post ) + , m_fadeLength( fadeMs ) + , m_paused( false ) + , m_terminated( false ) +{ + DEBUG_BLOCK + + if( engine->makeNewStream() ) + { + m_increase = engine->m_stream; + + xine_set_param( m_increase, XINE_PARAM_AUDIO_AMP_LEVEL, 0 ); + } + else { + s_fader = 0; + deleteLater(); + } +} + +Fader::~Fader() +{ + DEBUG_BLOCK + + wait(); + + xine_close( m_decrease ); + xine_dispose( m_decrease ); + xine_close_audio_driver( m_xine, m_port ); + if( m_post ) xine_post_dispose( m_xine, m_post ); + + if( !m_engine->m_stopFader ) + m_engine->setVolume( m_engine->volume() ); + + m_engine->m_stopFader = false; + s_fader = 0; +} + +void +Fader::run() +{ + DEBUG_BLOCK + + // do a volume change in 100 steps (or every 10ms) + uint stepsCount = m_fadeLength < 1000 ? m_fadeLength / 10 : 100; + uint stepSizeUs = (int)( 1000.0 * (float)m_fadeLength / (float)stepsCount ); + + float mix = 0.0; + float elapsedUs = 0.0; + while ( mix < 1.0 ) + { + if ( m_terminated ) + break; + // sleep a constant amount of time + QThread::usleep( stepSizeUs ); + + if ( m_paused ) + continue; + + elapsedUs += stepSizeUs; + + // get volume (amarok main * equalizer preamp) + float vol = Engine::Base::makeVolumeLogarithmic( m_engine->m_volume ) * m_engine->m_preamp; + + // compute the mix factor as the percentage of time spent since fade begun + float mix = (elapsedUs / 1000.0) / (float)m_fadeLength; + if ( mix > 1.0 ) + { + if ( m_increase ) + xine_set_param( m_increase, XINE_PARAM_AUDIO_AMP_LEVEL, (uint)vol ); + break; + } + + // change volume of streams (using dj-like cross-fade profile) + if ( m_decrease ) + { + //xine_set_param( m_decrease, XINE_PARAM_AUDIO_AMP_LEVEL, (uint)(vol * (1.0 - mix)) ); // linear + float v = 4.0 * (1.0 - mix) / 3.0; + xine_set_param( m_decrease, XINE_PARAM_AUDIO_AMP_LEVEL, (uint)( v < 1.0 ? vol * v : vol ) ); + } + if ( m_increase ) + { + //xine_set_param( m_increase, XINE_PARAM_AUDIO_AMP_LEVEL, (uint)(vol * mix) ); //linear + float v = 4.0 * mix / 3.0; + xine_set_param( m_increase, XINE_PARAM_AUDIO_AMP_LEVEL, (uint)( v < 1.0 ? vol * v : vol ) ); + } + } + + //stop using cpu! + xine_stop( m_decrease ); + + deleteLater(); +} + +void +Fader::pause() +{ + m_paused = true; +} + +void +Fader::resume() +{ + m_paused = false; +} + +void +Fader::finish() +{ + DEBUG_BLOCK + m_terminated = true; +} + +////////////////////////////////////////////////////////////////////////////// +/// class OutFader +////////////////////////////////////////////////////////////////////////////// + +OutFader::OutFader( XineEngine *engine, uint fadeLength ) + : QObject( engine ) + , QThread() + , m_engine( engine ) + , m_terminated( false ) + , m_fadeLength( fadeLength ) +{ + DEBUG_BLOCK +} + +OutFader::~OutFader() +{ + DEBUG_BLOCK + + wait(); + + s_outfader = 0; +} + +void +OutFader::run() +{ + DEBUG_BLOCK + + m_engine->fadeOut( m_fadeLength, &m_terminated ); + + xine_stop( m_engine->m_stream ); + xine_close( m_engine->m_stream ); + xine_set_param( m_engine->m_stream, XINE_PARAM_AUDIO_CLOSE_DEVICE, 1); + + deleteLater(); +} + +void +OutFader::finish() +{ + DEBUG_BLOCK + m_terminated = true; +} + +#include "xine-engine.moc" diff --git a/amarok/src/engine/xine/xine-engine.h b/amarok/src/engine/xine/xine-engine.h new file mode 100644 index 00000000..fb4d3ecb --- /dev/null +++ b/amarok/src/engine/xine/xine-engine.h @@ -0,0 +1,141 @@ +/*************************************************************************** + * Copyright (C) 2004,5 Max Howell <max.howell@methylblue.com> * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef XINE_ENGINE_H +#define XINE_ENGINE_H + +#include "enginebase.h" +#include <qthread.h> + +extern "C" +{ + #include <sys/types.h> + #include <xine.h> +} + +class XineConfigDialog; + +class XineEngine : public Engine::Base +{ + Q_OBJECT + + friend class Fader; + friend class OutFader; + + ~XineEngine(); + + virtual bool init(); + virtual bool canDecode( const KURL& ) const; + virtual bool load( const KURL &url, bool stream ); + virtual bool play( uint = 0 ); + virtual void stop(); + virtual void pause(); + virtual void unpause(); + virtual uint position() const; + virtual uint length() const; + virtual void seek( uint ); + + virtual bool metaDataForUrl(const KURL &url, Engine::SimpleMetaBundle &b); + virtual bool getAudioCDContents(const QString &device, KURL::List &urls); + virtual bool flushBuffer(); + + virtual Engine::State state() const; + virtual const Engine::Scope &scope(); + + virtual Amarok::PluginConfig *configure() const; + virtual void setEqualizerEnabled( bool ); + virtual void setEqualizerParameters( int preamp, const QValueList<int>& ); + virtual void setVolumeSW( uint ); + virtual void fadeOut( uint fadeLength, bool* terminate, bool exiting = false ); + + static void XineEventListener( void*, const xine_event_t* ); + virtual void customEvent( QCustomEvent* ); + virtual void timerEvent( QTimerEvent* ); + + virtual void playlistChanged(); + + Engine::SimpleMetaBundle fetchMetaData() const; + + virtual bool lastFmProxyRequired(); + + bool makeNewStream(); + bool ensureStream(); + + void determineAndShowErrorMessage(); //call after failure to load/play + + xine_t *m_xine; + xine_stream_t *m_stream; + xine_audio_port_t *m_audioPort; + xine_event_queue_t *m_eventQueue; + xine_post_t *m_post; + + int64_t m_currentVpts; + float m_preamp; + + bool m_stopFader; + bool m_fadeOutRunning; + + QString m_currentAudioPlugin; //to see if audio plugin has been changed + XineConfigDialog* m_configDialog; + //need to save these for when the audio plugin is changed and xine reloaded + bool m_equalizerEnabled; + int m_intPreamp; + QValueList<int> m_equalizerGains; + + mutable Engine::SimpleMetaBundle m_currentBundle; + +private slots: + void configChanged(); + +public: + XineEngine(); + +signals: + void resetConfig(xine_t *xine); +}; + +class Fader : public QObject, public QThread +{ + XineEngine *m_engine; + xine_t *m_xine; + xine_stream_t *m_decrease; + xine_stream_t *m_increase; + xine_audio_port_t *m_port; + xine_post_t *m_post; + uint m_fadeLength; + bool m_paused; + bool m_terminated; + + virtual void run(); + +public: + Fader( XineEngine *, uint fadeLengthMs ); + ~Fader(); + void pause(); + void resume(); + void finish(); +}; + +class OutFader : public QObject, public QThread +{ + XineEngine *m_engine; + bool m_terminated; + uint m_fadeLength; + + virtual void run(); + +public: + OutFader( XineEngine *, uint fadeLengthMs ); + ~OutFader(); + + void finish(); +}; + +#endif diff --git a/amarok/src/engine/xine/xine-scope.c b/amarok/src/engine/xine/xine-scope.c new file mode 100644 index 00000000..8ff422e9 --- /dev/null +++ b/amarok/src/engine/xine/xine-scope.c @@ -0,0 +1,186 @@ +/* Author: Max Howell <max.howell@methylblue.com>, (C) 2004 + Copyright: See COPYING file that comes with this distribution + + This has to be a c file or for some reason it won't link! (GCC 3.4.1) +*/ + +/* gcc doesn't like inline for me */ +#define inline +/* need access to port_ticket */ +#define XINE_ENGINE_INTERNAL + +#include "xine-scope.h" +#include <xine/post.h> +#include <xine/xine_internal.h> + +typedef struct scope_plugin_s scope_plugin_t; + +struct scope_plugin_s +{ + post_plugin_t post; + + metronom_t metronom; + int channels; + MyNode *list; +}; + +/************************* + * post plugin functions * + *************************/ + +static int +scope_port_open( xine_audio_port_t *port_gen, xine_stream_t *stream, uint32_t bits, uint32_t rate, int mode ) +{ + #define port ((post_audio_port_t*)port_gen) + #define this ((scope_plugin_t*)((post_audio_port_t*)port_gen)->post) + + _x_post_rewire( (post_plugin_t*)port->post ); + _x_post_inc_usage( port ); + + port->stream = stream; + port->bits = bits; + port->rate = rate; + port->mode = mode; + + this->channels = _x_ao_mode2channels( mode ); + + return port->original_port->open( port->original_port, stream, bits, rate, mode ); +} + +static void +scope_port_close( xine_audio_port_t *port_gen, xine_stream_t *stream ) +{ + MyNode *node; + + /* ensure the buffers are deleted during the next XineEngine::timerEvent() */ + for( node = this->list->next; node != this->list; node = node->next ) + node->vpts = node->vpts_end = -1; + + port->stream = NULL; + port->original_port->close( port->original_port, stream ); + + _x_post_dec_usage( port ); +} + +static void +scope_port_put_buffer( xine_audio_port_t *port_gen, audio_buffer_t *buf, xine_stream_t *stream ) +{ +/* FIXME With 8-bit samples the scope won't work correctly. For a special 8-bit code path, + the sample size could be checked like this: if( port->bits == 8 ) */ + + const int num_samples = buf->num_frames * this->channels; + metronom_t *myMetronom = &this->metronom; + MyNode *new_node; + + /* I keep my own metronom because xine wouldn't for some reason */ + memcpy( &this->metronom, stream->metronom, sizeof(metronom_t) ); + + new_node = malloc( sizeof(MyNode) ); + new_node->vpts = myMetronom->got_audio_samples( myMetronom, buf->vpts, buf->num_frames ); + new_node->num_frames = buf->num_frames; + new_node->mem = malloc( num_samples * 2 ); + memcpy( new_node->mem, buf->mem, num_samples * 2 ); + + { + int64_t + K = myMetronom->pts_per_smpls; /*smpls = 1<<16 samples*/ + K *= num_samples; + K /= (1<<16); + K += new_node->vpts; + + new_node->vpts_end = K; + } + + port->original_port->put_buffer( port->original_port, buf, stream ); + + /* finally we should append the current buffer to the list + * this is thread-safe due to the way we handle the list in the GUI thread */ + new_node->next = this->list->next; + this->list->next = new_node; + + #undef port + #undef this +} + +static void +scope_dispose( post_plugin_t *this ) +{ + MyNode *list = ((scope_plugin_t*)this)->list; + MyNode *prev; + MyNode *node = list; + + /* Free all elements of the list (a ring buffer) */ + do { + prev = node->next; + + free( node->mem ); + free( node ); + + node = prev; + } + while( node != list ); + + + free( this ); +} + + +/************************ + * plugin init function * + ************************/ + +xine_post_t* +scope_plugin_new( xine_t *xine, xine_audio_port_t *audio_target ) +{ + scope_plugin_t *scope_plugin = xine_xmalloc( sizeof(scope_plugin_t) ); + post_plugin_t *post_plugin = (post_plugin_t*)scope_plugin; + + { + post_in_t *input; + post_out_t *output; + post_audio_port_t *port; + + _x_post_init( post_plugin, 1, 0 ); + + port = _x_post_intercept_audio_port( post_plugin, audio_target, &input, &output ); + port->new_port.open = scope_port_open; + port->new_port.close = scope_port_close; + port->new_port.put_buffer = scope_port_put_buffer; + + post_plugin->xine_post.audio_input[0] = &port->new_port; + post_plugin->xine_post.type = PLUGIN_POST; + + post_plugin->dispose = scope_dispose; + } + + /* code is straight from xine_init_post() + can't use that function as it only dlopens the plugins + and our plugin is statically linked in */ + + post_plugin->running_ticket = xine->port_ticket; + post_plugin->xine = xine; + + /* scope_plugin_t init */ + scope_plugin->list = xine_xmalloc( sizeof(MyNode) ); + scope_plugin->list->next = scope_plugin->list; + + return &post_plugin->xine_post; +} + +MyNode* +scope_plugin_list( void *post ) +{ + return ((scope_plugin_t*)post)->list; +} + +int +scope_plugin_channels( void *post ) +{ + return ((scope_plugin_t*)post)->channels; +} + +metronom_t* +scope_plugin_metronom( void *post ) +{ + return &((scope_plugin_t*)post)->metronom; +} diff --git a/amarok/src/engine/xine/xine-scope.h b/amarok/src/engine/xine/xine-scope.h new file mode 100644 index 00000000..ce2d7ee7 --- /dev/null +++ b/amarok/src/engine/xine/xine-scope.h @@ -0,0 +1,50 @@ +/* Author: Max Howell <max.howell@methylblue.com>, (C) 2004 + Copyright: See COPYING file that comes with this distribution + + This has to be a c file or for some reason it won't link! (GCC 3.4.1) +*/ + +#ifndef XINESCOPE_H +#define XINESCOPE_H + +/* need access to some stuff for scope time stamping */ +#define METRONOM_INTERNAL + +#include <sys/types.h> +#include <xine/metronom.h> + +typedef struct my_node_s MyNode; + +struct my_node_s +{ + MyNode *next; + int16_t *mem; + int num_frames; + int64_t vpts; + int64_t vpts_end; +}; + +#ifdef __cplusplus +extern "C" +{ +#endif + xine_post_t* + scope_plugin_new( xine_t*, xine_audio_port_t* ); + + /* we sacrifice type-safety here because some GCCs appear broken + * and choke on redefining the xine_post_t typedef + */ + + MyNode* + scope_plugin_list( void* ); + + int + scope_plugin_channels( void* ); + + metronom_t* + scope_plugin_metronom( void* ); +#ifdef __cplusplus +} +#endif + +#endif diff --git a/amarok/src/engine/xine/xinecfg.kcfg b/amarok/src/engine/xine/xinecfg.kcfg new file mode 100644 index 00000000..8f825f52 --- /dev/null +++ b/amarok/src/engine/xine/xinecfg.kcfg @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- Author: Ian Monroe, ian@monroe.nu --> +<kcfg xmlns="http://www.kde.org/standards/kcfg/1.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://www.kde.org/standards/kcfg/1.0 + http://www.kde.org/standards/kcfg/1.0/kcfg.xsd" > + <kcfgfile name="amarokrc"/> + + <group name="Xine-Engine"> + <entry key="Output Plugin" type="String"> + <label>Sound output method to use</label> + <whatsthis>Select the sound output plugin.</whatsthis> + <default>auto</default> + </entry> + <entry key="Custom Device" type="Bool"> + <label>Enable a custom device</label> + <whatsthis>If selected, enables the setting of a custom audio device. Otherwise the default is used.</whatsthis> + <default>false</default> + </entry> + </group> + +</kcfg> + diff --git a/amarok/src/engine/xine/xinecfg.kcfgc b/amarok/src/engine/xine/xinecfg.kcfgc new file mode 100644 index 00000000..127cdf81 --- /dev/null +++ b/amarok/src/engine/xine/xinecfg.kcfgc @@ -0,0 +1,7 @@ +# Code generation options for kconfig_compiler +File=xinecfg.kcfg +ClassName=XineCfg +Singleton=true +Mutators=true +MemberVariables=private + diff --git a/amarok/src/engine/xine/xineconfigbase.ui b/amarok/src/engine/xine/xineconfigbase.ui new file mode 100644 index 00000000..c3925ec0 --- /dev/null +++ b/amarok/src/engine/xine/xineconfigbase.ui @@ -0,0 +1,506 @@ +<!DOCTYPE UI><UI version="3.3" stdsetdef="1"> +<class>XineConfigBase</class> +<widget class="QWidget"> + <property name="name"> + <cstring>XineConfigBase</cstring> + </property> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>437</width> + <height>458</height> + </rect> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>5</hsizetype> + <vsizetype>5</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="caption"> + <string>Xine Configure</string> + </property> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <property name="margin"> + <number>0</number> + </property> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>layout11</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLabel"> + <property name="name"> + <cstring>xineLogo</cstring> + </property> + <property name="paletteForegroundColor"> + <color> + <red>0</red> + <green>0</green> + <blue>0</blue> + </color> + </property> + <property name="paletteBackgroundColor"> + <color> + <red>255</red> + <green>255</green> + <blue>255</blue> + </color> + </property> + <property name="frameShape"> + <enum>StyledPanel</enum> + </property> + <property name="frameShadow"> + <enum>Raised</enum> + </property> + <property name="margin"> + <number>1</number> + </property> + <property name="text"> + <string></string> + </property> + <property name="alignment"> + <set>AlignCenter</set> + </property> + </widget> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>layout10</cstring> + </property> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>layout10</cstring> + </property> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>layout2</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLabel"> + <property name="name"> + <cstring>textLabel2</cstring> + </property> + <property name="text"> + <string>&Output plugin:</string> + </property> + <property name="buddy" stdset="0"> + <cstring>deviceComboBox</cstring> + </property> + </widget> + <widget class="KComboBox"> + <property name="name"> + <cstring>deviceComboBox</cstring> + </property> + </widget> + </hbox> + </widget> + <widget class="QLabel"> + <property name="name"> + <cstring>textLabel3</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>1</hsizetype> + <vsizetype>1</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Sound device may be modified after the output plugin has been changed to ALSA or OSS.</string> + </property> + <property name="alignment"> + <set>WordBreak|AlignVCenter</set> + </property> + </widget> + <widget class="QGroupBox"> + <property name="name"> + <cstring>alsaGroupBox</cstring> + </property> + <property name="title"> + <string>ALSA Device Configuration</string> + </property> + <grid> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLabel" row="0" column="0"> + <property name="name"> + <cstring>textLabel2_3</cstring> + </property> + <property name="text"> + <string>&Mono:</string> + </property> + <property name="buddy" stdset="0"> + <cstring>monoLineEdit</cstring> + </property> + </widget> + <widget class="QLabel" row="1" column="0"> + <property name="name"> + <cstring>textLabel3_3</cstring> + </property> + <property name="text"> + <string>&Stereo:</string> + </property> + <property name="buddy" stdset="0"> + <cstring>stereoLineEdit</cstring> + </property> + </widget> + <widget class="QLabel" row="0" column="2"> + <property name="name"> + <cstring>textLabel4_2</cstring> + </property> + <property name="text"> + <string>&4 Channels:</string> + </property> + <property name="buddy" stdset="0"> + <cstring>chan4LineEdit</cstring> + </property> + </widget> + <widget class="QLabel" row="1" column="2"> + <property name="name"> + <cstring>chan5Label</cstring> + </property> + <property name="text"> + <string>&6 Channels:</string> + </property> + <property name="buddy" stdset="0"> + <cstring>chan5LineEdit</cstring> + </property> + </widget> + <widget class="QLineEdit" row="0" column="1"> + <property name="name"> + <cstring>monoLineEdit</cstring> + </property> + </widget> + <widget class="QLineEdit" row="1" column="1"> + <property name="name"> + <cstring>stereoLineEdit</cstring> + </property> + </widget> + <widget class="QLineEdit" row="0" column="3"> + <property name="name"> + <cstring>chan4LineEdit</cstring> + </property> + </widget> + <widget class="QLineEdit" row="1" column="3"> + <property name="name"> + <cstring>chan5LineEdit</cstring> + </property> + </widget> + </grid> + </widget> + <widget class="QGroupBox"> + <property name="name"> + <cstring>ossGroupBox</cstring> + </property> + <property name="title"> + <string>OSS Device Configuration</string> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLabel"> + <property name="name"> + <cstring>textLabel1</cstring> + </property> + <property name="text"> + <string>&Device:</string> + </property> + <property name="buddy" stdset="0"> + <cstring>ossDeviceComboBox</cstring> + </property> + </widget> + <widget class="QComboBox"> + <property name="name"> + <cstring>ossDeviceComboBox</cstring> + </property> + </widget> + </hbox> + </widget> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>layout5</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLabel"> + <property name="name"> + <cstring>textLabel1_2</cstring> + </property> + <property name="text"> + <string>Speaker &arrangement:</string> + </property> + <property name="buddy" stdset="0"> + <cstring>speakerComboBox</cstring> + </property> + </widget> + <widget class="QComboBox"> + <property name="name"> + <cstring>speakerComboBox</cstring> + </property> + </widget> + </hbox> + </widget> + <widget class="QGroupBox"> + <property name="name"> + <cstring>groupBox1</cstring> + </property> + <property name="title"> + <string>HTTP Proxy for Streaming</string> + </property> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>layout4</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLabel"> + <property name="name"> + <cstring>textLabel2_2</cstring> + </property> + <property name="text"> + <string>&Host:</string> + </property> + <property name="buddy" stdset="0"> + <cstring>hostLineEdit</cstring> + </property> + </widget> + <widget class="QLineEdit"> + <property name="name"> + <cstring>hostLineEdit</cstring> + </property> + </widget> + <widget class="KIntSpinBox"> + <property name="name"> + <cstring>portIntBox</cstring> + </property> + <property name="maxValue"> + <number>65534</number> + </property> + <property name="minValue"> + <number>1</number> + </property> + <property name="value"> + <number>60000</number> + </property> + </widget> + </hbox> + </widget> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>layout2</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLabel"> + <property name="name"> + <cstring>textLabel3_2</cstring> + </property> + <property name="text"> + <string>&User:</string> + </property> + <property name="buddy" stdset="0"> + <cstring>userLineEdit</cstring> + </property> + </widget> + <widget class="QLineEdit"> + <property name="name"> + <cstring>userLineEdit</cstring> + </property> + </widget> + <widget class="QLabel"> + <property name="name"> + <cstring>textLabel4</cstring> + </property> + <property name="text"> + <string>&Password:</string> + </property> + <property name="buddy" stdset="0"> + <cstring>passLineEdit</cstring> + </property> + </widget> + <widget class="QLineEdit"> + <property name="name"> + <cstring>passLineEdit</cstring> + </property> + <property name="echoMode"> + <enum>Password</enum> + </property> + </widget> + </hbox> + </widget> + </vbox> + </widget> + <widget class="QGroupBox"> + <property name="name"> + <cstring>audiocdGroupBox</cstring> + </property> + <property name="title"> + <string>Audio CD Configuration</string> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>layout11</cstring> + </property> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLabel"> + <property name="name"> + <cstring>textLabel2_3_2</cstring> + </property> + <property name="text"> + <string>Default device:</string> + </property> + <property name="buddy" stdset="0"> + <cstring>monoLineEdit</cstring> + </property> + </widget> + <widget class="QLabel"> + <property name="name"> + <cstring>textLabel2_2_2</cstring> + </property> + <property name="text"> + <string>CDDB Server:</string> + </property> + <property name="buddy" stdset="0"> + <cstring>hostLineEdit</cstring> + </property> + </widget> + <widget class="QLabel"> + <property name="name"> + <cstring>textLabel2_3_2_2</cstring> + </property> + <property name="text"> + <string>CDDB Cache dir:</string> + </property> + <property name="buddy" stdset="0"> + <cstring>monoLineEdit</cstring> + </property> + </widget> + </vbox> + </widget> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>layout12</cstring> + </property> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLineEdit"> + <property name="name"> + <cstring>audiocd_device</cstring> + </property> + <property name="text"> + <string></string> + </property> + </widget> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>layout10</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLineEdit"> + <property name="name"> + <cstring>cddb_server</cstring> + </property> + </widget> + <widget class="KIntSpinBox"> + <property name="name"> + <cstring>cddb_port</cstring> + </property> + <property name="maxValue"> + <number>65534</number> + </property> + <property name="minValue"> + <number>1</number> + </property> + <property name="value"> + <number>60000</number> + </property> + </widget> + </hbox> + </widget> + <widget class="QLineEdit"> + <property name="name"> + <cstring>cddb_cache_dir</cstring> + </property> + <property name="text"> + <string></string> + </property> + </widget> + </vbox> + </widget> + </hbox> + </widget> + </vbox> + </widget> + <spacer> + <property name="name"> + <cstring>spacer3</cstring> + </property> + <property name="orientation"> + <enum>Vertical</enum> + </property> + <property name="sizeType"> + <enum>Expanding</enum> + </property> + <property name="sizeHint"> + <size> + <width>20</width> + <height>16</height> + </size> + </property> + </spacer> + </vbox> + </widget> + </hbox> + </widget> + </vbox> +</widget> +<includes> + <include location="local" impldecl="in declaration">plugin/pluginconfig.h</include> +</includes> +<layoutdefaults spacing="6" margin="11"/> +</UI> diff --git a/amarok/src/engine/yauap/Makefile.am b/amarok/src/engine/yauap/Makefile.am new file mode 100644 index 00000000..54538d6b --- /dev/null +++ b/amarok/src/engine/yauap/Makefile.am @@ -0,0 +1,29 @@ +kde_module_LTLIBRARIES = \ + libamarok_yauap-engine_plugin.la + +INCLUDES = \ + -I$(top_srcdir)/amarok/src/plugin \ + -I$(top_srcdir)/amarok/src \ + $(CFLAGS_YAUAP) \ + $(all_includes) + + + +libamarok_yauap_engine_plugin_la_LIBADD = \ + $(top_builddir)/amarok/src/libamarok.la \ + $(top_builddir)/amarok/src/plugin/libplugin.la \ + $(LIB_YAUAP)\ + $(LIB_KDECORE) $(LIB_QT) + + +libamarok_yauap_engine_plugin_la_SOURCES = \ + yauap-engine.cpp + +libamarok_yauap_engine_plugin_la_LDFLAGS = \ + $(KDE_PLUGIN) \ + $(all_libraries) + +METASOURCES = AUTO + +kde_services_DATA = \ + amarok_yauap-engine_plugin.desktop diff --git a/amarok/src/engine/yauap/amarok_yauap-engine_plugin.desktop b/amarok/src/engine/yauap/amarok_yauap-engine_plugin.desktop new file mode 100644 index 00000000..d420385b --- /dev/null +++ b/amarok/src/engine/yauap/amarok_yauap-engine_plugin.desktop @@ -0,0 +1,109 @@ +[Desktop Entry] +Encoding=UTF-8 +Type=Service +Name=yauap engine +Name[af]=yauap enjin +Name[bg]=Yauap +Name[bn]=yauap ইঞ্জিন +Name[ca]=Motor yauap +Name[cs]=yauap +Name[da]=Yauap-grænseflade +Name[de]=Yauap +Name[el]=μηχανή yauap +Name[eo]=yauap ilo +Name[es]=motor yauap +Name[et]=yauap mootor +Name[fa]=موتور yauap +Name[fi]=yauap +Name[fr]=Moteur yauap +Name[ga]=inneall yauap +Name[hu]=Yauap alrendszer +Name[is]=yauap vél +Name[it]=Motore yauap +Name[ja]=yauap エンジン +Name[ka]=yauap ძრავი +Name[km]=ម៉ាស៊ីន yauap +Name[lt]=yauap variklis +Name[mk]=yauap-машина +Name[ms]=Enjin yauap +Name[nb]=yauap-motor +Name[nds]=Yauap +Name[ne]=yauap इन्जिन +Name[nl]=yauap-engine +Name[nn]=yauap-motor +Name[pa]=yauap ਇੰਜਣ +Name[pl]=moduł yauap +Name[pt]=Motor yauap +Name[pt_BR]=Mecanismo Yauap +Name[se]=yauap-mohtor +Name[sr]=Мотора yauap +Name[sr@Latn]=Motora yauap +Name[sv]=Yauap-gränssnitt +Name[tr]=yauap motoru +Name[uk]=Рушій yauap +Name[uz]=yauap tizimi +Name[uz@cyrillic]=yauap тизими +Name[wa]=Éndjin yauap +Name[zh_CN]=yauap 引擎 +Name[zh_TW]=yauap 引擎 +X-KDE-Library=libamarok_yauap-engine_plugin +Comment=Plugin for Amarok +Comment[af]=Inprop module vir Amarok +Comment[ar]= قابس ( برنامج مضاف الى) AmaroK +Comment[bg]=Приставка за Amarok +Comment[bn]=আমারক-এর জন্য প্লাগিন +Comment[br]=Lugent evit Amarok +Comment[ca]=Connector per l'Amarok +Comment[cs]=Modul pro AmaroK +Comment[de]=Modul für Amarok +Comment[el]=Πρόσθετο για το AmaroK +Comment[eo]=Kromaĵo por Amarok +Comment[es]=Extensión para Amarok +Comment[et]=Amaroki plugin +Comment[fa]=وصله برای amaroK +Comment[fi]=Amarok-liitännäinen +Comment[fr]=Module pour Amarok +Comment[ga]=Breiseán AmaroK +Comment[gl]=Extensión para Amarok +Comment[hu]=Bővítőmodul az Amarokhoz +Comment[is]=Íforrit fyrir Amarok +Comment[it]=Plugin per Amarok +Comment[ja]=Amarok のためのプラグイン +Comment[ka]=მოდული Amarok-ისთვის +Comment[km]=កម្មវិធី​ជំនួយ​សម្រាប់ Amarok +Comment[lt]=Amarok įskiepis +Comment[mk]=Приклучок за Амарок +Comment[nb]=Programtillegg for Amarok +Comment[nds]=Moduul för Amarok +Comment[ne]=अमारोकका लागि प्लगइन +Comment[nl]=Plugin voor Amarok +Comment[nn]=Programtillegg for Amarok +Comment[pa]=ਅਮਰੋਕ ਲਈ ਪਲੱਗਇਨ +Comment[pl]=Wtyczka Amaroka +Comment[pt]='Plugin' para o Amarok +Comment[pt_BR]=Plugin para o Amarok +Comment[ru]=Модуль amaroK +Comment[se]=Lassemoduvla Amarok:ii +Comment[sk]=Amarok modul +Comment[sr]=Прикључак за Amarok +Comment[sr@Latn]=Priključak za Amarok +Comment[sv]=Insticksprogram för Amarok +Comment[th]=โปรแกรมเสริมสำหรับ Amarok +Comment[tr]=Amarok için Eklenti +Comment[uk]=Втулок для Amarok +Comment[uz]=Amarok uchun plagin +Comment[uz@cyrillic]=Amarok учун плагин +Comment[wa]=Tchôke-divins po Amarok +Comment[zh_CN]=Amarok 插件 +Comment[zh_TW]=amaroK 插件 +ServiceTypes=Amarok/Plugin + +X-KDE-Amarok-plugintype=engine +X-KDE-Amarok-name=yauap-engine +X-KDE-Amarok-authors=Sascha Sommer +X-KDE-Amarok-email=ssommer@suse.de +X-KDE-Amarok-rank=1 +X-KDE-Amarok-version=1 +X-KDE-Amarok-framework-version=32 + + diff --git a/amarok/src/engine/yauap/yauap-engine.cpp b/amarok/src/engine/yauap/yauap-engine.cpp new file mode 100644 index 00000000..0a4fb9d0 --- /dev/null +++ b/amarok/src/engine/yauap/yauap-engine.cpp @@ -0,0 +1,676 @@ +/*************************************************************************** + yauap-engine.h - yauap engine plugin + +copyright : (C) 2006 by Sascha Sommer <saschasommer@freenet.de> +***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include <qapplication.h> + +#include <klocale.h> +#include <iostream> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <stdlib.h> + +#define DBUS_API_SUBJECT_TO_CHANGE +#include <dbus/connection.h> + +//#define MANUAL_YAUAP_START +#define YAUAP_DBUS_SERVICE "org.yauap.CommandService" +#define YAUAP_DBUS_PATH "/yauapObject" +#define YAUAP_DBUS_INTERFACE "org.yauap.CommandInterface" + +#include "yauap-engine.h" +#include "debug.h" + + + +AMAROK_EXPORT_PLUGIN( yauapEngine ) + +/* signal handler for DBus signals */ +static DBusHandlerResult +signal_handler( DBusConnection * /*con*/, DBusMessage *msg, void *data ) +{ + yauapEngine* engine = (yauapEngine*)data; + const char *objectpath = dbus_message_get_path(msg); + const char *member = dbus_message_get_member(msg); + const char *interface = dbus_message_get_interface(msg); + bool handled = true; + + debug() << "SIGNAL member " << member << " interface " << interface << " objpath " << objectpath << endl; + + if (dbus_message_is_signal( msg, "org.yauap.CommandInterface", "MetadataSignal")) + QApplication::postEvent(engine, new QCustomEvent(3004)); + else if(dbus_message_is_signal( msg, "org.yauap.CommandInterface", "EosSignal")) { + if (engine->m_state == Engine::Playing) + QApplication::postEvent(engine, new QCustomEvent(3000)); + } + else if(dbus_message_is_signal( msg, "org.yauap.CommandInterface", "ErrorSignal")) + { + char* text = NULL; + DBusError error; + dbus_error_init(&error); + if(dbus_message_get_args( msg, &error, DBUS_TYPE_STRING, &text, DBUS_TYPE_INVALID)) { + QCustomEvent* e = new QCustomEvent(3002); + e->setData(new QString(text)); + QApplication::postEvent(engine, e); + } + } + else + handled = false; + + return (handled ? DBUS_HANDLER_RESULT_HANDLED : DBUS_HANDLER_RESULT_NOT_YET_HANDLED); +} + +int +yauapProcess::commSetupDoneC() +{ + int r = Amarok::Process::commSetupDoneC(); + int fd = open("/dev/null", O_RDWR); + dup2(fd, 1); + dup2(fd, 2); + close(fd); + return r; +} + +/* create a qt dbus connection that will receive the signals */ +bool +DBusConnection::open() +{ + DBusError error; + dbus_error_init( &error ); + + debug() << " connecting to dbus" << endl; + + // close open connection + close(); + + /* connect to session bus */ + dbus_connection = dbus_bus_get_private( DBUS_BUS_SESSION, &error ); /* dbus_bus_get somehow doesn't work here */ + if( dbus_error_is_set(&error) ) + { + debug() << "unable to connect to DBUS." << endl; + dbus_error_free(&error); + return false; + } + dbus_connection_set_exit_on_disconnect( dbus_connection, false ); + + /* create qt connection */ + qt_connection = new DBusQt::Connection( this ); + qt_connection->dbus_connection_setup_with_qt_main( dbus_connection ); + + if ( !dbus_connection_add_filter(dbus_connection, signal_handler, context, NULL) ) + { + debug() << "Failed to add filter function." << endl; + return false; + } + + /* Match for DBUS_INTERFACE_DBUS */ + dbus_bus_add_match( dbus_connection, "type='signal',interface='org.yauap.CommandInterface'", &error); + if ( dbus_error_is_set( &error ) ) + { + debug() << "Error adding match, " << error.name << " " << error.message; + dbus_error_free (&error); + return false; + } + + debug() << " connected " << endl; + return true; +} + +/* close qt dbus connection */ +void +DBusConnection::close() +{ + debug() << "close DBusConnection" << endl; + + if( dbus_connection ) + dbus_connection_close( dbus_connection ); + + if(qt_connection) + qt_connection->close(); + + /* DBusConnection::open () calls dbus_bus_get (), we need to unref the connection */ + debug() << "calling dbus connection close" << endl; + + dbus_connection = NULL; + qt_connection = NULL; + debug() << "DBusConnection closed" << endl; +} + + +DBusConnection::DBusConnection( yauapEngine* c ) +{ + qt_connection = NULL; + dbus_connection = NULL; + context = c; +} + +DBusConnection::~DBusConnection() +{ + close(); +} + +bool +DBusConnection::send(const char *method, int first_arg_type, ...) +{ + dbus_uint32_t serial = 0; + bool ret = false; + + QMutexLocker lock(&m_mutex); + + DBusMessage* msg = dbus_message_new_method_call( + YAUAP_DBUS_SERVICE, YAUAP_DBUS_PATH, YAUAP_DBUS_INTERFACE, + method); + + if (msg) { + va_list ap; + + va_start(ap, first_arg_type); + dbus_message_append_args_valist(msg, first_arg_type, ap); + va_end(ap); + + ret = dbus_connection_send(dbus_connection, msg, &serial); + dbus_message_unref (msg); + } + + return ret; +} + +int +DBusConnection::call(const char *method, int first_arg_type, ...) +{ + dbus_uint32_t ret = -1; + + va_list ap; + va_start (ap, first_arg_type); + DBusMessage* msg = send_with_reply(method, first_arg_type, ap); + va_end (ap); + + if (msg) { + DBusMessageIter args; + if (dbus_message_iter_init(msg, &args) && + (DBUS_TYPE_INT32 == dbus_message_iter_get_arg_type(&args) || + DBUS_TYPE_UINT32 == dbus_message_iter_get_arg_type(&args))) + dbus_message_iter_get_basic(&args, &ret); + + dbus_message_unref (msg); + } + + return ret; +} + +DBusMessage* +DBusConnection::send_with_reply(const char* method, int first_arg_type, ...) +{ + va_list ap; + va_start(ap, first_arg_type); + DBusMessage* msg = send_with_reply(method, first_arg_type, ap); + va_end(ap); + return msg; +} + +DBusMessage* +DBusConnection::send_with_reply(const char* method, int first_arg_type, va_list ap) +{ + QMutexLocker lock(&m_mutex); + + DBusMessage* msg = dbus_message_new_method_call( + YAUAP_DBUS_SERVICE, YAUAP_DBUS_PATH, YAUAP_DBUS_INTERFACE, method); + + if (msg) { + DBusError error; + dbus_error_init(&error); + + dbus_message_append_args_valist(msg, first_arg_type, ap); + + DBusMessage* oldmsg = msg; + msg = dbus_connection_send_with_reply_and_block(dbus_connection, oldmsg, -1, &error); + DBusDispatchStatus status; + while ((status = dbus_connection_get_dispatch_status(dbus_connection)) == DBUS_DISPATCH_DATA_REMAINS) + dbus_connection_dispatch (dbus_connection); + dbus_message_unref (oldmsg); + + if (!msg) + debug() << "dbus error while waiting for reply: " << error.message << endl; + } + + return msg; +} + + +/* emit state change signal */ +void +yauapEngine::change_state( Engine::State state ) +{ + m_state = state; + emit stateChanged(m_state); +} + + +/* destroy engine */ +yauapEngine::~yauapEngine() +{ + /* make sure we really stopped */ + stop(); + + /* quit the player */ + if ( !con->send("quit", DBUS_TYPE_INVALID) ) + debug() << "quit failed " << endl; + + delete con; +} + +/* fetch metadata from yauap */ +void +yauapEngine::update_metadata() +{ + Engine::SimpleMetaBundle* bndl = new Engine::SimpleMetaBundle; + debug() << " emit metadata change " << endl; + + DBusMessage* msg = con->send_with_reply("get_metadata", DBUS_TYPE_INVALID); + if (msg) { + DBusMessageIter args; + if (dbus_message_iter_init(msg, &args) && DBUS_TYPE_ARRAY == + dbus_message_iter_get_arg_type(&args)) { + DBusMessageIter sub; + dbus_message_iter_recurse(&args, &sub); + dbus_message_iter_next(&args); + while (dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_STRING) { + char* reply_ptr = 0; + dbus_message_iter_get_basic(&sub, &reply_ptr); + dbus_message_iter_next(&sub); + + debug() << "reply_ptr: " << reply_ptr << endl; + +#define ASSIGN(a,b) if(!strncmp(reply_ptr,b,strlen(b)) && strlen(reply_ptr + strlen(b) + 1)){ \ + bndl->a = reply_ptr + strlen(b) + 1; \ + continue; \ + } + + ASSIGN( title, "title" ) + ASSIGN( artist, "artist" ) + ASSIGN( album, "album" ) + ASSIGN( comment, "comment" ) + ASSIGN( genre, "genre" ) + ASSIGN( samplerate, "samplerate" ) + ASSIGN( year, "date" ) + ASSIGN( tracknr, "track-number" ) + ASSIGN( length, "length" ) + ASSIGN( bitrate, "bitrate" ) +#undef ASSIGN + } + } + dbus_message_unref(msg); + } + + debug() << "title:" << bndl->title << endl; + debug() << "artist:" << bndl->artist << endl; + debug() << "album:" << bndl->album << endl; + debug() << "comment:" << bndl->comment << endl; + debug() << "genre:" << bndl->genre << endl; + debug() << "samplerate:" << bndl->samplerate << endl; + debug() << "year:" << bndl->year << endl; + debug() << "tracknr:" << bndl->tracknr << endl; + debug() << "length:" << bndl->length << endl; + debug() << "bitrate:" << bndl->bitrate << endl; + + + /* do not overwrite manually generated metadata from audio cds */ + if(bndl->title.isEmpty() && loaded_url.protocol() == "cdda") + return; + + QCustomEvent* e = new QCustomEvent(3003); + e->setData(bndl); + QApplication::postEvent(this, e); +} + + +/* fetch current sample buffer from yauap */ +const Engine::Scope & +yauapEngine::scope() +{ + int len = 0; + dbus_int16_t* data = 0; + + DBusMessage* msg = con->send_with_reply("get_scopedata", DBUS_TYPE_INVALID); + if (msg) { + DBusMessageIter args; + if (dbus_message_iter_init(msg, &args) && DBUS_TYPE_ARRAY == + dbus_message_iter_get_arg_type(&args)) { + DBusMessageIter sub; + dbus_message_iter_recurse(&args, &sub); + dbus_message_iter_next(&args); + dbus_message_iter_get_fixed_array(&sub,&data,&len); + } + dbus_message_unref(msg); + } + + /* 2 channel 16 bit samples */ + if(len == SCOPESIZE * 2) + { + for( int i=0; i < SCOPESIZE ; i++) + m_scope[i] = data[i]; + }else + debug() << "get_scopedata returned the wrong amount of data " << len << endl; + + return m_scope; +} + +void +yauapEngine::yauapProcessExited() +{ + debug() << "yauapProcessExited!!!!!" << endl; + + closeDbusConnection(); + initDbusConnection(); +} + +void +yauapEngine::closeDbusConnection() +{ + /* destroy Qt DBus connection */ + delete con; + con = 0; + + /* kill yauap */ +#ifndef MANUAL_YAUAP_START + helper.kill(); +#endif +} + +bool +yauapEngine::initDbusConnection() +{ +#ifndef MANUAL_YAUAP_START + /* start yauap in slave mode */ + helper.clearArguments(); + helper << "yauap" << "-noexit"; + + if( !helper.start(KProcess::NotifyOnExit, KProcess::All)) + { + debug() << "could not start yauap " << endl; + emit statusText( i18n( "could not start yauap" ) ); + return false; + } +#endif + + /* create and open qt DBus connection so that we are able to receive signals */ + con = new DBusConnection( this ); + if (!con->open()) + { + debug() << "could not connect to dbus" << endl; + emit statusText( i18n( "Error: could not connect to dbus" ) ); + return false; + } + + /* makes sure the player is stopped: retry the call a few times because it takes some + time until yauap registered its dbus service */ + for( int i=0; i < 2; ++i ) { + if( con->send("stop", DBUS_TYPE_INVALID) >= 0 ) + return true; + usleep(20 * 1000); + } + + return false; +} + +#if 0 +void +yauapEngine::handleDbusError(const char* method) +{ + debug() << method << " failed " << endl; + + closeDbusConnection(); + initDbusConnection(); +} +#endif + +void +yauapEngine::customEvent(QCustomEvent *e) +{ + QString* message = static_cast<QString*>(e->data()); + + switch (e->type()) { + case 3000: + m_state = Engine::Idle; + emit trackEnded(); + break; + case 3002: + emit statusText(*message); + delete message; + break; + case 3003: + { + Engine::SimpleMetaBundle* bundle = static_cast<Engine::SimpleMetaBundle*>(e->data()); + emit metaData(*bundle); + delete bundle; + break; + } + case 3004: + update_metadata(); + break; + default: + ; + } +} + +/* init engine */ +bool +yauapEngine::init() +{ + debug() << "In init" << endl; + + m_state = Engine::Idle; + + connect(&helper, SIGNAL(processExited(KProcess*)), SLOT(yauapProcessExited())); + + if (initDbusConnection()) + return true; + + emit statusText( i18n( "Error: timed out waiting for yauap" ) ); + return false; +} + +/* check if the given url can be decoded */ +bool +yauapEngine::canDecode( const KURL &kurl ) const +{ + QCString qurl = kurl.url().utf8(); + const char* url = qurl.data(); + + return con->call("can_decode", DBUS_TYPE_STRING, &url, DBUS_TYPE_INVALID) > 0; +} + +/* load a new track FIXME: implement cross fading */ +bool +yauapEngine::load( const KURL &url, bool isStream ) +{ + QString qurl = url.url(); + const char* curl = qurl.ascii(); + + m_isStream = isStream; + + Engine::Base::load( url, isStream || url.protocol() == "http" ); + change_state(Engine::Idle); + + if (!curl || !con->call("load", DBUS_TYPE_STRING, &curl, DBUS_TYPE_INVALID)) + return false; + + loaded_url = url; + return true; +} + +/* set volume */ +void +yauapEngine::setVolumeSW( uint volume ) +{ + dbus_uint32_t dbus_volume = volume; + int ret; + + debug() << "In setVolumeSW " << volume << endl; + + ret = con->send("set_volume", DBUS_TYPE_UINT32, &dbus_volume, DBUS_TYPE_INVALID); + debug() << "=> " << ret << endl; +} + +/* start playback */ +bool +yauapEngine::play( uint offset ) +{ + dbus_uint32_t dbus_offset = offset; + + debug() << "In play" << endl; + + if (con->send("start", DBUS_TYPE_UINT32, &dbus_offset, DBUS_TYPE_INVALID) > 0) + { + change_state( Engine::Playing ); + return true; + } + + change_state( Engine::Empty ); + return false; +} + +/* stop playback */ +void +yauapEngine::stop() +{ + change_state( Engine::Empty ); + + if (con->send("stop", DBUS_TYPE_INVALID) <= 0) + { + debug() << "stop failed " << endl; + return; + } + + change_state(Engine::Empty); +} + + +/* pause playback */ +void +yauapEngine::pause() +{ + debug() << "In pause " << endl; + + if (!con->call("pause", DBUS_TYPE_INVALID)) + return; + + if( state() == Engine::Playing ) + change_state( Engine::Paused ); + else + change_state( Engine::Playing ); +} + +/* unpause playback */ +void +yauapEngine::unpause() +{ + pause(); +} + +/* get track length in ms */ +uint +yauapEngine::length() const +{ + debug() << "In length " << endl; + + int length = con->call("get_length", DBUS_TYPE_INVALID); + if (length < 0) return 0; + + debug() << "length is => " << length << endl; + return (uint) length; +} + +/* get current position */ +uint +yauapEngine::position() const +{ + int position = 0; + + position = con->call("get_position", DBUS_TYPE_INVALID); + + if (position < 0) position = 0; + return (uint) position; +} + +/* seek to offset in ms */ +void +yauapEngine::seek( uint offset ) +{ + dbus_uint32_t dbus_offset = offset; + + if (!con->send("seek", DBUS_TYPE_UINT32, &dbus_offset, DBUS_TYPE_INVALID)) + debug() << "seek failed " << endl; +} + +bool +yauapEngine::getAudioCDContents(const QString &device, KURL::List &urls) +{ + debug() << "Getting AudioCD contents..." << endl; + + QCString cdevice = device.latin1(); + const char* cdevice_ptr = cdevice.data(); + + DBusMessage* msg = con->send_with_reply("get_audio_cd_contents", + DBUS_TYPE_STRING, &cdevice_ptr, DBUS_TYPE_INVALID); + if (msg) { + DBusMessageIter args; + int i = 0; + + if (dbus_message_iter_init(msg, &args) && DBUS_TYPE_ARRAY == + dbus_message_iter_get_arg_type(&args)) { + DBusMessageIter sub; + dbus_message_iter_recurse(&args, &sub); + dbus_message_iter_next(&args); + while (dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_STRING) { + char* reply_ptr = 0; + dbus_message_iter_get_basic(&sub, &reply_ptr); + dbus_message_iter_next(&sub); + + debug() << "reply_ptr: " << reply_ptr << endl; + + Engine::SimpleMetaBundle b; + char* saveptr; + KURL url = QString("cdda://").append( strtok_r(reply_ptr,"=",&saveptr)); + urls << url; + debug() << url << endl; + b.title = QString( i18n( "Track %1" ) ).arg( i+1 ); + b.length = strtok_r(NULL,"=",&saveptr); + b.album = "AudioCD"; + b.tracknr = i+1; + b.samplerate = "44100"; + b.bitrate = "1411"; + cd_tracks.push_back(b); + ++i; + } + } + dbus_message_unref(msg); + } + + return true; +} + +bool +yauapEngine::metaDataForUrl(const KURL &url, Engine::SimpleMetaBundle &b) +{ + if ( url.protocol() == "cdda" ) + { + b = cd_tracks[url.host().toUInt()-1]; + return true; + } + return false; +} + +#include "yauap-engine.moc" diff --git a/amarok/src/engine/yauap/yauap-engine.h b/amarok/src/engine/yauap/yauap-engine.h new file mode 100644 index 00000000..1a3a71d9 --- /dev/null +++ b/amarok/src/engine/yauap/yauap-engine.h @@ -0,0 +1,107 @@ +/*************************************************************************** + yauap-engine.h - yauap engine plugin + +copyright : (C) 2006 by Sascha Sommer <ssommer@suse.de> +***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef AMAROK_YAUAP_ENGINE_H +#define AMAROK_YAUAP_ENGINE_H + +#define DBUS_API_SUBJECT_TO_CHANGE +#include <dbus/connection.h> + +#include <amarok.h> + +#include "enginebase.h" +#include "debug.h" + +class yauapEngine; + +class DBusConnection : public QObject +{ + friend class yauapEngine; + + DBusQt::Connection *qt_connection; + DBusConnection *dbus_connection; + yauapEngine *context; + QMutex m_mutex; + +public: + bool open(); + void close(); + DBusConnection( yauapEngine *context ); + ~DBusConnection(); + + bool send(const char *method, int first_arg_type, ...); + DBusMessage* send_with_reply(const char* method, int first_arg_type, ...); + DBusMessage* send_with_reply(const char* method, int first_arg_type, va_list); + int call(const char *method, int first_arg_type, ...); +}; + +class yauapProcess : public Amarok::Process +{ +public: + yauapProcess(QObject* parent) : Amarok::Process(parent) {} + + virtual int commSetupDoneC(); +}; + +static DBusHandlerResult signal_handler( DBusConnection *, DBusMessage *, void *); + +class yauapEngine : public Engine::Base +{ + Q_OBJECT + + friend class DBusConnection; + + friend DBusHandlerResult signal_handler( DBusConnection *, DBusMessage *, void *); + + virtual ~yauapEngine(); + virtual bool init(); + virtual bool canDecode( const KURL& ) const; + virtual uint position() const ; + virtual bool load( const KURL&, bool ); + virtual bool play( uint ); + virtual void stop(); + virtual void pause(); + virtual void unpause(); + virtual void setVolumeSW( uint ); + virtual void seek( uint ); + virtual uint length() const ; + virtual Engine::State state() const { return m_state; } + virtual const Engine::Scope &scope(); + virtual bool getAudioCDContents(const QString &device, KURL::List &urls); + virtual bool metaDataForUrl(const KURL &url, Engine::SimpleMetaBundle &b); +public: + yauapEngine() : EngineBase(), helper(0) {} + /* these need to be public because they are called from the dbus signal handler */ + void update_metadata(); + void update_scope(); + virtual void customEvent(QCustomEvent*); + +private slots: + void yauapProcessExited(); + +private: + KURL loaded_url; + std::vector<Engine::SimpleMetaBundle> cd_tracks; + void change_state( Engine::State ); + bool initDbusConnection(); + void closeDbusConnection(); + + Engine::State m_state; + DBusConnection *con; + /* helper process to start */ + yauapProcess helper; +}; + +#endif diff --git a/amarok/src/engine_fwd.h b/amarok/src/engine_fwd.h new file mode 100644 index 00000000..30068a8b --- /dev/null +++ b/amarok/src/engine_fwd.h @@ -0,0 +1,26 @@ + +#ifndef ENGINE_FWD_H +#define ENGINE_FWD_H + +/// Used by eg engineobserver.h, and thus we reduce header dependencies on enginebase.h + +namespace Engine +{ + class SimpleMetaBundle; + class Base; + + /** + * You should return: + * Playing when playing, + * Paused when paused + * Idle when you still have a URL loaded (ie you have not been told to stop()) + * Empty when you have been told to stop(), or an error occurred and you stopped yourself + * + * It is vital to be Idle just after the track has ended! + */ + enum State { Empty, Idle, Playing, Paused }; +} + +typedef Engine::Base EngineBase; + +#endif diff --git a/amarok/src/enginebase.cpp b/amarok/src/enginebase.cpp new file mode 100644 index 00000000..eaacc6e1 --- /dev/null +++ b/amarok/src/enginebase.cpp @@ -0,0 +1,53 @@ +//Copyright: (C) 2003 Mark Kretschmann +// (C) 2004,2005 Max Howell, <max.howell@methylblue.com> +//License: See COPYING + +#include "enginebase.h" + +#include <cmath> + + +Engine::Base::Base() + : Amarok::Plugin() + , m_xfadeLength( 0 ) + , m_xfadeNextTrack( false ) + , m_volume( 50 ) + , m_scope( SCOPESIZE ) + , m_isStream( false ) +{} + + +Engine::Base::~Base() +{ +} + +////////////////////////////////////////////////////////////////////// + + +bool +Engine::Base::load( const KURL &url, bool stream ) +{ + m_url = url; + m_isStream = stream; + + return true; +} + + +void Engine::Base::setVolume( uint value ) +{ + m_volume = value; + + setVolumeSW( makeVolumeLogarithmic( value ) ); +} + + +uint +Engine::Base::makeVolumeLogarithmic( uint volume ) // static +{ + // We're using a logarithmic function to make the volume ramp more natural. + return static_cast<uint>( 100 - 100.0 * std::log10( ( 100 - volume ) * 0.09 + 1.0 ) ); +} + + +#include "enginebase.moc" diff --git a/amarok/src/enginebase.h b/amarok/src/enginebase.h new file mode 100644 index 00000000..f47858a5 --- /dev/null +++ b/amarok/src/enginebase.h @@ -0,0 +1,282 @@ +//Copyright: (C) 2003 Mark Kretschmann +// (C) 2004 Max Howell, <max.howell@methylblue.com> +//License: See COPYING + +#ifndef AMAROK_ENGINEBASE_H +#define AMAROK_ENGINEBASE_H + +#include "plugin/plugin.h" //baseclass +#include "amarok_export.h" + +#include <kurl.h> +#include <qobject.h> //baseclass +#include <qvaluelist.h> //stack alloc +#include <vector> + +#include <sys/types.h> + + +/** + * @class Engine::Base + * @author Mark Kretshmann + * @author Max Howell + * + * This is an abstract base class that you need to derive when making your own backends. + * It is typdefed to EngineBase for your conveniece. + * + * The only key thing to get right is what to return from state(), as some Amarok + * behaviour is dependent on you returning the right state at the right time. + * + * Empty = No URL loaded and ready to play + * Idle = URL ready for play, but not playing, so before AND after playback + * Playing = Playing a stream + * Paused = Stream playback is paused + * + * Not returning idle when you have reached End-Of-Stream but Amarok has not told you + * to stop would be bad because some components behave differently when the engine is + * Empty or not. You are Idle because you still have a URL assigned. + * + * load( KURL ) is a key function because after this point your engine is loaded, and + * Amarok will expect you to be able to play the URL until stop() or another load() is + * called. + * + * You must handle your own media, do not rely on Amarok to call stop() before play() etc. + * + * At this time, emitting stateChanged( Engine::Idle ) is not necessary, otherwise you should + * let Amarok know of state changes so it updates the UI correctly. + * + * Basically, reimplement everything virtual and ensure you emit stateChanged() correctly, + * try not to block in any function that is called by Amarok, try to keep the user informed + * with emit statusText() + * + * Only canDecode() needs to be thread-safe. Everything else is only called from the GUI thread. + */ + +#include "engine_fwd.h" + +namespace Engine +{ + typedef std::vector<int16_t> Scope; + + class LIBAMAROK_EXPORT Base : public QObject, public Amarok::Plugin + { + Q_OBJECT + + signals: + /** Emitted when end of current track is reached. */ + void trackEnded(); + + /** Transmits status message, the message disappears after ~2s. */ + void statusText( const QString& ); + + /** + * Shows a long message in a non-invasive manner, you should prefer + * this over KMessageBoxes, but do use KMessageBox when you must + * interrupt the user or the message is very important. + */ + void infoMessage( const QString& ); + + /** Transmits metadata package. */ + void metaData( const Engine::SimpleMetaBundle& ); + + /** Signals that a SYNC has been recieved, and new last.fm data needs to be downloaded */ + void lastFmTrackChange(); + + /** Signals a change in the engine's state. */ + void stateChanged( Engine::State ); + + /** Shows Amarok config dialog at specified page */ + void showConfigDialog( const QCString& ); + + public: + virtual ~Base(); + + /** + * Initializes the engine. Must be called after the engine was loaded. + * @return True if initialization was successful. + */ + virtual bool init() = 0; + + /** + * Determines if the engine is able to play a given URL. + * @param url The URL of the file/stream. + * @return True if we can play the URL. + */ + virtual bool canDecode( const KURL &url ) const = 0; + + /** + * Determines if current track is a stream. + * @return True if track is a stream. + */ + inline bool isStream() { return m_isStream; } + + /** + * Load new track for playing. + * @param url URL to be played. + * @param stream True if URL is a stream. + * @return True for success. + */ + virtual bool load( const KURL &url, bool stream = false ); + + /** + * Load new track and start Playback. Convenience function for Amarok to use. + * @param url URL to be played. + * @param stream True if URL is a stream. + * @return True for success. + */ + bool play( const KURL &u, bool stream = false ) { return load( u, stream ) && play(); } + + /** + * Start playback. + * @param offset Start playing at @p msec position. + * @return True for success. + */ + virtual bool play( uint offset = 0 ) = 0; + + /** Stops playback */ + virtual void stop() = 0; + + /** Pauses playback */ + virtual void pause() = 0; + + /** Resumes playback if paused */ + virtual void unpause() = 0; + + /** + * Get current engine status. + * @return the correct State as described at the enum + */ + virtual State state() const = 0; + + /** Get time position (msec). */ + virtual uint position() const = 0; + + /** Get track length (msec). */ + virtual uint length() const { return 0; } + + /** + * Jump to new time position. + * @param ms New position. + */ + virtual void seek( uint ms ) = 0; + + /** + * Determines whether media is currently loaded. + * @return True if media is loaded, system is ready to play. + */ + inline bool loaded() const { return state() != Empty; } + + inline uint volume() const { return m_volume; } + + /** + * Fetch the current audio sample buffer. + * @return Audio sample buffer. + */ + virtual const Scope &scope() { return m_scope; }; + + /** + * Set new volume value. + * @param value Volume in range 0 to 100. + */ + void setVolume( uint value ); + + /** Set new crossfade length (msec) */ + void setXfadeLength( int value ) { m_xfadeLength = value; } + + /** Set whether to crossfade the next track + * Used when the engine is switching tracks automatically + * instead of manually. + */ + void setXFadeNextTrack( bool enable ) { m_xfadeNextTrack = enable; } + + /** Set whether equalizer is enabled + * You don't need to cache the parameters, setEqualizerParameters is called straight after this + * function, _always_. + */ + virtual void setEqualizerEnabled( bool ) {}; + + /** Set equalizer parameters, all in range -100..100, where 0 = no adjustment + * @param preamp the preamplification value + * @param bandGains a list of 10 integers, ascending in frequency, the exact frequencies you amplify + * are not too-important at this time + */ + virtual void setEqualizerParameters( int /*preamp*/, const QValueList<int> &/*bandGains*/ ) {}; + + + /** Tries to retrieve metadata for the given url (called only if url + * is not in the collection). The intended usage is to retrieve + * information for AudiCD tracks when they are added to the playlist + * (i.e. before they are actually played) + * @param url the url of the item + * @param bundle the SimpleMetaBundle to fill + * @return true if metadata found, false otherwise + */ + virtual bool metaDataForUrl(const KURL &, Engine::SimpleMetaBundle &) + { return false; } + + /** returns true if this engine performs some special action to play + * audio cds: in this case, the KURL::List is filled with the urls of + * the songs in the cd... + * + * @param device the cdrom device , with QString::null meaning use engine-specific default value + * @param urls the list of urls for AudioCD tracks to fill + * @return true if the engine has the feature of reading from audio cds, false otherwise (note that this should return true also in case of error if the engine is capable of reading audio cds in general...) + * */ + virtual bool getAudioCDContents(const QString &, KURL::List &) + { return false; } + + /** + * Whether amarok_proxy.rb is needed for last.fm files. + * @return true if engine doesn't handle 'SYNC' messages in the stream from last.fm. + False if (like >=libxine-1.1.8) it does. + */ + virtual bool lastFmProxyRequired() { return true; } + + /** flush the current stream buffer */ + virtual bool flushBuffer() { return false; } + + /** allow the engine to perform necessary work on changes in the playlist **/ + virtual void playlistChanged() { }; + + protected: + Base(); + + /** Shows the Amarok configuration dialog at the engine page */ + void showEngineConfigDialog() { emit showConfigDialog( "Engine" ); } + + virtual void setVolumeSW( uint percent ) = 0; + + /** Converts master volume to a logarithmic scale */ + static uint makeVolumeLogarithmic( uint volume ); + + Base( const Base& ); //disable copy constructor + const Base &operator=( const Base& ); //disable copy constructor + + int m_xfadeLength; + bool m_xfadeNextTrack; + + protected: + static const int SCOPESIZE = 1024; + uint m_volume; + KURL m_url; + Scope m_scope; + bool m_isStream; + }; + + + class SimpleMetaBundle { + public: + QString title; + QString artist; + QString album; + QString comment; + QString genre; + QString bitrate; + QString samplerate; + QString length; + QString year; + QString tracknr; + }; +} + +#endif diff --git a/amarok/src/enginecontroller.cpp b/amarok/src/enginecontroller.cpp new file mode 100644 index 00000000..e16ab2f0 --- /dev/null +++ b/amarok/src/enginecontroller.cpp @@ -0,0 +1,802 @@ +/*************************************************************************** + * Copyright (C) 2004 Frederik Holljen <fh@ez.no> * + * (C) 2004,5 Max Howell <max.howell@methylblue.com> * + * (C) 2004,5 Mark Kretschmann * + * (C) 2006 Ian Monroe * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#define DEBUG_PREFIX "controller" + +#include "amarok.h" +#include "amarokconfig.h" +#include "debug.h" +#include "enginebase.h" +#include "enginecontroller.h" +#include "lastfm.h" +#include "mediabrowser.h" +#include "playlist.h" +#include "playlistloader.h" +#include "pluginmanager.h" +#include "statusbar.h" + +#include <qfile.h> +#include <qobjectlist.h> +#include <qtimer.h> + +#include <kapplication.h> +#include <kfileitem.h> +#include <kio/global.h> +#include <kio/job.h> +#include <kmessagebox.h> +#include <krun.h> + +#include <cstdlib> + + +EngineController::ExtensionCache EngineController::s_extensionCache; + + +EngineController* +EngineController::instance() +{ + //will only be instantiated the first time this function is called + //will work with the inline directive + static EngineController Instance; + + return &Instance; +} + + +EngineController::EngineController() + : m_engine( 0 ) + , m_voidEngine( 0 ) + , m_delayTime( 0 ) + , m_muteVolume( 0 ) + , m_xFadeThisTrack( false ) + , m_timer( new QTimer( this ) ) + , m_playFailureCount( 0 ) + , m_lastFm( false ) + , m_positionOffset( 0 ) + , m_lastPositionOffset( 0 ) +{ + m_voidEngine = m_engine = loadEngine( "void-engine" ); + + connect( m_timer, SIGNAL( timeout() ), SLOT( slotMainTimer() ) ); +} + +EngineController::~EngineController() +{ + DEBUG_FUNC_INFO //we like to know when singletons are destroyed +} + + +////////////////////////////////////////////////////////////////////////////////////////// +// PUBLIC +////////////////////////////////////////////////////////////////////////////////////////// + +EngineBase* +EngineController::loadEngine() //static +{ + /// always returns a valid pointer to EngineBase + + DEBUG_BLOCK + //TODO remember song position, and resume playback + + // new engine, new ext cache required + extensionCache().clear(); + + if( m_engine != m_voidEngine ) { + EngineBase *oldEngine = m_engine; + + // we assign this first for thread-safety, + // EngineController::engine() must always return an engine! + m_engine = m_voidEngine; + + // we unload the old engine first because there are a number of + // bugs associated with keeping one engine loaded while loading + // another, eg xine-engine can't init(), and aRts-engine crashes + PluginManager::unload( oldEngine ); + + // the engine is not required to do this when we unload it but + // we need to do it to ensure Amarok looks correct. + // We don't do this for the void-engine because that + // means Amarok sets all components to empty on startup, which is + // their responsibility. + slotStateChanged( Engine::Empty ); + } + + m_engine = loadEngine( AmarokConfig::soundSystem() ); + + const QString engineName = PluginManager::getService( m_engine )->property( "X-KDE-Amarok-name" ).toString(); + + if( !AmarokConfig::soundSystem().isEmpty() && engineName != AmarokConfig::soundSystem() ) { + //AmarokConfig::soundSystem() is empty on the first-ever-run + + Amarok::StatusBar::instance()->longMessage( i18n( + "Sorry, the '%1' could not be loaded, instead we have loaded the '%2'." ) + .arg( AmarokConfig::soundSystem() ) + .arg( engineName ), + KDE::StatusBar::Sorry ); + + AmarokConfig::setSoundSystem( engineName ); + } + + // Important: Make sure soundSystem is not empty + if( AmarokConfig::soundSystem().isEmpty() ) + AmarokConfig::setSoundSystem( engineName ); + + return m_engine; +} + +#include <qvaluevector.h> +EngineBase* +EngineController::loadEngine( const QString &engineName ) +{ + /// always returns a valid plugin (exits if it can't get one) + + DEBUG_BLOCK + + QString query = "[X-KDE-Amarok-plugintype] == 'engine' and [X-KDE-Amarok-name] != '%1'"; + KTrader::OfferList offers = PluginManager::query( query.arg( engineName ) ); + + // sort by rank, QValueList::operator[] is O(n), so this is quite inefficient + #define rank( x ) (x)->property( "X-KDE-Amarok-rank" ).toInt() + for( int n = offers.count()-1, i = 0; i < n; i++ ) + for( int j = n; j > i; j-- ) + if( rank( offers[j] ) > rank( offers[j-1] ) ) + qSwap( offers[j], offers[j-1] ); + #undef rank + + // this is the actual engine we want + query = "[X-KDE-Amarok-plugintype] == 'engine' and [X-KDE-Amarok-name] == '%1'"; + offers = PluginManager::query( query.arg( engineName ) ) + offers; + + foreachType( KTrader::OfferList, offers ) { + Amarok::Plugin *plugin = PluginManager::createFromService( *it ); + + if( plugin ) { + QObject *bar = Amarok::StatusBar::instance(); + EngineBase *engine = static_cast<EngineBase*>( plugin ); + + connect( engine, SIGNAL(stateChanged( Engine::State )), + this, SLOT(slotStateChanged( Engine::State )) ); + connect( engine, SIGNAL(trackEnded()), + this, SLOT(slotTrackEnded()) ); + if( bar ) + { + connect( engine, SIGNAL(statusText( const QString& )), + bar, SLOT(shortMessage( const QString& )) ); + connect( engine, SIGNAL(infoMessage( const QString& )), + bar, SLOT(longMessage( const QString& )) ); + } + connect( engine, SIGNAL(metaData( const Engine::SimpleMetaBundle& )), + this, SLOT(slotEngineMetaData( const Engine::SimpleMetaBundle& )) ); + connect( engine, SIGNAL(showConfigDialog( const QCString& )), + kapp, SLOT(slotConfigAmarok( const QCString& )) ); + + if( engine->init() ) + return engine; + else + warning() << "Could not init() an engine\n"; + } + } + + KRun::runCommand( "kbuildsycoca" ); + + KMessageBox::error( 0, i18n( + "<p>Amarok could not find any sound-engine plugins. " + "Amarok is now updating the KDE configuration database. Please wait a couple of minutes, then restart Amarok.</p>" + "<p>If this does not help, " + "it is likely that Amarok is installed under the wrong prefix, please fix your installation using:<pre>" + "$ cd /path/to/amarok/source-code/<br>" + "$ su -c \"make uninstall\"<br>" + "$ ./configure --prefix=`kde-config --prefix` && su -c \"make install\"<br>" + "$ kbuildsycoca<br>" + "$ amarok</pre>" + "More information can be found in the README file. For further assistance join us at #amarok on irc.freenode.net.</p>" ) ); + + // don't use QApplication::exit, as the eventloop may not have started yet + std::exit( EXIT_SUCCESS ); + + // Not executed, just here to prevent compiler warning + return 0; +} + + +bool EngineController::canDecode( const KURL &url ) //static +{ + //NOTE this function must be thread-safe + //TODO a KFileItem version? <- presumably so we can mimetype check + + const QString fileName = url.fileName(); + const QString ext = Amarok::extension( fileName ); + + if ( PlaylistFile::isPlaylistFile( fileName ) ) return false; + + // Ignore protocols "fetchcover" and "musicbrainz", they're not local but we don't really want them in the playlist :) + if ( url.protocol() == "fetchcover" || url.protocol() == "musicbrainz" ) return false; + + // Accept non-local files, since we can't test them for validity at this point + // TODO actually, only accept unconditionally http stuff + // TODO this actually makes things like "Blarrghgjhjh:!!!" automatically get inserted + // into the playlist + // TODO remove for Amarok 1.3 and above silly checks, instead check for http type servers + if ( !url.isLocalFile() ) return true; + + // If extension is already in the cache, return cache result + if ( extensionCache().contains( ext ) ) + return s_extensionCache[ext]; + + // If file has 0 bytes, ignore it and return false, not to infect the cache with corrupt files. + // TODO also ignore files that are too small? + KFileItem f( KFileItem::Unknown, KFileItem::Unknown, url, false ); + if ( !f.size() ) + return false; + + const bool valid = engine()->canDecode( url ); + + if( engine() != EngineController::instance()->m_voidEngine ) + { + //we special case this as otherwise users hate us + if ( !valid && ext.lower() == "mp3"){ + QCustomEvent * e = new QCustomEvent( 2000 ); + QApplication::postEvent( Amarok::StatusBar::instance(), e ); + } + + // Cache this result for the next lookup + if ( !ext.isEmpty() ) + extensionCache().insert( ext, valid ); + } + + return valid; +} + +void EngineController::unplayableNotification() { + + if( !installDistroCodec(AmarokConfig::soundSystem())) + Amarok::StatusBar::instance()->longMessageThreadSafe( + i18n( "<p>The %1 claims it <b>cannot</b> play MP3 files." + "<p>You may want to choose a different engine from the <i>Configure Dialog</i>, or examine " + "the installation of the multimedia-framework that the current engine uses. " + "<p>You may find useful information in the <i>FAQ</i> section of the <i>Amarok HandBook</i>." ) + .arg( AmarokConfig::soundSystem() ), KDE::StatusBar::Error ); +} + +bool EngineController::installDistroCodec( const QString& engine /*Filetype type*/) +{ + KService::Ptr service = KTrader::self()->query( "Amarok/CodecInstall" + , QString("[X-KDE-Amarok-codec] == 'mp3' and [X-KDE-Amarok-engine] == '%1'").arg(engine) ).first(); + if( service ) + { + QString installScript = service->exec(); + if( !installScript.isNull() ) //just a sanity check + { + KGuiItem installButton( i18n( "Install MP3 Support" ) ); + if(KMessageBox::questionYesNo(PlaylistWindow::self() + , i18n("Amarok currently cannot play MP3 files.") + , i18n( "No MP3 Support" ) + , installButton + , KStdGuiItem::no() + , "codecInstallWarning" ) == KMessageBox::Yes ) + { + KRun::runCommand(installScript); + return true; + } + } + } +return false; +} + +void EngineController::restoreSession() +{ + //here we restore the session + //however, do note, this is always done, KDE session management is not involved + + if( !AmarokConfig::resumeTrack().isEmpty() ) + { + const KURL url = AmarokConfig::resumeTrack(); + + play( MetaBundle( url ), AmarokConfig::resumeTime() ); + } +} + + +void EngineController::endSession() +{ + //only update song stats, when we're not going to resume it + if ( !AmarokConfig::resumePlayback() ) + { + trackEnded( trackPosition(), m_bundle.length() * 1000, "quit" ); + } + + PluginManager::unload( m_voidEngine ); + m_voidEngine = 0; +} + +////////////////////////////////////////////////////////////////////////////////////////// +// PUBLIC SLOTS +////////////////////////////////////////////////////////////////////////////////////////// + +void EngineController::previous() //SLOT +{ + emit orderPrevious(); +} + + +void EngineController::next( bool forceNext ) //SLOT +{ + m_previousUrl = m_bundle.url(); + m_isTiming = false; + emit orderNext(forceNext); +} + + +void EngineController::play() //SLOT +{ + if ( m_engine->state() == Engine::Paused ) + { + m_engine->unpause(); + } + else emit orderCurrent(); +} + + +void EngineController::play( const MetaBundle &bundle, uint offset ) +{ + DEBUG_BLOCK + + KURL url = bundle.url(); + // don't destroy connection if we need to change station + if( url.protocol() != "lastfm" && LastFm::Controller::instance()->isPlaying() ) + { + m_engine->stop(); + LastFm::Controller::instance()->playbackStopped(); + } + m_lastFm = false; + //Holds the time since we started trying to play non-existent files + //so we know when to abort + static QTime failure_time; + if ( !m_playFailureCount ) + failure_time.start(); + + debug() << "Loading URL: " << url.url() << endl; + m_lastMetadata.clear(); + + //TODO bummer why'd I do it this way? it should _not_ be in play! + //let Amarok know that the previous track is no longer playing + if ( m_timer->isActive() ) + trackEnded( trackPosition(), m_bundle.length() * 1000, "change" ); + + if ( url.isLocalFile() ) { + // does the file really exist? the playlist entry might be old + if ( ! QFile::exists( url.path()) ) { + //debug() << " file >" << url.path() << "< does not exist!" << endl; + Amarok::StatusBar::instance()->shortMessage( i18n("Local file does not exist.") ); + goto some_kind_of_failure; + } + } + else + { + if( url.protocol() == "cdda" ) + Amarok::StatusBar::instance()->shortMessage( i18n("Starting CD Audio track...") ); + else + Amarok::StatusBar::instance()->shortMessage( i18n("Connecting to stream source...") ); + debug() << "Connecting to protocol: " << url.protocol() << endl; + } + + // WebDAV protocol is HTTP with extensions (and the "webdav" scheme + // is a KDE-ism anyway). Most engines cope with HTTP streaming, but + // not through KIO, so they don't support KDE-isms. + if ( url.protocol() == "webdav" ) + url.setProtocol( "http" ); + else if ( url.protocol() == "webdavs" ) + url.setProtocol( "https" ); + + // streams from last.fm should be handled by our proxy, in order to authenticate with the server + else if ( url.protocol() == "lastfm" ) + { + if( LastFm::Controller::instance()->isPlaying() ) + { + if (LastFm::Controller::instance()->changeStation( url.url() ) == -1) + // Request was canceled, return immediately. + return; + connect( m_engine, SIGNAL( lastFmTrackChange() ), LastFm::Controller::instance()->getService() + , SLOT( requestMetaData() ) ); + connect( LastFm::Controller::instance()->getService(), SIGNAL( metaDataResult( const MetaBundle& ) ), + this, SLOT( slotStreamMetaData( const MetaBundle& ) ) ); + return; + } + else + { + url = LastFm::Controller::instance()->getNewProxy( url.url(), m_engine->lastFmProxyRequired() ); + if( url.isEmpty() ) + goto some_kind_of_failure; + else if ( !url.isValid() && url.url() == "lastfm://" ) + // Request was canceled, return immediately. + return; + + m_lastFm = true; + connect( m_engine, SIGNAL( lastFmTrackChange() ), LastFm::Controller::instance()->getService() + , SLOT( requestMetaData() ) ); + connect( LastFm::Controller::instance()->getService(), SIGNAL( metaDataResult( const MetaBundle& ) ), + this, SLOT( slotStreamMetaData( const MetaBundle& ) ) ); + } + debug() << "New URL is " << url.url() << endl; + } + else if (url.protocol() == "daap" ) + { + KURL newUrl = MediaBrowser::instance()->getProxyUrl( url ); + if( !newUrl.isEmpty() ) + { + debug() << newUrl << endl; + url = newUrl; + } + else + return; + } + + if( m_engine->load( url, url.protocol() == "http" || url.protocol() == "rtsp" ) ) + { + //assign bundle now so that it is available when the engine + //emits stateChanged( Playing ) + if( !m_bundle.url().path().isEmpty() ) //wasn't playing before + m_previousUrl = m_bundle.url(); + else + m_previousUrl = bundle.url(); + m_bundle = bundle; + + if( m_engine->play( offset ) ) + { + //Reset failure count as we are now successfully playing a song + m_playFailureCount = 0; + + // Ask engine for track length, if available. It's more reliable than TagLib. + const uint trackLength = m_engine->length() / 1000; + if ( trackLength ) m_bundle.setLength( trackLength ); + + m_xFadeThisTrack = !m_engine->isStream() && !(url.protocol() == "cdda") && + m_bundle.length()*1000 - offset - AmarokConfig::crossfadeLength()*2 > 0; + + newMetaDataNotify( m_bundle, true /* track change */ ); + return; + } + } + + some_kind_of_failure: + debug() << "Failed to play this track." << endl; + + ++m_playFailureCount; + + //Code to skip to next track if playback fails: + // + //* The failure counter is reset if a track plays successfully or if playback is + // stopped, for whatever reason. + //* For normal playback, the attempt to play is stopped at the end of the playlist + //* For repeat playlist , a whole playlist worth of songs is tried + //* For repeat album, the number of songs tried is the number of tracks from the + // album that are in the playlist. + //* For repeat track, no attempts are made + //* For the nmm engine, no attempts are made (necessary? / FIXME) + //* To prevent GUI freezes we don't try to play again after 0.5s of failure + int totalTracks = Playlist::instance()->totalTrackCount(); + int currentTrack = Playlist::instance()->currentTrackIndex(); + if ( ( ( Amarok::repeatPlaylist() && static_cast<int>(m_playFailureCount) < totalTracks ) + || ( Amarok::repeatNone() && currentTrack != totalTracks - 1 ) + || ( Amarok::repeatAlbum() && m_playFailureCount < Playlist::instance()->repeatAlbumTrackCount() ) ) + && AmarokConfig::soundSystem() != "nmm-engine" + && failure_time.elapsed() < 500 ) + { + + debug() << "Skipping to next track." << endl; + + // The test for loaded must be done _before_ next is called + if ( !m_engine->loaded() ) + { + //False gives behaviour as if track played successfully + next( false ); + QTimer::singleShot( 0, this, SLOT(play()) ); + } + else + { + //False gives behaviour as if track played successfully + next( false ); + } + } + else + { + //Stop playback, including resetting failure count (as all new failures are + //treated as independent after playback is stopped) + stop(); + } +} + + +void EngineController::pause() //SLOT +{ + if ( m_engine->loaded() && !LastFm::Controller::instance()->isPlaying() ) + m_engine->pause(); +} + + +void EngineController::stop() //SLOT +{ + //Reset failure counter as after stop, everything else is unrelated + m_playFailureCount = 0; + + //let Amarok know that the previous track is no longer playing + trackEnded( trackPosition(), m_bundle.length() * 1000, "stop" ); + + //Remove requirement for track to be loaded for stop to be called (fixes gltiches + //where stop never properly happens if call to m_engine->load fails in play) + //if ( m_engine->loaded() ) + m_engine->stop(); +} + + +void EngineController::playPause() //SLOT +{ + //this is used by the TrayIcon, PlayPauseAction and DCOP + + if( m_engine->state() == Engine::Playing ) + { + pause(); + } + else if( m_engine->state() == Engine::Paused ) + { + if ( m_engine->loaded() ) + m_engine->unpause(); + } + else + play(); +} + + +void EngineController::seek( int ms ) //SLOT +{ + if( bundle().length() > 0 ) + { + trackPositionChangedNotify( ms, true ); /* User seek */ + engine()->seek( ms ); + } +} + + +void EngineController::seekRelative( int ms ) //SLOT +{ + if( m_engine->state() != Engine::Empty ) + { + int newPos = m_engine->position() + ms; + seek( newPos <= 0 ? 1 : newPos ); + } +} + + +void EngineController::seekForward( int ms ) +{ + seekRelative( ms ); +} + + +void EngineController::seekBackward( int ms ) +{ + seekRelative( -ms ); +} + + +int EngineController::increaseVolume( int ticks ) //SLOT +{ + return setVolume( m_engine->volume() + ticks ); +} + + +int EngineController::decreaseVolume( int ticks ) //SLOT +{ + return setVolume( m_engine->volume() - ticks ); +} + + +int EngineController::setVolume( int percent ) //SLOT +{ + m_muteVolume = 0; + + if( percent < 0 ) percent = 0; + if( percent > 100 ) percent = 100; + + if( (uint)percent != m_engine->volume() ) + { + m_engine->setVolume( (uint)percent ); + + percent = m_engine->volume(); + AmarokConfig::setMasterVolume( percent ); + volumeChangedNotify( percent ); + return percent; + } + else // Still notify + { + volumeChangedNotify( percent ); + } + + return m_engine->volume(); +} + + +void EngineController::mute() //SLOT +{ + if( m_muteVolume == 0 ) + { + int saveVolume = m_engine->volume(); + setVolume( 0 ); + m_muteVolume = saveVolume; + } + else + { + setVolume( m_muteVolume ); + m_muteVolume = 0; + } +} + + +const MetaBundle& +EngineController::bundle() const +{ + static MetaBundle null; + return m_engine->state() == Engine::Empty ? null : m_bundle; +} + + +void EngineController::slotStreamMetaData( const MetaBundle &bundle ) //SLOT +{ + // Prevent spamming by ignoring repeated identical data (some servers repeat it every 10 seconds) + if ( m_lastMetadata.contains( bundle ) ) + return; + + // We compare the new item with the last two items, because mth.house currently cycles + // two messages alternating, which gets very annoying + if ( m_lastMetadata.count() == 2 ) + m_lastMetadata.pop_front(); + + m_lastMetadata << bundle; + + m_previousUrl = m_bundle.url(); + m_bundle = bundle; + m_lastPositionOffset = m_positionOffset; + if( m_lastFm ) + m_positionOffset = m_engine->position(); + else + m_positionOffset = 0; + newMetaDataNotify( m_bundle, false /* not a new track */ ); +} + +void EngineController::currentTrackMetaDataChanged( const MetaBundle& bundle ) +{ + m_previousUrl = m_bundle.url(); + m_bundle = bundle; + newMetaDataNotify( bundle, false /* no track change */ ); +} + +////////////////////////////////////////////////////////////////////////////////////////// +// PRIVATE SLOTS +////////////////////////////////////////////////////////////////////////////////////////// + +void EngineController::slotEngineMetaData( const Engine::SimpleMetaBundle &simpleBundle ) //SLOT +{ + if ( !m_bundle.url().isLocalFile() ) + { + MetaBundle bundle = m_bundle; + bundle.setArtist( simpleBundle.artist ); + bundle.setTitle( simpleBundle.title ); + bundle.setComment( simpleBundle.comment ); + bundle.setAlbum( simpleBundle.album ); + + if( !simpleBundle.genre.isEmpty() ) + bundle.setGenre( simpleBundle.genre ); + if( !simpleBundle.bitrate.isEmpty() ) + bundle.setBitrate( simpleBundle.bitrate.toInt() ); + if( !simpleBundle.samplerate.isEmpty() ) + bundle.setSampleRate( simpleBundle.samplerate.toInt() ); + if( !simpleBundle.year.isEmpty() ) + bundle.setYear( simpleBundle.year.toInt() ); + if( !simpleBundle.tracknr.isEmpty() ) + bundle.setTrack( simpleBundle.tracknr.toInt() ); + + slotStreamMetaData( bundle ); + } +} + + +void EngineController::slotMainTimer() //SLOT +{ + const uint position = trackPosition(); + + trackPositionChangedNotify( position ); + + // Crossfading + if ( m_engine->state() == Engine::Playing && + AmarokConfig::crossfade() && m_xFadeThisTrack && + m_engine->hasPluginProperty( "HasCrossfade" ) && + Playlist::instance()->stopAfterMode() != Playlist::StopAfterCurrent && + ( (uint) AmarokConfig::crossfadeType() == 0 || //Always or... + (uint) AmarokConfig::crossfadeType() == 1 ) && //...automatic track change only + Playlist::instance()->isTrackAfter() && + m_bundle.length()*1000 - position < (uint) AmarokConfig::crossfadeLength() ) + { + debug() << "Crossfading to next track...\n"; + m_engine->setXFadeNextTrack( true ); + trackFinished(); + } + else if ( m_engine->state() == Engine::Playing && + AmarokConfig::fadeout() && + Playlist::instance()->stopAfterMode() == Playlist::StopAfterCurrent && + m_bundle.length()*1000 - position < (uint) AmarokConfig::fadeoutLength() ) + { + m_engine->stop(); + } +} + + +void EngineController::slotTrackEnded() //SLOT +{ + if ( AmarokConfig::trackDelayLength() > 0 ) + { + //FIXME not perfect + if ( !m_isTiming ) + { + QTimer::singleShot( AmarokConfig::trackDelayLength(), this, SLOT(trackFinished()) ); + m_isTiming = true; + } + + } + else trackFinished(); +} + + +void EngineController::slotStateChanged( Engine::State newState ) //SLOT +{ + + switch( newState ) + { + case Engine::Empty: + + //FALL THROUGH... + + case Engine::Paused: + + m_timer->stop(); + break; + + case Engine::Playing: + + m_timer->start( MAIN_TIMER ); + break; + + default: + ; + } + + stateChangedNotify( newState ); +} + +uint EngineController::trackPosition() const +{ + const uint buffertime = 5000; // worked for me with xine engine over 1 mbit dsl + if( !m_engine ) + return 0; + uint pos = m_engine->position(); + if( !m_lastFm ) + return pos; + + if( m_positionOffset + buffertime <= pos ) + return pos - m_positionOffset - buffertime; + if( m_lastPositionOffset + buffertime <= pos ) + return pos - m_lastPositionOffset - buffertime; + return pos; +} + + +#include "enginecontroller.moc" diff --git a/amarok/src/enginecontroller.h b/amarok/src/enginecontroller.h new file mode 100644 index 00000000..b85af21d --- /dev/null +++ b/amarok/src/enginecontroller.h @@ -0,0 +1,143 @@ +/*************************************************************************** + * Copyright (C) 2004 Frederik Holljen <fh@ez.no> * + * (C) 2004,5 Max Howell <max.howell@methylblue.com> * + * (C) 2004,5 Mark Kretschmann * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef AMAROK_ENGINECONTROLLER_H +#define AMAROK_ENGINECONTROLLER_H + +#include "enginebase.h" +#include "engineobserver.h" +#include "metabundle.h" + +#include <qmap.h> +#include <qobject.h> +#include <qvaluelist.h> + +class QTimer; + +namespace KIO { class Job; } + + +/** + * This class captures Amarok specific behaviour for some common features. + * Accessing the engine directly is perfectly legal but on your own risk. + * TODO: Hide proxy stuff! + */ + +class EngineController : public QObject, public EngineSubject +{ + Q_OBJECT + +public: + typedef QMap<QString, bool> ExtensionCache; + + // plugins have their own static space, so calling instance + // from a plugin won't do any good. you'll only get a new + // instance with a voidEngine + static EngineController* instance(); + static EngineBase* engine() { return instance()->m_engine; } + static bool canDecode( const KURL& ); + static ExtensionCache& extensionCache() { return s_extensionCache; } + static QString engineProperty( const QString& key ) { return engine()->pluginProperty( key ); } + static bool hasEngineProperty( const QString& key ) { return engine()->hasPluginProperty( key ); } + + uint trackPosition() const; + + EngineBase* loadEngine(); + void unplayableNotification(); + + uint trackLength() const { return m_bundle.length() * 1000; } + const MetaBundle &bundle() const; + KURL previousURL() const { return m_previousUrl; } + KURL playingURL() const { return bundle().url(); } + + void restoreSession(); + void endSession(); + + void updateBundleRating( const int rating ) { m_bundle.setRating(rating); } //Can't update metabundle rating from bundle(), d'oh + + //xx000, xx100, xx200, so at most will be 200ms delay before time displays are updated + static const int MAIN_TIMER = 300; + + /*enum Filetype { MP3 };*/ //assuming MP3 for time being + LIBAMAROK_EXPORT static bool installDistroCodec(const QString& engine /*Filetype type*/); + +public slots: + void previous(); + // forceNext make we go to next track even if Repeat Track is on + //NOTE If the track ended normally, call next(false) ! + void next( const bool forceNext = true ); + void trackFinished() { next(false); }; + void play(); + void play( const MetaBundle&, uint offset = 0 ); + void pause(); + void stop(); + void playPause(); //pauses if playing, plays if paused or stopped + + void seek( int ms ); + void seekRelative( int ms ); + void seekForward( int ms = 10000 ); + void seekBackward( int ms = 10000 ); + + int increaseVolume( int ticks = 100/25 ); + int decreaseVolume( int ticks = 100/25 ); + int setVolume( int percent ); + + void mute(); + + void playlistChanged() { m_engine->playlistChanged(); } + + void slotStreamMetaData( const MetaBundle &bundle ); + void currentTrackMetaDataChanged( const MetaBundle& bundle ); + +signals: + void orderPrevious(); + void orderCurrent(); + void orderNext( const bool ); + void statusText( const QString& ); + +private slots: + void slotEngineMetaData( const Engine::SimpleMetaBundle& ); + void slotMainTimer(); + void slotTrackEnded(); + void slotStateChanged( Engine::State ); + +protected: + EngineController(); + ~EngineController(); + + // undefined + EngineController( const EngineController& ); + EngineController &operator=( const EngineController& ); + +private: + static ExtensionCache s_extensionCache; + + EngineBase* loadEngine( const QString &engineName ); + + EngineBase* m_engine; + EngineBase* m_voidEngine; + MetaBundle m_bundle; + KURL m_previousUrl; + BundleList m_lastMetadata; + long m_delayTime; + int m_muteVolume; + bool m_xFadeThisTrack; + bool m_isTiming; + QTimer* m_timer; + uint m_playFailureCount; + // try to correct start time for tracks from last.fm streams + bool m_lastFm; + uint m_positionOffset, m_lastPositionOffset; +}; + + +#endif diff --git a/amarok/src/engineobserver.cpp b/amarok/src/engineobserver.cpp new file mode 100644 index 00000000..082a0c48 --- /dev/null +++ b/amarok/src/engineobserver.cpp @@ -0,0 +1,151 @@ +/*************************************************************************** + engineobserver.cpp - Observer pattern for engine + ------------------- +begin : Mar 14 2003 +copyright : (C) 2003 by Frederik Holljen +email : fh@ez.no +***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "debug.h" +#include "collectiondb.h" +#include "engineobserver.h" +#include "metabundle.h" +#include "podcastbundle.h" +#include <qptrlist.h> + + +////////////////////////////////////////////////////////////////////////////////////////// +/// CLASS EngineObserver +////////////////////////////////////////////////////////////////////////////////////////// + +EngineObserver::EngineObserver() + : m_subject( 0 ) +{} + +EngineObserver::EngineObserver( EngineSubject *s ) + : m_subject( s ) +{ + m_subject->attach( this ); +} + +EngineObserver::~EngineObserver() +{ + if ( m_subject ) + m_subject->detach( this ); +} + + +////////////////////////////////////////////////////////////////////////////////////////// +/// CLASS EngineSubject +////////////////////////////////////////////////////////////////////////////////////////// + +EngineSubject::EngineSubject() + : m_oldEngineState( Engine::Empty ) +{} + +EngineSubject::~EngineSubject() +{} + + +void EngineSubject::stateChangedNotify( Engine::State state ) +{ + DEBUG_BLOCK + + QPtrListIterator<EngineObserver> it( Observers ); + EngineObserver *observer; + while( ( observer = it.current() ) != 0 ) + { + ++it; + observer->engineStateChanged( state, m_oldEngineState ); + } + + m_oldEngineState = state; +} + + +void EngineSubject::newMetaDataNotify( const MetaBundle &bundle, bool trackChanged ) +{ + DEBUG_BLOCK + + QPtrListIterator<EngineObserver> it( Observers ); + EngineObserver *observer; + + PodcastEpisodeBundle peb; + MetaBundle b( bundle ); + if( CollectionDB::instance()->getPodcastEpisodeBundle( bundle.url(), &peb ) ) + { + b.setPodcastBundle( peb ); + } + + while( ( observer = it.current() ) != 0 ) + { + ++it; + observer->engineNewMetaData( b, trackChanged ); + } +} + + +void EngineSubject::trackEnded( int finalPosition, int trackLength, const QString &reason ) +{ + for( QPtrListIterator<EngineObserver> it( Observers ); *it; ++it ) + (*it)->engineTrackEnded( finalPosition, trackLength, reason ); +} + + +void EngineSubject::volumeChangedNotify( int percent ) +{ + QPtrListIterator<EngineObserver> it( Observers ); + EngineObserver *observer; + while( ( observer = it.current() ) != 0 ) + { + ++it; + observer->engineVolumeChanged( percent ); + } +} + + +void EngineSubject::trackPositionChangedNotify( long position, bool userSeek ) +{ + QPtrListIterator<EngineObserver> it( Observers ); + EngineObserver *observer; + while( ( observer = it.current() ) != 0 ) + { + ++it; + observer->engineTrackPositionChanged( position, userSeek ); + } +} + + +void EngineSubject::trackLengthChangedNotify( long length ) +{ + QPtrListIterator<EngineObserver> it( Observers ); + EngineObserver *observer; + while( ( observer = it.current() ) != 0 ) + { + ++it; + observer->engineTrackLengthChanged( length ); + } +} + + +void EngineSubject::attach( EngineObserver *observer ) +{ + if( !observer || Observers.find( observer ) != -1 ) + return; + Observers.append( observer ); +} + + +void EngineSubject::detach( EngineObserver *observer ) +{ + if( Observers.find( observer ) != -1 ) Observers.remove(); +} diff --git a/amarok/src/engineobserver.h b/amarok/src/engineobserver.h new file mode 100644 index 00000000..a48419a4 --- /dev/null +++ b/amarok/src/engineobserver.h @@ -0,0 +1,76 @@ +/*************************************************************************** + engineobserver.h - Observer pattern for engine + ------------------- +begin : Mar 14 2003 +copyright : (C) 2003 by Frederik Holljen +email : fh@ez.no +***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef AMAROK_ENGINEOBSERVER_H +#define AMAROK_ENGINEOBSERVER_H + +#include "engine_fwd.h" + +class EngineSubject; +class MetaBundle; +class QString; + +/** + * if you want to observe the engine, inherit from this class and attach yourself to + * the engine with attach + * Note that all positional information and times are in milliseconds + */ +class EngineObserver +{ +public: + EngineObserver(); + EngineObserver( EngineSubject* ); + virtual ~EngineObserver(); + virtual void engineStateChanged( Engine::State /*state*/, Engine::State /*oldState*/ = Engine::Empty ) {} + virtual void engineNewMetaData( const MetaBundle &/*bundle*/, bool /*trackChanged*/ ) {} + virtual void engineTrackEnded( int /*finalPosition*/, int /*trackLength*/, const QString &/*reason*/ ) {} + virtual void engineVolumeChanged( int /*percent*/ ) {} + virtual void engineTrackPositionChanged( long /*position*/ , bool /*userSeek*/ ) {} + virtual void engineTrackLengthChanged( long /*length*/ ) {} + +private: + EngineSubject *m_subject; +}; + +#include <qptrlist.h> +/** + * Inherited by EngineController. + * Notify observer functionality is captured in this class. + */ +class EngineSubject +{ +public: + void attach( EngineObserver *observer ); + void detach( EngineObserver *observer ); + +protected: + EngineSubject(); + virtual ~EngineSubject(); + void stateChangedNotify( Engine::State /*state*/ ); + void newMetaDataNotify( const MetaBundle &/*bundle*/, bool /*trackChanged*/ ); + void trackEnded( int /*finalPosition*/, int /*trackLength*/, const QString &reason ); + void volumeChangedNotify( int /*percent*/ ); + /* userSeek means the position didn't change due to normal playback */ + void trackPositionChangedNotify( long /*position*/ , bool userSeek=false ); + void trackLengthChangedNotify( long /*length*/ ); + +private: + QPtrList<EngineObserver> Observers; + Engine::State m_oldEngineState; +}; + +#endif // AMAROK_ENGINEOBSERVER_H diff --git a/amarok/src/equalizergraph.cpp b/amarok/src/equalizergraph.cpp new file mode 100644 index 00000000..3714f4d3 --- /dev/null +++ b/amarok/src/equalizergraph.cpp @@ -0,0 +1,203 @@ +/*************************************************************************** + Graphical spline display for equalizer + + (c) 2004 Mark Kretschmann <markey@web.de> + (c) 2005 Markus Brueffer <markus@brueffer.de> + Based on code from XMMS + (c) 1998-2000 Peter Alm, Mikael Alm, Olle Hallnas, Thomas Nilsson and 4Front Technologies +***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "amarokconfig.h" +#include "equalizergraph.h" + +#include <qpainter.h> +#include <qpixmap.h> +#include <qvaluelist.h> + +#include <kapplication.h> + +EqualizerGraph::EqualizerGraph( QWidget* parent ) + : QWidget( parent, 0, Qt::WNoAutoErase ) + , m_backgroundPixmap( new QPixmap() ) + , m_composePixmap( new QPixmap() ) +{ +} + + +EqualizerGraph::~EqualizerGraph() +{ + delete m_backgroundPixmap; + delete m_composePixmap; +} + + +///////////////////////////////////////////////////////////////////////////////////// +// PROTECTED +///////////////////////////////////////////////////////////////////////////////////// + +void +EqualizerGraph::resizeEvent( QResizeEvent* ) +{ + drawBackground(); +} + +QSize +EqualizerGraph::sizeHint() const +{ + return QSize( 100, 60 ); +} + +void +EqualizerGraph::paintEvent( QPaintEvent* ) +{ + bitBlt( m_composePixmap, 0, 0, m_backgroundPixmap ); + + QPainter p( m_composePixmap ); + + // Draw middle line + int middleLineY = (int) ( ( height() - 1 ) / 2.0 + AmarokConfig::equalizerPreamp() * ( height() - 1 ) / 200.0 ); + QPen pen( colorGroup().dark(), 0, Qt::DotLine); + p.setPen( pen ); + p.drawLine( 8, middleLineY, width() - 1, middleLineY ); + + QColor color( colorGroup().highlight() ); + int h, s, v; + color.getHsv( &h, &s, &v ); + + int i, y, ymin, ymax, py = 0; + float x[NUM_BANDS], yf[NUM_BANDS]; + float gains[NUM_BANDS] = { 0, 0, 0, 0, 0, 0, 0, 0, 0}; + + // Don't calculate 0 and NUM_BANDS-1 for accuracy reasons + for ( i = 1; i < NUM_BANDS -1 ; i++) + x[i] = ( width() - 8 ) * i / ( NUM_BANDS -1 ) + 8; + x[ 0 ] = 8; + x[ NUM_BANDS - 1 ] = width() - 1; + + if ( AmarokConfig::equalizerEnabled() ) + for ( i = 0; i < NUM_BANDS; i++ ) + gains[i] = ( height() - 1 ) * AmarokConfig::equalizerGains()[i] / 200.0; + + init_spline( x, gains, NUM_BANDS, yf ); + + for ( i = 8; i < width(); i++ ) { + y = (int) ( ( height() - 1 ) / 2 - eval_spline( x, gains, yf, NUM_BANDS, i ) ); + + if ( y < 0 ) + y = 0; + + if ( y > height() - 1 ) + y = height() - 1; + + if ( i == 8 ) + py = y; + + if ( y < py ) { + ymin = y; + ymax = py; + } else { + ymin = py; + ymax = y; + } + + py = y; + for ( y = ymin; y <= ymax; y++ ) { + // Absolute carthesian coordinate + s = y - ( height() - 1 ) / 2; + s = QABS(s); + + // Normalise to a base of 256 + // short for: s / ( ( height() / 2.0 ) * 255; + s = (int) ( s * 510.0 / height() ); + + color.setHsv( h, 255 - s, v ); + p.setPen( color ); + + p.drawPoint( i, y ); + } + } + + p.end(); + bitBlt( this, 0, 0, m_composePixmap ); +} + + +///////////////////////////////////////////////////////////////////////////////////// +// PRIVATE +///////////////////////////////////////////////////////////////////////////////////// + +void +EqualizerGraph::drawBackground() +{ + m_backgroundPixmap->resize( size() ); + m_composePixmap->resize( size() ); + + m_backgroundPixmap->fill( colorGroup().background().dark( 105 ) ); + QPainter p( m_backgroundPixmap ); + + // Erase background for scale + p.fillRect( 0, 0, 7, height() -1, colorGroup().background()); + + // Draw scale + p.setPen( colorGroup().shadow() ); + p.drawLine( 7, 0, 7, height() - 1 ); + p.drawLine( 0, 0, 7, 0 ); + p.drawLine( 0, height() / 2 - 1, 7, height() / 2 - 1 ); + p.drawLine( 0, height() - 1, 7, height() - 1 ); +} + + +void +EqualizerGraph::init_spline( float* x, float* y, int n, float* y2 ) +{ + int i, k; + float p, qn, sig, un; + QMemArray<float> u(n * sizeof(float)); + + y2[ 0 ] = u[ 0 ] = 0.0; + + for ( i = 1; i < n - 1; i++ ) { + sig = ( (float)x[i] - x[i-1] ) / ( (float)x[i+1] - x[i-1] ); + p = sig * y2[i-1] + 2.0; + y2[i] = ( sig - 1.0 ) / p; + u[i] = ( ( (float)y[i+1] - y[i] ) / ( x[i+1] - x[i] ) ) - ( ( (float)y[i] - y[i-1] ) / ( x[i] - x[i-1] ) ); + u[i] = ( 6.0 * u[i] / ( x[i+1] - x[i-1] ) - sig * u[i-1] ) / p; + } + qn = un = 0.0; + + y2[n-1] = ( un - qn * u[n-2] ) / ( qn * y2[n-2] + 1.0 ); + for ( k = n - 2; k >= 0; k-- ) + y2[k] = y2[k] * y2[k+1] + u[k]; +} + + +float +EqualizerGraph::eval_spline( float xa[], float ya[], float y2a[], int n, float x ) +{ + int klo, khi, k; + float h, b, a; + + klo = 0; + khi = n - 1; + while ( khi - klo > 1 ) { + k = ( khi + klo ) >> 1; + if ( xa[k] > x ) + khi = k; + else + klo = k; + } + h = xa[khi] - xa[klo]; + a = ( xa[khi] - x ) / h; + b = ( x - xa[klo] ) / h; + return ( a * ya[klo] + b * ya[khi] + ( ( a*a*a - a ) * y2a[klo] + ( b*b*b - b ) * y2a[khi] ) * ( h*h ) / 6.0 ); +} + diff --git a/amarok/src/equalizergraph.h b/amarok/src/equalizergraph.h new file mode 100644 index 00000000..df76824f --- /dev/null +++ b/amarok/src/equalizergraph.h @@ -0,0 +1,50 @@ +/*************************************************************************** + Graphical spline display for equalizer + + (c) 2004 Mark Kretschmann <markey@web.de> + Based on code from XMMS + (c) 1998-2000 Peter Alm, Mikael Alm, Olle Hallnas, Thomas Nilsson and 4Front Technologies +***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef AMAROK_EQUALIZERGRAPH_H +#define AMAROK_EQUALIZERGRAPH_H + +#include <qwidget.h> //baseclass + +class QPixmap; + + +class EqualizerGraph : public QWidget +{ + public: + EqualizerGraph( QWidget* parent ); + ~EqualizerGraph(); + QSize sizeHint() const; + + protected: + void resizeEvent( QResizeEvent* ); + void paintEvent( QPaintEvent* ); + + private: + static const int NUM_BANDS = 10; + + void drawBackground(); + + void init_spline( float* x, float* y, int n, float* y2 ); + float eval_spline( float xa[], float ya[], float y2a[], int n, float x ); + + QPixmap* m_backgroundPixmap; + QPixmap* m_composePixmap; +}; + + +#endif /*AMAROK_EQUALIZERGRAPH_H*/ diff --git a/amarok/src/equalizerpresetmanager.cpp b/amarok/src/equalizerpresetmanager.cpp new file mode 100644 index 00000000..62c3ffb9 --- /dev/null +++ b/amarok/src/equalizerpresetmanager.cpp @@ -0,0 +1,196 @@ +/*************************************************************************** + * Copyright (C) 2005 by Markus Brueffer <markus@brueffer.de> * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Steet, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "equalizerpresetmanager.h" + +#include <qdom.h> +#include <qfile.h> +#include <qlayout.h> +#include <qpushbutton.h> +#include <qvbox.h> + +#include <kapplication.h> +#include <kinputdialog.h> +#include <klistview.h> +#include <klocale.h> +#include <kmessagebox.h> +#include <kstandarddirs.h> //locate() + +EqualizerPresetManager::EqualizerPresetManager( QWidget *parent, const char *name ) + : KDialogBase( parent, name, true, i18n("Presets"), Ok | Cancel | Default, Ok, true ) +{ + QWidget *mainWidget = new QWidget( this ); + setMainWidget( mainWidget ); + QHBoxLayout *mainLayout = new QHBoxLayout( mainWidget, 0, spacingHint() ); + + m_presetsView = new KListView( mainWidget, "presetListView" ); + m_presetsView->addColumn( i18n( "Presets" ) ); + m_presetsView->setFullWidth( true ); + connect(m_presetsView, SIGNAL( selectionChanged() ), SLOT( updateButtonState() )); + connect(m_presetsView, SIGNAL( doubleClicked ( QListViewItem*, const QPoint&, int ) ), SLOT( slotRename() )); + mainLayout->addWidget( m_presetsView ); + + QVBoxLayout* buttonsLayout = new QVBoxLayout( mainLayout ); + + m_renameBtn = new QPushButton( i18n("&Rename"), mainWidget, "renameBtn" ); + m_deleteBtn = new QPushButton( i18n("&Delete"), mainWidget, "deleteBtn" ); + + buttonsLayout->addWidget( m_renameBtn ); + buttonsLayout->addWidget( m_deleteBtn ); + + connect(m_renameBtn, SIGNAL( clicked() ), SLOT( slotRename() )); + connect(m_deleteBtn, SIGNAL( clicked() ), SLOT( slotDelete() )); + connect(this, SIGNAL( defaultClicked() ), SLOT( slotDefault() )); + + QSpacerItem* spacer = new QSpacerItem( 20, 40, QSizePolicy::Minimum, QSizePolicy::Expanding ); + buttonsLayout->addItem( spacer ); + + updateButtonState(); + + resize( QSize(300, 250).expandedTo(minimumSizeHint()) ); +} + + +EqualizerPresetManager::~EqualizerPresetManager() +{ +} + +void +EqualizerPresetManager::setPresets(QMap< QString, QValueList<int> > presets) +{ + if ( presets.empty() ) + return; + + m_presets = presets; + m_presetsView->clear(); + + QMap< QString, QValueList<int> >::Iterator end = presets.end(); + for ( QMap< QString, QValueList<int> >::Iterator it = presets.begin(); it != end; ++it ) + if ( it.key() != i18n( "Zero" ) && it.key() != i18n( "Manual" ) ) // Don't add 'Manual' and 'Zero' + new KListViewItem( m_presetsView, it.key() ); +} + +QMap< QString, QValueList<int> > +EqualizerPresetManager::presets() +{ + return m_presets; +} + +void +EqualizerPresetManager::slotRename() +{ + bool ok; + QListViewItem* item = m_presetsView->selectedItem(); + const QString title = KInputDialog::getText( i18n("Rename Equalizer Preset"), + i18n("Enter new preset name:"), item->text(0), &ok, this); + + if ( ok && item->text(0) != title ) { + // Check if the new preset title exists + if ( m_presets.find( title ) != m_presets.end() ) { + int button = KMessageBox::warningYesNo( this, i18n( "A preset with the name %1 already exists. Overwrite?" ).arg( title ) ); + + if ( button != KMessageBox::Yes ) + return; + } + + m_presets[ title ] = m_presets[ item->text(0)]; + m_presets.remove( item->text(0) ); + item->setText(0, title); + } +} + +void +EqualizerPresetManager::slotDefault() +{ + int button = KMessageBox::warningYesNo( this, i18n( "All presets will be deleted and defaults will be restored. Are you sure?" ) ); + + if ( button != KMessageBox::Yes ) + return; + + // Preserve the 'Manual' preset + QValueList<int> manualGains = m_presets[ i18n("Manual") ]; + + // Delete all presets + m_presets.clear(); + + // Create predefined presets 'Zero' and 'Manual' + QValueList<int> zeroGains; + zeroGains << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0; + m_presets[ i18n("Zero") ] = zeroGains; + m_presets[ i18n("Manual") ] = manualGains; + + // Load the default presets + QFile file( locate( "data", "amarok/data/equalizer_presets.xml" ) ); + + QTextStream stream( &file ); + stream.setEncoding( QTextStream::UnicodeUTF8 ); + + QDomDocument d; + + if( !file.open( IO_ReadOnly ) || !d.setContent( stream.read() ) ) + return; + + QDomNode n = d.namedItem( "equalizerpresets" ).namedItem("preset"); + + for( ; !n.isNull(); n = n.nextSibling() ) + { + QDomElement e = n.toElement(); + QString title = e.attribute( "name" ); + + QValueList<int> gains; + gains << e.namedItem( "b0" ).toElement().text().toInt(); + gains << e.namedItem( "b1" ).toElement().text().toInt(); + gains << e.namedItem( "b2" ).toElement().text().toInt(); + gains << e.namedItem( "b3" ).toElement().text().toInt(); + gains << e.namedItem( "b4" ).toElement().text().toInt(); + gains << e.namedItem( "b5" ).toElement().text().toInt(); + gains << e.namedItem( "b6" ).toElement().text().toInt(); + gains << e.namedItem( "b7" ).toElement().text().toInt(); + gains << e.namedItem( "b8" ).toElement().text().toInt(); + gains << e.namedItem( "b9" ).toElement().text().toInt(); + + m_presets[ title ] = gains; + } + + file.close(); + + // Update listview + setPresets( m_presets ); +} + +void +EqualizerPresetManager::slotDelete() +{ + QListViewItem* item = m_presetsView->selectedItem(); + + m_presets.remove( item->text(0) ); + + delete item; +} + +void +EqualizerPresetManager::updateButtonState() +{ + bool selected = ( m_presetsView->selectedItem() != 0 ); + + m_deleteBtn->setEnabled( selected ); + m_renameBtn->setEnabled( selected ); +} + +#include "equalizerpresetmanager.moc" diff --git a/amarok/src/equalizerpresetmanager.h b/amarok/src/equalizerpresetmanager.h new file mode 100644 index 00000000..17dc7882 --- /dev/null +++ b/amarok/src/equalizerpresetmanager.h @@ -0,0 +1,59 @@ +/*************************************************************************** + * Copyright (C) 2005 by Markus Brueffer <markus@brueffer.de> * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Steet, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef AMAROK_EQUALIZERPRESETMANAGER_H +#define AMAROK_EQUALIZERPRESETMANAGER_H + +#include "equalizersetup.h" + +#include <kdialogbase.h> //baseclass + +class QPushButton; +class QStringList; +class KListView; + +class EqualizerPresetManager : public KDialogBase +{ + Q_OBJECT + + public: + EqualizerPresetManager( QWidget *parent = 0, const char *name = 0 ); + virtual ~EqualizerPresetManager(); + + void setPresets(QMap< QString, QValueList<int> > presets); + QMap< QString, QValueList<int> > presets(); + + private slots: + void slotRename(); + void slotDelete(); + void slotDefault(); + + void updateButtonState(); + + private: + QMap< QString, QValueList<int> > m_presets; + KListView* m_presetsView; + + //QPushButton* m_addBtn; + QPushButton* m_renameBtn; + QPushButton* m_deleteBtn; +}; + + +#endif /* AMAROK_EQUALIZERPRESETMANAGER_H */ diff --git a/amarok/src/equalizersetup.cpp b/amarok/src/equalizersetup.cpp new file mode 100644 index 00000000..ec1066c6 --- /dev/null +++ b/amarok/src/equalizersetup.cpp @@ -0,0 +1,495 @@ +/*************************************************************************** + Setup dialog for the equalizer + + (c) 2004 Mark Kretschmann <markey@web.de> + (c) 2005 Seb Ruiz <me@sebruiz.net> + (c) 2005 Markus Brueffer <markus@brueffer.de> +***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "amarok.h" +#include "amarokconfig.h" +#include "enginebase.h" +#include "enginecontroller.h" +#include "equalizergraph.h" +#include "equalizerpresetmanager.h" +#include "equalizersetup.h" +#include "sliderwidget.h" + +#include <qcheckbox.h> +#include <qdom.h> +#include <qfile.h> +#include <qgroupbox.h> +#include <qlabel.h> +#include <qlayout.h> +#include <qpushbutton.h> +#include <qstringlist.h> +#include <qtextstream.h> //presets +#include <qtooltip.h> +#include <qvbox.h> + +#include <kapplication.h> +#include <kcombobox.h> +#include <kiconloader.h> +#include <kinputdialog.h> //presets +#include <klocale.h> +#include <kmessagebox.h> +#include <kpopupmenu.h> +#include <kstandarddirs.h> //locate() +#include <kwin.h> + +EqualizerSetup* EqualizerSetup::s_instance = 0; + + +EqualizerSetup::EqualizerSetup() + : KDialogBase( Amarok::mainWindow(), 0, false, 0, 0, Ok, false ) +{ + using Amarok::Slider; + + s_instance = this; + + kapp->setTopWidget( this ); + setCaption( kapp->makeStdCaption( i18n( "Equalizer" ) ) ); + + // Gives the window a small title bar, and skips a taskbar entry + KWin::setType( winId(), NET::Utility ); + KWin::setState( winId(), NET::SkipTaskbar ); + + QVBox* vbox = makeVBoxMainWidget(); + vbox->setSpacing( KDialog::spacingHint() ); + + // BEGIN Presets + QHBox* presetBox = new QHBox( vbox ); + presetBox->setSpacing( KDialog::spacingHint() ); + + new QLabel( i18n("Presets:"), presetBox ); + + m_presetCombo = new KComboBox( presetBox ); + m_presetCombo->setSizePolicy( QSizePolicy( QSizePolicy::Expanding, QSizePolicy::Preferred ) ); + + QPushButton* presetAdd = new QPushButton( presetBox ); + presetAdd->setIconSet( SmallIconSet( Amarok::icon( "add_playlist" ) ) ); + QToolTip::add( presetAdd, i18n("Add new preset") ); + connect( presetAdd, SIGNAL( clicked() ), SLOT( addPreset() ) ); + + QPushButton* presetConf = new QPushButton( presetBox ); + presetConf->setIconSet( SmallIconSet( Amarok::icon( "configure" ) ) ); + QToolTip::add( presetConf, i18n("Manage presets") ); + connect( presetConf, SIGNAL( clicked() ), SLOT( editPresets() ) ); + + loadPresets(); + connect( m_presetCombo, SIGNAL( activated(int) ), SLOT( presetChanged(int) ) ); + // END Presets + + // BEGIN GroupBox + m_groupBoxSliders = new QGroupBox( 1, Qt::Vertical, i18n("Enable Equalizer"), vbox ); + m_groupBoxSliders->setCheckable( true ); + m_groupBoxSliders->setChecked( AmarokConfig::equalizerEnabled() ); + m_groupBoxSliders->setInsideMargin( KDialog::marginHint() ); + connect( m_groupBoxSliders, SIGNAL( toggled( bool ) ), SLOT( setEqualizerEnabled( bool ) ) ); + + // Helper widget for layouting inside the groupbox + QWidget* slidersLayoutWidget = new QWidget( m_groupBoxSliders ); + slidersLayoutWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + QGridLayout* slidersGridLayout = new QGridLayout( slidersLayoutWidget, 1, 1, 0, KDialog::spacingHint() ); + // END GroupBox + + // BEGIN Preamp slider + m_slider_preamp = new Slider( Qt::Vertical, slidersLayoutWidget, 100 ); + m_slider_preamp->setMinValue( -100 ); + m_slider_preamp->setTickmarks( QSlider::Right ); + m_slider_preamp->setTickInterval( 100 ); + connect( m_slider_preamp, SIGNAL( valueChanged( int ) ), SLOT( setEqualizerParameters() ) ); + slidersGridLayout->addMultiCellWidget(m_slider_preamp, 0, 0, 0, 1, Qt::AlignHCenter ); + + QLabel* preampLabel = new QLabel( i18n("Pre-amp"), slidersLayoutWidget ); + slidersGridLayout->addMultiCellWidget(preampLabel, 1, 1 , 0, 1, Qt::AlignHCenter ); + // END + + // BEGIN Band Sliders + const char *bandLabels[] = { "30", "60", "125", "250", "500", "1k", "2k", "4k", "8k", "16k" }; + + int minWidth = 0; + QFontMetrics fm = fontMetrics(); //apparently it's an expensive call + for ( int i = 0; i < 10; i++ ) { + int w = fm.width( bandLabels[i] ); + if ( w > minWidth ) + minWidth = w; + } + + for ( int i = 0; i < 10; i++ ) { + Slider *slider = new Slider( Qt::Vertical, slidersLayoutWidget ); + QLabel *label = new QLabel( bandLabels[i], slidersLayoutWidget ); + + slider->setMinValue( -100 ); + slider->setMaxValue( +100 ); + slider->setMinimumWidth( minWidth ); + slidersGridLayout->addMultiCellWidget(slider, 0, 0, 2 * i + 2, 2 * i + 3, Qt::AlignHCenter ); + slidersGridLayout->addMultiCellWidget(label, 1, 1, 2 * i + 2, 2 * i + 3, Qt::AlignHCenter ); + m_bandSliders.append( slider ); + + connect( slider, SIGNAL( valueChanged( int ) ), SLOT( setEqualizerParameters() ) ); + connect( slider, SIGNAL( valueChanged( int ) ), SLOT( sliderChanged() ) ); + } + // END + + // BEGIN Equalizer Graph Widget + QGroupBox* graphGBox = new QGroupBox( 2, Qt::Horizontal, 0, vbox ); + graphGBox->setInsideMargin( KDialog::marginHint() ); + + QVBox* graphVBox = new QVBox( graphGBox ); + QLabel* graphLabel1 = new QLabel("+20 db", graphVBox); + QLabel* graphLabel2 = new QLabel("0 db", graphVBox); + QLabel* graphLabel3 = new QLabel("-20 db", graphVBox); + graphLabel1->setAlignment( Qt::AlignRight | Qt::AlignTop ); + graphLabel2->setAlignment( Qt::AlignRight | Qt::AlignVCenter ); + graphLabel3->setAlignment( Qt::AlignRight | Qt::AlignBottom ); + + m_equalizerGraph = new EqualizerGraph( graphGBox ); + m_equalizerGraph->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Expanding ); + // END Graph Widget + + // Fill the combobox + updatePresets( AmarokConfig::equalizerPreset() ); + + // make sure to restore the current preamp value + m_slider_preamp->setValue( AmarokConfig::equalizerPreamp() ); + + // Init sliders + presetChanged( AmarokConfig::equalizerPreset() ); +} + + +EqualizerSetup::~EqualizerSetup() +{ + savePresets(); + s_instance = 0; +} + +void +EqualizerSetup::setActive( bool active ) +{ + m_groupBoxSliders->setChecked( active ); +} + +void +EqualizerSetup::setBands( int preamp, QValueList<int> gains ) +{ + m_slider_preamp->setValue( preamp ); + + // Note: As a side effect, this automatically switches the + // preset to 'Manual', which is by intention + for ( uint i = 0; i < m_bandSliders.count(); i++ ) + m_bandSliders.at(i)->setValue( ( *gains.at(i) ) ); + + setEqualizerParameters(); +} + +void +EqualizerSetup::setPreset( QString name ) +{ + // Look for the preset id and verify name + int i, count = m_presetCombo->count(); + bool found = false; + for( i = 0; i < count; i++ ) { + if ( m_presetCombo->text( i ) == name ) { + found = true; + break; + } + } + + if ( found ) { + m_presetCombo->setCurrentItem( i ); + presetChanged( name ); + } +} + +///////////////////////////////////////////////////////////////////////////////////// +// EQUALIZER PRESETS +///////////////////////////////////////////////////////////////////////////////////// + +QString +EqualizerSetup::presetsCache() const +{ + // returns the playlists stats cache file + return Amarok::saveLocation() + "equalizerpresets_save.xml"; +} + + +void +EqualizerSetup::loadPresets() +{ + // Create predefined presets 'Zero' and 'Manual' + QValueList<int> zeroGains; + zeroGains << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0; + m_presets[ i18n("Manual") ] = zeroGains; + m_presets[ i18n("Zero") ] = zeroGains; + + QFile file( presetsCache() ); + if ( !file.exists() ) + file.setName( locate( "data", "amarok/data/equalizer_presets.xml" ) ); + + QTextStream stream( &file ); + stream.setEncoding( QTextStream::UnicodeUTF8 ); + + QDomDocument d; + + if( !file.open( IO_ReadOnly ) || !d.setContent( stream.read() ) ) { + // Everything went wrong, so at least provide the two predefined presets + updatePresets( AmarokConfig::equalizerPreset() ); + return; + } + + QDomNode n = d.namedItem( "equalizerpresets" ).namedItem("preset"); + + for( ; !n.isNull(); n = n.nextSibling() ) + { + QDomElement e = n.toElement(); + QString title = e.attribute( "name" ); + + QValueList<int> gains; + gains << e.namedItem( "b0" ).toElement().text().toInt(); + gains << e.namedItem( "b1" ).toElement().text().toInt(); + gains << e.namedItem( "b2" ).toElement().text().toInt(); + gains << e.namedItem( "b3" ).toElement().text().toInt(); + gains << e.namedItem( "b4" ).toElement().text().toInt(); + gains << e.namedItem( "b5" ).toElement().text().toInt(); + gains << e.namedItem( "b6" ).toElement().text().toInt(); + gains << e.namedItem( "b7" ).toElement().text().toInt(); + gains << e.namedItem( "b8" ).toElement().text().toInt(); + gains << e.namedItem( "b9" ).toElement().text().toInt(); + + m_presets[ title ] = gains; + } + + file.close(); +} + + +void +EqualizerSetup::savePresets() +{ + QFile file( presetsCache() ); + + if( !file.open( IO_WriteOnly ) ) return; + + QDomDocument doc; + QDomElement e = doc.createElement("equalizerpresets"); + e.setAttribute( "product", "Amarok" ); + e.setAttribute( "version", APP_VERSION ); + e.setAttribute( "formatversion", "1.1" ); + + doc.appendChild( e ); + + QStringList info; + info << "b0" << "b1" << "b2" << "b3" << "b4" + << "b5" << "b6" << "b7" << "b8" << "b9"; + + for( uint x = 0; x < m_presets.count(); x++ ) + { + const QString title = m_presetCombo->text( x ); + + // don't save the 'Zero' preset + if ( title == i18n("Zero") ) + continue; + + QValueList<int> gains = m_presets[ title ]; + + QDomElement i = doc.createElement("preset"); + i.setAttribute( "name", title ); + + QDomElement attr; + QDomText t; + for( uint y=0; y < info.count(); y++ ) + { + attr = doc.createElement( info[y] ); + t = doc.createTextNode( QString::number( gains.first() ) ); + attr.appendChild( t ); + i.appendChild( attr ); + gains.pop_front(); + } + e.appendChild( i ); + } + + QTextStream stream( &file ); + stream.setEncoding( QTextStream::UnicodeUTF8 ); + stream << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"; + stream << doc.toString(); + file.close(); +} + +void +EqualizerSetup::editPresets() +{ + EqualizerPresetManager * editor = new EqualizerPresetManager(this); + editor->setPresets(m_presets); + + if ( editor->exec() ) { + QMap< QString, QValueList<int> > presets = editor->presets(); + + QString currentTitle = m_presetCombo->currentText(); + QValueList<int> currentGains= m_presets[ currentTitle ]; + + QString newTitle = currentTitle; + + // Check if the selected item was renamed + if ( presets.find( currentTitle ) == presets.end() || currentGains != presets[ currentTitle ] ) { + + // Find the new name + QMap< QString, QValueList<int> >::Iterator end = presets.end(); + for ( QMap< QString, QValueList<int> >::Iterator it = presets.begin(); it != end; ++it ) { + if ( it.data() == currentGains ) { + newTitle = it.key(); + break; + } + } + } + + m_presets = presets; + updatePresets( newTitle ); + } + + delete editor; +} + +void +EqualizerSetup::addPreset() +{ + bool ok; + const QString title = KInputDialog::getText( i18n("Add Equalizer Preset"), + i18n("Enter preset name:"), i18n("Untitled"), &ok, this); + + if (ok) { + // Check if the new preset title exists + if ( m_presets.find( title ) != m_presets.end() ) { + int button = KMessageBox::warningYesNo( this, i18n( "A preset with the name %1 already exists. Overwrite?" ).arg( title ) ); + + if ( button != KMessageBox::Yes ) + return; + } + + // Add the new preset based on the current slider positions + QValueList <int> gains; + for ( uint i = 0; i < m_bandSliders.count(); i++ ) + gains += m_bandSliders.at( i )->value(); + m_presets[ title ] = gains; + + // Rebuild the combobox + updatePresets(title); + + // Save + setEqualizerParameters(); + } +} + +void +EqualizerSetup::updatePresets(QString selectTitle) +{ + // Save the selected item + if ( selectTitle.isEmpty() ) + selectTitle = m_presetCombo->currentText(); + + // Sort titles + QStringList titles; + QMap< QString, QValueList<int> >::Iterator end = m_presets.end(); + for ( QMap< QString, QValueList<int> >::Iterator it = m_presets.begin(); it != end; ++it ) + titles << it.key(); + + titles.sort(); + + // rebuild preset combobox and look for the previously selected title + int i = 0; + int newIndex = -1; + m_presetCombo->clear(); + QStringList::Iterator titlesEnd = titles.end(); + for ( QStringList::Iterator it = titles.begin(); it != titlesEnd; ++it ) { + m_presetCombo->insertItem( *it ); + if ( *it == selectTitle ) + newIndex = i; + if ( *it == i18n("Manual") ) + m_manualPos = i; + i++; + } + + if ( newIndex == -1 ) + newIndex = m_manualPos; + + m_presetCombo->setCurrentItem( newIndex ); +} + +///////////////////////////////////////////////////////////////////////////////////// +// PRIVATE SLOTS +///////////////////////////////////////////////////////////////////////////////////// + +void +EqualizerSetup::presetChanged( int id ) //SLOT +{ + presetChanged( m_presetCombo->text(id) ); +} + +void +EqualizerSetup::presetChanged( QString title ) //SLOT +{ + const QValueList<int> gains = m_presets[ title ]; + + for ( uint i = 0; i < m_bandSliders.count(); i++ ) { + // Block signals to prevent unwanted setting to 'Manual' + m_bandSliders.at(i)->blockSignals(true); + m_bandSliders.at(i)->setValue( ( *gains.at(i) ) ); + m_bandSliders.at(i)->blockSignals(false); + } + + setEqualizerParameters(); +} + +void +EqualizerSetup::setEqualizerEnabled( bool active ) //SLOT +{ + EngineController::engine()->setEqualizerEnabled( active ); + AmarokConfig::setEqualizerEnabled( active ); + + if( active ) + //this way the developer of the eq doesn't have to cache the eq values + setEqualizerParameters(); + else + // zero the graph + m_equalizerGraph->update(); +} + + +void +EqualizerSetup::setEqualizerParameters() //SLOT +{ + AmarokConfig::setEqualizerPreamp( m_slider_preamp->value() ); + AmarokConfig::setEqualizerPreset( m_presetCombo->currentText() ); + AmarokConfig::setEqualizerGains ( m_presets[ m_presetCombo->currentText() ] ); + + // Transfer values to the engine if the EQ is enabled + if ( AmarokConfig::equalizerEnabled() ) + EngineController::engine()->setEqualizerParameters( m_slider_preamp->value(), m_presets[ m_presetCombo->currentText() ] ); + + m_equalizerGraph->update(); +} + + +void +EqualizerSetup::sliderChanged() //SLOT +{ + m_presetCombo->setCurrentItem( m_manualPos ); + + QValueList<int> gains; + for ( uint i = 0; i < m_bandSliders.count(); i++ ) + gains += m_bandSliders.at( i )->value(); + + m_presets[ i18n("Manual") ] = gains; +} + +#include "equalizersetup.moc" diff --git a/amarok/src/equalizersetup.h b/amarok/src/equalizersetup.h new file mode 100644 index 00000000..0be7ee04 --- /dev/null +++ b/amarok/src/equalizersetup.h @@ -0,0 +1,77 @@ +/*************************************************************************** + Setup dialog for the equalizer + + (c) 2004 Mark Kretschmann <markey@web.de> + (c) 2005 Seb Ruiz <me@sebruiz.net> + (c) 2005 Markus Brueffer <markus@brueffer.de> +***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef AMAROK_EQUALIZERSETUP_H +#define AMAROK_EQUALIZERSETUP_H + +#include <qptrlist.h> //stack alloc +#include <kdialogbase.h> + +class EqualizerGraph; +class QGroupBox; +class QCheckBox; +class KComboBox; +class KPopupMenu; + +namespace Amarok { class Slider; } + + +class EqualizerSetup : public KDialogBase +{ + Q_OBJECT + + public: + static EqualizerSetup* instance() { return s_instance ? s_instance : new EqualizerSetup(); } + static bool isInstantiated() { return s_instance ? true : false; } + + EqualizerSetup(); + ~EqualizerSetup(); + + // for use by DCOP + void setActive( bool active ); + void setBands( int preamp, QValueList<int> gains ); + void setPreset( QString name ); + + private slots: + void presetChanged( int id ); + void presetChanged( QString title ); + void sliderChanged(); + void setEqualizerEnabled( bool ); + void setEqualizerParameters(); + void editPresets(); + void addPreset(); + + private: + static EqualizerSetup* s_instance; + + void loadPresets(); + void savePresets(); + void updatePresets(QString selectTitle = QString::null); + QString presetsCache() const; + + Amarok::Slider* m_slider_preamp; + EqualizerGraph* m_equalizerGraph; + QPtrList<Amarok::Slider> m_bandSliders; + + QGroupBox* m_groupBoxSliders; + KComboBox* m_presetCombo; + uint m_manualPos; + + QMap< QString, QValueList<int> > m_presets; +}; + +#endif /*AMAROK_EQUALIZERSETUP_H*/ diff --git a/amarok/src/expression.cpp b/amarok/src/expression.cpp new file mode 100644 index 00000000..bd120b38 --- /dev/null +++ b/amarok/src/expression.cpp @@ -0,0 +1,191 @@ +/* + Copyright (c) 2006 Gábor Lehel <illissius@gmail.com> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "expression.h" + +ExpressionParser::ExpressionParser( const QString &expression ) + : m_expression( expression ) + , m_state( ExpectMinus ) + , m_haveGroup( false ) + , m_inQuote( false ) + , m_inOrGroup( false ) +{ } + +ParsedExpression ExpressionParser::parse() +{ + const uint length = m_expression.length(); + for( uint pos = 0; pos < length; ++pos ) + parseChar( m_expression.constref( pos ) ); + finishedToken(); + finishedOrGroup(); + return m_parsed; +} + +ParsedExpression ExpressionParser::parse( const QString &expression ) //static +{ + ExpressionParser p( expression ); + return p.parse(); +} + +bool ExpressionParser::isAdvancedExpression( const QString &expression ) //static +{ + return ( expression.contains( '"' ) || + expression.contains( ':' ) || + expression.contains( '-' ) || + expression.contains( "AND" ) || + expression.contains( "OR" ) ); +} + +/* PRIVATE */ + +void ExpressionParser::parseChar( const QChar &c ) +{ + if( m_inQuote && c != '"' ) + m_string += c; + else if( c.isSpace() ) + handleSpace( c ); + else if( c == '-' ) + handleMinus( c ); + else if( c == ':' ) + handleColon( c ); + else if( c == '>' || c == '<' ) + handleMod( c ); + else if( c == '"' ) + handleQuote( c ); + else + handleChar( c ); +} + +void ExpressionParser::handleSpace( const QChar& ) +{ + if( m_state > ExpectMinus ) + finishedToken(); +} + +void ExpressionParser::handleMinus( const QChar &c ) +{ + if( m_state == ExpectMinus ) + { + m_element.negate = true; + m_state = ExpectField; + } + else + handleChar( c ); +} + +void ExpressionParser::handleColon( const QChar &c ) +{ + if( m_state <= ExpectField && !m_string.isEmpty() ) + { + m_element.field = m_string; + m_string = QString::null; + m_state = ExpectMod; + } + else + handleChar( c ); +} + +void ExpressionParser::handleMod( const QChar &c ) +{ + if( m_state == ExpectMod ) + { + m_element.match = ( c == '>' ) ? expression_element::More : expression_element::Less; + m_state = ExpectText; + } + else + handleChar( c ); +} + +void ExpressionParser::handleQuote( const QChar& ) +{ + if( m_inQuote ) + { + finishedElement(); + m_inQuote = false; + } + else + { + if( !m_string.isEmpty() ) + finishedToken(); + m_state = ExpectText; + m_inQuote = true; + } +} + +void ExpressionParser::handleChar( const QChar &c ) +{ + m_string += c; + if( m_state <= ExpectField ) + m_state = ExpectField; + else if( m_state <= ExpectText ) + m_state = ExpectText; +} + +void ExpressionParser::finishedToken() +{ + enum { And, Or, Neither }; + int s; + if( m_haveGroup || !m_element.field.isEmpty() ) + s = Neither; + else if( m_string == "AND" ) + s = And; + else if( m_string == "OR" ) + s = Or; + else + s = Neither; + + if( s == Neither ) + finishedElement(); + else + { + m_haveGroup = true; + + if( s == Or ) + m_inOrGroup = true; + else + finishedOrGroup(); + + m_string = QString::null; + m_state = ExpectMinus; + } +} + +void ExpressionParser::finishedElement() +{ + if( !m_inOrGroup ) + finishedOrGroup(); + m_inOrGroup = m_haveGroup = false; + m_element.text = m_string; + m_string = QString::null; + + if( !m_element.text.isEmpty() || !m_element.field.isEmpty() ) + m_or.append( m_element ); + + m_element = expression_element(); + m_state = ExpectMinus; +} + +void ExpressionParser::finishedOrGroup() +{ + if( !m_or.isEmpty() ) + m_parsed.append( m_or ); + m_or.clear(); + m_inOrGroup = false; +} + diff --git a/amarok/src/expression.h b/amarok/src/expression.h new file mode 100644 index 00000000..e24e7806 --- /dev/null +++ b/amarok/src/expression.h @@ -0,0 +1,72 @@ +/* + Copyright (c) 2006 Gábor Lehel <illissius@gmail.com> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef AMAROK_EXPRESSION_H +#define AMAROK_EXPRESSION_H + +#include <qstring.h> +#include <qvaluevector.h> + +struct expression_element +{ + QString field; + QString text; + bool negate: 1; + enum { Contains, Less, More } match: 2; + expression_element(): negate( false ), match( Contains ) { } +}; +typedef QValueVector<expression_element> or_list; + +typedef QValueVector<or_list> ParsedExpression; + +class ExpressionParser +{ + public: + ExpressionParser( const QString &expression ); + ParsedExpression parse(); + static ParsedExpression parse( const QString &expression ); + + static bool isAdvancedExpression( const QString &expression ); + + private: + void parseChar( const QChar &c ); + void handleSpace( const QChar &c ); + void handleMinus( const QChar &c ); + void handleColon( const QChar &c ); + void handleMod( const QChar &c ); + void handleQuote( const QChar &c ); + void handleChar( const QChar &c ); + void finishedToken(); + void finishedElement(); + void finishedOrGroup(); + + const QString &m_expression; + enum State { ExpectMinus, ExpectField, ExpectMod, ExpectText }; + int m_state; + bool m_haveGroup; + bool m_inQuote; + bool m_inOrGroup; + QString m_string; + expression_element m_element; + or_list m_or; + ParsedExpression m_parsed; +}; + + +#endif diff --git a/amarok/src/fht.cpp b/amarok/src/fht.cpp new file mode 100644 index 00000000..27d377b6 --- /dev/null +++ b/amarok/src/fht.cpp @@ -0,0 +1,242 @@ +// FHT - Fast Hartley Transform Class +// +// Copyright (C) 2004 Melchior FRANZ - mfranz@kde.org +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation; either version 2 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, 51 Franklin Steet, Fifth Floor, Boston, MA 02110-1301, USA +// +// $Id: fht.cpp 581133 2006-09-05 12:14:35Z seb $ + +#include <math.h> +#include <string.h> +#include "fht.h" + + +FHT::FHT(int n) : + m_buf(0), + m_tab(0), + m_log(0) +{ + if (n < 3) { + m_num = 0; + m_exp2 = -1; + return; + } + m_exp2 = n; + m_num = 1 << n; + if (n > 3) { + m_buf = new float[m_num]; + m_tab = new float[m_num * 2]; + makeCasTable(); + } +} + + +FHT::~FHT() +{ + delete[] m_buf; + delete[] m_tab; + delete[] m_log; +} + + +void FHT::makeCasTable(void) +{ + float d, *costab, *sintab; + int ul, ndiv2 = m_num / 2; + + for (costab = m_tab, sintab = m_tab + m_num / 2 + 1, ul = 0; ul < m_num; ul++) { + d = M_PI * ul / ndiv2; + *costab = *sintab = cos(d); + + costab += 2, sintab += 2; + if (sintab > m_tab + m_num * 2) + sintab = m_tab + 1; + } +} + + +float* FHT::copy(float *d, float *s) +{ + return (float *)memcpy(d, s, m_num * sizeof(float)); +} + + +float* FHT::clear(float *d) +{ + return (float *)memset(d, 0, m_num * sizeof(float)); +} + + +void FHT::scale(float *p, float d) +{ + for (int i = 0; i < (m_num / 2); i++) + *p++ *= d; +} + + +void FHT::ewma(float *d, float *s, float w) +{ + for (int i = 0; i < (m_num / 2); i++, d++, s++) + *d = *d * w + *s * (1 - w); +} + + +void FHT::logSpectrum(float *out, float *p) +{ + int n = m_num / 2, i, j, k, *r; + if (!m_log) { + m_log = new int[n]; + float f = n / log10((double)n); + for (i = 0, r = m_log; i < n; i++, r++) { + j = int(rint(log10(i + 1.0) * f)); + *r = j >= n ? n - 1 : j; + } + } + semiLogSpectrum(p); + *out++ = *p = *p / 100; + for (k = i = 1, r = m_log; i < n; i++) { + j = *r++; + if (i == j) + *out++ = p[i]; + else { + float base = p[k - 1]; + float step = (p[j] - base) / (j - (k - 1)); + for (float corr = 0; k <= j; k++, corr += step) + *out++ = base + corr; + } + } +} + + +void FHT::semiLogSpectrum(float *p) +{ + float e; + power2(p); + for (int i = 0; i < (m_num / 2); i++, p++) { + e = 10.0 * log10(sqrt(*p * .5)); + *p = e < 0 ? 0 : e; + } +} + + +void FHT::spectrum(float *p) +{ + power2(p); + for (int i = 0; i < (m_num / 2); i++, p++) + *p = (float)sqrt(*p * .5); +} + + +void FHT::power(float *p) +{ + power2(p); + for (int i = 0; i < (m_num / 2); i++) + *p++ *= .5; +} + + +void FHT::power2(float *p) +{ + int i; + float *q; + _transform(p, m_num, 0); + + *p = (*p * *p), *p += *p, p++; + + for (i = 1, q = p + m_num - 2; i < (m_num / 2); i++, --q) + *p = (*p * *p) + (*q * *q), p++; +} + + +void FHT::transform(float *p) +{ + if (m_num == 8) + transform8(p); + else + _transform(p, m_num, 0); +} + + +void FHT::transform8(float *p) +{ + float a, b, c, d, e, f, g, h, b_f2, d_h2; + float a_c_eg, a_ce_g, ac_e_g, aceg, b_df_h, bdfh; + + a = *p++, b = *p++, c = *p++, d = *p++; + e = *p++, f = *p++, g = *p++, h = *p; + b_f2 = (b - f) * M_SQRT2; + d_h2 = (d - h) * M_SQRT2; + + a_c_eg = a - c - e + g; + a_ce_g = a - c + e - g; + ac_e_g = a + c - e - g; + aceg = a + c + e + g; + + b_df_h = b - d + f - h; + bdfh = b + d + f + h; + + *p = a_c_eg - d_h2; + *--p = a_ce_g - b_df_h; + *--p = ac_e_g - b_f2; + *--p = aceg - bdfh; + *--p = a_c_eg + d_h2; + *--p = a_ce_g + b_df_h; + *--p = ac_e_g + b_f2; + *--p = aceg + bdfh; +} + + +void FHT::_transform(float *p, int n, int k) +{ + if (n == 8) { + transform8(p + k); + return; + } + + int i, j, ndiv2 = n / 2; + float a, *t1, *t2, *t3, *t4, *ptab, *pp; + + for (i = 0, t1 = m_buf, t2 = m_buf + ndiv2, pp = &p[k]; i < ndiv2; i++) + *t1++ = *pp++, *t2++ = *pp++; + + memcpy(p + k, m_buf, sizeof(float) * n); + + _transform(p, ndiv2, k); + _transform(p, ndiv2, k + ndiv2); + + j = m_num / ndiv2 - 1; + t1 = m_buf; + t2 = t1 + ndiv2; + t3 = p + k + ndiv2; + ptab = m_tab; + pp = p + k; + + a = *ptab++ * *t3++; + a += *ptab * *pp; + ptab += j; + + *t1++ = *pp + a; + *t2++ = *pp++ - a; + + for (i = 1, t4 = p + k + n; i < ndiv2; i++, ptab += j) { + a = *ptab++ * *t3++; + a += *ptab * *--t4; + + *t1++ = *pp + a; + *t2++ = *pp++ - a; + } + memcpy(p + k, m_buf, sizeof(float) * n); +} + diff --git a/amarok/src/fht.h b/amarok/src/fht.h new file mode 100644 index 00000000..9dbab83b --- /dev/null +++ b/amarok/src/fht.h @@ -0,0 +1,119 @@ +// FHT - Fast Hartley Transform Class +// +// Copyright (C) 2004 Melchior FRANZ - mfranz@kde.org +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation; either version 2 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, 51 Franklin Steet, Fifth Floor, Boston, MA 02110-1301, USA +// +// $Id: fht.h 439804 2005-07-28 23:45:59Z mueller $ + +#ifndef FHT_H +#define FHT_H + +/** + * Implementation of the Hartley Transform after Bracewell's discrete + * algorithm. The algorithm is subject to US patent No. 4,646,256 (1987) + * but was put into public domain by the Board of Trustees of Stanford + * University in 1994 and is now freely available[1]. + * + * [1] Computer in Physics, Vol. 9, No. 4, Jul/Aug 1995 pp 373-379 + */ +class FHT +{ + int m_exp2; + int m_num; + float *m_buf; + float *m_tab; + int *m_log; + + /** + * Create a table of "cas" (cosine and sine) values. + * Has only to be done in the constructor and saves from + * calculating the same values over and over while transforming. + */ + void makeCasTable(); + + /** + * Recursive in-place Hartley transform. For internal use only! + */ + void _transform(float *, int, int); + + public: + /** + * Prepare transform for data sets with @f$2^n@f$ numbers, whereby @f$n@f$ + * should be at least 3. Values of more than 3 need a trigonometry table. + * @see makeCasTable() + */ + FHT(int); + + ~FHT(); + inline int sizeExp() const { return m_exp2; } + inline int size() const { return m_num; } + float *copy(float *, float *); + float *clear(float *); + void scale(float *, float); + + /** + * Exponentially Weighted Moving Average (EWMA) filter. + * @param d is the filtered data. + * @param s is fresh input. + * @param w is the weighting factor. + */ + void ewma(float *d, float *s, float w); + + /** + * Logarithmic audio spectrum. Maps semi-logarithmic spectrum + * to logarithmic frequency scale, interpolates missing values. + * A logarithmic index map is calculated at the first run only. + * @param p is the input array. + * @param out is the spectrum. + */ + void logSpectrum(float *out, float *p); + + /** + * Semi-logarithmic audio spectrum. + */ + void semiLogSpectrum(float *); + + /** + * Fourier spectrum. + */ + void spectrum(float *); + + /** + * Calculates a mathematically correct FFT power spectrum. + * If further scaling is applied later, use power2 instead + * and factor the 0.5 in the final scaling factor. + * @see FHT::power2() + */ + void power(float *); + + /** + * Calculates an FFT power spectrum with doubled values as a + * result. The values need to be multiplied by 0.5 to be exact. + * Note that you only get @f$2^{n-1}@f$ power values for a data set + * of @f$2^n@f$ input values. This is the fastest transform. + * @see FHT::power() + */ + void power2(float *); + + /** + * Discrete Hartley transform of data sets with 8 values. + */ + void transform8(float *); + + void transform(float *); +}; + +#endif diff --git a/amarok/src/filebrowser.cpp b/amarok/src/filebrowser.cpp new file mode 100644 index 00000000..20e47f86 --- /dev/null +++ b/amarok/src/filebrowser.cpp @@ -0,0 +1,710 @@ +/* This file is part of the KDE project + Copyright (C) 2004 Mark Kretschmann <markey@web.de> + Copyright (C) 2003 Alexander Dymo <cloudtemple@mksat.net> + Copyright (C) 2003 Roberto Raggi <roberto@kdevelop.org> + Copyright (C) 2001 Christoph Cullmann <cullmann@kde.org> + Copyright (C) 2001 Joseph Wenninger <jowenn@kde.org> + Copyright (C) 2001 Anders Lund <anders.lund@lund.tdcadsl.dk> + + This library 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 library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Steet, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "amarok.h" +#include "browserToolBar.h" +#include "clicklineedit.h" +#include "enginecontroller.h" +#include "filebrowser.h" +#include "k3bexporter.h" + +#include <kaction.h> +#include <kapplication.h> +#include "kbookmarkhandler.h" +#include <kdiroperator.h> +#include <kiconloader.h> +#include <kio/netaccess.h> +#include <klistview.h> +#include <klocale.h> +#include <kpopupmenu.h> +#include <kpushbutton.h> ///@see SearchPane +#include <ktoolbarbutton.h> ///@see ctor +#include <kurlcombobox.h> +#include <kurlcompletion.h> + +#include "mediabrowser.h" +#include "medium.h" +#include "mydirlister.h" +#include "mydiroperator.h" +#include "playlist.h" +#include "playlistbrowser.h" +#include "playlistloader.h" +#include "playlistwindow.h" +#include "collectionbrowser.h" +#include "statusbar.h" +#include "tagdialog.h" + +#include <qdir.h> +#include <qhbox.h> +#include <qiconview.h> +#include <qlabel.h> +#include <qtimer.h> +#include <qtooltip.h> + + + +//BEGIN Constructor/destructor + +FileBrowser::FileBrowser( const char * name, Medium * medium ) + : QVBox( 0, name ) +{ + KActionCollection *actionCollection; + SearchPane *searchPane; + + KURL *location; + + // Try to keep filebrowser working even if not in a medium context + // so if a medium object not passed in, keep earlier behavior + if (!medium) { + m_medium = 0; + location = new KURL( Amarok::config( "Filebrowser" )->readPathEntry( "Location", QDir::homeDirPath() ) ); + KFileItem *currentFolder = new KFileItem( KFileItem::Unknown, KFileItem::Unknown, *location ); + //KIO sucks, NetAccess::exists puts up a dialog and has annoying error message boxes + //if there is a problem so there is no point in using it anyways. + //so... setting the diroperator to ~ is the least sucky option + if ( !location->isLocalFile() || !currentFolder->isReadable() ) { + delete location; + location = new KURL( QDir::homeDirPath() ) ; + } + } + else{ + m_medium = medium; + location = new KURL( m_medium->mountPoint() ); + } + + KActionCollection* ac = new KActionCollection( this ); + KStdAction::selectAll( this, SLOT( selectAll() ), ac, "filebrowser_select_all" ); + + KToolBar *toolbar = new Browser::ToolBar( this ); + + { //Filter LineEdit + KToolBar* searchToolBar = new Browser::ToolBar( this ); + KToolBarButton *button = new KToolBarButton( "locationbar_erase", 0, searchToolBar ); + m_filter = new ClickLineEdit( i18n( "Enter search terms here" ), searchToolBar ); + + searchToolBar->setStretchableWidget( m_filter ); + + connect( button, SIGNAL(clicked()), m_filter, SLOT(clear()) ); + + QToolTip::add( button, i18n( "Clear search field" ) ); + QToolTip::add( m_filter, i18n( "Enter space-separated terms to search in the directory-listing" ) ); + } + + { //Directory Listing + QVBox *container; QHBox *box; + + container = new QVBox( this ); + container->setFrameStyle( m_filter->frameStyle() ); + container->setMargin( 3 ); + container->setSpacing( 2 ); + container->setBackgroundMode( Qt::PaletteBase ); + + box = new QHBox( container ); + box->setMargin( 3 ); + box->setBackgroundMode( Qt::PaletteBase ); + + //folder selection combo box + m_combo = new KURLComboBox( KURLComboBox::Directories, true, box, "path combo" ); + + if (!m_medium){ + m_combo->setCompletionObject( new KURLCompletion( KURLCompletion::DirCompletion ) ); + m_combo->setAutoDeleteCompletionObject( true ); + } + m_combo->setMaxItems( 9 ); + m_combo->setURLs( Amarok::config( "Filebrowser" )->readPathListEntry( "Dir History" ) ); + + if (!m_medium) + m_combo->lineEdit()->setText( location->path() ); + else + m_combo->lineEdit()->setText( "/" ); + + //The main widget with file listings and that + m_dir = new MyDirOperator( *location, container, m_medium ); + m_dir->setEnableDirHighlighting( true ); + m_dir->setMode( KFile::Mode((int)KFile::Files | (int)KFile::Directory) ); //allow selection of multiple files + dirs + m_dir->setOnlyDoubleClickSelectsFiles( true ); //Amarok type settings + m_dir->readConfig( Amarok::config( "Filebrowser" ) ); + m_dir->setView( KFile::Default ); //will set userconfigured view, will load URL + m_dir->setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Expanding ); + m_dir->setAcceptDrops( true ); + //Automatically open folder after hovering above it...probably a good thing + //but easily disabled by commenting this line out + //Disabled for now because can't show . and .. folders. + //TODO: Find out a way to fix this? + //m_dir->setDropOptions( KFileView::AutoOpenDirs ); + + static_cast<QFrame*>(m_dir->viewWidget())->setFrameStyle( QFrame::NoFrame ); + static_cast<QIconView*>(m_dir->viewWidget())->setSpacing( 1 ); + + actionCollection = m_dir->actionCollection(); + + searchPane = new SearchPane( this ); + + setStretchFactor( container, 2 ); + } + + { + QPopupMenu* const menu = static_cast<KActionMenu*>(actionCollection->action("popupMenu"))->popupMenu(); + + menu->clear(); + menu->insertItem( SmallIconSet( Amarok::icon( "files" ) ), i18n( "&Load" ), MakePlaylist ); + menu->insertItem( SmallIconSet( Amarok::icon( "add_playlist" ) ), i18n( "&Append to Playlist" ), AppendToPlaylist ); + menu->insertItem( SmallIconSet( Amarok::icon( "queue_track" ) ), i18n( "&Queue Track" ), QueueTrack ); + menu->insertItem( SmallIconSet( Amarok::icon( "queue_track" ) ), i18n( "&Queue Tracks" ), QueueTracks ); + + menu->insertItem( SmallIconSet( Amarok::icon( "save" ) ), i18n( "&Save as Playlist..." ), SavePlaylist ); + menu->insertSeparator(); + + if (!m_medium) + menu->insertItem( SmallIconSet( Amarok::icon( "device" ) ), i18n( "&Transfer to Media Device" ), MediaDevice ); + + menu->insertItem( SmallIconSet( Amarok::icon( "collection" ) ), i18n( "&Organize Files..." ), OrganizeFiles ); + menu->insertItem( SmallIconSet( Amarok::icon( "collection" ) ), i18n( "&Copy Files to Collection..." ), CopyToCollection ); + menu->insertItem( SmallIconSet( Amarok::icon( "collection" ) ), i18n( "&Move Files to Collection..." ), MoveToCollection ); + menu->insertItem( SmallIconSet( Amarok::icon( "burn" ) ), i18n("Burn to CD..."), BurnCd ); + menu->insertSeparator(); + menu->insertItem( i18n( "&Select All Files" ), SelectAllFiles ); + menu->insertSeparator(); + actionCollection->action( "delete" )->setIcon( Amarok::icon( "remove" ) ); + actionCollection->action( "delete" )->plug( menu ); + menu->insertSeparator(); + menu->insertItem( SmallIconSet( Amarok::icon( "info" ) ), i18n( "Edit Track &Information..." ), EditTags ); + actionCollection->action( "properties" )->plug( menu ); + + menu->setItemEnabled( BurnCd, K3bExporter::isAvailable() ); + + connect( menu, SIGNAL(aboutToShow()), SLOT(prepareContextMenu()) ); + connect( menu, SIGNAL(activated( int )), SLOT(contextMenuActivated( int )) ); + } + + { + KActionMenu *a; + + a = static_cast<KActionMenu*>( actionCollection->action( "sorting menu" ) ); + a->setIcon( Amarok::icon( "configure" ) ); + a->setDelayed( false ); //TODO should be done by KDirOperator + + actionCollection->action( "delete" )->setShortcut( KShortcut( SHIFT + Key_Delete ) ); + + a = new KActionMenu( i18n("Bookmarks"), "bookmark", actionCollection, "bookmarks" ); + a->setDelayed( false ); + + new KBookmarkHandler( m_dir, a->popupMenu() ); + } + + { + if ( KAction *a = actionCollection->action( "up" ) ) + a->plug( toolbar ); + if ( KAction *a = actionCollection->action( "back" ) ) + a->plug( toolbar ); + if ( KAction *a = actionCollection->action( "forward" ) ) + a->plug( toolbar ); + if ( KAction *a = actionCollection->action( "home" ) ) + a->plug( toolbar ); + if ( KAction *a = actionCollection->action( "reload" ) ) { + a->setIcon( Amarok::icon( "refresh" ) ); + a->plug( toolbar ); + } + + toolbar->insertLineSeparator(); + + if ( KAction *a = actionCollection->action( "short view" ) ) + a->plug( toolbar ); + if ( KAction *a = actionCollection->action( "detailed view" ) ) + a->plug( toolbar ); + + toolbar->insertLineSeparator(); + + if ( KAction *a = actionCollection->action( "sorting menu" ) ) + a->plug( toolbar ); + if ( KAction *a = actionCollection->action( "bookmarks" ) ) + a->plug( toolbar ); + + + KAction *gotoCurrent = new KAction( i18n("Go To Current Track Folder"), Amarok::icon( "music" ), 0, + this, SLOT( gotoCurrentFolder() ), actionCollection ); + gotoCurrent->plug( toolbar ); + + disconnect( actionCollection->action( "up" ), SIGNAL( activated() ), m_dir, SLOT( cdUp() ) ); + connect( actionCollection->action( "up" ), SIGNAL( activated() ), m_dir, SLOT( myCdUp() ) ); + disconnect( actionCollection->action( "home" ), SIGNAL( activated() ), m_dir, SLOT( home() ) ); + connect( actionCollection->action( "home" ), SIGNAL( activated() ), m_dir, SLOT( myHome() ) ); + } + + connect( m_filter, SIGNAL(textChanged( const QString& )), SLOT(setFilter( const QString& )) ); + connect( m_combo, SIGNAL(urlActivated( const KURL& )), SLOT(setUrl( const KURL& )) ); + connect( m_combo, SIGNAL(returnPressed( const QString& )), SLOT(setUrl( const QString& )) ); + connect( m_dir, SIGNAL(viewChanged( KFileView* )), SLOT(slotViewChanged( KFileView* )) ); + connect( m_dir, SIGNAL(fileSelected( const KFileItem* )), SLOT(activate( const KFileItem* )) ); + connect( m_dir, SIGNAL(urlEntered( const KURL& )), SLOT(urlChanged( const KURL& )) ); + connect( m_dir, SIGNAL(urlEntered( const KURL& )), searchPane, SLOT(urlChanged( const KURL& )) ); + connect( m_dir, SIGNAL(dropped( const KFileItem*, QDropEvent*, const KURL::List& )), + SLOT(dropped( const KFileItem*, QDropEvent*, const KURL::List& )) ); + + setSpacing( 4 ); + setFocusProxy( m_dir ); //so the dirOperator is focused when we get focus events + // Toolbar is more than 250px wide, BrowserBar doesn't allow that. -> Resizing issues. + setMinimumWidth( 250 /* toolbar->sizeHint().width() */ ); +} + + +FileBrowser::~FileBrowser() +{ + KConfig* const c = Amarok::config( "Filebrowser" ); + + m_dir->writeConfig( c ); //uses currently set group + + c->writePathEntry( "Location", m_dir->url().url() ); + c->writePathEntry( "Dir History", m_combo->urls() ); +} + +//END Constructor/Destructor + +void FileBrowser::setUrl( const KURL &url ) +{ + m_dir->setFocus(); + if (!m_medium) + m_dir->setURL( url, true ); + else { + QString urlpath = url.isLocalFile() ? url.path() : url.prettyURL(); + KURL newURL( urlpath.prepend( m_medium->mountPoint() ).remove("..") ); + //debug() << "set-url-kurl: changing to: " << newURL.path() << endl; + m_dir->setURL( newURL, true ); + } +} + +void FileBrowser::setUrl( const QString &url ) +{ + if (!m_medium) + m_dir->setURL( KURL(url), true ); + else{ + KURL newURL( QString(url).prepend( m_medium->mountPoint() ).remove("..") ); + //debug() << "set-url-qstring: changing to: " << newURL.path() << endl; + m_dir->setURL( newURL, true ); + } +} + +//BEGIN Private Methods + +KURL::List FileBrowser::selectedItems() +{ + KURL::List list; + for( KFileItemListIterator it( m_dir->selectedItems()->count() ? *m_dir->selectedItems() : *m_dir->view()->items() ); *it; ++it ) + list.append( (*it)->url() ); + + return list; +} + +void FileBrowser::playlistFromURLs( const KURL::List &urls ) +{ + QString suggestion; + if( urls.count() == 1 && QFileInfo( urls.first().path() ).isDir() ) + suggestion = urls.first().fileName(); + else + suggestion = i18n( "Untitled" ); + const QString path = PlaylistDialog::getSaveFileName( suggestion ); + if( path.isEmpty() ) + return; + + if( PlaylistBrowser::savePlaylist( path, urls ) ) + { + //FIXME: uncomment after string freeze + //Amarok::StatusBar::instance()->shortMessage( "Playlist saved to playlist browser" ); + } +} + + +//END Private Methods + + +//BEGIN Public Slots + +void +FileBrowser::setFilter( const QString &text ) +{ + if ( text.isEmpty() ) + m_dir->clearFilter(); + + else { + QString filter; + + const QStringList terms = QStringList::split( ' ', text ); + foreach( terms ) { + filter += '*'; + filter += *it; + } + filter += '*'; + + m_dir->setNameFilter( filter ); + } + + m_dir->updateDir(); +} + +void +FileBrowser::dropped( const KFileItem* /*item*/, QDropEvent* event, const KURL::List &urls){ + //Do nothing right now + event->ignore(); + //Run into const problems iterating over the list, so copy it to a malleable one + //(besides, need to filter for local giles) + KURL::List list(urls); + + for ( KURL::List::iterator it = list.begin(); it != list.end(); ){ + if ( m_medium && !(*it).isLocalFile() ) + it = list.erase( it ); + else{ + debug() << "Dropped: " << (*it) << endl; + it++; + } + } +} + +//END Public Slots + + +//BEGIN Private Slots + +inline void +FileBrowser::urlChanged( const KURL &u ) +{ + //the DirOperator's URL has changed + + QString url = u.isLocalFile() ? u.path() : u.prettyURL(); + + if( m_medium ){ + //remove the leading mountPoint value + url.remove( 0, m_medium->mountPoint().length() ); + } + + QStringList urls = m_combo->urls(); + urls.remove( url ); + urls.prepend( url ); + + m_combo->setURLs( urls, KURLComboBox::RemoveBottom ); +} + +inline void +FileBrowser::slotViewChanged( KFileView *view ) +{ + if( view->widget()->inherits( "KListView" ) ) + { + using namespace Amarok::ColorScheme; + + static_cast<KListView*>(view->widget())->setAlternateBackground( AltBase ); + } +} + +inline void +FileBrowser::activate( const KFileItem *item ) +{ + Playlist::instance()->insertMedia( item->url(), Playlist::DefaultOptions ); +} + +inline void +FileBrowser::prepareContextMenu() +{ + const KFileItemList &items = *m_dir->selectedItems(); + static_cast<KActionMenu*>(m_dir->actionCollection()->action("popupMenu"))->popupMenu()->setItemVisible( SavePlaylist, + items.count() > 1 || ( items.count() == 1 && items.getFirst()->isDir() ) ); + static_cast<KActionMenu*>(m_dir->actionCollection()->action("popupMenu"))->popupMenu()->setItemVisible( QueueTrack, + items.count() == 1 ); + static_cast<KActionMenu*>(m_dir->actionCollection()->action("popupMenu"))->popupMenu()->setItemVisible( QueueTracks, + items.count() > 1 ); + static_cast<KActionMenu*>(m_dir->actionCollection()->action("popupMenu"))->popupMenu()->setItemVisible( MediaDevice, + MediaBrowser::isAvailable() ); + static_cast<KActionMenu*>(m_dir->actionCollection()->action("popupMenu"))->popupMenu()->setItemVisible( MoveToCollection, !CollectionDB::instance()->isDirInCollection( url().path() ) ); + static_cast<KActionMenu*>(m_dir->actionCollection()->action("popupMenu"))->popupMenu()->setItemVisible( CopyToCollection, !CollectionDB::instance()->isDirInCollection( url().path() ) ); + static_cast<KActionMenu*>(m_dir->actionCollection()->action("popupMenu"))->popupMenu()->setItemVisible( OrganizeFiles, CollectionDB::instance()->isDirInCollection( url().path() ) ); +} + +inline void +FileBrowser::contextMenuActivated( int id ) +{ + switch( id ) + { + case MakePlaylist: + Playlist::instance()->insertMedia( selectedItems(), Playlist::Replace ); + break; + + case SavePlaylist: + playlistFromURLs( selectedItems() ); + break; + + case AppendToPlaylist: + Playlist::instance()->insertMedia( selectedItems() ); + break; + + case QueueTrack: + case QueueTracks: + Playlist::instance()->insertMedia( selectedItems(), Playlist::Queue ); + break; + + case EditTags: + { + KURL::List list = Amarok::recursiveUrlExpand( selectedItems() ); + TagDialog *dialog = NULL; + if( list.count() == 1 ) + { + dialog = new TagDialog( list.first(), this ); + } + else + { + dialog = new TagDialog( list, this ); + } + dialog->show(); + } + break; + + case CopyToCollection: + CollectionView::instance()->organizeFiles( selectedItems(), i18n( "Copy Files To Collection" ), true ); + break; + + case MoveToCollection: + CollectionView::instance()->organizeFiles( selectedItems(), i18n( "Move Files To Collection" ), false ); + break; + + case OrganizeFiles: + CollectionView::instance()->organizeFiles( selectedItems(), i18n( "Organize Collection Files" ), false ); + break; + + case MediaDevice: + MediaBrowser::queue()->addURLs( selectedItems() ); + break; + + case SelectAllFiles: + selectAll(); + break; + + case BurnCd: + K3bExporter::instance()->exportTracks( selectedItems() ); + break; + } +} + +inline void +FileBrowser::gotoCurrentFolder() +{ + const KURL &url = EngineController::instance()->bundle().url(); + KURL dirURL = KURL::fromPathOrURL( url.directory() ); + + m_combo->setURL( dirURL ); + setUrl( dirURL ); +} + +//END Private Slots + +void +FileBrowser::selectAll() +{ + KFileItemList list( *m_dir->view()->items() ); + + // Select all items which represent files + for( KFileItem* item = list.first(); item; item = list.next() ) + m_dir->view()->setSelected( item, item->isFile() ); +} + +#include <kurldrag.h> +#include <qpainter.h> +#include <qsimplerichtext.h> + +class KURLView : public KListView +{ +public: + KURLView( QWidget *parent ) : KListView( parent ) + { + reinterpret_cast<QWidget*>(header())->hide(); + addColumn( QString() ); + setResizeMode( KListView::LastColumn ); + setDragEnabled( true ); + setSelectionMode( QListView::Extended ); + } + + class Item : public KListViewItem { + public: + Item( const KURL &url, KURLView *parent ) : KListViewItem( parent, url.fileName() ), m_url( url ) {} + KURL m_url; + }; + + virtual QDragObject *dragObject() + { + QPtrList<QListViewItem> items = selectedItems(); + KURL::List urls; + + for( Item *item = static_cast<Item*>( items.first() ); item; item = static_cast<Item*>( items.next() ) ) + urls += item->m_url; + + return new KURLDrag( urls, this ); + } + + virtual void viewportPaintEvent( QPaintEvent *e ) + { + KListView::viewportPaintEvent( e ); + + if ( childCount() == 0 ) { + QPainter p( viewport() ); + + if ( m_text.isEmpty() ) { + //TODO Perhaps it's time to put this in some header, as we use it in three places now + QSimpleRichText t( i18n( + "<div align=center>" + "Enter a search term above; you can use wildcards like * and ?" + "</div>" ), font() ); + + t.setWidth( width() - 50 ); + + const uint w = t.width() + 20; + const uint h = t.height() + 20; + + p.setBrush( colorGroup().background() ); + p.drawRoundRect( 15, 15, w, h, (8*200)/w, (8*200)/h ); + t.draw( &p, 20, 20, QRect(), colorGroup() ); + } + else { + p.setPen( palette().color( QPalette::Disabled, QColorGroup::Text ) ); + p.drawText( rect(), Qt::AlignCenter | Qt::WordBreak, m_text ); + } + } + } + + void unsetText() { setText( QString::null ); } + void setText( const QString &text ) { m_text = text; viewport()->update(); } + +private: + QString m_text; +}; + + + +SearchPane::SearchPane( FileBrowser *parent ) + : QVBox( parent ) + , m_lineEdit( 0 ) + , m_listView( 0 ) + , m_lister( 0 ) +{ + QFrame *container = new QVBox( this, "container" ); + container->hide(); + + { + QFrame *box = new QHBox( container ); + box->setMargin( 5 ); + box->setBackgroundMode( Qt::PaletteBase ); + + m_lineEdit = new ClickLineEdit( i18n("Search here..."), box ); + connect( m_lineEdit, SIGNAL(textChanged( const QString& )), SLOT(searchTextChanged( const QString& )) ); + + m_listView = new KURLView( container ); + + container->setFrameStyle( m_listView->frameStyle() ); + container->setMargin( 5 ); + container->setBackgroundMode( Qt::PaletteBase ); + + m_listView->setFrameStyle( QFrame::NoFrame ); + connect( m_listView, SIGNAL(executed( QListViewItem* )), SLOT(activate( QListViewItem* )) ); + } + + KPushButton *button = new KPushButton( KGuiItem( i18n("&Show Search Panel"), "find" ), this ); + button->setToggleButton( true ); + connect( button, SIGNAL(toggled( bool )), SLOT(toggle( bool )) ); + + m_lister = new MyDirLister( true /*delay mimetypes*/ ); + insertChild( m_lister ); + connect( m_lister, SIGNAL(newItems( const KFileItemList& )), SLOT(searchMatches( const KFileItemList& )) ); + connect( m_lister, SIGNAL(completed()), SLOT(searchComplete()) ); +} + +void +SearchPane::toggle( bool toggled ) +{ + if ( toggled ) + m_lineEdit->setFocus(); + + static_cast<QWidget*>(child("container"))->setShown( toggled ); +} + +void +SearchPane::urlChanged( const KURL& ) +{ + searchTextChanged( m_lineEdit->text() ); +} + +void +SearchPane::searchTextChanged( const QString &text ) +{ + //TODO if user changes search directory then we need to update the search too + + m_lister->stop(); + m_listView->clear(); + m_dirs.clear(); + + if ( text.isEmpty() ) { + m_listView->unsetText(); + return; + } + + m_filter = QRegExp( text.contains( "*" ) ? text : '*'+text+'*', false, true ); + + m_lister->openURL( searchURL() ); + + m_listView->setText( i18n( "Searching..." ) ); +} + +void +SearchPane::searchMatches( const KFileItemList &list ) +{ + for( KFileItemList::ConstIterator it = list.begin(), end = list.end(); it != end; ++it ) { + if( (*it)->isDir() ) + m_dirs += (*it)->url(); + else if( m_filter.exactMatch( (*it)->name() ) ) + new KURLView::Item( (*it)->url(), static_cast<KURLView*>( m_listView ) ); + } +} + +void +SearchPane::searchComplete() +{ + //KDirLister crashes if you call openURL() from a slot + //connected to KDirLister::complete() + //TODO fix crappy KDElibs + + QTimer::singleShot( 0, this, SLOT(_searchComplete()) ); +} + +void +SearchPane::_searchComplete() +{ + if ( !m_dirs.isEmpty() ) { + KURL url = m_dirs.first(); + m_dirs.pop_front(); + m_lister->openURL( url ); + } + else + m_listView->setText( i18n("No results found") ); //only displayed if the listview is empty +} + +void +SearchPane::activate( QListViewItem *item ) +{ + Playlist::instance()->insertMedia( static_cast<KURLView::Item*>(item)->m_url, Playlist::DirectPlay ); +} + +#include "filebrowser.moc" diff --git a/amarok/src/filebrowser.h b/amarok/src/filebrowser.h new file mode 100644 index 00000000..bd4f7be2 --- /dev/null +++ b/amarok/src/filebrowser.h @@ -0,0 +1,127 @@ +/* This file is part of the KDE project + Copyright (C) 2004 Max Howell + Copyright (C) 2004 Mark Kretschmann <markey@web.de> + Copyright (C) 2003 Roberto Raggi <roberto@kdevelop.org> + Copyright (C) 2001 Christoph Cullmann <cullmann@kde.org> + Copyright (C) 2001 Joseph Wenninger <jowenn@kde.org> + Copyright (C) 2001 Anders Lund <anders.lund@lund.tdcadsl.dk> + + This library 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 library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Steet, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef FILESELECTOR_WIDGET_H +#define FILESELECTOR_WIDGET_H + +#include <qvbox.h> //baseclass +#include <kdiroperator.h> //some inline functions +#include <ktoolbar.h> //baseclass +#include <kurl.h> //stack allocated + +class ClickLineEdit; +class QTimer; +class KActionCollection; +class KFileItem; +class KFileView; +class KURLComboBox; +class Medium; + +//Hi! I think we ripped this from Kate, since then it's been modified somewhat + +/* + The KDev file selector presents a directory view, in which the default action is + to open the activated file. + Additinally, a toolbar for managing the kdiroperator widget + sync that to + the directory of the current file is available, as well as a filter widget + allowing to filter the displayed files using a name filter. +*/ + + +class FileBrowser : public QVBox +{ + Q_OBJECT + + enum MenuId { MakePlaylist, SavePlaylist, MediaDevice, AppendToPlaylist, QueueTrack, QueueTracks, SelectAllFiles, BurnCd, MoveToCollection, CopyToCollection, OrganizeFiles, EditTags }; + +public: + FileBrowser( const char *name = 0, Medium *medium = 0 ); + ~FileBrowser(); + + KURL url() const { return m_dir->url(); } + +public slots: + void setUrl( const KURL &url ); + void setUrl( const QString &url ); + void setFilter( const QString& ); + void dropped( const KFileItem*, QDropEvent*, const KURL::List& ); + +private slots: + void activate( const KFileItem* ); + void contextMenuActivated( int ); + void gotoCurrentFolder(); + void prepareContextMenu(); + void selectAll(); + void slotViewChanged( KFileView* ); + void urlChanged( const KURL& ); + +private: + KURL::List selectedItems(); + void playlistFromURLs( const KURL::List &urls ); + + KURLComboBox *m_combo; + KDirOperator *m_dir; + ClickLineEdit *m_filter; + Medium *m_medium; +}; + + + +#include <kfileitem.h> //KFileItemList +#include <qregexp.h> + +class KDirLister; +class KURLView; +class QLineEdit; +class QListViewItem; + +///@author Max Howell +///@short Widget for recursive searching of current FileBrowser location + +class SearchPane : public QVBox +{ + Q_OBJECT + +public: + SearchPane( FileBrowser *parent ); + +private slots: + void toggle( bool ); + void urlChanged( const KURL& ); + void searchTextChanged( const QString &text ); + void searchMatches( const KFileItemList& ); + void searchComplete(); + void _searchComplete(); + void activate( QListViewItem* ); + +private: + KURL searchURL() const { return static_cast<FileBrowser*>(parentWidget())->url(); } + + QLineEdit *m_lineEdit; + KURLView *m_listView; + KDirLister *m_lister; + QRegExp m_filter; + KURL::List m_dirs; +}; + +#endif diff --git a/amarok/src/firstrunwizard.ui b/amarok/src/firstrunwizard.ui new file mode 100644 index 00000000..2b5ace9f --- /dev/null +++ b/amarok/src/firstrunwizard.ui @@ -0,0 +1,302 @@ +<!DOCTYPE UI><UI version="3.3" stdsetdef="1"> +<class>FirstRunWizard</class> +<widget class="QWizard"> + <property name="name"> + <cstring>FirstRunWizard</cstring> + </property> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>824</width> + <height>410</height> + </rect> + </property> + <property name="caption"> + <string>First-Run Wizard</string> + </property> + <widget class="QWidget"> + <property name="name"> + <cstring>WizardPage</cstring> + </property> + <attribute name="title"> + <string></string> + </attribute> + <grid> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="KActiveLabel" row="0" column="0" rowspan="2" colspan="1"> + <property name="name"> + <cstring>text1</cstring> + </property> + <property name="text"> + <string><h1>Welcome to Amarok!</h1> +<p>There are many media-players around these days, this is true. Amarok however provides an aural experience so pleasurable it always has you coming back for more. What is missing from most players is an interface that does not get in your way. Amarok tries to be a little different, and at the same time intuitive. It provides a simple drag-and-drop interface that makes playlist handling simple and fun. By using Amarok we truly hope you will:</p> +<p align="center"><i><b>"Rediscover your music!"</b></i> </p></string> + </property> + </widget> + <widget class="QLabel" row="0" column="2"> + <property name="name"> + <cstring>picture1</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>4</hsizetype> + <vsizetype>4</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="frameShape"> + <enum>Box</enum> + </property> + <property name="scaledContents"> + <bool>false</bool> + </property> + </widget> + <widget class="KActiveLabel" row="2" column="0" rowspan="1" colspan="3"> + <property name="name"> + <cstring>text1_2</cstring> + </property> + <property name="text"> + <string><h2>First-run Wizard</h2> +<p>This wizard will help you setup Amarok in three easy steps. Click <i>Next</i> to begin, or if you do not like wizards, click <i>Skip</i>.</p></string> + </property> + </widget> + <spacer row="1" column="1"> + <property name="name"> + <cstring>spacer1</cstring> + </property> + <property name="orientation"> + <enum>Horizontal</enum> + </property> + <property name="sizeType"> + <enum>Expanding</enum> + </property> + <property name="sizeHint"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </grid> + </widget> + <widget class="QWidget"> + <property name="name"> + <cstring>WizardPage</cstring> + </property> + <attribute name="title"> + <string>Locate your Music</string> + </attribute> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="KActiveLabel"> + <property name="name"> + <cstring>text3</cstring> + </property> + <property name="lineWidth"> + <number>1</number> + </property> + <property name="text"> + <string><p>Please select the folders on the right where your music files are stored.</p> +<p>Doing so is highly recommended, and will enhance the features available to you.</p> +<p>If you wish, Amarok is able to monitor these folders for new files and can automatically add them to the collection.</p></string> + </property> + </widget> + </hbox> + </widget> + <widget class="QWidget"> + <property name="name"> + <cstring>WizardPage_3</cstring> + </property> + <attribute name="title"> + <string>Database Setup</string> + </attribute> + <grid> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <spacer row="1" column="0" rowspan="2" colspan="1"> + <property name="name"> + <cstring>spacer15</cstring> + </property> + <property name="orientation"> + <enum>Vertical</enum> + </property> + <property name="sizeType"> + <enum>Expanding</enum> + </property> + <property name="sizeHint"> + <size> + <width>20</width> + <height>257</height> + </size> + </property> + </spacer> + <spacer row="2" column="1"> + <property name="name"> + <cstring>spacer14</cstring> + </property> + <property name="orientation"> + <enum>Vertical</enum> + </property> + <property name="sizeType"> + <enum>Expanding</enum> + </property> + <property name="sizeHint"> + <size> + <width>20</width> + <height>194</height> + </size> + </property> + </spacer> + <widget class="KActiveLabel" row="0" column="0"> + <property name="name"> + <cstring>dbActiveLabel</cstring> + </property> + <property name="text"> + <string>Amarok uses a database to store information about your music. If you are not sure which to use, press Next. +<p><b>MySQL</b> or <b>Postgresql</b> are faster than <b>sqlite</b>, but require additional setup.</p> +<ul> +<li><a href="http://amarok.kde.org/wiki/MySQL_HowTo">Instructions for configuring MySQL</a>.</li> +<li><a href="http://amarok.kde.org/wiki/Postgresql_HowTo">Instructions for configuring Postgresql</a>.</li> +</ul></string> + </property> + </widget> + <widget class="DbSetup" row="0" column="1" rowspan="2" colspan="1"> + <property name="name"> + <cstring>dbSetup7</cstring> + </property> + </widget> + </grid> + </widget> + <widget class="QWidget"> + <property name="name"> + <cstring>WizardPage_4</cstring> + </property> + <attribute name="title"> + <string></string> + </attribute> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="KActiveLabel"> + <property name="name"> + <cstring>text4</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>7</hsizetype> + <vsizetype>7</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="maximumSize"> + <size> + <width>32767</width> + <height>32767</height> + </size> + </property> + <property name="text"> + <string><h1>Congratulations!</h1> +<p>Amarok is ready for use! When you click finish Amarok will appear and begin scanning the folders in your collection.</p> +<p>Amarok's playlist-window will show your <b>Collection</b> on the left and the <b>Playlist</b> on the right. Drag and drop music from the Collection to the Playlist and press <b>Play</b>.</p> +<p>If you want more help or a tutorial, please check out the <a href="help:/amarok">Amarok handbook</a>. We hope you enjoy using Amarok.</p> +<p align="right">The Amarok developers</p></string> + </property> + </widget> + <spacer> + <property name="name"> + <cstring>spacer3</cstring> + </property> + <property name="orientation"> + <enum>Vertical</enum> + </property> + <property name="sizeType"> + <enum>Expanding</enum> + </property> + <property name="sizeHint"> + <size> + <width>21</width> + <height>294</height> + </size> + </property> + </spacer> + <widget class="QLabel"> + <property name="name"> + <cstring>picture4</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>4</hsizetype> + <vsizetype>4</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="frameShape"> + <enum>Box</enum> + </property> + <property name="scaledContents"> + <bool>false</bool> + </property> + </widget> + </hbox> + </widget> +</widget> +<customwidgets> + <customwidget> + <class>DbSetup</class> + <header location="local">dbsetup.h</header> + <sizehint> + <width>380</width> + <height>165</height> + </sizehint> + <container>1</container> + <sizepolicy> + <hordata>3</hordata> + <verdata>3</verdata> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + <pixmap>image0</pixmap> + </customwidget> +</customwidgets> +<images> + <image name="image0"> + <data format="XBM.GZ" length="79">789c534e494dcbcc4b554829cdcdad8c2fcf4c29c95030e0524611cd48cd4ccf28010a1797249664262b2467241641a592324b8aa363156c15aab914146aadb90067111b1f</data> + </image> +</images> +<forwards> + <forward>class CollectionSetup;</forward> +</forwards> +<variables> + <variable access="public">enum Interface { XMMS, Compact };</variable> + <variable access="private">CollectionSetup* m_folderSetup;</variable> +</variables> +<slots> + <slot access="private" specifier="non virtual">invokeHandbook()</slot> + <slot>openLink( const QString & s )</slot> +</slots> +<functions> + <function access="private" specifier="non virtual">init()</function> + <function access="protected">showPage( QWidget * w )</function> + <function specifier="non virtual">writeCollectionConfig()</function> + <function specifier="non virtual" returnType="FirstRunWizard::Interface">interface()</function> +</functions> +<layoutdefaults spacing="6" margin="11"/> +<includehints> + <includehint>kactivelabel.h</includehint> + <includehint>kactivelabel.h</includehint> + <includehint>kactivelabel.h</includehint> + <includehint>kactivelabel.h</includehint> + <includehint>dbsetup.h</includehint> + <includehint>kactivelabel.h</includehint> +</includehints> +</UI> diff --git a/amarok/src/firstrunwizard.ui.h b/amarok/src/firstrunwizard.ui.h new file mode 100644 index 00000000..ea684319 --- /dev/null +++ b/amarok/src/firstrunwizard.ui.h @@ -0,0 +1,79 @@ +/**************************************************************************** +** ui.h extension file, included from the uic-generated form implementation. +** +** If you want to add, delete, or rename functions or slots, use +** Qt Designer to update this file, preserving your code. +** +** You should not define a constructor or destructor in this file. +** Instead, write your code in functions called init() and destroy(). +** These will automatically be called by the form's constructor and +** destructor. +*****************************************************************************/ + +#include "amarok.h" +#include "config.h" +#include "directorylist.h" + +#include <kapplication.h> +#include <kconfig.h> +#include <klocale.h> +#include <qpushbutton.h> + +namespace Amarok +{ + extern QPixmap getPNG( const QString& ); + extern QPixmap getJPG( const QString& ); + + extern KConfig *config( const QString& ); +} + +void +FirstRunWizard::init() +{ + using namespace Amarok; + + //aesthetics + helpButton()->hide(); + + picture1->setPixmap( getJPG( "amarok_rocks" ) ); + picture4->setPixmap( *picture1->pixmap() ); + + WizardPageLayout_2->addWidget( m_folderSetup = new CollectionSetup( WizardPage_2 ) ); + + text4->disconnect( SIGNAL(linkClicked( const QString& )) ); + connect( text4, SIGNAL(linkClicked( const QString& )), SLOT(invokeHandbook()) ); + dbActiveLabel->disconnect( SIGNAL(linkClicked( const QString& )) ); + connect( dbActiveLabel, SIGNAL(linkClicked( const QString& )), SLOT(openLink( const QString &)) ); + setFinishEnabled ( WizardPage_4, true ); +#if !defined(USE_MYSQL) && !defined(USE_POSTGRESQL) + removePage(WizardPage_3); +#endif + +} + +void +FirstRunWizard::showPage( QWidget *w ) //virtual +{ + QWizard::showPage( w ); + + cancelButton()->setText( w == WizardPage ? i18n("&Skip") : i18n("&Cancel") ); +} + +inline void +FirstRunWizard::invokeHandbook() //SLOT +{ + // Show handbook + kapp->invokeHelp( QString::null, QString::null, 0 ); +} + +void +FirstRunWizard::writeCollectionConfig() +{ + m_folderSetup->writeConfig(); +} + +void +FirstRunWizard::openLink(const QString& s) +{ + Amarok::invokeBrowser(s); +} diff --git a/amarok/src/hi128-app-amarok.png b/amarok/src/hi128-app-amarok.png new file mode 100644 index 00000000..85e4aeaa Binary files /dev/null and b/amarok/src/hi128-app-amarok.png differ diff --git a/amarok/src/hi16-app-amarok.png b/amarok/src/hi16-app-amarok.png new file mode 100644 index 00000000..50a761a4 Binary files /dev/null and b/amarok/src/hi16-app-amarok.png differ diff --git a/amarok/src/hi22-app-amarok.png b/amarok/src/hi22-app-amarok.png new file mode 100644 index 00000000..ee6fa140 Binary files /dev/null and b/amarok/src/hi22-app-amarok.png differ diff --git a/amarok/src/hi32-app-amarok.png b/amarok/src/hi32-app-amarok.png new file mode 100644 index 00000000..07968313 Binary files /dev/null and b/amarok/src/hi32-app-amarok.png differ diff --git a/amarok/src/hi48-app-amarok.png b/amarok/src/hi48-app-amarok.png new file mode 100644 index 00000000..2af1c87a Binary files /dev/null and b/amarok/src/hi48-app-amarok.png differ diff --git a/amarok/src/hi64-app-amarok.png b/amarok/src/hi64-app-amarok.png new file mode 100644 index 00000000..2700c4d9 Binary files /dev/null and b/amarok/src/hi64-app-amarok.png differ diff --git a/amarok/src/hintlineedit.cpp b/amarok/src/hintlineedit.cpp new file mode 100644 index 00000000..3d29b76a --- /dev/null +++ b/amarok/src/hintlineedit.cpp @@ -0,0 +1,58 @@ +#include <hintlineedit.h> +#include <qvbox.h> +#include <qlabel.h> +#include <qfont.h> + +HintLineEdit::HintLineEdit( const QString &hint, const QString &text, QWidget *parent, const char *name ) + : KLineEdit( text, 0, name ) + , m_vbox( new QVBox( parent ) ) +{ + init(); + m_hint->setText( hint ); +} + +HintLineEdit::HintLineEdit( const QString &text, QWidget *parent, const char *name ) + : KLineEdit( text, 0, name ) + , m_vbox( new QVBox( parent ) ) +{ + init(); +} + +HintLineEdit::HintLineEdit( QWidget *parent, const char *name ) + : KLineEdit( 0, name ) + , m_vbox( new QVBox( parent ) ) +{ + init(); +} + +void +HintLineEdit::init() +{ + reparent( m_vbox, 0, QPoint(0,0), true ); + m_hint = new QLabel( m_vbox ); + //m_hint->setBuddy( this ); + m_hint->setFocusPolicy( NoFocus ); + QFont font; + font.setPointSize( font.pointSize() - 2); + m_hint->setFont( font ); +} + +HintLineEdit::~HintLineEdit() +{ + reparent( 0, 0, QPoint(0,0), false ); + delete m_vbox; +} + +void +HintLineEdit::setHint( const QString &hint ) +{ + m_hint->setText( hint ); +} + +QObject * +HintLineEdit::parent() +{ + return m_vbox->parent(); +} + +#include "hintlineedit.moc" diff --git a/amarok/src/hintlineedit.h b/amarok/src/hintlineedit.h new file mode 100644 index 00000000..9e3c666e --- /dev/null +++ b/amarok/src/hintlineedit.h @@ -0,0 +1,27 @@ +#ifndef HINTLINEEDIT_H +#define HINTLINEEDIT_H + +#include <klineedit.h> //baseclass + +class QVBox; +class QLabel; +class QWidget; + +class HintLineEdit : public KLineEdit +{ + Q_OBJECT + +public: + HintLineEdit( const QString &hint, const QString &text, QWidget *parent = 0, const char *name = 0 ); + HintLineEdit( const QString &text, QWidget *parent = 0, const char *name = 0 ); + HintLineEdit( QWidget *parent = 0, const char *name = 0 ); + virtual ~HintLineEdit(); + virtual QObject *parent(); + virtual void setHint( const QString &hint ); +private: + void init(); + QVBox *m_vbox; + QLabel *m_hint; +}; + +#endif diff --git a/amarok/src/htmlview.cpp b/amarok/src/htmlview.cpp new file mode 100644 index 00000000..ff3ffa44 --- /dev/null +++ b/amarok/src/htmlview.cpp @@ -0,0 +1,312 @@ +// (c) 2005 Christian Muehlhaeuser <chris@chris.de> +// (c) 2006 Seb Ruiz <me@sebruiz.net> +// License: GNU General Public License V2 + + +#include "amarok.h" +#include "amarokconfig.h" +#include "app.h" +#include "contextbrowser.h" +#include "htmlview.h" +#include "playlist.h" //appendMedia() + +#include <qclipboard.h> +#include <qfile.h> // External CSS opening +#include <qimage.h> // External CSS opening + +#include <kapplication.h> //kapp +#include <kactioncollection.h> +#include <kglobal.h> //kapp +#include <kimageeffect.h> // gradient background image +#include <kpopupmenu.h> +#include <kstandarddirs.h> //locate file +#include <ktempfile.h> + +KTempFile *HTMLView::m_bgGradientImage = 0; +KTempFile *HTMLView::m_headerGradientImage = 0; +KTempFile *HTMLView::m_shadowGradientImage = 0; +int HTMLView::m_instances = 0; + +HTMLView::HTMLView( QWidget *parentWidget, const char *widgetname, const bool DNDEnabled, const bool JScriptEnabled ) + : KHTMLPart( parentWidget, widgetname ) +{ + m_instances++; + setJavaEnabled( false ); + setPluginsEnabled( false ); + + setDNDEnabled( DNDEnabled ); + setJScriptEnabled( JScriptEnabled ); + + KActionCollection* ac = actionCollection(); + ac->setAutoConnectShortcuts( true ); + m_copy = KStdAction::copy( this, SLOT( copyText() ), ac, "htmlview_copy" ); + m_selectAll = KStdAction::selectAll( this, SLOT( selectAll() ), ac, "htmlview_select_all" ); + { + KPopupMenu m; + m_copy->plug( &m ); + m_selectAll->plug( &m ); + + m_copy->unplug( &m ); + m_selectAll->unplug( &m ); + } + + connect( this, SIGNAL( selectionChanged() ), SLOT( enableCopyAction() ) ); + enableCopyAction(); +} + + +HTMLView::~HTMLView() +{ + m_instances--; + if ( m_instances < 1 ) { + delete m_bgGradientImage; + delete m_headerGradientImage; + delete m_shadowGradientImage; + } +} + +void +HTMLView::enableCopyAction() +{ + m_copy->setEnabled( hasSelection() ); +} + +void +HTMLView::selectAll() +{ + KHTMLPart::selectAll(); +} + +void +HTMLView::copyText() +{ + QString text = selectedText(); + + // Copy both to clipboard and X11-selection + QApplication::clipboard()->setText( text, QClipboard::Clipboard ); + QApplication::clipboard()->setText( text, QClipboard::Selection ); +} + +void HTMLView::paletteChange() { + delete m_bgGradientImage; + delete m_headerGradientImage; + delete m_shadowGradientImage; + m_bgGradientImage = m_headerGradientImage = m_shadowGradientImage = 0; +} + +QString +HTMLView::loadStyleSheet() +{ + QString themeName = AmarokConfig::contextBrowserStyleSheet().latin1(); + const QString file = kapp->dirs()->findResource( "data","amarok/themes/" + themeName + "/stylesheet.css" ); + + QString styleSheet; + if ( themeName != "Default" && QFile::exists( file ) ) + { + const QString CSSLocation = kapp->dirs()->findResource( "data","amarok/themes/" + themeName + "/stylesheet.css" ); + QFile ExternalCSS( CSSLocation ); + if ( !ExternalCSS.open( IO_ReadOnly ) ) + return QString(); //FIXME: should actually return the default style sheet, then + + const QString pxSize = QString::number( ContextBrowser::instance()->fontMetrics().height() - 4 ); + const QString fontFamily = AmarokConfig::useCustomFonts() ? + AmarokConfig::contextBrowserFont().family() : + QApplication::font().family(); + const QString text = ContextBrowser::instance()->colorGroup().text().name(); + const QString link = ContextBrowser::instance()->colorGroup().link().name(); + const QString fg = ContextBrowser::instance()->colorGroup().highlightedText().name(); + const QString bg = ContextBrowser::instance()->colorGroup().highlight().name(); + const QString base = ContextBrowser::instance()->colorGroup().base().name(); + const QColor bgColor = ContextBrowser::instance()->colorGroup().highlight(); + QColor gradientColor = bgColor; + + //we have to set the color for body due to a KHTML bug + //KHTML sets the base color but not the text color + styleSheet = QString( "body { margin: 8px; font-size: %1px; color: %2; background-color: %3; font-family: %4; }" ) + .arg( pxSize ) + .arg( text ) + .arg( AmarokConfig::schemeAmarok() ? fg : gradientColor.name() ) + .arg( fontFamily ); + + QTextStream eCSSts( &ExternalCSS ); + QString tmpCSS = eCSSts.read(); + ExternalCSS.close(); + + tmpCSS.replace( "./", KURL::fromPathOrURL( CSSLocation ).directory( false ) ); + tmpCSS.replace( "AMAROK_FONTSIZE-2", pxSize ); + tmpCSS.replace( "AMAROK_FONTSIZE", pxSize ); + tmpCSS.replace( "AMAROK_FONTSIZE+2", pxSize ); + tmpCSS.replace( "AMAROK_FONTFAMILY", fontFamily ); + tmpCSS.replace( "AMAROK_TEXTCOLOR", text ); + tmpCSS.replace( "AMAROK_LINKCOLOR", link ); + tmpCSS.replace( "AMAROK_BGCOLOR", bg ); + tmpCSS.replace( "AMAROK_FGCOLOR", fg ); + tmpCSS.replace( "AMAROK_BASECOLOR", base ); + tmpCSS.replace( "AMAROK_DARKBASECOLOR", ContextBrowser::instance()->colorGroup().base().dark( 120 ).name() ); + tmpCSS.replace( "AMAROK_GRADIENTCOLOR", gradientColor.name() ); + + styleSheet += tmpCSS; + } + else + { + int pxSize = ContextBrowser::instance()->fontMetrics().height() - 4; + const QString fontFamily = AmarokConfig::useCustomFonts() ? AmarokConfig::contextBrowserFont().family() : QApplication::font().family(); + const QString text = ContextBrowser::instance()->colorGroup().text().name(); + const QString link = ContextBrowser::instance()->colorGroup().link().name(); + const QString fg = ContextBrowser::instance()->colorGroup().highlightedText().name(); + const QString bg = ContextBrowser::instance()->colorGroup().highlight().name(); + const QColor baseColor = ContextBrowser::instance()->colorGroup().base(); + const QColor bgColor = ContextBrowser::instance()->colorGroup().highlight(); + const QColor gradientColor = bgColor; + + if ( !m_bgGradientImage ) { + m_bgGradientImage = new KTempFile( locateLocal( "tmp", "gradient" ), ".png", 0600 ); + QImage image = KImageEffect::gradient( QSize( 600, 1 ), gradientColor, gradientColor.light( 130 ), KImageEffect::PipeCrossGradient ); + image.save( m_bgGradientImage->file(), "PNG" ); + m_bgGradientImage->close(); + } + + if ( !m_headerGradientImage ) { + m_headerGradientImage = new KTempFile( locateLocal( "tmp", "gradient_header" ), ".png", 0600 ); + QImage imageH = KImageEffect::unbalancedGradient( QSize( 1, 10 ), bgColor, gradientColor.light( 130 ), KImageEffect::VerticalGradient, 100, -100 ); + imageH.copy( 0, 1, 1, 9 ).save( m_headerGradientImage->file(), "PNG" ); + m_headerGradientImage->close(); + } + + if ( !m_shadowGradientImage ) { + m_shadowGradientImage = new KTempFile( locateLocal( "tmp", "gradient_shadow" ), ".png", 0600 ); + QImage imageS = KImageEffect::unbalancedGradient( QSize( 1, 10 ), baseColor, Qt::gray, KImageEffect::VerticalGradient, 100, -100 ); + imageS.save( m_shadowGradientImage->file(), "PNG" ); + m_shadowGradientImage->close(); + } + + //unlink the files for us on deletion + m_bgGradientImage->setAutoDelete( true ); + m_headerGradientImage->setAutoDelete( true ); + m_shadowGradientImage->setAutoDelete( true ); + + //we have to set the color for body due to a KHTML bug + //KHTML sets the base color but not the text color + styleSheet = QString( "body { margin: 4px; font-size: %1px; color: %2; background-color: %3; background-image: url( %4 ); background-repeat: repeat; font-family: %5; }" ) + .arg( pxSize ) + .arg( text ) + .arg( AmarokConfig::schemeAmarok() ? fg : gradientColor.name() ) + .arg( m_bgGradientImage->name() ) + .arg( fontFamily ); + + //text attributes + styleSheet += QString( "h1 { font-size: %1px; }" ).arg( pxSize + 8 ); + styleSheet += QString( "h2 { font-size: %1px; }" ).arg( pxSize + 6 ); + styleSheet += QString( "h3 { font-size: %1px; }" ).arg( pxSize + 4 ); + styleSheet += QString( "h4 { font-size: %1px; }" ).arg( pxSize + 3 ); + styleSheet += QString( "h5 { font-size: %1px; }" ).arg( pxSize + 2 ); + styleSheet += QString( "h6 { font-size: %1px; }" ).arg( pxSize + 1 ); + styleSheet += QString( "a { font-size: %1px; color: %2; }" ).arg( pxSize ).arg( text ); + styleSheet += QString( ".info { display: block; margin-left: 4px; font-weight: normal; }" ); + + styleSheet += QString( ".song a { display: block; padding: 1px 2px; font-weight: normal; text-decoration: none; }" ); + styleSheet += QString( ".song a:hover { color: %1; background-color: %2; }" ).arg( fg ).arg( bg ); + styleSheet += QString( ".song-title { font-weight: bold; }" ); + styleSheet += QString( ".song-place { font-size: %1px; font-weight: bold; }" ).arg( pxSize + 3 ); + + //box: the base container for every block (border hilighted on hover, 'A' without underlining) + styleSheet += QString( ".box { border: solid %1 1px; text-align: left; margin-bottom: 10px; }" ).arg( bg ); + styleSheet += QString( ".box a { text-decoration: none; }" ); + styleSheet += QString( ".box:hover { border: solid %1 1px; }" ).arg( text ); + + //box contents: header, body, rows and alternate-rows + styleSheet += QString( ".box-header { color: %1; background-color: %2; background-image: url( %4 ); background-repeat: repeat-x; font-size: %3px; font-weight: bold; padding: 1px 0.5em; border-bottom: 1px solid #000; }" ) + .arg( fg ) + .arg( bg ) + .arg( pxSize + 2 ) + .arg( m_headerGradientImage->name() ); + + styleSheet += QString( ".box-body { padding: 2px; background-color: %1; background-image: url( %2 ); background-repeat: repeat-x; font-size:%3px; }" ) + .arg( ContextBrowser::instance()->colorGroup().base().name() ) + .arg( m_shadowGradientImage->name() ) + .arg( pxSize ); + + //"Related Artists" related styles + styleSheet += QString( ".box-header-nav { color: %1; background-color: %2; font-size: %3px; font-weight: bold; padding: 1px 0.5em; border-bottom: 1px solid #000; text-align: right; }" ) + .arg( fg ) + .arg( bg ) + .arg( pxSize ); + + //"Albums by ..." related styles + styleSheet += QString( ".album-header:hover { color: %1; background-color: %2; cursor: pointer; }" ).arg( fg ).arg( bg ); + styleSheet += QString( ".album-header:hover a { color: %1; }" ).arg( fg ); + styleSheet += QString( ".album-body { background-color: %1; border-bottom: solid %2 1px; border-top: solid %3 1px; }" ).arg( ContextBrowser::instance()->colorGroup().base().name() ).arg( bg ).arg( bg ); + styleSheet += QString( ".album-title { font-weight: bold; }" ); + styleSheet += QString( ".album-info { float:right; padding-right:4px; font-size: %1px }" ).arg( pxSize ); + styleSheet += QString( ".album-length { float:right; padding-right:4px; font-size: %1px; clear:right; }" ).arg( pxSize ); + styleSheet += QString( ".album-image { padding-right: 4px; }" ); + styleSheet += QString( ".album-song a { display: block; padding: 1px 2px; font-weight: normal; text-decoration: none; }" ); + styleSheet += QString( ".album-song a:hover { color: %1; background-color: %2; }" ).arg( fg ).arg( bg ); + styleSheet += QString( ".album-song-trackno { font-weight: bold; }" ); + + styleSheet += QString( ".disc-separator { color: %1; border-bottom: 1px solid %2; }" ).arg( bg ).arg( bg ); + styleSheet += QString( ".disc-separator a { display: block; padding: 1px 2px; font-weight: normal; text-decoration: none; }" ); + styleSheet += QString( ".disc-separator a:hover { color: %1; background-color: %2; }" ).arg( fg ).arg( bg ); + + styleSheet += QString( ".button { width: 100%; }" ); + + //boxes used to display score (sb: score box) + styleSheet += QString( ".sbtext { text-align: right; padding: 0px 4px; }" ); + styleSheet += QString( ".sbinner { height: 8px; background-color: %1; border: solid %2 1px; }" ) + .arg( ContextBrowser::instance()->colorGroup().highlight().name() ) + .arg( ContextBrowser::instance()->colorGroup().highlightedText().name() ); + styleSheet += QString( ".sbouter { width: 52px; height: 10px; background-color: %1; border: solid %2 1px; }" ) + .arg( ContextBrowser::instance()->colorGroup().base().dark( 120 ).name() ) + .arg( ContextBrowser::instance()->colorGroup().highlight().name() ); + + styleSheet += QString( ".ratingBox { padding: 0px 4px; }" ); + styleSheet += QString( ".ratingStar { height: 0.9em; }" ); + + styleSheet += QString( ".statsBox { border-left: solid %1 1px; }" ) + .arg( ContextBrowser::instance()->colorGroup().base().dark( 120 ).name() ); + + styleSheet += QString( "#current_box-header-album { font-weight: normal; }" ); + styleSheet += QString( "#current_box-information-td { text-align: right; vertical-align: bottom; padding: 3px; }" ); + styleSheet += QString( "#current_box-largecover-td { text-align: left; width: 100px; padding: 0; vertical-align: bottom; }" ); + styleSheet += QString( "#current_box-largecover-image { padding: 4px; vertical-align: bottom; }" ); + + styleSheet += QString( "#wiki_box-body a { color: %1; }" ).arg( link ); + styleSheet += QString( "#wiki_box-body a:hover { text-decoration: underline; }" ); + + //labels in tag dialog + styleSheet += ".label a:hover { font-weight: bold; }"; + styleSheet += QString( ".label.size1 { font-size: %1px; }" ).arg( pxSize ); + styleSheet += QString( ".label.size2 { font-size: %1px; }" ).arg( pxSize + 1 ); + styleSheet += QString( ".label.size3 { font-size: %1px; }" ).arg( pxSize + 2 ); + styleSheet += QString( ".label.size4 { font-size: %1px; }" ).arg( pxSize + 3 ); + styleSheet += QString( ".label.size5 { font-size: %1px; }" ).arg( pxSize + 4); + styleSheet += QString( ".label.size6 { font-size: %1px; }" ).arg( pxSize + 5 ); + styleSheet += QString( ".label.size7 { font-size: %1px; }" ).arg( pxSize + 6 ); + styleSheet += QString( ".label.size8 { font-size: %1px; }" ).arg( pxSize + 7 ); + styleSheet += QString( ".label.size9 { font-size: %1px; }" ).arg( pxSize + 8 ); + styleSheet += QString( ".label.size10 { font-size: %1px; }" ).arg( pxSize + 9 ); + } + + return styleSheet; +} + + +void +HTMLView::set( const QString& data ) +{ + begin(); + setUserStyleSheet( loadStyleSheet() ); + write( data ); + end(); +} + + +void HTMLView::openURLRequest( const KURL &url ) +{ + // here, http urls are streams. For webpages we use externalurl + // NOTE there have been no links to streams! http now used for wiki tab. + if ( url.protocol() == "file" ) + Playlist::instance()->insertMedia( url, Playlist::DefaultOptions ); +} + +#include "htmlview.moc" diff --git a/amarok/src/htmlview.h b/amarok/src/htmlview.h new file mode 100644 index 00000000..b64dfcd8 --- /dev/null +++ b/amarok/src/htmlview.h @@ -0,0 +1,44 @@ +// (c) 2005 Christian Muehlhaeuser <chris@chris.de> +// (c) 2006 Seb Ruiz <me@sebruiz.net> +// License: GNU General Public License V2 + + +#ifndef AMAROK_HTMLVIEW_H +#define AMAROK_HTMLVIEW_H + +#include <khtml_events.h> +#include <khtml_part.h> +#include <khtmlview.h> + +class KAction; +class KTempFile; + +class HTMLView : public KHTMLPart +{ + Q_OBJECT + + public: + HTMLView( QWidget *parentWidget = 0, const char *widgetname = 0, const bool DNDEnabled = false, const bool JScriptEnabled = true ); + ~HTMLView(); + + static QString loadStyleSheet(); + static void openURLRequest(const KURL &url ); + void set( const QString& data ); + static void paletteChange(); + + private: + static KTempFile *m_bgGradientImage; + static KTempFile *m_headerGradientImage; + static KTempFile *m_shadowGradientImage; + static int m_instances; + + KAction *m_selectAll; + KAction *m_copy; + + private slots: + void enableCopyAction(); + void selectAll(); + void copyText(); +}; + +#endif /* AMAROK_HTMLVIEW_H */ diff --git a/amarok/src/iconloader.cpp b/amarok/src/iconloader.cpp new file mode 100644 index 00000000..9dea3659 --- /dev/null +++ b/amarok/src/iconloader.cpp @@ -0,0 +1,126 @@ +/*************************************************************************** + * Copyright (C) 2006 by Mark Kretschmann <markey@web.de> * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Steet, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "amarok.h" +#include "amarokconfig.h" + +#include <qmap.h> + + +QString +Amarok::icon( const QString& name ) //declared in amarok.h +{ + // We map our Amarok icon theme names to system icons, instead of using the same + // naming scheme. This has two advantages: + // 1. Our icons can have simpler and more meaningful names + // 2. We can map several of our icons to one system icon, if necessary + static QMap<QString, QString> iconMap; + + if( iconMap.empty() ) { + iconMap["add_lyrics"] = "edit_add"; + iconMap["add_playlist"] = "1downarrow"; + iconMap["album"] = "cdrom_unmount"; + iconMap["artist"] = "personal"; + iconMap["audioscrobbler"] = "audioscrobbler"; + iconMap["love"] = "bookmark"; + iconMap["back"] = "player_start"; + iconMap["burn"] = "cdrom_unmount"; + iconMap["change_language"] = "configure"; + iconMap["clock"] = "history"; + iconMap["collection"] = "collection"; + iconMap["configure"] = "configure"; + iconMap["covermanager"] = "covermanager"; + iconMap["device"] = "usbpendrive_unmount"; + iconMap["download"] = "khtml_kget"; + iconMap["dynamic"] = "dynamic"; + iconMap["edit"] = "edit"; + iconMap["editcopy"] = "editcopy"; + iconMap["equalizer"] = "equalizer"; + iconMap["external"] = "exec"; + iconMap["fastforward"] = "2rightarrow"; + iconMap["favourite_genres"] = "kfm"; + iconMap["files"] = "folder"; + iconMap["files2"] = "folder_red"; + iconMap["info"] = "info"; + iconMap["lyrics"] = "document"; + iconMap["magnatune"] = "cd"; + iconMap["mostplayed"] = "favorites"; + iconMap["music"] = "today"; + iconMap["next"] = "player_end"; + iconMap["pause"] = "player_pause"; + iconMap["play"] = "player_play"; + iconMap["playlist"] = "player_playlist_2"; + iconMap["playlist_clear"] = "view_remove"; + iconMap["playlist_refresh"] = "rebuild"; + iconMap["queue"] = "goto"; + iconMap["queue_track"] = "2rightarrow"; + iconMap["dequeue_track"] = "2leftarrow"; + iconMap["random"] = "random"; + iconMap["random_album"] = "cd"; + iconMap["random_no"] = "forward"; + iconMap["random_track"] = "random"; + iconMap["redo"] = "redo"; + iconMap["refresh"] = "reload"; + iconMap["remove"] = "editdelete"; + iconMap["remove_from_playlist"] = "remove"; + iconMap["repeat_album"] = "cdrom_unmount"; + iconMap["repeat_no"] = "bottom"; + iconMap["repeat_playlist"] = "repeat_playlist"; + iconMap["repeat_track"] = "repeat_track"; + iconMap["rescan"] = "reload"; + iconMap["rewind"] = "2leftarrow"; + iconMap["save"] = "filesave"; + iconMap["scripts"] = "pencil"; + iconMap["search"] = "find"; + iconMap["settings_engine"] = "amarok"; + iconMap["settings_general"] = "misc"; + iconMap["settings_indicator"] = "tv"; + iconMap["settings_playback"] = "kmix"; + iconMap["settings_view"] = "colors"; + iconMap["stop"] = "player_stop"; + iconMap["podcast"] = "podcast"; + iconMap["podcast2"] = "podcast_new"; + iconMap["track"] = "sound"; + iconMap["undo"] = "undo"; + iconMap["visualizations"] = "visualizations"; + iconMap["zoom"] = "find"; + } + + static QMap<QString, QString> amarokMap; + if( amarokMap.empty() ) { + amarokMap["queue_track"] = "fastforward"; + amarokMap["dequeue_track"] = "rewind"; + } + + if( iconMap.contains( name ) ) + { + if( AmarokConfig::useCustomIconTheme() ) + { + if( amarokMap.contains( name ) ) + return QString( "amarok_" ) + amarokMap[name]; + return QString( "amarok_" ) + name; + } + else + return iconMap[name]; + } + + return name; +} + + diff --git a/amarok/src/images/Makefile.am b/amarok/src/images/Makefile.am new file mode 100644 index 00000000..3b204121 --- /dev/null +++ b/amarok/src/images/Makefile.am @@ -0,0 +1,50 @@ +SUBDIRS = \ + icons + +imagesdir = \ + $(kde_datadir)/amarok/images + +images_DATA = \ + amarok_cut.png \ + amarok_rocks.jpg \ + b_next.png \ + b_pause.png \ + b_play.png \ + b_prev.png \ + b_stop.png \ + back_stars_grey.png \ + currenttrack_bar_left.png \ + currenttrack_bar_mid.png \ + currenttrack_bar_right.png \ + currenttrack_play.png \ + currenttrack_pause.png \ + currenttrack_stop.png \ + currenttrack_stop_small.png \ + currenttrack_repeat.png \ + currenttrack_repeat_small.png \ + eq_active2.png \ + eq_inactive2.png \ + lastfm.png \ + loading1.png \ + loading2.png \ + menu_sidepixmap.png \ + more_albums.png \ + musicbrainz.png \ + nocover.png \ + pl_active2.png \ + pl_inactive2.png \ + shadow_albumcover.png \ + sbinner_stars.png \ + smallstar.png \ + splash_screen.jpg \ + star.png \ + time_minus.png \ + time_plus.png \ + vol_speaker.png \ + volumeslider-gradient.png \ + volumeslider-handle.png \ + volumeslider-handle_glow.png \ + volumeslider-inset.png \ + wizard_compact.png \ + wizard_xmms.png \ + xine_logo.png diff --git a/amarok/src/images/amarok_cut.png b/amarok/src/images/amarok_cut.png new file mode 100644 index 00000000..9aa15acf Binary files /dev/null and b/amarok/src/images/amarok_cut.png differ diff --git a/amarok/src/images/amarok_icon.svg b/amarok/src/images/amarok_icon.svg new file mode 100644 index 00000000..60a267b6 --- /dev/null +++ b/amarok/src/images/amarok_icon.svg @@ -0,0 +1,933 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://web.resource.org/cc/" + 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="128" + height="128" + viewBox="0 0 854.238 854.238" + overflow="visible" + enable-background="new 0 0 854.238 854.238" + xml:space="preserve" + id="svg2" + sodipodi:version="0.32" + inkscape:version="0.45" + sodipodi:docname="amarok_icon_oxygen.svg" + sodipodi:docbase="/home/knome/Work/Amarok logo" + inkscape:output_extension="org.inkscape.output.svg.inkscape" + sodipodi:modified="TRUE"><sodipodi:namedview + inkscape:window-height="688" + inkscape:window-width="1258" + inkscape:pageshadow="2" + inkscape:pageopacity="0.0" + guidetolerance="10.0" + gridtolerance="10.0" + objecttolerance="10.0" + borderopacity="1.0" + bordercolor="#666666" + pagecolor="#ffffff" + id="base" + inkscape:zoom="2.5708446" + inkscape:cx="168.23042" + inkscape:cy="57.765811" + inkscape:window-x="1280" + inkscape:window-y="26" + inkscape:current-layer="svg2" /> + <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> + <defs + id="defs217"><radialGradient + gradientTransform="matrix(1.1256295,0,0,1.1256295,-41.873391,-39.54673)" + id="radialGradient29545" + cx="375.21439" + cy="783.16357" + r="300.5752" + fx="375.21439" + fy="783.16357" + gradientUnits="userSpaceOnUse"> + <stop + offset="0.0112" + style="stop-color:#eeeeef;stop-opacity:0.94117647;" + id="stop29547" /> + <stop + offset="0.29210001" + style="stop-color:#6193cf;stop-opacity:1;" + id="stop29549" /> + <stop + offset="0.45750001" + style="stop-color:#2c72c7;stop-opacity:1;" + id="stop29551" /> + <stop + offset="0.6767" + style="stop-color:#0057ae;stop-opacity:1;" + id="stop29553" /> + <stop + offset="0.8653" + style="stop-color:#004d9a;stop-opacity:1;" + id="stop29555" /> + <stop + offset="1" + style="stop-color:#00438a;stop-opacity:1;" + id="stop29557" /> + </radialGradient><linearGradient + id="linearGradient5498"><stop + style="stop-color:#eeeeee;stop-opacity:1;" + offset="0" + id="stop5500" /><stop + style="stop-color:#eeeeee;stop-opacity:0;" + offset="1" + id="stop5502" /></linearGradient><radialGradient + gradientUnits="userSpaceOnUse" + fy="783.16357" + fx="375.21439" + r="300.5752" + cy="783.16357" + cx="375.21439" + id="XMLID_25_" + gradientTransform="matrix(1.1256295,0,0,1.1256295,-41.873391,-39.54673)"> + <stop + id="stop46" + style="stop-color:#D2E8EC" + offset="0.0112" /> + <stop + id="stop48" + style="stop-color:#60A8CE" + offset="0.2921" /> + <stop + id="stop50" + style="stop-color:#4680A8" + offset="0.4575" /> + <stop + id="stop52" + style="stop-color:#2F5C84" + offset="0.6767" /> + <stop + id="stop54" + style="stop-color:#234970" + offset="0.8653" /> + <stop + id="stop56" + style="stop-color:#1F426A" + offset="1" /> + </radialGradient><mask + id="XMLID_26_" + height="44.813" + width="93.126" + y="461.543" + x="155.705" + maskUnits="userSpaceOnUse"> + <g + id="g69"> + <linearGradient + y2="466.75449" + x2="200.0733" + y1="562.85791" + x1="211.7222" + gradientUnits="userSpaceOnUse" + id="XMLID_1_"> + <stop + id="stop72" + style="stop-color:#FFFFFF" + offset="0" /> + <stop + id="stop74" + style="stop-color:#000000" + offset="1" /> + </linearGradient> + <ellipse + style="fill:url(#XMLID_1_)" + id="ellipse76" + ry="59.682999" + rx="80.542" + cy="495.98801" + cx="203.617" /> + </g> + </mask><mask + id="XMLID_2_" + height="127.553" + width="156.907" + y="425.034" + x="101.57" + maskUnits="userSpaceOnUse"> + <g + id="g86"> + <linearGradient + y2="563.47321" + x2="186.92191" + y1="397.62061" + x1="206.09081" + gradientUnits="userSpaceOnUse" + id="XMLID_3_"> + <stop + id="stop89" + style="stop-color:#FFFFFF" + offset="0" /> + <stop + id="stop91" + style="stop-color:#CBCBCB" + offset="0.1111" /> + <stop + id="stop93" + style="stop-color:#969696" + offset="0.2396" /> + <stop + id="stop95" + style="stop-color:#686868" + offset="0.3698" /> + <stop + id="stop97" + style="stop-color:#424242" + offset="0.4991" /> + <stop + id="stop99" + style="stop-color:#252525" + offset="0.6273" /> + <stop + id="stop101" + style="stop-color:#111111" + offset="0.7542" /> + <stop + id="stop103" + style="stop-color:#040404" + offset="0.8792" /> + <stop + id="stop105" + style="stop-color:#000000" + offset="1" /> + </linearGradient> + <ellipse + style="fill:url(#XMLID_3_)" + id="ellipse107" + ry="123.604" + rx="160.94" + cy="495.62299" + cx="194.76401" /> + </g> + </mask><linearGradient + gradientTransform="matrix(-1.1256295,0,0,1.1256295,1028.9091,-39.54673)" + y2="643.30768" + x2="480.12021" + y1="815.45459" + x1="429.84909" + gradientUnits="userSpaceOnUse" + id="XMLID_4_"> + <stop + id="stop112" + style="stop-color:#FFFFFF" + offset="0" /> + <stop + id="stop114" + style="stop-color:#E5EAF2" + offset="1" /> + </linearGradient><linearGradient + y2="560.35498" + x2="278.14471" + y1="634.63232" + x1="220.9512" + gradientUnits="userSpaceOnUse" + id="XMLID_5_"> + <stop + id="stop119" + style="stop-color:#FFFFFF" + offset="0" /> + <stop + id="stop121" + style="stop-color:#193D6B" + offset="1" /> + </linearGradient><linearGradient + y2="565.86761" + x2="328.18469" + y1="684.01898" + x1="237.20799" + gradientUnits="userSpaceOnUse" + id="XMLID_6_"> + <stop + id="stop126" + style="stop-color:#FFFFFF" + offset="0" /> + <stop + id="stop128" + style="stop-color:#193D6B" + offset="1" /> + </linearGradient><linearGradient + y2="612.60522" + x2="335.09619" + y1="704.23578" + x1="264.5405" + gradientUnits="userSpaceOnUse" + id="XMLID_7_"> + <stop + id="stop133" + style="stop-color:#FFFFFF" + offset="0" /> + <stop + id="stop135" + style="stop-color:#193D6B" + offset="1" /> + </linearGradient><linearGradient + y2="626.00598" + x2="373.12509" + y1="713.54639" + x1="319.186" + gradientUnits="userSpaceOnUse" + id="XMLID_8_"> + <stop + id="stop140" + style="stop-color:#FFFFFF" + offset="0" /> + <stop + id="stop142" + style="stop-color:#193D6B" + offset="1" /> + </linearGradient><linearGradient + y2="688.75092" + x2="409.85471" + y1="790.51508" + x1="331.49609" + gradientUnits="userSpaceOnUse" + id="XMLID_9_"> + <stop + id="stop147" + style="stop-color:#FFFFFF" + offset="0" /> + <stop + id="stop149" + style="stop-color:#193D6B" + offset="1" /> + </linearGradient><mask + id="XMLID_10_" + height="527.73" + width="447.197" + y="217.631" + x="191.541" + maskUnits="userSpaceOnUse"> + <g + id="g159"> + <radialGradient + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.931,0,0,0.9299,28.7459,21.6629)" + fy="155.23289" + fx="657.2124" + r="606.95343" + cy="155.23289" + cx="657.2124" + id="XMLID_11_"> + <stop + id="stop162" + style="stop-color:#C9C9C8" + offset="0" /> + <stop + id="stop164" + style="stop-color:#C4C4C4" + offset="0.0881" /> + <stop + id="stop166" + style="stop-color:#B8B8B7" + offset="0.2192" /> + <stop + id="stop168" + style="stop-color:#A1A0A0" + offset="0.3769" /> + <stop + id="stop170" + style="stop-color:#7F7F7E" + offset="0.5556" /> + <stop + id="stop172" + style="stop-color:#4F4F4E" + offset="0.7517" /> + <stop + id="stop174" + style="stop-color:#121212" + offset="0.9595" /> + <stop + id="stop176" + style="stop-color:#000000" + offset="1" /> + </radialGradient> + <circle + style="fill:url(#XMLID_11_)" + id="circle178" + r="302.95401" + cy="444.22601" + cx="408.427" /> + </g> + </mask><mask + id="XMLID_13_" + height="187.183" + width="252.506" + y="279.317" + x="243.061" + maskUnits="userSpaceOnUse"> + <g + style="filter:url(#Adobe_OpacityMaskFilter_4_)" + id="g193"> + <linearGradient + y2="417.81519" + x2="372.38071" + y1="266.93359" + x1="340.0488" + gradientUnits="userSpaceOnUse" + id="XMLID_14_"> + <stop + id="stop196" + style="stop-color:#FFFFFF" + offset="0" /> + <stop + id="stop198" + style="stop-color:#F8F8F8" + offset="0.0867" /> + <stop + id="stop200" + style="stop-color:#E4E4E4" + offset="0.2156" /> + <stop + id="stop202" + style="stop-color:#C2C2C2" + offset="0.3708" /> + <stop + id="stop204" + style="stop-color:#949494" + offset="0.5465" /> + <stop + id="stop206" + style="stop-color:#595959" + offset="0.7394" /> + <stop + id="stop208" + style="stop-color:#151515" + offset="0.9438" /> + <stop + id="stop210" + style="stop-color:#000000" + offset="0.9944" /> + </linearGradient> + <ellipse + style="fill:url(#XMLID_14_)" + id="ellipse212" + ry="183.32201" + rx="215.32401" + cy="374.28201" + cx="363.052" /> + </g> + </mask><mask + id="XMLID_23_" + height="587.491" + width="576.609" + y="97.149" + x="122.963" + maskUnits="userSpaceOnUse"> + <g + style="filter:url(#Adobe_OpacityMaskFilter)" + id="g31"> + <radialGradient + gradientUnits="userSpaceOnUse" + fy="-20.899401" + fx="88.977501" + r="673.11609" + cy="-20.899401" + cx="88.977501" + id="XMLID_24_"> + <stop + id="stop34" + style="stop-color:#FFFFFF" + offset="0.309" /> + <stop + id="stop36" + style="stop-color:#000000" + offset="1" /> + </radialGradient> + <ellipse + style="fill:url(#XMLID_24_)" + id="ellipse38" + ry="349.10599" + rx="512.93402" + cy="286.172" + cx="295.89499" /> + </g> + </mask><radialGradient + id="XMLID_22_" + cx="622.72803" + cy="621.17328" + r="510.71881" + fx="622.72803" + fy="621.17328" + gradientUnits="userSpaceOnUse"> + <stop + offset="0" + style="stop-color:#FFFFFF" + id="stop14" /> + <stop + offset="0.2814" + style="stop-color:#E6E6E6" + id="stop16" /> + <stop + offset="0.7515" + style="stop-color:#C0C0C0" + id="stop18" /> + <stop + offset="1" + style="stop-color:#B2B2B2" + id="stop20" /> + </radialGradient><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_9_" + id="linearGradient12328" + gradientUnits="userSpaceOnUse" + x1="331.49609" + y1="790.51508" + x2="409.85471" + y2="688.75092" + gradientTransform="matrix(1.1256295,0,0,1.1256295,-41.873391,-39.54673)" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_8_" + id="linearGradient12331" + gradientUnits="userSpaceOnUse" + x1="319.186" + y1="713.54639" + x2="373.12509" + y2="626.00598" + gradientTransform="matrix(1.1256295,0,0,1.1256295,-41.873391,-39.54673)" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_7_" + id="linearGradient12334" + gradientUnits="userSpaceOnUse" + x1="264.5405" + y1="704.23578" + x2="335.09619" + y2="612.60522" + gradientTransform="matrix(1.1256295,0,0,1.1256295,-41.873391,-39.54673)" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_6_" + id="linearGradient12337" + gradientUnits="userSpaceOnUse" + x1="237.20799" + y1="684.01898" + x2="328.18469" + y2="565.86761" + gradientTransform="matrix(1.1256295,0,0,1.1256295,-41.873391,-39.54673)" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_5_" + id="linearGradient12340" + gradientUnits="userSpaceOnUse" + x1="220.9512" + y1="634.63232" + x2="278.14471" + y2="560.35498" + gradientTransform="matrix(1.1256295,0,0,1.1256295,-41.873391,-39.54673)" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_4_" + id="linearGradient12343" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-1.1256295,0,0,1.1256295,1028.9091,-39.54673)" + x1="429.84909" + y1="815.45459" + x2="480.12021" + y2="643.30768" /><radialGradient + inkscape:collect="always" + xlink:href="#radialGradient29545" + id="radialGradient12349" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.1256295,0,0,1.1256295,-41.873391,-39.54673)" + cx="375.21439" + cy="783.16357" + fx="375.21439" + fy="783.16357" + r="300.5752" /><linearGradient + inkscape:collect="always" + xlink:href="#linearGradient5498" + id="linearGradient17820" + gradientUnits="userSpaceOnUse" + x1="1508.4957" + y1="-363.42487" + x2="361.1441" + y2="630.52039" /><linearGradient + inkscape:collect="always" + xlink:href="#linearGradient5498" + id="linearGradient2486" + x1="370.62177" + y1="-309.6048" + x2="370.20648" + y2="458.38397" + gradientUnits="userSpaceOnUse" /><linearGradient + inkscape:collect="always" + xlink:href="#linearGradient5498" + id="linearGradient5490" + x1="247.47713" + y1="274.88394" + x2="130.31757" + y2="580.54669" + gradientUnits="userSpaceOnUse" /><linearGradient + inkscape:collect="always" + xlink:href="#linearGradient5498" + id="linearGradient6539" + x1="220.36789" + y1="756.76105" + x2="185.21225" + y2="491.06723" + gradientUnits="userSpaceOnUse" /><radialGradient + inkscape:collect="always" + xlink:href="#radialGradient29545" + id="radialGradient33274" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.1256295,0,0,1.1256295,-41.873391,-39.54673)" + cx="375.21439" + cy="783.16357" + fx="375.21439" + fy="783.16357" + r="300.5752" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_4_" + id="linearGradient33276" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-1.1256295,0,0,1.1256295,1028.9091,-39.54673)" + x1="429.84909" + y1="815.45459" + x2="480.12021" + y2="643.30768" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_5_" + id="linearGradient33278" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.1256295,0,0,1.1256295,-41.873391,-39.54673)" + x1="220.9512" + y1="634.63232" + x2="278.14471" + y2="560.35498" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_6_" + id="linearGradient33280" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.1256295,0,0,1.1256295,-41.873391,-39.54673)" + x1="237.20799" + y1="684.01898" + x2="328.18469" + y2="565.86761" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_7_" + id="linearGradient33282" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.1256295,0,0,1.1256295,-41.873391,-39.54673)" + x1="264.5405" + y1="704.23578" + x2="335.09619" + y2="612.60522" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_8_" + id="linearGradient33284" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.1256295,0,0,1.1256295,-41.873391,-39.54673)" + x1="319.186" + y1="713.54639" + x2="373.12509" + y2="626.00598" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_9_" + id="linearGradient33286" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.1256295,0,0,1.1256295,-41.873391,-39.54673)" + x1="331.49609" + y1="790.51508" + x2="409.85471" + y2="688.75092" /><linearGradient + inkscape:collect="always" + xlink:href="#linearGradient5498" + id="linearGradient33288" + gradientUnits="userSpaceOnUse" + x1="1508.4957" + y1="-363.42487" + x2="361.1441" + y2="630.52039" /><linearGradient + inkscape:collect="always" + xlink:href="#linearGradient5498" + id="linearGradient33290" + gradientUnits="userSpaceOnUse" + x1="370.62177" + y1="-309.6048" + x2="370.20648" + y2="458.38397" /><linearGradient + inkscape:collect="always" + xlink:href="#linearGradient5498" + id="linearGradient33292" + gradientUnits="userSpaceOnUse" + x1="247.47713" + y1="274.88394" + x2="130.31757" + y2="580.54669" /><linearGradient + inkscape:collect="always" + xlink:href="#linearGradient5498" + id="linearGradient33294" + gradientUnits="userSpaceOnUse" + x1="220.36789" + y1="756.76105" + x2="185.21225" + y2="491.06723" /><linearGradient + inkscape:collect="always" + xlink:href="#linearGradient5498" + id="linearGradient33297" + gradientUnits="userSpaceOnUse" + x1="220.36789" + y1="756.76105" + x2="185.21225" + y2="491.06723" /><linearGradient + inkscape:collect="always" + xlink:href="#linearGradient5498" + id="linearGradient33300" + gradientUnits="userSpaceOnUse" + x1="247.47713" + y1="274.88394" + x2="130.31757" + y2="580.54669" /><linearGradient + inkscape:collect="always" + xlink:href="#linearGradient5498" + id="linearGradient33303" + gradientUnits="userSpaceOnUse" + x1="370.62177" + y1="-309.6048" + x2="370.20648" + y2="458.38397" /><linearGradient + inkscape:collect="always" + xlink:href="#linearGradient5498" + id="linearGradient33306" + gradientUnits="userSpaceOnUse" + x1="1508.4957" + y1="-363.42487" + x2="361.1441" + y2="630.52039" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_9_" + id="linearGradient33309" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.1256295,0,0,1.1256295,-41.873391,-39.54673)" + x1="331.49609" + y1="790.51508" + x2="409.85471" + y2="688.75092" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_8_" + id="linearGradient33312" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.1256295,0,0,1.1256295,-41.873391,-39.54673)" + x1="319.186" + y1="713.54639" + x2="373.12509" + y2="626.00598" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_7_" + id="linearGradient33315" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.1256295,0,0,1.1256295,-41.873391,-39.54673)" + x1="264.5405" + y1="704.23578" + x2="335.09619" + y2="612.60522" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_6_" + id="linearGradient33318" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.1256295,0,0,1.1256295,-41.873391,-39.54673)" + x1="237.20799" + y1="684.01898" + x2="328.18469" + y2="565.86761" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_5_" + id="linearGradient33321" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.1256295,0,0,1.1256295,-41.873391,-39.54673)" + x1="220.9512" + y1="634.63232" + x2="278.14471" + y2="560.35498" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_4_" + id="linearGradient33324" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-1.1256295,0,0,1.1256295,1028.9091,-39.54673)" + x1="429.84909" + y1="815.45459" + x2="480.12021" + y2="643.30768" /><radialGradient + inkscape:collect="always" + xlink:href="#radialGradient29545" + id="radialGradient33328" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.1256295,0,0,1.1256295,-41.873391,-39.54673)" + cx="375.21439" + cy="783.16357" + fx="375.21439" + fy="783.16357" + r="300.5752" /><linearGradient + inkscape:collect="always" + xlink:href="#linearGradient5498" + id="linearGradient2295" + gradientUnits="userSpaceOnUse" + x1="220.36789" + y1="756.76105" + x2="185.21225" + y2="491.06723" /><linearGradient + inkscape:collect="always" + xlink:href="#linearGradient5498" + id="linearGradient2298" + gradientUnits="userSpaceOnUse" + x1="247.47713" + y1="274.88394" + x2="130.31757" + y2="580.54669" /><linearGradient + inkscape:collect="always" + xlink:href="#linearGradient5498" + id="linearGradient2301" + gradientUnits="userSpaceOnUse" + x1="370.62177" + y1="-309.6048" + x2="370.20648" + y2="458.38397" /><linearGradient + inkscape:collect="always" + xlink:href="#linearGradient5498" + id="linearGradient2304" + gradientUnits="userSpaceOnUse" + x1="1508.4957" + y1="-363.42487" + x2="361.1441" + y2="630.52039" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_5_" + id="linearGradient2307" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.1256295,0,0,1.1256295,-41.873391,-39.54673)" + x1="331.49609" + y1="790.51508" + x2="409.85471" + y2="688.75092" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_5_" + id="linearGradient2310" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.1256295,0,0,1.1256295,-41.873391,-39.54673)" + x1="319.186" + y1="713.54639" + x2="373.12509" + y2="626.00598" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_5_" + id="linearGradient2313" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.1256295,0,0,1.1256295,-41.873391,-39.54673)" + x1="264.5405" + y1="704.23578" + x2="335.09619" + y2="612.60522" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_5_" + id="linearGradient2316" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.1256295,0,0,1.1256295,-41.873391,-39.54673)" + x1="237.20799" + y1="684.01898" + x2="328.18469" + y2="565.86761" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_5_" + id="linearGradient2319" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.1256295,0,0,1.1256295,-41.873391,-39.54673)" + x1="220.9512" + y1="634.63232" + x2="278.14471" + y2="560.35498" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_4_" + id="linearGradient2322" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-1.1256295,0,0,1.1256295,1028.9091,-39.54673)" + x1="429.84909" + y1="815.45459" + x2="480.12021" + y2="643.30768" /><radialGradient + inkscape:collect="always" + xlink:href="#radialGradient29545" + id="radialGradient2326" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.1256295,0,0,1.1256295,-41.873391,-39.54673)" + cx="375.21439" + cy="783.16357" + fx="375.21439" + fy="783.16357" + r="300.5752" /><radialGradient + inkscape:collect="always" + xlink:href="#XMLID_22_" + id="radialGradient5267" + cx="663.91144" + cy="649.22662" + fx="663.91144" + fy="649.22662" + r="427.12018" + gradientTransform="translate(0,-4.9999861e-6)" + gradientUnits="userSpaceOnUse" /></defs> + +<g + id="g2305" + inkscape:export-filename="/home/knome/Work/Amarok logo/oxygen-icon.png" + inkscape:export-xdpi="421.87378" + inkscape:export-ydpi="421.87378"><path + id="circle22" + d="M 840.16762,427.119 C 840.16762,655.12184 655.12184,840.16763 427.119,840.16763 C 199.11616,840.16763 14.070375,655.12184 14.070375,427.119 C 14.070375,199.11616 199.11616,14.070375 427.119,14.070375 C 655.12184,14.070375 840.16762,199.11616 840.16762,427.119 z " + style="fill:url(#radialGradient5267);stroke:#0057ae;stroke-width:28.14313698;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;fill-opacity:1.0" /><path + id="ellipse40" + d="M 699.57098,390.89499 C 699.57098,553.04278 570.4108,684.64099 411.267,684.64099 C 252.1232,684.64099 122.96301,553.04278 122.96301,390.89499 C 122.96301,228.7472 252.1232,97.148987 411.267,97.148987 C 570.4108,97.148987 699.57098,228.7472 699.57098,390.89499 z " + mask="url(#XMLID_23_)" + style="opacity:0.86000001;fill:#eeeeee" + transform="matrix(1.1256295,0,0,1.1256295,-41.873391,-39.54673)" /><path + id="path43" + d="M 654.69879,222.73733 C 637.51042,222.73733 604.13438,258.58412 591.62864,264.24379 C 579.11727,269.90345 480.08889,334.04295 470.70564,345.36228 C 461.3269,356.68048 448.81777,361.398 435.26745,360.45472 C 421.71824,359.51144 378.87904,359.12422 364.2875,366.67044 C 349.69259,374.21666 283.08009,435.91467 266.40051,443.46201 C 249.72318,451.00598 227.96364,454.87365 217.54031,455.8158 C 207.11698,456.7602 172.58492,491.56804 163.2028,498.17324 C 153.8218,504.7728 85.234943,531.21834 74.81274,536.87576 C 64.388285,542.53655 136.10214,632.11302 148.61126,633.99957 C 161.11813,635.88725 136.32389,645.13542 112.25793,645.13542 C 109.6082,645.13542 146.65154,742.275 272.95618,804.97144 C 382.14786,859.1705 470.12031,826.25709 513.14637,813.11199 C 543.56989,803.81767 549.0866,799.6731 550.13231,784.58066 C 551.17577,769.48934 553.06007,757.5633 555.14586,740.58543 C 557.23165,723.60981 564.52348,581.1749 591.62977,543.44605 C 618.72817,505.71608 696.19287,460.423 723.29352,455.70886 C 750.39643,450.99248 745.18589,441.56083 745.18589,433.07133 C 745.18589,424.58183 741.01543,405.71628 733.7191,402.88645 C 726.42165,400.05774 697.94997,417.05024 688.56898,418.9368 C 679.18686,420.82335 634.36542,423.65319 628.11029,414.22154 C 621.85629,404.78764 678.67695,308.57445 685.97328,297.25625 C 693.26736,285.93692 696.39548,270.84448 682.8474,254.80989 C 669.29145,238.77192 659.90933,222.73733 654.69879,222.73733 z " + style="opacity:0.22000002;fill:#323232" /><path + id="path58" + d="M 618.12146,172.83479 C 601.44751,172.83479 569.07553,211.25928 556.94237,217.3253 C 544.80921,223.39244 448.75361,292.14477 439.65402,304.27905 C 430.55444,316.41109 418.42015,321.46741 405.2773,320.4566 C 392.13445,319.44691 350.58296,319.03043 336.42817,327.1192 C 322.27113,335.2091 257.65887,401.34434 241.48132,409.43423 C 225.3049,417.52076 204.19935,421.6642 194.08782,422.67614 C 183.97854,423.68583 150.48318,460.99707 141.38359,468.07615 C 132.28401,475.15524 60.378793,506.26989 50.268389,512.33366 C 40.156859,518.4008 124.03202,603.36669 136.16518,605.38719 C 148.29834,607.4122 127.8085,653.4482 104.46407,653.4482 C 101.89313,653.4482 125.32536,729.73323 247.83775,796.93894 C 353.75161,855.03831 466.12658,829.3143 507.85929,815.22592 C 537.37105,805.2596 516.65834,799.75189 517.67141,783.57547 C 518.67997,767.3968 519.52982,746.12465 521.55258,727.9266 C 523.57646,709.72742 530.65104,557.05041 556.94124,516.60767 C 583.22807,476.16718 658.36384,427.61653 684.65291,422.56133 C 710.94312,417.50612 705.88566,407.39459 705.88566,398.29613 C 705.88566,389.19767 701.8424,368.97461 694.76332,365.94104 C 687.68648,362.90859 660.07029,381.12353 650.9707,383.14516 C 641.87111,385.16679 598.39367,388.20149 592.32541,378.09108 C 586.25939,367.97843 641.37358,264.84713 648.45154,252.71396 C 655.53063,240.57968 658.56195,224.40101 645.41685,207.21489 C 632.27737,190.02316 623.17666,172.83479 618.12146,172.83479 z " + style="fill:url(#radialGradient2326);fill-opacity:1" /><path + id="path60" + d="M 416.43229,363.42863 C 416.43229,363.42863 376.37564,357.26468 353.26309,380.37836 C 330.14941,403.49091 310.11546,454.33672 311.66095,469.74546 C 313.19968,485.15421 359.42591,417.35754 379.45423,412.73683 C 399.48706,408.11275 421.0575,374.21216 416.43229,363.42863 z " + style="fill:#ffffff" /><path + id="path116" + d="M 517.5791,576.1107 C 517.5791,576.1107 481.73456,698.13344 480.82843,700.01887 C 475.70569,710.66507 466.25715,741.5152 469.51022,751.47814 C 473.34074,763.21508 487.69814,780.27963 494.39902,788.8119 C 501.09989,797.34755 503.97137,801.61368 502.05667,804.8161 C 500.1431,808.01626 475.25431,808.01626 475.25431,808.01626 C 475.25431,808.01626 456.11073,779.21366 452.27909,768.54606 C 448.45194,757.88073 446.53612,735.47957 450.36664,728.0144 C 454.19491,720.54585 517.5791,576.1107 517.5791,576.1107 z " + style="opacity:0.18000004;fill:url(#linearGradient2322)" /><path + id="polygon123" + d="M 264.93381,598.84278 L 199.45032,655.53061 L 281.54923,606.66141 L 264.93381,598.84278 z " + style="opacity:0.2;fill:url(#linearGradient2319)" /><path + id="polygon130" + d="M 329.44139,579.29735 L 218.01983,723.9475 L 346.05568,589.06894 L 329.44139,579.29735 z " + style="opacity:0.2;fill:url(#linearGradient2316);fill-opacity:1.0" /><path + id="polygon137" + d="M 338.23818,629.14248 L 253.20588,764.01991 L 357.78474,637.93815 L 338.23818,629.14248 z " + style="opacity:0.20132015;fill:url(#linearGradient2313);fill-opacity:1.0" /><path + id="polygon144" + d="M 388.08331,623.27682 L 316.73416,790.41029 L 411.5403,628.16543 L 388.08331,623.27682 z " + style="opacity:0.2;fill:url(#linearGradient2310);fill-opacity:1.0" /><path + id="polygon151" + d="M 416.42779,667.25967 L 410.56213,819.73069 L 435.97322,669.21489 L 416.42779,667.25967 z " + style="opacity:0.2;fill:url(#linearGradient2307);fill-opacity:1.0" /><path + id="path2270" + d="M 420.91732,729.36642 C 351.30018,698.39383 288.50975,655.88934 234.26482,603.01627 C 228.17499,597.08044 226.8144,595.4965 226.8144,594.34262 C 226.8144,593.13125 228.55133,591.40592 239.05626,582.1824 C 260.44809,563.40004 265.15448,558.38692 268.80041,550.49987 C 272.52283,542.44735 280.21466,532.95491 295.21739,517.89884 C 328.33946,484.65899 388.46538,434.98491 416.82689,417.42901 C 421.75597,414.37788 424.58874,412.17246 428.48767,408.35063 C 440.85841,396.22453 460.01914,371.72167 499.93884,316.97838 C 542.43234,258.70557 556.73716,239.87049 570.88047,223.57002 C 579.4268,213.72016 585.7114,207.8731 588.1486,207.5041 C 589.18431,207.34729 590.90434,207.06941 591.9709,206.88659 C 593.03746,206.70377 594.40047,206.78803 594.99981,207.07385 C 595.59916,207.35966 596.47603,207.71632 596.94842,207.86643 C 597.85387,208.15415 615.10032,227.66836 621.45221,235.59225 C 629.06069,245.08374 629.74587,247.72667 625.93867,252.8978 C 624.51775,254.82777 623.14916,257.60325 622.0958,260.69106 C 620.28646,265.995 610.96485,284.8373 596.62206,312.18261 C 585.34543,333.68211 570.08225,364.07457 565.31216,374.52776 C 555.24252,396.59436 550.331,411.79539 551.61625,416.91622 C 552.03701,418.59268 554.5804,419.79199 561.41213,421.53535 C 577.39273,425.61339 592.16664,424.99594 605.9335,419.67469 C 608.01588,418.8698 612.90001,416.3756 616.78711,414.13203 C 628.65388,407.28274 637.59912,403.45373 648.76876,400.44235 C 654.10472,399.00376 655.37311,398.8526 662.48236,398.8081 C 669.94112,398.76141 670.43639,398.82084 672.82712,400.04901 C 674.99738,401.16392 675.39512,401.60914 675.75849,403.33033 C 676.57722,407.20845 676.72083,409.70195 676.15791,410.26491 C 675.85023,410.57255 675.59852,411.32821 675.59852,411.94417 C 675.59852,414.47076 669.52221,416.51234 652.58246,419.67732 C 634.81328,422.99725 633.95536,423.35495 621.58956,432.59958 C 593.90887,453.29357 550.84044,490.78155 535.5622,507.4802 C 528.63101,515.05579 518.00575,529.71705 503.68433,551.46683 C 471.5521,600.26572 449.29204,642.4077 443.34539,665.69842 C 442.16377,670.32635 438.83962,691.2303 435.82204,713.00897 C 433.03687,733.11041 432.91259,733.71553 431.57904,733.67009 C 430.99045,733.65005 426.19268,731.71337 420.91732,729.36642 z " + style="opacity:0.57999998;fill:url(#linearGradient2304);fill-opacity:1" /><path + id="path2476" + d="M 232.63545,457.72623 C 232.32648,456.61911 232.38894,446.89128 232.73423,442.33838 C 232.98128,439.08089 233.18774,438.00445 234.05625,435.44502 C 236.74093,427.53362 240.66616,423.12959 246.34405,421.65829 C 248.46188,421.1095 249.61118,420.54692 252.67377,418.55984 C 258.96449,414.47824 266.9936,407.70183 279.8434,395.62919 C 283.33012,392.35334 297.4753,378.44049 311.27713,364.71177 C 325.07896,350.98304 336.99177,339.3012 337.75003,338.75214 C 338.5083,338.20306 339.62503,337.62248 340.23165,337.46196 C 344.66331,336.28925 355.38062,335.72073 382.55689,335.21676 C 408.09255,334.74322 418.96719,334.25126 422.32093,333.41786 C 425.79448,332.5547 433.72051,327.86095 449.69823,317.2052 C 461.25088,309.50061 485.83784,292.50027 500.08576,282.36542 C 503.90907,279.64579 505.46448,278.68996 506.06672,278.68996 C 506.51435,278.68996 506.95729,278.56587 507.05101,278.41423 C 507.14472,278.26258 507.41122,278.13849 507.64324,278.13849 C 507.99016,278.13849 508.03868,278.42006 507.9166,279.72396 C 507.60339,283.0691 507.40571,283.75666 506.35404,285.15932 C 503.48868,288.98093 495.94139,295.14902 487.04407,300.94052 C 484.7598,302.4274 480.32374,305.63362 477.18606,308.06546 C 445.54219,332.59103 424.04481,347.10814 414.75017,350.22825 C 412.90728,350.8469 412.19408,350.93239 408.87631,350.93239 C 402.47717,350.93239 395.12723,352.3194 382.58568,355.89371 C 364.10099,361.16178 347.09763,368.07111 338.16364,373.94464 C 330.40581,379.04493 324.1723,388.83443 317.90944,405.75312 C 315.44537,412.40965 312.53953,422.24882 309.03897,435.78866 C 308.16672,439.16239 307.36897,442.05878 307.26619,442.22508 C 307.16342,442.39138 290.59105,446.05592 270.43873,450.36849 C 250.28639,454.68105 233.58513,458.27493 233.32479,458.35485 C 232.98198,458.4601 232.79187,458.28672 232.63545,457.72623 z " + style="fill:url(#linearGradient2301);fill-opacity:1" /><path + id="path5482" + d="M 137.72559,580.45986 C 137.34282,580.37465 136.58716,580.30043 136.04635,580.29491 C 133.6773,580.27075 126.95272,576.87395 119.71787,572.04689 C 111.87019,566.81093 105.56261,561.80559 98.411879,555.13967 C 96.972365,553.79776 94.464264,551.48807 92.838314,550.00703 C 88.471835,546.02972 81.735123,539.26156 79.754779,536.86241 C 76.134794,532.47692 74.197025,529.24921 73.930598,527.16116 C 73.525521,523.98649 73.539647,523.23809 74.056146,520.50727 C 74.173258,519.88813 75.704,518.55644 77.786876,517.26169 C 81.684875,514.83862 89.97768,509.21429 119.19592,489.17727 C 163.65173,458.69075 182.38099,446.29601 192.25667,440.8268 C 194.75874,439.44115 195.44804,439.28657 199.14319,439.28264 C 202.68422,439.27886 205.66402,439.59679 207.78132,440.20429 C 212.01481,441.41899 218.55894,443.9768 224.32907,446.6721 C 227.2094,448.01756 227.41617,448.14556 227.41617,448.58327 C 227.41617,449.00581 227.31681,449.08032 226.41575,449.33347 C 224.34431,449.91544 218.81508,451.7359 214.28011,453.32905 C 186.7276,463.00831 159.15855,476.3578 134.50683,491.95688 C 128.66425,495.65393 127.98593,496.15089 125.80375,498.33306 C 119.60523,504.53153 114.02491,514.21693 112.46913,521.47709 C 112.25189,522.49083 112.15177,523.69473 112.15082,525.30481 C 112.14929,527.87219 112.45637,529.2153 113.42141,530.86199 C 117.23617,537.37141 136.60237,543.51816 161.6507,546.16976 C 168.95857,546.94337 172.32439,547.12227 179.74358,547.13138 L 186.96406,547.14026 L 188.87793,546.47918 C 193.99516,544.7116 200.99731,540.71142 216.19398,530.8741 C 229.3623,522.34981 233.42675,519.86654 238.11641,517.4801 C 242.50747,515.24563 244.72611,514.58169 246.61672,514.93637 C 247.33648,515.0714 247.46896,515.47588 247.47685,517.56237 C 247.48827,520.57957 247.15877,521.72825 245.6302,523.9999 C 241.21384,530.56322 230.3452,539.48309 215.0564,549.09176 C 202.20767,557.16691 187.57399,564.6544 179.78048,567.14111 C 179.04248,567.37658 177.06305,567.88388 175.38173,568.26843 C 166.39674,570.32344 157.42239,573.5234 145.55502,578.90368 C 143.87492,579.6654 142.39182,580.22135 141.8143,580.30598 C 140.1115,580.55547 138.43805,580.61845 137.72559,580.45986 z " + style="opacity:0.7;fill:url(#linearGradient2298);fill-opacity:1" /><path + id="path6524" + d="M 168.63849,529.9315 C 168.4693,529.90931 166.27755,529.84827 163.76793,529.79586 C 159.24918,529.70149 156.41999,529.50691 152.38621,529.01309 C 143.03032,527.86773 136.79422,526.024 134.60723,523.75665 C 134.14337,523.27573 133.99029,522.70506 133.99765,521.4841 C 134.00721,519.89411 134.15346,519.274 134.8333,517.94054 C 137.71533,512.28764 146.00612,503.33611 155.76996,495.33536 C 157.91225,493.57991 161.46514,490.91334 163.30919,489.6769 L 164.6395,488.78491 L 194.83695,485.13213 L 225.03441,481.47934 L 227.0339,481.5935 C 228.89293,481.69963 230.48389,481.81725 234.5344,482.14805 C 236.40256,482.30062 236.54756,482.36826 236.3708,483.00474 C 236.31176,483.21731 236.26321,483.58448 236.26289,483.82069 C 236.26209,484.42647 235.31089,486.65231 233.98756,489.14512 C 226.84799,502.59401 212.42824,519.53378 202.83492,525.74201 C 200.0852,527.52146 198.15537,528.36324 195.67618,528.86463 C 193.14723,529.37607 189.91738,529.70056 186.3263,529.80398 C 182.79755,529.9056 169.18899,530.00371 168.63849,529.9315 z " + style="fill:url(#linearGradient2295);fill-opacity:1" /></g></svg> \ No newline at end of file diff --git a/amarok/src/images/amarok_icon_small.svg b/amarok/src/images/amarok_icon_small.svg new file mode 100644 index 00000000..6010c395 --- /dev/null +++ b/amarok/src/images/amarok_icon_small.svg @@ -0,0 +1,1133 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://web.resource.org/cc/" + 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="22" + height="22" + viewBox="0 0 854.238 854.238" + overflow="visible" + enable-background="new 0 0 854.238 854.238" + xml:space="preserve" + id="svg2" + sodipodi:version="0.32" + inkscape:version="0.45" + sodipodi:docname="amarok-icon-small_oxygen.svg" + sodipodi:docbase="/home/me" + inkscape:output_extension="org.inkscape.output.svg.inkscape" + version="1.0" + inkscape:export-filename="/home/me/amarok22.png" + inkscape:export-xdpi="12.643598" + inkscape:export-ydpi="12.643598" + sodipodi:modified="true"><sodipodi:namedview + inkscape:window-height="688" + inkscape:window-width="1022" + inkscape:pageshadow="2" + inkscape:pageopacity="0.0" + guidetolerance="10.0" + gridtolerance="10.0" + objecttolerance="10.0" + borderopacity="1.0" + bordercolor="#666666" + pagecolor="#ffffff" + id="base" + inkscape:zoom="3.7930793" + inkscape:cx="107.38014" + inkscape:cy="19.259016" + inkscape:window-x="0" + inkscape:window-y="0" + inkscape:current-layer="svg2" + width="22px" + height="22px" /> + <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> + <defs + id="defs217"><radialGradient + gradientTransform="matrix(1.1256295,0,0,1.1256295,-41.873391,-39.54673)" + id="radialGradient29545" + cx="375.21439" + cy="783.16357" + r="300.5752" + fx="375.21439" + fy="783.16357" + gradientUnits="userSpaceOnUse"> + <stop + offset="0.0112" + style="stop-color:#eeeeef;stop-opacity:0.94117647;" + id="stop29547" /> + <stop + offset="0.29210001" + style="stop-color:#6193cf;stop-opacity:1;" + id="stop29549" /> + <stop + offset="0.45750001" + style="stop-color:#2c72c7;stop-opacity:1;" + id="stop29551" /> + <stop + offset="0.6767" + style="stop-color:#0057ae;stop-opacity:1;" + id="stop29553" /> + <stop + offset="0.8653" + style="stop-color:#004d9a;stop-opacity:1;" + id="stop29555" /> + <stop + offset="1" + style="stop-color:#00438a;stop-opacity:1;" + id="stop29557" /> + </radialGradient><linearGradient + id="linearGradient5498"><stop + style="stop-color:#eeeeee;stop-opacity:1;" + offset="0" + id="stop5500" /><stop + style="stop-color:#eeeeee;stop-opacity:0;" + offset="1" + id="stop5502" /></linearGradient><radialGradient + gradientUnits="userSpaceOnUse" + fy="783.16357" + fx="375.21439" + r="300.5752" + cy="783.16357" + cx="375.21439" + id="XMLID_25_" + gradientTransform="matrix(1.1256295,0,0,1.1256295,-41.873391,-39.54673)"> + <stop + id="stop46" + style="stop-color:#D2E8EC" + offset="0.0112" /> + <stop + id="stop48" + style="stop-color:#60A8CE" + offset="0.2921" /> + <stop + id="stop50" + style="stop-color:#4680A8" + offset="0.4575" /> + <stop + id="stop52" + style="stop-color:#2F5C84" + offset="0.6767" /> + <stop + id="stop54" + style="stop-color:#234970" + offset="0.8653" /> + <stop + id="stop56" + style="stop-color:#1F426A" + offset="1" /> + </radialGradient><mask + id="XMLID_26_" + height="44.813" + width="93.126" + y="461.543" + x="155.705" + maskUnits="userSpaceOnUse"> + <g + id="g69"> + <linearGradient + y2="466.75449" + x2="200.0733" + y1="562.85791" + x1="211.7222" + gradientUnits="userSpaceOnUse" + id="XMLID_1_"> + <stop + id="stop72" + style="stop-color:#FFFFFF" + offset="0" /> + <stop + id="stop74" + style="stop-color:#000000" + offset="1" /> + </linearGradient> + <ellipse + style="fill:url(#XMLID_1_)" + id="ellipse76" + ry="59.682999" + rx="80.542" + cy="495.98801" + cx="203.617" + sodipodi:cx="203.617" + sodipodi:cy="495.98801" + sodipodi:rx="80.542" + sodipodi:ry="59.682999" /> + </g> + </mask><mask + id="XMLID_2_" + height="127.553" + width="156.907" + y="425.034" + x="101.57" + maskUnits="userSpaceOnUse"> + <g + id="g86"> + <linearGradient + y2="563.47321" + x2="186.92191" + y1="397.62061" + x1="206.09081" + gradientUnits="userSpaceOnUse" + id="XMLID_3_"> + <stop + id="stop89" + style="stop-color:#FFFFFF" + offset="0" /> + <stop + id="stop91" + style="stop-color:#CBCBCB" + offset="0.1111" /> + <stop + id="stop93" + style="stop-color:#969696" + offset="0.2396" /> + <stop + id="stop95" + style="stop-color:#686868" + offset="0.3698" /> + <stop + id="stop97" + style="stop-color:#424242" + offset="0.4991" /> + <stop + id="stop99" + style="stop-color:#252525" + offset="0.6273" /> + <stop + id="stop101" + style="stop-color:#111111" + offset="0.7542" /> + <stop + id="stop103" + style="stop-color:#040404" + offset="0.8792" /> + <stop + id="stop105" + style="stop-color:#000000" + offset="1" /> + </linearGradient> + <ellipse + style="fill:url(#XMLID_3_)" + id="ellipse107" + ry="123.604" + rx="160.94" + cy="495.62299" + cx="194.76401" + sodipodi:cx="194.76401" + sodipodi:cy="495.62299" + sodipodi:rx="160.94" + sodipodi:ry="123.604" /> + </g> + </mask><linearGradient + gradientTransform="matrix(-1.1256295,0,0,1.1256295,1028.9091,-39.54673)" + y2="643.30768" + x2="480.12021" + y1="815.45459" + x1="429.84909" + gradientUnits="userSpaceOnUse" + id="XMLID_4_"> + <stop + id="stop112" + style="stop-color:#FFFFFF" + offset="0" /> + <stop + id="stop114" + style="stop-color:#E5EAF2" + offset="1" /> + </linearGradient><linearGradient + y2="560.35498" + x2="278.14471" + y1="634.63232" + x1="220.9512" + gradientUnits="userSpaceOnUse" + id="XMLID_5_"> + <stop + id="stop119" + style="stop-color:#FFFFFF" + offset="0" /> + <stop + id="stop121" + style="stop-color:#193D6B" + offset="1" /> + </linearGradient><linearGradient + y2="565.86761" + x2="328.18469" + y1="684.01898" + x1="237.20799" + gradientUnits="userSpaceOnUse" + id="XMLID_6_"> + <stop + id="stop126" + style="stop-color:#FFFFFF" + offset="0" /> + <stop + id="stop128" + style="stop-color:#193D6B" + offset="1" /> + </linearGradient><linearGradient + y2="612.60522" + x2="335.09619" + y1="704.23578" + x1="264.5405" + gradientUnits="userSpaceOnUse" + id="XMLID_7_"> + <stop + id="stop133" + style="stop-color:#FFFFFF" + offset="0" /> + <stop + id="stop135" + style="stop-color:#193D6B" + offset="1" /> + </linearGradient><linearGradient + y2="626.00598" + x2="373.12509" + y1="713.54639" + x1="319.186" + gradientUnits="userSpaceOnUse" + id="XMLID_8_"> + <stop + id="stop140" + style="stop-color:#FFFFFF" + offset="0" /> + <stop + id="stop142" + style="stop-color:#193D6B" + offset="1" /> + </linearGradient><linearGradient + y2="688.75092" + x2="409.85471" + y1="790.51508" + x1="331.49609" + gradientUnits="userSpaceOnUse" + id="XMLID_9_"> + <stop + id="stop147" + style="stop-color:#FFFFFF" + offset="0" /> + <stop + id="stop149" + style="stop-color:#193D6B" + offset="1" /> + </linearGradient><mask + id="XMLID_10_" + height="527.73" + width="447.197" + y="217.631" + x="191.541" + maskUnits="userSpaceOnUse"> + <g + id="g159"> + <radialGradient + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.931,0,0,0.9299,28.7459,21.6629)" + fy="155.23289" + fx="657.2124" + r="606.95343" + cy="155.23289" + cx="657.2124" + id="XMLID_11_"> + <stop + id="stop162" + style="stop-color:#C9C9C8" + offset="0" /> + <stop + id="stop164" + style="stop-color:#C4C4C4" + offset="0.0881" /> + <stop + id="stop166" + style="stop-color:#B8B8B7" + offset="0.2192" /> + <stop + id="stop168" + style="stop-color:#A1A0A0" + offset="0.3769" /> + <stop + id="stop170" + style="stop-color:#7F7F7E" + offset="0.5556" /> + <stop + id="stop172" + style="stop-color:#4F4F4E" + offset="0.7517" /> + <stop + id="stop174" + style="stop-color:#121212" + offset="0.9595" /> + <stop + id="stop176" + style="stop-color:#000000" + offset="1" /> + </radialGradient> + <circle + style="fill:url(#XMLID_11_)" + id="circle178" + r="302.95401" + cy="444.22601" + cx="408.427" + sodipodi:cx="408.427" + sodipodi:cy="444.22601" + sodipodi:rx="302.95401" + sodipodi:ry="302.95401" /> + </g> + </mask><mask + id="XMLID_13_" + height="187.183" + width="252.506" + y="279.317" + x="243.061" + maskUnits="userSpaceOnUse"> + <g + style="filter:url(#Adobe_OpacityMaskFilter_4_)" + id="g193"> + <linearGradient + y2="417.81519" + x2="372.38071" + y1="266.93359" + x1="340.0488" + gradientUnits="userSpaceOnUse" + id="XMLID_14_"> + <stop + id="stop196" + style="stop-color:#FFFFFF" + offset="0" /> + <stop + id="stop198" + style="stop-color:#F8F8F8" + offset="0.0867" /> + <stop + id="stop200" + style="stop-color:#E4E4E4" + offset="0.2156" /> + <stop + id="stop202" + style="stop-color:#C2C2C2" + offset="0.3708" /> + <stop + id="stop204" + style="stop-color:#949494" + offset="0.5465" /> + <stop + id="stop206" + style="stop-color:#595959" + offset="0.7394" /> + <stop + id="stop208" + style="stop-color:#151515" + offset="0.9438" /> + <stop + id="stop210" + style="stop-color:#000000" + offset="0.9944" /> + </linearGradient> + <ellipse + style="fill:url(#XMLID_14_)" + id="ellipse212" + ry="183.32201" + rx="215.32401" + cy="374.28201" + cx="363.052" + sodipodi:cx="363.052" + sodipodi:cy="374.28201" + sodipodi:rx="215.32401" + sodipodi:ry="183.32201" /> + </g> + </mask><mask + id="XMLID_23_" + height="587.491" + width="576.609" + y="97.149" + x="122.963" + maskUnits="userSpaceOnUse"> + <g + style="filter:url(#Adobe_OpacityMaskFilter)" + id="g31"> + <radialGradient + gradientUnits="userSpaceOnUse" + fy="-20.899401" + fx="88.977501" + r="673.11609" + cy="-20.899401" + cx="88.977501" + id="XMLID_24_"> + <stop + id="stop34" + style="stop-color:#FFFFFF" + offset="0.309" /> + <stop + id="stop36" + style="stop-color:#000000" + offset="1" /> + </radialGradient> + <ellipse + style="fill:url(#XMLID_24_)" + id="ellipse38" + ry="349.10599" + rx="512.93402" + cy="286.172" + cx="295.89499" + sodipodi:cx="295.89499" + sodipodi:cy="286.172" + sodipodi:rx="512.93402" + sodipodi:ry="349.10599" /> + </g> + </mask><radialGradient + id="XMLID_22_" + cx="622.72803" + cy="621.17328" + r="510.71881" + fx="622.72803" + fy="621.17328" + gradientUnits="userSpaceOnUse"> + <stop + offset="0" + style="stop-color:#FFFFFF" + id="stop14" /> + <stop + offset="0.2814" + style="stop-color:#E6E6E6" + id="stop16" /> + <stop + offset="0.7515" + style="stop-color:#C0C0C0" + id="stop18" /> + <stop + offset="1" + style="stop-color:#B2B2B2" + id="stop20" /> + </radialGradient><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_9_" + id="linearGradient12328" + gradientUnits="userSpaceOnUse" + x1="331.49609" + y1="790.51508" + x2="409.85471" + y2="688.75092" + gradientTransform="matrix(1.1256295,0,0,1.1256295,-41.873391,-39.54673)" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_8_" + id="linearGradient12331" + gradientUnits="userSpaceOnUse" + x1="319.186" + y1="713.54639" + x2="373.12509" + y2="626.00598" + gradientTransform="matrix(1.1256295,0,0,1.1256295,-41.873391,-39.54673)" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_7_" + id="linearGradient12334" + gradientUnits="userSpaceOnUse" + x1="264.5405" + y1="704.23578" + x2="335.09619" + y2="612.60522" + gradientTransform="matrix(1.1256295,0,0,1.1256295,-41.873391,-39.54673)" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_6_" + id="linearGradient12337" + gradientUnits="userSpaceOnUse" + x1="237.20799" + y1="684.01898" + x2="328.18469" + y2="565.86761" + gradientTransform="matrix(1.1256295,0,0,1.1256295,-41.873391,-39.54673)" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_5_" + id="linearGradient12340" + gradientUnits="userSpaceOnUse" + x1="220.9512" + y1="634.63232" + x2="278.14471" + y2="560.35498" + gradientTransform="matrix(1.1256295,0,0,1.1256295,-41.873391,-39.54673)" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_4_" + id="linearGradient12343" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-1.1256295,0,0,1.1256295,1028.9091,-39.54673)" + x1="429.84909" + y1="815.45459" + x2="480.12021" + y2="643.30768" /><radialGradient + inkscape:collect="always" + xlink:href="#radialGradient29545" + id="radialGradient12349" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.1256295,0,0,1.1256295,-41.873391,-39.54673)" + cx="375.21439" + cy="783.16357" + fx="375.21439" + fy="783.16357" + r="300.5752" /><linearGradient + inkscape:collect="always" + xlink:href="#linearGradient5498" + id="linearGradient17820" + gradientUnits="userSpaceOnUse" + x1="1508.4957" + y1="-363.42487" + x2="361.1441" + y2="630.52039" /><linearGradient + inkscape:collect="always" + xlink:href="#linearGradient5498" + id="linearGradient2486" + x1="370.62177" + y1="-309.6048" + x2="370.20648" + y2="458.38397" + gradientUnits="userSpaceOnUse" /><linearGradient + inkscape:collect="always" + xlink:href="#linearGradient5498" + id="linearGradient5490" + x1="247.47713" + y1="274.88394" + x2="130.31757" + y2="580.54669" + gradientUnits="userSpaceOnUse" /><linearGradient + inkscape:collect="always" + xlink:href="#linearGradient5498" + id="linearGradient6539" + x1="220.36789" + y1="756.76105" + x2="185.21225" + y2="491.06723" + gradientUnits="userSpaceOnUse" /><radialGradient + inkscape:collect="always" + xlink:href="#radialGradient29545" + id="radialGradient33274" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.1256295,0,0,1.1256295,-41.873391,-39.54673)" + cx="375.21439" + cy="783.16357" + fx="375.21439" + fy="783.16357" + r="300.5752" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_4_" + id="linearGradient33276" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-1.1256295,0,0,1.1256295,1028.9091,-39.54673)" + x1="429.84909" + y1="815.45459" + x2="480.12021" + y2="643.30768" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_5_" + id="linearGradient33278" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.1256295,0,0,1.1256295,-41.873391,-39.54673)" + x1="220.9512" + y1="634.63232" + x2="278.14471" + y2="560.35498" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_6_" + id="linearGradient33280" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.1256295,0,0,1.1256295,-41.873391,-39.54673)" + x1="237.20799" + y1="684.01898" + x2="328.18469" + y2="565.86761" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_7_" + id="linearGradient33282" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.1256295,0,0,1.1256295,-41.873391,-39.54673)" + x1="264.5405" + y1="704.23578" + x2="335.09619" + y2="612.60522" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_8_" + id="linearGradient33284" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.1256295,0,0,1.1256295,-41.873391,-39.54673)" + x1="319.186" + y1="713.54639" + x2="373.12509" + y2="626.00598" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_9_" + id="linearGradient33286" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.1256295,0,0,1.1256295,-41.873391,-39.54673)" + x1="331.49609" + y1="790.51508" + x2="409.85471" + y2="688.75092" /><linearGradient + inkscape:collect="always" + xlink:href="#linearGradient5498" + id="linearGradient33288" + gradientUnits="userSpaceOnUse" + x1="1508.4957" + y1="-363.42487" + x2="361.1441" + y2="630.52039" /><linearGradient + inkscape:collect="always" + xlink:href="#linearGradient5498" + id="linearGradient33290" + gradientUnits="userSpaceOnUse" + x1="370.62177" + y1="-309.6048" + x2="370.20648" + y2="458.38397" /><linearGradient + inkscape:collect="always" + xlink:href="#linearGradient5498" + id="linearGradient33292" + gradientUnits="userSpaceOnUse" + x1="247.47713" + y1="274.88394" + x2="130.31757" + y2="580.54669" /><linearGradient + inkscape:collect="always" + xlink:href="#linearGradient5498" + id="linearGradient33294" + gradientUnits="userSpaceOnUse" + x1="220.36789" + y1="756.76105" + x2="185.21225" + y2="491.06723" /><linearGradient + inkscape:collect="always" + xlink:href="#linearGradient5498" + id="linearGradient33297" + gradientUnits="userSpaceOnUse" + x1="220.36789" + y1="756.76105" + x2="185.21225" + y2="491.06723" /><linearGradient + inkscape:collect="always" + xlink:href="#linearGradient5498" + id="linearGradient33300" + gradientUnits="userSpaceOnUse" + x1="247.47713" + y1="274.88394" + x2="130.31757" + y2="580.54669" /><linearGradient + inkscape:collect="always" + xlink:href="#linearGradient5498" + id="linearGradient33303" + gradientUnits="userSpaceOnUse" + x1="370.62177" + y1="-309.6048" + x2="370.20648" + y2="458.38397" /><linearGradient + inkscape:collect="always" + xlink:href="#linearGradient5498" + id="linearGradient33306" + gradientUnits="userSpaceOnUse" + x1="1508.4957" + y1="-363.42487" + x2="361.1441" + y2="630.52039" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_9_" + id="linearGradient33309" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.1256295,0,0,1.1256295,-41.873391,-39.54673)" + x1="331.49609" + y1="790.51508" + x2="409.85471" + y2="688.75092" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_8_" + id="linearGradient33312" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.1256295,0,0,1.1256295,-41.873391,-39.54673)" + x1="319.186" + y1="713.54639" + x2="373.12509" + y2="626.00598" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_7_" + id="linearGradient33315" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.1256295,0,0,1.1256295,-41.873391,-39.54673)" + x1="264.5405" + y1="704.23578" + x2="335.09619" + y2="612.60522" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_6_" + id="linearGradient33318" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.1256295,0,0,1.1256295,-41.873391,-39.54673)" + x1="237.20799" + y1="684.01898" + x2="328.18469" + y2="565.86761" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_5_" + id="linearGradient33321" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.1256295,0,0,1.1256295,-41.873391,-39.54673)" + x1="220.9512" + y1="634.63232" + x2="278.14471" + y2="560.35498" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_4_" + id="linearGradient33324" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-1.1256295,0,0,1.1256295,1028.9091,-39.54673)" + x1="429.84909" + y1="815.45459" + x2="480.12021" + y2="643.30768" /><radialGradient + inkscape:collect="always" + xlink:href="#radialGradient29545" + id="radialGradient33328" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.1256295,0,0,1.1256295,-41.873391,-39.54673)" + cx="375.21439" + cy="783.16357" + fx="375.21439" + fy="783.16357" + r="300.5752" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_4_" + id="linearGradient7344" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-1.1256295,0,0,1.1256295,1058.6084,-9.8474173)" + x1="429.84909" + y1="815.45459" + x2="480.12021" + y2="643.30768" /><linearGradient + inkscape:collect="always" + xlink:href="#linearGradient5498" + id="linearGradient7346" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(29.699313,29.699313)" + x1="1508.4957" + y1="-363.42487" + x2="361.1441" + y2="630.52039" /><linearGradient + inkscape:collect="always" + xlink:href="#linearGradient5498" + id="linearGradient7348" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(29.699313,29.699313)" + x1="370.62177" + y1="-309.6048" + x2="370.20648" + y2="458.38397" /><linearGradient + inkscape:collect="always" + xlink:href="#linearGradient5498" + id="linearGradient7350" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(29.699313,29.699313)" + x1="247.47713" + y1="274.88394" + x2="130.31757" + y2="580.54669" /><linearGradient + inkscape:collect="always" + xlink:href="#linearGradient5498" + id="linearGradient7352" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(29.699313,29.699313)" + x1="220.36789" + y1="756.76105" + x2="185.21225" + y2="491.06723" /><linearGradient + inkscape:collect="always" + xlink:href="#linearGradient5498" + id="linearGradient7373" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.9343129,0,0,0.9343129,27.748451,27.748451)" + x1="220.36789" + y1="756.76105" + x2="185.21225" + y2="491.06723" /><linearGradient + inkscape:collect="always" + xlink:href="#linearGradient5498" + id="linearGradient7376" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.9343129,0,0,0.9343129,27.748451,27.748451)" + x1="247.47713" + y1="274.88394" + x2="130.31757" + y2="580.54669" /><linearGradient + inkscape:collect="always" + xlink:href="#linearGradient5498" + id="linearGradient7379" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.9343129,0,0,0.9343129,27.748451,27.748451)" + x1="370.62177" + y1="-309.6048" + x2="370.20648" + y2="458.38397" /><linearGradient + inkscape:collect="always" + xlink:href="#linearGradient5498" + id="linearGradient7382" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.9343129,0,0,0.9343129,27.748451,27.748451)" + x1="1508.4957" + y1="-363.42487" + x2="361.1441" + y2="630.52039" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_4_" + id="linearGradient7385" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-1.0516902,0,0,1.0516902,989.07148,-9.200569)" + x1="429.84909" + y1="815.45459" + x2="480.12021" + y2="643.30768" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_4_" + id="linearGradient7431" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-1.0516902,0,0,1.0516902,989.07148,-9.200569)" + x1="429.84909" + y1="815.45459" + x2="480.12021" + y2="643.30768" /><linearGradient + inkscape:collect="always" + xlink:href="#linearGradient5498" + id="linearGradient7433" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.9343129,0,0,0.9343129,27.748451,27.748451)" + x1="1508.4957" + y1="-363.42487" + x2="361.1441" + y2="630.52039" /><linearGradient + inkscape:collect="always" + xlink:href="#linearGradient5498" + id="linearGradient7435" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.9343129,0,0,0.9343129,27.748451,27.748451)" + x1="370.62177" + y1="-309.6048" + x2="370.20648" + y2="458.38397" /><linearGradient + inkscape:collect="always" + xlink:href="#linearGradient5498" + id="linearGradient7437" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.9343129,0,0,0.9343129,27.748451,27.748451)" + x1="247.47713" + y1="274.88394" + x2="130.31757" + y2="580.54669" /><linearGradient + inkscape:collect="always" + xlink:href="#linearGradient5498" + id="linearGradient7439" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.9343129,0,0,0.9343129,27.748451,27.748451)" + x1="220.36789" + y1="756.76105" + x2="185.21225" + y2="491.06723" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_4_" + id="linearGradient7454" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-1.0516902,0,0,1.0516902,989.07148,-9.200569)" + x1="429.84909" + y1="815.45459" + x2="480.12021" + y2="643.30768" /><linearGradient + inkscape:collect="always" + xlink:href="#linearGradient5498" + id="linearGradient7456" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.9343129,0,0,0.9343129,27.748451,27.748451)" + x1="1508.4957" + y1="-363.42487" + x2="361.1441" + y2="630.52039" /><linearGradient + inkscape:collect="always" + xlink:href="#linearGradient5498" + id="linearGradient7458" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.9343129,0,0,0.9343129,27.748451,27.748451)" + x1="370.62177" + y1="-309.6048" + x2="370.20648" + y2="458.38397" /><linearGradient + inkscape:collect="always" + xlink:href="#linearGradient5498" + id="linearGradient7460" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.9343129,0,0,0.9343129,27.748451,27.748451)" + x1="247.47713" + y1="274.88394" + x2="130.31757" + y2="580.54669" /><linearGradient + inkscape:collect="always" + xlink:href="#linearGradient5498" + id="linearGradient7462" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.9343129,0,0,0.9343129,27.748451,27.748451)" + x1="220.36789" + y1="756.76105" + x2="185.21225" + y2="491.06723" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_4_" + id="linearGradient7475" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-1.0516902,0,0,1.0516902,989.07148,-9.200569)" + x1="429.84909" + y1="815.45459" + x2="480.12021" + y2="643.30768" /><linearGradient + inkscape:collect="always" + xlink:href="#linearGradient5498" + id="linearGradient7477" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.9343129,0,0,0.9343129,27.748451,27.748451)" + x1="1508.4957" + y1="-363.42487" + x2="361.1441" + y2="630.52039" /><linearGradient + inkscape:collect="always" + xlink:href="#linearGradient5498" + id="linearGradient7479" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.9343129,0,0,0.9343129,27.748451,27.748451)" + x1="370.62177" + y1="-309.6048" + x2="370.20648" + y2="458.38397" /><linearGradient + inkscape:collect="always" + xlink:href="#linearGradient5498" + id="linearGradient7481" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.9343129,0,0,0.9343129,27.748451,27.748451)" + x1="247.47713" + y1="274.88394" + x2="130.31757" + y2="580.54669" /><linearGradient + inkscape:collect="always" + xlink:href="#linearGradient5498" + id="linearGradient7483" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.9343129,0,0,0.9343129,27.748451,27.748451)" + x1="220.36789" + y1="756.76105" + x2="185.21225" + y2="491.06723" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_4_" + id="linearGradient7494" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-1.0516902,0,0,1.0516902,989.07148,-9.200569)" + x1="429.84909" + y1="815.45459" + x2="480.12021" + y2="643.30768" /><linearGradient + inkscape:collect="always" + xlink:href="#linearGradient5498" + id="linearGradient7496" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.9343129,0,0,0.9343129,27.748451,27.748451)" + x1="1508.4957" + y1="-363.42487" + x2="361.1441" + y2="630.52039" /><linearGradient + inkscape:collect="always" + xlink:href="#linearGradient5498" + id="linearGradient7498" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.9343129,0,0,0.9343129,27.748451,27.748451)" + x1="370.62177" + y1="-309.6048" + x2="370.20648" + y2="458.38397" /><linearGradient + inkscape:collect="always" + xlink:href="#linearGradient5498" + id="linearGradient7500" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.9343129,0,0,0.9343129,27.748451,27.748451)" + x1="247.47713" + y1="274.88394" + x2="130.31757" + y2="580.54669" /><linearGradient + inkscape:collect="always" + xlink:href="#linearGradient5498" + id="linearGradient7502" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.9343129,0,0,0.9343129,27.748451,27.748451)" + x1="220.36789" + y1="756.76105" + x2="185.21225" + y2="491.06723" /><linearGradient + inkscape:collect="always" + xlink:href="#linearGradient5498" + id="linearGradient7505" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.8998301,0,0,0.8998301,53.458406,33.708279)" + x1="220.36789" + y1="756.76105" + x2="185.21225" + y2="491.06723" /><linearGradient + inkscape:collect="always" + xlink:href="#linearGradient5498" + id="linearGradient7508" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.8998301,0,0,0.8998301,53.458406,33.708279)" + x1="247.47713" + y1="274.88394" + x2="130.31757" + y2="580.54669" /><linearGradient + inkscape:collect="always" + xlink:href="#linearGradient5498" + id="linearGradient7511" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.8998301,0,0,0.8998301,53.458406,33.708279)" + x1="370.62177" + y1="-309.6048" + x2="370.20648" + y2="458.38397" /><linearGradient + inkscape:collect="always" + xlink:href="#linearGradient5498" + id="linearGradient7514" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.8998301,0,0,0.8998301,53.458406,33.708279)" + x1="1508.4957" + y1="-363.42487" + x2="361.1441" + y2="630.52039" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_4_" + id="linearGradient7517" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-1.0128754,0,0,1.0128754,979.30179,-1.8770598)" + x1="429.84909" + y1="815.45459" + x2="480.12021" + y2="643.30768" /></defs> + +<g + id="g8826" + transform="translate(-9.9999999e-7,-77.04248)"><path + id="circle22" + d="M 813.31393,504.16148 C 813.31393,717.34108 640.2986,890.35641 427.119,890.35641 C 213.9394,890.35641 40.924072,717.34108 40.924072,504.16148 C 40.924072,290.98188 213.9394,117.96655 427.119,117.96655 C 640.2986,117.96655 813.31393,290.98188 813.31393,504.16148 z " + style="fill:#dddddd;fill-opacity:1;stroke:#0057ae;stroke-width:81.84814453;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /><g + transform="matrix(1.0007211,0,0,1.0007211,0,77.042475)" + id="g7521"><path + style="opacity:0.22000002;fill:#323232" + d="M 642.57609,234.13404 C 627.10948,234.13404 597.07671,266.39006 585.82368,271.4828 C 574.56556,276.57553 485.45685,334.29019 477.01352,344.47566 C 468.57424,354.66011 457.31815,358.90508 445.12516,358.05629 C 432.93318,357.20749 394.38517,356.85907 381.25527,363.64938 C 368.12233,370.4397 308.1824,425.95743 293.17361,432.74876 C 278.16685,439.53704 258.58696,443.01729 249.20773,443.86506 C 239.8285,444.71487 208.75552,476.03601 200.3132,481.97956 C 191.87189,487.91805 130.15538,511.71454 120.77716,516.80526 C 111.39693,521.89901 175.92721,602.50261 187.1833,604.20019 C 198.43735,605.89878 176.12675,614.22056 154.47147,614.22056 C 152.08716,614.22056 185.41987,701.62968 299.0726,758.04583 C 397.32655,806.81577 476.48682,777.1993 515.20296,765.37094 C 542.57896,757.00763 547.54306,753.27822 548.48402,739.69759 C 549.42296,726.11797 551.11852,715.38655 552.99537,700.10935 C 554.87223,684.83418 561.43364,556.66695 585.82469,522.7174 C 610.20865,488.76684 679.91372,448.01076 704.29969,443.76883 C 728.68771,439.52489 723.99911,431.03801 723.99911,423.3989 C 723.99911,415.7598 720.2464,398.784 713.68095,396.23764 C 707.11449,393.69229 681.49481,408.98265 673.05351,410.68022 C 664.61119,412.3778 624.27951,414.92418 618.65097,406.4373 C 613.02342,397.94839 664.15237,311.37286 670.71782,301.18841 C 677.28126,291.00293 680.09603,277.4223 667.90506,262.99389 C 655.70701,248.56244 647.2647,234.13404 642.57609,234.13404 z " + id="path43" /><path + style="fill:#00316e;fill-opacity:1" + d="M 609.66271,189.23022 C 594.65899,189.23022 565.52971,223.80574 554.61192,229.26413 C 543.69414,234.72353 457.26042,296.58894 449.07233,307.50773 C 440.88426,318.4245 429.96546,322.97434 418.13913,322.06477 C 406.31279,321.15623 368.92351,320.78146 356.18661,328.05999 C 343.44767,335.33952 285.30761,394.85 270.75057,402.12953 C 256.19454,409.40603 237.20313,413.13442 228.10447,414.045 C 219.00783,414.95354 188.8677,448.52733 180.67962,454.89729 C 172.49154,461.26727 107.78906,489.26517 98.691417,494.72154 C 89.592758,500.18093 165.06615,576.6358 175.98393,578.45391 C 186.90172,580.27607 168.46434,621.70065 147.45832,621.70065 C 145.14491,621.70065 166.22994,690.34422 276.47028,750.81794 C 371.77476,803.0975 472.89314,779.95027 510.44549,767.27311 C 537.00106,758.30512 518.36314,753.34912 519.27473,738.79309 C 520.18227,724.23503 520.94698,705.09371 522.76712,688.71856 C 524.58827,672.34238 530.95419,534.95901 554.6109,498.56742 C 578.26459,462.17785 645.87402,418.49051 669.52971,413.94169 C 693.18644,409.39286 688.63559,400.2942 688.63559,392.10713 C 688.63559,383.92006 684.99734,365.72275 678.62736,362.99304 C 672.25941,360.26436 647.40954,376.65471 639.22145,378.47383 C 631.03337,380.29296 591.91105,383.02367 586.45065,373.92602 C 580.99226,364.82635 630.58567,272.0257 636.95463,261.10791 C 643.32461,250.18912 646.05228,235.63107 634.22393,220.16647 C 622.40062,204.69684 614.21153,189.23022 609.66271,189.23022 z " + id="path58" /><path + style="fill:#ffffff" + d="M 428.17672,360.73231 C 428.17672,360.73231 392.13254,355.1858 371.33517,375.98418 C 350.53679,396.78155 332.50963,442.53415 333.90031,456.39939 C 335.28491,470.26465 376.88066,409.25916 394.90275,405.10131 C 412.92889,400.94043 432.33862,370.43565 428.17672,360.73231 z " + id="path60" /></g></g></svg> \ No newline at end of file diff --git a/amarok/src/images/amarok_logo.svg b/amarok/src/images/amarok_logo.svg new file mode 100644 index 00000000..191ef0e2 --- /dev/null +++ b/amarok/src/images/amarok_logo.svg @@ -0,0 +1,2344 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Generator: Adobe Illustrator 10, SVG Export Plug-In . SVG Version: 3.0.0 Build 76) --> +<svg + xmlns:ns0="http://ns.adobe.com/SaveForWeb/1.0/" + xmlns:ns="http://ns.adobe.com/Variables/1.0/" + xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/" + xmlns:i="http://ns.adobe.com/AdobeIllustrator/10.0/" + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://web.resource.org/cc/" + 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" + i:viewOrigin="0.5107 456.6963" + i:rulerOrigin="39 -87.8027" + i:pageBounds="-39 513 839.7402 87.8027" + width="670" + height="250" + viewBox="0 0 760.908 307.134" + overflow="visible" + enable-background="new 0 0 760.908 307.134" + xml:space="preserve" + id="svg2" + sodipodi:version="0.32" + inkscape:version="0.45" + sodipodi:docname="amarok_logo_oxygen.svg" + sodipodi:docbase="/home/knome/Work/Amarok logo" + version="1.0" + inkscape:export-filename="/home/me/amarok_logo2.png" + inkscape:export-xdpi="43.119404" + inkscape:export-ydpi="43.119404" + inkscape:output_extension="org.inkscape.output.svg.inkscape" + sodipodi:modified="TRUE"><defs + id="defs48"><mask + maskUnits="userSpaceOnUse" + x="243.061" + y="279.317" + width="252.506" + height="187.183" + id="XMLID_13_" + i:invertMask="false" + i:clipMask="true"> + <g + filter="url(#Adobe_OpacityMaskFilter_4_)" + id="g6864"> + <linearGradient + id="XMLID_14_" + gradientUnits="userSpaceOnUse" + x1="340.0488" + y1="266.93359" + x2="372.38071" + y2="417.81519"> + <stop + offset="0" + style="stop-color:#FFFFFF" + id="stop6867" /> + <stop + offset="0.0867" + style="stop-color:#F8F8F8" + id="stop6869" /> + <stop + offset="0.2156" + style="stop-color:#E4E4E4" + id="stop6871" /> + <stop + offset="0.3708" + style="stop-color:#C2C2C2" + id="stop6873" /> + <stop + offset="0.5465" + style="stop-color:#949494" + id="stop6875" /> + <stop + offset="0.7394" + style="stop-color:#595959" + id="stop6877" /> + <stop + offset="0.9438" + style="stop-color:#151515" + id="stop6879" /> + <stop + offset="0.9944" + style="stop-color:#000000" + id="stop6881" /> + <a:midPointStop + offset="0" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="0.6215" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="0.9944" + style="stop-color:#000000" /> + </linearGradient> + <ellipse + style="fill:url(#XMLID_14_)" + sodipodi:ry="183.32201" + sodipodi:rx="215.32401" + sodipodi:cy="374.28201" + sodipodi:cx="363.052" + i:knockout="Off" + cx="363.052" + cy="374.28201" + rx="215.32401" + ry="183.32201" + id="ellipse6883" /> + </g> + </mask><mask + maskUnits="userSpaceOnUse" + x="191.541" + y="217.631" + width="447.197" + height="527.73" + id="XMLID_10_" + i:invertMask="false" + i:clipMask="true"> + <g + filter="url(#Adobe_OpacityMaskFilter_3_)" + id="g6830"> + + <radialGradient + id="XMLID_11_" + cx="657.2124" + cy="155.23289" + r="606.95343" + fx="657.2124" + fy="155.23289" + gradientTransform="matrix(0.931,0,0,0.9299,28.7459,21.6629)" + gradientUnits="userSpaceOnUse"> + <stop + offset="0" + style="stop-color:#C9C9C8" + id="stop6833" /> + <stop + offset="0.0881" + style="stop-color:#C4C4C4" + id="stop6835" /> + <stop + offset="0.2192" + style="stop-color:#B8B8B7" + id="stop6837" /> + <stop + offset="0.3769" + style="stop-color:#A1A0A0" + id="stop6839" /> + <stop + offset="0.5556" + style="stop-color:#7F7F7E" + id="stop6841" /> + <stop + offset="0.7517" + style="stop-color:#4F4F4E" + id="stop6843" /> + <stop + offset="0.9595" + style="stop-color:#121212" + id="stop6845" /> + <stop + offset="1" + style="stop-color:#000000" + id="stop6847" /> + <a:midPointStop + offset="0" + style="stop-color:#C9C9C8" /> + <a:midPointStop + offset="0.6215" + style="stop-color:#C9C9C8" /> + <a:midPointStop + offset="1" + style="stop-color:#000000" /> + </radialGradient> + <circle + style="fill:url(#XMLID_11_)" + sodipodi:ry="302.95401" + sodipodi:rx="302.95401" + sodipodi:cy="444.22601" + sodipodi:cx="408.427" + i:knockout="Off" + cx="408.427" + cy="444.22601" + r="302.95401" + id="circle6849" /> + </g> + </mask><linearGradient + id="XMLID_12_" + gradientUnits="userSpaceOnUse" + x1="517.18988" + y1="239.939" + x2="410.27759" + y2="522.02393"> + <stop + offset="0" + style="stop-color:#FFFFFF" + id="stop6852" /> + <stop + offset="0.9944" + style="stop-color:#FFFFFF" + id="stop6854" /> + <a:midPointStop + offset="0" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="0.6215" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="0.9944" + style="stop-color:#FFFFFF" /> + </linearGradient><linearGradient + id="XMLID_9_" + gradientUnits="userSpaceOnUse" + x1="331.49609" + y1="790.51508" + x2="409.85471" + y2="688.75092"> + <stop + offset="0" + style="stop-color:#FFFFFF" + id="stop6818" /> + <stop + offset="1" + style="stop-color:#193D6B" + id="stop6820" /> + <a:midPointStop + offset="0" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="0.5" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="1" + style="stop-color:#193D6B" /> + </linearGradient><linearGradient + id="XMLID_8_" + gradientUnits="userSpaceOnUse" + x1="319.186" + y1="713.54639" + x2="373.12509" + y2="626.00598"> + <stop + offset="0" + style="stop-color:#FFFFFF" + id="stop6811" /> + <stop + offset="1" + style="stop-color:#193D6B" + id="stop6813" /> + <a:midPointStop + offset="0" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="0.5" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="1" + style="stop-color:#193D6B" /> + </linearGradient><linearGradient + id="XMLID_7_" + gradientUnits="userSpaceOnUse" + x1="264.5405" + y1="704.23578" + x2="335.09619" + y2="612.60522"> + <stop + offset="0" + style="stop-color:#FFFFFF" + id="stop6804" /> + <stop + offset="1" + style="stop-color:#193D6B" + id="stop6806" /> + <a:midPointStop + offset="0" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="0.5" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="1" + style="stop-color:#193D6B" /> + </linearGradient><linearGradient + id="XMLID_6_" + gradientUnits="userSpaceOnUse" + x1="237.20799" + y1="684.01898" + x2="328.18469" + y2="565.86761"> + <stop + offset="0" + style="stop-color:#FFFFFF" + id="stop6797" /> + <stop + offset="1" + style="stop-color:#193D6B" + id="stop6799" /> + <a:midPointStop + offset="0" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="0.5" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="1" + style="stop-color:#193D6B" /> + </linearGradient><linearGradient + id="XMLID_5_" + gradientUnits="userSpaceOnUse" + x1="220.9512" + y1="634.63232" + x2="278.14471" + y2="560.35498"> + <stop + offset="0" + style="stop-color:#FFFFFF" + id="stop6790" /> + <stop + offset="1" + style="stop-color:#193D6B" + id="stop6792" /> + <a:midPointStop + offset="0" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="0.5" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="1" + style="stop-color:#193D6B" /> + </linearGradient><linearGradient + id="XMLID_4_" + gradientUnits="userSpaceOnUse" + x1="429.84909" + y1="815.45459" + x2="480.12021" + y2="643.30768" + gradientTransform="matrix(-1,0,0,1,951.2744,0)"> + <stop + offset="0" + style="stop-color:#FFFFFF" + id="stop6783" /> + <stop + offset="1" + style="stop-color:#E5EAF2" + id="stop6785" /> + <a:midPointStop + offset="0" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="0.5" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="1" + style="stop-color:#E5EAF2" /> + </linearGradient><mask + maskUnits="userSpaceOnUse" + x="101.57" + y="425.034" + width="156.907" + height="127.553" + id="XMLID_2_" + i:invertMask="false" + i:clipMask="true"> + <g + filter="url(#Adobe_OpacityMaskFilter_2_)" + id="g6757"> + <linearGradient + id="XMLID_3_" + gradientUnits="userSpaceOnUse" + x1="206.09081" + y1="397.62061" + x2="186.92191" + y2="563.47321"> + <stop + offset="0" + style="stop-color:#FFFFFF" + id="stop6760" /> + <stop + offset="0.1111" + style="stop-color:#CBCBCB" + id="stop6762" /> + <stop + offset="0.2396" + style="stop-color:#969696" + id="stop6764" /> + <stop + offset="0.3698" + style="stop-color:#686868" + id="stop6766" /> + <stop + offset="0.4991" + style="stop-color:#424242" + id="stop6768" /> + <stop + offset="0.6273" + style="stop-color:#252525" + id="stop6770" /> + <stop + offset="0.7542" + style="stop-color:#111111" + id="stop6772" /> + <stop + offset="0.8792" + style="stop-color:#040404" + id="stop6774" /> + <stop + offset="1" + style="stop-color:#000000" + id="stop6776" /> + <a:midPointStop + offset="0" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="0.2994" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="1" + style="stop-color:#000000" /> + </linearGradient> + <ellipse + style="fill:url(#XMLID_3_)" + sodipodi:ry="123.604" + sodipodi:rx="160.94" + sodipodi:cy="495.62299" + sodipodi:cx="194.76401" + i:knockout="Off" + cx="194.76401" + cy="495.62299" + rx="160.94" + ry="123.604" + id="ellipse6778" /> + </g> + </mask><mask + maskUnits="userSpaceOnUse" + x="155.705" + y="461.543" + width="93.126" + height="44.813" + id="XMLID_26_" + i:invertMask="false" + i:clipMask="true"> + <g + filter="url(#Adobe_OpacityMaskFilter_1_)" + id="g6740"> + <linearGradient + id="XMLID_1_" + gradientUnits="userSpaceOnUse" + x1="211.7222" + y1="562.85791" + x2="200.0733" + y2="466.75449"> + <stop + offset="0" + style="stop-color:#FFFFFF" + id="stop6743" /> + <stop + offset="1" + style="stop-color:#000000" + id="stop6745" /> + <a:midPointStop + offset="0" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="0.5" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="1" + style="stop-color:#000000" /> + </linearGradient> + <ellipse + style="fill:url(#XMLID_1_)" + sodipodi:ry="59.682999" + sodipodi:rx="80.542" + sodipodi:cy="495.98801" + sodipodi:cx="203.617" + i:knockout="Off" + cx="203.617" + cy="495.98801" + rx="80.542" + ry="59.682999" + id="ellipse6747" /> + </g> + </mask><radialGradient + id="XMLID_25_" + cx="375.21439" + cy="783.16357" + r="300.5752" + fx="375.21439" + fy="783.16357" + gradientUnits="userSpaceOnUse"> + <stop + offset="0.0112" + style="stop-color:#D2E8EC" + id="stop6717" /> + <stop + offset="0.2921" + style="stop-color:#60A8CE" + id="stop6719" /> + <stop + offset="0.4575" + style="stop-color:#4680A8" + id="stop6721" /> + <stop + offset="0.6767" + style="stop-color:#2F5C84" + id="stop6723" /> + <stop + offset="0.8653" + style="stop-color:#234970" + id="stop6725" /> + <stop + offset="1" + style="stop-color:#1F426A" + id="stop6727" /> + <a:midPointStop + offset="0.0112" + style="stop-color:#D2E8EC" /> + <a:midPointStop + offset="0.5" + style="stop-color:#D2E8EC" /> + <a:midPointStop + offset="0.2921" + style="stop-color:#60A8CE" /> + <a:midPointStop + offset="0.3651" + style="stop-color:#60A8CE" /> + <a:midPointStop + offset="1" + style="stop-color:#1F426A" /> + </radialGradient><mask + maskUnits="userSpaceOnUse" + x="122.963" + y="97.149" + width="576.609" + height="587.491" + id="XMLID_23_" + i:invertMask="false" + i:clipMask="true"> + <g + filter="url(#Adobe_OpacityMaskFilter)" + id="g6702"> + + <radialGradient + id="XMLID_24_" + cx="88.977501" + cy="-20.899401" + r="673.11609" + fx="88.977501" + fy="-20.899401" + gradientUnits="userSpaceOnUse"> + <stop + offset="0.309" + style="stop-color:#FFFFFF" + id="stop6705" /> + <stop + offset="1" + style="stop-color:#000000" + id="stop6707" /> + <a:midPointStop + offset="0.309" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="0.5" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="1" + style="stop-color:#000000" /> + </radialGradient> + <ellipse + style="fill:url(#XMLID_24_)" + sodipodi:ry="349.10599" + sodipodi:rx="512.93402" + sodipodi:cy="286.172" + sodipodi:cx="295.89499" + i:knockout="Off" + cx="295.89499" + cy="286.172" + rx="512.93402" + ry="349.10599" + id="ellipse6709" /> + </g> + </mask><radialGradient + id="XMLID_22_" + cx="622.72803" + cy="621.17328" + r="510.71881" + fx="622.72803" + fy="621.17328" + gradientUnits="userSpaceOnUse"> + <stop + offset="0" + style="stop-color:#FFFFFF" + id="stop6685" /> + <stop + offset="0.2814" + style="stop-color:#E6E6E6" + id="stop6687" /> + <stop + offset="0.7515" + style="stop-color:#C0C0C0" + id="stop6689" /> + <stop + offset="1" + style="stop-color:#B2B2B2" + id="stop6691" /> + <a:midPointStop + offset="0" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="0.4359" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="1" + style="stop-color:#B2B2B2" /> + </radialGradient> + + + + + + + + + + + <radialGradient + inkscape:collect="always" + xlink:href="#XMLID_22_" + id="radialGradient9742" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(3.12237,0,0,3.12237,-1084.425,-172.2462)" + cx="622.72803" + cy="621.17328" + fx="622.72803" + fy="621.17328" + r="510.71881" /><radialGradient + inkscape:collect="always" + xlink:href="#XMLID_25_" + id="radialGradient9744" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(3.12237,0,0,3.12237,-1084.425,-172.2462)" + cx="375.21439" + cy="783.16357" + fx="375.21439" + fy="783.16357" + r="300.5752" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_4_" + id="linearGradient9746" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-3.12237,0,0,3.12237,1885.808,-172.2462)" + x1="429.84909" + y1="815.45459" + x2="480.12021" + y2="643.30768" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_5_" + id="linearGradient9748" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(3.12237,0,0,3.12237,-1084.425,-172.2462)" + x1="220.9512" + y1="634.63232" + x2="278.14471" + y2="560.35498" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_6_" + id="linearGradient9750" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(3.12237,0,0,3.12237,-1084.425,-172.2462)" + x1="237.20799" + y1="684.01898" + x2="328.18469" + y2="565.86761" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_7_" + id="linearGradient9752" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(3.12237,0,0,3.12237,-1084.425,-172.2462)" + x1="264.5405" + y1="704.23578" + x2="335.09619" + y2="612.60522" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_8_" + id="linearGradient9754" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(3.12237,0,0,3.12237,-1084.425,-172.2462)" + x1="319.186" + y1="713.54639" + x2="373.12509" + y2="626.00598" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_9_" + id="linearGradient9756" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(3.12237,0,0,3.12237,-1084.425,-172.2462)" + x1="331.49609" + y1="790.51508" + x2="409.85471" + y2="688.75092" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_12_" + id="linearGradient9758" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(3.12237,0,0,3.12237,-1084.425,-172.2462)" + x1="517.18988" + y1="239.939" + x2="410.27759" + y2="522.02393" /> + <foreignObject + id="foreignObject8" + height="1" + width="1" + y="0" + x="0" + requiredExtensions="http://ns.adobe.com/AdobeIllustrator/10.0/"> + <i:pgfRef + xlink:href="#adobe_illustrator_pgf"> + </i:pgfRef> + </foreignObject> + + <linearGradient + inkscape:collect="always" + xlink:href="#XMLID_12_" + id="linearGradient3423" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(3.12237,0,0,3.12237,-1084.425,-172.2462)" + x1="517.18988" + y1="239.939" + x2="410.27759" + y2="522.02393" /><radialGradient + inkscape:collect="always" + xlink:href="#XMLID_22_" + id="radialGradient3567" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(3.12237,0,0,3.12237,-1084.425,-172.2462)" + cx="622.72803" + cy="621.17328" + fx="622.72803" + fy="621.17328" + r="510.71881" /><radialGradient + inkscape:collect="always" + xlink:href="#XMLID_25_" + id="radialGradient3569" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(3.12237,0,0,3.12237,-1084.425,-172.2462)" + cx="375.21439" + cy="783.16357" + fx="375.21439" + fy="783.16357" + r="300.5752" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_4_" + id="linearGradient3571" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-3.12237,0,0,3.12237,1885.808,-172.2462)" + x1="429.84909" + y1="815.45459" + x2="480.12021" + y2="643.30768" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_5_" + id="linearGradient3573" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(3.12237,0,0,3.12237,-1084.425,-172.2462)" + x1="220.9512" + y1="634.63232" + x2="278.14471" + y2="560.35498" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_6_" + id="linearGradient3575" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(3.12237,0,0,3.12237,-1084.425,-172.2462)" + x1="237.20799" + y1="684.01898" + x2="328.18469" + y2="565.86761" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_7_" + id="linearGradient3577" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(3.12237,0,0,3.12237,-1084.425,-172.2462)" + x1="264.5405" + y1="704.23578" + x2="335.09619" + y2="612.60522" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_8_" + id="linearGradient3579" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(3.12237,0,0,3.12237,-1084.425,-172.2462)" + x1="319.186" + y1="713.54639" + x2="373.12509" + y2="626.00598" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_9_" + id="linearGradient3581" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(3.12237,0,0,3.12237,-1084.425,-172.2462)" + x1="331.49609" + y1="790.51508" + x2="409.85471" + y2="688.75092" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_12_" + id="linearGradient3583" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(3.12237,0,0,3.12237,-1084.425,-172.2462)" + x1="517.18988" + y1="239.939" + x2="410.27759" + y2="522.02393" /><radialGradient + gradientUnits="userSpaceOnUse" + fy="621.17328" + fx="622.72803" + r="510.71881" + cy="621.17328" + cx="622.72803" + id="radialGradient3930"> + <stop + id="stop14" + style="stop-color:#FFFFFF" + offset="0" /> + <stop + id="stop16" + style="stop-color:#E6E6E6" + offset="0.2814" /> + <stop + id="stop18" + style="stop-color:#C0C0C0" + offset="0.7515" /> + <stop + id="stop20" + style="stop-color:#B2B2B2" + offset="1" /> + </radialGradient><mask + maskUnits="userSpaceOnUse" + x="122.963" + y="97.149" + width="576.609" + height="587.491" + id="mask3921"> + <g + id="g3923" + style="filter:url(#Adobe_OpacityMaskFilter)"> + <radialGradient + id="radialGradient3925" + cx="88.977501" + cy="-20.899401" + r="673.11609" + fx="88.977501" + fy="-20.899401" + gradientUnits="userSpaceOnUse"> + <stop + offset="0.309" + style="stop-color:#FFFFFF" + id="stop34" /> + <stop + offset="1" + style="stop-color:#000000" + id="stop36" /> + </radialGradient> + <ellipse + cx="295.89499" + cy="286.172" + rx="512.93402" + ry="349.10599" + id="ellipse38" + style="fill:url(#XMLID_24_)" /> + </g> + </mask><mask + maskUnits="userSpaceOnUse" + x="243.061" + y="279.317" + width="252.506" + height="187.183" + id="mask3907"> + <g + id="g193" + style="filter:url(#Adobe_OpacityMaskFilter_4_)"> + <linearGradient + id="linearGradient3910" + gradientUnits="userSpaceOnUse" + x1="340.0488" + y1="266.93359" + x2="372.38071" + y2="417.81519"> + <stop + offset="0" + style="stop-color:#FFFFFF" + id="stop196" /> + <stop + offset="0.0867" + style="stop-color:#F8F8F8" + id="stop198" /> + <stop + offset="0.2156" + style="stop-color:#E4E4E4" + id="stop200" /> + <stop + offset="0.3708" + style="stop-color:#C2C2C2" + id="stop202" /> + <stop + offset="0.5465" + style="stop-color:#949494" + id="stop204" /> + <stop + offset="0.7394" + style="stop-color:#595959" + id="stop206" /> + <stop + offset="0.9438" + style="stop-color:#151515" + id="stop208" /> + <stop + offset="0.9944" + style="stop-color:#000000" + id="stop210" /> + </linearGradient> + <ellipse + cx="363.052" + cy="374.28201" + rx="215.32401" + ry="183.32201" + id="ellipse212" + style="fill:url(#XMLID_14_)" /> + </g> + </mask><mask + maskUnits="userSpaceOnUse" + x="191.541" + y="217.631" + width="447.197" + height="527.73" + id="mask3893"> + <g + id="g159"> + <radialGradient + id="radialGradient3896" + cx="657.2124" + cy="155.23289" + r="606.95343" + fx="657.2124" + fy="155.23289" + gradientTransform="matrix(0.931,0,0,0.9299,28.7459,21.6629)" + gradientUnits="userSpaceOnUse"> + <stop + offset="0" + style="stop-color:#C9C9C8" + id="stop162" /> + <stop + offset="0.0881" + style="stop-color:#C4C4C4" + id="stop164" /> + <stop + offset="0.2192" + style="stop-color:#B8B8B7" + id="stop166" /> + <stop + offset="0.3769" + style="stop-color:#A1A0A0" + id="stop168" /> + <stop + offset="0.5556" + style="stop-color:#7F7F7E" + id="stop170" /> + <stop + offset="0.7517" + style="stop-color:#4F4F4E" + id="stop172" /> + <stop + offset="0.9595" + style="stop-color:#121212" + id="stop174" /> + <stop + offset="1" + style="stop-color:#000000" + id="stop176" /> + </radialGradient> + <circle + cx="408.427" + cy="444.22601" + r="302.95401" + id="circle178" + style="fill:url(#XMLID_11_)" /> + </g> + </mask><linearGradient + id="linearGradient3889" + gradientUnits="userSpaceOnUse" + x1="331.49609" + y1="790.51508" + x2="409.85471" + y2="688.75092"> + <stop + offset="0" + style="stop-color:#FFFFFF" + id="stop147" /> + <stop + offset="1" + style="stop-color:#193D6B" + id="stop149" /> + </linearGradient><linearGradient + id="linearGradient3885" + gradientUnits="userSpaceOnUse" + x1="319.186" + y1="713.54639" + x2="373.12509" + y2="626.00598"> + <stop + offset="0" + style="stop-color:#FFFFFF" + id="stop140" /> + <stop + offset="1" + style="stop-color:#193D6B" + id="stop142" /> + </linearGradient><linearGradient + id="linearGradient3881" + gradientUnits="userSpaceOnUse" + x1="264.5405" + y1="704.23578" + x2="335.09619" + y2="612.60522"> + <stop + offset="0" + style="stop-color:#FFFFFF" + id="stop133" /> + <stop + offset="1" + style="stop-color:#193D6B" + id="stop135" /> + </linearGradient><linearGradient + id="linearGradient3877" + gradientUnits="userSpaceOnUse" + x1="237.20799" + y1="684.01898" + x2="328.18469" + y2="565.86761"> + <stop + offset="0" + style="stop-color:#FFFFFF" + id="stop126" /> + <stop + offset="1" + style="stop-color:#193D6B" + id="stop128" /> + </linearGradient><linearGradient + id="linearGradient3873" + gradientUnits="userSpaceOnUse" + x1="220.9512" + y1="634.63232" + x2="278.14471" + y2="560.35498"> + <stop + offset="0" + style="stop-color:#FFFFFF" + id="stop119" /> + <stop + offset="1" + style="stop-color:#193D6B" + id="stop121" /> + </linearGradient><linearGradient + id="linearGradient3869" + gradientUnits="userSpaceOnUse" + x1="429.84909" + y1="815.45459" + x2="480.12021" + y2="643.30768" + gradientTransform="matrix(-1.1256295,0,0,1.1256295,1028.9091,-39.54673)"> + <stop + offset="0" + style="stop-color:#FFFFFF" + id="stop112" /> + <stop + offset="1" + style="stop-color:#E5EAF2" + id="stop114" /> + </linearGradient><mask + maskUnits="userSpaceOnUse" + x="101.57" + y="425.034" + width="156.907" + height="127.553" + id="mask3854"> + <g + id="g86"> + <linearGradient + id="linearGradient3857" + gradientUnits="userSpaceOnUse" + x1="206.09081" + y1="397.62061" + x2="186.92191" + y2="563.47321"> + <stop + offset="0" + style="stop-color:#FFFFFF" + id="stop89" /> + <stop + offset="0.1111" + style="stop-color:#CBCBCB" + id="stop91" /> + <stop + offset="0.2396" + style="stop-color:#969696" + id="stop93" /> + <stop + offset="0.3698" + style="stop-color:#686868" + id="stop95" /> + <stop + offset="0.4991" + style="stop-color:#424242" + id="stop97" /> + <stop + offset="0.6273" + style="stop-color:#252525" + id="stop99" /> + <stop + offset="0.7542" + style="stop-color:#111111" + id="stop101" /> + <stop + offset="0.8792" + style="stop-color:#040404" + id="stop103" /> + <stop + offset="1" + style="stop-color:#000000" + id="stop105" /> + </linearGradient> + <ellipse + cx="194.76401" + cy="495.62299" + rx="160.94" + ry="123.604" + id="ellipse107" + style="fill:url(#XMLID_3_)" /> + </g> + </mask><mask + maskUnits="userSpaceOnUse" + x="155.705" + y="461.543" + width="93.126" + height="44.813" + id="mask3846"> + <g + id="g69"> + <linearGradient + id="linearGradient3849" + gradientUnits="userSpaceOnUse" + x1="211.7222" + y1="562.85791" + x2="200.0733" + y2="466.75449"> + <stop + offset="0" + style="stop-color:#FFFFFF" + id="stop72" /> + <stop + offset="1" + style="stop-color:#000000" + id="stop74" /> + </linearGradient> + <ellipse + cx="203.617" + cy="495.98801" + rx="80.542" + ry="59.682999" + id="ellipse76" + style="fill:url(#XMLID_1_)" /> + </g> + </mask><radialGradient + gradientTransform="matrix(1.1256295,0,0,1.1256295,-41.873391,-39.54673)" + id="radialGradient3838" + cx="375.21439" + cy="783.16357" + r="300.5752" + fx="375.21439" + fy="783.16357" + gradientUnits="userSpaceOnUse"> + <stop + offset="0.0112" + style="stop-color:#D2E8EC" + id="stop46" /> + <stop + offset="0.2921" + style="stop-color:#60A8CE" + id="stop48" /> + <stop + offset="0.4575" + style="stop-color:#4680A8" + id="stop50" /> + <stop + offset="0.6767" + style="stop-color:#2F5C84" + id="stop52" /> + <stop + offset="0.8653" + style="stop-color:#234970" + id="stop54" /> + <stop + offset="1" + style="stop-color:#1F426A" + id="stop56" /> + </radialGradient><linearGradient + id="linearGradient5498"><stop + id="stop5500" + offset="0" + style="stop-color:#eeeeee;stop-opacity:1;" /><stop + id="stop5502" + offset="1" + style="stop-color:#eeeeee;stop-opacity:0;" /></linearGradient><radialGradient + r="300.5752" + fy="783.16357" + fx="375.21439" + cy="783.16357" + cx="375.21439" + gradientTransform="matrix(1.1256295,0,0,1.1256295,-41.873391,-39.54673)" + gradientUnits="userSpaceOnUse" + id="radialGradient33328" + xlink:href="#radialGradient29545" + inkscape:collect="always" /><linearGradient + y2="643.30768" + x2="480.12021" + y1="815.45459" + x1="429.84909" + gradientTransform="matrix(-1.1256295,0,0,1.1256295,1028.9091,-39.54673)" + gradientUnits="userSpaceOnUse" + id="linearGradient33324" + xlink:href="#XMLID_4_" + inkscape:collect="always" /><linearGradient + y2="560.35498" + x2="278.14471" + y1="634.63232" + x1="220.9512" + gradientTransform="matrix(1.1256295,0,0,1.1256295,-41.873391,-39.54673)" + gradientUnits="userSpaceOnUse" + id="linearGradient33321" + xlink:href="#XMLID_5_" + inkscape:collect="always" /><linearGradient + y2="565.86761" + x2="328.18469" + y1="684.01898" + x1="237.20799" + gradientTransform="matrix(1.1256295,0,0,1.1256295,-41.873391,-39.54673)" + gradientUnits="userSpaceOnUse" + id="linearGradient33318" + xlink:href="#XMLID_6_" + inkscape:collect="always" /><linearGradient + y2="612.60522" + x2="335.09619" + y1="704.23578" + x1="264.5405" + gradientTransform="matrix(1.1256295,0,0,1.1256295,-41.873391,-39.54673)" + gradientUnits="userSpaceOnUse" + id="linearGradient33315" + xlink:href="#XMLID_7_" + inkscape:collect="always" /><linearGradient + y2="626.00598" + x2="373.12509" + y1="713.54639" + x1="319.186" + gradientTransform="matrix(1.1256295,0,0,1.1256295,-41.873391,-39.54673)" + gradientUnits="userSpaceOnUse" + id="linearGradient33312" + xlink:href="#XMLID_8_" + inkscape:collect="always" /><linearGradient + y2="688.75092" + x2="409.85471" + y1="790.51508" + x1="331.49609" + gradientTransform="matrix(1.1256295,0,0,1.1256295,-41.873391,-39.54673)" + gradientUnits="userSpaceOnUse" + id="linearGradient33309" + xlink:href="#XMLID_9_" + inkscape:collect="always" /><linearGradient + y2="630.52039" + x2="361.1441" + y1="-363.42487" + x1="1508.4957" + gradientUnits="userSpaceOnUse" + id="linearGradient33306" + xlink:href="#linearGradient5498" + inkscape:collect="always" /><linearGradient + y2="458.38397" + x2="370.20648" + y1="-309.6048" + x1="370.62177" + gradientUnits="userSpaceOnUse" + id="linearGradient33303" + xlink:href="#linearGradient5498" + inkscape:collect="always" /><linearGradient + y2="580.54669" + x2="130.31757" + y1="274.88394" + x1="247.47713" + gradientUnits="userSpaceOnUse" + id="linearGradient33300" + xlink:href="#linearGradient5498" + inkscape:collect="always" /><linearGradient + y2="491.06723" + x2="185.21225" + y1="756.76105" + x1="220.36789" + gradientUnits="userSpaceOnUse" + id="linearGradient33297" + xlink:href="#linearGradient5498" + inkscape:collect="always" /><linearGradient + y2="491.06723" + x2="185.21225" + y1="756.76105" + x1="220.36789" + gradientUnits="userSpaceOnUse" + id="linearGradient33294" + xlink:href="#linearGradient5498" + inkscape:collect="always" /><linearGradient + y2="580.54669" + x2="130.31757" + y1="274.88394" + x1="247.47713" + gradientUnits="userSpaceOnUse" + id="linearGradient33292" + xlink:href="#linearGradient5498" + inkscape:collect="always" /><linearGradient + y2="458.38397" + x2="370.20648" + y1="-309.6048" + x1="370.62177" + gradientUnits="userSpaceOnUse" + id="linearGradient33290" + xlink:href="#linearGradient5498" + inkscape:collect="always" /><linearGradient + y2="630.52039" + x2="361.1441" + y1="-363.42487" + x1="1508.4957" + gradientUnits="userSpaceOnUse" + id="linearGradient33288" + xlink:href="#linearGradient5498" + inkscape:collect="always" /><linearGradient + y2="688.75092" + x2="409.85471" + y1="790.51508" + x1="331.49609" + gradientTransform="matrix(1.1256295,0,0,1.1256295,-41.873391,-39.54673)" + gradientUnits="userSpaceOnUse" + id="linearGradient33286" + xlink:href="#XMLID_9_" + inkscape:collect="always" /><linearGradient + y2="626.00598" + x2="373.12509" + y1="713.54639" + x1="319.186" + gradientTransform="matrix(1.1256295,0,0,1.1256295,-41.873391,-39.54673)" + gradientUnits="userSpaceOnUse" + id="linearGradient33284" + xlink:href="#XMLID_8_" + inkscape:collect="always" /><linearGradient + y2="612.60522" + x2="335.09619" + y1="704.23578" + x1="264.5405" + gradientTransform="matrix(1.1256295,0,0,1.1256295,-41.873391,-39.54673)" + gradientUnits="userSpaceOnUse" + id="linearGradient33282" + xlink:href="#XMLID_7_" + inkscape:collect="always" /><linearGradient + y2="565.86761" + x2="328.18469" + y1="684.01898" + x1="237.20799" + gradientTransform="matrix(1.1256295,0,0,1.1256295,-41.873391,-39.54673)" + gradientUnits="userSpaceOnUse" + id="linearGradient33280" + xlink:href="#XMLID_6_" + inkscape:collect="always" /><linearGradient + y2="560.35498" + x2="278.14471" + y1="634.63232" + x1="220.9512" + gradientTransform="matrix(1.1256295,0,0,1.1256295,-41.873391,-39.54673)" + gradientUnits="userSpaceOnUse" + id="linearGradient33278" + xlink:href="#XMLID_5_" + inkscape:collect="always" /><linearGradient + y2="643.30768" + x2="480.12021" + y1="815.45459" + x1="429.84909" + gradientTransform="matrix(-1.1256295,0,0,1.1256295,1028.9091,-39.54673)" + gradientUnits="userSpaceOnUse" + id="linearGradient33276" + xlink:href="#XMLID_4_" + inkscape:collect="always" /><radialGradient + r="300.5752" + fy="783.16357" + fx="375.21439" + cy="783.16357" + cx="375.21439" + gradientTransform="matrix(1.1256295,0,0,1.1256295,-41.873391,-39.54673)" + gradientUnits="userSpaceOnUse" + id="radialGradient33274" + xlink:href="#radialGradient29545" + inkscape:collect="always" /><linearGradient + gradientUnits="userSpaceOnUse" + y2="491.06723" + x2="185.21225" + y1="756.76105" + x1="220.36789" + id="linearGradient2976" + xlink:href="#linearGradient5498" + inkscape:collect="always" /><linearGradient + gradientUnits="userSpaceOnUse" + y2="580.54669" + x2="130.31757" + y1="274.88394" + x1="247.47713" + id="linearGradient2974" + xlink:href="#linearGradient5498" + inkscape:collect="always" /><linearGradient + gradientUnits="userSpaceOnUse" + y2="458.38397" + x2="370.20648" + y1="-309.6048" + x1="370.62177" + id="linearGradient2972" + xlink:href="#linearGradient5498" + inkscape:collect="always" /><linearGradient + y2="630.52039" + x2="361.1441" + y1="-363.42487" + x1="1508.4957" + gradientUnits="userSpaceOnUse" + id="linearGradient2970" + xlink:href="#linearGradient5498" + inkscape:collect="always" /><radialGradient + r="300.5752" + fy="783.16357" + fx="375.21439" + cy="783.16357" + cx="375.21439" + gradientTransform="matrix(1.1256295,0,0,1.1256295,-41.873391,-39.54673)" + gradientUnits="userSpaceOnUse" + id="radialGradient2968" + xlink:href="#radialGradient29545" + inkscape:collect="always" /><linearGradient + y2="643.30768" + x2="480.12021" + y1="815.45459" + x1="429.84909" + gradientTransform="matrix(-1.1256295,0,0,1.1256295,1028.9091,-39.54673)" + gradientUnits="userSpaceOnUse" + id="linearGradient2966" + xlink:href="#XMLID_4_" + inkscape:collect="always" /><linearGradient + gradientTransform="matrix(1.1256295,0,0,1.1256295,-41.873391,-39.54673)" + y2="560.35498" + x2="278.14471" + y1="634.63232" + x1="220.9512" + gradientUnits="userSpaceOnUse" + id="linearGradient2964" + xlink:href="#XMLID_5_" + inkscape:collect="always" /><linearGradient + gradientTransform="matrix(1.1256295,0,0,1.1256295,-41.873391,-39.54673)" + y2="565.86761" + x2="328.18469" + y1="684.01898" + x1="237.20799" + gradientUnits="userSpaceOnUse" + id="linearGradient2962" + xlink:href="#XMLID_6_" + inkscape:collect="always" /><linearGradient + gradientTransform="matrix(1.1256295,0,0,1.1256295,-41.873391,-39.54673)" + y2="612.60522" + x2="335.09619" + y1="704.23578" + x1="264.5405" + gradientUnits="userSpaceOnUse" + id="linearGradient2960" + xlink:href="#XMLID_7_" + inkscape:collect="always" /><linearGradient + gradientTransform="matrix(1.1256295,0,0,1.1256295,-41.873391,-39.54673)" + y2="626.00598" + x2="373.12509" + y1="713.54639" + x1="319.186" + gradientUnits="userSpaceOnUse" + id="linearGradient2958" + xlink:href="#XMLID_8_" + inkscape:collect="always" /><linearGradient + gradientTransform="matrix(1.1256295,0,0,1.1256295,-41.873391,-39.54673)" + y2="688.75092" + x2="409.85471" + y1="790.51508" + x1="331.49609" + gradientUnits="userSpaceOnUse" + id="linearGradient2956" + xlink:href="#XMLID_9_" + inkscape:collect="always" /><radialGradient + gradientUnits="userSpaceOnUse" + fy="621.17328" + fx="622.72803" + r="510.71881" + cy="621.17328" + cx="622.72803" + id="radialGradient2946"> + <stop + id="stop2948" + style="stop-color:#FFFFFF" + offset="0" /> + <stop + id="stop2950" + style="stop-color:#E6E6E6" + offset="0.2814" /> + <stop + id="stop2952" + style="stop-color:#C0C0C0" + offset="0.7515" /> + <stop + id="stop2954" + style="stop-color:#B2B2B2" + offset="1" /> + </radialGradient><mask + maskUnits="userSpaceOnUse" + x="122.963" + y="97.149" + width="576.609" + height="587.491" + id="mask2934"> + <g + id="g2936" + style="filter:url(#Adobe_OpacityMaskFilter)"> + <radialGradient + id="radialGradient2938" + cx="88.977501" + cy="-20.899401" + r="673.11609" + fx="88.977501" + fy="-20.899401" + gradientUnits="userSpaceOnUse"> + <stop + offset="0.309" + style="stop-color:#FFFFFF" + id="stop2940" /> + <stop + offset="1" + style="stop-color:#000000" + id="stop2942" /> + </radialGradient> + <ellipse + cx="295.89499" + cy="286.172" + rx="512.93402" + ry="349.10599" + id="ellipse2944" + style="fill:url(#XMLID_24_)" /> + </g> + </mask><mask + maskUnits="userSpaceOnUse" + x="243.061" + y="279.317" + width="252.506" + height="187.183" + id="mask2910"> + <g + id="g2912" + style="filter:url(#Adobe_OpacityMaskFilter_4_)"> + <linearGradient + id="linearGradient2914" + gradientUnits="userSpaceOnUse" + x1="340.0488" + y1="266.93359" + x2="372.38071" + y2="417.81519"> + <stop + offset="0" + style="stop-color:#FFFFFF" + id="stop2916" /> + <stop + offset="0.0867" + style="stop-color:#F8F8F8" + id="stop2918" /> + <stop + offset="0.2156" + style="stop-color:#E4E4E4" + id="stop2920" /> + <stop + offset="0.3708" + style="stop-color:#C2C2C2" + id="stop2922" /> + <stop + offset="0.5465" + style="stop-color:#949494" + id="stop2924" /> + <stop + offset="0.7394" + style="stop-color:#595959" + id="stop2926" /> + <stop + offset="0.9438" + style="stop-color:#151515" + id="stop2928" /> + <stop + offset="0.9944" + style="stop-color:#000000" + id="stop2930" /> + </linearGradient> + <ellipse + cx="363.052" + cy="374.28201" + rx="215.32401" + ry="183.32201" + id="ellipse2932" + style="fill:url(#XMLID_14_)" /> + </g> + </mask><mask + maskUnits="userSpaceOnUse" + x="191.541" + y="217.631" + width="447.197" + height="527.73" + id="mask2886"> + <g + id="g2888"> + <radialGradient + id="radialGradient2890" + cx="657.2124" + cy="155.23289" + r="606.95343" + fx="657.2124" + fy="155.23289" + gradientTransform="matrix(0.931,0,0,0.9299,28.7459,21.6629)" + gradientUnits="userSpaceOnUse"> + <stop + offset="0" + style="stop-color:#C9C9C8" + id="stop2892" /> + <stop + offset="0.0881" + style="stop-color:#C4C4C4" + id="stop2894" /> + <stop + offset="0.2192" + style="stop-color:#B8B8B7" + id="stop2896" /> + <stop + offset="0.3769" + style="stop-color:#A1A0A0" + id="stop2898" /> + <stop + offset="0.5556" + style="stop-color:#7F7F7E" + id="stop2900" /> + <stop + offset="0.7517" + style="stop-color:#4F4F4E" + id="stop2902" /> + <stop + offset="0.9595" + style="stop-color:#121212" + id="stop2904" /> + <stop + offset="1" + style="stop-color:#000000" + id="stop2906" /> + </radialGradient> + <circle + cx="408.427" + cy="444.22601" + r="302.95401" + id="circle2908" + style="fill:url(#XMLID_11_)" /> + </g> + </mask><linearGradient + id="linearGradient2880" + gradientUnits="userSpaceOnUse" + x1="331.49609" + y1="790.51508" + x2="409.85471" + y2="688.75092"> + <stop + offset="0" + style="stop-color:#FFFFFF" + id="stop2882" /> + <stop + offset="1" + style="stop-color:#193D6B" + id="stop2884" /> + </linearGradient><linearGradient + id="linearGradient2874" + gradientUnits="userSpaceOnUse" + x1="319.186" + y1="713.54639" + x2="373.12509" + y2="626.00598"> + <stop + offset="0" + style="stop-color:#FFFFFF" + id="stop2876" /> + <stop + offset="1" + style="stop-color:#193D6B" + id="stop2878" /> + </linearGradient><linearGradient + id="linearGradient2868" + gradientUnits="userSpaceOnUse" + x1="264.5405" + y1="704.23578" + x2="335.09619" + y2="612.60522"> + <stop + offset="0" + style="stop-color:#FFFFFF" + id="stop2870" /> + <stop + offset="1" + style="stop-color:#193D6B" + id="stop2872" /> + </linearGradient><linearGradient + id="linearGradient2862" + gradientUnits="userSpaceOnUse" + x1="237.20799" + y1="684.01898" + x2="328.18469" + y2="565.86761"> + <stop + offset="0" + style="stop-color:#FFFFFF" + id="stop2864" /> + <stop + offset="1" + style="stop-color:#193D6B" + id="stop2866" /> + </linearGradient><linearGradient + id="linearGradient2856" + gradientUnits="userSpaceOnUse" + x1="220.9512" + y1="634.63232" + x2="278.14471" + y2="560.35498"> + <stop + offset="0" + style="stop-color:#FFFFFF" + id="stop2858" /> + <stop + offset="1" + style="stop-color:#193D6B" + id="stop2860" /> + </linearGradient><linearGradient + id="linearGradient2850" + gradientUnits="userSpaceOnUse" + x1="429.84909" + y1="815.45459" + x2="480.12021" + y2="643.30768" + gradientTransform="matrix(-1.1256295,0,0,1.1256295,1028.9091,-39.54673)"> + <stop + offset="0" + style="stop-color:#FFFFFF" + id="stop2852" /> + <stop + offset="1" + style="stop-color:#E5EAF2" + id="stop2854" /> + </linearGradient><mask + maskUnits="userSpaceOnUse" + x="101.57" + y="425.034" + width="156.907" + height="127.553" + id="mask2824"> + <g + id="g2826"> + <linearGradient + id="linearGradient2828" + gradientUnits="userSpaceOnUse" + x1="206.09081" + y1="397.62061" + x2="186.92191" + y2="563.47321"> + <stop + offset="0" + style="stop-color:#FFFFFF" + id="stop2830" /> + <stop + offset="0.1111" + style="stop-color:#CBCBCB" + id="stop2832" /> + <stop + offset="0.2396" + style="stop-color:#969696" + id="stop2834" /> + <stop + offset="0.3698" + style="stop-color:#686868" + id="stop2836" /> + <stop + offset="0.4991" + style="stop-color:#424242" + id="stop2838" /> + <stop + offset="0.6273" + style="stop-color:#252525" + id="stop2840" /> + <stop + offset="0.7542" + style="stop-color:#111111" + id="stop2842" /> + <stop + offset="0.8792" + style="stop-color:#040404" + id="stop2844" /> + <stop + offset="1" + style="stop-color:#000000" + id="stop2846" /> + </linearGradient> + <ellipse + cx="194.76401" + cy="495.62299" + rx="160.94" + ry="123.604" + id="ellipse2848" + style="fill:url(#XMLID_3_)" /> + </g> + </mask><mask + maskUnits="userSpaceOnUse" + x="155.705" + y="461.543" + width="93.126" + height="44.813" + id="mask2812"> + <g + id="g2814"> + <linearGradient + id="linearGradient2816" + gradientUnits="userSpaceOnUse" + x1="211.7222" + y1="562.85791" + x2="200.0733" + y2="466.75449"> + <stop + offset="0" + style="stop-color:#FFFFFF" + id="stop2818" /> + <stop + offset="1" + style="stop-color:#000000" + id="stop2820" /> + </linearGradient> + <ellipse + cx="203.617" + cy="495.98801" + rx="80.542" + ry="59.682999" + id="ellipse2822" + style="fill:url(#XMLID_1_)" /> + </g> + </mask><radialGradient + gradientTransform="matrix(1.1256295,0,0,1.1256295,-41.873391,-39.54673)" + id="radialGradient2798" + cx="375.21439" + cy="783.16357" + r="300.5752" + fx="375.21439" + fy="783.16357" + gradientUnits="userSpaceOnUse"> + <stop + offset="0.0112" + style="stop-color:#D2E8EC" + id="stop2800" /> + <stop + offset="0.2921" + style="stop-color:#60A8CE" + id="stop2802" /> + <stop + offset="0.4575" + style="stop-color:#4680A8" + id="stop2804" /> + <stop + offset="0.6767" + style="stop-color:#2F5C84" + id="stop2806" /> + <stop + offset="0.8653" + style="stop-color:#234970" + id="stop2808" /> + <stop + offset="1" + style="stop-color:#1F426A" + id="stop2810" /> + </radialGradient><linearGradient + id="linearGradient2792"><stop + id="stop2794" + offset="0" + style="stop-color:#eeeeee;stop-opacity:1;" /><stop + id="stop2796" + offset="1" + style="stop-color:#eeeeee;stop-opacity:0;" /></linearGradient><radialGradient + gradientUnits="userSpaceOnUse" + fy="783.16357" + fx="375.21439" + r="300.5752" + cy="783.16357" + cx="375.21439" + id="radialGradient29545" + gradientTransform="matrix(1.1256295,0,0,1.1256295,-41.873391,-39.54673)"> + <stop + id="stop29547" + style="stop-color:#eeeeef;stop-opacity:0.94117647;" + offset="0.0112" /> + <stop + id="stop29549" + style="stop-color:#6193cf;stop-opacity:1;" + offset="0.29210001" /> + <stop + id="stop29551" + style="stop-color:#2c72c7;stop-opacity:1;" + offset="0.45750001" /> + <stop + id="stop29553" + style="stop-color:#0057ae;stop-opacity:1;" + offset="0.6767" /> + <stop + id="stop29555" + style="stop-color:#004d9a;stop-opacity:1;" + offset="0.8653" /> + <stop + id="stop29557" + style="stop-color:#00438a;stop-opacity:1;" + offset="1" /> + </radialGradient><linearGradient + inkscape:collect="always" + xlink:href="#linearGradient5498" + id="linearGradient5118" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.245382,0,0,0.245382,401.46597,47.397214)" + x1="220.36789" + y1="756.76105" + x2="185.21225" + y2="491.06723" /><linearGradient + inkscape:collect="always" + xlink:href="#linearGradient5498" + id="linearGradient5121" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.245382,0,0,0.245382,401.46597,47.397214)" + x1="247.47713" + y1="274.88394" + x2="130.31757" + y2="580.54669" /><linearGradient + inkscape:collect="always" + xlink:href="#linearGradient5498" + id="linearGradient5124" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.245382,0,0,0.245382,401.46597,47.397214)" + x1="370.62177" + y1="-309.6048" + x2="370.20648" + y2="458.38397" /><linearGradient + inkscape:collect="always" + xlink:href="#linearGradient5498" + id="linearGradient5127" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.245382,0,0,0.245382,401.46597,47.397214)" + x1="1508.4957" + y1="-363.42487" + x2="361.1441" + y2="630.52039" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_5_" + id="linearGradient5130" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.2762093,0,0,0.2762093,391.19099,37.693155)" + x1="331.49609" + y1="790.51508" + x2="409.85471" + y2="688.75092" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_5_" + id="linearGradient5133" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.2762093,0,0,0.2762093,391.19099,37.693155)" + x1="319.186" + y1="713.54639" + x2="373.12509" + y2="626.00598" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_5_" + id="linearGradient5136" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.2762093,0,0,0.2762093,391.19099,37.693155)" + x1="264.5405" + y1="704.23578" + x2="335.09619" + y2="612.60522" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_5_" + id="linearGradient5139" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.2762093,0,0,0.2762093,391.19099,37.693155)" + x1="237.20799" + y1="684.01898" + x2="328.18469" + y2="565.86761" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_5_" + id="linearGradient5142" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.2762093,0,0,0.2762093,391.19099,37.693155)" + x1="220.9512" + y1="634.63232" + x2="278.14471" + y2="560.35498" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_4_" + id="linearGradient5145" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-0.2762093,0,0,0.2762093,653.94178,37.693155)" + x1="429.84909" + y1="815.45459" + x2="480.12021" + y2="643.30768" /><radialGradient + inkscape:collect="always" + xlink:href="#radialGradient29545" + id="radialGradient5149" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.2762093,0,0,0.2762093,391.19099,37.693155)" + cx="375.21439" + cy="783.16357" + fx="375.21439" + fy="783.16357" + r="300.5752" /><radialGradient + inkscape:collect="always" + xlink:href="#XMLID_22_" + id="radialGradient5154" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.245382,0,0,0.245382,401.46597,47.397214)" + cx="663.91144" + cy="649.22662" + fx="663.91144" + fy="649.22662" + r="427.12018" /></defs><sodipodi:namedview + inkscape:window-height="593" + inkscape:window-width="773" + inkscape:pageshadow="2" + inkscape:pageopacity="0.0" + borderopacity="1.0" + bordercolor="#666666" + pagecolor="#ffffff" + id="base" + inkscape:zoom="0.90286865" + inkscape:cx="367.33414" + inkscape:cy="138.18589" + inkscape:window-x="1630" + inkscape:window-y="133" + inkscape:current-layer="svg2" + width="441px" + height="180px" + showguides="true" + inkscape:guide-bbox="true" /> + <metadata + id="metadata4"> + <ns:variableSets> + <ns:variableSet + varSetName="binding1" + locked="none"> + <ns:variables /> + <ns:sampleDataSets /> + </ns:variableSet> + </ns:variableSets> + <ns0:sfw> + <ns0:slices /> + <ns0:sliceSourceBounds + y="-7892" + x="-7791" + width="16383" + height="16383" + bottomLeftOrigin="true" /> + </ns0:sfw> + <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> + + <i:pgf> + + eJzdWwtv3MZ27h/gf2BRuJDSiJ73wykK7DPX9yq2YNmJgSAQqF1a4vVqueBypTi/vt8ZPlcrW3aa +Jm4hW1p+c+bMeZ8ZkvvkX8/OT0bL4jI7kQmLoydPJmWWVkX5LA5o/Hy12m2rkqCjV8cxZwkD0ei5 +u2gIf8zKbV6sn7VDc5p7dF5lt1n89+JyexwfbbJ8fXK3q0621W6ZF9tjkL3Oq1UGwvQmLYv38Tq7 +i29rTmKBT+/BOUnz41Yg4NO0wgQmEvwTjKlY8GdKx2+uSxCNi916ma+vxsWvoIm58rE1IlbaYvBv ++atsu0+RaM4skSXaCA1anijuPCaYxHgjMWtaLHY32bo6K4tFtt1OilVRbp/Fkw/pOv4hvcJIGsEQ ++mKerzJofZNWsSETjJ7Li0D9ZgsyzKDPBNuL5zdAzrOqgiDgRfZ69f14uAJJTz9HP7/KrvJgeej+ +y3Ewuqn5nmebtMbBCobHzxEG42n2Lt2tqnrBuKfCx+qYfPsf8csNAdswyZChmv/1D69/XAe0Pyec +/vVk98drVMQyVmGZs7MpLSFqSsNgV4yKZubD849IRLLd6+xms4Kzg6cUg69i4eGo4eeGEpYPVCdQ +5sS4WCsWWy7q0d6B2W2e3T2LXxTrrHbZqKzO89/gG2ddYhVDpAidcG98Pf5qt8rKN+u8IksR5GvL +/1Ass1UDBS7zVRocOdAr/K4JXqflVVYh+IrVrgpJ4lgz9GJ3c5p+yMp+gZebbP26+DFIeuIk/CA8 +p0jFB4SotyYWJnBHcCvRrcXb1YgBTW/5UuifId5elvlVvn7W2CgEkrw4SzdZ+SpbVMBd7ISKtbWw +Yzv+A0THJIydKHK+i2mIYvj7Ml/2IWwT5oyJXf8h6J+4wX/f/q8Vhc2qKlvX4T9bLyfFDblpS2mM +mF8jHVbFVT3WfQ4jWHa3qZUN1xfw6FmZr0mW6MUm4vF4PRj+vkyXOTijzLxZr9ObbBlfNVDMj6OH +QMgu4vEy+jn6z2g+n8/m0/lkPp6P5n7u5nZu5nqu5nIu5nzOZvPZbDadTWbj2WjmZ25mZ2amZ2om +Z2LGZ2w6n86m0+lkOp6Opn7qoqmdmqmeqqmciimfssl8MptMJ5PJeDKa+Imb2ImZ6ImayImY8Akb +z8ez8XQ8GY/Ho7Efu7Edm7Eeq7EcizEfs2g0H81G09FkNB6NRn7kRnZkRnqkRnIkRnzE/NzP/NRP +/NiPvPfOW0S49spLj9jyzM3dzE3dxI3dyHnnIqSDcdopJ51w3DE7tzM7tRM7tiPrrbPWGqutstIK +yy0zczMzUzMxYzNC2XTGGmO0UUYaYbhhkZ7rmZ7qiR7rkfbaaauN1lppqYXmmqm5mqmpmqixGimv +nLLKKK2Ukkoorpicy5mcyokcy5H00kXSSiO1VFJKIblkYi5mYiomYixGwgsnrEBBF0pIIQQXjM/5 +jE/5hI/5iHvuuOWGa6645IJzziIGRzL4go0ZDMagMzMMgjHwZhzp+l8Ri59cjEvEBKWcRsaxgJxH +Ty4GyHgb8fqqG+2uMTae1pGJoG7jsgOGcXxy8okA/z0TfD1CJa/6sMq20dN/rIu7dbiIn0VHPzdt +45fj+OkL5EP8bfT0PEcJzloSFr+M9lvE2xTAq2i/HbwdRYOLD7j4Oz78E9BdrOIf4p9/YfES6NtX +UeCxjJ6epRA1/i6Kn0II/A3iQr9e2Ec1PktXKEZZkOXs8p5I97toGHr7W3SAny0illinJMqUQB+o +5ydCyVCCEdlUvwRKKFQ/eoHdStNk79JqcU2FAxzOxp24rVRf4plTCDR0x4E1AsHvCQHO6qHzDzeX +xYrY/EsDg9E98IuZu4uz1S5U22K3eb5+V0RH9dbxjMp8uX4JF1XX8bjcba/j10Wxwo5wj6AZqrnQ +YFkNqT+5Bmg/zr9n9Lt4ny+CfA9x3h/quD8w45MrTNLVKkf32Vzni4eWeWC8W+tjcz+5IFpvmfU8 +wmX399PGCFGSb296GwyQ/vNHmJCL3uXrJYxzvsurrFexuNnQzjw+v8aOBPAB5WMxBtevsvVy27Gs +L3stT/PbFvw0M2QetvGDYAnobH2brYrNwGwdkkLun9Jyc/yJxGmSvN2M4hSDY8PF0/Ya5ZWu8gUV +orT8UF+//eH0Bbaa4eLo35YNLQr0eQXmV/HRrzerNQhO6BdtYo5RtZ9cPDx+m652DYGPnz7HmnvD +1YdNM/p0VJbpQxLcZFW6hOD/cwH47xPgNi3z9HJFR6ft1yDEXy7Dn2mF73qaxXW+WpbZuqG5F7YD +QtTBMr/cVSTot2Ho20Ndnm1T2mZQSvzJjv1slfYlpjmPi3n07+vtxWK3rYqb7z5CPJBZ/LluaHU4 +dEcYeHb7eeohDrdfq3Kkw7efE46/MwqgO8L1RTDIY7a6zMNNJ/71mWqoxWEwrIrF+2z5uH7rYv0x +K/yFurXS/29GwRfUgq85WT7TSPu6b9/d/WUdcLvKF/8v2l9Q5LzYlYss3Jv+KnXaF/nDoyKeWOcR +x6+ydPXVxPmHB7vdZ6hiPf/KVPn1IVXu8mV1/ag63EgnvzJ1GskPVbrO8qvrx3fZX6NOreiHSl0W +FbaEp9m7qr4j/7h68dMxjvRfjWqHCnwlDRb96P94f/3jmswfJPzgpgMhRmupG/uNnvs4LavLIi2X +8SLcBuVx2e63HqW8KrNWpEdpLzvn0S1993FS0QvwKOVAgEdpLw966yHZfnTReFWm6+0mhekXH7Bc +voy3+W8NH9YmNRFu6CkcBm92q3AnuiaRjLVFjYhe7qrNropfpdsqK/Pf6ke6/UPFRjy9N+eHbHv9 +yIz78aAaBaBTo5JtdVpu8uSe9Okq396DbtLt+0YB08qySZf9Pnz0PB7tqqKTq7UsqdvYjrP4Xf14 +EHYpA93Jbbao4IzLdJWuF4dW3JtS7KpVvs7iKvu1+jzKbVUW77P7utwjXqzyDZxNdyt+RaRd0TP0 +T8/YlNk2K2+zuLjNyg3dEdzeK+rvR8/P0hKFDDpuZzeX2XK0Ws0LEP4j+/Ap2vPd5TarAuU4WxV3 +AyvWRv8Y9SsKhI55a/RDOc7K4l2+yj5N+dN1vri+T/mwwGfTOd3mxeqX+SqvPklLhDDd9qNhul+n +z7PV39JqWixOi0W6otPsNow/VNM7WiiTlc+nQ8rh8Gu6wUartkMw66Iol9nyUKD46Yui2hvmw/xe +F73/43wdQqjYhvvf92tKd0M4PORGZVhnq/3Kcv7j9+TG2qF0g3mfSTNMhhiUhX4wuLZOvSZL+7Gz +Mlvkvc0HK87Wi2LZraX6gfBE6nXXPju1MRTumwdfbNLFA+PtCx6HI2dX7+6lFcC36eaQ8hynp06s +7w7d0D9L3L/zTi8V3H+NILwwETUvv3TvslgfC63pT/MQ9/QyOgqk9LjvdF3HV/XAHubN8ykio6a9 +AO13dXWg90UwdNzKdxGxmB6Zvr2rn7B+9vPHz33Kqr1LDMMcyUzCFT7cDDDhEnA3sfYCSyoXS2UT +bp2NtZOJhihSi8Q4xeNJNISckDY+jbRyCQ8YxozX9PYNT6S1sWI+EV6r+EcQaawsTCyYSpQXgiZq +nignVSxIBKUlFoSqUgDRJpGahAJPz8BcaJ9ABEcyQFDpGb0GoxMD+eNOGeFUwo3g8aHKi+jdw57a +azqL9jHUlh5DxTfYFQ38xOPR5iNPwD/TaaMIKtxF2jCooDTEkwkX3pNHWkwImQhnoYQwZHcTc+3q +99OU0wnzfoAsIqXgEq6HVLC7Y8r2nDqkWw/zOsyJxFsu4o6TQgTQ22/9eh2yiDqpOqzXpuV0qN8i +uvyCyEYQR8ohiqT2LQ8FG/WYNInjENBLKIG50sDznIIWanGPYOoQElkl2okBkbQJ08J2fHqgXQyz +WkwYj/gkRRs+2C0mXEKHbrEOgV1bkTqsk7pjdKgbhedBZGHxBEFuAplzUlKYtJiwrvaW5hDCSocc +sQmE4Y1YIWsahMTydXp3mPIca8ueUQt0q9GsBhKYrcEYPuQeJrE20cLDJBwIY6JHyNwsMXaPqlOk +4XOgGOm/i77ZUS3cfEGkGNQTiV+oSAycGEWJUSzhzA6w0wHGYXoWKpehtz0F5CQ6b1HgTiPokjgq +LgPMWJkwpchbKnFSq0AHw0hO5nQwgOYNP60p7wYY1mWenOOpTFoxlGVI1+rxENbpsYKVvnkTvQmh +8nYZGXpb8u1PsFxjuy9pH5QNxlEuaTKDgOUkKirTxvTYKTAEE2ssYpymNGeo4SgrUmvYA/6UXide +Wh0QZRBli6jHBKgsGIAXcgPLUjwZLETFxwczmwHiEI5O9wBYWZQTB9t1rEImU4+RHBILyqRGG/gT +zSdUnA5CNVJgRwIg/qxR4kGMWqH3eh+TFKmkKxD0J3xAWlvHbSCHy3lohpZZ3SxZy9Vip4d2/rgX +v9iHAvVLOV8b1DDq7AQZK2g9RJwzaCjox/QmHkTXaKsEQFHnqNWTUpR91HFR3jBLo+N6Rd3VNJq3 +PhUWvdRLNfQyaldiNbFqMfIIkp6Yk32QQT2CwuCZDS5tMQHfahUCDZDltB9AFHploRSHdspRBUaZ +dF4HxHGjev+RCCJhjOuhTwXqFjKWZsL4huoTEOYEUTkkE4ToEQtfNdpQfQuq1hA4YUtRa28EvdqK +cLIKWhgbKqmB8mFIMcRnh4AV7XgweUAlscEynGSyzXalQ9BnDdeC5nUYqaUczdOJYIKKpa09K7Bz +8qFwcVXvijAPMviQN2gH2ALx4C6P9h/Mzi1tqxyVflvThN6PeR0WdmEIu9MBBmFQ9EQQEBJTYaB9 +GCMLQSqrVI+QDORBqNpTQSrtGSEoYcG3rZhNwE4OY5jyg8fTKJRCRL1wtI+k+tQjqLSIleDekFOk +bKhc5Dmu0FE7pNZRMSMHVK2GZGVvrBvYhloSDzmBxii9q7s0dkEmbtenRkCvxcb3RVx8OrWnX5je +3gdTKlriBlconlRbWug04tSRHKf0QzBrKWKODIHgg4TkKJpGSzlMW46tHkc7GlAh77ElFF1NoO0k +d+Spto5gGsztGE1riSgK0AIGfByl15AzDIlNgmADkcLGNFQNRUFO+dwhkFYzQfW7w4aJzRBiflgA +BljXDIYYthqWIWfIhyHw2iU5nWVCx2kRKAiXC6H4gAplUNVxzxlqJ6dSpHnPmKwCj1LKUloJLWkS +NhlcUqKh/dOevEeQxE6bel6DDXQhxzhN2fsANtCvxyQczy22/7SrsGpgU2zckNFURwcKduHRUcFS +3qk60hDHnN5iRsA0XdpbSSlE5ykZNoKi3lp2wdhq2AG9gi000AWJ6xltmA+hQcukuN+/akP+D+yd +kMAxIfqIo1MxTjQhB0BNXxyiQouTf9s4kQn0bnl3TTUiwXnZdghHUDgsLNFzncHZFPbGNtGF/YM0 +9aa6CbsWwW4SNCI0TBFiG83L6GFPRfBx7oY5BVHDpr2DqO55qvYKNc4zGVtIIClCcA== + + + avGaU7FvIaqfOImHDS/2BooKIboag9GJD85xMvR/4+AhaoV62HApbPV+2OKTd9TjWi4BMpwOS3sQ +M6GDtt0Vuw64wIeWz1BIYoR18HrXNSUPpWPQWTkd4qTsoeZbXNTYOdW59tphu2JI7RagIKbvpiDw +6STAQq4jc3zCyNnwASq/D5tXqOjp9gEnnth546wrnXRhAwI6HXfRA4+DnUcjOwiotpFJ1DEZ2r51 +mvoYJJfMmx4ybVqgCxnaP3a8GFJQkki0n7AhvyABQgvpnahwhEQYCSoA9XWvdgvg+GJouyKoUPB+ +0Xtifax3vYl886m5//GlXaz7Vl19tnEwQDhmOeU66HQAYceFYzNlfj/zIayb+q4BWW17+vZHu4Y1 +PXTaQ+Fwx0XHjmY+hPVT39Un0/ptgO5weBM57cXwsHj04jj+piEjCaGRacm6632y7ljZcuuPmTWZ +xeYpHFRRpmnN7rp+zaIjw0ktnF1bsvZ6n6yRrT2PtrL159OGG+IqnFxbbu31PrfT/ssO4UYnve0c +vm72ukzzVVZGV9v0ll6RXhdVWmUbjNCTt21VlFm8vS7uCKEvgjbkT57MXs6j/wZ+BIAW + + </i:pgf> +<g + id="g6143"><g + inkscape:export-ydpi="42.189999" + inkscape:export-xdpi="42.189999" + inkscape:export-filename="/home/knome/Work/Amarok logo/oxygen_logo.png" + id="g25" + style="fill:#0057ae;fill-opacity:1" + transform="matrix(1.057339,0,0,1.057339,-27.56698,-5.6631554)"> + <g + i:knockout="Off" + id="g27" + style="fill:#0057ae;fill-opacity:1"> + <path + i:knockout="Off" + d="M 364.178,121.267 L 364.546,121.267 C 368.59,106.011 379.986,100.864 395.427,101.231 L 395.427,130.825 C 392.854,130.641 390.464,130.641 388.074,130.641 C 369.876,130.825 363.627,141.67 364.178,159.316 L 364.178,196.263 L 329.253,196.263 L 329.253,127.7 C 329.253,119.429 329.069,113.914 328.15,103.988 L 363.627,103.988 L 364.178,121.267 z " + id="path29" + style="fill:#0057ae;fill-opacity:1" /> + </g> + <g + i:knockout="Off" + id="g31" + style="fill:#0057ae;fill-opacity:1"> + <path + i:knockout="Off" + d="M 235.979,130.09 C 236.163,118.509 241.126,111.341 248.479,107.113 C 256.015,102.701 265.757,101.231 275.684,101.231 C 294.249,101.231 315.02,108.032 315.02,135.788 L 315.02,186.154 C 315.204,190.749 315.939,193.507 316.307,196.263 L 291.492,196.263 C 290.94,192.035 290.573,187.808 290.573,183.948 L 290.205,183.948 C 283.955,194.609 274.397,199.02 261.897,199.02 C 244.618,199.02 231.751,189.094 231.751,171.081 C 231.751,164.647 234.692,149.023 251.419,141.854 C 262.448,137.075 276.418,136.523 287.447,136.891 L 287.447,128.251 C 287.447,124.391 284.506,116.855 274.764,116.855 C 265.205,116.855 262.08,125.862 262.448,130.09 L 235.979,130.09 L 235.979,130.09 z M 258.22,167.404 C 258.22,175.125 263.918,180.639 271.638,180.639 C 286.895,180.639 287.446,163.728 287.446,152.883 C 277.888,152.7 258.22,152.516 258.22,167.404 z " + id="path33" + style="fill:#0057ae;fill-opacity:1" /> + </g> + <g + i:knockout="Off" + id="g35" + style="fill:#0057ae;fill-opacity:1"> + <path + i:knockout="Off" + d="M 98.489,117.223 L 98.857,117.223 C 104.371,105.643 115.768,101.231 125.143,101.231 C 142.605,101.231 152.899,107.113 157.678,118.509 C 164.295,107.113 174.773,101.231 188.375,101.231 C 209.697,101.231 222.748,114.649 222.748,131.193 L 222.748,196.263 L 205.286,196.263 L 205.286,141.67 C 205.286,128.987 203.448,114.649 183.044,114.649 C 177.713,114.649 165.949,117.59 162.641,128.986 C 160.251,137.442 160.803,146.632 160.803,148.838 L 160.803,196.262 L 143.34,196.262 L 143.34,141.67 C 143.34,125.678 139.664,114.649 122.936,114.649 C 115.768,114.649 105.474,117.406 100.143,129.722 C 98.121,134.501 98.856,146.633 98.856,148.839 L 98.856,196.263 L 81.395,196.263 L 81.395,103.988 L 98.49,103.988 L 98.49,117.223 L 98.489,117.223 z " + id="path37" + style="fill:#0057ae;fill-opacity:1" /> + </g> + <g + i:knockout="Off" + id="g39" + style="fill:#0057ae;fill-opacity:1"> + <path + i:knockout="Off" + d="M 60.292,131.193 C 59.189,116.12 51.469,111.341 38.418,111.341 C 26.837,111.341 17.279,114.833 15.809,127.333 L 4.044,127.333 C 6.801,108.4 20.955,101.231 38.601,101.231 C 59.005,101.231 71.688,110.606 71.32,131.744 L 71.32,175.492 C 71.136,182.661 71.871,190.013 72.239,196.263 L 60.843,196.263 L 60.475,182.66 L 60.107,182.66 L 59.556,183.947 C 55.512,191.667 43.197,199.019 30.697,199.019 C 13.603,199.021 0,188.543 0,170.529 C 0,158.03 7.537,148.471 18.565,143.876 C 30.88,138.545 46.873,140.383 60.291,139.648 L 60.291,131.193 L 60.292,131.193 z M 32.719,188.911 C 56.799,188.911 61.394,169.058 60.291,149.758 C 44.666,150.31 11.948,147.368 11.948,170.529 C 11.948,183.029 21.506,188.911 32.719,188.911 z " + id="path41" + style="fill:#0057ae;fill-opacity:1" /> + </g> + </g><g + inkscape:export-ydpi="42.189999" + inkscape:export-xdpi="42.189999" + inkscape:export-filename="/home/knome/Work/Amarok logo/oxygen_logo.png" + style="fill:#0057ae" + id="g4531" + transform="matrix(1.057339,0,0,1.057339,-37.592927,-9.1602333)"><g + transform="translate(20.36266,1.566356)" + style="fill:#0057ae;fill-opacity:1" + id="g21" + i:knockout="Off"> + <path + style="fill:#0057ae;fill-opacity:1" + id="path23" + d="M 603.85,105.816 L 639.597,105.816 L 639.597,145.393 L 660.919,105.723 L 700.38,105.723 L 672.534,151.862 L 700.621,197.992 L 661.044,197.992 L 639.583,158.54 L 639.597,197.992 L 603.85,197.992 L 603.85,105.816 z " + i:knockout="Off" /> + </g><path + style="fill:#0057ae;fill-opacity:1" + id="path43" + d="M 781.27063,199.55435 L 720.73663,199.55435 L 720.73663,173.86635 L 781.27063,173.86635 L 781.27063,199.55435 z " + i:knockout="Off" /><path + style="fill:#0057ae;fill-opacity:1" + id="path45" + d="M 781.26163,132.98035 L 720.72763,132.98035 L 720.72763,107.29235 L 781.26163,107.29235 L 781.26163,132.98035 z " + i:knockout="Off" /></g><path + inkscape:export-ydpi="42.189999" + inkscape:export-xdpi="42.189999" + inkscape:export-filename="/home/knome/Work/Amarok logo/oxygen_logo.png" + id="flowRoot7510" + d="M -55.413237,281.31195 C -55.737529,281.12367 -56.09319,280.98768 -56.48022,280.90398 C -56.856814,280.80985 -57.275239,280.76278 -57.735494,280.76276 C -59.36736,280.76278 -60.622634,281.29627 -61.501318,282.36324 C -62.369555,283.41977 -62.803671,284.94179 -62.803665,286.9293 L -62.803665,296.18695 L -65.706487,296.18695 L -65.706487,278.61311 L -62.803665,278.61311 L -62.803665,281.34333 C -62.196955,280.27636 -61.407179,279.48658 -60.434334,278.974 C -59.461506,278.45098 -58.279457,278.18947 -56.888184,278.18945 C -56.689444,278.18947 -56.469772,278.20516 -56.229165,278.23652 C -55.988584,278.25746 -55.721838,278.29407 -55.428928,278.34636 L -55.413237,281.31195 M -38.027686,286.67824 L -38.027686,288.09043 L -51.302213,288.09043 C -51.176691,290.07795 -50.580436,291.59474 -49.513447,292.6408 C -48.436011,293.6764 -46.940144,294.1942 -45.025841,294.1942 C -43.917028,294.1942 -42.844815,294.05821 -41.8092,293.78624 C -40.763154,293.51426 -39.727553,293.1063 -38.702396,292.56234 L -38.702396,295.29256 C -39.738014,295.73191 -40.799766,296.06665 -41.887655,296.29678 C -42.975573,296.52692 -44.079167,296.64198 -45.198441,296.64198 C -48.001896,296.64198 -50.224775,295.82606 -51.867087,294.1942 C -53.498946,292.56235 -54.314874,290.35516 -54.314872,287.57263 C -54.314874,284.69597 -53.540788,282.41555 -51.992614,280.73138 C -50.433987,279.03678 -48.336635,278.18947 -45.700551,278.18945 C -43.336464,278.18947 -41.469245,278.95309 -40.098889,280.48033 C -38.718105,281.99713 -38.027704,284.0631 -38.027686,286.67824 M -40.914817,285.83093 C -40.935754,284.25139 -41.38033,282.99089 -42.248546,282.04942 C -43.10633,281.10798 -44.246537,280.63725 -45.669169,280.63724 C -47.280114,280.63725 -48.571999,281.09229 -49.544829,282.00235 C -50.507212,282.91243 -51.061624,284.19386 -51.208068,285.84662 L -40.914817,285.83093 M -21.72481,281.28056 L -21.72481,271.77186 L -18.837679,271.77186 L -18.837679,296.18695 L -21.72481,296.18695 L -21.72481,293.55087 C -22.33154,294.59693 -23.100395,295.37625 -24.031377,295.88882 C -24.951923,296.39093 -26.060747,296.64198 -27.357854,296.64198 C -29.481367,296.64198 -31.212598,295.79467 -32.551552,294.10005 C -33.880053,292.40544 -34.544302,290.17733 -34.5443,287.41572 C -34.544302,284.65413 -33.880053,282.42602 -32.551552,280.73138 C -31.212598,279.03678 -29.481367,278.18947 -27.357854,278.18945 C -26.060747,278.18947 -24.951923,278.44575 -24.031377,278.95831 C -23.100395,279.46043 -22.33154,280.23452 -21.72481,281.28056 M -31.563023,287.41572 C -31.563028,289.53923 -31.128913,291.2077 -30.260676,292.42112 C -29.381991,293.6241 -28.179021,294.22558 -26.651762,294.22558 C -25.124523,294.22558 -23.921553,293.6241 -23.042848,292.42112 C -22.16417,291.2077 -21.724825,289.53923 -21.72481,287.41572 C -21.724825,285.29222 -22.16417,283.62899 -23.042848,282.426 C -23.921553,281.21258 -25.124523,280.60587 -26.651762,280.60585 C -28.179021,280.60587 -29.381991,281.21258 -30.260676,282.426 C -31.128913,283.62899 -31.563028,285.29222 -31.563023,287.41572 M -12.890817,278.61311 L -10.003686,278.61311 L -10.003686,296.18695 L -12.890817,296.18695 L -12.890817,278.61311 M -12.890817,271.77186 L -10.003686,271.77186 L -10.003686,275.42785 L -12.890817,275.42785 L -12.890817,271.77186 M 7.224956,279.13091 L 7.224956,281.86113 C 6.4090142,281.44272 5.5617048,281.1289 4.6830253,280.91967 C 3.8043224,280.71048 2.8942494,280.60587 1.9528035,280.60585 C 0.51969097,280.60587 -0.55775182,280.82554 -1.2795282,281.26487 C -1.9908553,281.70423 -2.3465161,282.36325 -2.3465114,283.24193 C -2.3465161,283.91142 -2.0902311,284.43968 -1.5776558,284.82671 C -1.0650914,285.20331 -0.03472134,285.5642 1.5134574,285.90939 L 2.501986,286.12906 C 4.552256,286.56842 6.0062807,287.19082 6.8640646,287.99628 C 7.7322813,288.7913 8.1663966,289.90535 8.1664118,291.33845 C 8.1663966,292.97031 7.5178388,294.2622 6.2207365,295.21411 C 4.9340683,296.16603 3.1609949,296.64198 0.90151113,296.64198 C -0.039951645,296.64198 -1.023249,296.54784 -2.0483837,296.35955 C -3.0630678,296.18172 -4.1352803,295.90974 -5.2650244,295.54362 L -5.2650244,292.56234 C -4.198044,293.11676 -3.1467527,293.53518 -2.1111475,293.81762 C -1.075552,294.0896 -0.050412255,294.22558 0.96427485,294.22558 C 2.3241461,294.22558 3.3702071,293.99545 4.1024609,293.53518 C 4.8346925,293.06446 5.2008138,292.40544 5.200826,291.55812 C 5.2008138,290.77358 4.9340683,290.1721 4.4005886,289.75367 C 3.8775467,289.33525 2.7216493,288.93251 0.93289299,288.54546 L -0.071326539,288.3101 C -1.8600977,287.93353 -3.151983,287.35819 -3.9469863,286.5841 C -4.7419957,285.79956 -5.1394989,284.72735 -5.139497,283.36746 C -5.1394989,281.71469 -4.5537047,280.4385 -3.3821128,279.53887 C -2.2105282,278.63927 -0.54729121,278.18947 1.607603,278.18945 C 2.6745766,278.18947 3.6787951,278.26792 4.6202616,278.42481 C 5.5617048,278.58174 6.4299354,278.81711 7.224956,279.13091 M 25.426437,279.28782 L 25.426437,281.98666 C 24.610494,281.53686 23.789336,281.20212 22.962961,280.98244 C 22.14702,280.75232 21.320632,280.63725 20.483794,280.63724 C 18.611334,280.63725 17.157309,281.23351 16.121715,282.426 C 15.086109,283.60806 14.568309,285.2713 14.568313,287.41572 C 14.568309,289.56015 15.086109,291.22862 16.121715,292.42112 C 17.157309,293.60318 18.611334,294.1942 20.483794,294.1942 C 21.320632,294.1942 22.14702,294.08436 22.962961,293.86469 C 23.789336,293.63456 24.610494,293.29459 25.426437,292.84478 L 25.426437,295.51224 C 24.620954,295.88882 23.784106,296.17126 22.915888,296.35955 C 22.058105,296.54784 21.142802,296.64198 20.169975,296.64198 C 17.523431,296.64198 15.420848,295.81037 13.862222,294.14713 C 12.303587,292.48389 11.524271,290.24009 11.524273,287.41572 C 11.524271,284.54952 12.308817,282.29526 13.877912,280.65293 C 15.45746,279.01063 17.617576,278.18947 20.358267,278.18945 C 21.247408,278.18947 22.115638,278.28361 22.962961,278.47189 C 23.810257,278.64974 24.631415,278.92171 25.426437,279.28782 M 37.288775,280.63724 C 35.740595,280.63725 34.516703,281.24397 33.617097,282.45738 C 32.717478,283.66037 32.267672,285.31314 32.267677,287.41572 C 32.267672,289.51831 32.712248,291.17632 33.601406,292.38974 C 34.501012,293.59272 35.730134,294.1942 37.288775,294.1942 C 38.826475,294.1942 40.045136,293.58749 40.944761,292.37405 C 41.84436,291.16062 42.294167,289.50785 42.294181,287.41572 C 42.294167,285.33406 41.84436,283.68652 40.944761,282.47307 C 40.045136,281.2492 38.826475,280.63725 37.288775,280.63724 M 37.288775,278.18945 C 39.799311,278.18947 41.771136,279.0054 43.204255,280.63724 C 44.637343,282.26911 45.353895,284.5286 45.353913,287.41572 C 45.353895,290.29239 44.637343,292.55189 43.204255,294.1942 C 41.771136,295.82606 39.799311,296.64198 37.288775,296.64198 C 34.767758,296.64198 32.790703,295.82606 31.357603,294.1942 C 29.934956,292.55189 29.223635,290.29239 29.223637,287.41572 C 29.223635,284.5286 29.934956,282.26911 31.357603,280.63724 C 32.790703,279.0054 34.767758,278.18947 37.288775,278.18945 M 48.052758,278.61311 L 51.11249,278.61311 L 56.604315,293.36258 L 62.096141,278.61311 L 65.155872,278.61311 L 58.565682,296.18695 L 54.642949,296.18695 L 48.052758,278.61311 M 84.173282,286.67824 L 84.173282,288.09043 L 70.898756,288.09043 C 71.024278,290.07795 71.620533,291.59474 72.687522,292.6408 C 73.764958,293.6764 75.260825,294.1942 77.175128,294.1942 C 78.283941,294.1942 79.356154,294.05821 80.391768,293.78624 C 81.437815,293.51426 82.473415,293.1063 83.498572,292.56234 L 83.498572,295.29256 C 82.462955,295.73191 81.401203,296.06665 80.313314,296.29678 C 79.225396,296.52692 78.121802,296.64198 77.002527,296.64198 C 74.199073,296.64198 71.976194,295.82606 70.333882,294.1942 C 68.702023,292.56235 67.886095,290.35516 67.886097,287.57263 C 67.886095,284.69597 68.66018,282.41555 70.208355,280.73138 C 71.766981,279.03678 73.864334,278.18947 76.500418,278.18945 C 78.864505,278.18947 80.731724,278.95309 82.10208,280.48033 C 83.482864,281.99713 84.173264,284.0631 84.173282,286.67824 M 81.286151,285.83093 C 81.265215,284.25139 80.820639,282.99089 79.952422,282.04942 C 79.094638,281.10798 77.954432,280.63725 76.531799,280.63724 C 74.920855,280.63725 73.62897,281.09229 72.65614,282.00235 C 71.693757,282.91243 71.139345,284.19386 70.992901,285.84662 L 81.286151,285.83093 M 99.095354,281.31195 C 98.771062,281.12367 98.415401,280.98768 98.028371,280.90398 C 97.651777,280.80985 97.233352,280.76278 96.773096,280.76276 C 95.14123,280.76278 93.885957,281.29627 93.007273,282.36324 C 92.139035,283.41977 91.70492,284.94179 91.704926,286.9293 L 91.704926,296.18695 L 88.802104,296.18695 L 88.802104,278.61311 L 91.704926,278.61311 L 91.704926,281.34333 C 92.311635,280.27636 93.101411,279.48658 94.074256,278.974 C 95.047085,278.45098 96.229134,278.18947 97.620406,278.18945 C 97.819146,278.18947 98.038819,278.20516 98.279426,278.23652 C 98.520007,278.25746 98.786753,278.29407 99.079663,278.34636 L 99.095354,281.31195 M 119.69755,297.8188 C 118.88161,299.91092 118.0866,301.27603 117.31253,301.91414 C 116.53843,302.55223 115.50283,302.87128 114.20572,302.87128 L 111.89915,302.87128 L 111.89915,300.45488 L 113.59377,300.45488 C 114.38878,300.45488 115.00595,300.26659 115.4453,299.89001 C 115.88464,299.51342 116.37106,298.62427 116.90456,297.22255 L 117.42236,295.90451 L 110.31437,278.61311 L 113.3741,278.61311 L 118.86593,292.35836 L 124.35775,278.61311 L 127.41748,278.61311 L 119.69755,297.8188 M 138.21285,280.63724 C 136.66467,280.63725 135.44078,281.24397 134.54117,282.45738 C 133.64155,283.66037 133.19174,285.31314 133.19175,287.41572 C 133.19174,289.51831 133.63632,291.17632 134.52548,292.38974 C 135.42508,293.59272 136.65421,294.1942 138.21285,294.1942 C 139.75055,294.1942 140.96921,293.58749 141.86883,292.37405 C 142.76843,291.16062 143.21824,289.50785 143.21825,287.41572 C 143.21824,285.33406 142.76843,283.68652 141.86883,282.47307 C 140.96921,281.2492 139.75055,280.63725 138.21285,280.63724 M 138.21285,278.18945 C 140.72338,278.18947 142.69521,279.0054 144.12833,280.63724 C 145.56142,282.26911 146.27797,284.5286 146.27799,287.41572 C 146.27797,290.29239 145.56142,292.55189 144.12833,294.1942 C 142.69521,295.82606 140.72338,296.64198 138.21285,296.64198 C 135.69183,296.64198 133.71478,295.82606 132.28168,294.1942 C 130.85903,292.55189 130.14771,290.29239 130.14771,287.41572 C 130.14771,284.5286 130.85903,282.26911 132.28168,280.63724 C 133.71478,279.0054 135.69183,278.18947 138.21285,278.18945 M 150.74989,289.25156 L 150.74989,278.61311 L 153.63702,278.61311 L 153.63702,289.14172 C 153.63702,290.80496 153.96129,292.05501 154.60986,292.89185 C 155.25841,293.71824 156.23125,294.13144 157.52837,294.13144 C 159.08699,294.13144 160.31612,293.63456 161.21574,292.6408 C 162.1258,291.64704 162.58084,290.29239 162.58085,288.57685 L 162.58085,278.61311 L 165.46798,278.61311 L 165.46798,296.18695 L 162.58085,296.18695 L 162.58085,293.48811 C 161.87998,294.55509 161.06405,295.3501 160.13307,295.87313 C 159.21252,296.3857 158.14031,296.64198 156.91643,296.64198 C 154.89752,296.64198 153.36504,296.01435 152.31898,294.75907 C 151.27292,293.5038 150.74989,291.66796 150.74989,289.25156 M 181.62964,281.31195 C 181.30535,281.12367 180.94969,280.98768 180.56266,280.90398 C 180.18606,280.80985 179.76764,280.76278 179.30738,280.76276 C 177.67552,280.76278 176.42024,281.29627 175.54156,282.36324 C 174.67332,283.41977 174.23921,284.94179 174.23921,286.9293 L 174.23921,296.18695 L 171.33639,296.18695 L 171.33639,278.61311 L 174.23921,278.61311 L 174.23921,281.34333 C 174.84592,280.27636 175.6357,279.48658 176.60854,278.974 C 177.58137,278.45098 178.76342,278.18947 180.15469,278.18945 C 180.35343,278.18947 180.57311,278.20516 180.81371,278.23652 C 181.05429,278.25746 181.32104,278.29407 181.61395,278.34636 L 181.62964,281.31195 M 208.60235,281.98666 C 209.32412,280.68955 210.18712,279.73241 211.19135,279.11522 C 212.19555,278.49806 213.3776,278.18947 214.7375,278.18945 C 216.56809,278.18947 217.98027,278.8328 218.97406,280.11943 C 219.96779,281.39565 220.46467,283.21579 220.46469,285.57988 L 220.46469,296.18695 L 217.56187,296.18695 L 217.56187,285.67402 C 217.56185,283.98988 217.26372,282.73983 216.66749,281.92389 C 216.07121,281.10798 215.16114,280.70001 213.93727,280.7 C 212.44138,280.70001 211.25933,281.19689 210.39112,282.19064 C 209.52287,283.18441 209.08875,284.53906 209.08877,286.25459 L 209.08877,296.18695 L 206.18595,296.18695 L 206.18595,285.67402 C 206.18593,283.97942 205.88781,282.72937 205.29156,281.92389 C 204.6953,281.10798 203.77476,280.70001 202.52996,280.7 C 201.055,280.70001 199.88342,281.20212 199.01519,282.20633 C 198.14695,283.2001 197.71284,284.54952 197.71285,286.25459 L 197.71285,296.18695 L 194.81002,296.18695 L 194.81002,278.61311 L 197.71285,278.61311 L 197.71285,281.34333 C 198.37186,280.2659 199.16163,279.47089 200.08218,278.95831 C 201.0027,278.44575 202.09583,278.18947 203.36158,278.18945 C 204.63776,278.18947 205.72044,278.51375 206.6096,279.16229 C 207.5092,279.81086 208.17345,280.75232 208.60235,281.98666 M 225.94082,289.25156 L 225.94082,278.61311 L 228.82795,278.61311 L 228.82795,289.14172 C 228.82795,290.80496 229.15223,292.05501 229.80079,292.89185 C 230.44934,293.71824 231.42218,294.13144 232.71931,294.13144 C 234.27793,294.13144 235.50705,293.63456 236.40667,292.6408 C 237.31673,291.64704 237.77177,290.29239 237.77179,288.57685 L 237.77179,278.61311 L 240.65892,278.61311 L 240.65892,296.18695 L 237.77179,296.18695 L 237.77179,293.48811 C 237.07091,294.55509 236.25498,295.3501 235.324,295.87313 C 234.40345,296.3857 233.33124,296.64198 232.10736,296.64198 C 230.08845,296.64198 228.55597,296.01435 227.50992,294.75907 C 226.46385,293.5038 225.94082,291.66796 225.94082,289.25156 M 257.8405,279.13091 L 257.8405,281.86113 C 257.02456,281.44272 256.17725,281.1289 255.29857,280.91967 C 254.41987,280.71048 253.50979,280.60587 252.56835,280.60585 C 251.13523,280.60587 250.05779,280.82554 249.33602,281.26487 C 248.62469,281.70423 248.26903,282.36325 248.26903,283.24193 C 248.26903,283.91142 248.52531,284.43968 249.03789,284.82671 C 249.55045,285.20331 250.58082,285.5642 252.129,285.90939 L 253.11753,286.12906 C 255.1678,286.56842 256.62182,287.19082 257.47961,287.99628 C 258.34782,288.7913 258.78194,289.90535 258.78196,291.33845 C 258.78194,292.97031 258.13338,294.2622 256.83628,295.21411 C 255.54961,296.16603 253.77654,296.64198 251.51705,296.64198 C 250.57559,296.64198 249.59229,296.54784 248.56716,296.35955 C 247.55248,296.18172 246.48026,295.90974 245.35052,295.54362 L 245.35052,292.56234 C 246.4175,293.11676 247.46879,293.53518 248.5044,293.81762 C 249.53999,294.0896 250.56513,294.22558 251.57982,294.22558 C 252.93969,294.22558 253.98575,293.99545 254.718,293.53518 C 255.45024,293.06446 255.81636,292.40544 255.81637,291.55812 C 255.81636,290.77358 255.54961,290.1721 255.01613,289.75367 C 254.49309,289.33525 253.33719,288.93251 251.54844,288.54546 L 250.54422,288.3101 C 248.75545,287.93353 247.46356,287.35819 246.66856,286.5841 C 245.87355,285.79956 245.47604,284.72735 245.47605,283.36746 C 245.47604,281.71469 246.06184,280.4385 247.23343,279.53887 C 248.40502,278.63927 250.06825,278.18947 252.22315,278.18945 C 253.29012,278.18947 254.29434,278.26792 255.2358,278.42481 C 256.17725,278.58174 257.04548,278.81711 257.8405,279.13091 M 263.39508,278.61311 L 266.28221,278.61311 L 266.28221,296.18695 L 263.39508,296.18695 L 263.39508,278.61311 M 263.39508,271.77186 L 266.28221,271.77186 L 266.28221,275.42785 L 263.39508,275.42785 L 263.39508,271.77186 M 284.95441,279.28782 L 284.95441,281.98666 C 284.13847,281.53686 283.31731,281.20212 282.49094,280.98244 C 281.675,280.75232 280.84861,280.63725 280.01177,280.63724 C 278.13931,280.63725 276.68529,281.23351 275.64969,282.426 C 274.61409,283.60806 274.09629,285.2713 274.09629,287.41572 C 274.09629,289.56015 274.61409,291.22862 275.64969,292.42112 C 276.68529,293.60318 278.13931,294.1942 280.01177,294.1942 C 280.84861,294.1942 281.675,294.08436 282.49094,293.86469 C 283.31731,293.63456 284.13847,293.29459 284.95441,292.84478 L 284.95441,295.51224 C 284.14893,295.88882 283.31208,296.17126 282.44387,296.35955 C 281.58608,296.54784 280.67078,296.64198 279.69795,296.64198 C 277.05141,296.64198 274.94883,295.81037 273.3902,294.14713 C 271.83156,292.48389 271.05225,290.24009 271.05225,287.41572 C 271.05225,284.54952 271.83679,282.29526 273.40589,280.65293 C 274.98544,279.01063 277.14555,278.18947 279.88624,278.18945 C 280.77538,278.18947 281.64362,278.28361 282.49094,278.47189 C 283.33823,278.64974 284.15939,278.92171 284.95441,279.28782" + style="font-size:32.13502502px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:100%;writing-mode:lr-tb;text-anchor:start;fill:#0057ae;fill-opacity:1;font-family:DejaVu Sans" + transform="matrix(1.223374,0,0,1.223374,59.51252,-116.39885)" /><path + inkscape:export-ydpi="42.189999" + inkscape:export-xdpi="42.189999" + inkscape:export-filename="/home/knome/Work/Amarok logo/oxygen_logo.png" + style="fill:url(#radialGradient5154);fill-opacity:1;stroke:#0057ae;stroke-width:17.74128838;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 607.62801,152.20455 C 607.62801,208.15234 562.2211,253.55926 506.2733,253.55926 C 450.3255,253.55926 404.91859,208.15234 404.91859,152.20455 C 404.91859,96.256742 450.3255,50.849834 506.2733,50.849834 C 562.2211,50.849834 607.62801,96.256742 607.62801,152.20455 z " + id="path3013" /><path + inkscape:export-ydpi="42.189999" + inkscape:export-xdpi="42.189999" + inkscape:export-filename="/home/knome/Work/Amarok logo/oxygen_logo.png" + transform="matrix(0.2762093,0,0,0.2762093,391.19099,37.693155)" + style="opacity:0.86000001;fill:#eeeeee" + mask="url(#XMLID_23_)" + d="M 699.57098,390.89499 C 699.57098,553.04278 570.4108,684.64099 411.267,684.64099 C 252.1232,684.64099 122.96301,553.04278 122.96301,390.89499 C 122.96301,228.7472 252.1232,97.148987 411.267,97.148987 C 570.4108,97.148987 699.57098,228.7472 699.57098,390.89499 z " + id="path3015" /><path + inkscape:export-ydpi="42.189999" + inkscape:export-xdpi="42.189999" + inkscape:export-filename="/home/knome/Work/Amarok logo/oxygen_logo.png" + style="opacity:0.22000002;fill:#323232" + d="M 562.11729,102.05295 C 557.89957,102.05295 549.70969,110.84911 546.64101,112.2379 C 543.57094,113.62667 519.27116,129.36535 516.96868,132.14291 C 514.6673,134.9202 511.59779,136.07779 508.27278,135.84633 C 504.94805,135.61486 494.43608,135.51985 490.85558,137.37155 C 487.27425,139.22326 470.92874,154.36284 466.83587,156.21483 C 462.74355,158.06598 457.40415,159.01504 454.84645,159.24622 C 452.28875,159.47796 443.81521,168.01918 441.513,169.63998 C 439.21107,171.25939 422.38109,177.74865 419.82367,179.13688 C 417.2657,180.52594 434.86299,202.50639 437.9325,202.96932 C 441.00146,203.43252 434.9174,205.70186 429.01205,205.70186 C 428.36185,205.70186 437.45162,229.53816 468.44451,244.92274 C 495.23819,258.22222 516.82504,250.14586 527.38287,246.92029 C 534.84825,244.63963 536.20196,243.62263 536.45855,239.91921 C 536.7146,236.21607 537.17698,233.28964 537.68879,229.12357 C 538.20061,224.95806 539.98989,190.00709 546.64128,180.74911 C 553.29074,171.49085 572.29919,160.37675 578.9492,159.21998 C 585.59977,158.06267 584.3212,155.74831 584.3212,153.66514 C 584.3212,151.58197 583.29784,146.9527 581.50745,146.25831 C 579.71679,145.5642 572.73035,149.73385 570.42843,150.19678 C 568.12622,150.65971 557.12785,151.3541 555.59295,149.03974 C 554.05833,146.72483 568.0011,123.11584 569.79149,120.33856 C 571.58132,117.561 572.34891,113.85759 569.02445,109.92298 C 565.69806,105.98755 563.39586,102.05295 562.11729,102.05295 z " + id="path3017" /><path + inkscape:export-ydpi="42.189999" + inkscape:export-xdpi="42.189999" + inkscape:export-filename="/home/knome/Work/Amarok logo/oxygen_logo.png" + style="fill:url(#radialGradient5149);fill-opacity:1" + d="M 553.14187,89.807766 C 549.05038,89.807766 541.10688,99.236444 538.12962,100.72494 C 535.15236,102.2137 511.58204,119.08429 509.34917,122.06183 C 507.11629,125.03881 504.13876,126.27954 500.91374,126.03151 C 497.68872,125.78375 487.49273,125.68155 484.0194,127.66639 C 480.54551,129.65151 464.69082,145.87991 460.72115,147.86502 C 456.75174,149.84931 451.57282,150.86604 449.09163,151.11435 C 446.611,151.36211 438.39184,160.51761 436.15896,162.25469 C 433.92609,163.99177 416.28184,171.62675 413.80093,173.11469 C 411.31974,174.60346 431.9012,195.45256 434.87846,195.94835 C 437.85572,196.44526 432.82788,207.74166 427.09958,207.74166 C 426.46871,207.74166 432.21856,226.46064 462.2809,242.95171 C 488.27026,257.20825 515.84506,250.89604 526.08551,247.43901 C 533.32717,244.99345 528.24464,243.64196 528.49323,239.67256 C 528.74072,235.7026 528.94925,230.4828 529.4456,226.01732 C 529.94223,221.55157 531.6782,184.08738 538.12934,174.16346 C 544.57966,164.24008 563.01663,152.32663 569.46749,151.08617 C 575.91864,149.84571 574.67763,147.36453 574.67763,145.13193 C 574.67763,142.89933 573.68548,137.93696 571.9484,137.19257 C 570.21187,136.44846 563.43536,140.91808 561.20248,141.41415 C 558.96961,141.91023 548.30102,142.65488 546.81198,140.17397 C 545.32349,137.69251 558.84752,112.38594 560.58433,109.40868 C 562.32141,106.43114 563.06524,102.46119 559.83967,98.244025 C 556.61548,94.025481 554.38232,89.807766 553.14187,89.807766 z " + id="path3019" /><path + inkscape:export-ydpi="42.189999" + inkscape:export-xdpi="42.189999" + inkscape:export-filename="/home/knome/Work/Amarok logo/oxygen_logo.png" + style="fill:#ffffff" + d="M 503.65097,136.57607 C 503.65097,136.57607 493.82179,135.06355 488.15038,140.73523 C 482.4787,146.40664 477.56273,158.88328 477.94196,162.66431 C 478.31954,166.44534 489.66263,149.80925 494.57722,148.67542 C 499.49291,147.54075 504.78591,139.22215 503.65097,136.57607 z " + id="path3021" /><path + inkscape:export-ydpi="42.189999" + inkscape:export-xdpi="42.189999" + inkscape:export-filename="/home/knome/Work/Amarok logo/oxygen_logo.png" + style="opacity:0.18000004;fill:url(#linearGradient5145)" + d="M 528.47058,188.76443 C 528.47058,188.76443 519.67498,218.70662 519.45263,219.16927 C 518.1956,221.78165 515.8771,229.35172 516.67534,231.79645 C 517.61528,234.67648 521.13833,238.86382 522.7826,240.95748 C 524.42688,243.05198 525.13149,244.09881 524.66165,244.88463 C 524.1921,245.66989 518.08484,245.66989 518.08484,245.66989 C 518.08484,245.66989 513.38735,238.60225 512.44713,235.98461 C 511.50802,233.36753 511.03791,227.87069 511.97785,226.03887 C 512.91724,224.20622 528.47058,188.76443 528.47058,188.76443 z " + id="path3023" /><path + inkscape:export-ydpi="42.189999" + inkscape:export-xdpi="42.189999" + inkscape:export-filename="/home/knome/Work/Amarok logo/oxygen_logo.png" + style="opacity:0.2;fill:url(#linearGradient5142)" + d="M 466.47597,194.34247 L 450.40749,208.25265 L 470.55309,196.26103 L 466.47597,194.34247 z " + id="path3025" /><path + inkscape:export-ydpi="42.189999" + inkscape:export-xdpi="42.189999" + inkscape:export-filename="/home/knome/Work/Amarok logo/oxygen_logo.png" + style="opacity:0.2;fill:url(#linearGradient5139);fill-opacity:1" + d="M 482.30496,189.54638 L 454.96412,225.04092 L 486.38181,191.94415 L 482.30496,189.54638 z " + id="path3027" /><path + inkscape:export-ydpi="42.189999" + inkscape:export-xdpi="42.189999" + inkscape:export-filename="/home/knome/Work/Amarok logo/oxygen_logo.png" + style="opacity:0.20132015;fill:url(#linearGradient5136);fill-opacity:1" + d="M 484.46354,201.77748 L 463.59814,234.87397 L 489.25991,203.93578 L 484.46354,201.77748 z " + id="path3029" /><path + inkscape:export-ydpi="42.189999" + inkscape:export-xdpi="42.189999" + inkscape:export-filename="/home/knome/Work/Amarok logo/oxygen_logo.png" + style="opacity:0.2;fill:url(#linearGradient5133);fill-opacity:1" + d="M 496.69464,200.33815 L 479.18684,241.3497 L 502.45056,201.53773 L 496.69464,200.33815 z " + id="path3031" /><path + inkscape:export-ydpi="42.189999" + inkscape:export-xdpi="42.189999" + inkscape:export-filename="/home/knome/Work/Amarok logo/oxygen_logo.png" + style="opacity:0.2;fill:url(#linearGradient5130);fill-opacity:1" + d="M 503.64987,211.13075 L 502.21054,248.5444 L 508.44596,211.61053 L 503.64987,211.13075 z " + id="path3033" /><path + inkscape:export-ydpi="42.189999" + inkscape:export-xdpi="42.189999" + inkscape:export-filename="/home/knome/Work/Amarok logo/oxygen_logo.png" + style="opacity:0.57999998;fill:url(#linearGradient5127);fill-opacity:1" + d="M 504.75152,226.37063 C 487.66872,218.77051 472.26108,208.34067 458.95034,195.36657 C 457.45601,193.91003 457.12215,193.52136 457.12215,193.23822 C 457.12215,192.94097 457.54836,192.5176 460.12608,190.25432 C 465.37525,185.64546 466.53012,184.41533 467.42476,182.47999 C 468.33817,180.50405 470.22561,178.17477 473.90701,174.48029 C 482.03457,166.32382 496.78839,154.1347 503.7478,149.82679 C 504.9573,149.0781 505.65242,148.53693 506.60915,147.59912 C 509.6447,144.62359 514.3464,138.61104 524.14198,125.17801 C 534.56912,110.87891 538.07927,106.25712 541.54978,102.25728 C 543.6469,99.840302 545.18902,98.405539 545.78707,98.314991 C 546.04121,98.276513 546.46327,98.208329 546.72499,98.163469 C 546.98671,98.118604 547.32116,98.139281 547.46823,98.209418 C 547.6153,98.279551 547.83047,98.367066 547.94639,98.403902 C 548.16857,98.474501 552.40053,103.26294 553.95918,105.20732 C 555.82616,107.53636 555.99429,108.18489 555.06007,109.45379 C 554.7114,109.92737 554.37557,110.60842 554.1171,111.36612 C 553.67312,112.66761 551.38577,117.29117 547.8663,124.00122 C 545.09922,129.27681 541.35391,136.73457 540.18341,139.2996 C 537.71251,144.71434 536.50731,148.4444 536.82268,149.70097 C 536.92593,150.11234 537.55004,150.40662 539.22642,150.83442 C 543.14777,151.8351 546.77302,151.68358 550.15116,150.37784 C 550.66214,150.18034 551.86062,149.5683 552.81444,149.01778 C 555.72634,147.33708 557.92134,146.39751 560.66217,145.65857 C 561.97151,145.30557 562.28275,145.26848 564.02724,145.25756 C 565.85748,145.2461 565.97901,145.26068 566.56565,145.56205 C 567.0982,145.83563 567.1958,145.94488 567.28496,146.36723 C 567.48586,147.31885 567.5211,147.93071 567.38297,148.06885 C 567.30747,148.14434 567.24571,148.32977 567.24571,148.48091 C 567.24571,149.10089 565.75469,149.60186 561.59798,150.37849 C 557.23774,151.19314 557.02723,151.28091 553.99288,153.54938 C 547.20053,158.62731 536.63232,167.82619 532.88331,171.92374 C 531.18252,173.78265 528.57528,177.38026 525.06105,182.71727 C 517.17638,194.69164 511.71417,205.03252 510.25496,210.74765 C 509.96501,211.88326 509.14933,217.01271 508.40886,222.35681 C 507.72543,227.28934 507.69494,227.43782 507.36771,227.42667 C 507.22328,227.42176 506.046,226.94653 504.75152,226.37063 z " + id="path3035" /><path + inkscape:export-ydpi="42.189999" + inkscape:export-xdpi="42.189999" + inkscape:export-filename="/home/knome/Work/Amarok logo/oxygen_logo.png" + style="fill:url(#linearGradient5124);fill-opacity:1" + d="M 458.55053,159.71501 C 458.47471,159.44334 458.49004,157.05631 458.57477,155.9391 C 458.63539,155.13978 458.68605,154.87564 458.89917,154.2476 C 459.55794,152.30629 460.52112,151.22561 461.91437,150.86458 C 462.43405,150.72992 462.71607,150.59187 463.46757,150.10428 C 465.0112,149.10273 466.9814,147.43992 470.13451,144.47751 C 470.99009,143.67368 474.46106,140.25971 477.84778,136.89093 C 481.23451,133.52215 484.15769,130.65563 484.34376,130.5209 C 484.52982,130.38617 484.80385,130.2437 484.9527,130.20432 C 486.04015,129.91655 488.66999,129.77705 495.33856,129.65338 C 501.60455,129.53719 504.27299,129.41647 505.09594,129.21197 C 505.94828,129.00016 507.89319,127.8484 511.81383,125.23367 C 514.64865,123.3431 520.68185,119.17152 524.17803,116.68462 C 525.1162,116.01727 525.49787,115.78273 525.64565,115.78273 C 525.75549,115.78273 525.86418,115.75228 525.88717,115.71506 C 525.91017,115.67785 525.97556,115.6474 526.0325,115.6474 C 526.11763,115.6474 526.12953,115.71649 526.09958,116.03645 C 526.02272,116.85729 525.97421,117.026 525.71615,117.37019 C 525.01305,118.30794 523.16108,119.82148 520.97783,121.24261 C 520.41731,121.60746 519.32878,122.39421 518.55885,122.99094 C 510.79402,129.00908 505.51895,132.57132 503.23821,133.33694 C 502.786,133.48874 502.61099,133.50972 501.79687,133.50972 C 500.22664,133.50972 498.42309,133.85006 495.34562,134.72713 C 490.80981,136.01983 486.63749,137.71525 484.44525,139.15651 C 482.54162,140.40803 481.01203,142.8102 479.47523,146.96174 C 478.8706,148.59513 478.15755,151.00949 477.29858,154.33192 C 477.08455,155.15977 476.88879,155.8705 476.86357,155.91131 C 476.83835,155.95211 472.77179,156.85132 467.82677,157.90955 C 462.88175,158.96778 458.78356,159.84965 458.71968,159.86926 C 458.63556,159.89509 458.58891,159.85254 458.55053,159.71501 z " + id="path3037" /><path + inkscape:export-ydpi="42.189999" + inkscape:export-xdpi="42.189999" + inkscape:export-filename="/home/knome/Work/Amarok logo/oxygen_logo.png" + style="opacity:0.7;fill:url(#linearGradient5121);fill-opacity:1" + d="M 435.26135,189.83164 C 435.16743,189.81073 434.982,189.79251 434.8493,189.79116 C 434.26798,189.78523 432.61789,188.95172 430.84258,187.76724 C 428.9169,186.48243 427.36914,185.25421 425.61448,183.61852 C 425.26124,183.28923 424.6458,182.72248 424.24682,182.35906 C 423.17537,181.3831 421.5223,179.72231 421.03636,179.1336 C 420.14808,178.05748 419.67259,177.26546 419.60721,176.75309 C 419.50781,175.97409 419.51127,175.79044 419.63802,175.12035 C 419.66675,174.96842 420.04237,174.64165 420.55347,174.32394 C 421.50997,173.72936 423.54487,172.34925 430.7145,167.43253 C 441.62316,159.95169 446.21899,156.91024 448.6423,155.56819 C 449.25626,155.22818 449.4254,155.19025 450.33213,155.18928 C 451.20103,155.18835 451.93222,155.26637 452.45177,155.41544 C 453.49059,155.7135 455.0964,156.34115 456.51229,157.00252 C 457.21907,157.33268 457.26981,157.36408 457.26981,157.47149 C 457.26981,157.57517 457.24543,157.59346 457.02433,157.65557 C 456.51603,157.79838 455.15926,158.24509 454.04646,158.63602 C 447.28556,161.01113 440.52061,164.28686 434.47153,168.11459 C 433.03786,169.02178 432.87141,169.14373 432.33595,169.67919 C 430.81494,171.20019 429.44563,173.57681 429.06387,175.35832 C 429.01057,175.60708 428.986,175.90249 428.98577,176.29758 C 428.98539,176.92757 429.06074,177.25714 429.29755,177.66121 C 430.23362,179.2585 434.98574,180.76681 441.13215,181.41746 C 442.92537,181.60729 443.75128,181.65119 445.57182,181.65343 L 447.34359,181.6556 L 447.81322,181.49339 C 449.06889,181.05965 450.7871,180.07808 454.51609,177.66418 C 457.74735,175.57247 458.7447,174.96312 459.89546,174.37753 C 460.97295,173.82923 461.51736,173.66632 461.98128,173.75335 C 462.1579,173.78648 462.1904,173.88573 462.19234,174.39772 C 462.19514,175.13809 462.11429,175.41995 461.73921,175.97738 C 460.65551,177.5879 457.98854,179.77667 454.23694,182.13447 C 451.0841,184.11596 447.49325,185.95326 445.58087,186.56345 C 445.39978,186.62123 444.91406,186.74572 444.50149,186.84008 C 442.29674,187.34434 440.0946,188.12955 437.18256,189.44978 C 436.77029,189.63669 436.40636,189.77311 436.26465,189.79388 C 435.84681,189.8551 435.43618,189.87055 435.26135,189.83164 z " + id="path3039" /><path + inkscape:export-ydpi="42.189999" + inkscape:export-xdpi="42.189999" + inkscape:export-filename="/home/knome/Work/Amarok logo/oxygen_logo.png" + style="fill:url(#linearGradient5118);fill-opacity:1" + d="M 442.84682,177.43288 C 442.80531,177.42744 442.26749,177.41246 441.65168,177.3996 C 440.54285,177.37644 439.84862,177.3287 438.85881,177.20752 C 436.56304,176.92647 435.03281,176.47405 434.49616,175.91769 C 434.38234,175.79968 434.34478,175.65965 434.34658,175.36004 C 434.34893,174.96989 434.38482,174.81772 434.55164,174.49052 C 435.25884,173.1034 437.29324,170.90685 439.68912,168.94361 C 440.2148,168.51286 441.08661,167.85853 441.53911,167.55513 L 441.86554,167.33625 L 449.27545,166.43992 L 456.68537,165.5436 L 457.17601,165.5716 C 457.63218,165.59765 458.02257,165.62651 459.01649,165.70768 C 459.47491,165.74512 459.51049,165.76172 459.46712,165.9179 C 459.45263,165.97006 459.44072,166.06016 459.44064,166.11812 C 459.44044,166.26677 459.20703,166.81295 458.88231,167.42464 C 457.13039,170.72476 453.59204,174.88147 451.23801,176.40486 C 450.56328,176.8415 450.08974,177.04806 449.48139,177.17109 C 448.86083,177.29659 448.06828,177.37622 447.18709,177.40159 C 446.32121,177.42653 442.98191,177.4506 442.84682,177.43288 z " + id="path3041" /></g></svg> \ No newline at end of file diff --git a/amarok/src/images/amarok_rocks.jpg b/amarok/src/images/amarok_rocks.jpg new file mode 100644 index 00000000..e8b7db9f Binary files /dev/null and b/amarok/src/images/amarok_rocks.jpg differ diff --git a/amarok/src/images/amarok_rocks.xcf b/amarok/src/images/amarok_rocks.xcf new file mode 100644 index 00000000..a370deb5 Binary files /dev/null and b/amarok/src/images/amarok_rocks.xcf differ diff --git a/amarok/src/images/b_next.png b/amarok/src/images/b_next.png new file mode 100644 index 00000000..9a92357e Binary files /dev/null and b/amarok/src/images/b_next.png differ diff --git a/amarok/src/images/b_pause.png b/amarok/src/images/b_pause.png new file mode 100644 index 00000000..8a991451 Binary files /dev/null and b/amarok/src/images/b_pause.png differ diff --git a/amarok/src/images/b_play.png b/amarok/src/images/b_play.png new file mode 100644 index 00000000..3c901b57 Binary files /dev/null and b/amarok/src/images/b_play.png differ diff --git a/amarok/src/images/b_prev.png b/amarok/src/images/b_prev.png new file mode 100644 index 00000000..4495c4ad Binary files /dev/null and b/amarok/src/images/b_prev.png differ diff --git a/amarok/src/images/b_stop.png b/amarok/src/images/b_stop.png new file mode 100644 index 00000000..abaa9edb Binary files /dev/null and b/amarok/src/images/b_stop.png differ diff --git a/amarok/src/images/back_stars_grey.png b/amarok/src/images/back_stars_grey.png new file mode 100644 index 00000000..0dba8835 Binary files /dev/null and b/amarok/src/images/back_stars_grey.png differ diff --git a/amarok/src/images/currenttrack_bar_left.png b/amarok/src/images/currenttrack_bar_left.png new file mode 100644 index 00000000..e0827d7e Binary files /dev/null and b/amarok/src/images/currenttrack_bar_left.png differ diff --git a/amarok/src/images/currenttrack_bar_mid.png b/amarok/src/images/currenttrack_bar_mid.png new file mode 100644 index 00000000..ef843770 Binary files /dev/null and b/amarok/src/images/currenttrack_bar_mid.png differ diff --git a/amarok/src/images/currenttrack_bar_right.png b/amarok/src/images/currenttrack_bar_right.png new file mode 100644 index 00000000..5dfd7dcc Binary files /dev/null and b/amarok/src/images/currenttrack_bar_right.png differ diff --git a/amarok/src/images/currenttrack_pause.png b/amarok/src/images/currenttrack_pause.png new file mode 100644 index 00000000..f4897310 Binary files /dev/null and b/amarok/src/images/currenttrack_pause.png differ diff --git a/amarok/src/images/currenttrack_play.png b/amarok/src/images/currenttrack_play.png new file mode 100644 index 00000000..ba4c061d Binary files /dev/null and b/amarok/src/images/currenttrack_play.png differ diff --git a/amarok/src/images/currenttrack_repeat.png b/amarok/src/images/currenttrack_repeat.png new file mode 100644 index 00000000..3b1ee59a Binary files /dev/null and b/amarok/src/images/currenttrack_repeat.png differ diff --git a/amarok/src/images/currenttrack_repeat_small.png b/amarok/src/images/currenttrack_repeat_small.png new file mode 100644 index 00000000..a2f629ac Binary files /dev/null and b/amarok/src/images/currenttrack_repeat_small.png differ diff --git a/amarok/src/images/currenttrack_stop.png b/amarok/src/images/currenttrack_stop.png new file mode 100644 index 00000000..3c2ddbd7 Binary files /dev/null and b/amarok/src/images/currenttrack_stop.png differ diff --git a/amarok/src/images/currenttrack_stop_small.png b/amarok/src/images/currenttrack_stop_small.png new file mode 100644 index 00000000..c087d4f4 Binary files /dev/null and b/amarok/src/images/currenttrack_stop_small.png differ diff --git a/amarok/src/images/eq_active2.png b/amarok/src/images/eq_active2.png new file mode 100644 index 00000000..2d76d49d Binary files /dev/null and b/amarok/src/images/eq_active2.png differ diff --git a/amarok/src/images/eq_inactive2.png b/amarok/src/images/eq_inactive2.png new file mode 100644 index 00000000..655eafa0 Binary files /dev/null and b/amarok/src/images/eq_inactive2.png differ diff --git a/amarok/src/images/icons/Makefile.am b/amarok/src/images/icons/Makefile.am new file mode 100644 index 00000000..31a0d20f --- /dev/null +++ b/amarok/src/images/icons/Makefile.am @@ -0,0 +1,2 @@ +amarokicondir = $(kde_datadir)/amarok/icons +amarokicon_ICON = AUTO diff --git a/amarok/src/images/icons/cr16-action-covermanager.png b/amarok/src/images/icons/cr16-action-covermanager.png new file mode 100644 index 00000000..55803463 Binary files /dev/null and b/amarok/src/images/icons/cr16-action-covermanager.png differ diff --git a/amarok/src/images/icons/cr16-action-dynamic.png b/amarok/src/images/icons/cr16-action-dynamic.png new file mode 100644 index 00000000..48a9a4e1 Binary files /dev/null and b/amarok/src/images/icons/cr16-action-dynamic.png differ diff --git a/amarok/src/images/icons/cr16-action-equalizer.png b/amarok/src/images/icons/cr16-action-equalizer.png new file mode 100644 index 00000000..875be4f6 Binary files /dev/null and b/amarok/src/images/icons/cr16-action-equalizer.png differ diff --git a/amarok/src/images/icons/cr16-action-mini_dock.png b/amarok/src/images/icons/cr16-action-mini_dock.png new file mode 100644 index 00000000..eb78dcf4 Binary files /dev/null and b/amarok/src/images/icons/cr16-action-mini_dock.png differ diff --git a/amarok/src/images/icons/cr16-action-player_playlist_2.png b/amarok/src/images/icons/cr16-action-player_playlist_2.png new file mode 100644 index 00000000..d36585a8 Binary files /dev/null and b/amarok/src/images/icons/cr16-action-player_playlist_2.png differ diff --git a/amarok/src/images/icons/cr16-action-podcast.png b/amarok/src/images/icons/cr16-action-podcast.png new file mode 100644 index 00000000..209991cf Binary files /dev/null and b/amarok/src/images/icons/cr16-action-podcast.png differ diff --git a/amarok/src/images/icons/cr16-action-podcast_new.png b/amarok/src/images/icons/cr16-action-podcast_new.png new file mode 100644 index 00000000..02c44e55 Binary files /dev/null and b/amarok/src/images/icons/cr16-action-podcast_new.png differ diff --git a/amarok/src/images/icons/cr16-action-random.png b/amarok/src/images/icons/cr16-action-random.png new file mode 100644 index 00000000..23dafa17 Binary files /dev/null and b/amarok/src/images/icons/cr16-action-random.png differ diff --git a/amarok/src/images/icons/cr16-action-repeat_playlist.png b/amarok/src/images/icons/cr16-action-repeat_playlist.png new file mode 100644 index 00000000..cd21d6cc Binary files /dev/null and b/amarok/src/images/icons/cr16-action-repeat_playlist.png differ diff --git a/amarok/src/images/icons/cr16-action-repeat_track.png b/amarok/src/images/icons/cr16-action-repeat_track.png new file mode 100644 index 00000000..c17a576b Binary files /dev/null and b/amarok/src/images/icons/cr16-action-repeat_track.png differ diff --git a/amarok/src/images/icons/cr16-action-visualizations.png b/amarok/src/images/icons/cr16-action-visualizations.png new file mode 100644 index 00000000..a9b77dc0 Binary files /dev/null and b/amarok/src/images/icons/cr16-action-visualizations.png differ diff --git a/amarok/src/images/icons/cr16-action-wiki.png b/amarok/src/images/icons/cr16-action-wiki.png new file mode 100644 index 00000000..a2f4a249 Binary files /dev/null and b/amarok/src/images/icons/cr16-action-wiki.png differ diff --git a/amarok/src/images/icons/cr22-action-amarok_podcast.png b/amarok/src/images/icons/cr22-action-amarok_podcast.png new file mode 100644 index 00000000..25b62a67 Binary files /dev/null and b/amarok/src/images/icons/cr22-action-amarok_podcast.png differ diff --git a/amarok/src/images/icons/cr22-action-amarok_podcast_new.png b/amarok/src/images/icons/cr22-action-amarok_podcast_new.png new file mode 100644 index 00000000..085055b2 Binary files /dev/null and b/amarok/src/images/icons/cr22-action-amarok_podcast_new.png differ diff --git a/amarok/src/images/icons/cr22-action-babelfish.png b/amarok/src/images/icons/cr22-action-babelfish.png new file mode 100644 index 00000000..0224fa2c Binary files /dev/null and b/amarok/src/images/icons/cr22-action-babelfish.png differ diff --git a/amarok/src/images/icons/cr22-action-dynamic.png b/amarok/src/images/icons/cr22-action-dynamic.png new file mode 100644 index 00000000..c634489a Binary files /dev/null and b/amarok/src/images/icons/cr22-action-dynamic.png differ diff --git a/amarok/src/images/icons/cr22-action-player_playlist_2.png b/amarok/src/images/icons/cr22-action-player_playlist_2.png new file mode 100644 index 00000000..7ca222ab Binary files /dev/null and b/amarok/src/images/icons/cr22-action-player_playlist_2.png differ diff --git a/amarok/src/images/icons/cr22-action-random.png b/amarok/src/images/icons/cr22-action-random.png new file mode 100644 index 00000000..53c55748 Binary files /dev/null and b/amarok/src/images/icons/cr22-action-random.png differ diff --git a/amarok/src/images/icons/cr22-action-repeat_playlist.png b/amarok/src/images/icons/cr22-action-repeat_playlist.png new file mode 100644 index 00000000..3466ce7d Binary files /dev/null and b/amarok/src/images/icons/cr22-action-repeat_playlist.png differ diff --git a/amarok/src/images/icons/cr22-action-visualizations.png b/amarok/src/images/icons/cr22-action-visualizations.png new file mode 100644 index 00000000..ba9e4117 Binary files /dev/null and b/amarok/src/images/icons/cr22-action-visualizations.png differ diff --git a/amarok/src/images/icons/cr64-action-dynamic.png b/amarok/src/images/icons/cr64-action-dynamic.png new file mode 100644 index 00000000..6f884056 Binary files /dev/null and b/amarok/src/images/icons/cr64-action-dynamic.png differ diff --git a/amarok/src/images/icons/cr64-action-podcast.png b/amarok/src/images/icons/cr64-action-podcast.png new file mode 100644 index 00000000..16aed570 Binary files /dev/null and b/amarok/src/images/icons/cr64-action-podcast.png differ diff --git a/amarok/src/images/icons/cr64-action-podcast_new.png b/amarok/src/images/icons/cr64-action-podcast_new.png new file mode 100644 index 00000000..c9698b9d Binary files /dev/null and b/amarok/src/images/icons/cr64-action-podcast_new.png differ diff --git a/amarok/src/images/icons/cr64-action-random.png b/amarok/src/images/icons/cr64-action-random.png new file mode 100644 index 00000000..baba3f42 Binary files /dev/null and b/amarok/src/images/icons/cr64-action-random.png differ diff --git a/amarok/src/images/icons/cr64-action-repeat_playlist.png b/amarok/src/images/icons/cr64-action-repeat_playlist.png new file mode 100644 index 00000000..fcc82501 Binary files /dev/null and b/amarok/src/images/icons/cr64-action-repeat_playlist.png differ diff --git a/amarok/src/images/icons/hi16-action-amarok_add_lyrics.png b/amarok/src/images/icons/hi16-action-amarok_add_lyrics.png new file mode 100644 index 00000000..eae64b40 Binary files /dev/null and b/amarok/src/images/icons/hi16-action-amarok_add_lyrics.png differ diff --git a/amarok/src/images/icons/hi16-action-amarok_add_playlist.png b/amarok/src/images/icons/hi16-action-amarok_add_playlist.png new file mode 100644 index 00000000..396692cd Binary files /dev/null and b/amarok/src/images/icons/hi16-action-amarok_add_playlist.png differ diff --git a/amarok/src/images/icons/hi16-action-amarok_album.png b/amarok/src/images/icons/hi16-action-amarok_album.png new file mode 100644 index 00000000..584f8cab Binary files /dev/null and b/amarok/src/images/icons/hi16-action-amarok_album.png differ diff --git a/amarok/src/images/icons/hi16-action-amarok_artist.png b/amarok/src/images/icons/hi16-action-amarok_artist.png new file mode 100644 index 00000000..eca23af0 Binary files /dev/null and b/amarok/src/images/icons/hi16-action-amarok_artist.png differ diff --git a/amarok/src/images/icons/hi16-action-amarok_audioscrobbler.png b/amarok/src/images/icons/hi16-action-amarok_audioscrobbler.png new file mode 100644 index 00000000..49e4cd57 Binary files /dev/null and b/amarok/src/images/icons/hi16-action-amarok_audioscrobbler.png differ diff --git a/amarok/src/images/icons/hi16-action-amarok_back.png b/amarok/src/images/icons/hi16-action-amarok_back.png new file mode 100644 index 00000000..63642764 Binary files /dev/null and b/amarok/src/images/icons/hi16-action-amarok_back.png differ diff --git a/amarok/src/images/icons/hi16-action-amarok_burn.png b/amarok/src/images/icons/hi16-action-amarok_burn.png new file mode 100644 index 00000000..d098e041 Binary files /dev/null and b/amarok/src/images/icons/hi16-action-amarok_burn.png differ diff --git a/amarok/src/images/icons/hi16-action-amarok_change_language.png b/amarok/src/images/icons/hi16-action-amarok_change_language.png new file mode 100644 index 00000000..f54c063c Binary files /dev/null and b/amarok/src/images/icons/hi16-action-amarok_change_language.png differ diff --git a/amarok/src/images/icons/hi16-action-amarok_circle.png b/amarok/src/images/icons/hi16-action-amarok_circle.png new file mode 100644 index 00000000..376f75b3 Binary files /dev/null and b/amarok/src/images/icons/hi16-action-amarok_circle.png differ diff --git a/amarok/src/images/icons/hi16-action-amarok_clock.png b/amarok/src/images/icons/hi16-action-amarok_clock.png new file mode 100644 index 00000000..f3f38d04 Binary files /dev/null and b/amarok/src/images/icons/hi16-action-amarok_clock.png differ diff --git a/amarok/src/images/icons/hi16-action-amarok_collection.png b/amarok/src/images/icons/hi16-action-amarok_collection.png new file mode 100644 index 00000000..3af9fb51 Binary files /dev/null and b/amarok/src/images/icons/hi16-action-amarok_collection.png differ diff --git a/amarok/src/images/icons/hi16-action-amarok_configure.png b/amarok/src/images/icons/hi16-action-amarok_configure.png new file mode 100644 index 00000000..8a685146 Binary files /dev/null and b/amarok/src/images/icons/hi16-action-amarok_configure.png differ diff --git a/amarok/src/images/icons/hi16-action-amarok_covermanager.png b/amarok/src/images/icons/hi16-action-amarok_covermanager.png new file mode 100644 index 00000000..51fd7404 Binary files /dev/null and b/amarok/src/images/icons/hi16-action-amarok_covermanager.png differ diff --git a/amarok/src/images/icons/hi16-action-amarok_device.png b/amarok/src/images/icons/hi16-action-amarok_device.png new file mode 100644 index 00000000..dbe1b7e9 Binary files /dev/null and b/amarok/src/images/icons/hi16-action-amarok_device.png differ diff --git a/amarok/src/images/icons/hi16-action-amarok_download.png b/amarok/src/images/icons/hi16-action-amarok_download.png new file mode 100644 index 00000000..2a8c2dcb Binary files /dev/null and b/amarok/src/images/icons/hi16-action-amarok_download.png differ diff --git a/amarok/src/images/icons/hi16-action-amarok_dynamic.png b/amarok/src/images/icons/hi16-action-amarok_dynamic.png new file mode 100644 index 00000000..23dafa17 Binary files /dev/null and b/amarok/src/images/icons/hi16-action-amarok_dynamic.png differ diff --git a/amarok/src/images/icons/hi16-action-amarok_edit.png b/amarok/src/images/icons/hi16-action-amarok_edit.png new file mode 100644 index 00000000..37809ecf Binary files /dev/null and b/amarok/src/images/icons/hi16-action-amarok_edit.png differ diff --git a/amarok/src/images/icons/hi16-action-amarok_editcopy.png b/amarok/src/images/icons/hi16-action-amarok_editcopy.png new file mode 100644 index 00000000..abaf69a1 Binary files /dev/null and b/amarok/src/images/icons/hi16-action-amarok_editcopy.png differ diff --git a/amarok/src/images/icons/hi16-action-amarok_equalizer.png b/amarok/src/images/icons/hi16-action-amarok_equalizer.png new file mode 100644 index 00000000..875be4f6 Binary files /dev/null and b/amarok/src/images/icons/hi16-action-amarok_equalizer.png differ diff --git a/amarok/src/images/icons/hi16-action-amarok_external.png b/amarok/src/images/icons/hi16-action-amarok_external.png new file mode 100644 index 00000000..905720e6 Binary files /dev/null and b/amarok/src/images/icons/hi16-action-amarok_external.png differ diff --git a/amarok/src/images/icons/hi16-action-amarok_fastforward.png b/amarok/src/images/icons/hi16-action-amarok_fastforward.png new file mode 100644 index 00000000..4ed96d54 Binary files /dev/null and b/amarok/src/images/icons/hi16-action-amarok_fastforward.png differ diff --git a/amarok/src/images/icons/hi16-action-amarok_favourite_genres.png b/amarok/src/images/icons/hi16-action-amarok_favourite_genres.png new file mode 100644 index 00000000..8394af07 Binary files /dev/null and b/amarok/src/images/icons/hi16-action-amarok_favourite_genres.png differ diff --git a/amarok/src/images/icons/hi16-action-amarok_files.png b/amarok/src/images/icons/hi16-action-amarok_files.png new file mode 100644 index 00000000..f29e29df Binary files /dev/null and b/amarok/src/images/icons/hi16-action-amarok_files.png differ diff --git a/amarok/src/images/icons/hi16-action-amarok_files2.png b/amarok/src/images/icons/hi16-action-amarok_files2.png new file mode 100644 index 00000000..82942ee4 Binary files /dev/null and b/amarok/src/images/icons/hi16-action-amarok_files2.png differ diff --git a/amarok/src/images/icons/hi16-action-amarok_info.png b/amarok/src/images/icons/hi16-action-amarok_info.png new file mode 100644 index 00000000..c382af90 Binary files /dev/null and b/amarok/src/images/icons/hi16-action-amarok_info.png differ diff --git a/amarok/src/images/icons/hi16-action-amarok_love.png b/amarok/src/images/icons/hi16-action-amarok_love.png new file mode 100644 index 00000000..c4f40d1b Binary files /dev/null and b/amarok/src/images/icons/hi16-action-amarok_love.png differ diff --git a/amarok/src/images/icons/hi16-action-amarok_lyrics.png b/amarok/src/images/icons/hi16-action-amarok_lyrics.png new file mode 100644 index 00000000..bdf74ee2 Binary files /dev/null and b/amarok/src/images/icons/hi16-action-amarok_lyrics.png differ diff --git a/amarok/src/images/icons/hi16-action-amarok_magnatune.png b/amarok/src/images/icons/hi16-action-amarok_magnatune.png new file mode 100644 index 00000000..2641bba5 Binary files /dev/null and b/amarok/src/images/icons/hi16-action-amarok_magnatune.png differ diff --git a/amarok/src/images/icons/hi16-action-amarok_mostplayed.png b/amarok/src/images/icons/hi16-action-amarok_mostplayed.png new file mode 100644 index 00000000..3cf5c484 Binary files /dev/null and b/amarok/src/images/icons/hi16-action-amarok_mostplayed.png differ diff --git a/amarok/src/images/icons/hi16-action-amarok_music.png b/amarok/src/images/icons/hi16-action-amarok_music.png new file mode 100644 index 00000000..74f594c1 Binary files /dev/null and b/amarok/src/images/icons/hi16-action-amarok_music.png differ diff --git a/amarok/src/images/icons/hi16-action-amarok_next.png b/amarok/src/images/icons/hi16-action-amarok_next.png new file mode 100644 index 00000000..bd9624cd Binary files /dev/null and b/amarok/src/images/icons/hi16-action-amarok_next.png differ diff --git a/amarok/src/images/icons/hi16-action-amarok_pause.png b/amarok/src/images/icons/hi16-action-amarok_pause.png new file mode 100644 index 00000000..5807ff85 Binary files /dev/null and b/amarok/src/images/icons/hi16-action-amarok_pause.png differ diff --git a/amarok/src/images/icons/hi16-action-amarok_play.png b/amarok/src/images/icons/hi16-action-amarok_play.png new file mode 100644 index 00000000..9d8b658a Binary files /dev/null and b/amarok/src/images/icons/hi16-action-amarok_play.png differ diff --git a/amarok/src/images/icons/hi16-action-amarok_playlist.png b/amarok/src/images/icons/hi16-action-amarok_playlist.png new file mode 100644 index 00000000..7a820b5b Binary files /dev/null and b/amarok/src/images/icons/hi16-action-amarok_playlist.png differ diff --git a/amarok/src/images/icons/hi16-action-amarok_playlist_clear.png b/amarok/src/images/icons/hi16-action-amarok_playlist_clear.png new file mode 100644 index 00000000..9bf8121c Binary files /dev/null and b/amarok/src/images/icons/hi16-action-amarok_playlist_clear.png differ diff --git a/amarok/src/images/icons/hi16-action-amarok_playlist_refresh.png b/amarok/src/images/icons/hi16-action-amarok_playlist_refresh.png new file mode 100644 index 00000000..f3f18c52 Binary files /dev/null and b/amarok/src/images/icons/hi16-action-amarok_playlist_refresh.png differ diff --git a/amarok/src/images/icons/hi16-action-amarok_podcast.png b/amarok/src/images/icons/hi16-action-amarok_podcast.png new file mode 100644 index 00000000..60448bef Binary files /dev/null and b/amarok/src/images/icons/hi16-action-amarok_podcast.png differ diff --git a/amarok/src/images/icons/hi16-action-amarok_podcast2.png b/amarok/src/images/icons/hi16-action-amarok_podcast2.png new file mode 100644 index 00000000..02c44e55 Binary files /dev/null and b/amarok/src/images/icons/hi16-action-amarok_podcast2.png differ diff --git a/amarok/src/images/icons/hi16-action-amarok_queue.png b/amarok/src/images/icons/hi16-action-amarok_queue.png new file mode 100644 index 00000000..7f3f363d Binary files /dev/null and b/amarok/src/images/icons/hi16-action-amarok_queue.png differ diff --git a/amarok/src/images/icons/hi16-action-amarok_random.png b/amarok/src/images/icons/hi16-action-amarok_random.png new file mode 100644 index 00000000..23dafa17 Binary files /dev/null and b/amarok/src/images/icons/hi16-action-amarok_random.png differ diff --git a/amarok/src/images/icons/hi16-action-amarok_random_album.png b/amarok/src/images/icons/hi16-action-amarok_random_album.png new file mode 100644 index 00000000..63f31ae3 Binary files /dev/null and b/amarok/src/images/icons/hi16-action-amarok_random_album.png differ diff --git a/amarok/src/images/icons/hi16-action-amarok_random_no.png b/amarok/src/images/icons/hi16-action-amarok_random_no.png new file mode 100644 index 00000000..0629db4f Binary files /dev/null and b/amarok/src/images/icons/hi16-action-amarok_random_no.png differ diff --git a/amarok/src/images/icons/hi16-action-amarok_random_track.png b/amarok/src/images/icons/hi16-action-amarok_random_track.png new file mode 100644 index 00000000..e0b206b7 Binary files /dev/null and b/amarok/src/images/icons/hi16-action-amarok_random_track.png differ diff --git a/amarok/src/images/icons/hi16-action-amarok_redo.png b/amarok/src/images/icons/hi16-action-amarok_redo.png new file mode 100644 index 00000000..5e0ca486 Binary files /dev/null and b/amarok/src/images/icons/hi16-action-amarok_redo.png differ diff --git a/amarok/src/images/icons/hi16-action-amarok_refresh.png b/amarok/src/images/icons/hi16-action-amarok_refresh.png new file mode 100644 index 00000000..428f21cc Binary files /dev/null and b/amarok/src/images/icons/hi16-action-amarok_refresh.png differ diff --git a/amarok/src/images/icons/hi16-action-amarok_remove.png b/amarok/src/images/icons/hi16-action-amarok_remove.png new file mode 100644 index 00000000..ba4ff236 Binary files /dev/null and b/amarok/src/images/icons/hi16-action-amarok_remove.png differ diff --git a/amarok/src/images/icons/hi16-action-amarok_remove_from_playlist.png b/amarok/src/images/icons/hi16-action-amarok_remove_from_playlist.png new file mode 100644 index 00000000..64defe7f Binary files /dev/null and b/amarok/src/images/icons/hi16-action-amarok_remove_from_playlist.png differ diff --git a/amarok/src/images/icons/hi16-action-amarok_repeat_album.png b/amarok/src/images/icons/hi16-action-amarok_repeat_album.png new file mode 100644 index 00000000..87edba8d Binary files /dev/null and b/amarok/src/images/icons/hi16-action-amarok_repeat_album.png differ diff --git a/amarok/src/images/icons/hi16-action-amarok_repeat_no.png b/amarok/src/images/icons/hi16-action-amarok_repeat_no.png new file mode 100644 index 00000000..f1282850 Binary files /dev/null and b/amarok/src/images/icons/hi16-action-amarok_repeat_no.png differ diff --git a/amarok/src/images/icons/hi16-action-amarok_repeat_playlist.png b/amarok/src/images/icons/hi16-action-amarok_repeat_playlist.png new file mode 100644 index 00000000..e757d62b Binary files /dev/null and b/amarok/src/images/icons/hi16-action-amarok_repeat_playlist.png differ diff --git a/amarok/src/images/icons/hi16-action-amarok_repeat_track.png b/amarok/src/images/icons/hi16-action-amarok_repeat_track.png new file mode 100644 index 00000000..f1dbb61c Binary files /dev/null and b/amarok/src/images/icons/hi16-action-amarok_repeat_track.png differ diff --git a/amarok/src/images/icons/hi16-action-amarok_rescan.png b/amarok/src/images/icons/hi16-action-amarok_rescan.png new file mode 100644 index 00000000..14e44cbe Binary files /dev/null and b/amarok/src/images/icons/hi16-action-amarok_rescan.png differ diff --git a/amarok/src/images/icons/hi16-action-amarok_rewind.png b/amarok/src/images/icons/hi16-action-amarok_rewind.png new file mode 100644 index 00000000..676fa4d4 Binary files /dev/null and b/amarok/src/images/icons/hi16-action-amarok_rewind.png differ diff --git a/amarok/src/images/icons/hi16-action-amarok_save.png b/amarok/src/images/icons/hi16-action-amarok_save.png new file mode 100644 index 00000000..6588e3b5 Binary files /dev/null and b/amarok/src/images/icons/hi16-action-amarok_save.png differ diff --git a/amarok/src/images/icons/hi16-action-amarok_scripts.png b/amarok/src/images/icons/hi16-action-amarok_scripts.png new file mode 100644 index 00000000..92490a17 Binary files /dev/null and b/amarok/src/images/icons/hi16-action-amarok_scripts.png differ diff --git a/amarok/src/images/icons/hi16-action-amarok_search.png b/amarok/src/images/icons/hi16-action-amarok_search.png new file mode 100644 index 00000000..9cd423ea Binary files /dev/null and b/amarok/src/images/icons/hi16-action-amarok_search.png differ diff --git a/amarok/src/images/icons/hi16-action-amarok_settings_engine.png b/amarok/src/images/icons/hi16-action-amarok_settings_engine.png new file mode 100644 index 00000000..23b0dc9b Binary files /dev/null and b/amarok/src/images/icons/hi16-action-amarok_settings_engine.png differ diff --git a/amarok/src/images/icons/hi16-action-amarok_settings_general.png b/amarok/src/images/icons/hi16-action-amarok_settings_general.png new file mode 100644 index 00000000..d0f8beaa Binary files /dev/null and b/amarok/src/images/icons/hi16-action-amarok_settings_general.png differ diff --git a/amarok/src/images/icons/hi16-action-amarok_settings_indicator.png b/amarok/src/images/icons/hi16-action-amarok_settings_indicator.png new file mode 100644 index 00000000..2e12e480 Binary files /dev/null and b/amarok/src/images/icons/hi16-action-amarok_settings_indicator.png differ diff --git a/amarok/src/images/icons/hi16-action-amarok_settings_playback.png b/amarok/src/images/icons/hi16-action-amarok_settings_playback.png new file mode 100644 index 00000000..9e6ecc1c Binary files /dev/null and b/amarok/src/images/icons/hi16-action-amarok_settings_playback.png differ diff --git a/amarok/src/images/icons/hi16-action-amarok_settings_view.png b/amarok/src/images/icons/hi16-action-amarok_settings_view.png new file mode 100644 index 00000000..aefae881 Binary files /dev/null and b/amarok/src/images/icons/hi16-action-amarok_settings_view.png differ diff --git a/amarok/src/images/icons/hi16-action-amarok_stop.png b/amarok/src/images/icons/hi16-action-amarok_stop.png new file mode 100644 index 00000000..102b8be9 Binary files /dev/null and b/amarok/src/images/icons/hi16-action-amarok_stop.png differ diff --git a/amarok/src/images/icons/hi16-action-amarok_track.png b/amarok/src/images/icons/hi16-action-amarok_track.png new file mode 100644 index 00000000..0b22305b Binary files /dev/null and b/amarok/src/images/icons/hi16-action-amarok_track.png differ diff --git a/amarok/src/images/icons/hi16-action-amarok_undo.png b/amarok/src/images/icons/hi16-action-amarok_undo.png new file mode 100644 index 00000000..4d49c6d1 Binary files /dev/null and b/amarok/src/images/icons/hi16-action-amarok_undo.png differ diff --git a/amarok/src/images/icons/hi16-action-amarok_visualizations.png b/amarok/src/images/icons/hi16-action-amarok_visualizations.png new file mode 100644 index 00000000..a9b77dc0 Binary files /dev/null and b/amarok/src/images/icons/hi16-action-amarok_visualizations.png differ diff --git a/amarok/src/images/icons/hi16-action-amarok_zoom.png b/amarok/src/images/icons/hi16-action-amarok_zoom.png new file mode 100644 index 00000000..26bdd818 Binary files /dev/null and b/amarok/src/images/icons/hi16-action-amarok_zoom.png differ diff --git a/amarok/src/images/icons/hi16-action-collection.png b/amarok/src/images/icons/hi16-action-collection.png new file mode 100644 index 00000000..0923e0cd Binary files /dev/null and b/amarok/src/images/icons/hi16-action-collection.png differ diff --git a/amarok/src/images/icons/hi22-action-amarok_add_lyrics.png b/amarok/src/images/icons/hi22-action-amarok_add_lyrics.png new file mode 100644 index 00000000..2f3dfb93 Binary files /dev/null and b/amarok/src/images/icons/hi22-action-amarok_add_lyrics.png differ diff --git a/amarok/src/images/icons/hi22-action-amarok_add_playlist.png b/amarok/src/images/icons/hi22-action-amarok_add_playlist.png new file mode 100644 index 00000000..87a1498a Binary files /dev/null and b/amarok/src/images/icons/hi22-action-amarok_add_playlist.png differ diff --git a/amarok/src/images/icons/hi22-action-amarok_album.png b/amarok/src/images/icons/hi22-action-amarok_album.png new file mode 100644 index 00000000..93e2fb5c Binary files /dev/null and b/amarok/src/images/icons/hi22-action-amarok_album.png differ diff --git a/amarok/src/images/icons/hi22-action-amarok_artist.png b/amarok/src/images/icons/hi22-action-amarok_artist.png new file mode 100644 index 00000000..1d12c9fb Binary files /dev/null and b/amarok/src/images/icons/hi22-action-amarok_artist.png differ diff --git a/amarok/src/images/icons/hi22-action-amarok_audioscrobbler.png b/amarok/src/images/icons/hi22-action-amarok_audioscrobbler.png new file mode 100644 index 00000000..c8528880 Binary files /dev/null and b/amarok/src/images/icons/hi22-action-amarok_audioscrobbler.png differ diff --git a/amarok/src/images/icons/hi22-action-amarok_back.png b/amarok/src/images/icons/hi22-action-amarok_back.png new file mode 100644 index 00000000..0ea46e1e Binary files /dev/null and b/amarok/src/images/icons/hi22-action-amarok_back.png differ diff --git a/amarok/src/images/icons/hi22-action-amarok_burn.png b/amarok/src/images/icons/hi22-action-amarok_burn.png new file mode 100644 index 00000000..ac3483fc Binary files /dev/null and b/amarok/src/images/icons/hi22-action-amarok_burn.png differ diff --git a/amarok/src/images/icons/hi22-action-amarok_change_language.png b/amarok/src/images/icons/hi22-action-amarok_change_language.png new file mode 100644 index 00000000..e42585e0 Binary files /dev/null and b/amarok/src/images/icons/hi22-action-amarok_change_language.png differ diff --git a/amarok/src/images/icons/hi22-action-amarok_circle.png b/amarok/src/images/icons/hi22-action-amarok_circle.png new file mode 100644 index 00000000..3a51d7ec Binary files /dev/null and b/amarok/src/images/icons/hi22-action-amarok_circle.png differ diff --git a/amarok/src/images/icons/hi22-action-amarok_clock.png b/amarok/src/images/icons/hi22-action-amarok_clock.png new file mode 100644 index 00000000..a569a5da Binary files /dev/null and b/amarok/src/images/icons/hi22-action-amarok_clock.png differ diff --git a/amarok/src/images/icons/hi22-action-amarok_collection.png b/amarok/src/images/icons/hi22-action-amarok_collection.png new file mode 100644 index 00000000..1452da9b Binary files /dev/null and b/amarok/src/images/icons/hi22-action-amarok_collection.png differ diff --git a/amarok/src/images/icons/hi22-action-amarok_configure.png b/amarok/src/images/icons/hi22-action-amarok_configure.png new file mode 100644 index 00000000..761e229d Binary files /dev/null and b/amarok/src/images/icons/hi22-action-amarok_configure.png differ diff --git a/amarok/src/images/icons/hi22-action-amarok_covermanager.png b/amarok/src/images/icons/hi22-action-amarok_covermanager.png new file mode 100644 index 00000000..7744a1c5 Binary files /dev/null and b/amarok/src/images/icons/hi22-action-amarok_covermanager.png differ diff --git a/amarok/src/images/icons/hi22-action-amarok_device.png b/amarok/src/images/icons/hi22-action-amarok_device.png new file mode 100644 index 00000000..2eae8a16 Binary files /dev/null and b/amarok/src/images/icons/hi22-action-amarok_device.png differ diff --git a/amarok/src/images/icons/hi22-action-amarok_download.png b/amarok/src/images/icons/hi22-action-amarok_download.png new file mode 100644 index 00000000..ba8074f4 Binary files /dev/null and b/amarok/src/images/icons/hi22-action-amarok_download.png differ diff --git a/amarok/src/images/icons/hi22-action-amarok_dynamic.png b/amarok/src/images/icons/hi22-action-amarok_dynamic.png new file mode 100644 index 00000000..53c55748 Binary files /dev/null and b/amarok/src/images/icons/hi22-action-amarok_dynamic.png differ diff --git a/amarok/src/images/icons/hi22-action-amarok_edit.png b/amarok/src/images/icons/hi22-action-amarok_edit.png new file mode 100644 index 00000000..61034b87 Binary files /dev/null and b/amarok/src/images/icons/hi22-action-amarok_edit.png differ diff --git a/amarok/src/images/icons/hi22-action-amarok_editcopy.png b/amarok/src/images/icons/hi22-action-amarok_editcopy.png new file mode 100644 index 00000000..6819eafa Binary files /dev/null and b/amarok/src/images/icons/hi22-action-amarok_editcopy.png differ diff --git a/amarok/src/images/icons/hi22-action-amarok_equalizer.png b/amarok/src/images/icons/hi22-action-amarok_equalizer.png new file mode 100644 index 00000000..57651fc9 Binary files /dev/null and b/amarok/src/images/icons/hi22-action-amarok_equalizer.png differ diff --git a/amarok/src/images/icons/hi22-action-amarok_external.png b/amarok/src/images/icons/hi22-action-amarok_external.png new file mode 100644 index 00000000..60ba1655 Binary files /dev/null and b/amarok/src/images/icons/hi22-action-amarok_external.png differ diff --git a/amarok/src/images/icons/hi22-action-amarok_fastforward.png b/amarok/src/images/icons/hi22-action-amarok_fastforward.png new file mode 100644 index 00000000..96ff6162 Binary files /dev/null and b/amarok/src/images/icons/hi22-action-amarok_fastforward.png differ diff --git a/amarok/src/images/icons/hi22-action-amarok_favourite_genres.png b/amarok/src/images/icons/hi22-action-amarok_favourite_genres.png new file mode 100644 index 00000000..d947db28 Binary files /dev/null and b/amarok/src/images/icons/hi22-action-amarok_favourite_genres.png differ diff --git a/amarok/src/images/icons/hi22-action-amarok_files.png b/amarok/src/images/icons/hi22-action-amarok_files.png new file mode 100644 index 00000000..43081254 Binary files /dev/null and b/amarok/src/images/icons/hi22-action-amarok_files.png differ diff --git a/amarok/src/images/icons/hi22-action-amarok_files2.png b/amarok/src/images/icons/hi22-action-amarok_files2.png new file mode 100644 index 00000000..df5c4cd4 Binary files /dev/null and b/amarok/src/images/icons/hi22-action-amarok_files2.png differ diff --git a/amarok/src/images/icons/hi22-action-amarok_info.png b/amarok/src/images/icons/hi22-action-amarok_info.png new file mode 100644 index 00000000..a47e3715 Binary files /dev/null and b/amarok/src/images/icons/hi22-action-amarok_info.png differ diff --git a/amarok/src/images/icons/hi22-action-amarok_love.png b/amarok/src/images/icons/hi22-action-amarok_love.png new file mode 100644 index 00000000..76bf3aa7 Binary files /dev/null and b/amarok/src/images/icons/hi22-action-amarok_love.png differ diff --git a/amarok/src/images/icons/hi22-action-amarok_lyrics.png b/amarok/src/images/icons/hi22-action-amarok_lyrics.png new file mode 100644 index 00000000..17a01fa1 Binary files /dev/null and b/amarok/src/images/icons/hi22-action-amarok_lyrics.png differ diff --git a/amarok/src/images/icons/hi22-action-amarok_magnatune.png b/amarok/src/images/icons/hi22-action-amarok_magnatune.png new file mode 100644 index 00000000..f5ea7602 Binary files /dev/null and b/amarok/src/images/icons/hi22-action-amarok_magnatune.png differ diff --git a/amarok/src/images/icons/hi22-action-amarok_mostplayed.png b/amarok/src/images/icons/hi22-action-amarok_mostplayed.png new file mode 100644 index 00000000..19044bca Binary files /dev/null and b/amarok/src/images/icons/hi22-action-amarok_mostplayed.png differ diff --git a/amarok/src/images/icons/hi22-action-amarok_music.png b/amarok/src/images/icons/hi22-action-amarok_music.png new file mode 100644 index 00000000..cc2d1f58 Binary files /dev/null and b/amarok/src/images/icons/hi22-action-amarok_music.png differ diff --git a/amarok/src/images/icons/hi22-action-amarok_next.png b/amarok/src/images/icons/hi22-action-amarok_next.png new file mode 100644 index 00000000..0849973d Binary files /dev/null and b/amarok/src/images/icons/hi22-action-amarok_next.png differ diff --git a/amarok/src/images/icons/hi22-action-amarok_pause.png b/amarok/src/images/icons/hi22-action-amarok_pause.png new file mode 100644 index 00000000..4fbd4ec8 Binary files /dev/null and b/amarok/src/images/icons/hi22-action-amarok_pause.png differ diff --git a/amarok/src/images/icons/hi22-action-amarok_play.png b/amarok/src/images/icons/hi22-action-amarok_play.png new file mode 100644 index 00000000..8844dffb Binary files /dev/null and b/amarok/src/images/icons/hi22-action-amarok_play.png differ diff --git a/amarok/src/images/icons/hi22-action-amarok_playlist.png b/amarok/src/images/icons/hi22-action-amarok_playlist.png new file mode 100644 index 00000000..7ca222ab Binary files /dev/null and b/amarok/src/images/icons/hi22-action-amarok_playlist.png differ diff --git a/amarok/src/images/icons/hi22-action-amarok_playlist_clear.png b/amarok/src/images/icons/hi22-action-amarok_playlist_clear.png new file mode 100644 index 00000000..d84ed2b4 Binary files /dev/null and b/amarok/src/images/icons/hi22-action-amarok_playlist_clear.png differ diff --git a/amarok/src/images/icons/hi22-action-amarok_playlist_refresh.png b/amarok/src/images/icons/hi22-action-amarok_playlist_refresh.png new file mode 100644 index 00000000..791d87f9 Binary files /dev/null and b/amarok/src/images/icons/hi22-action-amarok_playlist_refresh.png differ diff --git a/amarok/src/images/icons/hi22-action-amarok_podcast.png b/amarok/src/images/icons/hi22-action-amarok_podcast.png new file mode 100644 index 00000000..21de9fe1 Binary files /dev/null and b/amarok/src/images/icons/hi22-action-amarok_podcast.png differ diff --git a/amarok/src/images/icons/hi22-action-amarok_podcast2.png b/amarok/src/images/icons/hi22-action-amarok_podcast2.png new file mode 100644 index 00000000..085055b2 Binary files /dev/null and b/amarok/src/images/icons/hi22-action-amarok_podcast2.png differ diff --git a/amarok/src/images/icons/hi22-action-amarok_queue.png b/amarok/src/images/icons/hi22-action-amarok_queue.png new file mode 100644 index 00000000..acd34f91 Binary files /dev/null and b/amarok/src/images/icons/hi22-action-amarok_queue.png differ diff --git a/amarok/src/images/icons/hi22-action-amarok_random.png b/amarok/src/images/icons/hi22-action-amarok_random.png new file mode 100644 index 00000000..53c55748 Binary files /dev/null and b/amarok/src/images/icons/hi22-action-amarok_random.png differ diff --git a/amarok/src/images/icons/hi22-action-amarok_random_album.png b/amarok/src/images/icons/hi22-action-amarok_random_album.png new file mode 100644 index 00000000..1adf37ae Binary files /dev/null and b/amarok/src/images/icons/hi22-action-amarok_random_album.png differ diff --git a/amarok/src/images/icons/hi22-action-amarok_random_no.png b/amarok/src/images/icons/hi22-action-amarok_random_no.png new file mode 100644 index 00000000..1f1451d0 Binary files /dev/null and b/amarok/src/images/icons/hi22-action-amarok_random_no.png differ diff --git a/amarok/src/images/icons/hi22-action-amarok_random_track.png b/amarok/src/images/icons/hi22-action-amarok_random_track.png new file mode 100644 index 00000000..8ebe1fef Binary files /dev/null and b/amarok/src/images/icons/hi22-action-amarok_random_track.png differ diff --git a/amarok/src/images/icons/hi22-action-amarok_redo.png b/amarok/src/images/icons/hi22-action-amarok_redo.png new file mode 100644 index 00000000..51d5a5ed Binary files /dev/null and b/amarok/src/images/icons/hi22-action-amarok_redo.png differ diff --git a/amarok/src/images/icons/hi22-action-amarok_refresh.png b/amarok/src/images/icons/hi22-action-amarok_refresh.png new file mode 100644 index 00000000..4a88c292 Binary files /dev/null and b/amarok/src/images/icons/hi22-action-amarok_refresh.png differ diff --git a/amarok/src/images/icons/hi22-action-amarok_remove.png b/amarok/src/images/icons/hi22-action-amarok_remove.png new file mode 100644 index 00000000..8045f951 Binary files /dev/null and b/amarok/src/images/icons/hi22-action-amarok_remove.png differ diff --git a/amarok/src/images/icons/hi22-action-amarok_remove_from_playlist.png b/amarok/src/images/icons/hi22-action-amarok_remove_from_playlist.png new file mode 100644 index 00000000..5473502e Binary files /dev/null and b/amarok/src/images/icons/hi22-action-amarok_remove_from_playlist.png differ diff --git a/amarok/src/images/icons/hi22-action-amarok_repeat_album.png b/amarok/src/images/icons/hi22-action-amarok_repeat_album.png new file mode 100644 index 00000000..1cda2f74 Binary files /dev/null and b/amarok/src/images/icons/hi22-action-amarok_repeat_album.png differ diff --git a/amarok/src/images/icons/hi22-action-amarok_repeat_no.png b/amarok/src/images/icons/hi22-action-amarok_repeat_no.png new file mode 100644 index 00000000..16adf46a Binary files /dev/null and b/amarok/src/images/icons/hi22-action-amarok_repeat_no.png differ diff --git a/amarok/src/images/icons/hi22-action-amarok_repeat_playlist.png b/amarok/src/images/icons/hi22-action-amarok_repeat_playlist.png new file mode 100644 index 00000000..eb3c9af1 Binary files /dev/null and b/amarok/src/images/icons/hi22-action-amarok_repeat_playlist.png differ diff --git a/amarok/src/images/icons/hi22-action-amarok_repeat_track.png b/amarok/src/images/icons/hi22-action-amarok_repeat_track.png new file mode 100644 index 00000000..53bef9e3 Binary files /dev/null and b/amarok/src/images/icons/hi22-action-amarok_repeat_track.png differ diff --git a/amarok/src/images/icons/hi22-action-amarok_rescan.png b/amarok/src/images/icons/hi22-action-amarok_rescan.png new file mode 100644 index 00000000..acbfe7a1 Binary files /dev/null and b/amarok/src/images/icons/hi22-action-amarok_rescan.png differ diff --git a/amarok/src/images/icons/hi22-action-amarok_rewind.png b/amarok/src/images/icons/hi22-action-amarok_rewind.png new file mode 100644 index 00000000..bcc9a171 Binary files /dev/null and b/amarok/src/images/icons/hi22-action-amarok_rewind.png differ diff --git a/amarok/src/images/icons/hi22-action-amarok_save.png b/amarok/src/images/icons/hi22-action-amarok_save.png new file mode 100644 index 00000000..3d5ccd29 Binary files /dev/null and b/amarok/src/images/icons/hi22-action-amarok_save.png differ diff --git a/amarok/src/images/icons/hi22-action-amarok_scripts.png b/amarok/src/images/icons/hi22-action-amarok_scripts.png new file mode 100644 index 00000000..3c6567c5 Binary files /dev/null and b/amarok/src/images/icons/hi22-action-amarok_scripts.png differ diff --git a/amarok/src/images/icons/hi22-action-amarok_search.png b/amarok/src/images/icons/hi22-action-amarok_search.png new file mode 100644 index 00000000..a9ef97ef Binary files /dev/null and b/amarok/src/images/icons/hi22-action-amarok_search.png differ diff --git a/amarok/src/images/icons/hi22-action-amarok_settings_engine.png b/amarok/src/images/icons/hi22-action-amarok_settings_engine.png new file mode 100644 index 00000000..276a7e13 Binary files /dev/null and b/amarok/src/images/icons/hi22-action-amarok_settings_engine.png differ diff --git a/amarok/src/images/icons/hi22-action-amarok_settings_general.png b/amarok/src/images/icons/hi22-action-amarok_settings_general.png new file mode 100644 index 00000000..90d1e81d Binary files /dev/null and b/amarok/src/images/icons/hi22-action-amarok_settings_general.png differ diff --git a/amarok/src/images/icons/hi22-action-amarok_settings_indicator.png b/amarok/src/images/icons/hi22-action-amarok_settings_indicator.png new file mode 100644 index 00000000..895c9d48 Binary files /dev/null and b/amarok/src/images/icons/hi22-action-amarok_settings_indicator.png differ diff --git a/amarok/src/images/icons/hi22-action-amarok_settings_playback.png b/amarok/src/images/icons/hi22-action-amarok_settings_playback.png new file mode 100644 index 00000000..1e77ea93 Binary files /dev/null and b/amarok/src/images/icons/hi22-action-amarok_settings_playback.png differ diff --git a/amarok/src/images/icons/hi22-action-amarok_settings_view.png b/amarok/src/images/icons/hi22-action-amarok_settings_view.png new file mode 100644 index 00000000..542eb842 Binary files /dev/null and b/amarok/src/images/icons/hi22-action-amarok_settings_view.png differ diff --git a/amarok/src/images/icons/hi22-action-amarok_stop.png b/amarok/src/images/icons/hi22-action-amarok_stop.png new file mode 100644 index 00000000..c2b9d8bd Binary files /dev/null and b/amarok/src/images/icons/hi22-action-amarok_stop.png differ diff --git a/amarok/src/images/icons/hi22-action-amarok_track.png b/amarok/src/images/icons/hi22-action-amarok_track.png new file mode 100644 index 00000000..b6625f93 Binary files /dev/null and b/amarok/src/images/icons/hi22-action-amarok_track.png differ diff --git a/amarok/src/images/icons/hi22-action-amarok_undo.png b/amarok/src/images/icons/hi22-action-amarok_undo.png new file mode 100644 index 00000000..879f6e50 Binary files /dev/null and b/amarok/src/images/icons/hi22-action-amarok_undo.png differ diff --git a/amarok/src/images/icons/hi22-action-amarok_visualizations.png b/amarok/src/images/icons/hi22-action-amarok_visualizations.png new file mode 100644 index 00000000..ba9e4117 Binary files /dev/null and b/amarok/src/images/icons/hi22-action-amarok_visualizations.png differ diff --git a/amarok/src/images/icons/hi22-action-amarok_zoom.png b/amarok/src/images/icons/hi22-action-amarok_zoom.png new file mode 100644 index 00000000..1670e56a Binary files /dev/null and b/amarok/src/images/icons/hi22-action-amarok_zoom.png differ diff --git a/amarok/src/images/icons/hi22-action-collection.png b/amarok/src/images/icons/hi22-action-collection.png new file mode 100644 index 00000000..460f5e8d Binary files /dev/null and b/amarok/src/images/icons/hi22-action-collection.png differ diff --git a/amarok/src/images/icons/hi32-action-amarok_add_lyrics.png b/amarok/src/images/icons/hi32-action-amarok_add_lyrics.png new file mode 100644 index 00000000..20a823ce Binary files /dev/null and b/amarok/src/images/icons/hi32-action-amarok_add_lyrics.png differ diff --git a/amarok/src/images/icons/hi32-action-amarok_add_playlist.png b/amarok/src/images/icons/hi32-action-amarok_add_playlist.png new file mode 100644 index 00000000..d0775170 Binary files /dev/null and b/amarok/src/images/icons/hi32-action-amarok_add_playlist.png differ diff --git a/amarok/src/images/icons/hi32-action-amarok_album.png b/amarok/src/images/icons/hi32-action-amarok_album.png new file mode 100644 index 00000000..b8cca19b Binary files /dev/null and b/amarok/src/images/icons/hi32-action-amarok_album.png differ diff --git a/amarok/src/images/icons/hi32-action-amarok_artist.png b/amarok/src/images/icons/hi32-action-amarok_artist.png new file mode 100644 index 00000000..87b4681f Binary files /dev/null and b/amarok/src/images/icons/hi32-action-amarok_artist.png differ diff --git a/amarok/src/images/icons/hi32-action-amarok_audioscrobbler.png b/amarok/src/images/icons/hi32-action-amarok_audioscrobbler.png new file mode 100644 index 00000000..42351fa6 Binary files /dev/null and b/amarok/src/images/icons/hi32-action-amarok_audioscrobbler.png differ diff --git a/amarok/src/images/icons/hi32-action-amarok_back.png b/amarok/src/images/icons/hi32-action-amarok_back.png new file mode 100644 index 00000000..e0f12044 Binary files /dev/null and b/amarok/src/images/icons/hi32-action-amarok_back.png differ diff --git a/amarok/src/images/icons/hi32-action-amarok_burn.png b/amarok/src/images/icons/hi32-action-amarok_burn.png new file mode 100644 index 00000000..0c18ad58 Binary files /dev/null and b/amarok/src/images/icons/hi32-action-amarok_burn.png differ diff --git a/amarok/src/images/icons/hi32-action-amarok_change_language.png b/amarok/src/images/icons/hi32-action-amarok_change_language.png new file mode 100644 index 00000000..ec19880d Binary files /dev/null and b/amarok/src/images/icons/hi32-action-amarok_change_language.png differ diff --git a/amarok/src/images/icons/hi32-action-amarok_circle.png b/amarok/src/images/icons/hi32-action-amarok_circle.png new file mode 100644 index 00000000..b26d8454 Binary files /dev/null and b/amarok/src/images/icons/hi32-action-amarok_circle.png differ diff --git a/amarok/src/images/icons/hi32-action-amarok_clock.png b/amarok/src/images/icons/hi32-action-amarok_clock.png new file mode 100644 index 00000000..fe64b140 Binary files /dev/null and b/amarok/src/images/icons/hi32-action-amarok_clock.png differ diff --git a/amarok/src/images/icons/hi32-action-amarok_collection.png b/amarok/src/images/icons/hi32-action-amarok_collection.png new file mode 100644 index 00000000..e0e4d071 Binary files /dev/null and b/amarok/src/images/icons/hi32-action-amarok_collection.png differ diff --git a/amarok/src/images/icons/hi32-action-amarok_configure.png b/amarok/src/images/icons/hi32-action-amarok_configure.png new file mode 100644 index 00000000..4a2117b0 Binary files /dev/null and b/amarok/src/images/icons/hi32-action-amarok_configure.png differ diff --git a/amarok/src/images/icons/hi32-action-amarok_covermanager.png b/amarok/src/images/icons/hi32-action-amarok_covermanager.png new file mode 100644 index 00000000..befe0142 Binary files /dev/null and b/amarok/src/images/icons/hi32-action-amarok_covermanager.png differ diff --git a/amarok/src/images/icons/hi32-action-amarok_device.png b/amarok/src/images/icons/hi32-action-amarok_device.png new file mode 100644 index 00000000..8a6d24d2 Binary files /dev/null and b/amarok/src/images/icons/hi32-action-amarok_device.png differ diff --git a/amarok/src/images/icons/hi32-action-amarok_download.png b/amarok/src/images/icons/hi32-action-amarok_download.png new file mode 100644 index 00000000..15c8b001 Binary files /dev/null and b/amarok/src/images/icons/hi32-action-amarok_download.png differ diff --git a/amarok/src/images/icons/hi32-action-amarok_dynamic.png b/amarok/src/images/icons/hi32-action-amarok_dynamic.png new file mode 100644 index 00000000..2838d50a Binary files /dev/null and b/amarok/src/images/icons/hi32-action-amarok_dynamic.png differ diff --git a/amarok/src/images/icons/hi32-action-amarok_edit.png b/amarok/src/images/icons/hi32-action-amarok_edit.png new file mode 100644 index 00000000..7547a515 Binary files /dev/null and b/amarok/src/images/icons/hi32-action-amarok_edit.png differ diff --git a/amarok/src/images/icons/hi32-action-amarok_editcopy.png b/amarok/src/images/icons/hi32-action-amarok_editcopy.png new file mode 100644 index 00000000..d5769264 Binary files /dev/null and b/amarok/src/images/icons/hi32-action-amarok_editcopy.png differ diff --git a/amarok/src/images/icons/hi32-action-amarok_equalizer.png b/amarok/src/images/icons/hi32-action-amarok_equalizer.png new file mode 100644 index 00000000..9f3ac918 Binary files /dev/null and b/amarok/src/images/icons/hi32-action-amarok_equalizer.png differ diff --git a/amarok/src/images/icons/hi32-action-amarok_external.png b/amarok/src/images/icons/hi32-action-amarok_external.png new file mode 100644 index 00000000..5335773b Binary files /dev/null and b/amarok/src/images/icons/hi32-action-amarok_external.png differ diff --git a/amarok/src/images/icons/hi32-action-amarok_fastforward.png b/amarok/src/images/icons/hi32-action-amarok_fastforward.png new file mode 100644 index 00000000..0e3c7c66 Binary files /dev/null and b/amarok/src/images/icons/hi32-action-amarok_fastforward.png differ diff --git a/amarok/src/images/icons/hi32-action-amarok_favourite_genres.png b/amarok/src/images/icons/hi32-action-amarok_favourite_genres.png new file mode 100644 index 00000000..f3b095bf Binary files /dev/null and b/amarok/src/images/icons/hi32-action-amarok_favourite_genres.png differ diff --git a/amarok/src/images/icons/hi32-action-amarok_files.png b/amarok/src/images/icons/hi32-action-amarok_files.png new file mode 100644 index 00000000..a5e397e8 Binary files /dev/null and b/amarok/src/images/icons/hi32-action-amarok_files.png differ diff --git a/amarok/src/images/icons/hi32-action-amarok_files2.png b/amarok/src/images/icons/hi32-action-amarok_files2.png new file mode 100644 index 00000000..5cd42481 Binary files /dev/null and b/amarok/src/images/icons/hi32-action-amarok_files2.png differ diff --git a/amarok/src/images/icons/hi32-action-amarok_info.png b/amarok/src/images/icons/hi32-action-amarok_info.png new file mode 100644 index 00000000..da2cf250 Binary files /dev/null and b/amarok/src/images/icons/hi32-action-amarok_info.png differ diff --git a/amarok/src/images/icons/hi32-action-amarok_love.png b/amarok/src/images/icons/hi32-action-amarok_love.png new file mode 100644 index 00000000..169794a4 Binary files /dev/null and b/amarok/src/images/icons/hi32-action-amarok_love.png differ diff --git a/amarok/src/images/icons/hi32-action-amarok_lyrics.png b/amarok/src/images/icons/hi32-action-amarok_lyrics.png new file mode 100644 index 00000000..98fd6f5e Binary files /dev/null and b/amarok/src/images/icons/hi32-action-amarok_lyrics.png differ diff --git a/amarok/src/images/icons/hi32-action-amarok_magnatune.png b/amarok/src/images/icons/hi32-action-amarok_magnatune.png new file mode 100644 index 00000000..3a127de6 Binary files /dev/null and b/amarok/src/images/icons/hi32-action-amarok_magnatune.png differ diff --git a/amarok/src/images/icons/hi32-action-amarok_mostplayed.png b/amarok/src/images/icons/hi32-action-amarok_mostplayed.png new file mode 100644 index 00000000..a20be447 Binary files /dev/null and b/amarok/src/images/icons/hi32-action-amarok_mostplayed.png differ diff --git a/amarok/src/images/icons/hi32-action-amarok_music.png b/amarok/src/images/icons/hi32-action-amarok_music.png new file mode 100644 index 00000000..c212a4d9 Binary files /dev/null and b/amarok/src/images/icons/hi32-action-amarok_music.png differ diff --git a/amarok/src/images/icons/hi32-action-amarok_next.png b/amarok/src/images/icons/hi32-action-amarok_next.png new file mode 100644 index 00000000..7935dec9 Binary files /dev/null and b/amarok/src/images/icons/hi32-action-amarok_next.png differ diff --git a/amarok/src/images/icons/hi32-action-amarok_pause.png b/amarok/src/images/icons/hi32-action-amarok_pause.png new file mode 100644 index 00000000..a5e3f159 Binary files /dev/null and b/amarok/src/images/icons/hi32-action-amarok_pause.png differ diff --git a/amarok/src/images/icons/hi32-action-amarok_play.png b/amarok/src/images/icons/hi32-action-amarok_play.png new file mode 100644 index 00000000..a343ba8a Binary files /dev/null and b/amarok/src/images/icons/hi32-action-amarok_play.png differ diff --git a/amarok/src/images/icons/hi32-action-amarok_playlist.png b/amarok/src/images/icons/hi32-action-amarok_playlist.png new file mode 100644 index 00000000..06aa7fd2 Binary files /dev/null and b/amarok/src/images/icons/hi32-action-amarok_playlist.png differ diff --git a/amarok/src/images/icons/hi32-action-amarok_playlist_clear.png b/amarok/src/images/icons/hi32-action-amarok_playlist_clear.png new file mode 100644 index 00000000..3bfea9c5 Binary files /dev/null and b/amarok/src/images/icons/hi32-action-amarok_playlist_clear.png differ diff --git a/amarok/src/images/icons/hi32-action-amarok_playlist_refresh.png b/amarok/src/images/icons/hi32-action-amarok_playlist_refresh.png new file mode 100644 index 00000000..aa01ea55 Binary files /dev/null and b/amarok/src/images/icons/hi32-action-amarok_playlist_refresh.png differ diff --git a/amarok/src/images/icons/hi32-action-amarok_podcast.png b/amarok/src/images/icons/hi32-action-amarok_podcast.png new file mode 100644 index 00000000..8351af26 Binary files /dev/null and b/amarok/src/images/icons/hi32-action-amarok_podcast.png differ diff --git a/amarok/src/images/icons/hi32-action-amarok_podcast2.png b/amarok/src/images/icons/hi32-action-amarok_podcast2.png new file mode 100644 index 00000000..a54eba82 Binary files /dev/null and b/amarok/src/images/icons/hi32-action-amarok_podcast2.png differ diff --git a/amarok/src/images/icons/hi32-action-amarok_queue.png b/amarok/src/images/icons/hi32-action-amarok_queue.png new file mode 100644 index 00000000..6153660a Binary files /dev/null and b/amarok/src/images/icons/hi32-action-amarok_queue.png differ diff --git a/amarok/src/images/icons/hi32-action-amarok_random.png b/amarok/src/images/icons/hi32-action-amarok_random.png new file mode 100644 index 00000000..2838d50a Binary files /dev/null and b/amarok/src/images/icons/hi32-action-amarok_random.png differ diff --git a/amarok/src/images/icons/hi32-action-amarok_random_album.png b/amarok/src/images/icons/hi32-action-amarok_random_album.png new file mode 100644 index 00000000..df414734 Binary files /dev/null and b/amarok/src/images/icons/hi32-action-amarok_random_album.png differ diff --git a/amarok/src/images/icons/hi32-action-amarok_random_no.png b/amarok/src/images/icons/hi32-action-amarok_random_no.png new file mode 100644 index 00000000..7fb63dde Binary files /dev/null and b/amarok/src/images/icons/hi32-action-amarok_random_no.png differ diff --git a/amarok/src/images/icons/hi32-action-amarok_random_track.png b/amarok/src/images/icons/hi32-action-amarok_random_track.png new file mode 100644 index 00000000..673466ee Binary files /dev/null and b/amarok/src/images/icons/hi32-action-amarok_random_track.png differ diff --git a/amarok/src/images/icons/hi32-action-amarok_redo.png b/amarok/src/images/icons/hi32-action-amarok_redo.png new file mode 100644 index 00000000..774afb81 Binary files /dev/null and b/amarok/src/images/icons/hi32-action-amarok_redo.png differ diff --git a/amarok/src/images/icons/hi32-action-amarok_refresh.png b/amarok/src/images/icons/hi32-action-amarok_refresh.png new file mode 100644 index 00000000..d428010a Binary files /dev/null and b/amarok/src/images/icons/hi32-action-amarok_refresh.png differ diff --git a/amarok/src/images/icons/hi32-action-amarok_remove.png b/amarok/src/images/icons/hi32-action-amarok_remove.png new file mode 100644 index 00000000..d090a436 Binary files /dev/null and b/amarok/src/images/icons/hi32-action-amarok_remove.png differ diff --git a/amarok/src/images/icons/hi32-action-amarok_remove_from_playlist.png b/amarok/src/images/icons/hi32-action-amarok_remove_from_playlist.png new file mode 100644 index 00000000..cbd706b3 Binary files /dev/null and b/amarok/src/images/icons/hi32-action-amarok_remove_from_playlist.png differ diff --git a/amarok/src/images/icons/hi32-action-amarok_repeat_album.png b/amarok/src/images/icons/hi32-action-amarok_repeat_album.png new file mode 100644 index 00000000..1046d59f Binary files /dev/null and b/amarok/src/images/icons/hi32-action-amarok_repeat_album.png differ diff --git a/amarok/src/images/icons/hi32-action-amarok_repeat_no.png b/amarok/src/images/icons/hi32-action-amarok_repeat_no.png new file mode 100644 index 00000000..bc956ea8 Binary files /dev/null and b/amarok/src/images/icons/hi32-action-amarok_repeat_no.png differ diff --git a/amarok/src/images/icons/hi32-action-amarok_repeat_playlist.png b/amarok/src/images/icons/hi32-action-amarok_repeat_playlist.png new file mode 100644 index 00000000..554c9f52 Binary files /dev/null and b/amarok/src/images/icons/hi32-action-amarok_repeat_playlist.png differ diff --git a/amarok/src/images/icons/hi32-action-amarok_repeat_track.png b/amarok/src/images/icons/hi32-action-amarok_repeat_track.png new file mode 100644 index 00000000..db99a204 Binary files /dev/null and b/amarok/src/images/icons/hi32-action-amarok_repeat_track.png differ diff --git a/amarok/src/images/icons/hi32-action-amarok_rescan.png b/amarok/src/images/icons/hi32-action-amarok_rescan.png new file mode 100644 index 00000000..0c37dccb Binary files /dev/null and b/amarok/src/images/icons/hi32-action-amarok_rescan.png differ diff --git a/amarok/src/images/icons/hi32-action-amarok_rewind.png b/amarok/src/images/icons/hi32-action-amarok_rewind.png new file mode 100644 index 00000000..f6e93e6a Binary files /dev/null and b/amarok/src/images/icons/hi32-action-amarok_rewind.png differ diff --git a/amarok/src/images/icons/hi32-action-amarok_save.png b/amarok/src/images/icons/hi32-action-amarok_save.png new file mode 100644 index 00000000..0bb4a46f Binary files /dev/null and b/amarok/src/images/icons/hi32-action-amarok_save.png differ diff --git a/amarok/src/images/icons/hi32-action-amarok_scripts.png b/amarok/src/images/icons/hi32-action-amarok_scripts.png new file mode 100644 index 00000000..7743e728 Binary files /dev/null and b/amarok/src/images/icons/hi32-action-amarok_scripts.png differ diff --git a/amarok/src/images/icons/hi32-action-amarok_search.png b/amarok/src/images/icons/hi32-action-amarok_search.png new file mode 100644 index 00000000..b5641b4f Binary files /dev/null and b/amarok/src/images/icons/hi32-action-amarok_search.png differ diff --git a/amarok/src/images/icons/hi32-action-amarok_settings_engine.png b/amarok/src/images/icons/hi32-action-amarok_settings_engine.png new file mode 100644 index 00000000..9dd4c190 Binary files /dev/null and b/amarok/src/images/icons/hi32-action-amarok_settings_engine.png differ diff --git a/amarok/src/images/icons/hi32-action-amarok_settings_general.png b/amarok/src/images/icons/hi32-action-amarok_settings_general.png new file mode 100644 index 00000000..108c7df5 Binary files /dev/null and b/amarok/src/images/icons/hi32-action-amarok_settings_general.png differ diff --git a/amarok/src/images/icons/hi32-action-amarok_settings_indicator.png b/amarok/src/images/icons/hi32-action-amarok_settings_indicator.png new file mode 100644 index 00000000..e3f5442f Binary files /dev/null and b/amarok/src/images/icons/hi32-action-amarok_settings_indicator.png differ diff --git a/amarok/src/images/icons/hi32-action-amarok_settings_playback.png b/amarok/src/images/icons/hi32-action-amarok_settings_playback.png new file mode 100644 index 00000000..a62d1749 Binary files /dev/null and b/amarok/src/images/icons/hi32-action-amarok_settings_playback.png differ diff --git a/amarok/src/images/icons/hi32-action-amarok_settings_view.png b/amarok/src/images/icons/hi32-action-amarok_settings_view.png new file mode 100644 index 00000000..d59211b9 Binary files /dev/null and b/amarok/src/images/icons/hi32-action-amarok_settings_view.png differ diff --git a/amarok/src/images/icons/hi32-action-amarok_stop.png b/amarok/src/images/icons/hi32-action-amarok_stop.png new file mode 100644 index 00000000..e7b0521b Binary files /dev/null and b/amarok/src/images/icons/hi32-action-amarok_stop.png differ diff --git a/amarok/src/images/icons/hi32-action-amarok_track.png b/amarok/src/images/icons/hi32-action-amarok_track.png new file mode 100644 index 00000000..dff10f20 Binary files /dev/null and b/amarok/src/images/icons/hi32-action-amarok_track.png differ diff --git a/amarok/src/images/icons/hi32-action-amarok_undo.png b/amarok/src/images/icons/hi32-action-amarok_undo.png new file mode 100644 index 00000000..1355739f Binary files /dev/null and b/amarok/src/images/icons/hi32-action-amarok_undo.png differ diff --git a/amarok/src/images/icons/hi32-action-amarok_visualizations.png b/amarok/src/images/icons/hi32-action-amarok_visualizations.png new file mode 100644 index 00000000..d41d1df5 Binary files /dev/null and b/amarok/src/images/icons/hi32-action-amarok_visualizations.png differ diff --git a/amarok/src/images/icons/hi32-action-amarok_zoom.png b/amarok/src/images/icons/hi32-action-amarok_zoom.png new file mode 100644 index 00000000..02cb6028 Binary files /dev/null and b/amarok/src/images/icons/hi32-action-amarok_zoom.png differ diff --git a/amarok/src/images/icons/hi32-action-audioscrobbler.png b/amarok/src/images/icons/hi32-action-audioscrobbler.png new file mode 100644 index 00000000..7d1640f8 Binary files /dev/null and b/amarok/src/images/icons/hi32-action-audioscrobbler.png differ diff --git a/amarok/src/images/icons/hi32-action-collection.png b/amarok/src/images/icons/hi32-action-collection.png new file mode 100644 index 00000000..f9bd3164 Binary files /dev/null and b/amarok/src/images/icons/hi32-action-collection.png differ diff --git a/amarok/src/images/icons/hi48-action-amarok_add_lyrics.png b/amarok/src/images/icons/hi48-action-amarok_add_lyrics.png new file mode 100644 index 00000000..015649fe Binary files /dev/null and b/amarok/src/images/icons/hi48-action-amarok_add_lyrics.png differ diff --git a/amarok/src/images/icons/hi48-action-amarok_add_playlist.png b/amarok/src/images/icons/hi48-action-amarok_add_playlist.png new file mode 100644 index 00000000..7f91219c Binary files /dev/null and b/amarok/src/images/icons/hi48-action-amarok_add_playlist.png differ diff --git a/amarok/src/images/icons/hi48-action-amarok_album.png b/amarok/src/images/icons/hi48-action-amarok_album.png new file mode 100644 index 00000000..96b65d5c Binary files /dev/null and b/amarok/src/images/icons/hi48-action-amarok_album.png differ diff --git a/amarok/src/images/icons/hi48-action-amarok_artist.png b/amarok/src/images/icons/hi48-action-amarok_artist.png new file mode 100644 index 00000000..23d002b3 Binary files /dev/null and b/amarok/src/images/icons/hi48-action-amarok_artist.png differ diff --git a/amarok/src/images/icons/hi48-action-amarok_audioscrobbler.png b/amarok/src/images/icons/hi48-action-amarok_audioscrobbler.png new file mode 100644 index 00000000..d2f9fd2b Binary files /dev/null and b/amarok/src/images/icons/hi48-action-amarok_audioscrobbler.png differ diff --git a/amarok/src/images/icons/hi48-action-amarok_back.png b/amarok/src/images/icons/hi48-action-amarok_back.png new file mode 100644 index 00000000..40ebcc80 Binary files /dev/null and b/amarok/src/images/icons/hi48-action-amarok_back.png differ diff --git a/amarok/src/images/icons/hi48-action-amarok_burn.png b/amarok/src/images/icons/hi48-action-amarok_burn.png new file mode 100644 index 00000000..7ef6c0bb Binary files /dev/null and b/amarok/src/images/icons/hi48-action-amarok_burn.png differ diff --git a/amarok/src/images/icons/hi48-action-amarok_change_language.png b/amarok/src/images/icons/hi48-action-amarok_change_language.png new file mode 100644 index 00000000..05266e69 Binary files /dev/null and b/amarok/src/images/icons/hi48-action-amarok_change_language.png differ diff --git a/amarok/src/images/icons/hi48-action-amarok_circle.png b/amarok/src/images/icons/hi48-action-amarok_circle.png new file mode 100644 index 00000000..c25b03d8 Binary files /dev/null and b/amarok/src/images/icons/hi48-action-amarok_circle.png differ diff --git a/amarok/src/images/icons/hi48-action-amarok_clock.png b/amarok/src/images/icons/hi48-action-amarok_clock.png new file mode 100644 index 00000000..b3e4e3a6 Binary files /dev/null and b/amarok/src/images/icons/hi48-action-amarok_clock.png differ diff --git a/amarok/src/images/icons/hi48-action-amarok_collection.png b/amarok/src/images/icons/hi48-action-amarok_collection.png new file mode 100644 index 00000000..a84670a5 Binary files /dev/null and b/amarok/src/images/icons/hi48-action-amarok_collection.png differ diff --git a/amarok/src/images/icons/hi48-action-amarok_configure.png b/amarok/src/images/icons/hi48-action-amarok_configure.png new file mode 100644 index 00000000..73eeb451 Binary files /dev/null and b/amarok/src/images/icons/hi48-action-amarok_configure.png differ diff --git a/amarok/src/images/icons/hi48-action-amarok_covermanager.png b/amarok/src/images/icons/hi48-action-amarok_covermanager.png new file mode 100644 index 00000000..0a2b535d Binary files /dev/null and b/amarok/src/images/icons/hi48-action-amarok_covermanager.png differ diff --git a/amarok/src/images/icons/hi48-action-amarok_device.png b/amarok/src/images/icons/hi48-action-amarok_device.png new file mode 100644 index 00000000..e0fa6c02 Binary files /dev/null and b/amarok/src/images/icons/hi48-action-amarok_device.png differ diff --git a/amarok/src/images/icons/hi48-action-amarok_download.png b/amarok/src/images/icons/hi48-action-amarok_download.png new file mode 100644 index 00000000..b68d86e3 Binary files /dev/null and b/amarok/src/images/icons/hi48-action-amarok_download.png differ diff --git a/amarok/src/images/icons/hi48-action-amarok_dynamic.png b/amarok/src/images/icons/hi48-action-amarok_dynamic.png new file mode 100644 index 00000000..acd4d678 Binary files /dev/null and b/amarok/src/images/icons/hi48-action-amarok_dynamic.png differ diff --git a/amarok/src/images/icons/hi48-action-amarok_edit.png b/amarok/src/images/icons/hi48-action-amarok_edit.png new file mode 100644 index 00000000..a146d3e3 Binary files /dev/null and b/amarok/src/images/icons/hi48-action-amarok_edit.png differ diff --git a/amarok/src/images/icons/hi48-action-amarok_editcopy.png b/amarok/src/images/icons/hi48-action-amarok_editcopy.png new file mode 100644 index 00000000..1942b185 Binary files /dev/null and b/amarok/src/images/icons/hi48-action-amarok_editcopy.png differ diff --git a/amarok/src/images/icons/hi48-action-amarok_equalizer.png b/amarok/src/images/icons/hi48-action-amarok_equalizer.png new file mode 100644 index 00000000..db0f84a0 Binary files /dev/null and b/amarok/src/images/icons/hi48-action-amarok_equalizer.png differ diff --git a/amarok/src/images/icons/hi48-action-amarok_external.png b/amarok/src/images/icons/hi48-action-amarok_external.png new file mode 100644 index 00000000..37a247bc Binary files /dev/null and b/amarok/src/images/icons/hi48-action-amarok_external.png differ diff --git a/amarok/src/images/icons/hi48-action-amarok_fastforward.png b/amarok/src/images/icons/hi48-action-amarok_fastforward.png new file mode 100644 index 00000000..54743a47 Binary files /dev/null and b/amarok/src/images/icons/hi48-action-amarok_fastforward.png differ diff --git a/amarok/src/images/icons/hi48-action-amarok_favourite_genres.png b/amarok/src/images/icons/hi48-action-amarok_favourite_genres.png new file mode 100644 index 00000000..d1701084 Binary files /dev/null and b/amarok/src/images/icons/hi48-action-amarok_favourite_genres.png differ diff --git a/amarok/src/images/icons/hi48-action-amarok_files.png b/amarok/src/images/icons/hi48-action-amarok_files.png new file mode 100644 index 00000000..2b239a46 Binary files /dev/null and b/amarok/src/images/icons/hi48-action-amarok_files.png differ diff --git a/amarok/src/images/icons/hi48-action-amarok_files2.png b/amarok/src/images/icons/hi48-action-amarok_files2.png new file mode 100644 index 00000000..1f6f37bc Binary files /dev/null and b/amarok/src/images/icons/hi48-action-amarok_files2.png differ diff --git a/amarok/src/images/icons/hi48-action-amarok_info.png b/amarok/src/images/icons/hi48-action-amarok_info.png new file mode 100644 index 00000000..ef5f8143 Binary files /dev/null and b/amarok/src/images/icons/hi48-action-amarok_info.png differ diff --git a/amarok/src/images/icons/hi48-action-amarok_love.png b/amarok/src/images/icons/hi48-action-amarok_love.png new file mode 100644 index 00000000..b4d36293 Binary files /dev/null and b/amarok/src/images/icons/hi48-action-amarok_love.png differ diff --git a/amarok/src/images/icons/hi48-action-amarok_lyrics.png b/amarok/src/images/icons/hi48-action-amarok_lyrics.png new file mode 100644 index 00000000..ba3d3af3 Binary files /dev/null and b/amarok/src/images/icons/hi48-action-amarok_lyrics.png differ diff --git a/amarok/src/images/icons/hi48-action-amarok_magnatune.png b/amarok/src/images/icons/hi48-action-amarok_magnatune.png new file mode 100644 index 00000000..0ec5f7b3 Binary files /dev/null and b/amarok/src/images/icons/hi48-action-amarok_magnatune.png differ diff --git a/amarok/src/images/icons/hi48-action-amarok_mostplayed.png b/amarok/src/images/icons/hi48-action-amarok_mostplayed.png new file mode 100644 index 00000000..fa81e277 Binary files /dev/null and b/amarok/src/images/icons/hi48-action-amarok_mostplayed.png differ diff --git a/amarok/src/images/icons/hi48-action-amarok_music.png b/amarok/src/images/icons/hi48-action-amarok_music.png new file mode 100644 index 00000000..9d3150f6 Binary files /dev/null and b/amarok/src/images/icons/hi48-action-amarok_music.png differ diff --git a/amarok/src/images/icons/hi48-action-amarok_next.png b/amarok/src/images/icons/hi48-action-amarok_next.png new file mode 100644 index 00000000..d9279bf4 Binary files /dev/null and b/amarok/src/images/icons/hi48-action-amarok_next.png differ diff --git a/amarok/src/images/icons/hi48-action-amarok_pause.png b/amarok/src/images/icons/hi48-action-amarok_pause.png new file mode 100644 index 00000000..0e0bc1fa Binary files /dev/null and b/amarok/src/images/icons/hi48-action-amarok_pause.png differ diff --git a/amarok/src/images/icons/hi48-action-amarok_play.png b/amarok/src/images/icons/hi48-action-amarok_play.png new file mode 100644 index 00000000..798c4c61 Binary files /dev/null and b/amarok/src/images/icons/hi48-action-amarok_play.png differ diff --git a/amarok/src/images/icons/hi48-action-amarok_playlist.png b/amarok/src/images/icons/hi48-action-amarok_playlist.png new file mode 100644 index 00000000..34182cb5 Binary files /dev/null and b/amarok/src/images/icons/hi48-action-amarok_playlist.png differ diff --git a/amarok/src/images/icons/hi48-action-amarok_playlist_clear.png b/amarok/src/images/icons/hi48-action-amarok_playlist_clear.png new file mode 100644 index 00000000..5068d486 Binary files /dev/null and b/amarok/src/images/icons/hi48-action-amarok_playlist_clear.png differ diff --git a/amarok/src/images/icons/hi48-action-amarok_playlist_refresh.png b/amarok/src/images/icons/hi48-action-amarok_playlist_refresh.png new file mode 100644 index 00000000..6ea47734 Binary files /dev/null and b/amarok/src/images/icons/hi48-action-amarok_playlist_refresh.png differ diff --git a/amarok/src/images/icons/hi48-action-amarok_podcast.png b/amarok/src/images/icons/hi48-action-amarok_podcast.png new file mode 100644 index 00000000..b374ebc1 Binary files /dev/null and b/amarok/src/images/icons/hi48-action-amarok_podcast.png differ diff --git a/amarok/src/images/icons/hi48-action-amarok_podcast2.png b/amarok/src/images/icons/hi48-action-amarok_podcast2.png new file mode 100644 index 00000000..e5200f7d Binary files /dev/null and b/amarok/src/images/icons/hi48-action-amarok_podcast2.png differ diff --git a/amarok/src/images/icons/hi48-action-amarok_queue.png b/amarok/src/images/icons/hi48-action-amarok_queue.png new file mode 100644 index 00000000..ec42e16a Binary files /dev/null and b/amarok/src/images/icons/hi48-action-amarok_queue.png differ diff --git a/amarok/src/images/icons/hi48-action-amarok_random.png b/amarok/src/images/icons/hi48-action-amarok_random.png new file mode 100644 index 00000000..acd4d678 Binary files /dev/null and b/amarok/src/images/icons/hi48-action-amarok_random.png differ diff --git a/amarok/src/images/icons/hi48-action-amarok_random_album.png b/amarok/src/images/icons/hi48-action-amarok_random_album.png new file mode 100644 index 00000000..6b5737d5 Binary files /dev/null and b/amarok/src/images/icons/hi48-action-amarok_random_album.png differ diff --git a/amarok/src/images/icons/hi48-action-amarok_random_no.png b/amarok/src/images/icons/hi48-action-amarok_random_no.png new file mode 100644 index 00000000..bf6b0e54 Binary files /dev/null and b/amarok/src/images/icons/hi48-action-amarok_random_no.png differ diff --git a/amarok/src/images/icons/hi48-action-amarok_random_track.png b/amarok/src/images/icons/hi48-action-amarok_random_track.png new file mode 100644 index 00000000..04320cfa Binary files /dev/null and b/amarok/src/images/icons/hi48-action-amarok_random_track.png differ diff --git a/amarok/src/images/icons/hi48-action-amarok_redo.png b/amarok/src/images/icons/hi48-action-amarok_redo.png new file mode 100644 index 00000000..204c5d54 Binary files /dev/null and b/amarok/src/images/icons/hi48-action-amarok_redo.png differ diff --git a/amarok/src/images/icons/hi48-action-amarok_refresh.png b/amarok/src/images/icons/hi48-action-amarok_refresh.png new file mode 100644 index 00000000..73415941 Binary files /dev/null and b/amarok/src/images/icons/hi48-action-amarok_refresh.png differ diff --git a/amarok/src/images/icons/hi48-action-amarok_remove.png b/amarok/src/images/icons/hi48-action-amarok_remove.png new file mode 100644 index 00000000..90853e53 Binary files /dev/null and b/amarok/src/images/icons/hi48-action-amarok_remove.png differ diff --git a/amarok/src/images/icons/hi48-action-amarok_remove_from_playlist.png b/amarok/src/images/icons/hi48-action-amarok_remove_from_playlist.png new file mode 100644 index 00000000..4a4b14e6 Binary files /dev/null and b/amarok/src/images/icons/hi48-action-amarok_remove_from_playlist.png differ diff --git a/amarok/src/images/icons/hi48-action-amarok_repeat_album.png b/amarok/src/images/icons/hi48-action-amarok_repeat_album.png new file mode 100644 index 00000000..8f7fdc6b Binary files /dev/null and b/amarok/src/images/icons/hi48-action-amarok_repeat_album.png differ diff --git a/amarok/src/images/icons/hi48-action-amarok_repeat_no.png b/amarok/src/images/icons/hi48-action-amarok_repeat_no.png new file mode 100644 index 00000000..c06686cd Binary files /dev/null and b/amarok/src/images/icons/hi48-action-amarok_repeat_no.png differ diff --git a/amarok/src/images/icons/hi48-action-amarok_repeat_playlist.png b/amarok/src/images/icons/hi48-action-amarok_repeat_playlist.png new file mode 100644 index 00000000..bcbfda56 Binary files /dev/null and b/amarok/src/images/icons/hi48-action-amarok_repeat_playlist.png differ diff --git a/amarok/src/images/icons/hi48-action-amarok_repeat_track.png b/amarok/src/images/icons/hi48-action-amarok_repeat_track.png new file mode 100644 index 00000000..fcd96d54 Binary files /dev/null and b/amarok/src/images/icons/hi48-action-amarok_repeat_track.png differ diff --git a/amarok/src/images/icons/hi48-action-amarok_rescan.png b/amarok/src/images/icons/hi48-action-amarok_rescan.png new file mode 100644 index 00000000..50af5c29 Binary files /dev/null and b/amarok/src/images/icons/hi48-action-amarok_rescan.png differ diff --git a/amarok/src/images/icons/hi48-action-amarok_rewind.png b/amarok/src/images/icons/hi48-action-amarok_rewind.png new file mode 100644 index 00000000..5bfeb743 Binary files /dev/null and b/amarok/src/images/icons/hi48-action-amarok_rewind.png differ diff --git a/amarok/src/images/icons/hi48-action-amarok_save.png b/amarok/src/images/icons/hi48-action-amarok_save.png new file mode 100644 index 00000000..2346e6dd Binary files /dev/null and b/amarok/src/images/icons/hi48-action-amarok_save.png differ diff --git a/amarok/src/images/icons/hi48-action-amarok_scripts.png b/amarok/src/images/icons/hi48-action-amarok_scripts.png new file mode 100644 index 00000000..52e4ebaa Binary files /dev/null and b/amarok/src/images/icons/hi48-action-amarok_scripts.png differ diff --git a/amarok/src/images/icons/hi48-action-amarok_search.png b/amarok/src/images/icons/hi48-action-amarok_search.png new file mode 100644 index 00000000..22c09423 Binary files /dev/null and b/amarok/src/images/icons/hi48-action-amarok_search.png differ diff --git a/amarok/src/images/icons/hi48-action-amarok_settings_engine.png b/amarok/src/images/icons/hi48-action-amarok_settings_engine.png new file mode 100644 index 00000000..03aae460 Binary files /dev/null and b/amarok/src/images/icons/hi48-action-amarok_settings_engine.png differ diff --git a/amarok/src/images/icons/hi48-action-amarok_settings_general.png b/amarok/src/images/icons/hi48-action-amarok_settings_general.png new file mode 100644 index 00000000..19dd8f63 Binary files /dev/null and b/amarok/src/images/icons/hi48-action-amarok_settings_general.png differ diff --git a/amarok/src/images/icons/hi48-action-amarok_settings_indicator.png b/amarok/src/images/icons/hi48-action-amarok_settings_indicator.png new file mode 100644 index 00000000..9fe19dec Binary files /dev/null and b/amarok/src/images/icons/hi48-action-amarok_settings_indicator.png differ diff --git a/amarok/src/images/icons/hi48-action-amarok_settings_playback.png b/amarok/src/images/icons/hi48-action-amarok_settings_playback.png new file mode 100644 index 00000000..ccfbe02d Binary files /dev/null and b/amarok/src/images/icons/hi48-action-amarok_settings_playback.png differ diff --git a/amarok/src/images/icons/hi48-action-amarok_settings_view.png b/amarok/src/images/icons/hi48-action-amarok_settings_view.png new file mode 100644 index 00000000..70a96d69 Binary files /dev/null and b/amarok/src/images/icons/hi48-action-amarok_settings_view.png differ diff --git a/amarok/src/images/icons/hi48-action-amarok_stop.png b/amarok/src/images/icons/hi48-action-amarok_stop.png new file mode 100644 index 00000000..4f9257a3 Binary files /dev/null and b/amarok/src/images/icons/hi48-action-amarok_stop.png differ diff --git a/amarok/src/images/icons/hi48-action-amarok_track.png b/amarok/src/images/icons/hi48-action-amarok_track.png new file mode 100644 index 00000000..323a0ac0 Binary files /dev/null and b/amarok/src/images/icons/hi48-action-amarok_track.png differ diff --git a/amarok/src/images/icons/hi48-action-amarok_undo.png b/amarok/src/images/icons/hi48-action-amarok_undo.png new file mode 100644 index 00000000..e2ae6219 Binary files /dev/null and b/amarok/src/images/icons/hi48-action-amarok_undo.png differ diff --git a/amarok/src/images/icons/hi48-action-amarok_visualizations.png b/amarok/src/images/icons/hi48-action-amarok_visualizations.png new file mode 100644 index 00000000..30b70113 Binary files /dev/null and b/amarok/src/images/icons/hi48-action-amarok_visualizations.png differ diff --git a/amarok/src/images/icons/hi48-action-amarok_zoom.png b/amarok/src/images/icons/hi48-action-amarok_zoom.png new file mode 100644 index 00000000..82778156 Binary files /dev/null and b/amarok/src/images/icons/hi48-action-amarok_zoom.png differ diff --git a/amarok/src/images/icons/hi48-action-collection.png b/amarok/src/images/icons/hi48-action-collection.png new file mode 100644 index 00000000..c324dcb7 Binary files /dev/null and b/amarok/src/images/icons/hi48-action-collection.png differ diff --git a/amarok/src/images/icons/hi64-action-amarok_add_lyrics.png b/amarok/src/images/icons/hi64-action-amarok_add_lyrics.png new file mode 100644 index 00000000..4bbf25e2 Binary files /dev/null and b/amarok/src/images/icons/hi64-action-amarok_add_lyrics.png differ diff --git a/amarok/src/images/icons/hi64-action-amarok_add_playlist.png b/amarok/src/images/icons/hi64-action-amarok_add_playlist.png new file mode 100644 index 00000000..ea70f817 Binary files /dev/null and b/amarok/src/images/icons/hi64-action-amarok_add_playlist.png differ diff --git a/amarok/src/images/icons/hi64-action-amarok_album.png b/amarok/src/images/icons/hi64-action-amarok_album.png new file mode 100644 index 00000000..c0e64c2d Binary files /dev/null and b/amarok/src/images/icons/hi64-action-amarok_album.png differ diff --git a/amarok/src/images/icons/hi64-action-amarok_artist.png b/amarok/src/images/icons/hi64-action-amarok_artist.png new file mode 100644 index 00000000..268d26b7 Binary files /dev/null and b/amarok/src/images/icons/hi64-action-amarok_artist.png differ diff --git a/amarok/src/images/icons/hi64-action-amarok_audioscrobbler.png b/amarok/src/images/icons/hi64-action-amarok_audioscrobbler.png new file mode 100644 index 00000000..e4cab5c5 Binary files /dev/null and b/amarok/src/images/icons/hi64-action-amarok_audioscrobbler.png differ diff --git a/amarok/src/images/icons/hi64-action-amarok_back.png b/amarok/src/images/icons/hi64-action-amarok_back.png new file mode 100644 index 00000000..3284433b Binary files /dev/null and b/amarok/src/images/icons/hi64-action-amarok_back.png differ diff --git a/amarok/src/images/icons/hi64-action-amarok_burn.png b/amarok/src/images/icons/hi64-action-amarok_burn.png new file mode 100644 index 00000000..286a9c33 Binary files /dev/null and b/amarok/src/images/icons/hi64-action-amarok_burn.png differ diff --git a/amarok/src/images/icons/hi64-action-amarok_change_language.png b/amarok/src/images/icons/hi64-action-amarok_change_language.png new file mode 100644 index 00000000..c32a6a57 Binary files /dev/null and b/amarok/src/images/icons/hi64-action-amarok_change_language.png differ diff --git a/amarok/src/images/icons/hi64-action-amarok_circle.png b/amarok/src/images/icons/hi64-action-amarok_circle.png new file mode 100644 index 00000000..ed56765a Binary files /dev/null and b/amarok/src/images/icons/hi64-action-amarok_circle.png differ diff --git a/amarok/src/images/icons/hi64-action-amarok_clock.png b/amarok/src/images/icons/hi64-action-amarok_clock.png new file mode 100644 index 00000000..caa7b018 Binary files /dev/null and b/amarok/src/images/icons/hi64-action-amarok_clock.png differ diff --git a/amarok/src/images/icons/hi64-action-amarok_collection.png b/amarok/src/images/icons/hi64-action-amarok_collection.png new file mode 100644 index 00000000..9892f338 Binary files /dev/null and b/amarok/src/images/icons/hi64-action-amarok_collection.png differ diff --git a/amarok/src/images/icons/hi64-action-amarok_configure.png b/amarok/src/images/icons/hi64-action-amarok_configure.png new file mode 100644 index 00000000..27365f6f Binary files /dev/null and b/amarok/src/images/icons/hi64-action-amarok_configure.png differ diff --git a/amarok/src/images/icons/hi64-action-amarok_covermanager.png b/amarok/src/images/icons/hi64-action-amarok_covermanager.png new file mode 100644 index 00000000..1e8ea6b8 Binary files /dev/null and b/amarok/src/images/icons/hi64-action-amarok_covermanager.png differ diff --git a/amarok/src/images/icons/hi64-action-amarok_device.png b/amarok/src/images/icons/hi64-action-amarok_device.png new file mode 100644 index 00000000..6d380d02 Binary files /dev/null and b/amarok/src/images/icons/hi64-action-amarok_device.png differ diff --git a/amarok/src/images/icons/hi64-action-amarok_download.png b/amarok/src/images/icons/hi64-action-amarok_download.png new file mode 100644 index 00000000..856629b3 Binary files /dev/null and b/amarok/src/images/icons/hi64-action-amarok_download.png differ diff --git a/amarok/src/images/icons/hi64-action-amarok_dynamic.png b/amarok/src/images/icons/hi64-action-amarok_dynamic.png new file mode 100644 index 00000000..baba3f42 Binary files /dev/null and b/amarok/src/images/icons/hi64-action-amarok_dynamic.png differ diff --git a/amarok/src/images/icons/hi64-action-amarok_edit.png b/amarok/src/images/icons/hi64-action-amarok_edit.png new file mode 100644 index 00000000..eaa6d3d8 Binary files /dev/null and b/amarok/src/images/icons/hi64-action-amarok_edit.png differ diff --git a/amarok/src/images/icons/hi64-action-amarok_editcopy.png b/amarok/src/images/icons/hi64-action-amarok_editcopy.png new file mode 100644 index 00000000..d579795f Binary files /dev/null and b/amarok/src/images/icons/hi64-action-amarok_editcopy.png differ diff --git a/amarok/src/images/icons/hi64-action-amarok_equalizer.png b/amarok/src/images/icons/hi64-action-amarok_equalizer.png new file mode 100644 index 00000000..43f1bd50 Binary files /dev/null and b/amarok/src/images/icons/hi64-action-amarok_equalizer.png differ diff --git a/amarok/src/images/icons/hi64-action-amarok_external.png b/amarok/src/images/icons/hi64-action-amarok_external.png new file mode 100644 index 00000000..852517b2 Binary files /dev/null and b/amarok/src/images/icons/hi64-action-amarok_external.png differ diff --git a/amarok/src/images/icons/hi64-action-amarok_fastforward.png b/amarok/src/images/icons/hi64-action-amarok_fastforward.png new file mode 100644 index 00000000..47cfb16a Binary files /dev/null and b/amarok/src/images/icons/hi64-action-amarok_fastforward.png differ diff --git a/amarok/src/images/icons/hi64-action-amarok_favourite_genres.png b/amarok/src/images/icons/hi64-action-amarok_favourite_genres.png new file mode 100644 index 00000000..852105a8 Binary files /dev/null and b/amarok/src/images/icons/hi64-action-amarok_favourite_genres.png differ diff --git a/amarok/src/images/icons/hi64-action-amarok_files.png b/amarok/src/images/icons/hi64-action-amarok_files.png new file mode 100644 index 00000000..0fb20e0b Binary files /dev/null and b/amarok/src/images/icons/hi64-action-amarok_files.png differ diff --git a/amarok/src/images/icons/hi64-action-amarok_files2.png b/amarok/src/images/icons/hi64-action-amarok_files2.png new file mode 100644 index 00000000..3a501c70 Binary files /dev/null and b/amarok/src/images/icons/hi64-action-amarok_files2.png differ diff --git a/amarok/src/images/icons/hi64-action-amarok_info.png b/amarok/src/images/icons/hi64-action-amarok_info.png new file mode 100644 index 00000000..17fa77bb Binary files /dev/null and b/amarok/src/images/icons/hi64-action-amarok_info.png differ diff --git a/amarok/src/images/icons/hi64-action-amarok_love.png b/amarok/src/images/icons/hi64-action-amarok_love.png new file mode 100644 index 00000000..b3b6532a Binary files /dev/null and b/amarok/src/images/icons/hi64-action-amarok_love.png differ diff --git a/amarok/src/images/icons/hi64-action-amarok_lyrics.png b/amarok/src/images/icons/hi64-action-amarok_lyrics.png new file mode 100644 index 00000000..bfed12fe Binary files /dev/null and b/amarok/src/images/icons/hi64-action-amarok_lyrics.png differ diff --git a/amarok/src/images/icons/hi64-action-amarok_magnatune.png b/amarok/src/images/icons/hi64-action-amarok_magnatune.png new file mode 100644 index 00000000..4b8a2b56 Binary files /dev/null and b/amarok/src/images/icons/hi64-action-amarok_magnatune.png differ diff --git a/amarok/src/images/icons/hi64-action-amarok_mostplayed.png b/amarok/src/images/icons/hi64-action-amarok_mostplayed.png new file mode 100644 index 00000000..4c8b5ed8 Binary files /dev/null and b/amarok/src/images/icons/hi64-action-amarok_mostplayed.png differ diff --git a/amarok/src/images/icons/hi64-action-amarok_music.png b/amarok/src/images/icons/hi64-action-amarok_music.png new file mode 100644 index 00000000..513d597a Binary files /dev/null and b/amarok/src/images/icons/hi64-action-amarok_music.png differ diff --git a/amarok/src/images/icons/hi64-action-amarok_next.png b/amarok/src/images/icons/hi64-action-amarok_next.png new file mode 100644 index 00000000..f0d466d3 Binary files /dev/null and b/amarok/src/images/icons/hi64-action-amarok_next.png differ diff --git a/amarok/src/images/icons/hi64-action-amarok_pause.png b/amarok/src/images/icons/hi64-action-amarok_pause.png new file mode 100644 index 00000000..ec4a4580 Binary files /dev/null and b/amarok/src/images/icons/hi64-action-amarok_pause.png differ diff --git a/amarok/src/images/icons/hi64-action-amarok_play.png b/amarok/src/images/icons/hi64-action-amarok_play.png new file mode 100644 index 00000000..e5d06d1a Binary files /dev/null and b/amarok/src/images/icons/hi64-action-amarok_play.png differ diff --git a/amarok/src/images/icons/hi64-action-amarok_playlist.png b/amarok/src/images/icons/hi64-action-amarok_playlist.png new file mode 100644 index 00000000..d3812b82 Binary files /dev/null and b/amarok/src/images/icons/hi64-action-amarok_playlist.png differ diff --git a/amarok/src/images/icons/hi64-action-amarok_playlist_clear.png b/amarok/src/images/icons/hi64-action-amarok_playlist_clear.png new file mode 100644 index 00000000..fe34a0c1 Binary files /dev/null and b/amarok/src/images/icons/hi64-action-amarok_playlist_clear.png differ diff --git a/amarok/src/images/icons/hi64-action-amarok_playlist_refresh.png b/amarok/src/images/icons/hi64-action-amarok_playlist_refresh.png new file mode 100644 index 00000000..1fa53106 Binary files /dev/null and b/amarok/src/images/icons/hi64-action-amarok_playlist_refresh.png differ diff --git a/amarok/src/images/icons/hi64-action-amarok_podcast.png b/amarok/src/images/icons/hi64-action-amarok_podcast.png new file mode 100644 index 00000000..2515e692 Binary files /dev/null and b/amarok/src/images/icons/hi64-action-amarok_podcast.png differ diff --git a/amarok/src/images/icons/hi64-action-amarok_podcast2.png b/amarok/src/images/icons/hi64-action-amarok_podcast2.png new file mode 100644 index 00000000..c9698b9d Binary files /dev/null and b/amarok/src/images/icons/hi64-action-amarok_podcast2.png differ diff --git a/amarok/src/images/icons/hi64-action-amarok_queue.png b/amarok/src/images/icons/hi64-action-amarok_queue.png new file mode 100644 index 00000000..1769ec6e Binary files /dev/null and b/amarok/src/images/icons/hi64-action-amarok_queue.png differ diff --git a/amarok/src/images/icons/hi64-action-amarok_random.png b/amarok/src/images/icons/hi64-action-amarok_random.png new file mode 100644 index 00000000..baba3f42 Binary files /dev/null and b/amarok/src/images/icons/hi64-action-amarok_random.png differ diff --git a/amarok/src/images/icons/hi64-action-amarok_random_album.png b/amarok/src/images/icons/hi64-action-amarok_random_album.png new file mode 100644 index 00000000..d009e93a Binary files /dev/null and b/amarok/src/images/icons/hi64-action-amarok_random_album.png differ diff --git a/amarok/src/images/icons/hi64-action-amarok_random_no.png b/amarok/src/images/icons/hi64-action-amarok_random_no.png new file mode 100644 index 00000000..980ec34a Binary files /dev/null and b/amarok/src/images/icons/hi64-action-amarok_random_no.png differ diff --git a/amarok/src/images/icons/hi64-action-amarok_random_track.png b/amarok/src/images/icons/hi64-action-amarok_random_track.png new file mode 100644 index 00000000..2912d46d Binary files /dev/null and b/amarok/src/images/icons/hi64-action-amarok_random_track.png differ diff --git a/amarok/src/images/icons/hi64-action-amarok_redo.png b/amarok/src/images/icons/hi64-action-amarok_redo.png new file mode 100644 index 00000000..219f5177 Binary files /dev/null and b/amarok/src/images/icons/hi64-action-amarok_redo.png differ diff --git a/amarok/src/images/icons/hi64-action-amarok_refresh.png b/amarok/src/images/icons/hi64-action-amarok_refresh.png new file mode 100644 index 00000000..ca89e03f Binary files /dev/null and b/amarok/src/images/icons/hi64-action-amarok_refresh.png differ diff --git a/amarok/src/images/icons/hi64-action-amarok_remove.png b/amarok/src/images/icons/hi64-action-amarok_remove.png new file mode 100644 index 00000000..96699b56 Binary files /dev/null and b/amarok/src/images/icons/hi64-action-amarok_remove.png differ diff --git a/amarok/src/images/icons/hi64-action-amarok_remove_from_playlist.png b/amarok/src/images/icons/hi64-action-amarok_remove_from_playlist.png new file mode 100644 index 00000000..ca367470 Binary files /dev/null and b/amarok/src/images/icons/hi64-action-amarok_remove_from_playlist.png differ diff --git a/amarok/src/images/icons/hi64-action-amarok_repeat_album.png b/amarok/src/images/icons/hi64-action-amarok_repeat_album.png new file mode 100644 index 00000000..e29cbe52 Binary files /dev/null and b/amarok/src/images/icons/hi64-action-amarok_repeat_album.png differ diff --git a/amarok/src/images/icons/hi64-action-amarok_repeat_no.png b/amarok/src/images/icons/hi64-action-amarok_repeat_no.png new file mode 100644 index 00000000..4c9d125b Binary files /dev/null and b/amarok/src/images/icons/hi64-action-amarok_repeat_no.png differ diff --git a/amarok/src/images/icons/hi64-action-amarok_repeat_playlist.png b/amarok/src/images/icons/hi64-action-amarok_repeat_playlist.png new file mode 100644 index 00000000..8e9a74cc Binary files /dev/null and b/amarok/src/images/icons/hi64-action-amarok_repeat_playlist.png differ diff --git a/amarok/src/images/icons/hi64-action-amarok_repeat_track.png b/amarok/src/images/icons/hi64-action-amarok_repeat_track.png new file mode 100644 index 00000000..f6e9b450 Binary files /dev/null and b/amarok/src/images/icons/hi64-action-amarok_repeat_track.png differ diff --git a/amarok/src/images/icons/hi64-action-amarok_rescan.png b/amarok/src/images/icons/hi64-action-amarok_rescan.png new file mode 100644 index 00000000..0ccbfb53 Binary files /dev/null and b/amarok/src/images/icons/hi64-action-amarok_rescan.png differ diff --git a/amarok/src/images/icons/hi64-action-amarok_rewind.png b/amarok/src/images/icons/hi64-action-amarok_rewind.png new file mode 100644 index 00000000..87a1e4bf Binary files /dev/null and b/amarok/src/images/icons/hi64-action-amarok_rewind.png differ diff --git a/amarok/src/images/icons/hi64-action-amarok_save.png b/amarok/src/images/icons/hi64-action-amarok_save.png new file mode 100644 index 00000000..18b28521 Binary files /dev/null and b/amarok/src/images/icons/hi64-action-amarok_save.png differ diff --git a/amarok/src/images/icons/hi64-action-amarok_scripts.png b/amarok/src/images/icons/hi64-action-amarok_scripts.png new file mode 100644 index 00000000..59b43ec1 Binary files /dev/null and b/amarok/src/images/icons/hi64-action-amarok_scripts.png differ diff --git a/amarok/src/images/icons/hi64-action-amarok_search.png b/amarok/src/images/icons/hi64-action-amarok_search.png new file mode 100644 index 00000000..5520f5ec Binary files /dev/null and b/amarok/src/images/icons/hi64-action-amarok_search.png differ diff --git a/amarok/src/images/icons/hi64-action-amarok_settings_engine.png b/amarok/src/images/icons/hi64-action-amarok_settings_engine.png new file mode 100644 index 00000000..232a612c Binary files /dev/null and b/amarok/src/images/icons/hi64-action-amarok_settings_engine.png differ diff --git a/amarok/src/images/icons/hi64-action-amarok_settings_general.png b/amarok/src/images/icons/hi64-action-amarok_settings_general.png new file mode 100644 index 00000000..eed6c1d5 Binary files /dev/null and b/amarok/src/images/icons/hi64-action-amarok_settings_general.png differ diff --git a/amarok/src/images/icons/hi64-action-amarok_settings_indicator.png b/amarok/src/images/icons/hi64-action-amarok_settings_indicator.png new file mode 100644 index 00000000..3fe0e0f1 Binary files /dev/null and b/amarok/src/images/icons/hi64-action-amarok_settings_indicator.png differ diff --git a/amarok/src/images/icons/hi64-action-amarok_settings_playback.png b/amarok/src/images/icons/hi64-action-amarok_settings_playback.png new file mode 100644 index 00000000..70eb09e6 Binary files /dev/null and b/amarok/src/images/icons/hi64-action-amarok_settings_playback.png differ diff --git a/amarok/src/images/icons/hi64-action-amarok_settings_view.png b/amarok/src/images/icons/hi64-action-amarok_settings_view.png new file mode 100644 index 00000000..ffa58a4b Binary files /dev/null and b/amarok/src/images/icons/hi64-action-amarok_settings_view.png differ diff --git a/amarok/src/images/icons/hi64-action-amarok_stop.png b/amarok/src/images/icons/hi64-action-amarok_stop.png new file mode 100644 index 00000000..4e56b160 Binary files /dev/null and b/amarok/src/images/icons/hi64-action-amarok_stop.png differ diff --git a/amarok/src/images/icons/hi64-action-amarok_track.png b/amarok/src/images/icons/hi64-action-amarok_track.png new file mode 100644 index 00000000..00da49e9 Binary files /dev/null and b/amarok/src/images/icons/hi64-action-amarok_track.png differ diff --git a/amarok/src/images/icons/hi64-action-amarok_undo.png b/amarok/src/images/icons/hi64-action-amarok_undo.png new file mode 100644 index 00000000..33c30eb3 Binary files /dev/null and b/amarok/src/images/icons/hi64-action-amarok_undo.png differ diff --git a/amarok/src/images/icons/hi64-action-amarok_visualizations.png b/amarok/src/images/icons/hi64-action-amarok_visualizations.png new file mode 100644 index 00000000..80e3f806 Binary files /dev/null and b/amarok/src/images/icons/hi64-action-amarok_visualizations.png differ diff --git a/amarok/src/images/icons/hi64-action-amarok_zoom.png b/amarok/src/images/icons/hi64-action-amarok_zoom.png new file mode 100644 index 00000000..30b760c8 Binary files /dev/null and b/amarok/src/images/icons/hi64-action-amarok_zoom.png differ diff --git a/amarok/src/images/icons/hi64-action-collection.png b/amarok/src/images/icons/hi64-action-collection.png new file mode 100644 index 00000000..cc6d5f46 Binary files /dev/null and b/amarok/src/images/icons/hi64-action-collection.png differ diff --git a/amarok/src/images/icons/svg/crsc-action-dynamic.svg b/amarok/src/images/icons/svg/crsc-action-dynamic.svg new file mode 100644 index 00000000..f2a2816e --- /dev/null +++ b/amarok/src/images/icons/svg/crsc-action-dynamic.svg @@ -0,0 +1,337 @@ +<?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://web.resource.org/cc/" + 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://inkscape.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + sodipodi:docname="crsc-action-dynamic.svg" + sodipodi:docbase="/usr/src/packages/BUILD/svn-amaroK/multimedia/amarok/src/images/icons" + inkscape:version="0.41" + sodipodi:version="0.32" + id="svg2" + height="149.50000pt" + width="149.50000pt"> + <defs + id="defs4"> + <linearGradient + id="linearGradient2235"> + <stop + style="stop-color:#000000;stop-opacity:0.67058825;" + offset="0.0000000" + id="stop2237" /> + <stop + style="stop-color:#909090;stop-opacity:0.81176472;" + offset="1.0000000" + id="stop2239" /> + </linearGradient> + <linearGradient + id="linearGradient2066"> + <stop + id="stop2068" + offset="0.0000000" + style="stop-color:#ffffff;stop-opacity:0.67006803;" /> + <stop + id="stop2070" + offset="1.0000000" + style="stop-color:#ffffff;stop-opacity:0.0000000;" /> + </linearGradient> + <linearGradient + y2="288.31531" + x2="32.924732" + y1="290.43964" + x1="-47.420975" + gradientTransform="matrix(-0.739464,0.316880,0.000000,0.782730,218.3584,157.8226)" + gradientUnits="userSpaceOnUse" + id="linearGradient2076" + xlink:href="#linearGradient2066" + inkscape:collect="always" /> + <linearGradient + gradientTransform="matrix(0.738093,0.000000,0.000000,0.708171,155.7329,140.8991)" + gradientUnits="userSpaceOnUse" + y2="267.50659" + x2="38.637398" + y1="220.60422" + x1="34.425518" + id="linearGradient2086" + xlink:href="#linearGradient2066" + inkscape:collect="always" /> + <linearGradient + y2="233.44221" + x2="-65.644867" + y1="312.57361" + x1="8.1585665" + gradientTransform="matrix(0.780224,0.331014,0.000000,0.817639,133.5725,134.5599)" + gradientUnits="userSpaceOnUse" + id="linearGradient2176" + xlink:href="#linearGradient2235" + inkscape:collect="always" /> + <linearGradient + y2="294.50870" + x2="32.924515" + y1="294.50870" + x1="-51.704765" + gradientTransform="matrix(0.222323,0.107080,-1.389416e-3,0.249686,-301.8608,-104.0289)" + gradientUnits="userSpaceOnUse" + id="linearGradient2227" + xlink:href="#linearGradient2066" + inkscape:collect="always" /> + <linearGradient + y2="294.50870" + x2="32.924515" + y1="294.50870" + x1="-51.704765" + gradientTransform="matrix(-0.222323,0.107080,1.389416e-3,0.249686,-404.2072,-319.7911)" + gradientUnits="userSpaceOnUse" + id="linearGradient2233" + xlink:href="#linearGradient2066" + inkscape:collect="always" /> + <linearGradient + y2="291.24316" + x2="28.564842" + y1="303.01590" + x1="-52.635437" + gradientTransform="matrix(0.739464,0.316880,0.000000,0.782730,145.1483,158.3725)" + gradientUnits="userSpaceOnUse" + id="linearGradient2243" + xlink:href="#linearGradient2066" + inkscape:collect="always" /> + <linearGradient + y2="353.22800" + x2="-42.572544" + y1="249.01021" + x1="52.432487" + gradientTransform="matrix(-0.780224,0.331014,0.000000,0.817639,229.5875,131.9624)" + gradientUnits="userSpaceOnUse" + id="linearGradient2247" + xlink:href="#linearGradient2235" + inkscape:collect="always" /> + <linearGradient + y2="363.85355" + x2="27.436520" + y1="335.26694" + x1="-43.497032" + gradientTransform="matrix(-0.625015,0.548080,-0.807895,-0.312233,449.6759,426.2777)" + gradientUnits="userSpaceOnUse" + id="linearGradient2251" + xlink:href="#linearGradient2235" + inkscape:collect="always" /> + <linearGradient + y2="353.22800" + x2="-42.572544" + y1="249.01021" + x1="52.432487" + gradientTransform="matrix(-0.780224,0.331014,0.000000,0.817639,229.5875,131.9624)" + gradientUnits="userSpaceOnUse" + id="linearGradient1345" + xlink:href="#linearGradient2235" + inkscape:collect="always" /> + <linearGradient + y2="233.44221" + x2="-65.644867" + y1="312.57361" + x1="8.1585665" + gradientTransform="matrix(0.780224,0.331014,0.000000,0.817639,133.5725,134.5599)" + gradientUnits="userSpaceOnUse" + id="linearGradient1347" + xlink:href="#linearGradient2235" + inkscape:collect="always" /> + <linearGradient + y2="363.85355" + x2="27.436520" + y1="335.26694" + x1="-43.497032" + gradientTransform="matrix(-0.625015,0.548080,-0.807895,-0.312233,449.6759,426.2777)" + gradientUnits="userSpaceOnUse" + id="linearGradient1349" + xlink:href="#linearGradient2235" + inkscape:collect="always" /> + <linearGradient + y2="288.31531" + x2="32.924732" + y1="290.43964" + x1="-47.420975" + gradientTransform="matrix(-0.739464,0.316880,0.000000,0.782730,218.3584,157.8226)" + gradientUnits="userSpaceOnUse" + id="linearGradient1351" + xlink:href="#linearGradient2066" + inkscape:collect="always" /> + <linearGradient + y2="267.50659" + x2="38.637398" + y1="220.60422" + x1="34.425518" + gradientTransform="matrix(0.738093,0.000000,0.000000,0.708171,155.7329,140.8991)" + gradientUnits="userSpaceOnUse" + id="linearGradient1353" + xlink:href="#linearGradient2066" + inkscape:collect="always" /> + <linearGradient + y2="291.24316" + x2="28.564842" + y1="303.01590" + x1="-52.635437" + gradientTransform="matrix(0.739464,0.316880,0.000000,0.782730,145.1483,158.3725)" + gradientUnits="userSpaceOnUse" + id="linearGradient1355" + xlink:href="#linearGradient2066" + inkscape:collect="always" /> + </defs> + <sodipodi:namedview + inkscape:window-y="0" + inkscape:window-x="0" + inkscape:window-height="955" + inkscape:window-width="1272" + inkscape:current-layer="layer1" + inkscape:document-units="px" + inkscape:cy="126.85380" + inkscape:cx="44.046356" + inkscape:zoom="1.9798990" + inkscape:pageshadow="2" + inkscape:pageopacity="0.0" + borderopacity="1.0" + bordercolor="#666666" + pagecolor="#ffffff" + id="base" /> + <metadata + id="metadata7"> + <rdf:RDF + id="RDF20"> + <cc:Work + id="Work22" + rdf:about=""> + <dc:format + id="format24">image/svg+xml</dc:format> + <dc:type + id="type26" + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + </cc:Work> + </rdf:RDF> + </metadata> + <g + id="layer1" + inkscape:groupmode="layer" + inkscape:label="Layer 1"> + <g + transform="translate(-86.96153,-280.4952)" + id="g1328"> + <path + inkscape:export-ydpi="30.750000" + inkscape:export-xdpi="30.750000" + inkscape:export-filename="/home/kant/Desktop/party/dynamic_64x64.png" + sodipodi:nodetypes="ccccccc" + d="M 268.90571,416.16686 L 180.65475,462.93062 L 94.471014,418.07404 L 90.202237,315.79374 L 181.62776,283.58477 L 270.83501,313.39183 L 268.90571,416.16686 z " + style="color:#000000;fill:#e2e2e2;fill-opacity:1.0000000;fill-rule:evenodd;stroke:none;stroke-width:6.1589108;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-dashoffset:0.0000000;stroke-opacity:1.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:block;overflow:visible" + id="path2088" /> + <path + inkscape:export-ydpi="30.750000" + inkscape:export-xdpi="30.750000" + inkscape:export-filename="/home/kant/Desktop/party/dynamic_64x64.png" + sodipodi:nodetypes="ccccc" + id="path2245" + d="M 273.36929,313.13698 L 182.56931,343.95690 L 182.08277,462.07264 L 267.86484,418.52384 L 273.36929,313.13698 z " + style="color:#000000;fill:url(#linearGradient1345);fill-opacity:1.0000000;fill-rule:evenodd;stroke:none;stroke-width:2.8598378;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-dashoffset:0.0000000;stroke-opacity:1.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:block;overflow:visible" /> + <path + inkscape:export-ydpi="30.750000" + inkscape:export-xdpi="30.750000" + inkscape:export-filename="/home/kant/Desktop/party/dynamic_64x64.png" + sodipodi:nodetypes="ccccc" + id="rect2064" + d="M 89.790761,315.73442 L 180.59073,346.55434 L 181.07726,464.67007 L 95.295205,421.12130 L 89.790761,315.73442 z " + style="color:#000000;fill:url(#linearGradient1347);fill-opacity:1.0000000;fill-rule:evenodd;stroke:none;stroke-width:2.8598378;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-dashoffset:0.0000000;stroke-opacity:1.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:block;overflow:visible" /> + <path + inkscape:export-ydpi="30.750000" + inkscape:export-xdpi="30.750000" + inkscape:export-filename="/home/kant/Desktop/party/dynamic_64x64.png" + sodipodi:nodetypes="ccccc" + id="path2249" + d="M 270.86577,311.52109 L 179.59306,346.06714 L 94.349654,312.96717 L 181.14610,283.75830 L 270.86577,311.52109 z " + style="color:#000000;fill:url(#linearGradient1349);fill-opacity:1.0000000;fill-rule:evenodd;stroke:none;stroke-width:2.8598378;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-dashoffset:0.0000000;stroke-opacity:1.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:block;overflow:visible" /> + <g + inkscape:export-ydpi="30.750000" + inkscape:export-xdpi="30.750000" + inkscape:export-filename="/home/kant/Desktop/party/dynamic_64x64.png" + transform="matrix(0.939083,0.000000,0.000000,0.901014,5.302929,46.26191)" + style="stroke:#373737;stroke-width:6.2828116;stroke-linecap:round;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000" + id="g2132"> + <path + id="rect1306" + style="color:#000000;fill:none;fill-opacity:1.0000000;fill-rule:evenodd;stroke-linejoin:round;stroke-dashoffset:0.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:block;overflow:visible" + d="M 188.28832,263.17340 L 283.21668,295.13291 L 187.25214,332.48982 L 90.277426,297.15322 L 188.28832,263.17340 z M 277.43780,410.40630 L 186.21600,464.24258 L 95.328185,412.33995" + sodipodi:nodetypes="cccccccc" /> + <path + sodipodi:nodetypes="cc" + style="fill:none;fill-opacity:0.75000000;fill-rule:evenodd;stroke-linejoin:miter" + d="M 90.032749,297.15297 L 95.083512,411.64790" + id="path2049" /> + <path + sodipodi:nodetypes="cc" + style="fill:none;fill-opacity:0.75000000;fill-rule:evenodd;stroke-linejoin:miter" + d="M 187.38330,332.31992 L 187.38330,462.32903" + id="path2051" /> + <path + sodipodi:nodetypes="cc" + style="fill:none;fill-opacity:0.75000000;fill-rule:evenodd;stroke-linejoin:miter" + d="M 283.37382,295.35693 L 277.72053,408.30072" + id="path2053" /> + </g> + <path + inkscape:export-ydpi="30.750000" + inkscape:export-xdpi="30.750000" + inkscape:export-filename="/home/kant/Desktop/party/dynamic_64x64.png" + sodipodi:nodetypes="ccccc" + id="rect2055" + d="M 99.588583,327.59982 L 172.61055,352.03387 L 172.61056,451.81590 L 102.23292,413.82090 L 99.588583,327.59982 z " + style="color:#000000;fill:#ff3a3a;fill-opacity:1.0000000;fill-rule:evenodd;stroke:none;stroke-width:1.8075000;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-dashoffset:0.0000000;stroke-opacity:1.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:block;overflow:visible" /> + <path + inkscape:export-ydpi="30.750000" + inkscape:export-xdpi="30.750000" + inkscape:export-filename="/home/kant/Desktop/party/dynamic_64x64.png" + sodipodi:nodetypes="ccccc" + id="rect2057" + d="M 263.04877,323.47185 L 188.99903,352.67760 L 189.48556,450.26231 L 258.92043,411.16859 L 263.04877,323.47185 z " + style="color:#000000;fill:#04bc00;fill-opacity:1.0000000;fill-rule:evenodd;stroke:none;stroke-width:2.7851450;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-dashoffset:0.0000000;stroke-opacity:1.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:block;overflow:visible" /> + <path + inkscape:export-ydpi="30.750000" + inkscape:export-xdpi="30.750000" + inkscape:export-filename="/home/kant/Desktop/party/dynamic_64x64.png" + sodipodi:nodetypes="ccccc" + id="rect2061" + d="M 251.26344,312.23112 L 181.53129,290.12270 L 110.07036,314.05627 L 181.27224,337.76419 L 251.26344,312.23112 z " + style="color:#000000;fill:#d9d900;fill-opacity:1.0000000;fill-rule:evenodd;stroke:none;stroke-width:2.9530923;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4.0000000;stroke-dashoffset:0.0000000;stroke-opacity:1.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:block;overflow:visible" /> + <path + inkscape:export-ydpi="30.750000" + inkscape:export-xdpi="30.750000" + inkscape:export-filename="/home/kant/Desktop/party/dynamic_64x64.png" + sodipodi:nodetypes="ccccc" + id="rect2074" + d="M 257.76701,331.36820 L 194.01178,356.71007 L 194.01178,441.90975 L 254.44478,408.68877 L 257.76701,331.36820 z " + style="color:#000000;fill:url(#linearGradient1351);fill-opacity:1.0000000;fill-rule:evenodd;stroke:none;stroke-width:2.8598378;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-dashoffset:0.0000000;stroke-opacity:1.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:block;overflow:visible" /> + <path + inkscape:export-ydpi="30.750000" + inkscape:export-xdpi="30.750000" + inkscape:export-filename="/home/kant/Desktop/party/dynamic_64x64.png" + sodipodi:nodetypes="ccccc" + id="path2078" + d="M 234.58899,313.13015 L 181.08427,295.62565 L 125.54761,313.69073 L 181.25451,331.08479 L 234.58899,313.13015 z " + style="color:#000000;fill:url(#linearGradient1353);fill-opacity:1.0000000;fill-rule:evenodd;stroke:none;stroke-width:2.7346835;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-dashoffset:0.0000000;stroke-opacity:1.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:block;overflow:visible" /> + <path + inkscape:export-ydpi="30.750000" + inkscape:export-xdpi="30.750000" + inkscape:export-filename="/home/kant/Desktop/party/dynamic_64x64.png" + sodipodi:nodetypes="ccccc" + id="path2241" + d="M 104.28014,333.92682 L 168.80691,355.32911 L 168.80691,445.03386 L 106.22626,411.62436 L 104.28014,333.92682 z " + style="color:#000000;fill:url(#linearGradient1355);fill-opacity:1.0000000;fill-rule:evenodd;stroke:none;stroke-width:2.8598378;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-dashoffset:0.0000000;stroke-opacity:1.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:block;overflow:visible" /> + </g> + <path + sodipodi:nodetypes="ccccc" + id="path2231" + d="M -391.78207,-264.20132 L -411.18537,-254.85589 L -411.04894,-230.33824 L -392.23530,-239.66782 L -391.78207,-264.20132 z " + style="color:#000000;fill:url(#linearGradient2233);fill-opacity:1.0000000;fill-rule:evenodd;stroke:none;stroke-width:2.8598378;stroke-linecap:round;stroke-linejoin:round;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4.0000000;stroke-dashoffset:0.0000000;stroke-opacity:1.0000000;visibility:visible;display:block;overflow:visible" /> + </g> +</svg> diff --git a/amarok/src/images/icons/svg/crsc-action-player_playlist_2.svg b/amarok/src/images/icons/svg/crsc-action-player_playlist_2.svg new file mode 100644 index 00000000..54ec121c --- /dev/null +++ b/amarok/src/images/icons/svg/crsc-action-player_playlist_2.svg @@ -0,0 +1,426 @@ +<?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://web.resource.org/cc/" + 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://inkscape.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + sodipodi:docname="playlist.svg" + sodipodi:docbase="/home/me/Documents/updated_collection_playlist_icons/playlist_icon" + inkscape:version="0.41" + sodipodi:version="0.32" + id="svg2" + height="437.00000px" + width="434.00000px"> + <defs + id="defs4"> + <linearGradient + id="linearGradient2222"> + <stop + style="stop-color:#ffffff;stop-opacity:1.0000000;" + offset="0.0000000" + id="stop2224" /> + <stop + style="stop-color:#ffffff;stop-opacity:0.0000000;" + offset="1.0000000" + id="stop2226" /> + </linearGradient> + <linearGradient + id="linearGradient2214"> + <stop + id="stop2216" + offset="0.0000000" + style="stop-color:#ffffff;stop-opacity:1.0000000;" /> + <stop + id="stop2218" + offset="1.0000000" + style="stop-color:#ffffff;stop-opacity:0.0000000;" /> + </linearGradient> + <linearGradient + id="linearGradient2204"> + <stop + style="stop-color:#ffffff;stop-opacity:1.0000000;" + offset="0.0000000" + id="stop2206" /> + <stop + style="stop-color:#ffffff;stop-opacity:0.0000000;" + offset="1.0000000" + id="stop2208" /> + </linearGradient> + <linearGradient + id="linearGradient2182"> + <stop + style="stop-color:#83aef7;stop-opacity:1.0000000;" + offset="0.0000000" + id="stop2184" /> + <stop + style="stop-color:#378cdd;stop-opacity:1.0000000;" + offset="1.0000000" + id="stop2186" /> + </linearGradient> + <linearGradient + id="linearGradient2172"> + <stop + id="stop2174" + offset="0.0000000" + style="stop-color:#000000;stop-opacity:0.28571430;" /> + <stop + id="stop2176" + offset="1.0000000" + style="stop-color:#4c68de;stop-opacity:0.0000000;" /> + </linearGradient> + <linearGradient + id="linearGradient2142"> + <stop + style="stop-color:#ffffff;stop-opacity:0.0000000;" + offset="0.0000000" + id="stop2144" /> + <stop + style="stop-color:#ffffff;stop-opacity:1.0000000;" + offset="1.0000000" + id="stop2146" /> + </linearGradient> + <linearGradient + id="linearGradient2116"> + <stop + id="stop2118" + offset="0.0000000" + style="stop-color:#c3d5fa;stop-opacity:1.0000000;" /> + <stop + id="stop2120" + offset="1.0000000" + style="stop-color:#a9c6ff;stop-opacity:0.0000000;" /> + </linearGradient> + <linearGradient + id="linearGradient2080"> + <stop + style="stop-color:none" + offset="0" + id="stop2082" /> + <stop + style="stop-color:#000274;stop-opacity:1.0000000;" + offset="1.0000000" + id="stop2084" /> + </linearGradient> + <linearGradient + id="linearGradient2050"> + <stop + id="stop2052" + offset="0.0000000" + style="stop-color:#2ba3cf;stop-opacity:1.0000000;" /> + <stop + id="stop2054" + offset="1.0000000" + style="stop-color:#245aad;stop-opacity:1.0000000;" /> + </linearGradient> + <radialGradient + gradientTransform="matrix(1.473629,-0.169147,8.785089e-2,0.908723,44.91220,7.928287)" + gradientUnits="userSpaceOnUse" + r="42.426407" + fy="298.65619" + fx="-128.01088" + cy="298.65619" + cx="-128.01088" + id="radialGradient2122" + xlink:href="#linearGradient2116" + inkscape:collect="always" /> + <linearGradient + gradientTransform="matrix(1.158721,0.000000,0.000000,1.000000,-16.77765,0.000000)" + gradientUnits="userSpaceOnUse" + y2="609.94598" + x2="449.80826" + y1="290.73776" + x1="133.74213" + id="linearGradient2130" + xlink:href="#linearGradient2182" + inkscape:collect="always" /> + <linearGradient + gradientTransform="matrix(1.076431,0.000000,0.000000,1.076431,-3.720561,-28.40424)" + gradientUnits="userSpaceOnUse" + y2="319.11700" + x2="270.21143" + y1="221.29817" + x1="254.63544" + id="linearGradient2168" + xlink:href="#linearGradient2222" + inkscape:collect="always" /> + <linearGradient + gradientTransform="matrix(1.158721,0.000000,0.000000,0.759283,-21.74361,66.14395)" + gradientUnits="userSpaceOnUse" + y2="318.79074" + x2="446.58481" + y1="318.79074" + x1="120.55804" + id="linearGradient2178" + xlink:href="#linearGradient2172" + inkscape:collect="always" /> + <linearGradient + spreadMethod="reflect" + y2="339.19901" + x2="-56.090500" + y1="507.59042" + x1="-32.221645" + gradientTransform="matrix(1.268065,-0.166342,0.166342,1.268065,285.4668,-44.58162)" + gradientUnits="userSpaceOnUse" + id="linearGradient2198" + xlink:href="#linearGradient2050" + inkscape:collect="always" /> + <linearGradient + gradientUnits="userSpaceOnUse" + y2="234.66333" + x2="-113.74761" + y1="312.81665" + x1="-117.00233" + id="linearGradient2210" + xlink:href="#linearGradient2204" + inkscape:collect="always" /> + <linearGradient + gradientTransform="translate(2.020305,4.040610)" + gradientUnits="userSpaceOnUse" + y2="413.22253" + x2="283.05798" + y1="396.07968" + x1="335.40170" + id="linearGradient2220" + xlink:href="#linearGradient2214" + inkscape:collect="always" /> + <linearGradient + y2="609.94598" + x2="449.80826" + y1="290.73776" + x1="133.74213" + gradientTransform="matrix(1.158721,0.000000,0.000000,1.000000,468.9366,265.7143)" + gradientUnits="userSpaceOnUse" + id="linearGradient2194" + xlink:href="#linearGradient2182" + inkscape:collect="always" /> + <linearGradient + y2="318.79074" + x2="446.58481" + y1="318.79074" + x1="120.55804" + gradientTransform="matrix(1.158721,0.000000,0.000000,0.759283,-21.74361,66.14395)" + gradientUnits="userSpaceOnUse" + id="linearGradient1357" + xlink:href="#linearGradient2172" + inkscape:collect="always" /> + <linearGradient + y2="609.94598" + x2="449.80826" + y1="290.73776" + x1="133.74213" + gradientTransform="matrix(1.158721,0.000000,0.000000,1.000000,-16.77765,0.000000)" + gradientUnits="userSpaceOnUse" + id="linearGradient1359" + xlink:href="#linearGradient2182" + inkscape:collect="always" /> + <linearGradient + y2="339.19901" + x2="-56.090500" + y1="507.59042" + x1="-32.221645" + spreadMethod="reflect" + gradientTransform="matrix(1.268065,-0.166342,0.166342,1.268065,285.4668,-44.58162)" + gradientUnits="userSpaceOnUse" + id="linearGradient1361" + xlink:href="#linearGradient2050" + inkscape:collect="always" /> + <radialGradient + r="42.426407" + fy="298.65619" + fx="-128.01088" + cy="298.65619" + cx="-128.01088" + gradientTransform="matrix(1.473629,-0.169147,8.785089e-2,0.908723,44.91220,7.928287)" + gradientUnits="userSpaceOnUse" + id="radialGradient1363" + xlink:href="#linearGradient2116" + inkscape:collect="always" /> + <linearGradient + y2="319.11700" + x2="270.21143" + y1="221.29817" + x1="254.63544" + gradientTransform="matrix(1.076431,0.000000,0.000000,1.076431,-3.720561,-28.40424)" + gradientUnits="userSpaceOnUse" + id="linearGradient1365" + xlink:href="#linearGradient2222" + inkscape:collect="always" /> + <linearGradient + y2="234.66333" + x2="-113.74761" + y1="312.81665" + x1="-117.00233" + gradientUnits="userSpaceOnUse" + id="linearGradient1367" + xlink:href="#linearGradient2204" + inkscape:collect="always" /> + <linearGradient + y2="413.22253" + x2="283.05798" + y1="396.07968" + x1="335.40170" + gradientTransform="translate(2.020305,4.040610)" + gradientUnits="userSpaceOnUse" + id="linearGradient1369" + xlink:href="#linearGradient2214" + inkscape:collect="always" /> + </defs> + <sodipodi:namedview + inkscape:window-y="0" + inkscape:window-x="3" + inkscape:window-height="957" + inkscape:window-width="1272" + inkscape:current-layer="layer1" + inkscape:document-units="px" + inkscape:cy="213.23890" + inkscape:cx="409.73403" + inkscape:zoom="1.1203939" + inkscape:pageshadow="2" + inkscape:pageopacity="0.0" + borderopacity="1.0" + bordercolor="#666666" + pagecolor="#ffffff" + id="base" /> + <metadata + id="metadata7"> + <rdf:RDF + id="RDF41"> + <cc:Work + id="Work43" + rdf:about=""> + <dc:format + id="format45">image/svg+xml</dc:format> + <dc:type + id="type47" + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + </cc:Work> + </rdf:RDF> + </metadata> + <g + id="layer1" + inkscape:groupmode="layer" + inkscape:label="Calque 1"> + <g + transform="translate(-106.3801,-213.4882)" + id="g1342"> + <path + inkscape:export-ydpi="3.3299999" + inkscape:export-xdpi="3.3299999" + inkscape:export-filename="/home/kant/Desktop/playlist16.png" + sodipodi:nodetypes="cccccc" + id="rect2192" + d="M 500.15895,306.10736 C 514.98946,306.10736 526.92881,317.38673 526.92881,331.39745 L 526.92881,613.32691 C 526.92881,627.33764 514.98946,638.61701 500.15895,638.61701 L 168.41245,638.61701 C 153.58194,638.61701 141.64259,627.33764 141.64259,613.32691" + style="color:#000000;fill:none;fill-opacity:1.0000000;fill-rule:evenodd;stroke:#064a7c;stroke-width:24.810678;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-dashoffset:0.0000000;stroke-opacity:1.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:block;overflow:visible" /> + <path + inkscape:export-ydpi="3.3299999" + inkscape:export-xdpi="3.3299999" + inkscape:export-filename="/home/kant/Desktop/playlist16.png" + id="path2180" + d="M 119.19800,290.70716 L 119.19800,615.97628 L 503.05597,615.97628 L 503.05597,288.68686 L 119.19800,290.70716 z " + style="fill:#ffffff;fill-opacity:1.0000000;fill-rule:evenodd;stroke:#000000;stroke-width:0.25000000pt;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1.0000000" /> + <path + inkscape:export-ydpi="3.3299999" + inkscape:export-xdpi="3.3299999" + inkscape:export-filename="/home/kant/Desktop/playlist16.png" + id="path2044" + d="M 121.44130,548.05398 L 502.16417,548.05398" + style="color:#000000;fill:none;fill-opacity:0.75000000;fill-rule:evenodd;stroke:#919191;stroke-width:22.743931;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4.0000000;stroke-dashoffset:0.0000000;stroke-opacity:1.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible" /> + <path + inkscape:export-ydpi="3.3299999" + inkscape:export-xdpi="3.3299999" + inkscape:export-filename="/home/kant/Desktop/playlist16.png" + id="path2042" + d="M 128.06257,480.55395 L 508.78546,480.55395" + style="color:#000000;fill:none;fill-opacity:0.75000000;fill-rule:evenodd;stroke:#919191;stroke-width:22.743931;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4.0000000;stroke-dashoffset:0.0000000;stroke-opacity:1.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible" /> + <path + inkscape:export-ydpi="3.3299999" + inkscape:export-xdpi="3.3299999" + inkscape:export-filename="/home/kant/Desktop/playlist16.png" + id="path2040" + d="M 119.78599,413.05397 L 500.50887,413.05397" + style="fill:none;fill-opacity:0.75000000;fill-rule:evenodd;stroke:#919191;stroke-width:22.743931;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000" /> + <path + inkscape:export-ydpi="3.3299999" + inkscape:export-xdpi="3.3299999" + inkscape:export-filename="/home/kant/Desktop/playlist16.png" + sodipodi:nodetypes="ccccccc" + id="rect2037" + d="M 147.38847,288.07648 L 477.92439,288.07648 C 495.49621,288.07648 509.64250,300.34073 509.64250,315.57480 L 509.64250,351.57908 L 115.67038,351.57908 L 115.67038,315.57480 C 115.67038,300.34073 129.81661,288.07648 147.38847,288.07648 z " + style="color:#000000;fill:#9cbff9;fill-opacity:1.0000000;fill-rule:evenodd;stroke:#919191;stroke-width:17.896660;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-dashoffset:0.0000000;stroke-opacity:1.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:block;overflow:visible" /> + <path + inkscape:export-ydpi="3.3299999" + inkscape:export-xdpi="3.3299999" + inkscape:export-filename="/home/kant/Desktop/playlist16.png" + sodipodi:nodetypes="ccccsc" + id="path2170" + d="M 133.02852,334.77117 L 118.13068,283.79076 L 496.37057,286.50249 L 497.19822,302.23048 C 497.19822,302.23048 207.10026,306.93266 157.85828,305.48455 C 125.24918,304.52560 133.02852,334.77117 133.02852,334.77117 z " + style="fill:url(#linearGradient1357);fill-opacity:1.0000000;fill-rule:evenodd;stroke:none;stroke-width:0.25000000pt;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1.0000000" /> + <rect + inkscape:export-ydpi="3.3299999" + inkscape:export-xdpi="3.3299999" + inkscape:export-filename="/home/kant/Desktop/playlist16.png" + ry="25.642496" + rx="27.142862" + y="283.79074" + x="118.95834" + height="337.14285" + width="390.65479" + id="rect2035" + style="color:#000000;fill:none;fill-opacity:1.0000000;fill-rule:evenodd;stroke:url(#linearGradient1359);stroke-width:25.156391;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-dashoffset:0.0000000;stroke-opacity:1.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:block;overflow:visible" /> + <path + inkscape:export-ydpi="3.3299999" + inkscape:export-xdpi="3.3299999" + inkscape:export-filename="/home/kant/Desktop/playlist16.png" + id="path2048" + d="M 227.37500,217.28125 C 211.95109,219.22072 204.16683,238.42233 211.39021,251.48630 C 233.81222,323.72003 256.23424,395.95377 278.65625,468.18750 C 238.14364,461.04875 191.79893,484.68027 180.20498,525.51256 C 171.71108,555.68601 188.92422,589.60912 216.99792,602.68974 C 258.72916,625.01678 318.18203,608.49508 338.83091,564.91682 C 348.75527,544.70766 345.65613,520.23450 334.86004,501.04488 C 317.13586,443.89450 299.41168,386.74413 281.68750,329.59375 C 314.34103,324.72603 348.34771,309.93602 365.78604,280.50938 C 372.35517,268.55979 379.39312,252.12494 369.00236,240.17784 C 360.56999,229.82408 345.19894,232.30608 334.98060,238.32877 C 308.42493,248.78655 279.25477,239.22522 254.81968,227.48764 C 246.27598,222.84753 237.61108,216.47682 227.37500,217.28125 z " + style="color:#000000;fill:url(#linearGradient1361);fill-opacity:1.0000000;fill-rule:evenodd;stroke:#000045;stroke-width:7.4475002;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-dashoffset:0.0000000;stroke-opacity:1.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:block;overflow:visible" /> + <path + inkscape:export-ydpi="3.3299999" + inkscape:export-xdpi="3.3299999" + inkscape:export-filename="/home/kant/Desktop/playlist16.png" + transform="matrix(1.370086,-0.483357,0.443594,1.257378,312.4309,158.1374)" + d="M -78.791901 262.42288 A 42.426407 42.426407 0 1 1 -163.64471,262.42288 A 42.426407 42.426407 0 1 1 -78.791901 262.42288 z" + sodipodi:ry="42.426407" + sodipodi:rx="42.426407" + sodipodi:cy="262.42288" + sodipodi:cx="-121.21831" + id="path2114" + style="color:#000000;fill:url(#radialGradient1363);fill-opacity:1.0000000;fill-rule:evenodd;stroke:none;stroke-width:11.760000;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4.0000000;stroke-dashoffset:0.0000000;stroke-opacity:1.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible" + sodipodi:type="arc" /> + <path + inkscape:export-ydpi="3.3299999" + inkscape:export-xdpi="3.3299999" + inkscape:export-filename="/home/kant/Desktop/playlist16.png" + sodipodi:nodetypes="czszzz" + id="path2166" + d="M 240.54509,300.27018 C 232.21730,278.44282 217.21267,239.75508 226.70527,233.47113 C 234.74749,228.14731 261.66530,247.79928 280.25996,250.14811 C 315.20385,254.56213 329.07512,244.56994 345.15432,258.53361 C 362.35410,273.47041 334.71241,273.66617 321.03274,279.27734 C 305.60741,285.60456 249.15593,322.83940 240.54509,300.27018 z " + style="color:#000000;fill:url(#linearGradient1365);fill-opacity:1.0000000;fill-rule:evenodd;stroke:none;stroke-width:13.999998;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4.0000000;stroke-dashoffset:0.0000000;stroke-opacity:1.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible" /> + <path + inkscape:export-ydpi="3.3299999" + inkscape:export-xdpi="3.3299999" + inkscape:export-filename="/home/kant/Desktop/playlist16.png" + transform="matrix(-0.924423,0.459928,-0.415284,-0.862286,235.3190,791.9451)" + d="M -71.479694 262.42288 A 49.738613 28.327747 0 1 1 -170.95692,262.42288 A 49.738613 28.327747 0 1 1 -71.479694 262.42288 z" + sodipodi:ry="28.327747" + sodipodi:rx="49.738613" + sodipodi:cy="262.42288" + sodipodi:cx="-121.21831" + id="path2200" + style="color:#000000;fill:url(#linearGradient1367);fill-opacity:1.0000000;fill-rule:evenodd;stroke:none;stroke-width:1.9823042;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4.0000000;stroke-dashoffset:0.0000000;stroke-opacity:1.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible" + sodipodi:type="arc" /> + <path + inkscape:export-ydpi="3.3299999" + inkscape:export-xdpi="3.3299999" + inkscape:export-filename="/home/kant/Desktop/playlist16.png" + sodipodi:nodetypes="cscc" + id="path2212" + d="M 287.02031,478.54564 C 287.02031,478.54564 317.02031,482.83136 322.73459,491.40279 C 328.44888,499.97422 272.73459,327.11708 272.73459,327.11708 C 251.30602,329.97422 287.02031,478.54564 287.02031,478.54564 z " + style="fill:url(#linearGradient1369);fill-opacity:1.0000000;fill-rule:evenodd;stroke:none;stroke-width:0.25000000pt;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1.0000000" /> + </g> + </g> +</svg> diff --git a/amarok/src/images/icons/svg/sources.svg b/amarok/src/images/icons/svg/sources.svg new file mode 100644 index 00000000..30ca5ad0 --- /dev/null +++ b/amarok/src/images/icons/svg/sources.svg @@ -0,0 +1,56296 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> +<svg + xmlns:i="http://ns.adobe.com/AdobeIllustrator/10.0/" + xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/" + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://web.resource.org/cc/" + 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="744.09448819" + height="1052.3622047" + id="svg29256" + sodipodi:version="0.32" + inkscape:version="0.45.1" + sodipodi:docbase="/home/theobroma/PROJECTS/Amarok/icons" + sodipodi:docname="new-blue-source.svg" + inkscape:output_extension="org.inkscape.output.svg.inkscape"> + <defs + id="defs29258"> + <linearGradient + inkscape:collect="always" + id="linearGradient10275"> + <stop + style="stop-color:#000000;stop-opacity:1;" + offset="0" + id="stop10277" /> + <stop + style="stop-color:#000000;stop-opacity:0;" + offset="1" + id="stop10279" /> + </linearGradient> + <linearGradient + inkscape:collect="always" + id="linearGradient10258"> + <stop + style="stop-color:#000000;stop-opacity:1;" + offset="0" + id="stop10260" /> + <stop + style="stop-color:#000000;stop-opacity:0;" + offset="1" + id="stop10262" /> + </linearGradient> + <linearGradient + inkscape:collect="always" + id="linearGradient10241"> + <stop + style="stop-color:#000000;stop-opacity:1;" + offset="0" + id="stop10243" /> + <stop + style="stop-color:#000000;stop-opacity:0;" + offset="1" + id="stop10245" /> + </linearGradient> + <linearGradient + inkscape:collect="always" + id="linearGradient10203"> + <stop + style="stop-color:#000000;stop-opacity:1;" + offset="0" + id="stop10205" /> + <stop + style="stop-color:#000000;stop-opacity:0;" + offset="1" + id="stop10207" /> + </linearGradient> + <linearGradient + inkscape:collect="always" + id="linearGradient10186"> + <stop + style="stop-color:#000000;stop-opacity:1;" + offset="0" + id="stop10188" /> + <stop + style="stop-color:#000000;stop-opacity:0;" + offset="1" + id="stop10190" /> + </linearGradient> + <linearGradient + id="linearGradient9597"> + <stop + style="stop-color:#888888;stop-opacity:1;" + offset="0" + id="stop9599" /> + <stop + style="stop-color:#bbbbbb" + offset="1" + id="stop9601" /> + </linearGradient> + <linearGradient + id="linearGradient10909"> + <stop + id="stop10911" + offset="0" + style="stop-color:#0000bf;stop-opacity:1;" /> + <stop + id="stop10913" + offset="1" + style="stop-color:#0066ff;stop-opacity:1;" /> + </linearGradient> + <linearGradient + id="linearGradient10866"> + <stop + style="stop-color:#0066ff;stop-opacity:1;" + offset="0" + id="stop10868" /> + <stop + style="stop-color:#bfd9ff;stop-opacity:1;" + offset="1" + id="stop10870" /> + </linearGradient> + <linearGradient + id="linearGradient13392"> + <stop + style="stop-color:#9c0f0f;stop-opacity:1;" + offset="0" + id="stop13394" /> + <stop + style="stop-color:#bf0303;stop-opacity:1;" + offset="1" + id="stop13396" /> + </linearGradient> + <linearGradient + id="linearGradient7682"> + <stop + id="stop7684" + offset="0" + style="stop-color:#0066ff;stop-opacity:1;" /> + <stop + id="stop7686" + offset="1" + style="stop-color:#bfd9ff;stop-opacity:1;" /> + </linearGradient> + <linearGradient + id="XMLID_12_" + gradientUnits="userSpaceOnUse" + x1="517.1899" + y1="239.939" + x2="410.2776" + y2="522.0239"> + <stop + offset="0" + style="stop-color:#FFFFFF" + id="stop11058" /> + <stop + offset="0.9944" + style="stop-color:#FFFFFF" + id="stop11060" /> + <a:midPointStop + offset="0" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="0.6215" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="0.9944" + style="stop-color:#FFFFFF" /> + </linearGradient> + <radialGradient + id="XMLID_25_" + cx="375.2144" + cy="783.1636" + r="300.5752" + fx="375.2144" + fy="783.1636" + gradientUnits="userSpaceOnUse"> + <stop + offset="0.0112" + style="stop-color:#D2E8EC" + id="stop10923" /> + <stop + offset="0.2921" + style="stop-color:#60A8CE" + id="stop10925" /> + <stop + offset="0.4575" + style="stop-color:#4680A8" + id="stop10927" /> + <stop + offset="0.6767" + style="stop-color:#2F5C84" + id="stop10929" /> + <stop + offset="0.8653" + style="stop-color:#234970" + id="stop10931" /> + <stop + offset="1" + style="stop-color:#1F426A" + id="stop10933" /> + <a:midPointStop + offset="0.0112" + style="stop-color:#D2E8EC" /> + <a:midPointStop + offset="0.5" + style="stop-color:#D2E8EC" /> + <a:midPointStop + offset="0.2921" + style="stop-color:#60A8CE" /> + <a:midPointStop + offset="0.3651" + style="stop-color:#60A8CE" /> + <a:midPointStop + offset="1" + style="stop-color:#1F426A" /> + </radialGradient> + <linearGradient + id="linearGradient9791"> + <stop + id="stop9793" + offset="0" + style="stop-color:#ffffff;stop-opacity:1;" /> + <stop + id="stop9795" + offset="1" + style="stop-color:#ffffff;stop-opacity:0.55605382;" /> + </linearGradient> + <linearGradient + inkscape:collect="always" + id="linearGradient10715"> + <stop + style="stop-color:#3500d3;stop-opacity:1;" + offset="0" + id="stop10717" /> + <stop + style="stop-color:#3500d3;stop-opacity:0;" + offset="1" + id="stop10719" /> + </linearGradient> + <linearGradient + inkscape:collect="always" + id="linearGradient10723"> + <stop + style="stop-color:#a2afe5;stop-opacity:1" + offset="0" + id="stop10725" /> + <stop + style="stop-color:#b4b8e7;stop-opacity:0" + offset="1" + id="stop10727" /> + </linearGradient> + <linearGradient + inkscape:collect="always" + id="linearGradient12907"> + <stop + style="stop-color:#ffffff;stop-opacity:1;" + offset="0" + id="stop12909" /> + <stop + style="stop-color:#adbcf8;stop-opacity:0.98823529" + offset="1" + id="stop12911" /> + </linearGradient> + <linearGradient + id="XMLID_9_" + gradientUnits="userSpaceOnUse" + x1="18.0327" + y1="124.1084" + x2="116.4508" + y2="70.4536"> + <stop + offset="0" + style="stop-color:#FFFFFF" + id="stop105" /> + <stop + offset="1" + style="stop-color:#B1B1C5" + id="stop107" /> + <a:midPointStop + offset="0" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="0.5" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="1" + style="stop-color:#B1B1C5" /> + </linearGradient> + <linearGradient + id="XMLID_8_" + gradientUnits="userSpaceOnUse" + x1="57.8667" + y1="100.9258" + x2="120.5585" + y2="70.7129"> + <stop + offset="0" + style="stop-color:#FFFFFF" + id="stop98" /> + <stop + offset="1" + style="stop-color:#B1B1C5" + id="stop100" /> + <a:midPointStop + offset="0" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="0.5" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="1" + style="stop-color:#B1B1C5" /> + </linearGradient> + <linearGradient + id="XMLID_7_" + gradientUnits="userSpaceOnUse" + x1="46.1694" + y1="55.1499" + x2="97.3291" + y2="76.854"> + <stop + offset="0" + style="stop-color:#6AC5FF" + id="stop83" /> + <stop + offset="0.1262" + style="stop-color:#61B3F6" + id="stop85" /> + <stop + offset="0.3781" + style="stop-color:#4886E0" + id="stop87" /> + <stop + offset="0.7286" + style="stop-color:#213DBD" + id="stop89" /> + <stop + offset="1" + style="stop-color:#00009F" + id="stop91" /> + <a:midPointStop + offset="0" + style="stop-color:#6AC5FF" /> + <a:midPointStop + offset="0.5526" + style="stop-color:#6AC5FF" /> + <a:midPointStop + offset="1" + style="stop-color:#00009F" /> + </linearGradient> + <linearGradient + id="XMLID_6_" + gradientUnits="userSpaceOnUse" + x1="237.208" + y1="684.019" + x2="328.1847" + y2="565.8676"> + <stop + offset="0" + style="stop-color:#FFFFFF" + id="stop11003" /> + <stop + offset="1" + style="stop-color:#193D6B" + id="stop11005" /> + <a:midPointStop + offset="0" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="0.5" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="1" + style="stop-color:#193D6B" /> + </linearGradient> + <linearGradient + id="XMLID_5_" + gradientUnits="userSpaceOnUse" + x1="220.9512" + y1="634.6323" + x2="278.1447" + y2="560.355"> + <stop + offset="0" + style="stop-color:#FFFFFF" + id="stop10996" /> + <stop + offset="1" + style="stop-color:#193D6B" + id="stop10998" /> + <a:midPointStop + offset="0" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="0.5" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="1" + style="stop-color:#193D6B" /> + </linearGradient> + <linearGradient + id="XMLID_4_" + gradientUnits="userSpaceOnUse" + x1="32.1113" + y1="59.9604" + x2="85.1559" + y2="67.4256"> + <stop + offset="0" + style="stop-color:#CDF8FF" + id="stop45" /> + <stop + offset="0.1068" + style="stop-color:#A1D6FF" + id="stop47" /> + <stop + offset="0.2858" + style="stop-color:#5CA2FF" + id="stop49" /> + <stop + offset="0.4358" + style="stop-color:#2A7BFF" + id="stop51" /> + <stop + offset="0.549" + style="stop-color:#0C64FF" + id="stop53" /> + <stop + offset="0.6124" + style="stop-color:#005BFF" + id="stop55" /> + <stop + offset="0.7113" + style="stop-color:#0056FF" + id="stop57" /> + <stop + offset="0.8313" + style="stop-color:#0047FF" + id="stop59" /> + <stop + offset="0.9616" + style="stop-color:#002FFF" + id="stop61" /> + <stop + offset="1" + style="stop-color:#0026FF" + id="stop63" /> + <a:midPointStop + offset="0" + style="stop-color:#CDF8FF" /> + <a:midPointStop + offset="0.4214" + style="stop-color:#CDF8FF" /> + <a:midPointStop + offset="0.6124" + style="stop-color:#005BFF" /> + <a:midPointStop + offset="0.6667" + style="stop-color:#005BFF" /> + <a:midPointStop + offset="1" + style="stop-color:#0026FF" /> + </linearGradient> + <linearGradient + inkscape:collect="always" + id="linearGradient19089"> + <stop + style="stop-color:#f2f5fa;stop-opacity:1;" + offset="0" + id="stop19091" /> + <stop + style="stop-color:#f2f5fa;stop-opacity:0;" + offset="1" + id="stop19093" /> + </linearGradient> + <linearGradient + id="linearGradient6969" + gradientUnits="userSpaceOnUse" + x1="340.0488" + y1="266.93359" + x2="372.38071" + y2="417.81519"> + <stop + offset="0" + style="stop-color:#FFFFFF" + id="stop6971" /> + <stop + offset="0.0867" + style="stop-color:#F8F8F8" + id="stop6973" /> + <stop + offset="0.2156" + style="stop-color:#E4E4E4" + id="stop6975" /> + <stop + offset="0.3708" + style="stop-color:#C2C2C2" + id="stop6977" /> + <stop + offset="0.5465" + style="stop-color:#949494" + id="stop6979" /> + <stop + offset="0.7394" + style="stop-color:#595959" + id="stop6981" /> + <stop + offset="0.9438" + style="stop-color:#151515" + id="stop6983" /> + <stop + offset="0.9944" + style="stop-color:#000000" + id="stop6985" /> + <a:midPointStop + offset="0" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="0.6215" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="0.9944" + style="stop-color:#000000" /> + </linearGradient> + <linearGradient + id="linearGradient13829"> + <stop + style="stop-color:#7575ce;stop-opacity:1;" + offset="0" + id="stop13831" /> + <stop + style="stop-color:#3f72cf;stop-opacity:1;" + offset="1" + id="stop13833" /> + </linearGradient> + <linearGradient + inkscape:collect="always" + id="linearGradient20611"> + <stop + style="stop-color:#ffffff;stop-opacity:1;" + offset="0" + id="stop20613" /> + <stop + style="stop-color:#ffffff;stop-opacity:0;" + offset="1" + id="stop20615" /> + </linearGradient> + <linearGradient + inkscape:collect="always" + id="linearGradient21450"> + <stop + style="stop-color:#ffffff;stop-opacity:1;" + offset="0" + id="stop21452" /> + <stop + style="stop-color:#ffffff;stop-opacity:0;" + offset="1" + id="stop21454" /> + </linearGradient> + <linearGradient + id="linearGradient9558"> + <stop + id="stop9560" + offset="0" + style="stop-color:#02081f;stop-opacity:1;" /> + <stop + style="stop-color:#4666f0;stop-opacity:1;" + offset="0.5" + id="stop9564" /> + <stop + id="stop9562" + offset="1" + style="stop-color:#061382;stop-opacity:1;" /> + </linearGradient> + <linearGradient + id="linearGradient10611"> + <stop + style="stop-color:#0049b0;stop-opacity:1;" + offset="0" + id="stop10613" /> + <stop + style="stop-color:#5d5d5d;stop-opacity:0;" + offset="1" + id="stop10615" /> + </linearGradient> + <linearGradient + id="linearGradient3870" + gradientUnits="userSpaceOnUse" + x1="340.0488" + y1="266.93359" + x2="372.38071" + y2="417.81519"> + <stop + offset="0" + style="stop-color:#FFFFFF" + id="stop3872" /> + <stop + offset="0.0867" + style="stop-color:#F8F8F8" + id="stop3874" /> + <stop + offset="0.2156" + style="stop-color:#E4E4E4" + id="stop3876" /> + <stop + offset="0.3708" + style="stop-color:#C2C2C2" + id="stop3878" /> + <stop + offset="0.5465" + style="stop-color:#949494" + id="stop3881" /> + <stop + offset="0.7394" + style="stop-color:#595959" + id="stop3883" /> + <stop + offset="0.9438" + style="stop-color:#151515" + id="stop3885" /> + <stop + offset="0.9944" + style="stop-color:#000000" + id="stop3888" /> + <a:midPointStop + offset="0" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="0.6215" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="0.9944" + style="stop-color:#000000" /> + </linearGradient> + <linearGradient + id="linearGradient7919"> + <stop + style="stop-color:#c5c8c8;stop-opacity:1.0000000;" + offset="0.0000000" + id="stop7921" /> + <stop + id="stop7929" + offset="0.25600001" + style="stop-color:#f1f1f2;stop-opacity:1.0000000;" /> + <stop + id="stop7927" + offset="0.72025603" + style="stop-color:#eef0f0;stop-opacity:1.0000000;" /> + <stop + style="stop-color:#c8c8cc;stop-opacity:1.0000000;" + offset="1.0000000" + id="stop7923" /> + </linearGradient> + <linearGradient + inkscape:collect="always" + id="linearGradient14711"> + <stop + style="stop-color:#ff2744;stop-opacity:1" + offset="0" + id="stop14713" /> + <stop + style="stop-color:#d60000;stop-opacity:1" + offset="1" + id="stop14715" /> + </linearGradient> + <linearGradient + id="linearGradient11767"> + <stop + id="stop11769" + offset="0" + style="stop-color:#c40000;stop-opacity:1;" /> + <stop + id="stop11771" + offset="1" + style="stop-color:#ff3b3b;stop-opacity:1;" /> + </linearGradient> + <linearGradient + inkscape:collect="always" + id="linearGradient7184"> + <stop + style="stop-color:#000000;stop-opacity:1;" + offset="0" + id="stop7186" /> + <stop + style="stop-color:#000000;stop-opacity:0.6130653" + offset="1" + id="stop7188" /> + </linearGradient> + <linearGradient + id="linearGradient9912"> + <stop + id="stop9914" + offset="0" + style="stop-color:#ffffff;stop-opacity:0.95161289;" /> + <stop + id="stop9916" + offset="1" + style="stop-color:#ffffff;stop-opacity:0.81182796;" /> + </linearGradient> + <linearGradient + id="linearGradient9906"> + <stop + id="stop9908" + offset="0" + style="stop-color:#afb2b6;stop-opacity:1;" /> + <stop + id="stop9910" + offset="1" + style="stop-color:#515971;stop-opacity:1;" /> + </linearGradient> + <linearGradient + id="linearGradient13837"> + <stop + style="stop-color:#80b3ff;stop-opacity:1;" + offset="0" + id="stop12682" /> + <stop + style="stop-color:#0000ff;stop-opacity:1;" + offset="1" + id="stop13841" /> + </linearGradient> + <linearGradient + id="linearGradient4198"> + <stop + style="stop-color:#fff8f8;stop-opacity:1;" + offset="0" + id="stop4200" /> + <stop + style="stop-color:#ffffff;stop-opacity:0;" + offset="1" + id="stop4202" /> + </linearGradient> + <linearGradient + id="linearGradient4190"> + <stop + style="stop-color:#ffffff;stop-opacity:1;" + offset="0" + id="stop4192" /> + <stop + style="stop-color:#ffffff;stop-opacity:0.81182796;" + offset="1" + id="stop4194" /> + </linearGradient> + <linearGradient + id="linearGradient4182"> + <stop + style="stop-color:#849bc3;stop-opacity:1;" + offset="0" + id="stop4184" /> + <stop + style="stop-color:#7c92d1;stop-opacity:1" + offset="1" + id="stop4186" /> + </linearGradient> + <linearGradient + id="linearGradient4166"> + <stop + style="stop-color:#ffffff;stop-opacity:0.43023255;" + offset="0" + id="stop4168" /> + <stop + style="stop-color:#000000;stop-opacity:0.42473119;" + offset="1" + id="stop4170" /> + </linearGradient> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient13837" + id="linearGradient13940" + x1="984.22174" + y1="1303.4946" + x2="984.05951" + y2="1197.8486" + gradientUnits="userSpaceOnUse" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient16603" + x1="993.34174" + y1="1303.9508" + x2="994.25732" + y2="1197.3925" + gradientUnits="userSpaceOnUse" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient17496" + x1="983.07001" + y1="1303.4946" + x2="984.46881" + y2="1197.8486" + gradientUnits="userSpaceOnUse" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient16687" + gradientUnits="userSpaceOnUse" + x1="981.90619" + y1="108.56988" + x2="981.90619" + y2="54.313427" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient16689" + gradientUnits="userSpaceOnUse" + x1="958.54846" + y1="81.44165" + x2="1005.2639" + y2="81.44165" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient7102" + x1="981.90619" + y1="108.16614" + x2="981.90619" + y2="54.717171" + gradientUnits="userSpaceOnUse" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient7996" + x1="1061.8239" + y1="1254.4092" + x2="1160.8185" + y2="1254.4092" + gradientUnits="userSpaceOnUse" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient13837" + id="linearGradient8010" + x1="1061.8239" + y1="1254.4092" + x2="1160.8185" + y2="1254.4092" + gradientUnits="userSpaceOnUse" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient13392" + id="linearGradient13398" + x1="1245.295" + y1="1249.462" + x2="1357.0825" + y2="1249.462" + gradientUnits="userSpaceOnUse" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient9049" + gradientUnits="userSpaceOnUse" + x1="1061.8239" + y1="1254.4092" + x2="1160.8185" + y2="1254.4092" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient13837" + id="linearGradient9051" + gradientUnits="userSpaceOnUse" + x1="1061.8239" + y1="1254.4092" + x2="1160.8185" + y2="1254.4092" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient9057" + gradientUnits="userSpaceOnUse" + x1="981.90619" + y1="108.16614" + x2="981.90619" + y2="54.717171" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient9059" + gradientUnits="userSpaceOnUse" + x1="981.90619" + y1="108.16614" + x2="981.90619" + y2="54.717171" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10866" + id="linearGradient9065" + gradientUnits="userSpaceOnUse" + x1="984.22174" + y1="1303.4946" + x2="984.05951" + y2="1197.8486" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient9075" + gradientUnits="userSpaceOnUse" + x1="993.34174" + y1="1303.9508" + x2="994.25732" + y2="1197.3925" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient9081" + gradientUnits="userSpaceOnUse" + x1="983.07001" + y1="1303.4946" + x2="984.46881" + y2="1197.8486" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient9087" + gradientUnits="userSpaceOnUse" + x1="981.90619" + y1="108.56988" + x2="981.90619" + y2="54.313427" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient9089" + gradientUnits="userSpaceOnUse" + x1="958.54846" + y1="81.44165" + x2="1005.2639" + y2="81.44165" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient10944" + gradientUnits="userSpaceOnUse" + x1="981.90619" + y1="108.16614" + x2="981.90619" + y2="54.717171" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient13837" + id="linearGradient8578" + gradientUnits="userSpaceOnUse" + x1="984.22174" + y1="1303.4946" + x2="984.05951" + y2="1197.8486" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient9036" + x1="307.53064" + y1="150.35727" + x2="307.53064" + y2="127.84666" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1,0,0,1.066655,311.6482,727.0443)" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10611" + id="linearGradient7136" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.119663,-8.52622e-17,-1.048043e-17,0.193497,348.9023,-127.3614)" + x1="470.54666" + y1="311.18387" + x2="470.54666" + y2="141.8587" /> + <linearGradient + inkscape:collect="always" + xlink:href="#radialGradient4046" + id="linearGradient18308" + x1="571.33203" + y1="143.60703" + x2="568.62524" + y2="143.60703" + gradientUnits="userSpaceOnUse" /> + <linearGradient + inkscape:collect="always" + xlink:href="#radialGradient10602" + id="linearGradient26781" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.548015,0,0,0.607138,257.6471,104.6036)" + x1="570.99854" + y1="244.65663" + x2="571.0885" + y2="232.43404" /> + <linearGradient + inkscape:collect="always" + xlink:href="#radialGradient10602" + id="linearGradient26818" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.548015,0,0,0.607138,256.6685,131.3065)" + x1="570.99854" + y1="244.65663" + x2="571.0885" + y2="232.43404" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient11583" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.187832,0,0,1.187832,167.6043,-142.7947)" + x1="752.12048" + y1="1229.3446" + x2="752.12048" + y2="1123.1832" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient11585" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.187832,0,0,1.187832,167.6043,-142.7947)" + x1="836.85321" + y1="1229.3446" + x2="836.85321" + y2="1123.1832" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient11587" + gradientUnits="userSpaceOnUse" + x1="1061.8239" + y1="1254.4092" + x2="1160.8185" + y2="1254.4092" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient13837" + id="linearGradient11589" + gradientUnits="userSpaceOnUse" + x1="1061.8239" + y1="1254.4092" + x2="1160.8185" + y2="1254.4092" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient11591" + gradientUnits="userSpaceOnUse" + x1="1061.8239" + y1="1316.6333" + x2="1061.8239" + y2="1192.1851" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient11593" + gradientUnits="userSpaceOnUse" + x1="1160.8185" + y1="1316.6333" + x2="1160.8185" + y2="1192.1851" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient11595" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.241919,0,0,1.241919,-315.5142,-295.2708)" + x1="1256.7722" + y1="1290.6655" + x2="1256.7722" + y2="1196.9891" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient11597" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.241919,0,0,1.241919,-315.5142,-295.2708)" + x1="1346.7841" + y1="1290.6655" + x2="1346.7841" + y2="1196.9891" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient11599" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.241919,0,0,1.241919,-315.5142,-295.2708)" + x1="1256.0541" + y1="1291.4971" + x2="1256.0541" + y2="1196.1576" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient11601" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.241919,0,0,1.241919,-315.5142,-295.2708)" + x1="1347.5022" + y1="1291.4971" + x2="1347.5022" + y2="1196.1576" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient11603" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(5.127033,0,0,2.178825,-2825.115,1395.643)" + x1="829.27258" + y1="102.44202" + x2="829.27258" + y2="59.307541" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient11605" + gradientUnits="userSpaceOnUse" + x1="1667.4916" + y1="1618.8462" + x2="1667.4916" + y2="1524.8638" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient11607" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(5.127033,0,0,2.178825,-2825.115,1395.643)" + x1="829.15527" + y1="102.46943" + x2="829.15527" + y2="59.28014" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient11609" + gradientUnits="userSpaceOnUse" + x1="1668.0929" + y1="1618.906" + x2="1668.0929" + y2="1524.8041" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient11611" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-1,0,0,1,1766.843,73.60998)" + x1="720.28668" + y1="1228.7766" + x2="720.28668" + y2="1124.5693" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient11613" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-1,0,0,1,1766.843,73.60998)" + x1="810.01794" + y1="1228.7766" + x2="810.01794" + y2="1124.5693" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient11615" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-1,0,0,1,1766.843,73.60998)" + x1="719.61267" + y1="1229.5638" + x2="719.61267" + y2="1123.7822" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient11617" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-1,0,0,1,1766.843,73.60998)" + x1="810.69189" + y1="1229.5638" + x2="810.69189" + y2="1123.7822" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient11619" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(8.716377e-2,2.313213e-2,2.313213e-2,-8.716377e-2,-146.3723,458.4766)" + x1="1492.3486" + y1="1614.788" + x2="1458.5889" + y2="1741.9977" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient11621" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-0.174823,0,0,0.174823,38.46479,332.3384)" + x1="31.778139" + y1="113.87746" + x2="31.778139" + y2="45.985836" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient11623" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-8.716377e-2,2.313213e-2,-2.313213e-2,-8.716377e-2,198.5152,423.819)" + x1="1619.3483" + y1="1648.4916" + x2="1585.5885" + y2="1775.7014" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient11625" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.174823,0,0,0.174823,13.67803,297.6808)" + x1="99.557076" + y1="113.87769" + x2="99.557076" + y2="45.986008" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient11627" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.126784,3.364672e-2,3.364672e-2,-0.126784,-185.7325,510.6844)" + x1="1492.4916" + y1="1615.0333" + x2="1458.8373" + y2="1741.8457" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient11629" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-0.254288,0,0,0.254288,83.12134,327.2107)" + x1="31.884119" + y1="113.77162" + x2="31.884119" + y2="46.091953" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient11631" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-0.126784,3.364672e-2,-3.364672e-2,-0.126784,316.4468,476.0268)" + x1="1619.0931" + y1="1648.6317" + x2="1585.439" + y2="1775.444" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient11633" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.254288,0,0,0.254288,47.59291,292.5531)" + x1="99.451042" + y1="113.77158" + x2="99.451042" + y2="46.091953" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient11635" + gradientUnits="userSpaceOnUse" + x1="981.90619" + y1="108.16614" + x2="981.90619" + y2="54.717171" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient11637" + gradientUnits="userSpaceOnUse" + x1="981.90619" + y1="108.16614" + x2="981.90619" + y2="54.717171" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient11639" + gradientUnits="userSpaceOnUse" + x1="956.82507" + y1="1302.3866" + x2="956.82507" + y2="1198.1793" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient11642" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-1,0,0,1,1766.843,73.60998)" + x1="720.28668" + y1="1228.7766" + x2="720.28668" + y2="1124.5693" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient13837" + id="linearGradient11645" + gradientUnits="userSpaceOnUse" + x1="984.22174" + y1="1303.4946" + x2="984.05951" + y2="1197.8486" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient11647" + gradientUnits="userSpaceOnUse" + x1="907.27698" + y1="1303.4946" + x2="907.27698" + y2="1197.8486" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient11650" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-1,0,0,1,1766.843,73.60998)" + x1="720.01282" + y1="1229.8846" + x2="720.01282" + y2="1124.2386" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10866" + id="linearGradient11652" + gradientUnits="userSpaceOnUse" + x1="15.457762" + y1="67.364845" + x2="15.457762" + y2="57.965576" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient11654" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-8.82077e-2,0,0,8.82077e-2,91.31834,-41.16069)" + x1="719.5567" + y1="1230.3408" + x2="719.5567" + y2="1123.7825" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient11656" + gradientUnits="userSpaceOnUse" + x1="993.34174" + y1="1303.9508" + x2="994.25732" + y2="1197.3925" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient11658" + gradientUnits="userSpaceOnUse" + x1="1047.2864" + y1="1303.9508" + x2="1047.2864" + y2="1197.3925" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient11660" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-1,0,0,1,1766.843,73.60998)" + x1="860.02216" + y1="1230.3408" + x2="860.02216" + y2="1123.7825" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient11663" + gradientUnits="userSpaceOnUse" + x1="983.07001" + y1="1303.4946" + x2="984.46881" + y2="1197.8486" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient11666" + gradientUnits="userSpaceOnUse" + x1="1046.8302" + y1="1303.4946" + x2="1046.8302" + y2="1197.8486" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient11668" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-1,0,0,1,1766.843,73.60998)" + x1="859.56598" + y1="1229.8846" + x2="859.56598" + y2="1124.2386" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient11671" + gradientUnits="userSpaceOnUse" + x1="981.90619" + y1="108.56988" + x2="981.90619" + y2="54.313427" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient11673" + gradientUnits="userSpaceOnUse" + x1="958.54846" + y1="81.44165" + x2="1005.2639" + y2="81.44165" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient11675" + gradientUnits="userSpaceOnUse" + x1="956.15106" + y1="1303.1738" + x2="956.15106" + y2="1197.3922" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient11677" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-1,0,0,1,1766.843,73.60998)" + x1="719.61267" + y1="1229.5638" + x2="719.61267" + y2="1123.7822" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10866" + id="linearGradient11685" + gradientUnits="userSpaceOnUse" + x1="15.457762" + y1="67.364845" + x2="15.457762" + y2="57.965576" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient11687" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-8.82077e-2,0,0,8.82077e-2,91.31834,-41.16069)" + x1="719.5567" + y1="1230.3408" + x2="719.5567" + y2="1123.7825" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient11697" + gradientUnits="userSpaceOnUse" + x1="993.34174" + y1="1303.9508" + x2="994.25732" + y2="1197.3925" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient11699" + gradientUnits="userSpaceOnUse" + x1="1047.2864" + y1="1303.9508" + x2="1047.2864" + y2="1197.3925" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient11701" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-1,0,0,1,1766.843,73.60998)" + x1="860.02216" + y1="1230.3408" + x2="860.02216" + y2="1123.7825" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient11713" + gradientUnits="userSpaceOnUse" + x1="981.90619" + y1="108.56988" + x2="981.90619" + y2="54.313427" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient11715" + gradientUnits="userSpaceOnUse" + x1="958.54846" + y1="81.44165" + x2="1005.2639" + y2="81.44165" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient11717" + gradientUnits="userSpaceOnUse" + x1="956.15106" + y1="1303.1738" + x2="956.15106" + y2="1197.3922" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient11719" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-1,0,0,1,1766.843,73.60998)" + x1="719.61267" + y1="1229.5638" + x2="719.61267" + y2="1123.7822" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient11729" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.187832,0,0,1.187832,167.6043,-142.7947)" + x1="752.12048" + y1="1229.3446" + x2="752.12048" + y2="1123.1832" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient11731" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.187832,0,0,1.187832,167.6043,-142.7947)" + x1="836.85321" + y1="1229.3446" + x2="836.85321" + y2="1123.1832" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient11743" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.241919,0,0,1.241919,-315.5142,-295.2708)" + x1="1256.0541" + y1="1291.4971" + x2="1256.0541" + y2="1196.1576" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient11745" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.241919,0,0,1.241919,-315.5142,-295.2708)" + x1="1347.5022" + y1="1291.4971" + x2="1347.5022" + y2="1196.1576" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient11755" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(5.127033,0,0,2.178825,-2825.115,1395.643)" + x1="829.27258" + y1="102.44202" + x2="829.27258" + y2="59.307541" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient11757" + gradientUnits="userSpaceOnUse" + x1="1667.4916" + y1="1618.8462" + x2="1667.4916" + y2="1524.8638" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient11769" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-1,0,0,1,1766.843,73.60998)" + x1="719.61267" + y1="1229.5638" + x2="719.61267" + y2="1123.7822" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient11771" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-1,0,0,1,1766.843,73.60998)" + x1="810.69189" + y1="1229.5638" + x2="810.69189" + y2="1123.7822" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient11780" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-8.716377e-2,2.313213e-2,-2.313213e-2,-8.716377e-2,198.5152,423.819)" + x1="1619.3483" + y1="1648.4916" + x2="1585.5885" + y2="1775.7014" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient11782" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.174823,0,0,0.174823,13.67803,297.6808)" + x1="99.557076" + y1="113.87769" + x2="99.557076" + y2="45.986008" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient11790" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(8.716377e-2,2.313213e-2,2.313213e-2,-8.716377e-2,-146.3723,458.4766)" + x1="1492.3486" + y1="1614.788" + x2="1458.5889" + y2="1741.9977" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient11792" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-0.174823,0,0,0.174823,38.46479,332.3384)" + x1="31.778139" + y1="113.87746" + x2="31.778139" + y2="45.985836" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient13837" + id="linearGradient11802" + gradientUnits="userSpaceOnUse" + x1="984.22174" + y1="1303.4946" + x2="984.05951" + y2="1197.8486" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient11804" + gradientUnits="userSpaceOnUse" + x1="907.27698" + y1="1303.4946" + x2="907.27698" + y2="1197.8486" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient11806" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-1,0,0,1,1766.843,73.60998)" + x1="720.01282" + y1="1229.8846" + x2="720.01282" + y2="1124.2386" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient11816" + gradientUnits="userSpaceOnUse" + x1="983.07001" + y1="1303.4946" + x2="984.46881" + y2="1197.8486" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient11818" + gradientUnits="userSpaceOnUse" + x1="1046.8302" + y1="1303.4946" + x2="1046.8302" + y2="1197.8486" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient11820" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-1,0,0,1,1766.843,73.60998)" + x1="859.56598" + y1="1229.8846" + x2="859.56598" + y2="1124.2386" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient11832" + gradientUnits="userSpaceOnUse" + x1="981.90619" + y1="108.16614" + x2="981.90619" + y2="54.717171" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient11834" + gradientUnits="userSpaceOnUse" + x1="981.90619" + y1="108.16614" + x2="981.90619" + y2="54.717171" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient11836" + gradientUnits="userSpaceOnUse" + x1="956.82507" + y1="1302.3866" + x2="956.82507" + y2="1198.1793" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient11838" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-1,0,0,1,1766.843,73.60998)" + x1="720.28668" + y1="1228.7766" + x2="720.28668" + y2="1124.5693" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient11848" + gradientUnits="userSpaceOnUse" + x1="1061.8239" + y1="1254.4092" + x2="1160.8185" + y2="1254.4092" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient13837" + id="linearGradient11850" + gradientUnits="userSpaceOnUse" + x1="1061.8239" + y1="1254.4092" + x2="1160.8185" + y2="1254.4092" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient11852" + gradientUnits="userSpaceOnUse" + x1="1061.8239" + y1="1316.6333" + x2="1061.8239" + y2="1192.1851" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient11854" + gradientUnits="userSpaceOnUse" + x1="1160.8185" + y1="1316.6333" + x2="1160.8185" + y2="1192.1851" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient11866" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.241919,0,0,1.241919,-315.5142,-295.2708)" + x1="1256.7722" + y1="1290.6655" + x2="1256.7722" + y2="1196.9891" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient11868" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.241919,0,0,1.241919,-315.5142,-295.2708)" + x1="1346.7841" + y1="1290.6655" + x2="1346.7841" + y2="1196.9891" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient11878" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(5.127033,0,0,2.178825,-2825.115,1395.643)" + x1="829.15527" + y1="102.46943" + x2="829.15527" + y2="59.28014" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient11880" + gradientUnits="userSpaceOnUse" + x1="1668.0929" + y1="1618.906" + x2="1668.0929" + y2="1524.8041" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient11892" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-1,0,0,1,1766.843,73.60998)" + x1="720.28668" + y1="1228.7766" + x2="720.28668" + y2="1124.5693" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient11894" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-1,0,0,1,1766.843,73.60998)" + x1="810.01794" + y1="1228.7766" + x2="810.01794" + y2="1124.5693" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient11902" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-0.126784,3.364672e-2,-3.364672e-2,-0.126784,316.4468,476.0268)" + x1="1619.0931" + y1="1648.6317" + x2="1585.439" + y2="1775.444" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient11904" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.254288,0,0,0.254288,47.59291,292.5531)" + x1="99.451042" + y1="113.77158" + x2="99.451042" + y2="46.091953" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient11912" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.126784,3.364672e-2,3.364672e-2,-0.126784,-185.7325,510.6844)" + x1="1492.4916" + y1="1615.0333" + x2="1458.8373" + y2="1741.8457" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient11914" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-0.254288,0,0,0.254288,83.12134,327.2107)" + x1="31.884119" + y1="113.77162" + x2="31.884119" + y2="46.091953" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient13837" + id="linearGradient11924" + gradientUnits="userSpaceOnUse" + x1="984.22174" + y1="1303.4946" + x2="984.05951" + y2="1197.8486" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient11938" + gradientUnits="userSpaceOnUse" + x1="983.07001" + y1="1303.4946" + x2="984.46881" + y2="1197.8486" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient11940" + gradientUnits="userSpaceOnUse" + x1="1046.8302" + y1="1303.4946" + x2="1046.8302" + y2="1197.8486" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient11942" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-1,0,0,1,1766.843,73.60998)" + x1="859.56598" + y1="1229.8846" + x2="859.56598" + y2="1124.2386" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient11954" + gradientUnits="userSpaceOnUse" + x1="981.90619" + y1="108.16614" + x2="981.90619" + y2="54.717171" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient11956" + gradientUnits="userSpaceOnUse" + x1="981.90619" + y1="108.16614" + x2="981.90619" + y2="54.717171" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient11958" + gradientUnits="userSpaceOnUse" + x1="956.82507" + y1="1302.3866" + x2="956.82507" + y2="1198.1793" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient11960" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-1,0,0,1,1766.843,73.60998)" + x1="720.28668" + y1="1228.7766" + x2="720.28668" + y2="1124.5693" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient11970" + gradientUnits="userSpaceOnUse" + x1="1061.8239" + y1="1254.4092" + x2="1160.8185" + y2="1254.4092" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient13837" + id="linearGradient11972" + gradientUnits="userSpaceOnUse" + x1="1061.8239" + y1="1254.4092" + x2="1160.8185" + y2="1254.4092" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient11974" + gradientUnits="userSpaceOnUse" + x1="1061.8239" + y1="1316.6333" + x2="1061.8239" + y2="1192.1851" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient11976" + gradientUnits="userSpaceOnUse" + x1="1160.8185" + y1="1316.6333" + x2="1160.8185" + y2="1192.1851" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient11988" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.241919,0,0,1.241919,-315.5142,-295.2708)" + x1="1256.7722" + y1="1290.6655" + x2="1256.7722" + y2="1196.9891" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient11990" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.241919,0,0,1.241919,-315.5142,-295.2708)" + x1="1346.7841" + y1="1290.6655" + x2="1346.7841" + y2="1196.9891" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient12000" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(5.127033,0,0,2.178825,-2825.115,1395.643)" + x1="829.15527" + y1="102.46943" + x2="829.15527" + y2="59.28014" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient12002" + gradientUnits="userSpaceOnUse" + x1="1668.0929" + y1="1618.906" + x2="1668.0929" + y2="1524.8041" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient12014" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-1,0,0,1,1766.843,73.60998)" + x1="720.28668" + y1="1228.7766" + x2="720.28668" + y2="1124.5693" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient12016" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-1,0,0,1,1766.843,73.60998)" + x1="810.01794" + y1="1228.7766" + x2="810.01794" + y2="1124.5693" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient12024" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-0.126784,3.364672e-2,-3.364672e-2,-0.126784,316.4468,476.0268)" + x1="1619.0931" + y1="1648.6317" + x2="1585.439" + y2="1775.444" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient12026" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.254288,0,0,0.254288,47.59291,292.5531)" + x1="99.451042" + y1="113.77158" + x2="99.451042" + y2="46.091953" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient12034" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.126784,3.364672e-2,3.364672e-2,-0.126784,-185.7325,510.6844)" + x1="1492.4916" + y1="1615.0333" + x2="1458.8373" + y2="1741.8457" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient12036" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-0.254288,0,0,0.254288,83.12134,327.2107)" + x1="31.884119" + y1="113.77162" + x2="31.884119" + y2="46.091953" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient9906" + id="linearGradient12289" + gradientUnits="userSpaceOnUse" + x1="417.36929" + y1="435.0079" + x2="417.36929" + y2="454.65622" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient9912" + id="radialGradient12291" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.726147,-8.054512e-3,1.109145e-2,0.999938,109.5649,3.398898)" + cx="418.51233" + cy="454.95853" + fx="418.51233" + fy="454.95853" + r="11.005301" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient12293" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.026533,0,0,1.026533,-375.626,-403.9638)" + x1="415.12271" + y1="435.43039" + x2="415.12271" + y2="453.03961" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient12295" + gradientUnits="userSpaceOnUse" + x1="320.77139" + y1="119.24062" + x2="320.77139" + y2="38.821857" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient12297" + gradientUnits="userSpaceOnUse" + x1="346.04111" + y1="119.24062" + x2="346.04111" + y2="38.821857" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient9906" + id="linearGradient12299" + gradientUnits="userSpaceOnUse" + x1="417.36929" + y1="435.0079" + x2="417.36929" + y2="454.65622" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient9912" + id="radialGradient12301" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.726147,-8.054512e-3,1.109145e-2,0.999938,109.5649,3.398898)" + cx="418.51233" + cy="454.95853" + fx="418.51233" + fy="454.95853" + r="11.005301" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient12303" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.89894,0,0,0.89894,-322.5201,-347.1671)" + x1="415.12271" + y1="435.43039" + x2="415.12271" + y2="453.03961" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient12305" + gradientUnits="userSpaceOnUse" + x1="320.77139" + y1="119.24062" + x2="320.77139" + y2="38.821857" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient12307" + gradientUnits="userSpaceOnUse" + x1="346.04111" + y1="119.24062" + x2="346.04111" + y2="38.821857" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient9906" + id="linearGradient12309" + gradientUnits="userSpaceOnUse" + x1="417.36929" + y1="435.0079" + x2="417.36929" + y2="454.65622" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient9912" + id="radialGradient12311" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.726147,-8.054512e-3,1.109145e-2,0.999938,109.5649,3.398898)" + cx="418.51233" + cy="454.95853" + fx="418.51233" + fy="454.95853" + r="11.005301" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient12313" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.026533,0,0,1.026533,-375.626,-403.9638)" + x1="415.12271" + y1="435.43039" + x2="415.12271" + y2="453.03961" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient12315" + gradientUnits="userSpaceOnUse" + x1="249.18983" + y1="78.604225" + x2="249.18983" + y2="48.843891" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient12317" + gradientUnits="userSpaceOnUse" + x1="249.18983" + y1="78.604225" + x2="249.18983" + y2="48.843891" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient9906" + id="linearGradient12319" + gradientUnits="userSpaceOnUse" + x1="417.36929" + y1="435.0079" + x2="417.36929" + y2="454.65622" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient9912" + id="radialGradient12321" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.726147,-8.054512e-3,1.109145e-2,0.999938,109.5649,3.398898)" + cx="418.51233" + cy="454.95853" + fx="418.51233" + fy="454.95853" + r="11.005301" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient12323" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.89894,0,0,0.89894,-322.5201,-347.1671)" + x1="415.12271" + y1="435.43039" + x2="415.12271" + y2="453.03961" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient12325" + gradientUnits="userSpaceOnUse" + x1="248.9754" + y1="78.818657" + x2="248.9754" + y2="48.629459" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient12327" + gradientUnits="userSpaceOnUse" + x1="248.9754" + y1="78.818657" + x2="248.9754" + y2="48.629459" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient9906" + id="linearGradient12329" + gradientUnits="userSpaceOnUse" + x1="417.36929" + y1="435.0079" + x2="417.36929" + y2="454.65622" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient9912" + id="radialGradient12331" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.726147,-8.054512e-3,1.109145e-2,0.999938,109.5649,3.398898)" + cx="418.51233" + cy="454.95853" + fx="418.51233" + fy="454.95853" + r="11.005301" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient12333" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.89894,0,0,0.89894,-322.5201,-347.1671)" + x1="415.12271" + y1="435.43039" + x2="415.12271" + y2="453.03961" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient12335" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(8.682852e-2,0,0,8.682852e-2,-8.9587,-12.61537)" + x1="1462.0917" + y1="1816.752" + x2="1462.0917" + y2="1632.0562" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient12337" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(8.682852e-2,0,0,8.682852e-2,-8.9587,-12.61537)" + x1="1610.308" + y1="1816.752" + x2="1610.308" + y2="1632.0562" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient9906" + id="linearGradient12339" + gradientUnits="userSpaceOnUse" + x1="417.36929" + y1="435.0079" + x2="417.36929" + y2="454.65622" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient9912" + id="radialGradient12341" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.726147,-8.054512e-3,1.109145e-2,0.999938,109.5649,3.398898)" + cx="418.51233" + cy="454.95853" + fx="418.51233" + fy="454.95853" + r="11.005301" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient12343" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.89894,0,0,0.89894,-322.5201,-347.1671)" + x1="415.12271" + y1="435.43039" + x2="415.12271" + y2="453.03961" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient12345" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.458666,0,0,0.458666,-24.99616,-73.5904)" + x1="310.68066" + y1="557.73413" + x2="310.68066" + y2="520.34814" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient12347" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.458666,0,0,0.458666,-24.99616,-73.5904)" + x1="343.24658" + y1="557.73413" + x2="343.24658" + y2="520.34814" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient9906" + id="linearGradient12359" + gradientUnits="userSpaceOnUse" + x1="417.36929" + y1="435.0079" + x2="417.36929" + y2="454.65622" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient9912" + id="radialGradient12361" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.726147,-8.054512e-3,1.109145e-2,0.999938,109.5649,3.398898)" + cx="418.51233" + cy="454.95853" + fx="418.51233" + fy="454.95853" + r="11.005301" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient12363" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.026533,0,0,1.026533,-375.626,-403.9638)" + x1="415.12271" + y1="435.43039" + x2="415.12271" + y2="453.03961" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient12365" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.667151,0,0,0.667151,-54.65523,-187.8396)" + x1="310.68066" + y1="557.73413" + x2="310.68066" + y2="520.34814" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient12367" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.667151,0,0,0.667151,-54.65523,-187.8396)" + x1="343.24658" + y1="557.73413" + x2="343.24658" + y2="520.34814" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient9906" + id="linearGradient12379" + gradientUnits="userSpaceOnUse" + x1="417.36929" + y1="435.0079" + x2="417.36929" + y2="454.65622" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient9912" + id="radialGradient12381" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.726147,-8.054512e-3,1.109145e-2,0.999938,109.5649,3.398898)" + cx="418.51233" + cy="454.95853" + fx="418.51233" + fy="454.95853" + r="11.005301" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient12383" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.89894,0,0,0.89894,-322.5201,-347.1671)" + x1="415.12271" + y1="435.43039" + x2="415.12271" + y2="453.03961" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient12385" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(5.148882e-2,-7.219555e-3,6.75419e-3,5.503636e-2,124.6994,186.4847)" + spreadMethod="reflect" + x1="-154.41507" + y1="501.6441" + x2="-113.26653" + y2="187.95882" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient12387" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(6.903014e-2,0,0,7.450646e-2,20.82859,161.9654)" + x1="1559.8582" + y1="714.60614" + x2="1559.8582" + y2="478.90616" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient9906" + id="linearGradient12389" + gradientUnits="userSpaceOnUse" + x1="417.36929" + y1="435.0079" + x2="417.36929" + y2="454.65622" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient9912" + id="radialGradient12391" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.726147,-8.054512e-3,1.109145e-2,0.999938,109.5649,3.398898)" + cx="418.51233" + cy="454.95853" + fx="418.51233" + fy="454.95853" + r="11.005301" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient12393" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.026533,0,0,1.026533,-375.626,-403.9638)" + x1="415.12271" + y1="435.43039" + x2="415.12271" + y2="453.03961" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient12395" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(-3.166181e-2,0.507886)" + x1="149.62633" + y1="326.0481" + x2="149.62633" + y2="306.0368" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient12397" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(-3.166181e-2,0.507886)" + x1="172.26367" + y1="326.0481" + x2="172.26367" + y2="306.0368" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient9906" + id="linearGradient12399" + gradientUnits="userSpaceOnUse" + x1="417.36929" + y1="435.0079" + x2="417.36929" + y2="454.65622" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient9912" + id="radialGradient12401" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.726147,-8.054512e-3,1.109145e-2,0.999938,109.5649,3.398898)" + cx="418.51233" + cy="454.95853" + fx="418.51233" + fy="454.95853" + r="11.005301" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient12403" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.89894,0,0,0.89894,-322.5201,-347.1671)" + x1="415.12271" + y1="435.43039" + x2="415.12271" + y2="453.03961" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient12405" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.637663,0,0,0.637663,19.34726,114.1855)" + x1="149.58427" + y1="326.09015" + x2="149.58427" + y2="305.99475" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient12407" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.637663,0,0,0.637663,19.34726,114.1855)" + x1="172.26366" + y1="326.0481" + x2="172.26366" + y2="306.0368" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient9906" + id="linearGradient12409" + gradientUnits="userSpaceOnUse" + x1="417.36929" + y1="435.0079" + x2="417.36929" + y2="454.65622" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient9912" + id="radialGradient12411" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.726147,-8.054512e-3,1.109145e-2,0.999938,109.5649,3.398898)" + cx="418.51233" + cy="454.95853" + fx="418.51233" + fy="454.95853" + r="11.005301" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient12413" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.026533,0,0,1.026533,-375.626,-403.9638)" + x1="415.12271" + y1="435.43039" + x2="415.12271" + y2="453.03961" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient12415" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.156181,0,0,1.010735,-25.34181,-1.937604)" + x1="155.93187" + y1="250.12737" + x2="155.93187" + y2="241.29185" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient12417" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.156181,0,0,1.010735,-25.34181,-1.937604)" + x1="162.65588" + y1="250.12737" + x2="162.65588" + y2="241.29185" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient12419" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.442363,0,0,1,-71.25107,0)" + x1="160.18631" + y1="243.74049" + x2="160.18631" + y2="230.55022" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient12421" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.442363,0,0,1,-71.25107,0)" + x1="162.38216" + y1="243.74049" + x2="162.38216" + y2="230.55022" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="radialGradient12423" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(2.014563,-1.079665e-5,8.168247e-6,1.438281,-163.45,-106.6208)" + cx="161.10194" + cy="243.27481" + fx="161.10194" + fy="243.27481" + r="1.868932" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient12425" + gradientUnits="userSpaceOnUse" + x1="163.22453" + y1="245.33675" + x2="163.22453" + y2="241.21288" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient9906" + id="linearGradient12427" + gradientUnits="userSpaceOnUse" + x1="417.36929" + y1="435.0079" + x2="417.36929" + y2="454.65622" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient9912" + id="radialGradient12429" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.726147,-8.054512e-3,1.109145e-2,0.999938,109.5649,3.398898)" + cx="418.51233" + cy="454.95853" + fx="418.51233" + fy="454.95853" + r="11.005301" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient12431" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.89894,0,0,0.89894,-322.5201,-347.1671)" + x1="415.12271" + y1="435.43039" + x2="415.12271" + y2="453.03961" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient12433" + gradientUnits="userSpaceOnUse" + x1="122.45271" + y1="242.89435" + x2="122.45271" + y2="235.94859" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient12435" + gradientUnits="userSpaceOnUse" + x1="119.28371" + y1="248.80389" + x2="119.28371" + y2="243.41698" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="radialGradient12437" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.500971,-4.456257e-6,4.704147e-6,1.584466,-61.42651,-142.5627)" + cx="122.61263" + cy="243.92044" + fx="122.61263" + fy="243.92044" + r="0.96019417" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient12439" + gradientUnits="userSpaceOnUse" + x1="123.82283" + y1="245.13063" + x2="123.82283" + y2="242.71025" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient9906" + id="linearGradient12451" + gradientUnits="userSpaceOnUse" + x1="417.36929" + y1="435.0079" + x2="417.36929" + y2="454.65622" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient9912" + id="radialGradient12453" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.726147,-8.054512e-3,1.109145e-2,0.999938,109.5649,3.398898)" + cx="418.51233" + cy="454.95853" + fx="418.51233" + fy="454.95853" + r="11.005301" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient12455" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.89894,0,0,0.89894,-322.5201,-347.1671)" + x1="415.12271" + y1="435.43039" + x2="415.12271" + y2="453.03961" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient14711" + id="linearGradient12457" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.348255e-3,0.76582,-0.76582,1.321424e-3,311.316,151.0365)" + x1="159.32025" + y1="256.35764" + x2="175.68762" + y2="256.38644" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient13392" + id="linearGradient12459" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.348255e-3,0.76582,-0.76582,1.321424e-3,311.316,151.0365)" + x1="175.71587" + y1="240.01489" + x2="159.3485" + y2="239.98607" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient9906" + id="linearGradient12476" + gradientUnits="userSpaceOnUse" + x1="417.36929" + y1="435.0079" + x2="417.36929" + y2="454.65622" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient9912" + id="radialGradient12478" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.726147,-8.054512e-3,1.109145e-2,0.999938,109.5649,3.398898)" + cx="418.51233" + cy="454.95853" + fx="418.51233" + fy="454.95853" + r="11.005301" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient12480" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.026533,0,0,1.026533,-375.626,-403.9638)" + x1="415.12271" + y1="435.43039" + x2="415.12271" + y2="453.03961" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient12482" + gradientUnits="userSpaceOnUse" + x1="320.77139" + y1="119.24062" + x2="320.77139" + y2="38.821857" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient12484" + gradientUnits="userSpaceOnUse" + x1="346.04111" + y1="119.24062" + x2="346.04111" + y2="38.821857" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient9906" + id="linearGradient12500" + gradientUnits="userSpaceOnUse" + x1="417.36929" + y1="435.0079" + x2="417.36929" + y2="454.65622" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient9912" + id="radialGradient12502" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.726147,-8.054512e-3,1.109145e-2,0.999938,109.5649,3.398898)" + cx="418.51233" + cy="454.95853" + fx="418.51233" + fy="454.95853" + r="11.005301" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient12504" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.026533,0,0,1.026533,-375.626,-403.9638)" + x1="415.12271" + y1="435.43039" + x2="415.12271" + y2="453.03961" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient12506" + gradientUnits="userSpaceOnUse" + x1="320.77139" + y1="119.24062" + x2="320.77139" + y2="38.821857" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient12508" + gradientUnits="userSpaceOnUse" + x1="346.04111" + y1="119.24062" + x2="346.04111" + y2="38.821857" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient9906" + id="linearGradient12524" + gradientUnits="userSpaceOnUse" + x1="417.36929" + y1="435.0079" + x2="417.36929" + y2="454.65622" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient9912" + id="radialGradient12526" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.726147,-8.054512e-3,1.109145e-2,0.999938,109.5649,3.398898)" + cx="418.51233" + cy="454.95853" + fx="418.51233" + fy="454.95853" + r="11.005301" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient12528" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.89894,0,0,0.89894,-322.5201,-347.1671)" + x1="415.12271" + y1="435.43039" + x2="415.12271" + y2="453.03961" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient12530" + gradientUnits="userSpaceOnUse" + x1="320.77139" + y1="119.24062" + x2="320.77139" + y2="38.821857" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient12532" + gradientUnits="userSpaceOnUse" + x1="346.04111" + y1="119.24062" + x2="346.04111" + y2="38.821857" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient9906" + id="linearGradient12546" + gradientUnits="userSpaceOnUse" + x1="417.36929" + y1="435.0079" + x2="417.36929" + y2="454.65622" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient9912" + id="radialGradient12548" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.726147,-8.054512e-3,1.109145e-2,0.999938,109.5649,3.398898)" + cx="418.51233" + cy="454.95853" + fx="418.51233" + fy="454.95853" + r="11.005301" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient12550" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.89894,0,0,0.89894,-322.5201,-347.1671)" + x1="415.12271" + y1="435.43039" + x2="415.12271" + y2="453.03961" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient12552" + gradientUnits="userSpaceOnUse" + x1="248.9754" + y1="78.818657" + x2="248.9754" + y2="48.629459" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient12554" + gradientUnits="userSpaceOnUse" + x1="248.9754" + y1="78.818657" + x2="248.9754" + y2="48.629459" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient9906" + id="linearGradient12568" + gradientUnits="userSpaceOnUse" + x1="417.36929" + y1="435.0079" + x2="417.36929" + y2="454.65622" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient9912" + id="radialGradient12570" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.726147,-8.054512e-3,1.109145e-2,0.999938,109.5649,3.398898)" + cx="418.51233" + cy="454.95853" + fx="418.51233" + fy="454.95853" + r="11.005301" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient12572" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.89894,0,0,0.89894,-322.5201,-347.1671)" + x1="415.12271" + y1="435.43039" + x2="415.12271" + y2="453.03961" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient12574" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(8.682852e-2,0,0,8.682852e-2,-8.9587,-12.61537)" + x1="1462.0917" + y1="1816.752" + x2="1462.0917" + y2="1632.0562" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient12576" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(8.682852e-2,0,0,8.682852e-2,-8.9587,-12.61537)" + x1="1610.308" + y1="1816.752" + x2="1610.308" + y2="1632.0562" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient9906" + id="linearGradient12585" + gradientUnits="userSpaceOnUse" + x1="417.36929" + y1="435.0079" + x2="417.36929" + y2="454.65622" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient9912" + id="radialGradient12587" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.726147,-8.054512e-3,1.109145e-2,0.999938,109.5649,3.398898)" + cx="418.51233" + cy="454.95853" + fx="418.51233" + fy="454.95853" + r="11.005301" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient12589" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.026533,0,0,1.026533,-375.626,-403.9638)" + x1="415.12271" + y1="435.43039" + x2="415.12271" + y2="453.03961" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient12591" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.126296,0,0,0.126296,-127.8702,679.3946)" + x1="1462.0935" + y1="1816.7543" + x2="1462.0935" + y2="1632.0582" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient12593" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.126296,0,0,0.126296,-127.8702,679.3946)" + x1="1610.3101" + y1="1816.7543" + x2="1610.3101" + y2="1632.0582" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient9906" + id="linearGradient12607" + gradientUnits="userSpaceOnUse" + x1="417.36929" + y1="435.0079" + x2="417.36929" + y2="454.65622" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient9912" + id="radialGradient12609" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.726147,-8.054512e-3,1.109145e-2,0.999938,109.5649,3.398898)" + cx="418.51233" + cy="454.95853" + fx="418.51233" + fy="454.95853" + r="11.005301" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient12611" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.89894,0,0,0.89894,-322.5201,-347.1671)" + x1="415.12271" + y1="435.43039" + x2="415.12271" + y2="453.03961" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient12613" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.458666,0,0,0.458666,-24.99616,-73.5904)" + x1="310.68066" + y1="557.73413" + x2="310.68066" + y2="520.34814" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient12615" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.458666,0,0,0.458666,-24.99616,-73.5904)" + x1="343.24658" + y1="557.73413" + x2="343.24658" + y2="520.34814" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient9906" + id="linearGradient12631" + gradientUnits="userSpaceOnUse" + x1="417.36929" + y1="435.0079" + x2="417.36929" + y2="454.65622" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient9912" + id="radialGradient12633" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.726147,-8.054512e-3,1.109145e-2,0.999938,109.5649,3.398898)" + cx="418.51233" + cy="454.95853" + fx="418.51233" + fy="454.95853" + r="11.005301" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient12635" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.89894,0,0,0.89894,-322.5201,-347.1671)" + x1="415.12271" + y1="435.43039" + x2="415.12271" + y2="453.03961" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient12637" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(5.148882e-2,-7.219555e-3,6.75419e-3,5.503636e-2,124.6994,186.4847)" + spreadMethod="reflect" + x1="-154.41507" + y1="501.6441" + x2="-113.26653" + y2="187.95882" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient12639" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(6.903014e-2,0,0,7.450646e-2,20.82859,161.9654)" + x1="1559.8582" + y1="714.60614" + x2="1559.8582" + y2="478.90616" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient9906" + id="linearGradient12681" + gradientUnits="userSpaceOnUse" + x1="417.36929" + y1="435.0079" + x2="417.36929" + y2="454.65622" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient9912" + id="radialGradient12683" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.726147,-8.054512e-3,1.109145e-2,0.999938,109.5649,3.398898)" + cx="418.51233" + cy="454.95853" + fx="418.51233" + fy="454.95853" + r="11.005301" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient12685" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.89894,0,0,0.89894,-322.5201,-347.1671)" + x1="415.12271" + y1="435.43039" + x2="415.12271" + y2="453.03961" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient12687" + gradientUnits="userSpaceOnUse" + x1="122.45271" + y1="242.89435" + x2="122.45271" + y2="235.94859" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient12689" + gradientUnits="userSpaceOnUse" + x1="119.28371" + y1="248.80389" + x2="119.28371" + y2="243.41698" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="radialGradient12691" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.500971,-4.456257e-6,4.704147e-6,1.584466,-61.42651,-142.5627)" + cx="122.61263" + cy="243.92044" + fx="122.61263" + fy="243.92044" + r="0.96019417" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient12693" + gradientUnits="userSpaceOnUse" + x1="123.82283" + y1="245.13063" + x2="123.82283" + y2="242.71025" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient9906" + id="linearGradient12707" + gradientUnits="userSpaceOnUse" + x1="417.36929" + y1="435.0079" + x2="417.36929" + y2="454.65622" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient9912" + id="radialGradient12709" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.726147,-8.054512e-3,1.109145e-2,0.999938,109.5649,3.398898)" + cx="418.51233" + cy="454.95853" + fx="418.51233" + fy="454.95853" + r="11.005301" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient12711" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.89894,0,0,0.89894,-322.5201,-347.1671)" + x1="415.12271" + y1="435.43039" + x2="415.12271" + y2="453.03961" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient14711" + id="linearGradient12713" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.348255e-3,0.76582,-0.76582,1.321424e-3,311.316,151.0365)" + x1="159.32025" + y1="256.35764" + x2="175.68762" + y2="256.38644" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient13392" + id="linearGradient12715" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.348255e-3,0.76582,-0.76582,1.321424e-3,311.316,151.0365)" + x1="175.71587" + y1="240.01489" + x2="159.3485" + y2="239.98607" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient9906" + id="linearGradient12729" + gradientUnits="userSpaceOnUse" + x1="417.36929" + y1="435.0079" + x2="417.36929" + y2="454.65622" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient9912" + id="radialGradient12731" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.726147,-8.054512e-3,1.109145e-2,0.999938,109.5649,3.398898)" + cx="418.51233" + cy="454.95853" + fx="418.51233" + fy="454.95853" + r="11.005301" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient12733" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.89894,0,0,0.89894,-322.5201,-347.1671)" + x1="415.12271" + y1="435.43039" + x2="415.12271" + y2="453.03961" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient12735" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.637663,0,0,0.637663,19.34726,114.1855)" + x1="149.58427" + y1="326.09015" + x2="149.58427" + y2="305.99475" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient12737" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.637663,0,0,0.637663,19.34726,114.1855)" + x1="172.26366" + y1="326.0481" + x2="172.26366" + y2="306.0368" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient9906" + id="linearGradient12752" + gradientUnits="userSpaceOnUse" + x1="417.36929" + y1="435.0079" + x2="417.36929" + y2="454.65622" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient9912" + id="radialGradient12754" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.726147,-8.054512e-3,1.109145e-2,0.999938,109.5649,3.398898)" + cx="418.51233" + cy="454.95853" + fx="418.51233" + fy="454.95853" + r="11.005301" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient12756" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.026533,0,0,1.026533,-375.626,-403.9638)" + x1="415.12271" + y1="435.43039" + x2="415.12271" + y2="453.03961" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient12758" + gradientUnits="userSpaceOnUse" + x1="249.18983" + y1="78.604225" + x2="249.18983" + y2="48.843891" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient12760" + gradientUnits="userSpaceOnUse" + x1="249.18983" + y1="78.604225" + x2="249.18983" + y2="48.843891" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient9906" + id="linearGradient12774" + gradientUnits="userSpaceOnUse" + x1="417.36929" + y1="435.0079" + x2="417.36929" + y2="454.65622" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient9912" + id="radialGradient12776" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.726147,-8.054512e-3,1.109145e-2,0.999938,109.5649,3.398898)" + cx="418.51233" + cy="454.95853" + fx="418.51233" + fy="454.95853" + r="11.005301" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient12778" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.026533,0,0,1.026533,-375.626,-403.9638)" + x1="415.12271" + y1="435.43039" + x2="415.12271" + y2="453.03961" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient12780" + gradientUnits="userSpaceOnUse" + x1="249.18983" + y1="78.604225" + x2="249.18983" + y2="48.843891" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient12782" + gradientUnits="userSpaceOnUse" + x1="249.18983" + y1="78.604225" + x2="249.18983" + y2="48.843891" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient9906" + id="linearGradient12796" + gradientUnits="userSpaceOnUse" + x1="417.36929" + y1="435.0079" + x2="417.36929" + y2="454.65622" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient9912" + id="radialGradient12798" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.726147,-8.054512e-3,1.109145e-2,0.999938,109.5649,3.398898)" + cx="418.51233" + cy="454.95853" + fx="418.51233" + fy="454.95853" + r="11.005301" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient12800" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.026533,0,0,1.026533,-375.626,-403.9638)" + x1="415.12271" + y1="435.43039" + x2="415.12271" + y2="453.03961" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient12802" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.126296,0,0,0.126296,-127.8702,679.3946)" + x1="1462.0935" + y1="1816.7543" + x2="1462.0935" + y2="1632.0582" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient12804" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.126296,0,0,0.126296,-127.8702,679.3946)" + x1="1610.3101" + y1="1816.7543" + x2="1610.3101" + y2="1632.0582" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient9906" + id="linearGradient12818" + gradientUnits="userSpaceOnUse" + x1="417.36929" + y1="435.0079" + x2="417.36929" + y2="454.65622" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient9912" + id="radialGradient12820" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.726147,-8.054512e-3,1.109145e-2,0.999938,109.5649,3.398898)" + cx="418.51233" + cy="454.95853" + fx="418.51233" + fy="454.95853" + r="11.005301" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient12822" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.026533,0,0,1.026533,-375.626,-403.9638)" + x1="415.12271" + y1="435.43039" + x2="415.12271" + y2="453.03961" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient12824" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.126296,0,0,0.126296,-127.8702,679.3946)" + x1="1462.0935" + y1="1816.7543" + x2="1462.0935" + y2="1632.0582" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient12826" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.126296,0,0,0.126296,-127.8702,679.3946)" + x1="1610.3101" + y1="1816.7543" + x2="1610.3101" + y2="1632.0582" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient9906" + id="linearGradient12840" + gradientUnits="userSpaceOnUse" + x1="417.36929" + y1="435.0079" + x2="417.36929" + y2="454.65622" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient9912" + id="radialGradient12842" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.726147,-8.054512e-3,1.109145e-2,0.999938,109.5649,3.398898)" + cx="418.51233" + cy="454.95853" + fx="418.51233" + fy="454.95853" + r="11.005301" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient12844" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.026533,0,0,1.026533,-375.626,-403.9638)" + x1="415.12271" + y1="435.43039" + x2="415.12271" + y2="453.03961" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient12846" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.667151,0,0,0.667151,-54.65523,-187.8396)" + x1="310.68066" + y1="557.73413" + x2="310.68066" + y2="520.34814" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient12848" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.667151,0,0,0.667151,-54.65523,-187.8396)" + x1="343.24658" + y1="557.73413" + x2="343.24658" + y2="520.34814" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient9906" + id="linearGradient12862" + gradientUnits="userSpaceOnUse" + x1="417.36929" + y1="435.0079" + x2="417.36929" + y2="454.65622" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient9912" + id="radialGradient12864" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.726147,-8.054512e-3,1.109145e-2,0.999938,109.5649,3.398898)" + cx="418.51233" + cy="454.95853" + fx="418.51233" + fy="454.95853" + r="11.005301" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient12866" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.026533,0,0,1.026533,-375.626,-403.9638)" + x1="415.12271" + y1="435.43039" + x2="415.12271" + y2="453.03961" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient12868" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.667151,0,0,0.667151,-54.65523,-187.8396)" + x1="310.68066" + y1="557.73413" + x2="310.68066" + y2="520.34814" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient12870" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.667151,0,0,0.667151,-54.65523,-187.8396)" + x1="343.24658" + y1="557.73413" + x2="343.24658" + y2="520.34814" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient9906" + id="linearGradient12894" + gradientUnits="userSpaceOnUse" + x1="417.36929" + y1="435.0079" + x2="417.36929" + y2="454.65622" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient9912" + id="radialGradient12896" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.726147,-8.054512e-3,1.109145e-2,0.999938,109.5649,3.398898)" + cx="418.51233" + cy="454.95853" + fx="418.51233" + fy="454.95853" + r="11.005301" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient12898" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.026533,0,0,1.026533,-375.626,-403.9638)" + x1="415.12271" + y1="435.43039" + x2="415.12271" + y2="453.03961" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient12900" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(7.489322e-2,-1.050117e-2,9.824327e-3,8.005286e-2,163.7889,177.7986)" + spreadMethod="reflect" + x1="-152.27144" + y1="500.10864" + x2="-111.59152" + y2="189.99583" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient12902" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.100408,0,0,0.108373,12.7033,142.1342)" + x1="1560.2874" + y1="715.00372" + x2="1560.2874" + y2="478.50854" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient9906" + id="linearGradient12918" + gradientUnits="userSpaceOnUse" + x1="417.36929" + y1="435.0079" + x2="417.36929" + y2="454.65622" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient9912" + id="radialGradient12920" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.726147,-8.054512e-3,1.109145e-2,0.999938,109.5649,3.398898)" + cx="418.51233" + cy="454.95853" + fx="418.51233" + fy="454.95853" + r="11.005301" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient12922" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.026533,0,0,1.026533,-375.626,-403.9638)" + x1="415.12271" + y1="435.43039" + x2="415.12271" + y2="453.03961" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient12924" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(7.489322e-2,-1.050117e-2,9.824327e-3,8.005286e-2,163.7889,177.7986)" + spreadMethod="reflect" + x1="-152.27144" + y1="500.10864" + x2="-111.59152" + y2="189.99583" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient12926" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.100408,0,0,0.108373,12.7033,142.1342)" + x1="1560.2874" + y1="715.00372" + x2="1560.2874" + y2="478.50854" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient9906" + id="linearGradient12968" + gradientUnits="userSpaceOnUse" + x1="417.36929" + y1="435.0079" + x2="417.36929" + y2="454.65622" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient9912" + id="radialGradient12970" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.726147,-8.054512e-3,1.109145e-2,0.999938,109.5649,3.398898)" + cx="418.51233" + cy="454.95853" + fx="418.51233" + fy="454.95853" + r="11.005301" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient12972" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.026533,0,0,1.026533,-375.626,-403.9638)" + x1="415.12271" + y1="435.43039" + x2="415.12271" + y2="453.03961" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient12974" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.156181,0,0,1.010735,-25.34181,-1.937604)" + x1="155.93187" + y1="250.12737" + x2="155.93187" + y2="241.29185" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient12976" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.156181,0,0,1.010735,-25.34181,-1.937604)" + x1="162.65588" + y1="250.12737" + x2="162.65588" + y2="241.29185" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient12978" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.442363,0,0,1,-71.25107,0)" + x1="160.18631" + y1="243.74049" + x2="160.18631" + y2="230.55022" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient12980" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.442363,0,0,1,-71.25107,0)" + x1="162.38216" + y1="243.74049" + x2="162.38216" + y2="230.55022" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="radialGradient12982" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(2.014563,-1.079665e-5,8.168247e-6,1.438281,-163.45,-106.6208)" + cx="161.10194" + cy="243.27481" + fx="161.10194" + fy="243.27481" + r="1.868932" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient12984" + gradientUnits="userSpaceOnUse" + x1="163.22453" + y1="245.33675" + x2="163.22453" + y2="241.21288" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient9906" + id="linearGradient13026" + gradientUnits="userSpaceOnUse" + x1="417.36929" + y1="435.0079" + x2="417.36929" + y2="454.65622" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient9912" + id="radialGradient13028" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.726147,-8.054512e-3,1.109145e-2,0.999938,109.5649,3.398898)" + cx="418.51233" + cy="454.95853" + fx="418.51233" + fy="454.95853" + r="11.005301" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient13030" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.026533,0,0,1.026533,-375.626,-403.9638)" + x1="415.12271" + y1="435.43039" + x2="415.12271" + y2="453.03961" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient13032" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.156181,0,0,1.010735,-25.34181,-1.937604)" + x1="155.93187" + y1="250.12737" + x2="155.93187" + y2="241.29185" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient13034" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.156181,0,0,1.010735,-25.34181,-1.937604)" + x1="162.65588" + y1="250.12737" + x2="162.65588" + y2="241.29185" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient13036" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.442363,0,0,1,-71.25107,0)" + x1="160.18631" + y1="243.74049" + x2="160.18631" + y2="230.55022" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient13038" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.442363,0,0,1,-71.25107,0)" + x1="162.38216" + y1="243.74049" + x2="162.38216" + y2="230.55022" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="radialGradient13040" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(2.014563,-1.079665e-5,8.168247e-6,1.438281,-163.45,-106.6208)" + cx="161.10194" + cy="243.27481" + fx="161.10194" + fy="243.27481" + r="1.868932" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient13042" + gradientUnits="userSpaceOnUse" + x1="163.22453" + y1="245.33675" + x2="163.22453" + y2="241.21288" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient9906" + id="linearGradient13051" + gradientUnits="userSpaceOnUse" + x1="417.36929" + y1="435.0079" + x2="417.36929" + y2="454.65622" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient9912" + id="radialGradient13053" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.726147,-8.054512e-3,1.109145e-2,0.999938,109.5649,3.398898)" + cx="418.51233" + cy="454.95853" + fx="418.51233" + fy="454.95853" + r="11.005301" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient13055" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.026533,0,0,1.026533,-375.626,-403.9638)" + x1="415.12271" + y1="435.43039" + x2="415.12271" + y2="453.03961" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient14711" + id="linearGradient13057" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.563327e-3,0.887982,-0.887982,1.532215e-3,281.9695,1010.228)" + x1="159.32025" + y1="256.35764" + x2="175.68762" + y2="256.38644" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient13392" + id="linearGradient13059" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.563327e-3,0.887982,-0.887982,1.532215e-3,281.9695,1010.228)" + x1="175.71587" + y1="240.01489" + x2="159.3485" + y2="239.98607" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient9906" + id="linearGradient13073" + gradientUnits="userSpaceOnUse" + x1="417.36929" + y1="435.0079" + x2="417.36929" + y2="454.65622" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient9912" + id="radialGradient13075" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.726147,-8.054512e-3,1.109145e-2,0.999938,109.5649,3.398898)" + cx="418.51233" + cy="454.95853" + fx="418.51233" + fy="454.95853" + r="11.005301" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient13077" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.026533,0,0,1.026533,-375.626,-403.9638)" + x1="415.12271" + y1="435.43039" + x2="415.12271" + y2="453.03961" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient14711" + id="linearGradient13079" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.563327e-3,0.887982,-0.887982,1.532215e-3,281.9695,1010.228)" + x1="159.32025" + y1="256.35764" + x2="175.68762" + y2="256.38644" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient13392" + id="linearGradient13081" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.563327e-3,0.887982,-0.887982,1.532215e-3,281.9695,1010.228)" + x1="175.71587" + y1="240.01489" + x2="159.3485" + y2="239.98607" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient9906" + id="linearGradient13095" + gradientUnits="userSpaceOnUse" + x1="417.36929" + y1="435.0079" + x2="417.36929" + y2="454.65622" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient9912" + id="radialGradient13097" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.726147,-8.054512e-3,1.109145e-2,0.999938,109.5649,3.398898)" + cx="418.51233" + cy="454.95853" + fx="418.51233" + fy="454.95853" + r="11.005301" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient13099" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.026533,0,0,1.026533,-375.626,-403.9638)" + x1="415.12271" + y1="435.43039" + x2="415.12271" + y2="453.03961" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient14711" + id="linearGradient13101" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.563327e-3,0.887982,-0.887982,1.532215e-3,281.9695,1010.228)" + x1="159.32025" + y1="256.35764" + x2="175.68762" + y2="256.38644" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient13392" + id="linearGradient13103" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.563327e-3,0.887982,-0.887982,1.532215e-3,281.9695,1010.228)" + x1="175.71587" + y1="240.01489" + x2="159.3485" + y2="239.98607" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient9906" + id="linearGradient13117" + gradientUnits="userSpaceOnUse" + x1="417.36929" + y1="435.0079" + x2="417.36929" + y2="454.65622" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient9912" + id="radialGradient13119" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.726147,-8.054512e-3,1.109145e-2,0.999938,109.5649,3.398898)" + cx="418.51233" + cy="454.95853" + fx="418.51233" + fy="454.95853" + r="11.005301" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient13121" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.026533,0,0,1.026533,-375.626,-403.9638)" + x1="415.12271" + y1="435.43039" + x2="415.12271" + y2="453.03961" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient13123" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(-3.166181e-2,0.507886)" + x1="149.62633" + y1="326.0481" + x2="149.62633" + y2="306.0368" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient13125" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(-3.166181e-2,0.507886)" + x1="172.26367" + y1="326.0481" + x2="172.26367" + y2="306.0368" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient9906" + id="linearGradient13139" + gradientUnits="userSpaceOnUse" + x1="417.36929" + y1="435.0079" + x2="417.36929" + y2="454.65622" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient9912" + id="radialGradient13141" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.726147,-8.054512e-3,1.109145e-2,0.999938,109.5649,3.398898)" + cx="418.51233" + cy="454.95853" + fx="418.51233" + fy="454.95853" + r="11.005301" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient13143" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.026533,0,0,1.026533,-375.626,-403.9638)" + x1="415.12271" + y1="435.43039" + x2="415.12271" + y2="453.03961" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient13145" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(-3.166181e-2,0.507886)" + x1="149.62633" + y1="326.0481" + x2="149.62633" + y2="306.0368" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient13147" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(-3.166181e-2,0.507886)" + x1="172.26367" + y1="326.0481" + x2="172.26367" + y2="306.0368" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient9906" + id="linearGradient13149" + gradientUnits="userSpaceOnUse" + x1="417.36929" + y1="435.0079" + x2="417.36929" + y2="454.65622" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient9912" + id="radialGradient13151" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.726147,-8.054512e-3,1.109145e-2,0.999938,109.5649,3.398898)" + cx="418.51233" + cy="454.95853" + fx="418.51233" + fy="454.95853" + r="11.005301" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient13153" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.026533,0,0,1.026533,-375.626,-403.9638)" + x1="415.12271" + y1="435.43039" + x2="415.12271" + y2="453.03961" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient13155" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(7.489322e-2,-1.050117e-2,9.824327e-3,8.005286e-2,163.7889,177.7986)" + spreadMethod="reflect" + x1="-152.27144" + y1="500.10864" + x2="-111.59152" + y2="189.99583" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient13157" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.100408,0,0,0.108373,12.7033,142.1342)" + x1="1560.2874" + y1="715.00372" + x2="1560.2874" + y2="478.50854" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient13632" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.515861,0,0,1.495984,89.34967,-82.05745)" + x1="101.35825" + y1="47.461205" + x2="101.35667" + y2="43.430031" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient13634" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.515861,0,0,1.604454,89.34967,-86.75818)" + x1="122.46678" + y1="58.964745" + x2="122.46678" + y2="43.42371" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4182" + id="linearGradient13636" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.62008,0,0,1.577605,77.79409,-86.55158)" + x1="111.84524" + y1="48.367947" + x2="111.84524" + y2="58.933025" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4166" + id="linearGradient13638" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.339136,0,0,0.334773,-30.70507,-73.43762)" + x1="829.4079" + y1="212.95419" + x2="829.4079" + y2="183.47195" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4190" + id="radialGradient13640" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(2.044377,1.047261e-5,-8.549617e-6,1.640387,30.62339,-95.37034)" + cx="111.70066" + cy="62.211178" + fx="111.70066" + fy="62.211178" + r="8.8008003" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient13642" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.480783,0,0,1.536549,93.27523,-83.78014)" + spreadMethod="reflect" + x1="111.33885" + y1="44.705486" + x2="111.33885" + y2="46.731094" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient13660" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.515861,0,0,1.495984,176.2067,800.6235)" + x1="101.35825" + y1="47.461205" + x2="101.35667" + y2="43.430031" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient13662" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.515861,0,0,1.604454,176.2067,795.9227)" + x1="122.46678" + y1="58.964745" + x2="122.46678" + y2="43.42371" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4182" + id="linearGradient13664" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.62008,0,0,1.577605,164.6511,796.1293)" + x1="111.84524" + y1="48.367947" + x2="111.84524" + y2="58.933025" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4166" + id="linearGradient13666" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.339136,0,0,0.334773,56.15202,809.2433)" + x1="829.4079" + y1="212.95419" + x2="829.4079" + y2="183.47195" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4190" + id="radialGradient13668" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(2.044377,1.047261e-5,-8.549617e-6,1.640387,117.4804,787.3106)" + cx="111.70066" + cy="62.211178" + fx="111.70066" + fy="62.211178" + r="8.8008003" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient13670" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.480783,0,0,1.536549,180.1323,798.9008)" + spreadMethod="reflect" + x1="111.33885" + y1="44.705486" + x2="111.33885" + y2="46.731094" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient13672" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.031982,0,0,1.032261,106.9031,-54.06894)" + x1="101.25272" + y1="49.529297" + x2="101.25272" + y2="43.32613" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient13674" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.031982,0,0,1.107109,106.9031,-57.31254)" + x1="122.57088" + y1="59.06163" + x2="122.57088" + y2="43.326801" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4182" + id="linearGradient13676" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.038287,0,0,1.017566,106.2802,-53.32718)" + x1="111.84524" + y1="48.367947" + x2="111.84524" + y2="58.933025" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4166" + id="linearGradient13678" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.217348,0,0,0.215931,36.74451,-44.86859)" + x1="829.4079" + y1="212.95419" + x2="829.4079" + y2="183.47195" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4190" + id="radialGradient13680" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.296464,6.961497e-6,-5.421832e-6,1.090421,77.58478,-60.94513)" + cx="111.70066" + cy="62.211178" + fx="111.70066" + fy="62.211178" + r="8.8008003" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient13682" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.013541,0,0,0.982207,109.0253,-51.62793)" + spreadMethod="reflect" + x1="111.33885" + y1="44.705486" + x2="111.33885" + y2="46.731094" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient11767" + id="linearGradient13684" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.22305,-6.682459e-3,6.682459e-3,0.22305,9.109984,12.81484)" + x1="839.11597" + y1="183.80774" + x2="837.56323" + y2="235.63457" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient11767" + id="linearGradient13686" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.22315,0,0,0.22315,10.41841,7.054327)" + x1="844.37262" + y1="238.3936" + x2="846.35309" + y2="185.10175" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7184" + id="linearGradient13688" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.22315,0,0,0.22315,18.73108,59.9264)" + x1="861.005" + y1="248.9993" + x2="859.50507" + y2="179.15657" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient13690" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.515861,0,0,1.495984,89.34967,-82.05745)" + x1="101.35825" + y1="47.461205" + x2="101.35667" + y2="43.430031" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient13692" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.515861,0,0,1.604454,89.34967,-86.75818)" + x1="122.46678" + y1="58.964745" + x2="122.46678" + y2="43.42371" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4182" + id="linearGradient13694" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.62008,0,0,1.577605,77.79409,-86.55158)" + x1="111.84524" + y1="48.367947" + x2="111.84524" + y2="58.933025" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4166" + id="linearGradient13696" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.339136,0,0,0.334773,-30.70507,-73.43762)" + x1="829.4079" + y1="212.95419" + x2="829.4079" + y2="183.47195" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4190" + id="radialGradient13698" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(2.044377,1.047261e-5,-8.549617e-6,1.640387,30.62339,-95.37034)" + cx="111.70066" + cy="62.211178" + fx="111.70066" + fy="62.211178" + r="8.8008003" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient13700" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.480783,0,0,1.536549,93.27523,-83.78014)" + spreadMethod="reflect" + x1="111.33885" + y1="44.705486" + x2="111.33885" + y2="46.731094" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient11767" + id="linearGradient13702" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.22305,-6.682459e-3,6.682459e-3,0.22305,9.109984,12.81484)" + x1="839.11597" + y1="183.80774" + x2="837.56323" + y2="235.63457" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient11767" + id="linearGradient13704" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.22315,0,0,0.22315,10.41841,7.054327)" + x1="844.37262" + y1="238.3936" + x2="846.35309" + y2="185.10175" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7184" + id="linearGradient13706" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.22315,0,0,0.22315,18.73108,59.9264)" + x1="861.005" + y1="248.9993" + x2="859.50507" + y2="179.15657" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient13708" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.031982,0,0,1.032261,106.9031,-54.06894)" + x1="101.25272" + y1="49.529297" + x2="101.25272" + y2="43.32613" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient13710" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.031982,0,0,1.107109,106.9031,-57.31254)" + x1="122.57088" + y1="59.06163" + x2="122.57088" + y2="43.326801" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4182" + id="linearGradient13712" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.038287,0,0,1.017566,106.2802,-53.32718)" + x1="111.84524" + y1="48.367947" + x2="111.84524" + y2="58.933025" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4166" + id="linearGradient13714" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.217348,0,0,0.215931,36.74451,-44.86859)" + x1="829.4079" + y1="212.95419" + x2="829.4079" + y2="183.47195" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4190" + id="radialGradient13716" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.296464,6.961497e-6,-5.421832e-6,1.090421,77.58478,-60.94513)" + cx="111.70066" + cy="62.211178" + fx="111.70066" + fy="62.211178" + r="8.8008003" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient13718" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.013541,0,0,0.982207,109.0253,-51.62793)" + spreadMethod="reflect" + x1="111.33885" + y1="44.705486" + x2="111.33885" + y2="46.731094" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7184" + id="linearGradient13720" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.22315,0,0,0.22315,119.1941,170.4369)" + x1="861.005" + y1="248.9993" + x2="859.50507" + y2="179.15657" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7184" + id="linearGradient13722" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.324616,0,0,0.324616,69.99073,883.8175)" + x1="861.005" + y1="248.9993" + x2="859.50507" + y2="179.15657" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient13724" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.515861,0,0,1.495984,89.34967,-82.05745)" + x1="101.35825" + y1="47.461205" + x2="101.35667" + y2="43.430031" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient13726" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.515861,0,0,1.604454,89.34967,-86.75818)" + x1="122.46678" + y1="58.964745" + x2="122.46678" + y2="43.42371" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4182" + id="linearGradient13728" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.62008,0,0,1.577605,77.79409,-86.55158)" + x1="111.84524" + y1="48.367947" + x2="111.84524" + y2="58.933025" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4166" + id="linearGradient13730" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.339136,0,0,0.334773,-30.70507,-73.43762)" + x1="829.4079" + y1="212.95419" + x2="829.4079" + y2="183.47195" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4190" + id="radialGradient13732" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(2.044377,1.047261e-5,-8.549617e-6,1.640387,30.62339,-95.37034)" + cx="111.70066" + cy="62.211178" + fx="111.70066" + fy="62.211178" + r="8.8008003" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient13734" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.480783,0,0,1.536549,93.27523,-83.78014)" + spreadMethod="reflect" + x1="111.33885" + y1="44.705486" + x2="111.33885" + y2="46.731094" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient13736" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.469833,0.469817,-0.469833,0.469817,404.0479,266.3918)" + x1="168.18773" + y1="260.09671" + x2="156.90306" + y2="248.81204" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient13738" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.469833,0.469817,-0.469833,0.469817,404.0479,266.3918)" + x1="179.47237" + y1="248.81206" + x2="168.18771" + y2="237.52739" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient13740" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.031982,0,0,1.032261,195.9426,31.01802)" + x1="101.25272" + y1="49.529297" + x2="101.25272" + y2="43.32613" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient13742" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.031982,0,0,1.107109,195.9426,27.77442)" + x1="122.57088" + y1="59.06163" + x2="122.57088" + y2="43.326801" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4182" + id="linearGradient13744" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.038287,0,0,1.017566,195.3197,31.75978)" + x1="111.84524" + y1="48.367947" + x2="111.84524" + y2="58.933025" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4166" + id="linearGradient13746" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.217348,0,0,0.215931,125.7841,40.21837)" + x1="829.4079" + y1="212.95419" + x2="829.4079" + y2="183.47195" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4190" + id="radialGradient13748" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.296464,6.961497e-6,-5.421832e-6,1.090421,166.6243,24.14183)" + cx="111.70066" + cy="62.211178" + fx="111.70066" + fy="62.211178" + r="8.8008003" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient13750" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.013541,0,0,0.982207,198.0648,33.45903)" + spreadMethod="reflect" + x1="111.33885" + y1="44.705486" + x2="111.33885" + y2="46.731094" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient13752" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(8.096273e-2,0,0,8.096273e-2,187.0762,-55.3649)" + x1="1462.0917" + y1="1816.752" + x2="1462.0917" + y2="1632.0562" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient13754" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(8.096273e-2,0,0,8.096273e-2,187.0762,-55.3649)" + x1="1610.308" + y1="1816.752" + x2="1610.308" + y2="1632.0562" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient13756" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.515861,0,0,1.495984,170.767,744.2636)" + x1="101.35825" + y1="47.461205" + x2="101.35667" + y2="43.430031" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient13758" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.515861,0,0,1.604454,170.767,739.5628)" + x1="122.46678" + y1="58.964745" + x2="122.46678" + y2="43.42371" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4182" + id="linearGradient13760" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.62008,0,0,1.577605,159.2115,739.7694)" + x1="111.84524" + y1="48.367947" + x2="111.84524" + y2="58.933025" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4166" + id="linearGradient13762" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.339136,0,0,0.334773,50.7123,752.8834)" + x1="829.4079" + y1="212.95419" + x2="829.4079" + y2="183.47195" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4190" + id="radialGradient13764" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(2.044377,1.047261e-5,-8.549617e-6,1.640387,112.0408,730.9507)" + cx="111.70066" + cy="62.211178" + fx="111.70066" + fy="62.211178" + r="8.8008003" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient13766" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.480783,0,0,1.536549,174.6926,742.5409)" + spreadMethod="reflect" + x1="111.33885" + y1="44.705486" + x2="111.33885" + y2="46.731094" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient13768" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.515861,0,0,1.495984,89.34967,-82.05745)" + x1="101.35825" + y1="47.461205" + x2="101.35667" + y2="43.430031" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient13770" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.515861,0,0,1.604454,89.34967,-86.75818)" + x1="122.46678" + y1="58.964745" + x2="122.46678" + y2="43.42371" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4182" + id="linearGradient13772" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.62008,0,0,1.577605,77.79409,-86.55158)" + x1="111.84524" + y1="48.367947" + x2="111.84524" + y2="58.933025" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4166" + id="linearGradient13774" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.339136,0,0,0.334773,-30.70507,-73.43762)" + x1="829.4079" + y1="212.95419" + x2="829.4079" + y2="183.47195" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4190" + id="radialGradient13776" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(2.044377,1.047261e-5,-8.549617e-6,1.640387,30.62339,-95.37034)" + cx="111.70066" + cy="62.211178" + fx="111.70066" + fy="62.211178" + r="8.8008003" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient13778" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.480783,0,0,1.536549,93.27523,-83.78014)" + spreadMethod="reflect" + x1="111.33885" + y1="44.705486" + x2="111.33885" + y2="46.731094" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient13780" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(7.489322e-2,-1.050117e-2,9.824327e-3,8.005286e-2,163.7889,177.7986)" + spreadMethod="reflect" + x1="-152.27144" + y1="500.10864" + x2="-111.59152" + y2="189.99583" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient13782" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.100408,0,0,0.108373,12.7033,142.1342)" + x1="1558.4095" + y1="713.26398" + x2="1558.4095" + y2="480.24832" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient13784" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.031982,0,0,1.032261,106.9031,-54.06894)" + x1="101.25272" + y1="49.529297" + x2="101.25272" + y2="43.32613" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient13786" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.031982,0,0,1.107109,106.9031,-57.31254)" + x1="122.57088" + y1="59.06163" + x2="122.57088" + y2="43.326801" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4182" + id="linearGradient13788" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.038287,0,0,1.017566,106.2802,-53.32718)" + x1="111.84524" + y1="48.367947" + x2="111.84524" + y2="58.933025" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4166" + id="linearGradient13790" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.217348,0,0,0.215931,36.74451,-44.86859)" + x1="829.4079" + y1="212.95419" + x2="829.4079" + y2="183.47195" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4190" + id="radialGradient13792" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.296464,6.961497e-6,-5.421832e-6,1.090421,77.58478,-60.94513)" + cx="111.70066" + cy="62.211178" + fx="111.70066" + fy="62.211178" + r="8.8008003" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient13794" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.013541,0,0,0.982207,109.0253,-51.62793)" + spreadMethod="reflect" + x1="111.33885" + y1="44.705486" + x2="111.33885" + y2="46.731094" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient13796" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(5.148882e-2,-7.219555e-3,6.75419e-3,5.503636e-2,124.6994,186.4847)" + spreadMethod="reflect" + x1="-154.41507" + y1="501.6441" + x2="-113.26653" + y2="187.95882" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient13798" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(6.903014e-2,0,0,7.450646e-2,20.82859,161.9654)" + x1="1559.8582" + y1="714.60614" + x2="1559.8582" + y2="478.90616" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient13800" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.031982,0,0,1.032261,88.29694,135.9676)" + x1="101.25272" + y1="49.529297" + x2="101.25272" + y2="43.32613" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient13802" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.031982,0,0,1.107109,88.29694,132.724)" + x1="122.57088" + y1="59.06163" + x2="122.57088" + y2="43.326801" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4182" + id="linearGradient13804" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.038287,0,0,1.017566,87.67404,136.7093)" + x1="111.84524" + y1="48.367947" + x2="111.84524" + y2="58.933025" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4166" + id="linearGradient13806" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.217348,0,0,0.215931,18.13835,145.1679)" + x1="829.4079" + y1="212.95419" + x2="829.4079" + y2="183.47195" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4190" + id="radialGradient13808" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.296464,6.961497e-6,-5.421832e-6,1.090421,58.97862,129.0914)" + cx="111.70066" + cy="62.211178" + fx="111.70066" + fy="62.211178" + r="8.8008003" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient13810" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.013541,0,0,0.982207,90.41914,138.4086)" + spreadMethod="reflect" + x1="111.33885" + y1="44.705486" + x2="111.33885" + y2="46.731094" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient13812" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(8.716377e-2,2.313213e-2,2.313213e-2,-8.716377e-2,147.8221,604.9863)" + x1="1492.3475" + y1="1614.7871" + x2="1458.5876" + y2="1741.9969" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient13814" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-0.174823,0,0,0.174823,332.6591,478.8481)" + x1="31.778322" + y1="113.87774" + x2="31.778322" + y2="45.986065" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient13816" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.031982,0,0,1.032261,106.9031,-54.06894)" + x1="101.25272" + y1="49.529297" + x2="101.25272" + y2="43.32613" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient13818" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.031982,0,0,1.107109,106.9031,-57.31254)" + x1="122.57088" + y1="59.06163" + x2="122.57088" + y2="43.326801" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4182" + id="linearGradient13820" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.038287,0,0,1.017566,106.2802,-53.32718)" + x1="111.84524" + y1="48.367947" + x2="111.84524" + y2="58.933025" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4166" + id="linearGradient13822" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.217348,0,0,0.215931,36.74451,-44.86859)" + x1="829.4079" + y1="212.95419" + x2="829.4079" + y2="183.47195" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4190" + id="radialGradient13824" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.296464,6.961497e-6,-5.421832e-6,1.090421,77.58478,-60.94513)" + cx="111.70066" + cy="62.211178" + fx="111.70066" + fy="62.211178" + r="8.8008003" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient13826" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.013541,0,0,0.982207,109.0253,-51.62793)" + spreadMethod="reflect" + x1="111.33885" + y1="44.705486" + x2="111.33885" + y2="46.731094" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient13828" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.352385,0.352373,-0.352385,0.352373,352.8199,313.5383)" + x1="168.18773" + y1="260.09671" + x2="156.90306" + y2="248.81204" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient13830" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.352385,0.352373,-0.352385,0.352373,352.8199,313.5383)" + x1="179.47237" + y1="248.81206" + x2="168.18771" + y2="237.52739" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient13832" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.441476,0,0,0.441476,201.3317,642.7581)" + x1="310.68079" + y1="557.73431" + x2="310.68079" + y2="520.34833" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient13834" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.441476,0,0,0.441476,201.3317,642.7581)" + x1="343.24673" + y1="557.73431" + x2="343.24673" + y2="520.34833" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient13836" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.121444,0,0,0.121444,153.4375,612.914)" + x1="1462.0935" + y1="1816.7543" + x2="1462.0935" + y2="1632.0582" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient13838" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.121444,0,0,0.121444,153.4375,612.914)" + x1="1610.3101" + y1="1816.7543" + x2="1610.3101" + y2="1632.0582" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient13852" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.031982,0,0,1.032261,106.9031,-54.06894)" + x1="101.25272" + y1="49.529297" + x2="101.25272" + y2="43.32613" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient13854" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.031982,0,0,1.107109,106.9031,-57.31254)" + x1="122.57088" + y1="59.06163" + x2="122.57088" + y2="43.326801" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4182" + id="linearGradient13856" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.038287,0,0,1.017566,106.2802,-53.32718)" + x1="111.84524" + y1="48.367947" + x2="111.84524" + y2="58.933025" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4166" + id="linearGradient13858" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.217348,0,0,0.215931,36.74451,-44.86859)" + x1="829.4079" + y1="212.95419" + x2="829.4079" + y2="183.47195" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4190" + id="radialGradient13860" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.296464,6.961497e-6,-5.421832e-6,1.090421,77.58478,-60.94513)" + cx="111.70066" + cy="62.211178" + fx="111.70066" + fy="62.211178" + r="8.8008003" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient13862" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.013541,0,0,0.982207,109.0253,-51.62793)" + spreadMethod="reflect" + x1="111.33885" + y1="44.705486" + x2="111.33885" + y2="46.731094" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient13888" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.031982,0,0,1.032261,88.29694,135.9676)" + x1="101.25272" + y1="49.529297" + x2="101.25272" + y2="43.32613" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient13890" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.031982,0,0,1.107109,88.29694,132.724)" + x1="122.57088" + y1="59.06163" + x2="122.57088" + y2="43.326801" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4182" + id="linearGradient13892" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.038287,0,0,1.017566,87.67404,136.7093)" + x1="111.84524" + y1="48.367947" + x2="111.84524" + y2="58.933025" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4166" + id="linearGradient13894" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.217348,0,0,0.215931,18.13835,145.1679)" + x1="829.4079" + y1="212.95419" + x2="829.4079" + y2="183.47195" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4190" + id="radialGradient13896" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.296464,6.961497e-6,-5.421832e-6,1.090421,58.97862,129.0914)" + cx="111.70066" + cy="62.211178" + fx="111.70066" + fy="62.211178" + r="8.8008003" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient13898" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.013541,0,0,0.982207,90.41914,138.4086)" + spreadMethod="reflect" + x1="111.33885" + y1="44.705486" + x2="111.33885" + y2="46.731094" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient13900" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.127329,2.313213e-2,3.379153e-2,-8.716377e-2,112.1554,605.05)" + x1="1492.3475" + y1="1614.7871" + x2="1458.5876" + y2="1741.9969" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient13902" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-0.255382,0,0,0.174823,382.1663,478.9118)" + x1="31.778322" + y1="113.87774" + x2="31.778322" + y2="45.986065" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient13904" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.515861,0,0,1.495984,89.34967,-82.05745)" + x1="101.35825" + y1="47.461205" + x2="101.35667" + y2="43.430031" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient13906" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.515861,0,0,1.604454,89.34967,-86.75818)" + x1="122.46678" + y1="58.964745" + x2="122.46678" + y2="43.42371" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4182" + id="linearGradient13908" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.62008,0,0,1.577605,77.79409,-86.55158)" + x1="111.84524" + y1="48.367947" + x2="111.84524" + y2="58.933025" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4166" + id="linearGradient13910" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.339136,0,0,0.334773,-30.70507,-73.43762)" + x1="829.4079" + y1="212.95419" + x2="829.4079" + y2="183.47195" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4190" + id="radialGradient13912" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(2.044377,1.047261e-5,-8.549617e-6,1.640387,30.62339,-95.37034)" + cx="111.70066" + cy="62.211178" + fx="111.70066" + fy="62.211178" + r="8.8008003" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient13914" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.480783,0,0,1.536549,93.27523,-83.78014)" + spreadMethod="reflect" + x1="111.33885" + y1="44.705486" + x2="111.33885" + y2="46.731094" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient14711" + id="linearGradient13916" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.563327e-3,0.887982,-0.887982,1.532215e-3,558.3919,954.9619)" + x1="159.32025" + y1="256.35764" + x2="175.68762" + y2="256.38644" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient13392" + id="linearGradient13918" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.563327e-3,0.887982,-0.887982,1.532215e-3,558.3919,954.9619)" + x1="175.71587" + y1="240.01489" + x2="159.3485" + y2="239.98607" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient13920" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.031982,0,0,1.032261,106.9031,-54.06894)" + x1="101.25272" + y1="49.529297" + x2="101.25272" + y2="43.32613" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient13922" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.031982,0,0,1.107109,106.9031,-57.31254)" + x1="122.57088" + y1="59.06163" + x2="122.57088" + y2="43.326801" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4182" + id="linearGradient13924" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.038287,0,0,1.017566,106.2802,-53.32718)" + x1="111.84524" + y1="48.367947" + x2="111.84524" + y2="58.933025" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4166" + id="linearGradient13926" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.217348,0,0,0.215931,36.74451,-44.86859)" + x1="829.4079" + y1="212.95419" + x2="829.4079" + y2="183.47195" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4190" + id="radialGradient13928" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.296464,6.961497e-6,-5.421832e-6,1.090421,77.58478,-60.94513)" + cx="111.70066" + cy="62.211178" + fx="111.70066" + fy="62.211178" + r="8.8008003" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient13930" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.013541,0,0,0.982207,109.0253,-51.62793)" + spreadMethod="reflect" + x1="111.33885" + y1="44.705486" + x2="111.33885" + y2="46.731094" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient14711" + id="linearGradient13932" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.027321e-3,0.583526,-0.583526,1.006877e-3,459.0028,233.4818)" + x1="159.32025" + y1="256.35764" + x2="175.68762" + y2="256.38644" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient13392" + id="linearGradient13934" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.027321e-3,0.583526,-0.583526,1.006877e-3,459.0028,233.4818)" + x1="175.71587" + y1="240.01489" + x2="159.3485" + y2="239.98607" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient14033" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.031982,0,0,1.032261,106.9031,-54.06894)" + x1="101.25272" + y1="49.529297" + x2="101.25272" + y2="43.32613" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient14035" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.031982,0,0,1.107109,106.9031,-57.31254)" + x1="122.57088" + y1="59.06163" + x2="122.57088" + y2="43.326801" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4182" + id="linearGradient14037" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.038287,0,0,1.017566,106.2802,-53.32718)" + x1="111.84524" + y1="48.367947" + x2="111.84524" + y2="58.933025" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4166" + id="linearGradient14039" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.217348,0,0,0.215931,36.74451,-44.86859)" + x1="829.4079" + y1="212.95419" + x2="829.4079" + y2="183.47195" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4190" + id="radialGradient14041" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.296464,6.961497e-6,-5.421832e-6,1.090421,77.58478,-60.94513)" + cx="111.70066" + cy="62.211178" + fx="111.70066" + fy="62.211178" + r="8.8008003" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient14043" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.013541,0,0,0.982207,109.0253,-51.62793)" + spreadMethod="reflect" + x1="111.33885" + y1="44.705486" + x2="111.33885" + y2="46.731094" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient14205" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.515861,0,0,1.495984,93.76703,-109.5959)" + x1="101.35825" + y1="47.461205" + x2="101.35667" + y2="43.430031" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient14207" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.515861,0,0,1.604454,93.76703,-114.2966)" + x1="122.46678" + y1="58.964745" + x2="122.46678" + y2="43.42371" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4182" + id="linearGradient14209" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.62008,0,0,1.577605,82.21145,-114.09)" + x1="111.84524" + y1="48.367947" + x2="111.84524" + y2="58.933025" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4166" + id="linearGradient14211" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.339136,0,0,0.334773,-26.28771,-100.9761)" + x1="829.4079" + y1="212.95419" + x2="829.4079" + y2="183.47195" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4190" + id="radialGradient14213" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(2.044377,1.047261e-5,-8.549617e-6,1.640387,35.04075,-122.9088)" + cx="111.70066" + cy="62.211178" + fx="111.70066" + fy="62.211178" + r="8.8008003" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient14215" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.480783,0,0,1.536549,97.69259,-111.3186)" + spreadMethod="reflect" + x1="111.33885" + y1="44.705486" + x2="111.33885" + y2="46.731094" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient14217" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.515861,0,0,1.495984,93.76703,-109.5959)" + x1="101.35825" + y1="47.461205" + x2="101.35667" + y2="43.430031" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient14219" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.515861,0,0,1.604454,93.76703,-114.2966)" + x1="122.46678" + y1="58.964745" + x2="122.46678" + y2="43.42371" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4182" + id="linearGradient14221" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.62008,0,0,1.577605,82.21145,-114.09)" + x1="111.84524" + y1="48.367947" + x2="111.84524" + y2="58.933025" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4166" + id="linearGradient14223" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.339136,0,0,0.334773,-26.28771,-100.9761)" + x1="829.4079" + y1="212.95419" + x2="829.4079" + y2="183.47195" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4190" + id="radialGradient14225" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(2.044377,1.047261e-5,-8.549617e-6,1.640387,35.04075,-122.9088)" + cx="111.70066" + cy="62.211178" + fx="111.70066" + fy="62.211178" + r="8.8008003" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient14227" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.480783,0,0,1.536549,97.69259,-111.3186)" + spreadMethod="reflect" + x1="111.33885" + y1="44.705486" + x2="111.33885" + y2="46.731094" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient14273" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.515861,0,0,1.495984,93.76703,-109.5959)" + x1="101.35825" + y1="47.461205" + x2="101.35667" + y2="43.430031" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient14275" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.515861,0,0,1.604454,93.76703,-114.2966)" + x1="122.46678" + y1="58.964745" + x2="122.46678" + y2="43.42371" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4182" + id="linearGradient14277" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.62008,0,0,1.577605,82.21145,-114.09)" + x1="111.84524" + y1="48.367947" + x2="111.84524" + y2="58.933025" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4166" + id="linearGradient14279" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.339136,0,0,0.334773,-26.28771,-100.9761)" + x1="829.4079" + y1="212.95419" + x2="829.4079" + y2="183.47195" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4190" + id="radialGradient14281" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(2.044377,1.047261e-5,-8.549617e-6,1.640387,35.04075,-122.9088)" + cx="111.70066" + cy="62.211178" + fx="111.70066" + fy="62.211178" + r="8.8008003" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient14283" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.480783,0,0,1.536549,97.69259,-111.3186)" + spreadMethod="reflect" + x1="111.33885" + y1="44.705486" + x2="111.33885" + y2="46.731094" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient14285" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.515861,0,0,1.495984,93.76703,-109.5959)" + x1="101.35825" + y1="47.461205" + x2="101.35667" + y2="43.430031" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient14287" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.515861,0,0,1.604454,93.76703,-114.2966)" + x1="122.46678" + y1="58.964745" + x2="122.46678" + y2="43.42371" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4182" + id="linearGradient14289" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.62008,0,0,1.577605,82.21145,-114.09)" + x1="111.84524" + y1="48.367947" + x2="111.84524" + y2="58.933025" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4166" + id="linearGradient14291" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.339136,0,0,0.334773,-26.28771,-100.9761)" + x1="829.4079" + y1="212.95419" + x2="829.4079" + y2="183.47195" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4190" + id="radialGradient14293" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(2.044377,1.047261e-5,-8.549617e-6,1.640387,35.04075,-122.9088)" + cx="111.70066" + cy="62.211178" + fx="111.70066" + fy="62.211178" + r="8.8008003" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient14295" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.480783,0,0,1.536549,97.69259,-111.3186)" + spreadMethod="reflect" + x1="111.33885" + y1="44.705486" + x2="111.33885" + y2="46.731094" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient14341" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.515861,0,0,1.495984,93.76703,-109.5959)" + x1="101.35825" + y1="47.461205" + x2="101.35667" + y2="43.430031" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient14343" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.515861,0,0,1.604454,93.76703,-114.2966)" + x1="122.46678" + y1="58.964745" + x2="122.46678" + y2="43.42371" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4182" + id="linearGradient14345" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.62008,0,0,1.577605,82.21145,-114.09)" + x1="111.84524" + y1="48.367947" + x2="111.84524" + y2="58.933025" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4166" + id="linearGradient14347" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.339136,0,0,0.334773,-26.28771,-100.9761)" + x1="829.4079" + y1="212.95419" + x2="829.4079" + y2="183.47195" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4190" + id="radialGradient14349" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(2.044377,1.047261e-5,-8.549617e-6,1.640387,35.04075,-122.9088)" + cx="111.70066" + cy="62.211178" + fx="111.70066" + fy="62.211178" + r="8.8008003" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient14351" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.480783,0,0,1.536549,97.69259,-111.3186)" + spreadMethod="reflect" + x1="111.33885" + y1="44.705486" + x2="111.33885" + y2="46.731094" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient14387" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.515861,0,0,1.495984,170.767,744.2636)" + x1="101.35825" + y1="47.461205" + x2="101.35667" + y2="43.430031" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient14389" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.515861,0,0,1.604454,170.767,739.5628)" + x1="122.46678" + y1="58.964745" + x2="122.46678" + y2="43.42371" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4182" + id="linearGradient14391" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.62008,0,0,1.577605,159.2115,739.7694)" + x1="111.84524" + y1="48.367947" + x2="111.84524" + y2="58.933025" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4166" + id="linearGradient14393" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.339136,0,0,0.334773,50.7123,752.8834)" + x1="829.4079" + y1="212.95419" + x2="829.4079" + y2="183.47195" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4190" + id="radialGradient14395" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(2.044377,1.047261e-5,-8.549617e-6,1.640387,112.0408,730.9507)" + cx="111.70066" + cy="62.211178" + fx="111.70066" + fy="62.211178" + r="8.8008003" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient14397" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.480783,0,0,1.536549,174.6926,742.5409)" + spreadMethod="reflect" + x1="111.33885" + y1="44.705486" + x2="111.33885" + y2="46.731094" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient14399" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.121444,0,0,0.121444,153.4375,612.914)" + x1="1462.0935" + y1="1816.7543" + x2="1462.0935" + y2="1632.0582" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient14401" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.121444,0,0,0.121444,153.4375,612.914)" + x1="1610.3101" + y1="1816.7543" + x2="1610.3101" + y2="1632.0582" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient14465" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.031982,0,0,1.032261,195.9426,31.01802)" + x1="101.25272" + y1="49.529297" + x2="101.25272" + y2="43.32613" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient14467" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.031982,0,0,1.107109,195.9426,27.77442)" + x1="122.57088" + y1="59.06163" + x2="122.57088" + y2="43.326801" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4182" + id="linearGradient14469" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.038287,0,0,1.017566,195.3197,31.75978)" + x1="111.84524" + y1="48.367947" + x2="111.84524" + y2="58.933025" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4166" + id="linearGradient14471" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.217348,0,0,0.215931,125.7841,40.21837)" + x1="829.4079" + y1="212.95419" + x2="829.4079" + y2="183.47195" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4190" + id="radialGradient14473" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.296464,6.961497e-6,-5.421832e-6,1.090421,166.6243,24.14183)" + cx="111.70066" + cy="62.211178" + fx="111.70066" + fy="62.211178" + r="8.8008003" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient14475" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.013541,0,0,0.982207,198.0648,33.45903)" + spreadMethod="reflect" + x1="111.33885" + y1="44.705486" + x2="111.33885" + y2="46.731094" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient14477" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(8.096273e-2,0,0,8.096273e-2,187.0762,-55.3649)" + x1="1462.0917" + y1="1816.752" + x2="1462.0917" + y2="1632.0562" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient14479" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(8.096273e-2,0,0,8.096273e-2,187.0762,-55.3649)" + x1="1610.308" + y1="1816.752" + x2="1610.308" + y2="1632.0562" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient14503" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.031982,0,0,1.032261,203.4626,82.51014)" + x1="101.25272" + y1="49.529297" + x2="101.25272" + y2="43.32613" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient14505" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.031982,0,0,1.107109,203.4626,79.26654)" + x1="122.57088" + y1="59.06163" + x2="122.57088" + y2="43.326801" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4182" + id="linearGradient14507" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.038287,0,0,1.017566,202.8397,83.2519)" + x1="111.84524" + y1="48.367947" + x2="111.84524" + y2="58.933025" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4166" + id="linearGradient14509" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.217348,0,0,0.215931,133.304,91.71049)" + x1="829.4079" + y1="212.95419" + x2="829.4079" + y2="183.47195" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4190" + id="radialGradient14511" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.296464,6.961497e-6,-5.421832e-6,1.090421,174.1442,75.63395)" + cx="111.70066" + cy="62.211178" + fx="111.70066" + fy="62.211178" + r="8.8008003" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient14513" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.013541,0,0,0.982207,205.5848,84.95115)" + spreadMethod="reflect" + x1="111.33885" + y1="44.705486" + x2="111.33885" + y2="46.731094" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient14515" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.303515,0,0,0.303515,219.7058,-25.8645)" + x1="310.68051" + y1="557.73425" + x2="310.68051" + y2="520.34827" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient14517" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.303515,0,0,0.303515,219.7058,-25.8645)" + x1="343.24649" + y1="557.73425" + x2="343.24649" + y2="520.34827" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient14541" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.515861,0,0,1.495984,176.2067,800.6235)" + x1="101.35825" + y1="47.461205" + x2="101.35667" + y2="43.430031" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient14543" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.515861,0,0,1.604454,176.2067,795.9227)" + x1="122.46678" + y1="58.964745" + x2="122.46678" + y2="43.42371" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4182" + id="linearGradient14545" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.62008,0,0,1.577605,164.6511,796.1293)" + x1="111.84524" + y1="48.367947" + x2="111.84524" + y2="58.933025" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4166" + id="linearGradient14547" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.339136,0,0,0.334773,56.15202,809.2433)" + x1="829.4079" + y1="212.95419" + x2="829.4079" + y2="183.47195" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4190" + id="radialGradient14549" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(2.044377,1.047261e-5,-8.549617e-6,1.640387,117.4804,787.3106)" + cx="111.70066" + cy="62.211178" + fx="111.70066" + fy="62.211178" + r="8.8008003" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient14551" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.480783,0,0,1.536549,180.1323,798.9008)" + spreadMethod="reflect" + x1="111.33885" + y1="44.705486" + x2="111.33885" + y2="46.731094" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient14553" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.441476,0,0,0.441476,201.3317,642.7581)" + x1="310.68079" + y1="557.73431" + x2="310.68079" + y2="520.34833" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient14555" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.441476,0,0,0.441476,201.3317,642.7581)" + x1="343.24673" + y1="557.73431" + x2="343.24673" + y2="520.34833" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient14579" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.515861,0,0,1.495984,176.2067,800.6235)" + x1="101.35825" + y1="47.461205" + x2="101.35667" + y2="43.430031" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient14581" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.515861,0,0,1.604454,176.2067,795.9227)" + x1="122.46678" + y1="58.964745" + x2="122.46678" + y2="43.42371" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4182" + id="linearGradient14583" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.62008,0,0,1.577605,164.6511,796.1293)" + x1="111.84524" + y1="48.367947" + x2="111.84524" + y2="58.933025" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4166" + id="linearGradient14585" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.339136,0,0,0.334773,56.15202,809.2433)" + x1="829.4079" + y1="212.95419" + x2="829.4079" + y2="183.47195" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4190" + id="radialGradient14587" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(2.044377,1.047261e-5,-8.549617e-6,1.640387,117.4804,787.3106)" + cx="111.70066" + cy="62.211178" + fx="111.70066" + fy="62.211178" + r="8.8008003" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient14589" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.480783,0,0,1.536549,180.1323,798.9008)" + spreadMethod="reflect" + x1="111.33885" + y1="44.705486" + x2="111.33885" + y2="46.731094" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient14591" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.441476,0,0,0.441476,201.3317,642.7581)" + x1="310.68079" + y1="557.73431" + x2="310.68079" + y2="520.34833" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient14593" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.441476,0,0,0.441476,201.3317,642.7581)" + x1="343.24673" + y1="557.73431" + x2="343.24673" + y2="520.34833" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient14619" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.031982,0,0,1.032261,106.9031,-54.06894)" + x1="101.25272" + y1="49.529297" + x2="101.25272" + y2="43.32613" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient14621" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.031982,0,0,1.107109,106.9031,-57.31254)" + x1="122.57088" + y1="59.06163" + x2="122.57088" + y2="43.326801" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4182" + id="linearGradient14623" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.038287,0,0,1.017566,106.2802,-53.32718)" + x1="111.84524" + y1="48.367947" + x2="111.84524" + y2="58.933025" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4166" + id="linearGradient14625" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.217348,0,0,0.215931,36.74451,-44.86859)" + x1="829.4079" + y1="212.95419" + x2="829.4079" + y2="183.47195" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4190" + id="radialGradient14627" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.296464,6.961497e-6,-5.421832e-6,1.090421,77.58478,-60.94513)" + cx="111.70066" + cy="62.211178" + fx="111.70066" + fy="62.211178" + r="8.8008003" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient14629" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.013541,0,0,0.982207,109.0253,-51.62793)" + spreadMethod="reflect" + x1="111.33885" + y1="44.705486" + x2="111.33885" + y2="46.731094" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7184" + id="linearGradient14631" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.22315,0,0,0.22315,119.1941,170.4369)" + x1="861.005" + y1="248.9993" + x2="859.50507" + y2="179.15657" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient14657" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.515861,0,0,1.495984,89.34967,-82.05745)" + x1="101.35825" + y1="47.461205" + x2="101.35667" + y2="43.430031" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient14659" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.515861,0,0,1.604454,89.34967,-86.75818)" + x1="122.46678" + y1="58.964745" + x2="122.46678" + y2="43.42371" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4182" + id="linearGradient14661" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.62008,0,0,1.577605,77.79409,-86.55158)" + x1="111.84524" + y1="48.367947" + x2="111.84524" + y2="58.933025" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4166" + id="linearGradient14663" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.339136,0,0,0.334773,-30.70507,-73.43762)" + x1="829.4079" + y1="212.95419" + x2="829.4079" + y2="183.47195" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4190" + id="radialGradient14665" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(2.044377,1.047261e-5,-8.549617e-6,1.640387,30.62339,-95.37034)" + cx="111.70066" + cy="62.211178" + fx="111.70066" + fy="62.211178" + r="8.8008003" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient14667" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.480783,0,0,1.536549,93.27523,-83.78014)" + spreadMethod="reflect" + x1="111.33885" + y1="44.705486" + x2="111.33885" + y2="46.731094" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7184" + id="linearGradient14669" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.324616,0,0,0.324616,69.99073,883.8175)" + x1="861.005" + y1="248.9993" + x2="859.50507" + y2="179.15657" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient14695" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.515861,0,0,1.495984,89.34967,-82.05745)" + x1="101.35825" + y1="47.461205" + x2="101.35667" + y2="43.430031" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient14697" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.515861,0,0,1.604454,89.34967,-86.75818)" + x1="122.46678" + y1="58.964745" + x2="122.46678" + y2="43.42371" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4182" + id="linearGradient14699" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.62008,0,0,1.577605,77.79409,-86.55158)" + x1="111.84524" + y1="48.367947" + x2="111.84524" + y2="58.933025" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4166" + id="linearGradient14701" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.339136,0,0,0.334773,-30.70507,-73.43762)" + x1="829.4079" + y1="212.95419" + x2="829.4079" + y2="183.47195" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4190" + id="radialGradient14703" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(2.044377,1.047261e-5,-8.549617e-6,1.640387,30.62339,-95.37034)" + cx="111.70066" + cy="62.211178" + fx="111.70066" + fy="62.211178" + r="8.8008003" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient14705" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.480783,0,0,1.536549,93.27523,-83.78014)" + spreadMethod="reflect" + x1="111.33885" + y1="44.705486" + x2="111.33885" + y2="46.731094" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7184" + id="linearGradient14707" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.324616,0,0,0.324616,69.99073,883.8175)" + x1="861.005" + y1="248.9993" + x2="859.50507" + y2="179.15657" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient14741" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.031982,0,0,1.032261,106.9031,-54.06894)" + x1="101.25272" + y1="49.529297" + x2="101.25272" + y2="43.32613" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient14743" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.031982,0,0,1.107109,106.9031,-57.31254)" + x1="122.57088" + y1="59.06163" + x2="122.57088" + y2="43.326801" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4182" + id="linearGradient14745" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.038287,0,0,1.017566,106.2802,-53.32718)" + x1="111.84524" + y1="48.367947" + x2="111.84524" + y2="58.933025" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4166" + id="linearGradient14747" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.217348,0,0,0.215931,36.74451,-44.86859)" + x1="829.4079" + y1="212.95419" + x2="829.4079" + y2="183.47195" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4190" + id="radialGradient14749" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.296464,6.961497e-6,-5.421832e-6,1.090421,77.58478,-60.94513)" + cx="111.70066" + cy="62.211178" + fx="111.70066" + fy="62.211178" + r="8.8008003" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient14751" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.013541,0,0,0.982207,109.0253,-51.62793)" + spreadMethod="reflect" + x1="111.33885" + y1="44.705486" + x2="111.33885" + y2="46.731094" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient11767" + id="linearGradient14753" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.22305,-6.682459e-3,6.682459e-3,0.22305,9.109984,12.81484)" + x1="839.11597" + y1="183.80774" + x2="837.56323" + y2="235.63457" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient11767" + id="linearGradient14755" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.22315,0,0,0.22315,10.41841,7.054327)" + x1="844.37262" + y1="238.3936" + x2="846.35309" + y2="185.10175" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7184" + id="linearGradient14757" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.22315,0,0,0.22315,18.73108,59.9264)" + x1="861.005" + y1="248.9993" + x2="859.50507" + y2="179.15657" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient14791" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.515861,0,0,1.495984,89.34967,-82.05745)" + x1="101.35825" + y1="47.461205" + x2="101.35667" + y2="43.430031" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient14793" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.515861,0,0,1.604454,89.34967,-86.75818)" + x1="122.46678" + y1="58.964745" + x2="122.46678" + y2="43.42371" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4182" + id="linearGradient14795" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.62008,0,0,1.577605,77.79409,-86.55158)" + x1="111.84524" + y1="48.367947" + x2="111.84524" + y2="58.933025" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4166" + id="linearGradient14797" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.339136,0,0,0.334773,-30.70507,-73.43762)" + x1="829.4079" + y1="212.95419" + x2="829.4079" + y2="183.47195" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4190" + id="radialGradient14799" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(2.044377,1.047261e-5,-8.549617e-6,1.640387,30.62339,-95.37034)" + cx="111.70066" + cy="62.211178" + fx="111.70066" + fy="62.211178" + r="8.8008003" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient14801" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.480783,0,0,1.536549,93.27523,-83.78014)" + spreadMethod="reflect" + x1="111.33885" + y1="44.705486" + x2="111.33885" + y2="46.731094" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient11767" + id="linearGradient14803" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.22305,-6.682459e-3,6.682459e-3,0.22305,9.109984,12.81484)" + x1="839.11597" + y1="183.80774" + x2="837.56323" + y2="235.63457" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient11767" + id="linearGradient14805" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.22315,0,0,0.22315,10.41841,7.054327)" + x1="844.37262" + y1="238.3936" + x2="846.35309" + y2="185.10175" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7184" + id="linearGradient14807" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.22315,0,0,0.22315,18.73108,59.9264)" + x1="861.005" + y1="248.9993" + x2="859.50507" + y2="179.15657" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient14841" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.515861,0,0,1.495984,89.34967,-82.05745)" + x1="101.35825" + y1="47.461205" + x2="101.35667" + y2="43.430031" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient14843" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.515861,0,0,1.604454,89.34967,-86.75818)" + x1="122.46678" + y1="58.964745" + x2="122.46678" + y2="43.42371" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4182" + id="linearGradient14845" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.62008,0,0,1.577605,77.79409,-86.55158)" + x1="111.84524" + y1="48.367947" + x2="111.84524" + y2="58.933025" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4166" + id="linearGradient14847" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.339136,0,0,0.334773,-30.70507,-73.43762)" + x1="829.4079" + y1="212.95419" + x2="829.4079" + y2="183.47195" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4190" + id="radialGradient14849" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(2.044377,1.047261e-5,-8.549617e-6,1.640387,30.62339,-95.37034)" + cx="111.70066" + cy="62.211178" + fx="111.70066" + fy="62.211178" + r="8.8008003" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient14851" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.480783,0,0,1.536549,93.27523,-83.78014)" + spreadMethod="reflect" + x1="111.33885" + y1="44.705486" + x2="111.33885" + y2="46.731094" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient11767" + id="linearGradient14853" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.22305,-6.682459e-3,6.682459e-3,0.22305,9.109984,12.81484)" + x1="839.11597" + y1="183.80774" + x2="837.56323" + y2="235.63457" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient11767" + id="linearGradient14855" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.22315,0,0,0.22315,10.41841,7.054327)" + x1="844.37262" + y1="238.3936" + x2="846.35309" + y2="185.10175" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7184" + id="linearGradient14857" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.22315,0,0,0.22315,18.73108,59.9264)" + x1="861.005" + y1="248.9993" + x2="859.50507" + y2="179.15657" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient14883" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.031982,0,0,1.032261,106.9031,-54.06894)" + x1="101.25272" + y1="49.529297" + x2="101.25272" + y2="43.32613" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient14885" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.031982,0,0,1.107109,106.9031,-57.31254)" + x1="122.57088" + y1="59.06163" + x2="122.57088" + y2="43.326801" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4182" + id="linearGradient14887" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.038287,0,0,1.017566,106.2802,-53.32718)" + x1="111.84524" + y1="48.367947" + x2="111.84524" + y2="58.933025" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4166" + id="linearGradient14890" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.217348,0,0,0.215931,36.74451,-44.86859)" + x1="829.4079" + y1="212.95419" + x2="829.4079" + y2="183.47195" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4190" + id="radialGradient14892" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.296464,6.961497e-6,-5.421832e-6,1.090421,77.58478,-60.94513)" + cx="111.70066" + cy="62.211178" + fx="111.70066" + fy="62.211178" + r="8.8008003" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient14894" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.013541,0,0,0.982207,109.0253,-51.62793)" + spreadMethod="reflect" + x1="111.33885" + y1="44.705486" + x2="111.33885" + y2="46.731094" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient14711" + id="linearGradient14896" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.027321e-3,0.583526,-0.583526,1.006877e-3,459.0028,233.4818)" + x1="159.32025" + y1="256.35764" + x2="175.68762" + y2="256.38644" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient13392" + id="linearGradient14898" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.027321e-3,0.583526,-0.583526,1.006877e-3,459.0028,233.4818)" + x1="175.71587" + y1="240.01489" + x2="159.3485" + y2="239.98607" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient14924" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.515861,0,0,1.495984,89.34967,-82.05745)" + x1="101.35825" + y1="47.461205" + x2="101.35667" + y2="43.430031" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient14926" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.515861,0,0,1.604454,89.34967,-86.75818)" + x1="122.46678" + y1="58.964745" + x2="122.46678" + y2="43.42371" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4182" + id="linearGradient14928" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.62008,0,0,1.577605,77.79409,-86.55158)" + x1="111.84524" + y1="48.367947" + x2="111.84524" + y2="58.933025" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4166" + id="linearGradient14930" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.339136,0,0,0.334773,-30.70507,-73.43762)" + x1="829.4079" + y1="212.95419" + x2="829.4079" + y2="183.47195" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4190" + id="radialGradient14932" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(2.044377,1.047261e-5,-8.549617e-6,1.640387,30.62339,-95.37034)" + cx="111.70066" + cy="62.211178" + fx="111.70066" + fy="62.211178" + r="8.8008003" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient14934" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.480783,0,0,1.536549,93.27523,-83.78014)" + spreadMethod="reflect" + x1="111.33885" + y1="44.705486" + x2="111.33885" + y2="46.731094" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient14711" + id="linearGradient14936" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.563327e-3,0.887982,-0.887982,1.532215e-3,558.3919,954.9619)" + x1="159.32025" + y1="256.35764" + x2="175.68762" + y2="256.38644" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient13392" + id="linearGradient14938" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.563327e-3,0.887982,-0.887982,1.532215e-3,558.3919,954.9619)" + x1="175.71587" + y1="240.01489" + x2="159.3485" + y2="239.98607" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient14964" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.515861,0,0,1.495984,89.34967,-82.05745)" + x1="101.35825" + y1="47.461205" + x2="101.35667" + y2="43.430031" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient14966" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.515861,0,0,1.604454,89.34967,-86.75818)" + x1="122.46678" + y1="58.964745" + x2="122.46678" + y2="43.42371" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4182" + id="linearGradient14968" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.62008,0,0,1.577605,77.79409,-86.55158)" + x1="111.84524" + y1="48.367947" + x2="111.84524" + y2="58.933025" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4166" + id="linearGradient14970" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.339136,0,0,0.334773,-30.70507,-73.43762)" + x1="829.4079" + y1="212.95419" + x2="829.4079" + y2="183.47195" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4190" + id="radialGradient14972" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(2.044377,1.047261e-5,-8.549617e-6,1.640387,30.62339,-95.37034)" + cx="111.70066" + cy="62.211178" + fx="111.70066" + fy="62.211178" + r="8.8008003" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient14974" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.480783,0,0,1.536549,93.27523,-83.78014)" + spreadMethod="reflect" + x1="111.33885" + y1="44.705486" + x2="111.33885" + y2="46.731094" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient14711" + id="linearGradient14976" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.563327e-3,0.887982,-0.887982,1.532215e-3,558.3919,954.9619)" + x1="159.32025" + y1="256.35764" + x2="175.68762" + y2="256.38644" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient13392" + id="linearGradient14978" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.563327e-3,0.887982,-0.887982,1.532215e-3,558.3919,954.9619)" + x1="175.71587" + y1="240.01489" + x2="159.3485" + y2="239.98607" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient15004" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.031982,0,0,1.032261,106.9031,-54.06894)" + x1="101.25272" + y1="49.529297" + x2="101.25272" + y2="43.32613" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient15006" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.031982,0,0,1.107109,106.9031,-57.31254)" + x1="122.57088" + y1="59.06163" + x2="122.57088" + y2="43.326801" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4182" + id="linearGradient15008" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.038287,0,0,1.017566,106.2802,-53.32718)" + x1="111.84524" + y1="48.367947" + x2="111.84524" + y2="58.933025" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4166" + id="linearGradient15010" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.217348,0,0,0.215931,36.74451,-44.86859)" + x1="829.4079" + y1="212.95419" + x2="829.4079" + y2="183.47195" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4190" + id="radialGradient15012" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.296464,6.961497e-6,-5.421832e-6,1.090421,77.58478,-60.94513)" + cx="111.70066" + cy="62.211178" + fx="111.70066" + fy="62.211178" + r="8.8008003" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient15014" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.013541,0,0,0.982207,109.0253,-51.62793)" + spreadMethod="reflect" + x1="111.33885" + y1="44.705486" + x2="111.33885" + y2="46.731094" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient15016" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.515861,0,0,1.495984,89.34967,-82.05745)" + x1="101.35825" + y1="47.461205" + x2="101.35667" + y2="43.430031" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient15018" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.515861,0,0,1.604454,89.34967,-86.75818)" + x1="122.46678" + y1="58.964745" + x2="122.46678" + y2="43.42371" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4182" + id="linearGradient15020" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.62008,0,0,1.577605,77.79409,-86.55158)" + x1="111.84524" + y1="48.367947" + x2="111.84524" + y2="58.933025" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4166" + id="linearGradient15022" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.339136,0,0,0.334773,-30.70507,-73.43762)" + x1="829.4079" + y1="212.95419" + x2="829.4079" + y2="183.47195" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4190" + id="radialGradient15024" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(2.044377,1.047261e-5,-8.549617e-6,1.640387,30.62339,-95.37034)" + cx="111.70066" + cy="62.211178" + fx="111.70066" + fy="62.211178" + r="8.8008003" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient15026" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.480783,0,0,1.536549,93.27523,-83.78014)" + spreadMethod="reflect" + x1="111.33885" + y1="44.705486" + x2="111.33885" + y2="46.731094" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient15052" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.515861,0,0,1.495984,89.34967,-82.05745)" + x1="101.35825" + y1="47.461205" + x2="101.35667" + y2="43.430031" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient15054" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.515861,0,0,1.604454,89.34967,-86.75818)" + x1="122.46678" + y1="58.964745" + x2="122.46678" + y2="43.42371" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4182" + id="linearGradient15056" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.62008,0,0,1.577605,77.79409,-86.55158)" + x1="111.84524" + y1="48.367947" + x2="111.84524" + y2="58.933025" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4166" + id="linearGradient15058" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.339136,0,0,0.334773,-30.70507,-73.43762)" + x1="829.4079" + y1="212.95419" + x2="829.4079" + y2="183.47195" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4190" + id="radialGradient15060" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(2.044377,1.047261e-5,-8.549617e-6,1.640387,30.62339,-95.37034)" + cx="111.70066" + cy="62.211178" + fx="111.70066" + fy="62.211178" + r="8.8008003" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient15062" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.480783,0,0,1.536549,93.27523,-83.78014)" + spreadMethod="reflect" + x1="111.33885" + y1="44.705486" + x2="111.33885" + y2="46.731094" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient15089" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.515861,0,0,1.495984,89.34967,-82.05745)" + x1="101.35825" + y1="47.461205" + x2="101.35667" + y2="43.430031" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient15091" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.515861,0,0,1.604454,89.34967,-86.75818)" + x1="122.46678" + y1="58.964745" + x2="122.46678" + y2="43.42371" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4182" + id="linearGradient15093" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.62008,0,0,1.577605,77.79409,-86.55158)" + x1="111.84524" + y1="48.367947" + x2="111.84524" + y2="58.933025" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4166" + id="linearGradient15096" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.339136,0,0,0.334773,-30.70507,-73.43762)" + x1="829.4079" + y1="212.95419" + x2="829.4079" + y2="183.47195" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4190" + id="radialGradient15098" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(2.044377,1.047261e-5,-8.549617e-6,1.640387,30.62339,-95.37034)" + cx="111.70066" + cy="62.211178" + fx="111.70066" + fy="62.211178" + r="8.8008003" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient15100" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.480783,0,0,1.536549,93.27523,-83.78014)" + spreadMethod="reflect" + x1="111.33885" + y1="44.705486" + x2="111.33885" + y2="46.731094" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient15128" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.031982,0,0,1.032261,106.9031,-54.06894)" + x1="101.25272" + y1="49.529297" + x2="101.25272" + y2="43.32613" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient15130" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.031982,0,0,1.107109,106.9031,-57.31254)" + x1="122.57088" + y1="59.06163" + x2="122.57088" + y2="43.326801" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4182" + id="linearGradient15132" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.038287,0,0,1.017566,106.2802,-53.32718)" + x1="111.84524" + y1="48.367947" + x2="111.84524" + y2="58.933025" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4166" + id="linearGradient15134" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.217348,0,0,0.215931,36.74451,-44.86859)" + x1="829.4079" + y1="212.95419" + x2="829.4079" + y2="183.47195" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4190" + id="radialGradient15136" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.296464,6.961497e-6,-5.421832e-6,1.090421,77.58478,-60.94513)" + cx="111.70066" + cy="62.211178" + fx="111.70066" + fy="62.211178" + r="8.8008003" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient15138" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.013541,0,0,0.982207,109.0253,-51.62793)" + spreadMethod="reflect" + x1="111.33885" + y1="44.705486" + x2="111.33885" + y2="46.731094" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient15141" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(5.148882e-2,-7.219555e-3,6.75419e-3,5.503636e-2,124.6994,186.4847)" + spreadMethod="reflect" + x1="-154.41507" + y1="501.6441" + x2="-113.26653" + y2="187.95882" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient15143" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(6.903014e-2,0,0,7.450646e-2,20.82859,161.9654)" + x1="1559.8582" + y1="714.60614" + x2="1559.8582" + y2="478.90616" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient15171" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.515861,0,0,1.495984,89.34967,-82.05745)" + x1="101.35825" + y1="47.461205" + x2="101.35667" + y2="43.430031" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient15173" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.515861,0,0,1.604454,89.34967,-86.75818)" + x1="122.46678" + y1="58.964745" + x2="122.46678" + y2="43.42371" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4182" + id="linearGradient15175" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.62008,0,0,1.577605,77.79409,-86.55158)" + x1="111.84524" + y1="48.367947" + x2="111.84524" + y2="58.933025" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4166" + id="linearGradient15177" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.339136,0,0,0.334773,-30.70507,-73.43762)" + x1="829.4079" + y1="212.95419" + x2="829.4079" + y2="183.47195" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4190" + id="radialGradient15179" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(2.044377,1.047261e-5,-8.549617e-6,1.640387,30.62339,-95.37034)" + cx="111.70066" + cy="62.211178" + fx="111.70066" + fy="62.211178" + r="8.8008003" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient15181" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.480783,0,0,1.536549,93.27523,-83.78014)" + spreadMethod="reflect" + x1="111.33885" + y1="44.705486" + x2="111.33885" + y2="46.731094" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient15183" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(7.489322e-2,-1.050117e-2,9.824327e-3,8.005286e-2,163.7889,177.7986)" + spreadMethod="reflect" + x1="-152.27144" + y1="500.10864" + x2="-111.59152" + y2="189.99583" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient15185" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.100408,0,0,0.108373,12.7033,142.1342)" + x1="1558.4095" + y1="713.26398" + x2="1558.4095" + y2="480.24832" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient15214" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.515861,0,0,1.495984,89.34967,-82.05745)" + x1="101.35825" + y1="47.461205" + x2="101.35667" + y2="43.430031" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient15216" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.515861,0,0,1.604454,89.34967,-86.75818)" + x1="122.46678" + y1="58.964745" + x2="122.46678" + y2="43.42371" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4182" + id="linearGradient15218" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.62008,0,0,1.577605,77.79409,-86.55158)" + x1="111.84524" + y1="48.367947" + x2="111.84524" + y2="58.933025" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4166" + id="linearGradient15220" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.339136,0,0,0.334773,-30.70507,-73.43762)" + x1="829.4079" + y1="212.95419" + x2="829.4079" + y2="183.47195" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4190" + id="radialGradient15222" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(2.044377,1.047261e-5,-8.549617e-6,1.640387,30.62339,-95.37034)" + cx="111.70066" + cy="62.211178" + fx="111.70066" + fy="62.211178" + r="8.8008003" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient15224" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.480783,0,0,1.536549,93.27523,-83.78014)" + spreadMethod="reflect" + x1="111.33885" + y1="44.705486" + x2="111.33885" + y2="46.731094" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient15226" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(7.489322e-2,-1.050117e-2,9.824327e-3,8.005286e-2,163.7889,177.7986)" + spreadMethod="reflect" + x1="-152.27144" + y1="500.10864" + x2="-111.59152" + y2="189.99583" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient15228" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.100408,0,0,0.108373,12.7033,142.1342)" + x1="1558.4095" + y1="713.26398" + x2="1558.4095" + y2="480.24832" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient15254" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.031982,0,0,1.032261,106.9031,-54.06894)" + x1="101.25272" + y1="49.529297" + x2="101.25272" + y2="43.32613" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient15256" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.031982,0,0,1.107109,106.9031,-57.31254)" + x1="122.57088" + y1="59.06163" + x2="122.57088" + y2="43.326801" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4182" + id="linearGradient15258" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.038287,0,0,1.017566,106.2802,-53.32718)" + x1="111.84524" + y1="48.367947" + x2="111.84524" + y2="58.933025" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4166" + id="linearGradient15260" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.217348,0,0,0.215931,36.74451,-44.86859)" + x1="829.4079" + y1="212.95419" + x2="829.4079" + y2="183.47195" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4190" + id="radialGradient15262" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.296464,6.961497e-6,-5.421832e-6,1.090421,77.58478,-60.94513)" + cx="111.70066" + cy="62.211178" + fx="111.70066" + fy="62.211178" + r="8.8008003" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient15264" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.013541,0,0,0.982207,109.0253,-51.62793)" + spreadMethod="reflect" + x1="111.33885" + y1="44.705486" + x2="111.33885" + y2="46.731094" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient15266" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.352385,0.352373,-0.352385,0.352373,352.8199,313.5383)" + x1="168.18773" + y1="260.09671" + x2="156.90306" + y2="248.81204" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient15268" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.352385,0.352373,-0.352385,0.352373,352.8199,313.5383)" + x1="179.47237" + y1="248.81206" + x2="168.18771" + y2="237.52739" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient15294" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.515861,0,0,1.495984,89.34967,-82.05745)" + x1="101.35825" + y1="47.461205" + x2="101.35667" + y2="43.430031" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient15296" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.515861,0,0,1.604454,89.34967,-86.75818)" + x1="122.46678" + y1="58.964745" + x2="122.46678" + y2="43.42371" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4182" + id="linearGradient15298" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.62008,0,0,1.577605,77.79409,-86.55158)" + x1="111.84524" + y1="48.367947" + x2="111.84524" + y2="58.933025" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4166" + id="linearGradient15300" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.339136,0,0,0.334773,-30.70507,-73.43762)" + x1="829.4079" + y1="212.95419" + x2="829.4079" + y2="183.47195" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4190" + id="radialGradient15302" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(2.044377,1.047261e-5,-8.549617e-6,1.640387,30.62339,-95.37034)" + cx="111.70066" + cy="62.211178" + fx="111.70066" + fy="62.211178" + r="8.8008003" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient15304" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.480783,0,0,1.536549,93.27523,-83.78014)" + spreadMethod="reflect" + x1="111.33885" + y1="44.705486" + x2="111.33885" + y2="46.731094" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient15306" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.469833,0.469817,-0.469833,0.469817,404.0479,266.3918)" + x1="168.18773" + y1="260.09671" + x2="156.90306" + y2="248.81204" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient15308" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.469833,0.469817,-0.469833,0.469817,404.0479,266.3918)" + x1="179.47237" + y1="248.81206" + x2="168.18771" + y2="237.52739" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient15334" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.515861,0,0,1.495984,89.34967,-82.05745)" + x1="101.35825" + y1="47.461205" + x2="101.35667" + y2="43.430031" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient15336" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.515861,0,0,1.604454,89.34967,-86.75818)" + x1="122.46678" + y1="58.964745" + x2="122.46678" + y2="43.42371" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4182" + id="linearGradient15338" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.62008,0,0,1.577605,77.79409,-86.55158)" + x1="111.84524" + y1="48.367947" + x2="111.84524" + y2="58.933025" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4166" + id="linearGradient15340" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.339136,0,0,0.334773,-30.70507,-73.43762)" + x1="829.4079" + y1="212.95419" + x2="829.4079" + y2="183.47195" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4190" + id="radialGradient15342" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(2.044377,1.047261e-5,-8.549617e-6,1.640387,30.62339,-95.37034)" + cx="111.70066" + cy="62.211178" + fx="111.70066" + fy="62.211178" + r="8.8008003" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient15344" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.480783,0,0,1.536549,93.27523,-83.78014)" + spreadMethod="reflect" + x1="111.33885" + y1="44.705486" + x2="111.33885" + y2="46.731094" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient15347" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.469833,0.469817,-0.469833,0.469817,404.0479,266.3918)" + x1="168.18773" + y1="260.09671" + x2="156.90306" + y2="248.81204" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient15349" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.469833,0.469817,-0.469833,0.469817,404.0479,266.3918)" + x1="179.47237" + y1="248.81206" + x2="168.18771" + y2="237.52739" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient15375" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.515861,0,0,1.495984,89.34967,-82.05745)" + x1="101.35825" + y1="47.461205" + x2="101.35667" + y2="43.430031" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient15377" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.515861,0,0,1.604454,89.34967,-86.75818)" + x1="122.46678" + y1="58.964745" + x2="122.46678" + y2="43.42371" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4182" + id="linearGradient15379" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.62008,0,0,1.577605,77.79409,-86.55158)" + x1="111.84524" + y1="48.367947" + x2="111.84524" + y2="58.933025" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4166" + id="linearGradient15381" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.339136,0,0,0.334773,-30.70507,-73.43762)" + x1="829.4079" + y1="212.95419" + x2="829.4079" + y2="183.47195" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4190" + id="radialGradient15383" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(2.044377,1.047261e-5,-8.549617e-6,1.640387,30.62339,-95.37034)" + cx="111.70066" + cy="62.211178" + fx="111.70066" + fy="62.211178" + r="8.8008003" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient15385" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.480783,0,0,1.536549,93.27523,-83.78014)" + spreadMethod="reflect" + x1="111.33885" + y1="44.705486" + x2="111.33885" + y2="46.731094" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient15387" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.469833,0.469817,-0.469833,0.469817,404.0479,266.3918)" + x1="168.18773" + y1="260.09671" + x2="156.90306" + y2="248.81204" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient15389" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.469833,0.469817,-0.469833,0.469817,404.0479,266.3918)" + x1="179.47237" + y1="248.81206" + x2="168.18771" + y2="237.52739" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient15416" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.031982,0,0,1.032261,88.29694,135.9676)" + x1="101.25272" + y1="49.529297" + x2="101.25272" + y2="43.32613" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient15418" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.031982,0,0,1.107109,88.29694,132.724)" + x1="122.57088" + y1="59.06163" + x2="122.57088" + y2="43.326801" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4182" + id="linearGradient15420" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.038287,0,0,1.017566,87.67404,136.7093)" + x1="111.84524" + y1="48.367947" + x2="111.84524" + y2="58.933025" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4166" + id="linearGradient15422" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.217348,0,0,0.215931,18.13835,145.1679)" + x1="829.4079" + y1="212.95419" + x2="829.4079" + y2="183.47195" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4190" + id="radialGradient15424" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.296464,6.961497e-6,-5.421832e-6,1.090421,58.97862,129.0914)" + cx="111.70066" + cy="62.211178" + fx="111.70066" + fy="62.211178" + r="8.8008003" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient15426" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.013541,0,0,0.982207,90.41914,138.4086)" + spreadMethod="reflect" + x1="111.33885" + y1="44.705486" + x2="111.33885" + y2="46.731094" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient15428" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(8.716377e-2,2.313213e-2,2.313213e-2,-8.716377e-2,147.8221,604.9863)" + x1="1492.3475" + y1="1614.7871" + x2="1458.5876" + y2="1741.9969" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient15430" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-0.174823,0,0,0.174823,332.6591,478.8481)" + x1="31.778322" + y1="113.87774" + x2="31.778322" + y2="45.986065" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient15452" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.031982,0,0,1.032261,88.29694,135.9676)" + x1="101.25272" + y1="49.529297" + x2="101.25272" + y2="43.32613" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient15454" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.031982,0,0,1.107109,88.29694,132.724)" + x1="122.57088" + y1="59.06163" + x2="122.57088" + y2="43.326801" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4182" + id="linearGradient15456" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.038287,0,0,1.017566,87.67404,136.7093)" + x1="111.84524" + y1="48.367947" + x2="111.84524" + y2="58.933025" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4166" + id="linearGradient15458" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.217348,0,0,0.215931,18.13835,145.1679)" + x1="829.4079" + y1="212.95419" + x2="829.4079" + y2="183.47195" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4190" + id="radialGradient15460" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.296464,6.961497e-6,-5.421832e-6,1.090421,58.97862,129.0914)" + cx="111.70066" + cy="62.211178" + fx="111.70066" + fy="62.211178" + r="8.8008003" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient15462" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.013541,0,0,0.982207,90.41914,138.4086)" + spreadMethod="reflect" + x1="111.33885" + y1="44.705486" + x2="111.33885" + y2="46.731094" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient15464" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.127329,2.313213e-2,3.379153e-2,-8.716377e-2,112.1554,605.05)" + x1="1492.3475" + y1="1614.7871" + x2="1458.5876" + y2="1741.9969" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient15466" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-0.255382,0,0,0.174823,382.1663,478.9118)" + x1="31.778322" + y1="113.87774" + x2="31.778322" + y2="45.986065" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient15488" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.031982,0,0,1.032261,88.29694,135.9676)" + x1="101.25272" + y1="49.529297" + x2="101.25272" + y2="43.32613" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient15490" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.031982,0,0,1.107109,88.29694,132.724)" + x1="122.57088" + y1="59.06163" + x2="122.57088" + y2="43.326801" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4182" + id="linearGradient15492" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.038287,0,0,1.017566,87.67404,136.7093)" + x1="111.84524" + y1="48.367947" + x2="111.84524" + y2="58.933025" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4166" + id="linearGradient15494" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.217348,0,0,0.215931,18.13835,145.1679)" + x1="829.4079" + y1="212.95419" + x2="829.4079" + y2="183.47195" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4190" + id="radialGradient15496" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.296464,6.961497e-6,-5.421832e-6,1.090421,58.97862,129.0914)" + cx="111.70066" + cy="62.211178" + fx="111.70066" + fy="62.211178" + r="8.8008003" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient15498" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.013541,0,0,0.982207,90.41914,138.4086)" + spreadMethod="reflect" + x1="111.33885" + y1="44.705486" + x2="111.33885" + y2="46.731094" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient15500" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.127329,2.313213e-2,3.379153e-2,-8.716377e-2,112.1554,605.05)" + x1="1492.3475" + y1="1614.7871" + x2="1458.5876" + y2="1741.9969" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient15502" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-0.255382,0,0,0.174823,382.1663,478.9118)" + x1="31.778322" + y1="113.87774" + x2="31.778322" + y2="45.986065" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient15504" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.031982,0,0,1.032261,106.9031,-54.06894)" + x1="101.25272" + y1="49.529297" + x2="101.25272" + y2="43.32613" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient15506" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.031982,0,0,1.107109,106.9031,-57.31254)" + x1="122.57088" + y1="59.06163" + x2="122.57088" + y2="43.326801" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4182" + id="linearGradient15508" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.038287,0,0,1.017566,106.2802,-53.32718)" + x1="111.84524" + y1="48.367947" + x2="111.84524" + y2="58.933025" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4166" + id="linearGradient15510" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.217348,0,0,0.215931,36.74451,-44.86859)" + x1="829.4079" + y1="212.95419" + x2="829.4079" + y2="183.47195" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4190" + id="radialGradient15512" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.296464,6.961497e-6,-5.421832e-6,1.090421,77.58478,-60.94513)" + cx="111.70066" + cy="62.211178" + fx="111.70066" + fy="62.211178" + r="8.8008003" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient15514" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.013541,0,0,0.982207,109.0253,-51.62793)" + spreadMethod="reflect" + x1="111.33885" + y1="44.705486" + x2="111.33885" + y2="46.731094" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient16033" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(27.99914,1.31776)" + x1="432.34427" + y1="65.815475" + x2="432.34427" + y2="43.934036" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient16035" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(10.43883,-16.39223)" + x1="431.24716" + y1="75.79641" + x2="431.24716" + y2="57.478249" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient16037" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(10.43883,-16.39223)" + x1="448.74854" + y1="82.005356" + x2="449.41235" + y2="66.314919" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient16039" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(7.618051e-2,0,0,7.282925e-2,240.3801,-14.402)" + x1="2642.5115" + y1="1026.281" + x2="2642.5115" + y2="921.65161" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7919" + id="linearGradient16041" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-8.997371e-3,-0.10169,9.914284e-2,-8.499707e-3,437.1222,162.91)" + x1="507.37094" + y1="290.88303" + x2="608.41498" + y2="398.94208" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient13837" + id="linearGradient16043" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-4.703229e-2,-0.531566,0.518253,-4.443083e-2,260.5061,715.1747)" + x1="1129.3501" + y1="517.39514" + x2="1087.9879" + y2="513.64142" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient16045" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(8.982791e-3,0.101525,-9.979687e-2,8.555778e-3,603.3036,-46.83784)" + x1="1394.5476" + y1="1587.7018" + x2="1412.9457" + y2="1547.1619" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient16047" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-8.900061e-3,-0.10059,9.960667e-2,-8.539472e-3,325.5096,251.3287)" + x1="1394.5476" + y1="1587.7018" + x2="1412.9457" + y2="1547.1619" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient16049" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(27.99914,1.31776)" + x1="432.34427" + y1="65.815475" + x2="432.34427" + y2="43.934036" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient16051" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(10.43883,-16.39223)" + x1="431.24716" + y1="75.79641" + x2="431.24716" + y2="57.478249" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient16053" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(10.43883,-16.39223)" + x1="448.74854" + y1="82.005356" + x2="448.74854" + y2="66.314919" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient16055" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(7.618051e-2,0,0,7.282925e-2,240.3801,-14.402)" + x1="2642.5115" + y1="1026.281" + x2="2642.5115" + y2="921.65161" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7919" + id="linearGradient16057" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-6.057343e-3,-6.845914e-2,6.674642e-2,-5.722136e-3,413.1479,144.3069)" + x1="507.37094" + y1="290.88303" + x2="608.41498" + y2="398.94208" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient13837" + id="linearGradient16059" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-3.166377e-2,-0.357859,0.348906,-2.991153e-2,294.2438,516.1006)" + x1="1130.2096" + y1="518.44458" + x2="1086.9674" + y2="514.52026" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7919" + id="linearGradient16155" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-1.299645e-2,-0.146888,0.143209,-1.227759e-2,430.5184,268.9886)" + x1="510.00525" + y1="299.785" + x2="608.41498" + y2="398.94208" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient16157" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-6.793684e-2,-0.767832,0.748602,-6.41791e-2,175.4015,1066.719)" + x1="1087.9877" + y1="513.64142" + x2="1129.3499" + y2="517.39508" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient16159" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.297539e-2,0.14665,-0.144154,1.235858e-2,670.5628,-33.9865)" + x1="1394.5476" + y1="1587.7018" + x2="1412.9457" + y2="1547.1619" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient16161" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-1.285589e-2,-0.145299,0.143879,-1.233503e-2,269.2972,396.7066)" + x1="1394.5476" + y1="1587.7018" + x2="1412.9457" + y2="1547.1619" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient9597" + id="linearGradient16189" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.314096,0,0,1.211583,46.567702,252.66613)" + x1="304.73999" + y1="219.05894" + x2="304.73999" + y2="201.60123" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient9906" + id="linearGradient16191" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.314096,0,0,1.211583,46.567702,252.66613)" + x1="320.84555" + y1="201.60123" + x2="320.84555" + y2="219.05894" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient16193" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(27.99914,1.31776)" + x1="432.34427" + y1="65.815475" + x2="432.34427" + y2="43.934036" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient16195" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(10.43883,-16.39223)" + x1="431.24716" + y1="75.79641" + x2="431.24716" + y2="57.478249" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient16197" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(10.43883,-16.39223)" + x1="448.74854" + y1="82.005356" + x2="448.74854" + y2="66.314919" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient16199" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(7.618051e-2,0,0,7.282925e-2,240.3801,-14.402)" + x1="2642.5115" + y1="1026.281" + x2="2642.5115" + y2="921.65161" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient16207" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.999739,0,0,0.953222,71.83257,233.751)" + x1="390.20804" + y1="182.18143" + x2="390.20804" + y2="159.18143" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient16209" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.999739,0,0,0.953222,71.83257,233.751)" + x1="409.20804" + y1="182.18143" + x2="409.20804" + y2="159.18143" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient16211" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(70.00097,193.003)" + x1="392.93735" + y1="193.43317" + x2="392.93735" + y2="230.89081" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient13837" + id="linearGradient16213" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.676915,0.40565,0.410155,-0.669481,376.4461,557.8599)" + spreadMethod="reflect" + x1="-1.78125" + y1="242.48718" + x2="-0.27687499" + y2="242.48718" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient3870" + id="linearGradient16215" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.676915,0.40565,0.410155,-0.669481,376.3937,557.9171)" + x1="-1.9580266" + y1="230.93272" + x2="0.14081553" + y2="230.84433" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient16217" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.785942,0,0,0.735282,158.3914,239.4019)" + x1="390.20804" + y1="182.18143" + x2="390.20804" + y2="159.18143" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient16219" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.785942,0,0,0.735282,158.3914,239.4019)" + x1="409.20804" + y1="182.18143" + x2="409.20804" + y2="159.18143" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient16221" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.786139,0,0,0.77135,156.9596,207.9717)" + x1="392.93735" + y1="193.43317" + x2="392.93735" + y2="230.89081" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient16223" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.785942,0,0,0.735282,152.3063,234.3383)" + x1="390.20804" + y1="182.18143" + x2="390.20804" + y2="159.18143" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient16225" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.785942,0,0,0.735282,152.3063,234.3383)" + x1="409.20804" + y1="182.18143" + x2="409.20804" + y2="159.18143" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient16227" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.786139,0,0,0.77135,150.8745,202.9081)" + x1="392.93735" + y1="193.43317" + x2="392.93735" + y2="230.89081" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient16239" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(27.99914,1.31776)" + x1="432.34427" + y1="65.815475" + x2="432.34427" + y2="43.934036" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient16241" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(10.43883,-16.39223)" + x1="431.24716" + y1="75.79641" + x2="431.24716" + y2="57.478249" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient16243" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(10.43883,-16.39223)" + x1="448.74854" + y1="82.005356" + x2="448.74854" + y2="66.314919" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient16245" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(7.618051e-2,0,0,7.282925e-2,240.3801,-14.402)" + x1="2642.5115" + y1="1026.281" + x2="2642.5115" + y2="921.65161" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient16247" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(27.99914,1.31776)" + x1="432.34427" + y1="65.815475" + x2="432.34427" + y2="43.934036" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient16249" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(10.43883,-16.39223)" + x1="431.24716" + y1="75.79641" + x2="431.24716" + y2="57.478249" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient16251" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(10.43883,-16.39223)" + x1="448.74854" + y1="82.005356" + x2="448.74854" + y2="66.314919" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient16253" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(7.618051e-2,0,0,7.282925e-2,240.3801,-14.402)" + x1="2642.5115" + y1="1026.281" + x2="2642.5115" + y2="921.65161" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7919" + id="linearGradient16255" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-5.855531e-3,-6.73221e-2,6.452263e-2,-5.627097e-3,410.7517,344.741)" + x1="507.37094" + y1="290.88303" + x2="608.41498" + y2="398.94208" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient13837" + id="linearGradient16257" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-3.060883e-2,-0.351915,0.337281,-2.941473e-2,295.8091,710.3596)" + x1="1130.2096" + y1="518.44458" + x2="1086.9674" + y2="514.52026" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient9597" + id="linearGradient16259" + gradientUnits="userSpaceOnUse" + x1="395.67776" + y1="231.79971" + x2="395.67776" + y2="203.73718" + gradientTransform="matrix(1.245078,0,0,1.146849,-11.263108,258.9794)" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient9906" + id="linearGradient16261" + gradientUnits="userSpaceOnUse" + x1="421.625" + y1="202.73347" + x2="421.625" + y2="231.79971" + gradientTransform="matrix(1.245078,0,0,1.146849,-11.263108,258.9794)" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient16263" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(27.99914,1.31776)" + x1="432.34427" + y1="65.815475" + x2="432.34427" + y2="43.934036" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient16265" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(10.43883,-16.39223)" + x1="431.24716" + y1="75.79641" + x2="431.24716" + y2="57.478249" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient16267" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(10.43883,-16.39223)" + x1="448.74854" + y1="82.005356" + x2="448.74854" + y2="66.314919" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient16269" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(7.618051e-2,0,0,7.282925e-2,240.3801,-14.402)" + x1="2642.5115" + y1="1026.281" + x2="2642.5115" + y2="921.65161" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7919" + id="linearGradient16271" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-8.997371e-3,-0.10169,9.914284e-2,-8.499707e-3,440.7439,367.3022)" + x1="507.37094" + y1="290.88303" + x2="608.41498" + y2="398.94208" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient13837" + id="linearGradient16273" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-4.703229e-2,-0.531566,0.518253,-4.443083e-2,264.1278,919.5669)" + x1="1129.3501" + y1="517.39514" + x2="1087.9879" + y2="513.64142" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient16275" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(8.982791e-3,0.101525,-9.979687e-2,8.555778e-3,606.9253,157.5544)" + x1="1394.5476" + y1="1587.7018" + x2="1412.9457" + y2="1547.1619" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient16277" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-8.900061e-3,-0.10059,9.960667e-2,-8.539472e-3,329.1313,455.7209)" + x1="1394.5476" + y1="1587.7018" + x2="1412.9457" + y2="1547.1619" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient16279" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.785942,0,0,0.735282,158.3914,239.4019)" + x1="390.20804" + y1="182.18143" + x2="390.20804" + y2="159.18143" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient16281" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.785942,0,0,0.735282,158.3914,239.4019)" + x1="409.20804" + y1="182.18143" + x2="409.20804" + y2="159.18143" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient16283" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.786139,0,0,0.77135,156.9596,207.9717)" + x1="392.93735" + y1="193.43317" + x2="392.93735" + y2="230.89081" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient16285" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.785942,0,0,0.735282,152.3063,234.3383)" + x1="390.20804" + y1="182.18143" + x2="390.20804" + y2="159.18143" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient16287" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.785942,0,0,0.735282,152.3063,234.3383)" + x1="409.20804" + y1="182.18143" + x2="409.20804" + y2="159.18143" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient16289" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.786139,0,0,0.77135,150.8745,202.9081)" + x1="392.93735" + y1="193.43317" + x2="392.93735" + y2="230.89081" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient16291" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.999739,0,0,0.953222,71.83257,233.751)" + x1="390.20804" + y1="182.18143" + x2="390.20804" + y2="159.18143" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient16293" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.999739,0,0,0.953222,71.83257,233.751)" + x1="409.20804" + y1="182.18143" + x2="409.20804" + y2="159.18143" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient16295" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(70.00097,193.003)" + x1="392.93735" + y1="193.43317" + x2="392.93735" + y2="230.89081" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient13837" + id="linearGradient16297" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.676915,0.40565,0.410155,-0.669481,376.4461,557.8599)" + spreadMethod="reflect" + x1="-1.78125" + y1="242.48718" + x2="-0.27687499" + y2="242.48718" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient3870" + id="linearGradient16299" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.676915,0.40565,0.410155,-0.669481,376.3937,557.9171)" + x1="-1.9580266" + y1="230.93272" + x2="0.14081553" + y2="230.84433" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient16301" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.999739,0,0,0.953222,22.5424,42.76085)" + x1="390.20804" + y1="182.18143" + x2="390.20804" + y2="159.18143" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient16303" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.999739,0,0,0.953222,22.5424,42.76085)" + x1="409.20804" + y1="182.18143" + x2="409.20804" + y2="159.18143" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient16305" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(20.71716,2.010781)" + x1="392.93735" + y1="193.43317" + x2="392.93735" + y2="230.89081" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient16307" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.999739,0,0,0.953222,22.5424,42.76085)" + x1="390.20804" + y1="182.18143" + x2="390.20804" + y2="159.18143" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient16309" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.999739,0,0,0.953222,22.5424,42.76085)" + x1="409.20804" + y1="182.18143" + x2="409.20804" + y2="159.18143" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient16311" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(20.71716,2.010781)" + x1="392.93735" + y1="193.43317" + x2="392.93735" + y2="230.89081" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient16313" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.62791,0.627888,-0.62791,0.627888,521.9233,206.3054)" + x1="168.18773" + y1="260.09671" + x2="156.90306" + y2="248.81204" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient16315" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.62791,0.627888,-0.62791,0.627888,521.9233,206.3054)" + x1="179.47237" + y1="248.81206" + x2="168.18771" + y2="237.52739" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7919" + id="linearGradient16369" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-4.875892e-3,-5.598396e-2,5.372793e-2,-4.679404e-3,617.0533,758.8446)" + x1="507.37094" + y1="290.88303" + x2="608.41498" + y2="398.94208" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient16371" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-2.548793e-2,-0.292647,0.280854,-2.446082e-2,521.3407,1062.887)" + x1="1088.9348" + y1="490.38297" + x2="1132.1641" + y2="494.30609" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient16373" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(4.867991e-3,5.589324e-2,-5.408237e-2,4.710273e-3,707.3524,643.1593)" + x1="1394.5476" + y1="1587.7018" + x2="1412.9457" + y2="1547.1619" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient16375" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-4.823157e-3,-5.537847e-2,5.39793e-2,-4.701296e-3,556.3565,807.7638)" + x1="1394.5476" + y1="1587.7018" + x2="1412.9457" + y2="1547.1619" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7919" + id="linearGradient16377" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-4.875892e-3,-5.598396e-2,5.372793e-2,-4.679404e-3,612.4965,750.8511)" + x1="507.37094" + y1="290.88303" + x2="608.41498" + y2="398.94208" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient16379" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-2.548793e-2,-0.292647,0.280854,-2.446082e-2,516.7839,1054.893)" + x1="1088.9348" + y1="490.38297" + x2="1132.1641" + y2="494.30609" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient16381" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(4.867991e-3,5.589324e-2,-5.408237e-2,4.710273e-3,702.7956,635.1658)" + x1="1394.5476" + y1="1587.7018" + x2="1412.9457" + y2="1547.1619" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient16383" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-4.823157e-3,-5.537847e-2,5.39793e-2,-4.701296e-3,551.7997,799.7703)" + x1="1394.5476" + y1="1587.7018" + x2="1412.9457" + y2="1547.1619" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7919" + id="linearGradient16385" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-4.875892e-3,-5.598396e-2,5.372793e-2,-4.679404e-3,607.8044,758.8446)" + x1="507.37094" + y1="290.88303" + x2="608.41498" + y2="398.94208" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient16387" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-2.548793e-2,-0.292647,0.280854,-2.446082e-2,512.0918,1062.887)" + x1="1088.9348" + y1="490.38297" + x2="1132.1641" + y2="494.30609" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient16389" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(4.867991e-3,5.589324e-2,-5.408237e-2,4.710273e-3,698.1035,643.1593)" + x1="1394.5476" + y1="1587.7018" + x2="1412.9457" + y2="1547.1619" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient16391" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-4.823157e-3,-5.537847e-2,5.39793e-2,-4.701296e-3,547.1076,807.7638)" + x1="1394.5476" + y1="1587.7018" + x2="1412.9457" + y2="1547.1619" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7919" + id="linearGradient16413" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-4.875892e-3,-5.598396e-2,5.372793e-2,-4.679404e-3,617.0533,758.8446)" + x1="507.37094" + y1="290.88303" + x2="608.41498" + y2="398.94208" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient16415" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-2.548793e-2,-0.292647,0.280854,-2.446082e-2,521.3407,1062.887)" + x1="1088.9348" + y1="490.38297" + x2="1132.1641" + y2="494.30609" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient16417" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(4.867991e-3,5.589324e-2,-5.408237e-2,4.710273e-3,707.3524,643.1593)" + x1="1394.5476" + y1="1587.7018" + x2="1412.9457" + y2="1547.1619" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient16419" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-4.823157e-3,-5.537847e-2,5.39793e-2,-4.701296e-3,556.3565,807.7638)" + x1="1394.5476" + y1="1587.7018" + x2="1412.9457" + y2="1547.1619" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7919" + id="linearGradient16421" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-4.875892e-3,-5.598396e-2,5.372793e-2,-4.679404e-3,612.4965,750.8511)" + x1="507.37094" + y1="290.88303" + x2="608.41498" + y2="398.94208" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient16423" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-2.548793e-2,-0.292647,0.280854,-2.446082e-2,516.7839,1054.893)" + x1="1088.9348" + y1="490.38297" + x2="1132.1641" + y2="494.30609" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient16425" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(4.867991e-3,5.589324e-2,-5.408237e-2,4.710273e-3,702.7956,635.1658)" + x1="1394.5476" + y1="1587.7018" + x2="1412.9457" + y2="1547.1619" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient16427" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-4.823157e-3,-5.537847e-2,5.39793e-2,-4.701296e-3,551.7997,799.7703)" + x1="1394.5476" + y1="1587.7018" + x2="1412.9457" + y2="1547.1619" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7919" + id="linearGradient16429" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-4.875892e-3,-5.598396e-2,5.372793e-2,-4.679404e-3,607.8044,758.8446)" + x1="507.37094" + y1="290.88303" + x2="608.41498" + y2="398.94208" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient16431" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-2.548793e-2,-0.292647,0.280854,-2.446082e-2,512.0918,1062.887)" + x1="1088.9348" + y1="490.38297" + x2="1132.1641" + y2="494.30609" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient16433" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(4.867991e-3,5.589324e-2,-5.408237e-2,4.710273e-3,698.1035,643.1593)" + x1="1394.5476" + y1="1587.7018" + x2="1412.9457" + y2="1547.1619" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient16435" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-4.823157e-3,-5.537847e-2,5.39793e-2,-4.701296e-3,547.1076,807.7638)" + x1="1394.5476" + y1="1587.7018" + x2="1412.9457" + y2="1547.1619" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7919" + id="linearGradient17354" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-7.371756e-3,-8.464087e-2,8.123002e-2,-7.074689e-3,649.2207,776.8758)" + x1="507.37094" + y1="290.88303" + x2="608.41498" + y2="398.94208" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient13837" + id="linearGradient17356" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-3.853465e-2,-0.442446,0.424617,-3.698179e-2,504.5152,1236.551)" + x1="1129.3464" + y1="517.39832" + x2="1087.991" + y2="513.6452" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient17358" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(7.359809e-3,8.450372e-2,-8.176588e-2,7.121361e-3,785.742,601.9738)" + x1="1394.5476" + y1="1587.7018" + x2="1412.9457" + y2="1547.1619" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient17361" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-7.292027e-3,-8.372545e-2,8.161005e-2,-7.107788e-3,557.4547,850.8355)" + x1="1394.5476" + y1="1587.7018" + x2="1412.9457" + y2="1547.1619" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7919" + id="linearGradient17364" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-7.371756e-3,-8.464087e-2,8.123002e-2,-7.074689e-3,635.2375,776.8758)" + x1="507.37094" + y1="290.88303" + x2="608.41498" + y2="398.94208" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient13837" + id="linearGradient17366" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-3.853465e-2,-0.442446,0.424617,-3.698179e-2,490.5319,1236.551)" + x1="1088.9348" + y1="490.38297" + x2="1132.1641" + y2="494.30609" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient17368" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(7.359809e-3,8.450372e-2,-8.176588e-2,7.121361e-3,771.7587,601.9738)" + x1="1394.5476" + y1="1587.7018" + x2="1412.9457" + y2="1547.1619" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient17370" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-7.292027e-3,-8.372545e-2,8.161005e-2,-7.107788e-3,543.4715,850.8355)" + x1="1394.5476" + y1="1587.7018" + x2="1412.9457" + y2="1547.1619" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7919" + id="linearGradient17372" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-7.371756e-3,-8.464087e-2,8.123002e-2,-7.074689e-3,642.3314,764.7906)" + x1="507.37094" + y1="290.88303" + x2="608.41498" + y2="398.94208" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient13837" + id="linearGradient17374" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-3.853465e-2,-0.442446,0.424617,-3.698179e-2,497.6258,1224.465)" + x1="1129.3464" + y1="517.39832" + x2="1087.991" + y2="513.6452" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient17376" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(7.359809e-3,8.450372e-2,-8.176588e-2,7.121361e-3,778.4877,590.208)" + x1="1394.5476" + y1="1587.7018" + x2="1412.9457" + y2="1547.1619" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient17378" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-7.292027e-3,-8.372545e-2,8.161005e-2,-7.107788e-3,550.8847,838.3854)" + x1="1394.5476" + y1="1587.7018" + x2="1412.9457" + y2="1547.1619" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7919" + id="linearGradient17400" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-7.371756e-3,-8.464087e-2,8.123002e-2,-7.074689e-3,649.2207,776.8758)" + x1="507.37094" + y1="290.88303" + x2="608.41498" + y2="398.94208" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient13837" + id="linearGradient17402" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-3.853465e-2,-0.442446,0.424617,-3.698179e-2,504.5152,1236.551)" + x1="1129.3464" + y1="517.39832" + x2="1087.991" + y2="513.6452" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient17404" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(7.359809e-3,8.450372e-2,-8.176588e-2,7.121361e-3,785.742,601.9738)" + x1="1394.5476" + y1="1587.7018" + x2="1412.9457" + y2="1547.1619" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient17406" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-7.292027e-3,-8.372545e-2,8.161005e-2,-7.107788e-3,557.4547,850.8355)" + x1="1394.5476" + y1="1587.7018" + x2="1412.9457" + y2="1547.1619" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7919" + id="linearGradient17408" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-7.371756e-3,-8.464087e-2,8.123002e-2,-7.074689e-3,635.2375,776.8758)" + x1="507.37094" + y1="290.88303" + x2="608.41498" + y2="398.94208" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient13837" + id="linearGradient17410" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-3.853465e-2,-0.442446,0.424617,-3.698179e-2,490.5319,1236.551)" + x1="1088.9348" + y1="490.38297" + x2="1132.1641" + y2="494.30609" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient17412" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(7.359809e-3,8.450372e-2,-8.176588e-2,7.121361e-3,771.7587,601.9738)" + x1="1394.5476" + y1="1587.7018" + x2="1412.9457" + y2="1547.1619" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient17414" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-7.292027e-3,-8.372545e-2,8.161005e-2,-7.107788e-3,543.4715,850.8355)" + x1="1394.5476" + y1="1587.7018" + x2="1412.9457" + y2="1547.1619" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7919" + id="linearGradient17416" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-7.371756e-3,-8.464087e-2,8.123002e-2,-7.074689e-3,642.3314,764.7906)" + x1="507.37094" + y1="290.88303" + x2="608.41498" + y2="398.94208" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient13837" + id="linearGradient17418" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-3.853465e-2,-0.442446,0.424617,-3.698179e-2,497.6258,1224.465)" + x1="1129.3464" + y1="517.39832" + x2="1087.991" + y2="513.6452" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient17420" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(7.359809e-3,8.450372e-2,-8.176588e-2,7.121361e-3,778.4877,590.208)" + x1="1394.5476" + y1="1587.7018" + x2="1412.9457" + y2="1547.1619" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient17422" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-7.292027e-3,-8.372545e-2,8.161005e-2,-7.107788e-3,550.8847,838.3854)" + x1="1394.5476" + y1="1587.7018" + x2="1412.9457" + y2="1547.1619" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7919" + id="linearGradient17444" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-7.371756e-3,-8.464087e-2,8.123002e-2,-7.074689e-3,649.2207,776.8758)" + x1="507.37094" + y1="290.88303" + x2="608.41498" + y2="398.94208" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient13837" + id="linearGradient17446" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-3.853465e-2,-0.442446,0.424617,-3.698179e-2,504.5152,1236.551)" + x1="1129.3464" + y1="517.39832" + x2="1087.991" + y2="513.6452" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient17448" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(7.359809e-3,8.450372e-2,-8.176588e-2,7.121361e-3,785.742,601.9738)" + x1="1394.5476" + y1="1587.7018" + x2="1412.9457" + y2="1547.1619" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient17450" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-7.292027e-3,-8.372545e-2,8.161005e-2,-7.107788e-3,557.4547,850.8355)" + x1="1394.5476" + y1="1587.7018" + x2="1412.9457" + y2="1547.1619" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7919" + id="linearGradient17452" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-7.371756e-3,-8.464087e-2,8.123002e-2,-7.074689e-3,635.2375,776.8758)" + x1="507.37094" + y1="290.88303" + x2="608.41498" + y2="398.94208" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient13837" + id="linearGradient17454" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-3.853465e-2,-0.442446,0.424617,-3.698179e-2,490.5319,1236.551)" + x1="1088.9348" + y1="490.38297" + x2="1132.1641" + y2="494.30609" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient17456" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(7.359809e-3,8.450372e-2,-8.176588e-2,7.121361e-3,771.7587,601.9738)" + x1="1394.5476" + y1="1587.7018" + x2="1412.9457" + y2="1547.1619" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient17458" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-7.292027e-3,-8.372545e-2,8.161005e-2,-7.107788e-3,543.4715,850.8355)" + x1="1394.5476" + y1="1587.7018" + x2="1412.9457" + y2="1547.1619" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7919" + id="linearGradient17460" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-7.371756e-3,-8.464087e-2,8.123002e-2,-7.074689e-3,642.3314,764.7906)" + x1="507.37094" + y1="290.88303" + x2="608.41498" + y2="398.94208" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient13837" + id="linearGradient17462" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-3.853465e-2,-0.442446,0.424617,-3.698179e-2,497.6258,1224.465)" + x1="1129.3464" + y1="517.39832" + x2="1087.991" + y2="513.6452" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient17464" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(7.359809e-3,8.450372e-2,-8.176588e-2,7.121361e-3,778.4877,590.208)" + x1="1394.5476" + y1="1587.7018" + x2="1412.9457" + y2="1547.1619" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient17466" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-7.292027e-3,-8.372545e-2,8.161005e-2,-7.107788e-3,550.8847,838.3854)" + x1="1394.5476" + y1="1587.7018" + x2="1412.9457" + y2="1547.1619" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7919" + id="linearGradient17481" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-8.546635e-3,-9.659278e-2,9.417606e-2,-8.073673e-3,600.9577,816.8757)" + x1="507.37094" + y1="290.88303" + x2="608.41498" + y2="398.94208" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient17483" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-4.467614e-2,-0.504923,0.49229,-4.220381e-2,433.1896,1341.46)" + x1="1086.9674" + y1="514.52026" + x2="1130.2096" + y2="518.44458" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient17485" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(8.532785e-3,9.643625e-2,-9.479733e-2,8.126934e-3,759.2369,617.2763)" + x1="1394.5476" + y1="1587.7018" + x2="1412.9457" + y2="1547.1619" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient17487" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-8.4542e-3,-9.554809e-2,9.461666e-2,-8.111446e-3,494.5664,901.2791)" + x1="1394.5476" + y1="1587.7018" + x2="1412.9457" + y2="1547.1619" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7919" + id="linearGradient17514" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-1.299645e-2,-0.146888,0.143209,-1.227759e-2,625.2179,843.8729)" + x1="507.37094" + y1="290.88303" + x2="608.41498" + y2="398.94208" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient13837" + id="linearGradient17516" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-6.793684e-2,-0.767832,0.748602,-6.41791e-2,370.1009,1641.603)" + x1="1129.3501" + y1="517.39514" + x2="1087.9879" + y2="513.64142" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient17518" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.297539e-2,0.14665,-0.144154,1.235858e-2,865.2623,540.898)" + x1="1394.5476" + y1="1587.7018" + x2="1412.9457" + y2="1547.1619" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient17520" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-1.285589e-2,-0.145299,0.143879,-1.233503e-2,463.9966,971.5911)" + x1="1394.5476" + y1="1587.7018" + x2="1412.9457" + y2="1547.1619" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7919" + id="linearGradient17530" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-1.299645e-2,-0.146888,0.143209,-1.227759e-2,625.2179,843.8729)" + x1="507.37094" + y1="290.88303" + x2="608.41498" + y2="398.94208" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient13837" + id="linearGradient17532" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-6.793684e-2,-0.767832,0.748602,-6.41791e-2,370.1009,1641.603)" + x1="1129.3501" + y1="517.39514" + x2="1087.9879" + y2="513.64142" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient17534" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.297539e-2,0.14665,-0.144154,1.235858e-2,865.2623,540.898)" + x1="1394.5476" + y1="1587.7018" + x2="1412.9457" + y2="1547.1619" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient17536" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-1.285589e-2,-0.145299,0.143879,-1.233503e-2,463.9966,971.5911)" + x1="1394.5476" + y1="1587.7018" + x2="1412.9457" + y2="1547.1619" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7919" + id="linearGradient17538" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-1.299645e-2,-0.146888,0.143209,-1.227759e-2,625.2179,843.8729)" + x1="507.37094" + y1="290.88303" + x2="608.41498" + y2="398.94208" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient13837" + id="linearGradient17540" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-6.793684e-2,-0.767832,0.748602,-6.41791e-2,370.1009,1641.603)" + x1="1129.3501" + y1="517.39514" + x2="1087.9879" + y2="513.64142" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient17542" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.297539e-2,0.14665,-0.144154,1.235858e-2,865.2623,540.898)" + x1="1394.5476" + y1="1587.7018" + x2="1412.9457" + y2="1547.1619" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient17544" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-1.285589e-2,-0.145299,0.143879,-1.233503e-2,463.9966,971.5911)" + x1="1394.5476" + y1="1587.7018" + x2="1412.9457" + y2="1547.1619" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7919" + id="linearGradient17546" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-8.546635e-3,-9.659278e-2,9.417606e-2,-8.073673e-3,600.9577,816.8757)" + x1="507.37094" + y1="290.88303" + x2="608.41498" + y2="398.94208" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient17548" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-4.467614e-2,-0.504923,0.49229,-4.220381e-2,433.1896,1341.46)" + x1="1086.9674" + y1="514.52026" + x2="1130.2096" + y2="518.44458" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient17550" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(8.532785e-3,9.643625e-2,-9.479733e-2,8.126934e-3,759.2369,617.2763)" + x1="1394.5476" + y1="1587.7018" + x2="1412.9457" + y2="1547.1619" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient17552" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-8.4542e-3,-9.554809e-2,9.461666e-2,-8.111446e-3,494.5664,901.2791)" + x1="1394.5476" + y1="1587.7018" + x2="1412.9457" + y2="1547.1619" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient17588" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(27.99914,1.31776)" + x1="432.34427" + y1="65.815475" + x2="432.34427" + y2="43.934036" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient17590" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(10.43883,-16.39223)" + x1="431.24716" + y1="75.79641" + x2="431.24716" + y2="57.478249" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient17592" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(10.43883,-16.39223)" + x1="448.74854" + y1="82.005356" + x2="448.74854" + y2="66.314919" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient17594" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(7.618051e-2,0,0,7.282925e-2,240.3801,-14.402)" + x1="2642.5115" + y1="1026.281" + x2="2642.5115" + y2="921.65161" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7919" + id="linearGradient17596" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-6.057343e-3,-6.845914e-2,6.674642e-2,-5.722136e-3,413.1479,144.3069)" + x1="507.37094" + y1="290.88303" + x2="608.41498" + y2="398.94208" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient13837" + id="linearGradient17598" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-3.166377e-2,-0.357859,0.348906,-2.991153e-2,294.2438,516.1006)" + x1="1130.2096" + y1="518.44458" + x2="1086.9674" + y2="514.52026" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient18511" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(27.99914,1.31776)" + x1="432.34427" + y1="65.815475" + x2="432.34427" + y2="43.934036" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient18513" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(10.43883,-16.39223)" + x1="431.24716" + y1="75.79641" + x2="431.24716" + y2="57.478249" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient18515" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(10.43883,-16.39223)" + x1="448.74854" + y1="82.005356" + x2="449.41235" + y2="66.314919" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient18517" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(7.618051e-2,0,0,7.282925e-2,240.3801,-14.402)" + x1="2642.5115" + y1="1026.281" + x2="2642.5115" + y2="921.65161" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7919" + id="linearGradient18519" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-8.997371e-3,-0.10169,9.914284e-2,-8.499707e-3,437.1222,162.91)" + x1="507.37094" + y1="290.88303" + x2="608.41498" + y2="398.94208" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient13837" + id="linearGradient18521" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-4.703229e-2,-0.531566,0.518253,-4.443083e-2,260.5061,715.1747)" + x1="1129.3501" + y1="517.39514" + x2="1087.9879" + y2="513.64142" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient18523" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(8.982791e-3,0.101525,-9.979687e-2,8.555778e-3,603.3036,-46.83784)" + x1="1394.5476" + y1="1587.7018" + x2="1412.9457" + y2="1547.1619" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient18525" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-8.900061e-3,-0.10059,9.960667e-2,-8.539472e-3,325.5096,251.3287)" + x1="1394.5476" + y1="1587.7018" + x2="1412.9457" + y2="1547.1619" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient18565" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(27.99914,1.31776)" + x1="432.34427" + y1="65.815475" + x2="432.34427" + y2="43.934036" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient18567" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(10.43883,-16.39223)" + x1="431.24716" + y1="75.79641" + x2="431.24716" + y2="57.478249" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient18569" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(10.43883,-16.39223)" + x1="448.74854" + y1="82.005356" + x2="449.41235" + y2="66.314919" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient18571" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(7.618051e-2,0,0,7.282925e-2,240.3801,-14.402)" + x1="2642.5115" + y1="1026.281" + x2="2642.5115" + y2="921.65161" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7919" + id="linearGradient18573" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-8.997371e-3,-0.10169,9.914284e-2,-8.499707e-3,437.1222,162.91)" + x1="507.37094" + y1="290.88303" + x2="608.41498" + y2="398.94208" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient13837" + id="linearGradient18575" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-4.703229e-2,-0.531566,0.518253,-4.443083e-2,260.5061,715.1747)" + x1="1129.3501" + y1="517.39514" + x2="1087.9879" + y2="513.64142" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient18577" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(8.982791e-3,0.101525,-9.979687e-2,8.555778e-3,603.3036,-46.83784)" + x1="1394.5476" + y1="1587.7018" + x2="1412.9457" + y2="1547.1619" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient18579" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-8.900061e-3,-0.10059,9.960667e-2,-8.539472e-3,325.5096,251.3287)" + x1="1394.5476" + y1="1587.7018" + x2="1412.9457" + y2="1547.1619" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient18600" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1,0,0,1.089096,311.6482,724.1753)" + x1="323.01694" + y1="149.89334" + x2="323.01694" + y2="127.84659" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient18602" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-0.133485,0,0,0.231539,638.0594,863.971)" + x1="126.05801" + y1="41.340328" + x2="-155.95905" + y2="41.340332" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="radialGradient18604" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.316498,1.408891e-6,-1.390072e-6,1.298913,212.391,693.9915)" + cx="314.87411" + cy="143.0015" + fx="314.87411" + fy="143.0015" + r="4.9997702" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient18606" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.225029,0,0,0.211095,493.6644,805.494)" + x1="571.4317" + y1="320.04877" + x2="571.4317" + y2="286.65356" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient18608" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(7.606155e-2,0,0,3.553691e-2,600.6234,862.314)" + x1="341.47736" + y1="119.2719" + x2="340.59683" + y2="235.50371" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient3870" + id="linearGradient18837" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.413732,0,0,1.438374,571.1076,608.8882)" + x1="66.578979" + y1="220.57655" + x2="66.578979" + y2="160.03337" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient18839" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.298967,0,0,0.305554,490.1007,805.8032)" + x1="622.77716" + y1="379.93118" + x2="622.77716" + y2="278.40219" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient18841" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.194074,0,0,0.347632,649.5234,890.3166)" + x1="89.630951" + y1="-3.8040497" + x2="89.910187" + y2="89.299561" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient18843" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.195263,0,0,0.336987,649.468,890.5356)" + x1="85.538399" + y1="102.15246" + x2="84.364136" + y2="-3.2729344" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient18845" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-0.194914,0,0,0.337032,684.4294,890.3832)" + x1="126.05801" + y1="41.340328" + x2="-155.95905" + y2="41.340332" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient13837" + id="linearGradient18847" + gradientUnits="userSpaceOnUse" + x1="-44.450043" + y1="100.95689" + x2="-99.26667" + y2="21.617956" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient18849" + gradientUnits="userSpaceOnUse" + x1="-87.630737" + y1="103.75634" + x2="-87.630737" + y2="55.26902" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient18851" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.328588,0,0,0.307274,473.0037,804.4055)" + x1="571.43158" + y1="320.04846" + x2="571.43158" + y2="286.65329" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient18853" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.111065,0,0,5.172816e-2,629.2236,887.114)" + x1="341.47736" + y1="119.2719" + x2="340.59683" + y2="235.50371" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient3870" + id="linearGradient18855" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.413732,0,0,1.438374,571.1076,608.8882)" + x1="66.578979" + y1="220.57655" + x2="66.578979" + y2="160.03337" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient18857" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.298967,0,0,0.305554,490.1007,805.8032)" + x1="622.77716" + y1="379.93118" + x2="622.77716" + y2="278.40219" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient18859" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.194074,0,0,0.347632,649.5234,890.3166)" + x1="89.630951" + y1="-3.8040497" + x2="89.910187" + y2="89.299561" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient18861" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.195263,0,0,0.336987,649.468,890.5356)" + x1="85.538399" + y1="102.15246" + x2="84.364136" + y2="-3.2729344" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient18863" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-0.194914,0,0,0.337032,684.4294,890.3832)" + x1="126.05801" + y1="41.340328" + x2="-155.95905" + y2="41.340332" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient13837" + id="linearGradient18865" + gradientUnits="userSpaceOnUse" + x1="-44.450043" + y1="100.95689" + x2="-99.26667" + y2="21.617956" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient18867" + gradientUnits="userSpaceOnUse" + x1="-87.630737" + y1="103.75634" + x2="-87.630737" + y2="55.26902" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient18869" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.328588,0,0,0.307274,473.0037,804.4055)" + x1="571.43158" + y1="320.04846" + x2="571.43158" + y2="286.65329" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient18871" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.111065,0,0,5.172816e-2,629.2236,887.114)" + x1="341.47736" + y1="119.2719" + x2="340.59683" + y2="235.50371" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient3870" + id="linearGradient18899" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.413732,0,0,1.438374,571.1076,608.8882)" + x1="66.578979" + y1="220.57655" + x2="66.578979" + y2="160.03337" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient18901" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.298967,0,0,0.305554,490.1007,805.8032)" + x1="622.77716" + y1="379.93118" + x2="622.77716" + y2="278.40219" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient18903" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.194074,0,0,0.347632,649.5234,890.3166)" + x1="89.630951" + y1="-3.8040497" + x2="89.910187" + y2="89.299561" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient18905" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.195263,0,0,0.336987,649.468,890.5356)" + x1="85.538399" + y1="102.15246" + x2="84.364136" + y2="-3.2729344" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient18907" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-0.194914,0,0,0.337032,684.4294,890.3832)" + x1="126.05801" + y1="41.340328" + x2="-155.95905" + y2="41.340332" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient13837" + id="linearGradient18909" + gradientUnits="userSpaceOnUse" + x1="-44.450043" + y1="100.95689" + x2="-99.26667" + y2="21.617956" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient18911" + gradientUnits="userSpaceOnUse" + x1="-87.630737" + y1="103.75634" + x2="-87.630737" + y2="55.26902" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient18913" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.328588,0,0,0.307274,473.0037,804.4055)" + x1="571.43158" + y1="320.04846" + x2="571.43158" + y2="286.65329" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient18915" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.111065,0,0,5.172816e-2,629.2236,887.114)" + x1="341.47736" + y1="119.2719" + x2="340.59683" + y2="235.50371" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7919" + id="linearGradient18927" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-8.54661e-3,-9.65924e-2,9.417587e-2,-8.07365e-3,609.1845,1082.889)" + x1="507.37094" + y1="290.88303" + x2="608.41498" + y2="398.94208" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient18929" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-4.467601e-2,-0.504921,0.492289,-4.220369e-2,441.4167,1607.471)" + x1="1087.3936" + y1="514.1344" + x2="1129.889" + y2="517.99091" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7919" + id="linearGradient18931" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-8.54661e-3,-9.65924e-2,9.417587e-2,-8.07365e-3,609.1845,1082.889)" + x1="507.37094" + y1="290.88303" + x2="608.41498" + y2="398.94208" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient18933" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-4.467601e-2,-0.504921,0.492289,-4.220369e-2,441.4167,1607.471)" + x1="1087.3936" + y1="514.1344" + x2="1129.889" + y2="517.99091" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7919" + id="linearGradient18945" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-1.299645e-2,-0.146888,0.143209,-1.227759e-2,430.5184,268.9886)" + x1="510.00525" + y1="299.785" + x2="608.41498" + y2="398.94208" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient18947" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-6.793684e-2,-0.767832,0.748602,-6.41791e-2,175.4015,1066.719)" + x1="1087.9877" + y1="513.64142" + x2="1129.3499" + y2="517.39508" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient18949" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.297539e-2,0.14665,-0.144154,1.235858e-2,670.5628,-33.9865)" + x1="1394.5476" + y1="1587.7018" + x2="1412.9457" + y2="1547.1619" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient18951" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-1.285589e-2,-0.145299,0.143879,-1.233503e-2,269.2972,396.7066)" + x1="1394.5476" + y1="1587.7018" + x2="1412.9457" + y2="1547.1619" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7919" + id="linearGradient18963" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-1.299645e-2,-0.146888,0.143209,-1.227759e-2,430.5184,268.9886)" + x1="510.00525" + y1="299.785" + x2="608.41498" + y2="398.94208" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient18965" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-6.793684e-2,-0.767832,0.748602,-6.41791e-2,175.4015,1066.719)" + x1="1087.9877" + y1="513.64142" + x2="1129.3499" + y2="517.39508" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient18967" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.297539e-2,0.14665,-0.144154,1.235858e-2,670.5628,-33.9865)" + x1="1394.5476" + y1="1587.7018" + x2="1412.9457" + y2="1547.1619" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient18969" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-1.285589e-2,-0.145299,0.143879,-1.233503e-2,269.2972,396.7066)" + x1="1394.5476" + y1="1587.7018" + x2="1412.9457" + y2="1547.1619" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient9597" + id="linearGradient19846" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.93864,0,0,0.865417,126.56479,334.7036)" + x1="304.73999" + y1="219.05894" + x2="304.73999" + y2="201.60123" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient9906" + id="linearGradient19848" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.93864,0,0,0.865417,126.56479,334.7036)" + x1="320.84555" + y1="201.60123" + x2="320.84555" + y2="219.05894" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient9597" + id="linearGradient19852" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.892923,0,0,1.743583,-227.00608,121.0296)" + x1="395.67776" + y1="231.79971" + x2="395.67776" + y2="203.73718" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient9906" + id="linearGradient19854" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.892923,0,0,1.743583,-227.00608,121.0296)" + x1="421.625" + y1="202.73347" + x2="421.625" + y2="231.79971" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient9597" + id="linearGradient19858" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(2.540768,0,0,2.340317,-413.74891,-16.920302)" + x1="395.67776" + y1="231.79971" + x2="395.67776" + y2="203.73718" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient9906" + id="linearGradient19860" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(2.540768,0,0,2.340317,-413.74891,-16.920302)" + x1="421.625" + y1="202.73347" + x2="421.625" + y2="231.79971" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient9906" + id="linearGradient19926" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.812882,0,0,0.812882,124.8274,172.4298)" + x1="432.43732" + y1="43.797894" + x2="432.43732" + y2="65.652176" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient9597" + id="linearGradient19928" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.812882,0,0,0.812882,110.5529,158.0337)" + x1="431.24716" + y1="75.79641" + x2="431.24716" + y2="57.478249" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient9597" + id="linearGradient19930" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.812882,0,0,0.812882,110.5529,158.0337)" + x1="448.74854" + y1="82.005356" + x2="448.74854" + y2="66.314919" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient9597" + id="linearGradient19932" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(6.192577e-2,0,0,5.920159e-2,297.468,159.6515)" + x1="2642.5115" + y1="1026.281" + x2="2642.5115" + y2="921.65161" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient9906" + id="linearGradient20270" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.812882,0,0,0.812882,124.8274,172.4298)" + x1="432.43732" + y1="43.797894" + x2="432.43732" + y2="65.652176" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient9597" + id="linearGradient20272" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.812882,0,0,0.812882,110.5529,158.0337)" + x1="431.24716" + y1="75.79641" + x2="431.24716" + y2="57.478249" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient9597" + id="linearGradient20274" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.812882,0,0,0.812882,110.5529,158.0337)" + x1="448.74854" + y1="82.005356" + x2="448.74854" + y2="66.314919" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient9597" + id="linearGradient20276" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(6.192577e-2,0,0,5.920159e-2,297.468,159.6515)" + x1="2642.5115" + y1="1026.281" + x2="2642.5115" + y2="921.65161" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient9906" + id="linearGradient20278" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.812882,0,0,0.812882,124.8274,172.4298)" + x1="432.43732" + y1="43.797894" + x2="432.43732" + y2="65.652176" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient9597" + id="linearGradient20280" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.812882,0,0,0.812882,110.5529,158.0337)" + x1="431.24716" + y1="75.79641" + x2="431.24716" + y2="57.478249" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient9597" + id="linearGradient20282" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.812882,0,0,0.812882,110.5529,158.0337)" + x1="448.74854" + y1="82.005356" + x2="448.74854" + y2="66.314919" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient9597" + id="linearGradient20284" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(6.192577e-2,0,0,5.920159e-2,297.468,159.6515)" + x1="2642.5115" + y1="1026.281" + x2="2642.5115" + y2="921.65161" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient9906" + id="linearGradient20344" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.812882,0,0,0.812882,124.8274,172.4298)" + x1="432.43732" + y1="43.797894" + x2="432.43732" + y2="65.652176" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient9597" + id="linearGradient20346" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.812882,0,0,0.812882,110.5529,158.0337)" + x1="431.24716" + y1="75.79641" + x2="431.24716" + y2="57.478249" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient9597" + id="linearGradient20348" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.812882,0,0,0.812882,110.5529,158.0337)" + x1="448.74854" + y1="82.005356" + x2="448.74854" + y2="66.314919" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient9597" + id="linearGradient20350" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(6.192577e-2,0,0,5.920159e-2,297.468,159.6515)" + x1="2642.5115" + y1="1026.281" + x2="2642.5115" + y2="921.65161" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient9906" + id="linearGradient20352" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.812882,0,0,0.812882,124.8274,172.4298)" + x1="432.43732" + y1="43.797894" + x2="432.43732" + y2="65.652176" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient9597" + id="linearGradient20354" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.812882,0,0,0.812882,110.5529,158.0337)" + x1="431.24716" + y1="75.79641" + x2="431.24716" + y2="57.478249" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient9597" + id="linearGradient20356" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.812882,0,0,0.812882,110.5529,158.0337)" + x1="448.74854" + y1="82.005356" + x2="448.74854" + y2="66.314919" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient9597" + id="linearGradient20358" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(6.192577e-2,0,0,5.920159e-2,297.468,159.6515)" + x1="2642.5115" + y1="1026.281" + x2="2642.5115" + y2="921.65161" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient9906" + id="linearGradient20418" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.812882,0,0,0.812882,124.8274,172.4298)" + x1="432.43732" + y1="43.797894" + x2="432.43732" + y2="65.652176" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient9597" + id="linearGradient20420" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.812882,0,0,0.812882,110.5529,158.0337)" + x1="431.24716" + y1="75.79641" + x2="431.24716" + y2="57.478249" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient9597" + id="linearGradient20422" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.812882,0,0,0.812882,110.5529,158.0337)" + x1="448.74854" + y1="82.005356" + x2="448.74854" + y2="66.314919" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient9597" + id="linearGradient20424" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(6.192577e-2,0,0,5.920159e-2,297.468,159.6515)" + x1="2642.5115" + y1="1026.281" + x2="2642.5115" + y2="921.65161" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient9906" + id="linearGradient20426" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.812882,0,0,0.812882,124.8274,172.4298)" + x1="432.43732" + y1="43.797894" + x2="432.43732" + y2="65.652176" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient9597" + id="linearGradient20428" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.812882,0,0,0.812882,110.5529,158.0337)" + x1="431.24716" + y1="75.79641" + x2="431.24716" + y2="57.478249" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient9597" + id="linearGradient20430" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.812882,0,0,0.812882,110.5529,158.0337)" + x1="448.74854" + y1="82.005356" + x2="448.74854" + y2="66.314919" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient9597" + id="linearGradient20432" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(6.192577e-2,0,0,5.920159e-2,297.468,159.6515)" + x1="2642.5115" + y1="1026.281" + x2="2642.5115" + y2="921.65161" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient9906" + id="linearGradient20434" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.812882,0,0,0.812882,124.8274,172.4298)" + x1="432.43732" + y1="43.797894" + x2="432.43732" + y2="65.652176" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient9597" + id="linearGradient20436" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.812882,0,0,0.812882,110.5529,158.0337)" + x1="431.24716" + y1="75.79641" + x2="431.24716" + y2="57.478249" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient9597" + id="linearGradient20438" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.812882,0,0,0.812882,110.5529,158.0337)" + x1="448.74854" + y1="82.005356" + x2="448.74854" + y2="66.314919" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient9597" + id="linearGradient20440" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(6.192577e-2,0,0,5.920159e-2,297.468,159.6515)" + x1="2642.5115" + y1="1026.281" + x2="2642.5115" + y2="921.65161" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient9906" + id="linearGradient20442" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.812882,0,0,0.812882,124.8274,172.4298)" + x1="432.43732" + y1="43.797894" + x2="432.43732" + y2="65.652176" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient9597" + id="linearGradient20444" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.812882,0,0,0.812882,110.5529,158.0337)" + x1="431.24716" + y1="75.79641" + x2="431.24716" + y2="57.478249" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient9597" + id="linearGradient20446" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.812882,0,0,0.812882,110.5529,158.0337)" + x1="448.74854" + y1="82.005356" + x2="448.74854" + y2="66.314919" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient9597" + id="linearGradient20448" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(6.192577e-2,0,0,5.920159e-2,297.468,159.6515)" + x1="2642.5115" + y1="1026.281" + x2="2642.5115" + y2="921.65161" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient20480" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(27.99914,1.31776)" + x1="432.34427" + y1="65.815475" + x2="432.34427" + y2="43.934036" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient20482" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(10.43883,-16.39223)" + x1="431.24716" + y1="75.79641" + x2="431.24716" + y2="57.478249" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient20484" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(10.43883,-16.39223)" + x1="448.74854" + y1="82.005356" + x2="448.74854" + y2="66.314919" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient20486" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(7.618051e-2,0,0,7.282925e-2,240.3801,-14.402)" + x1="2642.5115" + y1="1026.281" + x2="2642.5115" + y2="921.65161" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient20518" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(27.99914,1.31776)" + x1="432.34427" + y1="65.815475" + x2="432.34427" + y2="43.934036" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient20520" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(10.43883,-16.39223)" + x1="431.24716" + y1="75.79641" + x2="431.24716" + y2="57.478249" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient20522" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(10.43883,-16.39223)" + x1="448.74854" + y1="82.005356" + x2="448.74854" + y2="66.314919" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient20524" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(7.618051e-2,0,0,7.282925e-2,240.3801,-14.402)" + x1="2642.5115" + y1="1026.281" + x2="2642.5115" + y2="921.65161" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient20606" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(27.99914,1.31776)" + x1="432.34427" + y1="65.815475" + x2="432.34427" + y2="43.934036" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient20608" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(10.43883,-16.39223)" + x1="431.24716" + y1="75.79641" + x2="431.24716" + y2="57.478249" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient20610" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(10.43883,-16.39223)" + x1="448.74854" + y1="82.005356" + x2="448.74854" + y2="66.314919" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient20612" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(7.618051e-2,0,0,7.282925e-2,240.3801,-14.402)" + x1="2642.5115" + y1="1026.281" + x2="2642.5115" + y2="921.65161" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7919" + id="linearGradient20614" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-8.997371e-3,-0.10169,9.914284e-2,-8.499707e-3,440.7439,367.3022)" + x1="507.37094" + y1="290.88303" + x2="608.41498" + y2="398.94208" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient13837" + id="linearGradient20616" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-4.703229e-2,-0.531566,0.518253,-4.443083e-2,264.1278,919.5669)" + x1="1129.3501" + y1="517.39514" + x2="1087.9879" + y2="513.64142" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient20618" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(8.982791e-3,0.101525,-9.979687e-2,8.555778e-3,606.9253,157.5544)" + x1="1394.5476" + y1="1587.7018" + x2="1412.9457" + y2="1547.1619" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient20620" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-8.900061e-3,-0.10059,9.960667e-2,-8.539472e-3,329.1313,455.7209)" + x1="1394.5476" + y1="1587.7018" + x2="1412.9457" + y2="1547.1619" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient20664" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(27.99914,1.31776)" + x1="432.34427" + y1="65.815475" + x2="432.34427" + y2="43.934036" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient20666" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(10.43883,-16.39223)" + x1="431.24716" + y1="75.79641" + x2="431.24716" + y2="57.478249" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient20668" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(10.43883,-16.39223)" + x1="448.74854" + y1="82.005356" + x2="448.74854" + y2="66.314919" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient20670" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(7.618051e-2,0,0,7.282925e-2,240.3801,-14.402)" + x1="2642.5115" + y1="1026.281" + x2="2642.5115" + y2="921.65161" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7919" + id="linearGradient20672" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-8.997371e-3,-0.10169,9.914284e-2,-8.499707e-3,440.7439,367.3022)" + x1="507.37094" + y1="290.88303" + x2="608.41498" + y2="398.94208" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient13837" + id="linearGradient20674" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-4.703229e-2,-0.531566,0.518253,-4.443083e-2,264.1278,919.5669)" + x1="1129.3501" + y1="517.39514" + x2="1087.9879" + y2="513.64142" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient20676" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(8.982791e-3,0.101525,-9.979687e-2,8.555778e-3,606.9253,157.5544)" + x1="1394.5476" + y1="1587.7018" + x2="1412.9457" + y2="1547.1619" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient20678" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-8.900061e-3,-0.10059,9.960667e-2,-8.539472e-3,329.1313,455.7209)" + x1="1394.5476" + y1="1587.7018" + x2="1412.9457" + y2="1547.1619" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient20722" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(27.99914,1.31776)" + x1="432.34427" + y1="65.815475" + x2="432.34427" + y2="43.934036" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient20724" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(10.43883,-16.39223)" + x1="431.24716" + y1="75.79641" + x2="431.24716" + y2="57.478249" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient20726" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(10.43883,-16.39223)" + x1="448.74854" + y1="82.005356" + x2="448.74854" + y2="66.314919" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient20728" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(7.618051e-2,0,0,7.282925e-2,240.3801,-14.402)" + x1="2642.5115" + y1="1026.281" + x2="2642.5115" + y2="921.65161" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7919" + id="linearGradient20730" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-8.997371e-3,-0.10169,9.914284e-2,-8.499707e-3,440.7439,367.3022)" + x1="507.37094" + y1="290.88303" + x2="608.41498" + y2="398.94208" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient13837" + id="linearGradient20732" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-4.703229e-2,-0.531566,0.518253,-4.443083e-2,264.1278,919.5669)" + x1="1129.3501" + y1="517.39514" + x2="1087.9879" + y2="513.64142" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient20734" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(8.982791e-3,0.101525,-9.979687e-2,8.555778e-3,606.9253,157.5544)" + x1="1394.5476" + y1="1587.7018" + x2="1412.9457" + y2="1547.1619" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient20736" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-8.900061e-3,-0.10059,9.960667e-2,-8.539472e-3,329.1313,455.7209)" + x1="1394.5476" + y1="1587.7018" + x2="1412.9457" + y2="1547.1619" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient20752" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.785942,0,0,0.735282,158.3914,239.4019)" + x1="390.20804" + y1="182.18143" + x2="390.20804" + y2="159.18143" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient20754" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.785942,0,0,0.735282,158.3914,239.4019)" + x1="409.20804" + y1="182.18143" + x2="409.20804" + y2="159.18143" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient20756" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.786139,0,0,0.77135,156.9596,207.9717)" + x1="392.93735" + y1="193.43317" + x2="392.93735" + y2="230.89081" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient20758" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.785942,0,0,0.735282,152.3063,234.3383)" + x1="390.20804" + y1="182.18143" + x2="390.20804" + y2="159.18143" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient20760" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.785942,0,0,0.735282,152.3063,234.3383)" + x1="409.20804" + y1="182.18143" + x2="409.20804" + y2="159.18143" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient20762" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.786139,0,0,0.77135,150.8745,202.9081)" + x1="392.93735" + y1="193.43317" + x2="392.93735" + y2="230.89081" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient20778" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.785942,0,0,0.735282,158.3914,239.4019)" + x1="390.20804" + y1="182.18143" + x2="390.20804" + y2="159.18143" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient20780" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.785942,0,0,0.735282,158.3914,239.4019)" + x1="409.20804" + y1="182.18143" + x2="409.20804" + y2="159.18143" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient20782" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.786139,0,0,0.77135,156.9596,207.9717)" + x1="392.93735" + y1="193.43317" + x2="392.93735" + y2="230.89081" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient20784" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.785942,0,0,0.735282,152.3063,234.3383)" + x1="390.20804" + y1="182.18143" + x2="390.20804" + y2="159.18143" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient20786" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.785942,0,0,0.735282,152.3063,234.3383)" + x1="409.20804" + y1="182.18143" + x2="409.20804" + y2="159.18143" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient20788" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.786139,0,0,0.77135,150.8745,202.9081)" + x1="392.93735" + y1="193.43317" + x2="392.93735" + y2="230.89081" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient20804" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.785942,0,0,0.735282,158.3914,239.4019)" + x1="390.20804" + y1="182.18143" + x2="390.20804" + y2="159.18143" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient20806" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.785942,0,0,0.735282,158.3914,239.4019)" + x1="409.20804" + y1="182.18143" + x2="409.20804" + y2="159.18143" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient20808" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.786139,0,0,0.77135,156.9596,207.9717)" + x1="392.93735" + y1="193.43317" + x2="392.93735" + y2="230.89081" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient20810" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.785942,0,0,0.735282,152.3063,234.3383)" + x1="390.20804" + y1="182.18143" + x2="390.20804" + y2="159.18143" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient20812" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.785942,0,0,0.735282,152.3063,234.3383)" + x1="409.20804" + y1="182.18143" + x2="409.20804" + y2="159.18143" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient20814" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.786139,0,0,0.77135,150.8745,202.9081)" + x1="392.93735" + y1="193.43317" + x2="392.93735" + y2="230.89081" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient20830" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.999739,0,0,0.953222,71.83257,233.751)" + x1="390.20804" + y1="182.18143" + x2="390.20804" + y2="159.18143" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient20832" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.999739,0,0,0.953222,71.83257,233.751)" + x1="409.20804" + y1="182.18143" + x2="409.20804" + y2="159.18143" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient20834" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(70.00097,193.003)" + x1="392.93735" + y1="193.43317" + x2="392.93735" + y2="230.89081" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient13837" + id="linearGradient20836" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.676915,0.40565,0.410155,-0.669481,376.4461,557.8599)" + spreadMethod="reflect" + x1="-1.78125" + y1="242.48718" + x2="-0.27687499" + y2="242.48718" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient3870" + id="linearGradient20838" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.676915,0.40565,0.410155,-0.669481,376.3937,557.9171)" + x1="-1.9580266" + y1="230.93272" + x2="0.14081553" + y2="230.84433" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient20854" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.999739,0,0,0.953222,71.83257,233.751)" + x1="390.20804" + y1="182.18143" + x2="390.20804" + y2="159.18143" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient20856" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.999739,0,0,0.953222,71.83257,233.751)" + x1="409.20804" + y1="182.18143" + x2="409.20804" + y2="159.18143" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient20858" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(70.00097,193.003)" + x1="392.93735" + y1="193.43317" + x2="392.93735" + y2="230.89081" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient13837" + id="linearGradient20860" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.676915,0.40565,0.410155,-0.669481,376.4461,557.8599)" + spreadMethod="reflect" + x1="-1.78125" + y1="242.48718" + x2="-0.27687499" + y2="242.48718" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient3870" + id="linearGradient20862" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.676915,0.40565,0.410155,-0.669481,376.3937,557.9171)" + x1="-1.9580266" + y1="230.93272" + x2="0.14081553" + y2="230.84433" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient20878" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.999739,0,0,0.953222,71.83257,233.751)" + x1="390.20804" + y1="182.18143" + x2="390.20804" + y2="159.18143" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient20880" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.999739,0,0,0.953222,71.83257,233.751)" + x1="409.20804" + y1="182.18143" + x2="409.20804" + y2="159.18143" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient20882" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(70.00097,193.003)" + x1="392.93735" + y1="193.43317" + x2="392.93735" + y2="230.89081" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient13837" + id="linearGradient20884" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.676915,0.40565,0.410155,-0.669481,376.4461,557.8599)" + spreadMethod="reflect" + x1="-1.78125" + y1="242.48718" + x2="-0.27687499" + y2="242.48718" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient3870" + id="linearGradient20886" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.676915,0.40565,0.410155,-0.669481,376.3937,557.9171)" + x1="-1.9580266" + y1="230.93272" + x2="0.14081553" + y2="230.84433" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient20896" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.999739,0,0,0.953222,22.5424,42.76085)" + x1="390.20804" + y1="182.18143" + x2="390.20804" + y2="159.18143" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient20898" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.999739,0,0,0.953222,22.5424,42.76085)" + x1="409.20804" + y1="182.18143" + x2="409.20804" + y2="159.18143" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient20900" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(20.71716,2.010781)" + x1="392.93735" + y1="193.43317" + x2="392.93735" + y2="230.89081" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient20910" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.999739,0,0,0.953222,22.5424,42.76085)" + x1="390.20804" + y1="182.18143" + x2="390.20804" + y2="159.18143" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient20912" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.999739,0,0,0.953222,22.5424,42.76085)" + x1="409.20804" + y1="182.18143" + x2="409.20804" + y2="159.18143" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient20914" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(20.71716,2.010781)" + x1="392.93735" + y1="193.43317" + x2="392.93735" + y2="230.89081" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient20924" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.999739,0,0,0.953222,22.5424,42.76085)" + x1="390.20804" + y1="182.18143" + x2="390.20804" + y2="159.18143" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient20926" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.999739,0,0,0.953222,22.5424,42.76085)" + x1="409.20804" + y1="182.18143" + x2="409.20804" + y2="159.18143" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient20928" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(20.71716,2.010781)" + x1="392.93735" + y1="193.43317" + x2="392.93735" + y2="230.89081" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient20964" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.999739,0,0,0.953222,22.5424,42.76085)" + x1="390.20804" + y1="182.18143" + x2="390.20804" + y2="159.18143" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient20966" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.999739,0,0,0.953222,22.5424,42.76085)" + x1="409.20804" + y1="182.18143" + x2="409.20804" + y2="159.18143" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient20968" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(20.71716,2.010781)" + x1="392.93735" + y1="193.43317" + x2="392.93735" + y2="230.89081" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient20970" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.62791,0.627888,-0.62791,0.627888,521.9233,206.3054)" + x1="168.18773" + y1="260.09671" + x2="156.90306" + y2="248.81204" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient20972" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.62791,0.627888,-0.62791,0.627888,521.9233,206.3054)" + x1="179.47237" + y1="248.81206" + x2="168.18771" + y2="237.52739" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient20986" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.999739,0,0,0.953222,22.5424,42.76085)" + x1="390.20804" + y1="182.18143" + x2="390.20804" + y2="159.18143" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient20988" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.999739,0,0,0.953222,22.5424,42.76085)" + x1="409.20804" + y1="182.18143" + x2="409.20804" + y2="159.18143" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient20990" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(20.71716,2.010781)" + x1="392.93735" + y1="193.43317" + x2="392.93735" + y2="230.89081" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient20992" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.62791,0.627888,-0.62791,0.627888,521.9233,206.3054)" + x1="168.18773" + y1="260.09671" + x2="156.90306" + y2="248.81204" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient20994" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.62791,0.627888,-0.62791,0.627888,521.9233,206.3054)" + x1="179.47237" + y1="248.81206" + x2="168.18771" + y2="237.52739" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient21059" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.999739,0,0,0.953222,22.5424,42.76085)" + x1="390.20804" + y1="182.18143" + x2="390.20804" + y2="159.18143" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient21061" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.999739,0,0,0.953222,22.5424,42.76085)" + x1="409.20804" + y1="182.18143" + x2="409.20804" + y2="159.18143" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient21063" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(20.71716,2.010781)" + x1="392.93735" + y1="193.43317" + x2="392.93735" + y2="230.89081" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient21065" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.43065,0.430635,-0.43065,0.430635,467.2213,282.8466)" + x1="168.18773" + y1="260.09671" + x2="156.90306" + y2="248.81204" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient21067" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.43065,0.430635,-0.43065,0.430635,467.2213,282.8466)" + x1="179.47237" + y1="248.81206" + x2="168.18771" + y2="237.52739" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7919" + id="linearGradient21079" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-6.943932e-3,-7.847897e-2,7.651583e-2,-6.559661e-3,595.0772,76.80545)" + x1="507.37094" + y1="290.88303" + x2="608.41498" + y2="398.94208" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient21081" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-3.629827e-2,-0.410236,0.399974,-3.428956e-2,458.7696,503.0157)" + x1="1086.9674" + y1="514.52026" + x2="1130.2096" + y2="518.44458" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient21083" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(6.932679e-3,7.835179e-2,-7.702059e-2,6.602935e-3,723.6754,-85.36357)" + x1="1394.5476" + y1="1587.7018" + x2="1412.9457" + y2="1547.1619" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient21085" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-6.868831e-3,-7.763019e-2,7.68738e-2,-6.590351e-3,508.6368,145.3809)" + x1="1394.5476" + y1="1587.7018" + x2="1412.9457" + y2="1547.1619" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient21087" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.100439,2.663402e-2,2.665516e-2,-0.100359,410.1769,161.7484)" + x1="1492.3453" + y1="1614.792" + x2="1458.5851" + y2="1742.0037" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient21089" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-0.201448,0,0,0.201288,623.1647,16.5145)" + x1="31.778969" + y1="113.87714" + x2="31.778969" + y2="45.984291" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7919" + id="linearGradient21091" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-1.274366e-2,-0.144128,0.140424,-1.204688e-2,437.2895,586.8257)" + x1="507.37094" + y1="290.88303" + x2="608.41498" + y2="398.94208" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient21093" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-6.661538e-2,-0.753401,0.734041,-6.297308e-2,187.1349,1369.564)" + x1="1086.9674" + y1="514.52026" + x2="1130.2096" + y2="518.44458" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient21095" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.249703e-2,0.141338,-0.138839,1.191098e-2,669.5185,293.0538)" + x1="1394.5476" + y1="1587.7018" + x2="1412.9457" + y2="1547.1619" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient21097" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-1.238193e-2,-0.140036,0.138575,-1.188828e-2,281.8843,709.2919)" + x1="1394.5476" + y1="1587.7018" + x2="1412.9457" + y2="1547.1619" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient21099" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.181054,4.80449e-2,4.804928e-2,-0.181037,108.3979,738.8171)" + x1="1492.3453" + y1="1614.792" + x2="1458.5851" + y2="1742.0037" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient21101" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-0.363135,0,0,0.363102,492.3353,476.8309)" + x1="31.778969" + y1="113.87714" + x2="31.778969" + y2="45.984291" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7919" + id="linearGradient21113" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-1.274366e-2,-0.144128,0.140424,-1.204688e-2,437.2895,586.8257)" + x1="507.37094" + y1="290.88303" + x2="608.41498" + y2="398.94208" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient21115" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-6.661538e-2,-0.753401,0.734041,-6.297308e-2,187.1349,1369.564)" + x1="1086.9674" + y1="514.52026" + x2="1130.2096" + y2="518.44458" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient21117" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.249703e-2,0.141338,-0.138839,1.191098e-2,669.5185,293.0538)" + x1="1394.5476" + y1="1587.7018" + x2="1412.9457" + y2="1547.1619" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient21119" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-1.238193e-2,-0.140036,0.138575,-1.188828e-2,281.8843,709.2919)" + x1="1394.5476" + y1="1587.7018" + x2="1412.9457" + y2="1547.1619" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient21121" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.181054,4.80449e-2,4.804928e-2,-0.181037,108.3979,738.8171)" + x1="1492.3453" + y1="1614.792" + x2="1458.5851" + y2="1742.0037" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient21123" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-0.363135,0,0,0.363102,492.3353,476.8309)" + x1="31.778969" + y1="113.87714" + x2="31.778969" + y2="45.984291" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7919" + id="linearGradient21135" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-1.274366e-2,-0.144128,0.140424,-1.204688e-2,437.2895,586.8257)" + x1="507.37094" + y1="290.88303" + x2="608.41498" + y2="398.94208" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient21137" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-6.661538e-2,-0.753401,0.734041,-6.297308e-2,187.1349,1369.564)" + x1="1086.9674" + y1="514.52026" + x2="1130.2096" + y2="518.44458" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient21139" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.249703e-2,0.141338,-0.138839,1.191098e-2,669.5185,293.0538)" + x1="1394.5476" + y1="1587.7018" + x2="1412.9457" + y2="1547.1619" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient21141" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-1.238193e-2,-0.140036,0.138575,-1.188828e-2,281.8843,709.2919)" + x1="1394.5476" + y1="1587.7018" + x2="1412.9457" + y2="1547.1619" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient21143" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.181054,4.80449e-2,4.804928e-2,-0.181037,108.3979,738.8171)" + x1="1492.3453" + y1="1614.792" + x2="1458.5851" + y2="1742.0037" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient21145" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-0.363135,0,0,0.363102,492.3353,476.8309)" + x1="31.778969" + y1="113.87714" + x2="31.778969" + y2="45.984291" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7919" + id="linearGradient21235" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-6.943932e-3,-7.847897e-2,7.651583e-2,-6.559661e-3,595.0772,76.80545)" + x1="507.37094" + y1="290.88303" + x2="608.41498" + y2="398.94208" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient21237" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-3.629827e-2,-0.410236,0.399974,-3.428956e-2,458.7696,503.0157)" + x1="1086.9674" + y1="514.52026" + x2="1130.2096" + y2="518.44458" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient21239" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(6.932679e-3,7.835179e-2,-7.702059e-2,6.602935e-3,723.6754,-85.36357)" + x1="1394.5476" + y1="1587.7018" + x2="1412.9457" + y2="1547.1619" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient21241" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-6.868831e-3,-7.763019e-2,7.68738e-2,-6.590351e-3,508.6368,145.3809)" + x1="1394.5476" + y1="1587.7018" + x2="1412.9457" + y2="1547.1619" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient21243" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.100439,2.663402e-2,2.665516e-2,-0.100359,410.1769,161.7484)" + x1="1492.3453" + y1="1614.792" + x2="1458.5851" + y2="1742.0037" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient21245" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-0.201448,0,0,0.201288,623.1647,16.5145)" + x1="31.778969" + y1="113.87714" + x2="31.778969" + y2="45.984291" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10866" + id="linearGradient7934" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.138892,0.255956,0.312734,1.227236,485.3759,1636.644)" + spreadMethod="reflect" + x1="142.57724" + y1="86.521561" + x2="144.66388" + y2="84.748222" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient7936" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.472173,0,0,1.472173,-128.0924,1553.634)" + x1="542.03156" + y1="152.7681" + x2="551.5567" + y2="152.7681" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient7919" + id="radialGradient7938" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-8.442359e-5,0.634257,-0.731707,-9.739477e-5,629.8264,-3.386077)" + cx="431.8631" + cy="270.50006" + fx="431.8631" + fy="270.50006" + r="1.2339805" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient7940" + gradientUnits="userSpaceOnUse" + x1="430.08484" + y1="270.69183" + x2="431.49219" + y2="267.01645" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient7942" + gradientUnits="userSpaceOnUse" + x1="428.77151" + y1="454.65622" + x2="428.77151" + y2="435.00833" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient9912" + id="radialGradient7944" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.726147,-8.054512e-3,1.109145e-2,0.999938,109.5649,3.398898)" + cx="418.51233" + cy="454.95853" + fx="418.51233" + fy="454.95853" + r="11.005301" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient7946" + gradientUnits="userSpaceOnUse" + x1="407.50702" + y1="446.44885" + x2="429.51764" + y2="446.44885" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient7948" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.013601,0,0,1.013601,241.9262,1313.98)" + x1="415.12271" + y1="435.43039" + x2="415.12271" + y2="453.03961" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient7950" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.554799,0.554779,-0.554799,0.554779,709.2206,1533.643)" + x1="168.18773" + y1="260.09671" + x2="156.90306" + y2="248.81204" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient7952" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.554799,0.554779,-0.554799,0.554779,709.2206,1533.643)" + x1="179.47237" + y1="248.81206" + x2="168.18771" + y2="237.52739" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10866" + id="linearGradient7968" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.138892,0.255956,0.312734,1.227236,485.3759,1636.644)" + spreadMethod="reflect" + x1="142.57724" + y1="86.521561" + x2="144.66388" + y2="84.748222" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient7970" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.472173,0,0,1.472173,-128.0924,1553.634)" + x1="542.03156" + y1="152.7681" + x2="551.5567" + y2="152.7681" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient7919" + id="radialGradient7972" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-8.442359e-5,0.634257,-0.731707,-9.739477e-5,629.8264,-3.386077)" + cx="431.8631" + cy="270.50006" + fx="431.8631" + fy="270.50006" + r="1.2339805" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient7974" + gradientUnits="userSpaceOnUse" + x1="430.08484" + y1="270.69183" + x2="431.49219" + y2="267.01645" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient7976" + gradientUnits="userSpaceOnUse" + x1="428.77151" + y1="454.65622" + x2="428.77151" + y2="435.00833" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient9912" + id="radialGradient7978" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.726147,-8.054512e-3,1.109145e-2,0.999938,109.5649,3.398898)" + cx="418.51233" + cy="454.95853" + fx="418.51233" + fy="454.95853" + r="11.005301" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient7980" + gradientUnits="userSpaceOnUse" + x1="407.50702" + y1="446.44885" + x2="429.51764" + y2="446.44885" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient7982" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.013601,0,0,1.013601,241.9262,1313.98)" + x1="415.12271" + y1="435.43039" + x2="415.12271" + y2="453.03961" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient7984" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.554799,0.554779,-0.554799,0.554779,709.2206,1533.643)" + x1="168.18773" + y1="260.09671" + x2="156.90306" + y2="248.81204" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient7986" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.554799,0.554779,-0.554799,0.554779,709.2206,1533.643)" + x1="179.47237" + y1="248.81206" + x2="168.18771" + y2="237.52739" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10866" + id="linearGradient8003" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.138892,0.255956,0.312734,1.227236,485.3759,1636.644)" + spreadMethod="reflect" + x1="142.57724" + y1="86.521561" + x2="144.66388" + y2="84.748222" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient8005" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.472173,0,0,1.472173,-128.0924,1553.634)" + x1="542.03156" + y1="152.7681" + x2="551.5567" + y2="152.7681" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient7919" + id="radialGradient8007" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-8.442359e-5,0.634257,-0.731707,-9.739477e-5,629.8264,-3.386077)" + cx="431.8631" + cy="270.50006" + fx="431.8631" + fy="270.50006" + r="1.2339805" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient8009" + gradientUnits="userSpaceOnUse" + x1="430.08484" + y1="270.69183" + x2="431.49219" + y2="267.01645" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient8011" + gradientUnits="userSpaceOnUse" + x1="428.77151" + y1="454.65622" + x2="428.77151" + y2="435.00833" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient9912" + id="radialGradient8013" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.726147,-8.054512e-3,1.109145e-2,0.999938,109.5649,3.398898)" + cx="418.51233" + cy="454.95853" + fx="418.51233" + fy="454.95853" + r="11.005301" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient8015" + gradientUnits="userSpaceOnUse" + x1="407.50702" + y1="446.44885" + x2="429.51764" + y2="446.44885" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient8017" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.013601,0,0,1.013601,241.9262,1313.98)" + x1="415.12271" + y1="435.43039" + x2="415.12271" + y2="453.03961" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient8019" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.554799,0.554779,-0.554799,0.554779,709.2206,1533.643)" + x1="168.18773" + y1="260.09671" + x2="156.90306" + y2="248.81204" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient8021" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.554799,0.554779,-0.554799,0.554779,709.2206,1533.643)" + x1="179.47237" + y1="248.81206" + x2="168.18771" + y2="237.52739" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10866" + id="linearGradient8037" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.138892,0.255956,0.312734,1.227236,485.3759,1636.644)" + spreadMethod="reflect" + x1="142.57724" + y1="86.521561" + x2="144.66388" + y2="84.748222" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient8039" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.472173,0,0,1.472173,-128.0924,1553.634)" + x1="542.03156" + y1="152.7681" + x2="551.5567" + y2="152.7681" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient7919" + id="radialGradient8041" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-8.442359e-5,0.634257,-0.731707,-9.739477e-5,629.8264,-3.386077)" + cx="431.8631" + cy="270.50006" + fx="431.8631" + fy="270.50006" + r="1.2339805" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient8043" + gradientUnits="userSpaceOnUse" + x1="430.08484" + y1="270.69183" + x2="431.49219" + y2="267.01645" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient8045" + gradientUnits="userSpaceOnUse" + x1="428.77151" + y1="454.65622" + x2="428.77151" + y2="435.00833" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient9912" + id="radialGradient8047" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.726147,-8.054512e-3,1.109145e-2,0.999938,109.5649,3.398898)" + cx="418.51233" + cy="454.95853" + fx="418.51233" + fy="454.95853" + r="11.005301" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient8049" + gradientUnits="userSpaceOnUse" + x1="407.50702" + y1="446.44885" + x2="429.51764" + y2="446.44885" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient8051" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.013601,0,0,1.013601,241.9262,1313.98)" + x1="415.12271" + y1="435.43039" + x2="415.12271" + y2="453.03961" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient8053" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.554799,0.554779,-0.554799,0.554779,709.2206,1533.643)" + x1="168.18773" + y1="260.09671" + x2="156.90306" + y2="248.81204" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient8055" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.554799,0.554779,-0.554799,0.554779,709.2206,1533.643)" + x1="179.47237" + y1="248.81206" + x2="168.18771" + y2="237.52739" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient8335" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.5,0,0,0.538461,-553.0571,-245.2036)" + x1="50.28577" + y1="385.7197" + x2="50.28577" + y2="424.71976" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient8337" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(-958.8284,-48.01717)" + x1="409.91418" + y1="10.508587" + x2="409.91418" + y2="31.508587" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient8339" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-0.604969,0,0,-0.659967,553.1156,303.6917)" + x1="25.127533" + y1="417.70789" + x2="25.127533" + y2="389.9552" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4190" + id="linearGradient8341" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.82801,0,0,0.978453,-559.2408,-424.7224)" + x1="15.432588" + y1="413.62369" + x2="15.627617" + y2="404.25516" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient8343" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.940969,0,0,0.94346,-934.0313,-46.77246)" + x1="428.77371" + y1="30.368084" + x2="428.77371" + y2="18.649082" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient8345" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.024964,0,0,1.024964,393.9513,18.1768)" + x1="142.57724" + y1="86.521561" + x2="146.14665" + y2="83.54879" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient9558" + id="linearGradient8347" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.024964,0,0,1.024964,393.9513,18.1768)" + x1="139.162" + y1="79.300407" + x2="148.07057" + y2="89.934387" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7919" + id="linearGradient8349" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.024964,0,0,1.024964,393.9513,18.1768)" + x1="140.23837" + y1="78.309875" + x2="138.27634" + y2="79.971138" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient9906" + id="linearGradient8351" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.024964,0,0,1.024964,393.9513,18.1768)" + x1="136.05594" + y1="79.796364" + x2="143.14455" + y2="79.796364" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient9906" + id="linearGradient8353" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.024964,0,0,1.024964,393.9513,18.1768)" + x1="135.05788" + y1="77.974876" + x2="141.70506" + y2="77.974876" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient6969" + id="linearGradient8355" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.375,0,0,1.375,141.9955,29.16945)" + x1="318.5" + y1="98.323685" + x2="318.5" + y2="146.11829" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient6969" + id="linearGradient8357" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.375,0,0,1.375,141.9955,29.16945)" + x1="316.58795" + y1="117.51183" + x2="316.58795" + y2="53.17001" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient8359" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(5.359937e-2,-7.515489e-3,7.031046e-3,5.729233e-2,575.5119,155.4549)" + spreadMethod="reflect" + x1="-155.07108" + y1="506.64426" + x2="-113.2667" + y2="187.95932" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient8361" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(7.185971e-2,0,0,7.756052e-2,467.3834,129.9306)" + x1="1559.8579" + y1="714.60571" + x2="1559.8579" + y2="478.90579" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient8363" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(4.189262e-2,-5.874016e-3,5.495382e-3,4.477899e-2,581.8527,160.2329)" + spreadMethod="reflect" + x1="-154.4162" + y1="501.64474" + x2="-113.26765" + y2="187.95938" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient8365" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(5.616469e-2,0,0,6.062037e-2,497.3407,140.2834)" + x1="1559.8582" + y1="706.13074" + x2="1559.8582" + y2="478.90613" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient8367" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(3.120259e-2,-4.375102e-3,4.093087e-3,3.335243e-2,586.8604,164.2519)" + spreadMethod="reflect" + x1="-154.41675" + y1="501.64297" + x2="-113.2682" + y2="187.95769" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient8369" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(4.183275e-2,0,0,4.515145e-2,523.9139,149.393)" + x1="1559.8582" + y1="714.60608" + x2="1559.8582" + y2="478.90613" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient6969" + id="linearGradient8371" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.375,0,0,1.375,142.1978,57.82426)" + x1="318.5" + y1="98.323685" + x2="318.5" + y2="146.11829" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient6969" + id="linearGradient8373" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.375,0,0,1.375,142.1978,57.82426)" + x1="316.58795" + y1="117.51183" + x2="316.58795" + y2="53.17001" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient8375" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.375,0,0,1.375,109.8932,32.83006)" + x1="335.99429" + y1="131.28978" + x2="335.99429" + y2="119.28978" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient8377" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.654818,0,0,0.654818,360.1651,32.6063)" + x1="369.62366" + y1="171.32159" + x2="369.62366" + y2="161.05565" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient19089" + id="linearGradient8379" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.654818,0,0,0.654818,360.1651,32.6063)" + spreadMethod="reflect" + x1="380.70312" + y1="167.74232" + x2="380.59375" + y2="167.47937" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient6969" + id="linearGradient8381" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.375,0,0,1.375,104.8789,58.3546)" + x1="318.5" + y1="98.323685" + x2="318.5" + y2="146.11829" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient6969" + id="linearGradient8383" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.375,0,0,1.375,104.8789,58.3546)" + x1="316.58795" + y1="117.51183" + x2="316.58795" + y2="53.17001" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient8385" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.60947,0,0,0.570185,301.5629,110.2771)" + x1="390.20804" + y1="182.18143" + x2="390.20804" + y2="159.18143" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient8387" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.60947,0,0,0.570185,301.5629,110.2771)" + x1="409.20804" + y1="182.18143" + x2="409.20804" + y2="159.18143" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient8389" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.609622,0,0,0.598154,300.4526,85.9042)" + x1="392.93735" + y1="193.43317" + x2="392.93735" + y2="230.89081" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient8391" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.60947,0,0,0.570185,296.8442,106.3505)" + x1="390.20804" + y1="182.18143" + x2="390.20804" + y2="159.18143" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient8393" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.60947,0,0,0.570185,296.8442,106.3505)" + x1="409.20804" + y1="182.18143" + x2="409.20804" + y2="159.18143" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient8395" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.609622,0,0,0.598154,295.7339,81.9775)" + x1="392.93735" + y1="193.43317" + x2="392.93735" + y2="230.89081" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient8415" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.548015,0,0,0.624769,257.647,99.9072)" + x1="572" + y1="266.36218" + x2="572" + y2="237.36218" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient8417" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.548015,0,0,0.587063,257.647,109.7158)" + x1="570.48602" + y1="266.85327" + x2="570.48602" + y2="239.87381" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient14711" + id="linearGradient8419" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.548015,0,0,0.624769,256.6684,126.6101)" + x1="572.00092" + y1="237.3624" + x2="572.00092" + y2="266.36243" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient14711" + id="linearGradient8421" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.548015,0,0,0.587063,256.6684,136.4187)" + x1="569.97595" + y1="239.58128" + x2="569.97595" + y2="266.76248" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient6969" + id="linearGradient8423" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.375,0,0,1.375,98.06251,29.08293)" + x1="318.5" + y1="98.323685" + x2="318.5" + y2="146.11829" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient6969" + id="linearGradient8425" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.375,0,0,1.375,98.06251,29.08293)" + x1="316.58795" + y1="117.51183" + x2="316.58795" + y2="53.17001" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient8427" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.866811,0,0,0.7341,94.72612,48.10282)" + x1="512.5" + y1="167.86218" + x2="512.5" + y2="163.86218" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient8429" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.866811,0,0,0.7341,94.72612,48.10282)" + x1="519.5" + y1="167.86218" + x2="519.5" + y2="163.86218" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient8431" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.866811,0,0,0.7341,88.7247,53.10282)" + x1="512.5" + y1="167.86218" + x2="512.5" + y2="163.86218" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient8433" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.866811,0,0,0.7341,88.7247,53.10282)" + x1="519.5" + y1="167.86218" + x2="519.5" + y2="163.86218" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient8435" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.866811,0,0,0.7341,82.72612,58.1028)" + x1="512.5" + y1="167.86218" + x2="512.5" + y2="163.86218" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient8437" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.866811,0,0,0.7341,82.72612,58.1028)" + x1="519.5" + y1="167.86218" + x2="519.5" + y2="163.86218" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient8439" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.273749,6.911895e-2,-7.365621e-2,1.195286,-144.3188,-58.63368)" + x1="565.21875" + y1="46.09375" + x2="565.21875" + y2="30.625" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient8441" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.273749,6.911895e-2,-7.365621e-2,1.195286,-144.3188,-58.63368)" + x1="581.50031" + y1="46.875332" + x2="581.50031" + y2="29.843418" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient8443" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.878948,-0.240121,0.255883,0.824805,54.92043,125.5999)" + x1="565.21875" + y1="46.09375" + x2="565.21875" + y2="30.625" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient8445" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.878948,-0.240121,0.255883,0.824805,54.92043,125.5999)" + x1="581.50031" + y1="46.875332" + x2="581.50031" + y2="29.843418" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient8447" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.878948,-0.240121,0.255883,0.824805,56.30809,143.0305)" + x1="565.21875" + y1="46.09375" + x2="565.21875" + y2="30.625" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient8449" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.878948,-0.240121,0.255883,0.824805,56.30809,143.0305)" + x1="581.50031" + y1="46.875332" + x2="581.50031" + y2="29.843418" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient8459" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.5,0,0,0.538461,-553.0571,-245.2036)" + x1="50.28577" + y1="385.7197" + x2="50.28577" + y2="424.71976" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient8461" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(-958.8284,-48.01717)" + x1="409.91418" + y1="10.508587" + x2="409.91418" + y2="31.508587" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient8463" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-0.604969,0,0,-0.659967,553.1156,303.6917)" + x1="25.127533" + y1="417.70789" + x2="25.127533" + y2="389.9552" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4190" + id="linearGradient8465" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.82801,0,0,0.978453,-559.2408,-424.7224)" + x1="15.432588" + y1="413.62369" + x2="15.627617" + y2="404.25516" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient8467" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.940969,0,0,0.94346,-934.0313,-46.77246)" + x1="428.77371" + y1="30.368084" + x2="428.77371" + y2="18.649082" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient8477" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.5,0,0,0.538461,-553.0571,-245.2036)" + x1="50.28577" + y1="385.7197" + x2="50.28577" + y2="424.71976" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient8479" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(-958.8284,-48.01717)" + x1="409.91418" + y1="10.508587" + x2="409.91418" + y2="31.508587" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient8481" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-0.604969,0,0,-0.659967,553.1156,303.6917)" + x1="25.127533" + y1="417.70789" + x2="25.127533" + y2="389.9552" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4190" + id="linearGradient8483" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.82801,0,0,0.978453,-559.2408,-424.7224)" + x1="15.432588" + y1="413.62369" + x2="15.627617" + y2="404.25516" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient8485" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.940969,0,0,0.94346,-934.0313,-46.77246)" + x1="428.77371" + y1="30.368084" + x2="428.77371" + y2="18.649082" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient8513" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.5,0,0,0.538461,-553.0571,-245.2036)" + x1="50.28577" + y1="385.7197" + x2="50.28577" + y2="424.71976" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient8516" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(-958.8284,-48.01717)" + x1="409.91418" + y1="10.508587" + x2="409.91418" + y2="31.508587" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient8518" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-0.604969,0,0,-0.659967,553.1156,303.6917)" + x1="25.127533" + y1="417.70789" + x2="25.127533" + y2="389.9552" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4190" + id="linearGradient8520" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.82801,0,0,0.978453,-559.2408,-424.7224)" + x1="15.432588" + y1="413.62369" + x2="15.627617" + y2="404.25516" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient8522" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.940969,0,0,0.94346,-934.0313,-46.77246)" + x1="428.77371" + y1="30.368084" + x2="428.77371" + y2="18.649082" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient8558" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.024964,0,0,1.024964,393.9513,18.1768)" + x1="142.57724" + y1="86.521561" + x2="146.14665" + y2="83.54879" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient9558" + id="linearGradient8560" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.024964,0,0,1.024964,393.9513,18.1768)" + x1="139.162" + y1="79.300407" + x2="148.07057" + y2="89.934387" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7919" + id="linearGradient8562" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.024964,0,0,1.024964,393.9513,18.1768)" + x1="140.23837" + y1="78.309875" + x2="138.27634" + y2="79.971138" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient9906" + id="linearGradient8564" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.024964,0,0,1.024964,393.9513,18.1768)" + x1="136.05594" + y1="79.796364" + x2="143.14455" + y2="79.796364" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient9906" + id="linearGradient8566" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.024964,0,0,1.024964,393.9513,18.1768)" + x1="135.05788" + y1="77.974876" + x2="141.70506" + y2="77.974876" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient8579" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.024964,0,0,1.024964,393.9513,18.1768)" + x1="142.57724" + y1="86.521561" + x2="146.14665" + y2="83.54879" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient9558" + id="linearGradient8581" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.024964,0,0,1.024964,393.9513,18.1768)" + x1="139.162" + y1="79.300407" + x2="148.07057" + y2="89.934387" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7919" + id="linearGradient8583" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.024964,0,0,1.024964,393.9513,18.1768)" + x1="140.23837" + y1="78.309875" + x2="138.27634" + y2="79.971138" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient9906" + id="linearGradient8585" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.024964,0,0,1.024964,393.9513,18.1768)" + x1="136.05594" + y1="79.796364" + x2="143.14455" + y2="79.796364" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient9906" + id="linearGradient8589" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.024964,0,0,1.024964,393.9513,18.1768)" + x1="135.05788" + y1="77.974876" + x2="141.70506" + y2="77.974876" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient8604" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.024964,0,0,1.024964,393.9513,18.1768)" + x1="142.57724" + y1="86.521561" + x2="146.14665" + y2="83.54879" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient9558" + id="linearGradient8606" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.024964,0,0,1.024964,393.9513,18.1768)" + x1="139.162" + y1="79.300407" + x2="148.07057" + y2="89.934387" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7919" + id="linearGradient8608" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.024964,0,0,1.024964,393.9513,18.1768)" + x1="140.23837" + y1="78.309875" + x2="138.27634" + y2="79.971138" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient9906" + id="linearGradient8610" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.024964,0,0,1.024964,393.9513,18.1768)" + x1="136.05594" + y1="79.796364" + x2="143.14455" + y2="79.796364" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient9906" + id="linearGradient8612" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.024964,0,0,1.024964,393.9513,18.1768)" + x1="135.05788" + y1="77.974876" + x2="141.70506" + y2="77.974876" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient8626" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.024964,0,0,1.024964,393.9513,18.1768)" + x1="142.57724" + y1="86.521561" + x2="146.14665" + y2="83.54879" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient9558" + id="linearGradient8628" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.024964,0,0,1.024964,393.9513,18.1768)" + x1="139.162" + y1="79.300407" + x2="148.07057" + y2="89.934387" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7919" + id="linearGradient8630" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.024964,0,0,1.024964,393.9513,18.1768)" + x1="140.23837" + y1="78.309875" + x2="138.27634" + y2="79.971138" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient9906" + id="linearGradient8632" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.024964,0,0,1.024964,393.9513,18.1768)" + x1="136.05594" + y1="79.796364" + x2="143.14455" + y2="79.796364" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient9906" + id="linearGradient8634" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.024964,0,0,1.024964,393.9513,18.1768)" + x1="135.05788" + y1="77.974876" + x2="141.70506" + y2="77.974876" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient8644" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.273749,6.911895e-2,-7.365621e-2,1.195286,-144.3188,-58.63368)" + x1="565.21875" + y1="46.09375" + x2="565.21875" + y2="30.625" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient8646" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.273749,6.911895e-2,-7.365621e-2,1.195286,-144.3188,-58.63368)" + x1="581.50031" + y1="46.875332" + x2="581.50031" + y2="29.843418" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient8648" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.878948,-0.240121,0.255883,0.824805,54.92043,125.5999)" + x1="565.21875" + y1="46.09375" + x2="565.21875" + y2="30.625" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient8650" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.878948,-0.240121,0.255883,0.824805,54.92043,125.5999)" + x1="581.50031" + y1="46.875332" + x2="581.50031" + y2="29.843418" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient8652" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.878948,-0.240121,0.255883,0.824805,56.30809,143.0305)" + x1="565.21875" + y1="46.09375" + x2="565.21875" + y2="30.625" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient8654" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.878948,-0.240121,0.255883,0.824805,56.30809,143.0305)" + x1="581.50031" + y1="46.875332" + x2="581.50031" + y2="29.843418" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient8664" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.273749,6.911895e-2,-7.365621e-2,1.195286,-144.3188,-58.63368)" + x1="565.21875" + y1="46.09375" + x2="565.21875" + y2="30.625" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient8666" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.273749,6.911895e-2,-7.365621e-2,1.195286,-144.3188,-58.63368)" + x1="581.50031" + y1="46.875332" + x2="581.50031" + y2="29.843418" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient8668" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.878948,-0.240121,0.255883,0.824805,54.92043,125.5999)" + x1="565.21875" + y1="46.09375" + x2="565.21875" + y2="30.625" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient8670" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.878948,-0.240121,0.255883,0.824805,54.92043,125.5999)" + x1="581.50031" + y1="46.875332" + x2="581.50031" + y2="29.843418" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient8673" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.878948,-0.240121,0.255883,0.824805,56.30809,143.0305)" + x1="565.21875" + y1="46.09375" + x2="565.21875" + y2="30.625" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient8675" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.878948,-0.240121,0.255883,0.824805,56.30809,143.0305)" + x1="581.50031" + y1="46.875332" + x2="581.50031" + y2="29.843418" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient8685" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.273749,6.911895e-2,-7.365621e-2,1.195286,-144.3188,-58.63368)" + x1="565.21875" + y1="46.09375" + x2="565.21875" + y2="30.625" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient8687" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.273749,6.911895e-2,-7.365621e-2,1.195286,-144.3188,-58.63368)" + x1="581.50031" + y1="46.875332" + x2="581.50031" + y2="29.843418" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient8689" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.878948,-0.240121,0.255883,0.824805,54.92043,125.5999)" + x1="565.21875" + y1="46.09375" + x2="565.21875" + y2="30.625" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient8691" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.878948,-0.240121,0.255883,0.824805,54.92043,125.5999)" + x1="581.50031" + y1="46.875332" + x2="581.50031" + y2="29.843418" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient8693" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.878948,-0.240121,0.255883,0.824805,56.30809,143.0305)" + x1="565.21875" + y1="46.09375" + x2="565.21875" + y2="30.625" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient8695" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.878948,-0.240121,0.255883,0.824805,56.30809,143.0305)" + x1="581.50031" + y1="46.875332" + x2="581.50031" + y2="29.843418" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient8705" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.273749,6.911895e-2,-7.365621e-2,1.195286,-144.3188,-58.63368)" + x1="565.21875" + y1="46.09375" + x2="565.21875" + y2="30.625" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient8708" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.273749,6.911895e-2,-7.365621e-2,1.195286,-144.3188,-58.63368)" + x1="581.50031" + y1="46.875332" + x2="581.50031" + y2="29.843418" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient8710" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.878948,-0.240121,0.255883,0.824805,54.92043,125.5999)" + x1="565.21875" + y1="46.09375" + x2="565.21875" + y2="30.625" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient8712" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.878948,-0.240121,0.255883,0.824805,54.92043,125.5999)" + x1="581.50031" + y1="46.875332" + x2="581.50031" + y2="29.843418" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient8714" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.878948,-0.240121,0.255883,0.824805,56.30809,143.0305)" + x1="565.21875" + y1="46.09375" + x2="565.21875" + y2="30.625" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient8716" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.878948,-0.240121,0.255883,0.824805,56.30809,143.0305)" + x1="581.50031" + y1="46.875332" + x2="581.50031" + y2="29.843418" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient6969" + id="linearGradient8734" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.375,0,0,1.375,98.06251,29.08293)" + x1="318.5" + y1="98.323685" + x2="318.5" + y2="146.11829" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient6969" + id="linearGradient8736" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.375,0,0,1.375,98.06251,29.08293)" + x1="316.58795" + y1="117.51183" + x2="316.58795" + y2="53.17001" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient8738" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.866811,0,0,0.7341,94.72612,48.10282)" + x1="512.5" + y1="167.86218" + x2="512.5" + y2="163.86218" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient8740" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.866811,0,0,0.7341,94.72612,48.10282)" + x1="519.5" + y1="167.86218" + x2="519.5" + y2="163.86218" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient8742" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.866811,0,0,0.7341,88.7247,53.10282)" + x1="512.5" + y1="167.86218" + x2="512.5" + y2="163.86218" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient8745" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.866811,0,0,0.7341,88.7247,53.10282)" + x1="519.5" + y1="167.86218" + x2="519.5" + y2="163.86218" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient8749" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.866811,0,0,0.7341,82.72612,58.1028)" + x1="512.5" + y1="167.86218" + x2="512.5" + y2="163.86218" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient8753" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.866811,0,0,0.7341,82.72612,58.1028)" + x1="519.5" + y1="167.86218" + x2="519.5" + y2="163.86218" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient6969" + id="linearGradient8771" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.375,0,0,1.375,98.06251,29.08293)" + x1="318.5" + y1="98.323685" + x2="318.5" + y2="146.11829" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient6969" + id="linearGradient8773" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.375,0,0,1.375,98.06251,29.08293)" + x1="316.58795" + y1="117.51183" + x2="316.58795" + y2="53.17001" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient8776" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.866811,0,0,0.7341,94.72612,48.10282)" + x1="512.5" + y1="167.86218" + x2="512.5" + y2="163.86218" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient8778" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.866811,0,0,0.7341,94.72612,48.10282)" + x1="519.5" + y1="167.86218" + x2="519.5" + y2="163.86218" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient8780" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.866811,0,0,0.7341,88.7247,53.10282)" + x1="512.5" + y1="167.86218" + x2="512.5" + y2="163.86218" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient8782" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.866811,0,0,0.7341,88.7247,53.10282)" + x1="519.5" + y1="167.86218" + x2="519.5" + y2="163.86218" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient8784" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.866811,0,0,0.7341,82.72612,58.1028)" + x1="512.5" + y1="167.86218" + x2="512.5" + y2="163.86218" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient8786" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.866811,0,0,0.7341,82.72612,58.1028)" + x1="519.5" + y1="167.86218" + x2="519.5" + y2="163.86218" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient6969" + id="linearGradient8804" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.375,0,0,1.375,98.06251,29.08293)" + x1="318.5" + y1="98.323685" + x2="318.5" + y2="146.11829" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient6969" + id="linearGradient8806" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.375,0,0,1.375,98.06251,29.08293)" + x1="316.58795" + y1="117.51183" + x2="316.58795" + y2="53.17001" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient8808" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.866811,0,0,0.7341,94.72612,48.10282)" + x1="512.5" + y1="167.86218" + x2="512.5" + y2="163.86218" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient8810" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.866811,0,0,0.7341,94.72612,48.10282)" + x1="519.5" + y1="167.86218" + x2="519.5" + y2="163.86218" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient8812" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.866811,0,0,0.7341,88.7247,53.10282)" + x1="512.5" + y1="167.86218" + x2="512.5" + y2="163.86218" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient8814" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.866811,0,0,0.7341,88.7247,53.10282)" + x1="519.5" + y1="167.86218" + x2="519.5" + y2="163.86218" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient8816" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.866811,0,0,0.7341,82.72612,58.1028)" + x1="512.5" + y1="167.86218" + x2="512.5" + y2="163.86218" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient8818" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.866811,0,0,0.7341,82.72612,58.1028)" + x1="519.5" + y1="167.86218" + x2="519.5" + y2="163.86218" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient6969" + id="linearGradient8836" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.375,0,0,1.375,98.06251,29.08293)" + x1="318.5" + y1="98.323685" + x2="318.5" + y2="146.11829" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient6969" + id="linearGradient8838" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.375,0,0,1.375,98.06251,29.08293)" + x1="316.58795" + y1="117.51183" + x2="316.58795" + y2="53.17001" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient8840" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.866811,0,0,0.7341,94.72612,48.10282)" + x1="512.5" + y1="167.86218" + x2="512.5" + y2="163.86218" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient8842" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.866811,0,0,0.7341,94.72612,48.10282)" + x1="519.5" + y1="167.86218" + x2="519.5" + y2="163.86218" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient8844" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.866811,0,0,0.7341,88.7247,53.10282)" + x1="512.5" + y1="167.86218" + x2="512.5" + y2="163.86218" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient8846" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.866811,0,0,0.7341,88.7247,53.10282)" + x1="519.5" + y1="167.86218" + x2="519.5" + y2="163.86218" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient8848" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.866811,0,0,0.7341,82.72612,58.1028)" + x1="512.5" + y1="167.86218" + x2="512.5" + y2="163.86218" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient8851" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.866811,0,0,0.7341,82.72612,58.1028)" + x1="519.5" + y1="167.86218" + x2="519.5" + y2="163.86218" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient6969" + id="linearGradient8863" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.375,0,0,1.375,141.9955,29.16945)" + x1="318.5" + y1="98.323685" + x2="318.5" + y2="146.11829" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient6969" + id="linearGradient8865" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.375,0,0,1.375,141.9955,29.16945)" + x1="316.58795" + y1="117.51183" + x2="316.58795" + y2="53.17001" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient8867" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(5.359937e-2,-7.515489e-3,7.031046e-3,5.729233e-2,575.5119,155.4549)" + spreadMethod="reflect" + x1="-155.07108" + y1="506.64426" + x2="-113.2667" + y2="187.95932" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient8869" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(7.185971e-2,0,0,7.756052e-2,467.3834,129.9306)" + x1="1559.8579" + y1="714.60571" + x2="1559.8579" + y2="478.90579" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient8871" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(4.189262e-2,-5.874016e-3,5.495382e-3,4.477899e-2,581.8527,160.2329)" + spreadMethod="reflect" + x1="-154.4162" + y1="501.64474" + x2="-113.26765" + y2="187.95938" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient8873" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(5.616469e-2,0,0,6.062037e-2,497.3407,140.2834)" + x1="1559.8582" + y1="706.13074" + x2="1559.8582" + y2="478.90613" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient8875" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(3.120259e-2,-4.375102e-3,4.093087e-3,3.335243e-2,586.8604,164.2519)" + spreadMethod="reflect" + x1="-154.41675" + y1="501.64297" + x2="-113.2682" + y2="187.95769" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient8877" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(4.183275e-2,0,0,4.515145e-2,523.9139,149.393)" + x1="1559.8582" + y1="714.60608" + x2="1559.8582" + y2="478.90613" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient6969" + id="linearGradient8889" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.375,0,0,1.375,141.9955,29.16945)" + x1="318.5" + y1="98.323685" + x2="318.5" + y2="146.11829" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient6969" + id="linearGradient8891" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.375,0,0,1.375,141.9955,29.16945)" + x1="316.58795" + y1="117.51183" + x2="316.58795" + y2="53.17001" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient8893" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(5.359937e-2,-7.515489e-3,7.031046e-3,5.729233e-2,575.5119,155.4549)" + spreadMethod="reflect" + x1="-155.07108" + y1="506.64426" + x2="-113.2667" + y2="187.95932" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient8895" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(7.185971e-2,0,0,7.756052e-2,467.3834,129.9306)" + x1="1559.8579" + y1="714.60571" + x2="1559.8579" + y2="478.90579" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient8897" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(4.189262e-2,-5.874016e-3,5.495382e-3,4.477899e-2,581.8527,160.2329)" + spreadMethod="reflect" + x1="-154.4162" + y1="501.64474" + x2="-113.26765" + y2="187.95938" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient8899" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(5.616469e-2,0,0,6.062037e-2,497.3407,140.2834)" + x1="1559.8582" + y1="706.13074" + x2="1559.8582" + y2="478.90613" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient8901" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(3.120259e-2,-4.375102e-3,4.093087e-3,3.335243e-2,586.8604,164.2519)" + spreadMethod="reflect" + x1="-154.41675" + y1="501.64297" + x2="-113.2682" + y2="187.95769" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient8903" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(4.183275e-2,0,0,4.515145e-2,523.9139,149.393)" + x1="1559.8582" + y1="714.60608" + x2="1559.8582" + y2="478.90613" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient6969" + id="linearGradient8917" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.375,0,0,1.375,141.9955,29.16945)" + x1="318.5" + y1="98.323685" + x2="318.5" + y2="146.11829" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient6969" + id="linearGradient8920" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.375,0,0,1.375,141.9955,29.16945)" + x1="316.58795" + y1="117.51183" + x2="316.58795" + y2="53.17001" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient8922" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(5.359937e-2,-7.515489e-3,7.031046e-3,5.729233e-2,575.5119,155.4549)" + spreadMethod="reflect" + x1="-155.07108" + y1="506.64426" + x2="-113.2667" + y2="187.95932" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient8924" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(7.185971e-2,0,0,7.756052e-2,467.3834,129.9306)" + x1="1559.8579" + y1="714.60571" + x2="1559.8579" + y2="478.90579" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient8926" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(4.189262e-2,-5.874016e-3,5.495382e-3,4.477899e-2,581.8527,160.2329)" + spreadMethod="reflect" + x1="-154.4162" + y1="501.64474" + x2="-113.26765" + y2="187.95938" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient8928" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(5.616469e-2,0,0,6.062037e-2,497.3407,140.2834)" + x1="1559.8582" + y1="706.13074" + x2="1559.8582" + y2="478.90613" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient8930" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(3.120259e-2,-4.375102e-3,4.093087e-3,3.335243e-2,586.8604,164.2519)" + spreadMethod="reflect" + x1="-154.41675" + y1="501.64297" + x2="-113.2682" + y2="187.95769" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient8932" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(4.183275e-2,0,0,4.515145e-2,523.9139,149.393)" + x1="1559.8582" + y1="714.60608" + x2="1559.8582" + y2="478.90613" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient6969" + id="linearGradient8944" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.375,0,0,1.375,141.9955,29.16945)" + x1="318.5" + y1="98.323685" + x2="318.5" + y2="146.11829" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient6969" + id="linearGradient8946" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.375,0,0,1.375,141.9955,29.16945)" + x1="316.58795" + y1="117.51183" + x2="316.58795" + y2="53.17001" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient8948" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(5.359937e-2,-7.515489e-3,7.031046e-3,5.729233e-2,575.5119,155.4549)" + spreadMethod="reflect" + x1="-155.07108" + y1="506.64426" + x2="-113.2667" + y2="187.95932" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient8951" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(7.185971e-2,0,0,7.756052e-2,467.3834,129.9306)" + x1="1559.8579" + y1="714.60571" + x2="1559.8579" + y2="478.90579" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient8953" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(4.189262e-2,-5.874016e-3,5.495382e-3,4.477899e-2,581.8527,160.2329)" + spreadMethod="reflect" + x1="-154.4162" + y1="501.64474" + x2="-113.26765" + y2="187.95938" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient8955" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(5.616469e-2,0,0,6.062037e-2,497.3407,140.2834)" + x1="1559.8582" + y1="706.13074" + x2="1559.8582" + y2="478.90613" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient8957" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(3.120259e-2,-4.375102e-3,4.093087e-3,3.335243e-2,586.8604,164.2519)" + spreadMethod="reflect" + x1="-154.41675" + y1="501.64297" + x2="-113.2682" + y2="187.95769" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient8959" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(4.183275e-2,0,0,4.515145e-2,523.9139,149.393)" + x1="1559.8582" + y1="714.60608" + x2="1559.8582" + y2="478.90613" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient6969" + id="linearGradient8990" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.375,0,0,1.375,104.8789,58.3546)" + x1="318.5" + y1="98.323685" + x2="318.5" + y2="146.11829" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient6969" + id="linearGradient8992" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.375,0,0,1.375,104.8789,58.3546)" + x1="316.58795" + y1="117.51183" + x2="316.58795" + y2="53.17001" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient8994" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.60947,0,0,0.570185,301.5629,110.2771)" + x1="390.20804" + y1="182.18143" + x2="390.20804" + y2="159.18143" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient8998" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.60947,0,0,0.570185,301.5629,110.2771)" + x1="409.20804" + y1="182.18143" + x2="409.20804" + y2="159.18143" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient9000" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.609622,0,0,0.598154,300.4526,85.9042)" + x1="392.93735" + y1="193.43317" + x2="392.93735" + y2="230.89081" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient9002" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.60947,0,0,0.570185,296.8442,106.3505)" + x1="390.20804" + y1="182.18143" + x2="390.20804" + y2="159.18143" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient9005" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.60947,0,0,0.570185,296.8442,106.3505)" + x1="409.20804" + y1="182.18143" + x2="409.20804" + y2="159.18143" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient9008" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.609622,0,0,0.598154,295.7339,81.9775)" + x1="392.93735" + y1="193.43317" + x2="392.93735" + y2="230.89081" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient6969" + id="linearGradient9030" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.375,0,0,1.375,104.8789,58.3546)" + x1="318.5" + y1="98.323685" + x2="318.5" + y2="146.11829" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient6969" + id="linearGradient9032" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.375,0,0,1.375,104.8789,58.3546)" + x1="316.58795" + y1="117.51183" + x2="316.58795" + y2="53.17001" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient9034" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.60947,0,0,0.570185,301.5629,110.2771)" + x1="390.20804" + y1="182.18143" + x2="390.20804" + y2="159.18143" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient9038" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.60947,0,0,0.570185,301.5629,110.2771)" + x1="409.20804" + y1="182.18143" + x2="409.20804" + y2="159.18143" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient9040" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.609622,0,0,0.598154,300.4526,85.9042)" + x1="392.93735" + y1="193.43317" + x2="392.93735" + y2="230.89081" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient9043" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.60947,0,0,0.570185,296.8442,106.3505)" + x1="390.20804" + y1="182.18143" + x2="390.20804" + y2="159.18143" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient9045" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.60947,0,0,0.570185,296.8442,106.3505)" + x1="409.20804" + y1="182.18143" + x2="409.20804" + y2="159.18143" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient9047" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.609622,0,0,0.598154,295.7339,81.9775)" + x1="392.93735" + y1="193.43317" + x2="392.93735" + y2="230.89081" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient6969" + id="linearGradient9066" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.375,0,0,1.375,104.8789,58.3546)" + x1="318.5" + y1="98.323685" + x2="318.5" + y2="146.11829" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient6969" + id="linearGradient9068" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.375,0,0,1.375,104.8789,58.3546)" + x1="316.58795" + y1="117.51183" + x2="316.58795" + y2="53.17001" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient9070" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.60947,0,0,0.570185,301.5629,110.2771)" + x1="390.20804" + y1="182.18143" + x2="390.20804" + y2="159.18143" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient9072" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.60947,0,0,0.570185,301.5629,110.2771)" + x1="409.20804" + y1="182.18143" + x2="409.20804" + y2="159.18143" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient9074" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.609622,0,0,0.598154,300.4526,85.9042)" + x1="392.93735" + y1="193.43317" + x2="392.93735" + y2="230.89081" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient9076" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.60947,0,0,0.570185,296.8442,106.3505)" + x1="390.20804" + y1="182.18143" + x2="390.20804" + y2="159.18143" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient9078" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.60947,0,0,0.570185,296.8442,106.3505)" + x1="409.20804" + y1="182.18143" + x2="409.20804" + y2="159.18143" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient9080" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.609622,0,0,0.598154,295.7339,81.9775)" + x1="392.93735" + y1="193.43317" + x2="392.93735" + y2="230.89081" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient6969" + id="linearGradient9098" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.375,0,0,1.375,104.8789,58.3546)" + x1="318.5" + y1="98.323685" + x2="318.5" + y2="146.11829" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient6969" + id="linearGradient9100" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.375,0,0,1.375,104.8789,58.3546)" + x1="316.58795" + y1="117.51183" + x2="316.58795" + y2="53.17001" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient9102" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.60947,0,0,0.570185,301.5629,110.2771)" + x1="390.20804" + y1="182.18143" + x2="390.20804" + y2="159.18143" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient9104" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.60947,0,0,0.570185,301.5629,110.2771)" + x1="409.20804" + y1="182.18143" + x2="409.20804" + y2="159.18143" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient9106" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.609622,0,0,0.598154,300.4526,85.9042)" + x1="392.93735" + y1="193.43317" + x2="392.93735" + y2="230.89081" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient9108" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.60947,0,0,0.570185,296.8442,106.3505)" + x1="390.20804" + y1="182.18143" + x2="390.20804" + y2="159.18143" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient9110" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.60947,0,0,0.570185,296.8442,106.3505)" + x1="409.20804" + y1="182.18143" + x2="409.20804" + y2="159.18143" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient9112" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.609622,0,0,0.598154,295.7339,81.9775)" + x1="392.93735" + y1="193.43317" + x2="392.93735" + y2="230.89081" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient6969" + id="linearGradient9120" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.375,0,0,1.375,142.1978,57.82426)" + x1="318.5" + y1="98.323685" + x2="318.5" + y2="146.11829" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient6969" + id="linearGradient9122" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.375,0,0,1.375,142.1978,57.82426)" + x1="316.58795" + y1="117.51183" + x2="316.58795" + y2="53.17001" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient9124" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.375,0,0,1.375,109.8932,32.83006)" + x1="335.99429" + y1="131.28978" + x2="335.99429" + y2="119.28978" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient6969" + id="linearGradient9132" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.375,0,0,1.375,142.1978,57.82426)" + x1="318.5" + y1="98.323685" + x2="318.5" + y2="146.11829" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient6969" + id="linearGradient9134" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.375,0,0,1.375,142.1978,57.82426)" + x1="316.58795" + y1="117.51183" + x2="316.58795" + y2="53.17001" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient9136" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.375,0,0,1.375,109.8932,32.83006)" + x1="335.99429" + y1="131.28978" + x2="335.99429" + y2="119.28978" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient6969" + id="linearGradient9144" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.375,0,0,1.375,142.1978,57.82426)" + x1="318.5" + y1="98.323685" + x2="318.5" + y2="146.11829" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient6969" + id="linearGradient9146" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.375,0,0,1.375,142.1978,57.82426)" + x1="316.58795" + y1="117.51183" + x2="316.58795" + y2="53.17001" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient9148" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.375,0,0,1.375,109.8932,32.83006)" + x1="335.99429" + y1="131.28978" + x2="335.99429" + y2="119.28978" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient6969" + id="linearGradient9156" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.375,0,0,1.375,142.1978,57.82426)" + x1="318.5" + y1="98.323685" + x2="318.5" + y2="146.11829" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient6969" + id="linearGradient9158" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.375,0,0,1.375,142.1978,57.82426)" + x1="316.58795" + y1="117.51183" + x2="316.58795" + y2="53.17001" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient9160" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.375,0,0,1.375,109.8932,32.83006)" + x1="335.99429" + y1="131.28978" + x2="335.99429" + y2="119.28978" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient9222" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.548015,0,0,0.624769,257.647,99.9072)" + x1="572" + y1="266.36218" + x2="572" + y2="237.36218" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient9224" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.548015,0,0,0.587063,257.647,109.7158)" + x1="570.48602" + y1="266.85327" + x2="570.48602" + y2="239.87381" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient9286" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.548015,0,0,0.624769,257.647,99.9072)" + x1="572" + y1="266.36218" + x2="572" + y2="237.36218" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient9288" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.548015,0,0,0.587063,257.647,109.7158)" + x1="570.48602" + y1="266.85327" + x2="570.48602" + y2="239.87381" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient9350" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.548015,0,0,0.624769,257.647,99.9072)" + x1="572" + y1="266.36218" + x2="572" + y2="237.36218" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient9352" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.548015,0,0,0.587063,257.647,109.7158)" + x1="570.48602" + y1="266.85327" + x2="570.48602" + y2="239.87381" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient9414" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.548015,0,0,0.624769,257.647,99.9072)" + x1="572" + y1="266.36218" + x2="572" + y2="237.36218" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient9416" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.548015,0,0,0.587063,257.647,109.7158)" + x1="570.48602" + y1="266.85327" + x2="570.48602" + y2="239.87381" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient14711" + id="linearGradient9478" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.548015,0,0,0.624769,256.6684,126.6101)" + x1="572.00092" + y1="237.3624" + x2="572.00092" + y2="266.36243" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient14711" + id="linearGradient9480" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.548015,0,0,0.587063,256.6684,136.4187)" + x1="569.97595" + y1="239.58128" + x2="569.97595" + y2="266.76248" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient14711" + id="linearGradient9542" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.548015,0,0,0.624769,256.6684,126.6101)" + x1="572.00092" + y1="237.3624" + x2="572.00092" + y2="266.36243" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient14711" + id="linearGradient9544" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.548015,0,0,0.587063,256.6684,136.4187)" + x1="569.97595" + y1="239.58128" + x2="569.97595" + y2="266.76248" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient14711" + id="linearGradient9606" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.548015,0,0,0.624769,256.6684,126.6101)" + x1="572.00092" + y1="237.3624" + x2="572.00092" + y2="266.36243" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient14711" + id="linearGradient9608" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.548015,0,0,0.587063,256.6684,136.4187)" + x1="569.97595" + y1="239.58128" + x2="569.97595" + y2="266.76248" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient14711" + id="linearGradient9670" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.548015,0,0,0.624769,256.6684,126.6101)" + x1="572.00092" + y1="237.3624" + x2="572.00092" + y2="266.36243" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient14711" + id="linearGradient9672" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.548015,0,0,0.587063,256.6684,136.4187)" + x1="569.97595" + y1="239.58128" + x2="569.97595" + y2="266.76248" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient10588" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.375,0,0,1.469276,179.5471,-10.85991)" + x1="282.96594" + y1="113.52765" + x2="282.96594" + y2="98.532501" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7919" + id="linearGradient10590" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-7.977528e-3,-9.016333e-2,8.790531e-2,-7.536272e-3,555.7662,198.1625)" + x1="507.37094" + y1="290.88303" + x2="608.41498" + y2="398.94208" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient10592" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.216699,0,0,1.197663,-126.7483,-28.54217)" + x1="589.09595" + y1="153.23845" + x2="589.09595" + y2="136.54427" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient21450" + id="linearGradient10594" + gradientUnits="userSpaceOnUse" + x1="1308.2411" + y1="1395.8469" + x2="1308.2411" + y2="1473.3281" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient20611" + id="linearGradient10596" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(5.767204e-3,6.518187e-2,-6.407273e-2,5.493064e-3,667.9806,50.83129)" + x1="1394.5476" + y1="1587.7018" + x2="1412.9457" + y2="1547.1619" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient13829" + id="linearGradient10598" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(3.288458e-2,5.641064e-2,-5.529172e-2,3.311662e-2,618.1039,20.62608)" + x1="1277.4498" + y1="1612.0433" + x2="1406.9330" + y2="1424.1870" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient20611" + id="linearGradient10600" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-5.80699e-3,-6.563155e-2,6.499001e-2,-5.571703e-3,490.7537,240.0609)" + x1="1394.5476" + y1="1587.7018" + x2="1412.9457" + y2="1547.1619" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient13829" + id="linearGradient10602" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-3.331678e-2,-5.678175e-2,5.610206e-2,-3.338171e-2,541.322,270.4442)" + x1="1277.4498" + y1="1612.0433" + x2="1406.9330" + y2="1424.1870" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient10604" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.130153,0,0,1.068982,108.8626,-330.5337)" + x1="415.12271" + y1="435.43039" + x2="415.12271" + y2="453.03961" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient13837" + id="linearGradient17012" + gradientUnits="userSpaceOnUse" + x1="417.36929" + y1="435.0079" + x2="417.36929" + y2="454.65622" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4190" + id="radialGradient17014" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.726147,-8.054512e-3,1.109145e-2,0.999938,109.5649,3.398898)" + cx="418.51233" + cy="454.95853" + fx="418.51233" + fy="454.95853" + r="11.005301" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient17016" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.018402,0,0,1.177105,-403.8095,-502.6985)" + x1="415.12271" + y1="435.43039" + x2="415.12271" + y2="453.03961" /> + <radialGradient + inkscape:collect="always" + xlink:href="#XMLID_25_" + id="radialGradient17018" + gradientUnits="userSpaceOnUse" + cx="375.2144" + cy="783.1636" + fx="375.2144" + fy="783.1636" + r="300.5752" /> + <linearGradient + inkscape:collect="always" + xlink:href="#XMLID_4_" + id="linearGradient17020" + gradientUnits="userSpaceOnUse" + x1="32.1113" + y1="59.9604" + x2="85.1559" + y2="67.4256" /> + <linearGradient + inkscape:collect="always" + xlink:href="#XMLID_5_" + id="linearGradient17022" + gradientUnits="userSpaceOnUse" + x1="220.9512" + y1="634.6323" + x2="278.1447" + y2="560.355" /> + <linearGradient + inkscape:collect="always" + xlink:href="#XMLID_6_" + id="linearGradient17024" + gradientUnits="userSpaceOnUse" + x1="237.208" + y1="684.019" + x2="328.1847" + y2="565.8676" /> + <linearGradient + inkscape:collect="always" + xlink:href="#XMLID_7_" + id="linearGradient17026" + gradientUnits="userSpaceOnUse" + x1="46.1694" + y1="55.1499" + x2="97.3291" + y2="76.854" /> + <linearGradient + inkscape:collect="always" + xlink:href="#XMLID_8_" + id="linearGradient17028" + gradientUnits="userSpaceOnUse" + x1="57.8667" + y1="100.9258" + x2="120.5585" + y2="70.7129" /> + <linearGradient + inkscape:collect="always" + xlink:href="#XMLID_9_" + id="linearGradient17030" + gradientUnits="userSpaceOnUse" + x1="18.0327" + y1="124.1084" + x2="116.4508" + y2="70.4536" /> + <linearGradient + inkscape:collect="always" + xlink:href="#XMLID_12_" + id="linearGradient17032" + gradientUnits="userSpaceOnUse" + x1="517.1899" + y1="239.939" + x2="410.2776" + y2="522.0239" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient13837" + id="linearGradient17034" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.899661,0,0,0.899661,35.25337,40.52805)" + x1="351.08401" + y1="225.61018" + x2="375.28113" + y2="814.00208" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient13837" + id="linearGradient17036" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.139011,0,0,1.139011,-47.50797,-52.7266)" + x1="319.9101" + y1="422.99152" + x2="404.22177" + y2="422.99152" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient13837" + id="linearGradient17038" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(20.1,3.396837)" + x1="219.38672" + y1="473.04828" + x2="230.90848" + y2="522.05719" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient13829" + id="linearGradient17040" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.896397,0,0,0.896397,37.37992,55.79666)" + x1="128.79208" + y1="497.57861" + x2="269.95502" + y2="497.57861" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient17075" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.208342,0,0,1.20833,486.8724,591.0532)" + x1="663.21844" + y1="225.97932" + x2="663.21844" + y2="199.3622" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient17077" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.208342,0,0,1.20833,486.8724,591.0532)" + x1="689.7746" + y1="225.97932" + x2="689.7746" + y2="199.3622" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient12907" + id="linearGradient17079" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(2.297735,0,0,2.297712,874.4659,461.2189)" + x1="187.07394" + y1="162.15895" + x2="187.07394" + y2="178.28224" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient17081" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.208342,0,0,1.20833,486.8724,591.0532)" + x1="687.57507" + y1="223.41319" + x2="687.57507" + y2="201.92833" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient17083" + gradientUnits="userSpaceOnUse" + x1="662.99841" + y1="70.997231" + x2="662.99841" + y2="62.014992" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient17085" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(7.603151e-2,-3.5534282e-6)" + x1="687.92395" + y1="82.974518" + x2="687.92395" + y2="61.362186" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient13837" + id="linearGradient17467" + gradientUnits="userSpaceOnUse" + x1="417.36929" + y1="435.0079" + x2="417.36929" + y2="454.65622" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4190" + id="radialGradient17469" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.726147,-8.054512e-3,1.109145e-2,0.999938,109.5649,3.398898)" + cx="418.51233" + cy="454.95853" + fx="418.51233" + fy="454.95853" + r="11.005301" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient17471" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.018402,0,0,1.177105,-403.8095,-502.6985)" + x1="415.12271" + y1="435.43039" + x2="415.12271" + y2="453.03961" /> + <radialGradient + inkscape:collect="always" + xlink:href="#XMLID_25_" + id="radialGradient17473" + gradientUnits="userSpaceOnUse" + cx="375.2144" + cy="783.1636" + fx="375.2144" + fy="783.1636" + r="300.5752" /> + <linearGradient + inkscape:collect="always" + xlink:href="#XMLID_4_" + id="linearGradient17475" + gradientUnits="userSpaceOnUse" + x1="32.1113" + y1="59.9604" + x2="85.1559" + y2="67.4256" /> + <linearGradient + inkscape:collect="always" + xlink:href="#XMLID_5_" + id="linearGradient17477" + gradientUnits="userSpaceOnUse" + x1="220.9512" + y1="634.6323" + x2="278.1447" + y2="560.355" /> + <linearGradient + inkscape:collect="always" + xlink:href="#XMLID_6_" + id="linearGradient17479" + gradientUnits="userSpaceOnUse" + x1="237.208" + y1="684.019" + x2="328.1847" + y2="565.8676" /> + <linearGradient + inkscape:collect="always" + xlink:href="#XMLID_7_" + id="linearGradient17482" + gradientUnits="userSpaceOnUse" + x1="46.1694" + y1="55.1499" + x2="97.3291" + y2="76.854" /> + <linearGradient + inkscape:collect="always" + xlink:href="#XMLID_8_" + id="linearGradient17484" + gradientUnits="userSpaceOnUse" + x1="57.8667" + y1="100.9258" + x2="120.5585" + y2="70.7129" /> + <linearGradient + inkscape:collect="always" + xlink:href="#XMLID_9_" + id="linearGradient17486" + gradientUnits="userSpaceOnUse" + x1="18.0327" + y1="124.1084" + x2="116.4508" + y2="70.4536" /> + <linearGradient + inkscape:collect="always" + xlink:href="#XMLID_12_" + id="linearGradient17488" + gradientUnits="userSpaceOnUse" + x1="517.1899" + y1="239.939" + x2="410.2776" + y2="522.0239" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient13837" + id="linearGradient17490" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.899661,0,0,0.899661,35.25337,40.52805)" + x1="351.08401" + y1="225.61018" + x2="375.28113" + y2="814.00208" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient13837" + id="linearGradient17492" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.139011,0,0,1.139011,-47.50797,-52.7266)" + x1="319.9101" + y1="422.99152" + x2="404.22177" + y2="422.99152" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient13837" + id="linearGradient17494" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(20.1,3.396837)" + x1="219.38672" + y1="473.04828" + x2="230.90848" + y2="522.05719" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient13829" + id="linearGradient17497" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.896397,0,0,0.896397,37.37992,55.79666)" + x1="128.79208" + y1="497.57861" + x2="269.95502" + y2="497.57861" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient13837" + id="linearGradient17847" + gradientUnits="userSpaceOnUse" + x1="417.36929" + y1="435.0079" + x2="417.36929" + y2="454.65622" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4190" + id="radialGradient17849" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.726147,-8.054512e-3,1.109145e-2,0.999938,109.5649,3.398898)" + cx="418.51233" + cy="454.95853" + fx="418.51233" + fy="454.95853" + r="11.005301" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient17851" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.018402,0,0,1.177105,-403.8095,-502.6985)" + x1="415.12271" + y1="435.43039" + x2="415.12271" + y2="453.03961" /> + <radialGradient + inkscape:collect="always" + xlink:href="#XMLID_25_" + id="radialGradient17853" + gradientUnits="userSpaceOnUse" + cx="375.2144" + cy="783.1636" + fx="375.2144" + fy="783.1636" + r="300.5752" /> + <linearGradient + inkscape:collect="always" + xlink:href="#XMLID_4_" + id="linearGradient17855" + gradientUnits="userSpaceOnUse" + x1="32.1113" + y1="59.9604" + x2="85.1559" + y2="67.4256" /> + <linearGradient + inkscape:collect="always" + xlink:href="#XMLID_5_" + id="linearGradient17857" + gradientUnits="userSpaceOnUse" + x1="220.9512" + y1="634.6323" + x2="278.1447" + y2="560.355" /> + <linearGradient + inkscape:collect="always" + xlink:href="#XMLID_6_" + id="linearGradient17859" + gradientUnits="userSpaceOnUse" + x1="237.208" + y1="684.019" + x2="328.1847" + y2="565.8676" /> + <linearGradient + inkscape:collect="always" + xlink:href="#XMLID_7_" + id="linearGradient17861" + gradientUnits="userSpaceOnUse" + x1="46.1694" + y1="55.1499" + x2="97.3291" + y2="76.854" /> + <linearGradient + inkscape:collect="always" + xlink:href="#XMLID_8_" + id="linearGradient17863" + gradientUnits="userSpaceOnUse" + x1="57.8667" + y1="100.9258" + x2="120.5585" + y2="70.7129" /> + <linearGradient + inkscape:collect="always" + xlink:href="#XMLID_9_" + id="linearGradient17865" + gradientUnits="userSpaceOnUse" + x1="18.0327" + y1="124.1084" + x2="116.4508" + y2="70.4536" /> + <linearGradient + inkscape:collect="always" + xlink:href="#XMLID_12_" + id="linearGradient17867" + gradientUnits="userSpaceOnUse" + x1="517.1899" + y1="239.939" + x2="410.2776" + y2="522.0239" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient13837" + id="linearGradient17869" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.899661,0,0,0.899661,35.25337,40.52805)" + x1="351.08401" + y1="225.61018" + x2="375.28113" + y2="814.00208" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient13837" + id="linearGradient17871" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.139011,0,0,1.139011,-47.50797,-52.7266)" + x1="319.9101" + y1="422.99152" + x2="404.22177" + y2="422.99152" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient13837" + id="linearGradient17873" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(20.1,3.396837)" + x1="219.38672" + y1="473.04828" + x2="230.90848" + y2="522.05719" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient13829" + id="linearGradient17875" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.896397,0,0,0.896397,37.37992,55.79666)" + x1="128.79208" + y1="497.57861" + x2="269.95502" + y2="497.57861" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient13837" + id="linearGradient18223" + gradientUnits="userSpaceOnUse" + x1="417.36929" + y1="435.0079" + x2="417.36929" + y2="454.65622" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4190" + id="radialGradient18225" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.726147,-8.054512e-3,1.109145e-2,0.999938,109.5649,3.398898)" + cx="418.51233" + cy="454.95853" + fx="418.51233" + fy="454.95853" + r="11.005301" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient18227" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.018402,0,0,1.177105,-403.8095,-502.6985)" + x1="415.12271" + y1="435.43039" + x2="415.12271" + y2="453.03961" /> + <radialGradient + inkscape:collect="always" + xlink:href="#XMLID_25_" + id="radialGradient18229" + gradientUnits="userSpaceOnUse" + cx="375.2144" + cy="783.1636" + fx="375.2144" + fy="783.1636" + r="300.5752" /> + <linearGradient + inkscape:collect="always" + xlink:href="#XMLID_4_" + id="linearGradient18231" + gradientUnits="userSpaceOnUse" + x1="32.1113" + y1="59.9604" + x2="85.1559" + y2="67.4256" /> + <linearGradient + inkscape:collect="always" + xlink:href="#XMLID_5_" + id="linearGradient18233" + gradientUnits="userSpaceOnUse" + x1="220.9512" + y1="634.6323" + x2="278.1447" + y2="560.355" /> + <linearGradient + inkscape:collect="always" + xlink:href="#XMLID_6_" + id="linearGradient18235" + gradientUnits="userSpaceOnUse" + x1="237.208" + y1="684.019" + x2="328.1847" + y2="565.8676" /> + <linearGradient + inkscape:collect="always" + xlink:href="#XMLID_7_" + id="linearGradient18237" + gradientUnits="userSpaceOnUse" + x1="46.1694" + y1="55.1499" + x2="97.3291" + y2="76.854" /> + <linearGradient + inkscape:collect="always" + xlink:href="#XMLID_8_" + id="linearGradient18239" + gradientUnits="userSpaceOnUse" + x1="57.8667" + y1="100.9258" + x2="120.5585" + y2="70.7129" /> + <linearGradient + inkscape:collect="always" + xlink:href="#XMLID_9_" + id="linearGradient18241" + gradientUnits="userSpaceOnUse" + x1="18.0327" + y1="124.1084" + x2="116.4508" + y2="70.4536" /> + <linearGradient + inkscape:collect="always" + xlink:href="#XMLID_12_" + id="linearGradient18243" + gradientUnits="userSpaceOnUse" + x1="517.1899" + y1="239.939" + x2="410.2776" + y2="522.0239" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient13837" + id="linearGradient18245" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.899661,0,0,0.899661,35.25337,40.52805)" + x1="351.08401" + y1="225.61018" + x2="375.28113" + y2="814.00208" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient13837" + id="linearGradient18247" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.139011,0,0,1.139011,-47.50797,-52.7266)" + x1="319.9101" + y1="422.99152" + x2="404.22177" + y2="422.99152" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient13837" + id="linearGradient18249" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(20.1,3.396837)" + x1="219.38672" + y1="473.04828" + x2="230.90848" + y2="522.05719" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient13829" + id="linearGradient18251" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.896397,0,0,0.896397,37.37992,55.79666)" + x1="128.79208" + y1="497.57861" + x2="269.95502" + y2="497.57861" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient13837" + id="linearGradient18601" + gradientUnits="userSpaceOnUse" + x1="417.36929" + y1="435.0079" + x2="417.36929" + y2="454.65622" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4190" + id="radialGradient18603" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.726147,-8.054512e-3,1.109145e-2,0.999938,109.5649,3.398898)" + cx="418.51233" + cy="454.95853" + fx="418.51233" + fy="454.95853" + r="11.005301" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient18605" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.018402,0,0,1.177105,-403.8095,-502.6985)" + x1="415.12271" + y1="435.43039" + x2="415.12271" + y2="453.03961" /> + <radialGradient + inkscape:collect="always" + xlink:href="#XMLID_25_" + id="radialGradient18607" + gradientUnits="userSpaceOnUse" + cx="375.2144" + cy="783.1636" + fx="375.2144" + fy="783.1636" + r="300.5752" /> + <linearGradient + inkscape:collect="always" + xlink:href="#XMLID_4_" + id="linearGradient18609" + gradientUnits="userSpaceOnUse" + x1="32.1113" + y1="59.9604" + x2="85.1559" + y2="67.4256" /> + <linearGradient + inkscape:collect="always" + xlink:href="#XMLID_5_" + id="linearGradient18611" + gradientUnits="userSpaceOnUse" + x1="220.9512" + y1="634.6323" + x2="278.1447" + y2="560.355" /> + <linearGradient + inkscape:collect="always" + xlink:href="#XMLID_6_" + id="linearGradient18613" + gradientUnits="userSpaceOnUse" + x1="237.208" + y1="684.019" + x2="328.1847" + y2="565.8676" /> + <linearGradient + inkscape:collect="always" + xlink:href="#XMLID_7_" + id="linearGradient18615" + gradientUnits="userSpaceOnUse" + x1="46.1694" + y1="55.1499" + x2="97.3291" + y2="76.854" /> + <linearGradient + inkscape:collect="always" + xlink:href="#XMLID_8_" + id="linearGradient18617" + gradientUnits="userSpaceOnUse" + x1="57.8667" + y1="100.9258" + x2="120.5585" + y2="70.7129" /> + <linearGradient + inkscape:collect="always" + xlink:href="#XMLID_9_" + id="linearGradient18619" + gradientUnits="userSpaceOnUse" + x1="18.0327" + y1="124.1084" + x2="116.4508" + y2="70.4536" /> + <linearGradient + inkscape:collect="always" + xlink:href="#XMLID_12_" + id="linearGradient18621" + gradientUnits="userSpaceOnUse" + x1="517.1899" + y1="239.939" + x2="410.2776" + y2="522.0239" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient13837" + id="linearGradient18623" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.899661,0,0,0.899661,35.25337,40.52805)" + x1="351.08401" + y1="225.61018" + x2="375.28113" + y2="814.00208" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient13837" + id="linearGradient18626" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.139011,0,0,1.139011,-47.50797,-52.7266)" + x1="319.9101" + y1="422.99152" + x2="404.22177" + y2="422.99152" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient13837" + id="linearGradient18629" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(20.1,3.396837)" + x1="219.38672" + y1="473.04828" + x2="230.90848" + y2="522.05719" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient13829" + id="linearGradient18631" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.896397,0,0,0.896397,37.37992,55.79666)" + x1="128.79208" + y1="497.57861" + x2="269.95502" + y2="497.57861" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient18709" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.710065,0,0,0.63994,617.4801,45.62798)" + x1="46.169399" + y1="55.149899" + x2="97.329102" + y2="76.853996" /> + <linearGradient + inkscape:collect="always" + xlink:href="#XMLID_8_" + id="linearGradient18711" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.710065,0,0,0.63994,617.4801,45.62798)" + x1="57.8667" + y1="100.9258" + x2="120.5585" + y2="70.7129" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient18713" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.710065,0,0,0.63994,617.4801,45.62798)" + x1="32.630985" + y1="75.947983" + x2="32.630985" + y2="26.44599" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient18715" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.710065,0,0,0.63994,617.4801,45.62798)" + x1="44.604591" + y1="62.292385" + x2="44.604591" + y2="33.60413" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient18717" + gradientUnits="userSpaceOnUse" + x1="679.37292" + y1="98.170349" + x2="679.37292" + y2="61.871277" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient18719" + gradientUnits="userSpaceOnUse" + x1="662.99841" + y1="70.997231" + x2="662.99841" + y2="62.014992" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient18721" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(7.603151e-2,-3.5534277e-6)" + x1="687.92395" + y1="82.974518" + x2="687.92395" + y2="61.362186" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient18723" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.710065,0,0,0.63994,617.4801,45.62798)" + x1="46.169399" + y1="55.149899" + x2="97.329102" + y2="76.853996" /> + <linearGradient + inkscape:collect="always" + xlink:href="#XMLID_8_" + id="linearGradient18725" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.710065,0,0,0.63994,617.4801,45.62798)" + x1="57.8667" + y1="100.9258" + x2="120.5585" + y2="70.7129" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient18727" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.710065,0,0,0.63994,617.4801,45.62798)" + x1="32.630985" + y1="75.947983" + x2="32.630985" + y2="26.44599" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient18729" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.710065,0,0,0.63994,617.4801,45.62798)" + x1="44.604591" + y1="62.292385" + x2="44.604591" + y2="33.60413" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient18731" + gradientUnits="userSpaceOnUse" + x1="679.37292" + y1="98.170349" + x2="679.37292" + y2="61.871277" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient18733" + gradientUnits="userSpaceOnUse" + x1="662.99841" + y1="70.997231" + x2="662.99841" + y2="62.014992" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient18735" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(7.603151e-2,-3.5534282e-6)" + x1="687.92395" + y1="82.974518" + x2="687.92395" + y2="61.362186" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient18787" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.710065,0,0,0.63994,617.4801,45.62798)" + x1="46.169399" + y1="55.149899" + x2="97.329102" + y2="76.853996" /> + <linearGradient + inkscape:collect="always" + xlink:href="#XMLID_8_" + id="linearGradient18789" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.710065,0,0,0.63994,617.4801,45.62798)" + x1="57.8667" + y1="100.9258" + x2="120.5585" + y2="70.7129" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient18791" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.710065,0,0,0.63994,617.4801,45.62798)" + x1="32.630985" + y1="75.947983" + x2="32.630985" + y2="26.44599" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient18794" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.710065,0,0,0.63994,617.4801,45.62798)" + x1="44.604591" + y1="62.292385" + x2="44.604591" + y2="33.60413" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient18796" + gradientUnits="userSpaceOnUse" + x1="679.37292" + y1="98.170349" + x2="679.37292" + y2="61.871277" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient18798" + gradientUnits="userSpaceOnUse" + x1="662.99841" + y1="70.997231" + x2="662.99841" + y2="62.014992" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient18800" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(7.603151e-2,-3.553428e-6)" + x1="687.92395" + y1="82.974518" + x2="687.92395" + y2="61.362186" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient18852" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.710065,0,0,0.63994,617.4801,45.62798)" + x1="46.169399" + y1="55.149899" + x2="97.329102" + y2="76.853996" /> + <linearGradient + inkscape:collect="always" + xlink:href="#XMLID_8_" + id="linearGradient18854" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.710065,0,0,0.63994,617.4801,45.62798)" + x1="57.8667" + y1="100.9258" + x2="120.5585" + y2="70.7129" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient18856" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.710065,0,0,0.63994,617.4801,45.62798)" + x1="32.630985" + y1="75.947983" + x2="32.630985" + y2="26.44599" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient18858" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.710065,0,0,0.63994,617.4801,45.62798)" + x1="44.604591" + y1="62.292385" + x2="44.604591" + y2="33.60413" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient18860" + gradientUnits="userSpaceOnUse" + x1="679.37292" + y1="98.170349" + x2="679.37292" + y2="61.871277" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient18862" + gradientUnits="userSpaceOnUse" + x1="662.99841" + y1="70.997231" + x2="662.99841" + y2="62.014992" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient18864" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(7.603151e-2,-3.553428e-6)" + x1="687.92395" + y1="82.974518" + x2="687.92395" + y2="61.362186" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient18916" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.710065,0,0,0.63994,617.4801,45.62798)" + x1="46.169399" + y1="55.149899" + x2="97.329102" + y2="76.853996" /> + <linearGradient + inkscape:collect="always" + xlink:href="#XMLID_8_" + id="linearGradient18918" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.710065,0,0,0.63994,617.4801,45.62798)" + x1="57.8667" + y1="100.9258" + x2="120.5585" + y2="70.7129" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient18920" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.710065,0,0,0.63994,617.4801,45.62798)" + x1="32.630985" + y1="75.947983" + x2="32.630985" + y2="26.44599" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient18922" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.710065,0,0,0.63994,617.4801,45.62798)" + x1="44.604591" + y1="62.292385" + x2="44.604591" + y2="33.60413" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient18924" + gradientUnits="userSpaceOnUse" + x1="679.37292" + y1="98.170349" + x2="679.37292" + y2="61.871277" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient18926" + gradientUnits="userSpaceOnUse" + x1="662.99841" + y1="70.997231" + x2="662.99841" + y2="62.014992" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient18928" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(7.603151e-2,-3.553428e-6)" + x1="687.92395" + y1="82.974518" + x2="687.92395" + y2="61.362186" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient18930" + gradientUnits="userSpaceOnUse" + x1="409.12363" + y1="454.65622" + x2="409.12363" + y2="435.00833" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient9791" + id="radialGradient18932" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.023094,-1.794164e-2,1.365068e-2,0.769099,-16.10154,112.152)" + cx="419.28891" + cy="453.16306" + fx="419.28891" + fy="453.16306" + r="11.005301" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient18934" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.596336,0,0,1.737952,606.0058,-21.55215)" + x1="415.12271" + y1="435.43039" + x2="415.17993" + y2="447.62582" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient18936" + gradientUnits="userSpaceOnUse" + x1="662.99841" + y1="70.997231" + x2="662.99841" + y2="62.014992" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient18938" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(7.603151e-2,-3.5534281e-6)" + x1="687.92395" + y1="82.974518" + x2="687.92395" + y2="61.362186" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient18972" + gradientUnits="userSpaceOnUse" + x1="409.12363" + y1="454.65622" + x2="409.12363" + y2="435.00833" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient9791" + id="radialGradient18974" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.023094,-1.794164e-2,1.365068e-2,0.769099,-16.10154,112.152)" + cx="419.28891" + cy="453.16306" + fx="419.28891" + fy="453.16306" + r="11.005301" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient18976" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.596336,0,0,1.737952,606.0058,-21.55215)" + x1="415.12271" + y1="435.43039" + x2="415.17993" + y2="447.62582" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient18978" + gradientUnits="userSpaceOnUse" + x1="662.99841" + y1="70.997231" + x2="662.99841" + y2="62.014992" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient18980" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(7.603151e-2,-3.5534279e-6)" + x1="687.92395" + y1="82.974518" + x2="687.92395" + y2="61.362186" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient19014" + gradientUnits="userSpaceOnUse" + x1="409.12363" + y1="454.65622" + x2="409.12363" + y2="435.00833" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient9791" + id="radialGradient19016" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.023094,-1.794164e-2,1.365068e-2,0.769099,-16.10154,112.152)" + cx="419.28891" + cy="453.16306" + fx="419.28891" + fy="453.16306" + r="11.005301" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient19018" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.596336,0,0,1.737952,606.0058,-21.55215)" + x1="415.12271" + y1="435.43039" + x2="415.17993" + y2="447.62582" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient19020" + gradientUnits="userSpaceOnUse" + x1="662.99841" + y1="70.997231" + x2="662.99841" + y2="62.014992" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient19022" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(7.603151e-2,-3.5534282e-6)" + x1="687.92395" + y1="82.974518" + x2="687.92395" + y2="61.362186" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient19056" + gradientUnits="userSpaceOnUse" + x1="409.12363" + y1="454.65622" + x2="409.12363" + y2="435.00833" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient9791" + id="radialGradient19058" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.023094,-1.794164e-2,1.365068e-2,0.769099,-16.10154,112.152)" + cx="419.28891" + cy="453.16306" + fx="419.28891" + fy="453.16306" + r="11.005301" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient19060" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.596336,0,0,1.737952,606.0058,-21.55215)" + x1="415.12271" + y1="435.43039" + x2="415.17993" + y2="447.62582" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient19062" + gradientUnits="userSpaceOnUse" + x1="662.99841" + y1="70.997231" + x2="662.99841" + y2="62.014992" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient19064" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(7.603151e-2,-3.553428e-6)" + x1="687.92395" + y1="82.974518" + x2="687.92395" + y2="61.362186" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient19098" + gradientUnits="userSpaceOnUse" + x1="409.12363" + y1="454.65622" + x2="409.12363" + y2="435.00833" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient9791" + id="radialGradient19100" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.023094,-1.794164e-2,1.365068e-2,0.769099,-16.10154,112.152)" + cx="419.28891" + cy="453.16306" + fx="419.28891" + fy="453.16306" + r="11.005301" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient19102" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.596336,0,0,1.737952,606.0058,-21.55215)" + x1="415.12271" + y1="435.43039" + x2="415.17993" + y2="447.62582" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient19104" + gradientUnits="userSpaceOnUse" + x1="662.99841" + y1="70.997231" + x2="662.99841" + y2="62.014992" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient19106" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(7.603151e-2,-3.553428e-6)" + x1="687.92395" + y1="82.974518" + x2="687.92395" + y2="61.362186" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient19411" + gradientUnits="userSpaceOnUse" + x1="27.140532" + y1="168.19347" + x2="26.278814" + y2="159.89067" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10723" + id="linearGradient19413" + gradientUnits="userSpaceOnUse" + x1="155.13081" + y1="179.27431" + x2="156.27428" + y2="157.36133" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10715" + id="linearGradient19415" + gradientUnits="userSpaceOnUse" + x1="153.85107" + y1="178.59009" + x2="154.40889" + y2="158.05486" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient19417" + gradientUnits="userSpaceOnUse" + x1="662.99841" + y1="70.997231" + x2="662.99841" + y2="62.014992" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient19419" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(7.603151e-2,-3.5534279e-6)" + x1="687.92395" + y1="82.974518" + x2="687.92395" + y2="61.362186" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient19506" + gradientUnits="userSpaceOnUse" + x1="27.140532" + y1="168.19347" + x2="26.278814" + y2="159.89067" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10723" + id="linearGradient19508" + gradientUnits="userSpaceOnUse" + x1="155.13081" + y1="179.27431" + x2="156.27428" + y2="157.36133" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10715" + id="linearGradient19510" + gradientUnits="userSpaceOnUse" + x1="153.85107" + y1="178.59009" + x2="154.40889" + y2="158.05486" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient19512" + gradientUnits="userSpaceOnUse" + x1="662.99841" + y1="70.997231" + x2="662.99841" + y2="62.014992" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient19514" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(7.603151e-2,-3.5534279e-6)" + x1="687.92395" + y1="82.974518" + x2="687.92395" + y2="61.362186" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient19516" + gradientUnits="userSpaceOnUse" + x1="27.140532" + y1="168.19347" + x2="26.278814" + y2="159.89067" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10723" + id="linearGradient19518" + gradientUnits="userSpaceOnUse" + x1="155.13081" + y1="179.27431" + x2="156.27428" + y2="157.36133" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10715" + id="linearGradient19520" + gradientUnits="userSpaceOnUse" + x1="153.85107" + y1="178.59009" + x2="154.40889" + y2="158.05486" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient19522" + gradientUnits="userSpaceOnUse" + x1="662.99841" + y1="70.997231" + x2="662.99841" + y2="62.014992" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient19524" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(7.603151e-2,-3.5534282e-6)" + x1="687.92395" + y1="82.974518" + x2="687.92395" + y2="61.362186" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient19526" + gradientUnits="userSpaceOnUse" + x1="27.140532" + y1="168.19347" + x2="26.278814" + y2="159.89067" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10723" + id="linearGradient19528" + gradientUnits="userSpaceOnUse" + x1="155.13081" + y1="179.27431" + x2="156.27428" + y2="157.36133" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10715" + id="linearGradient19530" + gradientUnits="userSpaceOnUse" + x1="153.85107" + y1="178.59009" + x2="154.40889" + y2="158.05486" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient19532" + gradientUnits="userSpaceOnUse" + x1="662.99841" + y1="70.997231" + x2="662.99841" + y2="62.014992" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient19534" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(7.603151e-2,-3.5534281e-6)" + x1="687.92395" + y1="82.974518" + x2="687.92395" + y2="61.362186" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient19536" + gradientUnits="userSpaceOnUse" + x1="27.140532" + y1="168.19347" + x2="26.278814" + y2="159.89067" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10723" + id="linearGradient19538" + gradientUnits="userSpaceOnUse" + x1="155.13081" + y1="179.27431" + x2="156.27428" + y2="157.36133" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10715" + id="linearGradient19540" + gradientUnits="userSpaceOnUse" + x1="153.85107" + y1="178.59009" + x2="154.40889" + y2="158.05486" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient19542" + gradientUnits="userSpaceOnUse" + x1="662.99841" + y1="70.997231" + x2="662.99841" + y2="62.014992" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient19544" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(7.603151e-2,-3.5534281e-6)" + x1="687.92395" + y1="82.974518" + x2="687.92395" + y2="61.362186" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient19576" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.208342,0,0,1.20833,486.8724,591.0532)" + x1="663.21844" + y1="225.97932" + x2="663.21844" + y2="199.3622" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient19578" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.208342,0,0,1.20833,486.8724,591.0532)" + x1="689.7746" + y1="225.97932" + x2="689.7746" + y2="199.3622" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient12907" + id="linearGradient19580" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(2.297735,0,0,2.297712,874.4659,461.2189)" + x1="187.07394" + y1="162.15895" + x2="187.07394" + y2="178.28224" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient19582" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.208342,0,0,1.20833,486.8724,591.0532)" + x1="687.57507" + y1="223.41319" + x2="687.57507" + y2="201.92833" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient19584" + gradientUnits="userSpaceOnUse" + x1="662.99841" + y1="70.997231" + x2="662.99841" + y2="62.014992" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient19586" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(7.603151e-2,-3.5534282e-6)" + x1="687.92395" + y1="82.974518" + x2="687.92395" + y2="61.362186" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient19618" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.208342,0,0,1.20833,486.8724,591.0532)" + x1="663.21844" + y1="225.97932" + x2="663.21844" + y2="199.3622" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient19620" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.208342,0,0,1.20833,486.8724,591.0532)" + x1="689.7746" + y1="225.97932" + x2="689.7746" + y2="199.3622" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient12907" + id="linearGradient19622" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(2.297735,0,0,2.297712,874.4659,461.2189)" + x1="187.07394" + y1="162.15895" + x2="187.07394" + y2="178.28224" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient19624" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.208342,0,0,1.20833,486.8724,591.0532)" + x1="687.57507" + y1="223.41319" + x2="687.57507" + y2="201.92833" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient19626" + gradientUnits="userSpaceOnUse" + x1="662.99841" + y1="70.997231" + x2="662.99841" + y2="62.014992" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient19628" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(7.603151e-2,-3.5534281e-6)" + x1="687.92395" + y1="82.974518" + x2="687.92395" + y2="61.362186" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient19660" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.208342,0,0,1.20833,486.8724,591.0532)" + x1="663.21844" + y1="225.97932" + x2="663.21844" + y2="199.3622" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient19662" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.208342,0,0,1.20833,486.8724,591.0532)" + x1="689.7746" + y1="225.97932" + x2="689.7746" + y2="199.3622" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient12907" + id="linearGradient19664" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(2.297735,0,0,2.297712,874.4659,461.2189)" + x1="187.07394" + y1="162.15895" + x2="187.07394" + y2="178.28224" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient19666" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.208342,0,0,1.20833,486.8724,591.0532)" + x1="687.57507" + y1="223.41319" + x2="687.57507" + y2="201.92833" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient19668" + gradientUnits="userSpaceOnUse" + x1="662.99841" + y1="70.997231" + x2="662.99841" + y2="62.014992" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient19670" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(7.603151e-2,-3.5534282e-6)" + x1="687.92395" + y1="82.974518" + x2="687.92395" + y2="61.362186" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient19702" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.208342,0,0,1.20833,486.8724,591.0532)" + x1="663.21844" + y1="225.97932" + x2="663.21844" + y2="199.3622" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient19704" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.208342,0,0,1.20833,486.8724,591.0532)" + x1="689.7746" + y1="225.97932" + x2="689.7746" + y2="199.3622" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient12907" + id="linearGradient19706" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(2.297735,0,0,2.297712,874.4659,461.2189)" + x1="187.07394" + y1="162.15895" + x2="187.07394" + y2="178.28224" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient19708" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.208342,0,0,1.20833,486.8724,591.0532)" + x1="687.57507" + y1="223.41319" + x2="687.57507" + y2="201.92833" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient19710" + gradientUnits="userSpaceOnUse" + x1="662.99841" + y1="70.997231" + x2="662.99841" + y2="62.014992" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient19712" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(7.603151e-2,-3.553428e-6)" + x1="687.92395" + y1="82.974518" + x2="687.92395" + y2="61.362186" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient19957" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.644894,0,0,0.654134,1264.905,875.3596)" + x1="221.95605" + y1="182.81895" + x2="221.95605" + y2="154.92436" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient19959" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.644894,0,0,0.654134,1264.905,875.3596)" + x1="267.75653" + y1="182.81895" + x2="267.75653" + y2="154.92436" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient19961" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.437568,0,0,0.437568,375.8931,217.9153)" + x1="662.99841" + y1="70.997231" + x2="662.99841" + y2="62.014992" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient19963" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.437568,0,0,0.437568,375.9264,217.9153)" + x1="687.92395" + y1="82.974518" + x2="687.92395" + y2="61.362186" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient19997" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.644894,0,0,0.654134,1264.905,875.3596)" + x1="221.95605" + y1="182.81895" + x2="221.95605" + y2="154.92436" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient19999" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.644894,0,0,0.654134,1264.905,875.3596)" + x1="267.75653" + y1="182.81895" + x2="267.75653" + y2="154.92436" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient20002" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.437568,0,0,0.437568,375.8931,217.9153)" + x1="662.99841" + y1="70.997231" + x2="662.99841" + y2="62.014992" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient20004" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.437568,0,0,0.437568,375.9264,217.9153)" + x1="687.92395" + y1="82.974518" + x2="687.92395" + y2="61.362186" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient20038" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.644894,0,0,0.654134,1264.905,875.3596)" + x1="221.95605" + y1="182.81895" + x2="221.95605" + y2="154.92436" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient20040" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.644894,0,0,0.654134,1264.905,875.3596)" + x1="267.75653" + y1="182.81895" + x2="267.75653" + y2="154.92436" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient20042" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.437568,0,0,0.437568,375.8931,217.9153)" + x1="662.99841" + y1="70.997231" + x2="662.99841" + y2="62.014992" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient20044" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.437568,0,0,0.437568,375.9264,217.9153)" + x1="687.92395" + y1="82.974518" + x2="687.92395" + y2="61.362186" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient20079" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.644894,0,0,0.654134,1264.905,875.3596)" + x1="221.95605" + y1="182.81895" + x2="221.95605" + y2="154.92436" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient20081" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.644894,0,0,0.654134,1264.905,875.3596)" + x1="267.75653" + y1="182.81895" + x2="267.75653" + y2="154.92436" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient20083" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.437568,0,0,0.437568,375.8931,217.9153)" + x1="662.99841" + y1="70.997231" + x2="662.99841" + y2="62.014992" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient20085" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.437568,0,0,0.437568,375.9264,217.9153)" + x1="687.92395" + y1="82.974518" + x2="687.92395" + y2="61.362186" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient20087" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.644894,0,0,0.654134,1264.905,875.3596)" + x1="221.95605" + y1="182.81895" + x2="221.95605" + y2="154.92436" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient20089" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.644894,0,0,0.654134,1264.905,875.3596)" + x1="267.75653" + y1="182.81895" + x2="267.75653" + y2="154.92436" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient20091" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.437568,0,0,0.437568,375.8931,217.9153)" + x1="662.99841" + y1="70.997231" + x2="662.99841" + y2="62.014992" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient20093" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.437568,0,0,0.437568,375.9264,217.9153)" + x1="687.92395" + y1="82.974518" + x2="687.92395" + y2="61.362186" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10203" + id="linearGradient10209" + x1="894.40143" + y1="1458.5347" + x2="897.12982" + y2="1458.5348" + gradientUnits="userSpaceOnUse" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient10338" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.375,0,0,1.469276,179.5471,-10.85991)" + x1="282.96594" + y1="113.52765" + x2="282.96594" + y2="98.532501" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7919" + id="linearGradient10340" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-7.977528e-3,-9.016333e-2,8.790531e-2,-7.536272e-3,555.7662,198.1625)" + x1="507.37094" + y1="290.88303" + x2="608.41498" + y2="398.94208" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient10342" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.216699,0,0,1.197663,-126.7483,-28.54217)" + x1="589.09595" + y1="153.23845" + x2="589.09595" + y2="136.54427" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient21450" + id="linearGradient10344" + gradientUnits="userSpaceOnUse" + x1="1308.2411" + y1="1395.8469" + x2="1308.2411" + y2="1473.3281" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient20611" + id="linearGradient10346" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(5.767204e-3,6.518187e-2,-6.407273e-2,5.493064e-3,667.9806,50.83129)" + x1="1394.5476" + y1="1587.7018" + x2="1412.9457" + y2="1547.1619" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient13829" + id="linearGradient10348" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(3.288458e-2,5.641064e-2,-5.529172e-2,3.311662e-2,618.1039,20.62608)" + x1="1277.4498" + y1="1612.0433" + x2="1406.9330" + y2="1424.1870" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient20611" + id="linearGradient10350" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-5.80699e-3,-6.563155e-2,6.499001e-2,-5.571703e-3,490.7537,240.0609)" + x1="1394.5476" + y1="1587.7018" + x2="1412.9457" + y2="1547.1619" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient13829" + id="linearGradient10352" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-3.331678e-2,-5.678175e-2,5.610206e-2,-3.338171e-2,541.322,270.4442)" + x1="1277.4498" + y1="1612.0433" + x2="1406.9330" + y2="1424.1870" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient10354" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.130153,0,0,1.068982,108.8626,-330.5337)" + x1="415.12271" + y1="435.43039" + x2="415.12271" + y2="453.03961" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10275" + id="linearGradient10356" + gradientUnits="userSpaceOnUse" + x1="1104.4021" + y1="1431.5235" + x2="1115.1294" + y2="1431.5236" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient10378" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.375,0,0,1.469276,179.5471,-10.85991)" + x1="282.96594" + y1="113.52765" + x2="282.96594" + y2="98.532501" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7919" + id="linearGradient10380" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-7.977528e-3,-9.016333e-2,8.790531e-2,-7.536272e-3,555.7662,198.1625)" + x1="507.37094" + y1="290.88303" + x2="608.41498" + y2="398.94208" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient10382" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.216699,0,0,1.197663,-126.7483,-28.54217)" + x1="589.09595" + y1="153.23845" + x2="589.09595" + y2="136.54427" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient21450" + id="linearGradient10384" + gradientUnits="userSpaceOnUse" + x1="1308.2411" + y1="1395.8469" + x2="1308.2411" + y2="1473.3281" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient20611" + id="linearGradient10386" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(5.767204e-3,6.518187e-2,-6.407273e-2,5.493064e-3,667.9806,50.83129)" + x1="1394.5476" + y1="1587.7018" + x2="1412.9457" + y2="1547.1619" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient13829" + id="linearGradient10388" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(3.288458e-2,5.641064e-2,-5.529172e-2,3.311662e-2,618.1039,20.62608)" + x1="1277.4498" + y1="1612.0433" + x2="1406.9330" + y2="1424.1870" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient20611" + id="linearGradient10390" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-5.80699e-3,-6.563155e-2,6.499001e-2,-5.571703e-3,490.7537,240.0609)" + x1="1394.5476" + y1="1587.7018" + x2="1412.9457" + y2="1547.1619" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient13829" + id="linearGradient10392" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-3.331678e-2,-5.678175e-2,5.610206e-2,-3.338171e-2,541.322,270.4442)" + x1="1277.4498" + y1="1612.0433" + x2="1406.9330" + y2="1424.1870" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient10394" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.130153,0,0,1.068982,108.8626,-330.5337)" + x1="415.12271" + y1="435.43039" + x2="415.12271" + y2="453.03961" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10186" + id="linearGradient10396" + gradientUnits="userSpaceOnUse" + x1="967.40192" + y1="1442.5419" + x2="973.32471" + y2="1442.5419" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient10398" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.375,0,0,1.469276,179.5471,-10.85991)" + x1="282.96594" + y1="113.52765" + x2="282.96594" + y2="98.532501" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7919" + id="linearGradient10400" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-7.977528e-3,-9.016333e-2,8.790531e-2,-7.536272e-3,555.7662,198.1625)" + x1="507.37094" + y1="290.88303" + x2="608.41498" + y2="398.94208" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient10402" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.216699,0,0,1.197663,-126.7483,-28.54217)" + x1="589.09595" + y1="153.23845" + x2="589.09595" + y2="136.54427" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient21450" + id="linearGradient10404" + gradientUnits="userSpaceOnUse" + x1="1308.2411" + y1="1395.8469" + x2="1308.2411" + y2="1473.3281" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient20611" + id="linearGradient10406" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(5.767204e-3,6.518187e-2,-6.407273e-2,5.493064e-3,667.9806,50.83129)" + x1="1394.5476" + y1="1587.7018" + x2="1412.9457" + y2="1547.1619" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient13829" + id="linearGradient10408" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(3.288458e-2,5.641064e-2,-5.529172e-2,3.311662e-2,618.1039,20.62608)" + x1="1277.4498" + y1="1612.0433" + x2="1406.9330" + y2="1424.1870" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient20611" + id="linearGradient10410" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-5.80699e-3,-6.563155e-2,6.499001e-2,-5.571703e-3,490.7537,240.0609)" + x1="1394.5476" + y1="1587.7018" + x2="1412.9457" + y2="1547.1619" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient13829" + id="linearGradient10412" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-3.331678e-2,-5.678175e-2,5.610206e-2,-3.338171e-2,541.322,270.4442)" + x1="1277.4498" + y1="1612.0433" + x2="1406.9330" + y2="1424.1870" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient10414" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.130153,0,0,1.068982,108.8626,-330.5337)" + x1="415.12271" + y1="435.43039" + x2="415.12271" + y2="453.03961" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10241" + id="linearGradient10416" + gradientUnits="userSpaceOnUse" + x1="928.40186" + y1="1454.6477" + x2="932.18848" + y2="1454.6477" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient10438" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.654818,0,0,0.654818,360.1651,32.6063)" + x1="369.62366" + y1="171.32159" + x2="369.62366" + y2="161.05565" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient19089" + id="linearGradient10440" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.654818,0,0,0.654818,360.1651,32.6063)" + spreadMethod="reflect" + x1="380.70312" + y1="167.74232" + x2="380.59375" + y2="167.47937" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient10460" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.654818,0,0,0.654818,360.1651,32.6063)" + x1="369.62366" + y1="171.32159" + x2="369.62366" + y2="161.05565" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient19089" + id="linearGradient10462" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.654818,0,0,0.654818,360.1651,32.6063)" + spreadMethod="reflect" + x1="380.70312" + y1="167.74232" + x2="380.59375" + y2="167.47937" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient10482" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.654818,0,0,0.654818,360.1651,32.6063)" + x1="369.62366" + y1="171.32159" + x2="369.62366" + y2="161.05565" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient19089" + id="linearGradient10484" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.654818,0,0,0.654818,360.1651,32.6063)" + spreadMethod="reflect" + x1="380.70312" + y1="167.74232" + x2="380.59375" + y2="167.47937" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient10505" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.654818,0,0,0.654818,360.1651,32.6063)" + x1="369.62366" + y1="171.32159" + x2="369.62366" + y2="161.05565" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient19089" + id="linearGradient10507" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.654818,0,0,0.654818,360.1651,32.6063)" + spreadMethod="reflect" + x1="380.70312" + y1="167.74232" + x2="380.59375" + y2="167.47937" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10866" + id="linearGradient12060" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.773613,0.173863,0.21243,0.833622,416.7094,56.3857)" + spreadMethod="reflect" + x1="142.57724" + y1="86.521561" + x2="144.66388" + y2="84.748222" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient12062" + gradientUnits="userSpaceOnUse" + x1="542.03156" + y1="152.7681" + x2="551.5567" + y2="152.7681" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient7919" + id="radialGradient12064" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-8.442359e-5,0.634257,-0.731707,-9.739477e-5,629.8264,-3.386077)" + cx="431.8631" + cy="270.50006" + fx="431.8631" + fy="270.50006" + r="1.2339805" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient12066" + gradientUnits="userSpaceOnUse" + x1="430.08484" + y1="270.69183" + x2="431.49219" + y2="267.01645" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient12068" + gradientUnits="userSpaceOnUse" + x1="428.77151" + y1="454.65622" + x2="428.77151" + y2="435.00833" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient9912" + id="radialGradient12070" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.726147,-8.054512e-3,1.109145e-2,0.999938,109.5649,3.398898)" + cx="418.51233" + cy="454.95853" + fx="418.51233" + fy="454.95853" + r="11.005301" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient12072" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.688507,0,0,0.688507,251.3418,-162.7892)" + x1="415.12271" + y1="435.43039" + x2="415.12271" + y2="453.03961" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10866" + id="linearGradient12088" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.773613,0.173863,0.21243,0.833622,416.7094,56.3857)" + spreadMethod="reflect" + x1="142.57724" + y1="86.521561" + x2="144.66388" + y2="84.748222" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient12090" + gradientUnits="userSpaceOnUse" + x1="542.03156" + y1="152.7681" + x2="551.5567" + y2="152.7681" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient7919" + id="radialGradient12092" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-8.442359e-5,0.634257,-0.731707,-9.739477e-5,629.8264,-3.386077)" + cx="431.8631" + cy="270.50006" + fx="431.8631" + fy="270.50006" + r="1.2339805" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient12094" + gradientUnits="userSpaceOnUse" + x1="430.08484" + y1="270.69183" + x2="431.49219" + y2="267.01645" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient12096" + gradientUnits="userSpaceOnUse" + x1="428.77151" + y1="454.65622" + x2="428.77151" + y2="435.00833" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient9912" + id="radialGradient12098" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.726147,-8.054512e-3,1.109145e-2,0.999938,109.5649,3.398898)" + cx="418.51233" + cy="454.95853" + fx="418.51233" + fy="454.95853" + r="11.005301" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient12100" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.688507,0,0,0.688507,251.3418,-162.7892)" + x1="415.12271" + y1="435.43039" + x2="415.12271" + y2="453.03961" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10866" + id="linearGradient12114" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.773613,0.173863,0.21243,0.833622,416.7094,56.3857)" + spreadMethod="reflect" + x1="142.57724" + y1="86.521561" + x2="144.66388" + y2="84.748222" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient12116" + gradientUnits="userSpaceOnUse" + x1="542.03156" + y1="152.7681" + x2="551.5567" + y2="152.7681" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient7919" + id="radialGradient12118" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-8.442359e-5,0.634257,-0.731707,-9.739477e-5,629.8264,-3.386077)" + cx="431.8631" + cy="270.50006" + fx="431.8631" + fy="270.50006" + r="1.2339805" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient12120" + gradientUnits="userSpaceOnUse" + x1="430.08484" + y1="270.69183" + x2="431.49219" + y2="267.01645" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient12122" + gradientUnits="userSpaceOnUse" + x1="428.77151" + y1="454.65622" + x2="428.77151" + y2="435.00833" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient9912" + id="radialGradient12124" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.726147,-8.054512e-3,1.109145e-2,0.999938,109.5649,3.398898)" + cx="418.51233" + cy="454.95853" + fx="418.51233" + fy="454.95853" + r="11.005301" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient12126" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.688507,0,0,0.688507,251.3418,-162.7892)" + x1="415.12271" + y1="435.43039" + x2="415.12271" + y2="453.03961" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10866" + id="linearGradient12140" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.773613,0.173863,0.21243,0.833622,416.7094,56.3857)" + spreadMethod="reflect" + x1="142.57724" + y1="86.521561" + x2="144.66388" + y2="84.748222" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient12142" + gradientUnits="userSpaceOnUse" + x1="542.03156" + y1="152.7681" + x2="551.5567" + y2="152.7681" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient7919" + id="radialGradient12144" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-8.442359e-5,0.634257,-0.731707,-9.739477e-5,629.8264,-3.386077)" + cx="431.8631" + cy="270.50006" + fx="431.8631" + fy="270.50006" + r="1.2339805" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient12146" + gradientUnits="userSpaceOnUse" + x1="430.08484" + y1="270.69183" + x2="431.49219" + y2="267.01645" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient12148" + gradientUnits="userSpaceOnUse" + x1="428.77151" + y1="454.65622" + x2="428.77151" + y2="435.00833" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient9912" + id="radialGradient12150" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.726147,-8.054512e-3,1.109145e-2,0.999938,109.5649,3.398898)" + cx="418.51233" + cy="454.95853" + fx="418.51233" + fy="454.95853" + r="11.005301" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient12152" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.688507,0,0,0.688507,251.3418,-162.7892)" + x1="415.12271" + y1="435.43039" + x2="415.12271" + y2="453.03961" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10866" + id="linearGradient12166" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.773613,0.173863,0.21243,0.833622,416.7094,56.3857)" + spreadMethod="reflect" + x1="142.57724" + y1="86.521561" + x2="144.66388" + y2="84.748222" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient12168" + gradientUnits="userSpaceOnUse" + x1="542.03156" + y1="152.7681" + x2="551.5567" + y2="152.7681" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient7919" + id="radialGradient12170" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-8.442359e-5,0.634257,-0.731707,-9.739477e-5,629.8264,-3.386077)" + cx="431.8631" + cy="270.50006" + fx="431.8631" + fy="270.50006" + r="1.2339805" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient12172" + gradientUnits="userSpaceOnUse" + x1="430.08484" + y1="270.69183" + x2="431.49219" + y2="267.01645" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient12174" + gradientUnits="userSpaceOnUse" + x1="428.77151" + y1="454.65622" + x2="428.77151" + y2="435.00833" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient9912" + id="radialGradient12176" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.726147,-8.054512e-3,1.109145e-2,0.999938,109.5649,3.398898)" + cx="418.51233" + cy="454.95853" + fx="418.51233" + fy="454.95853" + r="11.005301" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient12178" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.688507,0,0,0.688507,251.3418,-162.7892)" + x1="415.12271" + y1="435.43039" + x2="415.12271" + y2="453.03961" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4166" + id="linearGradient12368" + gradientUnits="userSpaceOnUse" + x1="364.35117" + y1="443.11044" + x2="363.85608" + y2="428.539" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4182" + id="linearGradient12370" + gradientUnits="userSpaceOnUse" + x1="417.36929" + y1="435.0079" + x2="417.36929" + y2="454.65622" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4190" + id="radialGradient12372" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.726147,-8.054512e-3,1.109145e-2,0.999938,109.5649,3.398898)" + cx="418.51233" + cy="454.95853" + fx="418.51233" + fy="454.95853" + r="11.005301" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient12374" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.89894,0,0,0.89894,-354.7664,-364.9796)" + x1="415.12271" + y1="435.43039" + x2="415.12271" + y2="453.03961" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4166" + id="linearGradient12386" + gradientUnits="userSpaceOnUse" + x1="364.35117" + y1="443.11044" + x2="363.85608" + y2="428.539" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4182" + id="linearGradient12388" + gradientUnits="userSpaceOnUse" + x1="417.36929" + y1="435.0079" + x2="417.36929" + y2="454.65622" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4190" + id="radialGradient12390" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.726147,-8.054512e-3,1.109145e-2,0.999938,109.5649,3.398898)" + cx="418.51233" + cy="454.95853" + fx="418.51233" + fy="454.95853" + r="11.005301" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient12392" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.89894,0,0,0.89894,-354.7664,-364.9796)" + x1="415.12271" + y1="435.43039" + x2="415.12271" + y2="453.03961" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4166" + id="linearGradient12404" + gradientUnits="userSpaceOnUse" + x1="364.35117" + y1="443.11044" + x2="363.85608" + y2="428.539" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4182" + id="linearGradient12406" + gradientUnits="userSpaceOnUse" + x1="417.36929" + y1="435.0079" + x2="417.36929" + y2="454.65622" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4190" + id="radialGradient12408" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.726147,-8.054512e-3,1.109145e-2,0.999938,109.5649,3.398898)" + cx="418.51233" + cy="454.95853" + fx="418.51233" + fy="454.95853" + r="11.005301" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient12410" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.89894,0,0,0.89894,-354.0102,-380.2835)" + x1="415.12271" + y1="435.43039" + x2="415.12271" + y2="453.03961" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4166" + id="linearGradient12422" + gradientUnits="userSpaceOnUse" + x1="364.35117" + y1="443.11044" + x2="363.85608" + y2="428.539" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4182" + id="linearGradient12424" + gradientUnits="userSpaceOnUse" + x1="417.36929" + y1="435.0079" + x2="417.36929" + y2="454.65622" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4190" + id="radialGradient12426" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.726147,-8.054512e-3,1.109145e-2,0.999938,109.5649,3.398898)" + cx="418.51233" + cy="454.95853" + fx="418.51233" + fy="454.95853" + r="11.005301" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient12428" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.89894,0,0,0.89894,-354.0102,-380.2835)" + x1="415.12271" + y1="435.43039" + x2="415.12271" + y2="453.03961" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4166" + id="linearGradient12440" + gradientUnits="userSpaceOnUse" + x1="364.35117" + y1="443.11044" + x2="363.85608" + y2="428.539" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4182" + id="linearGradient12442" + gradientUnits="userSpaceOnUse" + x1="417.36929" + y1="435.0079" + x2="417.36929" + y2="454.65622" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4190" + id="radialGradient12444" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.726147,-8.054512e-3,1.109145e-2,0.999938,109.5649,3.398898)" + cx="418.51233" + cy="454.95853" + fx="418.51233" + fy="454.95853" + r="11.005301" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient12446" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.89894,0,0,0.89894,-354.0102,-380.2835)" + x1="415.12271" + y1="435.43039" + x2="415.12271" + y2="453.03961" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient11333" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1,0,0,1.089096,311.6482,724.1753)" + x1="323.01694" + y1="149.89334" + x2="323.01694" + y2="127.84659" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient11335" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-0.133485,0,0,0.231539,638.0594,863.971)" + x1="126.05801" + y1="41.340328" + x2="-155.95905" + y2="41.340332" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="radialGradient11337" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.316498,1.408891e-6,-1.390072e-6,1.298913,212.391,693.9915)" + cx="314.87411" + cy="143.0015" + fx="314.87411" + fy="143.0015" + r="4.9997702" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient11339" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.225029,0,0,0.211095,493.6644,805.494)" + x1="571.4317" + y1="320.04877" + x2="571.4317" + y2="286.65356" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient11341" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(7.606155e-2,0,0,3.553691e-2,600.6234,862.314)" + x1="341.47736" + y1="119.2719" + x2="340.59683" + y2="235.50371" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10866" + id="linearGradient11418" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.138892,0.255956,0.312734,1.227236,485.3759,1636.644)" + spreadMethod="reflect" + x1="142.57724" + y1="86.521561" + x2="144.66388" + y2="84.748222" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient11420" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.472173,0,0,1.472173,-128.0924,1553.634)" + x1="542.03156" + y1="152.7681" + x2="551.5567" + y2="152.7681" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient7919" + id="radialGradient11422" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-8.442359e-5,0.634257,-0.731707,-9.739477e-5,629.8264,-3.386077)" + cx="431.8631" + cy="270.50006" + fx="431.8631" + fy="270.50006" + r="1.2339805" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient11424" + gradientUnits="userSpaceOnUse" + x1="430.08484" + y1="270.69183" + x2="431.49219" + y2="267.01645" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient11426" + gradientUnits="userSpaceOnUse" + x1="428.77151" + y1="454.65622" + x2="428.77151" + y2="435.00833" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient9912" + id="radialGradient11428" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.726147,-8.054512e-3,1.109145e-2,0.999938,109.5649,3.398898)" + cx="418.51233" + cy="454.95853" + fx="418.51233" + fy="454.95853" + r="11.005301" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient11431" + gradientUnits="userSpaceOnUse" + x1="407.50702" + y1="446.44885" + x2="429.51764" + y2="446.44885" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient11433" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.013601,0,0,1.013601,241.9262,1313.98)" + x1="415.12271" + y1="435.43039" + x2="415.12271" + y2="453.03961" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient11435" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.554799,0.554779,-0.554799,0.554779,709.2206,1533.643)" + x1="168.18773" + y1="260.09671" + x2="156.90306" + y2="248.81204" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient11437" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.554799,0.554779,-0.554799,0.554779,709.2206,1533.643)" + x1="179.47237" + y1="248.81206" + x2="168.18771" + y2="237.52739" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient10418" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(27.99914,1.31776)" + x1="432.34427" + y1="65.815475" + x2="432.34427" + y2="43.934036" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient10420" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(10.43883,-16.39223)" + x1="431.24716" + y1="75.79641" + x2="431.24716" + y2="57.478249" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient10423" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(10.43883,-16.39223)" + x1="448.74854" + y1="82.005356" + x2="448.74854" + y2="66.314919" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient10425" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(7.618051e-2,0,0,7.282925e-2,240.3801,-14.402)" + x1="2642.5115" + y1="1026.281" + x2="2642.5115" + y2="921.65161" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient10469" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.453291,0,0,1.45926,-447.5553,433.669)" + x1="624.93506" + y1="95.983948" + x2="624.93506" + y2="74.911629" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient10472" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.453291,0,0,1.45926,-447.5553,433.669)" + x1="646.50549" + y1="96.483948" + x2="646.50549" + y2="74.411629" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4190" + id="radialGradient10474" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.659035,0,0,0.659035,417.1851,525.5536)" + cx="89.191399" + cy="34.808926" + fx="89.191399" + fy="34.808926" + r="22.2183" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient10476" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-6.374707e-2,0.4625773,0.2783092,0.105957,413.92013,808.40178)" + x1="1618.8217" + y1="1645.2587" + x2="1488.2998" + y2="1615.3624" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient10478" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-1.894751e-2,-0.9194206,-0.5531678,3.1486836e-2,833.07543,1757.7736)" + x1="30.148834" + y1="47.78376" + x2="99.181152" + y2="45.419212" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient10499" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.453291,0,0,1.45926,-447.5553,433.669)" + x1="624.93506" + y1="95.983948" + x2="624.93506" + y2="74.911629" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient10501" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.453291,0,0,1.45926,-447.5553,433.669)" + x1="646.50549" + y1="96.483948" + x2="646.50549" + y2="74.411629" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4190" + id="radialGradient10503" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.659035,0,0,0.659035,417.1851,525.5536)" + cx="89.191399" + cy="34.808926" + fx="89.191399" + fy="34.808926" + r="22.2183" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient10506" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-6.374707e-2,0.4625773,0.2783092,0.105957,413.92013,808.40178)" + x1="1618.8217" + y1="1645.2587" + x2="1488.2998" + y2="1615.3624" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient10508" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-1.894751e-2,-0.9194206,-0.5531678,3.1486836e-2,833.07543,1757.7736)" + x1="30.148834" + y1="47.78376" + x2="99.181152" + y2="45.419212" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient10522" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.453291,0,0,1.45926,-447.5553,433.669)" + x1="624.93506" + y1="95.983948" + x2="624.93506" + y2="74.911629" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient10525" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.453291,0,0,1.45926,-447.5553,433.669)" + x1="646.50549" + y1="96.483948" + x2="646.50549" + y2="74.411629" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4190" + id="radialGradient10527" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.659035,0,0,0.659035,417.1851,525.5536)" + cx="89.191399" + cy="34.808926" + fx="89.191399" + fy="34.808926" + r="22.2183" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient10529" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-6.374707e-2,0.4625773,0.2783092,0.105957,413.92013,808.40178)" + x1="1618.8217" + y1="1645.2587" + x2="1488.2998" + y2="1615.3624" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient10531" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-1.894751e-2,-0.9194206,-0.5531678,3.1486836e-2,833.07543,1757.7736)" + x1="30.148834" + y1="47.78376" + x2="99.181152" + y2="45.419212" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient10545" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.453291,0,0,1.45926,-447.5553,433.669)" + x1="624.93506" + y1="95.983948" + x2="624.93506" + y2="74.911629" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient10547" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.453291,0,0,1.45926,-447.5553,433.669)" + x1="646.50549" + y1="96.483948" + x2="646.50549" + y2="74.411629" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4190" + id="radialGradient10549" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.659035,0,0,0.659035,417.1851,525.5536)" + cx="89.191399" + cy="34.808926" + fx="89.191399" + fy="34.808926" + r="22.2183" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient10551" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-6.374707e-2,0.4625773,0.2783092,0.105957,413.92013,808.40178)" + x1="1618.8217" + y1="1645.2587" + x2="1488.2998" + y2="1615.3624" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient10553" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-1.894751e-2,-0.9194206,-0.5531678,3.1486836e-2,833.07543,1757.7736)" + x1="30.148834" + y1="47.78376" + x2="99.181152" + y2="45.419212" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient10555" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.453291,0,0,1.45926,-447.5553,433.669)" + x1="624.93506" + y1="95.983948" + x2="624.93506" + y2="74.911629" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient10557" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.453291,0,0,1.45926,-447.5553,433.669)" + x1="646.50549" + y1="96.483948" + x2="646.50549" + y2="74.411629" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4190" + id="radialGradient10559" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.659035,0,0,0.659035,417.1851,525.5536)" + cx="89.191399" + cy="34.808926" + fx="89.191399" + fy="34.808926" + r="22.2183" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient10561" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-6.374707e-2,0.4625773,0.2783092,0.105957,413.92013,808.40178)" + x1="1618.8217" + y1="1645.2587" + x2="1488.2998" + y2="1615.3624" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient10563" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-1.894751e-2,-0.9194206,-0.5531678,3.1486836e-2,833.07543,1757.7736)" + x1="30.148834" + y1="47.78376" + x2="99.181152" + y2="45.419212" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7919" + id="linearGradient10544" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-4.875892e-3,-5.598396e-2,5.372793e-2,-4.679404e-3,617.0533,758.8446)" + x1="507.37094" + y1="290.88303" + x2="608.41498" + y2="398.94208" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient10546" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-2.548793e-2,-0.292647,0.280854,-2.446082e-2,521.3407,1062.887)" + x1="1088.9348" + y1="490.38297" + x2="1132.1641" + y2="494.30609" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient10548" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(4.867991e-3,5.589324e-2,-5.408237e-2,4.710273e-3,707.3524,643.1593)" + x1="1394.5476" + y1="1587.7018" + x2="1412.9457" + y2="1547.1619" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient10550" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-4.823157e-3,-5.537847e-2,5.39793e-2,-4.701296e-3,556.3565,807.7638)" + x1="1394.5476" + y1="1587.7018" + x2="1412.9457" + y2="1547.1619" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7919" + id="linearGradient10552" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-4.875892e-3,-5.598396e-2,5.372793e-2,-4.679404e-3,612.4965,750.8511)" + x1="507.37094" + y1="290.88303" + x2="608.41498" + y2="398.94208" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient10554" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-2.548793e-2,-0.292647,0.280854,-2.446082e-2,516.7839,1054.893)" + x1="1088.9348" + y1="490.38297" + x2="1132.1641" + y2="494.30609" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient10558" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-4.823157e-3,-5.537847e-2,5.39793e-2,-4.701296e-3,551.7997,799.7703)" + x1="1394.5476" + y1="1587.7018" + x2="1412.9457" + y2="1547.1619" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7919" + id="linearGradient10560" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-4.875892e-3,-5.598396e-2,5.372793e-2,-4.679404e-3,607.8044,758.8446)" + x1="507.37094" + y1="290.88303" + x2="608.41498" + y2="398.94208" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient10562" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-2.548793e-2,-0.292647,0.280854,-2.446082e-2,512.0918,1062.887)" + x1="1088.9348" + y1="490.38297" + x2="1132.1641" + y2="494.30609" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient10564" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(4.867991e-3,5.589324e-2,-5.408237e-2,4.710273e-3,698.1035,643.1593)" + x1="1394.5476" + y1="1587.7018" + x2="1412.9457" + y2="1547.1619" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient10566" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-4.823157e-3,-5.537847e-2,5.39793e-2,-4.701296e-3,547.1076,807.7638)" + x1="1394.5476" + y1="1587.7018" + x2="1412.9457" + y2="1547.1619" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7919" + id="linearGradient10568" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-7.371756e-3,-8.464087e-2,8.123002e-2,-7.074689e-3,649.2207,776.8758)" + x1="507.37094" + y1="290.88303" + x2="608.41498" + y2="398.94208" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient13837" + id="linearGradient10570" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-3.853465e-2,-0.442446,0.424617,-3.698179e-2,504.5152,1236.551)" + x1="1129.3464" + y1="517.39832" + x2="1087.991" + y2="513.6452" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient10572" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(7.359809e-3,8.450372e-2,-8.176588e-2,7.121361e-3,785.742,601.9738)" + x1="1394.5476" + y1="1587.7018" + x2="1412.9457" + y2="1547.1619" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient10574" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-7.292027e-3,-8.372545e-2,8.161005e-2,-7.107788e-3,557.4547,850.8355)" + x1="1394.5476" + y1="1587.7018" + x2="1412.9457" + y2="1547.1619" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7919" + id="linearGradient10576" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-7.371756e-3,-8.464087e-2,8.123002e-2,-7.074689e-3,635.2375,776.8758)" + x1="507.37094" + y1="290.88303" + x2="608.41498" + y2="398.94208" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient13837" + id="linearGradient10578" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-3.853465e-2,-0.442446,0.424617,-3.698179e-2,490.5319,1236.551)" + x1="1088.9348" + y1="490.38297" + x2="1132.1641" + y2="494.30609" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient10580" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(7.359809e-3,8.450372e-2,-8.176588e-2,7.121361e-3,771.7587,601.9738)" + x1="1394.5476" + y1="1587.7018" + x2="1412.9457" + y2="1547.1619" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient10582" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-7.292027e-3,-8.372545e-2,8.161005e-2,-7.107788e-3,543.4715,850.8355)" + x1="1394.5476" + y1="1587.7018" + x2="1412.9457" + y2="1547.1619" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7919" + id="linearGradient10584" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-7.371756e-3,-8.464087e-2,8.123002e-2,-7.074689e-3,642.3314,764.7906)" + x1="507.37094" + y1="290.88303" + x2="608.41498" + y2="398.94208" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient13837" + id="linearGradient10586" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-3.853465e-2,-0.442446,0.424617,-3.698179e-2,497.6258,1224.465)" + x1="1129.3464" + y1="517.39832" + x2="1087.991" + y2="513.6452" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient10589" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(7.359809e-3,8.450372e-2,-8.176588e-2,7.121361e-3,778.4877,590.208)" + x1="1394.5476" + y1="1587.7018" + x2="1412.9457" + y2="1547.1619" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient10591" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-7.292027e-3,-8.372545e-2,8.161005e-2,-7.107788e-3,550.8847,838.3854)" + x1="1394.5476" + y1="1587.7018" + x2="1412.9457" + y2="1547.1619" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7919" + id="linearGradient10593" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-4.875892e-3,-5.598396e-2,5.372793e-2,-4.679404e-3,617.0533,758.8446)" + x1="507.37094" + y1="290.88303" + x2="608.41498" + y2="398.94208" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient10595" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-2.548793e-2,-0.292647,0.280854,-2.446082e-2,521.3407,1062.887)" + x1="1088.9348" + y1="490.38297" + x2="1132.1641" + y2="494.30609" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient10597" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(4.867991e-3,5.589324e-2,-5.408237e-2,4.710273e-3,707.3524,643.1593)" + x1="1394.5476" + y1="1587.7018" + x2="1412.9457" + y2="1547.1619" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient10599" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-4.823157e-3,-5.537847e-2,5.39793e-2,-4.701296e-3,556.3565,807.7638)" + x1="1394.5476" + y1="1587.7018" + x2="1412.9457" + y2="1547.1619" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7919" + id="linearGradient10601" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-4.875892e-3,-5.598396e-2,5.372793e-2,-4.679404e-3,612.4965,750.8511)" + x1="507.37094" + y1="290.88303" + x2="608.41498" + y2="398.94208" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient10603" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-2.548793e-2,-0.292647,0.280854,-2.446082e-2,516.7839,1054.893)" + x1="1088.9348" + y1="490.38297" + x2="1132.1641" + y2="494.30609" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient10605" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(4.867991e-3,5.589324e-2,-5.408237e-2,4.710273e-3,702.7956,635.1658)" + x1="1394.5476" + y1="1587.7018" + x2="1412.9457" + y2="1547.1619" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient10607" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-4.823157e-3,-5.537847e-2,5.39793e-2,-4.701296e-3,551.7997,799.7703)" + x1="1394.5476" + y1="1587.7018" + x2="1412.9457" + y2="1547.1619" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7919" + id="linearGradient10609" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-4.875892e-3,-5.598396e-2,5.372793e-2,-4.679404e-3,607.8044,758.8446)" + x1="507.37094" + y1="290.88303" + x2="608.41498" + y2="398.94208" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient10612" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-2.548793e-2,-0.292647,0.280854,-2.446082e-2,512.0918,1062.887)" + x1="1088.9348" + y1="490.38297" + x2="1132.1641" + y2="494.30609" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient10614" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(4.867991e-3,5.589324e-2,-5.408237e-2,4.710273e-3,698.1035,643.1593)" + x1="1394.5476" + y1="1587.7018" + x2="1412.9457" + y2="1547.1619" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient10616" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-4.823157e-3,-5.537847e-2,5.39793e-2,-4.701296e-3,547.1076,807.7638)" + x1="1394.5476" + y1="1587.7018" + x2="1412.9457" + y2="1547.1619" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7919" + id="linearGradient10618" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-7.371756e-3,-8.464087e-2,8.123002e-2,-7.074689e-3,649.2207,776.8758)" + x1="507.37094" + y1="290.88303" + x2="608.41498" + y2="398.94208" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient13837" + id="linearGradient10620" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-3.853465e-2,-0.442446,0.424617,-3.698179e-2,504.5152,1236.551)" + x1="1129.3464" + y1="517.39832" + x2="1087.991" + y2="513.6452" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient10622" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(7.359809e-3,8.450372e-2,-8.176588e-2,7.121361e-3,785.742,601.9738)" + x1="1394.5476" + y1="1587.7018" + x2="1412.9457" + y2="1547.1619" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient10624" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-7.292027e-3,-8.372545e-2,8.161005e-2,-7.107788e-3,557.4547,850.8355)" + x1="1394.5476" + y1="1587.7018" + x2="1412.9457" + y2="1547.1619" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7919" + id="linearGradient10626" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-7.371756e-3,-8.464087e-2,8.123002e-2,-7.074689e-3,635.2375,776.8758)" + x1="507.37094" + y1="290.88303" + x2="608.41498" + y2="398.94208" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient13837" + id="linearGradient10628" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-3.853465e-2,-0.442446,0.424617,-3.698179e-2,490.5319,1236.551)" + x1="1088.9348" + y1="490.38297" + x2="1132.1641" + y2="494.30609" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient10630" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(7.359809e-3,8.450372e-2,-8.176588e-2,7.121361e-3,771.7587,601.9738)" + x1="1394.5476" + y1="1587.7018" + x2="1412.9457" + y2="1547.1619" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient10632" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-7.292027e-3,-8.372545e-2,8.161005e-2,-7.107788e-3,543.4715,850.8355)" + x1="1394.5476" + y1="1587.7018" + x2="1412.9457" + y2="1547.1619" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7919" + id="linearGradient10634" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-7.371756e-3,-8.464087e-2,8.123002e-2,-7.074689e-3,642.3314,764.7906)" + x1="507.37094" + y1="290.88303" + x2="608.41498" + y2="398.94208" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient13837" + id="linearGradient10636" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-3.853465e-2,-0.442446,0.424617,-3.698179e-2,497.6258,1224.465)" + x1="1129.3464" + y1="517.39832" + x2="1087.991" + y2="513.6452" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient10638" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(7.359809e-3,8.450372e-2,-8.176588e-2,7.121361e-3,778.4877,590.208)" + x1="1394.5476" + y1="1587.7018" + x2="1412.9457" + y2="1547.1619" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient10640" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-7.292027e-3,-8.372545e-2,8.161005e-2,-7.107788e-3,550.8847,838.3854)" + x1="1394.5476" + y1="1587.7018" + x2="1412.9457" + y2="1547.1619" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7919" + id="linearGradient10643" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-7.371756e-3,-8.464087e-2,8.123002e-2,-7.074689e-3,649.2207,776.8758)" + x1="507.37094" + y1="290.88303" + x2="608.41498" + y2="398.94208" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient13837" + id="linearGradient10645" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-3.853465e-2,-0.442446,0.424617,-3.698179e-2,504.5152,1236.551)" + x1="1129.3464" + y1="517.39832" + x2="1087.991" + y2="513.6452" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient10648" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(7.359809e-3,8.450372e-2,-8.176588e-2,7.121361e-3,785.742,601.9738)" + x1="1394.5476" + y1="1587.7018" + x2="1412.9457" + y2="1547.1619" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient10650" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-7.292027e-3,-8.372545e-2,8.161005e-2,-7.107788e-3,557.4547,850.8355)" + x1="1394.5476" + y1="1587.7018" + x2="1412.9457" + y2="1547.1619" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7919" + id="linearGradient10653" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-7.371756e-3,-8.464087e-2,8.123002e-2,-7.074689e-3,635.2375,776.8758)" + x1="507.37094" + y1="290.88303" + x2="608.41498" + y2="398.94208" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient13837" + id="linearGradient10656" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-3.853465e-2,-0.442446,0.424617,-3.698179e-2,490.5319,1236.551)" + x1="1088.9348" + y1="490.38297" + x2="1132.1641" + y2="494.30609" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient10658" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(7.359809e-3,8.450372e-2,-8.176588e-2,7.121361e-3,771.7587,601.9738)" + x1="1394.5476" + y1="1587.7018" + x2="1412.9457" + y2="1547.1619" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient10661" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-7.292027e-3,-8.372545e-2,8.161005e-2,-7.107788e-3,543.4715,850.8355)" + x1="1394.5476" + y1="1587.7018" + x2="1412.9457" + y2="1547.1619" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7919" + id="linearGradient10664" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-7.371756e-3,-8.464087e-2,8.123002e-2,-7.074689e-3,642.3314,764.7906)" + x1="507.37094" + y1="290.88303" + x2="608.41498" + y2="398.94208" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient13837" + id="linearGradient10667" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-3.853465e-2,-0.442446,0.424617,-3.698179e-2,497.6258,1224.465)" + x1="1129.3464" + y1="517.39832" + x2="1087.991" + y2="513.6452" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient10669" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(7.359809e-3,8.450372e-2,-8.176588e-2,7.121361e-3,778.4877,590.208)" + x1="1394.5476" + y1="1587.7018" + x2="1412.9457" + y2="1547.1619" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient10671" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-7.292027e-3,-8.372545e-2,8.161005e-2,-7.107788e-3,550.8847,838.3854)" + x1="1394.5476" + y1="1587.7018" + x2="1412.9457" + y2="1547.1619" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient10673" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(4.867991e-3,5.589324e-2,-5.408237e-2,4.710273e-3,702.7956,635.1658)" + x1="1394.5476" + y1="1587.7018" + x2="1412.9457" + y2="1547.1619" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient10849" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.031982,0,0,1.032261,106.9031,-54.06894)" + x1="101.25272" + y1="49.529297" + x2="101.25272" + y2="43.32613" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient10851" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.031982,0,0,1.107109,106.9031,-57.31254)" + x1="122.57088" + y1="59.06163" + x2="122.57088" + y2="43.326801" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4182" + id="linearGradient10853" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.038287,0,0,1.017566,106.2802,-53.32718)" + x1="111.84524" + y1="48.367947" + x2="111.84524" + y2="58.933025" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4166" + id="linearGradient10855" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.217348,0,0,0.215931,36.74451,-44.86859)" + x1="829.4079" + y1="212.95419" + x2="829.4079" + y2="183.47195" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4190" + id="radialGradient10857" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.296464,6.961497e-6,-5.421832e-6,1.090421,77.58478,-60.94513)" + cx="111.70066" + cy="62.211178" + fx="111.70066" + fy="62.211178" + r="8.8008003" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient10859" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.013541,0,0,0.982207,109.0253,-51.62793)" + spreadMethod="reflect" + x1="111.33885" + y1="44.705486" + x2="111.33885" + y2="46.731094" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient10867" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.515861,0,0,1.495984,89.34967,-82.05745)" + x1="101.35825" + y1="47.461205" + x2="101.35667" + y2="43.430031" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient10869" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.515861,0,0,1.604454,89.34967,-86.75818)" + x1="122.46678" + y1="58.964745" + x2="122.46678" + y2="43.42371" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4182" + id="linearGradient10871" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.62008,0,0,1.577605,77.79409,-86.55158)" + x1="111.84524" + y1="48.367947" + x2="111.84524" + y2="58.933025" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4166" + id="linearGradient10873" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.339136,0,0,0.334773,-30.70507,-73.43762)" + x1="829.4079" + y1="212.95419" + x2="829.4079" + y2="183.47195" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4190" + id="radialGradient10875" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(2.044377,1.047261e-5,-8.549617e-6,1.640387,30.62339,-95.37034)" + cx="111.70066" + cy="62.211178" + fx="111.70066" + fy="62.211178" + r="8.8008003" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient10877" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.480783,0,0,1.536549,93.27523,-83.78014)" + spreadMethod="reflect" + x1="111.33885" + y1="44.705486" + x2="111.33885" + y2="46.731094" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient10885" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.031982,0,0,1.032261,106.9031,-54.06894)" + x1="101.25272" + y1="49.529297" + x2="101.25272" + y2="43.32613" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient10887" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.031982,0,0,1.107109,106.9031,-57.31254)" + x1="122.57088" + y1="59.06163" + x2="122.57088" + y2="43.326801" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4182" + id="linearGradient10889" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.038287,0,0,1.017566,106.2802,-53.32718)" + x1="111.84524" + y1="48.367947" + x2="111.84524" + y2="58.933025" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4166" + id="linearGradient10891" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.217348,0,0,0.215931,36.74451,-44.86859)" + x1="829.4079" + y1="212.95419" + x2="829.4079" + y2="183.47195" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4190" + id="radialGradient10893" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.296464,6.961497e-6,-5.421832e-6,1.090421,77.58478,-60.94513)" + cx="111.70066" + cy="62.211178" + fx="111.70066" + fy="62.211178" + r="8.8008003" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient10895" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.013541,0,0,0.982207,109.0253,-51.62793)" + spreadMethod="reflect" + x1="111.33885" + y1="44.705486" + x2="111.33885" + y2="46.731094" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient10903" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.515861,0,0,1.495984,89.34967,-82.05745)" + x1="101.35825" + y1="47.461205" + x2="101.35667" + y2="43.430031" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient10905" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.515861,0,0,1.604454,89.34967,-86.75818)" + x1="122.46678" + y1="58.964745" + x2="122.46678" + y2="43.42371" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4182" + id="linearGradient10907" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.62008,0,0,1.577605,77.79409,-86.55158)" + x1="111.84524" + y1="48.367947" + x2="111.84524" + y2="58.933025" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4166" + id="linearGradient10910" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.339136,0,0,0.334773,-30.70507,-73.43762)" + x1="829.4079" + y1="212.95419" + x2="829.4079" + y2="183.47195" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4190" + id="radialGradient10912" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(2.044377,1.047261e-5,-8.549617e-6,1.640387,30.62339,-95.37034)" + cx="111.70066" + cy="62.211178" + fx="111.70066" + fy="62.211178" + r="8.8008003" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient10914" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.480783,0,0,1.536549,93.27523,-83.78014)" + spreadMethod="reflect" + x1="111.33885" + y1="44.705486" + x2="111.33885" + y2="46.731094" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7184" + id="linearGradient10963" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.6566991,0,0,0.6566991,-238.41741,277.5144)" + x1="861.005" + y1="248.9993" + x2="859.50507" + y2="179.15657" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient11767" + id="linearGradient11939" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.22315,0,0,0.22315,10.41841,7.054327)" + x1="844.37262" + y1="238.3936" + x2="846.35309" + y2="185.10175" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient11957" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(2.9956255,0,0,3.1084402,-9.5196067,254.24692)" + spreadMethod="reflect" + x1="111.33885" + y1="44.705486" + x2="111.33885" + y2="46.731094" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4190" + id="radialGradient11960" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(4.1357767,2.1186101e-5,-1.7295884e-5,3.3185045,-136.26434,230.79994)" + cx="111.70066" + cy="62.211178" + fx="111.70066" + fy="62.211178" + r="8.8008003" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4182" + id="linearGradient11963" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(3.2774235,0,0,3.1914965,-40.837967,248.6403)" + x1="111.84524" + y1="48.367947" + x2="111.84524" + y2="58.933025" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4166" + id="linearGradient11965" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.6860725,0,0,0.6772461,-260.33187,275.16985)" + x1="829.4079" + y1="212.95419" + x2="829.4079" + y2="183.47195" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient11968" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(3.0665883,0,0,3.0263771,-17.461017,257.73193)" + x1="101.35825" + y1="47.461205" + x2="101.35667" + y2="43.430031" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient11971" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(3.0665883,0,0,3.245812,-17.461017,248.22235)" + x1="122.46678" + y1="58.964745" + x2="122.46678" + y2="43.42371" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient11973" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.22305,-6.682459e-3,6.682459e-3,0.22305,9.109984,12.81484)" + x1="839.11597" + y1="183.80774" + x2="837.56323" + y2="235.63457" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7184" + id="linearGradient11998" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.4905767,0,0,0.4905767,-182.31534,322.13908)" + x1="861.005" + y1="248.9993" + x2="859.50507" + y2="179.15657" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient11767" + id="linearGradient12005" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.22315,0,0,0.22315,10.41841,7.054327)" + x1="844.37262" + y1="238.3936" + x2="846.35309" + y2="185.10175" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient12009" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.22305,-6.682459e-3,6.682459e-3,0.22305,9.109984,12.81484)" + x1="839.11597" + y1="183.80774" + x2="837.56323" + y2="235.63457" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7184" + id="linearGradient12035" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.3244598,0,0,0.3244598,-104.99121,358.72783)" + x1="861.005" + y1="248.9993" + x2="859.50507" + y2="179.15657" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient11767" + id="linearGradient12042" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.22315,0,0,0.22315,10.41841,7.054327)" + x1="844.37262" + y1="238.3936" + x2="846.35309" + y2="185.10175" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient12046" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.22305,-6.682459e-3,6.682459e-3,0.22305,9.109984,12.81484)" + x1="839.11597" + y1="183.80774" + x2="837.56323" + y2="235.63457" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7184" + id="linearGradient12070" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.2227816,0,0,0.2227816,-62.024189,386.35243)" + x1="861.005" + y1="248.9993" + x2="859.50507" + y2="179.15657" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient11767" + id="linearGradient12077" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.22315,0,0,0.22315,10.41841,7.054327)" + x1="844.37262" + y1="238.3936" + x2="846.35309" + y2="185.10175" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient12081" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.22305,-6.682459e-3,6.682459e-3,0.22305,9.109984,12.81484)" + x1="839.11597" + y1="183.80774" + x2="837.56323" + y2="235.63457" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7184" + id="linearGradient12106" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.159883,0,0,0.159883,-43.218847,402.71313)" + x1="861.005" + y1="248.9993" + x2="859.50507" + y2="179.15657" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient11767" + id="linearGradient12113" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.22315,0,0,0.22315,10.41841,7.054327)" + x1="844.37262" + y1="238.3936" + x2="846.35309" + y2="185.10175" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient12117" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.22305,-6.682459e-3,6.682459e-3,0.22305,9.109984,12.81484)" + x1="839.11597" + y1="183.80774" + x2="837.56323" + y2="235.63457" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient12244" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.375,0,0,1.469276,179.5471,-10.85991)" + x1="282.96594" + y1="113.52765" + x2="282.96594" + y2="98.532501" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7919" + id="linearGradient12246" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-7.977528e-3,-9.016333e-2,8.790531e-2,-7.536272e-3,555.7662,198.1625)" + x1="507.37094" + y1="290.88303" + x2="608.41498" + y2="398.94208" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient12248" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.216699,0,0,1.197663,-126.7483,-28.54217)" + x1="589.09595" + y1="153.23845" + x2="589.09595" + y2="136.54427" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient21450" + id="linearGradient12250" + gradientUnits="userSpaceOnUse" + x1="1308.2411" + y1="1395.8469" + x2="1308.2411" + y2="1473.3281" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient20611" + id="linearGradient12252" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(5.767204e-3,6.518187e-2,-6.407273e-2,5.493064e-3,667.9806,50.83129)" + x1="1394.5476" + y1="1587.7018" + x2="1412.9457" + y2="1547.1619" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient13829" + id="linearGradient12254" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(3.288458e-2,5.641064e-2,-5.529172e-2,3.311662e-2,618.1039,20.62608)" + x1="1277.4498" + y1="1612.0433" + x2="1406.9330" + y2="1424.1870" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient20611" + id="linearGradient12256" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-5.80699e-3,-6.563155e-2,6.499001e-2,-5.571703e-3,490.7537,240.0609)" + x1="1394.5476" + y1="1587.7018" + x2="1412.9457" + y2="1547.1619" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient13829" + id="linearGradient12258" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-3.331678e-2,-5.678175e-2,5.610206e-2,-3.338171e-2,541.322,270.4442)" + x1="1277.4498" + y1="1612.0433" + x2="1406.9330" + y2="1424.1870" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient12260" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.130153,0,0,1.068982,108.8626,-330.5337)" + x1="415.12271" + y1="435.43039" + x2="415.12271" + y2="453.03961" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10275" + id="linearGradient12262" + gradientUnits="userSpaceOnUse" + x1="1104.4021" + y1="1431.5235" + x2="1115.1294" + y2="1431.5236" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient12264" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.375,0,0,1.469276,179.5471,-10.85991)" + x1="282.96594" + y1="113.52765" + x2="282.96594" + y2="98.532501" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7919" + id="linearGradient12266" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-7.977528e-3,-9.016333e-2,8.790531e-2,-7.536272e-3,555.7662,198.1625)" + x1="507.37094" + y1="290.88303" + x2="608.41498" + y2="398.94208" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient12268" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.216699,0,0,1.197663,-126.7483,-28.54217)" + x1="589.09595" + y1="153.23845" + x2="589.09595" + y2="136.54427" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient21450" + id="linearGradient12270" + gradientUnits="userSpaceOnUse" + x1="1308.2411" + y1="1395.8469" + x2="1308.2411" + y2="1473.3281" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient20611" + id="linearGradient12272" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(5.767204e-3,6.518187e-2,-6.407273e-2,5.493064e-3,667.9806,50.83129)" + x1="1394.5476" + y1="1587.7018" + x2="1412.9457" + y2="1547.1619" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient13829" + id="linearGradient12274" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(3.288458e-2,5.641064e-2,-5.529172e-2,3.311662e-2,618.1039,20.62608)" + x1="1277.4498" + y1="1612.0433" + x2="1406.9330" + y2="1424.1870" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient20611" + id="linearGradient12276" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-5.80699e-3,-6.563155e-2,6.499001e-2,-5.571703e-3,490.7537,240.0609)" + x1="1394.5476" + y1="1587.7018" + x2="1412.9457" + y2="1547.1619" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient13829" + id="linearGradient12278" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-3.331678e-2,-5.678175e-2,5.610206e-2,-3.338171e-2,541.322,270.4442)" + x1="1277.4498" + y1="1612.0433" + x2="1406.9330" + y2="1424.1870" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient12280" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.130153,0,0,1.068982,108.8626,-330.5337)" + x1="415.12271" + y1="435.43039" + x2="415.12271" + y2="453.03961" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10258" + id="linearGradient12282" + gradientUnits="userSpaceOnUse" + x1="1028.4021" + y1="1439.5235" + x2="1036.7737" + y2="1439.5234" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient11767" + id="linearGradient12371" + x1="1145.7107" + y1="1577.8585" + x2="1178.2305" + y2="1577.8585" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(-170.67044,-589.31607)" /> + <filter + inkscape:collect="always" + id="filter12395"> + <feGaussianBlur + inkscape:collect="always" + stdDeviation="0.99193554" + id="feGaussianBlur12397" /> + </filter> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient13392" + id="linearGradient12408" + x1="1145.7107" + y1="1577.8585" + x2="1178.2305" + y2="1577.8585" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(-170.67044,-589.31607)" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient12441" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.375,0,0,1.469276,179.5471,-10.85991)" + x1="282.96594" + y1="113.52765" + x2="282.96594" + y2="98.532501" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7919" + id="linearGradient12443" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-7.977528e-3,-9.016333e-2,8.790531e-2,-7.536272e-3,555.7662,198.1625)" + x1="507.37094" + y1="290.88303" + x2="608.41498" + y2="398.94208" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient12445" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.216699,0,0,1.197663,-126.7483,-28.54217)" + x1="589.09595" + y1="153.23845" + x2="589.09595" + y2="136.54427" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient21450" + id="linearGradient12447" + gradientUnits="userSpaceOnUse" + x1="1308.2411" + y1="1395.8469" + x2="1308.2411" + y2="1473.3281" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient20611" + id="linearGradient12449" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(5.767204e-3,6.518187e-2,-6.407273e-2,5.493064e-3,667.9806,50.83129)" + x1="1394.5476" + y1="1587.7018" + x2="1412.9457" + y2="1547.1619" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient13829" + id="linearGradient12452" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(3.288458e-2,5.641064e-2,-5.529172e-2,3.311662e-2,618.1039,20.62608)" + x1="1277.4498" + y1="1612.0433" + x2="1406.9330" + y2="1424.1870" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient20611" + id="linearGradient12454" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-5.80699e-3,-6.563155e-2,6.499001e-2,-5.571703e-3,490.7537,240.0609)" + x1="1394.5476" + y1="1587.7018" + x2="1412.9457" + y2="1547.1619" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient13829" + id="linearGradient12456" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-3.331678e-2,-5.678175e-2,5.610206e-2,-3.338171e-2,541.322,270.4442)" + x1="1277.4498" + y1="1612.0433" + x2="1406.9330" + y2="1424.1870" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient12458" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.130153,0,0,1.068982,108.8626,-330.5337)" + x1="415.12271" + y1="435.43039" + x2="415.12271" + y2="453.03961" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10275" + id="linearGradient12460" + gradientUnits="userSpaceOnUse" + x1="1104.4021" + y1="1431.5235" + x2="1115.1294" + y2="1431.5236" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient11767" + id="linearGradient12462" + gradientUnits="userSpaceOnUse" + x1="1145.7107" + y1="1577.8585" + x2="1178.2305" + y2="1577.8585" + gradientTransform="matrix(0.7499994,0,0,0.7499994,26.396454,-187.92936)" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient13392" + id="linearGradient12464" + gradientUnits="userSpaceOnUse" + x1="1145.7107" + y1="1577.8585" + x2="1178.2305" + y2="1577.8585" + gradientTransform="matrix(0.7499994,0,0,0.7499994,26.396454,-187.92936)" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient12496" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.375,0,0,1.469276,179.5471,-10.85991)" + x1="282.96594" + y1="113.52765" + x2="282.96594" + y2="98.532501" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7919" + id="linearGradient12498" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-7.977528e-3,-9.016333e-2,8.790531e-2,-7.536272e-3,555.7662,198.1625)" + x1="507.37094" + y1="290.88303" + x2="608.41498" + y2="398.94208" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient12501" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.216699,0,0,1.197663,-126.7483,-28.54217)" + x1="589.09595" + y1="153.23845" + x2="589.09595" + y2="136.54427" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient21450" + id="linearGradient12503" + gradientUnits="userSpaceOnUse" + x1="1308.2411" + y1="1395.8469" + x2="1308.2411" + y2="1473.3281" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient20611" + id="linearGradient12505" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(5.767204e-3,6.518187e-2,-6.407273e-2,5.493064e-3,667.9806,50.83129)" + x1="1394.5476" + y1="1587.7018" + x2="1412.9457" + y2="1547.1619" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient13829" + id="linearGradient12507" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(3.288458e-2,5.641064e-2,-5.529172e-2,3.311662e-2,618.1039,20.62608)" + x1="1277.4498" + y1="1612.0433" + x2="1406.9330" + y2="1424.1870" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient20611" + id="linearGradient12509" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-5.80699e-3,-6.563155e-2,6.499001e-2,-5.571703e-3,490.7537,240.0609)" + x1="1394.5476" + y1="1587.7018" + x2="1412.9457" + y2="1547.1619" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient13829" + id="linearGradient12511" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-3.331678e-2,-5.678175e-2,5.610206e-2,-3.338171e-2,541.322,270.4442)" + x1="1277.4498" + y1="1612.0433" + x2="1406.9330" + y2="1424.1870" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient12513" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.130153,0,0,1.068982,108.8626,-330.5337)" + x1="415.12271" + y1="435.43039" + x2="415.12271" + y2="453.03961" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10275" + id="linearGradient12515" + gradientUnits="userSpaceOnUse" + x1="1104.4021" + y1="1431.5235" + x2="1115.1294" + y2="1431.5236" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient11767" + id="linearGradient12517" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.4999996,0,0,0.4999996,235.65742,214.44017)" + x1="1145.7107" + y1="1577.8585" + x2="1178.2305" + y2="1577.8585" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient13392" + id="linearGradient12519" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.4999996,0,0,0.4999996,235.65742,214.44017)" + x1="1145.7107" + y1="1577.8585" + x2="1178.2305" + y2="1577.8585" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient12551" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.375,0,0,1.469276,179.5471,-10.85991)" + x1="282.96594" + y1="113.52765" + x2="282.96594" + y2="98.532501" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7919" + id="linearGradient12553" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-7.977528e-3,-9.016333e-2,8.790531e-2,-7.536272e-3,555.7662,198.1625)" + x1="507.37094" + y1="290.88303" + x2="608.41498" + y2="398.94208" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient12555" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.216699,0,0,1.197663,-126.7483,-28.54217)" + x1="589.09595" + y1="153.23845" + x2="589.09595" + y2="136.54427" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient21450" + id="linearGradient12557" + gradientUnits="userSpaceOnUse" + x1="1308.2411" + y1="1395.8469" + x2="1308.2411" + y2="1473.3281" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient20611" + id="linearGradient12559" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(5.767204e-3,6.518187e-2,-6.407273e-2,5.493064e-3,667.9806,50.83129)" + x1="1394.5476" + y1="1587.7018" + x2="1412.9457" + y2="1547.1619" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient13829" + id="linearGradient12561" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(3.288458e-2,5.641064e-2,-5.529172e-2,3.311662e-2,618.1039,20.62608)" + x1="1277.4498" + y1="1612.0433" + x2="1406.9330" + y2="1424.1870" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient20611" + id="linearGradient12563" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-5.80699e-3,-6.563155e-2,6.499001e-2,-5.571703e-3,490.7537,240.0609)" + x1="1394.5476" + y1="1587.7018" + x2="1412.9457" + y2="1547.1619" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient13829" + id="linearGradient12565" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-3.331678e-2,-5.678175e-2,5.610206e-2,-3.338171e-2,541.322,270.4442)" + x1="1277.4498" + y1="1612.0433" + x2="1406.9330" + y2="1424.1870" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient12567" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.130153,0,0,1.068982,108.8626,-330.5337)" + x1="415.12271" + y1="435.43039" + x2="415.12271" + y2="453.03961" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10275" + id="linearGradient12569" + gradientUnits="userSpaceOnUse" + x1="1104.4021" + y1="1431.5235" + x2="1115.1294" + y2="1431.5236" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient11767" + id="linearGradient12571" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.3437494,0,0,0.3437495,364.79442,467.88958)" + x1="1145.7107" + y1="1577.8585" + x2="1178.2305" + y2="1577.8585" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient13392" + id="linearGradient12573" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.3437494,0,0,0.3437495,364.79442,467.88958)" + x1="1145.7107" + y1="1577.8585" + x2="1178.2305" + y2="1577.8585" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient12605" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.375,0,0,1.469276,179.5471,-10.85991)" + x1="282.96594" + y1="113.52765" + x2="282.96594" + y2="98.532501" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7919" + id="linearGradient12608" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-7.977528e-3,-9.016333e-2,8.790531e-2,-7.536272e-3,555.7662,198.1625)" + x1="507.37094" + y1="290.88303" + x2="608.41498" + y2="398.94208" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient12610" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.216699,0,0,1.197663,-126.7483,-28.54217)" + x1="589.09595" + y1="153.23845" + x2="589.09595" + y2="136.54427" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient21450" + id="linearGradient12612" + gradientUnits="userSpaceOnUse" + x1="1308.2411" + y1="1395.8469" + x2="1308.2411" + y2="1473.3281" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient20611" + id="linearGradient12614" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(5.767204e-3,6.518187e-2,-6.407273e-2,5.493064e-3,667.9806,50.83129)" + x1="1394.5476" + y1="1587.7018" + x2="1412.9457" + y2="1547.1619" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient13829" + id="linearGradient12616" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(3.288458e-2,5.641064e-2,-5.529172e-2,3.311662e-2,618.1039,20.62608)" + x1="1277.4498" + y1="1612.0433" + x2="1406.9330" + y2="1424.1870" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient20611" + id="linearGradient12618" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-5.80699e-3,-6.563155e-2,6.499001e-2,-5.571703e-3,490.7537,240.0609)" + x1="1394.5476" + y1="1587.7018" + x2="1412.9457" + y2="1547.1619" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient13829" + id="linearGradient12620" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-3.331678e-2,-5.678175e-2,5.610206e-2,-3.338171e-2,541.322,270.4442)" + x1="1277.4498" + y1="1612.0433" + x2="1406.9330" + y2="1424.1870" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient12622" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.130153,0,0,1.068982,108.8626,-330.5337)" + x1="415.12271" + y1="435.43039" + x2="415.12271" + y2="453.03961" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10275" + id="linearGradient12624" + gradientUnits="userSpaceOnUse" + x1="1104.4021" + y1="1431.5235" + x2="1115.1294" + y2="1431.5236" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient11767" + id="linearGradient12626" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.2499998,0,0,0.2499999,435.77213,618.7776)" + x1="1145.7107" + y1="1577.8585" + x2="1178.2305" + y2="1577.8585" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient13392" + id="linearGradient12628" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.2499998,0,0,0.2499999,435.77213,618.7776)" + x1="1145.7107" + y1="1577.8585" + x2="1178.2305" + y2="1577.8585" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4166" + id="linearGradient12712" + gradientUnits="userSpaceOnUse" + x1="364.35117" + y1="443.11044" + x2="363.85608" + y2="428.539" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4182" + id="linearGradient12714" + gradientUnits="userSpaceOnUse" + x1="417.36929" + y1="435.0079" + x2="417.36929" + y2="454.65622" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4190" + id="radialGradient12716" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.726147,-8.054512e-3,1.109145e-2,0.999938,109.5649,3.398898)" + cx="418.51233" + cy="454.95853" + fx="418.51233" + fy="454.95853" + r="11.005301" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient12718" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.89894,0,0,0.89894,-354.0102,-380.2835)" + x1="415.12271" + y1="435.43039" + x2="415.12271" + y2="453.03961" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient13699" + x1="-60.640533" + y1="581.21619" + x2="-60.640533" + y2="609.21283" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(-1.9098366e-6,-1.2502583e-5)" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient13707" + x1="-83.643311" + y1="595.21454" + x2="-37.637753" + y2="595.21454" + gradientUnits="userSpaceOnUse" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4190" + id="linearGradient13713" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.892594,0,0,2.2364637,-904.55241,-963.5926)" + x1="15.432588" + y1="413.62369" + x2="15.627617" + y2="404.25516" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient13715" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(2.150786,0,0,2.1564797,-1761.2163,-99.70718)" + x1="428.77371" + y1="30.368084" + x2="428.77371" + y2="18.649082" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient13719" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-1.3827861,0,0,-1.5084958,890.55201,686.95111)" + x1="25.127533" + y1="417.70789" + x2="25.127533" + y2="389.9552" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient13723" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.142857,0,0,1.2307678,-890.41831,-553.26401)" + x1="50.28577" + y1="385.7197" + x2="50.28577" + y2="424.71976" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient13725" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(2.285714,0,0,2.285714,-1817.8954,-102.55223)" + x1="409.91418" + y1="10.508587" + x2="409.91418" + y2="31.508587" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4166" + id="linearGradient14722" + gradientUnits="userSpaceOnUse" + x1="364.35117" + y1="443.11044" + x2="363.85608" + y2="428.539" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4182" + id="linearGradient14724" + gradientUnits="userSpaceOnUse" + x1="417.36929" + y1="435.0079" + x2="417.36929" + y2="454.65622" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4190" + id="radialGradient14726" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.726147,-8.054512e-3,1.109145e-2,0.999938,109.5649,3.398898)" + cx="418.51233" + cy="454.95853" + fx="418.51233" + fy="454.95853" + r="11.005301" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient14728" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.89894,0,0,0.89894,-354.0102,-380.2835)" + x1="415.12271" + y1="435.43039" + x2="415.12271" + y2="453.03961" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient14730" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(-1.9098364e-6,-1.2502583e-5)" + x1="-60.640533" + y1="581.21619" + x2="-60.640533" + y2="609.21283" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient14732" + gradientUnits="userSpaceOnUse" + x1="-83.643311" + y1="595.21454" + x2="-37.637753" + y2="595.21454" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4166" + id="linearGradient14750" + gradientUnits="userSpaceOnUse" + x1="364.35117" + y1="443.11044" + x2="363.85608" + y2="428.539" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4182" + id="linearGradient14752" + gradientUnits="userSpaceOnUse" + x1="417.36929" + y1="435.0079" + x2="417.36929" + y2="454.65622" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4190" + id="radialGradient14754" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.726147,-8.054512e-3,1.109145e-2,0.999938,109.5649,3.398898)" + cx="418.51233" + cy="454.95853" + fx="418.51233" + fy="454.95853" + r="11.005301" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient14756" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.89894,0,0,0.89894,-354.0102,-380.2835)" + x1="415.12271" + y1="435.43039" + x2="415.12271" + y2="453.03961" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient14758" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(-1.9098366e-6,-1.2502583e-5)" + x1="-60.640533" + y1="581.21619" + x2="-60.640533" + y2="609.21283" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient14760" + gradientUnits="userSpaceOnUse" + x1="-83.643311" + y1="595.21454" + x2="-37.637753" + y2="595.21454" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4166" + id="linearGradient14778" + gradientUnits="userSpaceOnUse" + x1="364.35117" + y1="443.11044" + x2="363.85608" + y2="428.539" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4182" + id="linearGradient14780" + gradientUnits="userSpaceOnUse" + x1="417.36929" + y1="435.0079" + x2="417.36929" + y2="454.65622" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4190" + id="radialGradient14782" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.726147,-8.054512e-3,1.109145e-2,0.999938,109.5649,3.398898)" + cx="418.51233" + cy="454.95853" + fx="418.51233" + fy="454.95853" + r="11.005301" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient14784" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.89894,0,0,0.89894,-354.0102,-380.2835)" + x1="415.12271" + y1="435.43039" + x2="415.12271" + y2="453.03961" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient14786" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(-1.9098366e-6,-1.2502583e-5)" + x1="-60.640533" + y1="581.21619" + x2="-60.640533" + y2="609.21283" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient14788" + gradientUnits="userSpaceOnUse" + x1="-83.643311" + y1="595.21454" + x2="-37.637753" + y2="595.21454" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4166" + id="linearGradient14806" + gradientUnits="userSpaceOnUse" + x1="364.35117" + y1="443.11044" + x2="363.85608" + y2="428.539" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4182" + id="linearGradient14808" + gradientUnits="userSpaceOnUse" + x1="417.36929" + y1="435.0079" + x2="417.36929" + y2="454.65622" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4190" + id="radialGradient14810" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.726147,-8.054512e-3,1.109145e-2,0.999938,109.5649,3.398898)" + cx="418.51233" + cy="454.95853" + fx="418.51233" + fy="454.95853" + r="11.005301" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient14812" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.89894,0,0,0.89894,-354.0102,-380.2835)" + x1="415.12271" + y1="435.43039" + x2="415.12271" + y2="453.03961" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient14814" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(-1.9098367e-6,-1.2502583e-5)" + x1="-60.640533" + y1="581.21619" + x2="-60.640533" + y2="609.21283" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient14816" + gradientUnits="userSpaceOnUse" + x1="-83.643311" + y1="595.21454" + x2="-37.637753" + y2="595.21454" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient13853" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(104.66897,-7.7976446)" + x1="1423.5" + y1="523.86218" + x2="1423.5" + y2="546.86218" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient13855" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(104.66897,-7.7976446)" + x1="1423.5" + y1="523.86218" + x2="1423.5" + y2="546.86218" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient13857" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(108.66897,-7.7976446)" + x1="1396.5" + y1="523.86218" + x2="1396.5" + y2="546.86218" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient13859" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(108.66897,-7.7976446)" + x1="1396.5" + y1="523.86218" + x2="1396.5" + y2="546.86218" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient13861" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(107.50003,-7.7976446)" + x1="1409.5" + y1="507.36218" + x2="1409.5" + y2="540.36218" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient13863" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(107.50003,-7.7976446)" + x1="1409.5" + y1="507.36218" + x2="1409.5" + y2="540.36218" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient13837" + id="linearGradient13865" + gradientUnits="userSpaceOnUse" + x1="417.36929" + y1="435.0079" + x2="417.36929" + y2="454.65622" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4190" + id="radialGradient13867" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.726147,-8.054512e-3,1.109145e-2,0.999938,109.5649,3.398898)" + cx="418.51233" + cy="454.95853" + fx="418.51233" + fy="454.95853" + r="11.005301" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient13869" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.018402,0,0,1.177105,-403.8095,-502.6985)" + x1="415.12271" + y1="435.43039" + x2="415.12271" + y2="453.03961" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient13837" + id="linearGradient13887" + gradientUnits="userSpaceOnUse" + x1="417.36929" + y1="435.0079" + x2="417.36929" + y2="454.65622" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4190" + id="radialGradient13889" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.726147,-8.054512e-3,1.109145e-2,0.999938,109.5649,3.398898)" + cx="418.51233" + cy="454.95853" + fx="418.51233" + fy="454.95853" + r="11.005301" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient13891" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.018402,0,0,1.177105,-403.8095,-502.6985)" + x1="415.12271" + y1="435.43039" + x2="415.12271" + y2="453.03961" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient13893" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(104.66897,-7.7976446)" + x1="1423.5" + y1="523.86218" + x2="1423.5" + y2="546.86218" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient13895" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(104.66897,-7.7976446)" + x1="1423.5" + y1="523.86218" + x2="1423.5" + y2="546.86218" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient13897" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(108.66897,-7.7976446)" + x1="1396.5" + y1="523.86218" + x2="1396.5" + y2="546.86218" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient13899" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(108.66897,-7.7976446)" + x1="1396.5" + y1="523.86218" + x2="1396.5" + y2="546.86218" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient13901" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(107.50003,-7.7976446)" + x1="1409.5" + y1="507.36218" + x2="1409.5" + y2="540.36218" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient13903" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(107.50003,-7.7976446)" + x1="1409.5" + y1="507.36218" + x2="1409.5" + y2="540.36218" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient13837" + id="linearGradient13921" + gradientUnits="userSpaceOnUse" + x1="417.36929" + y1="435.0079" + x2="417.36929" + y2="454.65622" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4190" + id="radialGradient13923" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.726147,-8.054512e-3,1.109145e-2,0.999938,109.5649,3.398898)" + cx="418.51233" + cy="454.95853" + fx="418.51233" + fy="454.95853" + r="11.005301" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient13925" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.018402,0,0,1.177105,-403.8095,-502.6985)" + x1="415.12271" + y1="435.43039" + x2="415.12271" + y2="453.03961" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient13927" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(104.66897,-7.7976446)" + x1="1423.5" + y1="523.86218" + x2="1423.5" + y2="546.86218" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient13929" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(104.66897,-7.7976446)" + x1="1423.5" + y1="523.86218" + x2="1423.5" + y2="546.86218" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient13931" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(108.66897,-7.7976446)" + x1="1396.5" + y1="523.86218" + x2="1396.5" + y2="546.86218" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient13933" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(108.66897,-7.7976446)" + x1="1396.5" + y1="523.86218" + x2="1396.5" + y2="546.86218" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient13935" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(107.50003,-7.7976446)" + x1="1409.5" + y1="507.36218" + x2="1409.5" + y2="540.36218" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient13937" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(107.50003,-7.7976446)" + x1="1409.5" + y1="507.36218" + x2="1409.5" + y2="540.36218" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient13837" + id="linearGradient13955" + gradientUnits="userSpaceOnUse" + x1="417.36929" + y1="435.0079" + x2="417.36929" + y2="454.65622" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4190" + id="radialGradient13957" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.726147,-8.054512e-3,1.109145e-2,0.999938,109.5649,3.398898)" + cx="418.51233" + cy="454.95853" + fx="418.51233" + fy="454.95853" + r="11.005301" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient13959" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.018402,0,0,1.177105,-403.8095,-502.6985)" + x1="415.12271" + y1="435.43039" + x2="415.12271" + y2="453.03961" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient13961" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(104.66897,-7.7976446)" + x1="1423.5" + y1="523.86218" + x2="1423.5" + y2="546.86218" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient13963" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(104.66897,-7.7976446)" + x1="1423.5" + y1="523.86218" + x2="1423.5" + y2="546.86218" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient13965" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(108.66897,-7.7976446)" + x1="1396.5" + y1="523.86218" + x2="1396.5" + y2="546.86218" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient13967" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(108.66897,-7.7976446)" + x1="1396.5" + y1="523.86218" + x2="1396.5" + y2="546.86218" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient13969" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(107.50003,-7.7976446)" + x1="1409.5" + y1="507.36218" + x2="1409.5" + y2="540.36218" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient13971" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(107.50003,-7.7976446)" + x1="1409.5" + y1="507.36218" + x2="1409.5" + y2="540.36218" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient13837" + id="linearGradient13990" + gradientUnits="userSpaceOnUse" + x1="417.36929" + y1="435.0079" + x2="417.36929" + y2="454.65622" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4190" + id="radialGradient13992" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.726147,-8.054512e-3,1.109145e-2,0.999938,109.5649,3.398898)" + cx="418.51233" + cy="454.95853" + fx="418.51233" + fy="454.95853" + r="11.005301" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient13994" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.018402,0,0,1.177105,-403.8095,-502.6985)" + x1="415.12271" + y1="435.43039" + x2="415.12271" + y2="453.03961" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient13996" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(104.66897,-7.7976446)" + x1="1423.5" + y1="523.86218" + x2="1423.5" + y2="546.86218" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient13998" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(104.66897,-7.7976446)" + x1="1423.5" + y1="523.86218" + x2="1423.5" + y2="546.86218" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient14000" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(108.66897,-7.7976446)" + x1="1396.5" + y1="523.86218" + x2="1396.5" + y2="546.86218" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient14002" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(108.66897,-7.7976446)" + x1="1396.5" + y1="523.86218" + x2="1396.5" + y2="546.86218" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient14004" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(107.50003,-7.7976446)" + x1="1409.5" + y1="507.36218" + x2="1409.5" + y2="540.36218" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient14006" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(107.50003,-7.7976446)" + x1="1409.5" + y1="507.36218" + x2="1409.5" + y2="540.36218" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient14059" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.2456813,0,0,0.2456813,-63.311359,-296.98845)" + x1="1462.0935" + y1="1816.7543" + x2="1462.0935" + y2="1632.0582" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient14061" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.2456813,0,0,0.2456813,-63.311359,-296.98845)" + x1="1610.3101" + y1="1816.7543" + x2="1610.3101" + y2="1632.0582" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient14069" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(2.9956255,0,0,3.1084402,-20.312277,-34.753101)" + spreadMethod="reflect" + x1="111.33885" + y1="44.705486" + x2="111.33885" + y2="46.731094" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4190" + id="radialGradient14072" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(4.1357767,2.1186101e-5,-1.7295884e-5,3.3185045,-147.05693,-58.200081)" + cx="111.70066" + cy="62.211178" + fx="111.70066" + fy="62.211178" + r="8.8008003" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4182" + id="linearGradient14075" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(3.2774235,0,0,3.1914965,-51.630557,-40.359841)" + x1="111.84524" + y1="48.367947" + x2="111.84524" + y2="58.933025" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4166" + id="linearGradient14077" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.6860725,0,0,0.6772461,-271.12455,-13.830211)" + x1="829.4079" + y1="212.95419" + x2="829.4079" + y2="183.47195" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient14080" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(3.0665883,0,0,3.0263771,-28.253767,-31.268071)" + x1="101.35825" + y1="47.461205" + x2="101.35667" + y2="43.430031" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient14082" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(3.0665883,0,0,3.245812,-28.253767,-40.777801)" + x1="122.46678" + y1="58.964745" + x2="122.46678" + y2="43.42371" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4166" + id="linearGradient11052" + gradientUnits="userSpaceOnUse" + x1="364.35117" + y1="443.11044" + x2="363.85608" + y2="428.539" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4182" + id="linearGradient11054" + gradientUnits="userSpaceOnUse" + x1="417.36929" + y1="435.0079" + x2="417.36929" + y2="454.65622" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4190" + id="radialGradient11056" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.726147,-8.054512e-3,1.109145e-2,0.999938,109.5649,3.398898)" + cx="418.51233" + cy="454.95853" + fx="418.51233" + fy="454.95853" + r="11.005301" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient11058" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.89894,0,0,0.89894,-354.0102,-380.2835)" + x1="415.12271" + y1="435.43039" + x2="415.12271" + y2="453.03961" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4166" + id="linearGradient11070" + gradientUnits="userSpaceOnUse" + x1="364.35117" + y1="443.11044" + x2="363.85608" + y2="428.539" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4182" + id="linearGradient11072" + gradientUnits="userSpaceOnUse" + x1="417.36929" + y1="435.0079" + x2="417.36929" + y2="454.65622" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4190" + id="radialGradient11074" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.726147,-8.054512e-3,1.109145e-2,0.999938,109.5649,3.398898)" + cx="418.51233" + cy="454.95853" + fx="418.51233" + fy="454.95853" + r="11.005301" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient11076" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.89894,0,0,0.89894,-354.0102,-380.2835)" + x1="415.12271" + y1="435.43039" + x2="415.12271" + y2="453.03961" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4166" + id="linearGradient11088" + gradientUnits="userSpaceOnUse" + x1="364.35117" + y1="443.11044" + x2="363.85608" + y2="428.539" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4182" + id="linearGradient11090" + gradientUnits="userSpaceOnUse" + x1="417.36929" + y1="435.0079" + x2="417.36929" + y2="454.65622" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4190" + id="radialGradient11092" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.726147,-8.054512e-3,1.109145e-2,0.999938,109.5649,3.398898)" + cx="418.51233" + cy="454.95853" + fx="418.51233" + fy="454.95853" + r="11.005301" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient11094" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.89894,0,0,0.89894,-354.0102,-380.2835)" + x1="415.12271" + y1="435.43039" + x2="415.12271" + y2="453.03961" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4166" + id="linearGradient11106" + gradientUnits="userSpaceOnUse" + x1="364.35117" + y1="443.11044" + x2="363.85608" + y2="428.539" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4182" + id="linearGradient11108" + gradientUnits="userSpaceOnUse" + x1="417.36929" + y1="435.0079" + x2="417.36929" + y2="454.65622" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4190" + id="radialGradient11110" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.726147,-8.054512e-3,1.109145e-2,0.999938,109.5649,3.398898)" + cx="418.51233" + cy="454.95853" + fx="418.51233" + fy="454.95853" + r="11.005301" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient11112" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.89894,0,0,0.89894,-354.0102,-380.2835)" + x1="415.12271" + y1="435.43039" + x2="415.12271" + y2="453.03961" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4166" + id="linearGradient11124" + gradientUnits="userSpaceOnUse" + x1="364.35117" + y1="443.11044" + x2="363.85608" + y2="428.539" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4182" + id="linearGradient11126" + gradientUnits="userSpaceOnUse" + x1="417.36929" + y1="435.0079" + x2="417.36929" + y2="454.65622" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4190" + id="radialGradient11128" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.726147,-8.054512e-3,1.109145e-2,0.999938,109.5649,3.398898)" + cx="418.51233" + cy="454.95853" + fx="418.51233" + fy="454.95853" + r="11.005301" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient11130" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.89894,0,0,0.89894,-354.0102,-380.2835)" + x1="415.12271" + y1="435.43039" + x2="415.12271" + y2="453.03961" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4166" + id="linearGradient11143" + gradientUnits="userSpaceOnUse" + x1="364.35117" + y1="443.11044" + x2="363.85608" + y2="428.539" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4182" + id="linearGradient11145" + gradientUnits="userSpaceOnUse" + x1="417.36929" + y1="435.0079" + x2="417.36929" + y2="454.65622" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4190" + id="radialGradient11147" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.726147,-8.054512e-3,1.109145e-2,0.999938,109.5649,3.398898)" + cx="418.51233" + cy="454.95853" + fx="418.51233" + fy="454.95853" + r="11.005301" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient11149" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.89894,0,0,0.89894,-354.0102,-380.2835)" + x1="415.12271" + y1="435.43039" + x2="415.12271" + y2="453.03961" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4166" + id="linearGradient11161" + gradientUnits="userSpaceOnUse" + x1="364.35117" + y1="443.11044" + x2="363.85608" + y2="428.539" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4182" + id="linearGradient11163" + gradientUnits="userSpaceOnUse" + x1="417.36929" + y1="435.0079" + x2="417.36929" + y2="454.65622" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4190" + id="radialGradient11165" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.726147,-8.054512e-3,1.109145e-2,0.999938,109.5649,3.398898)" + cx="418.51233" + cy="454.95853" + fx="418.51233" + fy="454.95853" + r="11.005301" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient11167" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.89894,0,0,0.89894,-354.0102,-380.2835)" + x1="415.12271" + y1="435.43039" + x2="415.12271" + y2="453.03961" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4166" + id="linearGradient11179" + gradientUnits="userSpaceOnUse" + x1="364.35117" + y1="443.11044" + x2="363.85608" + y2="428.539" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4182" + id="linearGradient11181" + gradientUnits="userSpaceOnUse" + x1="417.36929" + y1="435.0079" + x2="417.36929" + y2="454.65622" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4190" + id="radialGradient11183" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.726147,-8.054512e-3,1.109145e-2,0.999938,109.5649,3.398898)" + cx="418.51233" + cy="454.95853" + fx="418.51233" + fy="454.95853" + r="11.005301" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient11185" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.89894,0,0,0.89894,-354.0102,-380.2835)" + x1="415.12271" + y1="435.43039" + x2="415.12271" + y2="453.03961" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4166" + id="linearGradient11197" + gradientUnits="userSpaceOnUse" + x1="364.35117" + y1="443.11044" + x2="363.85608" + y2="428.539" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4182" + id="linearGradient11199" + gradientUnits="userSpaceOnUse" + x1="417.36929" + y1="435.0079" + x2="417.36929" + y2="454.65622" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4190" + id="radialGradient11201" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.726147,-8.054512e-3,1.109145e-2,0.999938,109.5649,3.398898)" + cx="418.51233" + cy="454.95853" + fx="418.51233" + fy="454.95853" + r="11.005301" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient11203" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.89894,0,0,0.89894,-354.0102,-380.2835)" + x1="415.12271" + y1="435.43039" + x2="415.12271" + y2="453.03961" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4166" + id="linearGradient11215" + gradientUnits="userSpaceOnUse" + x1="364.35117" + y1="443.11044" + x2="363.85608" + y2="428.539" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4182" + id="linearGradient11217" + gradientUnits="userSpaceOnUse" + x1="417.36929" + y1="435.0079" + x2="417.36929" + y2="454.65622" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4190" + id="radialGradient11219" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.726147,-8.054512e-3,1.109145e-2,0.999938,109.5649,3.398898)" + cx="418.51233" + cy="454.95853" + fx="418.51233" + fy="454.95853" + r="11.005301" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient11221" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.89894,0,0,0.89894,-354.0102,-380.2835)" + x1="415.12271" + y1="435.43039" + x2="415.12271" + y2="453.03961" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4166" + id="linearGradient11233" + gradientUnits="userSpaceOnUse" + x1="364.35117" + y1="443.11044" + x2="363.85608" + y2="428.539" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4182" + id="linearGradient11235" + gradientUnits="userSpaceOnUse" + x1="417.36929" + y1="435.0079" + x2="417.36929" + y2="454.65622" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4190" + id="radialGradient11237" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.726147,-8.054512e-3,1.109145e-2,0.999938,109.5649,3.398898)" + cx="418.51233" + cy="454.95853" + fx="418.51233" + fy="454.95853" + r="11.005301" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient11239" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.89894,0,0,0.89894,-354.0102,-380.2835)" + x1="415.12271" + y1="435.43039" + x2="415.12271" + y2="453.03961" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4166" + id="linearGradient11251" + gradientUnits="userSpaceOnUse" + x1="364.35117" + y1="443.11044" + x2="363.85608" + y2="428.539" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4182" + id="linearGradient11253" + gradientUnits="userSpaceOnUse" + x1="417.36929" + y1="435.0079" + x2="417.36929" + y2="454.65622" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4190" + id="radialGradient11255" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.726147,-8.054512e-3,1.109145e-2,0.999938,109.5649,3.398898)" + cx="418.51233" + cy="454.95853" + fx="418.51233" + fy="454.95853" + r="11.005301" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient11257" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.89894,0,0,0.89894,-354.0102,-380.2835)" + x1="415.12271" + y1="435.43039" + x2="415.12271" + y2="453.03961" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4166" + id="linearGradient11269" + gradientUnits="userSpaceOnUse" + x1="364.35117" + y1="443.11044" + x2="363.85608" + y2="428.539" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4182" + id="linearGradient11271" + gradientUnits="userSpaceOnUse" + x1="417.36929" + y1="435.0079" + x2="417.36929" + y2="454.65622" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4190" + id="radialGradient11273" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.726147,-8.054512e-3,1.109145e-2,0.999938,109.5649,3.398898)" + cx="418.51233" + cy="454.95853" + fx="418.51233" + fy="454.95853" + r="11.005301" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient11275" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.89894,0,0,0.89894,-354.0102,-380.2835)" + x1="415.12271" + y1="435.43039" + x2="415.12271" + y2="453.03961" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4166" + id="linearGradient11287" + gradientUnits="userSpaceOnUse" + x1="364.35117" + y1="443.11044" + x2="363.85608" + y2="428.539" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4182" + id="linearGradient11289" + gradientUnits="userSpaceOnUse" + x1="417.36929" + y1="435.0079" + x2="417.36929" + y2="454.65622" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4190" + id="radialGradient11291" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.726147,-8.054512e-3,1.109145e-2,0.999938,109.5649,3.398898)" + cx="418.51233" + cy="454.95853" + fx="418.51233" + fy="454.95853" + r="11.005301" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient11293" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.89894,0,0,0.89894,-354.0102,-380.2835)" + x1="415.12271" + y1="435.43039" + x2="415.12271" + y2="453.03961" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4166" + id="linearGradient11305" + gradientUnits="userSpaceOnUse" + x1="364.35117" + y1="443.11044" + x2="363.85608" + y2="428.539" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4182" + id="linearGradient11307" + gradientUnits="userSpaceOnUse" + x1="417.36929" + y1="435.0079" + x2="417.36929" + y2="454.65622" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4190" + id="radialGradient11309" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.726147,-8.054512e-3,1.109145e-2,0.999938,109.5649,3.398898)" + cx="418.51233" + cy="454.95853" + fx="418.51233" + fy="454.95853" + r="11.005301" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient11311" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.89894,0,0,0.89894,-354.0102,-380.2835)" + x1="415.12271" + y1="435.43039" + x2="415.12271" + y2="453.03961" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4166" + id="linearGradient11323" + gradientUnits="userSpaceOnUse" + x1="364.35117" + y1="443.11044" + x2="363.85608" + y2="428.539" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4182" + id="linearGradient11325" + gradientUnits="userSpaceOnUse" + x1="417.36929" + y1="435.0079" + x2="417.36929" + y2="454.65622" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4190" + id="radialGradient11327" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.726147,-8.054512e-3,1.109145e-2,0.999938,109.5649,3.398898)" + cx="418.51233" + cy="454.95853" + fx="418.51233" + fy="454.95853" + r="11.005301" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient11329" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.89894,0,0,0.89894,-354.0102,-380.2835)" + x1="415.12271" + y1="435.43039" + x2="415.12271" + y2="453.03961" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4166" + id="linearGradient11342" + gradientUnits="userSpaceOnUse" + x1="364.35117" + y1="443.11044" + x2="363.85608" + y2="428.539" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4182" + id="linearGradient11344" + gradientUnits="userSpaceOnUse" + x1="417.36929" + y1="435.0079" + x2="417.36929" + y2="454.65622" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4190" + id="radialGradient11346" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.726147,-8.054512e-3,1.109145e-2,0.999938,109.5649,3.398898)" + cx="418.51233" + cy="454.95853" + fx="418.51233" + fy="454.95853" + r="11.005301" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient11348" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.89894,0,0,0.89894,-354.0102,-380.2835)" + x1="415.12271" + y1="435.43039" + x2="415.12271" + y2="453.03961" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4166" + id="linearGradient11360" + gradientUnits="userSpaceOnUse" + x1="364.35117" + y1="443.11044" + x2="363.85608" + y2="428.539" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4182" + id="linearGradient11362" + gradientUnits="userSpaceOnUse" + x1="417.36929" + y1="435.0079" + x2="417.36929" + y2="454.65622" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4190" + id="radialGradient11364" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.726147,-8.054512e-3,1.109145e-2,0.999938,109.5649,3.398898)" + cx="418.51233" + cy="454.95853" + fx="418.51233" + fy="454.95853" + r="11.005301" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient11366" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.89894,0,0,0.89894,-354.0102,-380.2835)" + x1="415.12271" + y1="435.43039" + x2="415.12271" + y2="453.03961" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4166" + id="linearGradient11399" + gradientUnits="userSpaceOnUse" + x1="364.35117" + y1="443.11044" + x2="363.85608" + y2="428.539" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4182" + id="linearGradient11401" + gradientUnits="userSpaceOnUse" + x1="417.36929" + y1="435.0079" + x2="417.36929" + y2="454.65622" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4190" + id="radialGradient11403" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.726147,-8.054512e-3,1.109145e-2,0.999938,109.5649,3.398898)" + cx="418.51233" + cy="454.95853" + fx="418.51233" + fy="454.95853" + r="11.005301" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient11405" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.89894,0,0,0.89894,-354.0102,-380.2835)" + x1="415.12271" + y1="435.43039" + x2="415.12271" + y2="453.03961" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4166" + id="linearGradient11417" + gradientUnits="userSpaceOnUse" + x1="364.35117" + y1="443.11044" + x2="363.85608" + y2="428.539" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4182" + id="linearGradient11419" + gradientUnits="userSpaceOnUse" + x1="417.36929" + y1="435.0079" + x2="417.36929" + y2="454.65622" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4190" + id="radialGradient11421" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.726147,-8.054512e-3,1.109145e-2,0.999938,109.5649,3.398898)" + cx="418.51233" + cy="454.95853" + fx="418.51233" + fy="454.95853" + r="11.005301" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient11436" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.89894,0,0,0.89894,-354.0102,-380.2835)" + x1="415.12271" + y1="435.43039" + x2="415.12271" + y2="453.03961" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4166" + id="linearGradient11449" + gradientUnits="userSpaceOnUse" + x1="364.35117" + y1="443.11044" + x2="363.85608" + y2="428.539" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4182" + id="linearGradient11451" + gradientUnits="userSpaceOnUse" + x1="417.36929" + y1="435.0079" + x2="417.36929" + y2="454.65622" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4190" + id="radialGradient11453" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.726147,-8.054512e-3,1.109145e-2,0.999938,109.5649,3.398898)" + cx="418.51233" + cy="454.95853" + fx="418.51233" + fy="454.95853" + r="11.005301" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient11455" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.89894,0,0,0.89894,-354.0102,-380.2835)" + x1="415.12271" + y1="435.43039" + x2="415.12271" + y2="453.03961" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4166" + id="linearGradient11467" + gradientUnits="userSpaceOnUse" + x1="364.35117" + y1="443.11044" + x2="363.85608" + y2="428.539" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4182" + id="linearGradient11469" + gradientUnits="userSpaceOnUse" + x1="417.36929" + y1="435.0079" + x2="417.36929" + y2="454.65622" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4190" + id="radialGradient11471" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.726147,-8.054512e-3,1.109145e-2,0.999938,109.5649,3.398898)" + cx="418.51233" + cy="454.95853" + fx="418.51233" + fy="454.95853" + r="11.005301" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient11473" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.89894,0,0,0.89894,-354.0102,-380.2835)" + x1="415.12271" + y1="435.43039" + x2="415.12271" + y2="453.03961" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4166" + id="linearGradient11487" + gradientUnits="userSpaceOnUse" + x1="364.35117" + y1="443.11044" + x2="363.85608" + y2="428.539" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4182" + id="linearGradient11489" + gradientUnits="userSpaceOnUse" + x1="417.36929" + y1="435.0079" + x2="417.36929" + y2="454.65622" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4190" + id="radialGradient11491" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.726147,-8.054512e-3,1.109145e-2,0.999938,109.5649,3.398898)" + cx="418.51233" + cy="454.95853" + fx="418.51233" + fy="454.95853" + r="11.005301" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient11493" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.89894,0,0,0.89894,-354.0102,-380.2835)" + x1="415.12271" + y1="435.43039" + x2="415.12271" + y2="453.03961" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4166" + id="linearGradient11506" + gradientUnits="userSpaceOnUse" + x1="364.35117" + y1="443.11044" + x2="363.85608" + y2="428.539" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4182" + id="linearGradient11508" + gradientUnits="userSpaceOnUse" + x1="417.36929" + y1="435.0079" + x2="417.36929" + y2="454.65622" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4190" + id="radialGradient11510" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.726147,-8.054512e-3,1.109145e-2,0.999938,109.5649,3.398898)" + cx="418.51233" + cy="454.95853" + fx="418.51233" + fy="454.95853" + r="11.005301" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient11512" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.89894,0,0,0.89894,-354.0102,-380.2835)" + x1="415.12271" + y1="435.43039" + x2="415.12271" + y2="453.03961" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4166" + id="linearGradient11524" + gradientUnits="userSpaceOnUse" + x1="364.35117" + y1="443.11044" + x2="363.85608" + y2="428.539" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4182" + id="linearGradient11526" + gradientUnits="userSpaceOnUse" + x1="417.36929" + y1="435.0079" + x2="417.36929" + y2="454.65622" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4190" + id="radialGradient11528" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.726147,-8.054512e-3,1.109145e-2,0.999938,109.5649,3.398898)" + cx="418.51233" + cy="454.95853" + fx="418.51233" + fy="454.95853" + r="11.005301" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient11530" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.89894,0,0,0.89894,-354.0102,-380.2835)" + x1="415.12271" + y1="435.43039" + x2="415.12271" + y2="453.03961" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4166" + id="linearGradient11542" + gradientUnits="userSpaceOnUse" + x1="364.35117" + y1="443.11044" + x2="363.85608" + y2="428.539" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4182" + id="linearGradient11544" + gradientUnits="userSpaceOnUse" + x1="417.36929" + y1="435.0079" + x2="417.36929" + y2="454.65622" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4190" + id="radialGradient11546" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.726147,-8.054512e-3,1.109145e-2,0.999938,109.5649,3.398898)" + cx="418.51233" + cy="454.95853" + fx="418.51233" + fy="454.95853" + r="11.005301" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient11548" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.89894,0,0,0.89894,-354.0102,-380.2835)" + x1="415.12271" + y1="435.43039" + x2="415.12271" + y2="453.03961" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4166" + id="linearGradient11560" + gradientUnits="userSpaceOnUse" + x1="364.35117" + y1="443.11044" + x2="363.85608" + y2="428.539" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4182" + id="linearGradient11562" + gradientUnits="userSpaceOnUse" + x1="417.36929" + y1="435.0079" + x2="417.36929" + y2="454.65622" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4190" + id="radialGradient11564" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.726147,-8.054512e-3,1.109145e-2,0.999938,109.5649,3.398898)" + cx="418.51233" + cy="454.95853" + fx="418.51233" + fy="454.95853" + r="11.005301" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient11566" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.89894,0,0,0.89894,-354.7664,-364.9796)" + x1="415.12271" + y1="435.43039" + x2="415.12271" + y2="453.03961" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4166" + id="linearGradient11578" + gradientUnits="userSpaceOnUse" + x1="364.35117" + y1="443.11044" + x2="363.85608" + y2="428.539" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4182" + id="linearGradient11580" + gradientUnits="userSpaceOnUse" + x1="417.36929" + y1="435.0079" + x2="417.36929" + y2="454.65622" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4190" + id="radialGradient11582" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.726147,-8.054512e-3,1.109145e-2,0.999938,109.5649,3.398898)" + cx="418.51233" + cy="454.95853" + fx="418.51233" + fy="454.95853" + r="11.005301" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient11584" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.89894,0,0,0.89894,-354.7664,-364.9796)" + x1="415.12271" + y1="435.43039" + x2="415.12271" + y2="453.03961" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4166" + id="linearGradient11596" + gradientUnits="userSpaceOnUse" + x1="364.35117" + y1="443.11044" + x2="363.85608" + y2="428.539" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4182" + id="linearGradient11598" + gradientUnits="userSpaceOnUse" + x1="417.36929" + y1="435.0079" + x2="417.36929" + y2="454.65622" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4190" + id="radialGradient11600" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.726147,-8.054512e-3,1.109145e-2,0.999938,109.5649,3.398898)" + cx="418.51233" + cy="454.95853" + fx="418.51233" + fy="454.95853" + r="11.005301" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient11602" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.89894,0,0,0.89894,-354.7664,-364.9796)" + x1="415.12271" + y1="435.43039" + x2="415.12271" + y2="453.03961" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4166" + id="linearGradient11614" + gradientUnits="userSpaceOnUse" + x1="364.35117" + y1="443.11044" + x2="363.85608" + y2="428.539" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4182" + id="linearGradient11616" + gradientUnits="userSpaceOnUse" + x1="417.36929" + y1="435.0079" + x2="417.36929" + y2="454.65622" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4190" + id="radialGradient11618" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.726147,-8.054512e-3,1.109145e-2,0.999938,109.5649,3.398898)" + cx="418.51233" + cy="454.95853" + fx="418.51233" + fy="454.95853" + r="11.005301" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient11620" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.89894,0,0,0.89894,-354.7664,-364.9796)" + x1="415.12271" + y1="435.43039" + x2="415.12271" + y2="453.03961" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4166" + id="linearGradient11632" + gradientUnits="userSpaceOnUse" + x1="364.35117" + y1="443.11044" + x2="363.85608" + y2="428.539" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4182" + id="linearGradient11634" + gradientUnits="userSpaceOnUse" + x1="417.36929" + y1="435.0079" + x2="417.36929" + y2="454.65622" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4190" + id="radialGradient11636" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.726147,-8.054512e-3,1.109145e-2,0.999938,109.5649,3.398898)" + cx="418.51233" + cy="454.95853" + fx="418.51233" + fy="454.95853" + r="11.005301" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient11638" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.89894,0,0,0.89894,-354.7664,-364.9796)" + x1="415.12271" + y1="435.43039" + x2="415.12271" + y2="453.03961" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4166" + id="linearGradient11653" + gradientUnits="userSpaceOnUse" + x1="364.35117" + y1="443.11044" + x2="363.85608" + y2="428.539" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4182" + id="linearGradient11655" + gradientUnits="userSpaceOnUse" + x1="417.36929" + y1="435.0079" + x2="417.36929" + y2="454.65622" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4190" + id="radialGradient11657" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.726147,-8.054512e-3,1.109145e-2,0.999938,109.5649,3.398898)" + cx="418.51233" + cy="454.95853" + fx="418.51233" + fy="454.95853" + r="11.005301" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient11659" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.89894,0,0,0.89894,-354.7664,-364.9796)" + x1="415.12271" + y1="435.43039" + x2="415.12271" + y2="453.03961" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4166" + id="linearGradient11679" + gradientUnits="userSpaceOnUse" + x1="364.35117" + y1="443.11044" + x2="363.85608" + y2="428.539" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4182" + id="linearGradient11681" + gradientUnits="userSpaceOnUse" + x1="417.36929" + y1="435.0079" + x2="417.36929" + y2="454.65622" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4190" + id="radialGradient11683" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.726147,-8.054512e-3,1.109145e-2,0.999938,109.5649,3.398898)" + cx="418.51233" + cy="454.95853" + fx="418.51233" + fy="454.95853" + r="11.005301" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient11686" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.89894,0,0,0.89894,-354.7664,-364.9796)" + x1="415.12271" + y1="435.43039" + x2="415.12271" + y2="453.03961" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4166" + id="linearGradient11702" + gradientUnits="userSpaceOnUse" + x1="364.35117" + y1="443.11044" + x2="363.85608" + y2="428.539" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4182" + id="linearGradient11704" + gradientUnits="userSpaceOnUse" + x1="417.36929" + y1="435.0079" + x2="417.36929" + y2="454.65622" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4190" + id="radialGradient11706" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.726147,-8.054512e-3,1.109145e-2,0.999938,109.5649,3.398898)" + cx="418.51233" + cy="454.95853" + fx="418.51233" + fy="454.95853" + r="11.005301" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient11708" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.89894,0,0,0.89894,-354.7664,-364.9796)" + x1="415.12271" + y1="435.43039" + x2="415.12271" + y2="453.03961" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4166" + id="linearGradient11720" + gradientUnits="userSpaceOnUse" + x1="364.35117" + y1="443.11044" + x2="363.85608" + y2="428.539" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4182" + id="linearGradient11722" + gradientUnits="userSpaceOnUse" + x1="417.36929" + y1="435.0079" + x2="417.36929" + y2="454.65622" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4190" + id="radialGradient11724" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.726147,-8.054512e-3,1.109145e-2,0.999938,109.5649,3.398898)" + cx="418.51233" + cy="454.95853" + fx="418.51233" + fy="454.95853" + r="11.005301" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient11726" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.89894,0,0,0.89894,-354.7664,-364.9796)" + x1="415.12271" + y1="435.43039" + x2="415.12271" + y2="453.03961" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4166" + id="linearGradient11738" + gradientUnits="userSpaceOnUse" + x1="364.35117" + y1="443.11044" + x2="363.85608" + y2="428.539" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4182" + id="linearGradient11740" + gradientUnits="userSpaceOnUse" + x1="417.36929" + y1="435.0079" + x2="417.36929" + y2="454.65622" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4190" + id="radialGradient11742" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.726147,-8.054512e-3,1.109145e-2,0.999938,109.5649,3.398898)" + cx="418.51233" + cy="454.95853" + fx="418.51233" + fy="454.95853" + r="11.005301" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient11744" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.89894,0,0,0.89894,-354.7664,-364.9796)" + x1="415.12271" + y1="435.43039" + x2="415.12271" + y2="453.03961" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4166" + id="linearGradient11756" + gradientUnits="userSpaceOnUse" + x1="364.35117" + y1="443.11044" + x2="363.85608" + y2="428.539" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4182" + id="linearGradient11758" + gradientUnits="userSpaceOnUse" + x1="417.36929" + y1="435.0079" + x2="417.36929" + y2="454.65622" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4190" + id="radialGradient11760" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.726147,-8.054512e-3,1.109145e-2,0.999938,109.5649,3.398898)" + cx="418.51233" + cy="454.95853" + fx="418.51233" + fy="454.95853" + r="11.005301" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient11762" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.89894,0,0,0.89894,-354.7664,-364.9796)" + x1="415.12271" + y1="435.43039" + x2="415.12271" + y2="453.03961" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4166" + id="linearGradient11774" + gradientUnits="userSpaceOnUse" + x1="364.35117" + y1="443.11044" + x2="363.85608" + y2="428.539" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4182" + id="linearGradient11777" + gradientUnits="userSpaceOnUse" + x1="417.36929" + y1="435.0079" + x2="417.36929" + y2="454.65622" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4190" + id="radialGradient11779" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.726147,-8.054512e-3,1.109145e-2,0.999938,109.5649,3.398898)" + cx="418.51233" + cy="454.95853" + fx="418.51233" + fy="454.95853" + r="11.005301" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient11781" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.89894,0,0,0.89894,-354.7664,-364.9796)" + x1="415.12271" + y1="435.43039" + x2="415.12271" + y2="453.03961" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4166" + id="linearGradient11793" + gradientUnits="userSpaceOnUse" + x1="364.35117" + y1="443.11044" + x2="363.85608" + y2="428.539" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4182" + id="linearGradient11795" + gradientUnits="userSpaceOnUse" + x1="417.36929" + y1="435.0079" + x2="417.36929" + y2="454.65622" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4190" + id="radialGradient11797" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.726147,-8.054512e-3,1.109145e-2,0.999938,109.5649,3.398898)" + cx="418.51233" + cy="454.95853" + fx="418.51233" + fy="454.95853" + r="11.005301" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient11799" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.89894,0,0,0.89894,-354.7664,-364.9796)" + x1="415.12271" + y1="435.43039" + x2="415.12271" + y2="453.03961" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4166" + id="linearGradient11811" + gradientUnits="userSpaceOnUse" + x1="364.35117" + y1="443.11044" + x2="363.85608" + y2="428.539" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4182" + id="linearGradient11814" + gradientUnits="userSpaceOnUse" + x1="417.36929" + y1="435.0079" + x2="417.36929" + y2="454.65622" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4190" + id="radialGradient11816" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.726147,-8.054512e-3,1.109145e-2,0.999938,109.5649,3.398898)" + cx="418.51233" + cy="454.95853" + fx="418.51233" + fy="454.95853" + r="11.005301" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient11819" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.89894,0,0,0.89894,-354.7664,-364.9796)" + x1="415.12271" + y1="435.43039" + x2="415.12271" + y2="453.03961" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4166" + id="linearGradient11831" + gradientUnits="userSpaceOnUse" + x1="364.35117" + y1="443.11044" + x2="363.85608" + y2="428.539" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4182" + id="linearGradient11833" + gradientUnits="userSpaceOnUse" + x1="417.36929" + y1="435.0079" + x2="417.36929" + y2="454.65622" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4190" + id="radialGradient11835" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.726147,-8.054512e-3,1.109145e-2,0.999938,109.5649,3.398898)" + cx="418.51233" + cy="454.95853" + fx="418.51233" + fy="454.95853" + r="11.005301" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient11837" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.89894,0,0,0.89894,-354.7664,-364.9796)" + x1="415.12271" + y1="435.43039" + x2="415.12271" + y2="453.03961" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4166" + id="linearGradient11849" + gradientUnits="userSpaceOnUse" + x1="364.35117" + y1="443.11044" + x2="363.85608" + y2="428.539" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4182" + id="linearGradient11851" + gradientUnits="userSpaceOnUse" + x1="417.36929" + y1="435.0079" + x2="417.36929" + y2="454.65622" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4190" + id="radialGradient11853" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.726147,-8.054512e-3,1.109145e-2,0.999938,109.5649,3.398898)" + cx="418.51233" + cy="454.95853" + fx="418.51233" + fy="454.95853" + r="11.005301" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient11855" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.89894,0,0,0.89894,-354.7664,-364.9796)" + x1="415.12271" + y1="435.43039" + x2="415.12271" + y2="453.03961" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4166" + id="linearGradient11867" + gradientUnits="userSpaceOnUse" + x1="364.35117" + y1="443.11044" + x2="363.85608" + y2="428.539" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4182" + id="linearGradient11869" + gradientUnits="userSpaceOnUse" + x1="417.36929" + y1="435.0079" + x2="417.36929" + y2="454.65622" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4190" + id="radialGradient11871" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.726147,-8.054512e-3,1.109145e-2,0.999938,109.5649,3.398898)" + cx="418.51233" + cy="454.95853" + fx="418.51233" + fy="454.95853" + r="11.005301" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient11873" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.89894,0,0,0.89894,-354.7664,-364.9796)" + x1="415.12271" + y1="435.43039" + x2="415.12271" + y2="453.03961" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4166" + id="linearGradient11885" + gradientUnits="userSpaceOnUse" + x1="364.35117" + y1="443.11044" + x2="363.85608" + y2="428.539" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4182" + id="linearGradient11887" + gradientUnits="userSpaceOnUse" + x1="417.36929" + y1="435.0079" + x2="417.36929" + y2="454.65622" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4190" + id="radialGradient11889" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.726147,-8.054512e-3,1.109145e-2,0.999938,109.5649,3.398898)" + cx="418.51233" + cy="454.95853" + fx="418.51233" + fy="454.95853" + r="11.005301" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient11891" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.89894,0,0,0.89894,-354.7664,-364.9796)" + x1="415.12271" + y1="435.43039" + x2="415.12271" + y2="453.03961" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient11893" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1,0,0,1.066655,311.6482,727.0443)" + x1="307.53064" + y1="150.35727" + x2="307.53064" + y2="127.84666" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient12169" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.031982,0,0,1.032261,203.4626,82.51014)" + x1="101.25272" + y1="49.529297" + x2="101.25272" + y2="43.32613" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient12171" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.031982,0,0,1.107109,203.4626,79.26654)" + x1="122.57088" + y1="59.06163" + x2="122.57088" + y2="43.326801" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4182" + id="linearGradient12173" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.038287,0,0,1.017566,202.8397,83.2519)" + x1="111.84524" + y1="48.367947" + x2="111.84524" + y2="58.933025" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4166" + id="linearGradient12175" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.217348,0,0,0.215931,133.304,91.71049)" + x1="829.4079" + y1="212.95419" + x2="829.4079" + y2="183.47195" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4190" + id="radialGradient12177" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.296464,6.961497e-6,-5.421832e-6,1.090421,174.1442,75.63395)" + cx="111.70066" + cy="62.211178" + fx="111.70066" + fy="62.211178" + r="8.8008003" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient12179" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.013541,0,0,0.982207,205.5848,84.95115)" + spreadMethod="reflect" + x1="111.33885" + y1="44.705486" + x2="111.33885" + y2="46.731094" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient12181" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.303515,0,0,0.303515,219.7058,-25.8645)" + x1="310.68051" + y1="557.73425" + x2="310.68051" + y2="520.34827" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient12183" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.303515,0,0,0.303515,219.7058,-25.8645)" + x1="343.24649" + y1="557.73425" + x2="343.24649" + y2="520.34827" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient13189" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.999739,0,0,0.953222,22.5424,42.76085)" + x1="390.20804" + y1="182.18143" + x2="390.20804" + y2="159.18143" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient13191" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.999739,0,0,0.953222,22.5424,42.76085)" + x1="409.20804" + y1="182.18143" + x2="409.20804" + y2="159.18143" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient13193" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(20.71716,2.010781)" + x1="392.93735" + y1="193.43317" + x2="392.93735" + y2="230.89081" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient13222" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.999739,0,0,0.953222,22.5424,42.76085)" + x1="390.20804" + y1="182.18143" + x2="390.20804" + y2="159.18143" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient13224" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.999739,0,0,0.953222,22.5424,42.76085)" + x1="409.20804" + y1="182.18143" + x2="409.20804" + y2="159.18143" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient13226" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(20.71716,2.010781)" + x1="392.93735" + y1="193.43317" + x2="392.93735" + y2="230.89081" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient13228" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.43065,0.430635,-0.43065,0.430635,467.2213,282.8466)" + x1="168.18773" + y1="260.09671" + x2="156.90306" + y2="248.81204" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient13230" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.43065,0.430635,-0.43065,0.430635,467.2213,282.8466)" + x1="179.47237" + y1="248.81206" + x2="168.18771" + y2="237.52739" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient13980" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.2512617,0,0,0.2572531,1090.8117,55.824753)" + x1="46.169399" + y1="55.149899" + x2="97.329102" + y2="76.853996" /> + <linearGradient + inkscape:collect="always" + xlink:href="#XMLID_8_" + id="linearGradient13982" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.2512617,0,0,0.2572531,1090.8117,55.824753)" + x1="57.8667" + y1="100.9258" + x2="120.5585" + y2="70.7129" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient13984" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.2512617,0,0,0.2572531,1090.8117,55.824753)" + x1="32.630985" + y1="75.947983" + x2="32.630985" + y2="26.44599" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient13986" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.3538574,0,0,0.4019956,872.31177,37.482513)" + x1="679.37292" + y1="98.170349" + x2="679.37292" + y2="61.871277" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient13988" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.2512617,0,0,0.2572531,1090.8117,55.824753)" + x1="44.604591" + y1="62.292385" + x2="44.604591" + y2="33.60413" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient13991" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(7.4439477e-2,-7.6023333e-2,7.6654679e-2,7.3814022e-2,865.10533,59.127983)" + x1="1492.3453" + y1="1614.792" + x2="1458.5851" + y2="1742.0037" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient13993" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-0.1775951,0.1057407,-0.1066133,-0.1761095,1129.7978,74.396683)" + x1="31.778969" + y1="113.87714" + x2="31.778969" + y2="45.984291" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient13995" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.3454846,0,0,0.3537227,1117.9539,48.904103)" + x1="46.169399" + y1="55.149899" + x2="97.329102" + y2="76.853996" /> + <linearGradient + inkscape:collect="always" + xlink:href="#XMLID_8_" + id="linearGradient13997" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.3454846,0,0,0.3537227,1117.9539,48.904103)" + x1="57.8667" + y1="100.9258" + x2="120.5585" + y2="70.7129" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient13999" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.3454846,0,0,0.3537227,1117.9539,48.904103)" + x1="32.630985" + y1="75.947983" + x2="32.630985" + y2="26.44599" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient14001" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.4865535,0,0,0.5527435,817.51691,23.683543)" + x1="679.37292" + y1="98.170349" + x2="679.37292" + y2="61.871277" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient14003" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.3454846,0,0,0.3537227,1117.9539,48.904103)" + x1="44.604591" + y1="62.292385" + x2="44.604591" + y2="33.60413" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient14005" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.1023542,-0.104532,0.1054001,0.1014942,807.60811,53.446073)" + x1="1492.3453" + y1="1614.792" + x2="1458.5851" + y2="1742.0037" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient14007" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-0.2441931,0.1453934,-0.1465932,-0.2421504,1171.56,74.440513)" + x1="31.778969" + y1="113.87714" + x2="31.778969" + y2="45.984291" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient14009" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.5025228,0,0,0.5145054,1152.1473,37.703023)" + x1="46.169399" + y1="55.149899" + x2="97.329102" + y2="76.853996" /> + <linearGradient + inkscape:collect="always" + xlink:href="#XMLID_8_" + id="linearGradient14011" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.5025228,0,0,0.5145054,1152.1473,37.703023)" + x1="57.8667" + y1="100.9258" + x2="120.5585" + y2="70.7129" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient14013" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.5025228,0,0,0.5145054,1152.1473,37.703023)" + x1="32.630985" + y1="75.947983" + x2="32.630985" + y2="26.44599" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient14015" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.7077138,0,0,0.8039901,715.14818,1.0185921)" + x1="679.37292" + y1="98.170349" + x2="679.37292" + y2="61.871277" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient14017" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.5025228,0,0,0.5145054,1152.1473,37.703023)" + x1="44.604591" + y1="62.292385" + x2="44.604591" + y2="33.60413" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient14019" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.1488787,-0.1520464,0.1533092,0.1476279,700.73539,44.309513)" + x1="1492.3453" + y1="1614.792" + x2="1458.5851" + y2="1742.0037" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient14021" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-0.3551897,0.2114812,-0.2132263,-0.3522185,1230.1197,74.846853)" + x1="31.778969" + y1="113.87714" + x2="31.778969" + y2="45.984291" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient14023" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.7537846,0,0,0.7717584,1191.5269,16.372792)" + x1="46.169399" + y1="55.149899" + x2="97.329102" + y2="76.853996" /> + <linearGradient + inkscape:collect="always" + xlink:href="#XMLID_8_" + id="linearGradient14025" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.7537846,0,0,0.7717584,1191.5269,16.372792)" + x1="57.8667" + y1="100.9258" + x2="120.5585" + y2="70.7129" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient14027" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.7537846,0,0,0.7717584,1191.5269,16.372792)" + x1="32.630985" + y1="75.947983" + x2="32.630985" + y2="26.44599" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient14029" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.0615712,0,0,1.2059857,536.02784,-38.653888)" + x1="679.37292" + y1="98.170349" + x2="679.37292" + y2="61.871277" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient14031" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.7537846,0,0,0.7717584,1191.5269,16.372792)" + x1="44.604591" + y1="62.292385" + x2="44.604591" + y2="33.60413" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient14034" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.2233182,-0.2280697,0.229964,0.221442,514.40869,26.282563)" + x1="1492.3453" + y1="1614.792" + x2="1458.5851" + y2="1742.0037" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient14036" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-0.5327848,0.317222,-0.3198397,-0.528328,1308.4857,72.088603)" + x1="31.778969" + y1="113.87714" + x2="31.778969" + y2="45.984291" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient14038" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.0050456,0,0,1.0290106,1248.9065,-2.7489379)" + x1="46.169399" + y1="55.149899" + x2="97.329102" + y2="76.853996" /> + <linearGradient + inkscape:collect="always" + xlink:href="#XMLID_8_" + id="linearGradient14040" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.0050456,0,0,1.0290106,1248.9065,-2.7489379)" + x1="57.8667" + y1="100.9258" + x2="120.5585" + y2="70.7129" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient14042" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.0050456,0,0,1.0290106,1248.9065,-2.7489379)" + x1="32.630985" + y1="75.947983" + x2="32.630985" + y2="26.44599" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient14044" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.4154275,0,0,1.60798,374.90824,-76.117797)" + x1="679.37292" + y1="98.170349" + x2="679.37292" + y2="61.871277" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient14046" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.0050456,0,0,1.0290106,1248.9065,-2.7489379)" + x1="44.604591" + y1="62.292385" + x2="44.604591" + y2="33.60413" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient14048" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.2977575,-0.3040928,0.3066185,0.2952558,346.08274,10.464102)" + x1="1492.3453" + y1="1614.792" + x2="1458.5851" + y2="1742.0037" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient14050" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-0.7103793,0.4229624,-0.4264527,-0.7044369,1404.8516,71.538793)" + x1="31.778969" + y1="113.87714" + x2="31.778969" + y2="45.984291" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient11767" + id="linearGradient14052" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.2499998,0,0,0.2499999,435.77213,618.7776)" + x1="1145.7107" + y1="1577.8585" + x2="1178.2305" + y2="1577.8585" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient13392" + id="linearGradient14054" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.2499998,0,0,0.2499999,435.77213,618.7776)" + x1="1145.7107" + y1="1577.8585" + x2="1178.2305" + y2="1577.8585" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient11767" + id="linearGradient14056" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.3437494,0,0,0.3437495,364.79442,467.88958)" + x1="1145.7107" + y1="1577.8585" + x2="1178.2305" + y2="1577.8585" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient13392" + id="linearGradient14058" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.3437494,0,0,0.3437495,364.79442,467.88958)" + x1="1145.7107" + y1="1577.8585" + x2="1178.2305" + y2="1577.8585" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient11767" + id="linearGradient14060" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.4999996,0,0,0.4999996,235.65742,214.44017)" + x1="1145.7107" + y1="1577.8585" + x2="1178.2305" + y2="1577.8585" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient13392" + id="linearGradient14062" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.4999996,0,0,0.4999996,235.65742,214.44017)" + x1="1145.7107" + y1="1577.8585" + x2="1178.2305" + y2="1577.8585" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient11767" + id="linearGradient14064" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.7499994,0,0,0.7499994,26.396454,-187.92936)" + x1="1145.7107" + y1="1577.8585" + x2="1178.2305" + y2="1577.8585" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient13392" + id="linearGradient14066" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.7499994,0,0,0.7499994,26.396454,-187.92936)" + x1="1145.7107" + y1="1577.8585" + x2="1178.2305" + y2="1577.8585" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient11767" + id="linearGradient14068" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(-170.67044,-589.31607)" + x1="1145.7107" + y1="1577.8585" + x2="1178.2305" + y2="1577.8585" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient13392" + id="linearGradient14070" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(-170.67044,-589.31607)" + x1="1145.7107" + y1="1577.8585" + x2="1178.2305" + y2="1577.8585" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient13837" + id="linearGradient14072" + gradientUnits="userSpaceOnUse" + x1="984.22174" + y1="1303.4946" + x2="984.05951" + y2="1197.8486" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient17023" + x1="-450" + y1="-96.637817" + x2="-349" + y2="-96.637817" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.4899567,0,0,0.520076,-234.26231,-45.878807)" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient17031" + x1="-450" + y1="-96.637817" + x2="-349" + y2="-96.637817" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.4899567,0,0,0.520076,-234.26231,-45.878807)" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient18010" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(78.367484,-550.85767)" + x1="-60.640533" + y1="581.21619" + x2="-60.640533" + y2="609.21283" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient18012" + gradientUnits="userSpaceOnUse" + x1="-83.643311" + y1="595.21454" + x2="-37.637753" + y2="595.21454" + gradientTransform="translate(78.367486,-550.85766)" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient13837" + id="linearGradient19964" + gradientUnits="userSpaceOnUse" + x1="984.22174" + y1="1303.4946" + x2="984.05951" + y2="1197.8486" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4166" + id="linearGradient19983" + gradientUnits="userSpaceOnUse" + x1="364.35117" + y1="443.11044" + x2="363.85608" + y2="428.539" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4182" + id="linearGradient19985" + gradientUnits="userSpaceOnUse" + x1="417.36929" + y1="435.0079" + x2="417.36929" + y2="454.65622" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4190" + id="radialGradient19987" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.726147,-8.054512e-3,1.109145e-2,0.999938,109.5649,3.398898)" + cx="418.51233" + cy="454.95853" + fx="418.51233" + fy="454.95853" + r="11.005301" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4198" + id="linearGradient19989" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.89894,0,0,0.89894,-354.0102,-380.2835)" + x1="415.12271" + y1="435.43039" + x2="415.12271" + y2="453.03961" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7682" + id="linearGradient19996" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.256604,0,0,0.256604,-533.75272,-196.48903)" + x1="907.27698" + y1="1303.4946" + x2="907.27698" + y2="1197.8486" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient10909" + id="linearGradient19998" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-0.256604,0,0,0.256604,-80.373739,-177.60041)" + x1="720.01282" + y1="1229.8846" + x2="720.01282" + y2="1124.2386" /> + </defs> + <sodipodi:namedview + id="base" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageopacity="0.0" + inkscape:pageshadow="2" + inkscape:zoom="1" + inkscape:cx="-167.18826" + inkscape:cy="1019.469" + inkscape:document-units="px" + inkscape:current-layer="layer1" + inkscape:window-width="1018" + inkscape:window-height="692" + inkscape:window-x="0" + inkscape:window-y="25" + showguides="true" + inkscape:guide-bbox="true" + showgrid="true" + fill="#bbbbbb" + guidecolor="#ff0000" + guideopacity="0.49803922" + guidehicolor="#00ed53" + guidehiopacity="0.49803922"> + <sodipodi:guide + orientation="horizontal" + position="1040" + id="guide14074" /> + </sodipodi:namedview> + <metadata + id="metadata29261"> + <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="Layer 1" + inkscape:groupmode="layer" + id="layer1" + style="display:inline"> + <rect + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/crystalsvg/64x64/actions/podcast.png" + style="fill:#b9cff9;fill-opacity:0;stroke:none;stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + id="rect10847" + width="64" + height="64" + x="288.72617" + y="372.36209" /> + <g + id="g12897" + transform="translate(6,-18.505286)"> + <rect + inkscape:export-ydpi="90.000092" + inkscape:export-xdpi="90.000092" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/16x16/actions/amarok_device.png" + y="363.10342" + x="399.21399" + height="16" + width="16" + id="rect10353" + style="fill:#b9cff9;fill-opacity:0;stroke:none;stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" /> + <g + inkscape:export-ydpi="90.000092" + inkscape:export-xdpi="90.000092" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/16x16/actions/amarok_device.png" + transform="matrix(0.653226,0,0,0.653226,-2.3077081,-200.7424)" + id="g18581"> + <rect + ry="3.5207255" + rx="3.5207253" + y="863.91321" + x="619.64813" + height="23.009743" + width="14.547573" + id="rect15544" + style="opacity:1;fill:url(#linearGradient9036);fill-opacity:1;stroke:url(#linearGradient11333);stroke-width:1.48417735;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + id="path15692" + d="M 621.98005,864.8914 C 621.25253,864.8914 620.66749,865.512 620.66749,866.2801 L 620.66749,883.6391 C 620.66749,884.4072 621.25253,885.0228 621.98005,885.0228 L 621.60358,885.0228 C 620.87606,885.0228 620.29102,884.4072 620.29102,883.6391 L 620.29102,866.2801 C 620.29102,865.512 620.87606,864.8914 621.60358,864.8914 L 621.98005,864.8914 z " + style="opacity:0.80512821;fill:url(#linearGradient11335);fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" /> + <path + id="path15694" + d="M 626.5323,874.8072 C 623.98062,875.0011 621.98653,877.1299 621.98654,879.7244 C 621.98654,882.4465 624.19284,884.6737 626.92194,884.6737 C 629.65106,884.6737 631.85734,882.4465 631.85733,879.7244 C 631.85733,877.0024 629.65107,874.8072 626.92194,874.8072 C 626.79401,874.8072 626.65779,874.7977 626.5323,874.8072 z M 626.79206,877.7961 C 626.85487,877.7897 626.9224,877.7961 626.98688,877.7961 C 628.01858,877.7961 628.87012,878.6311 628.87012,879.6601 C 628.87011,880.6892 628.01858,881.5242 626.98688,881.5242 C 625.95517,881.5242 625.13611,880.6892 625.13611,879.6601 C 625.13611,878.6954 625.84997,877.8916 626.79206,877.7961 z " + style="fill:url(#radialGradient11337);fill-opacity:1;stroke:none;stroke-width:0.5;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + ry="1.107012" + rx="1.1070117" + y="866.00488" + x="622.25305" + height="7.0495582" + width="9.1704264" + id="rect15696" + style="fill:url(#linearGradient11339);fill-opacity:1;stroke:none;stroke-width:0.5;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + sodipodi:nodetypes="ccccccc" + style="opacity:0.29787233;fill:url(#linearGradient11341);fill-opacity:1;stroke:none;stroke-width:3.00600219;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 623.13964,866.2289 C 622.91188,866.2289 622.5585,866.4761 622.5585,866.7092 L 622.57328,870.2008 C 626.57854,871.1331 626.14856,869.44 631.01911,869.6871 L 631.06558,866.6816 C 631.06558,866.4485 630.88221,866.2608 630.65446,866.2608 L 623.13964,866.2289 z " + id="path15698" + inkscape:export-filename="/home/steve/Desktop/creative zen.png" + inkscape:export-xdpi="90.000000" + inkscape:export-ydpi="90.000000" /> + </g> + </g> + <g + transform="matrix(-2.548793e-2,-0.292647,0.280854,-2.446082e-2,142.0473,366.8613)" + id="g50741" /> + <g + transform="matrix(-2.548793e-2,-0.292647,0.280854,-2.446082e-2,137.3552,374.8548)" + id="use50753" /> + <g + id="g51098" + transform="matrix(-3.733576e-2,-0.428681,0.411406,-3.583121e-2,97.79384,526.8332)" /> + <g + id="g51100" + transform="matrix(-3.733576e-2,-0.428681,0.411406,-3.583121e-2,90.92066,538.5424)" /> + <g + id="g3697" + i:layer="yes" + i:dimmedPercent="50" + i:rgbTrio="#4F00FFFFFFFF" + transform="matrix(2.137143e-2,0,0,2.137143e-2,614.2135,175.4559)" /> + <path + d="" + id="path20231" /> + <rect + ry="2.9675667" + rx="0" + y="-88.254128" + x="405.05341" + height="9.9948788" + width="0" + id="rect7977" + style="opacity:0.10697674;fill:url(#linearGradient7136);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + transform="matrix(0.997579,-6.954143e-2,-8.538193e-2,-0.996348,0,0)" /> + <g + style="display:inline" + id="g11436" + transform="translate(-459.17369,203.26257)" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/22x22/actions/amarok_play.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <g + transform="translate(4.4941,137.81598)" + id="use11438"> + <path + sodipodi:type="arc" + style="fill:url(#linearGradient11560);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path11552" + sodipodi:cx="366.7616" + sodipodi:cy="436.4032" + sodipodi:rx="10.639691" + sodipodi:ry="10.639691" + d="M 377.40129 436.4032 A 10.639691 10.639691 0 1 1 356.12191,436.4032 A 10.639691 10.639691 0 1 1 377.40129 436.4032 z" + transform="matrix(1.033865,0,0,1.033865,-359.2117,-416.8144)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:type="arc" + style="fill:url(#linearGradient11562);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path11554" + sodipodi:cx="418.94757" + sodipodi:cy="444.83228" + sodipodi:rx="9.8239412" + sodipodi:ry="9.8239412" + d="M 428.77151 444.83228 A 9.8239412 9.8239412 0 1 1 409.12363,444.83228 A 9.8239412 9.8239412 0 1 1 428.77151 444.83228 z" + transform="matrix(1.014646,0,0,1.014647,-405.1135,-416.9803)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:type="arc" + style="fill:url(#radialGradient11564);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path11556" + sodipodi:cx="418.51233" + sodipodi:cy="446.44885" + sodipodi:rx="11.005301" + sodipodi:ry="11.005301" + d="M 429.51763 446.44885 A 11.005301 11.005301 0 1 1 407.50703,446.44885 A 11.005301 11.005301 0 1 1 429.51763 446.44885 z" + transform="matrix(0.810701,0,0,0.810702,-319.3183,-327.5696)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + style="opacity:0.7;fill:url(#linearGradient11566);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 27.878882,34.367333 C 27.878882,38.733063 24.335692,34.874413 19.969982,34.874413 C 15.604272,34.874413 12.061082,38.733063 12.061082,34.367333 C 12.061082,30.001633 15.604272,26.458433 19.969982,26.458433 C 24.335692,26.458433 27.878882,30.001633 27.878882,34.367333 z " + id="path11558" + sodipodi:nodetypes="csssc" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" /> + </g> + <g + inkscape:export-ydpi="90.000000" + inkscape:export-xdpi="90.000000" + inkscape:export-filename="/home/vadim/collection.png" + transform="matrix(8.30464e-2,0,0,8.30464e-2,-66.65521,68.00942)" + id="g11440"> + <path + style="fill:url(#linearGradient11583);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient11585);stroke-width:6.47018957;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 1064.5688,1194.5934 L 1064.2322,1314.225 L 1158.4102,1259.0756 L 1064.5688,1194.5934 z " + id="path11442" + sodipodi:nodetypes="cccc" /> + </g> + </g> + <g + style="display:inline" + id="g11444" + transform="translate(-459.53581,196.83382)" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/32x32/actions/amarok_play.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <g + inkscape:export-ydpi="89.999962" + inkscape:export-xdpi="89.999962" + inkscape:export-filename="/home/vadim/amarok icons/1/22x22/actions/amarok_circle.png" + transform="matrix(1.454545,0,0,1.454545,35.92383,145.88333)" + id="use11446"> + <path + sodipodi:type="arc" + style="opacity:1;fill:url(#linearGradient11052);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path11043" + sodipodi:cx="366.7616" + sodipodi:cy="436.4032" + sodipodi:rx="10.639691" + sodipodi:ry="10.639691" + d="M 377.40129 436.4032 A 10.639691 10.639691 0 1 1 356.12191,436.4032 A 10.639691 10.639691 0 1 1 377.40129 436.4032 z" + transform="matrix(1.033865,0,0,1.033865,-358.4555,-432.1183)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:type="arc" + style="opacity:1;fill:url(#linearGradient11054);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path11045" + sodipodi:cx="418.94757" + sodipodi:cy="444.83228" + sodipodi:rx="9.8239412" + sodipodi:ry="9.8239412" + d="M 428.77151 444.83228 A 9.8239412 9.8239412 0 1 1 409.12363,444.83228 A 9.8239412 9.8239412 0 1 1 428.77151 444.83228 z" + transform="matrix(1.068817,0,0,1.068817,-427.0518,-456.3806)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:type="arc" + style="opacity:1;fill:url(#radialGradient11056);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path11047" + sodipodi:cx="418.51233" + sodipodi:cy="446.44885" + sodipodi:rx="11.005301" + sodipodi:ry="11.005301" + d="M 429.51763 446.44885 A 11.005301 11.005301 0 1 1 407.50703,446.44885 A 11.005301 11.005301 0 1 1 429.51763 446.44885 z" + transform="matrix(0.908653,0,0,0.908654,-359.556,-386.6038)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + style="opacity:0.7;fill:url(#linearGradient11058);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 30.226478,19.072457 C 30.226478,24.316478 25.970475,19.68155 20.726479,19.68155 C 15.482481,19.68155 11.226478,24.316478 11.226478,19.072457 C 11.226478,13.828473 15.482481,9.5724574 20.726479,9.5724574 C 25.970475,9.5724574 30.226478,13.828473 30.226478,19.072457 z " + id="path11049" + sodipodi:nodetypes="csssc" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" /> + </g> + <g + style="fill:url(#linearGradient11587);fill-opacity:1;stroke:url(#linearGradient11589)" + inkscape:export-ydpi="90.000000" + inkscape:export-xdpi="90.000000" + inkscape:export-filename="/home/vadim/collection.png" + transform="matrix(0.120795,0,0,0.120795,-66.46611,22.08624)" + id="g11448"> + <path + style="fill:url(#linearGradient11591);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient11593);stroke-width:4.81658459;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 1064.5688,1194.5934 L 1064.2322,1314.225 L 1158.4102,1259.0756 L 1064.5688,1194.5934 z " + id="path11450" + sodipodi:nodetypes="cccc" /> + </g> + </g> + <g + style="display:inline" + id="g11452" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/32x32/amarok_stop.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" + transform="translate(-457.53581,233.72687)"> + <g + inkscape:export-ydpi="89.999962" + inkscape:export-xdpi="89.999962" + inkscape:export-filename="/home/vadim/amarok icons/1/22x22/actions/amarok_circle.png" + transform="matrix(1.454545,0,0,1.454545,35.20954,179.99033)" + id="use11454"> + <path + sodipodi:type="arc" + style="opacity:1;fill:url(#linearGradient11070);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path11062" + sodipodi:cx="366.7616" + sodipodi:cy="436.4032" + sodipodi:rx="10.639691" + sodipodi:ry="10.639691" + d="M 377.40129 436.4032 A 10.639691 10.639691 0 1 1 356.12191,436.4032 A 10.639691 10.639691 0 1 1 377.40129 436.4032 z" + transform="matrix(1.033865,0,0,1.033865,-358.4555,-432.1183)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:type="arc" + style="opacity:1;fill:url(#linearGradient11072);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path11064" + sodipodi:cx="418.94757" + sodipodi:cy="444.83228" + sodipodi:rx="9.8239412" + sodipodi:ry="9.8239412" + d="M 428.77151 444.83228 A 9.8239412 9.8239412 0 1 1 409.12363,444.83228 A 9.8239412 9.8239412 0 1 1 428.77151 444.83228 z" + transform="matrix(1.068817,0,0,1.068817,-427.0518,-456.3806)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:type="arc" + style="opacity:1;fill:url(#radialGradient11074);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path11066" + sodipodi:cx="418.51233" + sodipodi:cy="446.44885" + sodipodi:rx="11.005301" + sodipodi:ry="11.005301" + d="M 429.51763 446.44885 A 11.005301 11.005301 0 1 1 407.50703,446.44885 A 11.005301 11.005301 0 1 1 429.51763 446.44885 z" + transform="matrix(0.908653,0,0,0.908654,-359.556,-386.6038)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + style="opacity:0.7;fill:url(#linearGradient11076);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 30.226478,19.072457 C 30.226478,24.316478 25.970475,19.68155 20.726479,19.68155 C 15.482481,19.68155 11.226478,24.316478 11.226478,19.072457 C 11.226478,13.828473 15.482481,9.5724574 20.726479,9.5724574 C 25.970475,9.5724574 30.226478,13.828473 30.226478,19.072457 z " + id="path11068" + sodipodi:nodetypes="csssc" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" /> + </g> + <g + inkscape:export-ydpi="90.000000" + inkscape:export-xdpi="90.000000" + inkscape:export-filename="/home/derek/Desktop/Devel/icons.png" + transform="matrix(0.120171,0,0,0.120171,-91.00753,57.57068)" + id="g11456"> + <g + id="g11458"> + <rect + y="1193.7136" + x="1247.7158" + height="111.49675" + width="106.94586" + id="rect11460" + style="opacity:1;fill:url(#linearGradient11595);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient11597);stroke-width:4.84159899;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + </g> + </g> + <g + style="display:inline" + id="g11462" + transform="translate(-452.46441,238.36962)" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/22x22/actions/amarok_stop.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <g + transform="translate(5.74401,173.70898)" + id="use11464"> + <path + sodipodi:type="arc" + style="fill:url(#linearGradient11578);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path11570" + sodipodi:cx="366.7616" + sodipodi:cy="436.4032" + sodipodi:rx="10.639691" + sodipodi:ry="10.639691" + d="M 377.40129 436.4032 A 10.639691 10.639691 0 1 1 356.12191,436.4032 A 10.639691 10.639691 0 1 1 377.40129 436.4032 z" + transform="matrix(1.033865,0,0,1.033865,-359.2117,-416.8144)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:type="arc" + style="fill:url(#linearGradient11580);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path11572" + sodipodi:cx="418.94757" + sodipodi:cy="444.83228" + sodipodi:rx="9.8239412" + sodipodi:ry="9.8239412" + d="M 428.77151 444.83228 A 9.8239412 9.8239412 0 1 1 409.12363,444.83228 A 9.8239412 9.8239412 0 1 1 428.77151 444.83228 z" + transform="matrix(1.014646,0,0,1.014647,-405.1135,-416.9803)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:type="arc" + style="fill:url(#radialGradient11582);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path11574" + sodipodi:cx="418.51233" + sodipodi:cy="446.44885" + sodipodi:rx="11.005301" + sodipodi:ry="11.005301" + d="M 429.51763 446.44885 A 11.005301 11.005301 0 1 1 407.50703,446.44885 A 11.005301 11.005301 0 1 1 429.51763 446.44885 z" + transform="matrix(0.810701,0,0,0.810702,-319.3183,-327.5696)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + style="opacity:0.7;fill:url(#linearGradient11584);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 27.878882,34.367333 C 27.878882,38.733063 24.335692,34.874413 19.969982,34.874413 C 15.604272,34.874413 12.061082,38.733063 12.061082,34.367333 C 12.061082,30.001633 15.604272,26.458433 19.969982,26.458433 C 24.335692,26.458433 27.878882,30.001633 27.878882,34.367333 z " + id="path11576" + sodipodi:nodetypes="csssc" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" /> + </g> + <g + inkscape:export-ydpi="90.000000" + inkscape:export-xdpi="90.000000" + inkscape:export-filename="/home/derek/Desktop/Devel/icons.png" + transform="matrix(7.924505e-2,0,0,7.601116e-2,-77.39848,113.1035)" + id="g11466"> + <g + id="g11468"> + <rect + y="1193.7136" + x="1247.7158" + height="111.49675" + width="106.94586" + id="rect11470" + style="opacity:1;fill:url(#linearGradient11599);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient11601);stroke-width:6.76481104;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + </g> + </g> + <g + style="display:inline" + id="g11472" + transform="translate(-457.07139,274.79825)" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/22x22/actions/amarok_pause.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <g + transform="translate(6.1011,207.28028)" + id="use11474"> + <path + sodipodi:type="arc" + style="fill:url(#linearGradient11596);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path11588" + sodipodi:cx="366.7616" + sodipodi:cy="436.4032" + sodipodi:rx="10.639691" + sodipodi:ry="10.639691" + d="M 377.40129 436.4032 A 10.639691 10.639691 0 1 1 356.12191,436.4032 A 10.639691 10.639691 0 1 1 377.40129 436.4032 z" + transform="matrix(1.033865,0,0,1.033865,-359.2117,-416.8144)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:type="arc" + style="fill:url(#linearGradient11598);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path11590" + sodipodi:cx="418.94757" + sodipodi:cy="444.83228" + sodipodi:rx="9.8239412" + sodipodi:ry="9.8239412" + d="M 428.77151 444.83228 A 9.8239412 9.8239412 0 1 1 409.12363,444.83228 A 9.8239412 9.8239412 0 1 1 428.77151 444.83228 z" + transform="matrix(1.014646,0,0,1.014647,-405.1135,-416.9803)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:type="arc" + style="fill:url(#radialGradient11600);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path11592" + sodipodi:cx="418.51233" + sodipodi:cy="446.44885" + sodipodi:rx="11.005301" + sodipodi:ry="11.005301" + d="M 429.51763 446.44885 A 11.005301 11.005301 0 1 1 407.50703,446.44885 A 11.005301 11.005301 0 1 1 429.51763 446.44885 z" + transform="matrix(0.810701,0,0,0.810702,-319.3183,-327.5696)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + style="opacity:0.7;fill:url(#linearGradient11602);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 27.878882,34.367333 C 27.878882,38.733063 24.335692,34.874413 19.969982,34.874413 C 15.604272,34.874413 12.061082,38.733063 12.061082,34.367333 C 12.061082,30.001633 15.604272,26.458433 19.969982,26.458433 C 24.335692,26.458433 27.878882,30.001633 27.878882,34.367333 z " + id="path11594" + sodipodi:nodetypes="csssc" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" /> + </g> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/vadim/collection.png" + transform="matrix(4.559546e-2,0,0,9.592578e-2,-44.46674,90.8665)" + id="g11476" + style="fill:#943c3e;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:6.68458128;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"> + <path + style="opacity:1;fill:url(#linearGradient11603);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient11605);stroke-width:5.36307859;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 1577.4039,1526.7125 L 1577.4039,1616.4528 L 1663.6021,1616.4528 L 1663.6021,1526.7125 L 1577.4039,1526.7125 z M 1430.4823,1527.1891 L 1430.4823,1616.9975 L 1516.6806,1616.9975 L 1516.6806,1527.1891 L 1430.4823,1527.1891 z " + id="path11479" /> + </g> + </g> + <g + style="display:inline" + id="g11481" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/32x32/actions/amarok_pause.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" + transform="translate(-462.14279,269.79825)"> + <g + inkscape:export-ydpi="89.999962" + inkscape:export-xdpi="89.999962" + inkscape:export-filename="/home/vadim/amarok icons/1/22x22/actions/amarok_circle.png" + transform="matrix(1.454545,0,0,1.454545,35.92385,213.91883)" + id="use11483"> + <path + sodipodi:type="arc" + style="opacity:1;fill:url(#linearGradient11088);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path11080" + sodipodi:cx="366.7616" + sodipodi:cy="436.4032" + sodipodi:rx="10.639691" + sodipodi:ry="10.639691" + d="M 377.40129 436.4032 A 10.639691 10.639691 0 1 1 356.12191,436.4032 A 10.639691 10.639691 0 1 1 377.40129 436.4032 z" + transform="matrix(1.033865,0,0,1.033865,-358.4555,-432.1183)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:type="arc" + style="opacity:1;fill:url(#linearGradient11090);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path11082" + sodipodi:cx="418.94757" + sodipodi:cy="444.83228" + sodipodi:rx="9.8239412" + sodipodi:ry="9.8239412" + d="M 428.77151 444.83228 A 9.8239412 9.8239412 0 1 1 409.12363,444.83228 A 9.8239412 9.8239412 0 1 1 428.77151 444.83228 z" + transform="matrix(1.068817,0,0,1.068817,-427.0518,-456.3806)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:type="arc" + style="opacity:1;fill:url(#radialGradient11092);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path11084" + sodipodi:cx="418.51233" + sodipodi:cy="446.44885" + sodipodi:rx="11.005301" + sodipodi:ry="11.005301" + d="M 429.51763 446.44885 A 11.005301 11.005301 0 1 1 407.50703,446.44885 A 11.005301 11.005301 0 1 1 429.51763 446.44885 z" + transform="matrix(0.908653,0,0,0.908654,-359.556,-386.6038)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + style="opacity:0.7;fill:url(#linearGradient11094);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 30.226478,19.072457 C 30.226478,24.316478 25.970475,19.68155 20.726479,19.68155 C 15.482481,19.68155 11.226478,24.316478 11.226478,19.072457 C 11.226478,13.828473 15.482481,9.5724574 20.726479,9.5724574 C 25.970475,9.5724574 30.226478,13.828473 30.226478,19.072457 z " + id="path11086" + sodipodi:nodetypes="csssc" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" /> + </g> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/vadim/collection.png" + transform="matrix(6.477895e-2,0,0,0.152432,-34.14433,2.046827)" + id="g11485" + style="fill:#943c3e;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:6.68458128;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"> + <path + style="opacity:1;fill:url(#linearGradient11607);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient11609);stroke-width:5.85507154;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 1577.4039,1526.7125 L 1577.4039,1616.4528 L 1663.6021,1616.4528 L 1663.6021,1526.7125 L 1577.4039,1526.7125 z M 1430.4823,1527.1891 L 1430.4823,1616.9975 L 1516.6806,1616.9975 L 1516.6806,1527.1891 L 1430.4823,1527.1891 z " + id="path11487" /> + </g> + </g> + <g + style="display:inline" + id="g11489" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/32x32/actions/amarok_next.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" + transform="translate(-462.53581,306.01235)"> + <g + inkscape:export-ydpi="89.999962" + inkscape:export-xdpi="89.999962" + inkscape:export-filename="/home/vadim/amarok icons/1/22x22/actions/amarok_circle.png" + transform="matrix(1.454545,0,0,1.454545,36.28093,250.70473)" + id="use11491"> + <path + sodipodi:type="arc" + style="opacity:1;fill:url(#linearGradient11106);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path11098" + sodipodi:cx="366.7616" + sodipodi:cy="436.4032" + sodipodi:rx="10.639691" + sodipodi:ry="10.639691" + d="M 377.40129 436.4032 A 10.639691 10.639691 0 1 1 356.12191,436.4032 A 10.639691 10.639691 0 1 1 377.40129 436.4032 z" + transform="matrix(1.033865,0,0,1.033865,-358.4555,-432.1183)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:type="arc" + style="opacity:1;fill:url(#linearGradient11108);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path11100" + sodipodi:cx="418.94757" + sodipodi:cy="444.83228" + sodipodi:rx="9.8239412" + sodipodi:ry="9.8239412" + d="M 428.77151 444.83228 A 9.8239412 9.8239412 0 1 1 409.12363,444.83228 A 9.8239412 9.8239412 0 1 1 428.77151 444.83228 z" + transform="matrix(1.068817,0,0,1.068817,-427.0518,-456.3806)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:type="arc" + style="opacity:1;fill:url(#radialGradient11110);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path11102" + sodipodi:cx="418.51233" + sodipodi:cy="446.44885" + sodipodi:rx="11.005301" + sodipodi:ry="11.005301" + d="M 429.51763 446.44885 A 11.005301 11.005301 0 1 1 407.50703,446.44885 A 11.005301 11.005301 0 1 1 429.51763 446.44885 z" + transform="matrix(0.908653,0,0,0.908654,-359.556,-386.6038)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + style="opacity:0.7;fill:url(#linearGradient11112);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 30.226478,19.072457 C 30.226478,24.316478 25.970475,19.68155 20.726479,19.68155 C 15.482481,19.68155 11.226478,24.316478 11.226478,19.072457 C 11.226478,13.828473 15.482481,9.5724574 20.726479,9.5724574 C 25.970475,9.5724574 30.226478,13.828473 30.226478,19.072457 z " + id="path11104" + sodipodi:nodetypes="csssc" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" /> + </g> + <g + transform="matrix(-0.284505,0,0,0.284505,345.7854,255.2632)" + id="g11493"> + <g + transform="matrix(0.51291,0,0,0.51291,468.129,-559.841)" + id="g11495"> + <path + sodipodi:nodetypes="ccccccccc" + id="path11497" + d="M 1044.2816,1200.1729 L 1044.5628,1300.3931 L 982.99023,1261.4011 L 982.98547,1300.3931 L 958.81862,1300.3931 L 958.81862,1200.1729 L 982.90595,1200.1729 L 982.9123,1238.4265 L 1044.2816,1200.1729 z " + style="fill:url(#linearGradient11611);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient11613);stroke-width:3.98709559;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + </g> + </g> + <g + style="display:inline" + id="g11499" + transform="matrix(0.727272,0,0,0.727272,-487.45141,390.20895)" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/16x16/actions/amarok_next.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <g + transform="translate(5.744,243.70878)" + id="use11501"> + <path + sodipodi:type="arc" + style="fill:url(#linearGradient11614);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path11606" + sodipodi:cx="366.7616" + sodipodi:cy="436.4032" + sodipodi:rx="10.639691" + sodipodi:ry="10.639691" + d="M 377.40129 436.4032 A 10.639691 10.639691 0 1 1 356.12191,436.4032 A 10.639691 10.639691 0 1 1 377.40129 436.4032 z" + transform="matrix(1.033865,0,0,1.033865,-359.2117,-416.8144)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:type="arc" + style="fill:url(#linearGradient11616);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path11608" + sodipodi:cx="418.94757" + sodipodi:cy="444.83228" + sodipodi:rx="9.8239412" + sodipodi:ry="9.8239412" + d="M 428.77151 444.83228 A 9.8239412 9.8239412 0 1 1 409.12363,444.83228 A 9.8239412 9.8239412 0 1 1 428.77151 444.83228 z" + transform="matrix(1.014646,0,0,1.014647,-405.1135,-416.9803)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:type="arc" + style="fill:url(#radialGradient11618);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path11610" + sodipodi:cx="418.51233" + sodipodi:cy="446.44885" + sodipodi:rx="11.005301" + sodipodi:ry="11.005301" + d="M 429.51763 446.44885 A 11.005301 11.005301 0 1 1 407.50703,446.44885 A 11.005301 11.005301 0 1 1 429.51763 446.44885 z" + transform="matrix(0.810701,0,0,0.810702,-319.3183,-327.5696)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + style="opacity:0.7;fill:url(#linearGradient11620);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 27.878882,34.367333 C 27.878882,38.733063 24.335692,34.874413 19.969982,34.874413 C 15.604272,34.874413 12.061082,38.733063 12.061082,34.367333 C 12.061082,30.001633 15.604272,26.458433 19.969982,26.458433 C 24.335692,26.458433 27.878882,30.001633 27.878882,34.367333 z " + id="path11612" + sodipodi:nodetypes="csssc" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" /> + </g> + <g + transform="matrix(-0.192419,0,0,0.184587,214.6517,263.0434)" + id="g11503"> + <g + transform="matrix(0.51291,0,0,0.51291,468.129,-559.841)" + id="g11505"> + <path + sodipodi:nodetypes="ccccccccc" + id="path11507" + d="M 1044.2816,1200.1729 L 1044.5628,1300.3931 L 982.99023,1261.4011 L 982.98547,1300.3931 L 958.81862,1300.3931 L 958.81862,1200.1729 L 982.90595,1200.1729 L 982.9123,1238.4265 L 1044.2816,1200.1729 z " + style="fill:url(#linearGradient11615);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient11617);stroke-width:7.48971319;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + </g> + </g> + <g + style="display:inline" + id="g11509" + transform="translate(-455.46441,394.86267)" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/22x22/actions/amarok_redo.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <g + transform="translate(6.2858,311.21618)" + id="use11511"> + <path + sodipodi:type="arc" + style="fill:url(#linearGradient11632);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path11624" + sodipodi:cx="366.7616" + sodipodi:cy="436.4032" + sodipodi:rx="10.639691" + sodipodi:ry="10.639691" + d="M 377.40129 436.4032 A 10.639691 10.639691 0 1 1 356.12191,436.4032 A 10.639691 10.639691 0 1 1 377.40129 436.4032 z" + transform="matrix(1.033865,0,0,1.033865,-359.2117,-416.8144)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:type="arc" + style="fill:url(#linearGradient11634);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path11626" + sodipodi:cx="418.94757" + sodipodi:cy="444.83228" + sodipodi:rx="9.8239412" + sodipodi:ry="9.8239412" + d="M 428.77151 444.83228 A 9.8239412 9.8239412 0 1 1 409.12363,444.83228 A 9.8239412 9.8239412 0 1 1 428.77151 444.83228 z" + transform="matrix(1.014646,0,0,1.014647,-405.1135,-416.9803)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:type="arc" + style="fill:url(#radialGradient11636);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path11628" + sodipodi:cx="418.51233" + sodipodi:cy="446.44885" + sodipodi:rx="11.005301" + sodipodi:ry="11.005301" + d="M 429.51763 446.44885 A 11.005301 11.005301 0 1 1 407.50703,446.44885 A 11.005301 11.005301 0 1 1 429.51763 446.44885 z" + transform="matrix(0.810701,0,0,0.810702,-319.3183,-327.5696)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + style="opacity:0.7;fill:url(#linearGradient11638);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 27.878882,34.367333 C 27.878882,38.733063 24.335692,34.874413 19.969982,34.874413 C 15.604272,34.874413 12.061082,38.733063 12.061082,34.367333 C 12.061082,30.001633 15.604272,26.458433 19.969982,26.458433 C 24.335692,26.458433 27.878882,30.001633 27.878882,34.367333 z " + id="path11630" + sodipodi:nodetypes="csssc" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" /> + </g> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/vadim/collection.png" + id="path11513" + d="M 27.322263,352.02827 L 27.373066,350.49643 C 24.826186,350.00139 22.590419,348.11976 21.683843,345.36819 C 21.154022,343.76009 21.161953,342.10104 21.600252,340.59631 C 21.589298,341.24219 21.684384,341.89225 21.893015,342.52548 C 22.68249,344.92165 24.915484,346.43949 27.500996,346.65248 L 27.578774,344.2939 L 32.690712,348.49919 L 27.322263,352.02827 z " + style="fill:url(#linearGradient11619);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient11621);stroke-width:0.43705663;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + <g + style="display:inline" + id="g11515" + transform="translate(-458.46441,350.51995)" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/22x22/actions/amarok_undo.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <g + transform="translate(5.9165,276.55858)" + id="use11517"> + <path + sodipodi:type="arc" + style="fill:url(#linearGradient11653);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path11642" + sodipodi:cx="366.7616" + sodipodi:cy="436.4032" + sodipodi:rx="10.639691" + sodipodi:ry="10.639691" + d="M 377.40129 436.4032 A 10.639691 10.639691 0 1 1 356.12191,436.4032 A 10.639691 10.639691 0 1 1 377.40129 436.4032 z" + transform="matrix(1.033865,0,0,1.033865,-359.2117,-416.8144)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:type="arc" + style="fill:url(#linearGradient11655);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path11644" + sodipodi:cx="418.94757" + sodipodi:cy="444.83228" + sodipodi:rx="9.8239412" + sodipodi:ry="9.8239412" + d="M 428.77151 444.83228 A 9.8239412 9.8239412 0 1 1 409.12363,444.83228 A 9.8239412 9.8239412 0 1 1 428.77151 444.83228 z" + transform="matrix(1.014646,0,0,1.014647,-405.1135,-416.9803)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:type="arc" + style="fill:url(#radialGradient11657);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path11646" + sodipodi:cx="418.51233" + sodipodi:cy="446.44885" + sodipodi:rx="11.005301" + sodipodi:ry="11.005301" + d="M 429.51763 446.44885 A 11.005301 11.005301 0 1 1 407.50703,446.44885 A 11.005301 11.005301 0 1 1 429.51763 446.44885 z" + transform="matrix(0.810701,0,0,0.810702,-319.3183,-327.5696)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + style="opacity:0.7;fill:url(#linearGradient11659);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 27.878882,34.367333 C 27.878882,38.733063 24.335692,34.874413 19.969982,34.874413 C 15.604272,34.874413 12.061082,38.733063 12.061082,34.367333 C 12.061082,30.001633 15.604272,26.458433 19.969982,26.458433 C 24.335692,26.458433 27.878882,30.001633 27.878882,34.367333 z " + id="path11648" + sodipodi:nodetypes="csssc" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" /> + </g> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + id="path11519" + d="M 24.820552,317.37071 L 24.769749,315.83886 C 27.316629,315.34382 29.552396,313.4622 30.458972,310.71063 C 30.988793,309.10253 30.980862,307.44348 30.542563,305.93874 C 30.553517,306.58463 30.458431,307.23468 30.2498,307.86791 C 29.460325,310.26409 27.227331,311.78192 24.641819,311.99491 L 24.564041,309.63634 L 19.452103,313.84163 L 24.820552,317.37071 z " + style="fill:url(#linearGradient11623);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient11625);stroke-width:0.43705663;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + <g + style="display:inline" + id="g11521" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/32x32/actions/amarok_redo.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" + transform="translate(-460.53581,388.96962)"> + <g + inkscape:export-ydpi="89.999962" + inkscape:export-xdpi="89.999962" + inkscape:export-filename="/home/vadim/amarok icons/1/22x22/actions/amarok_circle.png" + transform="matrix(1.454545,0,0,1.454545,35.21562,318.74783)" + id="use11523"> + <path + sodipodi:type="arc" + style="opacity:1;fill:url(#linearGradient11124);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path11116" + sodipodi:cx="366.7616" + sodipodi:cy="436.4032" + sodipodi:rx="10.639691" + sodipodi:ry="10.639691" + d="M 377.40129 436.4032 A 10.639691 10.639691 0 1 1 356.12191,436.4032 A 10.639691 10.639691 0 1 1 377.40129 436.4032 z" + transform="matrix(1.033865,0,0,1.033865,-358.4555,-432.1183)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:type="arc" + style="opacity:1;fill:url(#linearGradient11126);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path11118" + sodipodi:cx="418.94757" + sodipodi:cy="444.83228" + sodipodi:rx="9.8239412" + sodipodi:ry="9.8239412" + d="M 428.77151 444.83228 A 9.8239412 9.8239412 0 1 1 409.12363,444.83228 A 9.8239412 9.8239412 0 1 1 428.77151 444.83228 z" + transform="matrix(1.068817,0,0,1.068817,-427.0518,-456.3806)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:type="arc" + style="opacity:1;fill:url(#radialGradient11128);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path11120" + sodipodi:cx="418.51233" + sodipodi:cy="446.44885" + sodipodi:rx="11.005301" + sodipodi:ry="11.005301" + d="M 429.51763 446.44885 A 11.005301 11.005301 0 1 1 407.50703,446.44885 A 11.005301 11.005301 0 1 1 429.51763 446.44885 z" + transform="matrix(0.908653,0,0,0.908654,-359.556,-386.6038)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + style="opacity:0.7;fill:url(#linearGradient11130);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 30.226478,19.072457 C 30.226478,24.316478 25.970475,19.68155 20.726479,19.68155 C 15.482481,19.68155 11.226478,24.316478 11.226478,19.072457 C 11.226478,13.828473 15.482481,9.5724574 20.726479,9.5724574 C 25.970475,9.5724574 30.226478,13.828473 30.226478,19.072457 z " + id="path11122" + sodipodi:nodetypes="csssc" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" /> + </g> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/vadim/collection.png" + id="path11525" + d="M 66.914031,355.85055 L 66.987927,353.62241 C 63.283375,352.90235 60.031351,350.16544 58.712696,346.16316 C 57.942047,343.82411 57.953583,341.41094 58.591109,339.22224 C 58.575176,340.16171 58.713483,341.10724 59.016946,342.0283 C 60.165273,345.51365 63.413263,347.72141 67.174007,348.03121 L 67.287138,344.60056 L 74.722682,350.71734 L 66.914031,355.85055 z " + style="fill:url(#linearGradient11627);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient11629);stroke-width:0.58181798;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + <g + style="display:inline" + id="g11527" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/32x32/actions/amarok_undo.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" + transform="translate(-463.53581,344.62695)"> + <g + inkscape:export-ydpi="89.999962" + inkscape:export-xdpi="89.999962" + inkscape:export-filename="/home/vadim/amarok icons/1/22x22/actions/amarok_circle.png" + transform="matrix(1.454545,0,0,1.454545,35.20348,284.09023)" + id="use11529"> + <path + sodipodi:type="arc" + style="opacity:1;fill:url(#linearGradient11143);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path11134" + sodipodi:cx="366.7616" + sodipodi:cy="436.4032" + sodipodi:rx="10.639691" + sodipodi:ry="10.639691" + d="M 377.40129 436.4032 A 10.639691 10.639691 0 1 1 356.12191,436.4032 A 10.639691 10.639691 0 1 1 377.40129 436.4032 z" + transform="matrix(1.033865,0,0,1.033865,-358.4555,-432.1183)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:type="arc" + style="opacity:1;fill:url(#linearGradient11145);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path11136" + sodipodi:cx="418.94757" + sodipodi:cy="444.83228" + sodipodi:rx="9.8239412" + sodipodi:ry="9.8239412" + d="M 428.77151 444.83228 A 9.8239412 9.8239412 0 1 1 409.12363,444.83228 A 9.8239412 9.8239412 0 1 1 428.77151 444.83228 z" + transform="matrix(1.068817,0,0,1.068817,-427.0518,-456.3806)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:type="arc" + style="opacity:1;fill:url(#radialGradient11147);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path11138" + sodipodi:cx="418.51233" + sodipodi:cy="446.44885" + sodipodi:rx="11.005301" + sodipodi:ry="11.005301" + d="M 429.51763 446.44885 A 11.005301 11.005301 0 1 1 407.50703,446.44885 A 11.005301 11.005301 0 1 1 429.51763 446.44885 z" + transform="matrix(0.908653,0,0,0.908654,-359.556,-386.6038)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + style="opacity:0.7;fill:url(#linearGradient11149);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 30.226478,19.072457 C 30.226478,24.316478 25.970475,19.68155 20.726479,19.68155 C 15.482481,19.68155 11.226478,24.316478 11.226478,19.072457 C 11.226478,13.828473 15.482481,9.5724574 20.726479,9.5724574 C 25.970475,9.5724574 30.226478,13.828473 30.226478,19.072457 z " + id="path11140" + sodipodi:nodetypes="csssc" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" /> + </g> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + id="path11531" + d="M 63.800205,321.19294 L 63.72631,318.96481 C 67.430861,318.24475 70.682885,315.50784 72.001541,311.50556 C 72.772189,309.1665 72.760653,306.75334 72.123127,304.56464 C 72.13906,305.5041 72.000754,306.44964 71.69729,307.3707 C 70.548963,310.85605 67.300973,313.06381 63.54023,313.37361 L 63.427098,309.94295 L 55.991554,316.05974 L 63.800205,321.19294 z " + style="fill:url(#linearGradient11631);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient11633);stroke-width:0.58181798;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + <g + style="display:inline" + id="g11533" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/32x32/actions/amarok_back.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" + transform="translate(-465.04245,157.15297)"> + <g + inkscape:export-ydpi="89.999962" + inkscape:export-xdpi="89.999962" + inkscape:export-filename="/home/vadim/amarok icons/1/22x22/actions/amarok_circle.png" + transform="matrix(1.454545,0,0,1.454545,36.21921,107.21293)" + id="use11535"> + <path + sodipodi:type="arc" + style="opacity:1;fill:url(#linearGradient11161);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path11153" + sodipodi:cx="366.7616" + sodipodi:cy="436.4032" + sodipodi:rx="10.639691" + sodipodi:ry="10.639691" + d="M 377.40129 436.4032 A 10.639691 10.639691 0 1 1 356.12191,436.4032 A 10.639691 10.639691 0 1 1 377.40129 436.4032 z" + transform="matrix(1.033865,0,0,1.033865,-358.4555,-432.1183)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:type="arc" + style="opacity:1;fill:url(#linearGradient11163);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path11155" + sodipodi:cx="418.94757" + sodipodi:cy="444.83228" + sodipodi:rx="9.8239412" + sodipodi:ry="9.8239412" + d="M 428.77151 444.83228 A 9.8239412 9.8239412 0 1 1 409.12363,444.83228 A 9.8239412 9.8239412 0 1 1 428.77151 444.83228 z" + transform="matrix(1.068817,0,0,1.068817,-427.0518,-456.3806)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:type="arc" + style="opacity:1;fill:url(#radialGradient11165);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path11157" + sodipodi:cx="418.51233" + sodipodi:cy="446.44885" + sodipodi:rx="11.005301" + sodipodi:ry="11.005301" + d="M 429.51763 446.44885 A 11.005301 11.005301 0 1 1 407.50703,446.44885 A 11.005301 11.005301 0 1 1 429.51763 446.44885 z" + transform="matrix(0.908653,0,0,0.908654,-359.556,-386.6038)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + style="opacity:0.7;fill:url(#linearGradient11167);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 30.226478,19.072457 C 30.226478,24.316478 25.970475,19.68155 20.726479,19.68155 C 15.482481,19.68155 11.226478,24.316478 11.226478,19.072457 C 11.226478,13.828473 15.482481,9.5724574 20.726479,9.5724574 C 25.970475,9.5724574 30.226478,13.828473 30.226478,19.072457 z " + id="path11159" + sodipodi:nodetypes="csssc" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" /> + </g> + <g + style="fill:url(#linearGradient11635);fill-opacity:1" + transform="matrix(0.284505,0,0,0.284505,-212.99,111.7714)" + id="g11537"> + <g + style="fill:url(#linearGradient11637);fill-opacity:1" + transform="matrix(0.51291,0,0,0.51291,468.129,-559.841)" + id="g11539"> + <path + sodipodi:nodetypes="ccccccccc" + id="path11541" + d="M 1044.2816,1200.1729 L 1044.5628,1300.3931 L 982.99023,1261.4011 L 982.98547,1300.3931 L 958.81862,1300.3931 L 958.81862,1200.1729 L 982.90595,1200.1729 L 982.9123,1238.4265 L 1044.2816,1200.1729 z " + style="fill:url(#linearGradient11639);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient11642);stroke-width:4.11168814;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + </g> + </g> + <g + style="display:inline" + id="g11543" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/32x32/actions/amarok_rewind.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" + transform="translate(-465.52906,77.848348)"> + <g + inkscape:export-ydpi="89.999962" + inkscape:export-xdpi="89.999962" + inkscape:export-filename="/home/vadim/amarok icons/1/22x22/actions/amarok_circle.png" + transform="matrix(1.454545,0,0,1.454545,35.71411,34.860727)" + id="use11545"> + <path + sodipodi:type="arc" + style="opacity:1;fill:url(#linearGradient11179);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path11171" + sodipodi:cx="366.7616" + sodipodi:cy="436.4032" + sodipodi:rx="10.639691" + sodipodi:ry="10.639691" + d="M 377.40129 436.4032 A 10.639691 10.639691 0 1 1 356.12191,436.4032 A 10.639691 10.639691 0 1 1 377.40129 436.4032 z" + transform="matrix(1.033865,0,0,1.033865,-358.4555,-432.1183)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:type="arc" + style="opacity:1;fill:url(#linearGradient11181);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path11173" + sodipodi:cx="418.94757" + sodipodi:cy="444.83228" + sodipodi:rx="9.8239412" + sodipodi:ry="9.8239412" + d="M 428.77151 444.83228 A 9.8239412 9.8239412 0 1 1 409.12363,444.83228 A 9.8239412 9.8239412 0 1 1 428.77151 444.83228 z" + transform="matrix(1.068817,0,0,1.068817,-427.0518,-456.3806)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:type="arc" + style="opacity:1;fill:url(#radialGradient11183);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path11175" + sodipodi:cx="418.51233" + sodipodi:cy="446.44885" + sodipodi:rx="11.005301" + sodipodi:ry="11.005301" + d="M 429.51763 446.44885 A 11.005301 11.005301 0 1 1 407.50703,446.44885 A 11.005301 11.005301 0 1 1 429.51763 446.44885 z" + transform="matrix(0.908653,0,0,0.908654,-359.556,-386.6038)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + style="opacity:0.7;fill:url(#linearGradient11185);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 30.226478,19.072457 C 30.226478,24.316478 25.970475,19.68155 20.726479,19.68155 C 15.482481,19.68155 11.226478,24.316478 11.226478,19.072457 C 11.226478,13.828473 15.482481,9.5724574 20.726479,9.5724574 C 25.970475,9.5724574 30.226478,13.828473 30.226478,19.072457 z " + id="path11177" + sodipodi:nodetypes="csssc" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" /> + </g> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Playback-controls/LightBlue/blue1/32/amarok_rewind.png" + style="fill:url(#linearGradient11645);fill-opacity:1" + id="g11547" + transform="matrix(0.128302,0,0,0.128302,-60.69367,-97.87383)"> + <path + style="fill:url(#linearGradient11647);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient11650);stroke-width:4.53475142;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 1044.2816,1200.1729 L 1044.5628,1300.8916 L 982.99023,1261.4011 L 982.98547,1301.2273 L 909.54438,1251.1264 L 982.90595,1200.116 L 982.9123,1238.4265 L 1044.2816,1200.1729 z " + id="path11549" + sodipodi:nodetypes="cccccccc" /> + </g> + </g> + <g + style="display:inline" + id="g11551" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/22x22/actions/amarok_rewind.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" + transform="translate(-457.10638,82.772858)"> + <g + transform="translate(2.5056,28.29762)" + id="use11553"> + <path + sodipodi:type="arc" + style="fill:url(#linearGradient11679);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path11663" + sodipodi:cx="366.7616" + sodipodi:cy="436.4032" + sodipodi:rx="10.639691" + sodipodi:ry="10.639691" + d="M 377.40129 436.4032 A 10.639691 10.639691 0 1 1 356.12191,436.4032 A 10.639691 10.639691 0 1 1 377.40129 436.4032 z" + transform="matrix(1.033865,0,0,1.033865,-359.2117,-416.8144)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:type="arc" + style="fill:url(#linearGradient11681);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path11665" + sodipodi:cx="418.94757" + sodipodi:cy="444.83228" + sodipodi:rx="9.8239412" + sodipodi:ry="9.8239412" + d="M 428.77151 444.83228 A 9.8239412 9.8239412 0 1 1 409.12363,444.83228 A 9.8239412 9.8239412 0 1 1 428.77151 444.83228 z" + transform="matrix(1.014646,0,0,1.014647,-405.1135,-416.9803)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:type="arc" + style="fill:url(#radialGradient11683);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path11667" + sodipodi:cx="418.51233" + sodipodi:cy="446.44885" + sodipodi:rx="11.005301" + sodipodi:ry="11.005301" + d="M 429.51763 446.44885 A 11.005301 11.005301 0 1 1 407.50703,446.44885 A 11.005301 11.005301 0 1 1 429.51763 446.44885 z" + transform="matrix(0.810701,0,0,0.810702,-319.3183,-327.5696)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + style="opacity:0.7;fill:url(#linearGradient11686);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 27.878882,34.367333 C 27.878882,38.733063 24.335692,34.874413 19.969982,34.874413 C 15.604272,34.874413 12.061082,38.733063 12.061082,34.367333 C 12.061082,30.001633 15.604272,26.458433 19.969982,26.458433 C 24.335692,26.458433 27.878882,30.001633 27.878882,34.367333 z " + id="path11669" + sodipodi:nodetypes="csssc" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" /> + </g> + <path + sodipodi:nodetypes="cccccccc" + id="path11555" + d="M 27.582858,58.210831 L 27.607662,67.094996 L 22.176487,63.61163 L 22.176067,67.124607 L 15.697998,62.705322 L 22.169053,58.205812 L 22.169613,61.585093 L 27.582858,58.210831 z " + style="fill:url(#linearGradient11652);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient11654);stroke-width:0.4804728;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + <g + style="display:inline" + id="g11557" + transform="translate(-454.10638,121.92057)" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/22x22/actions/amarok_fastforward.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <g + transform="translate(2.5056,64.15804)" + id="use11559"> + <path + sodipodi:type="arc" + style="fill:url(#linearGradient11702);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path11690" + sodipodi:cx="366.7616" + sodipodi:cy="436.4032" + sodipodi:rx="10.639691" + sodipodi:ry="10.639691" + d="M 377.40129 436.4032 A 10.639691 10.639691 0 1 1 356.12191,436.4032 A 10.639691 10.639691 0 1 1 377.40129 436.4032 z" + transform="matrix(1.033865,0,0,1.033865,-359.2117,-416.8144)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:type="arc" + style="fill:url(#linearGradient11704);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path11692" + sodipodi:cx="418.94757" + sodipodi:cy="444.83228" + sodipodi:rx="9.8239412" + sodipodi:ry="9.8239412" + d="M 428.77151 444.83228 A 9.8239412 9.8239412 0 1 1 409.12363,444.83228 A 9.8239412 9.8239412 0 1 1 428.77151 444.83228 z" + transform="matrix(1.014646,0,0,1.014647,-405.1135,-416.9803)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:type="arc" + style="fill:url(#radialGradient11706);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path11694" + sodipodi:cx="418.51233" + sodipodi:cy="446.44885" + sodipodi:rx="11.005301" + sodipodi:ry="11.005301" + d="M 429.51763 446.44885 A 11.005301 11.005301 0 1 1 407.50703,446.44885 A 11.005301 11.005301 0 1 1 429.51763 446.44885 z" + transform="matrix(0.810701,0,0,0.810702,-319.3183,-327.5696)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + style="opacity:0.7;fill:url(#linearGradient11708);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 27.878882,34.367333 C 27.878882,38.733063 24.335692,34.874413 19.969982,34.874413 C 15.604272,34.874413 12.061082,38.733063 12.061082,34.367333 C 12.061082,30.001633 15.604272,26.458433 19.969982,26.458433 C 24.335692,26.458433 27.878882,30.001633 27.878882,34.367333 z " + id="path11696" + sodipodi:nodetypes="csssc" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" /> + </g> + <g + style="fill:url(#linearGradient11656);fill-opacity:1" + transform="matrix(-8.82077e-2,0,0,8.82077e-2,109.4968,-11.79324)" + id="g11561"> + <path + sodipodi:nodetypes="cccccccc" + id="path11563" + d="M 1044.2816,1200.1729 L 1044.5628,1300.8916 L 982.99023,1261.4011 L 982.98547,1301.2273 L 909.54438,1251.1264 L 982.90595,1200.116 L 982.9123,1238.4265 L 1044.2816,1200.1729 z " + style="fill:url(#linearGradient11658);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient11660);stroke-width:5.44706202;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + </g> + <g + style="display:inline" + id="g11565" + transform="translate(-459.17778,117.62742)" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/32x32/actions/amarok_fastforward.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <g + inkscape:export-ydpi="89.999962" + inkscape:export-xdpi="89.999962" + inkscape:export-filename="/home/vadim/amarok icons/1/22x22/actions/amarok_circle.png" + transform="matrix(1.454545,0,0,1.454545,36.21933,70.089787)" + id="use11567"> + <path + sodipodi:type="arc" + style="opacity:1;fill:url(#linearGradient11197);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path11189" + sodipodi:cx="366.7616" + sodipodi:cy="436.4032" + sodipodi:rx="10.639691" + sodipodi:ry="10.639691" + d="M 377.40129 436.4032 A 10.639691 10.639691 0 1 1 356.12191,436.4032 A 10.639691 10.639691 0 1 1 377.40129 436.4032 z" + transform="matrix(1.033865,0,0,1.033865,-358.4555,-432.1183)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:type="arc" + style="opacity:1;fill:url(#linearGradient11199);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path11191" + sodipodi:cx="418.94757" + sodipodi:cy="444.83228" + sodipodi:rx="9.8239412" + sodipodi:ry="9.8239412" + d="M 428.77151 444.83228 A 9.8239412 9.8239412 0 1 1 409.12363,444.83228 A 9.8239412 9.8239412 0 1 1 428.77151 444.83228 z" + transform="matrix(1.068817,0,0,1.068817,-427.0518,-456.3806)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:type="arc" + style="opacity:1;fill:url(#radialGradient11201);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path11193" + sodipodi:cx="418.51233" + sodipodi:cy="446.44885" + sodipodi:rx="11.005301" + sodipodi:ry="11.005301" + d="M 429.51763 446.44885 A 11.005301 11.005301 0 1 1 407.50703,446.44885 A 11.005301 11.005301 0 1 1 429.51763 446.44885 z" + transform="matrix(0.908653,0,0,0.908654,-359.556,-386.6038)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + style="opacity:0.7;fill:url(#linearGradient11203);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 30.226478,19.072457 C 30.226478,24.316478 25.970475,19.68155 20.726479,19.68155 C 15.482481,19.68155 11.226478,24.316478 11.226478,19.072457 C 11.226478,13.828473 15.482481,9.5724574 20.726479,9.5724574 C 25.970475,9.5724574 30.226478,13.828473 30.226478,19.072457 z " + id="path11195" + sodipodi:nodetypes="csssc" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" /> + </g> + <g + style="fill:url(#linearGradient11663);fill-opacity:1" + transform="matrix(-0.128302,0,0,0.128302,192.943,-62.64477)" + id="g11569"> + <path + sodipodi:nodetypes="cccccccc" + id="path11571" + d="M 1044.2816,1200.1729 L 1044.5628,1300.8916 L 982.99023,1261.4011 L 982.98547,1301.2273 L 909.54438,1251.1264 L 982.90595,1200.116 L 982.9123,1238.4265 L 1044.2816,1200.1729 z " + style="fill:url(#linearGradient11666);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient11668);stroke-width:4.53475142;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + </g> + <g + style="display:inline" + id="g11573" + transform="translate(-458.29541,165.30255)" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/22x22/actions/amarok_back.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <g + transform="translate(3.7683,100.77608)" + id="use11575"> + <path + sodipodi:type="arc" + style="fill:url(#linearGradient11720);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path11712" + sodipodi:cx="366.7616" + sodipodi:cy="436.4032" + sodipodi:rx="10.639691" + sodipodi:ry="10.639691" + d="M 377.40129 436.4032 A 10.639691 10.639691 0 1 1 356.12191,436.4032 A 10.639691 10.639691 0 1 1 377.40129 436.4032 z" + transform="matrix(1.033865,0,0,1.033865,-359.2117,-416.8144)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:type="arc" + style="fill:url(#linearGradient11722);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path11714" + sodipodi:cx="418.94757" + sodipodi:cy="444.83228" + sodipodi:rx="9.8239412" + sodipodi:ry="9.8239412" + d="M 428.77151 444.83228 A 9.8239412 9.8239412 0 1 1 409.12363,444.83228 A 9.8239412 9.8239412 0 1 1 428.77151 444.83228 z" + transform="matrix(1.014646,0,0,1.014647,-405.1135,-416.9803)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:type="arc" + style="fill:url(#radialGradient11724);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path11716" + sodipodi:cx="418.51233" + sodipodi:cy="446.44885" + sodipodi:rx="11.005301" + sodipodi:ry="11.005301" + d="M 429.51763 446.44885 A 11.005301 11.005301 0 1 1 407.50703,446.44885 A 11.005301 11.005301 0 1 1 429.51763 446.44885 z" + transform="matrix(0.810701,0,0,0.810702,-319.3183,-327.5696)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + style="opacity:0.7;fill:url(#linearGradient11726);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 27.878882,34.367333 C 27.878882,38.733063 24.335692,34.874413 19.969982,34.874413 C 15.604272,34.874413 12.061082,38.733063 12.061082,34.367333 C 12.061082,30.001633 15.604272,26.458433 19.969982,26.458433 C 24.335692,26.458433 27.878882,30.001633 27.878882,34.367333 z " + id="path11718" + sodipodi:nodetypes="csssc" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" /> + </g> + <g + id="g11577" + transform="matrix(0.192419,0,0,0.184587,-165.3865,120.268)" + style="fill:url(#linearGradient11671);fill-opacity:1"> + <g + id="g11579" + transform="matrix(0.51291,0,0,0.51291,468.129,-559.841)" + style="fill:url(#linearGradient11673);fill-opacity:1"> + <path + style="fill:url(#linearGradient11675);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient11677);stroke-width:5.44706202;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 1044.2816,1200.1729 L 1044.5628,1300.3931 L 982.99023,1261.4011 L 982.98547,1300.3931 L 958.81862,1300.3931 L 958.81862,1200.1729 L 982.90595,1200.1729 L 982.9123,1238.4265 L 1044.2816,1200.1729 z " + id="path11581" + sodipodi:nodetypes="ccccccccc" /> + </g> + </g> + </g> + <g + transform="matrix(0.727272,0,0,0.727272,-483.97658,102.86343)" + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/16x16/actions/amarok_rewind.png" + id="g11679" + style="display:inline"> + <g + transform="translate(2.5056,28.29762)" + id="use11681"> + <path + sodipodi:type="arc" + style="fill:url(#linearGradient11738);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path11730" + sodipodi:cx="366.7616" + sodipodi:cy="436.4032" + sodipodi:rx="10.639691" + sodipodi:ry="10.639691" + d="M 377.40129 436.4032 A 10.639691 10.639691 0 1 1 356.12191,436.4032 A 10.639691 10.639691 0 1 1 377.40129 436.4032 z" + transform="matrix(1.033865,0,0,1.033865,-359.2117,-416.8144)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:type="arc" + style="fill:url(#linearGradient11740);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path11732" + sodipodi:cx="418.94757" + sodipodi:cy="444.83228" + sodipodi:rx="9.8239412" + sodipodi:ry="9.8239412" + d="M 428.77151 444.83228 A 9.8239412 9.8239412 0 1 1 409.12363,444.83228 A 9.8239412 9.8239412 0 1 1 428.77151 444.83228 z" + transform="matrix(1.014646,0,0,1.014647,-405.1135,-416.9803)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:type="arc" + style="fill:url(#radialGradient11742);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path11734" + sodipodi:cx="418.51233" + sodipodi:cy="446.44885" + sodipodi:rx="11.005301" + sodipodi:ry="11.005301" + d="M 429.51763 446.44885 A 11.005301 11.005301 0 1 1 407.50703,446.44885 A 11.005301 11.005301 0 1 1 429.51763 446.44885 z" + transform="matrix(0.810701,0,0,0.810702,-319.3183,-327.5696)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + style="opacity:0.7;fill:url(#linearGradient11744);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 27.878882,34.367333 C 27.878882,38.733063 24.335692,34.874413 19.969982,34.874413 C 15.604272,34.874413 12.061082,38.733063 12.061082,34.367333 C 12.061082,30.001633 15.604272,26.458433 19.969982,26.458433 C 24.335692,26.458433 27.878882,30.001633 27.878882,34.367333 z " + id="path11736" + sodipodi:nodetypes="csssc" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" /> + </g> + <path + style="fill:url(#linearGradient11685);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient11687);stroke-width:0.66065037;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 27.582858,58.210831 L 27.607662,67.094996 L 22.176487,63.61163 L 22.176067,67.124607 L 15.697998,62.705322 L 22.169053,58.205812 L 22.169613,61.585093 L 27.582858,58.210831 z " + id="path11683" + sodipodi:nodetypes="cccccccc" /> + </g> + <g + transform="matrix(0.727272,0,0,0.727272,-480.97658,151.79128)" + id="g11689" + style="display:inline" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/16x16/actions/amarok_fastforward.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <g + transform="translate(2.5056,64.15804)" + id="use11691"> + <path + sodipodi:type="arc" + style="fill:url(#linearGradient11756);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path11748" + sodipodi:cx="366.7616" + sodipodi:cy="436.4032" + sodipodi:rx="10.639691" + sodipodi:ry="10.639691" + d="M 377.40129 436.4032 A 10.639691 10.639691 0 1 1 356.12191,436.4032 A 10.639691 10.639691 0 1 1 377.40129 436.4032 z" + transform="matrix(1.033865,0,0,1.033865,-359.2117,-416.8144)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:type="arc" + style="fill:url(#linearGradient11758);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path11750" + sodipodi:cx="418.94757" + sodipodi:cy="444.83228" + sodipodi:rx="9.8239412" + sodipodi:ry="9.8239412" + d="M 428.77151 444.83228 A 9.8239412 9.8239412 0 1 1 409.12363,444.83228 A 9.8239412 9.8239412 0 1 1 428.77151 444.83228 z" + transform="matrix(1.014646,0,0,1.014647,-405.1135,-416.9803)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:type="arc" + style="fill:url(#radialGradient11760);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path11752" + sodipodi:cx="418.51233" + sodipodi:cy="446.44885" + sodipodi:rx="11.005301" + sodipodi:ry="11.005301" + d="M 429.51763 446.44885 A 11.005301 11.005301 0 1 1 407.50703,446.44885 A 11.005301 11.005301 0 1 1 429.51763 446.44885 z" + transform="matrix(0.810701,0,0,0.810702,-319.3183,-327.5696)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + style="opacity:0.7;fill:url(#linearGradient11762);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 27.878882,34.367333 C 27.878882,38.733063 24.335692,34.874413 19.969982,34.874413 C 15.604272,34.874413 12.061082,38.733063 12.061082,34.367333 C 12.061082,30.001633 15.604272,26.458433 19.969982,26.458433 C 24.335692,26.458433 27.878882,30.001633 27.878882,34.367333 z " + id="path11754" + sodipodi:nodetypes="csssc" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" /> + </g> + <g + id="g11693" + transform="matrix(-8.82077e-2,0,0,8.82077e-2,109.4968,-11.79324)" + style="fill:url(#linearGradient11697);fill-opacity:1"> + <path + style="fill:url(#linearGradient11699);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient11701);stroke-width:7.48971319;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 1044.2816,1200.1729 L 1044.5628,1300.8916 L 982.99023,1261.4011 L 982.98547,1301.2273 L 909.54438,1251.1264 L 982.90595,1200.116 L 982.9123,1238.4265 L 1044.2816,1200.1729 z " + id="path11695" + sodipodi:nodetypes="cccccccc" /> + </g> + </g> + <g + transform="matrix(0.727272,0,0,0.727272,-486.82121,205.16005)" + id="g11703" + style="display:inline" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/16x16/actions/amarok_back.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <g + transform="translate(3.7683,100.77608)" + id="use11705"> + <path + sodipodi:type="arc" + style="fill:url(#linearGradient11774);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path11766" + sodipodi:cx="366.7616" + sodipodi:cy="436.4032" + sodipodi:rx="10.639691" + sodipodi:ry="10.639691" + d="M 377.40129 436.4032 A 10.639691 10.639691 0 1 1 356.12191,436.4032 A 10.639691 10.639691 0 1 1 377.40129 436.4032 z" + transform="matrix(1.033865,0,0,1.033865,-359.2117,-416.8144)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:type="arc" + style="fill:url(#linearGradient11777);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path11768" + sodipodi:cx="418.94757" + sodipodi:cy="444.83228" + sodipodi:rx="9.8239412" + sodipodi:ry="9.8239412" + d="M 428.77151 444.83228 A 9.8239412 9.8239412 0 1 1 409.12363,444.83228 A 9.8239412 9.8239412 0 1 1 428.77151 444.83228 z" + transform="matrix(1.014646,0,0,1.014647,-405.1135,-416.9803)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:type="arc" + style="fill:url(#radialGradient11779);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path11770" + sodipodi:cx="418.51233" + sodipodi:cy="446.44885" + sodipodi:rx="11.005301" + sodipodi:ry="11.005301" + d="M 429.51763 446.44885 A 11.005301 11.005301 0 1 1 407.50703,446.44885 A 11.005301 11.005301 0 1 1 429.51763 446.44885 z" + transform="matrix(0.810701,0,0,0.810702,-319.3183,-327.5696)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + style="opacity:0.7;fill:url(#linearGradient11781);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 27.878882,34.367333 C 27.878882,38.733063 24.335692,34.874413 19.969982,34.874413 C 15.604272,34.874413 12.061082,38.733063 12.061082,34.367333 C 12.061082,30.001633 15.604272,26.458433 19.969982,26.458433 C 24.335692,26.458433 27.878882,30.001633 27.878882,34.367333 z " + id="path11772" + sodipodi:nodetypes="csssc" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" /> + </g> + <g + style="fill:url(#linearGradient11713);fill-opacity:1" + transform="matrix(0.192419,0,0,0.184587,-165.3865,120.268)" + id="g11707"> + <g + style="fill:url(#linearGradient11715);fill-opacity:1" + transform="matrix(0.51291,0,0,0.51291,468.129,-559.841)" + id="g11709"> + <path + sodipodi:nodetypes="ccccccccc" + id="path11711" + d="M 1044.2816,1200.1729 L 1044.5628,1300.3931 L 982.99023,1261.4011 L 982.98547,1300.3931 L 958.81862,1300.3931 L 958.81862,1200.1729 L 982.90595,1200.1729 L 982.9123,1238.4265 L 1044.2816,1200.1729 z " + style="fill:url(#linearGradient11717);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient11719);stroke-width:7.48971319;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + </g> + </g> + <g + transform="matrix(0.727272,0,0,0.727272,-484.79231,253.22186)" + id="g11721" + style="display:inline" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/16x16/actions/amarok_play.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <g + transform="translate(4.4941,137.81598)" + id="use11723"> + <path + sodipodi:type="arc" + style="fill:url(#linearGradient11793);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path11785" + sodipodi:cx="366.7616" + sodipodi:cy="436.4032" + sodipodi:rx="10.639691" + sodipodi:ry="10.639691" + d="M 377.40129 436.4032 A 10.639691 10.639691 0 1 1 356.12191,436.4032 A 10.639691 10.639691 0 1 1 377.40129 436.4032 z" + transform="matrix(1.033865,0,0,1.033865,-359.2117,-416.8144)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:type="arc" + style="fill:url(#linearGradient11795);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path11787" + sodipodi:cx="418.94757" + sodipodi:cy="444.83228" + sodipodi:rx="9.8239412" + sodipodi:ry="9.8239412" + d="M 428.77151 444.83228 A 9.8239412 9.8239412 0 1 1 409.12363,444.83228 A 9.8239412 9.8239412 0 1 1 428.77151 444.83228 z" + transform="matrix(1.014646,0,0,1.014647,-405.1135,-416.9803)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:type="arc" + style="fill:url(#radialGradient11797);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path11789" + sodipodi:cx="418.51233" + sodipodi:cy="446.44885" + sodipodi:rx="11.005301" + sodipodi:ry="11.005301" + d="M 429.51763 446.44885 A 11.005301 11.005301 0 1 1 407.50703,446.44885 A 11.005301 11.005301 0 1 1 429.51763 446.44885 z" + transform="matrix(0.810701,0,0,0.810702,-319.3183,-327.5696)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + style="opacity:0.7;fill:url(#linearGradient11799);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 27.878882,34.367333 C 27.878882,38.733063 24.335692,34.874413 19.969982,34.874413 C 15.604272,34.874413 12.061082,38.733063 12.061082,34.367333 C 12.061082,30.001633 15.604272,26.458433 19.969982,26.458433 C 24.335692,26.458433 27.878882,30.001633 27.878882,34.367333 z " + id="path11791" + sodipodi:nodetypes="csssc" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" /> + </g> + <g + id="g11725" + transform="matrix(8.30464e-2,0,0,8.30464e-2,-66.65521,68.00942)" + inkscape:export-filename="/home/vadim/collection.png" + inkscape:export-xdpi="90.000000" + inkscape:export-ydpi="90.000000"> + <path + sodipodi:nodetypes="cccc" + id="path11727" + d="M 1064.5688,1194.5934 L 1064.2322,1314.225 L 1158.4102,1259.0756 L 1064.5688,1194.5934 z " + style="fill:url(#linearGradient11729);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient11731);stroke-width:8.89651394;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + </g> + <g + transform="matrix(0.727272,0,0,0.727272,-485.5656,298.11794)" + id="g11733" + style="display:inline" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/16x16/actions/amarok_stop.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <g + transform="translate(5.74401,173.70898)" + id="use11735"> + <path + sodipodi:type="arc" + style="fill:url(#linearGradient11811);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path11803" + sodipodi:cx="366.7616" + sodipodi:cy="436.4032" + sodipodi:rx="10.639691" + sodipodi:ry="10.639691" + d="M 377.40129 436.4032 A 10.639691 10.639691 0 1 1 356.12191,436.4032 A 10.639691 10.639691 0 1 1 377.40129 436.4032 z" + transform="matrix(1.033865,0,0,1.033865,-359.2117,-416.8144)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:type="arc" + style="fill:url(#linearGradient11814);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path11805" + sodipodi:cx="418.94757" + sodipodi:cy="444.83228" + sodipodi:rx="9.8239412" + sodipodi:ry="9.8239412" + d="M 428.77151 444.83228 A 9.8239412 9.8239412 0 1 1 409.12363,444.83228 A 9.8239412 9.8239412 0 1 1 428.77151 444.83228 z" + transform="matrix(1.014646,0,0,1.014647,-405.1135,-416.9803)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:type="arc" + style="fill:url(#radialGradient11816);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path11807" + sodipodi:cx="418.51233" + sodipodi:cy="446.44885" + sodipodi:rx="11.005301" + sodipodi:ry="11.005301" + d="M 429.51763 446.44885 A 11.005301 11.005301 0 1 1 407.50703,446.44885 A 11.005301 11.005301 0 1 1 429.51763 446.44885 z" + transform="matrix(0.810701,0,0,0.810702,-319.3183,-327.5696)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + style="opacity:0.7;fill:url(#linearGradient11819);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 27.878882,34.367333 C 27.878882,38.733063 24.335692,34.874413 19.969982,34.874413 C 15.604272,34.874413 12.061082,38.733063 12.061082,34.367333 C 12.061082,30.001633 15.604272,26.458433 19.969982,26.458433 C 24.335692,26.458433 27.878882,30.001633 27.878882,34.367333 z " + id="path11809" + sodipodi:nodetypes="csssc" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" /> + </g> + <g + id="g11737" + transform="matrix(7.924505e-2,0,0,7.601116e-2,-77.39848,113.1035)" + inkscape:export-filename="/home/derek/Desktop/Devel/icons.png" + inkscape:export-xdpi="90.000000" + inkscape:export-ydpi="90.000000"> + <g + id="g11739"> + <rect + style="opacity:1;fill:url(#linearGradient11743);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient11745);stroke-width:9.30161858;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect11741" + width="106.94586" + height="111.49675" + x="1247.7158" + y="1193.7136" /> + </g> + </g> + </g> + <g + transform="matrix(0.727272,0,0,0.727272,-486.96099,343.70245)" + id="g11747" + style="display:inline" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/32x32/actions/amarok_pause.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <g + transform="translate(6.1011,207.28028)" + id="use11749"> + <path + sodipodi:type="arc" + style="fill:url(#linearGradient11831);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path11823" + sodipodi:cx="366.7616" + sodipodi:cy="436.4032" + sodipodi:rx="10.639691" + sodipodi:ry="10.639691" + d="M 377.40129 436.4032 A 10.639691 10.639691 0 1 1 356.12191,436.4032 A 10.639691 10.639691 0 1 1 377.40129 436.4032 z" + transform="matrix(1.033865,0,0,1.033865,-359.2117,-416.8144)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:type="arc" + style="fill:url(#linearGradient11833);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path11825" + sodipodi:cx="418.94757" + sodipodi:cy="444.83228" + sodipodi:rx="9.8239412" + sodipodi:ry="9.8239412" + d="M 428.77151 444.83228 A 9.8239412 9.8239412 0 1 1 409.12363,444.83228 A 9.8239412 9.8239412 0 1 1 428.77151 444.83228 z" + transform="matrix(1.014646,0,0,1.014647,-405.1135,-416.9803)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:type="arc" + style="fill:url(#radialGradient11835);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path11827" + sodipodi:cx="418.51233" + sodipodi:cy="446.44885" + sodipodi:rx="11.005301" + sodipodi:ry="11.005301" + d="M 429.51763 446.44885 A 11.005301 11.005301 0 1 1 407.50703,446.44885 A 11.005301 11.005301 0 1 1 429.51763 446.44885 z" + transform="matrix(0.810701,0,0,0.810702,-319.3183,-327.5696)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + style="opacity:0.7;fill:url(#linearGradient11837);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 27.878882,34.367333 C 27.878882,38.733063 24.335692,34.874413 19.969982,34.874413 C 15.604272,34.874413 12.061082,38.733063 12.061082,34.367333 C 12.061082,30.001633 15.604272,26.458433 19.969982,26.458433 C 24.335692,26.458433 27.878882,30.001633 27.878882,34.367333 z " + id="path11829" + sodipodi:nodetypes="csssc" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" /> + </g> + <g + style="fill:#943c3e;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:6.68458128;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="g11751" + transform="matrix(4.559546e-2,0,0,9.592578e-2,-44.46674,90.8665)" + inkscape:export-filename="/home/vadim/collection.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <path + id="path11753" + d="M 1577.4039,1526.7125 L 1577.4039,1616.4528 L 1663.6021,1616.4528 L 1663.6021,1526.7125 L 1577.4039,1526.7125 z M 1430.4823,1527.1891 L 1430.4823,1616.9975 L 1516.6806,1616.9975 L 1516.6806,1527.1891 L 1430.4823,1527.1891 z " + style="opacity:1;fill:url(#linearGradient11755);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient11757);stroke-width:7.37423611;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + </g> + <g + transform="translate(-455.46441,311.36975)" + id="g11759" + style="display:inline" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/22x22/actions/amarok_next.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <g + transform="translate(5.744,243.70878)" + id="use11761"> + <path + sodipodi:type="arc" + style="fill:url(#linearGradient11849);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path11841" + sodipodi:cx="366.7616" + sodipodi:cy="436.4032" + sodipodi:rx="10.639691" + sodipodi:ry="10.639691" + d="M 377.40129 436.4032 A 10.639691 10.639691 0 1 1 356.12191,436.4032 A 10.639691 10.639691 0 1 1 377.40129 436.4032 z" + transform="matrix(1.033865,0,0,1.033865,-359.2117,-416.8144)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:type="arc" + style="fill:url(#linearGradient11851);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path11843" + sodipodi:cx="418.94757" + sodipodi:cy="444.83228" + sodipodi:rx="9.8239412" + sodipodi:ry="9.8239412" + d="M 428.77151 444.83228 A 9.8239412 9.8239412 0 1 1 409.12363,444.83228 A 9.8239412 9.8239412 0 1 1 428.77151 444.83228 z" + transform="matrix(1.014646,0,0,1.014647,-405.1135,-416.9803)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:type="arc" + style="fill:url(#radialGradient11853);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path11845" + sodipodi:cx="418.51233" + sodipodi:cy="446.44885" + sodipodi:rx="11.005301" + sodipodi:ry="11.005301" + d="M 429.51763 446.44885 A 11.005301 11.005301 0 1 1 407.50703,446.44885 A 11.005301 11.005301 0 1 1 429.51763 446.44885 z" + transform="matrix(0.810701,0,0,0.810702,-319.3183,-327.5696)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + style="opacity:0.7;fill:url(#linearGradient11855);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 27.878882,34.367333 C 27.878882,38.733063 24.335692,34.874413 19.969982,34.874413 C 15.604272,34.874413 12.061082,38.733063 12.061082,34.367333 C 12.061082,30.001633 15.604272,26.458433 19.969982,26.458433 C 24.335692,26.458433 27.878882,30.001633 27.878882,34.367333 z " + id="path11847" + sodipodi:nodetypes="csssc" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" /> + </g> + <g + id="g11763" + transform="matrix(-0.192419,0,0,0.184587,214.6517,263.0434)"> + <g + id="g11765" + transform="matrix(0.51291,0,0,0.51291,468.129,-559.841)"> + <path + style="fill:url(#linearGradient11769);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient11771);stroke-width:5.44706106;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 1044.2816,1200.1729 L 1044.5628,1300.3931 L 982.99023,1261.4011 L 982.98547,1300.3931 L 958.81862,1300.3931 L 958.81862,1200.1729 L 982.90595,1200.1729 L 982.9123,1238.4265 L 1044.2816,1200.1729 z " + id="path11767" + sodipodi:nodetypes="ccccccccc" /> + </g> + </g> + </g> + <g + transform="matrix(0.727272,0,0,0.727272,-488.40431,438.31795)" + id="g11773" + style="display:inline" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/16x16/actions/amarok_undo.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <g + transform="translate(5.9165,276.55858)" + id="use11775"> + <path + sodipodi:type="arc" + style="fill:url(#linearGradient11867);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path11859" + sodipodi:cx="366.7616" + sodipodi:cy="436.4032" + sodipodi:rx="10.639691" + sodipodi:ry="10.639691" + d="M 377.40129 436.4032 A 10.639691 10.639691 0 1 1 356.12191,436.4032 A 10.639691 10.639691 0 1 1 377.40129 436.4032 z" + transform="matrix(1.033865,0,0,1.033865,-359.2117,-416.8144)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:type="arc" + style="fill:url(#linearGradient11869);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path11861" + sodipodi:cx="418.94757" + sodipodi:cy="444.83228" + sodipodi:rx="9.8239412" + sodipodi:ry="9.8239412" + d="M 428.77151 444.83228 A 9.8239412 9.8239412 0 1 1 409.12363,444.83228 A 9.8239412 9.8239412 0 1 1 428.77151 444.83228 z" + transform="matrix(1.014646,0,0,1.014647,-405.1135,-416.9803)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:type="arc" + style="fill:url(#radialGradient11871);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path11863" + sodipodi:cx="418.51233" + sodipodi:cy="446.44885" + sodipodi:rx="11.005301" + sodipodi:ry="11.005301" + d="M 429.51763 446.44885 A 11.005301 11.005301 0 1 1 407.50703,446.44885 A 11.005301 11.005301 0 1 1 429.51763 446.44885 z" + transform="matrix(0.810701,0,0,0.810702,-319.3183,-327.5696)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + style="opacity:0.7;fill:url(#linearGradient11873);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 27.878882,34.367333 C 27.878882,38.733063 24.335692,34.874413 19.969982,34.874413 C 15.604272,34.874413 12.061082,38.733063 12.061082,34.367333 C 12.061082,30.001633 15.604272,26.458433 19.969982,26.458433 C 24.335692,26.458433 27.878882,30.001633 27.878882,34.367333 z " + id="path11865" + sodipodi:nodetypes="csssc" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" /> + </g> + <path + style="fill:url(#linearGradient11780);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient11782);stroke-width:0.6009531;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 24.820552,317.37071 L 24.769749,315.83886 C 27.316629,315.34382 29.552396,313.4622 30.458972,310.71063 C 30.988793,309.10253 30.980862,307.44348 30.542563,305.93874 C 30.553517,306.58463 30.458431,307.23468 30.2498,307.86791 C 29.460325,310.26409 27.227331,311.78192 24.641819,311.99491 L 24.564041,309.63634 L 19.452103,313.84163 L 24.820552,317.37071 z " + id="path11777" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + </g> + <g + transform="matrix(0.727272,0,0,0.727272,-487.30361,492.11305)" + id="g11784" + style="display:inline" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/16x16/actions/amarok_redo.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <g + transform="translate(6.2858,311.21618)" + id="use11786"> + <path + sodipodi:type="arc" + style="fill:url(#linearGradient11885);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path11877" + sodipodi:cx="366.7616" + sodipodi:cy="436.4032" + sodipodi:rx="10.639691" + sodipodi:ry="10.639691" + d="M 377.40129 436.4032 A 10.639691 10.639691 0 1 1 356.12191,436.4032 A 10.639691 10.639691 0 1 1 377.40129 436.4032 z" + transform="matrix(1.033865,0,0,1.033865,-359.2117,-416.8144)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:type="arc" + style="fill:url(#linearGradient11887);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path11879" + sodipodi:cx="418.94757" + sodipodi:cy="444.83228" + sodipodi:rx="9.8239412" + sodipodi:ry="9.8239412" + d="M 428.77151 444.83228 A 9.8239412 9.8239412 0 1 1 409.12363,444.83228 A 9.8239412 9.8239412 0 1 1 428.77151 444.83228 z" + transform="matrix(1.014646,0,0,1.014647,-405.1135,-416.9803)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:type="arc" + style="fill:url(#radialGradient11889);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path11881" + sodipodi:cx="418.51233" + sodipodi:cy="446.44885" + sodipodi:rx="11.005301" + sodipodi:ry="11.005301" + d="M 429.51763 446.44885 A 11.005301 11.005301 0 1 1 407.50703,446.44885 A 11.005301 11.005301 0 1 1 429.51763 446.44885 z" + transform="matrix(0.810701,0,0,0.810702,-319.3183,-327.5696)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + style="opacity:0.7;fill:url(#linearGradient11891);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 27.878882,34.367333 C 27.878882,38.733063 24.335692,34.874413 19.969982,34.874413 C 15.604272,34.874413 12.061082,38.733063 12.061082,34.367333 C 12.061082,30.001633 15.604272,26.458433 19.969982,26.458433 C 24.335692,26.458433 27.878882,30.001633 27.878882,34.367333 z " + id="path11883" + sodipodi:nodetypes="csssc" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" /> + </g> + <path + style="fill:url(#linearGradient11790);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient11792);stroke-width:0.6009531;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 27.322263,352.02827 L 27.373066,350.49643 C 24.826186,350.00139 22.590419,348.11976 21.683843,345.36819 C 21.154022,343.76009 21.161953,342.10104 21.600252,340.59631 C 21.589298,341.24219 21.684384,341.89225 21.893015,342.52548 C 22.68249,344.92165 24.915484,346.43949 27.500996,346.65248 L 27.578774,344.2939 L 32.690712,348.49919 L 27.322263,352.02827 z " + id="path11788" + inkscape:export-filename="/home/vadim/collection.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + </g> + <g + transform="matrix(1.5,0,0,1.5,-441.43458,38.553488)" + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/48x48/actions/amarok_rewind.png" + id="g11794" + style="display:inline"> + <g + inkscape:export-ydpi="89.999962" + inkscape:export-xdpi="89.999962" + inkscape:export-filename="/home/vadim/amarok icons/1/22x22/actions/amarok_circle.png" + transform="matrix(1.454545,0,0,1.454545,35.71411,34.860727)" + id="use11796"> + <path + sodipodi:type="arc" + style="opacity:1;fill:url(#linearGradient11215);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path11207" + sodipodi:cx="366.7616" + sodipodi:cy="436.4032" + sodipodi:rx="10.639691" + sodipodi:ry="10.639691" + d="M 377.40129 436.4032 A 10.639691 10.639691 0 1 1 356.12191,436.4032 A 10.639691 10.639691 0 1 1 377.40129 436.4032 z" + transform="matrix(1.033865,0,0,1.033865,-358.4555,-432.1183)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:type="arc" + style="opacity:1;fill:url(#linearGradient11217);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path11209" + sodipodi:cx="418.94757" + sodipodi:cy="444.83228" + sodipodi:rx="9.8239412" + sodipodi:ry="9.8239412" + d="M 428.77151 444.83228 A 9.8239412 9.8239412 0 1 1 409.12363,444.83228 A 9.8239412 9.8239412 0 1 1 428.77151 444.83228 z" + transform="matrix(1.068817,0,0,1.068817,-427.0518,-456.3806)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:type="arc" + style="opacity:1;fill:url(#radialGradient11219);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path11211" + sodipodi:cx="418.51233" + sodipodi:cy="446.44885" + sodipodi:rx="11.005301" + sodipodi:ry="11.005301" + d="M 429.51763 446.44885 A 11.005301 11.005301 0 1 1 407.50703,446.44885 A 11.005301 11.005301 0 1 1 429.51763 446.44885 z" + transform="matrix(0.908653,0,0,0.908654,-359.556,-386.6038)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + style="opacity:0.7;fill:url(#linearGradient11221);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 30.226478,19.072457 C 30.226478,24.316478 25.970475,19.68155 20.726479,19.68155 C 15.482481,19.68155 11.226478,24.316478 11.226478,19.072457 C 11.226478,13.828473 15.482481,9.5724574 20.726479,9.5724574 C 25.970475,9.5724574 30.226478,13.828473 30.226478,19.072457 z " + id="path11213" + sodipodi:nodetypes="csssc" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" /> + </g> + <g + transform="matrix(0.128302,0,0,0.128302,-60.69367,-97.87383)" + id="g11798" + style="fill:url(#linearGradient11802);fill-opacity:1" + inkscape:export-filename="/home/lando/Projects/Amarok/Playback-controls/LightBlue/blue1/32/amarok_rewind.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <path + sodipodi:nodetypes="cccccccc" + id="path11800" + d="M 1044.2816,1200.1729 L 1044.5628,1300.8916 L 982.99023,1261.4011 L 982.98547,1301.2273 L 909.54438,1251.1264 L 982.90595,1200.116 L 982.9123,1238.4265 L 1044.2816,1200.1729 z " + style="fill:url(#linearGradient11804);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient11806);stroke-width:3.02316785;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + </g> + <g + transform="matrix(1.5,0,0,1.5,-444.19238,60.718028)" + id="g11808" + style="display:inline" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/48x48/actions/amarok_fastforward.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <g + inkscape:export-ydpi="89.999962" + inkscape:export-xdpi="89.999962" + inkscape:export-filename="/home/vadim/amarok icons/1/22x22/actions/amarok_circle.png" + transform="matrix(1.454545,0,0,1.454545,36.21933,70.089787)" + id="use11810"> + <path + sodipodi:type="arc" + style="opacity:1;fill:url(#linearGradient11233);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path11225" + sodipodi:cx="366.7616" + sodipodi:cy="436.4032" + sodipodi:rx="10.639691" + sodipodi:ry="10.639691" + d="M 377.40129 436.4032 A 10.639691 10.639691 0 1 1 356.12191,436.4032 A 10.639691 10.639691 0 1 1 377.40129 436.4032 z" + transform="matrix(1.033865,0,0,1.033865,-358.4555,-432.1183)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:type="arc" + style="opacity:1;fill:url(#linearGradient11235);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path11227" + sodipodi:cx="418.94757" + sodipodi:cy="444.83228" + sodipodi:rx="9.8239412" + sodipodi:ry="9.8239412" + d="M 428.77151 444.83228 A 9.8239412 9.8239412 0 1 1 409.12363,444.83228 A 9.8239412 9.8239412 0 1 1 428.77151 444.83228 z" + transform="matrix(1.068817,0,0,1.068817,-427.0518,-456.3806)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:type="arc" + style="opacity:1;fill:url(#radialGradient11237);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path11229" + sodipodi:cx="418.51233" + sodipodi:cy="446.44885" + sodipodi:rx="11.005301" + sodipodi:ry="11.005301" + d="M 429.51763 446.44885 A 11.005301 11.005301 0 1 1 407.50703,446.44885 A 11.005301 11.005301 0 1 1 429.51763 446.44885 z" + transform="matrix(0.908653,0,0,0.908654,-359.556,-386.6038)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + style="opacity:0.7;fill:url(#linearGradient11239);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 30.226478,19.072457 C 30.226478,24.316478 25.970475,19.68155 20.726479,19.68155 C 15.482481,19.68155 11.226478,24.316478 11.226478,19.072457 C 11.226478,13.828473 15.482481,9.5724574 20.726479,9.5724574 C 25.970475,9.5724574 30.226478,13.828473 30.226478,19.072457 z " + id="path11231" + sodipodi:nodetypes="csssc" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" /> + </g> + <g + id="g11812" + transform="matrix(-0.128302,0,0,0.128302,192.943,-62.64477)" + style="fill:url(#linearGradient11816);fill-opacity:1"> + <path + style="fill:url(#linearGradient11818);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient11820);stroke-width:3.02316785;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 1044.2816,1200.1729 L 1044.5628,1300.8916 L 982.99023,1261.4011 L 982.98547,1301.2273 L 909.54438,1251.1264 L 982.90595,1200.116 L 982.9123,1238.4265 L 1044.2816,1200.1729 z " + id="path11814" + sodipodi:nodetypes="cccccccc" /> + </g> + </g> + <g + transform="matrix(1.5,0,0,1.5,-448.55021,85.033348)" + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/48x48/actions/amarok_back.png" + id="g11822" + style="display:inline"> + <g + inkscape:export-ydpi="89.999962" + inkscape:export-xdpi="89.999962" + inkscape:export-filename="/home/vadim/amarok icons/1/22x22/actions/amarok_circle.png" + transform="matrix(1.454545,0,0,1.454545,36.21921,107.21293)" + id="use11824"> + <path + sodipodi:type="arc" + style="opacity:1;fill:url(#linearGradient11251);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path11243" + sodipodi:cx="366.7616" + sodipodi:cy="436.4032" + sodipodi:rx="10.639691" + sodipodi:ry="10.639691" + d="M 377.40129 436.4032 A 10.639691 10.639691 0 1 1 356.12191,436.4032 A 10.639691 10.639691 0 1 1 377.40129 436.4032 z" + transform="matrix(1.033865,0,0,1.033865,-358.4555,-432.1183)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:type="arc" + style="opacity:1;fill:url(#linearGradient11253);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path11245" + sodipodi:cx="418.94757" + sodipodi:cy="444.83228" + sodipodi:rx="9.8239412" + sodipodi:ry="9.8239412" + d="M 428.77151 444.83228 A 9.8239412 9.8239412 0 1 1 409.12363,444.83228 A 9.8239412 9.8239412 0 1 1 428.77151 444.83228 z" + transform="matrix(1.068817,0,0,1.068817,-427.0518,-456.3806)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:type="arc" + style="opacity:1;fill:url(#radialGradient11255);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path11247" + sodipodi:cx="418.51233" + sodipodi:cy="446.44885" + sodipodi:rx="11.005301" + sodipodi:ry="11.005301" + d="M 429.51763 446.44885 A 11.005301 11.005301 0 1 1 407.50703,446.44885 A 11.005301 11.005301 0 1 1 429.51763 446.44885 z" + transform="matrix(0.908653,0,0,0.908654,-359.556,-386.6038)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + style="opacity:0.7;fill:url(#linearGradient11257);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 30.226478,19.072457 C 30.226478,24.316478 25.970475,19.68155 20.726479,19.68155 C 15.482481,19.68155 11.226478,24.316478 11.226478,19.072457 C 11.226478,13.828473 15.482481,9.5724574 20.726479,9.5724574 C 25.970475,9.5724574 30.226478,13.828473 30.226478,19.072457 z " + id="path11249" + sodipodi:nodetypes="csssc" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" /> + </g> + <g + id="g11826" + transform="matrix(0.284505,0,0,0.284505,-212.99,111.7714)" + style="fill:url(#linearGradient11832);fill-opacity:1"> + <g + id="g11828" + transform="matrix(0.51291,0,0,0.51291,468.129,-559.841)" + style="fill:url(#linearGradient11834);fill-opacity:1"> + <path + style="fill:url(#linearGradient11836);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient11838);stroke-width:2.74112558;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 1044.2816,1200.1729 L 1044.5628,1300.3931 L 982.99023,1261.4011 L 982.98547,1300.3931 L 958.81862,1300.3931 L 958.81862,1200.1729 L 982.90595,1200.1729 L 982.9123,1238.4265 L 1044.2816,1200.1729 z " + id="path11830" + sodipodi:nodetypes="ccccccccc" /> + </g> + </g> + </g> + <g + transform="matrix(1.5,0,0,1.5,-446.57151,102.02765)" + id="g11840" + style="display:inline" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/48x48/actions/amarok_play.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <g + inkscape:export-ydpi="89.999962" + inkscape:export-xdpi="89.999962" + inkscape:export-filename="/home/vadim/amarok icons/1/22x22/actions/amarok_circle.png" + transform="matrix(1.454545,0,0,1.454545,35.92383,145.88333)" + id="use11842"> + <path + sodipodi:type="arc" + style="opacity:1;fill:url(#linearGradient11269);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path11261" + sodipodi:cx="366.7616" + sodipodi:cy="436.4032" + sodipodi:rx="10.639691" + sodipodi:ry="10.639691" + d="M 377.40129 436.4032 A 10.639691 10.639691 0 1 1 356.12191,436.4032 A 10.639691 10.639691 0 1 1 377.40129 436.4032 z" + transform="matrix(1.033865,0,0,1.033865,-358.4555,-432.1183)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:type="arc" + style="opacity:1;fill:url(#linearGradient11271);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path11263" + sodipodi:cx="418.94757" + sodipodi:cy="444.83228" + sodipodi:rx="9.8239412" + sodipodi:ry="9.8239412" + d="M 428.77151 444.83228 A 9.8239412 9.8239412 0 1 1 409.12363,444.83228 A 9.8239412 9.8239412 0 1 1 428.77151 444.83228 z" + transform="matrix(1.068817,0,0,1.068817,-427.0518,-456.3806)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:type="arc" + style="opacity:1;fill:url(#radialGradient11273);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path11265" + sodipodi:cx="418.51233" + sodipodi:cy="446.44885" + sodipodi:rx="11.005301" + sodipodi:ry="11.005301" + d="M 429.51763 446.44885 A 11.005301 11.005301 0 1 1 407.50703,446.44885 A 11.005301 11.005301 0 1 1 429.51763 446.44885 z" + transform="matrix(0.908653,0,0,0.908654,-359.556,-386.6038)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + style="opacity:0.7;fill:url(#linearGradient11275);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 30.226478,19.072457 C 30.226478,24.316478 25.970475,19.68155 20.726479,19.68155 C 15.482481,19.68155 11.226478,24.316478 11.226478,19.072457 C 11.226478,13.828473 15.482481,9.5724574 20.726479,9.5724574 C 25.970475,9.5724574 30.226478,13.828473 30.226478,19.072457 z " + id="path11267" + sodipodi:nodetypes="csssc" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" /> + </g> + <g + id="g11844" + transform="matrix(0.120795,0,0,0.120795,-66.46611,22.08624)" + inkscape:export-filename="/home/vadim/collection.png" + inkscape:export-xdpi="90.000000" + inkscape:export-ydpi="90.000000" + style="fill:url(#linearGradient11848);fill-opacity:1;stroke:url(#linearGradient11850)"> + <path + sodipodi:nodetypes="cccc" + id="path11846" + d="M 1064.5688,1194.5934 L 1064.2322,1314.225 L 1158.4102,1259.0756 L 1064.5688,1194.5934 z " + style="fill:url(#linearGradient11852);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient11854);stroke-width:3.21105671;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + </g> + <g + transform="matrix(1.5,0,0,1.5,-447.03571,121.86721)" + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/48x48/actions/amarok_stop.png" + id="g11856" + style="display:inline"> + <g + inkscape:export-ydpi="89.999962" + inkscape:export-xdpi="89.999962" + inkscape:export-filename="/home/vadim/amarok icons/1/22x22/actions/amarok_circle.png" + transform="matrix(1.454545,0,0,1.454545,35.20954,179.99033)" + id="use11858"> + <path + sodipodi:type="arc" + style="opacity:1;fill:url(#linearGradient11287);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path11279" + sodipodi:cx="366.7616" + sodipodi:cy="436.4032" + sodipodi:rx="10.639691" + sodipodi:ry="10.639691" + d="M 377.40129 436.4032 A 10.639691 10.639691 0 1 1 356.12191,436.4032 A 10.639691 10.639691 0 1 1 377.40129 436.4032 z" + transform="matrix(1.033865,0,0,1.033865,-358.4555,-432.1183)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:type="arc" + style="opacity:1;fill:url(#linearGradient11289);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path11281" + sodipodi:cx="418.94757" + sodipodi:cy="444.83228" + sodipodi:rx="9.8239412" + sodipodi:ry="9.8239412" + d="M 428.77151 444.83228 A 9.8239412 9.8239412 0 1 1 409.12363,444.83228 A 9.8239412 9.8239412 0 1 1 428.77151 444.83228 z" + transform="matrix(1.068817,0,0,1.068817,-427.0518,-456.3806)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:type="arc" + style="opacity:1;fill:url(#radialGradient11291);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path11283" + sodipodi:cx="418.51233" + sodipodi:cy="446.44885" + sodipodi:rx="11.005301" + sodipodi:ry="11.005301" + d="M 429.51763 446.44885 A 11.005301 11.005301 0 1 1 407.50703,446.44885 A 11.005301 11.005301 0 1 1 429.51763 446.44885 z" + transform="matrix(0.908653,0,0,0.908654,-359.556,-386.6038)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + style="opacity:0.7;fill:url(#linearGradient11293);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 30.226478,19.072457 C 30.226478,24.316478 25.970475,19.68155 20.726479,19.68155 C 15.482481,19.68155 11.226478,24.316478 11.226478,19.072457 C 11.226478,13.828473 15.482481,9.5724574 20.726479,9.5724574 C 25.970475,9.5724574 30.226478,13.828473 30.226478,19.072457 z " + id="path11285" + sodipodi:nodetypes="csssc" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" /> + </g> + <g + id="g11860" + transform="matrix(0.120171,0,0,0.120171,-91.00753,57.57068)" + inkscape:export-filename="/home/derek/Desktop/Devel/icons.png" + inkscape:export-xdpi="90.000000" + inkscape:export-ydpi="90.000000"> + <g + id="g11862"> + <rect + style="opacity:1;fill:url(#linearGradient11866);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient11868);stroke-width:3.2277329;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect11864" + width="106.94586" + height="111.49675" + x="1247.7158" + y="1193.7136" /> + </g> + </g> + </g> + <g + transform="matrix(1.5,0,0,1.5,-445.17849,140.97435)" + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/48x48/actions/amarok_pause.png" + id="g11870" + style="display:inline"> + <g + inkscape:export-ydpi="89.999962" + inkscape:export-xdpi="89.999962" + inkscape:export-filename="/home/vadim/amarok icons/1/22x22/actions/amarok_circle.png" + transform="matrix(1.454545,0,0,1.454545,35.92385,213.91883)" + id="use11872"> + <path + sodipodi:type="arc" + style="opacity:1;fill:url(#linearGradient11305);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path11297" + sodipodi:cx="366.7616" + sodipodi:cy="436.4032" + sodipodi:rx="10.639691" + sodipodi:ry="10.639691" + d="M 377.40129 436.4032 A 10.639691 10.639691 0 1 1 356.12191,436.4032 A 10.639691 10.639691 0 1 1 377.40129 436.4032 z" + transform="matrix(1.033865,0,0,1.033865,-358.4555,-432.1183)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:type="arc" + style="opacity:1;fill:url(#linearGradient11307);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path11299" + sodipodi:cx="418.94757" + sodipodi:cy="444.83228" + sodipodi:rx="9.8239412" + sodipodi:ry="9.8239412" + d="M 428.77151 444.83228 A 9.8239412 9.8239412 0 1 1 409.12363,444.83228 A 9.8239412 9.8239412 0 1 1 428.77151 444.83228 z" + transform="matrix(1.068817,0,0,1.068817,-427.0518,-456.3806)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:type="arc" + style="opacity:1;fill:url(#radialGradient11309);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path11301" + sodipodi:cx="418.51233" + sodipodi:cy="446.44885" + sodipodi:rx="11.005301" + sodipodi:ry="11.005301" + d="M 429.51763 446.44885 A 11.005301 11.005301 0 1 1 407.50703,446.44885 A 11.005301 11.005301 0 1 1 429.51763 446.44885 z" + transform="matrix(0.908653,0,0,0.908654,-359.556,-386.6038)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + style="opacity:0.7;fill:url(#linearGradient11311);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 30.226478,19.072457 C 30.226478,24.316478 25.970475,19.68155 20.726479,19.68155 C 15.482481,19.68155 11.226478,24.316478 11.226478,19.072457 C 11.226478,13.828473 15.482481,9.5724574 20.726479,9.5724574 C 25.970475,9.5724574 30.226478,13.828473 30.226478,19.072457 z " + id="path11303" + sodipodi:nodetypes="csssc" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" /> + </g> + <g + style="fill:#943c3e;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:6.68458128;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="g11874" + transform="matrix(6.477895e-2,0,0,0.152432,-34.14433,2.046827)" + inkscape:export-filename="/home/vadim/collection.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <path + id="path11876" + d="M 1577.4039,1526.7125 L 1577.4039,1616.4528 L 1663.6021,1616.4528 L 1663.6021,1526.7125 L 1577.4039,1526.7125 z M 1430.4823,1527.1891 L 1430.4823,1616.9975 L 1516.6806,1616.9975 L 1516.6806,1527.1891 L 1430.4823,1527.1891 z " + style="opacity:1;fill:url(#linearGradient11878);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient11880);stroke-width:3.90338135;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + </g> + <g + transform="matrix(1.5,0,0,1.5,-448.64281,158.79555)" + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/48x48/actions/amarok_next.png" + id="g11882" + style="display:inline"> + <g + inkscape:export-ydpi="89.999962" + inkscape:export-xdpi="89.999962" + inkscape:export-filename="/home/vadim/amarok icons/1/22x22/actions/amarok_circle.png" + transform="matrix(1.454545,0,0,1.454545,36.28093,250.70473)" + id="use11884"> + <path + sodipodi:type="arc" + style="opacity:1;fill:url(#linearGradient11323);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path11315" + sodipodi:cx="366.7616" + sodipodi:cy="436.4032" + sodipodi:rx="10.639691" + sodipodi:ry="10.639691" + d="M 377.40129 436.4032 A 10.639691 10.639691 0 1 1 356.12191,436.4032 A 10.639691 10.639691 0 1 1 377.40129 436.4032 z" + transform="matrix(1.033865,0,0,1.033865,-358.4555,-432.1183)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:type="arc" + style="opacity:1;fill:url(#linearGradient11325);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path11317" + sodipodi:cx="418.94757" + sodipodi:cy="444.83228" + sodipodi:rx="9.8239412" + sodipodi:ry="9.8239412" + d="M 428.77151 444.83228 A 9.8239412 9.8239412 0 1 1 409.12363,444.83228 A 9.8239412 9.8239412 0 1 1 428.77151 444.83228 z" + transform="matrix(1.068817,0,0,1.068817,-427.0518,-456.3806)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:type="arc" + style="opacity:1;fill:url(#radialGradient11327);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path11319" + sodipodi:cx="418.51233" + sodipodi:cy="446.44885" + sodipodi:rx="11.005301" + sodipodi:ry="11.005301" + d="M 429.51763 446.44885 A 11.005301 11.005301 0 1 1 407.50703,446.44885 A 11.005301 11.005301 0 1 1 429.51763 446.44885 z" + transform="matrix(0.908653,0,0,0.908654,-359.556,-386.6038)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + style="opacity:0.7;fill:url(#linearGradient11329);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 30.226478,19.072457 C 30.226478,24.316478 25.970475,19.68155 20.726479,19.68155 C 15.482481,19.68155 11.226478,24.316478 11.226478,19.072457 C 11.226478,13.828473 15.482481,9.5724574 20.726479,9.5724574 C 25.970475,9.5724574 30.226478,13.828473 30.226478,19.072457 z " + id="path11321" + sodipodi:nodetypes="csssc" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" /> + </g> + <g + id="g11886" + transform="matrix(-0.284505,0,0,0.284505,345.7854,255.2632)"> + <g + id="g11888" + transform="matrix(0.51291,0,0,0.51291,468.129,-559.841)"> + <path + style="fill:url(#linearGradient11892);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient11894);stroke-width:2.65806389;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 1044.2816,1200.1729 L 1044.5628,1300.3931 L 982.99023,1261.4011 L 982.98547,1300.3931 L 958.81862,1300.3931 L 958.81862,1200.1729 L 982.90595,1200.1729 L 982.9123,1238.4265 L 1044.2816,1200.1729 z " + id="path11890" + sodipodi:nodetypes="ccccccccc" /> + </g> + </g> + </g> + <g + transform="matrix(1.5,0,0,1.5,-450.02661,180.71735)" + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/48x48/actions/amarok_undo.png" + id="g11896" + style="display:inline"> + <g + inkscape:export-ydpi="89.999962" + inkscape:export-xdpi="89.999962" + inkscape:export-filename="/home/vadim/amarok icons/1/22x22/actions/amarok_circle.png" + transform="matrix(1.454545,0,0,1.454545,35.20348,284.09023)" + id="use11898"> + <path + sodipodi:type="arc" + style="opacity:1;fill:url(#linearGradient11342);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path11333" + sodipodi:cx="366.7616" + sodipodi:cy="436.4032" + sodipodi:rx="10.639691" + sodipodi:ry="10.639691" + d="M 377.40129 436.4032 A 10.639691 10.639691 0 1 1 356.12191,436.4032 A 10.639691 10.639691 0 1 1 377.40129 436.4032 z" + transform="matrix(1.033865,0,0,1.033865,-358.4555,-432.1183)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:type="arc" + style="opacity:1;fill:url(#linearGradient11344);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path11335" + sodipodi:cx="418.94757" + sodipodi:cy="444.83228" + sodipodi:rx="9.8239412" + sodipodi:ry="9.8239412" + d="M 428.77151 444.83228 A 9.8239412 9.8239412 0 1 1 409.12363,444.83228 A 9.8239412 9.8239412 0 1 1 428.77151 444.83228 z" + transform="matrix(1.068817,0,0,1.068817,-427.0518,-456.3806)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:type="arc" + style="opacity:1;fill:url(#radialGradient11346);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path11337" + sodipodi:cx="418.51233" + sodipodi:cy="446.44885" + sodipodi:rx="11.005301" + sodipodi:ry="11.005301" + d="M 429.51763 446.44885 A 11.005301 11.005301 0 1 1 407.50703,446.44885 A 11.005301 11.005301 0 1 1 429.51763 446.44885 z" + transform="matrix(0.908653,0,0,0.908654,-359.556,-386.6038)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + style="opacity:0.7;fill:url(#linearGradient11348);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 30.226478,19.072457 C 30.226478,24.316478 25.970475,19.68155 20.726479,19.68155 C 15.482481,19.68155 11.226478,24.316478 11.226478,19.072457 C 11.226478,13.828473 15.482481,9.5724574 20.726479,9.5724574 C 25.970475,9.5724574 30.226478,13.828473 30.226478,19.072457 z " + id="path11339" + sodipodi:nodetypes="csssc" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" /> + </g> + <path + style="fill:url(#linearGradient11902);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient11904);stroke-width:0.38787869;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 63.800205,321.19294 L 63.72631,318.96481 C 67.430861,318.24475 70.682885,315.50784 72.001541,311.50556 C 72.772189,309.1665 72.760653,306.75334 72.123127,304.56464 C 72.13906,305.5041 72.000754,306.44964 71.69729,307.3707 C 70.548963,310.85605 67.300973,313.06381 63.54023,313.37361 L 63.427098,309.94295 L 55.991554,316.05974 L 63.800205,321.19294 z " + id="path11900" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + </g> + <g + transform="matrix(1.5,0,0,1.5,-446.04481,207.7312)" + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/48x48/actions/amarok_redo.png" + id="g11906" + style="display:inline"> + <g + inkscape:export-ydpi="89.999962" + inkscape:export-xdpi="89.999962" + inkscape:export-filename="/home/vadim/amarok icons/1/22x22/actions/amarok_circle.png" + transform="matrix(1.454545,0,0,1.454545,35.21562,318.74783)" + id="use11908"> + <path + sodipodi:type="arc" + style="opacity:1;fill:url(#linearGradient11360);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path11352" + sodipodi:cx="366.7616" + sodipodi:cy="436.4032" + sodipodi:rx="10.639691" + sodipodi:ry="10.639691" + d="M 377.40129 436.4032 A 10.639691 10.639691 0 1 1 356.12191,436.4032 A 10.639691 10.639691 0 1 1 377.40129 436.4032 z" + transform="matrix(1.033865,0,0,1.033865,-358.4555,-432.1183)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:type="arc" + style="opacity:1;fill:url(#linearGradient11362);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path11354" + sodipodi:cx="418.94757" + sodipodi:cy="444.83228" + sodipodi:rx="9.8239412" + sodipodi:ry="9.8239412" + d="M 428.77151 444.83228 A 9.8239412 9.8239412 0 1 1 409.12363,444.83228 A 9.8239412 9.8239412 0 1 1 428.77151 444.83228 z" + transform="matrix(1.068817,0,0,1.068817,-427.0518,-456.3806)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:type="arc" + style="opacity:1;fill:url(#radialGradient11364);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path11356" + sodipodi:cx="418.51233" + sodipodi:cy="446.44885" + sodipodi:rx="11.005301" + sodipodi:ry="11.005301" + d="M 429.51763 446.44885 A 11.005301 11.005301 0 1 1 407.50703,446.44885 A 11.005301 11.005301 0 1 1 429.51763 446.44885 z" + transform="matrix(0.908653,0,0,0.908654,-359.556,-386.6038)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + style="opacity:0.7;fill:url(#linearGradient11366);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 30.226478,19.072457 C 30.226478,24.316478 25.970475,19.68155 20.726479,19.68155 C 15.482481,19.68155 11.226478,24.316478 11.226478,19.072457 C 11.226478,13.828473 15.482481,9.5724574 20.726479,9.5724574 C 25.970475,9.5724574 30.226478,13.828473 30.226478,19.072457 z " + id="path11358" + sodipodi:nodetypes="csssc" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" /> + </g> + <path + style="fill:url(#linearGradient11912);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient11914);stroke-width:0.38787869;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 66.914031,355.85055 L 66.987927,353.62241 C 63.283375,352.90235 60.031351,350.16544 58.712696,346.16316 C 57.942047,343.82411 57.953583,341.41094 58.591109,339.22224 C 58.575176,340.16171 58.713483,341.10724 59.016946,342.0283 C 60.165273,345.51365 63.413263,347.72141 67.174007,348.03121 L 67.287138,344.60056 L 74.722682,350.71734 L 66.914031,355.85055 z " + id="path11910" + inkscape:export-filename="/home/vadim/collection.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + </g> + <g + inkscape:export-ydpi="89.999962" + inkscape:export-xdpi="89.999962" + inkscape:export-filename="/home/vadim/amarok icons/1/22x22/actions/amarok_circle.png" + transform="matrix(2.90909,0,0,2.90909,-340.93716,68.980079)" + id="use11918"> + <path + sodipodi:type="arc" + style="opacity:1;fill:url(#linearGradient19983);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path11372" + sodipodi:cx="366.7616" + sodipodi:cy="436.4032" + sodipodi:rx="10.639691" + sodipodi:ry="10.639691" + d="M 377.40129 436.4032 A 10.639691 10.639691 0 1 1 356.12191,436.4032 A 10.639691 10.639691 0 1 1 377.40129 436.4032 z" + transform="matrix(1.033865,0,0,1.033865,-358.4555,-432.1183)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:type="arc" + style="opacity:1;fill:url(#linearGradient19985);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path11374" + sodipodi:cx="418.94757" + sodipodi:cy="444.83228" + sodipodi:rx="9.8239412" + sodipodi:ry="9.8239412" + d="M 428.77151 444.83228 A 9.8239412 9.8239412 0 1 1 409.12363,444.83228 A 9.8239412 9.8239412 0 1 1 428.77151 444.83228 z" + transform="matrix(1.068817,0,0,1.068817,-427.0518,-456.3806)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:type="arc" + style="opacity:1;fill:url(#radialGradient19987);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path11376" + sodipodi:cx="418.51233" + sodipodi:cy="446.44885" + sodipodi:rx="11.005301" + sodipodi:ry="11.005301" + d="M 429.51763 446.44885 A 11.005301 11.005301 0 1 1 407.50703,446.44885 A 11.005301 11.005301 0 1 1 429.51763 446.44885 z" + transform="matrix(0.908653,0,0,0.908654,-359.556,-386.6038)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + style="opacity:0.7;fill:url(#linearGradient19989);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 30.226478,19.072457 C 30.226478,24.316478 25.970475,19.68155 20.726479,19.68155 C 15.482481,19.68155 11.226478,24.316478 11.226478,19.072457 C 11.226478,13.828473 15.482481,9.5724574 20.726479,9.5724574 C 25.970475,9.5724574 30.226478,13.828473 30.226478,19.072457 z " + id="path11378" + sodipodi:nodetypes="csssc" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" /> + </g> + <path + style="fill:url(#linearGradient19996);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient19998);stroke-width:0.58181781;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M -265.78588,111.48014 L -265.71373,137.32496 L -281.5135,127.19154 L -281.51472,137.4111 L -300.35999,124.55501 L -281.53512,111.46554 L -281.53349,121.29616 L -265.78588,111.48014 z " + id="path11922" + sodipodi:nodetypes="cccccccc" /> + <g + style="display:inline" + id="g11930" + transform="matrix(2,0,0,2,-412.37578,3.8086323)" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/64x64/actions/amarok_fastforward.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <g + inkscape:export-ydpi="89.999962" + inkscape:export-xdpi="89.999962" + inkscape:export-filename="/home/vadim/amarok icons/1/22x22/actions/amarok_circle.png" + transform="matrix(1.454545,0,0,1.454545,36.21933,70.089787)" + id="use11932"> + <path + sodipodi:type="arc" + style="opacity:1;fill:url(#linearGradient11399);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path11391" + sodipodi:cx="366.7616" + sodipodi:cy="436.4032" + sodipodi:rx="10.639691" + sodipodi:ry="10.639691" + d="M 377.40129 436.4032 A 10.639691 10.639691 0 1 1 356.12191,436.4032 A 10.639691 10.639691 0 1 1 377.40129 436.4032 z" + transform="matrix(1.033865,0,0,1.033865,-358.4555,-432.1183)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:type="arc" + style="opacity:1;fill:url(#linearGradient11401);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path11393" + sodipodi:cx="418.94757" + sodipodi:cy="444.83228" + sodipodi:rx="9.8239412" + sodipodi:ry="9.8239412" + d="M 428.77151 444.83228 A 9.8239412 9.8239412 0 1 1 409.12363,444.83228 A 9.8239412 9.8239412 0 1 1 428.77151 444.83228 z" + transform="matrix(1.068817,0,0,1.068817,-427.0518,-456.3806)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:type="arc" + style="opacity:1;fill:url(#radialGradient11403);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path11395" + sodipodi:cx="418.51233" + sodipodi:cy="446.44885" + sodipodi:rx="11.005301" + sodipodi:ry="11.005301" + d="M 429.51763 446.44885 A 11.005301 11.005301 0 1 1 407.50703,446.44885 A 11.005301 11.005301 0 1 1 429.51763 446.44885 z" + transform="matrix(0.908653,0,0,0.908654,-359.556,-386.6038)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + style="opacity:0.7;fill:url(#linearGradient11405);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 30.226478,19.072457 C 30.226478,24.316478 25.970475,19.68155 20.726479,19.68155 C 15.482481,19.68155 11.226478,24.316478 11.226478,19.072457 C 11.226478,13.828473 15.482481,9.5724574 20.726479,9.5724574 C 25.970475,9.5724574 30.226478,13.828473 30.226478,19.072457 z " + id="path11397" + sodipodi:nodetypes="csssc" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" /> + </g> + <g + style="fill:url(#linearGradient11938);fill-opacity:1" + transform="matrix(-0.128302,0,0,0.128302,192.943,-62.64477)" + id="g11934"> + <path + sodipodi:nodetypes="cccccccc" + id="path11936" + d="M 1044.2816,1200.1729 L 1044.5628,1300.8916 L 982.99023,1261.4011 L 982.98547,1301.2273 L 909.54438,1251.1264 L 982.90595,1200.116 L 982.9123,1238.4265 L 1044.2816,1200.1729 z " + style="fill:url(#linearGradient11940);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient11942);stroke-width:2.26737618;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + </g> + <g + style="display:inline" + id="g11944" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/64x64/actions/amarok_back.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" + transform="matrix(2,0,0,2,-413.73361,9.5623483)"> + <g + inkscape:export-ydpi="89.999962" + inkscape:export-xdpi="89.999962" + inkscape:export-filename="/home/vadim/amarok icons/1/22x22/actions/amarok_circle.png" + transform="matrix(1.454545,0,0,1.454545,36.21921,107.21293)" + id="use11946"> + <path + sodipodi:type="arc" + style="opacity:1;fill:url(#linearGradient11417);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path11409" + sodipodi:cx="366.7616" + sodipodi:cy="436.4032" + sodipodi:rx="10.639691" + sodipodi:ry="10.639691" + d="M 377.40129 436.4032 A 10.639691 10.639691 0 1 1 356.12191,436.4032 A 10.639691 10.639691 0 1 1 377.40129 436.4032 z" + transform="matrix(1.033865,0,0,1.033865,-358.4555,-432.1183)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:type="arc" + style="opacity:1;fill:url(#linearGradient11419);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path11411" + sodipodi:cx="418.94757" + sodipodi:cy="444.83228" + sodipodi:rx="9.8239412" + sodipodi:ry="9.8239412" + d="M 428.77151 444.83228 A 9.8239412 9.8239412 0 1 1 409.12363,444.83228 A 9.8239412 9.8239412 0 1 1 428.77151 444.83228 z" + transform="matrix(1.068817,0,0,1.068817,-427.0518,-456.3806)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:type="arc" + style="opacity:1;fill:url(#radialGradient11421);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path11413" + sodipodi:cx="418.51233" + sodipodi:cy="446.44885" + sodipodi:rx="11.005301" + sodipodi:ry="11.005301" + d="M 429.51763 446.44885 A 11.005301 11.005301 0 1 1 407.50703,446.44885 A 11.005301 11.005301 0 1 1 429.51763 446.44885 z" + transform="matrix(0.908653,0,0,0.908654,-359.556,-386.6038)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + style="opacity:0.7;fill:url(#linearGradient11436);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 30.226478,19.072457 C 30.226478,24.316478 25.970475,19.68155 20.726479,19.68155 C 15.482481,19.68155 11.226478,24.316478 11.226478,19.072457 C 11.226478,13.828473 15.482481,9.5724574 20.726479,9.5724574 C 25.970475,9.5724574 30.226478,13.828473 30.226478,19.072457 z " + id="path11415" + sodipodi:nodetypes="csssc" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" /> + </g> + <g + style="fill:url(#linearGradient11954);fill-opacity:1" + transform="matrix(0.284505,0,0,0.284505,-212.99,111.7714)" + id="g11948"> + <g + style="fill:url(#linearGradient11956);fill-opacity:1" + transform="matrix(0.51291,0,0,0.51291,468.129,-559.841)" + id="g11950"> + <path + sodipodi:nodetypes="ccccccccc" + id="path11952" + d="M 1044.2816,1200.1729 L 1044.5628,1300.3931 L 982.99023,1261.4011 L 982.98547,1300.3931 L 958.81862,1300.3931 L 958.81862,1200.1729 L 982.90595,1200.1729 L 982.9123,1238.4265 L 1044.2816,1200.1729 z " + style="fill:url(#linearGradient11958);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient11960);stroke-width:2.05584431;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + </g> + </g> + <g + style="display:inline" + id="g11962" + transform="matrix(2,0,0,2,-414.60721,7.2214883)" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/64x64/actions/amarok_play.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <g + inkscape:export-ydpi="89.999962" + inkscape:export-xdpi="89.999962" + inkscape:export-filename="/home/vadim/amarok icons/1/22x22/actions/amarok_circle.png" + transform="matrix(1.454545,0,0,1.454545,35.92383,145.88333)" + id="use11964"> + <path + sodipodi:type="arc" + style="opacity:1;fill:url(#linearGradient11449);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path11440" + sodipodi:cx="366.7616" + sodipodi:cy="436.4032" + sodipodi:rx="10.639691" + sodipodi:ry="10.639691" + d="M 377.40129 436.4032 A 10.639691 10.639691 0 1 1 356.12191,436.4032 A 10.639691 10.639691 0 1 1 377.40129 436.4032 z" + transform="matrix(1.033865,0,0,1.033865,-358.4555,-432.1183)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:type="arc" + style="opacity:1;fill:url(#linearGradient11451);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path11443" + sodipodi:cx="418.94757" + sodipodi:cy="444.83228" + sodipodi:rx="9.8239412" + sodipodi:ry="9.8239412" + d="M 428.77151 444.83228 A 9.8239412 9.8239412 0 1 1 409.12363,444.83228 A 9.8239412 9.8239412 0 1 1 428.77151 444.83228 z" + transform="matrix(1.068817,0,0,1.068817,-427.0518,-456.3806)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:type="arc" + style="opacity:1;fill:url(#radialGradient11453);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path11445" + sodipodi:cx="418.51233" + sodipodi:cy="446.44885" + sodipodi:rx="11.005301" + sodipodi:ry="11.005301" + d="M 429.51763 446.44885 A 11.005301 11.005301 0 1 1 407.50703,446.44885 A 11.005301 11.005301 0 1 1 429.51763 446.44885 z" + transform="matrix(0.908653,0,0,0.908654,-359.556,-386.6038)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + style="opacity:0.7;fill:url(#linearGradient11455);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 30.226478,19.072457 C 30.226478,24.316478 25.970475,19.68155 20.726479,19.68155 C 15.482481,19.68155 11.226478,24.316478 11.226478,19.072457 C 11.226478,13.828473 15.482481,9.5724574 20.726479,9.5724574 C 25.970475,9.5724574 30.226478,13.828473 30.226478,19.072457 z " + id="path11447" + sodipodi:nodetypes="csssc" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" /> + </g> + <g + style="fill:url(#linearGradient11970);fill-opacity:1;stroke:url(#linearGradient11972)" + inkscape:export-ydpi="90.000000" + inkscape:export-xdpi="90.000000" + inkscape:export-filename="/home/vadim/collection.png" + transform="matrix(0.120795,0,0,0.120795,-66.46611,22.08624)" + id="g11966"> + <path + style="fill:url(#linearGradient11974);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient11976);stroke-width:2.40829277;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 1064.5688,1194.5934 L 1064.2322,1314.225 L 1158.4102,1259.0756 L 1064.5688,1194.5934 z " + id="path11968" + sodipodi:nodetypes="cccc" /> + </g> + </g> + <g + style="display:inline" + id="g11978" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/64x64/actions/amarok_stop.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" + transform="matrix(2,0,0,2,-412.71431,10.007538)"> + <g + inkscape:export-ydpi="89.999962" + inkscape:export-xdpi="89.999962" + inkscape:export-filename="/home/vadim/amarok icons/1/22x22/actions/amarok_circle.png" + transform="matrix(1.454545,0,0,1.454545,35.20954,179.99033)" + id="use11980"> + <path + sodipodi:type="arc" + style="opacity:1;fill:url(#linearGradient11467);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path11459" + sodipodi:cx="366.7616" + sodipodi:cy="436.4032" + sodipodi:rx="10.639691" + sodipodi:ry="10.639691" + d="M 377.40129 436.4032 A 10.639691 10.639691 0 1 1 356.12191,436.4032 A 10.639691 10.639691 0 1 1 377.40129 436.4032 z" + transform="matrix(1.033865,0,0,1.033865,-358.4555,-432.1183)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:type="arc" + style="opacity:1;fill:url(#linearGradient11469);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path11461" + sodipodi:cx="418.94757" + sodipodi:cy="444.83228" + sodipodi:rx="9.8239412" + sodipodi:ry="9.8239412" + d="M 428.77151 444.83228 A 9.8239412 9.8239412 0 1 1 409.12363,444.83228 A 9.8239412 9.8239412 0 1 1 428.77151 444.83228 z" + transform="matrix(1.068817,0,0,1.068817,-427.0518,-456.3806)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:type="arc" + style="opacity:1;fill:url(#radialGradient11471);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path11463" + sodipodi:cx="418.51233" + sodipodi:cy="446.44885" + sodipodi:rx="11.005301" + sodipodi:ry="11.005301" + d="M 429.51763 446.44885 A 11.005301 11.005301 0 1 1 407.50703,446.44885 A 11.005301 11.005301 0 1 1 429.51763 446.44885 z" + transform="matrix(0.908653,0,0,0.908654,-359.556,-386.6038)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + style="opacity:0.7;fill:url(#linearGradient11473);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 30.226478,19.072457 C 30.226478,24.316478 25.970475,19.68155 20.726479,19.68155 C 15.482481,19.68155 11.226478,24.316478 11.226478,19.072457 C 11.226478,13.828473 15.482481,9.5724574 20.726479,9.5724574 C 25.970475,9.5724574 30.226478,13.828473 30.226478,19.072457 z " + id="path11465" + sodipodi:nodetypes="csssc" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" /> + </g> + <g + inkscape:export-ydpi="90.000000" + inkscape:export-xdpi="90.000000" + inkscape:export-filename="/home/derek/Desktop/Devel/icons.png" + transform="matrix(0.120171,0,0,0.120171,-91.00753,57.57068)" + id="g11982"> + <g + id="g11984"> + <rect + y="1193.7136" + x="1247.7158" + height="111.49675" + width="106.94586" + id="rect11986" + style="opacity:1;fill:url(#linearGradient11988);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient11990);stroke-width:2.42079997;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + </g> + </g> + <g + style="display:inline" + id="g11992" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/64x64/actions/amarok_pause.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" + transform="matrix(2,0,0,2,-413.14289,12.150448)"> + <g + inkscape:export-ydpi="89.999962" + inkscape:export-xdpi="89.999962" + inkscape:export-filename="/home/vadim/amarok icons/1/22x22/actions/amarok_circle.png" + transform="matrix(1.454545,0,0,1.454545,35.92385,213.91883)" + id="use11994"> + <path + sodipodi:type="arc" + style="opacity:1;fill:url(#linearGradient11487);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path11477" + sodipodi:cx="366.7616" + sodipodi:cy="436.4032" + sodipodi:rx="10.639691" + sodipodi:ry="10.639691" + d="M 377.40129 436.4032 A 10.639691 10.639691 0 1 1 356.12191,436.4032 A 10.639691 10.639691 0 1 1 377.40129 436.4032 z" + transform="matrix(1.033865,0,0,1.033865,-358.4555,-432.1183)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:type="arc" + style="opacity:1;fill:url(#linearGradient11489);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path11481" + sodipodi:cx="418.94757" + sodipodi:cy="444.83228" + sodipodi:rx="9.8239412" + sodipodi:ry="9.8239412" + d="M 428.77151 444.83228 A 9.8239412 9.8239412 0 1 1 409.12363,444.83228 A 9.8239412 9.8239412 0 1 1 428.77151 444.83228 z" + transform="matrix(1.068817,0,0,1.068817,-427.0518,-456.3806)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:type="arc" + style="opacity:1;fill:url(#radialGradient11491);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path11483" + sodipodi:cx="418.51233" + sodipodi:cy="446.44885" + sodipodi:rx="11.005301" + sodipodi:ry="11.005301" + d="M 429.51763 446.44885 A 11.005301 11.005301 0 1 1 407.50703,446.44885 A 11.005301 11.005301 0 1 1 429.51763 446.44885 z" + transform="matrix(0.908653,0,0,0.908654,-359.556,-386.6038)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + style="opacity:0.7;fill:url(#linearGradient11493);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 30.226478,19.072457 C 30.226478,24.316478 25.970475,19.68155 20.726479,19.68155 C 15.482481,19.68155 11.226478,24.316478 11.226478,19.072457 C 11.226478,13.828473 15.482481,9.5724574 20.726479,9.5724574 C 25.970475,9.5724574 30.226478,13.828473 30.226478,19.072457 z " + id="path11485" + sodipodi:nodetypes="csssc" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" /> + </g> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/vadim/collection.png" + transform="matrix(6.477895e-2,0,0,0.152432,-34.14433,2.046827)" + id="g11996" + style="fill:#943c3e;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:6.68458128;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"> + <path + style="opacity:1;fill:url(#linearGradient12000);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient12002);stroke-width:2.92753625;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 1577.4039,1526.7125 L 1577.4039,1616.4528 L 1663.6021,1616.4528 L 1663.6021,1526.7125 L 1577.4039,1526.7125 z M 1430.4823,1527.1891 L 1430.4823,1616.9975 L 1516.6806,1616.9975 L 1516.6806,1527.1891 L 1430.4823,1527.1891 z " + id="path11998" /> + </g> + </g> + <g + style="display:inline" + id="g12004" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/64x64/actions/amarok_next.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" + transform="matrix(2,0,0,2,-416.85701,11.578648)"> + <g + inkscape:export-ydpi="89.999962" + inkscape:export-xdpi="89.999962" + inkscape:export-filename="/home/vadim/amarok icons/1/22x22/actions/amarok_circle.png" + transform="matrix(1.454545,0,0,1.454545,36.28093,250.70473)" + id="use12006"> + <path + sodipodi:type="arc" + style="opacity:1;fill:url(#linearGradient11506);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path11498" + sodipodi:cx="366.7616" + sodipodi:cy="436.4032" + sodipodi:rx="10.639691" + sodipodi:ry="10.639691" + d="M 377.40129 436.4032 A 10.639691 10.639691 0 1 1 356.12191,436.4032 A 10.639691 10.639691 0 1 1 377.40129 436.4032 z" + transform="matrix(1.033865,0,0,1.033865,-358.4555,-432.1183)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:type="arc" + style="opacity:1;fill:url(#linearGradient11508);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path11500" + sodipodi:cx="418.94757" + sodipodi:cy="444.83228" + sodipodi:rx="9.8239412" + sodipodi:ry="9.8239412" + d="M 428.77151 444.83228 A 9.8239412 9.8239412 0 1 1 409.12363,444.83228 A 9.8239412 9.8239412 0 1 1 428.77151 444.83228 z" + transform="matrix(1.068817,0,0,1.068817,-427.0518,-456.3806)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:type="arc" + style="opacity:1;fill:url(#radialGradient11510);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path11502" + sodipodi:cx="418.51233" + sodipodi:cy="446.44885" + sodipodi:rx="11.005301" + sodipodi:ry="11.005301" + d="M 429.51763 446.44885 A 11.005301 11.005301 0 1 1 407.50703,446.44885 A 11.005301 11.005301 0 1 1 429.51763 446.44885 z" + transform="matrix(0.908653,0,0,0.908654,-359.556,-386.6038)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + style="opacity:0.7;fill:url(#linearGradient11512);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 30.226478,19.072457 C 30.226478,24.316478 25.970475,19.68155 20.726479,19.68155 C 15.482481,19.68155 11.226478,24.316478 11.226478,19.072457 C 11.226478,13.828473 15.482481,9.5724574 20.726479,9.5724574 C 25.970475,9.5724574 30.226478,13.828473 30.226478,19.072457 z " + id="path11504" + sodipodi:nodetypes="csssc" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" /> + </g> + <g + transform="matrix(-0.284505,0,0,0.284505,345.7854,255.2632)" + id="g12008"> + <g + transform="matrix(0.51291,0,0,0.51291,468.129,-559.841)" + id="g12010"> + <path + sodipodi:nodetypes="ccccccccc" + id="path12012" + d="M 1044.2816,1200.1729 L 1044.5628,1300.3931 L 982.99023,1261.4011 L 982.98547,1300.3931 L 958.81862,1300.3931 L 958.81862,1200.1729 L 982.90595,1200.1729 L 982.9123,1238.4265 L 1044.2816,1200.1729 z " + style="fill:url(#linearGradient12014);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient12016);stroke-width:1.99354815;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + </g> + </g> + <g + style="display:inline" + id="g12018" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/64x64/actions/amarok_undo.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" + transform="matrix(2,0,0,2,-415.70211,16.807648)"> + <g + inkscape:export-ydpi="89.999962" + inkscape:export-xdpi="89.999962" + inkscape:export-filename="/home/vadim/amarok icons/1/22x22/actions/amarok_circle.png" + transform="matrix(1.454545,0,0,1.454545,35.20348,284.09023)" + id="use12020"> + <path + sodipodi:type="arc" + style="opacity:1;fill:url(#linearGradient11524);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path11516" + sodipodi:cx="366.7616" + sodipodi:cy="436.4032" + sodipodi:rx="10.639691" + sodipodi:ry="10.639691" + d="M 377.40129 436.4032 A 10.639691 10.639691 0 1 1 356.12191,436.4032 A 10.639691 10.639691 0 1 1 377.40129 436.4032 z" + transform="matrix(1.033865,0,0,1.033865,-358.4555,-432.1183)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:type="arc" + style="opacity:1;fill:url(#linearGradient11526);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path11518" + sodipodi:cx="418.94757" + sodipodi:cy="444.83228" + sodipodi:rx="9.8239412" + sodipodi:ry="9.8239412" + d="M 428.77151 444.83228 A 9.8239412 9.8239412 0 1 1 409.12363,444.83228 A 9.8239412 9.8239412 0 1 1 428.77151 444.83228 z" + transform="matrix(1.068817,0,0,1.068817,-427.0518,-456.3806)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:type="arc" + style="opacity:1;fill:url(#radialGradient11528);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path11520" + sodipodi:cx="418.51233" + sodipodi:cy="446.44885" + sodipodi:rx="11.005301" + sodipodi:ry="11.005301" + d="M 429.51763 446.44885 A 11.005301 11.005301 0 1 1 407.50703,446.44885 A 11.005301 11.005301 0 1 1 429.51763 446.44885 z" + transform="matrix(0.908653,0,0,0.908654,-359.556,-386.6038)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + style="opacity:0.7;fill:url(#linearGradient11530);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 30.226478,19.072457 C 30.226478,24.316478 25.970475,19.68155 20.726479,19.68155 C 15.482481,19.68155 11.226478,24.316478 11.226478,19.072457 C 11.226478,13.828473 15.482481,9.5724574 20.726479,9.5724574 C 25.970475,9.5724574 30.226478,13.828473 30.226478,19.072457 z " + id="path11522" + sodipodi:nodetypes="csssc" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" /> + </g> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + id="path12022" + d="M 63.800205,321.19294 L 63.72631,318.96481 C 67.430861,318.24475 70.682885,315.50784 72.001541,311.50556 C 72.772189,309.1665 72.760653,306.75334 72.123127,304.56464 C 72.13906,305.5041 72.000754,306.44964 71.69729,307.3707 C 70.548963,310.85605 67.300973,313.06381 63.54023,313.37361 L 63.427098,309.94295 L 55.991554,316.05974 L 63.800205,321.19294 z " + style="fill:url(#linearGradient12024);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient12026);stroke-width:0.29090905;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + <g + style="display:inline" + id="g12028" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/64x64/actions/amarok_redo.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" + transform="matrix(2,0,0,2,-415.72641,26.492788)"> + <g + inkscape:export-ydpi="89.999962" + inkscape:export-xdpi="89.999962" + inkscape:export-filename="/home/vadim/amarok icons/1/22x22/actions/amarok_circle.png" + transform="matrix(1.454545,0,0,1.454545,35.21562,318.74783)" + id="use12030"> + <path + sodipodi:type="arc" + style="opacity:1;fill:url(#linearGradient11542);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path11534" + sodipodi:cx="366.7616" + sodipodi:cy="436.4032" + sodipodi:rx="10.639691" + sodipodi:ry="10.639691" + d="M 377.40129 436.4032 A 10.639691 10.639691 0 1 1 356.12191,436.4032 A 10.639691 10.639691 0 1 1 377.40129 436.4032 z" + transform="matrix(1.033865,0,0,1.033865,-358.4555,-432.1183)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:type="arc" + style="opacity:1;fill:url(#linearGradient11544);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path11536" + sodipodi:cx="418.94757" + sodipodi:cy="444.83228" + sodipodi:rx="9.8239412" + sodipodi:ry="9.8239412" + d="M 428.77151 444.83228 A 9.8239412 9.8239412 0 1 1 409.12363,444.83228 A 9.8239412 9.8239412 0 1 1 428.77151 444.83228 z" + transform="matrix(1.068817,0,0,1.068817,-427.0518,-456.3806)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:type="arc" + style="opacity:1;fill:url(#radialGradient11546);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path11538" + sodipodi:cx="418.51233" + sodipodi:cy="446.44885" + sodipodi:rx="11.005301" + sodipodi:ry="11.005301" + d="M 429.51763 446.44885 A 11.005301 11.005301 0 1 1 407.50703,446.44885 A 11.005301 11.005301 0 1 1 429.51763 446.44885 z" + transform="matrix(0.908653,0,0,0.908654,-359.556,-386.6038)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + style="opacity:0.7;fill:url(#linearGradient11548);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 30.226478,19.072457 C 30.226478,24.316478 25.970475,19.68155 20.726479,19.68155 C 15.482481,19.68155 11.226478,24.316478 11.226478,19.072457 C 11.226478,13.828473 15.482481,9.5724574 20.726479,9.5724574 C 25.970475,9.5724574 30.226478,13.828473 30.226478,19.072457 z " + id="path11540" + sodipodi:nodetypes="csssc" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" /> + </g> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/vadim/collection.png" + id="path12032" + d="M 66.914031,355.85055 L 66.987927,353.62241 C 63.283375,352.90235 60.031351,350.16544 58.712696,346.16316 C 57.942047,343.82411 57.953583,341.41094 58.591109,339.22224 C 58.575176,340.16171 58.713483,341.10724 59.016946,342.0283 C 60.165273,345.51365 63.413263,347.72141 67.174007,348.03121 L 67.287138,344.60056 L 74.722682,350.71734 L 66.914031,355.85055 z " + style="fill:url(#linearGradient12034);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient12036);stroke-width:0.29090905;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/32x32/actions/amarok_info.png" + transform="translate(-276.41332,76.58399)" + id="g12038"> + <g + id="g12040" + transform="matrix(1.605163,0,0,1.605163,80.416,-22.0111)"> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + transform="matrix(1.014646,0,0,1.014647,-372.754,-399.2811)" + d="M 428.77151 444.83228 A 9.8239412 9.8239412 0 1 1 409.12363,444.83228 A 9.8239412 9.8239412 0 1 1 428.77151 444.83228 z" + sodipodi:ry="9.8239412" + sodipodi:rx="9.8239412" + sodipodi:cy="444.83228" + sodipodi:cx="418.94757" + id="path12042" + style="fill:url(#linearGradient12289);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + transform="matrix(0.86456,0,0,0.864561,-309.4995,-333.9156)" + d="M 429.51763 446.44885 A 11.005301 11.005301 0 1 1 407.50703,446.44885 A 11.005301 11.005301 0 1 1 429.51763 446.44885 z" + sodipodi:ry="11.005301" + sodipodi:rx="11.005301" + sodipodi:cy="446.44885" + sodipodi:cx="418.51233" + id="path12044" + style="fill:url(#radialGradient12291);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + sodipodi:nodetypes="csssc" + id="path12046" + d="M 61.33077,52.065179 C 61.980407,54.489659 57.284671,51.286579 52.299305,51.286579 C 47.31394,51.286579 43.267841,55.264182 43.267841,52.065179 C 43.267841,47.079824 47.31394,43.033715 52.299305,43.033715 C 57.284671,43.033715 61.33077,47.079824 61.33077,52.065179 z " + style="opacity:0.89767444;fill:url(#linearGradient12293);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" /> + </g> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/vadim/collection.png" + id="g12048" + transform="matrix(0.342068,0,0,0.32758,49.52561,37.1377)"> + <path + style="fill:url(#linearGradient12295);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient12297);stroke-width:1.99999928;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 336.875,39.84375 C 335.1305,39.847373 333.35067,40.415792 331.84375,41.5625 C 328.2044,44.331905 327.44874,49.497742 330.15625,53.09375 C 332.86376,56.689757 338.01691,57.363154 341.65625,54.59375 C 345.2956,51.824345 346.05125,46.658505 343.34375,43.0625 C 341.75732,40.955465 339.34363,39.838624 336.875,39.84375 z M 345.0625,67.3125 L 321.75,71.25 L 321.9375,73.125 L 329.84375,75 L 330.40625,118.1875 L 344.875,118.21875 L 345.0625,67.3125 z " + transform="matrix(0.871656,0,0,0.871656,42.22375,6.017503)" + id="path12050" /> + </g> + </g> + <g + transform="translate(-178.0152,-283.17184)" + id="g12052" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/22x22/actions/amarok_info.png" + inkscape:export-xdpi="90.000092" + inkscape:export-ydpi="90.000092"> + <g + id="g12054" + transform="matrix(1.10355,0,0,1.10355,-32.622,368.737)" + inkscape:export-filename="/home/vadim/collection.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <path + sodipodi:type="arc" + style="fill:url(#linearGradient12299);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path12056" + sodipodi:cx="418.94757" + sodipodi:cy="444.83228" + sodipodi:rx="9.8239412" + sodipodi:ry="9.8239412" + d="M 428.77151 444.83228 A 9.8239412 9.8239412 0 1 1 409.12363,444.83228 A 9.8239412 9.8239412 0 1 1 428.77151 444.83228 z" + transform="matrix(1.014646,0,0,1.014647,-372.8672,-399.1678)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:type="arc" + style="fill:url(#radialGradient12301);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path12058" + sodipodi:cx="418.51233" + sodipodi:cy="446.44885" + sodipodi:rx="11.005301" + sodipodi:ry="11.005301" + d="M 429.51763 446.44885 A 11.005301 11.005301 0 1 1 407.50703,446.44885 A 11.005301 11.005301 0 1 1 429.51763 446.44885 z" + transform="matrix(0.810701,0,0,0.810702,-287.072,-309.7571)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + style="opacity:0.89767444;fill:url(#linearGradient12303);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 60.125175,52.179833 C 60.694066,54.302963 56.581985,51.498009 52.216275,51.498009 C 47.850565,51.498009 44.307375,54.981216 44.307375,52.179833 C 44.307375,47.814133 47.850565,44.270933 52.216275,44.270933 C 56.581985,44.270933 60.125175,47.814133 60.125175,52.179833 z " + id="path12060" + sodipodi:nodetypes="csssc" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" /> + </g> + <g + transform="matrix(0.235172,0,0,0.225211,-53.85915,409.4017)" + id="g12062" + inkscape:export-filename="/home/vadim/collection.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <path + id="path12064" + transform="matrix(0.871656,0,0,0.871656,42.22375,6.017503)" + d="M 336.875,39.84375 C 335.1305,39.847373 333.35067,40.415792 331.84375,41.5625 C 328.2044,44.331905 327.44874,49.497742 330.15625,53.09375 C 332.86376,56.689757 338.01691,57.363154 341.65625,54.59375 C 345.2956,51.824345 346.05125,46.658505 343.34375,43.0625 C 341.75732,40.955465 339.34363,39.838624 336.875,39.84375 z M 345.0625,67.3125 L 321.75,71.25 L 321.9375,73.125 L 329.84375,75 L 330.40625,118.1875 L 344.875,118.21875 L 345.0625,67.3125 z " + style="fill:url(#linearGradient12305);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient12307);stroke-width:1.99999928;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + </g> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/32x32/actions/amarok_mostplayed.png" + id="g12066" + transform="translate(-278.94375,127.554)"> + <g + id="g12068" + transform="matrix(1.605163,0,0,1.605163,80.51891,19.23282)"> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + transform="matrix(1.014646,0,0,1.014647,-372.754,-399.2811)" + d="M 428.77151 444.83228 A 9.8239412 9.8239412 0 1 1 409.12363,444.83228 A 9.8239412 9.8239412 0 1 1 428.77151 444.83228 z" + sodipodi:ry="9.8239412" + sodipodi:rx="9.8239412" + sodipodi:cy="444.83228" + sodipodi:cx="418.94757" + id="path12070" + style="fill:url(#linearGradient12309);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + transform="matrix(0.86456,0,0,0.864561,-309.4995,-333.9156)" + d="M 429.51763 446.44885 A 11.005301 11.005301 0 1 1 407.50703,446.44885 A 11.005301 11.005301 0 1 1 429.51763 446.44885 z" + sodipodi:ry="11.005301" + sodipodi:rx="11.005301" + sodipodi:cy="446.44885" + sodipodi:cx="418.51233" + id="path12072" + style="fill:url(#radialGradient12311);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + sodipodi:nodetypes="csssc" + id="path12074" + d="M 61.33077,52.065179 C 61.980407,54.489659 57.284671,51.286579 52.299305,51.286579 C 47.31394,51.286579 43.267841,55.264182 43.267841,52.065179 C 43.267841,47.079824 47.31394,43.033715 52.299305,43.033715 C 57.284671,43.033715 61.33077,47.079824 61.33077,52.065179 z " + style="opacity:0.89767444;fill:url(#linearGradient12313);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" /> + </g> + <path + d="M 279.95363,60.307923 L 272.1273,67.457686 L 274.18241,78.1177 L 264.96411,72.883814 L 255.4609,78.13247 L 257.59001,67.747987 L 249.66159,60.331821 L 260.19575,59.147744 L 264.79893,49.315645 L 269.18029,58.968328 L 279.95363,60.307923 z " + inkscape:randomized="0" + inkscape:rounded="0" + inkscape:flatsided="false" + sodipodi:arg2="0.2941925" + sodipodi:arg1="-0.31494821" + sodipodi:r2="7.6442261" + sodipodi:r1="15.925471" + sodipodi:cy="65.241112" + sodipodi:cx="264.81149" + sodipodi:sides="5" + id="path12076" + style="fill:url(#linearGradient12315);fill-opacity:1;stroke:url(#linearGradient12317);stroke-width:1.88700962;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="star" + transform="matrix(0.529939,0,0,0.529939,24.18437,69.03839)" /> + </g> + <g + id="g12079" + transform="translate(-277.26811,132.91569)" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/22x22/actions/amarok_mostplayed.png" + inkscape:export-xdpi="90.000092" + inkscape:export-ydpi="90.000092"> + <g + id="g12081" + transform="matrix(1.10355,0,0,1.10355,66.4319,44.86339)" + inkscape:export-filename="/home/vadim/collection.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <path + sodipodi:type="arc" + style="fill:url(#linearGradient12319);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path12083" + sodipodi:cx="418.94757" + sodipodi:cy="444.83228" + sodipodi:rx="9.8239412" + sodipodi:ry="9.8239412" + d="M 428.77151 444.83228 A 9.8239412 9.8239412 0 1 1 409.12363,444.83228 A 9.8239412 9.8239412 0 1 1 428.77151 444.83228 z" + transform="matrix(1.014646,0,0,1.014647,-372.8672,-399.1678)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:type="arc" + style="fill:url(#radialGradient12321);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path12085" + sodipodi:cx="418.51233" + sodipodi:cy="446.44885" + sodipodi:rx="11.005301" + sodipodi:ry="11.005301" + d="M 429.51763 446.44885 A 11.005301 11.005301 0 1 1 407.50703,446.44885 A 11.005301 11.005301 0 1 1 429.51763 446.44885 z" + transform="matrix(0.810701,0,0,0.810702,-287.072,-309.7571)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + style="opacity:0.89767444;fill:url(#linearGradient12323);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 60.125175,52.179833 C 60.694066,54.302963 56.581985,51.498009 52.216275,51.498009 C 47.850565,51.498009 44.307375,54.981216 44.307375,52.179833 C 44.307375,47.814133 47.850565,44.270933 52.216275,44.270933 C 56.581985,44.270933 60.125175,47.814133 60.125175,52.179833 z " + id="path12087" + sodipodi:nodetypes="csssc" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" /> + </g> + <path + d="M 279.95363,60.307923 L 272.1273,67.457686 L 274.18241,78.1177 L 264.96411,72.883814 L 255.4609,78.13247 L 257.59001,67.747987 L 249.66159,60.331821 L 260.19575,59.147744 L 264.79893,49.315645 L 269.18029,58.968328 L 279.95363,60.307923 z " + inkscape:randomized="0" + inkscape:rounded="0" + inkscape:flatsided="false" + sodipodi:arg2="0.2941925" + sodipodi:arg1="-0.31494821" + sodipodi:r2="7.6442261" + sodipodi:r1="15.925471" + sodipodi:cy="65.241112" + sodipodi:cx="264.81149" + sodipodi:sides="5" + id="path12089" + style="fill:url(#linearGradient12325);fill-opacity:1;stroke:url(#linearGradient12327);stroke-width:1.59743965;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="star" + transform="matrix(0.364333,0,0,0.364333,27.57707,79.10146)" /> + </g> + <g + id="g12091" + transform="translate(-277.26811,181.03611)" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/22x22/actions/amarok_refresh.png" + inkscape:export-xdpi="90.000092" + inkscape:export-ydpi="90.000092"> + <g + id="g12093" + transform="matrix(1.10355,0,0,1.10355,66.80399,79.52888)" + inkscape:export-filename="/home/vadim/collection.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <path + sodipodi:type="arc" + style="fill:url(#linearGradient12329);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path12095" + sodipodi:cx="418.94757" + sodipodi:cy="444.83228" + sodipodi:rx="9.8239412" + sodipodi:ry="9.8239412" + d="M 428.77151 444.83228 A 9.8239412 9.8239412 0 1 1 409.12363,444.83228 A 9.8239412 9.8239412 0 1 1 428.77151 444.83228 z" + transform="matrix(1.014646,0,0,1.014647,-372.8672,-399.1678)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:type="arc" + style="fill:url(#radialGradient12331);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path12097" + sodipodi:cx="418.51233" + sodipodi:cy="446.44885" + sodipodi:rx="11.005301" + sodipodi:ry="11.005301" + d="M 429.51763 446.44885 A 11.005301 11.005301 0 1 1 407.50703,446.44885 A 11.005301 11.005301 0 1 1 429.51763 446.44885 z" + transform="matrix(0.810701,0,0,0.810702,-287.072,-309.7571)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + style="opacity:0.89767444;fill:url(#linearGradient12333);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 60.125175,52.179833 C 60.694066,54.302963 56.581985,51.498009 52.216275,51.498009 C 47.850565,51.498009 44.307375,54.981216 44.307375,52.179833 C 44.307375,47.814133 47.850565,44.270933 52.216275,44.270933 C 56.581985,44.270933 60.125175,47.814133 60.125175,52.179833 z " + id="path12099" + sodipodi:nodetypes="csssc" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" /> + </g> + <path + style="fill:url(#linearGradient12335);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient12337);stroke-width:0.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 126.39311,129.34365 L 126.06207,130.78175 C 123.56966,130.61343 121.02433,131.81233 119.50111,134.14906 C 118.6109,135.51473 118.20855,137.06061 118.2448,138.56917 C 118.39412,137.96541 118.64316,137.38394 118.9937,136.84618 C 120.32018,134.81125 122.77308,133.95022 125.23178,134.39056 L 124.72167,136.60468 L 130.51746,133.9537 L 126.39311,129.34365 z M 130.60971,135.655 C 130.46039,136.25876 130.21408,136.84023 129.86353,137.378 C 128.53705,139.41292 126.08415,140.27395 123.62546,139.83361 L 124.13557,137.61949 L 118.33978,140.27318 L 122.46141,144.88051 L 122.79244,143.44241 C 125.28487,143.61074 127.83019,142.41184 129.35341,140.0751 C 130.24362,138.70943 130.64598,137.16356 130.60971,135.655 z " + id="path12101" + inkscape:export-filename="/home/vadim/collection.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + </g> + <g + id="g12103" + transform="translate(-279.26811,224.49871)" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/22x22/actions/amarok_rescan.png" + inkscape:export-xdpi="90.000092" + inkscape:export-ydpi="90.000092"> + <g + id="g12105" + transform="matrix(1.10355,0,0,1.10355,67.34767,116.0663)" + inkscape:export-filename="/home/vadim/collection.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <path + sodipodi:type="arc" + style="fill:url(#linearGradient12339);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path12107" + sodipodi:cx="418.94757" + sodipodi:cy="444.83228" + sodipodi:rx="9.8239412" + sodipodi:ry="9.8239412" + d="M 428.77151 444.83228 A 9.8239412 9.8239412 0 1 1 409.12363,444.83228 A 9.8239412 9.8239412 0 1 1 428.77151 444.83228 z" + transform="matrix(1.014646,0,0,1.014647,-372.8672,-399.1678)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:type="arc" + style="fill:url(#radialGradient12341);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path12109" + sodipodi:cx="418.51233" + sodipodi:cy="446.44885" + sodipodi:rx="11.005301" + sodipodi:ry="11.005301" + d="M 429.51763 446.44885 A 11.005301 11.005301 0 1 1 407.50703,446.44885 A 11.005301 11.005301 0 1 1 429.51763 446.44885 z" + transform="matrix(0.810701,0,0,0.810702,-287.072,-309.7571)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + style="opacity:0.89767444;fill:url(#linearGradient12343);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 60.125175,52.179833 C 60.694066,54.302963 56.581985,51.498009 52.216275,51.498009 C 47.850565,51.498009 44.307375,54.981216 44.307375,52.179833 C 44.307375,47.814133 47.850565,44.270933 52.216275,44.270933 C 56.581985,44.270933 60.125175,47.814133 60.125175,52.179833 z " + id="path12111" + sodipodi:nodetypes="csssc" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" /> + </g> + <path + id="path12113" + d="M 127.81678,165.30511 L 123.56183,167.90417 L 126.99711,171.35134 L 127.19051,169.93782 C 128.68686,170.70223 129.68978,172.20332 129.72321,173.96862 C 129.75774,175.72276 129.06265,177.81332 126.53864,178.37203 L 128.16639,179.17582 L 126.6651,180.6004 C 129.98947,180.46985 132.20988,177.81509 132.20988,173.8592 C 132.20988,170.7727 130.27095,168.17855 127.56812,167.12902 L 127.81678,165.30511 z M 122.65038,167.11473 C 120.68784,168.01084 117.732,170.26222 117.732,173.8592 C 117.732,176.70975 119.38616,179.14619 121.76591,180.33403 L 121.4804,181.99378 L 125.80904,179.73215 L 122.53033,175.98404 L 122.24482,177.61643 C 121.09556,176.78017 120.32917,175.47502 120.32917,173.96862 C 120.32917,172.76197 120.68363,171.25353 122.83223,169.81473 L 121.59563,169.52429 L 122.65038,167.11473 z " + style="fill:url(#linearGradient12345);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient12347);stroke-width:0.45900002;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:nodetypes="ccccscccscccscccccsccc" /> + </g> + <g + id="g12578" + transform="translate(-178.61977,-583.85013)" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/32x32/actions/amarok_refresh.png" + inkscape:export-xdpi="90.000092" + inkscape:export-ydpi="90.000092"> + <g + id="g12115" + transform="matrix(1.605163,0,0,1.605163,-17.66973,813.4229)" + inkscape:export-filename="/home/lando/Projects/Amarok/Toolbar-and-menu-icons/amarok_refresh.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + transform="matrix(1.014646,0,0,1.014647,-372.754,-399.2811)" + d="M 428.77151 444.83228 A 9.8239412 9.8239412 0 1 1 409.12363,444.83228 A 9.8239412 9.8239412 0 1 1 428.77151 444.83228 z" + sodipodi:ry="9.8239412" + sodipodi:rx="9.8239412" + sodipodi:cy="444.83228" + sodipodi:cx="418.94757" + id="path12117" + style="fill:url(#linearGradient12585);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + transform="matrix(0.86456,0,0,0.864561,-309.4995,-333.9156)" + d="M 429.51763 446.44885 A 11.005301 11.005301 0 1 1 407.50703,446.44885 A 11.005301 11.005301 0 1 1 429.51763 446.44885 z" + sodipodi:ry="11.005301" + sodipodi:rx="11.005301" + sodipodi:cy="446.44885" + sodipodi:cx="418.51233" + id="path12119" + style="fill:url(#radialGradient12587);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + sodipodi:nodetypes="csssc" + id="path12121" + d="M 61.33077,52.065179 C 61.980407,54.489659 57.284671,51.286579 52.299305,51.286579 C 47.31394,51.286579 43.267841,55.264182 43.267841,52.065179 C 43.267841,47.079824 47.31394,43.033715 52.299305,43.033715 C 57.284671,43.033715 61.33077,47.079824 61.33077,52.065179 z " + style="opacity:0.89767444;fill:url(#linearGradient12589);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" /> + </g> + <path + style="fill:url(#linearGradient12591);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient12593);stroke-width:0.72727352;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 69.005356,885.88068 L 68.523846,887.97246 C 64.898516,887.72763 61.196216,889.47148 58.980616,892.87037 C 57.685766,894.8568 57.100516,897.10535 57.153256,899.29963 C 57.370456,898.42142 57.732686,897.57566 58.242566,896.79345 C 60.171986,893.83356 63.739846,892.58115 67.316146,893.22164 L 66.574156,896.44219 L 75.004416,892.5862 L 69.005356,885.88068 z M 75.138596,895.06082 C 74.921406,895.93903 74.563126,896.7848 74.053246,897.56702 C 72.123816,900.5269 68.555956,901.7793 64.979676,901.13881 L 65.721666,897.91827 L 57.291406,901.77819 L 63.286506,908.47977 L 63.768016,906.38799 C 67.393366,906.63282 71.095656,904.88897 73.311256,901.49008 C 74.606096,899.50365 75.191346,897.2551 75.138596,895.06082 z " + id="path12123" + inkscape:export-filename="/home/lando/Projects/Amarok/Toolbar-and-menu-icons/amarok_refresh.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + </g> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/32x32/actions/amarok_rescan.png" + id="g12125" + transform="translate(-279.26811,221.54781)"> + <g + id="g12127" + transform="matrix(1.605163,0,0,1.605163,79.66329,88.02498)"> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + transform="matrix(1.014646,0,0,1.014647,-372.754,-399.2811)" + d="M 428.77151 444.83228 A 9.8239412 9.8239412 0 1 1 409.12363,444.83228 A 9.8239412 9.8239412 0 1 1 428.77151 444.83228 z" + sodipodi:ry="9.8239412" + sodipodi:rx="9.8239412" + sodipodi:cy="444.83228" + sodipodi:cx="418.94757" + id="path12129" + style="fill:url(#linearGradient12359);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + transform="matrix(0.86456,0,0,0.864561,-309.4995,-333.9156)" + d="M 429.51763 446.44885 A 11.005301 11.005301 0 1 1 407.50703,446.44885 A 11.005301 11.005301 0 1 1 429.51763 446.44885 z" + sodipodi:ry="11.005301" + sodipodi:rx="11.005301" + sodipodi:cy="446.44885" + sodipodi:cx="418.51233" + id="path12131" + style="fill:url(#radialGradient12361);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + sodipodi:nodetypes="csssc" + id="path12133" + d="M 61.33077,52.065179 C 61.980407,54.489659 57.284671,51.286579 52.299305,51.286579 C 47.31394,51.286579 43.267841,55.264182 43.267841,52.065179 C 43.267841,47.079824 47.31394,43.033715 52.299305,43.033715 C 57.284671,43.033715 61.33077,47.079824 61.33077,52.065179 z " + style="opacity:0.89767444;fill:url(#linearGradient12363);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" /> + </g> + <path + id="path12135" + d="M 167.61829,159.64502 L 161.42927,163.42547 L 166.42604,168.43954 L 166.70735,166.3835 C 168.88386,167.49537 170.34265,169.67878 170.39128,172.2465 C 170.4415,174.79796 169.43047,177.83879 165.75917,178.65146 L 168.12681,179.8206 L 165.94312,181.89273 C 170.77857,181.70283 174.00825,177.84136 174.00825,172.08734 C 174.00825,167.59788 171.188,163.82457 167.2566,162.29797 L 167.61829,159.64502 z M 160.10353,162.27719 C 157.24892,163.58062 152.94951,166.85537 152.94951,172.08734 C 152.94951,176.23359 155.35556,179.77751 158.81702,181.50527 L 158.40174,183.91947 L 164.69793,180.62981 L 159.9289,175.17801 L 159.51362,177.5524 C 157.84196,176.33602 156.72721,174.43762 156.72721,172.2465 C 156.72721,170.49137 157.2428,168.29727 160.36802,166.20447 L 158.56934,165.78201 L 160.10353,162.27719 z " + style="fill:url(#linearGradient12365);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient12367);stroke-width:0.66763687;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:nodetypes="ccccscccscccscccccsccc" /> + </g> + <g + id="g12872" + transform="translate(-181.29218,-555.21468)" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/32x32/actions/amarok_track.png" + inkscape:export-xdpi="90.000092" + inkscape:export-ydpi="90.000092"> + <g + id="g12137" + transform="matrix(1.605163,0,0,1.605163,-18.07929,941.3218)" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_track.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + transform="matrix(1.014646,0,0,1.014647,-372.754,-399.2811)" + d="M 428.77151 444.83228 A 9.8239412 9.8239412 0 1 1 409.12363,444.83228 A 9.8239412 9.8239412 0 1 1 428.77151 444.83228 z" + sodipodi:ry="9.8239412" + sodipodi:rx="9.8239412" + sodipodi:cy="444.83228" + sodipodi:cx="418.94757" + id="path12139" + style="fill:url(#linearGradient13149);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + transform="matrix(0.86456,0,0,0.864561,-309.4995,-333.9156)" + d="M 429.51763 446.44885 A 11.005301 11.005301 0 1 1 407.50703,446.44885 A 11.005301 11.005301 0 1 1 429.51763 446.44885 z" + sodipodi:ry="11.005301" + sodipodi:rx="11.005301" + sodipodi:cy="446.44885" + sodipodi:cx="418.51233" + id="path12141" + style="fill:url(#radialGradient13151);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + sodipodi:nodetypes="csssc" + id="path12143" + d="M 61.33077,52.065179 C 61.980407,54.489659 57.284671,51.286579 52.299305,51.286579 C 47.31394,51.286579 43.267841,55.264182 43.267841,52.065179 C 43.267841,47.079824 47.31394,43.033715 52.299305,43.033715 C 57.284671,43.033715 61.33077,47.079824 61.33077,52.065179 z " + style="opacity:0.89767444;fill:url(#linearGradient13153);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" /> + </g> + <g + id="g12145" + transform="translate(-97.43747,818.1603)" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_track.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <path + style="color:#000000;fill:url(#linearGradient13155);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient13157);stroke-width:1;stroke-linecap:round;stroke-linejoin:round;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0.75376886;visibility:visible;display:block;overflow:visible" + d="M 160.35798,194.32998 C 159.44703,194.45242 158.98728,195.66462 159.41391,196.48935 C 160.73817,201.04946 162.06244,205.60958 163.38671,210.16969 C 160.99398,209.71902 158.25682,211.21088 157.57207,213.78862 C 157.07041,215.69347 158.08704,217.83504 159.7451,218.66082 C 162.2098,220.07032 165.72114,219.02731 166.94069,216.27621 C 167.52683,215.00041 167.34378,213.45542 166.70616,212.24398 C 165.65935,208.63608 164.61254,205.02817 163.56574,201.42027 C 165.49428,201.11297 167.50276,200.17928 168.53268,198.32157 C 168.92066,197.5672 169.33633,196.52966 168.72264,195.77544 C 168.22462,195.12181 167.31678,195.2785 166.71328,195.65871 C 165.14487,196.31891 163.42205,195.7153 161.97889,194.97431 C 161.4743,194.68138 160.96253,194.2792 160.35798,194.32998 z " + id="path12147" + inkscape:export-filename="/home/kant/Desktop/playlist16.png" + inkscape:export-xdpi="3.3299999" + inkscape:export-ydpi="3.3299999" /> + </g> + </g> + <g + id="g12149" + transform="translate(-278.26811,270.37341)" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/22x22/actions/amarok_track.png" + inkscape:export-xdpi="90.000092" + inkscape:export-ydpi="90.000092"> + <g + id="g12151" + transform="matrix(1.10355,0,0,1.10355,66.41684,146.726)" + inkscape:export-filename="/home/vadim/collection.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <path + sodipodi:type="arc" + style="fill:url(#linearGradient12379);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path12153" + sodipodi:cx="418.94757" + sodipodi:cy="444.83228" + sodipodi:rx="9.8239412" + sodipodi:ry="9.8239412" + d="M 428.77151 444.83228 A 9.8239412 9.8239412 0 1 1 409.12363,444.83228 A 9.8239412 9.8239412 0 1 1 428.77151 444.83228 z" + transform="matrix(1.014646,0,0,1.014647,-372.8672,-399.1678)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:type="arc" + style="fill:url(#radialGradient12381);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path12155" + sodipodi:cx="418.51233" + sodipodi:cy="446.44885" + sodipodi:rx="11.005301" + sodipodi:ry="11.005301" + d="M 429.51763 446.44885 A 11.005301 11.005301 0 1 1 407.50703,446.44885 A 11.005301 11.005301 0 1 1 429.51763 446.44885 z" + transform="matrix(0.810701,0,0,0.810702,-287.072,-309.7571)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + style="opacity:0.89767444;fill:url(#linearGradient12383);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 60.125175,52.179833 C 60.694066,54.302963 56.581985,51.498009 52.216275,51.498009 C 47.850565,51.498009 44.307375,54.981216 44.307375,52.179833 C 44.307375,47.814133 47.850565,44.270933 52.216275,44.270933 C 56.581985,44.270933 60.125175,47.814133 60.125175,52.179833 z " + id="path12157" + sodipodi:nodetypes="csssc" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" /> + </g> + <g + id="g12159" + transform="translate(-0.461535,-1.892739)"> + <path + style="color:#000000;fill:url(#linearGradient12385);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient12387);stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0.75376886;visibility:visible;display:block;overflow:visible" + d="M 122.34066,197.85001 C 121.71438,197.93419 121.3983,198.76757 121.69161,199.33457 C 122.60203,202.46965 123.51246,205.60474 124.42289,208.73981 C 122.7779,208.42998 120.89611,209.45563 120.42535,211.22783 C 120.08046,212.53742 120.77939,214.00974 121.9193,214.57747 C 123.61377,215.5465 126.0278,214.82943 126.86624,212.93805 C 127.26921,212.06093 127.14337,210.99875 126.705,210.16589 C 125.98533,207.68545 125.26565,205.20502 124.54597,202.72459 C 125.87184,202.51332 127.25266,201.8714 127.96073,200.59423 C 128.22746,200.0756 128.51323,199.36229 128.09133,198.84377 C 127.74894,198.39439 127.1248,198.50212 126.7099,198.76351 C 125.63163,199.2174 124.44719,198.80242 123.45502,198.29299 C 123.10812,198.0916 122.75628,197.8151 122.34066,197.85001 z " + id="path12161" + inkscape:export-filename="/home/kant/Desktop/playlist16.png" + inkscape:export-xdpi="3.3299999" + inkscape:export-ydpi="3.3299999" /> + </g> + </g> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/32x32/actions/amarok_love.png" + id="g12163" + transform="translate(-275.26811,384.49491)"> + <g + id="g12165" + transform="matrix(1.605163,0,0,1.605163,76.916,231.5603)"> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + transform="matrix(1.014646,0,0,1.014647,-372.754,-399.2811)" + d="M 428.77151 444.83228 A 9.8239412 9.8239412 0 1 1 409.12363,444.83228 A 9.8239412 9.8239412 0 1 1 428.77151 444.83228 z" + sodipodi:ry="9.8239412" + sodipodi:rx="9.8239412" + sodipodi:cy="444.83228" + sodipodi:cx="418.94757" + id="path12167" + style="fill:url(#linearGradient12389);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + transform="matrix(0.86456,0,0,0.864561,-309.4995,-333.9156)" + d="M 429.51763 446.44885 A 11.005301 11.005301 0 1 1 407.50703,446.44885 A 11.005301 11.005301 0 1 1 429.51763 446.44885 z" + sodipodi:ry="11.005301" + sodipodi:rx="11.005301" + sodipodi:cy="446.44885" + sodipodi:cx="418.51233" + id="path12169" + style="fill:url(#radialGradient12391);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + sodipodi:nodetypes="csssc" + id="path12171" + d="M 61.33077,52.065179 C 61.980407,54.489659 57.284671,51.286579 52.299305,51.286579 C 47.31394,51.286579 43.267841,55.264182 43.267841,52.065179 C 43.267841,47.079824 47.31394,43.033715 52.299305,43.033715 C 57.284671,43.033715 61.33077,47.079824 61.33077,52.065179 z " + style="opacity:0.89767444;fill:url(#linearGradient12393);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" /> + </g> + <path + style="fill:url(#linearGradient12395);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient12397);stroke-width:0.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 160.99719,326.20599 C 160.99719,326.20599 146.31058,317.80296 150.79695,310.32569 C 152.24223,307.91688 153.88708,307.02377 155.42573,306.90751 C 158.36316,306.68555 160.90907,309.88251 160.90907,309.88251 C 160.90907,309.88251 163.0436,307.09476 165.66248,306.90751 C 167.39706,306.78349 169.34597,307.53365 171.02119,310.32569 C 175.50755,317.80296 160.99719,326.20599 160.99719,326.20599 z " + id="path12173" + sodipodi:nodetypes="cscccsc" /> + </g> + <g + inkscape:export-ydpi="90.000092" + inkscape:export-xdpi="90.000092" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/22x22/actions/amarok_love.png" + id="g12175" + transform="translate(-275.26811,389.97751)"> + <g + id="g12177" + transform="matrix(1.10355,0,0,1.10355,64.35265,257.07)" + inkscape:export-filename="/home/vadim/collection.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <path + sodipodi:type="arc" + style="fill:url(#linearGradient12399);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path12179" + sodipodi:cx="418.94757" + sodipodi:cy="444.83228" + sodipodi:rx="9.8239412" + sodipodi:ry="9.8239412" + d="M 428.77151 444.83228 A 9.8239412 9.8239412 0 1 1 409.12363,444.83228 A 9.8239412 9.8239412 0 1 1 428.77151 444.83228 z" + transform="matrix(1.014646,0,0,1.014647,-372.8672,-399.1678)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:type="arc" + style="fill:url(#radialGradient12401);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path12181" + sodipodi:cx="418.51233" + sodipodi:cy="446.44885" + sodipodi:rx="11.005301" + sodipodi:ry="11.005301" + d="M 429.51763 446.44885 A 11.005301 11.005301 0 1 1 407.50703,446.44885 A 11.005301 11.005301 0 1 1 429.51763 446.44885 z" + transform="matrix(0.810701,0,0,0.810702,-287.072,-309.7571)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + style="opacity:0.89767444;fill:url(#linearGradient12403);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 60.125175,52.179833 C 60.694066,54.302963 56.581985,51.498009 52.216275,51.498009 C 47.850565,51.498009 44.307375,54.981216 44.307375,52.179833 C 44.307375,47.814133 47.850565,44.270933 52.216275,44.270933 C 56.581985,44.270933 60.125175,47.814133 60.125175,52.179833 z " + id="path12183" + sodipodi:nodetypes="csssc" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" /> + </g> + <path + sodipodi:nodetypes="cscccsc" + id="path12185" + d="M 122.0294,321.87112 C 122.0294,321.87112 112.66429,316.51282 115.52508,311.74484 C 116.44668,310.20884 117.49554,309.63933 118.47668,309.5652 C 120.34977,309.42366 121.9732,311.9387 121.9732,311.9387 C 121.9732,311.9387 123.33432,309.6846 125.00427,309.5652 C 126.11035,309.48612 127.3531,309.96446 128.42132,311.74484 C 131.28211,316.51282 122.0294,321.87112 122.0294,321.87112 z " + style="fill:url(#linearGradient12405);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient12407);stroke-width:0.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/32x32/actions/amarok_clock.png" + id="g12187" + transform="translate(-278.26811,301.42321)"> + <g + id="g12189" + transform="matrix(1.605163,0,0,1.605163,77.31162,159.1495)"> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + transform="matrix(1.014646,0,0,1.014647,-372.754,-399.2811)" + d="M 428.77151 444.83228 A 9.8239412 9.8239412 0 1 1 409.12363,444.83228 A 9.8239412 9.8239412 0 1 1 428.77151 444.83228 z" + sodipodi:ry="9.8239412" + sodipodi:rx="9.8239412" + sodipodi:cy="444.83228" + sodipodi:cx="418.94757" + id="path12191" + style="fill:url(#linearGradient12409);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + transform="matrix(0.86456,0,0,0.864561,-309.4995,-333.9156)" + d="M 429.51763 446.44885 A 11.005301 11.005301 0 1 1 407.50703,446.44885 A 11.005301 11.005301 0 1 1 429.51763 446.44885 z" + sodipodi:ry="11.005301" + sodipodi:rx="11.005301" + sodipodi:cy="446.44885" + sodipodi:cx="418.51233" + id="path12193" + style="fill:url(#radialGradient12411);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + sodipodi:nodetypes="csssc" + id="path12195" + d="M 61.33077,52.065179 C 61.980407,54.489659 57.284671,51.286579 52.299305,51.286579 C 47.31394,51.286579 43.267841,55.264182 43.267841,52.065179 C 43.267841,47.079824 47.31394,43.033715 52.299305,43.033715 C 57.284671,43.033715 61.33077,47.079824 61.33077,52.065179 z " + style="opacity:0.89767444;fill:url(#linearGradient12413);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" /> + </g> + <rect + style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.25;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect12197" + width="1.1570846" + height="2.8284271" + x="160.73047" + y="228.87321" /> + <rect + y="254.26477" + x="160.73047" + height="2.8284271" + width="1.1570846" + id="rect12199" + style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.25;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.25;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect12201" + width="1.1570839" + height="2.828429" + x="-243.30341" + y="172.30132" + transform="matrix(0,-1,1,0,0,0)" /> + <rect + transform="matrix(0,-1,1,0,0,0)" + y="147.48828" + x="-243.30341" + height="2.828429" + width="1.1570839" + id="rect12203" + style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.25;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + y="97.521248" + x="268.86493" + height="2.828429" + width="1.1570832" + id="rect12205" + style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.25;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + transform="matrix(0.829849,0.557988,-0.557988,0.829849,0,0)" /> + <rect + style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.25;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect12207" + width="1.1570832" + height="2.828429" + x="268.86493" + y="122.91283" + transform="matrix(0.829849,0.557988,-0.557988,0.829849,0,0)" /> + <rect + transform="matrix(0.557988,-0.829849,0.829849,0.557988,0,0)" + y="280.43579" + x="-111.95145" + height="2.8284252" + width="1.1570847" + id="rect12209" + style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.25;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.25;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect12211" + width="1.1570847" + height="2.8284252" + x="-111.95145" + y="255.6228" + transform="matrix(0.557988,-0.829849,0.829849,0.557988,0,0)" /> + <rect + transform="matrix(0.484003,0.875066,-0.875066,0.484003,0,0)" + style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.25;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect12213" + width="1.1570789" + height="2.8284206" + x="290.07108" + y="-37.706596" /> + <rect + transform="matrix(0.484003,0.875066,-0.875066,0.484003,0,0)" + y="-12.315089" + x="290.07108" + height="2.8284206" + width="1.1570789" + id="rect12215" + style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.25;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.25;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect12217" + width="1.1570812" + height="2.8284147" + x="23.276415" + y="301.64191" + transform="matrix(0.875066,-0.484003,0.484003,0.875066,0,0)" /> + <rect + transform="matrix(0.875066,-0.484003,0.484003,0.875066,0,0)" + y="276.82895" + x="23.276415" + height="2.8284147" + width="1.1570812" + id="rect12219" + style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.25;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + sodipodi:nodetypes="ccccc" + id="path12221" + d="M 162.13976,243.20705 L 155.94631,250.36942 L 155.52177,250.11699 L 160.86615,242.4498 L 162.13976,243.20705 z " + style="fill:url(#linearGradient12415);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient12417);stroke-width:1.08101451px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + style="fill:url(#linearGradient12419);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient12421);stroke-width:1.20098424px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="M 160.51696,243.24049 L 161.0919,231.05022 L 161.66684,231.05022 L 162.24179,243.24049 L 160.51696,243.24049 z " + id="path12223" + sodipodi:nodetypes="ccccc" /> + <path + sodipodi:type="arc" + style="opacity:1;fill:url(#radialGradient12423);fill-opacity:1;stroke:url(#linearGradient12425);stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path12225" + sodipodi:cx="161.10194" + sodipodi:cy="243.27481" + sodipodi:rx="1.868932" + sodipodi:ry="1.815534" + d="M 162.97088 243.27481 A 1.868932 1.815534 0 1 1 159.23301,243.27481 A 1.868932 1.815534 0 1 1 162.97088 243.27481 z" + transform="matrix(1.07013,0,0,1.101604,-11.09106,-25.26757)" /> + </g> + <g + id="g12227" + transform="translate(-278.26811,305.17311)" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/22x22/actions/amarok_clock.png" + inkscape:export-xdpi="90.000092" + inkscape:export-ydpi="90.000092"> + <g + id="g12229" + transform="matrix(1.10355,0,0,1.10355,64.9714,186.3919)" + inkscape:export-filename="/home/vadim/collection.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <path + sodipodi:type="arc" + style="fill:url(#linearGradient12427);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path12231" + sodipodi:cx="418.94757" + sodipodi:cy="444.83228" + sodipodi:rx="9.8239412" + sodipodi:ry="9.8239412" + d="M 428.77151 444.83228 A 9.8239412 9.8239412 0 1 1 409.12363,444.83228 A 9.8239412 9.8239412 0 1 1 428.77151 444.83228 z" + transform="matrix(1.014646,0,0,1.014647,-372.8672,-399.1678)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:type="arc" + style="fill:url(#radialGradient12429);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path12233" + sodipodi:cx="418.51233" + sodipodi:cy="446.44885" + sodipodi:rx="11.005301" + sodipodi:ry="11.005301" + d="M 429.51763 446.44885 A 11.005301 11.005301 0 1 1 407.50703,446.44885 A 11.005301 11.005301 0 1 1 429.51763 446.44885 z" + transform="matrix(0.810701,0,0,0.810702,-287.072,-309.7571)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + style="opacity:0.89767444;fill:url(#linearGradient12431);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 60.125175,52.179833 C 60.694066,54.302963 56.581985,51.498009 52.216275,51.498009 C 47.850565,51.498009 44.307375,54.981216 44.307375,52.179833 C 44.307375,47.814133 47.850565,44.270933 52.216275,44.270933 C 56.581985,44.270933 60.125175,47.814133 60.125175,52.179833 z " + id="path12235" + sodipodi:nodetypes="csssc" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" /> + </g> + <rect + y="234.4519" + x="122.07199" + height="1.9445429" + width="0.79549539" + id="rect12237" + style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.25;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.25;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect12239" + width="0.79549539" + height="1.9445429" + x="122.07199" + y="251.90858" /> + <rect + transform="matrix(0,-1,1,0,0,0)" + y="130.02696" + x="-244.37267" + height="1.9445442" + width="0.79549491" + id="rect12241" + style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.25;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.25;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect12243" + width="0.79549491" + height="1.9445442" + x="-244.37267" + y="112.96799" + transform="matrix(0,-1,1,0,0,0)" /> + <rect + transform="matrix(0.829849,0.557988,-0.557988,0.829849,0,0)" + style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.25;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect12245" + width="0.79549432" + height="1.9445441" + x="237.46754" + y="124.57272" /> + <rect + transform="matrix(0.829849,0.557988,-0.557988,0.829849,0,0)" + y="142.02942" + x="237.46754" + height="1.9445441" + width="0.79549432" + id="rect12247" + style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.25;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.25;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect12249" + width="0.79549539" + height="1.9445411" + x="-134.49347" + y="245.42249" + transform="matrix(0.557988,-0.829849,0.829849,0.557988,0,0)" /> + <rect + transform="matrix(0.557988,-0.829849,0.829849,0.557988,0,0)" + y="228.36357" + x="-134.49347" + height="1.9445411" + width="0.79549539" + id="rect12251" + style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.25;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + y="1.2698265" + x="272.49246" + height="1.944538" + width="0.79549128" + id="rect12253" + style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.25;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + transform="matrix(0.484003,0.875066,-0.875066,0.484003,0,0)" /> + <rect + style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.25;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect12255" + width="0.79549128" + height="1.944538" + x="272.49246" + y="18.726486" + transform="matrix(0.484003,0.875066,-0.875066,0.484003,0,0)" /> + <rect + transform="matrix(0.875066,-0.484003,0.484003,0.875066,0,0)" + y="280.44739" + x="-11.190576" + height="1.9445341" + width="0.79549277" + id="rect12257" + style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.25;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.25;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect12259" + width="0.79549277" + height="1.9445341" + x="-11.190576" + y="263.38855" + transform="matrix(0.875066,-0.484003,0.484003,0.875066,0,0)" /> + <path + sodipodi:nodetypes="ccccc" + id="path12261" + d="M 122.04164,244.32941 L 122.31569,235.9486 L 122.58973,235.9486 L 122.86378,244.32941 L 122.04164,244.32941 z " + style="fill:url(#linearGradient12433);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + style="fill:url(#linearGradient12435);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="M 123.21896,243.93206 L 119.53615,248.80389 L 119.28371,248.63219 L 122.46164,243.41697 L 123.21896,243.93206 z " + id="path12263" + sodipodi:nodetypes="ccccc" /> + <path + sodipodi:type="arc" + style="opacity:1;fill:url(#radialGradient12437);fill-opacity:1;stroke:url(#linearGradient12439);stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path12265" + sodipodi:cx="122.61263" + sodipodi:cy="243.92044" + sodipodi:rx="0.96019417" + sodipodi:ry="0.96019417" + d="M 123.57283 243.92044 A 0.96019417 0.96019417 0 1 1 121.65244,243.92044 A 0.96019417 0.96019417 0 1 1 123.57283 243.92044 z" + transform="matrix(1.041456,0,0,1.041456,-5.10099,-10.05734)" /> + </g> + <g + id="g13044" + transform="matrix(1.500002,0,0,1.500002,-148.06198,-1130.8978)" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/48x48/actions/amarok_remove.png" + inkscape:export-xdpi="90.000092" + inkscape:export-ydpi="90.000092"> + <g + id="g12267" + transform="matrix(1.605163,0,0,1.605163,-22.15089,1075.787)"> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + transform="matrix(1.014646,0,0,1.014647,-372.754,-399.2811)" + d="M 428.77151 444.83228 A 9.8239412 9.8239412 0 1 1 409.12363,444.83228 A 9.8239412 9.8239412 0 1 1 428.77151 444.83228 z" + sodipodi:ry="9.8239412" + sodipodi:rx="9.8239412" + sodipodi:cy="444.83228" + sodipodi:cx="418.94757" + id="path12269" + style="fill:url(#linearGradient13051);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + transform="matrix(0.86456,0,0,0.864561,-309.4995,-333.9156)" + d="M 429.51763 446.44885 A 11.005301 11.005301 0 1 1 407.50703,446.44885 A 11.005301 11.005301 0 1 1 429.51763 446.44885 z" + sodipodi:ry="11.005301" + sodipodi:rx="11.005301" + sodipodi:cy="446.44885" + sodipodi:cx="418.51233" + id="path12271" + style="fill:url(#radialGradient13053);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + sodipodi:nodetypes="csssc" + id="path12273" + d="M 61.33077,52.065179 C 61.980407,54.489659 57.284671,51.286579 52.299305,51.286579 C 47.31394,51.286579 43.267841,55.264182 43.267841,52.065179 C 43.267841,47.079824 47.31394,43.033715 52.299305,43.033715 C 57.284671,43.033715 61.33077,47.079824 61.33077,52.065179 z " + style="opacity:0.89767444;fill:url(#linearGradient13055);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" /> + </g> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_add_lyrics.png" + sodipodi:nodetypes="ccccccccccccc" + style="fill:url(#linearGradient13057);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient13059);stroke-width:0.40403828;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + d="M 68.812258,1163.1445 L 64.989808,1159.3353 L 68.732838,1155.5242 L 65.595638,1152.3979 L 61.852608,1156.2091 L 58.050838,1152.4754 L 54.880618,1155.6567 L 58.682378,1159.3904 L 54.899318,1163.1316 L 58.036518,1166.2578 L 61.819568,1162.5166 L 65.642038,1166.3258 L 68.812258,1163.1445 z " + id="path12275" /> + </g> + <g + id="g12277" + transform="translate(-278.26811,341.49501)" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/22x22/actions/amarok_remove.png" + inkscape:export-xdpi="90.000092" + inkscape:export-ydpi="90.000092"> + <g + id="g12279" + transform="matrix(1.10355,0,0,1.10355,63.85265,222.07)" + inkscape:export-filename="/home/vadim/collection.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <path + sodipodi:type="arc" + style="fill:url(#linearGradient12451);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path12281" + sodipodi:cx="418.94757" + sodipodi:cy="444.83228" + sodipodi:rx="9.8239412" + sodipodi:ry="9.8239412" + d="M 428.77151 444.83228 A 9.8239412 9.8239412 0 1 1 409.12363,444.83228 A 9.8239412 9.8239412 0 1 1 428.77151 444.83228 z" + transform="matrix(1.014646,0,0,1.014647,-372.8672,-399.1678)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:type="arc" + style="fill:url(#radialGradient12453);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path12283" + sodipodi:cx="418.51233" + sodipodi:cy="446.44885" + sodipodi:rx="11.005301" + sodipodi:ry="11.005301" + d="M 429.51763 446.44885 A 11.005301 11.005301 0 1 1 407.50703,446.44885 A 11.005301 11.005301 0 1 1 429.51763 446.44885 z" + transform="matrix(0.810701,0,0,0.810702,-287.072,-309.7571)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + style="opacity:0.89767444;fill:url(#linearGradient12455);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 60.125175,52.179833 C 60.694066,54.302963 56.581985,51.498009 52.216275,51.498009 C 47.850565,51.498009 44.307375,54.981216 44.307375,52.179833 C 44.307375,47.814133 47.850565,44.270933 52.216275,44.270933 C 56.581985,44.270933 60.125175,47.814133 60.125175,52.179833 z " + id="path12285" + sodipodi:nodetypes="csssc" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" /> + </g> + <path + id="path12287" + d="M 127.48343,282.91543 L 124.18685,279.63029 L 127.41494,276.34345 L 124.70933,273.64726 L 121.48124,276.93413 L 118.20249,273.71409 L 115.46841,276.45772 L 118.74715,279.67776 L 115.48454,282.90428 L 118.19014,285.60046 L 121.45275,282.37394 L 124.74935,285.65907 L 127.48343,282.91543 z " + style="fill:url(#linearGradient12457);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient12459);stroke-width:0.60605842;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + sodipodi:nodetypes="ccccccccccccc" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_add_lyrics.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + </g> + <g + id="g12461" + transform="matrix(1.500002,0,0,1.500002,-301.6203,37.80167)" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/48x48/amarok_info.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <g + transform="matrix(1.605163,0,0,1.605163,80.416,-22.0111)" + id="g12463"> + <path + sodipodi:type="arc" + style="fill:url(#linearGradient12476);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path12465" + sodipodi:cx="418.94757" + sodipodi:cy="444.83228" + sodipodi:rx="9.8239412" + sodipodi:ry="9.8239412" + d="M 428.77151 444.83228 A 9.8239412 9.8239412 0 1 1 409.12363,444.83228 A 9.8239412 9.8239412 0 1 1 428.77151 444.83228 z" + transform="matrix(1.014646,0,0,1.014647,-372.754,-399.2811)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:type="arc" + style="fill:url(#radialGradient12478);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path12467" + sodipodi:cx="418.51233" + sodipodi:cy="446.44885" + sodipodi:rx="11.005301" + sodipodi:ry="11.005301" + d="M 429.51763 446.44885 A 11.005301 11.005301 0 1 1 407.50703,446.44885 A 11.005301 11.005301 0 1 1 429.51763 446.44885 z" + transform="matrix(0.86456,0,0,0.864561,-309.4995,-333.9156)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + style="opacity:0.89767444;fill:url(#linearGradient12480);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 61.33077,52.065179 C 61.980407,54.489659 57.284671,51.286579 52.299305,51.286579 C 47.31394,51.286579 43.267841,55.264182 43.267841,52.065179 C 43.267841,47.079824 47.31394,43.033715 52.299305,43.033715 C 57.284671,43.033715 61.33077,47.079824 61.33077,52.065179 z " + id="path12469" + sodipodi:nodetypes="csssc" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" /> + </g> + <g + transform="matrix(0.342068,0,0,0.32758,49.52561,37.1377)" + id="g12471" + inkscape:export-filename="/home/vadim/collection.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <path + id="path12473" + transform="matrix(0.871656,0,0,0.871656,42.22375,6.017503)" + d="M 336.875,39.84375 C 335.1305,39.847373 333.35067,40.415792 331.84375,41.5625 C 328.2044,44.331905 327.44874,49.497742 330.15625,53.09375 C 332.86376,56.689757 338.01691,57.363154 341.65625,54.59375 C 345.2956,51.824345 346.05125,46.658505 343.34375,43.0625 C 341.75732,40.955465 339.34363,39.838624 336.875,39.84375 z M 345.0625,67.3125 L 321.75,71.25 L 321.9375,73.125 L 329.84375,75 L 330.40625,118.1875 L 344.875,118.21875 L 345.0625,67.3125 z " + style="fill:url(#linearGradient12482);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient12484);stroke-width:1.33333123;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + </g> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/64x64/actions/amarok_info.png" + transform="matrix(2.000002,0,0,2.000002,-307.827,-0.9804944)" + id="g12486"> + <g + id="g12488" + transform="matrix(1.605163,0,0,1.605163,80.416,-22.0111)"> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + transform="matrix(1.014646,0,0,1.014647,-372.754,-399.2811)" + d="M 428.77151 444.83228 A 9.8239412 9.8239412 0 1 1 409.12363,444.83228 A 9.8239412 9.8239412 0 1 1 428.77151 444.83228 z" + sodipodi:ry="9.8239412" + sodipodi:rx="9.8239412" + sodipodi:cy="444.83228" + sodipodi:cx="418.94757" + id="path12490" + style="fill:url(#linearGradient12500);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + transform="matrix(0.86456,0,0,0.864561,-309.4995,-333.9156)" + d="M 429.51763 446.44885 A 11.005301 11.005301 0 1 1 407.50703,446.44885 A 11.005301 11.005301 0 1 1 429.51763 446.44885 z" + sodipodi:ry="11.005301" + sodipodi:rx="11.005301" + sodipodi:cy="446.44885" + sodipodi:cx="418.51233" + id="path12492" + style="fill:url(#radialGradient12502);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + sodipodi:nodetypes="csssc" + id="path12494" + d="M 61.33077,52.065179 C 61.980407,54.489659 57.284671,51.286579 52.299305,51.286579 C 47.31394,51.286579 43.267841,55.264182 43.267841,52.065179 C 43.267841,47.079824 47.31394,43.033715 52.299305,43.033715 C 57.284671,43.033715 61.33077,47.079824 61.33077,52.065179 z " + style="opacity:0.89767444;fill:url(#linearGradient12504);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" /> + </g> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/vadim/collection.png" + id="g12496" + transform="matrix(0.342068,0,0,0.32758,49.52561,37.1377)"> + <path + style="fill:url(#linearGradient12506);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient12508);stroke-width:0.99999851;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 336.875,39.84375 C 335.1305,39.847373 333.35067,40.415792 331.84375,41.5625 C 328.2044,44.331905 327.44874,49.497742 330.15625,53.09375 C 332.86376,56.689757 338.01691,57.363154 341.65625,54.59375 C 345.2956,51.824345 346.05125,46.658505 343.34375,43.0625 C 341.75732,40.955465 339.34363,39.838624 336.875,39.84375 z M 345.0625,67.3125 L 321.75,71.25 L 321.9375,73.125 L 329.84375,75 L 330.40625,118.1875 L 344.875,118.21875 L 345.0625,67.3125 z " + transform="matrix(0.871656,0,0,0.871656,42.22375,6.017503)" + id="path12498" /> + </g> + </g> + <g + id="g12510" + transform="matrix(0.727273,0,0,0.727273,-198.18275,-163.90283)" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/16x16/actions/amarok_info.png" + inkscape:export-xdpi="90.000092" + inkscape:export-ydpi="90.000092"> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/vadim/collection.png" + transform="matrix(1.10355,0,0,1.10355,-32.622,368.737)" + id="g12512"> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + transform="matrix(1.014646,0,0,1.014647,-372.8672,-399.1678)" + d="M 428.77151 444.83228 A 9.8239412 9.8239412 0 1 1 409.12363,444.83228 A 9.8239412 9.8239412 0 1 1 428.77151 444.83228 z" + sodipodi:ry="9.8239412" + sodipodi:rx="9.8239412" + sodipodi:cy="444.83228" + sodipodi:cx="418.94757" + id="path12514" + style="fill:url(#linearGradient12524);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + transform="matrix(0.810701,0,0,0.810702,-287.072,-309.7571)" + d="M 429.51763 446.44885 A 11.005301 11.005301 0 1 1 407.50703,446.44885 A 11.005301 11.005301 0 1 1 429.51763 446.44885 z" + sodipodi:ry="11.005301" + sodipodi:rx="11.005301" + sodipodi:cy="446.44885" + sodipodi:cx="418.51233" + id="path12516" + style="fill:url(#radialGradient12526);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + sodipodi:nodetypes="csssc" + id="path12518" + d="M 60.125175,52.179833 C 60.694066,54.302963 56.581985,51.498009 52.216275,51.498009 C 47.850565,51.498009 44.307375,54.981216 44.307375,52.179833 C 44.307375,47.814133 47.850565,44.270933 52.216275,44.270933 C 56.581985,44.270933 60.125175,47.814133 60.125175,52.179833 z " + style="opacity:0.89767444;fill:url(#linearGradient12528);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" /> + </g> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/vadim/collection.png" + id="g12520" + transform="matrix(0.235172,0,0,0.225211,-53.85915,409.4017)"> + <path + style="fill:url(#linearGradient12530);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient12532);stroke-width:2.74999666;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 336.875,39.84375 C 335.1305,39.847373 333.35067,40.415792 331.84375,41.5625 C 328.2044,44.331905 327.44874,49.497742 330.15625,53.09375 C 332.86376,56.689757 338.01691,57.363154 341.65625,54.59375 C 345.2956,51.824345 346.05125,46.658505 343.34375,43.0625 C 341.75732,40.955465 339.34363,39.838624 336.875,39.84375 z M 345.0625,67.3125 L 321.75,71.25 L 321.9375,73.125 L 329.84375,75 L 330.40625,118.1875 L 344.875,118.21875 L 345.0625,67.3125 z " + transform="matrix(0.871656,0,0,0.871656,42.22375,6.017503)" + id="path12522" /> + </g> + </g> + <g + transform="matrix(0.727273,0,0,0.727273,-276.51422,163.85563)" + id="g12534" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/16x16/actions/amarok_mostplayed.png" + inkscape:export-xdpi="90.000092" + inkscape:export-ydpi="90.000092"> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/vadim/collection.png" + transform="matrix(1.10355,0,0,1.10355,66.4319,44.86339)" + id="g12536"> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + transform="matrix(1.014646,0,0,1.014647,-372.8672,-399.1678)" + d="M 428.77151 444.83228 A 9.8239412 9.8239412 0 1 1 409.12363,444.83228 A 9.8239412 9.8239412 0 1 1 428.77151 444.83228 z" + sodipodi:ry="9.8239412" + sodipodi:rx="9.8239412" + sodipodi:cy="444.83228" + sodipodi:cx="418.94757" + id="path12538" + style="fill:url(#linearGradient12546);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + transform="matrix(0.810701,0,0,0.810702,-287.072,-309.7571)" + d="M 429.51763 446.44885 A 11.005301 11.005301 0 1 1 407.50703,446.44885 A 11.005301 11.005301 0 1 1 429.51763 446.44885 z" + sodipodi:ry="11.005301" + sodipodi:rx="11.005301" + sodipodi:cy="446.44885" + sodipodi:cx="418.51233" + id="path12540" + style="fill:url(#radialGradient12548);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + sodipodi:nodetypes="csssc" + id="path12542" + d="M 60.125175,52.179833 C 60.694066,54.302963 56.581985,51.498009 52.216275,51.498009 C 47.850565,51.498009 44.307375,54.981216 44.307375,52.179833 C 44.307375,47.814133 47.850565,44.270933 52.216275,44.270933 C 56.581985,44.270933 60.125175,47.814133 60.125175,52.179833 z " + style="opacity:0.89767444;fill:url(#linearGradient12550);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" /> + </g> + <path + transform="matrix(0.364333,0,0,0.364333,27.57707,79.10146)" + sodipodi:type="star" + style="fill:url(#linearGradient12552);fill-opacity:1;stroke:url(#linearGradient12554);stroke-width:2.19647765;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path12544" + sodipodi:sides="5" + sodipodi:cx="264.81149" + sodipodi:cy="65.241112" + sodipodi:r1="15.925471" + sodipodi:r2="7.6442261" + sodipodi:arg1="-0.31494821" + sodipodi:arg2="0.2941925" + inkscape:flatsided="false" + inkscape:rounded="0" + inkscape:randomized="0" + d="M 279.95363,60.307923 L 272.1273,67.457686 L 274.18241,78.1177 L 264.96411,72.883814 L 255.4609,78.13247 L 257.59001,67.747987 L 249.66159,60.331821 L 260.19575,59.147744 L 264.79893,49.315645 L 269.18029,58.968328 L 279.95363,60.307923 z " /> + </g> + <g + transform="matrix(0.727273,0,0,0.727273,-277.33347,221.43031)" + id="g12556" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/16x16/actions/amarok_refresh.png" + inkscape:export-xdpi="90.000092" + inkscape:export-ydpi="90.000092"> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/vadim/collection.png" + transform="matrix(1.10355,0,0,1.10355,66.80399,79.52888)" + id="g12558"> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + transform="matrix(1.014646,0,0,1.014647,-372.8672,-399.1678)" + d="M 428.77151 444.83228 A 9.8239412 9.8239412 0 1 1 409.12363,444.83228 A 9.8239412 9.8239412 0 1 1 428.77151 444.83228 z" + sodipodi:ry="9.8239412" + sodipodi:rx="9.8239412" + sodipodi:cy="444.83228" + sodipodi:cx="418.94757" + id="path12560" + style="fill:url(#linearGradient12568);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + transform="matrix(0.810701,0,0,0.810702,-287.072,-309.7571)" + d="M 429.51763 446.44885 A 11.005301 11.005301 0 1 1 407.50703,446.44885 A 11.005301 11.005301 0 1 1 429.51763 446.44885 z" + sodipodi:ry="11.005301" + sodipodi:rx="11.005301" + sodipodi:cy="446.44885" + sodipodi:cx="418.51233" + id="path12562" + style="fill:url(#radialGradient12570);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + sodipodi:nodetypes="csssc" + id="path12564" + d="M 60.125175,52.179833 C 60.694066,54.302963 56.581985,51.498009 52.216275,51.498009 C 47.850565,51.498009 44.307375,54.981216 44.307375,52.179833 C 44.307375,47.814133 47.850565,44.270933 52.216275,44.270933 C 56.581985,44.270933 60.125175,47.814133 60.125175,52.179833 z " + style="opacity:0.89767444;fill:url(#linearGradient12572);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" /> + </g> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/vadim/collection.png" + id="path12566" + d="M 126.39311,129.34365 L 126.06207,130.78175 C 123.56966,130.61343 121.02433,131.81233 119.50111,134.14906 C 118.6109,135.51473 118.20855,137.06061 118.2448,138.56917 C 118.39412,137.96541 118.64316,137.38394 118.9937,136.84618 C 120.32018,134.81125 122.77308,133.95022 125.23178,134.39056 L 124.72167,136.60468 L 130.51746,133.9537 L 126.39311,129.34365 z M 130.60971,135.655 C 130.46039,136.25876 130.21408,136.84023 129.86353,137.378 C 128.53705,139.41292 126.08415,140.27395 123.62546,139.83361 L 124.13557,137.61949 L 118.33978,140.27318 L 122.46141,144.88051 L 122.79244,143.44241 C 125.28487,143.61074 127.83019,142.41184 129.35341,140.0751 C 130.24362,138.70943 130.64598,137.16356 130.60971,135.655 z " + style="fill:url(#linearGradient12574);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient12576);stroke-width:0.6874994;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + <g + transform="matrix(0.727273,0,0,0.727273,-278.18026,274.85761)" + id="g12595" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/16x16/actions/amarok_rescan.png" + inkscape:export-xdpi="90.000092" + inkscape:export-ydpi="90.000092"> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/vadim/collection.png" + transform="matrix(1.10355,0,0,1.10355,67.34767,116.0663)" + id="g12597"> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + transform="matrix(1.014646,0,0,1.014647,-372.8672,-399.1678)" + d="M 428.77151 444.83228 A 9.8239412 9.8239412 0 1 1 409.12363,444.83228 A 9.8239412 9.8239412 0 1 1 428.77151 444.83228 z" + sodipodi:ry="9.8239412" + sodipodi:rx="9.8239412" + sodipodi:cy="444.83228" + sodipodi:cx="418.94757" + id="path12599" + style="fill:url(#linearGradient12607);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + transform="matrix(0.810701,0,0,0.810702,-287.072,-309.7571)" + d="M 429.51763 446.44885 A 11.005301 11.005301 0 1 1 407.50703,446.44885 A 11.005301 11.005301 0 1 1 429.51763 446.44885 z" + sodipodi:ry="11.005301" + sodipodi:rx="11.005301" + sodipodi:cy="446.44885" + sodipodi:cx="418.51233" + id="path12601" + style="fill:url(#radialGradient12609);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + sodipodi:nodetypes="csssc" + id="path12603" + d="M 60.125175,52.179833 C 60.694066,54.302963 56.581985,51.498009 52.216275,51.498009 C 47.850565,51.498009 44.307375,54.981216 44.307375,52.179833 C 44.307375,47.814133 47.850565,44.270933 52.216275,44.270933 C 56.581985,44.270933 60.125175,47.814133 60.125175,52.179833 z " + style="opacity:0.89767444;fill:url(#linearGradient12611);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" /> + </g> + <path + sodipodi:nodetypes="ccccscccscccscccccsccc" + style="fill:url(#linearGradient12613);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient12615);stroke-width:0.6311245;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 127.81678,165.30511 L 123.56183,167.90417 L 126.99711,171.35134 L 127.19051,169.93782 C 128.68686,170.70223 129.68978,172.20332 129.72321,173.96862 C 129.75774,175.72276 129.06265,177.81332 126.53864,178.37203 L 128.16639,179.17582 L 126.6651,180.6004 C 129.98947,180.46985 132.20988,177.81509 132.20988,173.8592 C 132.20988,170.7727 130.27095,168.17855 127.56812,167.12902 L 127.81678,165.30511 z M 122.65038,167.11473 C 120.68784,168.01084 117.732,170.26222 117.732,173.8592 C 117.732,176.70975 119.38616,179.14619 121.76591,180.33403 L 121.4804,181.99378 L 125.80904,179.73215 L 122.53033,175.98404 L 122.24482,177.61643 C 121.09556,176.78017 120.32917,175.47502 120.32917,173.96862 C 120.32917,172.76197 120.68363,171.25353 122.83223,169.81473 L 121.59563,169.52429 L 122.65038,167.11473 z " + id="path12605" /> + </g> + <g + transform="matrix(0.727273,0,0,0.727273,-278.5032,329.09401)" + id="g12617" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/16x16/actions/amarok_track.png" + inkscape:export-xdpi="90.000092" + inkscape:export-ydpi="90.000092"> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/vadim/collection.png" + transform="matrix(1.10355,0,0,1.10355,66.41684,146.726)" + id="g12619"> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + transform="matrix(1.014646,0,0,1.014647,-372.8672,-399.1678)" + d="M 428.77151 444.83228 A 9.8239412 9.8239412 0 1 1 409.12363,444.83228 A 9.8239412 9.8239412 0 1 1 428.77151 444.83228 z" + sodipodi:ry="9.8239412" + sodipodi:rx="9.8239412" + sodipodi:cy="444.83228" + sodipodi:cx="418.94757" + id="path12621" + style="fill:url(#linearGradient12631);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + transform="matrix(0.810701,0,0,0.810702,-287.072,-309.7571)" + d="M 429.51763 446.44885 A 11.005301 11.005301 0 1 1 407.50703,446.44885 A 11.005301 11.005301 0 1 1 429.51763 446.44885 z" + sodipodi:ry="11.005301" + sodipodi:rx="11.005301" + sodipodi:cy="446.44885" + sodipodi:cx="418.51233" + id="path12623" + style="fill:url(#radialGradient12633);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + sodipodi:nodetypes="csssc" + id="path12625" + d="M 60.125175,52.179833 C 60.694066,54.302963 56.581985,51.498009 52.216275,51.498009 C 47.850565,51.498009 44.307375,54.981216 44.307375,52.179833 C 44.307375,47.814133 47.850565,44.270933 52.216275,44.270933 C 56.581985,44.270933 60.125175,47.814133 60.125175,52.179833 z " + style="opacity:0.89767444;fill:url(#linearGradient12635);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" /> + </g> + <g + transform="translate(-0.461535,-1.892739)" + id="g12627"> + <path + inkscape:export-ydpi="3.3299999" + inkscape:export-xdpi="3.3299999" + inkscape:export-filename="/home/kant/Desktop/playlist16.png" + id="path12629" + d="M 122.34066,197.85001 C 121.71438,197.93419 121.3983,198.76757 121.69161,199.33457 C 122.60203,202.46965 123.51246,205.60474 124.42289,208.73981 C 122.7779,208.42998 120.89611,209.45563 120.42535,211.22783 C 120.08046,212.53742 120.77939,214.00974 121.9193,214.57747 C 123.61377,215.5465 126.0278,214.82943 126.86624,212.93805 C 127.26921,212.06093 127.14337,210.99875 126.705,210.16589 C 125.98533,207.68545 125.26565,205.20502 124.54597,202.72459 C 125.87184,202.51332 127.25266,201.8714 127.96073,200.59423 C 128.22746,200.0756 128.51323,199.36229 128.09133,198.84377 C 127.74894,198.39439 127.1248,198.50212 126.7099,198.76351 C 125.63163,199.2174 124.44719,198.80242 123.45502,198.29299 C 123.10812,198.0916 122.75628,197.8151 122.34066,197.85001 z " + style="color:#000000;fill:url(#linearGradient12637);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient12639);stroke-width:0.6874994;stroke-linecap:round;stroke-linejoin:round;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0.75376886;visibility:visible;display:block;overflow:visible" /> + </g> + </g> + <g + transform="matrix(0.727273,0,0,0.727273,-280.45198,374.71171)" + id="g12641" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/16x16/actions/amarok_clock.png" + inkscape:export-xdpi="90.000092" + inkscape:export-ydpi="90.000092"> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/vadim/collection.png" + transform="matrix(1.10355,0,0,1.10355,64.9714,186.3919)" + id="g12643"> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + transform="matrix(1.014646,0,0,1.014647,-372.8672,-399.1678)" + d="M 428.77151 444.83228 A 9.8239412 9.8239412 0 1 1 409.12363,444.83228 A 9.8239412 9.8239412 0 1 1 428.77151 444.83228 z" + sodipodi:ry="9.8239412" + sodipodi:rx="9.8239412" + sodipodi:cy="444.83228" + sodipodi:cx="418.94757" + id="path12645" + style="fill:url(#linearGradient12681);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + transform="matrix(0.810701,0,0,0.810702,-287.072,-309.7571)" + d="M 429.51763 446.44885 A 11.005301 11.005301 0 1 1 407.50703,446.44885 A 11.005301 11.005301 0 1 1 429.51763 446.44885 z" + sodipodi:ry="11.005301" + sodipodi:rx="11.005301" + sodipodi:cy="446.44885" + sodipodi:cx="418.51233" + id="path12647" + style="fill:url(#radialGradient12683);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + sodipodi:nodetypes="csssc" + id="path12649" + d="M 60.125175,52.179833 C 60.694066,54.302963 56.581985,51.498009 52.216275,51.498009 C 47.850565,51.498009 44.307375,54.981216 44.307375,52.179833 C 44.307375,47.814133 47.850565,44.270933 52.216275,44.270933 C 56.581985,44.270933 60.125175,47.814133 60.125175,52.179833 z " + style="opacity:0.89767444;fill:url(#linearGradient12685);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" /> + </g> + <rect + style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.25;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect12651" + width="0.79549539" + height="1.9445429" + x="122.07199" + y="234.4519" /> + <rect + y="251.90858" + x="122.07199" + height="1.9445429" + width="0.79549539" + id="rect12653" + style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.25;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.25;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect12655" + width="0.79549491" + height="1.9445442" + x="-244.37267" + y="130.02696" + transform="matrix(0,-1,1,0,0,0)" /> + <rect + transform="matrix(0,-1,1,0,0,0)" + y="112.96799" + x="-244.37267" + height="1.9445442" + width="0.79549491" + id="rect12657" + style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.25;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + y="124.57272" + x="237.46754" + height="1.9445441" + width="0.79549432" + id="rect12659" + style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.25;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + transform="matrix(0.829849,0.557988,-0.557988,0.829849,0,0)" /> + <rect + style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.25;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect12661" + width="0.79549432" + height="1.9445441" + x="237.46754" + y="142.02942" + transform="matrix(0.829849,0.557988,-0.557988,0.829849,0,0)" /> + <rect + transform="matrix(0.557988,-0.829849,0.829849,0.557988,0,0)" + y="245.42249" + x="-134.49347" + height="1.9445411" + width="0.79549539" + id="rect12663" + style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.25;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.25;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect12665" + width="0.79549539" + height="1.9445411" + x="-134.49347" + y="228.36357" + transform="matrix(0.557988,-0.829849,0.829849,0.557988,0,0)" /> + <rect + transform="matrix(0.484003,0.875066,-0.875066,0.484003,0,0)" + style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.25;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect12667" + width="0.79549128" + height="1.944538" + x="272.49246" + y="1.2698265" /> + <rect + transform="matrix(0.484003,0.875066,-0.875066,0.484003,0,0)" + y="18.726486" + x="272.49246" + height="1.944538" + width="0.79549128" + id="rect12669" + style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.25;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.25;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect12671" + width="0.79549277" + height="1.9445341" + x="-11.190576" + y="280.44739" + transform="matrix(0.875066,-0.484003,0.484003,0.875066,0,0)" /> + <rect + transform="matrix(0.875066,-0.484003,0.484003,0.875066,0,0)" + y="263.38855" + x="-11.190576" + height="1.9445341" + width="0.79549277" + id="rect12673" + style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.25;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + style="fill:url(#linearGradient12687);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="M 122.04164,244.32941 L 122.31569,235.9486 L 122.58973,235.9486 L 122.86378,244.32941 L 122.04164,244.32941 z " + id="path12675" + sodipodi:nodetypes="ccccc" /> + <path + sodipodi:nodetypes="ccccc" + id="path12677" + d="M 123.21896,243.93206 L 119.53615,248.80389 L 119.28371,248.63219 L 122.46164,243.41697 L 123.21896,243.93206 z " + style="fill:url(#linearGradient12689);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + transform="matrix(1.041456,0,0,1.041456,-5.10099,-10.05734)" + d="M 123.57283 243.92044 A 0.96019417 0.96019417 0 1 1 121.65244,243.92044 A 0.96019417 0.96019417 0 1 1 123.57283 243.92044 z" + sodipodi:ry="0.96019417" + sodipodi:rx="0.96019417" + sodipodi:cy="243.92044" + sodipodi:cx="122.61263" + id="path12679" + style="opacity:1;fill:url(#radialGradient12691);fill-opacity:1;stroke:url(#linearGradient12693);stroke-width:0.6874994;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + </g> + <g + transform="matrix(0.727273,0,0,0.727273,-272.13838,420.76401)" + id="g12695" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/16x16/actions/amarok_remove.png" + inkscape:export-xdpi="90.000092" + inkscape:export-ydpi="90.000092"> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/vadim/collection.png" + transform="matrix(1.10355,0,0,1.10355,63.85265,222.07)" + id="g12697"> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + transform="matrix(1.014646,0,0,1.014647,-372.8672,-399.1678)" + d="M 428.77151 444.83228 A 9.8239412 9.8239412 0 1 1 409.12363,444.83228 A 9.8239412 9.8239412 0 1 1 428.77151 444.83228 z" + sodipodi:ry="9.8239412" + sodipodi:rx="9.8239412" + sodipodi:cy="444.83228" + sodipodi:cx="418.94757" + id="path12699" + style="fill:url(#linearGradient12707);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + transform="matrix(0.810701,0,0,0.810702,-287.072,-309.7571)" + d="M 429.51763 446.44885 A 11.005301 11.005301 0 1 1 407.50703,446.44885 A 11.005301 11.005301 0 1 1 429.51763 446.44885 z" + sodipodi:ry="11.005301" + sodipodi:rx="11.005301" + sodipodi:cy="446.44885" + sodipodi:cx="418.51233" + id="path12701" + style="fill:url(#radialGradient12709);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + sodipodi:nodetypes="csssc" + id="path12703" + d="M 60.125175,52.179833 C 60.694066,54.302963 56.581985,51.498009 52.216275,51.498009 C 47.850565,51.498009 44.307375,54.981216 44.307375,52.179833 C 44.307375,47.814133 47.850565,44.270933 52.216275,44.270933 C 56.581985,44.270933 60.125175,47.814133 60.125175,52.179833 z " + style="opacity:0.89767444;fill:url(#linearGradient12711);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" /> + </g> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_add_lyrics.png" + sodipodi:nodetypes="ccccccccccccc" + style="fill:url(#linearGradient12713);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient12715);stroke-width:0.83332962;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + d="M 127.48343,282.91543 L 124.18685,279.63029 L 127.41494,276.34345 L 124.70933,273.64726 L 121.48124,276.93413 L 118.20249,273.71409 L 115.46841,276.45772 L 118.74715,279.67776 L 115.48454,282.90428 L 118.19014,285.60046 L 121.45275,282.37394 L 124.74935,285.65907 L 127.48343,282.91543 z " + id="path12705" /> + </g> + <g + transform="matrix(0.727273,0,0,0.727273,-273.00198,478.79201)" + id="g12717" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/16x16/actions/amarok_love.png" + inkscape:export-xdpi="90.000092" + inkscape:export-ydpi="90.000092"> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/vadim/collection.png" + transform="matrix(1.10355,0,0,1.10355,64.35265,257.07)" + id="g12719"> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + transform="matrix(1.014646,0,0,1.014647,-372.8672,-399.1678)" + d="M 428.77151 444.83228 A 9.8239412 9.8239412 0 1 1 409.12363,444.83228 A 9.8239412 9.8239412 0 1 1 428.77151 444.83228 z" + sodipodi:ry="9.8239412" + sodipodi:rx="9.8239412" + sodipodi:cy="444.83228" + sodipodi:cx="418.94757" + id="path12721" + style="fill:url(#linearGradient12729);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + transform="matrix(0.810701,0,0,0.810702,-287.072,-309.7571)" + d="M 429.51763 446.44885 A 11.005301 11.005301 0 1 1 407.50703,446.44885 A 11.005301 11.005301 0 1 1 429.51763 446.44885 z" + sodipodi:ry="11.005301" + sodipodi:rx="11.005301" + sodipodi:cy="446.44885" + sodipodi:cx="418.51233" + id="path12723" + style="fill:url(#radialGradient12731);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + sodipodi:nodetypes="csssc" + id="path12725" + d="M 60.125175,52.179833 C 60.694066,54.302963 56.581985,51.498009 52.216275,51.498009 C 47.850565,51.498009 44.307375,54.981216 44.307375,52.179833 C 44.307375,47.814133 47.850565,44.270933 52.216275,44.270933 C 56.581985,44.270933 60.125175,47.814133 60.125175,52.179833 z " + style="opacity:0.89767444;fill:url(#linearGradient12733);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" /> + </g> + <path + style="fill:url(#linearGradient12735);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient12737);stroke-width:0.6874994;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 122.0294,321.87112 C 122.0294,321.87112 112.66429,316.51282 115.52508,311.74484 C 116.44668,310.20884 117.49554,309.63933 118.47668,309.5652 C 120.34977,309.42366 121.9732,311.9387 121.9732,311.9387 C 121.9732,311.9387 123.33432,309.6846 125.00427,309.5652 C 126.11035,309.48612 127.3531,309.96446 128.42132,311.74484 C 131.28211,316.51282 122.0294,321.87112 122.0294,321.87112 z " + id="path12727" + sodipodi:nodetypes="cscccsc" /> + </g> + <g + transform="matrix(1.500002,0,0,1.500002,-305.52648,68.149634)" + id="g12739" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/48x48/actions/amarok_mostplayed.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <g + transform="matrix(1.605163,0,0,1.605163,80.51891,19.23282)" + id="g12741"> + <path + sodipodi:type="arc" + style="fill:url(#linearGradient12752);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path12744" + sodipodi:cx="418.94757" + sodipodi:cy="444.83228" + sodipodi:rx="9.8239412" + sodipodi:ry="9.8239412" + d="M 428.77151 444.83228 A 9.8239412 9.8239412 0 1 1 409.12363,444.83228 A 9.8239412 9.8239412 0 1 1 428.77151 444.83228 z" + transform="matrix(1.014646,0,0,1.014647,-372.754,-399.2811)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:type="arc" + style="fill:url(#radialGradient12754);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path12746" + sodipodi:cx="418.51233" + sodipodi:cy="446.44885" + sodipodi:rx="11.005301" + sodipodi:ry="11.005301" + d="M 429.51763 446.44885 A 11.005301 11.005301 0 1 1 407.50703,446.44885 A 11.005301 11.005301 0 1 1 429.51763 446.44885 z" + transform="matrix(0.86456,0,0,0.864561,-309.4995,-333.9156)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + style="opacity:0.89767444;fill:url(#linearGradient12756);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 61.33077,52.065179 C 61.980407,54.489659 57.284671,51.286579 52.299305,51.286579 C 47.31394,51.286579 43.267841,55.264182 43.267841,52.065179 C 43.267841,47.079824 47.31394,43.033715 52.299305,43.033715 C 57.284671,43.033715 61.33077,47.079824 61.33077,52.065179 z " + id="path12748" + sodipodi:nodetypes="csssc" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" /> + </g> + <path + transform="matrix(0.529939,0,0,0.529939,24.18437,69.03839)" + sodipodi:type="star" + style="fill:url(#linearGradient12758);fill-opacity:1;stroke:url(#linearGradient12760);stroke-width:1.2580049;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path12750" + sodipodi:sides="5" + sodipodi:cx="264.81149" + sodipodi:cy="65.241112" + sodipodi:r1="15.925471" + sodipodi:r2="7.6442261" + sodipodi:arg1="-0.31494821" + sodipodi:arg2="0.2941925" + inkscape:flatsided="false" + inkscape:rounded="0" + inkscape:randomized="0" + d="M 279.95363,60.307923 L 272.1273,67.457686 L 274.18241,78.1177 L 264.96411,72.883814 L 255.4609,78.13247 L 257.59001,67.747987 L 249.66159,60.331821 L 260.19575,59.147744 L 264.79893,49.315645 L 269.18029,58.968328 L 279.95363,60.307923 z " /> + </g> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/64x64/actions/amarok_mostplayed.png" + id="g12762" + transform="matrix(2.000002,0,0,2.000002,-305.78468,8.7455093)"> + <g + id="g12764" + transform="matrix(1.605163,0,0,1.605163,80.51891,19.23282)"> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + transform="matrix(1.014646,0,0,1.014647,-372.754,-399.2811)" + d="M 428.77151 444.83228 A 9.8239412 9.8239412 0 1 1 409.12363,444.83228 A 9.8239412 9.8239412 0 1 1 428.77151 444.83228 z" + sodipodi:ry="9.8239412" + sodipodi:rx="9.8239412" + sodipodi:cy="444.83228" + sodipodi:cx="418.94757" + id="path12766" + style="fill:url(#linearGradient12774);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + transform="matrix(0.86456,0,0,0.864561,-309.4995,-333.9156)" + d="M 429.51763 446.44885 A 11.005301 11.005301 0 1 1 407.50703,446.44885 A 11.005301 11.005301 0 1 1 429.51763 446.44885 z" + sodipodi:ry="11.005301" + sodipodi:rx="11.005301" + sodipodi:cy="446.44885" + sodipodi:cx="418.51233" + id="path12768" + style="fill:url(#radialGradient12776);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + sodipodi:nodetypes="csssc" + id="path12770" + d="M 61.33077,52.065179 C 61.980407,54.489659 57.284671,51.286579 52.299305,51.286579 C 47.31394,51.286579 43.267841,55.264182 43.267841,52.065179 C 43.267841,47.079824 47.31394,43.033715 52.299305,43.033715 C 57.284671,43.033715 61.33077,47.079824 61.33077,52.065179 z " + style="opacity:0.89767444;fill:url(#linearGradient12778);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" /> + </g> + <path + d="M 279.95363,60.307923 L 272.1273,67.457686 L 274.18241,78.1177 L 264.96411,72.883814 L 255.4609,78.13247 L 257.59001,67.747987 L 249.66159,60.331821 L 260.19575,59.147744 L 264.79893,49.315645 L 269.18029,58.968328 L 279.95363,60.307923 z " + inkscape:randomized="0" + inkscape:rounded="0" + inkscape:flatsided="false" + sodipodi:arg2="0.2941925" + sodipodi:arg1="-0.31494821" + sodipodi:r2="7.6442261" + sodipodi:r1="15.925471" + sodipodi:cy="65.241112" + sodipodi:cx="264.81149" + sodipodi:sides="5" + id="path12772" + style="fill:url(#linearGradient12780);fill-opacity:1;stroke:url(#linearGradient12782);stroke-width:0.94350374;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="star" + transform="matrix(0.529939,0,0,0.529939,24.18437,69.03839)" /> + </g> + <g + transform="matrix(2.000002,0,0,2.000002,-109.94753,-1496.8503)" + id="g12784" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/64x64/actions/amarok_refresh.png" + inkscape:export-xdpi="90.000092" + inkscape:export-ydpi="90.000092"> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Toolbar-and-menu-icons/amarok_refresh.png" + transform="matrix(1.605163,0,0,1.605163,-17.66973,813.4229)" + id="g12786"> + <path + sodipodi:type="arc" + style="fill:url(#linearGradient12796);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path12788" + sodipodi:cx="418.94757" + sodipodi:cy="444.83228" + sodipodi:rx="9.8239412" + sodipodi:ry="9.8239412" + d="M 428.77151 444.83228 A 9.8239412 9.8239412 0 1 1 409.12363,444.83228 A 9.8239412 9.8239412 0 1 1 428.77151 444.83228 z" + transform="matrix(1.014646,0,0,1.014647,-372.754,-399.2811)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:type="arc" + style="fill:url(#radialGradient12798);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path12790" + sodipodi:cx="418.51233" + sodipodi:cy="446.44885" + sodipodi:rx="11.005301" + sodipodi:ry="11.005301" + d="M 429.51763 446.44885 A 11.005301 11.005301 0 1 1 407.50703,446.44885 A 11.005301 11.005301 0 1 1 429.51763 446.44885 z" + transform="matrix(0.86456,0,0,0.864561,-309.4995,-333.9156)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + style="opacity:0.89767444;fill:url(#linearGradient12800);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 61.33077,52.065179 C 61.980407,54.489659 57.284671,51.286579 52.299305,51.286579 C 47.31394,51.286579 43.267841,55.264182 43.267841,52.065179 C 43.267841,47.079824 47.31394,43.033715 52.299305,43.033715 C 57.284671,43.033715 61.33077,47.079824 61.33077,52.065179 z " + id="path12792" + sodipodi:nodetypes="csssc" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" /> + </g> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Toolbar-and-menu-icons/amarok_refresh.png" + id="path12794" + d="M 69.005356,885.88068 L 68.523846,887.97246 C 64.898516,887.72763 61.196216,889.47148 58.980616,892.87037 C 57.685766,894.8568 57.100516,897.10535 57.153256,899.29963 C 57.370456,898.42142 57.732686,897.57566 58.242566,896.79345 C 60.171986,893.83356 63.739846,892.58115 67.316146,893.22164 L 66.574156,896.44219 L 75.004416,892.5862 L 69.005356,885.88068 z M 75.138596,895.06082 C 74.921406,895.93903 74.563126,896.7848 74.053246,897.56702 C 72.123816,900.5269 68.555956,901.7793 64.979676,901.13881 L 65.721666,897.91827 L 57.291406,901.77819 L 63.286506,908.47977 L 63.768016,906.38799 C 67.393366,906.63282 71.095656,904.88897 73.311256,901.49008 C 74.606096,899.50365 75.191346,897.2551 75.138596,895.06082 z " + style="fill:url(#linearGradient12802);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient12804);stroke-width:0.36363631;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + <g + id="g12806" + transform="matrix(1.500002,0,0,1.500002,-157.78373,-1040.3511)" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/48x48/actions/amarok_refresh.png" + inkscape:export-xdpi="90.000092" + inkscape:export-ydpi="90.000092"> + <g + id="g12808" + transform="matrix(1.605163,0,0,1.605163,-17.66973,813.4229)" + inkscape:export-filename="/home/lando/Projects/Amarok/Toolbar-and-menu-icons/amarok_refresh.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + transform="matrix(1.014646,0,0,1.014647,-372.754,-399.2811)" + d="M 428.77151 444.83228 A 9.8239412 9.8239412 0 1 1 409.12363,444.83228 A 9.8239412 9.8239412 0 1 1 428.77151 444.83228 z" + sodipodi:ry="9.8239412" + sodipodi:rx="9.8239412" + sodipodi:cy="444.83228" + sodipodi:cx="418.94757" + id="path12810" + style="fill:url(#linearGradient12818);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + transform="matrix(0.86456,0,0,0.864561,-309.4995,-333.9156)" + d="M 429.51763 446.44885 A 11.005301 11.005301 0 1 1 407.50703,446.44885 A 11.005301 11.005301 0 1 1 429.51763 446.44885 z" + sodipodi:ry="11.005301" + sodipodi:rx="11.005301" + sodipodi:cy="446.44885" + sodipodi:cx="418.51233" + id="path12812" + style="fill:url(#radialGradient12820);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + sodipodi:nodetypes="csssc" + id="path12814" + d="M 61.33077,52.065179 C 61.980407,54.489659 57.284671,51.286579 52.299305,51.286579 C 47.31394,51.286579 43.267841,55.264182 43.267841,52.065179 C 43.267841,47.079824 47.31394,43.033715 52.299305,43.033715 C 57.284671,43.033715 61.33077,47.079824 61.33077,52.065179 z " + style="opacity:0.89767444;fill:url(#linearGradient12822);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" /> + </g> + <path + style="fill:url(#linearGradient12824);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient12826);stroke-width:0.48484817;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 69.005356,885.88068 L 68.523846,887.97246 C 64.898516,887.72763 61.196216,889.47148 58.980616,892.87037 C 57.685766,894.8568 57.100516,897.10535 57.153256,899.29963 C 57.370456,898.42142 57.732686,897.57566 58.242566,896.79345 C 60.171986,893.83356 63.739846,892.58115 67.316146,893.22164 L 66.574156,896.44219 L 75.004416,892.5862 L 69.005356,885.88068 z M 75.138596,895.06082 C 74.921406,895.93903 74.563126,896.7848 74.053246,897.56702 C 72.123816,900.5269 68.555956,901.7793 64.979676,901.13881 L 65.721666,897.91827 L 57.291406,901.77819 L 63.286506,908.47977 L 63.768016,906.38799 C 67.393366,906.63282 71.095656,904.88897 73.311256,901.49008 C 74.606096,899.50365 75.191346,897.2551 75.138596,895.06082 z " + id="path12816" + inkscape:export-filename="/home/lando/Projects/Amarok/Toolbar-and-menu-icons/amarok_refresh.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + </g> + <g + transform="matrix(1.500002,0,0,1.500002,-307.78348,127.74721)" + id="g12828" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/48x48/actions/amarok_rescan.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <g + transform="matrix(1.605163,0,0,1.605163,79.66329,88.02498)" + id="g12830"> + <path + sodipodi:type="arc" + style="fill:url(#linearGradient12840);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path12832" + sodipodi:cx="418.94757" + sodipodi:cy="444.83228" + sodipodi:rx="9.8239412" + sodipodi:ry="9.8239412" + d="M 428.77151 444.83228 A 9.8239412 9.8239412 0 1 1 409.12363,444.83228 A 9.8239412 9.8239412 0 1 1 428.77151 444.83228 z" + transform="matrix(1.014646,0,0,1.014647,-372.754,-399.2811)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:type="arc" + style="fill:url(#radialGradient12842);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path12834" + sodipodi:cx="418.51233" + sodipodi:cy="446.44885" + sodipodi:rx="11.005301" + sodipodi:ry="11.005301" + d="M 429.51763 446.44885 A 11.005301 11.005301 0 1 1 407.50703,446.44885 A 11.005301 11.005301 0 1 1 429.51763 446.44885 z" + transform="matrix(0.86456,0,0,0.864561,-309.4995,-333.9156)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + style="opacity:0.89767444;fill:url(#linearGradient12844);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 61.33077,52.065179 C 61.980407,54.489659 57.284671,51.286579 52.299305,51.286579 C 47.31394,51.286579 43.267841,55.264182 43.267841,52.065179 C 43.267841,47.079824 47.31394,43.033715 52.299305,43.033715 C 57.284671,43.033715 61.33077,47.079824 61.33077,52.065179 z " + id="path12836" + sodipodi:nodetypes="csssc" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" /> + </g> + <path + sodipodi:nodetypes="ccccscccscccscccccsccc" + style="fill:url(#linearGradient12846);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient12848);stroke-width:0.44509068;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 167.61829,159.64502 L 161.42927,163.42547 L 166.42604,168.43954 L 166.70735,166.3835 C 168.88386,167.49537 170.34265,169.67878 170.39128,172.2465 C 170.4415,174.79796 169.43047,177.83879 165.75917,178.65146 L 168.12681,179.8206 L 165.94312,181.89273 C 170.77857,181.70283 174.00825,177.84136 174.00825,172.08734 C 174.00825,167.59788 171.188,163.82457 167.2566,162.29797 L 167.61829,159.64502 z M 160.10353,162.27719 C 157.24892,163.58062 152.94951,166.85537 152.94951,172.08734 C 152.94951,176.23359 155.35556,179.77751 158.81702,181.50527 L 158.40174,183.91947 L 164.69793,180.62981 L 159.9289,175.17801 L 159.51362,177.5524 C 157.84196,176.33602 156.72721,174.43762 156.72721,172.2465 C 156.72721,170.49137 157.2428,168.29727 160.36802,166.20447 L 158.56934,165.78201 L 160.10353,162.27719 z " + id="path12838" /> + </g> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/64x64/actions/amarok_rescan.png" + id="g12850" + transform="matrix(2.000002,0,0,2.000002,-305.61378,33.947005)"> + <g + id="g12852" + transform="matrix(1.605163,0,0,1.605163,79.66329,88.02498)"> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + transform="matrix(1.014646,0,0,1.014647,-372.754,-399.2811)" + d="M 428.77151 444.83228 A 9.8239412 9.8239412 0 1 1 409.12363,444.83228 A 9.8239412 9.8239412 0 1 1 428.77151 444.83228 z" + sodipodi:ry="9.8239412" + sodipodi:rx="9.8239412" + sodipodi:cy="444.83228" + sodipodi:cx="418.94757" + id="path12854" + style="fill:url(#linearGradient12862);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + transform="matrix(0.86456,0,0,0.864561,-309.4995,-333.9156)" + d="M 429.51763 446.44885 A 11.005301 11.005301 0 1 1 407.50703,446.44885 A 11.005301 11.005301 0 1 1 429.51763 446.44885 z" + sodipodi:ry="11.005301" + sodipodi:rx="11.005301" + sodipodi:cy="446.44885" + sodipodi:cx="418.51233" + id="path12856" + style="fill:url(#radialGradient12864);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + sodipodi:nodetypes="csssc" + id="path12858" + d="M 61.33077,52.065179 C 61.980407,54.489659 57.284671,51.286579 52.299305,51.286579 C 47.31394,51.286579 43.267841,55.264182 43.267841,52.065179 C 43.267841,47.079824 47.31394,43.033715 52.299305,43.033715 C 57.284671,43.033715 61.33077,47.079824 61.33077,52.065179 z " + style="opacity:0.89767444;fill:url(#linearGradient12866);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" /> + </g> + <path + id="path12860" + d="M 167.61829,159.64502 L 161.42927,163.42547 L 166.42604,168.43954 L 166.70735,166.3835 C 168.88386,167.49537 170.34265,169.67878 170.39128,172.2465 C 170.4415,174.79796 169.43047,177.83879 165.75917,178.65146 L 168.12681,179.8206 L 165.94312,181.89273 C 170.77857,181.70283 174.00825,177.84136 174.00825,172.08734 C 174.00825,167.59788 171.188,163.82457 167.2566,162.29797 L 167.61829,159.64502 z M 160.10353,162.27719 C 157.24892,163.58062 152.94951,166.85537 152.94951,172.08734 C 152.94951,176.23359 155.35556,179.77751 158.81702,181.50527 L 158.40174,183.91947 L 164.69793,180.62981 L 159.9289,175.17801 L 159.51362,177.5524 C 157.84196,176.33602 156.72721,174.43762 156.72721,172.2465 C 156.72721,170.49137 157.2428,168.29727 160.36802,166.20447 L 158.56934,165.78201 L 160.10353,162.27719 z " + style="fill:url(#linearGradient12868);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient12870);stroke-width:0.33381805;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:nodetypes="ccccscccscccscccccsccc" /> + </g> + <g + id="g12880" + transform="matrix(2.000002,0,0,2.000002,-113.12842,-1596.114)" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/64x64/actions/amarok_track.png" + inkscape:export-xdpi="90.000092" + inkscape:export-ydpi="90.000092"> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_track.png" + transform="matrix(1.605163,0,0,1.605163,-18.07929,941.3218)" + id="g12882"> + <path + sodipodi:type="arc" + style="fill:url(#linearGradient12894);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path12884" + sodipodi:cx="418.94757" + sodipodi:cy="444.83228" + sodipodi:rx="9.8239412" + sodipodi:ry="9.8239412" + d="M 428.77151 444.83228 A 9.8239412 9.8239412 0 1 1 409.12363,444.83228 A 9.8239412 9.8239412 0 1 1 428.77151 444.83228 z" + transform="matrix(1.014646,0,0,1.014647,-372.754,-399.2811)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:type="arc" + style="fill:url(#radialGradient12896);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path12886" + sodipodi:cx="418.51233" + sodipodi:cy="446.44885" + sodipodi:rx="11.005301" + sodipodi:ry="11.005301" + d="M 429.51763 446.44885 A 11.005301 11.005301 0 1 1 407.50703,446.44885 A 11.005301 11.005301 0 1 1 429.51763 446.44885 z" + transform="matrix(0.86456,0,0,0.864561,-309.4995,-333.9156)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + style="opacity:0.89767444;fill:url(#linearGradient12898);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 61.33077,52.065179 C 61.980407,54.489659 57.284671,51.286579 52.299305,51.286579 C 47.31394,51.286579 43.267841,55.264182 43.267841,52.065179 C 43.267841,47.079824 47.31394,43.033715 52.299305,43.033715 C 57.284671,43.033715 61.33077,47.079824 61.33077,52.065179 z " + id="path12888" + sodipodi:nodetypes="csssc" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" /> + </g> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_track.png" + transform="translate(-97.43747,818.1603)" + id="g12890"> + <path + inkscape:export-ydpi="3.3299999" + inkscape:export-xdpi="3.3299999" + inkscape:export-filename="/home/kant/Desktop/playlist16.png" + id="path12892" + d="M 160.35798,194.32998 C 159.44703,194.45242 158.98728,195.66462 159.41391,196.48935 C 160.73817,201.04946 162.06244,205.60958 163.38671,210.16969 C 160.99398,209.71902 158.25682,211.21088 157.57207,213.78862 C 157.07041,215.69347 158.08704,217.83504 159.7451,218.66082 C 162.2098,220.07032 165.72114,219.02731 166.94069,216.27621 C 167.52683,215.00041 167.34378,213.45542 166.70616,212.24398 C 165.65935,208.63608 164.61254,205.02817 163.56574,201.42027 C 165.49428,201.11297 167.50276,200.17928 168.53268,198.32157 C 168.92066,197.5672 169.33633,196.52966 168.72264,195.77544 C 168.22462,195.12181 167.31678,195.2785 166.71328,195.65871 C 165.14487,196.31891 163.42205,195.7153 161.97889,194.97431 C 161.4743,194.68138 160.96253,194.2792 160.35798,194.32998 z " + style="color:#000000;fill:url(#linearGradient12900);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient12902);stroke-width:0.49999943;stroke-linecap:round;stroke-linejoin:round;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0.75376886;visibility:visible;display:block;overflow:visible" /> + </g> + </g> + <g + transform="matrix(1.500002,0,0,1.500002,-155.16938,-1075.6654)" + id="g12904" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/48x48/actions/amarok_track.png" + inkscape:export-xdpi="90.000092" + inkscape:export-ydpi="90.000092"> + <g + id="g12906" + transform="matrix(1.605163,0,0,1.605163,-18.07929,941.3218)" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_track.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + transform="matrix(1.014646,0,0,1.014647,-372.754,-399.2811)" + d="M 428.77151 444.83228 A 9.8239412 9.8239412 0 1 1 409.12363,444.83228 A 9.8239412 9.8239412 0 1 1 428.77151 444.83228 z" + sodipodi:ry="9.8239412" + sodipodi:rx="9.8239412" + sodipodi:cy="444.83228" + sodipodi:cx="418.94757" + id="path12908" + style="fill:url(#linearGradient12918);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + transform="matrix(0.86456,0,0,0.864561,-309.4995,-333.9156)" + d="M 429.51763 446.44885 A 11.005301 11.005301 0 1 1 407.50703,446.44885 A 11.005301 11.005301 0 1 1 429.51763 446.44885 z" + sodipodi:ry="11.005301" + sodipodi:rx="11.005301" + sodipodi:cy="446.44885" + sodipodi:cx="418.51233" + id="path12910" + style="fill:url(#radialGradient12920);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + sodipodi:nodetypes="csssc" + id="path12912" + d="M 61.33077,52.065179 C 61.980407,54.489659 57.284671,51.286579 52.299305,51.286579 C 47.31394,51.286579 43.267841,55.264182 43.267841,52.065179 C 43.267841,47.079824 47.31394,43.033715 52.299305,43.033715 C 57.284671,43.033715 61.33077,47.079824 61.33077,52.065179 z " + style="opacity:0.89767444;fill:url(#linearGradient12922);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" /> + </g> + <g + id="g12914" + transform="translate(-97.43747,818.1603)" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_track.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <path + style="color:#000000;fill:url(#linearGradient12924);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient12926);stroke-width:0.66666573;stroke-linecap:round;stroke-linejoin:round;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0.75376886;visibility:visible;display:block;overflow:visible" + d="M 160.35798,194.32998 C 159.44703,194.45242 158.98728,195.66462 159.41391,196.48935 C 160.73817,201.04946 162.06244,205.60958 163.38671,210.16969 C 160.99398,209.71902 158.25682,211.21088 157.57207,213.78862 C 157.07041,215.69347 158.08704,217.83504 159.7451,218.66082 C 162.2098,220.07032 165.72114,219.02731 166.94069,216.27621 C 167.52683,215.00041 167.34378,213.45542 166.70616,212.24398 C 165.65935,208.63608 164.61254,205.02817 163.56574,201.42027 C 165.49428,201.11297 167.50276,200.17928 168.53268,198.32157 C 168.92066,197.5672 169.33633,196.52966 168.72264,195.77544 C 168.22462,195.12181 167.31678,195.2785 166.71328,195.65871 C 165.14487,196.31891 163.42205,195.7153 161.97889,194.97431 C 161.4743,194.68138 160.96253,194.2792 160.35798,194.32998 z " + id="path12916" + inkscape:export-filename="/home/kant/Desktop/playlist16.png" + inkscape:export-xdpi="3.3299999" + inkscape:export-ydpi="3.3299999" /> + </g> + </g> + <g + transform="matrix(1.500002,0,0,1.500002,-300.25598,172.06031)" + id="g12928" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/48x48/actions/amarok_clock.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <g + transform="matrix(1.605163,0,0,1.605163,77.31162,159.1495)" + id="g12930"> + <path + sodipodi:type="arc" + style="fill:url(#linearGradient12968);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path12932" + sodipodi:cx="418.94757" + sodipodi:cy="444.83228" + sodipodi:rx="9.8239412" + sodipodi:ry="9.8239412" + d="M 428.77151 444.83228 A 9.8239412 9.8239412 0 1 1 409.12363,444.83228 A 9.8239412 9.8239412 0 1 1 428.77151 444.83228 z" + transform="matrix(1.014646,0,0,1.014647,-372.754,-399.2811)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:type="arc" + style="fill:url(#radialGradient12970);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path12934" + sodipodi:cx="418.51233" + sodipodi:cy="446.44885" + sodipodi:rx="11.005301" + sodipodi:ry="11.005301" + d="M 429.51763 446.44885 A 11.005301 11.005301 0 1 1 407.50703,446.44885 A 11.005301 11.005301 0 1 1 429.51763 446.44885 z" + transform="matrix(0.86456,0,0,0.864561,-309.4995,-333.9156)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + style="opacity:0.89767444;fill:url(#linearGradient12972);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 61.33077,52.065179 C 61.980407,54.489659 57.284671,51.286579 52.299305,51.286579 C 47.31394,51.286579 43.267841,55.264182 43.267841,52.065179 C 43.267841,47.079824 47.31394,43.033715 52.299305,43.033715 C 57.284671,43.033715 61.33077,47.079824 61.33077,52.065179 z " + id="path12936" + sodipodi:nodetypes="csssc" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" /> + </g> + <rect + y="228.87321" + x="160.73047" + height="2.8284271" + width="1.1570846" + id="rect12938" + style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.25;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.25;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect12940" + width="1.1570846" + height="2.8284271" + x="160.73047" + y="254.26477" /> + <rect + transform="matrix(0,-1,1,0,0,0)" + y="172.30132" + x="-243.30341" + height="2.828429" + width="1.1570839" + id="rect12942" + style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.25;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.25;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect12944" + width="1.1570839" + height="2.828429" + x="-243.30341" + y="147.48828" + transform="matrix(0,-1,1,0,0,0)" /> + <rect + transform="matrix(0.829849,0.557988,-0.557988,0.829849,0,0)" + style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.25;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect12946" + width="1.1570832" + height="2.828429" + x="268.86493" + y="97.521248" /> + <rect + transform="matrix(0.829849,0.557988,-0.557988,0.829849,0,0)" + y="122.91283" + x="268.86493" + height="2.828429" + width="1.1570832" + id="rect12948" + style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.25;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.25;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect12950" + width="1.1570847" + height="2.8284252" + x="-111.95145" + y="280.43579" + transform="matrix(0.557988,-0.829849,0.829849,0.557988,0,0)" /> + <rect + transform="matrix(0.557988,-0.829849,0.829849,0.557988,0,0)" + y="255.6228" + x="-111.95145" + height="2.8284252" + width="1.1570847" + id="rect12952" + style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.25;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + y="-37.706596" + x="290.07108" + height="2.8284206" + width="1.1570789" + id="rect12954" + style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.25;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + transform="matrix(0.484003,0.875066,-0.875066,0.484003,0,0)" /> + <rect + style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.25;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect12956" + width="1.1570789" + height="2.8284206" + x="290.07108" + y="-12.315089" + transform="matrix(0.484003,0.875066,-0.875066,0.484003,0,0)" /> + <rect + transform="matrix(0.875066,-0.484003,0.484003,0.875066,0,0)" + y="301.64191" + x="23.276415" + height="2.8284147" + width="1.1570812" + id="rect12958" + style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.25;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.25;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect12960" + width="1.1570812" + height="2.8284147" + x="23.276415" + y="276.82895" + transform="matrix(0.875066,-0.484003,0.484003,0.875066,0,0)" /> + <path + style="fill:url(#linearGradient12974);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient12976);stroke-width:0.72067547px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="M 162.13976,243.20705 L 155.94631,250.36942 L 155.52177,250.11699 L 160.86615,242.4498 L 162.13976,243.20705 z " + id="path12962" + sodipodi:nodetypes="ccccc" /> + <path + sodipodi:nodetypes="ccccc" + id="path12964" + d="M 160.51696,243.24049 L 161.0919,231.05022 L 161.66684,231.05022 L 162.24179,243.24049 L 160.51696,243.24049 z " + style="fill:url(#linearGradient12978);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient12980);stroke-width:0.80065519px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + transform="matrix(1.07013,0,0,1.101604,-11.09106,-25.26757)" + d="M 162.97088 243.27481 A 1.868932 1.815534 0 1 1 159.23301,243.27481 A 1.868932 1.815534 0 1 1 162.97088 243.27481 z" + sodipodi:ry="1.815534" + sodipodi:rx="1.868932" + sodipodi:cy="243.27481" + sodipodi:cx="161.10194" + id="path12966" + style="opacity:1;fill:url(#radialGradient12982);fill-opacity:1;stroke:url(#linearGradient12984);stroke-width:0.33333293;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + </g> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/64x64/actions/amarok_clock.png" + id="g12986" + transform="matrix(2.000002,0,0,2.000002,-306.91048,42.697805)"> + <g + id="g12988" + transform="matrix(1.605163,0,0,1.605163,77.31162,159.1495)"> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + transform="matrix(1.014646,0,0,1.014647,-372.754,-399.2811)" + d="M 428.77151 444.83228 A 9.8239412 9.8239412 0 1 1 409.12363,444.83228 A 9.8239412 9.8239412 0 1 1 428.77151 444.83228 z" + sodipodi:ry="9.8239412" + sodipodi:rx="9.8239412" + sodipodi:cy="444.83228" + sodipodi:cx="418.94757" + id="path12990" + style="fill:url(#linearGradient13026);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + transform="matrix(0.86456,0,0,0.864561,-309.4995,-333.9156)" + d="M 429.51763 446.44885 A 11.005301 11.005301 0 1 1 407.50703,446.44885 A 11.005301 11.005301 0 1 1 429.51763 446.44885 z" + sodipodi:ry="11.005301" + sodipodi:rx="11.005301" + sodipodi:cy="446.44885" + sodipodi:cx="418.51233" + id="path12992" + style="fill:url(#radialGradient13028);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + sodipodi:nodetypes="csssc" + id="path12994" + d="M 61.33077,52.065179 C 61.980407,54.489659 57.284671,51.286579 52.299305,51.286579 C 47.31394,51.286579 43.267841,55.264182 43.267841,52.065179 C 43.267841,47.079824 47.31394,43.033715 52.299305,43.033715 C 57.284671,43.033715 61.33077,47.079824 61.33077,52.065179 z " + style="opacity:0.89767444;fill:url(#linearGradient13030);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" /> + </g> + <rect + style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.25;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect12996" + width="1.1570846" + height="2.8284271" + x="160.73047" + y="228.87321" /> + <rect + y="254.26477" + x="160.73047" + height="2.8284271" + width="1.1570846" + id="rect12998" + style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.25;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.25;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect13000" + width="1.1570839" + height="2.828429" + x="-243.30341" + y="172.30132" + transform="matrix(0,-1,1,0,0,0)" /> + <rect + transform="matrix(0,-1,1,0,0,0)" + y="147.48828" + x="-243.30341" + height="2.828429" + width="1.1570839" + id="rect13002" + style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.25;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + y="97.521248" + x="268.86493" + height="2.828429" + width="1.1570832" + id="rect13004" + style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.25;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + transform="matrix(0.829849,0.557988,-0.557988,0.829849,0,0)" /> + <rect + style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.25;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect13006" + width="1.1570832" + height="2.828429" + x="268.86493" + y="122.91283" + transform="matrix(0.829849,0.557988,-0.557988,0.829849,0,0)" /> + <rect + transform="matrix(0.557988,-0.829849,0.829849,0.557988,0,0)" + y="280.43579" + x="-111.95145" + height="2.8284252" + width="1.1570847" + id="rect13008" + style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.25;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.25;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect13010" + width="1.1570847" + height="2.8284252" + x="-111.95145" + y="255.6228" + transform="matrix(0.557988,-0.829849,0.829849,0.557988,0,0)" /> + <rect + transform="matrix(0.484003,0.875066,-0.875066,0.484003,0,0)" + style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.25;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect13012" + width="1.1570789" + height="2.8284206" + x="290.07108" + y="-37.706596" /> + <rect + transform="matrix(0.484003,0.875066,-0.875066,0.484003,0,0)" + y="-12.315089" + x="290.07108" + height="2.8284206" + width="1.1570789" + id="rect13014" + style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.25;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.25;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect13016" + width="1.1570812" + height="2.8284147" + x="23.276415" + y="301.64191" + transform="matrix(0.875066,-0.484003,0.484003,0.875066,0,0)" /> + <rect + transform="matrix(0.875066,-0.484003,0.484003,0.875066,0,0)" + y="276.82895" + x="23.276415" + height="2.8284147" + width="1.1570812" + id="rect13018" + style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.25;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + sodipodi:nodetypes="ccccc" + id="path13020" + d="M 162.13976,243.20705 L 155.94631,250.36942 L 155.52177,250.11699 L 160.86615,242.4498 L 162.13976,243.20705 z " + style="fill:url(#linearGradient13032);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient13034);stroke-width:0.54050666px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + style="fill:url(#linearGradient13036);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient13038);stroke-width:0.60049146px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="M 160.51696,243.24049 L 161.0919,231.05022 L 161.66684,231.05022 L 162.24179,243.24049 L 160.51696,243.24049 z " + id="path13022" + sodipodi:nodetypes="ccccc" /> + <path + sodipodi:type="arc" + style="opacity:1;fill:url(#radialGradient13040);fill-opacity:1;stroke:url(#linearGradient13042);stroke-width:0.24999972;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path13024" + sodipodi:cx="161.10194" + sodipodi:cy="243.27481" + sodipodi:rx="1.868932" + sodipodi:ry="1.815534" + d="M 162.97088 243.27481 A 1.868932 1.815534 0 1 1 159.23301,243.27481 A 1.868932 1.815534 0 1 1 162.97088 243.27481 z" + transform="matrix(1.07013,0,0,1.101604,-11.09106,-25.26757)" /> + </g> + <g + transform="translate(-175.13869,-543.21422)" + id="g13061" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/32x32/actions/amarok_remove.png" + inkscape:export-xdpi="90.000092" + inkscape:export-ydpi="90.000092"> + <g + transform="matrix(1.605163,0,0,1.605163,-22.15089,1075.787)" + id="g13063"> + <path + sodipodi:type="arc" + style="fill:url(#linearGradient13073);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path13065" + sodipodi:cx="418.94757" + sodipodi:cy="444.83228" + sodipodi:rx="9.8239412" + sodipodi:ry="9.8239412" + d="M 428.77151 444.83228 A 9.8239412 9.8239412 0 1 1 409.12363,444.83228 A 9.8239412 9.8239412 0 1 1 428.77151 444.83228 z" + transform="matrix(1.014646,0,0,1.014647,-372.754,-399.2811)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:type="arc" + style="fill:url(#radialGradient13075);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path13067" + sodipodi:cx="418.51233" + sodipodi:cy="446.44885" + sodipodi:rx="11.005301" + sodipodi:ry="11.005301" + d="M 429.51763 446.44885 A 11.005301 11.005301 0 1 1 407.50703,446.44885 A 11.005301 11.005301 0 1 1 429.51763 446.44885 z" + transform="matrix(0.86456,0,0,0.864561,-309.4995,-333.9156)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + style="opacity:0.89767444;fill:url(#linearGradient13077);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 61.33077,52.065179 C 61.980407,54.489659 57.284671,51.286579 52.299305,51.286579 C 47.31394,51.286579 43.267841,55.264182 43.267841,52.065179 C 43.267841,47.079824 47.31394,43.033715 52.299305,43.033715 C 57.284671,43.033715 61.33077,47.079824 61.33077,52.065179 z " + id="path13069" + sodipodi:nodetypes="csssc" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" /> + </g> + <path + id="path13071" + d="M 68.812258,1163.1445 L 64.989808,1159.3353 L 68.732838,1155.5242 L 65.595638,1152.3979 L 61.852608,1156.2091 L 58.050838,1152.4754 L 54.880618,1155.6567 L 58.682378,1159.3904 L 54.899318,1163.1316 L 58.036518,1166.2578 L 61.819568,1162.5166 L 65.642038,1166.3258 L 68.812258,1163.1445 z " + style="fill:url(#linearGradient13079);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient13081);stroke-width:0.60605747;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + sodipodi:nodetypes="ccccccccccccc" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_add_lyrics.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + </g> + <g + transform="matrix(2.000002,0,0,2.000002,-108.98521,-1717.579)" + id="g13083" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/64x64/actions/amarok_remove.png" + inkscape:export-xdpi="90.000092" + inkscape:export-ydpi="90.000092"> + <g + transform="matrix(1.605163,0,0,1.605163,-22.15089,1075.787)" + id="g13085"> + <path + sodipodi:type="arc" + style="fill:url(#linearGradient13095);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path13087" + sodipodi:cx="418.94757" + sodipodi:cy="444.83228" + sodipodi:rx="9.8239412" + sodipodi:ry="9.8239412" + d="M 428.77151 444.83228 A 9.8239412 9.8239412 0 1 1 409.12363,444.83228 A 9.8239412 9.8239412 0 1 1 428.77151 444.83228 z" + transform="matrix(1.014646,0,0,1.014647,-372.754,-399.2811)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:type="arc" + style="fill:url(#radialGradient13097);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path13089" + sodipodi:cx="418.51233" + sodipodi:cy="446.44885" + sodipodi:rx="11.005301" + sodipodi:ry="11.005301" + d="M 429.51763 446.44885 A 11.005301 11.005301 0 1 1 407.50703,446.44885 A 11.005301 11.005301 0 1 1 429.51763 446.44885 z" + transform="matrix(0.86456,0,0,0.864561,-309.4995,-333.9156)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + style="opacity:0.89767444;fill:url(#linearGradient13099);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 61.33077,52.065179 C 61.980407,54.489659 57.284671,51.286579 52.299305,51.286579 C 47.31394,51.286579 43.267841,55.264182 43.267841,52.065179 C 43.267841,47.079824 47.31394,43.033715 52.299305,43.033715 C 57.284671,43.033715 61.33077,47.079824 61.33077,52.065179 z " + id="path13091" + sodipodi:nodetypes="csssc" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" /> + </g> + <path + id="path13093" + d="M 68.812258,1163.1445 L 64.989808,1159.3353 L 68.732838,1155.5242 L 65.595638,1152.3979 L 61.852608,1156.2091 L 58.050838,1152.4754 L 54.880618,1155.6567 L 58.682378,1159.3904 L 54.899318,1163.1316 L 58.036518,1166.2578 L 61.819568,1162.5166 L 65.642038,1166.3258 L 68.812258,1163.1445 z " + style="fill:url(#linearGradient13101);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient13103);stroke-width:0.30302873;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + sodipodi:nodetypes="ccccccccccccc" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_add_lyrics.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + </g> + <g + transform="matrix(1.500002,0,0,1.500002,-299.66248,218.92641)" + id="g13105" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/48x48/actions/amarok_love.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <g + transform="matrix(1.605163,0,0,1.605163,76.916,231.5603)" + id="g13107"> + <path + sodipodi:type="arc" + style="fill:url(#linearGradient13117);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path13109" + sodipodi:cx="418.94757" + sodipodi:cy="444.83228" + sodipodi:rx="9.8239412" + sodipodi:ry="9.8239412" + d="M 428.77151 444.83228 A 9.8239412 9.8239412 0 1 1 409.12363,444.83228 A 9.8239412 9.8239412 0 1 1 428.77151 444.83228 z" + transform="matrix(1.014646,0,0,1.014647,-372.754,-399.2811)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:type="arc" + style="fill:url(#radialGradient13119);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path13111" + sodipodi:cx="418.51233" + sodipodi:cy="446.44885" + sodipodi:rx="11.005301" + sodipodi:ry="11.005301" + d="M 429.51763 446.44885 A 11.005301 11.005301 0 1 1 407.50703,446.44885 A 11.005301 11.005301 0 1 1 429.51763 446.44885 z" + transform="matrix(0.86456,0,0,0.864561,-309.4995,-333.9156)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + style="opacity:0.89767444;fill:url(#linearGradient13121);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 61.33077,52.065179 C 61.980407,54.489659 57.284671,51.286579 52.299305,51.286579 C 47.31394,51.286579 43.267841,55.264182 43.267841,52.065179 C 43.267841,47.079824 47.31394,43.033715 52.299305,43.033715 C 57.284671,43.033715 61.33077,47.079824 61.33077,52.065179 z " + id="path13113" + sodipodi:nodetypes="csssc" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" /> + </g> + <path + sodipodi:nodetypes="cscccsc" + id="path13115" + d="M 160.99719,326.20599 C 160.99719,326.20599 146.31058,317.80296 150.79695,310.32569 C 152.24223,307.91688 153.88708,307.02377 155.42573,306.90751 C 158.36316,306.68555 160.90907,309.88251 160.90907,309.88251 C 160.90907,309.88251 163.0436,307.09476 165.66248,306.90751 C 167.39706,306.78349 169.34597,307.53365 171.02119,310.32569 C 175.50755,317.80296 160.99719,326.20599 160.99719,326.20599 z " + style="fill:url(#linearGradient13123);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient13125);stroke-width:0.33333293;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/64x64/actions/amarok_love.png" + id="g13127" + transform="matrix(2.000002,0,0,2.000002,-305.11918,53.358505)"> + <g + id="g13129" + transform="matrix(1.605163,0,0,1.605163,76.916,231.5603)"> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + transform="matrix(1.014646,0,0,1.014647,-372.754,-399.2811)" + d="M 428.77151 444.83228 A 9.8239412 9.8239412 0 1 1 409.12363,444.83228 A 9.8239412 9.8239412 0 1 1 428.77151 444.83228 z" + sodipodi:ry="9.8239412" + sodipodi:rx="9.8239412" + sodipodi:cy="444.83228" + sodipodi:cx="418.94757" + id="path13131" + style="fill:url(#linearGradient13139);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + transform="matrix(0.86456,0,0,0.864561,-309.4995,-333.9156)" + d="M 429.51763 446.44885 A 11.005301 11.005301 0 1 1 407.50703,446.44885 A 11.005301 11.005301 0 1 1 429.51763 446.44885 z" + sodipodi:ry="11.005301" + sodipodi:rx="11.005301" + sodipodi:cy="446.44885" + sodipodi:cx="418.51233" + id="path13133" + style="fill:url(#radialGradient13141);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + sodipodi:nodetypes="csssc" + id="path13135" + d="M 61.33077,52.065179 C 61.980407,54.489659 57.284671,51.286579 52.299305,51.286579 C 47.31394,51.286579 43.267841,55.264182 43.267841,52.065179 C 43.267841,47.079824 47.31394,43.033715 52.299305,43.033715 C 57.284671,43.033715 61.33077,47.079824 61.33077,52.065179 z " + style="opacity:0.89767444;fill:url(#linearGradient13143);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" /> + </g> + <path + style="fill:url(#linearGradient13145);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient13147);stroke-width:0.24999972;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 160.99719,326.20599 C 160.99719,326.20599 146.31058,317.80296 150.79695,310.32569 C 152.24223,307.91688 153.88708,307.02377 155.42573,306.90751 C 158.36316,306.68555 160.90907,309.88251 160.90907,309.88251 C 160.90907,309.88251 163.0436,307.09476 165.66248,306.90751 C 167.39706,306.78349 169.34597,307.53365 171.02119,310.32569 C 175.50755,317.80296 160.99719,326.20599 160.99719,326.20599 z " + id="path13137" + sodipodi:nodetypes="cscccsc" /> + </g> + <g + id="g11957" + transform="translate(-5,-5.0270042)"> + <rect + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/48x48/actions/amarok_playlist_clear.png" + y="33.389187" + x="217.80756" + height="48" + width="48" + id="rect11523" + style="fill:#b9cff9;fill-opacity:0;stroke:none;stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" /> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/48x48/actions/amarok_playlist_clear.png" + id="g14231" + transform="matrix(1.499261,0,0,1.499261,-153.11226,105.60221)"> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Toolbar-and-menu-icons/amarok_playlist_clear.png" + sodipodi:nodetypes="ccccccccc" + style="fill:url(#linearGradient14273);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient14275);stroke-width:0.50024641;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 251.89706,-44.262813 C 249.61272,-44.262813 247.77694,-43.450681 247.77694,-41.080157 L 247.77694,-21.909202 C 247.90734,-21.054374 248.86314,-20.21381 249.43677,-20.052932 L 277.16366,-20.052932 C 277.74178,-20.215073 278.91607,-21.0447 279.04236,-21.909202 L 279.04236,-41.080157 C 279.04236,-43.450681 277.20658,-44.262813 274.92225,-44.262813 L 251.89706,-44.262813 z " + id="path14233" /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Toolbar-and-menu-icons/amarok_playlist_clear.png" + id="path14235" + d="M 250.12056,-38.645009 L 276.69881,-38.645009 C 277.54022,-38.645009 278.21759,-37.985392 278.21759,-37.166047 L 278.21759,-22.24056 C 278.21759,-21.421215 277.54022,-20.761598 276.69881,-20.761598 L 250.12056,-20.761598 C 249.27915,-20.761598 248.60178,-21.421215 248.60178,-22.24056 L 248.60178,-37.166047 C 248.60178,-37.985392 249.27915,-38.645009 250.12056,-38.645009 z " + style="fill:url(#linearGradient14277);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient14279);stroke-width:0.6180014;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Toolbar-and-menu-icons/amarok_playlist_clear.png" + style="opacity:0.99578091;fill:url(#radialGradient14281);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.57956129;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 250.08215,-38.266202 L 276.71513,-38.266202 C 277.55825,-38.266202 278.23703,-37.624086 278.23703,-36.826475 L 278.23703,-22.296956 C 278.23703,-21.499349 277.55825,-20.857229 276.71513,-20.857229 L 250.08215,-20.857229 C 249.23911,-20.857229 248.56031,-21.499349 248.56031,-22.296956 L 248.56031,-36.826475 C 248.56031,-37.624086 249.23911,-38.266202 250.08215,-38.266202 z " + id="path14237" /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Toolbar-and-menu-icons/amarok_playlist_clear.png" + id="path14239" + d="M 252.16347,-44.213855 C 249.93199,-44.213855 248.13869,-43.379701 248.13869,-40.944894 L 248.13869,-40.580717 C 248.26605,-39.702708 249.19975,-38.839348 249.76009,-38.674109 L 276.84538,-38.674109 C 277.41011,-38.840648 278.55724,-39.692772 278.68061,-40.580717 L 278.68061,-40.944894 C 278.68061,-43.379701 276.88731,-44.213855 274.65582,-44.213855 L 252.16347,-44.213855 z " + style="opacity:0.15189873;fill:url(#linearGradient14283);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.71897209;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:nodetypes="ccccccccc" /> + </g> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/48x48/actions/amarok_playlist_clear.png" + transform="matrix(2.3752417,0,0,2.2858018,-282.02892,-412.38886)" + id="g14241"> + <g + id="g14243"> + <path + id="path14245" + d="M 216.59388,201.58154 L 216.59388,213.16264" + style="opacity:0.39183673;fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#264a9e;stroke-width:0.19311959;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:nodetypes="cc" /> + <path + style="opacity:0.39183673;fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#264a9e;stroke-width:0.19311959;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 223.52993,201.58154 L 223.52993,213.17716" + id="path14247" + sodipodi:nodetypes="cc" /> + </g> + <rect + style="opacity:0.56734691;fill:#6785ed;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.28953668;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect14249" + width="18.826923" + height="3.4617176" + x="211.13826" + y="205.69659" /> + </g> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/48x48/actions/amarok_playlist_clear.png" + transform="matrix(1.5166019,0,0,1.5166019,-157.68002,106.15986)" + id="g14253"> + <path + id="path14255" + d="M 251.89706,-44.262813 C 249.61272,-44.262813 247.77694,-43.450681 247.77694,-41.080157 L 247.77694,-21.909202 C 247.90734,-21.054374 248.86314,-20.21381 249.43677,-20.052932 L 277.16366,-20.052932 C 277.74178,-20.215073 278.91607,-21.0447 279.04236,-21.909202 L 279.04236,-41.080157 C 279.04236,-43.450681 277.20658,-44.262813 274.92225,-44.262813 L 251.89706,-44.262813 z " + style="fill:url(#linearGradient14285);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient14287);stroke-width:0.38428423;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:nodetypes="ccccccccc" + inkscape:export-filename="/home/lando/Projects/Amarok/Toolbar-and-menu-icons/amarok_playlist_clear.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + style="fill:url(#linearGradient14289);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient14291);stroke-width:0.47474238;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 250.12056,-38.645009 L 276.69881,-38.645009 C 277.54022,-38.645009 278.21759,-37.985392 278.21759,-37.166047 L 278.21759,-22.24056 C 278.21759,-21.421215 277.54022,-20.761598 276.69881,-20.761598 L 250.12056,-20.761598 C 249.27915,-20.761598 248.60178,-21.421215 248.60178,-22.24056 L 248.60178,-37.166047 C 248.60178,-37.985392 249.27915,-38.645009 250.12056,-38.645009 z " + id="path14257" + inkscape:export-filename="/home/lando/Projects/Amarok/Toolbar-and-menu-icons/amarok_playlist_clear.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + id="path14259" + d="M 250.08215,-38.266202 L 276.71513,-38.266202 C 277.55825,-38.266202 278.23703,-37.624086 278.23703,-36.826475 L 278.23703,-22.296956 C 278.23703,-21.499349 277.55825,-20.857229 276.71513,-20.857229 L 250.08215,-20.857229 C 249.23911,-20.857229 248.56031,-21.499349 248.56031,-22.296956 L 248.56031,-36.826475 C 248.56031,-37.624086 249.23911,-38.266202 250.08215,-38.266202 z " + style="opacity:0.99578091;fill:url(#radialGradient14293);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.57956129;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + inkscape:export-filename="/home/lando/Projects/Amarok/Toolbar-and-menu-icons/amarok_playlist_clear.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:nodetypes="ccccccccc" + style="opacity:0.15189873;fill:url(#linearGradient14295);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.71897209;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 252.16347,-44.213855 C 249.93199,-44.213855 248.13869,-43.379701 248.13869,-40.944894 L 248.13869,-40.580717 C 248.26605,-39.702708 249.19975,-38.839348 249.76009,-38.674109 L 276.84538,-38.674109 C 277.41011,-38.840648 278.55724,-39.692772 278.68061,-40.580717 L 278.68061,-40.944894 C 278.68061,-43.379701 276.88731,-44.213855 274.65582,-44.213855 L 252.16347,-44.213855 z " + id="path14261" + inkscape:export-filename="/home/lando/Projects/Amarok/Toolbar-and-menu-icons/amarok_playlist_clear.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + </g> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/48x48/actions/amarok_playlist_clear.png" + id="g14263" + transform="matrix(2.4027145,0,0,2.3122401,-288.08777,-417.82246)"> + <g + id="g14265"> + <path + sodipodi:nodetypes="cc" + style="opacity:0.39183673;fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#264a9e;stroke-width:0.14835252;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 216.59388,201.58154 L 216.59388,213.16264" + id="path14267" /> + <path + sodipodi:nodetypes="cc" + id="path14269" + d="M 223.52993,201.58154 L 223.52993,213.17716" + style="opacity:0.39183673;fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#264a9e;stroke-width:0.14835252;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + <rect + y="205.69659" + x="211.13826" + height="3.4617176" + width="18.826923" + id="rect14271" + style="opacity:0.56734691;fill:#6785ed;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.28953668;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + </g> + <g + id="g12053" + transform="translate(-5,-8.0270023)"> + <rect + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/64x64/actions/amarok_playlist_refresh.png" + y="93.389183" + x="282.9335" + height="64" + width="64" + id="rect11535" + style="fill:#b9cff9;fill-opacity:0;stroke:none;stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" /> + <path + sodipodi:nodetypes="ccccccccc" + style="fill:url(#linearGradient14080);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient14082);stroke-width:0.74999988;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 291.6435,100.90087 C 287.02228,100.90087 283.30849,102.54381 283.30849,107.33938 L 283.30849,146.12225 C 283.57229,147.85157 285.50588,149.55202 286.66633,149.87749 L 342.75786,149.87749 C 343.9274,149.54948 346.30299,147.87113 346.55847,146.12225 L 346.55847,107.33938 C 346.55847,102.54381 342.84469,100.90087 338.22348,100.90087 L 291.6435,100.90087 z " + id="path14405" /> + <path + id="path14407" + d="M 288.04964,112.26569 L 341.81747,112.26569 C 343.51964,112.26569 344.88996,113.6001 344.88996,115.25763 L 344.88996,145.45191 C 344.88996,147.10943 343.51964,148.44385 341.81747,148.44385 L 288.04964,148.44385 C 286.34747,148.44385 284.97715,147.10943 284.97715,145.45191 L 284.97715,115.25763 C 284.97715,113.6001 286.34747,112.26569 288.04964,112.26569 z " + style="fill:url(#linearGradient14075);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient14077);stroke-width:0.92654544;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" /> + <path + style="opacity:0.99578091;fill:url(#radialGradient14072);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.57956129;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 287.97194,113.03202 L 341.85048,113.03202 C 343.55611,113.03202 344.92929,114.33101 344.92929,115.94458 L 344.92929,145.33781 C 344.92929,146.95138 343.55611,148.25039 341.85048,148.25039 L 287.97194,148.25039 C 286.26647,148.25039 284.89325,146.95138 284.89325,145.33781 L 284.89325,115.94458 C 284.89325,114.33101 286.26647,113.03202 287.97194,113.03202 z " + id="path14409" /> + <path + id="path14411" + d="M 292.18245,100.9999 C 287.66816,100.9999 284.04032,102.68741 284.04032,107.61303 L 284.04032,108.34975 C 284.29796,110.12596 286.18684,111.87254 287.32041,112.20682 L 342.11398,112.20682 C 343.25643,111.86991 345.57707,110.14607 345.82665,108.34975 L 345.82665,107.61303 C 345.82665,102.68741 342.1988,100.9999 337.6845,100.9999 L 292.18245,100.9999 z " + style="opacity:0.15189873;fill:url(#linearGradient14069);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.71897209;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:nodetypes="ccccccccc" /> + <g + transform="matrix(3.2049899,0,0,3.0843058,-391.85199,-508.98034)" + id="g14413"> + <g + id="g14415"> + <path + id="path14417" + d="M 216.59388,201.58154 L 216.59388,213.16264" + style="opacity:0.39183673;fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#264a9e;stroke-width:0.14312236;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:nodetypes="cc" /> + <path + style="opacity:0.39183673;fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#264a9e;stroke-width:0.14312236;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 223.52993,201.58154 L 223.52993,213.17716" + id="path14419" + sodipodi:nodetypes="cc" /> + </g> + <rect + style="opacity:0.56734691;fill:#6785ed;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.28953668;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect14421" + width="18.826923" + height="3.4617176" + x="211.13826" + y="205.69659" /> + </g> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/vadim/collection.png" + id="path14423" + d="M 319.66716,104.6855 L 318.73049,108.75461 C 311.67819,108.27833 304.47619,111.67062 300.1662,118.28242 C 297.64736,122.14659 296.50888,126.52064 296.61149,130.78916 C 297.03399,129.08077 297.73864,127.43555 298.7305,125.91393 C 302.48375,120.1561 309.42426,117.7198 316.38116,118.96575 L 314.93779,125.23062 L 331.33703,117.72963 L 319.66716,104.6855 z M 331.59805,122.54346 C 331.17555,124.25183 330.47859,125.89709 329.48673,127.41874 C 325.73346,133.17654 318.79294,135.6128 311.83609,134.36687 L 313.27946,128.102 L 296.88022,135.61066 L 308.54238,148.64712 L 309.47905,144.57801 C 316.53139,145.05427 323.73337,141.662 328.04336,135.05018 C 330.56218,131.18603 331.70066,126.81196 331.59805,122.54346 z " + style="fill:url(#linearGradient14059);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient14061);stroke-width:0.69933343;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + <g + id="g12831" + transform="translate(7,-13.954873)"> + <rect + inkscape:export-ydpi="90.000092" + inkscape:export-xdpi="90.000092" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/32x32/actions/amarok_random_album.png" + y="262.90286" + x="461.367" + height="32" + width="32" + id="rect10379" + style="fill:#b9cff9;fill-opacity:0;stroke:none;stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" /> + <g + inkscape:export-ydpi="90.000092" + inkscape:export-xdpi="90.000092" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/32x32/actions/amarok_random_album.png" + transform="matrix(0.9626079,0,0,0.9626079,25.221122,176.17523)" + id="g15546"> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Playback-modes/amarok_random_album.png" + transform="translate(26,55)" + id="g15548"> + <path + sodipodi:nodetypes="ccccccc" + id="path15550" + d="M 441.06383,60.713305 L 441.06542,49.495395 L 450.43446,45.740673 L 459.81147,49.492971 L 459.81139,60.717581 L 450.43626,66.344952 L 441.06383,60.713305 z " + style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient16033);stroke-width:1.03884327;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <g + id="g15552"> + <path + sodipodi:nodetypes="ccccc" + id="path15554" + d="M 441.62894,60.482979 L 441.68599,49.920973 L 450.4374,54.20208 L 450.43841,65.798814 L 441.62894,60.482979 z " + style="fill:url(#linearGradient16035);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.0742841;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + sodipodi:nodetypes="ccccc" + id="path15556" + d="M 450.43385,54.203364 L 459.3,49.83834 L 459.30702,60.424236 L 450.44184,65.854521 L 450.43385,54.203364 z " + style="fill:url(#linearGradient16037);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.07509746;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + sodipodi:nodetypes="ccccc" + id="path15558" + d="M 441.688,49.920202 L 450.43621,46.291756 L 459.29421,49.844316 L 450.43299,54.201471 L 441.688,49.920202 z " + style="fill:url(#linearGradient16039);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.79999995;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + <path + sodipodi:type="arc" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path15560" + sodipodi:cx="1749.9153" + sodipodi:cy="1631.8588" + sodipodi:rx="13.21095" + sodipodi:ry="22.49432" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + transform="matrix(0.129577,-2.161349e-2,2.662017e-2,3.635072e-2,180.263,31.06197)" /> + <path + transform="matrix(0.108645,-1.714179e-2,2.231983e-2,2.883016e-2,223.9065,30.65984)" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + sodipodi:ry="22.49432" + sodipodi:rx="13.21095" + sodipodi:cy="1631.8588" + sodipodi:cx="1749.9153" + id="path15562" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + sodipodi:type="arc" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path15564" + sodipodi:cx="1749.9153" + sodipodi:cy="1631.8588" + sodipodi:rx="13.21095" + sodipodi:ry="22.49432" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + transform="matrix(0.11443,-1.853676e-2,2.350834e-2,3.117637e-2,211.8228,31.4122)" /> + <path + transform="matrix(0.11443,-1.853676e-2,2.350834e-2,3.117637e-2,206.4981,31.4122)" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + sodipodi:ry="22.49432" + sodipodi:rx="13.21095" + sodipodi:cy="1631.8588" + sodipodi:cx="1749.9153" + id="path15566" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + sodipodi:type="arc" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path15568" + sodipodi:cx="1749.9153" + sodipodi:cy="1631.8588" + sodipodi:rx="13.21095" + sodipodi:ry="22.49432" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + transform="matrix(0.11443,-1.853676e-2,2.350834e-2,3.117637e-2,217.0829,31.4122)" /> + <path + transform="matrix(9.309428e-2,-2.478274e-2,-3.935631e-3,7.164391e-2,295.9902,-17.92848)" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + sodipodi:ry="22.49432" + sodipodi:rx="13.21095" + sodipodi:cy="1631.8588" + sodipodi:cx="1749.9153" + id="path15570" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + sodipodi:type="arc" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path15572" + sodipodi:cx="1749.9153" + sodipodi:cy="1631.8588" + sodipodi:rx="13.21095" + sodipodi:ry="22.49432" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + transform="matrix(8.73537e-2,-2.437095e-2,-2.584881e-3,6.94881e-2,306.3905,-13.26718)" /> + <path + transform="matrix(8.170002e-2,-2.177155e-2,-3.529402e-3,6.291213e-2,320.3887,-5.30443)" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + sodipodi:ry="22.49432" + sodipodi:rx="13.21095" + sodipodi:cy="1631.8588" + sodipodi:cx="1749.9153" + id="path15574" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + sodipodi:type="arc" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path15576" + sodipodi:cx="1749.9153" + sodipodi:cy="1631.8588" + sodipodi:rx="13.21095" + sodipodi:ry="22.49432" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + transform="matrix(8.021497e-2,-3.444141e-2,2.833695e-2,6.672447e-2,259.4509,9.15401)" /> + </g> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Playback-modes/amarok_random_album.png" + style="fill:url(#linearGradient16041);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient16043);stroke-width:1.03884327;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 453.65005,103.86523 C 454.17794,109.83142 459.44501,114.2581 465.40977,113.74673 C 471.37453,113.23535 475.78796,107.97875 475.26007,102.01256 C 474.73219,96.046374 469.46202,91.619968 463.49726,92.131338 C 457.53251,92.642708 453.12217,97.899043 453.65005,103.86523 z M 461.29024,103.21022 C 461.13004,101.39955 462.41616,99.809325 464.16335,99.659535 C 465.91055,99.509745 467.45968,100.8569 467.61989,102.66757 C 467.78009,104.47825 466.49087,106.06868 464.74368,106.21848 C 462.99648,106.36827 461.45045,105.02084 461.29024,103.21022 z " + id="path15578" /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Playback-modes/amarok_random_album.png" + style="fill:url(#linearGradient16045);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.4984282;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + d="M 463.60314,103.42172 C 463.44371,103.33116 463.15831,103.27438 463.04486,103.11448 L 456.42039,109.90831 C 458.34841,111.93806 460.72077,113.38276 463.42291,113.5204 L 463.60314,103.42172 z " + id="path15580" + sodipodi:nodetypes="ccccc" /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Playback-modes/amarok_random_album.png" + style="fill:url(#linearGradient16047);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.49565426;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + d="M 465.03236,102.35804 C 465.19142,102.44767 465.33082,102.56753 465.44394,102.72585 L 472.39608,95.808963 C 470.47314,93.798917 467.91547,92.486434 465.21871,92.351712 L 465.03236,102.35804 z " + id="path15582" + sodipodi:nodetypes="ccccc" /> + </g> + </g> + <g + id="g12811" + transform="translate(7,-8.6474389)"> + <rect + inkscape:export-ydpi="90.000092" + inkscape:export-xdpi="90.000092" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/22x22/actions/amarok_random_album.png" + style="fill:#b9cff9;fill-opacity:0;stroke:none;stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + id="rect10377" + width="22" + height="22" + x="431.40265" + y="267.59543" /> + <g + inkscape:export-ydpi="90.000092" + inkscape:export-xdpi="90.000092" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/22x22/actions/amarok_random_album.png" + transform="matrix(0.9625071,0,0,0.9625071,23.562582,175.76243)" + id="g15584"> + <g + id="g15586" + transform="matrix(0.703331,0,0,0.703331,122.6647,70.38444)"> + <path + style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient16049);stroke-width:1.47718954;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 441.06383,60.713305 L 441.06542,49.495395 L 450.43446,45.740673 L 459.81147,49.492971 L 459.81139,60.717581 L 450.43626,66.344952 L 441.06383,60.713305 z " + id="path15588" + sodipodi:nodetypes="ccccccc" /> + <g + id="g15590"> + <path + style="fill:url(#linearGradient16051);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.0742841;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 441.68929,60.362282 L 441.68599,49.920973 L 450.4374,54.20208 L 450.43841,65.617769 L 441.68929,60.362282 z " + id="path15592" + sodipodi:nodetypes="ccccc" /> + <path + style="fill:url(#linearGradient16053);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.07509746;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 450.43385,54.203364 L 459.18738,49.922686 L 459.18632,60.363888 L 450.44184,65.613128 L 450.43385,54.203364 z " + id="path15594" + sodipodi:nodetypes="ccccc" /> + <path + style="fill:url(#linearGradient16055);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.79999995;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 441.688,49.920202 L 450.43621,46.412453 L 459.18586,49.920584 L 450.43299,54.201471 L 441.688,49.920202 z " + id="path15596" + sodipodi:nodetypes="ccccc" /> + </g> + <path + transform="matrix(0.129577,-2.161349e-2,2.662017e-2,3.635072e-2,180.263,31.06197)" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + sodipodi:ry="22.49432" + sodipodi:rx="13.21095" + sodipodi:cy="1631.8588" + sodipodi:cx="1749.9153" + id="path15598" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + sodipodi:type="arc" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path15600" + sodipodi:cx="1749.9153" + sodipodi:cy="1631.8588" + sodipodi:rx="13.21095" + sodipodi:ry="22.49432" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + transform="matrix(0.108645,-1.714179e-2,2.231983e-2,2.883016e-2,223.9065,30.65984)" /> + <path + transform="matrix(0.11443,-1.853676e-2,2.350834e-2,3.117637e-2,211.8228,31.4122)" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + sodipodi:ry="22.49432" + sodipodi:rx="13.21095" + sodipodi:cy="1631.8588" + sodipodi:cx="1749.9153" + id="path15602" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + sodipodi:type="arc" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path15604" + sodipodi:cx="1749.9153" + sodipodi:cy="1631.8588" + sodipodi:rx="13.21095" + sodipodi:ry="22.49432" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + transform="matrix(0.11443,-1.853676e-2,2.350834e-2,3.117637e-2,206.4981,31.4122)" /> + <path + transform="matrix(0.11443,-1.853676e-2,2.350834e-2,3.117637e-2,217.0829,31.4122)" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + sodipodi:ry="22.49432" + sodipodi:rx="13.21095" + sodipodi:cy="1631.8588" + sodipodi:cx="1749.9153" + id="path15607" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + sodipodi:type="arc" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path15609" + sodipodi:cx="1749.9153" + sodipodi:cy="1631.8588" + sodipodi:rx="13.21095" + sodipodi:ry="22.49432" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + transform="matrix(9.309428e-2,-2.478274e-2,-3.935631e-3,7.164391e-2,295.9902,-17.92848)" /> + <path + transform="matrix(8.73537e-2,-2.437095e-2,-2.584881e-3,6.94881e-2,306.3905,-13.26718)" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + sodipodi:ry="22.49432" + sodipodi:rx="13.21095" + sodipodi:cy="1631.8588" + sodipodi:cx="1749.9153" + id="path15611" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + sodipodi:type="arc" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path15613" + sodipodi:cx="1749.9153" + sodipodi:cy="1631.8588" + sodipodi:rx="13.21095" + sodipodi:ry="22.49432" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + transform="matrix(8.170002e-2,-2.177155e-2,-3.529402e-3,6.291213e-2,320.3887,-5.30443)" /> + <path + transform="matrix(8.021497e-2,-3.444141e-2,2.833695e-2,6.672447e-2,259.4509,9.15401)" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + sodipodi:ry="22.49432" + sodipodi:rx="13.21095" + sodipodi:cy="1631.8588" + sodipodi:cx="1749.9153" + id="path15615" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + </g> + <path + style="fill:url(#linearGradient16057);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient16059);stroke-width:1.03895319;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 424.27506,104.557 C 424.63045,108.57354 428.17642,111.55365 432.19211,111.20938 C 436.20779,110.86512 439.17907,107.32629 438.82368,103.30975 C 438.46829,99.293217 434.92023,96.313287 430.90454,96.65755 C 426.88886,97.001812 423.91967,100.54046 424.27506,104.557 z M 429.4187,104.11604 C 429.31084,102.89706 430.1767,101.8265 431.35297,101.72566 C 432.52925,101.62481 433.57218,102.53174 433.68004,103.75072 C 433.78789,104.96969 432.91994,106.0404 431.74367,106.14124 C 430.5674,106.24208 429.52655,105.33498 429.4187,104.11604 z " + id="path15617" /> + </g> + </g> + <g + id="g16358" + transform="matrix(1.009207,0,0,1.009207,-197.9038,-612.58026)" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/22x22/actions/amarok_collection.png" + inkscape:export-xdpi="90.000092" + inkscape:export-ydpi="90.000092"> + <path + style="fill:url(#linearGradient16369);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient16371);stroke-width:0.79270077;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 626.01011,726.3382 C 626.29619,729.62281 629.15054,732.05986 632.38299,731.77834 C 635.61544,731.49681 638.00719,728.60285 637.72111,725.31824 C 637.43504,722.03363 634.57901,719.59673 631.34656,719.87825 C 628.11411,720.15978 625.72404,723.05359 626.01011,726.3382 z M 630.15052,725.9776 C 630.0637,724.98075 630.76068,724.10527 631.70753,724.02281 C 632.65438,723.94034 633.49389,724.682 633.58071,725.67885 C 633.66753,726.67569 632.96887,727.55129 632.02202,727.63375 C 631.07517,727.71622 630.23734,726.97441 630.15052,725.9776 z " + id="path15619" /> + <path + style="fill:url(#linearGradient16373);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.4984282;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + d="M 631.64529,725.88285 C 631.5589,725.833 631.40423,725.80174 631.34275,725.71371 L 627.75279,729.45396 C 628.79763,730.57142 630.08327,731.36678 631.54763,731.44255 L 631.64529,725.88285 z " + id="path15621" + sodipodi:nodetypes="ccccc" /> + <path + style="fill:url(#linearGradient16375);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.49565426;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + d="M 631.96728,725.74981 C 632.05347,725.79915 632.12902,725.86514 632.19032,725.9523 L 635.95786,722.1443 C 634.91577,721.03769 633.5297,720.31512 632.06826,720.24095 L 631.96728,725.74981 z " + id="path15623" + sodipodi:nodetypes="ccccc" /> + <path + id="path15625" + d="M 621.45331,718.3447 C 621.73939,721.62931 624.59374,724.06636 627.82619,723.78484 C 631.05864,723.50331 633.45039,720.60935 633.16431,717.32474 C 632.87824,714.04013 630.02221,711.60323 626.78976,711.88475 C 623.55731,712.16628 621.16724,715.06009 621.45331,718.3447 z M 625.59372,717.9841 C 625.5069,716.98725 626.20388,716.11177 627.15073,716.02931 C 628.09758,715.94684 628.93709,716.6885 629.02391,717.68535 C 629.11073,718.68219 628.41207,719.55779 627.46522,719.64025 C 626.51837,719.72272 625.68054,718.98091 625.59372,717.9841 z " + style="fill:url(#linearGradient16377);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient16379);stroke-width:0.79270077;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + sodipodi:nodetypes="ccccc" + id="path15627" + d="M 627.08849,717.88935 C 627.0021,717.8395 626.84743,717.80824 626.78595,717.72021 L 623.19599,721.46046 C 624.24083,722.57792 625.52647,723.37328 626.99083,723.44905 L 627.08849,717.88935 z " + style="fill:url(#linearGradient16381);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.4984282;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" /> + <path + sodipodi:nodetypes="ccccc" + id="path15629" + d="M 627.41048,717.75631 C 627.49667,717.80565 627.57222,717.87164 627.63352,717.9588 L 631.40106,714.1508 C 630.35897,713.04419 628.9729,712.32162 627.51146,712.24745 L 627.41048,717.75631 z " + style="fill:url(#linearGradient16383);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.49565426;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" /> + <path + style="fill:url(#linearGradient16385);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient16387);stroke-width:0.99087697;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 616.76121,726.3382 C 617.04729,729.62281 619.90164,732.05986 623.13409,731.77834 C 626.36654,731.49681 628.75829,728.60285 628.47221,725.31824 C 628.18614,722.03363 625.33011,719.59673 622.09766,719.87825 C 618.86521,720.15978 616.47514,723.05359 616.76121,726.3382 z M 620.90162,725.9776 C 620.8148,724.98075 621.51178,724.10527 622.45863,724.02281 C 623.40548,723.94034 624.24499,724.682 624.33181,725.67885 C 624.41863,726.67569 623.71997,727.55129 622.77312,727.63375 C 621.82627,727.71622 620.98844,726.97441 620.90162,725.9776 z " + id="path15631" /> + <path + style="fill:url(#linearGradient16389);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.4984282;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + d="M 622.39639,725.88285 C 622.31,725.833 622.15533,725.80174 622.09385,725.71371 L 618.50389,729.45396 C 619.54873,730.57142 620.83437,731.36678 622.29873,731.44255 L 622.39639,725.88285 z " + id="path15633" + sodipodi:nodetypes="ccccc" /> + <path + style="fill:url(#linearGradient16391);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.49565426;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + d="M 622.71838,725.74981 C 622.80457,725.79915 622.88012,725.86514 622.94142,725.9523 L 626.70896,722.1443 C 625.66687,721.03769 624.2808,720.31512 622.81936,720.24095 L 622.71838,725.74981 z " + id="path15635" + sodipodi:nodetypes="ccccc" /> + </g> + <g + id="g17343" + transform="matrix(0.976101,0,0,0.976101,-176.7362,-592.44112)" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/32x32/actions/amarok_collection.png" + inkscape:export-xdpi="90.000092" + inkscape:export-ydpi="90.000092"> + <path + id="path15639" + d="M 662.76245,727.73012 C 663.19496,732.69605 667.5104,736.38057 672.39746,735.95494 C 677.28452,735.5293 680.90055,731.15399 680.46805,726.18806 C 680.03554,721.22213 675.71757,717.53784 670.8305,717.96347 C 665.94344,718.38911 662.32995,722.76419 662.76245,727.73012 z M 669.02224,727.18493 C 668.89098,725.67783 669.94473,724.35421 671.37624,724.22953 C 672.80776,724.10485 674.077,725.22615 674.20826,726.73326 C 674.33952,728.24036 673.28323,729.56416 671.85172,729.68883 C 670.4202,729.81351 669.1535,728.69199 669.02224,727.18493 z " + style="fill:url(#linearGradient17354);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient17356);stroke-width:1.02448475;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + sodipodi:nodetypes="ccccc" + id="path15641" + d="M 671.28216,727.04169 C 671.15153,726.96631 670.9177,726.91905 670.82474,726.78596 L 665.39716,732.44077 C 666.97683,734.13022 668.92056,735.33271 671.13449,735.44727 L 671.28216,727.04169 z " + style="fill:url(#linearGradient17358);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.4984282;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" /> + <path + sodipodi:nodetypes="ccccc" + id="path15643" + d="M 671.76895,726.84054 C 671.89927,726.91514 672.01349,727.01491 672.10616,727.14668 L 677.80222,721.38945 C 676.2267,719.7164 674.13114,718.62396 671.92163,718.51182 L 671.76895,726.84054 z " + style="fill:url(#linearGradient17361);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.49565426;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" /> + <path + id="path15645" + d="M 648.7792,727.73012 C 649.21171,732.69605 653.52715,736.38057 658.41421,735.95494 C 663.30127,735.5293 666.91731,731.15399 666.4848,726.18806 C 666.05229,721.22213 661.73432,717.53784 656.84726,717.96347 C 651.96019,718.38911 648.3467,722.76419 648.7792,727.73012 z M 655.03899,727.18493 C 654.90774,725.67783 655.96148,724.35421 657.393,724.22953 C 658.82451,724.10485 660.09375,725.22615 660.22502,726.73326 C 660.35627,728.24036 659.29999,729.56416 657.86847,729.68883 C 656.43695,729.81351 655.17025,728.69199 655.03899,727.18493 z " + style="fill:url(#linearGradient17364);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient17366);stroke-width:1.02448475;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + sodipodi:nodetypes="ccccc" + id="path15647" + d="M 657.29891,727.04169 C 657.16829,726.96631 656.93445,726.91905 656.8415,726.78596 L 651.41392,732.44077 C 652.99358,734.13022 654.93732,735.33271 657.15125,735.44727 L 657.29891,727.04169 z " + style="fill:url(#linearGradient17368);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" /> + <path + sodipodi:nodetypes="ccccc" + id="path15649" + d="M 657.7857,726.84054 C 657.91602,726.91514 658.03024,727.01491 658.12292,727.14668 L 663.81897,721.38945 C 662.24345,719.7164 660.1479,718.62396 657.93838,718.51182 L 657.7857,726.84054 z " + style="fill:url(#linearGradient17370);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" /> + <path + style="fill:url(#linearGradient17372);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient17374);stroke-width:1.02448475;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 655.87311,715.64493 C 656.30562,720.61086 660.62106,724.29538 665.50811,723.86974 C 670.39518,723.4441 674.01121,719.0688 673.57871,714.10287 C 673.1462,709.13694 668.82823,705.45264 663.94116,705.87828 C 659.0541,706.30391 655.4406,710.679 655.87311,715.64493 z M 662.13289,715.09974 C 662.00164,713.59263 663.05538,712.26901 664.4869,712.14434 C 665.91842,712.01966 667.18766,713.14096 667.31892,714.64806 C 667.45018,716.15517 666.39389,717.47896 664.96238,717.60364 C 663.53086,717.72832 662.26416,716.6068 662.13289,715.09974 z " + id="path15651" /> + <path + style="fill:url(#linearGradient17376);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + d="M 664.0279,715.27577 C 663.89729,715.2004 663.66344,715.15314 663.5705,715.02005 L 658.14291,720.67486 C 659.72258,722.36431 661.66631,723.5668 663.88025,723.68136 L 664.0279,715.27577 z " + id="path15653" + sodipodi:nodetypes="ccccc" /> + <path + style="fill:url(#linearGradient17378);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + d="M 665.1989,714.39042 C 665.32922,714.46502 665.44343,714.56479 665.53612,714.69656 L 671.23216,708.93933 C 669.65665,707.26628 667.5611,706.17384 665.35157,706.06171 L 665.1989,714.39042 z " + id="path15655" + sodipodi:nodetypes="ccccc" /> + </g> + <g + id="g17468" + transform="translate(-186.97849,-571.41534)" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/22x22/actions/amarok_album.png" + inkscape:export-xdpi="90.000092" + inkscape:export-ydpi="90.000092"> + <path + style="fill:url(#linearGradient17546);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient17548);stroke-width:1;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 616.6575,760.79031 C 617.15894,766.45746 622.16215,770.66226 627.82809,770.17652 C 633.49403,769.69078 637.68637,764.69766 637.18492,759.0305 C 636.68349,753.36335 631.67734,749.1588 626.0114,749.64454 C 620.34546,750.13028 616.15607,755.12315 616.6575,760.79031 z M 623.91494,760.16813 C 623.76276,758.44821 624.98445,756.93769 626.64411,756.79541 C 628.30378,756.65313 629.77531,757.93276 629.92749,759.65268 C 630.07967,761.3726 628.85503,762.88332 627.19537,763.0256 C 625.53571,763.16788 624.06712,761.888 623.91494,760.16813 z " + id="path15657" /> + <path + style="fill:url(#linearGradient17550);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + d="M 626.53503,760.00466 C 626.38359,759.91864 626.11249,759.86471 626.00473,759.71282 L 619.71212,766.16613 C 621.54355,768.09415 623.79706,769.46644 626.36384,769.59718 L 626.53503,760.00466 z " + id="path15659" + sodipodi:nodetypes="ccccc" /> + <path + style="fill:url(#linearGradient17552);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + d="M 627.09941,759.77511 C 627.2505,759.86025 627.38292,759.9741 627.49037,760.12448 L 634.09423,753.55429 C 632.26762,751.64499 629.83808,750.39829 627.27642,750.27032 L 627.09941,759.77511 z " + id="path15661" + sodipodi:nodetypes="ccccc" /> + </g> + <g + id="g17501" + transform="translate(-186.97849,-574.11652)" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/32x32/actions/amarok_album.png" + inkscape:export-xdpi="90.000092" + inkscape:export-ydpi="90.000092"> + <path + style="fill:url(#linearGradient17538);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient17540);stroke-width:1;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 649.09189,758.58427 C 649.8544,767.20227 657.46255,773.59647 666.07847,772.85781 C 674.6944,772.11915 681.06948,764.52614 680.30696,755.90815 C 679.54446,747.29015 671.93184,740.89633 663.31591,741.635 C 654.69999,742.37366 648.32938,749.96628 649.09189,758.58427 z M 660.12793,757.63813 C 659.89652,755.02267 661.75429,752.72563 664.27805,752.50926 C 666.80183,752.29289 669.03951,754.23882 669.27092,756.85429 C 669.50234,759.46975 667.6401,761.7671 665.11632,761.98346 C 662.59255,762.19983 660.35934,760.25352 660.12793,757.63813 z " + id="path15663" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_album.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + style="fill:url(#linearGradient17542);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.4984282;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + d="M 663.46884,757.94364 C 663.23856,757.81282 662.8263,757.7308 662.66244,757.49984 L 653.09356,767.31333 C 655.87853,770.24525 659.30534,772.33208 663.20852,772.53089 L 663.46884,757.94364 z " + id="path15665" + sodipodi:nodetypes="ccccc" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_album.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + style="fill:url(#linearGradient17544);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.49565426;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + d="M 665.53332,756.40717 C 665.76307,756.53664 665.96444,756.70977 666.12783,756.93846 L 676.17,746.94722 C 673.39236,744.04377 669.69789,742.14792 665.80249,741.95332 L 665.53332,756.40717 z " + id="path15667" + sodipodi:nodetypes="ccccc" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_album.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + </g> + <g + id="g18917" + transform="matrix(1.0203255,0,0,1.018446,-196.4765,-613.42094)" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/22x22/actions/amarok_repeat_album.png" + inkscape:export-xdpi="90.000092" + inkscape:export-ydpi="90.000092"> + <path + style="fill:url(#linearGradient18931);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient18933);stroke-width:0.9809832;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 634.4919,1015.6237 C 634.40371,1015.629 634.33043,1015.6473 634.2419,1015.6549 C 628.57597,1016.1406 624.39671,1021.144 624.89815,1026.8112 C 625.39956,1032.4783 630.38847,1036.6719 636.0544,1036.1862 C 641.72034,1035.7004 645.89959,1030.697 645.39815,1025.0299 C 644.90457,1019.4513 640.04808,1015.289 634.4919,1015.6237 z M 634.71065,1022.4362 C 634.85657,1022.4176 634.99721,1022.4362 635.14815,1022.4362 C 637.08017,1022.4361 638.64815,1024.0042 638.64815,1025.9362 C 638.64814,1027.8682 637.08016,1029.4362 635.14815,1029.4362 C 633.21615,1029.4362 631.64815,1027.8682 631.64815,1025.9362 C 631.64814,1024.1551 632.98878,1022.6552 634.71065,1022.4362 z " + id="path15700" /> + <path + style="fill:#0368ff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:3.75;stroke-miterlimit:4;stroke-dashoffset:0;stroke-opacity:1" + d="M 630.4283,1016.71 L 631.05714,1018.5572 C 630.3677,1018.9258 629.72325,1019.3802 629.17058,1019.9328 C 627.64127,1021.4621 626.65518,1023.6086 626.65518,1025.9462 C 626.65518,1028.2838 627.64553,1030.3953 629.17058,1031.9203 C 629.93098,1032.6807 630.86328,1033.2969 631.88251,1033.7283 C 632.40189,1033.948 632.92034,1034.0905 633.45464,1034.1999 L 632.78648,1033.1387 L 633.80837,1032.3133 C 633.36419,1032.2224 632.96307,1032.1175 632.58997,1031.9596 C 631.80878,1031.629 631.13556,1031.134 630.5462,1030.5447 C 629.36749,1029.366 628.65964,1027.7531 628.65964,1025.9462 C 628.65964,1024.1394 629.36749,1022.5264 630.5462,1021.3477 C 630.90349,1020.9905 631.30099,1020.6768 631.72529,1020.4045 L 632.31484,1022.0945 L 636.36307,1018.4393 L 630.4283,1016.71 z M 636.83471,1017.6532 L 637.58147,1018.8716 L 636.48099,1019.5398 C 636.89084,1019.6237 637.29523,1019.7618 637.69939,1019.9328 C 638.48057,1020.2634 639.15381,1020.7584 639.74315,1021.3477 C 640.92186,1022.5265 641.6297,1024.1394 641.6297,1025.9462 C 641.6297,1027.7531 640.92186,1029.366 639.74315,1030.5447 C 639.41164,1030.8762 639.07084,1031.1893 638.68196,1031.4487 L 638.09241,1029.7586 L 634.04418,1033.4138 L 639.97897,1035.1432 L 639.31082,1033.2566 C 639.98589,1032.8832 640.62389,1032.4545 641.15806,1031.9203 C 642.68739,1030.391 643.63417,1028.2759 643.63417,1025.9462 C 643.63417,1023.6165 642.69165,1021.4664 641.15806,1019.9328 C 640.38063,1019.1554 639.4058,1018.5869 638.40684,1018.1642 C 637.91596,1017.9564 637.40056,1017.7691 636.83471,1017.6532 z " + id="path15702" /> + </g> + <g + id="g15704" + transform="matrix(0.9891985,0,0,0.9891985,22.764242,246.02515)" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/32x32/actions/amarok_repeat_album.png" + inkscape:export-xdpi="90.000092" + inkscape:export-ydpi="90.000092"> + <path + id="path15706" + d="M 454.3925,183.70001 C 455.15501,192.31801 462.76316,198.71221 471.37908,197.97355 C 479.99501,197.23489 486.37009,189.64188 485.60757,181.02388 C 484.84507,172.40589 477.23245,166.01207 468.61652,166.75073 C 460.0006,167.48939 453.62999,175.08202 454.3925,183.70001 z M 465.42854,182.75387 C 465.19713,180.13841 467.0549,177.84137 469.57866,177.625 C 472.10244,177.40863 474.34012,179.35456 474.57153,181.97003 C 474.80295,184.58549 472.94071,186.88283 470.41693,187.0992 C 467.89316,187.31557 465.65995,185.36926 465.42854,182.75387 z " + style="fill:url(#linearGradient16155);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient16157);stroke-width:1.01091945;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + sodipodi:nodetypes="ccccc" + id="path15708" + d="M 468.76941,183.05917 C 468.53913,182.92836 468.12687,182.84634 467.96301,182.61537 L 458.39413,192.42887 C 461.1791,195.36078 464.60591,197.44761 468.50909,197.64642 L 468.76941,183.05917 z " + style="fill:url(#linearGradient16159);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.4984282;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" /> + <path + sodipodi:nodetypes="ccccc" + id="path15710" + d="M 470.83389,181.52271 C 471.06364,181.65218 471.26501,181.82531 471.4284,182.054 L 481.47057,172.06276 C 478.69293,169.1593 474.99846,167.26346 471.10306,167.06886 L 470.83389,181.52271 z " + style="fill:url(#linearGradient16161);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.49565426;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" /> + <path + id="path15712" + d="M 463.13964,168.95567 L 464.05434,171.64264 C 463.0515,172.17874 462.1141,172.8397 461.3102,173.64358 C 459.0857,175.86808 457.65136,178.99032 457.65136,182.39053 C 457.65136,185.79073 459.0919,188.86199 461.3102,191.0803 C 462.41626,192.18636 463.77236,193.08268 465.2549,193.7101 C 466.01039,194.0298 466.76451,194.23696 467.54169,194.39614 L 466.56981,192.85256 L 468.05623,191.652 C 467.41013,191.51966 466.82667,191.36714 466.28397,191.13746 C 465.14766,190.65659 464.1684,189.93661 463.31114,189.07935 C 461.59662,187.36483 460.567,185.01877 460.567,182.39053 C 460.567,179.7623 461.59662,177.4162 463.31114,175.70168 C 463.83084,175.18198 464.40904,174.72574 465.02622,174.32962 L 465.88377,176.7879 L 471.77223,171.47114 L 463.13964,168.95567 z M 472.45827,170.32773 L 473.54449,172.1 L 471.94375,173.07188 C 472.53991,173.19398 473.12813,173.3948 473.71601,173.64358 C 474.85229,174.12444 475.83158,174.84444 476.68882,175.70168 C 478.40334,177.41622 479.43296,179.7623 479.43296,182.39053 C 479.43296,185.01877 478.40334,187.36483 476.68882,189.07935 C 476.20662,189.56155 475.71089,190.01705 475.14523,190.39425 L 474.28769,187.93597 L 468.39923,193.25274 L 477.03184,195.7682 L 476.05996,193.02406 C 477.0419,192.48096 477.96992,191.85732 478.74692,191.0803 C 480.97144,188.85579 482.3486,185.77923 482.3486,182.39053 C 482.3486,179.00182 480.97764,175.87428 478.74692,173.64358 C 477.61608,172.51272 476.19812,171.6859 474.74505,171.07096 C 474.03103,170.76877 473.28133,170.49631 472.45827,170.32773 z " + style="fill:#0368ff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:3.75;stroke-miterlimit:4;stroke-dashoffset:0;stroke-opacity:1" /> + </g> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/22x22/actions/amarok_repeat_no.png" + id="path15744" + d="M 451.77412,496.9984 L 452.55189,499.1029 C 451.69918,499.5228 450.90211,500.0405 450.21855,500.67 C 448.32706,502.4124 447.10745,504.8577 447.10745,507.5208 C 447.10745,510.184 448.33233,512.5894 450.21855,514.3267 C 451.15902,515.1931 452.31212,515.8951 453.57273,516.3865 C 454.2151,516.6368 454.85635,516.7992 455.51718,516.9239 L 454.69079,515.7148 L 455.95468,514.7745 C 455.4053,514.6709 454.9092,514.5515 454.44774,514.3715 C 453.48154,513.9949 452.64887,513.431 451.91995,512.7597 C 450.46209,511.4167 449.58661,509.5793 449.58661,507.5208 C 449.58661,505.4623 450.46209,503.6248 451.91995,502.282 C 452.36184,501.8749 452.85349,501.5176 453.37827,501.2073 L 454.10744,503.1328 L 459.1144,498.9686 L 451.77412,496.9984 z M 459.69772,498.073 L 460.62134,499.4611 L 459.26024,500.2223 C 459.76714,500.3179 460.26731,500.4752 460.76718,500.67 C 461.73337,501.0467 462.56604,501.6106 463.29496,502.282 C 464.7528,503.6248 465.62829,505.4623 465.62829,507.5208 C 465.62829,509.5793 464.7528,511.4167 463.29496,512.7597 C 462.88495,513.1373 462.46342,513.494 461.98246,513.7895 L 461.25328,511.8641 L 456.24634,516.0283 L 463.58663,517.9984 L 462.76024,515.8492 C 463.59518,515.4238 464.38428,514.9353 465.04495,514.3267 C 466.93646,512.5845 468.10745,510.1749 468.10745,507.5208 C 468.10745,504.8667 466.94173,502.4172 465.04495,500.67 C 464.0834,499.7843 462.87772,499.1367 461.64218,498.6551 C 461.03504,498.4184 460.39758,498.205 459.69772,498.073 z " + style="fill:url(#linearGradient16189);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient16191);stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/48x48/actions/amarok_random.png" + transform="matrix(2.4072159,0,0,2.1903206,-532.93079,541.18862)" + id="g15746"> + <path + sodipodi:nodetypes="ccccccc" + id="path15748" + d="M 441.06383,60.713305 L 441.06542,49.495395 L 450.43446,45.740673 L 459.81147,49.492971 L 459.81139,60.717581 L 450.43626,66.344952 L 441.06383,60.713305 z " + style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient16193);stroke-width:1.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <g + id="g15750"> + <path + sodipodi:nodetypes="ccccc" + id="path15752" + d="M 441.68929,60.362282 L 441.68599,49.920973 L 450.4374,54.20208 L 450.43841,65.617769 L 441.68929,60.362282 z " + style="fill:url(#linearGradient16195);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.0742841;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + sodipodi:nodetypes="ccccc" + id="path15754" + d="M 450.43385,54.203364 L 459.18738,49.922686 L 459.18632,60.363888 L 450.44184,65.613128 L 450.43385,54.203364 z " + style="fill:url(#linearGradient16197);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.07509746;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + sodipodi:nodetypes="ccccc" + id="path15756" + d="M 441.688,49.920202 L 450.43621,46.412453 L 459.18586,49.920584 L 450.43299,54.201471 L 441.688,49.920202 z " + style="fill:url(#linearGradient16199);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.79999995;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + <path + sodipodi:type="arc" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path15758" + sodipodi:cx="1749.9153" + sodipodi:cy="1631.8588" + sodipodi:rx="13.21095" + sodipodi:ry="22.49432" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + transform="matrix(0.129577,-2.161349e-2,2.662017e-2,3.635072e-2,180.263,31.06197)" /> + <path + transform="matrix(0.108645,-1.714179e-2,2.231983e-2,2.883016e-2,223.9065,30.65984)" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + sodipodi:ry="22.49432" + sodipodi:rx="13.21095" + sodipodi:cy="1631.8588" + sodipodi:cx="1749.9153" + id="path15760" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + sodipodi:type="arc" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path15762" + sodipodi:cx="1749.9153" + sodipodi:cy="1631.8588" + sodipodi:rx="13.21095" + sodipodi:ry="22.49432" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + transform="matrix(0.11443,-1.853676e-2,2.350834e-2,3.117637e-2,211.8228,31.4122)" /> + <path + transform="matrix(0.11443,-1.853676e-2,2.350834e-2,3.117637e-2,206.4981,31.4122)" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + sodipodi:ry="22.49432" + sodipodi:rx="13.21095" + sodipodi:cy="1631.8588" + sodipodi:cx="1749.9153" + id="path15764" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + sodipodi:type="arc" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path15766" + sodipodi:cx="1749.9153" + sodipodi:cy="1631.8588" + sodipodi:rx="13.21095" + sodipodi:ry="22.49432" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + transform="matrix(0.11443,-1.853676e-2,2.350834e-2,3.117637e-2,217.0829,31.4122)" /> + <path + transform="matrix(9.309428e-2,-2.478274e-2,-3.935631e-3,7.164391e-2,295.9902,-17.92848)" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + sodipodi:ry="22.49432" + sodipodi:rx="13.21095" + sodipodi:cy="1631.8588" + sodipodi:cx="1749.9153" + id="path15768" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + sodipodi:type="arc" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path15770" + sodipodi:cx="1749.9153" + sodipodi:cy="1631.8588" + sodipodi:rx="13.21095" + sodipodi:ry="22.49432" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + transform="matrix(8.73537e-2,-2.437095e-2,-2.584881e-3,6.94881e-2,306.3905,-13.26718)" /> + <path + transform="matrix(8.170002e-2,-2.177155e-2,-3.529402e-3,6.291213e-2,320.3887,-5.30443)" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + sodipodi:ry="22.49432" + sodipodi:rx="13.21095" + sodipodi:cy="1631.8588" + sodipodi:cx="1749.9153" + id="path15772" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + sodipodi:type="arc" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path15774" + sodipodi:cx="1749.9153" + sodipodi:cy="1631.8588" + sodipodi:rx="13.21095" + sodipodi:ry="22.49432" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + transform="matrix(8.021497e-2,-3.444141e-2,2.833695e-2,6.672447e-2,259.4509,9.15401)" /> + </g> + <g + transform="matrix(1.0494026,0,0,1,-30.813668,471.92031)" + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/22x22/actions/amarok_editcopy.png" + id="g15798"> + <path + sodipodi:nodetypes="ccccccccc" + id="path15800" + d="M 467.40952,356.81279 L 477.66775,356.81279 C 478.74493,356.81279 479.61211,357.78674 479.61211,358.99651 L 479.61989,369.13266 L 476.34175,372.98901 L 467.40952,372.98901 C 466.33235,372.98901 465.46516,372.01506 465.46516,370.80528 L 465.46516,358.99651 C 465.46516,357.78674 466.33235,356.81279 467.40952,356.81279 z " + style="fill:url(#linearGradient16217);fill-opacity:1;stroke:url(#linearGradient16219);stroke-width:0.74211603;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_editcopy.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:nodetypes="ccccccccc" + id="path15802" + d="M 467.41413,357.1764 L 477.67093,357.1764 C 478.53023,357.1764 479.22202,357.98439 479.22202,358.98804 L 479.22202,369.71006 L 476.66623,372.57368 L 467.41413,372.6254 C 466.55482,372.6254 465.86302,371.81742 465.86302,370.81377 L 465.86302,358.98804 C 465.86302,357.98439 466.55482,357.1764 467.41413,357.1764 z " + style="fill:url(#linearGradient16221);fill-opacity:1;stroke:none;stroke-width:0.90379584;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_editcopy.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:nodetypes="ccccz" + id="path15804" + d="M 476.13488,370.48751 L 476.12869,373.36218 C 476.12869,373.36218 479.92907,368.874 479.92907,368.874 L 477.75567,368.81341 C 476.79482,368.82351 476.1369,369.5494 476.13488,370.48751 z " + style="fill:#0004c1;fill-opacity:1;stroke:none;stroke-width:1.0039922;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_editcopy.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + style="fill:url(#linearGradient16223);fill-opacity:1;stroke:url(#linearGradient16225);stroke-width:0.74211603;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 461.32442,351.74918 L 471.58265,351.74918 C 472.65983,351.74918 473.52701,352.72313 473.52701,353.9329 L 473.53479,364.06905 L 470.25665,367.9254 L 461.32442,367.9254 C 460.24725,367.9254 459.38006,366.95145 459.38006,365.74167 L 459.38006,353.9329 C 459.38006,352.72313 460.24725,351.74918 461.32442,351.74918 z " + id="path15806" + sodipodi:nodetypes="ccccccccc" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_editcopy.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + style="fill:url(#linearGradient16227);fill-opacity:1;stroke:none;stroke-width:0.90379584;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 461.32903,352.11279 L 471.58583,352.11279 C 472.44513,352.11279 473.13692,352.92078 473.13692,353.92443 L 473.13692,364.64645 L 470.58113,367.51007 L 461.32903,367.56179 C 460.46972,367.56179 459.77792,366.75381 459.77792,365.75016 L 459.77792,353.92443 C 459.77792,352.92078 460.46972,352.11279 461.32903,352.11279 z " + id="path15808" + sodipodi:nodetypes="ccccccccc" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_editcopy.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + style="fill:#0004c1;fill-opacity:1;stroke:none;stroke-width:1.0039922;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 470.04978,365.4239 L 470.04359,368.29857 C 470.04359,368.29857 473.93315,363.75068 473.93315,363.75068 L 471.67057,363.7498 C 470.70972,363.7599 470.0518,364.48579 470.04978,365.4239 z " + id="path15810" + sodipodi:nodetypes="ccccz" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_editcopy.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + </g> + <g + id="g15824" + transform="matrix(0.8024034,0,0,0.730105,55.386412,639.02331)" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/16x16/actions/amarok_random.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <path + style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient16239);stroke-width:1.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 441.06383,60.713305 L 441.06542,49.495395 L 450.43446,45.740673 L 459.81147,49.492971 L 459.81139,60.717581 L 450.43626,66.344952 L 441.06383,60.713305 z " + id="path15826" + sodipodi:nodetypes="ccccccc" /> + <g + id="g15828"> + <path + style="fill:url(#linearGradient16241);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.0742841;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 441.68929,60.362282 L 441.68599,49.920973 L 450.4374,54.20208 L 450.43841,65.617769 L 441.68929,60.362282 z " + id="path15830" + sodipodi:nodetypes="ccccc" /> + <path + style="fill:url(#linearGradient16243);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.07509746;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 450.43385,54.203364 L 459.18738,49.922686 L 459.18632,60.363888 L 450.44184,65.613128 L 450.43385,54.203364 z " + id="path15832" + sodipodi:nodetypes="ccccc" /> + <path + style="fill:url(#linearGradient16245);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.79999995;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 441.688,49.920202 L 450.43621,46.412453 L 459.18586,49.920584 L 450.43299,54.201471 L 441.688,49.920202 z " + id="path15834" + sodipodi:nodetypes="ccccc" /> + </g> + <path + transform="matrix(0.129577,-2.161349e-2,2.662017e-2,3.635072e-2,180.263,31.06197)" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + sodipodi:ry="22.49432" + sodipodi:rx="13.21095" + sodipodi:cy="1631.8588" + sodipodi:cx="1749.9153" + id="path15836" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + sodipodi:type="arc" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path15838" + sodipodi:cx="1749.9153" + sodipodi:cy="1631.8588" + sodipodi:rx="13.21095" + sodipodi:ry="22.49432" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + transform="matrix(0.108645,-1.714179e-2,2.231983e-2,2.883016e-2,223.9065,30.65984)" /> + <path + transform="matrix(0.11443,-1.853676e-2,2.350834e-2,3.117637e-2,211.8228,31.4122)" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + sodipodi:ry="22.49432" + sodipodi:rx="13.21095" + sodipodi:cy="1631.8588" + sodipodi:cx="1749.9153" + id="path15840" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + sodipodi:type="arc" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path15842" + sodipodi:cx="1749.9153" + sodipodi:cy="1631.8588" + sodipodi:rx="13.21095" + sodipodi:ry="22.49432" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + transform="matrix(0.11443,-1.853676e-2,2.350834e-2,3.117637e-2,206.4981,31.4122)" /> + <path + transform="matrix(0.11443,-1.853676e-2,2.350834e-2,3.117637e-2,217.0829,31.4122)" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + sodipodi:ry="22.49432" + sodipodi:rx="13.21095" + sodipodi:cy="1631.8588" + sodipodi:cx="1749.9153" + id="path15844" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + sodipodi:type="arc" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path15846" + sodipodi:cx="1749.9153" + sodipodi:cy="1631.8588" + sodipodi:rx="13.21095" + sodipodi:ry="22.49432" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + transform="matrix(9.309428e-2,-2.478274e-2,-3.935631e-3,7.164391e-2,295.9902,-17.92848)" /> + <path + transform="matrix(8.73537e-2,-2.437095e-2,-2.584881e-3,6.94881e-2,306.3905,-13.26718)" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + sodipodi:ry="22.49432" + sodipodi:rx="13.21095" + sodipodi:cy="1631.8588" + sodipodi:cx="1749.9153" + id="path15848" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + sodipodi:type="arc" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path15850" + sodipodi:cx="1749.9153" + sodipodi:cy="1631.8588" + sodipodi:rx="13.21095" + sodipodi:ry="22.49432" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + transform="matrix(8.170002e-2,-2.177155e-2,-3.529402e-3,6.291213e-2,320.3887,-5.30443)" /> + <path + transform="matrix(8.021497e-2,-3.444141e-2,2.833695e-2,6.672447e-2,259.4509,9.15401)" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + sodipodi:ry="22.49432" + sodipodi:rx="13.21095" + sodipodi:cy="1631.8588" + sodipodi:cx="1749.9153" + id="path15852" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + </g> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/32x32/actions/amarok_repeat_no.png" + id="path15893" + d="M 488.99799,493.3518 L 490.13686,496.4334 C 488.88824,497.0483 487.72111,497.8063 486.72019,498.7282 C 483.95051,501.2793 482.16465,504.8601 482.16465,508.7596 C 482.16465,512.6592 483.95823,516.1815 486.72019,518.7255 C 488.09732,519.9939 489.78577,521.0219 491.63165,521.7415 C 492.57229,522.1082 493.51123,522.3457 494.47888,522.5283 L 493.26882,520.758 L 495.11952,519.3812 C 494.31508,519.2293 493.58863,519.0544 492.91292,518.791 C 491.49813,518.2396 490.27887,517.4139 489.21152,516.4307 C 487.07681,514.4644 485.79485,511.7738 485.79485,508.7596 C 485.79485,505.7455 487.07681,503.0549 489.21152,501.0886 C 489.85858,500.4925 490.57849,499.9693 491.34693,499.515 L 492.41464,502.3343 L 499.74623,496.2367 L 488.99799,493.3518 z M 500.60041,494.9254 L 501.95284,496.958 L 499.95979,498.0725 C 500.70206,498.2126 501.43443,498.4429 502.16639,498.7282 C 503.58115,499.2797 504.80044,500.1054 505.86777,501.0886 C 508.00248,503.0549 509.28444,505.7455 509.28444,508.7596 C 509.28444,511.7738 508.00248,514.4644 505.86777,516.4307 C 505.2674,516.9837 504.65017,517.5061 503.94588,517.9387 L 502.87818,515.1194 L 495.54659,521.2169 L 506.29486,524.1018 L 505.08479,520.9547 C 506.30738,520.3318 507.46284,519.6166 508.43027,518.7255 C 511.19997,516.1744 512.91464,512.646 512.91464,508.7596 C 512.91464,504.8733 511.20769,501.2864 508.43027,498.7282 C 507.02228,497.4313 505.25681,496.4831 503.44763,495.7778 C 502.55862,495.4312 501.62518,495.1188 500.60041,494.9254 z " + style="fill:url(#linearGradient16259);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient16261);stroke-width:1.24999988;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + <g + id="g15937" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/32x32/amarok_editcopy.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" + transform="matrix(1.5386371,0,0,1.462902,-223.55503,299.09716)"> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_editcopy.png" + style="fill:url(#linearGradient16279);fill-opacity:1;stroke:url(#linearGradient16281);stroke-width:0.5096314;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 467.40952,356.81279 L 477.66775,356.81279 C 478.74493,356.81279 479.61211,357.78674 479.61211,358.99651 L 479.61989,369.13266 L 476.34175,372.98901 L 467.40952,372.98901 C 466.33235,372.98901 465.46516,372.01506 465.46516,370.80528 L 465.46516,358.99651 C 465.46516,357.78674 466.33235,356.81279 467.40952,356.81279 z " + id="path15939" + sodipodi:nodetypes="ccccccccc" /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_editcopy.png" + style="fill:url(#linearGradient16283);fill-opacity:1;stroke:none;stroke-width:0.90379584;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 467.41413,357.1764 L 477.67093,357.1764 C 478.53023,357.1764 479.22202,357.98439 479.22202,358.98804 L 479.22202,369.71006 L 476.66623,372.57368 L 467.41413,372.6254 C 466.55482,372.6254 465.86302,371.81742 465.86302,370.81377 L 465.86302,358.98804 C 465.86302,357.98439 466.55482,357.1764 467.41413,357.1764 z " + id="path15941" + sodipodi:nodetypes="ccccccccc" /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_editcopy.png" + style="fill:#0004c1;fill-opacity:1;stroke:none;stroke-width:1.0039922;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 476.13488,370.48751 L 476.12869,373.36218 C 476.12869,373.36218 479.92907,368.874 479.92907,368.874 L 477.75567,368.81341 C 476.79482,368.82351 476.1369,369.5494 476.13488,370.48751 z " + id="path15943" + sodipodi:nodetypes="ccccz" /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_editcopy.png" + sodipodi:nodetypes="ccccccccc" + id="path15945" + d="M 461.32442,351.74918 L 471.58265,351.74918 C 472.65983,351.74918 473.52701,352.72313 473.52701,353.9329 L 473.53479,364.06905 L 470.25665,367.9254 L 461.32442,367.9254 C 460.24725,367.9254 459.38006,366.95145 459.38006,365.74167 L 459.38006,353.9329 C 459.38006,352.72313 460.24725,351.74918 461.32442,351.74918 z " + style="fill:url(#linearGradient16285);fill-opacity:1;stroke:url(#linearGradient16287);stroke-width:0.5096314;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_editcopy.png" + sodipodi:nodetypes="ccccccccc" + id="path15947" + d="M 461.32903,352.11279 L 471.58583,352.11279 C 472.44513,352.11279 473.13692,352.92078 473.13692,353.92443 L 473.13692,364.64645 L 470.58113,367.51007 L 461.32903,367.56179 C 460.46972,367.56179 459.77792,366.75381 459.77792,365.75016 L 459.77792,353.92443 C 459.77792,352.92078 460.46972,352.11279 461.32903,352.11279 z " + style="fill:url(#linearGradient16289);fill-opacity:1;stroke:none;stroke-width:0.90379584;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_editcopy.png" + sodipodi:nodetypes="ccccz" + id="path15949" + d="M 470.04978,365.4239 L 470.04359,368.29857 C 470.04359,368.29857 473.93315,363.75068 473.93315,363.75068 L 471.67057,363.7498 C 470.70972,363.7599 470.0518,364.48579 470.04978,365.4239 z " + style="fill:#0004c1;fill-opacity:1;stroke:none;stroke-width:1.0039922;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" /> + </g> + <g + transform="matrix(0.723593,0,0,0.723593,-45.869418,-403.46781)" + id="g16393" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/16x16/actions/amarok_collection.png" + inkscape:export-xdpi="90.000092" + inkscape:export-ydpi="90.000092"> + <path + id="path16395" + d="M 626.01011,726.3382 C 626.29619,729.62281 629.15054,732.05986 632.38299,731.77834 C 635.61544,731.49681 638.00719,728.60285 637.72111,725.31824 C 637.43504,722.03363 634.57901,719.59673 631.34656,719.87825 C 628.11411,720.15978 625.72404,723.05359 626.01011,726.3382 z M 630.15052,725.9776 C 630.0637,724.98075 630.76068,724.10527 631.70753,724.02281 C 632.65438,723.94034 633.49389,724.682 633.58071,725.67885 C 633.66753,726.67569 632.96887,727.55129 632.02202,727.63375 C 631.07517,727.71622 630.23734,726.97441 630.15052,725.9776 z " + style="fill:url(#linearGradient16413);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient16415);stroke-width:1.10559309;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + sodipodi:nodetypes="ccccc" + id="path16397" + d="M 631.64529,725.88285 C 631.5589,725.833 631.40423,725.80174 631.34275,725.71371 L 627.75279,729.45396 C 628.79763,730.57142 630.08327,731.36678 631.54763,731.44255 L 631.64529,725.88285 z " + style="fill:url(#linearGradient16417);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.4984282;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" /> + <path + sodipodi:nodetypes="ccccc" + id="path16399" + d="M 631.96728,725.74981 C 632.05347,725.79915 632.12902,725.86514 632.19032,725.9523 L 635.95786,722.1443 C 634.91577,721.03769 633.5297,720.31512 632.06826,720.24095 L 631.96728,725.74981 z " + style="fill:url(#linearGradient16419);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.49565426;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" /> + <path + style="fill:url(#linearGradient16421);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient16423);stroke-width:1.38199234;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 621.45331,718.3447 C 621.73939,721.62931 624.59374,724.06636 627.82619,723.78484 C 631.05864,723.50331 633.45039,720.60935 633.16431,717.32474 C 632.87824,714.04013 630.02221,711.60323 626.78976,711.88475 C 623.55731,712.16628 621.16724,715.06009 621.45331,718.3447 z M 625.59372,717.9841 C 625.5069,716.98725 626.20388,716.11177 627.15073,716.02931 C 628.09758,715.94684 628.93709,716.6885 629.02391,717.68535 C 629.11073,718.68219 628.41207,719.55779 627.46522,719.64025 C 626.51837,719.72272 625.68054,718.98091 625.59372,717.9841 z " + id="path16401" /> + <path + style="fill:url(#linearGradient16425);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.4984282;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + d="M 627.08849,717.88935 C 627.0021,717.8395 626.84743,717.80824 626.78595,717.72021 L 623.19599,721.46046 C 624.24083,722.57792 625.52647,723.37328 626.99083,723.44905 L 627.08849,717.88935 z " + id="path16403" + sodipodi:nodetypes="ccccc" /> + <path + style="fill:url(#linearGradient16427);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.49565426;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + d="M 627.41048,717.75631 C 627.49667,717.80565 627.57222,717.87164 627.63352,717.9588 L 631.40106,714.1508 C 630.35897,713.04419 628.9729,712.32162 627.51146,712.24745 L 627.41048,717.75631 z " + id="path16405" + sodipodi:nodetypes="ccccc" /> + <path + id="path16407" + d="M 616.76121,726.3382 C 617.04729,729.62281 619.90164,732.05986 623.13409,731.77834 C 626.36654,731.49681 628.75829,728.60285 628.47221,725.31824 C 628.18614,722.03363 625.33011,719.59673 622.09766,719.87825 C 618.86521,720.15978 616.47514,723.05359 616.76121,726.3382 z M 620.90162,725.9776 C 620.8148,724.98075 621.51178,724.10527 622.45863,724.02281 C 623.40548,723.94034 624.24499,724.682 624.33181,725.67885 C 624.41863,726.67569 623.71997,727.55129 622.77312,727.63375 C 621.82627,727.71622 620.98844,726.97441 620.90162,725.9776 z " + style="fill:url(#linearGradient16429);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient16431);stroke-width:1.10559309;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + sodipodi:nodetypes="ccccc" + id="path16409" + d="M 622.39639,725.88285 C 622.31,725.833 622.15533,725.80174 622.09385,725.71371 L 618.50389,729.45396 C 619.54873,730.57142 620.83437,731.36678 622.29873,731.44255 L 622.39639,725.88285 z " + style="fill:url(#linearGradient16433);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.4984282;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" /> + <path + sodipodi:nodetypes="ccccc" + id="path16411" + d="M 622.71838,725.74981 C 622.80457,725.79915 622.88012,725.86514 622.94142,725.9523 L 626.70896,722.1443 C 625.66687,721.03769 624.2808,720.31512 622.81936,720.24095 L 622.71838,725.74981 z " + style="fill:url(#linearGradient16435);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.49565426;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" /> + </g> + <g + transform="matrix(1.479886,0,0,1.479886,-458.30822,-963.22101)" + id="g17380" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/48x48/actions/amarok_collection.png" + inkscape:export-xdpi="90.000092" + inkscape:export-ydpi="90.000092"> + <path + style="fill:url(#linearGradient17400);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient17402);stroke-width:0.6757282;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 662.76245,727.73012 C 663.19496,732.69605 667.5104,736.38057 672.39746,735.95494 C 677.28452,735.5293 680.90055,731.15399 680.46805,726.18806 C 680.03554,721.22213 675.71757,717.53784 670.8305,717.96347 C 665.94344,718.38911 662.32995,722.76419 662.76245,727.73012 z M 669.02224,727.18493 C 668.89098,725.67783 669.94473,724.35421 671.37624,724.22953 C 672.80776,724.10485 674.077,725.22615 674.20826,726.73326 C 674.33952,728.24036 673.28323,729.56416 671.85172,729.68883 C 670.4202,729.81351 669.1535,728.69199 669.02224,727.18493 z " + id="path17382" /> + <path + style="fill:url(#linearGradient17404);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.4984282;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + d="M 671.28216,727.04169 C 671.15153,726.96631 670.9177,726.91905 670.82474,726.78596 L 665.39716,732.44077 C 666.97683,734.13022 668.92056,735.33271 671.13449,735.44727 L 671.28216,727.04169 z " + id="path17384" + sodipodi:nodetypes="ccccc" /> + <path + style="fill:url(#linearGradient17406);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.49565426;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + d="M 671.76895,726.84054 C 671.89927,726.91514 672.01349,727.01491 672.10616,727.14668 L 677.80222,721.38945 C 676.2267,719.7164 674.13114,718.62396 671.92163,718.51182 L 671.76895,726.84054 z " + id="path17386" + sodipodi:nodetypes="ccccc" /> + <path + style="fill:url(#linearGradient17408);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient17410);stroke-width:0.6757282;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 648.7792,727.73012 C 649.21171,732.69605 653.52715,736.38057 658.41421,735.95494 C 663.30127,735.5293 666.91731,731.15399 666.4848,726.18806 C 666.05229,721.22213 661.73432,717.53784 656.84726,717.96347 C 651.96019,718.38911 648.3467,722.76419 648.7792,727.73012 z M 655.03899,727.18493 C 654.90774,725.67783 655.96148,724.35421 657.393,724.22953 C 658.82451,724.10485 660.09375,725.22615 660.22502,726.73326 C 660.35627,728.24036 659.29999,729.56416 657.86847,729.68883 C 656.43695,729.81351 655.17025,728.69199 655.03899,727.18493 z " + id="path17388" /> + <path + style="fill:url(#linearGradient17412);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + d="M 657.29891,727.04169 C 657.16829,726.96631 656.93445,726.91905 656.8415,726.78596 L 651.41392,732.44077 C 652.99358,734.13022 654.93732,735.33271 657.15125,735.44727 L 657.29891,727.04169 z " + id="path17390" + sodipodi:nodetypes="ccccc" /> + <path + style="fill:url(#linearGradient17414);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + d="M 657.7857,726.84054 C 657.91602,726.91514 658.03024,727.01491 658.12292,727.14668 L 663.81897,721.38945 C 662.24345,719.7164 660.1479,718.62396 657.93838,718.51182 L 657.7857,726.84054 z " + id="path17392" + sodipodi:nodetypes="ccccc" /> + <path + id="path17394" + d="M 655.87311,715.64493 C 656.30562,720.61086 660.62106,724.29538 665.50811,723.86974 C 670.39518,723.4441 674.01121,719.0688 673.57871,714.10287 C 673.1462,709.13694 668.82823,705.45264 663.94116,705.87828 C 659.0541,706.30391 655.4406,710.679 655.87311,715.64493 z M 662.13289,715.09974 C 662.00164,713.59263 663.05538,712.26901 664.4869,712.14434 C 665.91842,712.01966 667.18766,713.14096 667.31892,714.64806 C 667.45018,716.15517 666.39389,717.47896 664.96238,717.60364 C 663.53086,717.72832 662.26416,716.6068 662.13289,715.09974 z " + style="fill:url(#linearGradient17416);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient17418);stroke-width:0.6757282;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + sodipodi:nodetypes="ccccc" + id="path17396" + d="M 664.0279,715.27577 C 663.89729,715.2004 663.66344,715.15314 663.5705,715.02005 L 658.14291,720.67486 C 659.72258,722.36431 661.66631,723.5668 663.88025,723.68136 L 664.0279,715.27577 z " + style="fill:url(#linearGradient17420);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" /> + <path + sodipodi:nodetypes="ccccc" + id="path17398" + d="M 665.1989,714.39042 C 665.32922,714.46502 665.44343,714.56479 665.53612,714.69656 L 671.23216,708.93933 C 669.65665,707.26628 667.5611,706.17384 665.35157,706.06171 L 665.1989,714.39042 z " + style="fill:url(#linearGradient17422);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" /> + </g> + <g + id="g17424" + transform="matrix(1.983684,0,0,1.983684,-708.97462,-1334.0105)" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/64x64/actions/amarok_collection.png" + inkscape:export-xdpi="90.000092" + inkscape:export-ydpi="90.000092"> + <path + id="path17426" + d="M 662.76245,727.73012 C 663.19496,732.69605 667.5104,736.38057 672.39746,735.95494 C 677.28452,735.5293 680.90055,731.15399 680.46805,726.18806 C 680.03554,721.22213 675.71757,717.53784 670.8305,717.96347 C 665.94344,718.38911 662.32995,722.76419 662.76245,727.73012 z M 669.02224,727.18493 C 668.89098,725.67783 669.94473,724.35421 671.37624,724.22953 C 672.80776,724.10485 674.077,725.22615 674.20826,726.73326 C 674.33952,728.24036 673.28323,729.56416 671.85172,729.68883 C 670.4202,729.81351 669.1535,728.69199 669.02224,727.18493 z " + style="fill:url(#linearGradient17444);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient17446);stroke-width:0.50411302;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + sodipodi:nodetypes="ccccc" + id="path17428" + d="M 671.28216,727.04169 C 671.15153,726.96631 670.9177,726.91905 670.82474,726.78596 L 665.39716,732.44077 C 666.97683,734.13022 668.92056,735.33271 671.13449,735.44727 L 671.28216,727.04169 z " + style="fill:url(#linearGradient17448);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.4984282;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" /> + <path + sodipodi:nodetypes="ccccc" + id="path17430" + d="M 671.76895,726.84054 C 671.89927,726.91514 672.01349,727.01491 672.10616,727.14668 L 677.80222,721.38945 C 676.2267,719.7164 674.13114,718.62396 671.92163,718.51182 L 671.76895,726.84054 z " + style="fill:url(#linearGradient17450);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.49565426;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" /> + <path + id="path17432" + d="M 648.7792,727.73012 C 649.21171,732.69605 653.52715,736.38057 658.41421,735.95494 C 663.30127,735.5293 666.91731,731.15399 666.4848,726.18806 C 666.05229,721.22213 661.73432,717.53784 656.84726,717.96347 C 651.96019,718.38911 648.3467,722.76419 648.7792,727.73012 z M 655.03899,727.18493 C 654.90774,725.67783 655.96148,724.35421 657.393,724.22953 C 658.82451,724.10485 660.09375,725.22615 660.22502,726.73326 C 660.35627,728.24036 659.29999,729.56416 657.86847,729.68883 C 656.43695,729.81351 655.17025,728.69199 655.03899,727.18493 z " + style="fill:url(#linearGradient17452);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient17454);stroke-width:0.50411302;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + sodipodi:nodetypes="ccccc" + id="path17434" + d="M 657.29891,727.04169 C 657.16829,726.96631 656.93445,726.91905 656.8415,726.78596 L 651.41392,732.44077 C 652.99358,734.13022 654.93732,735.33271 657.15125,735.44727 L 657.29891,727.04169 z " + style="fill:url(#linearGradient17456);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" /> + <path + sodipodi:nodetypes="ccccc" + id="path17436" + d="M 657.7857,726.84054 C 657.91602,726.91514 658.03024,727.01491 658.12292,727.14668 L 663.81897,721.38945 C 662.24345,719.7164 660.1479,718.62396 657.93838,718.51182 L 657.7857,726.84054 z " + style="fill:url(#linearGradient17458);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" /> + <path + style="fill:url(#linearGradient17460);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient17462);stroke-width:0.50411302;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 655.87311,715.64493 C 656.30562,720.61086 660.62106,724.29538 665.50811,723.86974 C 670.39518,723.4441 674.01121,719.0688 673.57871,714.10287 C 673.1462,709.13694 668.82823,705.45264 663.94116,705.87828 C 659.0541,706.30391 655.4406,710.679 655.87311,715.64493 z M 662.13289,715.09974 C 662.00164,713.59263 663.05538,712.26901 664.4869,712.14434 C 665.91842,712.01966 667.18766,713.14096 667.31892,714.64806 C 667.45018,716.15517 666.39389,717.47896 664.96238,717.60364 C 663.53086,717.72832 662.26416,716.6068 662.13289,715.09974 z " + id="path17438" /> + <path + style="fill:url(#linearGradient17464);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + d="M 664.0279,715.27577 C 663.89729,715.2004 663.66344,715.15314 663.5705,715.02005 L 658.14291,720.67486 C 659.72258,722.36431 661.66631,723.5668 663.88025,723.68136 L 664.0279,715.27577 z " + id="path17440" + sodipodi:nodetypes="ccccc" /> + <path + style="fill:url(#linearGradient17466);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + d="M 665.1989,714.39042 C 665.32922,714.46502 665.44343,714.56479 665.53612,714.69656 L 671.23216,708.93933 C 669.65665,707.26628 667.5611,706.17384 665.35157,706.06171 L 665.1989,714.39042 z " + id="path17442" + sodipodi:nodetypes="ccccc" /> + </g> + <g + id="g17473" + transform="matrix(0.727847,0,0,0.727847,-46.302684,-361.79911)" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/16x16/actions/amarok_album.png" + inkscape:export-xdpi="90.000092" + inkscape:export-ydpi="90.000092"> + <path + id="path17475" + d="M 616.6575,760.79031 C 617.15894,766.45746 622.16215,770.66226 627.82809,770.17652 C 633.49403,769.69078 637.68637,764.69766 637.18492,759.0305 C 636.68349,753.36335 631.67734,749.1588 626.0114,749.64454 C 620.34546,750.13028 616.15607,755.12315 616.6575,760.79031 z M 623.91494,760.16813 C 623.76276,758.44821 624.98445,756.93769 626.64411,756.79541 C 628.30378,756.65313 629.77531,757.93276 629.92749,759.65268 C 630.07967,761.3726 628.85503,762.88332 627.19537,763.0256 C 625.53571,763.16788 624.06712,761.888 623.91494,760.16813 z " + style="fill:url(#linearGradient17481);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient17483);stroke-width:1.37391508;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + sodipodi:nodetypes="ccccc" + id="path17477" + d="M 626.53503,760.00466 C 626.38359,759.91864 626.11249,759.86471 626.00473,759.71282 L 619.71212,766.16613 C 621.54355,768.09415 623.79706,769.46644 626.36384,769.59718 L 626.53503,760.00466 z " + style="fill:url(#linearGradient17485);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" /> + <path + sodipodi:nodetypes="ccccc" + id="path17479" + d="M 627.09941,759.77511 C 627.2505,759.86025 627.38292,759.9741 627.49037,760.12448 L 634.09423,753.55429 C 632.26762,751.64499 629.83808,750.39829 627.27642,750.27032 L 627.09941,759.77511 z " + style="fill:url(#linearGradient17487);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" /> + </g> + <g + id="g17506" + transform="matrix(1.49973,0,0,1.49973,-465.06769,-960.36571)" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/48x48/actions/amarok_album.png" + inkscape:export-xdpi="90.000092" + inkscape:export-ydpi="90.000092"> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_album.png" + id="path17508" + d="M 649.09189,758.58427 C 649.8544,767.20227 657.46255,773.59647 666.07847,772.85781 C 674.6944,772.11915 681.06948,764.52614 680.30696,755.90815 C 679.54446,747.29015 671.93184,740.89633 663.31591,741.635 C 654.69999,742.37366 648.32938,749.96628 649.09189,758.58427 z M 660.12793,757.63813 C 659.89652,755.02267 661.75429,752.72563 664.27805,752.50926 C 666.80183,752.29289 669.03951,754.23882 669.27092,756.85429 C 669.50234,759.46975 667.6401,761.7671 665.11632,761.98346 C 662.59255,762.19983 660.35934,760.25352 660.12793,757.63813 z " + style="fill:url(#linearGradient17514);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient17516);stroke-width:0.66678703;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_album.png" + sodipodi:nodetypes="ccccc" + id="path17510" + d="M 663.46884,757.94364 C 663.23856,757.81282 662.8263,757.7308 662.66244,757.49984 L 653.09356,767.31333 C 655.87853,770.24525 659.30534,772.33208 663.20852,772.53089 L 663.46884,757.94364 z " + style="fill:url(#linearGradient17518);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.4984282;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_album.png" + sodipodi:nodetypes="ccccc" + id="path17512" + d="M 665.53332,756.40717 C 665.76307,756.53664 665.96444,756.70977 666.12783,756.93846 L 676.17,746.94722 C 673.39236,744.04377 669.69789,742.14792 665.80249,741.95332 L 665.53332,756.40717 z " + style="fill:url(#linearGradient17520);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.49565426;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" /> + </g> + <g + transform="matrix(2.010293,0,0,2.010293,-726.12839,-1354.988)" + id="g17522" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/64x64/actions/amarok_album.png" + inkscape:export-xdpi="90.000092" + inkscape:export-ydpi="90.000092"> + <path + style="fill:url(#linearGradient17530);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient17532);stroke-width:0.49744025;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 649.09189,758.58427 C 649.8544,767.20227 657.46255,773.59647 666.07847,772.85781 C 674.6944,772.11915 681.06948,764.52614 680.30696,755.90815 C 679.54446,747.29015 671.93184,740.89633 663.31591,741.635 C 654.69999,742.37366 648.32938,749.96628 649.09189,758.58427 z M 660.12793,757.63813 C 659.89652,755.02267 661.75429,752.72563 664.27805,752.50926 C 666.80183,752.29289 669.03951,754.23882 669.27092,756.85429 C 669.50234,759.46975 667.6401,761.7671 665.11632,761.98346 C 662.59255,762.19983 660.35934,760.25352 660.12793,757.63813 z " + id="path17524" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_album.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + style="fill:url(#linearGradient17534);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.4984282;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + d="M 663.46884,757.94364 C 663.23856,757.81282 662.8263,757.7308 662.66244,757.49984 L 653.09356,767.31333 C 655.87853,770.24525 659.30534,772.33208 663.20852,772.53089 L 663.46884,757.94364 z " + id="path17526" + sodipodi:nodetypes="ccccc" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_album.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + style="fill:url(#linearGradient17536);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.49565426;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + d="M 665.53332,756.40717 C 665.76307,756.53664 665.96444,756.70977 666.12783,756.93846 L 676.17,746.94722 C 673.39236,744.04377 669.69789,742.14792 665.80249,741.95332 L 665.53332,756.40717 z " + id="path17528" + sodipodi:nodetypes="ccccc" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_album.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + </g> + <g + id="g12791" + transform="translate(7,-4.3911519)"> + <rect + inkscape:export-ydpi="90.000092" + inkscape:export-xdpi="90.000092" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/16x16/actions/amarok_random_album.png" + style="fill:#b9cff9;fill-opacity:0;stroke:none;stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + id="rect10375" + width="16" + height="16" + x="401.41187" + y="269.33914" /> + <g + inkscape:export-ydpi="90.000092" + inkscape:export-xdpi="90.000092" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/16x16/actions/amarok_random_album.png" + id="g17554" + transform="matrix(0.6875117,0,0,0.6875117,110.23751,203.88629)"> + <g + transform="matrix(0.703331,0,0,0.703331,122.6647,70.38444)" + id="g17556"> + <path + sodipodi:nodetypes="ccccccc" + id="path17558" + d="M 441.06383,60.713305 L 441.06542,49.495395 L 450.43446,45.740673 L 459.81147,49.492971 L 459.81139,60.717581 L 450.43626,66.344952 L 441.06383,60.713305 z " + style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient17588);stroke-width:2.06804752;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <g + id="g17560"> + <path + sodipodi:nodetypes="ccccc" + id="path17562" + d="M 441.68929,60.362282 L 441.68599,49.920973 L 450.4374,54.20208 L 450.43841,65.617769 L 441.68929,60.362282 z " + style="fill:url(#linearGradient17590);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.0742841;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + sodipodi:nodetypes="ccccc" + id="path17564" + d="M 450.43385,54.203364 L 459.18738,49.922686 L 459.18632,60.363888 L 450.44184,65.613128 L 450.43385,54.203364 z " + style="fill:url(#linearGradient17592);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.07509746;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + sodipodi:nodetypes="ccccc" + id="path17566" + d="M 441.688,49.920202 L 450.43621,46.412453 L 459.18586,49.920584 L 450.43299,54.201471 L 441.688,49.920202 z " + style="fill:url(#linearGradient17594);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.79999995;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + <path + sodipodi:type="arc" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path17568" + sodipodi:cx="1749.9153" + sodipodi:cy="1631.8588" + sodipodi:rx="13.21095" + sodipodi:ry="22.49432" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + transform="matrix(0.129577,-2.161349e-2,2.662017e-2,3.635072e-2,180.263,31.06197)" /> + <path + transform="matrix(0.108645,-1.714179e-2,2.231983e-2,2.883016e-2,223.9065,30.65984)" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + sodipodi:ry="22.49432" + sodipodi:rx="13.21095" + sodipodi:cy="1631.8588" + sodipodi:cx="1749.9153" + id="path17570" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + sodipodi:type="arc" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path17572" + sodipodi:cx="1749.9153" + sodipodi:cy="1631.8588" + sodipodi:rx="13.21095" + sodipodi:ry="22.49432" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + transform="matrix(0.11443,-1.853676e-2,2.350834e-2,3.117637e-2,211.8228,31.4122)" /> + <path + transform="matrix(0.11443,-1.853676e-2,2.350834e-2,3.117637e-2,206.4981,31.4122)" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + sodipodi:ry="22.49432" + sodipodi:rx="13.21095" + sodipodi:cy="1631.8588" + sodipodi:cx="1749.9153" + id="path17574" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + sodipodi:type="arc" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path17576" + sodipodi:cx="1749.9153" + sodipodi:cy="1631.8588" + sodipodi:rx="13.21095" + sodipodi:ry="22.49432" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + transform="matrix(0.11443,-1.853676e-2,2.350834e-2,3.117637e-2,217.0829,31.4122)" /> + <path + transform="matrix(9.309428e-2,-2.478274e-2,-3.935631e-3,7.164391e-2,295.9902,-17.92848)" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + sodipodi:ry="22.49432" + sodipodi:rx="13.21095" + sodipodi:cy="1631.8588" + sodipodi:cx="1749.9153" + id="path17578" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + sodipodi:type="arc" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path17580" + sodipodi:cx="1749.9153" + sodipodi:cy="1631.8588" + sodipodi:rx="13.21095" + sodipodi:ry="22.49432" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + transform="matrix(8.73537e-2,-2.437095e-2,-2.584881e-3,6.94881e-2,306.3905,-13.26718)" /> + <path + transform="matrix(8.170002e-2,-2.177155e-2,-3.529402e-3,6.291213e-2,320.3887,-5.30443)" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + sodipodi:ry="22.49432" + sodipodi:rx="13.21095" + sodipodi:cy="1631.8588" + sodipodi:cx="1749.9153" + id="path17582" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + sodipodi:type="arc" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path17584" + sodipodi:cx="1749.9153" + sodipodi:cy="1631.8588" + sodipodi:rx="13.21095" + sodipodi:ry="22.49432" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + transform="matrix(8.021497e-2,-3.444141e-2,2.833695e-2,6.672447e-2,259.4509,9.15401)" /> + </g> + <path + id="path17586" + d="M 424.27506,104.557 C 424.63045,108.57354 428.17642,111.55365 432.19211,111.20938 C 436.20779,110.86512 439.17907,107.32629 438.82368,103.30975 C 438.46829,99.293217 434.92023,96.313287 430.90454,96.65755 C 426.88886,97.001812 423.91967,100.54046 424.27506,104.557 z M 429.4187,104.11604 C 429.31084,102.89706 430.1767,101.8265 431.35297,101.72566 C 432.52925,101.62481 433.57218,102.53174 433.68004,103.75072 C 433.78789,104.96969 432.91994,106.0404 431.74367,106.14124 C 430.5674,106.24208 429.52655,105.33498 429.4187,104.11604 z " + style="fill:url(#linearGradient17596);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient17598);stroke-width:1.45452189;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + </g> + <g + id="g12853" + transform="translate(7,-19.80613)"> + <rect + inkscape:export-ydpi="90.000092" + inkscape:export-xdpi="90.000092" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/48x48/actions/amarok_random_album.png" + style="fill:#b9cff9;fill-opacity:0;stroke:none;stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + id="rect10381" + width="48" + height="48" + x="501.98773" + y="252.75412" /> + <g + inkscape:export-ydpi="90.000092" + inkscape:export-xdpi="90.000092" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/48x48/actions/amarok_random_album.png" + id="g18473" + transform="matrix(1.4594349,0,0,1.4594349,-159.52248,121.00608)"> + <g + id="g18475" + transform="translate(26,55)" + inkscape:export-filename="/home/lando/Projects/Amarok/Playback-modes/amarok_random_album.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <path + style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient18511);stroke-width:0.68519694;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 441.06383,60.713305 L 441.06542,49.495395 L 450.43446,45.740673 L 459.81147,49.492971 L 459.81139,60.717581 L 450.43626,66.344952 L 441.06383,60.713305 z " + id="path18477" + sodipodi:nodetypes="ccccccc" /> + <g + id="g18479"> + <path + style="fill:url(#linearGradient18513);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.0742841;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 441.38379,60.564696 L 441.68599,49.920973 L 450.4374,54.20208 L 450.44151,65.948761 L 441.38379,60.564696 z " + id="path18481" + sodipodi:nodetypes="ccccc" /> + <path + style="fill:url(#linearGradient18515);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.07509746;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 450.43385,54.203364 L 459.46957,49.708881 L 459.46866,60.526907 L 450.43605,65.950915 L 450.43385,54.203364 z " + id="path18483" + sodipodi:nodetypes="ccccc" /> + <path + style="fill:url(#linearGradient18517);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.79999995;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 441.48371,49.838485 L 450.47707,46.128322 L 459.46992,49.710517 L 450.43299,54.201471 L 441.48371,49.838485 z " + id="path18485" + sodipodi:nodetypes="ccccc" /> + </g> + <path + transform="matrix(0.129577,-2.161349e-2,2.662017e-2,3.635072e-2,180.263,31.06197)" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + sodipodi:ry="22.49432" + sodipodi:rx="13.21095" + sodipodi:cy="1631.8588" + sodipodi:cx="1749.9153" + id="path18487" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + sodipodi:type="arc" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path18489" + sodipodi:cx="1749.9153" + sodipodi:cy="1631.8588" + sodipodi:rx="13.21095" + sodipodi:ry="22.49432" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + transform="matrix(0.108645,-1.714179e-2,2.231983e-2,2.883016e-2,223.9065,30.65984)" /> + <path + transform="matrix(0.11443,-1.853676e-2,2.350834e-2,3.117637e-2,211.8228,31.4122)" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + sodipodi:ry="22.49432" + sodipodi:rx="13.21095" + sodipodi:cy="1631.8588" + sodipodi:cx="1749.9153" + id="path18491" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + sodipodi:type="arc" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path18493" + sodipodi:cx="1749.9153" + sodipodi:cy="1631.8588" + sodipodi:rx="13.21095" + sodipodi:ry="22.49432" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + transform="matrix(0.11443,-1.853676e-2,2.350834e-2,3.117637e-2,206.4981,31.4122)" /> + <path + transform="matrix(0.11443,-1.853676e-2,2.350834e-2,3.117637e-2,217.0829,31.4122)" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + sodipodi:ry="22.49432" + sodipodi:rx="13.21095" + sodipodi:cy="1631.8588" + sodipodi:cx="1749.9153" + id="path18495" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + sodipodi:type="arc" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path18497" + sodipodi:cx="1749.9153" + sodipodi:cy="1631.8588" + sodipodi:rx="13.21095" + sodipodi:ry="22.49432" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + transform="matrix(9.309428e-2,-2.478274e-2,-3.935631e-3,7.164391e-2,295.9902,-17.92848)" /> + <path + transform="matrix(8.73537e-2,-2.437095e-2,-2.584881e-3,6.94881e-2,306.3905,-13.26718)" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + sodipodi:ry="22.49432" + sodipodi:rx="13.21095" + sodipodi:cy="1631.8588" + sodipodi:cx="1749.9153" + id="path18499" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + sodipodi:type="arc" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path18501" + sodipodi:cx="1749.9153" + sodipodi:cy="1631.8588" + sodipodi:rx="13.21095" + sodipodi:ry="22.49432" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + transform="matrix(8.170002e-2,-2.177155e-2,-3.529402e-3,6.291213e-2,320.3887,-5.30443)" /> + <path + transform="matrix(8.021497e-2,-3.444141e-2,2.833695e-2,6.672447e-2,259.4509,9.15401)" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + sodipodi:ry="22.49432" + sodipodi:rx="13.21095" + sodipodi:cy="1631.8588" + sodipodi:cx="1749.9153" + id="path18503" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + </g> + <path + id="path18505" + d="M 453.65005,103.86523 C 454.17794,109.83142 459.44501,114.2581 465.40977,113.74673 C 471.37453,113.23535 475.78796,107.97875 475.26007,102.01256 C 474.73219,96.046374 469.46202,91.619968 463.49726,92.131338 C 457.53251,92.642708 453.12217,97.899043 453.65005,103.86523 z M 461.29024,103.21022 C 461.13004,101.39955 462.41616,99.809325 464.16335,99.659535 C 465.91055,99.509745 467.45968,100.8569 467.61989,102.66757 C 467.78009,104.47825 466.49087,106.06868 464.74368,106.21848 C 462.99648,106.36827 461.45045,105.02084 461.29024,103.21022 z " + style="fill:url(#linearGradient18519);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient18521);stroke-width:0.68519694;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + inkscape:export-filename="/home/lando/Projects/Amarok/Playback-modes/amarok_random_album.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:nodetypes="ccccc" + id="path18507" + d="M 463.60314,103.42172 C 463.44371,103.33116 463.15831,103.27438 463.04486,103.11448 L 456.42039,109.90831 C 458.34841,111.93806 460.72077,113.38276 463.42291,113.5204 L 463.60314,103.42172 z " + style="fill:url(#linearGradient18523);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.4984282;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + inkscape:export-filename="/home/lando/Projects/Amarok/Playback-modes/amarok_random_album.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:nodetypes="ccccc" + id="path18509" + d="M 465.03236,102.35804 C 465.19142,102.44767 465.33082,102.56753 465.44394,102.72585 L 472.39608,95.808963 C 470.47314,93.798917 467.91547,92.486434 465.21871,92.351712 L 465.03236,102.35804 z " + style="fill:url(#linearGradient18525);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.49565426;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + inkscape:export-filename="/home/lando/Projects/Amarok/Playback-modes/amarok_random_album.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + </g> + </g> + <g + id="g12875" + transform="translate(7,-25.944802)"> + <rect + inkscape:export-ydpi="90.000092" + inkscape:export-xdpi="90.000092" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/64x64/actions/amarok_random_album.png" + y="242.89279" + x="566.01538" + height="64" + width="64" + id="rect10383" + style="fill:#b9cff9;fill-opacity:0;stroke:none;stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" /> + <g + inkscape:export-ydpi="90.000092" + inkscape:export-xdpi="90.000092" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/64x64/actions/amarok_random_album.png" + transform="matrix(1.9562628,0,0,1.9562628,-320.85953,66.12425)" + id="g18527"> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Playback-modes/amarok_random_album.png" + transform="translate(26,55)" + id="g18529"> + <path + sodipodi:nodetypes="ccccccc" + id="path18531" + d="M 441.06383,60.713305 L 441.06542,49.495395 L 450.43446,45.740673 L 459.81147,49.492971 L 459.81139,60.717581 L 450.43626,66.344952 L 441.06383,60.713305 z " + style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient18565);stroke-width:0.51117897;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <g + id="g18533"> + <path + sodipodi:nodetypes="ccccc" + id="path18535" + d="M 441.31247,60.588468 L 441.68599,49.920973 L 450.4374,54.20208 L 450.44197,66.051923 L 441.31247,60.588468 z " + style="fill:url(#linearGradient18567);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.0742841;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + sodipodi:nodetypes="ccccc" + id="path18537" + d="M 450.43385,54.203364 L 459.56372,49.680107 L 459.55762,60.57565 L 450.43971,66.04644 L 450.43385,54.203364 z " + style="fill:url(#linearGradient18569);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.07509746;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + sodipodi:nodetypes="ccccc" + id="path18539" + d="M 441.688,49.920202 L 450.43621,45.97529 L 459.55793,49.686083 L 450.43299,54.201471 L 441.688,49.920202 z " + style="fill:url(#linearGradient18571);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.79999995;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + <path + sodipodi:type="arc" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path18541" + sodipodi:cx="1749.9153" + sodipodi:cy="1631.8588" + sodipodi:rx="13.21095" + sodipodi:ry="22.49432" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + transform="matrix(0.129577,-2.161349e-2,2.662017e-2,3.635072e-2,180.263,31.06197)" /> + <path + transform="matrix(0.108645,-1.714179e-2,2.231983e-2,2.883016e-2,223.9065,30.65984)" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + sodipodi:ry="22.49432" + sodipodi:rx="13.21095" + sodipodi:cy="1631.8588" + sodipodi:cx="1749.9153" + id="path18543" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + sodipodi:type="arc" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path18545" + sodipodi:cx="1749.9153" + sodipodi:cy="1631.8588" + sodipodi:rx="13.21095" + sodipodi:ry="22.49432" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + transform="matrix(0.11443,-1.853676e-2,2.350834e-2,3.117637e-2,211.8228,31.4122)" /> + <path + transform="matrix(0.11443,-1.853676e-2,2.350834e-2,3.117637e-2,206.4981,31.4122)" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + sodipodi:ry="22.49432" + sodipodi:rx="13.21095" + sodipodi:cy="1631.8588" + sodipodi:cx="1749.9153" + id="path18547" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + sodipodi:type="arc" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path18549" + sodipodi:cx="1749.9153" + sodipodi:cy="1631.8588" + sodipodi:rx="13.21095" + sodipodi:ry="22.49432" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + transform="matrix(0.11443,-1.853676e-2,2.350834e-2,3.117637e-2,217.0829,31.4122)" /> + <path + transform="matrix(9.309428e-2,-2.478274e-2,-3.935631e-3,7.164391e-2,295.9902,-17.92848)" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + sodipodi:ry="22.49432" + sodipodi:rx="13.21095" + sodipodi:cy="1631.8588" + sodipodi:cx="1749.9153" + id="path18551" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + sodipodi:type="arc" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path18553" + sodipodi:cx="1749.9153" + sodipodi:cy="1631.8588" + sodipodi:rx="13.21095" + sodipodi:ry="22.49432" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + transform="matrix(8.73537e-2,-2.437095e-2,-2.584881e-3,6.94881e-2,306.3905,-13.26718)" /> + <path + transform="matrix(8.170002e-2,-2.177155e-2,-3.529402e-3,6.291213e-2,320.3887,-5.30443)" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + sodipodi:ry="22.49432" + sodipodi:rx="13.21095" + sodipodi:cy="1631.8588" + sodipodi:cx="1749.9153" + id="path18555" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + sodipodi:type="arc" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path18557" + sodipodi:cx="1749.9153" + sodipodi:cy="1631.8588" + sodipodi:rx="13.21095" + sodipodi:ry="22.49432" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + transform="matrix(8.021497e-2,-3.444141e-2,2.833695e-2,6.672447e-2,259.4509,9.15401)" /> + </g> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Playback-modes/amarok_random_album.png" + style="fill:url(#linearGradient18573);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient18575);stroke-width:0.51117897;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 453.65005,103.86523 C 454.17794,109.83142 459.44501,114.2581 465.40977,113.74673 C 471.37453,113.23535 475.78796,107.97875 475.26007,102.01256 C 474.73219,96.046374 469.46202,91.619968 463.49726,92.131338 C 457.53251,92.642708 453.12217,97.899043 453.65005,103.86523 z M 461.29024,103.21022 C 461.13004,101.39955 462.41616,99.809325 464.16335,99.659535 C 465.91055,99.509745 467.45968,100.8569 467.61989,102.66757 C 467.78009,104.47825 466.49087,106.06868 464.74368,106.21848 C 462.99648,106.36827 461.45045,105.02084 461.29024,103.21022 z " + id="path18559" /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Playback-modes/amarok_random_album.png" + style="fill:url(#linearGradient18577);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.4984282;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + d="M 463.60314,103.42172 C 463.44371,103.33116 463.15831,103.27438 463.04486,103.11448 L 456.42039,109.90831 C 458.34841,111.93806 460.72077,113.38276 463.42291,113.5204 L 463.60314,103.42172 z " + id="path18561" + sodipodi:nodetypes="ccccc" /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Playback-modes/amarok_random_album.png" + style="fill:url(#linearGradient18579);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.49565426;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + d="M 465.03236,102.35804 C 465.19142,102.44767 465.33082,102.56753 465.44394,102.72585 L 472.39608,95.808963 C 470.47314,93.798917 467.91547,92.486434 465.21871,92.351712 L 465.03236,102.35804 z " + id="path18563" + sodipodi:nodetypes="ccccc" /> + </g> + </g> + <g + id="g18921" + transform="matrix(0.7288167,0,0,0.7274679,-52.328608,-311.90231)" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/16x16/actions/amarok_repeat_album.png" + inkscape:export-xdpi="90.000092" + inkscape:export-ydpi="90.000092"> + <path + id="path18923" + d="M 634.4919,1015.6237 C 634.40371,1015.629 634.33043,1015.6473 634.2419,1015.6549 C 628.57597,1016.1406 624.39671,1021.144 624.89815,1026.8112 C 625.39956,1032.4783 630.38847,1036.6719 636.0544,1036.1862 C 641.72034,1035.7004 645.89959,1030.697 645.39815,1025.0299 C 644.90457,1019.4513 640.04808,1015.289 634.4919,1015.6237 z M 634.71065,1022.4362 C 634.85657,1022.4176 634.99721,1022.4362 635.14815,1022.4362 C 637.08017,1022.4361 638.64815,1024.0042 638.64815,1025.9362 C 638.64814,1027.8682 637.08016,1029.4362 635.14815,1029.4362 C 633.21615,1029.4362 631.64815,1027.8682 631.64815,1025.9362 C 631.64814,1024.1551 632.98878,1022.6552 634.71065,1022.4362 z " + style="fill:url(#linearGradient18927);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient18929);stroke-width:1.3733592;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + id="path18925" + d="M 630.4283,1016.71 L 631.05714,1018.5572 C 630.3677,1018.9258 629.72325,1019.3802 629.17058,1019.9328 C 627.64127,1021.4621 626.65518,1023.6086 626.65518,1025.9462 C 626.65518,1028.2838 627.64553,1030.3953 629.17058,1031.9203 C 629.93098,1032.6807 630.86328,1033.2969 631.88251,1033.7283 C 632.40189,1033.948 632.92034,1034.0905 633.45464,1034.1999 L 632.78648,1033.1387 L 633.80837,1032.3133 C 633.36419,1032.2224 632.96307,1032.1175 632.58997,1031.9596 C 631.80878,1031.629 631.13556,1031.134 630.5462,1030.5447 C 629.36749,1029.366 628.65964,1027.7531 628.65964,1025.9462 C 628.65964,1024.1394 629.36749,1022.5264 630.5462,1021.3477 C 630.90349,1020.9905 631.30099,1020.6768 631.72529,1020.4045 L 632.31484,1022.0945 L 636.36307,1018.4393 L 630.4283,1016.71 z M 636.83471,1017.6532 L 637.58147,1018.8716 L 636.48099,1019.5398 C 636.89084,1019.6237 637.29523,1019.7618 637.69939,1019.9328 C 638.48057,1020.2634 639.15381,1020.7584 639.74315,1021.3477 C 640.92186,1022.5265 641.6297,1024.1394 641.6297,1025.9462 C 641.6297,1027.7531 640.92186,1029.366 639.74315,1030.5447 C 639.41164,1030.8762 639.07084,1031.1893 638.68196,1031.4487 L 638.09241,1029.7586 L 634.04418,1033.4138 L 639.97897,1035.1432 L 639.31082,1033.2566 C 639.98589,1032.8832 640.62389,1032.4545 641.15806,1031.9203 C 642.68739,1030.391 643.63417,1028.2759 643.63417,1025.9462 C 643.63417,1023.6165 642.69165,1021.4664 641.15806,1019.9328 C 640.38063,1019.1554 639.4058,1018.5869 638.40684,1018.1642 C 637.91596,1017.9564 637.40056,1017.7691 636.83471,1017.6532 z " + style="fill:#0368ff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:3.75;stroke-miterlimit:4;stroke-dashoffset:0;stroke-opacity:1" /> + </g> + <g + transform="matrix(1.4997414,0,0,1.4997414,-165.02132,144.92151)" + id="g18935" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/48x48/actions/amarok_repeat_album.png" + inkscape:export-xdpi="90.000092" + inkscape:export-ydpi="90.000092"> + <path + style="fill:url(#linearGradient18945);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient18947);stroke-width:0.66678166;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 454.3925,183.70001 C 455.15501,192.31801 462.76316,198.71221 471.37908,197.97355 C 479.99501,197.23489 486.37009,189.64188 485.60757,181.02388 C 484.84507,172.40589 477.23245,166.01207 468.61652,166.75073 C 460.0006,167.48939 453.62999,175.08202 454.3925,183.70001 z M 465.42854,182.75387 C 465.19713,180.13841 467.0549,177.84137 469.57866,177.625 C 472.10244,177.40863 474.34012,179.35456 474.57153,181.97003 C 474.80295,184.58549 472.94071,186.88283 470.41693,187.0992 C 467.89316,187.31557 465.65995,185.36926 465.42854,182.75387 z " + id="path18937" /> + <path + style="fill:url(#linearGradient18949);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.4984282;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + d="M 468.76941,183.05917 C 468.53913,182.92836 468.12687,182.84634 467.96301,182.61537 L 458.39413,192.42887 C 461.1791,195.36078 464.60591,197.44761 468.50909,197.64642 L 468.76941,183.05917 z " + id="path18939" + sodipodi:nodetypes="ccccc" /> + <path + style="fill:url(#linearGradient18951);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.49565426;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + d="M 470.83389,181.52271 C 471.06364,181.65218 471.26501,181.82531 471.4284,182.054 L 481.47057,172.06276 C 478.69293,169.1593 474.99846,167.26346 471.10306,167.06886 L 470.83389,181.52271 z " + id="path18941" + sodipodi:nodetypes="ccccc" /> + <path + style="fill:#0368ff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:3.75;stroke-miterlimit:4;stroke-dashoffset:0;stroke-opacity:1" + d="M 463.13964,168.95567 L 464.05434,171.64264 C 463.0515,172.17874 462.1141,172.8397 461.3102,173.64358 C 459.0857,175.86808 457.65136,178.99032 457.65136,182.39053 C 457.65136,185.79073 459.0919,188.86199 461.3102,191.0803 C 462.41626,192.18636 463.77236,193.08268 465.2549,193.7101 C 466.01039,194.0298 466.76451,194.23696 467.54169,194.39614 L 466.56981,192.85256 L 468.05623,191.652 C 467.41013,191.51966 466.82667,191.36714 466.28397,191.13746 C 465.14766,190.65659 464.1684,189.93661 463.31114,189.07935 C 461.59662,187.36483 460.567,185.01877 460.567,182.39053 C 460.567,179.7623 461.59662,177.4162 463.31114,175.70168 C 463.83084,175.18198 464.40904,174.72574 465.02622,174.32962 L 465.88377,176.7879 L 471.77223,171.47114 L 463.13964,168.95567 z M 472.45827,170.32773 L 473.54449,172.1 L 471.94375,173.07188 C 472.53991,173.19398 473.12813,173.3948 473.71601,173.64358 C 474.85229,174.12444 475.83158,174.84444 476.68882,175.70168 C 478.40334,177.41622 479.43296,179.7623 479.43296,182.39053 C 479.43296,185.01877 478.40334,187.36483 476.68882,189.07935 C 476.20662,189.56155 475.71089,190.01705 475.14523,190.39425 L 474.28769,187.93597 L 468.39923,193.25274 L 477.03184,195.7682 L 476.05996,193.02406 C 477.0419,192.48096 477.96992,191.85732 478.74692,191.0803 C 480.97144,188.85579 482.3486,185.77923 482.3486,182.39053 C 482.3486,179.00182 480.97764,175.87428 478.74692,173.64358 C 477.61608,172.51272 476.19812,171.6859 474.74505,171.07096 C 474.03103,170.76877 473.28133,170.49631 472.45827,170.32773 z " + id="path18943" /> + </g> + <g + id="g18953" + transform="matrix(2.010288,0,0,2.010288,-321.86639,43.817134)" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/64x64/actions/amarok_repeat_album.png" + inkscape:export-xdpi="90.000092" + inkscape:export-ydpi="90.000092"> + <path + id="path18955" + d="M 454.3925,183.70001 C 455.15501,192.31801 462.76316,198.71221 471.37908,197.97355 C 479.99501,197.23489 486.37009,189.64188 485.60757,181.02388 C 484.84507,172.40589 477.23245,166.01207 468.61652,166.75073 C 460.0006,167.48939 453.62999,175.08202 454.3925,183.70001 z M 465.42854,182.75387 C 465.19713,180.13841 467.0549,177.84137 469.57866,177.625 C 472.10244,177.40863 474.34012,179.35456 474.57153,181.97003 C 474.80295,184.58549 472.94071,186.88283 470.41693,187.0992 C 467.89316,187.31557 465.65995,185.36926 465.42854,182.75387 z " + style="fill:url(#linearGradient18963);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient18965);stroke-width:0.49744111;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + sodipodi:nodetypes="ccccc" + id="path18957" + d="M 468.76941,183.05917 C 468.53913,182.92836 468.12687,182.84634 467.96301,182.61537 L 458.39413,192.42887 C 461.1791,195.36078 464.60591,197.44761 468.50909,197.64642 L 468.76941,183.05917 z " + style="fill:url(#linearGradient18967);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.4984282;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" /> + <path + sodipodi:nodetypes="ccccc" + id="path18959" + d="M 470.83389,181.52271 C 471.06364,181.65218 471.26501,181.82531 471.4284,182.054 L 481.47057,172.06276 C 478.69293,169.1593 474.99846,167.26346 471.10306,167.06886 L 470.83389,181.52271 z " + style="fill:url(#linearGradient18969);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.49565426;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" /> + <path + id="path18961" + d="M 463.13964,168.95567 L 464.05434,171.64264 C 463.0515,172.17874 462.1141,172.8397 461.3102,173.64358 C 459.0857,175.86808 457.65136,178.99032 457.65136,182.39053 C 457.65136,185.79073 459.0919,188.86199 461.3102,191.0803 C 462.41626,192.18636 463.77236,193.08268 465.2549,193.7101 C 466.01039,194.0298 466.76451,194.23696 467.54169,194.39614 L 466.56981,192.85256 L 468.05623,191.652 C 467.41013,191.51966 466.82667,191.36714 466.28397,191.13746 C 465.14766,190.65659 464.1684,189.93661 463.31114,189.07935 C 461.59662,187.36483 460.567,185.01877 460.567,182.39053 C 460.567,179.7623 461.59662,177.4162 463.31114,175.70168 C 463.83084,175.18198 464.40904,174.72574 465.02622,174.32962 L 465.88377,176.7879 L 471.77223,171.47114 L 463.13964,168.95567 z M 472.45827,170.32773 L 473.54449,172.1 L 471.94375,173.07188 C 472.53991,173.19398 473.12813,173.3948 473.71601,173.64358 C 474.85229,174.12444 475.83158,174.84444 476.68882,175.70168 C 478.40334,177.41622 479.43296,179.7623 479.43296,182.39053 C 479.43296,185.01877 478.40334,187.36483 476.68882,189.07935 C 476.20662,189.56155 475.71089,190.01705 475.14523,190.39425 L 474.28769,187.93597 L 468.39923,193.25274 L 477.03184,195.7682 L 476.05996,193.02406 C 477.0419,192.48096 477.96992,191.85732 478.74692,191.0803 C 480.97144,188.85579 482.3486,185.77923 482.3486,182.39053 C 482.3486,179.00182 480.97764,175.87428 478.74692,173.64358 C 477.61608,172.51272 476.19812,171.6859 474.74505,171.07096 C 474.03103,170.76877 473.28133,170.49631 472.45827,170.32773 z " + style="fill:#0368ff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:3.75;stroke-miterlimit:4;stroke-dashoffset:0;stroke-opacity:1" /> + </g> + <path + style="fill:url(#linearGradient19846);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient19848);stroke-width:1.00000036;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + d="M 415.99808,509.2268 L 416.55363,510.73 C 415.94456,511.0299 415.37522,511.3997 414.88696,511.8493 C 413.5359,513.0939 412.66475,514.8406 412.66475,516.7428 C 412.66475,518.6451 413.53966,520.3632 414.88696,521.6041 C 415.55873,522.223 416.38237,522.7244 417.28281,523.0754 C 417.74164,523.2542 418.19968,523.3702 418.6717,523.4593 L 418.08142,522.5956 L 418.9842,521.924 C 418.59179,521.85 418.23743,521.7647 417.90781,521.6361 C 417.21767,521.3671 416.62291,520.9643 416.10225,520.4848 C 415.06092,519.5256 414.43558,518.2131 414.43558,516.7428 C 414.43558,515.2724 415.06092,513.9599 416.10225,513.0008 C 416.41788,512.71 416.76906,512.4548 417.14391,512.2331 L 417.66474,513.6085 L 421.24114,510.634 L 415.99808,509.2268 z M 421.6578,509.9943 L 422.31753,510.9858 L 421.34532,511.5295 C 421.70739,511.5978 422.06465,511.7102 422.4217,511.8493 C 423.11184,512.1184 423.7066,512.5212 424.22726,513.0008 C 425.26857,513.9599 425.89392,515.2724 425.89392,516.7428 C 425.89392,518.2131 425.26857,519.5256 424.22726,520.4848 C 423.93439,520.7546 423.6333,521.0093 423.28976,521.2204 L 422.76892,519.8451 L 419.19253,522.8196 L 424.4356,524.2268 L 423.84532,522.6916 C 424.4417,522.3878 425.00535,522.0388 425.47725,521.6041 C 426.82833,520.3597 427.66475,518.6386 427.66475,516.7428 C 427.66475,514.847 426.8321,513.0973 425.47725,511.8493 C 424.79043,511.2167 423.92923,510.7541 423.0467,510.4101 C 422.61303,510.241 422.1577,510.0886 421.6578,509.9943 z " + id="path19844" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/16x16/actions/amarok_repeat_no.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + style="fill:url(#linearGradient19852);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient19854);stroke-width:1.24999976;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + d="M 533.55337,477.3518 L 535.28483,482.0368 C 533.38652,482.9717 531.6121,484.1241 530.09038,485.5257 C 525.87956,489.4042 523.16448,494.8482 523.16448,500.7767 C 523.16448,506.7053 525.8913,512.0604 530.09038,515.9281 C 532.18406,517.8565 534.75106,519.4193 537.55739,520.5134 C 538.98747,521.0709 540.41497,521.432 541.88611,521.7096 L 540.04642,519.0181 L 542.86009,516.925 C 541.63708,516.694 540.53264,516.4281 539.50534,516.0277 C 537.3544,515.1893 535.50073,513.934 533.87801,512.4392 C 530.63256,509.4498 528.68356,505.3592 528.68356,500.7767 C 528.68356,496.1943 530.63256,492.1037 533.87801,489.1143 C 534.86175,488.208 535.95625,487.4126 537.12453,486.7219 L 538.74779,491.0081 L 549.89419,481.7378 L 533.55337,477.3518 z M 551.19282,479.7442 L 553.24896,482.8344 L 550.21887,484.5288 C 551.34737,484.7418 552.46081,485.0919 553.57362,485.5257 C 555.72452,486.3641 557.57823,487.6195 559.20092,489.1143 C 562.44638,492.1037 564.39537,496.1943 564.39537,500.7767 C 564.39537,505.3592 562.44638,509.4498 559.20092,512.4392 C 558.28817,513.28 557.34978,514.0742 556.27903,514.7319 L 554.65577,510.4456 L 543.50938,519.7158 L 559.85024,524.1018 L 558.01054,519.3172 C 559.86927,518.3702 561.62595,517.2828 563.09676,515.9281 C 567.3076,512.0496 569.91446,506.6853 569.91446,500.7767 C 569.91446,494.8682 567.31934,489.415 563.09676,485.5257 C 560.95615,483.554 558.27207,482.1124 555.52152,481.0401 C 554.16994,480.5132 552.75081,480.0382 551.19282,479.7442 z " + id="path19850" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/48x48/actions/amarok_repeat_no.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/64x64/actions/amarok_repeat_no.png" + id="path19856" + d="M 607.10893,461.3518 L 609.43297,467.6402 C 606.88497,468.895 604.50327,470.4418 602.46074,472.3231 C 596.80879,477.529 593.16448,484.8362 593.16448,492.7937 C 593.16448,500.7514 596.82454,507.9392 602.46074,513.1306 C 605.27098,515.719 608.71652,517.8167 612.48331,519.2852 C 614.40283,520.0335 616.31888,520.5182 618.29351,520.8908 L 615.8242,517.2782 L 619.60084,514.4687 C 617.95925,514.1587 616.47682,513.8018 615.09794,513.2643 C 612.21084,512.139 609.72276,510.4541 607.54467,508.4477 C 603.18848,504.4352 600.57244,498.9446 600.57244,492.7937 C 600.57244,486.643 603.18848,481.1524 607.54467,477.1399 C 608.8651,475.9234 610.33418,474.8558 611.9023,473.9287 L 614.08112,479.6819 L 629.04233,467.2388 L 607.10893,461.3518 z M 630.78541,464.5629 L 633.54525,468.7108 L 629.47813,470.9851 C 630.99285,471.271 632.48736,471.7409 633.98103,472.3231 C 636.86806,473.4485 639.3562,475.1335 641.53425,477.1399 C 645.89045,481.1524 648.50648,486.643 648.50648,492.7937 C 648.50648,498.9446 645.89045,504.4352 641.53425,508.4477 C 640.30911,509.5762 639.04956,510.6422 637.61235,511.525 L 635.43354,505.7718 L 620.47234,518.2147 L 642.4058,524.1018 L 639.93647,517.6796 C 642.43134,516.4085 644.78924,514.949 646.76342,513.1306 C 652.41541,507.9247 655.91446,500.7245 655.91446,492.7937 C 655.91446,484.8631 652.43117,477.5435 646.76342,472.3231 C 643.8902,469.6766 640.2875,467.7417 636.59559,466.3024 C 634.78144,465.5951 632.87661,464.9576 630.78541,464.5629 z " + style="fill:url(#linearGradient19858);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient19860);stroke-width:1.24999976;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + <g + id="g19898" + transform="matrix(0.9676024,0,0,0.880408,-35.234388,415.6498)" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/16x16/actions/amarok_random_no.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <path + sodipodi:nodetypes="ccccccc" + id="path19900" + d="M 460.60022,220.71135 L 460.60151,211.59252 L 468.21743,208.54037 L 475.83984,211.59055 L 475.83977,220.71483 L 468.2189,225.28922 L 460.60022,220.71135 z " + style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient19926);stroke-width:1.35889351;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + style="fill:url(#linearGradient19928);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.0742841;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 461.10864,220.42601 L 461.10596,211.93846 L 468.21982,215.4185 L 468.22065,224.6981 L 461.10864,220.42601 z " + id="path19902" + sodipodi:nodetypes="ccccc" /> + <path + style="fill:url(#linearGradient19930);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.07509746;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 468.21694,215.41954 L 475.33253,211.93985 L 475.33166,220.42732 L 468.22343,224.69433 L 468.21694,215.41954 z " + id="path19904" + sodipodi:nodetypes="ccccc" /> + <path + style="fill:url(#linearGradient19932);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.79999995;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 461.10759,211.93783 L 468.21886,209.08645 L 475.33129,211.93814 L 468.21624,215.418 L 461.10759,211.93783 z " + id="path19906" + sodipodi:nodetypes="ccccc" /> + <path + sodipodi:type="arc" + style="fill:#535353;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path19908" + sodipodi:cx="1749.9153" + sodipodi:cy="1631.8588" + sodipodi:rx="13.21095" + sodipodi:ry="22.49432" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + transform="matrix(0.105331,-1.756922e-2,2.163906e-2,2.954885e-2,248.5999,196.6083)" /> + <path + transform="matrix(8.831556e-2,-1.393425e-2,1.814339e-2,2.343552e-2,284.0769,196.2814)" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + sodipodi:ry="22.49432" + sodipodi:rx="13.21095" + sodipodi:cy="1631.8588" + sodipodi:cx="1749.9153" + id="path19910" + style="fill:#535353;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + sodipodi:type="arc" + style="fill:#535353;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path19912" + sodipodi:cx="1749.9153" + sodipodi:cy="1631.8588" + sodipodi:rx="13.21095" + sodipodi:ry="22.49432" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + transform="matrix(9.301809e-2,-1.50682e-2,1.910951e-2,2.534271e-2,274.2543,196.893)" /> + <path + transform="matrix(9.301809e-2,-1.50682e-2,1.910951e-2,2.534271e-2,269.926,196.893)" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + sodipodi:ry="22.49432" + sodipodi:rx="13.21095" + sodipodi:cy="1631.8588" + sodipodi:cx="1749.9153" + id="path19914" + style="fill:#535353;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + sodipodi:type="arc" + style="fill:#535353;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path19916" + sodipodi:cx="1749.9153" + sodipodi:cy="1631.8588" + sodipodi:rx="13.21095" + sodipodi:ry="22.49432" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + transform="matrix(9.301809e-2,-1.50682e-2,1.910951e-2,2.534271e-2,278.5302,196.893)" /> + <path + transform="matrix(7.567466e-2,-2.014544e-2,-3.199204e-3,5.823804e-2,342.6725,156.7849)" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + sodipodi:ry="22.49432" + sodipodi:rx="13.21095" + sodipodi:cy="1631.8588" + sodipodi:cx="1749.9153" + id="path19918" + style="fill:#535353;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + sodipodi:type="arc" + style="fill:#535353;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path19920" + sodipodi:cx="1749.9153" + sodipodi:cy="1631.8588" + sodipodi:rx="13.21095" + sodipodi:ry="22.49432" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + transform="matrix(7.100825e-2,-1.981071e-2,-2.101203e-3,5.648563e-2,351.1267,160.5739)" /> + <path + transform="matrix(6.641248e-2,-1.76977e-2,-2.868987e-3,5.114014e-2,362.5056,167.0467)" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + sodipodi:ry="22.49432" + sodipodi:rx="13.21095" + sodipodi:cy="1631.8588" + sodipodi:cx="1749.9153" + id="path19922" + style="fill:#535353;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + sodipodi:type="arc" + style="fill:#535353;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path19924" + sodipodi:cx="1749.9153" + sodipodi:cy="1631.8588" + sodipodi:rx="13.21095" + sodipodi:ry="22.49432" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + transform="matrix(6.520531e-2,-2.79968e-2,2.30346e-2,5.423912e-2,312.9703,178.7997)" /> + </g> + <g + id="g20179" + transform="matrix(1.1066592,0,0,1,-248.95378,-505.23223)" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/22x22/actions/amarok_random_no.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <g + id="g15516" + transform="matrix(1.230079,0,0,1.238637,63.79075,840.1769)" + inkscape:export-filename="/home/lando/Projects/Amarok/Playback-modes/amarok_random_no.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <path + sodipodi:nodetypes="ccccccc" + id="path15518" + d="M 460.60022,220.71135 L 460.60151,211.59252 L 468.21743,208.54037 L 475.83984,211.59055 L 475.83977,220.71483 L 468.2189,225.28922 L 460.60022,220.71135 z " + style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient20434);stroke-width:0.96589649;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + style="fill:url(#linearGradient20436);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.0742841;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 461.10864,220.42601 L 461.10596,211.93846 L 468.21982,215.4185 L 468.22065,224.6981 L 461.10864,220.42601 z " + id="path15520" + sodipodi:nodetypes="ccccc" /> + <path + style="fill:url(#linearGradient20438);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.07509746;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 468.21694,215.41954 L 475.33253,211.93985 L 475.33166,220.42732 L 468.22343,224.69433 L 468.21694,215.41954 z " + id="path15522" + sodipodi:nodetypes="ccccc" /> + <path + style="fill:url(#linearGradient20440);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.79999995;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 461.10759,211.93783 L 468.21886,209.08645 L 475.33129,211.93814 L 468.21624,215.418 L 461.10759,211.93783 z " + id="path15524" + sodipodi:nodetypes="ccccc" /> + <path + sodipodi:type="arc" + style="fill:#535353;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path15526" + sodipodi:cx="1749.9153" + sodipodi:cy="1631.8588" + sodipodi:rx="13.21095" + sodipodi:ry="22.49432" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + transform="matrix(0.105331,-1.756922e-2,2.163906e-2,2.954885e-2,248.5999,196.6083)" /> + <path + transform="matrix(8.831556e-2,-1.393425e-2,1.814339e-2,2.343552e-2,284.0769,196.2814)" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + sodipodi:ry="22.49432" + sodipodi:rx="13.21095" + sodipodi:cy="1631.8588" + sodipodi:cx="1749.9153" + id="path15528" + style="fill:#535353;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + sodipodi:type="arc" + style="fill:#535353;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path15530" + sodipodi:cx="1749.9153" + sodipodi:cy="1631.8588" + sodipodi:rx="13.21095" + sodipodi:ry="22.49432" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + transform="matrix(9.301809e-2,-1.50682e-2,1.910951e-2,2.534271e-2,274.2543,196.893)" /> + <path + transform="matrix(9.301809e-2,-1.50682e-2,1.910951e-2,2.534271e-2,269.926,196.893)" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + sodipodi:ry="22.49432" + sodipodi:rx="13.21095" + sodipodi:cy="1631.8588" + sodipodi:cx="1749.9153" + id="path15532" + style="fill:#535353;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + sodipodi:type="arc" + style="fill:#535353;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path15534" + sodipodi:cx="1749.9153" + sodipodi:cy="1631.8588" + sodipodi:rx="13.21095" + sodipodi:ry="22.49432" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + transform="matrix(9.301809e-2,-1.50682e-2,1.910951e-2,2.534271e-2,278.5302,196.893)" /> + <path + transform="matrix(7.567466e-2,-2.014544e-2,-3.199204e-3,5.823804e-2,342.6725,156.7849)" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + sodipodi:ry="22.49432" + sodipodi:rx="13.21095" + sodipodi:cy="1631.8588" + sodipodi:cx="1749.9153" + id="path15536" + style="fill:#535353;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + sodipodi:type="arc" + style="fill:#535353;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path15538" + sodipodi:cx="1749.9153" + sodipodi:cy="1631.8588" + sodipodi:rx="13.21095" + sodipodi:ry="22.49432" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + transform="matrix(7.100825e-2,-1.981071e-2,-2.101203e-3,5.648563e-2,351.1267,160.5739)" /> + <path + transform="matrix(6.641248e-2,-1.76977e-2,-2.868987e-3,5.114014e-2,362.5056,167.0467)" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + sodipodi:ry="22.49432" + sodipodi:rx="13.21095" + sodipodi:cy="1631.8588" + sodipodi:cx="1749.9153" + id="path15540" + style="fill:#535353;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + sodipodi:type="arc" + style="fill:#535353;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path15542" + sodipodi:cx="1749.9153" + sodipodi:cy="1631.8588" + sodipodi:rx="13.21095" + sodipodi:ry="22.49432" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + transform="matrix(6.520531e-2,-2.79968e-2,2.30346e-2,5.423912e-2,312.9703,178.7997)" /> + </g> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Playback-modes/amarok_random_no.png" + transform="matrix(1.230079,0,0,1.238637,63.79075,840.1769)" + id="g19934"> + <path + style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient20442);stroke-width:0.96589649;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 460.60022,220.71135 L 460.60151,211.59252 L 468.21743,208.54037 L 475.83984,211.59055 L 475.83977,220.71483 L 468.2189,225.28922 L 460.60022,220.71135 z " + id="path19936" + sodipodi:nodetypes="ccccccc" /> + <path + sodipodi:nodetypes="ccccc" + id="path19938" + d="M 461.10864,220.42601 L 461.10596,211.93846 L 468.21982,215.4185 L 468.22065,224.6981 L 461.10864,220.42601 z " + style="fill:url(#linearGradient20444);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.0742841;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + sodipodi:nodetypes="ccccc" + id="path19940" + d="M 468.21694,215.41954 L 475.33253,211.93985 L 475.33166,220.42732 L 468.22343,224.69433 L 468.21694,215.41954 z " + style="fill:url(#linearGradient20446);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.07509746;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + sodipodi:nodetypes="ccccc" + id="path19942" + d="M 461.10759,211.93783 L 468.21886,209.08645 L 475.33129,211.93814 L 468.21624,215.418 L 461.10759,211.93783 z " + style="fill:url(#linearGradient20448);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.79999995;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + transform="matrix(0.105331,-1.756922e-2,2.163906e-2,2.954885e-2,248.5999,196.6083)" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + sodipodi:ry="22.49432" + sodipodi:rx="13.21095" + sodipodi:cy="1631.8588" + sodipodi:cx="1749.9153" + id="path19944" + style="fill:#535353;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + sodipodi:type="arc" + style="fill:#535353;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path19946" + sodipodi:cx="1749.9153" + sodipodi:cy="1631.8588" + sodipodi:rx="13.21095" + sodipodi:ry="22.49432" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + transform="matrix(8.831556e-2,-1.393425e-2,1.814339e-2,2.343552e-2,284.0769,196.2814)" /> + <path + transform="matrix(9.301809e-2,-1.50682e-2,1.910951e-2,2.534271e-2,274.2543,196.893)" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + sodipodi:ry="22.49432" + sodipodi:rx="13.21095" + sodipodi:cy="1631.8588" + sodipodi:cx="1749.9153" + id="path19948" + style="fill:#535353;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + sodipodi:type="arc" + style="fill:#535353;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path19950" + sodipodi:cx="1749.9153" + sodipodi:cy="1631.8588" + sodipodi:rx="13.21095" + sodipodi:ry="22.49432" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + transform="matrix(9.301809e-2,-1.50682e-2,1.910951e-2,2.534271e-2,269.926,196.893)" /> + <path + transform="matrix(9.301809e-2,-1.50682e-2,1.910951e-2,2.534271e-2,278.5302,196.893)" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + sodipodi:ry="22.49432" + sodipodi:rx="13.21095" + sodipodi:cy="1631.8588" + sodipodi:cx="1749.9153" + id="path19952" + style="fill:#535353;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + sodipodi:type="arc" + style="fill:#535353;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path19954" + sodipodi:cx="1749.9153" + sodipodi:cy="1631.8588" + sodipodi:rx="13.21095" + sodipodi:ry="22.49432" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + transform="matrix(7.567466e-2,-2.014544e-2,-3.199204e-3,5.823804e-2,342.6725,156.7849)" /> + <path + transform="matrix(7.100825e-2,-1.981071e-2,-2.101203e-3,5.648563e-2,351.1267,160.5739)" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + sodipodi:ry="22.49432" + sodipodi:rx="13.21095" + sodipodi:cy="1631.8588" + sodipodi:cx="1749.9153" + id="path19956" + style="fill:#535353;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + sodipodi:type="arc" + style="fill:#535353;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path19958" + sodipodi:cx="1749.9153" + sodipodi:cy="1631.8588" + sodipodi:rx="13.21095" + sodipodi:ry="22.49432" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + transform="matrix(6.641248e-2,-1.76977e-2,-2.868987e-3,5.114014e-2,362.5056,167.0467)" /> + <path + transform="matrix(6.520531e-2,-2.79968e-2,2.30346e-2,5.423912e-2,312.9703,178.7997)" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + sodipodi:ry="22.49432" + sodipodi:rx="13.21095" + sodipodi:cy="1631.8588" + sodipodi:cx="1749.9153" + id="path19960" + style="fill:#535353;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + </g> + </g> + <g + id="g20210" + transform="matrix(1.609716,0,0,1.454547,-529.08895,-1014.2592)" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/32x32/actions/amarok_random_no.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Playback-modes/amarok_random_no.png" + transform="matrix(1.230079,0,0,1.238637,63.79075,840.1769)" + id="g20212"> + <path + style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient20270);stroke-width:0.96588802;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 460.60022,220.71135 L 460.60151,211.59252 L 468.21743,208.54037 L 475.83984,211.59055 L 475.83977,220.71483 L 468.2189,225.28922 L 460.60022,220.71135 z " + id="path20214" + sodipodi:nodetypes="ccccccc" /> + <path + sodipodi:nodetypes="ccccc" + id="path20216" + d="M 461.10864,220.42601 L 461.10596,211.93846 L 468.21982,215.4185 L 468.22065,224.6981 L 461.10864,220.42601 z " + style="fill:url(#linearGradient20272);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.0742841;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + sodipodi:nodetypes="ccccc" + id="path20219" + d="M 468.21694,215.41954 L 475.33253,211.93985 L 475.33166,220.42732 L 468.22343,224.69433 L 468.21694,215.41954 z " + style="fill:url(#linearGradient20274);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.07509746;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + sodipodi:nodetypes="ccccc" + id="path20221" + d="M 461.10759,211.93783 L 468.21886,209.08645 L 475.33129,211.93814 L 468.21624,215.418 L 461.10759,211.93783 z " + style="fill:url(#linearGradient20276);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.79999995;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + transform="matrix(0.105331,-1.756922e-2,2.163906e-2,2.954885e-2,248.5999,196.6083)" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + sodipodi:ry="22.49432" + sodipodi:rx="13.21095" + sodipodi:cy="1631.8588" + sodipodi:cx="1749.9153" + id="path20223" + style="fill:#535353;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + sodipodi:type="arc" + style="fill:#535353;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path20225" + sodipodi:cx="1749.9153" + sodipodi:cy="1631.8588" + sodipodi:rx="13.21095" + sodipodi:ry="22.49432" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + transform="matrix(8.831556e-2,-1.393425e-2,1.814339e-2,2.343552e-2,284.0769,196.2814)" /> + <path + transform="matrix(9.301809e-2,-1.50682e-2,1.910951e-2,2.534271e-2,274.2543,196.893)" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + sodipodi:ry="22.49432" + sodipodi:rx="13.21095" + sodipodi:cy="1631.8588" + sodipodi:cx="1749.9153" + id="path20228" + style="fill:#535353;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + sodipodi:type="arc" + style="fill:#535353;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path20230" + sodipodi:cx="1749.9153" + sodipodi:cy="1631.8588" + sodipodi:rx="13.21095" + sodipodi:ry="22.49432" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + transform="matrix(9.301809e-2,-1.50682e-2,1.910951e-2,2.534271e-2,269.926,196.893)" /> + <path + transform="matrix(9.301809e-2,-1.50682e-2,1.910951e-2,2.534271e-2,278.5302,196.893)" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + sodipodi:ry="22.49432" + sodipodi:rx="13.21095" + sodipodi:cy="1631.8588" + sodipodi:cx="1749.9153" + id="path20232" + style="fill:#535353;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + sodipodi:type="arc" + style="fill:#535353;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path20234" + sodipodi:cx="1749.9153" + sodipodi:cy="1631.8588" + sodipodi:rx="13.21095" + sodipodi:ry="22.49432" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + transform="matrix(7.567466e-2,-2.014544e-2,-3.199204e-3,5.823804e-2,342.6725,156.7849)" /> + <path + transform="matrix(7.100825e-2,-1.981071e-2,-2.101203e-3,5.648563e-2,351.1267,160.5739)" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + sodipodi:ry="22.49432" + sodipodi:rx="13.21095" + sodipodi:cy="1631.8588" + sodipodi:cx="1749.9153" + id="path20236" + style="fill:#535353;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + sodipodi:type="arc" + style="fill:#535353;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path20238" + sodipodi:cx="1749.9153" + sodipodi:cy="1631.8588" + sodipodi:rx="13.21095" + sodipodi:ry="22.49432" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + transform="matrix(6.641248e-2,-1.76977e-2,-2.868987e-3,5.114014e-2,362.5056,167.0467)" /> + <path + transform="matrix(6.520531e-2,-2.79968e-2,2.30346e-2,5.423912e-2,312.9703,178.7997)" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + sodipodi:ry="22.49432" + sodipodi:rx="13.21095" + sodipodi:cy="1631.8588" + sodipodi:cx="1749.9153" + id="path20240" + style="fill:#535353;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + </g> + <g + id="g20242" + transform="matrix(1.230079,0,0,1.238637,63.79075,840.1769)" + inkscape:export-filename="/home/lando/Projects/Amarok/Playback-modes/amarok_random_no.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <path + sodipodi:nodetypes="ccccccc" + id="path20244" + d="M 460.60022,220.71135 L 460.60151,211.59252 L 468.21743,208.54037 L 475.83984,211.59055 L 475.83977,220.71483 L 468.2189,225.28922 L 460.60022,220.71135 z " + style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient20278);stroke-width:0.96588802;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + style="fill:url(#linearGradient20280);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.0742841;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 461.10864,220.42601 L 461.10596,211.93846 L 468.21982,215.4185 L 468.22065,224.6981 L 461.10864,220.42601 z " + id="path20246" + sodipodi:nodetypes="ccccc" /> + <path + style="fill:url(#linearGradient20282);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.07509746;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 468.21694,215.41954 L 475.33253,211.93985 L 475.33166,220.42732 L 468.22343,224.69433 L 468.21694,215.41954 z " + id="path20248" + sodipodi:nodetypes="ccccc" /> + <path + style="fill:url(#linearGradient20284);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.79999995;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 461.10759,211.93783 L 468.21886,209.08645 L 475.33129,211.93814 L 468.21624,215.418 L 461.10759,211.93783 z " + id="path20250" + sodipodi:nodetypes="ccccc" /> + <path + sodipodi:type="arc" + style="fill:#535353;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path20252" + sodipodi:cx="1749.9153" + sodipodi:cy="1631.8588" + sodipodi:rx="13.21095" + sodipodi:ry="22.49432" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + transform="matrix(0.105331,-1.756922e-2,2.163906e-2,2.954885e-2,248.5999,196.6083)" /> + <path + transform="matrix(8.831556e-2,-1.393425e-2,1.814339e-2,2.343552e-2,284.0769,196.2814)" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + sodipodi:ry="22.49432" + sodipodi:rx="13.21095" + sodipodi:cy="1631.8588" + sodipodi:cx="1749.9153" + id="path20254" + style="fill:#535353;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + sodipodi:type="arc" + style="fill:#535353;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path20256" + sodipodi:cx="1749.9153" + sodipodi:cy="1631.8588" + sodipodi:rx="13.21095" + sodipodi:ry="22.49432" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + transform="matrix(9.301809e-2,-1.50682e-2,1.910951e-2,2.534271e-2,274.2543,196.893)" /> + <path + transform="matrix(9.301809e-2,-1.50682e-2,1.910951e-2,2.534271e-2,269.926,196.893)" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + sodipodi:ry="22.49432" + sodipodi:rx="13.21095" + sodipodi:cy="1631.8588" + sodipodi:cx="1749.9153" + id="path20258" + style="fill:#535353;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + sodipodi:type="arc" + style="fill:#535353;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path20260" + sodipodi:cx="1749.9153" + sodipodi:cy="1631.8588" + sodipodi:rx="13.21095" + sodipodi:ry="22.49432" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + transform="matrix(9.301809e-2,-1.50682e-2,1.910951e-2,2.534271e-2,278.5302,196.893)" /> + <path + transform="matrix(7.567466e-2,-2.014544e-2,-3.199204e-3,5.823804e-2,342.6725,156.7849)" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + sodipodi:ry="22.49432" + sodipodi:rx="13.21095" + sodipodi:cy="1631.8588" + sodipodi:cx="1749.9153" + id="path20262" + style="fill:#535353;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + sodipodi:type="arc" + style="fill:#535353;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path20264" + sodipodi:cx="1749.9153" + sodipodi:cy="1631.8588" + sodipodi:rx="13.21095" + sodipodi:ry="22.49432" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + transform="matrix(7.100825e-2,-1.981071e-2,-2.101203e-3,5.648563e-2,351.1267,160.5739)" /> + <path + transform="matrix(6.641248e-2,-1.76977e-2,-2.868987e-3,5.114014e-2,362.5056,167.0467)" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + sodipodi:ry="22.49432" + sodipodi:rx="13.21095" + sodipodi:cy="1631.8588" + sodipodi:cx="1749.9153" + id="path20266" + style="fill:#535353;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + sodipodi:type="arc" + style="fill:#535353;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path20268" + sodipodi:cx="1749.9153" + sodipodi:cy="1631.8588" + sodipodi:rx="13.21095" + sodipodi:ry="22.49432" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + transform="matrix(6.520531e-2,-2.79968e-2,2.30346e-2,5.423912e-2,312.9703,178.7997)" /> + </g> + </g> + <g + transform="matrix(2.4145694,0,0,2.181821,-990.79323,-1828.7011)" + id="g20286" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/48x48/actions/amarok_random_no.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <g + id="g20288" + transform="matrix(1.230079,0,0,1.238637,63.79075,840.1769)" + inkscape:export-filename="/home/lando/Projects/Amarok/Playback-modes/amarok_random_no.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <path + sodipodi:nodetypes="ccccccc" + id="path20290" + d="M 460.60022,220.71135 L 460.60151,211.59252 L 468.21743,208.54037 L 475.83984,211.59055 L 475.83977,220.71483 L 468.2189,225.28922 L 460.60022,220.71135 z " + style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient20344);stroke-width:0.9658891;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + style="fill:url(#linearGradient20346);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.0742841;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 461.10864,220.42601 L 461.10596,211.93846 L 468.21982,215.4185 L 468.22065,224.6981 L 461.10864,220.42601 z " + id="path20292" + sodipodi:nodetypes="ccccc" /> + <path + style="fill:url(#linearGradient20348);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.07509746;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 468.21694,215.41954 L 475.33253,211.93985 L 475.33166,220.42732 L 468.22343,224.69433 L 468.21694,215.41954 z " + id="path20294" + sodipodi:nodetypes="ccccc" /> + <path + style="fill:url(#linearGradient20350);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.79999995;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 461.10759,211.93783 L 468.21886,209.08645 L 475.33129,211.93814 L 468.21624,215.418 L 461.10759,211.93783 z " + id="path20296" + sodipodi:nodetypes="ccccc" /> + <path + sodipodi:type="arc" + style="fill:#535353;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path20298" + sodipodi:cx="1749.9153" + sodipodi:cy="1631.8588" + sodipodi:rx="13.21095" + sodipodi:ry="22.49432" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + transform="matrix(0.105331,-1.756922e-2,2.163906e-2,2.954885e-2,248.5999,196.6083)" /> + <path + transform="matrix(8.831556e-2,-1.393425e-2,1.814339e-2,2.343552e-2,284.0769,196.2814)" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + sodipodi:ry="22.49432" + sodipodi:rx="13.21095" + sodipodi:cy="1631.8588" + sodipodi:cx="1749.9153" + id="path20300" + style="fill:#535353;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + sodipodi:type="arc" + style="fill:#535353;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path20302" + sodipodi:cx="1749.9153" + sodipodi:cy="1631.8588" + sodipodi:rx="13.21095" + sodipodi:ry="22.49432" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + transform="matrix(9.301809e-2,-1.50682e-2,1.910951e-2,2.534271e-2,274.2543,196.893)" /> + <path + transform="matrix(9.301809e-2,-1.50682e-2,1.910951e-2,2.534271e-2,269.926,196.893)" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + sodipodi:ry="22.49432" + sodipodi:rx="13.21095" + sodipodi:cy="1631.8588" + sodipodi:cx="1749.9153" + id="path20304" + style="fill:#535353;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + sodipodi:type="arc" + style="fill:#535353;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path20306" + sodipodi:cx="1749.9153" + sodipodi:cy="1631.8588" + sodipodi:rx="13.21095" + sodipodi:ry="22.49432" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + transform="matrix(9.301809e-2,-1.50682e-2,1.910951e-2,2.534271e-2,278.5302,196.893)" /> + <path + transform="matrix(7.567466e-2,-2.014544e-2,-3.199204e-3,5.823804e-2,342.6725,156.7849)" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + sodipodi:ry="22.49432" + sodipodi:rx="13.21095" + sodipodi:cy="1631.8588" + sodipodi:cx="1749.9153" + id="path20308" + style="fill:#535353;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + sodipodi:type="arc" + style="fill:#535353;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path20310" + sodipodi:cx="1749.9153" + sodipodi:cy="1631.8588" + sodipodi:rx="13.21095" + sodipodi:ry="22.49432" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + transform="matrix(7.100825e-2,-1.981071e-2,-2.101203e-3,5.648563e-2,351.1267,160.5739)" /> + <path + transform="matrix(6.641248e-2,-1.76977e-2,-2.868987e-3,5.114014e-2,362.5056,167.0467)" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + sodipodi:ry="22.49432" + sodipodi:rx="13.21095" + sodipodi:cy="1631.8588" + sodipodi:cx="1749.9153" + id="path20312" + style="fill:#535353;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + sodipodi:type="arc" + style="fill:#535353;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path20314" + sodipodi:cx="1749.9153" + sodipodi:cy="1631.8588" + sodipodi:rx="13.21095" + sodipodi:ry="22.49432" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + transform="matrix(6.520531e-2,-2.79968e-2,2.30346e-2,5.423912e-2,312.9703,178.7997)" /> + </g> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Playback-modes/amarok_random_no.png" + transform="matrix(1.230079,0,0,1.238637,63.79075,840.1769)" + id="g20316"> + <path + style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient20352);stroke-width:0.9658891;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 460.60022,220.71135 L 460.60151,211.59252 L 468.21743,208.54037 L 475.83984,211.59055 L 475.83977,220.71483 L 468.2189,225.28922 L 460.60022,220.71135 z " + id="path20318" + sodipodi:nodetypes="ccccccc" /> + <path + sodipodi:nodetypes="ccccc" + id="path20320" + d="M 461.10864,220.42601 L 461.10596,211.93846 L 468.21982,215.4185 L 468.22065,224.6981 L 461.10864,220.42601 z " + style="fill:url(#linearGradient20354);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.0742841;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + sodipodi:nodetypes="ccccc" + id="path20322" + d="M 468.21694,215.41954 L 475.33253,211.93985 L 475.33166,220.42732 L 468.22343,224.69433 L 468.21694,215.41954 z " + style="fill:url(#linearGradient20356);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.07509746;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + sodipodi:nodetypes="ccccc" + id="path20324" + d="M 461.10759,211.93783 L 468.21886,209.08645 L 475.33129,211.93814 L 468.21624,215.418 L 461.10759,211.93783 z " + style="fill:url(#linearGradient20358);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.79999995;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + transform="matrix(0.105331,-1.756922e-2,2.163906e-2,2.954885e-2,248.5999,196.6083)" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + sodipodi:ry="22.49432" + sodipodi:rx="13.21095" + sodipodi:cy="1631.8588" + sodipodi:cx="1749.9153" + id="path20326" + style="fill:#535353;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + sodipodi:type="arc" + style="fill:#535353;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path20328" + sodipodi:cx="1749.9153" + sodipodi:cy="1631.8588" + sodipodi:rx="13.21095" + sodipodi:ry="22.49432" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + transform="matrix(8.831556e-2,-1.393425e-2,1.814339e-2,2.343552e-2,284.0769,196.2814)" /> + <path + transform="matrix(9.301809e-2,-1.50682e-2,1.910951e-2,2.534271e-2,274.2543,196.893)" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + sodipodi:ry="22.49432" + sodipodi:rx="13.21095" + sodipodi:cy="1631.8588" + sodipodi:cx="1749.9153" + id="path20330" + style="fill:#535353;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + sodipodi:type="arc" + style="fill:#535353;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path20332" + sodipodi:cx="1749.9153" + sodipodi:cy="1631.8588" + sodipodi:rx="13.21095" + sodipodi:ry="22.49432" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + transform="matrix(9.301809e-2,-1.50682e-2,1.910951e-2,2.534271e-2,269.926,196.893)" /> + <path + transform="matrix(9.301809e-2,-1.50682e-2,1.910951e-2,2.534271e-2,278.5302,196.893)" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + sodipodi:ry="22.49432" + sodipodi:rx="13.21095" + sodipodi:cy="1631.8588" + sodipodi:cx="1749.9153" + id="path20334" + style="fill:#535353;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + sodipodi:type="arc" + style="fill:#535353;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path20336" + sodipodi:cx="1749.9153" + sodipodi:cy="1631.8588" + sodipodi:rx="13.21095" + sodipodi:ry="22.49432" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + transform="matrix(7.567466e-2,-2.014544e-2,-3.199204e-3,5.823804e-2,342.6725,156.7849)" /> + <path + transform="matrix(7.100825e-2,-1.981071e-2,-2.101203e-3,5.648563e-2,351.1267,160.5739)" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + sodipodi:ry="22.49432" + sodipodi:rx="13.21095" + sodipodi:cy="1631.8588" + sodipodi:cx="1749.9153" + id="path20338" + style="fill:#535353;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + sodipodi:type="arc" + style="fill:#535353;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path20340" + sodipodi:cx="1749.9153" + sodipodi:cy="1631.8588" + sodipodi:rx="13.21095" + sodipodi:ry="22.49432" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + transform="matrix(6.641248e-2,-1.76977e-2,-2.868987e-3,5.114014e-2,362.5056,167.0467)" /> + <path + transform="matrix(6.520531e-2,-2.79968e-2,2.30346e-2,5.423912e-2,312.9703,178.7997)" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + sodipodi:ry="22.49432" + sodipodi:rx="13.21095" + sodipodi:cy="1631.8588" + sodipodi:cx="1749.9153" + id="path20342" + style="fill:#535353;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + </g> + </g> + <g + id="g20360" + transform="matrix(3.219432,0,0,2.909095,-1432.8102,-2643.1429)" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/64x64/actions/amarok_random_no.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Playback-modes/amarok_random_no.png" + transform="matrix(1.230079,0,0,1.238637,63.79075,840.1769)" + id="g20362"> + <path + style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient20418);stroke-width:0.9658882;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 460.60022,220.71135 L 460.60151,211.59252 L 468.21743,208.54037 L 475.83984,211.59055 L 475.83977,220.71483 L 468.2189,225.28922 L 460.60022,220.71135 z " + id="path20364" + sodipodi:nodetypes="ccccccc" /> + <path + sodipodi:nodetypes="ccccc" + id="path20366" + d="M 461.10864,220.42601 L 461.10596,211.93846 L 468.21982,215.4185 L 468.22065,224.6981 L 461.10864,220.42601 z " + style="fill:url(#linearGradient20420);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.0742841;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + sodipodi:nodetypes="ccccc" + id="path20368" + d="M 468.21694,215.41954 L 475.33253,211.93985 L 475.33166,220.42732 L 468.22343,224.69433 L 468.21694,215.41954 z " + style="fill:url(#linearGradient20422);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.07509746;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + sodipodi:nodetypes="ccccc" + id="path20370" + d="M 461.10759,211.93783 L 468.21886,209.08645 L 475.33129,211.93814 L 468.21624,215.418 L 461.10759,211.93783 z " + style="fill:url(#linearGradient20424);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.79999995;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + transform="matrix(0.105331,-1.756922e-2,2.163906e-2,2.954885e-2,248.5999,196.6083)" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + sodipodi:ry="22.49432" + sodipodi:rx="13.21095" + sodipodi:cy="1631.8588" + sodipodi:cx="1749.9153" + id="path20372" + style="fill:#535353;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + sodipodi:type="arc" + style="fill:#535353;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path20374" + sodipodi:cx="1749.9153" + sodipodi:cy="1631.8588" + sodipodi:rx="13.21095" + sodipodi:ry="22.49432" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + transform="matrix(8.831556e-2,-1.393425e-2,1.814339e-2,2.343552e-2,284.0769,196.2814)" /> + <path + transform="matrix(9.301809e-2,-1.50682e-2,1.910951e-2,2.534271e-2,274.2543,196.893)" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + sodipodi:ry="22.49432" + sodipodi:rx="13.21095" + sodipodi:cy="1631.8588" + sodipodi:cx="1749.9153" + id="path20376" + style="fill:#535353;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + sodipodi:type="arc" + style="fill:#535353;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path20378" + sodipodi:cx="1749.9153" + sodipodi:cy="1631.8588" + sodipodi:rx="13.21095" + sodipodi:ry="22.49432" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + transform="matrix(9.301809e-2,-1.50682e-2,1.910951e-2,2.534271e-2,269.926,196.893)" /> + <path + transform="matrix(9.301809e-2,-1.50682e-2,1.910951e-2,2.534271e-2,278.5302,196.893)" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + sodipodi:ry="22.49432" + sodipodi:rx="13.21095" + sodipodi:cy="1631.8588" + sodipodi:cx="1749.9153" + id="path20380" + style="fill:#535353;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + sodipodi:type="arc" + style="fill:#535353;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path20382" + sodipodi:cx="1749.9153" + sodipodi:cy="1631.8588" + sodipodi:rx="13.21095" + sodipodi:ry="22.49432" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + transform="matrix(7.567466e-2,-2.014544e-2,-3.199204e-3,5.823804e-2,342.6725,156.7849)" /> + <path + transform="matrix(7.100825e-2,-1.981071e-2,-2.101203e-3,5.648563e-2,351.1267,160.5739)" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + sodipodi:ry="22.49432" + sodipodi:rx="13.21095" + sodipodi:cy="1631.8588" + sodipodi:cx="1749.9153" + id="path20384" + style="fill:#535353;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + sodipodi:type="arc" + style="fill:#535353;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path20386" + sodipodi:cx="1749.9153" + sodipodi:cy="1631.8588" + sodipodi:rx="13.21095" + sodipodi:ry="22.49432" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + transform="matrix(6.641248e-2,-1.76977e-2,-2.868987e-3,5.114014e-2,362.5056,167.0467)" /> + <path + transform="matrix(6.520531e-2,-2.79968e-2,2.30346e-2,5.423912e-2,312.9703,178.7997)" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + sodipodi:ry="22.49432" + sodipodi:rx="13.21095" + sodipodi:cy="1631.8588" + sodipodi:cx="1749.9153" + id="path20388" + style="fill:#535353;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + </g> + <g + id="g20390" + transform="matrix(1.230079,0,0,1.238637,63.79075,840.1769)" + inkscape:export-filename="/home/lando/Projects/Amarok/Playback-modes/amarok_random_no.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <path + sodipodi:nodetypes="ccccccc" + id="path20392" + d="M 460.60022,220.71135 L 460.60151,211.59252 L 468.21743,208.54037 L 475.83984,211.59055 L 475.83977,220.71483 L 468.2189,225.28922 L 460.60022,220.71135 z " + style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient20426);stroke-width:0.9658882;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + style="fill:url(#linearGradient20428);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.0742841;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 461.10864,220.42601 L 461.10596,211.93846 L 468.21982,215.4185 L 468.22065,224.6981 L 461.10864,220.42601 z " + id="path20394" + sodipodi:nodetypes="ccccc" /> + <path + style="fill:url(#linearGradient20430);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.07509746;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 468.21694,215.41954 L 475.33253,211.93985 L 475.33166,220.42732 L 468.22343,224.69433 L 468.21694,215.41954 z " + id="path20396" + sodipodi:nodetypes="ccccc" /> + <path + style="fill:url(#linearGradient20432);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.79999995;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 461.10759,211.93783 L 468.21886,209.08645 L 475.33129,211.93814 L 468.21624,215.418 L 461.10759,211.93783 z " + id="path20398" + sodipodi:nodetypes="ccccc" /> + <path + sodipodi:type="arc" + style="fill:#535353;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path20400" + sodipodi:cx="1749.9153" + sodipodi:cy="1631.8588" + sodipodi:rx="13.21095" + sodipodi:ry="22.49432" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + transform="matrix(0.105331,-1.756922e-2,2.163906e-2,2.954885e-2,248.5999,196.6083)" /> + <path + transform="matrix(8.831556e-2,-1.393425e-2,1.814339e-2,2.343552e-2,284.0769,196.2814)" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + sodipodi:ry="22.49432" + sodipodi:rx="13.21095" + sodipodi:cy="1631.8588" + sodipodi:cx="1749.9153" + id="path20402" + style="fill:#535353;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + sodipodi:type="arc" + style="fill:#535353;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path20404" + sodipodi:cx="1749.9153" + sodipodi:cy="1631.8588" + sodipodi:rx="13.21095" + sodipodi:ry="22.49432" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + transform="matrix(9.301809e-2,-1.50682e-2,1.910951e-2,2.534271e-2,274.2543,196.893)" /> + <path + transform="matrix(9.301809e-2,-1.50682e-2,1.910951e-2,2.534271e-2,269.926,196.893)" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + sodipodi:ry="22.49432" + sodipodi:rx="13.21095" + sodipodi:cy="1631.8588" + sodipodi:cx="1749.9153" + id="path20406" + style="fill:#535353;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + sodipodi:type="arc" + style="fill:#535353;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path20408" + sodipodi:cx="1749.9153" + sodipodi:cy="1631.8588" + sodipodi:rx="13.21095" + sodipodi:ry="22.49432" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + transform="matrix(9.301809e-2,-1.50682e-2,1.910951e-2,2.534271e-2,278.5302,196.893)" /> + <path + transform="matrix(7.567466e-2,-2.014544e-2,-3.199204e-3,5.823804e-2,342.6725,156.7849)" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + sodipodi:ry="22.49432" + sodipodi:rx="13.21095" + sodipodi:cy="1631.8588" + sodipodi:cx="1749.9153" + id="path20410" + style="fill:#535353;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + sodipodi:type="arc" + style="fill:#535353;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path20412" + sodipodi:cx="1749.9153" + sodipodi:cy="1631.8588" + sodipodi:rx="13.21095" + sodipodi:ry="22.49432" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + transform="matrix(7.100825e-2,-1.981071e-2,-2.101203e-3,5.648563e-2,351.1267,160.5739)" /> + <path + transform="matrix(6.641248e-2,-1.76977e-2,-2.868987e-3,5.114014e-2,362.5056,167.0467)" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + sodipodi:ry="22.49432" + sodipodi:rx="13.21095" + sodipodi:cy="1631.8588" + sodipodi:cx="1749.9153" + id="path20414" + style="fill:#535353;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + sodipodi:type="arc" + style="fill:#535353;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path20416" + sodipodi:cx="1749.9153" + sodipodi:cy="1631.8588" + sodipodi:rx="13.21095" + sodipodi:ry="22.49432" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + transform="matrix(6.520531e-2,-2.79968e-2,2.30346e-2,5.423912e-2,312.9703,178.7997)" /> + </g> + </g> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/32x32/actions/amarok_random.png" + transform="matrix(1.6048105,0,0,1.4602138,-221.488,590.1059)" + id="g20450"> + <path + sodipodi:nodetypes="ccccccc" + id="path20452" + d="M 441.06383,60.713305 L 441.06542,49.495395 L 450.43446,45.740673 L 459.81147,49.492971 L 459.81139,60.717581 L 450.43626,66.344952 L 441.06383,60.713305 z " + style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient20480);stroke-width:1.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <g + id="g20454"> + <path + sodipodi:nodetypes="ccccc" + id="path20456" + d="M 441.68929,60.362282 L 441.68599,49.920973 L 450.4374,54.20208 L 450.43841,65.617769 L 441.68929,60.362282 z " + style="fill:url(#linearGradient20482);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.0742841;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + sodipodi:nodetypes="ccccc" + id="path20458" + d="M 450.43385,54.203364 L 459.18738,49.922686 L 459.18632,60.363888 L 450.44184,65.613128 L 450.43385,54.203364 z " + style="fill:url(#linearGradient20484);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.07509746;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + sodipodi:nodetypes="ccccc" + id="path20460" + d="M 441.688,49.920202 L 450.43621,46.412453 L 459.18586,49.920584 L 450.43299,54.201471 L 441.688,49.920202 z " + style="fill:url(#linearGradient20486);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.79999995;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + <path + sodipodi:type="arc" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path20462" + sodipodi:cx="1749.9153" + sodipodi:cy="1631.8588" + sodipodi:rx="13.21095" + sodipodi:ry="22.49432" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + transform="matrix(0.129577,-2.161349e-2,2.662017e-2,3.635072e-2,180.263,31.06197)" /> + <path + transform="matrix(0.108645,-1.714179e-2,2.231983e-2,2.883016e-2,223.9065,30.65984)" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + sodipodi:ry="22.49432" + sodipodi:rx="13.21095" + sodipodi:cy="1631.8588" + sodipodi:cx="1749.9153" + id="path20464" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + sodipodi:type="arc" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path20466" + sodipodi:cx="1749.9153" + sodipodi:cy="1631.8588" + sodipodi:rx="13.21095" + sodipodi:ry="22.49432" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + transform="matrix(0.11443,-1.853676e-2,2.350834e-2,3.117637e-2,211.8228,31.4122)" /> + <path + transform="matrix(0.11443,-1.853676e-2,2.350834e-2,3.117637e-2,206.4981,31.4122)" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + sodipodi:ry="22.49432" + sodipodi:rx="13.21095" + sodipodi:cy="1631.8588" + sodipodi:cx="1749.9153" + id="path20468" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + sodipodi:type="arc" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path20470" + sodipodi:cx="1749.9153" + sodipodi:cy="1631.8588" + sodipodi:rx="13.21095" + sodipodi:ry="22.49432" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + transform="matrix(0.11443,-1.853676e-2,2.350834e-2,3.117637e-2,217.0829,31.4122)" /> + <path + transform="matrix(9.309428e-2,-2.478274e-2,-3.935631e-3,7.164391e-2,295.9902,-17.92848)" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + sodipodi:ry="22.49432" + sodipodi:rx="13.21095" + sodipodi:cy="1631.8588" + sodipodi:cx="1749.9153" + id="path20472" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + sodipodi:type="arc" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path20474" + sodipodi:cx="1749.9153" + sodipodi:cy="1631.8588" + sodipodi:rx="13.21095" + sodipodi:ry="22.49432" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + transform="matrix(8.73537e-2,-2.437095e-2,-2.584881e-3,6.94881e-2,306.3905,-13.26718)" /> + <path + transform="matrix(8.170002e-2,-2.177155e-2,-3.529402e-3,6.291213e-2,320.3887,-5.30443)" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + sodipodi:ry="22.49432" + sodipodi:rx="13.21095" + sodipodi:cy="1631.8588" + sodipodi:cx="1749.9153" + id="path20476" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + sodipodi:type="arc" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path20478" + sodipodi:cx="1749.9153" + sodipodi:cy="1631.8588" + sodipodi:rx="13.21095" + sodipodi:ry="22.49432" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + transform="matrix(8.021497e-2,-3.444141e-2,2.833695e-2,6.672447e-2,259.4509,9.15401)" /> + </g> + <g + id="g20488" + transform="matrix(1.1033073,0,0,1.0038969,-49.875298,620.67921)" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/22x22/actions/amarok_random.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <path + style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient20518);stroke-width:1.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 441.06383,60.713305 L 441.06542,49.495395 L 450.43446,45.740673 L 459.81147,49.492971 L 459.81139,60.717581 L 450.43626,66.344952 L 441.06383,60.713305 z " + id="path20490" + sodipodi:nodetypes="ccccccc" /> + <g + id="g20492"> + <path + style="fill:url(#linearGradient20520);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.0742841;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 441.68929,60.362282 L 441.68599,49.920973 L 450.4374,54.20208 L 450.43841,65.617769 L 441.68929,60.362282 z " + id="path20494" + sodipodi:nodetypes="ccccc" /> + <path + style="fill:url(#linearGradient20522);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.07509746;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 450.43385,54.203364 L 459.18738,49.922686 L 459.18632,60.363888 L 450.44184,65.613128 L 450.43385,54.203364 z " + id="path20496" + sodipodi:nodetypes="ccccc" /> + <path + style="fill:url(#linearGradient20524);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.79999995;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 441.688,49.920202 L 450.43621,46.412453 L 459.18586,49.920584 L 450.43299,54.201471 L 441.688,49.920202 z " + id="path20498" + sodipodi:nodetypes="ccccc" /> + </g> + <path + transform="matrix(0.129577,-2.161349e-2,2.662017e-2,3.635072e-2,180.263,31.06197)" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + sodipodi:ry="22.49432" + sodipodi:rx="13.21095" + sodipodi:cy="1631.8588" + sodipodi:cx="1749.9153" + id="path20500" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + sodipodi:type="arc" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path20502" + sodipodi:cx="1749.9153" + sodipodi:cy="1631.8588" + sodipodi:rx="13.21095" + sodipodi:ry="22.49432" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + transform="matrix(0.108645,-1.714179e-2,2.231983e-2,2.883016e-2,223.9065,30.65984)" /> + <path + transform="matrix(0.11443,-1.853676e-2,2.350834e-2,3.117637e-2,211.8228,31.4122)" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + sodipodi:ry="22.49432" + sodipodi:rx="13.21095" + sodipodi:cy="1631.8588" + sodipodi:cx="1749.9153" + id="path20504" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + sodipodi:type="arc" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path20506" + sodipodi:cx="1749.9153" + sodipodi:cy="1631.8588" + sodipodi:rx="13.21095" + sodipodi:ry="22.49432" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + transform="matrix(0.11443,-1.853676e-2,2.350834e-2,3.117637e-2,206.4981,31.4122)" /> + <path + transform="matrix(0.11443,-1.853676e-2,2.350834e-2,3.117637e-2,217.0829,31.4122)" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + sodipodi:ry="22.49432" + sodipodi:rx="13.21095" + sodipodi:cy="1631.8588" + sodipodi:cx="1749.9153" + id="path20508" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + sodipodi:type="arc" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path20510" + sodipodi:cx="1749.9153" + sodipodi:cy="1631.8588" + sodipodi:rx="13.21095" + sodipodi:ry="22.49432" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + transform="matrix(9.309428e-2,-2.478274e-2,-3.935631e-3,7.164391e-2,295.9902,-17.92848)" /> + <path + transform="matrix(8.73537e-2,-2.437095e-2,-2.584881e-3,6.94881e-2,306.3905,-13.26718)" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + sodipodi:ry="22.49432" + sodipodi:rx="13.21095" + sodipodi:cy="1631.8588" + sodipodi:cx="1749.9153" + id="path20512" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + sodipodi:type="arc" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path20514" + sodipodi:cx="1749.9153" + sodipodi:cy="1631.8588" + sodipodi:rx="13.21095" + sodipodi:ry="22.49432" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + transform="matrix(8.170002e-2,-2.177155e-2,-3.529402e-3,6.291213e-2,320.3887,-5.30443)" /> + <path + transform="matrix(8.021497e-2,-3.444141e-2,2.833695e-2,6.672447e-2,259.4509,9.15401)" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + sodipodi:ry="22.49432" + sodipodi:rx="13.21095" + sodipodi:cy="1631.8588" + sodipodi:cx="1749.9153" + id="path20516" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + </g> + <g + id="g20526" + transform="matrix(3.2243279,0,0,2.9337727,-805.98291,491.52368)" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/64x64/actions/amarok_random.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <path + style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient10418);stroke-width:1.15474308;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 441.06383,60.713305 L 441.06542,49.495395 L 450.43446,45.740673 L 459.81147,49.492971 L 459.81139,60.717581 L 450.43626,66.344952 L 441.06383,60.713305 z " + id="path20528" + sodipodi:nodetypes="ccccccc" /> + <g + id="g20530"> + <path + style="fill:url(#linearGradient10420);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.0742841;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 441.68929,60.362282 L 441.68599,49.920973 L 450.4374,54.20208 L 450.43841,65.617769 L 441.68929,60.362282 z " + id="path20532" + sodipodi:nodetypes="ccccc" /> + <path + style="fill:url(#linearGradient10423);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.07509746;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 450.43385,54.203364 L 459.18738,49.922686 L 459.18632,60.363888 L 450.44184,65.613128 L 450.43385,54.203364 z " + id="path20534" + sodipodi:nodetypes="ccccc" /> + <path + style="fill:url(#linearGradient10425);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.79999995;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 441.688,49.920202 L 450.43621,46.412453 L 459.18586,49.920584 L 450.43299,54.201471 L 441.688,49.920202 z " + id="path20536" + sodipodi:nodetypes="ccccc" /> + </g> + <path + transform="matrix(0.129577,-2.161349e-2,2.662017e-2,3.635072e-2,180.263,31.06197)" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + sodipodi:ry="22.49432" + sodipodi:rx="13.21095" + sodipodi:cy="1631.8588" + sodipodi:cx="1749.9153" + id="path20538" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + sodipodi:type="arc" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path20540" + sodipodi:cx="1749.9153" + sodipodi:cy="1631.8588" + sodipodi:rx="13.21095" + sodipodi:ry="22.49432" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + transform="matrix(0.108645,-1.714179e-2,2.231983e-2,2.883016e-2,223.9065,30.65984)" /> + <path + transform="matrix(0.11443,-1.853676e-2,2.350834e-2,3.117637e-2,211.8228,31.4122)" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + sodipodi:ry="22.49432" + sodipodi:rx="13.21095" + sodipodi:cy="1631.8588" + sodipodi:cx="1749.9153" + id="path20542" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + sodipodi:type="arc" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path20544" + sodipodi:cx="1749.9153" + sodipodi:cy="1631.8588" + sodipodi:rx="13.21095" + sodipodi:ry="22.49432" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + transform="matrix(0.11443,-1.853676e-2,2.350834e-2,3.117637e-2,206.4981,31.4122)" /> + <path + transform="matrix(0.11443,-1.853676e-2,2.350834e-2,3.117637e-2,217.0829,31.4122)" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + sodipodi:ry="22.49432" + sodipodi:rx="13.21095" + sodipodi:cy="1631.8588" + sodipodi:cx="1749.9153" + id="path20546" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + sodipodi:type="arc" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path20548" + sodipodi:cx="1749.9153" + sodipodi:cy="1631.8588" + sodipodi:rx="13.21095" + sodipodi:ry="22.49432" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + transform="matrix(9.309428e-2,-2.478274e-2,-3.935631e-3,7.164391e-2,295.9902,-17.92848)" /> + <path + transform="matrix(8.73537e-2,-2.437095e-2,-2.584881e-3,6.94881e-2,306.3905,-13.26718)" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + sodipodi:ry="22.49432" + sodipodi:rx="13.21095" + sodipodi:cy="1631.8588" + sodipodi:cx="1749.9153" + id="path20550" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + sodipodi:type="arc" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path20552" + sodipodi:cx="1749.9153" + sodipodi:cy="1631.8588" + sodipodi:rx="13.21095" + sodipodi:ry="22.49432" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + transform="matrix(8.170002e-2,-2.177155e-2,-3.529402e-3,6.291213e-2,320.3887,-5.30443)" /> + <path + transform="matrix(8.021497e-2,-3.444141e-2,2.833695e-2,6.672447e-2,259.4509,9.15401)" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + sodipodi:ry="22.49432" + sodipodi:rx="13.21095" + sodipodi:cy="1631.8588" + sodipodi:cx="1749.9153" + id="path20554" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + </g> + <g + id="g20738" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/16x16/actions/amarok_editcopy.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" + transform="matrix(0.763202,0,0,0.727271,63.552552,573.7489)"> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_editcopy.png" + style="fill:url(#linearGradient20752);fill-opacity:1;stroke:url(#linearGradient20754);stroke-width:0.74211508;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 467.40952,356.81279 L 477.66775,356.81279 C 478.74493,356.81279 479.61211,357.78674 479.61211,358.99651 L 479.61989,369.13266 L 476.34175,372.98901 L 467.40952,372.98901 C 466.33235,372.98901 465.46516,372.01506 465.46516,370.80528 L 465.46516,358.99651 C 465.46516,357.78674 466.33235,356.81279 467.40952,356.81279 z " + id="path20740" + sodipodi:nodetypes="ccccccccc" /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_editcopy.png" + style="fill:url(#linearGradient20756);fill-opacity:1;stroke:none;stroke-width:0.90379584;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 467.41413,357.1764 L 477.67093,357.1764 C 478.53023,357.1764 479.22202,357.98439 479.22202,358.98804 L 479.22202,369.71006 L 476.66623,372.57368 L 467.41413,372.6254 C 466.55482,372.6254 465.86302,371.81742 465.86302,370.81377 L 465.86302,358.98804 C 465.86302,357.98439 466.55482,357.1764 467.41413,357.1764 z " + id="path20742" + sodipodi:nodetypes="ccccccccc" /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_editcopy.png" + style="fill:#0004c1;fill-opacity:1;stroke:none;stroke-width:1.0039922;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 476.13488,370.48751 L 476.12869,373.36218 C 476.12869,373.36218 479.92907,368.874 479.92907,368.874 L 477.75567,368.81341 C 476.79482,368.82351 476.1369,369.5494 476.13488,370.48751 z " + id="path20744" + sodipodi:nodetypes="ccccz" /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_editcopy.png" + sodipodi:nodetypes="ccccccccc" + id="path20746" + d="M 461.32442,351.74918 L 471.58265,351.74918 C 472.65983,351.74918 473.52701,352.72313 473.52701,353.9329 L 473.53479,364.06905 L 470.25665,367.9254 L 461.32442,367.9254 C 460.24725,367.9254 459.38006,366.95145 459.38006,365.74167 L 459.38006,353.9329 C 459.38006,352.72313 460.24725,351.74918 461.32442,351.74918 z " + style="fill:url(#linearGradient20758);fill-opacity:1;stroke:url(#linearGradient20760);stroke-width:0.74211508;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_editcopy.png" + sodipodi:nodetypes="ccccccccc" + id="path20748" + d="M 461.32903,352.11279 L 471.58583,352.11279 C 472.44513,352.11279 473.13692,352.92078 473.13692,353.92443 L 473.13692,364.64645 L 470.58113,367.51007 L 461.32903,367.56179 C 460.46972,367.56179 459.77792,366.75381 459.77792,365.75016 L 459.77792,353.92443 C 459.77792,352.92078 460.46972,352.11279 461.32903,352.11279 z " + style="fill:url(#linearGradient20762);fill-opacity:1;stroke:none;stroke-width:0.90379584;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_editcopy.png" + sodipodi:nodetypes="ccccz" + id="path20750" + d="M 470.04978,365.4239 L 470.04359,368.29857 C 470.04359,368.29857 473.93315,363.75068 473.93315,363.75068 L 471.67057,363.7498 C 470.70972,363.7599 470.0518,364.48579 470.04978,365.4239 z " + style="fill:#0004c1;fill-opacity:1;stroke:none;stroke-width:1.0039922;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" /> + </g> + <g + transform="matrix(2.3079556,0,0,2.194353,-526.76359,26.001014)" + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/48x48/actions/amarok_editcopy.png" + id="g20764"> + <path + sodipodi:nodetypes="ccccccccc" + id="path20766" + d="M 467.40952,356.81279 L 477.66775,356.81279 C 478.74493,356.81279 479.61211,357.78674 479.61211,358.99651 L 479.61989,369.13266 L 476.34175,372.98901 L 467.40952,372.98901 C 466.33235,372.98901 465.46516,372.01506 465.46516,370.80528 L 465.46516,358.99651 C 465.46516,357.78674 466.33235,356.81279 467.40952,356.81279 z " + style="fill:url(#linearGradient20778);fill-opacity:1;stroke:url(#linearGradient20780);stroke-width:0.50963145;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_editcopy.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:nodetypes="ccccccccc" + id="path20768" + d="M 467.41413,357.1764 L 477.67093,357.1764 C 478.53023,357.1764 479.22202,357.98439 479.22202,358.98804 L 479.22202,369.71006 L 476.66623,372.57368 L 467.41413,372.6254 C 466.55482,372.6254 465.86302,371.81742 465.86302,370.81377 L 465.86302,358.98804 C 465.86302,357.98439 466.55482,357.1764 467.41413,357.1764 z " + style="fill:url(#linearGradient20782);fill-opacity:1;stroke:none;stroke-width:0.90379584;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_editcopy.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:nodetypes="ccccz" + id="path20770" + d="M 476.13488,370.48751 L 476.12869,373.36218 C 476.12869,373.36218 479.92907,368.874 479.92907,368.874 L 477.75567,368.81341 C 476.79482,368.82351 476.1369,369.5494 476.13488,370.48751 z " + style="fill:#0004c1;fill-opacity:1;stroke:none;stroke-width:1.0039922;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_editcopy.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + style="fill:url(#linearGradient20784);fill-opacity:1;stroke:url(#linearGradient20786);stroke-width:0.50963145;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 461.32442,351.74918 L 471.58265,351.74918 C 472.65983,351.74918 473.52701,352.72313 473.52701,353.9329 L 473.53479,364.06905 L 470.25665,367.9254 L 461.32442,367.9254 C 460.24725,367.9254 459.38006,366.95145 459.38006,365.74167 L 459.38006,353.9329 C 459.38006,352.72313 460.24725,351.74918 461.32442,351.74918 z " + id="path20772" + sodipodi:nodetypes="ccccccccc" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_editcopy.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + style="fill:url(#linearGradient20788);fill-opacity:1;stroke:none;stroke-width:0.90379584;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 461.32903,352.11279 L 471.58583,352.11279 C 472.44513,352.11279 473.13692,352.92078 473.13692,353.92443 L 473.13692,364.64645 L 470.58113,367.51007 L 461.32903,367.56179 C 460.46972,367.56179 459.77792,366.75381 459.77792,365.75016 L 459.77792,353.92443 C 459.77792,352.92078 460.46972,352.11279 461.32903,352.11279 z " + id="path20774" + sodipodi:nodetypes="ccccccccc" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_editcopy.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + style="fill:#0004c1;fill-opacity:1;stroke:none;stroke-width:1.0039922;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 470.04978,365.4239 L 470.04359,368.29857 C 470.04359,368.29857 473.93315,363.75068 473.93315,363.75068 L 471.67057,363.7498 C 470.70972,363.7599 470.0518,364.48579 470.04978,365.4239 z " + id="path20776" + sodipodi:nodetypes="ccccz" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_editcopy.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + </g> + <g + id="g20790" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/64x64/actions/amarok_editcopy.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" + transform="matrix(3.0772741,0,0,2.925804,-819.9722,-247.09512)"> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_editcopy.png" + style="fill:url(#linearGradient20804);fill-opacity:1;stroke:url(#linearGradient20806);stroke-width:0.50963145;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 467.40952,356.81279 L 477.66775,356.81279 C 478.74493,356.81279 479.61211,357.78674 479.61211,358.99651 L 479.61989,369.13266 L 476.34175,372.98901 L 467.40952,372.98901 C 466.33235,372.98901 465.46516,372.01506 465.46516,370.80528 L 465.46516,358.99651 C 465.46516,357.78674 466.33235,356.81279 467.40952,356.81279 z " + id="path20792" + sodipodi:nodetypes="ccccccccc" /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_editcopy.png" + style="fill:url(#linearGradient20808);fill-opacity:1;stroke:none;stroke-width:0.90379584;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 467.41413,357.1764 L 477.67093,357.1764 C 478.53023,357.1764 479.22202,357.98439 479.22202,358.98804 L 479.22202,369.71006 L 476.66623,372.57368 L 467.41413,372.6254 C 466.55482,372.6254 465.86302,371.81742 465.86302,370.81377 L 465.86302,358.98804 C 465.86302,357.98439 466.55482,357.1764 467.41413,357.1764 z " + id="path20794" + sodipodi:nodetypes="ccccccccc" /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_editcopy.png" + style="fill:#0004c1;fill-opacity:1;stroke:none;stroke-width:1.0039922;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 476.13488,370.48751 L 476.12869,373.36218 C 476.12869,373.36218 479.92907,368.874 479.92907,368.874 L 477.75567,368.81341 C 476.79482,368.82351 476.1369,369.5494 476.13488,370.48751 z " + id="path20796" + sodipodi:nodetypes="ccccz" /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_editcopy.png" + sodipodi:nodetypes="ccccccccc" + id="path20798" + d="M 461.32442,351.74918 L 471.58265,351.74918 C 472.65983,351.74918 473.52701,352.72313 473.52701,353.9329 L 473.53479,364.06905 L 470.25665,367.9254 L 461.32442,367.9254 C 460.24725,367.9254 459.38006,366.95145 459.38006,365.74167 L 459.38006,353.9329 C 459.38006,352.72313 460.24725,351.74918 461.32442,351.74918 z " + style="fill:url(#linearGradient20810);fill-opacity:1;stroke:url(#linearGradient20812);stroke-width:0.50963145;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_editcopy.png" + sodipodi:nodetypes="ccccccccc" + id="path20800" + d="M 461.32903,352.11279 L 471.58583,352.11279 C 472.44513,352.11279 473.13692,352.92078 473.13692,353.92443 L 473.13692,364.64645 L 470.58113,367.51007 L 461.32903,367.56179 C 460.46972,367.56179 459.77792,366.75381 459.77792,365.75016 L 459.77792,353.92443 C 459.77792,352.92078 460.46972,352.11279 461.32903,352.11279 z " + style="fill:url(#linearGradient20814);fill-opacity:1;stroke:none;stroke-width:0.90379584;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_editcopy.png" + sodipodi:nodetypes="ccccz" + id="path20802" + d="M 470.04978,365.4239 L 470.04359,368.29857 C 470.04359,368.29857 473.93315,363.75068 473.93315,363.75068 L 471.67057,363.7498 C 470.70972,363.7599 470.0518,364.48579 470.04978,365.4239 z " + style="fill:#0004c1;fill-opacity:1;stroke:none;stroke-width:1.0039922;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" /> + </g> + <g + id="g21069" + transform="matrix(0.712398,0,0,0.7128978,-25.777518,1133.5767)" + inkscape:export-filename="/home/lando/Projects/Amarok/icons/16/amarok_burn.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <path + id="path21071" + d="M 607.83307,31.237603 C 608.24048,35.842013 612.30547,39.258293 616.90891,38.863643 C 621.51235,38.468993 624.91853,34.412213 624.51112,29.807803 C 624.10371,25.203403 620.03634,21.787323 615.43289,22.181974 C 610.82945,22.576624 607.42567,26.633203 607.83307,31.237603 z M 613.72957,30.732103 C 613.60593,29.334713 614.59852,28.107463 615.94696,27.991863 C 617.2954,27.876253 618.49098,28.915923 618.61462,30.313313 C 618.73826,31.710703 617.74328,32.938123 616.39484,33.053723 C 615.0464,33.169323 613.85321,32.129453 613.72957,30.732103 z " + style="fill:url(#linearGradient21079);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient21081);stroke-width:1.27565432;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + sodipodi:nodetypes="ccccc" + id="path21073" + d="M 615.85833,30.599293 C 615.73529,30.529403 615.51503,30.485583 615.42747,30.362183 L 610.31487,35.605313 C 611.80287,37.171773 613.63379,38.286723 615.71924,38.392943 L 615.85833,30.599293 z " + style="fill:url(#linearGradient21083);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.4984282;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" /> + <path + sodipodi:nodetypes="ccccc" + id="path21075" + d="M 616.31687,30.412783 C 616.43963,30.481953 616.54722,30.574463 616.63452,30.696643 L 622,25.358533 C 620.51592,23.807285 618.54198,22.794376 616.46069,22.690404 L 616.31687,30.412783 z " + style="fill:url(#linearGradient21085);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.49565426;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/vadim/collection.png" + id="path21077" + d="M 610.32516,39.184893 L 610.38371,37.421143 C 607.44894,36.851173 604.87266,34.684693 603.82801,31.516583 C 603.2175,29.665023 603.22664,27.754823 603.73169,26.022303 C 603.71907,26.765963 603.82863,27.514423 604.06905,28.243513 C 604.97875,31.002443 607.55183,32.750053 610.53112,32.995283 L 610.62074,30.279653 L 616.51123,35.121563 L 610.32516,39.184893 z " + style="fill:url(#linearGradient21087);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient21089);stroke-width:1.27565432;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + <g + id="g10437" + transform="translate(-182.786,-487.90576)" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/64x64/actions/amarok_download.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <g + id="g21215" + transform="matrix(1.7997159,0,0,1.7706507,-53.063771,707.35048)"> + <path + d="M 475.96622,573.73445 C 484.42079,573.73445 491.27867,566.81816 491.27867,558.33367 C 491.27867,549.90281 484.42011,542.98445 475.96622,542.98445 C 467.57327,542.98445 460.65719,549.90281 460.65719,558.33367 C 460.65719,566.81884 467.57327,573.73445 475.96622,573.73445 z " + style="fill:url(#linearGradient10555);fill-opacity:1;stroke:url(#linearGradient10557);stroke-width:1.36656916;stroke-miterlimit:4;stroke-dasharray:none" + id="path21217" /> + <path + d="M 471.03814,544.70319 C 470.96367,544.70319 470.88261,544.73417 470.80022,544.78095 C 470.99597,544.72362 471.08759,544.70319 471.03814,544.70319 z M 475.88139,545.6489 L 475.93015,544.93912 L 475.18281,544.98592 L 475.28167,545.6489 L 475.88139,545.6489 z M 467.44378,557.89508 C 467.29285,557.75472 467.39433,557.13852 467.39433,557.13852 C 467.39433,557.13852 465.14769,555.95753 462.69938,555.24841 C 462.39688,555.15943 462.54976,554.53929 462.84963,554.3027 L 462.75012,553.63971 C 462.70004,553.30952 463.24967,551.70083 463.79929,551.55978 C 464.34893,551.4181 463.74986,552.50551 463.74986,552.50551 L 463.19958,552.83699 C 463.19958,552.83699 463.84806,553.59225 463.99964,553.59225 C 464.1499,553.59225 464.39837,553.21331 464.39837,553.21331 L 463.69847,552.74144 L 464.34828,552.45608 L 464.38914,552.20761 L 464.49723,552.174 L 465.5721,550.54553 C 466.31484,550.24039 467.22827,549.8641 467.34359,549.80872 C 467.54394,549.71448 468.94306,548.91179 469.19153,548.72265 C 469.44261,548.53151 469.98961,548.57962 470.18996,548.57962 C 470.3903,548.57962 470.69016,548.48539 470.7396,547.96409 C 470.79101,547.44346 470.99003,547.34922 471.13963,547.49288 C 471.28922,547.63259 470.98938,547.86986 471.33998,547.96409 C 471.68926,548.06031 471.9878,548.29624 472.23758,548.06031 C 472.42081,547.88634 472.17629,547.68927 472.01088,547.54034 L 474.78409,547.54034 L 475.08461,546.68755 L 474.43613,546.59396 L 472.03724,546.35869 L 472.03724,546.07464 L 471.84612,546.10693 C 472.1071,544.67946 473.64792,544.91606 472.48736,544.08963 C 472.4175,544.03756 471.40194,545.68976 471.14359,545.65682 C 470.67566,545.59551 470.06937,545.59091 469.9415,545.74182 C 469.77148,545.94218 470.32374,545.05116 470.80154,544.7803 C 470.04169,545.00503 467.63884,545.82684 465.09825,548.5322 C 462.66906,551.11955 461.7016,554.6052 461.7016,554.72645 C 461.7016,554.96238 462.20246,555.05862 462.25254,555.34199 C 462.30131,555.62471 461.30419,556.56977 461.30419,557.04362 C 461.30419,557.26176 461.06893,560.17603 461.90194,562.57687 C 462.87664,565.37777 464.95724,567.71734 465.19909,567.87222 L 465.69733,567.63562 C 465.69733,567.63562 464.54928,565.60251 464.49919,565.41337 C 464.44976,565.22488 465.79881,562.48132 466.44665,562.57752 C 467.09514,562.66981 466.94619,562.86093 467.34492,562.623 C 467.74693,562.38773 467.99405,560.44687 468.44286,560.25971 C 468.89364,560.06926 469.39385,559.83266 469.34309,559.36145 C 469.29039,558.88958 467.59403,558.03876 467.44378,557.89508 z M 478.27896,543.99341 L 476.99251,543.73705 L 477.17968,544.27811 L 478.27896,543.99341 z M 473.23469,545.74249 C 473.38627,545.74249 476.3816,543.75748 476.08041,543.71069 C 475.78252,543.66389 475.70805,543.86095 474.90734,543.7667 C 474.10924,543.67114 473.2848,544.74998 473.08576,544.93912 C 472.88609,545.12893 472.95329,545.74249 473.23469,545.74249 z M 486.98282,560.00072 L 487.37033,559.49984 L 486.98282,559.36341 L 486.69418,559.72786 L 486.35478,560.22873 L 486.64541,560.36648 L 486.98282,560.00072 z M 488.67524,561.64236 L 488.57836,560.91347 L 487.95096,560.91347 L 487.90218,561.45849 L 487.32157,561.36952 L 487.17724,560.77574 L 486.88859,560.59515 L 486.54918,561.00442 L 486.21043,560.91413 L 486.11357,561.23047 L 486.50108,561.32404 L 486.50108,564.28509 L 487.86922,564.61461 C 487.83694,564.66998 487.81453,564.71479 487.80532,564.74115 C 487.70844,565.05816 488.19151,565.19654 488.53025,565.05816 C 488.65546,565.00805 489.05352,564.6067 489.30395,564.01093 C 489.65193,563.18385 489.9564,562.09051 490.01702,561.70298 L 490.17257,561.36952 L 489.20641,561.73331 L 488.67524,561.64236 z M 490.5179,556.03925 L 490.45266,555.63922 C 490.15278,553.95341 489.46344,551.79903 487.99905,549.56489 C 485.44859,545.67857 479.8692,544.07777 479.8692,544.07777 L 479.29783,544.68869 L 479.10539,544.37038 L 478.62166,544.1865 L 478.62166,544.59774 L 479.05596,544.96351 L 478.76665,545.09993 L 477.6542,545.19022 L 475.14195,546.55837 L 475.38248,547.65238 L 475.0932,547.74465 L 474.9482,547.97135 L 475.77001,549.2011 L 475.81681,549.61168 L 475.14195,549.74942 L 475.14195,550.56991 L 474.75576,550.65955 L 474.80387,551.29815 L 471.51594,553.57775 L 471.61216,554.85233 C 471.85469,555.17063 473.74019,557.08645 473.74019,557.08645 C 473.74019,557.08645 475.91501,557.17675 476.39742,556.90324 C 476.8805,556.62975 476.54306,557.17675 476.68806,557.31446 C 476.83304,557.45156 476.8805,558.40782 477.02548,558.49943 C 477.17113,558.58971 477.02548,559.13605 477.21791,559.31927 C 477.41167,559.50116 477.41167,561.68982 477.41167,561.68982 C 477.41167,561.68982 478.57223,563.64913 478.57223,564.15001 C 478.57223,564.65152 478.52543,564.60604 479.44215,564.56123 C 480.36152,564.51443 480.55394,564.15001 480.74639,564.01356 C 480.94146,563.8765 480.94146,563.55818 481.1339,563.28402 C 481.32766,563.00988 481.66574,561.96267 482.10136,561.59886 C 482.53566,561.23243 483.6949,560.95894 483.79113,560.32232 C 483.88801,559.68305 484.32428,559.18217 484.32428,559.18217 L 486.41277,556.97179 L 486.35347,557.26967 L 486.30666,558.40913 L 486.93339,558.18177 L 486.8866,556.9507 L 486.6599,556.71147 L 486.69285,556.67588 C 486.69285,556.67588 486.5472,556.40435 486.35347,556.40435 C 486.16101,556.40435 485.00045,556.67588 484.80801,556.63172 C 484.61294,556.58558 483.79113,554.39891 483.64615,554.30863 C 483.5018,554.21702 482.58377,552.7131 482.58377,552.7131 C 482.58377,552.7131 484.71048,555.26555 485.04724,556.26793 C 485.24494,556.85119 485.98176,556.30815 486.58213,555.69852 L 486.73899,556.0867 L 487.12651,555.99311 L 487.07839,555.53971 L 487.51401,555.53971 L 487.51401,556.22246 L 487.36837,556.58691 L 487.31894,557.17938 L 487.70578,557.54383 L 487.89955,557.22682 L 488.52759,556.63303 L 489.25321,556.26859 L 489.44631,556.63303 L 489.54318,557.13589 L 489.34942,557.68089 L 488.96192,558.00053 L 488.76816,558.82102 L 488.76816,559.22963 L 488.33385,558.95681 L 488.28575,558.09148 L 487.659,558.13828 L 487.36903,558.91133 L 487.80333,559.55059 L 488.81758,559.6857 L 489.64004,558.91198 L 489.73628,557.41005 L 490.10271,556.92564 C 490.34061,557.5379 490.50932,558.17914 490.50932,558.77556 C 490.50932,559.42998 490.80919,558.32346 490.55479,556.32791 L 490.5179,556.03925 z M 478.5261,551.47872 L 475.91566,551.38779 L 477.02679,550.47699 L 477.60673,550.47699 L 478.52675,551.11428 L 478.52675,551.47872 L 478.5261,551.47872 z M 481.71516,551.1604 L 481.71516,551.57033 L 480.60402,551.57033 L 480.70025,551.84515 L 480.02408,551.93676 L 479.97465,552.16477 L 479.49224,552.07317 L 478.62232,551.89063 L 478.7673,551.66195 L 478.91295,551.38779 L 479.39538,550.88691 L 479.58911,551.25071 L 480.31405,551.20588 L 480.70157,550.79598 L 482.19824,551.0688 L 481.71516,551.1604 z M 481.81205,550.56861 L 481.23275,550.65823 L 481.13587,550.24831 L 481.86016,550.15737 L 481.95638,549.7481 L 482.48887,550.29445 L 481.81205,550.56861 z M 484.61425,564.7418 L 484.27684,565.01398 L 484.32493,565.69739 L 484.75925,565.69739 L 484.75925,565.10625 L 485.14676,564.60473 L 485.14676,563.55687 L 484.90489,563.51007 L 484.61425,564.7418 z M 481.37708,563.37299 C 481.37708,563.37299 481.03965,563.4626 481.42452,563.60167 C 481.81139,563.7381 483.35814,561.36952 483.35814,561.36952 L 482.0539,562.18805 L 481.37708,563.37299 z M 478.74754,572.10915 L 478.45689,571.83302 L 477.8776,571.74075 L 477.78138,572.01623 L 477.00701,571.92463 L 476.96021,571.5582 L 476.38027,571.5582 L 475.75223,571.92463 L 474.64109,571.92463 L 474.54553,571.65047 L 472.75624,571.46528 L 472.46758,571.74075 L 471.74265,571.55886 L 471.64775,570.91499 L 471.30902,570.8682 L 470.9228,571.55886 L 469.61856,571.51207 C 469.85188,571.62279 471.79866,572.78467 474.78608,573.02785 C 478.74819,573.34813 480.63039,572.38397 480.63039,572.38397 L 480.4854,572.24689 L 478.74754,572.10915 z " + style="fill:url(#radialGradient10559);fill-opacity:1" + id="path21219" /> + <path + d="M 463.22765,552.78788 C 463.22631,552.71934 463.22104,552.6508 463.22104,552.58227 C 463.22104,552.36544 463.23489,552.15126 463.25664,551.93971 C 462.87044,552.47023 462.56729,553.45218 462.6055,553.70196 L 462.70437,554.37088 C 462.40583,554.60945 462.25294,555.23554 462.55476,555.32451 C 463.07078,555.47544 463.57759,555.64876 464.05604,555.82539 C 463.60197,554.97391 463.31925,554.05981 463.24346,553.10752 C 463.13339,552.98758 463.05365,552.89266 463.05365,552.89266 L 463.22765,552.78788 z " + style="fill:none" + id="path21221" /> + </g> + <path + style="fill:url(#linearGradient10561);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient10563);stroke-width:2.4977715;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 769.56671,1702.7194 L 774.41924,1702.7107 C 775.70957,1689.2271 781.42102,1677.1298 790.02919,1671.8664 C 795.0601,1668.7904 799.29969,1668.5333 804.61379,1668.9938 C 802.56891,1669.0526 798.75352,1673.079 795.25637,1675.8638 C 790.37513,1680.3116 786.98967,1689.0551 786.59596,1702.6912 L 794.06734,1702.6755 L 781.31513,1730.3175 L 769.56671,1702.7194 z " + id="path21223" + inkscape:export-filename="/home/vadim/collection.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" + sodipodi:nodetypes="ccscccccc" /> + </g> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/22x22/actions/amarok_save.png" + id="g8091" + transform="matrix(1.047619,0,0,1.047619,187.47881,39.237912)"> + <rect + ry="1.1565754" + rx="1.0676081" + y="-37.008583" + x="-548.41418" + height="20" + width="20" + id="rect8093" + style="fill:url(#linearGradient8335);fill-opacity:1;stroke:url(#linearGradient8337);stroke-width:1;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + transform="scale(-1,-1)" /> + <path + style="fill:url(#linearGradient8339);fill-opacity:1;stroke:none;stroke-width:0.71899998;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 543.41423,36.654983 L 543.41423,29.654983 L 532.41423,29.654983 L 532.41423,36.654983 L 543.41423,36.654983 z M 537.41423,36.154983 L 534.41423,36.154983 L 534.41423,30.154983 L 537.41423,30.154983 L 537.41423,36.154983 z " + id="path8095" /> + <rect + style="fill:url(#linearGradient8341);fill-opacity:1;stroke:url(#linearGradient8343);stroke-width:1;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect8097" + width="15.055487" + height="10.37806" + x="-545.96246" + y="-28.838623" + transform="scale(-1,-1)" + ry="1.2455429" + rx="0.97864074" /> + </g> + <g + id="g8099" + transform="translate(210.80551,76.929312)" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/22x22/amarok_repeat_track.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <text + xml:space="preserve" + style="font-size:14.98905945px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#0368ff;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Nimbus Sans L" + x="501.99185" + y="75.245209" + id="text8101" + sodipodi:linespacing="125%" + transform="scale(1.068059,0.936278)" + inkscape:export-filename="/home/lando/Projects/Amarok/Playback-modes/amarok_repeat_track.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"><tspan + sodipodi:role="line" + id="tspan8103" + x="501.99185" + y="75.245209">1</tspan></text> + <path + id="path8105" + d="M 534.63898,54.69023 L 535.4538,56.89492 C 534.56046,57.334793 533.72544,57.877113 533.00935,58.536711 C 531.02779,60.361948 529.75009,62.923772 529.75009,65.713683 C 529.75009,68.503594 531.03331,71.023599 533.00935,72.843747 C 533.99461,73.751276 535.2026,74.486723 536.52324,75.001529 C 537.19621,75.263855 537.86798,75.433821 538.56028,75.564429 L 537.69454,74.297904 L 539.01861,73.31283 C 538.44307,73.204243 537.92334,73.079099 537.43991,72.890655 C 536.42771,72.4961 535.55539,71.90534 534.79176,71.201956 C 533.26449,69.795172 532.34731,67.87019 532.34731,65.713683 C 532.34731,63.557192 533.26449,61.632195 534.79176,60.225411 C 535.2547,59.798988 535.76975,59.424637 536.31954,59.099611 L 537.08343,61.116668 L 542.3288,56.754195 L 534.63898,54.69023 z M 542.93991,55.816029 L 543.9075,57.270187 L 542.48157,58.067628 C 543.01262,58.167809 543.53661,58.332581 544.06028,58.536711 C 545.07247,58.931267 545.9448,59.522026 546.70843,60.225411 C 548.2357,61.63221 549.15287,63.557192 549.15287,65.713683 C 549.15287,67.87019 548.2357,69.795172 546.70843,71.201956 C 546.27889,71.597607 545.83731,71.971342 545.33343,72.280847 L 544.56954,70.263789 L 539.32417,74.626262 L 547.01398,76.690228 L 546.14824,74.438629 C 547.02295,73.993008 547.84961,73.481294 548.54176,72.843747 C 550.52333,71.01851 551.7501,68.494153 551.7501,65.713683 C 551.7501,62.933214 550.52885,60.367036 548.54176,58.536711 C 547.53441,57.608827 546.27131,56.930406 544.97695,56.425837 C 544.3409,56.177906 543.67308,55.954337 542.93991,55.816029 z " + style="fill:#0368ff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:3.75;stroke-miterlimit:4;stroke-dashoffset:0;stroke-opacity:1" + inkscape:export-filename="/home/lando/Projects/Amarok/Playback-modes/amarok_repeat_track.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + </g> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/22x22/actions/amarok_queue.png" + id="g8117" + transform="translate(189.06031,277.50271)"> + <rect + style="fill:url(#linearGradient8355);fill-opacity:1;stroke:url(#linearGradient8357);stroke-width:1.71875;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect8119" + width="20.28125" + height="20.28125" + x="569.79236" + y="166.30807" /> + <path + style="color:#000000;fill:url(#linearGradient8359);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient8361);stroke-width:0.41639617;stroke-linecap:round;stroke-linejoin:round;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0.75376886;visibility:visible;display:block;overflow:visible" + d="M 573.05646,167.28611 C 572.40451,167.37374 572.07548,168.24128 572.38081,168.83153 C 573.32855,172.09512 574.2763,175.35871 575.22405,178.62229 C 573.51163,178.29976 571.5527,179.36745 571.06265,181.2123 C 570.70362,182.57556 571.4312,184.10824 572.61784,184.69923 C 574.38176,185.70799 576.89475,184.96152 577.76755,182.99262 C 578.18704,182.07955 578.05604,180.97383 577.5997,180.10682 C 576.85053,177.52471 576.10135,174.9426 575.35218,172.3605 C 576.73239,172.14057 578.16981,171.47234 578.9069,170.14282 C 579.18457,169.60292 579.48206,168.86038 579.04286,168.3206 C 578.68643,167.85281 578.03671,167.96495 577.6048,168.23706 C 576.48233,168.70955 575.24935,168.27756 574.21651,167.74724 C 573.85538,167.5376 573.48912,167.24977 573.05646,167.28611 z " + id="path8121" + inkscape:export-filename="/home/kant/Desktop/playlist16.png" + inkscape:export-xdpi="3.3299999" + inkscape:export-ydpi="3.3299999" /> + <path + inkscape:export-ydpi="3.3299999" + inkscape:export-xdpi="3.3299999" + inkscape:export-filename="/home/kant/Desktop/playlist16.png" + id="path8123" + d="M 579.93352,169.48004 C 579.42396,169.54853 579.1668,170.22659 579.40543,170.68792 C 580.14618,173.2387 580.88693,175.78948 581.62768,178.34027 C 580.28927,178.08818 578.7582,178.92267 578.37518,180.36458 C 578.09457,181.43009 578.66323,182.62801 579.59069,183.08993 C 580.96936,183.87836 582.93347,183.29493 583.61565,181.75606 C 583.94352,181.04241 583.84112,180.1782 583.48446,179.50055 C 582.89892,177.48241 582.31337,175.46426 581.72782,173.44612 C 582.80658,173.27423 583.93005,172.75195 584.50615,171.71281 C 584.72317,171.29083 584.95568,170.71047 584.61241,170.28859 C 584.33383,169.92296 583.82602,170.01061 583.48844,170.22329 C 582.61113,170.59258 581.64745,170.25495 580.84019,169.84046 C 580.55794,169.6766 580.27168,169.45163 579.93352,169.48004 z " + style="color:#000000;fill:url(#linearGradient8363);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient8365);stroke-width:0.32545027;stroke-linecap:round;stroke-linejoin:round;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0.75376886;visibility:visible;display:block;overflow:visible" /> + <path + style="color:#000000;fill:url(#linearGradient8367);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient8369);stroke-width:0.24240285;stroke-linecap:round;stroke-linejoin:round;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0.75376886;visibility:visible;display:block;overflow:visible" + d="M 585.43093,171.13933 C 585.0514,171.19034 584.85985,171.69538 585.0376,172.03899 C 585.58932,173.93887 586.14105,175.83875 586.69278,177.73863 C 585.6959,177.55087 584.55552,178.17242 584.27024,179.24639 C 584.06123,180.04 584.48479,180.93225 585.17558,181.27629 C 586.20244,181.86353 587.66536,181.42898 588.17346,180.28279 C 588.41767,179.75125 588.3414,179.10756 588.07575,178.60284 C 587.63962,177.09968 587.20349,175.59652 586.76736,174.09336 C 587.57085,173.96533 588.40763,173.57632 588.83673,172.80235 C 588.99837,172.48805 589.17155,172.05579 588.91587,171.74155 C 588.70838,171.46923 588.33015,171.53451 588.07872,171.69292 C 587.42528,171.96798 586.7075,171.7165 586.10624,171.40778 C 585.89601,171.28574 585.6828,171.11817 585.43093,171.13933 z " + id="path8125" + inkscape:export-filename="/home/kant/Desktop/playlist16.png" + inkscape:export-xdpi="3.3299999" + inkscape:export-ydpi="3.3299999" /> + </g> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/crystalsvg/22x22/actions/visualizations.png" + id="g8127" + transform="translate(188.15431,390.75811)"> + <rect + y="194.96288" + x="569.99475" + height="20.28125" + width="20.28125" + id="rect8129" + style="fill:url(#linearGradient8371);fill-opacity:1;stroke:url(#linearGradient8373);stroke-width:1.71875;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + inkscape:export-filename="/home/vadim/amarok icons/1/16x16/actions/amarok_visualizations.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + style="fill:url(#linearGradient8375);fill-opacity:1;stroke:none;stroke-width:1.25;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 571.88535,196.85351 L 571.88535,199.60351 L 574.63535,199.60351 L 574.63535,196.85351 L 571.88535,196.85351 z M 582.19785,196.85351 L 582.19785,199.60351 L 584.94785,199.60351 L 584.94785,196.85351 L 582.19785,196.85351 z M 571.88535,200.29101 L 571.88535,203.04101 L 574.63535,203.04101 L 574.63535,200.29101 L 571.88535,200.29101 z M 578.76035,200.29101 L 578.76035,203.04101 L 581.51035,203.04101 L 581.51035,200.29101 L 578.76035,200.29101 z M 582.19785,200.29101 L 582.19785,203.04101 L 584.94785,203.04101 L 584.94785,200.29101 L 582.19785,200.29101 z M 585.63535,200.29101 L 585.63535,203.04101 L 588.38535,203.04101 L 588.38535,200.29101 L 585.63535,200.29101 z M 571.88535,203.72851 L 571.88535,206.47851 L 574.63535,206.47851 L 574.63535,203.72851 L 571.88535,203.72851 z M 575.32285,203.72851 L 575.32285,206.47851 L 578.07285,206.47851 L 578.07285,203.72851 L 575.32285,203.72851 z M 578.76035,203.72851 L 578.76035,206.47851 L 581.51035,206.47851 L 581.51035,203.72851 L 578.76035,203.72851 z M 582.19785,203.72851 L 582.19785,206.47851 L 584.94785,206.47851 L 584.94785,203.72851 L 582.19785,203.72851 z M 585.63535,203.72851 L 585.63535,206.47851 L 588.38535,206.47851 L 588.38535,203.72851 L 585.63535,203.72851 z M 571.88535,207.16601 L 571.88535,209.91601 L 574.63535,209.91601 L 574.63535,207.16601 L 571.88535,207.16601 z M 575.32285,207.16601 L 575.32285,209.91601 L 578.07285,209.91601 L 578.07285,207.16601 L 575.32285,207.16601 z M 578.76035,207.16601 L 578.76035,209.91601 L 581.51035,209.91601 L 581.51035,207.16601 L 578.76035,207.16601 z M 582.19785,207.16601 L 582.19785,209.91601 L 584.94785,209.91601 L 584.94785,207.16601 L 582.19785,207.16601 z M 585.63535,207.16601 L 585.63535,209.91601 L 588.38535,209.91601 L 588.38535,207.16601 L 585.63535,207.16601 z M 571.88535,210.60351 L 571.88535,213.35351 L 574.63535,213.35351 L 574.63535,210.60351 L 571.88535,210.60351 z M 575.32285,210.60351 L 575.32285,213.35351 L 578.07285,213.35351 L 578.07285,210.60351 L 575.32285,210.60351 z M 578.76035,210.60351 L 578.76035,213.35351 L 581.51035,213.35351 L 581.51035,210.60351 L 578.76035,210.60351 z M 582.19785,210.60351 L 582.19785,213.35351 L 584.94785,213.35351 L 584.94785,210.60351 L 582.19785,210.60351 z M 585.63535,210.60351 L 585.63535,213.35351 L 588.38535,213.35351 L 588.38535,210.60351 L 585.63535,210.60351 z " + id="path8131" + sodipodi:nodetypes="ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc" /> + </g> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/22x22/actions/amarok_scripts.png" + id="g8151" + transform="translate(227.11441,318.27481)"> + <rect + y="195.49319" + x="532.67584" + height="20.28125" + width="20.28125" + id="rect8153" + style="fill:url(#linearGradient8381);fill-opacity:1;stroke:url(#linearGradient8383);stroke-width:1.71875;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + inkscape:export-filename="/home/vadim/amarok icons/1/32x32/actions/amarok_scripts.png" + inkscape:export-xdpi="180" + inkscape:export-ydpi="180" /> + <path + style="fill:url(#linearGradient8385);fill-opacity:1;stroke:url(#linearGradient8387);stroke-width:0.58952814;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 541.1954,201.32507 L 549.15029,201.32507 C 549.9856,201.32507 550.65807,202.08034 550.65807,203.01847 L 550.6641,210.87869 L 548.12202,213.86915 L 541.1954,213.86915 C 540.36009,213.86915 539.68762,213.11389 539.68762,212.17575 L 539.68762,203.01847 C 539.68762,202.08034 540.36009,201.32507 541.1954,201.32507 z " + id="path8155" + sodipodi:nodetypes="ccccccccc" /> + <path + style="fill:url(#linearGradient8389);fill-opacity:1;stroke:none;stroke-width:0.90379584;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 541.19897,201.60704 L 549.15275,201.60704 C 549.81911,201.60704 550.35557,202.23361 550.35557,203.0119 L 550.35557,211.32644 L 548.37364,213.54708 L 541.19897,213.58718 C 540.53261,213.58718 539.99614,212.96062 539.99614,212.18233 L 539.99614,203.0119 C 539.99614,202.23361 540.53261,201.60704 541.19897,201.60704 z " + id="path8157" + sodipodi:nodetypes="ccccccccc" /> + <path + style="fill:#0004c1;fill-opacity:1;stroke:none;stroke-width:1.0039922;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 547.9616,211.92933 L 547.9568,214.15853 C 547.9568,214.15853 550.90386,210.67811 550.90386,210.67811 L 549.21846,210.63112 C 548.47336,210.63896 547.96317,211.20186 547.9616,211.92933 z " + id="path8159" + sodipodi:nodetypes="ccccz" /> + <path + sodipodi:nodetypes="ccccccccc" + id="path8161" + d="M 536.47662,197.39843 L 544.43151,197.39843 C 545.26682,197.39843 545.93929,198.15369 545.93929,199.09182 L 545.94532,206.95204 L 543.40324,209.9425 L 536.47662,209.9425 C 535.64132,209.9425 534.96884,209.18724 534.96884,208.2491 L 534.96884,199.09182 C 534.96884,198.15369 535.64132,197.39843 536.47662,197.39843 z " + style="fill:url(#linearGradient8391);fill-opacity:1;stroke:url(#linearGradient8393);stroke-width:0.58952814;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + sodipodi:nodetypes="ccccccccc" + id="path8163" + d="M 536.4802,197.68039 L 544.43398,197.68039 C 545.10033,197.68039 545.63679,198.30696 545.63679,199.08526 L 545.63679,207.3998 L 543.65487,209.62043 L 536.4802,209.66054 C 535.81383,209.66054 535.27737,209.03398 535.27737,208.25568 L 535.27737,199.08526 C 535.27737,198.30696 535.81383,197.68039 536.4802,197.68039 z " + style="fill:url(#linearGradient8395);fill-opacity:1;stroke:none;stroke-width:0.90379584;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" /> + <path + sodipodi:nodetypes="ccccz" + id="path8165" + d="M 543.24282,208.00268 L 543.23802,210.23188 C 543.23802,210.23188 546.25424,206.70516 546.25424,206.70516 L 544.49969,206.70448 C 543.75458,206.71231 543.24439,207.27521 543.24282,208.00268 z " + style="fill:#0004c1;fill-opacity:1;stroke:none;stroke-width:1.0039922;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" /> + </g> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/32x32/actions/amarok_repeat_track.png" + id="g8303" + transform="matrix(1.142857,0,0,1.142857,135.72111,58.074412)"> + <text + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Playback-modes/amarok_repeat_track.png" + transform="scale(1.029207,0.971622)" + sodipodi:linespacing="125%" + id="text8305" + y="77.87027" + x="555.0069" + style="font-size:18.3830452px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#0368ff;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Nimbus Sans L" + xml:space="preserve"><tspan + y="77.87027" + x="555.0069" + id="tspan8307" + sodipodi:role="line">1</tspan></text> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Playback-modes/amarok_repeat_track.png" + style="fill:#0368ff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:3.75;stroke-miterlimit:4;stroke-dashoffset:0;stroke-opacity:1" + d="M 569.42291,55.601969 L 570.38588,58.407938 C 569.33011,58.967777 568.34327,59.658002 567.49698,60.497491 C 565.15514,62.82052 563.64513,66.081023 563.64513,69.63182 C 563.64513,73.182616 565.16166,76.389895 567.49698,78.706447 C 568.66138,79.861484 570.089,80.797508 571.64976,81.452716 C 572.44509,81.786585 573.239,82.002905 574.05717,82.169134 L 573.03402,80.557193 L 574.59883,79.303462 C 573.91865,79.16526 573.30442,79.005986 572.73309,78.766148 C 571.53686,78.263987 570.50594,77.512111 569.60346,76.616895 C 567.79851,74.826442 566.71457,72.376465 566.71457,69.63182 C 566.71457,66.887194 567.79851,64.437198 569.60346,62.646745 C 570.15058,62.104025 570.75927,61.627578 571.40902,61.213909 L 572.3118,63.781073 L 578.51087,58.228834 L 569.42291,55.601969 z M 579.23309,57.034804 L 580.37661,58.885551 L 578.69142,59.900476 C 579.31902,60.027979 579.93828,60.237689 580.55717,60.497491 C 581.75339,60.999653 582.78432,61.751528 583.6868,62.646745 C 585.49175,64.437217 586.57568,66.887194 586.57568,69.63182 C 586.57568,72.376465 585.49175,74.826442 583.6868,76.616895 C 583.17916,77.120451 582.65729,77.596114 582.0618,77.990029 L 581.15902,75.422864 L 574.95995,80.975103 L 584.0479,83.601969 L 583.02475,80.736297 C 584.0585,80.169143 585.03546,79.517871 585.85346,78.706447 C 588.19531,76.383418 589.64513,73.1706 589.64513,69.63182 C 589.64513,66.09304 588.20184,62.826995 585.85346,60.497491 C 584.66295,59.316547 583.1702,58.453102 581.6405,57.810924 C 580.88881,57.495375 580.09957,57.210833 579.23309,57.034804 z " + id="path8309" /> + </g> + <g + id="g8311" + transform="translate(234.36281,198.67641)" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/22x22/actions/amarok_equalizer.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <rect + style="fill:url(#linearGradient8423);fill-opacity:1;stroke:url(#linearGradient8425);stroke-width:1.71875;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect8313" + width="20.28125" + height="20.28125" + x="525.85938" + y="166.22156" /> + <rect + style="fill:#7e7e7e;fill-opacity:1;stroke:none;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect8315" + width="1.35" + height="18.5625" + x="529.13751" + y="167.08093" /> + <rect + style="fill:#7e7e7e;fill-opacity:1;stroke:none;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="use8317" + width="1.5979586" + height="18.564598" + x="535.20087" + y="167.0799" /> + <rect + y="166.71782" + x="541.38837" + height="18.923731" + width="1.5979586" + id="use8319" + style="fill:#7e7e7e;fill-opacity:1;stroke:none;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + style="opacity:1;fill:url(#linearGradient8427);fill-opacity:1;stroke:url(#linearGradient8429);stroke-width:0.79770041;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect8321" + width="5.2008667" + height="2.2022996" + x="539.40027" + y="168.76106" /> + <rect + style="opacity:1;fill:url(#linearGradient8431);fill-opacity:1;stroke:url(#linearGradient8433);stroke-width:0.79770041;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect8323" + width="5.2008667" + height="2.2022996" + x="533.39886" + y="173.76106" /> + <rect + y="178.76103" + x="527.40027" + height="2.2022996" + width="5.2008667" + id="rect8325" + style="opacity:1;fill:url(#linearGradient8435);fill-opacity:1;stroke:url(#linearGradient8437);stroke-width:0.79770041;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + <g + transform="matrix(0.975782,0,0,1,245.32371,269.06651)" + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/32x32/actions/amarok_configure.png" + id="g8327" + style="display:inline"> + <path + id="path8329" + d="M 582.68683,17.544694 L 581.23652,20.988887 C 580.82877,21.079116 580.42923,21.214121 580.05457,21.374483 L 576.57342,19.536567 L 575.64707,20.310806 L 577.22882,23.807099 C 577.22879,23.810338 577.22612,23.840839 577.22652,23.844453 C 577.05824,24.116207 576.89737,24.384486 576.77555,24.681964 L 576.73575,24.679805 L 572.84313,25.630381 L 572.76949,26.825668 L 576.51477,28.265663 C 576.6133,28.637077 576.75877,28.99596 576.92798,29.337457 C 576.92761,29.340689 576.92651,29.371236 576.92567,29.374811 L 574.9319,32.564649 L 575.79679,33.436089 L 579.44069,31.984806 C 579.44454,31.984463 579.47701,31.986921 579.48046,31.986966 C 579.79136,32.15841 580.10995,32.293648 580.45259,32.414492 L 581.50767,36.032117 L 582.7814,36.101237 L 584.27382,32.62185 C 584.64424,32.535317 584.99133,32.411941 585.33408,32.267129 L 588.8129,34.142398 L 589.74154,33.330806 L 588.19499,29.911378 C 588.39462,29.60129 588.55703,29.271116 588.695,28.926613 L 592.5126,27.897013 L 592.58626,26.701726 L 588.91367,25.378105 C 588.81893,25.020976 588.69818,24.675438 588.53795,24.345826 C 588.53832,24.342597 588.53944,24.312045 588.54026,24.308473 L 590.49651,21.079122 L 589.63165,20.207682 L 585.98547,21.696317 C 585.68489,21.527789 585.34649,21.352524 585.01565,21.231439 L 584.97587,21.22928 L 583.96058,17.613813 L 582.68683,17.544694 z M 582.41656,23.226626 C 582.57705,23.216247 582.72943,23.243601 582.89422,23.252542 C 585.00354,23.367002 586.6165,25.066364 586.4945,27.045759 C 586.3725,29.02515 584.56161,30.53872 582.4523,30.42426 C 580.34294,30.309798 578.73002,28.610438 578.85201,26.631044 C 578.96442,24.806288 580.52282,23.349072 582.41656,23.226626 z " + style="fill:url(#linearGradient8439);fill-opacity:1;stroke:url(#linearGradient8441);stroke-width:1.58244431;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + style="fill:url(#linearGradient8443);fill-opacity:1;stroke:url(#linearGradient8445);stroke-width:1.58244503;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 565.98206,13.383882 L 565.88225,16.039358 C 565.62961,16.192194 565.39399,16.37346 565.18167,16.566266 L 562.35777,16.107495 L 561.92941,16.83963 L 563.89311,18.8475 C 563.8939,18.849701 563.89988,18.870928 563.90109,18.873275 C 563.85682,19.094927 563.81664,19.312556 563.81036,19.541141 L 563.78285,19.548645 L 561.39392,21.068031 L 561.64982,21.892834 L 564.55052,22.023059 C 564.71214,22.252006 564.90227,22.461909 565.10402,22.654719 C 565.1046,22.65698 565.11166,22.677888 565.11203,22.680493 L 564.57967,25.286372 L 565.38733,25.680839 L 567.48007,23.8789 C 567.4826,23.877798 567.50518,23.872145 567.50753,23.871399 C 567.7616,23.917309 568.01159,23.937009 568.27416,23.941555 L 569.91276,26.150078 L 570.79172,25.909956 L 570.91102,23.221199 C 571.13937,23.079266 571.34248,22.91768 571.53719,22.742575 L 574.36907,23.227123 L 574.78943,22.469214 L 572.86922,20.50539 C 572.92489,20.250762 572.95027,19.990933 572.95546,19.726919 L 575.27348,18.170992 L 575.0176,17.346187 L 572.19581,17.278282 C 572.04041,17.058136 571.87039,16.851688 571.67777,16.664894 C 571.67718,16.662631 571.67013,16.641719 571.66977,16.639119 L 572.16665,14.014968 L 571.35899,13.620501 L 569.27425,15.448212 C 569.02791,15.401949 568.75427,15.359653 568.49962,15.35228 L 568.47216,15.359784 L 566.86102,13.143762 L 565.98206,13.383882 z M 567.25244,17.286694 C 567.35831,17.243536 567.46833,17.227716 567.58206,17.196646 C 569.03758,16.799016 570.56279,17.584827 570.98653,18.950704 C 571.41024,20.316584 570.57287,21.747838 569.11734,22.145475 C 567.66177,22.543115 566.13659,21.757295 565.71286,20.391418 C 565.32221,19.132255 566.00328,17.795968 567.25244,17.286694 z " + id="path8331" /> + <path + id="path8333" + d="M 567.36972,30.814389 L 567.26991,33.469865 C 567.01727,33.622701 566.78165,33.803966 566.56933,33.996773 L 563.74543,33.538002 L 563.31707,34.270137 L 565.28077,36.278007 C 565.28156,36.280207 565.28754,36.301434 565.28875,36.303782 C 565.24448,36.525434 565.2043,36.743063 565.19802,36.971647 L 565.17051,36.979152 L 562.78158,38.498538 L 563.03748,39.32334 L 565.93818,39.453566 C 566.0998,39.682512 566.28993,39.892416 566.49168,40.085225 C 566.49226,40.087487 566.49932,40.108394 566.49969,40.111 L 565.96733,42.716879 L 566.77499,43.111346 L 568.86773,41.309407 C 568.87026,41.308305 568.89284,41.302652 568.89519,41.301906 C 569.14926,41.347816 569.39925,41.367516 569.66182,41.372062 L 571.30042,43.580584 L 572.17938,43.340463 L 572.29868,40.651705 C 572.52703,40.509773 572.73014,40.348187 572.92485,40.173082 L 575.75673,40.65763 L 576.17709,39.899721 L 574.25688,37.935897 C 574.31255,37.681269 574.33793,37.42144 574.34312,37.157426 L 576.66114,35.601499 L 576.40526,34.776694 L 573.58347,34.708789 C 573.42807,34.488643 573.25805,34.282195 573.06543,34.0954 C 573.06484,34.093138 573.05779,34.072226 573.05743,34.069626 L 573.55431,31.445475 L 572.74665,31.051008 L 570.66191,32.878719 C 570.41557,32.832456 570.14193,32.790159 569.88728,32.782787 L 569.85982,32.79029 L 568.24868,30.574269 L 567.36972,30.814389 z M 568.6401,34.717201 C 568.74597,34.674043 568.85599,34.658223 568.96972,34.627153 C 570.42524,34.229523 571.95045,35.015334 572.37419,36.381211 C 572.7979,37.747091 571.96053,39.178344 570.505,39.575982 C 569.04943,39.973621 567.52425,39.187801 567.10052,37.821925 C 566.70987,36.562762 567.39094,35.226475 568.6401,34.717201 z " + style="fill:url(#linearGradient8447);fill-opacity:1;stroke:url(#linearGradient8449);stroke-width:1.58244503;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + <g + transform="matrix(0.761905,0,0,0.761905,308.41181,49.048612)" + id="g8451" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/16x16/actions/amarok_save.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <rect + transform="scale(-1,-1)" + style="fill:url(#linearGradient8459);fill-opacity:1;stroke:url(#linearGradient8461);stroke-width:1;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect8453" + width="20" + height="20" + x="-548.41418" + y="-37.008583" + rx="1.4679611" + ry="1.5902911" /> + <path + id="path8455" + d="M 543.41423,36.654983 L 543.41423,29.654983 L 532.41423,29.654983 L 532.41423,36.654983 L 543.41423,36.654983 z M 537.41423,36.154983 L 534.41423,36.154983 L 534.41423,30.154983 L 537.41423,30.154983 L 537.41423,36.154983 z " + style="fill:url(#linearGradient8463);fill-opacity:1;stroke:none;stroke-width:0.71899998;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" /> + <rect + rx="1.345631" + ry="1.7126213" + transform="scale(-1,-1)" + y="-28.838623" + x="-545.96246" + height="10.37806" + width="15.055487" + id="rect8457" + style="fill:url(#linearGradient8465);fill-opacity:1;stroke:url(#linearGradient8467);stroke-width:1;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + <g + transform="matrix(1.52381,0,0,1.52381,-25.854887,22.282712)" + id="g8469" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/32x32/actions/amarok_save.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <rect + transform="scale(-1,-1)" + style="fill:url(#linearGradient8477);fill-opacity:1;stroke:url(#linearGradient8479);stroke-width:1;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect8471" + width="20" + height="20" + x="-548.41418" + y="-37.008583" + rx="0.73398054" + ry="0.79514557" /> + <path + id="path8473" + d="M 543.41423,36.654983 L 543.41423,29.654983 L 532.41423,29.654983 L 532.41423,36.654983 L 543.41423,36.654983 z M 537.41423,36.154983 L 534.41423,36.154983 L 534.41423,30.154983 L 537.41423,30.154983 L 537.41423,36.154983 z " + style="fill:url(#linearGradient8481);fill-opacity:1;stroke:none;stroke-width:0.71899998;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" /> + <rect + rx="0.6728155" + ry="0.85631067" + transform="scale(-1,-1)" + y="-28.838623" + x="-545.96246" + height="10.37806" + width="15.055487" + id="rect8475" + style="fill:url(#linearGradient8483);fill-opacity:1;stroke:url(#linearGradient8485);stroke-width:1;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + <rect + transform="scale(-1,-1)" + style="fill:url(#linearGradient13723);fill-opacity:1;stroke:url(#linearGradient13725);stroke-width:2.28571391;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect8489" + width="45.714279" + height="45.714279" + x="-879.80591" + y="-77.38974" + rx="1.1184467" + ry="1.2116506" /> + <path + id="path8491" + d="M 868.37741,76.581522 L 868.37741,60.581522 L 843.23461,60.581522 L 843.23461,76.581522 L 868.37741,76.581522 z M 854.66311,75.438662 L 847.80601,75.438662 L 847.80601,61.724382 L 854.66311,61.724382 L 854.66311,75.438662 z " + style="fill:url(#linearGradient13719);fill-opacity:1;stroke:none;stroke-width:0.71899998;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" /> + <rect + rx="1.0252429" + ry="1.3048546" + transform="scale(-1,-1)" + y="-58.715546" + x="-874.2019" + height="23.721277" + width="34.412537" + id="rect8493" + style="fill:url(#linearGradient13713);fill-opacity:1;stroke:url(#linearGradient13715);stroke-width:2.28571391;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <g + transform="matrix(3.047619,0,0,3.047619,-705.26079,-36.685288)" + id="g8505" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/64x64/actions/amarok_save.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <rect + transform="scale(-1,-1)" + style="fill:url(#linearGradient8513);fill-opacity:1;stroke:url(#linearGradient8516);stroke-width:1;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect8507" + width="20" + height="20" + x="-548.41418" + y="-37.008583" + rx="0.36699033" + ry="0.39757285" /> + <path + id="path8509" + d="M 543.41423,36.654983 L 543.41423,29.654983 L 532.41423,29.654983 L 532.41423,36.654983 L 543.41423,36.654983 z M 537.41423,36.154983 L 534.41423,36.154983 L 534.41423,30.154983 L 537.41423,30.154983 L 537.41423,36.154983 z " + style="fill:url(#linearGradient8518);fill-opacity:1;stroke:none;stroke-width:0.71899998;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" /> + <rect + rx="0.33640781" + ry="0.42815542" + transform="scale(-1,-1)" + y="-28.838623" + x="-545.96246" + height="10.37806" + width="15.055487" + id="rect8511" + style="fill:url(#linearGradient8520);fill-opacity:1;stroke:url(#linearGradient8522);stroke-width:1;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + <g + transform="matrix(0.727273,0,0,0.727273,327.19531,97.84481)" + id="g8524" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/16x16/actions/amarok_repeat_track.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <text + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Playback-modes/amarok_repeat_track.png" + transform="scale(1.068059,0.936278)" + sodipodi:linespacing="125%" + id="text8526" + y="75.245209" + x="501.99185" + style="font-size:14.98905945px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#0368ff;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Nimbus Sans L" + xml:space="preserve"><tspan + y="75.245209" + x="501.99185" + id="tspan8528" + sodipodi:role="line">1</tspan></text> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Playback-modes/amarok_repeat_track.png" + style="fill:#0368ff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:3.75;stroke-miterlimit:4;stroke-dashoffset:0;stroke-opacity:1" + d="M 534.63898,54.69023 L 535.4538,56.89492 C 534.56046,57.334793 533.72544,57.877113 533.00935,58.536711 C 531.02779,60.361948 529.75009,62.923772 529.75009,65.713683 C 529.75009,68.503594 531.03331,71.023599 533.00935,72.843747 C 533.99461,73.751276 535.2026,74.486723 536.52324,75.001529 C 537.19621,75.263855 537.86798,75.433821 538.56028,75.564429 L 537.69454,74.297904 L 539.01861,73.31283 C 538.44307,73.204243 537.92334,73.079099 537.43991,72.890655 C 536.42771,72.4961 535.55539,71.90534 534.79176,71.201956 C 533.26449,69.795172 532.34731,67.87019 532.34731,65.713683 C 532.34731,63.557192 533.26449,61.632195 534.79176,60.225411 C 535.2547,59.798988 535.76975,59.424637 536.31954,59.099611 L 537.08343,61.116668 L 542.3288,56.754195 L 534.63898,54.69023 z M 542.93991,55.816029 L 543.9075,57.270187 L 542.48157,58.067628 C 543.01262,58.167809 543.53661,58.332581 544.06028,58.536711 C 545.07247,58.931267 545.9448,59.522026 546.70843,60.225411 C 548.2357,61.63221 549.15287,63.557192 549.15287,65.713683 C 549.15287,67.87019 548.2357,69.795172 546.70843,71.201956 C 546.27889,71.597607 545.83731,71.971342 545.33343,72.280847 L 544.56954,70.263789 L 539.32417,74.626262 L 547.01398,76.690228 L 546.14824,74.438629 C 547.02295,73.993008 547.84961,73.481294 548.54176,72.843747 C 550.52333,71.01851 551.7501,68.494153 551.7501,65.713683 C 551.7501,62.933214 550.52885,60.367036 548.54176,58.536711 C 547.53441,57.608827 546.27131,56.930406 544.97695,56.425837 C 544.3409,56.177906 543.67308,55.954337 542.93991,55.816029 z " + id="path8530" /> + </g> + <g + transform="matrix(1.714286,0,0,1.714286,-131.09293,10.301812)" + id="g8532" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/48x48/actions/amarok_repeat_track.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <text + xml:space="preserve" + style="font-size:18.3830452px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#0368ff;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Nimbus Sans L" + x="555.0069" + y="77.87027" + id="text8534" + sodipodi:linespacing="125%" + transform="scale(1.029207,0.971622)" + inkscape:export-filename="/home/lando/Projects/Amarok/Playback-modes/amarok_repeat_track.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"><tspan + sodipodi:role="line" + id="tspan8536" + x="555.0069" + y="77.87027">1</tspan></text> + <path + id="path8538" + d="M 569.42291,55.601969 L 570.38588,58.407938 C 569.33011,58.967777 568.34327,59.658002 567.49698,60.497491 C 565.15514,62.82052 563.64513,66.081023 563.64513,69.63182 C 563.64513,73.182616 565.16166,76.389895 567.49698,78.706447 C 568.66138,79.861484 570.089,80.797508 571.64976,81.452716 C 572.44509,81.786585 573.239,82.002905 574.05717,82.169134 L 573.03402,80.557193 L 574.59883,79.303462 C 573.91865,79.16526 573.30442,79.005986 572.73309,78.766148 C 571.53686,78.263987 570.50594,77.512111 569.60346,76.616895 C 567.79851,74.826442 566.71457,72.376465 566.71457,69.63182 C 566.71457,66.887194 567.79851,64.437198 569.60346,62.646745 C 570.15058,62.104025 570.75927,61.627578 571.40902,61.213909 L 572.3118,63.781073 L 578.51087,58.228834 L 569.42291,55.601969 z M 579.23309,57.034804 L 580.37661,58.885551 L 578.69142,59.900476 C 579.31902,60.027979 579.93828,60.237689 580.55717,60.497491 C 581.75339,60.999653 582.78432,61.751528 583.6868,62.646745 C 585.49175,64.437217 586.57568,66.887194 586.57568,69.63182 C 586.57568,72.376465 585.49175,74.826442 583.6868,76.616895 C 583.17916,77.120451 582.65729,77.596114 582.0618,77.990029 L 581.15902,75.422864 L 574.95995,80.975103 L 584.0479,83.601969 L 583.02475,80.736297 C 584.0585,80.169143 585.03546,79.517871 585.85346,78.706447 C 588.19531,76.383418 589.64513,73.1706 589.64513,69.63182 C 589.64513,66.09304 588.20184,62.826995 585.85346,60.497491 C 584.66295,59.316547 583.1702,58.453102 581.6405,57.810924 C 580.88881,57.495375 580.09957,57.210833 579.23309,57.034804 z " + style="fill:#0368ff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:3.75;stroke-miterlimit:4;stroke-dashoffset:0;stroke-opacity:1" + inkscape:export-filename="/home/lando/Projects/Amarok/Playback-modes/amarok_repeat_track.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + </g> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/64x64/actions/amarok_repeat_track.png" + id="g8540" + transform="matrix(2.285714,0,0,2.285714,-384.31599,-37.470688)"> + <text + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Playback-modes/amarok_repeat_track.png" + transform="scale(1.029207,0.971622)" + sodipodi:linespacing="125%" + id="text8542" + y="77.87027" + x="555.0069" + style="font-size:18.3830452px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#0368ff;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Nimbus Sans L" + xml:space="preserve"><tspan + y="77.87027" + x="555.0069" + id="tspan8544" + sodipodi:role="line">1</tspan></text> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Playback-modes/amarok_repeat_track.png" + style="fill:#0368ff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:3.75;stroke-miterlimit:4;stroke-dashoffset:0;stroke-opacity:1" + d="M 569.42291,55.601969 L 570.38588,58.407938 C 569.33011,58.967777 568.34327,59.658002 567.49698,60.497491 C 565.15514,62.82052 563.64513,66.081023 563.64513,69.63182 C 563.64513,73.182616 565.16166,76.389895 567.49698,78.706447 C 568.66138,79.861484 570.089,80.797508 571.64976,81.452716 C 572.44509,81.786585 573.239,82.002905 574.05717,82.169134 L 573.03402,80.557193 L 574.59883,79.303462 C 573.91865,79.16526 573.30442,79.005986 572.73309,78.766148 C 571.53686,78.263987 570.50594,77.512111 569.60346,76.616895 C 567.79851,74.826442 566.71457,72.376465 566.71457,69.63182 C 566.71457,66.887194 567.79851,64.437198 569.60346,62.646745 C 570.15058,62.104025 570.75927,61.627578 571.40902,61.213909 L 572.3118,63.781073 L 578.51087,58.228834 L 569.42291,55.601969 z M 579.23309,57.034804 L 580.37661,58.885551 L 578.69142,59.900476 C 579.31902,60.027979 579.93828,60.237689 580.55717,60.497491 C 581.75339,60.999653 582.78432,61.751528 583.6868,62.646745 C 585.49175,64.437217 586.57568,66.887194 586.57568,69.63182 C 586.57568,72.376465 585.49175,74.826442 583.6868,76.616895 C 583.17916,77.120451 582.65729,77.596114 582.0618,77.990029 L 581.15902,75.422864 L 574.95995,80.975103 L 584.0479,83.601969 L 583.02475,80.736297 C 584.0585,80.169143 585.03546,79.517871 585.85346,78.706447 C 588.19531,76.383418 589.64513,73.1706 589.64513,69.63182 C 589.64513,66.09304 588.20184,62.826995 585.85346,60.497491 C 584.66295,59.316547 583.1702,58.453102 581.6405,57.810924 C 580.88881,57.495375 580.09957,57.210833 579.23309,57.034804 z " + id="path8546" /> + </g> + <g + style="display:inline" + id="g8636" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/22x22/actions/amarok_configure.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" + transform="matrix(0.67085,0,0,0.687498,380.03011,282.92961)"> + <path + style="fill:url(#linearGradient8644);fill-opacity:1;stroke:url(#linearGradient8646);stroke-width:1.58244658;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 582.68683,17.544694 L 581.23652,20.988887 C 580.82877,21.079116 580.42923,21.214121 580.05457,21.374483 L 576.57342,19.536567 L 575.64707,20.310806 L 577.22882,23.807099 C 577.22879,23.810338 577.22612,23.840839 577.22652,23.844453 C 577.05824,24.116207 576.89737,24.384486 576.77555,24.681964 L 576.73575,24.679805 L 572.84313,25.630381 L 572.76949,26.825668 L 576.51477,28.265663 C 576.6133,28.637077 576.75877,28.99596 576.92798,29.337457 C 576.92761,29.340689 576.92651,29.371236 576.92567,29.374811 L 574.9319,32.564649 L 575.79679,33.436089 L 579.44069,31.984806 C 579.44454,31.984463 579.47701,31.986921 579.48046,31.986966 C 579.79136,32.15841 580.10995,32.293648 580.45259,32.414492 L 581.50767,36.032117 L 582.7814,36.101237 L 584.27382,32.62185 C 584.64424,32.535317 584.99133,32.411941 585.33408,32.267129 L 588.8129,34.142398 L 589.74154,33.330806 L 588.19499,29.911378 C 588.39462,29.60129 588.55703,29.271116 588.695,28.926613 L 592.5126,27.897013 L 592.58626,26.701726 L 588.91367,25.378105 C 588.81893,25.020976 588.69818,24.675438 588.53795,24.345826 C 588.53832,24.342597 588.53944,24.312045 588.54026,24.308473 L 590.49651,21.079122 L 589.63165,20.207682 L 585.98547,21.696317 C 585.68489,21.527789 585.34649,21.352524 585.01565,21.231439 L 584.97587,21.22928 L 583.96058,17.613813 L 582.68683,17.544694 z M 582.41656,23.226626 C 582.57705,23.216247 582.72943,23.243601 582.89422,23.252542 C 585.00354,23.367002 586.6165,25.066364 586.4945,27.045759 C 586.3725,29.02515 584.56161,30.53872 582.4523,30.42426 C 580.34294,30.309798 578.73002,28.610438 578.85201,26.631044 C 578.96442,24.806288 580.52282,23.349072 582.41656,23.226626 z " + id="path8638" /> + <path + id="path8640" + d="M 565.98206,13.383882 L 565.88225,16.039358 C 565.62961,16.192194 565.39399,16.37346 565.18167,16.566266 L 562.35777,16.107495 L 561.92941,16.83963 L 563.89311,18.8475 C 563.8939,18.849701 563.89988,18.870928 563.90109,18.873275 C 563.85682,19.094927 563.81664,19.312556 563.81036,19.541141 L 563.78285,19.548645 L 561.39392,21.068031 L 561.64982,21.892834 L 564.55052,22.023059 C 564.71214,22.252006 564.90227,22.461909 565.10402,22.654719 C 565.1046,22.65698 565.11166,22.677888 565.11203,22.680493 L 564.57967,25.286372 L 565.38733,25.680839 L 567.48007,23.8789 C 567.4826,23.877798 567.50518,23.872145 567.50753,23.871399 C 567.7616,23.917309 568.01159,23.937009 568.27416,23.941555 L 569.91276,26.150078 L 570.79172,25.909956 L 570.91102,23.221199 C 571.13937,23.079266 571.34248,22.91768 571.53719,22.742575 L 574.36907,23.227123 L 574.78943,22.469214 L 572.86922,20.50539 C 572.92489,20.250762 572.95027,19.990933 572.95546,19.726919 L 575.27348,18.170992 L 575.0176,17.346187 L 572.19581,17.278282 C 572.04041,17.058136 571.87039,16.851688 571.67777,16.664894 C 571.67718,16.662631 571.67013,16.641719 571.66977,16.639119 L 572.16665,14.014968 L 571.35899,13.620501 L 569.27425,15.448212 C 569.02791,15.401949 568.75427,15.359653 568.49962,15.35228 L 568.47216,15.359784 L 566.86102,13.143762 L 565.98206,13.383882 z M 567.25244,17.286694 C 567.35831,17.243536 567.46833,17.227716 567.58206,17.196646 C 569.03758,16.799016 570.56279,17.584827 570.98653,18.950704 C 571.41024,20.316584 570.57287,21.747838 569.11734,22.145475 C 567.66177,22.543115 566.13659,21.757295 565.71286,20.391418 C 565.32221,19.132255 566.00328,17.795968 567.25244,17.286694 z " + style="fill:url(#linearGradient8648);fill-opacity:1;stroke:url(#linearGradient8650);stroke-width:1.58244741;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + style="fill:url(#linearGradient8652);fill-opacity:1;stroke:url(#linearGradient8654);stroke-width:1.58244741;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 567.36972,30.814389 L 567.26991,33.469865 C 567.01727,33.622701 566.78165,33.803966 566.56933,33.996773 L 563.74543,33.538002 L 563.31707,34.270137 L 565.28077,36.278007 C 565.28156,36.280207 565.28754,36.301434 565.28875,36.303782 C 565.24448,36.525434 565.2043,36.743063 565.19802,36.971647 L 565.17051,36.979152 L 562.78158,38.498538 L 563.03748,39.32334 L 565.93818,39.453566 C 566.0998,39.682512 566.28993,39.892416 566.49168,40.085225 C 566.49226,40.087487 566.49932,40.108394 566.49969,40.111 L 565.96733,42.716879 L 566.77499,43.111346 L 568.86773,41.309407 C 568.87026,41.308305 568.89284,41.302652 568.89519,41.301906 C 569.14926,41.347816 569.39925,41.367516 569.66182,41.372062 L 571.30042,43.580584 L 572.17938,43.340463 L 572.29868,40.651705 C 572.52703,40.509773 572.73014,40.348187 572.92485,40.173082 L 575.75673,40.65763 L 576.17709,39.899721 L 574.25688,37.935897 C 574.31255,37.681269 574.33793,37.42144 574.34312,37.157426 L 576.66114,35.601499 L 576.40526,34.776694 L 573.58347,34.708789 C 573.42807,34.488643 573.25805,34.282195 573.06543,34.0954 C 573.06484,34.093138 573.05779,34.072226 573.05743,34.069626 L 573.55431,31.445475 L 572.74665,31.051008 L 570.66191,32.878719 C 570.41557,32.832456 570.14193,32.790159 569.88728,32.782787 L 569.85982,32.79029 L 568.24868,30.574269 L 567.36972,30.814389 z M 568.6401,34.717201 C 568.74597,34.674043 568.85599,34.658223 568.96972,34.627153 C 570.42524,34.229523 571.95045,35.015334 572.37419,36.381211 C 572.7979,37.747091 571.96053,39.178344 570.505,39.575982 C 569.04943,39.973621 567.52425,39.187801 567.10052,37.821925 C 566.70987,36.562762 567.39094,35.226475 568.6401,34.717201 z " + id="path8642" /> + </g> + <g + transform="matrix(0.5,0,0,0.5,443.19281,291.24761)" + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/16x16/actions/amarok_configure.png" + id="g8656" + style="display:inline"> + <path + id="path8658" + d="M 582.68683,17.544694 L 581.23652,20.988887 C 580.82877,21.079116 580.42923,21.214121 580.05457,21.374483 L 576.57342,19.536567 L 575.64707,20.310806 L 577.22882,23.807099 C 577.22879,23.810338 577.22612,23.840839 577.22652,23.844453 C 577.05824,24.116207 576.89737,24.384486 576.77555,24.681964 L 576.73575,24.679805 L 572.84313,25.630381 L 572.76949,26.825668 L 576.51477,28.265663 C 576.6133,28.637077 576.75877,28.99596 576.92798,29.337457 C 576.92761,29.340689 576.92651,29.371236 576.92567,29.374811 L 574.9319,32.564649 L 575.79679,33.436089 L 579.44069,31.984806 C 579.44454,31.984463 579.47701,31.986921 579.48046,31.986966 C 579.79136,32.15841 580.10995,32.293648 580.45259,32.414492 L 581.50767,36.032117 L 582.7814,36.101237 L 584.27382,32.62185 C 584.64424,32.535317 584.99133,32.411941 585.33408,32.267129 L 588.8129,34.142398 L 589.74154,33.330806 L 588.19499,29.911378 C 588.39462,29.60129 588.55703,29.271116 588.695,28.926613 L 592.5126,27.897013 L 592.58626,26.701726 L 588.91367,25.378105 C 588.81893,25.020976 588.69818,24.675438 588.53795,24.345826 C 588.53832,24.342597 588.53944,24.312045 588.54026,24.308473 L 590.49651,21.079122 L 589.63165,20.207682 L 585.98547,21.696317 C 585.68489,21.527789 585.34649,21.352524 585.01565,21.231439 L 584.97587,21.22928 L 583.96058,17.613813 L 582.68683,17.544694 z M 582.41656,23.226626 C 582.57705,23.216247 582.72943,23.243601 582.89422,23.252542 C 585.00354,23.367002 586.6165,25.066364 586.4945,27.045759 C 586.3725,29.02515 584.56161,30.53872 582.4523,30.42426 C 580.34294,30.309798 578.73002,28.610438 578.85201,26.631044 C 578.96442,24.806288 580.52282,23.349072 582.41656,23.226626 z " + style="fill:url(#linearGradient8664);fill-opacity:1;stroke:url(#linearGradient8666);stroke-width:1.56316459;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + style="fill:url(#linearGradient8668);fill-opacity:1;stroke:url(#linearGradient8670);stroke-width:1.56316531;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 565.98206,13.383882 L 565.88225,16.039358 C 565.62961,16.192194 565.39399,16.37346 565.18167,16.566266 L 562.35777,16.107495 L 561.92941,16.83963 L 563.89311,18.8475 C 563.8939,18.849701 563.89988,18.870928 563.90109,18.873275 C 563.85682,19.094927 563.81664,19.312556 563.81036,19.541141 L 563.78285,19.548645 L 561.39392,21.068031 L 561.64982,21.892834 L 564.55052,22.023059 C 564.71214,22.252006 564.90227,22.461909 565.10402,22.654719 C 565.1046,22.65698 565.11166,22.677888 565.11203,22.680493 L 564.57967,25.286372 L 565.38733,25.680839 L 567.48007,23.8789 C 567.4826,23.877798 567.50518,23.872145 567.50753,23.871399 C 567.7616,23.917309 568.01159,23.937009 568.27416,23.941555 L 569.91276,26.150078 L 570.79172,25.909956 L 570.91102,23.221199 C 571.13937,23.079266 571.34248,22.91768 571.53719,22.742575 L 574.36907,23.227123 L 574.78943,22.469214 L 572.86922,20.50539 C 572.92489,20.250762 572.95027,19.990933 572.95546,19.726919 L 575.27348,18.170992 L 575.0176,17.346187 L 572.19581,17.278282 C 572.04041,17.058136 571.87039,16.851688 571.67777,16.664894 C 571.67718,16.662631 571.67013,16.641719 571.66977,16.639119 L 572.16665,14.014968 L 571.35899,13.620501 L 569.27425,15.448212 C 569.02791,15.401949 568.75427,15.359653 568.49962,15.35228 L 568.47216,15.359784 L 566.86102,13.143762 L 565.98206,13.383882 z M 567.25244,17.286694 C 567.35831,17.243536 567.46833,17.227716 567.58206,17.196646 C 569.03758,16.799016 570.56279,17.584827 570.98653,18.950704 C 571.41024,20.316584 570.57287,21.747838 569.11734,22.145475 C 567.66177,22.543115 566.13659,21.757295 565.71286,20.391418 C 565.32221,19.132255 566.00328,17.795968 567.25244,17.286694 z " + id="path8660" /> + <path + id="path8662" + d="M 567.36972,30.814389 L 567.26991,33.469865 C 567.01727,33.622701 566.78165,33.803966 566.56933,33.996773 L 563.74543,33.538002 L 563.31707,34.270137 L 565.28077,36.278007 C 565.28156,36.280207 565.28754,36.301434 565.28875,36.303782 C 565.24448,36.525434 565.2043,36.743063 565.19802,36.971647 L 565.17051,36.979152 L 562.78158,38.498538 L 563.03748,39.32334 L 565.93818,39.453566 C 566.0998,39.682512 566.28993,39.892416 566.49168,40.085225 C 566.49226,40.087487 566.49932,40.108394 566.49969,40.111 L 565.96733,42.716879 L 566.77499,43.111346 L 568.86773,41.309407 C 568.87026,41.308305 568.89284,41.302652 568.89519,41.301906 C 569.14926,41.347816 569.39925,41.367516 569.66182,41.372062 L 571.30042,43.580584 L 572.17938,43.340463 L 572.29868,40.651705 C 572.52703,40.509773 572.73014,40.348187 572.92485,40.173082 L 575.75673,40.65763 L 576.17709,39.899721 L 574.25688,37.935897 C 574.31255,37.681269 574.33793,37.42144 574.34312,37.157426 L 576.66114,35.601499 L 576.40526,34.776694 L 573.58347,34.708789 C 573.42807,34.488643 573.25805,34.282195 573.06543,34.0954 C 573.06484,34.093138 573.05779,34.072226 573.05743,34.069626 L 573.55431,31.445475 L 572.74665,31.051008 L 570.66191,32.878719 C 570.41557,32.832456 570.14193,32.790159 569.88728,32.782787 L 569.85982,32.79029 L 568.24868,30.574269 L 567.36972,30.814389 z M 568.6401,34.717201 C 568.74597,34.674043 568.85599,34.658223 568.96972,34.627153 C 570.42524,34.229523 571.95045,35.015334 572.37419,36.381211 C 572.7979,37.747091 571.96053,39.178344 570.505,39.575982 C 569.04943,39.973621 567.52425,39.187801 567.10052,37.821925 C 566.70987,36.562762 567.39094,35.226475 568.6401,34.717201 z " + style="fill:url(#linearGradient8673);fill-opacity:1;stroke:url(#linearGradient8675);stroke-width:1.56316531;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + <g + style="display:inline" + id="g8677" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/48x48/actions/amarok_configure.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" + transform="matrix(1.463673,0,0,1.500001,18.014713,246.88541)"> + <path + style="fill:url(#linearGradient8685);fill-opacity:1;stroke:url(#linearGradient8687);stroke-width:1.58244467;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 582.68683,17.544694 L 581.23652,20.988887 C 580.82877,21.079116 580.42923,21.214121 580.05457,21.374483 L 576.57342,19.536567 L 575.64707,20.310806 L 577.22882,23.807099 C 577.22879,23.810338 577.22612,23.840839 577.22652,23.844453 C 577.05824,24.116207 576.89737,24.384486 576.77555,24.681964 L 576.73575,24.679805 L 572.84313,25.630381 L 572.76949,26.825668 L 576.51477,28.265663 C 576.6133,28.637077 576.75877,28.99596 576.92798,29.337457 C 576.92761,29.340689 576.92651,29.371236 576.92567,29.374811 L 574.9319,32.564649 L 575.79679,33.436089 L 579.44069,31.984806 C 579.44454,31.984463 579.47701,31.986921 579.48046,31.986966 C 579.79136,32.15841 580.10995,32.293648 580.45259,32.414492 L 581.50767,36.032117 L 582.7814,36.101237 L 584.27382,32.62185 C 584.64424,32.535317 584.99133,32.411941 585.33408,32.267129 L 588.8129,34.142398 L 589.74154,33.330806 L 588.19499,29.911378 C 588.39462,29.60129 588.55703,29.271116 588.695,28.926613 L 592.5126,27.897013 L 592.58626,26.701726 L 588.91367,25.378105 C 588.81893,25.020976 588.69818,24.675438 588.53795,24.345826 C 588.53832,24.342597 588.53944,24.312045 588.54026,24.308473 L 590.49651,21.079122 L 589.63165,20.207682 L 585.98547,21.696317 C 585.68489,21.527789 585.34649,21.352524 585.01565,21.231439 L 584.97587,21.22928 L 583.96058,17.613813 L 582.68683,17.544694 z M 582.41656,23.226626 C 582.57705,23.216247 582.72943,23.243601 582.89422,23.252542 C 585.00354,23.367002 586.6165,25.066364 586.4945,27.045759 C 586.3725,29.02515 584.56161,30.53872 582.4523,30.42426 C 580.34294,30.309798 578.73002,28.610438 578.85201,26.631044 C 578.96442,24.806288 580.52282,23.349072 582.41656,23.226626 z " + id="path8679" /> + <path + id="path8681" + d="M 565.98206,13.383882 L 565.88225,16.039358 C 565.62961,16.192194 565.39399,16.37346 565.18167,16.566266 L 562.35777,16.107495 L 561.92941,16.83963 L 563.89311,18.8475 C 563.8939,18.849701 563.89988,18.870928 563.90109,18.873275 C 563.85682,19.094927 563.81664,19.312556 563.81036,19.541141 L 563.78285,19.548645 L 561.39392,21.068031 L 561.64982,21.892834 L 564.55052,22.023059 C 564.71214,22.252006 564.90227,22.461909 565.10402,22.654719 C 565.1046,22.65698 565.11166,22.677888 565.11203,22.680493 L 564.57967,25.286372 L 565.38733,25.680839 L 567.48007,23.8789 C 567.4826,23.877798 567.50518,23.872145 567.50753,23.871399 C 567.7616,23.917309 568.01159,23.937009 568.27416,23.941555 L 569.91276,26.150078 L 570.79172,25.909956 L 570.91102,23.221199 C 571.13937,23.079266 571.34248,22.91768 571.53719,22.742575 L 574.36907,23.227123 L 574.78943,22.469214 L 572.86922,20.50539 C 572.92489,20.250762 572.95027,19.990933 572.95546,19.726919 L 575.27348,18.170992 L 575.0176,17.346187 L 572.19581,17.278282 C 572.04041,17.058136 571.87039,16.851688 571.67777,16.664894 C 571.67718,16.662631 571.67013,16.641719 571.66977,16.639119 L 572.16665,14.014968 L 571.35899,13.620501 L 569.27425,15.448212 C 569.02791,15.401949 568.75427,15.359653 568.49962,15.35228 L 568.47216,15.359784 L 566.86102,13.143762 L 565.98206,13.383882 z M 567.25244,17.286694 C 567.35831,17.243536 567.46833,17.227716 567.58206,17.196646 C 569.03758,16.799016 570.56279,17.584827 570.98653,18.950704 C 571.41024,20.316584 570.57287,21.747838 569.11734,22.145475 C 567.66177,22.543115 566.13659,21.757295 565.71286,20.391418 C 565.32221,19.132255 566.00328,17.795968 567.25244,17.286694 z " + style="fill:url(#linearGradient8689);fill-opacity:1;stroke:url(#linearGradient8691);stroke-width:1.58244538;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + style="fill:url(#linearGradient8693);fill-opacity:1;stroke:url(#linearGradient8695);stroke-width:1.58244538;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 567.36972,30.814389 L 567.26991,33.469865 C 567.01727,33.622701 566.78165,33.803966 566.56933,33.996773 L 563.74543,33.538002 L 563.31707,34.270137 L 565.28077,36.278007 C 565.28156,36.280207 565.28754,36.301434 565.28875,36.303782 C 565.24448,36.525434 565.2043,36.743063 565.19802,36.971647 L 565.17051,36.979152 L 562.78158,38.498538 L 563.03748,39.32334 L 565.93818,39.453566 C 566.0998,39.682512 566.28993,39.892416 566.49168,40.085225 C 566.49226,40.087487 566.49932,40.108394 566.49969,40.111 L 565.96733,42.716879 L 566.77499,43.111346 L 568.86773,41.309407 C 568.87026,41.308305 568.89284,41.302652 568.89519,41.301906 C 569.14926,41.347816 569.39925,41.367516 569.66182,41.372062 L 571.30042,43.580584 L 572.17938,43.340463 L 572.29868,40.651705 C 572.52703,40.509773 572.73014,40.348187 572.92485,40.173082 L 575.75673,40.65763 L 576.17709,39.899721 L 574.25688,37.935897 C 574.31255,37.681269 574.33793,37.42144 574.34312,37.157426 L 576.66114,35.601499 L 576.40526,34.776694 L 573.58347,34.708789 C 573.42807,34.488643 573.25805,34.282195 573.06543,34.0954 C 573.06484,34.093138 573.05779,34.072226 573.05743,34.069626 L 573.55431,31.445475 L 572.74665,31.051008 L 570.66191,32.878719 C 570.41557,32.832456 570.14193,32.790159 569.88728,32.782787 L 569.85982,32.79029 L 568.24868,30.574269 L 567.36972,30.814389 z M 568.6401,34.717201 C 568.74597,34.674043 568.85599,34.658223 568.96972,34.627153 C 570.42524,34.229523 571.95045,35.015334 572.37419,36.381211 C 572.7979,37.747091 571.96053,39.178344 570.505,39.575982 C 569.04943,39.973621 567.52425,39.187801 567.10052,37.821925 C 566.70987,36.562762 567.39094,35.226475 568.6401,34.717201 z " + id="path8683" /> + </g> + <g + transform="matrix(1.951564,0,0,2.000001,-195.7039,224.70431)" + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/64x64/actions/amarok_configure.png" + id="g8697" + style="display:inline"> + <path + id="path8699" + d="M 582.68683,17.544694 L 581.23652,20.988887 C 580.82877,21.079116 580.42923,21.214121 580.05457,21.374483 L 576.57342,19.536567 L 575.64707,20.310806 L 577.22882,23.807099 C 577.22879,23.810338 577.22612,23.840839 577.22652,23.844453 C 577.05824,24.116207 576.89737,24.384486 576.77555,24.681964 L 576.73575,24.679805 L 572.84313,25.630381 L 572.76949,26.825668 L 576.51477,28.265663 C 576.6133,28.637077 576.75877,28.99596 576.92798,29.337457 C 576.92761,29.340689 576.92651,29.371236 576.92567,29.374811 L 574.9319,32.564649 L 575.79679,33.436089 L 579.44069,31.984806 C 579.44454,31.984463 579.47701,31.986921 579.48046,31.986966 C 579.79136,32.15841 580.10995,32.293648 580.45259,32.414492 L 581.50767,36.032117 L 582.7814,36.101237 L 584.27382,32.62185 C 584.64424,32.535317 584.99133,32.411941 585.33408,32.267129 L 588.8129,34.142398 L 589.74154,33.330806 L 588.19499,29.911378 C 588.39462,29.60129 588.55703,29.271116 588.695,28.926613 L 592.5126,27.897013 L 592.58626,26.701726 L 588.91367,25.378105 C 588.81893,25.020976 588.69818,24.675438 588.53795,24.345826 C 588.53832,24.342597 588.53944,24.312045 588.54026,24.308473 L 590.49651,21.079122 L 589.63165,20.207682 L 585.98547,21.696317 C 585.68489,21.527789 585.34649,21.352524 585.01565,21.231439 L 584.97587,21.22928 L 583.96058,17.613813 L 582.68683,17.544694 z M 582.41656,23.226626 C 582.57705,23.216247 582.72943,23.243601 582.89422,23.252542 C 585.00354,23.367002 586.6165,25.066364 586.4945,27.045759 C 586.3725,29.02515 584.56161,30.53872 582.4523,30.42426 C 580.34294,30.309798 578.73002,28.610438 578.85201,26.631044 C 578.96442,24.806288 580.52282,23.349072 582.41656,23.226626 z " + style="fill:url(#linearGradient8705);fill-opacity:1;stroke:url(#linearGradient8708);stroke-width:1.58244443;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + style="fill:url(#linearGradient8710);fill-opacity:1;stroke:url(#linearGradient8712);stroke-width:1.58244514;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 565.98206,13.383882 L 565.88225,16.039358 C 565.62961,16.192194 565.39399,16.37346 565.18167,16.566266 L 562.35777,16.107495 L 561.92941,16.83963 L 563.89311,18.8475 C 563.8939,18.849701 563.89988,18.870928 563.90109,18.873275 C 563.85682,19.094927 563.81664,19.312556 563.81036,19.541141 L 563.78285,19.548645 L 561.39392,21.068031 L 561.64982,21.892834 L 564.55052,22.023059 C 564.71214,22.252006 564.90227,22.461909 565.10402,22.654719 C 565.1046,22.65698 565.11166,22.677888 565.11203,22.680493 L 564.57967,25.286372 L 565.38733,25.680839 L 567.48007,23.8789 C 567.4826,23.877798 567.50518,23.872145 567.50753,23.871399 C 567.7616,23.917309 568.01159,23.937009 568.27416,23.941555 L 569.91276,26.150078 L 570.79172,25.909956 L 570.91102,23.221199 C 571.13937,23.079266 571.34248,22.91768 571.53719,22.742575 L 574.36907,23.227123 L 574.78943,22.469214 L 572.86922,20.50539 C 572.92489,20.250762 572.95027,19.990933 572.95546,19.726919 L 575.27348,18.170992 L 575.0176,17.346187 L 572.19581,17.278282 C 572.04041,17.058136 571.87039,16.851688 571.67777,16.664894 C 571.67718,16.662631 571.67013,16.641719 571.66977,16.639119 L 572.16665,14.014968 L 571.35899,13.620501 L 569.27425,15.448212 C 569.02791,15.401949 568.75427,15.359653 568.49962,15.35228 L 568.47216,15.359784 L 566.86102,13.143762 L 565.98206,13.383882 z M 567.25244,17.286694 C 567.35831,17.243536 567.46833,17.227716 567.58206,17.196646 C 569.03758,16.799016 570.56279,17.584827 570.98653,18.950704 C 571.41024,20.316584 570.57287,21.747838 569.11734,22.145475 C 567.66177,22.543115 566.13659,21.757295 565.71286,20.391418 C 565.32221,19.132255 566.00328,17.795968 567.25244,17.286694 z " + id="path8701" /> + <path + id="path8703" + d="M 567.36972,30.814389 L 567.26991,33.469865 C 567.01727,33.622701 566.78165,33.803966 566.56933,33.996773 L 563.74543,33.538002 L 563.31707,34.270137 L 565.28077,36.278007 C 565.28156,36.280207 565.28754,36.301434 565.28875,36.303782 C 565.24448,36.525434 565.2043,36.743063 565.19802,36.971647 L 565.17051,36.979152 L 562.78158,38.498538 L 563.03748,39.32334 L 565.93818,39.453566 C 566.0998,39.682512 566.28993,39.892416 566.49168,40.085225 C 566.49226,40.087487 566.49932,40.108394 566.49969,40.111 L 565.96733,42.716879 L 566.77499,43.111346 L 568.86773,41.309407 C 568.87026,41.308305 568.89284,41.302652 568.89519,41.301906 C 569.14926,41.347816 569.39925,41.367516 569.66182,41.372062 L 571.30042,43.580584 L 572.17938,43.340463 L 572.29868,40.651705 C 572.52703,40.509773 572.73014,40.348187 572.92485,40.173082 L 575.75673,40.65763 L 576.17709,39.899721 L 574.25688,37.935897 C 574.31255,37.681269 574.33793,37.42144 574.34312,37.157426 L 576.66114,35.601499 L 576.40526,34.776694 L 573.58347,34.708789 C 573.42807,34.488643 573.25805,34.282195 573.06543,34.0954 C 573.06484,34.093138 573.05779,34.072226 573.05743,34.069626 L 573.55431,31.445475 L 572.74665,31.051008 L 570.66191,32.878719 C 570.41557,32.832456 570.14193,32.790159 569.88728,32.782787 L 569.85982,32.79029 L 568.24868,30.574269 L 567.36972,30.814389 z M 568.6401,34.717201 C 568.74597,34.674043 568.85599,34.658223 568.96972,34.627153 C 570.42524,34.229523 571.95045,35.015334 572.37419,36.381211 C 572.7979,37.747091 571.96053,39.178344 570.505,39.575982 C 569.04943,39.973621 567.52425,39.187801 567.10052,37.821925 C 566.70987,36.562762 567.39094,35.226475 568.6401,34.717201 z " + style="fill:url(#linearGradient8714);fill-opacity:1;stroke:url(#linearGradient8716);stroke-width:1.58244514;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + <g + transform="matrix(0.727273,0,0,0.727273,343.11471,249.77511)" + id="g8718" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/crystalsvg/16x16/actions/equalizer.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <rect + y="166.22156" + x="525.85938" + height="20.28125" + width="20.28125" + id="rect8720" + style="fill:url(#linearGradient8734);fill-opacity:1;stroke:url(#linearGradient8736);stroke-width:1.71875;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + y="167.08093" + x="529.13751" + height="18.5625" + width="1.35" + id="rect8722" + style="fill:#7e7e7e;fill-opacity:1;stroke:none;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + style="fill:#7e7e7e;fill-opacity:1;stroke:none;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="use8724" + width="1.5979586" + height="18.564598" + x="535.20087" + y="167.0799" /> + <rect + y="166.71782" + x="541.38837" + height="18.923731" + width="1.5979586" + id="use8726" + style="fill:#7e7e7e;fill-opacity:1;stroke:none;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + y="168.76106" + x="539.40027" + height="2.2022996" + width="5.2008667" + id="rect8728" + style="opacity:1;fill:url(#linearGradient8738);fill-opacity:1;stroke:url(#linearGradient8740);stroke-width:0.79770041;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + y="173.76106" + x="533.39886" + height="2.2022996" + width="5.2008667" + id="rect8730" + style="opacity:1;fill:url(#linearGradient8742);fill-opacity:1;stroke:url(#linearGradient8745);stroke-width:0.79770041;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + style="opacity:1;fill:url(#linearGradient8749);fill-opacity:1;stroke:url(#linearGradient8753);stroke-width:0.79770041;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect8732" + width="5.2008667" + height="2.2022996" + x="527.40027" + y="178.76103" /> + </g> + <g + transform="matrix(1.454545,0,0,1.454545,34.686513,113.51181)" + id="g8755" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/32x32/actions/amarok_equalizer.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <rect + y="166.22156" + x="525.85938" + height="20.28125" + width="20.28125" + id="rect8757" + style="fill:url(#linearGradient8771);fill-opacity:1;stroke:url(#linearGradient8773);stroke-width:1.71875;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + y="167.08093" + x="529.13751" + height="18.5625" + width="1.35" + id="rect8759" + style="fill:#7e7e7e;fill-opacity:1;stroke:none;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + style="fill:#7e7e7e;fill-opacity:1;stroke:none;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="use8761" + width="1.5979586" + height="18.564598" + x="535.20087" + y="167.0799" /> + <rect + y="166.71782" + x="541.38837" + height="18.923731" + width="1.5979586" + id="use8763" + style="fill:#7e7e7e;fill-opacity:1;stroke:none;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + y="168.76106" + x="539.40027" + height="2.2022996" + width="5.2008667" + id="rect8765" + style="opacity:1;fill:url(#linearGradient8776);fill-opacity:1;stroke:url(#linearGradient8778);stroke-width:0.79770041;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + y="173.76106" + x="533.39886" + height="2.2022996" + width="5.2008667" + id="rect8767" + style="opacity:1;fill:url(#linearGradient8780);fill-opacity:1;stroke:url(#linearGradient8782);stroke-width:0.79770041;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + style="opacity:1;fill:url(#linearGradient8784);fill-opacity:1;stroke:url(#linearGradient8786);stroke-width:0.79770041;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect8769" + width="5.2008667" + height="2.2022996" + x="527.40027" + y="178.76103" /> + </g> + <g + id="g8788" + transform="matrix(2.181818,0,0,2.181818,-297.29929,-22.751588)" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/48x48/actions/amarok_equalizer.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <rect + style="fill:url(#linearGradient8804);fill-opacity:1;stroke:url(#linearGradient8806);stroke-width:1.71875;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect8790" + width="20.28125" + height="20.28125" + x="525.85938" + y="166.22156" /> + <rect + style="fill:#7e7e7e;fill-opacity:1;stroke:none;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect8792" + width="1.35" + height="18.5625" + x="529.13751" + y="167.08093" /> + <rect + style="fill:#7e7e7e;fill-opacity:1;stroke:none;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="use8794" + width="1.5979586" + height="18.564598" + x="535.20087" + y="167.0799" /> + <rect + y="166.71782" + x="541.38837" + height="18.923731" + width="1.5979586" + id="use8796" + style="fill:#7e7e7e;fill-opacity:1;stroke:none;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + style="opacity:1;fill:url(#linearGradient8808);fill-opacity:1;stroke:url(#linearGradient8810);stroke-width:0.79770041;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect8798" + width="5.2008667" + height="2.2022996" + x="539.40027" + y="168.76106" /> + <rect + style="opacity:1;fill:url(#linearGradient8812);fill-opacity:1;stroke:url(#linearGradient8814);stroke-width:0.79770041;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect8800" + width="5.2008667" + height="2.2022996" + x="533.39886" + y="173.76106" /> + <rect + y="178.76103" + x="527.40027" + height="2.2022996" + width="5.2008667" + id="rect8802" + style="opacity:1;fill:url(#linearGradient8816);fill-opacity:1;stroke:url(#linearGradient8818);stroke-width:0.79770041;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + <g + transform="matrix(2.909091,0,0,2.909091,-609.35179,-159.01509)" + id="g8820" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/64x64/actions/amarok_equalizer.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <rect + y="166.22156" + x="525.85938" + height="20.28125" + width="20.28125" + id="rect8822" + style="fill:url(#linearGradient8836);fill-opacity:1;stroke:url(#linearGradient8838);stroke-width:1.71875;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + y="167.08093" + x="529.13751" + height="18.5625" + width="1.35" + id="rect8824" + style="fill:#7e7e7e;fill-opacity:1;stroke:none;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + style="fill:#7e7e7e;fill-opacity:1;stroke:none;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="use8826" + width="1.5979586" + height="18.564598" + x="535.20087" + y="167.0799" /> + <rect + y="166.71782" + x="541.38837" + height="18.923731" + width="1.5979586" + id="use8828" + style="fill:#7e7e7e;fill-opacity:1;stroke:none;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + y="168.76106" + x="539.40027" + height="2.2022996" + width="5.2008667" + id="rect8830" + style="opacity:1;fill:url(#linearGradient8840);fill-opacity:1;stroke:url(#linearGradient8842);stroke-width:0.79770041;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + y="173.76106" + x="533.39886" + height="2.2022996" + width="5.2008667" + id="rect8832" + style="opacity:1;fill:url(#linearGradient8844);fill-opacity:1;stroke:url(#linearGradient8846);stroke-width:0.79770041;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + style="opacity:1;fill:url(#linearGradient8848);fill-opacity:1;stroke:url(#linearGradient8851);stroke-width:0.79770041;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect8834" + width="5.2008667" + height="2.2022996" + x="527.40027" + y="178.76103" /> + </g> + <g + transform="matrix(0.727273,0,0,0.727273,312.51211,329.53111)" + id="g8853" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/16x16/actions/amarok_queue.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <rect + y="166.30807" + x="569.79236" + height="20.28125" + width="20.28125" + id="rect8855" + style="fill:url(#linearGradient8863);fill-opacity:1;stroke:url(#linearGradient8865);stroke-width:1.71875;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + inkscape:export-ydpi="3.3299999" + inkscape:export-xdpi="3.3299999" + inkscape:export-filename="/home/kant/Desktop/playlist16.png" + id="path8857" + d="M 573.05646,167.28611 C 572.40451,167.37374 572.07548,168.24128 572.38081,168.83153 C 573.32855,172.09512 574.2763,175.35871 575.22405,178.62229 C 573.51163,178.29976 571.5527,179.36745 571.06265,181.2123 C 570.70362,182.57556 571.4312,184.10824 572.61784,184.69923 C 574.38176,185.70799 576.89475,184.96152 577.76755,182.99262 C 578.18704,182.07955 578.05604,180.97383 577.5997,180.10682 C 576.85053,177.52471 576.10135,174.9426 575.35218,172.3605 C 576.73239,172.14057 578.16981,171.47234 578.9069,170.14282 C 579.18457,169.60292 579.48206,168.86038 579.04286,168.3206 C 578.68643,167.85281 578.03671,167.96495 577.6048,168.23706 C 576.48233,168.70955 575.24935,168.27756 574.21651,167.74724 C 573.85538,167.5376 573.48912,167.24977 573.05646,167.28611 z " + style="color:#000000;fill:url(#linearGradient8867);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient8869);stroke-width:0.41639617;stroke-linecap:round;stroke-linejoin:round;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0.75376886;visibility:visible;display:block;overflow:visible" /> + <path + style="color:#000000;fill:url(#linearGradient8871);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient8873);stroke-width:0.32545027;stroke-linecap:round;stroke-linejoin:round;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0.75376886;visibility:visible;display:block;overflow:visible" + d="M 579.93352,169.48004 C 579.42396,169.54853 579.1668,170.22659 579.40543,170.68792 C 580.14618,173.2387 580.88693,175.78948 581.62768,178.34027 C 580.28927,178.08818 578.7582,178.92267 578.37518,180.36458 C 578.09457,181.43009 578.66323,182.62801 579.59069,183.08993 C 580.96936,183.87836 582.93347,183.29493 583.61565,181.75606 C 583.94352,181.04241 583.84112,180.1782 583.48446,179.50055 C 582.89892,177.48241 582.31337,175.46426 581.72782,173.44612 C 582.80658,173.27423 583.93005,172.75195 584.50615,171.71281 C 584.72317,171.29083 584.95568,170.71047 584.61241,170.28859 C 584.33383,169.92296 583.82602,170.01061 583.48844,170.22329 C 582.61113,170.59258 581.64745,170.25495 580.84019,169.84046 C 580.55794,169.6766 580.27168,169.45163 579.93352,169.48004 z " + id="path8859" + inkscape:export-filename="/home/kant/Desktop/playlist16.png" + inkscape:export-xdpi="3.3299999" + inkscape:export-ydpi="3.3299999" /> + <path + inkscape:export-ydpi="3.3299999" + inkscape:export-xdpi="3.3299999" + inkscape:export-filename="/home/kant/Desktop/playlist16.png" + id="path8861" + d="M 585.43093,171.13933 C 585.0514,171.19034 584.85985,171.69538 585.0376,172.03899 C 585.58932,173.93887 586.14105,175.83875 586.69278,177.73863 C 585.6959,177.55087 584.55552,178.17242 584.27024,179.24639 C 584.06123,180.04 584.48479,180.93225 585.17558,181.27629 C 586.20244,181.86353 587.66536,181.42898 588.17346,180.28279 C 588.41767,179.75125 588.3414,179.10756 588.07575,178.60284 C 587.63962,177.09968 587.20349,175.59652 586.76736,174.09336 C 587.57085,173.96533 588.40763,173.57632 588.83673,172.80235 C 588.99837,172.48805 589.17155,172.05579 588.91587,171.74155 C 588.70838,171.46923 588.33015,171.53451 588.07872,171.69292 C 587.42528,171.96798 586.7075,171.7165 586.10624,171.40778 C 585.89601,171.28574 585.6828,171.11817 585.43093,171.13933 z " + style="color:#000000;fill:url(#linearGradient8875);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient8877);stroke-width:0.24240285;stroke-linecap:round;stroke-linejoin:round;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0.75376886;visibility:visible;display:block;overflow:visible" /> + </g> + <g + transform="matrix(1.454545,0,0,1.454545,-28.773387,192.29871)" + id="g8879" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/32x32/actions/amarok_queue.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <rect + y="166.30807" + x="569.79236" + height="20.28125" + width="20.28125" + id="rect8881" + style="fill:url(#linearGradient8889);fill-opacity:1;stroke:url(#linearGradient8891);stroke-width:1.71875;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + inkscape:export-ydpi="3.3299999" + inkscape:export-xdpi="3.3299999" + inkscape:export-filename="/home/kant/Desktop/playlist16.png" + id="path8883" + d="M 573.05646,167.28611 C 572.40451,167.37374 572.07548,168.24128 572.38081,168.83153 C 573.32855,172.09512 574.2763,175.35871 575.22405,178.62229 C 573.51163,178.29976 571.5527,179.36745 571.06265,181.2123 C 570.70362,182.57556 571.4312,184.10824 572.61784,184.69923 C 574.38176,185.70799 576.89475,184.96152 577.76755,182.99262 C 578.18704,182.07955 578.05604,180.97383 577.5997,180.10682 C 576.85053,177.52471 576.10135,174.9426 575.35218,172.3605 C 576.73239,172.14057 578.16981,171.47234 578.9069,170.14282 C 579.18457,169.60292 579.48206,168.86038 579.04286,168.3206 C 578.68643,167.85281 578.03671,167.96495 577.6048,168.23706 C 576.48233,168.70955 575.24935,168.27756 574.21651,167.74724 C 573.85538,167.5376 573.48912,167.24977 573.05646,167.28611 z " + style="color:#000000;fill:url(#linearGradient8893);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient8895);stroke-width:0.41639617;stroke-linecap:round;stroke-linejoin:round;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0.75376886;visibility:visible;display:block;overflow:visible" /> + <path + style="color:#000000;fill:url(#linearGradient8897);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient8899);stroke-width:0.32545027;stroke-linecap:round;stroke-linejoin:round;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0.75376886;visibility:visible;display:block;overflow:visible" + d="M 579.93352,169.48004 C 579.42396,169.54853 579.1668,170.22659 579.40543,170.68792 C 580.14618,173.2387 580.88693,175.78948 581.62768,178.34027 C 580.28927,178.08818 578.7582,178.92267 578.37518,180.36458 C 578.09457,181.43009 578.66323,182.62801 579.59069,183.08993 C 580.96936,183.87836 582.93347,183.29493 583.61565,181.75606 C 583.94352,181.04241 583.84112,180.1782 583.48446,179.50055 C 582.89892,177.48241 582.31337,175.46426 581.72782,173.44612 C 582.80658,173.27423 583.93005,172.75195 584.50615,171.71281 C 584.72317,171.29083 584.95568,170.71047 584.61241,170.28859 C 584.33383,169.92296 583.82602,170.01061 583.48844,170.22329 C 582.61113,170.59258 581.64745,170.25495 580.84019,169.84046 C 580.55794,169.6766 580.27168,169.45163 579.93352,169.48004 z " + id="path8885" + inkscape:export-filename="/home/kant/Desktop/playlist16.png" + inkscape:export-xdpi="3.3299999" + inkscape:export-ydpi="3.3299999" /> + <path + inkscape:export-ydpi="3.3299999" + inkscape:export-xdpi="3.3299999" + inkscape:export-filename="/home/kant/Desktop/playlist16.png" + id="path8887" + d="M 585.43093,171.13933 C 585.0514,171.19034 584.85985,171.69538 585.0376,172.03899 C 585.58932,173.93887 586.14105,175.83875 586.69278,177.73863 C 585.6959,177.55087 584.55552,178.17242 584.27024,179.24639 C 584.06123,180.04 584.48479,180.93225 585.17558,181.27629 C 586.20244,181.86353 587.66536,181.42898 588.17346,180.28279 C 588.41767,179.75125 588.3414,179.10756 588.07575,178.60284 C 587.63962,177.09968 587.20349,175.59652 586.76736,174.09336 C 587.57085,173.96533 588.40763,173.57632 588.83673,172.80235 C 588.99837,172.48805 589.17155,172.05579 588.91587,171.74155 C 588.70838,171.46923 588.33015,171.53451 588.07872,171.69292 C 587.42528,171.96798 586.7075,171.7165 586.10624,171.40778 C 585.89601,171.28574 585.6828,171.11817 585.43093,171.13933 z " + style="color:#000000;fill:url(#linearGradient8901);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient8903);stroke-width:0.24240285;stroke-linecap:round;stroke-linejoin:round;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0.75376886;visibility:visible;display:block;overflow:visible" /> + </g> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/48x48/actions/amarok_queue.png" + id="g8905" + transform="matrix(2.181818,0,0,2.181818,-393.61649,55.066312)"> + <rect + style="fill:url(#linearGradient8917);fill-opacity:1;stroke:url(#linearGradient8920);stroke-width:1.71875;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect8907" + width="20.28125" + height="20.28125" + x="569.79236" + y="166.30807" /> + <path + style="color:#000000;fill:url(#linearGradient8922);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient8924);stroke-width:0.41639617;stroke-linecap:round;stroke-linejoin:round;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0.75376886;visibility:visible;display:block;overflow:visible" + d="M 573.05646,167.28611 C 572.40451,167.37374 572.07548,168.24128 572.38081,168.83153 C 573.32855,172.09512 574.2763,175.35871 575.22405,178.62229 C 573.51163,178.29976 571.5527,179.36745 571.06265,181.2123 C 570.70362,182.57556 571.4312,184.10824 572.61784,184.69923 C 574.38176,185.70799 576.89475,184.96152 577.76755,182.99262 C 578.18704,182.07955 578.05604,180.97383 577.5997,180.10682 C 576.85053,177.52471 576.10135,174.9426 575.35218,172.3605 C 576.73239,172.14057 578.16981,171.47234 578.9069,170.14282 C 579.18457,169.60292 579.48206,168.86038 579.04286,168.3206 C 578.68643,167.85281 578.03671,167.96495 577.6048,168.23706 C 576.48233,168.70955 575.24935,168.27756 574.21651,167.74724 C 573.85538,167.5376 573.48912,167.24977 573.05646,167.28611 z " + id="path8910" + inkscape:export-filename="/home/kant/Desktop/playlist16.png" + inkscape:export-xdpi="3.3299999" + inkscape:export-ydpi="3.3299999" /> + <path + inkscape:export-ydpi="3.3299999" + inkscape:export-xdpi="3.3299999" + inkscape:export-filename="/home/kant/Desktop/playlist16.png" + id="path8912" + d="M 579.93352,169.48004 C 579.42396,169.54853 579.1668,170.22659 579.40543,170.68792 C 580.14618,173.2387 580.88693,175.78948 581.62768,178.34027 C 580.28927,178.08818 578.7582,178.92267 578.37518,180.36458 C 578.09457,181.43009 578.66323,182.62801 579.59069,183.08993 C 580.96936,183.87836 582.93347,183.29493 583.61565,181.75606 C 583.94352,181.04241 583.84112,180.1782 583.48446,179.50055 C 582.89892,177.48241 582.31337,175.46426 581.72782,173.44612 C 582.80658,173.27423 583.93005,172.75195 584.50615,171.71281 C 584.72317,171.29083 584.95568,170.71047 584.61241,170.28859 C 584.33383,169.92296 583.82602,170.01061 583.48844,170.22329 C 582.61113,170.59258 581.64745,170.25495 580.84019,169.84046 C 580.55794,169.6766 580.27168,169.45163 579.93352,169.48004 z " + style="color:#000000;fill:url(#linearGradient8926);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient8928);stroke-width:0.32545027;stroke-linecap:round;stroke-linejoin:round;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0.75376886;visibility:visible;display:block;overflow:visible" /> + <path + style="color:#000000;fill:url(#linearGradient8930);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient8932);stroke-width:0.24240285;stroke-linecap:round;stroke-linejoin:round;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0.75376886;visibility:visible;display:block;overflow:visible" + d="M 585.43093,171.13933 C 585.0514,171.19034 584.85985,171.69538 585.0376,172.03899 C 585.58932,173.93887 586.14105,175.83875 586.69278,177.73863 C 585.6959,177.55087 584.55552,178.17242 584.27024,179.24639 C 584.06123,180.04 584.48479,180.93225 585.17558,181.27629 C 586.20244,181.86353 587.66536,181.42898 588.17346,180.28279 C 588.41767,179.75125 588.3414,179.10756 588.07575,178.60284 C 587.63962,177.09968 587.20349,175.59652 586.76736,174.09336 C 587.57085,173.96533 588.40763,173.57632 588.83673,172.80235 C 588.99837,172.48805 589.17155,172.05579 588.91587,171.74155 C 588.70838,171.46923 588.33015,171.53451 588.07872,171.69292 C 587.42528,171.96798 586.7075,171.7165 586.10624,171.40778 C 585.89601,171.28574 585.6828,171.11817 585.43093,171.13933 z " + id="path8914" + inkscape:export-filename="/home/kant/Desktop/playlist16.png" + inkscape:export-xdpi="3.3299999" + inkscape:export-ydpi="3.3299999" /> + </g> + <g + transform="matrix(2.909091,0,0,2.909091,-739.43229,-83.072188)" + id="g8934" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/64x64/actions/amarok_queue.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <rect + y="166.30807" + x="569.79236" + height="20.28125" + width="20.28125" + id="rect8936" + style="fill:url(#linearGradient8944);fill-opacity:1;stroke:url(#linearGradient8946);stroke-width:1.71875;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + inkscape:export-ydpi="3.3299999" + inkscape:export-xdpi="3.3299999" + inkscape:export-filename="/home/kant/Desktop/playlist16.png" + id="path8938" + d="M 573.05646,167.28611 C 572.40451,167.37374 572.07548,168.24128 572.38081,168.83153 C 573.32855,172.09512 574.2763,175.35871 575.22405,178.62229 C 573.51163,178.29976 571.5527,179.36745 571.06265,181.2123 C 570.70362,182.57556 571.4312,184.10824 572.61784,184.69923 C 574.38176,185.70799 576.89475,184.96152 577.76755,182.99262 C 578.18704,182.07955 578.05604,180.97383 577.5997,180.10682 C 576.85053,177.52471 576.10135,174.9426 575.35218,172.3605 C 576.73239,172.14057 578.16981,171.47234 578.9069,170.14282 C 579.18457,169.60292 579.48206,168.86038 579.04286,168.3206 C 578.68643,167.85281 578.03671,167.96495 577.6048,168.23706 C 576.48233,168.70955 575.24935,168.27756 574.21651,167.74724 C 573.85538,167.5376 573.48912,167.24977 573.05646,167.28611 z " + style="color:#000000;fill:url(#linearGradient8948);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient8951);stroke-width:0.41639617;stroke-linecap:round;stroke-linejoin:round;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0.75376886;visibility:visible;display:block;overflow:visible" /> + <path + style="color:#000000;fill:url(#linearGradient8953);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient8955);stroke-width:0.32545027;stroke-linecap:round;stroke-linejoin:round;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0.75376886;visibility:visible;display:block;overflow:visible" + d="M 579.93352,169.48004 C 579.42396,169.54853 579.1668,170.22659 579.40543,170.68792 C 580.14618,173.2387 580.88693,175.78948 581.62768,178.34027 C 580.28927,178.08818 578.7582,178.92267 578.37518,180.36458 C 578.09457,181.43009 578.66323,182.62801 579.59069,183.08993 C 580.96936,183.87836 582.93347,183.29493 583.61565,181.75606 C 583.94352,181.04241 583.84112,180.1782 583.48446,179.50055 C 582.89892,177.48241 582.31337,175.46426 581.72782,173.44612 C 582.80658,173.27423 583.93005,172.75195 584.50615,171.71281 C 584.72317,171.29083 584.95568,170.71047 584.61241,170.28859 C 584.33383,169.92296 583.82602,170.01061 583.48844,170.22329 C 582.61113,170.59258 581.64745,170.25495 580.84019,169.84046 C 580.55794,169.6766 580.27168,169.45163 579.93352,169.48004 z " + id="path8940" + inkscape:export-filename="/home/kant/Desktop/playlist16.png" + inkscape:export-xdpi="3.3299999" + inkscape:export-ydpi="3.3299999" /> + <path + inkscape:export-ydpi="3.3299999" + inkscape:export-xdpi="3.3299999" + inkscape:export-filename="/home/kant/Desktop/playlist16.png" + id="path8942" + d="M 585.43093,171.13933 C 585.0514,171.19034 584.85985,171.69538 585.0376,172.03899 C 585.58932,173.93887 586.14105,175.83875 586.69278,177.73863 C 585.6959,177.55087 584.55552,178.17242 584.27024,179.24639 C 584.06123,180.04 584.48479,180.93225 585.17558,181.27629 C 586.20244,181.86353 587.66536,181.42898 588.17346,180.28279 C 588.41767,179.75125 588.3414,179.10756 588.07575,178.60284 C 587.63962,177.09968 587.20349,175.59652 586.76736,174.09336 C 587.57085,173.96533 588.40763,173.57632 588.83673,172.80235 C 588.99837,172.48805 589.17155,172.05579 588.91587,171.74155 C 588.70838,171.46923 588.33015,171.53451 588.07872,171.69292 C 587.42528,171.96798 586.7075,171.7165 586.10624,171.40778 C 585.89601,171.28574 585.6828,171.11817 585.43093,171.13933 z " + style="color:#000000;fill:url(#linearGradient8957);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient8959);stroke-width:0.24240285;stroke-linecap:round;stroke-linejoin:round;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0.75376886;visibility:visible;display:block;overflow:visible" /> + </g> + <g + transform="matrix(0.727273,0,0,0.727273,340.44361,377.35671)" + id="g8961" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/16x16/actions/amarok_scripts.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <rect + inkscape:export-ydpi="180" + inkscape:export-xdpi="180" + inkscape:export-filename="/home/vadim/amarok icons/1/32x32/actions/amarok_scripts.png" + style="fill:url(#linearGradient8990);fill-opacity:1;stroke:url(#linearGradient8992);stroke-width:1.71875;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect8963" + width="20.28125" + height="20.28125" + x="532.67584" + y="195.49319" /> + <path + sodipodi:nodetypes="ccccccccc" + id="path8974" + d="M 541.1954,201.32507 L 549.15029,201.32507 C 549.9856,201.32507 550.65807,202.08034 550.65807,203.01847 L 550.6641,210.87869 L 548.12202,213.86915 L 541.1954,213.86915 C 540.36009,213.86915 539.68762,213.11389 539.68762,212.17575 L 539.68762,203.01847 C 539.68762,202.08034 540.36009,201.32507 541.1954,201.32507 z " + style="fill:url(#linearGradient8994);fill-opacity:1;stroke:url(#linearGradient8998);stroke-width:0.58952814;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + sodipodi:nodetypes="ccccccccc" + id="path8980" + d="M 541.19897,201.60704 L 549.15275,201.60704 C 549.81911,201.60704 550.35557,202.23361 550.35557,203.0119 L 550.35557,211.32644 L 548.37364,213.54708 L 541.19897,213.58718 C 540.53261,213.58718 539.99614,212.96062 539.99614,212.18233 L 539.99614,203.0119 C 539.99614,202.23361 540.53261,201.60704 541.19897,201.60704 z " + style="fill:url(#linearGradient9000);fill-opacity:1;stroke:none;stroke-width:0.90379584;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" /> + <path + sodipodi:nodetypes="ccccz" + id="path8982" + d="M 547.9616,211.92933 L 547.9568,214.15853 C 547.9568,214.15853 550.90386,210.67811 550.90386,210.67811 L 549.21846,210.63112 C 548.47336,210.63896 547.96317,211.20186 547.9616,211.92933 z " + style="fill:#0004c1;fill-opacity:1;stroke:none;stroke-width:1.0039922;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" /> + <path + style="fill:url(#linearGradient9002);fill-opacity:1;stroke:url(#linearGradient9005);stroke-width:0.58952814;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 536.47662,197.39843 L 544.43151,197.39843 C 545.26682,197.39843 545.93929,198.15369 545.93929,199.09182 L 545.94532,206.95204 L 543.40324,209.9425 L 536.47662,209.9425 C 535.64132,209.9425 534.96884,209.18724 534.96884,208.2491 L 534.96884,199.09182 C 534.96884,198.15369 535.64132,197.39843 536.47662,197.39843 z " + id="path8984" + sodipodi:nodetypes="ccccccccc" /> + <path + style="fill:url(#linearGradient9008);fill-opacity:1;stroke:none;stroke-width:0.90379584;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 536.4802,197.68039 L 544.43398,197.68039 C 545.10033,197.68039 545.63679,198.30696 545.63679,199.08526 L 545.63679,207.3998 L 543.65487,209.62043 L 536.4802,209.66054 C 535.81383,209.66054 535.27737,209.03398 535.27737,208.25568 L 535.27737,199.08526 C 535.27737,198.30696 535.81383,197.68039 536.4802,197.68039 z " + id="path8986" + sodipodi:nodetypes="ccccccccc" /> + <path + style="fill:#0004c1;fill-opacity:1;stroke:none;stroke-width:1.0039922;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 543.24282,208.00268 L 543.23802,210.23188 C 543.23802,210.23188 546.25424,206.70516 546.25424,206.70516 L 544.49969,206.70448 C 543.75458,206.71231 543.24439,207.27521 543.24282,208.00268 z " + id="path8988" + sodipodi:nodetypes="ccccz" /> + </g> + <g + transform="matrix(1.454545,0,0,1.454545,29.776013,219.80501)" + id="g9010" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/32x32/actions/amarok_scripts.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <rect + inkscape:export-ydpi="180" + inkscape:export-xdpi="180" + inkscape:export-filename="/home/vadim/amarok icons/1/32x32/actions/amarok_scripts.png" + style="fill:url(#linearGradient9030);fill-opacity:1;stroke:url(#linearGradient9032);stroke-width:1.71875;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect9012" + width="20.28125" + height="20.28125" + x="532.67584" + y="195.49319" /> + <path + sodipodi:nodetypes="ccccccccc" + id="path9015" + d="M 541.1954,201.32507 L 549.15029,201.32507 C 549.9856,201.32507 550.65807,202.08034 550.65807,203.01847 L 550.6641,210.87869 L 548.12202,213.86915 L 541.1954,213.86915 C 540.36009,213.86915 539.68762,213.11389 539.68762,212.17575 L 539.68762,203.01847 C 539.68762,202.08034 540.36009,201.32507 541.1954,201.32507 z " + style="fill:url(#linearGradient9034);fill-opacity:1;stroke:url(#linearGradient9038);stroke-width:0.58952814;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + sodipodi:nodetypes="ccccccccc" + id="path9017" + d="M 541.19897,201.60704 L 549.15275,201.60704 C 549.81911,201.60704 550.35557,202.23361 550.35557,203.0119 L 550.35557,211.32644 L 548.37364,213.54708 L 541.19897,213.58718 C 540.53261,213.58718 539.99614,212.96062 539.99614,212.18233 L 539.99614,203.0119 C 539.99614,202.23361 540.53261,201.60704 541.19897,201.60704 z " + style="fill:url(#linearGradient9040);fill-opacity:1;stroke:none;stroke-width:0.90379584;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" /> + <path + sodipodi:nodetypes="ccccz" + id="path9019" + d="M 547.9616,211.92933 L 547.9568,214.15853 C 547.9568,214.15853 550.90386,210.67811 550.90386,210.67811 L 549.21846,210.63112 C 548.47336,210.63896 547.96317,211.20186 547.9616,211.92933 z " + style="fill:#0004c1;fill-opacity:1;stroke:none;stroke-width:1.0039922;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" /> + <path + style="fill:url(#linearGradient9043);fill-opacity:1;stroke:url(#linearGradient9045);stroke-width:0.58952814;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 536.47662,197.39843 L 544.43151,197.39843 C 545.26682,197.39843 545.93929,198.15369 545.93929,199.09182 L 545.94532,206.95204 L 543.40324,209.9425 L 536.47662,209.9425 C 535.64132,209.9425 534.96884,209.18724 534.96884,208.2491 L 534.96884,199.09182 C 534.96884,198.15369 535.64132,197.39843 536.47662,197.39843 z " + id="path9023" + sodipodi:nodetypes="ccccccccc" /> + <path + style="fill:url(#linearGradient9047);fill-opacity:1;stroke:none;stroke-width:0.90379584;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 536.4802,197.68039 L 544.43398,197.68039 C 545.10033,197.68039 545.63679,198.30696 545.63679,199.08526 L 545.63679,207.3998 L 543.65487,209.62043 L 536.4802,209.66054 C 535.81383,209.66054 535.27737,209.03398 535.27737,208.25568 L 535.27737,199.08526 C 535.27737,198.30696 535.81383,197.68039 536.4802,197.68039 z " + id="path9026" + sodipodi:nodetypes="ccccccccc" /> + <path + style="fill:#0004c1;fill-opacity:1;stroke:none;stroke-width:1.0039922;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 543.24282,208.00268 L 543.23802,210.23188 C 543.23802,210.23188 546.25424,206.70516 546.25424,206.70516 L 544.49969,206.70448 C 543.75458,206.71231 543.24439,207.27521 543.24282,208.00268 z " + id="path9028" + sodipodi:nodetypes="ccccz" /> + </g> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/48x48/actions/amarok_scripts.png" + id="g9049" + transform="matrix(2.181818,0,0,2.181818,-309.88539,62.253012)"> + <rect + y="195.49319" + x="532.67584" + height="20.28125" + width="20.28125" + id="rect9051" + style="fill:url(#linearGradient9066);fill-opacity:1;stroke:url(#linearGradient9068);stroke-width:1.71875;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + inkscape:export-filename="/home/vadim/amarok icons/1/32x32/actions/amarok_scripts.png" + inkscape:export-xdpi="180" + inkscape:export-ydpi="180" /> + <path + style="fill:url(#linearGradient9070);fill-opacity:1;stroke:url(#linearGradient9072);stroke-width:0.58952814;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 541.1954,201.32507 L 549.15029,201.32507 C 549.9856,201.32507 550.65807,202.08034 550.65807,203.01847 L 550.6641,210.87869 L 548.12202,213.86915 L 541.1954,213.86915 C 540.36009,213.86915 539.68762,213.11389 539.68762,212.17575 L 539.68762,203.01847 C 539.68762,202.08034 540.36009,201.32507 541.1954,201.32507 z " + id="path9053" + sodipodi:nodetypes="ccccccccc" /> + <path + style="fill:url(#linearGradient9074);fill-opacity:1;stroke:none;stroke-width:0.90379584;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 541.19897,201.60704 L 549.15275,201.60704 C 549.81911,201.60704 550.35557,202.23361 550.35557,203.0119 L 550.35557,211.32644 L 548.37364,213.54708 L 541.19897,213.58718 C 540.53261,213.58718 539.99614,212.96062 539.99614,212.18233 L 539.99614,203.0119 C 539.99614,202.23361 540.53261,201.60704 541.19897,201.60704 z " + id="path9055" + sodipodi:nodetypes="ccccccccc" /> + <path + style="fill:#0004c1;fill-opacity:1;stroke:none;stroke-width:1.0039922;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 547.9616,211.92933 L 547.9568,214.15853 C 547.9568,214.15853 550.90386,210.67811 550.90386,210.67811 L 549.21846,210.63112 C 548.47336,210.63896 547.96317,211.20186 547.9616,211.92933 z " + id="path9057" + sodipodi:nodetypes="ccccz" /> + <path + sodipodi:nodetypes="ccccccccc" + id="path9059" + d="M 536.47662,197.39843 L 544.43151,197.39843 C 545.26682,197.39843 545.93929,198.15369 545.93929,199.09182 L 545.94532,206.95204 L 543.40324,209.9425 L 536.47662,209.9425 C 535.64132,209.9425 534.96884,209.18724 534.96884,208.2491 L 534.96884,199.09182 C 534.96884,198.15369 535.64132,197.39843 536.47662,197.39843 z " + style="fill:url(#linearGradient9076);fill-opacity:1;stroke:url(#linearGradient9078);stroke-width:0.58952814;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + sodipodi:nodetypes="ccccccccc" + id="path9061" + d="M 536.4802,197.68039 L 544.43398,197.68039 C 545.10033,197.68039 545.63679,198.30696 545.63679,199.08526 L 545.63679,207.3998 L 543.65487,209.62043 L 536.4802,209.66054 C 535.81383,209.66054 535.27737,209.03398 535.27737,208.25568 L 535.27737,199.08526 C 535.27737,198.30696 535.81383,197.68039 536.4802,197.68039 z " + style="fill:url(#linearGradient9080);fill-opacity:1;stroke:none;stroke-width:0.90379584;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" /> + <path + sodipodi:nodetypes="ccccz" + id="path9063" + d="M 543.24282,208.00268 L 543.23802,210.23188 C 543.23802,210.23188 546.25424,206.70516 546.25424,206.70516 L 544.49969,206.70448 C 543.75458,206.71231 543.24439,207.27521 543.24282,208.00268 z " + style="fill:#0004c1;fill-opacity:1;stroke:none;stroke-width:1.0039922;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" /> + </g> + <g + transform="matrix(2.909091,0,0,2.909091,-626.89529,-95.298888)" + id="g9082" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/64x64/actions/amarok_scripts.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <rect + inkscape:export-ydpi="180" + inkscape:export-xdpi="180" + inkscape:export-filename="/home/vadim/amarok icons/1/32x32/actions/amarok_scripts.png" + style="fill:url(#linearGradient9098);fill-opacity:1;stroke:url(#linearGradient9100);stroke-width:1.71875;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect9084" + width="20.28125" + height="20.28125" + x="532.67584" + y="195.49319" /> + <path + sodipodi:nodetypes="ccccccccc" + id="path9086" + d="M 541.1954,201.32507 L 549.15029,201.32507 C 549.9856,201.32507 550.65807,202.08034 550.65807,203.01847 L 550.6641,210.87869 L 548.12202,213.86915 L 541.1954,213.86915 C 540.36009,213.86915 539.68762,213.11389 539.68762,212.17575 L 539.68762,203.01847 C 539.68762,202.08034 540.36009,201.32507 541.1954,201.32507 z " + style="fill:url(#linearGradient9102);fill-opacity:1;stroke:url(#linearGradient9104);stroke-width:0.58952814;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + sodipodi:nodetypes="ccccccccc" + id="path9088" + d="M 541.19897,201.60704 L 549.15275,201.60704 C 549.81911,201.60704 550.35557,202.23361 550.35557,203.0119 L 550.35557,211.32644 L 548.37364,213.54708 L 541.19897,213.58718 C 540.53261,213.58718 539.99614,212.96062 539.99614,212.18233 L 539.99614,203.0119 C 539.99614,202.23361 540.53261,201.60704 541.19897,201.60704 z " + style="fill:url(#linearGradient9106);fill-opacity:1;stroke:none;stroke-width:0.90379584;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" /> + <path + sodipodi:nodetypes="ccccz" + id="path9090" + d="M 547.9616,211.92933 L 547.9568,214.15853 C 547.9568,214.15853 550.90386,210.67811 550.90386,210.67811 L 549.21846,210.63112 C 548.47336,210.63896 547.96317,211.20186 547.9616,211.92933 z " + style="fill:#0004c1;fill-opacity:1;stroke:none;stroke-width:1.0039922;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" /> + <path + style="fill:url(#linearGradient9108);fill-opacity:1;stroke:url(#linearGradient9110);stroke-width:0.58952814;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 536.47662,197.39843 L 544.43151,197.39843 C 545.26682,197.39843 545.93929,198.15369 545.93929,199.09182 L 545.94532,206.95204 L 543.40324,209.9425 L 536.47662,209.9425 C 535.64132,209.9425 534.96884,209.18724 534.96884,208.2491 L 534.96884,199.09182 C 534.96884,198.15369 535.64132,197.39843 536.47662,197.39843 z " + id="path9092" + sodipodi:nodetypes="ccccccccc" /> + <path + style="fill:url(#linearGradient9112);fill-opacity:1;stroke:none;stroke-width:0.90379584;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 536.4802,197.68039 L 544.43398,197.68039 C 545.10033,197.68039 545.63679,198.30696 545.63679,199.08526 L 545.63679,207.3998 L 543.65487,209.62043 L 536.4802,209.66054 C 535.81383,209.66054 535.27737,209.03398 535.27737,208.25568 L 535.27737,199.08526 C 535.27737,198.30696 535.81383,197.68039 536.4802,197.68039 z " + id="path9094" + sodipodi:nodetypes="ccccccccc" /> + <path + style="fill:#0004c1;fill-opacity:1;stroke:none;stroke-width:1.0039922;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 543.24282,208.00268 L 543.23802,210.23188 C 543.23802,210.23188 546.25424,206.70516 546.25424,206.70516 L 544.49969,206.70448 C 543.75458,206.71231 543.24439,207.27521 543.24282,208.00268 z " + id="path9096" + sodipodi:nodetypes="ccccz" /> + </g> + <g + transform="matrix(0.727273,0,0,0.727273,310.75521,449.69611)" + id="g9114" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/crystalsvg/16x16/actions/visualizations.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <rect + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/vadim/amarok icons/1/16x16/actions/amarok_visualizations.png" + style="fill:url(#linearGradient9120);fill-opacity:1;stroke:url(#linearGradient9122);stroke-width:1.71875;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect9116" + width="20.28125" + height="20.28125" + x="569.99475" + y="194.96288" /> + <path + sodipodi:nodetypes="ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc" + id="path9118" + d="M 571.88535,196.85351 L 571.88535,199.60351 L 574.63535,199.60351 L 574.63535,196.85351 L 571.88535,196.85351 z M 582.19785,196.85351 L 582.19785,199.60351 L 584.94785,199.60351 L 584.94785,196.85351 L 582.19785,196.85351 z M 571.88535,200.29101 L 571.88535,203.04101 L 574.63535,203.04101 L 574.63535,200.29101 L 571.88535,200.29101 z M 578.76035,200.29101 L 578.76035,203.04101 L 581.51035,203.04101 L 581.51035,200.29101 L 578.76035,200.29101 z M 582.19785,200.29101 L 582.19785,203.04101 L 584.94785,203.04101 L 584.94785,200.29101 L 582.19785,200.29101 z M 585.63535,200.29101 L 585.63535,203.04101 L 588.38535,203.04101 L 588.38535,200.29101 L 585.63535,200.29101 z M 571.88535,203.72851 L 571.88535,206.47851 L 574.63535,206.47851 L 574.63535,203.72851 L 571.88535,203.72851 z M 575.32285,203.72851 L 575.32285,206.47851 L 578.07285,206.47851 L 578.07285,203.72851 L 575.32285,203.72851 z M 578.76035,203.72851 L 578.76035,206.47851 L 581.51035,206.47851 L 581.51035,203.72851 L 578.76035,203.72851 z M 582.19785,203.72851 L 582.19785,206.47851 L 584.94785,206.47851 L 584.94785,203.72851 L 582.19785,203.72851 z M 585.63535,203.72851 L 585.63535,206.47851 L 588.38535,206.47851 L 588.38535,203.72851 L 585.63535,203.72851 z M 571.88535,207.16601 L 571.88535,209.91601 L 574.63535,209.91601 L 574.63535,207.16601 L 571.88535,207.16601 z M 575.32285,207.16601 L 575.32285,209.91601 L 578.07285,209.91601 L 578.07285,207.16601 L 575.32285,207.16601 z M 578.76035,207.16601 L 578.76035,209.91601 L 581.51035,209.91601 L 581.51035,207.16601 L 578.76035,207.16601 z M 582.19785,207.16601 L 582.19785,209.91601 L 584.94785,209.91601 L 584.94785,207.16601 L 582.19785,207.16601 z M 585.63535,207.16601 L 585.63535,209.91601 L 588.38535,209.91601 L 588.38535,207.16601 L 585.63535,207.16601 z M 571.88535,210.60351 L 571.88535,213.35351 L 574.63535,213.35351 L 574.63535,210.60351 L 571.88535,210.60351 z M 575.32285,210.60351 L 575.32285,213.35351 L 578.07285,213.35351 L 578.07285,210.60351 L 575.32285,210.60351 z M 578.76035,210.60351 L 578.76035,213.35351 L 581.51035,213.35351 L 581.51035,210.60351 L 578.76035,210.60351 z M 582.19785,210.60351 L 582.19785,213.35351 L 584.94785,213.35351 L 584.94785,210.60351 L 582.19785,210.60351 z M 585.63535,210.60351 L 585.63535,213.35351 L 588.38535,213.35351 L 588.38535,210.60351 L 585.63535,210.60351 z " + style="fill:url(#linearGradient9124);fill-opacity:1;stroke:none;stroke-width:1.25;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + <g + transform="matrix(1.454545,0,0,1.454545,-30.677387,292.52981)" + id="g9126" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/32x32/actions/amarok_visualizations.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <rect + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/vadim/amarok icons/1/16x16/actions/amarok_visualizations.png" + style="fill:url(#linearGradient9132);fill-opacity:1;stroke:url(#linearGradient9134);stroke-width:1.71875;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect9128" + width="20.28125" + height="20.28125" + x="569.99475" + y="194.96288" /> + <path + sodipodi:nodetypes="ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc" + id="path9130" + d="M 571.88535,196.85351 L 571.88535,199.60351 L 574.63535,199.60351 L 574.63535,196.85351 L 571.88535,196.85351 z M 582.19785,196.85351 L 582.19785,199.60351 L 584.94785,199.60351 L 584.94785,196.85351 L 582.19785,196.85351 z M 571.88535,200.29101 L 571.88535,203.04101 L 574.63535,203.04101 L 574.63535,200.29101 L 571.88535,200.29101 z M 578.76035,200.29101 L 578.76035,203.04101 L 581.51035,203.04101 L 581.51035,200.29101 L 578.76035,200.29101 z M 582.19785,200.29101 L 582.19785,203.04101 L 584.94785,203.04101 L 584.94785,200.29101 L 582.19785,200.29101 z M 585.63535,200.29101 L 585.63535,203.04101 L 588.38535,203.04101 L 588.38535,200.29101 L 585.63535,200.29101 z M 571.88535,203.72851 L 571.88535,206.47851 L 574.63535,206.47851 L 574.63535,203.72851 L 571.88535,203.72851 z M 575.32285,203.72851 L 575.32285,206.47851 L 578.07285,206.47851 L 578.07285,203.72851 L 575.32285,203.72851 z M 578.76035,203.72851 L 578.76035,206.47851 L 581.51035,206.47851 L 581.51035,203.72851 L 578.76035,203.72851 z M 582.19785,203.72851 L 582.19785,206.47851 L 584.94785,206.47851 L 584.94785,203.72851 L 582.19785,203.72851 z M 585.63535,203.72851 L 585.63535,206.47851 L 588.38535,206.47851 L 588.38535,203.72851 L 585.63535,203.72851 z M 571.88535,207.16601 L 571.88535,209.91601 L 574.63535,209.91601 L 574.63535,207.16601 L 571.88535,207.16601 z M 575.32285,207.16601 L 575.32285,209.91601 L 578.07285,209.91601 L 578.07285,207.16601 L 575.32285,207.16601 z M 578.76035,207.16601 L 578.76035,209.91601 L 581.51035,209.91601 L 581.51035,207.16601 L 578.76035,207.16601 z M 582.19785,207.16601 L 582.19785,209.91601 L 584.94785,209.91601 L 584.94785,207.16601 L 582.19785,207.16601 z M 585.63535,207.16601 L 585.63535,209.91601 L 588.38535,209.91601 L 588.38535,207.16601 L 585.63535,207.16601 z M 571.88535,210.60351 L 571.88535,213.35351 L 574.63535,213.35351 L 574.63535,210.60351 L 571.88535,210.60351 z M 575.32285,210.60351 L 575.32285,213.35351 L 578.07285,213.35351 L 578.07285,210.60351 L 575.32285,210.60351 z M 578.76035,210.60351 L 578.76035,213.35351 L 581.51035,213.35351 L 581.51035,210.60351 L 578.76035,210.60351 z M 582.19785,210.60351 L 582.19785,213.35351 L 584.94785,213.35351 L 584.94785,210.60351 L 582.19785,210.60351 z M 585.63535,210.60351 L 585.63535,213.35351 L 588.38535,213.35351 L 588.38535,210.60351 L 585.63535,210.60351 z " + style="fill:url(#linearGradient9136);fill-opacity:1;stroke:none;stroke-width:1.25;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/64x64/actions/amarok_visualizations.png" + id="g9138" + transform="matrix(2.909091,0,0,2.909091,-731.66449,-21.802688)"> + <rect + y="194.96288" + x="569.99475" + height="20.28125" + width="20.28125" + id="rect9140" + style="fill:url(#linearGradient9144);fill-opacity:1;stroke:url(#linearGradient9146);stroke-width:1.71875;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + inkscape:export-filename="/home/vadim/amarok icons/1/16x16/actions/amarok_visualizations.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + style="fill:url(#linearGradient9148);fill-opacity:1;stroke:none;stroke-width:1.25;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 571.88535,196.85351 L 571.88535,199.60351 L 574.63535,199.60351 L 574.63535,196.85351 L 571.88535,196.85351 z M 582.19785,196.85351 L 582.19785,199.60351 L 584.94785,199.60351 L 584.94785,196.85351 L 582.19785,196.85351 z M 571.88535,200.29101 L 571.88535,203.04101 L 574.63535,203.04101 L 574.63535,200.29101 L 571.88535,200.29101 z M 578.76035,200.29101 L 578.76035,203.04101 L 581.51035,203.04101 L 581.51035,200.29101 L 578.76035,200.29101 z M 582.19785,200.29101 L 582.19785,203.04101 L 584.94785,203.04101 L 584.94785,200.29101 L 582.19785,200.29101 z M 585.63535,200.29101 L 585.63535,203.04101 L 588.38535,203.04101 L 588.38535,200.29101 L 585.63535,200.29101 z M 571.88535,203.72851 L 571.88535,206.47851 L 574.63535,206.47851 L 574.63535,203.72851 L 571.88535,203.72851 z M 575.32285,203.72851 L 575.32285,206.47851 L 578.07285,206.47851 L 578.07285,203.72851 L 575.32285,203.72851 z M 578.76035,203.72851 L 578.76035,206.47851 L 581.51035,206.47851 L 581.51035,203.72851 L 578.76035,203.72851 z M 582.19785,203.72851 L 582.19785,206.47851 L 584.94785,206.47851 L 584.94785,203.72851 L 582.19785,203.72851 z M 585.63535,203.72851 L 585.63535,206.47851 L 588.38535,206.47851 L 588.38535,203.72851 L 585.63535,203.72851 z M 571.88535,207.16601 L 571.88535,209.91601 L 574.63535,209.91601 L 574.63535,207.16601 L 571.88535,207.16601 z M 575.32285,207.16601 L 575.32285,209.91601 L 578.07285,209.91601 L 578.07285,207.16601 L 575.32285,207.16601 z M 578.76035,207.16601 L 578.76035,209.91601 L 581.51035,209.91601 L 581.51035,207.16601 L 578.76035,207.16601 z M 582.19785,207.16601 L 582.19785,209.91601 L 584.94785,209.91601 L 584.94785,207.16601 L 582.19785,207.16601 z M 585.63535,207.16601 L 585.63535,209.91601 L 588.38535,209.91601 L 588.38535,207.16601 L 585.63535,207.16601 z M 571.88535,210.60351 L 571.88535,213.35351 L 574.63535,213.35351 L 574.63535,210.60351 L 571.88535,210.60351 z M 575.32285,210.60351 L 575.32285,213.35351 L 578.07285,213.35351 L 578.07285,210.60351 L 575.32285,210.60351 z M 578.76035,210.60351 L 578.76035,213.35351 L 581.51035,213.35351 L 581.51035,210.60351 L 578.76035,210.60351 z M 582.19785,210.60351 L 582.19785,213.35351 L 584.94785,213.35351 L 584.94785,210.60351 L 582.19785,210.60351 z M 585.63535,210.60351 L 585.63535,213.35351 L 588.38535,213.35351 L 588.38535,210.60351 L 585.63535,210.60351 z " + id="path9142" + sodipodi:nodetypes="ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc" /> + </g> + <g + transform="matrix(2.181818,0,0,2.181818,-384.79509,135.36351)" + id="g9150" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/48x48/actions/amarok_visualizations.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <rect + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/vadim/amarok icons/1/16x16/actions/amarok_visualizations.png" + style="fill:url(#linearGradient9156);fill-opacity:1;stroke:url(#linearGradient9158);stroke-width:1.71875;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect9152" + width="20.28125" + height="20.28125" + x="569.99475" + y="194.96288" /> + <path + sodipodi:nodetypes="ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc" + id="path9154" + d="M 571.88535,196.85351 L 571.88535,199.60351 L 574.63535,199.60351 L 574.63535,196.85351 L 571.88535,196.85351 z M 582.19785,196.85351 L 582.19785,199.60351 L 584.94785,199.60351 L 584.94785,196.85351 L 582.19785,196.85351 z M 571.88535,200.29101 L 571.88535,203.04101 L 574.63535,203.04101 L 574.63535,200.29101 L 571.88535,200.29101 z M 578.76035,200.29101 L 578.76035,203.04101 L 581.51035,203.04101 L 581.51035,200.29101 L 578.76035,200.29101 z M 582.19785,200.29101 L 582.19785,203.04101 L 584.94785,203.04101 L 584.94785,200.29101 L 582.19785,200.29101 z M 585.63535,200.29101 L 585.63535,203.04101 L 588.38535,203.04101 L 588.38535,200.29101 L 585.63535,200.29101 z M 571.88535,203.72851 L 571.88535,206.47851 L 574.63535,206.47851 L 574.63535,203.72851 L 571.88535,203.72851 z M 575.32285,203.72851 L 575.32285,206.47851 L 578.07285,206.47851 L 578.07285,203.72851 L 575.32285,203.72851 z M 578.76035,203.72851 L 578.76035,206.47851 L 581.51035,206.47851 L 581.51035,203.72851 L 578.76035,203.72851 z M 582.19785,203.72851 L 582.19785,206.47851 L 584.94785,206.47851 L 584.94785,203.72851 L 582.19785,203.72851 z M 585.63535,203.72851 L 585.63535,206.47851 L 588.38535,206.47851 L 588.38535,203.72851 L 585.63535,203.72851 z M 571.88535,207.16601 L 571.88535,209.91601 L 574.63535,209.91601 L 574.63535,207.16601 L 571.88535,207.16601 z M 575.32285,207.16601 L 575.32285,209.91601 L 578.07285,209.91601 L 578.07285,207.16601 L 575.32285,207.16601 z M 578.76035,207.16601 L 578.76035,209.91601 L 581.51035,209.91601 L 581.51035,207.16601 L 578.76035,207.16601 z M 582.19785,207.16601 L 582.19785,209.91601 L 584.94785,209.91601 L 584.94785,207.16601 L 582.19785,207.16601 z M 585.63535,207.16601 L 585.63535,209.91601 L 588.38535,209.91601 L 588.38535,207.16601 L 585.63535,207.16601 z M 571.88535,210.60351 L 571.88535,213.35351 L 574.63535,213.35351 L 574.63535,210.60351 L 571.88535,210.60351 z M 575.32285,210.60351 L 575.32285,213.35351 L 578.07285,213.35351 L 578.07285,210.60351 L 575.32285,210.60351 z M 578.76035,210.60351 L 578.76035,213.35351 L 581.51035,213.35351 L 581.51035,210.60351 L 578.76035,210.60351 z M 582.19785,210.60351 L 582.19785,213.35351 L 584.94785,213.35351 L 584.94785,210.60351 L 582.19785,210.60351 z M 585.63535,210.60351 L 585.63535,213.35351 L 588.38535,213.35351 L 588.38535,210.60351 L 585.63535,210.60351 z " + style="fill:url(#linearGradient9160);fill-opacity:1;stroke:none;stroke-width:1.25;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + <g + id="g9354" + transform="matrix(2.9102772,0,0,2.7306454,-735.99867,-51.804638)" + inkscape:export-filename="/home/lando/Projects/Amarok/icons/64/amarok_files.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <rect + y="248.20387" + x="571.11212" + height="18.118319" + width="18.63254" + id="rect9356" + style="fill:url(#linearGradient9414);fill-opacity:1;stroke:none;stroke-width:0.97624999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + rx="0.19135316" + ry="0.29134434" + y="249.92944" + x="571.6601" + height="15.785593" + width="16.988491" + id="rect9358" + style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + sodipodi:nodetypes="ccczccc" + id="path9360" + d="M 570.00242,253.00028 L 575.50872,252.97546 L 580.43808,250.53657 C 580.43808,250.53657 591.76762,250.11752 591.95659,250.58959 C 592.12569,251.01206 589.43891,261.90861 589.6534,266.26916 L 571.22277,266.32217 C 571.25576,262.15603 571.40471,260.02943 570.00242,253.00028 z " + style="opacity:0.6978022;fill:url(#linearGradient9416);fill-opacity:1;stroke:none;stroke-width:0.97624999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1" /> + <path + sodipodi:nodetypes="cccc" + id="path9362" + d="M 570.11373,252.99334 L 575.59692,252.96514 L 580.45816,250.53657 L 591.82763,250.53657" + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:url(#linearGradient26781);stroke-width:0.28610274;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + ry="0" + rx="0" + y="253.9832" + x="570.17865" + height="0.46407768" + width="21.23255" + id="rect9364" + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect9366" + width="18.571243" + height="0.46407768" + x="571.14294" + y="264.92337" + rx="0" + ry="0" /> + <rect + ry="0" + rx="0" + y="263.70779" + x="571.14294" + height="0.46407768" + width="18.571243" + id="rect9368" + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect9370" + width="18.714163" + height="0.46407768" + x="571.10718" + y="262.49222" + rx="0" + ry="0" /> + <rect + ry="0" + rx="0" + y="261.27664" + x="571.10718" + height="0.46407768" + width="18.892811" + id="rect9372" + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect9374" + width="19.229099" + height="0.46407768" + x="571.07147" + y="259.45328" + rx="0" + ry="0" /> + <rect + ry="0" + rx="0" + y="258.2377" + x="570.89282" + height="0.46407768" + width="19.638405" + id="rect9376" + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect9378" + width="20.892811" + height="0.46407768" + x="570.28583" + y="254.59099" + rx="0" + ry="0" /> + <rect + ry="0" + rx="0.030011397" + y="255.80656" + x="570.53552" + height="0.46407768" + width="20.464485" + id="rect9380" + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect9382" + width="20.024101" + height="0.46407768" + x="570.71417" + y="257.02213" + rx="0" + ry="0" /> + <rect + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect9384" + width="16.012278" + height="0.46407768" + x="575.61328" + y="252.76762" + rx="0" + ry="0" /> + <rect + ry="0" + rx="0" + y="264.31558" + x="571.10718" + height="0.46407768" + width="18.642706" + id="rect9386" + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect9388" + width="18.571243" + height="0.46407768" + x="571.14294" + y="263.10001" + rx="0" + ry="0" /> + <rect + ry="0" + rx="0" + y="261.88443" + x="571.21436" + height="0.46407768" + width="18.749891" + id="rect9390" + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect9392" + width="19.03573" + height="0.46407768" + x="571.10718" + y="260.66885" + rx="0" + ry="0" /> + <rect + ry="0" + rx="0" + y="260.06107" + x="571.07147" + height="0.46407768" + width="19.121912" + id="rect9394" + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect9396" + width="19.424026" + height="0.46407768" + x="571" + y="258.84549" + rx="0" + ry="0" /> + <rect + ry="0" + rx="0" + y="253.37541" + x="570" + height="0.46407768" + width="21.464485" + id="rect9398" + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect9400" + width="20.250107" + height="0.46407768" + x="570.60699" + y="256.41434" + rx="0" + ry="0" /> + <rect + ry="0" + rx="0" + y="257.62991" + x="570.82135" + height="0.46407768" + width="19.809723" + id="rect9402" + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + ry="0" + rx="0" + y="255.19878" + x="570.37927" + height="0.46407768" + width="20.799389" + id="rect9404" + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + ry="0" + rx="0" + y="252.15984" + x="576.87366" + height="0.46407768" + width="14.800593" + id="rect9406" + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect9408" + width="13.804427" + height="0.46407768" + x="578.00934" + y="251.55205" + rx="0" + ry="0" /> + <rect + ry="0" + rx="0" + y="250.94426" + x="579.32727" + height="0.46407768" + width="12.505892" + id="rect9410" + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect9412" + width="18.584423" + height="0.46407768" + x="571.12012" + y="265.53116" + rx="0" + ry="0" /> + </g> + <g + id="g18633" + transform="translate(-116.78026,-376.60451)" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/32x32/actions/amarok_settings_engine.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Setting-dialog/amarok_settings_engine.png" + transform="matrix(0.792918,0,0,0.76641,745.2599,642.905)" + id="g8882" + style="display:inline"> + <g + id="g1462" + transform="matrix(0.710065,0,0,0.63994,617.4801,45.62798)"> + <path + d="M 45.936,27.94 L 45.233,26.783 L 44.282,32.5 L 47.689,30.98 L 45.936,27.94 z " + style="fill:#ffffff" + id="path1464" /> + <path + d="M 85.14,50.286 L 82.531,50.264 L 81.42,50.308 L 81.646,54.354 L 85.14,50.286 z " + style="fill:#ffffff" + id="path1466" /> + <path + d="M 38.56,42.962 L 39.772,37.613 L 36.078,34.799 L 35.265,34.352 L 34.427,40.249 L 37.467,43.023 L 38.56,42.962 z " + style="fill:#ffffff" + id="path1468" /> + <path + d="M 39.6,59.891 L 42.767,56.869 L 39.487,50.521 L 32.926,49.88 L 32.218,43.364 L 32.13,43.361 L 31.005,49.145 L 31.67,55.573 L 37.786,56.202 L 39.6,59.891 z " + style="fill:#ffffff" + id="path1470" /> + <path + d="M 84.592,63.482 L 80.635,61.503 L 77.141,65.853 L 78.351,68.12 L 80.929,69.476 L 85.687,64.113 L 84.592,63.482 z " + style="fill:#ffffff" + id="path1472" /> + </g> + <path + d="M 665.32854,69.75563 C 659.85394,66.552731 653.30216,66.889339 650.69551,70.508199 C 649.86758,71.658171 649.53882,73.000766 649.64817,74.399674 C 651.7153,69.674455 659.29717,69.172118 664.14053,72.23871 C 668.31358,74.879743 670.55782,80.390816 668.09638,84.108203 C 669.0784,83.624409 669.90988,82.957591 670.52196,82.107751 C 673.12932,78.48953 670.80386,72.95917 665.32854,69.75563 z " + style="fill:url(#linearGradient18723);fill-opacity:1" + id="path1488" + sodipodi:nodetypes="cscscsc" /> + <path + d="M 644.49735,84.876771 L 643.41379,88.344605 L 647.14306,92.145209 L 650.77717,89.775511 L 655.21437,92.23416 L 655.26123,96.884604 L 660.00234,97.735084 L 662.73751,94.462431 L 662.90012,93.473724 L 665.4102,93.396931 L 667.95933,96.929399 L 671.49901,95.230999 L 674.62685,91.905871 L 674.68507,91.878354 L 673.1137,89.221963 L 674.94425,90.089721 L 678.36534,86.856745 L 678.88795,83.390191 L 675.49455,81.108805 L 675.45195,80.41191 L 677.87469,77.731842 L 676.15278,74.582697 L 673.31749,74.035548 L 670.78611,71.023351 L 672.00103,69.563009 L 669.11603,67.142756 L 666.93897,68.100746 L 663.29278,65.864795 L 663.24166,64.22143 L 659.29228,62.715011 L 658.38055,64.86009 L 655.13981,64.312301 L 653.36749,62.30417 L 649.42876,62.748288 L 648.92319,66.425383 L 648.05052,66.77607 L 644.6067,65.224216 L 642.38633,67.547837 L 641.92621,71.382998 L 644.08481,73.158191 L 640.13826,73.254182 L 639.49636,77.076543 L 639.96856,81.190077 L 644.31132,81.592599 L 645.59866,83.952698 L 644.49735,84.876771 z M 670.52196,82.108391 C 667.9146,85.727891 661.36354,86.065779 655.88822,82.86224 C 650.41291,79.658701 648.08815,74.12898 650.69551,70.50884 C 653.30216,66.88998 659.85394,66.552731 665.32854,69.75627 C 670.80386,72.95917 673.12932,78.48953 670.52196,82.108391 z " + style="fill:url(#linearGradient18725)" + id="path1503" /> + <path + d="M 675.251,81.065288 L 675.05147,77.830392 L 677.59421,77.740161 L 675.89858,74.639652 L 673.1066,74.100823 L 670.61356,71.134701 L 671.81002,69.696756 L 668.96834,67.3149 L 666.82465,68.258811 L 663.23456,66.057418 L 663.18485,64.43965 L 659.29512,62.957548 L 658.3983,65.068711 L 655.20798,64.529881 L 653.46264,62.551827 L 649.77385,63.001705 L 651.46878,65.652976 L 648.22875,66.956533 L 644.83818,65.428357 L 642.79462,67.675185 L 645.93524,69.832423 L 645.08813,73.202347 L 640.65022,73.426966 L 641.14939,77.561618 L 645.73571,77.965419 L 648.02851,81.963764 L 644.78848,84.750063 L 648.72721,88.569224 L 652.56583,86.187368 L 657.25226,88.658176 L 657.30196,93.331657 L 662.68639,94.230133 L 663.38509,89.961734 L 668.02111,89.826706 L 670.71297,93.376453 L 674.45288,91.669094 L 672.05996,87.625312 L 674.50188,84.88445 L 678.09268,86.502219 L 678.59043,83.312117 L 675.251,81.065288 z M 670.35297,82.051436 C 667.7875,85.614622 661.33584,85.94675 655.94574,82.793126 C 650.55492,79.638862 648.26567,74.194254 650.83256,70.630428 C 653.39944,67.066603 659.84968,66.734474 665.2412,69.888738 C 670.63131,73.043002 672.91985,78.488251 670.35297,82.051436 z " + style="fill:url(#linearGradient18727);fill-opacity:1" + id="path1518" /> + <path + d="M 650.29149,70.272702 C 647.54424,74.086104 649.93716,79.897398 655.62621,83.226366 C 658.87689,85.127628 662.72473,85.897475 665.91932,85.285052 C 668.112,84.865251 669.84314,83.847748 670.92599,82.344529 C 671.68718,81.287988 672.07416,80.064423 672.07416,78.742947 C 672.07416,78.186199 672.00529,77.612172 671.86753,77.026628 C 671.188,74.147538 668.84124,71.294046 665.59056,69.391505 C 659.90151,66.063817 653.03873,66.458659 650.29149,70.272702 z M 656.15095,82.498114 C 650.90711,79.430242 648.64129,74.157777 651.09883,70.744977 C 653.55708,67.332178 659.82269,67.051884 665.06653,70.119756 C 668.10703,71.899429 670.29687,74.548781 670.92528,77.20773 C 671.33712,78.954127 671.05877,80.566776 670.11651,81.872253 C 669.17639,83.17837 667.65543,84.064687 665.71766,84.436492 C 662.76805,85.001559 659.19145,84.277787 656.15095,82.498114 z " + style="fill:url(#linearGradient18729);fill-opacity:1" + id="path1533" /> + <path + d="M 653.30784,61.87477 L 649.36911,62.318888 C 649.15041,62.343846 648.97858,62.498711 648.95088,62.696453 C 648.95088,62.696453 648.53762,65.706091 648.4794,66.130371 C 648.30969,66.198204 648.21384,66.237241 648.06259,66.297395 C 647.70188,66.13549 644.82256,64.837692 644.82256,64.837692 C 644.62516,64.74874 644.38658,64.792256 644.24244,64.943282 L 642.02206,67.267544 C 641.95816,67.334097 641.9184,67.41601 641.90845,67.503682 L 641.44833,71.338841 C 641.44691,71.3542 641.44549,71.369559 641.44549,71.385558 C 641.44549,71.506506 641.5023,71.622975 641.60242,71.705527 C 641.60242,71.705527 642.26207,72.248196 642.87911,72.755669 C 641.71745,72.783826 640.12619,72.822862 640.12619,72.822862 C 639.89258,72.827982 639.69731,72.983488 639.66252,73.191468 L 639.02133,77.013189 C 639.01778,77.034307 639.01565,77.056705 639.01565,77.078463 C 639.01565,77.093181 639.01636,77.10854 639.01849,77.123259 L 639.49068,81.236792 C 639.51411,81.440934 639.69376,81.602198 639.92098,81.623316 C 639.92098,81.623316 643.53024,81.957365 643.99818,82.001521 C 644.16575,82.309332 644.79274,83.456744 645.01002,83.856066 C 644.7054,84.110763 644.17143,84.559361 644.17143,84.559361 C 644.10682,84.613116 644.05995,84.682229 644.03652,84.759662 L 642.95225,88.228136 C 642.94018,88.267172 642.9345,88.306209 642.9345,88.345245 C 642.9345,88.450196 642.9771,88.552586 643.05521,88.633218 L 646.78376,92.433182 C 646.94779,92.600846 647.22613,92.628363 647.42708,92.498456 C 647.42708,92.498456 650.35184,90.591434 650.8006,90.298342 C 651.25718,90.551118 654.35519,92.267437 654.73721,92.479897 C 654.74147,92.911216 654.78194,96.889723 654.78194,96.889723 C 654.78336,97.095144 654.94526,97.271767 655.16893,97.311443 L 659.91003,98.161923 C 660.09181,98.19456 660.27785,98.130567 660.38933,97.99682 L 663.1245,94.724806 C 663.17278,94.666572 663.20403,94.599378 663.21681,94.527065 C 663.21681,94.527065 663.27219,94.187897 663.31977,93.896724 C 663.81326,93.881365 664.80238,93.851289 665.15742,93.839769 C 665.42014,94.204536 667.55815,97.166817 667.55815,97.166817 C 667.69377,97.354959 667.96715,97.418954 668.18798,97.313364 L 671.72836,95.614322 C 671.78091,95.589365 671.8299,95.553528 671.86895,95.511932 C 671.86895,95.511932 674.87466,92.316712 674.95561,92.23096 C 675.04366,92.176565 675.11537,92.102332 675.14733,92.008901 C 675.16224,91.966666 675.16934,91.92187 675.16934,91.878354 C 675.16934,91.80796 675.15017,91.738206 675.11324,91.674852 C 675.11324,91.674852 674.6936,90.966439 674.2775,90.263145 C 674.44223,90.341218 674.72413,90.474965 674.72413,90.474965 C 674.91443,90.564557 675.14875,90.53 675.29644,90.389853 L 678.71683,87.156877 C 678.78783,87.090323 678.83186,87.006491 678.84606,86.91626 L 679.36867,83.449705 C 679.37151,83.429866 679.37293,83.410028 679.37293,83.39083 C 679.37293,83.254523 679.30192,83.125256 679.1805,83.042703 C 679.1805,83.042703 676.29053,81.099846 675.96745,80.882265 C 675.95893,80.747879 675.95467,80.675565 675.94757,80.555897 C 676.16059,80.320399 678.25315,78.005736 678.25315,78.005736 C 678.32416,77.927023 678.36108,77.829753 678.36108,77.731842 C 678.36108,77.666567 678.34475,77.600654 678.31138,77.53986 L 676.58947,74.390716 C 676.52485,74.271687 676.40201,74.185934 676.25858,74.157777 C 676.25858,74.157777 673.90542,73.70406 673.59725,73.644545 C 673.4169,73.430166 671.74185,71.436112 671.39321,71.021432 C 671.68647,70.668825 672.39085,69.822824 672.39085,69.822824 C 672.45476,69.745391 672.48671,69.65324 672.48671,69.562368 C 672.48671,69.44526 672.43488,69.32943 672.33334,69.244318 L 669.44692,66.825985 C 669.30136,66.703117 669.08692,66.67496 668.90798,66.753672 C 668.90798,66.753672 667.37495,67.428169 666.98157,67.601593 C 666.60098,67.368014 664.10865,65.839838 663.77066,65.632497 C 663.76001,65.299729 663.72592,64.210551 663.72592,64.210551 C 663.72095,64.040966 663.60663,63.889301 663.43338,63.823387 L 659.48328,62.316969 C 659.36257,62.270893 659.22695,62.270893 659.10695,62.316969 C 658.98695,62.363045 658.89251,62.451356 658.84564,62.561426 C 658.84564,62.561426 658.28256,63.888661 658.07806,64.367976 C 657.50646,64.271345 655.68301,63.963534 655.40822,63.916818 C 655.21224,63.69412 653.74738,62.035395 653.74738,62.035395 C 653.63945,61.916366 653.474,61.855572 653.30784,61.87477 z M 653.15873,62.766207 C 653.43424,63.079137 654.76277,64.582996 654.76277,64.582996 C 654.83449,64.664908 654.93744,64.719943 655.05106,64.739781 L 658.29179,65.286929 C 658.52044,65.325967 658.74695,65.211417 658.83002,65.016236 C 658.83002,65.016236 659.33275,63.832986 659.56281,63.290957 C 660.22175,63.542454 662.33988,64.349417 662.76876,64.512603 C 662.78083,64.897846 662.81136,65.876954 662.81136,65.876954 C 662.81562,66.016461 662.89302,66.145089 663.02154,66.223802 L 666.66773,68.459752 C 666.80974,68.547424 666.99436,68.559583 667.14986,68.490469 C 667.14986,68.490469 668.58703,67.858208 669.03011,67.663667 C 669.38799,67.963158 670.90256,69.232159 671.357,69.613563 C 671.03534,70.000088 670.40125,70.760976 670.40125,70.760976 C 670.33663,70.838409 670.30468,70.92992 670.30468,71.022071 C 670.30468,71.114223 670.33734,71.206374 670.40267,71.284446 L 672.93476,74.296644 C 673.00435,74.379196 673.10376,74.43679 673.21737,74.459189 C 673.21737,74.459189 675.43916,74.887948 675.82473,74.962181 C 675.99088,75.268073 677.09078,77.276204 677.30664,77.672327 C 676.97646,78.037733 675.07845,80.138016 675.07845,80.138016 C 675.00886,80.215448 674.97052,80.312079 674.97052,80.41191 C 674.97052,80.420229 674.97052,80.427908 674.97123,80.436228 L 675.01525,81.132482 C 675.02307,81.26047 675.09336,81.379499 675.20768,81.456292 C 675.20768,81.456292 677.97694,83.319157 678.37244,83.585372 C 678.30996,84.001333 677.94498,86.422226 677.90948,86.659004 C 677.72202,86.835627 675.26307,89.159889 674.84697,89.552812 C 674.41028,89.346112 673.33666,88.838639 673.33666,88.838639 C 673.1492,88.749047 672.92056,88.781044 672.77074,88.916712 C 672.67914,88.999903 672.63157,89.110614 672.63157,89.222603 C 672.63157,89.291717 672.65003,89.36211 672.68766,89.426744 C 672.68766,89.426744 673.82661,91.351043 674.09288,91.800921 C 673.8053,92.106812 671.29877,94.772162 671.19013,94.886711 C 671.05238,94.953265 668.72194,96.07188 668.12123,96.360493 C 667.72643,95.813344 665.81281,93.161433 665.81281,93.161433 C 665.72121,93.034085 665.56144,92.959852 665.39245,92.963692 L 662.88237,93.040484 C 662.65018,93.048163 662.45633,93.203669 662.42224,93.41101 C 662.42224,93.41101 662.30721,94.117503 662.27952,94.290287 C 662.14816,94.445792 660.10743,96.888443 659.79926,97.257048 C 659.32352,97.171297 656.34266,96.636307 655.73911,96.528157 C 655.73272,95.925334 655.69508,92.23096 655.69508,92.23096 C 655.69366,92.081854 655.60704,91.943628 655.46573,91.865554 L 651.02782,89.406906 C 650.86238,89.314114 650.65078,89.321154 650.49315,89.424184 C 650.49315,89.424184 647.78922,91.187858 647.21264,91.563503 C 646.74542,91.087387 644.23747,88.531468 643.94634,88.235176 C 644.06634,87.851851 644.85878,85.315769 644.92268,85.112269 C 645.086,84.974682 645.92458,84.272028 645.92458,84.272028 C 646.0247,84.187555 646.07796,84.071726 646.07796,83.953977 C 646.07796,83.888703 646.06234,83.82407 646.02896,83.762635 L 644.74162,81.402537 C 644.66848,81.26879 644.52362,81.177918 644.35889,81.1632 C 644.35889,81.1632 641.0514,80.856668 640.40596,80.796514 C 640.33921,80.218008 639.99128,77.186613 639.97992,77.089342 C 639.99625,76.992711 640.45424,74.260808 640.55223,73.680382 C 641.20194,73.664383 644.09617,73.59399 644.09617,73.59399 C 644.29214,73.589511 644.46611,73.477521 644.53428,73.311776 C 644.55416,73.262501 644.56481,73.211306 644.56481,73.160111 C 644.56481,73.040442 644.50942,72.923333 644.40788,72.840141 C 644.40788,72.840141 642.73284,71.46235 642.42964,71.212773 C 642.47438,70.838409 642.81947,67.966358 642.84716,67.73086 C 643.00551,67.565756 644.35676,66.150849 644.72528,65.765605 C 645.27132,66.011342 647.83324,67.165794 647.83324,67.165794 C 647.96247,67.224028 648.11372,67.226588 648.24437,67.174113 L 649.11704,66.823426 C 649.27112,66.761352 649.37834,66.631444 649.39893,66.479778 C 649.39893,66.479778 649.78237,63.692199 649.85835,63.138012 C 650.42427,63.074658 652.73979,62.812922 653.15873,62.766207 z " + style="fill:url(#linearGradient18731);fill-opacity:1" + id="path1548" /> + </g> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Setting-dialog/amarok_settings_engine.png" + transform="matrix(0.899443,0,0,0.869374,663.1337,632.7971)" + id="g16546" + style="display:inline"> + <path + transform="matrix(0.116367,-0.126279,0.138815,0.105858,2044.201,-572.7461)" + d="M -7824.3296 -3285.1472 A 25.97311 5.306335 0 1 1 -7876.2758,-3285.1472 A 25.97311 5.306335 0 1 1 -7824.3296 -3285.1472 z" + sodipodi:ry="5.306335" + sodipodi:rx="25.97311" + sodipodi:cy="-3285.1472" + sodipodi:cx="-7850.3027" + id="path29780" + style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:21.03624916;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0.68131869" + sodipodi:type="arc" /> + <path + style="fill:url(#linearGradient18733);fill-opacity:1" + d="M 684.58932,62.015066 C 683.95163,62.03676 682.73778,63.425764 682.28042,63.646529 C 681.81946,63.869109 678.16982,66.391273 677.82409,66.836327 C 677.47838,67.281337 677.01748,67.46692 676.51816,67.429844 C 676.01883,67.392834 674.44026,67.377225 673.90249,67.673903 C 673.36463,67.970636 670.90988,70.397011 670.29527,70.693715 C 669.68068,70.990341 668.87865,71.141876 668.49449,71.178979 C 668.11043,71.216028 666.83809,72.584967 666.49238,72.844636 C 666.14667,73.10429 663.41458,74.2451 663.03046,74.467545 C 662.64631,74.690071 665.83291,77.807344 666.29388,77.881456 C 666.75485,77.955728 665.97647,79.643959 665.08957,79.643959 C 664.99189,79.643959 665.88202,82.441727 670.53651,84.906802 C 674.56037,87.037898 678.82957,86.094974 680.41507,85.578197 C 681.53629,85.212648 680.74985,85.010148 680.78834,84.416789 C 680.82666,83.823378 680.8587,83.043556 680.93555,82.376043 C 681.01244,81.708505 681.28161,76.107653 682.28042,74.624229 C 683.27912,73.140873 686.13308,71.360612 687.13185,71.175177 C 688.13068,70.989794 687.93916,70.619136 687.93916,70.285379 C 687.93916,69.95165 687.78546,69.209626 687.51651,69.098343 C 687.24765,68.987127 686.19822,69.654757 685.85251,69.728899 C 685.5068,69.803078 683.85489,69.914575 683.62435,69.543728 C 683.3939,69.172775 686.14108,63.906545 685.64166,63.276177 C 685.14247,62.645555 684.79657,62.015066 684.60452,62.015066 C 684.59957,62.015066 684.59434,62.014895 684.58932,62.015066 z M 676.42414,68.978694 C 676.73261,68.974532 676.94176,69.006232 676.94176,69.006232 C 677.11748,69.40176 676.29813,70.644697 675.53706,70.814323 C 674.77614,70.983791 673.01974,73.4706 672.96126,72.905408 C 672.90256,72.340212 673.66355,70.476005 674.54169,69.628234 C 675.09392,69.095034 675.90135,68.985752 676.42414,68.978694 z " + id="path29782" + sodipodi:nodetypes="ccccsccsccsccccccsssccsccscccc" /> + <path + style="fill:url(#linearGradient18735);fill-opacity:1" + d="M 666.16392,81.423003 C 665.9031,81.091515 665.42361,80.435598 665.26754,80.004036 C 665.14138,79.655175 665.13889,79.611803 665.24522,79.614525 C 665.37457,79.61782 665.53249,79.582317 665.71495,79.414066 C 666.12829,79.032933 666.46122,78.479561 666.36093,78.193327 C 666.34151,78.137933 666.28178,78.082005 666.19489,78.037937 C 665.68311,77.778228 663.68597,75.546616 663.20752,74.699847 C 663.14869,74.595715 663.09317,74.467102 663.08417,74.414107 L 663.06779,74.317713 L 663.5356,74.095961 C 663.79289,73.973979 664.43521,73.676485 664.96298,73.434854 C 666.54934,72.708572 666.40066,72.802712 667.30233,71.953513 C 668.1459,71.159029 668.45291,70.912676 668.63209,70.886525 C 669.29678,70.789464 669.74333,70.680556 670.16886,70.511811 C 670.62212,70.332045 670.78417,70.203751 672.21307,68.893356 C 673.39223,67.812027 673.7656,67.48122 673.99398,67.315529 C 674.36671,67.045079 674.85595,66.975476 676.21407,66.999677 C 677.02172,67.014072 677.16671,67.009258 677.3026,66.963628 C 677.54873,66.881043 677.75482,66.732936 678.04247,66.432002 C 678.18878,66.278881 678.48874,66.009285 678.70903,65.832881 C 679.56399,65.148239 681.38883,63.831519 682.2969,63.244071 C 682.83405,62.896574 682.83553,62.895428 683.52111,62.285059 C 684.09054,61.778114 684.47641,61.493496 684.70119,61.414567 C 684.91293,61.34024 684.93449,61.341828 685.04373,61.439835 C 685.15035,61.535483 686.03394,62.9158 686.10698,63.100796 C 686.24105,63.440395 686.31503,63.800609 686.17623,64.085306 C 685.91549,64.62008 685.53887,65.389083 685.37514,65.683372 C 684.01271,68.277198 683.57638,69.315608 683.75865,69.53045 C 683.9225,69.723542 684.62451,69.838696 685.37986,69.796375 C 685.63126,69.7823 685.89265,69.757165 685.96072,69.740527 C 686.02879,69.723904 686.33237,69.592247 686.63534,69.447957 C 687.2815,69.140217 687.50858,69.064356 687.61979,69.119151 C 687.73185,69.174334 687.84105,69.402649 687.91551,69.737405 C 687.98654,70.056699 688.02311,70.630231 687.98411,70.812999 C 687.93246,71.054964 687.70898,71.202203 687.22201,71.315153 C 686.50051,71.482471 685.39489,72.093335 684.25696,72.95334 C 683.19009,73.759659 682.46764,74.510542 682.13211,75.161789 C 681.942,75.530794 681.88754,75.797654 681.7263,76.390906 C 681.60627,76.832449 680.94162,82.834448 680.93784,82.974322 C 680.93675,83.015121 681.05021,76.469849 681.04313,76.469662 C 681.03606,76.469473 680.47326,77.712213 680.46324,77.71196 C 680.45319,77.711706 679.26724,79.616271 679.14064,79.557035 C 679.01407,79.497811 680.84098,75.164632 681.29637,74.622279 C 681.72505,74.111718 682.69675,73.228218 683.92824,72.229312 C 684.68954,71.611793 685.08523,71.319382 685.27321,71.235423 C 685.34613,71.202844 685.65566,71.131984 685.96107,71.077938 C 686.71523,70.944468 686.90799,70.86055 686.9133,70.663386 C 686.91912,70.447624 686.66022,70.304493 686.31274,70.331392 C 685.85732,70.366626 685.34615,70.54459 684.72472,70.884297 C 684.05931,71.248032 683.51826,71.34764 682.8754,71.224763 C 682.69316,71.189901 682.45974,71.128317 682.35669,71.087851 C 682.13555,71.001063 682.1061,70.935662 682.16257,70.656928 C 682.28424,70.056285 682.7318,69.095497 684.09589,66.506489 C 684.52724,65.687816 684.91444,64.919869 684.95636,64.799939 C 684.99825,64.680011 685.1686,64.493296 685.20972,64.242845 C 685.27332,63.855494 684.94437,63.305418 684.69042,63.096047 C 684.1717,62.710286 684.01007,62.609168 683.74917,62.719264 C 683.27598,62.918962 682.63747,63.885861 680.89506,66.251944 C 678.10234,70.044237 677.77609,70.437338 677.00798,70.935529 C 675.9344,71.631833 674.25248,73.011563 672.89065,74.31307 C 671.94197,75.219744 671.23101,76.306587 670.94267,76.585575 C 670.75542,76.76678 669.96025,77.745288 669.68107,77.980021 C 668.26676,78.87945 667.27756,79.841733 666.4157,81.272282 L 666.26937,81.5468 L 666.16392,81.423003 z M 668.6295,76.32153 C 669.21552,76.035684 669.9402,75.554275 670.29047,75.218104 C 670.57943,74.940811 670.69555,74.663571 670.53426,74.6361 C 670.43004,74.618356 670.14725,74.769144 669.42758,75.226216 C 668.60241,75.75033 668.36243,75.871421 668.1267,75.882628 C 667.6555,75.905015 666.59584,75.72705 666.06717,75.536718 C 665.70072,75.404769 665.57392,75.331295 665.45729,75.183336 C 665.38321,75.089358 665.37887,75.065462 665.39778,74.856572 C 665.42208,74.588243 665.56219,74.28492 665.80217,73.981211 C 665.92518,73.825514 666.03467,73.73747 666.35462,73.537052 C 667.27497,72.96057 668.28907,72.478015 669.33316,72.119738 C 669.57465,72.036872 669.8005,71.958064 669.83504,71.944615 C 669.88896,71.923614 669.87183,71.906057 669.71409,71.820563 C 669.45042,71.677633 669.10707,71.548405 668.91845,71.521096 C 668.73014,71.493813 668.65946,71.521481 668.21594,71.796028 C 667.88157,72.003024 666.25388,73.091052 665.48621,73.620705 C 665.18146,73.830983 664.72391,74.14191 664.46942,74.311617 C 664.21494,74.481377 663.98901,74.640691 663.96737,74.665692 C 663.90854,74.733682 663.91479,74.906936 663.98115,75.047732 C 664.05408,75.202428 664.40387,75.635913 664.59279,75.805673 L 664.73365,75.93226 L 666.46235,76.190566 C 667.41313,76.332656 668.22595,76.451329 668.26859,76.454332 C 668.31124,76.457306 668.47365,76.397562 668.6295,76.32153 z M 668.25415,75.137027 C 668.72466,75.109689 668.82443,75.064367 669.20342,74.70609 C 669.59232,74.338446 669.98504,73.821189 670.13614,73.477602 L 670.18873,73.358048 L 670.0701,73.373682 C 670.00486,73.382261 669.32753,73.452719 668.5649,73.53022 L 667.17835,73.671095 L 667.05139,73.770438 C 666.8115,73.958127 666.48365,74.29119 666.35948,74.473383 C 666.21238,74.689172 666.19775,74.804299 666.3076,74.881267 C 666.43204,74.968454 666.84529,75.068852 667.23887,75.107529 C 667.76006,75.158734 667.84061,75.161069 668.25415,75.137027 z M 673.23289,72.884003 C 673.36857,72.793999 673.63342,72.530209 674.28656,71.834505 C 674.76864,71.320997 675.22409,70.892729 675.40078,70.786743 C 675.46833,70.746223 675.60467,70.684907 675.70377,70.650443 C 675.92776,70.572528 676.07455,70.48466 676.28578,70.302053 C 676.73011,69.917918 677.18695,69.102408 677.12911,68.796643 C 677.11207,68.706574 677.1094,68.705506 676.85795,68.688776 C 675.95769,68.628965 675.25732,68.824768 674.75202,69.277624 C 674.16986,69.799364 673.40627,71.172265 673.12421,72.204378 C 673.01269,72.612445 672.98317,72.817187 673.02395,72.899506 C 673.06635,72.985066 673.08235,72.983877 673.23289,72.884003 z M 671.57619,71.796562 C 672.34776,71.643906 672.98398,71.514265 672.99004,71.508448 C 672.99608,71.502658 673.07634,71.260586 673.16839,70.970524 C 673.54217,69.792774 673.83364,69.271727 674.26438,69.011271 C 674.90419,68.624388 676.2258,68.209742 676.95832,68.166034 C 677.22424,68.150158 677.26926,68.137257 677.51161,68.007456 C 677.8718,67.814601 678.3997,67.456604 679.12431,66.913865 C 679.45905,66.663135 679.91088,66.333048 680.12838,66.180365 C 680.68623,65.788718 680.96159,65.51887 680.96589,65.359637 C 680.96661,65.332846 680.95475,65.310646 680.93954,65.310246 C 680.92433,65.309872 680.56473,65.552198 680.14043,65.848756 C 679.71612,66.145328 679.09133,66.572036 678.75201,66.797005 C 677.56622,67.583204 677.74205,67.5241 676.57466,67.528756 C 676.06082,67.530783 675.36265,67.538481 675.02318,67.545859 C 674.42556,67.558856 674.40209,67.56187 674.28268,67.640998 C 674.21487,67.685947 673.78028,68.103582 673.31692,68.569102 C 672.3073,69.583402 671.44162,70.400928 671.10522,70.657742 C 670.9698,70.761139 670.75997,70.888754 670.63892,70.941346 C 670.39611,71.046839 670.3172,71.116109 670.21049,71.317382 C 670.13004,71.469129 670.04208,71.806968 670.03645,71.985906 C 670.03309,72.092787 670.03952,72.102793 670.10304,72.089172 C 670.14171,72.080887 670.80463,71.949217 671.57619,71.796562 z " + id="path29786" + sodipodi:nodetypes="csssssscccscsscsssssscsssscsssssssssssssssscssssccssssscscscscccccsscsssssssssssscsscccsccsccccccsssccssssscsssccssssssssssscsscsssssc" /> + <g + id="g7923" + style="fill:#002ddb;fill-opacity:1"> + <path + id="path29788" + d="M 680.83905,76.838803 C 680.83905,76.838803 679.45446,81.3797 679.41945,81.449838 C 679.22156,81.846006 678.85659,82.994032 678.98224,83.364797 C 679.13021,83.801538 679.68481,84.436589 679.94365,84.754121 C 680.2025,85.071759 680.31342,85.230473 680.23945,85.349692 C 680.16554,85.468783 679.20413,85.468783 679.20413,85.468783 C 679.20413,85.468783 678.46465,84.396936 678.31664,83.999943 C 678.16882,83.603079 678.09481,82.769436 678.24277,82.491624 C 678.39065,82.213703 680.83905,76.838803 680.83905,76.838803 z " + i:knockout="Off" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:21.03624916;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <polygon + id="polygon29790" + points="272.565,567.14 214.39,617.501 287.326,574.086 272.565,567.14 " + a:adobe-blending-mode="lighten" + i:knockout="Off" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:17.46648407;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + transform="matrix(4.348075e-2,0,0,4.188821e-2,659.2285,53.92804)" /> + <polygon + id="polygon29792" + points="329.873,549.776 230.887,678.282 344.633,558.457 329.873,549.776 " + a:adobe-blending-mode="lighten" + i:knockout="Off" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:17.46648407;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + transform="matrix(4.348075e-2,0,0,4.188821e-2,659.2285,53.92804)" /> + <polygon + id="polygon29794" + points="337.688,594.058 262.146,713.882 355.053,601.872 337.688,594.058 " + a:adobe-blending-mode="lighten" + i:knockout="Off" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:17.46648407;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + transform="matrix(4.348075e-2,0,0,4.188821e-2,659.2285,53.92804)" /> + <polygon + id="polygon29796" + points="381.97,588.847 318.584,737.327 402.809,593.19 381.97,588.847 " + a:adobe-blending-mode="lighten" + i:knockout="Off" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:17.46648407;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + transform="matrix(4.348075e-2,0,0,4.188821e-2,659.2285,53.92804)" /> + <polygon + id="polygon29798" + points="407.151,627.921 401.94,763.375 424.515,629.658 407.151,627.921 " + a:adobe-blending-mode="lighten" + i:knockout="Off" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:17.46648407;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + transform="matrix(4.348075e-2,0,0,4.188821e-2,659.2285,53.92804)" /> + </g> + <path + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#002ddb;stroke-width:0.57512665;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 665.61116,79.917164 C 666.44697,82.39457 673.07693,87.947061 680.79385,85.431014 C 680.71584,83.40215 681.5919,77.986014 681.41239,76.464632" + id="path7919" + sodipodi:nodetypes="ccc" /> + </g> + </g> + <g + id="g17042" + transform="translate(-115.11865,-346.43382)" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/32x32/actions/amarok_settings_view.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Setting-dialog/amarok_settings_view.png" + transform="matrix(1.628674,0,0,1.628674,589.2965,25.3152)" + d="M 428.77151 444.83228 A 9.8239412 9.8239412 0 1 1 409.12363,444.83228 A 9.8239412 9.8239412 0 1 1 428.77151 444.83228 z" + sodipodi:ry="9.8239412" + sodipodi:rx="9.8239412" + sodipodi:cy="444.83228" + sodipodi:cx="418.94757" + id="path6375" + style="fill:url(#linearGradient18930);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;display:inline" + sodipodi:type="arc" /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Setting-dialog/amarok_settings_view.png" + transform="matrix(1.351475,0,0,1.37725,706.0166,134.9307)" + d="M 429.51763 446.44885 A 11.005301 11.005301 0 1 1 407.50703,446.44885 A 11.005301 11.005301 0 1 1 429.51763 446.44885 z" + sodipodi:ry="11.005301" + sodipodi:rx="11.005301" + sodipodi:cy="446.44885" + sodipodi:cx="418.51233" + id="path6377" + style="fill:url(#radialGradient18932);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;display:inline" + sodipodi:type="arc" /> + <path + inkscape:export-filename="/home/lando/Projects/Amarok/Setting-dialog/amarok_settings_view.png" + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + sodipodi:nodetypes="csssc" + id="path6379" + d="M 1285.507,750.519 C 1285.507,758.95943 1279.2149,751.49937 1271.4623,751.49937 C 1263.7097,751.49937 1257.4177,758.95943 1257.4177,750.519 C 1257.4177,742.07865 1263.7097,735.22846 1271.4623,735.22846 C 1279.2149,735.22846 1285.507,742.07865 1285.507,750.519 z " + style="opacity:0.85531915;fill:url(#linearGradient18934);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;display:inline" /> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Setting-dialog/amarok_settings_view.png" + transform="matrix(1.033489,0,0,1.033488,572.0631,676.0966)" + id="g7967" + style="display:inline"> + <path + sodipodi:type="arc" + style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:21.03624916;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0.68131869" + id="path7969" + sodipodi:cx="-7850.3027" + sodipodi:cy="-3285.1472" + sodipodi:rx="25.97311" + sodipodi:ry="5.306335" + d="M -7824.3296 -3285.1472 A 25.97311 5.306335 0 1 1 -7876.2758,-3285.1472 A 25.97311 5.306335 0 1 1 -7824.3296 -3285.1472 z" + transform="matrix(0.116367,-0.126279,0.138815,0.105858,2044.201,-572.7461)" /> + <path + sodipodi:nodetypes="ccccsccsccsccccccsssccsccscccc" + id="path7971" + d="M 684.58932,62.015066 C 683.95163,62.03676 682.73778,63.425764 682.28042,63.646529 C 681.81946,63.869109 678.16982,66.391273 677.82409,66.836327 C 677.47838,67.281337 677.01748,67.46692 676.51816,67.429844 C 676.01883,67.392834 674.44026,67.377225 673.90249,67.673903 C 673.36463,67.970636 670.90988,70.397011 670.29527,70.693715 C 669.68068,70.990341 668.87865,71.141876 668.49449,71.178979 C 668.11043,71.216028 666.83809,72.584967 666.49238,72.844636 C 666.14667,73.10429 663.41458,74.2451 663.03046,74.467545 C 662.64631,74.690071 665.83291,77.807344 666.29388,77.881456 C 666.75485,77.955728 665.97647,79.643959 665.08957,79.643959 C 664.99189,79.643959 665.88202,82.441727 670.53651,84.906802 C 674.56037,87.037898 678.82957,86.094974 680.41507,85.578197 C 681.53629,85.212648 680.74985,85.010148 680.78834,84.416789 C 680.82666,83.823378 680.8587,83.043556 680.93555,82.376043 C 681.01244,81.708505 681.28161,76.107653 682.28042,74.624229 C 683.27912,73.140873 686.13308,71.360612 687.13185,71.175177 C 688.13068,70.989794 687.93916,70.619136 687.93916,70.285379 C 687.93916,69.95165 687.78546,69.209626 687.51651,69.098343 C 687.24765,68.987127 686.19822,69.654757 685.85251,69.728899 C 685.5068,69.803078 683.85489,69.914575 683.62435,69.543728 C 683.3939,69.172775 686.14108,63.906545 685.64166,63.276177 C 685.14247,62.645555 684.79657,62.015066 684.60452,62.015066 C 684.59957,62.015066 684.59434,62.014895 684.58932,62.015066 z M 676.42414,68.978694 C 676.73261,68.974532 676.94176,69.006232 676.94176,69.006232 C 677.11748,69.40176 676.29813,70.644697 675.53706,70.814323 C 674.77614,70.983791 673.01974,73.4706 672.96126,72.905408 C 672.90256,72.340212 673.66355,70.476005 674.54169,69.628234 C 675.09392,69.095034 675.90135,68.985752 676.42414,68.978694 z " + style="fill:url(#linearGradient18936);fill-opacity:1" /> + <path + sodipodi:nodetypes="csssssscccscsscsssssscsssscsssssssssssssssscssssccssssscscscscccccsscsssssssssssscsscccsccsccccccsssccssssscsssccssssssssssscsscsssssc" + id="path7973" + d="M 666.16392,81.423003 C 665.9031,81.091515 665.42361,80.435598 665.26754,80.004036 C 665.14138,79.655175 665.13889,79.611803 665.24522,79.614525 C 665.37457,79.61782 665.53249,79.582317 665.71495,79.414066 C 666.12829,79.032933 666.46122,78.479561 666.36093,78.193327 C 666.34151,78.137933 666.28178,78.082005 666.19489,78.037937 C 665.68311,77.778228 663.68597,75.546616 663.20752,74.699847 C 663.14869,74.595715 663.09317,74.467102 663.08417,74.414107 L 663.06779,74.317713 L 663.5356,74.095961 C 663.79289,73.973979 664.43521,73.676485 664.96298,73.434854 C 666.54934,72.708572 666.40066,72.802712 667.30233,71.953513 C 668.1459,71.159029 668.45291,70.912676 668.63209,70.886525 C 669.29678,70.789464 669.74333,70.680556 670.16886,70.511811 C 670.62212,70.332045 670.78417,70.203751 672.21307,68.893356 C 673.39223,67.812027 673.7656,67.48122 673.99398,67.315529 C 674.36671,67.045079 674.85595,66.975476 676.21407,66.999677 C 677.02172,67.014072 677.16671,67.009258 677.3026,66.963628 C 677.54873,66.881043 677.75482,66.732936 678.04247,66.432002 C 678.18878,66.278881 678.48874,66.009285 678.70903,65.832881 C 679.56399,65.148239 681.38883,63.831519 682.2969,63.244071 C 682.83405,62.896574 682.83553,62.895428 683.52111,62.285059 C 684.09054,61.778114 684.47641,61.493496 684.70119,61.414567 C 684.91293,61.34024 684.93449,61.341828 685.04373,61.439835 C 685.15035,61.535483 686.03394,62.9158 686.10698,63.100796 C 686.24105,63.440395 686.31503,63.800609 686.17623,64.085306 C 685.91549,64.62008 685.53887,65.389083 685.37514,65.683372 C 684.01271,68.277198 683.57638,69.315608 683.75865,69.53045 C 683.9225,69.723542 684.62451,69.838696 685.37986,69.796375 C 685.63126,69.7823 685.89265,69.757165 685.96072,69.740527 C 686.02879,69.723904 686.33237,69.592247 686.63534,69.447957 C 687.2815,69.140217 687.50858,69.064356 687.61979,69.119151 C 687.73185,69.174334 687.84105,69.402649 687.91551,69.737405 C 687.98654,70.056699 688.02311,70.630231 687.98411,70.812999 C 687.93246,71.054964 687.70898,71.202203 687.22201,71.315153 C 686.50051,71.482471 685.39489,72.093335 684.25696,72.95334 C 683.19009,73.759659 682.46764,74.510542 682.13211,75.161789 C 681.942,75.530794 681.88754,75.797654 681.7263,76.390906 C 681.60627,76.832449 680.94162,82.834448 680.93784,82.974322 C 680.93675,83.015121 681.05021,76.469849 681.04313,76.469662 C 681.03606,76.469473 680.47326,77.712213 680.46324,77.71196 C 680.45319,77.711706 679.26724,79.616271 679.14064,79.557035 C 679.01407,79.497811 680.84098,75.164632 681.29637,74.622279 C 681.72505,74.111718 682.69675,73.228218 683.92824,72.229312 C 684.68954,71.611793 685.08523,71.319382 685.27321,71.235423 C 685.34613,71.202844 685.65566,71.131984 685.96107,71.077938 C 686.71523,70.944468 686.90799,70.86055 686.9133,70.663386 C 686.91912,70.447624 686.66022,70.304493 686.31274,70.331392 C 685.85732,70.366626 685.34615,70.54459 684.72472,70.884297 C 684.05931,71.248032 683.51826,71.34764 682.8754,71.224763 C 682.69316,71.189901 682.45974,71.128317 682.35669,71.087851 C 682.13555,71.001063 682.1061,70.935662 682.16257,70.656928 C 682.28424,70.056285 682.7318,69.095497 684.09589,66.506489 C 684.52724,65.687816 684.91444,64.919869 684.95636,64.799939 C 684.99825,64.680011 685.1686,64.493296 685.20972,64.242845 C 685.27332,63.855494 684.94437,63.305418 684.69042,63.096047 C 684.1717,62.710286 684.01007,62.609168 683.74917,62.719264 C 683.27598,62.918962 682.63747,63.885861 680.89506,66.251944 C 678.10234,70.044237 677.77609,70.437338 677.00798,70.935529 C 675.9344,71.631833 674.25248,73.011563 672.89065,74.31307 C 671.94197,75.219744 671.23101,76.306587 670.94267,76.585575 C 670.75542,76.76678 669.96025,77.745288 669.68107,77.980021 C 668.26676,78.87945 667.27756,79.841733 666.4157,81.272282 L 666.26937,81.5468 L 666.16392,81.423003 z M 668.6295,76.32153 C 669.21552,76.035684 669.9402,75.554275 670.29047,75.218104 C 670.57943,74.940811 670.69555,74.663571 670.53426,74.6361 C 670.43004,74.618356 670.14725,74.769144 669.42758,75.226216 C 668.60241,75.75033 668.36243,75.871421 668.1267,75.882628 C 667.6555,75.905015 666.59584,75.72705 666.06717,75.536718 C 665.70072,75.404769 665.57392,75.331295 665.45729,75.183336 C 665.38321,75.089358 665.37887,75.065462 665.39778,74.856572 C 665.42208,74.588243 665.56219,74.28492 665.80217,73.981211 C 665.92518,73.825514 666.03467,73.73747 666.35462,73.537052 C 667.27497,72.96057 668.28907,72.478015 669.33316,72.119738 C 669.57465,72.036872 669.8005,71.958064 669.83504,71.944615 C 669.88896,71.923614 669.87183,71.906057 669.71409,71.820563 C 669.45042,71.677633 669.10707,71.548405 668.91845,71.521096 C 668.73014,71.493813 668.65946,71.521481 668.21594,71.796028 C 667.88157,72.003024 666.25388,73.091052 665.48621,73.620705 C 665.18146,73.830983 664.72391,74.14191 664.46942,74.311617 C 664.21494,74.481377 663.98901,74.640691 663.96737,74.665692 C 663.90854,74.733682 663.91479,74.906936 663.98115,75.047732 C 664.05408,75.202428 664.40387,75.635913 664.59279,75.805673 L 664.73365,75.93226 L 666.46235,76.190566 C 667.41313,76.332656 668.22595,76.451329 668.26859,76.454332 C 668.31124,76.457306 668.47365,76.397562 668.6295,76.32153 z M 668.25415,75.137027 C 668.72466,75.109689 668.82443,75.064367 669.20342,74.70609 C 669.59232,74.338446 669.98504,73.821189 670.13614,73.477602 L 670.18873,73.358048 L 670.0701,73.373682 C 670.00486,73.382261 669.32753,73.452719 668.5649,73.53022 L 667.17835,73.671095 L 667.05139,73.770438 C 666.8115,73.958127 666.48365,74.29119 666.35948,74.473383 C 666.21238,74.689172 666.19775,74.804299 666.3076,74.881267 C 666.43204,74.968454 666.84529,75.068852 667.23887,75.107529 C 667.76006,75.158734 667.84061,75.161069 668.25415,75.137027 z M 673.23289,72.884003 C 673.36857,72.793999 673.63342,72.530209 674.28656,71.834505 C 674.76864,71.320997 675.22409,70.892729 675.40078,70.786743 C 675.46833,70.746223 675.60467,70.684907 675.70377,70.650443 C 675.92776,70.572528 676.07455,70.48466 676.28578,70.302053 C 676.73011,69.917918 677.18695,69.102408 677.12911,68.796643 C 677.11207,68.706574 677.1094,68.705506 676.85795,68.688776 C 675.95769,68.628965 675.25732,68.824768 674.75202,69.277624 C 674.16986,69.799364 673.40627,71.172265 673.12421,72.204378 C 673.01269,72.612445 672.98317,72.817187 673.02395,72.899506 C 673.06635,72.985066 673.08235,72.983877 673.23289,72.884003 z M 671.57619,71.796562 C 672.34776,71.643906 672.98398,71.514265 672.99004,71.508448 C 672.99608,71.502658 673.07634,71.260586 673.16839,70.970524 C 673.54217,69.792774 673.83364,69.271727 674.26438,69.011271 C 674.90419,68.624388 676.2258,68.209742 676.95832,68.166034 C 677.22424,68.150158 677.26926,68.137257 677.51161,68.007456 C 677.8718,67.814601 678.3997,67.456604 679.12431,66.913865 C 679.45905,66.663135 679.91088,66.333048 680.12838,66.180365 C 680.68623,65.788718 680.96159,65.51887 680.96589,65.359637 C 680.96661,65.332846 680.95475,65.310646 680.93954,65.310246 C 680.92433,65.309872 680.56473,65.552198 680.14043,65.848756 C 679.71612,66.145328 679.09133,66.572036 678.75201,66.797005 C 677.56622,67.583204 677.74205,67.5241 676.57466,67.528756 C 676.06082,67.530783 675.36265,67.538481 675.02318,67.545859 C 674.42556,67.558856 674.40209,67.56187 674.28268,67.640998 C 674.21487,67.685947 673.78028,68.103582 673.31692,68.569102 C 672.3073,69.583402 671.44162,70.400928 671.10522,70.657742 C 670.9698,70.761139 670.75997,70.888754 670.63892,70.941346 C 670.39611,71.046839 670.3172,71.116109 670.21049,71.317382 C 670.13004,71.469129 670.04208,71.806968 670.03645,71.985906 C 670.03309,72.092787 670.03952,72.102793 670.10304,72.089172 C 670.14171,72.080887 670.80463,71.949217 671.57619,71.796562 z " + style="fill:url(#linearGradient18938);fill-opacity:1" /> + <g + style="fill:#002ddb;fill-opacity:1" + id="g7975"> + <path + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:21.03624916;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + i:knockout="Off" + d="M 680.83905,76.838803 C 680.83905,76.838803 679.45446,81.3797 679.41945,81.449838 C 679.22156,81.846006 678.85659,82.994032 678.98224,83.364797 C 679.13021,83.801538 679.68481,84.436589 679.94365,84.754121 C 680.2025,85.071759 680.31342,85.230473 680.23945,85.349692 C 680.16554,85.468783 679.20413,85.468783 679.20413,85.468783 C 679.20413,85.468783 678.46465,84.396936 678.31664,83.999943 C 678.16882,83.603079 678.09481,82.769436 678.24277,82.491624 C 678.39065,82.213703 680.83905,76.838803 680.83905,76.838803 z " + id="path7977" /> + <polygon + transform="matrix(4.348075e-2,0,0,4.188821e-2,659.2285,53.92804)" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:17.46648407;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + i:knockout="Off" + a:adobe-blending-mode="lighten" + points="272.565,567.14 214.39,617.501 287.326,574.086 272.565,567.14 " + id="polygon7979" /> + <polygon + transform="matrix(4.348075e-2,0,0,4.188821e-2,659.2285,53.92804)" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:17.46648407;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + i:knockout="Off" + a:adobe-blending-mode="lighten" + points="329.873,549.776 230.887,678.282 344.633,558.457 329.873,549.776 " + id="polygon7981" /> + <polygon + transform="matrix(4.348075e-2,0,0,4.188821e-2,659.2285,53.92804)" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:17.46648407;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + i:knockout="Off" + a:adobe-blending-mode="lighten" + points="337.688,594.058 262.146,713.882 355.053,601.872 337.688,594.058 " + id="polygon7983" /> + <polygon + transform="matrix(4.348075e-2,0,0,4.188821e-2,659.2285,53.92804)" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:17.46648407;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + i:knockout="Off" + a:adobe-blending-mode="lighten" + points="381.97,588.847 318.584,737.327 402.809,593.19 381.97,588.847 " + id="polygon7985" /> + <polygon + transform="matrix(4.348075e-2,0,0,4.188821e-2,659.2285,53.92804)" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:17.46648407;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + i:knockout="Off" + a:adobe-blending-mode="lighten" + points="407.151,627.921 401.94,763.375 424.515,629.658 407.151,627.921 " + id="polygon7987" /> + </g> + <path + sodipodi:nodetypes="ccc" + id="path7989" + d="M 665.61116,79.917164 C 666.44697,82.39457 673.07693,87.947061 680.79385,85.431014 C 680.71584,83.40215 681.5919,77.986014 681.41239,76.464632" + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#002ddb;stroke-width:0.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + </g> + <g + id="g19907" + transform="translate(-144.5679,-360.12982)" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/16x16/actions/amarok_audioscrobbler.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <rect + y="994.01929" + x="1195.9285" + height="16" + width="16" + id="rect19905" + style="opacity:1;fill:#969696;fill-opacity:0;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + <g + transform="matrix(0.54166,0,0,0.54166,433.2485,468.0377)" + id="g19737"> + <path + style="fill:url(#linearGradient20087);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient20089);stroke-width:0.33316556;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + d="M 1416.6163,976.87301 C 1413.7955,976.98421 1411.0672,978.47329 1409.4822,981.08398 C 1406.9578,985.24204 1408.3269,990.61665 1412.3439,993.30812 C 1415.6774,995.54165 1421.8911,994.48167 1423.0451,991.46838 L 1422.8637,988.36123 C 1421.0309,992.0906 1416.605,992.89821 1413.6941,991.07998 C 1412.6026,990.39814 1411.8056,989.43698 1411.3161,988.34079 C 1411.2234,988.30036 1410.761,986.9822 1410.772,986.17397 C 1410.6917,984.9241 1410.9786,983.62737 1411.6789,982.47402 C 1413.4714,979.52145 1417.2524,978.59117 1420.1632,980.40942 C 1421.0703,980.97601 1421.4449,981.12435 1422.3599,982.45358 C 1423.2882,983.8022 1423.7587,985.03971 1424.3551,987.15517 C 1424.8354,988.85895 1425.5732,990.66731 1426.7331,992.14294 C 1427.8931,993.61858 1429.5751,994.77992 1431.6706,994.77992 C 1433.5298,994.77993 1435.0228,994.27283 1435.9833,993.20591 C 1436.9438,992.139 1437.4142,990.70421 1437.4142,989.24022 C 1437.4142,987.6258 1436.5305,986.28324 1435.4996,985.45851 C 1434.4688,984.63379 1433.232,984.25566 1431.9729,984.0276 C 1430.9678,983.84555 1430.2756,983.51582 1429.877,983.12816 C 1429.4784,982.7405 1429.2546,982.29281 1429.2522,981.41106 C 1429.2509,980.90955 1429.8386,979.94819 1430.7234,979.57131 C 1431.6082,979.19442 1432.6104,979.18491 1433.7866,980.47074 L 1435.5803,978.77408 C 1434.2614,977.33234 1432.6296,976.801 1431.1668,976.93433 C 1430.6791,976.97878 1430.2046,977.09934 1429.7762,977.28183 C 1428.0625,978.01181 1426.7887,979.56637 1426.7936,981.41106 C 1426.7974,982.81176 1427.2798,984.06711 1428.164,984.92703 C 1429.0481,985.78695 1430.2274,986.24473 1431.5295,986.48059 C 1432.5776,986.67044 1433.4616,986.99964 1433.9882,987.42091 C 1434.5147,987.84219 1434.9555,988.26362 1434.9555,989.24022 C 1434.9555,990.36725 1434.5534,991.08287 1434.1695,991.50926 C 1433.7857,991.93565 1433.1167,992.28603 1431.6706,992.28603 C 1430.4288,992.28604 1429.502,991.67622 1428.6476,990.58938 C 1427.7933,989.50254 1427.1328,987.94925 1426.713,986.46015 C 1426.0536,984.12119 1425.3757,982.57272 1424.4357,981.16576 C 1423.489,979.74888 1422.7214,978.93573 1421.5135,978.18127 C 1420.0083,977.24106 1418.3274,976.82065 1416.6768,976.87301 C 1416.6546,976.87371 1416.6385,976.87212 1416.6163,976.87301 z " + id="path2896" + sodipodi:nodetypes="cssccsccsssssssssssssccssssssssssssssc" /> + <g + id="g16594" + transform="matrix(0.96097,0,0,0.974738,770.9242,742.8116)"> + <path + transform="matrix(5.091848e-2,-5.525565e-2,6.0741e-2,4.632007e-2,1270.37,-32.70007)" + d="M -7824.3296 -3285.1472 A 25.97311 5.306335 0 1 1 -7876.2758,-3285.1472 A 25.97311 5.306335 0 1 1 -7824.3296 -3285.1472 z" + sodipodi:ry="5.306335" + sodipodi:rx="25.97311" + sodipodi:cy="-3285.1472" + sodipodi:cx="-7850.3027" + id="path16596" + style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:21.03624916;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0.68131869" + sodipodi:type="arc" /> + <path + style="fill:url(#linearGradient20091);fill-opacity:1" + d="M 675.44748,245.05111 C 675.16845,245.0606 674.6373,245.66838 674.43718,245.76498 C 674.23548,245.86238 672.63851,246.966 672.48723,247.16074 C 672.33596,247.35546 672.13428,247.43667 671.9158,247.42044 C 671.69731,247.40425 671.00658,247.39742 670.77126,247.52723 C 670.53591,247.65708 669.46179,248.71878 669.19286,248.84861 C 668.92394,248.9784 668.57299,249.04471 668.4049,249.06094 C 668.23684,249.07715 667.68011,249.67616 667.52884,249.78978 C 667.37757,249.9034 666.18209,250.40258 666.01401,250.49991 C 665.84592,250.59728 667.24027,251.9613 667.44198,251.99373 C 667.64369,252.02623 667.30309,252.76495 666.91501,252.76495 C 666.87227,252.76495 667.26176,253.98916 669.29842,255.0678 C 671.05913,256.0003 672.9272,255.58771 673.62096,255.36158 C 674.11157,255.20163 673.76745,255.11302 673.78429,254.85339 C 673.80106,254.59373 673.81508,254.2525 673.84871,253.96042 C 673.88235,253.66833 674.00013,251.21757 674.43718,250.56847 C 674.87418,249.91941 676.12298,249.14042 676.56001,249.05928 C 676.99707,248.97816 676.91326,248.81597 676.91326,248.66993 C 676.91326,248.5239 676.84601,248.19922 676.72832,248.15052 C 676.61068,248.10186 676.15148,248.39399 676.00021,248.42643 C 675.84894,248.45889 675.12612,248.50768 675.02524,248.34541 C 674.9244,248.18309 676.12648,245.87876 675.90795,245.60293 C 675.68952,245.32699 675.53817,245.05111 675.45413,245.05111 C 675.45196,245.05111 675.44968,245.05103 675.44748,245.05111 z M 671.87466,248.09817 C 672.00963,248.09635 672.10115,248.11022 672.10115,248.11022 C 672.17804,248.28329 671.81952,248.82716 671.4865,248.90138 C 671.15355,248.97554 670.385,250.06368 670.35941,249.81637 C 670.33373,249.56906 670.66671,248.75334 671.05096,248.38239 C 671.2926,248.14908 671.6459,248.10126 671.87466,248.09817 z " + id="path16598" + sodipodi:nodetypes="ccccsccsccsccccccsssccsccscccc" /> + <path + style="fill:url(#linearGradient20093);fill-opacity:1" + d="M 667.38511,253.5434 C 667.27099,253.39835 667.06118,253.11134 666.99289,252.92251 C 666.93768,252.76986 666.93659,252.75088 666.98312,252.75207 C 667.03972,252.75351 667.10882,252.73798 667.18866,252.66435 C 667.36952,252.49758 667.5152,252.25544 667.47132,252.1302 C 667.46282,252.10596 667.43669,252.08149 667.39867,252.0622 C 667.17473,251.94856 666.30084,250.97208 666.09149,250.60156 C 666.06575,250.556 666.04145,250.49972 666.03751,250.47653 L 666.03035,250.43435 L 666.23505,250.33732 C 666.34763,250.28395 666.62869,250.15377 666.85962,250.04804 C 667.55376,249.73024 667.4887,249.77144 667.88325,249.39985 C 668.25237,249.05221 668.3867,248.94442 668.46511,248.93297 C 668.75595,248.8905 668.95135,248.84285 669.13755,248.76901 C 669.33588,248.69035 669.40679,248.63421 670.03203,248.06083 C 670.54799,247.58767 670.71137,247.44292 670.8113,247.37042 C 670.97439,247.25208 671.18847,247.22163 671.78274,247.23221 C 672.13614,247.23851 672.19958,247.23641 672.25904,247.21644 C 672.36674,247.1803 672.45692,247.1155 672.58279,246.98382 C 672.64681,246.91682 672.77806,246.79885 672.87445,246.72166 C 673.24856,246.42208 674.04705,245.84593 674.44439,245.58888 C 674.67943,245.43683 674.68008,245.43633 674.98007,245.16925 C 675.22923,244.94743 675.39807,244.82289 675.49643,244.78835 C 675.58908,244.75583 675.59851,244.75652 675.64631,244.79941 C 675.69297,244.84126 676.0796,245.44524 676.11156,245.52619 C 676.17022,245.67479 676.2026,245.8324 676.14186,245.95698 C 676.02777,246.19098 675.86297,246.52747 675.79133,246.65624 C 675.19517,247.79122 675.00425,248.24559 675.084,248.3396 C 675.1557,248.42409 675.46288,248.47448 675.79339,248.45596 C 675.9034,248.4498 676.01778,248.4388 676.04756,248.43152 C 676.07735,248.42425 676.21018,248.36664 676.34275,248.3035 C 676.62549,248.16885 676.72485,248.13565 676.77352,248.15963 C 676.82255,248.18377 676.87033,248.28368 676.90291,248.43016 C 676.93399,248.56987 676.95,248.82083 676.93293,248.9008 C 676.91033,249.00668 676.81254,249.07111 676.59946,249.12053 C 676.28376,249.19374 675.79997,249.46104 675.30205,249.83735 C 674.83522,250.19017 674.5191,250.51873 674.37228,250.80369 C 674.2891,250.96516 674.26527,251.08193 674.19471,251.34152 C 674.14219,251.53472 673.85136,254.161 673.84971,254.22221 C 673.84923,254.24006 673.89888,251.37606 673.89578,251.37598 C 673.89269,251.37589 673.64642,251.91968 673.64204,251.91957 C 673.63764,251.91946 673.11871,252.75283 673.06331,252.72691 C 673.00793,252.701 673.80733,250.80494 674.00659,250.56762 C 674.19417,250.34422 674.61935,249.95762 675.15821,249.52054 C 675.49133,249.25033 675.66447,249.12238 675.74673,249.08564 C 675.77864,249.07139 675.91408,249.04038 676.04771,249.01673 C 676.37771,248.95833 676.46206,248.92161 676.46438,248.83534 C 676.46693,248.74093 676.35364,248.6783 676.20159,248.69007 C 676.00232,248.70548 675.77864,248.78336 675.50673,248.932 C 675.21556,249.09116 674.97882,249.13474 674.69752,249.08098 C 674.61778,249.06572 674.51564,249.03878 674.47055,249.02107 C 674.37379,248.98309 674.3609,248.95448 674.38561,248.83251 C 674.43885,248.56969 674.63469,248.14928 675.23157,247.01641 C 675.42032,246.65819 675.58974,246.32216 675.60808,246.26968 C 675.62641,246.2172 675.70095,246.1355 675.71895,246.02591 C 675.74678,245.85642 675.60284,245.61573 675.49172,245.52411 C 675.26474,245.35531 675.19402,245.31107 675.07986,245.35924 C 674.8728,245.44662 674.59341,245.86971 673.83099,246.90503 C 672.60898,248.56442 672.46623,248.73643 672.13013,248.95442 C 671.66036,249.2591 670.92441,249.86282 670.32852,250.43232 C 669.9134,250.82905 669.60231,251.30462 669.47614,251.4267 C 669.39421,251.50599 669.04627,251.93415 668.92411,252.03686 C 668.30525,252.43042 667.87241,252.85149 667.49529,253.47745 L 667.43126,253.59757 L 667.38511,253.5434 z M 668.46397,251.31116 C 668.7204,251.18608 669.03749,250.97543 669.19076,250.82834 C 669.3172,250.707 669.36801,250.58569 669.29744,250.57367 C 669.25183,250.5659 669.12809,250.63188 668.81319,250.83188 C 668.45212,251.06122 668.34711,251.11421 668.24396,251.11911 C 668.03778,251.12891 667.57411,251.05103 667.34278,250.96775 C 667.18243,250.91001 667.12695,250.87786 667.07592,250.81312 C 667.0435,250.772 667.0416,250.76154 667.04988,250.67014 C 667.06051,250.55273 667.12182,250.42 667.22682,250.28711 C 667.28065,250.21898 667.32856,250.18046 667.46856,250.09276 C 667.87127,249.84051 668.31501,249.62936 668.77187,249.47259 C 668.87754,249.43633 668.97637,249.40185 668.99148,249.39596 C 669.01507,249.38677 669.00758,249.37909 668.93855,249.34168 C 668.82318,249.27914 668.67294,249.22259 668.59041,249.21064 C 668.50801,249.1987 668.47708,249.21081 668.28301,249.33094 C 668.1367,249.42152 667.42448,249.89761 667.08857,250.12936 C 666.95522,250.22138 666.75501,250.35743 666.64366,250.43169 C 666.5323,250.50597 666.43344,250.57568 666.42397,250.58662 C 666.39823,250.61637 666.40097,250.69218 666.43,250.75379 C 666.46192,250.82148 666.61497,251.01116 666.69764,251.08544 L 666.75927,251.14083 L 667.5157,251.25385 C 667.93173,251.31603 668.28739,251.36796 668.30605,251.36927 C 668.32471,251.37057 668.39578,251.34443 668.46397,251.31116 z M 668.29973,250.79286 C 668.50561,250.7809 668.54927,250.76106 668.7151,250.60429 C 668.88527,250.44343 669.05711,250.21709 669.12323,250.06675 L 669.14624,250.01443 L 669.09433,250.02128 C 669.06579,250.02503 668.76941,250.05586 668.43571,250.08977 L 667.829,250.15141 L 667.77344,250.19488 C 667.66847,250.27701 667.52502,250.42275 667.47068,250.50247 C 667.40632,250.59689 667.39992,250.64727 667.44798,250.68095 C 667.50243,250.7191 667.68326,250.76303 667.85548,250.77995 C 668.08353,250.80236 668.11878,250.80338 668.29973,250.79286 z M 670.47827,249.80701 C 670.53764,249.76762 670.65353,249.6522 670.93932,249.34778 C 671.15026,249.12309 671.34955,248.93569 671.42687,248.88931 C 671.45643,248.87158 671.51608,248.84475 671.55945,248.82967 C 671.65746,248.79558 671.72169,248.75713 671.81412,248.67723 C 672.00854,248.50914 672.20844,248.1523 672.18313,248.01851 C 672.17567,247.9791 672.17451,247.97863 672.06448,247.97131 C 671.67055,247.94514 671.36409,248.03082 671.14299,248.22897 C 670.88826,248.45727 670.55413,249.05801 670.43071,249.50963 C 670.38192,249.68818 670.369,249.77777 670.38684,249.81379 C 670.4054,249.85123 670.4124,249.85071 670.47827,249.80701 z M 669.75335,249.33118 C 670.09096,249.26438 670.36935,249.20765 670.37201,249.20511 C 670.37465,249.20258 670.40977,249.09665 670.45005,248.96973 C 670.6136,248.45438 670.74114,248.22639 670.92962,248.11242 C 671.20958,247.94314 671.78787,247.7617 672.1084,247.74258 C 672.22476,247.73563 672.24446,247.72998 672.3505,247.67319 C 672.50811,247.5888 672.7391,247.43215 673.05617,247.19467 C 673.20264,247.08495 673.40034,246.94052 673.49551,246.87371 C 673.73961,246.70234 673.8601,246.58426 673.86198,246.51459 C 673.8623,246.50286 673.85711,246.49315 673.85045,246.49297 C 673.8438,246.49281 673.68645,246.59884 673.50079,246.72861 C 673.31512,246.85838 673.04174,247.04509 672.89326,247.14353 C 672.3744,247.48755 672.45133,247.46169 671.94052,247.46372 C 671.71568,247.46461 671.41018,247.46798 671.26164,247.47121 C 671.00014,247.47689 670.98987,247.47821 670.93762,247.51284 C 670.90795,247.5325 670.71779,247.71525 670.51504,247.91894 C 670.07326,248.36277 669.69447,248.72049 669.54727,248.83287 C 669.48801,248.87811 669.3962,248.93395 669.34323,248.95696 C 669.23699,249.00312 669.20246,249.03343 669.15576,249.1215 C 669.12056,249.1879 669.08207,249.33573 669.07961,249.41403 C 669.07814,249.4608 669.08095,249.46517 669.10875,249.45921 C 669.12567,249.45559 669.41574,249.39797 669.75335,249.33118 z " + id="path16600" + sodipodi:nodetypes="csssssscccscsscsssssscsssscsssssssssssssssscssssccssssscscscscccccsscsssssssssssscsscccsccsccccccsssccssssscsssccssssssssssscsscsssssc" /> + <g + transform="matrix(0.437568,0,0,0.437568,375.8931,217.9153)" + id="g16602" + style="fill:#002ddb;fill-opacity:1"> + <path + id="path16604" + d="M 680.83905,76.838803 C 680.83905,76.838803 679.45446,81.3797 679.41945,81.449838 C 679.22156,81.846006 678.85659,82.994032 678.98224,83.364797 C 679.13021,83.801538 679.68481,84.436589 679.94365,84.754121 C 680.2025,85.071759 680.31342,85.230473 680.23945,85.349692 C 680.16554,85.468783 679.20413,85.468783 679.20413,85.468783 C 679.20413,85.468783 678.46465,84.396936 678.31664,83.999943 C 678.16882,83.603079 678.09481,82.769436 678.24277,82.491624 C 678.39065,82.213703 680.83905,76.838803 680.83905,76.838803 z " + i:knockout="Off" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:21.03624916;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <polygon + id="polygon16606" + points="272.565,567.14 214.39,617.501 287.326,574.086 272.565,567.14 " + a:adobe-blending-mode="lighten" + i:knockout="Off" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:17.46648407;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + transform="matrix(4.348075e-2,0,0,4.188821e-2,659.2285,53.92804)" /> + <polygon + id="polygon16608" + points="329.873,549.776 230.887,678.282 344.633,558.457 329.873,549.776 " + a:adobe-blending-mode="lighten" + i:knockout="Off" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:17.46648407;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + transform="matrix(4.348075e-2,0,0,4.188821e-2,659.2285,53.92804)" /> + <polygon + id="polygon16610" + points="337.688,594.058 262.146,713.882 355.053,601.872 337.688,594.058 " + a:adobe-blending-mode="lighten" + i:knockout="Off" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:17.46648407;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + transform="matrix(4.348075e-2,0,0,4.188821e-2,659.2285,53.92804)" /> + <polygon + id="polygon16612" + points="381.97,588.847 318.584,737.327 402.809,593.19 381.97,588.847 " + a:adobe-blending-mode="lighten" + i:knockout="Off" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:17.46648407;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + transform="matrix(4.348075e-2,0,0,4.188821e-2,659.2285,53.92804)" /> + <polygon + id="polygon16614" + points="407.151,627.921 401.94,763.375 424.515,629.658 407.151,627.921 " + a:adobe-blending-mode="lighten" + i:knockout="Off" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:17.46648407;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + transform="matrix(4.348075e-2,0,0,4.188821e-2,659.2285,53.92804)" /> + </g> + <path + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#0018ce;stroke-width:0.25647929;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 667.20325,253.00451 C 667.56898,254.08855 670.41003,256.39812 673.7867,255.29718 C 673.75257,254.52943 674.1359,252.21951 674.05736,251.55381" + id="path16616" + sodipodi:nodetypes="ccc" /> + </g> + </g> + </g> + <g + id="g17059" + transform="matrix(0.689102,0,0,0.687484,234.4455,-18.011476)" + inkscape:export-filename="/home/lando/Projects/Amarok/icons/22/amarok_settings_indicator.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <rect + style="fill:url(#linearGradient17075);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient17077);stroke-width:0.77971518;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;display:inline" + id="rect12885" + width="31.146786" + height="31.220118" + x="1257.8357" + y="819.315" + rx="2.9266343" + ry="3.9271221" + inkscape:export-filename="/home/lando/Projects/Amarok/Setting-dialog/amarok_settings_indicator.png" + inkscape:export-xdpi="395.48178" + inkscape:export-ydpi="395.48178" /> + <rect + style="fill:url(#linearGradient17079);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient17081);stroke-width:0.47538957;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;display:inline" + id="rect12897" + width="26.198996" + height="25.386372" + x="1260.3097" + y="822.23187" + rx="1.2816533" + ry="1.2846692" + inkscape:export-filename="/home/lando/Projects/Amarok/Setting-dialog/amarok_settings_indicator.png" + inkscape:export-xdpi="395.48178" + inkscape:export-ydpi="395.48178" /> + <g + inkscape:export-ydpi="395.48178" + inkscape:export-xdpi="395.48178" + inkscape:export-filename="/home/lando/Projects/Amarok/Setting-dialog/amarok_settings_indicator.png" + transform="matrix(1.076035,0,0,1.076025,544.8993,756.7895)" + id="g16620" + style="display:inline"> + <path + sodipodi:type="arc" + style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:21.03624916;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0.68131869" + id="path16622" + sodipodi:cx="-7850.3027" + sodipodi:cy="-3285.1472" + sodipodi:rx="25.97311" + sodipodi:ry="5.306335" + d="M -7824.3296 -3285.1472 A 25.97311 5.306335 0 1 1 -7876.2758,-3285.1472 A 25.97311 5.306335 0 1 1 -7824.3296 -3285.1472 z" + transform="matrix(0.116367,-0.126279,0.138815,0.105858,2044.201,-572.7461)" /> + <path + sodipodi:nodetypes="ccccsccsccsccccccsssccsccscccc" + id="path16624" + d="M 684.58932,62.015066 C 683.95163,62.03676 682.73778,63.425764 682.28042,63.646529 C 681.81946,63.869109 678.16982,66.391273 677.82409,66.836327 C 677.47838,67.281337 677.01748,67.46692 676.51816,67.429844 C 676.01883,67.392834 674.44026,67.377225 673.90249,67.673903 C 673.36463,67.970636 670.90988,70.397011 670.29527,70.693715 C 669.68068,70.990341 668.87865,71.141876 668.49449,71.178979 C 668.11043,71.216028 666.83809,72.584967 666.49238,72.844636 C 666.14667,73.10429 663.41458,74.2451 663.03046,74.467545 C 662.64631,74.690071 665.83291,77.807344 666.29388,77.881456 C 666.75485,77.955728 665.97647,79.643959 665.08957,79.643959 C 664.99189,79.643959 665.88202,82.441727 670.53651,84.906802 C 674.56037,87.037898 678.82957,86.094974 680.41507,85.578197 C 681.53629,85.212648 680.74985,85.010148 680.78834,84.416789 C 680.82666,83.823378 680.8587,83.043556 680.93555,82.376043 C 681.01244,81.708505 681.28161,76.107653 682.28042,74.624229 C 683.27912,73.140873 686.13308,71.360612 687.13185,71.175177 C 688.13068,70.989794 687.93916,70.619136 687.93916,70.285379 C 687.93916,69.95165 687.78546,69.209626 687.51651,69.098343 C 687.24765,68.987127 686.19822,69.654757 685.85251,69.728899 C 685.5068,69.803078 683.85489,69.914575 683.62435,69.543728 C 683.3939,69.172775 686.14108,63.906545 685.64166,63.276177 C 685.14247,62.645555 684.79657,62.015066 684.60452,62.015066 C 684.59957,62.015066 684.59434,62.014895 684.58932,62.015066 z M 676.42414,68.978694 C 676.73261,68.974532 676.94176,69.006232 676.94176,69.006232 C 677.11748,69.40176 676.29813,70.644697 675.53706,70.814323 C 674.77614,70.983791 673.01974,73.4706 672.96126,72.905408 C 672.90256,72.340212 673.66355,70.476005 674.54169,69.628234 C 675.09392,69.095034 675.90135,68.985752 676.42414,68.978694 z " + style="fill:url(#linearGradient17083);fill-opacity:1" /> + <path + sodipodi:nodetypes="csssssscccscsscsssssscsssscsssssssssssssssscssssccssssscscscscccccsscsssssssssssscsscccsccsccccccsssccssssscsssccssssssssssscsscsssssc" + id="path16626" + d="M 666.16392,81.423003 C 665.9031,81.091515 665.42361,80.435598 665.26754,80.004036 C 665.14138,79.655175 665.13889,79.611803 665.24522,79.614525 C 665.37457,79.61782 665.53249,79.582317 665.71495,79.414066 C 666.12829,79.032933 666.46122,78.479561 666.36093,78.193327 C 666.34151,78.137933 666.28178,78.082005 666.19489,78.037937 C 665.68311,77.778228 663.68597,75.546616 663.20752,74.699847 C 663.14869,74.595715 663.09317,74.467102 663.08417,74.414107 L 663.06779,74.317713 L 663.5356,74.095961 C 663.79289,73.973979 664.43521,73.676485 664.96298,73.434854 C 666.54934,72.708572 666.40066,72.802712 667.30233,71.953513 C 668.1459,71.159029 668.45291,70.912676 668.63209,70.886525 C 669.29678,70.789464 669.74333,70.680556 670.16886,70.511811 C 670.62212,70.332045 670.78417,70.203751 672.21307,68.893356 C 673.39223,67.812027 673.7656,67.48122 673.99398,67.315529 C 674.36671,67.045079 674.85595,66.975476 676.21407,66.999677 C 677.02172,67.014072 677.16671,67.009258 677.3026,66.963628 C 677.54873,66.881043 677.75482,66.732936 678.04247,66.432002 C 678.18878,66.278881 678.48874,66.009285 678.70903,65.832881 C 679.56399,65.148239 681.38883,63.831519 682.2969,63.244071 C 682.83405,62.896574 682.83553,62.895428 683.52111,62.285059 C 684.09054,61.778114 684.47641,61.493496 684.70119,61.414567 C 684.91293,61.34024 684.93449,61.341828 685.04373,61.439835 C 685.15035,61.535483 686.03394,62.9158 686.10698,63.100796 C 686.24105,63.440395 686.31503,63.800609 686.17623,64.085306 C 685.91549,64.62008 685.53887,65.389083 685.37514,65.683372 C 684.01271,68.277198 683.57638,69.315608 683.75865,69.53045 C 683.9225,69.723542 684.62451,69.838696 685.37986,69.796375 C 685.63126,69.7823 685.89265,69.757165 685.96072,69.740527 C 686.02879,69.723904 686.33237,69.592247 686.63534,69.447957 C 687.2815,69.140217 687.50858,69.064356 687.61979,69.119151 C 687.73185,69.174334 687.84105,69.402649 687.91551,69.737405 C 687.98654,70.056699 688.02311,70.630231 687.98411,70.812999 C 687.93246,71.054964 687.70898,71.202203 687.22201,71.315153 C 686.50051,71.482471 685.39489,72.093335 684.25696,72.95334 C 683.19009,73.759659 682.46764,74.510542 682.13211,75.161789 C 681.942,75.530794 681.88754,75.797654 681.7263,76.390906 C 681.60627,76.832449 680.94162,82.834448 680.93784,82.974322 C 680.93675,83.015121 681.05021,76.469849 681.04313,76.469662 C 681.03606,76.469473 680.47326,77.712213 680.46324,77.71196 C 680.45319,77.711706 679.26724,79.616271 679.14064,79.557035 C 679.01407,79.497811 680.84098,75.164632 681.29637,74.622279 C 681.72505,74.111718 682.69675,73.228218 683.92824,72.229312 C 684.68954,71.611793 685.08523,71.319382 685.27321,71.235423 C 685.34613,71.202844 685.65566,71.131984 685.96107,71.077938 C 686.71523,70.944468 686.90799,70.86055 686.9133,70.663386 C 686.91912,70.447624 686.66022,70.304493 686.31274,70.331392 C 685.85732,70.366626 685.34615,70.54459 684.72472,70.884297 C 684.05931,71.248032 683.51826,71.34764 682.8754,71.224763 C 682.69316,71.189901 682.45974,71.128317 682.35669,71.087851 C 682.13555,71.001063 682.1061,70.935662 682.16257,70.656928 C 682.28424,70.056285 682.7318,69.095497 684.09589,66.506489 C 684.52724,65.687816 684.91444,64.919869 684.95636,64.799939 C 684.99825,64.680011 685.1686,64.493296 685.20972,64.242845 C 685.27332,63.855494 684.94437,63.305418 684.69042,63.096047 C 684.1717,62.710286 684.01007,62.609168 683.74917,62.719264 C 683.27598,62.918962 682.63747,63.885861 680.89506,66.251944 C 678.10234,70.044237 677.77609,70.437338 677.00798,70.935529 C 675.9344,71.631833 674.25248,73.011563 672.89065,74.31307 C 671.94197,75.219744 671.23101,76.306587 670.94267,76.585575 C 670.75542,76.76678 669.96025,77.745288 669.68107,77.980021 C 668.26676,78.87945 667.27756,79.841733 666.4157,81.272282 L 666.26937,81.5468 L 666.16392,81.423003 z M 668.6295,76.32153 C 669.21552,76.035684 669.9402,75.554275 670.29047,75.218104 C 670.57943,74.940811 670.69555,74.663571 670.53426,74.6361 C 670.43004,74.618356 670.14725,74.769144 669.42758,75.226216 C 668.60241,75.75033 668.36243,75.871421 668.1267,75.882628 C 667.6555,75.905015 666.59584,75.72705 666.06717,75.536718 C 665.70072,75.404769 665.57392,75.331295 665.45729,75.183336 C 665.38321,75.089358 665.37887,75.065462 665.39778,74.856572 C 665.42208,74.588243 665.56219,74.28492 665.80217,73.981211 C 665.92518,73.825514 666.03467,73.73747 666.35462,73.537052 C 667.27497,72.96057 668.28907,72.478015 669.33316,72.119738 C 669.57465,72.036872 669.8005,71.958064 669.83504,71.944615 C 669.88896,71.923614 669.87183,71.906057 669.71409,71.820563 C 669.45042,71.677633 669.10707,71.548405 668.91845,71.521096 C 668.73014,71.493813 668.65946,71.521481 668.21594,71.796028 C 667.88157,72.003024 666.25388,73.091052 665.48621,73.620705 C 665.18146,73.830983 664.72391,74.14191 664.46942,74.311617 C 664.21494,74.481377 663.98901,74.640691 663.96737,74.665692 C 663.90854,74.733682 663.91479,74.906936 663.98115,75.047732 C 664.05408,75.202428 664.40387,75.635913 664.59279,75.805673 L 664.73365,75.93226 L 666.46235,76.190566 C 667.41313,76.332656 668.22595,76.451329 668.26859,76.454332 C 668.31124,76.457306 668.47365,76.397562 668.6295,76.32153 z M 668.25415,75.137027 C 668.72466,75.109689 668.82443,75.064367 669.20342,74.70609 C 669.59232,74.338446 669.98504,73.821189 670.13614,73.477602 L 670.18873,73.358048 L 670.0701,73.373682 C 670.00486,73.382261 669.32753,73.452719 668.5649,73.53022 L 667.17835,73.671095 L 667.05139,73.770438 C 666.8115,73.958127 666.48365,74.29119 666.35948,74.473383 C 666.21238,74.689172 666.19775,74.804299 666.3076,74.881267 C 666.43204,74.968454 666.84529,75.068852 667.23887,75.107529 C 667.76006,75.158734 667.84061,75.161069 668.25415,75.137027 z M 673.23289,72.884003 C 673.36857,72.793999 673.63342,72.530209 674.28656,71.834505 C 674.76864,71.320997 675.22409,70.892729 675.40078,70.786743 C 675.46833,70.746223 675.60467,70.684907 675.70377,70.650443 C 675.92776,70.572528 676.07455,70.48466 676.28578,70.302053 C 676.73011,69.917918 677.18695,69.102408 677.12911,68.796643 C 677.11207,68.706574 677.1094,68.705506 676.85795,68.688776 C 675.95769,68.628965 675.25732,68.824768 674.75202,69.277624 C 674.16986,69.799364 673.40627,71.172265 673.12421,72.204378 C 673.01269,72.612445 672.98317,72.817187 673.02395,72.899506 C 673.06635,72.985066 673.08235,72.983877 673.23289,72.884003 z M 671.57619,71.796562 C 672.34776,71.643906 672.98398,71.514265 672.99004,71.508448 C 672.99608,71.502658 673.07634,71.260586 673.16839,70.970524 C 673.54217,69.792774 673.83364,69.271727 674.26438,69.011271 C 674.90419,68.624388 676.2258,68.209742 676.95832,68.166034 C 677.22424,68.150158 677.26926,68.137257 677.51161,68.007456 C 677.8718,67.814601 678.3997,67.456604 679.12431,66.913865 C 679.45905,66.663135 679.91088,66.333048 680.12838,66.180365 C 680.68623,65.788718 680.96159,65.51887 680.96589,65.359637 C 680.96661,65.332846 680.95475,65.310646 680.93954,65.310246 C 680.92433,65.309872 680.56473,65.552198 680.14043,65.848756 C 679.71612,66.145328 679.09133,66.572036 678.75201,66.797005 C 677.56622,67.583204 677.74205,67.5241 676.57466,67.528756 C 676.06082,67.530783 675.36265,67.538481 675.02318,67.545859 C 674.42556,67.558856 674.40209,67.56187 674.28268,67.640998 C 674.21487,67.685947 673.78028,68.103582 673.31692,68.569102 C 672.3073,69.583402 671.44162,70.400928 671.10522,70.657742 C 670.9698,70.761139 670.75997,70.888754 670.63892,70.941346 C 670.39611,71.046839 670.3172,71.116109 670.21049,71.317382 C 670.13004,71.469129 670.04208,71.806968 670.03645,71.985906 C 670.03309,72.092787 670.03952,72.102793 670.10304,72.089172 C 670.14171,72.080887 670.80463,71.949217 671.57619,71.796562 z " + style="fill:url(#linearGradient17085);fill-opacity:1" /> + <g + style="fill:#002ddb;fill-opacity:1" + id="g16628"> + <path + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:21.03624916;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + i:knockout="Off" + d="M 680.83905,76.838803 C 680.83905,76.838803 679.45446,81.3797 679.41945,81.449838 C 679.22156,81.846006 678.85659,82.994032 678.98224,83.364797 C 679.13021,83.801538 679.68481,84.436589 679.94365,84.754121 C 680.2025,85.071759 680.31342,85.230473 680.23945,85.349692 C 680.16554,85.468783 679.20413,85.468783 679.20413,85.468783 C 679.20413,85.468783 678.46465,84.396936 678.31664,83.999943 C 678.16882,83.603079 678.09481,82.769436 678.24277,82.491624 C 678.39065,82.213703 680.83905,76.838803 680.83905,76.838803 z " + id="path16630" /> + <polygon + transform="matrix(4.348075e-2,0,0,4.188821e-2,659.2285,53.92804)" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:17.46648407;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + i:knockout="Off" + a:adobe-blending-mode="lighten" + points="272.565,567.14 214.39,617.501 287.326,574.086 272.565,567.14 " + id="polygon16632" /> + <polygon + transform="matrix(4.348075e-2,0,0,4.188821e-2,659.2285,53.92804)" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:17.46648407;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + i:knockout="Off" + a:adobe-blending-mode="lighten" + points="329.873,549.776 230.887,678.282 344.633,558.457 329.873,549.776 " + id="polygon16634" /> + <polygon + transform="matrix(4.348075e-2,0,0,4.188821e-2,659.2285,53.92804)" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:17.46648407;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + i:knockout="Off" + a:adobe-blending-mode="lighten" + points="337.688,594.058 262.146,713.882 355.053,601.872 337.688,594.058 " + id="polygon16636" /> + <polygon + transform="matrix(4.348075e-2,0,0,4.188821e-2,659.2285,53.92804)" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:17.46648407;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + i:knockout="Off" + a:adobe-blending-mode="lighten" + points="381.97,588.847 318.584,737.327 402.809,593.19 381.97,588.847 " + id="polygon16638" /> + <polygon + transform="matrix(4.348075e-2,0,0,4.188821e-2,659.2285,53.92804)" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:17.46648407;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + i:knockout="Off" + a:adobe-blending-mode="lighten" + points="407.151,627.921 401.94,763.375 424.515,629.658 407.151,627.921 " + id="polygon16640" /> + </g> + <path + sodipodi:nodetypes="ccc" + id="path16642" + d="M 665.61116,79.917164 C 666.44697,82.39457 673.07693,87.947061 680.79385,85.431014 C 680.71584,83.40215 681.5919,77.986014 681.41239,76.464632" + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#002ddb;stroke-width:0.46467122;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + </g> + <g + style="display:inline" + id="g16644" + transform="matrix(1.529282,0,0,1.52928,1121.165,-5.9204653)" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/32x32/actions/amarok_settings_general.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <g + id="g16646" + transform="translate(0.141718,145.4379)" + inkscape:export-filename="/home/vadim/collection.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + transform="matrix(1.064993,0,0,1.064994,-425.4498,-454.68)" + d="M 428.77151 444.83228 A 9.8239412 9.8239412 0 1 1 409.12363,444.83228 A 9.8239412 9.8239412 0 1 1 428.77151 444.83228 z" + sodipodi:ry="9.8239412" + sodipodi:rx="9.8239412" + sodipodi:cy="444.83228" + sodipodi:cx="418.94757" + id="path16648" + style="opacity:1;fill:url(#linearGradient17012);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + transform="matrix(0.883732,0,0,0.900587,-349.1263,-383.0023)" + d="M 429.51763 446.44885 A 11.005301 11.005301 0 1 1 407.50703,446.44885 A 11.005301 11.005301 0 1 1 429.51763 446.44885 z" + sodipodi:ry="11.005301" + sodipodi:rx="11.005301" + sodipodi:cy="446.44885" + sodipodi:cx="418.51233" + id="path16650" + style="opacity:1;fill:url(#radialGradient17014);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + sodipodi:nodetypes="csssc" + id="path16652" + d="M 29.686411,20.220753 C 29.686411,25.937399 25.672358,20.884742 20.726478,20.884742 C 15.780598,20.884742 11.766545,25.937399 11.766545,20.220753 C 11.766545,14.504147 15.780598,9.8645522 20.726478,9.8645522 C 25.672358,9.8645522 29.686411,14.504147 29.686411,20.220753 z " + style="opacity:0.7;fill:url(#linearGradient17016);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" /> + </g> + <g + transform="matrix(2.879032e-2,0,0,2.879032e-2,9.015838,152.7103)" + id="wolf" + i:layer="yes" + i:dimmedPercent="3" + i:rgbTrio="#4F004F00FFFF"> + <radialGradient + id="radialGradient16655" + cx="375.21439" + cy="783.16357" + r="300.5752" + fx="375.21439" + fy="783.16357" + gradientUnits="userSpaceOnUse"> + <stop + offset="0.0112" + style="stop-color:#D2E8EC" + id="stop16657" /> + <stop + offset="0.2921" + style="stop-color:#60A8CE" + id="stop16659" /> + <stop + offset="0.4575" + style="stop-color:#4680A8" + id="stop16661" /> + <stop + offset="0.6767" + style="stop-color:#2F5C84" + id="stop16663" /> + <stop + offset="0.8653" + style="stop-color:#234970" + id="stop16665" /> + <stop + offset="1" + style="stop-color:#1F426A" + id="stop16667" /> + <a:midPointStop + offset="0.0112" + style="stop-color:#D2E8EC" /> + <a:midPointStop + offset="0.5" + style="stop-color:#D2E8EC" /> + <a:midPointStop + offset="0.2921" + style="stop-color:#60A8CE" /> + <a:midPointStop + offset="0.3651" + style="stop-color:#60A8CE" /> + <a:midPointStop + offset="1" + style="stop-color:#1F426A" /> + </radialGradient> + <g + style="opacity:0.18297873" + transform="matrix(0.925462,0,0,0.925462,22.38513,40.63705)" + i:rgbTrio="#4F004F00FFFF" + i:dimmedPercent="3" + i:layer="yes" + id="g3961"> + <path + style="opacity:0.22000002" + id="path3783" + d="M 618.829,233.011 C 603.559,233.011 573.908,264.857 562.798,269.885 C 551.683,274.913 463.707,331.894 455.371,341.95 C 447.039,352.005 435.926,356.196 423.888,355.358 C 411.851,354.52 373.793,354.176 360.83,360.88 C 347.864,367.584 288.686,422.396 273.868,429.101 C 259.052,435.803 239.721,439.239 230.461,440.076 C 221.201,440.915 190.523,471.838 182.188,477.706 C 173.854,483.569 112.922,507.063 103.663,512.089 C 94.402,517.118 158.112,596.697 169.225,598.373 C 180.336,600.05 158.309,608.266 136.929,608.266 C 134.575,608.266 167.484,694.564 279.692,750.263 C 376.697,798.413 454.851,769.173 493.075,757.495 C 520.103,749.238 525.004,745.556 525.933,732.148 C 526.86,718.741 528.534,708.146 530.387,693.063 C 532.24,677.982 538.718,551.444 562.799,517.926 C 586.873,484.407 655.692,444.169 679.768,439.981 C 703.846,435.791 699.217,427.412 699.217,419.87 C 699.217,412.328 695.512,395.568 689.03,393.054 C 682.547,390.541 657.253,405.637 648.919,407.313 C 640.584,408.989 600.765,411.503 595.208,403.124 C 589.652,394.743 640.131,309.268 646.613,299.213 C 653.093,289.157 655.872,275.749 643.836,261.504 C 631.793,247.256 623.458,233.011 618.829,233.011 z " + i:knockout="Off" /> + <radialGradient + gradientUnits="userSpaceOnUse" + fy="783.16357" + fx="375.21439" + r="300.5752" + cy="783.16357" + cx="375.21439" + id="radialGradient16671"> + <stop + id="stop16673" + style="stop-color:#D2E8EC" + offset="0.0112" /> + <stop + id="stop16675" + style="stop-color:#60A8CE" + offset="0.2921" /> + <stop + id="stop16677" + style="stop-color:#4680A8" + offset="0.4575" /> + <stop + id="stop16679" + style="stop-color:#2F5C84" + offset="0.6767" /> + <stop + id="stop16681" + style="stop-color:#234970" + offset="0.8653" /> + <stop + id="stop16683" + style="stop-color:#1F426A" + offset="1" /> + <a:midPointStop + style="stop-color:#D2E8EC" + offset="0.0112" /> + <a:midPointStop + style="stop-color:#D2E8EC" + offset="0.5" /> + <a:midPointStop + style="stop-color:#60A8CE" + offset="0.2921" /> + <a:midPointStop + style="stop-color:#60A8CE" + offset="0.3651" /> + <a:midPointStop + style="stop-color:#1F426A" + offset="1" /> + </radialGradient> + <path + style="fill:url(#radialGradient17018)" + id="path3798" + d="M 586.334,188.678 C 571.521,188.678 542.762,222.814 531.983,228.203 C 521.204,233.593 435.869,294.672 427.785,305.452 C 419.701,316.23 408.921,320.722 397.245,319.824 C 385.569,318.927 348.655,318.557 336.08,325.743 C 323.503,332.93 266.102,391.684 251.73,398.871 C 237.359,406.055 218.609,409.736 209.626,410.635 C 200.645,411.532 170.888,444.679 162.804,450.968 C 154.72,457.257 90.84,484.899 81.858,490.286 C 72.875,495.676 147.389,571.159 158.168,572.954 C 168.947,574.753 150.744,615.651 130.005,615.651 C 127.721,615.651 148.538,683.422 257.377,743.127 C 351.47,794.742 451.303,771.889 488.378,759.373 C 514.596,750.519 496.195,745.626 497.095,731.255 C 497.991,716.882 498.746,697.984 500.543,681.817 C 502.341,665.649 508.626,530.012 531.982,494.083 C 555.335,458.156 622.085,415.024 645.44,410.533 C 668.796,406.042 664.303,397.059 664.303,388.976 C 664.303,380.893 660.711,362.927 654.422,360.232 C 648.135,357.538 623.601,373.72 615.517,375.516 C 607.433,377.312 568.808,380.008 563.417,371.026 C 558.028,362.042 606.991,270.421 613.279,259.642 C 619.568,248.862 622.261,234.489 610.583,219.221 C 598.91,203.948 590.825,188.678 586.334,188.678 z " + i:knockout="Off" /> + <g + i:rgbTrio="#4F00FFFFFFFF" + i:dimmedPercent="50" + i:layer="yes" + id="g16686" /> + <defs + id="defs16688" /> + <mask + i:clipMask="true" + i:invertMask="false" + id="mask16690" + height="44.813" + width="93.126" + y="461.543" + x="155.705" + maskUnits="userSpaceOnUse"> + <g + id="g16692" + style="filter:url(#Adobe_OpacityMaskFilter_1_)"> + <linearGradient + y2="466.75449" + x2="200.0733" + y1="562.85791" + x1="211.7222" + gradientUnits="userSpaceOnUse" + id="linearGradient16694"> + <stop + id="stop16697" + style="stop-color:#FFFFFF" + offset="0" /> + <stop + id="stop16699" + style="stop-color:#000000" + offset="1" /> + <a:midPointStop + style="stop-color:#FFFFFF" + offset="0" /> + <a:midPointStop + style="stop-color:#FFFFFF" + offset="0.5" /> + <a:midPointStop + style="stop-color:#000000" + offset="1" /> + </linearGradient> + <ellipse + style="fill:url(#XMLID_1_)" + sodipodi:ry="59.682999" + sodipodi:rx="80.542" + sodipodi:cy="495.98801" + sodipodi:cx="203.617" + id="ellipse16701" + ry="59.682999" + rx="80.542" + cy="495.98801" + cx="203.617" + i:knockout="Off" /> + </g> + </mask> + <defs + id="defs16703" /> + <mask + i:clipMask="true" + i:invertMask="false" + id="mask16705" + height="127.553" + width="156.907" + y="425.034" + x="101.57" + maskUnits="userSpaceOnUse"> + <g + id="g16707" + style="filter:url(#Adobe_OpacityMaskFilter_2_)"> + <linearGradient + y2="563.47321" + x2="186.92191" + y1="397.62061" + x1="206.09081" + gradientUnits="userSpaceOnUse" + id="linearGradient16709"> + <stop + id="stop16711" + style="stop-color:#FFFFFF" + offset="0" /> + <stop + id="stop16713" + style="stop-color:#CBCBCB" + offset="0.1111" /> + <stop + id="stop16715" + style="stop-color:#969696" + offset="0.2396" /> + <stop + id="stop16717" + style="stop-color:#686868" + offset="0.3698" /> + <stop + id="stop16719" + style="stop-color:#424242" + offset="0.4991" /> + <stop + id="stop16721" + style="stop-color:#252525" + offset="0.6273" /> + <stop + id="stop16723" + style="stop-color:#111111" + offset="0.7542" /> + <stop + id="stop16725" + style="stop-color:#040404" + offset="0.8792" /> + <stop + id="stop16727" + style="stop-color:#000000" + offset="1" /> + <a:midPointStop + style="stop-color:#FFFFFF" + offset="0" /> + <a:midPointStop + style="stop-color:#FFFFFF" + offset="0.2994" /> + <a:midPointStop + style="stop-color:#000000" + offset="1" /> + </linearGradient> + <ellipse + style="fill:url(#XMLID_3_)" + sodipodi:ry="123.604" + sodipodi:rx="160.94" + sodipodi:cy="495.62299" + sodipodi:cx="194.76401" + id="ellipse16729" + ry="123.604" + rx="160.94" + cy="495.62299" + cx="194.76401" + i:knockout="Off" /> + </g> + </mask> + <linearGradient + gradientTransform="matrix(-1,0,0,1,951.2744,0)" + y2="643.30768" + x2="480.12021" + y1="815.45459" + x1="429.84909" + gradientUnits="userSpaceOnUse" + id="linearGradient16731"> + <stop + id="stop16733" + style="stop-color:#FFFFFF" + offset="0" /> + <stop + id="stop16735" + style="stop-color:#E5EAF2" + offset="1" /> + <a:midPointStop + style="stop-color:#FFFFFF" + offset="0" /> + <a:midPointStop + style="stop-color:#FFFFFF" + offset="0.5" /> + <a:midPointStop + style="stop-color:#E5EAF2" + offset="1" /> + </linearGradient> + <path + style="opacity:0.18000004;fill:url(#linearGradient17020)" + id="path3856" + d="M 497.013,546.945 C 497.013,546.945 465.169,655.349 464.364,657.024 C 459.813,666.482 451.419,693.889 454.309,702.74 C 457.712,713.167 470.467,728.327 476.42,735.907 C 482.373,743.49 484.924,747.28 483.223,750.125 C 481.523,752.968 459.412,752.968 459.412,752.968 C 459.412,752.968 442.405,727.38 439.001,717.903 C 435.601,708.428 433.899,688.527 437.302,681.895 C 440.703,675.26 497.013,546.945 497.013,546.945 z " + i:knockout="Off" /> + <linearGradient + y2="560.35498" + x2="278.14471" + y1="634.63232" + x1="220.9512" + gradientUnits="userSpaceOnUse" + id="linearGradient16738"> + <stop + id="stop16740" + style="stop-color:#FFFFFF" + offset="0" /> + <stop + id="stop16742" + style="stop-color:#193D6B" + offset="1" /> + <a:midPointStop + style="stop-color:#FFFFFF" + offset="0" /> + <a:midPointStop + style="stop-color:#FFFFFF" + offset="0.5" /> + <a:midPointStop + style="stop-color:#193D6B" + offset="1" /> + </linearGradient> + <polygon + style="opacity:0.65;fill:url(#linearGradient17022)" + id="polygon3863" + points="272.565,567.14 214.39,617.501 287.326,574.086 272.565,567.14 " + a:adobe-blending-mode="lighten" + i:knockout="Off" /> + <linearGradient + y2="565.86761" + x2="328.18469" + y1="684.01898" + x1="237.20799" + gradientUnits="userSpaceOnUse" + id="linearGradient16745"> + <stop + id="stop16747" + style="stop-color:#FFFFFF" + offset="0" /> + <stop + id="stop16749" + style="stop-color:#193D6B" + offset="1" /> + <a:midPointStop + style="stop-color:#FFFFFF" + offset="0" /> + <a:midPointStop + style="stop-color:#FFFFFF" + offset="0.5" /> + <a:midPointStop + style="stop-color:#193D6B" + offset="1" /> + </linearGradient> + <polygon + style="opacity:0.65;fill:url(#linearGradient17024)" + id="polygon3870" + points="329.873,549.776 230.887,678.282 344.633,558.457 329.873,549.776 " + a:adobe-blending-mode="lighten" + i:knockout="Off" /> + <linearGradient + y2="612.60522" + x2="335.09619" + y1="704.23578" + x1="264.5405" + gradientUnits="userSpaceOnUse" + id="linearGradient16752"> + <stop + id="stop16754" + style="stop-color:#FFFFFF" + offset="0" /> + <stop + id="stop16756" + style="stop-color:#193D6B" + offset="1" /> + <a:midPointStop + style="stop-color:#FFFFFF" + offset="0" /> + <a:midPointStop + style="stop-color:#FFFFFF" + offset="0.5" /> + <a:midPointStop + style="stop-color:#193D6B" + offset="1" /> + </linearGradient> + <polygon + style="opacity:0.65;fill:url(#linearGradient17026)" + id="polygon3877" + points="337.688,594.058 262.146,713.882 355.053,601.872 337.688,594.058 " + a:adobe-blending-mode="lighten" + i:knockout="Off" /> + <linearGradient + y2="626.00598" + x2="373.12509" + y1="713.54639" + x1="319.186" + gradientUnits="userSpaceOnUse" + id="linearGradient16759"> + <stop + id="stop16761" + style="stop-color:#FFFFFF" + offset="0" /> + <stop + id="stop16763" + style="stop-color:#193D6B" + offset="1" /> + <a:midPointStop + style="stop-color:#FFFFFF" + offset="0" /> + <a:midPointStop + style="stop-color:#FFFFFF" + offset="0.5" /> + <a:midPointStop + style="stop-color:#193D6B" + offset="1" /> + </linearGradient> + <polygon + style="opacity:0.65;fill:url(#linearGradient17028)" + id="polygon3884" + points="381.97,588.847 318.584,737.327 402.809,593.19 381.97,588.847 " + a:adobe-blending-mode="lighten" + i:knockout="Off" /> + <linearGradient + y2="688.75092" + x2="409.85471" + y1="790.51508" + x1="331.49609" + gradientUnits="userSpaceOnUse" + id="linearGradient16766"> + <stop + id="stop16768" + style="stop-color:#FFFFFF" + offset="0" /> + <stop + id="stop16770" + style="stop-color:#193D6B" + offset="1" /> + <a:midPointStop + style="stop-color:#FFFFFF" + offset="0" /> + <a:midPointStop + style="stop-color:#FFFFFF" + offset="0.5" /> + <a:midPointStop + style="stop-color:#193D6B" + offset="1" /> + </linearGradient> + <polygon + style="opacity:0.65;fill:url(#linearGradient17030)" + id="polygon3891" + points="407.151,627.921 401.94,763.375 424.515,629.658 407.151,627.921 " + a:adobe-blending-mode="lighten" + i:knockout="Off" /> + <defs + id="defs16773" /> + <mask + i:clipMask="true" + i:invertMask="false" + id="mask16775" + height="527.73" + width="447.197" + y="217.631" + x="191.541" + maskUnits="userSpaceOnUse"> + <g + id="g16777" + style="filter:url(#Adobe_OpacityMaskFilter_3_)"> + <radialGradient + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.931,0,0,0.9299,28.7459,21.6629)" + fy="155.23289" + fx="657.2124" + r="606.95343" + cy="155.23289" + cx="657.2124" + id="radialGradient16779"> + <stop + id="stop16781" + style="stop-color:#C9C9C8" + offset="0" /> + <stop + id="stop16783" + style="stop-color:#C4C4C4" + offset="0.0881" /> + <stop + id="stop16785" + style="stop-color:#B8B8B7" + offset="0.2192" /> + <stop + id="stop16787" + style="stop-color:#A1A0A0" + offset="0.3769" /> + <stop + id="stop16789" + style="stop-color:#7F7F7E" + offset="0.5556" /> + <stop + id="stop16791" + style="stop-color:#4F4F4E" + offset="0.7517" /> + <stop + id="stop16793" + style="stop-color:#121212" + offset="0.9595" /> + <stop + id="stop16795" + style="stop-color:#000000" + offset="1" /> + <a:midPointStop + style="stop-color:#C9C9C8" + offset="0" /> + <a:midPointStop + style="stop-color:#C9C9C8" + offset="0.6215" /> + <a:midPointStop + style="stop-color:#000000" + offset="1" /> + </radialGradient> + <circle + style="fill:url(#XMLID_11_)" + sodipodi:ry="302.95401" + sodipodi:rx="302.95401" + sodipodi:cy="444.22601" + sodipodi:cx="408.427" + id="circle16797" + r="302.95401" + cy="444.22601" + cx="408.427" + i:knockout="Off" /> + </g> + </mask> + <linearGradient + y2="522.02393" + x2="410.27759" + y1="239.939" + x1="517.18988" + gradientUnits="userSpaceOnUse" + id="linearGradient16799"> + <stop + id="stop16801" + style="stop-color:#FFFFFF" + offset="0" /> + <stop + id="stop16803" + style="stop-color:#FFFFFF" + offset="0.9944" /> + <a:midPointStop + style="stop-color:#FFFFFF" + offset="0" /> + <a:midPointStop + style="stop-color:#FFFFFF" + offset="0.6215" /> + <a:midPointStop + style="stop-color:#FFFFFF" + offset="0.9944" /> + </linearGradient> + <path + style="opacity:0.47999998;fill:url(#linearGradient17032)" + id="path3925" + d="M 566.303,218.317 C 607.373,264.655 592.756,249.235 589.541,263.89 C 586.325,278.548 512.629,401.164 528.704,407.028 C 544.779,412.89 567.565,414.686 586.053,402.96 C 604.542,391.235 633.438,382.73 638.259,393.721 C 643.083,404.713 610.113,404.175 599.664,409.305 C 589.215,414.434 525.003,467.462 508.928,487.247 C 492.851,507.035 435.463,593.164 429.836,628.34 C 424.207,663.514 418.582,703.819 418.582,722.87 C 418.582,741.923 384.824,749.254 342.222,743.389 C 299.62,737.529 232.901,685.496 198.338,645.192 C 163.776,604.887 271.922,544.985 278.354,523.733 C 284.786,502.481 384.553,419.303 412.542,403.68 C 439.074,388.87 554.529,205.029 566.303,218.317 z " + mask="url(#XMLID_10_)" + i:knockout="Off" /> + <defs + id="defs16806" /> + <mask + i:clipMask="true" + i:invertMask="false" + id="mask16808" + height="187.183" + width="252.506" + y="279.317" + x="243.061" + maskUnits="userSpaceOnUse"> + <g + id="g16810" + style="filter:url(#Adobe_OpacityMaskFilter_4_)"> + <linearGradient + y2="417.81519" + x2="372.38071" + y1="266.93359" + x1="340.0488" + gradientUnits="userSpaceOnUse" + id="linearGradient16812"> + <stop + id="stop16814" + style="stop-color:#FFFFFF" + offset="0" /> + <stop + id="stop16816" + style="stop-color:#F8F8F8" + offset="0.0867" /> + <stop + id="stop16818" + style="stop-color:#E4E4E4" + offset="0.2156" /> + <stop + id="stop16820" + style="stop-color:#C2C2C2" + offset="0.3708" /> + <stop + id="stop16822" + style="stop-color:#949494" + offset="0.5465" /> + <stop + id="stop16824" + style="stop-color:#595959" + offset="0.7394" /> + <stop + id="stop16826" + style="stop-color:#151515" + offset="0.9438" /> + <stop + id="stop16828" + style="stop-color:#000000" + offset="0.9944" /> + <a:midPointStop + style="stop-color:#FFFFFF" + offset="0" /> + <a:midPointStop + style="stop-color:#FFFFFF" + offset="0.6215" /> + <a:midPointStop + style="stop-color:#000000" + offset="0.9944" /> + </linearGradient> + <ellipse + style="fill:url(#XMLID_14_)" + sodipodi:ry="183.32201" + sodipodi:rx="215.32401" + sodipodi:cy="374.28201" + sodipodi:cx="363.052" + id="ellipse16830" + ry="183.32201" + rx="215.32401" + cy="374.28201" + cx="363.052" + i:knockout="Off" /> + </g> + </mask> + <path + style="opacity:0.47999998;fill:#ffffff" + id="path3954" + d="M 495.251,279.317 C 495.251,279.317 427.161,328.038 418.318,330.515 C 409.476,332.993 347.575,331.341 341.385,334.644 C 335.195,337.947 270.642,408.964 256.494,411.441 C 242.346,413.918 237.924,446.949 250.304,462.639 C 262.684,478.329 307.782,441.994 311.32,428.783 C 314.858,415.571 322.816,377.585 338.733,366.85 C 354.65,356.115 396.212,343.728 405.939,345.38 C 415.666,347.031 471.376,302.439 471.376,302.439 C 471.376,302.439 498.79,285.924 495.251,279.317 z " + mask="url(#XMLID_13_)" + i:knockout="Off" /> + </g> + <path + sodipodi:nodetypes="csssssssssscssssssssss" + i:knockout="Off" + d="M 481.10291,750.63818 C 504.60991,742.43859 484.89535,736.14215 485.70505,723.21312 C 486.51114,710.28229 486.11667,685.76446 487.73336,671.21963 C 489.35095,656.67391 495.00532,534.64658 516.0178,502.32266 C 537.02759,470.00053 597.07997,431.19635 618.09155,427.15597 C 639.10404,423.1156 635.06186,415.03394 635.06186,407.76198 C 635.06186,400.49002 631.83028,384.32671 626.17231,381.90212 C 620.51614,379.47843 598.44385,394.03675 591.17099,395.65254 C 583.89813,397.26833 549.14872,399.69382 544.29865,391.61306 C 539.45038,383.53051 583.50048,301.10266 589.15755,291.40521 C 594.81552,281.70687 597.23831,268.77604 586.73207,255.04001 C 576.23032,241.29949 568.95656,227.56166 564.91618,227.56166 C 551.5895,227.56166 525.71615,258.27249 516.0187,263.12077 C 506.32126,267.96994 429.54868,322.92034 422.27582,332.61869 C 415.00296,342.31523 405.30461,346.35651 394.80017,345.54862 C 384.29572,344.74162 351.08563,344.40875 339.7724,350.87371 C 328.45736,357.33957 276.81591,410.19826 263.88598,416.66413 C 250.95695,423.12729 234.08831,426.43894 226.00665,427.24774 C 217.92679,428.05474 191.15558,457.8758 183.88272,463.53377 C 176.60986,469.19174 119.1395,494.06017 111.05874,498.90665 C 102.97709,503.75582 170.01444,571.66494 179.71189,573.27983 C 189.40934,574.89832 129.82914,605.28739 137.19522,622.42986" + id="path10935" + style="fill:none;stroke:url(#linearGradient17034);stroke-width:26.21903229;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + i:knockout="Off" + d="M 412.32879,379.97694 C 412.32879,379.97694 375.86296,374.36555 354.82231,395.40722 C 333.78063,416.44789 315.54259,462.73569 316.94953,476.76314 C 318.35033,490.79058 360.43266,429.07146 378.66558,424.86497 C 396.90259,420.6554 416.53938,389.79379 412.32879,379.97694 z " + id="path10937" + style="fill:url(#linearGradient17036);fill-opacity:1" /> + <g + id="g16835" + i:layer="yes" + i:dimmedPercent="50" + i:rgbTrio="#4F00FFFFFFFF" /> + <defs + id="defs16837" /> + <mask + maskUnits="userSpaceOnUse" + x="155.705" + y="461.543" + width="93.126" + height="44.813" + id="mask16839" + i:invertMask="false" + i:clipMask="true"> + <g + id="g16841" + style="filter:url(#Adobe_OpacityMaskFilter_1_)"> + <linearGradient + id="linearGradient16843" + gradientUnits="userSpaceOnUse" + x1="211.7222" + y1="562.85791" + x2="200.0733" + y2="466.75449"> + <stop + offset="0" + style="stop-color:#FFFFFF" + id="stop16845" /> + <stop + offset="1" + style="stop-color:#000000" + id="stop16847" /> + <a:midPointStop + offset="0" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="0.5" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="1" + style="stop-color:#000000" /> + </linearGradient> + <ellipse + i:knockout="Off" + cx="203.617" + cy="495.98801" + rx="80.542" + ry="59.682999" + id="ellipse16849" + sodipodi:cx="203.617" + sodipodi:cy="495.98801" + sodipodi:rx="80.542" + sodipodi:ry="59.682999" + style="fill:url(#XMLID_1_)" /> + </g> + </mask> + <path + i:knockout="Off" + mask="url(#XMLID_26_)" + d="M 281.37786,477.99374 C 281.37786,477.99374 244.36221,474.50936 232.90412,477.99374 C 221.44514,481.47543 199.41154,501.50549 197.64911,509.34333 C 195.88577,517.17848 238.19143,518.05025 248.76695,515.43674 C 259.34336,512.82592 278.73556,487.57064 281.37786,477.99374 z " + id="path10955" + style="fill:url(#linearGradient17038);fill-opacity:1" /> + <defs + id="defs16852" /> + <mask + maskUnits="userSpaceOnUse" + x="101.57" + y="425.034" + width="156.907" + height="127.553" + id="mask16854" + i:invertMask="false" + i:clipMask="true"> + <g + id="g16856" + style="filter:url(#Adobe_OpacityMaskFilter_2_)"> + <linearGradient + id="linearGradient16858" + gradientUnits="userSpaceOnUse" + x1="206.09081" + y1="397.62061" + x2="186.92191" + y2="563.47321"> + <stop + offset="0" + style="stop-color:#FFFFFF" + id="stop16860" /> + <stop + offset="0.1111" + style="stop-color:#CBCBCB" + id="stop16862" /> + <stop + offset="0.2396" + style="stop-color:#969696" + id="stop16864" /> + <stop + offset="0.3698" + style="stop-color:#686868" + id="stop16866" /> + <stop + offset="0.4991" + style="stop-color:#424242" + id="stop16868" /> + <stop + offset="0.6273" + style="stop-color:#252525" + id="stop16870" /> + <stop + offset="0.7542" + style="stop-color:#111111" + id="stop16872" /> + <stop + offset="0.8792" + style="stop-color:#040404" + id="stop16874" /> + <stop + offset="1" + style="stop-color:#000000" + id="stop16876" /> + <a:midPointStop + offset="0" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="0.2994" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="1" + style="stop-color:#000000" /> + </linearGradient> + <ellipse + i:knockout="Off" + cx="194.76401" + cy="495.62299" + rx="160.94" + ry="123.604" + id="ellipse16878" + sodipodi:cx="194.76401" + sodipodi:cy="495.62299" + sodipodi:rx="160.94" + sodipodi:ry="123.604" + style="fill:url(#XMLID_3_)" /> + </g> + </mask> + <path + i:knockout="Off" + mask="url(#XMLID_2_)" + d="M 264.39264,457.23757 C 264.39264,457.23757 248.74502,449.20288 242.34258,450.5424 C 235.94257,451.88111 162.5424,503.92155 155.43109,507.93608 C 145.58106,513.50061 166.41337,529.61436 172.78919,535.94906 C 176.51582,539.65148 196.50296,555.63297 202.1933,552.95716 C 207.88364,550.27731 218.15866,545.6144 228.11755,543.60472 C 238.07564,541.59584 280.81526,517.27562 279.32897,506.77962 C 278.36042,499.93526 243.69985,528.9442 234.43128,529.27888 C 216.00946,529.94581 184.72874,524.8579 181.17147,516.82481 C 177.61502,508.7885 187.76262,494.32799 193.26425,490.71426 C 229.47159,466.92308 264.39264,457.23757 264.39264,457.23757 z " + id="path10986" + style="fill:url(#linearGradient17040);fill-opacity:1" /> + <linearGradient + id="linearGradient16881" + gradientUnits="userSpaceOnUse" + x1="429.84909" + y1="815.45459" + x2="480.12021" + y2="643.30768" + gradientTransform="matrix(-1,0,0,1,951.2744,0)"> + <stop + offset="0" + style="stop-color:#FFFFFF" + id="stop16883" /> + <stop + offset="1" + style="stop-color:#E5EAF2" + id="stop16885" /> + <a:midPointStop + offset="0" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="0.5" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="1" + style="stop-color:#E5EAF2" /> + </linearGradient> + <linearGradient + id="linearGradient16887" + gradientUnits="userSpaceOnUse" + x1="220.9512" + y1="634.63232" + x2="278.14471" + y2="560.35498"> + <stop + offset="0" + style="stop-color:#FFFFFF" + id="stop16889" /> + <stop + offset="1" + style="stop-color:#193D6B" + id="stop16891" /> + <a:midPointStop + offset="0" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="0.5" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="1" + style="stop-color:#193D6B" /> + </linearGradient> + <linearGradient + id="linearGradient16893" + gradientUnits="userSpaceOnUse" + x1="237.20799" + y1="684.01898" + x2="328.18469" + y2="565.86761"> + <stop + offset="0" + style="stop-color:#FFFFFF" + id="stop16895" /> + <stop + offset="1" + style="stop-color:#193D6B" + id="stop16897" /> + <a:midPointStop + offset="0" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="0.5" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="1" + style="stop-color:#193D6B" /> + </linearGradient> + <linearGradient + id="linearGradient16899" + gradientUnits="userSpaceOnUse" + x1="264.5405" + y1="704.23578" + x2="335.09619" + y2="612.60522"> + <stop + offset="0" + style="stop-color:#FFFFFF" + id="stop16901" /> + <stop + offset="1" + style="stop-color:#193D6B" + id="stop16903" /> + <a:midPointStop + offset="0" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="0.5" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="1" + style="stop-color:#193D6B" /> + </linearGradient> + <linearGradient + id="linearGradient16905" + gradientUnits="userSpaceOnUse" + x1="319.186" + y1="713.54639" + x2="373.12509" + y2="626.00598"> + <stop + offset="0" + style="stop-color:#FFFFFF" + id="stop16907" /> + <stop + offset="1" + style="stop-color:#193D6B" + id="stop16909" /> + <a:midPointStop + offset="0" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="0.5" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="1" + style="stop-color:#193D6B" /> + </linearGradient> + <linearGradient + id="linearGradient16911" + gradientUnits="userSpaceOnUse" + x1="331.49609" + y1="790.51508" + x2="409.85471" + y2="688.75092"> + <stop + offset="0" + style="stop-color:#FFFFFF" + id="stop16913" /> + <stop + offset="1" + style="stop-color:#193D6B" + id="stop16915" /> + <a:midPointStop + offset="0" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="0.5" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="1" + style="stop-color:#193D6B" /> + </linearGradient> + <defs + id="defs16917" /> + <mask + maskUnits="userSpaceOnUse" + x="191.541" + y="217.631" + width="447.197" + height="527.73" + id="mask16919" + i:invertMask="false" + i:clipMask="true"> + <g + id="g16921" + style="filter:url(#Adobe_OpacityMaskFilter_3_)"> + <radialGradient + id="radialGradient16923" + cx="657.2124" + cy="155.23289" + r="606.95343" + fx="657.2124" + fy="155.23289" + gradientTransform="matrix(0.931,0,0,0.9299,28.7459,21.6629)" + gradientUnits="userSpaceOnUse"> + <stop + offset="0" + style="stop-color:#C9C9C8" + id="stop16925" /> + <stop + offset="0.0881" + style="stop-color:#C4C4C4" + id="stop16927" /> + <stop + offset="0.2192" + style="stop-color:#B8B8B7" + id="stop16932" /> + <stop + offset="0.3769" + style="stop-color:#A1A0A0" + id="stop16934" /> + <stop + offset="0.5556" + style="stop-color:#7F7F7E" + id="stop16936" /> + <stop + offset="0.7517" + style="stop-color:#4F4F4E" + id="stop16938" /> + <stop + offset="0.9595" + style="stop-color:#121212" + id="stop16940" /> + <stop + offset="1" + style="stop-color:#000000" + id="stop16942" /> + <a:midPointStop + offset="0" + style="stop-color:#C9C9C8" /> + <a:midPointStop + offset="0.6215" + style="stop-color:#C9C9C8" /> + <a:midPointStop + offset="1" + style="stop-color:#000000" /> + </radialGradient> + <circle + i:knockout="Off" + cx="408.427" + cy="444.22601" + r="302.95401" + id="circle16944" + sodipodi:cx="408.427" + sodipodi:cy="444.22601" + sodipodi:rx="302.95401" + sodipodi:ry="302.95401" + style="fill:url(#XMLID_11_)" /> + </g> + </mask> + <linearGradient + id="linearGradient16946" + gradientUnits="userSpaceOnUse" + x1="517.18988" + y1="239.939" + x2="410.27759" + y2="522.02393"> + <stop + offset="0" + style="stop-color:#FFFFFF" + id="stop16948" /> + <stop + offset="0.9944" + style="stop-color:#FFFFFF" + id="stop16950" /> + <a:midPointStop + offset="0" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="0.6215" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="0.9944" + style="stop-color:#FFFFFF" /> + </linearGradient> + <defs + id="defs16952" /> + <mask + maskUnits="userSpaceOnUse" + x="243.061" + y="279.317" + width="252.506" + height="187.183" + id="mask16954" + i:invertMask="false" + i:clipMask="true"> + <g + id="g16956" + style="filter:url(#Adobe_OpacityMaskFilter_4_)"> + <linearGradient + id="linearGradient16958" + gradientUnits="userSpaceOnUse" + x1="340.0488" + y1="266.93359" + x2="372.38071" + y2="417.81519"> + <stop + offset="0" + style="stop-color:#FFFFFF" + id="stop16960" /> + <stop + offset="0.0867" + style="stop-color:#F8F8F8" + id="stop16962" /> + <stop + offset="0.2156" + style="stop-color:#E4E4E4" + id="stop16964" /> + <stop + offset="0.3708" + style="stop-color:#C2C2C2" + id="stop16966" /> + <stop + offset="0.5465" + style="stop-color:#949494" + id="stop16968" /> + <stop + offset="0.7394" + style="stop-color:#595959" + id="stop16970" /> + <stop + offset="0.9438" + style="stop-color:#151515" + id="stop16972" /> + <stop + offset="0.9944" + style="stop-color:#000000" + id="stop16974" /> + <a:midPointStop + offset="0" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="0.6215" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="0.9944" + style="stop-color:#000000" /> + </linearGradient> + <ellipse + i:knockout="Off" + cx="363.052" + cy="374.28201" + rx="215.32401" + ry="183.32201" + id="ellipse16976" + sodipodi:cx="363.052" + sodipodi:cy="374.28201" + sodipodi:rx="215.32401" + sodipodi:ry="183.32201" + style="fill:url(#XMLID_14_)" /> + </g> + </mask> + </g> + </g> + <g + transform="matrix(0.764641,0,0,0.76464,1060.3151,127.86404)" + id="g17087" + style="display:inline" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/16x16/actions/amarok_settings_general.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/vadim/collection.png" + transform="translate(0.141718,145.4379)" + id="g17089"> + <path + sodipodi:type="arc" + style="opacity:1;fill:url(#linearGradient17467);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path17091" + sodipodi:cx="418.94757" + sodipodi:cy="444.83228" + sodipodi:rx="9.8239412" + sodipodi:ry="9.8239412" + d="M 428.77151 444.83228 A 9.8239412 9.8239412 0 1 1 409.12363,444.83228 A 9.8239412 9.8239412 0 1 1 428.77151 444.83228 z" + transform="matrix(1.064993,0,0,1.064994,-425.4498,-454.68)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:type="arc" + style="opacity:1;fill:url(#radialGradient17469);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path17093" + sodipodi:cx="418.51233" + sodipodi:cy="446.44885" + sodipodi:rx="11.005301" + sodipodi:ry="11.005301" + d="M 429.51763 446.44885 A 11.005301 11.005301 0 1 1 407.50703,446.44885 A 11.005301 11.005301 0 1 1 429.51763 446.44885 z" + transform="matrix(0.883732,0,0,0.900587,-349.1263,-383.0023)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + style="opacity:0.7;fill:url(#linearGradient17471);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 29.686411,20.220753 C 29.686411,25.937399 25.672358,20.884742 20.726478,20.884742 C 15.780598,20.884742 11.766545,25.937399 11.766545,20.220753 C 11.766545,14.504147 15.780598,9.8645522 20.726478,9.8645522 C 25.672358,9.8645522 29.686411,14.504147 29.686411,20.220753 z " + id="path17095" + sodipodi:nodetypes="csssc" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" /> + </g> + <g + i:rgbTrio="#4F004F00FFFF" + i:dimmedPercent="3" + i:layer="yes" + id="g17097" + transform="matrix(2.879032e-2,0,0,2.879032e-2,9.015838,152.7103)"> + <radialGradient + gradientUnits="userSpaceOnUse" + fy="783.16357" + fx="375.21439" + r="300.5752" + cy="783.16357" + cx="375.21439" + id="radialGradient17099"> + <stop + id="stop17101" + style="stop-color:#D2E8EC" + offset="0.0112" /> + <stop + id="stop17103" + style="stop-color:#60A8CE" + offset="0.2921" /> + <stop + id="stop17105" + style="stop-color:#4680A8" + offset="0.4575" /> + <stop + id="stop17107" + style="stop-color:#2F5C84" + offset="0.6767" /> + <stop + id="stop17109" + style="stop-color:#234970" + offset="0.8653" /> + <stop + id="stop17111" + style="stop-color:#1F426A" + offset="1" /> + <a:midPointStop + style="stop-color:#D2E8EC" + offset="0.0112" /> + <a:midPointStop + style="stop-color:#D2E8EC" + offset="0.5" /> + <a:midPointStop + style="stop-color:#60A8CE" + offset="0.2921" /> + <a:midPointStop + style="stop-color:#60A8CE" + offset="0.3651" /> + <a:midPointStop + style="stop-color:#1F426A" + offset="1" /> + </radialGradient> + <g + id="g17113" + i:layer="yes" + i:dimmedPercent="3" + i:rgbTrio="#4F004F00FFFF" + transform="matrix(0.925462,0,0,0.925462,22.38513,40.63705)" + style="opacity:0.18297873"> + <path + i:knockout="Off" + d="M 618.829,233.011 C 603.559,233.011 573.908,264.857 562.798,269.885 C 551.683,274.913 463.707,331.894 455.371,341.95 C 447.039,352.005 435.926,356.196 423.888,355.358 C 411.851,354.52 373.793,354.176 360.83,360.88 C 347.864,367.584 288.686,422.396 273.868,429.101 C 259.052,435.803 239.721,439.239 230.461,440.076 C 221.201,440.915 190.523,471.838 182.188,477.706 C 173.854,483.569 112.922,507.063 103.663,512.089 C 94.402,517.118 158.112,596.697 169.225,598.373 C 180.336,600.05 158.309,608.266 136.929,608.266 C 134.575,608.266 167.484,694.564 279.692,750.263 C 376.697,798.413 454.851,769.173 493.075,757.495 C 520.103,749.238 525.004,745.556 525.933,732.148 C 526.86,718.741 528.534,708.146 530.387,693.063 C 532.24,677.982 538.718,551.444 562.799,517.926 C 586.873,484.407 655.692,444.169 679.768,439.981 C 703.846,435.791 699.217,427.412 699.217,419.87 C 699.217,412.328 695.512,395.568 689.03,393.054 C 682.547,390.541 657.253,405.637 648.919,407.313 C 640.584,408.989 600.765,411.503 595.208,403.124 C 589.652,394.743 640.131,309.268 646.613,299.213 C 653.093,289.157 655.872,275.749 643.836,261.504 C 631.793,247.256 623.458,233.011 618.829,233.011 z " + id="path17115" + style="opacity:0.22000002" /> + <radialGradient + id="radialGradient17117" + cx="375.21439" + cy="783.16357" + r="300.5752" + fx="375.21439" + fy="783.16357" + gradientUnits="userSpaceOnUse"> + <stop + offset="0.0112" + style="stop-color:#D2E8EC" + id="stop17119" /> + <stop + offset="0.2921" + style="stop-color:#60A8CE" + id="stop17121" /> + <stop + offset="0.4575" + style="stop-color:#4680A8" + id="stop17123" /> + <stop + offset="0.6767" + style="stop-color:#2F5C84" + id="stop17125" /> + <stop + offset="0.8653" + style="stop-color:#234970" + id="stop17127" /> + <stop + offset="1" + style="stop-color:#1F426A" + id="stop17129" /> + <a:midPointStop + offset="0.0112" + style="stop-color:#D2E8EC" /> + <a:midPointStop + offset="0.5" + style="stop-color:#D2E8EC" /> + <a:midPointStop + offset="0.2921" + style="stop-color:#60A8CE" /> + <a:midPointStop + offset="0.3651" + style="stop-color:#60A8CE" /> + <a:midPointStop + offset="1" + style="stop-color:#1F426A" /> + </radialGradient> + <path + i:knockout="Off" + d="M 586.334,188.678 C 571.521,188.678 542.762,222.814 531.983,228.203 C 521.204,233.593 435.869,294.672 427.785,305.452 C 419.701,316.23 408.921,320.722 397.245,319.824 C 385.569,318.927 348.655,318.557 336.08,325.743 C 323.503,332.93 266.102,391.684 251.73,398.871 C 237.359,406.055 218.609,409.736 209.626,410.635 C 200.645,411.532 170.888,444.679 162.804,450.968 C 154.72,457.257 90.84,484.899 81.858,490.286 C 72.875,495.676 147.389,571.159 158.168,572.954 C 168.947,574.753 150.744,615.651 130.005,615.651 C 127.721,615.651 148.538,683.422 257.377,743.127 C 351.47,794.742 451.303,771.889 488.378,759.373 C 514.596,750.519 496.195,745.626 497.095,731.255 C 497.991,716.882 498.746,697.984 500.543,681.817 C 502.341,665.649 508.626,530.012 531.982,494.083 C 555.335,458.156 622.085,415.024 645.44,410.533 C 668.796,406.042 664.303,397.059 664.303,388.976 C 664.303,380.893 660.711,362.927 654.422,360.232 C 648.135,357.538 623.601,373.72 615.517,375.516 C 607.433,377.312 568.808,380.008 563.417,371.026 C 558.028,362.042 606.991,270.421 613.279,259.642 C 619.568,248.862 622.261,234.489 610.583,219.221 C 598.91,203.948 590.825,188.678 586.334,188.678 z " + id="path17131" + style="fill:url(#radialGradient17473)" /> + <g + id="g17133" + i:layer="yes" + i:dimmedPercent="50" + i:rgbTrio="#4F00FFFFFFFF" /> + <defs + id="defs17135" /> + <mask + maskUnits="userSpaceOnUse" + x="155.705" + y="461.543" + width="93.126" + height="44.813" + id="mask17137" + i:invertMask="false" + i:clipMask="true"> + <g + id="g17139" + style="filter:url(#Adobe_OpacityMaskFilter_1_)"> + <linearGradient + id="linearGradient17141" + gradientUnits="userSpaceOnUse" + x1="211.7222" + y1="562.85791" + x2="200.0733" + y2="466.75449"> + <stop + offset="0" + style="stop-color:#FFFFFF" + id="stop17143" /> + <stop + offset="1" + style="stop-color:#000000" + id="stop17145" /> + <a:midPointStop + offset="0" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="0.5" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="1" + style="stop-color:#000000" /> + </linearGradient> + <ellipse + i:knockout="Off" + cx="203.617" + cy="495.98801" + rx="80.542" + ry="59.682999" + id="ellipse17147" + sodipodi:cx="203.617" + sodipodi:cy="495.98801" + sodipodi:rx="80.542" + sodipodi:ry="59.682999" + style="fill:url(#XMLID_1_)" /> + </g> + </mask> + <defs + id="defs17149" /> + <mask + maskUnits="userSpaceOnUse" + x="101.57" + y="425.034" + width="156.907" + height="127.553" + id="mask17151" + i:invertMask="false" + i:clipMask="true"> + <g + id="g17153" + style="filter:url(#Adobe_OpacityMaskFilter_2_)"> + <linearGradient + id="linearGradient17155" + gradientUnits="userSpaceOnUse" + x1="206.09081" + y1="397.62061" + x2="186.92191" + y2="563.47321"> + <stop + offset="0" + style="stop-color:#FFFFFF" + id="stop17157" /> + <stop + offset="0.1111" + style="stop-color:#CBCBCB" + id="stop17159" /> + <stop + offset="0.2396" + style="stop-color:#969696" + id="stop17161" /> + <stop + offset="0.3698" + style="stop-color:#686868" + id="stop17163" /> + <stop + offset="0.4991" + style="stop-color:#424242" + id="stop17165" /> + <stop + offset="0.6273" + style="stop-color:#252525" + id="stop17167" /> + <stop + offset="0.7542" + style="stop-color:#111111" + id="stop17169" /> + <stop + offset="0.8792" + style="stop-color:#040404" + id="stop17171" /> + <stop + offset="1" + style="stop-color:#000000" + id="stop17173" /> + <a:midPointStop + offset="0" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="0.2994" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="1" + style="stop-color:#000000" /> + </linearGradient> + <ellipse + i:knockout="Off" + cx="194.76401" + cy="495.62299" + rx="160.94" + ry="123.604" + id="ellipse17175" + sodipodi:cx="194.76401" + sodipodi:cy="495.62299" + sodipodi:rx="160.94" + sodipodi:ry="123.604" + style="fill:url(#XMLID_3_)" /> + </g> + </mask> + <linearGradient + id="linearGradient17177" + gradientUnits="userSpaceOnUse" + x1="429.84909" + y1="815.45459" + x2="480.12021" + y2="643.30768" + gradientTransform="matrix(-1,0,0,1,951.2744,0)"> + <stop + offset="0" + style="stop-color:#FFFFFF" + id="stop17179" /> + <stop + offset="1" + style="stop-color:#E5EAF2" + id="stop17181" /> + <a:midPointStop + offset="0" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="0.5" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="1" + style="stop-color:#E5EAF2" /> + </linearGradient> + <path + i:knockout="Off" + d="M 497.013,546.945 C 497.013,546.945 465.169,655.349 464.364,657.024 C 459.813,666.482 451.419,693.889 454.309,702.74 C 457.712,713.167 470.467,728.327 476.42,735.907 C 482.373,743.49 484.924,747.28 483.223,750.125 C 481.523,752.968 459.412,752.968 459.412,752.968 C 459.412,752.968 442.405,727.38 439.001,717.903 C 435.601,708.428 433.899,688.527 437.302,681.895 C 440.703,675.26 497.013,546.945 497.013,546.945 z " + id="path17183" + style="opacity:0.18000004;fill:url(#linearGradient17475)" /> + <linearGradient + id="linearGradient17185" + gradientUnits="userSpaceOnUse" + x1="220.9512" + y1="634.63232" + x2="278.14471" + y2="560.35498"> + <stop + offset="0" + style="stop-color:#FFFFFF" + id="stop17187" /> + <stop + offset="1" + style="stop-color:#193D6B" + id="stop17189" /> + <a:midPointStop + offset="0" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="0.5" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="1" + style="stop-color:#193D6B" /> + </linearGradient> + <polygon + i:knockout="Off" + a:adobe-blending-mode="lighten" + points="272.565,567.14 214.39,617.501 287.326,574.086 272.565,567.14 " + id="polygon17191" + style="opacity:0.65;fill:url(#linearGradient17477)" /> + <linearGradient + id="linearGradient17193" + gradientUnits="userSpaceOnUse" + x1="237.20799" + y1="684.01898" + x2="328.18469" + y2="565.86761"> + <stop + offset="0" + style="stop-color:#FFFFFF" + id="stop17195" /> + <stop + offset="1" + style="stop-color:#193D6B" + id="stop17197" /> + <a:midPointStop + offset="0" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="0.5" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="1" + style="stop-color:#193D6B" /> + </linearGradient> + <polygon + i:knockout="Off" + a:adobe-blending-mode="lighten" + points="329.873,549.776 230.887,678.282 344.633,558.457 329.873,549.776 " + id="polygon17199" + style="opacity:0.65;fill:url(#linearGradient17479)" /> + <linearGradient + id="linearGradient17201" + gradientUnits="userSpaceOnUse" + x1="264.5405" + y1="704.23578" + x2="335.09619" + y2="612.60522"> + <stop + offset="0" + style="stop-color:#FFFFFF" + id="stop17203" /> + <stop + offset="1" + style="stop-color:#193D6B" + id="stop17205" /> + <a:midPointStop + offset="0" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="0.5" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="1" + style="stop-color:#193D6B" /> + </linearGradient> + <polygon + i:knockout="Off" + a:adobe-blending-mode="lighten" + points="337.688,594.058 262.146,713.882 355.053,601.872 337.688,594.058 " + id="polygon17207" + style="opacity:0.65;fill:url(#linearGradient17482)" /> + <linearGradient + id="linearGradient17209" + gradientUnits="userSpaceOnUse" + x1="319.186" + y1="713.54639" + x2="373.12509" + y2="626.00598"> + <stop + offset="0" + style="stop-color:#FFFFFF" + id="stop17211" /> + <stop + offset="1" + style="stop-color:#193D6B" + id="stop17213" /> + <a:midPointStop + offset="0" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="0.5" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="1" + style="stop-color:#193D6B" /> + </linearGradient> + <polygon + i:knockout="Off" + a:adobe-blending-mode="lighten" + points="381.97,588.847 318.584,737.327 402.809,593.19 381.97,588.847 " + id="polygon17215" + style="opacity:0.65;fill:url(#linearGradient17484)" /> + <linearGradient + id="linearGradient17217" + gradientUnits="userSpaceOnUse" + x1="331.49609" + y1="790.51508" + x2="409.85471" + y2="688.75092"> + <stop + offset="0" + style="stop-color:#FFFFFF" + id="stop17219" /> + <stop + offset="1" + style="stop-color:#193D6B" + id="stop17221" /> + <a:midPointStop + offset="0" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="0.5" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="1" + style="stop-color:#193D6B" /> + </linearGradient> + <polygon + i:knockout="Off" + a:adobe-blending-mode="lighten" + points="407.151,627.921 401.94,763.375 424.515,629.658 407.151,627.921 " + id="polygon17223" + style="opacity:0.65;fill:url(#linearGradient17486)" /> + <defs + id="defs17225" /> + <mask + maskUnits="userSpaceOnUse" + x="191.541" + y="217.631" + width="447.197" + height="527.73" + id="mask17227" + i:invertMask="false" + i:clipMask="true"> + <g + id="g17229" + style="filter:url(#Adobe_OpacityMaskFilter_3_)"> + <radialGradient + id="radialGradient17231" + cx="657.2124" + cy="155.23289" + r="606.95343" + fx="657.2124" + fy="155.23289" + gradientTransform="matrix(0.931,0,0,0.9299,28.7459,21.6629)" + gradientUnits="userSpaceOnUse"> + <stop + offset="0" + style="stop-color:#C9C9C8" + id="stop17233" /> + <stop + offset="0.0881" + style="stop-color:#C4C4C4" + id="stop17235" /> + <stop + offset="0.2192" + style="stop-color:#B8B8B7" + id="stop17237" /> + <stop + offset="0.3769" + style="stop-color:#A1A0A0" + id="stop17239" /> + <stop + offset="0.5556" + style="stop-color:#7F7F7E" + id="stop17241" /> + <stop + offset="0.7517" + style="stop-color:#4F4F4E" + id="stop17243" /> + <stop + offset="0.9595" + style="stop-color:#121212" + id="stop17245" /> + <stop + offset="1" + style="stop-color:#000000" + id="stop17247" /> + <a:midPointStop + offset="0" + style="stop-color:#C9C9C8" /> + <a:midPointStop + offset="0.6215" + style="stop-color:#C9C9C8" /> + <a:midPointStop + offset="1" + style="stop-color:#000000" /> + </radialGradient> + <circle + i:knockout="Off" + cx="408.427" + cy="444.22601" + r="302.95401" + id="circle17249" + sodipodi:cx="408.427" + sodipodi:cy="444.22601" + sodipodi:rx="302.95401" + sodipodi:ry="302.95401" + style="fill:url(#XMLID_11_)" /> + </g> + </mask> + <linearGradient + id="linearGradient17251" + gradientUnits="userSpaceOnUse" + x1="517.18988" + y1="239.939" + x2="410.27759" + y2="522.02393"> + <stop + offset="0" + style="stop-color:#FFFFFF" + id="stop17253" /> + <stop + offset="0.9944" + style="stop-color:#FFFFFF" + id="stop17255" /> + <a:midPointStop + offset="0" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="0.6215" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="0.9944" + style="stop-color:#FFFFFF" /> + </linearGradient> + <path + i:knockout="Off" + mask="url(#XMLID_10_)" + d="M 566.303,218.317 C 607.373,264.655 592.756,249.235 589.541,263.89 C 586.325,278.548 512.629,401.164 528.704,407.028 C 544.779,412.89 567.565,414.686 586.053,402.96 C 604.542,391.235 633.438,382.73 638.259,393.721 C 643.083,404.713 610.113,404.175 599.664,409.305 C 589.215,414.434 525.003,467.462 508.928,487.247 C 492.851,507.035 435.463,593.164 429.836,628.34 C 424.207,663.514 418.582,703.819 418.582,722.87 C 418.582,741.923 384.824,749.254 342.222,743.389 C 299.62,737.529 232.901,685.496 198.338,645.192 C 163.776,604.887 271.922,544.985 278.354,523.733 C 284.786,502.481 384.553,419.303 412.542,403.68 C 439.074,388.87 554.529,205.029 566.303,218.317 z " + id="path17257" + style="opacity:0.47999998;fill:url(#linearGradient17488)" /> + <defs + id="defs17259" /> + <mask + maskUnits="userSpaceOnUse" + x="243.061" + y="279.317" + width="252.506" + height="187.183" + id="mask17261" + i:invertMask="false" + i:clipMask="true"> + <g + id="g17263" + style="filter:url(#Adobe_OpacityMaskFilter_4_)"> + <linearGradient + id="linearGradient17265" + gradientUnits="userSpaceOnUse" + x1="340.0488" + y1="266.93359" + x2="372.38071" + y2="417.81519"> + <stop + offset="0" + style="stop-color:#FFFFFF" + id="stop17267" /> + <stop + offset="0.0867" + style="stop-color:#F8F8F8" + id="stop17269" /> + <stop + offset="0.2156" + style="stop-color:#E4E4E4" + id="stop17271" /> + <stop + offset="0.3708" + style="stop-color:#C2C2C2" + id="stop17273" /> + <stop + offset="0.5465" + style="stop-color:#949494" + id="stop17275" /> + <stop + offset="0.7394" + style="stop-color:#595959" + id="stop17277" /> + <stop + offset="0.9438" + style="stop-color:#151515" + id="stop17279" /> + <stop + offset="0.9944" + style="stop-color:#000000" + id="stop17281" /> + <a:midPointStop + offset="0" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="0.6215" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="0.9944" + style="stop-color:#000000" /> + </linearGradient> + <ellipse + i:knockout="Off" + cx="363.052" + cy="374.28201" + rx="215.32401" + ry="183.32201" + id="ellipse17283" + sodipodi:cx="363.052" + sodipodi:cy="374.28201" + sodipodi:rx="215.32401" + sodipodi:ry="183.32201" + style="fill:url(#XMLID_14_)" /> + </g> + </mask> + <path + i:knockout="Off" + mask="url(#XMLID_13_)" + d="M 495.251,279.317 C 495.251,279.317 427.161,328.038 418.318,330.515 C 409.476,332.993 347.575,331.341 341.385,334.644 C 335.195,337.947 270.642,408.964 256.494,411.441 C 242.346,413.918 237.924,446.949 250.304,462.639 C 262.684,478.329 307.782,441.994 311.32,428.783 C 314.858,415.571 322.816,377.585 338.733,366.85 C 354.65,356.115 396.212,343.728 405.939,345.38 C 415.666,347.031 471.376,302.439 471.376,302.439 C 471.376,302.439 498.79,285.924 495.251,279.317 z " + id="path17285" + style="opacity:0.47999998;fill:#ffffff" /> + </g> + <path + style="fill:none;stroke:url(#linearGradient17490);stroke-width:26.21903229;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path17287" + d="M 481.10291,750.63818 C 504.60991,742.43859 484.89535,736.14215 485.70505,723.21312 C 486.51114,710.28229 486.11667,685.76446 487.73336,671.21963 C 489.35095,656.67391 495.00532,534.64658 516.0178,502.32266 C 537.02759,470.00053 597.07997,431.19635 618.09155,427.15597 C 639.10404,423.1156 635.06186,415.03394 635.06186,407.76198 C 635.06186,400.49002 631.83028,384.32671 626.17231,381.90212 C 620.51614,379.47843 598.44385,394.03675 591.17099,395.65254 C 583.89813,397.26833 549.14872,399.69382 544.29865,391.61306 C 539.45038,383.53051 583.50048,301.10266 589.15755,291.40521 C 594.81552,281.70687 597.23831,268.77604 586.73207,255.04001 C 576.23032,241.29949 568.95656,227.56166 564.91618,227.56166 C 551.5895,227.56166 525.71615,258.27249 516.0187,263.12077 C 506.32126,267.96994 429.54868,322.92034 422.27582,332.61869 C 415.00296,342.31523 405.30461,346.35651 394.80017,345.54862 C 384.29572,344.74162 351.08563,344.40875 339.7724,350.87371 C 328.45736,357.33957 276.81591,410.19826 263.88598,416.66413 C 250.95695,423.12729 234.08831,426.43894 226.00665,427.24774 C 217.92679,428.05474 191.15558,457.8758 183.88272,463.53377 C 176.60986,469.19174 119.1395,494.06017 111.05874,498.90665 C 102.97709,503.75582 170.01444,571.66494 179.71189,573.27983 C 189.40934,574.89832 129.82914,605.28739 137.19522,622.42986" + i:knockout="Off" + sodipodi:nodetypes="csssssssssscssssssssss" /> + <path + style="fill:url(#linearGradient17492);fill-opacity:1" + id="path17289" + d="M 412.32879,379.97694 C 412.32879,379.97694 375.86296,374.36555 354.82231,395.40722 C 333.78063,416.44789 315.54259,462.73569 316.94953,476.76314 C 318.35033,490.79058 360.43266,429.07146 378.66558,424.86497 C 396.90259,420.6554 416.53938,389.79379 412.32879,379.97694 z " + i:knockout="Off" /> + <g + i:rgbTrio="#4F00FFFFFFFF" + i:dimmedPercent="50" + i:layer="yes" + id="g17291" /> + <defs + id="defs17293" /> + <mask + i:clipMask="true" + i:invertMask="false" + id="mask17295" + height="44.813" + width="93.126" + y="461.543" + x="155.705" + maskUnits="userSpaceOnUse"> + <g + id="g17297" + style="filter:url(#Adobe_OpacityMaskFilter_1_)"> + <linearGradient + y2="466.75449" + x2="200.0733" + y1="562.85791" + x1="211.7222" + gradientUnits="userSpaceOnUse" + id="linearGradient17299"> + <stop + id="stop17301" + style="stop-color:#FFFFFF" + offset="0" /> + <stop + id="stop17303" + style="stop-color:#000000" + offset="1" /> + <a:midPointStop + style="stop-color:#FFFFFF" + offset="0" /> + <a:midPointStop + style="stop-color:#FFFFFF" + offset="0.5" /> + <a:midPointStop + style="stop-color:#000000" + offset="1" /> + </linearGradient> + <ellipse + style="fill:url(#XMLID_1_)" + sodipodi:ry="59.682999" + sodipodi:rx="80.542" + sodipodi:cy="495.98801" + sodipodi:cx="203.617" + id="ellipse17305" + ry="59.682999" + rx="80.542" + cy="495.98801" + cx="203.617" + i:knockout="Off" /> + </g> + </mask> + <path + style="fill:url(#linearGradient17494);fill-opacity:1" + id="path17307" + d="M 281.37786,477.99374 C 281.37786,477.99374 244.36221,474.50936 232.90412,477.99374 C 221.44514,481.47543 199.41154,501.50549 197.64911,509.34333 C 195.88577,517.17848 238.19143,518.05025 248.76695,515.43674 C 259.34336,512.82592 278.73556,487.57064 281.37786,477.99374 z " + mask="url(#XMLID_26_)" + i:knockout="Off" /> + <defs + id="defs17309" /> + <mask + i:clipMask="true" + i:invertMask="false" + id="mask17311" + height="127.553" + width="156.907" + y="425.034" + x="101.57" + maskUnits="userSpaceOnUse"> + <g + id="g17313" + style="filter:url(#Adobe_OpacityMaskFilter_2_)"> + <linearGradient + y2="563.47321" + x2="186.92191" + y1="397.62061" + x1="206.09081" + gradientUnits="userSpaceOnUse" + id="linearGradient17315"> + <stop + id="stop17317" + style="stop-color:#FFFFFF" + offset="0" /> + <stop + id="stop17319" + style="stop-color:#CBCBCB" + offset="0.1111" /> + <stop + id="stop17321" + style="stop-color:#969696" + offset="0.2396" /> + <stop + id="stop17323" + style="stop-color:#686868" + offset="0.3698" /> + <stop + id="stop17325" + style="stop-color:#424242" + offset="0.4991" /> + <stop + id="stop17327" + style="stop-color:#252525" + offset="0.6273" /> + <stop + id="stop17329" + style="stop-color:#111111" + offset="0.7542" /> + <stop + id="stop17331" + style="stop-color:#040404" + offset="0.8792" /> + <stop + id="stop17333" + style="stop-color:#000000" + offset="1" /> + <a:midPointStop + style="stop-color:#FFFFFF" + offset="0" /> + <a:midPointStop + style="stop-color:#FFFFFF" + offset="0.2994" /> + <a:midPointStop + style="stop-color:#000000" + offset="1" /> + </linearGradient> + <ellipse + style="fill:url(#XMLID_3_)" + sodipodi:ry="123.604" + sodipodi:rx="160.94" + sodipodi:cy="495.62299" + sodipodi:cx="194.76401" + id="ellipse17335" + ry="123.604" + rx="160.94" + cy="495.62299" + cx="194.76401" + i:knockout="Off" /> + </g> + </mask> + <path + style="fill:url(#linearGradient17497);fill-opacity:1" + id="path17337" + d="M 264.39264,457.23757 C 264.39264,457.23757 248.74502,449.20288 242.34258,450.5424 C 235.94257,451.88111 162.5424,503.92155 155.43109,507.93608 C 145.58106,513.50061 166.41337,529.61436 172.78919,535.94906 C 176.51582,539.65148 196.50296,555.63297 202.1933,552.95716 C 207.88364,550.27731 218.15866,545.6144 228.11755,543.60472 C 238.07564,541.59584 280.81526,517.27562 279.32897,506.77962 C 278.36042,499.93526 243.69985,528.9442 234.43128,529.27888 C 216.00946,529.94581 184.72874,524.8579 181.17147,516.82481 C 177.61502,508.7885 187.76262,494.32799 193.26425,490.71426 C 229.47159,466.92308 264.39264,457.23757 264.39264,457.23757 z " + mask="url(#XMLID_2_)" + i:knockout="Off" /> + <linearGradient + gradientTransform="matrix(-1,0,0,1,951.2744,0)" + y2="643.30768" + x2="480.12021" + y1="815.45459" + x1="429.84909" + gradientUnits="userSpaceOnUse" + id="linearGradient17339"> + <stop + id="stop17341" + style="stop-color:#FFFFFF" + offset="0" /> + <stop + id="stop17343" + style="stop-color:#E5EAF2" + offset="1" /> + <a:midPointStop + style="stop-color:#FFFFFF" + offset="0" /> + <a:midPointStop + style="stop-color:#FFFFFF" + offset="0.5" /> + <a:midPointStop + style="stop-color:#E5EAF2" + offset="1" /> + </linearGradient> + <linearGradient + y2="560.35498" + x2="278.14471" + y1="634.63232" + x1="220.9512" + gradientUnits="userSpaceOnUse" + id="linearGradient17345"> + <stop + id="stop17347" + style="stop-color:#FFFFFF" + offset="0" /> + <stop + id="stop17349" + style="stop-color:#193D6B" + offset="1" /> + <a:midPointStop + style="stop-color:#FFFFFF" + offset="0" /> + <a:midPointStop + style="stop-color:#FFFFFF" + offset="0.5" /> + <a:midPointStop + style="stop-color:#193D6B" + offset="1" /> + </linearGradient> + <linearGradient + y2="565.86761" + x2="328.18469" + y1="684.01898" + x1="237.20799" + gradientUnits="userSpaceOnUse" + id="linearGradient17351"> + <stop + id="stop17353" + style="stop-color:#FFFFFF" + offset="0" /> + <stop + id="stop17355" + style="stop-color:#193D6B" + offset="1" /> + <a:midPointStop + style="stop-color:#FFFFFF" + offset="0" /> + <a:midPointStop + style="stop-color:#FFFFFF" + offset="0.5" /> + <a:midPointStop + style="stop-color:#193D6B" + offset="1" /> + </linearGradient> + <linearGradient + y2="612.60522" + x2="335.09619" + y1="704.23578" + x1="264.5405" + gradientUnits="userSpaceOnUse" + id="linearGradient17357"> + <stop + id="stop17359" + style="stop-color:#FFFFFF" + offset="0" /> + <stop + id="stop17361" + style="stop-color:#193D6B" + offset="1" /> + <a:midPointStop + style="stop-color:#FFFFFF" + offset="0" /> + <a:midPointStop + style="stop-color:#FFFFFF" + offset="0.5" /> + <a:midPointStop + style="stop-color:#193D6B" + offset="1" /> + </linearGradient> + <linearGradient + y2="626.00598" + x2="373.12509" + y1="713.54639" + x1="319.186" + gradientUnits="userSpaceOnUse" + id="linearGradient17367"> + <stop + id="stop17369" + style="stop-color:#FFFFFF" + offset="0" /> + <stop + id="stop17371" + style="stop-color:#193D6B" + offset="1" /> + <a:midPointStop + style="stop-color:#FFFFFF" + offset="0" /> + <a:midPointStop + style="stop-color:#FFFFFF" + offset="0.5" /> + <a:midPointStop + style="stop-color:#193D6B" + offset="1" /> + </linearGradient> + <linearGradient + y2="688.75092" + x2="409.85471" + y1="790.51508" + x1="331.49609" + gradientUnits="userSpaceOnUse" + id="linearGradient17373"> + <stop + id="stop17375" + style="stop-color:#FFFFFF" + offset="0" /> + <stop + id="stop17377" + style="stop-color:#193D6B" + offset="1" /> + <a:midPointStop + style="stop-color:#FFFFFF" + offset="0" /> + <a:midPointStop + style="stop-color:#FFFFFF" + offset="0.5" /> + <a:midPointStop + style="stop-color:#193D6B" + offset="1" /> + </linearGradient> + <defs + id="defs17379" /> + <mask + i:clipMask="true" + i:invertMask="false" + id="mask17381" + height="527.73" + width="447.197" + y="217.631" + x="191.541" + maskUnits="userSpaceOnUse"> + <g + id="g17383" + style="filter:url(#Adobe_OpacityMaskFilter_3_)"> + <radialGradient + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.931,0,0,0.9299,28.7459,21.6629)" + fy="155.23289" + fx="657.2124" + r="606.95343" + cy="155.23289" + cx="657.2124" + id="radialGradient17385"> + <stop + id="stop17387" + style="stop-color:#C9C9C8" + offset="0" /> + <stop + id="stop17389" + style="stop-color:#C4C4C4" + offset="0.0881" /> + <stop + id="stop17391" + style="stop-color:#B8B8B7" + offset="0.2192" /> + <stop + id="stop17393" + style="stop-color:#A1A0A0" + offset="0.3769" /> + <stop + id="stop17395" + style="stop-color:#7F7F7E" + offset="0.5556" /> + <stop + id="stop17397" + style="stop-color:#4F4F4E" + offset="0.7517" /> + <stop + id="stop17399" + style="stop-color:#121212" + offset="0.9595" /> + <stop + id="stop17401" + style="stop-color:#000000" + offset="1" /> + <a:midPointStop + style="stop-color:#C9C9C8" + offset="0" /> + <a:midPointStop + style="stop-color:#C9C9C8" + offset="0.6215" /> + <a:midPointStop + style="stop-color:#000000" + offset="1" /> + </radialGradient> + <circle + style="fill:url(#XMLID_11_)" + sodipodi:ry="302.95401" + sodipodi:rx="302.95401" + sodipodi:cy="444.22601" + sodipodi:cx="408.427" + id="circle17403" + r="302.95401" + cy="444.22601" + cx="408.427" + i:knockout="Off" /> + </g> + </mask> + <linearGradient + y2="522.02393" + x2="410.27759" + y1="239.939" + x1="517.18988" + gradientUnits="userSpaceOnUse" + id="linearGradient17405"> + <stop + id="stop17407" + style="stop-color:#FFFFFF" + offset="0" /> + <stop + id="stop17409" + style="stop-color:#FFFFFF" + offset="0.9944" /> + <a:midPointStop + style="stop-color:#FFFFFF" + offset="0" /> + <a:midPointStop + style="stop-color:#FFFFFF" + offset="0.6215" /> + <a:midPointStop + style="stop-color:#FFFFFF" + offset="0.9944" /> + </linearGradient> + <defs + id="defs17411" /> + <mask + i:clipMask="true" + i:invertMask="false" + id="mask17413" + height="187.183" + width="252.506" + y="279.317" + x="243.061" + maskUnits="userSpaceOnUse"> + <g + id="g17415" + style="filter:url(#Adobe_OpacityMaskFilter_4_)"> + <linearGradient + y2="417.81519" + x2="372.38071" + y1="266.93359" + x1="340.0488" + gradientUnits="userSpaceOnUse" + id="linearGradient17417"> + <stop + id="stop17419" + style="stop-color:#FFFFFF" + offset="0" /> + <stop + id="stop17421" + style="stop-color:#F8F8F8" + offset="0.0867" /> + <stop + id="stop17423" + style="stop-color:#E4E4E4" + offset="0.2156" /> + <stop + id="stop17425" + style="stop-color:#C2C2C2" + offset="0.3708" /> + <stop + id="stop17427" + style="stop-color:#949494" + offset="0.5465" /> + <stop + id="stop17429" + style="stop-color:#595959" + offset="0.7394" /> + <stop + id="stop17431" + style="stop-color:#151515" + offset="0.9438" /> + <stop + id="stop17433" + style="stop-color:#000000" + offset="0.9944" /> + <a:midPointStop + style="stop-color:#FFFFFF" + offset="0" /> + <a:midPointStop + style="stop-color:#FFFFFF" + offset="0.6215" /> + <a:midPointStop + style="stop-color:#000000" + offset="0.9944" /> + </linearGradient> + <ellipse + style="fill:url(#XMLID_14_)" + sodipodi:ry="183.32201" + sodipodi:rx="215.32401" + sodipodi:cy="374.28201" + sodipodi:cx="363.052" + id="ellipse17435" + ry="183.32201" + rx="215.32401" + cy="374.28201" + cx="363.052" + i:knockout="Off" /> + </g> + </mask> + </g> + </g> + <g + transform="matrix(1.051382,0,0,1.05138,1089.088,77.694854)" + id="g17499" + style="display:inline" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/22x22/actions/amarok_settings_general.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/vadim/collection.png" + transform="translate(0.141718,145.4379)" + id="g17502"> + <path + sodipodi:type="arc" + style="opacity:1;fill:url(#linearGradient17847);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path17504" + sodipodi:cx="418.94757" + sodipodi:cy="444.83228" + sodipodi:rx="9.8239412" + sodipodi:ry="9.8239412" + d="M 428.77151 444.83228 A 9.8239412 9.8239412 0 1 1 409.12363,444.83228 A 9.8239412 9.8239412 0 1 1 428.77151 444.83228 z" + transform="matrix(1.064993,0,0,1.064994,-425.4498,-454.68)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:type="arc" + style="opacity:1;fill:url(#radialGradient17849);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path17506" + sodipodi:cx="418.51233" + sodipodi:cy="446.44885" + sodipodi:rx="11.005301" + sodipodi:ry="11.005301" + d="M 429.51763 446.44885 A 11.005301 11.005301 0 1 1 407.50703,446.44885 A 11.005301 11.005301 0 1 1 429.51763 446.44885 z" + transform="matrix(0.883732,0,0,0.900587,-349.1263,-383.0023)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + style="opacity:0.7;fill:url(#linearGradient17851);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 29.686411,20.220753 C 29.686411,25.937399 25.672358,20.884742 20.726478,20.884742 C 15.780598,20.884742 11.766545,25.937399 11.766545,20.220753 C 11.766545,14.504147 15.780598,9.8645522 20.726478,9.8645522 C 25.672358,9.8645522 29.686411,14.504147 29.686411,20.220753 z " + id="path17509" + sodipodi:nodetypes="csssc" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" /> + </g> + <g + i:rgbTrio="#4F004F00FFFF" + i:dimmedPercent="3" + i:layer="yes" + id="g17511" + transform="matrix(2.879032e-2,0,0,2.879032e-2,9.015838,152.7103)"> + <radialGradient + gradientUnits="userSpaceOnUse" + fy="783.16357" + fx="375.21439" + r="300.5752" + cy="783.16357" + cx="375.21439" + id="radialGradient17513"> + <stop + id="stop17515" + style="stop-color:#D2E8EC" + offset="0.0112" /> + <stop + id="stop17517" + style="stop-color:#60A8CE" + offset="0.2921" /> + <stop + id="stop17519" + style="stop-color:#4680A8" + offset="0.4575" /> + <stop + id="stop17521" + style="stop-color:#2F5C84" + offset="0.6767" /> + <stop + id="stop17523" + style="stop-color:#234970" + offset="0.8653" /> + <stop + id="stop17525" + style="stop-color:#1F426A" + offset="1" /> + <a:midPointStop + style="stop-color:#D2E8EC" + offset="0.0112" /> + <a:midPointStop + style="stop-color:#D2E8EC" + offset="0.5" /> + <a:midPointStop + style="stop-color:#60A8CE" + offset="0.2921" /> + <a:midPointStop + style="stop-color:#60A8CE" + offset="0.3651" /> + <a:midPointStop + style="stop-color:#1F426A" + offset="1" /> + </radialGradient> + <g + id="g17527" + i:layer="yes" + i:dimmedPercent="3" + i:rgbTrio="#4F004F00FFFF" + transform="matrix(0.925462,0,0,0.925462,22.38513,40.63705)" + style="opacity:0.18297873"> + <path + i:knockout="Off" + d="M 618.829,233.011 C 603.559,233.011 573.908,264.857 562.798,269.885 C 551.683,274.913 463.707,331.894 455.371,341.95 C 447.039,352.005 435.926,356.196 423.888,355.358 C 411.851,354.52 373.793,354.176 360.83,360.88 C 347.864,367.584 288.686,422.396 273.868,429.101 C 259.052,435.803 239.721,439.239 230.461,440.076 C 221.201,440.915 190.523,471.838 182.188,477.706 C 173.854,483.569 112.922,507.063 103.663,512.089 C 94.402,517.118 158.112,596.697 169.225,598.373 C 180.336,600.05 158.309,608.266 136.929,608.266 C 134.575,608.266 167.484,694.564 279.692,750.263 C 376.697,798.413 454.851,769.173 493.075,757.495 C 520.103,749.238 525.004,745.556 525.933,732.148 C 526.86,718.741 528.534,708.146 530.387,693.063 C 532.24,677.982 538.718,551.444 562.799,517.926 C 586.873,484.407 655.692,444.169 679.768,439.981 C 703.846,435.791 699.217,427.412 699.217,419.87 C 699.217,412.328 695.512,395.568 689.03,393.054 C 682.547,390.541 657.253,405.637 648.919,407.313 C 640.584,408.989 600.765,411.503 595.208,403.124 C 589.652,394.743 640.131,309.268 646.613,299.213 C 653.093,289.157 655.872,275.749 643.836,261.504 C 631.793,247.256 623.458,233.011 618.829,233.011 z " + id="path17529" + style="opacity:0.22000002" /> + <radialGradient + id="radialGradient17531" + cx="375.21439" + cy="783.16357" + r="300.5752" + fx="375.21439" + fy="783.16357" + gradientUnits="userSpaceOnUse"> + <stop + offset="0.0112" + style="stop-color:#D2E8EC" + id="stop17533" /> + <stop + offset="0.2921" + style="stop-color:#60A8CE" + id="stop17535" /> + <stop + offset="0.4575" + style="stop-color:#4680A8" + id="stop17537" /> + <stop + offset="0.6767" + style="stop-color:#2F5C84" + id="stop17539" /> + <stop + offset="0.8653" + style="stop-color:#234970" + id="stop17541" /> + <stop + offset="1" + style="stop-color:#1F426A" + id="stop17543" /> + <a:midPointStop + offset="0.0112" + style="stop-color:#D2E8EC" /> + <a:midPointStop + offset="0.5" + style="stop-color:#D2E8EC" /> + <a:midPointStop + offset="0.2921" + style="stop-color:#60A8CE" /> + <a:midPointStop + offset="0.3651" + style="stop-color:#60A8CE" /> + <a:midPointStop + offset="1" + style="stop-color:#1F426A" /> + </radialGradient> + <path + i:knockout="Off" + d="M 586.334,188.678 C 571.521,188.678 542.762,222.814 531.983,228.203 C 521.204,233.593 435.869,294.672 427.785,305.452 C 419.701,316.23 408.921,320.722 397.245,319.824 C 385.569,318.927 348.655,318.557 336.08,325.743 C 323.503,332.93 266.102,391.684 251.73,398.871 C 237.359,406.055 218.609,409.736 209.626,410.635 C 200.645,411.532 170.888,444.679 162.804,450.968 C 154.72,457.257 90.84,484.899 81.858,490.286 C 72.875,495.676 147.389,571.159 158.168,572.954 C 168.947,574.753 150.744,615.651 130.005,615.651 C 127.721,615.651 148.538,683.422 257.377,743.127 C 351.47,794.742 451.303,771.889 488.378,759.373 C 514.596,750.519 496.195,745.626 497.095,731.255 C 497.991,716.882 498.746,697.984 500.543,681.817 C 502.341,665.649 508.626,530.012 531.982,494.083 C 555.335,458.156 622.085,415.024 645.44,410.533 C 668.796,406.042 664.303,397.059 664.303,388.976 C 664.303,380.893 660.711,362.927 654.422,360.232 C 648.135,357.538 623.601,373.72 615.517,375.516 C 607.433,377.312 568.808,380.008 563.417,371.026 C 558.028,362.042 606.991,270.421 613.279,259.642 C 619.568,248.862 622.261,234.489 610.583,219.221 C 598.91,203.948 590.825,188.678 586.334,188.678 z " + id="path17545" + style="fill:url(#radialGradient17853)" /> + <g + id="g17547" + i:layer="yes" + i:dimmedPercent="50" + i:rgbTrio="#4F00FFFFFFFF" /> + <defs + id="defs17549" /> + <mask + maskUnits="userSpaceOnUse" + x="155.705" + y="461.543" + width="93.126" + height="44.813" + id="mask17551" + i:invertMask="false" + i:clipMask="true"> + <g + id="g17553" + style="filter:url(#Adobe_OpacityMaskFilter_1_)"> + <linearGradient + id="linearGradient17555" + gradientUnits="userSpaceOnUse" + x1="211.7222" + y1="562.85791" + x2="200.0733" + y2="466.75449"> + <stop + offset="0" + style="stop-color:#FFFFFF" + id="stop17557" /> + <stop + offset="1" + style="stop-color:#000000" + id="stop17559" /> + <a:midPointStop + offset="0" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="0.5" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="1" + style="stop-color:#000000" /> + </linearGradient> + <ellipse + i:knockout="Off" + cx="203.617" + cy="495.98801" + rx="80.542" + ry="59.682999" + id="ellipse17561" + sodipodi:cx="203.617" + sodipodi:cy="495.98801" + sodipodi:rx="80.542" + sodipodi:ry="59.682999" + style="fill:url(#XMLID_1_)" /> + </g> + </mask> + <defs + id="defs17563" /> + <mask + maskUnits="userSpaceOnUse" + x="101.57" + y="425.034" + width="156.907" + height="127.553" + id="mask17565" + i:invertMask="false" + i:clipMask="true"> + <g + id="g17567" + style="filter:url(#Adobe_OpacityMaskFilter_2_)"> + <linearGradient + id="linearGradient17569" + gradientUnits="userSpaceOnUse" + x1="206.09081" + y1="397.62061" + x2="186.92191" + y2="563.47321"> + <stop + offset="0" + style="stop-color:#FFFFFF" + id="stop17571" /> + <stop + offset="0.1111" + style="stop-color:#CBCBCB" + id="stop17573" /> + <stop + offset="0.2396" + style="stop-color:#969696" + id="stop17575" /> + <stop + offset="0.3698" + style="stop-color:#686868" + id="stop17577" /> + <stop + offset="0.4991" + style="stop-color:#424242" + id="stop17579" /> + <stop + offset="0.6273" + style="stop-color:#252525" + id="stop17581" /> + <stop + offset="0.7542" + style="stop-color:#111111" + id="stop17583" /> + <stop + offset="0.8792" + style="stop-color:#040404" + id="stop17585" /> + <stop + offset="1" + style="stop-color:#000000" + id="stop17587" /> + <a:midPointStop + offset="0" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="0.2994" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="1" + style="stop-color:#000000" /> + </linearGradient> + <ellipse + i:knockout="Off" + cx="194.76401" + cy="495.62299" + rx="160.94" + ry="123.604" + id="ellipse17589" + sodipodi:cx="194.76401" + sodipodi:cy="495.62299" + sodipodi:rx="160.94" + sodipodi:ry="123.604" + style="fill:url(#XMLID_3_)" /> + </g> + </mask> + <linearGradient + id="linearGradient17591" + gradientUnits="userSpaceOnUse" + x1="429.84909" + y1="815.45459" + x2="480.12021" + y2="643.30768" + gradientTransform="matrix(-1,0,0,1,951.2744,0)"> + <stop + offset="0" + style="stop-color:#FFFFFF" + id="stop17593" /> + <stop + offset="1" + style="stop-color:#E5EAF2" + id="stop17595" /> + <a:midPointStop + offset="0" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="0.5" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="1" + style="stop-color:#E5EAF2" /> + </linearGradient> + <path + i:knockout="Off" + d="M 497.013,546.945 C 497.013,546.945 465.169,655.349 464.364,657.024 C 459.813,666.482 451.419,693.889 454.309,702.74 C 457.712,713.167 470.467,728.327 476.42,735.907 C 482.373,743.49 484.924,747.28 483.223,750.125 C 481.523,752.968 459.412,752.968 459.412,752.968 C 459.412,752.968 442.405,727.38 439.001,717.903 C 435.601,708.428 433.899,688.527 437.302,681.895 C 440.703,675.26 497.013,546.945 497.013,546.945 z " + id="path17597" + style="opacity:0.18000004;fill:url(#linearGradient17855)" /> + <linearGradient + id="linearGradient17599" + gradientUnits="userSpaceOnUse" + x1="220.9512" + y1="634.63232" + x2="278.14471" + y2="560.35498"> + <stop + offset="0" + style="stop-color:#FFFFFF" + id="stop17601" /> + <stop + offset="1" + style="stop-color:#193D6B" + id="stop17603" /> + <a:midPointStop + offset="0" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="0.5" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="1" + style="stop-color:#193D6B" /> + </linearGradient> + <polygon + i:knockout="Off" + a:adobe-blending-mode="lighten" + points="272.565,567.14 214.39,617.501 287.326,574.086 272.565,567.14 " + id="polygon17605" + style="opacity:0.65;fill:url(#linearGradient17857)" /> + <linearGradient + id="linearGradient17607" + gradientUnits="userSpaceOnUse" + x1="237.20799" + y1="684.01898" + x2="328.18469" + y2="565.86761"> + <stop + offset="0" + style="stop-color:#FFFFFF" + id="stop17609" /> + <stop + offset="1" + style="stop-color:#193D6B" + id="stop17611" /> + <a:midPointStop + offset="0" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="0.5" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="1" + style="stop-color:#193D6B" /> + </linearGradient> + <polygon + i:knockout="Off" + a:adobe-blending-mode="lighten" + points="329.873,549.776 230.887,678.282 344.633,558.457 329.873,549.776 " + id="polygon17613" + style="opacity:0.65;fill:url(#linearGradient17859)" /> + <linearGradient + id="linearGradient17615" + gradientUnits="userSpaceOnUse" + x1="264.5405" + y1="704.23578" + x2="335.09619" + y2="612.60522"> + <stop + offset="0" + style="stop-color:#FFFFFF" + id="stop17617" /> + <stop + offset="1" + style="stop-color:#193D6B" + id="stop17619" /> + <a:midPointStop + offset="0" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="0.5" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="1" + style="stop-color:#193D6B" /> + </linearGradient> + <polygon + i:knockout="Off" + a:adobe-blending-mode="lighten" + points="337.688,594.058 262.146,713.882 355.053,601.872 337.688,594.058 " + id="polygon17621" + style="opacity:0.65;fill:url(#linearGradient17861)" /> + <linearGradient + id="linearGradient17623" + gradientUnits="userSpaceOnUse" + x1="319.186" + y1="713.54639" + x2="373.12509" + y2="626.00598"> + <stop + offset="0" + style="stop-color:#FFFFFF" + id="stop17625" /> + <stop + offset="1" + style="stop-color:#193D6B" + id="stop17627" /> + <a:midPointStop + offset="0" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="0.5" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="1" + style="stop-color:#193D6B" /> + </linearGradient> + <polygon + i:knockout="Off" + a:adobe-blending-mode="lighten" + points="381.97,588.847 318.584,737.327 402.809,593.19 381.97,588.847 " + id="polygon17629" + style="opacity:0.65;fill:url(#linearGradient17863)" /> + <linearGradient + id="linearGradient17631" + gradientUnits="userSpaceOnUse" + x1="331.49609" + y1="790.51508" + x2="409.85471" + y2="688.75092"> + <stop + offset="0" + style="stop-color:#FFFFFF" + id="stop17633" /> + <stop + offset="1" + style="stop-color:#193D6B" + id="stop17635" /> + <a:midPointStop + offset="0" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="0.5" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="1" + style="stop-color:#193D6B" /> + </linearGradient> + <polygon + i:knockout="Off" + a:adobe-blending-mode="lighten" + points="407.151,627.921 401.94,763.375 424.515,629.658 407.151,627.921 " + id="polygon17637" + style="opacity:0.65;fill:url(#linearGradient17865)" /> + <defs + id="defs17639" /> + <mask + maskUnits="userSpaceOnUse" + x="191.541" + y="217.631" + width="447.197" + height="527.73" + id="mask17641" + i:invertMask="false" + i:clipMask="true"> + <g + id="g17643" + style="filter:url(#Adobe_OpacityMaskFilter_3_)"> + <radialGradient + id="radialGradient17645" + cx="657.2124" + cy="155.23289" + r="606.95343" + fx="657.2124" + fy="155.23289" + gradientTransform="matrix(0.931,0,0,0.9299,28.7459,21.6629)" + gradientUnits="userSpaceOnUse"> + <stop + offset="0" + style="stop-color:#C9C9C8" + id="stop17647" /> + <stop + offset="0.0881" + style="stop-color:#C4C4C4" + id="stop17649" /> + <stop + offset="0.2192" + style="stop-color:#B8B8B7" + id="stop17651" /> + <stop + offset="0.3769" + style="stop-color:#A1A0A0" + id="stop17653" /> + <stop + offset="0.5556" + style="stop-color:#7F7F7E" + id="stop17655" /> + <stop + offset="0.7517" + style="stop-color:#4F4F4E" + id="stop17657" /> + <stop + offset="0.9595" + style="stop-color:#121212" + id="stop17659" /> + <stop + offset="1" + style="stop-color:#000000" + id="stop17661" /> + <a:midPointStop + offset="0" + style="stop-color:#C9C9C8" /> + <a:midPointStop + offset="0.6215" + style="stop-color:#C9C9C8" /> + <a:midPointStop + offset="1" + style="stop-color:#000000" /> + </radialGradient> + <circle + i:knockout="Off" + cx="408.427" + cy="444.22601" + r="302.95401" + id="circle17663" + sodipodi:cx="408.427" + sodipodi:cy="444.22601" + sodipodi:rx="302.95401" + sodipodi:ry="302.95401" + style="fill:url(#XMLID_11_)" /> + </g> + </mask> + <linearGradient + id="linearGradient17665" + gradientUnits="userSpaceOnUse" + x1="517.18988" + y1="239.939" + x2="410.27759" + y2="522.02393"> + <stop + offset="0" + style="stop-color:#FFFFFF" + id="stop17667" /> + <stop + offset="0.9944" + style="stop-color:#FFFFFF" + id="stop17669" /> + <a:midPointStop + offset="0" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="0.6215" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="0.9944" + style="stop-color:#FFFFFF" /> + </linearGradient> + <path + i:knockout="Off" + mask="url(#XMLID_10_)" + d="M 566.303,218.317 C 607.373,264.655 592.756,249.235 589.541,263.89 C 586.325,278.548 512.629,401.164 528.704,407.028 C 544.779,412.89 567.565,414.686 586.053,402.96 C 604.542,391.235 633.438,382.73 638.259,393.721 C 643.083,404.713 610.113,404.175 599.664,409.305 C 589.215,414.434 525.003,467.462 508.928,487.247 C 492.851,507.035 435.463,593.164 429.836,628.34 C 424.207,663.514 418.582,703.819 418.582,722.87 C 418.582,741.923 384.824,749.254 342.222,743.389 C 299.62,737.529 232.901,685.496 198.338,645.192 C 163.776,604.887 271.922,544.985 278.354,523.733 C 284.786,502.481 384.553,419.303 412.542,403.68 C 439.074,388.87 554.529,205.029 566.303,218.317 z " + id="path17671" + style="opacity:0.47999998;fill:url(#linearGradient17867)" /> + <defs + id="defs17673" /> + <mask + maskUnits="userSpaceOnUse" + x="243.061" + y="279.317" + width="252.506" + height="187.183" + id="mask17675" + i:invertMask="false" + i:clipMask="true"> + <g + id="g17677" + style="filter:url(#Adobe_OpacityMaskFilter_4_)"> + <linearGradient + id="linearGradient17679" + gradientUnits="userSpaceOnUse" + x1="340.0488" + y1="266.93359" + x2="372.38071" + y2="417.81519"> + <stop + offset="0" + style="stop-color:#FFFFFF" + id="stop17681" /> + <stop + offset="0.0867" + style="stop-color:#F8F8F8" + id="stop17683" /> + <stop + offset="0.2156" + style="stop-color:#E4E4E4" + id="stop17685" /> + <stop + offset="0.3708" + style="stop-color:#C2C2C2" + id="stop17687" /> + <stop + offset="0.5465" + style="stop-color:#949494" + id="stop17689" /> + <stop + offset="0.7394" + style="stop-color:#595959" + id="stop17691" /> + <stop + offset="0.9438" + style="stop-color:#151515" + id="stop17693" /> + <stop + offset="0.9944" + style="stop-color:#000000" + id="stop17695" /> + <a:midPointStop + offset="0" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="0.6215" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="0.9944" + style="stop-color:#000000" /> + </linearGradient> + <ellipse + i:knockout="Off" + cx="363.052" + cy="374.28201" + rx="215.32401" + ry="183.32201" + id="ellipse17697" + sodipodi:cx="363.052" + sodipodi:cy="374.28201" + sodipodi:rx="215.32401" + sodipodi:ry="183.32201" + style="fill:url(#XMLID_14_)" /> + </g> + </mask> + <path + i:knockout="Off" + mask="url(#XMLID_13_)" + d="M 495.251,279.317 C 495.251,279.317 427.161,328.038 418.318,330.515 C 409.476,332.993 347.575,331.341 341.385,334.644 C 335.195,337.947 270.642,408.964 256.494,411.441 C 242.346,413.918 237.924,446.949 250.304,462.639 C 262.684,478.329 307.782,441.994 311.32,428.783 C 314.858,415.571 322.816,377.585 338.733,366.85 C 354.65,356.115 396.212,343.728 405.939,345.38 C 415.666,347.031 471.376,302.439 471.376,302.439 C 471.376,302.439 498.79,285.924 495.251,279.317 z " + id="path17699" + style="opacity:0.47999998;fill:#ffffff" /> + </g> + <path + style="fill:none;stroke:url(#linearGradient17869);stroke-width:26.21903229;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path17701" + d="M 481.10291,750.63818 C 504.60991,742.43859 484.89535,736.14215 485.70505,723.21312 C 486.51114,710.28229 486.11667,685.76446 487.73336,671.21963 C 489.35095,656.67391 495.00532,534.64658 516.0178,502.32266 C 537.02759,470.00053 597.07997,431.19635 618.09155,427.15597 C 639.10404,423.1156 635.06186,415.03394 635.06186,407.76198 C 635.06186,400.49002 631.83028,384.32671 626.17231,381.90212 C 620.51614,379.47843 598.44385,394.03675 591.17099,395.65254 C 583.89813,397.26833 549.14872,399.69382 544.29865,391.61306 C 539.45038,383.53051 583.50048,301.10266 589.15755,291.40521 C 594.81552,281.70687 597.23831,268.77604 586.73207,255.04001 C 576.23032,241.29949 568.95656,227.56166 564.91618,227.56166 C 551.5895,227.56166 525.71615,258.27249 516.0187,263.12077 C 506.32126,267.96994 429.54868,322.92034 422.27582,332.61869 C 415.00296,342.31523 405.30461,346.35651 394.80017,345.54862 C 384.29572,344.74162 351.08563,344.40875 339.7724,350.87371 C 328.45736,357.33957 276.81591,410.19826 263.88598,416.66413 C 250.95695,423.12729 234.08831,426.43894 226.00665,427.24774 C 217.92679,428.05474 191.15558,457.8758 183.88272,463.53377 C 176.60986,469.19174 119.1395,494.06017 111.05874,498.90665 C 102.97709,503.75582 170.01444,571.66494 179.71189,573.27983 C 189.40934,574.89832 129.82914,605.28739 137.19522,622.42986" + i:knockout="Off" + sodipodi:nodetypes="csssssssssscssssssssss" /> + <path + style="fill:url(#linearGradient17871);fill-opacity:1" + id="path17703" + d="M 412.32879,379.97694 C 412.32879,379.97694 375.86296,374.36555 354.82231,395.40722 C 333.78063,416.44789 315.54259,462.73569 316.94953,476.76314 C 318.35033,490.79058 360.43266,429.07146 378.66558,424.86497 C 396.90259,420.6554 416.53938,389.79379 412.32879,379.97694 z " + i:knockout="Off" /> + <g + i:rgbTrio="#4F00FFFFFFFF" + i:dimmedPercent="50" + i:layer="yes" + id="g17705" /> + <defs + id="defs17707" /> + <mask + i:clipMask="true" + i:invertMask="false" + id="mask17709" + height="44.813" + width="93.126" + y="461.543" + x="155.705" + maskUnits="userSpaceOnUse"> + <g + id="g17711" + style="filter:url(#Adobe_OpacityMaskFilter_1_)"> + <linearGradient + y2="466.75449" + x2="200.0733" + y1="562.85791" + x1="211.7222" + gradientUnits="userSpaceOnUse" + id="linearGradient17713"> + <stop + id="stop17715" + style="stop-color:#FFFFFF" + offset="0" /> + <stop + id="stop17717" + style="stop-color:#000000" + offset="1" /> + <a:midPointStop + style="stop-color:#FFFFFF" + offset="0" /> + <a:midPointStop + style="stop-color:#FFFFFF" + offset="0.5" /> + <a:midPointStop + style="stop-color:#000000" + offset="1" /> + </linearGradient> + <ellipse + style="fill:url(#XMLID_1_)" + sodipodi:ry="59.682999" + sodipodi:rx="80.542" + sodipodi:cy="495.98801" + sodipodi:cx="203.617" + id="ellipse17719" + ry="59.682999" + rx="80.542" + cy="495.98801" + cx="203.617" + i:knockout="Off" /> + </g> + </mask> + <path + style="fill:url(#linearGradient17873);fill-opacity:1" + id="path17721" + d="M 281.37786,477.99374 C 281.37786,477.99374 244.36221,474.50936 232.90412,477.99374 C 221.44514,481.47543 199.41154,501.50549 197.64911,509.34333 C 195.88577,517.17848 238.19143,518.05025 248.76695,515.43674 C 259.34336,512.82592 278.73556,487.57064 281.37786,477.99374 z " + mask="url(#XMLID_26_)" + i:knockout="Off" /> + <defs + id="defs17723" /> + <mask + i:clipMask="true" + i:invertMask="false" + id="mask17725" + height="127.553" + width="156.907" + y="425.034" + x="101.57" + maskUnits="userSpaceOnUse"> + <g + id="g17727" + style="filter:url(#Adobe_OpacityMaskFilter_2_)"> + <linearGradient + y2="563.47321" + x2="186.92191" + y1="397.62061" + x1="206.09081" + gradientUnits="userSpaceOnUse" + id="linearGradient17729"> + <stop + id="stop17731" + style="stop-color:#FFFFFF" + offset="0" /> + <stop + id="stop17733" + style="stop-color:#CBCBCB" + offset="0.1111" /> + <stop + id="stop17735" + style="stop-color:#969696" + offset="0.2396" /> + <stop + id="stop17737" + style="stop-color:#686868" + offset="0.3698" /> + <stop + id="stop17739" + style="stop-color:#424242" + offset="0.4991" /> + <stop + id="stop17741" + style="stop-color:#252525" + offset="0.6273" /> + <stop + id="stop17743" + style="stop-color:#111111" + offset="0.7542" /> + <stop + id="stop17745" + style="stop-color:#040404" + offset="0.8792" /> + <stop + id="stop17747" + style="stop-color:#000000" + offset="1" /> + <a:midPointStop + style="stop-color:#FFFFFF" + offset="0" /> + <a:midPointStop + style="stop-color:#FFFFFF" + offset="0.2994" /> + <a:midPointStop + style="stop-color:#000000" + offset="1" /> + </linearGradient> + <ellipse + style="fill:url(#XMLID_3_)" + sodipodi:ry="123.604" + sodipodi:rx="160.94" + sodipodi:cy="495.62299" + sodipodi:cx="194.76401" + id="ellipse17749" + ry="123.604" + rx="160.94" + cy="495.62299" + cx="194.76401" + i:knockout="Off" /> + </g> + </mask> + <path + style="fill:url(#linearGradient17875);fill-opacity:1" + id="path17751" + d="M 264.39264,457.23757 C 264.39264,457.23757 248.74502,449.20288 242.34258,450.5424 C 235.94257,451.88111 162.5424,503.92155 155.43109,507.93608 C 145.58106,513.50061 166.41337,529.61436 172.78919,535.94906 C 176.51582,539.65148 196.50296,555.63297 202.1933,552.95716 C 207.88364,550.27731 218.15866,545.6144 228.11755,543.60472 C 238.07564,541.59584 280.81526,517.27562 279.32897,506.77962 C 278.36042,499.93526 243.69985,528.9442 234.43128,529.27888 C 216.00946,529.94581 184.72874,524.8579 181.17147,516.82481 C 177.61502,508.7885 187.76262,494.32799 193.26425,490.71426 C 229.47159,466.92308 264.39264,457.23757 264.39264,457.23757 z " + mask="url(#XMLID_2_)" + i:knockout="Off" /> + <linearGradient + gradientTransform="matrix(-1,0,0,1,951.2744,0)" + y2="643.30768" + x2="480.12021" + y1="815.45459" + x1="429.84909" + gradientUnits="userSpaceOnUse" + id="linearGradient17753"> + <stop + id="stop17755" + style="stop-color:#FFFFFF" + offset="0" /> + <stop + id="stop17757" + style="stop-color:#E5EAF2" + offset="1" /> + <a:midPointStop + style="stop-color:#FFFFFF" + offset="0" /> + <a:midPointStop + style="stop-color:#FFFFFF" + offset="0.5" /> + <a:midPointStop + style="stop-color:#E5EAF2" + offset="1" /> + </linearGradient> + <linearGradient + y2="560.35498" + x2="278.14471" + y1="634.63232" + x1="220.9512" + gradientUnits="userSpaceOnUse" + id="linearGradient17759"> + <stop + id="stop17761" + style="stop-color:#FFFFFF" + offset="0" /> + <stop + id="stop17763" + style="stop-color:#193D6B" + offset="1" /> + <a:midPointStop + style="stop-color:#FFFFFF" + offset="0" /> + <a:midPointStop + style="stop-color:#FFFFFF" + offset="0.5" /> + <a:midPointStop + style="stop-color:#193D6B" + offset="1" /> + </linearGradient> + <linearGradient + y2="565.86761" + x2="328.18469" + y1="684.01898" + x1="237.20799" + gradientUnits="userSpaceOnUse" + id="linearGradient17765"> + <stop + id="stop17767" + style="stop-color:#FFFFFF" + offset="0" /> + <stop + id="stop17769" + style="stop-color:#193D6B" + offset="1" /> + <a:midPointStop + style="stop-color:#FFFFFF" + offset="0" /> + <a:midPointStop + style="stop-color:#FFFFFF" + offset="0.5" /> + <a:midPointStop + style="stop-color:#193D6B" + offset="1" /> + </linearGradient> + <linearGradient + y2="612.60522" + x2="335.09619" + y1="704.23578" + x1="264.5405" + gradientUnits="userSpaceOnUse" + id="linearGradient17771"> + <stop + id="stop17773" + style="stop-color:#FFFFFF" + offset="0" /> + <stop + id="stop17775" + style="stop-color:#193D6B" + offset="1" /> + <a:midPointStop + style="stop-color:#FFFFFF" + offset="0" /> + <a:midPointStop + style="stop-color:#FFFFFF" + offset="0.5" /> + <a:midPointStop + style="stop-color:#193D6B" + offset="1" /> + </linearGradient> + <linearGradient + y2="626.00598" + x2="373.12509" + y1="713.54639" + x1="319.186" + gradientUnits="userSpaceOnUse" + id="linearGradient17777"> + <stop + id="stop17779" + style="stop-color:#FFFFFF" + offset="0" /> + <stop + id="stop17781" + style="stop-color:#193D6B" + offset="1" /> + <a:midPointStop + style="stop-color:#FFFFFF" + offset="0" /> + <a:midPointStop + style="stop-color:#FFFFFF" + offset="0.5" /> + <a:midPointStop + style="stop-color:#193D6B" + offset="1" /> + </linearGradient> + <linearGradient + y2="688.75092" + x2="409.85471" + y1="790.51508" + x1="331.49609" + gradientUnits="userSpaceOnUse" + id="linearGradient17783"> + <stop + id="stop17785" + style="stop-color:#FFFFFF" + offset="0" /> + <stop + id="stop17787" + style="stop-color:#193D6B" + offset="1" /> + <a:midPointStop + style="stop-color:#FFFFFF" + offset="0" /> + <a:midPointStop + style="stop-color:#FFFFFF" + offset="0.5" /> + <a:midPointStop + style="stop-color:#193D6B" + offset="1" /> + </linearGradient> + <defs + id="defs17789" /> + <mask + i:clipMask="true" + i:invertMask="false" + id="mask17791" + height="527.73" + width="447.197" + y="217.631" + x="191.541" + maskUnits="userSpaceOnUse"> + <g + id="g17793" + style="filter:url(#Adobe_OpacityMaskFilter_3_)"> + <radialGradient + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.931,0,0,0.9299,28.7459,21.6629)" + fy="155.23289" + fx="657.2124" + r="606.95343" + cy="155.23289" + cx="657.2124" + id="radialGradient17795"> + <stop + id="stop17797" + style="stop-color:#C9C9C8" + offset="0" /> + <stop + id="stop17799" + style="stop-color:#C4C4C4" + offset="0.0881" /> + <stop + id="stop17801" + style="stop-color:#B8B8B7" + offset="0.2192" /> + <stop + id="stop17803" + style="stop-color:#A1A0A0" + offset="0.3769" /> + <stop + id="stop17805" + style="stop-color:#7F7F7E" + offset="0.5556" /> + <stop + id="stop17807" + style="stop-color:#4F4F4E" + offset="0.7517" /> + <stop + id="stop17809" + style="stop-color:#121212" + offset="0.9595" /> + <stop + id="stop17811" + style="stop-color:#000000" + offset="1" /> + <a:midPointStop + style="stop-color:#C9C9C8" + offset="0" /> + <a:midPointStop + style="stop-color:#C9C9C8" + offset="0.6215" /> + <a:midPointStop + style="stop-color:#000000" + offset="1" /> + </radialGradient> + <circle + style="fill:url(#XMLID_11_)" + sodipodi:ry="302.95401" + sodipodi:rx="302.95401" + sodipodi:cy="444.22601" + sodipodi:cx="408.427" + id="circle17813" + r="302.95401" + cy="444.22601" + cx="408.427" + i:knockout="Off" /> + </g> + </mask> + <linearGradient + y2="522.02393" + x2="410.27759" + y1="239.939" + x1="517.18988" + gradientUnits="userSpaceOnUse" + id="linearGradient17815"> + <stop + id="stop17817" + style="stop-color:#FFFFFF" + offset="0" /> + <stop + id="stop17819" + style="stop-color:#FFFFFF" + offset="0.9944" /> + <a:midPointStop + style="stop-color:#FFFFFF" + offset="0" /> + <a:midPointStop + style="stop-color:#FFFFFF" + offset="0.6215" /> + <a:midPointStop + style="stop-color:#FFFFFF" + offset="0.9944" /> + </linearGradient> + <defs + id="defs17821" /> + <mask + i:clipMask="true" + i:invertMask="false" + id="mask17823" + height="187.183" + width="252.506" + y="279.317" + x="243.061" + maskUnits="userSpaceOnUse"> + <g + id="g17825" + style="filter:url(#Adobe_OpacityMaskFilter_4_)"> + <linearGradient + y2="417.81519" + x2="372.38071" + y1="266.93359" + x1="340.0488" + gradientUnits="userSpaceOnUse" + id="linearGradient17827"> + <stop + id="stop17829" + style="stop-color:#FFFFFF" + offset="0" /> + <stop + id="stop17831" + style="stop-color:#F8F8F8" + offset="0.0867" /> + <stop + id="stop17833" + style="stop-color:#E4E4E4" + offset="0.2156" /> + <stop + id="stop17835" + style="stop-color:#C2C2C2" + offset="0.3708" /> + <stop + id="stop17837" + style="stop-color:#949494" + offset="0.5465" /> + <stop + id="stop17839" + style="stop-color:#595959" + offset="0.7394" /> + <stop + id="stop17841" + style="stop-color:#151515" + offset="0.9438" /> + <stop + id="stop17843" + style="stop-color:#000000" + offset="0.9944" /> + <a:midPointStop + style="stop-color:#FFFFFF" + offset="0" /> + <a:midPointStop + style="stop-color:#FFFFFF" + offset="0.6215" /> + <a:midPointStop + style="stop-color:#000000" + offset="0.9944" /> + </linearGradient> + <ellipse + style="fill:url(#XMLID_14_)" + sodipodi:ry="183.32201" + sodipodi:rx="215.32401" + sodipodi:cy="374.28201" + sodipodi:cx="363.052" + id="ellipse17845" + ry="183.32201" + rx="215.32401" + cy="374.28201" + cx="363.052" + i:knockout="Off" /> + </g> + </mask> + </g> + </g> + <g + transform="matrix(2.293923,0,0,2.29392,1155.551,-139.70497)" + id="g17877" + style="display:inline" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/48x48/actions/amarok_settings_general.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/vadim/collection.png" + transform="translate(0.141718,145.4379)" + id="g17879"> + <path + sodipodi:type="arc" + style="opacity:1;fill:url(#linearGradient18223);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path17881" + sodipodi:cx="418.94757" + sodipodi:cy="444.83228" + sodipodi:rx="9.8239412" + sodipodi:ry="9.8239412" + d="M 428.77151 444.83228 A 9.8239412 9.8239412 0 1 1 409.12363,444.83228 A 9.8239412 9.8239412 0 1 1 428.77151 444.83228 z" + transform="matrix(1.064993,0,0,1.064994,-425.4498,-454.68)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:type="arc" + style="opacity:1;fill:url(#radialGradient18225);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path17883" + sodipodi:cx="418.51233" + sodipodi:cy="446.44885" + sodipodi:rx="11.005301" + sodipodi:ry="11.005301" + d="M 429.51763 446.44885 A 11.005301 11.005301 0 1 1 407.50703,446.44885 A 11.005301 11.005301 0 1 1 429.51763 446.44885 z" + transform="matrix(0.883732,0,0,0.900587,-349.1263,-383.0023)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + style="opacity:0.7;fill:url(#linearGradient18227);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 29.686411,20.220753 C 29.686411,25.937399 25.672358,20.884742 20.726478,20.884742 C 15.780598,20.884742 11.766545,25.937399 11.766545,20.220753 C 11.766545,14.504147 15.780598,9.8645522 20.726478,9.8645522 C 25.672358,9.8645522 29.686411,14.504147 29.686411,20.220753 z " + id="path17885" + sodipodi:nodetypes="csssc" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" /> + </g> + <g + i:rgbTrio="#4F004F00FFFF" + i:dimmedPercent="3" + i:layer="yes" + id="g17887" + transform="matrix(2.879032e-2,0,0,2.879032e-2,9.015838,152.7103)"> + <radialGradient + gradientUnits="userSpaceOnUse" + fy="783.16357" + fx="375.21439" + r="300.5752" + cy="783.16357" + cx="375.21439" + id="radialGradient17889"> + <stop + id="stop17891" + style="stop-color:#D2E8EC" + offset="0.0112" /> + <stop + id="stop17893" + style="stop-color:#60A8CE" + offset="0.2921" /> + <stop + id="stop17895" + style="stop-color:#4680A8" + offset="0.4575" /> + <stop + id="stop17897" + style="stop-color:#2F5C84" + offset="0.6767" /> + <stop + id="stop17899" + style="stop-color:#234970" + offset="0.8653" /> + <stop + id="stop17901" + style="stop-color:#1F426A" + offset="1" /> + <a:midPointStop + style="stop-color:#D2E8EC" + offset="0.0112" /> + <a:midPointStop + style="stop-color:#D2E8EC" + offset="0.5" /> + <a:midPointStop + style="stop-color:#60A8CE" + offset="0.2921" /> + <a:midPointStop + style="stop-color:#60A8CE" + offset="0.3651" /> + <a:midPointStop + style="stop-color:#1F426A" + offset="1" /> + </radialGradient> + <g + id="g17903" + i:layer="yes" + i:dimmedPercent="3" + i:rgbTrio="#4F004F00FFFF" + transform="matrix(0.925462,0,0,0.925462,22.38513,40.63705)" + style="opacity:0.18297873"> + <path + i:knockout="Off" + d="M 618.829,233.011 C 603.559,233.011 573.908,264.857 562.798,269.885 C 551.683,274.913 463.707,331.894 455.371,341.95 C 447.039,352.005 435.926,356.196 423.888,355.358 C 411.851,354.52 373.793,354.176 360.83,360.88 C 347.864,367.584 288.686,422.396 273.868,429.101 C 259.052,435.803 239.721,439.239 230.461,440.076 C 221.201,440.915 190.523,471.838 182.188,477.706 C 173.854,483.569 112.922,507.063 103.663,512.089 C 94.402,517.118 158.112,596.697 169.225,598.373 C 180.336,600.05 158.309,608.266 136.929,608.266 C 134.575,608.266 167.484,694.564 279.692,750.263 C 376.697,798.413 454.851,769.173 493.075,757.495 C 520.103,749.238 525.004,745.556 525.933,732.148 C 526.86,718.741 528.534,708.146 530.387,693.063 C 532.24,677.982 538.718,551.444 562.799,517.926 C 586.873,484.407 655.692,444.169 679.768,439.981 C 703.846,435.791 699.217,427.412 699.217,419.87 C 699.217,412.328 695.512,395.568 689.03,393.054 C 682.547,390.541 657.253,405.637 648.919,407.313 C 640.584,408.989 600.765,411.503 595.208,403.124 C 589.652,394.743 640.131,309.268 646.613,299.213 C 653.093,289.157 655.872,275.749 643.836,261.504 C 631.793,247.256 623.458,233.011 618.829,233.011 z " + id="path17905" + style="opacity:0.22000002" /> + <radialGradient + id="radialGradient17907" + cx="375.21439" + cy="783.16357" + r="300.5752" + fx="375.21439" + fy="783.16357" + gradientUnits="userSpaceOnUse"> + <stop + offset="0.0112" + style="stop-color:#D2E8EC" + id="stop17909" /> + <stop + offset="0.2921" + style="stop-color:#60A8CE" + id="stop17911" /> + <stop + offset="0.4575" + style="stop-color:#4680A8" + id="stop17913" /> + <stop + offset="0.6767" + style="stop-color:#2F5C84" + id="stop17915" /> + <stop + offset="0.8653" + style="stop-color:#234970" + id="stop17917" /> + <stop + offset="1" + style="stop-color:#1F426A" + id="stop17919" /> + <a:midPointStop + offset="0.0112" + style="stop-color:#D2E8EC" /> + <a:midPointStop + offset="0.5" + style="stop-color:#D2E8EC" /> + <a:midPointStop + offset="0.2921" + style="stop-color:#60A8CE" /> + <a:midPointStop + offset="0.3651" + style="stop-color:#60A8CE" /> + <a:midPointStop + offset="1" + style="stop-color:#1F426A" /> + </radialGradient> + <path + i:knockout="Off" + d="M 586.334,188.678 C 571.521,188.678 542.762,222.814 531.983,228.203 C 521.204,233.593 435.869,294.672 427.785,305.452 C 419.701,316.23 408.921,320.722 397.245,319.824 C 385.569,318.927 348.655,318.557 336.08,325.743 C 323.503,332.93 266.102,391.684 251.73,398.871 C 237.359,406.055 218.609,409.736 209.626,410.635 C 200.645,411.532 170.888,444.679 162.804,450.968 C 154.72,457.257 90.84,484.899 81.858,490.286 C 72.875,495.676 147.389,571.159 158.168,572.954 C 168.947,574.753 150.744,615.651 130.005,615.651 C 127.721,615.651 148.538,683.422 257.377,743.127 C 351.47,794.742 451.303,771.889 488.378,759.373 C 514.596,750.519 496.195,745.626 497.095,731.255 C 497.991,716.882 498.746,697.984 500.543,681.817 C 502.341,665.649 508.626,530.012 531.982,494.083 C 555.335,458.156 622.085,415.024 645.44,410.533 C 668.796,406.042 664.303,397.059 664.303,388.976 C 664.303,380.893 660.711,362.927 654.422,360.232 C 648.135,357.538 623.601,373.72 615.517,375.516 C 607.433,377.312 568.808,380.008 563.417,371.026 C 558.028,362.042 606.991,270.421 613.279,259.642 C 619.568,248.862 622.261,234.489 610.583,219.221 C 598.91,203.948 590.825,188.678 586.334,188.678 z " + id="path17921" + style="fill:url(#radialGradient18229)" /> + <g + id="g17923" + i:layer="yes" + i:dimmedPercent="50" + i:rgbTrio="#4F00FFFFFFFF" /> + <defs + id="defs17925" /> + <mask + maskUnits="userSpaceOnUse" + x="155.705" + y="461.543" + width="93.126" + height="44.813" + id="mask17927" + i:invertMask="false" + i:clipMask="true"> + <g + id="g17929" + style="filter:url(#Adobe_OpacityMaskFilter_1_)"> + <linearGradient + id="linearGradient17931" + gradientUnits="userSpaceOnUse" + x1="211.7222" + y1="562.85791" + x2="200.0733" + y2="466.75449"> + <stop + offset="0" + style="stop-color:#FFFFFF" + id="stop17933" /> + <stop + offset="1" + style="stop-color:#000000" + id="stop17935" /> + <a:midPointStop + offset="0" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="0.5" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="1" + style="stop-color:#000000" /> + </linearGradient> + <ellipse + i:knockout="Off" + cx="203.617" + cy="495.98801" + rx="80.542" + ry="59.682999" + id="ellipse17937" + sodipodi:cx="203.617" + sodipodi:cy="495.98801" + sodipodi:rx="80.542" + sodipodi:ry="59.682999" + style="fill:url(#XMLID_1_)" /> + </g> + </mask> + <defs + id="defs17939" /> + <mask + maskUnits="userSpaceOnUse" + x="101.57" + y="425.034" + width="156.907" + height="127.553" + id="mask17941" + i:invertMask="false" + i:clipMask="true"> + <g + id="g17943" + style="filter:url(#Adobe_OpacityMaskFilter_2_)"> + <linearGradient + id="linearGradient17945" + gradientUnits="userSpaceOnUse" + x1="206.09081" + y1="397.62061" + x2="186.92191" + y2="563.47321"> + <stop + offset="0" + style="stop-color:#FFFFFF" + id="stop17947" /> + <stop + offset="0.1111" + style="stop-color:#CBCBCB" + id="stop17949" /> + <stop + offset="0.2396" + style="stop-color:#969696" + id="stop17951" /> + <stop + offset="0.3698" + style="stop-color:#686868" + id="stop17953" /> + <stop + offset="0.4991" + style="stop-color:#424242" + id="stop17955" /> + <stop + offset="0.6273" + style="stop-color:#252525" + id="stop17957" /> + <stop + offset="0.7542" + style="stop-color:#111111" + id="stop17959" /> + <stop + offset="0.8792" + style="stop-color:#040404" + id="stop17961" /> + <stop + offset="1" + style="stop-color:#000000" + id="stop17963" /> + <a:midPointStop + offset="0" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="0.2994" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="1" + style="stop-color:#000000" /> + </linearGradient> + <ellipse + i:knockout="Off" + cx="194.76401" + cy="495.62299" + rx="160.94" + ry="123.604" + id="ellipse17965" + sodipodi:cx="194.76401" + sodipodi:cy="495.62299" + sodipodi:rx="160.94" + sodipodi:ry="123.604" + style="fill:url(#XMLID_3_)" /> + </g> + </mask> + <linearGradient + id="linearGradient17967" + gradientUnits="userSpaceOnUse" + x1="429.84909" + y1="815.45459" + x2="480.12021" + y2="643.30768" + gradientTransform="matrix(-1,0,0,1,951.2744,0)"> + <stop + offset="0" + style="stop-color:#FFFFFF" + id="stop17969" /> + <stop + offset="1" + style="stop-color:#E5EAF2" + id="stop17971" /> + <a:midPointStop + offset="0" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="0.5" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="1" + style="stop-color:#E5EAF2" /> + </linearGradient> + <path + i:knockout="Off" + d="M 497.013,546.945 C 497.013,546.945 465.169,655.349 464.364,657.024 C 459.813,666.482 451.419,693.889 454.309,702.74 C 457.712,713.167 470.467,728.327 476.42,735.907 C 482.373,743.49 484.924,747.28 483.223,750.125 C 481.523,752.968 459.412,752.968 459.412,752.968 C 459.412,752.968 442.405,727.38 439.001,717.903 C 435.601,708.428 433.899,688.527 437.302,681.895 C 440.703,675.26 497.013,546.945 497.013,546.945 z " + id="path17973" + style="opacity:0.18000004;fill:url(#linearGradient18231)" /> + <linearGradient + id="linearGradient17975" + gradientUnits="userSpaceOnUse" + x1="220.9512" + y1="634.63232" + x2="278.14471" + y2="560.35498"> + <stop + offset="0" + style="stop-color:#FFFFFF" + id="stop17977" /> + <stop + offset="1" + style="stop-color:#193D6B" + id="stop17979" /> + <a:midPointStop + offset="0" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="0.5" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="1" + style="stop-color:#193D6B" /> + </linearGradient> + <polygon + i:knockout="Off" + a:adobe-blending-mode="lighten" + points="272.565,567.14 214.39,617.501 287.326,574.086 272.565,567.14 " + id="polygon17981" + style="opacity:0.65;fill:url(#linearGradient18233)" /> + <linearGradient + id="linearGradient17983" + gradientUnits="userSpaceOnUse" + x1="237.20799" + y1="684.01898" + x2="328.18469" + y2="565.86761"> + <stop + offset="0" + style="stop-color:#FFFFFF" + id="stop17985" /> + <stop + offset="1" + style="stop-color:#193D6B" + id="stop17987" /> + <a:midPointStop + offset="0" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="0.5" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="1" + style="stop-color:#193D6B" /> + </linearGradient> + <polygon + i:knockout="Off" + a:adobe-blending-mode="lighten" + points="329.873,549.776 230.887,678.282 344.633,558.457 329.873,549.776 " + id="polygon17989" + style="opacity:0.65;fill:url(#linearGradient18235)" /> + <linearGradient + id="linearGradient17991" + gradientUnits="userSpaceOnUse" + x1="264.5405" + y1="704.23578" + x2="335.09619" + y2="612.60522"> + <stop + offset="0" + style="stop-color:#FFFFFF" + id="stop17993" /> + <stop + offset="1" + style="stop-color:#193D6B" + id="stop17995" /> + <a:midPointStop + offset="0" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="0.5" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="1" + style="stop-color:#193D6B" /> + </linearGradient> + <polygon + i:knockout="Off" + a:adobe-blending-mode="lighten" + points="337.688,594.058 262.146,713.882 355.053,601.872 337.688,594.058 " + id="polygon17997" + style="opacity:0.65;fill:url(#linearGradient18237)" /> + <linearGradient + id="linearGradient17999" + gradientUnits="userSpaceOnUse" + x1="319.186" + y1="713.54639" + x2="373.12509" + y2="626.00598"> + <stop + offset="0" + style="stop-color:#FFFFFF" + id="stop18001" /> + <stop + offset="1" + style="stop-color:#193D6B" + id="stop18003" /> + <a:midPointStop + offset="0" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="0.5" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="1" + style="stop-color:#193D6B" /> + </linearGradient> + <polygon + i:knockout="Off" + a:adobe-blending-mode="lighten" + points="381.97,588.847 318.584,737.327 402.809,593.19 381.97,588.847 " + id="polygon18005" + style="opacity:0.65;fill:url(#linearGradient18239)" /> + <linearGradient + id="linearGradient18007" + gradientUnits="userSpaceOnUse" + x1="331.49609" + y1="790.51508" + x2="409.85471" + y2="688.75092"> + <stop + offset="0" + style="stop-color:#FFFFFF" + id="stop18009" /> + <stop + offset="1" + style="stop-color:#193D6B" + id="stop18011" /> + <a:midPointStop + offset="0" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="0.5" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="1" + style="stop-color:#193D6B" /> + </linearGradient> + <polygon + i:knockout="Off" + a:adobe-blending-mode="lighten" + points="407.151,627.921 401.94,763.375 424.515,629.658 407.151,627.921 " + id="polygon18013" + style="opacity:0.65;fill:url(#linearGradient18241)" /> + <defs + id="defs18015" /> + <mask + maskUnits="userSpaceOnUse" + x="191.541" + y="217.631" + width="447.197" + height="527.73" + id="mask18017" + i:invertMask="false" + i:clipMask="true"> + <g + id="g18019" + style="filter:url(#Adobe_OpacityMaskFilter_3_)"> + <radialGradient + id="radialGradient18021" + cx="657.2124" + cy="155.23289" + r="606.95343" + fx="657.2124" + fy="155.23289" + gradientTransform="matrix(0.931,0,0,0.9299,28.7459,21.6629)" + gradientUnits="userSpaceOnUse"> + <stop + offset="0" + style="stop-color:#C9C9C8" + id="stop18023" /> + <stop + offset="0.0881" + style="stop-color:#C4C4C4" + id="stop18025" /> + <stop + offset="0.2192" + style="stop-color:#B8B8B7" + id="stop18027" /> + <stop + offset="0.3769" + style="stop-color:#A1A0A0" + id="stop18029" /> + <stop + offset="0.5556" + style="stop-color:#7F7F7E" + id="stop18031" /> + <stop + offset="0.7517" + style="stop-color:#4F4F4E" + id="stop18033" /> + <stop + offset="0.9595" + style="stop-color:#121212" + id="stop18035" /> + <stop + offset="1" + style="stop-color:#000000" + id="stop18037" /> + <a:midPointStop + offset="0" + style="stop-color:#C9C9C8" /> + <a:midPointStop + offset="0.6215" + style="stop-color:#C9C9C8" /> + <a:midPointStop + offset="1" + style="stop-color:#000000" /> + </radialGradient> + <circle + i:knockout="Off" + cx="408.427" + cy="444.22601" + r="302.95401" + id="circle18039" + sodipodi:cx="408.427" + sodipodi:cy="444.22601" + sodipodi:rx="302.95401" + sodipodi:ry="302.95401" + style="fill:url(#XMLID_11_)" /> + </g> + </mask> + <linearGradient + id="linearGradient18041" + gradientUnits="userSpaceOnUse" + x1="517.18988" + y1="239.939" + x2="410.27759" + y2="522.02393"> + <stop + offset="0" + style="stop-color:#FFFFFF" + id="stop18043" /> + <stop + offset="0.9944" + style="stop-color:#FFFFFF" + id="stop18045" /> + <a:midPointStop + offset="0" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="0.6215" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="0.9944" + style="stop-color:#FFFFFF" /> + </linearGradient> + <path + i:knockout="Off" + mask="url(#XMLID_10_)" + d="M 566.303,218.317 C 607.373,264.655 592.756,249.235 589.541,263.89 C 586.325,278.548 512.629,401.164 528.704,407.028 C 544.779,412.89 567.565,414.686 586.053,402.96 C 604.542,391.235 633.438,382.73 638.259,393.721 C 643.083,404.713 610.113,404.175 599.664,409.305 C 589.215,414.434 525.003,467.462 508.928,487.247 C 492.851,507.035 435.463,593.164 429.836,628.34 C 424.207,663.514 418.582,703.819 418.582,722.87 C 418.582,741.923 384.824,749.254 342.222,743.389 C 299.62,737.529 232.901,685.496 198.338,645.192 C 163.776,604.887 271.922,544.985 278.354,523.733 C 284.786,502.481 384.553,419.303 412.542,403.68 C 439.074,388.87 554.529,205.029 566.303,218.317 z " + id="path18047" + style="opacity:0.47999998;fill:url(#linearGradient18243)" /> + <defs + id="defs18049" /> + <mask + maskUnits="userSpaceOnUse" + x="243.061" + y="279.317" + width="252.506" + height="187.183" + id="mask18051" + i:invertMask="false" + i:clipMask="true"> + <g + id="g18053" + style="filter:url(#Adobe_OpacityMaskFilter_4_)"> + <linearGradient + id="linearGradient18055" + gradientUnits="userSpaceOnUse" + x1="340.0488" + y1="266.93359" + x2="372.38071" + y2="417.81519"> + <stop + offset="0" + style="stop-color:#FFFFFF" + id="stop18057" /> + <stop + offset="0.0867" + style="stop-color:#F8F8F8" + id="stop18059" /> + <stop + offset="0.2156" + style="stop-color:#E4E4E4" + id="stop18061" /> + <stop + offset="0.3708" + style="stop-color:#C2C2C2" + id="stop18063" /> + <stop + offset="0.5465" + style="stop-color:#949494" + id="stop18065" /> + <stop + offset="0.7394" + style="stop-color:#595959" + id="stop18067" /> + <stop + offset="0.9438" + style="stop-color:#151515" + id="stop18069" /> + <stop + offset="0.9944" + style="stop-color:#000000" + id="stop18071" /> + <a:midPointStop + offset="0" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="0.6215" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="0.9944" + style="stop-color:#000000" /> + </linearGradient> + <ellipse + i:knockout="Off" + cx="363.052" + cy="374.28201" + rx="215.32401" + ry="183.32201" + id="ellipse18073" + sodipodi:cx="363.052" + sodipodi:cy="374.28201" + sodipodi:rx="215.32401" + sodipodi:ry="183.32201" + style="fill:url(#XMLID_14_)" /> + </g> + </mask> + <path + i:knockout="Off" + mask="url(#XMLID_13_)" + d="M 495.251,279.317 C 495.251,279.317 427.161,328.038 418.318,330.515 C 409.476,332.993 347.575,331.341 341.385,334.644 C 335.195,337.947 270.642,408.964 256.494,411.441 C 242.346,413.918 237.924,446.949 250.304,462.639 C 262.684,478.329 307.782,441.994 311.32,428.783 C 314.858,415.571 322.816,377.585 338.733,366.85 C 354.65,356.115 396.212,343.728 405.939,345.38 C 415.666,347.031 471.376,302.439 471.376,302.439 C 471.376,302.439 498.79,285.924 495.251,279.317 z " + id="path18075" + style="opacity:0.47999998;fill:#ffffff" /> + </g> + <path + style="fill:none;stroke:url(#linearGradient18245);stroke-width:26.21903229;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path18077" + d="M 481.10291,750.63818 C 504.60991,742.43859 484.89535,736.14215 485.70505,723.21312 C 486.51114,710.28229 486.11667,685.76446 487.73336,671.21963 C 489.35095,656.67391 495.00532,534.64658 516.0178,502.32266 C 537.02759,470.00053 597.07997,431.19635 618.09155,427.15597 C 639.10404,423.1156 635.06186,415.03394 635.06186,407.76198 C 635.06186,400.49002 631.83028,384.32671 626.17231,381.90212 C 620.51614,379.47843 598.44385,394.03675 591.17099,395.65254 C 583.89813,397.26833 549.14872,399.69382 544.29865,391.61306 C 539.45038,383.53051 583.50048,301.10266 589.15755,291.40521 C 594.81552,281.70687 597.23831,268.77604 586.73207,255.04001 C 576.23032,241.29949 568.95656,227.56166 564.91618,227.56166 C 551.5895,227.56166 525.71615,258.27249 516.0187,263.12077 C 506.32126,267.96994 429.54868,322.92034 422.27582,332.61869 C 415.00296,342.31523 405.30461,346.35651 394.80017,345.54862 C 384.29572,344.74162 351.08563,344.40875 339.7724,350.87371 C 328.45736,357.33957 276.81591,410.19826 263.88598,416.66413 C 250.95695,423.12729 234.08831,426.43894 226.00665,427.24774 C 217.92679,428.05474 191.15558,457.8758 183.88272,463.53377 C 176.60986,469.19174 119.1395,494.06017 111.05874,498.90665 C 102.97709,503.75582 170.01444,571.66494 179.71189,573.27983 C 189.40934,574.89832 129.82914,605.28739 137.19522,622.42986" + i:knockout="Off" + sodipodi:nodetypes="csssssssssscssssssssss" /> + <path + style="fill:url(#linearGradient18247);fill-opacity:1" + id="path18079" + d="M 412.32879,379.97694 C 412.32879,379.97694 375.86296,374.36555 354.82231,395.40722 C 333.78063,416.44789 315.54259,462.73569 316.94953,476.76314 C 318.35033,490.79058 360.43266,429.07146 378.66558,424.86497 C 396.90259,420.6554 416.53938,389.79379 412.32879,379.97694 z " + i:knockout="Off" /> + <g + i:rgbTrio="#4F00FFFFFFFF" + i:dimmedPercent="50" + i:layer="yes" + id="g18081" /> + <defs + id="defs18083" /> + <mask + i:clipMask="true" + i:invertMask="false" + id="mask18085" + height="44.813" + width="93.126" + y="461.543" + x="155.705" + maskUnits="userSpaceOnUse"> + <g + id="g18087" + style="filter:url(#Adobe_OpacityMaskFilter_1_)"> + <linearGradient + y2="466.75449" + x2="200.0733" + y1="562.85791" + x1="211.7222" + gradientUnits="userSpaceOnUse" + id="linearGradient18089"> + <stop + id="stop18091" + style="stop-color:#FFFFFF" + offset="0" /> + <stop + id="stop18093" + style="stop-color:#000000" + offset="1" /> + <a:midPointStop + style="stop-color:#FFFFFF" + offset="0" /> + <a:midPointStop + style="stop-color:#FFFFFF" + offset="0.5" /> + <a:midPointStop + style="stop-color:#000000" + offset="1" /> + </linearGradient> + <ellipse + style="fill:url(#XMLID_1_)" + sodipodi:ry="59.682999" + sodipodi:rx="80.542" + sodipodi:cy="495.98801" + sodipodi:cx="203.617" + id="ellipse18095" + ry="59.682999" + rx="80.542" + cy="495.98801" + cx="203.617" + i:knockout="Off" /> + </g> + </mask> + <path + style="fill:url(#linearGradient18249);fill-opacity:1" + id="path18097" + d="M 281.37786,477.99374 C 281.37786,477.99374 244.36221,474.50936 232.90412,477.99374 C 221.44514,481.47543 199.41154,501.50549 197.64911,509.34333 C 195.88577,517.17848 238.19143,518.05025 248.76695,515.43674 C 259.34336,512.82592 278.73556,487.57064 281.37786,477.99374 z " + mask="url(#XMLID_26_)" + i:knockout="Off" /> + <defs + id="defs18099" /> + <mask + i:clipMask="true" + i:invertMask="false" + id="mask18101" + height="127.553" + width="156.907" + y="425.034" + x="101.57" + maskUnits="userSpaceOnUse"> + <g + id="g18103" + style="filter:url(#Adobe_OpacityMaskFilter_2_)"> + <linearGradient + y2="563.47321" + x2="186.92191" + y1="397.62061" + x1="206.09081" + gradientUnits="userSpaceOnUse" + id="linearGradient18105"> + <stop + id="stop18107" + style="stop-color:#FFFFFF" + offset="0" /> + <stop + id="stop18109" + style="stop-color:#CBCBCB" + offset="0.1111" /> + <stop + id="stop18111" + style="stop-color:#969696" + offset="0.2396" /> + <stop + id="stop18113" + style="stop-color:#686868" + offset="0.3698" /> + <stop + id="stop18115" + style="stop-color:#424242" + offset="0.4991" /> + <stop + id="stop18117" + style="stop-color:#252525" + offset="0.6273" /> + <stop + id="stop18119" + style="stop-color:#111111" + offset="0.7542" /> + <stop + id="stop18121" + style="stop-color:#040404" + offset="0.8792" /> + <stop + id="stop18123" + style="stop-color:#000000" + offset="1" /> + <a:midPointStop + style="stop-color:#FFFFFF" + offset="0" /> + <a:midPointStop + style="stop-color:#FFFFFF" + offset="0.2994" /> + <a:midPointStop + style="stop-color:#000000" + offset="1" /> + </linearGradient> + <ellipse + style="fill:url(#XMLID_3_)" + sodipodi:ry="123.604" + sodipodi:rx="160.94" + sodipodi:cy="495.62299" + sodipodi:cx="194.76401" + id="ellipse18125" + ry="123.604" + rx="160.94" + cy="495.62299" + cx="194.76401" + i:knockout="Off" /> + </g> + </mask> + <path + style="fill:url(#linearGradient18251);fill-opacity:1" + id="path18127" + d="M 264.39264,457.23757 C 264.39264,457.23757 248.74502,449.20288 242.34258,450.5424 C 235.94257,451.88111 162.5424,503.92155 155.43109,507.93608 C 145.58106,513.50061 166.41337,529.61436 172.78919,535.94906 C 176.51582,539.65148 196.50296,555.63297 202.1933,552.95716 C 207.88364,550.27731 218.15866,545.6144 228.11755,543.60472 C 238.07564,541.59584 280.81526,517.27562 279.32897,506.77962 C 278.36042,499.93526 243.69985,528.9442 234.43128,529.27888 C 216.00946,529.94581 184.72874,524.8579 181.17147,516.82481 C 177.61502,508.7885 187.76262,494.32799 193.26425,490.71426 C 229.47159,466.92308 264.39264,457.23757 264.39264,457.23757 z " + mask="url(#XMLID_2_)" + i:knockout="Off" /> + <linearGradient + gradientTransform="matrix(-1,0,0,1,951.2744,0)" + y2="643.30768" + x2="480.12021" + y1="815.45459" + x1="429.84909" + gradientUnits="userSpaceOnUse" + id="linearGradient18129"> + <stop + id="stop18131" + style="stop-color:#FFFFFF" + offset="0" /> + <stop + id="stop18133" + style="stop-color:#E5EAF2" + offset="1" /> + <a:midPointStop + style="stop-color:#FFFFFF" + offset="0" /> + <a:midPointStop + style="stop-color:#FFFFFF" + offset="0.5" /> + <a:midPointStop + style="stop-color:#E5EAF2" + offset="1" /> + </linearGradient> + <linearGradient + y2="560.35498" + x2="278.14471" + y1="634.63232" + x1="220.9512" + gradientUnits="userSpaceOnUse" + id="linearGradient18135"> + <stop + id="stop18137" + style="stop-color:#FFFFFF" + offset="0" /> + <stop + id="stop18139" + style="stop-color:#193D6B" + offset="1" /> + <a:midPointStop + style="stop-color:#FFFFFF" + offset="0" /> + <a:midPointStop + style="stop-color:#FFFFFF" + offset="0.5" /> + <a:midPointStop + style="stop-color:#193D6B" + offset="1" /> + </linearGradient> + <linearGradient + y2="565.86761" + x2="328.18469" + y1="684.01898" + x1="237.20799" + gradientUnits="userSpaceOnUse" + id="linearGradient18141"> + <stop + id="stop18143" + style="stop-color:#FFFFFF" + offset="0" /> + <stop + id="stop18145" + style="stop-color:#193D6B" + offset="1" /> + <a:midPointStop + style="stop-color:#FFFFFF" + offset="0" /> + <a:midPointStop + style="stop-color:#FFFFFF" + offset="0.5" /> + <a:midPointStop + style="stop-color:#193D6B" + offset="1" /> + </linearGradient> + <linearGradient + y2="612.60522" + x2="335.09619" + y1="704.23578" + x1="264.5405" + gradientUnits="userSpaceOnUse" + id="linearGradient18147"> + <stop + id="stop18149" + style="stop-color:#FFFFFF" + offset="0" /> + <stop + id="stop18151" + style="stop-color:#193D6B" + offset="1" /> + <a:midPointStop + style="stop-color:#FFFFFF" + offset="0" /> + <a:midPointStop + style="stop-color:#FFFFFF" + offset="0.5" /> + <a:midPointStop + style="stop-color:#193D6B" + offset="1" /> + </linearGradient> + <linearGradient + y2="626.00598" + x2="373.12509" + y1="713.54639" + x1="319.186" + gradientUnits="userSpaceOnUse" + id="linearGradient18153"> + <stop + id="stop18155" + style="stop-color:#FFFFFF" + offset="0" /> + <stop + id="stop18157" + style="stop-color:#193D6B" + offset="1" /> + <a:midPointStop + style="stop-color:#FFFFFF" + offset="0" /> + <a:midPointStop + style="stop-color:#FFFFFF" + offset="0.5" /> + <a:midPointStop + style="stop-color:#193D6B" + offset="1" /> + </linearGradient> + <linearGradient + y2="688.75092" + x2="409.85471" + y1="790.51508" + x1="331.49609" + gradientUnits="userSpaceOnUse" + id="linearGradient18159"> + <stop + id="stop18161" + style="stop-color:#FFFFFF" + offset="0" /> + <stop + id="stop18163" + style="stop-color:#193D6B" + offset="1" /> + <a:midPointStop + style="stop-color:#FFFFFF" + offset="0" /> + <a:midPointStop + style="stop-color:#FFFFFF" + offset="0.5" /> + <a:midPointStop + style="stop-color:#193D6B" + offset="1" /> + </linearGradient> + <defs + id="defs18165" /> + <mask + i:clipMask="true" + i:invertMask="false" + id="mask18167" + height="527.73" + width="447.197" + y="217.631" + x="191.541" + maskUnits="userSpaceOnUse"> + <g + id="g18169" + style="filter:url(#Adobe_OpacityMaskFilter_3_)"> + <radialGradient + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.931,0,0,0.9299,28.7459,21.6629)" + fy="155.23289" + fx="657.2124" + r="606.95343" + cy="155.23289" + cx="657.2124" + id="radialGradient18171"> + <stop + id="stop18173" + style="stop-color:#C9C9C8" + offset="0" /> + <stop + id="stop18175" + style="stop-color:#C4C4C4" + offset="0.0881" /> + <stop + id="stop18177" + style="stop-color:#B8B8B7" + offset="0.2192" /> + <stop + id="stop18179" + style="stop-color:#A1A0A0" + offset="0.3769" /> + <stop + id="stop18181" + style="stop-color:#7F7F7E" + offset="0.5556" /> + <stop + id="stop18183" + style="stop-color:#4F4F4E" + offset="0.7517" /> + <stop + id="stop18185" + style="stop-color:#121212" + offset="0.9595" /> + <stop + id="stop18187" + style="stop-color:#000000" + offset="1" /> + <a:midPointStop + style="stop-color:#C9C9C8" + offset="0" /> + <a:midPointStop + style="stop-color:#C9C9C8" + offset="0.6215" /> + <a:midPointStop + style="stop-color:#000000" + offset="1" /> + </radialGradient> + <circle + style="fill:url(#XMLID_11_)" + sodipodi:ry="302.95401" + sodipodi:rx="302.95401" + sodipodi:cy="444.22601" + sodipodi:cx="408.427" + id="circle18189" + r="302.95401" + cy="444.22601" + cx="408.427" + i:knockout="Off" /> + </g> + </mask> + <linearGradient + y2="522.02393" + x2="410.27759" + y1="239.939" + x1="517.18988" + gradientUnits="userSpaceOnUse" + id="linearGradient18191"> + <stop + id="stop18193" + style="stop-color:#FFFFFF" + offset="0" /> + <stop + id="stop18195" + style="stop-color:#FFFFFF" + offset="0.9944" /> + <a:midPointStop + style="stop-color:#FFFFFF" + offset="0" /> + <a:midPointStop + style="stop-color:#FFFFFF" + offset="0.6215" /> + <a:midPointStop + style="stop-color:#FFFFFF" + offset="0.9944" /> + </linearGradient> + <defs + id="defs18197" /> + <mask + i:clipMask="true" + i:invertMask="false" + id="mask18199" + height="187.183" + width="252.506" + y="279.317" + x="243.061" + maskUnits="userSpaceOnUse"> + <g + id="g18201" + style="filter:url(#Adobe_OpacityMaskFilter_4_)"> + <linearGradient + y2="417.81519" + x2="372.38071" + y1="266.93359" + x1="340.0488" + gradientUnits="userSpaceOnUse" + id="linearGradient18203"> + <stop + id="stop18205" + style="stop-color:#FFFFFF" + offset="0" /> + <stop + id="stop18207" + style="stop-color:#F8F8F8" + offset="0.0867" /> + <stop + id="stop18209" + style="stop-color:#E4E4E4" + offset="0.2156" /> + <stop + id="stop18211" + style="stop-color:#C2C2C2" + offset="0.3708" /> + <stop + id="stop18213" + style="stop-color:#949494" + offset="0.5465" /> + <stop + id="stop18215" + style="stop-color:#595959" + offset="0.7394" /> + <stop + id="stop18217" + style="stop-color:#151515" + offset="0.9438" /> + <stop + id="stop18219" + style="stop-color:#000000" + offset="0.9944" /> + <a:midPointStop + style="stop-color:#FFFFFF" + offset="0" /> + <a:midPointStop + style="stop-color:#FFFFFF" + offset="0.6215" /> + <a:midPointStop + style="stop-color:#000000" + offset="0.9944" /> + </linearGradient> + <ellipse + style="fill:url(#XMLID_14_)" + sodipodi:ry="183.32201" + sodipodi:rx="215.32401" + sodipodi:cy="374.28201" + sodipodi:cx="363.052" + id="ellipse18221" + ry="183.32201" + rx="215.32401" + cy="374.28201" + cx="363.052" + i:knockout="Off" /> + </g> + </mask> + </g> + </g> + <g + style="display:inline" + id="g18253" + transform="matrix(3.058564,0,0,3.05856,1209.785,-273.48948)" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/64x64/actions/amarok_settings_general.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <g + id="g18255" + transform="translate(0.141718,145.4379)" + inkscape:export-filename="/home/vadim/collection.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + transform="matrix(1.064993,0,0,1.064994,-425.4498,-454.68)" + d="M 428.77151 444.83228 A 9.8239412 9.8239412 0 1 1 409.12363,444.83228 A 9.8239412 9.8239412 0 1 1 428.77151 444.83228 z" + sodipodi:ry="9.8239412" + sodipodi:rx="9.8239412" + sodipodi:cy="444.83228" + sodipodi:cx="418.94757" + id="path18257" + style="opacity:1;fill:url(#linearGradient18601);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + transform="matrix(0.883732,0,0,0.900587,-349.1263,-383.0023)" + d="M 429.51763 446.44885 A 11.005301 11.005301 0 1 1 407.50703,446.44885 A 11.005301 11.005301 0 1 1 429.51763 446.44885 z" + sodipodi:ry="11.005301" + sodipodi:rx="11.005301" + sodipodi:cy="446.44885" + sodipodi:cx="418.51233" + id="path18259" + style="opacity:1;fill:url(#radialGradient18603);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + sodipodi:nodetypes="csssc" + id="path18261" + d="M 29.686411,20.220753 C 29.686411,25.937399 25.672358,20.884742 20.726478,20.884742 C 15.780598,20.884742 11.766545,25.937399 11.766545,20.220753 C 11.766545,14.504147 15.780598,9.8645522 20.726478,9.8645522 C 25.672358,9.8645522 29.686411,14.504147 29.686411,20.220753 z " + style="opacity:0.7;fill:url(#linearGradient18605);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" /> + </g> + <g + transform="matrix(2.879032e-2,0,0,2.879032e-2,9.015838,152.7103)" + id="g18263" + i:layer="yes" + i:dimmedPercent="3" + i:rgbTrio="#4F004F00FFFF"> + <radialGradient + id="radialGradient18265" + cx="375.21439" + cy="783.16357" + r="300.5752" + fx="375.21439" + fy="783.16357" + gradientUnits="userSpaceOnUse"> + <stop + offset="0.0112" + style="stop-color:#D2E8EC" + id="stop18267" /> + <stop + offset="0.2921" + style="stop-color:#60A8CE" + id="stop18269" /> + <stop + offset="0.4575" + style="stop-color:#4680A8" + id="stop18271" /> + <stop + offset="0.6767" + style="stop-color:#2F5C84" + id="stop18273" /> + <stop + offset="0.8653" + style="stop-color:#234970" + id="stop18275" /> + <stop + offset="1" + style="stop-color:#1F426A" + id="stop18277" /> + <a:midPointStop + offset="0.0112" + style="stop-color:#D2E8EC" /> + <a:midPointStop + offset="0.5" + style="stop-color:#D2E8EC" /> + <a:midPointStop + offset="0.2921" + style="stop-color:#60A8CE" /> + <a:midPointStop + offset="0.3651" + style="stop-color:#60A8CE" /> + <a:midPointStop + offset="1" + style="stop-color:#1F426A" /> + </radialGradient> + <g + style="opacity:0.18297873" + transform="matrix(0.925462,0,0,0.925462,22.38513,40.63705)" + i:rgbTrio="#4F004F00FFFF" + i:dimmedPercent="3" + i:layer="yes" + id="g18279"> + <path + style="opacity:0.22000002" + id="path18281" + d="M 618.829,233.011 C 603.559,233.011 573.908,264.857 562.798,269.885 C 551.683,274.913 463.707,331.894 455.371,341.95 C 447.039,352.005 435.926,356.196 423.888,355.358 C 411.851,354.52 373.793,354.176 360.83,360.88 C 347.864,367.584 288.686,422.396 273.868,429.101 C 259.052,435.803 239.721,439.239 230.461,440.076 C 221.201,440.915 190.523,471.838 182.188,477.706 C 173.854,483.569 112.922,507.063 103.663,512.089 C 94.402,517.118 158.112,596.697 169.225,598.373 C 180.336,600.05 158.309,608.266 136.929,608.266 C 134.575,608.266 167.484,694.564 279.692,750.263 C 376.697,798.413 454.851,769.173 493.075,757.495 C 520.103,749.238 525.004,745.556 525.933,732.148 C 526.86,718.741 528.534,708.146 530.387,693.063 C 532.24,677.982 538.718,551.444 562.799,517.926 C 586.873,484.407 655.692,444.169 679.768,439.981 C 703.846,435.791 699.217,427.412 699.217,419.87 C 699.217,412.328 695.512,395.568 689.03,393.054 C 682.547,390.541 657.253,405.637 648.919,407.313 C 640.584,408.989 600.765,411.503 595.208,403.124 C 589.652,394.743 640.131,309.268 646.613,299.213 C 653.093,289.157 655.872,275.749 643.836,261.504 C 631.793,247.256 623.458,233.011 618.829,233.011 z " + i:knockout="Off" /> + <radialGradient + gradientUnits="userSpaceOnUse" + fy="783.16357" + fx="375.21439" + r="300.5752" + cy="783.16357" + cx="375.21439" + id="radialGradient18283"> + <stop + id="stop18285" + style="stop-color:#D2E8EC" + offset="0.0112" /> + <stop + id="stop18287" + style="stop-color:#60A8CE" + offset="0.2921" /> + <stop + id="stop18289" + style="stop-color:#4680A8" + offset="0.4575" /> + <stop + id="stop18291" + style="stop-color:#2F5C84" + offset="0.6767" /> + <stop + id="stop18293" + style="stop-color:#234970" + offset="0.8653" /> + <stop + id="stop18295" + style="stop-color:#1F426A" + offset="1" /> + <a:midPointStop + style="stop-color:#D2E8EC" + offset="0.0112" /> + <a:midPointStop + style="stop-color:#D2E8EC" + offset="0.5" /> + <a:midPointStop + style="stop-color:#60A8CE" + offset="0.2921" /> + <a:midPointStop + style="stop-color:#60A8CE" + offset="0.3651" /> + <a:midPointStop + style="stop-color:#1F426A" + offset="1" /> + </radialGradient> + <path + style="fill:url(#radialGradient18607)" + id="path18297" + d="M 586.334,188.678 C 571.521,188.678 542.762,222.814 531.983,228.203 C 521.204,233.593 435.869,294.672 427.785,305.452 C 419.701,316.23 408.921,320.722 397.245,319.824 C 385.569,318.927 348.655,318.557 336.08,325.743 C 323.503,332.93 266.102,391.684 251.73,398.871 C 237.359,406.055 218.609,409.736 209.626,410.635 C 200.645,411.532 170.888,444.679 162.804,450.968 C 154.72,457.257 90.84,484.899 81.858,490.286 C 72.875,495.676 147.389,571.159 158.168,572.954 C 168.947,574.753 150.744,615.651 130.005,615.651 C 127.721,615.651 148.538,683.422 257.377,743.127 C 351.47,794.742 451.303,771.889 488.378,759.373 C 514.596,750.519 496.195,745.626 497.095,731.255 C 497.991,716.882 498.746,697.984 500.543,681.817 C 502.341,665.649 508.626,530.012 531.982,494.083 C 555.335,458.156 622.085,415.024 645.44,410.533 C 668.796,406.042 664.303,397.059 664.303,388.976 C 664.303,380.893 660.711,362.927 654.422,360.232 C 648.135,357.538 623.601,373.72 615.517,375.516 C 607.433,377.312 568.808,380.008 563.417,371.026 C 558.028,362.042 606.991,270.421 613.279,259.642 C 619.568,248.862 622.261,234.489 610.583,219.221 C 598.91,203.948 590.825,188.678 586.334,188.678 z " + i:knockout="Off" /> + <g + i:rgbTrio="#4F00FFFFFFFF" + i:dimmedPercent="50" + i:layer="yes" + id="g18299" /> + <defs + id="defs18301" /> + <mask + i:clipMask="true" + i:invertMask="false" + id="mask18303" + height="44.813" + width="93.126" + y="461.543" + x="155.705" + maskUnits="userSpaceOnUse"> + <g + id="g18305" + style="filter:url(#Adobe_OpacityMaskFilter_1_)"> + <linearGradient + y2="466.75449" + x2="200.0733" + y1="562.85791" + x1="211.7222" + gradientUnits="userSpaceOnUse" + id="linearGradient18307"> + <stop + id="stop18309" + style="stop-color:#FFFFFF" + offset="0" /> + <stop + id="stop18311" + style="stop-color:#000000" + offset="1" /> + <a:midPointStop + style="stop-color:#FFFFFF" + offset="0" /> + <a:midPointStop + style="stop-color:#FFFFFF" + offset="0.5" /> + <a:midPointStop + style="stop-color:#000000" + offset="1" /> + </linearGradient> + <ellipse + style="fill:url(#XMLID_1_)" + sodipodi:ry="59.682999" + sodipodi:rx="80.542" + sodipodi:cy="495.98801" + sodipodi:cx="203.617" + id="ellipse18313" + ry="59.682999" + rx="80.542" + cy="495.98801" + cx="203.617" + i:knockout="Off" /> + </g> + </mask> + <defs + id="defs18315" /> + <mask + i:clipMask="true" + i:invertMask="false" + id="mask18317" + height="127.553" + width="156.907" + y="425.034" + x="101.57" + maskUnits="userSpaceOnUse"> + <g + id="g18319" + style="filter:url(#Adobe_OpacityMaskFilter_2_)"> + <linearGradient + y2="563.47321" + x2="186.92191" + y1="397.62061" + x1="206.09081" + gradientUnits="userSpaceOnUse" + id="linearGradient18321"> + <stop + id="stop18323" + style="stop-color:#FFFFFF" + offset="0" /> + <stop + id="stop18325" + style="stop-color:#CBCBCB" + offset="0.1111" /> + <stop + id="stop18327" + style="stop-color:#969696" + offset="0.2396" /> + <stop + id="stop18329" + style="stop-color:#686868" + offset="0.3698" /> + <stop + id="stop18331" + style="stop-color:#424242" + offset="0.4991" /> + <stop + id="stop18333" + style="stop-color:#252525" + offset="0.6273" /> + <stop + id="stop18335" + style="stop-color:#111111" + offset="0.7542" /> + <stop + id="stop18337" + style="stop-color:#040404" + offset="0.8792" /> + <stop + id="stop18339" + style="stop-color:#000000" + offset="1" /> + <a:midPointStop + style="stop-color:#FFFFFF" + offset="0" /> + <a:midPointStop + style="stop-color:#FFFFFF" + offset="0.2994" /> + <a:midPointStop + style="stop-color:#000000" + offset="1" /> + </linearGradient> + <ellipse + style="fill:url(#XMLID_3_)" + sodipodi:ry="123.604" + sodipodi:rx="160.94" + sodipodi:cy="495.62299" + sodipodi:cx="194.76401" + id="ellipse18341" + ry="123.604" + rx="160.94" + cy="495.62299" + cx="194.76401" + i:knockout="Off" /> + </g> + </mask> + <linearGradient + gradientTransform="matrix(-1,0,0,1,951.2744,0)" + y2="643.30768" + x2="480.12021" + y1="815.45459" + x1="429.84909" + gradientUnits="userSpaceOnUse" + id="linearGradient18343"> + <stop + id="stop18345" + style="stop-color:#FFFFFF" + offset="0" /> + <stop + id="stop18347" + style="stop-color:#E5EAF2" + offset="1" /> + <a:midPointStop + style="stop-color:#FFFFFF" + offset="0" /> + <a:midPointStop + style="stop-color:#FFFFFF" + offset="0.5" /> + <a:midPointStop + style="stop-color:#E5EAF2" + offset="1" /> + </linearGradient> + <path + style="opacity:0.18000004;fill:url(#linearGradient18609)" + id="path18349" + d="M 497.013,546.945 C 497.013,546.945 465.169,655.349 464.364,657.024 C 459.813,666.482 451.419,693.889 454.309,702.74 C 457.712,713.167 470.467,728.327 476.42,735.907 C 482.373,743.49 484.924,747.28 483.223,750.125 C 481.523,752.968 459.412,752.968 459.412,752.968 C 459.412,752.968 442.405,727.38 439.001,717.903 C 435.601,708.428 433.899,688.527 437.302,681.895 C 440.703,675.26 497.013,546.945 497.013,546.945 z " + i:knockout="Off" /> + <linearGradient + y2="560.35498" + x2="278.14471" + y1="634.63232" + x1="220.9512" + gradientUnits="userSpaceOnUse" + id="linearGradient18351"> + <stop + id="stop18353" + style="stop-color:#FFFFFF" + offset="0" /> + <stop + id="stop18355" + style="stop-color:#193D6B" + offset="1" /> + <a:midPointStop + style="stop-color:#FFFFFF" + offset="0" /> + <a:midPointStop + style="stop-color:#FFFFFF" + offset="0.5" /> + <a:midPointStop + style="stop-color:#193D6B" + offset="1" /> + </linearGradient> + <polygon + style="opacity:0.65;fill:url(#linearGradient18611)" + id="polygon18357" + points="272.565,567.14 214.39,617.501 287.326,574.086 272.565,567.14 " + a:adobe-blending-mode="lighten" + i:knockout="Off" /> + <linearGradient + y2="565.86761" + x2="328.18469" + y1="684.01898" + x1="237.20799" + gradientUnits="userSpaceOnUse" + id="linearGradient18359"> + <stop + id="stop18361" + style="stop-color:#FFFFFF" + offset="0" /> + <stop + id="stop18363" + style="stop-color:#193D6B" + offset="1" /> + <a:midPointStop + style="stop-color:#FFFFFF" + offset="0" /> + <a:midPointStop + style="stop-color:#FFFFFF" + offset="0.5" /> + <a:midPointStop + style="stop-color:#193D6B" + offset="1" /> + </linearGradient> + <polygon + style="opacity:0.65;fill:url(#linearGradient18613)" + id="polygon18365" + points="329.873,549.776 230.887,678.282 344.633,558.457 329.873,549.776 " + a:adobe-blending-mode="lighten" + i:knockout="Off" /> + <linearGradient + y2="612.60522" + x2="335.09619" + y1="704.23578" + x1="264.5405" + gradientUnits="userSpaceOnUse" + id="linearGradient18367"> + <stop + id="stop18369" + style="stop-color:#FFFFFF" + offset="0" /> + <stop + id="stop18371" + style="stop-color:#193D6B" + offset="1" /> + <a:midPointStop + style="stop-color:#FFFFFF" + offset="0" /> + <a:midPointStop + style="stop-color:#FFFFFF" + offset="0.5" /> + <a:midPointStop + style="stop-color:#193D6B" + offset="1" /> + </linearGradient> + <polygon + style="opacity:0.65;fill:url(#linearGradient18615)" + id="polygon18373" + points="337.688,594.058 262.146,713.882 355.053,601.872 337.688,594.058 " + a:adobe-blending-mode="lighten" + i:knockout="Off" /> + <linearGradient + y2="626.00598" + x2="373.12509" + y1="713.54639" + x1="319.186" + gradientUnits="userSpaceOnUse" + id="linearGradient18375"> + <stop + id="stop18377" + style="stop-color:#FFFFFF" + offset="0" /> + <stop + id="stop18379" + style="stop-color:#193D6B" + offset="1" /> + <a:midPointStop + style="stop-color:#FFFFFF" + offset="0" /> + <a:midPointStop + style="stop-color:#FFFFFF" + offset="0.5" /> + <a:midPointStop + style="stop-color:#193D6B" + offset="1" /> + </linearGradient> + <polygon + style="opacity:0.65;fill:url(#linearGradient18617)" + id="polygon18381" + points="381.97,588.847 318.584,737.327 402.809,593.19 381.97,588.847 " + a:adobe-blending-mode="lighten" + i:knockout="Off" /> + <linearGradient + y2="688.75092" + x2="409.85471" + y1="790.51508" + x1="331.49609" + gradientUnits="userSpaceOnUse" + id="linearGradient18383"> + <stop + id="stop18385" + style="stop-color:#FFFFFF" + offset="0" /> + <stop + id="stop18387" + style="stop-color:#193D6B" + offset="1" /> + <a:midPointStop + style="stop-color:#FFFFFF" + offset="0" /> + <a:midPointStop + style="stop-color:#FFFFFF" + offset="0.5" /> + <a:midPointStop + style="stop-color:#193D6B" + offset="1" /> + </linearGradient> + <polygon + style="opacity:0.65;fill:url(#linearGradient18619)" + id="polygon18389" + points="407.151,627.921 401.94,763.375 424.515,629.658 407.151,627.921 " + a:adobe-blending-mode="lighten" + i:knockout="Off" /> + <defs + id="defs18391" /> + <mask + i:clipMask="true" + i:invertMask="false" + id="mask18393" + height="527.73" + width="447.197" + y="217.631" + x="191.541" + maskUnits="userSpaceOnUse"> + <g + id="g18395" + style="filter:url(#Adobe_OpacityMaskFilter_3_)"> + <radialGradient + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.931,0,0,0.9299,28.7459,21.6629)" + fy="155.23289" + fx="657.2124" + r="606.95343" + cy="155.23289" + cx="657.2124" + id="radialGradient18397"> + <stop + id="stop18399" + style="stop-color:#C9C9C8" + offset="0" /> + <stop + id="stop18401" + style="stop-color:#C4C4C4" + offset="0.0881" /> + <stop + id="stop18403" + style="stop-color:#B8B8B7" + offset="0.2192" /> + <stop + id="stop18405" + style="stop-color:#A1A0A0" + offset="0.3769" /> + <stop + id="stop18407" + style="stop-color:#7F7F7E" + offset="0.5556" /> + <stop + id="stop18409" + style="stop-color:#4F4F4E" + offset="0.7517" /> + <stop + id="stop18411" + style="stop-color:#121212" + offset="0.9595" /> + <stop + id="stop18413" + style="stop-color:#000000" + offset="1" /> + <a:midPointStop + style="stop-color:#C9C9C8" + offset="0" /> + <a:midPointStop + style="stop-color:#C9C9C8" + offset="0.6215" /> + <a:midPointStop + style="stop-color:#000000" + offset="1" /> + </radialGradient> + <circle + style="fill:url(#XMLID_11_)" + sodipodi:ry="302.95401" + sodipodi:rx="302.95401" + sodipodi:cy="444.22601" + sodipodi:cx="408.427" + id="circle18415" + r="302.95401" + cy="444.22601" + cx="408.427" + i:knockout="Off" /> + </g> + </mask> + <linearGradient + y2="522.02393" + x2="410.27759" + y1="239.939" + x1="517.18988" + gradientUnits="userSpaceOnUse" + id="linearGradient18417"> + <stop + id="stop18419" + style="stop-color:#FFFFFF" + offset="0" /> + <stop + id="stop18421" + style="stop-color:#FFFFFF" + offset="0.9944" /> + <a:midPointStop + style="stop-color:#FFFFFF" + offset="0" /> + <a:midPointStop + style="stop-color:#FFFFFF" + offset="0.6215" /> + <a:midPointStop + style="stop-color:#FFFFFF" + offset="0.9944" /> + </linearGradient> + <path + style="opacity:0.47999998;fill:url(#linearGradient18621)" + id="path18423" + d="M 566.303,218.317 C 607.373,264.655 592.756,249.235 589.541,263.89 C 586.325,278.548 512.629,401.164 528.704,407.028 C 544.779,412.89 567.565,414.686 586.053,402.96 C 604.542,391.235 633.438,382.73 638.259,393.721 C 643.083,404.713 610.113,404.175 599.664,409.305 C 589.215,414.434 525.003,467.462 508.928,487.247 C 492.851,507.035 435.463,593.164 429.836,628.34 C 424.207,663.514 418.582,703.819 418.582,722.87 C 418.582,741.923 384.824,749.254 342.222,743.389 C 299.62,737.529 232.901,685.496 198.338,645.192 C 163.776,604.887 271.922,544.985 278.354,523.733 C 284.786,502.481 384.553,419.303 412.542,403.68 C 439.074,388.87 554.529,205.029 566.303,218.317 z " + mask="url(#XMLID_10_)" + i:knockout="Off" /> + <defs + id="defs18425" /> + <mask + i:clipMask="true" + i:invertMask="false" + id="mask18427" + height="187.183" + width="252.506" + y="279.317" + x="243.061" + maskUnits="userSpaceOnUse"> + <g + id="g18429" + style="filter:url(#Adobe_OpacityMaskFilter_4_)"> + <linearGradient + y2="417.81519" + x2="372.38071" + y1="266.93359" + x1="340.0488" + gradientUnits="userSpaceOnUse" + id="linearGradient18431"> + <stop + id="stop18433" + style="stop-color:#FFFFFF" + offset="0" /> + <stop + id="stop18435" + style="stop-color:#F8F8F8" + offset="0.0867" /> + <stop + id="stop18437" + style="stop-color:#E4E4E4" + offset="0.2156" /> + <stop + id="stop18439" + style="stop-color:#C2C2C2" + offset="0.3708" /> + <stop + id="stop18441" + style="stop-color:#949494" + offset="0.5465" /> + <stop + id="stop18443" + style="stop-color:#595959" + offset="0.7394" /> + <stop + id="stop18445" + style="stop-color:#151515" + offset="0.9438" /> + <stop + id="stop18447" + style="stop-color:#000000" + offset="0.9944" /> + <a:midPointStop + style="stop-color:#FFFFFF" + offset="0" /> + <a:midPointStop + style="stop-color:#FFFFFF" + offset="0.6215" /> + <a:midPointStop + style="stop-color:#000000" + offset="0.9944" /> + </linearGradient> + <ellipse + style="fill:url(#XMLID_14_)" + sodipodi:ry="183.32201" + sodipodi:rx="215.32401" + sodipodi:cy="374.28201" + sodipodi:cx="363.052" + id="ellipse18449" + ry="183.32201" + rx="215.32401" + cy="374.28201" + cx="363.052" + i:knockout="Off" /> + </g> + </mask> + <path + style="opacity:0.47999998;fill:#ffffff" + id="path18451" + d="M 495.251,279.317 C 495.251,279.317 427.161,328.038 418.318,330.515 C 409.476,332.993 347.575,331.341 341.385,334.644 C 335.195,337.947 270.642,408.964 256.494,411.441 C 242.346,413.918 237.924,446.949 250.304,462.639 C 262.684,478.329 307.782,441.994 311.32,428.783 C 314.858,415.571 322.816,377.585 338.733,366.85 C 354.65,356.115 396.212,343.728 405.939,345.38 C 415.666,347.031 471.376,302.439 471.376,302.439 C 471.376,302.439 498.79,285.924 495.251,279.317 z " + mask="url(#XMLID_13_)" + i:knockout="Off" /> + </g> + <path + sodipodi:nodetypes="csssssssssscssssssssss" + i:knockout="Off" + d="M 481.10291,750.63818 C 504.60991,742.43859 484.89535,736.14215 485.70505,723.21312 C 486.51114,710.28229 486.11667,685.76446 487.73336,671.21963 C 489.35095,656.67391 495.00532,534.64658 516.0178,502.32266 C 537.02759,470.00053 597.07997,431.19635 618.09155,427.15597 C 639.10404,423.1156 635.06186,415.03394 635.06186,407.76198 C 635.06186,400.49002 631.83028,384.32671 626.17231,381.90212 C 620.51614,379.47843 598.44385,394.03675 591.17099,395.65254 C 583.89813,397.26833 549.14872,399.69382 544.29865,391.61306 C 539.45038,383.53051 583.50048,301.10266 589.15755,291.40521 C 594.81552,281.70687 597.23831,268.77604 586.73207,255.04001 C 576.23032,241.29949 568.95656,227.56166 564.91618,227.56166 C 551.5895,227.56166 525.71615,258.27249 516.0187,263.12077 C 506.32126,267.96994 429.54868,322.92034 422.27582,332.61869 C 415.00296,342.31523 405.30461,346.35651 394.80017,345.54862 C 384.29572,344.74162 351.08563,344.40875 339.7724,350.87371 C 328.45736,357.33957 276.81591,410.19826 263.88598,416.66413 C 250.95695,423.12729 234.08831,426.43894 226.00665,427.24774 C 217.92679,428.05474 191.15558,457.8758 183.88272,463.53377 C 176.60986,469.19174 119.1395,494.06017 111.05874,498.90665 C 102.97709,503.75582 170.01444,571.66494 179.71189,573.27983 C 189.40934,574.89832 129.82914,605.28739 137.19522,622.42986" + id="path18453" + style="fill:none;stroke:url(#linearGradient18623);stroke-width:26.21903229;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + i:knockout="Off" + d="M 412.32879,379.97694 C 412.32879,379.97694 375.86296,374.36555 354.82231,395.40722 C 333.78063,416.44789 315.54259,462.73569 316.94953,476.76314 C 318.35033,490.79058 360.43266,429.07146 378.66558,424.86497 C 396.90259,420.6554 416.53938,389.79379 412.32879,379.97694 z " + id="path18455" + style="fill:url(#linearGradient18626);fill-opacity:1" /> + <g + id="g18457" + i:layer="yes" + i:dimmedPercent="50" + i:rgbTrio="#4F00FFFFFFFF" /> + <defs + id="defs18459" /> + <mask + maskUnits="userSpaceOnUse" + x="155.705" + y="461.543" + width="93.126" + height="44.813" + id="mask18461" + i:invertMask="false" + i:clipMask="true"> + <g + id="g18463" + style="filter:url(#Adobe_OpacityMaskFilter_1_)"> + <linearGradient + id="linearGradient18465" + gradientUnits="userSpaceOnUse" + x1="211.7222" + y1="562.85791" + x2="200.0733" + y2="466.75449"> + <stop + offset="0" + style="stop-color:#FFFFFF" + id="stop18467" /> + <stop + offset="1" + style="stop-color:#000000" + id="stop18469" /> + <a:midPointStop + offset="0" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="0.5" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="1" + style="stop-color:#000000" /> + </linearGradient> + <ellipse + i:knockout="Off" + cx="203.617" + cy="495.98801" + rx="80.542" + ry="59.682999" + id="ellipse18471" + sodipodi:cx="203.617" + sodipodi:cy="495.98801" + sodipodi:rx="80.542" + sodipodi:ry="59.682999" + style="fill:url(#XMLID_1_)" /> + </g> + </mask> + <path + i:knockout="Off" + mask="url(#XMLID_26_)" + d="M 281.37786,477.99374 C 281.37786,477.99374 244.36221,474.50936 232.90412,477.99374 C 221.44514,481.47543 199.41154,501.50549 197.64911,509.34333 C 195.88577,517.17848 238.19143,518.05025 248.76695,515.43674 C 259.34336,512.82592 278.73556,487.57064 281.37786,477.99374 z " + id="path18473" + style="fill:url(#linearGradient18629);fill-opacity:1" /> + <defs + id="defs18475" /> + <mask + maskUnits="userSpaceOnUse" + x="101.57" + y="425.034" + width="156.907" + height="127.553" + id="mask18477" + i:invertMask="false" + i:clipMask="true"> + <g + id="g18480" + style="filter:url(#Adobe_OpacityMaskFilter_2_)"> + <linearGradient + id="linearGradient18482" + gradientUnits="userSpaceOnUse" + x1="206.09081" + y1="397.62061" + x2="186.92191" + y2="563.47321"> + <stop + offset="0" + style="stop-color:#FFFFFF" + id="stop18484" /> + <stop + offset="0.1111" + style="stop-color:#CBCBCB" + id="stop18486" /> + <stop + offset="0.2396" + style="stop-color:#969696" + id="stop18488" /> + <stop + offset="0.3698" + style="stop-color:#686868" + id="stop18490" /> + <stop + offset="0.4991" + style="stop-color:#424242" + id="stop18492" /> + <stop + offset="0.6273" + style="stop-color:#252525" + id="stop18494" /> + <stop + offset="0.7542" + style="stop-color:#111111" + id="stop18496" /> + <stop + offset="0.8792" + style="stop-color:#040404" + id="stop18498" /> + <stop + offset="1" + style="stop-color:#000000" + id="stop18500" /> + <a:midPointStop + offset="0" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="0.2994" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="1" + style="stop-color:#000000" /> + </linearGradient> + <ellipse + i:knockout="Off" + cx="194.76401" + cy="495.62299" + rx="160.94" + ry="123.604" + id="ellipse18502" + sodipodi:cx="194.76401" + sodipodi:cy="495.62299" + sodipodi:rx="160.94" + sodipodi:ry="123.604" + style="fill:url(#XMLID_3_)" /> + </g> + </mask> + <path + i:knockout="Off" + mask="url(#XMLID_2_)" + d="M 264.39264,457.23757 C 264.39264,457.23757 248.74502,449.20288 242.34258,450.5424 C 235.94257,451.88111 162.5424,503.92155 155.43109,507.93608 C 145.58106,513.50061 166.41337,529.61436 172.78919,535.94906 C 176.51582,539.65148 196.50296,555.63297 202.1933,552.95716 C 207.88364,550.27731 218.15866,545.6144 228.11755,543.60472 C 238.07564,541.59584 280.81526,517.27562 279.32897,506.77962 C 278.36042,499.93526 243.69985,528.9442 234.43128,529.27888 C 216.00946,529.94581 184.72874,524.8579 181.17147,516.82481 C 177.61502,508.7885 187.76262,494.32799 193.26425,490.71426 C 229.47159,466.92308 264.39264,457.23757 264.39264,457.23757 z " + id="path18504" + style="fill:url(#linearGradient18631);fill-opacity:1" /> + <linearGradient + id="linearGradient18506" + gradientUnits="userSpaceOnUse" + x1="429.84909" + y1="815.45459" + x2="480.12021" + y2="643.30768" + gradientTransform="matrix(-1,0,0,1,951.2744,0)"> + <stop + offset="0" + style="stop-color:#FFFFFF" + id="stop18508" /> + <stop + offset="1" + style="stop-color:#E5EAF2" + id="stop18510" /> + <a:midPointStop + offset="0" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="0.5" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="1" + style="stop-color:#E5EAF2" /> + </linearGradient> + <linearGradient + id="linearGradient18512" + gradientUnits="userSpaceOnUse" + x1="220.9512" + y1="634.63232" + x2="278.14471" + y2="560.35498"> + <stop + offset="0" + style="stop-color:#FFFFFF" + id="stop18514" /> + <stop + offset="1" + style="stop-color:#193D6B" + id="stop18516" /> + <a:midPointStop + offset="0" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="0.5" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="1" + style="stop-color:#193D6B" /> + </linearGradient> + <linearGradient + id="linearGradient18518" + gradientUnits="userSpaceOnUse" + x1="237.20799" + y1="684.01898" + x2="328.18469" + y2="565.86761"> + <stop + offset="0" + style="stop-color:#FFFFFF" + id="stop18520" /> + <stop + offset="1" + style="stop-color:#193D6B" + id="stop18522" /> + <a:midPointStop + offset="0" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="0.5" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="1" + style="stop-color:#193D6B" /> + </linearGradient> + <linearGradient + id="linearGradient18524" + gradientUnits="userSpaceOnUse" + x1="264.5405" + y1="704.23578" + x2="335.09619" + y2="612.60522"> + <stop + offset="0" + style="stop-color:#FFFFFF" + id="stop18526" /> + <stop + offset="1" + style="stop-color:#193D6B" + id="stop18528" /> + <a:midPointStop + offset="0" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="0.5" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="1" + style="stop-color:#193D6B" /> + </linearGradient> + <linearGradient + id="linearGradient18530" + gradientUnits="userSpaceOnUse" + x1="319.186" + y1="713.54639" + x2="373.12509" + y2="626.00598"> + <stop + offset="0" + style="stop-color:#FFFFFF" + id="stop18532" /> + <stop + offset="1" + style="stop-color:#193D6B" + id="stop18534" /> + <a:midPointStop + offset="0" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="0.5" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="1" + style="stop-color:#193D6B" /> + </linearGradient> + <linearGradient + id="linearGradient18536" + gradientUnits="userSpaceOnUse" + x1="331.49609" + y1="790.51508" + x2="409.85471" + y2="688.75092"> + <stop + offset="0" + style="stop-color:#FFFFFF" + id="stop18538" /> + <stop + offset="1" + style="stop-color:#193D6B" + id="stop18540" /> + <a:midPointStop + offset="0" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="0.5" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="1" + style="stop-color:#193D6B" /> + </linearGradient> + <defs + id="defs18542" /> + <mask + maskUnits="userSpaceOnUse" + x="191.541" + y="217.631" + width="447.197" + height="527.73" + id="mask18544" + i:invertMask="false" + i:clipMask="true"> + <g + id="g18546" + style="filter:url(#Adobe_OpacityMaskFilter_3_)"> + <radialGradient + id="radialGradient18548" + cx="657.2124" + cy="155.23289" + r="606.95343" + fx="657.2124" + fy="155.23289" + gradientTransform="matrix(0.931,0,0,0.9299,28.7459,21.6629)" + gradientUnits="userSpaceOnUse"> + <stop + offset="0" + style="stop-color:#C9C9C8" + id="stop18550" /> + <stop + offset="0.0881" + style="stop-color:#C4C4C4" + id="stop18552" /> + <stop + offset="0.2192" + style="stop-color:#B8B8B7" + id="stop18554" /> + <stop + offset="0.3769" + style="stop-color:#A1A0A0" + id="stop18556" /> + <stop + offset="0.5556" + style="stop-color:#7F7F7E" + id="stop18558" /> + <stop + offset="0.7517" + style="stop-color:#4F4F4E" + id="stop18560" /> + <stop + offset="0.9595" + style="stop-color:#121212" + id="stop18562" /> + <stop + offset="1" + style="stop-color:#000000" + id="stop18564" /> + <a:midPointStop + offset="0" + style="stop-color:#C9C9C8" /> + <a:midPointStop + offset="0.6215" + style="stop-color:#C9C9C8" /> + <a:midPointStop + offset="1" + style="stop-color:#000000" /> + </radialGradient> + <circle + i:knockout="Off" + cx="408.427" + cy="444.22601" + r="302.95401" + id="circle18566" + sodipodi:cx="408.427" + sodipodi:cy="444.22601" + sodipodi:rx="302.95401" + sodipodi:ry="302.95401" + style="fill:url(#XMLID_11_)" /> + </g> + </mask> + <linearGradient + id="linearGradient18568" + gradientUnits="userSpaceOnUse" + x1="517.18988" + y1="239.939" + x2="410.27759" + y2="522.02393"> + <stop + offset="0" + style="stop-color:#FFFFFF" + id="stop18570" /> + <stop + offset="0.9944" + style="stop-color:#FFFFFF" + id="stop18572" /> + <a:midPointStop + offset="0" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="0.6215" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="0.9944" + style="stop-color:#FFFFFF" /> + </linearGradient> + <defs + id="defs18574" /> + <mask + maskUnits="userSpaceOnUse" + x="243.061" + y="279.317" + width="252.506" + height="187.183" + id="mask18576" + i:invertMask="false" + i:clipMask="true"> + <g + id="g18578" + style="filter:url(#Adobe_OpacityMaskFilter_4_)"> + <linearGradient + id="linearGradient18580" + gradientUnits="userSpaceOnUse" + x1="340.0488" + y1="266.93359" + x2="372.38071" + y2="417.81519"> + <stop + offset="0" + style="stop-color:#FFFFFF" + id="stop18582" /> + <stop + offset="0.0867" + style="stop-color:#F8F8F8" + id="stop18584" /> + <stop + offset="0.2156" + style="stop-color:#E4E4E4" + id="stop18586" /> + <stop + offset="0.3708" + style="stop-color:#C2C2C2" + id="stop18588" /> + <stop + offset="0.5465" + style="stop-color:#949494" + id="stop18590" /> + <stop + offset="0.7394" + style="stop-color:#595959" + id="stop18592" /> + <stop + offset="0.9438" + style="stop-color:#151515" + id="stop18594" /> + <stop + offset="0.9944" + style="stop-color:#000000" + id="stop18596" /> + <a:midPointStop + offset="0" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="0.6215" + style="stop-color:#FFFFFF" /> + <a:midPointStop + offset="0.9944" + style="stop-color:#000000" /> + </linearGradient> + <ellipse + i:knockout="Off" + cx="363.052" + cy="374.28201" + rx="215.32401" + ry="183.32201" + id="ellipse18598" + sodipodi:cx="363.052" + sodipodi:cy="374.28201" + sodipodi:rx="215.32401" + sodipodi:ry="183.32201" + style="fill:url(#XMLID_14_)" /> + </g> + </mask> + </g> + </g> + <g + id="g18659" + transform="matrix(0.500001,0,0,0.500001,441.0467,-17.533413)" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/16x16/actions/amarok_settings_engine.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <g + style="display:inline" + id="g18661" + transform="matrix(0.792918,0,0,0.76641,745.2599,642.905)" + inkscape:export-filename="/home/lando/Projects/Amarok/Setting-dialog/amarok_settings_engine.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <g + transform="matrix(0.710065,0,0,0.63994,617.4801,45.62798)" + id="g18663"> + <path + id="path18665" + style="fill:#ffffff" + d="M 45.936,27.94 L 45.233,26.783 L 44.282,32.5 L 47.689,30.98 L 45.936,27.94 z " /> + <path + id="path18667" + style="fill:#ffffff" + d="M 85.14,50.286 L 82.531,50.264 L 81.42,50.308 L 81.646,54.354 L 85.14,50.286 z " /> + <path + id="path18669" + style="fill:#ffffff" + d="M 38.56,42.962 L 39.772,37.613 L 36.078,34.799 L 35.265,34.352 L 34.427,40.249 L 37.467,43.023 L 38.56,42.962 z " /> + <path + id="path18671" + style="fill:#ffffff" + d="M 39.6,59.891 L 42.767,56.869 L 39.487,50.521 L 32.926,49.88 L 32.218,43.364 L 32.13,43.361 L 31.005,49.145 L 31.67,55.573 L 37.786,56.202 L 39.6,59.891 z " /> + <path + id="path18673" + style="fill:#ffffff" + d="M 84.592,63.482 L 80.635,61.503 L 77.141,65.853 L 78.351,68.12 L 80.929,69.476 L 85.687,64.113 L 84.592,63.482 z " /> + </g> + <path + sodipodi:nodetypes="cscscsc" + id="path18675" + style="fill:url(#linearGradient18709);fill-opacity:1" + d="M 665.32854,69.75563 C 659.85394,66.552731 653.30216,66.889339 650.69551,70.508199 C 649.86758,71.658171 649.53882,73.000766 649.64817,74.399674 C 651.7153,69.674455 659.29717,69.172118 664.14053,72.23871 C 668.31358,74.879743 670.55782,80.390816 668.09638,84.108203 C 669.0784,83.624409 669.90988,82.957591 670.52196,82.107751 C 673.12932,78.48953 670.80386,72.95917 665.32854,69.75563 z " /> + <path + id="path18677" + style="fill:url(#linearGradient18711)" + d="M 644.49735,84.876771 L 643.41379,88.344605 L 647.14306,92.145209 L 650.77717,89.775511 L 655.21437,92.23416 L 655.26123,96.884604 L 660.00234,97.735084 L 662.73751,94.462431 L 662.90012,93.473724 L 665.4102,93.396931 L 667.95933,96.929399 L 671.49901,95.230999 L 674.62685,91.905871 L 674.68507,91.878354 L 673.1137,89.221963 L 674.94425,90.089721 L 678.36534,86.856745 L 678.88795,83.390191 L 675.49455,81.108805 L 675.45195,80.41191 L 677.87469,77.731842 L 676.15278,74.582697 L 673.31749,74.035548 L 670.78611,71.023351 L 672.00103,69.563009 L 669.11603,67.142756 L 666.93897,68.100746 L 663.29278,65.864795 L 663.24166,64.22143 L 659.29228,62.715011 L 658.38055,64.86009 L 655.13981,64.312301 L 653.36749,62.30417 L 649.42876,62.748288 L 648.92319,66.425383 L 648.05052,66.77607 L 644.6067,65.224216 L 642.38633,67.547837 L 641.92621,71.382998 L 644.08481,73.158191 L 640.13826,73.254182 L 639.49636,77.076543 L 639.96856,81.190077 L 644.31132,81.592599 L 645.59866,83.952698 L 644.49735,84.876771 z M 670.52196,82.108391 C 667.9146,85.727891 661.36354,86.065779 655.88822,82.86224 C 650.41291,79.658701 648.08815,74.12898 650.69551,70.50884 C 653.30216,66.88998 659.85394,66.552731 665.32854,69.75627 C 670.80386,72.95917 673.12932,78.48953 670.52196,82.108391 z " /> + <path + id="path18679" + style="fill:url(#linearGradient18713);fill-opacity:1" + d="M 675.251,81.065288 L 675.05147,77.830392 L 677.59421,77.740161 L 675.89858,74.639652 L 673.1066,74.100823 L 670.61356,71.134701 L 671.81002,69.696756 L 668.96834,67.3149 L 666.82465,68.258811 L 663.23456,66.057418 L 663.18485,64.43965 L 659.29512,62.957548 L 658.3983,65.068711 L 655.20798,64.529881 L 653.46264,62.551827 L 649.77385,63.001705 L 651.46878,65.652976 L 648.22875,66.956533 L 644.83818,65.428357 L 642.79462,67.675185 L 645.93524,69.832423 L 645.08813,73.202347 L 640.65022,73.426966 L 641.14939,77.561618 L 645.73571,77.965419 L 648.02851,81.963764 L 644.78848,84.750063 L 648.72721,88.569224 L 652.56583,86.187368 L 657.25226,88.658176 L 657.30196,93.331657 L 662.68639,94.230133 L 663.38509,89.961734 L 668.02111,89.826706 L 670.71297,93.376453 L 674.45288,91.669094 L 672.05996,87.625312 L 674.50188,84.88445 L 678.09268,86.502219 L 678.59043,83.312117 L 675.251,81.065288 z M 670.35297,82.051436 C 667.7875,85.614622 661.33584,85.94675 655.94574,82.793126 C 650.55492,79.638862 648.26567,74.194254 650.83256,70.630428 C 653.39944,67.066603 659.84968,66.734474 665.2412,69.888738 C 670.63131,73.043002 672.91985,78.488251 670.35297,82.051436 z " /> + <path + id="path18681" + style="fill:url(#linearGradient18715);fill-opacity:1" + d="M 650.29149,70.272702 C 647.54424,74.086104 649.93716,79.897398 655.62621,83.226366 C 658.87689,85.127628 662.72473,85.897475 665.91932,85.285052 C 668.112,84.865251 669.84314,83.847748 670.92599,82.344529 C 671.68718,81.287988 672.07416,80.064423 672.07416,78.742947 C 672.07416,78.186199 672.00529,77.612172 671.86753,77.026628 C 671.188,74.147538 668.84124,71.294046 665.59056,69.391505 C 659.90151,66.063817 653.03873,66.458659 650.29149,70.272702 z M 656.15095,82.498114 C 650.90711,79.430242 648.64129,74.157777 651.09883,70.744977 C 653.55708,67.332178 659.82269,67.051884 665.06653,70.119756 C 668.10703,71.899429 670.29687,74.548781 670.92528,77.20773 C 671.33712,78.954127 671.05877,80.566776 670.11651,81.872253 C 669.17639,83.17837 667.65543,84.064687 665.71766,84.436492 C 662.76805,85.001559 659.19145,84.277787 656.15095,82.498114 z " /> + <path + id="path18683" + style="fill:url(#linearGradient18717);fill-opacity:1" + d="M 653.30784,61.87477 L 649.36911,62.318888 C 649.15041,62.343846 648.97858,62.498711 648.95088,62.696453 C 648.95088,62.696453 648.53762,65.706091 648.4794,66.130371 C 648.30969,66.198204 648.21384,66.237241 648.06259,66.297395 C 647.70188,66.13549 644.82256,64.837692 644.82256,64.837692 C 644.62516,64.74874 644.38658,64.792256 644.24244,64.943282 L 642.02206,67.267544 C 641.95816,67.334097 641.9184,67.41601 641.90845,67.503682 L 641.44833,71.338841 C 641.44691,71.3542 641.44549,71.369559 641.44549,71.385558 C 641.44549,71.506506 641.5023,71.622975 641.60242,71.705527 C 641.60242,71.705527 642.26207,72.248196 642.87911,72.755669 C 641.71745,72.783826 640.12619,72.822862 640.12619,72.822862 C 639.89258,72.827982 639.69731,72.983488 639.66252,73.191468 L 639.02133,77.013189 C 639.01778,77.034307 639.01565,77.056705 639.01565,77.078463 C 639.01565,77.093181 639.01636,77.10854 639.01849,77.123259 L 639.49068,81.236792 C 639.51411,81.440934 639.69376,81.602198 639.92098,81.623316 C 639.92098,81.623316 643.53024,81.957365 643.99818,82.001521 C 644.16575,82.309332 644.79274,83.456744 645.01002,83.856066 C 644.7054,84.110763 644.17143,84.559361 644.17143,84.559361 C 644.10682,84.613116 644.05995,84.682229 644.03652,84.759662 L 642.95225,88.228136 C 642.94018,88.267172 642.9345,88.306209 642.9345,88.345245 C 642.9345,88.450196 642.9771,88.552586 643.05521,88.633218 L 646.78376,92.433182 C 646.94779,92.600846 647.22613,92.628363 647.42708,92.498456 C 647.42708,92.498456 650.35184,90.591434 650.8006,90.298342 C 651.25718,90.551118 654.35519,92.267437 654.73721,92.479897 C 654.74147,92.911216 654.78194,96.889723 654.78194,96.889723 C 654.78336,97.095144 654.94526,97.271767 655.16893,97.311443 L 659.91003,98.161923 C 660.09181,98.19456 660.27785,98.130567 660.38933,97.99682 L 663.1245,94.724806 C 663.17278,94.666572 663.20403,94.599378 663.21681,94.527065 C 663.21681,94.527065 663.27219,94.187897 663.31977,93.896724 C 663.81326,93.881365 664.80238,93.851289 665.15742,93.839769 C 665.42014,94.204536 667.55815,97.166817 667.55815,97.166817 C 667.69377,97.354959 667.96715,97.418954 668.18798,97.313364 L 671.72836,95.614322 C 671.78091,95.589365 671.8299,95.553528 671.86895,95.511932 C 671.86895,95.511932 674.87466,92.316712 674.95561,92.23096 C 675.04366,92.176565 675.11537,92.102332 675.14733,92.008901 C 675.16224,91.966666 675.16934,91.92187 675.16934,91.878354 C 675.16934,91.80796 675.15017,91.738206 675.11324,91.674852 C 675.11324,91.674852 674.6936,90.966439 674.2775,90.263145 C 674.44223,90.341218 674.72413,90.474965 674.72413,90.474965 C 674.91443,90.564557 675.14875,90.53 675.29644,90.389853 L 678.71683,87.156877 C 678.78783,87.090323 678.83186,87.006491 678.84606,86.91626 L 679.36867,83.449705 C 679.37151,83.429866 679.37293,83.410028 679.37293,83.39083 C 679.37293,83.254523 679.30192,83.125256 679.1805,83.042703 C 679.1805,83.042703 676.29053,81.099846 675.96745,80.882265 C 675.95893,80.747879 675.95467,80.675565 675.94757,80.555897 C 676.16059,80.320399 678.25315,78.005736 678.25315,78.005736 C 678.32416,77.927023 678.36108,77.829753 678.36108,77.731842 C 678.36108,77.666567 678.34475,77.600654 678.31138,77.53986 L 676.58947,74.390716 C 676.52485,74.271687 676.40201,74.185934 676.25858,74.157777 C 676.25858,74.157777 673.90542,73.70406 673.59725,73.644545 C 673.4169,73.430166 671.74185,71.436112 671.39321,71.021432 C 671.68647,70.668825 672.39085,69.822824 672.39085,69.822824 C 672.45476,69.745391 672.48671,69.65324 672.48671,69.562368 C 672.48671,69.44526 672.43488,69.32943 672.33334,69.244318 L 669.44692,66.825985 C 669.30136,66.703117 669.08692,66.67496 668.90798,66.753672 C 668.90798,66.753672 667.37495,67.428169 666.98157,67.601593 C 666.60098,67.368014 664.10865,65.839838 663.77066,65.632497 C 663.76001,65.299729 663.72592,64.210551 663.72592,64.210551 C 663.72095,64.040966 663.60663,63.889301 663.43338,63.823387 L 659.48328,62.316969 C 659.36257,62.270893 659.22695,62.270893 659.10695,62.316969 C 658.98695,62.363045 658.89251,62.451356 658.84564,62.561426 C 658.84564,62.561426 658.28256,63.888661 658.07806,64.367976 C 657.50646,64.271345 655.68301,63.963534 655.40822,63.916818 C 655.21224,63.69412 653.74738,62.035395 653.74738,62.035395 C 653.63945,61.916366 653.474,61.855572 653.30784,61.87477 z M 653.15873,62.766207 C 653.43424,63.079137 654.76277,64.582996 654.76277,64.582996 C 654.83449,64.664908 654.93744,64.719943 655.05106,64.739781 L 658.29179,65.286929 C 658.52044,65.325967 658.74695,65.211417 658.83002,65.016236 C 658.83002,65.016236 659.33275,63.832986 659.56281,63.290957 C 660.22175,63.542454 662.33988,64.349417 662.76876,64.512603 C 662.78083,64.897846 662.81136,65.876954 662.81136,65.876954 C 662.81562,66.016461 662.89302,66.145089 663.02154,66.223802 L 666.66773,68.459752 C 666.80974,68.547424 666.99436,68.559583 667.14986,68.490469 C 667.14986,68.490469 668.58703,67.858208 669.03011,67.663667 C 669.38799,67.963158 670.90256,69.232159 671.357,69.613563 C 671.03534,70.000088 670.40125,70.760976 670.40125,70.760976 C 670.33663,70.838409 670.30468,70.92992 670.30468,71.022071 C 670.30468,71.114223 670.33734,71.206374 670.40267,71.284446 L 672.93476,74.296644 C 673.00435,74.379196 673.10376,74.43679 673.21737,74.459189 C 673.21737,74.459189 675.43916,74.887948 675.82473,74.962181 C 675.99088,75.268073 677.09078,77.276204 677.30664,77.672327 C 676.97646,78.037733 675.07845,80.138016 675.07845,80.138016 C 675.00886,80.215448 674.97052,80.312079 674.97052,80.41191 C 674.97052,80.420229 674.97052,80.427908 674.97123,80.436228 L 675.01525,81.132482 C 675.02307,81.26047 675.09336,81.379499 675.20768,81.456292 C 675.20768,81.456292 677.97694,83.319157 678.37244,83.585372 C 678.30996,84.001333 677.94498,86.422226 677.90948,86.659004 C 677.72202,86.835627 675.26307,89.159889 674.84697,89.552812 C 674.41028,89.346112 673.33666,88.838639 673.33666,88.838639 C 673.1492,88.749047 672.92056,88.781044 672.77074,88.916712 C 672.67914,88.999903 672.63157,89.110614 672.63157,89.222603 C 672.63157,89.291717 672.65003,89.36211 672.68766,89.426744 C 672.68766,89.426744 673.82661,91.351043 674.09288,91.800921 C 673.8053,92.106812 671.29877,94.772162 671.19013,94.886711 C 671.05238,94.953265 668.72194,96.07188 668.12123,96.360493 C 667.72643,95.813344 665.81281,93.161433 665.81281,93.161433 C 665.72121,93.034085 665.56144,92.959852 665.39245,92.963692 L 662.88237,93.040484 C 662.65018,93.048163 662.45633,93.203669 662.42224,93.41101 C 662.42224,93.41101 662.30721,94.117503 662.27952,94.290287 C 662.14816,94.445792 660.10743,96.888443 659.79926,97.257048 C 659.32352,97.171297 656.34266,96.636307 655.73911,96.528157 C 655.73272,95.925334 655.69508,92.23096 655.69508,92.23096 C 655.69366,92.081854 655.60704,91.943628 655.46573,91.865554 L 651.02782,89.406906 C 650.86238,89.314114 650.65078,89.321154 650.49315,89.424184 C 650.49315,89.424184 647.78922,91.187858 647.21264,91.563503 C 646.74542,91.087387 644.23747,88.531468 643.94634,88.235176 C 644.06634,87.851851 644.85878,85.315769 644.92268,85.112269 C 645.086,84.974682 645.92458,84.272028 645.92458,84.272028 C 646.0247,84.187555 646.07796,84.071726 646.07796,83.953977 C 646.07796,83.888703 646.06234,83.82407 646.02896,83.762635 L 644.74162,81.402537 C 644.66848,81.26879 644.52362,81.177918 644.35889,81.1632 C 644.35889,81.1632 641.0514,80.856668 640.40596,80.796514 C 640.33921,80.218008 639.99128,77.186613 639.97992,77.089342 C 639.99625,76.992711 640.45424,74.260808 640.55223,73.680382 C 641.20194,73.664383 644.09617,73.59399 644.09617,73.59399 C 644.29214,73.589511 644.46611,73.477521 644.53428,73.311776 C 644.55416,73.262501 644.56481,73.211306 644.56481,73.160111 C 644.56481,73.040442 644.50942,72.923333 644.40788,72.840141 C 644.40788,72.840141 642.73284,71.46235 642.42964,71.212773 C 642.47438,70.838409 642.81947,67.966358 642.84716,67.73086 C 643.00551,67.565756 644.35676,66.150849 644.72528,65.765605 C 645.27132,66.011342 647.83324,67.165794 647.83324,67.165794 C 647.96247,67.224028 648.11372,67.226588 648.24437,67.174113 L 649.11704,66.823426 C 649.27112,66.761352 649.37834,66.631444 649.39893,66.479778 C 649.39893,66.479778 649.78237,63.692199 649.85835,63.138012 C 650.42427,63.074658 652.73979,62.812922 653.15873,62.766207 z " /> + </g> + <g + style="display:inline" + id="g18685" + transform="matrix(0.899443,0,0,0.869374,663.1337,632.7971)" + inkscape:export-filename="/home/lando/Projects/Amarok/Setting-dialog/amarok_settings_engine.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <path + sodipodi:type="arc" + style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:21.03624916;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0.68131869" + id="path18687" + sodipodi:cx="-7850.3027" + sodipodi:cy="-3285.1472" + sodipodi:rx="25.97311" + sodipodi:ry="5.306335" + d="M -7824.3296 -3285.1472 A 25.97311 5.306335 0 1 1 -7876.2758,-3285.1472 A 25.97311 5.306335 0 1 1 -7824.3296 -3285.1472 z" + transform="matrix(0.116367,-0.126279,0.138815,0.105858,2044.201,-572.7461)" /> + <path + sodipodi:nodetypes="ccccsccsccsccccccsssccsccscccc" + id="path18689" + d="M 684.58932,62.015066 C 683.95163,62.03676 682.73778,63.425764 682.28042,63.646529 C 681.81946,63.869109 678.16982,66.391273 677.82409,66.836327 C 677.47838,67.281337 677.01748,67.46692 676.51816,67.429844 C 676.01883,67.392834 674.44026,67.377225 673.90249,67.673903 C 673.36463,67.970636 670.90988,70.397011 670.29527,70.693715 C 669.68068,70.990341 668.87865,71.141876 668.49449,71.178979 C 668.11043,71.216028 666.83809,72.584967 666.49238,72.844636 C 666.14667,73.10429 663.41458,74.2451 663.03046,74.467545 C 662.64631,74.690071 665.83291,77.807344 666.29388,77.881456 C 666.75485,77.955728 665.97647,79.643959 665.08957,79.643959 C 664.99189,79.643959 665.88202,82.441727 670.53651,84.906802 C 674.56037,87.037898 678.82957,86.094974 680.41507,85.578197 C 681.53629,85.212648 680.74985,85.010148 680.78834,84.416789 C 680.82666,83.823378 680.8587,83.043556 680.93555,82.376043 C 681.01244,81.708505 681.28161,76.107653 682.28042,74.624229 C 683.27912,73.140873 686.13308,71.360612 687.13185,71.175177 C 688.13068,70.989794 687.93916,70.619136 687.93916,70.285379 C 687.93916,69.95165 687.78546,69.209626 687.51651,69.098343 C 687.24765,68.987127 686.19822,69.654757 685.85251,69.728899 C 685.5068,69.803078 683.85489,69.914575 683.62435,69.543728 C 683.3939,69.172775 686.14108,63.906545 685.64166,63.276177 C 685.14247,62.645555 684.79657,62.015066 684.60452,62.015066 C 684.59957,62.015066 684.59434,62.014895 684.58932,62.015066 z M 676.42414,68.978694 C 676.73261,68.974532 676.94176,69.006232 676.94176,69.006232 C 677.11748,69.40176 676.29813,70.644697 675.53706,70.814323 C 674.77614,70.983791 673.01974,73.4706 672.96126,72.905408 C 672.90256,72.340212 673.66355,70.476005 674.54169,69.628234 C 675.09392,69.095034 675.90135,68.985752 676.42414,68.978694 z " + style="fill:url(#linearGradient18719);fill-opacity:1" /> + <path + sodipodi:nodetypes="csssssscccscsscsssssscsssscsssssssssssssssscssssccssssscscscscccccsscsssssssssssscsscccsccsccccccsssccssssscsssccssssssssssscsscsssssc" + id="path18691" + d="M 666.16392,81.423003 C 665.9031,81.091515 665.42361,80.435598 665.26754,80.004036 C 665.14138,79.655175 665.13889,79.611803 665.24522,79.614525 C 665.37457,79.61782 665.53249,79.582317 665.71495,79.414066 C 666.12829,79.032933 666.46122,78.479561 666.36093,78.193327 C 666.34151,78.137933 666.28178,78.082005 666.19489,78.037937 C 665.68311,77.778228 663.68597,75.546616 663.20752,74.699847 C 663.14869,74.595715 663.09317,74.467102 663.08417,74.414107 L 663.06779,74.317713 L 663.5356,74.095961 C 663.79289,73.973979 664.43521,73.676485 664.96298,73.434854 C 666.54934,72.708572 666.40066,72.802712 667.30233,71.953513 C 668.1459,71.159029 668.45291,70.912676 668.63209,70.886525 C 669.29678,70.789464 669.74333,70.680556 670.16886,70.511811 C 670.62212,70.332045 670.78417,70.203751 672.21307,68.893356 C 673.39223,67.812027 673.7656,67.48122 673.99398,67.315529 C 674.36671,67.045079 674.85595,66.975476 676.21407,66.999677 C 677.02172,67.014072 677.16671,67.009258 677.3026,66.963628 C 677.54873,66.881043 677.75482,66.732936 678.04247,66.432002 C 678.18878,66.278881 678.48874,66.009285 678.70903,65.832881 C 679.56399,65.148239 681.38883,63.831519 682.2969,63.244071 C 682.83405,62.896574 682.83553,62.895428 683.52111,62.285059 C 684.09054,61.778114 684.47641,61.493496 684.70119,61.414567 C 684.91293,61.34024 684.93449,61.341828 685.04373,61.439835 C 685.15035,61.535483 686.03394,62.9158 686.10698,63.100796 C 686.24105,63.440395 686.31503,63.800609 686.17623,64.085306 C 685.91549,64.62008 685.53887,65.389083 685.37514,65.683372 C 684.01271,68.277198 683.57638,69.315608 683.75865,69.53045 C 683.9225,69.723542 684.62451,69.838696 685.37986,69.796375 C 685.63126,69.7823 685.89265,69.757165 685.96072,69.740527 C 686.02879,69.723904 686.33237,69.592247 686.63534,69.447957 C 687.2815,69.140217 687.50858,69.064356 687.61979,69.119151 C 687.73185,69.174334 687.84105,69.402649 687.91551,69.737405 C 687.98654,70.056699 688.02311,70.630231 687.98411,70.812999 C 687.93246,71.054964 687.70898,71.202203 687.22201,71.315153 C 686.50051,71.482471 685.39489,72.093335 684.25696,72.95334 C 683.19009,73.759659 682.46764,74.510542 682.13211,75.161789 C 681.942,75.530794 681.88754,75.797654 681.7263,76.390906 C 681.60627,76.832449 680.94162,82.834448 680.93784,82.974322 C 680.93675,83.015121 681.05021,76.469849 681.04313,76.469662 C 681.03606,76.469473 680.47326,77.712213 680.46324,77.71196 C 680.45319,77.711706 679.26724,79.616271 679.14064,79.557035 C 679.01407,79.497811 680.84098,75.164632 681.29637,74.622279 C 681.72505,74.111718 682.69675,73.228218 683.92824,72.229312 C 684.68954,71.611793 685.08523,71.319382 685.27321,71.235423 C 685.34613,71.202844 685.65566,71.131984 685.96107,71.077938 C 686.71523,70.944468 686.90799,70.86055 686.9133,70.663386 C 686.91912,70.447624 686.66022,70.304493 686.31274,70.331392 C 685.85732,70.366626 685.34615,70.54459 684.72472,70.884297 C 684.05931,71.248032 683.51826,71.34764 682.8754,71.224763 C 682.69316,71.189901 682.45974,71.128317 682.35669,71.087851 C 682.13555,71.001063 682.1061,70.935662 682.16257,70.656928 C 682.28424,70.056285 682.7318,69.095497 684.09589,66.506489 C 684.52724,65.687816 684.91444,64.919869 684.95636,64.799939 C 684.99825,64.680011 685.1686,64.493296 685.20972,64.242845 C 685.27332,63.855494 684.94437,63.305418 684.69042,63.096047 C 684.1717,62.710286 684.01007,62.609168 683.74917,62.719264 C 683.27598,62.918962 682.63747,63.885861 680.89506,66.251944 C 678.10234,70.044237 677.77609,70.437338 677.00798,70.935529 C 675.9344,71.631833 674.25248,73.011563 672.89065,74.31307 C 671.94197,75.219744 671.23101,76.306587 670.94267,76.585575 C 670.75542,76.76678 669.96025,77.745288 669.68107,77.980021 C 668.26676,78.87945 667.27756,79.841733 666.4157,81.272282 L 666.26937,81.5468 L 666.16392,81.423003 z M 668.6295,76.32153 C 669.21552,76.035684 669.9402,75.554275 670.29047,75.218104 C 670.57943,74.940811 670.69555,74.663571 670.53426,74.6361 C 670.43004,74.618356 670.14725,74.769144 669.42758,75.226216 C 668.60241,75.75033 668.36243,75.871421 668.1267,75.882628 C 667.6555,75.905015 666.59584,75.72705 666.06717,75.536718 C 665.70072,75.404769 665.57392,75.331295 665.45729,75.183336 C 665.38321,75.089358 665.37887,75.065462 665.39778,74.856572 C 665.42208,74.588243 665.56219,74.28492 665.80217,73.981211 C 665.92518,73.825514 666.03467,73.73747 666.35462,73.537052 C 667.27497,72.96057 668.28907,72.478015 669.33316,72.119738 C 669.57465,72.036872 669.8005,71.958064 669.83504,71.944615 C 669.88896,71.923614 669.87183,71.906057 669.71409,71.820563 C 669.45042,71.677633 669.10707,71.548405 668.91845,71.521096 C 668.73014,71.493813 668.65946,71.521481 668.21594,71.796028 C 667.88157,72.003024 666.25388,73.091052 665.48621,73.620705 C 665.18146,73.830983 664.72391,74.14191 664.46942,74.311617 C 664.21494,74.481377 663.98901,74.640691 663.96737,74.665692 C 663.90854,74.733682 663.91479,74.906936 663.98115,75.047732 C 664.05408,75.202428 664.40387,75.635913 664.59279,75.805673 L 664.73365,75.93226 L 666.46235,76.190566 C 667.41313,76.332656 668.22595,76.451329 668.26859,76.454332 C 668.31124,76.457306 668.47365,76.397562 668.6295,76.32153 z M 668.25415,75.137027 C 668.72466,75.109689 668.82443,75.064367 669.20342,74.70609 C 669.59232,74.338446 669.98504,73.821189 670.13614,73.477602 L 670.18873,73.358048 L 670.0701,73.373682 C 670.00486,73.382261 669.32753,73.452719 668.5649,73.53022 L 667.17835,73.671095 L 667.05139,73.770438 C 666.8115,73.958127 666.48365,74.29119 666.35948,74.473383 C 666.21238,74.689172 666.19775,74.804299 666.3076,74.881267 C 666.43204,74.968454 666.84529,75.068852 667.23887,75.107529 C 667.76006,75.158734 667.84061,75.161069 668.25415,75.137027 z M 673.23289,72.884003 C 673.36857,72.793999 673.63342,72.530209 674.28656,71.834505 C 674.76864,71.320997 675.22409,70.892729 675.40078,70.786743 C 675.46833,70.746223 675.60467,70.684907 675.70377,70.650443 C 675.92776,70.572528 676.07455,70.48466 676.28578,70.302053 C 676.73011,69.917918 677.18695,69.102408 677.12911,68.796643 C 677.11207,68.706574 677.1094,68.705506 676.85795,68.688776 C 675.95769,68.628965 675.25732,68.824768 674.75202,69.277624 C 674.16986,69.799364 673.40627,71.172265 673.12421,72.204378 C 673.01269,72.612445 672.98317,72.817187 673.02395,72.899506 C 673.06635,72.985066 673.08235,72.983877 673.23289,72.884003 z M 671.57619,71.796562 C 672.34776,71.643906 672.98398,71.514265 672.99004,71.508448 C 672.99608,71.502658 673.07634,71.260586 673.16839,70.970524 C 673.54217,69.792774 673.83364,69.271727 674.26438,69.011271 C 674.90419,68.624388 676.2258,68.209742 676.95832,68.166034 C 677.22424,68.150158 677.26926,68.137257 677.51161,68.007456 C 677.8718,67.814601 678.3997,67.456604 679.12431,66.913865 C 679.45905,66.663135 679.91088,66.333048 680.12838,66.180365 C 680.68623,65.788718 680.96159,65.51887 680.96589,65.359637 C 680.96661,65.332846 680.95475,65.310646 680.93954,65.310246 C 680.92433,65.309872 680.56473,65.552198 680.14043,65.848756 C 679.71612,66.145328 679.09133,66.572036 678.75201,66.797005 C 677.56622,67.583204 677.74205,67.5241 676.57466,67.528756 C 676.06082,67.530783 675.36265,67.538481 675.02318,67.545859 C 674.42556,67.558856 674.40209,67.56187 674.28268,67.640998 C 674.21487,67.685947 673.78028,68.103582 673.31692,68.569102 C 672.3073,69.583402 671.44162,70.400928 671.10522,70.657742 C 670.9698,70.761139 670.75997,70.888754 670.63892,70.941346 C 670.39611,71.046839 670.3172,71.116109 670.21049,71.317382 C 670.13004,71.469129 670.04208,71.806968 670.03645,71.985906 C 670.03309,72.092787 670.03952,72.102793 670.10304,72.089172 C 670.14171,72.080887 670.80463,71.949217 671.57619,71.796562 z " + style="fill:url(#linearGradient18721);fill-opacity:1" /> + <g + style="fill:#002ddb;fill-opacity:1" + id="g18693"> + <path + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:21.03624916;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + i:knockout="Off" + d="M 680.83905,76.838803 C 680.83905,76.838803 679.45446,81.3797 679.41945,81.449838 C 679.22156,81.846006 678.85659,82.994032 678.98224,83.364797 C 679.13021,83.801538 679.68481,84.436589 679.94365,84.754121 C 680.2025,85.071759 680.31342,85.230473 680.23945,85.349692 C 680.16554,85.468783 679.20413,85.468783 679.20413,85.468783 C 679.20413,85.468783 678.46465,84.396936 678.31664,83.999943 C 678.16882,83.603079 678.09481,82.769436 678.24277,82.491624 C 678.39065,82.213703 680.83905,76.838803 680.83905,76.838803 z " + id="path18695" /> + <polygon + transform="matrix(4.348075e-2,0,0,4.188821e-2,659.2285,53.92804)" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:17.46648407;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + i:knockout="Off" + a:adobe-blending-mode="lighten" + points="272.565,567.14 214.39,617.501 287.326,574.086 272.565,567.14 " + id="polygon18697" /> + <polygon + transform="matrix(4.348075e-2,0,0,4.188821e-2,659.2285,53.92804)" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:17.46648407;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + i:knockout="Off" + a:adobe-blending-mode="lighten" + points="329.873,549.776 230.887,678.282 344.633,558.457 329.873,549.776 " + id="polygon18699" /> + <polygon + transform="matrix(4.348075e-2,0,0,4.188821e-2,659.2285,53.92804)" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:17.46648407;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + i:knockout="Off" + a:adobe-blending-mode="lighten" + points="337.688,594.058 262.146,713.882 355.053,601.872 337.688,594.058 " + id="polygon18701" /> + <polygon + transform="matrix(4.348075e-2,0,0,4.188821e-2,659.2285,53.92804)" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:17.46648407;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + i:knockout="Off" + a:adobe-blending-mode="lighten" + points="381.97,588.847 318.584,737.327 402.809,593.19 381.97,588.847 " + id="polygon18703" /> + <polygon + transform="matrix(4.348075e-2,0,0,4.188821e-2,659.2285,53.92804)" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:17.46648407;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + i:knockout="Off" + a:adobe-blending-mode="lighten" + points="407.151,627.921 401.94,763.375 424.515,629.658 407.151,627.921 " + id="polygon18705" /> + </g> + <path + sodipodi:nodetypes="ccc" + id="path18707" + d="M 665.61116,79.917164 C 666.44697,82.39457 673.07693,87.947061 680.79385,85.431014 C 680.71584,83.40215 681.5919,77.986014 681.41239,76.464632" + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#002ddb;stroke-width:0.57512665;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + </g> + <g + transform="matrix(0.687502,0,0,0.687502,238.7243,-152.18608)" + id="g18737" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/22x22/actions/amarok_settings_engine.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Setting-dialog/amarok_settings_engine.png" + transform="matrix(0.792918,0,0,0.76641,745.2599,642.905)" + id="g18739" + style="display:inline"> + <g + id="g18741" + transform="matrix(0.710065,0,0,0.63994,617.4801,45.62798)"> + <path + d="M 45.936,27.94 L 45.233,26.783 L 44.282,32.5 L 47.689,30.98 L 45.936,27.94 z " + style="fill:#ffffff" + id="path18743" /> + <path + d="M 85.14,50.286 L 82.531,50.264 L 81.42,50.308 L 81.646,54.354 L 85.14,50.286 z " + style="fill:#ffffff" + id="path18745" /> + <path + d="M 38.56,42.962 L 39.772,37.613 L 36.078,34.799 L 35.265,34.352 L 34.427,40.249 L 37.467,43.023 L 38.56,42.962 z " + style="fill:#ffffff" + id="path18747" /> + <path + d="M 39.6,59.891 L 42.767,56.869 L 39.487,50.521 L 32.926,49.88 L 32.218,43.364 L 32.13,43.361 L 31.005,49.145 L 31.67,55.573 L 37.786,56.202 L 39.6,59.891 z " + style="fill:#ffffff" + id="path18749" /> + <path + d="M 84.592,63.482 L 80.635,61.503 L 77.141,65.853 L 78.351,68.12 L 80.929,69.476 L 85.687,64.113 L 84.592,63.482 z " + style="fill:#ffffff" + id="path18751" /> + </g> + <path + d="M 665.32854,69.75563 C 659.85394,66.552731 653.30216,66.889339 650.69551,70.508199 C 649.86758,71.658171 649.53882,73.000766 649.64817,74.399674 C 651.7153,69.674455 659.29717,69.172118 664.14053,72.23871 C 668.31358,74.879743 670.55782,80.390816 668.09638,84.108203 C 669.0784,83.624409 669.90988,82.957591 670.52196,82.107751 C 673.12932,78.48953 670.80386,72.95917 665.32854,69.75563 z " + style="fill:url(#linearGradient18787);fill-opacity:1" + id="path18753" + sodipodi:nodetypes="cscscsc" /> + <path + d="M 644.49735,84.876771 L 643.41379,88.344605 L 647.14306,92.145209 L 650.77717,89.775511 L 655.21437,92.23416 L 655.26123,96.884604 L 660.00234,97.735084 L 662.73751,94.462431 L 662.90012,93.473724 L 665.4102,93.396931 L 667.95933,96.929399 L 671.49901,95.230999 L 674.62685,91.905871 L 674.68507,91.878354 L 673.1137,89.221963 L 674.94425,90.089721 L 678.36534,86.856745 L 678.88795,83.390191 L 675.49455,81.108805 L 675.45195,80.41191 L 677.87469,77.731842 L 676.15278,74.582697 L 673.31749,74.035548 L 670.78611,71.023351 L 672.00103,69.563009 L 669.11603,67.142756 L 666.93897,68.100746 L 663.29278,65.864795 L 663.24166,64.22143 L 659.29228,62.715011 L 658.38055,64.86009 L 655.13981,64.312301 L 653.36749,62.30417 L 649.42876,62.748288 L 648.92319,66.425383 L 648.05052,66.77607 L 644.6067,65.224216 L 642.38633,67.547837 L 641.92621,71.382998 L 644.08481,73.158191 L 640.13826,73.254182 L 639.49636,77.076543 L 639.96856,81.190077 L 644.31132,81.592599 L 645.59866,83.952698 L 644.49735,84.876771 z M 670.52196,82.108391 C 667.9146,85.727891 661.36354,86.065779 655.88822,82.86224 C 650.41291,79.658701 648.08815,74.12898 650.69551,70.50884 C 653.30216,66.88998 659.85394,66.552731 665.32854,69.75627 C 670.80386,72.95917 673.12932,78.48953 670.52196,82.108391 z " + style="fill:url(#linearGradient18789)" + id="path18755" /> + <path + d="M 675.251,81.065288 L 675.05147,77.830392 L 677.59421,77.740161 L 675.89858,74.639652 L 673.1066,74.100823 L 670.61356,71.134701 L 671.81002,69.696756 L 668.96834,67.3149 L 666.82465,68.258811 L 663.23456,66.057418 L 663.18485,64.43965 L 659.29512,62.957548 L 658.3983,65.068711 L 655.20798,64.529881 L 653.46264,62.551827 L 649.77385,63.001705 L 651.46878,65.652976 L 648.22875,66.956533 L 644.83818,65.428357 L 642.79462,67.675185 L 645.93524,69.832423 L 645.08813,73.202347 L 640.65022,73.426966 L 641.14939,77.561618 L 645.73571,77.965419 L 648.02851,81.963764 L 644.78848,84.750063 L 648.72721,88.569224 L 652.56583,86.187368 L 657.25226,88.658176 L 657.30196,93.331657 L 662.68639,94.230133 L 663.38509,89.961734 L 668.02111,89.826706 L 670.71297,93.376453 L 674.45288,91.669094 L 672.05996,87.625312 L 674.50188,84.88445 L 678.09268,86.502219 L 678.59043,83.312117 L 675.251,81.065288 z M 670.35297,82.051436 C 667.7875,85.614622 661.33584,85.94675 655.94574,82.793126 C 650.55492,79.638862 648.26567,74.194254 650.83256,70.630428 C 653.39944,67.066603 659.84968,66.734474 665.2412,69.888738 C 670.63131,73.043002 672.91985,78.488251 670.35297,82.051436 z " + style="fill:url(#linearGradient18791);fill-opacity:1" + id="path18757" /> + <path + d="M 650.29149,70.272702 C 647.54424,74.086104 649.93716,79.897398 655.62621,83.226366 C 658.87689,85.127628 662.72473,85.897475 665.91932,85.285052 C 668.112,84.865251 669.84314,83.847748 670.92599,82.344529 C 671.68718,81.287988 672.07416,80.064423 672.07416,78.742947 C 672.07416,78.186199 672.00529,77.612172 671.86753,77.026628 C 671.188,74.147538 668.84124,71.294046 665.59056,69.391505 C 659.90151,66.063817 653.03873,66.458659 650.29149,70.272702 z M 656.15095,82.498114 C 650.90711,79.430242 648.64129,74.157777 651.09883,70.744977 C 653.55708,67.332178 659.82269,67.051884 665.06653,70.119756 C 668.10703,71.899429 670.29687,74.548781 670.92528,77.20773 C 671.33712,78.954127 671.05877,80.566776 670.11651,81.872253 C 669.17639,83.17837 667.65543,84.064687 665.71766,84.436492 C 662.76805,85.001559 659.19145,84.277787 656.15095,82.498114 z " + style="fill:url(#linearGradient18794);fill-opacity:1" + id="path18759" /> + <path + d="M 653.30784,61.87477 L 649.36911,62.318888 C 649.15041,62.343846 648.97858,62.498711 648.95088,62.696453 C 648.95088,62.696453 648.53762,65.706091 648.4794,66.130371 C 648.30969,66.198204 648.21384,66.237241 648.06259,66.297395 C 647.70188,66.13549 644.82256,64.837692 644.82256,64.837692 C 644.62516,64.74874 644.38658,64.792256 644.24244,64.943282 L 642.02206,67.267544 C 641.95816,67.334097 641.9184,67.41601 641.90845,67.503682 L 641.44833,71.338841 C 641.44691,71.3542 641.44549,71.369559 641.44549,71.385558 C 641.44549,71.506506 641.5023,71.622975 641.60242,71.705527 C 641.60242,71.705527 642.26207,72.248196 642.87911,72.755669 C 641.71745,72.783826 640.12619,72.822862 640.12619,72.822862 C 639.89258,72.827982 639.69731,72.983488 639.66252,73.191468 L 639.02133,77.013189 C 639.01778,77.034307 639.01565,77.056705 639.01565,77.078463 C 639.01565,77.093181 639.01636,77.10854 639.01849,77.123259 L 639.49068,81.236792 C 639.51411,81.440934 639.69376,81.602198 639.92098,81.623316 C 639.92098,81.623316 643.53024,81.957365 643.99818,82.001521 C 644.16575,82.309332 644.79274,83.456744 645.01002,83.856066 C 644.7054,84.110763 644.17143,84.559361 644.17143,84.559361 C 644.10682,84.613116 644.05995,84.682229 644.03652,84.759662 L 642.95225,88.228136 C 642.94018,88.267172 642.9345,88.306209 642.9345,88.345245 C 642.9345,88.450196 642.9771,88.552586 643.05521,88.633218 L 646.78376,92.433182 C 646.94779,92.600846 647.22613,92.628363 647.42708,92.498456 C 647.42708,92.498456 650.35184,90.591434 650.8006,90.298342 C 651.25718,90.551118 654.35519,92.267437 654.73721,92.479897 C 654.74147,92.911216 654.78194,96.889723 654.78194,96.889723 C 654.78336,97.095144 654.94526,97.271767 655.16893,97.311443 L 659.91003,98.161923 C 660.09181,98.19456 660.27785,98.130567 660.38933,97.99682 L 663.1245,94.724806 C 663.17278,94.666572 663.20403,94.599378 663.21681,94.527065 C 663.21681,94.527065 663.27219,94.187897 663.31977,93.896724 C 663.81326,93.881365 664.80238,93.851289 665.15742,93.839769 C 665.42014,94.204536 667.55815,97.166817 667.55815,97.166817 C 667.69377,97.354959 667.96715,97.418954 668.18798,97.313364 L 671.72836,95.614322 C 671.78091,95.589365 671.8299,95.553528 671.86895,95.511932 C 671.86895,95.511932 674.87466,92.316712 674.95561,92.23096 C 675.04366,92.176565 675.11537,92.102332 675.14733,92.008901 C 675.16224,91.966666 675.16934,91.92187 675.16934,91.878354 C 675.16934,91.80796 675.15017,91.738206 675.11324,91.674852 C 675.11324,91.674852 674.6936,90.966439 674.2775,90.263145 C 674.44223,90.341218 674.72413,90.474965 674.72413,90.474965 C 674.91443,90.564557 675.14875,90.53 675.29644,90.389853 L 678.71683,87.156877 C 678.78783,87.090323 678.83186,87.006491 678.84606,86.91626 L 679.36867,83.449705 C 679.37151,83.429866 679.37293,83.410028 679.37293,83.39083 C 679.37293,83.254523 679.30192,83.125256 679.1805,83.042703 C 679.1805,83.042703 676.29053,81.099846 675.96745,80.882265 C 675.95893,80.747879 675.95467,80.675565 675.94757,80.555897 C 676.16059,80.320399 678.25315,78.005736 678.25315,78.005736 C 678.32416,77.927023 678.36108,77.829753 678.36108,77.731842 C 678.36108,77.666567 678.34475,77.600654 678.31138,77.53986 L 676.58947,74.390716 C 676.52485,74.271687 676.40201,74.185934 676.25858,74.157777 C 676.25858,74.157777 673.90542,73.70406 673.59725,73.644545 C 673.4169,73.430166 671.74185,71.436112 671.39321,71.021432 C 671.68647,70.668825 672.39085,69.822824 672.39085,69.822824 C 672.45476,69.745391 672.48671,69.65324 672.48671,69.562368 C 672.48671,69.44526 672.43488,69.32943 672.33334,69.244318 L 669.44692,66.825985 C 669.30136,66.703117 669.08692,66.67496 668.90798,66.753672 C 668.90798,66.753672 667.37495,67.428169 666.98157,67.601593 C 666.60098,67.368014 664.10865,65.839838 663.77066,65.632497 C 663.76001,65.299729 663.72592,64.210551 663.72592,64.210551 C 663.72095,64.040966 663.60663,63.889301 663.43338,63.823387 L 659.48328,62.316969 C 659.36257,62.270893 659.22695,62.270893 659.10695,62.316969 C 658.98695,62.363045 658.89251,62.451356 658.84564,62.561426 C 658.84564,62.561426 658.28256,63.888661 658.07806,64.367976 C 657.50646,64.271345 655.68301,63.963534 655.40822,63.916818 C 655.21224,63.69412 653.74738,62.035395 653.74738,62.035395 C 653.63945,61.916366 653.474,61.855572 653.30784,61.87477 z M 653.15873,62.766207 C 653.43424,63.079137 654.76277,64.582996 654.76277,64.582996 C 654.83449,64.664908 654.93744,64.719943 655.05106,64.739781 L 658.29179,65.286929 C 658.52044,65.325967 658.74695,65.211417 658.83002,65.016236 C 658.83002,65.016236 659.33275,63.832986 659.56281,63.290957 C 660.22175,63.542454 662.33988,64.349417 662.76876,64.512603 C 662.78083,64.897846 662.81136,65.876954 662.81136,65.876954 C 662.81562,66.016461 662.89302,66.145089 663.02154,66.223802 L 666.66773,68.459752 C 666.80974,68.547424 666.99436,68.559583 667.14986,68.490469 C 667.14986,68.490469 668.58703,67.858208 669.03011,67.663667 C 669.38799,67.963158 670.90256,69.232159 671.357,69.613563 C 671.03534,70.000088 670.40125,70.760976 670.40125,70.760976 C 670.33663,70.838409 670.30468,70.92992 670.30468,71.022071 C 670.30468,71.114223 670.33734,71.206374 670.40267,71.284446 L 672.93476,74.296644 C 673.00435,74.379196 673.10376,74.43679 673.21737,74.459189 C 673.21737,74.459189 675.43916,74.887948 675.82473,74.962181 C 675.99088,75.268073 677.09078,77.276204 677.30664,77.672327 C 676.97646,78.037733 675.07845,80.138016 675.07845,80.138016 C 675.00886,80.215448 674.97052,80.312079 674.97052,80.41191 C 674.97052,80.420229 674.97052,80.427908 674.97123,80.436228 L 675.01525,81.132482 C 675.02307,81.26047 675.09336,81.379499 675.20768,81.456292 C 675.20768,81.456292 677.97694,83.319157 678.37244,83.585372 C 678.30996,84.001333 677.94498,86.422226 677.90948,86.659004 C 677.72202,86.835627 675.26307,89.159889 674.84697,89.552812 C 674.41028,89.346112 673.33666,88.838639 673.33666,88.838639 C 673.1492,88.749047 672.92056,88.781044 672.77074,88.916712 C 672.67914,88.999903 672.63157,89.110614 672.63157,89.222603 C 672.63157,89.291717 672.65003,89.36211 672.68766,89.426744 C 672.68766,89.426744 673.82661,91.351043 674.09288,91.800921 C 673.8053,92.106812 671.29877,94.772162 671.19013,94.886711 C 671.05238,94.953265 668.72194,96.07188 668.12123,96.360493 C 667.72643,95.813344 665.81281,93.161433 665.81281,93.161433 C 665.72121,93.034085 665.56144,92.959852 665.39245,92.963692 L 662.88237,93.040484 C 662.65018,93.048163 662.45633,93.203669 662.42224,93.41101 C 662.42224,93.41101 662.30721,94.117503 662.27952,94.290287 C 662.14816,94.445792 660.10743,96.888443 659.79926,97.257048 C 659.32352,97.171297 656.34266,96.636307 655.73911,96.528157 C 655.73272,95.925334 655.69508,92.23096 655.69508,92.23096 C 655.69366,92.081854 655.60704,91.943628 655.46573,91.865554 L 651.02782,89.406906 C 650.86238,89.314114 650.65078,89.321154 650.49315,89.424184 C 650.49315,89.424184 647.78922,91.187858 647.21264,91.563503 C 646.74542,91.087387 644.23747,88.531468 643.94634,88.235176 C 644.06634,87.851851 644.85878,85.315769 644.92268,85.112269 C 645.086,84.974682 645.92458,84.272028 645.92458,84.272028 C 646.0247,84.187555 646.07796,84.071726 646.07796,83.953977 C 646.07796,83.888703 646.06234,83.82407 646.02896,83.762635 L 644.74162,81.402537 C 644.66848,81.26879 644.52362,81.177918 644.35889,81.1632 C 644.35889,81.1632 641.0514,80.856668 640.40596,80.796514 C 640.33921,80.218008 639.99128,77.186613 639.97992,77.089342 C 639.99625,76.992711 640.45424,74.260808 640.55223,73.680382 C 641.20194,73.664383 644.09617,73.59399 644.09617,73.59399 C 644.29214,73.589511 644.46611,73.477521 644.53428,73.311776 C 644.55416,73.262501 644.56481,73.211306 644.56481,73.160111 C 644.56481,73.040442 644.50942,72.923333 644.40788,72.840141 C 644.40788,72.840141 642.73284,71.46235 642.42964,71.212773 C 642.47438,70.838409 642.81947,67.966358 642.84716,67.73086 C 643.00551,67.565756 644.35676,66.150849 644.72528,65.765605 C 645.27132,66.011342 647.83324,67.165794 647.83324,67.165794 C 647.96247,67.224028 648.11372,67.226588 648.24437,67.174113 L 649.11704,66.823426 C 649.27112,66.761352 649.37834,66.631444 649.39893,66.479778 C 649.39893,66.479778 649.78237,63.692199 649.85835,63.138012 C 650.42427,63.074658 652.73979,62.812922 653.15873,62.766207 z " + style="fill:url(#linearGradient18796);fill-opacity:1" + id="path18761" /> + </g> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Setting-dialog/amarok_settings_engine.png" + transform="matrix(0.899443,0,0,0.869374,663.1337,632.7971)" + id="g18763" + style="display:inline"> + <path + transform="matrix(0.116367,-0.126279,0.138815,0.105858,2044.201,-572.7461)" + d="M -7824.3296 -3285.1472 A 25.97311 5.306335 0 1 1 -7876.2758,-3285.1472 A 25.97311 5.306335 0 1 1 -7824.3296 -3285.1472 z" + sodipodi:ry="5.306335" + sodipodi:rx="25.97311" + sodipodi:cy="-3285.1472" + sodipodi:cx="-7850.3027" + id="path18765" + style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:21.03624916;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0.68131869" + sodipodi:type="arc" /> + <path + style="fill:url(#linearGradient18798);fill-opacity:1" + d="M 684.58932,62.015066 C 683.95163,62.03676 682.73778,63.425764 682.28042,63.646529 C 681.81946,63.869109 678.16982,66.391273 677.82409,66.836327 C 677.47838,67.281337 677.01748,67.46692 676.51816,67.429844 C 676.01883,67.392834 674.44026,67.377225 673.90249,67.673903 C 673.36463,67.970636 670.90988,70.397011 670.29527,70.693715 C 669.68068,70.990341 668.87865,71.141876 668.49449,71.178979 C 668.11043,71.216028 666.83809,72.584967 666.49238,72.844636 C 666.14667,73.10429 663.41458,74.2451 663.03046,74.467545 C 662.64631,74.690071 665.83291,77.807344 666.29388,77.881456 C 666.75485,77.955728 665.97647,79.643959 665.08957,79.643959 C 664.99189,79.643959 665.88202,82.441727 670.53651,84.906802 C 674.56037,87.037898 678.82957,86.094974 680.41507,85.578197 C 681.53629,85.212648 680.74985,85.010148 680.78834,84.416789 C 680.82666,83.823378 680.8587,83.043556 680.93555,82.376043 C 681.01244,81.708505 681.28161,76.107653 682.28042,74.624229 C 683.27912,73.140873 686.13308,71.360612 687.13185,71.175177 C 688.13068,70.989794 687.93916,70.619136 687.93916,70.285379 C 687.93916,69.95165 687.78546,69.209626 687.51651,69.098343 C 687.24765,68.987127 686.19822,69.654757 685.85251,69.728899 C 685.5068,69.803078 683.85489,69.914575 683.62435,69.543728 C 683.3939,69.172775 686.14108,63.906545 685.64166,63.276177 C 685.14247,62.645555 684.79657,62.015066 684.60452,62.015066 C 684.59957,62.015066 684.59434,62.014895 684.58932,62.015066 z M 676.42414,68.978694 C 676.73261,68.974532 676.94176,69.006232 676.94176,69.006232 C 677.11748,69.40176 676.29813,70.644697 675.53706,70.814323 C 674.77614,70.983791 673.01974,73.4706 672.96126,72.905408 C 672.90256,72.340212 673.66355,70.476005 674.54169,69.628234 C 675.09392,69.095034 675.90135,68.985752 676.42414,68.978694 z " + id="path18767" + sodipodi:nodetypes="ccccsccsccsccccccsssccsccscccc" /> + <path + style="fill:url(#linearGradient18800);fill-opacity:1" + d="M 666.16392,81.423003 C 665.9031,81.091515 665.42361,80.435598 665.26754,80.004036 C 665.14138,79.655175 665.13889,79.611803 665.24522,79.614525 C 665.37457,79.61782 665.53249,79.582317 665.71495,79.414066 C 666.12829,79.032933 666.46122,78.479561 666.36093,78.193327 C 666.34151,78.137933 666.28178,78.082005 666.19489,78.037937 C 665.68311,77.778228 663.68597,75.546616 663.20752,74.699847 C 663.14869,74.595715 663.09317,74.467102 663.08417,74.414107 L 663.06779,74.317713 L 663.5356,74.095961 C 663.79289,73.973979 664.43521,73.676485 664.96298,73.434854 C 666.54934,72.708572 666.40066,72.802712 667.30233,71.953513 C 668.1459,71.159029 668.45291,70.912676 668.63209,70.886525 C 669.29678,70.789464 669.74333,70.680556 670.16886,70.511811 C 670.62212,70.332045 670.78417,70.203751 672.21307,68.893356 C 673.39223,67.812027 673.7656,67.48122 673.99398,67.315529 C 674.36671,67.045079 674.85595,66.975476 676.21407,66.999677 C 677.02172,67.014072 677.16671,67.009258 677.3026,66.963628 C 677.54873,66.881043 677.75482,66.732936 678.04247,66.432002 C 678.18878,66.278881 678.48874,66.009285 678.70903,65.832881 C 679.56399,65.148239 681.38883,63.831519 682.2969,63.244071 C 682.83405,62.896574 682.83553,62.895428 683.52111,62.285059 C 684.09054,61.778114 684.47641,61.493496 684.70119,61.414567 C 684.91293,61.34024 684.93449,61.341828 685.04373,61.439835 C 685.15035,61.535483 686.03394,62.9158 686.10698,63.100796 C 686.24105,63.440395 686.31503,63.800609 686.17623,64.085306 C 685.91549,64.62008 685.53887,65.389083 685.37514,65.683372 C 684.01271,68.277198 683.57638,69.315608 683.75865,69.53045 C 683.9225,69.723542 684.62451,69.838696 685.37986,69.796375 C 685.63126,69.7823 685.89265,69.757165 685.96072,69.740527 C 686.02879,69.723904 686.33237,69.592247 686.63534,69.447957 C 687.2815,69.140217 687.50858,69.064356 687.61979,69.119151 C 687.73185,69.174334 687.84105,69.402649 687.91551,69.737405 C 687.98654,70.056699 688.02311,70.630231 687.98411,70.812999 C 687.93246,71.054964 687.70898,71.202203 687.22201,71.315153 C 686.50051,71.482471 685.39489,72.093335 684.25696,72.95334 C 683.19009,73.759659 682.46764,74.510542 682.13211,75.161789 C 681.942,75.530794 681.88754,75.797654 681.7263,76.390906 C 681.60627,76.832449 680.94162,82.834448 680.93784,82.974322 C 680.93675,83.015121 681.05021,76.469849 681.04313,76.469662 C 681.03606,76.469473 680.47326,77.712213 680.46324,77.71196 C 680.45319,77.711706 679.26724,79.616271 679.14064,79.557035 C 679.01407,79.497811 680.84098,75.164632 681.29637,74.622279 C 681.72505,74.111718 682.69675,73.228218 683.92824,72.229312 C 684.68954,71.611793 685.08523,71.319382 685.27321,71.235423 C 685.34613,71.202844 685.65566,71.131984 685.96107,71.077938 C 686.71523,70.944468 686.90799,70.86055 686.9133,70.663386 C 686.91912,70.447624 686.66022,70.304493 686.31274,70.331392 C 685.85732,70.366626 685.34615,70.54459 684.72472,70.884297 C 684.05931,71.248032 683.51826,71.34764 682.8754,71.224763 C 682.69316,71.189901 682.45974,71.128317 682.35669,71.087851 C 682.13555,71.001063 682.1061,70.935662 682.16257,70.656928 C 682.28424,70.056285 682.7318,69.095497 684.09589,66.506489 C 684.52724,65.687816 684.91444,64.919869 684.95636,64.799939 C 684.99825,64.680011 685.1686,64.493296 685.20972,64.242845 C 685.27332,63.855494 684.94437,63.305418 684.69042,63.096047 C 684.1717,62.710286 684.01007,62.609168 683.74917,62.719264 C 683.27598,62.918962 682.63747,63.885861 680.89506,66.251944 C 678.10234,70.044237 677.77609,70.437338 677.00798,70.935529 C 675.9344,71.631833 674.25248,73.011563 672.89065,74.31307 C 671.94197,75.219744 671.23101,76.306587 670.94267,76.585575 C 670.75542,76.76678 669.96025,77.745288 669.68107,77.980021 C 668.26676,78.87945 667.27756,79.841733 666.4157,81.272282 L 666.26937,81.5468 L 666.16392,81.423003 z M 668.6295,76.32153 C 669.21552,76.035684 669.9402,75.554275 670.29047,75.218104 C 670.57943,74.940811 670.69555,74.663571 670.53426,74.6361 C 670.43004,74.618356 670.14725,74.769144 669.42758,75.226216 C 668.60241,75.75033 668.36243,75.871421 668.1267,75.882628 C 667.6555,75.905015 666.59584,75.72705 666.06717,75.536718 C 665.70072,75.404769 665.57392,75.331295 665.45729,75.183336 C 665.38321,75.089358 665.37887,75.065462 665.39778,74.856572 C 665.42208,74.588243 665.56219,74.28492 665.80217,73.981211 C 665.92518,73.825514 666.03467,73.73747 666.35462,73.537052 C 667.27497,72.96057 668.28907,72.478015 669.33316,72.119738 C 669.57465,72.036872 669.8005,71.958064 669.83504,71.944615 C 669.88896,71.923614 669.87183,71.906057 669.71409,71.820563 C 669.45042,71.677633 669.10707,71.548405 668.91845,71.521096 C 668.73014,71.493813 668.65946,71.521481 668.21594,71.796028 C 667.88157,72.003024 666.25388,73.091052 665.48621,73.620705 C 665.18146,73.830983 664.72391,74.14191 664.46942,74.311617 C 664.21494,74.481377 663.98901,74.640691 663.96737,74.665692 C 663.90854,74.733682 663.91479,74.906936 663.98115,75.047732 C 664.05408,75.202428 664.40387,75.635913 664.59279,75.805673 L 664.73365,75.93226 L 666.46235,76.190566 C 667.41313,76.332656 668.22595,76.451329 668.26859,76.454332 C 668.31124,76.457306 668.47365,76.397562 668.6295,76.32153 z M 668.25415,75.137027 C 668.72466,75.109689 668.82443,75.064367 669.20342,74.70609 C 669.59232,74.338446 669.98504,73.821189 670.13614,73.477602 L 670.18873,73.358048 L 670.0701,73.373682 C 670.00486,73.382261 669.32753,73.452719 668.5649,73.53022 L 667.17835,73.671095 L 667.05139,73.770438 C 666.8115,73.958127 666.48365,74.29119 666.35948,74.473383 C 666.21238,74.689172 666.19775,74.804299 666.3076,74.881267 C 666.43204,74.968454 666.84529,75.068852 667.23887,75.107529 C 667.76006,75.158734 667.84061,75.161069 668.25415,75.137027 z M 673.23289,72.884003 C 673.36857,72.793999 673.63342,72.530209 674.28656,71.834505 C 674.76864,71.320997 675.22409,70.892729 675.40078,70.786743 C 675.46833,70.746223 675.60467,70.684907 675.70377,70.650443 C 675.92776,70.572528 676.07455,70.48466 676.28578,70.302053 C 676.73011,69.917918 677.18695,69.102408 677.12911,68.796643 C 677.11207,68.706574 677.1094,68.705506 676.85795,68.688776 C 675.95769,68.628965 675.25732,68.824768 674.75202,69.277624 C 674.16986,69.799364 673.40627,71.172265 673.12421,72.204378 C 673.01269,72.612445 672.98317,72.817187 673.02395,72.899506 C 673.06635,72.985066 673.08235,72.983877 673.23289,72.884003 z M 671.57619,71.796562 C 672.34776,71.643906 672.98398,71.514265 672.99004,71.508448 C 672.99608,71.502658 673.07634,71.260586 673.16839,70.970524 C 673.54217,69.792774 673.83364,69.271727 674.26438,69.011271 C 674.90419,68.624388 676.2258,68.209742 676.95832,68.166034 C 677.22424,68.150158 677.26926,68.137257 677.51161,68.007456 C 677.8718,67.814601 678.3997,67.456604 679.12431,66.913865 C 679.45905,66.663135 679.91088,66.333048 680.12838,66.180365 C 680.68623,65.788718 680.96159,65.51887 680.96589,65.359637 C 680.96661,65.332846 680.95475,65.310646 680.93954,65.310246 C 680.92433,65.309872 680.56473,65.552198 680.14043,65.848756 C 679.71612,66.145328 679.09133,66.572036 678.75201,66.797005 C 677.56622,67.583204 677.74205,67.5241 676.57466,67.528756 C 676.06082,67.530783 675.36265,67.538481 675.02318,67.545859 C 674.42556,67.558856 674.40209,67.56187 674.28268,67.640998 C 674.21487,67.685947 673.78028,68.103582 673.31692,68.569102 C 672.3073,69.583402 671.44162,70.400928 671.10522,70.657742 C 670.9698,70.761139 670.75997,70.888754 670.63892,70.941346 C 670.39611,71.046839 670.3172,71.116109 670.21049,71.317382 C 670.13004,71.469129 670.04208,71.806968 670.03645,71.985906 C 670.03309,72.092787 670.03952,72.102793 670.10304,72.089172 C 670.14171,72.080887 670.80463,71.949217 671.57619,71.796562 z " + id="path18769" + sodipodi:nodetypes="csssssscccscsscsssssscsssscsssssssssssssssscssssccssssscscscscccccsscsssssssssssscsscccsccsccccccsssccssssscsssccssssssssssscsscsssssc" /> + <g + id="g18771" + style="fill:#002ddb;fill-opacity:1"> + <path + id="path18773" + d="M 680.83905,76.838803 C 680.83905,76.838803 679.45446,81.3797 679.41945,81.449838 C 679.22156,81.846006 678.85659,82.994032 678.98224,83.364797 C 679.13021,83.801538 679.68481,84.436589 679.94365,84.754121 C 680.2025,85.071759 680.31342,85.230473 680.23945,85.349692 C 680.16554,85.468783 679.20413,85.468783 679.20413,85.468783 C 679.20413,85.468783 678.46465,84.396936 678.31664,83.999943 C 678.16882,83.603079 678.09481,82.769436 678.24277,82.491624 C 678.39065,82.213703 680.83905,76.838803 680.83905,76.838803 z " + i:knockout="Off" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:21.03624916;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <polygon + id="polygon18775" + points="272.565,567.14 214.39,617.501 287.326,574.086 272.565,567.14 " + a:adobe-blending-mode="lighten" + i:knockout="Off" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:17.46648407;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + transform="matrix(4.348075e-2,0,0,4.188821e-2,659.2285,53.92804)" /> + <polygon + id="polygon18777" + points="329.873,549.776 230.887,678.282 344.633,558.457 329.873,549.776 " + a:adobe-blending-mode="lighten" + i:knockout="Off" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:17.46648407;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + transform="matrix(4.348075e-2,0,0,4.188821e-2,659.2285,53.92804)" /> + <polygon + id="polygon18779" + points="337.688,594.058 262.146,713.882 355.053,601.872 337.688,594.058 " + a:adobe-blending-mode="lighten" + i:knockout="Off" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:17.46648407;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + transform="matrix(4.348075e-2,0,0,4.188821e-2,659.2285,53.92804)" /> + <polygon + id="polygon18781" + points="381.97,588.847 318.584,737.327 402.809,593.19 381.97,588.847 " + a:adobe-blending-mode="lighten" + i:knockout="Off" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:17.46648407;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + transform="matrix(4.348075e-2,0,0,4.188821e-2,659.2285,53.92804)" /> + <polygon + id="polygon18783" + points="407.151,627.921 401.94,763.375 424.515,629.658 407.151,627.921 " + a:adobe-blending-mode="lighten" + i:knockout="Off" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:17.46648407;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + transform="matrix(4.348075e-2,0,0,4.188821e-2,659.2285,53.92804)" /> + </g> + <path + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#002ddb;stroke-width:0.57512665;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 665.61116,79.917164 C 666.44697,82.39457 673.07693,87.947061 680.79385,85.431014 C 680.71584,83.40215 681.5919,77.986014 681.41239,76.464632" + id="path18785" + sodipodi:nodetypes="ccc" /> + </g> + </g> + <g + transform="matrix(1.500004,0,0,1.500004,-701.7386,-735.67926)" + id="g18802" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/48x48/actions/amarok_settings_engine.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <g + style="display:inline" + id="g18804" + transform="matrix(0.792918,0,0,0.76641,745.2599,642.905)" + inkscape:export-filename="/home/lando/Projects/Amarok/Setting-dialog/amarok_settings_engine.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <g + transform="matrix(0.710065,0,0,0.63994,617.4801,45.62798)" + id="g18806"> + <path + id="path18808" + style="fill:#ffffff" + d="M 45.936,27.94 L 45.233,26.783 L 44.282,32.5 L 47.689,30.98 L 45.936,27.94 z " /> + <path + id="path18810" + style="fill:#ffffff" + d="M 85.14,50.286 L 82.531,50.264 L 81.42,50.308 L 81.646,54.354 L 85.14,50.286 z " /> + <path + id="path18812" + style="fill:#ffffff" + d="M 38.56,42.962 L 39.772,37.613 L 36.078,34.799 L 35.265,34.352 L 34.427,40.249 L 37.467,43.023 L 38.56,42.962 z " /> + <path + id="path18814" + style="fill:#ffffff" + d="M 39.6,59.891 L 42.767,56.869 L 39.487,50.521 L 32.926,49.88 L 32.218,43.364 L 32.13,43.361 L 31.005,49.145 L 31.67,55.573 L 37.786,56.202 L 39.6,59.891 z " /> + <path + id="path18816" + style="fill:#ffffff" + d="M 84.592,63.482 L 80.635,61.503 L 77.141,65.853 L 78.351,68.12 L 80.929,69.476 L 85.687,64.113 L 84.592,63.482 z " /> + </g> + <path + sodipodi:nodetypes="cscscsc" + id="path18818" + style="fill:url(#linearGradient18852);fill-opacity:1" + d="M 665.32854,69.75563 C 659.85394,66.552731 653.30216,66.889339 650.69551,70.508199 C 649.86758,71.658171 649.53882,73.000766 649.64817,74.399674 C 651.7153,69.674455 659.29717,69.172118 664.14053,72.23871 C 668.31358,74.879743 670.55782,80.390816 668.09638,84.108203 C 669.0784,83.624409 669.90988,82.957591 670.52196,82.107751 C 673.12932,78.48953 670.80386,72.95917 665.32854,69.75563 z " /> + <path + id="path18820" + style="fill:url(#linearGradient18854)" + d="M 644.49735,84.876771 L 643.41379,88.344605 L 647.14306,92.145209 L 650.77717,89.775511 L 655.21437,92.23416 L 655.26123,96.884604 L 660.00234,97.735084 L 662.73751,94.462431 L 662.90012,93.473724 L 665.4102,93.396931 L 667.95933,96.929399 L 671.49901,95.230999 L 674.62685,91.905871 L 674.68507,91.878354 L 673.1137,89.221963 L 674.94425,90.089721 L 678.36534,86.856745 L 678.88795,83.390191 L 675.49455,81.108805 L 675.45195,80.41191 L 677.87469,77.731842 L 676.15278,74.582697 L 673.31749,74.035548 L 670.78611,71.023351 L 672.00103,69.563009 L 669.11603,67.142756 L 666.93897,68.100746 L 663.29278,65.864795 L 663.24166,64.22143 L 659.29228,62.715011 L 658.38055,64.86009 L 655.13981,64.312301 L 653.36749,62.30417 L 649.42876,62.748288 L 648.92319,66.425383 L 648.05052,66.77607 L 644.6067,65.224216 L 642.38633,67.547837 L 641.92621,71.382998 L 644.08481,73.158191 L 640.13826,73.254182 L 639.49636,77.076543 L 639.96856,81.190077 L 644.31132,81.592599 L 645.59866,83.952698 L 644.49735,84.876771 z M 670.52196,82.108391 C 667.9146,85.727891 661.36354,86.065779 655.88822,82.86224 C 650.41291,79.658701 648.08815,74.12898 650.69551,70.50884 C 653.30216,66.88998 659.85394,66.552731 665.32854,69.75627 C 670.80386,72.95917 673.12932,78.48953 670.52196,82.108391 z " /> + <path + id="path18822" + style="fill:url(#linearGradient18856);fill-opacity:1" + d="M 675.251,81.065288 L 675.05147,77.830392 L 677.59421,77.740161 L 675.89858,74.639652 L 673.1066,74.100823 L 670.61356,71.134701 L 671.81002,69.696756 L 668.96834,67.3149 L 666.82465,68.258811 L 663.23456,66.057418 L 663.18485,64.43965 L 659.29512,62.957548 L 658.3983,65.068711 L 655.20798,64.529881 L 653.46264,62.551827 L 649.77385,63.001705 L 651.46878,65.652976 L 648.22875,66.956533 L 644.83818,65.428357 L 642.79462,67.675185 L 645.93524,69.832423 L 645.08813,73.202347 L 640.65022,73.426966 L 641.14939,77.561618 L 645.73571,77.965419 L 648.02851,81.963764 L 644.78848,84.750063 L 648.72721,88.569224 L 652.56583,86.187368 L 657.25226,88.658176 L 657.30196,93.331657 L 662.68639,94.230133 L 663.38509,89.961734 L 668.02111,89.826706 L 670.71297,93.376453 L 674.45288,91.669094 L 672.05996,87.625312 L 674.50188,84.88445 L 678.09268,86.502219 L 678.59043,83.312117 L 675.251,81.065288 z M 670.35297,82.051436 C 667.7875,85.614622 661.33584,85.94675 655.94574,82.793126 C 650.55492,79.638862 648.26567,74.194254 650.83256,70.630428 C 653.39944,67.066603 659.84968,66.734474 665.2412,69.888738 C 670.63131,73.043002 672.91985,78.488251 670.35297,82.051436 z " /> + <path + id="path18824" + style="fill:url(#linearGradient18858);fill-opacity:1" + d="M 650.29149,70.272702 C 647.54424,74.086104 649.93716,79.897398 655.62621,83.226366 C 658.87689,85.127628 662.72473,85.897475 665.91932,85.285052 C 668.112,84.865251 669.84314,83.847748 670.92599,82.344529 C 671.68718,81.287988 672.07416,80.064423 672.07416,78.742947 C 672.07416,78.186199 672.00529,77.612172 671.86753,77.026628 C 671.188,74.147538 668.84124,71.294046 665.59056,69.391505 C 659.90151,66.063817 653.03873,66.458659 650.29149,70.272702 z M 656.15095,82.498114 C 650.90711,79.430242 648.64129,74.157777 651.09883,70.744977 C 653.55708,67.332178 659.82269,67.051884 665.06653,70.119756 C 668.10703,71.899429 670.29687,74.548781 670.92528,77.20773 C 671.33712,78.954127 671.05877,80.566776 670.11651,81.872253 C 669.17639,83.17837 667.65543,84.064687 665.71766,84.436492 C 662.76805,85.001559 659.19145,84.277787 656.15095,82.498114 z " /> + <path + id="path18826" + style="fill:url(#linearGradient18860);fill-opacity:1" + d="M 653.30784,61.87477 L 649.36911,62.318888 C 649.15041,62.343846 648.97858,62.498711 648.95088,62.696453 C 648.95088,62.696453 648.53762,65.706091 648.4794,66.130371 C 648.30969,66.198204 648.21384,66.237241 648.06259,66.297395 C 647.70188,66.13549 644.82256,64.837692 644.82256,64.837692 C 644.62516,64.74874 644.38658,64.792256 644.24244,64.943282 L 642.02206,67.267544 C 641.95816,67.334097 641.9184,67.41601 641.90845,67.503682 L 641.44833,71.338841 C 641.44691,71.3542 641.44549,71.369559 641.44549,71.385558 C 641.44549,71.506506 641.5023,71.622975 641.60242,71.705527 C 641.60242,71.705527 642.26207,72.248196 642.87911,72.755669 C 641.71745,72.783826 640.12619,72.822862 640.12619,72.822862 C 639.89258,72.827982 639.69731,72.983488 639.66252,73.191468 L 639.02133,77.013189 C 639.01778,77.034307 639.01565,77.056705 639.01565,77.078463 C 639.01565,77.093181 639.01636,77.10854 639.01849,77.123259 L 639.49068,81.236792 C 639.51411,81.440934 639.69376,81.602198 639.92098,81.623316 C 639.92098,81.623316 643.53024,81.957365 643.99818,82.001521 C 644.16575,82.309332 644.79274,83.456744 645.01002,83.856066 C 644.7054,84.110763 644.17143,84.559361 644.17143,84.559361 C 644.10682,84.613116 644.05995,84.682229 644.03652,84.759662 L 642.95225,88.228136 C 642.94018,88.267172 642.9345,88.306209 642.9345,88.345245 C 642.9345,88.450196 642.9771,88.552586 643.05521,88.633218 L 646.78376,92.433182 C 646.94779,92.600846 647.22613,92.628363 647.42708,92.498456 C 647.42708,92.498456 650.35184,90.591434 650.8006,90.298342 C 651.25718,90.551118 654.35519,92.267437 654.73721,92.479897 C 654.74147,92.911216 654.78194,96.889723 654.78194,96.889723 C 654.78336,97.095144 654.94526,97.271767 655.16893,97.311443 L 659.91003,98.161923 C 660.09181,98.19456 660.27785,98.130567 660.38933,97.99682 L 663.1245,94.724806 C 663.17278,94.666572 663.20403,94.599378 663.21681,94.527065 C 663.21681,94.527065 663.27219,94.187897 663.31977,93.896724 C 663.81326,93.881365 664.80238,93.851289 665.15742,93.839769 C 665.42014,94.204536 667.55815,97.166817 667.55815,97.166817 C 667.69377,97.354959 667.96715,97.418954 668.18798,97.313364 L 671.72836,95.614322 C 671.78091,95.589365 671.8299,95.553528 671.86895,95.511932 C 671.86895,95.511932 674.87466,92.316712 674.95561,92.23096 C 675.04366,92.176565 675.11537,92.102332 675.14733,92.008901 C 675.16224,91.966666 675.16934,91.92187 675.16934,91.878354 C 675.16934,91.80796 675.15017,91.738206 675.11324,91.674852 C 675.11324,91.674852 674.6936,90.966439 674.2775,90.263145 C 674.44223,90.341218 674.72413,90.474965 674.72413,90.474965 C 674.91443,90.564557 675.14875,90.53 675.29644,90.389853 L 678.71683,87.156877 C 678.78783,87.090323 678.83186,87.006491 678.84606,86.91626 L 679.36867,83.449705 C 679.37151,83.429866 679.37293,83.410028 679.37293,83.39083 C 679.37293,83.254523 679.30192,83.125256 679.1805,83.042703 C 679.1805,83.042703 676.29053,81.099846 675.96745,80.882265 C 675.95893,80.747879 675.95467,80.675565 675.94757,80.555897 C 676.16059,80.320399 678.25315,78.005736 678.25315,78.005736 C 678.32416,77.927023 678.36108,77.829753 678.36108,77.731842 C 678.36108,77.666567 678.34475,77.600654 678.31138,77.53986 L 676.58947,74.390716 C 676.52485,74.271687 676.40201,74.185934 676.25858,74.157777 C 676.25858,74.157777 673.90542,73.70406 673.59725,73.644545 C 673.4169,73.430166 671.74185,71.436112 671.39321,71.021432 C 671.68647,70.668825 672.39085,69.822824 672.39085,69.822824 C 672.45476,69.745391 672.48671,69.65324 672.48671,69.562368 C 672.48671,69.44526 672.43488,69.32943 672.33334,69.244318 L 669.44692,66.825985 C 669.30136,66.703117 669.08692,66.67496 668.90798,66.753672 C 668.90798,66.753672 667.37495,67.428169 666.98157,67.601593 C 666.60098,67.368014 664.10865,65.839838 663.77066,65.632497 C 663.76001,65.299729 663.72592,64.210551 663.72592,64.210551 C 663.72095,64.040966 663.60663,63.889301 663.43338,63.823387 L 659.48328,62.316969 C 659.36257,62.270893 659.22695,62.270893 659.10695,62.316969 C 658.98695,62.363045 658.89251,62.451356 658.84564,62.561426 C 658.84564,62.561426 658.28256,63.888661 658.07806,64.367976 C 657.50646,64.271345 655.68301,63.963534 655.40822,63.916818 C 655.21224,63.69412 653.74738,62.035395 653.74738,62.035395 C 653.63945,61.916366 653.474,61.855572 653.30784,61.87477 z M 653.15873,62.766207 C 653.43424,63.079137 654.76277,64.582996 654.76277,64.582996 C 654.83449,64.664908 654.93744,64.719943 655.05106,64.739781 L 658.29179,65.286929 C 658.52044,65.325967 658.74695,65.211417 658.83002,65.016236 C 658.83002,65.016236 659.33275,63.832986 659.56281,63.290957 C 660.22175,63.542454 662.33988,64.349417 662.76876,64.512603 C 662.78083,64.897846 662.81136,65.876954 662.81136,65.876954 C 662.81562,66.016461 662.89302,66.145089 663.02154,66.223802 L 666.66773,68.459752 C 666.80974,68.547424 666.99436,68.559583 667.14986,68.490469 C 667.14986,68.490469 668.58703,67.858208 669.03011,67.663667 C 669.38799,67.963158 670.90256,69.232159 671.357,69.613563 C 671.03534,70.000088 670.40125,70.760976 670.40125,70.760976 C 670.33663,70.838409 670.30468,70.92992 670.30468,71.022071 C 670.30468,71.114223 670.33734,71.206374 670.40267,71.284446 L 672.93476,74.296644 C 673.00435,74.379196 673.10376,74.43679 673.21737,74.459189 C 673.21737,74.459189 675.43916,74.887948 675.82473,74.962181 C 675.99088,75.268073 677.09078,77.276204 677.30664,77.672327 C 676.97646,78.037733 675.07845,80.138016 675.07845,80.138016 C 675.00886,80.215448 674.97052,80.312079 674.97052,80.41191 C 674.97052,80.420229 674.97052,80.427908 674.97123,80.436228 L 675.01525,81.132482 C 675.02307,81.26047 675.09336,81.379499 675.20768,81.456292 C 675.20768,81.456292 677.97694,83.319157 678.37244,83.585372 C 678.30996,84.001333 677.94498,86.422226 677.90948,86.659004 C 677.72202,86.835627 675.26307,89.159889 674.84697,89.552812 C 674.41028,89.346112 673.33666,88.838639 673.33666,88.838639 C 673.1492,88.749047 672.92056,88.781044 672.77074,88.916712 C 672.67914,88.999903 672.63157,89.110614 672.63157,89.222603 C 672.63157,89.291717 672.65003,89.36211 672.68766,89.426744 C 672.68766,89.426744 673.82661,91.351043 674.09288,91.800921 C 673.8053,92.106812 671.29877,94.772162 671.19013,94.886711 C 671.05238,94.953265 668.72194,96.07188 668.12123,96.360493 C 667.72643,95.813344 665.81281,93.161433 665.81281,93.161433 C 665.72121,93.034085 665.56144,92.959852 665.39245,92.963692 L 662.88237,93.040484 C 662.65018,93.048163 662.45633,93.203669 662.42224,93.41101 C 662.42224,93.41101 662.30721,94.117503 662.27952,94.290287 C 662.14816,94.445792 660.10743,96.888443 659.79926,97.257048 C 659.32352,97.171297 656.34266,96.636307 655.73911,96.528157 C 655.73272,95.925334 655.69508,92.23096 655.69508,92.23096 C 655.69366,92.081854 655.60704,91.943628 655.46573,91.865554 L 651.02782,89.406906 C 650.86238,89.314114 650.65078,89.321154 650.49315,89.424184 C 650.49315,89.424184 647.78922,91.187858 647.21264,91.563503 C 646.74542,91.087387 644.23747,88.531468 643.94634,88.235176 C 644.06634,87.851851 644.85878,85.315769 644.92268,85.112269 C 645.086,84.974682 645.92458,84.272028 645.92458,84.272028 C 646.0247,84.187555 646.07796,84.071726 646.07796,83.953977 C 646.07796,83.888703 646.06234,83.82407 646.02896,83.762635 L 644.74162,81.402537 C 644.66848,81.26879 644.52362,81.177918 644.35889,81.1632 C 644.35889,81.1632 641.0514,80.856668 640.40596,80.796514 C 640.33921,80.218008 639.99128,77.186613 639.97992,77.089342 C 639.99625,76.992711 640.45424,74.260808 640.55223,73.680382 C 641.20194,73.664383 644.09617,73.59399 644.09617,73.59399 C 644.29214,73.589511 644.46611,73.477521 644.53428,73.311776 C 644.55416,73.262501 644.56481,73.211306 644.56481,73.160111 C 644.56481,73.040442 644.50942,72.923333 644.40788,72.840141 C 644.40788,72.840141 642.73284,71.46235 642.42964,71.212773 C 642.47438,70.838409 642.81947,67.966358 642.84716,67.73086 C 643.00551,67.565756 644.35676,66.150849 644.72528,65.765605 C 645.27132,66.011342 647.83324,67.165794 647.83324,67.165794 C 647.96247,67.224028 648.11372,67.226588 648.24437,67.174113 L 649.11704,66.823426 C 649.27112,66.761352 649.37834,66.631444 649.39893,66.479778 C 649.39893,66.479778 649.78237,63.692199 649.85835,63.138012 C 650.42427,63.074658 652.73979,62.812922 653.15873,62.766207 z " /> + </g> + <g + style="display:inline" + id="g18828" + transform="matrix(0.899443,0,0,0.869374,663.1337,632.7971)" + inkscape:export-filename="/home/lando/Projects/Amarok/Setting-dialog/amarok_settings_engine.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <path + sodipodi:type="arc" + style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:21.03624916;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0.68131869" + id="path18830" + sodipodi:cx="-7850.3027" + sodipodi:cy="-3285.1472" + sodipodi:rx="25.97311" + sodipodi:ry="5.306335" + d="M -7824.3296 -3285.1472 A 25.97311 5.306335 0 1 1 -7876.2758,-3285.1472 A 25.97311 5.306335 0 1 1 -7824.3296 -3285.1472 z" + transform="matrix(0.116367,-0.126279,0.138815,0.105858,2044.201,-572.7461)" /> + <path + sodipodi:nodetypes="ccccsccsccsccccccsssccsccscccc" + id="path18832" + d="M 684.58932,62.015066 C 683.95163,62.03676 682.73778,63.425764 682.28042,63.646529 C 681.81946,63.869109 678.16982,66.391273 677.82409,66.836327 C 677.47838,67.281337 677.01748,67.46692 676.51816,67.429844 C 676.01883,67.392834 674.44026,67.377225 673.90249,67.673903 C 673.36463,67.970636 670.90988,70.397011 670.29527,70.693715 C 669.68068,70.990341 668.87865,71.141876 668.49449,71.178979 C 668.11043,71.216028 666.83809,72.584967 666.49238,72.844636 C 666.14667,73.10429 663.41458,74.2451 663.03046,74.467545 C 662.64631,74.690071 665.83291,77.807344 666.29388,77.881456 C 666.75485,77.955728 665.97647,79.643959 665.08957,79.643959 C 664.99189,79.643959 665.88202,82.441727 670.53651,84.906802 C 674.56037,87.037898 678.82957,86.094974 680.41507,85.578197 C 681.53629,85.212648 680.74985,85.010148 680.78834,84.416789 C 680.82666,83.823378 680.8587,83.043556 680.93555,82.376043 C 681.01244,81.708505 681.28161,76.107653 682.28042,74.624229 C 683.27912,73.140873 686.13308,71.360612 687.13185,71.175177 C 688.13068,70.989794 687.93916,70.619136 687.93916,70.285379 C 687.93916,69.95165 687.78546,69.209626 687.51651,69.098343 C 687.24765,68.987127 686.19822,69.654757 685.85251,69.728899 C 685.5068,69.803078 683.85489,69.914575 683.62435,69.543728 C 683.3939,69.172775 686.14108,63.906545 685.64166,63.276177 C 685.14247,62.645555 684.79657,62.015066 684.60452,62.015066 C 684.59957,62.015066 684.59434,62.014895 684.58932,62.015066 z M 676.42414,68.978694 C 676.73261,68.974532 676.94176,69.006232 676.94176,69.006232 C 677.11748,69.40176 676.29813,70.644697 675.53706,70.814323 C 674.77614,70.983791 673.01974,73.4706 672.96126,72.905408 C 672.90256,72.340212 673.66355,70.476005 674.54169,69.628234 C 675.09392,69.095034 675.90135,68.985752 676.42414,68.978694 z " + style="fill:url(#linearGradient18862);fill-opacity:1" /> + <path + sodipodi:nodetypes="csssssscccscsscsssssscsssscsssssssssssssssscssssccssssscscscscccccsscsssssssssssscsscccsccsccccccsssccssssscsssccssssssssssscsscsssssc" + id="path18834" + d="M 666.16392,81.423003 C 665.9031,81.091515 665.42361,80.435598 665.26754,80.004036 C 665.14138,79.655175 665.13889,79.611803 665.24522,79.614525 C 665.37457,79.61782 665.53249,79.582317 665.71495,79.414066 C 666.12829,79.032933 666.46122,78.479561 666.36093,78.193327 C 666.34151,78.137933 666.28178,78.082005 666.19489,78.037937 C 665.68311,77.778228 663.68597,75.546616 663.20752,74.699847 C 663.14869,74.595715 663.09317,74.467102 663.08417,74.414107 L 663.06779,74.317713 L 663.5356,74.095961 C 663.79289,73.973979 664.43521,73.676485 664.96298,73.434854 C 666.54934,72.708572 666.40066,72.802712 667.30233,71.953513 C 668.1459,71.159029 668.45291,70.912676 668.63209,70.886525 C 669.29678,70.789464 669.74333,70.680556 670.16886,70.511811 C 670.62212,70.332045 670.78417,70.203751 672.21307,68.893356 C 673.39223,67.812027 673.7656,67.48122 673.99398,67.315529 C 674.36671,67.045079 674.85595,66.975476 676.21407,66.999677 C 677.02172,67.014072 677.16671,67.009258 677.3026,66.963628 C 677.54873,66.881043 677.75482,66.732936 678.04247,66.432002 C 678.18878,66.278881 678.48874,66.009285 678.70903,65.832881 C 679.56399,65.148239 681.38883,63.831519 682.2969,63.244071 C 682.83405,62.896574 682.83553,62.895428 683.52111,62.285059 C 684.09054,61.778114 684.47641,61.493496 684.70119,61.414567 C 684.91293,61.34024 684.93449,61.341828 685.04373,61.439835 C 685.15035,61.535483 686.03394,62.9158 686.10698,63.100796 C 686.24105,63.440395 686.31503,63.800609 686.17623,64.085306 C 685.91549,64.62008 685.53887,65.389083 685.37514,65.683372 C 684.01271,68.277198 683.57638,69.315608 683.75865,69.53045 C 683.9225,69.723542 684.62451,69.838696 685.37986,69.796375 C 685.63126,69.7823 685.89265,69.757165 685.96072,69.740527 C 686.02879,69.723904 686.33237,69.592247 686.63534,69.447957 C 687.2815,69.140217 687.50858,69.064356 687.61979,69.119151 C 687.73185,69.174334 687.84105,69.402649 687.91551,69.737405 C 687.98654,70.056699 688.02311,70.630231 687.98411,70.812999 C 687.93246,71.054964 687.70898,71.202203 687.22201,71.315153 C 686.50051,71.482471 685.39489,72.093335 684.25696,72.95334 C 683.19009,73.759659 682.46764,74.510542 682.13211,75.161789 C 681.942,75.530794 681.88754,75.797654 681.7263,76.390906 C 681.60627,76.832449 680.94162,82.834448 680.93784,82.974322 C 680.93675,83.015121 681.05021,76.469849 681.04313,76.469662 C 681.03606,76.469473 680.47326,77.712213 680.46324,77.71196 C 680.45319,77.711706 679.26724,79.616271 679.14064,79.557035 C 679.01407,79.497811 680.84098,75.164632 681.29637,74.622279 C 681.72505,74.111718 682.69675,73.228218 683.92824,72.229312 C 684.68954,71.611793 685.08523,71.319382 685.27321,71.235423 C 685.34613,71.202844 685.65566,71.131984 685.96107,71.077938 C 686.71523,70.944468 686.90799,70.86055 686.9133,70.663386 C 686.91912,70.447624 686.66022,70.304493 686.31274,70.331392 C 685.85732,70.366626 685.34615,70.54459 684.72472,70.884297 C 684.05931,71.248032 683.51826,71.34764 682.8754,71.224763 C 682.69316,71.189901 682.45974,71.128317 682.35669,71.087851 C 682.13555,71.001063 682.1061,70.935662 682.16257,70.656928 C 682.28424,70.056285 682.7318,69.095497 684.09589,66.506489 C 684.52724,65.687816 684.91444,64.919869 684.95636,64.799939 C 684.99825,64.680011 685.1686,64.493296 685.20972,64.242845 C 685.27332,63.855494 684.94437,63.305418 684.69042,63.096047 C 684.1717,62.710286 684.01007,62.609168 683.74917,62.719264 C 683.27598,62.918962 682.63747,63.885861 680.89506,66.251944 C 678.10234,70.044237 677.77609,70.437338 677.00798,70.935529 C 675.9344,71.631833 674.25248,73.011563 672.89065,74.31307 C 671.94197,75.219744 671.23101,76.306587 670.94267,76.585575 C 670.75542,76.76678 669.96025,77.745288 669.68107,77.980021 C 668.26676,78.87945 667.27756,79.841733 666.4157,81.272282 L 666.26937,81.5468 L 666.16392,81.423003 z M 668.6295,76.32153 C 669.21552,76.035684 669.9402,75.554275 670.29047,75.218104 C 670.57943,74.940811 670.69555,74.663571 670.53426,74.6361 C 670.43004,74.618356 670.14725,74.769144 669.42758,75.226216 C 668.60241,75.75033 668.36243,75.871421 668.1267,75.882628 C 667.6555,75.905015 666.59584,75.72705 666.06717,75.536718 C 665.70072,75.404769 665.57392,75.331295 665.45729,75.183336 C 665.38321,75.089358 665.37887,75.065462 665.39778,74.856572 C 665.42208,74.588243 665.56219,74.28492 665.80217,73.981211 C 665.92518,73.825514 666.03467,73.73747 666.35462,73.537052 C 667.27497,72.96057 668.28907,72.478015 669.33316,72.119738 C 669.57465,72.036872 669.8005,71.958064 669.83504,71.944615 C 669.88896,71.923614 669.87183,71.906057 669.71409,71.820563 C 669.45042,71.677633 669.10707,71.548405 668.91845,71.521096 C 668.73014,71.493813 668.65946,71.521481 668.21594,71.796028 C 667.88157,72.003024 666.25388,73.091052 665.48621,73.620705 C 665.18146,73.830983 664.72391,74.14191 664.46942,74.311617 C 664.21494,74.481377 663.98901,74.640691 663.96737,74.665692 C 663.90854,74.733682 663.91479,74.906936 663.98115,75.047732 C 664.05408,75.202428 664.40387,75.635913 664.59279,75.805673 L 664.73365,75.93226 L 666.46235,76.190566 C 667.41313,76.332656 668.22595,76.451329 668.26859,76.454332 C 668.31124,76.457306 668.47365,76.397562 668.6295,76.32153 z M 668.25415,75.137027 C 668.72466,75.109689 668.82443,75.064367 669.20342,74.70609 C 669.59232,74.338446 669.98504,73.821189 670.13614,73.477602 L 670.18873,73.358048 L 670.0701,73.373682 C 670.00486,73.382261 669.32753,73.452719 668.5649,73.53022 L 667.17835,73.671095 L 667.05139,73.770438 C 666.8115,73.958127 666.48365,74.29119 666.35948,74.473383 C 666.21238,74.689172 666.19775,74.804299 666.3076,74.881267 C 666.43204,74.968454 666.84529,75.068852 667.23887,75.107529 C 667.76006,75.158734 667.84061,75.161069 668.25415,75.137027 z M 673.23289,72.884003 C 673.36857,72.793999 673.63342,72.530209 674.28656,71.834505 C 674.76864,71.320997 675.22409,70.892729 675.40078,70.786743 C 675.46833,70.746223 675.60467,70.684907 675.70377,70.650443 C 675.92776,70.572528 676.07455,70.48466 676.28578,70.302053 C 676.73011,69.917918 677.18695,69.102408 677.12911,68.796643 C 677.11207,68.706574 677.1094,68.705506 676.85795,68.688776 C 675.95769,68.628965 675.25732,68.824768 674.75202,69.277624 C 674.16986,69.799364 673.40627,71.172265 673.12421,72.204378 C 673.01269,72.612445 672.98317,72.817187 673.02395,72.899506 C 673.06635,72.985066 673.08235,72.983877 673.23289,72.884003 z M 671.57619,71.796562 C 672.34776,71.643906 672.98398,71.514265 672.99004,71.508448 C 672.99608,71.502658 673.07634,71.260586 673.16839,70.970524 C 673.54217,69.792774 673.83364,69.271727 674.26438,69.011271 C 674.90419,68.624388 676.2258,68.209742 676.95832,68.166034 C 677.22424,68.150158 677.26926,68.137257 677.51161,68.007456 C 677.8718,67.814601 678.3997,67.456604 679.12431,66.913865 C 679.45905,66.663135 679.91088,66.333048 680.12838,66.180365 C 680.68623,65.788718 680.96159,65.51887 680.96589,65.359637 C 680.96661,65.332846 680.95475,65.310646 680.93954,65.310246 C 680.92433,65.309872 680.56473,65.552198 680.14043,65.848756 C 679.71612,66.145328 679.09133,66.572036 678.75201,66.797005 C 677.56622,67.583204 677.74205,67.5241 676.57466,67.528756 C 676.06082,67.530783 675.36265,67.538481 675.02318,67.545859 C 674.42556,67.558856 674.40209,67.56187 674.28268,67.640998 C 674.21487,67.685947 673.78028,68.103582 673.31692,68.569102 C 672.3073,69.583402 671.44162,70.400928 671.10522,70.657742 C 670.9698,70.761139 670.75997,70.888754 670.63892,70.941346 C 670.39611,71.046839 670.3172,71.116109 670.21049,71.317382 C 670.13004,71.469129 670.04208,71.806968 670.03645,71.985906 C 670.03309,72.092787 670.03952,72.102793 670.10304,72.089172 C 670.14171,72.080887 670.80463,71.949217 671.57619,71.796562 z " + style="fill:url(#linearGradient18864);fill-opacity:1" /> + <g + style="fill:#002ddb;fill-opacity:1" + id="g18836"> + <path + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:21.03624916;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + i:knockout="Off" + d="M 680.83905,76.838803 C 680.83905,76.838803 679.45446,81.3797 679.41945,81.449838 C 679.22156,81.846006 678.85659,82.994032 678.98224,83.364797 C 679.13021,83.801538 679.68481,84.436589 679.94365,84.754121 C 680.2025,85.071759 680.31342,85.230473 680.23945,85.349692 C 680.16554,85.468783 679.20413,85.468783 679.20413,85.468783 C 679.20413,85.468783 678.46465,84.396936 678.31664,83.999943 C 678.16882,83.603079 678.09481,82.769436 678.24277,82.491624 C 678.39065,82.213703 680.83905,76.838803 680.83905,76.838803 z " + id="path18838" /> + <polygon + transform="matrix(4.348075e-2,0,0,4.188821e-2,659.2285,53.92804)" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:17.46648407;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + i:knockout="Off" + a:adobe-blending-mode="lighten" + points="272.565,567.14 214.39,617.501 287.326,574.086 272.565,567.14 " + id="polygon18840" /> + <polygon + transform="matrix(4.348075e-2,0,0,4.188821e-2,659.2285,53.92804)" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:17.46648407;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + i:knockout="Off" + a:adobe-blending-mode="lighten" + points="329.873,549.776 230.887,678.282 344.633,558.457 329.873,549.776 " + id="polygon18842" /> + <polygon + transform="matrix(4.348075e-2,0,0,4.188821e-2,659.2285,53.92804)" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:17.46648407;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + i:knockout="Off" + a:adobe-blending-mode="lighten" + points="337.688,594.058 262.146,713.882 355.053,601.872 337.688,594.058 " + id="polygon18844" /> + <polygon + transform="matrix(4.348075e-2,0,0,4.188821e-2,659.2285,53.92804)" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:17.46648407;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + i:knockout="Off" + a:adobe-blending-mode="lighten" + points="381.97,588.847 318.584,737.327 402.809,593.19 381.97,588.847 " + id="polygon18846" /> + <polygon + transform="matrix(4.348075e-2,0,0,4.188821e-2,659.2285,53.92804)" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:17.46648407;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + i:knockout="Off" + a:adobe-blending-mode="lighten" + points="407.151,627.921 401.94,763.375 424.515,629.658 407.151,627.921 " + id="polygon18848" /> + </g> + <path + sodipodi:nodetypes="ccc" + id="path18850" + d="M 665.61116,79.917164 C 666.44697,82.39457 673.07693,87.947061 680.79385,85.431014 C 680.71584,83.40215 681.5919,77.986014 681.41239,76.464632" + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#002ddb;stroke-width:0.57512665;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + </g> + <g + id="g18866" + transform="matrix(2.000004,0,0,2.000004,-1266.182,-1094.7511)" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/64x64/actions/amarok_settings_engine.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Setting-dialog/amarok_settings_engine.png" + transform="matrix(0.792918,0,0,0.76641,745.2599,642.905)" + id="g18868" + style="display:inline"> + <g + id="g18870" + transform="matrix(0.710065,0,0,0.63994,617.4801,45.62798)"> + <path + d="M 45.936,27.94 L 45.233,26.783 L 44.282,32.5 L 47.689,30.98 L 45.936,27.94 z " + style="fill:#ffffff" + id="path18872" /> + <path + d="M 85.14,50.286 L 82.531,50.264 L 81.42,50.308 L 81.646,54.354 L 85.14,50.286 z " + style="fill:#ffffff" + id="path18874" /> + <path + d="M 38.56,42.962 L 39.772,37.613 L 36.078,34.799 L 35.265,34.352 L 34.427,40.249 L 37.467,43.023 L 38.56,42.962 z " + style="fill:#ffffff" + id="path18876" /> + <path + d="M 39.6,59.891 L 42.767,56.869 L 39.487,50.521 L 32.926,49.88 L 32.218,43.364 L 32.13,43.361 L 31.005,49.145 L 31.67,55.573 L 37.786,56.202 L 39.6,59.891 z " + style="fill:#ffffff" + id="path18878" /> + <path + d="M 84.592,63.482 L 80.635,61.503 L 77.141,65.853 L 78.351,68.12 L 80.929,69.476 L 85.687,64.113 L 84.592,63.482 z " + style="fill:#ffffff" + id="path18880" /> + </g> + <path + d="M 665.32854,69.75563 C 659.85394,66.552731 653.30216,66.889339 650.69551,70.508199 C 649.86758,71.658171 649.53882,73.000766 649.64817,74.399674 C 651.7153,69.674455 659.29717,69.172118 664.14053,72.23871 C 668.31358,74.879743 670.55782,80.390816 668.09638,84.108203 C 669.0784,83.624409 669.90988,82.957591 670.52196,82.107751 C 673.12932,78.48953 670.80386,72.95917 665.32854,69.75563 z " + style="fill:url(#linearGradient18916);fill-opacity:1" + id="path18882" + sodipodi:nodetypes="cscscsc" /> + <path + d="M 644.49735,84.876771 L 643.41379,88.344605 L 647.14306,92.145209 L 650.77717,89.775511 L 655.21437,92.23416 L 655.26123,96.884604 L 660.00234,97.735084 L 662.73751,94.462431 L 662.90012,93.473724 L 665.4102,93.396931 L 667.95933,96.929399 L 671.49901,95.230999 L 674.62685,91.905871 L 674.68507,91.878354 L 673.1137,89.221963 L 674.94425,90.089721 L 678.36534,86.856745 L 678.88795,83.390191 L 675.49455,81.108805 L 675.45195,80.41191 L 677.87469,77.731842 L 676.15278,74.582697 L 673.31749,74.035548 L 670.78611,71.023351 L 672.00103,69.563009 L 669.11603,67.142756 L 666.93897,68.100746 L 663.29278,65.864795 L 663.24166,64.22143 L 659.29228,62.715011 L 658.38055,64.86009 L 655.13981,64.312301 L 653.36749,62.30417 L 649.42876,62.748288 L 648.92319,66.425383 L 648.05052,66.77607 L 644.6067,65.224216 L 642.38633,67.547837 L 641.92621,71.382998 L 644.08481,73.158191 L 640.13826,73.254182 L 639.49636,77.076543 L 639.96856,81.190077 L 644.31132,81.592599 L 645.59866,83.952698 L 644.49735,84.876771 z M 670.52196,82.108391 C 667.9146,85.727891 661.36354,86.065779 655.88822,82.86224 C 650.41291,79.658701 648.08815,74.12898 650.69551,70.50884 C 653.30216,66.88998 659.85394,66.552731 665.32854,69.75627 C 670.80386,72.95917 673.12932,78.48953 670.52196,82.108391 z " + style="fill:url(#linearGradient18918)" + id="path18884" /> + <path + d="M 675.251,81.065288 L 675.05147,77.830392 L 677.59421,77.740161 L 675.89858,74.639652 L 673.1066,74.100823 L 670.61356,71.134701 L 671.81002,69.696756 L 668.96834,67.3149 L 666.82465,68.258811 L 663.23456,66.057418 L 663.18485,64.43965 L 659.29512,62.957548 L 658.3983,65.068711 L 655.20798,64.529881 L 653.46264,62.551827 L 649.77385,63.001705 L 651.46878,65.652976 L 648.22875,66.956533 L 644.83818,65.428357 L 642.79462,67.675185 L 645.93524,69.832423 L 645.08813,73.202347 L 640.65022,73.426966 L 641.14939,77.561618 L 645.73571,77.965419 L 648.02851,81.963764 L 644.78848,84.750063 L 648.72721,88.569224 L 652.56583,86.187368 L 657.25226,88.658176 L 657.30196,93.331657 L 662.68639,94.230133 L 663.38509,89.961734 L 668.02111,89.826706 L 670.71297,93.376453 L 674.45288,91.669094 L 672.05996,87.625312 L 674.50188,84.88445 L 678.09268,86.502219 L 678.59043,83.312117 L 675.251,81.065288 z M 670.35297,82.051436 C 667.7875,85.614622 661.33584,85.94675 655.94574,82.793126 C 650.55492,79.638862 648.26567,74.194254 650.83256,70.630428 C 653.39944,67.066603 659.84968,66.734474 665.2412,69.888738 C 670.63131,73.043002 672.91985,78.488251 670.35297,82.051436 z " + style="fill:url(#linearGradient18920);fill-opacity:1" + id="path18886" /> + <path + d="M 650.29149,70.272702 C 647.54424,74.086104 649.93716,79.897398 655.62621,83.226366 C 658.87689,85.127628 662.72473,85.897475 665.91932,85.285052 C 668.112,84.865251 669.84314,83.847748 670.92599,82.344529 C 671.68718,81.287988 672.07416,80.064423 672.07416,78.742947 C 672.07416,78.186199 672.00529,77.612172 671.86753,77.026628 C 671.188,74.147538 668.84124,71.294046 665.59056,69.391505 C 659.90151,66.063817 653.03873,66.458659 650.29149,70.272702 z M 656.15095,82.498114 C 650.90711,79.430242 648.64129,74.157777 651.09883,70.744977 C 653.55708,67.332178 659.82269,67.051884 665.06653,70.119756 C 668.10703,71.899429 670.29687,74.548781 670.92528,77.20773 C 671.33712,78.954127 671.05877,80.566776 670.11651,81.872253 C 669.17639,83.17837 667.65543,84.064687 665.71766,84.436492 C 662.76805,85.001559 659.19145,84.277787 656.15095,82.498114 z " + style="fill:url(#linearGradient18922);fill-opacity:1" + id="path18888" /> + <path + d="M 653.30784,61.87477 L 649.36911,62.318888 C 649.15041,62.343846 648.97858,62.498711 648.95088,62.696453 C 648.95088,62.696453 648.53762,65.706091 648.4794,66.130371 C 648.30969,66.198204 648.21384,66.237241 648.06259,66.297395 C 647.70188,66.13549 644.82256,64.837692 644.82256,64.837692 C 644.62516,64.74874 644.38658,64.792256 644.24244,64.943282 L 642.02206,67.267544 C 641.95816,67.334097 641.9184,67.41601 641.90845,67.503682 L 641.44833,71.338841 C 641.44691,71.3542 641.44549,71.369559 641.44549,71.385558 C 641.44549,71.506506 641.5023,71.622975 641.60242,71.705527 C 641.60242,71.705527 642.26207,72.248196 642.87911,72.755669 C 641.71745,72.783826 640.12619,72.822862 640.12619,72.822862 C 639.89258,72.827982 639.69731,72.983488 639.66252,73.191468 L 639.02133,77.013189 C 639.01778,77.034307 639.01565,77.056705 639.01565,77.078463 C 639.01565,77.093181 639.01636,77.10854 639.01849,77.123259 L 639.49068,81.236792 C 639.51411,81.440934 639.69376,81.602198 639.92098,81.623316 C 639.92098,81.623316 643.53024,81.957365 643.99818,82.001521 C 644.16575,82.309332 644.79274,83.456744 645.01002,83.856066 C 644.7054,84.110763 644.17143,84.559361 644.17143,84.559361 C 644.10682,84.613116 644.05995,84.682229 644.03652,84.759662 L 642.95225,88.228136 C 642.94018,88.267172 642.9345,88.306209 642.9345,88.345245 C 642.9345,88.450196 642.9771,88.552586 643.05521,88.633218 L 646.78376,92.433182 C 646.94779,92.600846 647.22613,92.628363 647.42708,92.498456 C 647.42708,92.498456 650.35184,90.591434 650.8006,90.298342 C 651.25718,90.551118 654.35519,92.267437 654.73721,92.479897 C 654.74147,92.911216 654.78194,96.889723 654.78194,96.889723 C 654.78336,97.095144 654.94526,97.271767 655.16893,97.311443 L 659.91003,98.161923 C 660.09181,98.19456 660.27785,98.130567 660.38933,97.99682 L 663.1245,94.724806 C 663.17278,94.666572 663.20403,94.599378 663.21681,94.527065 C 663.21681,94.527065 663.27219,94.187897 663.31977,93.896724 C 663.81326,93.881365 664.80238,93.851289 665.15742,93.839769 C 665.42014,94.204536 667.55815,97.166817 667.55815,97.166817 C 667.69377,97.354959 667.96715,97.418954 668.18798,97.313364 L 671.72836,95.614322 C 671.78091,95.589365 671.8299,95.553528 671.86895,95.511932 C 671.86895,95.511932 674.87466,92.316712 674.95561,92.23096 C 675.04366,92.176565 675.11537,92.102332 675.14733,92.008901 C 675.16224,91.966666 675.16934,91.92187 675.16934,91.878354 C 675.16934,91.80796 675.15017,91.738206 675.11324,91.674852 C 675.11324,91.674852 674.6936,90.966439 674.2775,90.263145 C 674.44223,90.341218 674.72413,90.474965 674.72413,90.474965 C 674.91443,90.564557 675.14875,90.53 675.29644,90.389853 L 678.71683,87.156877 C 678.78783,87.090323 678.83186,87.006491 678.84606,86.91626 L 679.36867,83.449705 C 679.37151,83.429866 679.37293,83.410028 679.37293,83.39083 C 679.37293,83.254523 679.30192,83.125256 679.1805,83.042703 C 679.1805,83.042703 676.29053,81.099846 675.96745,80.882265 C 675.95893,80.747879 675.95467,80.675565 675.94757,80.555897 C 676.16059,80.320399 678.25315,78.005736 678.25315,78.005736 C 678.32416,77.927023 678.36108,77.829753 678.36108,77.731842 C 678.36108,77.666567 678.34475,77.600654 678.31138,77.53986 L 676.58947,74.390716 C 676.52485,74.271687 676.40201,74.185934 676.25858,74.157777 C 676.25858,74.157777 673.90542,73.70406 673.59725,73.644545 C 673.4169,73.430166 671.74185,71.436112 671.39321,71.021432 C 671.68647,70.668825 672.39085,69.822824 672.39085,69.822824 C 672.45476,69.745391 672.48671,69.65324 672.48671,69.562368 C 672.48671,69.44526 672.43488,69.32943 672.33334,69.244318 L 669.44692,66.825985 C 669.30136,66.703117 669.08692,66.67496 668.90798,66.753672 C 668.90798,66.753672 667.37495,67.428169 666.98157,67.601593 C 666.60098,67.368014 664.10865,65.839838 663.77066,65.632497 C 663.76001,65.299729 663.72592,64.210551 663.72592,64.210551 C 663.72095,64.040966 663.60663,63.889301 663.43338,63.823387 L 659.48328,62.316969 C 659.36257,62.270893 659.22695,62.270893 659.10695,62.316969 C 658.98695,62.363045 658.89251,62.451356 658.84564,62.561426 C 658.84564,62.561426 658.28256,63.888661 658.07806,64.367976 C 657.50646,64.271345 655.68301,63.963534 655.40822,63.916818 C 655.21224,63.69412 653.74738,62.035395 653.74738,62.035395 C 653.63945,61.916366 653.474,61.855572 653.30784,61.87477 z M 653.15873,62.766207 C 653.43424,63.079137 654.76277,64.582996 654.76277,64.582996 C 654.83449,64.664908 654.93744,64.719943 655.05106,64.739781 L 658.29179,65.286929 C 658.52044,65.325967 658.74695,65.211417 658.83002,65.016236 C 658.83002,65.016236 659.33275,63.832986 659.56281,63.290957 C 660.22175,63.542454 662.33988,64.349417 662.76876,64.512603 C 662.78083,64.897846 662.81136,65.876954 662.81136,65.876954 C 662.81562,66.016461 662.89302,66.145089 663.02154,66.223802 L 666.66773,68.459752 C 666.80974,68.547424 666.99436,68.559583 667.14986,68.490469 C 667.14986,68.490469 668.58703,67.858208 669.03011,67.663667 C 669.38799,67.963158 670.90256,69.232159 671.357,69.613563 C 671.03534,70.000088 670.40125,70.760976 670.40125,70.760976 C 670.33663,70.838409 670.30468,70.92992 670.30468,71.022071 C 670.30468,71.114223 670.33734,71.206374 670.40267,71.284446 L 672.93476,74.296644 C 673.00435,74.379196 673.10376,74.43679 673.21737,74.459189 C 673.21737,74.459189 675.43916,74.887948 675.82473,74.962181 C 675.99088,75.268073 677.09078,77.276204 677.30664,77.672327 C 676.97646,78.037733 675.07845,80.138016 675.07845,80.138016 C 675.00886,80.215448 674.97052,80.312079 674.97052,80.41191 C 674.97052,80.420229 674.97052,80.427908 674.97123,80.436228 L 675.01525,81.132482 C 675.02307,81.26047 675.09336,81.379499 675.20768,81.456292 C 675.20768,81.456292 677.97694,83.319157 678.37244,83.585372 C 678.30996,84.001333 677.94498,86.422226 677.90948,86.659004 C 677.72202,86.835627 675.26307,89.159889 674.84697,89.552812 C 674.41028,89.346112 673.33666,88.838639 673.33666,88.838639 C 673.1492,88.749047 672.92056,88.781044 672.77074,88.916712 C 672.67914,88.999903 672.63157,89.110614 672.63157,89.222603 C 672.63157,89.291717 672.65003,89.36211 672.68766,89.426744 C 672.68766,89.426744 673.82661,91.351043 674.09288,91.800921 C 673.8053,92.106812 671.29877,94.772162 671.19013,94.886711 C 671.05238,94.953265 668.72194,96.07188 668.12123,96.360493 C 667.72643,95.813344 665.81281,93.161433 665.81281,93.161433 C 665.72121,93.034085 665.56144,92.959852 665.39245,92.963692 L 662.88237,93.040484 C 662.65018,93.048163 662.45633,93.203669 662.42224,93.41101 C 662.42224,93.41101 662.30721,94.117503 662.27952,94.290287 C 662.14816,94.445792 660.10743,96.888443 659.79926,97.257048 C 659.32352,97.171297 656.34266,96.636307 655.73911,96.528157 C 655.73272,95.925334 655.69508,92.23096 655.69508,92.23096 C 655.69366,92.081854 655.60704,91.943628 655.46573,91.865554 L 651.02782,89.406906 C 650.86238,89.314114 650.65078,89.321154 650.49315,89.424184 C 650.49315,89.424184 647.78922,91.187858 647.21264,91.563503 C 646.74542,91.087387 644.23747,88.531468 643.94634,88.235176 C 644.06634,87.851851 644.85878,85.315769 644.92268,85.112269 C 645.086,84.974682 645.92458,84.272028 645.92458,84.272028 C 646.0247,84.187555 646.07796,84.071726 646.07796,83.953977 C 646.07796,83.888703 646.06234,83.82407 646.02896,83.762635 L 644.74162,81.402537 C 644.66848,81.26879 644.52362,81.177918 644.35889,81.1632 C 644.35889,81.1632 641.0514,80.856668 640.40596,80.796514 C 640.33921,80.218008 639.99128,77.186613 639.97992,77.089342 C 639.99625,76.992711 640.45424,74.260808 640.55223,73.680382 C 641.20194,73.664383 644.09617,73.59399 644.09617,73.59399 C 644.29214,73.589511 644.46611,73.477521 644.53428,73.311776 C 644.55416,73.262501 644.56481,73.211306 644.56481,73.160111 C 644.56481,73.040442 644.50942,72.923333 644.40788,72.840141 C 644.40788,72.840141 642.73284,71.46235 642.42964,71.212773 C 642.47438,70.838409 642.81947,67.966358 642.84716,67.73086 C 643.00551,67.565756 644.35676,66.150849 644.72528,65.765605 C 645.27132,66.011342 647.83324,67.165794 647.83324,67.165794 C 647.96247,67.224028 648.11372,67.226588 648.24437,67.174113 L 649.11704,66.823426 C 649.27112,66.761352 649.37834,66.631444 649.39893,66.479778 C 649.39893,66.479778 649.78237,63.692199 649.85835,63.138012 C 650.42427,63.074658 652.73979,62.812922 653.15873,62.766207 z " + style="fill:url(#linearGradient18924);fill-opacity:1" + id="path18890" /> + </g> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Setting-dialog/amarok_settings_engine.png" + transform="matrix(0.899443,0,0,0.869374,663.1337,632.7971)" + id="g18892" + style="display:inline"> + <path + transform="matrix(0.116367,-0.126279,0.138815,0.105858,2044.201,-572.7461)" + d="M -7824.3296 -3285.1472 A 25.97311 5.306335 0 1 1 -7876.2758,-3285.1472 A 25.97311 5.306335 0 1 1 -7824.3296 -3285.1472 z" + sodipodi:ry="5.306335" + sodipodi:rx="25.97311" + sodipodi:cy="-3285.1472" + sodipodi:cx="-7850.3027" + id="path18894" + style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:21.03624916;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0.68131869" + sodipodi:type="arc" /> + <path + style="fill:url(#linearGradient18926);fill-opacity:1" + d="M 684.58932,62.015066 C 683.95163,62.03676 682.73778,63.425764 682.28042,63.646529 C 681.81946,63.869109 678.16982,66.391273 677.82409,66.836327 C 677.47838,67.281337 677.01748,67.46692 676.51816,67.429844 C 676.01883,67.392834 674.44026,67.377225 673.90249,67.673903 C 673.36463,67.970636 670.90988,70.397011 670.29527,70.693715 C 669.68068,70.990341 668.87865,71.141876 668.49449,71.178979 C 668.11043,71.216028 666.83809,72.584967 666.49238,72.844636 C 666.14667,73.10429 663.41458,74.2451 663.03046,74.467545 C 662.64631,74.690071 665.83291,77.807344 666.29388,77.881456 C 666.75485,77.955728 665.97647,79.643959 665.08957,79.643959 C 664.99189,79.643959 665.88202,82.441727 670.53651,84.906802 C 674.56037,87.037898 678.82957,86.094974 680.41507,85.578197 C 681.53629,85.212648 680.74985,85.010148 680.78834,84.416789 C 680.82666,83.823378 680.8587,83.043556 680.93555,82.376043 C 681.01244,81.708505 681.28161,76.107653 682.28042,74.624229 C 683.27912,73.140873 686.13308,71.360612 687.13185,71.175177 C 688.13068,70.989794 687.93916,70.619136 687.93916,70.285379 C 687.93916,69.95165 687.78546,69.209626 687.51651,69.098343 C 687.24765,68.987127 686.19822,69.654757 685.85251,69.728899 C 685.5068,69.803078 683.85489,69.914575 683.62435,69.543728 C 683.3939,69.172775 686.14108,63.906545 685.64166,63.276177 C 685.14247,62.645555 684.79657,62.015066 684.60452,62.015066 C 684.59957,62.015066 684.59434,62.014895 684.58932,62.015066 z M 676.42414,68.978694 C 676.73261,68.974532 676.94176,69.006232 676.94176,69.006232 C 677.11748,69.40176 676.29813,70.644697 675.53706,70.814323 C 674.77614,70.983791 673.01974,73.4706 672.96126,72.905408 C 672.90256,72.340212 673.66355,70.476005 674.54169,69.628234 C 675.09392,69.095034 675.90135,68.985752 676.42414,68.978694 z " + id="path18896" + sodipodi:nodetypes="ccccsccsccsccccccsssccsccscccc" /> + <path + style="fill:url(#linearGradient18928);fill-opacity:1" + d="M 666.16392,81.423003 C 665.9031,81.091515 665.42361,80.435598 665.26754,80.004036 C 665.14138,79.655175 665.13889,79.611803 665.24522,79.614525 C 665.37457,79.61782 665.53249,79.582317 665.71495,79.414066 C 666.12829,79.032933 666.46122,78.479561 666.36093,78.193327 C 666.34151,78.137933 666.28178,78.082005 666.19489,78.037937 C 665.68311,77.778228 663.68597,75.546616 663.20752,74.699847 C 663.14869,74.595715 663.09317,74.467102 663.08417,74.414107 L 663.06779,74.317713 L 663.5356,74.095961 C 663.79289,73.973979 664.43521,73.676485 664.96298,73.434854 C 666.54934,72.708572 666.40066,72.802712 667.30233,71.953513 C 668.1459,71.159029 668.45291,70.912676 668.63209,70.886525 C 669.29678,70.789464 669.74333,70.680556 670.16886,70.511811 C 670.62212,70.332045 670.78417,70.203751 672.21307,68.893356 C 673.39223,67.812027 673.7656,67.48122 673.99398,67.315529 C 674.36671,67.045079 674.85595,66.975476 676.21407,66.999677 C 677.02172,67.014072 677.16671,67.009258 677.3026,66.963628 C 677.54873,66.881043 677.75482,66.732936 678.04247,66.432002 C 678.18878,66.278881 678.48874,66.009285 678.70903,65.832881 C 679.56399,65.148239 681.38883,63.831519 682.2969,63.244071 C 682.83405,62.896574 682.83553,62.895428 683.52111,62.285059 C 684.09054,61.778114 684.47641,61.493496 684.70119,61.414567 C 684.91293,61.34024 684.93449,61.341828 685.04373,61.439835 C 685.15035,61.535483 686.03394,62.9158 686.10698,63.100796 C 686.24105,63.440395 686.31503,63.800609 686.17623,64.085306 C 685.91549,64.62008 685.53887,65.389083 685.37514,65.683372 C 684.01271,68.277198 683.57638,69.315608 683.75865,69.53045 C 683.9225,69.723542 684.62451,69.838696 685.37986,69.796375 C 685.63126,69.7823 685.89265,69.757165 685.96072,69.740527 C 686.02879,69.723904 686.33237,69.592247 686.63534,69.447957 C 687.2815,69.140217 687.50858,69.064356 687.61979,69.119151 C 687.73185,69.174334 687.84105,69.402649 687.91551,69.737405 C 687.98654,70.056699 688.02311,70.630231 687.98411,70.812999 C 687.93246,71.054964 687.70898,71.202203 687.22201,71.315153 C 686.50051,71.482471 685.39489,72.093335 684.25696,72.95334 C 683.19009,73.759659 682.46764,74.510542 682.13211,75.161789 C 681.942,75.530794 681.88754,75.797654 681.7263,76.390906 C 681.60627,76.832449 680.94162,82.834448 680.93784,82.974322 C 680.93675,83.015121 681.05021,76.469849 681.04313,76.469662 C 681.03606,76.469473 680.47326,77.712213 680.46324,77.71196 C 680.45319,77.711706 679.26724,79.616271 679.14064,79.557035 C 679.01407,79.497811 680.84098,75.164632 681.29637,74.622279 C 681.72505,74.111718 682.69675,73.228218 683.92824,72.229312 C 684.68954,71.611793 685.08523,71.319382 685.27321,71.235423 C 685.34613,71.202844 685.65566,71.131984 685.96107,71.077938 C 686.71523,70.944468 686.90799,70.86055 686.9133,70.663386 C 686.91912,70.447624 686.66022,70.304493 686.31274,70.331392 C 685.85732,70.366626 685.34615,70.54459 684.72472,70.884297 C 684.05931,71.248032 683.51826,71.34764 682.8754,71.224763 C 682.69316,71.189901 682.45974,71.128317 682.35669,71.087851 C 682.13555,71.001063 682.1061,70.935662 682.16257,70.656928 C 682.28424,70.056285 682.7318,69.095497 684.09589,66.506489 C 684.52724,65.687816 684.91444,64.919869 684.95636,64.799939 C 684.99825,64.680011 685.1686,64.493296 685.20972,64.242845 C 685.27332,63.855494 684.94437,63.305418 684.69042,63.096047 C 684.1717,62.710286 684.01007,62.609168 683.74917,62.719264 C 683.27598,62.918962 682.63747,63.885861 680.89506,66.251944 C 678.10234,70.044237 677.77609,70.437338 677.00798,70.935529 C 675.9344,71.631833 674.25248,73.011563 672.89065,74.31307 C 671.94197,75.219744 671.23101,76.306587 670.94267,76.585575 C 670.75542,76.76678 669.96025,77.745288 669.68107,77.980021 C 668.26676,78.87945 667.27756,79.841733 666.4157,81.272282 L 666.26937,81.5468 L 666.16392,81.423003 z M 668.6295,76.32153 C 669.21552,76.035684 669.9402,75.554275 670.29047,75.218104 C 670.57943,74.940811 670.69555,74.663571 670.53426,74.6361 C 670.43004,74.618356 670.14725,74.769144 669.42758,75.226216 C 668.60241,75.75033 668.36243,75.871421 668.1267,75.882628 C 667.6555,75.905015 666.59584,75.72705 666.06717,75.536718 C 665.70072,75.404769 665.57392,75.331295 665.45729,75.183336 C 665.38321,75.089358 665.37887,75.065462 665.39778,74.856572 C 665.42208,74.588243 665.56219,74.28492 665.80217,73.981211 C 665.92518,73.825514 666.03467,73.73747 666.35462,73.537052 C 667.27497,72.96057 668.28907,72.478015 669.33316,72.119738 C 669.57465,72.036872 669.8005,71.958064 669.83504,71.944615 C 669.88896,71.923614 669.87183,71.906057 669.71409,71.820563 C 669.45042,71.677633 669.10707,71.548405 668.91845,71.521096 C 668.73014,71.493813 668.65946,71.521481 668.21594,71.796028 C 667.88157,72.003024 666.25388,73.091052 665.48621,73.620705 C 665.18146,73.830983 664.72391,74.14191 664.46942,74.311617 C 664.21494,74.481377 663.98901,74.640691 663.96737,74.665692 C 663.90854,74.733682 663.91479,74.906936 663.98115,75.047732 C 664.05408,75.202428 664.40387,75.635913 664.59279,75.805673 L 664.73365,75.93226 L 666.46235,76.190566 C 667.41313,76.332656 668.22595,76.451329 668.26859,76.454332 C 668.31124,76.457306 668.47365,76.397562 668.6295,76.32153 z M 668.25415,75.137027 C 668.72466,75.109689 668.82443,75.064367 669.20342,74.70609 C 669.59232,74.338446 669.98504,73.821189 670.13614,73.477602 L 670.18873,73.358048 L 670.0701,73.373682 C 670.00486,73.382261 669.32753,73.452719 668.5649,73.53022 L 667.17835,73.671095 L 667.05139,73.770438 C 666.8115,73.958127 666.48365,74.29119 666.35948,74.473383 C 666.21238,74.689172 666.19775,74.804299 666.3076,74.881267 C 666.43204,74.968454 666.84529,75.068852 667.23887,75.107529 C 667.76006,75.158734 667.84061,75.161069 668.25415,75.137027 z M 673.23289,72.884003 C 673.36857,72.793999 673.63342,72.530209 674.28656,71.834505 C 674.76864,71.320997 675.22409,70.892729 675.40078,70.786743 C 675.46833,70.746223 675.60467,70.684907 675.70377,70.650443 C 675.92776,70.572528 676.07455,70.48466 676.28578,70.302053 C 676.73011,69.917918 677.18695,69.102408 677.12911,68.796643 C 677.11207,68.706574 677.1094,68.705506 676.85795,68.688776 C 675.95769,68.628965 675.25732,68.824768 674.75202,69.277624 C 674.16986,69.799364 673.40627,71.172265 673.12421,72.204378 C 673.01269,72.612445 672.98317,72.817187 673.02395,72.899506 C 673.06635,72.985066 673.08235,72.983877 673.23289,72.884003 z M 671.57619,71.796562 C 672.34776,71.643906 672.98398,71.514265 672.99004,71.508448 C 672.99608,71.502658 673.07634,71.260586 673.16839,70.970524 C 673.54217,69.792774 673.83364,69.271727 674.26438,69.011271 C 674.90419,68.624388 676.2258,68.209742 676.95832,68.166034 C 677.22424,68.150158 677.26926,68.137257 677.51161,68.007456 C 677.8718,67.814601 678.3997,67.456604 679.12431,66.913865 C 679.45905,66.663135 679.91088,66.333048 680.12838,66.180365 C 680.68623,65.788718 680.96159,65.51887 680.96589,65.359637 C 680.96661,65.332846 680.95475,65.310646 680.93954,65.310246 C 680.92433,65.309872 680.56473,65.552198 680.14043,65.848756 C 679.71612,66.145328 679.09133,66.572036 678.75201,66.797005 C 677.56622,67.583204 677.74205,67.5241 676.57466,67.528756 C 676.06082,67.530783 675.36265,67.538481 675.02318,67.545859 C 674.42556,67.558856 674.40209,67.56187 674.28268,67.640998 C 674.21487,67.685947 673.78028,68.103582 673.31692,68.569102 C 672.3073,69.583402 671.44162,70.400928 671.10522,70.657742 C 670.9698,70.761139 670.75997,70.888754 670.63892,70.941346 C 670.39611,71.046839 670.3172,71.116109 670.21049,71.317382 C 670.13004,71.469129 670.04208,71.806968 670.03645,71.985906 C 670.03309,72.092787 670.03952,72.102793 670.10304,72.089172 C 670.14171,72.080887 670.80463,71.949217 671.57619,71.796562 z " + id="path18898" + sodipodi:nodetypes="csssssscccscsscsssssscsssscsssssssssssssssscssssccssssscscscscccccsscsssssssssssscsscccsccsccccccsssccssssscsssccssssssssssscsscsssssc" /> + <g + id="g18900" + style="fill:#002ddb;fill-opacity:1"> + <path + id="path18902" + d="M 680.83905,76.838803 C 680.83905,76.838803 679.45446,81.3797 679.41945,81.449838 C 679.22156,81.846006 678.85659,82.994032 678.98224,83.364797 C 679.13021,83.801538 679.68481,84.436589 679.94365,84.754121 C 680.2025,85.071759 680.31342,85.230473 680.23945,85.349692 C 680.16554,85.468783 679.20413,85.468783 679.20413,85.468783 C 679.20413,85.468783 678.46465,84.396936 678.31664,83.999943 C 678.16882,83.603079 678.09481,82.769436 678.24277,82.491624 C 678.39065,82.213703 680.83905,76.838803 680.83905,76.838803 z " + i:knockout="Off" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:21.03624916;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <polygon + id="polygon18904" + points="272.565,567.14 214.39,617.501 287.326,574.086 272.565,567.14 " + a:adobe-blending-mode="lighten" + i:knockout="Off" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:17.46648407;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + transform="matrix(4.348075e-2,0,0,4.188821e-2,659.2285,53.92804)" /> + <polygon + id="polygon18906" + points="329.873,549.776 230.887,678.282 344.633,558.457 329.873,549.776 " + a:adobe-blending-mode="lighten" + i:knockout="Off" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:17.46648407;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + transform="matrix(4.348075e-2,0,0,4.188821e-2,659.2285,53.92804)" /> + <polygon + id="polygon18908" + points="337.688,594.058 262.146,713.882 355.053,601.872 337.688,594.058 " + a:adobe-blending-mode="lighten" + i:knockout="Off" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:17.46648407;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + transform="matrix(4.348075e-2,0,0,4.188821e-2,659.2285,53.92804)" /> + <polygon + id="polygon18910" + points="381.97,588.847 318.584,737.327 402.809,593.19 381.97,588.847 " + a:adobe-blending-mode="lighten" + i:knockout="Off" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:17.46648407;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + transform="matrix(4.348075e-2,0,0,4.188821e-2,659.2285,53.92804)" /> + <polygon + id="polygon18912" + points="407.151,627.921 401.94,763.375 424.515,629.658 407.151,627.921 " + a:adobe-blending-mode="lighten" + i:knockout="Off" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:17.46648407;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + transform="matrix(4.348075e-2,0,0,4.188821e-2,659.2285,53.92804)" /> + </g> + <path + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#002ddb;stroke-width:0.57512665;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 665.61116,79.917164 C 666.44697,82.39457 673.07693,87.947061 680.79385,85.431014 C 680.71584,83.40215 681.5919,77.986014 681.41239,76.464632" + id="path18914" + sodipodi:nodetypes="ccc" /> + </g> + </g> + <g + transform="matrix(0.6875,0,0,0.6875,238.2292,-107.12068)" + id="g18940" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/22x22/actions/amarok_settings_view.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <path + sodipodi:type="arc" + style="fill:url(#linearGradient18972);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;display:inline" + id="path18942" + sodipodi:cx="418.94757" + sodipodi:cy="444.83228" + sodipodi:rx="9.8239412" + sodipodi:ry="9.8239412" + d="M 428.77151 444.83228 A 9.8239412 9.8239412 0 1 1 409.12363,444.83228 A 9.8239412 9.8239412 0 1 1 428.77151 444.83228 z" + transform="matrix(1.628674,0,0,1.628674,589.2965,25.3152)" + inkscape:export-filename="/home/lando/Projects/Amarok/Setting-dialog/amarok_settings_view.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:type="arc" + style="fill:url(#radialGradient18974);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;display:inline" + id="path18944" + sodipodi:cx="418.51233" + sodipodi:cy="446.44885" + sodipodi:rx="11.005301" + sodipodi:ry="11.005301" + d="M 429.51763 446.44885 A 11.005301 11.005301 0 1 1 407.50703,446.44885 A 11.005301 11.005301 0 1 1 429.51763 446.44885 z" + transform="matrix(1.351475,0,0,1.37725,706.0166,134.9307)" + inkscape:export-filename="/home/lando/Projects/Amarok/Setting-dialog/amarok_settings_view.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + style="opacity:0.85531915;fill:url(#linearGradient18976);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;display:inline" + d="M 1285.507,750.519 C 1285.507,758.95943 1279.2149,751.49937 1271.4623,751.49937 C 1263.7097,751.49937 1257.4177,758.95943 1257.4177,750.519 C 1257.4177,742.07865 1263.7097,735.22846 1271.4623,735.22846 C 1279.2149,735.22846 1285.507,742.07865 1285.507,750.519 z " + id="path18946" + sodipodi:nodetypes="csssc" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Setting-dialog/amarok_settings_view.png" /> + <g + style="display:inline" + id="g18948" + transform="matrix(1.033489,0,0,1.033488,572.0631,676.0966)" + inkscape:export-filename="/home/lando/Projects/Amarok/Setting-dialog/amarok_settings_view.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <path + transform="matrix(0.116367,-0.126279,0.138815,0.105858,2044.201,-572.7461)" + d="M -7824.3296 -3285.1472 A 25.97311 5.306335 0 1 1 -7876.2758,-3285.1472 A 25.97311 5.306335 0 1 1 -7824.3296 -3285.1472 z" + sodipodi:ry="5.306335" + sodipodi:rx="25.97311" + sodipodi:cy="-3285.1472" + sodipodi:cx="-7850.3027" + id="path18950" + style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:21.03624916;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0.68131869" + sodipodi:type="arc" /> + <path + style="fill:url(#linearGradient18978);fill-opacity:1" + d="M 684.58932,62.015066 C 683.95163,62.03676 682.73778,63.425764 682.28042,63.646529 C 681.81946,63.869109 678.16982,66.391273 677.82409,66.836327 C 677.47838,67.281337 677.01748,67.46692 676.51816,67.429844 C 676.01883,67.392834 674.44026,67.377225 673.90249,67.673903 C 673.36463,67.970636 670.90988,70.397011 670.29527,70.693715 C 669.68068,70.990341 668.87865,71.141876 668.49449,71.178979 C 668.11043,71.216028 666.83809,72.584967 666.49238,72.844636 C 666.14667,73.10429 663.41458,74.2451 663.03046,74.467545 C 662.64631,74.690071 665.83291,77.807344 666.29388,77.881456 C 666.75485,77.955728 665.97647,79.643959 665.08957,79.643959 C 664.99189,79.643959 665.88202,82.441727 670.53651,84.906802 C 674.56037,87.037898 678.82957,86.094974 680.41507,85.578197 C 681.53629,85.212648 680.74985,85.010148 680.78834,84.416789 C 680.82666,83.823378 680.8587,83.043556 680.93555,82.376043 C 681.01244,81.708505 681.28161,76.107653 682.28042,74.624229 C 683.27912,73.140873 686.13308,71.360612 687.13185,71.175177 C 688.13068,70.989794 687.93916,70.619136 687.93916,70.285379 C 687.93916,69.95165 687.78546,69.209626 687.51651,69.098343 C 687.24765,68.987127 686.19822,69.654757 685.85251,69.728899 C 685.5068,69.803078 683.85489,69.914575 683.62435,69.543728 C 683.3939,69.172775 686.14108,63.906545 685.64166,63.276177 C 685.14247,62.645555 684.79657,62.015066 684.60452,62.015066 C 684.59957,62.015066 684.59434,62.014895 684.58932,62.015066 z M 676.42414,68.978694 C 676.73261,68.974532 676.94176,69.006232 676.94176,69.006232 C 677.11748,69.40176 676.29813,70.644697 675.53706,70.814323 C 674.77614,70.983791 673.01974,73.4706 672.96126,72.905408 C 672.90256,72.340212 673.66355,70.476005 674.54169,69.628234 C 675.09392,69.095034 675.90135,68.985752 676.42414,68.978694 z " + id="path18952" + sodipodi:nodetypes="ccccsccsccsccccccsssccsccscccc" /> + <path + style="fill:url(#linearGradient18980);fill-opacity:1" + d="M 666.16392,81.423003 C 665.9031,81.091515 665.42361,80.435598 665.26754,80.004036 C 665.14138,79.655175 665.13889,79.611803 665.24522,79.614525 C 665.37457,79.61782 665.53249,79.582317 665.71495,79.414066 C 666.12829,79.032933 666.46122,78.479561 666.36093,78.193327 C 666.34151,78.137933 666.28178,78.082005 666.19489,78.037937 C 665.68311,77.778228 663.68597,75.546616 663.20752,74.699847 C 663.14869,74.595715 663.09317,74.467102 663.08417,74.414107 L 663.06779,74.317713 L 663.5356,74.095961 C 663.79289,73.973979 664.43521,73.676485 664.96298,73.434854 C 666.54934,72.708572 666.40066,72.802712 667.30233,71.953513 C 668.1459,71.159029 668.45291,70.912676 668.63209,70.886525 C 669.29678,70.789464 669.74333,70.680556 670.16886,70.511811 C 670.62212,70.332045 670.78417,70.203751 672.21307,68.893356 C 673.39223,67.812027 673.7656,67.48122 673.99398,67.315529 C 674.36671,67.045079 674.85595,66.975476 676.21407,66.999677 C 677.02172,67.014072 677.16671,67.009258 677.3026,66.963628 C 677.54873,66.881043 677.75482,66.732936 678.04247,66.432002 C 678.18878,66.278881 678.48874,66.009285 678.70903,65.832881 C 679.56399,65.148239 681.38883,63.831519 682.2969,63.244071 C 682.83405,62.896574 682.83553,62.895428 683.52111,62.285059 C 684.09054,61.778114 684.47641,61.493496 684.70119,61.414567 C 684.91293,61.34024 684.93449,61.341828 685.04373,61.439835 C 685.15035,61.535483 686.03394,62.9158 686.10698,63.100796 C 686.24105,63.440395 686.31503,63.800609 686.17623,64.085306 C 685.91549,64.62008 685.53887,65.389083 685.37514,65.683372 C 684.01271,68.277198 683.57638,69.315608 683.75865,69.53045 C 683.9225,69.723542 684.62451,69.838696 685.37986,69.796375 C 685.63126,69.7823 685.89265,69.757165 685.96072,69.740527 C 686.02879,69.723904 686.33237,69.592247 686.63534,69.447957 C 687.2815,69.140217 687.50858,69.064356 687.61979,69.119151 C 687.73185,69.174334 687.84105,69.402649 687.91551,69.737405 C 687.98654,70.056699 688.02311,70.630231 687.98411,70.812999 C 687.93246,71.054964 687.70898,71.202203 687.22201,71.315153 C 686.50051,71.482471 685.39489,72.093335 684.25696,72.95334 C 683.19009,73.759659 682.46764,74.510542 682.13211,75.161789 C 681.942,75.530794 681.88754,75.797654 681.7263,76.390906 C 681.60627,76.832449 680.94162,82.834448 680.93784,82.974322 C 680.93675,83.015121 681.05021,76.469849 681.04313,76.469662 C 681.03606,76.469473 680.47326,77.712213 680.46324,77.71196 C 680.45319,77.711706 679.26724,79.616271 679.14064,79.557035 C 679.01407,79.497811 680.84098,75.164632 681.29637,74.622279 C 681.72505,74.111718 682.69675,73.228218 683.92824,72.229312 C 684.68954,71.611793 685.08523,71.319382 685.27321,71.235423 C 685.34613,71.202844 685.65566,71.131984 685.96107,71.077938 C 686.71523,70.944468 686.90799,70.86055 686.9133,70.663386 C 686.91912,70.447624 686.66022,70.304493 686.31274,70.331392 C 685.85732,70.366626 685.34615,70.54459 684.72472,70.884297 C 684.05931,71.248032 683.51826,71.34764 682.8754,71.224763 C 682.69316,71.189901 682.45974,71.128317 682.35669,71.087851 C 682.13555,71.001063 682.1061,70.935662 682.16257,70.656928 C 682.28424,70.056285 682.7318,69.095497 684.09589,66.506489 C 684.52724,65.687816 684.91444,64.919869 684.95636,64.799939 C 684.99825,64.680011 685.1686,64.493296 685.20972,64.242845 C 685.27332,63.855494 684.94437,63.305418 684.69042,63.096047 C 684.1717,62.710286 684.01007,62.609168 683.74917,62.719264 C 683.27598,62.918962 682.63747,63.885861 680.89506,66.251944 C 678.10234,70.044237 677.77609,70.437338 677.00798,70.935529 C 675.9344,71.631833 674.25248,73.011563 672.89065,74.31307 C 671.94197,75.219744 671.23101,76.306587 670.94267,76.585575 C 670.75542,76.76678 669.96025,77.745288 669.68107,77.980021 C 668.26676,78.87945 667.27756,79.841733 666.4157,81.272282 L 666.26937,81.5468 L 666.16392,81.423003 z M 668.6295,76.32153 C 669.21552,76.035684 669.9402,75.554275 670.29047,75.218104 C 670.57943,74.940811 670.69555,74.663571 670.53426,74.6361 C 670.43004,74.618356 670.14725,74.769144 669.42758,75.226216 C 668.60241,75.75033 668.36243,75.871421 668.1267,75.882628 C 667.6555,75.905015 666.59584,75.72705 666.06717,75.536718 C 665.70072,75.404769 665.57392,75.331295 665.45729,75.183336 C 665.38321,75.089358 665.37887,75.065462 665.39778,74.856572 C 665.42208,74.588243 665.56219,74.28492 665.80217,73.981211 C 665.92518,73.825514 666.03467,73.73747 666.35462,73.537052 C 667.27497,72.96057 668.28907,72.478015 669.33316,72.119738 C 669.57465,72.036872 669.8005,71.958064 669.83504,71.944615 C 669.88896,71.923614 669.87183,71.906057 669.71409,71.820563 C 669.45042,71.677633 669.10707,71.548405 668.91845,71.521096 C 668.73014,71.493813 668.65946,71.521481 668.21594,71.796028 C 667.88157,72.003024 666.25388,73.091052 665.48621,73.620705 C 665.18146,73.830983 664.72391,74.14191 664.46942,74.311617 C 664.21494,74.481377 663.98901,74.640691 663.96737,74.665692 C 663.90854,74.733682 663.91479,74.906936 663.98115,75.047732 C 664.05408,75.202428 664.40387,75.635913 664.59279,75.805673 L 664.73365,75.93226 L 666.46235,76.190566 C 667.41313,76.332656 668.22595,76.451329 668.26859,76.454332 C 668.31124,76.457306 668.47365,76.397562 668.6295,76.32153 z M 668.25415,75.137027 C 668.72466,75.109689 668.82443,75.064367 669.20342,74.70609 C 669.59232,74.338446 669.98504,73.821189 670.13614,73.477602 L 670.18873,73.358048 L 670.0701,73.373682 C 670.00486,73.382261 669.32753,73.452719 668.5649,73.53022 L 667.17835,73.671095 L 667.05139,73.770438 C 666.8115,73.958127 666.48365,74.29119 666.35948,74.473383 C 666.21238,74.689172 666.19775,74.804299 666.3076,74.881267 C 666.43204,74.968454 666.84529,75.068852 667.23887,75.107529 C 667.76006,75.158734 667.84061,75.161069 668.25415,75.137027 z M 673.23289,72.884003 C 673.36857,72.793999 673.63342,72.530209 674.28656,71.834505 C 674.76864,71.320997 675.22409,70.892729 675.40078,70.786743 C 675.46833,70.746223 675.60467,70.684907 675.70377,70.650443 C 675.92776,70.572528 676.07455,70.48466 676.28578,70.302053 C 676.73011,69.917918 677.18695,69.102408 677.12911,68.796643 C 677.11207,68.706574 677.1094,68.705506 676.85795,68.688776 C 675.95769,68.628965 675.25732,68.824768 674.75202,69.277624 C 674.16986,69.799364 673.40627,71.172265 673.12421,72.204378 C 673.01269,72.612445 672.98317,72.817187 673.02395,72.899506 C 673.06635,72.985066 673.08235,72.983877 673.23289,72.884003 z M 671.57619,71.796562 C 672.34776,71.643906 672.98398,71.514265 672.99004,71.508448 C 672.99608,71.502658 673.07634,71.260586 673.16839,70.970524 C 673.54217,69.792774 673.83364,69.271727 674.26438,69.011271 C 674.90419,68.624388 676.2258,68.209742 676.95832,68.166034 C 677.22424,68.150158 677.26926,68.137257 677.51161,68.007456 C 677.8718,67.814601 678.3997,67.456604 679.12431,66.913865 C 679.45905,66.663135 679.91088,66.333048 680.12838,66.180365 C 680.68623,65.788718 680.96159,65.51887 680.96589,65.359637 C 680.96661,65.332846 680.95475,65.310646 680.93954,65.310246 C 680.92433,65.309872 680.56473,65.552198 680.14043,65.848756 C 679.71612,66.145328 679.09133,66.572036 678.75201,66.797005 C 677.56622,67.583204 677.74205,67.5241 676.57466,67.528756 C 676.06082,67.530783 675.36265,67.538481 675.02318,67.545859 C 674.42556,67.558856 674.40209,67.56187 674.28268,67.640998 C 674.21487,67.685947 673.78028,68.103582 673.31692,68.569102 C 672.3073,69.583402 671.44162,70.400928 671.10522,70.657742 C 670.9698,70.761139 670.75997,70.888754 670.63892,70.941346 C 670.39611,71.046839 670.3172,71.116109 670.21049,71.317382 C 670.13004,71.469129 670.04208,71.806968 670.03645,71.985906 C 670.03309,72.092787 670.03952,72.102793 670.10304,72.089172 C 670.14171,72.080887 670.80463,71.949217 671.57619,71.796562 z " + id="path18954" + sodipodi:nodetypes="csssssscccscsscsssssscsssscsssssssssssssssscssssccssssscscscscccccsscsssssssssssscsscccsccsccccccsssccssssscsssccssssssssssscsscsssssc" /> + <g + id="g18956" + style="fill:#002ddb;fill-opacity:1"> + <path + id="path18958" + d="M 680.83905,76.838803 C 680.83905,76.838803 679.45446,81.3797 679.41945,81.449838 C 679.22156,81.846006 678.85659,82.994032 678.98224,83.364797 C 679.13021,83.801538 679.68481,84.436589 679.94365,84.754121 C 680.2025,85.071759 680.31342,85.230473 680.23945,85.349692 C 680.16554,85.468783 679.20413,85.468783 679.20413,85.468783 C 679.20413,85.468783 678.46465,84.396936 678.31664,83.999943 C 678.16882,83.603079 678.09481,82.769436 678.24277,82.491624 C 678.39065,82.213703 680.83905,76.838803 680.83905,76.838803 z " + i:knockout="Off" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:21.03624916;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <polygon + id="polygon18960" + points="272.565,567.14 214.39,617.501 287.326,574.086 272.565,567.14 " + a:adobe-blending-mode="lighten" + i:knockout="Off" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:17.46648407;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + transform="matrix(4.348075e-2,0,0,4.188821e-2,659.2285,53.92804)" /> + <polygon + id="polygon18962" + points="329.873,549.776 230.887,678.282 344.633,558.457 329.873,549.776 " + a:adobe-blending-mode="lighten" + i:knockout="Off" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:17.46648407;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + transform="matrix(4.348075e-2,0,0,4.188821e-2,659.2285,53.92804)" /> + <polygon + id="polygon18964" + points="337.688,594.058 262.146,713.882 355.053,601.872 337.688,594.058 " + a:adobe-blending-mode="lighten" + i:knockout="Off" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:17.46648407;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + transform="matrix(4.348075e-2,0,0,4.188821e-2,659.2285,53.92804)" /> + <polygon + id="polygon18966" + points="381.97,588.847 318.584,737.327 402.809,593.19 381.97,588.847 " + a:adobe-blending-mode="lighten" + i:knockout="Off" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:17.46648407;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + transform="matrix(4.348075e-2,0,0,4.188821e-2,659.2285,53.92804)" /> + <polygon + id="polygon18968" + points="407.151,627.921 401.94,763.375 424.515,629.658 407.151,627.921 " + a:adobe-blending-mode="lighten" + i:knockout="Off" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:17.46648407;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + transform="matrix(4.348075e-2,0,0,4.188821e-2,659.2285,53.92804)" /> + </g> + <path + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#002ddb;stroke-width:0.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 665.61116,79.917164 C 666.44697,82.39457 673.07693,87.947061 680.79385,85.431014 C 680.71584,83.40215 681.5919,77.986014 681.41239,76.464632" + id="path18970" + sodipodi:nodetypes="ccc" /> + </g> + </g> + <g + id="g18982" + transform="matrix(0.5,0,0,0.5,444.548,36.467127)" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/16x16/actions/amarok_settings_view.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Setting-dialog/amarok_settings_view.png" + transform="matrix(1.628674,0,0,1.628674,589.2965,25.3152)" + d="M 428.77151 444.83228 A 9.8239412 9.8239412 0 1 1 409.12363,444.83228 A 9.8239412 9.8239412 0 1 1 428.77151 444.83228 z" + sodipodi:ry="9.8239412" + sodipodi:rx="9.8239412" + sodipodi:cy="444.83228" + sodipodi:cx="418.94757" + id="path18984" + style="fill:url(#linearGradient19014);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;display:inline" + sodipodi:type="arc" /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Setting-dialog/amarok_settings_view.png" + transform="matrix(1.351475,0,0,1.37725,706.0166,134.9307)" + d="M 429.51763 446.44885 A 11.005301 11.005301 0 1 1 407.50703,446.44885 A 11.005301 11.005301 0 1 1 429.51763 446.44885 z" + sodipodi:ry="11.005301" + sodipodi:rx="11.005301" + sodipodi:cy="446.44885" + sodipodi:cx="418.51233" + id="path18986" + style="fill:url(#radialGradient19016);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;display:inline" + sodipodi:type="arc" /> + <path + inkscape:export-filename="/home/lando/Projects/Amarok/Setting-dialog/amarok_settings_view.png" + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + sodipodi:nodetypes="csssc" + id="path18988" + d="M 1285.507,750.519 C 1285.507,758.95943 1279.2149,751.49937 1271.4623,751.49937 C 1263.7097,751.49937 1257.4177,758.95943 1257.4177,750.519 C 1257.4177,742.07865 1263.7097,735.22846 1271.4623,735.22846 C 1279.2149,735.22846 1285.507,742.07865 1285.507,750.519 z " + style="opacity:0.85531915;fill:url(#linearGradient19018);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;display:inline" /> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Setting-dialog/amarok_settings_view.png" + transform="matrix(1.033489,0,0,1.033488,572.0631,676.0966)" + id="g18990" + style="display:inline"> + <path + sodipodi:type="arc" + style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:21.03624916;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0.68131869" + id="path18992" + sodipodi:cx="-7850.3027" + sodipodi:cy="-3285.1472" + sodipodi:rx="25.97311" + sodipodi:ry="5.306335" + d="M -7824.3296 -3285.1472 A 25.97311 5.306335 0 1 1 -7876.2758,-3285.1472 A 25.97311 5.306335 0 1 1 -7824.3296 -3285.1472 z" + transform="matrix(0.116367,-0.126279,0.138815,0.105858,2044.201,-572.7461)" /> + <path + sodipodi:nodetypes="ccccsccsccsccccccsssccsccscccc" + id="path18994" + d="M 684.58932,62.015066 C 683.95163,62.03676 682.73778,63.425764 682.28042,63.646529 C 681.81946,63.869109 678.16982,66.391273 677.82409,66.836327 C 677.47838,67.281337 677.01748,67.46692 676.51816,67.429844 C 676.01883,67.392834 674.44026,67.377225 673.90249,67.673903 C 673.36463,67.970636 670.90988,70.397011 670.29527,70.693715 C 669.68068,70.990341 668.87865,71.141876 668.49449,71.178979 C 668.11043,71.216028 666.83809,72.584967 666.49238,72.844636 C 666.14667,73.10429 663.41458,74.2451 663.03046,74.467545 C 662.64631,74.690071 665.83291,77.807344 666.29388,77.881456 C 666.75485,77.955728 665.97647,79.643959 665.08957,79.643959 C 664.99189,79.643959 665.88202,82.441727 670.53651,84.906802 C 674.56037,87.037898 678.82957,86.094974 680.41507,85.578197 C 681.53629,85.212648 680.74985,85.010148 680.78834,84.416789 C 680.82666,83.823378 680.8587,83.043556 680.93555,82.376043 C 681.01244,81.708505 681.28161,76.107653 682.28042,74.624229 C 683.27912,73.140873 686.13308,71.360612 687.13185,71.175177 C 688.13068,70.989794 687.93916,70.619136 687.93916,70.285379 C 687.93916,69.95165 687.78546,69.209626 687.51651,69.098343 C 687.24765,68.987127 686.19822,69.654757 685.85251,69.728899 C 685.5068,69.803078 683.85489,69.914575 683.62435,69.543728 C 683.3939,69.172775 686.14108,63.906545 685.64166,63.276177 C 685.14247,62.645555 684.79657,62.015066 684.60452,62.015066 C 684.59957,62.015066 684.59434,62.014895 684.58932,62.015066 z M 676.42414,68.978694 C 676.73261,68.974532 676.94176,69.006232 676.94176,69.006232 C 677.11748,69.40176 676.29813,70.644697 675.53706,70.814323 C 674.77614,70.983791 673.01974,73.4706 672.96126,72.905408 C 672.90256,72.340212 673.66355,70.476005 674.54169,69.628234 C 675.09392,69.095034 675.90135,68.985752 676.42414,68.978694 z " + style="fill:url(#linearGradient19020);fill-opacity:1" /> + <path + sodipodi:nodetypes="csssssscccscsscsssssscsssscsssssssssssssssscssssccssssscscscscccccsscsssssssssssscsscccsccsccccccsssccssssscsssccssssssssssscsscsssssc" + id="path18996" + d="M 666.16392,81.423003 C 665.9031,81.091515 665.42361,80.435598 665.26754,80.004036 C 665.14138,79.655175 665.13889,79.611803 665.24522,79.614525 C 665.37457,79.61782 665.53249,79.582317 665.71495,79.414066 C 666.12829,79.032933 666.46122,78.479561 666.36093,78.193327 C 666.34151,78.137933 666.28178,78.082005 666.19489,78.037937 C 665.68311,77.778228 663.68597,75.546616 663.20752,74.699847 C 663.14869,74.595715 663.09317,74.467102 663.08417,74.414107 L 663.06779,74.317713 L 663.5356,74.095961 C 663.79289,73.973979 664.43521,73.676485 664.96298,73.434854 C 666.54934,72.708572 666.40066,72.802712 667.30233,71.953513 C 668.1459,71.159029 668.45291,70.912676 668.63209,70.886525 C 669.29678,70.789464 669.74333,70.680556 670.16886,70.511811 C 670.62212,70.332045 670.78417,70.203751 672.21307,68.893356 C 673.39223,67.812027 673.7656,67.48122 673.99398,67.315529 C 674.36671,67.045079 674.85595,66.975476 676.21407,66.999677 C 677.02172,67.014072 677.16671,67.009258 677.3026,66.963628 C 677.54873,66.881043 677.75482,66.732936 678.04247,66.432002 C 678.18878,66.278881 678.48874,66.009285 678.70903,65.832881 C 679.56399,65.148239 681.38883,63.831519 682.2969,63.244071 C 682.83405,62.896574 682.83553,62.895428 683.52111,62.285059 C 684.09054,61.778114 684.47641,61.493496 684.70119,61.414567 C 684.91293,61.34024 684.93449,61.341828 685.04373,61.439835 C 685.15035,61.535483 686.03394,62.9158 686.10698,63.100796 C 686.24105,63.440395 686.31503,63.800609 686.17623,64.085306 C 685.91549,64.62008 685.53887,65.389083 685.37514,65.683372 C 684.01271,68.277198 683.57638,69.315608 683.75865,69.53045 C 683.9225,69.723542 684.62451,69.838696 685.37986,69.796375 C 685.63126,69.7823 685.89265,69.757165 685.96072,69.740527 C 686.02879,69.723904 686.33237,69.592247 686.63534,69.447957 C 687.2815,69.140217 687.50858,69.064356 687.61979,69.119151 C 687.73185,69.174334 687.84105,69.402649 687.91551,69.737405 C 687.98654,70.056699 688.02311,70.630231 687.98411,70.812999 C 687.93246,71.054964 687.70898,71.202203 687.22201,71.315153 C 686.50051,71.482471 685.39489,72.093335 684.25696,72.95334 C 683.19009,73.759659 682.46764,74.510542 682.13211,75.161789 C 681.942,75.530794 681.88754,75.797654 681.7263,76.390906 C 681.60627,76.832449 680.94162,82.834448 680.93784,82.974322 C 680.93675,83.015121 681.05021,76.469849 681.04313,76.469662 C 681.03606,76.469473 680.47326,77.712213 680.46324,77.71196 C 680.45319,77.711706 679.26724,79.616271 679.14064,79.557035 C 679.01407,79.497811 680.84098,75.164632 681.29637,74.622279 C 681.72505,74.111718 682.69675,73.228218 683.92824,72.229312 C 684.68954,71.611793 685.08523,71.319382 685.27321,71.235423 C 685.34613,71.202844 685.65566,71.131984 685.96107,71.077938 C 686.71523,70.944468 686.90799,70.86055 686.9133,70.663386 C 686.91912,70.447624 686.66022,70.304493 686.31274,70.331392 C 685.85732,70.366626 685.34615,70.54459 684.72472,70.884297 C 684.05931,71.248032 683.51826,71.34764 682.8754,71.224763 C 682.69316,71.189901 682.45974,71.128317 682.35669,71.087851 C 682.13555,71.001063 682.1061,70.935662 682.16257,70.656928 C 682.28424,70.056285 682.7318,69.095497 684.09589,66.506489 C 684.52724,65.687816 684.91444,64.919869 684.95636,64.799939 C 684.99825,64.680011 685.1686,64.493296 685.20972,64.242845 C 685.27332,63.855494 684.94437,63.305418 684.69042,63.096047 C 684.1717,62.710286 684.01007,62.609168 683.74917,62.719264 C 683.27598,62.918962 682.63747,63.885861 680.89506,66.251944 C 678.10234,70.044237 677.77609,70.437338 677.00798,70.935529 C 675.9344,71.631833 674.25248,73.011563 672.89065,74.31307 C 671.94197,75.219744 671.23101,76.306587 670.94267,76.585575 C 670.75542,76.76678 669.96025,77.745288 669.68107,77.980021 C 668.26676,78.87945 667.27756,79.841733 666.4157,81.272282 L 666.26937,81.5468 L 666.16392,81.423003 z M 668.6295,76.32153 C 669.21552,76.035684 669.9402,75.554275 670.29047,75.218104 C 670.57943,74.940811 670.69555,74.663571 670.53426,74.6361 C 670.43004,74.618356 670.14725,74.769144 669.42758,75.226216 C 668.60241,75.75033 668.36243,75.871421 668.1267,75.882628 C 667.6555,75.905015 666.59584,75.72705 666.06717,75.536718 C 665.70072,75.404769 665.57392,75.331295 665.45729,75.183336 C 665.38321,75.089358 665.37887,75.065462 665.39778,74.856572 C 665.42208,74.588243 665.56219,74.28492 665.80217,73.981211 C 665.92518,73.825514 666.03467,73.73747 666.35462,73.537052 C 667.27497,72.96057 668.28907,72.478015 669.33316,72.119738 C 669.57465,72.036872 669.8005,71.958064 669.83504,71.944615 C 669.88896,71.923614 669.87183,71.906057 669.71409,71.820563 C 669.45042,71.677633 669.10707,71.548405 668.91845,71.521096 C 668.73014,71.493813 668.65946,71.521481 668.21594,71.796028 C 667.88157,72.003024 666.25388,73.091052 665.48621,73.620705 C 665.18146,73.830983 664.72391,74.14191 664.46942,74.311617 C 664.21494,74.481377 663.98901,74.640691 663.96737,74.665692 C 663.90854,74.733682 663.91479,74.906936 663.98115,75.047732 C 664.05408,75.202428 664.40387,75.635913 664.59279,75.805673 L 664.73365,75.93226 L 666.46235,76.190566 C 667.41313,76.332656 668.22595,76.451329 668.26859,76.454332 C 668.31124,76.457306 668.47365,76.397562 668.6295,76.32153 z M 668.25415,75.137027 C 668.72466,75.109689 668.82443,75.064367 669.20342,74.70609 C 669.59232,74.338446 669.98504,73.821189 670.13614,73.477602 L 670.18873,73.358048 L 670.0701,73.373682 C 670.00486,73.382261 669.32753,73.452719 668.5649,73.53022 L 667.17835,73.671095 L 667.05139,73.770438 C 666.8115,73.958127 666.48365,74.29119 666.35948,74.473383 C 666.21238,74.689172 666.19775,74.804299 666.3076,74.881267 C 666.43204,74.968454 666.84529,75.068852 667.23887,75.107529 C 667.76006,75.158734 667.84061,75.161069 668.25415,75.137027 z M 673.23289,72.884003 C 673.36857,72.793999 673.63342,72.530209 674.28656,71.834505 C 674.76864,71.320997 675.22409,70.892729 675.40078,70.786743 C 675.46833,70.746223 675.60467,70.684907 675.70377,70.650443 C 675.92776,70.572528 676.07455,70.48466 676.28578,70.302053 C 676.73011,69.917918 677.18695,69.102408 677.12911,68.796643 C 677.11207,68.706574 677.1094,68.705506 676.85795,68.688776 C 675.95769,68.628965 675.25732,68.824768 674.75202,69.277624 C 674.16986,69.799364 673.40627,71.172265 673.12421,72.204378 C 673.01269,72.612445 672.98317,72.817187 673.02395,72.899506 C 673.06635,72.985066 673.08235,72.983877 673.23289,72.884003 z M 671.57619,71.796562 C 672.34776,71.643906 672.98398,71.514265 672.99004,71.508448 C 672.99608,71.502658 673.07634,71.260586 673.16839,70.970524 C 673.54217,69.792774 673.83364,69.271727 674.26438,69.011271 C 674.90419,68.624388 676.2258,68.209742 676.95832,68.166034 C 677.22424,68.150158 677.26926,68.137257 677.51161,68.007456 C 677.8718,67.814601 678.3997,67.456604 679.12431,66.913865 C 679.45905,66.663135 679.91088,66.333048 680.12838,66.180365 C 680.68623,65.788718 680.96159,65.51887 680.96589,65.359637 C 680.96661,65.332846 680.95475,65.310646 680.93954,65.310246 C 680.92433,65.309872 680.56473,65.552198 680.14043,65.848756 C 679.71612,66.145328 679.09133,66.572036 678.75201,66.797005 C 677.56622,67.583204 677.74205,67.5241 676.57466,67.528756 C 676.06082,67.530783 675.36265,67.538481 675.02318,67.545859 C 674.42556,67.558856 674.40209,67.56187 674.28268,67.640998 C 674.21487,67.685947 673.78028,68.103582 673.31692,68.569102 C 672.3073,69.583402 671.44162,70.400928 671.10522,70.657742 C 670.9698,70.761139 670.75997,70.888754 670.63892,70.941346 C 670.39611,71.046839 670.3172,71.116109 670.21049,71.317382 C 670.13004,71.469129 670.04208,71.806968 670.03645,71.985906 C 670.03309,72.092787 670.03952,72.102793 670.10304,72.089172 C 670.14171,72.080887 670.80463,71.949217 671.57619,71.796562 z " + style="fill:url(#linearGradient19022);fill-opacity:1" /> + <g + style="fill:#002ddb;fill-opacity:1" + id="g18998"> + <path + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:21.03624916;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + i:knockout="Off" + d="M 680.83905,76.838803 C 680.83905,76.838803 679.45446,81.3797 679.41945,81.449838 C 679.22156,81.846006 678.85659,82.994032 678.98224,83.364797 C 679.13021,83.801538 679.68481,84.436589 679.94365,84.754121 C 680.2025,85.071759 680.31342,85.230473 680.23945,85.349692 C 680.16554,85.468783 679.20413,85.468783 679.20413,85.468783 C 679.20413,85.468783 678.46465,84.396936 678.31664,83.999943 C 678.16882,83.603079 678.09481,82.769436 678.24277,82.491624 C 678.39065,82.213703 680.83905,76.838803 680.83905,76.838803 z " + id="path19000" /> + <polygon + transform="matrix(4.348075e-2,0,0,4.188821e-2,659.2285,53.92804)" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:17.46648407;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + i:knockout="Off" + a:adobe-blending-mode="lighten" + points="272.565,567.14 214.39,617.501 287.326,574.086 272.565,567.14 " + id="polygon19002" /> + <polygon + transform="matrix(4.348075e-2,0,0,4.188821e-2,659.2285,53.92804)" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:17.46648407;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + i:knockout="Off" + a:adobe-blending-mode="lighten" + points="329.873,549.776 230.887,678.282 344.633,558.457 329.873,549.776 " + id="polygon19004" /> + <polygon + transform="matrix(4.348075e-2,0,0,4.188821e-2,659.2285,53.92804)" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:17.46648407;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + i:knockout="Off" + a:adobe-blending-mode="lighten" + points="337.688,594.058 262.146,713.882 355.053,601.872 337.688,594.058 " + id="polygon19006" /> + <polygon + transform="matrix(4.348075e-2,0,0,4.188821e-2,659.2285,53.92804)" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:17.46648407;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + i:knockout="Off" + a:adobe-blending-mode="lighten" + points="381.97,588.847 318.584,737.327 402.809,593.19 381.97,588.847 " + id="polygon19008" /> + <polygon + transform="matrix(4.348075e-2,0,0,4.188821e-2,659.2285,53.92804)" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:17.46648407;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + i:knockout="Off" + a:adobe-blending-mode="lighten" + points="407.151,627.921 401.94,763.375 424.515,629.658 407.151,627.921 " + id="polygon19010" /> + </g> + <path + sodipodi:nodetypes="ccc" + id="path19012" + d="M 665.61116,79.917164 C 666.44697,82.39457 673.07693,87.947061 680.79385,85.431014 C 680.71584,83.40215 681.5919,77.986014 681.41239,76.464632" + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#002ddb;stroke-width:0.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + </g> + <g + transform="matrix(1.5,0,0,1.5,-701.9119,-729.33478)" + id="g19024" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/48x48/actions/amarok_settings_view.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <path + sodipodi:type="arc" + style="fill:url(#linearGradient19056);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;display:inline" + id="path19026" + sodipodi:cx="418.94757" + sodipodi:cy="444.83228" + sodipodi:rx="9.8239412" + sodipodi:ry="9.8239412" + d="M 428.77151 444.83228 A 9.8239412 9.8239412 0 1 1 409.12363,444.83228 A 9.8239412 9.8239412 0 1 1 428.77151 444.83228 z" + transform="matrix(1.628674,0,0,1.628674,589.2965,25.3152)" + inkscape:export-filename="/home/lando/Projects/Amarok/Setting-dialog/amarok_settings_view.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:type="arc" + style="fill:url(#radialGradient19058);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;display:inline" + id="path19028" + sodipodi:cx="418.51233" + sodipodi:cy="446.44885" + sodipodi:rx="11.005301" + sodipodi:ry="11.005301" + d="M 429.51763 446.44885 A 11.005301 11.005301 0 1 1 407.50703,446.44885 A 11.005301 11.005301 0 1 1 429.51763 446.44885 z" + transform="matrix(1.351475,0,0,1.37725,706.0166,134.9307)" + inkscape:export-filename="/home/lando/Projects/Amarok/Setting-dialog/amarok_settings_view.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + style="opacity:0.85531915;fill:url(#linearGradient19060);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;display:inline" + d="M 1285.507,750.519 C 1285.507,758.95943 1279.2149,751.49937 1271.4623,751.49937 C 1263.7097,751.49937 1257.4177,758.95943 1257.4177,750.519 C 1257.4177,742.07865 1263.7097,735.22846 1271.4623,735.22846 C 1279.2149,735.22846 1285.507,742.07865 1285.507,750.519 z " + id="path19030" + sodipodi:nodetypes="csssc" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Setting-dialog/amarok_settings_view.png" /> + <g + style="display:inline" + id="g19032" + transform="matrix(1.033489,0,0,1.033488,572.0631,676.0966)" + inkscape:export-filename="/home/lando/Projects/Amarok/Setting-dialog/amarok_settings_view.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <path + transform="matrix(0.116367,-0.126279,0.138815,0.105858,2044.201,-572.7461)" + d="M -7824.3296 -3285.1472 A 25.97311 5.306335 0 1 1 -7876.2758,-3285.1472 A 25.97311 5.306335 0 1 1 -7824.3296 -3285.1472 z" + sodipodi:ry="5.306335" + sodipodi:rx="25.97311" + sodipodi:cy="-3285.1472" + sodipodi:cx="-7850.3027" + id="path19034" + style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:21.03624916;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0.68131869" + sodipodi:type="arc" /> + <path + style="fill:url(#linearGradient19062);fill-opacity:1" + d="M 684.58932,62.015066 C 683.95163,62.03676 682.73778,63.425764 682.28042,63.646529 C 681.81946,63.869109 678.16982,66.391273 677.82409,66.836327 C 677.47838,67.281337 677.01748,67.46692 676.51816,67.429844 C 676.01883,67.392834 674.44026,67.377225 673.90249,67.673903 C 673.36463,67.970636 670.90988,70.397011 670.29527,70.693715 C 669.68068,70.990341 668.87865,71.141876 668.49449,71.178979 C 668.11043,71.216028 666.83809,72.584967 666.49238,72.844636 C 666.14667,73.10429 663.41458,74.2451 663.03046,74.467545 C 662.64631,74.690071 665.83291,77.807344 666.29388,77.881456 C 666.75485,77.955728 665.97647,79.643959 665.08957,79.643959 C 664.99189,79.643959 665.88202,82.441727 670.53651,84.906802 C 674.56037,87.037898 678.82957,86.094974 680.41507,85.578197 C 681.53629,85.212648 680.74985,85.010148 680.78834,84.416789 C 680.82666,83.823378 680.8587,83.043556 680.93555,82.376043 C 681.01244,81.708505 681.28161,76.107653 682.28042,74.624229 C 683.27912,73.140873 686.13308,71.360612 687.13185,71.175177 C 688.13068,70.989794 687.93916,70.619136 687.93916,70.285379 C 687.93916,69.95165 687.78546,69.209626 687.51651,69.098343 C 687.24765,68.987127 686.19822,69.654757 685.85251,69.728899 C 685.5068,69.803078 683.85489,69.914575 683.62435,69.543728 C 683.3939,69.172775 686.14108,63.906545 685.64166,63.276177 C 685.14247,62.645555 684.79657,62.015066 684.60452,62.015066 C 684.59957,62.015066 684.59434,62.014895 684.58932,62.015066 z M 676.42414,68.978694 C 676.73261,68.974532 676.94176,69.006232 676.94176,69.006232 C 677.11748,69.40176 676.29813,70.644697 675.53706,70.814323 C 674.77614,70.983791 673.01974,73.4706 672.96126,72.905408 C 672.90256,72.340212 673.66355,70.476005 674.54169,69.628234 C 675.09392,69.095034 675.90135,68.985752 676.42414,68.978694 z " + id="path19036" + sodipodi:nodetypes="ccccsccsccsccccccsssccsccscccc" /> + <path + style="fill:url(#linearGradient19064);fill-opacity:1" + d="M 666.16392,81.423003 C 665.9031,81.091515 665.42361,80.435598 665.26754,80.004036 C 665.14138,79.655175 665.13889,79.611803 665.24522,79.614525 C 665.37457,79.61782 665.53249,79.582317 665.71495,79.414066 C 666.12829,79.032933 666.46122,78.479561 666.36093,78.193327 C 666.34151,78.137933 666.28178,78.082005 666.19489,78.037937 C 665.68311,77.778228 663.68597,75.546616 663.20752,74.699847 C 663.14869,74.595715 663.09317,74.467102 663.08417,74.414107 L 663.06779,74.317713 L 663.5356,74.095961 C 663.79289,73.973979 664.43521,73.676485 664.96298,73.434854 C 666.54934,72.708572 666.40066,72.802712 667.30233,71.953513 C 668.1459,71.159029 668.45291,70.912676 668.63209,70.886525 C 669.29678,70.789464 669.74333,70.680556 670.16886,70.511811 C 670.62212,70.332045 670.78417,70.203751 672.21307,68.893356 C 673.39223,67.812027 673.7656,67.48122 673.99398,67.315529 C 674.36671,67.045079 674.85595,66.975476 676.21407,66.999677 C 677.02172,67.014072 677.16671,67.009258 677.3026,66.963628 C 677.54873,66.881043 677.75482,66.732936 678.04247,66.432002 C 678.18878,66.278881 678.48874,66.009285 678.70903,65.832881 C 679.56399,65.148239 681.38883,63.831519 682.2969,63.244071 C 682.83405,62.896574 682.83553,62.895428 683.52111,62.285059 C 684.09054,61.778114 684.47641,61.493496 684.70119,61.414567 C 684.91293,61.34024 684.93449,61.341828 685.04373,61.439835 C 685.15035,61.535483 686.03394,62.9158 686.10698,63.100796 C 686.24105,63.440395 686.31503,63.800609 686.17623,64.085306 C 685.91549,64.62008 685.53887,65.389083 685.37514,65.683372 C 684.01271,68.277198 683.57638,69.315608 683.75865,69.53045 C 683.9225,69.723542 684.62451,69.838696 685.37986,69.796375 C 685.63126,69.7823 685.89265,69.757165 685.96072,69.740527 C 686.02879,69.723904 686.33237,69.592247 686.63534,69.447957 C 687.2815,69.140217 687.50858,69.064356 687.61979,69.119151 C 687.73185,69.174334 687.84105,69.402649 687.91551,69.737405 C 687.98654,70.056699 688.02311,70.630231 687.98411,70.812999 C 687.93246,71.054964 687.70898,71.202203 687.22201,71.315153 C 686.50051,71.482471 685.39489,72.093335 684.25696,72.95334 C 683.19009,73.759659 682.46764,74.510542 682.13211,75.161789 C 681.942,75.530794 681.88754,75.797654 681.7263,76.390906 C 681.60627,76.832449 680.94162,82.834448 680.93784,82.974322 C 680.93675,83.015121 681.05021,76.469849 681.04313,76.469662 C 681.03606,76.469473 680.47326,77.712213 680.46324,77.71196 C 680.45319,77.711706 679.26724,79.616271 679.14064,79.557035 C 679.01407,79.497811 680.84098,75.164632 681.29637,74.622279 C 681.72505,74.111718 682.69675,73.228218 683.92824,72.229312 C 684.68954,71.611793 685.08523,71.319382 685.27321,71.235423 C 685.34613,71.202844 685.65566,71.131984 685.96107,71.077938 C 686.71523,70.944468 686.90799,70.86055 686.9133,70.663386 C 686.91912,70.447624 686.66022,70.304493 686.31274,70.331392 C 685.85732,70.366626 685.34615,70.54459 684.72472,70.884297 C 684.05931,71.248032 683.51826,71.34764 682.8754,71.224763 C 682.69316,71.189901 682.45974,71.128317 682.35669,71.087851 C 682.13555,71.001063 682.1061,70.935662 682.16257,70.656928 C 682.28424,70.056285 682.7318,69.095497 684.09589,66.506489 C 684.52724,65.687816 684.91444,64.919869 684.95636,64.799939 C 684.99825,64.680011 685.1686,64.493296 685.20972,64.242845 C 685.27332,63.855494 684.94437,63.305418 684.69042,63.096047 C 684.1717,62.710286 684.01007,62.609168 683.74917,62.719264 C 683.27598,62.918962 682.63747,63.885861 680.89506,66.251944 C 678.10234,70.044237 677.77609,70.437338 677.00798,70.935529 C 675.9344,71.631833 674.25248,73.011563 672.89065,74.31307 C 671.94197,75.219744 671.23101,76.306587 670.94267,76.585575 C 670.75542,76.76678 669.96025,77.745288 669.68107,77.980021 C 668.26676,78.87945 667.27756,79.841733 666.4157,81.272282 L 666.26937,81.5468 L 666.16392,81.423003 z M 668.6295,76.32153 C 669.21552,76.035684 669.9402,75.554275 670.29047,75.218104 C 670.57943,74.940811 670.69555,74.663571 670.53426,74.6361 C 670.43004,74.618356 670.14725,74.769144 669.42758,75.226216 C 668.60241,75.75033 668.36243,75.871421 668.1267,75.882628 C 667.6555,75.905015 666.59584,75.72705 666.06717,75.536718 C 665.70072,75.404769 665.57392,75.331295 665.45729,75.183336 C 665.38321,75.089358 665.37887,75.065462 665.39778,74.856572 C 665.42208,74.588243 665.56219,74.28492 665.80217,73.981211 C 665.92518,73.825514 666.03467,73.73747 666.35462,73.537052 C 667.27497,72.96057 668.28907,72.478015 669.33316,72.119738 C 669.57465,72.036872 669.8005,71.958064 669.83504,71.944615 C 669.88896,71.923614 669.87183,71.906057 669.71409,71.820563 C 669.45042,71.677633 669.10707,71.548405 668.91845,71.521096 C 668.73014,71.493813 668.65946,71.521481 668.21594,71.796028 C 667.88157,72.003024 666.25388,73.091052 665.48621,73.620705 C 665.18146,73.830983 664.72391,74.14191 664.46942,74.311617 C 664.21494,74.481377 663.98901,74.640691 663.96737,74.665692 C 663.90854,74.733682 663.91479,74.906936 663.98115,75.047732 C 664.05408,75.202428 664.40387,75.635913 664.59279,75.805673 L 664.73365,75.93226 L 666.46235,76.190566 C 667.41313,76.332656 668.22595,76.451329 668.26859,76.454332 C 668.31124,76.457306 668.47365,76.397562 668.6295,76.32153 z M 668.25415,75.137027 C 668.72466,75.109689 668.82443,75.064367 669.20342,74.70609 C 669.59232,74.338446 669.98504,73.821189 670.13614,73.477602 L 670.18873,73.358048 L 670.0701,73.373682 C 670.00486,73.382261 669.32753,73.452719 668.5649,73.53022 L 667.17835,73.671095 L 667.05139,73.770438 C 666.8115,73.958127 666.48365,74.29119 666.35948,74.473383 C 666.21238,74.689172 666.19775,74.804299 666.3076,74.881267 C 666.43204,74.968454 666.84529,75.068852 667.23887,75.107529 C 667.76006,75.158734 667.84061,75.161069 668.25415,75.137027 z M 673.23289,72.884003 C 673.36857,72.793999 673.63342,72.530209 674.28656,71.834505 C 674.76864,71.320997 675.22409,70.892729 675.40078,70.786743 C 675.46833,70.746223 675.60467,70.684907 675.70377,70.650443 C 675.92776,70.572528 676.07455,70.48466 676.28578,70.302053 C 676.73011,69.917918 677.18695,69.102408 677.12911,68.796643 C 677.11207,68.706574 677.1094,68.705506 676.85795,68.688776 C 675.95769,68.628965 675.25732,68.824768 674.75202,69.277624 C 674.16986,69.799364 673.40627,71.172265 673.12421,72.204378 C 673.01269,72.612445 672.98317,72.817187 673.02395,72.899506 C 673.06635,72.985066 673.08235,72.983877 673.23289,72.884003 z M 671.57619,71.796562 C 672.34776,71.643906 672.98398,71.514265 672.99004,71.508448 C 672.99608,71.502658 673.07634,71.260586 673.16839,70.970524 C 673.54217,69.792774 673.83364,69.271727 674.26438,69.011271 C 674.90419,68.624388 676.2258,68.209742 676.95832,68.166034 C 677.22424,68.150158 677.26926,68.137257 677.51161,68.007456 C 677.8718,67.814601 678.3997,67.456604 679.12431,66.913865 C 679.45905,66.663135 679.91088,66.333048 680.12838,66.180365 C 680.68623,65.788718 680.96159,65.51887 680.96589,65.359637 C 680.96661,65.332846 680.95475,65.310646 680.93954,65.310246 C 680.92433,65.309872 680.56473,65.552198 680.14043,65.848756 C 679.71612,66.145328 679.09133,66.572036 678.75201,66.797005 C 677.56622,67.583204 677.74205,67.5241 676.57466,67.528756 C 676.06082,67.530783 675.36265,67.538481 675.02318,67.545859 C 674.42556,67.558856 674.40209,67.56187 674.28268,67.640998 C 674.21487,67.685947 673.78028,68.103582 673.31692,68.569102 C 672.3073,69.583402 671.44162,70.400928 671.10522,70.657742 C 670.9698,70.761139 670.75997,70.888754 670.63892,70.941346 C 670.39611,71.046839 670.3172,71.116109 670.21049,71.317382 C 670.13004,71.469129 670.04208,71.806968 670.03645,71.985906 C 670.03309,72.092787 670.03952,72.102793 670.10304,72.089172 C 670.14171,72.080887 670.80463,71.949217 671.57619,71.796562 z " + id="path19038" + sodipodi:nodetypes="csssssscccscsscsssssscsssscsssssssssssssssscssssccssssscscscscccccsscsssssssssssscsscccsccsccccccsssccssssscsssccssssssssssscsscsssssc" /> + <g + id="g19040" + style="fill:#002ddb;fill-opacity:1"> + <path + id="path19042" + d="M 680.83905,76.838803 C 680.83905,76.838803 679.45446,81.3797 679.41945,81.449838 C 679.22156,81.846006 678.85659,82.994032 678.98224,83.364797 C 679.13021,83.801538 679.68481,84.436589 679.94365,84.754121 C 680.2025,85.071759 680.31342,85.230473 680.23945,85.349692 C 680.16554,85.468783 679.20413,85.468783 679.20413,85.468783 C 679.20413,85.468783 678.46465,84.396936 678.31664,83.999943 C 678.16882,83.603079 678.09481,82.769436 678.24277,82.491624 C 678.39065,82.213703 680.83905,76.838803 680.83905,76.838803 z " + i:knockout="Off" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:21.03624916;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <polygon + id="polygon19044" + points="272.565,567.14 214.39,617.501 287.326,574.086 272.565,567.14 " + a:adobe-blending-mode="lighten" + i:knockout="Off" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:17.46648407;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + transform="matrix(4.348075e-2,0,0,4.188821e-2,659.2285,53.92804)" /> + <polygon + id="polygon19046" + points="329.873,549.776 230.887,678.282 344.633,558.457 329.873,549.776 " + a:adobe-blending-mode="lighten" + i:knockout="Off" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:17.46648407;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + transform="matrix(4.348075e-2,0,0,4.188821e-2,659.2285,53.92804)" /> + <polygon + id="polygon19048" + points="337.688,594.058 262.146,713.882 355.053,601.872 337.688,594.058 " + a:adobe-blending-mode="lighten" + i:knockout="Off" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:17.46648407;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + transform="matrix(4.348075e-2,0,0,4.188821e-2,659.2285,53.92804)" /> + <polygon + id="polygon19050" + points="381.97,588.847 318.584,737.327 402.809,593.19 381.97,588.847 " + a:adobe-blending-mode="lighten" + i:knockout="Off" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:17.46648407;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + transform="matrix(4.348075e-2,0,0,4.188821e-2,659.2285,53.92804)" /> + <polygon + id="polygon19052" + points="407.151,627.921 401.94,763.375 424.515,629.658 407.151,627.921 " + a:adobe-blending-mode="lighten" + i:knockout="Off" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:17.46648407;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + transform="matrix(4.348075e-2,0,0,4.188821e-2,659.2285,53.92804)" /> + </g> + <path + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#002ddb;stroke-width:0.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 665.61116,79.917164 C 666.44697,82.39457 673.07693,87.947061 680.79385,85.431014 C 680.71584,83.40215 681.5919,77.986014 681.41239,76.464632" + id="path19054" + sodipodi:nodetypes="ccc" /> + </g> + </g> + <g + id="g19066" + transform="matrix(2,0,0,2,-1273.488,-1112.2357)" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/64x64/actions/amarok_settings_view.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Setting-dialog/amarok_settings_view.png" + transform="matrix(1.628674,0,0,1.628674,589.2965,25.3152)" + d="M 428.77151 444.83228 A 9.8239412 9.8239412 0 1 1 409.12363,444.83228 A 9.8239412 9.8239412 0 1 1 428.77151 444.83228 z" + sodipodi:ry="9.8239412" + sodipodi:rx="9.8239412" + sodipodi:cy="444.83228" + sodipodi:cx="418.94757" + id="path19068" + style="fill:url(#linearGradient19098);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;display:inline" + sodipodi:type="arc" /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Setting-dialog/amarok_settings_view.png" + transform="matrix(1.351475,0,0,1.37725,706.0166,134.9307)" + d="M 429.51763 446.44885 A 11.005301 11.005301 0 1 1 407.50703,446.44885 A 11.005301 11.005301 0 1 1 429.51763 446.44885 z" + sodipodi:ry="11.005301" + sodipodi:rx="11.005301" + sodipodi:cy="446.44885" + sodipodi:cx="418.51233" + id="path19070" + style="fill:url(#radialGradient19100);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;display:inline" + sodipodi:type="arc" /> + <path + inkscape:export-filename="/home/lando/Projects/Amarok/Setting-dialog/amarok_settings_view.png" + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + sodipodi:nodetypes="csssc" + id="path19072" + d="M 1285.507,750.519 C 1285.507,758.95943 1279.2149,751.49937 1271.4623,751.49937 C 1263.7097,751.49937 1257.4177,758.95943 1257.4177,750.519 C 1257.4177,742.07865 1263.7097,735.22846 1271.4623,735.22846 C 1279.2149,735.22846 1285.507,742.07865 1285.507,750.519 z " + style="opacity:0.85531915;fill:url(#linearGradient19102);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;display:inline" /> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Setting-dialog/amarok_settings_view.png" + transform="matrix(1.033489,0,0,1.033488,572.0631,676.0966)" + id="g19074" + style="display:inline"> + <path + sodipodi:type="arc" + style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:21.03624916;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0.68131869" + id="path19076" + sodipodi:cx="-7850.3027" + sodipodi:cy="-3285.1472" + sodipodi:rx="25.97311" + sodipodi:ry="5.306335" + d="M -7824.3296 -3285.1472 A 25.97311 5.306335 0 1 1 -7876.2758,-3285.1472 A 25.97311 5.306335 0 1 1 -7824.3296 -3285.1472 z" + transform="matrix(0.116367,-0.126279,0.138815,0.105858,2044.201,-572.7461)" /> + <path + sodipodi:nodetypes="ccccsccsccsccccccsssccsccscccc" + id="path19078" + d="M 684.58932,62.015066 C 683.95163,62.03676 682.73778,63.425764 682.28042,63.646529 C 681.81946,63.869109 678.16982,66.391273 677.82409,66.836327 C 677.47838,67.281337 677.01748,67.46692 676.51816,67.429844 C 676.01883,67.392834 674.44026,67.377225 673.90249,67.673903 C 673.36463,67.970636 670.90988,70.397011 670.29527,70.693715 C 669.68068,70.990341 668.87865,71.141876 668.49449,71.178979 C 668.11043,71.216028 666.83809,72.584967 666.49238,72.844636 C 666.14667,73.10429 663.41458,74.2451 663.03046,74.467545 C 662.64631,74.690071 665.83291,77.807344 666.29388,77.881456 C 666.75485,77.955728 665.97647,79.643959 665.08957,79.643959 C 664.99189,79.643959 665.88202,82.441727 670.53651,84.906802 C 674.56037,87.037898 678.82957,86.094974 680.41507,85.578197 C 681.53629,85.212648 680.74985,85.010148 680.78834,84.416789 C 680.82666,83.823378 680.8587,83.043556 680.93555,82.376043 C 681.01244,81.708505 681.28161,76.107653 682.28042,74.624229 C 683.27912,73.140873 686.13308,71.360612 687.13185,71.175177 C 688.13068,70.989794 687.93916,70.619136 687.93916,70.285379 C 687.93916,69.95165 687.78546,69.209626 687.51651,69.098343 C 687.24765,68.987127 686.19822,69.654757 685.85251,69.728899 C 685.5068,69.803078 683.85489,69.914575 683.62435,69.543728 C 683.3939,69.172775 686.14108,63.906545 685.64166,63.276177 C 685.14247,62.645555 684.79657,62.015066 684.60452,62.015066 C 684.59957,62.015066 684.59434,62.014895 684.58932,62.015066 z M 676.42414,68.978694 C 676.73261,68.974532 676.94176,69.006232 676.94176,69.006232 C 677.11748,69.40176 676.29813,70.644697 675.53706,70.814323 C 674.77614,70.983791 673.01974,73.4706 672.96126,72.905408 C 672.90256,72.340212 673.66355,70.476005 674.54169,69.628234 C 675.09392,69.095034 675.90135,68.985752 676.42414,68.978694 z " + style="fill:url(#linearGradient19104);fill-opacity:1" /> + <path + sodipodi:nodetypes="csssssscccscsscsssssscsssscsssssssssssssssscssssccssssscscscscccccsscsssssssssssscsscccsccsccccccsssccssssscsssccssssssssssscsscsssssc" + id="path19080" + d="M 666.16392,81.423003 C 665.9031,81.091515 665.42361,80.435598 665.26754,80.004036 C 665.14138,79.655175 665.13889,79.611803 665.24522,79.614525 C 665.37457,79.61782 665.53249,79.582317 665.71495,79.414066 C 666.12829,79.032933 666.46122,78.479561 666.36093,78.193327 C 666.34151,78.137933 666.28178,78.082005 666.19489,78.037937 C 665.68311,77.778228 663.68597,75.546616 663.20752,74.699847 C 663.14869,74.595715 663.09317,74.467102 663.08417,74.414107 L 663.06779,74.317713 L 663.5356,74.095961 C 663.79289,73.973979 664.43521,73.676485 664.96298,73.434854 C 666.54934,72.708572 666.40066,72.802712 667.30233,71.953513 C 668.1459,71.159029 668.45291,70.912676 668.63209,70.886525 C 669.29678,70.789464 669.74333,70.680556 670.16886,70.511811 C 670.62212,70.332045 670.78417,70.203751 672.21307,68.893356 C 673.39223,67.812027 673.7656,67.48122 673.99398,67.315529 C 674.36671,67.045079 674.85595,66.975476 676.21407,66.999677 C 677.02172,67.014072 677.16671,67.009258 677.3026,66.963628 C 677.54873,66.881043 677.75482,66.732936 678.04247,66.432002 C 678.18878,66.278881 678.48874,66.009285 678.70903,65.832881 C 679.56399,65.148239 681.38883,63.831519 682.2969,63.244071 C 682.83405,62.896574 682.83553,62.895428 683.52111,62.285059 C 684.09054,61.778114 684.47641,61.493496 684.70119,61.414567 C 684.91293,61.34024 684.93449,61.341828 685.04373,61.439835 C 685.15035,61.535483 686.03394,62.9158 686.10698,63.100796 C 686.24105,63.440395 686.31503,63.800609 686.17623,64.085306 C 685.91549,64.62008 685.53887,65.389083 685.37514,65.683372 C 684.01271,68.277198 683.57638,69.315608 683.75865,69.53045 C 683.9225,69.723542 684.62451,69.838696 685.37986,69.796375 C 685.63126,69.7823 685.89265,69.757165 685.96072,69.740527 C 686.02879,69.723904 686.33237,69.592247 686.63534,69.447957 C 687.2815,69.140217 687.50858,69.064356 687.61979,69.119151 C 687.73185,69.174334 687.84105,69.402649 687.91551,69.737405 C 687.98654,70.056699 688.02311,70.630231 687.98411,70.812999 C 687.93246,71.054964 687.70898,71.202203 687.22201,71.315153 C 686.50051,71.482471 685.39489,72.093335 684.25696,72.95334 C 683.19009,73.759659 682.46764,74.510542 682.13211,75.161789 C 681.942,75.530794 681.88754,75.797654 681.7263,76.390906 C 681.60627,76.832449 680.94162,82.834448 680.93784,82.974322 C 680.93675,83.015121 681.05021,76.469849 681.04313,76.469662 C 681.03606,76.469473 680.47326,77.712213 680.46324,77.71196 C 680.45319,77.711706 679.26724,79.616271 679.14064,79.557035 C 679.01407,79.497811 680.84098,75.164632 681.29637,74.622279 C 681.72505,74.111718 682.69675,73.228218 683.92824,72.229312 C 684.68954,71.611793 685.08523,71.319382 685.27321,71.235423 C 685.34613,71.202844 685.65566,71.131984 685.96107,71.077938 C 686.71523,70.944468 686.90799,70.86055 686.9133,70.663386 C 686.91912,70.447624 686.66022,70.304493 686.31274,70.331392 C 685.85732,70.366626 685.34615,70.54459 684.72472,70.884297 C 684.05931,71.248032 683.51826,71.34764 682.8754,71.224763 C 682.69316,71.189901 682.45974,71.128317 682.35669,71.087851 C 682.13555,71.001063 682.1061,70.935662 682.16257,70.656928 C 682.28424,70.056285 682.7318,69.095497 684.09589,66.506489 C 684.52724,65.687816 684.91444,64.919869 684.95636,64.799939 C 684.99825,64.680011 685.1686,64.493296 685.20972,64.242845 C 685.27332,63.855494 684.94437,63.305418 684.69042,63.096047 C 684.1717,62.710286 684.01007,62.609168 683.74917,62.719264 C 683.27598,62.918962 682.63747,63.885861 680.89506,66.251944 C 678.10234,70.044237 677.77609,70.437338 677.00798,70.935529 C 675.9344,71.631833 674.25248,73.011563 672.89065,74.31307 C 671.94197,75.219744 671.23101,76.306587 670.94267,76.585575 C 670.75542,76.76678 669.96025,77.745288 669.68107,77.980021 C 668.26676,78.87945 667.27756,79.841733 666.4157,81.272282 L 666.26937,81.5468 L 666.16392,81.423003 z M 668.6295,76.32153 C 669.21552,76.035684 669.9402,75.554275 670.29047,75.218104 C 670.57943,74.940811 670.69555,74.663571 670.53426,74.6361 C 670.43004,74.618356 670.14725,74.769144 669.42758,75.226216 C 668.60241,75.75033 668.36243,75.871421 668.1267,75.882628 C 667.6555,75.905015 666.59584,75.72705 666.06717,75.536718 C 665.70072,75.404769 665.57392,75.331295 665.45729,75.183336 C 665.38321,75.089358 665.37887,75.065462 665.39778,74.856572 C 665.42208,74.588243 665.56219,74.28492 665.80217,73.981211 C 665.92518,73.825514 666.03467,73.73747 666.35462,73.537052 C 667.27497,72.96057 668.28907,72.478015 669.33316,72.119738 C 669.57465,72.036872 669.8005,71.958064 669.83504,71.944615 C 669.88896,71.923614 669.87183,71.906057 669.71409,71.820563 C 669.45042,71.677633 669.10707,71.548405 668.91845,71.521096 C 668.73014,71.493813 668.65946,71.521481 668.21594,71.796028 C 667.88157,72.003024 666.25388,73.091052 665.48621,73.620705 C 665.18146,73.830983 664.72391,74.14191 664.46942,74.311617 C 664.21494,74.481377 663.98901,74.640691 663.96737,74.665692 C 663.90854,74.733682 663.91479,74.906936 663.98115,75.047732 C 664.05408,75.202428 664.40387,75.635913 664.59279,75.805673 L 664.73365,75.93226 L 666.46235,76.190566 C 667.41313,76.332656 668.22595,76.451329 668.26859,76.454332 C 668.31124,76.457306 668.47365,76.397562 668.6295,76.32153 z M 668.25415,75.137027 C 668.72466,75.109689 668.82443,75.064367 669.20342,74.70609 C 669.59232,74.338446 669.98504,73.821189 670.13614,73.477602 L 670.18873,73.358048 L 670.0701,73.373682 C 670.00486,73.382261 669.32753,73.452719 668.5649,73.53022 L 667.17835,73.671095 L 667.05139,73.770438 C 666.8115,73.958127 666.48365,74.29119 666.35948,74.473383 C 666.21238,74.689172 666.19775,74.804299 666.3076,74.881267 C 666.43204,74.968454 666.84529,75.068852 667.23887,75.107529 C 667.76006,75.158734 667.84061,75.161069 668.25415,75.137027 z M 673.23289,72.884003 C 673.36857,72.793999 673.63342,72.530209 674.28656,71.834505 C 674.76864,71.320997 675.22409,70.892729 675.40078,70.786743 C 675.46833,70.746223 675.60467,70.684907 675.70377,70.650443 C 675.92776,70.572528 676.07455,70.48466 676.28578,70.302053 C 676.73011,69.917918 677.18695,69.102408 677.12911,68.796643 C 677.11207,68.706574 677.1094,68.705506 676.85795,68.688776 C 675.95769,68.628965 675.25732,68.824768 674.75202,69.277624 C 674.16986,69.799364 673.40627,71.172265 673.12421,72.204378 C 673.01269,72.612445 672.98317,72.817187 673.02395,72.899506 C 673.06635,72.985066 673.08235,72.983877 673.23289,72.884003 z M 671.57619,71.796562 C 672.34776,71.643906 672.98398,71.514265 672.99004,71.508448 C 672.99608,71.502658 673.07634,71.260586 673.16839,70.970524 C 673.54217,69.792774 673.83364,69.271727 674.26438,69.011271 C 674.90419,68.624388 676.2258,68.209742 676.95832,68.166034 C 677.22424,68.150158 677.26926,68.137257 677.51161,68.007456 C 677.8718,67.814601 678.3997,67.456604 679.12431,66.913865 C 679.45905,66.663135 679.91088,66.333048 680.12838,66.180365 C 680.68623,65.788718 680.96159,65.51887 680.96589,65.359637 C 680.96661,65.332846 680.95475,65.310646 680.93954,65.310246 C 680.92433,65.309872 680.56473,65.552198 680.14043,65.848756 C 679.71612,66.145328 679.09133,66.572036 678.75201,66.797005 C 677.56622,67.583204 677.74205,67.5241 676.57466,67.528756 C 676.06082,67.530783 675.36265,67.538481 675.02318,67.545859 C 674.42556,67.558856 674.40209,67.56187 674.28268,67.640998 C 674.21487,67.685947 673.78028,68.103582 673.31692,68.569102 C 672.3073,69.583402 671.44162,70.400928 671.10522,70.657742 C 670.9698,70.761139 670.75997,70.888754 670.63892,70.941346 C 670.39611,71.046839 670.3172,71.116109 670.21049,71.317382 C 670.13004,71.469129 670.04208,71.806968 670.03645,71.985906 C 670.03309,72.092787 670.03952,72.102793 670.10304,72.089172 C 670.14171,72.080887 670.80463,71.949217 671.57619,71.796562 z " + style="fill:url(#linearGradient19106);fill-opacity:1" /> + <g + style="fill:#002ddb;fill-opacity:1" + id="g19082"> + <path + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:21.03624916;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + i:knockout="Off" + d="M 680.83905,76.838803 C 680.83905,76.838803 679.45446,81.3797 679.41945,81.449838 C 679.22156,81.846006 678.85659,82.994032 678.98224,83.364797 C 679.13021,83.801538 679.68481,84.436589 679.94365,84.754121 C 680.2025,85.071759 680.31342,85.230473 680.23945,85.349692 C 680.16554,85.468783 679.20413,85.468783 679.20413,85.468783 C 679.20413,85.468783 678.46465,84.396936 678.31664,83.999943 C 678.16882,83.603079 678.09481,82.769436 678.24277,82.491624 C 678.39065,82.213703 680.83905,76.838803 680.83905,76.838803 z " + id="path19084" /> + <polygon + transform="matrix(4.348075e-2,0,0,4.188821e-2,659.2285,53.92804)" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:17.46648407;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + i:knockout="Off" + a:adobe-blending-mode="lighten" + points="272.565,567.14 214.39,617.501 287.326,574.086 272.565,567.14 " + id="polygon19086" /> + <polygon + transform="matrix(4.348075e-2,0,0,4.188821e-2,659.2285,53.92804)" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:17.46648407;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + i:knockout="Off" + a:adobe-blending-mode="lighten" + points="329.873,549.776 230.887,678.282 344.633,558.457 329.873,549.776 " + id="polygon19088" /> + <polygon + transform="matrix(4.348075e-2,0,0,4.188821e-2,659.2285,53.92804)" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:17.46648407;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + i:knockout="Off" + a:adobe-blending-mode="lighten" + points="337.688,594.058 262.146,713.882 355.053,601.872 337.688,594.058 " + id="polygon19090" /> + <polygon + transform="matrix(4.348075e-2,0,0,4.188821e-2,659.2285,53.92804)" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:17.46648407;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + i:knockout="Off" + a:adobe-blending-mode="lighten" + points="381.97,588.847 318.584,737.327 402.809,593.19 381.97,588.847 " + id="polygon19092" /> + <polygon + transform="matrix(4.348075e-2,0,0,4.188821e-2,659.2285,53.92804)" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:17.46648407;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + i:knockout="Off" + a:adobe-blending-mode="lighten" + points="407.151,627.921 401.94,763.375 424.515,629.658 407.151,627.921 " + id="polygon19094" /> + </g> + <path + sodipodi:nodetypes="ccc" + id="path19096" + d="M 665.61116,79.917164 C 666.44697,82.39457 673.07693,87.947061 680.79385,85.431014 C 680.71584,83.40215 681.5919,77.986014 681.41239,76.464632" + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#002ddb;stroke-width:0.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + </g> + <g + style="display:inline" + id="g16571" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/32x32/actions/amarok_settings_playback.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" + transform="matrix(0.976573,0,0,1,492.6015,302.08362)"> + <g + transform="matrix(1.118235,0.112523,-0.116057,1.153316,675.5163,-25.95307)" + id="g11369"> + <path + id="path10707" + d="M 30.272221,159.50106 L 28.859123,159.71846 C 29.342001,162.73108 31.039486,165.17772 33.685397,166.69699 L 34.402815,165.47955 C 32.090253,164.15169 30.694269,162.13413 30.272221,159.50106 z M 28.772164,161.56636 L 27.598205,161.76202 C 28.002527,164.28454 29.426359,166.31621 31.641839,167.58833 L 32.228819,166.56655 C 30.292452,165.45469 29.125555,163.77109 28.772164,161.56636 z M 27.511245,163.67513 L 26.685127,163.80557 C 26.972164,165.59635 27.982004,167.05482 29.554802,167.95791 L 29.967862,167.21875 C 28.593221,166.42943 27.762121,165.24029 27.511245,163.67513 z " + style="fill:url(#linearGradient19536);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:2.6928978;stroke-miterlimit:4;stroke-dashoffset:0;stroke-opacity:1" /> + </g> + <path + transform="matrix(0.52748,0.530602,-0.530613,0.527468,681.7934,8.403042)" + d="M 170.23595 161.93797 A 17.500893 17.500893 0 1 1 135.23417,161.93797 A 17.500893 17.500893 0 1 1 170.23595 161.93797 z" + sodipodi:ry="17.500893" + sodipodi:rx="17.500893" + sodipodi:cy="161.93797" + sodipodi:cx="152.73506" + id="path10713" + style="fill:url(#linearGradient19538);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient19540);stroke-width:1.03762257;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + sodipodi:type="arc" /> + <rect + ry="1.4534353" + y="158.55241" + x="662.01404" + height="31.999983" + width="32.000656" + id="rect7193" + style="fill:none;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + rx="1.4883012" /> + <g + id="g5322" + transform="matrix(0.963751,0,0,0.963731,24.51233,105.1982)"> + <path + transform="matrix(0.116367,-0.126279,0.138815,0.105858,2044.201,-572.7461)" + d="M -7824.3296 -3285.1472 A 25.97311 5.306335 0 1 1 -7876.2758,-3285.1472 A 25.97311 5.306335 0 1 1 -7824.3296 -3285.1472 z" + sodipodi:ry="5.306335" + sodipodi:rx="25.97311" + sodipodi:cy="-3285.1472" + sodipodi:cx="-7850.3027" + id="path5324" + style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:21.03624916;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0.68131869" + sodipodi:type="arc" /> + <path + style="fill:url(#linearGradient19542);fill-opacity:1" + d="M 684.58932,62.015066 C 683.95163,62.03676 682.73778,63.425764 682.28042,63.646529 C 681.81946,63.869109 678.16982,66.391273 677.82409,66.836327 C 677.47838,67.281337 677.01748,67.46692 676.51816,67.429844 C 676.01883,67.392834 674.44026,67.377225 673.90249,67.673903 C 673.36463,67.970636 670.90988,70.397011 670.29527,70.693715 C 669.68068,70.990341 668.87865,71.141876 668.49449,71.178979 C 668.11043,71.216028 666.83809,72.584967 666.49238,72.844636 C 666.14667,73.10429 663.41458,74.2451 663.03046,74.467545 C 662.64631,74.690071 665.83291,77.807344 666.29388,77.881456 C 666.75485,77.955728 665.97647,79.643959 665.08957,79.643959 C 664.99189,79.643959 665.88202,82.441727 670.53651,84.906802 C 674.56037,87.037898 678.82957,86.094974 680.41507,85.578197 C 681.53629,85.212648 680.74985,85.010148 680.78834,84.416789 C 680.82666,83.823378 680.8587,83.043556 680.93555,82.376043 C 681.01244,81.708505 681.28161,76.107653 682.28042,74.624229 C 683.27912,73.140873 686.13308,71.360612 687.13185,71.175177 C 688.13068,70.989794 687.93916,70.619136 687.93916,70.285379 C 687.93916,69.95165 687.78546,69.209626 687.51651,69.098343 C 687.24765,68.987127 686.19822,69.654757 685.85251,69.728899 C 685.5068,69.803078 683.85489,69.914575 683.62435,69.543728 C 683.3939,69.172775 686.14108,63.906545 685.64166,63.276177 C 685.14247,62.645555 684.79657,62.015066 684.60452,62.015066 C 684.59957,62.015066 684.59434,62.014895 684.58932,62.015066 z M 676.42414,68.978694 C 676.73261,68.974532 676.94176,69.006232 676.94176,69.006232 C 677.11748,69.40176 676.29813,70.644697 675.53706,70.814323 C 674.77614,70.983791 673.01974,73.4706 672.96126,72.905408 C 672.90256,72.340212 673.66355,70.476005 674.54169,69.628234 C 675.09392,69.095034 675.90135,68.985752 676.42414,68.978694 z " + id="path5326" + sodipodi:nodetypes="ccccsccsccsccccccsssccsccscccc" /> + <path + style="fill:url(#linearGradient19544);fill-opacity:1" + d="M 666.16392,81.423003 C 665.9031,81.091515 665.42361,80.435598 665.26754,80.004036 C 665.14138,79.655175 665.13889,79.611803 665.24522,79.614525 C 665.37457,79.61782 665.53249,79.582317 665.71495,79.414066 C 666.12829,79.032933 666.46122,78.479561 666.36093,78.193327 C 666.34151,78.137933 666.28178,78.082005 666.19489,78.037937 C 665.68311,77.778228 663.68597,75.546616 663.20752,74.699847 C 663.14869,74.595715 663.09317,74.467102 663.08417,74.414107 L 663.06779,74.317713 L 663.5356,74.095961 C 663.79289,73.973979 664.43521,73.676485 664.96298,73.434854 C 666.54934,72.708572 666.40066,72.802712 667.30233,71.953513 C 668.1459,71.159029 668.45291,70.912676 668.63209,70.886525 C 669.29678,70.789464 669.74333,70.680556 670.16886,70.511811 C 670.62212,70.332045 670.78417,70.203751 672.21307,68.893356 C 673.39223,67.812027 673.7656,67.48122 673.99398,67.315529 C 674.36671,67.045079 674.85595,66.975476 676.21407,66.999677 C 677.02172,67.014072 677.16671,67.009258 677.3026,66.963628 C 677.54873,66.881043 677.75482,66.732936 678.04247,66.432002 C 678.18878,66.278881 678.48874,66.009285 678.70903,65.832881 C 679.56399,65.148239 681.38883,63.831519 682.2969,63.244071 C 682.83405,62.896574 682.83553,62.895428 683.52111,62.285059 C 684.09054,61.778114 684.47641,61.493496 684.70119,61.414567 C 684.91293,61.34024 684.93449,61.341828 685.04373,61.439835 C 685.15035,61.535483 686.03394,62.9158 686.10698,63.100796 C 686.24105,63.440395 686.31503,63.800609 686.17623,64.085306 C 685.91549,64.62008 685.53887,65.389083 685.37514,65.683372 C 684.01271,68.277198 683.57638,69.315608 683.75865,69.53045 C 683.9225,69.723542 684.62451,69.838696 685.37986,69.796375 C 685.63126,69.7823 685.89265,69.757165 685.96072,69.740527 C 686.02879,69.723904 686.33237,69.592247 686.63534,69.447957 C 687.2815,69.140217 687.50858,69.064356 687.61979,69.119151 C 687.73185,69.174334 687.84105,69.402649 687.91551,69.737405 C 687.98654,70.056699 688.02311,70.630231 687.98411,70.812999 C 687.93246,71.054964 687.70898,71.202203 687.22201,71.315153 C 686.50051,71.482471 685.39489,72.093335 684.25696,72.95334 C 683.19009,73.759659 682.46764,74.510542 682.13211,75.161789 C 681.942,75.530794 681.88754,75.797654 681.7263,76.390906 C 681.60627,76.832449 680.94162,82.834448 680.93784,82.974322 C 680.93675,83.015121 681.05021,76.469849 681.04313,76.469662 C 681.03606,76.469473 680.47326,77.712213 680.46324,77.71196 C 680.45319,77.711706 679.26724,79.616271 679.14064,79.557035 C 679.01407,79.497811 680.84098,75.164632 681.29637,74.622279 C 681.72505,74.111718 682.69675,73.228218 683.92824,72.229312 C 684.68954,71.611793 685.08523,71.319382 685.27321,71.235423 C 685.34613,71.202844 685.65566,71.131984 685.96107,71.077938 C 686.71523,70.944468 686.90799,70.86055 686.9133,70.663386 C 686.91912,70.447624 686.66022,70.304493 686.31274,70.331392 C 685.85732,70.366626 685.34615,70.54459 684.72472,70.884297 C 684.05931,71.248032 683.51826,71.34764 682.8754,71.224763 C 682.69316,71.189901 682.45974,71.128317 682.35669,71.087851 C 682.13555,71.001063 682.1061,70.935662 682.16257,70.656928 C 682.28424,70.056285 682.7318,69.095497 684.09589,66.506489 C 684.52724,65.687816 684.91444,64.919869 684.95636,64.799939 C 684.99825,64.680011 685.1686,64.493296 685.20972,64.242845 C 685.27332,63.855494 684.94437,63.305418 684.69042,63.096047 C 684.1717,62.710286 684.01007,62.609168 683.74917,62.719264 C 683.27598,62.918962 682.63747,63.885861 680.89506,66.251944 C 678.10234,70.044237 677.77609,70.437338 677.00798,70.935529 C 675.9344,71.631833 674.25248,73.011563 672.89065,74.31307 C 671.94197,75.219744 671.23101,76.306587 670.94267,76.585575 C 670.75542,76.76678 669.96025,77.745288 669.68107,77.980021 C 668.26676,78.87945 667.27756,79.841733 666.4157,81.272282 L 666.26937,81.5468 L 666.16392,81.423003 z M 668.6295,76.32153 C 669.21552,76.035684 669.9402,75.554275 670.29047,75.218104 C 670.57943,74.940811 670.69555,74.663571 670.53426,74.6361 C 670.43004,74.618356 670.14725,74.769144 669.42758,75.226216 C 668.60241,75.75033 668.36243,75.871421 668.1267,75.882628 C 667.6555,75.905015 666.59584,75.72705 666.06717,75.536718 C 665.70072,75.404769 665.57392,75.331295 665.45729,75.183336 C 665.38321,75.089358 665.37887,75.065462 665.39778,74.856572 C 665.42208,74.588243 665.56219,74.28492 665.80217,73.981211 C 665.92518,73.825514 666.03467,73.73747 666.35462,73.537052 C 667.27497,72.96057 668.28907,72.478015 669.33316,72.119738 C 669.57465,72.036872 669.8005,71.958064 669.83504,71.944615 C 669.88896,71.923614 669.87183,71.906057 669.71409,71.820563 C 669.45042,71.677633 669.10707,71.548405 668.91845,71.521096 C 668.73014,71.493813 668.65946,71.521481 668.21594,71.796028 C 667.88157,72.003024 666.25388,73.091052 665.48621,73.620705 C 665.18146,73.830983 664.72391,74.14191 664.46942,74.311617 C 664.21494,74.481377 663.98901,74.640691 663.96737,74.665692 C 663.90854,74.733682 663.91479,74.906936 663.98115,75.047732 C 664.05408,75.202428 664.40387,75.635913 664.59279,75.805673 L 664.73365,75.93226 L 666.46235,76.190566 C 667.41313,76.332656 668.22595,76.451329 668.26859,76.454332 C 668.31124,76.457306 668.47365,76.397562 668.6295,76.32153 z M 668.25415,75.137027 C 668.72466,75.109689 668.82443,75.064367 669.20342,74.70609 C 669.59232,74.338446 669.98504,73.821189 670.13614,73.477602 L 670.18873,73.358048 L 670.0701,73.373682 C 670.00486,73.382261 669.32753,73.452719 668.5649,73.53022 L 667.17835,73.671095 L 667.05139,73.770438 C 666.8115,73.958127 666.48365,74.29119 666.35948,74.473383 C 666.21238,74.689172 666.19775,74.804299 666.3076,74.881267 C 666.43204,74.968454 666.84529,75.068852 667.23887,75.107529 C 667.76006,75.158734 667.84061,75.161069 668.25415,75.137027 z M 673.23289,72.884003 C 673.36857,72.793999 673.63342,72.530209 674.28656,71.834505 C 674.76864,71.320997 675.22409,70.892729 675.40078,70.786743 C 675.46833,70.746223 675.60467,70.684907 675.70377,70.650443 C 675.92776,70.572528 676.07455,70.48466 676.28578,70.302053 C 676.73011,69.917918 677.18695,69.102408 677.12911,68.796643 C 677.11207,68.706574 677.1094,68.705506 676.85795,68.688776 C 675.95769,68.628965 675.25732,68.824768 674.75202,69.277624 C 674.16986,69.799364 673.40627,71.172265 673.12421,72.204378 C 673.01269,72.612445 672.98317,72.817187 673.02395,72.899506 C 673.06635,72.985066 673.08235,72.983877 673.23289,72.884003 z M 671.57619,71.796562 C 672.34776,71.643906 672.98398,71.514265 672.99004,71.508448 C 672.99608,71.502658 673.07634,71.260586 673.16839,70.970524 C 673.54217,69.792774 673.83364,69.271727 674.26438,69.011271 C 674.90419,68.624388 676.2258,68.209742 676.95832,68.166034 C 677.22424,68.150158 677.26926,68.137257 677.51161,68.007456 C 677.8718,67.814601 678.3997,67.456604 679.12431,66.913865 C 679.45905,66.663135 679.91088,66.333048 680.12838,66.180365 C 680.68623,65.788718 680.96159,65.51887 680.96589,65.359637 C 680.96661,65.332846 680.95475,65.310646 680.93954,65.310246 C 680.92433,65.309872 680.56473,65.552198 680.14043,65.848756 C 679.71612,66.145328 679.09133,66.572036 678.75201,66.797005 C 677.56622,67.583204 677.74205,67.5241 676.57466,67.528756 C 676.06082,67.530783 675.36265,67.538481 675.02318,67.545859 C 674.42556,67.558856 674.40209,67.56187 674.28268,67.640998 C 674.21487,67.685947 673.78028,68.103582 673.31692,68.569102 C 672.3073,69.583402 671.44162,70.400928 671.10522,70.657742 C 670.9698,70.761139 670.75997,70.888754 670.63892,70.941346 C 670.39611,71.046839 670.3172,71.116109 670.21049,71.317382 C 670.13004,71.469129 670.04208,71.806968 670.03645,71.985906 C 670.03309,72.092787 670.03952,72.102793 670.10304,72.089172 C 670.14171,72.080887 670.80463,71.949217 671.57619,71.796562 z " + id="path5328" + sodipodi:nodetypes="csssssscccscsscsssssscsssscsssssssssssssssscssssccssssscscscscccccsscsssssssssssscsscccsccsccccccsssccssssscsssccssssssssssscsscsssssc" /> + <g + id="g5330" + style="fill:#002ddb;fill-opacity:1"> + <path + id="path5332" + d="M 680.83905,76.838803 C 680.83905,76.838803 679.45446,81.3797 679.41945,81.449838 C 679.22156,81.846006 678.85659,82.994032 678.98224,83.364797 C 679.13021,83.801538 679.68481,84.436589 679.94365,84.754121 C 680.2025,85.071759 680.31342,85.230473 680.23945,85.349692 C 680.16554,85.468783 679.20413,85.468783 679.20413,85.468783 C 679.20413,85.468783 678.46465,84.396936 678.31664,83.999943 C 678.16882,83.603079 678.09481,82.769436 678.24277,82.491624 C 678.39065,82.213703 680.83905,76.838803 680.83905,76.838803 z " + i:knockout="Off" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:21.03624916;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <polygon + id="polygon5334" + points="272.565,567.14 214.39,617.501 287.326,574.086 272.565,567.14 " + a:adobe-blending-mode="lighten" + i:knockout="Off" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:17.46648407;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + transform="matrix(4.348075e-2,0,0,4.188821e-2,659.2285,53.92804)" /> + <polygon + id="polygon5336" + points="329.873,549.776 230.887,678.282 344.633,558.457 329.873,549.776 " + a:adobe-blending-mode="lighten" + i:knockout="Off" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:17.46648407;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + transform="matrix(4.348075e-2,0,0,4.188821e-2,659.2285,53.92804)" /> + <polygon + id="polygon5338" + points="337.688,594.058 262.146,713.882 355.053,601.872 337.688,594.058 " + a:adobe-blending-mode="lighten" + i:knockout="Off" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:17.46648407;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + transform="matrix(4.348075e-2,0,0,4.188821e-2,659.2285,53.92804)" /> + <polygon + id="polygon5340" + points="381.97,588.847 318.584,737.327 402.809,593.19 381.97,588.847 " + a:adobe-blending-mode="lighten" + i:knockout="Off" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:17.46648407;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + transform="matrix(4.348075e-2,0,0,4.188821e-2,659.2285,53.92804)" /> + <polygon + id="polygon5342" + points="407.151,627.921 401.94,763.375 424.515,629.658 407.151,627.921 " + a:adobe-blending-mode="lighten" + i:knockout="Off" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:17.46648407;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + transform="matrix(4.348075e-2,0,0,4.188821e-2,659.2285,53.92804)" /> + </g> + <path + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#002ddb;stroke-width:0.51881158;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 665.61116,79.917164 C 666.44697,82.39457 673.07693,87.947061 680.79385,85.431014 C 680.71584,83.40215 681.5919,77.986014 681.41239,76.464632" + id="path5344" + sodipodi:nodetypes="ccc" /> + </g> + </g> + <g + transform="matrix(0.671394,0,0,0.6875,656.9223,361.63122)" + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/22x22/actions/amarok_settings_playback.png" + id="g19108" + style="display:inline"> + <g + id="g19110" + transform="matrix(1.118235,0.112523,-0.116057,1.153316,675.5163,-25.95307)"> + <path + style="fill:url(#linearGradient19526);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:2.6928978;stroke-miterlimit:4;stroke-dashoffset:0;stroke-opacity:1" + d="M 30.272221,159.50106 L 28.859123,159.71846 C 29.342001,162.73108 31.039486,165.17772 33.685397,166.69699 L 34.402815,165.47955 C 32.090253,164.15169 30.694269,162.13413 30.272221,159.50106 z M 28.772164,161.56636 L 27.598205,161.76202 C 28.002527,164.28454 29.426359,166.31621 31.641839,167.58833 L 32.228819,166.56655 C 30.292452,165.45469 29.125555,163.77109 28.772164,161.56636 z M 27.511245,163.67513 L 26.685127,163.80557 C 26.972164,165.59635 27.982004,167.05482 29.554802,167.95791 L 29.967862,167.21875 C 28.593221,166.42943 27.762121,165.24029 27.511245,163.67513 z " + id="path19112" /> + </g> + <path + sodipodi:type="arc" + style="fill:url(#linearGradient19528);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient19530);stroke-width:1.03762257;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="path19114" + sodipodi:cx="152.73506" + sodipodi:cy="161.93797" + sodipodi:rx="17.500893" + sodipodi:ry="17.500893" + d="M 170.23595 161.93797 A 17.500893 17.500893 0 1 1 135.23417,161.93797 A 17.500893 17.500893 0 1 1 170.23595 161.93797 z" + transform="matrix(0.52748,0.530602,-0.530613,0.527468,681.7934,8.403042)" /> + <rect + rx="2.1648004" + style="fill:none;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + id="rect19116" + width="32.000656" + height="31.999983" + x="662.01404" + y="158.55241" + ry="2.1140866" /> + <g + transform="matrix(0.963751,0,0,0.963731,24.51233,105.1982)" + id="g19118"> + <path + sodipodi:type="arc" + style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:21.03624916;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0.68131869" + id="path19120" + sodipodi:cx="-7850.3027" + sodipodi:cy="-3285.1472" + sodipodi:rx="25.97311" + sodipodi:ry="5.306335" + d="M -7824.3296 -3285.1472 A 25.97311 5.306335 0 1 1 -7876.2758,-3285.1472 A 25.97311 5.306335 0 1 1 -7824.3296 -3285.1472 z" + transform="matrix(0.116367,-0.126279,0.138815,0.105858,2044.201,-572.7461)" /> + <path + sodipodi:nodetypes="ccccsccsccsccccccsssccsccscccc" + id="path19122" + d="M 684.58932,62.015066 C 683.95163,62.03676 682.73778,63.425764 682.28042,63.646529 C 681.81946,63.869109 678.16982,66.391273 677.82409,66.836327 C 677.47838,67.281337 677.01748,67.46692 676.51816,67.429844 C 676.01883,67.392834 674.44026,67.377225 673.90249,67.673903 C 673.36463,67.970636 670.90988,70.397011 670.29527,70.693715 C 669.68068,70.990341 668.87865,71.141876 668.49449,71.178979 C 668.11043,71.216028 666.83809,72.584967 666.49238,72.844636 C 666.14667,73.10429 663.41458,74.2451 663.03046,74.467545 C 662.64631,74.690071 665.83291,77.807344 666.29388,77.881456 C 666.75485,77.955728 665.97647,79.643959 665.08957,79.643959 C 664.99189,79.643959 665.88202,82.441727 670.53651,84.906802 C 674.56037,87.037898 678.82957,86.094974 680.41507,85.578197 C 681.53629,85.212648 680.74985,85.010148 680.78834,84.416789 C 680.82666,83.823378 680.8587,83.043556 680.93555,82.376043 C 681.01244,81.708505 681.28161,76.107653 682.28042,74.624229 C 683.27912,73.140873 686.13308,71.360612 687.13185,71.175177 C 688.13068,70.989794 687.93916,70.619136 687.93916,70.285379 C 687.93916,69.95165 687.78546,69.209626 687.51651,69.098343 C 687.24765,68.987127 686.19822,69.654757 685.85251,69.728899 C 685.5068,69.803078 683.85489,69.914575 683.62435,69.543728 C 683.3939,69.172775 686.14108,63.906545 685.64166,63.276177 C 685.14247,62.645555 684.79657,62.015066 684.60452,62.015066 C 684.59957,62.015066 684.59434,62.014895 684.58932,62.015066 z M 676.42414,68.978694 C 676.73261,68.974532 676.94176,69.006232 676.94176,69.006232 C 677.11748,69.40176 676.29813,70.644697 675.53706,70.814323 C 674.77614,70.983791 673.01974,73.4706 672.96126,72.905408 C 672.90256,72.340212 673.66355,70.476005 674.54169,69.628234 C 675.09392,69.095034 675.90135,68.985752 676.42414,68.978694 z " + style="fill:url(#linearGradient19532);fill-opacity:1" /> + <path + sodipodi:nodetypes="csssssscccscsscsssssscsssscsssssssssssssssscssssccssssscscscscccccsscsssssssssssscsscccsccsccccccsssccssssscsssccssssssssssscsscsssssc" + id="path19124" + d="M 666.16392,81.423003 C 665.9031,81.091515 665.42361,80.435598 665.26754,80.004036 C 665.14138,79.655175 665.13889,79.611803 665.24522,79.614525 C 665.37457,79.61782 665.53249,79.582317 665.71495,79.414066 C 666.12829,79.032933 666.46122,78.479561 666.36093,78.193327 C 666.34151,78.137933 666.28178,78.082005 666.19489,78.037937 C 665.68311,77.778228 663.68597,75.546616 663.20752,74.699847 C 663.14869,74.595715 663.09317,74.467102 663.08417,74.414107 L 663.06779,74.317713 L 663.5356,74.095961 C 663.79289,73.973979 664.43521,73.676485 664.96298,73.434854 C 666.54934,72.708572 666.40066,72.802712 667.30233,71.953513 C 668.1459,71.159029 668.45291,70.912676 668.63209,70.886525 C 669.29678,70.789464 669.74333,70.680556 670.16886,70.511811 C 670.62212,70.332045 670.78417,70.203751 672.21307,68.893356 C 673.39223,67.812027 673.7656,67.48122 673.99398,67.315529 C 674.36671,67.045079 674.85595,66.975476 676.21407,66.999677 C 677.02172,67.014072 677.16671,67.009258 677.3026,66.963628 C 677.54873,66.881043 677.75482,66.732936 678.04247,66.432002 C 678.18878,66.278881 678.48874,66.009285 678.70903,65.832881 C 679.56399,65.148239 681.38883,63.831519 682.2969,63.244071 C 682.83405,62.896574 682.83553,62.895428 683.52111,62.285059 C 684.09054,61.778114 684.47641,61.493496 684.70119,61.414567 C 684.91293,61.34024 684.93449,61.341828 685.04373,61.439835 C 685.15035,61.535483 686.03394,62.9158 686.10698,63.100796 C 686.24105,63.440395 686.31503,63.800609 686.17623,64.085306 C 685.91549,64.62008 685.53887,65.389083 685.37514,65.683372 C 684.01271,68.277198 683.57638,69.315608 683.75865,69.53045 C 683.9225,69.723542 684.62451,69.838696 685.37986,69.796375 C 685.63126,69.7823 685.89265,69.757165 685.96072,69.740527 C 686.02879,69.723904 686.33237,69.592247 686.63534,69.447957 C 687.2815,69.140217 687.50858,69.064356 687.61979,69.119151 C 687.73185,69.174334 687.84105,69.402649 687.91551,69.737405 C 687.98654,70.056699 688.02311,70.630231 687.98411,70.812999 C 687.93246,71.054964 687.70898,71.202203 687.22201,71.315153 C 686.50051,71.482471 685.39489,72.093335 684.25696,72.95334 C 683.19009,73.759659 682.46764,74.510542 682.13211,75.161789 C 681.942,75.530794 681.88754,75.797654 681.7263,76.390906 C 681.60627,76.832449 680.94162,82.834448 680.93784,82.974322 C 680.93675,83.015121 681.05021,76.469849 681.04313,76.469662 C 681.03606,76.469473 680.47326,77.712213 680.46324,77.71196 C 680.45319,77.711706 679.26724,79.616271 679.14064,79.557035 C 679.01407,79.497811 680.84098,75.164632 681.29637,74.622279 C 681.72505,74.111718 682.69675,73.228218 683.92824,72.229312 C 684.68954,71.611793 685.08523,71.319382 685.27321,71.235423 C 685.34613,71.202844 685.65566,71.131984 685.96107,71.077938 C 686.71523,70.944468 686.90799,70.86055 686.9133,70.663386 C 686.91912,70.447624 686.66022,70.304493 686.31274,70.331392 C 685.85732,70.366626 685.34615,70.54459 684.72472,70.884297 C 684.05931,71.248032 683.51826,71.34764 682.8754,71.224763 C 682.69316,71.189901 682.45974,71.128317 682.35669,71.087851 C 682.13555,71.001063 682.1061,70.935662 682.16257,70.656928 C 682.28424,70.056285 682.7318,69.095497 684.09589,66.506489 C 684.52724,65.687816 684.91444,64.919869 684.95636,64.799939 C 684.99825,64.680011 685.1686,64.493296 685.20972,64.242845 C 685.27332,63.855494 684.94437,63.305418 684.69042,63.096047 C 684.1717,62.710286 684.01007,62.609168 683.74917,62.719264 C 683.27598,62.918962 682.63747,63.885861 680.89506,66.251944 C 678.10234,70.044237 677.77609,70.437338 677.00798,70.935529 C 675.9344,71.631833 674.25248,73.011563 672.89065,74.31307 C 671.94197,75.219744 671.23101,76.306587 670.94267,76.585575 C 670.75542,76.76678 669.96025,77.745288 669.68107,77.980021 C 668.26676,78.87945 667.27756,79.841733 666.4157,81.272282 L 666.26937,81.5468 L 666.16392,81.423003 z M 668.6295,76.32153 C 669.21552,76.035684 669.9402,75.554275 670.29047,75.218104 C 670.57943,74.940811 670.69555,74.663571 670.53426,74.6361 C 670.43004,74.618356 670.14725,74.769144 669.42758,75.226216 C 668.60241,75.75033 668.36243,75.871421 668.1267,75.882628 C 667.6555,75.905015 666.59584,75.72705 666.06717,75.536718 C 665.70072,75.404769 665.57392,75.331295 665.45729,75.183336 C 665.38321,75.089358 665.37887,75.065462 665.39778,74.856572 C 665.42208,74.588243 665.56219,74.28492 665.80217,73.981211 C 665.92518,73.825514 666.03467,73.73747 666.35462,73.537052 C 667.27497,72.96057 668.28907,72.478015 669.33316,72.119738 C 669.57465,72.036872 669.8005,71.958064 669.83504,71.944615 C 669.88896,71.923614 669.87183,71.906057 669.71409,71.820563 C 669.45042,71.677633 669.10707,71.548405 668.91845,71.521096 C 668.73014,71.493813 668.65946,71.521481 668.21594,71.796028 C 667.88157,72.003024 666.25388,73.091052 665.48621,73.620705 C 665.18146,73.830983 664.72391,74.14191 664.46942,74.311617 C 664.21494,74.481377 663.98901,74.640691 663.96737,74.665692 C 663.90854,74.733682 663.91479,74.906936 663.98115,75.047732 C 664.05408,75.202428 664.40387,75.635913 664.59279,75.805673 L 664.73365,75.93226 L 666.46235,76.190566 C 667.41313,76.332656 668.22595,76.451329 668.26859,76.454332 C 668.31124,76.457306 668.47365,76.397562 668.6295,76.32153 z M 668.25415,75.137027 C 668.72466,75.109689 668.82443,75.064367 669.20342,74.70609 C 669.59232,74.338446 669.98504,73.821189 670.13614,73.477602 L 670.18873,73.358048 L 670.0701,73.373682 C 670.00486,73.382261 669.32753,73.452719 668.5649,73.53022 L 667.17835,73.671095 L 667.05139,73.770438 C 666.8115,73.958127 666.48365,74.29119 666.35948,74.473383 C 666.21238,74.689172 666.19775,74.804299 666.3076,74.881267 C 666.43204,74.968454 666.84529,75.068852 667.23887,75.107529 C 667.76006,75.158734 667.84061,75.161069 668.25415,75.137027 z M 673.23289,72.884003 C 673.36857,72.793999 673.63342,72.530209 674.28656,71.834505 C 674.76864,71.320997 675.22409,70.892729 675.40078,70.786743 C 675.46833,70.746223 675.60467,70.684907 675.70377,70.650443 C 675.92776,70.572528 676.07455,70.48466 676.28578,70.302053 C 676.73011,69.917918 677.18695,69.102408 677.12911,68.796643 C 677.11207,68.706574 677.1094,68.705506 676.85795,68.688776 C 675.95769,68.628965 675.25732,68.824768 674.75202,69.277624 C 674.16986,69.799364 673.40627,71.172265 673.12421,72.204378 C 673.01269,72.612445 672.98317,72.817187 673.02395,72.899506 C 673.06635,72.985066 673.08235,72.983877 673.23289,72.884003 z M 671.57619,71.796562 C 672.34776,71.643906 672.98398,71.514265 672.99004,71.508448 C 672.99608,71.502658 673.07634,71.260586 673.16839,70.970524 C 673.54217,69.792774 673.83364,69.271727 674.26438,69.011271 C 674.90419,68.624388 676.2258,68.209742 676.95832,68.166034 C 677.22424,68.150158 677.26926,68.137257 677.51161,68.007456 C 677.8718,67.814601 678.3997,67.456604 679.12431,66.913865 C 679.45905,66.663135 679.91088,66.333048 680.12838,66.180365 C 680.68623,65.788718 680.96159,65.51887 680.96589,65.359637 C 680.96661,65.332846 680.95475,65.310646 680.93954,65.310246 C 680.92433,65.309872 680.56473,65.552198 680.14043,65.848756 C 679.71612,66.145328 679.09133,66.572036 678.75201,66.797005 C 677.56622,67.583204 677.74205,67.5241 676.57466,67.528756 C 676.06082,67.530783 675.36265,67.538481 675.02318,67.545859 C 674.42556,67.558856 674.40209,67.56187 674.28268,67.640998 C 674.21487,67.685947 673.78028,68.103582 673.31692,68.569102 C 672.3073,69.583402 671.44162,70.400928 671.10522,70.657742 C 670.9698,70.761139 670.75997,70.888754 670.63892,70.941346 C 670.39611,71.046839 670.3172,71.116109 670.21049,71.317382 C 670.13004,71.469129 670.04208,71.806968 670.03645,71.985906 C 670.03309,72.092787 670.03952,72.102793 670.10304,72.089172 C 670.14171,72.080887 670.80463,71.949217 671.57619,71.796562 z " + style="fill:url(#linearGradient19534);fill-opacity:1" /> + <g + style="fill:#002ddb;fill-opacity:1" + id="g19126"> + <path + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:21.03624916;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + i:knockout="Off" + d="M 680.83905,76.838803 C 680.83905,76.838803 679.45446,81.3797 679.41945,81.449838 C 679.22156,81.846006 678.85659,82.994032 678.98224,83.364797 C 679.13021,83.801538 679.68481,84.436589 679.94365,84.754121 C 680.2025,85.071759 680.31342,85.230473 680.23945,85.349692 C 680.16554,85.468783 679.20413,85.468783 679.20413,85.468783 C 679.20413,85.468783 678.46465,84.396936 678.31664,83.999943 C 678.16882,83.603079 678.09481,82.769436 678.24277,82.491624 C 678.39065,82.213703 680.83905,76.838803 680.83905,76.838803 z " + id="path19128" /> + <polygon + transform="matrix(4.348075e-2,0,0,4.188821e-2,659.2285,53.92804)" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:17.46648407;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + i:knockout="Off" + a:adobe-blending-mode="lighten" + points="272.565,567.14 214.39,617.501 287.326,574.086 272.565,567.14 " + id="polygon19130" /> + <polygon + transform="matrix(4.348075e-2,0,0,4.188821e-2,659.2285,53.92804)" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:17.46648407;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + i:knockout="Off" + a:adobe-blending-mode="lighten" + points="329.873,549.776 230.887,678.282 344.633,558.457 329.873,549.776 " + id="polygon19132" /> + <polygon + transform="matrix(4.348075e-2,0,0,4.188821e-2,659.2285,53.92804)" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:17.46648407;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + i:knockout="Off" + a:adobe-blending-mode="lighten" + points="337.688,594.058 262.146,713.882 355.053,601.872 337.688,594.058 " + id="polygon19134" /> + <polygon + transform="matrix(4.348075e-2,0,0,4.188821e-2,659.2285,53.92804)" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:17.46648407;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + i:knockout="Off" + a:adobe-blending-mode="lighten" + points="381.97,588.847 318.584,737.327 402.809,593.19 381.97,588.847 " + id="polygon19136" /> + <polygon + transform="matrix(4.348075e-2,0,0,4.188821e-2,659.2285,53.92804)" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:17.46648407;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + i:knockout="Off" + a:adobe-blending-mode="lighten" + points="407.151,627.921 401.94,763.375 424.515,629.658 407.151,627.921 " + id="polygon19138" /> + </g> + <path + sodipodi:nodetypes="ccc" + id="path19140" + d="M 665.61116,79.917164 C 666.44697,82.39457 673.07693,87.947061 680.79385,85.431014 C 680.71584,83.40215 681.5919,77.986014 681.41239,76.464632" + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#002ddb;stroke-width:0.51881158;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + </g> + <g + style="display:inline" + id="g19152" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/16x16/actions/amarok_settings_playback.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" + transform="matrix(0.488287,0,0,0.5,749.0309,397.35982)"> + <g + transform="matrix(1.118235,0.112523,-0.116057,1.153316,675.5163,-25.95307)" + id="g19154"> + <path + id="path19156" + d="M 30.272221,159.50106 L 28.859123,159.71846 C 29.342001,162.73108 31.039486,165.17772 33.685397,166.69699 L 34.402815,165.47955 C 32.090253,164.15169 30.694269,162.13413 30.272221,159.50106 z M 28.772164,161.56636 L 27.598205,161.76202 C 28.002527,164.28454 29.426359,166.31621 31.641839,167.58833 L 32.228819,166.56655 C 30.292452,165.45469 29.125555,163.77109 28.772164,161.56636 z M 27.511245,163.67513 L 26.685127,163.80557 C 26.972164,165.59635 27.982004,167.05482 29.554802,167.95791 L 29.967862,167.21875 C 28.593221,166.42943 27.762121,165.24029 27.511245,163.67513 z " + style="fill:url(#linearGradient19516);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:2.6928978;stroke-miterlimit:4;stroke-dashoffset:0;stroke-opacity:1" /> + </g> + <path + transform="matrix(0.52748,0.530602,-0.530613,0.527468,681.7934,8.403042)" + d="M 170.23595 161.93797 A 17.500893 17.500893 0 1 1 135.23417,161.93797 A 17.500893 17.500893 0 1 1 170.23595 161.93797 z" + sodipodi:ry="17.500893" + sodipodi:rx="17.500893" + sodipodi:cy="161.93797" + sodipodi:cx="152.73506" + id="path19158" + style="fill:url(#linearGradient19518);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient19520);stroke-width:1.03762257;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + sodipodi:type="arc" /> + <rect + ry="2.9068675" + y="158.55241" + x="662.01404" + height="31.999983" + width="32.000656" + id="rect19160" + style="fill:none;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + rx="2.976599" /> + <g + id="g19162" + transform="matrix(0.963751,0,0,0.963731,24.51233,105.1982)"> + <path + transform="matrix(0.116367,-0.126279,0.138815,0.105858,2044.201,-572.7461)" + d="M -7824.3296 -3285.1472 A 25.97311 5.306335 0 1 1 -7876.2758,-3285.1472 A 25.97311 5.306335 0 1 1 -7824.3296 -3285.1472 z" + sodipodi:ry="5.306335" + sodipodi:rx="25.97311" + sodipodi:cy="-3285.1472" + sodipodi:cx="-7850.3027" + id="path19164" + style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:21.03624916;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0.68131869" + sodipodi:type="arc" /> + <path + style="fill:url(#linearGradient19522);fill-opacity:1" + d="M 684.58932,62.015066 C 683.95163,62.03676 682.73778,63.425764 682.28042,63.646529 C 681.81946,63.869109 678.16982,66.391273 677.82409,66.836327 C 677.47838,67.281337 677.01748,67.46692 676.51816,67.429844 C 676.01883,67.392834 674.44026,67.377225 673.90249,67.673903 C 673.36463,67.970636 670.90988,70.397011 670.29527,70.693715 C 669.68068,70.990341 668.87865,71.141876 668.49449,71.178979 C 668.11043,71.216028 666.83809,72.584967 666.49238,72.844636 C 666.14667,73.10429 663.41458,74.2451 663.03046,74.467545 C 662.64631,74.690071 665.83291,77.807344 666.29388,77.881456 C 666.75485,77.955728 665.97647,79.643959 665.08957,79.643959 C 664.99189,79.643959 665.88202,82.441727 670.53651,84.906802 C 674.56037,87.037898 678.82957,86.094974 680.41507,85.578197 C 681.53629,85.212648 680.74985,85.010148 680.78834,84.416789 C 680.82666,83.823378 680.8587,83.043556 680.93555,82.376043 C 681.01244,81.708505 681.28161,76.107653 682.28042,74.624229 C 683.27912,73.140873 686.13308,71.360612 687.13185,71.175177 C 688.13068,70.989794 687.93916,70.619136 687.93916,70.285379 C 687.93916,69.95165 687.78546,69.209626 687.51651,69.098343 C 687.24765,68.987127 686.19822,69.654757 685.85251,69.728899 C 685.5068,69.803078 683.85489,69.914575 683.62435,69.543728 C 683.3939,69.172775 686.14108,63.906545 685.64166,63.276177 C 685.14247,62.645555 684.79657,62.015066 684.60452,62.015066 C 684.59957,62.015066 684.59434,62.014895 684.58932,62.015066 z M 676.42414,68.978694 C 676.73261,68.974532 676.94176,69.006232 676.94176,69.006232 C 677.11748,69.40176 676.29813,70.644697 675.53706,70.814323 C 674.77614,70.983791 673.01974,73.4706 672.96126,72.905408 C 672.90256,72.340212 673.66355,70.476005 674.54169,69.628234 C 675.09392,69.095034 675.90135,68.985752 676.42414,68.978694 z " + id="path19166" + sodipodi:nodetypes="ccccsccsccsccccccsssccsccscccc" /> + <path + style="fill:url(#linearGradient19524);fill-opacity:1" + d="M 666.16392,81.423003 C 665.9031,81.091515 665.42361,80.435598 665.26754,80.004036 C 665.14138,79.655175 665.13889,79.611803 665.24522,79.614525 C 665.37457,79.61782 665.53249,79.582317 665.71495,79.414066 C 666.12829,79.032933 666.46122,78.479561 666.36093,78.193327 C 666.34151,78.137933 666.28178,78.082005 666.19489,78.037937 C 665.68311,77.778228 663.68597,75.546616 663.20752,74.699847 C 663.14869,74.595715 663.09317,74.467102 663.08417,74.414107 L 663.06779,74.317713 L 663.5356,74.095961 C 663.79289,73.973979 664.43521,73.676485 664.96298,73.434854 C 666.54934,72.708572 666.40066,72.802712 667.30233,71.953513 C 668.1459,71.159029 668.45291,70.912676 668.63209,70.886525 C 669.29678,70.789464 669.74333,70.680556 670.16886,70.511811 C 670.62212,70.332045 670.78417,70.203751 672.21307,68.893356 C 673.39223,67.812027 673.7656,67.48122 673.99398,67.315529 C 674.36671,67.045079 674.85595,66.975476 676.21407,66.999677 C 677.02172,67.014072 677.16671,67.009258 677.3026,66.963628 C 677.54873,66.881043 677.75482,66.732936 678.04247,66.432002 C 678.18878,66.278881 678.48874,66.009285 678.70903,65.832881 C 679.56399,65.148239 681.38883,63.831519 682.2969,63.244071 C 682.83405,62.896574 682.83553,62.895428 683.52111,62.285059 C 684.09054,61.778114 684.47641,61.493496 684.70119,61.414567 C 684.91293,61.34024 684.93449,61.341828 685.04373,61.439835 C 685.15035,61.535483 686.03394,62.9158 686.10698,63.100796 C 686.24105,63.440395 686.31503,63.800609 686.17623,64.085306 C 685.91549,64.62008 685.53887,65.389083 685.37514,65.683372 C 684.01271,68.277198 683.57638,69.315608 683.75865,69.53045 C 683.9225,69.723542 684.62451,69.838696 685.37986,69.796375 C 685.63126,69.7823 685.89265,69.757165 685.96072,69.740527 C 686.02879,69.723904 686.33237,69.592247 686.63534,69.447957 C 687.2815,69.140217 687.50858,69.064356 687.61979,69.119151 C 687.73185,69.174334 687.84105,69.402649 687.91551,69.737405 C 687.98654,70.056699 688.02311,70.630231 687.98411,70.812999 C 687.93246,71.054964 687.70898,71.202203 687.22201,71.315153 C 686.50051,71.482471 685.39489,72.093335 684.25696,72.95334 C 683.19009,73.759659 682.46764,74.510542 682.13211,75.161789 C 681.942,75.530794 681.88754,75.797654 681.7263,76.390906 C 681.60627,76.832449 680.94162,82.834448 680.93784,82.974322 C 680.93675,83.015121 681.05021,76.469849 681.04313,76.469662 C 681.03606,76.469473 680.47326,77.712213 680.46324,77.71196 C 680.45319,77.711706 679.26724,79.616271 679.14064,79.557035 C 679.01407,79.497811 680.84098,75.164632 681.29637,74.622279 C 681.72505,74.111718 682.69675,73.228218 683.92824,72.229312 C 684.68954,71.611793 685.08523,71.319382 685.27321,71.235423 C 685.34613,71.202844 685.65566,71.131984 685.96107,71.077938 C 686.71523,70.944468 686.90799,70.86055 686.9133,70.663386 C 686.91912,70.447624 686.66022,70.304493 686.31274,70.331392 C 685.85732,70.366626 685.34615,70.54459 684.72472,70.884297 C 684.05931,71.248032 683.51826,71.34764 682.8754,71.224763 C 682.69316,71.189901 682.45974,71.128317 682.35669,71.087851 C 682.13555,71.001063 682.1061,70.935662 682.16257,70.656928 C 682.28424,70.056285 682.7318,69.095497 684.09589,66.506489 C 684.52724,65.687816 684.91444,64.919869 684.95636,64.799939 C 684.99825,64.680011 685.1686,64.493296 685.20972,64.242845 C 685.27332,63.855494 684.94437,63.305418 684.69042,63.096047 C 684.1717,62.710286 684.01007,62.609168 683.74917,62.719264 C 683.27598,62.918962 682.63747,63.885861 680.89506,66.251944 C 678.10234,70.044237 677.77609,70.437338 677.00798,70.935529 C 675.9344,71.631833 674.25248,73.011563 672.89065,74.31307 C 671.94197,75.219744 671.23101,76.306587 670.94267,76.585575 C 670.75542,76.76678 669.96025,77.745288 669.68107,77.980021 C 668.26676,78.87945 667.27756,79.841733 666.4157,81.272282 L 666.26937,81.5468 L 666.16392,81.423003 z M 668.6295,76.32153 C 669.21552,76.035684 669.9402,75.554275 670.29047,75.218104 C 670.57943,74.940811 670.69555,74.663571 670.53426,74.6361 C 670.43004,74.618356 670.14725,74.769144 669.42758,75.226216 C 668.60241,75.75033 668.36243,75.871421 668.1267,75.882628 C 667.6555,75.905015 666.59584,75.72705 666.06717,75.536718 C 665.70072,75.404769 665.57392,75.331295 665.45729,75.183336 C 665.38321,75.089358 665.37887,75.065462 665.39778,74.856572 C 665.42208,74.588243 665.56219,74.28492 665.80217,73.981211 C 665.92518,73.825514 666.03467,73.73747 666.35462,73.537052 C 667.27497,72.96057 668.28907,72.478015 669.33316,72.119738 C 669.57465,72.036872 669.8005,71.958064 669.83504,71.944615 C 669.88896,71.923614 669.87183,71.906057 669.71409,71.820563 C 669.45042,71.677633 669.10707,71.548405 668.91845,71.521096 C 668.73014,71.493813 668.65946,71.521481 668.21594,71.796028 C 667.88157,72.003024 666.25388,73.091052 665.48621,73.620705 C 665.18146,73.830983 664.72391,74.14191 664.46942,74.311617 C 664.21494,74.481377 663.98901,74.640691 663.96737,74.665692 C 663.90854,74.733682 663.91479,74.906936 663.98115,75.047732 C 664.05408,75.202428 664.40387,75.635913 664.59279,75.805673 L 664.73365,75.93226 L 666.46235,76.190566 C 667.41313,76.332656 668.22595,76.451329 668.26859,76.454332 C 668.31124,76.457306 668.47365,76.397562 668.6295,76.32153 z M 668.25415,75.137027 C 668.72466,75.109689 668.82443,75.064367 669.20342,74.70609 C 669.59232,74.338446 669.98504,73.821189 670.13614,73.477602 L 670.18873,73.358048 L 670.0701,73.373682 C 670.00486,73.382261 669.32753,73.452719 668.5649,73.53022 L 667.17835,73.671095 L 667.05139,73.770438 C 666.8115,73.958127 666.48365,74.29119 666.35948,74.473383 C 666.21238,74.689172 666.19775,74.804299 666.3076,74.881267 C 666.43204,74.968454 666.84529,75.068852 667.23887,75.107529 C 667.76006,75.158734 667.84061,75.161069 668.25415,75.137027 z M 673.23289,72.884003 C 673.36857,72.793999 673.63342,72.530209 674.28656,71.834505 C 674.76864,71.320997 675.22409,70.892729 675.40078,70.786743 C 675.46833,70.746223 675.60467,70.684907 675.70377,70.650443 C 675.92776,70.572528 676.07455,70.48466 676.28578,70.302053 C 676.73011,69.917918 677.18695,69.102408 677.12911,68.796643 C 677.11207,68.706574 677.1094,68.705506 676.85795,68.688776 C 675.95769,68.628965 675.25732,68.824768 674.75202,69.277624 C 674.16986,69.799364 673.40627,71.172265 673.12421,72.204378 C 673.01269,72.612445 672.98317,72.817187 673.02395,72.899506 C 673.06635,72.985066 673.08235,72.983877 673.23289,72.884003 z M 671.57619,71.796562 C 672.34776,71.643906 672.98398,71.514265 672.99004,71.508448 C 672.99608,71.502658 673.07634,71.260586 673.16839,70.970524 C 673.54217,69.792774 673.83364,69.271727 674.26438,69.011271 C 674.90419,68.624388 676.2258,68.209742 676.95832,68.166034 C 677.22424,68.150158 677.26926,68.137257 677.51161,68.007456 C 677.8718,67.814601 678.3997,67.456604 679.12431,66.913865 C 679.45905,66.663135 679.91088,66.333048 680.12838,66.180365 C 680.68623,65.788718 680.96159,65.51887 680.96589,65.359637 C 680.96661,65.332846 680.95475,65.310646 680.93954,65.310246 C 680.92433,65.309872 680.56473,65.552198 680.14043,65.848756 C 679.71612,66.145328 679.09133,66.572036 678.75201,66.797005 C 677.56622,67.583204 677.74205,67.5241 676.57466,67.528756 C 676.06082,67.530783 675.36265,67.538481 675.02318,67.545859 C 674.42556,67.558856 674.40209,67.56187 674.28268,67.640998 C 674.21487,67.685947 673.78028,68.103582 673.31692,68.569102 C 672.3073,69.583402 671.44162,70.400928 671.10522,70.657742 C 670.9698,70.761139 670.75997,70.888754 670.63892,70.941346 C 670.39611,71.046839 670.3172,71.116109 670.21049,71.317382 C 670.13004,71.469129 670.04208,71.806968 670.03645,71.985906 C 670.03309,72.092787 670.03952,72.102793 670.10304,72.089172 C 670.14171,72.080887 670.80463,71.949217 671.57619,71.796562 z " + id="path19168" + sodipodi:nodetypes="csssssscccscsscsssssscsssscsssssssssssssssscssssccssssscscscscccccsscsssssssssssscsscccsccsccccccsssccssssscsssccssssssssssscsscsssssc" /> + <g + id="g19170" + style="fill:#002ddb;fill-opacity:1"> + <path + id="path19172" + d="M 680.83905,76.838803 C 680.83905,76.838803 679.45446,81.3797 679.41945,81.449838 C 679.22156,81.846006 678.85659,82.994032 678.98224,83.364797 C 679.13021,83.801538 679.68481,84.436589 679.94365,84.754121 C 680.2025,85.071759 680.31342,85.230473 680.23945,85.349692 C 680.16554,85.468783 679.20413,85.468783 679.20413,85.468783 C 679.20413,85.468783 678.46465,84.396936 678.31664,83.999943 C 678.16882,83.603079 678.09481,82.769436 678.24277,82.491624 C 678.39065,82.213703 680.83905,76.838803 680.83905,76.838803 z " + i:knockout="Off" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:21.03624916;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <polygon + id="polygon19174" + points="272.565,567.14 214.39,617.501 287.326,574.086 272.565,567.14 " + a:adobe-blending-mode="lighten" + i:knockout="Off" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:17.46648407;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + transform="matrix(4.348075e-2,0,0,4.188821e-2,659.2285,53.92804)" /> + <polygon + id="polygon19176" + points="329.873,549.776 230.887,678.282 344.633,558.457 329.873,549.776 " + a:adobe-blending-mode="lighten" + i:knockout="Off" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:17.46648407;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + transform="matrix(4.348075e-2,0,0,4.188821e-2,659.2285,53.92804)" /> + <polygon + id="polygon19178" + points="337.688,594.058 262.146,713.882 355.053,601.872 337.688,594.058 " + a:adobe-blending-mode="lighten" + i:knockout="Off" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:17.46648407;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + transform="matrix(4.348075e-2,0,0,4.188821e-2,659.2285,53.92804)" /> + <polygon + id="polygon19180" + points="381.97,588.847 318.584,737.327 402.809,593.19 381.97,588.847 " + a:adobe-blending-mode="lighten" + i:knockout="Off" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:17.46648407;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + transform="matrix(4.348075e-2,0,0,4.188821e-2,659.2285,53.92804)" /> + <polygon + id="polygon19182" + points="407.151,627.921 401.94,763.375 424.515,629.658 407.151,627.921 " + a:adobe-blending-mode="lighten" + i:knockout="Off" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:17.46648407;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + transform="matrix(4.348075e-2,0,0,4.188821e-2,659.2285,53.92804)" /> + </g> + <path + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#002ddb;stroke-width:0.51881158;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 665.61116,79.917164 C 666.44697,82.39457 673.07693,87.947061 680.79385,85.431014 C 680.71584,83.40215 681.5919,77.986014 681.41239,76.464632" + id="path19184" + sodipodi:nodetypes="ccc" /> + </g> + </g> + <g + transform="matrix(1.46486,0,0,1.500001,211.6916,206.80722)" + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/48x48/actions/amarok_settings_playback.png" + id="g19196" + style="display:inline"> + <g + id="g19198" + transform="matrix(1.118235,0.112523,-0.116057,1.153316,675.5163,-25.95307)"> + <path + style="fill:url(#linearGradient19506);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:2.6928978;stroke-miterlimit:4;stroke-dashoffset:0;stroke-opacity:1" + d="M 30.272221,159.50106 L 28.859123,159.71846 C 29.342001,162.73108 31.039486,165.17772 33.685397,166.69699 L 34.402815,165.47955 C 32.090253,164.15169 30.694269,162.13413 30.272221,159.50106 z M 28.772164,161.56636 L 27.598205,161.76202 C 28.002527,164.28454 29.426359,166.31621 31.641839,167.58833 L 32.228819,166.56655 C 30.292452,165.45469 29.125555,163.77109 28.772164,161.56636 z M 27.511245,163.67513 L 26.685127,163.80557 C 26.972164,165.59635 27.982004,167.05482 29.554802,167.95791 L 29.967862,167.21875 C 28.593221,166.42943 27.762121,165.24029 27.511245,163.67513 z " + id="path19200" /> + </g> + <path + sodipodi:type="arc" + style="fill:url(#linearGradient19508);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient19510);stroke-width:1.03762257;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="path19202" + sodipodi:cx="152.73506" + sodipodi:cy="161.93797" + sodipodi:rx="17.500893" + sodipodi:ry="17.500893" + d="M 170.23595 161.93797 A 17.500893 17.500893 0 1 1 135.23417,161.93797 A 17.500893 17.500893 0 1 1 170.23595 161.93797 z" + transform="matrix(0.52748,0.530602,-0.530613,0.527468,681.7934,8.403042)" /> + <rect + rx="0.99220026" + style="fill:none;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + id="rect19204" + width="32.000656" + height="31.999983" + x="662.01404" + y="158.55241" + ry="0.96895635" /> + <g + transform="matrix(0.963751,0,0,0.963731,24.51233,105.1982)" + id="g19206"> + <path + sodipodi:type="arc" + style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:21.03624916;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0.68131869" + id="path19208" + sodipodi:cx="-7850.3027" + sodipodi:cy="-3285.1472" + sodipodi:rx="25.97311" + sodipodi:ry="5.306335" + d="M -7824.3296 -3285.1472 A 25.97311 5.306335 0 1 1 -7876.2758,-3285.1472 A 25.97311 5.306335 0 1 1 -7824.3296 -3285.1472 z" + transform="matrix(0.116367,-0.126279,0.138815,0.105858,2044.201,-572.7461)" /> + <path + sodipodi:nodetypes="ccccsccsccsccccccsssccsccscccc" + id="path19210" + d="M 684.58932,62.015066 C 683.95163,62.03676 682.73778,63.425764 682.28042,63.646529 C 681.81946,63.869109 678.16982,66.391273 677.82409,66.836327 C 677.47838,67.281337 677.01748,67.46692 676.51816,67.429844 C 676.01883,67.392834 674.44026,67.377225 673.90249,67.673903 C 673.36463,67.970636 670.90988,70.397011 670.29527,70.693715 C 669.68068,70.990341 668.87865,71.141876 668.49449,71.178979 C 668.11043,71.216028 666.83809,72.584967 666.49238,72.844636 C 666.14667,73.10429 663.41458,74.2451 663.03046,74.467545 C 662.64631,74.690071 665.83291,77.807344 666.29388,77.881456 C 666.75485,77.955728 665.97647,79.643959 665.08957,79.643959 C 664.99189,79.643959 665.88202,82.441727 670.53651,84.906802 C 674.56037,87.037898 678.82957,86.094974 680.41507,85.578197 C 681.53629,85.212648 680.74985,85.010148 680.78834,84.416789 C 680.82666,83.823378 680.8587,83.043556 680.93555,82.376043 C 681.01244,81.708505 681.28161,76.107653 682.28042,74.624229 C 683.27912,73.140873 686.13308,71.360612 687.13185,71.175177 C 688.13068,70.989794 687.93916,70.619136 687.93916,70.285379 C 687.93916,69.95165 687.78546,69.209626 687.51651,69.098343 C 687.24765,68.987127 686.19822,69.654757 685.85251,69.728899 C 685.5068,69.803078 683.85489,69.914575 683.62435,69.543728 C 683.3939,69.172775 686.14108,63.906545 685.64166,63.276177 C 685.14247,62.645555 684.79657,62.015066 684.60452,62.015066 C 684.59957,62.015066 684.59434,62.014895 684.58932,62.015066 z M 676.42414,68.978694 C 676.73261,68.974532 676.94176,69.006232 676.94176,69.006232 C 677.11748,69.40176 676.29813,70.644697 675.53706,70.814323 C 674.77614,70.983791 673.01974,73.4706 672.96126,72.905408 C 672.90256,72.340212 673.66355,70.476005 674.54169,69.628234 C 675.09392,69.095034 675.90135,68.985752 676.42414,68.978694 z " + style="fill:url(#linearGradient19512);fill-opacity:1" /> + <path + sodipodi:nodetypes="csssssscccscsscsssssscsssscsssssssssssssssscssssccssssscscscscccccsscsssssssssssscsscccsccsccccccsssccssssscsssccssssssssssscsscsssssc" + id="path19212" + d="M 666.16392,81.423003 C 665.9031,81.091515 665.42361,80.435598 665.26754,80.004036 C 665.14138,79.655175 665.13889,79.611803 665.24522,79.614525 C 665.37457,79.61782 665.53249,79.582317 665.71495,79.414066 C 666.12829,79.032933 666.46122,78.479561 666.36093,78.193327 C 666.34151,78.137933 666.28178,78.082005 666.19489,78.037937 C 665.68311,77.778228 663.68597,75.546616 663.20752,74.699847 C 663.14869,74.595715 663.09317,74.467102 663.08417,74.414107 L 663.06779,74.317713 L 663.5356,74.095961 C 663.79289,73.973979 664.43521,73.676485 664.96298,73.434854 C 666.54934,72.708572 666.40066,72.802712 667.30233,71.953513 C 668.1459,71.159029 668.45291,70.912676 668.63209,70.886525 C 669.29678,70.789464 669.74333,70.680556 670.16886,70.511811 C 670.62212,70.332045 670.78417,70.203751 672.21307,68.893356 C 673.39223,67.812027 673.7656,67.48122 673.99398,67.315529 C 674.36671,67.045079 674.85595,66.975476 676.21407,66.999677 C 677.02172,67.014072 677.16671,67.009258 677.3026,66.963628 C 677.54873,66.881043 677.75482,66.732936 678.04247,66.432002 C 678.18878,66.278881 678.48874,66.009285 678.70903,65.832881 C 679.56399,65.148239 681.38883,63.831519 682.2969,63.244071 C 682.83405,62.896574 682.83553,62.895428 683.52111,62.285059 C 684.09054,61.778114 684.47641,61.493496 684.70119,61.414567 C 684.91293,61.34024 684.93449,61.341828 685.04373,61.439835 C 685.15035,61.535483 686.03394,62.9158 686.10698,63.100796 C 686.24105,63.440395 686.31503,63.800609 686.17623,64.085306 C 685.91549,64.62008 685.53887,65.389083 685.37514,65.683372 C 684.01271,68.277198 683.57638,69.315608 683.75865,69.53045 C 683.9225,69.723542 684.62451,69.838696 685.37986,69.796375 C 685.63126,69.7823 685.89265,69.757165 685.96072,69.740527 C 686.02879,69.723904 686.33237,69.592247 686.63534,69.447957 C 687.2815,69.140217 687.50858,69.064356 687.61979,69.119151 C 687.73185,69.174334 687.84105,69.402649 687.91551,69.737405 C 687.98654,70.056699 688.02311,70.630231 687.98411,70.812999 C 687.93246,71.054964 687.70898,71.202203 687.22201,71.315153 C 686.50051,71.482471 685.39489,72.093335 684.25696,72.95334 C 683.19009,73.759659 682.46764,74.510542 682.13211,75.161789 C 681.942,75.530794 681.88754,75.797654 681.7263,76.390906 C 681.60627,76.832449 680.94162,82.834448 680.93784,82.974322 C 680.93675,83.015121 681.05021,76.469849 681.04313,76.469662 C 681.03606,76.469473 680.47326,77.712213 680.46324,77.71196 C 680.45319,77.711706 679.26724,79.616271 679.14064,79.557035 C 679.01407,79.497811 680.84098,75.164632 681.29637,74.622279 C 681.72505,74.111718 682.69675,73.228218 683.92824,72.229312 C 684.68954,71.611793 685.08523,71.319382 685.27321,71.235423 C 685.34613,71.202844 685.65566,71.131984 685.96107,71.077938 C 686.71523,70.944468 686.90799,70.86055 686.9133,70.663386 C 686.91912,70.447624 686.66022,70.304493 686.31274,70.331392 C 685.85732,70.366626 685.34615,70.54459 684.72472,70.884297 C 684.05931,71.248032 683.51826,71.34764 682.8754,71.224763 C 682.69316,71.189901 682.45974,71.128317 682.35669,71.087851 C 682.13555,71.001063 682.1061,70.935662 682.16257,70.656928 C 682.28424,70.056285 682.7318,69.095497 684.09589,66.506489 C 684.52724,65.687816 684.91444,64.919869 684.95636,64.799939 C 684.99825,64.680011 685.1686,64.493296 685.20972,64.242845 C 685.27332,63.855494 684.94437,63.305418 684.69042,63.096047 C 684.1717,62.710286 684.01007,62.609168 683.74917,62.719264 C 683.27598,62.918962 682.63747,63.885861 680.89506,66.251944 C 678.10234,70.044237 677.77609,70.437338 677.00798,70.935529 C 675.9344,71.631833 674.25248,73.011563 672.89065,74.31307 C 671.94197,75.219744 671.23101,76.306587 670.94267,76.585575 C 670.75542,76.76678 669.96025,77.745288 669.68107,77.980021 C 668.26676,78.87945 667.27756,79.841733 666.4157,81.272282 L 666.26937,81.5468 L 666.16392,81.423003 z M 668.6295,76.32153 C 669.21552,76.035684 669.9402,75.554275 670.29047,75.218104 C 670.57943,74.940811 670.69555,74.663571 670.53426,74.6361 C 670.43004,74.618356 670.14725,74.769144 669.42758,75.226216 C 668.60241,75.75033 668.36243,75.871421 668.1267,75.882628 C 667.6555,75.905015 666.59584,75.72705 666.06717,75.536718 C 665.70072,75.404769 665.57392,75.331295 665.45729,75.183336 C 665.38321,75.089358 665.37887,75.065462 665.39778,74.856572 C 665.42208,74.588243 665.56219,74.28492 665.80217,73.981211 C 665.92518,73.825514 666.03467,73.73747 666.35462,73.537052 C 667.27497,72.96057 668.28907,72.478015 669.33316,72.119738 C 669.57465,72.036872 669.8005,71.958064 669.83504,71.944615 C 669.88896,71.923614 669.87183,71.906057 669.71409,71.820563 C 669.45042,71.677633 669.10707,71.548405 668.91845,71.521096 C 668.73014,71.493813 668.65946,71.521481 668.21594,71.796028 C 667.88157,72.003024 666.25388,73.091052 665.48621,73.620705 C 665.18146,73.830983 664.72391,74.14191 664.46942,74.311617 C 664.21494,74.481377 663.98901,74.640691 663.96737,74.665692 C 663.90854,74.733682 663.91479,74.906936 663.98115,75.047732 C 664.05408,75.202428 664.40387,75.635913 664.59279,75.805673 L 664.73365,75.93226 L 666.46235,76.190566 C 667.41313,76.332656 668.22595,76.451329 668.26859,76.454332 C 668.31124,76.457306 668.47365,76.397562 668.6295,76.32153 z M 668.25415,75.137027 C 668.72466,75.109689 668.82443,75.064367 669.20342,74.70609 C 669.59232,74.338446 669.98504,73.821189 670.13614,73.477602 L 670.18873,73.358048 L 670.0701,73.373682 C 670.00486,73.382261 669.32753,73.452719 668.5649,73.53022 L 667.17835,73.671095 L 667.05139,73.770438 C 666.8115,73.958127 666.48365,74.29119 666.35948,74.473383 C 666.21238,74.689172 666.19775,74.804299 666.3076,74.881267 C 666.43204,74.968454 666.84529,75.068852 667.23887,75.107529 C 667.76006,75.158734 667.84061,75.161069 668.25415,75.137027 z M 673.23289,72.884003 C 673.36857,72.793999 673.63342,72.530209 674.28656,71.834505 C 674.76864,71.320997 675.22409,70.892729 675.40078,70.786743 C 675.46833,70.746223 675.60467,70.684907 675.70377,70.650443 C 675.92776,70.572528 676.07455,70.48466 676.28578,70.302053 C 676.73011,69.917918 677.18695,69.102408 677.12911,68.796643 C 677.11207,68.706574 677.1094,68.705506 676.85795,68.688776 C 675.95769,68.628965 675.25732,68.824768 674.75202,69.277624 C 674.16986,69.799364 673.40627,71.172265 673.12421,72.204378 C 673.01269,72.612445 672.98317,72.817187 673.02395,72.899506 C 673.06635,72.985066 673.08235,72.983877 673.23289,72.884003 z M 671.57619,71.796562 C 672.34776,71.643906 672.98398,71.514265 672.99004,71.508448 C 672.99608,71.502658 673.07634,71.260586 673.16839,70.970524 C 673.54217,69.792774 673.83364,69.271727 674.26438,69.011271 C 674.90419,68.624388 676.2258,68.209742 676.95832,68.166034 C 677.22424,68.150158 677.26926,68.137257 677.51161,68.007456 C 677.8718,67.814601 678.3997,67.456604 679.12431,66.913865 C 679.45905,66.663135 679.91088,66.333048 680.12838,66.180365 C 680.68623,65.788718 680.96159,65.51887 680.96589,65.359637 C 680.96661,65.332846 680.95475,65.310646 680.93954,65.310246 C 680.92433,65.309872 680.56473,65.552198 680.14043,65.848756 C 679.71612,66.145328 679.09133,66.572036 678.75201,66.797005 C 677.56622,67.583204 677.74205,67.5241 676.57466,67.528756 C 676.06082,67.530783 675.36265,67.538481 675.02318,67.545859 C 674.42556,67.558856 674.40209,67.56187 674.28268,67.640998 C 674.21487,67.685947 673.78028,68.103582 673.31692,68.569102 C 672.3073,69.583402 671.44162,70.400928 671.10522,70.657742 C 670.9698,70.761139 670.75997,70.888754 670.63892,70.941346 C 670.39611,71.046839 670.3172,71.116109 670.21049,71.317382 C 670.13004,71.469129 670.04208,71.806968 670.03645,71.985906 C 670.03309,72.092787 670.03952,72.102793 670.10304,72.089172 C 670.14171,72.080887 670.80463,71.949217 671.57619,71.796562 z " + style="fill:url(#linearGradient19514);fill-opacity:1" /> + <g + style="fill:#002ddb;fill-opacity:1" + id="g19214"> + <path + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:21.03624916;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + i:knockout="Off" + d="M 680.83905,76.838803 C 680.83905,76.838803 679.45446,81.3797 679.41945,81.449838 C 679.22156,81.846006 678.85659,82.994032 678.98224,83.364797 C 679.13021,83.801538 679.68481,84.436589 679.94365,84.754121 C 680.2025,85.071759 680.31342,85.230473 680.23945,85.349692 C 680.16554,85.468783 679.20413,85.468783 679.20413,85.468783 C 679.20413,85.468783 678.46465,84.396936 678.31664,83.999943 C 678.16882,83.603079 678.09481,82.769436 678.24277,82.491624 C 678.39065,82.213703 680.83905,76.838803 680.83905,76.838803 z " + id="path19216" /> + <polygon + transform="matrix(4.348075e-2,0,0,4.188821e-2,659.2285,53.92804)" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:17.46648407;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + i:knockout="Off" + a:adobe-blending-mode="lighten" + points="272.565,567.14 214.39,617.501 287.326,574.086 272.565,567.14 " + id="polygon19218" /> + <polygon + transform="matrix(4.348075e-2,0,0,4.188821e-2,659.2285,53.92804)" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:17.46648407;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + i:knockout="Off" + a:adobe-blending-mode="lighten" + points="329.873,549.776 230.887,678.282 344.633,558.457 329.873,549.776 " + id="polygon19220" /> + <polygon + transform="matrix(4.348075e-2,0,0,4.188821e-2,659.2285,53.92804)" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:17.46648407;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + i:knockout="Off" + a:adobe-blending-mode="lighten" + points="337.688,594.058 262.146,713.882 355.053,601.872 337.688,594.058 " + id="polygon19222" /> + <polygon + transform="matrix(4.348075e-2,0,0,4.188821e-2,659.2285,53.92804)" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:17.46648407;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + i:knockout="Off" + a:adobe-blending-mode="lighten" + points="381.97,588.847 318.584,737.327 402.809,593.19 381.97,588.847 " + id="polygon19224" /> + <polygon + transform="matrix(4.348075e-2,0,0,4.188821e-2,659.2285,53.92804)" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:17.46648407;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + i:knockout="Off" + a:adobe-blending-mode="lighten" + points="407.151,627.921 401.94,763.375 424.515,629.658 407.151,627.921 " + id="polygon19226" /> + </g> + <path + sodipodi:nodetypes="ccc" + id="path19228" + d="M 665.61116,79.917164 C 666.44697,82.39457 673.07693,87.947061 680.79385,85.431014 C 680.71584,83.40215 681.5919,77.986014 681.41239,76.464632" + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#002ddb;stroke-width:0.51881158;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + </g> + <g + style="display:inline" + id="g19240" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/64x64/actions/amarok_settings_playback.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" + transform="matrix(1.953146,0,0,2.000001,-51.339143,113.53102)"> + <g + transform="matrix(1.118235,0.112523,-0.116057,1.153316,675.5163,-25.95307)" + id="g19242"> + <path + id="path19244" + d="M 30.272221,159.50106 L 28.859123,159.71846 C 29.342001,162.73108 31.039486,165.17772 33.685397,166.69699 L 34.402815,165.47955 C 32.090253,164.15169 30.694269,162.13413 30.272221,159.50106 z M 28.772164,161.56636 L 27.598205,161.76202 C 28.002527,164.28454 29.426359,166.31621 31.641839,167.58833 L 32.228819,166.56655 C 30.292452,165.45469 29.125555,163.77109 28.772164,161.56636 z M 27.511245,163.67513 L 26.685127,163.80557 C 26.972164,165.59635 27.982004,167.05482 29.554802,167.95791 L 29.967862,167.21875 C 28.593221,166.42943 27.762121,165.24029 27.511245,163.67513 z " + style="fill:url(#linearGradient19411);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:2.6928978;stroke-miterlimit:4;stroke-dashoffset:0;stroke-opacity:1" /> + </g> + <path + transform="matrix(0.52748,0.530602,-0.530613,0.527468,681.7934,8.403042)" + d="M 170.23595 161.93797 A 17.500893 17.500893 0 1 1 135.23417,161.93797 A 17.500893 17.500893 0 1 1 170.23595 161.93797 z" + sodipodi:ry="17.500893" + sodipodi:rx="17.500893" + sodipodi:cy="161.93797" + sodipodi:cx="152.73506" + id="path19246" + style="fill:url(#linearGradient19413);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient19415);stroke-width:1.03762257;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + sodipodi:type="arc" /> + <rect + ry="0.72671735" + y="158.55241" + x="662.01404" + height="31.999983" + width="32.000656" + id="rect19248" + style="fill:none;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + rx="0.74415028" /> + <g + id="g19250" + transform="matrix(0.963751,0,0,0.963731,24.51233,105.1982)"> + <path + transform="matrix(0.116367,-0.126279,0.138815,0.105858,2044.201,-572.7461)" + d="M -7824.3296 -3285.1472 A 25.97311 5.306335 0 1 1 -7876.2758,-3285.1472 A 25.97311 5.306335 0 1 1 -7824.3296 -3285.1472 z" + sodipodi:ry="5.306335" + sodipodi:rx="25.97311" + sodipodi:cy="-3285.1472" + sodipodi:cx="-7850.3027" + id="path19252" + style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:21.03624916;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0.68131869" + sodipodi:type="arc" /> + <path + style="fill:url(#linearGradient19417);fill-opacity:1" + d="M 684.58932,62.015066 C 683.95163,62.03676 682.73778,63.425764 682.28042,63.646529 C 681.81946,63.869109 678.16982,66.391273 677.82409,66.836327 C 677.47838,67.281337 677.01748,67.46692 676.51816,67.429844 C 676.01883,67.392834 674.44026,67.377225 673.90249,67.673903 C 673.36463,67.970636 670.90988,70.397011 670.29527,70.693715 C 669.68068,70.990341 668.87865,71.141876 668.49449,71.178979 C 668.11043,71.216028 666.83809,72.584967 666.49238,72.844636 C 666.14667,73.10429 663.41458,74.2451 663.03046,74.467545 C 662.64631,74.690071 665.83291,77.807344 666.29388,77.881456 C 666.75485,77.955728 665.97647,79.643959 665.08957,79.643959 C 664.99189,79.643959 665.88202,82.441727 670.53651,84.906802 C 674.56037,87.037898 678.82957,86.094974 680.41507,85.578197 C 681.53629,85.212648 680.74985,85.010148 680.78834,84.416789 C 680.82666,83.823378 680.8587,83.043556 680.93555,82.376043 C 681.01244,81.708505 681.28161,76.107653 682.28042,74.624229 C 683.27912,73.140873 686.13308,71.360612 687.13185,71.175177 C 688.13068,70.989794 687.93916,70.619136 687.93916,70.285379 C 687.93916,69.95165 687.78546,69.209626 687.51651,69.098343 C 687.24765,68.987127 686.19822,69.654757 685.85251,69.728899 C 685.5068,69.803078 683.85489,69.914575 683.62435,69.543728 C 683.3939,69.172775 686.14108,63.906545 685.64166,63.276177 C 685.14247,62.645555 684.79657,62.015066 684.60452,62.015066 C 684.59957,62.015066 684.59434,62.014895 684.58932,62.015066 z M 676.42414,68.978694 C 676.73261,68.974532 676.94176,69.006232 676.94176,69.006232 C 677.11748,69.40176 676.29813,70.644697 675.53706,70.814323 C 674.77614,70.983791 673.01974,73.4706 672.96126,72.905408 C 672.90256,72.340212 673.66355,70.476005 674.54169,69.628234 C 675.09392,69.095034 675.90135,68.985752 676.42414,68.978694 z " + id="path19254" + sodipodi:nodetypes="ccccsccsccsccccccsssccsccscccc" /> + <path + style="fill:url(#linearGradient19419);fill-opacity:1" + d="M 666.16392,81.423003 C 665.9031,81.091515 665.42361,80.435598 665.26754,80.004036 C 665.14138,79.655175 665.13889,79.611803 665.24522,79.614525 C 665.37457,79.61782 665.53249,79.582317 665.71495,79.414066 C 666.12829,79.032933 666.46122,78.479561 666.36093,78.193327 C 666.34151,78.137933 666.28178,78.082005 666.19489,78.037937 C 665.68311,77.778228 663.68597,75.546616 663.20752,74.699847 C 663.14869,74.595715 663.09317,74.467102 663.08417,74.414107 L 663.06779,74.317713 L 663.5356,74.095961 C 663.79289,73.973979 664.43521,73.676485 664.96298,73.434854 C 666.54934,72.708572 666.40066,72.802712 667.30233,71.953513 C 668.1459,71.159029 668.45291,70.912676 668.63209,70.886525 C 669.29678,70.789464 669.74333,70.680556 670.16886,70.511811 C 670.62212,70.332045 670.78417,70.203751 672.21307,68.893356 C 673.39223,67.812027 673.7656,67.48122 673.99398,67.315529 C 674.36671,67.045079 674.85595,66.975476 676.21407,66.999677 C 677.02172,67.014072 677.16671,67.009258 677.3026,66.963628 C 677.54873,66.881043 677.75482,66.732936 678.04247,66.432002 C 678.18878,66.278881 678.48874,66.009285 678.70903,65.832881 C 679.56399,65.148239 681.38883,63.831519 682.2969,63.244071 C 682.83405,62.896574 682.83553,62.895428 683.52111,62.285059 C 684.09054,61.778114 684.47641,61.493496 684.70119,61.414567 C 684.91293,61.34024 684.93449,61.341828 685.04373,61.439835 C 685.15035,61.535483 686.03394,62.9158 686.10698,63.100796 C 686.24105,63.440395 686.31503,63.800609 686.17623,64.085306 C 685.91549,64.62008 685.53887,65.389083 685.37514,65.683372 C 684.01271,68.277198 683.57638,69.315608 683.75865,69.53045 C 683.9225,69.723542 684.62451,69.838696 685.37986,69.796375 C 685.63126,69.7823 685.89265,69.757165 685.96072,69.740527 C 686.02879,69.723904 686.33237,69.592247 686.63534,69.447957 C 687.2815,69.140217 687.50858,69.064356 687.61979,69.119151 C 687.73185,69.174334 687.84105,69.402649 687.91551,69.737405 C 687.98654,70.056699 688.02311,70.630231 687.98411,70.812999 C 687.93246,71.054964 687.70898,71.202203 687.22201,71.315153 C 686.50051,71.482471 685.39489,72.093335 684.25696,72.95334 C 683.19009,73.759659 682.46764,74.510542 682.13211,75.161789 C 681.942,75.530794 681.88754,75.797654 681.7263,76.390906 C 681.60627,76.832449 680.94162,82.834448 680.93784,82.974322 C 680.93675,83.015121 681.05021,76.469849 681.04313,76.469662 C 681.03606,76.469473 680.47326,77.712213 680.46324,77.71196 C 680.45319,77.711706 679.26724,79.616271 679.14064,79.557035 C 679.01407,79.497811 680.84098,75.164632 681.29637,74.622279 C 681.72505,74.111718 682.69675,73.228218 683.92824,72.229312 C 684.68954,71.611793 685.08523,71.319382 685.27321,71.235423 C 685.34613,71.202844 685.65566,71.131984 685.96107,71.077938 C 686.71523,70.944468 686.90799,70.86055 686.9133,70.663386 C 686.91912,70.447624 686.66022,70.304493 686.31274,70.331392 C 685.85732,70.366626 685.34615,70.54459 684.72472,70.884297 C 684.05931,71.248032 683.51826,71.34764 682.8754,71.224763 C 682.69316,71.189901 682.45974,71.128317 682.35669,71.087851 C 682.13555,71.001063 682.1061,70.935662 682.16257,70.656928 C 682.28424,70.056285 682.7318,69.095497 684.09589,66.506489 C 684.52724,65.687816 684.91444,64.919869 684.95636,64.799939 C 684.99825,64.680011 685.1686,64.493296 685.20972,64.242845 C 685.27332,63.855494 684.94437,63.305418 684.69042,63.096047 C 684.1717,62.710286 684.01007,62.609168 683.74917,62.719264 C 683.27598,62.918962 682.63747,63.885861 680.89506,66.251944 C 678.10234,70.044237 677.77609,70.437338 677.00798,70.935529 C 675.9344,71.631833 674.25248,73.011563 672.89065,74.31307 C 671.94197,75.219744 671.23101,76.306587 670.94267,76.585575 C 670.75542,76.76678 669.96025,77.745288 669.68107,77.980021 C 668.26676,78.87945 667.27756,79.841733 666.4157,81.272282 L 666.26937,81.5468 L 666.16392,81.423003 z M 668.6295,76.32153 C 669.21552,76.035684 669.9402,75.554275 670.29047,75.218104 C 670.57943,74.940811 670.69555,74.663571 670.53426,74.6361 C 670.43004,74.618356 670.14725,74.769144 669.42758,75.226216 C 668.60241,75.75033 668.36243,75.871421 668.1267,75.882628 C 667.6555,75.905015 666.59584,75.72705 666.06717,75.536718 C 665.70072,75.404769 665.57392,75.331295 665.45729,75.183336 C 665.38321,75.089358 665.37887,75.065462 665.39778,74.856572 C 665.42208,74.588243 665.56219,74.28492 665.80217,73.981211 C 665.92518,73.825514 666.03467,73.73747 666.35462,73.537052 C 667.27497,72.96057 668.28907,72.478015 669.33316,72.119738 C 669.57465,72.036872 669.8005,71.958064 669.83504,71.944615 C 669.88896,71.923614 669.87183,71.906057 669.71409,71.820563 C 669.45042,71.677633 669.10707,71.548405 668.91845,71.521096 C 668.73014,71.493813 668.65946,71.521481 668.21594,71.796028 C 667.88157,72.003024 666.25388,73.091052 665.48621,73.620705 C 665.18146,73.830983 664.72391,74.14191 664.46942,74.311617 C 664.21494,74.481377 663.98901,74.640691 663.96737,74.665692 C 663.90854,74.733682 663.91479,74.906936 663.98115,75.047732 C 664.05408,75.202428 664.40387,75.635913 664.59279,75.805673 L 664.73365,75.93226 L 666.46235,76.190566 C 667.41313,76.332656 668.22595,76.451329 668.26859,76.454332 C 668.31124,76.457306 668.47365,76.397562 668.6295,76.32153 z M 668.25415,75.137027 C 668.72466,75.109689 668.82443,75.064367 669.20342,74.70609 C 669.59232,74.338446 669.98504,73.821189 670.13614,73.477602 L 670.18873,73.358048 L 670.0701,73.373682 C 670.00486,73.382261 669.32753,73.452719 668.5649,73.53022 L 667.17835,73.671095 L 667.05139,73.770438 C 666.8115,73.958127 666.48365,74.29119 666.35948,74.473383 C 666.21238,74.689172 666.19775,74.804299 666.3076,74.881267 C 666.43204,74.968454 666.84529,75.068852 667.23887,75.107529 C 667.76006,75.158734 667.84061,75.161069 668.25415,75.137027 z M 673.23289,72.884003 C 673.36857,72.793999 673.63342,72.530209 674.28656,71.834505 C 674.76864,71.320997 675.22409,70.892729 675.40078,70.786743 C 675.46833,70.746223 675.60467,70.684907 675.70377,70.650443 C 675.92776,70.572528 676.07455,70.48466 676.28578,70.302053 C 676.73011,69.917918 677.18695,69.102408 677.12911,68.796643 C 677.11207,68.706574 677.1094,68.705506 676.85795,68.688776 C 675.95769,68.628965 675.25732,68.824768 674.75202,69.277624 C 674.16986,69.799364 673.40627,71.172265 673.12421,72.204378 C 673.01269,72.612445 672.98317,72.817187 673.02395,72.899506 C 673.06635,72.985066 673.08235,72.983877 673.23289,72.884003 z M 671.57619,71.796562 C 672.34776,71.643906 672.98398,71.514265 672.99004,71.508448 C 672.99608,71.502658 673.07634,71.260586 673.16839,70.970524 C 673.54217,69.792774 673.83364,69.271727 674.26438,69.011271 C 674.90419,68.624388 676.2258,68.209742 676.95832,68.166034 C 677.22424,68.150158 677.26926,68.137257 677.51161,68.007456 C 677.8718,67.814601 678.3997,67.456604 679.12431,66.913865 C 679.45905,66.663135 679.91088,66.333048 680.12838,66.180365 C 680.68623,65.788718 680.96159,65.51887 680.96589,65.359637 C 680.96661,65.332846 680.95475,65.310646 680.93954,65.310246 C 680.92433,65.309872 680.56473,65.552198 680.14043,65.848756 C 679.71612,66.145328 679.09133,66.572036 678.75201,66.797005 C 677.56622,67.583204 677.74205,67.5241 676.57466,67.528756 C 676.06082,67.530783 675.36265,67.538481 675.02318,67.545859 C 674.42556,67.558856 674.40209,67.56187 674.28268,67.640998 C 674.21487,67.685947 673.78028,68.103582 673.31692,68.569102 C 672.3073,69.583402 671.44162,70.400928 671.10522,70.657742 C 670.9698,70.761139 670.75997,70.888754 670.63892,70.941346 C 670.39611,71.046839 670.3172,71.116109 670.21049,71.317382 C 670.13004,71.469129 670.04208,71.806968 670.03645,71.985906 C 670.03309,72.092787 670.03952,72.102793 670.10304,72.089172 C 670.14171,72.080887 670.80463,71.949217 671.57619,71.796562 z " + id="path19256" + sodipodi:nodetypes="csssssscccscsscsssssscsssscsssssssssssssssscssssccssssscscscscccccsscsssssssssssscsscccsccsccccccsssccssssscsssccssssssssssscsscsssssc" /> + <g + id="g19258" + style="fill:#002ddb;fill-opacity:1"> + <path + id="path19260" + d="M 680.83905,76.838803 C 680.83905,76.838803 679.45446,81.3797 679.41945,81.449838 C 679.22156,81.846006 678.85659,82.994032 678.98224,83.364797 C 679.13021,83.801538 679.68481,84.436589 679.94365,84.754121 C 680.2025,85.071759 680.31342,85.230473 680.23945,85.349692 C 680.16554,85.468783 679.20413,85.468783 679.20413,85.468783 C 679.20413,85.468783 678.46465,84.396936 678.31664,83.999943 C 678.16882,83.603079 678.09481,82.769436 678.24277,82.491624 C 678.39065,82.213703 680.83905,76.838803 680.83905,76.838803 z " + i:knockout="Off" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:21.03624916;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <polygon + id="polygon19262" + points="272.565,567.14 214.39,617.501 287.326,574.086 272.565,567.14 " + a:adobe-blending-mode="lighten" + i:knockout="Off" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:17.46648407;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + transform="matrix(4.348075e-2,0,0,4.188821e-2,659.2285,53.92804)" /> + <polygon + id="polygon19264" + points="329.873,549.776 230.887,678.282 344.633,558.457 329.873,549.776 " + a:adobe-blending-mode="lighten" + i:knockout="Off" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:17.46648407;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + transform="matrix(4.348075e-2,0,0,4.188821e-2,659.2285,53.92804)" /> + <polygon + id="polygon19266" + points="337.688,594.058 262.146,713.882 355.053,601.872 337.688,594.058 " + a:adobe-blending-mode="lighten" + i:knockout="Off" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:17.46648407;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + transform="matrix(4.348075e-2,0,0,4.188821e-2,659.2285,53.92804)" /> + <polygon + id="polygon19268" + points="381.97,588.847 318.584,737.327 402.809,593.19 381.97,588.847 " + a:adobe-blending-mode="lighten" + i:knockout="Off" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:17.46648407;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + transform="matrix(4.348075e-2,0,0,4.188821e-2,659.2285,53.92804)" /> + <polygon + id="polygon19270" + points="407.151,627.921 401.94,763.375 424.515,629.658 407.151,627.921 " + a:adobe-blending-mode="lighten" + i:knockout="Off" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:17.46648407;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + transform="matrix(4.348075e-2,0,0,4.188821e-2,659.2285,53.92804)" /> + </g> + <path + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#002ddb;stroke-width:0.51881158;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 665.61116,79.917164 C 666.44697,82.39457 673.07693,87.947061 680.79385,85.431014 C 680.71584,83.40215 681.5919,77.986014 681.41239,76.464632" + id="path19272" + sodipodi:nodetypes="ccc" /> + </g> + </g> + <g + transform="matrix(0.501165,0,0,0.499988,440.9935,141.53362)" + id="g19546" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/16x16/actions/amarok_settings_indicator.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <rect + inkscape:export-ydpi="395.48178" + inkscape:export-xdpi="395.48178" + inkscape:export-filename="/home/lando/Projects/Amarok/Setting-dialog/amarok_settings_indicator.png" + ry="5.3997931" + rx="4.0241222" + y="819.315" + x="1257.8357" + height="31.220118" + width="31.146786" + id="rect19548" + style="fill:url(#linearGradient19576);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient19578);stroke-width:0.77971518;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;display:inline" /> + <rect + inkscape:export-ydpi="395.48178" + inkscape:export-xdpi="395.48178" + inkscape:export-filename="/home/lando/Projects/Amarok/Setting-dialog/amarok_settings_indicator.png" + ry="1.76642" + rx="1.7622732" + y="822.23187" + x="1260.3097" + height="25.386372" + width="26.198996" + id="rect19550" + style="fill:url(#linearGradient19580);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient19582);stroke-width:0.47538957;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;display:inline" /> + <g + style="display:inline" + id="g19552" + transform="matrix(1.076035,0,0,1.076025,544.8993,756.7895)" + inkscape:export-filename="/home/lando/Projects/Amarok/Setting-dialog/amarok_settings_indicator.png" + inkscape:export-xdpi="395.48178" + inkscape:export-ydpi="395.48178"> + <path + transform="matrix(0.116367,-0.126279,0.138815,0.105858,2044.201,-572.7461)" + d="M -7824.3296 -3285.1472 A 25.97311 5.306335 0 1 1 -7876.2758,-3285.1472 A 25.97311 5.306335 0 1 1 -7824.3296 -3285.1472 z" + sodipodi:ry="5.306335" + sodipodi:rx="25.97311" + sodipodi:cy="-3285.1472" + sodipodi:cx="-7850.3027" + id="path19554" + style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:21.03624916;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0.68131869" + sodipodi:type="arc" /> + <path + style="fill:url(#linearGradient19584);fill-opacity:1" + d="M 684.58932,62.015066 C 683.95163,62.03676 682.73778,63.425764 682.28042,63.646529 C 681.81946,63.869109 678.16982,66.391273 677.82409,66.836327 C 677.47838,67.281337 677.01748,67.46692 676.51816,67.429844 C 676.01883,67.392834 674.44026,67.377225 673.90249,67.673903 C 673.36463,67.970636 670.90988,70.397011 670.29527,70.693715 C 669.68068,70.990341 668.87865,71.141876 668.49449,71.178979 C 668.11043,71.216028 666.83809,72.584967 666.49238,72.844636 C 666.14667,73.10429 663.41458,74.2451 663.03046,74.467545 C 662.64631,74.690071 665.83291,77.807344 666.29388,77.881456 C 666.75485,77.955728 665.97647,79.643959 665.08957,79.643959 C 664.99189,79.643959 665.88202,82.441727 670.53651,84.906802 C 674.56037,87.037898 678.82957,86.094974 680.41507,85.578197 C 681.53629,85.212648 680.74985,85.010148 680.78834,84.416789 C 680.82666,83.823378 680.8587,83.043556 680.93555,82.376043 C 681.01244,81.708505 681.28161,76.107653 682.28042,74.624229 C 683.27912,73.140873 686.13308,71.360612 687.13185,71.175177 C 688.13068,70.989794 687.93916,70.619136 687.93916,70.285379 C 687.93916,69.95165 687.78546,69.209626 687.51651,69.098343 C 687.24765,68.987127 686.19822,69.654757 685.85251,69.728899 C 685.5068,69.803078 683.85489,69.914575 683.62435,69.543728 C 683.3939,69.172775 686.14108,63.906545 685.64166,63.276177 C 685.14247,62.645555 684.79657,62.015066 684.60452,62.015066 C 684.59957,62.015066 684.59434,62.014895 684.58932,62.015066 z M 676.42414,68.978694 C 676.73261,68.974532 676.94176,69.006232 676.94176,69.006232 C 677.11748,69.40176 676.29813,70.644697 675.53706,70.814323 C 674.77614,70.983791 673.01974,73.4706 672.96126,72.905408 C 672.90256,72.340212 673.66355,70.476005 674.54169,69.628234 C 675.09392,69.095034 675.90135,68.985752 676.42414,68.978694 z " + id="path19556" + sodipodi:nodetypes="ccccsccsccsccccccsssccsccscccc" /> + <path + style="fill:url(#linearGradient19586);fill-opacity:1" + d="M 666.16392,81.423003 C 665.9031,81.091515 665.42361,80.435598 665.26754,80.004036 C 665.14138,79.655175 665.13889,79.611803 665.24522,79.614525 C 665.37457,79.61782 665.53249,79.582317 665.71495,79.414066 C 666.12829,79.032933 666.46122,78.479561 666.36093,78.193327 C 666.34151,78.137933 666.28178,78.082005 666.19489,78.037937 C 665.68311,77.778228 663.68597,75.546616 663.20752,74.699847 C 663.14869,74.595715 663.09317,74.467102 663.08417,74.414107 L 663.06779,74.317713 L 663.5356,74.095961 C 663.79289,73.973979 664.43521,73.676485 664.96298,73.434854 C 666.54934,72.708572 666.40066,72.802712 667.30233,71.953513 C 668.1459,71.159029 668.45291,70.912676 668.63209,70.886525 C 669.29678,70.789464 669.74333,70.680556 670.16886,70.511811 C 670.62212,70.332045 670.78417,70.203751 672.21307,68.893356 C 673.39223,67.812027 673.7656,67.48122 673.99398,67.315529 C 674.36671,67.045079 674.85595,66.975476 676.21407,66.999677 C 677.02172,67.014072 677.16671,67.009258 677.3026,66.963628 C 677.54873,66.881043 677.75482,66.732936 678.04247,66.432002 C 678.18878,66.278881 678.48874,66.009285 678.70903,65.832881 C 679.56399,65.148239 681.38883,63.831519 682.2969,63.244071 C 682.83405,62.896574 682.83553,62.895428 683.52111,62.285059 C 684.09054,61.778114 684.47641,61.493496 684.70119,61.414567 C 684.91293,61.34024 684.93449,61.341828 685.04373,61.439835 C 685.15035,61.535483 686.03394,62.9158 686.10698,63.100796 C 686.24105,63.440395 686.31503,63.800609 686.17623,64.085306 C 685.91549,64.62008 685.53887,65.389083 685.37514,65.683372 C 684.01271,68.277198 683.57638,69.315608 683.75865,69.53045 C 683.9225,69.723542 684.62451,69.838696 685.37986,69.796375 C 685.63126,69.7823 685.89265,69.757165 685.96072,69.740527 C 686.02879,69.723904 686.33237,69.592247 686.63534,69.447957 C 687.2815,69.140217 687.50858,69.064356 687.61979,69.119151 C 687.73185,69.174334 687.84105,69.402649 687.91551,69.737405 C 687.98654,70.056699 688.02311,70.630231 687.98411,70.812999 C 687.93246,71.054964 687.70898,71.202203 687.22201,71.315153 C 686.50051,71.482471 685.39489,72.093335 684.25696,72.95334 C 683.19009,73.759659 682.46764,74.510542 682.13211,75.161789 C 681.942,75.530794 681.88754,75.797654 681.7263,76.390906 C 681.60627,76.832449 680.94162,82.834448 680.93784,82.974322 C 680.93675,83.015121 681.05021,76.469849 681.04313,76.469662 C 681.03606,76.469473 680.47326,77.712213 680.46324,77.71196 C 680.45319,77.711706 679.26724,79.616271 679.14064,79.557035 C 679.01407,79.497811 680.84098,75.164632 681.29637,74.622279 C 681.72505,74.111718 682.69675,73.228218 683.92824,72.229312 C 684.68954,71.611793 685.08523,71.319382 685.27321,71.235423 C 685.34613,71.202844 685.65566,71.131984 685.96107,71.077938 C 686.71523,70.944468 686.90799,70.86055 686.9133,70.663386 C 686.91912,70.447624 686.66022,70.304493 686.31274,70.331392 C 685.85732,70.366626 685.34615,70.54459 684.72472,70.884297 C 684.05931,71.248032 683.51826,71.34764 682.8754,71.224763 C 682.69316,71.189901 682.45974,71.128317 682.35669,71.087851 C 682.13555,71.001063 682.1061,70.935662 682.16257,70.656928 C 682.28424,70.056285 682.7318,69.095497 684.09589,66.506489 C 684.52724,65.687816 684.91444,64.919869 684.95636,64.799939 C 684.99825,64.680011 685.1686,64.493296 685.20972,64.242845 C 685.27332,63.855494 684.94437,63.305418 684.69042,63.096047 C 684.1717,62.710286 684.01007,62.609168 683.74917,62.719264 C 683.27598,62.918962 682.63747,63.885861 680.89506,66.251944 C 678.10234,70.044237 677.77609,70.437338 677.00798,70.935529 C 675.9344,71.631833 674.25248,73.011563 672.89065,74.31307 C 671.94197,75.219744 671.23101,76.306587 670.94267,76.585575 C 670.75542,76.76678 669.96025,77.745288 669.68107,77.980021 C 668.26676,78.87945 667.27756,79.841733 666.4157,81.272282 L 666.26937,81.5468 L 666.16392,81.423003 z M 668.6295,76.32153 C 669.21552,76.035684 669.9402,75.554275 670.29047,75.218104 C 670.57943,74.940811 670.69555,74.663571 670.53426,74.6361 C 670.43004,74.618356 670.14725,74.769144 669.42758,75.226216 C 668.60241,75.75033 668.36243,75.871421 668.1267,75.882628 C 667.6555,75.905015 666.59584,75.72705 666.06717,75.536718 C 665.70072,75.404769 665.57392,75.331295 665.45729,75.183336 C 665.38321,75.089358 665.37887,75.065462 665.39778,74.856572 C 665.42208,74.588243 665.56219,74.28492 665.80217,73.981211 C 665.92518,73.825514 666.03467,73.73747 666.35462,73.537052 C 667.27497,72.96057 668.28907,72.478015 669.33316,72.119738 C 669.57465,72.036872 669.8005,71.958064 669.83504,71.944615 C 669.88896,71.923614 669.87183,71.906057 669.71409,71.820563 C 669.45042,71.677633 669.10707,71.548405 668.91845,71.521096 C 668.73014,71.493813 668.65946,71.521481 668.21594,71.796028 C 667.88157,72.003024 666.25388,73.091052 665.48621,73.620705 C 665.18146,73.830983 664.72391,74.14191 664.46942,74.311617 C 664.21494,74.481377 663.98901,74.640691 663.96737,74.665692 C 663.90854,74.733682 663.91479,74.906936 663.98115,75.047732 C 664.05408,75.202428 664.40387,75.635913 664.59279,75.805673 L 664.73365,75.93226 L 666.46235,76.190566 C 667.41313,76.332656 668.22595,76.451329 668.26859,76.454332 C 668.31124,76.457306 668.47365,76.397562 668.6295,76.32153 z M 668.25415,75.137027 C 668.72466,75.109689 668.82443,75.064367 669.20342,74.70609 C 669.59232,74.338446 669.98504,73.821189 670.13614,73.477602 L 670.18873,73.358048 L 670.0701,73.373682 C 670.00486,73.382261 669.32753,73.452719 668.5649,73.53022 L 667.17835,73.671095 L 667.05139,73.770438 C 666.8115,73.958127 666.48365,74.29119 666.35948,74.473383 C 666.21238,74.689172 666.19775,74.804299 666.3076,74.881267 C 666.43204,74.968454 666.84529,75.068852 667.23887,75.107529 C 667.76006,75.158734 667.84061,75.161069 668.25415,75.137027 z M 673.23289,72.884003 C 673.36857,72.793999 673.63342,72.530209 674.28656,71.834505 C 674.76864,71.320997 675.22409,70.892729 675.40078,70.786743 C 675.46833,70.746223 675.60467,70.684907 675.70377,70.650443 C 675.92776,70.572528 676.07455,70.48466 676.28578,70.302053 C 676.73011,69.917918 677.18695,69.102408 677.12911,68.796643 C 677.11207,68.706574 677.1094,68.705506 676.85795,68.688776 C 675.95769,68.628965 675.25732,68.824768 674.75202,69.277624 C 674.16986,69.799364 673.40627,71.172265 673.12421,72.204378 C 673.01269,72.612445 672.98317,72.817187 673.02395,72.899506 C 673.06635,72.985066 673.08235,72.983877 673.23289,72.884003 z M 671.57619,71.796562 C 672.34776,71.643906 672.98398,71.514265 672.99004,71.508448 C 672.99608,71.502658 673.07634,71.260586 673.16839,70.970524 C 673.54217,69.792774 673.83364,69.271727 674.26438,69.011271 C 674.90419,68.624388 676.2258,68.209742 676.95832,68.166034 C 677.22424,68.150158 677.26926,68.137257 677.51161,68.007456 C 677.8718,67.814601 678.3997,67.456604 679.12431,66.913865 C 679.45905,66.663135 679.91088,66.333048 680.12838,66.180365 C 680.68623,65.788718 680.96159,65.51887 680.96589,65.359637 C 680.96661,65.332846 680.95475,65.310646 680.93954,65.310246 C 680.92433,65.309872 680.56473,65.552198 680.14043,65.848756 C 679.71612,66.145328 679.09133,66.572036 678.75201,66.797005 C 677.56622,67.583204 677.74205,67.5241 676.57466,67.528756 C 676.06082,67.530783 675.36265,67.538481 675.02318,67.545859 C 674.42556,67.558856 674.40209,67.56187 674.28268,67.640998 C 674.21487,67.685947 673.78028,68.103582 673.31692,68.569102 C 672.3073,69.583402 671.44162,70.400928 671.10522,70.657742 C 670.9698,70.761139 670.75997,70.888754 670.63892,70.941346 C 670.39611,71.046839 670.3172,71.116109 670.21049,71.317382 C 670.13004,71.469129 670.04208,71.806968 670.03645,71.985906 C 670.03309,72.092787 670.03952,72.102793 670.10304,72.089172 C 670.14171,72.080887 670.80463,71.949217 671.57619,71.796562 z " + id="path19558" + sodipodi:nodetypes="csssssscccscsscsssssscsssscsssssssssssssssscssssccssssscscscscccccsscsssssssssssscsscccsccsccccccsssccssssscsssccssssssssssscsscsssssc" /> + <g + id="g19560" + style="fill:#002ddb;fill-opacity:1"> + <path + id="path19562" + d="M 680.83905,76.838803 C 680.83905,76.838803 679.45446,81.3797 679.41945,81.449838 C 679.22156,81.846006 678.85659,82.994032 678.98224,83.364797 C 679.13021,83.801538 679.68481,84.436589 679.94365,84.754121 C 680.2025,85.071759 680.31342,85.230473 680.23945,85.349692 C 680.16554,85.468783 679.20413,85.468783 679.20413,85.468783 C 679.20413,85.468783 678.46465,84.396936 678.31664,83.999943 C 678.16882,83.603079 678.09481,82.769436 678.24277,82.491624 C 678.39065,82.213703 680.83905,76.838803 680.83905,76.838803 z " + i:knockout="Off" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:21.03624916;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <polygon + id="polygon19564" + points="272.565,567.14 214.39,617.501 287.326,574.086 272.565,567.14 " + a:adobe-blending-mode="lighten" + i:knockout="Off" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:17.46648407;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + transform="matrix(4.348075e-2,0,0,4.188821e-2,659.2285,53.92804)" /> + <polygon + id="polygon19566" + points="329.873,549.776 230.887,678.282 344.633,558.457 329.873,549.776 " + a:adobe-blending-mode="lighten" + i:knockout="Off" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:17.46648407;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + transform="matrix(4.348075e-2,0,0,4.188821e-2,659.2285,53.92804)" /> + <polygon + id="polygon19568" + points="337.688,594.058 262.146,713.882 355.053,601.872 337.688,594.058 " + a:adobe-blending-mode="lighten" + i:knockout="Off" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:17.46648407;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + transform="matrix(4.348075e-2,0,0,4.188821e-2,659.2285,53.92804)" /> + <polygon + id="polygon19570" + points="381.97,588.847 318.584,737.327 402.809,593.19 381.97,588.847 " + a:adobe-blending-mode="lighten" + i:knockout="Off" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:17.46648407;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + transform="matrix(4.348075e-2,0,0,4.188821e-2,659.2285,53.92804)" /> + <polygon + id="polygon19572" + points="407.151,627.921 401.94,763.375 424.515,629.658 407.151,627.921 " + a:adobe-blending-mode="lighten" + i:knockout="Off" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:17.46648407;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + transform="matrix(4.348075e-2,0,0,4.188821e-2,659.2285,53.92804)" /> + </g> + <path + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#002ddb;stroke-width:0.46467122;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 665.61116,79.917164 C 666.44697,82.39457 673.07693,87.947061 680.79385,85.431014 C 680.71584,83.40215 681.5919,77.986014 681.41239,76.464632" + id="path19574" + sodipodi:nodetypes="ccc" /> + </g> + </g> + <g + transform="matrix(1.00233,0,0,0.999977,-124.35657,-283.91977)" + id="g19588" + inkscape:export-filename="/home/lando/Projects/Amarok/icons/32/amarok_settings_indicator.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <rect + inkscape:export-ydpi="395.48178" + inkscape:export-xdpi="395.48178" + inkscape:export-filename="/home/lando/Projects/Amarok/Setting-dialog/amarok_settings_indicator.png" + ry="2.6998968" + rx="2.0120614" + y="819.315" + x="1257.8357" + height="31.220118" + width="31.146786" + id="rect19590" + style="fill:url(#linearGradient19618);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient19620);stroke-width:0.77971518;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;display:inline" /> + <rect + inkscape:export-ydpi="395.48178" + inkscape:export-xdpi="395.48178" + inkscape:export-filename="/home/lando/Projects/Amarok/Setting-dialog/amarok_settings_indicator.png" + ry="0.88321018" + rx="0.88113678" + y="822.23187" + x="1260.3097" + height="25.386372" + width="26.198996" + id="rect19592" + style="fill:url(#linearGradient19622);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient19624);stroke-width:0.47538957;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;display:inline" /> + <g + style="display:inline" + id="g19594" + transform="matrix(1.076035,0,0,1.076025,544.8993,756.7895)" + inkscape:export-filename="/home/lando/Projects/Amarok/Setting-dialog/amarok_settings_indicator.png" + inkscape:export-xdpi="395.48178" + inkscape:export-ydpi="395.48178"> + <path + transform="matrix(0.116367,-0.126279,0.138815,0.105858,2044.201,-572.7461)" + d="M -7824.3296 -3285.1472 A 25.97311 5.306335 0 1 1 -7876.2758,-3285.1472 A 25.97311 5.306335 0 1 1 -7824.3296 -3285.1472 z" + sodipodi:ry="5.306335" + sodipodi:rx="25.97311" + sodipodi:cy="-3285.1472" + sodipodi:cx="-7850.3027" + id="path19596" + style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:21.03624916;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0.68131869" + sodipodi:type="arc" /> + <path + style="fill:url(#linearGradient19626);fill-opacity:1" + d="M 684.58932,62.015066 C 683.95163,62.03676 682.73778,63.425764 682.28042,63.646529 C 681.81946,63.869109 678.16982,66.391273 677.82409,66.836327 C 677.47838,67.281337 677.01748,67.46692 676.51816,67.429844 C 676.01883,67.392834 674.44026,67.377225 673.90249,67.673903 C 673.36463,67.970636 670.90988,70.397011 670.29527,70.693715 C 669.68068,70.990341 668.87865,71.141876 668.49449,71.178979 C 668.11043,71.216028 666.83809,72.584967 666.49238,72.844636 C 666.14667,73.10429 663.41458,74.2451 663.03046,74.467545 C 662.64631,74.690071 665.83291,77.807344 666.29388,77.881456 C 666.75485,77.955728 665.97647,79.643959 665.08957,79.643959 C 664.99189,79.643959 665.88202,82.441727 670.53651,84.906802 C 674.56037,87.037898 678.82957,86.094974 680.41507,85.578197 C 681.53629,85.212648 680.74985,85.010148 680.78834,84.416789 C 680.82666,83.823378 680.8587,83.043556 680.93555,82.376043 C 681.01244,81.708505 681.28161,76.107653 682.28042,74.624229 C 683.27912,73.140873 686.13308,71.360612 687.13185,71.175177 C 688.13068,70.989794 687.93916,70.619136 687.93916,70.285379 C 687.93916,69.95165 687.78546,69.209626 687.51651,69.098343 C 687.24765,68.987127 686.19822,69.654757 685.85251,69.728899 C 685.5068,69.803078 683.85489,69.914575 683.62435,69.543728 C 683.3939,69.172775 686.14108,63.906545 685.64166,63.276177 C 685.14247,62.645555 684.79657,62.015066 684.60452,62.015066 C 684.59957,62.015066 684.59434,62.014895 684.58932,62.015066 z M 676.42414,68.978694 C 676.73261,68.974532 676.94176,69.006232 676.94176,69.006232 C 677.11748,69.40176 676.29813,70.644697 675.53706,70.814323 C 674.77614,70.983791 673.01974,73.4706 672.96126,72.905408 C 672.90256,72.340212 673.66355,70.476005 674.54169,69.628234 C 675.09392,69.095034 675.90135,68.985752 676.42414,68.978694 z " + id="path19598" + sodipodi:nodetypes="ccccsccsccsccccccsssccsccscccc" /> + <path + style="fill:url(#linearGradient19628);fill-opacity:1" + d="M 666.16392,81.423003 C 665.9031,81.091515 665.42361,80.435598 665.26754,80.004036 C 665.14138,79.655175 665.13889,79.611803 665.24522,79.614525 C 665.37457,79.61782 665.53249,79.582317 665.71495,79.414066 C 666.12829,79.032933 666.46122,78.479561 666.36093,78.193327 C 666.34151,78.137933 666.28178,78.082005 666.19489,78.037937 C 665.68311,77.778228 663.68597,75.546616 663.20752,74.699847 C 663.14869,74.595715 663.09317,74.467102 663.08417,74.414107 L 663.06779,74.317713 L 663.5356,74.095961 C 663.79289,73.973979 664.43521,73.676485 664.96298,73.434854 C 666.54934,72.708572 666.40066,72.802712 667.30233,71.953513 C 668.1459,71.159029 668.45291,70.912676 668.63209,70.886525 C 669.29678,70.789464 669.74333,70.680556 670.16886,70.511811 C 670.62212,70.332045 670.78417,70.203751 672.21307,68.893356 C 673.39223,67.812027 673.7656,67.48122 673.99398,67.315529 C 674.36671,67.045079 674.85595,66.975476 676.21407,66.999677 C 677.02172,67.014072 677.16671,67.009258 677.3026,66.963628 C 677.54873,66.881043 677.75482,66.732936 678.04247,66.432002 C 678.18878,66.278881 678.48874,66.009285 678.70903,65.832881 C 679.56399,65.148239 681.38883,63.831519 682.2969,63.244071 C 682.83405,62.896574 682.83553,62.895428 683.52111,62.285059 C 684.09054,61.778114 684.47641,61.493496 684.70119,61.414567 C 684.91293,61.34024 684.93449,61.341828 685.04373,61.439835 C 685.15035,61.535483 686.03394,62.9158 686.10698,63.100796 C 686.24105,63.440395 686.31503,63.800609 686.17623,64.085306 C 685.91549,64.62008 685.53887,65.389083 685.37514,65.683372 C 684.01271,68.277198 683.57638,69.315608 683.75865,69.53045 C 683.9225,69.723542 684.62451,69.838696 685.37986,69.796375 C 685.63126,69.7823 685.89265,69.757165 685.96072,69.740527 C 686.02879,69.723904 686.33237,69.592247 686.63534,69.447957 C 687.2815,69.140217 687.50858,69.064356 687.61979,69.119151 C 687.73185,69.174334 687.84105,69.402649 687.91551,69.737405 C 687.98654,70.056699 688.02311,70.630231 687.98411,70.812999 C 687.93246,71.054964 687.70898,71.202203 687.22201,71.315153 C 686.50051,71.482471 685.39489,72.093335 684.25696,72.95334 C 683.19009,73.759659 682.46764,74.510542 682.13211,75.161789 C 681.942,75.530794 681.88754,75.797654 681.7263,76.390906 C 681.60627,76.832449 680.94162,82.834448 680.93784,82.974322 C 680.93675,83.015121 681.05021,76.469849 681.04313,76.469662 C 681.03606,76.469473 680.47326,77.712213 680.46324,77.71196 C 680.45319,77.711706 679.26724,79.616271 679.14064,79.557035 C 679.01407,79.497811 680.84098,75.164632 681.29637,74.622279 C 681.72505,74.111718 682.69675,73.228218 683.92824,72.229312 C 684.68954,71.611793 685.08523,71.319382 685.27321,71.235423 C 685.34613,71.202844 685.65566,71.131984 685.96107,71.077938 C 686.71523,70.944468 686.90799,70.86055 686.9133,70.663386 C 686.91912,70.447624 686.66022,70.304493 686.31274,70.331392 C 685.85732,70.366626 685.34615,70.54459 684.72472,70.884297 C 684.05931,71.248032 683.51826,71.34764 682.8754,71.224763 C 682.69316,71.189901 682.45974,71.128317 682.35669,71.087851 C 682.13555,71.001063 682.1061,70.935662 682.16257,70.656928 C 682.28424,70.056285 682.7318,69.095497 684.09589,66.506489 C 684.52724,65.687816 684.91444,64.919869 684.95636,64.799939 C 684.99825,64.680011 685.1686,64.493296 685.20972,64.242845 C 685.27332,63.855494 684.94437,63.305418 684.69042,63.096047 C 684.1717,62.710286 684.01007,62.609168 683.74917,62.719264 C 683.27598,62.918962 682.63747,63.885861 680.89506,66.251944 C 678.10234,70.044237 677.77609,70.437338 677.00798,70.935529 C 675.9344,71.631833 674.25248,73.011563 672.89065,74.31307 C 671.94197,75.219744 671.23101,76.306587 670.94267,76.585575 C 670.75542,76.76678 669.96025,77.745288 669.68107,77.980021 C 668.26676,78.87945 667.27756,79.841733 666.4157,81.272282 L 666.26937,81.5468 L 666.16392,81.423003 z M 668.6295,76.32153 C 669.21552,76.035684 669.9402,75.554275 670.29047,75.218104 C 670.57943,74.940811 670.69555,74.663571 670.53426,74.6361 C 670.43004,74.618356 670.14725,74.769144 669.42758,75.226216 C 668.60241,75.75033 668.36243,75.871421 668.1267,75.882628 C 667.6555,75.905015 666.59584,75.72705 666.06717,75.536718 C 665.70072,75.404769 665.57392,75.331295 665.45729,75.183336 C 665.38321,75.089358 665.37887,75.065462 665.39778,74.856572 C 665.42208,74.588243 665.56219,74.28492 665.80217,73.981211 C 665.92518,73.825514 666.03467,73.73747 666.35462,73.537052 C 667.27497,72.96057 668.28907,72.478015 669.33316,72.119738 C 669.57465,72.036872 669.8005,71.958064 669.83504,71.944615 C 669.88896,71.923614 669.87183,71.906057 669.71409,71.820563 C 669.45042,71.677633 669.10707,71.548405 668.91845,71.521096 C 668.73014,71.493813 668.65946,71.521481 668.21594,71.796028 C 667.88157,72.003024 666.25388,73.091052 665.48621,73.620705 C 665.18146,73.830983 664.72391,74.14191 664.46942,74.311617 C 664.21494,74.481377 663.98901,74.640691 663.96737,74.665692 C 663.90854,74.733682 663.91479,74.906936 663.98115,75.047732 C 664.05408,75.202428 664.40387,75.635913 664.59279,75.805673 L 664.73365,75.93226 L 666.46235,76.190566 C 667.41313,76.332656 668.22595,76.451329 668.26859,76.454332 C 668.31124,76.457306 668.47365,76.397562 668.6295,76.32153 z M 668.25415,75.137027 C 668.72466,75.109689 668.82443,75.064367 669.20342,74.70609 C 669.59232,74.338446 669.98504,73.821189 670.13614,73.477602 L 670.18873,73.358048 L 670.0701,73.373682 C 670.00486,73.382261 669.32753,73.452719 668.5649,73.53022 L 667.17835,73.671095 L 667.05139,73.770438 C 666.8115,73.958127 666.48365,74.29119 666.35948,74.473383 C 666.21238,74.689172 666.19775,74.804299 666.3076,74.881267 C 666.43204,74.968454 666.84529,75.068852 667.23887,75.107529 C 667.76006,75.158734 667.84061,75.161069 668.25415,75.137027 z M 673.23289,72.884003 C 673.36857,72.793999 673.63342,72.530209 674.28656,71.834505 C 674.76864,71.320997 675.22409,70.892729 675.40078,70.786743 C 675.46833,70.746223 675.60467,70.684907 675.70377,70.650443 C 675.92776,70.572528 676.07455,70.48466 676.28578,70.302053 C 676.73011,69.917918 677.18695,69.102408 677.12911,68.796643 C 677.11207,68.706574 677.1094,68.705506 676.85795,68.688776 C 675.95769,68.628965 675.25732,68.824768 674.75202,69.277624 C 674.16986,69.799364 673.40627,71.172265 673.12421,72.204378 C 673.01269,72.612445 672.98317,72.817187 673.02395,72.899506 C 673.06635,72.985066 673.08235,72.983877 673.23289,72.884003 z M 671.57619,71.796562 C 672.34776,71.643906 672.98398,71.514265 672.99004,71.508448 C 672.99608,71.502658 673.07634,71.260586 673.16839,70.970524 C 673.54217,69.792774 673.83364,69.271727 674.26438,69.011271 C 674.90419,68.624388 676.2258,68.209742 676.95832,68.166034 C 677.22424,68.150158 677.26926,68.137257 677.51161,68.007456 C 677.8718,67.814601 678.3997,67.456604 679.12431,66.913865 C 679.45905,66.663135 679.91088,66.333048 680.12838,66.180365 C 680.68623,65.788718 680.96159,65.51887 680.96589,65.359637 C 680.96661,65.332846 680.95475,65.310646 680.93954,65.310246 C 680.92433,65.309872 680.56473,65.552198 680.14043,65.848756 C 679.71612,66.145328 679.09133,66.572036 678.75201,66.797005 C 677.56622,67.583204 677.74205,67.5241 676.57466,67.528756 C 676.06082,67.530783 675.36265,67.538481 675.02318,67.545859 C 674.42556,67.558856 674.40209,67.56187 674.28268,67.640998 C 674.21487,67.685947 673.78028,68.103582 673.31692,68.569102 C 672.3073,69.583402 671.44162,70.400928 671.10522,70.657742 C 670.9698,70.761139 670.75997,70.888754 670.63892,70.941346 C 670.39611,71.046839 670.3172,71.116109 670.21049,71.317382 C 670.13004,71.469129 670.04208,71.806968 670.03645,71.985906 C 670.03309,72.092787 670.03952,72.102793 670.10304,72.089172 C 670.14171,72.080887 670.80463,71.949217 671.57619,71.796562 z " + id="path19600" + sodipodi:nodetypes="csssssscccscsscsssssscsssscsssssssssssssssscssssccssssscscscscccccsscsssssssssssscsscccsccsccccccsssccssssscsssccssssssssssscsscsssssc" /> + <g + id="g19602" + style="fill:#002ddb;fill-opacity:1"> + <path + id="path19604" + d="M 680.83905,76.838803 C 680.83905,76.838803 679.45446,81.3797 679.41945,81.449838 C 679.22156,81.846006 678.85659,82.994032 678.98224,83.364797 C 679.13021,83.801538 679.68481,84.436589 679.94365,84.754121 C 680.2025,85.071759 680.31342,85.230473 680.23945,85.349692 C 680.16554,85.468783 679.20413,85.468783 679.20413,85.468783 C 679.20413,85.468783 678.46465,84.396936 678.31664,83.999943 C 678.16882,83.603079 678.09481,82.769436 678.24277,82.491624 C 678.39065,82.213703 680.83905,76.838803 680.83905,76.838803 z " + i:knockout="Off" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:21.03624916;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <polygon + id="polygon19606" + points="272.565,567.14 214.39,617.501 287.326,574.086 272.565,567.14 " + a:adobe-blending-mode="lighten" + i:knockout="Off" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:17.46648407;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + transform="matrix(4.348075e-2,0,0,4.188821e-2,659.2285,53.92804)" /> + <polygon + id="polygon19608" + points="329.873,549.776 230.887,678.282 344.633,558.457 329.873,549.776 " + a:adobe-blending-mode="lighten" + i:knockout="Off" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:17.46648407;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + transform="matrix(4.348075e-2,0,0,4.188821e-2,659.2285,53.92804)" /> + <polygon + id="polygon19610" + points="337.688,594.058 262.146,713.882 355.053,601.872 337.688,594.058 " + a:adobe-blending-mode="lighten" + i:knockout="Off" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:17.46648407;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + transform="matrix(4.348075e-2,0,0,4.188821e-2,659.2285,53.92804)" /> + <polygon + id="polygon19612" + points="381.97,588.847 318.584,737.327 402.809,593.19 381.97,588.847 " + a:adobe-blending-mode="lighten" + i:knockout="Off" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:17.46648407;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + transform="matrix(4.348075e-2,0,0,4.188821e-2,659.2285,53.92804)" /> + <polygon + id="polygon19614" + points="407.151,627.921 401.94,763.375 424.515,629.658 407.151,627.921 " + a:adobe-blending-mode="lighten" + i:knockout="Off" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:17.46648407;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + transform="matrix(4.348075e-2,0,0,4.188821e-2,659.2285,53.92804)" /> + </g> + <path + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#002ddb;stroke-width:0.46467122;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 665.61116,79.917164 C 666.44697,82.39457 673.07693,87.947061 680.79385,85.431014 C 680.71584,83.40215 681.5919,77.986014 681.41239,76.464632" + id="path19616" + sodipodi:nodetypes="ccc" /> + </g> + </g> + <g + id="g19630" + transform="matrix(1.503494,0,0,1.499965,-706.2462,-709.37228)" + inkscape:export-filename="/home/lando/Projects/Amarok/icons/48/amarok_settings_indicator.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <rect + style="fill:url(#linearGradient19660);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient19662);stroke-width:0.77971518;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;display:inline" + id="rect19632" + width="31.146786" + height="31.220118" + x="1257.8357" + y="819.315" + rx="1.3413748" + ry="1.799932" + inkscape:export-filename="/home/lando/Projects/Amarok/Setting-dialog/amarok_settings_indicator.png" + inkscape:export-xdpi="395.48178" + inkscape:export-ydpi="395.48178" /> + <rect + style="fill:url(#linearGradient19664);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient19666);stroke-width:0.47538957;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;display:inline" + id="rect19634" + width="26.198996" + height="25.386372" + x="1260.3097" + y="822.23187" + rx="0.58742476" + ry="0.58880705" + inkscape:export-filename="/home/lando/Projects/Amarok/Setting-dialog/amarok_settings_indicator.png" + inkscape:export-xdpi="395.48178" + inkscape:export-ydpi="395.48178" /> + <g + inkscape:export-ydpi="395.48178" + inkscape:export-xdpi="395.48178" + inkscape:export-filename="/home/lando/Projects/Amarok/Setting-dialog/amarok_settings_indicator.png" + transform="matrix(1.076035,0,0,1.076025,544.8993,756.7895)" + id="g19636" + style="display:inline"> + <path + sodipodi:type="arc" + style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:21.03624916;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0.68131869" + id="path19638" + sodipodi:cx="-7850.3027" + sodipodi:cy="-3285.1472" + sodipodi:rx="25.97311" + sodipodi:ry="5.306335" + d="M -7824.3296 -3285.1472 A 25.97311 5.306335 0 1 1 -7876.2758,-3285.1472 A 25.97311 5.306335 0 1 1 -7824.3296 -3285.1472 z" + transform="matrix(0.116367,-0.126279,0.138815,0.105858,2044.201,-572.7461)" /> + <path + sodipodi:nodetypes="ccccsccsccsccccccsssccsccscccc" + id="path19640" + d="M 684.58932,62.015066 C 683.95163,62.03676 682.73778,63.425764 682.28042,63.646529 C 681.81946,63.869109 678.16982,66.391273 677.82409,66.836327 C 677.47838,67.281337 677.01748,67.46692 676.51816,67.429844 C 676.01883,67.392834 674.44026,67.377225 673.90249,67.673903 C 673.36463,67.970636 670.90988,70.397011 670.29527,70.693715 C 669.68068,70.990341 668.87865,71.141876 668.49449,71.178979 C 668.11043,71.216028 666.83809,72.584967 666.49238,72.844636 C 666.14667,73.10429 663.41458,74.2451 663.03046,74.467545 C 662.64631,74.690071 665.83291,77.807344 666.29388,77.881456 C 666.75485,77.955728 665.97647,79.643959 665.08957,79.643959 C 664.99189,79.643959 665.88202,82.441727 670.53651,84.906802 C 674.56037,87.037898 678.82957,86.094974 680.41507,85.578197 C 681.53629,85.212648 680.74985,85.010148 680.78834,84.416789 C 680.82666,83.823378 680.8587,83.043556 680.93555,82.376043 C 681.01244,81.708505 681.28161,76.107653 682.28042,74.624229 C 683.27912,73.140873 686.13308,71.360612 687.13185,71.175177 C 688.13068,70.989794 687.93916,70.619136 687.93916,70.285379 C 687.93916,69.95165 687.78546,69.209626 687.51651,69.098343 C 687.24765,68.987127 686.19822,69.654757 685.85251,69.728899 C 685.5068,69.803078 683.85489,69.914575 683.62435,69.543728 C 683.3939,69.172775 686.14108,63.906545 685.64166,63.276177 C 685.14247,62.645555 684.79657,62.015066 684.60452,62.015066 C 684.59957,62.015066 684.59434,62.014895 684.58932,62.015066 z M 676.42414,68.978694 C 676.73261,68.974532 676.94176,69.006232 676.94176,69.006232 C 677.11748,69.40176 676.29813,70.644697 675.53706,70.814323 C 674.77614,70.983791 673.01974,73.4706 672.96126,72.905408 C 672.90256,72.340212 673.66355,70.476005 674.54169,69.628234 C 675.09392,69.095034 675.90135,68.985752 676.42414,68.978694 z " + style="fill:url(#linearGradient19668);fill-opacity:1" /> + <path + sodipodi:nodetypes="csssssscccscsscsssssscsssscsssssssssssssssscssssccssssscscscscccccsscsssssssssssscsscccsccsccccccsssccssssscsssccssssssssssscsscsssssc" + id="path19642" + d="M 666.16392,81.423003 C 665.9031,81.091515 665.42361,80.435598 665.26754,80.004036 C 665.14138,79.655175 665.13889,79.611803 665.24522,79.614525 C 665.37457,79.61782 665.53249,79.582317 665.71495,79.414066 C 666.12829,79.032933 666.46122,78.479561 666.36093,78.193327 C 666.34151,78.137933 666.28178,78.082005 666.19489,78.037937 C 665.68311,77.778228 663.68597,75.546616 663.20752,74.699847 C 663.14869,74.595715 663.09317,74.467102 663.08417,74.414107 L 663.06779,74.317713 L 663.5356,74.095961 C 663.79289,73.973979 664.43521,73.676485 664.96298,73.434854 C 666.54934,72.708572 666.40066,72.802712 667.30233,71.953513 C 668.1459,71.159029 668.45291,70.912676 668.63209,70.886525 C 669.29678,70.789464 669.74333,70.680556 670.16886,70.511811 C 670.62212,70.332045 670.78417,70.203751 672.21307,68.893356 C 673.39223,67.812027 673.7656,67.48122 673.99398,67.315529 C 674.36671,67.045079 674.85595,66.975476 676.21407,66.999677 C 677.02172,67.014072 677.16671,67.009258 677.3026,66.963628 C 677.54873,66.881043 677.75482,66.732936 678.04247,66.432002 C 678.18878,66.278881 678.48874,66.009285 678.70903,65.832881 C 679.56399,65.148239 681.38883,63.831519 682.2969,63.244071 C 682.83405,62.896574 682.83553,62.895428 683.52111,62.285059 C 684.09054,61.778114 684.47641,61.493496 684.70119,61.414567 C 684.91293,61.34024 684.93449,61.341828 685.04373,61.439835 C 685.15035,61.535483 686.03394,62.9158 686.10698,63.100796 C 686.24105,63.440395 686.31503,63.800609 686.17623,64.085306 C 685.91549,64.62008 685.53887,65.389083 685.37514,65.683372 C 684.01271,68.277198 683.57638,69.315608 683.75865,69.53045 C 683.9225,69.723542 684.62451,69.838696 685.37986,69.796375 C 685.63126,69.7823 685.89265,69.757165 685.96072,69.740527 C 686.02879,69.723904 686.33237,69.592247 686.63534,69.447957 C 687.2815,69.140217 687.50858,69.064356 687.61979,69.119151 C 687.73185,69.174334 687.84105,69.402649 687.91551,69.737405 C 687.98654,70.056699 688.02311,70.630231 687.98411,70.812999 C 687.93246,71.054964 687.70898,71.202203 687.22201,71.315153 C 686.50051,71.482471 685.39489,72.093335 684.25696,72.95334 C 683.19009,73.759659 682.46764,74.510542 682.13211,75.161789 C 681.942,75.530794 681.88754,75.797654 681.7263,76.390906 C 681.60627,76.832449 680.94162,82.834448 680.93784,82.974322 C 680.93675,83.015121 681.05021,76.469849 681.04313,76.469662 C 681.03606,76.469473 680.47326,77.712213 680.46324,77.71196 C 680.45319,77.711706 679.26724,79.616271 679.14064,79.557035 C 679.01407,79.497811 680.84098,75.164632 681.29637,74.622279 C 681.72505,74.111718 682.69675,73.228218 683.92824,72.229312 C 684.68954,71.611793 685.08523,71.319382 685.27321,71.235423 C 685.34613,71.202844 685.65566,71.131984 685.96107,71.077938 C 686.71523,70.944468 686.90799,70.86055 686.9133,70.663386 C 686.91912,70.447624 686.66022,70.304493 686.31274,70.331392 C 685.85732,70.366626 685.34615,70.54459 684.72472,70.884297 C 684.05931,71.248032 683.51826,71.34764 682.8754,71.224763 C 682.69316,71.189901 682.45974,71.128317 682.35669,71.087851 C 682.13555,71.001063 682.1061,70.935662 682.16257,70.656928 C 682.28424,70.056285 682.7318,69.095497 684.09589,66.506489 C 684.52724,65.687816 684.91444,64.919869 684.95636,64.799939 C 684.99825,64.680011 685.1686,64.493296 685.20972,64.242845 C 685.27332,63.855494 684.94437,63.305418 684.69042,63.096047 C 684.1717,62.710286 684.01007,62.609168 683.74917,62.719264 C 683.27598,62.918962 682.63747,63.885861 680.89506,66.251944 C 678.10234,70.044237 677.77609,70.437338 677.00798,70.935529 C 675.9344,71.631833 674.25248,73.011563 672.89065,74.31307 C 671.94197,75.219744 671.23101,76.306587 670.94267,76.585575 C 670.75542,76.76678 669.96025,77.745288 669.68107,77.980021 C 668.26676,78.87945 667.27756,79.841733 666.4157,81.272282 L 666.26937,81.5468 L 666.16392,81.423003 z M 668.6295,76.32153 C 669.21552,76.035684 669.9402,75.554275 670.29047,75.218104 C 670.57943,74.940811 670.69555,74.663571 670.53426,74.6361 C 670.43004,74.618356 670.14725,74.769144 669.42758,75.226216 C 668.60241,75.75033 668.36243,75.871421 668.1267,75.882628 C 667.6555,75.905015 666.59584,75.72705 666.06717,75.536718 C 665.70072,75.404769 665.57392,75.331295 665.45729,75.183336 C 665.38321,75.089358 665.37887,75.065462 665.39778,74.856572 C 665.42208,74.588243 665.56219,74.28492 665.80217,73.981211 C 665.92518,73.825514 666.03467,73.73747 666.35462,73.537052 C 667.27497,72.96057 668.28907,72.478015 669.33316,72.119738 C 669.57465,72.036872 669.8005,71.958064 669.83504,71.944615 C 669.88896,71.923614 669.87183,71.906057 669.71409,71.820563 C 669.45042,71.677633 669.10707,71.548405 668.91845,71.521096 C 668.73014,71.493813 668.65946,71.521481 668.21594,71.796028 C 667.88157,72.003024 666.25388,73.091052 665.48621,73.620705 C 665.18146,73.830983 664.72391,74.14191 664.46942,74.311617 C 664.21494,74.481377 663.98901,74.640691 663.96737,74.665692 C 663.90854,74.733682 663.91479,74.906936 663.98115,75.047732 C 664.05408,75.202428 664.40387,75.635913 664.59279,75.805673 L 664.73365,75.93226 L 666.46235,76.190566 C 667.41313,76.332656 668.22595,76.451329 668.26859,76.454332 C 668.31124,76.457306 668.47365,76.397562 668.6295,76.32153 z M 668.25415,75.137027 C 668.72466,75.109689 668.82443,75.064367 669.20342,74.70609 C 669.59232,74.338446 669.98504,73.821189 670.13614,73.477602 L 670.18873,73.358048 L 670.0701,73.373682 C 670.00486,73.382261 669.32753,73.452719 668.5649,73.53022 L 667.17835,73.671095 L 667.05139,73.770438 C 666.8115,73.958127 666.48365,74.29119 666.35948,74.473383 C 666.21238,74.689172 666.19775,74.804299 666.3076,74.881267 C 666.43204,74.968454 666.84529,75.068852 667.23887,75.107529 C 667.76006,75.158734 667.84061,75.161069 668.25415,75.137027 z M 673.23289,72.884003 C 673.36857,72.793999 673.63342,72.530209 674.28656,71.834505 C 674.76864,71.320997 675.22409,70.892729 675.40078,70.786743 C 675.46833,70.746223 675.60467,70.684907 675.70377,70.650443 C 675.92776,70.572528 676.07455,70.48466 676.28578,70.302053 C 676.73011,69.917918 677.18695,69.102408 677.12911,68.796643 C 677.11207,68.706574 677.1094,68.705506 676.85795,68.688776 C 675.95769,68.628965 675.25732,68.824768 674.75202,69.277624 C 674.16986,69.799364 673.40627,71.172265 673.12421,72.204378 C 673.01269,72.612445 672.98317,72.817187 673.02395,72.899506 C 673.06635,72.985066 673.08235,72.983877 673.23289,72.884003 z M 671.57619,71.796562 C 672.34776,71.643906 672.98398,71.514265 672.99004,71.508448 C 672.99608,71.502658 673.07634,71.260586 673.16839,70.970524 C 673.54217,69.792774 673.83364,69.271727 674.26438,69.011271 C 674.90419,68.624388 676.2258,68.209742 676.95832,68.166034 C 677.22424,68.150158 677.26926,68.137257 677.51161,68.007456 C 677.8718,67.814601 678.3997,67.456604 679.12431,66.913865 C 679.45905,66.663135 679.91088,66.333048 680.12838,66.180365 C 680.68623,65.788718 680.96159,65.51887 680.96589,65.359637 C 680.96661,65.332846 680.95475,65.310646 680.93954,65.310246 C 680.92433,65.309872 680.56473,65.552198 680.14043,65.848756 C 679.71612,66.145328 679.09133,66.572036 678.75201,66.797005 C 677.56622,67.583204 677.74205,67.5241 676.57466,67.528756 C 676.06082,67.530783 675.36265,67.538481 675.02318,67.545859 C 674.42556,67.558856 674.40209,67.56187 674.28268,67.640998 C 674.21487,67.685947 673.78028,68.103582 673.31692,68.569102 C 672.3073,69.583402 671.44162,70.400928 671.10522,70.657742 C 670.9698,70.761139 670.75997,70.888754 670.63892,70.941346 C 670.39611,71.046839 670.3172,71.116109 670.21049,71.317382 C 670.13004,71.469129 670.04208,71.806968 670.03645,71.985906 C 670.03309,72.092787 670.03952,72.102793 670.10304,72.089172 C 670.14171,72.080887 670.80463,71.949217 671.57619,71.796562 z " + style="fill:url(#linearGradient19670);fill-opacity:1" /> + <g + style="fill:#002ddb;fill-opacity:1" + id="g19644"> + <path + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:21.03624916;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + i:knockout="Off" + d="M 680.83905,76.838803 C 680.83905,76.838803 679.45446,81.3797 679.41945,81.449838 C 679.22156,81.846006 678.85659,82.994032 678.98224,83.364797 C 679.13021,83.801538 679.68481,84.436589 679.94365,84.754121 C 680.2025,85.071759 680.31342,85.230473 680.23945,85.349692 C 680.16554,85.468783 679.20413,85.468783 679.20413,85.468783 C 679.20413,85.468783 678.46465,84.396936 678.31664,83.999943 C 678.16882,83.603079 678.09481,82.769436 678.24277,82.491624 C 678.39065,82.213703 680.83905,76.838803 680.83905,76.838803 z " + id="path19646" /> + <polygon + transform="matrix(4.348075e-2,0,0,4.188821e-2,659.2285,53.92804)" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:17.46648407;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + i:knockout="Off" + a:adobe-blending-mode="lighten" + points="272.565,567.14 214.39,617.501 287.326,574.086 272.565,567.14 " + id="polygon19648" /> + <polygon + transform="matrix(4.348075e-2,0,0,4.188821e-2,659.2285,53.92804)" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:17.46648407;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + i:knockout="Off" + a:adobe-blending-mode="lighten" + points="329.873,549.776 230.887,678.282 344.633,558.457 329.873,549.776 " + id="polygon19650" /> + <polygon + transform="matrix(4.348075e-2,0,0,4.188821e-2,659.2285,53.92804)" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:17.46648407;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + i:knockout="Off" + a:adobe-blending-mode="lighten" + points="337.688,594.058 262.146,713.882 355.053,601.872 337.688,594.058 " + id="polygon19652" /> + <polygon + transform="matrix(4.348075e-2,0,0,4.188821e-2,659.2285,53.92804)" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:17.46648407;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + i:knockout="Off" + a:adobe-blending-mode="lighten" + points="381.97,588.847 318.584,737.327 402.809,593.19 381.97,588.847 " + id="polygon19654" /> + <polygon + transform="matrix(4.348075e-2,0,0,4.188821e-2,659.2285,53.92804)" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:17.46648407;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + i:knockout="Off" + a:adobe-blending-mode="lighten" + points="407.151,627.921 401.94,763.375 424.515,629.658 407.151,627.921 " + id="polygon19656" /> + </g> + <path + sodipodi:nodetypes="ccc" + id="path19658" + d="M 665.61116,79.917164 C 666.44697,82.39457 673.07693,87.947061 680.79385,85.431014 C 680.71584,83.40215 681.5919,77.986014 681.41239,76.464632" + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#002ddb;stroke-width:0.46467122;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + </g> + <g + transform="matrix(2.004658,0,0,1.999953,-1278.874,-1134.8247)" + id="g19672" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/64x64/actions/amarok_settings_indicator.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <rect + inkscape:export-ydpi="395.48178" + inkscape:export-xdpi="395.48178" + inkscape:export-filename="/home/lando/Projects/Amarok/Setting-dialog/amarok_settings_indicator.png" + ry="1.3499491" + rx="1.0060312" + y="819.315" + x="1257.8357" + height="31.220118" + width="31.146786" + id="rect19674" + style="fill:url(#linearGradient19702);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient19704);stroke-width:0.77971518;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;display:inline" /> + <rect + inkscape:export-ydpi="395.48178" + inkscape:export-xdpi="395.48178" + inkscape:export-filename="/home/lando/Projects/Amarok/Setting-dialog/amarok_settings_indicator.png" + ry="0.44160533" + rx="0.4405686" + y="822.23187" + x="1260.3097" + height="25.386372" + width="26.198996" + id="rect19676" + style="fill:url(#linearGradient19706);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient19708);stroke-width:0.47538957;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;display:inline" /> + <g + style="display:inline" + id="g19678" + transform="matrix(1.076035,0,0,1.076025,544.8993,756.7895)" + inkscape:export-filename="/home/lando/Projects/Amarok/Setting-dialog/amarok_settings_indicator.png" + inkscape:export-xdpi="395.48178" + inkscape:export-ydpi="395.48178"> + <path + transform="matrix(0.116367,-0.126279,0.138815,0.105858,2044.201,-572.7461)" + d="M -7824.3296 -3285.1472 A 25.97311 5.306335 0 1 1 -7876.2758,-3285.1472 A 25.97311 5.306335 0 1 1 -7824.3296 -3285.1472 z" + sodipodi:ry="5.306335" + sodipodi:rx="25.97311" + sodipodi:cy="-3285.1472" + sodipodi:cx="-7850.3027" + id="path19680" + style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:21.03624916;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0.68131869" + sodipodi:type="arc" /> + <path + style="fill:url(#linearGradient19710);fill-opacity:1" + d="M 684.58932,62.015066 C 683.95163,62.03676 682.73778,63.425764 682.28042,63.646529 C 681.81946,63.869109 678.16982,66.391273 677.82409,66.836327 C 677.47838,67.281337 677.01748,67.46692 676.51816,67.429844 C 676.01883,67.392834 674.44026,67.377225 673.90249,67.673903 C 673.36463,67.970636 670.90988,70.397011 670.29527,70.693715 C 669.68068,70.990341 668.87865,71.141876 668.49449,71.178979 C 668.11043,71.216028 666.83809,72.584967 666.49238,72.844636 C 666.14667,73.10429 663.41458,74.2451 663.03046,74.467545 C 662.64631,74.690071 665.83291,77.807344 666.29388,77.881456 C 666.75485,77.955728 665.97647,79.643959 665.08957,79.643959 C 664.99189,79.643959 665.88202,82.441727 670.53651,84.906802 C 674.56037,87.037898 678.82957,86.094974 680.41507,85.578197 C 681.53629,85.212648 680.74985,85.010148 680.78834,84.416789 C 680.82666,83.823378 680.8587,83.043556 680.93555,82.376043 C 681.01244,81.708505 681.28161,76.107653 682.28042,74.624229 C 683.27912,73.140873 686.13308,71.360612 687.13185,71.175177 C 688.13068,70.989794 687.93916,70.619136 687.93916,70.285379 C 687.93916,69.95165 687.78546,69.209626 687.51651,69.098343 C 687.24765,68.987127 686.19822,69.654757 685.85251,69.728899 C 685.5068,69.803078 683.85489,69.914575 683.62435,69.543728 C 683.3939,69.172775 686.14108,63.906545 685.64166,63.276177 C 685.14247,62.645555 684.79657,62.015066 684.60452,62.015066 C 684.59957,62.015066 684.59434,62.014895 684.58932,62.015066 z M 676.42414,68.978694 C 676.73261,68.974532 676.94176,69.006232 676.94176,69.006232 C 677.11748,69.40176 676.29813,70.644697 675.53706,70.814323 C 674.77614,70.983791 673.01974,73.4706 672.96126,72.905408 C 672.90256,72.340212 673.66355,70.476005 674.54169,69.628234 C 675.09392,69.095034 675.90135,68.985752 676.42414,68.978694 z " + id="path19682" + sodipodi:nodetypes="ccccsccsccsccccccsssccsccscccc" /> + <path + style="fill:url(#linearGradient19712);fill-opacity:1" + d="M 666.16392,81.423003 C 665.9031,81.091515 665.42361,80.435598 665.26754,80.004036 C 665.14138,79.655175 665.13889,79.611803 665.24522,79.614525 C 665.37457,79.61782 665.53249,79.582317 665.71495,79.414066 C 666.12829,79.032933 666.46122,78.479561 666.36093,78.193327 C 666.34151,78.137933 666.28178,78.082005 666.19489,78.037937 C 665.68311,77.778228 663.68597,75.546616 663.20752,74.699847 C 663.14869,74.595715 663.09317,74.467102 663.08417,74.414107 L 663.06779,74.317713 L 663.5356,74.095961 C 663.79289,73.973979 664.43521,73.676485 664.96298,73.434854 C 666.54934,72.708572 666.40066,72.802712 667.30233,71.953513 C 668.1459,71.159029 668.45291,70.912676 668.63209,70.886525 C 669.29678,70.789464 669.74333,70.680556 670.16886,70.511811 C 670.62212,70.332045 670.78417,70.203751 672.21307,68.893356 C 673.39223,67.812027 673.7656,67.48122 673.99398,67.315529 C 674.36671,67.045079 674.85595,66.975476 676.21407,66.999677 C 677.02172,67.014072 677.16671,67.009258 677.3026,66.963628 C 677.54873,66.881043 677.75482,66.732936 678.04247,66.432002 C 678.18878,66.278881 678.48874,66.009285 678.70903,65.832881 C 679.56399,65.148239 681.38883,63.831519 682.2969,63.244071 C 682.83405,62.896574 682.83553,62.895428 683.52111,62.285059 C 684.09054,61.778114 684.47641,61.493496 684.70119,61.414567 C 684.91293,61.34024 684.93449,61.341828 685.04373,61.439835 C 685.15035,61.535483 686.03394,62.9158 686.10698,63.100796 C 686.24105,63.440395 686.31503,63.800609 686.17623,64.085306 C 685.91549,64.62008 685.53887,65.389083 685.37514,65.683372 C 684.01271,68.277198 683.57638,69.315608 683.75865,69.53045 C 683.9225,69.723542 684.62451,69.838696 685.37986,69.796375 C 685.63126,69.7823 685.89265,69.757165 685.96072,69.740527 C 686.02879,69.723904 686.33237,69.592247 686.63534,69.447957 C 687.2815,69.140217 687.50858,69.064356 687.61979,69.119151 C 687.73185,69.174334 687.84105,69.402649 687.91551,69.737405 C 687.98654,70.056699 688.02311,70.630231 687.98411,70.812999 C 687.93246,71.054964 687.70898,71.202203 687.22201,71.315153 C 686.50051,71.482471 685.39489,72.093335 684.25696,72.95334 C 683.19009,73.759659 682.46764,74.510542 682.13211,75.161789 C 681.942,75.530794 681.88754,75.797654 681.7263,76.390906 C 681.60627,76.832449 680.94162,82.834448 680.93784,82.974322 C 680.93675,83.015121 681.05021,76.469849 681.04313,76.469662 C 681.03606,76.469473 680.47326,77.712213 680.46324,77.71196 C 680.45319,77.711706 679.26724,79.616271 679.14064,79.557035 C 679.01407,79.497811 680.84098,75.164632 681.29637,74.622279 C 681.72505,74.111718 682.69675,73.228218 683.92824,72.229312 C 684.68954,71.611793 685.08523,71.319382 685.27321,71.235423 C 685.34613,71.202844 685.65566,71.131984 685.96107,71.077938 C 686.71523,70.944468 686.90799,70.86055 686.9133,70.663386 C 686.91912,70.447624 686.66022,70.304493 686.31274,70.331392 C 685.85732,70.366626 685.34615,70.54459 684.72472,70.884297 C 684.05931,71.248032 683.51826,71.34764 682.8754,71.224763 C 682.69316,71.189901 682.45974,71.128317 682.35669,71.087851 C 682.13555,71.001063 682.1061,70.935662 682.16257,70.656928 C 682.28424,70.056285 682.7318,69.095497 684.09589,66.506489 C 684.52724,65.687816 684.91444,64.919869 684.95636,64.799939 C 684.99825,64.680011 685.1686,64.493296 685.20972,64.242845 C 685.27332,63.855494 684.94437,63.305418 684.69042,63.096047 C 684.1717,62.710286 684.01007,62.609168 683.74917,62.719264 C 683.27598,62.918962 682.63747,63.885861 680.89506,66.251944 C 678.10234,70.044237 677.77609,70.437338 677.00798,70.935529 C 675.9344,71.631833 674.25248,73.011563 672.89065,74.31307 C 671.94197,75.219744 671.23101,76.306587 670.94267,76.585575 C 670.75542,76.76678 669.96025,77.745288 669.68107,77.980021 C 668.26676,78.87945 667.27756,79.841733 666.4157,81.272282 L 666.26937,81.5468 L 666.16392,81.423003 z M 668.6295,76.32153 C 669.21552,76.035684 669.9402,75.554275 670.29047,75.218104 C 670.57943,74.940811 670.69555,74.663571 670.53426,74.6361 C 670.43004,74.618356 670.14725,74.769144 669.42758,75.226216 C 668.60241,75.75033 668.36243,75.871421 668.1267,75.882628 C 667.6555,75.905015 666.59584,75.72705 666.06717,75.536718 C 665.70072,75.404769 665.57392,75.331295 665.45729,75.183336 C 665.38321,75.089358 665.37887,75.065462 665.39778,74.856572 C 665.42208,74.588243 665.56219,74.28492 665.80217,73.981211 C 665.92518,73.825514 666.03467,73.73747 666.35462,73.537052 C 667.27497,72.96057 668.28907,72.478015 669.33316,72.119738 C 669.57465,72.036872 669.8005,71.958064 669.83504,71.944615 C 669.88896,71.923614 669.87183,71.906057 669.71409,71.820563 C 669.45042,71.677633 669.10707,71.548405 668.91845,71.521096 C 668.73014,71.493813 668.65946,71.521481 668.21594,71.796028 C 667.88157,72.003024 666.25388,73.091052 665.48621,73.620705 C 665.18146,73.830983 664.72391,74.14191 664.46942,74.311617 C 664.21494,74.481377 663.98901,74.640691 663.96737,74.665692 C 663.90854,74.733682 663.91479,74.906936 663.98115,75.047732 C 664.05408,75.202428 664.40387,75.635913 664.59279,75.805673 L 664.73365,75.93226 L 666.46235,76.190566 C 667.41313,76.332656 668.22595,76.451329 668.26859,76.454332 C 668.31124,76.457306 668.47365,76.397562 668.6295,76.32153 z M 668.25415,75.137027 C 668.72466,75.109689 668.82443,75.064367 669.20342,74.70609 C 669.59232,74.338446 669.98504,73.821189 670.13614,73.477602 L 670.18873,73.358048 L 670.0701,73.373682 C 670.00486,73.382261 669.32753,73.452719 668.5649,73.53022 L 667.17835,73.671095 L 667.05139,73.770438 C 666.8115,73.958127 666.48365,74.29119 666.35948,74.473383 C 666.21238,74.689172 666.19775,74.804299 666.3076,74.881267 C 666.43204,74.968454 666.84529,75.068852 667.23887,75.107529 C 667.76006,75.158734 667.84061,75.161069 668.25415,75.137027 z M 673.23289,72.884003 C 673.36857,72.793999 673.63342,72.530209 674.28656,71.834505 C 674.76864,71.320997 675.22409,70.892729 675.40078,70.786743 C 675.46833,70.746223 675.60467,70.684907 675.70377,70.650443 C 675.92776,70.572528 676.07455,70.48466 676.28578,70.302053 C 676.73011,69.917918 677.18695,69.102408 677.12911,68.796643 C 677.11207,68.706574 677.1094,68.705506 676.85795,68.688776 C 675.95769,68.628965 675.25732,68.824768 674.75202,69.277624 C 674.16986,69.799364 673.40627,71.172265 673.12421,72.204378 C 673.01269,72.612445 672.98317,72.817187 673.02395,72.899506 C 673.06635,72.985066 673.08235,72.983877 673.23289,72.884003 z M 671.57619,71.796562 C 672.34776,71.643906 672.98398,71.514265 672.99004,71.508448 C 672.99608,71.502658 673.07634,71.260586 673.16839,70.970524 C 673.54217,69.792774 673.83364,69.271727 674.26438,69.011271 C 674.90419,68.624388 676.2258,68.209742 676.95832,68.166034 C 677.22424,68.150158 677.26926,68.137257 677.51161,68.007456 C 677.8718,67.814601 678.3997,67.456604 679.12431,66.913865 C 679.45905,66.663135 679.91088,66.333048 680.12838,66.180365 C 680.68623,65.788718 680.96159,65.51887 680.96589,65.359637 C 680.96661,65.332846 680.95475,65.310646 680.93954,65.310246 C 680.92433,65.309872 680.56473,65.552198 680.14043,65.848756 C 679.71612,66.145328 679.09133,66.572036 678.75201,66.797005 C 677.56622,67.583204 677.74205,67.5241 676.57466,67.528756 C 676.06082,67.530783 675.36265,67.538481 675.02318,67.545859 C 674.42556,67.558856 674.40209,67.56187 674.28268,67.640998 C 674.21487,67.685947 673.78028,68.103582 673.31692,68.569102 C 672.3073,69.583402 671.44162,70.400928 671.10522,70.657742 C 670.9698,70.761139 670.75997,70.888754 670.63892,70.941346 C 670.39611,71.046839 670.3172,71.116109 670.21049,71.317382 C 670.13004,71.469129 670.04208,71.806968 670.03645,71.985906 C 670.03309,72.092787 670.03952,72.102793 670.10304,72.089172 C 670.14171,72.080887 670.80463,71.949217 671.57619,71.796562 z " + id="path19684" + sodipodi:nodetypes="csssssscccscsscsssssscsssscsssssssssssssssscssssccssssscscscscccccsscsssssssssssscsscccsccsccccccsssccssssscsssccssssssssssscsscsssssc" /> + <g + id="g19686" + style="fill:#002ddb;fill-opacity:1"> + <path + id="path19688" + d="M 680.83905,76.838803 C 680.83905,76.838803 679.45446,81.3797 679.41945,81.449838 C 679.22156,81.846006 678.85659,82.994032 678.98224,83.364797 C 679.13021,83.801538 679.68481,84.436589 679.94365,84.754121 C 680.2025,85.071759 680.31342,85.230473 680.23945,85.349692 C 680.16554,85.468783 679.20413,85.468783 679.20413,85.468783 C 679.20413,85.468783 678.46465,84.396936 678.31664,83.999943 C 678.16882,83.603079 678.09481,82.769436 678.24277,82.491624 C 678.39065,82.213703 680.83905,76.838803 680.83905,76.838803 z " + i:knockout="Off" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:21.03624916;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <polygon + id="polygon19690" + points="272.565,567.14 214.39,617.501 287.326,574.086 272.565,567.14 " + a:adobe-blending-mode="lighten" + i:knockout="Off" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:17.46648407;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + transform="matrix(4.348075e-2,0,0,4.188821e-2,659.2285,53.92804)" /> + <polygon + id="polygon19692" + points="329.873,549.776 230.887,678.282 344.633,558.457 329.873,549.776 " + a:adobe-blending-mode="lighten" + i:knockout="Off" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:17.46648407;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + transform="matrix(4.348075e-2,0,0,4.188821e-2,659.2285,53.92804)" /> + <polygon + id="polygon19694" + points="337.688,594.058 262.146,713.882 355.053,601.872 337.688,594.058 " + a:adobe-blending-mode="lighten" + i:knockout="Off" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:17.46648407;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + transform="matrix(4.348075e-2,0,0,4.188821e-2,659.2285,53.92804)" /> + <polygon + id="polygon19696" + points="381.97,588.847 318.584,737.327 402.809,593.19 381.97,588.847 " + a:adobe-blending-mode="lighten" + i:knockout="Off" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:17.46648407;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + transform="matrix(4.348075e-2,0,0,4.188821e-2,659.2285,53.92804)" /> + <polygon + id="polygon19698" + points="407.151,627.921 401.94,763.375 424.515,629.658 407.151,627.921 " + a:adobe-blending-mode="lighten" + i:knockout="Off" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:17.46648407;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + transform="matrix(4.348075e-2,0,0,4.188821e-2,659.2285,53.92804)" /> + </g> + <path + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#002ddb;stroke-width:0.46467122;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 665.61116,79.917164 C 666.44697,82.39457 673.07693,87.947061 680.79385,85.431014 C 680.71584,83.40215 681.5919,77.986014 681.41239,76.464632" + id="path19700" + sodipodi:nodetypes="ccc" /> + </g> + </g> + <g + id="g19924" + transform="matrix(1.375,0,0,1.375,-551.3596,-738.88708)" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/22x22/actions/amarok_audioscrobbler.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <rect + style="opacity:1;fill:#969696;fill-opacity:0;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect19926" + width="16" + height="16" + x="1195.9285" + y="994.01929" /> + <g + id="g19928" + transform="matrix(0.54166,0,0,0.54166,433.2485,468.0377)"> + <path + sodipodi:nodetypes="cssccsccsssssssssssssccssssssssssssssc" + id="path19930" + d="M 1416.6163,976.87301 C 1413.7955,976.98421 1411.0672,978.47329 1409.4822,981.08398 C 1406.9578,985.24204 1408.3269,990.61665 1412.3439,993.30812 C 1415.6774,995.54165 1421.8911,994.48167 1423.0451,991.46838 L 1422.8637,988.36123 C 1421.0309,992.0906 1416.605,992.89821 1413.6941,991.07998 C 1412.6026,990.39814 1411.8056,989.43698 1411.3161,988.34079 C 1411.2234,988.30036 1410.761,986.9822 1410.772,986.17397 C 1410.6917,984.9241 1410.9786,983.62737 1411.6789,982.47402 C 1413.4714,979.52145 1417.2524,978.59117 1420.1632,980.40942 C 1421.0703,980.97601 1421.4449,981.12435 1422.3599,982.45358 C 1423.2882,983.8022 1423.7587,985.03971 1424.3551,987.15517 C 1424.8354,988.85895 1425.5732,990.66731 1426.7331,992.14294 C 1427.8931,993.61858 1429.5751,994.77992 1431.6706,994.77992 C 1433.5298,994.77993 1435.0228,994.27283 1435.9833,993.20591 C 1436.9438,992.139 1437.4142,990.70421 1437.4142,989.24022 C 1437.4142,987.6258 1436.5305,986.28324 1435.4996,985.45851 C 1434.4688,984.63379 1433.232,984.25566 1431.9729,984.0276 C 1430.9678,983.84555 1430.2756,983.51582 1429.877,983.12816 C 1429.4784,982.7405 1429.2546,982.29281 1429.2522,981.41106 C 1429.2509,980.90955 1429.8386,979.94819 1430.7234,979.57131 C 1431.6082,979.19442 1432.6104,979.18491 1433.7866,980.47074 L 1435.5803,978.77408 C 1434.2614,977.33234 1432.6296,976.801 1431.1668,976.93433 C 1430.6791,976.97878 1430.2046,977.09934 1429.7762,977.28183 C 1428.0625,978.01181 1426.7887,979.56637 1426.7936,981.41106 C 1426.7974,982.81176 1427.2798,984.06711 1428.164,984.92703 C 1429.0481,985.78695 1430.2274,986.24473 1431.5295,986.48059 C 1432.5776,986.67044 1433.4616,986.99964 1433.9882,987.42091 C 1434.5147,987.84219 1434.9555,988.26362 1434.9555,989.24022 C 1434.9555,990.36725 1434.5534,991.08287 1434.1695,991.50926 C 1433.7857,991.93565 1433.1167,992.28603 1431.6706,992.28603 C 1430.4288,992.28604 1429.502,991.67622 1428.6476,990.58938 C 1427.7933,989.50254 1427.1328,987.94925 1426.713,986.46015 C 1426.0536,984.12119 1425.3757,982.57272 1424.4357,981.16576 C 1423.489,979.74888 1422.7214,978.93573 1421.5135,978.18127 C 1420.0083,977.24106 1418.3274,976.82065 1416.6768,976.87301 C 1416.6546,976.87371 1416.6385,976.87212 1416.6163,976.87301 z " + style="fill:url(#linearGradient19957);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient19959);stroke-width:0.33316556;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + <g + transform="matrix(0.96097,0,0,0.974738,770.9242,742.8116)" + id="g19932"> + <path + sodipodi:type="arc" + style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:21.03624916;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0.68131869" + id="path19934" + sodipodi:cx="-7850.3027" + sodipodi:cy="-3285.1472" + sodipodi:rx="25.97311" + sodipodi:ry="5.306335" + d="M -7824.3296 -3285.1472 A 25.97311 5.306335 0 1 1 -7876.2758,-3285.1472 A 25.97311 5.306335 0 1 1 -7824.3296 -3285.1472 z" + transform="matrix(5.091848e-2,-5.525565e-2,6.0741e-2,4.632007e-2,1270.37,-32.70007)" /> + <path + sodipodi:nodetypes="ccccsccsccsccccccsssccsccscccc" + id="path19937" + d="M 675.44748,245.05111 C 675.16845,245.0606 674.6373,245.66838 674.43718,245.76498 C 674.23548,245.86238 672.63851,246.966 672.48723,247.16074 C 672.33596,247.35546 672.13428,247.43667 671.9158,247.42044 C 671.69731,247.40425 671.00658,247.39742 670.77126,247.52723 C 670.53591,247.65708 669.46179,248.71878 669.19286,248.84861 C 668.92394,248.9784 668.57299,249.04471 668.4049,249.06094 C 668.23684,249.07715 667.68011,249.67616 667.52884,249.78978 C 667.37757,249.9034 666.18209,250.40258 666.01401,250.49991 C 665.84592,250.59728 667.24027,251.9613 667.44198,251.99373 C 667.64369,252.02623 667.30309,252.76495 666.91501,252.76495 C 666.87227,252.76495 667.26176,253.98916 669.29842,255.0678 C 671.05913,256.0003 672.9272,255.58771 673.62096,255.36158 C 674.11157,255.20163 673.76745,255.11302 673.78429,254.85339 C 673.80106,254.59373 673.81508,254.2525 673.84871,253.96042 C 673.88235,253.66833 674.00013,251.21757 674.43718,250.56847 C 674.87418,249.91941 676.12298,249.14042 676.56001,249.05928 C 676.99707,248.97816 676.91326,248.81597 676.91326,248.66993 C 676.91326,248.5239 676.84601,248.19922 676.72832,248.15052 C 676.61068,248.10186 676.15148,248.39399 676.00021,248.42643 C 675.84894,248.45889 675.12612,248.50768 675.02524,248.34541 C 674.9244,248.18309 676.12648,245.87876 675.90795,245.60293 C 675.68952,245.32699 675.53817,245.05111 675.45413,245.05111 C 675.45196,245.05111 675.44968,245.05103 675.44748,245.05111 z M 671.87466,248.09817 C 672.00963,248.09635 672.10115,248.11022 672.10115,248.11022 C 672.17804,248.28329 671.81952,248.82716 671.4865,248.90138 C 671.15355,248.97554 670.385,250.06368 670.35941,249.81637 C 670.33373,249.56906 670.66671,248.75334 671.05096,248.38239 C 671.2926,248.14908 671.6459,248.10126 671.87466,248.09817 z " + style="fill:url(#linearGradient19961);fill-opacity:1" /> + <path + sodipodi:nodetypes="csssssscccscsscsssssscsssscsssssssssssssssscssssccssssscscscscccccsscsssssssssssscsscccsccsccccccsssccssssscsssccssssssssssscsscsssssc" + id="path19939" + d="M 667.38511,253.5434 C 667.27099,253.39835 667.06118,253.11134 666.99289,252.92251 C 666.93768,252.76986 666.93659,252.75088 666.98312,252.75207 C 667.03972,252.75351 667.10882,252.73798 667.18866,252.66435 C 667.36952,252.49758 667.5152,252.25544 667.47132,252.1302 C 667.46282,252.10596 667.43669,252.08149 667.39867,252.0622 C 667.17473,251.94856 666.30084,250.97208 666.09149,250.60156 C 666.06575,250.556 666.04145,250.49972 666.03751,250.47653 L 666.03035,250.43435 L 666.23505,250.33732 C 666.34763,250.28395 666.62869,250.15377 666.85962,250.04804 C 667.55376,249.73024 667.4887,249.77144 667.88325,249.39985 C 668.25237,249.05221 668.3867,248.94442 668.46511,248.93297 C 668.75595,248.8905 668.95135,248.84285 669.13755,248.76901 C 669.33588,248.69035 669.40679,248.63421 670.03203,248.06083 C 670.54799,247.58767 670.71137,247.44292 670.8113,247.37042 C 670.97439,247.25208 671.18847,247.22163 671.78274,247.23221 C 672.13614,247.23851 672.19958,247.23641 672.25904,247.21644 C 672.36674,247.1803 672.45692,247.1155 672.58279,246.98382 C 672.64681,246.91682 672.77806,246.79885 672.87445,246.72166 C 673.24856,246.42208 674.04705,245.84593 674.44439,245.58888 C 674.67943,245.43683 674.68008,245.43633 674.98007,245.16925 C 675.22923,244.94743 675.39807,244.82289 675.49643,244.78835 C 675.58908,244.75583 675.59851,244.75652 675.64631,244.79941 C 675.69297,244.84126 676.0796,245.44524 676.11156,245.52619 C 676.17022,245.67479 676.2026,245.8324 676.14186,245.95698 C 676.02777,246.19098 675.86297,246.52747 675.79133,246.65624 C 675.19517,247.79122 675.00425,248.24559 675.084,248.3396 C 675.1557,248.42409 675.46288,248.47448 675.79339,248.45596 C 675.9034,248.4498 676.01778,248.4388 676.04756,248.43152 C 676.07735,248.42425 676.21018,248.36664 676.34275,248.3035 C 676.62549,248.16885 676.72485,248.13565 676.77352,248.15963 C 676.82255,248.18377 676.87033,248.28368 676.90291,248.43016 C 676.93399,248.56987 676.95,248.82083 676.93293,248.9008 C 676.91033,249.00668 676.81254,249.07111 676.59946,249.12053 C 676.28376,249.19374 675.79997,249.46104 675.30205,249.83735 C 674.83522,250.19017 674.5191,250.51873 674.37228,250.80369 C 674.2891,250.96516 674.26527,251.08193 674.19471,251.34152 C 674.14219,251.53472 673.85136,254.161 673.84971,254.22221 C 673.84923,254.24006 673.89888,251.37606 673.89578,251.37598 C 673.89269,251.37589 673.64642,251.91968 673.64204,251.91957 C 673.63764,251.91946 673.11871,252.75283 673.06331,252.72691 C 673.00793,252.701 673.80733,250.80494 674.00659,250.56762 C 674.19417,250.34422 674.61935,249.95762 675.15821,249.52054 C 675.49133,249.25033 675.66447,249.12238 675.74673,249.08564 C 675.77864,249.07139 675.91408,249.04038 676.04771,249.01673 C 676.37771,248.95833 676.46206,248.92161 676.46438,248.83534 C 676.46693,248.74093 676.35364,248.6783 676.20159,248.69007 C 676.00232,248.70548 675.77864,248.78336 675.50673,248.932 C 675.21556,249.09116 674.97882,249.13474 674.69752,249.08098 C 674.61778,249.06572 674.51564,249.03878 674.47055,249.02107 C 674.37379,248.98309 674.3609,248.95448 674.38561,248.83251 C 674.43885,248.56969 674.63469,248.14928 675.23157,247.01641 C 675.42032,246.65819 675.58974,246.32216 675.60808,246.26968 C 675.62641,246.2172 675.70095,246.1355 675.71895,246.02591 C 675.74678,245.85642 675.60284,245.61573 675.49172,245.52411 C 675.26474,245.35531 675.19402,245.31107 675.07986,245.35924 C 674.8728,245.44662 674.59341,245.86971 673.83099,246.90503 C 672.60898,248.56442 672.46623,248.73643 672.13013,248.95442 C 671.66036,249.2591 670.92441,249.86282 670.32852,250.43232 C 669.9134,250.82905 669.60231,251.30462 669.47614,251.4267 C 669.39421,251.50599 669.04627,251.93415 668.92411,252.03686 C 668.30525,252.43042 667.87241,252.85149 667.49529,253.47745 L 667.43126,253.59757 L 667.38511,253.5434 z M 668.46397,251.31116 C 668.7204,251.18608 669.03749,250.97543 669.19076,250.82834 C 669.3172,250.707 669.36801,250.58569 669.29744,250.57367 C 669.25183,250.5659 669.12809,250.63188 668.81319,250.83188 C 668.45212,251.06122 668.34711,251.11421 668.24396,251.11911 C 668.03778,251.12891 667.57411,251.05103 667.34278,250.96775 C 667.18243,250.91001 667.12695,250.87786 667.07592,250.81312 C 667.0435,250.772 667.0416,250.76154 667.04988,250.67014 C 667.06051,250.55273 667.12182,250.42 667.22682,250.28711 C 667.28065,250.21898 667.32856,250.18046 667.46856,250.09276 C 667.87127,249.84051 668.31501,249.62936 668.77187,249.47259 C 668.87754,249.43633 668.97637,249.40185 668.99148,249.39596 C 669.01507,249.38677 669.00758,249.37909 668.93855,249.34168 C 668.82318,249.27914 668.67294,249.22259 668.59041,249.21064 C 668.50801,249.1987 668.47708,249.21081 668.28301,249.33094 C 668.1367,249.42152 667.42448,249.89761 667.08857,250.12936 C 666.95522,250.22138 666.75501,250.35743 666.64366,250.43169 C 666.5323,250.50597 666.43344,250.57568 666.42397,250.58662 C 666.39823,250.61637 666.40097,250.69218 666.43,250.75379 C 666.46192,250.82148 666.61497,251.01116 666.69764,251.08544 L 666.75927,251.14083 L 667.5157,251.25385 C 667.93173,251.31603 668.28739,251.36796 668.30605,251.36927 C 668.32471,251.37057 668.39578,251.34443 668.46397,251.31116 z M 668.29973,250.79286 C 668.50561,250.7809 668.54927,250.76106 668.7151,250.60429 C 668.88527,250.44343 669.05711,250.21709 669.12323,250.06675 L 669.14624,250.01443 L 669.09433,250.02128 C 669.06579,250.02503 668.76941,250.05586 668.43571,250.08977 L 667.829,250.15141 L 667.77344,250.19488 C 667.66847,250.27701 667.52502,250.42275 667.47068,250.50247 C 667.40632,250.59689 667.39992,250.64727 667.44798,250.68095 C 667.50243,250.7191 667.68326,250.76303 667.85548,250.77995 C 668.08353,250.80236 668.11878,250.80338 668.29973,250.79286 z M 670.47827,249.80701 C 670.53764,249.76762 670.65353,249.6522 670.93932,249.34778 C 671.15026,249.12309 671.34955,248.93569 671.42687,248.88931 C 671.45643,248.87158 671.51608,248.84475 671.55945,248.82967 C 671.65746,248.79558 671.72169,248.75713 671.81412,248.67723 C 672.00854,248.50914 672.20844,248.1523 672.18313,248.01851 C 672.17567,247.9791 672.17451,247.97863 672.06448,247.97131 C 671.67055,247.94514 671.36409,248.03082 671.14299,248.22897 C 670.88826,248.45727 670.55413,249.05801 670.43071,249.50963 C 670.38192,249.68818 670.369,249.77777 670.38684,249.81379 C 670.4054,249.85123 670.4124,249.85071 670.47827,249.80701 z M 669.75335,249.33118 C 670.09096,249.26438 670.36935,249.20765 670.37201,249.20511 C 670.37465,249.20258 670.40977,249.09665 670.45005,248.96973 C 670.6136,248.45438 670.74114,248.22639 670.92962,248.11242 C 671.20958,247.94314 671.78787,247.7617 672.1084,247.74258 C 672.22476,247.73563 672.24446,247.72998 672.3505,247.67319 C 672.50811,247.5888 672.7391,247.43215 673.05617,247.19467 C 673.20264,247.08495 673.40034,246.94052 673.49551,246.87371 C 673.73961,246.70234 673.8601,246.58426 673.86198,246.51459 C 673.8623,246.50286 673.85711,246.49315 673.85045,246.49297 C 673.8438,246.49281 673.68645,246.59884 673.50079,246.72861 C 673.31512,246.85838 673.04174,247.04509 672.89326,247.14353 C 672.3744,247.48755 672.45133,247.46169 671.94052,247.46372 C 671.71568,247.46461 671.41018,247.46798 671.26164,247.47121 C 671.00014,247.47689 670.98987,247.47821 670.93762,247.51284 C 670.90795,247.5325 670.71779,247.71525 670.51504,247.91894 C 670.07326,248.36277 669.69447,248.72049 669.54727,248.83287 C 669.48801,248.87811 669.3962,248.93395 669.34323,248.95696 C 669.23699,249.00312 669.20246,249.03343 669.15576,249.1215 C 669.12056,249.1879 669.08207,249.33573 669.07961,249.41403 C 669.07814,249.4608 669.08095,249.46517 669.10875,249.45921 C 669.12567,249.45559 669.41574,249.39797 669.75335,249.33118 z " + style="fill:url(#linearGradient19963);fill-opacity:1" /> + <g + style="fill:#002ddb;fill-opacity:1" + id="g19941" + transform="matrix(0.437568,0,0,0.437568,375.8931,217.9153)"> + <path + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:21.03624916;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + i:knockout="Off" + d="M 680.83905,76.838803 C 680.83905,76.838803 679.45446,81.3797 679.41945,81.449838 C 679.22156,81.846006 678.85659,82.994032 678.98224,83.364797 C 679.13021,83.801538 679.68481,84.436589 679.94365,84.754121 C 680.2025,85.071759 680.31342,85.230473 680.23945,85.349692 C 680.16554,85.468783 679.20413,85.468783 679.20413,85.468783 C 679.20413,85.468783 678.46465,84.396936 678.31664,83.999943 C 678.16882,83.603079 678.09481,82.769436 678.24277,82.491624 C 678.39065,82.213703 680.83905,76.838803 680.83905,76.838803 z " + id="path19943" /> + <polygon + transform="matrix(4.348075e-2,0,0,4.188821e-2,659.2285,53.92804)" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:17.46648407;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + i:knockout="Off" + a:adobe-blending-mode="lighten" + points="272.565,567.14 214.39,617.501 287.326,574.086 272.565,567.14 " + id="polygon19945" /> + <polygon + transform="matrix(4.348075e-2,0,0,4.188821e-2,659.2285,53.92804)" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:17.46648407;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + i:knockout="Off" + a:adobe-blending-mode="lighten" + points="329.873,549.776 230.887,678.282 344.633,558.457 329.873,549.776 " + id="polygon19947" /> + <polygon + transform="matrix(4.348075e-2,0,0,4.188821e-2,659.2285,53.92804)" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:17.46648407;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + i:knockout="Off" + a:adobe-blending-mode="lighten" + points="337.688,594.058 262.146,713.882 355.053,601.872 337.688,594.058 " + id="polygon19949" /> + <polygon + transform="matrix(4.348075e-2,0,0,4.188821e-2,659.2285,53.92804)" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:17.46648407;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + i:knockout="Off" + a:adobe-blending-mode="lighten" + points="381.97,588.847 318.584,737.327 402.809,593.19 381.97,588.847 " + id="polygon19951" /> + <polygon + transform="matrix(4.348075e-2,0,0,4.188821e-2,659.2285,53.92804)" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:17.46648407;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + i:knockout="Off" + a:adobe-blending-mode="lighten" + points="407.151,627.921 401.94,763.375 424.515,629.658 407.151,627.921 " + id="polygon19953" /> + </g> + <path + sodipodi:nodetypes="ccc" + id="path19955" + d="M 667.20325,253.00451 C 667.56898,254.08855 670.41003,256.39812 673.7867,255.29718 C 673.75257,254.52943 674.1359,252.21951 674.05736,251.55381" + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#0018ce;stroke-width:0.25647929;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + </g> + </g> + <g + transform="matrix(2,0,0,2,-1256.472,-1370.1486)" + id="g19965" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/32x32/actions/amarok_audioscrobbler.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <rect + y="994.01929" + x="1195.9285" + height="16" + width="16" + id="rect19967" + style="opacity:1;fill:#969696;fill-opacity:0;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + <g + transform="matrix(0.54166,0,0,0.54166,433.2485,468.0377)" + id="g19969"> + <path + style="fill:url(#linearGradient19997);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient19999);stroke-width:0.33316556;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + d="M 1416.6163,976.87301 C 1413.7955,976.98421 1411.0672,978.47329 1409.4822,981.08398 C 1406.9578,985.24204 1408.3269,990.61665 1412.3439,993.30812 C 1415.6774,995.54165 1421.8911,994.48167 1423.0451,991.46838 L 1422.8637,988.36123 C 1421.0309,992.0906 1416.605,992.89821 1413.6941,991.07998 C 1412.6026,990.39814 1411.8056,989.43698 1411.3161,988.34079 C 1411.2234,988.30036 1410.761,986.9822 1410.772,986.17397 C 1410.6917,984.9241 1410.9786,983.62737 1411.6789,982.47402 C 1413.4714,979.52145 1417.2524,978.59117 1420.1632,980.40942 C 1421.0703,980.97601 1421.4449,981.12435 1422.3599,982.45358 C 1423.2882,983.8022 1423.7587,985.03971 1424.3551,987.15517 C 1424.8354,988.85895 1425.5732,990.66731 1426.7331,992.14294 C 1427.8931,993.61858 1429.5751,994.77992 1431.6706,994.77992 C 1433.5298,994.77993 1435.0228,994.27283 1435.9833,993.20591 C 1436.9438,992.139 1437.4142,990.70421 1437.4142,989.24022 C 1437.4142,987.6258 1436.5305,986.28324 1435.4996,985.45851 C 1434.4688,984.63379 1433.232,984.25566 1431.9729,984.0276 C 1430.9678,983.84555 1430.2756,983.51582 1429.877,983.12816 C 1429.4784,982.7405 1429.2546,982.29281 1429.2522,981.41106 C 1429.2509,980.90955 1429.8386,979.94819 1430.7234,979.57131 C 1431.6082,979.19442 1432.6104,979.18491 1433.7866,980.47074 L 1435.5803,978.77408 C 1434.2614,977.33234 1432.6296,976.801 1431.1668,976.93433 C 1430.6791,976.97878 1430.2046,977.09934 1429.7762,977.28183 C 1428.0625,978.01181 1426.7887,979.56637 1426.7936,981.41106 C 1426.7974,982.81176 1427.2798,984.06711 1428.164,984.92703 C 1429.0481,985.78695 1430.2274,986.24473 1431.5295,986.48059 C 1432.5776,986.67044 1433.4616,986.99964 1433.9882,987.42091 C 1434.5147,987.84219 1434.9555,988.26362 1434.9555,989.24022 C 1434.9555,990.36725 1434.5534,991.08287 1434.1695,991.50926 C 1433.7857,991.93565 1433.1167,992.28603 1431.6706,992.28603 C 1430.4288,992.28604 1429.502,991.67622 1428.6476,990.58938 C 1427.7933,989.50254 1427.1328,987.94925 1426.713,986.46015 C 1426.0536,984.12119 1425.3757,982.57272 1424.4357,981.16576 C 1423.489,979.74888 1422.7214,978.93573 1421.5135,978.18127 C 1420.0083,977.24106 1418.3274,976.82065 1416.6768,976.87301 C 1416.6546,976.87371 1416.6385,976.87212 1416.6163,976.87301 z " + id="path19971" + sodipodi:nodetypes="cssccsccsssssssssssssccssssssssssssssc" /> + <g + id="g19973" + transform="matrix(0.96097,0,0,0.974738,770.9242,742.8116)"> + <path + transform="matrix(5.091848e-2,-5.525565e-2,6.0741e-2,4.632007e-2,1270.37,-32.70007)" + d="M -7824.3296 -3285.1472 A 25.97311 5.306335 0 1 1 -7876.2758,-3285.1472 A 25.97311 5.306335 0 1 1 -7824.3296 -3285.1472 z" + sodipodi:ry="5.306335" + sodipodi:rx="25.97311" + sodipodi:cy="-3285.1472" + sodipodi:cx="-7850.3027" + id="path19975" + style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:21.03624916;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0.68131869" + sodipodi:type="arc" /> + <path + style="fill:url(#linearGradient20002);fill-opacity:1" + d="M 675.44748,245.05111 C 675.16845,245.0606 674.6373,245.66838 674.43718,245.76498 C 674.23548,245.86238 672.63851,246.966 672.48723,247.16074 C 672.33596,247.35546 672.13428,247.43667 671.9158,247.42044 C 671.69731,247.40425 671.00658,247.39742 670.77126,247.52723 C 670.53591,247.65708 669.46179,248.71878 669.19286,248.84861 C 668.92394,248.9784 668.57299,249.04471 668.4049,249.06094 C 668.23684,249.07715 667.68011,249.67616 667.52884,249.78978 C 667.37757,249.9034 666.18209,250.40258 666.01401,250.49991 C 665.84592,250.59728 667.24027,251.9613 667.44198,251.99373 C 667.64369,252.02623 667.30309,252.76495 666.91501,252.76495 C 666.87227,252.76495 667.26176,253.98916 669.29842,255.0678 C 671.05913,256.0003 672.9272,255.58771 673.62096,255.36158 C 674.11157,255.20163 673.76745,255.11302 673.78429,254.85339 C 673.80106,254.59373 673.81508,254.2525 673.84871,253.96042 C 673.88235,253.66833 674.00013,251.21757 674.43718,250.56847 C 674.87418,249.91941 676.12298,249.14042 676.56001,249.05928 C 676.99707,248.97816 676.91326,248.81597 676.91326,248.66993 C 676.91326,248.5239 676.84601,248.19922 676.72832,248.15052 C 676.61068,248.10186 676.15148,248.39399 676.00021,248.42643 C 675.84894,248.45889 675.12612,248.50768 675.02524,248.34541 C 674.9244,248.18309 676.12648,245.87876 675.90795,245.60293 C 675.68952,245.32699 675.53817,245.05111 675.45413,245.05111 C 675.45196,245.05111 675.44968,245.05103 675.44748,245.05111 z M 671.87466,248.09817 C 672.00963,248.09635 672.10115,248.11022 672.10115,248.11022 C 672.17804,248.28329 671.81952,248.82716 671.4865,248.90138 C 671.15355,248.97554 670.385,250.06368 670.35941,249.81637 C 670.33373,249.56906 670.66671,248.75334 671.05096,248.38239 C 671.2926,248.14908 671.6459,248.10126 671.87466,248.09817 z " + id="path19977" + sodipodi:nodetypes="ccccsccsccsccccccsssccsccscccc" /> + <path + style="fill:url(#linearGradient20004);fill-opacity:1" + d="M 667.38511,253.5434 C 667.27099,253.39835 667.06118,253.11134 666.99289,252.92251 C 666.93768,252.76986 666.93659,252.75088 666.98312,252.75207 C 667.03972,252.75351 667.10882,252.73798 667.18866,252.66435 C 667.36952,252.49758 667.5152,252.25544 667.47132,252.1302 C 667.46282,252.10596 667.43669,252.08149 667.39867,252.0622 C 667.17473,251.94856 666.30084,250.97208 666.09149,250.60156 C 666.06575,250.556 666.04145,250.49972 666.03751,250.47653 L 666.03035,250.43435 L 666.23505,250.33732 C 666.34763,250.28395 666.62869,250.15377 666.85962,250.04804 C 667.55376,249.73024 667.4887,249.77144 667.88325,249.39985 C 668.25237,249.05221 668.3867,248.94442 668.46511,248.93297 C 668.75595,248.8905 668.95135,248.84285 669.13755,248.76901 C 669.33588,248.69035 669.40679,248.63421 670.03203,248.06083 C 670.54799,247.58767 670.71137,247.44292 670.8113,247.37042 C 670.97439,247.25208 671.18847,247.22163 671.78274,247.23221 C 672.13614,247.23851 672.19958,247.23641 672.25904,247.21644 C 672.36674,247.1803 672.45692,247.1155 672.58279,246.98382 C 672.64681,246.91682 672.77806,246.79885 672.87445,246.72166 C 673.24856,246.42208 674.04705,245.84593 674.44439,245.58888 C 674.67943,245.43683 674.68008,245.43633 674.98007,245.16925 C 675.22923,244.94743 675.39807,244.82289 675.49643,244.78835 C 675.58908,244.75583 675.59851,244.75652 675.64631,244.79941 C 675.69297,244.84126 676.0796,245.44524 676.11156,245.52619 C 676.17022,245.67479 676.2026,245.8324 676.14186,245.95698 C 676.02777,246.19098 675.86297,246.52747 675.79133,246.65624 C 675.19517,247.79122 675.00425,248.24559 675.084,248.3396 C 675.1557,248.42409 675.46288,248.47448 675.79339,248.45596 C 675.9034,248.4498 676.01778,248.4388 676.04756,248.43152 C 676.07735,248.42425 676.21018,248.36664 676.34275,248.3035 C 676.62549,248.16885 676.72485,248.13565 676.77352,248.15963 C 676.82255,248.18377 676.87033,248.28368 676.90291,248.43016 C 676.93399,248.56987 676.95,248.82083 676.93293,248.9008 C 676.91033,249.00668 676.81254,249.07111 676.59946,249.12053 C 676.28376,249.19374 675.79997,249.46104 675.30205,249.83735 C 674.83522,250.19017 674.5191,250.51873 674.37228,250.80369 C 674.2891,250.96516 674.26527,251.08193 674.19471,251.34152 C 674.14219,251.53472 673.85136,254.161 673.84971,254.22221 C 673.84923,254.24006 673.89888,251.37606 673.89578,251.37598 C 673.89269,251.37589 673.64642,251.91968 673.64204,251.91957 C 673.63764,251.91946 673.11871,252.75283 673.06331,252.72691 C 673.00793,252.701 673.80733,250.80494 674.00659,250.56762 C 674.19417,250.34422 674.61935,249.95762 675.15821,249.52054 C 675.49133,249.25033 675.66447,249.12238 675.74673,249.08564 C 675.77864,249.07139 675.91408,249.04038 676.04771,249.01673 C 676.37771,248.95833 676.46206,248.92161 676.46438,248.83534 C 676.46693,248.74093 676.35364,248.6783 676.20159,248.69007 C 676.00232,248.70548 675.77864,248.78336 675.50673,248.932 C 675.21556,249.09116 674.97882,249.13474 674.69752,249.08098 C 674.61778,249.06572 674.51564,249.03878 674.47055,249.02107 C 674.37379,248.98309 674.3609,248.95448 674.38561,248.83251 C 674.43885,248.56969 674.63469,248.14928 675.23157,247.01641 C 675.42032,246.65819 675.58974,246.32216 675.60808,246.26968 C 675.62641,246.2172 675.70095,246.1355 675.71895,246.02591 C 675.74678,245.85642 675.60284,245.61573 675.49172,245.52411 C 675.26474,245.35531 675.19402,245.31107 675.07986,245.35924 C 674.8728,245.44662 674.59341,245.86971 673.83099,246.90503 C 672.60898,248.56442 672.46623,248.73643 672.13013,248.95442 C 671.66036,249.2591 670.92441,249.86282 670.32852,250.43232 C 669.9134,250.82905 669.60231,251.30462 669.47614,251.4267 C 669.39421,251.50599 669.04627,251.93415 668.92411,252.03686 C 668.30525,252.43042 667.87241,252.85149 667.49529,253.47745 L 667.43126,253.59757 L 667.38511,253.5434 z M 668.46397,251.31116 C 668.7204,251.18608 669.03749,250.97543 669.19076,250.82834 C 669.3172,250.707 669.36801,250.58569 669.29744,250.57367 C 669.25183,250.5659 669.12809,250.63188 668.81319,250.83188 C 668.45212,251.06122 668.34711,251.11421 668.24396,251.11911 C 668.03778,251.12891 667.57411,251.05103 667.34278,250.96775 C 667.18243,250.91001 667.12695,250.87786 667.07592,250.81312 C 667.0435,250.772 667.0416,250.76154 667.04988,250.67014 C 667.06051,250.55273 667.12182,250.42 667.22682,250.28711 C 667.28065,250.21898 667.32856,250.18046 667.46856,250.09276 C 667.87127,249.84051 668.31501,249.62936 668.77187,249.47259 C 668.87754,249.43633 668.97637,249.40185 668.99148,249.39596 C 669.01507,249.38677 669.00758,249.37909 668.93855,249.34168 C 668.82318,249.27914 668.67294,249.22259 668.59041,249.21064 C 668.50801,249.1987 668.47708,249.21081 668.28301,249.33094 C 668.1367,249.42152 667.42448,249.89761 667.08857,250.12936 C 666.95522,250.22138 666.75501,250.35743 666.64366,250.43169 C 666.5323,250.50597 666.43344,250.57568 666.42397,250.58662 C 666.39823,250.61637 666.40097,250.69218 666.43,250.75379 C 666.46192,250.82148 666.61497,251.01116 666.69764,251.08544 L 666.75927,251.14083 L 667.5157,251.25385 C 667.93173,251.31603 668.28739,251.36796 668.30605,251.36927 C 668.32471,251.37057 668.39578,251.34443 668.46397,251.31116 z M 668.29973,250.79286 C 668.50561,250.7809 668.54927,250.76106 668.7151,250.60429 C 668.88527,250.44343 669.05711,250.21709 669.12323,250.06675 L 669.14624,250.01443 L 669.09433,250.02128 C 669.06579,250.02503 668.76941,250.05586 668.43571,250.08977 L 667.829,250.15141 L 667.77344,250.19488 C 667.66847,250.27701 667.52502,250.42275 667.47068,250.50247 C 667.40632,250.59689 667.39992,250.64727 667.44798,250.68095 C 667.50243,250.7191 667.68326,250.76303 667.85548,250.77995 C 668.08353,250.80236 668.11878,250.80338 668.29973,250.79286 z M 670.47827,249.80701 C 670.53764,249.76762 670.65353,249.6522 670.93932,249.34778 C 671.15026,249.12309 671.34955,248.93569 671.42687,248.88931 C 671.45643,248.87158 671.51608,248.84475 671.55945,248.82967 C 671.65746,248.79558 671.72169,248.75713 671.81412,248.67723 C 672.00854,248.50914 672.20844,248.1523 672.18313,248.01851 C 672.17567,247.9791 672.17451,247.97863 672.06448,247.97131 C 671.67055,247.94514 671.36409,248.03082 671.14299,248.22897 C 670.88826,248.45727 670.55413,249.05801 670.43071,249.50963 C 670.38192,249.68818 670.369,249.77777 670.38684,249.81379 C 670.4054,249.85123 670.4124,249.85071 670.47827,249.80701 z M 669.75335,249.33118 C 670.09096,249.26438 670.36935,249.20765 670.37201,249.20511 C 670.37465,249.20258 670.40977,249.09665 670.45005,248.96973 C 670.6136,248.45438 670.74114,248.22639 670.92962,248.11242 C 671.20958,247.94314 671.78787,247.7617 672.1084,247.74258 C 672.22476,247.73563 672.24446,247.72998 672.3505,247.67319 C 672.50811,247.5888 672.7391,247.43215 673.05617,247.19467 C 673.20264,247.08495 673.40034,246.94052 673.49551,246.87371 C 673.73961,246.70234 673.8601,246.58426 673.86198,246.51459 C 673.8623,246.50286 673.85711,246.49315 673.85045,246.49297 C 673.8438,246.49281 673.68645,246.59884 673.50079,246.72861 C 673.31512,246.85838 673.04174,247.04509 672.89326,247.14353 C 672.3744,247.48755 672.45133,247.46169 671.94052,247.46372 C 671.71568,247.46461 671.41018,247.46798 671.26164,247.47121 C 671.00014,247.47689 670.98987,247.47821 670.93762,247.51284 C 670.90795,247.5325 670.71779,247.71525 670.51504,247.91894 C 670.07326,248.36277 669.69447,248.72049 669.54727,248.83287 C 669.48801,248.87811 669.3962,248.93395 669.34323,248.95696 C 669.23699,249.00312 669.20246,249.03343 669.15576,249.1215 C 669.12056,249.1879 669.08207,249.33573 669.07961,249.41403 C 669.07814,249.4608 669.08095,249.46517 669.10875,249.45921 C 669.12567,249.45559 669.41574,249.39797 669.75335,249.33118 z " + id="path19979" + sodipodi:nodetypes="csssssscccscsscsssssscsssscsssssssssssssssscssssccssssscscscscccccsscsssssssssssscsscccsccsccccccsssccssssscsssccssssssssssscsscsssssc" /> + <g + transform="matrix(0.437568,0,0,0.437568,375.8931,217.9153)" + id="g19981" + style="fill:#002ddb;fill-opacity:1"> + <path + id="path19983" + d="M 680.83905,76.838803 C 680.83905,76.838803 679.45446,81.3797 679.41945,81.449838 C 679.22156,81.846006 678.85659,82.994032 678.98224,83.364797 C 679.13021,83.801538 679.68481,84.436589 679.94365,84.754121 C 680.2025,85.071759 680.31342,85.230473 680.23945,85.349692 C 680.16554,85.468783 679.20413,85.468783 679.20413,85.468783 C 679.20413,85.468783 678.46465,84.396936 678.31664,83.999943 C 678.16882,83.603079 678.09481,82.769436 678.24277,82.491624 C 678.39065,82.213703 680.83905,76.838803 680.83905,76.838803 z " + i:knockout="Off" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:21.03624916;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <polygon + id="polygon19985" + points="272.565,567.14 214.39,617.501 287.326,574.086 272.565,567.14 " + a:adobe-blending-mode="lighten" + i:knockout="Off" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:17.46648407;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + transform="matrix(4.348075e-2,0,0,4.188821e-2,659.2285,53.92804)" /> + <polygon + id="polygon19987" + points="329.873,549.776 230.887,678.282 344.633,558.457 329.873,549.776 " + a:adobe-blending-mode="lighten" + i:knockout="Off" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:17.46648407;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + transform="matrix(4.348075e-2,0,0,4.188821e-2,659.2285,53.92804)" /> + <polygon + id="polygon19989" + points="337.688,594.058 262.146,713.882 355.053,601.872 337.688,594.058 " + a:adobe-blending-mode="lighten" + i:knockout="Off" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:17.46648407;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + transform="matrix(4.348075e-2,0,0,4.188821e-2,659.2285,53.92804)" /> + <polygon + id="polygon19991" + points="381.97,588.847 318.584,737.327 402.809,593.19 381.97,588.847 " + a:adobe-blending-mode="lighten" + i:knockout="Off" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:17.46648407;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + transform="matrix(4.348075e-2,0,0,4.188821e-2,659.2285,53.92804)" /> + <polygon + id="polygon19993" + points="407.151,627.921 401.94,763.375 424.515,629.658 407.151,627.921 " + a:adobe-blending-mode="lighten" + i:knockout="Off" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:17.46648407;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + transform="matrix(4.348075e-2,0,0,4.188821e-2,659.2285,53.92804)" /> + </g> + <path + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#0018ce;stroke-width:0.25647929;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 667.20325,253.00451 C 667.56898,254.08855 670.41003,256.39812 673.7867,255.29718 C 673.75257,254.52943 674.1359,252.21951 674.05736,251.55381" + id="path19995" + sodipodi:nodetypes="ccc" /> + </g> + </g> + </g> + <g + id="g20006" + transform="matrix(4,0,0,4,-3536.516,-3390.1876)" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/64x64/actions/amarok_audioscrobbler.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <rect + style="opacity:1;fill:#969696;fill-opacity:0;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect20008" + width="16" + height="16" + x="1195.9285" + y="994.01929" /> + <g + id="g20010" + transform="matrix(0.54166,0,0,0.54166,433.2485,468.0377)"> + <path + sodipodi:nodetypes="cssccsccsssssssssssssccssssssssssssssc" + id="path20012" + d="M 1416.6163,976.87301 C 1413.7955,976.98421 1411.0672,978.47329 1409.4822,981.08398 C 1406.9578,985.24204 1408.3269,990.61665 1412.3439,993.30812 C 1415.6774,995.54165 1421.8911,994.48167 1423.0451,991.46838 L 1422.8637,988.36123 C 1421.0309,992.0906 1416.605,992.89821 1413.6941,991.07998 C 1412.6026,990.39814 1411.8056,989.43698 1411.3161,988.34079 C 1411.2234,988.30036 1410.761,986.9822 1410.772,986.17397 C 1410.6917,984.9241 1410.9786,983.62737 1411.6789,982.47402 C 1413.4714,979.52145 1417.2524,978.59117 1420.1632,980.40942 C 1421.0703,980.97601 1421.4449,981.12435 1422.3599,982.45358 C 1423.2882,983.8022 1423.7587,985.03971 1424.3551,987.15517 C 1424.8354,988.85895 1425.5732,990.66731 1426.7331,992.14294 C 1427.8931,993.61858 1429.5751,994.77992 1431.6706,994.77992 C 1433.5298,994.77993 1435.0228,994.27283 1435.9833,993.20591 C 1436.9438,992.139 1437.4142,990.70421 1437.4142,989.24022 C 1437.4142,987.6258 1436.5305,986.28324 1435.4996,985.45851 C 1434.4688,984.63379 1433.232,984.25566 1431.9729,984.0276 C 1430.9678,983.84555 1430.2756,983.51582 1429.877,983.12816 C 1429.4784,982.7405 1429.2546,982.29281 1429.2522,981.41106 C 1429.2509,980.90955 1429.8386,979.94819 1430.7234,979.57131 C 1431.6082,979.19442 1432.6104,979.18491 1433.7866,980.47074 L 1435.5803,978.77408 C 1434.2614,977.33234 1432.6296,976.801 1431.1668,976.93433 C 1430.6791,976.97878 1430.2046,977.09934 1429.7762,977.28183 C 1428.0625,978.01181 1426.7887,979.56637 1426.7936,981.41106 C 1426.7974,982.81176 1427.2798,984.06711 1428.164,984.92703 C 1429.0481,985.78695 1430.2274,986.24473 1431.5295,986.48059 C 1432.5776,986.67044 1433.4616,986.99964 1433.9882,987.42091 C 1434.5147,987.84219 1434.9555,988.26362 1434.9555,989.24022 C 1434.9555,990.36725 1434.5534,991.08287 1434.1695,991.50926 C 1433.7857,991.93565 1433.1167,992.28603 1431.6706,992.28603 C 1430.4288,992.28604 1429.502,991.67622 1428.6476,990.58938 C 1427.7933,989.50254 1427.1328,987.94925 1426.713,986.46015 C 1426.0536,984.12119 1425.3757,982.57272 1424.4357,981.16576 C 1423.489,979.74888 1422.7214,978.93573 1421.5135,978.18127 C 1420.0083,977.24106 1418.3274,976.82065 1416.6768,976.87301 C 1416.6546,976.87371 1416.6385,976.87212 1416.6163,976.87301 z " + style="fill:url(#linearGradient20038);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient20040);stroke-width:0.33316556;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + <g + transform="matrix(0.96097,0,0,0.974738,770.9242,742.8116)" + id="g20014"> + <path + sodipodi:type="arc" + style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:21.03624916;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0.68131869" + id="path20016" + sodipodi:cx="-7850.3027" + sodipodi:cy="-3285.1472" + sodipodi:rx="25.97311" + sodipodi:ry="5.306335" + d="M -7824.3296 -3285.1472 A 25.97311 5.306335 0 1 1 -7876.2758,-3285.1472 A 25.97311 5.306335 0 1 1 -7824.3296 -3285.1472 z" + transform="matrix(5.091848e-2,-5.525565e-2,6.0741e-2,4.632007e-2,1270.37,-32.70007)" /> + <path + sodipodi:nodetypes="ccccsccsccsccccccsssccsccscccc" + id="path20018" + d="M 675.44748,245.05111 C 675.16845,245.0606 674.6373,245.66838 674.43718,245.76498 C 674.23548,245.86238 672.63851,246.966 672.48723,247.16074 C 672.33596,247.35546 672.13428,247.43667 671.9158,247.42044 C 671.69731,247.40425 671.00658,247.39742 670.77126,247.52723 C 670.53591,247.65708 669.46179,248.71878 669.19286,248.84861 C 668.92394,248.9784 668.57299,249.04471 668.4049,249.06094 C 668.23684,249.07715 667.68011,249.67616 667.52884,249.78978 C 667.37757,249.9034 666.18209,250.40258 666.01401,250.49991 C 665.84592,250.59728 667.24027,251.9613 667.44198,251.99373 C 667.64369,252.02623 667.30309,252.76495 666.91501,252.76495 C 666.87227,252.76495 667.26176,253.98916 669.29842,255.0678 C 671.05913,256.0003 672.9272,255.58771 673.62096,255.36158 C 674.11157,255.20163 673.76745,255.11302 673.78429,254.85339 C 673.80106,254.59373 673.81508,254.2525 673.84871,253.96042 C 673.88235,253.66833 674.00013,251.21757 674.43718,250.56847 C 674.87418,249.91941 676.12298,249.14042 676.56001,249.05928 C 676.99707,248.97816 676.91326,248.81597 676.91326,248.66993 C 676.91326,248.5239 676.84601,248.19922 676.72832,248.15052 C 676.61068,248.10186 676.15148,248.39399 676.00021,248.42643 C 675.84894,248.45889 675.12612,248.50768 675.02524,248.34541 C 674.9244,248.18309 676.12648,245.87876 675.90795,245.60293 C 675.68952,245.32699 675.53817,245.05111 675.45413,245.05111 C 675.45196,245.05111 675.44968,245.05103 675.44748,245.05111 z M 671.87466,248.09817 C 672.00963,248.09635 672.10115,248.11022 672.10115,248.11022 C 672.17804,248.28329 671.81952,248.82716 671.4865,248.90138 C 671.15355,248.97554 670.385,250.06368 670.35941,249.81637 C 670.33373,249.56906 670.66671,248.75334 671.05096,248.38239 C 671.2926,248.14908 671.6459,248.10126 671.87466,248.09817 z " + style="fill:url(#linearGradient20042);fill-opacity:1" /> + <path + sodipodi:nodetypes="csssssscccscsscsssssscsssscsssssssssssssssscssssccssssscscscscccccsscsssssssssssscsscccsccsccccccsssccssssscsssccssssssssssscsscsssssc" + id="path20020" + d="M 667.38511,253.5434 C 667.27099,253.39835 667.06118,253.11134 666.99289,252.92251 C 666.93768,252.76986 666.93659,252.75088 666.98312,252.75207 C 667.03972,252.75351 667.10882,252.73798 667.18866,252.66435 C 667.36952,252.49758 667.5152,252.25544 667.47132,252.1302 C 667.46282,252.10596 667.43669,252.08149 667.39867,252.0622 C 667.17473,251.94856 666.30084,250.97208 666.09149,250.60156 C 666.06575,250.556 666.04145,250.49972 666.03751,250.47653 L 666.03035,250.43435 L 666.23505,250.33732 C 666.34763,250.28395 666.62869,250.15377 666.85962,250.04804 C 667.55376,249.73024 667.4887,249.77144 667.88325,249.39985 C 668.25237,249.05221 668.3867,248.94442 668.46511,248.93297 C 668.75595,248.8905 668.95135,248.84285 669.13755,248.76901 C 669.33588,248.69035 669.40679,248.63421 670.03203,248.06083 C 670.54799,247.58767 670.71137,247.44292 670.8113,247.37042 C 670.97439,247.25208 671.18847,247.22163 671.78274,247.23221 C 672.13614,247.23851 672.19958,247.23641 672.25904,247.21644 C 672.36674,247.1803 672.45692,247.1155 672.58279,246.98382 C 672.64681,246.91682 672.77806,246.79885 672.87445,246.72166 C 673.24856,246.42208 674.04705,245.84593 674.44439,245.58888 C 674.67943,245.43683 674.68008,245.43633 674.98007,245.16925 C 675.22923,244.94743 675.39807,244.82289 675.49643,244.78835 C 675.58908,244.75583 675.59851,244.75652 675.64631,244.79941 C 675.69297,244.84126 676.0796,245.44524 676.11156,245.52619 C 676.17022,245.67479 676.2026,245.8324 676.14186,245.95698 C 676.02777,246.19098 675.86297,246.52747 675.79133,246.65624 C 675.19517,247.79122 675.00425,248.24559 675.084,248.3396 C 675.1557,248.42409 675.46288,248.47448 675.79339,248.45596 C 675.9034,248.4498 676.01778,248.4388 676.04756,248.43152 C 676.07735,248.42425 676.21018,248.36664 676.34275,248.3035 C 676.62549,248.16885 676.72485,248.13565 676.77352,248.15963 C 676.82255,248.18377 676.87033,248.28368 676.90291,248.43016 C 676.93399,248.56987 676.95,248.82083 676.93293,248.9008 C 676.91033,249.00668 676.81254,249.07111 676.59946,249.12053 C 676.28376,249.19374 675.79997,249.46104 675.30205,249.83735 C 674.83522,250.19017 674.5191,250.51873 674.37228,250.80369 C 674.2891,250.96516 674.26527,251.08193 674.19471,251.34152 C 674.14219,251.53472 673.85136,254.161 673.84971,254.22221 C 673.84923,254.24006 673.89888,251.37606 673.89578,251.37598 C 673.89269,251.37589 673.64642,251.91968 673.64204,251.91957 C 673.63764,251.91946 673.11871,252.75283 673.06331,252.72691 C 673.00793,252.701 673.80733,250.80494 674.00659,250.56762 C 674.19417,250.34422 674.61935,249.95762 675.15821,249.52054 C 675.49133,249.25033 675.66447,249.12238 675.74673,249.08564 C 675.77864,249.07139 675.91408,249.04038 676.04771,249.01673 C 676.37771,248.95833 676.46206,248.92161 676.46438,248.83534 C 676.46693,248.74093 676.35364,248.6783 676.20159,248.69007 C 676.00232,248.70548 675.77864,248.78336 675.50673,248.932 C 675.21556,249.09116 674.97882,249.13474 674.69752,249.08098 C 674.61778,249.06572 674.51564,249.03878 674.47055,249.02107 C 674.37379,248.98309 674.3609,248.95448 674.38561,248.83251 C 674.43885,248.56969 674.63469,248.14928 675.23157,247.01641 C 675.42032,246.65819 675.58974,246.32216 675.60808,246.26968 C 675.62641,246.2172 675.70095,246.1355 675.71895,246.02591 C 675.74678,245.85642 675.60284,245.61573 675.49172,245.52411 C 675.26474,245.35531 675.19402,245.31107 675.07986,245.35924 C 674.8728,245.44662 674.59341,245.86971 673.83099,246.90503 C 672.60898,248.56442 672.46623,248.73643 672.13013,248.95442 C 671.66036,249.2591 670.92441,249.86282 670.32852,250.43232 C 669.9134,250.82905 669.60231,251.30462 669.47614,251.4267 C 669.39421,251.50599 669.04627,251.93415 668.92411,252.03686 C 668.30525,252.43042 667.87241,252.85149 667.49529,253.47745 L 667.43126,253.59757 L 667.38511,253.5434 z M 668.46397,251.31116 C 668.7204,251.18608 669.03749,250.97543 669.19076,250.82834 C 669.3172,250.707 669.36801,250.58569 669.29744,250.57367 C 669.25183,250.5659 669.12809,250.63188 668.81319,250.83188 C 668.45212,251.06122 668.34711,251.11421 668.24396,251.11911 C 668.03778,251.12891 667.57411,251.05103 667.34278,250.96775 C 667.18243,250.91001 667.12695,250.87786 667.07592,250.81312 C 667.0435,250.772 667.0416,250.76154 667.04988,250.67014 C 667.06051,250.55273 667.12182,250.42 667.22682,250.28711 C 667.28065,250.21898 667.32856,250.18046 667.46856,250.09276 C 667.87127,249.84051 668.31501,249.62936 668.77187,249.47259 C 668.87754,249.43633 668.97637,249.40185 668.99148,249.39596 C 669.01507,249.38677 669.00758,249.37909 668.93855,249.34168 C 668.82318,249.27914 668.67294,249.22259 668.59041,249.21064 C 668.50801,249.1987 668.47708,249.21081 668.28301,249.33094 C 668.1367,249.42152 667.42448,249.89761 667.08857,250.12936 C 666.95522,250.22138 666.75501,250.35743 666.64366,250.43169 C 666.5323,250.50597 666.43344,250.57568 666.42397,250.58662 C 666.39823,250.61637 666.40097,250.69218 666.43,250.75379 C 666.46192,250.82148 666.61497,251.01116 666.69764,251.08544 L 666.75927,251.14083 L 667.5157,251.25385 C 667.93173,251.31603 668.28739,251.36796 668.30605,251.36927 C 668.32471,251.37057 668.39578,251.34443 668.46397,251.31116 z M 668.29973,250.79286 C 668.50561,250.7809 668.54927,250.76106 668.7151,250.60429 C 668.88527,250.44343 669.05711,250.21709 669.12323,250.06675 L 669.14624,250.01443 L 669.09433,250.02128 C 669.06579,250.02503 668.76941,250.05586 668.43571,250.08977 L 667.829,250.15141 L 667.77344,250.19488 C 667.66847,250.27701 667.52502,250.42275 667.47068,250.50247 C 667.40632,250.59689 667.39992,250.64727 667.44798,250.68095 C 667.50243,250.7191 667.68326,250.76303 667.85548,250.77995 C 668.08353,250.80236 668.11878,250.80338 668.29973,250.79286 z M 670.47827,249.80701 C 670.53764,249.76762 670.65353,249.6522 670.93932,249.34778 C 671.15026,249.12309 671.34955,248.93569 671.42687,248.88931 C 671.45643,248.87158 671.51608,248.84475 671.55945,248.82967 C 671.65746,248.79558 671.72169,248.75713 671.81412,248.67723 C 672.00854,248.50914 672.20844,248.1523 672.18313,248.01851 C 672.17567,247.9791 672.17451,247.97863 672.06448,247.97131 C 671.67055,247.94514 671.36409,248.03082 671.14299,248.22897 C 670.88826,248.45727 670.55413,249.05801 670.43071,249.50963 C 670.38192,249.68818 670.369,249.77777 670.38684,249.81379 C 670.4054,249.85123 670.4124,249.85071 670.47827,249.80701 z M 669.75335,249.33118 C 670.09096,249.26438 670.36935,249.20765 670.37201,249.20511 C 670.37465,249.20258 670.40977,249.09665 670.45005,248.96973 C 670.6136,248.45438 670.74114,248.22639 670.92962,248.11242 C 671.20958,247.94314 671.78787,247.7617 672.1084,247.74258 C 672.22476,247.73563 672.24446,247.72998 672.3505,247.67319 C 672.50811,247.5888 672.7391,247.43215 673.05617,247.19467 C 673.20264,247.08495 673.40034,246.94052 673.49551,246.87371 C 673.73961,246.70234 673.8601,246.58426 673.86198,246.51459 C 673.8623,246.50286 673.85711,246.49315 673.85045,246.49297 C 673.8438,246.49281 673.68645,246.59884 673.50079,246.72861 C 673.31512,246.85838 673.04174,247.04509 672.89326,247.14353 C 672.3744,247.48755 672.45133,247.46169 671.94052,247.46372 C 671.71568,247.46461 671.41018,247.46798 671.26164,247.47121 C 671.00014,247.47689 670.98987,247.47821 670.93762,247.51284 C 670.90795,247.5325 670.71779,247.71525 670.51504,247.91894 C 670.07326,248.36277 669.69447,248.72049 669.54727,248.83287 C 669.48801,248.87811 669.3962,248.93395 669.34323,248.95696 C 669.23699,249.00312 669.20246,249.03343 669.15576,249.1215 C 669.12056,249.1879 669.08207,249.33573 669.07961,249.41403 C 669.07814,249.4608 669.08095,249.46517 669.10875,249.45921 C 669.12567,249.45559 669.41574,249.39797 669.75335,249.33118 z " + style="fill:url(#linearGradient20044);fill-opacity:1" /> + <g + style="fill:#002ddb;fill-opacity:1" + id="g20022" + transform="matrix(0.437568,0,0,0.437568,375.8931,217.9153)"> + <path + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:21.03624916;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + i:knockout="Off" + d="M 680.83905,76.838803 C 680.83905,76.838803 679.45446,81.3797 679.41945,81.449838 C 679.22156,81.846006 678.85659,82.994032 678.98224,83.364797 C 679.13021,83.801538 679.68481,84.436589 679.94365,84.754121 C 680.2025,85.071759 680.31342,85.230473 680.23945,85.349692 C 680.16554,85.468783 679.20413,85.468783 679.20413,85.468783 C 679.20413,85.468783 678.46465,84.396936 678.31664,83.999943 C 678.16882,83.603079 678.09481,82.769436 678.24277,82.491624 C 678.39065,82.213703 680.83905,76.838803 680.83905,76.838803 z " + id="path20024" /> + <polygon + transform="matrix(4.348075e-2,0,0,4.188821e-2,659.2285,53.92804)" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:17.46648407;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + i:knockout="Off" + a:adobe-blending-mode="lighten" + points="272.565,567.14 214.39,617.501 287.326,574.086 272.565,567.14 " + id="polygon20026" /> + <polygon + transform="matrix(4.348075e-2,0,0,4.188821e-2,659.2285,53.92804)" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:17.46648407;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + i:knockout="Off" + a:adobe-blending-mode="lighten" + points="329.873,549.776 230.887,678.282 344.633,558.457 329.873,549.776 " + id="polygon20028" /> + <polygon + transform="matrix(4.348075e-2,0,0,4.188821e-2,659.2285,53.92804)" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:17.46648407;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + i:knockout="Off" + a:adobe-blending-mode="lighten" + points="337.688,594.058 262.146,713.882 355.053,601.872 337.688,594.058 " + id="polygon20030" /> + <polygon + transform="matrix(4.348075e-2,0,0,4.188821e-2,659.2285,53.92804)" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:17.46648407;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + i:knockout="Off" + a:adobe-blending-mode="lighten" + points="381.97,588.847 318.584,737.327 402.809,593.19 381.97,588.847 " + id="polygon20032" /> + <polygon + transform="matrix(4.348075e-2,0,0,4.188821e-2,659.2285,53.92804)" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:17.46648407;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + i:knockout="Off" + a:adobe-blending-mode="lighten" + points="407.151,627.921 401.94,763.375 424.515,629.658 407.151,627.921 " + id="polygon20034" /> + </g> + <path + sodipodi:nodetypes="ccc" + id="path20036" + d="M 667.20325,253.00451 C 667.56898,254.08855 670.41003,256.39812 673.7867,255.29718 C 673.75257,254.52943 674.1359,252.21951 674.05736,251.55381" + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#0018ce;stroke-width:0.25647929;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + </g> + </g> + <g + transform="matrix(3,0,0,3,-2404.103,-2380.1686)" + id="g20046" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/48x48/actions/amarok_audioscrobbler.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <rect + y="994.01929" + x="1195.9285" + height="16" + width="16" + id="rect20048" + style="opacity:1;fill:#969696;fill-opacity:0;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + <g + transform="matrix(0.54166,0,0,0.54166,433.2485,468.0377)" + id="g20050"> + <path + style="fill:url(#linearGradient20079);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient20081);stroke-width:0.33316556;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + d="M 1416.6163,976.87301 C 1413.7955,976.98421 1411.0672,978.47329 1409.4822,981.08398 C 1406.9578,985.24204 1408.3269,990.61665 1412.3439,993.30812 C 1415.6774,995.54165 1421.8911,994.48167 1423.0451,991.46838 L 1422.8637,988.36123 C 1421.0309,992.0906 1416.605,992.89821 1413.6941,991.07998 C 1412.6026,990.39814 1411.8056,989.43698 1411.3161,988.34079 C 1411.2234,988.30036 1410.761,986.9822 1410.772,986.17397 C 1410.6917,984.9241 1410.9786,983.62737 1411.6789,982.47402 C 1413.4714,979.52145 1417.2524,978.59117 1420.1632,980.40942 C 1421.0703,980.97601 1421.4449,981.12435 1422.3599,982.45358 C 1423.2882,983.8022 1423.7587,985.03971 1424.3551,987.15517 C 1424.8354,988.85895 1425.5732,990.66731 1426.7331,992.14294 C 1427.8931,993.61858 1429.5751,994.77992 1431.6706,994.77992 C 1433.5298,994.77993 1435.0228,994.27283 1435.9833,993.20591 C 1436.9438,992.139 1437.4142,990.70421 1437.4142,989.24022 C 1437.4142,987.6258 1436.5305,986.28324 1435.4996,985.45851 C 1434.4688,984.63379 1433.232,984.25566 1431.9729,984.0276 C 1430.9678,983.84555 1430.2756,983.51582 1429.877,983.12816 C 1429.4784,982.7405 1429.2546,982.29281 1429.2522,981.41106 C 1429.2509,980.90955 1429.8386,979.94819 1430.7234,979.57131 C 1431.6082,979.19442 1432.6104,979.18491 1433.7866,980.47074 L 1435.5803,978.77408 C 1434.2614,977.33234 1432.6296,976.801 1431.1668,976.93433 C 1430.6791,976.97878 1430.2046,977.09934 1429.7762,977.28183 C 1428.0625,978.01181 1426.7887,979.56637 1426.7936,981.41106 C 1426.7974,982.81176 1427.2798,984.06711 1428.164,984.92703 C 1429.0481,985.78695 1430.2274,986.24473 1431.5295,986.48059 C 1432.5776,986.67044 1433.4616,986.99964 1433.9882,987.42091 C 1434.5147,987.84219 1434.9555,988.26362 1434.9555,989.24022 C 1434.9555,990.36725 1434.5534,991.08287 1434.1695,991.50926 C 1433.7857,991.93565 1433.1167,992.28603 1431.6706,992.28603 C 1430.4288,992.28604 1429.502,991.67622 1428.6476,990.58938 C 1427.7933,989.50254 1427.1328,987.94925 1426.713,986.46015 C 1426.0536,984.12119 1425.3757,982.57272 1424.4357,981.16576 C 1423.489,979.74888 1422.7214,978.93573 1421.5135,978.18127 C 1420.0083,977.24106 1418.3274,976.82065 1416.6768,976.87301 C 1416.6546,976.87371 1416.6385,976.87212 1416.6163,976.87301 z " + id="path20052" + sodipodi:nodetypes="cssccsccsssssssssssssccssssssssssssssc" /> + <g + id="g20054" + transform="matrix(0.96097,0,0,0.974738,770.9242,742.8116)"> + <path + transform="matrix(5.091848e-2,-5.525565e-2,6.0741e-2,4.632007e-2,1270.37,-32.70007)" + d="M -7824.3296 -3285.1472 A 25.97311 5.306335 0 1 1 -7876.2758,-3285.1472 A 25.97311 5.306335 0 1 1 -7824.3296 -3285.1472 z" + sodipodi:ry="5.306335" + sodipodi:rx="25.97311" + sodipodi:cy="-3285.1472" + sodipodi:cx="-7850.3027" + id="path20056" + style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:21.03624916;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0.68131869" + sodipodi:type="arc" /> + <path + style="fill:url(#linearGradient20083);fill-opacity:1" + d="M 675.44748,245.05111 C 675.16845,245.0606 674.6373,245.66838 674.43718,245.76498 C 674.23548,245.86238 672.63851,246.966 672.48723,247.16074 C 672.33596,247.35546 672.13428,247.43667 671.9158,247.42044 C 671.69731,247.40425 671.00658,247.39742 670.77126,247.52723 C 670.53591,247.65708 669.46179,248.71878 669.19286,248.84861 C 668.92394,248.9784 668.57299,249.04471 668.4049,249.06094 C 668.23684,249.07715 667.68011,249.67616 667.52884,249.78978 C 667.37757,249.9034 666.18209,250.40258 666.01401,250.49991 C 665.84592,250.59728 667.24027,251.9613 667.44198,251.99373 C 667.64369,252.02623 667.30309,252.76495 666.91501,252.76495 C 666.87227,252.76495 667.26176,253.98916 669.29842,255.0678 C 671.05913,256.0003 672.9272,255.58771 673.62096,255.36158 C 674.11157,255.20163 673.76745,255.11302 673.78429,254.85339 C 673.80106,254.59373 673.81508,254.2525 673.84871,253.96042 C 673.88235,253.66833 674.00013,251.21757 674.43718,250.56847 C 674.87418,249.91941 676.12298,249.14042 676.56001,249.05928 C 676.99707,248.97816 676.91326,248.81597 676.91326,248.66993 C 676.91326,248.5239 676.84601,248.19922 676.72832,248.15052 C 676.61068,248.10186 676.15148,248.39399 676.00021,248.42643 C 675.84894,248.45889 675.12612,248.50768 675.02524,248.34541 C 674.9244,248.18309 676.12648,245.87876 675.90795,245.60293 C 675.68952,245.32699 675.53817,245.05111 675.45413,245.05111 C 675.45196,245.05111 675.44968,245.05103 675.44748,245.05111 z M 671.87466,248.09817 C 672.00963,248.09635 672.10115,248.11022 672.10115,248.11022 C 672.17804,248.28329 671.81952,248.82716 671.4865,248.90138 C 671.15355,248.97554 670.385,250.06368 670.35941,249.81637 C 670.33373,249.56906 670.66671,248.75334 671.05096,248.38239 C 671.2926,248.14908 671.6459,248.10126 671.87466,248.09817 z " + id="path20058" + sodipodi:nodetypes="ccccsccsccsccccccsssccsccscccc" /> + <path + style="fill:url(#linearGradient20085);fill-opacity:1" + d="M 667.38511,253.5434 C 667.27099,253.39835 667.06118,253.11134 666.99289,252.92251 C 666.93768,252.76986 666.93659,252.75088 666.98312,252.75207 C 667.03972,252.75351 667.10882,252.73798 667.18866,252.66435 C 667.36952,252.49758 667.5152,252.25544 667.47132,252.1302 C 667.46282,252.10596 667.43669,252.08149 667.39867,252.0622 C 667.17473,251.94856 666.30084,250.97208 666.09149,250.60156 C 666.06575,250.556 666.04145,250.49972 666.03751,250.47653 L 666.03035,250.43435 L 666.23505,250.33732 C 666.34763,250.28395 666.62869,250.15377 666.85962,250.04804 C 667.55376,249.73024 667.4887,249.77144 667.88325,249.39985 C 668.25237,249.05221 668.3867,248.94442 668.46511,248.93297 C 668.75595,248.8905 668.95135,248.84285 669.13755,248.76901 C 669.33588,248.69035 669.40679,248.63421 670.03203,248.06083 C 670.54799,247.58767 670.71137,247.44292 670.8113,247.37042 C 670.97439,247.25208 671.18847,247.22163 671.78274,247.23221 C 672.13614,247.23851 672.19958,247.23641 672.25904,247.21644 C 672.36674,247.1803 672.45692,247.1155 672.58279,246.98382 C 672.64681,246.91682 672.77806,246.79885 672.87445,246.72166 C 673.24856,246.42208 674.04705,245.84593 674.44439,245.58888 C 674.67943,245.43683 674.68008,245.43633 674.98007,245.16925 C 675.22923,244.94743 675.39807,244.82289 675.49643,244.78835 C 675.58908,244.75583 675.59851,244.75652 675.64631,244.79941 C 675.69297,244.84126 676.0796,245.44524 676.11156,245.52619 C 676.17022,245.67479 676.2026,245.8324 676.14186,245.95698 C 676.02777,246.19098 675.86297,246.52747 675.79133,246.65624 C 675.19517,247.79122 675.00425,248.24559 675.084,248.3396 C 675.1557,248.42409 675.46288,248.47448 675.79339,248.45596 C 675.9034,248.4498 676.01778,248.4388 676.04756,248.43152 C 676.07735,248.42425 676.21018,248.36664 676.34275,248.3035 C 676.62549,248.16885 676.72485,248.13565 676.77352,248.15963 C 676.82255,248.18377 676.87033,248.28368 676.90291,248.43016 C 676.93399,248.56987 676.95,248.82083 676.93293,248.9008 C 676.91033,249.00668 676.81254,249.07111 676.59946,249.12053 C 676.28376,249.19374 675.79997,249.46104 675.30205,249.83735 C 674.83522,250.19017 674.5191,250.51873 674.37228,250.80369 C 674.2891,250.96516 674.26527,251.08193 674.19471,251.34152 C 674.14219,251.53472 673.85136,254.161 673.84971,254.22221 C 673.84923,254.24006 673.89888,251.37606 673.89578,251.37598 C 673.89269,251.37589 673.64642,251.91968 673.64204,251.91957 C 673.63764,251.91946 673.11871,252.75283 673.06331,252.72691 C 673.00793,252.701 673.80733,250.80494 674.00659,250.56762 C 674.19417,250.34422 674.61935,249.95762 675.15821,249.52054 C 675.49133,249.25033 675.66447,249.12238 675.74673,249.08564 C 675.77864,249.07139 675.91408,249.04038 676.04771,249.01673 C 676.37771,248.95833 676.46206,248.92161 676.46438,248.83534 C 676.46693,248.74093 676.35364,248.6783 676.20159,248.69007 C 676.00232,248.70548 675.77864,248.78336 675.50673,248.932 C 675.21556,249.09116 674.97882,249.13474 674.69752,249.08098 C 674.61778,249.06572 674.51564,249.03878 674.47055,249.02107 C 674.37379,248.98309 674.3609,248.95448 674.38561,248.83251 C 674.43885,248.56969 674.63469,248.14928 675.23157,247.01641 C 675.42032,246.65819 675.58974,246.32216 675.60808,246.26968 C 675.62641,246.2172 675.70095,246.1355 675.71895,246.02591 C 675.74678,245.85642 675.60284,245.61573 675.49172,245.52411 C 675.26474,245.35531 675.19402,245.31107 675.07986,245.35924 C 674.8728,245.44662 674.59341,245.86971 673.83099,246.90503 C 672.60898,248.56442 672.46623,248.73643 672.13013,248.95442 C 671.66036,249.2591 670.92441,249.86282 670.32852,250.43232 C 669.9134,250.82905 669.60231,251.30462 669.47614,251.4267 C 669.39421,251.50599 669.04627,251.93415 668.92411,252.03686 C 668.30525,252.43042 667.87241,252.85149 667.49529,253.47745 L 667.43126,253.59757 L 667.38511,253.5434 z M 668.46397,251.31116 C 668.7204,251.18608 669.03749,250.97543 669.19076,250.82834 C 669.3172,250.707 669.36801,250.58569 669.29744,250.57367 C 669.25183,250.5659 669.12809,250.63188 668.81319,250.83188 C 668.45212,251.06122 668.34711,251.11421 668.24396,251.11911 C 668.03778,251.12891 667.57411,251.05103 667.34278,250.96775 C 667.18243,250.91001 667.12695,250.87786 667.07592,250.81312 C 667.0435,250.772 667.0416,250.76154 667.04988,250.67014 C 667.06051,250.55273 667.12182,250.42 667.22682,250.28711 C 667.28065,250.21898 667.32856,250.18046 667.46856,250.09276 C 667.87127,249.84051 668.31501,249.62936 668.77187,249.47259 C 668.87754,249.43633 668.97637,249.40185 668.99148,249.39596 C 669.01507,249.38677 669.00758,249.37909 668.93855,249.34168 C 668.82318,249.27914 668.67294,249.22259 668.59041,249.21064 C 668.50801,249.1987 668.47708,249.21081 668.28301,249.33094 C 668.1367,249.42152 667.42448,249.89761 667.08857,250.12936 C 666.95522,250.22138 666.75501,250.35743 666.64366,250.43169 C 666.5323,250.50597 666.43344,250.57568 666.42397,250.58662 C 666.39823,250.61637 666.40097,250.69218 666.43,250.75379 C 666.46192,250.82148 666.61497,251.01116 666.69764,251.08544 L 666.75927,251.14083 L 667.5157,251.25385 C 667.93173,251.31603 668.28739,251.36796 668.30605,251.36927 C 668.32471,251.37057 668.39578,251.34443 668.46397,251.31116 z M 668.29973,250.79286 C 668.50561,250.7809 668.54927,250.76106 668.7151,250.60429 C 668.88527,250.44343 669.05711,250.21709 669.12323,250.06675 L 669.14624,250.01443 L 669.09433,250.02128 C 669.06579,250.02503 668.76941,250.05586 668.43571,250.08977 L 667.829,250.15141 L 667.77344,250.19488 C 667.66847,250.27701 667.52502,250.42275 667.47068,250.50247 C 667.40632,250.59689 667.39992,250.64727 667.44798,250.68095 C 667.50243,250.7191 667.68326,250.76303 667.85548,250.77995 C 668.08353,250.80236 668.11878,250.80338 668.29973,250.79286 z M 670.47827,249.80701 C 670.53764,249.76762 670.65353,249.6522 670.93932,249.34778 C 671.15026,249.12309 671.34955,248.93569 671.42687,248.88931 C 671.45643,248.87158 671.51608,248.84475 671.55945,248.82967 C 671.65746,248.79558 671.72169,248.75713 671.81412,248.67723 C 672.00854,248.50914 672.20844,248.1523 672.18313,248.01851 C 672.17567,247.9791 672.17451,247.97863 672.06448,247.97131 C 671.67055,247.94514 671.36409,248.03082 671.14299,248.22897 C 670.88826,248.45727 670.55413,249.05801 670.43071,249.50963 C 670.38192,249.68818 670.369,249.77777 670.38684,249.81379 C 670.4054,249.85123 670.4124,249.85071 670.47827,249.80701 z M 669.75335,249.33118 C 670.09096,249.26438 670.36935,249.20765 670.37201,249.20511 C 670.37465,249.20258 670.40977,249.09665 670.45005,248.96973 C 670.6136,248.45438 670.74114,248.22639 670.92962,248.11242 C 671.20958,247.94314 671.78787,247.7617 672.1084,247.74258 C 672.22476,247.73563 672.24446,247.72998 672.3505,247.67319 C 672.50811,247.5888 672.7391,247.43215 673.05617,247.19467 C 673.20264,247.08495 673.40034,246.94052 673.49551,246.87371 C 673.73961,246.70234 673.8601,246.58426 673.86198,246.51459 C 673.8623,246.50286 673.85711,246.49315 673.85045,246.49297 C 673.8438,246.49281 673.68645,246.59884 673.50079,246.72861 C 673.31512,246.85838 673.04174,247.04509 672.89326,247.14353 C 672.3744,247.48755 672.45133,247.46169 671.94052,247.46372 C 671.71568,247.46461 671.41018,247.46798 671.26164,247.47121 C 671.00014,247.47689 670.98987,247.47821 670.93762,247.51284 C 670.90795,247.5325 670.71779,247.71525 670.51504,247.91894 C 670.07326,248.36277 669.69447,248.72049 669.54727,248.83287 C 669.48801,248.87811 669.3962,248.93395 669.34323,248.95696 C 669.23699,249.00312 669.20246,249.03343 669.15576,249.1215 C 669.12056,249.1879 669.08207,249.33573 669.07961,249.41403 C 669.07814,249.4608 669.08095,249.46517 669.10875,249.45921 C 669.12567,249.45559 669.41574,249.39797 669.75335,249.33118 z " + id="path20060" + sodipodi:nodetypes="csssssscccscsscsssssscsssscsssssssssssssssscssssccssssscscscscccccsscsssssssssssscsscccsccsccccccsssccssssscsssccssssssssssscsscsssssc" /> + <g + transform="matrix(0.437568,0,0,0.437568,375.8931,217.9153)" + id="g20062" + style="fill:#002ddb;fill-opacity:1"> + <path + id="path20064" + d="M 680.83905,76.838803 C 680.83905,76.838803 679.45446,81.3797 679.41945,81.449838 C 679.22156,81.846006 678.85659,82.994032 678.98224,83.364797 C 679.13021,83.801538 679.68481,84.436589 679.94365,84.754121 C 680.2025,85.071759 680.31342,85.230473 680.23945,85.349692 C 680.16554,85.468783 679.20413,85.468783 679.20413,85.468783 C 679.20413,85.468783 678.46465,84.396936 678.31664,83.999943 C 678.16882,83.603079 678.09481,82.769436 678.24277,82.491624 C 678.39065,82.213703 680.83905,76.838803 680.83905,76.838803 z " + i:knockout="Off" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:21.03624916;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <polygon + id="polygon20066" + points="272.565,567.14 214.39,617.501 287.326,574.086 272.565,567.14 " + a:adobe-blending-mode="lighten" + i:knockout="Off" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:17.46648407;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + transform="matrix(4.348075e-2,0,0,4.188821e-2,659.2285,53.92804)" /> + <polygon + id="polygon20068" + points="329.873,549.776 230.887,678.282 344.633,558.457 329.873,549.776 " + a:adobe-blending-mode="lighten" + i:knockout="Off" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:17.46648407;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + transform="matrix(4.348075e-2,0,0,4.188821e-2,659.2285,53.92804)" /> + <polygon + id="polygon20070" + points="337.688,594.058 262.146,713.882 355.053,601.872 337.688,594.058 " + a:adobe-blending-mode="lighten" + i:knockout="Off" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:17.46648407;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + transform="matrix(4.348075e-2,0,0,4.188821e-2,659.2285,53.92804)" /> + <polygon + id="polygon20072" + points="381.97,588.847 318.584,737.327 402.809,593.19 381.97,588.847 " + a:adobe-blending-mode="lighten" + i:knockout="Off" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:17.46648407;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + transform="matrix(4.348075e-2,0,0,4.188821e-2,659.2285,53.92804)" /> + <polygon + id="polygon20074" + points="407.151,627.921 401.94,763.375 424.515,629.658 407.151,627.921 " + a:adobe-blending-mode="lighten" + i:knockout="Off" + style="fill:#002ddb;fill-opacity:1;stroke:none;stroke-width:17.46648407;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + transform="matrix(4.348075e-2,0,0,4.188821e-2,659.2285,53.92804)" /> + </g> + <path + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#0018ce;stroke-width:0.25647929;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 667.20325,253.00451 C 667.56898,254.08855 670.41003,256.39812 673.7867,255.29718 C 673.75257,254.52943 674.1359,252.21951 674.05736,251.55381" + id="path20076" + sodipodi:nodetypes="ccc" /> + </g> + </g> + </g> + <g + transform="matrix(0.727272,0,0,0.727272,-484.42778,43.367608)" + id="g12358" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/16x16/actions/amarok_circle.png" + inkscape:export-xdpi="89.999962" + inkscape:export-ydpi="89.999962"> + <path + sodipodi:type="arc" + style="fill:url(#linearGradient12368);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path12360" + sodipodi:cx="366.7616" + sodipodi:cy="436.4032" + sodipodi:rx="10.639691" + sodipodi:ry="10.639691" + d="M 377.40129 436.4032 A 10.639691 10.639691 0 1 1 356.12191,436.4032 A 10.639691 10.639691 0 1 1 377.40129 436.4032 z" + transform="matrix(1.033865,0,0,1.033865,-359.2117,-416.8144)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:type="arc" + style="fill:url(#linearGradient12370);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path12362" + sodipodi:cx="418.94757" + sodipodi:cy="444.83228" + sodipodi:rx="9.8239412" + sodipodi:ry="9.8239412" + d="M 428.77151 444.83228 A 9.8239412 9.8239412 0 1 1 409.12363,444.83228 A 9.8239412 9.8239412 0 1 1 428.77151 444.83228 z" + transform="matrix(1.014646,0,0,1.014647,-405.1135,-416.9803)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:type="arc" + style="fill:url(#radialGradient12372);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path12364" + sodipodi:cx="418.51233" + sodipodi:cy="446.44885" + sodipodi:rx="11.005301" + sodipodi:ry="11.005301" + d="M 429.51763 446.44885 A 11.005301 11.005301 0 1 1 407.50703,446.44885 A 11.005301 11.005301 0 1 1 429.51763 446.44885 z" + transform="matrix(0.810701,0,0,0.810702,-319.3183,-327.5696)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + style="opacity:0.7;fill:url(#linearGradient12374);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 27.878882,34.367333 C 27.878882,38.733063 24.335692,34.874413 19.969982,34.874413 C 15.604272,34.874413 12.061082,38.733063 12.061082,34.367333 C 12.061082,30.001633 15.604272,26.458433 19.969982,26.458433 C 24.335692,26.458433 27.878882,30.001633 27.878882,34.367333 z " + id="path12366" + sodipodi:nodetypes="csssc" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" /> + </g> + <g + id="g12376" + transform="translate(-458.87418,30.994588)" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/22x22/actions/amarok_circle.png" + inkscape:export-xdpi="89.999962" + inkscape:export-ydpi="89.999962"> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + transform="matrix(1.033865,0,0,1.033865,-359.2117,-416.8144)" + d="M 377.40129 436.4032 A 10.639691 10.639691 0 1 1 356.12191,436.4032 A 10.639691 10.639691 0 1 1 377.40129 436.4032 z" + sodipodi:ry="10.639691" + sodipodi:rx="10.639691" + sodipodi:cy="436.4032" + sodipodi:cx="366.7616" + id="path12378" + style="fill:url(#linearGradient12386);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + transform="matrix(1.014646,0,0,1.014647,-405.1135,-416.9803)" + d="M 428.77151 444.83228 A 9.8239412 9.8239412 0 1 1 409.12363,444.83228 A 9.8239412 9.8239412 0 1 1 428.77151 444.83228 z" + sodipodi:ry="9.8239412" + sodipodi:rx="9.8239412" + sodipodi:cy="444.83228" + sodipodi:cx="418.94757" + id="path12380" + style="fill:url(#linearGradient12388);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + transform="matrix(0.810701,0,0,0.810702,-319.3183,-327.5696)" + d="M 429.51763 446.44885 A 11.005301 11.005301 0 1 1 407.50703,446.44885 A 11.005301 11.005301 0 1 1 429.51763 446.44885 z" + sodipodi:ry="11.005301" + sodipodi:rx="11.005301" + sodipodi:cy="446.44885" + sodipodi:cx="418.51233" + id="path12382" + style="fill:url(#radialGradient12390);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + sodipodi:nodetypes="csssc" + id="path12384" + d="M 27.878882,34.367333 C 27.878882,38.733063 24.335692,34.874413 19.969982,34.874413 C 15.604272,34.874413 12.061082,38.733063 12.061082,34.367333 C 12.061082,30.001633 15.604272,26.458433 19.969982,26.458433 C 24.335692,26.458433 27.878882,30.001633 27.878882,34.367333 z " + style="opacity:0.7;fill:url(#linearGradient12392);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" /> + </g> + <g + inkscape:export-ydpi="89.999962" + inkscape:export-xdpi="89.999962" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/32x32/actions/amarok_circle.png" + transform="matrix(1.454545,0,0,1.454545,-427.42278,32.633188)" + id="g12394"> + <path + sodipodi:type="arc" + style="opacity:1;fill:url(#linearGradient12404);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path12396" + sodipodi:cx="366.7616" + sodipodi:cy="436.4032" + sodipodi:rx="10.639691" + sodipodi:ry="10.639691" + d="M 377.40129 436.4032 A 10.639691 10.639691 0 1 1 356.12191,436.4032 A 10.639691 10.639691 0 1 1 377.40129 436.4032 z" + transform="matrix(1.033865,0,0,1.033865,-358.4555,-432.1183)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:type="arc" + style="opacity:1;fill:url(#linearGradient12406);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path12398" + sodipodi:cx="418.94757" + sodipodi:cy="444.83228" + sodipodi:rx="9.8239412" + sodipodi:ry="9.8239412" + d="M 428.77151 444.83228 A 9.8239412 9.8239412 0 1 1 409.12363,444.83228 A 9.8239412 9.8239412 0 1 1 428.77151 444.83228 z" + transform="matrix(1.068817,0,0,1.068817,-427.0518,-456.3806)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:type="arc" + style="opacity:1;fill:url(#radialGradient12408);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path12400" + sodipodi:cx="418.51233" + sodipodi:cy="446.44885" + sodipodi:rx="11.005301" + sodipodi:ry="11.005301" + d="M 429.51763 446.44885 A 11.005301 11.005301 0 1 1 407.50703,446.44885 A 11.005301 11.005301 0 1 1 429.51763 446.44885 z" + transform="matrix(0.908653,0,0,0.908654,-359.556,-386.6038)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + style="opacity:0.7;fill:url(#linearGradient12410);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 30.226478,19.072457 C 30.226478,24.316478 25.970475,19.68155 20.726479,19.68155 C 15.482481,19.68155 11.226478,24.316478 11.226478,19.072457 C 11.226478,13.828473 15.482481,9.5724574 20.726479,9.5724574 C 25.970475,9.5724574 30.226478,13.828473 30.226478,19.072457 z " + id="path12402" + sodipodi:nodetypes="csssc" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" /> + </g> + <g + id="g12412" + transform="matrix(2.181817,0,0,2.181817,-386.49658,10.768708)" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/48x48/actions/amarok_circle.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + transform="matrix(1.033865,0,0,1.033865,-358.4555,-432.1183)" + d="M 377.40129 436.4032 A 10.639691 10.639691 0 1 1 356.12191,436.4032 A 10.639691 10.639691 0 1 1 377.40129 436.4032 z" + sodipodi:ry="10.639691" + sodipodi:rx="10.639691" + sodipodi:cy="436.4032" + sodipodi:cx="366.7616" + id="path12414" + style="opacity:1;fill:url(#linearGradient12422);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + transform="matrix(1.068817,0,0,1.068817,-427.0518,-456.3806)" + d="M 428.77151 444.83228 A 9.8239412 9.8239412 0 1 1 409.12363,444.83228 A 9.8239412 9.8239412 0 1 1 428.77151 444.83228 z" + sodipodi:ry="9.8239412" + sodipodi:rx="9.8239412" + sodipodi:cy="444.83228" + sodipodi:cx="418.94757" + id="path12416" + style="opacity:1;fill:url(#linearGradient12424);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + transform="matrix(0.908653,0,0,0.908654,-359.556,-386.6038)" + d="M 429.51763 446.44885 A 11.005301 11.005301 0 1 1 407.50703,446.44885 A 11.005301 11.005301 0 1 1 429.51763 446.44885 z" + sodipodi:ry="11.005301" + sodipodi:rx="11.005301" + sodipodi:cy="446.44885" + sodipodi:cx="418.51233" + id="path12418" + style="opacity:1;fill:url(#radialGradient12426);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + sodipodi:nodetypes="csssc" + id="path12420" + d="M 30.226478,19.072457 C 30.226478,24.316478 25.970475,19.68155 20.726479,19.68155 C 15.482481,19.68155 11.226478,24.316478 11.226478,19.072457 C 11.226478,13.828473 15.482481,9.5724574 20.726479,9.5724574 C 25.970475,9.5724574 30.226478,13.828473 30.226478,19.072457 z " + style="opacity:0.7;fill:url(#linearGradient12428);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" /> + </g> + <g + inkscape:export-ydpi="89.999962" + inkscape:export-xdpi="89.999962" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/64x64/actions/amarok_circle.png" + transform="matrix(2.90909,0,0,2.90909,-338.57038,-11.095812)" + id="g12430"> + <path + sodipodi:type="arc" + style="opacity:1;fill:url(#linearGradient12440);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path12432" + sodipodi:cx="366.7616" + sodipodi:cy="436.4032" + sodipodi:rx="10.639691" + sodipodi:ry="10.639691" + d="M 377.40129 436.4032 A 10.639691 10.639691 0 1 1 356.12191,436.4032 A 10.639691 10.639691 0 1 1 377.40129 436.4032 z" + transform="matrix(1.033865,0,0,1.033865,-358.4555,-432.1183)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:type="arc" + style="opacity:1;fill:url(#linearGradient12442);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path12434" + sodipodi:cx="418.94757" + sodipodi:cy="444.83228" + sodipodi:rx="9.8239412" + sodipodi:ry="9.8239412" + d="M 428.77151 444.83228 A 9.8239412 9.8239412 0 1 1 409.12363,444.83228 A 9.8239412 9.8239412 0 1 1 428.77151 444.83228 z" + transform="matrix(1.068817,0,0,1.068817,-427.0518,-456.3806)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:type="arc" + style="opacity:1;fill:url(#radialGradient12444);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path12436" + sodipodi:cx="418.51233" + sodipodi:cy="446.44885" + sodipodi:rx="11.005301" + sodipodi:ry="11.005301" + d="M 429.51763 446.44885 A 11.005301 11.005301 0 1 1 407.50703,446.44885 A 11.005301 11.005301 0 1 1 429.51763 446.44885 z" + transform="matrix(0.908653,0,0,0.908654,-359.556,-386.6038)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + style="opacity:0.7;fill:url(#linearGradient12446);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 30.226478,19.072457 C 30.226478,24.316478 25.970475,19.68155 20.726479,19.68155 C 15.482481,19.68155 11.226478,24.316478 11.226478,19.072457 C 11.226478,13.828473 15.482481,9.5724574 20.726479,9.5724574 C 25.970475,9.5724574 30.226478,13.828473 30.226478,19.072457 z " + id="path12438" + sodipodi:nodetypes="csssc" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" /> + </g> + <g + id="g12933" + transform="translate(6,-16.453469)"> + <g + inkscape:export-ydpi="90.000092" + inkscape:export-xdpi="90.000092" + inkscape:export-filename="/home/lando/Projects/Amarok/icons/48/amarok_device.png" + transform="matrix(1.528514,0,0,1.528514,-480.2492,-1032.3651)" + id="g18811"> + <rect + ry="1.2004516" + rx="1.0886607" + y="891.08899" + x="657.85583" + height="30.584967" + width="18.215515" + id="rect18813" + style="fill:url(#linearGradient18837);fill-opacity:1;stroke:url(#linearGradient18839);stroke-width:0.81778795;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + sodipodi:nodetypes="cccczccc" + id="path18815" + d="M 660.47295,891.69884 L 674.4095,891.71056 C 674.93388,891.70665 675.43003,892.21284 675.44175,892.86688 L 675.46519,906.12429 C 674.5027,906.85633 672.34172,908.89947 666.58287,909.39084 C 660.88061,909.88222 661.00594,911.69357 658.60126,912.65604 C 658.46553,909.34481 658.48054,893.6958 658.48054,893.6958 C 658.55182,891.57307 659.01153,891.69396 660.47295,891.69884 z " + style="opacity:0.83589747;fill:url(#linearGradient18841);fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" /> + <rect + ry="0.65097278" + rx="0.65097278" + y="891.87561" + x="658.48016" + height="29.302814" + width="16.979269" + id="rect18817" + style="opacity:0.83589747;fill:url(#linearGradient18843);fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" /> + <path + sodipodi:nodetypes="ccccccccc" + id="path18819" + d="M 660.95037,891.70372 C 660.26694,891.77207 659.03378,892.32485 659.03378,893.74469 L 659.03378,919.01274 C 659.03378,920.13084 659.88807,921.02684 660.95037,921.02684 L 660.40065,921.02684 C 659.33834,921.02684 658.48405,920.13084 658.48405,919.01274 L 658.48405,892.98791 C 658.48112,892.13981 659.02,891.71837 659.53645,891.71837 L 660.95037,891.70372 z " + style="opacity:0.80512821;fill:url(#linearGradient18845);fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" /> + <g + transform="translate(-0.352721,0)" + id="g18821"> + <path + sodipodi:type="arc" + style="opacity:0.80512821;fill:url(#linearGradient18847);fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + id="path18823" + sodipodi:cx="-63.387074" + sodipodi:cy="79.51268" + sodipodi:rx="24.243662" + sodipodi:ry="24.243662" + d="M -39.143412 79.51268 A 24.243662 24.243662 0 1 1 -87.630735,79.51268 A 24.243662 24.243662 0 1 1 -39.143412 79.51268 z" + transform="matrix(0.298537,0,0,0.29989,686.2397,887.8487)" /> + <path + sodipodi:type="arc" + style="fill:url(#linearGradient18849);fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + id="path18825" + sodipodi:cx="-63.387074" + sodipodi:cy="79.51268" + sodipodi:rx="24.243662" + sodipodi:ry="24.243662" + d="M -39.143412 79.51268 A 24.243662 24.243662 0 1 1 -87.630735,79.51268 A 24.243662 24.243662 0 1 1 -39.143412 79.51268 z" + transform="matrix(0.286595,0,0,0.287894,685.4829,888.8026)" /> + <path + sodipodi:type="arc" + style="opacity:0.80512821;fill:#fcfcfc;fill-opacity:1;stroke:#2255dc;stroke-width:0.30210102;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path18827" + sodipodi:cx="-63.63961" + sodipodi:cy="81.280441" + sodipodi:rx="8.3337584" + sodipodi:ry="8.3337584" + d="M -55.305852 81.280441 A 8.3337584 8.3337584 0 1 1 -71.973369,81.280441 A 8.3337584 8.3337584 0 1 1 -55.305852 81.280441 z" + transform="matrix(0.315182,0,0,0.316611,687.4697,885.855)" /> + </g> + <g + transform="translate(-0.500974,0)" + id="g18829"> + <rect + style="fill:url(#linearGradient18851);fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + id="rect18831" + width="13.39068" + height="10.261462" + x="660.76923" + y="892.48651" + rx="0.47309223" + ry="0.47309223" /> + <path + inkscape:export-ydpi="90.000000" + inkscape:export-xdpi="90.000000" + inkscape:export-filename="/home/steve/Desktop/creative zen.png" + id="path18833" + d="M 662.10212,892.81263 C 661.76954,892.81263 661.25353,893.17254 661.25353,893.51185 L 661.27512,898.59428 C 667.1236,899.95132 666.49575,897.48685 673.60774,897.84643 L 673.6756,893.47158 C 673.6756,893.13226 673.40784,892.85908 673.07527,892.85908 L 662.10212,892.81263 z " + style="opacity:0.29787233;fill:url(#linearGradient18853);fill-opacity:1;stroke:none;stroke-width:3.00600219;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + sodipodi:nodetypes="ccccccc" /> + </g> + <path + id="path18835" + d="M 662.14742,893.01061 C 661.53233,893.01061 661.03722,893.29822 661.03722,893.65547 L 661.03722,901.73118 C 661.03722,902.0884 661.53233,902.37602 662.14742,902.37602 L 662.25582,902.37602 C 661.91247,902.26697 661.67037,902.0562 661.67037,901.80422 L 661.67037,893.6227 C 661.67037,893.33354 661.98373,893.09706 662.41195,893.01061 L 662.14742,893.01061 z " + style="opacity:0.45581396;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" /> + </g> + <rect + inkscape:export-ydpi="90.000092" + inkscape:export-xdpi="90.000092" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/48x48/actions/amarok_device.png" + y="329.05167" + x="515.21399" + height="48" + width="48" + id="rect11352" + style="fill:#b9cff9;fill-opacity:0;stroke:none;stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" /> + </g> + <g + id="g12917" + transform="translate(6,-19.944802)"> + <g + inkscape:export-ydpi="90.000092" + inkscape:export-xdpi="90.000092" + inkscape:export-filename="/home/lando/Projects/Amarok/icons/32/amarok_device.png" + transform="matrix(1.005388,0,0,1.005388,-193.3432,-546.72205)" + id="g18797"> + <rect + style="fill:url(#linearGradient18855);fill-opacity:1;stroke:url(#linearGradient18857);stroke-width:1.24330235;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect15671" + width="18.215515" + height="30.584967" + x="657.85583" + y="891.08899" + rx="1.6551166" + ry="1.8250749" /> + <path + style="opacity:0.83589747;fill:url(#linearGradient18859);fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 660.47295,891.69884 L 674.4095,891.71056 C 674.93388,891.70665 675.43003,892.21284 675.44175,892.86688 L 675.46519,906.12429 C 674.5027,906.85633 672.34172,908.89947 666.58287,909.39084 C 660.88061,909.88222 661.00594,911.69357 658.60126,912.65604 C 658.46553,909.34481 658.48054,893.6958 658.48054,893.6958 C 658.55182,891.57307 659.01153,891.69396 660.47295,891.69884 z " + id="path15684" + sodipodi:nodetypes="cccczccc" /> + <rect + style="opacity:0.83589747;fill:url(#linearGradient18861);fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + id="rect15673" + width="16.979269" + height="29.302814" + x="658.48016" + y="891.87561" + rx="0.98968923" + ry="0.98968923" /> + <path + style="opacity:0.80512821;fill:url(#linearGradient18863);fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 660.95037,891.70372 C 660.26694,891.77207 659.03378,892.32485 659.03378,893.74469 L 659.03378,919.01274 C 659.03378,920.13084 659.88807,921.02684 660.95037,921.02684 L 660.40065,921.02684 C 659.33834,921.02684 658.48405,920.13084 658.48405,919.01274 L 658.48405,892.98791 C 658.48112,892.13981 659.02,891.71837 659.53645,891.71837 L 660.95037,891.70372 z " + id="path15675" + sodipodi:nodetypes="ccccccccc" /> + <g + id="g18788" + transform="translate(-0.352721,0)"> + <path + transform="matrix(0.298537,0,0,0.29989,686.2397,887.8487)" + d="M -39.143412 79.51268 A 24.243662 24.243662 0 1 1 -87.630735,79.51268 A 24.243662 24.243662 0 1 1 -39.143412 79.51268 z" + sodipodi:ry="24.243662" + sodipodi:rx="24.243662" + sodipodi:cy="79.51268" + sodipodi:cx="-63.387074" + id="path15677" + style="opacity:0.80512821;fill:url(#linearGradient18865);fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + sodipodi:type="arc" /> + <path + transform="matrix(0.286595,0,0,0.287894,685.4829,888.8026)" + d="M -39.143412 79.51268 A 24.243662 24.243662 0 1 1 -87.630735,79.51268 A 24.243662 24.243662 0 1 1 -39.143412 79.51268 z" + sodipodi:ry="24.243662" + sodipodi:rx="24.243662" + sodipodi:cy="79.51268" + sodipodi:cx="-63.387074" + id="path15680" + style="fill:url(#linearGradient18867);fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + sodipodi:type="arc" /> + <path + transform="matrix(0.315182,0,0,0.316611,687.4697,885.855)" + d="M -55.305852 81.280441 A 8.3337584 8.3337584 0 1 1 -71.973369,81.280441 A 8.3337584 8.3337584 0 1 1 -55.305852 81.280441 z" + sodipodi:ry="8.3337584" + sodipodi:rx="8.3337584" + sodipodi:cy="81.280441" + sodipodi:cx="-63.63961" + id="path15682" + style="opacity:0.80512821;fill:#fcfcfc;fill-opacity:1;stroke:#2255dc;stroke-width:0.45929128;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + </g> + <g + id="g18780" + transform="translate(-0.500974,0)"> + <rect + ry="0.7192533" + rx="0.7192533" + y="892.48651" + x="660.76923" + height="10.261462" + width="13.39068" + id="rect15686" + style="fill:url(#linearGradient18869);fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" /> + <path + sodipodi:nodetypes="ccccccc" + style="opacity:0.29787233;fill:url(#linearGradient18871);fill-opacity:1;stroke:none;stroke-width:3.00600219;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 662.10212,892.81263 C 661.76954,892.81263 661.25353,893.17254 661.25353,893.51185 L 661.27512,898.59428 C 667.1236,899.95132 666.49575,897.48685 673.60774,897.84643 L 673.6756,893.47158 C 673.6756,893.13226 673.40784,892.85908 673.07527,892.85908 L 662.10212,892.81263 z " + id="path15688" + inkscape:export-filename="/home/steve/Desktop/creative zen.png" + inkscape:export-xdpi="90.000000" + inkscape:export-ydpi="90.000000" /> + </g> + <path + style="opacity:0.45581396;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 662.14742,893.01061 C 661.53233,893.01061 661.03722,893.29822 661.03722,893.65547 L 661.03722,901.73118 C 661.03722,902.0884 661.53233,902.37602 662.14742,902.37602 L 662.25582,902.37602 C 661.91247,902.26697 661.67037,902.0562 661.67037,901.80422 L 661.67037,893.6227 C 661.67037,893.33354 661.98373,893.09706 662.41195,893.01061 L 662.14742,893.01061 z " + id="path15690" /> + </g> + <rect + inkscape:export-ydpi="90.000092" + inkscape:export-xdpi="90.000092" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/32x32/actions/amarok_device.png" + style="fill:#b9cff9;fill-opacity:0;stroke:none;stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + id="rect11354" + width="32" + height="32" + x="461.21399" + y="348.543" /> + </g> + <g + id="g12949" + transform="translate(6,-15.94468)"> + <g + inkscape:export-ydpi="90.000092" + inkscape:export-xdpi="90.000092" + inkscape:export-filename="/home/lando/Projects/Amarok/icons/64/amarok_device.png" + id="g18873" + transform="matrix(2.051656,0,0,2.051656,-753.16587,-1515.0401)"> + <rect + style="fill:url(#linearGradient18899);fill-opacity:1;stroke:url(#linearGradient18901);stroke-width:0.60926414;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect18875" + width="18.215515" + height="30.584967" + x="657.85583" + y="891.08899" + rx="0.81106842" + ry="0.89435422" /> + <path + style="opacity:0.83589747;fill:url(#linearGradient18903);fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 660.47295,891.69884 L 674.4095,891.71056 C 674.93388,891.70665 675.43003,892.21284 675.44175,892.86688 L 675.46519,906.12429 C 674.5027,906.85633 672.34172,908.89947 666.58287,909.39084 C 660.88061,909.88222 661.00594,911.69357 658.60126,912.65604 C 658.46553,909.34481 658.48054,893.6958 658.48054,893.6958 C 658.55182,891.57307 659.01153,891.69396 660.47295,891.69884 z " + id="path18877" + sodipodi:nodetypes="cccczccc" /> + <rect + style="opacity:0.83589747;fill:url(#linearGradient18905);fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + id="rect18879" + width="16.979269" + height="29.302814" + x="658.48016" + y="891.87561" + rx="0.48498437" + ry="0.48498437" /> + <path + style="opacity:0.80512821;fill:url(#linearGradient18907);fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 660.95037,891.70372 C 660.26694,891.77207 659.03378,892.32485 659.03378,893.74469 L 659.03378,919.01274 C 659.03378,920.13084 659.88807,921.02684 660.95037,921.02684 L 660.40065,921.02684 C 659.33834,921.02684 658.48405,920.13084 658.48405,919.01274 L 658.48405,892.98791 C 658.48112,892.13981 659.02,891.71837 659.53645,891.71837 L 660.95037,891.70372 z " + id="path18881" + sodipodi:nodetypes="ccccccccc" /> + <g + id="g18883" + transform="translate(-0.352721,0)"> + <path + transform="matrix(0.298537,0,0,0.29989,686.2397,887.8487)" + d="M -39.143412 79.51268 A 24.243662 24.243662 0 1 1 -87.630735,79.51268 A 24.243662 24.243662 0 1 1 -39.143412 79.51268 z" + sodipodi:ry="24.243662" + sodipodi:rx="24.243662" + sodipodi:cy="79.51268" + sodipodi:cx="-63.387074" + id="path18885" + style="opacity:0.80512821;fill:url(#linearGradient18909);fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + sodipodi:type="arc" /> + <path + transform="matrix(0.286595,0,0,0.287894,685.4829,888.8026)" + d="M -39.143412 79.51268 A 24.243662 24.243662 0 1 1 -87.630735,79.51268 A 24.243662 24.243662 0 1 1 -39.143412 79.51268 z" + sodipodi:ry="24.243662" + sodipodi:rx="24.243662" + sodipodi:cy="79.51268" + sodipodi:cx="-63.387074" + id="path18887" + style="fill:url(#linearGradient18911);fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + sodipodi:type="arc" /> + <path + transform="matrix(0.315182,0,0,0.316611,687.4697,885.855)" + d="M -55.305852 81.280441 A 8.3337584 8.3337584 0 1 1 -71.973369,81.280441 A 8.3337584 8.3337584 0 1 1 -55.305852 81.280441 z" + sodipodi:ry="8.3337584" + sodipodi:rx="8.3337584" + sodipodi:cy="81.280441" + sodipodi:cx="-63.63961" + id="path18889" + style="opacity:0.80512821;fill:#fcfcfc;fill-opacity:1;stroke:#2255dc;stroke-width:0.22506973;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + </g> + <g + id="g18891" + transform="translate(-0.500974,0)"> + <rect + ry="0.35246074" + rx="0.35246074" + y="892.48651" + x="660.76923" + height="10.261462" + width="13.39068" + id="rect18893" + style="fill:url(#linearGradient18913);fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" /> + <path + sodipodi:nodetypes="ccccccc" + style="opacity:0.29787233;fill:url(#linearGradient18915);fill-opacity:1;stroke:none;stroke-width:3.00600219;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 662.10212,892.81263 C 661.76954,892.81263 661.25353,893.17254 661.25353,893.51185 L 661.27512,898.59428 C 667.1236,899.95132 666.49575,897.48685 673.60774,897.84643 L 673.6756,893.47158 C 673.6756,893.13226 673.40784,892.85908 673.07527,892.85908 L 662.10212,892.81263 z " + id="path18895" + inkscape:export-filename="/home/steve/Desktop/creative zen.png" + inkscape:export-xdpi="90.000000" + inkscape:export-ydpi="90.000000" /> + </g> + <path + style="opacity:0.45581396;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 662.14742,893.01061 C 661.53233,893.01061 661.03722,893.29822 661.03722,893.65547 L 661.03722,901.73118 C 661.03722,902.0884 661.53233,902.37602 662.14742,902.37602 L 662.25582,902.37602 C 661.91247,902.26697 661.67037,902.0562 661.67037,901.80422 L 661.67037,893.6227 C 661.67037,893.33354 661.98373,893.09706 662.41195,893.01061 L 662.14742,893.01061 z " + id="path18897" /> + </g> + <rect + inkscape:export-ydpi="90.000092" + inkscape:export-xdpi="90.000092" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/64x64/actions/amarok_device.png" + style="fill:#b9cff9;fill-opacity:0;stroke:none;stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + id="rect11356" + width="64" + height="64" + x="583.21399" + y="312.54288" /> + </g> + <g + id="g11449" + transform="translate(-180.78601,-465.1341)" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/64x64/actions/amarok_zoom.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/icons/64/amarok_zoom.png" + transform="matrix(2.032372,0,0,2.032372,-554.02296,-1831.0992)" + id="g8057"> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_search.png" + style="fill:url(#linearGradient11418);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient11420);stroke-width:0.58479202;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 671.80685,1770.9388 C 674.11958,1774.6316 676.84781,1775.2997 679.81659,1778.7278 C 682.99323,1782.3959 683.16738,1783.6055 683.89447,1784.3973 C 683.84622,1785.221 682.80269,1786.0992 681.70783,1786.1313 C 681.207,1785.6395 680.23541,1785.3457 676.4096,1781.1719 C 672.69485,1777.1193 674.31211,1776.2676 669.87184,1772.7239 C 670.33961,1772.0402 670.896,1771.3566 671.80685,1770.9388 z " + id="path8059" + sodipodi:nodetypes="csccscc" /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_search.png" + transform="matrix(1.168793,-0.895106,0.447554,0.584397,45.33414,2000.44)" + d="M 433.09708 270.50006 A 1.2339805 0.78252429 0 1 1 430.62912,270.50006 A 1.2339805 0.78252429 0 1 1 433.09708 270.50006 z" + sodipodi:ry="0.78252429" + sodipodi:rx="1.2339805" + sodipodi:cy="270.50006" + sodipodi:cx="431.8631" + id="path8061" + style="fill:url(#radialGradient11422);fill-opacity:1;stroke:url(#linearGradient11424);stroke-width:0.66313559;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_search.png" + transform="matrix(0.87576,0,0,0.875761,297.5928,1374.699)" + d="M 428.77151 444.83228 A 9.8239412 9.8239412 0 1 1 409.12363,444.83228 A 9.8239412 9.8239412 0 1 1 428.77151 444.83228 z" + sodipodi:ry="9.8239412" + sodipodi:rx="9.8239412" + sodipodi:cy="444.83228" + sodipodi:cx="418.94757" + id="path8063" + style="fill:url(#linearGradient11426);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_search.png" + transform="matrix(0.802617,0,0,0.802617,328.5854,1405.939)" + d="M 429.51763 446.44885 A 11.005301 11.005301 0 1 1 407.50703,446.44885 A 11.005301 11.005301 0 1 1 429.51763 446.44885 z" + sodipodi:ry="11.005301" + sodipodi:rx="11.005301" + sodipodi:cy="446.44885" + sodipodi:cx="418.51233" + id="path8065" + style="fill:url(#radialGradient11428);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient11431);stroke-width:1.24592423;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_search.png" + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + sodipodi:nodetypes="csssc" + id="path8067" + d="M 673.37848,1764.2644 C 674.01994,1766.6583 669.38336,1763.4956 664.4608,1763.4956 C 659.53823,1763.4956 655.54311,1767.4231 655.54311,1764.2644 C 655.54311,1759.3419 659.53823,1755.3467 664.4608,1755.3467 C 669.38336,1755.3467 673.37848,1759.3419 673.37848,1764.2644 z " + style="opacity:0;fill:url(#linearGradient11433);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_add_lyrics.png" + sodipodi:nodetypes="ccccccccccccc" + style="fill:url(#linearGradient11435);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient11437);stroke-width:0.53549153;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + d="M 671.18526,1762.2911 L 666.4172,1762.2911 L 666.38286,1757.5714 L 662.4696,1757.5714 L 662.50394,1762.2911 L 657.79592,1762.3255 L 657.79592,1766.2937 L 662.50394,1766.2594 L 662.4696,1770.9603 L 666.38286,1770.9603 L 666.4172,1766.2594 L 671.18526,1766.2594 L 671.18526,1762.2911 z " + id="path8069" /> + </g> + <rect + y="1735.5784" + x="775" + height="64" + width="64" + id="rect11358" + style="fill:#b9cff9;fill-opacity:0;stroke:none;stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" /> + </g> + <g + id="g11398" + transform="translate(-180.78601,-465.1341)" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/48x48/actions/amarok_zoom.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/icons/48/amarok_zoom.png" + id="g8023" + transform="matrix(1.524279,0,0,1.524279,-287.56399,-929.00698)"> + <path + sodipodi:nodetypes="csccscc" + id="path8025" + d="M 671.80685,1770.9388 C 674.11958,1774.6316 676.84781,1775.2997 679.81659,1778.7278 C 682.99323,1782.3959 683.16738,1783.6055 683.89447,1784.3973 C 683.84622,1785.221 682.80269,1786.0992 681.70783,1786.1313 C 681.207,1785.6395 680.23541,1785.3457 676.4096,1781.1719 C 672.69485,1777.1193 674.31211,1776.2676 669.87184,1772.7239 C 670.33961,1772.0402 670.896,1771.3566 671.80685,1770.9388 z " + style="fill:url(#linearGradient8037);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient8039);stroke-width:0.58479202;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_search.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:type="arc" + style="fill:url(#radialGradient8041);fill-opacity:1;stroke:url(#linearGradient8043);stroke-width:0.66313559;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path8027" + sodipodi:cx="431.8631" + sodipodi:cy="270.50006" + sodipodi:rx="1.2339805" + sodipodi:ry="0.78252429" + d="M 433.09708 270.50006 A 1.2339805 0.78252429 0 1 1 430.62912,270.50006 A 1.2339805 0.78252429 0 1 1 433.09708 270.50006 z" + transform="matrix(1.168793,-0.895106,0.447554,0.584397,45.33414,2000.44)" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_search.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:type="arc" + style="fill:url(#linearGradient8045);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path8029" + sodipodi:cx="418.94757" + sodipodi:cy="444.83228" + sodipodi:rx="9.8239412" + sodipodi:ry="9.8239412" + d="M 428.77151 444.83228 A 9.8239412 9.8239412 0 1 1 409.12363,444.83228 A 9.8239412 9.8239412 0 1 1 428.77151 444.83228 z" + transform="matrix(0.87576,0,0,0.875761,297.5928,1374.699)" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_search.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:type="arc" + style="fill:url(#radialGradient8047);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient8049);stroke-width:1.24592423;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path8031" + sodipodi:cx="418.51233" + sodipodi:cy="446.44885" + sodipodi:rx="11.005301" + sodipodi:ry="11.005301" + d="M 429.51763 446.44885 A 11.005301 11.005301 0 1 1 407.50703,446.44885 A 11.005301 11.005301 0 1 1 429.51763 446.44885 z" + transform="matrix(0.802617,0,0,0.802617,328.5854,1405.939)" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_search.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + style="opacity:0;fill:url(#linearGradient8051);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 673.37848,1764.2644 C 674.01994,1766.6583 669.38336,1763.4956 664.4608,1763.4956 C 659.53823,1763.4956 655.54311,1767.4231 655.54311,1764.2644 C 655.54311,1759.3419 659.53823,1755.3467 664.4608,1755.3467 C 669.38336,1755.3467 673.37848,1759.3419 673.37848,1764.2644 z " + id="path8033" + sodipodi:nodetypes="csssc" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_search.png" /> + <path + id="path8035" + d="M 671.18526,1762.2911 L 666.4172,1762.2911 L 666.38286,1757.5714 L 662.4696,1757.5714 L 662.50394,1762.2911 L 657.79592,1762.3255 L 657.79592,1766.2937 L 662.50394,1766.2594 L 662.4696,1770.9603 L 666.38286,1770.9603 L 666.4172,1766.2594 L 671.18526,1766.2594 L 671.18526,1762.2911 z " + style="fill:url(#linearGradient8053);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient8055);stroke-width:0.53549153;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + sodipodi:nodetypes="ccccccccccccc" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_add_lyrics.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + </g> + <rect + style="fill:#b9cff9;fill-opacity:0;stroke:none;stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + id="rect11360" + width="48" + height="48" + x="709.20325" + y="1746.0011" /> + </g> + <g + id="g11388" + transform="translate(-180.78601,-465.1341)" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/32x32/actions/amarok_zoom.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/icons/32/amarok_zoom.png" + transform="matrix(1.016186,0,0,1.016186,-5.081475,-26.626316)" + id="g7926"> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_search.png" + style="fill:url(#linearGradient7934);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient7936);stroke-width:0.58479202;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 671.80685,1770.9388 C 674.11958,1774.6316 676.84781,1775.2997 679.81659,1778.7278 C 682.99323,1782.3959 683.16738,1783.6055 683.89447,1784.3973 C 683.84622,1785.221 682.80269,1786.0992 681.70783,1786.1313 C 681.207,1785.6395 680.23541,1785.3457 676.4096,1781.1719 C 672.69485,1777.1193 674.31211,1776.2676 669.87184,1772.7239 C 670.33961,1772.0402 670.896,1771.3566 671.80685,1770.9388 z " + id="path16011" + sodipodi:nodetypes="csccscc" /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_search.png" + transform="matrix(1.168793,-0.895106,0.447554,0.584397,45.33414,2000.44)" + d="M 433.09708 270.50006 A 1.2339805 0.78252429 0 1 1 430.62912,270.50006 A 1.2339805 0.78252429 0 1 1 433.09708 270.50006 z" + sodipodi:ry="0.78252429" + sodipodi:rx="1.2339805" + sodipodi:cy="270.50006" + sodipodi:cx="431.8631" + id="path16013" + style="fill:url(#radialGradient7938);fill-opacity:1;stroke:url(#linearGradient7940);stroke-width:0.66313559;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_search.png" + transform="matrix(0.87576,0,0,0.875761,297.5928,1374.699)" + d="M 428.77151 444.83228 A 9.8239412 9.8239412 0 1 1 409.12363,444.83228 A 9.8239412 9.8239412 0 1 1 428.77151 444.83228 z" + sodipodi:ry="9.8239412" + sodipodi:rx="9.8239412" + sodipodi:cy="444.83228" + sodipodi:cx="418.94757" + id="path16015" + style="fill:url(#linearGradient7942);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_search.png" + transform="matrix(0.802617,0,0,0.802617,328.5854,1405.939)" + d="M 429.51763 446.44885 A 11.005301 11.005301 0 1 1 407.50703,446.44885 A 11.005301 11.005301 0 1 1 429.51763 446.44885 z" + sodipodi:ry="11.005301" + sodipodi:rx="11.005301" + sodipodi:cy="446.44885" + sodipodi:cx="418.51233" + id="path16017" + style="fill:url(#radialGradient7944);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient7946);stroke-width:1.24592423;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_search.png" + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + sodipodi:nodetypes="csssc" + id="path16019" + d="M 673.37848,1764.2644 C 674.01994,1766.6583 669.38336,1763.4956 664.4608,1763.4956 C 659.53823,1763.4956 655.54311,1767.4231 655.54311,1764.2644 C 655.54311,1759.3419 659.53823,1755.3467 664.4608,1755.3467 C 669.38336,1755.3467 673.37848,1759.3419 673.37848,1764.2644 z " + style="opacity:0;fill:url(#linearGradient7948);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_add_lyrics.png" + sodipodi:nodetypes="ccccccccccccc" + style="fill:url(#linearGradient7950);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient7952);stroke-width:0.53549153;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + d="M 671.18526,1762.2911 L 666.4172,1762.2911 L 666.38286,1757.5714 L 662.4696,1757.5714 L 662.50394,1762.2911 L 657.79592,1762.3255 L 657.79592,1766.2937 L 662.50394,1766.2594 L 662.4696,1770.9603 L 666.38286,1770.9603 L 666.4172,1766.2594 L 671.18526,1766.2594 L 671.18526,1762.2911 z " + id="path16021" /> + </g> + <rect + y="1756.7124" + x="659.42999" + height="32" + width="32" + id="rect11362" + style="fill:#b9cff9;fill-opacity:0;stroke:none;stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" /> + </g> + <g + id="g11378" + transform="translate(-180.78601,-465.1341)" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/22x22/actions/amarok_zoom.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/icons/22/amarok_zoom.png" + id="g7954" + transform="matrix(0.698628,0,0,0.698628,162.58242,539.52259)"> + <path + sodipodi:nodetypes="csccscc" + id="path7956" + d="M 671.80685,1770.9388 C 674.11958,1774.6316 676.84781,1775.2997 679.81659,1778.7278 C 682.99323,1782.3959 683.16738,1783.6055 683.89447,1784.3973 C 683.84622,1785.221 682.80269,1786.0992 681.70783,1786.1313 C 681.207,1785.6395 680.23541,1785.3457 676.4096,1781.1719 C 672.69485,1777.1193 674.31211,1776.2676 669.87184,1772.7239 C 670.33961,1772.0402 670.896,1771.3566 671.80685,1770.9388 z " + style="fill:url(#linearGradient7968);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient7970);stroke-width:0.58479202;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_search.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:type="arc" + style="fill:url(#radialGradient7972);fill-opacity:1;stroke:url(#linearGradient7974);stroke-width:0.66313559;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path7958" + sodipodi:cx="431.8631" + sodipodi:cy="270.50006" + sodipodi:rx="1.2339805" + sodipodi:ry="0.78252429" + d="M 433.09708 270.50006 A 1.2339805 0.78252429 0 1 1 430.62912,270.50006 A 1.2339805 0.78252429 0 1 1 433.09708 270.50006 z" + transform="matrix(1.168793,-0.895106,0.447554,0.584397,45.33414,2000.44)" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_search.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:type="arc" + style="fill:url(#linearGradient7976);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path7960" + sodipodi:cx="418.94757" + sodipodi:cy="444.83228" + sodipodi:rx="9.8239412" + sodipodi:ry="9.8239412" + d="M 428.77151 444.83228 A 9.8239412 9.8239412 0 1 1 409.12363,444.83228 A 9.8239412 9.8239412 0 1 1 428.77151 444.83228 z" + transform="matrix(0.87576,0,0,0.875761,297.5928,1374.699)" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_search.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:type="arc" + style="fill:url(#radialGradient7978);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient7980);stroke-width:1.24592423;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path7962" + sodipodi:cx="418.51233" + sodipodi:cy="446.44885" + sodipodi:rx="11.005301" + sodipodi:ry="11.005301" + d="M 429.51763 446.44885 A 11.005301 11.005301 0 1 1 407.50703,446.44885 A 11.005301 11.005301 0 1 1 429.51763 446.44885 z" + transform="matrix(0.802617,0,0,0.802617,328.5854,1405.939)" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_search.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + style="opacity:0;fill:url(#linearGradient7982);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 673.37848,1764.2644 C 674.01994,1766.6583 669.38336,1763.4956 664.4608,1763.4956 C 659.53823,1763.4956 655.54311,1767.4231 655.54311,1764.2644 C 655.54311,1759.3419 659.53823,1755.3467 664.4608,1755.3467 C 669.38336,1755.3467 673.37848,1759.3419 673.37848,1764.2644 z " + id="path7964" + sodipodi:nodetypes="csssc" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_search.png" /> + <path + id="path7966" + d="M 671.18526,1762.2911 L 666.4172,1762.2911 L 666.38286,1757.5714 L 662.4696,1757.5714 L 662.50394,1762.2911 L 657.79592,1762.3255 L 657.79592,1766.2937 L 662.50394,1766.2594 L 662.4696,1770.9603 L 666.38286,1770.9603 L 666.4172,1766.2594 L 671.18526,1766.2594 L 671.18526,1762.2911 z " + style="fill:url(#linearGradient7984);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient7986);stroke-width:0.53549153;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + sodipodi:nodetypes="ccccccccccccc" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_add_lyrics.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + </g> + <rect + style="fill:#b9cff9;fill-opacity:0;stroke:none;stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + id="rect11364" + width="22" + height="22" + x="619.43414" + y="1765.5682" /> + </g> + <g + id="g11368" + transform="translate(-180.78601,-465.1341)" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/16x16/actions/amarok_zoom.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/icons/16/amarok_zoom.png" + transform="matrix(0.508093,0,0,0.508093,251.41491,875.32678)" + id="g7988"> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_search.png" + style="fill:url(#linearGradient8003);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient8005);stroke-width:0.58479202;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 671.80685,1770.9388 C 674.11958,1774.6316 676.84781,1775.2997 679.81659,1778.7278 C 682.99323,1782.3959 683.16738,1783.6055 683.89447,1784.3973 C 683.84622,1785.221 682.80269,1786.0992 681.70783,1786.1313 C 681.207,1785.6395 680.23541,1785.3457 676.4096,1781.1719 C 672.69485,1777.1193 674.31211,1776.2676 669.87184,1772.7239 C 670.33961,1772.0402 670.896,1771.3566 671.80685,1770.9388 z " + id="path7990" + sodipodi:nodetypes="csccscc" /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_search.png" + transform="matrix(1.168793,-0.895106,0.447554,0.584397,45.33414,2000.44)" + d="M 433.09708 270.50006 A 1.2339805 0.78252429 0 1 1 430.62912,270.50006 A 1.2339805 0.78252429 0 1 1 433.09708 270.50006 z" + sodipodi:ry="0.78252429" + sodipodi:rx="1.2339805" + sodipodi:cy="270.50006" + sodipodi:cx="431.8631" + id="path7992" + style="fill:url(#radialGradient8007);fill-opacity:1;stroke:url(#linearGradient8009);stroke-width:0.66313559;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_search.png" + transform="matrix(0.87576,0,0,0.875761,297.5928,1374.699)" + d="M 428.77151 444.83228 A 9.8239412 9.8239412 0 1 1 409.12363,444.83228 A 9.8239412 9.8239412 0 1 1 428.77151 444.83228 z" + sodipodi:ry="9.8239412" + sodipodi:rx="9.8239412" + sodipodi:cy="444.83228" + sodipodi:cx="418.94757" + id="path7994" + style="fill:url(#linearGradient8011);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_search.png" + transform="matrix(0.802617,0,0,0.802617,328.5854,1405.939)" + d="M 429.51763 446.44885 A 11.005301 11.005301 0 1 1 407.50703,446.44885 A 11.005301 11.005301 0 1 1 429.51763 446.44885 z" + sodipodi:ry="11.005301" + sodipodi:rx="11.005301" + sodipodi:cy="446.44885" + sodipodi:cx="418.51233" + id="path7996" + style="fill:url(#radialGradient8013);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient8015);stroke-width:1.24592423;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_search.png" + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + sodipodi:nodetypes="csssc" + id="path7998" + d="M 673.37848,1764.2644 C 674.01994,1766.6583 669.38336,1763.4956 664.4608,1763.4956 C 659.53823,1763.4956 655.54311,1767.4231 655.54311,1764.2644 C 655.54311,1759.3419 659.53823,1755.3467 664.4608,1755.3467 C 669.38336,1755.3467 673.37848,1759.3419 673.37848,1764.2644 z " + style="opacity:0;fill:url(#linearGradient8017);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_add_lyrics.png" + sodipodi:nodetypes="ccccccccccccc" + style="fill:url(#linearGradient8019);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient8021);stroke-width:0.53549153;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + d="M 671.18526,1762.2911 L 666.4172,1762.2911 L 666.38286,1757.5714 L 662.4696,1757.5714 L 662.50394,1762.2911 L 657.79592,1762.3255 L 657.79592,1766.2937 L 662.50394,1766.2594 L 662.4696,1770.9603 L 666.38286,1770.9603 L 666.4172,1766.2594 L 671.18526,1766.2594 L 671.18526,1762.2911 z " + id="path8001" /> + </g> + <rect + y="1766.9962" + x="583.67065" + height="16" + width="16" + id="rect11366" + style="fill:#b9cff9;fill-opacity:0;stroke:none;stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" /> + </g> + <g + id="g13325" + transform="translate(-17,-137)"> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/48x48/actions/amarok_favourite_genres.png" + transform="matrix(2.18182,0,0,2.18182,-450.7823,827.88471)" + id="g10464"> + <path + style="fill:url(#linearGradient10482);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 602.90661,146.08356 C 602.56806,145.31597 601.28808,142.24809 603.3407,141.25072 C 605.59908,140.15336 607.49098,141.20628 608.41704,140.91689 C 609.34309,140.6275 610.08352,139.54331 610.807,139.28286 C 611.53047,139.0224 612.22501,138.87771 612.48547,139.22498 C 612.74592,139.57225 611.3279,139.8327 611.38578,140.4983 C 611.44366,141.1639 612.74592,141.80056 612.74592,141.80056 C 612.74592,141.80056 612.1382,142.52404 611.84881,142.92919 C 611.55941,143.33433 611.35867,143.34543 611.55941,143.79736 C 612.10138,145.01752 613.68251,144.05551 613.87454,144.60766 C 614.28343,145.78331 611.43749,145.44898 610.4515,145.88158 C 609.3499,146.36492 609.22087,148.12123 607.10605,148.91608 C 604.81915,149.77561 603.24175,146.84339 602.90661,146.08356 z " + id="path10466" + sodipodi:nodetypes="cssssscsssssz" /> + <path + style="fill:#0001c0;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="M 610.42835,141.87066 L 620.31201,138.16684 L 620.41433,137.83944 C 620.41433,137.83944 623.27916,137.61434 623.48379,137.67573 C 623.68842,137.73712 623.62703,137.94175 623.89305,138.0236 C 624.15907,138.10546 624.34324,138.08499 624.05675,138.18731 C 623.77027,138.28962 621.86721,138.9649 621.66258,139.29231 C 621.45795,139.61972 620.98729,138.86259 620.65989,139.02629 C 620.33248,139.19 620.23016,139.27185 620.23016,139.27185 L 611.04225,143.28261" + id="path10468" /> + <path + sodipodi:nodetypes="cccccc" + id="path10470" + d="M 606.00833,143.58956 L 605.17958,143.88627 C 605.30671,144.22248 606.05949,145.67679 606.05949,145.67679 L 606.83708,145.34938 L 606.00833,143.58956 L 606.00833,143.58956 z " + style="fill:#293f73;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + style="fill:#5675bf;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 605.98787,143.30307 L 605.15911,143.59978 C 605.28625,143.936 606.03903,145.39031 606.03903,145.39031 L 606.81662,145.0629 L 605.98787,143.30307 L 605.98787,143.30307 z " + id="path10472" + sodipodi:nodetypes="cccccc" /> + <path + style="opacity:0.38396624;fill:url(#linearGradient10484);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="M 606.11065,145.05266 L 605.46606,143.70211 L 620.31201,138.35101 L 620.68035,138.16684 L 623.34055,137.8599 L 620.84405,138.92398 L 606.11065,145.05266 z " + id="path10474" + sodipodi:nodetypes="ccccccc" /> + <path + sodipodi:type="arc" + style="fill:#839bd3;fill-opacity:1;stroke:none;stroke-width:0.2;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path10476" + sodipodi:cx="378.67188" + sodipodi:cy="172.51843" + sodipodi:rx="0.359375" + sodipodi:ry="0.34375" + d="M 379.03125 172.51843 A 0.359375 0.34375 0 1 1 378.3125,172.51843 A 0.359375 0.34375 0 1 1 379.03125 172.51843 z" + transform="matrix(0.911051,0,0,0.911051,263.2289,-11.51056)" /> + <path + d="M 379.03125 172.51843 A 0.359375 0.34375 0 1 1 378.3125,172.51843 A 0.359375 0.34375 0 1 1 379.03125 172.51843 z" + sodipodi:ry="0.34375" + sodipodi:rx="0.359375" + sodipodi:cy="172.51843" + sodipodi:cx="378.67188" + id="path10478" + style="fill:#839bd3;fill-opacity:1;stroke:none;stroke-width:0.2;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" + transform="matrix(0.80364,0,0,0.80364,303.0453,8.660843)" /> + <path + transform="matrix(0.80364,0,0,0.80364,303.5773,7.883247)" + sodipodi:type="arc" + style="fill:#839bd3;fill-opacity:1;stroke:none;stroke-width:0.2;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path10480" + sodipodi:cx="378.67188" + sodipodi:cy="172.51843" + sodipodi:rx="0.359375" + sodipodi:ry="0.34375" + d="M 379.03125 172.51843 A 0.359375 0.34375 0 1 1 378.3125,172.51843 A 0.359375 0.34375 0 1 1 379.03125 172.51843 z" /> + </g> + <rect + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/48x48/actions/amarok_favourite_genres.png" + style="fill:#b9cff9;fill-opacity:0;stroke:none;stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + id="rect11461" + width="48" + height="48" + x="863.11267" + y="1116.6895" /> + </g> + <g + id="g13337" + transform="translate(-17,-136)"> + <g + id="g10442" + transform="matrix(1.454546,0,0,1.454546,-70.807829,939.1531)" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/32x32/actions/amarok_favourite_genres.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <path + sodipodi:nodetypes="cssssscsssssz" + id="path10444" + d="M 602.90661,146.08356 C 602.56806,145.31597 601.28808,142.24809 603.3407,141.25072 C 605.59908,140.15336 607.49098,141.20628 608.41704,140.91689 C 609.34309,140.6275 610.08352,139.54331 610.807,139.28286 C 611.53047,139.0224 612.22501,138.87771 612.48547,139.22498 C 612.74592,139.57225 611.3279,139.8327 611.38578,140.4983 C 611.44366,141.1639 612.74592,141.80056 612.74592,141.80056 C 612.74592,141.80056 612.1382,142.52404 611.84881,142.92919 C 611.55941,143.33433 611.35867,143.34543 611.55941,143.79736 C 612.10138,145.01752 613.68251,144.05551 613.87454,144.60766 C 614.28343,145.78331 611.43749,145.44898 610.4515,145.88158 C 609.3499,146.36492 609.22087,148.12123 607.10605,148.91608 C 604.81915,149.77561 603.24175,146.84339 602.90661,146.08356 z " + style="fill:url(#linearGradient10460);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + id="path10446" + d="M 610.42835,141.87066 L 620.31201,138.16684 L 620.41433,137.83944 C 620.41433,137.83944 623.27916,137.61434 623.48379,137.67573 C 623.68842,137.73712 623.62703,137.94175 623.89305,138.0236 C 624.15907,138.10546 624.34324,138.08499 624.05675,138.18731 C 623.77027,138.28962 621.86721,138.9649 621.66258,139.29231 C 621.45795,139.61972 620.98729,138.86259 620.65989,139.02629 C 620.33248,139.19 620.23016,139.27185 620.23016,139.27185 L 611.04225,143.28261" + style="fill:#0001c0;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + style="fill:#293f73;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 606.00833,143.58956 L 605.17958,143.88627 C 605.30671,144.22248 606.05949,145.67679 606.05949,145.67679 L 606.83708,145.34938 L 606.00833,143.58956 L 606.00833,143.58956 z " + id="path10448" + sodipodi:nodetypes="cccccc" /> + <path + sodipodi:nodetypes="cccccc" + id="path10450" + d="M 605.98787,143.30307 L 605.15911,143.59978 C 605.28625,143.936 606.03903,145.39031 606.03903,145.39031 L 606.81662,145.0629 L 605.98787,143.30307 L 605.98787,143.30307 z " + style="fill:#5675bf;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + sodipodi:nodetypes="ccccccc" + id="path10452" + d="M 606.11065,145.05266 L 605.46606,143.70211 L 620.31201,138.35101 L 620.68035,138.16684 L 623.34055,137.8599 L 620.84405,138.92398 L 606.11065,145.05266 z " + style="opacity:0.38396624;fill:url(#linearGradient10462);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + transform="matrix(0.911051,0,0,0.911051,263.2289,-11.51056)" + d="M 379.03125 172.51843 A 0.359375 0.34375 0 1 1 378.3125,172.51843 A 0.359375 0.34375 0 1 1 379.03125 172.51843 z" + sodipodi:ry="0.34375" + sodipodi:rx="0.359375" + sodipodi:cy="172.51843" + sodipodi:cx="378.67188" + id="path10454" + style="fill:#839bd3;fill-opacity:1;stroke:none;stroke-width:0.2;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + transform="matrix(0.80364,0,0,0.80364,303.0453,8.660843)" + sodipodi:type="arc" + style="fill:#839bd3;fill-opacity:1;stroke:none;stroke-width:0.2;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path10456" + sodipodi:cx="378.67188" + sodipodi:cy="172.51843" + sodipodi:rx="0.359375" + sodipodi:ry="0.34375" + d="M 379.03125 172.51843 A 0.359375 0.34375 0 1 1 378.3125,172.51843 A 0.359375 0.34375 0 1 1 379.03125 172.51843 z" /> + <path + d="M 379.03125 172.51843 A 0.359375 0.34375 0 1 1 378.3125,172.51843 A 0.359375 0.34375 0 1 1 379.03125 172.51843 z" + sodipodi:ry="0.34375" + sodipodi:rx="0.359375" + sodipodi:cy="172.51843" + sodipodi:cx="378.67188" + id="path10458" + style="fill:#839bd3;fill-opacity:1;stroke:none;stroke-width:0.2;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" + transform="matrix(0.80364,0,0,0.80364,303.5773,7.883247)" /> + </g> + <rect + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/32x32/actions/amarok_favourite_genres.png" + y="1131.6895" + x="805.1217" + height="32" + width="32" + id="rect11463" + style="fill:#b9cff9;fill-opacity:0;stroke:none;stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" /> + </g> + <g + id="g13349" + transform="translate(-17,-135)"> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/22x22/actions/amarok_favorite_genres.png" + transform="translate(157.92504,1008.3207)" + id="g10418"> + <path + style="fill:url(#linearGradient10438);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 602.90661,146.08356 C 602.56806,145.31597 601.28808,142.24809 603.3407,141.25072 C 605.59908,140.15336 607.49098,141.20628 608.41704,140.91689 C 609.34309,140.6275 610.08352,139.54331 610.807,139.28286 C 611.53047,139.0224 612.22501,138.87771 612.48547,139.22498 C 612.74592,139.57225 611.3279,139.8327 611.38578,140.4983 C 611.44366,141.1639 612.74592,141.80056 612.74592,141.80056 C 612.74592,141.80056 612.1382,142.52404 611.84881,142.92919 C 611.55941,143.33433 611.35867,143.34543 611.55941,143.79736 C 612.10138,145.01752 613.68251,144.05551 613.87454,144.60766 C 614.28343,145.78331 611.43749,145.44898 610.4515,145.88158 C 609.3499,146.36492 609.22087,148.12123 607.10605,148.91608 C 604.81915,149.77561 603.24175,146.84339 602.90661,146.08356 z " + id="path10421" + sodipodi:nodetypes="cssssscsssssz" /> + <path + style="fill:#0001c0;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="M 610.42835,141.87066 L 620.31201,138.16684 L 620.41433,137.83944 C 620.41433,137.83944 623.27916,137.61434 623.48379,137.67573 C 623.68842,137.73712 623.62703,137.94175 623.89305,138.0236 C 624.15907,138.10546 624.34324,138.08499 624.05675,138.18731 C 623.77027,138.28962 621.86721,138.9649 621.66258,139.29231 C 621.45795,139.61972 620.98729,138.86259 620.65989,139.02629 C 620.33248,139.19 620.23016,139.27185 620.23016,139.27185 L 611.04225,143.28261" + id="path10423" /> + <path + sodipodi:nodetypes="cccccc" + id="path10425" + d="M 606.00833,143.58956 L 605.17958,143.88627 C 605.30671,144.22248 606.05949,145.67679 606.05949,145.67679 L 606.83708,145.34938 L 606.00833,143.58956 L 606.00833,143.58956 z " + style="fill:#293f73;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + style="fill:#5675bf;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 605.98787,143.30307 L 605.15911,143.59978 C 605.28625,143.936 606.03903,145.39031 606.03903,145.39031 L 606.81662,145.0629 L 605.98787,143.30307 L 605.98787,143.30307 z " + id="path10427" + sodipodi:nodetypes="cccccc" /> + <path + style="opacity:0.38396624;fill:url(#linearGradient10440);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="M 606.11065,145.05266 L 605.46606,143.70211 L 620.31201,138.35101 L 620.68035,138.16684 L 623.34055,137.8599 L 620.84405,138.92398 L 606.11065,145.05266 z " + id="path10429" + sodipodi:nodetypes="ccccccc" /> + <path + sodipodi:type="arc" + style="fill:#839bd3;fill-opacity:1;stroke:none;stroke-width:0.2;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path10431" + sodipodi:cx="378.67188" + sodipodi:cy="172.51843" + sodipodi:rx="0.359375" + sodipodi:ry="0.34375" + d="M 379.03125 172.51843 A 0.359375 0.34375 0 1 1 378.3125,172.51843 A 0.359375 0.34375 0 1 1 379.03125 172.51843 z" + transform="matrix(0.911051,0,0,0.911051,263.2289,-11.51056)" /> + <path + d="M 379.03125 172.51843 A 0.359375 0.34375 0 1 1 378.3125,172.51843 A 0.359375 0.34375 0 1 1 379.03125 172.51843 z" + sodipodi:ry="0.34375" + sodipodi:rx="0.359375" + sodipodi:cy="172.51843" + sodipodi:cx="378.67188" + id="path10433" + style="fill:#839bd3;fill-opacity:1;stroke:none;stroke-width:0.2;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" + transform="matrix(0.80364,0,0,0.80364,303.0453,8.660843)" /> + <path + transform="matrix(0.80364,0,0,0.80364,303.5773,7.883247)" + sodipodi:type="arc" + style="fill:#839bd3;fill-opacity:1;stroke:none;stroke-width:0.2;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path10435" + sodipodi:cx="378.67188" + sodipodi:cy="172.51843" + sodipodi:rx="0.359375" + sodipodi:ry="0.34375" + d="M 379.03125 172.51843 A 0.359375 0.34375 0 1 1 378.3125,172.51843 A 0.359375 0.34375 0 1 1 379.03125 172.51843 z" /> + </g> + <rect + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/22x22/actions/amarok_favourite_genres.png" + style="fill:#b9cff9;fill-opacity:0;stroke:none;stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + id="rect11465" + width="22" + height="22" + x="760.12634" + y="1140.6895" /> + </g> + <g + id="g13361" + transform="translate(-17,-134.4364)"> + <g + id="g8133" + transform="matrix(0.727273,0,0,0.727273,289.16384,1049.8576)" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/16x16/actions/amarok_favorite_genres.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <path + sodipodi:nodetypes="cssssscsssssz" + id="path8135" + d="M 602.90661,146.08356 C 602.56806,145.31597 601.28808,142.24809 603.3407,141.25072 C 605.59908,140.15336 607.49098,141.20628 608.41704,140.91689 C 609.34309,140.6275 610.08352,139.54331 610.807,139.28286 C 611.53047,139.0224 612.22501,138.87771 612.48547,139.22498 C 612.74592,139.57225 611.3279,139.8327 611.38578,140.4983 C 611.44366,141.1639 612.74592,141.80056 612.74592,141.80056 C 612.74592,141.80056 612.1382,142.52404 611.84881,142.92919 C 611.55941,143.33433 611.35867,143.34543 611.55941,143.79736 C 612.10138,145.01752 613.68251,144.05551 613.87454,144.60766 C 614.28343,145.78331 611.43749,145.44898 610.4515,145.88158 C 609.3499,146.36492 609.22087,148.12123 607.10605,148.91608 C 604.81915,149.77561 603.24175,146.84339 602.90661,146.08356 z " + style="fill:url(#linearGradient8377);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + id="path8137" + d="M 610.42835,141.87066 L 620.31201,138.16684 L 620.41433,137.83944 C 620.41433,137.83944 623.27916,137.61434 623.48379,137.67573 C 623.68842,137.73712 623.62703,137.94175 623.89305,138.0236 C 624.15907,138.10546 624.34324,138.08499 624.05675,138.18731 C 623.77027,138.28962 621.86721,138.9649 621.66258,139.29231 C 621.45795,139.61972 620.98729,138.86259 620.65989,139.02629 C 620.33248,139.19 620.23016,139.27185 620.23016,139.27185 L 611.04225,143.28261" + style="fill:#0001c0;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + style="fill:#293f73;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 606.00833,143.58956 L 605.17958,143.88627 C 605.30671,144.22248 606.05949,145.67679 606.05949,145.67679 L 606.83708,145.34938 L 606.00833,143.58956 L 606.00833,143.58956 z " + id="path8139" + sodipodi:nodetypes="cccccc" /> + <path + sodipodi:nodetypes="cccccc" + id="path8141" + d="M 605.98787,143.30307 L 605.15911,143.59978 C 605.28625,143.936 606.03903,145.39031 606.03903,145.39031 L 606.81662,145.0629 L 605.98787,143.30307 L 605.98787,143.30307 z " + style="fill:#5675bf;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + sodipodi:nodetypes="ccccccc" + id="path8143" + d="M 606.11065,145.05266 L 605.46606,143.70211 L 620.31201,138.35101 L 620.68035,138.16684 L 623.34055,137.8599 L 620.84405,138.92398 L 606.11065,145.05266 z " + style="opacity:0.38396624;fill:url(#linearGradient8379);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + transform="matrix(0.911051,0,0,0.911051,263.2289,-11.51056)" + d="M 379.03125 172.51843 A 0.359375 0.34375 0 1 1 378.3125,172.51843 A 0.359375 0.34375 0 1 1 379.03125 172.51843 z" + sodipodi:ry="0.34375" + sodipodi:rx="0.359375" + sodipodi:cy="172.51843" + sodipodi:cx="378.67188" + id="path8145" + style="fill:#839bd3;fill-opacity:1;stroke:none;stroke-width:0.2;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + transform="matrix(0.80364,0,0,0.80364,303.0453,8.660843)" + sodipodi:type="arc" + style="fill:#839bd3;fill-opacity:1;stroke:none;stroke-width:0.2;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path8147" + sodipodi:cx="378.67188" + sodipodi:cy="172.51843" + sodipodi:rx="0.359375" + sodipodi:ry="0.34375" + d="M 379.03125 172.51843 A 0.359375 0.34375 0 1 1 378.3125,172.51843 A 0.359375 0.34375 0 1 1 379.03125 172.51843 z" /> + <path + d="M 379.03125 172.51843 A 0.359375 0.34375 0 1 1 378.3125,172.51843 A 0.359375 0.34375 0 1 1 379.03125 172.51843 z" + sodipodi:ry="0.34375" + sodipodi:rx="0.359375" + sodipodi:cy="172.51843" + sodipodi:cx="378.67188" + id="path8149" + style="fill:#839bd3;fill-opacity:1;stroke:none;stroke-width:0.2;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" + transform="matrix(0.80364,0,0,0.80364,303.5773,7.883247)" /> + </g> + <rect + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/16x16/actions/amarok_favourite_genres.png" + style="fill:#b9cff9;fill-opacity:0;stroke:none;stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + id="rect11469" + width="16" + height="16" + x="727.1286" + y="1146.1259" /> + </g> + <g + id="g13266" + transform="translate(-23,-100)"> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/16x16/actions/amarok_search.png" + id="g12102" + transform="matrix(0.736085,0,0,0.736085,335.33895,1113.4047)"> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_search.png" + style="fill:url(#linearGradient12114);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient12116);stroke-width:0.39723051;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 543.34596,147.60818 C 544.91692,150.11659 546.77012,150.57043 548.78672,152.89903 C 550.94451,155.39065 551.0628,156.2123 551.55669,156.75013 C 551.52392,157.30966 550.81508,157.90618 550.07138,157.928 C 549.73118,157.59388 549.07121,157.39432 546.47246,154.55919 C 543.94915,151.80639 545.0477,151.2279 542.03157,148.82073 C 542.34931,148.35635 542.72725,147.89197 543.34596,147.60818 z " + id="path12104" + sodipodi:nodetypes="csccscc" /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_search.png" + transform="matrix(0.793924,-0.608017,0.304009,0.396962,117.8031,303.5009)" + d="M 433.09708 270.50006 A 1.2339805 0.78252429 0 1 1 430.62912,270.50006 A 1.2339805 0.78252429 0 1 1 433.09708 270.50006 z" + sodipodi:ry="0.78252429" + sodipodi:rx="1.2339805" + sodipodi:cy="270.50006" + sodipodi:cx="431.8631" + id="path12106" + style="opacity:1;fill:url(#radialGradient12118);fill-opacity:1;stroke:url(#linearGradient12120);stroke-width:0.66313559;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_search.png" + transform="matrix(0.680534,0,0,0.680535,253.2681,-159.6484)" + d="M 428.77151 444.83228 A 9.8239412 9.8239412 0 1 1 409.12363,444.83228 A 9.8239412 9.8239412 0 1 1 428.77151 444.83228 z" + sodipodi:ry="9.8239412" + sodipodi:rx="9.8239412" + sodipodi:cy="444.83228" + sodipodi:cx="418.94757" + id="path12108" + style="fill:url(#linearGradient12122);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_search.png" + transform="matrix(0.545192,0,0,0.545192,310.2066,-100.3248)" + d="M 429.51763 446.44885 A 11.005301 11.005301 0 1 1 407.50703,446.44885 A 11.005301 11.005301 0 1 1 429.51763 446.44885 z" + sodipodi:ry="11.005301" + sodipodi:rx="11.005301" + sodipodi:cy="446.44885" + sodipodi:cx="418.51233" + id="path12110" + style="fill:url(#radialGradient12124);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_search.png" + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + sodipodi:nodetypes="csssc" + id="path12112" + d="M 544.41352,143.07449 C 544.84924,144.70061 541.69976,142.55227 538.35602,142.55227 C 535.01228,142.55227 532.29852,145.22009 532.29852,143.07449 C 532.29852,139.73076 535.01228,137.01699 538.35602,137.01699 C 541.69976,137.01699 544.41352,139.73076 544.41352,143.07449 z " + style="opacity:0;fill:url(#linearGradient12126);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" /> + </g> + <rect + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/16x16/actions/amarok_search.png" + y="1213.7993" + x="726.09314" + height="16" + width="16" + id="rect11471" + style="fill:#b9cff9;fill-opacity:0;stroke:none;stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" /> + </g> + <g + id="g13277" + transform="translate(-23,-100)"> + <g + transform="matrix(1.012117,0,0,1.012117,224.83931,1070.0385)" + id="g12074"> + <path + sodipodi:nodetypes="csccscc" + id="path12077" + d="M 543.34596,147.60818 C 544.91692,150.11659 546.77012,150.57043 548.78672,152.89903 C 550.94451,155.39065 551.0628,156.2123 551.55669,156.75013 C 551.52392,157.30966 550.81508,157.90618 550.07138,157.928 C 549.73118,157.59388 549.07121,157.39432 546.47246,154.55919 C 543.94915,151.80639 545.0477,151.2279 542.03157,148.82073 C 542.34931,148.35635 542.72725,147.89197 543.34596,147.60818 z " + style="fill:url(#linearGradient12088);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient12090);stroke-width:0.39723051;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_search.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:type="arc" + style="opacity:1;fill:url(#radialGradient12092);fill-opacity:1;stroke:url(#linearGradient12094);stroke-width:0.66313559;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path12079" + sodipodi:cx="431.8631" + sodipodi:cy="270.50006" + sodipodi:rx="1.2339805" + sodipodi:ry="0.78252429" + d="M 433.09708 270.50006 A 1.2339805 0.78252429 0 1 1 430.62912,270.50006 A 1.2339805 0.78252429 0 1 1 433.09708 270.50006 z" + transform="matrix(0.793924,-0.608017,0.304009,0.396962,117.8031,303.5009)" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_search.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:type="arc" + style="fill:url(#linearGradient12096);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path12081" + sodipodi:cx="418.94757" + sodipodi:cy="444.83228" + sodipodi:rx="9.8239412" + sodipodi:ry="9.8239412" + d="M 428.77151 444.83228 A 9.8239412 9.8239412 0 1 1 409.12363,444.83228 A 9.8239412 9.8239412 0 1 1 428.77151 444.83228 z" + transform="matrix(0.680534,0,0,0.680535,253.2681,-159.6484)" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_search.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:type="arc" + style="fill:url(#radialGradient12098);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path12084" + sodipodi:cx="418.51233" + sodipodi:cy="446.44885" + sodipodi:rx="11.005301" + sodipodi:ry="11.005301" + d="M 429.51763 446.44885 A 11.005301 11.005301 0 1 1 407.50703,446.44885 A 11.005301 11.005301 0 1 1 429.51763 446.44885 z" + transform="matrix(0.545192,0,0,0.545192,310.2066,-100.3248)" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_search.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + style="opacity:0;fill:url(#linearGradient12100);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 544.41352,143.07449 C 544.84924,144.70061 541.69976,142.55227 538.35602,142.55227 C 535.01228,142.55227 532.29852,145.22009 532.29852,143.07449 C 532.29852,139.73076 535.01228,137.01699 538.35602,137.01699 C 541.69976,137.01699 544.41352,139.73076 544.41352,143.07449 z " + id="path12086" + sodipodi:nodetypes="csssc" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_search.png" /> + </g> + <rect + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/22x22/actions/amarok_search.png" + y="1208.0811" + x="762.12634" + height="22" + width="22" + id="rect11473" + style="fill:#b9cff9;fill-opacity:0;stroke:none;stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" /> + </g> + <g + id="g13286" + transform="translate(-23,-100)"> + <g + id="g12016" + transform="matrix(1.472173,0,0,1.472173,28.611763,997.7914)"> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_search.png" + style="fill:url(#linearGradient12060);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient12062);stroke-width:0.39723051;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 543.34596,147.60818 C 544.91692,150.11659 546.77012,150.57043 548.78672,152.89903 C 550.94451,155.39065 551.0628,156.2123 551.55669,156.75013 C 551.52392,157.30966 550.81508,157.90618 550.07138,157.928 C 549.73118,157.59388 549.07121,157.39432 546.47246,154.55919 C 543.94915,151.80639 545.0477,151.2279 542.03157,148.82073 C 542.34931,148.35635 542.72725,147.89197 543.34596,147.60818 z " + id="path12018" + sodipodi:nodetypes="csccscc" /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_search.png" + transform="matrix(0.793924,-0.608017,0.304009,0.396962,117.8031,303.5009)" + d="M 433.09708 270.50006 A 1.2339805 0.78252429 0 1 1 430.62912,270.50006 A 1.2339805 0.78252429 0 1 1 433.09708 270.50006 z" + sodipodi:ry="0.78252429" + sodipodi:rx="1.2339805" + sodipodi:cy="270.50006" + sodipodi:cx="431.8631" + id="path12020" + style="opacity:1;fill:url(#radialGradient12064);fill-opacity:1;stroke:url(#linearGradient12066);stroke-width:0.66313559;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_search.png" + transform="matrix(0.680534,0,0,0.680535,253.2681,-159.6484)" + d="M 428.77151 444.83228 A 9.8239412 9.8239412 0 1 1 409.12363,444.83228 A 9.8239412 9.8239412 0 1 1 428.77151 444.83228 z" + sodipodi:ry="9.8239412" + sodipodi:rx="9.8239412" + sodipodi:cy="444.83228" + sodipodi:cx="418.94757" + id="path12023" + style="fill:url(#linearGradient12068);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_search.png" + transform="matrix(0.545192,0,0,0.545192,310.2066,-100.3248)" + d="M 429.51763 446.44885 A 11.005301 11.005301 0 1 1 407.50703,446.44885 A 11.005301 11.005301 0 1 1 429.51763 446.44885 z" + sodipodi:ry="11.005301" + sodipodi:rx="11.005301" + sodipodi:cy="446.44885" + sodipodi:cx="418.51233" + id="path12025" + style="fill:url(#radialGradient12070);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_search.png" + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + sodipodi:nodetypes="csssc" + id="path12027" + d="M 544.41352,143.07449 C 544.84924,144.70061 541.69976,142.55227 538.35602,142.55227 C 535.01228,142.55227 532.29852,145.22009 532.29852,143.07449 C 532.29852,139.73076 535.01228,137.01699 538.35602,137.01699 C 541.69976,137.01699 544.41352,139.73076 544.41352,143.07449 z " + style="opacity:0;fill:url(#linearGradient12072);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" /> + </g> + <rect + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/32x32/actions/amarok_search.png" + style="fill:#b9cff9;fill-opacity:0;stroke:none;stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + id="rect11475" + width="32" + height="32" + x="810.1217" + y="1198.5811" /> + </g> + <g + id="g13295" + transform="translate(-23,-100)"> + <g + transform="matrix(2.208255,0,0,2.208255,-298.1498,881.39731)" + id="g12128"> + <path + sodipodi:nodetypes="csccscc" + id="path12130" + d="M 543.34596,147.60818 C 544.91692,150.11659 546.77012,150.57043 548.78672,152.89903 C 550.94451,155.39065 551.0628,156.2123 551.55669,156.75013 C 551.52392,157.30966 550.81508,157.90618 550.07138,157.928 C 549.73118,157.59388 549.07121,157.39432 546.47246,154.55919 C 543.94915,151.80639 545.0477,151.2279 542.03157,148.82073 C 542.34931,148.35635 542.72725,147.89197 543.34596,147.60818 z " + style="fill:url(#linearGradient12140);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient12142);stroke-width:0.39723051;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_search.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:type="arc" + style="opacity:1;fill:url(#radialGradient12144);fill-opacity:1;stroke:url(#linearGradient12146);stroke-width:0.66313559;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path12132" + sodipodi:cx="431.8631" + sodipodi:cy="270.50006" + sodipodi:rx="1.2339805" + sodipodi:ry="0.78252429" + d="M 433.09708 270.50006 A 1.2339805 0.78252429 0 1 1 430.62912,270.50006 A 1.2339805 0.78252429 0 1 1 433.09708 270.50006 z" + transform="matrix(0.793924,-0.608017,0.304009,0.396962,117.8031,303.5009)" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_search.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:type="arc" + style="fill:url(#linearGradient12148);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path12134" + sodipodi:cx="418.94757" + sodipodi:cy="444.83228" + sodipodi:rx="9.8239412" + sodipodi:ry="9.8239412" + d="M 428.77151 444.83228 A 9.8239412 9.8239412 0 1 1 409.12363,444.83228 A 9.8239412 9.8239412 0 1 1 428.77151 444.83228 z" + transform="matrix(0.680534,0,0,0.680535,253.2681,-159.6484)" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_search.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:type="arc" + style="fill:url(#radialGradient12150);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path12136" + sodipodi:cx="418.51233" + sodipodi:cy="446.44885" + sodipodi:rx="11.005301" + sodipodi:ry="11.005301" + d="M 429.51763 446.44885 A 11.005301 11.005301 0 1 1 407.50703,446.44885 A 11.005301 11.005301 0 1 1 429.51763 446.44885 z" + transform="matrix(0.545192,0,0,0.545192,310.2066,-100.3248)" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_search.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + style="opacity:0;fill:url(#linearGradient12152);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 544.41352,143.07449 C 544.84924,144.70061 541.69976,142.55227 538.35602,142.55227 C 535.01228,142.55227 532.29852,145.22009 532.29852,143.07449 C 532.29852,139.73076 535.01228,137.01699 538.35602,137.01699 C 541.69976,137.01699 544.41352,139.73076 544.41352,143.07449 z " + id="path12138" + sodipodi:nodetypes="csssc" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_search.png" /> + </g> + <rect + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/48x48/actions/amarok_search.png" + y="1182.5812" + x="874.11267" + height="48" + width="48" + id="rect11477" + style="fill:#b9cff9;fill-opacity:0;stroke:none;stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" /> + </g> + <g + id="g13304" + transform="translate(-23,-100)"> + <g + id="g12154" + transform="matrix(2.94434,0,0,2.94434,-595.94073,765.00251)"> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_search.png" + style="fill:url(#linearGradient12166);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient12168);stroke-width:0.39723051;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 543.34596,147.60818 C 544.91692,150.11659 546.77012,150.57043 548.78672,152.89903 C 550.94451,155.39065 551.0628,156.2123 551.55669,156.75013 C 551.52392,157.30966 550.81508,157.90618 550.07138,157.928 C 549.73118,157.59388 549.07121,157.39432 546.47246,154.55919 C 543.94915,151.80639 545.0477,151.2279 542.03157,148.82073 C 542.34931,148.35635 542.72725,147.89197 543.34596,147.60818 z " + id="path12156" + sodipodi:nodetypes="csccscc" /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_search.png" + transform="matrix(0.793924,-0.608017,0.304009,0.396962,117.8031,303.5009)" + d="M 433.09708 270.50006 A 1.2339805 0.78252429 0 1 1 430.62912,270.50006 A 1.2339805 0.78252429 0 1 1 433.09708 270.50006 z" + sodipodi:ry="0.78252429" + sodipodi:rx="1.2339805" + sodipodi:cy="270.50006" + sodipodi:cx="431.8631" + id="path12158" + style="opacity:1;fill:url(#radialGradient12170);fill-opacity:1;stroke:url(#linearGradient12172);stroke-width:0.66313559;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_search.png" + transform="matrix(0.680534,0,0,0.680535,253.2681,-159.6484)" + d="M 428.77151 444.83228 A 9.8239412 9.8239412 0 1 1 409.12363,444.83228 A 9.8239412 9.8239412 0 1 1 428.77151 444.83228 z" + sodipodi:ry="9.8239412" + sodipodi:rx="9.8239412" + sodipodi:cy="444.83228" + sodipodi:cx="418.94757" + id="path12160" + style="fill:url(#linearGradient12174);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_search.png" + transform="matrix(0.545192,0,0,0.545192,310.2066,-100.3248)" + d="M 429.51763 446.44885 A 11.005301 11.005301 0 1 1 407.50703,446.44885 A 11.005301 11.005301 0 1 1 429.51763 446.44885 z" + sodipodi:ry="11.005301" + sodipodi:rx="11.005301" + sodipodi:cy="446.44885" + sodipodi:cx="418.51233" + id="path12162" + style="fill:url(#radialGradient12176);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_search.png" + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + sodipodi:nodetypes="csssc" + id="path12164" + d="M 544.41352,143.07449 C 544.84924,144.70061 541.69976,142.55227 538.35602,142.55227 C 535.01228,142.55227 532.29852,145.22009 532.29852,143.07449 C 532.29852,139.73076 535.01228,137.01699 538.35602,137.01699 C 541.69976,137.01699 544.41352,139.73076 544.41352,143.07449 z " + style="opacity:0;fill:url(#linearGradient12178);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" /> + </g> + <rect + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/64x64/actions/amarok_search.png" + style="fill:#b9cff9;fill-opacity:0;stroke:none;stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + id="rect11479" + width="64" + height="64" + x="967.07593" + y="1166.5811" /> + </g> + <g + id="g13526" + transform="translate(1,-13)"> + <g + id="g9610" + transform="matrix(2.9102772,0,0,2.7306468,-729.26059,-29.721168)" + inkscape:export-filename="/home/lando/Projects/Amarok/icons/64/amarok_files2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <rect + style="fill:url(#linearGradient9670);fill-opacity:1;stroke:none;stroke-width:0.97624999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect9612" + width="18.63254" + height="18.118319" + x="570.13348" + y="274.90677" /> + <rect + style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect9614" + width="16.988491" + height="15.785593" + x="570.68146" + y="276.63235" + ry="0.29134426" + rx="0.19135311" /> + <path + style="opacity:0.6978022;fill:url(#linearGradient9672);fill-opacity:1;stroke:none;stroke-width:0.97624999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1" + d="M 569.02378,279.70319 L 574.53008,279.67837 L 579.45944,277.23948 C 579.45944,277.23948 590.78898,276.82043 590.97795,277.2925 C 591.14705,277.71497 588.46027,288.61152 588.67476,292.97207 L 570.24413,293.02508 C 570.27712,288.85894 570.42607,286.73234 569.02378,279.70319 z " + id="path9616" + sodipodi:nodetypes="ccczccc" /> + <path + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:url(#linearGradient26818);stroke-width:0.28610274;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 569.13509,279.69625 L 574.61828,279.66805 L 579.47952,277.23948 L 590.84899,277.23948" + id="path9618" + sodipodi:nodetypes="cccc" /> + <rect + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect9620" + width="21.23255" + height="0.46407768" + x="569.20001" + y="280.6861" + rx="0" + ry="0" /> + <rect + ry="0" + rx="0" + y="291.62628" + x="570.16431" + height="0.46407768" + width="18.571243" + id="rect9622" + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect9624" + width="18.571243" + height="0.46407768" + x="570.16431" + y="290.41071" + rx="0" + ry="0" /> + <rect + ry="0" + rx="0" + y="289.19513" + x="570.12854" + height="0.46407768" + width="18.714163" + id="rect9626" + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect9628" + width="18.892811" + height="0.46407768" + x="570.12854" + y="287.97955" + rx="0" + ry="0" /> + <rect + ry="0" + rx="0" + y="286.15619" + x="570.09283" + height="0.46407768" + width="19.229099" + id="rect9630" + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect9632" + width="19.638405" + height="0.46407768" + x="569.91418" + y="284.94061" + rx="0" + ry="0" /> + <rect + ry="0" + rx="0" + y="281.29388" + x="569.30719" + height="0.46407768" + width="20.892811" + id="rect9634" + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect9636" + width="20.464485" + height="0.46407768" + x="569.55688" + y="282.50946" + rx="0.030011393" + ry="0" /> + <rect + ry="0" + rx="0" + y="283.72504" + x="569.73553" + height="0.46407768" + width="20.024101" + id="rect9638" + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + ry="0" + rx="0" + y="279.47052" + x="574.63464" + height="0.46407768" + width="16.012278" + id="rect9640" + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect9642" + width="18.642706" + height="0.46407768" + x="570.12854" + y="291.01849" + rx="0" + ry="0" /> + <rect + ry="0" + rx="0" + y="289.80292" + x="570.16431" + height="0.46407768" + width="18.571243" + id="rect9644" + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect9646" + width="18.749891" + height="0.46407768" + x="570.23572" + y="288.58734" + rx="0" + ry="0" /> + <rect + ry="0" + rx="0" + y="287.37177" + x="570.12854" + height="0.46407768" + width="19.03573" + id="rect9648" + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect9650" + width="19.121912" + height="0.46407768" + x="570.09283" + y="286.76398" + rx="0" + ry="0" /> + <rect + ry="0" + rx="0" + y="285.5484" + x="570.02136" + height="0.46407768" + width="19.424026" + id="rect9652" + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect9654" + width="21.464485" + height="0.46407768" + x="569.02136" + y="280.07831" + rx="0" + ry="0" /> + <rect + ry="0" + rx="0" + y="283.11725" + x="569.62836" + height="0.46407768" + width="20.250107" + id="rect9656" + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect9658" + width="19.809723" + height="0.46407768" + x="569.84271" + y="284.33282" + rx="0" + ry="0" /> + <rect + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect9660" + width="20.799389" + height="0.46407768" + x="569.40063" + y="281.90167" + rx="0" + ry="0" /> + <rect + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect9662" + width="14.800593" + height="0.46407768" + x="575.89502" + y="278.86273" + rx="0" + ry="0" /> + <rect + ry="0" + rx="0" + y="278.25494" + x="577.0307" + height="0.46407768" + width="13.804427" + id="rect9664" + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect9666" + width="12.505892" + height="0.46407768" + x="578.34863" + y="277.64716" + rx="0" + ry="0" /> + <rect + ry="0" + rx="0" + y="292.23407" + x="570.14148" + height="0.46407768" + width="18.584423" + id="rect9668" + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + <rect + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/64x64/actions/amarok_files2.png" + style="fill:#b9cff9;fill-opacity:0;stroke:none;stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + id="rect11481" + width="64" + height="64" + x="926.677" + y="713.68945" /> + </g> + <g + id="g13559" + transform="translate(1,-13)"> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/icons/48/amarok_files2.png" + transform="matrix(2.1827079,0,0,2.0479851,-387.96193,170.13149)" + id="g9546"> + <rect + y="274.90677" + x="570.13348" + height="18.118319" + width="18.63254" + id="rect9548" + style="fill:url(#linearGradient9606);fill-opacity:1;stroke:none;stroke-width:0.97624999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + rx="0.25513753" + ry="0.38845909" + y="276.63235" + x="570.68146" + height="15.785593" + width="16.988491" + id="rect9550" + style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + sodipodi:nodetypes="ccczccc" + id="path9552" + d="M 569.02378,279.70319 L 574.53008,279.67837 L 579.45944,277.23948 C 579.45944,277.23948 590.78898,276.82043 590.97795,277.2925 C 591.14705,277.71497 588.46027,288.61152 588.67476,292.97207 L 570.24413,293.02508 C 570.27712,288.85894 570.42607,286.73234 569.02378,279.70319 z " + style="opacity:0.6978022;fill:url(#linearGradient9608);fill-opacity:1;stroke:none;stroke-width:0.97624999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1" /> + <path + sodipodi:nodetypes="cccc" + id="path9554" + d="M 569.13509,279.69625 L 574.61828,279.66805 L 579.47952,277.23948 L 590.84899,277.23948" + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:url(#linearGradient26818);stroke-width:0.28610274;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + ry="0" + rx="0" + y="280.6861" + x="569.20001" + height="0.46407768" + width="21.23255" + id="rect9556" + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect9558" + width="18.571243" + height="0.46407768" + x="570.16431" + y="291.62628" + rx="0" + ry="0" /> + <rect + ry="0" + rx="0" + y="290.41071" + x="570.16431" + height="0.46407768" + width="18.571243" + id="rect9560" + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect9562" + width="18.714163" + height="0.46407768" + x="570.12854" + y="289.19513" + rx="0" + ry="0" /> + <rect + ry="0" + rx="0" + y="287.97955" + x="570.12854" + height="0.46407768" + width="18.892811" + id="rect9564" + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect9566" + width="19.229099" + height="0.46407768" + x="570.09283" + y="286.15619" + rx="0" + ry="0" /> + <rect + ry="0" + rx="0" + y="284.94061" + x="569.91418" + height="0.46407768" + width="19.638405" + id="rect9568" + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect9570" + width="20.892811" + height="0.46407768" + x="569.30719" + y="281.29388" + rx="0" + ry="0" /> + <rect + ry="0" + rx="0.040015198" + y="282.50946" + x="569.55688" + height="0.46407768" + width="20.464485" + id="rect9572" + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect9574" + width="20.024101" + height="0.46407768" + x="569.73553" + y="283.72504" + rx="0" + ry="0" /> + <rect + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect9576" + width="16.012278" + height="0.46407768" + x="574.63464" + y="279.47052" + rx="0" + ry="0" /> + <rect + ry="0" + rx="0" + y="291.01849" + x="570.12854" + height="0.46407768" + width="18.642706" + id="rect9578" + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect9580" + width="18.571243" + height="0.46407768" + x="570.16431" + y="289.80292" + rx="0" + ry="0" /> + <rect + ry="0" + rx="0" + y="288.58734" + x="570.23572" + height="0.46407768" + width="18.749891" + id="rect9582" + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect9584" + width="19.03573" + height="0.46407768" + x="570.12854" + y="287.37177" + rx="0" + ry="0" /> + <rect + ry="0" + rx="0" + y="286.76398" + x="570.09283" + height="0.46407768" + width="19.121912" + id="rect9586" + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect9588" + width="19.424026" + height="0.46407768" + x="570.02136" + y="285.5484" + rx="0" + ry="0" /> + <rect + ry="0" + rx="0" + y="280.07831" + x="569.02136" + height="0.46407768" + width="21.464485" + id="rect9590" + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect9592" + width="20.250107" + height="0.46407768" + x="569.62836" + y="283.11725" + rx="0" + ry="0" /> + <rect + ry="0" + rx="0" + y="284.33282" + x="569.84271" + height="0.46407768" + width="19.809723" + id="rect9594" + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + ry="0" + rx="0" + y="281.90167" + x="569.40063" + height="0.46407768" + width="20.799389" + id="rect9596" + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + ry="0" + rx="0" + y="278.86273" + x="575.89502" + height="0.46407768" + width="14.800593" + id="rect9598" + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect9600" + width="13.804427" + height="0.46407768" + x="577.0307" + y="278.25494" + rx="0" + ry="0" /> + <rect + ry="0" + rx="0" + y="277.64716" + x="578.34863" + height="0.46407768" + width="12.505892" + id="rect9602" + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect9604" + width="18.584423" + height="0.46407768" + x="570.14148" + y="292.23407" + rx="0" + ry="0" /> + </g> + <rect + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/48x48/actions/amarok_files2.png" + y="727.68945" + x="853.99133" + height="48" + width="48" + id="rect11483" + style="fill:#b9cff9;fill-opacity:0;stroke:none;stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" /> + </g> + <g + id="g13592" + transform="translate(1,-13)"> + <g + id="g9482" + transform="matrix(1.4551386,0,0,1.3653234,-24.342247,373.98415)" + inkscape:export-filename="/home/lando/Projects/Amarok/icons/32/amarok_files2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <rect + style="fill:url(#linearGradient9542);fill-opacity:1;stroke:none;stroke-width:0.97624999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect9484" + width="18.63254" + height="18.118319" + x="570.13348" + y="274.90677" /> + <rect + style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect9486" + width="16.988491" + height="15.785593" + x="570.68146" + y="276.63235" + ry="0.58268875" + rx="0.38270637" /> + <path + style="opacity:0.6978022;fill:url(#linearGradient9544);fill-opacity:1;stroke:none;stroke-width:0.97624999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1" + d="M 569.02378,279.70319 L 574.53008,279.67837 L 579.45944,277.23948 C 579.45944,277.23948 590.78898,276.82043 590.97795,277.2925 C 591.14705,277.71497 588.46027,288.61152 588.67476,292.97207 L 570.24413,293.02508 C 570.27712,288.85894 570.42607,286.73234 569.02378,279.70319 z " + id="path9488" + sodipodi:nodetypes="ccczccc" /> + <path + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:url(#linearGradient26818);stroke-width:0.28610274;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 569.13509,279.69625 L 574.61828,279.66805 L 579.47952,277.23948 L 590.84899,277.23948" + id="path9490" + sodipodi:nodetypes="cccc" /> + <rect + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect9492" + width="21.23255" + height="0.46407768" + x="569.20001" + y="280.6861" + rx="0" + ry="0" /> + <rect + ry="0" + rx="0" + y="291.62628" + x="570.16431" + height="0.46407768" + width="18.571243" + id="rect9494" + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect9496" + width="18.571243" + height="0.46407768" + x="570.16431" + y="290.41071" + rx="0" + ry="0" /> + <rect + ry="0" + rx="0" + y="289.19513" + x="570.12854" + height="0.46407768" + width="18.714163" + id="rect9498" + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect9500" + width="18.892811" + height="0.46407768" + x="570.12854" + y="287.97955" + rx="0" + ry="0" /> + <rect + ry="0" + rx="0" + y="286.15619" + x="570.09283" + height="0.46407768" + width="19.229099" + id="rect9502" + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect9504" + width="19.638405" + height="0.46407768" + x="569.91418" + y="284.94061" + rx="0" + ry="0" /> + <rect + ry="0" + rx="0" + y="281.29388" + x="569.30719" + height="0.46407768" + width="20.892811" + id="rect9506" + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect9508" + width="20.464485" + height="0.46407768" + x="569.55688" + y="282.50946" + rx="0.060022809" + ry="0" /> + <rect + ry="0" + rx="0" + y="283.72504" + x="569.73553" + height="0.46407768" + width="20.024101" + id="rect9510" + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + ry="0" + rx="0" + y="279.47052" + x="574.63464" + height="0.46407768" + width="16.012278" + id="rect9512" + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect9514" + width="18.642706" + height="0.46407768" + x="570.12854" + y="291.01849" + rx="0" + ry="0" /> + <rect + ry="0" + rx="0" + y="289.80292" + x="570.16431" + height="0.46407768" + width="18.571243" + id="rect9516" + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect9518" + width="18.749891" + height="0.46407768" + x="570.23572" + y="288.58734" + rx="0" + ry="0" /> + <rect + ry="0" + rx="0" + y="287.37177" + x="570.12854" + height="0.46407768" + width="19.03573" + id="rect9520" + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect9522" + width="19.121912" + height="0.46407768" + x="570.09283" + y="286.76398" + rx="0" + ry="0" /> + <rect + ry="0" + rx="0" + y="285.5484" + x="570.02136" + height="0.46407768" + width="19.424026" + id="rect9524" + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect9526" + width="21.464485" + height="0.46407768" + x="569.02136" + y="280.07831" + rx="0" + ry="0" /> + <rect + ry="0" + rx="0" + y="283.11725" + x="569.62836" + height="0.46407768" + width="20.250107" + id="rect9528" + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect9530" + width="19.809723" + height="0.46407768" + x="569.84271" + y="284.33282" + rx="0" + ry="0" /> + <rect + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect9532" + width="20.799389" + height="0.46407768" + x="569.40063" + y="281.90167" + rx="0" + ry="0" /> + <rect + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect9534" + width="14.800593" + height="0.46407768" + x="575.89502" + y="278.86273" + rx="0" + ry="0" /> + <rect + ry="0" + rx="0" + y="278.25494" + x="577.0307" + height="0.46407768" + width="13.804427" + id="rect9536" + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect9538" + width="12.505892" + height="0.46407768" + x="578.34863" + y="277.64716" + rx="0" + ry="0" /> + <rect + ry="0" + rx="0" + y="292.23407" + x="570.14148" + height="0.46407768" + width="18.584423" + id="rect9540" + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + <rect + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/32x32/actions/amarok_files2.png" + style="fill:#b9cff9;fill-opacity:0;stroke:none;stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + id="rect11485" + width="32" + height="32" + x="803.62659" + y="745.68945" /> + </g> + <g + id="g13625" + transform="translate(1,-13)"> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/icons/22/amarok_files2.png" + transform="matrix(1.0004078,0,0,0.9386602,190.15501,496.14201)" + id="g9418"> + <rect + y="274.90677" + x="570.13348" + height="18.118319" + width="18.63254" + id="rect9420" + style="fill:url(#linearGradient9478);fill-opacity:1;stroke:none;stroke-width:0.97624999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + rx="0.55666369" + ry="0.84754705" + y="276.63235" + x="570.68146" + height="15.785593" + width="16.988491" + id="rect9422" + style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + sodipodi:nodetypes="ccczccc" + id="path9424" + d="M 569.02378,279.70319 L 574.53008,279.67837 L 579.45944,277.23948 C 579.45944,277.23948 590.78898,276.82043 590.97795,277.2925 C 591.14705,277.71497 588.46027,288.61152 588.67476,292.97207 L 570.24413,293.02508 C 570.27712,288.85894 570.42607,286.73234 569.02378,279.70319 z " + style="opacity:0.6978022;fill:url(#linearGradient9480);fill-opacity:1;stroke:none;stroke-width:0.97624999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1" /> + <path + sodipodi:nodetypes="cccc" + id="path9426" + d="M 569.13509,279.69625 L 574.61828,279.66805 L 579.47952,277.23948 L 590.84899,277.23948" + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:url(#linearGradient26818);stroke-width:0.28610274;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + ry="0" + rx="0" + y="280.6861" + x="569.20001" + height="0.46407768" + width="21.23255" + id="rect9428" + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect9430" + width="18.571243" + height="0.46407768" + x="570.16431" + y="291.62628" + rx="0" + ry="0" /> + <rect + ry="0" + rx="0" + y="290.41071" + x="570.16431" + height="0.46407768" + width="18.571243" + id="rect9432" + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect9434" + width="18.714163" + height="0.46407768" + x="570.12854" + y="289.19513" + rx="0" + ry="0" /> + <rect + ry="0" + rx="0" + y="287.97955" + x="570.12854" + height="0.46407768" + width="18.892811" + id="rect9436" + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect9438" + width="19.229099" + height="0.46407768" + x="570.09283" + y="286.15619" + rx="0" + ry="0" /> + <rect + ry="0" + rx="0" + y="284.94061" + x="569.91418" + height="0.46407768" + width="19.638405" + id="rect9440" + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect9442" + width="20.892811" + height="0.46407768" + x="569.30719" + y="281.29388" + rx="0" + ry="0" /> + <rect + ry="0" + rx="0.087305881" + y="282.50946" + x="569.55688" + height="0.46407768" + width="20.464485" + id="rect9444" + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect9446" + width="20.024101" + height="0.46407768" + x="569.73553" + y="283.72504" + rx="0" + ry="0" /> + <rect + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect9448" + width="16.012278" + height="0.46407768" + x="574.63464" + y="279.47052" + rx="0" + ry="0" /> + <rect + ry="0" + rx="0" + y="291.01849" + x="570.12854" + height="0.46407768" + width="18.642706" + id="rect9450" + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect9452" + width="18.571243" + height="0.46407768" + x="570.16431" + y="289.80292" + rx="0" + ry="0" /> + <rect + ry="0" + rx="0" + y="288.58734" + x="570.23572" + height="0.46407768" + width="18.749891" + id="rect9454" + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect9456" + width="19.03573" + height="0.46407768" + x="570.12854" + y="287.37177" + rx="0" + ry="0" /> + <rect + ry="0" + rx="0" + y="286.76398" + x="570.09283" + height="0.46407768" + width="19.121912" + id="rect9458" + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect9460" + width="19.424026" + height="0.46407768" + x="570.02136" + y="285.5484" + rx="0" + ry="0" /> + <rect + ry="0" + rx="0" + y="280.07831" + x="569.02136" + height="0.46407768" + width="21.464485" + id="rect9462" + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect9464" + width="20.250107" + height="0.46407768" + x="569.62836" + y="283.11725" + rx="0" + ry="0" /> + <rect + ry="0" + rx="0" + y="284.33282" + x="569.84271" + height="0.46407768" + width="19.809723" + id="rect9466" + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + ry="0" + rx="0" + y="281.90167" + x="569.40063" + height="0.46407768" + width="20.799389" + id="rect9468" + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + ry="0" + rx="0" + y="278.86273" + x="575.89502" + height="0.46407768" + width="14.800593" + id="rect9470" + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect9472" + width="13.804427" + height="0.46407768" + x="577.0307" + y="278.25494" + rx="0" + ry="0" /> + <rect + ry="0" + rx="0" + y="277.64716" + x="578.34863" + height="0.46407768" + width="12.505892" + id="rect9474" + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect9476" + width="18.584423" + height="0.46407768" + x="570.14148" + y="292.23407" + rx="0" + ry="0" /> + </g> + <rect + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/22x22/actions/amarok_files2.png" + y="751.68945" + x="759.38361" + height="22" + width="22" + id="rect11487" + style="fill:#b9cff9;fill-opacity:0;stroke:none;stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" /> + </g> + <g + id="g13658" + transform="translate(1,-13)"> + <g + id="g8243" + transform="matrix(0.7275693,0,0,0.6826617,306.87334,572.27321)" + inkscape:export-filename="/home/lando/Projects/Amarok/icons/16/amarok_files2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <rect + style="fill:url(#linearGradient8419);fill-opacity:1;stroke:none;stroke-width:0.97624999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect8245" + width="18.63254" + height="18.118319" + x="570.13348" + y="274.90677" /> + <rect + style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect8247" + width="16.988491" + height="15.785593" + x="570.68146" + y="276.63235" + ry="1.1653774" + rx="0.76541269" /> + <path + style="opacity:0.6978022;fill:url(#linearGradient8421);fill-opacity:1;stroke:none;stroke-width:0.97624999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1" + d="M 569.02378,279.70319 L 574.53008,279.67837 L 579.45944,277.23948 C 579.45944,277.23948 590.78898,276.82043 590.97795,277.2925 C 591.14705,277.71497 588.46027,288.61152 588.67476,292.97207 L 570.24413,293.02508 C 570.27712,288.85894 570.42607,286.73234 569.02378,279.70319 z " + id="path8249" + sodipodi:nodetypes="ccczccc" /> + <path + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:url(#linearGradient26818);stroke-width:0.28610274;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 569.13509,279.69625 L 574.61828,279.66805 L 579.47952,277.23948 L 590.84899,277.23948" + id="path8251" + sodipodi:nodetypes="cccc" /> + <rect + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect8253" + width="21.23255" + height="0.46407768" + x="569.20001" + y="280.6861" + rx="0" + ry="0" /> + <rect + ry="0" + rx="0" + y="291.62628" + x="570.16431" + height="0.46407768" + width="18.571243" + id="rect8255" + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect8257" + width="18.571243" + height="0.46407768" + x="570.16431" + y="290.41071" + rx="0" + ry="0" /> + <rect + ry="0" + rx="0" + y="289.19513" + x="570.12854" + height="0.46407768" + width="18.714163" + id="rect8259" + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect8261" + width="18.892811" + height="0.46407768" + x="570.12854" + y="287.97955" + rx="0" + ry="0" /> + <rect + ry="0" + rx="0" + y="286.15619" + x="570.09283" + height="0.46407768" + width="19.229099" + id="rect8263" + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect8265" + width="19.638405" + height="0.46407768" + x="569.91418" + y="284.94061" + rx="0" + ry="0" /> + <rect + ry="0" + rx="0" + y="281.29388" + x="569.30719" + height="0.46407768" + width="20.892811" + id="rect8267" + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect8269" + width="20.464485" + height="0.46407768" + x="569.55688" + y="282.50946" + rx="0.1200456" + ry="0" /> + <rect + ry="0" + rx="0" + y="283.72504" + x="569.73553" + height="0.46407768" + width="20.024101" + id="rect8271" + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + ry="0" + rx="0" + y="279.47052" + x="574.63464" + height="0.46407768" + width="16.012278" + id="rect8273" + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect8275" + width="18.642706" + height="0.46407768" + x="570.12854" + y="291.01849" + rx="0" + ry="0" /> + <rect + ry="0" + rx="0" + y="289.80292" + x="570.16431" + height="0.46407768" + width="18.571243" + id="rect8277" + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect8279" + width="18.749891" + height="0.46407768" + x="570.23572" + y="288.58734" + rx="0" + ry="0" /> + <rect + ry="0" + rx="0" + y="287.37177" + x="570.12854" + height="0.46407768" + width="19.03573" + id="rect8281" + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect8283" + width="19.121912" + height="0.46407768" + x="570.09283" + y="286.76398" + rx="0" + ry="0" /> + <rect + ry="0" + rx="0" + y="285.5484" + x="570.02136" + height="0.46407768" + width="19.424026" + id="rect8285" + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect8287" + width="21.464485" + height="0.46407768" + x="569.02136" + y="280.07831" + rx="0" + ry="0" /> + <rect + ry="0" + rx="0" + y="283.11725" + x="569.62836" + height="0.46407768" + width="20.250107" + id="rect8289" + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect8291" + width="19.809723" + height="0.46407768" + x="569.84271" + y="284.33282" + rx="0" + ry="0" /> + <rect + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect8293" + width="20.799389" + height="0.46407768" + x="569.40063" + y="281.90167" + rx="0" + ry="0" /> + <rect + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect8295" + width="14.800593" + height="0.46407768" + x="575.89502" + y="278.86273" + rx="0" + ry="0" /> + <rect + ry="0" + rx="0" + y="278.25494" + x="577.0307" + height="0.46407768" + width="13.804427" + id="rect8297" + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect8299" + width="12.505892" + height="0.46407768" + x="578.34863" + y="277.64716" + rx="0" + ry="0" /> + <rect + ry="0" + rx="0" + y="292.23407" + x="570.14148" + height="0.46407768" + width="18.584423" + id="rect8301" + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + <rect + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/16x16/actions/amarok_files2.png" + y="758.12585" + x="720.85773" + height="16" + width="16" + id="rect11489" + style="fill:#b9cff9;fill-opacity:0;stroke:none;stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" /> + </g> + <rect + style="fill:#b9cff9;fill-opacity:0;stroke:none;stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + id="rect11491" + width="64" + height="64" + x="922.78711" + y="618.68945" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/64x64/actions/amarok_files.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <g + id="g13790" + transform="translate(1,-13)"> + <g + id="g9290" + transform="matrix(2.1827079,0,0,2.0479836,-391.57513,144.81901)" + inkscape:export-filename="/home/lando/Projects/Amarok/icons/48/amarok_files.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <rect + y="248.20387" + x="571.11212" + height="18.118319" + width="18.63254" + id="rect9292" + style="fill:url(#linearGradient9350);fill-opacity:1;stroke:none;stroke-width:0.97624999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + rx="0.25513759" + ry="0.38845918" + y="249.92944" + x="571.6601" + height="15.785593" + width="16.988491" + id="rect9294" + style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + sodipodi:nodetypes="ccczccc" + id="path9296" + d="M 570.00242,253.00028 L 575.50872,252.97546 L 580.43808,250.53657 C 580.43808,250.53657 591.76762,250.11752 591.95659,250.58959 C 592.12569,251.01206 589.43891,261.90861 589.6534,266.26916 L 571.22277,266.32217 C 571.25576,262.15603 571.40471,260.02943 570.00242,253.00028 z " + style="opacity:0.6978022;fill:url(#linearGradient9352);fill-opacity:1;stroke:none;stroke-width:0.97624999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1" /> + <path + sodipodi:nodetypes="cccc" + id="path9298" + d="M 570.11373,252.99334 L 575.59692,252.96514 L 580.45816,250.53657 L 591.82763,250.53657" + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:url(#linearGradient26781);stroke-width:0.28610274;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + ry="0" + rx="0" + y="253.9832" + x="570.17865" + height="0.46407768" + width="21.23255" + id="rect9300" + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect9302" + width="18.571243" + height="0.46407768" + x="571.14294" + y="264.92337" + rx="0" + ry="0" /> + <rect + ry="0" + rx="0" + y="263.70779" + x="571.14294" + height="0.46407768" + width="18.571243" + id="rect9304" + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect9306" + width="18.714163" + height="0.46407768" + x="571.10718" + y="262.49222" + rx="0" + ry="0" /> + <rect + ry="0" + rx="0" + y="261.27664" + x="571.10718" + height="0.46407768" + width="18.892811" + id="rect9308" + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect9310" + width="19.229099" + height="0.46407768" + x="571.07147" + y="259.45328" + rx="0" + ry="0" /> + <rect + ry="0" + rx="0" + y="258.2377" + x="570.89282" + height="0.46407768" + width="19.638405" + id="rect9312" + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect9314" + width="20.892811" + height="0.46407768" + x="570.28583" + y="254.59099" + rx="0" + ry="0" /> + <rect + ry="0" + rx="0.040015202" + y="255.80656" + x="570.53552" + height="0.46407768" + width="20.464485" + id="rect9316" + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect9318" + width="20.024101" + height="0.46407768" + x="570.71417" + y="257.02213" + rx="0" + ry="0" /> + <rect + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect9320" + width="16.012278" + height="0.46407768" + x="575.61328" + y="252.76762" + rx="0" + ry="0" /> + <rect + ry="0" + rx="0" + y="264.31558" + x="571.10718" + height="0.46407768" + width="18.642706" + id="rect9322" + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect9324" + width="18.571243" + height="0.46407768" + x="571.14294" + y="263.10001" + rx="0" + ry="0" /> + <rect + ry="0" + rx="0" + y="261.88443" + x="571.21436" + height="0.46407768" + width="18.749891" + id="rect9326" + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect9328" + width="19.03573" + height="0.46407768" + x="571.10718" + y="260.66885" + rx="0" + ry="0" /> + <rect + ry="0" + rx="0" + y="260.06107" + x="571.07147" + height="0.46407768" + width="19.121912" + id="rect9330" + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect9332" + width="19.424026" + height="0.46407768" + x="571" + y="258.84549" + rx="0" + ry="0" /> + <rect + ry="0" + rx="0" + y="253.37541" + x="570" + height="0.46407768" + width="21.464485" + id="rect9334" + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect9336" + width="20.250107" + height="0.46407768" + x="570.60699" + y="256.41434" + rx="0" + ry="0" /> + <rect + ry="0" + rx="0" + y="257.62991" + x="570.82135" + height="0.46407768" + width="19.809723" + id="rect9338" + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + ry="0" + rx="0" + y="255.19878" + x="570.37927" + height="0.46407768" + width="20.799389" + id="rect9340" + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + ry="0" + rx="0" + y="252.15984" + x="576.87366" + height="0.46407768" + width="14.800593" + id="rect9342" + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect9344" + width="13.804427" + height="0.46407768" + x="578.00934" + y="251.55205" + rx="0" + ry="0" /> + <rect + ry="0" + rx="0" + y="250.94426" + x="579.32727" + height="0.46407768" + width="12.505892" + id="rect9346" + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect9348" + width="18.584423" + height="0.46407768" + x="571.12012" + y="265.53116" + rx="0" + ry="0" /> + </g> + <rect + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/48x48/actions/amarok_files.png" + style="fill:#b9cff9;fill-opacity:0;stroke:none;stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + id="rect11493" + width="48" + height="48" + x="852.5141" + y="647.68945" /> + </g> + <g + id="g13757" + transform="translate(1,-13)"> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/icons/32/amarok_files.png" + transform="matrix(1.4551386,0,0,1.3653227,-34.054637,323.44242)" + id="g9226"> + <rect + style="fill:url(#linearGradient9286);fill-opacity:1;stroke:none;stroke-width:0.97624999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect9228" + width="18.63254" + height="18.118319" + x="571.11212" + y="248.20387" /> + <rect + style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect9230" + width="16.988491" + height="15.785593" + x="571.6601" + y="249.92944" + ry="0.58268887" + rx="0.38270643" /> + <path + style="opacity:0.6978022;fill:url(#linearGradient9288);fill-opacity:1;stroke:none;stroke-width:0.97624999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1" + d="M 570.00242,253.00028 L 575.50872,252.97546 L 580.43808,250.53657 C 580.43808,250.53657 591.76762,250.11752 591.95659,250.58959 C 592.12569,251.01206 589.43891,261.90861 589.6534,266.26916 L 571.22277,266.32217 C 571.25576,262.15603 571.40471,260.02943 570.00242,253.00028 z " + id="path9232" + sodipodi:nodetypes="ccczccc" /> + <path + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:url(#linearGradient26781);stroke-width:0.28610274;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 570.11373,252.99334 L 575.59692,252.96514 L 580.45816,250.53657 L 591.82763,250.53657" + id="path9234" + sodipodi:nodetypes="cccc" /> + <rect + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect9236" + width="21.23255" + height="0.46407768" + x="570.17865" + y="253.9832" + rx="0" + ry="0" /> + <rect + ry="0" + rx="0" + y="264.92337" + x="571.14294" + height="0.46407768" + width="18.571243" + id="rect9238" + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect9240" + width="18.571243" + height="0.46407768" + x="571.14294" + y="263.70779" + rx="0" + ry="0" /> + <rect + ry="0" + rx="0" + y="262.49222" + x="571.10718" + height="0.46407768" + width="18.714163" + id="rect9242" + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect9244" + width="18.892811" + height="0.46407768" + x="571.10718" + y="261.27664" + rx="0" + ry="0" /> + <rect + ry="0" + rx="0" + y="259.45328" + x="571.07147" + height="0.46407768" + width="19.229099" + id="rect9246" + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect9248" + width="19.638405" + height="0.46407768" + x="570.89282" + y="258.2377" + rx="0" + ry="0" /> + <rect + ry="0" + rx="0" + y="254.59099" + x="570.28583" + height="0.46407768" + width="20.892811" + id="rect9250" + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect9252" + width="20.464485" + height="0.46407768" + x="570.53552" + y="255.80656" + rx="0.060022812" + ry="0" /> + <rect + ry="0" + rx="0" + y="257.02213" + x="570.71417" + height="0.46407768" + width="20.024101" + id="rect9254" + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + ry="0" + rx="0" + y="252.76762" + x="575.61328" + height="0.46407768" + width="16.012278" + id="rect9256" + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect9258" + width="18.642706" + height="0.46407768" + x="571.10718" + y="264.31558" + rx="0" + ry="0" /> + <rect + ry="0" + rx="0" + y="263.10001" + x="571.14294" + height="0.46407768" + width="18.571243" + id="rect9260" + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect9262" + width="18.749891" + height="0.46407768" + x="571.21436" + y="261.88443" + rx="0" + ry="0" /> + <rect + ry="0" + rx="0" + y="260.66885" + x="571.10718" + height="0.46407768" + width="19.03573" + id="rect9264" + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect9266" + width="19.121912" + height="0.46407768" + x="571.07147" + y="260.06107" + rx="0" + ry="0" /> + <rect + ry="0" + rx="0" + y="258.84549" + x="571" + height="0.46407768" + width="19.424026" + id="rect9268" + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect9270" + width="21.464485" + height="0.46407768" + x="570" + y="253.37541" + rx="0" + ry="0" /> + <rect + ry="0" + rx="0" + y="256.41434" + x="570.60699" + height="0.46407768" + width="20.250107" + id="rect9272" + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect9274" + width="19.809723" + height="0.46407768" + x="570.82135" + y="257.62991" + rx="0" + ry="0" /> + <rect + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect9276" + width="20.799389" + height="0.46407768" + x="570.37927" + y="255.19878" + rx="0" + ry="0" /> + <rect + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect9278" + width="14.800593" + height="0.46407768" + x="576.87366" + y="252.15984" + rx="0" + ry="0" /> + <rect + ry="0" + rx="0" + y="251.55205" + x="578.00934" + height="0.46407768" + width="13.804427" + id="rect9280" + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect9282" + width="12.505892" + height="0.46407768" + x="579.32727" + y="250.94426" + rx="0" + ry="0" /> + <rect + ry="0" + rx="0" + y="265.53116" + x="571.12012" + height="0.46407768" + width="18.584423" + id="rect9284" + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + <rect + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/32x32/actions/amarok_files.png" + y="658.68945" + x="795.33826" + height="32" + width="32" + id="rect11495" + style="fill:#b9cff9;fill-opacity:0;stroke:none;stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" /> + </g> + <g + id="g13724" + transform="translate(1,-13)"> + <g + id="g8183" + transform="matrix(1.0004078,0,0,0.9386595,186.48212,437.20711)" + inkscape:export-filename="/home/lando/Projects/Amarok/icons/22/amarok_files.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <rect + y="248.20387" + x="571.11212" + height="18.118319" + width="18.63254" + id="rect8185" + style="fill:url(#linearGradient8415);fill-opacity:1;stroke:none;stroke-width:0.97624999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + rx="0.55666375" + ry="0.84754723" + y="249.92944" + x="571.6601" + height="15.785593" + width="16.988491" + id="rect8187" + style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + sodipodi:nodetypes="ccczccc" + id="path8189" + d="M 570.00242,253.00028 L 575.50872,252.97546 L 580.43808,250.53657 C 580.43808,250.53657 591.76762,250.11752 591.95659,250.58959 C 592.12569,251.01206 589.43891,261.90861 589.6534,266.26916 L 571.22277,266.32217 C 571.25576,262.15603 571.40471,260.02943 570.00242,253.00028 z " + style="opacity:0.6978022;fill:url(#linearGradient8417);fill-opacity:1;stroke:none;stroke-width:0.97624999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1" /> + <path + sodipodi:nodetypes="cccc" + id="path8191" + d="M 570.11373,252.99334 L 575.59692,252.96514 L 580.45816,250.53657 L 591.82763,250.53657" + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:url(#linearGradient26781);stroke-width:0.28610274;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + ry="0" + rx="0" + y="253.9832" + x="570.17865" + height="0.46407768" + width="21.23255" + id="rect8193" + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect8195" + width="18.571243" + height="0.46407768" + x="571.14294" + y="264.92337" + rx="0" + ry="0" /> + <rect + ry="0" + rx="0" + y="263.70779" + x="571.14294" + height="0.46407768" + width="18.571243" + id="rect8197" + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect8199" + width="18.714163" + height="0.46407768" + x="571.10718" + y="262.49222" + rx="0" + ry="0" /> + <rect + ry="0" + rx="0" + y="261.27664" + x="571.10718" + height="0.46407768" + width="18.892811" + id="rect8201" + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect8203" + width="19.229099" + height="0.46407768" + x="571.07147" + y="259.45328" + rx="0" + ry="0" /> + <rect + ry="0" + rx="0" + y="258.2377" + x="570.89282" + height="0.46407768" + width="19.638405" + id="rect8205" + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect8207" + width="20.892811" + height="0.46407768" + x="570.28583" + y="254.59099" + rx="0" + ry="0" /> + <rect + ry="0" + rx="0.087305889" + y="255.80656" + x="570.53552" + height="0.46407768" + width="20.464485" + id="rect8209" + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect8211" + width="20.024101" + height="0.46407768" + x="570.71417" + y="257.02213" + rx="0" + ry="0" /> + <rect + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect8213" + width="16.012278" + height="0.46407768" + x="575.61328" + y="252.76762" + rx="0" + ry="0" /> + <rect + ry="0" + rx="0" + y="264.31558" + x="571.10718" + height="0.46407768" + width="18.642706" + id="rect8215" + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect8217" + width="18.571243" + height="0.46407768" + x="571.14294" + y="263.10001" + rx="0" + ry="0" /> + <rect + ry="0" + rx="0" + y="261.88443" + x="571.21436" + height="0.46407768" + width="18.749891" + id="rect8219" + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect8221" + width="19.03573" + height="0.46407768" + x="571.10718" + y="260.66885" + rx="0" + ry="0" /> + <rect + ry="0" + rx="0" + y="260.06107" + x="571.07147" + height="0.46407768" + width="19.121912" + id="rect8223" + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect8225" + width="19.424026" + height="0.46407768" + x="571" + y="258.84549" + rx="0" + ry="0" /> + <rect + ry="0" + rx="0" + y="253.37541" + x="570" + height="0.46407768" + width="21.464485" + id="rect8227" + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect8229" + width="20.250107" + height="0.46407768" + x="570.60699" + y="256.41434" + rx="0" + ry="0" /> + <rect + ry="0" + rx="0" + y="257.62991" + x="570.82135" + height="0.46407768" + width="19.809723" + id="rect8231" + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + ry="0" + rx="0" + y="255.19878" + x="570.37927" + height="0.46407768" + width="20.799389" + id="rect8233" + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + ry="0" + rx="0" + y="252.15984" + x="576.87366" + height="0.46407768" + width="14.800593" + id="rect8235" + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect8237" + width="13.804427" + height="0.46407768" + x="578.00934" + y="251.55205" + rx="0" + ry="0" /> + <rect + ry="0" + rx="0" + y="250.94426" + x="579.32727" + height="0.46407768" + width="12.505892" + id="rect8239" + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect8241" + width="18.584423" + height="0.46407768" + x="571.12012" + y="265.53116" + rx="0" + ry="0" /> + </g> + <rect + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/22x22/actions/amarok_files.png" + style="fill:#b9cff9;fill-opacity:0;stroke:none;stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + id="rect11497" + width="22" + height="22" + x="756.6897" + y="667.68945" /> + </g> + <g + id="g13691" + transform="translate(1,-13)"> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/16x16/actions/amarok_files.png" + transform="matrix(0.7275693,0,0,0.6826617,306.06216,504.50231)" + id="g9162"> + <rect + style="fill:url(#linearGradient9222);fill-opacity:1;stroke:none;stroke-width:0.97624999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect9164" + width="18.63254" + height="18.118319" + x="571.11212" + y="248.20387" /> + <rect + style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect9166" + width="16.988491" + height="15.785593" + x="571.6601" + y="249.92944" + ry="1.1653774" + rx="0.76541269" /> + <path + style="opacity:0.6978022;fill:url(#linearGradient9224);fill-opacity:1;stroke:none;stroke-width:0.97624999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1" + d="M 570.00242,253.00028 L 575.50872,252.97546 L 580.43808,250.53657 C 580.43808,250.53657 591.76762,250.11752 591.95659,250.58959 C 592.12569,251.01206 589.43891,261.90861 589.6534,266.26916 L 571.22277,266.32217 C 571.25576,262.15603 571.40471,260.02943 570.00242,253.00028 z " + id="path9168" + sodipodi:nodetypes="ccczccc" /> + <path + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:url(#linearGradient26781);stroke-width:0.28610274;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 570.11373,252.99334 L 575.59692,252.96514 L 580.45816,250.53657 L 591.82763,250.53657" + id="path9170" + sodipodi:nodetypes="cccc" /> + <rect + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect9172" + width="21.23255" + height="0.46407768" + x="570.17865" + y="253.9832" + rx="0" + ry="0" /> + <rect + ry="0" + rx="0" + y="264.92337" + x="571.14294" + height="0.46407768" + width="18.571243" + id="rect9174" + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect9176" + width="18.571243" + height="0.46407768" + x="571.14294" + y="263.70779" + rx="0" + ry="0" /> + <rect + ry="0" + rx="0" + y="262.49222" + x="571.10718" + height="0.46407768" + width="18.714163" + id="rect9178" + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect9180" + width="18.892811" + height="0.46407768" + x="571.10718" + y="261.27664" + rx="0" + ry="0" /> + <rect + ry="0" + rx="0" + y="259.45328" + x="571.07147" + height="0.46407768" + width="19.229099" + id="rect9182" + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect9184" + width="19.638405" + height="0.46407768" + x="570.89282" + y="258.2377" + rx="0" + ry="0" /> + <rect + ry="0" + rx="0" + y="254.59099" + x="570.28583" + height="0.46407768" + width="20.892811" + id="rect9186" + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect9188" + width="20.464485" + height="0.46407768" + x="570.53552" + y="255.80656" + rx="0.1200456" + ry="0" /> + <rect + ry="0" + rx="0" + y="257.02213" + x="570.71417" + height="0.46407768" + width="20.024101" + id="rect9190" + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + ry="0" + rx="0" + y="252.76762" + x="575.61328" + height="0.46407768" + width="16.012278" + id="rect9192" + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect9194" + width="18.642706" + height="0.46407768" + x="571.10718" + y="264.31558" + rx="0" + ry="0" /> + <rect + ry="0" + rx="0" + y="263.10001" + x="571.14294" + height="0.46407768" + width="18.571243" + id="rect9196" + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect9198" + width="18.749891" + height="0.46407768" + x="571.21436" + y="261.88443" + rx="0" + ry="0" /> + <rect + ry="0" + rx="0" + y="260.66885" + x="571.10718" + height="0.46407768" + width="19.03573" + id="rect9200" + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect9202" + width="19.121912" + height="0.46407768" + x="571.07147" + y="260.06107" + rx="0" + ry="0" /> + <rect + ry="0" + rx="0" + y="258.84549" + x="571" + height="0.46407768" + width="19.424026" + id="rect9204" + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect9206" + width="21.464485" + height="0.46407768" + x="570" + y="253.37541" + rx="0" + ry="0" /> + <rect + ry="0" + rx="0" + y="256.41434" + x="570.60699" + height="0.46407768" + width="20.250107" + id="rect9208" + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect9210" + width="19.809723" + height="0.46407768" + x="570.82135" + y="257.62991" + rx="0" + ry="0" /> + <rect + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect9212" + width="20.799389" + height="0.46407768" + x="570.37927" + y="255.19878" + rx="0" + ry="0" /> + <rect + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect9214" + width="14.800593" + height="0.46407768" + x="576.87366" + y="252.15984" + rx="0" + ry="0" /> + <rect + ry="0" + rx="0" + y="251.55205" + x="578.00934" + height="0.46407768" + width="13.804427" + id="rect9216" + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect9218" + width="12.505892" + height="0.46407768" + x="579.32727" + y="250.94426" + rx="0" + ry="0" /> + <rect + ry="0" + rx="0" + y="265.53116" + x="571.12012" + height="0.46407768" + width="18.584423" + id="rect9220" + style="opacity:0.05769234;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.49599999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + <rect + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/16x16/actions/amarok_files.png" + style="fill:#b9cff9;fill-opacity:0;stroke:none;stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + id="rect11499" + width="16" + height="16" + x="720.75861" + y="672.12585" /> + </g> + <g + id="g13856" + transform="translate(1,-13)"> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/16x16/actions/amarok_artist.png" + id="g8548" + transform="matrix(0.727273,0,0,0.727273,322.47072,165.94026)"> + <path + style="fill:url(#linearGradient8558);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient8560);stroke-width:0.76872301;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 533.75405,95.921 C 536.1344,93.9568 538.75448,95.57073 539.46715,96.43629 C 540.18879,97.31275 540.34562,98.0857 540.6693,99.01481 C 541.97878,101.82587 546.33908,107.83622 548.33924,110.28214 C 550.47943,112.89931 550.81751,113.69102 551.31901,114.2477 C 551.07257,114.98704 549.86274,115.97283 548.80974,116.21928 C 548.45127,115.88322 548.1028,115.71054 545.4659,112.77462 C 542.90555,109.92395 537.89884,104.68107 535.65842,103.22479 C 534.71744,102.82152 533.8413,102.43272 533.26116,101.61169 C 532.67346,100.77996 531.38701,97.87422 533.75405,95.921 z " + id="path8550" + sodipodi:nodetypes="czcsccsczz" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_artist.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:nodetypes="czcccczz" + id="path8552" + d="M 533.75405,95.921 C 536.1344,93.9568 538.75448,95.57073 539.46715,96.43629 C 540.18879,97.31275 540.34562,98.0857 540.6693,99.01481 C 541.3451,100.32086 541.10835,99.92544 541.52708,100.68176 C 539.99187,101.61965 538.37568,102.98923 537.19625,104.31487 C 535.80093,103.25654 536.94831,104.17412 535.65842,103.22479 C 534.71744,102.82152 533.8413,102.43272 533.26116,101.61169 C 532.67346,100.77996 531.38701,97.87422 533.75405,95.921 z " + style="fill:url(#linearGradient8562);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_artist.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:nodetypes="ccccc" + id="path8554" + d="M 539.66765,96.68321 C 540.29425,97.60719 540.34562,98.0857 540.6693,99.01481 C 538.84203,100.08323 537.04337,101.51263 535.65842,103.22479 C 534.73664,102.79271 534.12364,102.54784 533.40374,101.83348 C 534.69579,100.15274 537.60513,97.81958 539.66765,96.68321 z " + style="fill:url(#linearGradient8564);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_artist.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + style="fill:url(#linearGradient8566);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 533.75405,95.921 C 534.94422,94.9389 536.19434,94.85133 537.23598,95.11748 C 537.7568,95.25056 538.22551,95.47207 538.60856,95.7144 C 538.80008,95.83557 539.09421,96.05044 539.23871,96.17357 C 537.65641,96.94623 534.4499,99.53365 533.01652,101.20848 C 532.92973,101.04561 532.8405,100.85625 532.75738,100.6463 C 532.59111,100.22641 532.44918,99.72418 532.39958,99.1869 C 532.30039,98.11235 532.57053,96.89761 533.75405,95.921 z " + id="path8556" + sodipodi:nodetypes="cssccssz" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_artist.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + </g> + <rect + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/16x16/actions/amarok_artist.png" + y="234.74295" + x="708.54352" + height="16" + width="16" + id="rect11501" + style="fill:#b9cff9;fill-opacity:0;stroke:none;stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" /> + </g> + <g + id="g13848" + transform="translate(1,-13)"> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/icons/22/amarok_artist.png" + transform="translate(210.01072,133.94786)" + id="g8107"> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_artist.png" + sodipodi:nodetypes="czcsccsczz" + id="path8109" + d="M 533.75405,95.921 C 536.1344,93.9568 538.75448,95.57073 539.46715,96.43629 C 540.18879,97.31275 540.34562,98.0857 540.6693,99.01481 C 541.97878,101.82587 546.33908,107.83622 548.33924,110.28214 C 550.47943,112.89931 550.81751,113.69102 551.31901,114.2477 C 551.07257,114.98704 549.86274,115.97283 548.80974,116.21928 C 548.45127,115.88322 548.1028,115.71054 545.4659,112.77462 C 542.90555,109.92395 537.89884,104.68107 535.65842,103.22479 C 534.71744,102.82152 533.8413,102.43272 533.26116,101.61169 C 532.67346,100.77996 531.38701,97.87422 533.75405,95.921 z " + style="fill:url(#linearGradient8345);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient8347);stroke-width:0.76872301;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_artist.png" + style="fill:url(#linearGradient8349);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 533.75405,95.921 C 536.1344,93.9568 538.75448,95.57073 539.46715,96.43629 C 540.18879,97.31275 540.34562,98.0857 540.6693,99.01481 C 541.3451,100.32086 541.10835,99.92544 541.52708,100.68176 C 539.99187,101.61965 538.37568,102.98923 537.19625,104.31487 C 535.80093,103.25654 536.94831,104.17412 535.65842,103.22479 C 534.71744,102.82152 533.8413,102.43272 533.26116,101.61169 C 532.67346,100.77996 531.38701,97.87422 533.75405,95.921 z " + id="path8111" + sodipodi:nodetypes="czcccczz" /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_artist.png" + style="fill:url(#linearGradient8351);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 539.66765,96.68321 C 540.29425,97.60719 540.34562,98.0857 540.6693,99.01481 C 538.84203,100.08323 537.04337,101.51263 535.65842,103.22479 C 534.73664,102.79271 534.12364,102.54784 533.40374,101.83348 C 534.69579,100.15274 537.60513,97.81958 539.66765,96.68321 z " + id="path8113" + sodipodi:nodetypes="ccccc" /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_artist.png" + sodipodi:nodetypes="cssccssz" + id="path8115" + d="M 533.75405,95.921 C 534.94422,94.9389 536.19434,94.85133 537.23598,95.11748 C 537.7568,95.25056 538.22551,95.47207 538.60856,95.7144 C 538.80008,95.83557 539.09421,96.05044 539.23871,96.17357 C 537.65641,96.94623 534.4499,99.53365 533.01652,101.20848 C 532.92973,101.04561 532.8405,100.85625 532.75738,100.6463 C 532.59111,100.22641 532.44918,99.72418 532.39958,99.1869 C 532.30039,98.11235 532.57053,96.89761 533.75405,95.921 z " + style="fill:url(#linearGradient8353);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + <rect + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/22x22/actions/amarok_artist.png" + style="fill:#b9cff9;fill-opacity:0;stroke:none;stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + id="rect11503" + width="22" + height="22" + x="740.8606" + y="228.55148" /> + </g> + <g + id="g13839" + transform="translate(1,-13)"> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/icons/32/amarok_artist.png" + id="g8568" + transform="matrix(1.454546,0,0,1.454546,15.007143,80.446152)"> + <path + style="fill:url(#linearGradient8579);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient8581);stroke-width:0.76872301;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 533.75405,95.921 C 536.1344,93.9568 538.75448,95.57073 539.46715,96.43629 C 540.18879,97.31275 540.34562,98.0857 540.6693,99.01481 C 541.97878,101.82587 546.33908,107.83622 548.33924,110.28214 C 550.47943,112.89931 550.81751,113.69102 551.31901,114.2477 C 551.07257,114.98704 549.86274,115.97283 548.80974,116.21928 C 548.45127,115.88322 548.1028,115.71054 545.4659,112.77462 C 542.90555,109.92395 537.89884,104.68107 535.65842,103.22479 C 534.71744,102.82152 533.8413,102.43272 533.26116,101.61169 C 532.67346,100.77996 531.38701,97.87422 533.75405,95.921 z " + id="path8570" + sodipodi:nodetypes="czcsccsczz" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_artist.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:nodetypes="czcccczz" + id="path8572" + d="M 533.75405,95.921 C 536.1344,93.9568 538.75448,95.57073 539.46715,96.43629 C 540.18879,97.31275 540.34562,98.0857 540.6693,99.01481 C 541.3451,100.32086 541.10835,99.92544 541.52708,100.68176 C 539.99187,101.61965 538.37568,102.98923 537.19625,104.31487 C 535.80093,103.25654 536.94831,104.17412 535.65842,103.22479 C 534.71744,102.82152 533.8413,102.43272 533.26116,101.61169 C 532.67346,100.77996 531.38701,97.87422 533.75405,95.921 z " + style="fill:url(#linearGradient8583);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_artist.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:nodetypes="ccccc" + id="path8574" + d="M 539.66765,96.68321 C 540.29425,97.60719 540.34562,98.0857 540.6693,99.01481 C 538.84203,100.08323 537.04337,101.51263 535.65842,103.22479 C 534.73664,102.79271 534.12364,102.54784 533.40374,101.83348 C 534.69579,100.15274 537.60513,97.81958 539.66765,96.68321 z " + style="fill:url(#linearGradient8585);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_artist.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + style="fill:url(#linearGradient8589);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 533.75405,95.921 C 534.94422,94.9389 536.19434,94.85133 537.23598,95.11748 C 537.7568,95.25056 538.22551,95.47207 538.60856,95.7144 C 538.80008,95.83557 539.09421,96.05044 539.23871,96.17357 C 537.65641,96.94623 534.4499,99.53365 533.01652,101.20848 C 532.92973,101.04561 532.8405,100.85625 532.75738,100.6463 C 532.59111,100.22641 532.44918,99.72418 532.39958,99.1869 C 532.30039,98.11235 532.57053,96.89761 533.75405,95.921 z " + id="path8577" + sodipodi:nodetypes="cssccssz" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_artist.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + </g> + <rect + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/32x32/actions/amarok_artist.png" + y="218.05148" + x="787.15271" + height="32" + width="32" + id="rect11505" + style="fill:#b9cff9;fill-opacity:0;stroke:none;stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" /> + </g> + <g + id="g13831" + transform="translate(1,-13)"> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/icons/48/amarok_artist.png" + transform="matrix(2.181819,0,0,2.181819,-322.66767,-4.3565482)" + id="g8591"> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_artist.png" + sodipodi:nodetypes="czcsccsczz" + id="path8593" + d="M 533.75405,95.921 C 536.1344,93.9568 538.75448,95.57073 539.46715,96.43629 C 540.18879,97.31275 540.34562,98.0857 540.6693,99.01481 C 541.97878,101.82587 546.33908,107.83622 548.33924,110.28214 C 550.47943,112.89931 550.81751,113.69102 551.31901,114.2477 C 551.07257,114.98704 549.86274,115.97283 548.80974,116.21928 C 548.45127,115.88322 548.1028,115.71054 545.4659,112.77462 C 542.90555,109.92395 537.89884,104.68107 535.65842,103.22479 C 534.71744,102.82152 533.8413,102.43272 533.26116,101.61169 C 532.67346,100.77996 531.38701,97.87422 533.75405,95.921 z " + style="fill:url(#linearGradient8604);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient8606);stroke-width:0.76872301;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_artist.png" + style="fill:url(#linearGradient8608);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 533.75405,95.921 C 536.1344,93.9568 538.75448,95.57073 539.46715,96.43629 C 540.18879,97.31275 540.34562,98.0857 540.6693,99.01481 C 541.3451,100.32086 541.10835,99.92544 541.52708,100.68176 C 539.99187,101.61965 538.37568,102.98923 537.19625,104.31487 C 535.80093,103.25654 536.94831,104.17412 535.65842,103.22479 C 534.71744,102.82152 533.8413,102.43272 533.26116,101.61169 C 532.67346,100.77996 531.38701,97.87422 533.75405,95.921 z " + id="path8595" + sodipodi:nodetypes="czcccczz" /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_artist.png" + style="fill:url(#linearGradient8610);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 539.66765,96.68321 C 540.29425,97.60719 540.34562,98.0857 540.6693,99.01481 C 538.84203,100.08323 537.04337,101.51263 535.65842,103.22479 C 534.73664,102.79271 534.12364,102.54784 533.40374,101.83348 C 534.69579,100.15274 537.60513,97.81958 539.66765,96.68321 z " + id="path8600" + sodipodi:nodetypes="ccccc" /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_artist.png" + sodipodi:nodetypes="cssccssz" + id="path8602" + d="M 533.75405,95.921 C 534.94422,94.9389 536.19434,94.85133 537.23598,95.11748 C 537.7568,95.25056 538.22551,95.47207 538.60856,95.7144 C 538.80008,95.83557 539.09421,96.05044 539.23871,96.17357 C 537.65641,96.94623 534.4499,99.53365 533.01652,101.20848 C 532.92973,101.04561 532.8405,100.85625 532.75738,100.6463 C 532.59111,100.22641 532.44918,99.72418 532.39958,99.1869 C 532.30039,98.11235 532.57053,96.89761 533.75405,95.921 z " + style="fill:url(#linearGradient8612);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + <rect + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/48x48/actions/amarok_artist.png" + style="fill:#b9cff9;fill-opacity:0;stroke:none;stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + id="rect11507" + width="48" + height="48" + x="835.55072" + y="202.05148" /> + </g> + <g + id="g13823" + transform="translate(1,-13)"> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/icons/64/amarok_artist.png" + id="g8614" + transform="matrix(2.909092,0,0,2.909092,-642.7477,-88.659228)"> + <path + style="fill:url(#linearGradient8626);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient8628);stroke-width:0.76872301;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 533.75405,95.921 C 536.1344,93.9568 538.75448,95.57073 539.46715,96.43629 C 540.18879,97.31275 540.34562,98.0857 540.6693,99.01481 C 541.97878,101.82587 546.33908,107.83622 548.33924,110.28214 C 550.47943,112.89931 550.81751,113.69102 551.31901,114.2477 C 551.07257,114.98704 549.86274,115.97283 548.80974,116.21928 C 548.45127,115.88322 548.1028,115.71054 545.4659,112.77462 C 542.90555,109.92395 537.89884,104.68107 535.65842,103.22479 C 534.71744,102.82152 533.8413,102.43272 533.26116,101.61169 C 532.67346,100.77996 531.38701,97.87422 533.75405,95.921 z " + id="path8617" + sodipodi:nodetypes="czcsccsczz" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_artist.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:nodetypes="czcccczz" + id="path8619" + d="M 533.75405,95.921 C 536.1344,93.9568 538.75448,95.57073 539.46715,96.43629 C 540.18879,97.31275 540.34562,98.0857 540.6693,99.01481 C 541.3451,100.32086 541.10835,99.92544 541.52708,100.68176 C 539.99187,101.61965 538.37568,102.98923 537.19625,104.31487 C 535.80093,103.25654 536.94831,104.17412 535.65842,103.22479 C 534.71744,102.82152 533.8413,102.43272 533.26116,101.61169 C 532.67346,100.77996 531.38701,97.87422 533.75405,95.921 z " + style="fill:url(#linearGradient8630);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_artist.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:nodetypes="ccccc" + id="path8621" + d="M 539.66765,96.68321 C 540.29425,97.60719 540.34562,98.0857 540.6693,99.01481 C 538.84203,100.08323 537.04337,101.51263 535.65842,103.22479 C 534.73664,102.79271 534.12364,102.54784 533.40374,101.83348 C 534.69579,100.15274 537.60513,97.81958 539.66765,96.68321 z " + style="fill:url(#linearGradient8632);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_artist.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + style="fill:url(#linearGradient8634);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 533.75405,95.921 C 534.94422,94.9389 536.19434,94.85133 537.23598,95.11748 C 537.7568,95.25056 538.22551,95.47207 538.60856,95.7144 C 538.80008,95.83557 539.09421,96.05044 539.23871,96.17357 C 537.65641,96.94623 534.4499,99.53365 533.01652,101.20848 C 532.92973,101.04561 532.8405,100.85625 532.75738,100.6463 C 532.59111,100.22641 532.44918,99.72418 532.39958,99.1869 C 532.30039,98.11235 532.57053,96.89761 533.75405,95.921 z " + id="path8624" + sodipodi:nodetypes="cssccssz" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_artist.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + </g> + <rect + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/64x64/actions/amarok_artist.png" + y="186.55148" + x="901.54346" + height="64" + width="64" + id="rect11509" + style="fill:#b9cff9;fill-opacity:0;stroke:none;stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" /> + </g> + <g + id="g11904" + transform="translate(-5,-5.9922714)"> + <g + inkscape:export-ydpi="90.000092" + inkscape:export-xdpi="90.000092" + inkscape:export-filename="/home/lando/Projects/Amarok/icons/16/amarok_playlist_clear.png" + transform="matrix(0.71698,0,0,0.71698,-116.05167,-460.21524)" + id="g14011"> + <g + transform="translate(79.02204,746.2202)" + id="g14013"> + <path + sodipodi:nodetypes="ccccccccc" + style="fill:url(#linearGradient14033);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient14035);stroke-width:1.03064454;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 214.55635,-8.9876506 C 213.0012,-8.9876506 211.75143,-8.4272616 211.75143,-6.7915466 L 211.75143,6.4368384 C 211.8402,7.0266884 212.4909,7.6066964 212.88141,7.7177054 L 231.75761,7.7177054 C 232.15119,7.6058244 232.95062,7.0333644 233.03661,6.4368384 L 233.03661,-6.7915466 C 233.03661,-8.4272616 231.78682,-8.9876506 230.23167,-8.9876506 L 214.55635,-8.9876506 z " + id="path14015" /> + <path + id="path14017" + d="M 213.8908,-4.6646566 L 230.92445,-4.6646566 C 231.46369,-4.6646566 231.89781,-4.2391996 231.89781,-3.7107166 L 231.89781,5.9163164 C 231.89781,6.4447994 231.46369,6.8702564 230.92445,6.8702564 L 213.8908,6.8702564 C 213.35155,6.8702564 212.91744,6.4447994 212.91744,5.9163164 L 212.91744,-3.7107166 C 212.91744,-4.2391996 213.35155,-4.6646566 213.8908,-4.6646566 z " + style="fill:url(#linearGradient14037);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient14039);stroke-width:1.35200274;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + style="opacity:0.99578091;fill:url(#radialGradient14041);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.57956129;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 213.95559,-4.6803566 L 230.84519,-4.6803566 C 231.37985,-4.6803566 231.81032,-4.2535196 231.81032,-3.7233216 L 231.81032,5.9349364 C 231.81032,6.4651324 231.37985,6.8919714 230.84519,6.8919714 L 213.95559,6.8919714 C 213.42098,6.8919714 212.9905,6.4651324 212.9905,5.9349364 L 212.9905,-3.7233216 C 212.9905,-4.2535196 213.42098,-4.6803566 213.95559,-4.6803566 z " + id="path14019" /> + <path + id="path14021" + d="M 214.7548,-8.7326426 C 213.22744,-8.7326426 212,-8.1994276 212,-6.6430286 L 212,-6.4102356 C 212.08716,-5.8489876 212.72625,-5.2971026 213.10978,-5.1914766 L 231.64865,-5.1914766 C 232.03518,-5.2979336 232.82035,-5.8426356 232.90479,-6.4102356 L 232.90479,-6.6430286 C 232.90479,-8.1994276 231.67735,-8.7326426 230.14998,-8.7326426 L 214.7548,-8.7326426 z " + style="opacity:0.15189873;fill:url(#linearGradient14043);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.71897209;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:nodetypes="ccccccccc" /> + </g> + <g + id="g14023" + transform="translate(80.86174,539.9254)"> + <g + id="g14025"> + <path + id="path14027" + d="M 216.59388,201.58154 L 216.59388,213.16264" + style="opacity:0.39183673;fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#264a9e;stroke-width:0.39787921;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:nodetypes="cc" /> + <path + style="opacity:0.39183673;fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#264a9e;stroke-width:0.39787921;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 223.52993,201.58154 L 223.52993,213.17716" + id="path14029" + sodipodi:nodetypes="cc" /> + </g> + <rect + style="opacity:0.56734691;fill:#6785ed;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.28953668;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect14031" + width="18.826923" + height="3.4617176" + x="211.13826" + y="205.69659" /> + </g> + </g> + <rect + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/16x16/actions/amarok_playlist_clear.png" + style="fill:#b9cff9;fill-opacity:0;stroke:none;stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + id="rect11511" + width="16" + height="16" + x="92.05761" + y="66.354454" /> + </g> + <g + id="g12907" + transform="translate(6,-18.470555)"> + <g + inkscape:export-ydpi="90.000092" + inkscape:export-xdpi="90.000092" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/22x22/actions/amarok_device.png" + transform="matrix(0.913989,0,0,0.913989,-129.78575,-432.05382)" + id="g18588"> + <rect + style="opacity:1;fill:url(#linearGradient11893);fill-opacity:1;stroke:url(#linearGradient18600);stroke-width:1.06073737;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect18590" + width="14.547573" + height="23.009743" + x="619.64813" + y="863.91321" + rx="2.5162525" + ry="2.5162528" /> + <path + style="opacity:0.80512821;fill:url(#linearGradient18602);fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 621.98005,864.8914 C 621.25253,864.8914 620.66749,865.512 620.66749,866.2801 L 620.66749,883.6391 C 620.66749,884.4072 621.25253,885.0228 621.98005,885.0228 L 621.60358,885.0228 C 620.87606,885.0228 620.29102,884.4072 620.29102,883.6391 L 620.29102,866.2801 C 620.29102,865.512 620.87606,864.8914 621.60358,864.8914 L 621.98005,864.8914 z " + id="path18592" /> + <path + style="fill:url(#radialGradient18604);fill-opacity:1;stroke:none;stroke-width:0.5;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 626.5323,874.8072 C 623.98062,875.0011 621.98653,877.1299 621.98654,879.7244 C 621.98654,882.4465 624.19284,884.6737 626.92194,884.6737 C 629.65106,884.6737 631.85734,882.4465 631.85733,879.7244 C 631.85733,877.0024 629.65107,874.8072 626.92194,874.8072 C 626.79401,874.8072 626.65779,874.7977 626.5323,874.8072 z M 626.79206,877.7961 C 626.85487,877.7897 626.9224,877.7961 626.98688,877.7961 C 628.01858,877.7961 628.87012,878.6311 628.87012,879.6601 C 628.87011,880.6892 628.01858,881.5242 626.98688,881.5242 C 625.95517,881.5242 625.13611,880.6892 625.13611,879.6601 C 625.13611,878.6954 625.84997,877.8916 626.79206,877.7961 z " + id="path18594" /> + <rect + style="fill:url(#linearGradient18606);fill-opacity:1;stroke:none;stroke-width:0.5;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect18596" + width="9.1704264" + height="7.0495582" + x="622.25305" + y="866.00488" + rx="0.79117817" + ry="0.79117841" /> + <path + inkscape:export-ydpi="90.000000" + inkscape:export-xdpi="90.000000" + inkscape:export-filename="/home/steve/Desktop/creative zen.png" + id="path18598" + d="M 623.13964,866.2289 C 622.91188,866.2289 622.5585,866.4761 622.5585,866.7092 L 622.57328,870.2008 C 626.57854,871.1331 626.14856,869.44 631.01911,869.6871 L 631.06558,866.6816 C 631.06558,866.4485 630.88221,866.2608 630.65446,866.2608 L 623.13964,866.2289 z " + style="opacity:0.29787233;fill:url(#linearGradient18608);fill-opacity:1;stroke:none;stroke-width:3.00600219;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + sodipodi:nodetypes="ccccccc" /> + </g> + <rect + inkscape:export-ydpi="90.000092" + inkscape:export-xdpi="90.000092" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/22x22/actions/amarok_device.png" + style="fill:#b9cff9;fill-opacity:0;stroke:none;stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + id="rect11350" + width="22" + height="22" + x="432.21399" + y="357.0687" /> + <rect + inkscape:export-ydpi="90.000092" + inkscape:export-xdpi="90.000092" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/22x22/actions/amarok_device.png" + y="357.0687" + x="432.21399" + height="22" + width="22" + id="rect11513" + style="fill:#b9cff9;fill-opacity:0;stroke:none;stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" /> + </g> + <g + id="g11918" + transform="translate(-5,-6.9922752)"> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/22x22/actions/amarok_playlist_clear.png" + transform="matrix(0.9984035,0,0,0.9984035,-166.678,-672.04045)" + id="g13999"> + <g + id="g13525" + transform="translate(79.02204,746.2202)"> + <path + id="path13527" + d="M 214.55635,-8.9876506 C 213.0012,-8.9876506 211.75143,-8.4272616 211.75143,-6.7915466 L 211.75143,6.4368384 C 211.8402,7.0266884 212.4909,7.6066964 212.88141,7.7177054 L 231.75761,7.7177054 C 232.15119,7.6058244 232.95062,7.0333644 233.03661,6.4368384 L 233.03661,-6.7915466 C 233.03661,-8.4272616 231.78682,-8.9876506 230.23167,-8.9876506 L 214.55635,-8.9876506 z " + style="fill:url(#linearGradient15504);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient15506);stroke-width:0.75;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:nodetypes="ccccccccc" /> + <path + style="fill:url(#linearGradient15508);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient15510);stroke-width:0.98385239;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 213.8908,-4.6646566 L 230.92445,-4.6646566 C 231.46369,-4.6646566 231.89781,-4.2391996 231.89781,-3.7107166 L 231.89781,5.9163164 C 231.89781,6.4447994 231.46369,6.8702564 230.92445,6.8702564 L 213.8908,6.8702564 C 213.35155,6.8702564 212.91744,6.4447994 212.91744,5.9163164 L 212.91744,-3.7107166 C 212.91744,-4.2391996 213.35155,-4.6646566 213.8908,-4.6646566 z " + id="path13529" /> + <path + id="path13531" + d="M 213.95559,-4.6803566 L 230.84519,-4.6803566 C 231.37985,-4.6803566 231.81032,-4.2535196 231.81032,-3.7233216 L 231.81032,5.9349364 C 231.81032,6.4651324 231.37985,6.8919714 230.84519,6.8919714 L 213.95559,6.8919714 C 213.42098,6.8919714 212.9905,6.4651324 212.9905,5.9349364 L 212.9905,-3.7233216 C 212.9905,-4.2535196 213.42098,-4.6803566 213.95559,-4.6803566 z " + style="opacity:0.99578091;fill:url(#radialGradient15512);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.57956129;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" /> + <path + sodipodi:nodetypes="ccccccccc" + style="opacity:0.15189873;fill:url(#linearGradient15514);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.71897209;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 214.7548,-8.7326426 C 213.22744,-8.7326426 212,-8.1994276 212,-6.6430286 L 212,-6.4102356 C 212.08716,-5.8489876 212.72625,-5.2971026 213.10978,-5.1914766 L 231.64865,-5.1914766 C 232.03518,-5.2979336 232.82035,-5.8426356 232.90479,-6.4102356 L 232.90479,-6.6430286 C 232.90479,-8.1994276 231.67735,-8.7326426 230.14998,-8.7326426 L 214.7548,-8.7326426 z " + id="path13533" /> + </g> + <g + transform="translate(80.86174,539.9254)" + id="g13555"> + <g + id="g13557"> + <path + sodipodi:nodetypes="cc" + style="opacity:0.39183673;fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#264a9e;stroke-width:0.28953668;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 216.59388,201.58154 L 216.59388,213.16264" + id="path13559" /> + <path + sodipodi:nodetypes="cc" + id="path13561" + d="M 223.52993,201.58154 L 223.52993,213.17716" + style="opacity:0.39183673;fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#264a9e;stroke-width:0.28953668;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + <rect + y="205.69659" + x="211.13826" + height="3.4617176" + width="18.826923" + id="rect13563" + style="opacity:0.56734691;fill:#6785ed;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.28953668;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + </g> + <rect + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/22x22/actions/amarok_playlist_clear.png" + y="61.354458" + x="123.25686" + height="22" + width="22" + id="rect11515" + style="fill:#b9cff9;fill-opacity:0;stroke:none;stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" /> + </g> + <g + id="g11932" + transform="translate(-5,-7.9922752)"> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/32x32/actions/amarok_playlist_clear.png" + transform="matrix(0.999518,0,0,0.999518,-155.43888,-678.1071)" + id="g14067"> + <g + id="g13535" + transform="translate(71.74824,778.9794)"> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Toolbar-and-menu-icons/amarok_playlist_clear.png" + sodipodi:nodetypes="ccccccccc" + style="fill:url(#linearGradient14217);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient14219);stroke-width:0.75036138;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 251.89706,-44.262813 C 249.61272,-44.262813 247.77694,-43.450681 247.77694,-41.080157 L 247.77694,-21.909202 C 247.90734,-21.054374 248.86314,-20.21381 249.43677,-20.052932 L 277.16366,-20.052932 C 277.74178,-20.215073 278.91607,-21.0447 279.04236,-21.909202 L 279.04236,-41.080157 C 279.04236,-43.450681 277.20658,-44.262813 274.92225,-44.262813 L 251.89706,-44.262813 z " + id="path13537" /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Toolbar-and-menu-icons/amarok_playlist_clear.png" + id="path13539" + d="M 250.12056,-38.645009 L 276.69881,-38.645009 C 277.54022,-38.645009 278.21759,-37.985392 278.21759,-37.166047 L 278.21759,-22.24056 C 278.21759,-21.421215 277.54022,-20.761598 276.69881,-20.761598 L 250.12056,-20.761598 C 249.27915,-20.761598 248.60178,-21.421215 248.60178,-22.24056 L 248.60178,-37.166047 C 248.60178,-37.985392 249.27915,-38.645009 250.12056,-38.645009 z " + style="fill:url(#linearGradient14221);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient14223);stroke-width:0.926992;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Toolbar-and-menu-icons/amarok_playlist_clear.png" + style="opacity:0.99578091;fill:url(#radialGradient14225);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.57956129;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 250.08215,-38.266202 L 276.71513,-38.266202 C 277.55825,-38.266202 278.23703,-37.624086 278.23703,-36.826475 L 278.23703,-22.296956 C 278.23703,-21.499349 277.55825,-20.857229 276.71513,-20.857229 L 250.08215,-20.857229 C 249.23911,-20.857229 248.56031,-21.499349 248.56031,-22.296956 L 248.56031,-36.826475 C 248.56031,-37.624086 249.23911,-38.266202 250.08215,-38.266202 z " + id="path13541" /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Toolbar-and-menu-icons/amarok_playlist_clear.png" + id="path13543" + d="M 252.16347,-44.213855 C 249.93199,-44.213855 248.13869,-43.379701 248.13869,-40.944894 L 248.13869,-40.580717 C 248.26605,-39.702708 249.19975,-38.839348 249.76009,-38.674109 L 276.84538,-38.674109 C 277.41011,-38.840648 278.55724,-39.692772 278.68061,-40.580717 L 278.68061,-40.944894 C 278.68061,-43.379701 276.88731,-44.213855 274.65582,-44.213855 L 252.16347,-44.213855 z " + style="opacity:0.15189873;fill:url(#linearGradient14227);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.71897209;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:nodetypes="ccccccccc" /> + </g> + <g + transform="matrix(1.584275,0,0,1.524619,-14.23856,433.4818)" + id="g13545"> + <g + id="g13547"> + <path + id="path13549" + d="M 216.59388,201.58154 L 216.59388,213.16264" + style="opacity:0.39183673;fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#264a9e;stroke-width:0.28967622;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:nodetypes="cc" /> + <path + style="opacity:0.39183673;fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#264a9e;stroke-width:0.28967622;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 223.52993,201.58154 L 223.52993,213.17716" + id="path13551" + sodipodi:nodetypes="cc" /> + </g> + <rect + style="opacity:0.56734691;fill:#6785ed;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.28953668;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect13553" + width="18.826923" + height="3.4617176" + x="211.13826" + y="205.69659" /> + </g> + </g> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/32x32/actions/amarok_playlist_clear.png" + id="g14091" + transform="matrix(0.999518,0,0,0.999518,-155.43888,-678.1071)"> + <g + transform="translate(71.74824,778.9794)" + id="g14093"> + <path + id="path14095" + d="M 251.89706,-44.262813 C 249.61272,-44.262813 247.77694,-43.450681 247.77694,-41.080157 L 247.77694,-21.909202 C 247.90734,-21.054374 248.86314,-20.21381 249.43677,-20.052932 L 277.16366,-20.052932 C 277.74178,-20.215073 278.91607,-21.0447 279.04236,-21.909202 L 279.04236,-41.080157 C 279.04236,-43.450681 277.20658,-44.262813 274.92225,-44.262813 L 251.89706,-44.262813 z " + style="fill:url(#linearGradient14205);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient14207);stroke-width:0.75036138;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:nodetypes="ccccccccc" + inkscape:export-filename="/home/lando/Projects/Amarok/Toolbar-and-menu-icons/amarok_playlist_clear.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + style="fill:url(#linearGradient14209);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient14211);stroke-width:0.926992;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 250.12056,-38.645009 L 276.69881,-38.645009 C 277.54022,-38.645009 278.21759,-37.985392 278.21759,-37.166047 L 278.21759,-22.24056 C 278.21759,-21.421215 277.54022,-20.761598 276.69881,-20.761598 L 250.12056,-20.761598 C 249.27915,-20.761598 248.60178,-21.421215 248.60178,-22.24056 L 248.60178,-37.166047 C 248.60178,-37.985392 249.27915,-38.645009 250.12056,-38.645009 z " + id="path14097" + inkscape:export-filename="/home/lando/Projects/Amarok/Toolbar-and-menu-icons/amarok_playlist_clear.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + id="path14099" + d="M 250.08215,-38.266202 L 276.71513,-38.266202 C 277.55825,-38.266202 278.23703,-37.624086 278.23703,-36.826475 L 278.23703,-22.296956 C 278.23703,-21.499349 277.55825,-20.857229 276.71513,-20.857229 L 250.08215,-20.857229 C 249.23911,-20.857229 248.56031,-21.499349 248.56031,-22.296956 L 248.56031,-36.826475 C 248.56031,-37.624086 249.23911,-38.266202 250.08215,-38.266202 z " + style="opacity:0.99578091;fill:url(#radialGradient14213);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.57956129;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + inkscape:export-filename="/home/lando/Projects/Amarok/Toolbar-and-menu-icons/amarok_playlist_clear.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:nodetypes="ccccccccc" + style="opacity:0.15189873;fill:url(#linearGradient14215);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.71897209;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 252.16347,-44.213855 C 249.93199,-44.213855 248.13869,-43.379701 248.13869,-40.944894 L 248.13869,-40.580717 C 248.26605,-39.702708 249.19975,-38.839348 249.76009,-38.674109 L 276.84538,-38.674109 C 277.41011,-38.840648 278.55724,-39.692772 278.68061,-40.580717 L 278.68061,-40.944894 C 278.68061,-43.379701 276.88731,-44.213855 274.65582,-44.213855 L 252.16347,-44.213855 z " + id="path14101" + inkscape:export-filename="/home/lando/Projects/Amarok/Toolbar-and-menu-icons/amarok_playlist_clear.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + </g> + <g + id="g14103" + transform="matrix(1.584275,0,0,1.524619,-14.23856,433.4818)"> + <g + id="g14105"> + <path + sodipodi:nodetypes="cc" + style="opacity:0.39183673;fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#264a9e;stroke-width:0.28967622;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 216.59388,201.58154 L 216.59388,213.16264" + id="path14107" /> + <path + sodipodi:nodetypes="cc" + id="path14109" + d="M 223.52993,201.58154 L 223.52993,213.17716" + style="opacity:0.39183673;fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#264a9e;stroke-width:0.28967622;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + <rect + y="205.69659" + x="211.13826" + height="3.4617176" + width="18.826923" + id="rect14111" + style="opacity:0.56734691;fill:#6785ed;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.28953668;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + </g> + <rect + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/32x32/actions/amarok_playlist_clear.png" + style="fill:#b9cff9;fill-opacity:0;stroke:none;stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + id="rect11517" + width="32" + height="32" + x="163.55746" + y="52.354458" /> + </g> + <g + id="g11980" + transform="translate(-5,-10.027002)"> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/64x64/actions/amarok_playlist_clear.png" + transform="matrix(2.022997,0,0,2.022997,-361.46571,-1456.4286)" + id="g14297"> + <g + id="g14299" + transform="translate(71.74824,778.9794)"> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Toolbar-and-menu-icons/amarok_playlist_clear.png" + sodipodi:nodetypes="ccccccccc" + style="fill:url(#linearGradient14341);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient14343);stroke-width:0.37073708;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 251.89706,-44.262813 C 249.61272,-44.262813 247.77694,-43.450681 247.77694,-41.080157 L 247.77694,-21.909202 C 247.90734,-21.054374 248.86314,-20.21381 249.43677,-20.052932 L 277.16366,-20.052932 C 277.74178,-20.215073 278.91607,-21.0447 279.04236,-21.909202 L 279.04236,-41.080157 C 279.04236,-43.450681 277.20658,-44.262813 274.92225,-44.262813 L 251.89706,-44.262813 z " + id="path14301" /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Toolbar-and-menu-icons/amarok_playlist_clear.png" + id="path14303" + d="M 250.12056,-38.645009 L 276.69881,-38.645009 C 277.54022,-38.645009 278.21759,-37.985392 278.21759,-37.166047 L 278.21759,-22.24056 C 278.21759,-21.421215 277.54022,-20.761598 276.69881,-20.761598 L 250.12056,-20.761598 C 249.27915,-20.761598 248.60178,-21.421215 248.60178,-22.24056 L 248.60178,-37.166047 C 248.60178,-37.985392 249.27915,-38.645009 250.12056,-38.645009 z " + style="fill:url(#linearGradient14345);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient14347);stroke-width:0.45800635;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Toolbar-and-menu-icons/amarok_playlist_clear.png" + style="opacity:0.99578091;fill:url(#radialGradient14349);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.57956129;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 250.08215,-38.266202 L 276.71513,-38.266202 C 277.55825,-38.266202 278.23703,-37.624086 278.23703,-36.826475 L 278.23703,-22.296956 C 278.23703,-21.499349 277.55825,-20.857229 276.71513,-20.857229 L 250.08215,-20.857229 C 249.23911,-20.857229 248.56031,-21.499349 248.56031,-22.296956 L 248.56031,-36.826475 C 248.56031,-37.624086 249.23911,-38.266202 250.08215,-38.266202 z " + id="path14305" /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Toolbar-and-menu-icons/amarok_playlist_clear.png" + id="path14307" + d="M 252.16347,-44.213855 C 249.93199,-44.213855 248.13869,-43.379701 248.13869,-40.944894 L 248.13869,-40.580717 C 248.26605,-39.702708 249.19975,-38.839348 249.76009,-38.674109 L 276.84538,-38.674109 C 277.41011,-38.840648 278.55724,-39.692772 278.68061,-40.580717 L 278.68061,-40.944894 C 278.68061,-43.379701 276.88731,-44.213855 274.65582,-44.213855 L 252.16347,-44.213855 z " + style="opacity:0.15189873;fill:url(#linearGradient14351);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.71897209;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:nodetypes="ccccccccc" /> + </g> + <g + transform="matrix(1.584275,0,0,1.524619,-14.23856,433.4818)" + id="g14309"> + <g + id="g14311"> + <path + id="path14313" + d="M 216.59388,201.58154 L 216.59388,213.16264" + style="opacity:0.39183673;fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#264a9e;stroke-width:0.14312263;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:nodetypes="cc" /> + <path + style="opacity:0.39183673;fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#264a9e;stroke-width:0.14312263;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 223.52993,201.58154 L 223.52993,213.17716" + id="path14315" + sodipodi:nodetypes="cc" /> + </g> + <rect + style="opacity:0.56734691;fill:#6785ed;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.28953668;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect14317" + width="18.826923" + height="3.4617176" + x="211.13826" + y="205.69659" /> + </g> + </g> + <rect + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/64x64/actions/amarok_playlist_clear.png" + style="fill:#b9cff9;fill-opacity:0;stroke:none;stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + id="rect11521" + width="64" + height="64" + x="284.55771" + y="22.389185" /> + </g> + <g + id="g11994" + transform="translate(-5,-0.9922809)"> + <g + inkscape:export-ydpi="90.000092" + inkscape:export-xdpi="90.000092" + inkscape:export-filename="/home/lando/Projects/Amarok/icons/16/amarok_playlist_refresh.png" + id="g14441" + transform="matrix(0.716482,0,0,0.716482,-121.92442,81.846129)"> + <path + id="path14443" + d="M 303.59589,76.099303 C 302.04074,76.099303 300.79097,76.659692 300.79097,78.295407 L 300.79097,91.523792 C 300.87974,92.113642 301.53044,92.69365 301.92095,92.804659 L 320.79715,92.804659 C 321.19073,92.692778 321.99016,92.120318 322.07615,91.523792 L 322.07615,78.295407 C 322.07615,76.659692 320.82636,76.099303 319.27121,76.099303 L 303.59589,76.099303 z " + style="fill:url(#linearGradient14465);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient14467);stroke-width:1.04678273;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:nodetypes="ccccccccc" /> + <path + style="fill:url(#linearGradient14469);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient14471);stroke-width:1.373173;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 302.93034,80.422297 L 319.96399,80.422297 C 320.50323,80.422297 320.93735,80.847754 320.93735,81.376237 L 320.93735,91.00327 C 320.93735,91.531753 320.50323,91.95721 319.96399,91.95721 L 302.93034,91.95721 C 302.39109,91.95721 301.95698,91.531753 301.95698,91.00327 L 301.95698,81.376237 C 301.95698,80.847754 302.39109,80.422297 302.93034,80.422297 z " + id="path14445" /> + <path + id="path14447" + d="M 302.99513,80.406597 L 319.88473,80.406597 C 320.41939,80.406597 320.84986,80.833434 320.84986,81.363632 L 320.84986,91.02189 C 320.84986,91.552086 320.41939,91.978925 319.88473,91.978925 L 302.99513,91.978925 C 302.46052,91.978925 302.03004,91.552086 302.03004,91.02189 L 302.03004,81.363632 C 302.03004,80.833434 302.46052,80.406597 302.99513,80.406597 z " + style="opacity:0.99578091;fill:url(#radialGradient14473);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.57956129;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" /> + <path + sodipodi:nodetypes="ccccccccc" + style="opacity:0.15189873;fill:url(#linearGradient14475);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.71897209;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 303.79434,76.354311 C 302.26698,76.354311 301.03954,76.887526 301.03954,78.443925 L 301.03954,78.676718 C 301.1267,79.237966 301.76579,79.789851 302.14932,79.895477 L 320.68819,79.895477 C 321.07472,79.78902 321.85989,79.244318 321.94433,78.676718 L 321.94433,78.443925 C 321.94433,76.887526 320.71689,76.354311 319.18952,76.354311 L 303.79434,76.354311 z " + id="path14449" /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/vadim/collection.png" + id="path14451" + d="M 313.25622,76.492555 L 312.92473,77.932619 C 310.42893,77.764069 307.97622,79.012661 306.45093,81.352583 C 305.55949,82.720117 305.20466,84.12394 305.24095,85.634561 C 305.39049,85.029977 305.49569,84.543824 305.84671,84.00533 C 307.175,81.967622 309.77542,81.201527 312.23747,81.642469 L 311.77472,83.619334 L 317.3862,81.1089 L 313.25622,76.492555 z M 317.62275,82.908635 C 317.47323,83.51322 317.08241,83.999372 316.73139,84.537876 C 315.40309,86.575575 312.94684,87.437781 310.48479,86.99684 L 310.99561,84.779696 L 305.43219,87.388953 L 309.46332,92.050631 L 309.79481,90.610568 C 312.29063,90.779128 314.83943,89.722757 316.36473,87.382826 C 317.25616,86.015291 317.65907,84.419255 317.62275,82.908635 z " + style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:1.04678273;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:nodetypes="ccscscccccscccccsc" /> + <g + transform="translate(90.91207,-121.1119)" + id="g14453"> + <g + id="g14455"> + <path + sodipodi:nodetypes="cc" + style="opacity:0.39183673;fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#264a9e;stroke-width:0.40410933;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 216.59388,201.58154 L 216.59388,213.16264" + id="path14457" /> + <path + sodipodi:nodetypes="cc" + id="path14459" + d="M 223.52993,201.58154 L 223.52993,213.17716" + style="opacity:0.39183673;fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#264a9e;stroke-width:0.40410933;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + <rect + y="205.69659" + x="211.13826" + height="3.4617176" + width="18.826923" + id="rect14461" + style="opacity:0.56734691;fill:#6785ed;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.28953668;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + <path + style="fill:url(#linearGradient14477);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient14479);stroke-width:1.04678273;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 313.2842,77.003938 L 312.97552,78.344886 C 310.65149,78.187937 308.27811,79.305844 306.85779,81.484714 C 306.02772,82.758125 305.65255,84.199572 305.68635,85.60622 C 305.82559,85.043248 306.0578,84.501059 306.38466,83.999628 C 307.62153,82.10217 309.90872,81.299307 312.20132,81.7099 L 311.72567,83.774443 L 317.12992,81.302552 L 313.2842,77.003938 z M 317.21594,82.888919 C 317.07671,83.451892 316.84704,83.99408 316.52017,84.495521 C 315.2833,86.39297 312.99611,87.195832 310.70352,86.78524 L 311.17917,84.720697 L 305.77492,87.195114 L 309.61811,91.491192 L 309.92677,90.150244 C 312.25082,90.307202 314.62419,89.189295 316.04451,87.010416 C 316.87458,85.737005 317.24976,84.295567 317.21594,82.888919 z " + id="path14463" + inkscape:export-filename="/home/vadim/collection.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + </g> + <rect + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/16x16/actions/amarok_playlist_refresh.png" + y="134.35446" + x="93.21212" + height="16" + width="16" + id="rect11527" + style="fill:#b9cff9;fill-opacity:0;stroke:none;stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" /> + </g> + <g + id="g12009" + transform="translate(-5,-1.9922809)"> + <g + inkscape:export-ydpi="90.000092" + inkscape:export-xdpi="90.000092" + inkscape:export-filename="/home/lando/Projects/Amarok/icons/22/amarok_playlist_refresh.png" + transform="translate(-178.06834,55.902469)" + id="g13333"> + <path + sodipodi:nodetypes="ccccccccc" + style="fill:url(#linearGradient13740);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient13742);stroke-width:0.75;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 303.59589,76.099303 C 302.04074,76.099303 300.79097,76.659692 300.79097,78.295407 L 300.79097,91.523792 C 300.87974,92.113642 301.53044,92.69365 301.92095,92.804659 L 320.79715,92.804659 C 321.19073,92.692778 321.99016,92.120318 322.07615,91.523792 L 322.07615,78.295407 C 322.07615,76.659692 320.82636,76.099303 319.27121,76.099303 L 303.59589,76.099303 z " + id="path13335" /> + <path + id="path13337" + d="M 302.93034,80.422297 L 319.96399,80.422297 C 320.50323,80.422297 320.93735,80.847754 320.93735,81.376237 L 320.93735,91.00327 C 320.93735,91.531753 320.50323,91.95721 319.96399,91.95721 L 302.93034,91.95721 C 302.39109,91.95721 301.95698,91.531753 301.95698,91.00327 L 301.95698,81.376237 C 301.95698,80.847754 302.39109,80.422297 302.93034,80.422297 z " + style="fill:url(#linearGradient13744);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient13746);stroke-width:0.98385239;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + style="opacity:0.99578091;fill:url(#radialGradient13748);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.57956129;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 302.99513,80.406597 L 319.88473,80.406597 C 320.41939,80.406597 320.84986,80.833434 320.84986,81.363632 L 320.84986,91.02189 C 320.84986,91.552086 320.41939,91.978925 319.88473,91.978925 L 302.99513,91.978925 C 302.46052,91.978925 302.03004,91.552086 302.03004,91.02189 L 302.03004,81.363632 C 302.03004,80.833434 302.46052,80.406597 302.99513,80.406597 z " + id="path13339" /> + <path + id="path13341" + d="M 303.79434,76.354311 C 302.26698,76.354311 301.03954,76.887526 301.03954,78.443925 L 301.03954,78.676718 C 301.1267,79.237966 301.76579,79.789851 302.14932,79.895477 L 320.68819,79.895477 C 321.07472,79.78902 321.85989,79.244318 321.94433,78.676718 L 321.94433,78.443925 C 321.94433,76.887526 320.71689,76.354311 319.18952,76.354311 L 303.79434,76.354311 z " + style="opacity:0.15189873;fill:url(#linearGradient13750);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.71897209;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:nodetypes="ccccccccc" /> + <path + sodipodi:nodetypes="ccscscccccscccccsc" + style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:0.75;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 313.25622,76.492555 L 312.92473,77.932619 C 310.42893,77.764069 307.97622,79.012661 306.45093,81.352583 C 305.55949,82.720117 305.20466,84.12394 305.24095,85.634561 C 305.39049,85.029977 305.49569,84.543824 305.84671,84.00533 C 307.175,81.967622 309.77542,81.201527 312.23747,81.642469 L 311.77472,83.619334 L 317.3862,81.1089 L 313.25622,76.492555 z M 317.62275,82.908635 C 317.47323,83.51322 317.08241,83.999372 316.73139,84.537876 C 315.40309,86.575575 312.94684,87.437781 310.48479,86.99684 L 310.99561,84.779696 L 305.43219,87.388953 L 309.46332,92.050631 L 309.79481,90.610568 C 312.29063,90.779128 314.83943,89.722757 316.36473,87.382826 C 317.25616,86.015291 317.65907,84.419255 317.62275,82.908635 z " + id="path13343" + inkscape:export-filename="/home/vadim/collection.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <g + id="g13345" + transform="translate(90.91207,-121.1119)"> + <g + id="g13347"> + <path + id="path13349" + d="M 216.59388,201.58154 L 216.59388,213.16264" + style="opacity:0.39183673;fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#264a9e;stroke-width:0.28953668;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:nodetypes="cc" /> + <path + style="opacity:0.39183673;fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#264a9e;stroke-width:0.28953668;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 223.52993,201.58154 L 223.52993,213.17716" + id="path13351" + sodipodi:nodetypes="cc" /> + </g> + <rect + style="opacity:0.56734691;fill:#6785ed;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.28953668;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect13353" + width="18.826923" + height="3.4617176" + x="211.13826" + y="205.69659" /> + </g> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/vadim/collection.png" + id="path13355" + d="M 313.2842,77.003938 L 312.97552,78.344886 C 310.65149,78.187937 308.27811,79.305844 306.85779,81.484714 C 306.02772,82.758125 305.65255,84.199572 305.68635,85.60622 C 305.82559,85.043248 306.0578,84.501059 306.38466,83.999628 C 307.62153,82.10217 309.90872,81.299307 312.20132,81.7099 L 311.72567,83.774443 L 317.12992,81.302552 L 313.2842,77.003938 z M 317.21594,82.888919 C 317.07671,83.451892 316.84704,83.99408 316.52017,84.495521 C 315.2833,86.39297 312.99611,87.195832 310.70352,86.78524 L 311.17917,84.720697 L 305.77492,87.195114 L 309.61811,91.491192 L 309.92677,90.150244 C 312.25082,90.307202 314.62419,89.189295 316.04451,87.010416 C 316.87458,85.737005 317.24976,84.295567 317.21594,82.888919 z " + style="fill:url(#linearGradient13752);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient13754);stroke-width:0.75;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + <rect + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/22x22/actions/amarok_playlist_refresh.png" + style="fill:#b9cff9;fill-opacity:0;stroke:none;stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + id="rect11529" + width="22" + height="22" + x="122.36523" + y="129.35446" /> + </g> + <g + id="g12024" + transform="translate(-5,-5.9922657)"> + <g + inkscape:export-ydpi="90.000092" + inkscape:export-xdpi="90.000092" + inkscape:export-filename="/home/lando/Projects/Amarok/icons/32/amarok_playlist_refresh.png" + transform="matrix(0.999518,0,0,0.999518,-167.23721,-681.95114)" + id="g13936"> + <path + id="path13357" + d="M 328.89706,809.59671 C 326.61272,809.59671 324.77694,810.40884 324.77694,812.77936 L 324.77694,831.95032 C 324.90734,832.80515 325.86314,833.64571 326.43677,833.80659 L 354.16366,833.80659 C 354.74178,833.64445 355.91607,832.81482 356.04236,831.95032 L 356.04236,812.77936 C 356.04236,810.40884 354.20658,809.59671 351.92225,809.59671 L 328.89706,809.59671 z " + style="fill:url(#linearGradient13756);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient13758);stroke-width:0.75036138;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:nodetypes="ccccccccc" /> + <path + style="fill:url(#linearGradient13760);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient13762);stroke-width:0.926992;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 327.12056,815.21451 L 353.69881,815.21451 C 354.54022,815.21451 355.21759,815.87413 355.21759,816.69347 L 355.21759,831.61896 C 355.21759,832.4383 354.54022,833.09792 353.69881,833.09792 L 327.12056,833.09792 C 326.27915,833.09792 325.60178,832.4383 325.60178,831.61896 L 325.60178,816.69347 C 325.60178,815.87413 326.27915,815.21451 327.12056,815.21451 z " + id="path13359" /> + <path + id="path13361" + d="M 327.08215,815.59332 L 353.71513,815.59332 C 354.55825,815.59332 355.23703,816.23543 355.23703,817.03304 L 355.23703,831.56256 C 355.23703,832.36017 354.55825,833.00229 353.71513,833.00229 L 327.08215,833.00229 C 326.23911,833.00229 325.56031,832.36017 325.56031,831.56256 L 325.56031,817.03304 C 325.56031,816.23543 326.23911,815.59332 327.08215,815.59332 z " + style="opacity:0.99578091;fill:url(#radialGradient13764);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.57956129;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" /> + <path + sodipodi:nodetypes="ccccccccc" + style="opacity:0.15189873;fill:url(#linearGradient13766);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.71897209;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 329.16347,809.64566 C 326.93199,809.64566 325.13869,810.47982 325.13869,812.91463 L 325.13869,813.2788 C 325.26605,814.15681 326.19975,815.02017 326.76009,815.18541 L 353.84538,815.18541 C 354.41011,815.01887 355.55724,814.16675 355.68061,813.2788 L 355.68061,812.91463 C 355.68061,810.47982 353.88731,809.64566 351.65582,809.64566 L 329.16347,809.64566 z " + id="path13363" /> + <g + id="g13467" + transform="matrix(1.584275,0,0,1.524619,-8.965103,508.1232)"> + <g + id="g13469"> + <path + sodipodi:nodetypes="cc" + style="opacity:0.39183673;fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#264a9e;stroke-width:0.28967622;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 216.59388,201.58154 L 216.59388,213.16264" + id="path13471" /> + <path + sodipodi:nodetypes="cc" + id="path13473" + d="M 223.52993,201.58154 L 223.52993,213.17716" + style="opacity:0.39183673;fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#264a9e;stroke-width:0.28967622;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + <rect + y="205.69659" + x="211.13826" + height="3.4617176" + width="18.826923" + id="rect13475" + style="opacity:0.56734691;fill:#6785ed;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.28953668;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + <path + style="fill:url(#linearGradient13836);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient13838);stroke-width:0.69967055;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 342.74958,811.46751 L 342.28657,813.47893 C 338.80051,813.2435 335.24045,814.92036 333.10996,818.18867 C 331.86486,820.09879 331.30209,822.26095 331.35281,824.37094 C 331.56166,823.52646 331.90998,822.7132 332.40027,821.96104 C 334.25556,819.11486 337.68636,817.91056 341.12526,818.52645 L 340.41178,821.62327 L 348.51817,817.91542 L 342.74958,811.46751 z M 348.6472,820.29497 C 348.43835,821.13944 348.09383,821.95272 347.60354,822.70489 C 345.74824,825.55106 342.31744,826.75534 338.87856,826.13946 L 339.59204,823.04264 L 331.48565,826.75428 L 337.25043,833.1984 L 337.71344,831.18698 C 341.19952,831.4224 344.75957,829.74555 346.89006,826.47723 C 348.13515,824.56712 348.69792,822.40495 348.6472,820.29497 z " + id="path13477" + inkscape:export-filename="/home/vadim/collection.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + </g> + <rect + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/32x32/actions/amarok_playlist_refresh.png" + y="123.35445" + x="157.00836" + height="32" + width="32" + id="rect11531" + style="fill:#b9cff9;fill-opacity:0;stroke:none;stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" /> + </g> + <g + id="g12039" + transform="translate(-5,-10.027002)"> + <g + inkscape:export-ydpi="90.000092" + inkscape:export-xdpi="90.000092" + inkscape:export-filename="/home/lando/Projects/Amarok/icons/48/amarok_playlist_refresh.png" + id="g14365" + transform="matrix(1.511251,0,0,1.511251,-275.82301,-1106.4083)"> + <path + sodipodi:nodetypes="ccccccccc" + style="fill:url(#linearGradient14387);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient14389);stroke-width:0.4962773;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 328.89706,809.59671 C 326.61272,809.59671 324.77694,810.40884 324.77694,812.77936 L 324.77694,831.95032 C 324.90734,832.80515 325.86314,833.64571 326.43677,833.80659 L 354.16366,833.80659 C 354.74178,833.64445 355.91607,832.81482 356.04236,831.95032 L 356.04236,812.77936 C 356.04236,810.40884 354.20658,809.59671 351.92225,809.59671 L 328.89706,809.59671 z " + id="path14367" /> + <path + id="path14369" + d="M 327.12056,815.21451 L 353.69881,815.21451 C 354.54022,815.21451 355.21759,815.87413 355.21759,816.69347 L 355.21759,831.61896 C 355.21759,832.4383 354.54022,833.09792 353.69881,833.09792 L 327.12056,833.09792 C 326.27915,833.09792 325.60178,832.4383 325.60178,831.61896 L 325.60178,816.69347 C 325.60178,815.87413 326.27915,815.21451 327.12056,815.21451 z " + style="fill:url(#linearGradient14391);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient14393);stroke-width:0.61309803;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" /> + <path + style="opacity:0.99578091;fill:url(#radialGradient14395);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.57956129;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 327.08215,815.59332 L 353.71513,815.59332 C 354.55825,815.59332 355.23703,816.23543 355.23703,817.03304 L 355.23703,831.56256 C 355.23703,832.36017 354.55825,833.00229 353.71513,833.00229 L 327.08215,833.00229 C 326.23911,833.00229 325.56031,832.36017 325.56031,831.56256 L 325.56031,817.03304 C 325.56031,816.23543 326.23911,815.59332 327.08215,815.59332 z " + id="path14371" /> + <path + id="path14373" + d="M 329.16347,809.64566 C 326.93199,809.64566 325.13869,810.47982 325.13869,812.91463 L 325.13869,813.2788 C 325.26605,814.15681 326.19975,815.02017 326.76009,815.18541 L 353.84538,815.18541 C 354.41011,815.01887 355.55724,814.16675 355.68061,813.2788 L 355.68061,812.91463 C 355.68061,810.47982 353.88731,809.64566 351.65582,809.64566 L 329.16347,809.64566 z " + style="opacity:0.15189873;fill:url(#linearGradient14397);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.71897209;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:nodetypes="ccccccccc" /> + <g + transform="matrix(1.584275,0,0,1.524619,-8.965103,508.1232)" + id="g14375"> + <g + id="g14377"> + <path + id="path14379" + d="M 216.59388,201.58154 L 216.59388,213.16264" + style="opacity:0.39183673;fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#264a9e;stroke-width:0.19158731;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:nodetypes="cc" /> + <path + style="opacity:0.39183673;fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#264a9e;stroke-width:0.19158731;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 223.52993,201.58154 L 223.52993,213.17716" + id="path14381" + sodipodi:nodetypes="cc" /> + </g> + <rect + style="opacity:0.56734691;fill:#6785ed;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.28953668;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect14383" + width="18.826923" + height="3.4617176" + x="211.13826" + y="205.69659" /> + </g> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/vadim/collection.png" + id="path14385" + d="M 342.74958,811.46751 L 342.28657,813.47893 C 338.80051,813.2435 335.24045,814.92036 333.10996,818.18867 C 331.86486,820.09879 331.30209,822.26095 331.35281,824.37094 C 331.56166,823.52646 331.90998,822.7132 332.40027,821.96104 C 334.25556,819.11486 337.68636,817.91056 341.12526,818.52645 L 340.41178,821.62327 L 348.51817,817.91542 L 342.74958,811.46751 z M 348.6472,820.29497 C 348.43835,821.13944 348.09383,821.95272 347.60354,822.70489 C 345.74824,825.55106 342.31744,826.75534 338.87856,826.13946 L 339.59204,823.04264 L 331.48565,826.75428 L 337.25043,833.1984 L 337.71344,831.18698 C 341.19952,831.4224 344.75957,829.74555 346.89006,826.47723 C 348.13515,824.56712 348.69792,822.40495 348.6472,820.29497 z " + style="fill:url(#linearGradient14399);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient14401);stroke-width:0.46275112;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + <rect + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/48x48/actions/amarok_playlist_refresh.png" + style="fill:#b9cff9;fill-opacity:0;stroke:none;stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + id="rect11533" + width="48" + height="48" + x="214.62143" + y="111.38918" /> + </g> + <g + id="g12124" + transform="translate(-5,-10.027002)"> + <g + inkscape:export-ydpi="90.000092" + inkscape:export-xdpi="90.000092" + inkscape:export-filename="/home/lando/Projects/Amarok/icons/64/amarok_repeat_playlist.png" + id="g14519" + transform="matrix(2.022998,0,0,2.022998,-384.65494,-1580.9276)"> + <path + id="path14521" + d="M 334.33675,865.95659 C 332.05241,865.95659 330.21663,866.76872 330.21663,869.13925 L 330.21663,888.3102 C 330.34703,889.16503 331.30283,890.0056 331.87646,890.16647 L 359.60335,890.16647 C 360.18147,890.00433 361.35576,889.1747 361.48205,888.3102 L 361.48205,869.13925 C 361.48205,866.76872 359.64627,865.95659 357.36194,865.95659 L 334.33675,865.95659 z " + style="fill:url(#linearGradient14541);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient14543);stroke-width:0.3707366;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:nodetypes="ccccccccc" /> + <path + style="fill:url(#linearGradient14545);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient14547);stroke-width:0.45800579;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 332.56025,871.5744 L 359.1385,871.5744 C 359.97991,871.5744 360.65728,872.23401 360.65728,873.05336 L 360.65728,887.97885 C 360.65728,888.79819 359.97991,889.45781 359.1385,889.45781 L 332.56025,889.45781 C 331.71884,889.45781 331.04147,888.79819 331.04147,887.97885 L 331.04147,873.05336 C 331.04147,872.23401 331.71884,871.5744 332.56025,871.5744 z " + id="path14523" /> + <path + id="path14525" + d="M 332.52184,871.9532 L 359.15482,871.9532 C 359.99794,871.9532 360.67672,872.59532 360.67672,873.39293 L 360.67672,887.92245 C 360.67672,888.72006 359.99794,889.36218 359.15482,889.36218 L 332.52184,889.36218 C 331.6788,889.36218 331,888.72006 331,887.92245 L 331,873.39293 C 331,872.59532 331.6788,871.9532 332.52184,871.9532 z " + style="opacity:0.99578091;fill:url(#radialGradient14549);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.57956129;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" /> + <path + sodipodi:nodetypes="ccccccccc" + style="opacity:0.15189873;fill:url(#linearGradient14551);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.71897209;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 334.60316,866.00555 C 332.37168,866.00555 330.57838,866.8397 330.57838,869.27451 L 330.57838,869.63869 C 330.70574,870.5167 331.63944,871.38006 332.19978,871.5453 L 359.28507,871.5453 C 359.8498,871.37876 360.99693,870.52663 361.1203,869.63869 L 361.1203,869.27451 C 361.1203,866.8397 359.327,866.00555 357.09551,866.00555 L 334.60316,866.00555 z " + id="path14527" /> + <g + id="g14529" + transform="matrix(1.584275,0,0,1.524619,-3.54568,564.4865)"> + <g + id="g14531"> + <path + sodipodi:nodetypes="cc" + style="opacity:0.39183673;fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#264a9e;stroke-width:0.14312246;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 216.59388,201.58154 L 216.59388,213.16264" + id="path14533" /> + <path + sodipodi:nodetypes="cc" + id="path14535" + d="M 223.52993,201.58154 L 223.52993,213.17716" + style="opacity:0.39183673;fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#264a9e;stroke-width:0.14312246;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + <rect + y="205.69659" + x="211.13826" + height="3.4617176" + width="18.826923" + id="rect14537" + style="opacity:0.56734691;fill:#6785ed;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.28953668;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + <path + sodipodi:nodetypes="ccccscccscccscccccsccc" + style="fill:url(#linearGradient14553);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient14555);stroke-width:0.21838744;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 348.41747,872.70033 L 344.32198,875.20198 L 347.62851,878.51995 L 347.81466,877.15941 C 349.25494,877.89517 350.22026,879.34 350.25245,881.03915 C 350.28567,882.72754 349.61664,884.73976 347.18723,885.27752 L 348.75397,886.05119 L 347.30895,887.42238 C 350.50873,887.29672 352.64592,884.74146 352.64592,880.93383 C 352.64592,877.963 350.77967,875.46607 348.17813,874.45588 L 348.41747,872.70033 z M 343.44469,874.44212 C 341.5557,875.30465 338.71064,877.47166 338.71064,880.93383 C 338.71064,883.67754 340.30281,886.02267 342.59337,887.16599 L 342.31856,888.76355 L 346.48496,886.58667 L 343.32914,882.97903 L 343.05433,884.55025 C 341.94815,883.74533 341.21048,882.48909 341.21048,881.03915 C 341.21048,879.87772 341.55166,878.42581 343.61973,877.04093 L 342.42947,876.76138 L 343.44469,874.44212 z " + id="path14539" /> + </g> + <rect + inkscape:export-ydpi="90.000092" + inkscape:export-xdpi="90.000092" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/crystalsvg/64x64/actions/repeat_playlist.png" + style="fill:#b9cff9;fill-opacity:0;stroke:none;stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + id="rect11537" + width="64" + height="64" + x="282.99759" + y="163.38919" /> + </g> + <g + id="g12110" + transform="translate(-5,-10.027002)"> + <g + inkscape:export-ydpi="90.000092" + inkscape:export-xdpi="90.000092" + inkscape:export-filename="/home/lando/Projects/Amarok/icons/48/amarok_repeat_playlist.png" + transform="matrix(1.511256,0,0,1.511256,-290.87492,-1121.5866)" + id="g14557"> + <path + sodipodi:nodetypes="ccccccccc" + style="fill:url(#linearGradient14579);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient14581);stroke-width:0.49627563;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 334.33675,865.95659 C 332.05241,865.95659 330.21663,866.76872 330.21663,869.13925 L 330.21663,888.3102 C 330.34703,889.16503 331.30283,890.0056 331.87646,890.16647 L 359.60335,890.16647 C 360.18147,890.00433 361.35576,889.1747 361.48205,888.3102 L 361.48205,869.13925 C 361.48205,866.76872 359.64627,865.95659 357.36194,865.95659 L 334.33675,865.95659 z " + id="path14559" /> + <path + id="path14561" + d="M 332.56025,871.5744 L 359.1385,871.5744 C 359.97991,871.5744 360.65728,872.23401 360.65728,873.05336 L 360.65728,887.97885 C 360.65728,888.79819 359.97991,889.45781 359.1385,889.45781 L 332.56025,889.45781 C 331.71884,889.45781 331.04147,888.79819 331.04147,887.97885 L 331.04147,873.05336 C 331.04147,872.23401 331.71884,871.5744 332.56025,871.5744 z " + style="fill:url(#linearGradient14583);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient14585);stroke-width:0.613096;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" /> + <path + style="opacity:0.99578091;fill:url(#radialGradient14587);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.57956129;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 332.52184,871.9532 L 359.15482,871.9532 C 359.99794,871.9532 360.67672,872.59532 360.67672,873.39293 L 360.67672,887.92245 C 360.67672,888.72006 359.99794,889.36218 359.15482,889.36218 L 332.52184,889.36218 C 331.6788,889.36218 331,888.72006 331,887.92245 L 331,873.39293 C 331,872.59532 331.6788,871.9532 332.52184,871.9532 z " + id="path14563" /> + <path + id="path14565" + d="M 334.60316,866.00555 C 332.37168,866.00555 330.57838,866.8397 330.57838,869.27451 L 330.57838,869.63869 C 330.70574,870.5167 331.63944,871.38006 332.19978,871.5453 L 359.28507,871.5453 C 359.8498,871.37876 360.99693,870.52663 361.1203,869.63869 L 361.1203,869.27451 C 361.1203,866.8397 359.327,866.00555 357.09551,866.00555 L 334.60316,866.00555 z " + style="opacity:0.15189873;fill:url(#linearGradient14589);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.71897209;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:nodetypes="ccccccccc" /> + <g + transform="matrix(1.584275,0,0,1.524619,-3.54568,564.4865)" + id="g14567"> + <g + id="g14569"> + <path + id="path14571" + d="M 216.59388,201.58154 L 216.59388,213.16264" + style="opacity:0.39183673;fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#264a9e;stroke-width:0.19158669;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:nodetypes="cc" /> + <path + style="opacity:0.39183673;fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#264a9e;stroke-width:0.19158669;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 223.52993,201.58154 L 223.52993,213.17716" + id="path14573" + sodipodi:nodetypes="cc" /> + </g> + <rect + style="opacity:0.56734691;fill:#6785ed;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.28953668;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect14575" + width="18.826923" + height="3.4617176" + x="211.13826" + y="205.69659" /> + </g> + <path + id="path14577" + d="M 348.41747,872.70033 L 344.32198,875.20198 L 347.62851,878.51995 L 347.81466,877.15941 C 349.25494,877.89517 350.22026,879.34 350.25245,881.03915 C 350.28567,882.72754 349.61664,884.73976 347.18723,885.27752 L 348.75397,886.05119 L 347.30895,887.42238 C 350.50873,887.29672 352.64592,884.74146 352.64592,880.93383 C 352.64592,877.963 350.77967,875.46607 348.17813,874.45588 L 348.41747,872.70033 z M 343.44469,874.44212 C 341.5557,875.30465 338.71064,877.47166 338.71064,880.93383 C 338.71064,883.67754 340.30281,886.02267 342.59337,887.16599 L 342.31856,888.76355 L 346.48496,886.58667 L 343.32914,882.97903 L 343.05433,884.55025 C 341.94815,883.74533 341.21048,882.48909 341.21048,881.03915 C 341.21048,879.87772 341.55166,878.42581 343.61973,877.04093 L 342.42947,876.76138 L 343.44469,874.44212 z " + style="fill:url(#linearGradient14591);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient14593);stroke-width:0.29233795;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:nodetypes="ccccscccscccscccccsccc" /> + </g> + <rect + y="181.38919" + x="207.79199" + height="48" + width="48" + id="rect11539" + style="fill:#b9cff9;fill-opacity:0;stroke:none;stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" /> + </g> + <g + id="g12096" + transform="translate(-5,-4.9922733)"> + <g + inkscape:export-ydpi="90.000092" + inkscape:export-xdpi="90.000092" + inkscape:export-filename="/home/lando/Projects/Amarok/icons/32/amarok_repeat_playlist.png" + transform="matrix(0.999518,0,0,0.999518,-168.22882,-669.28385)" + id="g13948"> + <path + sodipodi:nodetypes="ccccccccc" + style="fill:url(#linearGradient13660);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient13662);stroke-width:0.75036138;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 334.33675,865.95659 C 332.05241,865.95659 330.21663,866.76872 330.21663,869.13925 L 330.21663,888.3102 C 330.34703,889.16503 331.30283,890.0056 331.87646,890.16647 L 359.60335,890.16647 C 360.18147,890.00433 361.35576,889.1747 361.48205,888.3102 L 361.48205,869.13925 C 361.48205,866.76872 359.64627,865.95659 357.36194,865.95659 L 334.33675,865.95659 z " + id="path13191" /> + <path + id="path13193" + d="M 332.56025,871.5744 L 359.1385,871.5744 C 359.97991,871.5744 360.65728,872.23401 360.65728,873.05336 L 360.65728,887.97885 C 360.65728,888.79819 359.97991,889.45781 359.1385,889.45781 L 332.56025,889.45781 C 331.71884,889.45781 331.04147,888.79819 331.04147,887.97885 L 331.04147,873.05336 C 331.04147,872.23401 331.71884,871.5744 332.56025,871.5744 z " + style="fill:url(#linearGradient13664);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient13666);stroke-width:0.926992;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" /> + <path + style="opacity:0.99578091;fill:url(#radialGradient13668);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.57956129;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 332.52184,871.9532 L 359.15482,871.9532 C 359.99794,871.9532 360.67672,872.59532 360.67672,873.39293 L 360.67672,887.92245 C 360.67672,888.72006 359.99794,889.36218 359.15482,889.36218 L 332.52184,889.36218 C 331.6788,889.36218 331,888.72006 331,887.92245 L 331,873.39293 C 331,872.59532 331.6788,871.9532 332.52184,871.9532 z " + id="path13195" /> + <path + id="path13197" + d="M 334.60316,866.00555 C 332.37168,866.00555 330.57838,866.8397 330.57838,869.27451 L 330.57838,869.63869 C 330.70574,870.5167 331.63944,871.38006 332.19978,871.5453 L 359.28507,871.5453 C 359.8498,871.37876 360.99693,870.52663 361.1203,869.63869 L 361.1203,869.27451 C 361.1203,866.8397 359.327,866.00555 357.09551,866.00555 L 334.60316,866.00555 z " + style="opacity:0.15189873;fill:url(#linearGradient13670);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.71897209;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:nodetypes="ccccccccc" /> + <g + transform="matrix(1.584275,0,0,1.524619,-3.54568,564.4865)" + id="g13199"> + <g + id="g13201"> + <path + id="path13203" + d="M 216.59388,201.58154 L 216.59388,213.16264" + style="opacity:0.39183673;fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#264a9e;stroke-width:0.28967622;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:nodetypes="cc" /> + <path + style="opacity:0.39183673;fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#264a9e;stroke-width:0.28967622;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 223.52993,201.58154 L 223.52993,213.17716" + id="path13205" + sodipodi:nodetypes="cc" /> + </g> + <rect + style="opacity:0.56734691;fill:#6785ed;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.28953668;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect13207" + width="18.826923" + height="3.4617176" + x="211.13826" + y="205.69659" /> + </g> + <path + id="path13465" + d="M 348.41747,872.70033 L 344.32198,875.20198 L 347.62851,878.51995 L 347.81466,877.15941 C 349.25494,877.89517 350.22026,879.34 350.25245,881.03915 C 350.28567,882.72754 349.61664,884.73976 347.18723,885.27752 L 348.75397,886.05119 L 347.30895,887.42238 C 350.50873,887.29672 352.64592,884.74146 352.64592,880.93383 C 352.64592,877.963 350.77967,875.46607 348.17813,874.45588 L 348.41747,872.70033 z M 343.44469,874.44212 C 341.5557,875.30465 338.71064,877.47166 338.71064,880.93383 C 338.71064,883.67754 340.30281,886.02267 342.59337,887.16599 L 342.31856,888.76355 L 346.48496,886.58667 L 343.32914,882.97903 L 343.05433,884.55025 C 341.94815,883.74533 341.21048,882.48909 341.21048,881.03915 C 341.21048,879.87772 341.55166,878.42581 343.61973,877.04093 L 342.42947,876.76138 L 343.44469,874.44212 z " + style="fill:url(#linearGradient13832);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient13834);stroke-width:0.44201061;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:nodetypes="ccccscccscccscccccsccc" /> + </g> + <rect + style="fill:#b9cff9;fill-opacity:0;stroke:none;stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + id="rect11541" + width="32" + height="32" + x="161.45383" + y="192.35446" /> + </g> + <g + id="g12082" + transform="translate(-11.693429,-4.8608646)"> + <g + inkscape:export-ydpi="90.000092" + inkscape:export-xdpi="90.000092" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/crystalsvg/22x22/actions/repeat_playlist.png" + transform="matrix(0.998349,0,0,0.998349,-180.73345,77.503369)" + id="g13169"> + <path + id="path13171" + d="M 311.11582,127.59142 C 309.56067,127.59142 308.3109,128.15181 308.3109,129.78752 L 308.3109,143.01591 C 308.39967,143.60576 309.05037,144.18577 309.44088,144.29677 L 328.31708,144.29677 C 328.71066,144.18489 329.51009,143.61243 329.59608,143.01591 L 329.59608,129.78752 C 329.59608,128.15181 328.34629,127.59142 326.79114,127.59142 L 311.11582,127.59142 z " + style="fill:url(#linearGradient12169);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient12171);stroke-width:0.75123984;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:nodetypes="ccccccccc" /> + <path + style="fill:url(#linearGradient12173);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient12175);stroke-width:0.98547882;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 310.45027,131.91441 L 327.48392,131.91441 C 328.02316,131.91441 328.45728,132.33987 328.45728,132.86835 L 328.45728,142.49538 C 328.45728,143.02387 328.02316,143.44933 327.48392,143.44933 L 310.45027,143.44933 C 309.91102,143.44933 309.47691,143.02387 309.47691,142.49538 L 309.47691,132.86835 C 309.47691,132.33987 309.91102,131.91441 310.45027,131.91441 z " + id="path13173" /> + <path + id="path13175" + d="M 310.51506,131.89871 L 327.40466,131.89871 C 327.93932,131.89871 328.36979,132.32555 328.36979,132.85575 L 328.36979,142.514 C 328.36979,143.0442 327.93932,143.47104 327.40466,143.47104 L 310.51506,143.47104 C 309.98045,143.47104 309.54997,143.0442 309.54997,142.514 L 309.54997,132.85575 C 309.54997,132.32555 309.98045,131.89871 310.51506,131.89871 z " + style="opacity:0.99578091;fill:url(#radialGradient12177);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.57956129;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" /> + <path + sodipodi:nodetypes="ccccccccc" + style="opacity:0.15189873;fill:url(#linearGradient12179);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.71897209;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 311.31427,127.84643 C 309.78691,127.84643 308.55947,128.37964 308.55947,129.93604 L 308.55947,130.16883 C 308.64663,130.73008 309.28572,131.28197 309.66925,131.38759 L 328.20812,131.38759 C 328.59465,131.28114 329.37982,130.73643 329.46426,130.16883 L 329.46426,129.93604 C 329.46426,128.37964 328.23682,127.84643 326.70945,127.84643 L 311.31427,127.84643 z " + id="path13177" /> + <g + transform="translate(98.41138,-69.6771)" + id="g13179"> + <g + id="g13181"> + <path + sodipodi:nodetypes="cc" + style="opacity:0.39183673;fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#264a9e;stroke-width:0.29001534;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 216.59388,201.58154 L 216.59388,213.16264" + id="path13183" /> + <path + sodipodi:nodetypes="cc" + id="path13185" + d="M 223.52993,201.58154 L 223.52993,213.17716" + style="opacity:0.39183673;fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#264a9e;stroke-width:0.29001534;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + <rect + y="205.69659" + x="211.13826" + height="3.4617176" + width="18.826923" + id="rect13187" + style="opacity:0.56734691;fill:#6785ed;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.28953668;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + <path + sodipodi:nodetypes="ccccscccscccscccccsccc" + style="fill:url(#linearGradient12181);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient12183);stroke-width:0.30425215;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 320.82723,132.22087 L 318.01158,133.94076 L 320.28482,136.22187 L 320.4128,135.28649 C 321.40299,135.79233 322.06665,136.78565 322.08878,137.95381 C 322.11162,139.11458 321.65166,140.49798 319.98144,140.8677 L 321.05857,141.39959 L 320.06512,142.34229 C 322.26497,142.2559 323.73429,140.49915 323.73429,137.88141 C 323.73429,135.83896 322.45124,134.12232 320.66268,133.42781 L 320.82723,132.22087 z M 317.40844,133.41836 C 316.10976,134.01134 314.15378,135.50116 314.15378,137.88141 C 314.15378,139.76771 315.2484,141.37999 316.82316,142.16602 L 316.63423,143.26434 L 319.49863,141.76774 L 317.329,139.28748 L 317.14007,140.36769 C 316.37957,139.81431 315.87242,138.95065 315.87242,137.95381 C 315.87242,137.15533 316.10698,136.15714 317.52878,135.20504 L 316.71048,135.01284 L 317.40844,133.41836 z " + id="path13189" /> + </g> + <rect + inkscape:export-ydpi="90.000092" + inkscape:export-xdpi="90.000092" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/crystalsvg/22x22/actions/repeat_playlist.png" + y="202.22305" + x="126.69344" + height="22" + width="22" + id="rect11543" + style="fill:#b9cff9;fill-opacity:0;stroke:none;stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" /> + </g> + <g + id="g12067" + transform="translate(-5,-4.6347294)"> + <g + inkscape:export-ydpi="90.000092" + inkscape:export-xdpi="90.000092" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/crystalsvg/16x16/actions/repeat_playlist.png" + id="g14481" + transform="matrix(0.726112,0,0,0.726112,-132.96109,117.28628)"> + <path + sodipodi:nodetypes="ccccccccc" + style="fill:url(#linearGradient14503);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient14505);stroke-width:1.03289902;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 311.11582,127.59142 C 309.56067,127.59142 308.3109,128.15181 308.3109,129.78752 L 308.3109,143.01591 C 308.39967,143.60576 309.05037,144.18577 309.44088,144.29677 L 328.31708,144.29677 C 328.71066,144.18489 329.51009,143.61243 329.59608,143.01591 L 329.59608,129.78752 C 329.59608,128.15181 328.34629,127.59142 326.79114,127.59142 L 311.11582,127.59142 z " + id="path14483" /> + <path + id="path14485" + d="M 310.45027,131.91441 L 327.48392,131.91441 C 328.02316,131.91441 328.45728,132.33987 328.45728,132.86835 L 328.45728,142.49538 C 328.45728,143.02387 328.02316,143.44933 327.48392,143.44933 L 310.45027,143.44933 C 309.91102,143.44933 309.47691,143.02387 309.47691,142.49538 L 309.47691,132.86835 C 309.47691,132.33987 309.91102,131.91441 310.45027,131.91441 z " + style="fill:url(#linearGradient14507);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient14509);stroke-width:1.35496032;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + style="opacity:0.99578091;fill:url(#radialGradient14511);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.57956129;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 310.51506,131.89871 L 327.40466,131.89871 C 327.93932,131.89871 328.36979,132.32555 328.36979,132.85575 L 328.36979,142.514 C 328.36979,143.0442 327.93932,143.47104 327.40466,143.47104 L 310.51506,143.47104 C 309.98045,143.47104 309.54997,143.0442 309.54997,142.514 L 309.54997,132.85575 C 309.54997,132.32555 309.98045,131.89871 310.51506,131.89871 z " + id="path14487" /> + <path + id="path14489" + d="M 311.31427,127.84643 C 309.78691,127.84643 308.55947,128.37964 308.55947,129.93604 L 308.55947,130.16883 C 308.64663,130.73008 309.28572,131.28197 309.66925,131.38759 L 328.20812,131.38759 C 328.59465,131.28114 329.37982,130.73643 329.46426,130.16883 L 329.46426,129.93604 C 329.46426,128.37964 328.23682,127.84643 326.70945,127.84643 L 311.31427,127.84643 z " + style="opacity:0.15189873;fill:url(#linearGradient14513);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.71897209;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:nodetypes="ccccccccc" /> + <g + id="g14491" + transform="translate(98.41138,-69.6771)"> + <g + id="g14493"> + <path + id="path14495" + d="M 216.59388,201.58154 L 216.59388,213.16264" + style="opacity:0.39183673;fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#264a9e;stroke-width:0.39874956;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:nodetypes="cc" /> + <path + style="opacity:0.39183673;fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#264a9e;stroke-width:0.39874956;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 223.52993,201.58154 L 223.52993,213.17716" + id="path14497" + sodipodi:nodetypes="cc" /> + </g> + <rect + style="opacity:0.56734691;fill:#6785ed;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.28953668;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect14499" + width="18.826923" + height="3.4617176" + x="211.13826" + y="205.69659" /> + </g> + <path + id="path14501" + d="M 320.82723,132.22087 L 318.01158,133.94076 L 320.28482,136.22187 L 320.4128,135.28649 C 321.40299,135.79233 322.06665,136.78565 322.08878,137.95381 C 322.11162,139.11458 321.65166,140.49798 319.98144,140.8677 L 321.05857,141.39959 L 320.06512,142.34229 C 322.26497,142.2559 323.73429,140.49915 323.73429,137.88141 C 323.73429,135.83896 322.45124,134.12232 320.66268,133.42781 L 320.82723,132.22087 z M 317.40844,133.41836 C 316.10976,134.01134 314.15378,135.50116 314.15378,137.88141 C 314.15378,139.76771 315.2484,141.37999 316.82316,142.16602 L 316.63423,143.26434 L 319.49863,141.76774 L 317.329,139.28748 L 317.14007,140.36769 C 316.37957,139.81431 315.87242,138.95065 315.87242,137.95381 C 315.87242,137.15533 316.10698,136.15714 317.52878,135.20504 L 316.71048,135.01284 L 317.40844,133.41836 z " + style="fill:url(#linearGradient14515);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient14517);stroke-width:0.41832414;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:nodetypes="ccccscccscccscccccsccc" /> + </g> + <rect + inkscape:export-ydpi="90.000092" + inkscape:export-xdpi="90.000092" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/crystalsvg/16x16/actions/repeat_playlist.png" + style="fill:#b9cff9;fill-opacity:0;stroke:none;stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + id="rect11545" + width="16" + height="16" + x="90.63485" + y="207.99692" /> + </g> + <g + id="g12138" + transform="translate(-5,-1.4685306)"> + <g + inkscape:export-ydpi="90.000092" + inkscape:export-xdpi="90.000092" + inkscape:export-filename="/home/lando/Projects/Amarok/icons/16/amarok_podcast.png" + id="g14595" + transform="matrix(0.716482,0,0,0.716482,-121.90782,128.02897)"> + <g + id="g14597" + transform="translate(88.4993,218.0888)"> + <path + sodipodi:nodetypes="ccccccccc" + style="fill:url(#linearGradient14619);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient14621);stroke-width:1.04678178;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 214.55635,-8.9876506 C 213.0012,-8.9876506 211.75143,-8.4272616 211.75143,-6.7915466 L 211.75143,6.4368384 C 211.8402,7.0266884 212.4909,7.6066964 212.88141,7.7177054 L 231.75761,7.7177054 C 232.15119,7.6058244 232.95062,7.0333644 233.03661,6.4368384 L 233.03661,-6.7915466 C 233.03661,-8.4272616 231.78682,-8.9876506 230.23167,-8.9876506 L 214.55635,-8.9876506 z " + id="path14599" /> + <path + id="path14601" + d="M 213.8908,-4.6646566 L 230.92445,-4.6646566 C 231.46369,-4.6646566 231.89781,-4.2391996 231.89781,-3.7107166 L 231.89781,5.9163164 C 231.89781,6.4447994 231.46369,6.8702564 230.92445,6.8702564 L 213.8908,6.8702564 C 213.35155,6.8702564 212.91744,6.4447994 212.91744,5.9163164 L 212.91744,-3.7107166 C 212.91744,-4.2391996 213.35155,-4.6646566 213.8908,-4.6646566 z " + style="fill:url(#linearGradient14623);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient14625);stroke-width:1.37317157;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + style="opacity:0.99578091;fill:url(#radialGradient14627);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.57956129;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 213.95559,-4.6803566 L 230.84519,-4.6803566 C 231.37985,-4.6803566 231.81032,-4.2535196 231.81032,-3.7233216 L 231.81032,5.9349364 C 231.81032,6.4651324 231.37985,6.8919714 230.84519,6.8919714 L 213.95559,6.8919714 C 213.42098,6.8919714 212.9905,6.4651324 212.9905,5.9349364 L 212.9905,-3.7233216 C 212.9905,-4.2535196 213.42098,-4.6803566 213.95559,-4.6803566 z " + id="path14603" /> + <path + id="path14605" + d="M 214.7548,-8.7326426 C 213.22744,-8.7326426 212,-8.1994276 212,-6.6430286 L 212,-6.4102356 C 212.08716,-5.8489876 212.72625,-5.2971026 213.10978,-5.1914766 L 231.64865,-5.1914766 C 232.03518,-5.2979336 232.82035,-5.8426356 232.90479,-6.4102356 L 232.90479,-6.6430286 C 232.90479,-8.1994276 231.67735,-8.7326426 230.14998,-8.7326426 L 214.7548,-8.7326426 z " + style="opacity:0.15189873;fill:url(#linearGradient14629);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.71897209;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:nodetypes="ccccccccc" /> + </g> + <g + id="g14607" + transform="translate(90.32382,11.81865)"> + <g + id="g14609"> + <path + id="path14611" + d="M 216.59388,201.58154 L 216.59388,213.16264" + style="opacity:0.39183673;fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#264a9e;stroke-width:0.404109;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:nodetypes="cc" /> + <path + style="opacity:0.39183673;fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#264a9e;stroke-width:0.404109;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 223.52993,201.58154 L 223.52993,213.17716" + id="path14613" + sodipodi:nodetypes="cc" /> + </g> + <rect + style="opacity:0.56734691;fill:#6785ed;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.28953668;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect14615" + width="18.826923" + height="3.4617176" + x="211.13826" + y="205.69659" /> + </g> + <path + sodipodi:nodetypes="csccccccsc" + id="path14617" + d="M 310.89331,216.29318 C 310.03681,216.29318 309.34165,216.99176 309.34165,217.85246 C 309.34165,218.32598 309.55614,218.74422 309.88804,219.03027 L 310.45921,219.29448 L 310.28501,224.85787 L 311.80967,224.85787 L 311.44051,219.31786 L 311.94115,218.99461 C 312.24861,218.70963 312.44496,218.30561 312.44496,217.85246 C 312.44496,216.99176 311.74979,216.29318 310.89331,216.29318 z " + style="fill:url(#linearGradient14631);fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.3114523;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.64321606" + inkscape:export-filename="/home/vadim/collection.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + </g> + <rect + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/16x16/actions/amarok_podcast.png" + y="275.83072" + x="92.841637" + height="16" + width="16" + id="rect11547" + style="fill:#b9cff9;fill-opacity:0;stroke:none;stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" /> + </g> + <g + id="g12153" + transform="translate(-5,0.1391357)"> + <g + inkscape:export-ydpi="90.000092" + inkscape:export-xdpi="90.000092" + inkscape:export-filename="/home/lando/Projects/Amarok/icons/22/amarok_podcast.png" + transform="matrix(0.998349,0,0,0.998349,-171.08095,62.128229)" + id="g13273"> + <g + transform="translate(88.4993,218.0888)" + id="g13275"> + <path + id="path13277" + d="M 214.55635,-8.9876506 C 213.0012,-8.9876506 211.75143,-8.4272616 211.75143,-6.7915466 L 211.75143,6.4368384 C 211.8402,7.0266884 212.4909,7.6066964 212.88141,7.7177054 L 231.75761,7.7177054 C 232.15119,7.6058244 232.95062,7.0333644 233.03661,6.4368384 L 233.03661,-6.7915466 C 233.03661,-8.4272616 231.78682,-8.9876506 230.23167,-8.9876506 L 214.55635,-8.9876506 z " + style="fill:url(#linearGradient13708);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient13710);stroke-width:0.75123984;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:nodetypes="ccccccccc" /> + <path + style="fill:url(#linearGradient13712);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient13714);stroke-width:0.98547882;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 213.8908,-4.6646566 L 230.92445,-4.6646566 C 231.46369,-4.6646566 231.89781,-4.2391996 231.89781,-3.7107166 L 231.89781,5.9163164 C 231.89781,6.4447994 231.46369,6.8702564 230.92445,6.8702564 L 213.8908,6.8702564 C 213.35155,6.8702564 212.91744,6.4447994 212.91744,5.9163164 L 212.91744,-3.7107166 C 212.91744,-4.2391996 213.35155,-4.6646566 213.8908,-4.6646566 z " + id="path13279" /> + <path + id="path13281" + d="M 213.95559,-4.6803566 L 230.84519,-4.6803566 C 231.37985,-4.6803566 231.81032,-4.2535196 231.81032,-3.7233216 L 231.81032,5.9349364 C 231.81032,6.4651324 231.37985,6.8919714 230.84519,6.8919714 L 213.95559,6.8919714 C 213.42098,6.8919714 212.9905,6.4651324 212.9905,5.9349364 L 212.9905,-3.7233216 C 212.9905,-4.2535196 213.42098,-4.6803566 213.95559,-4.6803566 z " + style="opacity:0.99578091;fill:url(#radialGradient13716);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.57956129;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" /> + <path + sodipodi:nodetypes="ccccccccc" + style="opacity:0.15189873;fill:url(#linearGradient13718);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.71897209;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 214.7548,-8.7326426 C 213.22744,-8.7326426 212,-8.1994276 212,-6.6430286 L 212,-6.4102356 C 212.08716,-5.8489876 212.72625,-5.2971026 213.10978,-5.1914766 L 231.64865,-5.1914766 C 232.03518,-5.2979336 232.82035,-5.8426356 232.90479,-6.4102356 L 232.90479,-6.6430286 C 232.90479,-8.1994276 231.67735,-8.7326426 230.14998,-8.7326426 L 214.7548,-8.7326426 z " + id="path13283" /> + </g> + <g + transform="translate(90.32382,11.81865)" + id="g13285"> + <g + id="g13287"> + <path + sodipodi:nodetypes="cc" + style="opacity:0.39183673;fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#264a9e;stroke-width:0.29001534;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 216.59388,201.58154 L 216.59388,213.16264" + id="path13289" /> + <path + sodipodi:nodetypes="cc" + id="path13291" + d="M 223.52993,201.58154 L 223.52993,213.17716" + style="opacity:0.39183673;fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#264a9e;stroke-width:0.29001534;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + <rect + y="205.69659" + x="211.13826" + height="3.4617176" + width="18.826923" + id="rect13293" + style="opacity:0.56734691;fill:#6785ed;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.28953668;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/vadim/collection.png" + style="fill:url(#linearGradient13720);fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.22351876;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.64321606" + d="M 310.89331,216.29318 C 310.03681,216.29318 309.34165,216.99176 309.34165,217.85246 C 309.34165,218.32598 309.55614,218.74422 309.88804,219.03027 L 310.45921,219.29448 L 310.28501,224.85787 L 311.80967,224.85787 L 311.44051,219.31786 L 311.94115,218.99461 C 312.24861,218.70963 312.44496,218.30561 312.44496,217.85246 C 312.44496,216.99176 311.74979,216.29318 310.89331,216.29318 z " + id="path13295" + sodipodi:nodetypes="csccccccsc" /> + </g> + <rect + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/22x22/actions/amarok_podcast.png" + style="fill:#b9cff9;fill-opacity:0;stroke:none;stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + id="rect11549" + width="22" + height="22" + x="128.29907" + y="268.22305" /> + </g> + <g + id="g12185" + transform="translate(-5,-1.9922733)"> + <g + inkscape:export-ydpi="90.000092" + inkscape:export-xdpi="90.000092" + inkscape:export-filename="/home/lando/Projects/Amarok/icons/32/amarok_podcast.png" + transform="matrix(0.999518,0,0,0.999518,-166.93951,-674.87052)" + id="g13960"> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Playlist Types/amarok_podcast.png" + id="g13159" + transform="translate(89.86323,956.3031)"> + <path + sodipodi:nodetypes="ccccccccc" + style="fill:url(#linearGradient13632);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient13634);stroke-width:0.75036138;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 247.4797,-16.724353 C 245.19536,-16.724353 243.35958,-15.912221 243.35958,-13.541697 L 243.35958,5.629258 C 243.48998,6.484086 244.44578,7.32465 245.01941,7.485528 L 272.7463,7.485528 C 273.32442,7.323387 274.49871,6.49376 274.625,5.629258 L 274.625,-13.541697 C 274.625,-15.912221 272.78922,-16.724353 270.50489,-16.724353 L 247.4797,-16.724353 z " + id="path13161" /> + <path + id="path13163" + d="M 245.7032,-11.106549 L 272.28145,-11.106549 C 273.12286,-11.106549 273.80023,-10.446932 273.80023,-9.627587 L 273.80023,5.2979 C 273.80023,6.117245 273.12286,6.776862 272.28145,6.776862 L 245.7032,6.776862 C 244.86179,6.776862 244.18442,6.117245 244.18442,5.2979 L 244.18442,-9.627587 C 244.18442,-10.446932 244.86179,-11.106549 245.7032,-11.106549 z " + style="fill:url(#linearGradient13636);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient13638);stroke-width:0.926992;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" /> + <path + style="opacity:0.99578091;fill:url(#radialGradient13640);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.57956129;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 245.66479,-10.727742 L 272.29777,-10.727742 C 273.14089,-10.727742 273.81967,-10.085626 273.81967,-9.288015 L 273.81967,5.241504 C 273.81967,6.039111 273.14089,6.681231 272.29777,6.681231 L 245.66479,6.681231 C 244.82175,6.681231 244.14295,6.039111 244.14295,5.241504 L 244.14295,-9.288015 C 244.14295,-10.085626 244.82175,-10.727742 245.66479,-10.727742 z " + id="path13165" /> + <path + id="path13167" + d="M 247.74611,-16.675395 C 245.51463,-16.675395 243.72133,-15.841241 243.72133,-13.406434 L 243.72133,-13.042257 C 243.84869,-12.164248 244.78239,-11.300888 245.34273,-11.135649 L 272.42802,-11.135649 C 272.99275,-11.302188 274.13988,-12.154312 274.26325,-13.042257 L 274.26325,-13.406434 C 274.26325,-15.841241 272.46995,-16.675395 270.23846,-16.675395 L 247.74611,-16.675395 z " + style="opacity:0.15189873;fill:url(#linearGradient13642);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.71897209;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:nodetypes="ccccccccc" /> + </g> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Playlist Types/amarok_podcast.png" + transform="matrix(1.584275,0,0,1.524619,-0.501067,638.0219)" + id="g13297"> + <g + id="g13299"> + <path + id="path13301" + d="M 216.59388,201.58154 L 216.59388,213.16264" + style="opacity:0.39183673;fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#264a9e;stroke-width:0.28967622;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:nodetypes="cc" /> + <path + style="opacity:0.39183673;fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#264a9e;stroke-width:0.28967622;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 223.52993,201.58154 L 223.52993,213.17716" + id="path13303" + sodipodi:nodetypes="cc" /> + </g> + <rect + style="opacity:0.56734691;fill:#6785ed;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.28953668;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect13305" + width="18.826923" + height="3.4617176" + x="211.13826" + y="205.69659" /> + </g> + <path + sodipodi:nodetypes="csccccccsc" + id="path13307" + d="M 348.85556,950.52449 C 347.60961,950.52449 346.59836,951.54071 346.59836,952.79277 C 346.59836,953.4816 346.91038,954.09002 347.3932,954.50614 L 348.22408,954.89048 L 347.97067,962.98354 L 350.18859,962.98354 L 349.65157,954.92449 L 350.37985,954.45426 C 350.82712,954.0397 351.11275,953.45197 351.11275,952.79277 C 351.11275,951.54071 350.10148,950.52449 348.85556,950.52449 z " + style="fill:url(#linearGradient13722);fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.32477254;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.64321606" + inkscape:export-filename="/home/lando/Projects/Amarok/Playlist Types/amarok_podcast.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + </g> + <rect + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/32x32/actions/amarok_podcast.png" + y="260.35446" + x="165.74786" + height="32" + width="32" + id="rect11551" + style="fill:#b9cff9;fill-opacity:0;stroke:none;stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" /> + </g> + <g + id="g12200" + transform="translate(-5,-3.0270023)"> + <g + inkscape:export-ydpi="90.000092" + inkscape:export-xdpi="90.000092" + inkscape:export-filename="/home/lando/Projects/Amarok/icons/48/amarok_podcast.png" + id="g14633" + transform="matrix(1.511259,0,0,1.511259,-291.76899,-1168.8514)"> + <g + transform="translate(89.86323,956.3031)" + id="g14635" + inkscape:export-filename="/home/lando/Projects/Amarok/Playlist Types/amarok_podcast.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <path + id="path14637" + d="M 247.4797,-16.724353 C 245.19536,-16.724353 243.35958,-15.912221 243.35958,-13.541697 L 243.35958,5.629258 C 243.48998,6.484086 244.44578,7.32465 245.01941,7.485528 L 272.7463,7.485528 C 273.32442,7.323387 274.49871,6.49376 274.625,5.629258 L 274.625,-13.541697 C 274.625,-15.912221 272.78922,-16.724353 270.50489,-16.724353 L 247.4797,-16.724353 z " + style="fill:url(#linearGradient14657);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient14659);stroke-width:0.49627423;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:nodetypes="ccccccccc" /> + <path + style="fill:url(#linearGradient14661);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient14663);stroke-width:0.61309427;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 245.7032,-11.106549 L 272.28145,-11.106549 C 273.12286,-11.106549 273.80023,-10.446932 273.80023,-9.627587 L 273.80023,5.2979 C 273.80023,6.117245 273.12286,6.776862 272.28145,6.776862 L 245.7032,6.776862 C 244.86179,6.776862 244.18442,6.117245 244.18442,5.2979 L 244.18442,-9.627587 C 244.18442,-10.446932 244.86179,-11.106549 245.7032,-11.106549 z " + id="path14639" /> + <path + id="path14641" + d="M 245.66479,-10.727742 L 272.29777,-10.727742 C 273.14089,-10.727742 273.81967,-10.085626 273.81967,-9.288015 L 273.81967,5.241504 C 273.81967,6.039111 273.14089,6.681231 272.29777,6.681231 L 245.66479,6.681231 C 244.82175,6.681231 244.14295,6.039111 244.14295,5.241504 L 244.14295,-9.288015 C 244.14295,-10.085626 244.82175,-10.727742 245.66479,-10.727742 z " + style="opacity:0.99578091;fill:url(#radialGradient14665);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.57956129;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" /> + <path + sodipodi:nodetypes="ccccccccc" + style="opacity:0.15189873;fill:url(#linearGradient14667);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.71897209;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 247.74611,-16.675395 C 245.51463,-16.675395 243.72133,-15.841241 243.72133,-13.406434 L 243.72133,-13.042257 C 243.84869,-12.164248 244.78239,-11.300888 245.34273,-11.135649 L 272.42802,-11.135649 C 272.99275,-11.302188 274.13988,-12.154312 274.26325,-13.042257 L 274.26325,-13.406434 C 274.26325,-15.841241 272.46995,-16.675395 270.23846,-16.675395 L 247.74611,-16.675395 z " + id="path14643" /> + </g> + <g + id="g14645" + transform="matrix(1.584275,0,0,1.524619,-0.501067,638.0219)" + inkscape:export-filename="/home/lando/Projects/Amarok/Playlist Types/amarok_podcast.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <g + id="g14647"> + <path + sodipodi:nodetypes="cc" + style="opacity:0.39183673;fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#264a9e;stroke-width:0.19158611;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 216.59388,201.58154 L 216.59388,213.16264" + id="path14649" /> + <path + sodipodi:nodetypes="cc" + id="path14651" + d="M 223.52993,201.58154 L 223.52993,213.17716" + style="opacity:0.39183673;fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#264a9e;stroke-width:0.19158611;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + <rect + y="205.69659" + x="211.13826" + height="3.4617176" + width="18.826923" + id="rect14653" + style="opacity:0.56734691;fill:#6785ed;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.28953668;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Playlist Types/amarok_podcast.png" + style="fill:url(#linearGradient14669);fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.21479817;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.64321606" + d="M 348.85556,950.52449 C 347.60961,950.52449 346.59836,951.54071 346.59836,952.79277 C 346.59836,953.4816 346.91038,954.09002 347.3932,954.50614 L 348.22408,954.89048 L 347.97067,962.98354 L 350.18859,962.98354 L 349.65157,954.92449 L 350.37985,954.45426 C 350.82712,954.0397 351.11275,953.45197 351.11275,952.79277 C 351.11275,951.54071 350.10148,950.52449 348.85556,950.52449 z " + id="path14655" + sodipodi:nodetypes="csccccccsc" /> + </g> + <rect + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/48x48/actions/amarok_podcast.png" + style="fill:#b9cff9;fill-opacity:0;stroke:none;stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + id="rect11553" + width="48" + height="48" + x="211.44208" + y="245.38919" /> + </g> + <g + id="g12215" + transform="translate(-5,-10.027002)"> + <g + inkscape:export-ydpi="90.000092" + inkscape:export-xdpi="90.000092" + inkscape:export-filename="/home/lando/Projects/Amarok/icons/64/amarok_podcast.png" + transform="matrix(2.023001,0,0,2.023001,-388.20598,-1656.8679)" + id="g14671"> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Playlist Types/amarok_podcast.png" + id="g14673" + transform="translate(89.86323,956.3031)"> + <path + sodipodi:nodetypes="ccccccccc" + style="fill:url(#linearGradient14695);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient14697);stroke-width:0.37073594;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 247.4797,-16.724353 C 245.19536,-16.724353 243.35958,-15.912221 243.35958,-13.541697 L 243.35958,5.629258 C 243.48998,6.484086 244.44578,7.32465 245.01941,7.485528 L 272.7463,7.485528 C 273.32442,7.323387 274.49871,6.49376 274.625,5.629258 L 274.625,-13.541697 C 274.625,-15.912221 272.78922,-16.724353 270.50489,-16.724353 L 247.4797,-16.724353 z " + id="path14675" /> + <path + id="path14677" + d="M 245.7032,-11.106549 L 272.28145,-11.106549 C 273.12286,-11.106549 273.80023,-10.446932 273.80023,-9.627587 L 273.80023,5.2979 C 273.80023,6.117245 273.12286,6.776862 272.28145,6.776862 L 245.7032,6.776862 C 244.86179,6.776862 244.18442,6.117245 244.18442,5.2979 L 244.18442,-9.627587 C 244.18442,-10.446932 244.86179,-11.106549 245.7032,-11.106549 z " + style="fill:url(#linearGradient14699);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient14701);stroke-width:0.45800501;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" /> + <path + style="opacity:0.99578091;fill:url(#radialGradient14703);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.57956129;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 245.66479,-10.727742 L 272.29777,-10.727742 C 273.14089,-10.727742 273.81967,-10.085626 273.81967,-9.288015 L 273.81967,5.241504 C 273.81967,6.039111 273.14089,6.681231 272.29777,6.681231 L 245.66479,6.681231 C 244.82175,6.681231 244.14295,6.039111 244.14295,5.241504 L 244.14295,-9.288015 C 244.14295,-10.085626 244.82175,-10.727742 245.66479,-10.727742 z " + id="path14679" /> + <path + id="path14681" + d="M 247.74611,-16.675395 C 245.51463,-16.675395 243.72133,-15.841241 243.72133,-13.406434 L 243.72133,-13.042257 C 243.84869,-12.164248 244.78239,-11.300888 245.34273,-11.135649 L 272.42802,-11.135649 C 272.99275,-11.302188 274.13988,-12.154312 274.26325,-13.042257 L 274.26325,-13.406434 C 274.26325,-15.841241 272.46995,-16.675395 270.23846,-16.675395 L 247.74611,-16.675395 z " + style="opacity:0.15189873;fill:url(#linearGradient14705);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.71897209;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:nodetypes="ccccccccc" /> + </g> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Playlist Types/amarok_podcast.png" + transform="matrix(1.584275,0,0,1.524619,-0.501067,638.0219)" + id="g14683"> + <g + id="g14685"> + <path + id="path14687" + d="M 216.59388,201.58154 L 216.59388,213.16264" + style="opacity:0.39183673;fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#264a9e;stroke-width:0.1431222;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:nodetypes="cc" /> + <path + style="opacity:0.39183673;fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#264a9e;stroke-width:0.1431222;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 223.52993,201.58154 L 223.52993,213.17716" + id="path14689" + sodipodi:nodetypes="cc" /> + </g> + <rect + style="opacity:0.56734691;fill:#6785ed;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.28953668;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect14691" + width="18.826923" + height="3.4617176" + x="211.13826" + y="205.69659" /> + </g> + <path + sodipodi:nodetypes="csccccccsc" + id="path14693" + d="M 348.85556,950.52449 C 347.60961,950.52449 346.59836,951.54071 346.59836,952.79277 C 346.59836,953.4816 346.91038,954.09002 347.3932,954.50614 L 348.22408,954.89048 L 347.97067,962.98354 L 350.18859,962.98354 L 349.65157,954.92449 L 350.37985,954.45426 C 350.82712,954.0397 351.11275,953.45197 351.11275,952.79277 C 351.11275,951.54071 350.10148,950.52449 348.85556,950.52449 z " + style="fill:url(#linearGradient14707);fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.1604625;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.64321606" + inkscape:export-filename="/home/lando/Projects/Amarok/Playlist Types/amarok_podcast.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + </g> + <rect + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/64x64/actions/amarok_podcast.png" + y="236.38919" + x="285.52908" + height="64" + width="64" + id="rect11555" + style="fill:#b9cff9;fill-opacity:0;stroke:none;stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" /> + </g> + <g + id="g12230" + transform="translate(-5,-5.9884281)"> + <g + inkscape:export-ydpi="90.000092" + inkscape:export-xdpi="90.000092" + inkscape:export-filename="/home/lando/Projects/Amarok/icons/16/amarok_podcast2.png" + id="g14709" + transform="matrix(0.716482,0,0,0.716482,-129.57432,238.40893)"> + <g + id="g14711" + transform="translate(92.62357,176.413)"> + <path + sodipodi:nodetypes="ccccccccc" + style="fill:url(#linearGradient14741);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient14743);stroke-width:1.04678178;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 214.55635,-8.9876506 C 213.0012,-8.9876506 211.75143,-8.4272616 211.75143,-6.7915466 L 211.75143,6.4368384 C 211.8402,7.0266884 212.4909,7.6066964 212.88141,7.7177054 L 231.75761,7.7177054 C 232.15119,7.6058244 232.95062,7.0333644 233.03661,6.4368384 L 233.03661,-6.7915466 C 233.03661,-8.4272616 231.78682,-8.9876506 230.23167,-8.9876506 L 214.55635,-8.9876506 z " + id="path14713" /> + <path + id="path14715" + d="M 213.8908,-4.6646566 L 230.92445,-4.6646566 C 231.46369,-4.6646566 231.89781,-4.2391996 231.89781,-3.7107166 L 231.89781,5.9163164 C 231.89781,6.4447994 231.46369,6.8702564 230.92445,6.8702564 L 213.8908,6.8702564 C 213.35155,6.8702564 212.91744,6.4447994 212.91744,5.9163164 L 212.91744,-3.7107166 C 212.91744,-4.2391996 213.35155,-4.6646566 213.8908,-4.6646566 z " + style="fill:url(#linearGradient14745);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient14747);stroke-width:1.37317157;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + style="opacity:0.99578091;fill:url(#radialGradient14749);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.57956129;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 213.95559,-4.6803566 L 230.84519,-4.6803566 C 231.37985,-4.6803566 231.81032,-4.2535196 231.81032,-3.7233216 L 231.81032,5.9349364 C 231.81032,6.4651324 231.37985,6.8919714 230.84519,6.8919714 L 213.95559,6.8919714 C 213.42098,6.8919714 212.9905,6.4651324 212.9905,5.9349364 L 212.9905,-3.7233216 C 212.9905,-4.2535196 213.42098,-4.6803566 213.95559,-4.6803566 z " + id="path14717" /> + <path + id="path14719" + d="M 214.7548,-8.7326426 C 213.22744,-8.7326426 212,-8.1994276 212,-6.6430286 L 212,-6.4102356 C 212.08716,-5.8489876 212.72625,-5.2971026 213.10978,-5.1914766 L 231.64865,-5.1914766 C 232.03518,-5.2979336 232.82035,-5.8426356 232.90479,-6.4102356 L 232.90479,-6.6430286 C 232.90479,-8.1994276 231.67735,-8.7326426 230.14998,-8.7326426 L 214.7548,-8.7326426 z " + style="opacity:0.15189873;fill:url(#linearGradient14751);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.71897209;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:nodetypes="ccccccccc" /> + </g> + <g + transform="translate(94.43049,-29.83694)" + id="g14721"> + <g + id="g14723"> + <path + sodipodi:nodetypes="cc" + style="opacity:0.39183673;fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#264a9e;stroke-width:0.404109;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 216.59388,201.58154 L 216.59388,213.16264" + id="path14725" /> + <path + sodipodi:nodetypes="cc" + id="path14727" + d="M 223.52993,201.58154 L 223.52993,213.17716" + style="opacity:0.39183673;fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#264a9e;stroke-width:0.404109;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + <rect + y="205.69659" + x="211.13826" + height="3.4617176" + width="18.826923" + id="rect14729" + style="opacity:0.56734691;fill:#6785ed;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.28953668;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + <g + id="g14731" + transform="translate(104.5874,68.90333)"> + <g + id="g14733" + transform="matrix(0.905843,0,0,0.905843,57.41603,37.898)"> + <path + sodipodi:type="inkscape:offset" + inkscape:radius="0.71942902" + inkscape:original="M 201.5 47.875 C 197.85718 48.205064 195.12959 51.35581 195.46875 54.90625 C 195.62893 56.583244 196.42448 58.049773 197.625 59.09375 C 197.85376 59.307253 198.20874 59.307253 198.4375 59.09375 L 199.375 58.1875 C 199.4971 58.074787 199.5625 57.916174 199.5625 57.75 C 199.56251 57.583826 199.4971 57.425213 199.375 57.3125 C 198.62088 56.652518 198.10105 55.714144 198 54.65625 C 197.78709 52.427426 199.44435 50.490156 201.75 50.28125 C 204.05353 50.072515 206.06871 51.712605 206.28125 53.9375 C 206.41033 55.288748 205.85565 56.51317 204.875 57.34375 C 204.73828 57.452227 204.66226 57.606825 204.65625 57.78125 C 204.65023 57.955674 204.71482 58.132362 204.84375 58.25 L 205.71875 59.09375 C 205.94276 59.288486 206.27599 59.288486 206.5 59.09375 C 208.01963 57.782537 208.95296 55.812212 208.75 53.6875 C 208.42165 50.250246 205.34588 47.729812 201.84375 47.875 C 201.8096 47.876415 201.69162 47.857633 201.5 47.875 z " + xlink:href="#path5359" + style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.2;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path14735" + d="M 201.4375,47.15625 C 197.41705,47.52053 194.37229,51.014726 194.75,54.96875 C 194.92784,56.830672 195.82274,58.465378 197.15625,59.625 C 197.65641,60.091804 198.43734,60.091804 198.9375,59.625 L 199.875,58.71875 C 200.15019,58.464713 200.28125,58.104935 200.28125,57.75 C 200.28127,57.395009 200.15018,57.035274 199.875,56.78125 C 199.86458,56.781024 199.85417,56.781024 199.84375,56.78125 C 199.22065,56.235933 198.80142,55.459244 198.71875,54.59375 C 198.5432,52.756008 199.87407,51.175633 201.8125,51 C 203.74115,50.825235 205.38771,52.17032 205.5625,54 C 205.66911,55.116077 205.22291,56.089566 204.40625,56.78125 C 204.11053,57.015883 203.9502,57.381534 203.9375,57.75 C 203.92478,58.118451 204.03823,58.502493 204.34375,58.78125 L 205.21875,59.625 C 205.22917,59.625226 205.23958,59.625226 205.25,59.625 C 205.74062,60.051502 206.47813,60.051502 206.96875,59.625 C 208.64796,58.176095 209.69557,55.999456 209.46875,53.625 C 209.10312,49.797516 205.67634,46.996067 201.8125,47.15625 C 201.96122,47.150088 201.83088,47.15954 201.75,47.15625 C 201.66912,47.15296 201.56975,47.144264 201.4375,47.15625 z " + transform="translate(-33.18982,22.6937)" + inkscape:href="#path5359" /> + <path + d="M 201.5,47.875 C 197.85718,48.205064 195.12959,51.35581 195.46875,54.90625 C 195.62893,56.583244 196.42448,58.049773 197.625,59.09375 C 197.85376,59.307253 198.20874,59.307253 198.4375,59.09375 L 199.375,58.1875 C 199.4971,58.074787 199.56655,57.916174 199.56655,57.75 C 199.56655,57.583826 199.4971,57.425213 199.375,57.3125 C 198.62088,56.652518 198.10105,55.714144 198,54.65625 C 197.78709,52.427426 199.44435,50.490156 201.75,50.28125 C 204.05353,50.072515 206.06871,51.712605 206.28125,53.9375 C 206.41033,55.288748 205.85565,56.51317 204.875,57.34375 C 204.73828,57.452227 204.65604,57.615231 204.65002,57.789656 C 204.64401,57.964081 204.71482,58.132362 204.84375,58.25 L 205.71875,59.09375 C 205.94276,59.288486 206.27599,59.288486 206.5,59.09375 C 208.01963,57.782537 208.95296,55.812212 208.75,53.6875 C 208.42165,50.250246 205.34588,47.729812 201.84375,47.875 C 201.8096,47.876415 201.69162,47.857633 201.5,47.875 z " + id="path14737" + style="fill:url(#linearGradient14753);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient14755);stroke-width:0.58703899;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + inkscape:original="M 201.5625 48.46875 C 198.22936 48.770755 195.75529 51.627732 196.0625 54.84375 C 196.20781 56.365042 196.9411 57.708249 198.03125 58.65625 L 198.96875 57.75 C 198.10643 56.995329 197.52218 55.93237 197.40625 54.71875 C 197.16225 52.164404 199.07203 49.924478 201.6875 49.6875 C 204.30298 49.450497 206.63099 51.320657 206.875 53.875 C 207.02314 55.425824 206.36999 56.863908 205.25 57.8125 L 206.125 58.65625 C 207.51113 57.460224 208.33975 55.670994 208.15625 53.75 C 207.85863 50.634479 205.07888 48.335927 201.875 48.46875 C 201.77164 48.473032 201.66666 48.45931 201.5625 48.46875 z " + inkscape:radius="0.59534109" + sodipodi:type="inkscape:offset" + transform="translate(-33.20619,22.68276)" /> + </g> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/vadim/collection.png" + style="fill:url(#linearGradient14757);fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.3114523;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.64321606" + d="M 210.43029,105.78259 C 209.57379,105.78259 208.87863,106.48117 208.87863,107.34187 C 208.87863,107.81539 209.09312,108.23363 209.42502,108.51968 L 209.99619,108.78389 L 209.82199,114.34728 L 211.34665,114.34728 L 210.97749,108.80727 L 211.47813,108.48402 C 211.78559,108.19904 211.98194,107.79502 211.98194,107.34187 C 211.98194,106.48117 211.28677,105.78259 210.43029,105.78259 z " + id="path14739" + sodipodi:nodetypes="csccccccsc" /> + </g> + </g> + <rect + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/crystalsvg/16x16/actions/podcast_new.png" + style="fill:#b9cff9;fill-opacity:0;stroke:none;stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + id="rect11598" + width="16" + height="16" + x="88.130119" + y="356.35065" /> + </g> + <g + id="g12249" + transform="translate(-5,-5.8608646)"> + <g + inkscape:export-ydpi="90.000092" + inkscape:export-xdpi="90.000092" + inkscape:export-filename="/home/lando/Projects/Amarok/icons/22/amarok_podcast2.png" + transform="matrix(0.998349,0,0,0.998349,-183.13906,185.73529)" + id="g13209"> + <g + transform="translate(92.62357,176.413)" + id="g13211"> + <path + id="path13213" + d="M 214.55635,-8.9876506 C 213.0012,-8.9876506 211.75143,-8.4272616 211.75143,-6.7915466 L 211.75143,6.4368384 C 211.8402,7.0266884 212.4909,7.6066964 212.88141,7.7177054 L 231.75761,7.7177054 C 232.15119,7.6058244 232.95062,7.0333644 233.03661,6.4368384 L 233.03661,-6.7915466 C 233.03661,-8.4272616 231.78682,-8.9876506 230.23167,-8.9876506 L 214.55635,-8.9876506 z " + style="fill:url(#linearGradient13672);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient13674);stroke-width:0.75123984;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:nodetypes="ccccccccc" /> + <path + style="fill:url(#linearGradient13676);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient13678);stroke-width:0.98547882;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 213.8908,-4.6646566 L 230.92445,-4.6646566 C 231.46369,-4.6646566 231.89781,-4.2391996 231.89781,-3.7107166 L 231.89781,5.9163164 C 231.89781,6.4447994 231.46369,6.8702564 230.92445,6.8702564 L 213.8908,6.8702564 C 213.35155,6.8702564 212.91744,6.4447994 212.91744,5.9163164 L 212.91744,-3.7107166 C 212.91744,-4.2391996 213.35155,-4.6646566 213.8908,-4.6646566 z " + id="path13215" /> + <path + id="path13217" + d="M 213.95559,-4.6803566 L 230.84519,-4.6803566 C 231.37985,-4.6803566 231.81032,-4.2535196 231.81032,-3.7233216 L 231.81032,5.9349364 C 231.81032,6.4651324 231.37985,6.8919714 230.84519,6.8919714 L 213.95559,6.8919714 C 213.42098,6.8919714 212.9905,6.4651324 212.9905,5.9349364 L 212.9905,-3.7233216 C 212.9905,-4.2535196 213.42098,-4.6803566 213.95559,-4.6803566 z " + style="opacity:0.99578091;fill:url(#radialGradient13680);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.57956129;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" /> + <path + sodipodi:nodetypes="ccccccccc" + style="opacity:0.15189873;fill:url(#linearGradient13682);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.71897209;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 214.7548,-8.7326426 C 213.22744,-8.7326426 212,-8.1994276 212,-6.6430286 L 212,-6.4102356 C 212.08716,-5.8489876 212.72625,-5.2971026 213.10978,-5.1914766 L 231.64865,-5.1914766 C 232.03518,-5.2979336 232.82035,-5.8426356 232.90479,-6.4102356 L 232.90479,-6.6430286 C 232.90479,-8.1994276 231.67735,-8.7326426 230.14998,-8.7326426 L 214.7548,-8.7326426 z " + id="path13219" /> + </g> + <g + id="g13221" + transform="translate(94.43049,-29.83694)"> + <g + id="g13223"> + <path + id="path13225" + d="M 216.59388,201.58154 L 216.59388,213.16264" + style="opacity:0.39183673;fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#264a9e;stroke-width:0.29001534;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:nodetypes="cc" /> + <path + style="opacity:0.39183673;fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#264a9e;stroke-width:0.29001534;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 223.52993,201.58154 L 223.52993,213.17716" + id="path13227" + sodipodi:nodetypes="cc" /> + </g> + <rect + style="opacity:0.56734691;fill:#6785ed;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.28953668;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect13229" + width="18.826923" + height="3.4617176" + x="211.13826" + y="205.69659" /> + </g> + <g + transform="translate(104.5874,68.90333)" + id="g13231"> + <g + transform="matrix(0.905843,0,0,0.905843,57.41603,37.898)" + id="g13233"> + <path + transform="translate(-33.18982,22.6937)" + d="M 201.4375,47.15625 C 197.41705,47.52053 194.37229,51.014726 194.75,54.96875 C 194.92784,56.830672 195.82274,58.465378 197.15625,59.625 C 197.65641,60.091804 198.43734,60.091804 198.9375,59.625 L 199.875,58.71875 C 200.15019,58.464713 200.28125,58.104935 200.28125,57.75 C 200.28127,57.395009 200.15018,57.035274 199.875,56.78125 C 199.86458,56.781024 199.85417,56.781024 199.84375,56.78125 C 199.22065,56.235933 198.80142,55.459244 198.71875,54.59375 C 198.5432,52.756008 199.87407,51.175633 201.8125,51 C 203.74115,50.825235 205.38771,52.17032 205.5625,54 C 205.66911,55.116077 205.22291,56.089566 204.40625,56.78125 C 204.11053,57.015883 203.9502,57.381534 203.9375,57.75 C 203.92478,58.118451 204.03823,58.502493 204.34375,58.78125 L 205.21875,59.625 C 205.22917,59.625226 205.23958,59.625226 205.25,59.625 C 205.74062,60.051502 206.47813,60.051502 206.96875,59.625 C 208.64796,58.176095 209.69557,55.999456 209.46875,53.625 C 209.10312,49.797516 205.67634,46.996067 201.8125,47.15625 C 201.96122,47.150088 201.83088,47.15954 201.75,47.15625 C 201.66912,47.15296 201.56975,47.144264 201.4375,47.15625 z " + id="path13235" + style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.2;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + xlink:href="#path5359" + inkscape:original="M 201.5 47.875 C 197.85718 48.205064 195.12959 51.35581 195.46875 54.90625 C 195.62893 56.583244 196.42448 58.049773 197.625 59.09375 C 197.85376 59.307253 198.20874 59.307253 198.4375 59.09375 L 199.375 58.1875 C 199.4971 58.074787 199.5625 57.916174 199.5625 57.75 C 199.56251 57.583826 199.4971 57.425213 199.375 57.3125 C 198.62088 56.652518 198.10105 55.714144 198 54.65625 C 197.78709 52.427426 199.44435 50.490156 201.75 50.28125 C 204.05353 50.072515 206.06871 51.712605 206.28125 53.9375 C 206.41033 55.288748 205.85565 56.51317 204.875 57.34375 C 204.73828 57.452227 204.66226 57.606825 204.65625 57.78125 C 204.65023 57.955674 204.71482 58.132362 204.84375 58.25 L 205.71875 59.09375 C 205.94276 59.288486 206.27599 59.288486 206.5 59.09375 C 208.01963 57.782537 208.95296 55.812212 208.75 53.6875 C 208.42165 50.250246 205.34588 47.729812 201.84375 47.875 C 201.8096 47.876415 201.69162 47.857633 201.5 47.875 z " + inkscape:radius="0.71942902" + sodipodi:type="inkscape:offset" + inkscape:href="#path5359" /> + <path + transform="translate(-33.20619,22.68276)" + sodipodi:type="inkscape:offset" + inkscape:radius="0.59534109" + inkscape:original="M 201.5625 48.46875 C 198.22936 48.770755 195.75529 51.627732 196.0625 54.84375 C 196.20781 56.365042 196.9411 57.708249 198.03125 58.65625 L 198.96875 57.75 C 198.10643 56.995329 197.52218 55.93237 197.40625 54.71875 C 197.16225 52.164404 199.07203 49.924478 201.6875 49.6875 C 204.30298 49.450497 206.63099 51.320657 206.875 53.875 C 207.02314 55.425824 206.36999 56.863908 205.25 57.8125 L 206.125 58.65625 C 207.51113 57.460224 208.33975 55.670994 208.15625 53.75 C 207.85863 50.634479 205.07888 48.335927 201.875 48.46875 C 201.77164 48.473032 201.66666 48.45931 201.5625 48.46875 z " + style="fill:url(#linearGradient13684);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient13686);stroke-width:0.421298;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path13237" + d="M 201.5,47.875 C 197.85718,48.205064 195.12959,51.35581 195.46875,54.90625 C 195.62893,56.583244 196.42448,58.049773 197.625,59.09375 C 197.85376,59.307253 198.20874,59.307253 198.4375,59.09375 L 199.375,58.1875 C 199.4971,58.074787 199.56655,57.916174 199.56655,57.75 C 199.56655,57.583826 199.4971,57.425213 199.375,57.3125 C 198.62088,56.652518 198.10105,55.714144 198,54.65625 C 197.78709,52.427426 199.44435,50.490156 201.75,50.28125 C 204.05353,50.072515 206.06871,51.712605 206.28125,53.9375 C 206.41033,55.288748 205.85565,56.51317 204.875,57.34375 C 204.73828,57.452227 204.65604,57.615231 204.65002,57.789656 C 204.64401,57.964081 204.71482,58.132362 204.84375,58.25 L 205.71875,59.09375 C 205.94276,59.288486 206.27599,59.288486 206.5,59.09375 C 208.01963,57.782537 208.95296,55.812212 208.75,53.6875 C 208.42165,50.250246 205.34588,47.729812 201.84375,47.875 C 201.8096,47.876415 201.69162,47.857633 201.5,47.875 z " /> + </g> + <path + sodipodi:nodetypes="csccccccsc" + id="path13239" + d="M 210.43029,105.78259 C 209.57379,105.78259 208.87863,106.48117 208.87863,107.34187 C 208.87863,107.81539 209.09312,108.23363 209.42502,108.51968 L 209.99619,108.78389 L 209.82199,114.34728 L 211.34665,114.34728 L 210.97749,108.80727 L 211.47813,108.48402 C 211.78559,108.19904 211.98194,107.79502 211.98194,107.34187 C 211.98194,106.48117 211.28677,105.78259 210.43029,105.78259 z " + style="fill:url(#linearGradient13688);fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.22351876;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.64321606" + inkscape:export-filename="/home/vadim/collection.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + </g> + </g> + <rect + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/crystalsvg/22x22/actions/amarok_podcast_new.png" + y="350.22308" + x="120.35842" + height="22" + width="22" + id="rect11600" + style="fill:#b9cff9;fill-opacity:0;stroke:none;stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" /> + </g> + <g + id="g12268" + transform="translate(-5,-3.9922123)"> + <g + inkscape:export-ydpi="90.000092" + inkscape:export-xdpi="90.000092" + inkscape:export-filename="/home/lando/Projects/Amarok/icons/32/amarok_podcast2.png" + transform="matrix(0.999518,0,0,0.999518,-172.41275,178.35984)" + id="g13241"> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Playlist Types/amarok_podcast2.png" + transform="translate(89.32743,180.6989)" + id="g13243"> + <path + id="path13245" + d="M 247.4797,-16.724353 C 245.19536,-16.724353 243.35958,-15.912221 243.35958,-13.541697 L 243.35958,5.629258 C 243.48998,6.484086 244.44578,7.32465 245.01941,7.485528 L 272.7463,7.485528 C 273.32442,7.323387 274.49871,6.49376 274.625,5.629258 L 274.625,-13.541697 C 274.625,-15.912221 272.78922,-16.724353 270.50489,-16.724353 L 247.4797,-16.724353 z " + style="fill:url(#linearGradient13690);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient13692);stroke-width:0.75036138;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:nodetypes="ccccccccc" /> + <path + style="fill:url(#linearGradient13694);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient13696);stroke-width:0.926992;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 245.7032,-11.106549 L 272.28145,-11.106549 C 273.12286,-11.106549 273.80023,-10.446932 273.80023,-9.627587 L 273.80023,5.2979 C 273.80023,6.117245 273.12286,6.776862 272.28145,6.776862 L 245.7032,6.776862 C 244.86179,6.776862 244.18442,6.117245 244.18442,5.2979 L 244.18442,-9.627587 C 244.18442,-10.446932 244.86179,-11.106549 245.7032,-11.106549 z " + id="path13247" /> + <path + id="path13249" + d="M 245.66479,-10.727742 L 272.29777,-10.727742 C 273.14089,-10.727742 273.81967,-10.085626 273.81967,-9.288015 L 273.81967,5.241504 C 273.81967,6.039111 273.14089,6.681231 272.29777,6.681231 L 245.66479,6.681231 C 244.82175,6.681231 244.14295,6.039111 244.14295,5.241504 L 244.14295,-9.288015 C 244.14295,-10.085626 244.82175,-10.727742 245.66479,-10.727742 z " + style="opacity:0.99578091;fill:url(#radialGradient13698);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.57956129;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" /> + <path + sodipodi:nodetypes="ccccccccc" + style="opacity:0.15189873;fill:url(#linearGradient13700);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.71897209;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 247.74611,-16.675395 C 245.51463,-16.675395 243.72133,-15.841241 243.72133,-13.406434 L 243.72133,-13.042257 C 243.84869,-12.164248 244.78239,-11.300888 245.34273,-11.135649 L 272.42802,-11.135649 C 272.99275,-11.302188 274.13988,-12.154312 274.26325,-13.042257 L 274.26325,-13.406434 C 274.26325,-15.841241 272.46995,-16.675395 270.23846,-16.675395 L 247.74611,-16.675395 z " + id="path13251" /> + </g> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Playlist Types/amarok_podcast2.png" + id="g13253" + transform="matrix(1.584275,0,0,1.524619,-1.05882,-137.5597)"> + <g + id="g13255"> + <path + sodipodi:nodetypes="cc" + style="opacity:0.39183673;fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#264a9e;stroke-width:0.28967622;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 216.59388,201.58154 L 216.59388,213.16264" + id="path13257" /> + <path + sodipodi:nodetypes="cc" + id="path13259" + d="M 223.52993,201.58154 L 223.52993,213.17716" + style="opacity:0.39183673;fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#264a9e;stroke-width:0.28967622;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + <rect + y="205.69659" + x="211.13826" + height="3.4617176" + width="18.826923" + id="rect13261" + style="opacity:0.56734691;fill:#6785ed;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.28953668;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Playlist Types/amarok_podcast2.png" + id="g13263" + transform="matrix(1.4547,0,0,1.4547,42.20691,21.24528)"> + <g + id="g13265" + transform="matrix(0.905843,0,0,0.905843,57.41603,37.898)"> + <path + sodipodi:type="inkscape:offset" + inkscape:radius="0.71942902" + inkscape:original="M 201.5 47.875 C 197.85718 48.205064 195.12959 51.35581 195.46875 54.90625 C 195.62893 56.583244 196.42448 58.049773 197.625 59.09375 C 197.85376 59.307253 198.20874 59.307253 198.4375 59.09375 L 199.375 58.1875 C 199.4971 58.074787 199.5625 57.916174 199.5625 57.75 C 199.56251 57.583826 199.4971 57.425213 199.375 57.3125 C 198.62088 56.652518 198.10105 55.714144 198 54.65625 C 197.78709 52.427426 199.44435 50.490156 201.75 50.28125 C 204.05353 50.072515 206.06871 51.712605 206.28125 53.9375 C 206.41033 55.288748 205.85565 56.51317 204.875 57.34375 C 204.73828 57.452227 204.66226 57.606825 204.65625 57.78125 C 204.65023 57.955674 204.71482 58.132362 204.84375 58.25 L 205.71875 59.09375 C 205.94276 59.288486 206.27599 59.288486 206.5 59.09375 C 208.01963 57.782537 208.95296 55.812212 208.75 53.6875 C 208.42165 50.250246 205.34588 47.729812 201.84375 47.875 C 201.8096 47.876415 201.69162 47.857633 201.5 47.875 z " + xlink:href="#path5359" + style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.2;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path13267" + d="M 201.4375,47.15625 C 197.41705,47.52053 194.37229,51.014726 194.75,54.96875 C 194.92784,56.830672 195.82274,58.465378 197.15625,59.625 C 197.65641,60.091804 198.43734,60.091804 198.9375,59.625 L 199.875,58.71875 C 200.15019,58.464713 200.28125,58.104935 200.28125,57.75 C 200.28127,57.395009 200.15018,57.035274 199.875,56.78125 C 199.86458,56.781024 199.85417,56.781024 199.84375,56.78125 C 199.22065,56.235933 198.80142,55.459244 198.71875,54.59375 C 198.5432,52.756008 199.87407,51.175633 201.8125,51 C 203.74115,50.825235 205.38771,52.17032 205.5625,54 C 205.66911,55.116077 205.22291,56.089566 204.40625,56.78125 C 204.11053,57.015883 203.9502,57.381534 203.9375,57.75 C 203.92478,58.118451 204.03823,58.502493 204.34375,58.78125 L 205.21875,59.625 C 205.22917,59.625226 205.23958,59.625226 205.25,59.625 C 205.74062,60.051502 206.47813,60.051502 206.96875,59.625 C 208.64796,58.176095 209.69557,55.999456 209.46875,53.625 C 209.10312,49.797516 205.67634,46.996067 201.8125,47.15625 C 201.96122,47.150088 201.83088,47.15954 201.75,47.15625 C 201.66912,47.15296 201.56975,47.144264 201.4375,47.15625 z " + transform="translate(-33.18982,22.6937)" + inkscape:href="#path5359" /> + <path + d="M 201.5,47.875 C 197.85718,48.205064 195.12959,51.35581 195.46875,54.90625 C 195.62893,56.583244 196.42448,58.049773 197.625,59.09375 C 197.85376,59.307253 198.20874,59.307253 198.4375,59.09375 L 199.375,58.1875 C 199.4971,58.074787 199.56655,57.916174 199.56655,57.75 C 199.56655,57.583826 199.4971,57.425213 199.375,57.3125 C 198.62088,56.652518 198.10105,55.714144 198,54.65625 C 197.78709,52.427426 199.44435,50.490156 201.75,50.28125 C 204.05353,50.072515 206.06871,51.712605 206.28125,53.9375 C 206.41033,55.288748 205.85565,56.51317 204.875,57.34375 C 204.73828,57.452227 204.65604,57.615231 204.65002,57.789656 C 204.64401,57.964081 204.71482,58.132362 204.84375,58.25 L 205.71875,59.09375 C 205.94276,59.288486 206.27599,59.288486 206.5,59.09375 C 208.01963,57.782537 208.95296,55.812212 208.75,53.6875 C 208.42165,50.250246 205.34588,47.729812 201.84375,47.875 C 201.8096,47.876415 201.69162,47.857633 201.5,47.875 z " + id="path13269" + style="fill:url(#linearGradient13702);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient13704);stroke-width:0.20009638;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + inkscape:original="M 201.5625 48.46875 C 198.22936 48.770755 195.75529 51.627732 196.0625 54.84375 C 196.20781 56.365042 196.9411 57.708249 198.03125 58.65625 L 198.96875 57.75 C 198.10643 56.995329 197.52218 55.93237 197.40625 54.71875 C 197.16225 52.164404 199.07203 49.924478 201.6875 49.6875 C 204.30298 49.450497 206.63099 51.320657 206.875 53.875 C 207.02314 55.425824 206.36999 56.863908 205.25 57.8125 L 206.125 58.65625 C 207.51113 57.460224 208.33975 55.670994 208.15625 53.75 C 207.85863 50.634479 205.07888 48.335927 201.875 48.46875 C 201.77164 48.473032 201.66666 48.45931 201.5625 48.46875 z " + inkscape:radius="0.59534109" + sodipodi:type="inkscape:offset" + transform="translate(-33.20619,22.68276)" /> + </g> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/vadim/collection.png" + style="fill:url(#linearGradient13706);fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.22325739;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.64321606" + d="M 210.43029,105.78259 C 209.57379,105.78259 208.87863,106.48117 208.87863,107.34187 C 208.87863,107.81539 209.09312,108.23363 209.42502,108.51968 L 209.99619,108.78389 L 209.82199,114.34728 L 211.34665,114.34728 L 210.97749,108.80727 L 211.47813,108.48402 C 211.78559,108.19904 211.98194,107.79502 211.98194,107.34187 C 211.98194,106.48117 211.28677,105.78259 210.43029,105.78259 z " + id="path13271" + sodipodi:nodetypes="csccccccsc" /> + </g> + </g> + <rect + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/32x32/actions/amarok_podcast2.png" + style="fill:#b9cff9;fill-opacity:0;stroke:none;stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + id="rect11602" + width="32" + height="32" + x="159.73907" + y="338.35443" /> + </g> + <g + id="g12287" + transform="translate(-5,-10.027002)"> + <g + inkscape:export-ydpi="90.000092" + inkscape:export-xdpi="90.000092" + inkscape:export-filename="/home/lando/Projects/Amarok/icons/48/amarok_podcast2.png" + id="g14759" + transform="matrix(1.511251,0,0,1.511251,-285.2793,86.288939)"> + <g + id="g14761" + transform="translate(89.32743,180.6989)" + inkscape:export-filename="/home/lando/Projects/Amarok/Playlist Types/amarok_podcast2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <path + sodipodi:nodetypes="ccccccccc" + style="fill:url(#linearGradient14791);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient14793);stroke-width:0.4962773;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 247.4797,-16.724353 C 245.19536,-16.724353 243.35958,-15.912221 243.35958,-13.541697 L 243.35958,5.629258 C 243.48998,6.484086 244.44578,7.32465 245.01941,7.485528 L 272.7463,7.485528 C 273.32442,7.323387 274.49871,6.49376 274.625,5.629258 L 274.625,-13.541697 C 274.625,-15.912221 272.78922,-16.724353 270.50489,-16.724353 L 247.4797,-16.724353 z " + id="path14763" /> + <path + id="path14765" + d="M 245.7032,-11.106549 L 272.28145,-11.106549 C 273.12286,-11.106549 273.80023,-10.446932 273.80023,-9.627587 L 273.80023,5.2979 C 273.80023,6.117245 273.12286,6.776862 272.28145,6.776862 L 245.7032,6.776862 C 244.86179,6.776862 244.18442,6.117245 244.18442,5.2979 L 244.18442,-9.627587 C 244.18442,-10.446932 244.86179,-11.106549 245.7032,-11.106549 z " + style="fill:url(#linearGradient14795);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient14797);stroke-width:0.61309803;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" /> + <path + style="opacity:0.99578091;fill:url(#radialGradient14799);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.57956129;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 245.66479,-10.727742 L 272.29777,-10.727742 C 273.14089,-10.727742 273.81967,-10.085626 273.81967,-9.288015 L 273.81967,5.241504 C 273.81967,6.039111 273.14089,6.681231 272.29777,6.681231 L 245.66479,6.681231 C 244.82175,6.681231 244.14295,6.039111 244.14295,5.241504 L 244.14295,-9.288015 C 244.14295,-10.085626 244.82175,-10.727742 245.66479,-10.727742 z " + id="path14767" /> + <path + id="path14769" + d="M 247.74611,-16.675395 C 245.51463,-16.675395 243.72133,-15.841241 243.72133,-13.406434 L 243.72133,-13.042257 C 243.84869,-12.164248 244.78239,-11.300888 245.34273,-11.135649 L 272.42802,-11.135649 C 272.99275,-11.302188 274.13988,-12.154312 274.26325,-13.042257 L 274.26325,-13.406434 C 274.26325,-15.841241 272.46995,-16.675395 270.23846,-16.675395 L 247.74611,-16.675395 z " + style="opacity:0.15189873;fill:url(#linearGradient14801);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.71897209;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:nodetypes="ccccccccc" /> + </g> + <g + transform="matrix(1.584275,0,0,1.524619,-1.05882,-137.5597)" + id="g14771" + inkscape:export-filename="/home/lando/Projects/Amarok/Playlist Types/amarok_podcast2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <g + id="g14773"> + <path + id="path14775" + d="M 216.59388,201.58154 L 216.59388,213.16264" + style="opacity:0.39183673;fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#264a9e;stroke-width:0.19158731;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:nodetypes="cc" /> + <path + style="opacity:0.39183673;fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#264a9e;stroke-width:0.19158731;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 223.52993,201.58154 L 223.52993,213.17716" + id="path14777" + sodipodi:nodetypes="cc" /> + </g> + <rect + style="opacity:0.56734691;fill:#6785ed;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.28953668;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect14779" + width="18.826923" + height="3.4617176" + x="211.13826" + y="205.69659" /> + </g> + <g + transform="matrix(1.4547,0,0,1.4547,42.20691,21.24528)" + id="g14781" + inkscape:export-filename="/home/lando/Projects/Amarok/Playlist Types/amarok_podcast2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <g + transform="matrix(0.905843,0,0,0.905843,57.41603,37.898)" + id="g14783"> + <path + transform="translate(-33.18982,22.6937)" + d="M 201.4375,47.15625 C 197.41705,47.52053 194.37229,51.014726 194.75,54.96875 C 194.92784,56.830672 195.82274,58.465378 197.15625,59.625 C 197.65641,60.091804 198.43734,60.091804 198.9375,59.625 L 199.875,58.71875 C 200.15019,58.464713 200.28125,58.104935 200.28125,57.75 C 200.28127,57.395009 200.15018,57.035274 199.875,56.78125 C 199.86458,56.781024 199.85417,56.781024 199.84375,56.78125 C 199.22065,56.235933 198.80142,55.459244 198.71875,54.59375 C 198.5432,52.756008 199.87407,51.175633 201.8125,51 C 203.74115,50.825235 205.38771,52.17032 205.5625,54 C 205.66911,55.116077 205.22291,56.089566 204.40625,56.78125 C 204.11053,57.015883 203.9502,57.381534 203.9375,57.75 C 203.92478,58.118451 204.03823,58.502493 204.34375,58.78125 L 205.21875,59.625 C 205.22917,59.625226 205.23958,59.625226 205.25,59.625 C 205.74062,60.051502 206.47813,60.051502 206.96875,59.625 C 208.64796,58.176095 209.69557,55.999456 209.46875,53.625 C 209.10312,49.797516 205.67634,46.996067 201.8125,47.15625 C 201.96122,47.150088 201.83088,47.15954 201.75,47.15625 C 201.66912,47.15296 201.56975,47.144264 201.4375,47.15625 z " + id="path14785" + style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.2;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + xlink:href="#path5359" + inkscape:original="M 201.5 47.875 C 197.85718 48.205064 195.12959 51.35581 195.46875 54.90625 C 195.62893 56.583244 196.42448 58.049773 197.625 59.09375 C 197.85376 59.307253 198.20874 59.307253 198.4375 59.09375 L 199.375 58.1875 C 199.4971 58.074787 199.5625 57.916174 199.5625 57.75 C 199.56251 57.583826 199.4971 57.425213 199.375 57.3125 C 198.62088 56.652518 198.10105 55.714144 198 54.65625 C 197.78709 52.427426 199.44435 50.490156 201.75 50.28125 C 204.05353 50.072515 206.06871 51.712605 206.28125 53.9375 C 206.41033 55.288748 205.85565 56.51317 204.875 57.34375 C 204.73828 57.452227 204.66226 57.606825 204.65625 57.78125 C 204.65023 57.955674 204.71482 58.132362 204.84375 58.25 L 205.71875 59.09375 C 205.94276 59.288486 206.27599 59.288486 206.5 59.09375 C 208.01963 57.782537 208.95296 55.812212 208.75 53.6875 C 208.42165 50.250246 205.34588 47.729812 201.84375 47.875 C 201.8096 47.876415 201.69162 47.857633 201.5 47.875 z " + inkscape:radius="0.71942902" + sodipodi:type="inkscape:offset" + inkscape:href="#path5359" /> + <path + transform="translate(-33.20619,22.68276)" + sodipodi:type="inkscape:offset" + inkscape:radius="0.59534109" + inkscape:original="M 201.5625 48.46875 C 198.22936 48.770755 195.75529 51.627732 196.0625 54.84375 C 196.20781 56.365042 196.9411 57.708249 198.03125 58.65625 L 198.96875 57.75 C 198.10643 56.995329 197.52218 55.93237 197.40625 54.71875 C 197.16225 52.164404 199.07203 49.924478 201.6875 49.6875 C 204.30298 49.450497 206.63099 51.320657 206.875 53.875 C 207.02314 55.425824 206.36999 56.863908 205.25 57.8125 L 206.125 58.65625 C 207.51113 57.460224 208.33975 55.670994 208.15625 53.75 C 207.85863 50.634479 205.07888 48.335927 201.875 48.46875 C 201.77164 48.473032 201.66666 48.45931 201.5625 48.46875 z " + style="fill:url(#linearGradient14803);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient14805);stroke-width:0.13234062;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path14787" + d="M 201.5,47.875 C 197.85718,48.205064 195.12959,51.35581 195.46875,54.90625 C 195.62893,56.583244 196.42448,58.049773 197.625,59.09375 C 197.85376,59.307253 198.20874,59.307253 198.4375,59.09375 L 199.375,58.1875 C 199.4971,58.074787 199.56655,57.916174 199.56655,57.75 C 199.56655,57.583826 199.4971,57.425213 199.375,57.3125 C 198.62088,56.652518 198.10105,55.714144 198,54.65625 C 197.78709,52.427426 199.44435,50.490156 201.75,50.28125 C 204.05353,50.072515 206.06871,51.712605 206.28125,53.9375 C 206.41033,55.288748 205.85565,56.51317 204.875,57.34375 C 204.73828,57.452227 204.65604,57.615231 204.65002,57.789656 C 204.64401,57.964081 204.71482,58.132362 204.84375,58.25 L 205.71875,59.09375 C 205.94276,59.288486 206.27599,59.288486 206.5,59.09375 C 208.01963,57.782537 208.95296,55.812212 208.75,53.6875 C 208.42165,50.250246 205.34588,47.729812 201.84375,47.875 C 201.8096,47.876415 201.69162,47.857633 201.5,47.875 z " /> + </g> + <path + sodipodi:nodetypes="csccccccsc" + id="path14789" + d="M 210.43029,105.78259 C 209.57379,105.78259 208.87863,106.48117 208.87863,107.34187 C 208.87863,107.81539 209.09312,108.23363 209.42502,108.51968 L 209.99619,108.78389 L 209.82199,114.34728 L 211.34665,114.34728 L 210.97749,108.80727 L 211.47813,108.48402 C 211.78559,108.19904 211.98194,107.79502 211.98194,107.34187 C 211.98194,106.48117 211.28677,105.78259 210.43029,105.78259 z " + style="fill:url(#linearGradient14807);fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.14765894;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.64321606" + inkscape:export-filename="/home/vadim/collection.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + </g> + </g> + <rect + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/48x48/actions/amarok_podcast2.png" + y="328.38922" + x="217.11923" + height="48" + width="48" + id="rect11604" + style="fill:#b9cff9;fill-opacity:0;stroke:none;stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" /> + </g> + <g + id="g12306" + transform="translate(-5,-8.0269718)"> + <g + inkscape:export-ydpi="90.000092" + inkscape:export-xdpi="90.000092" + inkscape:export-filename="/home/lando/Projects/Amarok/icons/64/amarok_podcast2.png" + transform="matrix(2.023001,0,0,2.023001,-376.92497,-13.819801)" + id="g14809"> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Playlist Types/amarok_podcast2.png" + transform="translate(89.32743,180.6989)" + id="g14811"> + <path + id="path14813" + d="M 247.4797,-16.724353 C 245.19536,-16.724353 243.35958,-15.912221 243.35958,-13.541697 L 243.35958,5.629258 C 243.48998,6.484086 244.44578,7.32465 245.01941,7.485528 L 272.7463,7.485528 C 273.32442,7.323387 274.49871,6.49376 274.625,5.629258 L 274.625,-13.541697 C 274.625,-15.912221 272.78922,-16.724353 270.50489,-16.724353 L 247.4797,-16.724353 z " + style="fill:url(#linearGradient14841);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient14843);stroke-width:0.3707363;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:nodetypes="ccccccccc" /> + <path + style="fill:url(#linearGradient14845);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient14847);stroke-width:0.45800543;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 245.7032,-11.106549 L 272.28145,-11.106549 C 273.12286,-11.106549 273.80023,-10.446932 273.80023,-9.627587 L 273.80023,5.2979 C 273.80023,6.117245 273.12286,6.776862 272.28145,6.776862 L 245.7032,6.776862 C 244.86179,6.776862 244.18442,6.117245 244.18442,5.2979 L 244.18442,-9.627587 C 244.18442,-10.446932 244.86179,-11.106549 245.7032,-11.106549 z " + id="path14815" /> + <path + id="path14817" + d="M 245.66479,-10.727742 L 272.29777,-10.727742 C 273.14089,-10.727742 273.81967,-10.085626 273.81967,-9.288015 L 273.81967,5.241504 C 273.81967,6.039111 273.14089,6.681231 272.29777,6.681231 L 245.66479,6.681231 C 244.82175,6.681231 244.14295,6.039111 244.14295,5.241504 L 244.14295,-9.288015 C 244.14295,-10.085626 244.82175,-10.727742 245.66479,-10.727742 z " + style="opacity:0.99578091;fill:url(#radialGradient14849);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.57956129;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" /> + <path + sodipodi:nodetypes="ccccccccc" + style="opacity:0.15189873;fill:url(#linearGradient14851);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.71897209;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 247.74611,-16.675395 C 245.51463,-16.675395 243.72133,-15.841241 243.72133,-13.406434 L 243.72133,-13.042257 C 243.84869,-12.164248 244.78239,-11.300888 245.34273,-11.135649 L 272.42802,-11.135649 C 272.99275,-11.302188 274.13988,-12.154312 274.26325,-13.042257 L 274.26325,-13.406434 C 274.26325,-15.841241 272.46995,-16.675395 270.23846,-16.675395 L 247.74611,-16.675395 z " + id="path14819" /> + </g> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Playlist Types/amarok_podcast2.png" + id="g14821" + transform="matrix(1.584275,0,0,1.524619,-1.05882,-137.5597)"> + <g + id="g14823"> + <path + sodipodi:nodetypes="cc" + style="opacity:0.39183673;fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#264a9e;stroke-width:0.14312236;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 216.59388,201.58154 L 216.59388,213.16264" + id="path14825" /> + <path + sodipodi:nodetypes="cc" + id="path14827" + d="M 223.52993,201.58154 L 223.52993,213.17716" + style="opacity:0.39183673;fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#264a9e;stroke-width:0.14312236;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + <rect + y="205.69659" + x="211.13826" + height="3.4617176" + width="18.826923" + id="rect14829" + style="opacity:0.56734691;fill:#6785ed;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.28953668;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Playlist Types/amarok_podcast2.png" + id="g14831" + transform="matrix(1.4547,0,0,1.4547,42.20691,21.24528)"> + <g + id="g14833" + transform="matrix(0.905843,0,0,0.905843,57.41603,37.898)"> + <path + sodipodi:type="inkscape:offset" + inkscape:radius="0.71942902" + inkscape:original="M 201.5 47.875 C 197.85718 48.205064 195.12959 51.35581 195.46875 54.90625 C 195.62893 56.583244 196.42448 58.049773 197.625 59.09375 C 197.85376 59.307253 198.20874 59.307253 198.4375 59.09375 L 199.375 58.1875 C 199.4971 58.074787 199.5625 57.916174 199.5625 57.75 C 199.56251 57.583826 199.4971 57.425213 199.375 57.3125 C 198.62088 56.652518 198.10105 55.714144 198 54.65625 C 197.78709 52.427426 199.44435 50.490156 201.75 50.28125 C 204.05353 50.072515 206.06871 51.712605 206.28125 53.9375 C 206.41033 55.288748 205.85565 56.51317 204.875 57.34375 C 204.73828 57.452227 204.66226 57.606825 204.65625 57.78125 C 204.65023 57.955674 204.71482 58.132362 204.84375 58.25 L 205.71875 59.09375 C 205.94276 59.288486 206.27599 59.288486 206.5 59.09375 C 208.01963 57.782537 208.95296 55.812212 208.75 53.6875 C 208.42165 50.250246 205.34588 47.729812 201.84375 47.875 C 201.8096 47.876415 201.69162 47.857633 201.5 47.875 z " + xlink:href="#path5359" + style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.2;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path14835" + d="M 201.4375,47.15625 C 197.41705,47.52053 194.37229,51.014726 194.75,54.96875 C 194.92784,56.830672 195.82274,58.465378 197.15625,59.625 C 197.65641,60.091804 198.43734,60.091804 198.9375,59.625 L 199.875,58.71875 C 200.15019,58.464713 200.28125,58.104935 200.28125,57.75 C 200.28127,57.395009 200.15018,57.035274 199.875,56.78125 C 199.86458,56.781024 199.85417,56.781024 199.84375,56.78125 C 199.22065,56.235933 198.80142,55.459244 198.71875,54.59375 C 198.5432,52.756008 199.87407,51.175633 201.8125,51 C 203.74115,50.825235 205.38771,52.17032 205.5625,54 C 205.66911,55.116077 205.22291,56.089566 204.40625,56.78125 C 204.11053,57.015883 203.9502,57.381534 203.9375,57.75 C 203.92478,58.118451 204.03823,58.502493 204.34375,58.78125 L 205.21875,59.625 C 205.22917,59.625226 205.23958,59.625226 205.25,59.625 C 205.74062,60.051502 206.47813,60.051502 206.96875,59.625 C 208.64796,58.176095 209.69557,55.999456 209.46875,53.625 C 209.10312,49.797516 205.67634,46.996067 201.8125,47.15625 C 201.96122,47.150088 201.83088,47.15954 201.75,47.15625 C 201.66912,47.15296 201.56975,47.144264 201.4375,47.15625 z " + transform="translate(-33.18982,22.6937)" + inkscape:href="#path5359" /> + <path + d="M 201.5,47.875 C 197.85718,48.205064 195.12959,51.35581 195.46875,54.90625 C 195.62893,56.583244 196.42448,58.049773 197.625,59.09375 C 197.85376,59.307253 198.20874,59.307253 198.4375,59.09375 L 199.375,58.1875 C 199.4971,58.074787 199.56655,57.916174 199.56655,57.75 C 199.56655,57.583826 199.4971,57.425213 199.375,57.3125 C 198.62088,56.652518 198.10105,55.714144 198,54.65625 C 197.78709,52.427426 199.44435,50.490156 201.75,50.28125 C 204.05353,50.072515 206.06871,51.712605 206.28125,53.9375 C 206.41033,55.288748 205.85565,56.51317 204.875,57.34375 C 204.73828,57.452227 204.65604,57.615231 204.65002,57.789656 C 204.64401,57.964081 204.71482,58.132362 204.84375,58.25 L 205.71875,59.09375 C 205.94276,59.288486 206.27599,59.288486 206.5,59.09375 C 208.01963,57.782537 208.95296,55.812212 208.75,53.6875 C 208.42165,50.250246 205.34588,47.729812 201.84375,47.875 C 201.8096,47.876415 201.69162,47.857633 201.5,47.875 z " + id="path14837" + style="fill:url(#linearGradient14853);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient14855);stroke-width:0.09886302;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + inkscape:original="M 201.5625 48.46875 C 198.22936 48.770755 195.75529 51.627732 196.0625 54.84375 C 196.20781 56.365042 196.9411 57.708249 198.03125 58.65625 L 198.96875 57.75 C 198.10643 56.995329 197.52218 55.93237 197.40625 54.71875 C 197.16225 52.164404 199.07203 49.924478 201.6875 49.6875 C 204.30298 49.450497 206.63099 51.320657 206.875 53.875 C 207.02314 55.425824 206.36999 56.863908 205.25 57.8125 L 206.125 58.65625 C 207.51113 57.460224 208.33975 55.670994 208.15625 53.75 C 207.85863 50.634479 205.07888 48.335927 201.875 48.46875 C 201.77164 48.473032 201.66666 48.45931 201.5625 48.46875 z " + inkscape:radius="0.59534109" + sodipodi:type="inkscape:offset" + transform="translate(-33.20619,22.68276)" /> + </g> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/vadim/collection.png" + style="fill:url(#linearGradient14857);fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.11030634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.64321606" + d="M 210.43029,105.78259 C 209.57379,105.78259 208.87863,106.48117 208.87863,107.34187 C 208.87863,107.81539 209.09312,108.23363 209.42502,108.51968 L 209.99619,108.78389 L 209.82199,114.34728 L 211.34665,114.34728 L 210.97749,108.80727 L 211.47813,108.48402 C 211.78559,108.19904 211.98194,107.79502 211.98194,107.34187 C 211.98194,106.48117 211.28677,105.78259 210.43029,105.78259 z " + id="path14839" + sodipodi:nodetypes="csccccccsc" /> + </g> + </g> + <rect + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/crystalsvg/64x64/actions/podcast_new.png" + style="fill:#b9cff9;fill-opacity:0;stroke:none;stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + id="rect11606" + width="64" + height="64" + x="295.72617" + y="310.38919" /> + </g> + <g + id="g12403" + transform="translate(-5,-30.852808)"> + <g + inkscape:export-ydpi="90.000092" + inkscape:export-xdpi="90.000092" + inkscape:export-filename="/home/lando/Projects/Amarok/icons/16/amarok_remove_from_playlist.png" + id="g14859" + transform="matrix(0.716482,0,0,0.716482,-129.59427,304.11228)"> + <g + id="g14861" + transform="translate(91.95751,330.3549)"> + <path + sodipodi:nodetypes="ccccccccc" + style="fill:url(#linearGradient14883);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient14885);stroke-width:1.04678178;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 214.55635,-8.9876506 C 213.0012,-8.9876506 211.75143,-8.4272616 211.75143,-6.7915466 L 211.75143,6.4368384 C 211.8402,7.0266884 212.4909,7.6066964 212.88141,7.7177054 L 231.75761,7.7177054 C 232.15119,7.6058244 232.95062,7.0333644 233.03661,6.4368384 L 233.03661,-6.7915466 C 233.03661,-8.4272616 231.78682,-8.9876506 230.23167,-8.9876506 L 214.55635,-8.9876506 z " + id="path14863" /> + <path + id="path14865" + d="M 213.8908,-4.6646566 L 230.92445,-4.6646566 C 231.46369,-4.6646566 231.89781,-4.2391996 231.89781,-3.7107166 L 231.89781,5.9163164 C 231.89781,6.4447994 231.46369,6.8702564 230.92445,6.8702564 L 213.8908,6.8702564 C 213.35155,6.8702564 212.91744,6.4447994 212.91744,5.9163164 L 212.91744,-3.7107166 C 212.91744,-4.2391996 213.35155,-4.6646566 213.8908,-4.6646566 z " + style="fill:url(#linearGradient14887);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient14890);stroke-width:1.37317157;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + style="opacity:0.99578091;fill:url(#radialGradient14892);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.57956129;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 213.95559,-4.6803566 L 230.84519,-4.6803566 C 231.37985,-4.6803566 231.81032,-4.2535196 231.81032,-3.7233216 L 231.81032,5.9349364 C 231.81032,6.4651324 231.37985,6.8919714 230.84519,6.8919714 L 213.95559,6.8919714 C 213.42098,6.8919714 212.9905,6.4651324 212.9905,5.9349364 L 212.9905,-3.7233216 C 212.9905,-4.2535196 213.42098,-4.6803566 213.95559,-4.6803566 z " + id="path14867" /> + <path + id="path14869" + d="M 214.7548,-8.7326426 C 213.22744,-8.7326426 212,-8.1994276 212,-6.6430286 L 212,-6.4102356 C 212.08716,-5.8489876 212.72625,-5.2971026 213.10978,-5.1914766 L 231.64865,-5.1914766 C 232.03518,-5.2979336 232.82035,-5.8426356 232.90479,-6.4102356 L 232.90479,-6.6430286 C 232.90479,-8.1994276 231.67735,-8.7326426 230.14998,-8.7326426 L 214.7548,-8.7326426 z " + style="opacity:0.15189873;fill:url(#linearGradient14894);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.71897209;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:nodetypes="ccccccccc" /> + </g> + <g + id="g14871" + transform="translate(93.82725,124.0854)"> + <g + id="g14873"> + <path + id="path14875" + d="M 216.59388,201.58154 L 216.59388,213.16264" + style="opacity:0.39183673;fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#264a9e;stroke-width:0.404109;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:nodetypes="cc" /> + <path + style="opacity:0.39183673;fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#264a9e;stroke-width:0.404109;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 223.52993,201.58154 L 223.52993,213.17716" + id="path14877" + sodipodi:nodetypes="cc" /> + </g> + <rect + style="opacity:0.56734691;fill:#6785ed;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.28953668;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect14879" + width="18.826923" + height="3.4617176" + x="211.13826" + y="205.69659" /> + </g> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_add_lyrics.png" + sodipodi:nodetypes="ccccccccccccc" + style="fill:url(#linearGradient14896);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient14898);stroke-width:0.84588069;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + d="M 318.92904,333.9686 L 316.41716,331.46544 L 318.87685,328.96099 L 316.81528,326.90659 L 314.35559,329.41106 L 311.85731,326.95752 L 309.77403,329.04806 L 312.27231,331.50161 L 309.78632,333.9601 L 311.8479,336.0145 L 314.33388,333.556 L 316.84577,336.05915 L 318.92904,333.9686 z " + id="path14881" /> + </g> + <rect + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/16x16/actions/amarok_remove_from_playlist.png" + y="532.35065" + x="87.632942" + height="16" + width="16" + id="rect11666" + style="fill:#b9cff9;fill-opacity:0;stroke:none;stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" /> + </g> + <g + id="g12418" + transform="translate(-5,-32.725244)"> + <g + inkscape:export-ydpi="90.000092" + inkscape:export-xdpi="90.000092" + inkscape:export-filename="/home/lando/Projects/Amarok/icons/22/amarok_remove_from_playlist.png" + transform="matrix(0.998349,0,0,0.998349,-188.45287,210.04755)" + id="g13608"> + <g + transform="translate(91.95751,330.3549)" + id="g13610"> + <path + id="path13612" + d="M 214.55635,-8.9876506 C 213.0012,-8.9876506 211.75143,-8.4272616 211.75143,-6.7915466 L 211.75143,6.4368384 C 211.8402,7.0266884 212.4909,7.6066964 212.88141,7.7177054 L 231.75761,7.7177054 C 232.15119,7.6058244 232.95062,7.0333644 233.03661,6.4368384 L 233.03661,-6.7915466 C 233.03661,-8.4272616 231.78682,-8.9876506 230.23167,-8.9876506 L 214.55635,-8.9876506 z " + style="fill:url(#linearGradient13920);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient13922);stroke-width:0.75123984;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:nodetypes="ccccccccc" /> + <path + style="fill:url(#linearGradient13924);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient13926);stroke-width:0.98547882;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 213.8908,-4.6646566 L 230.92445,-4.6646566 C 231.46369,-4.6646566 231.89781,-4.2391996 231.89781,-3.7107166 L 231.89781,5.9163164 C 231.89781,6.4447994 231.46369,6.8702564 230.92445,6.8702564 L 213.8908,6.8702564 C 213.35155,6.8702564 212.91744,6.4447994 212.91744,5.9163164 L 212.91744,-3.7107166 C 212.91744,-4.2391996 213.35155,-4.6646566 213.8908,-4.6646566 z " + id="path13614" /> + <path + id="path13616" + d="M 213.95559,-4.6803566 L 230.84519,-4.6803566 C 231.37985,-4.6803566 231.81032,-4.2535196 231.81032,-3.7233216 L 231.81032,5.9349364 C 231.81032,6.4651324 231.37985,6.8919714 230.84519,6.8919714 L 213.95559,6.8919714 C 213.42098,6.8919714 212.9905,6.4651324 212.9905,5.9349364 L 212.9905,-3.7233216 C 212.9905,-4.2535196 213.42098,-4.6803566 213.95559,-4.6803566 z " + style="opacity:0.99578091;fill:url(#radialGradient13928);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.57956129;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" /> + <path + sodipodi:nodetypes="ccccccccc" + style="opacity:0.15189873;fill:url(#linearGradient13930);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.71897209;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 214.7548,-8.7326426 C 213.22744,-8.7326426 212,-8.1994276 212,-6.6430286 L 212,-6.4102356 C 212.08716,-5.8489876 212.72625,-5.2971026 213.10978,-5.1914766 L 231.64865,-5.1914766 C 232.03518,-5.2979336 232.82035,-5.8426356 232.90479,-6.4102356 L 232.90479,-6.6430286 C 232.90479,-8.1994276 231.67735,-8.7326426 230.14998,-8.7326426 L 214.7548,-8.7326426 z " + id="path13618" /> + </g> + <g + transform="translate(93.82725,124.0854)" + id="g13620"> + <g + id="g13622"> + <path + sodipodi:nodetypes="cc" + style="opacity:0.39183673;fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#264a9e;stroke-width:0.29001534;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 216.59388,201.58154 L 216.59388,213.16264" + id="path13624" /> + <path + sodipodi:nodetypes="cc" + id="path13626" + d="M 223.52993,201.58154 L 223.52993,213.17716" + style="opacity:0.39183673;fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#264a9e;stroke-width:0.29001534;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + <rect + y="205.69659" + x="211.13826" + height="3.4617176" + width="18.826923" + id="rect13628" + style="opacity:0.56734691;fill:#6785ed;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.28953668;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + <path + id="path13630" + d="M 318.92904,333.9686 L 316.41716,331.46544 L 318.87685,328.96099 L 316.81528,326.90659 L 314.35559,329.41106 L 311.85731,326.95752 L 309.77403,329.04806 L 312.27231,331.50161 L 309.78632,333.9601 L 311.8479,336.0145 L 314.33388,333.556 L 316.84577,336.05915 L 318.92904,333.9686 z " + style="fill:url(#linearGradient13932);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient13934);stroke-width:0.6070599;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + sodipodi:nodetypes="ccccccccccccc" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_add_lyrics.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + </g> + <rect + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/22x22/actions/amarok_remove_from_playlist.png" + style="fill:#b9cff9;fill-opacity:0;stroke:none;stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + id="rect11668" + width="22" + height="22" + x="114.37966" + y="528.22308" /> + </g> + <g + id="g12433" + transform="translate(-5,-35.704004)"> + <g + inkscape:export-ydpi="90.000092" + inkscape:export-xdpi="90.000092" + inkscape:export-filename="/home/lando/Projects/Amarok/icons/32/amarok_remove_from_playlist.png" + transform="matrix(0.999518,0,0,0.999518,-173.03541,-563.71477)" + id="g13973"> + <g + transform="translate(79.2766,1106.067)" + id="g13585"> + <path + id="path13587" + d="M 247.4797,-16.724353 C 245.19536,-16.724353 243.35958,-15.912221 243.35958,-13.541697 L 243.35958,5.629258 C 243.48998,6.484086 244.44578,7.32465 245.01941,7.485528 L 272.7463,7.485528 C 273.32442,7.323387 274.49871,6.49376 274.625,5.629258 L 274.625,-13.541697 C 274.625,-15.912221 272.78922,-16.724353 270.50489,-16.724353 L 247.4797,-16.724353 z " + style="fill:url(#linearGradient13904);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient13906);stroke-width:0.75036138;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:nodetypes="ccccccccc" /> + <path + style="fill:url(#linearGradient13908);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient13910);stroke-width:0.926992;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 245.7032,-11.106549 L 272.28145,-11.106549 C 273.12286,-11.106549 273.80023,-10.446932 273.80023,-9.627587 L 273.80023,5.2979 C 273.80023,6.117245 273.12286,6.776862 272.28145,6.776862 L 245.7032,6.776862 C 244.86179,6.776862 244.18442,6.117245 244.18442,5.2979 L 244.18442,-9.627587 C 244.18442,-10.446932 244.86179,-11.106549 245.7032,-11.106549 z " + id="path13589" /> + <path + id="path13591" + d="M 245.66479,-10.727742 L 272.29777,-10.727742 C 273.14089,-10.727742 273.81967,-10.085626 273.81967,-9.288015 L 273.81967,5.241504 C 273.81967,6.039111 273.14089,6.681231 272.29777,6.681231 L 245.66479,6.681231 C 244.82175,6.681231 244.14295,6.039111 244.14295,5.241504 L 244.14295,-9.288015 C 244.14295,-10.085626 244.82175,-10.727742 245.66479,-10.727742 z " + style="opacity:0.99578091;fill:url(#radialGradient13912);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.57956129;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" /> + <path + sodipodi:nodetypes="ccccccccc" + style="opacity:0.15189873;fill:url(#linearGradient13914);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.71897209;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 247.74611,-16.675395 C 245.51463,-16.675395 243.72133,-15.841241 243.72133,-13.406434 L 243.72133,-13.042257 C 243.84869,-12.164248 244.78239,-11.300888 245.34273,-11.135649 L 272.42802,-11.135649 C 272.99275,-11.302188 274.13988,-12.154312 274.26325,-13.042257 L 274.26325,-13.406434 C 274.26325,-15.841241 272.46995,-16.675395 270.23846,-16.675395 L 247.74611,-16.675395 z " + id="path13593" /> + </g> + <g + transform="matrix(1.584275,0,0,1.524619,-11.2611,787.9135)" + id="g13595"> + <g + id="g13597"> + <path + id="path13600" + d="M 216.59388,201.58154 L 216.59388,213.16264" + style="opacity:0.39183673;fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#264a9e;stroke-width:0.28967622;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:nodetypes="cc" /> + <path + style="opacity:0.39183673;fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#264a9e;stroke-width:0.28967622;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 223.52993,201.58154 L 223.52993,213.17716" + id="path13602" + sodipodi:nodetypes="cc" /> + </g> + <rect + style="opacity:0.56734691;fill:#6785ed;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.28953668;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect13604" + width="18.826923" + height="3.4617176" + x="211.13826" + y="205.69659" /> + </g> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_add_lyrics.png" + sodipodi:nodetypes="ccccccccccccc" + style="fill:url(#linearGradient13916);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient13918);stroke-width:0.60635024;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + d="M 345.23467,1107.8779 L 341.41222,1104.0687 L 345.15525,1100.2575 L 342.01805,1097.1312 L 338.27502,1100.9424 L 334.47325,1097.2087 L 331.30303,1100.39 L 335.10479,1104.1237 L 331.32173,1107.8649 L 334.45893,1110.9912 L 338.24198,1107.25 L 342.06445,1111.0592 L 345.23467,1107.8779 z " + id="path13606" /> + </g> + <rect + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/32x32/actions/amarok_remove_from_playlist.png" + y="521.20184" + x="149.07043" + height="32" + width="32" + id="rect11670" + style="fill:#b9cff9;fill-opacity:0;stroke:none;stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" /> + </g> + <g + id="g12448" + transform="translate(-5,-42.027002)"> + <g + inkscape:export-ydpi="90.000092" + inkscape:export-xdpi="90.000092" + inkscape:export-filename="/home/lando/Projects/Amarok/icons/48/amarok_remove_from_playlist.png" + id="g14900" + transform="matrix(1.511251,0,0,1.511251,-273.35371,-1129.039)"> + <g + id="g14902" + transform="translate(79.2766,1106.067)"> + <path + sodipodi:nodetypes="ccccccccc" + style="fill:url(#linearGradient14924);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient14926);stroke-width:0.4962773;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 247.4797,-16.724353 C 245.19536,-16.724353 243.35958,-15.912221 243.35958,-13.541697 L 243.35958,5.629258 C 243.48998,6.484086 244.44578,7.32465 245.01941,7.485528 L 272.7463,7.485528 C 273.32442,7.323387 274.49871,6.49376 274.625,5.629258 L 274.625,-13.541697 C 274.625,-15.912221 272.78922,-16.724353 270.50489,-16.724353 L 247.4797,-16.724353 z " + id="path14904" /> + <path + id="path14906" + d="M 245.7032,-11.106549 L 272.28145,-11.106549 C 273.12286,-11.106549 273.80023,-10.446932 273.80023,-9.627587 L 273.80023,5.2979 C 273.80023,6.117245 273.12286,6.776862 272.28145,6.776862 L 245.7032,6.776862 C 244.86179,6.776862 244.18442,6.117245 244.18442,5.2979 L 244.18442,-9.627587 C 244.18442,-10.446932 244.86179,-11.106549 245.7032,-11.106549 z " + style="fill:url(#linearGradient14928);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient14930);stroke-width:0.61309803;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" /> + <path + style="opacity:0.99578091;fill:url(#radialGradient14932);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.57956129;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 245.66479,-10.727742 L 272.29777,-10.727742 C 273.14089,-10.727742 273.81967,-10.085626 273.81967,-9.288015 L 273.81967,5.241504 C 273.81967,6.039111 273.14089,6.681231 272.29777,6.681231 L 245.66479,6.681231 C 244.82175,6.681231 244.14295,6.039111 244.14295,5.241504 L 244.14295,-9.288015 C 244.14295,-10.085626 244.82175,-10.727742 245.66479,-10.727742 z " + id="path14908" /> + <path + id="path14910" + d="M 247.74611,-16.675395 C 245.51463,-16.675395 243.72133,-15.841241 243.72133,-13.406434 L 243.72133,-13.042257 C 243.84869,-12.164248 244.78239,-11.300888 245.34273,-11.135649 L 272.42802,-11.135649 C 272.99275,-11.302188 274.13988,-12.154312 274.26325,-13.042257 L 274.26325,-13.406434 C 274.26325,-15.841241 272.46995,-16.675395 270.23846,-16.675395 L 247.74611,-16.675395 z " + style="opacity:0.15189873;fill:url(#linearGradient14934);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.71897209;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:nodetypes="ccccccccc" /> + </g> + <g + id="g14912" + transform="matrix(1.584275,0,0,1.524619,-11.2611,787.9135)"> + <g + id="g14914"> + <path + sodipodi:nodetypes="cc" + style="opacity:0.39183673;fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#264a9e;stroke-width:0.19158731;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 216.59388,201.58154 L 216.59388,213.16264" + id="path14916" /> + <path + sodipodi:nodetypes="cc" + id="path14918" + d="M 223.52993,201.58154 L 223.52993,213.17716" + style="opacity:0.39183673;fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#264a9e;stroke-width:0.19158731;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + <rect + y="205.69659" + x="211.13826" + height="3.4617176" + width="18.826923" + id="rect14920" + style="opacity:0.56734691;fill:#6785ed;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.28953668;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + <path + id="path14922" + d="M 345.23467,1107.8779 L 341.41222,1104.0687 L 345.15525,1100.2575 L 342.01805,1097.1312 L 338.27502,1100.9424 L 334.47325,1097.2087 L 331.30303,1100.39 L 335.10479,1104.1237 L 331.32173,1107.8649 L 334.45893,1110.9912 L 338.24198,1107.25 L 342.06445,1111.0592 L 345.23467,1107.8779 z " + style="fill:url(#linearGradient14936);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient14938);stroke-width:0.40103057;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + sodipodi:nodetypes="ccccccccccccc" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_add_lyrics.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + </g> + <rect + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/48x48/actions/amarok_remove_from_playlist.png" + style="fill:#b9cff9;fill-opacity:0;stroke:none;stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + id="rect11672" + width="48" + height="48" + x="213.8555" + y="511.52484" /> + </g> + <g + id="g12464" + transform="translate(-5,-35.89126)"> + <g + inkscape:export-ydpi="90.000092" + inkscape:export-xdpi="90.000092" + inkscape:export-filename="/home/lando/Projects/Amarok/icons/64/amarok_remove_from_playlist.png" + transform="matrix(2.023001,0,0,2.023001,-358.09591,-1706.8405)" + id="g14940"> + <g + transform="translate(79.2766,1106.067)" + id="g14942"> + <path + id="path14944" + d="M 247.4797,-16.724353 C 245.19536,-16.724353 243.35958,-15.912221 243.35958,-13.541697 L 243.35958,5.629258 C 243.48998,6.484086 244.44578,7.32465 245.01941,7.485528 L 272.7463,7.485528 C 273.32442,7.323387 274.49871,6.49376 274.625,5.629258 L 274.625,-13.541697 C 274.625,-15.912221 272.78922,-16.724353 270.50489,-16.724353 L 247.4797,-16.724353 z " + style="fill:url(#linearGradient14964);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient14966);stroke-width:0.3707363;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:nodetypes="ccccccccc" /> + <path + style="fill:url(#linearGradient14968);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient14970);stroke-width:0.45800543;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 245.7032,-11.106549 L 272.28145,-11.106549 C 273.12286,-11.106549 273.80023,-10.446932 273.80023,-9.627587 L 273.80023,5.2979 C 273.80023,6.117245 273.12286,6.776862 272.28145,6.776862 L 245.7032,6.776862 C 244.86179,6.776862 244.18442,6.117245 244.18442,5.2979 L 244.18442,-9.627587 C 244.18442,-10.446932 244.86179,-11.106549 245.7032,-11.106549 z " + id="path14946" /> + <path + id="path14948" + d="M 245.66479,-10.727742 L 272.29777,-10.727742 C 273.14089,-10.727742 273.81967,-10.085626 273.81967,-9.288015 L 273.81967,5.241504 C 273.81967,6.039111 273.14089,6.681231 272.29777,6.681231 L 245.66479,6.681231 C 244.82175,6.681231 244.14295,6.039111 244.14295,5.241504 L 244.14295,-9.288015 C 244.14295,-10.085626 244.82175,-10.727742 245.66479,-10.727742 z " + style="opacity:0.99578091;fill:url(#radialGradient14972);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.57956129;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" /> + <path + sodipodi:nodetypes="ccccccccc" + style="opacity:0.15189873;fill:url(#linearGradient14974);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.71897209;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 247.74611,-16.675395 C 245.51463,-16.675395 243.72133,-15.841241 243.72133,-13.406434 L 243.72133,-13.042257 C 243.84869,-12.164248 244.78239,-11.300888 245.34273,-11.135649 L 272.42802,-11.135649 C 272.99275,-11.302188 274.13988,-12.154312 274.26325,-13.042257 L 274.26325,-13.406434 C 274.26325,-15.841241 272.46995,-16.675395 270.23846,-16.675395 L 247.74611,-16.675395 z " + id="path14950" /> + </g> + <g + transform="matrix(1.584275,0,0,1.524619,-11.2611,787.9135)" + id="g14952"> + <g + id="g14954"> + <path + id="path14956" + d="M 216.59388,201.58154 L 216.59388,213.16264" + style="opacity:0.39183673;fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#264a9e;stroke-width:0.14312236;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:nodetypes="cc" /> + <path + style="opacity:0.39183673;fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#264a9e;stroke-width:0.14312236;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 223.52993,201.58154 L 223.52993,213.17716" + id="path14958" + sodipodi:nodetypes="cc" /> + </g> + <rect + style="opacity:0.56734691;fill:#6785ed;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.28953668;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect14960" + width="18.826923" + height="3.4617176" + x="211.13826" + y="205.69659" /> + </g> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_add_lyrics.png" + sodipodi:nodetypes="ccccccccccccc" + style="fill:url(#linearGradient14976);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient14978);stroke-width:0.2995837;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + d="M 345.23467,1107.8779 L 341.41222,1104.0687 L 345.15525,1100.2575 L 342.01805,1097.1312 L 338.27502,1100.9424 L 334.47325,1097.2087 L 331.30303,1100.39 L 335.10479,1104.1237 L 331.32173,1107.8649 L 334.45893,1110.9912 L 338.24198,1107.25 L 342.06445,1111.0592 L 345.23467,1107.8779 z " + id="path14962" /> + </g> + <rect + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/64x64/actions/amarok_remove_from_playlist.png" + y="489.3891" + x="294.22241" + height="64" + width="64" + id="rect11674" + style="fill:#b9cff9;fill-opacity:0;stroke:none;stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" /> + </g> + <g + id="g12479" + transform="translate(-5,-1.9885502)"> + <g + inkscape:export-ydpi="90.000092" + inkscape:export-xdpi="90.000092" + inkscape:export-filename="/home/lando/Projects/Amarok/icons/16/amarok_repeat_playlist.png" + transform="matrix(0.716482,0,0,0.716482,-131.40191,320.76592)" + id="g13499"> + <g + transform="translate(100.2158,393.6452)" + id="g13501"> + <path + id="path13503" + d="M 214.55635,-8.9876506 C 213.0012,-8.9876506 211.75143,-8.4272616 211.75143,-6.7915466 L 211.75143,6.4368384 C 211.8402,7.0266884 212.4909,7.6066964 212.88141,7.7177054 L 231.75761,7.7177054 C 232.15119,7.6058244 232.95062,7.0333644 233.03661,6.4368384 L 233.03661,-6.7915466 C 233.03661,-8.4272616 231.78682,-8.9876506 230.23167,-8.9876506 L 214.55635,-8.9876506 z " + style="fill:url(#linearGradient13852);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient13854);stroke-width:1.04678142;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:nodetypes="ccccccccc" /> + <path + style="fill:url(#linearGradient13856);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient13858);stroke-width:1.37317109;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 213.8908,-4.6646566 L 230.92445,-4.6646566 C 231.46369,-4.6646566 231.89781,-4.2391996 231.89781,-3.7107166 L 231.89781,5.9163164 C 231.89781,6.4447994 231.46369,6.8702564 230.92445,6.8702564 L 213.8908,6.8702564 C 213.35155,6.8702564 212.91744,6.4447994 212.91744,5.9163164 L 212.91744,-3.7107166 C 212.91744,-4.2391996 213.35155,-4.6646566 213.8908,-4.6646566 z " + id="path13505" /> + <path + id="path13507" + d="M 213.95559,-4.6803566 L 230.84519,-4.6803566 C 231.37985,-4.6803566 231.81032,-4.2535196 231.81032,-3.7233216 L 231.81032,5.9349364 C 231.81032,6.4651324 231.37985,6.8919714 230.84519,6.8919714 L 213.95559,6.8919714 C 213.42098,6.8919714 212.9905,6.4651324 212.9905,5.9349364 L 212.9905,-3.7233216 C 212.9905,-4.2535196 213.42098,-4.6803566 213.95559,-4.6803566 z " + style="opacity:0.99578091;fill:url(#radialGradient13860);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.57956129;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" /> + <path + sodipodi:nodetypes="ccccccccc" + style="opacity:0.15189873;fill:url(#linearGradient13862);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.71897209;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 214.7548,-8.7326426 C 213.22744,-8.7326426 212,-8.1994276 212,-6.6430286 L 212,-6.4102356 C 212.08716,-5.8489876 212.72625,-5.2971026 213.10978,-5.1914766 L 231.64865,-5.1914766 C 232.03518,-5.2979336 232.82035,-5.8426356 232.90479,-6.4102356 L 232.90479,-6.6430286 C 232.90479,-8.1994276 231.67735,-8.7326426 230.14998,-8.7326426 L 214.7548,-8.7326426 z " + id="path13509" /> + </g> + <g + id="g13511" + transform="translate(102.0827,187.4194)"> + <g + id="g13513"> + <path + id="path13515" + d="M 216.59388,201.58154 L 216.59388,213.16264" + style="opacity:0.39183673;fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#264a9e;stroke-width:0.40410882;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:nodetypes="cc" /> + <path + style="opacity:0.39183673;fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#264a9e;stroke-width:0.40410882;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 223.52993,201.58154 L 223.52993,213.17716" + id="path13517" + sodipodi:nodetypes="cc" /> + </g> + <rect + style="opacity:0.56734691;fill:#6785ed;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.28953668;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect13519" + width="18.826923" + height="3.4617176" + x="211.13826" + y="205.69659" /> + </g> + <path + transform="translate(75.64107,144.7133)" + d="M 243.1875,240.90625 C 243.18465,240.91647 243.18465,240.92728 243.1875,240.9375 C 243.17728,240.93465 243.16647,240.93465 243.15625,240.9375 C 243.1534,240.94772 243.1534,240.95853 243.15625,240.96875 C 243.1534,240.97897 243.1534,240.98978 243.15625,241 L 243.625,242.40625 C 243.09526,242.69566 242.61543,243.07207 242.1875,243.5 C 240.96113,244.72637 240.15625,246.43752 240.15625,248.3125 C 240.15625,250.18755 240.96485,251.8711 242.1875,253.09375 C 242.79779,253.70404 243.52677,254.21675 244.34375,254.5625 C 244.7612,254.73916 245.19771,254.84998 245.625,254.9375 C 245.63522,254.94035 245.64603,254.94035 245.65625,254.9375 C 245.6591,254.92728 245.6591,254.91647 245.65625,254.90625 C 245.66647,254.9091 245.67728,254.9091 245.6875,254.90625 C 245.69035,254.89603 245.69035,254.88522 245.6875,254.875 C 245.69035,254.86478 245.69035,254.85397 245.6875,254.84375 L 245.15625,254.03125 L 245.9375,253.40625 C 245.94772,253.4091 245.95853,253.4091 245.96875,253.40625 C 245.9716,253.39603 245.9716,253.38522 245.96875,253.375 C 245.9716,253.36478 245.9716,253.35397 245.96875,253.34375 C 245.95853,253.3409 245.94772,253.3409 245.9375,253.34375 C 245.94035,253.33353 245.94035,253.32272 245.9375,253.3125 C 245.92728,253.30965 245.91647,253.30965 245.90625,253.3125 C 245.55494,253.24054 245.26013,253.15456 244.96875,253.03125 C 244.35578,252.77185 243.80819,252.40194 243.34375,251.9375 C 242.41709,251.01084 241.875,249.73395 241.875,248.3125 C 241.875,246.89106 242.41709,245.61416 243.34375,244.6875 C 243.60351,244.42774 243.91272,244.23412 244.21875,244.03125 L 244.65625,245.28125 C 244.66647,245.2841 244.67728,245.2841 244.6875,245.28125 C 244.68465,245.29147 244.68465,245.30228 244.6875,245.3125 C 244.69772,245.31535 244.70853,245.31535 244.71875,245.3125 C 244.72897,245.31535 244.73978,245.31535 244.75,245.3125 C 244.75285,245.30228 244.75285,245.29147 244.75,245.28125 L 247.96875,242.375 C 247.97897,242.37785 247.98978,242.37785 248,242.375 C 248.00285,242.36478 248.00285,242.35397 248,242.34375 C 248.00285,242.33353 248.00285,242.32272 248,242.3125 C 247.98978,242.30965 247.97897,242.30965 247.96875,242.3125 C 247.9716,242.30228 247.9716,242.29147 247.96875,242.28125 L 243.25,240.90625 C 243.23978,240.9034 243.22897,240.9034 243.21875,240.90625 C 243.20853,240.9034 243.19772,240.9034 243.1875,240.90625 L 243.1875,240.90625 z M 248.28125,241.65625 C 248.2784,241.66647 248.2784,241.67728 248.28125,241.6875 C 248.27103,241.68465 248.26022,241.68465 248.25,241.6875 C 248.24715,241.69772 248.24715,241.70853 248.25,241.71875 C 248.24715,241.72897 248.24715,241.73978 248.25,241.75 L 248.8125,242.65625 L 248,243.15625 C 247.99715,243.16647 247.99715,243.17728 248,243.1875 C 247.98978,243.18465 247.97897,243.18465 247.96875,243.1875 C 247.9659,243.19772 247.9659,243.20853 247.96875,243.21875 C 247.9659,243.22897 247.9659,243.23978 247.96875,243.25 C 247.97897,243.25285 247.98978,243.25285 248,243.25 C 247.99715,243.26022 247.99715,243.27103 248,243.28125 C 248.01022,243.2841 248.02103,243.2841 248.03125,243.28125 C 248.3522,243.34698 248.64989,243.45881 248.96875,243.59375 C 249.58172,243.85315 250.12931,244.22306 250.59375,244.6875 C 251.52041,245.61417 252.0625,246.89106 252.0625,248.3125 C 252.0625,249.73395 251.52041,251.01084 250.59375,251.9375 C 250.35259,252.17866 250.0869,252.36977 249.8125,252.5625 L 249.375,251.3125 C 249.36478,251.30965 249.35397,251.30965 249.34375,251.3125 C 249.3466,251.30228 249.3466,251.29147 249.34375,251.28125 C 249.33353,251.2784 249.32272,251.2784 249.3125,251.28125 C 249.30228,251.2784 249.29147,251.2784 249.28125,251.28125 C 249.2784,251.29147 249.2784,251.30228 249.28125,251.3125 L 246.0625,254.21875 C 246.05228,254.2159 246.04147,254.2159 246.03125,254.21875 C 246.0284,254.22897 246.0284,254.23978 246.03125,254.25 C 246.0284,254.26022 246.0284,254.27103 246.03125,254.28125 C 246.04147,254.2841 246.05228,254.2841 246.0625,254.28125 C 246.05965,254.29147 246.05965,254.30228 246.0625,254.3125 L 250.78125,255.6875 C 250.79147,255.69035 250.80228,255.69035 250.8125,255.6875 C 250.82272,255.69035 250.83353,255.69035 250.84375,255.6875 C 250.8466,255.67728 250.8466,255.66647 250.84375,255.65625 C 250.85397,255.6591 250.86478,255.6591 250.875,255.65625 C 250.87785,255.64603 250.87785,255.63522 250.875,255.625 C 250.87785,255.61478 250.87785,255.60397 250.875,255.59375 L 250.375,254.15625 C 250.89239,253.86563 251.36726,253.50774 251.78125,253.09375 C 253.00769,251.86731 253.78125,250.18059 253.78125,248.3125 C 253.78125,246.44448 253.0114,244.73015 251.78125,243.5 C 251.15593,242.87468 250.39275,242.40064 249.59375,242.0625 C 249.20155,241.89653 248.76767,241.74947 248.3125,241.65625 C 248.30228,241.6534 248.29147,241.6534 248.28125,241.65625 z " + id="path13521" + style="fill:#0368ff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + inkscape:original="M 243.21875 240.96875 L 243.71875 242.4375 C 243.17057 242.73054 242.65817 243.09183 242.21875 243.53125 C 241.00279 244.74721 240.21875 246.45388 240.21875 248.3125 C 240.21875 250.17112 241.00618 251.84993 242.21875 253.0625 C 242.82334 253.66709 243.56461 254.15704 244.375 254.5 C 244.78796 254.67476 245.20018 254.78799 245.625 254.875 L 245.09375 254.03125 L 245.90625 253.375 C 245.55308 253.30266 245.23415 253.21929 244.9375 253.09375 C 244.31638 252.8309 243.78109 252.43734 243.3125 251.96875 C 242.37531 251.03156 241.8125 249.74915 241.8125 248.3125 C 241.8125 246.87586 242.37531 245.59344 243.3125 244.65625 C 243.59658 244.37217 243.91263 244.12278 244.25 243.90625 L 244.71875 245.25 L 247.9375 242.34375 L 243.21875 240.96875 z M 248.3125 241.71875 L 248.90625 242.6875 L 248.03125 243.21875 C 248.35712 243.28549 248.67866 243.39526 249 243.53125 C 249.62112 243.7941 250.15641 244.18766 250.625 244.65625 C 251.56219 245.59345 252.125 246.87586 252.125 248.3125 C 252.125 249.74915 251.56219 251.03156 250.625 251.96875 C 250.36142 252.23233 250.09045 252.48131 249.78125 252.6875 L 249.3125 251.34375 L 246.09375 254.25 L 250.8125 255.625 L 250.28125 254.125 C 250.818 253.82813 251.32527 253.48723 251.75 253.0625 C 252.96596 251.84654 253.71875 250.16483 253.71875 248.3125 C 253.71875 246.46017 252.96935 244.7506 251.75 243.53125 C 251.13185 242.9131 250.35677 242.46114 249.5625 242.125 C 249.1722 241.95983 248.7624 241.81089 248.3125 241.71875 z " + inkscape:radius="0.058185551" + sodipodi:type="inkscape:offset" /> + </g> + <rect + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/16x16/actions/amarok_repeat_playlist.png" + style="fill:#b9cff9;fill-opacity:0;stroke:none;stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + id="rect11676" + width="16" + height="16" + x="91.74221" + y="594.35065" /> + </g> + <g + id="g12494" + transform="translate(-5,-3.8609867)"> + <g + inkscape:export-ydpi="90.000092" + inkscape:export-xdpi="90.000092" + inkscape:export-filename="/home/lando/Projects/Amarok/icons/22/amarok_repeat_playlist.png" + id="g14980" + transform="matrix(0.998349,0,0,0.998349,-188.90826,208.86174)"> + <g + id="g14982" + transform="translate(100.2158,393.6452)"> + <path + sodipodi:nodetypes="ccccccccc" + style="fill:url(#linearGradient15004);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient15006);stroke-width:0.75123984;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 214.55635,-8.9876506 C 213.0012,-8.9876506 211.75143,-8.4272616 211.75143,-6.7915466 L 211.75143,6.4368384 C 211.8402,7.0266884 212.4909,7.6066964 212.88141,7.7177054 L 231.75761,7.7177054 C 232.15119,7.6058244 232.95062,7.0333644 233.03661,6.4368384 L 233.03661,-6.7915466 C 233.03661,-8.4272616 231.78682,-8.9876506 230.23167,-8.9876506 L 214.55635,-8.9876506 z " + id="path14984" /> + <path + id="path14986" + d="M 213.8908,-4.6646566 L 230.92445,-4.6646566 C 231.46369,-4.6646566 231.89781,-4.2391996 231.89781,-3.7107166 L 231.89781,5.9163164 C 231.89781,6.4447994 231.46369,6.8702564 230.92445,6.8702564 L 213.8908,6.8702564 C 213.35155,6.8702564 212.91744,6.4447994 212.91744,5.9163164 L 212.91744,-3.7107166 C 212.91744,-4.2391996 213.35155,-4.6646566 213.8908,-4.6646566 z " + style="fill:url(#linearGradient15008);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient15010);stroke-width:0.98547882;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + style="opacity:0.99578091;fill:url(#radialGradient15012);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.57956129;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 213.95559,-4.6803566 L 230.84519,-4.6803566 C 231.37985,-4.6803566 231.81032,-4.2535196 231.81032,-3.7233216 L 231.81032,5.9349364 C 231.81032,6.4651324 231.37985,6.8919714 230.84519,6.8919714 L 213.95559,6.8919714 C 213.42098,6.8919714 212.9905,6.4651324 212.9905,5.9349364 L 212.9905,-3.7233216 C 212.9905,-4.2535196 213.42098,-4.6803566 213.95559,-4.6803566 z " + id="path14988" /> + <path + id="path14990" + d="M 214.7548,-8.7326426 C 213.22744,-8.7326426 212,-8.1994276 212,-6.6430286 L 212,-6.4102356 C 212.08716,-5.8489876 212.72625,-5.2971026 213.10978,-5.1914766 L 231.64865,-5.1914766 C 232.03518,-5.2979336 232.82035,-5.8426356 232.90479,-6.4102356 L 232.90479,-6.6430286 C 232.90479,-8.1994276 231.67735,-8.7326426 230.14998,-8.7326426 L 214.7548,-8.7326426 z " + style="opacity:0.15189873;fill:url(#linearGradient15014);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.71897209;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:nodetypes="ccccccccc" /> + </g> + <g + transform="translate(102.0827,187.4194)" + id="g14992"> + <g + id="g14994"> + <path + sodipodi:nodetypes="cc" + style="opacity:0.39183673;fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#264a9e;stroke-width:0.29001534;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 216.59388,201.58154 L 216.59388,213.16264" + id="path14996" /> + <path + sodipodi:nodetypes="cc" + id="path14998" + d="M 223.52993,201.58154 L 223.52993,213.17716" + style="opacity:0.39183673;fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#264a9e;stroke-width:0.29001534;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + <rect + y="205.69659" + x="211.13826" + height="3.4617176" + width="18.826923" + id="rect15000" + style="opacity:0.56734691;fill:#6785ed;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.28953668;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + <path + sodipodi:type="inkscape:offset" + inkscape:radius="0.058185551" + inkscape:original="M 243.21875 240.96875 L 243.71875 242.4375 C 243.17057 242.73054 242.65817 243.09183 242.21875 243.53125 C 241.00279 244.74721 240.21875 246.45388 240.21875 248.3125 C 240.21875 250.17112 241.00618 251.84993 242.21875 253.0625 C 242.82334 253.66709 243.56461 254.15704 244.375 254.5 C 244.78796 254.67476 245.20018 254.78799 245.625 254.875 L 245.09375 254.03125 L 245.90625 253.375 C 245.55308 253.30266 245.23415 253.21929 244.9375 253.09375 C 244.31638 252.8309 243.78109 252.43734 243.3125 251.96875 C 242.37531 251.03156 241.8125 249.74915 241.8125 248.3125 C 241.8125 246.87586 242.37531 245.59344 243.3125 244.65625 C 243.59658 244.37217 243.91263 244.12278 244.25 243.90625 L 244.71875 245.25 L 247.9375 242.34375 L 243.21875 240.96875 z M 248.3125 241.71875 L 248.90625 242.6875 L 248.03125 243.21875 C 248.35712 243.28549 248.67866 243.39526 249 243.53125 C 249.62112 243.7941 250.15641 244.18766 250.625 244.65625 C 251.56219 245.59345 252.125 246.87586 252.125 248.3125 C 252.125 249.74915 251.56219 251.03156 250.625 251.96875 C 250.36142 252.23233 250.09045 252.48131 249.78125 252.6875 L 249.3125 251.34375 L 246.09375 254.25 L 250.8125 255.625 L 250.28125 254.125 C 250.818 253.82813 251.32527 253.48723 251.75 253.0625 C 252.96596 251.84654 253.71875 250.16483 253.71875 248.3125 C 253.71875 246.46017 252.96935 244.7506 251.75 243.53125 C 251.13185 242.9131 250.35677 242.46114 249.5625 242.125 C 249.1722 241.95983 248.7624 241.81089 248.3125 241.71875 z " + style="fill:#0368ff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="path15002" + d="M 243.1875,240.90625 C 243.18465,240.91647 243.18465,240.92728 243.1875,240.9375 C 243.17728,240.93465 243.16647,240.93465 243.15625,240.9375 C 243.1534,240.94772 243.1534,240.95853 243.15625,240.96875 C 243.1534,240.97897 243.1534,240.98978 243.15625,241 L 243.625,242.40625 C 243.09526,242.69566 242.61543,243.07207 242.1875,243.5 C 240.96113,244.72637 240.15625,246.43752 240.15625,248.3125 C 240.15625,250.18755 240.96485,251.8711 242.1875,253.09375 C 242.79779,253.70404 243.52677,254.21675 244.34375,254.5625 C 244.7612,254.73916 245.19771,254.84998 245.625,254.9375 C 245.63522,254.94035 245.64603,254.94035 245.65625,254.9375 C 245.6591,254.92728 245.6591,254.91647 245.65625,254.90625 C 245.66647,254.9091 245.67728,254.9091 245.6875,254.90625 C 245.69035,254.89603 245.69035,254.88522 245.6875,254.875 C 245.69035,254.86478 245.69035,254.85397 245.6875,254.84375 L 245.15625,254.03125 L 245.9375,253.40625 C 245.94772,253.4091 245.95853,253.4091 245.96875,253.40625 C 245.9716,253.39603 245.9716,253.38522 245.96875,253.375 C 245.9716,253.36478 245.9716,253.35397 245.96875,253.34375 C 245.95853,253.3409 245.94772,253.3409 245.9375,253.34375 C 245.94035,253.33353 245.94035,253.32272 245.9375,253.3125 C 245.92728,253.30965 245.91647,253.30965 245.90625,253.3125 C 245.55494,253.24054 245.26013,253.15456 244.96875,253.03125 C 244.35578,252.77185 243.80819,252.40194 243.34375,251.9375 C 242.41709,251.01084 241.875,249.73395 241.875,248.3125 C 241.875,246.89106 242.41709,245.61416 243.34375,244.6875 C 243.60351,244.42774 243.91272,244.23412 244.21875,244.03125 L 244.65625,245.28125 C 244.66647,245.2841 244.67728,245.2841 244.6875,245.28125 C 244.68465,245.29147 244.68465,245.30228 244.6875,245.3125 C 244.69772,245.31535 244.70853,245.31535 244.71875,245.3125 C 244.72897,245.31535 244.73978,245.31535 244.75,245.3125 C 244.75285,245.30228 244.75285,245.29147 244.75,245.28125 L 247.96875,242.375 C 247.97897,242.37785 247.98978,242.37785 248,242.375 C 248.00285,242.36478 248.00285,242.35397 248,242.34375 C 248.00285,242.33353 248.00285,242.32272 248,242.3125 C 247.98978,242.30965 247.97897,242.30965 247.96875,242.3125 C 247.9716,242.30228 247.9716,242.29147 247.96875,242.28125 L 243.25,240.90625 C 243.23978,240.9034 243.22897,240.9034 243.21875,240.90625 C 243.20853,240.9034 243.19772,240.9034 243.1875,240.90625 L 243.1875,240.90625 z M 248.28125,241.65625 C 248.2784,241.66647 248.2784,241.67728 248.28125,241.6875 C 248.27103,241.68465 248.26022,241.68465 248.25,241.6875 C 248.24715,241.69772 248.24715,241.70853 248.25,241.71875 C 248.24715,241.72897 248.24715,241.73978 248.25,241.75 L 248.8125,242.65625 L 248,243.15625 C 247.99715,243.16647 247.99715,243.17728 248,243.1875 C 247.98978,243.18465 247.97897,243.18465 247.96875,243.1875 C 247.9659,243.19772 247.9659,243.20853 247.96875,243.21875 C 247.9659,243.22897 247.9659,243.23978 247.96875,243.25 C 247.97897,243.25285 247.98978,243.25285 248,243.25 C 247.99715,243.26022 247.99715,243.27103 248,243.28125 C 248.01022,243.2841 248.02103,243.2841 248.03125,243.28125 C 248.3522,243.34698 248.64989,243.45881 248.96875,243.59375 C 249.58172,243.85315 250.12931,244.22306 250.59375,244.6875 C 251.52041,245.61417 252.0625,246.89106 252.0625,248.3125 C 252.0625,249.73395 251.52041,251.01084 250.59375,251.9375 C 250.35259,252.17866 250.0869,252.36977 249.8125,252.5625 L 249.375,251.3125 C 249.36478,251.30965 249.35397,251.30965 249.34375,251.3125 C 249.3466,251.30228 249.3466,251.29147 249.34375,251.28125 C 249.33353,251.2784 249.32272,251.2784 249.3125,251.28125 C 249.30228,251.2784 249.29147,251.2784 249.28125,251.28125 C 249.2784,251.29147 249.2784,251.30228 249.28125,251.3125 L 246.0625,254.21875 C 246.05228,254.2159 246.04147,254.2159 246.03125,254.21875 C 246.0284,254.22897 246.0284,254.23978 246.03125,254.25 C 246.0284,254.26022 246.0284,254.27103 246.03125,254.28125 C 246.04147,254.2841 246.05228,254.2841 246.0625,254.28125 C 246.05965,254.29147 246.05965,254.30228 246.0625,254.3125 L 250.78125,255.6875 C 250.79147,255.69035 250.80228,255.69035 250.8125,255.6875 C 250.82272,255.69035 250.83353,255.69035 250.84375,255.6875 C 250.8466,255.67728 250.8466,255.66647 250.84375,255.65625 C 250.85397,255.6591 250.86478,255.6591 250.875,255.65625 C 250.87785,255.64603 250.87785,255.63522 250.875,255.625 C 250.87785,255.61478 250.87785,255.60397 250.875,255.59375 L 250.375,254.15625 C 250.89239,253.86563 251.36726,253.50774 251.78125,253.09375 C 253.00769,251.86731 253.78125,250.18059 253.78125,248.3125 C 253.78125,246.44448 253.0114,244.73015 251.78125,243.5 C 251.15593,242.87468 250.39275,242.40064 249.59375,242.0625 C 249.20155,241.89653 248.76767,241.74947 248.3125,241.65625 C 248.30228,241.6534 248.29147,241.6534 248.28125,241.65625 z " + transform="translate(75.64107,144.7133)" /> + </g> + <rect + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/22x22/actions/amarok_repeat_playlist.png" + y="590.22308" + x="122.16894" + height="22" + width="22" + id="rect11678" + style="fill:#b9cff9;fill-opacity:0;stroke:none;stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" /> + </g> + <g + id="g12509" + transform="translate(-5,-3.8397464)"> + <g + inkscape:export-ydpi="90.000092" + inkscape:export-xdpi="90.000092" + inkscape:export-filename="/home/lando/Projects/Amarok/icons/32/amarok_repeat_playlist.png" + transform="matrix(0.999518,0,0,0.999518,-163.23021,-562.60191)" + id="g13986"> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Playback-modes/amarok_repeat_playlist.png" + id="g13479" + transform="translate(81.91681,1163.982)"> + <path + sodipodi:nodetypes="ccccccccc" + style="fill:url(#linearGradient15016);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient15018);stroke-width:0.75036138;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 247.4797,-16.724353 C 245.19536,-16.724353 243.35958,-15.912221 243.35958,-13.541697 L 243.35958,5.629258 C 243.48998,6.484086 244.44578,7.32465 245.01941,7.485528 L 272.7463,7.485528 C 273.32442,7.323387 274.49871,6.49376 274.625,5.629258 L 274.625,-13.541697 C 274.625,-15.912221 272.78922,-16.724353 270.50489,-16.724353 L 247.4797,-16.724353 z " + id="path13481" /> + <path + id="path13483" + d="M 245.7032,-11.106549 L 272.28145,-11.106549 C 273.12286,-11.106549 273.80023,-10.446932 273.80023,-9.627587 L 273.80023,5.2979 C 273.80023,6.117245 273.12286,6.776862 272.28145,6.776862 L 245.7032,6.776862 C 244.86179,6.776862 244.18442,6.117245 244.18442,5.2979 L 244.18442,-9.627587 C 244.18442,-10.446932 244.86179,-11.106549 245.7032,-11.106549 z " + style="fill:url(#linearGradient15020);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient15022);stroke-width:0.926992;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" /> + <path + style="opacity:0.99578091;fill:url(#radialGradient15024);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.57956129;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 245.66479,-10.727742 L 272.29777,-10.727742 C 273.14089,-10.727742 273.81967,-10.085626 273.81967,-9.288015 L 273.81967,5.241504 C 273.81967,6.039111 273.14089,6.681231 272.29777,6.681231 L 245.66479,6.681231 C 244.82175,6.681231 244.14295,6.039111 244.14295,5.241504 L 244.14295,-9.288015 C 244.14295,-10.085626 244.82175,-10.727742 245.66479,-10.727742 z " + id="path13485" /> + <path + id="path13487" + d="M 247.74611,-16.675395 C 245.51463,-16.675395 243.72133,-15.841241 243.72133,-13.406434 L 243.72133,-13.042257 C 243.84869,-12.164248 244.78239,-11.300888 245.34273,-11.135649 L 272.42802,-11.135649 C 272.99275,-11.302188 274.13988,-12.154312 274.26325,-13.042257 L 274.26325,-13.406434 C 274.26325,-15.841241 272.46995,-16.675395 270.23846,-16.675395 L 247.74611,-16.675395 z " + style="opacity:0.15189873;fill:url(#linearGradient15026);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.71897209;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:nodetypes="ccccccccc" /> + </g> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Playback-modes/amarok_repeat_playlist.png" + id="g13489" + transform="matrix(1.584275,0,0,1.524619,-8.668287,846.0872)"> + <g + id="g13491"> + <path + sodipodi:nodetypes="cc" + style="opacity:0.39183673;fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#264a9e;stroke-width:0.28967622;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 216.59388,201.58154 L 216.59388,213.16264" + id="path13493" /> + <path + sodipodi:nodetypes="cc" + id="path13495" + d="M 223.52993,201.58154 L 223.52993,213.17716" + style="opacity:0.39183673;fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#264a9e;stroke-width:0.28967622;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + <rect + y="205.69659" + x="211.13826" + height="3.4617176" + width="18.826923" + id="rect13497" + style="opacity:0.56734691;fill:#6785ed;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.28953668;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Playback-modes/amarok_repeat_playlist.png" + d="M 243.1875,240.90625 C 243.18465,240.91647 243.18465,240.92728 243.1875,240.9375 C 243.17728,240.93465 243.16647,240.93465 243.15625,240.9375 C 243.1534,240.94772 243.1534,240.95853 243.15625,240.96875 C 243.1534,240.97897 243.1534,240.98978 243.15625,241 L 243.625,242.40625 C 243.09526,242.69566 242.61543,243.07207 242.1875,243.5 C 240.96113,244.72637 240.15625,246.43752 240.15625,248.3125 C 240.15625,250.18755 240.96485,251.8711 242.1875,253.09375 C 242.79779,253.70404 243.52677,254.21675 244.34375,254.5625 C 244.7612,254.73916 245.19771,254.84998 245.625,254.9375 C 245.63522,254.94035 245.64603,254.94035 245.65625,254.9375 C 245.6591,254.92728 245.6591,254.91647 245.65625,254.90625 C 245.66647,254.9091 245.67728,254.9091 245.6875,254.90625 C 245.69035,254.89603 245.69035,254.88522 245.6875,254.875 C 245.69035,254.86478 245.69035,254.85397 245.6875,254.84375 L 245.15625,254.03125 L 245.9375,253.40625 C 245.94772,253.4091 245.95853,253.4091 245.96875,253.40625 C 245.9716,253.39603 245.9716,253.38522 245.96875,253.375 C 245.9716,253.36478 245.9716,253.35397 245.96875,253.34375 C 245.95853,253.3409 245.94772,253.3409 245.9375,253.34375 C 245.94035,253.33353 245.94035,253.32272 245.9375,253.3125 C 245.92728,253.30965 245.91647,253.30965 245.90625,253.3125 C 245.55494,253.24054 245.26013,253.15456 244.96875,253.03125 C 244.35578,252.77185 243.80819,252.40194 243.34375,251.9375 C 242.41709,251.01084 241.875,249.73395 241.875,248.3125 C 241.875,246.89106 242.41709,245.61416 243.34375,244.6875 C 243.60351,244.42774 243.91272,244.23412 244.21875,244.03125 L 244.65625,245.28125 C 244.66647,245.2841 244.67728,245.2841 244.6875,245.28125 C 244.68465,245.29147 244.68465,245.30228 244.6875,245.3125 C 244.69772,245.31535 244.70853,245.31535 244.71875,245.3125 C 244.72897,245.31535 244.73978,245.31535 244.75,245.3125 C 244.75285,245.30228 244.75285,245.29147 244.75,245.28125 L 247.96875,242.375 C 247.97897,242.37785 247.98978,242.37785 248,242.375 C 248.00285,242.36478 248.00285,242.35397 248,242.34375 C 248.00285,242.33353 248.00285,242.32272 248,242.3125 C 247.98978,242.30965 247.97897,242.30965 247.96875,242.3125 C 247.9716,242.30228 247.9716,242.29147 247.96875,242.28125 L 243.25,240.90625 C 243.23978,240.9034 243.22897,240.9034 243.21875,240.90625 C 243.20853,240.9034 243.19772,240.9034 243.1875,240.90625 L 243.1875,240.90625 z M 248.28125,241.65625 C 248.2784,241.66647 248.2784,241.67728 248.28125,241.6875 C 248.27103,241.68465 248.26022,241.68465 248.25,241.6875 C 248.24715,241.69772 248.24715,241.70853 248.25,241.71875 C 248.24715,241.72897 248.24715,241.73978 248.25,241.75 L 248.8125,242.65625 L 248,243.15625 C 247.99715,243.16647 247.99715,243.17728 248,243.1875 C 247.98978,243.18465 247.97897,243.18465 247.96875,243.1875 C 247.9659,243.19772 247.9659,243.20853 247.96875,243.21875 C 247.9659,243.22897 247.9659,243.23978 247.96875,243.25 C 247.97897,243.25285 247.98978,243.25285 248,243.25 C 247.99715,243.26022 247.99715,243.27103 248,243.28125 C 248.01022,243.2841 248.02103,243.2841 248.03125,243.28125 C 248.3522,243.34698 248.64989,243.45881 248.96875,243.59375 C 249.58172,243.85315 250.12931,244.22306 250.59375,244.6875 C 251.52041,245.61417 252.0625,246.89106 252.0625,248.3125 C 252.0625,249.73395 251.52041,251.01084 250.59375,251.9375 C 250.35259,252.17866 250.0869,252.36977 249.8125,252.5625 L 249.375,251.3125 C 249.36478,251.30965 249.35397,251.30965 249.34375,251.3125 C 249.3466,251.30228 249.3466,251.29147 249.34375,251.28125 C 249.33353,251.2784 249.32272,251.2784 249.3125,251.28125 C 249.30228,251.2784 249.29147,251.2784 249.28125,251.28125 C 249.2784,251.29147 249.2784,251.30228 249.28125,251.3125 L 246.0625,254.21875 C 246.05228,254.2159 246.04147,254.2159 246.03125,254.21875 C 246.0284,254.22897 246.0284,254.23978 246.03125,254.25 C 246.0284,254.26022 246.0284,254.27103 246.03125,254.28125 C 246.04147,254.2841 246.05228,254.2841 246.0625,254.28125 C 246.05965,254.29147 246.05965,254.30228 246.0625,254.3125 L 250.78125,255.6875 C 250.79147,255.69035 250.80228,255.69035 250.8125,255.6875 C 250.82272,255.69035 250.83353,255.69035 250.84375,255.6875 C 250.8466,255.67728 250.8466,255.66647 250.84375,255.65625 C 250.85397,255.6591 250.86478,255.6591 250.875,255.65625 C 250.87785,255.64603 250.87785,255.63522 250.875,255.625 C 250.87785,255.61478 250.87785,255.60397 250.875,255.59375 L 250.375,254.15625 C 250.89239,253.86563 251.36726,253.50774 251.78125,253.09375 C 253.00769,251.86731 253.78125,250.18059 253.78125,248.3125 C 253.78125,246.44448 253.0114,244.73015 251.78125,243.5 C 251.15593,242.87468 250.39275,242.40064 249.59375,242.0625 C 249.20155,241.89653 248.76767,241.74947 248.3125,241.65625 C 248.30228,241.6534 248.29147,241.6534 248.28125,241.65625 z " + id="path13523" + style="fill:#0368ff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:3.75;stroke-miterlimit:4;stroke-dashoffset:0;stroke-opacity:1" + inkscape:original="M 243.21875 240.96875 L 243.71875 242.4375 C 243.17057 242.73054 242.65817 243.09183 242.21875 243.53125 C 241.00279 244.74721 240.21875 246.45388 240.21875 248.3125 C 240.21875 250.17112 241.00618 251.84993 242.21875 253.0625 C 242.82334 253.66709 243.56461 254.15704 244.375 254.5 C 244.78796 254.67476 245.20018 254.78799 245.625 254.875 L 245.09375 254.03125 L 245.90625 253.375 C 245.55308 253.30266 245.23415 253.21929 244.9375 253.09375 C 244.31638 252.8309 243.78109 252.43734 243.3125 251.96875 C 242.37531 251.03156 241.8125 249.74915 241.8125 248.3125 C 241.8125 246.87586 242.37531 245.59344 243.3125 244.65625 C 243.59658 244.37217 243.91263 244.12278 244.25 243.90625 L 244.71875 245.25 L 247.9375 242.34375 L 243.21875 240.96875 z M 248.3125 241.71875 L 248.90625 242.6875 L 248.03125 243.21875 C 248.35712 243.28549 248.67866 243.39526 249 243.53125 C 249.62112 243.7941 250.15641 244.18766 250.625 244.65625 C 251.56219 245.59345 252.125 246.87586 252.125 248.3125 C 252.125 249.74915 251.56219 251.03156 250.625 251.96875 C 250.36142 252.23233 250.09045 252.48131 249.78125 252.6875 L 249.3125 251.34375 L 246.09375 254.25 L 250.8125 255.625 L 250.28125 254.125 C 250.818 253.82813 251.32527 253.48723 251.75 253.0625 C 252.96596 251.84654 253.71875 250.16483 253.71875 248.3125 C 253.71875 246.46017 252.96935 244.7506 251.75 243.53125 C 251.13185 242.9131 250.35677 242.46114 249.5625 242.125 C 249.1722 241.95983 248.7624 241.81089 248.3125 241.71875 z " + inkscape:radius="0.058185551" + sodipodi:type="inkscape:offset" + transform="matrix(1.454545,0,0,1.454545,-18.31809,798.2032)" /> + </g> + <rect + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/32x32/actions/amarok_repeat_playlist.png" + style="fill:#b9cff9;fill-opacity:0;stroke:none;stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + id="rect11680" + width="32" + height="32" + x="161.51459" + y="580.20184" /> + </g> + <g + id="g12524" + transform="translate(-5,-7.1627445)"> + <g + inkscape:export-ydpi="90.000092" + inkscape:export-xdpi="90.000092" + inkscape:export-filename="/home/lando/Projects/Amarok/icons/48/amarok_repeat_playlist.png" + id="g15028" + transform="matrix(1.511251,0,0,1.511251,-272.19698,-1160.563)"> + <g + transform="translate(81.91681,1163.982)" + id="g15030" + inkscape:export-filename="/home/lando/Projects/Amarok/Playback-modes/amarok_repeat_playlist.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <path + id="path15032" + d="M 247.4797,-16.724353 C 245.19536,-16.724353 243.35958,-15.912221 243.35958,-13.541697 L 243.35958,5.629258 C 243.48998,6.484086 244.44578,7.32465 245.01941,7.485528 L 272.7463,7.485528 C 273.32442,7.323387 274.49871,6.49376 274.625,5.629258 L 274.625,-13.541697 C 274.625,-15.912221 272.78922,-16.724353 270.50489,-16.724353 L 247.4797,-16.724353 z " + style="fill:url(#linearGradient15052);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient15054);stroke-width:0.4962773;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:nodetypes="ccccccccc" /> + <path + style="fill:url(#linearGradient15056);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient15058);stroke-width:0.61309803;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 245.7032,-11.106549 L 272.28145,-11.106549 C 273.12286,-11.106549 273.80023,-10.446932 273.80023,-9.627587 L 273.80023,5.2979 C 273.80023,6.117245 273.12286,6.776862 272.28145,6.776862 L 245.7032,6.776862 C 244.86179,6.776862 244.18442,6.117245 244.18442,5.2979 L 244.18442,-9.627587 C 244.18442,-10.446932 244.86179,-11.106549 245.7032,-11.106549 z " + id="path15034" /> + <path + id="path15036" + d="M 245.66479,-10.727742 L 272.29777,-10.727742 C 273.14089,-10.727742 273.81967,-10.085626 273.81967,-9.288015 L 273.81967,5.241504 C 273.81967,6.039111 273.14089,6.681231 272.29777,6.681231 L 245.66479,6.681231 C 244.82175,6.681231 244.14295,6.039111 244.14295,5.241504 L 244.14295,-9.288015 C 244.14295,-10.085626 244.82175,-10.727742 245.66479,-10.727742 z " + style="opacity:0.99578091;fill:url(#radialGradient15060);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.57956129;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" /> + <path + sodipodi:nodetypes="ccccccccc" + style="opacity:0.15189873;fill:url(#linearGradient15062);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.71897209;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 247.74611,-16.675395 C 245.51463,-16.675395 243.72133,-15.841241 243.72133,-13.406434 L 243.72133,-13.042257 C 243.84869,-12.164248 244.78239,-11.300888 245.34273,-11.135649 L 272.42802,-11.135649 C 272.99275,-11.302188 274.13988,-12.154312 274.26325,-13.042257 L 274.26325,-13.406434 C 274.26325,-15.841241 272.46995,-16.675395 270.23846,-16.675395 L 247.74611,-16.675395 z " + id="path15038" /> + </g> + <g + transform="matrix(1.584275,0,0,1.524619,-8.668287,846.0872)" + id="g15040" + inkscape:export-filename="/home/lando/Projects/Amarok/Playback-modes/amarok_repeat_playlist.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <g + id="g15042"> + <path + id="path15044" + d="M 216.59388,201.58154 L 216.59388,213.16264" + style="opacity:0.39183673;fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#264a9e;stroke-width:0.19158731;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:nodetypes="cc" /> + <path + style="opacity:0.39183673;fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#264a9e;stroke-width:0.19158731;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 223.52993,201.58154 L 223.52993,213.17716" + id="path15046" + sodipodi:nodetypes="cc" /> + </g> + <rect + style="opacity:0.56734691;fill:#6785ed;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.28953668;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect15048" + width="18.826923" + height="3.4617176" + x="211.13826" + y="205.69659" /> + </g> + <path + transform="matrix(1.454545,0,0,1.454545,-18.31809,798.2032)" + sodipodi:type="inkscape:offset" + inkscape:radius="0.058185551" + inkscape:original="M 243.21875 240.96875 L 243.71875 242.4375 C 243.17057 242.73054 242.65817 243.09183 242.21875 243.53125 C 241.00279 244.74721 240.21875 246.45388 240.21875 248.3125 C 240.21875 250.17112 241.00618 251.84993 242.21875 253.0625 C 242.82334 253.66709 243.56461 254.15704 244.375 254.5 C 244.78796 254.67476 245.20018 254.78799 245.625 254.875 L 245.09375 254.03125 L 245.90625 253.375 C 245.55308 253.30266 245.23415 253.21929 244.9375 253.09375 C 244.31638 252.8309 243.78109 252.43734 243.3125 251.96875 C 242.37531 251.03156 241.8125 249.74915 241.8125 248.3125 C 241.8125 246.87586 242.37531 245.59344 243.3125 244.65625 C 243.59658 244.37217 243.91263 244.12278 244.25 243.90625 L 244.71875 245.25 L 247.9375 242.34375 L 243.21875 240.96875 z M 248.3125 241.71875 L 248.90625 242.6875 L 248.03125 243.21875 C 248.35712 243.28549 248.67866 243.39526 249 243.53125 C 249.62112 243.7941 250.15641 244.18766 250.625 244.65625 C 251.56219 245.59345 252.125 246.87586 252.125 248.3125 C 252.125 249.74915 251.56219 251.03156 250.625 251.96875 C 250.36142 252.23233 250.09045 252.48131 249.78125 252.6875 L 249.3125 251.34375 L 246.09375 254.25 L 250.8125 255.625 L 250.28125 254.125 C 250.818 253.82813 251.32527 253.48723 251.75 253.0625 C 252.96596 251.84654 253.71875 250.16483 253.71875 248.3125 C 253.71875 246.46017 252.96935 244.7506 251.75 243.53125 C 251.13185 242.9131 250.35677 242.46114 249.5625 242.125 C 249.1722 241.95983 248.7624 241.81089 248.3125 241.71875 z " + style="fill:#0368ff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:3.75;stroke-miterlimit:4;stroke-dashoffset:0;stroke-opacity:1" + id="path15050" + d="M 243.1875,240.90625 C 243.18465,240.91647 243.18465,240.92728 243.1875,240.9375 C 243.17728,240.93465 243.16647,240.93465 243.15625,240.9375 C 243.1534,240.94772 243.1534,240.95853 243.15625,240.96875 C 243.1534,240.97897 243.1534,240.98978 243.15625,241 L 243.625,242.40625 C 243.09526,242.69566 242.61543,243.07207 242.1875,243.5 C 240.96113,244.72637 240.15625,246.43752 240.15625,248.3125 C 240.15625,250.18755 240.96485,251.8711 242.1875,253.09375 C 242.79779,253.70404 243.52677,254.21675 244.34375,254.5625 C 244.7612,254.73916 245.19771,254.84998 245.625,254.9375 C 245.63522,254.94035 245.64603,254.94035 245.65625,254.9375 C 245.6591,254.92728 245.6591,254.91647 245.65625,254.90625 C 245.66647,254.9091 245.67728,254.9091 245.6875,254.90625 C 245.69035,254.89603 245.69035,254.88522 245.6875,254.875 C 245.69035,254.86478 245.69035,254.85397 245.6875,254.84375 L 245.15625,254.03125 L 245.9375,253.40625 C 245.94772,253.4091 245.95853,253.4091 245.96875,253.40625 C 245.9716,253.39603 245.9716,253.38522 245.96875,253.375 C 245.9716,253.36478 245.9716,253.35397 245.96875,253.34375 C 245.95853,253.3409 245.94772,253.3409 245.9375,253.34375 C 245.94035,253.33353 245.94035,253.32272 245.9375,253.3125 C 245.92728,253.30965 245.91647,253.30965 245.90625,253.3125 C 245.55494,253.24054 245.26013,253.15456 244.96875,253.03125 C 244.35578,252.77185 243.80819,252.40194 243.34375,251.9375 C 242.41709,251.01084 241.875,249.73395 241.875,248.3125 C 241.875,246.89106 242.41709,245.61416 243.34375,244.6875 C 243.60351,244.42774 243.91272,244.23412 244.21875,244.03125 L 244.65625,245.28125 C 244.66647,245.2841 244.67728,245.2841 244.6875,245.28125 C 244.68465,245.29147 244.68465,245.30228 244.6875,245.3125 C 244.69772,245.31535 244.70853,245.31535 244.71875,245.3125 C 244.72897,245.31535 244.73978,245.31535 244.75,245.3125 C 244.75285,245.30228 244.75285,245.29147 244.75,245.28125 L 247.96875,242.375 C 247.97897,242.37785 247.98978,242.37785 248,242.375 C 248.00285,242.36478 248.00285,242.35397 248,242.34375 C 248.00285,242.33353 248.00285,242.32272 248,242.3125 C 247.98978,242.30965 247.97897,242.30965 247.96875,242.3125 C 247.9716,242.30228 247.9716,242.29147 247.96875,242.28125 L 243.25,240.90625 C 243.23978,240.9034 243.22897,240.9034 243.21875,240.90625 C 243.20853,240.9034 243.19772,240.9034 243.1875,240.90625 L 243.1875,240.90625 z M 248.28125,241.65625 C 248.2784,241.66647 248.2784,241.67728 248.28125,241.6875 C 248.27103,241.68465 248.26022,241.68465 248.25,241.6875 C 248.24715,241.69772 248.24715,241.70853 248.25,241.71875 C 248.24715,241.72897 248.24715,241.73978 248.25,241.75 L 248.8125,242.65625 L 248,243.15625 C 247.99715,243.16647 247.99715,243.17728 248,243.1875 C 247.98978,243.18465 247.97897,243.18465 247.96875,243.1875 C 247.9659,243.19772 247.9659,243.20853 247.96875,243.21875 C 247.9659,243.22897 247.9659,243.23978 247.96875,243.25 C 247.97897,243.25285 247.98978,243.25285 248,243.25 C 247.99715,243.26022 247.99715,243.27103 248,243.28125 C 248.01022,243.2841 248.02103,243.2841 248.03125,243.28125 C 248.3522,243.34698 248.64989,243.45881 248.96875,243.59375 C 249.58172,243.85315 250.12931,244.22306 250.59375,244.6875 C 251.52041,245.61417 252.0625,246.89106 252.0625,248.3125 C 252.0625,249.73395 251.52041,251.01084 250.59375,251.9375 C 250.35259,252.17866 250.0869,252.36977 249.8125,252.5625 L 249.375,251.3125 C 249.36478,251.30965 249.35397,251.30965 249.34375,251.3125 C 249.3466,251.30228 249.3466,251.29147 249.34375,251.28125 C 249.33353,251.2784 249.32272,251.2784 249.3125,251.28125 C 249.30228,251.2784 249.29147,251.2784 249.28125,251.28125 C 249.2784,251.29147 249.2784,251.30228 249.28125,251.3125 L 246.0625,254.21875 C 246.05228,254.2159 246.04147,254.2159 246.03125,254.21875 C 246.0284,254.22897 246.0284,254.23978 246.03125,254.25 C 246.0284,254.26022 246.0284,254.27103 246.03125,254.28125 C 246.04147,254.2841 246.05228,254.2841 246.0625,254.28125 C 246.05965,254.29147 246.05965,254.30228 246.0625,254.3125 L 250.78125,255.6875 C 250.79147,255.69035 250.80228,255.69035 250.8125,255.6875 C 250.82272,255.69035 250.83353,255.69035 250.84375,255.6875 C 250.8466,255.67728 250.8466,255.66647 250.84375,255.65625 C 250.85397,255.6591 250.86478,255.6591 250.875,255.65625 C 250.87785,255.64603 250.87785,255.63522 250.875,255.625 C 250.87785,255.61478 250.87785,255.60397 250.875,255.59375 L 250.375,254.15625 C 250.89239,253.86563 251.36726,253.50774 251.78125,253.09375 C 253.00769,251.86731 253.78125,250.18059 253.78125,248.3125 C 253.78125,246.44448 253.0114,244.73015 251.78125,243.5 C 251.15593,242.87468 250.39275,242.40064 249.59375,242.0625 C 249.20155,241.89653 248.76767,241.74947 248.3125,241.65625 C 248.30228,241.6534 248.29147,241.6534 248.28125,241.65625 z " + inkscape:export-filename="/home/lando/Projects/Amarok/Playback-modes/amarok_repeat_playlist.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + </g> + <rect + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/48x48/actions/amarok_repeat_playlist.png" + y="567.52484" + x="219.00223" + height="48" + width="48" + id="rect11682" + style="fill:#b9cff9;fill-opacity:0;stroke:none;stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" /> + </g> + <g + id="g12539" + transform="translate(-5,-10.027002)"> + <g + inkscape:export-ydpi="90.000092" + inkscape:export-xdpi="90.000092" + inkscape:export-filename="/home/lando/Projects/Amarok/icons/64/amarok_repeat_playlist.png" + transform="matrix(2.023001,0,0,2.023001,-355.91032,-1759.0026)" + id="g15064"> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Playback-modes/amarok_repeat_playlist.png" + id="g15066" + transform="translate(81.91681,1163.982)"> + <path + sodipodi:nodetypes="ccccccccc" + style="fill:url(#linearGradient15089);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient15091);stroke-width:0.3707363;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 247.4797,-16.724353 C 245.19536,-16.724353 243.35958,-15.912221 243.35958,-13.541697 L 243.35958,5.629258 C 243.48998,6.484086 244.44578,7.32465 245.01941,7.485528 L 272.7463,7.485528 C 273.32442,7.323387 274.49871,6.49376 274.625,5.629258 L 274.625,-13.541697 C 274.625,-15.912221 272.78922,-16.724353 270.50489,-16.724353 L 247.4797,-16.724353 z " + id="path15068" /> + <path + id="path15070" + d="M 245.7032,-11.106549 L 272.28145,-11.106549 C 273.12286,-11.106549 273.80023,-10.446932 273.80023,-9.627587 L 273.80023,5.2979 C 273.80023,6.117245 273.12286,6.776862 272.28145,6.776862 L 245.7032,6.776862 C 244.86179,6.776862 244.18442,6.117245 244.18442,5.2979 L 244.18442,-9.627587 C 244.18442,-10.446932 244.86179,-11.106549 245.7032,-11.106549 z " + style="fill:url(#linearGradient15093);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient15096);stroke-width:0.45800543;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" /> + <path + style="opacity:0.99578091;fill:url(#radialGradient15098);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.57956129;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 245.66479,-10.727742 L 272.29777,-10.727742 C 273.14089,-10.727742 273.81967,-10.085626 273.81967,-9.288015 L 273.81967,5.241504 C 273.81967,6.039111 273.14089,6.681231 272.29777,6.681231 L 245.66479,6.681231 C 244.82175,6.681231 244.14295,6.039111 244.14295,5.241504 L 244.14295,-9.288015 C 244.14295,-10.085626 244.82175,-10.727742 245.66479,-10.727742 z " + id="path15072" /> + <path + id="path15074" + d="M 247.74611,-16.675395 C 245.51463,-16.675395 243.72133,-15.841241 243.72133,-13.406434 L 243.72133,-13.042257 C 243.84869,-12.164248 244.78239,-11.300888 245.34273,-11.135649 L 272.42802,-11.135649 C 272.99275,-11.302188 274.13988,-12.154312 274.26325,-13.042257 L 274.26325,-13.406434 C 274.26325,-15.841241 272.46995,-16.675395 270.23846,-16.675395 L 247.74611,-16.675395 z " + style="opacity:0.15189873;fill:url(#linearGradient15100);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.71897209;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:nodetypes="ccccccccc" /> + </g> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Playback-modes/amarok_repeat_playlist.png" + id="g15076" + transform="matrix(1.584275,0,0,1.524619,-8.668287,846.0872)"> + <g + id="g15078"> + <path + sodipodi:nodetypes="cc" + style="opacity:0.39183673;fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#264a9e;stroke-width:0.14312236;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 216.59388,201.58154 L 216.59388,213.16264" + id="path15080" /> + <path + sodipodi:nodetypes="cc" + id="path15082" + d="M 223.52993,201.58154 L 223.52993,213.17716" + style="opacity:0.39183673;fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#264a9e;stroke-width:0.14312236;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + <rect + y="205.69659" + x="211.13826" + height="3.4617176" + width="18.826923" + id="rect15084" + style="opacity:0.56734691;fill:#6785ed;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.28953668;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Playback-modes/amarok_repeat_playlist.png" + d="M 243.1875,240.90625 C 243.18465,240.91647 243.18465,240.92728 243.1875,240.9375 C 243.17728,240.93465 243.16647,240.93465 243.15625,240.9375 C 243.1534,240.94772 243.1534,240.95853 243.15625,240.96875 C 243.1534,240.97897 243.1534,240.98978 243.15625,241 L 243.625,242.40625 C 243.09526,242.69566 242.61543,243.07207 242.1875,243.5 C 240.96113,244.72637 240.15625,246.43752 240.15625,248.3125 C 240.15625,250.18755 240.96485,251.8711 242.1875,253.09375 C 242.79779,253.70404 243.52677,254.21675 244.34375,254.5625 C 244.7612,254.73916 245.19771,254.84998 245.625,254.9375 C 245.63522,254.94035 245.64603,254.94035 245.65625,254.9375 C 245.6591,254.92728 245.6591,254.91647 245.65625,254.90625 C 245.66647,254.9091 245.67728,254.9091 245.6875,254.90625 C 245.69035,254.89603 245.69035,254.88522 245.6875,254.875 C 245.69035,254.86478 245.69035,254.85397 245.6875,254.84375 L 245.15625,254.03125 L 245.9375,253.40625 C 245.94772,253.4091 245.95853,253.4091 245.96875,253.40625 C 245.9716,253.39603 245.9716,253.38522 245.96875,253.375 C 245.9716,253.36478 245.9716,253.35397 245.96875,253.34375 C 245.95853,253.3409 245.94772,253.3409 245.9375,253.34375 C 245.94035,253.33353 245.94035,253.32272 245.9375,253.3125 C 245.92728,253.30965 245.91647,253.30965 245.90625,253.3125 C 245.55494,253.24054 245.26013,253.15456 244.96875,253.03125 C 244.35578,252.77185 243.80819,252.40194 243.34375,251.9375 C 242.41709,251.01084 241.875,249.73395 241.875,248.3125 C 241.875,246.89106 242.41709,245.61416 243.34375,244.6875 C 243.60351,244.42774 243.91272,244.23412 244.21875,244.03125 L 244.65625,245.28125 C 244.66647,245.2841 244.67728,245.2841 244.6875,245.28125 C 244.68465,245.29147 244.68465,245.30228 244.6875,245.3125 C 244.69772,245.31535 244.70853,245.31535 244.71875,245.3125 C 244.72897,245.31535 244.73978,245.31535 244.75,245.3125 C 244.75285,245.30228 244.75285,245.29147 244.75,245.28125 L 247.96875,242.375 C 247.97897,242.37785 247.98978,242.37785 248,242.375 C 248.00285,242.36478 248.00285,242.35397 248,242.34375 C 248.00285,242.33353 248.00285,242.32272 248,242.3125 C 247.98978,242.30965 247.97897,242.30965 247.96875,242.3125 C 247.9716,242.30228 247.9716,242.29147 247.96875,242.28125 L 243.25,240.90625 C 243.23978,240.9034 243.22897,240.9034 243.21875,240.90625 C 243.20853,240.9034 243.19772,240.9034 243.1875,240.90625 L 243.1875,240.90625 z M 248.28125,241.65625 C 248.2784,241.66647 248.2784,241.67728 248.28125,241.6875 C 248.27103,241.68465 248.26022,241.68465 248.25,241.6875 C 248.24715,241.69772 248.24715,241.70853 248.25,241.71875 C 248.24715,241.72897 248.24715,241.73978 248.25,241.75 L 248.8125,242.65625 L 248,243.15625 C 247.99715,243.16647 247.99715,243.17728 248,243.1875 C 247.98978,243.18465 247.97897,243.18465 247.96875,243.1875 C 247.9659,243.19772 247.9659,243.20853 247.96875,243.21875 C 247.9659,243.22897 247.9659,243.23978 247.96875,243.25 C 247.97897,243.25285 247.98978,243.25285 248,243.25 C 247.99715,243.26022 247.99715,243.27103 248,243.28125 C 248.01022,243.2841 248.02103,243.2841 248.03125,243.28125 C 248.3522,243.34698 248.64989,243.45881 248.96875,243.59375 C 249.58172,243.85315 250.12931,244.22306 250.59375,244.6875 C 251.52041,245.61417 252.0625,246.89106 252.0625,248.3125 C 252.0625,249.73395 251.52041,251.01084 250.59375,251.9375 C 250.35259,252.17866 250.0869,252.36977 249.8125,252.5625 L 249.375,251.3125 C 249.36478,251.30965 249.35397,251.30965 249.34375,251.3125 C 249.3466,251.30228 249.3466,251.29147 249.34375,251.28125 C 249.33353,251.2784 249.32272,251.2784 249.3125,251.28125 C 249.30228,251.2784 249.29147,251.2784 249.28125,251.28125 C 249.2784,251.29147 249.2784,251.30228 249.28125,251.3125 L 246.0625,254.21875 C 246.05228,254.2159 246.04147,254.2159 246.03125,254.21875 C 246.0284,254.22897 246.0284,254.23978 246.03125,254.25 C 246.0284,254.26022 246.0284,254.27103 246.03125,254.28125 C 246.04147,254.2841 246.05228,254.2841 246.0625,254.28125 C 246.05965,254.29147 246.05965,254.30228 246.0625,254.3125 L 250.78125,255.6875 C 250.79147,255.69035 250.80228,255.69035 250.8125,255.6875 C 250.82272,255.69035 250.83353,255.69035 250.84375,255.6875 C 250.8466,255.67728 250.8466,255.66647 250.84375,255.65625 C 250.85397,255.6591 250.86478,255.6591 250.875,255.65625 C 250.87785,255.64603 250.87785,255.63522 250.875,255.625 C 250.87785,255.61478 250.87785,255.60397 250.875,255.59375 L 250.375,254.15625 C 250.89239,253.86563 251.36726,253.50774 251.78125,253.09375 C 253.00769,251.86731 253.78125,250.18059 253.78125,248.3125 C 253.78125,246.44448 253.0114,244.73015 251.78125,243.5 C 251.15593,242.87468 250.39275,242.40064 249.59375,242.0625 C 249.20155,241.89653 248.76767,241.74947 248.3125,241.65625 C 248.30228,241.6534 248.29147,241.6534 248.28125,241.65625 z " + id="path15086" + style="fill:#0368ff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:3.75;stroke-miterlimit:4;stroke-dashoffset:0;stroke-opacity:1" + inkscape:original="M 243.21875 240.96875 L 243.71875 242.4375 C 243.17057 242.73054 242.65817 243.09183 242.21875 243.53125 C 241.00279 244.74721 240.21875 246.45388 240.21875 248.3125 C 240.21875 250.17112 241.00618 251.84993 242.21875 253.0625 C 242.82334 253.66709 243.56461 254.15704 244.375 254.5 C 244.78796 254.67476 245.20018 254.78799 245.625 254.875 L 245.09375 254.03125 L 245.90625 253.375 C 245.55308 253.30266 245.23415 253.21929 244.9375 253.09375 C 244.31638 252.8309 243.78109 252.43734 243.3125 251.96875 C 242.37531 251.03156 241.8125 249.74915 241.8125 248.3125 C 241.8125 246.87586 242.37531 245.59344 243.3125 244.65625 C 243.59658 244.37217 243.91263 244.12278 244.25 243.90625 L 244.71875 245.25 L 247.9375 242.34375 L 243.21875 240.96875 z M 248.3125 241.71875 L 248.90625 242.6875 L 248.03125 243.21875 C 248.35712 243.28549 248.67866 243.39526 249 243.53125 C 249.62112 243.7941 250.15641 244.18766 250.625 244.65625 C 251.56219 245.59345 252.125 246.87586 252.125 248.3125 C 252.125 249.74915 251.56219 251.03156 250.625 251.96875 C 250.36142 252.23233 250.09045 252.48131 249.78125 252.6875 L 249.3125 251.34375 L 246.09375 254.25 L 250.8125 255.625 L 250.28125 254.125 C 250.818 253.82813 251.32527 253.48723 251.75 253.0625 C 252.96596 251.84654 253.71875 250.16483 253.71875 248.3125 C 253.71875 246.46017 252.96935 244.7506 251.75 243.53125 C 251.13185 242.9131 250.35677 242.46114 249.5625 242.125 C 249.1722 241.95983 248.7624 241.81089 248.3125 241.71875 z " + inkscape:radius="0.058185551" + sodipodi:type="inkscape:offset" + transform="matrix(1.454545,0,0,1.454545,-18.31809,798.2032)" /> + </g> + <rect + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/64x64/actions/amarok_repeat_playlist.png" + style="fill:#b9cff9;fill-opacity:0;stroke:none;stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + id="rect11684" + width="64" + height="64" + x="301.74915" + y="554.3891" /> + </g> + <g + id="g12618" + transform="translate(-5,-9.8912601)"> + <g + transform="matrix(2.023001,0,0,2.023001,-373.06472,128.76142)" + id="g15187" + inkscape:export-filename="/home/lando/Projects/Amarok/icons/64/amarok_playlist.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <g + id="g15189" + transform="translate(93.64593,266.4224)"> + <path + sodipodi:nodetypes="ccccccccc" + style="fill:url(#linearGradient15214);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient15216);stroke-width:0.3707363;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 247.4797,-16.724353 C 245.19536,-16.724353 243.35958,-15.912221 243.35958,-13.541697 L 243.35958,5.629258 C 243.48998,6.484086 244.44578,7.32465 245.01941,7.485528 L 272.7463,7.485528 C 273.32442,7.323387 274.49871,6.49376 274.625,5.629258 L 274.625,-13.541697 C 274.625,-15.912221 272.78922,-16.724353 270.50489,-16.724353 L 247.4797,-16.724353 z " + id="path15191" /> + <path + id="path15193" + d="M 245.7032,-11.106549 L 272.28145,-11.106549 C 273.12286,-11.106549 273.80023,-10.446932 273.80023,-9.627587 L 273.80023,5.2979 C 273.80023,6.117245 273.12286,6.776862 272.28145,6.776862 L 245.7032,6.776862 C 244.86179,6.776862 244.18442,6.117245 244.18442,5.2979 L 244.18442,-9.627587 C 244.18442,-10.446932 244.86179,-11.106549 245.7032,-11.106549 z " + style="fill:url(#linearGradient15218);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient15220);stroke-width:0.45800543;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" /> + <path + style="opacity:0.99578091;fill:url(#radialGradient15222);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.57956129;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 245.66479,-10.727742 L 272.29777,-10.727742 C 273.14089,-10.727742 273.81967,-10.085626 273.81967,-9.288015 L 273.81967,5.241504 C 273.81967,6.039111 273.14089,6.681231 272.29777,6.681231 L 245.66479,6.681231 C 244.82175,6.681231 244.14295,6.039111 244.14295,5.241504 L 244.14295,-9.288015 C 244.14295,-10.085626 244.82175,-10.727742 245.66479,-10.727742 z " + id="path15195" /> + <path + id="path15197" + d="M 247.74611,-16.675395 C 245.51463,-16.675395 243.72133,-15.841241 243.72133,-13.406434 L 243.72133,-13.042257 C 243.84869,-12.164248 244.78239,-11.300888 245.34273,-11.135649 L 272.42802,-11.135649 C 272.99275,-11.302188 274.13988,-12.154312 274.26325,-13.042257 L 274.26325,-13.406434 C 274.26325,-15.841241 272.46995,-16.675395 270.23846,-16.675395 L 247.74611,-16.675395 z " + style="opacity:0.15189873;fill:url(#linearGradient15224);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.71897209;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:nodetypes="ccccccccc" /> + </g> + <g + id="g15199" + transform="matrix(1.584275,0,0,1.524619,3.096535,-51.87676)"> + <g + id="g15201"> + <path + sodipodi:nodetypes="cc" + style="opacity:0.39183673;fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#264a9e;stroke-width:0.14312236;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 216.59388,201.58154 L 216.59388,213.16264" + id="path15204" /> + <path + sodipodi:nodetypes="cc" + id="path15206" + d="M 223.52993,201.58154 L 223.52993,213.17716" + style="opacity:0.39183673;fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#264a9e;stroke-width:0.14312236;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + <rect + y="205.69659" + x="211.13826" + height="3.4617176" + width="18.826923" + id="rect15208" + style="opacity:0.56734691;fill:#6785ed;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.28953668;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + <g + transform="matrix(0.897396,0,0,0.897396,206.9142,76.23569)" + id="g15210"> + <path + inkscape:export-ydpi="3.3299999" + inkscape:export-xdpi="3.3299999" + inkscape:export-filename="/home/kant/Desktop/playlist16.png" + id="path15212" + d="M 160.35798,194.32998 C 159.44703,194.45242 158.98728,195.66462 159.41391,196.48935 C 160.73817,201.04946 162.06244,205.60958 163.38671,210.16969 C 160.99398,209.71902 158.25682,211.21088 157.57207,213.78862 C 157.07041,215.69347 158.08704,217.83504 159.7451,218.66082 C 162.2098,220.07032 165.72114,219.02731 166.94069,216.27621 C 167.52683,215.00041 167.34378,213.45542 166.70616,212.24398 C 165.65935,208.63608 164.61254,205.02817 163.56574,201.42027 C 165.49428,201.11297 167.50276,200.17928 168.53268,198.32157 C 168.92066,197.5672 169.33633,196.52966 168.72264,195.77544 C 168.22462,195.12181 167.31678,195.2785 166.71328,195.65871 C 165.14487,196.31891 163.42205,195.7153 161.97889,194.97431 C 161.4743,194.68138 160.96253,194.2792 160.35798,194.32998 z " + style="color:#000000;fill:url(#linearGradient15226);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient15228);stroke-width:0.55083269;stroke-linecap:round;stroke-linejoin:round;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0.75376886;visibility:visible;display:block;overflow:visible" /> + </g> + </g> + <rect + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/64x64/actions/amarok_playlist.png" + y="626.3891" + x="308.32275" + height="64" + width="64" + id="rect11686" + style="fill:#b9cff9;fill-opacity:0;stroke:none;stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" /> + </g> + <g + id="g12602" + transform="translate(-5,-10.027002)"> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/icons/48/amarok_playlist.png" + id="g15145" + transform="matrix(1.511251,0,0,1.511251,-296.03894,270.87484)"> + <g + transform="translate(93.64593,266.4224)" + id="g15147"> + <path + id="path15149" + d="M 247.4797,-16.724353 C 245.19536,-16.724353 243.35958,-15.912221 243.35958,-13.541697 L 243.35958,5.629258 C 243.48998,6.484086 244.44578,7.32465 245.01941,7.485528 L 272.7463,7.485528 C 273.32442,7.323387 274.49871,6.49376 274.625,5.629258 L 274.625,-13.541697 C 274.625,-15.912221 272.78922,-16.724353 270.50489,-16.724353 L 247.4797,-16.724353 z " + style="fill:url(#linearGradient15171);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient15173);stroke-width:0.4962773;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:nodetypes="ccccccccc" /> + <path + style="fill:url(#linearGradient15175);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient15177);stroke-width:0.61309803;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 245.7032,-11.106549 L 272.28145,-11.106549 C 273.12286,-11.106549 273.80023,-10.446932 273.80023,-9.627587 L 273.80023,5.2979 C 273.80023,6.117245 273.12286,6.776862 272.28145,6.776862 L 245.7032,6.776862 C 244.86179,6.776862 244.18442,6.117245 244.18442,5.2979 L 244.18442,-9.627587 C 244.18442,-10.446932 244.86179,-11.106549 245.7032,-11.106549 z " + id="path15151" /> + <path + id="path15153" + d="M 245.66479,-10.727742 L 272.29777,-10.727742 C 273.14089,-10.727742 273.81967,-10.085626 273.81967,-9.288015 L 273.81967,5.241504 C 273.81967,6.039111 273.14089,6.681231 272.29777,6.681231 L 245.66479,6.681231 C 244.82175,6.681231 244.14295,6.039111 244.14295,5.241504 L 244.14295,-9.288015 C 244.14295,-10.085626 244.82175,-10.727742 245.66479,-10.727742 z " + style="opacity:0.99578091;fill:url(#radialGradient15179);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.57956129;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" /> + <path + sodipodi:nodetypes="ccccccccc" + style="opacity:0.15189873;fill:url(#linearGradient15181);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.71897209;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 247.74611,-16.675395 C 245.51463,-16.675395 243.72133,-15.841241 243.72133,-13.406434 L 243.72133,-13.042257 C 243.84869,-12.164248 244.78239,-11.300888 245.34273,-11.135649 L 272.42802,-11.135649 C 272.99275,-11.302188 274.13988,-12.154312 274.26325,-13.042257 L 274.26325,-13.406434 C 274.26325,-15.841241 272.46995,-16.675395 270.23846,-16.675395 L 247.74611,-16.675395 z " + id="path15155" /> + </g> + <g + transform="matrix(1.584275,0,0,1.524619,3.096535,-51.87676)" + id="g15157"> + <g + id="g15159"> + <path + id="path15161" + d="M 216.59388,201.58154 L 216.59388,213.16264" + style="opacity:0.39183673;fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#264a9e;stroke-width:0.19158731;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:nodetypes="cc" /> + <path + style="opacity:0.39183673;fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#264a9e;stroke-width:0.19158731;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 223.52993,201.58154 L 223.52993,213.17716" + id="path15163" + sodipodi:nodetypes="cc" /> + </g> + <rect + style="opacity:0.56734691;fill:#6785ed;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.28953668;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect15165" + width="18.826923" + height="3.4617176" + x="211.13826" + y="205.69659" /> + </g> + <g + id="g15167" + transform="matrix(0.897396,0,0,0.897396,206.9142,76.23569)"> + <path + style="color:#000000;fill:url(#linearGradient15183);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient15185);stroke-width:0.73735899;stroke-linecap:round;stroke-linejoin:round;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0.75376886;visibility:visible;display:block;overflow:visible" + d="M 160.35798,194.32998 C 159.44703,194.45242 158.98728,195.66462 159.41391,196.48935 C 160.73817,201.04946 162.06244,205.60958 163.38671,210.16969 C 160.99398,209.71902 158.25682,211.21088 157.57207,213.78862 C 157.07041,215.69347 158.08704,217.83504 159.7451,218.66082 C 162.2098,220.07032 165.72114,219.02731 166.94069,216.27621 C 167.52683,215.00041 167.34378,213.45542 166.70616,212.24398 C 165.65935,208.63608 164.61254,205.02817 163.56574,201.42027 C 165.49428,201.11297 167.50276,200.17928 168.53268,198.32157 C 168.92066,197.5672 169.33633,196.52966 168.72264,195.77544 C 168.22462,195.12181 167.31678,195.2785 166.71328,195.65871 C 165.14487,196.31891 163.42205,195.7153 161.97889,194.97431 C 161.4743,194.68138 160.96253,194.2792 160.35798,194.32998 z " + id="path15169" + inkscape:export-filename="/home/kant/Desktop/playlist16.png" + inkscape:export-xdpi="3.3299999" + inkscape:export-ydpi="3.3299999" /> + </g> + </g> + <rect + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/48x48/actions/amarok_playlist.png" + style="fill:#b9cff9;fill-opacity:0;stroke:none;stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + id="rect11688" + width="48" + height="48" + x="212.88593" + y="642.52484" /> + </g> + <g + id="g12586" + transform="translate(-5,-8.7040043)"> + <g + transform="matrix(0.999518,0,0,0.999518,-180.92131,411.52503)" + id="g13365" + inkscape:export-filename="/home/lando/Projects/Amarok/icons/32/amarok_playlist.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <g + id="g13367" + transform="translate(93.64593,266.4224)"> + <path + sodipodi:nodetypes="ccccccccc" + style="fill:url(#linearGradient13768);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient13770);stroke-width:0.75036138;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 247.4797,-16.724353 C 245.19536,-16.724353 243.35958,-15.912221 243.35958,-13.541697 L 243.35958,5.629258 C 243.48998,6.484086 244.44578,7.32465 245.01941,7.485528 L 272.7463,7.485528 C 273.32442,7.323387 274.49871,6.49376 274.625,5.629258 L 274.625,-13.541697 C 274.625,-15.912221 272.78922,-16.724353 270.50489,-16.724353 L 247.4797,-16.724353 z " + id="path13369" /> + <path + id="path13371" + d="M 245.7032,-11.106549 L 272.28145,-11.106549 C 273.12286,-11.106549 273.80023,-10.446932 273.80023,-9.627587 L 273.80023,5.2979 C 273.80023,6.117245 273.12286,6.776862 272.28145,6.776862 L 245.7032,6.776862 C 244.86179,6.776862 244.18442,6.117245 244.18442,5.2979 L 244.18442,-9.627587 C 244.18442,-10.446932 244.86179,-11.106549 245.7032,-11.106549 z " + style="fill:url(#linearGradient13772);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient13774);stroke-width:0.926992;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" /> + <path + style="opacity:0.99578091;fill:url(#radialGradient13776);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.57956129;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 245.66479,-10.727742 L 272.29777,-10.727742 C 273.14089,-10.727742 273.81967,-10.085626 273.81967,-9.288015 L 273.81967,5.241504 C 273.81967,6.039111 273.14089,6.681231 272.29777,6.681231 L 245.66479,6.681231 C 244.82175,6.681231 244.14295,6.039111 244.14295,5.241504 L 244.14295,-9.288015 C 244.14295,-10.085626 244.82175,-10.727742 245.66479,-10.727742 z " + id="path13373" /> + <path + id="path13375" + d="M 247.74611,-16.675395 C 245.51463,-16.675395 243.72133,-15.841241 243.72133,-13.406434 L 243.72133,-13.042257 C 243.84869,-12.164248 244.78239,-11.300888 245.34273,-11.135649 L 272.42802,-11.135649 C 272.99275,-11.302188 274.13988,-12.154312 274.26325,-13.042257 L 274.26325,-13.406434 C 274.26325,-15.841241 272.46995,-16.675395 270.23846,-16.675395 L 247.74611,-16.675395 z " + style="opacity:0.15189873;fill:url(#linearGradient13778);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.71897209;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:nodetypes="ccccccccc" /> + </g> + <g + id="g13377" + transform="matrix(1.584275,0,0,1.524619,3.096535,-51.87676)"> + <g + id="g13379"> + <path + sodipodi:nodetypes="cc" + style="opacity:0.39183673;fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#264a9e;stroke-width:0.28967622;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 216.59388,201.58154 L 216.59388,213.16264" + id="path13381" /> + <path + sodipodi:nodetypes="cc" + id="path13383" + d="M 223.52993,201.58154 L 223.52993,213.17716" + style="opacity:0.39183673;fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#264a9e;stroke-width:0.28967622;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + <rect + y="205.69659" + x="211.13826" + height="3.4617176" + width="18.826923" + id="rect13385" + style="opacity:0.56734691;fill:#6785ed;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.28953668;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + <g + transform="matrix(0.897396,0,0,0.897396,206.9142,76.23569)" + id="g13387"> + <path + inkscape:export-ydpi="3.3299999" + inkscape:export-xdpi="3.3299999" + inkscape:export-filename="/home/kant/Desktop/playlist16.png" + id="path13389" + d="M 160.35798,194.32998 C 159.44703,194.45242 158.98728,195.66462 159.41391,196.48935 C 160.73817,201.04946 162.06244,205.60958 163.38671,210.16969 C 160.99398,209.71902 158.25682,211.21088 157.57207,213.78862 C 157.07041,215.69347 158.08704,217.83504 159.7451,218.66082 C 162.2098,220.07032 165.72114,219.02731 166.94069,216.27621 C 167.52683,215.00041 167.34378,213.45542 166.70616,212.24398 C 165.65935,208.63608 164.61254,205.02817 163.56574,201.42027 C 165.49428,201.11297 167.50276,200.17928 168.53268,198.32157 C 168.92066,197.5672 169.33633,196.52966 168.72264,195.77544 C 168.22462,195.12181 167.31678,195.2785 166.71328,195.65871 C 165.14487,196.31891 163.42205,195.7153 161.97889,194.97431 C 161.4743,194.68138 160.96253,194.2792 160.35798,194.32998 z " + style="color:#000000;fill:url(#linearGradient13780);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient13782);stroke-width:1.11487222;stroke-linecap:round;stroke-linejoin:round;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0.75376886;visibility:visible;display:block;overflow:visible" /> + </g> + </g> + <rect + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/32x32/actions/amarok_playlist.png" + y="657.20184" + x="155.54694" + height="32" + width="32" + id="rect11690" + style="fill:#b9cff9;fill-opacity:0;stroke:none;stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" /> + </g> + <g + id="g12570" + transform="translate(-5,-6.7252445)"> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/crystalsvg/22x22/actions/player_playlist_2.png" + transform="matrix(0.998349,0,0,0.998349,-180.49627,415.60333)" + id="g13391"> + <g + transform="translate(90.7321,261.6857)" + id="g13393"> + <path + id="path13395" + d="M 214.55635,-8.9876506 C 213.0012,-8.9876506 211.75143,-8.4272616 211.75143,-6.7915466 L 211.75143,6.4368384 C 211.8402,7.0266884 212.4909,7.6066964 212.88141,7.7177054 L 231.75761,7.7177054 C 232.15119,7.6058244 232.95062,7.0333644 233.03661,6.4368384 L 233.03661,-6.7915466 C 233.03661,-8.4272616 231.78682,-8.9876506 230.23167,-8.9876506 L 214.55635,-8.9876506 z " + style="fill:url(#linearGradient13784);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient13786);stroke-width:0.75123984;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:nodetypes="ccccccccc" /> + <path + style="fill:url(#linearGradient13788);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient13790);stroke-width:0.98547882;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 213.8908,-4.6646566 L 230.92445,-4.6646566 C 231.46369,-4.6646566 231.89781,-4.2391996 231.89781,-3.7107166 L 231.89781,5.9163164 C 231.89781,6.4447994 231.46369,6.8702564 230.92445,6.8702564 L 213.8908,6.8702564 C 213.35155,6.8702564 212.91744,6.4447994 212.91744,5.9163164 L 212.91744,-3.7107166 C 212.91744,-4.2391996 213.35155,-4.6646566 213.8908,-4.6646566 z " + id="path13397" /> + <path + id="path13399" + d="M 213.95559,-4.6803566 L 230.84519,-4.6803566 C 231.37985,-4.6803566 231.81032,-4.2535196 231.81032,-3.7233216 L 231.81032,5.9349364 C 231.81032,6.4651324 231.37985,6.8919714 230.84519,6.8919714 L 213.95559,6.8919714 C 213.42098,6.8919714 212.9905,6.4651324 212.9905,5.9349364 L 212.9905,-3.7233216 C 212.9905,-4.2535196 213.42098,-4.6803566 213.95559,-4.6803566 z " + style="opacity:0.99578091;fill:url(#radialGradient13792);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.57956129;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" /> + <path + sodipodi:nodetypes="ccccccccc" + style="opacity:0.15189873;fill:url(#linearGradient13794);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.71897209;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 214.7548,-8.7326426 C 213.22744,-8.7326426 212,-8.1994276 212,-6.6430286 L 212,-6.4102356 C 212.08716,-5.8489876 212.72625,-5.2971026 213.10978,-5.1914766 L 231.64865,-5.1914766 C 232.03518,-5.2979336 232.82035,-5.8426356 232.90479,-6.4102356 L 232.90479,-6.6430286 C 232.90479,-8.1994276 231.67735,-8.7326426 230.14998,-8.7326426 L 214.7548,-8.7326426 z " + id="path13401" /> + </g> + <g + id="g13403" + transform="translate(92.55481,55.41122)"> + <g + id="g13405"> + <path + id="path13407" + d="M 216.59388,201.58154 L 216.59388,213.16264" + style="opacity:0.39183673;fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#264a9e;stroke-width:0.29001534;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:nodetypes="cc" /> + <path + style="opacity:0.39183673;fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#264a9e;stroke-width:0.29001534;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 223.52993,201.58154 L 223.52993,213.17716" + id="path13409" + sodipodi:nodetypes="cc" /> + </g> + <rect + style="opacity:0.56734691;fill:#6785ed;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.28953668;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect13411" + width="18.826923" + height="3.4617176" + x="211.13826" + y="205.69659" /> + </g> + <g + transform="matrix(0.878841,0,0,0.878841,203.8674,79.66672)" + id="g13413"> + <path + inkscape:export-ydpi="3.3299999" + inkscape:export-xdpi="3.3299999" + inkscape:export-filename="/home/kant/Desktop/playlist16.png" + id="path13415" + d="M 122.34066,197.85001 C 121.71438,197.93419 121.3983,198.76757 121.69161,199.33457 C 122.60203,202.46965 123.51246,205.60474 124.42289,208.73981 C 122.7779,208.42998 120.89611,209.45563 120.42535,211.22783 C 120.08046,212.53742 120.77939,214.00974 121.9193,214.57747 C 123.61377,215.5465 126.0278,214.82943 126.86624,212.93805 C 127.26921,212.06093 127.14337,210.99875 126.705,210.16589 C 125.98533,207.68545 125.26565,205.20502 124.54597,202.72459 C 125.87184,202.51332 127.25266,201.8714 127.96073,200.59423 C 128.22746,200.0756 128.51323,199.36229 128.09133,198.84377 C 127.74894,198.39439 127.1248,198.50212 126.7099,198.76351 C 125.63163,199.2174 124.44719,198.80242 123.45502,198.29299 C 123.10812,198.0916 122.75628,197.8151 122.34066,197.85001 z " + style="color:#000000;fill:url(#linearGradient13796);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient13798);stroke-width:0.50082654;stroke-linecap:round;stroke-linejoin:round;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0.75376886;visibility:visible;display:block;overflow:visible" /> + </g> + </g> + <rect + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/crystalsvg/22x22/actions/player_playlist_2.png" + style="fill:#b9cff9;fill-opacity:0;stroke:none;stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + id="rect11692" + width="22" + height="22" + x="121.11288" + y="665.22308" /> + </g> + <g + id="g12554" + transform="translate(-5,-4.852808)"> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/crystalsvg/16x16/actions/player_playlist_2.png" + id="g15102" + transform="matrix(0.716482,0,0,0.716482,-131.50321,490.31253)"> + <g + id="g15104" + transform="translate(90.7321,261.6857)"> + <path + sodipodi:nodetypes="ccccccccc" + style="fill:url(#linearGradient15128);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient15130);stroke-width:1.04678178;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 214.55635,-8.9876506 C 213.0012,-8.9876506 211.75143,-8.4272616 211.75143,-6.7915466 L 211.75143,6.4368384 C 211.8402,7.0266884 212.4909,7.6066964 212.88141,7.7177054 L 231.75761,7.7177054 C 232.15119,7.6058244 232.95062,7.0333644 233.03661,6.4368384 L 233.03661,-6.7915466 C 233.03661,-8.4272616 231.78682,-8.9876506 230.23167,-8.9876506 L 214.55635,-8.9876506 z " + id="path15106" /> + <path + id="path15108" + d="M 213.8908,-4.6646566 L 230.92445,-4.6646566 C 231.46369,-4.6646566 231.89781,-4.2391996 231.89781,-3.7107166 L 231.89781,5.9163164 C 231.89781,6.4447994 231.46369,6.8702564 230.92445,6.8702564 L 213.8908,6.8702564 C 213.35155,6.8702564 212.91744,6.4447994 212.91744,5.9163164 L 212.91744,-3.7107166 C 212.91744,-4.2391996 213.35155,-4.6646566 213.8908,-4.6646566 z " + style="fill:url(#linearGradient15132);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient15134);stroke-width:1.37317157;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + style="opacity:0.99578091;fill:url(#radialGradient15136);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.57956129;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 213.95559,-4.6803566 L 230.84519,-4.6803566 C 231.37985,-4.6803566 231.81032,-4.2535196 231.81032,-3.7233216 L 231.81032,5.9349364 C 231.81032,6.4651324 231.37985,6.8919714 230.84519,6.8919714 L 213.95559,6.8919714 C 213.42098,6.8919714 212.9905,6.4651324 212.9905,5.9349364 L 212.9905,-3.7233216 C 212.9905,-4.2535196 213.42098,-4.6803566 213.95559,-4.6803566 z " + id="path15110" /> + <path + id="path15112" + d="M 214.7548,-8.7326426 C 213.22744,-8.7326426 212,-8.1994276 212,-6.6430286 L 212,-6.4102356 C 212.08716,-5.8489876 212.72625,-5.2971026 213.10978,-5.1914766 L 231.64865,-5.1914766 C 232.03518,-5.2979336 232.82035,-5.8426356 232.90479,-6.4102356 L 232.90479,-6.6430286 C 232.90479,-8.1994276 231.67735,-8.7326426 230.14998,-8.7326426 L 214.7548,-8.7326426 z " + style="opacity:0.15189873;fill:url(#linearGradient15138);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.71897209;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:nodetypes="ccccccccc" /> + </g> + <g + transform="translate(92.55481,55.41122)" + id="g15114"> + <g + id="g15116"> + <path + sodipodi:nodetypes="cc" + style="opacity:0.39183673;fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#264a9e;stroke-width:0.404109;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 216.59388,201.58154 L 216.59388,213.16264" + id="path15118" /> + <path + sodipodi:nodetypes="cc" + id="path15120" + d="M 223.52993,201.58154 L 223.52993,213.17716" + style="opacity:0.39183673;fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#264a9e;stroke-width:0.404109;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + <rect + y="205.69659" + x="211.13826" + height="3.4617176" + width="18.826923" + id="rect15122" + style="opacity:0.56734691;fill:#6785ed;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.28953668;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + <g + id="g15124" + transform="matrix(0.878841,0,0,0.878841,203.8674,79.66672)"> + <path + style="color:#000000;fill:url(#linearGradient15141);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient15143);stroke-width:0.69785452;stroke-linecap:round;stroke-linejoin:round;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0.75376886;visibility:visible;display:block;overflow:visible" + d="M 122.34066,197.85001 C 121.71438,197.93419 121.3983,198.76757 121.69161,199.33457 C 122.60203,202.46965 123.51246,205.60474 124.42289,208.73981 C 122.7779,208.42998 120.89611,209.45563 120.42535,211.22783 C 120.08046,212.53742 120.77939,214.00974 121.9193,214.57747 C 123.61377,215.5465 126.0278,214.82943 126.86624,212.93805 C 127.26921,212.06093 127.14337,210.99875 126.705,210.16589 C 125.98533,207.68545 125.26565,205.20502 124.54597,202.72459 C 125.87184,202.51332 127.25266,201.8714 127.96073,200.59423 C 128.22746,200.0756 128.51323,199.36229 128.09133,198.84377 C 127.74894,198.39439 127.1248,198.50212 126.7099,198.76351 C 125.63163,199.2174 124.44719,198.80242 123.45502,198.29299 C 123.10812,198.0916 122.75628,197.8151 122.34066,197.85001 z " + id="path15126" + inkscape:export-filename="/home/kant/Desktop/playlist16.png" + inkscape:export-xdpi="3.3299999" + inkscape:export-ydpi="3.3299999" /> + </g> + </g> + <rect + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/crystalsvg/16x16/actions/player_playlist_2.png" + y="669.35065" + x="84.846016" + height="16" + width="16" + id="rect11694" + style="fill:#b9cff9;fill-opacity:0;stroke:none;stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" /> + </g> + <g + id="g12706" + transform="translate(-5,-10.027002)"> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/icons/64/amarok_add_playlist.png" + id="g15351" + transform="matrix(2.023001,0,0,2.023001,-399.31914,-201.69972)"> + <g + id="g15353" + transform="translate(107.1759,464.3764)"> + <path + sodipodi:nodetypes="ccccccccc" + style="fill:url(#linearGradient15375);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient15377);stroke-width:0.37073588;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 247.4797,-16.724353 C 245.19536,-16.724353 243.35958,-15.912221 243.35958,-13.541697 L 243.35958,5.629258 C 243.48998,6.484086 244.44578,7.32465 245.01941,7.485528 L 272.7463,7.485528 C 273.32442,7.323387 274.49871,6.49376 274.625,5.629258 L 274.625,-13.541697 C 274.625,-15.912221 272.78922,-16.724353 270.50489,-16.724353 L 247.4797,-16.724353 z " + id="path15355" /> + <path + id="path15357" + d="M 245.7032,-11.106549 L 272.28145,-11.106549 C 273.12286,-11.106549 273.80023,-10.446932 273.80023,-9.627587 L 273.80023,5.2979 C 273.80023,6.117245 273.12286,6.776862 272.28145,6.776862 L 245.7032,6.776862 C 244.86179,6.776862 244.18442,6.117245 244.18442,5.2979 L 244.18442,-9.627587 C 244.18442,-10.446932 244.86179,-11.106549 245.7032,-11.106549 z " + style="fill:url(#linearGradient15379);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient15381);stroke-width:0.45800495;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" /> + <path + style="opacity:0.99578091;fill:url(#radialGradient15383);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.57956129;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 245.66479,-10.727742 L 272.29777,-10.727742 C 273.14089,-10.727742 273.81967,-10.085626 273.81967,-9.288015 L 273.81967,5.241504 C 273.81967,6.039111 273.14089,6.681231 272.29777,6.681231 L 245.66479,6.681231 C 244.82175,6.681231 244.14295,6.039111 244.14295,5.241504 L 244.14295,-9.288015 C 244.14295,-10.085626 244.82175,-10.727742 245.66479,-10.727742 z " + id="path15359" /> + <path + id="path15361" + d="M 247.74611,-16.675395 C 245.51463,-16.675395 243.72133,-15.841241 243.72133,-13.406434 L 243.72133,-13.042257 C 243.84869,-12.164248 244.78239,-11.300888 245.34273,-11.135649 L 272.42802,-11.135649 C 272.99275,-11.302188 274.13988,-12.154312 274.26325,-13.042257 L 274.26325,-13.406434 C 274.26325,-15.841241 272.46995,-16.675395 270.23846,-16.675395 L 247.74611,-16.675395 z " + style="opacity:0.15189873;fill:url(#linearGradient15385);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.71897209;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:nodetypes="ccccccccc" /> + </g> + <g + id="g15363" + transform="matrix(1.584275,0,0,1.524619,16.77221,146.1226)"> + <g + id="g15365"> + <path + sodipodi:nodetypes="cc" + style="opacity:0.39183673;fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#264a9e;stroke-width:0.14312218;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 216.59388,201.58154 L 216.59388,213.16264" + id="path15367" /> + <path + sodipodi:nodetypes="cc" + id="path15369" + d="M 223.52993,201.58154 L 223.52993,213.17716" + style="opacity:0.39183673;fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#264a9e;stroke-width:0.14312218;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + <rect + y="205.69659" + x="211.13826" + height="3.4617176" + width="18.826923" + id="rect15371" + style="opacity:0.56734691;fill:#6785ed;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.28953668;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + <path + sodipodi:nodetypes="ccccccccccccc" + style="fill:url(#linearGradient15387);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient15389);stroke-width:0.32684124;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + d="M 371.83755,460.02243 L 367.79971,460.02242 L 367.77063,456.02551 L 364.45668,456.02551 L 364.48575,460.02243 L 360.49875,460.05151 L 360.49875,463.41205 L 364.48575,463.38297 L 364.45668,467.36396 L 367.77063,467.36396 L 367.79971,463.38297 L 371.83755,463.38297 L 371.83755,460.02243 z " + id="path15373" /> + </g> + <rect + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/64x64/actions/amarok_add_playlist.png" + style="fill:#b9cff9;fill-opacity:0;stroke:none;stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + id="rect11696" + width="64" + height="64" + x="309.43948" + y="696.3891" /> + </g> + <g + id="g12679" + transform="translate(-5,-8.1627445)"> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Album-artist-menu/amarok_add_playlist.png" + id="g15270" + transform="matrix(1.319339,0,0,1.319339,-240.45583,127.94954)"> + <g + id="g15272" + transform="translate(107.1759,464.3764)"> + <path + sodipodi:nodetypes="ccccccccc" + style="fill:url(#linearGradient15294);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient15296);stroke-width:0.56846565;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 247.4797,-16.724353 C 245.19536,-16.724353 243.35958,-15.912221 243.35958,-13.541697 L 243.35958,5.629258 C 243.48998,6.484086 244.44578,7.32465 245.01941,7.485528 L 272.7463,7.485528 C 273.32442,7.323387 274.49871,6.49376 274.625,5.629258 L 274.625,-13.541697 C 274.625,-15.912221 272.78922,-16.724353 270.50489,-16.724353 L 247.4797,-16.724353 z " + id="path15274" /> + <path + id="path15276" + d="M 245.7032,-11.106549 L 272.28145,-11.106549 C 273.12286,-11.106549 273.80023,-10.446932 273.80023,-9.627587 L 273.80023,5.2979 C 273.80023,6.117245 273.12286,6.776862 272.28145,6.776862 L 245.7032,6.776862 C 244.86179,6.776862 244.18442,6.117245 244.18442,5.2979 L 244.18442,-9.627587 C 244.18442,-10.446932 244.86179,-11.106549 245.7032,-11.106549 z " + style="fill:url(#linearGradient15298);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient15300);stroke-width:0.70227915;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" /> + <path + style="opacity:0.99578091;fill:url(#radialGradient15302);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.57956129;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 245.66479,-10.727742 L 272.29777,-10.727742 C 273.14089,-10.727742 273.81967,-10.085626 273.81967,-9.288015 L 273.81967,5.241504 C 273.81967,6.039111 273.14089,6.681231 272.29777,6.681231 L 245.66479,6.681231 C 244.82175,6.681231 244.14295,6.039111 244.14295,5.241504 L 244.14295,-9.288015 C 244.14295,-10.085626 244.82175,-10.727742 245.66479,-10.727742 z " + id="path15278" /> + <path + id="path15280" + d="M 247.74611,-16.675395 C 245.51463,-16.675395 243.72133,-15.841241 243.72133,-13.406434 L 243.72133,-13.042257 C 243.84869,-12.164248 244.78239,-11.300888 245.34273,-11.135649 L 272.42802,-11.135649 C 272.99275,-11.302188 274.13988,-12.154312 274.26325,-13.042257 L 274.26325,-13.406434 C 274.26325,-15.841241 272.46995,-16.675395 270.23846,-16.675395 L 247.74611,-16.675395 z " + style="opacity:0.15189873;fill:url(#linearGradient15304);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.71897209;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:nodetypes="ccccccccc" /> + </g> + <g + id="g15282" + transform="matrix(1.584275,0,0,1.524619,16.77221,146.1226)"> + <g + id="g15284"> + <path + sodipodi:nodetypes="cc" + style="opacity:0.39183673;fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#264a9e;stroke-width:0.21945556;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 216.59388,201.58154 L 216.59388,213.16264" + id="path15286" /> + <path + sodipodi:nodetypes="cc" + id="path15288" + d="M 223.52993,201.58154 L 223.52993,213.17716" + style="opacity:0.39183673;fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#264a9e;stroke-width:0.21945556;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + <rect + y="205.69659" + x="211.13826" + height="3.4617176" + width="18.826923" + id="rect15290" + style="opacity:0.56734691;fill:#6785ed;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.28953668;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + <path + sodipodi:nodetypes="ccccccccccccc" + style="fill:url(#linearGradient15306);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient15308);stroke-width:0.50116009;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + d="M 371.83755,460.02243 L 367.79971,460.02242 L 367.77063,456.02551 L 364.45668,456.02551 L 364.48575,460.02243 L 360.49875,460.05151 L 360.49875,463.41205 L 364.48575,463.38297 L 364.45668,467.36396 L 367.77063,467.36396 L 367.79971,463.38297 L 371.83755,463.38297 L 371.83755,460.02243 z " + id="path15292" /> + </g> + <g + transform="matrix(1.511253,0,0,1.511253,-310.72864,39.715739)" + id="g15310" + inkscape:export-filename="/home/lando/Projects/Amarok/icons/48/amarok_add_playlist.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <g + transform="translate(107.1759,464.3764)" + id="g15312"> + <path + id="path15314" + d="M 247.4797,-16.724353 C 245.19536,-16.724353 243.35958,-15.912221 243.35958,-13.541697 L 243.35958,5.629258 C 243.48998,6.484086 244.44578,7.32465 245.01941,7.485528 L 272.7463,7.485528 C 273.32442,7.323387 274.49871,6.49376 274.625,5.629258 L 274.625,-13.541697 C 274.625,-15.912221 272.78922,-16.724353 270.50489,-16.724353 L 247.4797,-16.724353 z " + style="fill:url(#linearGradient15334);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient15336);stroke-width:0.49627608;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:nodetypes="ccccccccc" /> + <path + style="fill:url(#linearGradient15338);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient15340);stroke-width:0.61309659;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 245.7032,-11.106549 L 272.28145,-11.106549 C 273.12286,-11.106549 273.80023,-10.446932 273.80023,-9.627587 L 273.80023,5.2979 C 273.80023,6.117245 273.12286,6.776862 272.28145,6.776862 L 245.7032,6.776862 C 244.86179,6.776862 244.18442,6.117245 244.18442,5.2979 L 244.18442,-9.627587 C 244.18442,-10.446932 244.86179,-11.106549 245.7032,-11.106549 z " + id="path15316" /> + <path + id="path15318" + d="M 245.66479,-10.727742 L 272.29777,-10.727742 C 273.14089,-10.727742 273.81967,-10.085626 273.81967,-9.288015 L 273.81967,5.241504 C 273.81967,6.039111 273.14089,6.681231 272.29777,6.681231 L 245.66479,6.681231 C 244.82175,6.681231 244.14295,6.039111 244.14295,5.241504 L 244.14295,-9.288015 C 244.14295,-10.085626 244.82175,-10.727742 245.66479,-10.727742 z " + style="opacity:0.99578091;fill:url(#radialGradient15342);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.57956129;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" /> + <path + sodipodi:nodetypes="ccccccccc" + style="opacity:0.15189873;fill:url(#linearGradient15344);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.71897209;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 247.74611,-16.675395 C 245.51463,-16.675395 243.72133,-15.841241 243.72133,-13.406434 L 243.72133,-13.042257 C 243.84869,-12.164248 244.78239,-11.300888 245.34273,-11.135649 L 272.42802,-11.135649 C 272.99275,-11.302188 274.13988,-12.154312 274.26325,-13.042257 L 274.26325,-13.406434 C 274.26325,-15.841241 272.46995,-16.675395 270.23846,-16.675395 L 247.74611,-16.675395 z " + id="path15320" /> + </g> + <g + transform="matrix(1.584275,0,0,1.524619,16.77221,146.1226)" + id="g15322"> + <g + id="g15324"> + <path + id="path15326" + d="M 216.59388,201.58154 L 216.59388,213.16264" + style="opacity:0.39183673;fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#264a9e;stroke-width:0.19158684;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:nodetypes="cc" /> + <path + style="opacity:0.39183673;fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#264a9e;stroke-width:0.19158684;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 223.52993,201.58154 L 223.52993,213.17716" + id="path15328" + sodipodi:nodetypes="cc" /> + </g> + <rect + style="opacity:0.56734691;fill:#6785ed;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.28953668;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect15330" + width="18.826923" + height="3.4617176" + x="211.13826" + y="205.69659" /> + </g> + <path + id="path15332" + d="M 371.83755,460.02243 L 367.79971,460.02242 L 367.77063,456.02551 L 364.45668,456.02551 L 364.48575,460.02243 L 360.49875,460.05151 L 360.49875,463.41205 L 364.48575,463.38297 L 364.45668,467.36396 L 367.77063,467.36396 L 367.79971,463.38297 L 371.83755,463.38297 L 371.83755,460.02243 z " + style="fill:url(#linearGradient15347);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient15349);stroke-width:0.43751767;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + sodipodi:nodetypes="ccccccccccccc" /> + </g> + <rect + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/48x48/actions/amarok_add_playlist.png" + y="710.52484" + x="218.64413" + height="48" + width="48" + id="rect11698" + style="fill:#b9cff9;fill-opacity:0;stroke:none;stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" /> + </g> + <g + id="g12664" + transform="translate(-5,-6.8397464)"> + <g + transform="matrix(0.999518,0,0,0.999518,-194.18958,281.66648)" + id="g13309" + inkscape:export-filename="/home/lando/Projects/Amarok/icons/32/amarok_add_playlist.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <g + transform="translate(107.1759,464.3764)" + id="g13311"> + <path + id="path13313" + d="M 247.4797,-16.724353 C 245.19536,-16.724353 243.35958,-15.912221 243.35958,-13.541697 L 243.35958,5.629258 C 243.48998,6.484086 244.44578,7.32465 245.01941,7.485528 L 272.7463,7.485528 C 273.32442,7.323387 274.49871,6.49376 274.625,5.629258 L 274.625,-13.541697 C 274.625,-15.912221 272.78922,-16.724353 270.50489,-16.724353 L 247.4797,-16.724353 z " + style="fill:url(#linearGradient13724);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient13726);stroke-width:0.75036138;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:nodetypes="ccccccccc" /> + <path + style="fill:url(#linearGradient13728);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient13730);stroke-width:0.926992;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 245.7032,-11.106549 L 272.28145,-11.106549 C 273.12286,-11.106549 273.80023,-10.446932 273.80023,-9.627587 L 273.80023,5.2979 C 273.80023,6.117245 273.12286,6.776862 272.28145,6.776862 L 245.7032,6.776862 C 244.86179,6.776862 244.18442,6.117245 244.18442,5.2979 L 244.18442,-9.627587 C 244.18442,-10.446932 244.86179,-11.106549 245.7032,-11.106549 z " + id="path13315" /> + <path + id="path13317" + d="M 245.66479,-10.727742 L 272.29777,-10.727742 C 273.14089,-10.727742 273.81967,-10.085626 273.81967,-9.288015 L 273.81967,5.241504 C 273.81967,6.039111 273.14089,6.681231 272.29777,6.681231 L 245.66479,6.681231 C 244.82175,6.681231 244.14295,6.039111 244.14295,5.241504 L 244.14295,-9.288015 C 244.14295,-10.085626 244.82175,-10.727742 245.66479,-10.727742 z " + style="opacity:0.99578091;fill:url(#radialGradient13732);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.57956129;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" /> + <path + sodipodi:nodetypes="ccccccccc" + style="opacity:0.15189873;fill:url(#linearGradient13734);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.71897209;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 247.74611,-16.675395 C 245.51463,-16.675395 243.72133,-15.841241 243.72133,-13.406434 L 243.72133,-13.042257 C 243.84869,-12.164248 244.78239,-11.300888 245.34273,-11.135649 L 272.42802,-11.135649 C 272.99275,-11.302188 274.13988,-12.154312 274.26325,-13.042257 L 274.26325,-13.406434 C 274.26325,-15.841241 272.46995,-16.675395 270.23846,-16.675395 L 247.74611,-16.675395 z " + id="path13319" /> + </g> + <g + transform="matrix(1.584275,0,0,1.524619,16.77221,146.1226)" + id="g13321"> + <g + id="g13323"> + <path + id="path13325" + d="M 216.59388,201.58154 L 216.59388,213.16264" + style="opacity:0.39183673;fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#264a9e;stroke-width:0.28967622;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:nodetypes="cc" /> + <path + style="opacity:0.39183673;fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#264a9e;stroke-width:0.28967622;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 223.52993,201.58154 L 223.52993,213.17716" + id="path13327" + sodipodi:nodetypes="cc" /> + </g> + <rect + style="opacity:0.56734691;fill:#6785ed;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.28953668;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect13329" + width="18.826923" + height="3.4617176" + x="211.13826" + y="205.69659" /> + </g> + <path + id="path13331" + d="M 371.83755,460.02243 L 367.79971,460.02242 L 367.77063,456.02551 L 364.45668,456.02551 L 364.48575,460.02243 L 360.49875,460.05151 L 360.49875,463.41205 L 364.48575,463.38297 L 364.45668,467.36396 L 367.77063,467.36396 L 367.79971,463.38297 L 371.83755,463.38297 L 371.83755,460.02243 z " + style="fill:url(#linearGradient13736);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient13738);stroke-width:0.66151959;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + sodipodi:nodetypes="ccccccccccccc" /> + </g> + <rect + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/32x32/actions/amarok_add_playlist.png" + style="fill:#b9cff9;fill-opacity:0;stroke:none;stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + id="rect11700" + width="32" + height="32" + x="155.80212" + y="725.20184" /> + </g> + <g + id="g12649" + transform="translate(-5,-3.8609867)"> + <g + inkscape:export-ydpi="90.000092" + inkscape:export-xdpi="90.000092" + inkscape:export-filename="/home/lando/Projects/Amarok/icons/22/amarok_add_playlist.png" + transform="translate(-194.3527,284.71388)" + id="g13441"> + <g + transform="translate(102.3038,459.1442)" + id="g13443"> + <path + id="path13445" + d="M 214.55635,-8.9876506 C 213.0012,-8.9876506 211.75143,-8.4272616 211.75143,-6.7915466 L 211.75143,6.4368384 C 211.8402,7.0266884 212.4909,7.6066964 212.88141,7.7177054 L 231.75761,7.7177054 C 232.15119,7.6058244 232.95062,7.0333644 233.03661,6.4368384 L 233.03661,-6.7915466 C 233.03661,-8.4272616 231.78682,-8.9876506 230.23167,-8.9876506 L 214.55635,-8.9876506 z " + style="fill:url(#linearGradient13816);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient13818);stroke-width:0.75;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:nodetypes="ccccccccc" /> + <path + style="fill:url(#linearGradient13820);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient13822);stroke-width:0.98385239;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 213.8908,-4.6646566 L 230.92445,-4.6646566 C 231.46369,-4.6646566 231.89781,-4.2391996 231.89781,-3.7107166 L 231.89781,5.9163164 C 231.89781,6.4447994 231.46369,6.8702564 230.92445,6.8702564 L 213.8908,6.8702564 C 213.35155,6.8702564 212.91744,6.4447994 212.91744,5.9163164 L 212.91744,-3.7107166 C 212.91744,-4.2391996 213.35155,-4.6646566 213.8908,-4.6646566 z " + id="path13447" /> + <path + id="path13449" + d="M 213.95559,-4.6803566 L 230.84519,-4.6803566 C 231.37985,-4.6803566 231.81032,-4.2535196 231.81032,-3.7233216 L 231.81032,5.9349364 C 231.81032,6.4651324 231.37985,6.8919714 230.84519,6.8919714 L 213.95559,6.8919714 C 213.42098,6.8919714 212.9905,6.4651324 212.9905,5.9349364 L 212.9905,-3.7233216 C 212.9905,-4.2535196 213.42098,-4.6803566 213.95559,-4.6803566 z " + style="opacity:0.99578091;fill:url(#radialGradient13824);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.57956129;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" /> + <path + sodipodi:nodetypes="ccccccccc" + style="opacity:0.15189873;fill:url(#linearGradient13826);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.71897209;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 214.7548,-8.7326426 C 213.22744,-8.7326426 212,-8.1994276 212,-6.6430286 L 212,-6.4102356 C 212.08716,-5.8489876 212.72625,-5.2971026 213.10978,-5.1914766 L 231.64865,-5.1914766 C 232.03518,-5.2979336 232.82035,-5.8426356 232.90479,-6.4102356 L 232.90479,-6.6430286 C 232.90479,-8.1994276 231.67735,-8.7326426 230.14998,-8.7326426 L 214.7548,-8.7326426 z " + id="path13451" /> + </g> + <g + transform="translate(104.1604,252.8747)" + id="g13453"> + <g + id="g13455"> + <path + sodipodi:nodetypes="cc" + style="opacity:0.39183673;fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#264a9e;stroke-width:0.28953668;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 216.59388,201.58154 L 216.59388,213.16264" + id="path13457" /> + <path + sodipodi:nodetypes="cc" + id="path13459" + d="M 223.52993,201.58154 L 223.52993,213.17716" + style="opacity:0.39183673;fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#264a9e;stroke-width:0.28953668;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + <rect + y="205.69659" + x="211.13826" + height="3.4617176" + width="18.826923" + id="rect13461" + style="opacity:0.56734691;fill:#6785ed;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.28953668;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + <path + sodipodi:nodetypes="ccccccccccccc" + style="fill:url(#linearGradient13828);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient13830);stroke-width:0.49591547;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + d="M 328.66134,458.76559 L 325.63287,458.76558 L 325.61106,455.76781 L 323.12552,455.76781 L 323.14733,458.76559 L 320.15699,458.7874 L 320.15699,461.30788 L 323.14733,461.28607 L 323.12552,464.2719 L 325.61106,464.2719 L 325.63287,461.28607 L 328.66134,461.28607 L 328.66134,458.76559 z " + id="path13463" /> + </g> + <rect + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/22x22/actions/amarok_add_playlist.png" + y="732.22308" + x="119.34512" + height="22" + width="22" + id="rect11702" + style="fill:#b9cff9;fill-opacity:0;stroke:none;stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" /> + </g> + <g + id="g12634" + transform="translate(-5,-3.9885502)"> + <g + inkscape:export-ydpi="90.000092" + inkscape:export-xdpi="90.000092" + inkscape:export-filename="/home/lando/Projects/Amarok/icons/16/amarok_add_playlist.png" + id="g15230" + transform="matrix(0.716482,0,0,0.716482,-144.98215,417.83703)"> + <g + id="g15232" + transform="translate(102.3038,459.1442)"> + <path + sodipodi:nodetypes="ccccccccc" + style="fill:url(#linearGradient15254);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient15256);stroke-width:1.04678273;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 214.55635,-8.9876506 C 213.0012,-8.9876506 211.75143,-8.4272616 211.75143,-6.7915466 L 211.75143,6.4368384 C 211.8402,7.0266884 212.4909,7.6066964 212.88141,7.7177054 L 231.75761,7.7177054 C 232.15119,7.6058244 232.95062,7.0333644 233.03661,6.4368384 L 233.03661,-6.7915466 C 233.03661,-8.4272616 231.78682,-8.9876506 230.23167,-8.9876506 L 214.55635,-8.9876506 z " + id="path15234" /> + <path + id="path15236" + d="M 213.8908,-4.6646566 L 230.92445,-4.6646566 C 231.46369,-4.6646566 231.89781,-4.2391996 231.89781,-3.7107166 L 231.89781,5.9163164 C 231.89781,6.4447994 231.46369,6.8702564 230.92445,6.8702564 L 213.8908,6.8702564 C 213.35155,6.8702564 212.91744,6.4447994 212.91744,5.9163164 L 212.91744,-3.7107166 C 212.91744,-4.2391996 213.35155,-4.6646566 213.8908,-4.6646566 z " + style="fill:url(#linearGradient15258);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient15260);stroke-width:1.373173;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + style="opacity:0.99578091;fill:url(#radialGradient15262);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.57956129;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 213.95559,-4.6803566 L 230.84519,-4.6803566 C 231.37985,-4.6803566 231.81032,-4.2535196 231.81032,-3.7233216 L 231.81032,5.9349364 C 231.81032,6.4651324 231.37985,6.8919714 230.84519,6.8919714 L 213.95559,6.8919714 C 213.42098,6.8919714 212.9905,6.4651324 212.9905,5.9349364 L 212.9905,-3.7233216 C 212.9905,-4.2535196 213.42098,-4.6803566 213.95559,-4.6803566 z " + id="path15238" /> + <path + id="path15240" + d="M 214.7548,-8.7326426 C 213.22744,-8.7326426 212,-8.1994276 212,-6.6430286 L 212,-6.4102356 C 212.08716,-5.8489876 212.72625,-5.2971026 213.10978,-5.1914766 L 231.64865,-5.1914766 C 232.03518,-5.2979336 232.82035,-5.8426356 232.90479,-6.4102356 L 232.90479,-6.6430286 C 232.90479,-8.1994276 231.67735,-8.7326426 230.14998,-8.7326426 L 214.7548,-8.7326426 z " + style="opacity:0.15189873;fill:url(#linearGradient15264);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.71897209;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:nodetypes="ccccccccc" /> + </g> + <g + id="g15242" + transform="translate(104.1604,252.8747)"> + <g + id="g15244"> + <path + id="path15246" + d="M 216.59388,201.58154 L 216.59388,213.16264" + style="opacity:0.39183673;fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#264a9e;stroke-width:0.40410933;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:nodetypes="cc" /> + <path + style="opacity:0.39183673;fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#264a9e;stroke-width:0.40410933;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 223.52993,201.58154 L 223.52993,213.17716" + id="path15248" + sodipodi:nodetypes="cc" /> + </g> + <rect + style="opacity:0.56734691;fill:#6785ed;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.28953668;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect15250" + width="18.826923" + height="3.4617176" + x="211.13826" + y="205.69659" /> + </g> + <path + id="path15252" + d="M 328.66134,458.76559 L 325.63287,458.76558 L 325.61106,455.76781 L 323.12552,455.76781 L 323.14733,458.76559 L 320.15699,458.7874 L 320.15699,461.30788 L 323.14733,461.28607 L 323.12552,464.2719 L 325.61106,464.2719 L 325.63287,461.28607 L 328.66134,461.28607 L 328.66134,458.76559 z " + style="fill:url(#linearGradient15266);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient15268);stroke-width:0.69215435;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + sodipodi:nodetypes="ccccccccccccc" /> + </g> + <rect + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/16x16/actions/amarok_add_playlist.png" + style="fill:#b9cff9;fill-opacity:0;stroke:none;stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + id="rect11704" + width="16" + height="16" + x="79.657997" + y="738.35065" /> + </g> + <g + id="g12721" + transform="translate(-4,3.0114497)"> + <g + inkscape:export-ydpi="90.000092" + inkscape:export-xdpi="90.000092" + inkscape:export-filename="/home/lando/Projects/Amarok/icons/16/amarok_music.png" + id="g15391" + transform="matrix(0.716482,0,0,0.716482,-147.59632,454.89493)"> + <g + id="g15393" + transform="translate(121.3727,303.9197)"> + <path + id="path15395" + d="M 195.95019,181.04885 C 194.39504,181.04885 193.14527,181.60924 193.14527,183.24495 L 193.14527,196.47334 C 193.23404,197.06319 193.88474,197.6432 194.27525,197.75421 L 213.15145,197.75421 C 213.54503,197.64232 214.34446,197.06986 214.43045,196.47334 L 214.43045,183.24495 C 214.43045,181.60924 213.18066,181.04885 211.62551,181.04885 L 195.95019,181.04885 z " + style="fill:url(#linearGradient15416);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient15418);stroke-width:1.04678178;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:nodetypes="ccccccccc" /> + <path + style="fill:url(#linearGradient15420);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient15422);stroke-width:1.37317157;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 195.28464,185.37184 L 212.31829,185.37184 C 212.85753,185.37184 213.29165,185.7973 213.29165,186.32578 L 213.29165,195.95282 C 213.29165,196.4813 212.85753,196.90676 212.31829,196.90676 L 195.28464,196.90676 C 194.74539,196.90676 194.31128,196.4813 194.31128,195.95282 L 194.31128,186.32578 C 194.31128,185.7973 194.74539,185.37184 195.28464,185.37184 z " + id="path15397" /> + <path + id="path15399" + d="M 195.34943,185.35614 L 212.23903,185.35614 C 212.77369,185.35614 213.20416,185.78298 213.20416,186.31318 L 213.20416,195.97144 C 213.20416,196.50163 212.77369,196.92847 212.23903,196.92847 L 195.34943,196.92847 C 194.81482,196.92847 194.38434,196.50163 194.38434,195.97144 L 194.38434,186.31318 C 194.38434,185.78298 194.81482,185.35614 195.34943,185.35614 z " + style="opacity:0.99578091;fill:url(#radialGradient15424);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.57956129;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" /> + <path + sodipodi:nodetypes="ccccccccc" + style="opacity:0.15189873;fill:url(#linearGradient15426);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.71897209;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 196.14864,181.30386 C 194.62128,181.30386 193.39384,181.83707 193.39384,183.39347 L 193.39384,183.62626 C 193.481,184.18751 194.12009,184.7394 194.50362,184.84502 L 213.04249,184.84502 C 213.42902,184.73857 214.21419,184.19386 214.29863,183.62626 L 214.29863,183.39347 C 214.29863,181.83707 213.07119,181.30386 211.54382,181.30386 L 196.14864,181.30386 z " + id="path15401" /> + </g> + <g + transform="translate(104.6346,287.6983)" + id="g15403"> + <g + id="g15405"> + <path + sodipodi:nodetypes="cc" + style="opacity:0.39183673;fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#264a9e;stroke-width:0.404109;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 216.59388,201.58154 L 216.59388,213.16264" + id="path15407" /> + <path + sodipodi:nodetypes="cc" + id="path15409" + d="M 223.52993,201.58154 L 223.52993,213.17716" + style="opacity:0.39183673;fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#264a9e;stroke-width:0.404109;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + <rect + y="205.69659" + x="211.13826" + height="3.4617176" + width="18.826923" + id="rect15411" + style="opacity:0.56734691;fill:#6785ed;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.28953668;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + <path + style="fill:url(#linearGradient15428);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient15430);stroke-width:0.61000395;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 321.51659,498.53799 L 321.56739,497.00614 C 319.02051,496.5111 316.78474,494.62947 315.87817,491.87791 C 315.34835,490.26981 315.35628,488.61076 315.79458,487.10602 C 315.78362,487.75191 315.87871,488.40196 316.08734,489.03519 C 316.87681,491.43137 319.10981,492.9492 321.69532,493.16219 L 321.7731,490.80362 L 326.88504,495.00891 L 321.51659,498.53799 z " + id="path15414" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + </g> + <rect + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/16x16/actions/amarok_music.png" + y="800.35065" + x="77.375374" + height="16" + width="16" + id="rect11706" + style="fill:#b9cff9;fill-opacity:0;stroke:none;stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" /> + </g> + <g + id="g12736" + transform="translate(-4,0.4825187)"> + <g + inkscape:export-ydpi="90.000092" + inkscape:export-xdpi="90.000092" + inkscape:export-filename="/home/lando/Projects/Amarok/icons/22/amarok_music.png" + transform="matrix(0.998349,0,0,0.998349,-192.35129,315.3728)" + id="g13417"> + <g + transform="translate(121.3727,303.9197)" + id="g13419"> + <path + sodipodi:nodetypes="ccccccccc" + style="fill:url(#linearGradient13800);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient13802);stroke-width:0.75123984;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 195.95019,181.04885 C 194.39504,181.04885 193.14527,181.60924 193.14527,183.24495 L 193.14527,196.47334 C 193.23404,197.06319 193.88474,197.6432 194.27525,197.75421 L 213.15145,197.75421 C 213.54503,197.64232 214.34446,197.06986 214.43045,196.47334 L 214.43045,183.24495 C 214.43045,181.60924 213.18066,181.04885 211.62551,181.04885 L 195.95019,181.04885 z " + id="path13421" /> + <path + id="path13423" + d="M 195.28464,185.37184 L 212.31829,185.37184 C 212.85753,185.37184 213.29165,185.7973 213.29165,186.32578 L 213.29165,195.95282 C 213.29165,196.4813 212.85753,196.90676 212.31829,196.90676 L 195.28464,196.90676 C 194.74539,196.90676 194.31128,196.4813 194.31128,195.95282 L 194.31128,186.32578 C 194.31128,185.7973 194.74539,185.37184 195.28464,185.37184 z " + style="fill:url(#linearGradient13804);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient13806);stroke-width:0.98547882;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + style="opacity:0.99578091;fill:url(#radialGradient13808);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.57956129;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 195.34943,185.35614 L 212.23903,185.35614 C 212.77369,185.35614 213.20416,185.78298 213.20416,186.31318 L 213.20416,195.97144 C 213.20416,196.50163 212.77369,196.92847 212.23903,196.92847 L 195.34943,196.92847 C 194.81482,196.92847 194.38434,196.50163 194.38434,195.97144 L 194.38434,186.31318 C 194.38434,185.78298 194.81482,185.35614 195.34943,185.35614 z " + id="path13425" /> + <path + id="path13427" + d="M 196.14864,181.30386 C 194.62128,181.30386 193.39384,181.83707 193.39384,183.39347 L 193.39384,183.62626 C 193.481,184.18751 194.12009,184.7394 194.50362,184.84502 L 213.04249,184.84502 C 213.42902,184.73857 214.21419,184.19386 214.29863,183.62626 L 214.29863,183.39347 C 214.29863,181.83707 213.07119,181.30386 211.54382,181.30386 L 196.14864,181.30386 z " + style="opacity:0.15189873;fill:url(#linearGradient13810);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.71897209;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:nodetypes="ccccccccc" /> + </g> + <g + id="g13429" + transform="translate(104.6346,287.6983)"> + <g + id="g13431"> + <path + id="path13433" + d="M 216.59388,201.58154 L 216.59388,213.16264" + style="opacity:0.39183673;fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#264a9e;stroke-width:0.29001534;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:nodetypes="cc" /> + <path + style="opacity:0.39183673;fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#264a9e;stroke-width:0.29001534;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 223.52993,201.58154 L 223.52993,213.17716" + id="path13435" + sodipodi:nodetypes="cc" /> + </g> + <rect + style="opacity:0.56734691;fill:#6785ed;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.28953668;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect13437" + width="18.826923" + height="3.4617176" + x="211.13826" + y="205.69659" /> + </g> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + id="path13439" + d="M 321.51659,498.53799 L 321.56739,497.00614 C 319.02051,496.5111 316.78474,494.62947 315.87817,491.87791 C 315.34835,490.26981 315.35628,488.61076 315.79458,487.10602 C 315.78362,487.75191 315.87871,488.40196 316.08734,489.03519 C 316.87681,491.43137 319.10981,492.9492 321.69532,493.16219 L 321.7731,490.80362 L 326.88504,495.00891 L 321.51659,498.53799 z " + style="fill:url(#linearGradient13812);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient13814);stroke-width:0.43777916;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + <rect + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/22x22/actions/amarok_music.png" + style="fill:#b9cff9;fill-opacity:0;stroke:none;stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + id="rect11708" + width="22" + height="22" + x="121.27242" + y="796.87958" /> + </g> + <g + id="g12751" + transform="translate(-4,2.1602537)"> + <g + id="g13565" + transform="matrix(0.994372,0,0,1.431775,-196.19343,94.785659)" + inkscape:export-filename="/home/lando/Projects/Amarok/icons/32/amarok_music.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <g + id="g13567" + transform="matrix(1.460806,0,0,1,73.51795,303.9834)"> + <path + id="path13569" + d="M 195.95019,181.04885 C 194.39504,181.04885 193.14527,181.60924 193.14527,183.24495 L 193.14527,196.47334 C 193.23404,197.06319 193.88474,197.6432 194.27525,197.75421 L 213.15145,197.75421 C 213.54503,197.64232 214.34446,197.06986 214.43045,196.47334 L 214.43045,183.24495 C 214.43045,181.60924 213.18066,181.04885 211.62551,181.04885 L 195.95019,181.04885 z " + style="fill:url(#linearGradient13888);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient13890);stroke-width:0.75;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:nodetypes="ccccccccc" /> + <path + style="fill:url(#linearGradient13892);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient13894);stroke-width:0.98385239;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 195.28464,185.37184 L 212.31829,185.37184 C 212.85753,185.37184 213.29165,185.7973 213.29165,186.32578 L 213.29165,195.95282 C 213.29165,196.4813 212.85753,196.90676 212.31829,196.90676 L 195.28464,196.90676 C 194.74539,196.90676 194.31128,196.4813 194.31128,195.95282 L 194.31128,186.32578 C 194.31128,185.7973 194.74539,185.37184 195.28464,185.37184 z " + id="path13571" /> + <path + id="path13573" + d="M 195.34943,185.35614 L 212.23903,185.35614 C 212.77369,185.35614 213.20416,185.78298 213.20416,186.31318 L 213.20416,195.97144 C 213.20416,196.50163 212.77369,196.92847 212.23903,196.92847 L 195.34943,196.92847 C 194.81482,196.92847 194.38434,196.50163 194.38434,195.97144 L 194.38434,186.31318 C 194.38434,185.78298 194.81482,185.35614 195.34943,185.35614 z " + style="opacity:0.99578091;fill:url(#radialGradient13896);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.57956129;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" /> + <path + sodipodi:nodetypes="ccccccccc" + style="opacity:0.15189873;fill:url(#linearGradient13898);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.71897209;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 196.14864,181.30386 C 194.62128,181.30386 193.39384,181.83707 193.39384,183.39347 L 193.39384,183.62626 C 193.481,184.18751 194.12009,184.7394 194.50362,184.84502 L 213.04249,184.84502 C 213.42902,184.73857 214.21419,184.19386 214.29863,183.62626 L 214.29863,183.39347 C 214.29863,181.83707 213.07119,181.30386 211.54382,181.30386 L 196.14864,181.30386 z " + id="path13575" /> + </g> + <path + style="opacity:0.39183673;fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#264a9e;stroke-width:0.34994498;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 365.43484,489.31976 L 365.43484,500.71207 L 365.55379,500.62705" + id="path13577" /> + <path + id="path13579" + d="M 375.54585,489.31976 L 375.54585,500.71207 L 375.66479,500.62705" + style="opacity:0.39183673;fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#264a9e;stroke-width:0.34994498;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + style="opacity:0.56734691;fill:#6785ed;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.28953668;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect13581" + width="27.502472" + height="3.4617176" + x="357.46518" + y="493.36218" /> + <path + style="fill:url(#linearGradient13900);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient13902);stroke-width:0.52824318;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 365.88924,498.60165 L 365.96345,497.0698 C 362.24294,496.57476 358.97692,494.69313 357.6526,491.94157 C 356.87864,490.33347 356.89022,488.67442 357.5305,487.16968 C 357.51449,487.81557 357.65339,488.46562 357.95816,489.09885 C 359.11143,491.49503 362.3734,493.01286 366.15032,493.22585 L 366.26395,490.86728 L 373.7315,495.07257 L 365.88924,498.60165 z " + id="path13583" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + </g> + <rect + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/32x32/actions/amarok_music.png" + y="785.20184" + x="156.92986" + height="32" + width="32" + id="rect11710" + style="fill:#b9cff9;fill-opacity:0;stroke:none;stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" /> + </g> + <g + id="g12765" + transform="translate(-4,-1.1627445)"> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/icons/48/amarok_music.png" + transform="matrix(1.508936,0,0,2.172684,-313.64673,-275.44468)" + id="g15432"> + <g + transform="matrix(1.460806,0,0,1,73.51795,303.9834)" + id="g15434"> + <path + sodipodi:nodetypes="ccccccccc" + style="fill:url(#linearGradient15452);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient15454);stroke-width:0.49424177;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 195.95019,181.04885 C 194.39504,181.04885 193.14527,181.60924 193.14527,183.24495 L 193.14527,196.47334 C 193.23404,197.06319 193.88474,197.6432 194.27525,197.75421 L 213.15145,197.75421 C 213.54503,197.64232 214.34446,197.06986 214.43045,196.47334 L 214.43045,183.24495 C 214.43045,181.60924 213.18066,181.04885 211.62551,181.04885 L 195.95019,181.04885 z " + id="path15436" /> + <path + id="path15438" + d="M 195.28464,185.37184 L 212.31829,185.37184 C 212.85753,185.37184 213.29165,185.7973 213.29165,186.32578 L 213.29165,195.95282 C 213.29165,196.4813 212.85753,196.90676 212.31829,196.90676 L 195.28464,196.90676 C 194.74539,196.90676 194.31128,196.4813 194.31128,195.95282 L 194.31128,186.32578 C 194.31128,185.7973 194.74539,185.37184 195.28464,185.37184 z " + style="fill:url(#linearGradient15456);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient15458);stroke-width:0.64834797;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + style="opacity:0.99578091;fill:url(#radialGradient15460);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.57956129;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 195.34943,185.35614 L 212.23903,185.35614 C 212.77369,185.35614 213.20416,185.78298 213.20416,186.31318 L 213.20416,195.97144 C 213.20416,196.50163 212.77369,196.92847 212.23903,196.92847 L 195.34943,196.92847 C 194.81482,196.92847 194.38434,196.50163 194.38434,195.97144 L 194.38434,186.31318 C 194.38434,185.78298 194.81482,185.35614 195.34943,185.35614 z " + id="path15440" /> + <path + id="path15442" + d="M 196.14864,181.30386 C 194.62128,181.30386 193.39384,181.83707 193.39384,183.39347 L 193.39384,183.62626 C 193.481,184.18751 194.12009,184.7394 194.50362,184.84502 L 213.04249,184.84502 C 213.42902,184.73857 214.21419,184.19386 214.29863,183.62626 L 214.29863,183.39347 C 214.29863,181.83707 213.07119,181.30386 211.54382,181.30386 L 196.14864,181.30386 z " + style="opacity:0.15189873;fill:url(#linearGradient15462);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.71897209;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:nodetypes="ccccccccc" /> + </g> + <path + id="path15444" + d="M 365.43484,489.31976 L 365.43484,500.71207 L 365.55379,500.62705" + style="opacity:0.39183673;fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#264a9e;stroke-width:0.23060989;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + style="opacity:0.39183673;fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#264a9e;stroke-width:0.23060989;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 375.54585,489.31976 L 375.54585,500.71207 L 375.66479,500.62705" + id="path15446" /> + <rect + y="493.36218" + x="357.46518" + height="3.4617176" + width="27.502472" + id="rect15448" + style="opacity:0.56734691;fill:#6785ed;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.28953668;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + id="path15450" + d="M 365.88924,498.60165 L 365.96345,497.0698 C 362.24294,496.57476 358.97692,494.69313 357.6526,491.94157 C 356.87864,490.33347 356.89022,488.67442 357.5305,487.16968 C 357.51449,487.81557 357.65339,488.46562 357.95816,489.09885 C 359.11143,491.49503 362.3734,493.01286 366.15032,493.22585 L 366.26395,490.86728 L 373.7315,495.07257 L 365.88924,498.60165 z " + style="fill:url(#linearGradient15464);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient15466);stroke-width:0.34810644;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + <rect + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/48x48/actions/amarok_music.png" + style="fill:#b9cff9;fill-opacity:0;stroke:none;stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + id="rect11712" + width="48" + height="48" + x="222.48914" + y="772.52484" /> + </g> + <g + id="g12778" + transform="translate(-4,-4.0270023)"> + <g + id="g15468" + transform="matrix(2.023518,0,0,2.913619,-411.17645,-646.14658)" + inkscape:export-filename="/home/lando/Projects/Amarok/icons/64/amarok_music.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <g + id="g15470" + transform="matrix(1.460806,0,0,1,73.51795,303.9834)"> + <path + id="path15472" + d="M 195.95019,181.04885 C 194.39504,181.04885 193.14527,181.60924 193.14527,183.24495 L 193.14527,196.47334 C 193.23404,197.06319 193.88474,197.6432 194.27525,197.75421 L 213.15145,197.75421 C 213.54503,197.64232 214.34446,197.06986 214.43045,196.47334 L 214.43045,183.24495 C 214.43045,181.60924 213.18066,181.04885 211.62551,181.04885 L 195.95019,181.04885 z " + style="fill:url(#linearGradient15488);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient15490);stroke-width:0.36855581;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:nodetypes="ccccccccc" /> + <path + style="fill:url(#linearGradient15492);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient15494);stroke-width:0.48347273;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 195.28464,185.37184 L 212.31829,185.37184 C 212.85753,185.37184 213.29165,185.7973 213.29165,186.32578 L 213.29165,195.95282 C 213.29165,196.4813 212.85753,196.90676 212.31829,196.90676 L 195.28464,196.90676 C 194.74539,196.90676 194.31128,196.4813 194.31128,195.95282 L 194.31128,186.32578 C 194.31128,185.7973 194.74539,185.37184 195.28464,185.37184 z " + id="path15474" /> + <path + id="path15476" + d="M 195.34943,185.35614 L 212.23903,185.35614 C 212.77369,185.35614 213.20416,185.78298 213.20416,186.31318 L 213.20416,195.97144 C 213.20416,196.50163 212.77369,196.92847 212.23903,196.92847 L 195.34943,196.92847 C 194.81482,196.92847 194.38434,196.50163 194.38434,195.97144 L 194.38434,186.31318 C 194.38434,185.78298 194.81482,185.35614 195.34943,185.35614 z " + style="opacity:0.99578091;fill:url(#radialGradient15496);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.57956129;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" /> + <path + sodipodi:nodetypes="ccccccccc" + style="opacity:0.15189873;fill:url(#linearGradient15498);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.71897209;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 196.14864,181.30386 C 194.62128,181.30386 193.39384,181.83707 193.39384,183.39347 L 193.39384,183.62626 C 193.481,184.18751 194.12009,184.7394 194.50362,184.84502 L 213.04249,184.84502 C 213.42902,184.73857 214.21419,184.19386 214.29863,183.62626 L 214.29863,183.39347 C 214.29863,181.83707 213.07119,181.30386 211.54382,181.30386 L 196.14864,181.30386 z " + id="path15478" /> + </g> + <path + style="opacity:0.39183673;fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#264a9e;stroke-width:0.17196566;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 365.43484,489.31976 L 365.43484,500.71207 L 365.55379,500.62705" + id="path15480" /> + <path + id="path15482" + d="M 375.54585,489.31976 L 375.54585,500.71207 L 375.66479,500.62705" + style="opacity:0.39183673;fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#264a9e;stroke-width:0.17196566;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + style="opacity:0.56734691;fill:#6785ed;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.28953668;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect15484" + width="27.502472" + height="3.4617176" + x="357.46518" + y="493.36218" /> + <path + style="fill:url(#linearGradient15500);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient15502);stroke-width:0.25958279;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 365.88924,498.60165 L 365.96345,497.0698 C 362.24294,496.57476 358.97692,494.69313 357.6526,491.94157 C 356.87864,490.33347 356.89022,488.67442 357.5305,487.16968 C 357.51449,487.81557 357.65339,488.46562 357.95816,489.09885 C 359.11143,491.49503 362.3734,493.01286 366.15032,493.22585 L 366.26395,490.86728 L 373.7315,495.07257 L 365.88924,498.60165 z " + id="path15486" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + </g> + <rect + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/64x64/actions/amarok_music.png" + y="759.3891" + x="307.9787" + height="64" + width="64" + id="rect11714" + style="fill:#b9cff9;fill-opacity:0;stroke:none;stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" /> + </g> + <g + id="g12965" + transform="translate(2,22.487205)"> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/icons/16/amarok_random_track.png" + transform="matrix(0.7260749,0,0,0.7260749,98.367392,522.6463)" + id="g15854"> + <g + transform="matrix(0.679898,0,0,0.691649,129.9465,272.0462)" + id="g15856"> + <path + sodipodi:nodetypes="ccccccc" + id="path15859" + d="M 441.06383,60.713305 L 441.06542,49.495395 L 450.43446,45.740673 L 459.81147,49.492971 L 459.81139,60.717581 L 450.43626,66.344952 L 441.06383,60.713305 z " + style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient16247);stroke-width:1.31171048;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <g + id="g15861"> + <path + sodipodi:nodetypes="ccccc" + id="path15863" + d="M 441.68929,60.362282 L 441.68599,49.920973 L 450.4374,54.20208 L 450.43841,65.617769 L 441.68929,60.362282 z " + style="fill:url(#linearGradient16249);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.0742841;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + sodipodi:nodetypes="ccccc" + id="path15865" + d="M 450.43385,54.203364 L 459.18738,49.922686 L 459.18632,60.363888 L 450.44184,65.613128 L 450.43385,54.203364 z " + style="fill:url(#linearGradient16251);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.07509746;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + sodipodi:nodetypes="ccccc" + id="path15867" + d="M 441.688,49.920202 L 450.43621,46.412453 L 459.18586,49.920584 L 450.43299,54.201471 L 441.688,49.920202 z " + style="fill:url(#linearGradient16253);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.79999995;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + <path + sodipodi:type="arc" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path15869" + sodipodi:cx="1749.9153" + sodipodi:cy="1631.8588" + sodipodi:rx="13.21095" + sodipodi:ry="22.49432" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + transform="matrix(0.129577,-2.161349e-2,2.662017e-2,3.635072e-2,180.263,31.06197)" /> + <path + transform="matrix(0.108645,-1.714179e-2,2.231983e-2,2.883016e-2,223.9065,30.65984)" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + sodipodi:ry="22.49432" + sodipodi:rx="13.21095" + sodipodi:cy="1631.8588" + sodipodi:cx="1749.9153" + id="path15871" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + sodipodi:type="arc" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path15873" + sodipodi:cx="1749.9153" + sodipodi:cy="1631.8588" + sodipodi:rx="13.21095" + sodipodi:ry="22.49432" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + transform="matrix(0.11443,-1.853676e-2,2.350834e-2,3.117637e-2,211.8228,31.4122)" /> + <path + transform="matrix(0.11443,-1.853676e-2,2.350834e-2,3.117637e-2,206.4981,31.4122)" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + sodipodi:ry="22.49432" + sodipodi:rx="13.21095" + sodipodi:cy="1631.8588" + sodipodi:cx="1749.9153" + id="path15875" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + sodipodi:type="arc" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path15877" + sodipodi:cx="1749.9153" + sodipodi:cy="1631.8588" + sodipodi:rx="13.21095" + sodipodi:ry="22.49432" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + transform="matrix(0.11443,-1.853676e-2,2.350834e-2,3.117637e-2,217.0829,31.4122)" /> + <path + transform="matrix(9.309428e-2,-2.478274e-2,-3.935631e-3,7.164391e-2,295.9902,-17.92848)" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + sodipodi:ry="22.49432" + sodipodi:rx="13.21095" + sodipodi:cy="1631.8588" + sodipodi:cx="1749.9153" + id="path15879" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + sodipodi:type="arc" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path15881" + sodipodi:cx="1749.9153" + sodipodi:cy="1631.8588" + sodipodi:rx="13.21095" + sodipodi:ry="22.49432" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + transform="matrix(8.73537e-2,-2.437095e-2,-2.584881e-3,6.94881e-2,306.3905,-13.26718)" /> + <path + transform="matrix(8.170002e-2,-2.177155e-2,-3.529402e-3,6.291213e-2,320.3887,-5.30443)" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + sodipodi:ry="22.49432" + sodipodi:rx="13.21095" + sodipodi:cy="1631.8588" + sodipodi:cx="1749.9153" + id="path15883" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + sodipodi:type="arc" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path15885" + sodipodi:cx="1749.9153" + sodipodi:cy="1631.8588" + sodipodi:rx="13.21095" + sodipodi:ry="22.49432" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + transform="matrix(8.021497e-2,-3.444141e-2,2.833695e-2,6.672447e-2,259.4509,9.15401)" /> + </g> + <path + id="path15887" + d="M 428.37452,297.84508 C 428.22442,297.84847 428.07302,297.86259 427.92139,297.87581 C 424.0395,298.21436 421.17357,301.70092 421.51712,305.65075 C 421.86067,309.60058 425.27805,312.53499 429.15995,312.19644 C 433.04185,311.8579 435.90777,308.37134 435.56422,304.42151 C 435.23411,300.62597 432.06699,297.76188 428.37452,297.84508 z " + style="fill:url(#linearGradient16255);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient16257);stroke-width:1.00883603;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <text + xml:space="preserve" + style="font-size:14.98907185px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#0368ff;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;display:inline;font-family:Nimbus Sans L" + x="396.89432" + y="331.12079" + id="text15889" + sodipodi:linespacing="125%" + transform="scale(1.068059,0.936278)"><tspan + sodipodi:role="line" + id="tspan15891" + x="396.89432" + y="331.12079">1</tspan></text> + </g> + <rect + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/16x16/actions/amarok_random_track.png" + style="fill:#b9cff9;fill-opacity:0;stroke:none;stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + id="rect10385" + width="16" + height="16" + x="404.03387" + y="738.17651" /> + </g> + <g + id="g12987" + transform="translate(2,21.491233)"> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/icons/22/amarok_random_track.png" + id="g20564" + transform="matrix(0.6639728,0,0,0.6639728,130.10838,537.4641)"> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Playback-modes/amarok_random_album.png" + transform="translate(29.62165,259.3922)" + id="g20566"> + <path + sodipodi:nodetypes="ccccccc" + id="path20568" + d="M 441.06383,60.713305 L 441.06542,49.495395 L 450.43446,45.740673 L 459.81147,49.492971 L 459.81139,60.717581 L 450.43626,66.344952 L 441.06383,60.713305 z " + style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient20606);stroke-width:1.37495267;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <g + id="g20570"> + <path + sodipodi:nodetypes="ccccc" + id="path20572" + d="M 441.68929,60.362282 L 441.68599,49.920973 L 450.4374,54.20208 L 450.43841,65.617769 L 441.68929,60.362282 z " + style="fill:url(#linearGradient20608);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.0742841;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + sodipodi:nodetypes="ccccc" + id="path20574" + d="M 450.43385,54.203364 L 459.18738,49.922686 L 459.18632,60.363888 L 450.44184,65.613128 L 450.43385,54.203364 z " + style="fill:url(#linearGradient20610);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.07509746;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + sodipodi:nodetypes="ccccc" + id="path20576" + d="M 441.688,49.920202 L 450.43621,46.412453 L 459.18586,49.920584 L 450.43299,54.201471 L 441.688,49.920202 z " + style="fill:url(#linearGradient20612);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.79999995;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + <path + sodipodi:type="arc" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path20578" + sodipodi:cx="1749.9153" + sodipodi:cy="1631.8588" + sodipodi:rx="13.21095" + sodipodi:ry="22.49432" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + transform="matrix(0.129577,-2.161349e-2,2.662017e-2,3.635072e-2,180.263,31.06197)" /> + <path + transform="matrix(0.108645,-1.714179e-2,2.231983e-2,2.883016e-2,223.9065,30.65984)" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + sodipodi:ry="22.49432" + sodipodi:rx="13.21095" + sodipodi:cy="1631.8588" + sodipodi:cx="1749.9153" + id="path20580" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + sodipodi:type="arc" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path20582" + sodipodi:cx="1749.9153" + sodipodi:cy="1631.8588" + sodipodi:rx="13.21095" + sodipodi:ry="22.49432" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + transform="matrix(0.11443,-1.853676e-2,2.350834e-2,3.117637e-2,211.8228,31.4122)" /> + <path + transform="matrix(0.11443,-1.853676e-2,2.350834e-2,3.117637e-2,206.4981,31.4122)" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + sodipodi:ry="22.49432" + sodipodi:rx="13.21095" + sodipodi:cy="1631.8588" + sodipodi:cx="1749.9153" + id="path20584" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + sodipodi:type="arc" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path20586" + sodipodi:cx="1749.9153" + sodipodi:cy="1631.8588" + sodipodi:rx="13.21095" + sodipodi:ry="22.49432" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + transform="matrix(0.11443,-1.853676e-2,2.350834e-2,3.117637e-2,217.0829,31.4122)" /> + <path + transform="matrix(9.309428e-2,-2.478274e-2,-3.935631e-3,7.164391e-2,295.9902,-17.92848)" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + sodipodi:ry="22.49432" + sodipodi:rx="13.21095" + sodipodi:cy="1631.8588" + sodipodi:cx="1749.9153" + id="path20588" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + sodipodi:type="arc" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path20590" + sodipodi:cx="1749.9153" + sodipodi:cy="1631.8588" + sodipodi:rx="13.21095" + sodipodi:ry="22.49432" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + transform="matrix(8.73537e-2,-2.437095e-2,-2.584881e-3,6.94881e-2,306.3905,-13.26718)" /> + <path + transform="matrix(8.170002e-2,-2.177155e-2,-3.529402e-3,6.291213e-2,320.3887,-5.30443)" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + sodipodi:ry="22.49432" + sodipodi:rx="13.21095" + sodipodi:cy="1631.8588" + sodipodi:cx="1749.9153" + id="path20592" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + sodipodi:type="arc" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path20594" + sodipodi:cx="1749.9153" + sodipodi:cy="1631.8588" + sodipodi:rx="13.21095" + sodipodi:ry="22.49432" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + transform="matrix(8.021497e-2,-3.444141e-2,2.833695e-2,6.672447e-2,259.4509,9.15401)" /> + </g> + <path + style="fill:url(#linearGradient20614);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient20616);stroke-width:0.50344837;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 467.9375,296.46875 C 467.66137,296.47137 467.4046,296.50728 467.125,296.53125 C 461.16026,297.04263 456.75337,302.28381 457.28125,308.25 C 457.80914,314.2162 463.06649,318.63637 469.03125,318.125 C 474.996,317.61363 479.40289,312.37244 478.875,306.40625 C 478.37187,300.71973 473.55212,296.41545 467.9375,296.46875 z " + id="path20596" /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Playback-modes/amarok_random_album.png" + style="fill:url(#linearGradient20618);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.4984282;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + d="M 467.22479,307.81392 C 467.06536,307.72336 466.77996,307.66658 466.66651,307.50668 L 460.04204,314.30051 C 461.97006,316.33026 464.34242,317.77496 467.04456,317.9126 L 467.22479,307.81392 z " + id="path20598" + sodipodi:nodetypes="ccccc" /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Playback-modes/amarok_random_album.png" + style="fill:url(#linearGradient20620);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.49565426;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + d="M 468.65401,306.75024 C 468.81307,306.83987 468.95247,306.95973 469.06559,307.11805 L 476.01773,300.20116 C 474.09479,298.19112 471.53712,296.87863 468.84036,296.74391 L 468.65401,306.75024 z " + id="path20600" + sodipodi:nodetypes="ccccc" /> + <text + xml:space="preserve" + style="font-size:24.09820747px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#0368ff;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;display:inline;font-family:Nimbus Sans L" + x="431.00589" + y="336.75986" + id="text20602" + sodipodi:linespacing="125%" + transform="scale(1.067851,0.93646)"><tspan + sodipodi:role="line" + id="tspan20604" + x="431.00589" + y="336.75986">1</tspan></text> + </g> + <rect + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/22x22/actions/amarok_random_track.png" + style="fill:#b9cff9;fill-opacity:0;stroke:none;stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + id="rect10387" + width="22" + height="22" + x="433.5351" + y="733.17249" /> + </g> + <g + id="g13011" + transform="translate(2,21.744895)"> + <g + transform="matrix(0.9657786,0,0,0.9657786,32.183782,438.25211)" + id="g15895" + inkscape:export-filename="/home/lando/Projects/Amarok/icons/32/amarok_random_track.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <g + id="g15897" + transform="translate(29.62165,259.3922)" + inkscape:export-filename="/home/lando/Projects/Amarok/Playback-modes/amarok_random_album.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <path + style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient16263);stroke-width:1.37495279;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 441.06383,60.713305 L 441.06542,49.495395 L 450.43446,45.740673 L 459.81147,49.492971 L 459.81139,60.717581 L 450.43626,66.344952 L 441.06383,60.713305 z " + id="path15899" + sodipodi:nodetypes="ccccccc" /> + <g + id="g15901"> + <path + style="fill:url(#linearGradient16265);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.0742841;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 441.68929,60.362282 L 441.68599,49.920973 L 450.4374,54.20208 L 450.43841,65.617769 L 441.68929,60.362282 z " + id="path15903" + sodipodi:nodetypes="ccccc" /> + <path + style="fill:url(#linearGradient16267);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.07509746;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 450.43385,54.203364 L 459.18738,49.922686 L 459.18632,60.363888 L 450.44184,65.613128 L 450.43385,54.203364 z " + id="path15905" + sodipodi:nodetypes="ccccc" /> + <path + style="fill:url(#linearGradient16269);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.79999995;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 441.688,49.920202 L 450.43621,46.412453 L 459.18586,49.920584 L 450.43299,54.201471 L 441.688,49.920202 z " + id="path15907" + sodipodi:nodetypes="ccccc" /> + </g> + <path + transform="matrix(0.129577,-2.161349e-2,2.662017e-2,3.635072e-2,180.263,31.06197)" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + sodipodi:ry="22.49432" + sodipodi:rx="13.21095" + sodipodi:cy="1631.8588" + sodipodi:cx="1749.9153" + id="path15909" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + sodipodi:type="arc" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path15911" + sodipodi:cx="1749.9153" + sodipodi:cy="1631.8588" + sodipodi:rx="13.21095" + sodipodi:ry="22.49432" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + transform="matrix(0.108645,-1.714179e-2,2.231983e-2,2.883016e-2,223.9065,30.65984)" /> + <path + transform="matrix(0.11443,-1.853676e-2,2.350834e-2,3.117637e-2,211.8228,31.4122)" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + sodipodi:ry="22.49432" + sodipodi:rx="13.21095" + sodipodi:cy="1631.8588" + sodipodi:cx="1749.9153" + id="path15913" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + sodipodi:type="arc" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path15915" + sodipodi:cx="1749.9153" + sodipodi:cy="1631.8588" + sodipodi:rx="13.21095" + sodipodi:ry="22.49432" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + transform="matrix(0.11443,-1.853676e-2,2.350834e-2,3.117637e-2,206.4981,31.4122)" /> + <path + transform="matrix(0.11443,-1.853676e-2,2.350834e-2,3.117637e-2,217.0829,31.4122)" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + sodipodi:ry="22.49432" + sodipodi:rx="13.21095" + sodipodi:cy="1631.8588" + sodipodi:cx="1749.9153" + id="path15917" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + sodipodi:type="arc" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path15919" + sodipodi:cx="1749.9153" + sodipodi:cy="1631.8588" + sodipodi:rx="13.21095" + sodipodi:ry="22.49432" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + transform="matrix(9.309428e-2,-2.478274e-2,-3.935631e-3,7.164391e-2,295.9902,-17.92848)" /> + <path + transform="matrix(8.73537e-2,-2.437095e-2,-2.584881e-3,6.94881e-2,306.3905,-13.26718)" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + sodipodi:ry="22.49432" + sodipodi:rx="13.21095" + sodipodi:cy="1631.8588" + sodipodi:cx="1749.9153" + id="path15921" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + sodipodi:type="arc" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path15923" + sodipodi:cx="1749.9153" + sodipodi:cy="1631.8588" + sodipodi:rx="13.21095" + sodipodi:ry="22.49432" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + transform="matrix(8.170002e-2,-2.177155e-2,-3.529402e-3,6.291213e-2,320.3887,-5.30443)" /> + <path + transform="matrix(8.021497e-2,-3.444141e-2,2.833695e-2,6.672447e-2,259.4509,9.15401)" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + sodipodi:ry="22.49432" + sodipodi:rx="13.21095" + sodipodi:cy="1631.8588" + sodipodi:cx="1749.9153" + id="path15925" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + </g> + <path + id="path15927" + d="M 467.9375,296.46875 C 467.66137,296.47137 467.4046,296.50728 467.125,296.53125 C 461.16026,297.04263 456.75337,302.28381 457.28125,308.25 C 457.80914,314.2162 463.06649,318.63637 469.03125,318.125 C 474.996,317.61363 479.40289,312.37244 478.875,306.40625 C 478.37187,300.71973 473.55212,296.41545 467.9375,296.46875 z " + style="fill:url(#linearGradient16271);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient16273);stroke-width:0.50344843;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + sodipodi:nodetypes="ccccc" + id="path15929" + d="M 467.22479,307.81392 C 467.06536,307.72336 466.77996,307.66658 466.66651,307.50668 L 460.04204,314.30051 C 461.97006,316.33026 464.34242,317.77496 467.04456,317.9126 L 467.22479,307.81392 z " + style="fill:url(#linearGradient16275);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.4984282;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + inkscape:export-filename="/home/lando/Projects/Amarok/Playback-modes/amarok_random_album.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:nodetypes="ccccc" + id="path15931" + d="M 468.65401,306.75024 C 468.81307,306.83987 468.95247,306.95973 469.06559,307.11805 L 476.01773,300.20116 C 474.09479,298.19112 471.53712,296.87863 468.84036,296.74391 L 468.65401,306.75024 z " + style="fill:url(#linearGradient16277);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.49565426;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + inkscape:export-filename="/home/lando/Projects/Amarok/Playback-modes/amarok_random_album.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <text + transform="scale(1.067851,0.93646)" + sodipodi:linespacing="125%" + id="text15933" + y="336.75986" + x="431.00589" + style="font-size:24.09820747px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#0368ff;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;display:inline;font-family:Nimbus Sans L" + xml:space="preserve"><tspan + y="336.75986" + x="431.00589" + id="tspan15935" + sodipodi:role="line">1</tspan></text> + </g> + <rect + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/32x32/actions/amarok_random_track.png" + y="722.91882" + x="473.53174" + height="32" + width="32" + id="rect10460" + style="fill:#b9cff9;fill-opacity:0;stroke:none;stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" /> + </g> + <g + id="g13035" + transform="translate(2,16.055198)"> + <g + transform="matrix(1.4486679,0,0,1.4486679,-137.82427,285.60836)" + id="g20680" + inkscape:export-filename="/home/lando/Projects/Amarok/icons/48/amarok_random_track.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <g + id="g20682" + transform="translate(29.62165,259.3922)" + inkscape:export-filename="/home/lando/Projects/Amarok/Playback-modes/amarok_random_album.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <path + style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient20722);stroke-width:1.37495363;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 441.06383,60.713305 L 441.06542,49.495395 L 450.43446,45.740673 L 459.81147,49.492971 L 459.81139,60.717581 L 450.43626,66.344952 L 441.06383,60.713305 z " + id="path20684" + sodipodi:nodetypes="ccccccc" /> + <g + id="g20686"> + <path + style="fill:url(#linearGradient20724);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.0742841;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 441.68929,60.362282 L 441.68599,49.920973 L 450.4374,54.20208 L 450.43841,65.617769 L 441.68929,60.362282 z " + id="path20688" + sodipodi:nodetypes="ccccc" /> + <path + style="fill:url(#linearGradient20726);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.07509746;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 450.43385,54.203364 L 459.18738,49.922686 L 459.18632,60.363888 L 450.44184,65.613128 L 450.43385,54.203364 z " + id="path20690" + sodipodi:nodetypes="ccccc" /> + <path + style="fill:url(#linearGradient20728);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.79999995;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 441.688,49.920202 L 450.43621,46.412453 L 459.18586,49.920584 L 450.43299,54.201471 L 441.688,49.920202 z " + id="path20692" + sodipodi:nodetypes="ccccc" /> + </g> + <path + transform="matrix(0.129577,-2.161349e-2,2.662017e-2,3.635072e-2,180.263,31.06197)" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + sodipodi:ry="22.49432" + sodipodi:rx="13.21095" + sodipodi:cy="1631.8588" + sodipodi:cx="1749.9153" + id="path20694" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + sodipodi:type="arc" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path20696" + sodipodi:cx="1749.9153" + sodipodi:cy="1631.8588" + sodipodi:rx="13.21095" + sodipodi:ry="22.49432" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + transform="matrix(0.108645,-1.714179e-2,2.231983e-2,2.883016e-2,223.9065,30.65984)" /> + <path + transform="matrix(0.11443,-1.853676e-2,2.350834e-2,3.117637e-2,211.8228,31.4122)" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + sodipodi:ry="22.49432" + sodipodi:rx="13.21095" + sodipodi:cy="1631.8588" + sodipodi:cx="1749.9153" + id="path20698" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + sodipodi:type="arc" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path20700" + sodipodi:cx="1749.9153" + sodipodi:cy="1631.8588" + sodipodi:rx="13.21095" + sodipodi:ry="22.49432" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + transform="matrix(0.11443,-1.853676e-2,2.350834e-2,3.117637e-2,206.4981,31.4122)" /> + <path + transform="matrix(0.11443,-1.853676e-2,2.350834e-2,3.117637e-2,217.0829,31.4122)" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + sodipodi:ry="22.49432" + sodipodi:rx="13.21095" + sodipodi:cy="1631.8588" + sodipodi:cx="1749.9153" + id="path20702" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + sodipodi:type="arc" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path20704" + sodipodi:cx="1749.9153" + sodipodi:cy="1631.8588" + sodipodi:rx="13.21095" + sodipodi:ry="22.49432" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + transform="matrix(9.309428e-2,-2.478274e-2,-3.935631e-3,7.164391e-2,295.9902,-17.92848)" /> + <path + transform="matrix(8.73537e-2,-2.437095e-2,-2.584881e-3,6.94881e-2,306.3905,-13.26718)" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + sodipodi:ry="22.49432" + sodipodi:rx="13.21095" + sodipodi:cy="1631.8588" + sodipodi:cx="1749.9153" + id="path20706" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + sodipodi:type="arc" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path20708" + sodipodi:cx="1749.9153" + sodipodi:cy="1631.8588" + sodipodi:rx="13.21095" + sodipodi:ry="22.49432" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + transform="matrix(8.170002e-2,-2.177155e-2,-3.529402e-3,6.291213e-2,320.3887,-5.30443)" /> + <path + transform="matrix(8.021497e-2,-3.444141e-2,2.833695e-2,6.672447e-2,259.4509,9.15401)" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + sodipodi:ry="22.49432" + sodipodi:rx="13.21095" + sodipodi:cy="1631.8588" + sodipodi:cx="1749.9153" + id="path20710" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + </g> + <path + id="path20712" + d="M 467.9375,296.46875 C 467.66137,296.47137 467.4046,296.50728 467.125,296.53125 C 461.16026,297.04263 456.75337,302.28381 457.28125,308.25 C 457.80914,314.2162 463.06649,318.63637 469.03125,318.125 C 474.996,317.61363 479.40289,312.37244 478.875,306.40625 C 478.37187,300.71973 473.55212,296.41545 467.9375,296.46875 z " + style="fill:url(#linearGradient20730);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient20732);stroke-width:0.50344872;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + sodipodi:nodetypes="ccccc" + id="path20714" + d="M 467.22479,307.81392 C 467.06536,307.72336 466.77996,307.66658 466.66651,307.50668 L 460.04204,314.30051 C 461.97006,316.33026 464.34242,317.77496 467.04456,317.9126 L 467.22479,307.81392 z " + style="fill:url(#linearGradient20734);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.4984282;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + inkscape:export-filename="/home/lando/Projects/Amarok/Playback-modes/amarok_random_album.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:nodetypes="ccccc" + id="path20716" + d="M 468.65401,306.75024 C 468.81307,306.83987 468.95247,306.95973 469.06559,307.11805 L 476.01773,300.20116 C 474.09479,298.19112 471.53712,296.87863 468.84036,296.74391 L 468.65401,306.75024 z " + style="fill:url(#linearGradient20736);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.49565426;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + inkscape:export-filename="/home/lando/Projects/Amarok/Playback-modes/amarok_random_album.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <text + transform="scale(1.067851,0.93646)" + sodipodi:linespacing="125%" + id="text20718" + y="336.75986" + x="431.00589" + style="font-size:24.09820747px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#0368ff;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;display:inline;font-family:Nimbus Sans L" + xml:space="preserve"><tspan + y="336.75986" + x="431.00589" + id="tspan20720" + sodipodi:role="line">1</tspan></text> + </g> + <rect + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/48x48/actions/amarok_random_track.png" + style="fill:#b9cff9;fill-opacity:0;stroke:none;stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + id="rect10462" + width="48" + height="48" + x="524.19763" + y="712.60852" /> + </g> + <g + id="g13059" + transform="translate(2,17.076072)"> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/icons/64/amarok_random_track.png" + id="g20622" + transform="matrix(1.9315573,0,0,1.9315573,-298.89664,126.25413)"> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Playback-modes/amarok_random_album.png" + transform="translate(29.62165,259.3922)" + id="g20624"> + <path + sodipodi:nodetypes="ccccccc" + id="path20626" + d="M 441.06383,60.713305 L 441.06542,49.495395 L 450.43446,45.740673 L 459.81147,49.492971 L 459.81139,60.717581 L 450.43626,66.344952 L 441.06383,60.713305 z " + style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient20664);stroke-width:1.37495339;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <g + id="g20628"> + <path + sodipodi:nodetypes="ccccc" + id="path20630" + d="M 441.68929,60.362282 L 441.68599,49.920973 L 450.4374,54.20208 L 450.43841,65.617769 L 441.68929,60.362282 z " + style="fill:url(#linearGradient20666);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.0742841;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + sodipodi:nodetypes="ccccc" + id="path20632" + d="M 450.43385,54.203364 L 459.18738,49.922686 L 459.18632,60.363888 L 450.44184,65.613128 L 450.43385,54.203364 z " + style="fill:url(#linearGradient20668);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.07509746;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + sodipodi:nodetypes="ccccc" + id="path20634" + d="M 441.688,49.920202 L 450.43621,46.412453 L 459.18586,49.920584 L 450.43299,54.201471 L 441.688,49.920202 z " + style="fill:url(#linearGradient20670);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.79999995;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + <path + sodipodi:type="arc" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path20636" + sodipodi:cx="1749.9153" + sodipodi:cy="1631.8588" + sodipodi:rx="13.21095" + sodipodi:ry="22.49432" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + transform="matrix(0.129577,-2.161349e-2,2.662017e-2,3.635072e-2,180.263,31.06197)" /> + <path + transform="matrix(0.108645,-1.714179e-2,2.231983e-2,2.883016e-2,223.9065,30.65984)" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + sodipodi:ry="22.49432" + sodipodi:rx="13.21095" + sodipodi:cy="1631.8588" + sodipodi:cx="1749.9153" + id="path20638" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + sodipodi:type="arc" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path20640" + sodipodi:cx="1749.9153" + sodipodi:cy="1631.8588" + sodipodi:rx="13.21095" + sodipodi:ry="22.49432" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + transform="matrix(0.11443,-1.853676e-2,2.350834e-2,3.117637e-2,211.8228,31.4122)" /> + <path + transform="matrix(0.11443,-1.853676e-2,2.350834e-2,3.117637e-2,206.4981,31.4122)" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + sodipodi:ry="22.49432" + sodipodi:rx="13.21095" + sodipodi:cy="1631.8588" + sodipodi:cx="1749.9153" + id="path20642" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + sodipodi:type="arc" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path20644" + sodipodi:cx="1749.9153" + sodipodi:cy="1631.8588" + sodipodi:rx="13.21095" + sodipodi:ry="22.49432" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + transform="matrix(0.11443,-1.853676e-2,2.350834e-2,3.117637e-2,217.0829,31.4122)" /> + <path + transform="matrix(9.309428e-2,-2.478274e-2,-3.935631e-3,7.164391e-2,295.9902,-17.92848)" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + sodipodi:ry="22.49432" + sodipodi:rx="13.21095" + sodipodi:cy="1631.8588" + sodipodi:cx="1749.9153" + id="path20646" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + sodipodi:type="arc" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path20648" + sodipodi:cx="1749.9153" + sodipodi:cy="1631.8588" + sodipodi:rx="13.21095" + sodipodi:ry="22.49432" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + transform="matrix(8.73537e-2,-2.437095e-2,-2.584881e-3,6.94881e-2,306.3905,-13.26718)" /> + <path + transform="matrix(8.170002e-2,-2.177155e-2,-3.529402e-3,6.291213e-2,320.3887,-5.30443)" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + sodipodi:ry="22.49432" + sodipodi:rx="13.21095" + sodipodi:cy="1631.8588" + sodipodi:cx="1749.9153" + id="path20650" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + sodipodi:type="arc" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path20652" + sodipodi:cx="1749.9153" + sodipodi:cy="1631.8588" + sodipodi:rx="13.21095" + sodipodi:ry="22.49432" + d="M 1763.1262 1631.8588 A 13.21095 22.49432 0 1 1 1736.7043,1631.8588 A 13.21095 22.49432 0 1 1 1763.1262 1631.8588 z" + transform="matrix(8.021497e-2,-3.444141e-2,2.833695e-2,6.672447e-2,259.4509,9.15401)" /> + </g> + <path + style="fill:url(#linearGradient20672);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient20674);stroke-width:0.50344861;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 467.9375,296.46875 C 467.66137,296.47137 467.4046,296.50728 467.125,296.53125 C 461.16026,297.04263 456.75337,302.28381 457.28125,308.25 C 457.80914,314.2162 463.06649,318.63637 469.03125,318.125 C 474.996,317.61363 479.40289,312.37244 478.875,306.40625 C 478.37187,300.71973 473.55212,296.41545 467.9375,296.46875 z " + id="path20654" /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Playback-modes/amarok_random_album.png" + style="fill:url(#linearGradient20676);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.4984282;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + d="M 467.22479,307.81392 C 467.06536,307.72336 466.77996,307.66658 466.66651,307.50668 L 460.04204,314.30051 C 461.97006,316.33026 464.34242,317.77496 467.04456,317.9126 L 467.22479,307.81392 z " + id="path20656" + sodipodi:nodetypes="ccccc" /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Playback-modes/amarok_random_album.png" + style="fill:url(#linearGradient20678);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.49565426;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + d="M 468.65401,306.75024 C 468.81307,306.83987 468.95247,306.95973 469.06559,307.11805 L 476.01773,300.20116 C 474.09479,298.19112 471.53712,296.87863 468.84036,296.74391 L 468.65401,306.75024 z " + id="path20658" + sodipodi:nodetypes="ccccc" /> + <text + xml:space="preserve" + style="font-size:24.09820747px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#0368ff;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;display:inline;font-family:Nimbus Sans L" + x="431.00589" + y="336.75986" + id="text20660" + sodipodi:linespacing="125%" + transform="scale(1.067851,0.93646)"><tspan + sodipodi:role="line" + id="tspan20662" + x="431.00589" + y="336.75986">1</tspan></text> + </g> + <rect + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/64x64/actions/amarok_random_track.png" + y="695.58765" + x="583.79932" + height="64" + width="64" + id="rect10464" + style="fill:#b9cff9;fill-opacity:0;stroke:none;stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" /> + </g> + <g + id="g13124" + transform="translate(2,17.613512)"> + <g + transform="matrix(2.916366,0,0,2.916366,-748.44574,-283.86322)" + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/64x64/actions/amarok_edit.png" + id="g20864"> + <path + sodipodi:nodetypes="ccccccccc" + id="path20866" + d="M 464.91192,385.96292 L 477.96066,385.96292 C 479.33086,385.96292 480.43394,387.22554 480.43394,388.7939 L 480.44383,401.93443 L 476.27396,406.93381 L 464.91192,406.93381 C 463.54173,406.93381 462.43864,405.67119 462.43864,404.10282 L 462.43864,388.7939 C 462.43864,387.22554 463.54173,385.96292 464.91192,385.96292 z " + style="fill:url(#linearGradient20878);fill-opacity:1;stroke:url(#linearGradient20880);stroke-width:0.67122036;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_edit.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:nodetypes="ccccccccc" + id="path20868" + d="M 464.9114,386.43614 L 477.95845,386.43614 C 479.05152,386.43614 479.93151,387.48364 479.93151,388.7848 L 479.93151,402.68513 L 476.68044,406.39761 L 464.9114,406.46466 C 463.81833,406.46466 462.93834,405.41717 462.93834,404.11601 L 462.93834,388.7848 C 462.93834,387.48364 463.81833,386.43614 464.9114,386.43614 z " + style="fill:url(#linearGradient20882);fill-opacity:1;stroke:none;stroke-width:0.90379584;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_edit.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:nodetypes="ccccz" + id="path20870" + d="M 476.01081,403.69086 L 476.00293,407.41759 C 476.00293,407.41759 480.95056,401.52169 480.95056,401.52169 L 478.0725,401.52056 C 476.85027,401.53365 476.01338,402.4747 476.01081,403.69086 z " + style="fill:#0004c1;fill-opacity:1;stroke:none;stroke-width:1.0039922;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_edit.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:nodetypes="ccccccccccc" + id="path20872" + d="M 469.25162,403.60326 L 469.30492,403.62987 C 469.42909,403.7149 469.61675,403.63861 469.83646,403.4987 L 471.3609,402.17867 L 480.77,386.82054 C 480.88297,386.63615 480.82382,386.39776 480.63738,386.28604 L 479.57584,385.6499 C 479.38941,385.53817 479.14837,385.59667 479.03541,385.78106 L 469.62631,401.13919 L 469.119,403.06876 C 469.0683,403.32431 469.06518,403.49153 469.25162,403.60326 z " + style="fill:url(#linearGradient20884);fill-opacity:1;fill-rule:evenodd;stroke:#0b21a8;stroke-width:0.24150403;stroke-miterlimit:4;stroke-dashoffset:0;stroke-opacity:1" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_edit.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + style="fill:url(#linearGradient20886);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.44624999;stroke-miterlimit:4;stroke-dashoffset:0;stroke-opacity:1" + d="M 469.19923,403.6605 L 469.24363,403.68711 C 469.43007,403.79883 469.58215,403.68696 469.78407,403.55594 L 471.30851,402.23591 L 470.68475,401.31866 L 469.57392,401.19643 L 469.06661,403.126 C 468.98922,403.35487 469.01279,403.54877 469.19923,403.6605 z " + id="path20874" + sodipodi:nodetypes="cccccccc" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_edit.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="M 469.10967,402.93183 L 469.00293,402.95852 L 469.4268,402.88736 L 469.60471,403.05637 L 469.84282,403.08305 L 469.97624,403.38201 C 469.97624,403.38201 469.60564,403.80356 469.31116,403.7413 C 469.01836,403.67939 469.01183,403.33753 469.01183,403.33753 L 469.10967,402.93183 z " + id="path20876" + sodipodi:nodetypes="cccccczcc" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_edit.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + </g> + <rect + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/64x64/actions/amarok_edit.png" + style="fill:#b9cff9;fill-opacity:0;stroke:none;stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + id="rect10466" + width="64" + height="64" + x="594.69897" + y="840.31555" /> + </g> + <g + id="g13114" + transform="translate(2,17.10305)"> + <g + id="g20840" + inkscape:export-filename="/home/lando/Projects/Amarok/icons/48/amarok_edit.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" + transform="matrix(2.187274,0,0,2.187274,-478.38053,13.69212)"> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_edit.png" + style="fill:url(#linearGradient20854);fill-opacity:1;stroke:url(#linearGradient20856);stroke-width:0.67122036;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 464.91192,385.96292 L 477.96066,385.96292 C 479.33086,385.96292 480.43394,387.22554 480.43394,388.7939 L 480.44383,401.93443 L 476.27396,406.93381 L 464.91192,406.93381 C 463.54173,406.93381 462.43864,405.67119 462.43864,404.10282 L 462.43864,388.7939 C 462.43864,387.22554 463.54173,385.96292 464.91192,385.96292 z " + id="path20842" + sodipodi:nodetypes="ccccccccc" /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_edit.png" + style="fill:url(#linearGradient20858);fill-opacity:1;stroke:none;stroke-width:0.90379584;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 464.9114,386.43614 L 477.95845,386.43614 C 479.05152,386.43614 479.93151,387.48364 479.93151,388.7848 L 479.93151,402.68513 L 476.68044,406.39761 L 464.9114,406.46466 C 463.81833,406.46466 462.93834,405.41717 462.93834,404.11601 L 462.93834,388.7848 C 462.93834,387.48364 463.81833,386.43614 464.9114,386.43614 z " + id="path20844" + sodipodi:nodetypes="ccccccccc" /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_edit.png" + style="fill:#0004c1;fill-opacity:1;stroke:none;stroke-width:1.0039922;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 476.01081,403.69086 L 476.00293,407.41759 C 476.00293,407.41759 480.95056,401.52169 480.95056,401.52169 L 478.0725,401.52056 C 476.85027,401.53365 476.01338,402.4747 476.01081,403.69086 z " + id="path20846" + sodipodi:nodetypes="ccccz" /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_edit.png" + style="fill:url(#linearGradient20860);fill-opacity:1;fill-rule:evenodd;stroke:#0b21a8;stroke-width:0.24150403;stroke-miterlimit:4;stroke-dashoffset:0;stroke-opacity:1" + d="M 469.25162,403.60326 L 469.30492,403.62987 C 469.42909,403.7149 469.61675,403.63861 469.83646,403.4987 L 471.3609,402.17867 L 480.77,386.82054 C 480.88297,386.63615 480.82382,386.39776 480.63738,386.28604 L 479.57584,385.6499 C 479.38941,385.53817 479.14837,385.59667 479.03541,385.78106 L 469.62631,401.13919 L 469.119,403.06876 C 469.0683,403.32431 469.06518,403.49153 469.25162,403.60326 z " + id="path20848" + sodipodi:nodetypes="ccccccccccc" /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_edit.png" + sodipodi:nodetypes="cccccccc" + id="path20850" + d="M 469.19923,403.6605 L 469.24363,403.68711 C 469.43007,403.79883 469.58215,403.68696 469.78407,403.55594 L 471.30851,402.23591 L 470.68475,401.31866 L 469.57392,401.19643 L 469.06661,403.126 C 468.98922,403.35487 469.01279,403.54877 469.19923,403.6605 z " + style="fill:url(#linearGradient20862);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.44624999;stroke-miterlimit:4;stroke-dashoffset:0;stroke-opacity:1" /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_edit.png" + sodipodi:nodetypes="cccccczcc" + id="path20852" + d="M 469.10967,402.93183 L 469.00293,402.95852 L 469.4268,402.88736 L 469.60471,403.05637 L 469.84282,403.08305 L 469.97624,403.38201 C 469.97624,403.38201 469.60564,403.80356 469.31116,403.7413 C 469.01836,403.67939 469.01183,403.33753 469.01183,403.33753 L 469.10967,402.93183 z " + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + </g> + <rect + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/48x48/actions/amarok_edit.png" + y="856.82605" + x="528.97778" + height="48" + width="48" + id="rect10468" + style="fill:#b9cff9;fill-opacity:0;stroke:none;stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" /> + </g> + <g + id="g13104" + transform="translate(2,16.055198)"> + <g + transform="matrix(1.458183,0,0,1.458183,-181.81137,311.78447)" + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/icons/32/amarok_edit.png" + id="g15951"> + <path + sodipodi:nodetypes="ccccccccc" + id="path15953" + d="M 464.91192,385.96292 L 477.96066,385.96292 C 479.33086,385.96292 480.43394,387.22554 480.43394,388.7939 L 480.44383,401.93443 L 476.27396,406.93381 L 464.91192,406.93381 C 463.54173,406.93381 462.43864,405.67119 462.43864,404.10282 L 462.43864,388.7939 C 462.43864,387.22554 463.54173,385.96292 464.91192,385.96292 z " + style="fill:url(#linearGradient16291);fill-opacity:1;stroke:url(#linearGradient16293);stroke-width:0.67122036;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_edit.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:nodetypes="ccccccccc" + id="path15955" + d="M 464.9114,386.43614 L 477.95845,386.43614 C 479.05152,386.43614 479.93151,387.48364 479.93151,388.7848 L 479.93151,402.68513 L 476.68044,406.39761 L 464.9114,406.46466 C 463.81833,406.46466 462.93834,405.41717 462.93834,404.11601 L 462.93834,388.7848 C 462.93834,387.48364 463.81833,386.43614 464.9114,386.43614 z " + style="fill:url(#linearGradient16295);fill-opacity:1;stroke:none;stroke-width:0.90379584;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_edit.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:nodetypes="ccccz" + id="path15957" + d="M 476.01081,403.69086 L 476.00293,407.41759 C 476.00293,407.41759 480.95056,401.52169 480.95056,401.52169 L 478.0725,401.52056 C 476.85027,401.53365 476.01338,402.4747 476.01081,403.69086 z " + style="fill:#0004c1;fill-opacity:1;stroke:none;stroke-width:1.0039922;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_edit.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:nodetypes="ccccccccccc" + id="path15959" + d="M 469.25162,403.60326 L 469.30492,403.62987 C 469.42909,403.7149 469.61675,403.63861 469.83646,403.4987 L 471.3609,402.17867 L 480.77,386.82054 C 480.88297,386.63615 480.82382,386.39776 480.63738,386.28604 L 479.57584,385.6499 C 479.38941,385.53817 479.14837,385.59667 479.03541,385.78106 L 469.62631,401.13919 L 469.119,403.06876 C 469.0683,403.32431 469.06518,403.49153 469.25162,403.60326 z " + style="fill:url(#linearGradient16297);fill-opacity:1;fill-rule:evenodd;stroke:#0b21a8;stroke-width:0.24150403;stroke-miterlimit:4;stroke-dashoffset:0;stroke-opacity:1" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_edit.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + style="fill:url(#linearGradient16299);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.44624999;stroke-miterlimit:4;stroke-dashoffset:0;stroke-opacity:1" + d="M 469.19923,403.6605 L 469.24363,403.68711 C 469.43007,403.79883 469.58215,403.68696 469.78407,403.55594 L 471.30851,402.23591 L 470.68475,401.31866 L 469.57392,401.19643 L 469.06661,403.126 C 468.98922,403.35487 469.01279,403.54877 469.19923,403.6605 z " + id="path15961" + sodipodi:nodetypes="cccccccc" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_edit.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="M 469.10967,402.93183 L 469.00293,402.95852 L 469.4268,402.88736 L 469.60471,403.05637 L 469.84282,403.08305 L 469.97624,403.38201 C 469.97624,403.38201 469.60564,403.80356 469.31116,403.7413 C 469.01836,403.67939 469.01183,403.33753 469.01183,403.33753 L 469.10967,402.93183 z " + id="path15963" + sodipodi:nodetypes="cccccczcc" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_edit.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + </g> + <rect + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/32x32/actions/amarok_edit.png" + style="fill:#b9cff9;fill-opacity:0;stroke:none;stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + id="rect10470" + width="32" + height="32" + x="489.76099" + y="873.8739" /> + </g> + <g + id="g13094" + transform="translate(2,16.707127)"> + <g + id="g15784" + inkscape:export-filename="/home/lando/Projects/Amarok/icons/22/amarok_edit.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" + transform="matrix(0.999803,0,0,0.999803,-7.8444981,497.8803)"> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_edit.png" + style="fill:url(#linearGradient16207);fill-opacity:1;stroke:url(#linearGradient16209);stroke-width:0.97624999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 464.91192,385.96292 L 477.96066,385.96292 C 479.33086,385.96292 480.43394,387.22554 480.43394,388.7939 L 480.44383,401.93443 L 476.27396,406.93381 L 464.91192,406.93381 C 463.54173,406.93381 462.43864,405.67119 462.43864,404.10282 L 462.43864,388.7939 C 462.43864,387.22554 463.54173,385.96292 464.91192,385.96292 z " + id="path15786" + sodipodi:nodetypes="ccccccccc" /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_edit.png" + style="fill:url(#linearGradient16211);fill-opacity:1;stroke:none;stroke-width:0.90379584;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 464.9114,386.43614 L 477.95845,386.43614 C 479.05152,386.43614 479.93151,387.48364 479.93151,388.7848 L 479.93151,402.68513 L 476.68044,406.39761 L 464.9114,406.46466 C 463.81833,406.46466 462.93834,405.41717 462.93834,404.11601 L 462.93834,388.7848 C 462.93834,387.48364 463.81833,386.43614 464.9114,386.43614 z " + id="path15788" + sodipodi:nodetypes="ccccccccc" /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_edit.png" + style="fill:#0004c1;fill-opacity:1;stroke:none;stroke-width:1.0039922;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 476.01081,403.69086 L 476.00293,407.41759 C 476.00293,407.41759 480.95056,401.52169 480.95056,401.52169 L 478.0725,401.52056 C 476.85027,401.53365 476.01338,402.4747 476.01081,403.69086 z " + id="path15790" + sodipodi:nodetypes="ccccz" /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_edit.png" + style="fill:url(#linearGradient16213);fill-opacity:1;fill-rule:evenodd;stroke:#0b21a8;stroke-width:0.35125321;stroke-miterlimit:4;stroke-dashoffset:0;stroke-opacity:1" + d="M 469.25162,403.60326 L 469.30492,403.62987 C 469.42909,403.7149 469.61675,403.63861 469.83646,403.4987 L 471.3609,402.17867 L 480.77,386.82054 C 480.88297,386.63615 480.82382,386.39776 480.63738,386.28604 L 479.57584,385.6499 C 479.38941,385.53817 479.14837,385.59667 479.03541,385.78106 L 469.62631,401.13919 L 469.119,403.06876 C 469.0683,403.32431 469.06518,403.49153 469.25162,403.60326 z " + id="path15792" + sodipodi:nodetypes="ccccccccccc" /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_edit.png" + sodipodi:nodetypes="cccccccc" + id="path15794" + d="M 469.19923,403.6605 L 469.24363,403.68711 C 469.43007,403.79883 469.58215,403.68696 469.78407,403.55594 L 471.30851,402.23591 L 470.68475,401.31866 L 469.57392,401.19643 L 469.06661,403.126 C 468.98922,403.35487 469.01279,403.54877 469.19923,403.6605 z " + style="fill:url(#linearGradient16215);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.44624999;stroke-miterlimit:4;stroke-dashoffset:0;stroke-opacity:1" /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_edit.png" + sodipodi:nodetypes="cccccczcc" + id="path15796" + d="M 469.10967,402.93183 L 469.00293,402.95852 L 469.4268,402.88736 L 469.60471,403.05637 L 469.84282,403.08305 L 469.97624,403.38201 C 469.97624,403.38201 469.60564,403.80356 469.31116,403.7413 C 469.01836,403.67939 469.01183,403.33753 469.01183,403.33753 L 469.10967,402.93183 z " + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + </g> + <rect + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/22x22/actions/amarok_edit.png" + y="883.22192" + x="452.53937" + height="22" + width="22" + id="rect10499" + style="fill:#b9cff9;fill-opacity:0;stroke:none;stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" /> + </g> + <g + id="g13084" + transform="translate(2,16.426292)"> + <g + transform="matrix(0.727129,0,0,0.727129,78.695392,609.2545)" + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/icons/16/amarok_edit.png" + id="g20816"> + <path + sodipodi:nodetypes="ccccccccc" + id="path20818" + d="M 464.91192,385.96292 L 477.96066,385.96292 C 479.33086,385.96292 480.43394,387.22554 480.43394,388.7939 L 480.44383,401.93443 L 476.27396,406.93381 L 464.91192,406.93381 C 463.54173,406.93381 462.43864,405.67119 462.43864,404.10282 L 462.43864,388.7939 C 462.43864,387.22554 463.54173,385.96292 464.91192,385.96292 z " + style="fill:url(#linearGradient20830);fill-opacity:1;stroke:url(#linearGradient20832);stroke-width:0.97624999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_edit.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:nodetypes="ccccccccc" + id="path20820" + d="M 464.9114,386.43614 L 477.95845,386.43614 C 479.05152,386.43614 479.93151,387.48364 479.93151,388.7848 L 479.93151,402.68513 L 476.68044,406.39761 L 464.9114,406.46466 C 463.81833,406.46466 462.93834,405.41717 462.93834,404.11601 L 462.93834,388.7848 C 462.93834,387.48364 463.81833,386.43614 464.9114,386.43614 z " + style="fill:url(#linearGradient20834);fill-opacity:1;stroke:none;stroke-width:0.90379584;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_edit.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:nodetypes="ccccz" + id="path20822" + d="M 476.01081,403.69086 L 476.00293,407.41759 C 476.00293,407.41759 480.95056,401.52169 480.95056,401.52169 L 478.0725,401.52056 C 476.85027,401.53365 476.01338,402.4747 476.01081,403.69086 z " + style="fill:#0004c1;fill-opacity:1;stroke:none;stroke-width:1.0039922;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_edit.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:nodetypes="ccccccccccc" + id="path20824" + d="M 469.25162,403.60326 L 469.30492,403.62987 C 469.42909,403.7149 469.61675,403.63861 469.83646,403.4987 L 471.3609,402.17867 L 480.77,386.82054 C 480.88297,386.63615 480.82382,386.39776 480.63738,386.28604 L 479.57584,385.6499 C 479.38941,385.53817 479.14837,385.59667 479.03541,385.78106 L 469.62631,401.13919 L 469.119,403.06876 C 469.0683,403.32431 469.06518,403.49153 469.25162,403.60326 z " + style="fill:url(#linearGradient20836);fill-opacity:1;fill-rule:evenodd;stroke:#0b21a8;stroke-width:0.35125321;stroke-miterlimit:4;stroke-dashoffset:0;stroke-opacity:1" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_edit.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + style="fill:url(#linearGradient20838);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.44624999;stroke-miterlimit:4;stroke-dashoffset:0;stroke-opacity:1" + d="M 469.19923,403.6605 L 469.24363,403.68711 C 469.43007,403.79883 469.58215,403.68696 469.78407,403.55594 L 471.30851,402.23591 L 470.68475,401.31866 L 469.57392,401.19643 L 469.06661,403.126 C 468.98922,403.35487 469.01279,403.54877 469.19923,403.6605 z " + id="path20826" + sodipodi:nodetypes="cccccccc" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_edit.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="M 469.10967,402.93183 L 469.00293,402.95852 L 469.4268,402.88736 L 469.60471,403.05637 L 469.84282,403.08305 L 469.97624,403.38201 C 469.97624,403.38201 469.60564,403.80356 469.31116,403.7413 C 469.01836,403.67939 469.01183,403.33753 469.01183,403.33753 L 469.10967,402.93183 z " + id="path20828" + sodipodi:nodetypes="cccccczcc" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_edit.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + </g> + <rect + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/16x16/actions/amarok_edit.png" + y="889.50281" + x="413.51978" + height="16" + width="16" + id="rect10501" + style="fill:#b9cff9;fill-opacity:0;stroke:none;stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" /> + </g> + <g + id="g13134" + transform="translate(1,30.797996)"> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/icons/16/amarok_lyrics.png" + transform="matrix(0.729026,0,0,0.729026,109.51521,817.6187)" + id="g20888"> + <path + style="fill:url(#linearGradient20896);fill-opacity:1;stroke:url(#linearGradient20898);stroke-width:0.97620374;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 415.62177,194.97276 L 428.67052,194.97276 C 430.04072,194.97276 431.1438,196.23538 431.1438,197.80374 L 431.15369,210.94427 L 426.98381,215.94365 L 415.62177,215.94365 C 414.25159,215.94365 413.1485,214.68103 413.1485,213.11266 L 413.1485,197.80374 C 413.1485,196.23538 414.25159,194.97276 415.62177,194.97276 z " + id="path20890" + sodipodi:nodetypes="ccccccccc" /> + <path + style="fill:url(#linearGradient20900);fill-opacity:1;stroke:none;stroke-width:0.90379584;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 415.62757,195.44394 L 428.67462,195.44394 C 429.76769,195.44394 430.64768,196.49144 430.64768,197.7926 L 430.64768,211.69293 L 427.39661,215.40541 L 415.62757,215.47246 C 414.5345,215.47246 413.65451,214.42497 413.65451,213.12381 L 413.65451,197.7926 C 413.65451,196.49144 414.5345,195.44394 415.62757,195.44394 z " + id="path20892" + sodipodi:nodetypes="ccccccccc" /> + <path + style="fill:#0004c1;fill-opacity:1;stroke:none;stroke-width:1.0039922;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 426.72067,212.7007 L 426.71279,216.42743 C 426.71279,216.42743 431.66042,210.53153 431.66042,210.53153 L 428.86955,210.48681 C 427.64732,210.4999 426.72324,211.48454 426.72067,212.7007 z " + id="path20894" + sodipodi:nodetypes="ccccz" /> + </g> + <rect + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/16x16/actions/amarok_lyrics.png" + style="fill:#b9cff9;fill-opacity:0;stroke:none;stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + id="rect10503" + width="16" + height="16" + x="409.28113" + y="959.40308" /> + </g> + <g + id="g13141" + transform="translate(-2.029541,28.602703)"> + <g + id="g15776" + transform="matrix(1.002411,0,0,1.002411,30.851292,760.6448)" + inkscape:export-filename="/home/lando/Projects/Amarok/icons/22/amarok_lyrics.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <path + sodipodi:nodetypes="ccccccccc" + id="path15778" + d="M 415.62177,194.97276 L 428.67052,194.97276 C 430.04072,194.97276 431.1438,196.23538 431.1438,197.80374 L 431.15369,210.94427 L 426.98381,215.94365 L 415.62177,215.94365 C 414.25159,215.94365 413.1485,214.68103 413.1485,213.11266 L 413.1485,197.80374 C 413.1485,196.23538 414.25159,194.97276 415.62177,194.97276 z " + style="fill:url(#linearGradient13189);fill-opacity:1;stroke:url(#linearGradient13191);stroke-width:0.97620374;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" /> + <path + sodipodi:nodetypes="ccccccccc" + id="path15780" + d="M 415.62757,195.44394 L 428.67462,195.44394 C 429.76769,195.44394 430.64768,196.49144 430.64768,197.7926 L 430.64768,211.69293 L 427.39661,215.40541 L 415.62757,215.47246 C 414.5345,215.47246 413.65451,214.42497 413.65451,213.12381 L 413.65451,197.7926 C 413.65451,196.49144 414.5345,195.44394 415.62757,195.44394 z " + style="fill:url(#linearGradient13193);fill-opacity:1;stroke:none;stroke-width:0.90379584;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" /> + <path + sodipodi:nodetypes="ccccz" + id="path15782" + d="M 426.72067,212.7007 L 426.71279,216.42743 C 426.71279,216.42743 431.66042,210.53153 431.66042,210.53153 L 428.86955,210.48681 C 427.64732,210.4999 426.72324,211.48454 426.72067,212.7007 z " + style="fill:#0004c1;fill-opacity:1;stroke:none;stroke-width:1.0039922;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" /> + </g> + <rect + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/22x22/actions/amarok_lyrics.png" + style="fill:#b9cff9;fill-opacity:0;stroke:none;stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + id="rect10505" + width="22" + height="22" + x="443.02954" + y="955.59827" /> + </g> + <g + id="g13148" + transform="translate(1,29.055198)"> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/icons/32/amarok_lyrics.png" + transform="matrix(1.468602,0,0,1.468602,-131.46528,659.3001)" + id="g15965"> + <path + style="fill:url(#linearGradient16301);fill-opacity:1;stroke:url(#linearGradient16303);stroke-width:0.6695261;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 415.62177,194.97276 L 428.67052,194.97276 C 430.04072,194.97276 431.1438,196.23538 431.1438,197.80374 L 431.15369,210.94427 L 426.98381,215.94365 L 415.62177,215.94365 C 414.25159,215.94365 413.1485,214.68103 413.1485,213.11266 L 413.1485,197.80374 C 413.1485,196.23538 414.25159,194.97276 415.62177,194.97276 z " + id="path15967" + sodipodi:nodetypes="ccccccccc" /> + <path + style="fill:url(#linearGradient16305);fill-opacity:1;stroke:none;stroke-width:0.90379584;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 415.62757,195.44394 L 428.67462,195.44394 C 429.76769,195.44394 430.64768,196.49144 430.64768,197.7926 L 430.64768,211.69293 L 427.39661,215.40541 L 415.62757,215.47246 C 414.5345,215.47246 413.65451,214.42497 413.65451,213.12381 L 413.65451,197.7926 C 413.65451,196.49144 414.5345,195.44394 415.62757,195.44394 z " + id="path15969" + sodipodi:nodetypes="ccccccccc" /> + <path + style="fill:#0004c1;fill-opacity:1;stroke:none;stroke-width:1.0039922;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 426.72067,212.7007 L 426.71279,216.42743 C 426.71279,216.42743 431.66042,210.53153 431.66042,210.53153 L 428.86955,210.48681 C 427.64732,210.4999 426.72324,211.48454 426.72067,212.7007 z " + id="path15971" + sodipodi:nodetypes="ccccz" /> + </g> + <rect + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/32x32/actions/amarok_lyrics.png" + y="945.14587" + x="472.63293" + height="32" + width="32" + id="rect10507" + style="fill:#b9cff9;fill-opacity:0;stroke:none;stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" /> + </g> + <g + id="g13155" + transform="translate(1,29.579237)"> + <g + id="g20902" + transform="matrix(2.202903,0,0,2.202903,-381.84081,499.8532)" + inkscape:export-filename="/home/lando/Projects/Amarok/icons/48/amarok_lyrics.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <path + sodipodi:nodetypes="ccccccccc" + id="path20904" + d="M 415.62177,194.97276 L 428.67052,194.97276 C 430.04072,194.97276 431.1438,196.23538 431.1438,197.80374 L 431.15369,210.94427 L 426.98381,215.94365 L 415.62177,215.94365 C 414.25159,215.94365 413.1485,214.68103 413.1485,213.11266 L 413.1485,197.80374 C 413.1485,196.23538 414.25159,194.97276 415.62177,194.97276 z " + style="fill:url(#linearGradient20910);fill-opacity:1;stroke:url(#linearGradient20912);stroke-width:0.6695261;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" /> + <path + sodipodi:nodetypes="ccccccccc" + id="path20906" + d="M 415.62757,195.44394 L 428.67462,195.44394 C 429.76769,195.44394 430.64768,196.49144 430.64768,197.7926 L 430.64768,211.69293 L 427.39661,215.40541 L 415.62757,215.47246 C 414.5345,215.47246 413.65451,214.42497 413.65451,213.12381 L 413.65451,197.7926 C 413.65451,196.49144 414.5345,195.44394 415.62757,195.44394 z " + style="fill:url(#linearGradient20914);fill-opacity:1;stroke:none;stroke-width:0.90379584;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" /> + <path + sodipodi:nodetypes="ccccz" + id="path20908" + d="M 426.72067,212.7007 L 426.71279,216.42743 C 426.71279,216.42743 431.66042,210.53153 431.66042,210.53153 L 428.86955,210.48681 C 427.64732,210.4999 426.72324,211.48454 426.72067,212.7007 z " + style="fill:#0004c1;fill-opacity:1;stroke:none;stroke-width:1.0039922;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" /> + </g> + <rect + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/48x48/actions/amarok_lyrics.png" + style="fill:#b9cff9;fill-opacity:0;stroke:none;stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + id="rect10509" + width="48" + height="48" + x="524.30652" + y="928.62183" /> + </g> + <g + id="g13162" + transform="translate(1,31.391459)"> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/icons/64/amarok_lyrics.png" + transform="matrix(2.937204,0,0,2.937204,-615.05453,339.1181)" + id="g20916"> + <path + style="fill:url(#linearGradient20924);fill-opacity:1;stroke:url(#linearGradient20926);stroke-width:0.6695261;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 415.62177,194.97276 L 428.67052,194.97276 C 430.04072,194.97276 431.1438,196.23538 431.1438,197.80374 L 431.15369,210.94427 L 426.98381,215.94365 L 415.62177,215.94365 C 414.25159,215.94365 413.1485,214.68103 413.1485,213.11266 L 413.1485,197.80374 C 413.1485,196.23538 414.25159,194.97276 415.62177,194.97276 z " + id="path20918" + sodipodi:nodetypes="ccccccccc" /> + <path + style="fill:url(#linearGradient20928);fill-opacity:1;stroke:none;stroke-width:0.90379584;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 415.62757,195.44394 L 428.67462,195.44394 C 429.76769,195.44394 430.64768,196.49144 430.64768,197.7926 L 430.64768,211.69293 L 427.39661,215.40541 L 415.62757,215.47246 C 414.5345,215.47246 413.65451,214.42497 413.65451,213.12381 L 413.65451,197.7926 C 413.65451,196.49144 414.5345,195.44394 415.62757,195.44394 z " + id="path20920" + sodipodi:nodetypes="ccccccccc" /> + <path + style="fill:#0004c1;fill-opacity:1;stroke:none;stroke-width:1.0039922;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 426.72067,212.7007 L 426.71279,216.42743 C 426.71279,216.42743 431.66042,210.53153 431.66042,210.53153 L 428.86955,210.48681 C 427.64732,210.4999 426.72324,211.48454 426.72067,212.7007 z " + id="path20922" + sodipodi:nodetypes="ccccz" /> + </g> + <rect + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/64x64/actions/amarok_lyrics.png" + y="910.80957" + x="593.14191" + height="64" + width="64" + id="rect10511" + style="fill:#b9cff9;fill-opacity:0;stroke:none;stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" /> + </g> + <g + id="g13170" + transform="translate(-2.6098633,38.210494)"> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/22x22/actions/amarok_add_lyrics.png" + id="g20930" + transform="matrix(0.729026,0,0,0.729026,96.141042,704.3974)"> + <g + transform="translate(11.93772,253.0084)" + id="g20932" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_add_lyrics.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <path + style="fill:url(#linearGradient13222);fill-opacity:1;stroke:url(#linearGradient13224);stroke-width:0.97620374;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 415.62177,194.97276 L 428.67052,194.97276 C 430.04072,194.97276 431.1438,196.23538 431.1438,197.80374 L 431.15369,210.94427 L 426.98381,215.94365 L 415.62177,215.94365 C 414.25159,215.94365 413.1485,214.68103 413.1485,213.11266 L 413.1485,197.80374 C 413.1485,196.23538 414.25159,194.97276 415.62177,194.97276 z " + id="path20934" + sodipodi:nodetypes="ccccccccc" /> + <path + style="fill:url(#linearGradient13226);fill-opacity:1;stroke:none;stroke-width:0.90379584;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 415.62757,195.44394 L 428.67462,195.44394 C 429.76769,195.44394 430.64768,196.49144 430.64768,197.7926 L 430.64768,211.69293 L 427.39661,215.40541 L 415.62757,215.47246 C 414.5345,215.47246 413.65451,214.42497 413.65451,213.12381 L 413.65451,197.7926 C 413.65451,196.49144 414.5345,195.44394 415.62757,195.44394 z " + id="path20936" + sodipodi:nodetypes="ccccccccc" /> + <path + style="fill:#0004c1;fill-opacity:1;stroke:none;stroke-width:1.0039922;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 426.72067,212.7007 L 426.71279,216.42743 C 426.71279,216.42743 431.66042,210.53153 431.66042,210.53153 L 428.86955,210.48681 C 427.64732,210.4999 426.72324,211.48454 426.72067,212.7007 z " + id="path20938" + sodipodi:nodetypes="ccccz" /> + </g> + <path + id="path20940" + d="M 437.69697,460.3288 L 433.99587,460.32878 L 433.96922,456.66521 L 430.93164,456.66521 L 430.9583,460.3288 L 427.3038,460.35545 L 427.3038,463.43573 L 430.9583,463.40908 L 430.93164,467.05806 L 433.96922,467.05806 L 433.99587,463.40908 L 437.69697,463.40908 L 437.69697,460.3288 z " + style="fill:url(#linearGradient13228);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient13230);stroke-width:0.6060583;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + sodipodi:nodetypes="ccccccccccccc" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_add_lyrics.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + </g> + <rect + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/22x22/actions/amarok_add_lyrics.png" + y="1030.6315" + x="404.60986" + height="16" + width="16" + id="rect10513" + style="fill:#b9cff9;fill-opacity:0;stroke:none;stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" /> + </g> + <g + id="g13180" + transform="translate(0.7414212,33.055198)"> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/icons/22/amarok_add_lyrics.png" + transform="matrix(1.002411,0,0,1.002411,8.6733819,581.2148)" + id="g15812"> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_add_lyrics.png" + id="g15814" + transform="translate(11.93772,253.0084)"> + <path + sodipodi:nodetypes="ccccccccc" + id="path15816" + d="M 415.62177,194.97276 L 428.67052,194.97276 C 430.04072,194.97276 431.1438,196.23538 431.1438,197.80374 L 431.15369,210.94427 L 426.98381,215.94365 L 415.62177,215.94365 C 414.25159,215.94365 413.1485,214.68103 413.1485,213.11266 L 413.1485,197.80374 C 413.1485,196.23538 414.25159,194.97276 415.62177,194.97276 z " + style="fill:url(#linearGradient21059);fill-opacity:1;stroke:url(#linearGradient21061);stroke-width:0.97620374;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" /> + <path + sodipodi:nodetypes="ccccccccc" + id="path15818" + d="M 415.62757,195.44394 L 428.67462,195.44394 C 429.76769,195.44394 430.64768,196.49144 430.64768,197.7926 L 430.64768,211.69293 L 427.39661,215.40541 L 415.62757,215.47246 C 414.5345,215.47246 413.65451,214.42497 413.65451,213.12381 L 413.65451,197.7926 C 413.65451,196.49144 414.5345,195.44394 415.62757,195.44394 z " + style="fill:url(#linearGradient21063);fill-opacity:1;stroke:none;stroke-width:0.90379584;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" /> + <path + sodipodi:nodetypes="ccccz" + id="path15820" + d="M 426.72067,212.7007 L 426.71279,216.42743 C 426.71279,216.42743 431.66042,210.53153 431.66042,210.53153 L 428.86955,210.48681 C 427.64732,210.4999 426.72324,211.48454 426.72067,212.7007 z " + style="fill:#0004c1;fill-opacity:1;stroke:none;stroke-width:1.0039922;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" /> + </g> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_add_lyrics.png" + sodipodi:nodetypes="ccccccccccccc" + style="fill:url(#linearGradient21065);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient21067);stroke-width:0.6060583;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + d="M 437.69697,460.3288 L 433.99587,460.32878 L 433.96922,456.66521 L 430.93164,456.66521 L 430.9583,460.3288 L 427.3038,460.35545 L 427.3038,463.43573 L 430.9583,463.40908 L 430.93164,467.05806 L 433.96922,467.05806 L 433.99587,463.40908 L 437.69697,463.40908 L 437.69697,460.3288 z " + id="path15822" /> + </g> + <rect + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/22x22/actions/amarok_add_lyrics.png" + y="1029.7867" + x="432.81812" + height="22" + width="22" + id="rect10515" + style="fill:#b9cff9;fill-opacity:0;stroke:none;stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" /> + </g> + <g + id="g13195" + transform="translate(0.7414212,38.895794)"> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/icons/32/amarok_add_lyrics.png" + transform="matrix(1.007236,0,0,1.007236,10.640592,564.1226)" + id="g15973"> + <g + transform="matrix(1.458052,0,0,1.458052,-141.9039,162.7997)" + id="g15975" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_add_lyrics.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <path + style="fill:url(#linearGradient16307);fill-opacity:1;stroke:url(#linearGradient16309);stroke-width:0.6695261;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 415.62177,194.97276 L 428.67052,194.97276 C 430.04072,194.97276 431.1438,196.23538 431.1438,197.80374 L 431.15369,210.94427 L 426.98381,215.94365 L 415.62177,215.94365 C 414.25159,215.94365 413.1485,214.68103 413.1485,213.11266 L 413.1485,197.80374 C 413.1485,196.23538 414.25159,194.97276 415.62177,194.97276 z " + id="path15977" + sodipodi:nodetypes="ccccccccc" /> + <path + style="fill:url(#linearGradient16311);fill-opacity:1;stroke:none;stroke-width:0.90379584;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 415.62757,195.44394 L 428.67462,195.44394 C 429.76769,195.44394 430.64768,196.49144 430.64768,197.7926 L 430.64768,211.69293 L 427.39661,215.40541 L 415.62757,215.47246 C 414.5345,215.47246 413.65451,214.42497 413.65451,213.12381 L 413.65451,197.7926 C 413.65451,196.49144 414.5345,195.44394 415.62757,195.44394 z " + id="path15979" + sodipodi:nodetypes="ccccccccc" /> + <path + style="fill:#0004c1;fill-opacity:1;stroke:none;stroke-width:1.0039922;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 426.72067,212.7007 L 426.71279,216.42743 C 426.71279,216.42743 431.66042,210.53153 431.66042,210.53153 L 428.86955,210.48681 C 427.64732,210.4999 426.72324,211.48454 426.72067,212.7007 z " + id="path15981" + sodipodi:nodetypes="ccccz" /> + </g> + <path + id="path15983" + d="M 478.87524,465.08363 L 473.47885,465.0836 L 473.43999,459.74192 L 469.01104,459.74192 L 469.04991,465.08363 L 463.72146,465.12248 L 463.72146,469.61369 L 469.04991,469.57484 L 469.01104,474.89524 L 473.43999,474.89524 L 473.47885,469.57484 L 478.87524,469.57484 L 478.87524,465.08363 z " + style="fill:url(#linearGradient16313);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient16315);stroke-width:0.60605836;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + sodipodi:nodetypes="ccccccccccccc" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_add_lyrics.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + </g> + <rect + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/32x32/actions/amarok_add_lyrics.png" + style="fill:#b9cff9;fill-opacity:0;stroke:none;stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + id="rect10517" + width="32" + height="32" + x="471.80829" + y="1013.946" /> + </g> + <g + id="g13213" + transform="translate(0.7414212,38.379315)"> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/icons/48/amarok_add_lyrics.png" + id="g20952" + transform="matrix(1.510853,0,0,1.510853,-170.46506,323.72777)"> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_add_lyrics.png" + id="g20954" + transform="matrix(1.458052,0,0,1.458052,-141.9039,162.7997)"> + <path + sodipodi:nodetypes="ccccccccc" + id="path20956" + d="M 415.62177,194.97276 L 428.67052,194.97276 C 430.04072,194.97276 431.1438,196.23538 431.1438,197.80374 L 431.15369,210.94427 L 426.98381,215.94365 L 415.62177,215.94365 C 414.25159,215.94365 413.1485,214.68103 413.1485,213.11266 L 413.1485,197.80374 C 413.1485,196.23538 414.25159,194.97276 415.62177,194.97276 z " + style="fill:url(#linearGradient20964);fill-opacity:1;stroke:url(#linearGradient20966);stroke-width:0.6695261;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" /> + <path + sodipodi:nodetypes="ccccccccc" + id="path20958" + d="M 415.62757,195.44394 L 428.67462,195.44394 C 429.76769,195.44394 430.64768,196.49144 430.64768,197.7926 L 430.64768,211.69293 L 427.39661,215.40541 L 415.62757,215.47246 C 414.5345,215.47246 413.65451,214.42497 413.65451,213.12381 L 413.65451,197.7926 C 413.65451,196.49144 414.5345,195.44394 415.62757,195.44394 z " + style="fill:url(#linearGradient20968);fill-opacity:1;stroke:none;stroke-width:0.90379584;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" /> + <path + sodipodi:nodetypes="ccccz" + id="path20960" + d="M 426.72067,212.7007 L 426.71279,216.42743 C 426.71279,216.42743 431.66042,210.53153 431.66042,210.53153 L 428.86955,210.48681 C 427.64732,210.4999 426.72324,211.48454 426.72067,212.7007 z " + style="fill:#0004c1;fill-opacity:1;stroke:none;stroke-width:1.0039922;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" /> + </g> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_add_lyrics.png" + sodipodi:nodetypes="ccccccccccccc" + style="fill:url(#linearGradient20970);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient20972);stroke-width:0.60605836;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + d="M 478.87524,465.08363 L 473.47885,465.0836 L 473.43999,459.74192 L 469.01104,459.74192 L 469.04991,465.08363 L 463.72146,465.12248 L 463.72146,469.61369 L 469.04991,469.57484 L 469.01104,474.89524 L 473.43999,474.89524 L 473.47885,469.57484 L 478.87524,469.57484 L 478.87524,465.08363 z " + id="path20962" /> + </g> + <rect + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/48x48/actions/amarok_add_lyrics.png" + y="998.46265" + x="521.28601" + height="48" + width="48" + id="rect10519" + style="fill:#b9cff9;fill-opacity:0;stroke:none;stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" /> + </g> + <g + id="g13204" + transform="translate(0.7414212,38.506977)"> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/icons/64/amarok_add_lyrics.png" + transform="matrix(2.014471,0,0,2.014471,-332.30721,82.68832)" + id="g20974"> + <g + transform="matrix(1.458052,0,0,1.458052,-141.9039,162.7997)" + id="g20976" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_add_lyrics.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <path + style="fill:url(#linearGradient20986);fill-opacity:1;stroke:url(#linearGradient20988);stroke-width:0.6695261;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 415.62177,194.97276 L 428.67052,194.97276 C 430.04072,194.97276 431.1438,196.23538 431.1438,197.80374 L 431.15369,210.94427 L 426.98381,215.94365 L 415.62177,215.94365 C 414.25159,215.94365 413.1485,214.68103 413.1485,213.11266 L 413.1485,197.80374 C 413.1485,196.23538 414.25159,194.97276 415.62177,194.97276 z " + id="path20978" + sodipodi:nodetypes="ccccccccc" /> + <path + style="fill:url(#linearGradient20990);fill-opacity:1;stroke:none;stroke-width:0.90379584;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 415.62757,195.44394 L 428.67462,195.44394 C 429.76769,195.44394 430.64768,196.49144 430.64768,197.7926 L 430.64768,211.69293 L 427.39661,215.40541 L 415.62757,215.47246 C 414.5345,215.47246 413.65451,214.42497 413.65451,213.12381 L 413.65451,197.7926 C 413.65451,196.49144 414.5345,195.44394 415.62757,195.44394 z " + id="path20980" + sodipodi:nodetypes="ccccccccc" /> + <path + style="fill:#0004c1;fill-opacity:1;stroke:none;stroke-width:1.0039922;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 426.72067,212.7007 L 426.71279,216.42743 C 426.71279,216.42743 431.66042,210.53153 431.66042,210.53153 L 428.86955,210.48681 C 427.64732,210.4999 426.72324,211.48454 426.72067,212.7007 z " + id="path20982" + sodipodi:nodetypes="ccccz" /> + </g> + <path + id="path20984" + d="M 478.87524,465.08363 L 473.47885,465.0836 L 473.43999,459.74192 L 469.01104,459.74192 L 469.04991,465.08363 L 463.72146,465.12248 L 463.72146,469.61369 L 469.04991,469.57484 L 469.01104,474.89524 L 473.43999,474.89524 L 473.47885,469.57484 L 478.87524,469.57484 L 478.87524,465.08363 z " + style="fill:url(#linearGradient20992);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient20994);stroke-width:0.60605836;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + sodipodi:nodetypes="ccccccccccccc" + inkscape:export-filename="/home/lando/Projects/Amarok/Context-browse-icons/amarok_add_lyrics.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + </g> + <rect + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/64x64/actions/amarok_add_lyrics.png" + style="fill:#b9cff9;fill-opacity:0;stroke:none;stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + id="rect10521" + width="64" + height="64" + x="590.02771" + y="982.33496" /> + </g> + <g + id="g13258" + transform="translate(-3,48.720847)"> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/icons/64/amarok_burn.png" + transform="matrix(1.7718917,0,0,1.7718917,-223.04835,192.04907)" + id="g21125"> + <path + style="fill:url(#linearGradient21135);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient21137);stroke-width:1.38821054;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 460.6994,503.14004 C 461.44708,511.59608 468.90724,517.8701 477.35557,517.14533 C 485.8039,516.42055 492.055,508.97024 491.30732,500.5142 C 490.55962,492.05818 483.0951,485.78452 474.64675,486.5093 C 466.19842,487.23407 459.95173,494.68401 460.6994,503.14004 z M 471.52078,502.21168 C 471.29387,499.64536 473.11549,497.39151 475.59018,497.17921 C 478.06487,496.96689 480.25903,498.87625 480.48593,501.44257 C 480.71283,504.00889 478.88683,506.26306 476.41214,506.47536 C 473.93745,506.68766 471.74769,504.77793 471.52078,502.21168 z " + id="path21127" /> + <path + style="fill:url(#linearGradient21139);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.4984282;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + d="M 475.16464,502.23832 C 474.94284,502.11225 474.5458,502.0332 474.38796,501.8106 L 465.17185,511.26864 C 467.85416,514.09436 471.15462,516.10561 474.91391,516.29722 L 475.16464,502.23832 z " + id="path21129" + sodipodi:nodetypes="ccccc" /> + <path + style="fill:url(#linearGradient21141);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.49565426;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + d="M 475.99121,501.90188 C 476.2125,502.02665 476.40645,502.19353 476.56382,502.41393 L 486.23577,492.78455 C 483.56053,489.98627 480.00226,488.15909 476.25047,487.97154 L 475.99121,501.90188 z " + id="path21131" + sodipodi:nodetypes="ccccc" /> + <path + style="fill:url(#linearGradient21143);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient21145);stroke-width:1.3882103;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 469.1904,517.72582 L 469.29595,514.5442 C 464.00565,513.51604 459.36158,509.60794 457.47847,503.89301 C 456.37795,500.553 456.39443,497.1072 457.30484,493.98192 C 457.28209,495.32341 457.47959,496.67355 457.91298,497.98875 C 459.55282,502.96556 464.19113,506.11806 469.56167,506.56043 L 469.72322,501.66172 L 480.34157,510.39601 L 469.1904,517.72582 z " + id="path21133" + inkscape:export-filename="/home/vadim/collection.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + </g> + <rect + style="fill:#b9cff9;fill-opacity:0;stroke:none;stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + id="rect10427" + width="64" + height="64" + x="584.83221" + y="1049.6965" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/64x64/actions/amarok_burn.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + </g> + <g + id="g13250" + transform="translate(-3,48.720847)"> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/icons/48/amarok_burn.png" + id="g21103" + transform="matrix(1.3289188,0,0,1.3289188,-95.20617,428.36781)"> + <path + id="path21105" + d="M 460.6994,503.14004 C 461.44708,511.59608 468.90724,517.8701 477.35557,517.14533 C 485.8039,516.42055 492.055,508.97024 491.30732,500.5142 C 490.55962,492.05818 483.0951,485.78452 474.64675,486.5093 C 466.19842,487.23407 459.95173,494.68401 460.6994,503.14004 z M 471.52078,502.21168 C 471.29387,499.64536 473.11549,497.39151 475.59018,497.17921 C 478.06487,496.96689 480.25903,498.87625 480.48593,501.44257 C 480.71283,504.00889 478.88683,506.26306 476.41214,506.47536 C 473.93745,506.68766 471.74769,504.77793 471.52078,502.21168 z " + style="fill:url(#linearGradient21113);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient21115);stroke-width:1.38821054;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + sodipodi:nodetypes="ccccc" + id="path21107" + d="M 475.16464,502.23832 C 474.94284,502.11225 474.5458,502.0332 474.38796,501.8106 L 465.17185,511.26864 C 467.85416,514.09436 471.15462,516.10561 474.91391,516.29722 L 475.16464,502.23832 z " + style="fill:url(#linearGradient21117);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.4984282;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" /> + <path + sodipodi:nodetypes="ccccc" + id="path21109" + d="M 475.99121,501.90188 C 476.2125,502.02665 476.40645,502.19353 476.56382,502.41393 L 486.23577,492.78455 C 483.56053,489.98627 480.00226,488.15909 476.25047,487.97154 L 475.99121,501.90188 z " + style="fill:url(#linearGradient21119);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.49565426;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/vadim/collection.png" + id="path21111" + d="M 469.1904,517.72582 L 469.29595,514.5442 C 464.00565,513.51604 459.36158,509.60794 457.47847,503.89301 C 456.37795,500.553 456.39443,497.1072 457.30484,493.98192 C 457.28209,495.32341 457.47959,496.67355 457.91298,497.98875 C 459.55282,502.96556 464.19113,506.11806 469.56167,506.56043 L 469.72322,501.66172 L 480.34157,510.39601 L 469.1904,517.72582 z " + style="fill:url(#linearGradient21121);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient21123);stroke-width:1.3882103;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + <rect + y="1071.6034" + x="510.70428" + height="48" + width="48" + id="rect10429" + style="fill:#b9cff9;fill-opacity:0;stroke:none;stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/48x48/actions/amarok_burn.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + </g> + <g + id="g13242" + transform="translate(-3,48.720847)"> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/icons/32/amarok_burn.png" + transform="matrix(0.8859458,0,0,0.8859458,64.396072,655.3408)" + id="g15985"> + <path + style="fill:url(#linearGradient21091);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient21093);stroke-width:1.38821042;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 460.6994,503.14004 C 461.44708,511.59608 468.90724,517.8701 477.35557,517.14533 C 485.8039,516.42055 492.055,508.97024 491.30732,500.5142 C 490.55962,492.05818 483.0951,485.78452 474.64675,486.5093 C 466.19842,487.23407 459.95173,494.68401 460.6994,503.14004 z M 471.52078,502.21168 C 471.29387,499.64536 473.11549,497.39151 475.59018,497.17921 C 478.06487,496.96689 480.25903,498.87625 480.48593,501.44257 C 480.71283,504.00889 478.88683,506.26306 476.41214,506.47536 C 473.93745,506.68766 471.74769,504.77793 471.52078,502.21168 z " + id="path15987" /> + <path + style="fill:url(#linearGradient21095);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.4984282;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + d="M 475.16464,502.23832 C 474.94284,502.11225 474.5458,502.0332 474.38796,501.8106 L 465.17185,511.26864 C 467.85416,514.09436 471.15462,516.10561 474.91391,516.29722 L 475.16464,502.23832 z " + id="path15989" + sodipodi:nodetypes="ccccc" /> + <path + style="fill:url(#linearGradient21097);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.49565426;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + d="M 475.99121,501.90188 C 476.2125,502.02665 476.40645,502.19353 476.56382,502.41393 L 486.23577,492.78455 C 483.56053,489.98627 480.00226,488.15909 476.25047,487.97154 L 475.99121,501.90188 z " + id="path15991" + sodipodi:nodetypes="ccccc" /> + <path + style="fill:url(#linearGradient21099);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient21101);stroke-width:1.38821018;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 469.1904,517.72582 L 469.29595,514.5442 C 464.00565,513.51604 459.36158,509.60794 457.47847,503.89301 C 456.37795,500.553 456.39443,497.1072 457.30484,493.98192 C 457.28209,495.32341 457.47959,496.67355 457.91298,497.98875 C 459.55282,502.96556 464.19113,506.11806 469.56167,506.56043 L 469.72322,501.66172 L 480.34157,510.39601 L 469.1904,517.72582 z " + id="path15993" + inkscape:export-filename="/home/vadim/collection.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + </g> + <rect + style="fill:#b9cff9;fill-opacity:0;stroke:none;stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + id="rect10431" + width="32" + height="32" + x="468.3363" + y="1084.1644" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/32x32/actions/amarok_burn.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + </g> + <g + id="g13232" + transform="translate(-3,48.720847)"> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/icons/22/amarok_burn.png" + id="g15714" + transform="matrix(0.9795473,0,0,0.9802353,-158.4083,1075.3563)"> + <path + id="path15716" + d="M 607.83307,31.237603 C 608.24048,35.842013 612.30547,39.258293 616.90891,38.863643 C 621.51235,38.468993 624.91853,34.412213 624.51112,29.807803 C 624.10371,25.203403 620.03634,21.787323 615.43289,22.181974 C 610.82945,22.576624 607.42567,26.633203 607.83307,31.237603 z M 613.72957,30.732103 C 613.60593,29.334713 614.59852,28.107463 615.94696,27.991863 C 617.2954,27.876253 618.49098,28.915923 618.61462,30.313313 C 618.73826,31.710703 617.74328,32.938123 616.39484,33.053723 C 615.0464,33.169323 613.85321,32.129453 613.72957,30.732103 z " + style="fill:url(#linearGradient21235);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient21237);stroke-width:1.27565074;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + sodipodi:nodetypes="ccccc" + id="path15718" + d="M 615.85833,30.599293 C 615.73529,30.529403 615.51503,30.485583 615.42747,30.362183 L 610.31487,35.605313 C 611.80287,37.171773 613.63379,38.286723 615.71924,38.392943 L 615.85833,30.599293 z " + style="fill:url(#linearGradient21239);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.4984282;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" /> + <path + sodipodi:nodetypes="ccccc" + id="path15720" + d="M 616.31687,30.412783 C 616.43963,30.481953 616.54722,30.574463 616.63452,30.696643 L 622,25.358533 C 620.51592,23.807285 618.54198,22.794376 616.46069,22.690404 L 616.31687,30.412783 z " + style="fill:url(#linearGradient21241);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.49565426;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/vadim/collection.png" + id="path15722" + d="M 610.32516,39.184893 L 610.38371,37.421143 C 607.44894,36.851173 604.87266,34.684693 603.82801,31.516583 C 603.2175,29.665023 603.22664,27.754823 603.73169,26.022303 C 603.71907,26.765963 603.82863,27.514423 604.06905,28.243513 C 604.97875,31.002443 607.55183,32.750053 610.53112,32.995283 L 610.62074,30.279653 L 616.51123,35.121563 L 610.32516,39.184893 z " + style="fill:url(#linearGradient21243);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient21245);stroke-width:1.27565074;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + <rect + y="1094.4181" + x="431.98724" + height="22" + width="22" + id="rect10433" + style="fill:#b9cff9;fill-opacity:0;stroke:none;stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/22x22/actions/amarok_burn.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <rect + y="1100.9791" + x="427.6322" + height="16" + width="16" + id="rect10435" + style="fill:#b9cff9;fill-opacity:0;stroke:none;stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/16x16/actions/amarok_burn.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + </g> + <g + id="g10444" + transform="matrix(0.7499978,0,0,0.7499978,-52.102648,-56.567442)" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/48x48/amarok_download.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <g + transform="matrix(1.7997159,0,0,1.7706507,-53.063771,707.35048)" + id="g10446"> + <path + id="path10460" + style="fill:url(#linearGradient10469);fill-opacity:1;stroke:url(#linearGradient10472);stroke-width:1.36656916;stroke-miterlimit:4;stroke-dasharray:none" + d="M 475.96622,573.73445 C 484.42079,573.73445 491.27867,566.81816 491.27867,558.33367 C 491.27867,549.90281 484.42011,542.98445 475.96622,542.98445 C 467.57327,542.98445 460.65719,549.90281 460.65719,558.33367 C 460.65719,566.81884 467.57327,573.73445 475.96622,573.73445 z " /> + <path + id="path10462" + style="fill:url(#radialGradient10474);fill-opacity:1" + d="M 471.03814,544.70319 C 470.96367,544.70319 470.88261,544.73417 470.80022,544.78095 C 470.99597,544.72362 471.08759,544.70319 471.03814,544.70319 z M 475.88139,545.6489 L 475.93015,544.93912 L 475.18281,544.98592 L 475.28167,545.6489 L 475.88139,545.6489 z M 467.44378,557.89508 C 467.29285,557.75472 467.39433,557.13852 467.39433,557.13852 C 467.39433,557.13852 465.14769,555.95753 462.69938,555.24841 C 462.39688,555.15943 462.54976,554.53929 462.84963,554.3027 L 462.75012,553.63971 C 462.70004,553.30952 463.24967,551.70083 463.79929,551.55978 C 464.34893,551.4181 463.74986,552.50551 463.74986,552.50551 L 463.19958,552.83699 C 463.19958,552.83699 463.84806,553.59225 463.99964,553.59225 C 464.1499,553.59225 464.39837,553.21331 464.39837,553.21331 L 463.69847,552.74144 L 464.34828,552.45608 L 464.38914,552.20761 L 464.49723,552.174 L 465.5721,550.54553 C 466.31484,550.24039 467.22827,549.8641 467.34359,549.80872 C 467.54394,549.71448 468.94306,548.91179 469.19153,548.72265 C 469.44261,548.53151 469.98961,548.57962 470.18996,548.57962 C 470.3903,548.57962 470.69016,548.48539 470.7396,547.96409 C 470.79101,547.44346 470.99003,547.34922 471.13963,547.49288 C 471.28922,547.63259 470.98938,547.86986 471.33998,547.96409 C 471.68926,548.06031 471.9878,548.29624 472.23758,548.06031 C 472.42081,547.88634 472.17629,547.68927 472.01088,547.54034 L 474.78409,547.54034 L 475.08461,546.68755 L 474.43613,546.59396 L 472.03724,546.35869 L 472.03724,546.07464 L 471.84612,546.10693 C 472.1071,544.67946 473.64792,544.91606 472.48736,544.08963 C 472.4175,544.03756 471.40194,545.68976 471.14359,545.65682 C 470.67566,545.59551 470.06937,545.59091 469.9415,545.74182 C 469.77148,545.94218 470.32374,545.05116 470.80154,544.7803 C 470.04169,545.00503 467.63884,545.82684 465.09825,548.5322 C 462.66906,551.11955 461.7016,554.6052 461.7016,554.72645 C 461.7016,554.96238 462.20246,555.05862 462.25254,555.34199 C 462.30131,555.62471 461.30419,556.56977 461.30419,557.04362 C 461.30419,557.26176 461.06893,560.17603 461.90194,562.57687 C 462.87664,565.37777 464.95724,567.71734 465.19909,567.87222 L 465.69733,567.63562 C 465.69733,567.63562 464.54928,565.60251 464.49919,565.41337 C 464.44976,565.22488 465.79881,562.48132 466.44665,562.57752 C 467.09514,562.66981 466.94619,562.86093 467.34492,562.623 C 467.74693,562.38773 467.99405,560.44687 468.44286,560.25971 C 468.89364,560.06926 469.39385,559.83266 469.34309,559.36145 C 469.29039,558.88958 467.59403,558.03876 467.44378,557.89508 z M 478.27896,543.99341 L 476.99251,543.73705 L 477.17968,544.27811 L 478.27896,543.99341 z M 473.23469,545.74249 C 473.38627,545.74249 476.3816,543.75748 476.08041,543.71069 C 475.78252,543.66389 475.70805,543.86095 474.90734,543.7667 C 474.10924,543.67114 473.2848,544.74998 473.08576,544.93912 C 472.88609,545.12893 472.95329,545.74249 473.23469,545.74249 z M 486.98282,560.00072 L 487.37033,559.49984 L 486.98282,559.36341 L 486.69418,559.72786 L 486.35478,560.22873 L 486.64541,560.36648 L 486.98282,560.00072 z M 488.67524,561.64236 L 488.57836,560.91347 L 487.95096,560.91347 L 487.90218,561.45849 L 487.32157,561.36952 L 487.17724,560.77574 L 486.88859,560.59515 L 486.54918,561.00442 L 486.21043,560.91413 L 486.11357,561.23047 L 486.50108,561.32404 L 486.50108,564.28509 L 487.86922,564.61461 C 487.83694,564.66998 487.81453,564.71479 487.80532,564.74115 C 487.70844,565.05816 488.19151,565.19654 488.53025,565.05816 C 488.65546,565.00805 489.05352,564.6067 489.30395,564.01093 C 489.65193,563.18385 489.9564,562.09051 490.01702,561.70298 L 490.17257,561.36952 L 489.20641,561.73331 L 488.67524,561.64236 z M 490.5179,556.03925 L 490.45266,555.63922 C 490.15278,553.95341 489.46344,551.79903 487.99905,549.56489 C 485.44859,545.67857 479.8692,544.07777 479.8692,544.07777 L 479.29783,544.68869 L 479.10539,544.37038 L 478.62166,544.1865 L 478.62166,544.59774 L 479.05596,544.96351 L 478.76665,545.09993 L 477.6542,545.19022 L 475.14195,546.55837 L 475.38248,547.65238 L 475.0932,547.74465 L 474.9482,547.97135 L 475.77001,549.2011 L 475.81681,549.61168 L 475.14195,549.74942 L 475.14195,550.56991 L 474.75576,550.65955 L 474.80387,551.29815 L 471.51594,553.57775 L 471.61216,554.85233 C 471.85469,555.17063 473.74019,557.08645 473.74019,557.08645 C 473.74019,557.08645 475.91501,557.17675 476.39742,556.90324 C 476.8805,556.62975 476.54306,557.17675 476.68806,557.31446 C 476.83304,557.45156 476.8805,558.40782 477.02548,558.49943 C 477.17113,558.58971 477.02548,559.13605 477.21791,559.31927 C 477.41167,559.50116 477.41167,561.68982 477.41167,561.68982 C 477.41167,561.68982 478.57223,563.64913 478.57223,564.15001 C 478.57223,564.65152 478.52543,564.60604 479.44215,564.56123 C 480.36152,564.51443 480.55394,564.15001 480.74639,564.01356 C 480.94146,563.8765 480.94146,563.55818 481.1339,563.28402 C 481.32766,563.00988 481.66574,561.96267 482.10136,561.59886 C 482.53566,561.23243 483.6949,560.95894 483.79113,560.32232 C 483.88801,559.68305 484.32428,559.18217 484.32428,559.18217 L 486.41277,556.97179 L 486.35347,557.26967 L 486.30666,558.40913 L 486.93339,558.18177 L 486.8866,556.9507 L 486.6599,556.71147 L 486.69285,556.67588 C 486.69285,556.67588 486.5472,556.40435 486.35347,556.40435 C 486.16101,556.40435 485.00045,556.67588 484.80801,556.63172 C 484.61294,556.58558 483.79113,554.39891 483.64615,554.30863 C 483.5018,554.21702 482.58377,552.7131 482.58377,552.7131 C 482.58377,552.7131 484.71048,555.26555 485.04724,556.26793 C 485.24494,556.85119 485.98176,556.30815 486.58213,555.69852 L 486.73899,556.0867 L 487.12651,555.99311 L 487.07839,555.53971 L 487.51401,555.53971 L 487.51401,556.22246 L 487.36837,556.58691 L 487.31894,557.17938 L 487.70578,557.54383 L 487.89955,557.22682 L 488.52759,556.63303 L 489.25321,556.26859 L 489.44631,556.63303 L 489.54318,557.13589 L 489.34942,557.68089 L 488.96192,558.00053 L 488.76816,558.82102 L 488.76816,559.22963 L 488.33385,558.95681 L 488.28575,558.09148 L 487.659,558.13828 L 487.36903,558.91133 L 487.80333,559.55059 L 488.81758,559.6857 L 489.64004,558.91198 L 489.73628,557.41005 L 490.10271,556.92564 C 490.34061,557.5379 490.50932,558.17914 490.50932,558.77556 C 490.50932,559.42998 490.80919,558.32346 490.55479,556.32791 L 490.5179,556.03925 z M 478.5261,551.47872 L 475.91566,551.38779 L 477.02679,550.47699 L 477.60673,550.47699 L 478.52675,551.11428 L 478.52675,551.47872 L 478.5261,551.47872 z M 481.71516,551.1604 L 481.71516,551.57033 L 480.60402,551.57033 L 480.70025,551.84515 L 480.02408,551.93676 L 479.97465,552.16477 L 479.49224,552.07317 L 478.62232,551.89063 L 478.7673,551.66195 L 478.91295,551.38779 L 479.39538,550.88691 L 479.58911,551.25071 L 480.31405,551.20588 L 480.70157,550.79598 L 482.19824,551.0688 L 481.71516,551.1604 z M 481.81205,550.56861 L 481.23275,550.65823 L 481.13587,550.24831 L 481.86016,550.15737 L 481.95638,549.7481 L 482.48887,550.29445 L 481.81205,550.56861 z M 484.61425,564.7418 L 484.27684,565.01398 L 484.32493,565.69739 L 484.75925,565.69739 L 484.75925,565.10625 L 485.14676,564.60473 L 485.14676,563.55687 L 484.90489,563.51007 L 484.61425,564.7418 z M 481.37708,563.37299 C 481.37708,563.37299 481.03965,563.4626 481.42452,563.60167 C 481.81139,563.7381 483.35814,561.36952 483.35814,561.36952 L 482.0539,562.18805 L 481.37708,563.37299 z M 478.74754,572.10915 L 478.45689,571.83302 L 477.8776,571.74075 L 477.78138,572.01623 L 477.00701,571.92463 L 476.96021,571.5582 L 476.38027,571.5582 L 475.75223,571.92463 L 474.64109,571.92463 L 474.54553,571.65047 L 472.75624,571.46528 L 472.46758,571.74075 L 471.74265,571.55886 L 471.64775,570.91499 L 471.30902,570.8682 L 470.9228,571.55886 L 469.61856,571.51207 C 469.85188,571.62279 471.79866,572.78467 474.78608,573.02785 C 478.74819,573.34813 480.63039,572.38397 480.63039,572.38397 L 480.4854,572.24689 L 478.74754,572.10915 z " /> + <path + id="path10464" + style="fill:none" + d="M 463.22765,552.78788 C 463.22631,552.71934 463.22104,552.6508 463.22104,552.58227 C 463.22104,552.36544 463.23489,552.15126 463.25664,551.93971 C 462.87044,552.47023 462.56729,553.45218 462.6055,553.70196 L 462.70437,554.37088 C 462.40583,554.60945 462.25294,555.23554 462.55476,555.32451 C 463.07078,555.47544 463.57759,555.64876 464.05604,555.82539 C 463.60197,554.97391 463.31925,554.05981 463.24346,553.10752 C 463.13339,552.98758 463.05365,552.89266 463.05365,552.89266 L 463.22765,552.78788 z " /> + </g> + <path + sodipodi:nodetypes="ccscccccc" + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/vadim/collection.png" + id="path10467" + d="M 769.56671,1702.7194 L 774.41924,1702.7107 C 775.70957,1689.2271 781.42102,1677.1298 790.02919,1671.8664 C 795.0601,1668.7904 799.29969,1668.5333 804.61379,1668.9938 C 802.56891,1669.0526 798.75352,1673.079 795.25637,1675.8638 C 790.37513,1680.3116 786.98967,1689.0551 786.59596,1702.6912 L 794.06734,1702.6755 L 781.31513,1730.3175 L 769.56671,1702.7194 z " + style="fill:url(#linearGradient10476);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient10478);stroke-width:2.4977715;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + <g + transform="matrix(0.4999985,0,0,0.4999985,95.706342,376.32294)" + id="g10480" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/32x32/actions/amarok_download.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <g + id="g10482" + transform="matrix(1.7997159,0,0,1.7706507,-53.063771,707.35048)"> + <path + d="M 475.96622,573.73445 C 484.42079,573.73445 491.27867,566.81816 491.27867,558.33367 C 491.27867,549.90281 484.42011,542.98445 475.96622,542.98445 C 467.57327,542.98445 460.65719,549.90281 460.65719,558.33367 C 460.65719,566.81884 467.57327,573.73445 475.96622,573.73445 z " + style="fill:url(#linearGradient10499);fill-opacity:1;stroke:url(#linearGradient10501);stroke-width:1.36656916;stroke-miterlimit:4;stroke-dasharray:none" + id="path10484" /> + <path + d="M 471.03814,544.70319 C 470.96367,544.70319 470.88261,544.73417 470.80022,544.78095 C 470.99597,544.72362 471.08759,544.70319 471.03814,544.70319 z M 475.88139,545.6489 L 475.93015,544.93912 L 475.18281,544.98592 L 475.28167,545.6489 L 475.88139,545.6489 z M 467.44378,557.89508 C 467.29285,557.75472 467.39433,557.13852 467.39433,557.13852 C 467.39433,557.13852 465.14769,555.95753 462.69938,555.24841 C 462.39688,555.15943 462.54976,554.53929 462.84963,554.3027 L 462.75012,553.63971 C 462.70004,553.30952 463.24967,551.70083 463.79929,551.55978 C 464.34893,551.4181 463.74986,552.50551 463.74986,552.50551 L 463.19958,552.83699 C 463.19958,552.83699 463.84806,553.59225 463.99964,553.59225 C 464.1499,553.59225 464.39837,553.21331 464.39837,553.21331 L 463.69847,552.74144 L 464.34828,552.45608 L 464.38914,552.20761 L 464.49723,552.174 L 465.5721,550.54553 C 466.31484,550.24039 467.22827,549.8641 467.34359,549.80872 C 467.54394,549.71448 468.94306,548.91179 469.19153,548.72265 C 469.44261,548.53151 469.98961,548.57962 470.18996,548.57962 C 470.3903,548.57962 470.69016,548.48539 470.7396,547.96409 C 470.79101,547.44346 470.99003,547.34922 471.13963,547.49288 C 471.28922,547.63259 470.98938,547.86986 471.33998,547.96409 C 471.68926,548.06031 471.9878,548.29624 472.23758,548.06031 C 472.42081,547.88634 472.17629,547.68927 472.01088,547.54034 L 474.78409,547.54034 L 475.08461,546.68755 L 474.43613,546.59396 L 472.03724,546.35869 L 472.03724,546.07464 L 471.84612,546.10693 C 472.1071,544.67946 473.64792,544.91606 472.48736,544.08963 C 472.4175,544.03756 471.40194,545.68976 471.14359,545.65682 C 470.67566,545.59551 470.06937,545.59091 469.9415,545.74182 C 469.77148,545.94218 470.32374,545.05116 470.80154,544.7803 C 470.04169,545.00503 467.63884,545.82684 465.09825,548.5322 C 462.66906,551.11955 461.7016,554.6052 461.7016,554.72645 C 461.7016,554.96238 462.20246,555.05862 462.25254,555.34199 C 462.30131,555.62471 461.30419,556.56977 461.30419,557.04362 C 461.30419,557.26176 461.06893,560.17603 461.90194,562.57687 C 462.87664,565.37777 464.95724,567.71734 465.19909,567.87222 L 465.69733,567.63562 C 465.69733,567.63562 464.54928,565.60251 464.49919,565.41337 C 464.44976,565.22488 465.79881,562.48132 466.44665,562.57752 C 467.09514,562.66981 466.94619,562.86093 467.34492,562.623 C 467.74693,562.38773 467.99405,560.44687 468.44286,560.25971 C 468.89364,560.06926 469.39385,559.83266 469.34309,559.36145 C 469.29039,558.88958 467.59403,558.03876 467.44378,557.89508 z M 478.27896,543.99341 L 476.99251,543.73705 L 477.17968,544.27811 L 478.27896,543.99341 z M 473.23469,545.74249 C 473.38627,545.74249 476.3816,543.75748 476.08041,543.71069 C 475.78252,543.66389 475.70805,543.86095 474.90734,543.7667 C 474.10924,543.67114 473.2848,544.74998 473.08576,544.93912 C 472.88609,545.12893 472.95329,545.74249 473.23469,545.74249 z M 486.98282,560.00072 L 487.37033,559.49984 L 486.98282,559.36341 L 486.69418,559.72786 L 486.35478,560.22873 L 486.64541,560.36648 L 486.98282,560.00072 z M 488.67524,561.64236 L 488.57836,560.91347 L 487.95096,560.91347 L 487.90218,561.45849 L 487.32157,561.36952 L 487.17724,560.77574 L 486.88859,560.59515 L 486.54918,561.00442 L 486.21043,560.91413 L 486.11357,561.23047 L 486.50108,561.32404 L 486.50108,564.28509 L 487.86922,564.61461 C 487.83694,564.66998 487.81453,564.71479 487.80532,564.74115 C 487.70844,565.05816 488.19151,565.19654 488.53025,565.05816 C 488.65546,565.00805 489.05352,564.6067 489.30395,564.01093 C 489.65193,563.18385 489.9564,562.09051 490.01702,561.70298 L 490.17257,561.36952 L 489.20641,561.73331 L 488.67524,561.64236 z M 490.5179,556.03925 L 490.45266,555.63922 C 490.15278,553.95341 489.46344,551.79903 487.99905,549.56489 C 485.44859,545.67857 479.8692,544.07777 479.8692,544.07777 L 479.29783,544.68869 L 479.10539,544.37038 L 478.62166,544.1865 L 478.62166,544.59774 L 479.05596,544.96351 L 478.76665,545.09993 L 477.6542,545.19022 L 475.14195,546.55837 L 475.38248,547.65238 L 475.0932,547.74465 L 474.9482,547.97135 L 475.77001,549.2011 L 475.81681,549.61168 L 475.14195,549.74942 L 475.14195,550.56991 L 474.75576,550.65955 L 474.80387,551.29815 L 471.51594,553.57775 L 471.61216,554.85233 C 471.85469,555.17063 473.74019,557.08645 473.74019,557.08645 C 473.74019,557.08645 475.91501,557.17675 476.39742,556.90324 C 476.8805,556.62975 476.54306,557.17675 476.68806,557.31446 C 476.83304,557.45156 476.8805,558.40782 477.02548,558.49943 C 477.17113,558.58971 477.02548,559.13605 477.21791,559.31927 C 477.41167,559.50116 477.41167,561.68982 477.41167,561.68982 C 477.41167,561.68982 478.57223,563.64913 478.57223,564.15001 C 478.57223,564.65152 478.52543,564.60604 479.44215,564.56123 C 480.36152,564.51443 480.55394,564.15001 480.74639,564.01356 C 480.94146,563.8765 480.94146,563.55818 481.1339,563.28402 C 481.32766,563.00988 481.66574,561.96267 482.10136,561.59886 C 482.53566,561.23243 483.6949,560.95894 483.79113,560.32232 C 483.88801,559.68305 484.32428,559.18217 484.32428,559.18217 L 486.41277,556.97179 L 486.35347,557.26967 L 486.30666,558.40913 L 486.93339,558.18177 L 486.8866,556.9507 L 486.6599,556.71147 L 486.69285,556.67588 C 486.69285,556.67588 486.5472,556.40435 486.35347,556.40435 C 486.16101,556.40435 485.00045,556.67588 484.80801,556.63172 C 484.61294,556.58558 483.79113,554.39891 483.64615,554.30863 C 483.5018,554.21702 482.58377,552.7131 482.58377,552.7131 C 482.58377,552.7131 484.71048,555.26555 485.04724,556.26793 C 485.24494,556.85119 485.98176,556.30815 486.58213,555.69852 L 486.73899,556.0867 L 487.12651,555.99311 L 487.07839,555.53971 L 487.51401,555.53971 L 487.51401,556.22246 L 487.36837,556.58691 L 487.31894,557.17938 L 487.70578,557.54383 L 487.89955,557.22682 L 488.52759,556.63303 L 489.25321,556.26859 L 489.44631,556.63303 L 489.54318,557.13589 L 489.34942,557.68089 L 488.96192,558.00053 L 488.76816,558.82102 L 488.76816,559.22963 L 488.33385,558.95681 L 488.28575,558.09148 L 487.659,558.13828 L 487.36903,558.91133 L 487.80333,559.55059 L 488.81758,559.6857 L 489.64004,558.91198 L 489.73628,557.41005 L 490.10271,556.92564 C 490.34061,557.5379 490.50932,558.17914 490.50932,558.77556 C 490.50932,559.42998 490.80919,558.32346 490.55479,556.32791 L 490.5179,556.03925 z M 478.5261,551.47872 L 475.91566,551.38779 L 477.02679,550.47699 L 477.60673,550.47699 L 478.52675,551.11428 L 478.52675,551.47872 L 478.5261,551.47872 z M 481.71516,551.1604 L 481.71516,551.57033 L 480.60402,551.57033 L 480.70025,551.84515 L 480.02408,551.93676 L 479.97465,552.16477 L 479.49224,552.07317 L 478.62232,551.89063 L 478.7673,551.66195 L 478.91295,551.38779 L 479.39538,550.88691 L 479.58911,551.25071 L 480.31405,551.20588 L 480.70157,550.79598 L 482.19824,551.0688 L 481.71516,551.1604 z M 481.81205,550.56861 L 481.23275,550.65823 L 481.13587,550.24831 L 481.86016,550.15737 L 481.95638,549.7481 L 482.48887,550.29445 L 481.81205,550.56861 z M 484.61425,564.7418 L 484.27684,565.01398 L 484.32493,565.69739 L 484.75925,565.69739 L 484.75925,565.10625 L 485.14676,564.60473 L 485.14676,563.55687 L 484.90489,563.51007 L 484.61425,564.7418 z M 481.37708,563.37299 C 481.37708,563.37299 481.03965,563.4626 481.42452,563.60167 C 481.81139,563.7381 483.35814,561.36952 483.35814,561.36952 L 482.0539,562.18805 L 481.37708,563.37299 z M 478.74754,572.10915 L 478.45689,571.83302 L 477.8776,571.74075 L 477.78138,572.01623 L 477.00701,571.92463 L 476.96021,571.5582 L 476.38027,571.5582 L 475.75223,571.92463 L 474.64109,571.92463 L 474.54553,571.65047 L 472.75624,571.46528 L 472.46758,571.74075 L 471.74265,571.55886 L 471.64775,570.91499 L 471.30902,570.8682 L 470.9228,571.55886 L 469.61856,571.51207 C 469.85188,571.62279 471.79866,572.78467 474.78608,573.02785 C 478.74819,573.34813 480.63039,572.38397 480.63039,572.38397 L 480.4854,572.24689 L 478.74754,572.10915 z " + style="fill:url(#radialGradient10503);fill-opacity:1" + id="path10487" /> + <path + d="M 463.22765,552.78788 C 463.22631,552.71934 463.22104,552.6508 463.22104,552.58227 C 463.22104,552.36544 463.23489,552.15126 463.25664,551.93971 C 462.87044,552.47023 462.56729,553.45218 462.6055,553.70196 L 462.70437,554.37088 C 462.40583,554.60945 462.25294,555.23554 462.55476,555.32451 C 463.07078,555.47544 463.57759,555.64876 464.05604,555.82539 C 463.60197,554.97391 463.31925,554.05981 463.24346,553.10752 C 463.13339,552.98758 463.05365,552.89266 463.05365,552.89266 L 463.22765,552.78788 z " + style="fill:none" + id="path10494" /> + </g> + <path + style="fill:url(#linearGradient10506);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient10508);stroke-width:2.4977715;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 769.56671,1702.7194 L 774.41924,1702.7107 C 775.70957,1689.2271 781.42102,1677.1298 790.02919,1671.8664 C 795.0601,1668.7904 799.29969,1668.5333 804.61379,1668.9938 C 802.56891,1669.0526 798.75352,1673.079 795.25637,1675.8638 C 790.37513,1680.3116 786.98967,1689.0551 786.59596,1702.6912 L 794.06734,1702.6755 L 781.31513,1730.3175 L 769.56671,1702.7194 z " + id="path10496" + inkscape:export-filename="/home/vadim/collection.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" + sodipodi:nodetypes="ccscccccc" /> + </g> + <g + id="g10510" + transform="matrix(0.343749,0,0,0.343749,165.48568,646.8793)" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/22x22/actions/amarok_download.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <g + transform="matrix(1.7997159,0,0,1.7706507,-53.063771,707.35048)" + id="g10512"> + <path + id="path10514" + style="fill:url(#linearGradient10522);fill-opacity:1;stroke:url(#linearGradient10525);stroke-width:1.36656916;stroke-miterlimit:4;stroke-dasharray:none" + d="M 475.96622,573.73445 C 484.42079,573.73445 491.27867,566.81816 491.27867,558.33367 C 491.27867,549.90281 484.42011,542.98445 475.96622,542.98445 C 467.57327,542.98445 460.65719,549.90281 460.65719,558.33367 C 460.65719,566.81884 467.57327,573.73445 475.96622,573.73445 z " /> + <path + id="path10516" + style="fill:url(#radialGradient10527);fill-opacity:1" + d="M 471.03814,544.70319 C 470.96367,544.70319 470.88261,544.73417 470.80022,544.78095 C 470.99597,544.72362 471.08759,544.70319 471.03814,544.70319 z M 475.88139,545.6489 L 475.93015,544.93912 L 475.18281,544.98592 L 475.28167,545.6489 L 475.88139,545.6489 z M 467.44378,557.89508 C 467.29285,557.75472 467.39433,557.13852 467.39433,557.13852 C 467.39433,557.13852 465.14769,555.95753 462.69938,555.24841 C 462.39688,555.15943 462.54976,554.53929 462.84963,554.3027 L 462.75012,553.63971 C 462.70004,553.30952 463.24967,551.70083 463.79929,551.55978 C 464.34893,551.4181 463.74986,552.50551 463.74986,552.50551 L 463.19958,552.83699 C 463.19958,552.83699 463.84806,553.59225 463.99964,553.59225 C 464.1499,553.59225 464.39837,553.21331 464.39837,553.21331 L 463.69847,552.74144 L 464.34828,552.45608 L 464.38914,552.20761 L 464.49723,552.174 L 465.5721,550.54553 C 466.31484,550.24039 467.22827,549.8641 467.34359,549.80872 C 467.54394,549.71448 468.94306,548.91179 469.19153,548.72265 C 469.44261,548.53151 469.98961,548.57962 470.18996,548.57962 C 470.3903,548.57962 470.69016,548.48539 470.7396,547.96409 C 470.79101,547.44346 470.99003,547.34922 471.13963,547.49288 C 471.28922,547.63259 470.98938,547.86986 471.33998,547.96409 C 471.68926,548.06031 471.9878,548.29624 472.23758,548.06031 C 472.42081,547.88634 472.17629,547.68927 472.01088,547.54034 L 474.78409,547.54034 L 475.08461,546.68755 L 474.43613,546.59396 L 472.03724,546.35869 L 472.03724,546.07464 L 471.84612,546.10693 C 472.1071,544.67946 473.64792,544.91606 472.48736,544.08963 C 472.4175,544.03756 471.40194,545.68976 471.14359,545.65682 C 470.67566,545.59551 470.06937,545.59091 469.9415,545.74182 C 469.77148,545.94218 470.32374,545.05116 470.80154,544.7803 C 470.04169,545.00503 467.63884,545.82684 465.09825,548.5322 C 462.66906,551.11955 461.7016,554.6052 461.7016,554.72645 C 461.7016,554.96238 462.20246,555.05862 462.25254,555.34199 C 462.30131,555.62471 461.30419,556.56977 461.30419,557.04362 C 461.30419,557.26176 461.06893,560.17603 461.90194,562.57687 C 462.87664,565.37777 464.95724,567.71734 465.19909,567.87222 L 465.69733,567.63562 C 465.69733,567.63562 464.54928,565.60251 464.49919,565.41337 C 464.44976,565.22488 465.79881,562.48132 466.44665,562.57752 C 467.09514,562.66981 466.94619,562.86093 467.34492,562.623 C 467.74693,562.38773 467.99405,560.44687 468.44286,560.25971 C 468.89364,560.06926 469.39385,559.83266 469.34309,559.36145 C 469.29039,558.88958 467.59403,558.03876 467.44378,557.89508 z M 478.27896,543.99341 L 476.99251,543.73705 L 477.17968,544.27811 L 478.27896,543.99341 z M 473.23469,545.74249 C 473.38627,545.74249 476.3816,543.75748 476.08041,543.71069 C 475.78252,543.66389 475.70805,543.86095 474.90734,543.7667 C 474.10924,543.67114 473.2848,544.74998 473.08576,544.93912 C 472.88609,545.12893 472.95329,545.74249 473.23469,545.74249 z M 486.98282,560.00072 L 487.37033,559.49984 L 486.98282,559.36341 L 486.69418,559.72786 L 486.35478,560.22873 L 486.64541,560.36648 L 486.98282,560.00072 z M 488.67524,561.64236 L 488.57836,560.91347 L 487.95096,560.91347 L 487.90218,561.45849 L 487.32157,561.36952 L 487.17724,560.77574 L 486.88859,560.59515 L 486.54918,561.00442 L 486.21043,560.91413 L 486.11357,561.23047 L 486.50108,561.32404 L 486.50108,564.28509 L 487.86922,564.61461 C 487.83694,564.66998 487.81453,564.71479 487.80532,564.74115 C 487.70844,565.05816 488.19151,565.19654 488.53025,565.05816 C 488.65546,565.00805 489.05352,564.6067 489.30395,564.01093 C 489.65193,563.18385 489.9564,562.09051 490.01702,561.70298 L 490.17257,561.36952 L 489.20641,561.73331 L 488.67524,561.64236 z M 490.5179,556.03925 L 490.45266,555.63922 C 490.15278,553.95341 489.46344,551.79903 487.99905,549.56489 C 485.44859,545.67857 479.8692,544.07777 479.8692,544.07777 L 479.29783,544.68869 L 479.10539,544.37038 L 478.62166,544.1865 L 478.62166,544.59774 L 479.05596,544.96351 L 478.76665,545.09993 L 477.6542,545.19022 L 475.14195,546.55837 L 475.38248,547.65238 L 475.0932,547.74465 L 474.9482,547.97135 L 475.77001,549.2011 L 475.81681,549.61168 L 475.14195,549.74942 L 475.14195,550.56991 L 474.75576,550.65955 L 474.80387,551.29815 L 471.51594,553.57775 L 471.61216,554.85233 C 471.85469,555.17063 473.74019,557.08645 473.74019,557.08645 C 473.74019,557.08645 475.91501,557.17675 476.39742,556.90324 C 476.8805,556.62975 476.54306,557.17675 476.68806,557.31446 C 476.83304,557.45156 476.8805,558.40782 477.02548,558.49943 C 477.17113,558.58971 477.02548,559.13605 477.21791,559.31927 C 477.41167,559.50116 477.41167,561.68982 477.41167,561.68982 C 477.41167,561.68982 478.57223,563.64913 478.57223,564.15001 C 478.57223,564.65152 478.52543,564.60604 479.44215,564.56123 C 480.36152,564.51443 480.55394,564.15001 480.74639,564.01356 C 480.94146,563.8765 480.94146,563.55818 481.1339,563.28402 C 481.32766,563.00988 481.66574,561.96267 482.10136,561.59886 C 482.53566,561.23243 483.6949,560.95894 483.79113,560.32232 C 483.88801,559.68305 484.32428,559.18217 484.32428,559.18217 L 486.41277,556.97179 L 486.35347,557.26967 L 486.30666,558.40913 L 486.93339,558.18177 L 486.8866,556.9507 L 486.6599,556.71147 L 486.69285,556.67588 C 486.69285,556.67588 486.5472,556.40435 486.35347,556.40435 C 486.16101,556.40435 485.00045,556.67588 484.80801,556.63172 C 484.61294,556.58558 483.79113,554.39891 483.64615,554.30863 C 483.5018,554.21702 482.58377,552.7131 482.58377,552.7131 C 482.58377,552.7131 484.71048,555.26555 485.04724,556.26793 C 485.24494,556.85119 485.98176,556.30815 486.58213,555.69852 L 486.73899,556.0867 L 487.12651,555.99311 L 487.07839,555.53971 L 487.51401,555.53971 L 487.51401,556.22246 L 487.36837,556.58691 L 487.31894,557.17938 L 487.70578,557.54383 L 487.89955,557.22682 L 488.52759,556.63303 L 489.25321,556.26859 L 489.44631,556.63303 L 489.54318,557.13589 L 489.34942,557.68089 L 488.96192,558.00053 L 488.76816,558.82102 L 488.76816,559.22963 L 488.33385,558.95681 L 488.28575,558.09148 L 487.659,558.13828 L 487.36903,558.91133 L 487.80333,559.55059 L 488.81758,559.6857 L 489.64004,558.91198 L 489.73628,557.41005 L 490.10271,556.92564 C 490.34061,557.5379 490.50932,558.17914 490.50932,558.77556 C 490.50932,559.42998 490.80919,558.32346 490.55479,556.32791 L 490.5179,556.03925 z M 478.5261,551.47872 L 475.91566,551.38779 L 477.02679,550.47699 L 477.60673,550.47699 L 478.52675,551.11428 L 478.52675,551.47872 L 478.5261,551.47872 z M 481.71516,551.1604 L 481.71516,551.57033 L 480.60402,551.57033 L 480.70025,551.84515 L 480.02408,551.93676 L 479.97465,552.16477 L 479.49224,552.07317 L 478.62232,551.89063 L 478.7673,551.66195 L 478.91295,551.38779 L 479.39538,550.88691 L 479.58911,551.25071 L 480.31405,551.20588 L 480.70157,550.79598 L 482.19824,551.0688 L 481.71516,551.1604 z M 481.81205,550.56861 L 481.23275,550.65823 L 481.13587,550.24831 L 481.86016,550.15737 L 481.95638,549.7481 L 482.48887,550.29445 L 481.81205,550.56861 z M 484.61425,564.7418 L 484.27684,565.01398 L 484.32493,565.69739 L 484.75925,565.69739 L 484.75925,565.10625 L 485.14676,564.60473 L 485.14676,563.55687 L 484.90489,563.51007 L 484.61425,564.7418 z M 481.37708,563.37299 C 481.37708,563.37299 481.03965,563.4626 481.42452,563.60167 C 481.81139,563.7381 483.35814,561.36952 483.35814,561.36952 L 482.0539,562.18805 L 481.37708,563.37299 z M 478.74754,572.10915 L 478.45689,571.83302 L 477.8776,571.74075 L 477.78138,572.01623 L 477.00701,571.92463 L 476.96021,571.5582 L 476.38027,571.5582 L 475.75223,571.92463 L 474.64109,571.92463 L 474.54553,571.65047 L 472.75624,571.46528 L 472.46758,571.74075 L 471.74265,571.55886 L 471.64775,570.91499 L 471.30902,570.8682 L 470.9228,571.55886 L 469.61856,571.51207 C 469.85188,571.62279 471.79866,572.78467 474.78608,573.02785 C 478.74819,573.34813 480.63039,572.38397 480.63039,572.38397 L 480.4854,572.24689 L 478.74754,572.10915 z " /> + <path + id="path10518" + style="fill:none" + d="M 463.22765,552.78788 C 463.22631,552.71934 463.22104,552.6508 463.22104,552.58227 C 463.22104,552.36544 463.23489,552.15126 463.25664,551.93971 C 462.87044,552.47023 462.56729,553.45218 462.6055,553.70196 L 462.70437,554.37088 C 462.40583,554.60945 462.25294,555.23554 462.55476,555.32451 C 463.07078,555.47544 463.57759,555.64876 464.05604,555.82539 C 463.60197,554.97391 463.31925,554.05981 463.24346,553.10752 C 463.13339,552.98758 463.05365,552.89266 463.05365,552.89266 L 463.22765,552.78788 z " /> + </g> + <path + sodipodi:nodetypes="ccscccccc" + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/vadim/collection.png" + id="path10520" + d="M 769.56671,1702.7194 L 774.41924,1702.7107 C 775.70957,1689.2271 781.42102,1677.1298 790.02919,1671.8664 C 795.0601,1668.7904 799.29969,1668.5333 804.61379,1668.9938 C 802.56891,1669.0526 798.75352,1673.079 795.25637,1675.8638 C 790.37513,1680.3116 786.98967,1689.0551 786.59596,1702.6912 L 794.06734,1702.6755 L 781.31513,1730.3175 L 769.56671,1702.7194 z " + style="fill:url(#linearGradient10529);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient10531);stroke-width:2.4977715;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + <g + transform="matrix(0.2499993,0,0,0.2499993,200.37336,810.7702)" + id="g10533" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/16x16/actions/amarok_download.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <g + id="g10535" + transform="matrix(1.7997159,0,0,1.7706507,-53.063771,707.35048)"> + <path + d="M 475.96622,573.73445 C 484.42079,573.73445 491.27867,566.81816 491.27867,558.33367 C 491.27867,549.90281 484.42011,542.98445 475.96622,542.98445 C 467.57327,542.98445 460.65719,549.90281 460.65719,558.33367 C 460.65719,566.81884 467.57327,573.73445 475.96622,573.73445 z " + style="fill:url(#linearGradient10545);fill-opacity:1;stroke:url(#linearGradient10547);stroke-width:1.36656916;stroke-miterlimit:4;stroke-dasharray:none" + id="path10537" /> + <path + d="M 471.03814,544.70319 C 470.96367,544.70319 470.88261,544.73417 470.80022,544.78095 C 470.99597,544.72362 471.08759,544.70319 471.03814,544.70319 z M 475.88139,545.6489 L 475.93015,544.93912 L 475.18281,544.98592 L 475.28167,545.6489 L 475.88139,545.6489 z M 467.44378,557.89508 C 467.29285,557.75472 467.39433,557.13852 467.39433,557.13852 C 467.39433,557.13852 465.14769,555.95753 462.69938,555.24841 C 462.39688,555.15943 462.54976,554.53929 462.84963,554.3027 L 462.75012,553.63971 C 462.70004,553.30952 463.24967,551.70083 463.79929,551.55978 C 464.34893,551.4181 463.74986,552.50551 463.74986,552.50551 L 463.19958,552.83699 C 463.19958,552.83699 463.84806,553.59225 463.99964,553.59225 C 464.1499,553.59225 464.39837,553.21331 464.39837,553.21331 L 463.69847,552.74144 L 464.34828,552.45608 L 464.38914,552.20761 L 464.49723,552.174 L 465.5721,550.54553 C 466.31484,550.24039 467.22827,549.8641 467.34359,549.80872 C 467.54394,549.71448 468.94306,548.91179 469.19153,548.72265 C 469.44261,548.53151 469.98961,548.57962 470.18996,548.57962 C 470.3903,548.57962 470.69016,548.48539 470.7396,547.96409 C 470.79101,547.44346 470.99003,547.34922 471.13963,547.49288 C 471.28922,547.63259 470.98938,547.86986 471.33998,547.96409 C 471.68926,548.06031 471.9878,548.29624 472.23758,548.06031 C 472.42081,547.88634 472.17629,547.68927 472.01088,547.54034 L 474.78409,547.54034 L 475.08461,546.68755 L 474.43613,546.59396 L 472.03724,546.35869 L 472.03724,546.07464 L 471.84612,546.10693 C 472.1071,544.67946 473.64792,544.91606 472.48736,544.08963 C 472.4175,544.03756 471.40194,545.68976 471.14359,545.65682 C 470.67566,545.59551 470.06937,545.59091 469.9415,545.74182 C 469.77148,545.94218 470.32374,545.05116 470.80154,544.7803 C 470.04169,545.00503 467.63884,545.82684 465.09825,548.5322 C 462.66906,551.11955 461.7016,554.6052 461.7016,554.72645 C 461.7016,554.96238 462.20246,555.05862 462.25254,555.34199 C 462.30131,555.62471 461.30419,556.56977 461.30419,557.04362 C 461.30419,557.26176 461.06893,560.17603 461.90194,562.57687 C 462.87664,565.37777 464.95724,567.71734 465.19909,567.87222 L 465.69733,567.63562 C 465.69733,567.63562 464.54928,565.60251 464.49919,565.41337 C 464.44976,565.22488 465.79881,562.48132 466.44665,562.57752 C 467.09514,562.66981 466.94619,562.86093 467.34492,562.623 C 467.74693,562.38773 467.99405,560.44687 468.44286,560.25971 C 468.89364,560.06926 469.39385,559.83266 469.34309,559.36145 C 469.29039,558.88958 467.59403,558.03876 467.44378,557.89508 z M 478.27896,543.99341 L 476.99251,543.73705 L 477.17968,544.27811 L 478.27896,543.99341 z M 473.23469,545.74249 C 473.38627,545.74249 476.3816,543.75748 476.08041,543.71069 C 475.78252,543.66389 475.70805,543.86095 474.90734,543.7667 C 474.10924,543.67114 473.2848,544.74998 473.08576,544.93912 C 472.88609,545.12893 472.95329,545.74249 473.23469,545.74249 z M 486.98282,560.00072 L 487.37033,559.49984 L 486.98282,559.36341 L 486.69418,559.72786 L 486.35478,560.22873 L 486.64541,560.36648 L 486.98282,560.00072 z M 488.67524,561.64236 L 488.57836,560.91347 L 487.95096,560.91347 L 487.90218,561.45849 L 487.32157,561.36952 L 487.17724,560.77574 L 486.88859,560.59515 L 486.54918,561.00442 L 486.21043,560.91413 L 486.11357,561.23047 L 486.50108,561.32404 L 486.50108,564.28509 L 487.86922,564.61461 C 487.83694,564.66998 487.81453,564.71479 487.80532,564.74115 C 487.70844,565.05816 488.19151,565.19654 488.53025,565.05816 C 488.65546,565.00805 489.05352,564.6067 489.30395,564.01093 C 489.65193,563.18385 489.9564,562.09051 490.01702,561.70298 L 490.17257,561.36952 L 489.20641,561.73331 L 488.67524,561.64236 z M 490.5179,556.03925 L 490.45266,555.63922 C 490.15278,553.95341 489.46344,551.79903 487.99905,549.56489 C 485.44859,545.67857 479.8692,544.07777 479.8692,544.07777 L 479.29783,544.68869 L 479.10539,544.37038 L 478.62166,544.1865 L 478.62166,544.59774 L 479.05596,544.96351 L 478.76665,545.09993 L 477.6542,545.19022 L 475.14195,546.55837 L 475.38248,547.65238 L 475.0932,547.74465 L 474.9482,547.97135 L 475.77001,549.2011 L 475.81681,549.61168 L 475.14195,549.74942 L 475.14195,550.56991 L 474.75576,550.65955 L 474.80387,551.29815 L 471.51594,553.57775 L 471.61216,554.85233 C 471.85469,555.17063 473.74019,557.08645 473.74019,557.08645 C 473.74019,557.08645 475.91501,557.17675 476.39742,556.90324 C 476.8805,556.62975 476.54306,557.17675 476.68806,557.31446 C 476.83304,557.45156 476.8805,558.40782 477.02548,558.49943 C 477.17113,558.58971 477.02548,559.13605 477.21791,559.31927 C 477.41167,559.50116 477.41167,561.68982 477.41167,561.68982 C 477.41167,561.68982 478.57223,563.64913 478.57223,564.15001 C 478.57223,564.65152 478.52543,564.60604 479.44215,564.56123 C 480.36152,564.51443 480.55394,564.15001 480.74639,564.01356 C 480.94146,563.8765 480.94146,563.55818 481.1339,563.28402 C 481.32766,563.00988 481.66574,561.96267 482.10136,561.59886 C 482.53566,561.23243 483.6949,560.95894 483.79113,560.32232 C 483.88801,559.68305 484.32428,559.18217 484.32428,559.18217 L 486.41277,556.97179 L 486.35347,557.26967 L 486.30666,558.40913 L 486.93339,558.18177 L 486.8866,556.9507 L 486.6599,556.71147 L 486.69285,556.67588 C 486.69285,556.67588 486.5472,556.40435 486.35347,556.40435 C 486.16101,556.40435 485.00045,556.67588 484.80801,556.63172 C 484.61294,556.58558 483.79113,554.39891 483.64615,554.30863 C 483.5018,554.21702 482.58377,552.7131 482.58377,552.7131 C 482.58377,552.7131 484.71048,555.26555 485.04724,556.26793 C 485.24494,556.85119 485.98176,556.30815 486.58213,555.69852 L 486.73899,556.0867 L 487.12651,555.99311 L 487.07839,555.53971 L 487.51401,555.53971 L 487.51401,556.22246 L 487.36837,556.58691 L 487.31894,557.17938 L 487.70578,557.54383 L 487.89955,557.22682 L 488.52759,556.63303 L 489.25321,556.26859 L 489.44631,556.63303 L 489.54318,557.13589 L 489.34942,557.68089 L 488.96192,558.00053 L 488.76816,558.82102 L 488.76816,559.22963 L 488.33385,558.95681 L 488.28575,558.09148 L 487.659,558.13828 L 487.36903,558.91133 L 487.80333,559.55059 L 488.81758,559.6857 L 489.64004,558.91198 L 489.73628,557.41005 L 490.10271,556.92564 C 490.34061,557.5379 490.50932,558.17914 490.50932,558.77556 C 490.50932,559.42998 490.80919,558.32346 490.55479,556.32791 L 490.5179,556.03925 z M 478.5261,551.47872 L 475.91566,551.38779 L 477.02679,550.47699 L 477.60673,550.47699 L 478.52675,551.11428 L 478.52675,551.47872 L 478.5261,551.47872 z M 481.71516,551.1604 L 481.71516,551.57033 L 480.60402,551.57033 L 480.70025,551.84515 L 480.02408,551.93676 L 479.97465,552.16477 L 479.49224,552.07317 L 478.62232,551.89063 L 478.7673,551.66195 L 478.91295,551.38779 L 479.39538,550.88691 L 479.58911,551.25071 L 480.31405,551.20588 L 480.70157,550.79598 L 482.19824,551.0688 L 481.71516,551.1604 z M 481.81205,550.56861 L 481.23275,550.65823 L 481.13587,550.24831 L 481.86016,550.15737 L 481.95638,549.7481 L 482.48887,550.29445 L 481.81205,550.56861 z M 484.61425,564.7418 L 484.27684,565.01398 L 484.32493,565.69739 L 484.75925,565.69739 L 484.75925,565.10625 L 485.14676,564.60473 L 485.14676,563.55687 L 484.90489,563.51007 L 484.61425,564.7418 z M 481.37708,563.37299 C 481.37708,563.37299 481.03965,563.4626 481.42452,563.60167 C 481.81139,563.7381 483.35814,561.36952 483.35814,561.36952 L 482.0539,562.18805 L 481.37708,563.37299 z M 478.74754,572.10915 L 478.45689,571.83302 L 477.8776,571.74075 L 477.78138,572.01623 L 477.00701,571.92463 L 476.96021,571.5582 L 476.38027,571.5582 L 475.75223,571.92463 L 474.64109,571.92463 L 474.54553,571.65047 L 472.75624,571.46528 L 472.46758,571.74075 L 471.74265,571.55886 L 471.64775,570.91499 L 471.30902,570.8682 L 470.9228,571.55886 L 469.61856,571.51207 C 469.85188,571.62279 471.79866,572.78467 474.78608,573.02785 C 478.74819,573.34813 480.63039,572.38397 480.63039,572.38397 L 480.4854,572.24689 L 478.74754,572.10915 z " + style="fill:url(#radialGradient10549);fill-opacity:1" + id="path10539" /> + <path + d="M 463.22765,552.78788 C 463.22631,552.71934 463.22104,552.6508 463.22104,552.58227 C 463.22104,552.36544 463.23489,552.15126 463.25664,551.93971 C 462.87044,552.47023 462.56729,553.45218 462.6055,553.70196 L 462.70437,554.37088 C 462.40583,554.60945 462.25294,555.23554 462.55476,555.32451 C 463.07078,555.47544 463.57759,555.64876 464.05604,555.82539 C 463.60197,554.97391 463.31925,554.05981 463.24346,553.10752 C 463.13339,552.98758 463.05365,552.89266 463.05365,552.89266 L 463.22765,552.78788 z " + style="fill:none" + id="path10541" /> + </g> + <path + style="fill:url(#linearGradient10551);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient10553);stroke-width:2.4977715;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 769.56671,1702.7194 L 774.41924,1702.7107 C 775.70957,1689.2271 781.42102,1677.1298 790.02919,1671.8664 C 795.0601,1668.7904 799.29969,1668.5333 804.61379,1668.9938 C 802.56891,1669.0526 798.75352,1673.079 795.25637,1675.8638 C 790.37513,1680.3116 786.98967,1689.0551 786.59596,1702.6912 L 794.06734,1702.6755 L 781.31513,1730.3175 L 769.56671,1702.7194 z " + id="path10543" + inkscape:export-filename="/home/vadim/collection.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" + sodipodi:nodetypes="ccscccccc" /> + </g> + <g + id="g13512" + transform="translate(-7,-79.747803)"> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/64x64/actions/amarok_covermanager.png" + transform="matrix(0.8827822,0,0,0.8827822,-41.723857,-356.61298)" + id="g10283"> + <g + transform="matrix(3.104024,0,0,3.104024,-654.6348,981.6642)" + id="g10736" + inkscape:export-filename="/home/lando/Projects/Amarok/Tool-icons/amarok_covermanager.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <rect + y="134.63829" + x="569.30548" + height="20.578251" + width="20.63953" + id="rect10738" + style="fill:url(#linearGradient10338);fill-opacity:1;stroke:none;stroke-width:1.36047041;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + sodipodi:nodetypes="cssssccsssc" + id="path10740" + d="M 570.42085,145.8102 C 570.8889,151.10014 575.55898,155.02506 580.84766,154.57165 C 586.13636,154.11825 590.04954,149.45748 589.58149,144.16753 C 589.11344,138.87755 584.44062,134.95286 579.15194,135.40629 C 574.37985,135.81541 570.72993,139.64985 570.40623,144.2813 C 570.37119,144.78271 570.37513,145.29344 570.42085,145.8102 z M 577.19506,145.22944 C 577.05303,143.62397 578.19336,142.21399 579.74253,142.08118 C 581.29168,141.94835 582.66523,143.14283 582.80728,144.74827 C 582.94931,146.35374 581.80622,147.7639 580.25705,147.89671 C 578.70791,148.02955 577.33711,146.83482 577.19506,145.22944 z " + style="opacity:0.5;fill:url(#linearGradient10340);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient10342);stroke-width:0.86471558;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + transform="matrix(-1.154934e-2,-0.130532,0.137633,-1.179942e-2,390.2798,333.5555)" + d="M 1376.7952 1487.2524 A 68.554123 64.983597 0 1 1 1239.687,1487.2524 A 68.554123 64.983597 0 1 1 1376.7952 1487.2524 z" + sodipodi:ry="64.983597" + sodipodi:rx="68.554123" + sodipodi:cy="1487.2524" + sodipodi:cx="1308.2411" + id="path10742" + style="fill:url(#linearGradient10344);fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:4.02878094;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + sodipodi:type="arc" /> + <path + sodipodi:nodetypes="ccccc" + id="path10744" + d="M 578.28881,147.30247 C 578.18646,147.24432 578.00323,147.20786 577.93039,147.10521 L 573.67729,151.46706 C 574.91512,152.77018 576.43825,153.69773 578.17312,153.78613 L 578.28881,147.30247 z " + style="fill:url(#linearGradient10346);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient10348);stroke-width:0.1909831;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" /> + <path + sodipodi:nodetypes="ccccc" + id="path10746" + d="M 581.78706,142.86262 C 581.89083,142.92112 581.98179,142.99931 582.05559,143.10261 L 586.59163,138.58961 C 585.33698,137.2781 583.66819,136.42176 581.90864,136.33387 L 581.78706,142.86262 z " + style="fill:url(#linearGradient10350);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient10352);stroke-width:0.19300705;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" /> + <rect + y="134.61842" + x="568.62524" + height="20.618" + width="1.3694109" + id="rect10748" + style="opacity:1;fill:url(#linearGradient18308);fill-opacity:1;stroke:none;stroke-width:0.97624999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + style="opacity:0.5;fill:url(#linearGradient10354);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 589.92675,144.21707 C 590.64196,146.74179 585.47222,143.54203 579.98362,143.54203 C 574.49503,143.54203 569.88413,147.68044 570.04051,144.35284 C 570.27811,139.29709 574.52897,134.98184 579.98362,134.98184 C 585.20068,134.9479 589.62127,139.12738 589.92675,144.21707 z " + id="path10750" + sodipodi:nodetypes="csscc" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" /> + </g> + <rect + ry="0" + rx="0" + y="1399.5242" + x="1104.4021" + height="63.998749" + width="8.3038969" + id="rect10752" + style="fill:url(#linearGradient10356);fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + </g> + <rect + style="fill:#b9cff9;fill-opacity:0;stroke:none;stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + id="rect10405" + width="64" + height="64" + x="933.22266" + y="875.10999" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/64x64/actions/amarok_covermanager.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + </g> + <g + id="g13485" + transform="translate(-7,-79.747803)"> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/32x32/actions/amarok_covermanager.png" + transform="matrix(0.8827817,0,0,0.8827817,-54.536667,-355.00293)" + id="g10305"> + <g + transform="matrix(1.552012,0,0,1.552012,87.88349,1217.612)" + id="g10652" + inkscape:export-filename="/home/lando/Projects/Amarok/Tool-icons/amarok_covermanager.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <rect + y="134.63829" + x="569.30548" + height="20.578251" + width="20.63953" + id="rect10654" + style="fill:url(#linearGradient10378);fill-opacity:1;stroke:none;stroke-width:1.36047041;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + sodipodi:nodetypes="cssssccsssc" + id="path10656" + d="M 570.42085,145.8102 C 570.8889,151.10014 575.55898,155.02506 580.84766,154.57165 C 586.13636,154.11825 590.04954,149.45748 589.58149,144.16753 C 589.11344,138.87755 584.44062,134.95286 579.15194,135.40629 C 574.37985,135.81541 570.72993,139.64985 570.40623,144.2813 C 570.37119,144.78271 570.37513,145.29344 570.42085,145.8102 z M 577.19506,145.22944 C 577.05303,143.62397 578.19336,142.21399 579.74253,142.08118 C 581.29168,141.94835 582.66523,143.14283 582.80728,144.74827 C 582.94931,146.35374 581.80622,147.7639 580.25705,147.89671 C 578.70791,148.02955 577.33711,146.83482 577.19506,145.22944 z " + style="opacity:0.5;fill:url(#linearGradient10380);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient10382);stroke-width:0.86471605;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + transform="matrix(-1.154934e-2,-0.130532,0.137633,-1.179942e-2,390.2798,333.5555)" + d="M 1376.7952 1487.2524 A 68.554123 64.983597 0 1 1 1239.687,1487.2524 A 68.554123 64.983597 0 1 1 1376.7952 1487.2524 z" + sodipodi:ry="64.983597" + sodipodi:rx="68.554123" + sodipodi:cy="1487.2524" + sodipodi:cx="1308.2411" + id="path10658" + style="fill:url(#linearGradient10384);fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:4.02878284;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + sodipodi:type="arc" /> + <path + sodipodi:nodetypes="ccccc" + id="path10660" + d="M 578.28881,147.30247 C 578.18646,147.24432 578.00323,147.20786 577.93039,147.10521 L 573.67729,151.46706 C 574.91512,152.77018 576.43825,153.69773 578.17312,153.78613 L 578.28881,147.30247 z " + style="fill:url(#linearGradient10386);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient10388);stroke-width:0.19098322;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" /> + <path + sodipodi:nodetypes="ccccc" + id="path10662" + d="M 581.78706,142.86262 C 581.89083,142.92112 581.98179,142.99931 582.05559,143.10261 L 586.59163,138.58961 C 585.33698,137.2781 583.66819,136.42176 581.90864,136.33387 L 581.78706,142.86262 z " + style="fill:url(#linearGradient10390);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient10392);stroke-width:0.19300719;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" /> + <rect + y="134.61842" + x="568.62524" + height="20.618" + width="1.3694109" + id="rect10664" + style="opacity:1;fill:url(#linearGradient18308);fill-opacity:1;stroke:none;stroke-width:0.97624999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + style="opacity:0.5;fill:url(#linearGradient10394);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 589.92675,144.21707 C 590.64196,146.74179 585.47222,143.54203 579.98362,143.54203 C 574.49503,143.54203 569.88413,147.68044 570.04051,144.35284 C 570.27811,139.29709 574.52897,134.98184 579.98362,134.98184 C 585.20068,134.9479 589.62127,139.12738 589.92675,144.21707 z " + id="path10666" + sodipodi:nodetypes="csscc" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" /> + </g> + <rect + ry="0" + rx="0" + y="1426.5422" + x="967.40192" + height="31.999374" + width="4.1519485" + id="rect10668" + style="fill:url(#linearGradient10396);fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + </g> + <rect + style="fill:#b9cff9;fill-opacity:0;stroke:none;stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + id="rect10409" + width="32" + height="32" + x="799.46808" + y="902.44629" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/32x32/actions/amarok_covermanager.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + </g> + <g + id="g13472" + transform="translate(-7,-79.747803)"> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/22x22/actions/amarok_covermanager.png" + transform="matrix(0.882767,0,0,0.882767,-60.396217,-360.21631)" + id="g10316"> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Tool-icons/amarok_covermanager.png" + id="g8167" + transform="matrix(1.067029,0,0,1.067029,323.7211,1300.006)"> + <rect + style="fill:url(#linearGradient10398);fill-opacity:1;stroke:none;stroke-width:1.36047041;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect8169" + width="20.63953" + height="20.578251" + x="569.30548" + y="134.63829" /> + <path + style="opacity:0.5;fill:url(#linearGradient10400);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient10402);stroke-width:0.86473042;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 570.42085,145.8102 C 570.8889,151.10014 575.55898,155.02506 580.84766,154.57165 C 586.13636,154.11825 590.04954,149.45748 589.58149,144.16753 C 589.11344,138.87755 584.44062,134.95286 579.15194,135.40629 C 574.37985,135.81541 570.72993,139.64985 570.40623,144.2813 C 570.37119,144.78271 570.37513,145.29344 570.42085,145.8102 z M 577.19506,145.22944 C 577.05303,143.62397 578.19336,142.21399 579.74253,142.08118 C 581.29168,141.94835 582.66523,143.14283 582.80728,144.74827 C 582.94931,146.35374 581.80622,147.7639 580.25705,147.89671 C 578.70791,148.02955 577.33711,146.83482 577.19506,145.22944 z " + id="path8171" + sodipodi:nodetypes="cssssccsssc" /> + <path + sodipodi:type="arc" + style="fill:url(#linearGradient10404);fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:4.0288496;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + id="path8173" + sodipodi:cx="1308.2411" + sodipodi:cy="1487.2524" + sodipodi:rx="68.554123" + sodipodi:ry="64.983597" + d="M 1376.7952 1487.2524 A 68.554123 64.983597 0 1 1 1239.687,1487.2524 A 68.554123 64.983597 0 1 1 1376.7952 1487.2524 z" + transform="matrix(-1.154934e-2,-0.130532,0.137633,-1.179942e-2,390.2798,333.5555)" /> + <path + style="fill:url(#linearGradient10406);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient10408);stroke-width:0.19098637;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + d="M 578.28881,147.30247 C 578.18646,147.24432 578.00323,147.20786 577.93039,147.10521 L 573.67729,151.46706 C 574.91512,152.77018 576.43825,153.69773 578.17312,153.78613 L 578.28881,147.30247 z " + id="path8175" + sodipodi:nodetypes="ccccc" /> + <path + style="fill:url(#linearGradient10410);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient10412);stroke-width:0.19301039;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + d="M 581.78706,142.86262 C 581.89083,142.92112 581.98179,142.99931 582.05559,143.10261 L 586.59163,138.58961 C 585.33698,137.2781 583.66819,136.42176 581.90864,136.33387 L 581.78706,142.86262 z " + id="path8177" + sodipodi:nodetypes="ccccc" /> + <rect + style="opacity:1;fill:url(#linearGradient18308);fill-opacity:1;stroke:none;stroke-width:0.97624999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect8179" + width="1.3694109" + height="20.618" + x="568.62524" + y="134.61842" /> + <path + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + sodipodi:nodetypes="csscc" + id="path8181" + d="M 589.92675,144.21707 C 590.64196,146.74179 585.47222,143.54203 579.98362,143.54203 C 574.49503,143.54203 569.88413,147.68044 570.04051,144.35284 C 570.27811,139.29709 574.52897,134.98184 579.98362,134.98184 C 585.20068,134.9479 589.62127,139.12738 589.92675,144.21707 z " + style="opacity:0.5;fill:url(#linearGradient10414);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" /> + </g> + <rect + style="fill:url(#linearGradient10416);fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect9674" + width="2.8545206" + height="22" + x="928.40186" + y="1443.6477" + rx="0" + ry="0" /> + </g> + <rect + y="912.89868" + x="759.16632" + height="22" + width="22" + id="rect10411" + style="fill:#b9cff9;fill-opacity:0;stroke:none;stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/22x22/actions/amarok_covermanager.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + </g> + <g + id="g13459" + transform="translate(-7,-79.747803)"> + <g + transform="matrix(0.8827968,0,0,0.8827968,-65.282849,-361.88614)" + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/16x16/actions/amarok_covermanager.png" + id="g10327"> + <g + transform="matrix(0.776005,0,0,0.776005,454.6425,1346.07)" + id="g10570" + inkscape:export-filename="/home/lando/Projects/Amarok/Tool-icons/amarok_covermanager.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <rect + y="134.63829" + x="569.30548" + height="20.578251" + width="20.63953" + id="rect10572" + style="fill:url(#linearGradient10588);fill-opacity:1;stroke:none;stroke-width:1.36047041;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + sodipodi:nodetypes="cssssccsssc" + id="path10574" + d="M 570.42085,145.8102 C 570.8889,151.10014 575.55898,155.02506 580.84766,154.57165 C 586.13636,154.11825 590.04954,149.45748 589.58149,144.16753 C 589.11344,138.87755 584.44062,134.95286 579.15194,135.40629 C 574.37985,135.81541 570.72993,139.64985 570.40623,144.2813 C 570.37119,144.78271 570.37513,145.29344 570.42085,145.8102 z M 577.19506,145.22944 C 577.05303,143.62397 578.19336,142.21399 579.74253,142.08118 C 581.29168,141.94835 582.66523,143.14283 582.80728,144.74827 C 582.94931,146.35374 581.80622,147.7639 580.25705,147.89671 C 578.70791,148.02955 577.33711,146.83482 577.19506,145.22944 z " + style="opacity:0.5;fill:url(#linearGradient10590);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient10592);stroke-width:0.86470121;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + transform="matrix(-1.154934e-2,-0.130532,0.137633,-1.179942e-2,390.2798,333.5555)" + d="M 1376.7952 1487.2524 A 68.554123 64.983597 0 1 1 1239.687,1487.2524 A 68.554123 64.983597 0 1 1 1376.7952 1487.2524 z" + sodipodi:ry="64.983597" + sodipodi:rx="68.554123" + sodipodi:cy="1487.2524" + sodipodi:cx="1308.2411" + id="path10576" + style="fill:url(#linearGradient10594);fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:4.0287137;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + sodipodi:type="arc" /> + <path + sodipodi:nodetypes="ccccc" + id="path10578" + d="M 578.28881,147.30247 C 578.18646,147.24432 578.00323,147.20786 577.93039,147.10521 L 573.67729,151.46706 C 574.91512,152.77018 576.43825,153.69773 578.17312,153.78613 L 578.28881,147.30247 z " + style="fill:url(#linearGradient10596);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient10598);stroke-width:0.19097991;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" /> + <path + sodipodi:nodetypes="ccccc" + id="path10580" + d="M 581.78706,142.86262 C 581.89083,142.92112 581.98179,142.99931 582.05559,143.10261 L 586.59163,138.58961 C 585.33698,137.2781 583.66819,136.42176 581.90864,136.33387 L 581.78706,142.86262 z " + style="fill:url(#linearGradient10600);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient10602);stroke-width:0.19300386;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" /> + <rect + y="134.61842" + x="568.62524" + height="20.618" + width="1.3694109" + id="rect10582" + style="opacity:1;fill:url(#linearGradient18308);fill-opacity:1;stroke:none;stroke-width:0.97624999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + style="opacity:0.5;fill:url(#linearGradient10604);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 589.92675,144.21707 C 590.64196,146.74179 585.47222,143.54203 579.98362,143.54203 C 574.49503,143.54203 569.88413,147.68044 570.04051,144.35284 C 570.27811,139.29709 574.52897,134.98184 579.98362,134.98184 C 585.20068,134.9479 589.62127,139.12738 589.92675,144.21707 z " + id="path10584" + sodipodi:nodetypes="csscc" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" /> + </g> + <rect + ry="0" + rx="0" + y="1450.5349" + x="894.40143" + height="15.999676" + width="2.0759728" + id="rect10586" + style="fill:url(#linearGradient10209);fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + </g> + <rect + y="917.70349" + x="724.29187" + height="16" + width="16" + id="rect10413" + style="fill:#b9cff9;fill-opacity:0;stroke:none;stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/16x16/actions/amarok_covermanager.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + </g> + <g + inkscape:export-ydpi="90.000092" + inkscape:export-xdpi="90.000092" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/22x22/actions/collection.png" + transform="matrix(-1.009207,0,0,-1.009207,1228.9929,755.4003)" + id="g10407"> + <path + id="path10411" + d="M 626.01011,726.3382 C 626.29619,729.62281 629.15054,732.05986 632.38299,731.77834 C 635.61544,731.49681 638.00719,728.60285 637.72111,725.31824 C 637.43504,722.03363 634.57901,719.59673 631.34656,719.87825 C 628.11411,720.15978 625.72404,723.05359 626.01011,726.3382 z M 630.15052,725.9776 C 630.0637,724.98075 630.76068,724.10527 631.70753,724.02281 C 632.65438,723.94034 633.49389,724.682 633.58071,725.67885 C 633.66753,726.67569 632.96887,727.55129 632.02202,727.63375 C 631.07517,727.71622 630.23734,726.97441 630.15052,725.9776 z " + style="fill:url(#linearGradient10544);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient10546);stroke-width:0.79270077;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + sodipodi:nodetypes="ccccc" + id="path10413" + d="M 631.64529,725.88285 C 631.5589,725.833 631.40423,725.80174 631.34275,725.71371 L 627.75279,729.45396 C 628.79763,730.57142 630.08327,731.36678 631.54763,731.44255 L 631.64529,725.88285 z " + style="fill:url(#linearGradient10548);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.4984282;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" /> + <path + sodipodi:nodetypes="ccccc" + id="path10415" + d="M 631.96728,725.74981 C 632.05347,725.79915 632.12902,725.86514 632.19032,725.9523 L 635.95786,722.1443 C 634.91577,721.03769 633.5297,720.31512 632.06826,720.24095 L 631.96728,725.74981 z " + style="fill:url(#linearGradient10550);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.49565426;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" /> + <path + style="fill:url(#linearGradient10552);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient10554);stroke-width:0.79270077;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 621.45331,718.3447 C 621.73939,721.62931 624.59374,724.06636 627.82619,723.78484 C 631.05864,723.50331 633.45039,720.60935 633.16431,717.32474 C 632.87824,714.04013 630.02221,711.60323 626.78976,711.88475 C 623.55731,712.16628 621.16724,715.06009 621.45331,718.3447 z M 625.59372,717.9841 C 625.5069,716.98725 626.20388,716.11177 627.15073,716.02931 C 628.09758,715.94684 628.93709,716.6885 629.02391,717.68535 C 629.11073,718.68219 628.41207,719.55779 627.46522,719.64025 C 626.51837,719.72272 625.68054,718.98091 625.59372,717.9841 z " + id="path10417" /> + <path + style="fill:url(#linearGradient10673);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.4984282;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + d="M 627.08849,717.88935 C 627.0021,717.8395 626.84743,717.80824 626.78595,717.72021 L 623.19599,721.46046 C 624.24083,722.57792 625.52647,723.37328 626.99083,723.44905 L 627.08849,717.88935 z " + id="path10419" + sodipodi:nodetypes="ccccc" /> + <path + style="fill:url(#linearGradient10558);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.49565426;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + d="M 627.41048,717.75631 C 627.49667,717.80565 627.57222,717.87164 627.63352,717.9588 L 631.40106,714.1508 C 630.35897,713.04419 628.9729,712.32162 627.51146,712.24745 L 627.41048,717.75631 z " + id="path10422" + sodipodi:nodetypes="ccccc" /> + <path + id="path10424" + d="M 616.76121,726.3382 C 617.04729,729.62281 619.90164,732.05986 623.13409,731.77834 C 626.36654,731.49681 628.75829,728.60285 628.47221,725.31824 C 628.18614,722.03363 625.33011,719.59673 622.09766,719.87825 C 618.86521,720.15978 616.47514,723.05359 616.76121,726.3382 z M 620.90162,725.9776 C 620.8148,724.98075 621.51178,724.10527 622.45863,724.02281 C 623.40548,723.94034 624.24499,724.682 624.33181,725.67885 C 624.41863,726.67569 623.71997,727.55129 622.77312,727.63375 C 621.82627,727.71622 620.98844,726.97441 620.90162,725.9776 z " + style="fill:url(#linearGradient10560);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient10562);stroke-width:0.99087697;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + sodipodi:nodetypes="ccccc" + id="path10426" + d="M 622.39639,725.88285 C 622.31,725.833 622.15533,725.80174 622.09385,725.71371 L 618.50389,729.45396 C 619.54873,730.57142 620.83437,731.36678 622.29873,731.44255 L 622.39639,725.88285 z " + style="fill:url(#linearGradient10564);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.4984282;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" /> + <path + sodipodi:nodetypes="ccccc" + id="path10428" + d="M 622.71838,725.74981 C 622.80457,725.79915 622.88012,725.86514 622.94142,725.9523 L 626.70896,722.1443 C 625.66687,721.03769 624.2808,720.31512 622.81936,720.24095 L 622.71838,725.74981 z " + style="fill:url(#linearGradient10566);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.49565426;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" /> + </g> + <g + inkscape:export-ydpi="90.000092" + inkscape:export-xdpi="90.000092" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/32x32/actions/collection.png" + transform="matrix(-0.976101,0,0,-0.976101,1207.8252,735.2612)" + id="g10431"> + <path + style="fill:url(#linearGradient10568);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient10570);stroke-width:1.02448475;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 662.76245,727.73012 C 663.19496,732.69605 667.5104,736.38057 672.39746,735.95494 C 677.28452,735.5293 680.90055,731.15399 680.46805,726.18806 C 680.03554,721.22213 675.71757,717.53784 670.8305,717.96347 C 665.94344,718.38911 662.32995,722.76419 662.76245,727.73012 z M 669.02224,727.18493 C 668.89098,725.67783 669.94473,724.35421 671.37624,724.22953 C 672.80776,724.10485 674.077,725.22615 674.20826,726.73326 C 674.33952,728.24036 673.28323,729.56416 671.85172,729.68883 C 670.4202,729.81351 669.1535,728.69199 669.02224,727.18493 z " + id="path10434" /> + <path + style="fill:url(#linearGradient10572);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.4984282;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + d="M 671.28216,727.04169 C 671.15153,726.96631 670.9177,726.91905 670.82474,726.78596 L 665.39716,732.44077 C 666.97683,734.13022 668.92056,735.33271 671.13449,735.44727 L 671.28216,727.04169 z " + id="path10436" + sodipodi:nodetypes="ccccc" /> + <path + style="fill:url(#linearGradient10574);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.49565426;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + d="M 671.76895,726.84054 C 671.89927,726.91514 672.01349,727.01491 672.10616,727.14668 L 677.80222,721.38945 C 676.2267,719.7164 674.13114,718.62396 671.92163,718.51182 L 671.76895,726.84054 z " + id="path10438" + sodipodi:nodetypes="ccccc" /> + <path + style="fill:url(#linearGradient10576);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient10578);stroke-width:1.02448475;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 648.7792,727.73012 C 649.21171,732.69605 653.52715,736.38057 658.41421,735.95494 C 663.30127,735.5293 666.91731,731.15399 666.4848,726.18806 C 666.05229,721.22213 661.73432,717.53784 656.84726,717.96347 C 651.96019,718.38911 648.3467,722.76419 648.7792,727.73012 z M 655.03899,727.18493 C 654.90774,725.67783 655.96148,724.35421 657.393,724.22953 C 658.82451,724.10485 660.09375,725.22615 660.22502,726.73326 C 660.35627,728.24036 659.29999,729.56416 657.86847,729.68883 C 656.43695,729.81351 655.17025,728.69199 655.03899,727.18493 z " + id="path10440" /> + <path + style="fill:url(#linearGradient10580);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + d="M 657.29891,727.04169 C 657.16829,726.96631 656.93445,726.91905 656.8415,726.78596 L 651.41392,732.44077 C 652.99358,734.13022 654.93732,735.33271 657.15125,735.44727 L 657.29891,727.04169 z " + id="path10442" + sodipodi:nodetypes="ccccc" /> + <path + style="fill:url(#linearGradient10582);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + d="M 657.7857,726.84054 C 657.91602,726.91514 658.03024,727.01491 658.12292,727.14668 L 663.81897,721.38945 C 662.24345,719.7164 660.1479,718.62396 657.93838,718.51182 L 657.7857,726.84054 z " + id="path10461" + sodipodi:nodetypes="ccccc" /> + <path + id="path10463" + d="M 655.87311,715.64493 C 656.30562,720.61086 660.62106,724.29538 665.50811,723.86974 C 670.39518,723.4441 674.01121,719.0688 673.57871,714.10287 C 673.1462,709.13694 668.82823,705.45264 663.94116,705.87828 C 659.0541,706.30391 655.4406,710.679 655.87311,715.64493 z M 662.13289,715.09974 C 662.00164,713.59263 663.05538,712.26901 664.4869,712.14434 C 665.91842,712.01966 667.18766,713.14096 667.31892,714.64806 C 667.45018,716.15517 666.39389,717.47896 664.96238,717.60364 C 663.53086,717.72832 662.26416,716.6068 662.13289,715.09974 z " + style="fill:url(#linearGradient10584);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient10586);stroke-width:1.02448475;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + sodipodi:nodetypes="ccccc" + id="path10465" + d="M 664.0279,715.27577 C 663.89729,715.2004 663.66344,715.15314 663.5705,715.02005 L 658.14291,720.67486 C 659.72258,722.36431 661.66631,723.5668 663.88025,723.68136 L 664.0279,715.27577 z " + style="fill:url(#linearGradient10589);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" /> + <path + sodipodi:nodetypes="ccccc" + id="path10469" + d="M 665.1989,714.39042 C 665.32922,714.46502 665.44343,714.56479 665.53612,714.69656 L 671.23216,708.93933 C 669.65665,707.26628 667.5611,706.17384 665.35157,706.06171 L 665.1989,714.39042 z " + style="fill:url(#linearGradient10591);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" /> + </g> + <g + inkscape:export-ydpi="90.000092" + inkscape:export-xdpi="90.000092" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/16x16/actions/collection.png" + id="g10471" + transform="matrix(-0.723593,0,0,-0.723593,1076.9585,546.2879)"> + <path + style="fill:url(#linearGradient10593);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient10595);stroke-width:1.10559309;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 626.01011,726.3382 C 626.29619,729.62281 629.15054,732.05986 632.38299,731.77834 C 635.61544,731.49681 638.00719,728.60285 637.72111,725.31824 C 637.43504,722.03363 634.57901,719.59673 631.34656,719.87825 C 628.11411,720.15978 625.72404,723.05359 626.01011,726.3382 z M 630.15052,725.9776 C 630.0637,724.98075 630.76068,724.10527 631.70753,724.02281 C 632.65438,723.94034 633.49389,724.682 633.58071,725.67885 C 633.66753,726.67569 632.96887,727.55129 632.02202,727.63375 C 631.07517,727.71622 630.23734,726.97441 630.15052,725.9776 z " + id="path10473" /> + <path + style="fill:url(#linearGradient10597);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.4984282;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + d="M 631.64529,725.88285 C 631.5589,725.833 631.40423,725.80174 631.34275,725.71371 L 627.75279,729.45396 C 628.79763,730.57142 630.08327,731.36678 631.54763,731.44255 L 631.64529,725.88285 z " + id="path10475" + sodipodi:nodetypes="ccccc" /> + <path + style="fill:url(#linearGradient10599);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.49565426;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + d="M 631.96728,725.74981 C 632.05347,725.79915 632.12902,725.86514 632.19032,725.9523 L 635.95786,722.1443 C 634.91577,721.03769 633.5297,720.31512 632.06826,720.24095 L 631.96728,725.74981 z " + id="path10477" + sodipodi:nodetypes="ccccc" /> + <path + id="path10479" + d="M 621.45331,718.3447 C 621.73939,721.62931 624.59374,724.06636 627.82619,723.78484 C 631.05864,723.50331 633.45039,720.60935 633.16431,717.32474 C 632.87824,714.04013 630.02221,711.60323 626.78976,711.88475 C 623.55731,712.16628 621.16724,715.06009 621.45331,718.3447 z M 625.59372,717.9841 C 625.5069,716.98725 626.20388,716.11177 627.15073,716.02931 C 628.09758,715.94684 628.93709,716.6885 629.02391,717.68535 C 629.11073,718.68219 628.41207,719.55779 627.46522,719.64025 C 626.51837,719.72272 625.68054,718.98091 625.59372,717.9841 z " + style="fill:url(#linearGradient10601);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient10603);stroke-width:1.38199234;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + sodipodi:nodetypes="ccccc" + id="path10481" + d="M 627.08849,717.88935 C 627.0021,717.8395 626.84743,717.80824 626.78595,717.72021 L 623.19599,721.46046 C 624.24083,722.57792 625.52647,723.37328 626.99083,723.44905 L 627.08849,717.88935 z " + style="fill:url(#linearGradient10605);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.4984282;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" /> + <path + sodipodi:nodetypes="ccccc" + id="path10483" + d="M 627.41048,717.75631 C 627.49667,717.80565 627.57222,717.87164 627.63352,717.9588 L 631.40106,714.1508 C 630.35897,713.04419 628.9729,712.32162 627.51146,712.24745 L 627.41048,717.75631 z " + style="fill:url(#linearGradient10607);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.49565426;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" /> + <path + style="fill:url(#linearGradient10609);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient10612);stroke-width:1.10559309;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 616.76121,726.3382 C 617.04729,729.62281 619.90164,732.05986 623.13409,731.77834 C 626.36654,731.49681 628.75829,728.60285 628.47221,725.31824 C 628.18614,722.03363 625.33011,719.59673 622.09766,719.87825 C 618.86521,720.15978 616.47514,723.05359 616.76121,726.3382 z M 620.90162,725.9776 C 620.8148,724.98075 621.51178,724.10527 622.45863,724.02281 C 623.40548,723.94034 624.24499,724.682 624.33181,725.67885 C 624.41863,726.67569 623.71997,727.55129 622.77312,727.63375 C 621.82627,727.71622 620.98844,726.97441 620.90162,725.9776 z " + id="path10485" /> + <path + style="fill:url(#linearGradient10614);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.4984282;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + d="M 622.39639,725.88285 C 622.31,725.833 622.15533,725.80174 622.09385,725.71371 L 618.50389,729.45396 C 619.54873,730.57142 620.83437,731.36678 622.29873,731.44255 L 622.39639,725.88285 z " + id="path10498" + sodipodi:nodetypes="ccccc" /> + <path + style="fill:url(#linearGradient10616);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.49565426;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + d="M 622.71838,725.74981 C 622.80457,725.79915 622.88012,725.86514 622.94142,725.9523 L 626.70896,722.1443 C 625.66687,721.03769 624.2808,720.31512 622.81936,720.24095 L 622.71838,725.74981 z " + id="path10500" + sodipodi:nodetypes="ccccc" /> + </g> + <g + inkscape:export-ydpi="90.000092" + inkscape:export-xdpi="90.000092" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/48x48/actions/collection.png" + id="g10502" + transform="matrix(-1.479886,0,0,-1.479886,1489.3973,1106.0411)"> + <path + id="path10504" + d="M 662.76245,727.73012 C 663.19496,732.69605 667.5104,736.38057 672.39746,735.95494 C 677.28452,735.5293 680.90055,731.15399 680.46805,726.18806 C 680.03554,721.22213 675.71757,717.53784 670.8305,717.96347 C 665.94344,718.38911 662.32995,722.76419 662.76245,727.73012 z M 669.02224,727.18493 C 668.89098,725.67783 669.94473,724.35421 671.37624,724.22953 C 672.80776,724.10485 674.077,725.22615 674.20826,726.73326 C 674.33952,728.24036 673.28323,729.56416 671.85172,729.68883 C 670.4202,729.81351 669.1535,728.69199 669.02224,727.18493 z " + style="fill:url(#linearGradient10618);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient10620);stroke-width:0.6757282;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + sodipodi:nodetypes="ccccc" + id="path10506" + d="M 671.28216,727.04169 C 671.15153,726.96631 670.9177,726.91905 670.82474,726.78596 L 665.39716,732.44077 C 666.97683,734.13022 668.92056,735.33271 671.13449,735.44727 L 671.28216,727.04169 z " + style="fill:url(#linearGradient10622);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.4984282;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" /> + <path + sodipodi:nodetypes="ccccc" + id="path10508" + d="M 671.76895,726.84054 C 671.89927,726.91514 672.01349,727.01491 672.10616,727.14668 L 677.80222,721.38945 C 676.2267,719.7164 674.13114,718.62396 671.92163,718.51182 L 671.76895,726.84054 z " + style="fill:url(#linearGradient10624);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.49565426;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" /> + <path + id="path10510" + d="M 648.7792,727.73012 C 649.21171,732.69605 653.52715,736.38057 658.41421,735.95494 C 663.30127,735.5293 666.91731,731.15399 666.4848,726.18806 C 666.05229,721.22213 661.73432,717.53784 656.84726,717.96347 C 651.96019,718.38911 648.3467,722.76419 648.7792,727.73012 z M 655.03899,727.18493 C 654.90774,725.67783 655.96148,724.35421 657.393,724.22953 C 658.82451,724.10485 660.09375,725.22615 660.22502,726.73326 C 660.35627,728.24036 659.29999,729.56416 657.86847,729.68883 C 656.43695,729.81351 655.17025,728.69199 655.03899,727.18493 z " + style="fill:url(#linearGradient10626);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient10628);stroke-width:0.6757282;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + sodipodi:nodetypes="ccccc" + id="path10512" + d="M 657.29891,727.04169 C 657.16829,726.96631 656.93445,726.91905 656.8415,726.78596 L 651.41392,732.44077 C 652.99358,734.13022 654.93732,735.33271 657.15125,735.44727 L 657.29891,727.04169 z " + style="fill:url(#linearGradient10630);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" /> + <path + sodipodi:nodetypes="ccccc" + id="path10515" + d="M 657.7857,726.84054 C 657.91602,726.91514 658.03024,727.01491 658.12292,727.14668 L 663.81897,721.38945 C 662.24345,719.7164 660.1479,718.62396 657.93838,718.51182 L 657.7857,726.84054 z " + style="fill:url(#linearGradient10632);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" /> + <path + style="fill:url(#linearGradient10634);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient10636);stroke-width:0.6757282;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 655.87311,715.64493 C 656.30562,720.61086 660.62106,724.29538 665.50811,723.86974 C 670.39518,723.4441 674.01121,719.0688 673.57871,714.10287 C 673.1462,709.13694 668.82823,705.45264 663.94116,705.87828 C 659.0541,706.30391 655.4406,710.679 655.87311,715.64493 z M 662.13289,715.09974 C 662.00164,713.59263 663.05538,712.26901 664.4869,712.14434 C 665.91842,712.01966 667.18766,713.14096 667.31892,714.64806 C 667.45018,716.15517 666.39389,717.47896 664.96238,717.60364 C 663.53086,717.72832 662.26416,716.6068 662.13289,715.09974 z " + id="path10517" /> + <path + style="fill:url(#linearGradient10638);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + d="M 664.0279,715.27577 C 663.89729,715.2004 663.66344,715.15314 663.5705,715.02005 L 658.14291,720.67486 C 659.72258,722.36431 661.66631,723.5668 663.88025,723.68136 L 664.0279,715.27577 z " + id="path10519" + sodipodi:nodetypes="ccccc" /> + <path + style="fill:url(#linearGradient10640);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + d="M 665.1989,714.39042 C 665.32922,714.46502 665.44343,714.56479 665.53612,714.69656 L 671.23216,708.93933 C 669.65665,707.26628 667.5611,706.17384 665.35157,706.06171 L 665.1989,714.39042 z " + id="path10521" + sodipodi:nodetypes="ccccc" /> + </g> + <g + inkscape:export-ydpi="90.000092" + inkscape:export-xdpi="90.000092" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/64x64/actions/collection.png" + transform="matrix(-1.983684,0,0,-1.983684,1736.0637,1472.8306)" + id="g10523"> + <path + style="fill:url(#linearGradient10643);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient10645);stroke-width:0.50411302;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 662.76245,727.73012 C 663.19496,732.69605 667.5104,736.38057 672.39746,735.95494 C 677.28452,735.5293 680.90055,731.15399 680.46805,726.18806 C 680.03554,721.22213 675.71757,717.53784 670.8305,717.96347 C 665.94344,718.38911 662.32995,722.76419 662.76245,727.73012 z M 669.02224,727.18493 C 668.89098,725.67783 669.94473,724.35421 671.37624,724.22953 C 672.80776,724.10485 674.077,725.22615 674.20826,726.73326 C 674.33952,728.24036 673.28323,729.56416 671.85172,729.68883 C 670.4202,729.81351 669.1535,728.69199 669.02224,727.18493 z " + id="path10525" /> + <path + style="fill:url(#linearGradient10648);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.4984282;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + d="M 671.28216,727.04169 C 671.15153,726.96631 670.9177,726.91905 670.82474,726.78596 L 665.39716,732.44077 C 666.97683,734.13022 668.92056,735.33271 671.13449,735.44727 L 671.28216,727.04169 z " + id="path10527" + sodipodi:nodetypes="ccccc" /> + <path + style="fill:url(#linearGradient10650);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.49565426;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + d="M 671.76895,726.84054 C 671.89927,726.91514 672.01349,727.01491 672.10616,727.14668 L 677.80222,721.38945 C 676.2267,719.7164 674.13114,718.62396 671.92163,718.51182 L 671.76895,726.84054 z " + id="path10529" + sodipodi:nodetypes="ccccc" /> + <path + style="fill:url(#linearGradient10653);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient10656);stroke-width:0.50411302;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 648.7792,727.73012 C 649.21171,732.69605 653.52715,736.38057 658.41421,735.95494 C 663.30127,735.5293 666.91731,731.15399 666.4848,726.18806 C 666.05229,721.22213 661.73432,717.53784 656.84726,717.96347 C 651.96019,718.38911 648.3467,722.76419 648.7792,727.73012 z M 655.03899,727.18493 C 654.90774,725.67783 655.96148,724.35421 657.393,724.22953 C 658.82451,724.10485 660.09375,725.22615 660.22502,726.73326 C 660.35627,728.24036 659.29999,729.56416 657.86847,729.68883 C 656.43695,729.81351 655.17025,728.69199 655.03899,727.18493 z " + id="path10531" /> + <path + style="fill:url(#linearGradient10658);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + d="M 657.29891,727.04169 C 657.16829,726.96631 656.93445,726.91905 656.8415,726.78596 L 651.41392,732.44077 C 652.99358,734.13022 654.93732,735.33271 657.15125,735.44727 L 657.29891,727.04169 z " + id="path10533" + sodipodi:nodetypes="ccccc" /> + <path + style="fill:url(#linearGradient10661);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + d="M 657.7857,726.84054 C 657.91602,726.91514 658.03024,727.01491 658.12292,727.14668 L 663.81897,721.38945 C 662.24345,719.7164 660.1479,718.62396 657.93838,718.51182 L 657.7857,726.84054 z " + id="path10535" + sodipodi:nodetypes="ccccc" /> + <path + id="path10538" + d="M 655.87311,715.64493 C 656.30562,720.61086 660.62106,724.29538 665.50811,723.86974 C 670.39518,723.4441 674.01121,719.0688 673.57871,714.10287 C 673.1462,709.13694 668.82823,705.45264 663.94116,705.87828 C 659.0541,706.30391 655.4406,710.679 655.87311,715.64493 z M 662.13289,715.09974 C 662.00164,713.59263 663.05538,712.26901 664.4869,712.14434 C 665.91842,712.01966 667.18766,713.14096 667.31892,714.64806 C 667.45018,716.15517 666.39389,717.47896 664.96238,717.60364 C 663.53086,717.72832 662.26416,716.6068 662.13289,715.09974 z " + style="fill:url(#linearGradient10664);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient10667);stroke-width:0.50411302;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + sodipodi:nodetypes="ccccc" + id="path10540" + d="M 664.0279,715.27577 C 663.89729,715.2004 663.66344,715.15314 663.5705,715.02005 L 658.14291,720.67486 C 659.72258,722.36431 661.66631,723.5668 663.88025,723.68136 L 664.0279,715.27577 z " + style="fill:url(#linearGradient10669);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" /> + <path + sodipodi:nodetypes="ccccc" + id="path10542" + d="M 665.1989,714.39042 C 665.32922,714.46502 665.44343,714.56479 665.53612,714.69656 L 671.23216,708.93933 C 669.65665,707.26628 667.5611,706.17384 665.35157,706.06171 L 665.1989,714.39042 z " + style="fill:url(#linearGradient10671);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" /> + </g> + <g + id="g12341" + transform="translate(-5,-5.8608646)"> + <rect + style="fill:#b9cff9;fill-opacity:0;stroke:none;stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + id="rect10841" + width="22" + height="22" + x="118.35842" + y="422.22308" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/crystalsvg/22x22/actions/amarok_podcast.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/crystalsvg/22x22/actions/amarok_podcast.png" + transform="matrix(0.998349,0,0,0.998349,-92.668419,433.85703)" + id="g10677"> + <path + id="path10679" + d="M 214.55635,-8.9876506 C 213.0012,-8.9876506 211.75143,-8.4272616 211.75143,-6.7915466 L 211.75143,6.4368384 C 211.8402,7.0266884 212.4909,7.6066964 212.88141,7.7177054 L 231.75761,7.7177054 C 232.15119,7.6058244 232.95062,7.0333644 233.03661,6.4368384 L 233.03661,-6.7915466 C 233.03661,-8.4272616 231.78682,-8.9876506 230.23167,-8.9876506 L 214.55635,-8.9876506 z " + style="fill:url(#linearGradient10849);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient10851);stroke-width:0.75123984;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:nodetypes="ccccccccc" /> + <path + style="fill:url(#linearGradient10853);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient10855);stroke-width:0.98547882;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 213.8908,-4.6646566 L 230.92445,-4.6646566 C 231.46369,-4.6646566 231.89781,-4.2391996 231.89781,-3.7107166 L 231.89781,5.9163164 C 231.89781,6.4447994 231.46369,6.8702564 230.92445,6.8702564 L 213.8908,6.8702564 C 213.35155,6.8702564 212.91744,6.4447994 212.91744,5.9163164 L 212.91744,-3.7107166 C 212.91744,-4.2391996 213.35155,-4.6646566 213.8908,-4.6646566 z " + id="path10681" /> + <path + id="path10683" + d="M 213.95559,-4.6803566 L 230.84519,-4.6803566 C 231.37985,-4.6803566 231.81032,-4.2535196 231.81032,-3.7233216 L 231.81032,5.9349364 C 231.81032,6.4651324 231.37985,6.8919714 230.84519,6.8919714 L 213.95559,6.8919714 C 213.42098,6.8919714 212.9905,6.4651324 212.9905,5.9349364 L 212.9905,-3.7233216 C 212.9905,-4.2535196 213.42098,-4.6803566 213.95559,-4.6803566 z " + style="opacity:0.99578091;fill:url(#radialGradient10857);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.57956129;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" /> + <path + sodipodi:nodetypes="ccccccccc" + style="opacity:0.15189873;fill:url(#linearGradient10859);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.71897209;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 214.7548,-8.7326426 C 213.22744,-8.7326426 212,-8.1994276 212,-6.6430286 L 212,-6.4102356 C 212.08716,-5.8489876 212.72625,-5.2971026 213.10978,-5.1914766 L 231.64865,-5.1914766 C 232.03518,-5.2979336 232.82035,-5.8426356 232.90479,-6.4102356 L 232.90479,-6.6430286 C 232.90479,-8.1994276 231.67735,-8.7326426 230.14998,-8.7326426 L 214.7548,-8.7326426 z " + id="path10685" /> + </g> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/crystalsvg/22x22/actions/amarok_podcast.png" + id="g10687" + transform="matrix(0.998349,0,0,0.998349,-90.864479,227.94765)"> + <g + id="g10689"> + <path + id="path10691" + d="M 216.59388,201.58154 L 216.59388,213.16264" + style="opacity:0.39183673;fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#264a9e;stroke-width:0.29001534;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:nodetypes="cc" /> + <path + style="opacity:0.39183673;fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#264a9e;stroke-width:0.29001534;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 223.52993,201.58154 L 223.52993,213.17716" + id="path10693" + sodipodi:nodetypes="cc" /> + </g> + <rect + style="opacity:0.56734691;fill:#6785ed;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.28953668;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect10695" + width="18.826923" + height="3.4617176" + x="211.13826" + y="205.69659" /> + </g> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/crystalsvg/22x22/actions/amarok_podcast.png" + transform="matrix(0.9043475,0,0,0.9043475,-53.418237,384.88333)" + d="M 201.4375,47.15625 C 197.41705,47.52053 194.37229,51.014726 194.75,54.96875 C 194.92784,56.830672 195.82274,58.465378 197.15625,59.625 C 197.65641,60.091804 198.43734,60.091804 198.9375,59.625 L 199.875,58.71875 C 200.15019,58.464713 200.28125,58.104935 200.28125,57.75 C 200.28127,57.395009 200.15018,57.035274 199.875,56.78125 C 199.86458,56.781024 199.85417,56.781024 199.84375,56.78125 C 199.22065,56.235933 198.80142,55.459244 198.71875,54.59375 C 198.5432,52.756008 199.87407,51.175633 201.8125,51 C 203.74115,50.825235 205.38771,52.17032 205.5625,54 C 205.66911,55.116077 205.22291,56.089566 204.40625,56.78125 C 204.11053,57.015883 203.9502,57.381534 203.9375,57.75 C 203.92478,58.118451 204.03823,58.502493 204.34375,58.78125 L 205.21875,59.625 C 205.22917,59.625226 205.23958,59.625226 205.25,59.625 C 205.74062,60.051502 206.47813,60.051502 206.96875,59.625 C 208.64796,58.176095 209.69557,55.999456 209.46875,53.625 C 209.10312,49.797516 205.67634,46.996067 201.8125,47.15625 C 201.96122,47.150088 201.83088,47.15954 201.75,47.15625 C 201.66912,47.15296 201.56975,47.144264 201.4375,47.15625 z " + id="path10701" + style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.2;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + xlink:href="#path5359" + inkscape:original="M 201.5 47.875 C 197.85718 48.205064 195.12959 51.35581 195.46875 54.90625 C 195.62893 56.583244 196.42448 58.049773 197.625 59.09375 C 197.85376 59.307253 198.20874 59.307253 198.4375 59.09375 L 199.375 58.1875 C 199.4971 58.074787 199.5625 57.916174 199.5625 57.75 C 199.56251 57.583826 199.4971 57.425213 199.375 57.3125 C 198.62088 56.652518 198.10105 55.714144 198 54.65625 C 197.78709 52.427426 199.44435 50.490156 201.75 50.28125 C 204.05353 50.072515 206.06871 51.712605 206.28125 53.9375 C 206.41033 55.288748 205.85565 56.51317 204.875 57.34375 C 204.73828 57.452227 204.66226 57.606825 204.65625 57.78125 C 204.65023 57.955674 204.71482 58.132362 204.84375 58.25 L 205.71875 59.09375 C 205.94276 59.288486 206.27599 59.288486 206.5 59.09375 C 208.01963 57.782537 208.95296 55.812212 208.75 53.6875 C 208.42165 50.250246 205.34588 47.729812 201.84375 47.875 C 201.8096 47.876415 201.69162 47.857633 201.5 47.875 z " + inkscape:radius="0.71942902" + sodipodi:type="inkscape:offset" + inkscape:href="#path5359" /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/crystalsvg/22x22/actions/amarok_podcast.png" + transform="matrix(0.9043475,0,0,0.9043475,-53.433037,384.87343)" + sodipodi:type="inkscape:offset" + inkscape:radius="0.59534109" + inkscape:original="M 201.5625 48.46875 C 198.22936 48.770755 195.75529 51.627732 196.0625 54.84375 C 196.20781 56.365042 196.9411 57.708249 198.03125 58.65625 L 198.96875 57.75 C 198.10643 56.995329 197.52218 55.93237 197.40625 54.71875 C 197.16225 52.164404 199.07203 49.924478 201.6875 49.6875 C 204.30298 49.450497 206.63099 51.320657 206.875 53.875 C 207.02314 55.425824 206.36999 56.863908 205.25 57.8125 L 206.125 58.65625 C 207.51113 57.460224 208.33975 55.670994 208.15625 53.75 C 207.85863 50.634479 205.07888 48.335927 201.875 48.46875 C 201.77164 48.473032 201.66666 48.45931 201.5625 48.46875 z " + style="fill:url(#linearGradient12081);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient12077);stroke-width:0.421298;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path10703" + d="M 201.5,47.875 C 197.85718,48.205064 195.12959,51.35581 195.46875,54.90625 C 195.62893,56.583244 196.42448,58.049773 197.625,59.09375 C 197.85376,59.307253 198.20874,59.307253 198.4375,59.09375 L 199.375,58.1875 C 199.4971,58.074787 199.56655,57.916174 199.56655,57.75 C 199.56655,57.583826 199.4971,57.425213 199.375,57.3125 C 198.62088,56.652518 198.10105,55.714144 198,54.65625 C 197.78709,52.427426 199.44435,50.490156 201.75,50.28125 C 204.05353,50.072515 206.06871,51.712605 206.28125,53.9375 C 206.41033,55.288748 205.85565,56.51317 204.875,57.34375 C 204.73828,57.452227 204.65604,57.615231 204.65002,57.789656 C 204.64401,57.964081 204.71482,58.132362 204.84375,58.25 L 205.71875,59.09375 C 205.94276,59.288486 206.27599,59.288486 206.5,59.09375 C 208.01963,57.782537 208.95296,55.812212 208.75,53.6875 C 208.42165,50.250246 205.34588,47.729812 201.84375,47.875 C 201.8096,47.876415 201.69162,47.857633 201.5,47.875 z " /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/crystalsvg/22x22/actions/amarok_podcast.png" + style="fill:url(#linearGradient12070);fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.22314973;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.64321606" + d="M 129.35853,432.13283 C 128.50344,432.13283 127.80943,432.83033 127.80943,433.68953 C 127.80943,434.16233 128.02356,434.57983 128.35492,434.86543 L 128.92514,435.12923 L 128.75123,440.68343 L 130.27337,440.68343 L 129.90482,435.15253 L 130.40464,434.82983 C 130.71159,434.54533 130.90761,434.14203 130.90761,433.68953 C 130.90761,432.83033 130.21359,432.13283 129.35853,432.13283 z " + id="path10705" + sodipodi:nodetypes="csccccccsc" /> + </g> + <g + id="g12357" + transform="translate(-5,-3.9922123)"> + <rect + y="410.35443" + x="157.73907" + height="32" + width="32" + id="rect10843" + style="fill:#b9cff9;fill-opacity:0;stroke:none;stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/32x32/actions/amarok_podcast2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Playlist Types/amarok_podcast2.png" + transform="matrix(0.999518,0,0,0.999518,-85.128369,430.97163)" + id="g10709"> + <path + id="path10712" + d="M 247.4797,-16.724353 C 245.19536,-16.724353 243.35958,-15.912221 243.35958,-13.541697 L 243.35958,5.629258 C 243.48998,6.484086 244.44578,7.32465 245.01941,7.485528 L 272.7463,7.485528 C 273.32442,7.323387 274.49871,6.49376 274.625,5.629258 L 274.625,-13.541697 C 274.625,-15.912221 272.78922,-16.724353 270.50489,-16.724353 L 247.4797,-16.724353 z " + style="fill:url(#linearGradient10867);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient10869);stroke-width:0.75036138;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:nodetypes="ccccccccc" /> + <path + style="fill:url(#linearGradient10871);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient10873);stroke-width:0.926992;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 245.7032,-11.106549 L 272.28145,-11.106549 C 273.12286,-11.106549 273.80023,-10.446932 273.80023,-9.627587 L 273.80023,5.2979 C 273.80023,6.117245 273.12286,6.776862 272.28145,6.776862 L 245.7032,6.776862 C 244.86179,6.776862 244.18442,6.117245 244.18442,5.2979 L 244.18442,-9.627587 C 244.18442,-10.446932 244.86179,-11.106549 245.7032,-11.106549 z " + id="path10714" /> + <path + id="path10716" + d="M 245.66479,-10.727742 L 272.29777,-10.727742 C 273.14089,-10.727742 273.81967,-10.085626 273.81967,-9.288015 L 273.81967,5.241504 C 273.81967,6.039111 273.14089,6.681231 272.29777,6.681231 L 245.66479,6.681231 C 244.82175,6.681231 244.14295,6.039111 244.14295,5.241504 L 244.14295,-9.288015 C 244.14295,-10.085626 244.82175,-10.727742 245.66479,-10.727742 z " + style="opacity:0.99578091;fill:url(#radialGradient10875);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.57956129;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" /> + <path + sodipodi:nodetypes="ccccccccc" + style="opacity:0.15189873;fill:url(#linearGradient10877);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.71897209;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 247.74611,-16.675395 C 245.51463,-16.675395 243.72133,-15.841241 243.72133,-13.406434 L 243.72133,-13.042257 C 243.84869,-12.164248 244.78239,-11.300888 245.34273,-11.135649 L 272.42802,-11.135649 C 272.99275,-11.302188 274.13988,-12.154312 274.26325,-13.042257 L 274.26325,-13.406434 C 274.26325,-15.841241 272.46995,-16.675395 270.23846,-16.675395 L 247.74611,-16.675395 z " + id="path10718" /> + </g> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Playlist Types/amarok_podcast2.png" + id="g10720" + transform="matrix(1.5835114,0,0,1.5238841,-175.47106,112.86643)"> + <g + id="g10722"> + <path + sodipodi:nodetypes="cc" + style="opacity:0.39183673;fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#264a9e;stroke-width:0.28967622;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 216.59388,201.58154 L 216.59388,213.16264" + id="path10724" /> + <path + sodipodi:nodetypes="cc" + id="path10726" + d="M 223.52993,201.58154 L 223.52993,213.17716" + style="opacity:0.39183673;fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#264a9e;stroke-width:0.28967622;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + <rect + y="205.69659" + x="211.13826" + height="3.4617176" + width="18.826923" + id="rect10728" + style="opacity:0.56734691;fill:#6785ed;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.28953668;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + <path + sodipodi:type="inkscape:offset" + inkscape:radius="0.71942902" + inkscape:original="M 201.5 47.875 C 197.85718 48.205064 195.12959 51.35581 195.46875 54.90625 C 195.62893 56.583244 196.42448 58.049773 197.625 59.09375 C 197.85376 59.307253 198.20874 59.307253 198.4375 59.09375 L 199.375 58.1875 C 199.4971 58.074787 199.5625 57.916174 199.5625 57.75 C 199.56251 57.583826 199.4971 57.425213 199.375 57.3125 C 198.62088 56.652518 198.10105 55.714144 198 54.65625 C 197.78709 52.427426 199.44435 50.490156 201.75 50.28125 C 204.05353 50.072515 206.06871 51.712605 206.28125 53.9375 C 206.41033 55.288748 205.85565 56.51317 204.875 57.34375 C 204.73828 57.452227 204.66226 57.606825 204.65625 57.78125 C 204.65023 57.955674 204.71482 58.132362 204.84375 58.25 L 205.71875 59.09375 C 205.94276 59.288486 206.27599 59.288486 206.5 59.09375 C 208.01963 57.782537 208.95296 55.812212 208.75 53.6875 C 208.42165 50.250246 205.34588 47.729812 201.84375 47.875 C 201.8096 47.876415 201.69162 47.857633 201.5 47.875 z " + xlink:href="#path5359" + style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.2;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path10734" + d="M 201.4375,47.15625 C 197.41705,47.52053 194.37229,51.014726 194.75,54.96875 C 194.92784,56.830672 195.82274,58.465378 197.15625,59.625 C 197.65641,60.091804 198.43734,60.091804 198.9375,59.625 L 199.875,58.71875 C 200.15019,58.464713 200.28125,58.104935 200.28125,57.75 C 200.28127,57.395009 200.15018,57.035274 199.875,56.78125 C 199.86458,56.781024 199.85417,56.781024 199.84375,56.78125 C 199.22065,56.235933 198.80142,55.459244 198.71875,54.59375 C 198.5432,52.756008 199.87407,51.175633 201.8125,51 C 203.74115,50.825235 205.38771,52.17032 205.5625,54 C 205.66911,55.116077 205.22291,56.089566 204.40625,56.78125 C 204.11053,57.015883 203.9502,57.381534 203.9375,57.75 C 203.92478,58.118451 204.03823,58.502493 204.34375,58.78125 L 205.21875,59.625 C 205.22917,59.625226 205.23958,59.625226 205.25,59.625 C 205.74062,60.051502 206.47813,60.051502 206.96875,59.625 C 208.64796,58.176095 209.69557,55.999456 209.46875,53.625 C 209.10312,49.797516 205.67634,46.996067 201.8125,47.15625 C 201.96122,47.150088 201.83088,47.15954 201.75,47.15625 C 201.66912,47.15296 201.56975,47.144264 201.4375,47.15625 z " + transform="matrix(1.3170946,0,0,1.3170946,-92.457479,356.58823)" + inkscape:href="#path5359" /> + <path + d="M 201.5,47.875 C 197.85718,48.205064 195.12959,51.35581 195.46875,54.90625 C 195.62893,56.583244 196.42448,58.049773 197.625,59.09375 C 197.85376,59.307253 198.20874,59.307253 198.4375,59.09375 L 199.375,58.1875 C 199.4971,58.074787 199.56655,57.916174 199.56655,57.75 C 199.56655,57.583826 199.4971,57.425213 199.375,57.3125 C 198.62088,56.652518 198.10105,55.714144 198,54.65625 C 197.78709,52.427426 199.44435,50.490156 201.75,50.28125 C 204.05353,50.072515 206.06871,51.712605 206.28125,53.9375 C 206.41033,55.288748 205.85565,56.51317 204.875,57.34375 C 204.73828,57.452227 204.65604,57.615231 204.65002,57.789656 C 204.64401,57.964081 204.71482,58.132362 204.84375,58.25 L 205.71875,59.09375 C 205.94276,59.288486 206.27599,59.288486 206.5,59.09375 C 208.01963,57.782537 208.95296,55.812212 208.75,53.6875 C 208.42165,50.250246 205.34588,47.729812 201.84375,47.875 C 201.8096,47.876415 201.69162,47.857633 201.5,47.875 z " + id="path10736" + style="fill:url(#linearGradient12046);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient12042);stroke-width:0.20009638;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + inkscape:original="M 201.5625 48.46875 C 198.22936 48.770755 195.75529 51.627732 196.0625 54.84375 C 196.20781 56.365042 196.9411 57.708249 198.03125 58.65625 L 198.96875 57.75 C 198.10643 56.995329 197.52218 55.93237 197.40625 54.71875 C 197.16225 52.164404 199.07203 49.924478 201.6875 49.6875 C 204.30298 49.450497 206.63099 51.320657 206.875 53.875 C 207.02314 55.425824 206.36999 56.863908 205.25 57.8125 L 206.125 58.65625 C 207.51113 57.460224 208.33975 55.670994 208.15625 53.75 C 207.85863 50.634479 205.07888 48.335927 201.875 48.46875 C 201.77164 48.473032 201.66666 48.45931 201.5625 48.46875 z " + inkscape:radius="0.59534109" + sodipodi:type="inkscape:offset" + transform="matrix(1.3170946,0,0,1.3170946,-92.479039,356.57383)" /> + <path + sodipodi:nodetypes="csccccccsc" + id="path10738" + d="M 173.73921,425.40263 C 172.49386,425.40263 171.48309,426.41833 171.48309,427.66983 C 171.48309,428.35833 171.79496,428.96643 172.27754,429.38233 L 173.10802,429.76653 L 172.85474,437.85563 L 175.07159,437.85563 L 174.53483,429.80053 L 175.26276,429.33053 C 175.70981,428.91613 175.9953,428.32873 175.9953,427.66983 C 175.9953,426.41833 174.98453,425.40263 173.73921,425.40263 z " + style="fill:url(#linearGradient12035);fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.32461599;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.64321606" + inkscape:export-filename="/home/vadim/collection.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + </g> + <g + id="g12325" + transform="translate(-5,-5.9884281)"> + <rect + y="428.35065" + x="86.130119" + height="16" + width="16" + id="rect10839" + style="fill:#b9cff9;fill-opacity:0;stroke:none;stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/crystalsvg/16x16/actions/podcast.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/crystalsvg/16x16/actions/podcast.png" + id="g10742" + transform="matrix(0.716482,0,0,0.716482,-65.211199,436.80563)"> + <path + sodipodi:nodetypes="ccccccccc" + style="fill:url(#linearGradient10885);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient10887);stroke-width:1.04678178;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 214.55635,-8.9876506 C 213.0012,-8.9876506 211.75143,-8.4272616 211.75143,-6.7915466 L 211.75143,6.4368384 C 211.8402,7.0266884 212.4909,7.6066964 212.88141,7.7177054 L 231.75761,7.7177054 C 232.15119,7.6058244 232.95062,7.0333644 233.03661,6.4368384 L 233.03661,-6.7915466 C 233.03661,-8.4272616 231.78682,-8.9876506 230.23167,-8.9876506 L 214.55635,-8.9876506 z " + id="path10745" /> + <path + id="path10747" + d="M 213.8908,-4.6646566 L 230.92445,-4.6646566 C 231.46369,-4.6646566 231.89781,-4.2391996 231.89781,-3.7107166 L 231.89781,5.9163164 C 231.89781,6.4447994 231.46369,6.8702564 230.92445,6.8702564 L 213.8908,6.8702564 C 213.35155,6.8702564 212.91744,6.4447994 212.91744,5.9163164 L 212.91744,-3.7107166 C 212.91744,-4.2391996 213.35155,-4.6646566 213.8908,-4.6646566 z " + style="fill:url(#linearGradient10889);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient10891);stroke-width:1.37317157;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + style="opacity:0.99578091;fill:url(#radialGradient10893);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.57956129;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 213.95559,-4.6803566 L 230.84519,-4.6803566 C 231.37985,-4.6803566 231.81032,-4.2535196 231.81032,-3.7233216 L 231.81032,5.9349364 C 231.81032,6.4651324 231.37985,6.8919714 230.84519,6.8919714 L 213.95559,6.8919714 C 213.42098,6.8919714 212.9905,6.4651324 212.9905,5.9349364 L 212.9905,-3.7233216 C 212.9905,-4.2535196 213.42098,-4.6803566 213.95559,-4.6803566 z " + id="path10749" /> + <path + id="path10751" + d="M 214.7548,-8.7326426 C 213.22744,-8.7326426 212,-8.1994276 212,-6.6430286 L 212,-6.4102356 C 212.08716,-5.8489876 212.72625,-5.2971026 213.10978,-5.1914766 L 231.64865,-5.1914766 C 232.03518,-5.2979336 232.82035,-5.8426356 232.90479,-6.4102356 L 232.90479,-6.6430286 C 232.90479,-8.1994276 231.67735,-8.7326426 230.14998,-8.7326426 L 214.7548,-8.7326426 z " + style="opacity:0.15189873;fill:url(#linearGradient10895);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.71897209;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:nodetypes="ccccccccc" /> + </g> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/crystalsvg/16x16/actions/podcast.png" + transform="matrix(0.716482,0,0,0.716482,-63.916569,289.0313)" + id="g10753"> + <g + id="g10755"> + <path + sodipodi:nodetypes="cc" + style="opacity:0.39183673;fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#264a9e;stroke-width:0.404109;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 216.59388,201.58154 L 216.59388,213.16264" + id="path10757" /> + <path + sodipodi:nodetypes="cc" + id="path10759" + d="M 223.52993,201.58154 L 223.52993,213.17716" + style="opacity:0.39183673;fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#264a9e;stroke-width:0.404109;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + <rect + y="205.69659" + x="211.13826" + height="3.4617176" + width="18.826923" + id="rect10761" + style="opacity:0.56734691;fill:#6785ed;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.28953668;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/crystalsvg/16x16/actions/podcast.png" + sodipodi:type="inkscape:offset" + inkscape:radius="0.71942902" + inkscape:original="M 201.5 47.875 C 197.85718 48.205064 195.12959 51.35581 195.46875 54.90625 C 195.62893 56.583244 196.42448 58.049773 197.625 59.09375 C 197.85376 59.307253 198.20874 59.307253 198.4375 59.09375 L 199.375 58.1875 C 199.4971 58.074787 199.5625 57.916174 199.5625 57.75 C 199.56251 57.583826 199.4971 57.425213 199.375 57.3125 C 198.62088 56.652518 198.10105 55.714144 198 54.65625 C 197.78709 52.427426 199.44435 50.490156 201.75 50.28125 C 204.05353 50.072515 206.06871 51.712605 206.28125 53.9375 C 206.41033 55.288748 205.85565 56.51317 204.875 57.34375 C 204.73828 57.452227 204.66226 57.606825 204.65625 57.78125 C 204.65023 57.955674 204.71482 58.132362 204.84375 58.25 L 205.71875 59.09375 C 205.94276 59.288486 206.27599 59.288486 206.5 59.09375 C 208.01963 57.782537 208.95296 55.812212 208.75 53.6875 C 208.42165 50.250246 205.34588 47.729812 201.84375 47.875 C 201.8096 47.876415 201.69162 47.857633 201.5 47.875 z " + xlink:href="#path5359" + style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.2;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path10767" + d="M 201.4375,47.15625 C 197.41705,47.52053 194.37229,51.014726 194.75,54.96875 C 194.92784,56.830672 195.82274,58.465378 197.15625,59.625 C 197.65641,60.091804 198.43734,60.091804 198.9375,59.625 L 199.875,58.71875 C 200.15019,58.464713 200.28125,58.104935 200.28125,57.75 C 200.28127,57.395009 200.15018,57.035274 199.875,56.78125 C 199.86458,56.781024 199.85417,56.781024 199.84375,56.78125 C 199.22065,56.235933 198.80142,55.459244 198.71875,54.59375 C 198.5432,52.756008 199.87407,51.175633 201.8125,51 C 203.74115,50.825235 205.38771,52.17032 205.5625,54 C 205.66911,55.116077 205.22291,56.089566 204.40625,56.78125 C 204.11053,57.015883 203.9502,57.381534 203.9375,57.75 C 203.92478,58.118451 204.03823,58.502493 204.34375,58.78125 L 205.21875,59.625 C 205.22917,59.625226 205.23958,59.625226 205.25,59.625 C 205.74062,60.051502 206.47813,60.051502 206.96875,59.625 C 208.64796,58.176095 209.69557,55.999456 209.46875,53.625 C 209.10312,49.797516 205.67634,46.996067 201.8125,47.15625 C 201.96122,47.150088 201.83088,47.15954 201.75,47.15625 C 201.66912,47.15296 201.56975,47.144264 201.4375,47.15625 z " + transform="matrix(0.6490202,0,0,0.6490202,-37.042637,401.65883)" + inkscape:href="#path5359" /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/crystalsvg/16x16/actions/podcast.png" + d="M 201.5,47.875 C 197.85718,48.205064 195.12959,51.35581 195.46875,54.90625 C 195.62893,56.583244 196.42448,58.049773 197.625,59.09375 C 197.85376,59.307253 198.20874,59.307253 198.4375,59.09375 L 199.375,58.1875 C 199.4971,58.074787 199.56655,57.916174 199.56655,57.75 C 199.56655,57.583826 199.4971,57.425213 199.375,57.3125 C 198.62088,56.652518 198.10105,55.714144 198,54.65625 C 197.78709,52.427426 199.44435,50.490156 201.75,50.28125 C 204.05353,50.072515 206.06871,51.712605 206.28125,53.9375 C 206.41033,55.288748 205.85565,56.51317 204.875,57.34375 C 204.73828,57.452227 204.65604,57.615231 204.65002,57.789656 C 204.64401,57.964081 204.71482,58.132362 204.84375,58.25 L 205.71875,59.09375 C 205.94276,59.288486 206.27599,59.288486 206.5,59.09375 C 208.01963,57.782537 208.95296,55.812212 208.75,53.6875 C 208.42165,50.250246 205.34588,47.729812 201.84375,47.875 C 201.8096,47.876415 201.69162,47.857633 201.5,47.875 z " + id="path10769" + style="fill:url(#linearGradient12117);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient12113);stroke-width:0.58703899;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + inkscape:original="M 201.5625 48.46875 C 198.22936 48.770755 195.75529 51.627732 196.0625 54.84375 C 196.20781 56.365042 196.9411 57.708249 198.03125 58.65625 L 198.96875 57.75 C 198.10643 56.995329 197.52218 55.93237 197.40625 54.71875 C 197.16225 52.164404 199.07203 49.924478 201.6875 49.6875 C 204.30298 49.450497 206.63099 51.320657 206.875 53.875 C 207.02314 55.425824 206.36999 56.863908 205.25 57.8125 L 206.125 58.65625 C 207.51113 57.460224 208.33975 55.670994 208.15625 53.75 C 207.85863 50.634479 205.07888 48.335927 201.875 48.46875 C 201.77164 48.473032 201.66666 48.45931 201.5625 48.46875 z " + inkscape:radius="0.59534109" + sodipodi:type="inkscape:offset" + transform="matrix(0.6490202,0,0,0.6490202,-37.053267,401.65173)" /> + <path + sodipodi:nodetypes="csccccccsc" + id="path10771" + d="M 94.130193,435.56823 C 93.516523,435.56823 93.018453,436.06873 93.018453,436.68543 C 93.018453,437.02473 93.172133,437.32433 93.409933,437.52933 L 93.819163,437.71863 L 93.694353,441.70473 L 94.786743,441.70473 L 94.522243,437.73543 L 94.880943,437.50373 C 95.101233,437.29963 95.241913,437.01013 95.241913,436.68543 C 95.241913,436.06873 94.743843,435.56823 94.130193,435.56823 z " + style="fill:url(#linearGradient12106);fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.22314997;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.64321606" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/crystalsvg/16x16/actions/podcast.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + </g> + <g + id="g12373" + transform="translate(-5,-10.027002)"> + <rect + style="fill:#b9cff9;fill-opacity:0;stroke:none;stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + id="rect10845" + width="48" + height="48" + x="215.11923" + y="400.38922" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/48x48/actions/amarok_podcast2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <g + id="g10775" + transform="matrix(1.511251,0,0,1.511251,-152.28313,431.37033)" + inkscape:export-filename="/home/lando/Projects/Amarok/Playlist Types/amarok_podcast2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <path + sodipodi:nodetypes="ccccccccc" + style="fill:url(#linearGradient10903);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient10905);stroke-width:0.4962773;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 247.4797,-16.724353 C 245.19536,-16.724353 243.35958,-15.912221 243.35958,-13.541697 L 243.35958,5.629258 C 243.48998,6.484086 244.44578,7.32465 245.01941,7.485528 L 272.7463,7.485528 C 273.32442,7.323387 274.49871,6.49376 274.625,5.629258 L 274.625,-13.541697 C 274.625,-15.912221 272.78922,-16.724353 270.50489,-16.724353 L 247.4797,-16.724353 z " + id="path10777" /> + <path + id="path10779" + d="M 245.7032,-11.106549 L 272.28145,-11.106549 C 273.12286,-11.106549 273.80023,-10.446932 273.80023,-9.627587 L 273.80023,5.2979 C 273.80023,6.117245 273.12286,6.776862 272.28145,6.776862 L 245.7032,6.776862 C 244.86179,6.776862 244.18442,6.117245 244.18442,5.2979 L 244.18442,-9.627587 C 244.18442,-10.446932 244.86179,-11.106549 245.7032,-11.106549 z " + style="fill:url(#linearGradient10907);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient10910);stroke-width:0.61309803;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" /> + <path + style="opacity:0.99578091;fill:url(#radialGradient10912);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.57956129;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 245.66479,-10.727742 L 272.29777,-10.727742 C 273.14089,-10.727742 273.81967,-10.085626 273.81967,-9.288015 L 273.81967,5.241504 C 273.81967,6.039111 273.14089,6.681231 272.29777,6.681231 L 245.66479,6.681231 C 244.82175,6.681231 244.14295,6.039111 244.14295,5.241504 L 244.14295,-9.288015 C 244.14295,-10.085626 244.82175,-10.727742 245.66479,-10.727742 z " + id="path10781" /> + <path + id="path10783" + d="M 247.74611,-16.675395 C 245.51463,-16.675395 243.72133,-15.841241 243.72133,-13.406434 L 243.72133,-13.042257 C 243.84869,-12.164248 244.78239,-11.300888 245.34273,-11.135649 L 272.42802,-11.135649 C 272.99275,-11.302188 274.13988,-12.154312 274.26325,-13.042257 L 274.26325,-13.406434 C 274.26325,-15.841241 272.46995,-16.675395 270.23846,-16.675395 L 247.74611,-16.675395 z " + style="opacity:0.15189873;fill:url(#linearGradient10914);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.71897209;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:nodetypes="ccccccccc" /> + </g> + <g + transform="matrix(2.3942372,0,0,2.304082,-288.87944,-49.598301)" + id="g10785" + inkscape:export-filename="/home/lando/Projects/Amarok/Playlist Types/amarok_podcast2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <g + id="g10787"> + <path + id="path10789" + d="M 216.59388,201.58154 L 216.59388,213.16264" + style="opacity:0.39183673;fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#264a9e;stroke-width:0.19158731;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:nodetypes="cc" /> + <path + style="opacity:0.39183673;fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#264a9e;stroke-width:0.19158731;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 223.52993,201.58154 L 223.52993,213.17716" + id="path10791" + sodipodi:nodetypes="cc" /> + </g> + <rect + style="opacity:0.56734691;fill:#6785ed;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.28953668;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect10793" + width="18.826923" + height="3.4617176" + x="211.13826" + y="205.69659" /> + </g> + <path + transform="matrix(1.9914205,0,0,1.9914205,-163.36459,318.90418)" + d="M 201.4375,47.15625 C 197.41705,47.52053 194.37229,51.014726 194.75,54.96875 C 194.92784,56.830672 195.82274,58.465378 197.15625,59.625 C 197.65641,60.091804 198.43734,60.091804 198.9375,59.625 L 199.875,58.71875 C 200.15019,58.464713 200.28125,58.104935 200.28125,57.75 C 200.28127,57.395009 200.15018,57.035274 199.875,56.78125 C 199.86458,56.781024 199.85417,56.781024 199.84375,56.78125 C 199.22065,56.235933 198.80142,55.459244 198.71875,54.59375 C 198.5432,52.756008 199.87407,51.175633 201.8125,51 C 203.74115,50.825235 205.38771,52.17032 205.5625,54 C 205.66911,55.116077 205.22291,56.089566 204.40625,56.78125 C 204.11053,57.015883 203.9502,57.381534 203.9375,57.75 C 203.92478,58.118451 204.03823,58.502493 204.34375,58.78125 L 205.21875,59.625 C 205.22917,59.625226 205.23958,59.625226 205.25,59.625 C 205.74062,60.051502 206.47813,60.051502 206.96875,59.625 C 208.64796,58.176095 209.69557,55.999456 209.46875,53.625 C 209.10312,49.797516 205.67634,46.996067 201.8125,47.15625 C 201.96122,47.150088 201.83088,47.15954 201.75,47.15625 C 201.66912,47.15296 201.56975,47.144264 201.4375,47.15625 z " + id="path10799" + style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.2;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + xlink:href="#path5359" + inkscape:original="M 201.5 47.875 C 197.85718 48.205064 195.12959 51.35581 195.46875 54.90625 C 195.62893 56.583244 196.42448 58.049773 197.625 59.09375 C 197.85376 59.307253 198.20874 59.307253 198.4375 59.09375 L 199.375 58.1875 C 199.4971 58.074787 199.5625 57.916174 199.5625 57.75 C 199.56251 57.583826 199.4971 57.425213 199.375 57.3125 C 198.62088 56.652518 198.10105 55.714144 198 54.65625 C 197.78709 52.427426 199.44435 50.490156 201.75 50.28125 C 204.05353 50.072515 206.06871 51.712605 206.28125 53.9375 C 206.41033 55.288748 205.85565 56.51317 204.875 57.34375 C 204.73828 57.452227 204.66226 57.606825 204.65625 57.78125 C 204.65023 57.955674 204.71482 58.132362 204.84375 58.25 L 205.71875 59.09375 C 205.94276 59.288486 206.27599 59.288486 206.5 59.09375 C 208.01963 57.782537 208.95296 55.812212 208.75 53.6875 C 208.42165 50.250246 205.34588 47.729812 201.84375 47.875 C 201.8096 47.876415 201.69162 47.857633 201.5 47.875 z " + inkscape:radius="0.71942902" + sodipodi:type="inkscape:offset" + inkscape:href="#path5359" /> + <path + transform="matrix(1.9914205,0,0,1.9914205,-163.39719,318.88239)" + sodipodi:type="inkscape:offset" + inkscape:radius="0.59534109" + inkscape:original="M 201.5625 48.46875 C 198.22936 48.770755 195.75529 51.627732 196.0625 54.84375 C 196.20781 56.365042 196.9411 57.708249 198.03125 58.65625 L 198.96875 57.75 C 198.10643 56.995329 197.52218 55.93237 197.40625 54.71875 C 197.16225 52.164404 199.07203 49.924478 201.6875 49.6875 C 204.30298 49.450497 206.63099 51.320657 206.875 53.875 C 207.02314 55.425824 206.36999 56.863908 205.25 57.8125 L 206.125 58.65625 C 207.51113 57.460224 208.33975 55.670994 208.15625 53.75 C 207.85863 50.634479 205.07888 48.335927 201.875 48.46875 C 201.77164 48.473032 201.66666 48.45931 201.5625 48.46875 z " + style="fill:url(#linearGradient12009);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient12005);stroke-width:0.13234062;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path10801" + d="M 201.5,47.875 C 197.85718,48.205064 195.12959,51.35581 195.46875,54.90625 C 195.62893,56.583244 196.42448,58.049773 197.625,59.09375 C 197.85376,59.307253 198.20874,59.307253 198.4375,59.09375 L 199.375,58.1875 C 199.4971,58.074787 199.56655,57.916174 199.56655,57.75 C 199.56655,57.583826 199.4971,57.425213 199.375,57.3125 C 198.62088,56.652518 198.10105,55.714144 198,54.65625 C 197.78709,52.427426 199.44435,50.490156 201.75,50.28125 C 204.05353,50.072515 206.06871,51.712605 206.28125,53.9375 C 206.41033,55.288748 205.85565,56.51317 204.875,57.34375 C 204.73828,57.452227 204.65604,57.615231 204.65002,57.789656 C 204.64401,57.964081 204.71482,58.132362 204.84375,58.25 L 205.71875,59.09375 C 205.94276,59.288486 206.27599,59.288486 206.5,59.09375 C 208.01963,57.782537 208.95296,55.812212 208.75,53.6875 C 208.42165,50.250246 205.34588,47.729812 201.84375,47.875 C 201.8096,47.876415 201.69162,47.857633 201.5,47.875 z " /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/vadim/collection.png" + style="fill:url(#linearGradient11998);fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.3246159;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.64321606" + d="M 239.11942,422.95013 C 237.23648,422.95013 235.70822,424.48583 235.70822,426.37803 C 235.70822,427.41903 236.17976,428.33853 236.90942,428.96733 L 238.16509,429.54823 L 237.78212,441.77883 L 241.13396,441.77883 L 240.32239,429.59963 L 241.42301,428.88893 C 242.09893,428.26243 242.53059,427.37423 242.53059,426.37803 C 242.53059,424.48583 241.00232,422.95013 239.11942,422.95013 z " + id="path10803" + sodipodi:nodetypes="csccccccsc" /> + </g> + <g + id="g12389" + transform="translate(-5,-0.890211)"> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/crystalsvg/64x64/actions/podcast.png" + sodipodi:nodetypes="ccccccccc" + style="fill:url(#linearGradient11968);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient11971);stroke-width:0.74999988;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 302.43619,389.90083 C 297.81497,389.90083 294.10118,391.54383 294.10118,396.33933 L 294.10118,435.12223 C 294.36498,436.85153 296.29857,438.55203 297.45902,438.87743 L 353.55055,438.87743 C 354.72008,438.54943 357.09567,436.87113 357.35116,435.12223 L 357.35116,396.33933 C 357.35116,391.54383 353.63737,389.90083 349.01617,389.90083 L 302.43619,389.90083 z " + id="path10810" /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/crystalsvg/64x64/actions/podcast.png" + id="path10812" + d="M 298.84233,401.26563 L 352.61016,401.26563 C 354.31233,401.26563 355.68265,402.60003 355.68265,404.25763 L 355.68265,434.45193 C 355.68265,436.10943 354.31233,437.44383 352.61016,437.44383 L 298.84233,437.44383 C 297.14016,437.44383 295.76984,436.10943 295.76984,434.45193 L 295.76984,404.25763 C 295.76984,402.60003 297.14016,401.26563 298.84233,401.26563 z " + style="fill:url(#linearGradient11963);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient11965);stroke-width:0.92654544;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/crystalsvg/64x64/actions/podcast.png" + style="opacity:0.99578091;fill:url(#radialGradient11960);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.57956129;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 298.76463,402.03203 L 352.64317,402.03203 C 354.3488,402.03203 355.72198,403.33103 355.72198,404.94453 L 355.72198,434.33783 C 355.72198,435.95133 354.3488,437.25033 352.64317,437.25033 L 298.76463,437.25033 C 297.05916,437.25033 295.68594,435.95133 295.68594,434.33783 L 295.68594,404.94453 C 295.68594,403.33103 297.05916,402.03203 298.76463,402.03203 z " + id="path10814" /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/crystalsvg/64x64/actions/podcast.png" + id="path10816" + d="M 302.97514,389.99993 C 298.46085,389.99993 294.833,391.68743 294.833,396.61303 L 294.833,397.34973 C 295.09065,399.12593 296.97953,400.87253 298.1131,401.20683 L 352.90667,401.20683 C 354.04912,400.86993 356.36976,399.14603 356.61934,397.34973 L 356.61934,396.61303 C 356.61934,391.68743 352.99149,389.99993 348.47718,389.99993 L 302.97514,389.99993 z " + style="opacity:0.15189873;fill:url(#linearGradient11957);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.71897209;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:nodetypes="ccccccccc" /> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/crystalsvg/64x64/actions/podcast.png" + id="g10818" + transform="matrix(3.2049899,0,0,3.0843058,-381.06696,-220.10321)"> + <g + id="g10821"> + <path + sodipodi:nodetypes="cc" + style="opacity:0.39183673;fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#264a9e;stroke-width:0.14312236;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 216.59388,201.58154 L 216.59388,213.16264" + id="path10823" /> + <path + sodipodi:nodetypes="cc" + id="path10825" + d="M 223.52993,201.58154 L 223.52993,213.17716" + style="opacity:0.39183673;fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#264a9e;stroke-width:0.14312236;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + <rect + y="205.69659" + x="211.13826" + height="3.4617176" + width="18.826923" + id="rect10827" + style="opacity:0.56734691;fill:#6785ed;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.28953668;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/crystalsvg/64x64/actions/podcast.png" + sodipodi:type="inkscape:offset" + inkscape:radius="0.71942902" + inkscape:original="M 201.5 47.875 C 197.85718 48.205064 195.12959 51.35581 195.46875 54.90625 C 195.62893 56.583244 196.42448 58.049773 197.625 59.09375 C 197.85376 59.307253 198.20874 59.307253 198.4375 59.09375 L 199.375 58.1875 C 199.4971 58.074787 199.5625 57.916174 199.5625 57.75 C 199.56251 57.583826 199.4971 57.425213 199.375 57.3125 C 198.62088 56.652518 198.10105 55.714144 198 54.65625 C 197.78709 52.427426 199.44435 50.490156 201.75 50.28125 C 204.05353 50.072515 206.06871 51.712605 206.28125 53.9375 C 206.41033 55.288748 205.85565 56.51317 204.875 57.34375 C 204.73828 57.452227 204.66226 57.606825 204.65625 57.78125 C 204.65023 57.955674 204.71482 58.132362 204.84375 58.25 L 205.71875 59.09375 C 205.94276 59.288486 206.27599 59.288486 206.5 59.09375 C 208.01963 57.782537 208.95296 55.812212 208.75 53.6875 C 208.42165 50.250246 205.34588 47.729812 201.84375 47.875 C 201.8096 47.876415 201.69162 47.857633 201.5 47.875 z " + xlink:href="#path5359" + style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.2;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path10833" + d="M 201.4375,47.15625 C 197.41705,47.52053 194.37229,51.014726 194.75,54.96875 C 194.92784,56.830672 195.82274,58.465378 197.15625,59.625 C 197.65641,60.091804 198.43734,60.091804 198.9375,59.625 L 199.875,58.71875 C 200.15019,58.464713 200.28125,58.104935 200.28125,57.75 C 200.28127,57.395009 200.15018,57.035274 199.875,56.78125 C 199.86458,56.781024 199.85417,56.781024 199.84375,56.78125 C 199.22065,56.235933 198.80142,55.459244 198.71875,54.59375 C 198.5432,52.756008 199.87407,51.175633 201.8125,51 C 203.74115,50.825235 205.38771,52.17032 205.5625,54 C 205.66911,55.116077 205.22291,56.089566 204.40625,56.78125 C 204.11053,57.015883 203.9502,57.381534 203.9375,57.75 C 203.92478,58.118451 204.03823,58.502493 204.34375,58.78125 L 205.21875,59.625 C 205.22917,59.625226 205.23958,59.625226 205.25,59.625 C 205.74062,60.051502 206.47813,60.051502 206.96875,59.625 C 208.64796,58.176095 209.69557,55.999456 209.46875,53.625 C 209.10312,49.797516 205.67634,46.996067 201.8125,47.15625 C 201.96122,47.150088 201.83088,47.15954 201.75,47.15625 C 201.66912,47.15296 201.56975,47.144264 201.4375,47.15625 z " + transform="matrix(2.6657688,0,0,2.6657688,-213.04942,273.18407)" + inkscape:href="#path5359" /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/crystalsvg/64x64/actions/podcast.png" + d="M 201.5,47.875 C 197.85718,48.205064 195.12959,51.35581 195.46875,54.90625 C 195.62893,56.583244 196.42448,58.049773 197.625,59.09375 C 197.85376,59.307253 198.20874,59.307253 198.4375,59.09375 L 199.375,58.1875 C 199.4971,58.074787 199.56655,57.916174 199.56655,57.75 C 199.56655,57.583826 199.4971,57.425213 199.375,57.3125 C 198.62088,56.652518 198.10105,55.714144 198,54.65625 C 197.78709,52.427426 199.44435,50.490156 201.75,50.28125 C 204.05353,50.072515 206.06871,51.712605 206.28125,53.9375 C 206.41033,55.288748 205.85565,56.51317 204.875,57.34375 C 204.73828,57.452227 204.65604,57.615231 204.65002,57.789656 C 204.64401,57.964081 204.71482,58.132362 204.84375,58.25 L 205.71875,59.09375 C 205.94276,59.288486 206.27599,59.288486 206.5,59.09375 C 208.01963,57.782537 208.95296,55.812212 208.75,53.6875 C 208.42165,50.250246 205.34588,47.729812 201.84375,47.875 C 201.8096,47.876415 201.69162,47.857633 201.5,47.875 z " + id="path10835" + style="fill:url(#linearGradient11973);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient11939);stroke-width:0.09886302;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + inkscape:original="M 201.5625 48.46875 C 198.22936 48.770755 195.75529 51.627732 196.0625 54.84375 C 196.20781 56.365042 196.9411 57.708249 198.03125 58.65625 L 198.96875 57.75 C 198.10643 56.995329 197.52218 55.93237 197.40625 54.71875 C 197.16225 52.164404 199.07203 49.924478 201.6875 49.6875 C 204.30298 49.450497 206.63099 51.320657 206.875 53.875 C 207.02314 55.425824 206.36999 56.863908 205.25 57.8125 L 206.125 58.65625 C 207.51113 57.460224 208.33975 55.670994 208.15625 53.75 C 207.85863 50.634479 205.07888 48.335927 201.875 48.46875 C 201.77164 48.473032 201.66666 48.45931 201.5625 48.46875 z " + inkscape:radius="0.59534109" + sodipodi:type="inkscape:offset" + transform="matrix(2.6657688,0,0,2.6657688,-213.09306,273.1549)" /> + <path + sodipodi:nodetypes="csccccccsc" + id="path10837" + d="M 325.72645,412.46273 C 323.20589,412.46273 321.16013,414.51853 321.16013,417.05143 C 321.16013,418.44493 321.79135,419.67583 322.76808,420.51763 L 324.44895,421.29513 L 323.93631,437.66743 L 328.42317,437.66743 L 327.33678,421.36393 L 328.8101,420.41263 C 329.71491,419.57403 330.29274,418.38503 330.29274,417.05143 C 330.29274,414.51853 328.24695,412.46273 325.72645,412.46273 z " + style="fill:url(#linearGradient10963);fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.32461607;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.64321606" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/crystalsvg/64x64/actions/podcast.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + </g> + <g + id="g13498" + transform="translate(-7,-79.747803)"> + <g + id="g12194" + transform="matrix(0.8827875,0,0,0.8827875,-46.055587,-359.87062)" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/48x48/actions/amarok_covermanager" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <g + transform="matrix(2.328017,0,0,2.328017,-290.8753,1102.129)" + id="g12196" + inkscape:export-filename="/home/lando/Projects/Amarok/Tool-icons/amarok_covermanager.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <rect + y="134.63829" + x="569.30548" + height="20.578251" + width="20.63953" + id="rect12198" + style="fill:url(#linearGradient12264);fill-opacity:1;stroke:none;stroke-width:1.36047041;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + sodipodi:nodetypes="cssssccsssc" + id="path12200" + d="M 570.42085,145.8102 C 570.8889,151.10014 575.55898,155.02506 580.84766,154.57165 C 586.13636,154.11825 590.04954,149.45748 589.58149,144.16753 C 589.11344,138.87755 584.44062,134.95286 579.15194,135.40629 C 574.37985,135.81541 570.72993,139.64985 570.40623,144.2813 C 570.37119,144.78271 570.37513,145.29344 570.42085,145.8102 z M 577.19506,145.22944 C 577.05303,143.62397 578.19336,142.21399 579.74253,142.08118 C 581.29168,141.94835 582.66523,143.14283 582.80728,144.74827 C 582.94931,146.35374 581.80622,147.7639 580.25705,147.89671 C 578.70791,148.02955 577.33711,146.83482 577.19506,145.22944 z " + style="opacity:0.5;fill:url(#linearGradient12266);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient12268);stroke-width:0.86471045;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + transform="matrix(-1.154934e-2,-0.130532,0.137633,-1.179942e-2,390.2798,333.5555)" + d="M 1376.7952 1487.2524 A 68.554123 64.983597 0 1 1 1239.687,1487.2524 A 68.554123 64.983597 0 1 1 1376.7952 1487.2524 z" + sodipodi:ry="64.983597" + sodipodi:rx="68.554123" + sodipodi:cy="1487.2524" + sodipodi:cx="1308.2411" + id="path12202" + style="fill:url(#linearGradient12270);fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:4.02875662;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + sodipodi:type="arc" /> + <path + sodipodi:nodetypes="ccccc" + id="path12204" + d="M 578.28881,147.30247 C 578.18646,147.24432 578.00323,147.20786 577.93039,147.10521 L 573.67729,151.46706 C 574.91512,152.77018 576.43825,153.69773 578.17312,153.78613 L 578.28881,147.30247 z " + style="fill:url(#linearGradient12272);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient12274);stroke-width:0.19098195;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" /> + <path + sodipodi:nodetypes="ccccc" + id="path12206" + d="M 581.78706,142.86262 C 581.89083,142.92112 581.98179,142.99931 582.05559,143.10261 L 586.59163,138.58961 C 585.33698,137.2781 583.66819,136.42176 581.90864,136.33387 L 581.78706,142.86262 z " + style="fill:url(#linearGradient12276);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient12278);stroke-width:0.1930059;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" /> + <rect + y="134.61842" + x="568.62524" + height="20.618" + width="1.3694109" + id="rect12208" + style="opacity:1;fill:url(#linearGradient18308);fill-opacity:1;stroke:none;stroke-width:0.97624999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + style="opacity:0.5;fill:url(#linearGradient12280);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 589.92675,144.21707 C 590.64196,146.74179 585.47222,143.54203 579.98362,143.54203 C 574.49503,143.54203 569.88413,147.68044 570.04051,144.35284 C 570.27811,139.29709 574.52897,134.98184 579.98362,134.98184 C 585.20068,134.9479 589.62127,139.12738 589.92675,144.21707 z " + id="path12210" + sodipodi:nodetypes="csscc" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" /> + </g> + <rect + ry="0" + rx="0" + y="1415.5239" + x="1028.4021" + height="47.99905" + width="6.2279215" + id="rect12212" + style="fill:url(#linearGradient12282);fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + </g> + <rect + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/48x48/actions/amarok_covermanager" + style="fill:#b9cff9;fill-opacity:0;stroke:none;stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + id="rect12236" + width="48" + height="48" + x="861.80493" + y="886.92218" /> + </g> + <g + id="g13373" + transform="translate(-8,-92)"> + <g + id="g12214" + transform="matrix(0.8827822,0,0,0.8827822,-35.223857,-258.98849)" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/64x64/actions/amarok_covermanager.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Tool-icons/amarok_covermanager.png" + id="g12216" + transform="matrix(3.104024,0,0,3.104024,-654.6348,981.6642)"> + <rect + style="fill:url(#linearGradient12244);fill-opacity:1;stroke:none;stroke-width:1.36047041;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect12218" + width="20.63953" + height="20.578251" + x="569.30548" + y="134.63829" /> + <path + style="opacity:0.5;fill:url(#linearGradient12246);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient12248);stroke-width:0.86471558;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 570.42085,145.8102 C 570.8889,151.10014 575.55898,155.02506 580.84766,154.57165 C 586.13636,154.11825 590.04954,149.45748 589.58149,144.16753 C 589.11344,138.87755 584.44062,134.95286 579.15194,135.40629 C 574.37985,135.81541 570.72993,139.64985 570.40623,144.2813 C 570.37119,144.78271 570.37513,145.29344 570.42085,145.8102 z M 577.19506,145.22944 C 577.05303,143.62397 578.19336,142.21399 579.74253,142.08118 C 581.29168,141.94835 582.66523,143.14283 582.80728,144.74827 C 582.94931,146.35374 581.80622,147.7639 580.25705,147.89671 C 578.70791,148.02955 577.33711,146.83482 577.19506,145.22944 z " + id="path12220" + sodipodi:nodetypes="cssssccsssc" /> + <path + sodipodi:type="arc" + style="fill:url(#linearGradient12250);fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:4.02878094;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + id="path12222" + sodipodi:cx="1308.2411" + sodipodi:cy="1487.2524" + sodipodi:rx="68.554123" + sodipodi:ry="64.983597" + d="M 1376.7952 1487.2524 A 68.554123 64.983597 0 1 1 1239.687,1487.2524 A 68.554123 64.983597 0 1 1 1376.7952 1487.2524 z" + transform="matrix(-1.154934e-2,-0.130532,0.137633,-1.179942e-2,390.2798,333.5555)" /> + <path + style="fill:url(#linearGradient12252);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient12254);stroke-width:0.1909831;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + d="M 578.28881,147.30247 C 578.18646,147.24432 578.00323,147.20786 577.93039,147.10521 L 573.67729,151.46706 C 574.91512,152.77018 576.43825,153.69773 578.17312,153.78613 L 578.28881,147.30247 z " + id="path12224" + sodipodi:nodetypes="ccccc" /> + <path + style="fill:url(#linearGradient12256);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient12258);stroke-width:0.19300705;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + d="M 581.78706,142.86262 C 581.89083,142.92112 581.98179,142.99931 582.05559,143.10261 L 586.59163,138.58961 C 585.33698,137.2781 583.66819,136.42176 581.90864,136.33387 L 581.78706,142.86262 z " + id="path12226" + sodipodi:nodetypes="ccccc" /> + <rect + style="opacity:1;fill:url(#linearGradient18308);fill-opacity:1;stroke:none;stroke-width:0.97624999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect12228" + width="1.3694109" + height="20.618" + x="568.62524" + y="134.61842" /> + <path + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + sodipodi:nodetypes="csscc" + id="path12230" + d="M 589.92675,144.21707 C 590.64196,146.74179 585.47222,143.54203 579.98362,143.54203 C 574.49503,143.54203 569.88413,147.68044 570.04051,144.35284 C 570.27811,139.29709 574.52897,134.98184 579.98362,134.98184 C 585.20068,134.9479 589.62127,139.12738 589.92675,144.21707 z " + style="opacity:0.5;fill:url(#linearGradient12260);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" /> + </g> + <rect + style="fill:url(#linearGradient12262);fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect12232" + width="8.3038969" + height="63.998749" + x="1104.4021" + y="1399.5242" + rx="0" + ry="0" /> + </g> + <rect + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/64x64/actions/amarok_covermanager.png" + y="972.7345" + x="939.72266" + height="64" + width="64" + id="rect12234" + style="fill:#b9cff9;fill-opacity:0;stroke:none;stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" /> + <text + xml:space="preserve" + style="font-size:66.33522797px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-indent:0;text-align:center;text-decoration:none;line-height:100%;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;text-anchor:middle;opacity:1;color:#000000;fill:#000000;fill-opacity:1;fill-rule:nonzero;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-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;filter:url(#filter12395);enable-background:accumulate;font-family:Verdana" + x="1163.4524" + y="1604.4269" + id="text12373" + sodipodi:linespacing="100%" + transform="matrix(0.9840157,0,0,1.016244,-167.94239,-598.88889)"><tspan + sodipodi:role="line" + id="tspan12375" + x="1163.4524" + y="1604.4269" + style="fill:#000000;fill-opacity:1;stroke:none;stroke-opacity:1">?</tspan></text> + <text + transform="scale(0.9840157,1.016244)" + sodipodi:linespacing="100%" + id="text12344" + y="1013.1429" + x="990.74957" + style="font-size:66.33522797px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-indent:0;text-align:center;text-decoration:none;line-height:100%;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;text-anchor:middle;opacity:1;color:#000000;fill:url(#linearGradient14068);fill-opacity:1;fill-rule:nonzero;stroke:url(#linearGradient14070);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;font-family:Verdana" + xml:space="preserve"><tspan + style="fill:url(#linearGradient14068);fill-opacity:1;stroke:url(#linearGradient14070);stroke-width:1;stroke-opacity:1" + y="1013.1429" + x="990.74957" + id="tspan12346" + sodipodi:role="line">?</tspan></text> + </g> + <g + id="g13390" + transform="translate(-8,-92)"> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/64x64/actions/amarok_covermanager.png" + transform="matrix(0.6620861,0,0,0.6620861,125.51333,63.943012)" + id="g12410"> + <g + transform="matrix(3.104024,0,0,3.104024,-654.6348,981.6642)" + id="g12413" + inkscape:export-filename="/home/lando/Projects/Amarok/Tool-icons/amarok_covermanager.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <rect + y="134.63829" + x="569.30548" + height="20.578251" + width="20.63953" + id="rect12415" + style="fill:url(#linearGradient12441);fill-opacity:1;stroke:none;stroke-width:1.36047041;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + sodipodi:nodetypes="cssssccsssc" + id="path12417" + d="M 570.42085,145.8102 C 570.8889,151.10014 575.55898,155.02506 580.84766,154.57165 C 586.13636,154.11825 590.04954,149.45748 589.58149,144.16753 C 589.11344,138.87755 584.44062,134.95286 579.15194,135.40629 C 574.37985,135.81541 570.72993,139.64985 570.40623,144.2813 C 570.37119,144.78271 570.37513,145.29344 570.42085,145.8102 z M 577.19506,145.22944 C 577.05303,143.62397 578.19336,142.21399 579.74253,142.08118 C 581.29168,141.94835 582.66523,143.14283 582.80728,144.74827 C 582.94931,146.35374 581.80622,147.7639 580.25705,147.89671 C 578.70791,148.02955 577.33711,146.83482 577.19506,145.22944 z " + style="opacity:0.5;fill:url(#linearGradient12443);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient12445);stroke-width:0.86471558;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + transform="matrix(-1.154934e-2,-0.130532,0.137633,-1.179942e-2,390.2798,333.5555)" + d="M 1376.7952 1487.2524 A 68.554123 64.983597 0 1 1 1239.687,1487.2524 A 68.554123 64.983597 0 1 1 1376.7952 1487.2524 z" + sodipodi:ry="64.983597" + sodipodi:rx="68.554123" + sodipodi:cy="1487.2524" + sodipodi:cx="1308.2411" + id="path12419" + style="fill:url(#linearGradient12447);fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:4.02878094;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + sodipodi:type="arc" /> + <path + sodipodi:nodetypes="ccccc" + id="path12421" + d="M 578.28881,147.30247 C 578.18646,147.24432 578.00323,147.20786 577.93039,147.10521 L 573.67729,151.46706 C 574.91512,152.77018 576.43825,153.69773 578.17312,153.78613 L 578.28881,147.30247 z " + style="fill:url(#linearGradient12449);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient12452);stroke-width:0.1909831;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" /> + <path + sodipodi:nodetypes="ccccc" + id="path12423" + d="M 581.78706,142.86262 C 581.89083,142.92112 581.98179,142.99931 582.05559,143.10261 L 586.59163,138.58961 C 585.33698,137.2781 583.66819,136.42176 581.90864,136.33387 L 581.78706,142.86262 z " + style="fill:url(#linearGradient12454);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient12456);stroke-width:0.19300705;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" /> + <rect + y="134.61842" + x="568.62524" + height="20.618" + width="1.3694109" + id="rect12425" + style="opacity:1;fill:url(#linearGradient18308);fill-opacity:1;stroke:none;stroke-width:0.97624999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + style="opacity:0.5;fill:url(#linearGradient12458);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 589.92675,144.21707 C 590.64196,146.74179 585.47222,143.54203 579.98362,143.54203 C 574.49503,143.54203 569.88413,147.68044 570.04051,144.35284 C 570.27811,139.29709 574.52897,134.98184 579.98362,134.98184 C 585.20068,134.9479 589.62127,139.12738 589.92675,144.21707 z " + id="path12427" + sodipodi:nodetypes="csscc" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" /> + </g> + <rect + ry="0" + rx="0" + y="1399.5242" + x="1104.4021" + height="63.998749" + width="8.3038969" + id="rect12429" + style="fill:url(#linearGradient12460);fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + </g> + <rect + style="fill:#b9cff9;fill-opacity:0;stroke:none;stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + id="rect12431" + width="47.999962" + height="47.999962" + x="856.72266" + y="987.7345" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/64x64/actions/amarok_covermanager.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <text + transform="matrix(0.7380112,0,0,0.7621824,25.974513,-190.98209)" + sodipodi:linespacing="100%" + id="text12433" + y="1604.4269" + x="1163.4524" + style="font-size:66.33522797px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-indent:0;text-align:center;text-decoration:none;line-height:100%;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;text-anchor:middle;opacity:1;color:#000000;fill:#000000;fill-opacity:1;fill-rule:nonzero;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-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;filter:url(#filter12395);enable-background:accumulate;font-family:Verdana" + xml:space="preserve"><tspan + style="fill:#000000;fill-opacity:1;stroke:none;stroke-opacity:1" + y="1604.4269" + x="1163.4524" + id="tspan12435" + sodipodi:role="line">?</tspan></text> + <text + xml:space="preserve" + style="font-size:49.75138474px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-indent:0;text-align:center;text-decoration:none;line-height:100%;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;text-anchor:middle;opacity:1;color:#000000;fill:url(#linearGradient14064);fill-opacity:1;fill-rule:nonzero;stroke:url(#linearGradient14066);stroke-width:0.74999946px;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;font-family:Verdana" + x="897.46075" + y="1013.914" + id="text12437" + sodipodi:linespacing="100%" + transform="scale(0.9840157,1.016244)"><tspan + sodipodi:role="line" + id="tspan12439" + x="897.46075" + y="1013.914" + style="fill:url(#linearGradient14064);fill-opacity:1;stroke:url(#linearGradient14066);stroke-width:0.74999946;stroke-opacity:1">?</tspan></text> + </g> + <g + id="g13407" + transform="translate(-8,-92)"> + <g + id="g12466" + transform="matrix(0.4413907,0,0,0.4413907,298.24978,387.87353)" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/64x64/actions/amarok_covermanager.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Tool-icons/amarok_covermanager.png" + id="g12468" + transform="matrix(3.104024,0,0,3.104024,-654.6348,981.6642)"> + <rect + style="fill:url(#linearGradient12496);fill-opacity:1;stroke:none;stroke-width:1.36047041;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect12470" + width="20.63953" + height="20.578251" + x="569.30548" + y="134.63829" /> + <path + style="opacity:0.5;fill:url(#linearGradient12498);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient12501);stroke-width:0.86471558;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 570.42085,145.8102 C 570.8889,151.10014 575.55898,155.02506 580.84766,154.57165 C 586.13636,154.11825 590.04954,149.45748 589.58149,144.16753 C 589.11344,138.87755 584.44062,134.95286 579.15194,135.40629 C 574.37985,135.81541 570.72993,139.64985 570.40623,144.2813 C 570.37119,144.78271 570.37513,145.29344 570.42085,145.8102 z M 577.19506,145.22944 C 577.05303,143.62397 578.19336,142.21399 579.74253,142.08118 C 581.29168,141.94835 582.66523,143.14283 582.80728,144.74827 C 582.94931,146.35374 581.80622,147.7639 580.25705,147.89671 C 578.70791,148.02955 577.33711,146.83482 577.19506,145.22944 z " + id="path12472" + sodipodi:nodetypes="cssssccsssc" /> + <path + sodipodi:type="arc" + style="fill:url(#linearGradient12503);fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:4.02878094;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + id="path12474" + sodipodi:cx="1308.2411" + sodipodi:cy="1487.2524" + sodipodi:rx="68.554123" + sodipodi:ry="64.983597" + d="M 1376.7952 1487.2524 A 68.554123 64.983597 0 1 1 1239.687,1487.2524 A 68.554123 64.983597 0 1 1 1376.7952 1487.2524 z" + transform="matrix(-1.154934e-2,-0.130532,0.137633,-1.179942e-2,390.2798,333.5555)" /> + <path + style="fill:url(#linearGradient12505);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient12507);stroke-width:0.1909831;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + d="M 578.28881,147.30247 C 578.18646,147.24432 578.00323,147.20786 577.93039,147.10521 L 573.67729,151.46706 C 574.91512,152.77018 576.43825,153.69773 578.17312,153.78613 L 578.28881,147.30247 z " + id="path12476" + sodipodi:nodetypes="ccccc" /> + <path + style="fill:url(#linearGradient12509);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient12511);stroke-width:0.19300705;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + d="M 581.78706,142.86262 C 581.89083,142.92112 581.98179,142.99931 582.05559,143.10261 L 586.59163,138.58961 C 585.33698,137.2781 583.66819,136.42176 581.90864,136.33387 L 581.78706,142.86262 z " + id="path12478" + sodipodi:nodetypes="ccccc" /> + <rect + style="opacity:1;fill:url(#linearGradient18308);fill-opacity:1;stroke:none;stroke-width:0.97624999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect12480" + width="1.3694109" + height="20.618" + x="568.62524" + y="134.61842" /> + <path + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + sodipodi:nodetypes="csscc" + id="path12482" + d="M 589.92675,144.21707 C 590.64196,146.74179 585.47222,143.54203 579.98362,143.54203 C 574.49503,143.54203 569.88413,147.68044 570.04051,144.35284 C 570.27811,139.29709 574.52897,134.98184 579.98362,134.98184 C 585.20068,134.9479 589.62127,139.12738 589.92675,144.21707 z " + style="opacity:0.5;fill:url(#linearGradient12513);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" /> + </g> + <rect + style="fill:url(#linearGradient12515);fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect12484" + width="8.3038969" + height="63.998749" + x="1104.4021" + y="1399.5242" + rx="0" + ry="0" /> + </g> + <rect + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/64x64/actions/amarok_covermanager.png" + y="1003.7345" + x="785.72266" + height="31.999973" + width="31.999973" + id="rect12486" + style="fill:#b9cff9;fill-opacity:0;stroke:none;stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" /> + <text + xml:space="preserve" + style="font-size:66.33522797px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-indent:0;text-align:center;text-decoration:none;line-height:100%;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;text-anchor:middle;opacity:1;color:#000000;fill:#000000;fill-opacity:1;fill-rule:nonzero;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-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;filter:url(#filter12395);enable-background:accumulate;font-family:Verdana" + x="1163.4524" + y="1604.4269" + id="text12488" + sodipodi:linespacing="100%" + transform="matrix(0.4920074,0,0,0.5081216,231.89057,217.92347)"><tspan + sodipodi:role="line" + id="tspan12490" + x="1163.4524" + y="1604.4269" + style="fill:#000000;fill-opacity:1;stroke:none;stroke-opacity:1">?</tspan></text> + <text + transform="scale(0.9840157,1.016244)" + sodipodi:linespacing="100%" + id="text12492" + y="1015.669" + x="816.36688" + style="font-size:33.16759109px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-indent:0;text-align:center;text-decoration:none;line-height:100%;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;text-anchor:middle;opacity:1;color:#000000;fill:url(#linearGradient14060);fill-opacity:1;fill-rule:nonzero;stroke:url(#linearGradient14062);stroke-width:0.49999964px;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;font-family:Verdana" + xml:space="preserve"><tspan + style="fill:url(#linearGradient14060);fill-opacity:1;stroke:url(#linearGradient14062);stroke-width:0.49999964;stroke-opacity:1" + y="1015.669" + x="816.36688" + id="tspan12494" + sodipodi:role="line">?</tspan></text> + </g> + <g + id="g13424" + transform="translate(-8,-92)"> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/64x64/actions/amarok_covermanager.png" + transform="matrix(0.3034559,0,0,0.3034559,404.58531,592.33041)" + id="g12521"> + <g + transform="matrix(3.104024,0,0,3.104024,-654.6348,981.6642)" + id="g12523" + inkscape:export-filename="/home/lando/Projects/Amarok/Tool-icons/amarok_covermanager.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <rect + y="134.63829" + x="569.30548" + height="20.578251" + width="20.63953" + id="rect12525" + style="fill:url(#linearGradient12551);fill-opacity:1;stroke:none;stroke-width:1.36047041;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + sodipodi:nodetypes="cssssccsssc" + id="path12527" + d="M 570.42085,145.8102 C 570.8889,151.10014 575.55898,155.02506 580.84766,154.57165 C 586.13636,154.11825 590.04954,149.45748 589.58149,144.16753 C 589.11344,138.87755 584.44062,134.95286 579.15194,135.40629 C 574.37985,135.81541 570.72993,139.64985 570.40623,144.2813 C 570.37119,144.78271 570.37513,145.29344 570.42085,145.8102 z M 577.19506,145.22944 C 577.05303,143.62397 578.19336,142.21399 579.74253,142.08118 C 581.29168,141.94835 582.66523,143.14283 582.80728,144.74827 C 582.94931,146.35374 581.80622,147.7639 580.25705,147.89671 C 578.70791,148.02955 577.33711,146.83482 577.19506,145.22944 z " + style="opacity:0.5;fill:url(#linearGradient12553);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient12555);stroke-width:0.86471558;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + transform="matrix(-1.154934e-2,-0.130532,0.137633,-1.179942e-2,390.2798,333.5555)" + d="M 1376.7952 1487.2524 A 68.554123 64.983597 0 1 1 1239.687,1487.2524 A 68.554123 64.983597 0 1 1 1376.7952 1487.2524 z" + sodipodi:ry="64.983597" + sodipodi:rx="68.554123" + sodipodi:cy="1487.2524" + sodipodi:cx="1308.2411" + id="path12529" + style="fill:url(#linearGradient12557);fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:4.02878094;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + sodipodi:type="arc" /> + <path + sodipodi:nodetypes="ccccc" + id="path12531" + d="M 578.28881,147.30247 C 578.18646,147.24432 578.00323,147.20786 577.93039,147.10521 L 573.67729,151.46706 C 574.91512,152.77018 576.43825,153.69773 578.17312,153.78613 L 578.28881,147.30247 z " + style="fill:url(#linearGradient12559);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient12561);stroke-width:0.1909831;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" /> + <path + sodipodi:nodetypes="ccccc" + id="path12533" + d="M 581.78706,142.86262 C 581.89083,142.92112 581.98179,142.99931 582.05559,143.10261 L 586.59163,138.58961 C 585.33698,137.2781 583.66819,136.42176 581.90864,136.33387 L 581.78706,142.86262 z " + style="fill:url(#linearGradient12563);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient12565);stroke-width:0.19300705;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" /> + <rect + y="134.61842" + x="568.62524" + height="20.618" + width="1.3694109" + id="rect12535" + style="opacity:1;fill:url(#linearGradient18308);fill-opacity:1;stroke:none;stroke-width:0.97624999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + style="opacity:0.5;fill:url(#linearGradient12567);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 589.92675,144.21707 C 590.64196,146.74179 585.47222,143.54203 579.98362,143.54203 C 574.49503,143.54203 569.88413,147.68044 570.04051,144.35284 C 570.27811,139.29709 574.52897,134.98184 579.98362,134.98184 C 585.20068,134.9479 589.62127,139.12738 589.92675,144.21707 z " + id="path12537" + sodipodi:nodetypes="csscc" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" /> + </g> + <rect + ry="0" + rx="0" + y="1399.5242" + x="1104.4021" + height="63.998749" + width="8.3038969" + id="rect12539" + style="fill:url(#linearGradient12569);fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + </g> + <rect + style="fill:#b9cff9;fill-opacity:0;stroke:none;stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + id="rect12541" + width="21.999964" + height="21.999964" + x="739.72266" + y="1015.7345" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/64x64/actions/amarok_covermanager.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <text + transform="matrix(0.3382548,0,0,0.3493333,358.96339,475.48991)" + sodipodi:linespacing="100%" + id="text12543" + y="1604.4269" + x="1163.4524" + style="font-size:66.33522797px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-indent:0;text-align:center;text-decoration:none;line-height:100%;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;text-anchor:middle;opacity:1;color:#000000;fill:#000000;fill-opacity:1;fill-rule:nonzero;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-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;filter:url(#filter12395);enable-background:accumulate;font-family:Verdana" + xml:space="preserve"><tspan + style="fill:#000000;fill-opacity:1;stroke:none;stroke-opacity:1" + y="1604.4269" + x="1163.4524" + id="tspan12545" + sodipodi:role="line">?</tspan></text> + <text + xml:space="preserve" + style="font-size:22.80270004px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-indent:0;text-align:center;text-decoration:none;line-height:100%;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;text-anchor:middle;opacity:1;color:#000000;fill:url(#linearGradient14056);fill-opacity:1;fill-rule:nonzero;stroke:url(#linearGradient14058);stroke-width:0.34374946px;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;font-family:Verdana" + x="764.03186" + y="1018.7339" + id="text12547" + sodipodi:linespacing="100%" + transform="scale(0.9840157,1.016244)"><tspan + sodipodi:role="line" + id="tspan12549" + x="764.03186" + y="1018.7339" + style="fill:url(#linearGradient14056);fill-opacity:1;stroke:url(#linearGradient14058);stroke-width:0.34374946;stroke-opacity:1">?</tspan></text> + </g> + <g + id="g13442" + transform="translate(-8,-92)"> + <g + id="g12575" + transform="matrix(0.2206954,0,0,0.2206954,461.98618,713.80391)" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/crystalsvg/16x16/actions/covermanager.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/lando/Projects/Amarok/Tool-icons/amarok_covermanager.png" + id="g12577" + transform="matrix(3.104024,0,0,3.104024,-654.6348,981.6642)"> + <rect + style="fill:url(#linearGradient12605);fill-opacity:1;stroke:none;stroke-width:1.36047041;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect12579" + width="20.63953" + height="20.578251" + x="569.30548" + y="134.63829" /> + <path + style="opacity:0.5;fill:url(#linearGradient12608);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient12610);stroke-width:0.86471558;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 570.42085,145.8102 C 570.8889,151.10014 575.55898,155.02506 580.84766,154.57165 C 586.13636,154.11825 590.04954,149.45748 589.58149,144.16753 C 589.11344,138.87755 584.44062,134.95286 579.15194,135.40629 C 574.37985,135.81541 570.72993,139.64985 570.40623,144.2813 C 570.37119,144.78271 570.37513,145.29344 570.42085,145.8102 z M 577.19506,145.22944 C 577.05303,143.62397 578.19336,142.21399 579.74253,142.08118 C 581.29168,141.94835 582.66523,143.14283 582.80728,144.74827 C 582.94931,146.35374 581.80622,147.7639 580.25705,147.89671 C 578.70791,148.02955 577.33711,146.83482 577.19506,145.22944 z " + id="path12581" + sodipodi:nodetypes="cssssccsssc" /> + <path + sodipodi:type="arc" + style="fill:url(#linearGradient12612);fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:4.02878094;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + id="path12583" + sodipodi:cx="1308.2411" + sodipodi:cy="1487.2524" + sodipodi:rx="68.554123" + sodipodi:ry="64.983597" + d="M 1376.7952 1487.2524 A 68.554123 64.983597 0 1 1 1239.687,1487.2524 A 68.554123 64.983597 0 1 1 1376.7952 1487.2524 z" + transform="matrix(-1.154934e-2,-0.130532,0.137633,-1.179942e-2,390.2798,333.5555)" /> + <path + style="fill:url(#linearGradient12614);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient12616);stroke-width:0.1909831;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + d="M 578.28881,147.30247 C 578.18646,147.24432 578.00323,147.20786 577.93039,147.10521 L 573.67729,151.46706 C 574.91512,152.77018 576.43825,153.69773 578.17312,153.78613 L 578.28881,147.30247 z " + id="path12585" + sodipodi:nodetypes="ccccc" /> + <path + style="fill:url(#linearGradient12618);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient12620);stroke-width:0.19300705;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + d="M 581.78706,142.86262 C 581.89083,142.92112 581.98179,142.99931 582.05559,143.10261 L 586.59163,138.58961 C 585.33698,137.2781 583.66819,136.42176 581.90864,136.33387 L 581.78706,142.86262 z " + id="path12587" + sodipodi:nodetypes="ccccc" /> + <rect + style="opacity:1;fill:url(#linearGradient18308);fill-opacity:1;stroke:none;stroke-width:0.97624999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect12589" + width="1.3694109" + height="20.618" + x="568.62524" + y="134.61842" /> + <path + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + sodipodi:nodetypes="csscc" + id="path12591" + d="M 589.92675,144.21707 C 590.64196,146.74179 585.47222,143.54203 579.98362,143.54203 C 574.49503,143.54203 569.88413,147.68044 570.04051,144.35284 C 570.27811,139.29709 574.52897,134.98184 579.98362,134.98184 C 585.20068,134.9479 589.62127,139.12738 589.92675,144.21707 z " + style="opacity:0.5;fill:url(#linearGradient12622);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" /> + </g> + <rect + style="fill:url(#linearGradient12624);fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect12593" + width="8.3038969" + height="63.998749" + x="1104.4021" + y="1399.5242" + rx="0" + ry="0" /> + </g> + <rect + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/crystalsvg/16x16/actions/covermanager.png" + y="1021.7345" + x="705.72266" + height="15.999989" + width="15.999989" + id="rect12595" + style="fill:#b9cff9;fill-opacity:0;stroke:none;stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" /> + <text + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/crystalsvg/16x16/actions/covermanager.png" + xml:space="preserve" + style="font-size:66.33522797px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-indent:0;text-align:center;text-decoration:none;line-height:100%;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;text-anchor:middle;opacity:1;color:#000000;fill:#000000;fill-opacity:1;fill-rule:nonzero;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-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;filter:url(#filter12395);enable-background:accumulate;font-family:Verdana" + x="1163.4524" + y="1604.4269" + id="text12597" + sodipodi:linespacing="100%" + transform="matrix(0.2460037,0,0,0.2540608,428.80657,628.82901)"><tspan + sodipodi:role="line" + id="tspan12599" + x="1163.4524" + y="1604.4269" + style="fill:#000000;fill-opacity:1;stroke:none;stroke-opacity:1">?</tspan></text> + <text + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/crystalsvg/16x16/actions/covermanager.png" + transform="scale(0.9840157,1.016244)" + sodipodi:linespacing="100%" + id="text12601" + y="1019.3922" + x="726.12695" + style="font-size:16.58379745px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-indent:0;text-align:center;text-decoration:none;line-height:100%;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;text-anchor:middle;opacity:1;color:#000000;fill:url(#linearGradient14052);fill-opacity:1;fill-rule:nonzero;stroke:url(#linearGradient14054);stroke-width:0.24999984px;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;font-family:Verdana" + xml:space="preserve"><tspan + style="fill:url(#linearGradient14052);fill-opacity:1;stroke:url(#linearGradient14054);stroke-width:0.24999984;stroke-opacity:1" + y="1019.3922" + x="726.12695" + id="tspan12603" + sodipodi:role="line">?</tspan></text> + </g> + <g + id="g12670" + transform="matrix(2.90909,0,0,2.90909,-42.57038,-11.09581)" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/64x64/actions/amarok_circle.png" + inkscape:export-xdpi="89.999962" + inkscape:export-ydpi="89.999962"> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + transform="matrix(1.033865,0,0,1.033865,-358.4555,-432.1183)" + d="M 377.40129 436.4032 A 10.639691 10.639691 0 1 1 356.12191,436.4032 A 10.639691 10.639691 0 1 1 377.40129 436.4032 z" + sodipodi:ry="10.639691" + sodipodi:rx="10.639691" + sodipodi:cy="436.4032" + sodipodi:cx="366.7616" + id="path12672" + style="opacity:1;fill:url(#linearGradient12712);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + transform="matrix(1.068817,0,0,1.068817,-427.0518,-456.3806)" + d="M 428.77151 444.83228 A 9.8239412 9.8239412 0 1 1 409.12363,444.83228 A 9.8239412 9.8239412 0 1 1 428.77151 444.83228 z" + sodipodi:ry="9.8239412" + sodipodi:rx="9.8239412" + sodipodi:cy="444.83228" + sodipodi:cx="418.94757" + id="path12674" + style="opacity:1;fill:url(#linearGradient12714);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + transform="matrix(0.908653,0,0,0.908654,-359.556,-386.6038)" + d="M 429.51763 446.44885 A 11.005301 11.005301 0 1 1 407.50703,446.44885 A 11.005301 11.005301 0 1 1 429.51763 446.44885 z" + sodipodi:ry="11.005301" + sodipodi:rx="11.005301" + sodipodi:cy="446.44885" + sodipodi:cx="418.51233" + id="path12676" + style="opacity:1;fill:url(#radialGradient12716);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + sodipodi:nodetypes="csssc" + id="path12678" + d="M 30.226478,19.072457 C 30.226478,24.316478 25.970475,19.68155 20.726479,19.68155 C 15.482481,19.68155 11.226478,24.316478 11.226478,19.072457 C 11.226478,13.828473 15.482481,9.5724574 20.726479,9.5724574 C 25.970475,9.5724574 30.226478,13.828473 30.226478,19.072457 z " + style="opacity:0.7;fill:url(#linearGradient12718);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" /> + </g> + <text + xml:space="preserve" + style="font-size:28.97274971px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-indent:0;text-align:center;text-decoration:none;line-height:100%;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;text-anchor:middle;color:#000000;fill:url(#linearGradient18010);fill-opacity:1;fill-rule:nonzero;stroke:url(#linearGradient18012);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;font-family:Verdana" + x="17.726954" + y="52.371033" + id="text12720" + sodipodi:linespacing="100%" + transform="scale(0.9998792,1.0001208)"><tspan + sodipodi:role="line" + id="tspan12722" + x="17.726954" + y="52.371033" + style="fill:url(#linearGradient18010);fill-opacity:1;stroke:url(#linearGradient18012);stroke-width:1">lng</tspan></text> + <g + id="g14705" + transform="matrix(0.7499999,0,0,0.7499999,-17.80029,-394.10255)" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/48x48/actions/amarok_change_language.png" + inkscape:export-xdpi="89.999962" + inkscape:export-ydpi="89.999962"> + <g + id="g14707" + transform="matrix(2.90909,0,0,2.90909,-120.9284,539.8284)" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/64x64/actions/amarok_circle.png" + inkscape:export-xdpi="89.999962" + inkscape:export-ydpi="89.999962"> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + transform="matrix(1.033865,0,0,1.033865,-358.4555,-432.1183)" + d="M 377.40129 436.4032 A 10.639691 10.639691 0 1 1 356.12191,436.4032 A 10.639691 10.639691 0 1 1 377.40129 436.4032 z" + sodipodi:ry="10.639691" + sodipodi:rx="10.639691" + sodipodi:cy="436.4032" + sodipodi:cx="366.7616" + id="path14709" + style="opacity:1;fill:url(#linearGradient14722);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + transform="matrix(1.068817,0,0,1.068817,-427.0518,-456.3806)" + d="M 428.77151 444.83228 A 9.8239412 9.8239412 0 1 1 409.12363,444.83228 A 9.8239412 9.8239412 0 1 1 428.77151 444.83228 z" + sodipodi:ry="9.8239412" + sodipodi:rx="9.8239412" + sodipodi:cy="444.83228" + sodipodi:cx="418.94757" + id="path14711" + style="opacity:1;fill:url(#linearGradient14724);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + transform="matrix(0.908653,0,0,0.908654,-359.556,-386.6038)" + d="M 429.51763 446.44885 A 11.005301 11.005301 0 1 1 407.50703,446.44885 A 11.005301 11.005301 0 1 1 429.51763 446.44885 z" + sodipodi:ry="11.005301" + sodipodi:rx="11.005301" + sodipodi:cy="446.44885" + sodipodi:cx="418.51233" + id="path14714" + style="opacity:1;fill:url(#radialGradient14726);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + sodipodi:nodetypes="csssc" + id="path14716" + d="M 30.226478,19.072457 C 30.226478,24.316478 25.970475,19.68155 20.726479,19.68155 C 15.482481,19.68155 11.226478,24.316478 11.226478,19.072457 C 11.226478,13.828473 15.482481,9.5724574 20.726479,9.5724574 C 25.970475,9.5724574 30.226478,13.828473 30.226478,19.072457 z " + style="opacity:0.7;fill:url(#linearGradient14728);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" /> + </g> + <text + xml:space="preserve" + style="font-size:28.97274971px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-indent:0;text-align:center;text-decoration:none;line-height:100%;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;text-anchor:middle;opacity:1;color:#000000;fill:url(#linearGradient14730);fill-opacity:1;fill-rule:nonzero;stroke:url(#linearGradient14732);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;font-family:Verdana" + x="-60.640533" + y="603.2287" + id="text14718" + sodipodi:linespacing="100%" + transform="scale(0.9998792,1.0001208)"><tspan + sodipodi:role="line" + id="tspan14720" + x="-60.640533" + y="603.2287" + style="fill:url(#linearGradient14730);fill-opacity:1;stroke:url(#linearGradient14732)">lng</tspan></text> + </g> + <g + transform="matrix(0.5,0,0,0.5,-89.95859,-237.28101)" + id="g14734" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/32x32/actions/amarok_change_language.png" + inkscape:export-xdpi="89.999962" + inkscape:export-ydpi="89.999962"> + <g + inkscape:export-ydpi="89.999962" + inkscape:export-xdpi="89.999962" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/64x64/actions/amarok_circle.png" + transform="matrix(2.90909,0,0,2.90909,-120.9284,539.8284)" + id="g14736"> + <path + sodipodi:type="arc" + style="opacity:1;fill:url(#linearGradient14750);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path14738" + sodipodi:cx="366.7616" + sodipodi:cy="436.4032" + sodipodi:rx="10.639691" + sodipodi:ry="10.639691" + d="M 377.40129 436.4032 A 10.639691 10.639691 0 1 1 356.12191,436.4032 A 10.639691 10.639691 0 1 1 377.40129 436.4032 z" + transform="matrix(1.033865,0,0,1.033865,-358.4555,-432.1183)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:type="arc" + style="opacity:1;fill:url(#linearGradient14752);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path14740" + sodipodi:cx="418.94757" + sodipodi:cy="444.83228" + sodipodi:rx="9.8239412" + sodipodi:ry="9.8239412" + d="M 428.77151 444.83228 A 9.8239412 9.8239412 0 1 1 409.12363,444.83228 A 9.8239412 9.8239412 0 1 1 428.77151 444.83228 z" + transform="matrix(1.068817,0,0,1.068817,-427.0518,-456.3806)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:type="arc" + style="opacity:1;fill:url(#radialGradient14754);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path14742" + sodipodi:cx="418.51233" + sodipodi:cy="446.44885" + sodipodi:rx="11.005301" + sodipodi:ry="11.005301" + d="M 429.51763 446.44885 A 11.005301 11.005301 0 1 1 407.50703,446.44885 A 11.005301 11.005301 0 1 1 429.51763 446.44885 z" + transform="matrix(0.908653,0,0,0.908654,-359.556,-386.6038)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + style="opacity:0.7;fill:url(#linearGradient14756);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 30.226478,19.072457 C 30.226478,24.316478 25.970475,19.68155 20.726479,19.68155 C 15.482481,19.68155 11.226478,24.316478 11.226478,19.072457 C 11.226478,13.828473 15.482481,9.5724574 20.726479,9.5724574 C 25.970475,9.5724574 30.226478,13.828473 30.226478,19.072457 z " + id="path14744" + sodipodi:nodetypes="csssc" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" /> + </g> + <text + transform="scale(0.9998792,1.0001208)" + sodipodi:linespacing="100%" + id="text14746" + y="603.2287" + x="-60.640533" + style="font-size:28.97274971px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-indent:0;text-align:center;text-decoration:none;line-height:100%;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;text-anchor:middle;opacity:1;color:#000000;fill:url(#linearGradient14758);fill-opacity:1;fill-rule:nonzero;stroke:url(#linearGradient14760);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;font-family:Verdana" + xml:space="preserve"><tspan + style="fill:url(#linearGradient14758);fill-opacity:1;stroke:url(#linearGradient14760)" + y="603.2287" + x="-60.640533" + id="tspan14748" + sodipodi:role="line">lng</tspan></text> + </g> + <g + id="g14762" + transform="matrix(0.34375,0,0,0.34375,-142.43253,-139.26751)" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/22x22/actions/amarok_change_language.png" + inkscape:export-xdpi="89.999962" + inkscape:export-ydpi="89.999962"> + <g + id="g14764" + transform="matrix(2.90909,0,0,2.90909,-120.9284,539.8284)" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/64x64/actions/amarok_circle.png" + inkscape:export-xdpi="89.999962" + inkscape:export-ydpi="89.999962"> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + transform="matrix(1.033865,0,0,1.033865,-358.4555,-432.1183)" + d="M 377.40129 436.4032 A 10.639691 10.639691 0 1 1 356.12191,436.4032 A 10.639691 10.639691 0 1 1 377.40129 436.4032 z" + sodipodi:ry="10.639691" + sodipodi:rx="10.639691" + sodipodi:cy="436.4032" + sodipodi:cx="366.7616" + id="path14766" + style="opacity:1;fill:url(#linearGradient14778);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + transform="matrix(1.068817,0,0,1.068817,-427.0518,-456.3806)" + d="M 428.77151 444.83228 A 9.8239412 9.8239412 0 1 1 409.12363,444.83228 A 9.8239412 9.8239412 0 1 1 428.77151 444.83228 z" + sodipodi:ry="9.8239412" + sodipodi:rx="9.8239412" + sodipodi:cy="444.83228" + sodipodi:cx="418.94757" + id="path14768" + style="opacity:1;fill:url(#linearGradient14780);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + transform="matrix(0.908653,0,0,0.908654,-359.556,-386.6038)" + d="M 429.51763 446.44885 A 11.005301 11.005301 0 1 1 407.50703,446.44885 A 11.005301 11.005301 0 1 1 429.51763 446.44885 z" + sodipodi:ry="11.005301" + sodipodi:rx="11.005301" + sodipodi:cy="446.44885" + sodipodi:cx="418.51233" + id="path14770" + style="opacity:1;fill:url(#radialGradient14782);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + sodipodi:nodetypes="csssc" + id="path14772" + d="M 30.226478,19.072457 C 30.226478,24.316478 25.970475,19.68155 20.726479,19.68155 C 15.482481,19.68155 11.226478,24.316478 11.226478,19.072457 C 11.226478,13.828473 15.482481,9.5724574 20.726479,9.5724574 C 25.970475,9.5724574 30.226478,13.828473 30.226478,19.072457 z " + style="opacity:0.7;fill:url(#linearGradient14784);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" /> + </g> + <text + xml:space="preserve" + style="font-size:28.97274971px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-indent:0;text-align:center;text-decoration:none;line-height:100%;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;text-anchor:middle;opacity:1;color:#000000;fill:url(#linearGradient14786);fill-opacity:1;fill-rule:nonzero;stroke:url(#linearGradient14788);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;font-family:Verdana" + x="-60.640533" + y="603.2287" + id="text14774" + sodipodi:linespacing="100%" + transform="scale(0.9998792,1.0001208)"><tspan + sodipodi:role="line" + id="tspan14776" + x="-60.640533" + y="603.2287" + style="fill:url(#linearGradient14786);fill-opacity:1;stroke:url(#linearGradient14788)">lng</tspan></text> + </g> + <g + transform="matrix(0.25,0,0,0.25,-186.1169,-80.459414)" + id="g14790" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/16x16/actions/amarok_change_language.png" + inkscape:export-xdpi="89.999962" + inkscape:export-ydpi="89.999962"> + <g + inkscape:export-ydpi="89.999962" + inkscape:export-xdpi="89.999962" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/64x64/actions/amarok_circle.png" + transform="matrix(2.90909,0,0,2.90909,-120.9284,539.8284)" + id="g14792"> + <path + sodipodi:type="arc" + style="opacity:1;fill:url(#linearGradient14806);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path14794" + sodipodi:cx="366.7616" + sodipodi:cy="436.4032" + sodipodi:rx="10.639691" + sodipodi:ry="10.639691" + d="M 377.40129 436.4032 A 10.639691 10.639691 0 1 1 356.12191,436.4032 A 10.639691 10.639691 0 1 1 377.40129 436.4032 z" + transform="matrix(1.033865,0,0,1.033865,-358.4555,-432.1183)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:type="arc" + style="opacity:1;fill:url(#linearGradient14808);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path14796" + sodipodi:cx="418.94757" + sodipodi:cy="444.83228" + sodipodi:rx="9.8239412" + sodipodi:ry="9.8239412" + d="M 428.77151 444.83228 A 9.8239412 9.8239412 0 1 1 409.12363,444.83228 A 9.8239412 9.8239412 0 1 1 428.77151 444.83228 z" + transform="matrix(1.068817,0,0,1.068817,-427.0518,-456.3806)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:type="arc" + style="opacity:1;fill:url(#radialGradient14810);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path14798" + sodipodi:cx="418.51233" + sodipodi:cy="446.44885" + sodipodi:rx="11.005301" + sodipodi:ry="11.005301" + d="M 429.51763 446.44885 A 11.005301 11.005301 0 1 1 407.50703,446.44885 A 11.005301 11.005301 0 1 1 429.51763 446.44885 z" + transform="matrix(0.908653,0,0,0.908654,-359.556,-386.6038)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + style="opacity:0.7;fill:url(#linearGradient14812);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 30.226478,19.072457 C 30.226478,24.316478 25.970475,19.68155 20.726479,19.68155 C 15.482481,19.68155 11.226478,24.316478 11.226478,19.072457 C 11.226478,13.828473 15.482481,9.5724574 20.726479,9.5724574 C 25.970475,9.5724574 30.226478,13.828473 30.226478,19.072457 z " + id="path14800" + sodipodi:nodetypes="csssc" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" /> + </g> + <text + transform="scale(0.9998792,1.0001208)" + sodipodi:linespacing="100%" + id="text14802" + y="603.2287" + x="-60.640533" + style="font-size:28.97274971px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-indent:0;text-align:center;text-decoration:none;line-height:100%;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;text-anchor:middle;opacity:1;color:#000000;fill:url(#linearGradient14814);fill-opacity:1;fill-rule:nonzero;stroke:url(#linearGradient14816);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;font-family:Verdana" + xml:space="preserve"><tspan + style="fill:url(#linearGradient14814);fill-opacity:1;stroke:url(#linearGradient14816)" + y="603.2287" + x="-60.640533" + id="tspan14804" + sodipodi:role="line">lng</tspan></text> + </g> + <g + id="g14048" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/64x64/actions/amarok_magnatune.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" + transform="translate(-143.5679,-403.79168)"> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/vadim/collection.png" + transform="matrix(3.058564,0,0,3.05856,1350.6069,492.0547)" + id="g13276"> + <path + sodipodi:type="arc" + style="opacity:1;fill:url(#linearGradient13865);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path13278" + sodipodi:cx="418.94757" + sodipodi:cy="444.83228" + sodipodi:rx="9.8239412" + sodipodi:ry="9.8239412" + d="M 428.77151 444.83228 A 9.8239412 9.8239412 0 1 1 409.12363,444.83228 A 9.8239412 9.8239412 0 1 1 428.77151 444.83228 z" + transform="matrix(1.064993,0,0,1.064994,-425.4498,-454.68)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:type="arc" + style="opacity:1;fill:url(#radialGradient13867);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path13280" + sodipodi:cx="418.51233" + sodipodi:cy="446.44885" + sodipodi:rx="11.005301" + sodipodi:ry="11.005301" + d="M 429.51763 446.44885 A 11.005301 11.005301 0 1 1 407.50703,446.44885 A 11.005301 11.005301 0 1 1 429.51763 446.44885 z" + transform="matrix(0.883732,0,0,0.900587,-349.1263,-383.0023)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + style="opacity:0.7;fill:url(#linearGradient13869);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 29.686411,20.220753 C 29.686411,25.937399 25.672358,20.884742 20.726478,20.884742 C 15.780598,20.884742 11.766545,25.937399 11.766545,20.220753 C 11.766545,14.504147 15.780598,9.8645522 20.726478,9.8645522 C 25.672358,9.8645522 29.686411,14.504147 29.686411,20.220753 z " + id="path13282" + sodipodi:nodetypes="csssc" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" /> + </g> + <g + transform="translate(-102.66893,31.047657)" + id="g13847"> + <rect + rx="3.5" + ry="3.1363637" + y="516.06451" + x="1524.6689" + height="23" + width="7" + id="rect13827" + style="fill:url(#linearGradient13853);fill-opacity:1;stroke:url(#linearGradient13855);stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + style="fill:url(#linearGradient13857);fill-opacity:1;stroke:url(#linearGradient13859);stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect13829" + width="7" + height="23" + x="1501.6689" + y="516.06451" + ry="3.1363637" + rx="3.5" /> + <rect + rx="3.5" + ry="4.5" + y="499.56454" + x="1513.5" + height="33" + width="7" + id="rect13831" + style="fill:url(#linearGradient13861);fill-opacity:1;stroke:url(#linearGradient13863);stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + </g> + <g + id="g14038" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/48x48/actions/amarok_magnatune.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" + transform="translate(-143.5679,-402.79169)"> + <g + id="g13871" + transform="matrix(2.2939225,0,0,2.2939195,1280.4552,513.63159)" + inkscape:export-filename="/home/vadim/collection.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + transform="matrix(1.064993,0,0,1.064994,-425.4498,-454.68)" + d="M 428.77151 444.83228 A 9.8239412 9.8239412 0 1 1 409.12363,444.83228 A 9.8239412 9.8239412 0 1 1 428.77151 444.83228 z" + sodipodi:ry="9.8239412" + sodipodi:rx="9.8239412" + sodipodi:cy="444.83228" + sodipodi:cx="418.94757" + id="path13873" + style="opacity:1;fill:url(#linearGradient13887);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + transform="matrix(0.883732,0,0,0.900587,-349.1263,-383.0023)" + d="M 429.51763 446.44885 A 11.005301 11.005301 0 1 1 407.50703,446.44885 A 11.005301 11.005301 0 1 1 429.51763 446.44885 z" + sodipodi:ry="11.005301" + sodipodi:rx="11.005301" + sodipodi:cy="446.44885" + sodipodi:cx="418.51233" + id="path13875" + style="opacity:1;fill:url(#radialGradient13889);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + sodipodi:nodetypes="csssc" + id="path13877" + d="M 29.686411,20.220753 C 29.686411,25.937399 25.672358,20.884742 20.726478,20.884742 C 15.780598,20.884742 11.766545,25.937399 11.766545,20.220753 C 11.766545,14.504147 15.780598,9.8645522 20.726478,9.8645522 C 25.672358,9.8645522 29.686411,14.504147 29.686411,20.220753 z " + style="opacity:0.7;fill:url(#linearGradient13891);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" /> + </g> + <g + id="g13879" + transform="matrix(0.7499998,0,0,0.7499998,190.49857,167.87639)"> + <rect + style="fill:url(#linearGradient13893);fill-opacity:1;stroke:url(#linearGradient13895);stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect13881" + width="7" + height="23" + x="1524.6689" + y="516.06451" + ry="3.1363637" + rx="3.5" /> + <rect + rx="3.5" + ry="3.1363637" + y="516.06451" + x="1501.6689" + height="23" + width="7" + id="rect13883" + style="fill:url(#linearGradient13897);fill-opacity:1;stroke:url(#linearGradient13899);stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + style="fill:url(#linearGradient13901);fill-opacity:1;stroke:url(#linearGradient13903);stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect13885" + width="7" + height="33" + x="1513.5" + y="499.56454" + ry="4.5" + rx="3.5" /> + </g> + </g> + <g + id="g14028" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/32x32/actions/amarok_magnatune.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" + transform="translate(-143.5679,-405.79167)"> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/vadim/collection.png" + transform="matrix(1.5292816,0,0,1.5292796,1245.3035,539.20844)" + id="g13905"> + <path + sodipodi:type="arc" + style="opacity:1;fill:url(#linearGradient13921);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path13907" + sodipodi:cx="418.94757" + sodipodi:cy="444.83228" + sodipodi:rx="9.8239412" + sodipodi:ry="9.8239412" + d="M 428.77151 444.83228 A 9.8239412 9.8239412 0 1 1 409.12363,444.83228 A 9.8239412 9.8239412 0 1 1 428.77151 444.83228 z" + transform="matrix(1.064993,0,0,1.064994,-425.4498,-454.68)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:type="arc" + style="opacity:1;fill:url(#radialGradient13923);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path13909" + sodipodi:cx="418.51233" + sodipodi:cy="446.44885" + sodipodi:rx="11.005301" + sodipodi:ry="11.005301" + d="M 429.51763 446.44885 A 11.005301 11.005301 0 1 1 407.50703,446.44885 A 11.005301 11.005301 0 1 1 429.51763 446.44885 z" + transform="matrix(0.883732,0,0,0.900587,-349.1263,-383.0023)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + style="opacity:0.7;fill:url(#linearGradient13925);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 29.686411,20.220753 C 29.686411,25.937399 25.672358,20.884742 20.726478,20.884742 C 15.780598,20.884742 11.766545,25.937399 11.766545,20.220753 C 11.766545,14.504147 15.780598,9.8645522 20.726478,9.8645522 C 25.672358,9.8645522 29.686411,14.504147 29.686411,20.220753 z " + id="path13911" + sodipodi:nodetypes="csssc" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" /> + </g> + <g + transform="matrix(0.4999999,0,0,0.4999999,518.66574,308.70497)" + id="g13913"> + <rect + rx="3.5" + ry="3.1363637" + y="516.06451" + x="1524.6689" + height="23" + width="7" + id="rect13915" + style="fill:url(#linearGradient13927);fill-opacity:1;stroke:url(#linearGradient13929);stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + style="fill:url(#linearGradient13931);fill-opacity:1;stroke:url(#linearGradient13933);stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect13917" + width="7" + height="23" + x="1501.6689" + y="516.06451" + ry="3.1363637" + rx="3.5" /> + <rect + rx="3.5" + ry="4.5" + y="499.56454" + x="1513.5" + height="33" + width="7" + id="rect13919" + style="fill:url(#linearGradient13935);fill-opacity:1;stroke:url(#linearGradient13937);stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + </g> + <g + id="g14018" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/22x22/actions/amarok_magnatune.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" + transform="translate(-143.5679,-403.79168)"> + <g + id="g13939" + transform="matrix(1.0513811,0,0,1.0513798,1220.2087,551.31899)" + inkscape:export-filename="/home/vadim/collection.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + transform="matrix(1.064993,0,0,1.064994,-425.4498,-454.68)" + d="M 428.77151 444.83228 A 9.8239412 9.8239412 0 1 1 409.12363,444.83228 A 9.8239412 9.8239412 0 1 1 428.77151 444.83228 z" + sodipodi:ry="9.8239412" + sodipodi:rx="9.8239412" + sodipodi:cy="444.83228" + sodipodi:cx="418.94757" + id="path13941" + style="opacity:1;fill:url(#linearGradient13955);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + transform="matrix(0.883732,0,0,0.900587,-349.1263,-383.0023)" + d="M 429.51763 446.44885 A 11.005301 11.005301 0 1 1 407.50703,446.44885 A 11.005301 11.005301 0 1 1 429.51763 446.44885 z" + sodipodi:ry="11.005301" + sodipodi:rx="11.005301" + sodipodi:cy="446.44885" + sodipodi:cx="418.51233" + id="path13943" + style="opacity:1;fill:url(#radialGradient13957);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + sodipodi:nodetypes="csssc" + id="path13945" + d="M 29.686411,20.220753 C 29.686411,25.937399 25.672358,20.884742 20.726478,20.884742 C 15.780598,20.884742 11.766545,25.937399 11.766545,20.220753 C 11.766545,14.504147 15.780598,9.8645522 20.726478,9.8645522 C 25.672358,9.8645522 29.686411,14.504147 29.686411,20.220753 z " + style="opacity:0.7;fill:url(#linearGradient13959);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" /> + </g> + <g + id="g13947" + transform="matrix(0.3437499,0,0,0.3437499,720.6452,392.84785)"> + <rect + style="fill:url(#linearGradient13961);fill-opacity:1;stroke:url(#linearGradient13963);stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect13949" + width="7" + height="23" + x="1524.6689" + y="516.06451" + ry="3.1363637" + rx="3.5" /> + <rect + rx="3.5" + ry="3.1363637" + y="516.06451" + x="1501.6689" + height="23" + width="7" + id="rect13951" + style="fill:url(#linearGradient13965);fill-opacity:1;stroke:url(#linearGradient13967);stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + style="fill:url(#linearGradient13969);fill-opacity:1;stroke:url(#linearGradient13971);stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect13953" + width="7" + height="33" + x="1513.5" + y="499.56454" + ry="4.5" + rx="3.5" /> + </g> + </g> + <g + id="g14008" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/16x16/actions/amarok_magnatune.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" + transform="translate(-143.5679,-408.79168)"> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/vadim/collection.png" + transform="matrix(0.7646408,0,0,0.7646399,1192.1517,564.78531)" + id="g13974"> + <path + sodipodi:type="arc" + style="opacity:1;fill:url(#linearGradient13990);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path13976" + sodipodi:cx="418.94757" + sodipodi:cy="444.83228" + sodipodi:rx="9.8239412" + sodipodi:ry="9.8239412" + d="M 428.77151 444.83228 A 9.8239412 9.8239412 0 1 1 409.12363,444.83228 A 9.8239412 9.8239412 0 1 1 428.77151 444.83228 z" + transform="matrix(1.064993,0,0,1.064994,-425.4498,-454.68)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + sodipodi:type="arc" + style="opacity:1;fill:url(#radialGradient13992);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path13978" + sodipodi:cx="418.51233" + sodipodi:cy="446.44885" + sodipodi:rx="11.005301" + sodipodi:ry="11.005301" + d="M 429.51763 446.44885 A 11.005301 11.005301 0 1 1 407.50703,446.44885 A 11.005301 11.005301 0 1 1 429.51763 446.44885 z" + transform="matrix(0.883732,0,0,0.900587,-349.1263,-383.0023)" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + <path + style="opacity:0.7;fill:url(#linearGradient13994);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 29.686411,20.220753 C 29.686411,25.937399 25.672358,20.884742 20.726478,20.884742 C 15.780598,20.884742 11.766545,25.937399 11.766545,20.220753 C 11.766545,14.504147 15.780598,9.8645522 20.726478,9.8645522 C 25.672358,9.8645522 29.686411,14.504147 29.686411,20.220753 z " + id="path13980" + sodipodi:nodetypes="csssc" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" + inkscape:export-filename="/home/vadim/amarok icons/newnew/zAmaroK2.png" /> + </g> + <g + transform="matrix(0.2499999,0,0,0.2499999,828.83289,449.5336)" + id="g13982"> + <rect + rx="3.5" + ry="3.1363637" + y="516.06451" + x="1524.6689" + height="23" + width="7" + id="rect13984" + style="fill:url(#linearGradient13996);fill-opacity:1;stroke:url(#linearGradient13998);stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + style="fill:url(#linearGradient14000);fill-opacity:1;stroke:url(#linearGradient14002);stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect13986" + width="7" + height="23" + x="1501.6689" + y="516.06451" + ry="3.1363637" + rx="3.5" /> + <rect + rx="3.5" + ry="4.5" + y="499.56454" + x="1513.5" + height="33" + width="7" + id="rect13988" + style="fill:url(#linearGradient14004);fill-opacity:1;stroke:url(#linearGradient14006);stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + </g> + <g + id="g13920" + transform="translate(-40.999913,-5.3760363)"> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/64x64/actions/amarok_external.png" + id="g8897" + transform="matrix(1.0050456,0,0,1.0290106,1248.9065,-2.7489379)"> + <path + d="M 45.936,27.94 L 45.233,26.783 L 44.282,32.5 L 47.689,30.98 L 45.936,27.94 z " + style="fill:#ffffff" + id="path8899" /> + <path + d="M 85.14,50.286 L 82.531,50.264 L 81.42,50.308 L 81.646,54.354 L 85.14,50.286 z " + style="fill:#ffffff" + id="path8901" /> + <path + d="M 38.56,42.962 L 39.772,37.613 L 36.078,34.799 L 35.265,34.352 L 34.427,40.249 L 37.467,43.023 L 38.56,42.962 z " + style="fill:#ffffff" + id="path8903" /> + <path + d="M 39.6,59.891 L 42.767,56.869 L 39.487,50.521 L 32.926,49.88 L 32.218,43.364 L 32.13,43.361 L 31.005,49.145 L 31.67,55.573 L 37.786,56.202 L 39.6,59.891 z " + style="fill:#ffffff" + id="path8905" /> + <path + d="M 84.592,63.482 L 80.635,61.503 L 77.141,65.853 L 78.351,68.12 L 80.929,69.476 L 85.687,64.113 L 84.592,63.482 z " + style="fill:#ffffff" + id="path8907" /> + </g> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/64x64/actions/amarok_external.png" + d="M 1316.6322,36.047853 C 1308.8834,30.897643 1299.6098,31.438923 1295.9202,37.257963 C 1294.7485,39.107103 1294.2832,41.265953 1294.4379,43.515393 C 1297.3641,35.917323 1308.0955,35.109573 1314.9508,40.040603 C 1320.8576,44.287323 1324.0342,53.149033 1320.5501,59.126513 C 1321.9403,58.348573 1323.1172,57.276343 1323.9833,55.909803 C 1327.674,50.091803 1324.3824,41.199093 1316.6322,36.047853 z " + style="fill:url(#linearGradient14038);fill-opacity:1" + id="path8909" + sodipodi:nodetypes="cscscsc" /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/64x64/actions/amarok_external.png" + d="M 1287.1472,60.362353 L 1285.6139,65.938553 L 1290.8921,72.049863 L 1296.0359,68.239463 L 1302.3165,72.192883 L 1302.3828,79.670653 L 1309.0937,81.038243 L 1312.9652,75.775893 L 1313.1954,74.186093 L 1316.7478,74.062573 L 1320.3563,79.742703 L 1325.3661,77.011713 L 1329.7935,71.665043 L 1329.8763,71.620743 L 1327.6518,67.349363 L 1330.2427,68.744673 L 1335.0852,63.546123 L 1335.8249,57.971973 L 1331.0219,54.303543 L 1330.9615,53.182953 L 1334.3906,48.873443 L 1331.9533,43.809653 L 1327.9403,42.929883 L 1324.3572,38.086343 L 1326.077,35.738123 L 1321.9936,31.846373 L 1318.9122,33.386853 L 1313.751,29.791473 L 1313.6784,27.148983 L 1308.0886,24.726703 L 1306.7979,28.175883 L 1302.2112,27.295063 L 1299.7026,24.066083 L 1294.1271,24.780203 L 1293.4119,30.692863 L 1292.1764,31.256753 L 1287.3024,28.761413 L 1284.1593,32.497783 L 1283.5081,38.664633 L 1286.5636,41.519093 L 1280.9771,41.673453 L 1280.0686,47.819733 L 1280.7374,54.434193 L 1286.8841,55.081483 L 1288.7063,58.876483 L 1287.1472,60.362353 z M 1323.9833,55.910853 C 1320.2928,61.730943 1311.0205,62.274233 1303.2704,57.123043 C 1295.5202,51.971793 1292.2299,43.080103 1295.9202,37.258983 C 1299.6098,31.439933 1308.8834,30.897643 1316.6322,36.048863 C 1324.3824,41.199093 1327.674,50.091803 1323.9833,55.910853 z " + style="fill:url(#linearGradient14040)" + id="path8911" /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/64x64/actions/amarok_external.png" + d="M 1330.6772,54.233583 L 1330.3947,49.031893 L 1333.9938,48.886813 L 1331.5938,43.901263 L 1327.6418,43.034843 L 1324.1131,38.265383 L 1325.8066,35.953183 L 1321.7844,32.123193 L 1318.7501,33.641023 L 1313.6683,30.101163 L 1313.5981,27.499843 L 1308.0928,25.116713 L 1306.8228,28.511383 L 1302.3076,27.644933 L 1299.837,24.464273 L 1294.6157,25.187703 L 1297.0148,29.450883 L 1292.4288,31.546973 L 1287.6299,29.089663 L 1284.7374,32.702533 L 1289.1826,36.171333 L 1287.9835,41.590093 L 1281.7019,41.951303 L 1282.4087,48.599733 L 1288.9001,49.249033 L 1292.1453,55.678283 L 1287.5595,60.158603 L 1293.1341,66.299763 L 1298.568,62.469783 L 1305.2009,66.442803 L 1305.2713,73.957613 L 1312.8928,75.402363 L 1313.8818,68.538843 L 1320.4436,68.321773 L 1324.2537,74.029633 L 1329.5471,71.284283 L 1326.1602,64.781933 L 1329.6169,60.374693 L 1334.6991,62.976043 L 1335.4036,57.846423 L 1330.6772,54.233583 z M 1323.7442,55.819273 C 1320.1131,61.548793 1310.9812,62.082853 1303.3517,57.011883 C 1295.7211,51.939863 1292.4811,43.185063 1296.1143,37.454503 C 1299.7476,31.723953 1308.8774,31.189873 1316.5088,36.261893 C 1324.1382,41.333863 1327.3771,50.089743 1323.7442,55.819273 z " + style="fill:url(#linearGradient14042);fill-opacity:1" + id="path8913" /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/64x64/actions/amarok_external.png" + d="M 1299.618,23.375633 L 1294.0427,24.089743 C 1293.7335,24.129853 1293.4903,24.378883 1293.4509,24.696823 C 1293.4509,24.696823 1292.8658,29.536273 1292.7835,30.218493 C 1292.5433,30.327583 1292.4077,30.390373 1292.1938,30.487063 C 1291.6832,30.226713 1287.6078,28.139883 1287.6078,28.139883 C 1287.3284,27.996873 1286.9906,28.066853 1286.7867,28.309663 L 1283.6436,32.047033 C 1283.5534,32.154053 1283.4969,32.285783 1283.483,32.426763 L 1282.8315,38.593613 C 1282.8295,38.618303 1282.8276,38.643013 1282.8276,38.668743 C 1282.8276,38.863233 1282.9081,39.050533 1283.0496,39.183233 C 1283.0496,39.183233 1283.9833,40.055853 1284.8567,40.871833 C 1283.2126,40.917113 1280.9602,40.979923 1280.9602,40.979923 C 1280.6295,40.988153 1280.3531,41.238163 1280.3038,41.572603 L 1279.3966,47.717903 C 1279.3913,47.751823 1279.3881,47.787823 1279.3881,47.822793 C 1279.3881,47.846473 1279.3892,47.871163 1279.3922,47.894843 L 1280.0606,54.509323 C 1280.0939,54.837583 1280.3481,55.096943 1280.6699,55.130883 C 1280.6699,55.130883 1285.7785,55.668023 1286.4411,55.738993 C 1286.6782,56.233963 1287.5657,58.078973 1287.8731,58.721093 C 1287.442,59.130653 1286.6859,59.851953 1286.6859,59.851953 C 1286.5947,59.938393 1286.5284,60.049503 1286.4953,60.174053 L 1284.9602,65.751283 C 1284.9433,65.814053 1284.9351,65.876843 1284.9351,65.939583 C 1284.9351,66.108363 1284.9954,66.273003 1285.1058,66.402643 L 1290.3837,72.512903 C 1290.6157,72.782523 1291.0098,72.826773 1291.2941,72.617883 C 1291.2941,72.617883 1295.434,69.551393 1296.0688,69.080123 C 1296.7156,69.486573 1301.1005,72.246413 1301.6411,72.588013 C 1301.6473,73.281583 1301.7044,79.678903 1301.7044,79.678903 C 1301.7065,80.009243 1301.9355,80.293243 1302.2523,80.357083 L 1308.9632,81.724593 C 1309.2203,81.777073 1309.4836,81.674153 1309.6413,81.459133 L 1313.5129,76.197793 C 1313.581,76.104143 1313.6253,75.996103 1313.6434,75.879803 C 1313.6434,75.879803 1313.7218,75.334453 1313.789,74.866253 C 1314.4875,74.841563 1315.8877,74.793193 1316.3901,74.774683 C 1316.7618,75.361193 1319.7885,80.124463 1319.7885,80.124463 C 1319.9803,80.427023 1320.3673,80.529923 1320.6797,80.360143 L 1325.6909,77.628103 C 1325.7654,77.587963 1325.8347,77.530363 1325.8898,77.463483 C 1325.8898,77.463483 1330.1444,72.325633 1330.2589,72.187733 C 1330.3835,72.100283 1330.485,71.980903 1330.5302,71.830683 C 1330.5515,71.762773 1330.5615,71.690733 1330.5615,71.620743 C 1330.5615,71.507573 1330.5343,71.395393 1330.482,71.293543 C 1330.482,71.293543 1329.8882,70.154433 1329.2991,69.023533 C 1329.5323,69.149113 1329.9314,69.364123 1329.9314,69.364123 C 1330.2006,69.508193 1330.5323,69.452633 1330.7414,69.227303 L 1335.5828,64.028703 C 1335.6831,63.921713 1335.7455,63.786893 1335.7657,63.641823 L 1336.5055,58.067653 C 1336.5094,58.035743 1336.5115,58.003863 1336.5115,57.973003 C 1336.5115,57.753823 1336.4105,57.545923 1336.2388,57.413193 C 1336.2388,57.413193 1332.1485,54.289143 1331.6914,53.939273 C 1331.6793,53.723183 1331.6731,53.606903 1331.663,53.414453 C 1331.9645,53.035823 1334.9263,49.313853 1334.9263,49.313853 C 1335.0269,49.187293 1335.079,49.030883 1335.079,48.873443 C 1335.079,48.768483 1335.0559,48.662493 1335.0089,48.564763 L 1332.5716,43.500993 C 1332.4799,43.309583 1332.306,43.171663 1332.1033,43.126413 C 1332.1033,43.126413 1328.7723,42.396833 1328.3362,42.301153 C 1328.0809,41.956443 1325.7101,38.750003 1325.2164,38.083233 C 1325.6315,37.516243 1326.6285,36.155903 1326.6285,36.155903 C 1326.719,36.031373 1326.7643,35.883213 1326.7643,35.737093 C 1326.7643,35.548793 1326.6909,35.362533 1326.5471,35.225663 L 1322.4617,31.337023 C 1322.2557,31.139473 1321.9522,31.094173 1321.6987,31.220733 C 1321.6987,31.220733 1319.5291,32.305323 1318.9723,32.584203 C 1318.4336,32.208613 1314.906,29.751333 1314.4274,29.417943 C 1314.4123,28.882853 1314.3638,27.131493 1314.3638,27.131493 C 1314.357,26.858813 1314.1952,26.614903 1313.9501,26.508933 L 1308.359,24.086633 C 1308.1881,24.012573 1307.9963,24.012573 1307.8262,24.086633 C 1307.6564,24.160733 1307.5227,24.302773 1307.4564,24.479703 C 1307.4564,24.479703 1306.6592,26.613873 1306.3698,27.384593 C 1305.5607,27.229213 1302.9799,26.734283 1302.5908,26.659153 C 1302.3136,26.301053 1300.2402,23.633873 1300.2402,23.633873 C 1300.0875,23.442473 1299.853,23.344733 1299.618,23.375633 z M 1299.4071,24.809033 C 1299.797,25.312203 1301.6772,27.730353 1301.6772,27.730353 C 1301.7787,27.862083 1301.9245,27.950563 1302.0855,27.982473 L 1306.6724,28.862283 C 1306.9961,28.925053 1307.3168,28.740823 1307.4345,28.427013 C 1307.4345,28.427013 1308.146,26.524363 1308.4715,25.652783 C 1309.4043,26.057163 1312.402,27.354743 1313.0093,27.617143 C 1313.0265,28.236613 1313.0697,29.811013 1313.0697,29.811013 C 1313.0758,30.035333 1313.1853,30.242143 1313.367,30.368753 L 1318.528,33.964113 C 1318.7289,34.105093 1318.9906,34.124633 1319.2105,34.013503 C 1319.2105,34.013503 1321.2445,32.996823 1321.872,32.684043 C 1322.3782,33.165593 1324.5218,35.206123 1325.1654,35.819403 C 1324.7101,36.440953 1323.8128,37.664423 1323.8128,37.664423 C 1323.7211,37.788933 1323.6758,37.936083 1323.6758,38.084293 C 1323.6758,38.232423 1323.722,38.380613 1323.8146,38.506163 L 1327.3983,43.349703 C 1327.4969,43.482483 1327.6377,43.575073 1327.7986,43.611073 C 1327.7986,43.611073 1330.9435,44.300513 1331.4889,44.419893 C 1331.7243,44.911733 1333.2811,48.140803 1333.5867,48.777723 C 1333.1193,49.365313 1330.4329,52.742513 1330.4329,52.742513 C 1330.3343,52.867023 1330.2799,53.022423 1330.2799,53.182953 C 1330.2799,53.196343 1330.2799,53.208663 1330.2811,53.222033 L 1330.3433,54.341583 C 1330.3542,54.547403 1330.4539,54.738793 1330.6159,54.862283 C 1330.6159,54.862283 1334.5355,57.857733 1335.0953,58.285783 C 1335.0068,58.954693 1334.49,62.847423 1334.4399,63.228153 C 1334.1746,63.512153 1330.6941,67.249543 1330.1053,67.881343 C 1329.487,67.548953 1327.9673,66.732953 1327.9673,66.732953 C 1327.7021,66.588863 1327.3782,66.640353 1327.1661,66.858503 C 1327.0365,66.992263 1326.9693,67.170303 1326.9693,67.350373 C 1326.9693,67.461473 1326.9956,67.574663 1327.0486,67.678623 C 1327.0486,67.678623 1328.6607,70.772853 1329.0379,71.496263 C 1328.6306,71.988103 1325.0829,76.273923 1324.9291,76.458113 C 1324.7344,76.565123 1321.4354,78.363843 1320.5853,78.827933 C 1320.0267,77.948123 1317.3177,73.683923 1317.3177,73.683923 C 1317.1883,73.479163 1316.9622,73.359783 1316.7227,73.365973 L 1313.1702,73.489423 C 1312.8413,73.501793 1312.567,73.751823 1312.5186,74.085193 C 1312.5186,74.085193 1312.3561,75.221263 1312.3168,75.499073 C 1312.1309,75.749153 1309.2425,79.676853 1308.8061,80.269583 C 1308.133,80.131683 1303.9136,79.271433 1303.0594,79.097513 C 1303.0503,78.128193 1302.9968,72.187733 1302.9968,72.187733 C 1302.9949,71.947993 1302.8722,71.725753 1302.6724,71.600173 L 1296.3911,67.646733 C 1296.1568,67.497503 1295.8569,67.508813 1295.6338,67.674503 C 1295.6338,67.674503 1291.8068,70.510453 1290.9906,71.114503 C 1290.3296,70.348903 1286.7796,66.239053 1286.3675,65.762603 C 1286.5376,65.146233 1287.659,61.068253 1287.7495,60.741053 C 1287.9806,60.519793 1289.1675,59.389913 1289.1675,59.389913 C 1289.3095,59.254083 1289.3847,59.067863 1289.3847,58.878513 C 1289.3847,58.773573 1289.3625,58.669623 1289.3152,58.570883 L 1287.4932,54.775863 C 1287.3897,54.560783 1287.1844,54.414653 1286.9513,54.390973 C 1286.9513,54.390973 1282.2697,53.898103 1281.3561,53.801373 C 1281.2617,52.871153 1280.7694,47.996743 1280.7531,47.840303 C 1280.7762,47.684923 1281.4249,43.292083 1281.5632,42.358773 C 1282.483,42.333043 1286.5796,42.219853 1286.5796,42.219853 C 1286.8569,42.212633 1287.1031,42.032583 1287.1995,41.766043 C 1287.2279,41.686823 1287.243,41.604513 1287.243,41.522203 C 1287.243,41.329753 1287.1643,41.141473 1287.0208,41.007693 C 1287.0208,41.007693 1284.6496,38.792223 1284.2206,38.390893 C 1284.2839,37.788933 1284.7723,33.170713 1284.8116,32.792023 C 1285.0356,32.526583 1286.9483,30.251423 1287.4702,29.631973 C 1288.2427,30.027103 1291.869,31.883443 1291.869,31.883443 C 1292.0521,31.977073 1292.2663,31.981203 1292.4509,31.896823 L 1293.6861,31.332913 C 1293.9042,31.233093 1294.056,31.024223 1294.085,30.780343 C 1294.085,30.780343 1294.6279,26.297943 1294.7353,25.406883 C 1295.5364,25.305003 1298.8139,24.884143 1299.4071,24.809033 z " + style="fill:url(#linearGradient14044);fill-opacity:1" + id="path8917" /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/64x64/actions/amarok_external.png" + d="M 1295.3485,36.879263 C 1291.4601,43.011163 1294.8468,52.355633 1302.8994,57.708523 C 1307.5005,60.765733 1312.9472,62.003633 1317.4685,61.018833 C 1320.5723,60.343833 1323.0228,58.707703 1324.5552,56.290563 C 1325.6326,54.591663 1326.1803,52.624183 1326.1803,50.499323 C 1326.1803,49.604053 1326.0829,48.681033 1325.8879,47.739503 C 1324.9261,43.109953 1321.6043,38.521593 1317.0032,35.462343 C 1308.951,30.111473 1299.2372,30.746393 1295.3485,36.879263 z M 1303.642,56.537483 C 1296.2199,51.604443 1293.0125,43.126413 1296.4914,37.638693 C 1299.9707,32.150993 1308.8393,31.700293 1316.2617,36.633343 C 1320.5652,39.495033 1323.6646,43.755143 1324.554,48.030683 C 1325.1371,50.838863 1324.743,53.431933 1323.4093,55.531133 C 1322.0788,57.631383 1319.9261,59.056503 1317.1833,59.654393 C 1313.0082,60.563013 1307.946,59.399173 1303.642,56.537483 z " + style="fill:url(#linearGradient14046);fill-opacity:1" + id="path8915" /> + <path + sodipodi:nodetypes="ccccccc" + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/64x64/actions/amarok_external.png" + id="path14100" + d="M 1311.5446,19.158382 L 1315.4877,25.208183 C 1306.346,33.364683 1299.232,52.417873 1302.2599,56.460823 C 1316.1581,63.849113 1305.5003,57.886513 1325.3844,40.387373 L 1331.4539,49.702973 L 1341.9679,20.390172 L 1311.5446,19.158382 z " + style="fill:url(#linearGradient14048);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient14050);stroke-width:2.84032607;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + <g + id="g13906" + transform="translate(-39.738926,-3.3760363)"> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/48x48/actions/amarok_external.png" + transform="matrix(0.7537846,0,0,0.7717584,1191.5269,16.372792)" + id="g14127"> + <path + id="path14129" + style="fill:#ffffff" + d="M 45.936,27.94 L 45.233,26.783 L 44.282,32.5 L 47.689,30.98 L 45.936,27.94 z " /> + <path + id="path14131" + style="fill:#ffffff" + d="M 85.14,50.286 L 82.531,50.264 L 81.42,50.308 L 81.646,54.354 L 85.14,50.286 z " /> + <path + id="path14133" + style="fill:#ffffff" + d="M 38.56,42.962 L 39.772,37.613 L 36.078,34.799 L 35.265,34.352 L 34.427,40.249 L 37.467,43.023 L 38.56,42.962 z " /> + <path + id="path14135" + style="fill:#ffffff" + d="M 39.6,59.891 L 42.767,56.869 L 39.487,50.521 L 32.926,49.88 L 32.218,43.364 L 32.13,43.361 L 31.005,49.145 L 31.67,55.573 L 37.786,56.202 L 39.6,59.891 z " /> + <path + id="path14138" + style="fill:#ffffff" + d="M 84.592,63.482 L 80.635,61.503 L 77.141,65.853 L 78.351,68.12 L 80.929,69.476 L 85.687,64.113 L 84.592,63.482 z " /> + </g> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/48x48/actions/amarok_external.png" + sodipodi:nodetypes="cscscsc" + id="path14140" + style="fill:url(#linearGradient14023);fill-opacity:1" + d="M 1242.3212,45.470393 C 1236.5096,41.607733 1229.5544,42.013703 1226.7872,46.377983 C 1225.9084,47.764833 1225.5594,49.383973 1225.6755,51.071053 C 1227.8701,45.372503 1235.9187,44.766683 1241.0601,48.464963 C 1245.4902,51.650003 1247.8727,58.296293 1245.2596,62.779403 C 1246.3023,62.195943 1247.1849,61.391773 1247.8345,60.366873 C 1250.6025,56.003363 1248.1338,49.333833 1242.3212,45.470393 z " /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/48x48/actions/amarok_external.png" + id="path14142" + style="fill:url(#linearGradient14025)" + d="M 1220.2074,63.706283 L 1219.0575,67.888433 L 1223.0161,72.471923 L 1226.874,69.614123 L 1231.5844,72.579183 L 1231.6341,78.187513 L 1236.6673,79.213213 L 1239.5709,75.266443 L 1239.7436,74.074093 L 1242.4079,73.981453 L 1245.1143,78.241553 L 1248.8716,76.193313 L 1252.1922,72.183303 L 1252.2543,72.150083 L 1250.5859,68.946543 L 1252.5291,69.993023 L 1256.1609,66.094113 L 1256.7157,61.913493 L 1253.1135,59.162173 L 1253.0682,58.321733 L 1255.64,55.089593 L 1253.812,51.291753 L 1250.8023,50.631923 L 1248.1149,46.999263 L 1249.4048,45.238103 L 1246.3422,42.319283 L 1244.0312,43.474643 L 1240.1603,40.778113 L 1240.1058,38.796243 L 1235.9135,36.979533 L 1234.9455,39.566413 L 1231.5054,38.905803 L 1229.624,36.484063 L 1225.4424,37.019653 L 1224.906,41.454153 L 1223.9793,41.877073 L 1220.3238,40.005563 L 1217.9665,42.807843 L 1217.4781,47.432983 L 1219.7697,49.573833 L 1215.5799,49.689603 L 1214.8985,54.299313 L 1215.4001,59.260163 L 1220.0101,59.745633 L 1221.3768,62.591883 L 1220.2074,63.706283 z M 1247.8345,60.367653 C 1245.0666,64.732723 1238.1124,65.140193 1232.2998,61.276803 C 1226.4872,57.413363 1224.0195,50.744593 1226.7872,46.378743 C 1229.5544,42.014453 1236.5096,41.607733 1242.3212,45.471153 C 1248.1338,49.333833 1250.6025,56.003363 1247.8345,60.367653 z " /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/48x48/actions/amarok_external.png" + id="path14144" + style="fill:url(#linearGradient14027);fill-opacity:1" + d="M 1252.8549,59.109703 L 1252.6431,55.208433 L 1255.3424,55.099623 L 1253.5424,51.360463 L 1250.5784,50.710643 L 1247.9319,47.133543 L 1249.202,45.399393 L 1246.1853,42.526903 L 1243.9096,43.665273 L 1240.0983,41.010373 L 1240.0456,39.059383 L 1235.9166,37.272033 L 1234.9641,39.818043 L 1231.5777,39.168203 L 1229.7248,36.782703 L 1225.8088,37.325283 L 1227.6081,40.522663 L 1224.1686,42.094733 L 1220.5695,40.251753 L 1218.4001,42.961403 L 1221.734,45.563003 L 1220.8347,49.627083 L 1216.1235,49.897983 L 1216.6536,54.884313 L 1221.5221,55.371293 L 1223.956,60.193233 L 1220.5167,63.553473 L 1224.6976,68.159343 L 1228.773,65.286853 L 1233.7477,68.266623 L 1233.8005,73.902733 L 1239.5166,74.986293 L 1240.2584,69.838653 L 1245.1797,69.675853 L 1248.0373,73.956753 L 1252.0074,71.897733 L 1249.4672,67.020973 L 1252.0597,63.715543 L 1255.8714,65.666553 L 1256.3997,61.819333 L 1252.8549,59.109703 z M 1247.6552,60.298973 C 1244.9319,64.596113 1238.0829,64.996663 1232.3608,61.193433 C 1226.6379,57.389413 1224.2079,50.823313 1226.9328,46.525383 C 1229.6577,42.227473 1236.5051,41.826913 1242.2286,45.630923 C 1247.9507,49.434903 1250.3799,56.001823 1247.6552,60.298973 z " /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/48x48/actions/amarok_external.png" + id="path14146" + style="fill:url(#linearGradient14029);fill-opacity:1" + d="M 1229.5605,35.966223 L 1225.3791,36.501813 C 1225.1472,36.531893 1224.9648,36.718663 1224.9352,36.957123 C 1224.9352,36.957123 1224.4964,40.586713 1224.4347,41.098373 C 1224.2545,41.180193 1224.1528,41.227283 1223.9924,41.299803 C 1223.6094,41.104543 1220.5529,39.539413 1220.5529,39.539413 C 1220.3433,39.432163 1220.09,39.484643 1219.9371,39.666753 L 1217.5797,42.469783 C 1217.5121,42.550043 1217.4697,42.648843 1217.4593,42.754583 L 1216.9707,47.379723 C 1216.9692,47.398233 1216.9677,47.416773 1216.9677,47.436063 C 1216.9677,47.581933 1217.0281,47.722413 1217.1342,47.821933 C 1217.1342,47.821933 1217.8345,48.476403 1218.4896,49.088383 C 1217.2565,49.122343 1215.5672,49.169453 1215.5672,49.169453 C 1215.3192,49.175623 1215.1119,49.363133 1215.0749,49.613963 L 1214.3945,54.222943 C 1214.3905,54.248383 1214.3881,54.275383 1214.3881,54.301603 C 1214.3881,54.319363 1214.3889,54.337883 1214.3912,54.355643 L 1214.8925,59.316513 C 1214.9175,59.562703 1215.1081,59.757223 1215.3495,59.782683 C 1215.3495,59.782683 1219.1809,60.185533 1219.6779,60.238763 C 1219.8557,60.609993 1220.5213,61.993743 1220.7519,62.475333 C 1220.4285,62.782503 1219.8615,63.323483 1219.8615,63.323483 C 1219.7931,63.388313 1219.7433,63.471643 1219.7185,63.565063 L 1218.5672,67.747983 C 1218.5545,67.795063 1218.5484,67.842153 1218.5484,67.889213 C 1218.5484,68.015793 1218.5936,68.139273 1218.6764,68.236503 L 1222.6348,72.819203 C 1222.8088,73.021413 1223.1044,73.054603 1223.3176,72.897933 C 1223.3176,72.897933 1226.4225,70.598063 1226.8986,70.244613 C 1227.3837,70.549453 1230.6724,72.619333 1231.0779,72.875533 C 1231.0825,73.395713 1231.1253,78.193703 1231.1253,78.193703 C 1231.1269,78.441463 1231.2987,78.654463 1231.5363,78.702343 L 1236.5694,79.727973 C 1236.7623,79.767333 1236.9597,79.690143 1237.078,79.528873 L 1239.9817,75.582873 C 1240.0328,75.512633 1240.066,75.431603 1240.0796,75.344373 C 1240.0796,75.344373 1240.1384,74.935363 1240.1888,74.584213 C 1240.7127,74.565693 1241.7628,74.529423 1242.1396,74.515533 C 1242.4184,74.955423 1244.6884,78.527873 1244.6884,78.527873 C 1244.8323,78.754793 1245.1225,78.831973 1245.3568,78.704633 L 1249.1152,76.655603 C 1249.1711,76.625503 1249.2231,76.582303 1249.2644,76.532143 C 1249.2644,76.532143 1252.4553,72.678753 1252.5412,72.575323 C 1252.6347,72.509733 1252.7108,72.420203 1252.7447,72.307533 C 1252.7607,72.256603 1252.7682,72.202573 1252.7682,72.150083 C 1252.7682,72.065203 1252.7478,71.981063 1252.7085,71.904683 C 1252.7085,71.904683 1252.2632,71.050343 1251.8214,70.202173 C 1251.9963,70.296353 1252.2956,70.457613 1252.2956,70.457613 C 1252.4975,70.565663 1252.7463,70.523993 1252.9031,70.355003 L 1256.5341,66.456043 C 1256.6094,66.375803 1256.6562,66.274693 1256.6713,66.165883 L 1257.2262,61.985253 C 1257.2291,61.961323 1257.2307,61.937413 1257.2307,61.914273 C 1257.2307,61.749883 1257.1549,61.593963 1257.0261,61.494413 C 1257.0261,61.494413 1253.9584,59.151373 1253.6156,58.888973 C 1253.6065,58.726903 1253.6019,58.639693 1253.5943,58.495353 C 1253.8204,58.211383 1256.0418,55.419903 1256.0418,55.419903 C 1256.1172,55.324983 1256.1563,55.207673 1256.1563,55.089593 C 1256.1563,55.010873 1256.139,54.931383 1256.1037,54.858083 L 1254.2757,51.060253 C 1254.207,50.916703 1254.0765,50.813263 1253.9245,50.779323 C 1253.9245,50.779323 1251.4263,50.232133 1251.0992,50.160373 C 1250.9077,49.901843 1249.1296,47.497013 1248.7593,46.996933 C 1249.0707,46.571693 1249.8184,45.551433 1249.8184,45.551433 C 1249.8863,45.458033 1249.9203,45.346913 1249.9203,45.237323 C 1249.9203,45.096103 1249.8652,44.956403 1249.7574,44.853753 L 1246.6933,41.937273 C 1246.5388,41.789113 1246.3112,41.755133 1246.1211,41.850053 C 1246.1211,41.850053 1244.4939,42.663503 1244.0763,42.872663 C 1243.6722,42.590963 1241.0265,40.748003 1240.6676,40.497963 C 1240.6563,40.096643 1240.6199,38.783123 1240.6199,38.783123 C 1240.6148,38.578613 1240.4934,38.395683 1240.3096,38.316203 L 1236.1163,36.499473 C 1235.9881,36.443933 1235.8443,36.443933 1235.7167,36.499473 C 1235.5893,36.555053 1235.4891,36.661583 1235.4393,36.794283 C 1235.4393,36.794283 1234.8414,38.394913 1234.6244,38.972953 C 1234.0176,38.856413 1232.082,38.485213 1231.7901,38.428873 C 1231.5822,38.160293 1230.0272,36.159903 1230.0272,36.159903 C 1229.9127,36.016353 1229.7368,35.943053 1229.5605,35.966223 z M 1229.4024,37.041273 C 1229.6948,37.418653 1231.1049,39.232273 1231.1049,39.232273 C 1231.1811,39.331063 1231.2904,39.397423 1231.4112,39.421363 L 1234.8513,40.081213 C 1235.0941,40.128293 1235.3346,39.990123 1235.4229,39.754763 C 1235.4229,39.754763 1235.9565,38.327773 1236.2007,37.674093 C 1236.9003,37.977373 1239.1485,38.950563 1239.604,39.147363 C 1239.6169,39.611963 1239.6493,40.792763 1239.6493,40.792763 C 1239.6539,40.961003 1239.736,41.116113 1239.8723,41.211073 L 1243.743,43.907593 C 1243.8937,44.013323 1244.09,44.027983 1244.2549,43.944633 C 1244.2549,43.944633 1245.7804,43.182123 1246.251,42.947543 C 1246.6307,43.308703 1248.2384,44.839103 1248.7211,45.299063 C 1248.3796,45.765223 1247.7066,46.682823 1247.7066,46.682823 C 1247.6379,46.776213 1247.6039,46.886573 1247.6039,46.997733 C 1247.6039,47.108823 1247.6385,47.219973 1247.708,47.314133 L 1250.3958,50.946793 C 1250.4697,51.046373 1250.5753,51.115813 1250.696,51.142813 C 1250.696,51.142813 1253.0547,51.659893 1253.4637,51.749433 C 1253.6403,52.118313 1254.8079,54.540113 1255.0371,55.017803 C 1254.6865,55.458503 1252.6717,57.991403 1252.6717,57.991403 C 1252.5978,58.084783 1252.557,58.201333 1252.557,58.321733 C 1252.557,58.331773 1252.557,58.341013 1252.5579,58.351043 L 1252.6045,59.190703 C 1252.6127,59.345073 1252.6875,59.488613 1252.809,59.581233 C 1252.809,59.581233 1255.7487,61.827813 1256.1685,62.148853 C 1256.1021,62.650533 1255.7145,65.570083 1255.677,65.855633 C 1255.478,66.068633 1252.8676,68.871683 1252.426,69.345533 C 1251.9623,69.096233 1250.8225,68.484233 1250.8225,68.484233 C 1250.6236,68.376173 1250.3807,68.414783 1250.2216,68.578403 C 1250.1244,68.678723 1250.074,68.812253 1250.074,68.947303 C 1250.074,69.030623 1250.0937,69.115523 1250.1335,69.193493 C 1250.1335,69.193493 1251.3426,71.514163 1251.6255,72.056723 C 1251.32,72.425603 1248.6592,75.639963 1248.5439,75.778113 C 1248.3978,75.858363 1245.9236,77.207413 1245.286,77.555473 C 1244.8671,76.895623 1242.8353,73.697463 1242.8353,73.697463 C 1242.7383,73.543893 1242.5687,73.454363 1242.3891,73.459003 L 1239.7247,73.551593 C 1239.478,73.560873 1239.2723,73.748393 1239.236,73.998423 C 1239.236,73.998423 1239.1141,74.850473 1239.0846,75.058833 C 1238.9452,75.246393 1236.7789,78.192163 1236.4516,78.636713 C 1235.9468,78.533293 1232.7822,77.888103 1232.1416,77.757663 C 1232.1348,77.030673 1232.0946,72.575323 1232.0946,72.575323 C 1232.0932,72.395523 1232.0012,72.228843 1231.8513,72.134653 L 1227.1404,69.169573 C 1226.9646,69.057653 1226.7397,69.066133 1226.5724,69.190403 C 1226.5724,69.190403 1223.7021,71.317363 1223.09,71.770403 C 1222.5942,71.196203 1219.9317,68.113813 1219.6227,67.756473 C 1219.7502,67.294193 1220.5913,64.235713 1220.6592,63.990313 C 1220.8325,63.824363 1221.7227,62.976953 1221.7227,62.976953 C 1221.8292,62.875083 1221.8856,62.735413 1221.8856,62.593403 C 1221.8856,62.514693 1221.8689,62.436733 1221.8334,62.362683 L 1220.4669,59.516413 C 1220.3893,59.355103 1220.2353,59.245503 1220.0605,59.227743 C 1220.0605,59.227743 1216.5493,58.858093 1215.8641,58.785543 C 1215.7933,58.087883 1215.4241,54.432073 1215.4119,54.314743 C 1215.4292,54.198203 1215.9157,50.903573 1216.0194,50.203593 C 1216.7093,50.184293 1219.7817,50.099403 1219.7817,50.099403 C 1219.9897,50.093983 1220.1744,49.958943 1220.2467,49.759043 C 1220.268,49.699623 1220.2793,49.637893 1220.2793,49.576163 C 1220.2793,49.431823 1220.2203,49.290613 1220.1126,49.190283 C 1220.1126,49.190283 1218.3342,47.528673 1218.0125,47.227683 C 1218.06,46.776213 1218.4263,43.312543 1218.4557,43.028523 C 1218.6237,42.829443 1220.0583,41.123073 1220.4497,40.658483 C 1221.0291,40.954833 1223.7488,42.347093 1223.7488,42.347093 C 1223.8861,42.417313 1224.0468,42.420413 1224.1852,42.357123 L 1225.1116,41.934193 C 1225.2752,41.859323 1225.389,41.702673 1225.4108,41.519763 C 1225.4108,41.519763 1225.818,38.157963 1225.8985,37.489663 C 1226.4993,37.413253 1228.9575,37.097613 1229.4024,37.041273 z " /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/48x48/actions/amarok_external.png" + id="path14148" + style="fill:url(#linearGradient14031);fill-opacity:1" + d="M 1226.3584,46.093953 C 1223.4421,50.692883 1225.9821,57.701243 1232.0216,61.715913 C 1235.4724,64.008823 1239.5574,64.937243 1242.9484,64.198643 C 1245.2763,63.692393 1247.1141,62.465293 1248.2634,60.652443 C 1249.0715,59.378263 1249.4823,57.902653 1249.4823,56.309003 C 1249.4823,55.637553 1249.4092,54.945293 1249.263,54.239143 C 1248.5416,50.766973 1246.0503,47.325703 1242.5994,45.031263 C 1236.5603,41.018113 1229.2749,41.494303 1226.3584,46.093953 z M 1232.5785,60.837633 C 1227.012,57.137843 1224.6064,50.779323 1227.2156,46.663533 C 1229.8251,42.547753 1236.4765,42.209723 1242.0433,45.909513 C 1245.2709,48.055783 1247.5955,51.250873 1248.2625,54.457523 C 1248.6999,56.563663 1248.4043,58.508463 1247.404,60.082863 C 1246.4061,61.658053 1244.7916,62.726893 1242.7345,63.175313 C 1239.6032,63.856783 1235.8065,62.983893 1232.5785,60.837633 z " /> + <path + style="fill:url(#linearGradient14034);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient14036);stroke-width:2.13024569;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 1238.5055,32.803283 L 1241.4628,37.340643 C 1234.6065,43.458023 1229.271,57.747923 1231.542,60.780133 C 1241.9656,66.321353 1233.9723,61.849403 1248.8853,48.725043 L 1253.4375,55.711743 L 1261.323,33.727133 L 1238.5055,32.803283 z " + id="path14150" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/48x48/actions/amarok_external.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" + sodipodi:nodetypes="ccccccc" /> + </g> + <g + id="g13892" + transform="translate(-40.47794,-3.5844714)"> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/32x32/actions/amarok_external.png" + id="g14166" + transform="matrix(0.5025228,0,0,0.5145054,1152.1473,37.703023)"> + <path + d="M 45.936,27.94 L 45.233,26.783 L 44.282,32.5 L 47.689,30.98 L 45.936,27.94 z " + style="fill:#ffffff" + id="path14168" /> + <path + d="M 85.14,50.286 L 82.531,50.264 L 81.42,50.308 L 81.646,54.354 L 85.14,50.286 z " + style="fill:#ffffff" + id="path14170" /> + <path + d="M 38.56,42.962 L 39.772,37.613 L 36.078,34.799 L 35.265,34.352 L 34.427,40.249 L 37.467,43.023 L 38.56,42.962 z " + style="fill:#ffffff" + id="path14172" /> + <path + d="M 39.6,59.891 L 42.767,56.869 L 39.487,50.521 L 32.926,49.88 L 32.218,43.364 L 32.13,43.361 L 31.005,49.145 L 31.67,55.573 L 37.786,56.202 L 39.6,59.891 z " + style="fill:#ffffff" + id="path14174" /> + <path + d="M 84.592,63.482 L 80.635,61.503 L 77.141,65.853 L 78.351,68.12 L 80.929,69.476 L 85.687,64.113 L 84.592,63.482 z " + style="fill:#ffffff" + id="path14176" /> + </g> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/32x32/actions/amarok_external.png" + d="M 1186.0102,57.101413 C 1182.1358,54.526313 1177.499,54.796953 1175.6542,57.706473 C 1175.0683,58.631043 1174.8356,59.710463 1174.913,60.835183 C 1176.3761,57.036153 1181.7418,56.632273 1185.1694,59.097793 C 1188.1228,61.221153 1189.7112,65.652013 1187.9691,68.640753 C 1188.6642,68.251773 1189.2526,67.715663 1189.6857,67.032393 C 1191.531,64.123393 1189.8852,59.677043 1186.0102,57.101413 z " + style="fill:url(#linearGradient14009);fill-opacity:1" + id="path14178" + sodipodi:nodetypes="cscscsc" /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/32x32/actions/amarok_external.png" + d="M 1171.2676,69.258673 L 1170.501,72.046763 L 1173.1401,75.102423 L 1175.712,73.197223 L 1178.8523,75.173933 L 1178.8854,78.912813 L 1182.2409,79.596613 L 1184.1766,76.965443 L 1184.2918,76.170543 L 1186.068,76.108783 L 1187.8722,78.948843 L 1190.3771,77.583353 L 1192.5908,74.910013 L 1192.6322,74.887863 L 1191.52,72.752173 L 1192.8154,73.449823 L 1195.2366,70.850553 L 1195.6065,68.063473 L 1193.205,66.229263 L 1193.1748,65.668973 L 1194.8894,63.514213 L 1193.6707,60.982323 L 1191.6642,60.542433 L 1189.8726,58.120663 L 1190.7326,56.946553 L 1188.6908,55.000673 L 1187.1502,55.770913 L 1184.5696,53.973233 L 1184.5332,52.651983 L 1181.7384,51.440843 L 1181.093,53.165433 L 1178.7996,52.725023 L 1177.5454,51.110533 L 1174.7576,51.467593 L 1174.4,54.423923 L 1173.7822,54.705873 L 1171.3452,53.458193 L 1169.7737,55.326383 L 1169.4481,58.409803 L 1170.9758,59.837043 L 1168.1826,59.914223 L 1167.7284,62.987363 L 1168.0628,66.294593 L 1171.1361,66.618233 L 1172.0472,68.515733 L 1171.2676,69.258673 z M 1189.6857,67.032913 C 1187.8404,69.942963 1183.2043,70.214613 1179.3292,67.639013 C 1175.4542,65.063393 1173.809,60.617543 1175.6542,57.706983 C 1177.499,54.797453 1182.1358,54.526313 1186.0102,57.101923 C 1189.8852,59.677043 1191.531,64.123393 1189.6857,67.032913 z " + style="fill:url(#linearGradient14011)" + id="path14180" /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/32x32/actions/amarok_external.png" + d="M 1193.0326,66.194283 L 1192.8914,63.593443 L 1194.691,63.520903 L 1193.491,61.028123 L 1191.515,60.594913 L 1189.7506,58.210183 L 1190.5974,57.054083 L 1188.5862,55.139093 L 1187.0691,55.898003 L 1184.5282,54.128073 L 1184.4931,52.827413 L 1181.7404,51.635843 L 1181.1054,53.333183 L 1178.8478,52.899953 L 1177.6126,51.309623 L 1175.0019,51.671343 L 1176.2014,53.802933 L 1173.9084,54.850973 L 1171.509,53.622323 L 1170.0628,55.428753 L 1172.2854,57.163153 L 1171.6858,59.872543 L 1168.545,60.053143 L 1168.8984,63.377363 L 1172.1441,63.702013 L 1173.7667,66.916633 L 1171.4738,69.156793 L 1174.2611,72.227373 L 1176.978,70.312383 L 1180.2945,72.298893 L 1180.3297,76.056303 L 1184.1404,76.778673 L 1184.635,73.346913 L 1187.9158,73.238383 L 1189.8209,76.092313 L 1192.4676,74.719633 L 1190.7742,71.468463 L 1192.5025,69.264843 L 1195.0436,70.565513 L 1195.3958,68.000703 L 1193.0326,66.194283 z M 1189.5662,66.987133 C 1187.7506,69.851893 1183.1846,70.118923 1179.3699,67.583433 C 1175.5546,65.047423 1173.9346,60.670023 1175.7512,57.804743 C 1177.5678,54.939473 1182.1328,54.672433 1185.9484,57.208433 C 1189.7632,59.744423 1191.3826,64.122363 1189.5662,66.987133 z " + style="fill:url(#linearGradient14013);fill-opacity:1" + id="path14182" /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/32x32/actions/amarok_external.png" + d="M 1177.503,50.765303 L 1174.7154,51.122363 C 1174.5608,51.142413 1174.4392,51.266933 1174.4195,51.425903 C 1174.4195,51.425903 1174.127,53.845633 1174.0858,54.186733 C 1173.9657,54.241283 1173.8979,54.272673 1173.791,54.321023 C 1173.5356,54.190853 1171.498,53.147433 1171.498,53.147433 C 1171.3582,53.075933 1171.1894,53.110913 1171.0874,53.232323 L 1169.5158,55.101013 C 1169.4708,55.154513 1169.4425,55.220383 1169.4356,55.290873 L 1169.1098,58.374303 C 1169.1088,58.386643 1169.1078,58.399003 1169.1078,58.411863 C 1169.1078,58.509103 1169.1481,58.602763 1169.2188,58.669103 C 1169.2188,58.669103 1169.6857,59.105423 1170.1224,59.513403 C 1169.3004,59.536043 1168.1742,59.567453 1168.1742,59.567453 C 1168.0088,59.571563 1167.8706,59.696573 1167.846,59.863793 L 1167.3924,62.936443 C 1167.3897,62.953403 1167.3881,62.971403 1167.3881,62.988883 C 1167.3881,63.000723 1167.3886,63.013073 1167.3902,63.024913 L 1167.7244,66.332153 C 1167.741,66.496283 1167.8681,66.625963 1168.029,66.642933 C 1168.029,66.642933 1170.5833,66.911503 1170.9146,66.946993 C 1171.0332,67.194473 1171.4769,68.116973 1171.6306,68.438033 C 1171.415,68.642813 1171.037,69.003473 1171.037,69.003473 C 1170.9914,69.046693 1170.9582,69.102243 1170.9417,69.164523 L 1170.1742,71.953133 C 1170.1657,71.984523 1170.1616,72.015913 1170.1616,72.047283 C 1170.1616,72.131673 1170.1918,72.213993 1170.247,72.278813 L 1172.8859,75.333943 C 1173.0019,75.468753 1173.199,75.490883 1173.3411,75.386433 C 1173.3411,75.386433 1175.411,73.853183 1175.7284,73.617553 C 1176.0518,73.820783 1178.2443,75.200703 1178.5146,75.371503 C 1178.5177,75.718283 1178.5462,78.916943 1178.5462,78.916943 C 1178.5473,79.082113 1178.6618,79.224113 1178.8202,79.256033 L 1182.1756,79.939793 C 1182.3042,79.966033 1182.4358,79.914573 1182.5147,79.807053 L 1184.4505,77.176393 C 1184.4846,77.129563 1184.5067,77.075543 1184.5158,77.017393 C 1184.5158,77.017393 1184.555,76.744723 1184.5886,76.510623 C 1184.9378,76.498273 1185.6379,76.474093 1185.8891,76.464833 C 1186.075,76.758093 1187.5883,79.139723 1187.5883,79.139723 C 1187.6842,79.291003 1187.8777,79.342453 1188.0339,79.257563 L 1190.5395,77.891543 C 1190.5768,77.871483 1190.6114,77.842683 1190.639,77.809243 C 1190.639,77.809243 1192.7662,75.240313 1192.8235,75.171363 C 1192.8858,75.127633 1192.9366,75.067943 1192.9592,74.992833 C 1192.9698,74.958883 1192.9748,74.922863 1192.9748,74.887863 C 1192.9748,74.831283 1192.9612,74.775183 1192.935,74.724263 C 1192.935,74.724263 1192.6382,74.154703 1192.3436,73.589263 C 1192.4602,73.652043 1192.6598,73.759553 1192.6598,73.759553 C 1192.7944,73.831583 1192.9602,73.803803 1193.0648,73.691143 L 1195.4854,71.091843 C 1195.5356,71.038353 1195.5668,70.970943 1195.5769,70.898403 L 1195.9468,68.111313 C 1195.9488,68.095363 1195.9498,68.079423 1195.9498,68.063993 C 1195.9498,67.954403 1195.8993,67.850453 1195.8134,67.784093 C 1195.8134,67.784093 1193.7683,66.222063 1193.5398,66.047133 C 1193.5337,65.939083 1193.5306,65.880943 1193.5256,65.784713 C 1193.6763,65.595403 1195.1572,63.734423 1195.1572,63.734423 C 1195.2075,63.671143 1195.2336,63.592933 1195.2336,63.514213 C 1195.2336,63.461733 1195.222,63.408743 1195.1985,63.359873 L 1193.9798,60.827983 C 1193.934,60.732283 1193.847,60.663323 1193.7457,60.640703 C 1193.7457,60.640703 1192.0802,60.275903 1191.8622,60.228063 C 1191.7345,60.055713 1190.5491,58.452493 1190.3022,58.119103 C 1190.5098,57.835613 1191.0083,57.155443 1191.0083,57.155443 C 1191.0536,57.093173 1191.0762,57.019093 1191.0762,56.946033 C 1191.0762,56.851893 1191.0395,56.758753 1190.9676,56.690323 L 1188.9249,54.746003 C 1188.8219,54.647233 1188.6702,54.624573 1188.5434,54.687853 C 1188.5434,54.687853 1187.4586,55.230153 1187.1802,55.369593 C 1186.9108,55.181793 1185.147,53.953153 1184.9078,53.786463 C 1184.9002,53.518913 1184.876,52.643233 1184.876,52.643233 C 1184.8726,52.506893 1184.7916,52.384943 1184.6691,52.331953 L 1181.8736,51.120803 C 1181.7881,51.083773 1181.6922,51.083773 1181.6072,51.120803 C 1181.5222,51.157853 1181.4554,51.228873 1181.4222,51.317343 C 1181.4222,51.317343 1181.0236,52.384433 1180.879,52.769793 C 1180.4744,52.692093 1179.184,52.444633 1178.9894,52.407073 C 1178.8508,52.228013 1177.8142,50.894423 1177.8142,50.894423 C 1177.7378,50.798723 1177.6206,50.749853 1177.503,50.765303 z M 1177.3976,51.482003 C 1177.5926,51.733593 1178.5326,52.942673 1178.5326,52.942673 C 1178.5834,53.008533 1178.6563,53.052773 1178.7368,53.068733 L 1181.0302,53.508633 C 1181.1921,53.540013 1181.3524,53.447903 1181.4113,53.290993 C 1181.4113,53.290993 1181.767,52.339673 1181.9298,51.903883 C 1182.3962,52.106073 1183.895,52.754863 1184.1987,52.886063 C 1184.2073,53.195793 1184.2289,53.982993 1184.2289,53.982993 C 1184.232,54.095153 1184.2867,54.198563 1184.3776,54.261873 L 1186.958,56.059553 C 1187.0585,56.130033 1187.1894,56.139813 1187.2993,56.084243 C 1187.2993,56.084243 1188.3163,55.575903 1188.63,55.419513 C 1188.8832,55.660293 1189.955,56.680553 1190.2768,56.987193 C 1190.0491,57.297963 1189.6004,57.909703 1189.6004,57.909703 C 1189.5546,57.971963 1189.532,58.045533 1189.532,58.119643 C 1189.532,58.193703 1189.555,58.267803 1189.6014,58.330573 L 1191.3932,60.752343 C 1191.4425,60.818733 1191.5129,60.865023 1191.5934,60.883023 C 1191.5934,60.883023 1193.1658,61.227743 1193.4385,61.287443 C 1193.5562,61.533363 1194.3346,63.147893 1194.4874,63.466353 C 1194.2537,63.760153 1192.9105,65.448753 1192.9105,65.448753 C 1192.8612,65.511003 1192.834,65.588703 1192.834,65.668973 C 1192.834,65.675663 1192.834,65.681823 1192.8346,65.688513 L 1192.8657,66.248283 C 1192.8712,66.351193 1192.921,66.446893 1193.002,66.508633 C 1193.002,66.508633 1194.9618,68.006353 1195.2417,68.220383 C 1195.1974,68.554833 1194.939,70.501203 1194.914,70.691573 C 1194.7814,70.833573 1193.0411,72.702263 1192.7467,73.018163 C 1192.4376,72.851963 1191.6777,72.443963 1191.6777,72.443963 C 1191.5451,72.371923 1191.3832,72.397663 1191.2771,72.506743 C 1191.2123,72.573623 1191.1787,72.662643 1191.1787,72.752683 C 1191.1787,72.808223 1191.1918,72.864823 1191.2184,72.916803 C 1191.2184,72.916803 1192.0244,74.463923 1192.213,74.825623 C 1192.0094,75.071543 1190.2355,77.214453 1190.1586,77.306553 C 1190.0612,77.360053 1188.4118,78.259423 1187.9867,78.491453 C 1187.7074,78.051563 1186.3529,75.919453 1186.3529,75.919453 C 1186.2882,75.817073 1186.1752,75.757383 1186.0554,75.760483 L 1184.2792,75.822203 C 1184.1147,75.828393 1183.9776,75.953403 1183.9534,76.120093 C 1183.9534,76.120093 1183.8721,76.688123 1183.8524,76.827033 C 1183.7595,76.952073 1182.3153,78.915913 1182.0971,79.212283 C 1181.7606,79.143333 1179.6508,78.713213 1179.2238,78.626253 C 1179.2192,78.141593 1179.1924,75.171363 1179.1924,75.171363 C 1179.1915,75.051493 1179.1302,74.940373 1179.0302,74.877583 L 1175.8896,72.900863 C 1175.7724,72.826243 1175.6225,72.831903 1175.511,72.914743 C 1175.511,72.914743 1173.5974,74.332723 1173.1894,74.634743 C 1172.8588,74.251943 1171.0838,72.197023 1170.8778,71.958793 C 1170.9628,71.650603 1171.5236,69.611623 1171.5688,69.448023 C 1171.6844,69.337393 1172.2778,68.772453 1172.2778,68.772453 C 1172.3488,68.704533 1172.3864,68.611423 1172.3864,68.516753 C 1172.3864,68.464273 1172.3753,68.412303 1172.3516,68.362933 L 1171.4406,66.465423 C 1171.3889,66.357883 1171.2862,66.284813 1171.1697,66.272973 C 1171.1697,66.272973 1168.8289,66.026543 1168.3721,65.978173 C 1168.3249,65.513073 1168.0788,63.075863 1168.0706,62.997643 C 1168.0822,62.919953 1168.4065,60.723533 1168.4756,60.256883 C 1168.9356,60.244013 1170.9838,60.187423 1170.9838,60.187423 C 1171.1225,60.183803 1171.2456,60.093783 1171.2938,59.960513 C 1171.308,59.920903 1171.3156,59.879743 1171.3156,59.838593 C 1171.3156,59.742363 1171.2762,59.648223 1171.2044,59.581343 C 1171.2044,59.581343 1170.0188,58.473603 1169.8044,58.272943 C 1169.836,57.971963 1170.0802,55.662853 1170.0998,55.473503 C 1170.2118,55.340783 1171.1682,54.203203 1171.4292,53.893473 C 1171.8154,54.091043 1173.6286,55.019213 1173.6286,55.019213 C 1173.7201,55.066033 1173.8272,55.068093 1173.9195,55.025903 L 1174.5371,54.743953 C 1174.6462,54.694033 1174.722,54.589603 1174.7366,54.467663 C 1174.7366,54.467663 1175.008,52.226463 1175.0617,51.780933 C 1175.4622,51.729993 1177.101,51.519563 1177.3976,51.482003 z " + style="fill:url(#linearGradient14015);fill-opacity:1" + id="path14184" /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/32x32/actions/amarok_external.png" + d="M 1175.3683,57.517123 C 1173.4241,60.583073 1175.1174,65.255313 1179.1438,67.931753 C 1181.4443,69.460363 1184.1676,70.079313 1186.4283,69.586913 C 1187.9802,69.249413 1189.2054,68.431343 1189.9716,67.222773 C 1190.5104,66.373323 1190.7842,65.389583 1190.7842,64.327153 C 1190.7842,63.879523 1190.7355,63.418013 1190.638,62.947243 C 1190.1571,60.632463 1188.4962,58.338283 1186.1956,56.808663 C 1182.1696,54.133233 1177.3126,54.450693 1175.3683,57.517123 z M 1179.515,67.346233 C 1175.804,64.879713 1174.2003,60.640703 1175.9398,57.896843 C 1177.6794,55.152993 1182.1137,54.927633 1185.8249,57.394163 C 1187.9766,58.825003 1189.5264,60.955063 1189.971,63.092833 C 1190.2626,64.496923 1190.0656,65.793453 1189.3987,66.843053 C 1188.7334,67.893183 1187.6571,68.605743 1186.2857,68.904693 C 1184.1982,69.359003 1181.667,68.777073 1179.515,67.346233 z " + style="fill:url(#linearGradient14017);fill-opacity:1" + id="path14186" /> + <path + sodipodi:nodetypes="ccccccc" + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/32x32/actions/amarok_external.png" + id="path14188" + d="M 1183.4664,48.656683 L 1185.4379,51.681583 C 1180.867,55.759833 1177.31,65.286433 1178.824,67.307903 C 1185.7731,71.002053 1180.4442,68.020753 1190.3862,59.271183 L 1193.421,63.928983 L 1198.678,49.272583 L 1183.4664,48.656683 z " + style="fill:url(#linearGradient14019);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient14021);stroke-width:1.42016315;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + <g + id="g13878" + transform="translate(-39.260854,-1.5844714)"> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/22x22/actions/amarok_external.png" + transform="matrix(0.3454846,0,0,0.3537227,1117.9539,48.904103)" + id="g14204"> + <path + id="path14206" + style="fill:#ffffff" + d="M 45.936,27.94 L 45.233,26.783 L 44.282,32.5 L 47.689,30.98 L 45.936,27.94 z " /> + <path + id="path14208" + style="fill:#ffffff" + d="M 85.14,50.286 L 82.531,50.264 L 81.42,50.308 L 81.646,54.354 L 85.14,50.286 z " /> + <path + id="path14210" + style="fill:#ffffff" + d="M 38.56,42.962 L 39.772,37.613 L 36.078,34.799 L 35.265,34.352 L 34.427,40.249 L 37.467,43.023 L 38.56,42.962 z " /> + <path + id="path14212" + style="fill:#ffffff" + d="M 39.6,59.891 L 42.767,56.869 L 39.487,50.521 L 32.926,49.88 L 32.218,43.364 L 32.13,43.361 L 31.005,49.145 L 31.67,55.573 L 37.786,56.202 L 39.6,59.891 z " /> + <path + id="path14214" + style="fill:#ffffff" + d="M 84.592,63.482 L 80.635,61.503 L 77.141,65.853 L 78.351,68.12 L 80.929,69.476 L 85.687,64.113 L 84.592,63.482 z " /> + </g> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/22x22/actions/amarok_external.png" + sodipodi:nodetypes="cscscsc" + id="path14216" + style="fill:url(#linearGradient13995);fill-opacity:1" + d="M 1141.2347,62.240503 C 1138.571,60.470123 1135.3832,60.656193 1134.1149,62.656483 C 1133.7121,63.292133 1133.5522,64.034233 1133.6054,64.807473 C 1134.6113,62.195643 1138.3002,61.917973 1140.6567,63.613023 C 1142.6871,65.072833 1143.7791,68.119053 1142.5814,70.173813 C 1143.0593,69.906383 1143.4639,69.537813 1143.7616,69.068063 C 1145.0303,67.068123 1143.8988,64.011253 1141.2347,62.240503 z " /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/22x22/actions/amarok_external.png" + id="path14218" + style="fill:url(#linearGradient13997)" + d="M 1131.0992,70.598633 L 1130.5721,72.515443 L 1132.3865,74.616213 L 1134.1547,73.306383 L 1136.3136,74.665373 L 1136.3364,77.235853 L 1138.6433,77.705963 L 1139.9741,75.897033 L 1140.0533,75.350543 L 1141.2744,75.308083 L 1142.5148,77.260623 L 1144.2369,76.321853 L 1145.7589,74.483923 L 1145.7873,74.468703 L 1145.0227,73.000413 L 1145.9133,73.480043 L 1147.5779,71.693043 L 1147.8322,69.776933 L 1146.1811,68.515913 L 1146.1604,68.130713 L 1147.3392,66.649313 L 1146.5013,64.908633 L 1145.1218,64.606213 L 1143.8901,62.941243 L 1144.4814,62.134043 L 1143.0776,60.796253 L 1142.0185,61.325793 L 1140.2443,60.089883 L 1140.2193,59.181523 L 1138.2978,58.348863 L 1137.8541,59.534523 L 1136.2774,59.231743 L 1135.4151,58.121773 L 1133.4985,58.367253 L 1133.2527,60.399733 L 1132.8279,60.593573 L 1131.1525,59.735793 L 1130.0721,61.020173 L 1129.8483,63.140023 L 1130.8985,64.121253 L 1128.9782,64.174313 L 1128.666,66.287103 L 1128.8959,68.560823 L 1131.0088,68.783323 L 1131.6351,70.087853 L 1131.0992,70.598633 z M 1143.7616,69.068423 C 1142.493,71.069083 1139.3056,71.255843 1136.6415,69.485113 C 1133.9774,67.714373 1132.8464,64.657853 1134.1149,62.656833 C 1135.3832,60.656533 1138.571,60.470123 1141.2347,62.240863 C 1143.8988,64.011253 1145.0303,67.068123 1143.7616,69.068423 z " /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/22x22/actions/amarok_external.png" + id="path14220" + style="fill:url(#linearGradient13999);fill-opacity:1" + d="M 1146.0626,68.491863 L 1145.9655,66.703783 L 1147.2028,66.653913 L 1146.3778,64.940123 L 1145.0193,64.642293 L 1143.8062,63.002783 L 1144.3884,62.207973 L 1143.0057,60.891413 L 1141.9627,61.413163 L 1140.2158,60.196333 L 1140.1917,59.302133 L 1138.2992,58.482923 L 1137.8626,59.649853 L 1136.3105,59.352003 L 1135.4613,58.258653 L 1133.6665,58.507333 L 1134.4911,59.972803 L 1132.9147,60.693333 L 1131.2651,59.848633 L 1130.2709,61.090553 L 1131.7989,62.282953 L 1131.3867,64.145663 L 1129.2274,64.269823 L 1129.4703,66.555223 L 1131.7018,66.778423 L 1132.8173,68.988473 L 1131.2409,70.528583 L 1133.1572,72.639613 L 1135.0251,71.323053 L 1137.3052,72.688783 L 1137.3294,75.272003 L 1139.9492,75.768633 L 1140.2893,73.409293 L 1142.5448,73.334683 L 1143.8546,75.296763 L 1145.6742,74.353043 L 1144.51,72.117863 L 1145.6982,70.602873 L 1147.4452,71.497083 L 1147.6873,69.733773 L 1146.0626,68.491863 z M 1143.6795,69.036943 C 1142.4312,71.006463 1139.2921,71.190053 1136.6695,69.446903 C 1134.0465,67.703393 1132.9327,64.693933 1134.1816,62.724043 C 1135.4305,60.754173 1138.569,60.570583 1141.1922,62.314083 C 1143.8149,64.057583 1144.9282,67.067413 1143.6795,69.036943 z " /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/22x22/actions/amarok_external.png" + id="path14222" + style="fill:url(#linearGradient14001);fill-opacity:1" + d="M 1135.386,57.884433 L 1133.4695,58.129913 C 1133.3632,58.143693 1133.2796,58.229303 1133.2661,58.338593 C 1133.2661,58.338593 1133.065,60.002153 1133.0367,60.236663 C 1132.9541,60.274163 1132.9075,60.295753 1132.834,60.328993 C 1132.6584,60.239493 1131.2576,59.522143 1131.2576,59.522143 C 1131.1614,59.472993 1131.0454,59.497033 1130.9753,59.580503 L 1129.8948,60.865233 C 1129.8639,60.902013 1129.8444,60.947303 1129.8397,60.995763 L 1129.6157,63.115623 C 1129.615,63.124103 1129.6143,63.132603 1129.6143,63.141443 C 1129.6143,63.208293 1129.642,63.272683 1129.6906,63.318293 C 1129.6906,63.318293 1130.0116,63.618263 1130.3118,63.898753 C 1129.7467,63.914313 1128.9724,63.935913 1128.9724,63.935913 C 1128.8587,63.938733 1128.7637,64.024683 1128.7468,64.139643 L 1128.435,66.252093 C 1128.4331,66.263753 1128.432,66.276133 1128.432,66.288143 C 1128.432,66.296283 1128.4323,66.304773 1128.4334,66.312913 L 1128.6632,68.586643 C 1128.6746,68.699483 1128.762,68.788643 1128.8726,68.800303 C 1128.8726,68.800303 1130.6287,68.984953 1130.8565,69.009353 C 1130.938,69.179493 1131.2431,69.813713 1131.3487,70.034443 C 1131.2005,70.175223 1130.9406,70.423183 1130.9406,70.423183 C 1130.9093,70.452893 1130.8864,70.491083 1130.8751,70.533903 L 1130.3474,72.451073 C 1130.3416,72.472653 1130.3388,72.494233 1130.3388,72.515803 C 1130.3388,72.573813 1130.3595,72.630413 1130.3975,72.674973 L 1132.2117,74.775383 C 1132.2915,74.868063 1132.427,74.883273 1132.5247,74.811463 C 1132.5247,74.811463 1133.9477,73.757353 1134.166,73.595363 C 1134.3883,73.735083 1135.8956,74.683773 1136.0815,74.801203 C 1136.0836,75.039613 1136.1032,77.238693 1136.1032,77.238693 C 1136.104,77.352243 1136.1827,77.449873 1136.2916,77.471813 L 1138.5984,77.941903 C 1138.6868,77.959943 1138.7773,77.924563 1138.8315,77.850643 L 1140.1624,76.042063 C 1140.1859,76.009873 1140.201,75.972733 1140.2073,75.932753 C 1140.2073,75.932753 1140.2343,75.745293 1140.2574,75.584343 C 1140.4974,75.575853 1140.9787,75.559233 1141.1514,75.552863 C 1141.2793,75.754483 1142.3196,77.391853 1142.3196,77.391853 C 1142.3856,77.495863 1142.5186,77.531233 1142.626,77.472873 L 1144.3486,76.533733 C 1144.3742,76.519943 1144.398,76.500143 1144.417,76.477153 C 1144.417,76.477153 1145.8795,74.711013 1145.9188,74.663603 C 1145.9617,74.633543 1145.9966,74.592503 1146.0121,74.540863 C 1146.0194,74.517523 1146.0229,74.492763 1146.0229,74.468703 C 1146.0229,74.429803 1146.0135,74.391233 1145.9955,74.356223 C 1145.9955,74.356223 1145.7915,73.964653 1145.5889,73.575913 C 1145.6691,73.619073 1145.8063,73.692983 1145.8063,73.692983 C 1145.8988,73.742503 1146.0128,73.723403 1146.0847,73.645953 L 1147.7489,71.858933 C 1147.7834,71.822163 1147.8049,71.775813 1147.8118,71.725943 L 1148.0661,69.809823 C 1148.0675,69.798853 1148.0682,69.787893 1148.0682,69.777283 C 1148.0682,69.701943 1148.0335,69.630473 1147.9744,69.584853 C 1147.9744,69.584853 1146.5684,68.510963 1146.4113,68.390693 C 1146.4071,68.316413 1146.405,68.276443 1146.4015,68.210283 C 1146.5051,68.080133 1147.5233,66.800703 1147.5233,66.800703 C 1147.5578,66.757203 1147.5758,66.703433 1147.5758,66.649313 C 1147.5758,66.613233 1147.5678,66.576803 1147.5517,66.543203 L 1146.7138,64.802523 C 1146.6823,64.736733 1146.6225,64.689323 1146.5529,64.673773 C 1146.5529,64.673773 1145.4078,64.422973 1145.258,64.390083 C 1145.1702,64.271593 1144.3552,63.169373 1144.1855,62.940173 C 1144.3282,62.745273 1144.6709,62.277653 1144.6709,62.277653 C 1144.702,62.234843 1144.7176,62.183913 1144.7176,62.133683 C 1144.7176,62.068963 1144.6923,62.004933 1144.6429,61.957883 L 1143.2386,60.621163 C 1143.1677,60.553263 1143.0635,60.537683 1142.9763,60.581183 C 1142.9763,60.581183 1142.2305,60.954013 1142.0391,61.049883 C 1141.8539,60.920773 1140.6413,60.076073 1140.4768,59.961483 C 1140.4716,59.777533 1140.4549,59.175503 1140.4549,59.175503 C 1140.4526,59.081773 1140.3969,58.997933 1140.3127,58.961503 L 1138.3908,58.128833 C 1138.332,58.103383 1138.2661,58.103383 1138.2076,58.128833 C 1138.1492,58.154313 1138.1033,58.203133 1138.0804,58.263953 C 1138.0804,58.263953 1137.8064,58.997583 1137.707,59.262513 C 1137.4288,59.209103 1136.5417,59.038973 1136.4079,59.013143 C 1136.3126,58.890043 1135.5999,57.973203 1135.5999,57.973203 C 1135.5474,57.907403 1135.4668,57.873803 1135.386,57.884433 z M 1135.3135,58.377163 C 1135.4476,58.550133 1136.0938,59.381373 1136.0938,59.381373 C 1136.1288,59.426653 1136.1789,59.457063 1136.2342,59.468043 L 1137.8109,59.770473 C 1137.9223,59.792043 1138.0325,59.728723 1138.073,59.620843 C 1138.073,59.620843 1138.3175,58.966813 1138.4294,58.667203 C 1138.7501,58.806213 1139.7805,59.252253 1139.9893,59.342453 C 1139.9952,59.555393 1140.0101,60.096593 1140.0101,60.096593 C 1140.0122,60.173703 1140.0498,60.244793 1140.1123,60.288323 L 1141.8863,61.524233 C 1141.9554,61.572683 1142.0454,61.579403 1142.121,61.541203 C 1142.121,61.541203 1142.8201,61.191723 1143.0358,61.084203 C 1143.2099,61.249733 1143.9468,61.951163 1144.168,62.161983 C 1144.0114,62.375633 1143.703,62.796203 1143.703,62.796203 C 1143.6715,62.839013 1143.6559,62.889593 1143.6559,62.940543 C 1143.6559,62.991453 1143.6718,63.042403 1143.7037,63.085553 L 1144.9355,64.750523 C 1144.9694,64.796163 1145.0178,64.827993 1145.0732,64.840363 C 1145.0732,64.840363 1146.1542,65.077363 1146.3417,65.118403 C 1146.4226,65.287473 1146.9577,66.397463 1147.0628,66.616403 C 1146.9021,66.818393 1145.9787,67.979303 1145.9787,67.979303 C 1145.9448,68.022103 1145.9261,68.075523 1145.9261,68.130713 C 1145.9261,68.135313 1145.9261,68.139543 1145.9265,68.144143 L 1145.9479,68.528983 C 1145.9516,68.599733 1145.9859,68.665533 1146.0416,68.707973 C 1146.0416,68.707973 1147.3889,69.737663 1147.5814,69.884803 C 1147.5509,70.114743 1147.3733,71.452873 1147.3561,71.583753 C 1147.2649,71.681373 1146.0684,72.966103 1145.866,73.183283 C 1145.6535,73.069013 1145.1311,72.788513 1145.1311,72.788513 C 1145.0399,72.738993 1144.9286,72.756683 1144.8557,72.831683 C 1144.8111,72.877663 1144.788,72.938863 1144.788,73.000763 C 1144.788,73.038943 1144.7971,73.077863 1144.8153,73.113593 C 1144.8153,73.113593 1145.3695,74.177243 1145.4991,74.425913 C 1145.3592,74.594983 1144.1396,76.068233 1144.0867,76.131553 C 1144.0198,76.168333 1142.8858,76.786653 1142.5935,76.946173 C 1142.4015,76.643743 1141.4703,75.177913 1141.4703,75.177913 C 1141.4258,75.107533 1141.3481,75.066493 1141.2658,75.068623 L 1140.0446,75.111063 C 1139.9315,75.115313 1139.8373,75.201263 1139.8207,75.315853 C 1139.8207,75.315853 1139.7648,75.706383 1139.7512,75.801883 C 1139.6873,75.887843 1138.6945,77.237983 1138.5444,77.441743 C 1138.3131,77.394333 1136.8626,77.098633 1136.569,77.038843 C 1136.5659,76.705643 1136.5475,74.663603 1136.5475,74.663603 C 1136.5468,74.581193 1136.5047,74.504803 1136.4359,74.461633 L 1134.2768,73.102633 C 1134.1962,73.051333 1134.0932,73.055223 1134.0165,73.112183 C 1134.0165,73.112183 1132.7009,74.087043 1132.4204,74.294683 C 1132.1931,74.031503 1130.9728,72.618743 1130.8312,72.454963 C 1130.8896,72.243083 1131.2752,70.841283 1131.3062,70.728803 C 1131.3857,70.652753 1131.7937,70.264353 1131.7937,70.264353 C 1131.8425,70.217653 1131.8683,70.153643 1131.8683,70.088563 C 1131.8683,70.052483 1131.8607,70.016753 1131.8444,69.982803 L 1131.2181,68.678273 C 1131.1826,68.604333 1131.1119,68.554103 1131.0319,68.545963 C 1131.0319,68.545963 1129.4226,68.376543 1129.1085,68.343283 C 1129.0761,68.023523 1128.9069,66.347943 1128.9012,66.294173 C 1128.9092,66.240753 1129.1322,64.730713 1129.1797,64.409893 C 1129.4959,64.401043 1130.904,64.362143 1130.904,64.362143 C 1130.9994,64.359653 1131.084,64.297763 1131.1172,64.206143 C 1131.1269,64.178913 1131.1322,64.150613 1131.1322,64.122323 C 1131.1322,64.056163 1131.1051,63.991443 1131.0557,63.945463 C 1131.0557,63.945463 1130.2406,63.183893 1130.0932,63.045933 C 1130.1149,62.839013 1130.2828,61.251493 1130.2963,61.121323 C 1130.3733,61.030073 1131.0308,60.247983 1131.2103,60.035043 C 1131.4758,60.170873 1132.7223,60.808993 1132.7223,60.808993 C 1132.7853,60.841183 1132.8589,60.842603 1132.9223,60.813593 L 1133.3469,60.619753 C 1133.4219,60.585433 1133.4741,60.513633 1133.4841,60.429803 C 1133.4841,60.429803 1133.6707,58.888973 1133.7076,58.582673 C 1133.9829,58.547653 1135.1096,58.402983 1135.3135,58.377163 z " /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/22x22/actions/amarok_external.png" + id="path14224" + style="fill:url(#linearGradient14003);fill-opacity:1" + d="M 1133.9184,62.526313 C 1132.5818,64.634153 1133.7459,67.846313 1136.514,69.686373 C 1138.0956,70.737293 1139.9679,71.162823 1141.5221,70.824293 C 1142.5891,70.592263 1143.4314,70.029843 1143.9582,69.198943 C 1144.3286,68.614953 1144.5168,67.938633 1144.5168,67.208203 C 1144.5168,66.900463 1144.4833,66.583173 1144.4163,66.259523 C 1144.0857,64.668103 1142.9438,63.090853 1141.3622,62.039243 C 1138.5943,60.199883 1135.2551,60.418133 1133.9184,62.526313 z M 1136.7692,69.283823 C 1134.2179,67.588093 1133.1154,64.673773 1134.3113,62.787363 C 1135.5073,60.900973 1138.5559,60.746033 1141.1073,62.441773 C 1142.5866,63.425473 1143.6521,64.889893 1143.9578,66.359613 C 1144.1582,67.324923 1144.0228,68.216293 1143.5643,68.937893 C 1143.1069,69.659853 1142.3669,70.149743 1141.4241,70.355263 C 1139.989,70.667603 1138.2487,70.267533 1136.7692,69.283823 z " /> + <path + style="fill:url(#linearGradient14005);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient14007);stroke-width:0.97636271;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 1139.4858,56.434753 L 1140.8412,58.514373 C 1137.6987,61.318173 1135.2533,67.867713 1136.2942,69.257473 C 1141.0717,71.797203 1137.4081,69.747563 1144.2432,63.732223 L 1146.3296,66.934463 L 1149.9438,56.858183 L 1139.4858,56.434753 z " + id="path14226" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/22x22/actions/amarok_external.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" + sodipodi:nodetypes="ccccccc" /> + </g> + <g + id="g13864" + transform="translate(-40.999971,-0.5844714)"> + <g + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/16x16/actions/amarok_external.png" + id="g14242" + transform="matrix(0.2512617,0,0,0.2572531,1090.8117,55.824753)"> + <path + d="M 45.936,27.94 L 45.233,26.783 L 44.282,32.5 L 47.689,30.98 L 45.936,27.94 z " + style="fill:#ffffff" + id="path14244" /> + <path + d="M 85.14,50.286 L 82.531,50.264 L 81.42,50.308 L 81.646,54.354 L 85.14,50.286 z " + style="fill:#ffffff" + id="path14246" /> + <path + d="M 38.56,42.962 L 39.772,37.613 L 36.078,34.799 L 35.265,34.352 L 34.427,40.249 L 37.467,43.023 L 38.56,42.962 z " + style="fill:#ffffff" + id="path14248" /> + <path + d="M 39.6,59.891 L 42.767,56.869 L 39.487,50.521 L 32.926,49.88 L 32.218,43.364 L 32.13,43.361 L 31.005,49.145 L 31.67,55.573 L 37.786,56.202 L 39.6,59.891 z " + style="fill:#ffffff" + id="path14250" /> + <path + d="M 84.592,63.482 L 80.635,61.503 L 77.141,65.853 L 78.351,68.12 L 80.929,69.476 L 85.687,64.113 L 84.592,63.482 z " + style="fill:#ffffff" + id="path14252" /> + </g> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/16x16/actions/amarok_external.png" + d="M 1107.7431,65.523963 C 1105.8058,64.236413 1103.4874,64.371733 1102.565,65.826493 C 1102.2721,66.288783 1102.1558,66.828493 1102.1945,67.390853 C 1102.926,65.491333 1105.6089,65.289393 1107.3227,66.522153 C 1108.7994,67.583833 1109.5935,69.799273 1108.7225,71.293643 C 1109.07,71.099153 1109.3643,70.831103 1109.5808,70.489463 C 1110.5035,69.034963 1109.6806,66.811783 1107.7431,65.523963 z " + style="fill:url(#linearGradient13980);fill-opacity:1" + id="path14254" + sodipodi:nodetypes="cscscsc" /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/16x16/actions/amarok_external.png" + d="M 1100.3719,71.602603 L 1099.9885,72.996653 L 1101.3081,74.524483 L 1102.594,73.571883 L 1104.1641,74.560233 L 1104.1807,76.429673 L 1105.8584,76.771573 L 1106.8263,75.455993 L 1106.8839,75.058543 L 1107.7719,75.027663 L 1108.674,76.447693 L 1109.9265,75.764953 L 1111.0334,74.428273 L 1111.054,74.417203 L 1110.498,73.349353 L 1111.1457,73.698173 L 1112.3563,72.398543 L 1112.5412,71.005003 L 1111.3404,70.087893 L 1111.3254,69.807753 L 1112.1827,68.730373 L 1111.5733,67.464423 L 1110.57,67.244473 L 1109.6743,66.033593 L 1110.1043,65.446533 L 1109.0834,64.473593 L 1108.3131,64.858713 L 1107.0228,63.959873 L 1107.0046,63.299243 L 1105.6071,62.693673 L 1105.2844,63.555973 L 1104.1378,63.335773 L 1103.5106,62.528513 L 1102.1167,62.707053 L 1101.938,64.185213 L 1101.629,64.326193 L 1100.4106,63.702353 L 1099.6249,64.636443 L 1099.4621,66.178153 L 1100.2259,66.891783 L 1098.8293,66.930363 L 1098.6023,68.466943 L 1098.7695,70.120563 L 1100.3061,70.282383 L 1100.7616,71.231123 L 1100.3719,71.602603 z M 1109.5808,70.489723 C 1108.6582,71.944753 1106.3401,72.080573 1104.4026,70.792773 C 1102.465,69.504963 1101.6425,67.282033 1102.565,65.826743 C 1103.4874,64.371983 1105.8058,64.236413 1107.7431,65.524223 C 1109.6806,66.811783 1110.5035,69.034963 1109.5808,70.489723 z " + style="fill:url(#linearGradient13982)" + id="path14256" /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/16x16/actions/amarok_external.png" + d="M 1111.2543,70.070403 L 1111.1836,68.769983 L 1112.0835,68.733713 L 1111.4835,67.487323 L 1110.4955,67.270713 L 1109.6132,66.078343 L 1110.0367,65.500303 L 1109.0311,64.542803 L 1108.2725,64.922253 L 1107.002,64.037293 L 1106.9845,63.386963 L 1105.6082,62.791173 L 1105.2906,63.639853 L 1104.1618,63.423233 L 1103.5442,62.628063 L 1102.2389,62.808923 L 1102.8386,63.874723 L 1101.6921,64.398743 L 1100.4925,63.784413 L 1099.7695,64.687633 L 1100.8808,65.554833 L 1100.581,66.909533 L 1099.0106,66.999833 L 1099.1872,68.661943 L 1100.8101,68.824263 L 1101.6213,70.431583 L 1100.4749,71.551663 L 1101.8685,73.086953 L 1103.227,72.129453 L 1104.8852,73.122713 L 1104.9028,75.001423 L 1106.8082,75.362603 L 1107.0555,73.646723 L 1108.6959,73.592463 L 1109.6484,75.019433 L 1110.9718,74.333083 L 1110.1251,72.707503 L 1110.9892,71.605693 L 1112.2598,72.256023 L 1112.4359,70.973613 L 1111.2543,70.070403 z M 1109.5211,70.466833 C 1108.6132,71.899213 1106.3303,72.032733 1104.4229,70.764983 C 1102.5153,69.496973 1101.7052,67.308273 1102.6135,65.875623 C 1103.5218,64.442993 1105.8044,64.309473 1107.7122,65.577473 C 1109.6196,66.845473 1110.4292,69.034443 1109.5211,70.466833 z " + style="fill:url(#linearGradient13984);fill-opacity:1" + id="path14258" /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/16x16/actions/amarok_external.png" + d="M 1103.4895,62.355903 L 1102.0956,62.534433 C 1102.0183,62.544463 1101.9575,62.606723 1101.9477,62.686203 C 1101.9477,62.686203 1101.8015,63.896063 1101.7809,64.066623 C 1101.7208,64.093893 1101.6869,64.109593 1101.6335,64.133773 C 1101.5057,64.068683 1100.4871,63.546973 1100.4871,63.546973 C 1100.4171,63.511223 1100.3328,63.528703 1100.2818,63.589413 L 1099.496,64.523763 C 1099.4735,64.550513 1099.4593,64.583453 1099.4559,64.618693 L 1099.293,66.160413 C 1099.2925,66.166583 1099.292,66.172763 1099.292,66.179193 C 1099.292,66.227803 1099.3121,66.274633 1099.3474,66.307803 C 1099.3474,66.307803 1099.5809,66.525963 1099.7992,66.729963 C 1099.3882,66.741273 1098.8251,66.756983 1098.8251,66.756983 C 1098.7424,66.759033 1098.6733,66.821543 1098.661,66.905153 L 1098.4343,68.441483 C 1098.4329,68.449963 1098.4321,68.458963 1098.4321,68.467703 C 1098.4321,68.473623 1098.4323,68.479793 1098.4331,68.485713 L 1098.6002,70.139333 C 1098.6085,70.221403 1098.6721,70.286243 1098.7525,70.294723 C 1098.7525,70.294723 1100.0297,70.429023 1100.1954,70.446763 C 1100.2546,70.570503 1100.4765,71.031753 1100.5533,71.192283 C 1100.4456,71.294673 1100.2565,71.475003 1100.2565,71.475003 C 1100.2338,71.496613 1100.2171,71.524383 1100.2089,71.555533 L 1099.8251,72.949833 C 1099.8209,72.965533 1099.8189,72.981223 1099.8189,72.996913 C 1099.8189,73.039103 1099.8339,73.080263 1099.8616,73.112673 L 1101.181,74.640243 C 1101.239,74.707643 1101.3376,74.718713 1101.4085,74.666483 C 1101.4085,74.666483 1102.4434,73.899853 1102.6022,73.782043 C 1102.7639,73.883663 1103.8601,74.573623 1103.9953,74.659023 C 1103.9968,74.832413 1104.0111,76.431743 1104.0111,76.431743 C 1104.0116,76.514323 1104.0689,76.585333 1104.1481,76.601283 L 1105.8258,76.943173 C 1105.89,76.956293 1105.9559,76.930563 1105.9953,76.876803 L 1106.9632,75.561463 C 1106.9803,75.538053 1106.9913,75.511043 1106.9959,75.481963 C 1106.9959,75.481963 1107.0155,75.345633 1107.0323,75.228583 C 1107.2068,75.222403 1107.5569,75.210313 1107.6825,75.205683 C 1107.7755,75.352313 1108.5321,76.543133 1108.5321,76.543133 C 1108.5801,76.618773 1108.6768,76.644503 1108.7549,76.602053 L 1110.0077,75.919043 C 1110.0263,75.909013 1110.0436,75.894613 1110.0575,75.877893 C 1110.0575,75.877893 1111.1211,74.593433 1111.1497,74.558953 C 1111.1809,74.537083 1111.2063,74.507243 1111.2175,74.469683 C 1111.2228,74.452713 1111.2254,74.434703 1111.2254,74.417203 C 1111.2254,74.388913 1111.2186,74.360863 1111.2055,74.335403 C 1111.2055,74.335403 1111.0571,74.050623 1110.9098,73.767903 C 1110.9681,73.799293 1111.0679,73.853043 1111.0679,73.853043 C 1111.1351,73.889053 1111.218,73.875163 1111.2703,73.818843 L 1112.4807,72.519183 C 1112.5058,72.492443 1112.5214,72.458733 1112.5264,72.422463 L 1112.7114,71.028923 C 1112.7124,71.020943 1112.7129,71.012973 1112.7129,71.005263 C 1112.7129,70.950463 1112.6876,70.898483 1112.6447,70.865313 C 1112.6447,70.865313 1111.6221,70.084293 1111.5079,69.996833 C 1111.5048,69.942803 1111.5033,69.913733 1111.5007,69.865623 C 1111.5761,69.770963 1112.3166,68.840473 1112.3166,68.840473 C 1112.3417,68.808833 1112.3548,68.769733 1112.3548,68.730373 C 1112.3548,68.704133 1112.349,68.677633 1112.3372,68.653203 L 1111.7279,67.387253 C 1111.705,67.339403 1111.6615,67.304923 1111.6108,67.293613 C 1111.6108,67.293613 1110.778,67.111213 1110.6691,67.087293 C 1110.6052,67.001113 1110.0125,66.199503 1109.8891,66.032813 C 1109.9929,65.891063 1110.2421,65.550973 1110.2421,65.550973 C 1110.2647,65.519843 1110.2761,65.482803 1110.2761,65.446273 C 1110.2761,65.399203 1110.2577,65.352633 1110.2218,65.318413 L 1109.2004,64.346253 C 1109.1489,64.296873 1109.0731,64.285543 1109.0097,64.317183 C 1109.0097,64.317183 1108.4673,64.588333 1108.3281,64.658053 C 1108.1934,64.564153 1107.3115,63.949823 1107.1919,63.866493 C 1107.1881,63.732703 1107.1759,63.294863 1107.1759,63.294863 C 1107.1743,63.226703 1107.1338,63.165723 1107.0725,63.139233 L 1105.6748,62.533653 C 1105.632,62.515143 1105.5841,62.515143 1105.5415,62.533653 C 1105.4991,62.552183 1105.4657,62.587683 1105.449,62.631923 C 1105.449,62.631923 1105.2498,63.165473 1105.1775,63.358143 C 1104.9751,63.319303 1104.33,63.195573 1104.2327,63.176783 C 1104.1634,63.087263 1103.645,62.420463 1103.645,62.420463 C 1103.6068,62.372613 1103.5482,62.348173 1103.4895,62.355903 z M 1103.4367,62.714253 C 1103.5343,62.840053 1104.0042,63.444593 1104.0042,63.444593 C 1104.0297,63.477523 1104.0661,63.499633 1104.1063,63.507623 L 1105.253,63.727573 C 1105.334,63.743263 1105.4142,63.697213 1105.4436,63.618753 C 1105.4436,63.618753 1105.6215,63.143093 1105.7028,62.925193 C 1105.9361,63.026293 1106.6855,63.350683 1106.8373,63.416283 C 1106.8416,63.571153 1106.8524,63.964753 1106.8524,63.964753 C 1106.854,64.020833 1106.8813,64.072533 1106.9268,64.104193 L 1108.217,65.003033 C 1108.2672,65.038273 1108.3327,65.043163 1108.3876,65.015373 C 1108.3876,65.015373 1108.8961,64.761213 1109.053,64.683013 C 1109.1796,64.803403 1109.7155,65.313533 1109.8764,65.466853 C 1109.7625,65.622233 1109.5382,65.928103 1109.5382,65.928103 C 1109.5153,65.959243 1109.5039,65.996023 1109.5039,66.033083 C 1109.5039,66.070103 1109.5155,66.107163 1109.5387,66.138543 L 1110.4346,67.349433 C 1110.4592,67.382623 1110.4944,67.405773 1110.5347,67.414773 C 1110.5347,67.414773 1111.3209,67.587133 1111.4572,67.616983 C 1111.5161,67.739943 1111.9052,68.547203 1111.9817,68.706433 C 1111.8648,68.853333 1111.1932,69.697633 1111.1932,69.697633 C 1111.1686,69.728763 1111.155,69.767613 1111.155,69.807753 C 1111.155,69.811093 1111.155,69.814173 1111.1553,69.817523 L 1111.1708,70.097403 C 1111.1735,70.148853 1111.1985,70.196713 1111.239,70.227583 C 1111.239,70.227583 1112.2188,70.976443 1112.3588,71.083453 C 1112.3367,71.250683 1112.2075,72.223873 1112.195,72.319053 C 1112.1287,72.390053 1111.2585,73.324403 1111.1113,73.482353 C 1110.9567,73.399243 1110.5768,73.195243 1110.5768,73.195243 C 1110.5105,73.159233 1110.4295,73.172093 1110.3765,73.226643 C 1110.3441,73.260083 1110.3273,73.304593 1110.3273,73.349613 C 1110.3273,73.377373 1110.3339,73.405683 1110.3471,73.431673 C 1110.3471,73.431673 1110.7502,74.205233 1110.8444,74.386083 C 1110.7427,74.509043 1109.8557,75.580503 1109.8172,75.626553 C 1109.7686,75.653303 1108.9439,76.102983 1108.7313,76.219003 C 1108.5916,75.999053 1107.9144,74.932993 1107.9144,74.932993 C 1107.882,74.881803 1107.8255,74.851963 1107.7657,74.853513 L 1106.8775,74.884373 C 1106.7953,74.887463 1106.7268,74.949973 1106.7147,75.033313 C 1106.7147,75.033313 1106.674,75.317333 1106.6642,75.386793 C 1106.6177,75.449303 1105.8956,76.431223 1105.7865,76.579413 C 1105.6183,76.544933 1104.5634,76.329883 1104.3498,76.286393 C 1104.3476,76.044073 1104.3342,74.558953 1104.3342,74.558953 C 1104.3337,74.499013 1104.3031,74.443453 1104.253,74.412063 L 1102.6828,73.423693 C 1102.6241,73.386383 1102.5492,73.389213 1102.4935,73.430643 C 1102.4935,73.430643 1101.5367,74.139633 1101.3328,74.290643 C 1101.1674,74.099243 1100.28,73.071773 1100.177,72.952663 C 1100.2194,72.798573 1100.4999,71.779073 1100.5224,71.697273 C 1100.5802,71.641963 1100.877,71.359493 1100.877,71.359493 C 1100.9125,71.325533 1100.9312,71.278973 1100.9312,71.231643 C 1100.9312,71.205403 1100.9257,71.179423 1100.9138,71.154723 L 1100.4584,70.205983 C 1100.4325,70.152203 1100.3811,70.115673 1100.3229,70.109753 C 1100.3229,70.109753 1099.1525,69.986533 1098.9241,69.962353 C 1098.9005,69.729793 1098.7775,68.511193 1098.7733,68.472083 C 1098.7792,68.433233 1098.9413,67.335023 1098.9759,67.101703 C 1099.2058,67.095263 1100.2299,67.066973 1100.2299,67.066973 C 1100.2993,67.065163 1100.3608,67.020153 1100.385,66.953513 C 1100.392,66.933713 1100.3959,66.913133 1100.3959,66.892553 C 1100.3959,66.844443 1100.3762,66.797373 1100.3402,66.763933 C 1100.3402,66.763933 1099.7474,66.210063 1099.6402,66.109723 C 1099.656,65.959243 1099.7781,64.804683 1099.788,64.710013 C 1099.844,64.643643 1100.3221,64.074853 1100.4527,63.919983 C 1100.6458,64.018773 1101.5522,64.482863 1101.5522,64.482863 C 1101.598,64.506273 1101.6516,64.507303 1101.6977,64.486203 L 1102.0065,64.345233 C 1102.061,64.320273 1102.099,64.268053 1102.1063,64.207083 C 1102.1063,64.207083 1102.242,63.086483 1102.2688,62.863713 C 1102.469,62.838253 1103.2884,62.733033 1103.4367,62.714253 z " + style="fill:url(#linearGradient13986);fill-opacity:1" + id="path14260" /> + <path + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/16x16/actions/amarok_external.png" + d="M 1102.4221,65.731823 C 1101.45,67.264793 1102.2967,69.600913 1104.3098,70.939143 C 1105.4601,71.703453 1106.8218,72.012923 1107.9521,71.766723 C 1108.7281,71.597973 1109.3407,71.188943 1109.7238,70.584643 C 1109.9932,70.159923 1110.13,69.668053 1110.13,69.136833 C 1110.13,68.913023 1110.1057,68.682263 1110.057,68.446883 C 1109.8165,67.289493 1108.986,66.142393 1107.8358,65.377593 C 1105.8228,64.039873 1103.3943,64.198603 1102.4221,65.731823 z M 1104.4954,70.646383 C 1102.6399,69.413123 1101.8381,67.293613 1102.7079,65.921673 C 1103.5777,64.549753 1105.7948,64.437073 1107.6504,65.670333 C 1108.7263,66.385753 1109.5012,67.450793 1109.7235,68.519683 C 1109.8692,69.221723 1109.7708,69.869993 1109.4373,70.394793 C 1109.1047,70.919853 1108.5665,71.276143 1107.8808,71.425613 C 1106.8371,71.652763 1105.5714,71.361803 1104.4954,70.646383 z " + style="fill:url(#linearGradient13988);fill-opacity:1" + id="path14262" /> + <path + sodipodi:nodetypes="ccccccc" + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/16x16/actions/amarok_external.png" + id="path14264" + d="M 1106.4711,61.301593 L 1107.4569,62.814043 C 1105.1714,64.853173 1103.3929,69.616483 1104.15,70.627213 C 1107.6245,72.474293 1104.9601,70.983643 1109.9311,66.608843 L 1111.4484,68.937753 L 1114.077,61.609543 L 1106.4711,61.301593 z " + style="fill:url(#linearGradient13991);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient13993);stroke-width:0.71008259;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + <g + id="g13313" + transform="translate(-17,-105)"> + <g + id="g10486" + transform="matrix(2.909093,0,0,2.909093,-806.27454,683.61651)" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/64x64/actions/amarok_favorite_genres.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <path + sodipodi:nodetypes="cssssscsssssz" + id="path10489" + d="M 602.90661,146.08356 C 602.56806,145.31597 601.28808,142.24809 603.3407,141.25072 C 605.59908,140.15336 607.49098,141.20628 608.41704,140.91689 C 609.34309,140.6275 610.08352,139.54331 610.807,139.28286 C 611.53047,139.0224 612.22501,138.87771 612.48547,139.22498 C 612.74592,139.57225 611.3279,139.8327 611.38578,140.4983 C 611.44366,141.1639 612.74592,141.80056 612.74592,141.80056 C 612.74592,141.80056 612.1382,142.52404 611.84881,142.92919 C 611.55941,143.33433 611.35867,143.34543 611.55941,143.79736 C 612.10138,145.01752 613.68251,144.05551 613.87454,144.60766 C 614.28343,145.78331 611.43749,145.44898 610.4515,145.88158 C 609.3499,146.36492 609.22087,148.12123 607.10605,148.91608 C 604.81915,149.77561 603.24175,146.84339 602.90661,146.08356 z " + style="fill:url(#linearGradient10505);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + id="path10491" + d="M 610.42835,141.87066 L 620.31201,138.16684 L 620.41433,137.83944 C 620.41433,137.83944 623.27916,137.61434 623.48379,137.67573 C 623.68842,137.73712 623.62703,137.94175 623.89305,138.0236 C 624.15907,138.10546 624.34324,138.08499 624.05675,138.18731 C 623.77027,138.28962 621.86721,138.9649 621.66258,139.29231 C 621.45795,139.61972 620.98729,138.86259 620.65989,139.02629 C 620.33248,139.19 620.23016,139.27185 620.23016,139.27185 L 611.04225,143.28261" + style="fill:#0001c0;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + style="fill:#293f73;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 606.00833,143.58956 L 605.17958,143.88627 C 605.30671,144.22248 606.05949,145.67679 606.05949,145.67679 L 606.83708,145.34938 L 606.00833,143.58956 L 606.00833,143.58956 z " + id="path10493" + sodipodi:nodetypes="cccccc" /> + <path + sodipodi:nodetypes="cccccc" + id="path10495" + d="M 605.98787,143.30307 L 605.15911,143.59978 C 605.28625,143.936 606.03903,145.39031 606.03903,145.39031 L 606.81662,145.0629 L 605.98787,143.30307 L 605.98787,143.30307 z " + style="fill:#5675bf;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + sodipodi:nodetypes="ccccccc" + id="path10497" + d="M 606.11065,145.05266 L 605.46606,143.70211 L 620.31201,138.35101 L 620.68035,138.16684 L 623.34055,137.8599 L 620.84405,138.92398 L 606.11065,145.05266 z " + style="opacity:0.38396624;fill:url(#linearGradient10507);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + transform="matrix(0.911051,0,0,0.911051,263.2289,-11.51056)" + d="M 379.03125 172.51843 A 0.359375 0.34375 0 1 1 378.3125,172.51843 A 0.359375 0.34375 0 1 1 379.03125 172.51843 z" + sodipodi:ry="0.34375" + sodipodi:rx="0.359375" + sodipodi:cy="172.51843" + sodipodi:cx="378.67188" + id="path10499" + style="fill:#839bd3;fill-opacity:1;stroke:none;stroke-width:0.2;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" /> + <path + transform="matrix(0.80364,0,0,0.80364,303.0453,8.660843)" + sodipodi:type="arc" + style="fill:#839bd3;fill-opacity:1;stroke:none;stroke-width:0.2;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path10501" + sodipodi:cx="378.67188" + sodipodi:cy="172.51843" + sodipodi:rx="0.359375" + sodipodi:ry="0.34375" + d="M 379.03125 172.51843 A 0.359375 0.34375 0 1 1 378.3125,172.51843 A 0.359375 0.34375 0 1 1 379.03125 172.51843 z" /> + <path + d="M 379.03125 172.51843 A 0.359375 0.34375 0 1 1 378.3125,172.51843 A 0.359375 0.34375 0 1 1 379.03125 172.51843 z" + sodipodi:ry="0.34375" + sodipodi:rx="0.359375" + sodipodi:cy="172.51843" + sodipodi:cx="378.67188" + id="path10503" + style="fill:#839bd3;fill-opacity:1;stroke:none;stroke-width:0.2;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="arc" + transform="matrix(0.80364,0,0,0.80364,303.5773,7.883247)" /> + </g> + <rect + y="1068.6895" + x="945.58508" + height="64" + width="64" + id="rect14280" + style="fill:#b9cff9;fill-opacity:0;stroke:none;stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" + inkscape:export-filename="/home/theobroma/PROJECTS/Amarok/icons/hicolor/64x64/actions/amarok_favourite_genres.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" /> + </g> + <text + xml:space="preserve" + style="font-size:20px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-indent:0;text-align:center;text-decoration:none;line-height:125%;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;text-anchor:middle;opacity:1;color:#000000;fill:#000000;fill-opacity:1;fill-rule:nonzero;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-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;font-family:Verdana" + x="187.67188" + y="-172.34485" + id="text14094" + sodipodi:linespacing="125%"><tspan + sodipodi:role="line" + id="tspan14096" + x="187.67188" + y="-172.34485">All of the icons are grouped together. In order to edit the details you will need to ungroup each one several time in some cases.</tspan></text> + <rect + style="opacity:1;fill:url(#linearGradient17023);fill-opacity:1;stroke:url(#linearGradient17031);stroke-width:1.51437521;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect16046" + width="49.485626" + height="38.485626" + x="-454.7428" + y="-115.38063" /> + <text + xml:space="preserve" + style="font-size:20px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-indent:0;text-align:start;text-decoration:none;line-height:100%;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;text-anchor:start;opacity:1;color:#000000;fill:#000000;fill-opacity:1;fill-rule:nonzero;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-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;font-family:Verdana" + x="-400" + y="-99.768677" + id="text17033" + sodipodi:linespacing="100%"><tspan + sodipodi:role="line" + id="tspan17035" + x="-400" + y="-99.768677">This block is using the new blue primary gradient colors. In order to quickly change most of the icons colors edit the gradient in the </tspan><tspan + sodipodi:role="line" + x="-400" + y="-79.768677" + id="tspan17037">colors dialog. It is just a matter of editing the misc gradients and colors that are not based on this primary gradient after that. Have </tspan><tspan + sodipodi:role="line" + x="-400" + y="-59.768677" + id="tspan20005">Fun make your own versions and share tem on the forum. http://amarok.kde.org/forum/index.php/board,19.0.html </tspan></text> + </g> +</svg> diff --git a/amarok/src/images/lastfm.png b/amarok/src/images/lastfm.png new file mode 100644 index 00000000..b69ef5a9 Binary files /dev/null and b/amarok/src/images/lastfm.png differ diff --git a/amarok/src/images/loading1.png b/amarok/src/images/loading1.png new file mode 100644 index 00000000..6ce3fd97 Binary files /dev/null and b/amarok/src/images/loading1.png differ diff --git a/amarok/src/images/loading2.png b/amarok/src/images/loading2.png new file mode 100644 index 00000000..99ee8aa3 Binary files /dev/null and b/amarok/src/images/loading2.png differ diff --git a/amarok/src/images/menu_sidepixmap.png b/amarok/src/images/menu_sidepixmap.png new file mode 100644 index 00000000..7ba47873 Binary files /dev/null and b/amarok/src/images/menu_sidepixmap.png differ diff --git a/amarok/src/images/menu_sidepixmap.xcf b/amarok/src/images/menu_sidepixmap.xcf new file mode 100644 index 00000000..99e865df Binary files /dev/null and b/amarok/src/images/menu_sidepixmap.xcf differ diff --git a/amarok/src/images/more_albums.png b/amarok/src/images/more_albums.png new file mode 100644 index 00000000..02e60848 Binary files /dev/null and b/amarok/src/images/more_albums.png differ diff --git a/amarok/src/images/musicbrainz.png b/amarok/src/images/musicbrainz.png new file mode 100644 index 00000000..66c8e958 Binary files /dev/null and b/amarok/src/images/musicbrainz.png differ diff --git a/amarok/src/images/nocover.png b/amarok/src/images/nocover.png new file mode 100644 index 00000000..e4dda111 Binary files /dev/null and b/amarok/src/images/nocover.png differ diff --git a/amarok/src/images/pl_active2.png b/amarok/src/images/pl_active2.png new file mode 100644 index 00000000..c2ecc1fb Binary files /dev/null and b/amarok/src/images/pl_active2.png differ diff --git a/amarok/src/images/pl_inactive2.png b/amarok/src/images/pl_inactive2.png new file mode 100644 index 00000000..2c08ed15 Binary files /dev/null and b/amarok/src/images/pl_inactive2.png differ diff --git a/amarok/src/images/player_background.jpg b/amarok/src/images/player_background.jpg new file mode 100644 index 00000000..bd1c3cf4 Binary files /dev/null and b/amarok/src/images/player_background.jpg differ diff --git a/amarok/src/images/sbinner_stars.png b/amarok/src/images/sbinner_stars.png new file mode 100644 index 00000000..57ba1b0e Binary files /dev/null and b/amarok/src/images/sbinner_stars.png differ diff --git a/amarok/src/images/shadow_albumcover.png b/amarok/src/images/shadow_albumcover.png new file mode 100644 index 00000000..b0e83471 Binary files /dev/null and b/amarok/src/images/shadow_albumcover.png differ diff --git a/amarok/src/images/smallstar.png b/amarok/src/images/smallstar.png new file mode 100644 index 00000000..44ed12a3 Binary files /dev/null and b/amarok/src/images/smallstar.png differ diff --git a/amarok/src/images/sound.png b/amarok/src/images/sound.png new file mode 100644 index 00000000..d2f0449e Binary files /dev/null and b/amarok/src/images/sound.png differ diff --git a/amarok/src/images/splash_screen.jpg b/amarok/src/images/splash_screen.jpg new file mode 100644 index 00000000..9a08016d Binary files /dev/null and b/amarok/src/images/splash_screen.jpg differ diff --git a/amarok/src/images/star.png b/amarok/src/images/star.png new file mode 100644 index 00000000..5ba5d2e8 Binary files /dev/null and b/amarok/src/images/star.png differ diff --git a/amarok/src/images/time_minus.png b/amarok/src/images/time_minus.png new file mode 100644 index 00000000..15c6ab71 Binary files /dev/null and b/amarok/src/images/time_minus.png differ diff --git a/amarok/src/images/time_plus.png b/amarok/src/images/time_plus.png new file mode 100644 index 00000000..8ca50f47 Binary files /dev/null and b/amarok/src/images/time_plus.png differ diff --git a/amarok/src/images/vol_speaker.png b/amarok/src/images/vol_speaker.png new file mode 100644 index 00000000..4d4f5344 Binary files /dev/null and b/amarok/src/images/vol_speaker.png differ diff --git a/amarok/src/images/volumeslider-gradient.png b/amarok/src/images/volumeslider-gradient.png new file mode 100644 index 00000000..d6dfab83 Binary files /dev/null and b/amarok/src/images/volumeslider-gradient.png differ diff --git a/amarok/src/images/volumeslider-handle.png b/amarok/src/images/volumeslider-handle.png new file mode 100644 index 00000000..7a8d2971 Binary files /dev/null and b/amarok/src/images/volumeslider-handle.png differ diff --git a/amarok/src/images/volumeslider-handle_glow.png b/amarok/src/images/volumeslider-handle_glow.png new file mode 100644 index 00000000..3684e225 Binary files /dev/null and b/amarok/src/images/volumeslider-handle_glow.png differ diff --git a/amarok/src/images/volumeslider-inset.png b/amarok/src/images/volumeslider-inset.png new file mode 100644 index 00000000..ca111093 Binary files /dev/null and b/amarok/src/images/volumeslider-inset.png differ diff --git a/amarok/src/images/wizard_compact.png b/amarok/src/images/wizard_compact.png new file mode 100644 index 00000000..b763b236 Binary files /dev/null and b/amarok/src/images/wizard_compact.png differ diff --git a/amarok/src/images/wizard_xmms.png b/amarok/src/images/wizard_xmms.png new file mode 100644 index 00000000..ed014410 Binary files /dev/null and b/amarok/src/images/wizard_xmms.png differ diff --git a/amarok/src/images/xine_logo.png b/amarok/src/images/xine_logo.png new file mode 100644 index 00000000..ba264e02 Binary files /dev/null and b/amarok/src/images/xine_logo.png differ diff --git a/amarok/src/inotify/inotify-syscalls.h b/amarok/src/inotify/inotify-syscalls.h new file mode 100644 index 00000000..1431d467 --- /dev/null +++ b/amarok/src/inotify/inotify-syscalls.h @@ -0,0 +1,61 @@ +#ifndef _LINUX_INOTIFY_SYSCALLS_H +#define _LINUX_INOTIFY_SYSCALLS_H + +#include <sys/syscall.h> + +#if defined(__i386__) +# define __NR_inotify_init 291 +# define __NR_inotify_add_watch 292 +# define __NR_inotify_rm_watch 293 +#elif defined(__x86_64__) +# define __NR_inotify_init 253 +# define __NR_inotify_add_watch 254 +# define __NR_inotify_rm_watch 255 +#elif defined(__powerpc__) || defined(__powerpc64__) +# define __NR_inotify_init 275 +# define __NR_inotify_add_watch 276 +# define __NR_inotify_rm_watch 277 +#elif defined (__ia64__) +# define __NR_inotify_init 1277 +# define __NR_inotify_add_watch 1278 +# define __NR_inotify_rm_watch 1279 +#elif defined (__s390__) +# define __NR_inotify_init 284 +# define __NR_inotify_add_watch 285 +# define __NR_inotify_rm_watch 286 +#elif defined (__alpha__) +# define __NR_inotify_init 444 +# define __NR_inotify_add_watch 445 +# define __NR_inotify_rm_watch 446 +#elif defined (__sparc__) || defined (__sparc64__) +# define __NR_inotify_init 151 +# define __NR_inotify_add_watch 152 +# define __NR_inotify_rm_watch 156 +#elif defined (__arm__) +# define __NR_inotify_init 316 +# define __NR_inotify_add_watch 317 +# define __NR_inotify_rm_watch 318 +#elif defined (__sh__) +# define __NR_inotify_init 290 +# define __NR_inotify_add_watch 291 +# define __NR_inotify_rm_watch 292 +#else +# error "Unsupported architecture!" +#endif + +static inline int inotify_init (void) +{ + return syscall (__NR_inotify_init); +} + +static inline int inotify_add_watch (int fd, const char *name, __u32 mask) +{ + return syscall (__NR_inotify_add_watch, fd, name, mask); +} + +static inline int inotify_rm_watch (int fd, __u32 wd) +{ + return syscall (__NR_inotify_rm_watch, fd, wd); +} + +#endif /* _LINUX_INOTIFY_SYSCALLS_H */ diff --git a/amarok/src/inotify/inotify.h b/amarok/src/inotify/inotify.h new file mode 100644 index 00000000..267c88b5 --- /dev/null +++ b/amarok/src/inotify/inotify.h @@ -0,0 +1,113 @@ +/* + * Inode based directory notification for Linux + * + * Copyright (C) 2005 John McCutchan + */ + +#ifndef _LINUX_INOTIFY_H +#define _LINUX_INOTIFY_H + +#include <linux/types.h> + +/* + * struct inotify_event - structure read from the inotify device for each event + * + * When you are watching a directory, you will receive the filename for events + * such as IN_CREATE, IN_DELETE, IN_OPEN, IN_CLOSE, ..., relative to the wd. + */ +struct inotify_event { + __s32 wd; /* watch descriptor */ + __u32 mask; /* watch mask */ + __u32 cookie; /* cookie to synchronize two events */ + __u32 len; /* length (including nulls) of name */ + char name[0]; /* stub for possible name */ +}; + +/* the following are legal, implemented events that user-space can watch for */ +#define IN_ACCESS 0x00000001 /* File was accessed */ +#define IN_MODIFY 0x00000002 /* File was modified */ +#define IN_ATTRIB 0x00000004 /* Metadata changed */ +#define IN_CLOSE_WRITE 0x00000008 /* Writtable file was closed */ +#define IN_CLOSE_NOWRITE 0x00000010 /* Unwrittable file closed */ +#define IN_OPEN 0x00000020 /* File was opened */ +#define IN_MOVED_FROM 0x00000040 /* File was moved from X */ +#define IN_MOVED_TO 0x00000080 /* File was moved to Y */ +#define IN_CREATE 0x00000100 /* Subfile was created */ +#define IN_DELETE 0x00000200 /* Subfile was deleted */ +#define IN_DELETE_SELF 0x00000400 /* Self was deleted */ +#define IN_MOVE_SELF 0x00000800 /* Self was moved */ + +/* the following are legal events. they are sent as needed to any watch */ +#define IN_UNMOUNT 0x00002000 /* Backing fs was unmounted */ +#define IN_Q_OVERFLOW 0x00004000 /* Event queued overflowed */ +#define IN_IGNORED 0x00008000 /* File was ignored */ + +/* helper events */ +#define IN_CLOSE (IN_CLOSE_WRITE | IN_CLOSE_NOWRITE) /* close */ +#define IN_MOVE (IN_MOVED_FROM | IN_MOVED_TO) /* moves */ + +/* special flags */ +#define IN_ONLYDIR 0x01000000 /* only watch the path if it is a directory */ +#define IN_DONT_FOLLOW 0x02000000 /* don't follow a sym link */ +#define IN_MASK_ADD 0x20000000 /* add to the mask of an already existing watch */ +#define IN_ISDIR 0x40000000 /* event occurred against dir */ +#define IN_ONESHOT 0x80000000 /* only send event once */ + +/* + * All of the events - we build the list by hand so that we can add flags in + * the future and not break backward compatibility. Apps will get only the + * events that they originally wanted. Be sure to add new events here! + */ +#define IN_ALL_EVENTS (IN_ACCESS | IN_MODIFY | IN_ATTRIB | IN_CLOSE_WRITE | \ + IN_CLOSE_NOWRITE | IN_OPEN | IN_MOVED_FROM | \ + IN_MOVED_TO | IN_DELETE | IN_CREATE | IN_DELETE_SELF | \ + IN_MOVE_SELF) + +#ifdef __KERNEL__ + +#include <linux/dcache.h> +#include <linux/fs.h> +#include <linux/config.h> + +#ifdef CONFIG_INOTIFY + +extern void inotify_inode_queue_event(struct inode *, __u32, __u32, + const char *); +extern void inotify_dentry_parent_queue_event(struct dentry *, __u32, __u32, + const char *); +extern void inotify_unmount_inodes(struct list_head *); +extern void inotify_inode_is_dead(struct inode *); +extern u32 inotify_get_cookie(void); + +#else + +static inline void inotify_inode_queue_event(struct inode *inode, + __u32 mask, __u32 cookie, + const char *filename) +{ +} + +static inline void inotify_dentry_parent_queue_event(struct dentry *dentry, + __u32 mask, __u32 cookie, + const char *filename) +{ +} + +static inline void inotify_unmount_inodes(struct list_head *list) +{ +} + +static inline void inotify_inode_is_dead(struct inode *inode) +{ +} + +static inline u32 inotify_get_cookie(void) +{ + return 0; +} + +#endif /* CONFIG_INOTIFY */ + +#endif /* __KERNEL __ */ + +#endif /* _LINUX_INOTIFY_H */ diff --git a/amarok/src/k3bexporter.cpp b/amarok/src/k3bexporter.cpp new file mode 100644 index 00000000..d9dba557 --- /dev/null +++ b/amarok/src/k3bexporter.cpp @@ -0,0 +1,260 @@ +/*************************************************************************** + begin : Mon May 31 2004 + copyright : (C) 2004 by Michael Pyne + (c) 2004 by Pierpaolo Di Panfilo + email : michael.pyne@kdemail.net +***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +#include "playlist.h" +#include "playlistitem.h" +#include "collectiondb.h" +#include "k3bexporter.h" +#include "amarok.h" + +#include <kprocess.h> +#include <kmessagebox.h> +#include <klocale.h> +#include <kstandarddirs.h> + +#include <qcstring.h> +#include <qstringlist.h> + +#include <dcopref.h> +#include <dcopclient.h> + + +K3bExporter *K3bExporter::s_instance = 0; + +bool K3bExporter::isAvailable() //static +{ + return !KStandardDirs::findExe( "k3b" ).isNull(); +} + +void K3bExporter::exportTracks( const KURL::List &urls, int openmode ) +{ + if( urls.empty() ) + return; + + DCOPClient *client = DCOPClient::mainClient(); + QCString appId, appObj; + QByteArray data; + + if( openmode == -1 ) + //ask to open a data or an audio cd project + openmode = openMode(); + + if( !client->findObject( "k3b-*", "K3bInterface", "", data, appId, appObj) ) + exportViaCmdLine( urls, openmode ); + else { + DCOPRef ref( appId, appObj ); + exportViaDCOP( urls, ref, openmode ); + } +} + +void K3bExporter::exportCurrentPlaylist( int openmode ) +{ + Playlist::instance()->burnPlaylist( openmode ); +} + +void K3bExporter::exportSelectedTracks( int openmode ) +{ + Playlist::instance()->burnSelectedTracks( openmode ); +} + +void K3bExporter::exportAlbum( const QString &album, int openmode ) +{ + exportAlbum( QString::null, album, openmode ); +} + +void K3bExporter::exportAlbum( const QString &artist, const QString &album, int openmode ) +{ + QString albumId = QString::number( CollectionDB::instance()->albumID( album, false, false, true ) ); + QString artistId; + if( !artist.isNull() ) + artistId = QString::number( CollectionDB::instance()->artistID( artist, false, false, true ) ); + + QueryBuilder qb; + qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valURL ); + qb.addMatch( QueryBuilder::tabSong, QueryBuilder::valAlbumID, albumId ); + if( !artist.isNull() ) + qb.addMatch( QueryBuilder::tabSong, QueryBuilder::valArtistID, artistId ); + qb.sortBy( QueryBuilder::tabSong, QueryBuilder::valDiscNumber ); + qb.sortBy( QueryBuilder::tabSong, QueryBuilder::valTrack ); + + QStringList values( qb.run() ); + + if( !values.isEmpty() ) + { + KURL::List urls; + + foreach( values ) + urls << KURL( *it ); + + exportTracks( urls, openmode ); + } +} + +void K3bExporter::exportArtist( const QString &artist, int openmode ) +{ + const QString artistId = QString::number( CollectionDB::instance()->artistID( artist, false, false, true ) ); + + QueryBuilder qb; + qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valURL ); + qb.addMatch( QueryBuilder::tabSong, QueryBuilder::valArtistID, artistId ); + qb.sortBy( QueryBuilder::tabAlbum, QueryBuilder::valName ); + qb.sortBy( QueryBuilder::tabSong, QueryBuilder::valDiscNumber ); + qb.sortBy( QueryBuilder::tabSong, QueryBuilder::valTrack ); + + QStringList values( qb.run() ); + + if( !values.isEmpty() ) + { + KURL::List urls; + + foreach( values ) + urls << KURL( *it ); + + exportTracks( urls, openmode ); + } +} + +void K3bExporter::exportComposer( const QString &composer, int openmode ) +{ + const QString composerId = QString::number( CollectionDB::instance()->composerID( composer, false, false, true ) ); + + QueryBuilder qb; + qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valURL ); + qb.addMatch( QueryBuilder::tabSong, QueryBuilder::valComposerID, composerId ); + qb.sortBy( QueryBuilder::tabAlbum, QueryBuilder::valName ); + qb.sortBy( QueryBuilder::tabSong, QueryBuilder::valDiscNumber ); + qb.sortBy( QueryBuilder::tabSong, QueryBuilder::valTrack ); + + QStringList values( qb.run() ); + + if( !values.isEmpty() ) + { + KURL::List urls; + + foreach( values ) + urls << KURL( *it ); + + exportTracks( urls, openmode ); + } +} + +void K3bExporter::exportViaCmdLine( const KURL::List &urls, int openmode ) +{ + QCString cmdOption; + + switch( openmode ) { + case AudioCD: + cmdOption = "--audiocd"; + break; + + case DataCD: + cmdOption = "--datacd"; + break; + + case Abort: + return; + } + + KProcess *process = new KProcess; + + *process << "k3b"; + *process << cmdOption; + + KURL::List::ConstIterator it; + KURL::List::ConstIterator end( urls.end() ); + for( it = urls.begin(); it != end; ++it ) + *process << ( *it ).path(); + + if( !process->start( KProcess::DontCare ) ) + KMessageBox::error( 0, i18n("Unable to start K3b.") ); +} + +void K3bExporter::exportViaDCOP( const KURL::List &urls, DCOPRef &ref, int openmode ) +{ + QValueList<DCOPRef> projectList; + DCOPReply projectListReply = ref.call("projects()"); + + if( !projectListReply.get<QValueList<DCOPRef> >(projectList, "QValueList<DCOPRef>") ) { + DCOPErrorMessage(); + return; + } + + if( projectList.count() == 0 && !startNewK3bProject(ref, openmode) ) + return; + + if( !ref.send( "addUrls(KURL::List)", DCOPArg(urls, "KURL::List") ) ) { + DCOPErrorMessage(); + return; + } +} + +void K3bExporter::DCOPErrorMessage() +{ + KMessageBox::error( 0, i18n("There was a DCOP communication error with K3b.")); +} + +bool K3bExporter::startNewK3bProject( DCOPRef &ref, int openmode ) +{ + QCString request; + //K3bOpenMode mode = openMode(); + + switch( openmode ) { + case AudioCD: + request = "createAudioCDProject()"; + break; + + case DataCD: + request = "createDataCDProject()"; + break; + + case Abort: + return false; + } + + KMessageBox::sorry(0,request); + if( !ref.send( request ) ) { + DCOPErrorMessage(); + return false; + } + + return true; +} + +K3bExporter::K3bOpenMode K3bExporter::openMode() +{ + int reply = KMessageBox::questionYesNoCancel( + 0, + i18n("Create an audio mode CD suitable for CD players, or a data " + "mode CD suitable for computers and other digital music " + "players?"), + i18n("Create K3b Project"), + i18n("Audio Mode"), + i18n("Data Mode") + ); + + switch(reply) { + case KMessageBox::Cancel: + return Abort; + + case KMessageBox::No: + return DataCD; + + case KMessageBox::Yes: + return AudioCD; + } + + return Abort; +} + diff --git a/amarok/src/k3bexporter.h b/amarok/src/k3bexporter.h new file mode 100644 index 00000000..e9a2e24a --- /dev/null +++ b/amarok/src/k3bexporter.h @@ -0,0 +1,103 @@ +/*************************************************************************** + begin : Mon May 31 2004 + copyright : (C) 2004 by Michael Pyne + (c) 2004 by Pierpaolo Di Panfilo + email : michael.pyne@kdemail.net +***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef K3BEXPORTER_H +#define K3BEXPORTER_H + +#include "amarok_export.h" +#include "collectiondb.h" +#include <kurl.h> + +class DCOPRef; + +/** + * This class will export a list of tracks to K3b. + */ +class K3bExporter +{ +public: + enum K3bOpenMode { AudioCD, DataCD, Abort }; + + /** + * @return true if the executable of K3B is found + */ + LIBAMAROK_EXPORT static bool isAvailable(); + + + /** + * Exports the list of urls @urls via DCOP to K3B. The mode @p openmode will be used + * @param urls The list of urls to export + * @param openmode The mode of the album + */ + LIBAMAROK_EXPORT void exportTracks( const KURL::List &urls, int openmode=-1 ); + + /** + * Exports the current playlist to K3B. The mode @p openmode will be used + * @param openmode The mode of the album + */ + void exportCurrentPlaylist( int openmode=-1 ); + + /** + * Exports the selected tracks to K3B. The mode @p openmode will be used + * @param openmode The mode of the tracks + */ + void exportSelectedTracks( int openmode=-1 ); + + /** + * Exports the album @p album to K3B. The mode @p openmode will be used + * @param openmode The mode of the album + * @param album The album to export + */ + void exportAlbum( const QString &album, int openmode=-1 ); + + /** + * Exports the album @p album by artist @ artist to K3B. The mode @p openmode will be used + * @param openmode The mode of the album + * @param album The album to export + */ + void exportAlbum( const QString &artist, const QString &album, int openmode=-1 ); + + /** + * Exports all tracks of the artist @p artist to K3B. The mode @p openmode will be used + * @param openmode The mode of the album + * @param artist The artists which tracks to export + */ + void exportArtist( const QString &artist, int openmode=-1 ); + + /** + * Exports all tracks of the composer @p composer to K3B. The mode @p openmode will be used + * @param openmode The mode of the album + * @param artist The artists which tracks to export + */ + void exportComposer( const QString &artist, int openmode=-1 ); + + /** + * @return the static instance of K3bExporter + */ + static K3bExporter *instance() { return s_instance; } + +private: + void exportViaCmdLine( const KURL::List &urls, int openmode ); + void exportViaDCOP( const KURL::List &urls, DCOPRef &ref, int mode ); + void DCOPErrorMessage(); + bool startNewK3bProject( DCOPRef &ref, int mode ); + K3bOpenMode openMode(); + + LIBAMAROK_EXPORT static K3bExporter *s_instance; +}; + + +#endif /* K3BEXPORTER_H */ diff --git a/amarok/src/kbookmarkhandler.cpp b/amarok/src/kbookmarkhandler.cpp new file mode 100644 index 00000000..ce32858c --- /dev/null +++ b/amarok/src/kbookmarkhandler.cpp @@ -0,0 +1,52 @@ +/* This file is part of the KDE project + Copyright (C) xxxx KFile Authors + Copyright (C) 2002 Anders Lund <anders.lund@lund.tdcadsl.dk> + + This library 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 library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Steet, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +// $Id: kbookmarkhandler.cpp 581133 2006-09-05 12:14:35Z seb $ + +#include "amarok.h" +#include "kbookmarkhandler.h" +#include <kbookmarkimporter.h> +#include <kbookmarkmenu.h> +#include <kdiroperator.h> +#include <kstandarddirs.h> + +KBookmarkHandler::KBookmarkHandler( KDirOperator *parent, KPopupMenu* rootmenu ) + : QObject( parent, "KBookmarkHandler" ) + , KBookmarkOwner() +{ + const QString file = Amarok::saveLocation() + "fileBrowserBookmarks.xml"; + + KBookmarkManager *manager = KBookmarkManager::managerForFile( file, false ); + manager->setUpdate( true ); + manager->setShowNSBookmarks( false ); + + new KBookmarkMenu( manager, this, rootmenu, 0, true ); +} + +QString +KBookmarkHandler::currentURL() const +{ + return static_cast<KDirOperator*>(parent())->url().url(); +} + +void +KBookmarkHandler::openBookmarkURL( const QString &url ) +{ + static_cast<KDirOperator*>(parent())->setURL( KURL(url), true ); +} diff --git a/amarok/src/kbookmarkhandler.h b/amarok/src/kbookmarkhandler.h new file mode 100644 index 00000000..2abb4833 --- /dev/null +++ b/amarok/src/kbookmarkhandler.h @@ -0,0 +1,39 @@ +/* This file is part of the KDE project + Copyright (C) xxxx KFile Authors + Copyright (C) 2002 Anders Lund <anders.lund@lund.tdcadsl.dk> + + This library 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 library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Steet, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef _KBOOKMARKHANDLER_H_ +#define _KBOOKMARKHANDLER_H_ + +#include <kbookmarkmanager.h> +#include <qobject.h> + +class KDirOperator; +class KPopupMenu; + +class KBookmarkHandler : public QObject, public KBookmarkOwner +{ +public: + KBookmarkHandler( KDirOperator *parent, KPopupMenu* ); + + /// KBookmarkOwner interface: + virtual void openBookmarkURL( const QString &url ); + virtual QString currentURL() const; +}; + +#endif // _KBOOKMARKHANDLER_H_ diff --git a/amarok/src/konquisidebar/Makefile.am b/amarok/src/konquisidebar/Makefile.am new file mode 100644 index 00000000..14cb6491 --- /dev/null +++ b/amarok/src/konquisidebar/Makefile.am @@ -0,0 +1,21 @@ +INCLUDES = -I$(top_srcdir)/amarok/src/amarokcore $(all_includes) +METASOURCES = AUTO + +kde_module_LTLIBRARIES = konqsidebar_universalamarok.la + +konqsidebar_universalamarok_la_SOURCES = universalamarok.cpp amarokdcopiface.stub +konqsidebar_universalamarok_la_LDFLAGS = -module $(KDE_PLUGIN) $(all_libraries) -lkonqsidebarplugin +konqsidebar_universalamarok_la_LIBADD = $(LIB_KHTML) $(LIB_QT) $(LIB_KDECORE) -lDCOP $(LIB_KDEUI) + + +amarokdcopiface_DIR=$(top_srcdir)/amarok/src/amarokcore +universalamarok_add_DATA = amarok.desktop +universalamarok_adddir = $(kde_datadir)/konqsidebartng/add + +universalamarok_kickerentries_DATA = amarok.desktop +universalamarok_kickerentriesdir = $(kde_datadir)/konqsidebartng/kicker_entries + +universalamarok_entries_DATA = amarok.desktop +universalamarok_entriesdir = $(kde_datadir)/konqsidebartng/entries + +noinst_HEADERS = universalamarok.h diff --git a/amarok/src/konquisidebar/amarok.desktop b/amarok/src/konquisidebar/amarok.desktop new file mode 100644 index 00000000..c80ea67a --- /dev/null +++ b/amarok/src/konquisidebar/amarok.desktop @@ -0,0 +1,128 @@ +[Desktop Entry] +Encoding=UTF-8 +Type=Link +URL= +Name=Amarok +Name[bn]=আমারক +Name[el]=AmaroK +Name[ga]=AmaroK +Name[lt]=amaroK +Name[mk]=Амарок +Name[ne]=अमारोक +Name[pa]=ਅਮਰੋਕ +Name[ru]=amaroK +Name[zh_TW]=AmaroK +GenericName=Audio Player +GenericName[af]=Oudio Speler +GenericName[ar]=قارئى الصوت +GenericName[bg]=Аудио плеър +GenericName[bn]=অডিও প্লেয়ার +GenericName[br]=C'hoarier klevet +GenericName[ca]=Reproductor d'àudio +GenericName[cs]=Zvukový přehrávač +GenericName[da]=Lydafspiller +GenericName[de]=Audio-Wiedergabeprogramm +GenericName[el]=Αναπαραγωγή ήχου +GenericName[eo]=Aŭdludilo +GenericName[es]=Reproductor de audio +GenericName[et]=Helifailide mängija +GenericName[eu]=Audio-erreproduktorea +GenericName[fa]=پخش‌کنندۀ صوتی +GenericName[fi]=Musiikkisoitin +GenericName[fr]=Lecteur audio +GenericName[ga]=Seinnteoir Fuaime +GenericName[gl]=Reprodutor de Áudio +GenericName[hi]=ऑडियो प्लेयर +GenericName[hu]=Zenelejátszó +GenericName[is]=Tónlistarspilari +GenericName[it]=Lettore audio +GenericName[ja]=オーディオプレーヤ +GenericName[ka]=აუდიოდამკვრელი +GenericName[km]=កម្មវិធី​ចាក់​អូឌីយ៉ូ +GenericName[lt]=Muzikos grotuvas +GenericName[mk]=Аудио-изведувач +GenericName[mn]=Аудио-изведувач +GenericName[ms]=Pemain Audio +GenericName[nb]=Musikkavspiller +GenericName[nds]=Klangafspeler +GenericName[ne]=अडियो प्लेयर +GenericName[nl]=Audiospeler +GenericName[nn]=Lydspelar +GenericName[pa]=ਆਡੀਓ ਪਲੇਅਰ +GenericName[pl]=Odtwarzacz audio +GenericName[pt]=Leitor de Áudio +GenericName[pt_BR]=Reprodutor de Áudio +GenericName[ru]=Аудиоплеер +GenericName[se]=Jietnačuojaheaddji +GenericName[sk]=Audio prehrávač +GenericName[sl]=Predvajalnik zvoka +GenericName[sq]=Audio plejer +GenericName[sr]=Аудио плејер +GenericName[sr@Latn]=Audio plejer +GenericName[ss]=Audio plejer +GenericName[sv]=Ljudspelare +GenericName[ta]=கேட்பொலி இயக்கி +GenericName[tg]=Аудио Плейер +GenericName[th]=โปรแกรมเล่นเสียง +GenericName[tr]=Müzik Çalar +GenericName[uk]=Аудіопрогравач +GenericName[uz]=Audio-pleyer +GenericName[uz@cyrillic]=Аудио-плейер +GenericName[wa]=Djouweu d' muzike +GenericName[zh_CN]=音频播放器 +GenericName[zh_TW]=音樂播放程式 +Comment=Uncle Rodney says, "10/10, Amarok is seriously super!" +Comment[af]="Amarok is wonderlik!", volgens "Uncle Rodney"... +Comment[ar]= يقول عنترةُ بن شدًاد "10/10 AmaroK هو الافضل فعلأ ! " +Comment[bg]=Чичо Родни казва "10/10, Amarok наистина е супер!" +Comment[bn]=অ্যাঙ্কল রডনি বলেন, "আমারক সত্যিই দারুন, ১০/১০!" +Comment[ca]=El tiet Rodney diu, «10/10, l'Amarok és realment superior!» +Comment[cs]=Strýček Rodney říká, "10/10, Amarok je vážně super!" +Comment[da]=Onkel Rodney siger, "10/10, Amarok er virkelig super!" +Comment[de]=Onkel Rodney sagt: "10/10, Amarok is seriously super!" +Comment[el]=Ο θείος Rodney δηλώνει, "10/10, το AmaroK είναι θαυμάσιο!" +Comment[eo]=Ludoviko diras, "10/10, Amarok estas vere mojosa!" +Comment[es]=El tío Rodney dice, "'10/10, ¡Amarok es seriamente super!" +Comment[et]=Onu Ronni ütleb: "Täitsa lõpp, Amarok on ikka superhea!" +Comment[fi]=Uncle Rodney: ”10/10, Amarok is seriously super” +Comment[fr]=Oncle Rodney a dit « 10/10, Amarok est vraiment formidable ! » +Comment[ga]=Deir Uncail Rodney, "10/10 - tá AmaroK thar barr!" +Comment[gl]=O Tio Rodney di, "10/10, Amarok é excelente!" +Comment[hu]=Rodney bácsi szerint: "10/10, az Amarok egyszerűen szuper!" +Comment[is]=Rodney frændi segir, "10/10, Amarok er virkilega súper!" +Comment[it]=Zio Rodney dice, "10/10, Amarok è davvero super!" +Comment[ja]=ロドニーおじさんが "完璧だ、Amarok はマジですごい!" って言ってる。 +Comment[ka]=ვაკელი გოგოები ამბობენ - "10/10, Amarok დაცემაა!" +Comment[km]=លោកពូ​ច្រូច​និយាយ​ថា "១០/១០, Amarok គឺ​ពិត​ជា​អស្ចារ្យ​មែន !" +Comment[lt]=Dėdė Rodney sako: „10 balų iš 10, Amarok tikrai yra jėga!“ +Comment[mk]=Чичко Петре вели „Сериозно, Амарок е супер!“ +Comment[ms]=Pakcik Rodney berkata, "10/10, Amarok betul-betul hebat!" +Comment[nb]=Onkel Rodney sier at «Amarok er seriøst bra!» +Comment[nds]="Amarok is afsluuts basig!", seggt Unkel Rickmer jümmers. +Comment[ne]=अङ्कल रोडनेले भन्नुभयो, "10/10, अमारोक साँचिकै अतिउत्तम छ!" +Comment[nl]=Oom Rodney zegt, "10/10, Amarok is werkelijk fantastisch!" +Comment[nn]=Onkel Rodney seier: «10/10 – amaroK er verkeleg super!» +Comment[pa]=ਅੰਕਲ ਰੋਡੀਨੇ ਕਹਿੰਦੇ ਹਨ, "10/10, ਅਮਰੋਕ ਸਹੀਂ 'ਚ ਸੁਪਰ ਹੈ!" +Comment[pl]=Wujek Dobra Rada twierdzi: "10/10, Amarok jest po prostu super!" +Comment[pt]=O Tio Rodney diz, "10/10, Amarok é excelente!" +Comment[pt_BR]=O Tio Rodney diz: "10/10, o Amarok é mesmo muito bom!" +Comment[ru]=amaroK - просто лучший плеер. +Comment[sk]=Uncle Rodney vraví, "10/10, Amarok je vážne super!" +Comment[sr]=Ујка Жика каже, „10/10, Amarok је заиста супер!“ +Comment[sr@Latn]=Ujka Žika kaže, „10/10, Amarok je zaista super!“ +Comment[sv]=Farbror Rodney säger, "10/10, Amarok är verkligen fantastiskt!" +Comment[th]=ลุง Rodney บอกว่า, "10/10, Amarok นี่ขั้นสุดยอด!" +Comment[tr]=Rodney Amca der ki, "10/10, Amarok cidden süper!" +Comment[uk]=Вуйко Петро каже: "10/10, Amarok - це дійсно супер!" +Comment[uz]=Imkoniyati boy audio-pleyer +Comment[uz@cyrillic]=Имконияти бой аудио-плейер +Comment[wa]=Mononke Rodney dit "10/10, Amarok est vormint clapant!" +Comment[zh_CN]=amaroK,开启您的音乐探秘之旅 +Comment[zh_TW]=Uncle Rodney 說:「10/10, amaroK is seriously super!」 +Icon=amarok + +Open=true +X-KDE-KonqSidebarModule=konqsidebar_universalamarok +X-KDE-KonqSidebarUniversal=true +X-KDE-KonqSidebarAddModule=konqsidebar_universalamarok + diff --git a/amarok/src/konquisidebar/universalamarok.cpp b/amarok/src/konquisidebar/universalamarok.cpp new file mode 100644 index 00000000..ff704ea4 --- /dev/null +++ b/amarok/src/konquisidebar/universalamarok.cpp @@ -0,0 +1,324 @@ +/*************************************************************************** + * Copyright (C) 2004 by Marco Gulino * + * marco@Paganini * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Steet, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "universalamarok.h" +#include "amarokdcopiface_stub.h" + + +#include <qlabel.h> +#include <kinstance.h> +#include <klocale.h> +#include <qstring.h> +#include <qwidget.h> +#include <kglobal.h> +#include <kstandarddirs.h> +#include <qlayout.h> +#include <qtimer.h> +#include <dcopclient.h> +#include <kmessagebox.h> +#include <kpushbutton.h> +#include <kiconloader.h> +#include <qdatetime.h> +#include <qfileinfo.h> +#include <ktoolbar.h> +#include <kapplication.h> +#include <qslider.h> +#include <kdebug.h> +#include <kurldrag.h> +#include <khtmlview.h> + +#define HTML_FILE KGlobal::dirs()->saveLocation( "data", "amarok/", true ) + "contextbrowser.html" + +amarokWidget::amarokWidget( QWidget * parent, const char * name, WFlags f ) + : QVBox(parent, name, f) +{ + setAcceptDrops(true); +} +void amarokWidget::dragEnterEvent(QDragEnterEvent* event) +{ + event->accept( KURLDrag::canDecode(event) ); +} + +void amarokWidget::dropEvent(QDropEvent* event) +{ + KURL::List urlList; + if( KURLDrag::decode(event, urlList) ) + { + KURL::List::iterator it; + KURL::List::iterator end( urlList.end() ); + for (it = urlList.begin(); it != end; ++it ) + emit emitURL(*it); + } +} + +bool amarokWidget::eventFilter( QObject *, QEvent *e ) +{ + if(e->type() < QEvent::DragEnter || e->type() > QEvent::Drop ) + return false; + QApplication::sendEvent(this, e); + return true; +} + +UniversalAmarok::UniversalAmarok(KInstance *inst,QObject *parent,QWidget *widgetParent, QString &desktopName, const char* name): + KonqSidebarPlugin(inst,parent,widgetParent,desktopName,name) +{ + KGlobal::iconLoader()->addAppDir( "amarok" ); + widget = new amarokWidget( widgetParent ); +// widgetParent->resize(580,300); + KToolBar *topBar = new KToolBar( widget, "Topbar" ); + topBar->setIconSize(16); + topBar->insertButton( "today", 0, SIGNAL( clicked() ), this, SLOT( currentTrack() ) ); + topBar->insertButton( "document", 0, SIGNAL( clicked() ), this, SLOT( lyrics() ) ); + topBar->insertButton( "personal", 0, SIGNAL( clicked() ), this, SLOT( wiki() ) ); + + browser = new KHTMLPart(widget, "widget-browser"); +//browser=new KHTMLPart(widget); + kdDebug() << "parentPart() << " << browser->parentPart() << endl; + browser->setDNDEnabled( true ); + browser->setEncoding( "utf8", true ); + updateBrowser( HTML_FILE ); + browser->view()->installEventFilter( widget ); + amarokDCOP = new DCOPClient(); + amarokDCOP->attach(); + + playerStub = new AmarokPlayerInterface_stub( amarokDCOP, "amarok", "player"); + playlistStub = new AmarokPlaylistInterface_stub( amarokDCOP, "amarok", "playlist"); + contextStub = new AmarokContextBrowserInterface_stub (amarokDCOP, "amarok", "contextbrowser"); + + KToolBar* toolBar=new KToolBar(widget, "PlayerControls"); + + toolBar->setIconSize(16); + toolBar->insertButton( "player_start",0, SIGNAL( clicked() ), this, SLOT( sendPrev() ) ); + toolBar->insertButton( "player_play", 0, SIGNAL( clicked() ), this, SLOT( sendPlay() ) ); + toolBar->insertButton( "player_pause",0, SIGNAL( clicked() ), this, SLOT( sendPause() ) ); + toolBar->insertButton( "player_stop", 0, SIGNAL( clicked() ), this, SLOT( sendStop() ) ); + toolBar->insertButton( "player_end", 0, SIGNAL( clicked() ), this, SLOT( sendNext() ) ); + + toolBar->insertSeparator(); + toolBar->insertButton( "arts", 0, SIGNAL( clicked() ), this, SLOT( sendMute() ) ); + + vol_slider = new QSlider(0,100,1,0,Qt::Horizontal, toolBar,"volume"); + vol_slider->setLineStep(2); + + connect(vol_slider, SIGNAL( valueChanged(int) ), this, SLOT(volChanged(int ) ) ); + toolBar->insertWidget(1,2, vol_slider); + + fileInfo = new QFileInfo(HTML_FILE); + QTimer *t = new QTimer( this ); + + connect( t, SIGNAL(timeout()), SLOT(updateStatus() ) ); + t->start( 2000, false ); + kdDebug() << "Connecting widget signal" << endl; + + connect( widget, SIGNAL( emitURL( const KURL &)), + this, SLOT( openURLRequest( const KURL &) ) ); + connect( browser->browserExtension(), SIGNAL( openURLRequest( const KURL &, const KParts::URLArgs & ) ), + this, SLOT( openURLRequest( const KURL & ) ) ); + widget->show(); +} + + +UniversalAmarok::~UniversalAmarok() +{ + delete fileInfo; +} + + +#include "universalamarok.moc" + +#if ! KDE_IS_VERSION(3,4,0) +#define KDE_EXPORT __attribute__ ((visibility("default"))) +#endif + +// FIXME: is this referenced from anywhere ??! + +extern "C" +{ + KDE_EXPORT void* create_konqsidebar_universalamarok(KInstance *instance,QObject *par,QWidget *widp,QString &desktopname,const char *name) + { + KGlobal::locale()->insertCatalogue( "amarok" ); + return new UniversalAmarok(instance,par,widp,desktopname,name); + } +} + +// FIXME: Is this referenced from anywhere ??! +extern "C" +{ + KDE_EXPORT bool add_konqsidebar_universalamarok(QString* fn, QString* param, QMap<QString,QString> *map) + { + Q_UNUSED(param); + + map->insert ("Type", "Link"); + map->insert ("URL", ""); + map->insert ("Icon", "amarok"); + map->insert ("Name", i18n ("Amarok")); + map->insert ("Open", "true"); + map->insert ("X-KDE-KonqSidebarModule","konqsidebar_universalamarok"); + fn->setLatin1 ("amarok.desktop"); + return true; + } +} + + +/*! + \fn UniversalAmarok::updateBrowser() + */ +void UniversalAmarok::updateBrowser(const QString& file) +{ + if (! (QFile::exists(file) ) ) + { + showIntroduction(); + return; + } + QString text; + QFile f_file(file); + if( f_file.open(IO_ReadOnly) ) + { + QTextStream stream( &f_file ); + stream.setEncoding( QTextStream::UnicodeUTF8 ); + QString line; + while ( !stream.atEnd() ) { + line = stream.readLine(); // line of text excluding '\n' + text += QString("\n") + line; + } f_file.close(); + text=text.replace("<img id='current_box-largecover-image' ", "<img id='current_box-largecover-image' width=70 height=70 "); + browser->begin(); + browser->write(text); + browser->end(); + } else + browser->openURL(file); +} + + +/*! + \fn UniversalAmarok::updateStatus() + */ +void UniversalAmarok::updateStatus() +{ + checkForAmarok(); + vol_slider->setValue( playerStub->getVolume() ); + fileInfo->refresh(); + if( fileInfo->lastModified() != fileDT ) + { + updateBrowser( HTML_FILE ); + fileDT=fileInfo->lastModified(); + } +} + + +/*! + \fn UniversalAmarok::getCurrentPlaying() + */ +QString UniversalAmarok::getCurrentPlaying() +{ + return playerStub->nowPlaying(); +} + + +/*! + \fn UniversalAmarok::openURLRequest( const KURL &url ) + */ +void UniversalAmarok::openURLRequest( const KURL &url ) +{ + if( ! url.isValid() ) return; + if (url.url() == "run:amarok") { + runAmarok(); + return; + } + checkForAmarok(); + playlistStub->playMedia(url); +} + + +/*! + \fn UniversalAmarok::checkForAmarok() + */ +void UniversalAmarok::checkForAmarok() +{ + if(!amarokDCOP->isApplicationRegistered("amarok")) + noAmarokRunning(); +} + + +void UniversalAmarok::noAmarokRunning() { + QString m_HTMLSource=""; + m_HTMLSource.append( + "<html>" + "<div id='introduction_box' class='box'>" + "<div id='introduction_box-header' class='box-header'>" + "<span id='introduction_box-header-title' class='box-header-title'>" + + i18n( "Amarok is not running!" ) + + "</span>" + "</div>" + "<div id='introduction_box-body' class='box-body'>" + "<p>" + + i18n( "To run Amarok, just click on the link below: " + ) + + "</p>" + "<a href='run:amarok' class='button'>" + i18n( "Run Amarok..." ) + "</a>" + "</div>" + "</div>" + "</html>" + ); + browser->begin(); + browser->write( m_HTMLSource ); + browser->end(); +} + +void UniversalAmarok::runAmarok() { + KApplication::kdeinitExecWait("amarok"); +} + + + +void UniversalAmarok::volChanged(int vol) +{ + checkForAmarok(); + playerStub->setVolume(vol); +} + +void UniversalAmarok::showIntroduction() +{ + QString m_HTMLSource=""; + m_HTMLSource.append( + "<html>" + "<div id='introduction_box' class='box'>" + "<div id='introduction_box-header' class='box-header'>" + "<span id='introduction_box-header-title' class='box-header-title'>" + + i18n( "Hello Amarok user!" ) + + "</span>" + "</div>" + "<div id='introduction_box-body' class='box-body'>" + "<p>" + + i18n( "This is the Context Browser: " + "it shows you contextual information about the currently playing track. " + "In order to use this feature of Amarok, you need to build a Collection." + ) + + "</p>" + "<a href='show:collectionSetup' class='button'>" + i18n( "Build Collection..." ) + "</a>" + "</div>" + "</div>" + "</html>" + ); +kdDebug() << m_HTMLSource << endl; + browser->begin(); + browser->write( m_HTMLSource ); + browser->end(); +} diff --git a/amarok/src/konquisidebar/universalamarok.h b/amarok/src/konquisidebar/universalamarok.h new file mode 100644 index 00000000..6227cb11 --- /dev/null +++ b/amarok/src/konquisidebar/universalamarok.h @@ -0,0 +1,102 @@ +/*************************************************************************** + * Copyright (C) 2004 by Marco Gulino * + * marco@Paganini * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Steet, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ +#ifndef KONQUERORSIDEBAR_H +#define KONQUERORSIDEBAR_H + +#include <khtml_part.h> +#include <konqsidebarplugin.h> +#include <dcopclient.h> +#include <qslider.h> +#include <qvbox.h> +#include <khtmlview.h> +#include <kurl.h> +#include "amarokdcopiface_stub.h" + +/** +@author Marco Gulino +*/ + +class universalamarokwidget; +class DCOPClient; +class QFileInfo; +class QDateTime; + +class amarokWidget : public QVBox +{ +Q_OBJECT +public: + amarokWidget( QWidget * parent = 0, const char * name = 0, WFlags f = 0 ); + +protected: + virtual void dragEnterEvent ( QDragEnterEvent * ); + virtual void dropEvent(QDropEvent*); + bool eventFilter( QObject *o, QEvent *e ); + +signals: + void emitURL( const KURL &); +}; + + +class UniversalAmarok : public KonqSidebarPlugin +{ +Q_OBJECT +public: + UniversalAmarok(KInstance *inst,QObject *parent,QWidget *widgetParent, QString &desktopName, const char* name=0); + + ~UniversalAmarok(); + + virtual QWidget *getWidget(){return (QWidget*)widget;} + virtual void *provides(const QString &) {return 0;} + virtual void handleURL(const KURL& /*url*/) {} + QString getCurrentPlaying(); + void showIntroduction(); + +private: + amarokWidget* widget; + KHTMLPart* browser; + QString amarokPlaying; + DCOPClient* amarokDCOP; + QFileInfo* fileInfo; + QDateTime fileDT; + QSlider* vol_slider; + AmarokPlayerInterface_stub *playerStub; + AmarokPlaylistInterface_stub *playlistStub; + AmarokContextBrowserInterface_stub *contextStub; + +public slots: + void updateBrowser(const QString&); + void updateStatus(); + void sendPrev() { checkForAmarok(); playerStub->prev(); } + void sendPlay() { checkForAmarok(); playerStub->play(); } + void sendPause() { checkForAmarok(); playerStub->pause(); } + void sendStop() { checkForAmarok(); playerStub->stop(); } + void sendNext() { checkForAmarok(); playerStub->next(); } + void sendMute() { checkForAmarok(); playerStub->mute(); } + void volChanged(int vol); + void openURLRequest( const KURL & ); + void checkForAmarok(); + void noAmarokRunning(); + void runAmarok(); + void lyrics() { contextStub->showLyrics(); } + void currentTrack() { contextStub->showCurrentTrack(); } + void wiki() { contextStub->showWiki(); } +}; + +#endif diff --git a/amarok/src/ktrm.cpp b/amarok/src/ktrm.cpp new file mode 100644 index 00000000..cd102376 --- /dev/null +++ b/amarok/src/ktrm.cpp @@ -0,0 +1,892 @@ +/*************************************************************************** + copyright : (C) 2004 by Scott Wheeler + email : wheeler@kde.org + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Steet, Fifth Floor, Boston, MA 02110-1301 * + * USA * + ***************************************************************************/ +#include "config.h" +#include "debug.h" +#include "statusbar.h" +#define DEBUG_PREFIX "KTRM" + +#include "ktrm.h" + +#include <kapplication.h> +#include <kio/job.h> +#include <kio/jobclasses.h> +#include <kprotocolmanager.h> +#include <kurl.h> +#include <kresolver.h> + +#include <qdom.h> +#include <qmutex.h> +#include <qevent.h> +#include <qobject.h> +#include <qfile.h> +#include <qregexp.h> + +#if HAVE_TUNEPIMP + +#if HAVE_TUNEPIMP >= 5 +#include <tunepimp-0.5/tp_c.h> +#else +#include <tunepimp/tp_c.h> +#endif + +class KTRMLookup; + +extern "C" +{ +#if HAVE_TUNEPIMP >= 4 + static void TRMNotifyCallback(tunepimp_t pimp, void *data, TPCallbackEnum type, int fileId, TPFileStatus status); +#else + static void TRMNotifyCallback(tunepimp_t pimp, void *data, TPCallbackEnum type, int fileId); +#endif +} + +/** + * This represents the main TunePimp instance and handles incoming requests. + */ + +class KTRMRequestHandler +{ +public: + static KTRMRequestHandler *instance() + { + static QMutex mutex; + mutex.lock(); + static KTRMRequestHandler handler; + mutex.unlock(); + return &handler; + } + + int startLookup(KTRMLookup *lookup) + { + int id; + if(!m_fileMap.contains(lookup->file())) { +#if HAVE_TUNEPIMP >= 4 + id = tp_AddFile(m_pimp, QFile::encodeName(lookup->file()), 0); +#else + id = tp_AddFile(m_pimp, QFile::encodeName(lookup->file())); +#endif + m_fileMap.insert(lookup->file(), id); + } + else { + id = m_fileMap[lookup->file()]; + tp_IdentifyAgain(m_pimp, id); + } + m_lookupMap[id] = lookup; + return id; + } + + void endLookup(KTRMLookup *lookup) + { + tp_ReleaseTrack(m_pimp, tp_GetTrack(m_pimp, lookup->fileId())); + tp_Remove(m_pimp, lookup->fileId()); + m_lookupMapMutex.lock(); + m_lookupMap.remove(lookup->fileId()); + m_fileMap.remove( lookup->file() ); + m_lookupMapMutex.unlock(); + } + + bool lookupMapContains(int fileId) const + { + m_lookupMapMutex.lock(); + bool contains = m_lookupMap.contains(fileId); + m_lookupMapMutex.unlock(); + return contains; + } + + KTRMLookup *lookup(int fileId) const + { + m_lookupMapMutex.lock(); + KTRMLookup *l = m_lookupMap[fileId]; + m_lookupMapMutex.unlock(); + return l; + } + + void removeFromLookupMap(int fileId) + { + m_lookupMapMutex.lock(); + m_lookupMap.remove(fileId); + m_lookupMapMutex.unlock(); + } + + const tunepimp_t &tunePimp() const + { + return m_pimp; + } + +protected: + KTRMRequestHandler() + { + m_pimp = tp_New("KTRM", "0.1"); + //tp_SetDebug(m_pimp, true); +#if HAVE_TUNEPIMP < 5 + tp_SetTRMCollisionThreshold(m_pimp, 100); + tp_SetAutoFileLookup(m_pimp,true); +#endif + tp_SetAutoSaveThreshold(m_pimp, -1); + tp_SetMoveFiles(m_pimp, false); + tp_SetRenameFiles(m_pimp, false); +#if HAVE_TUNEPIMP >= 4 + tp_SetFileNameEncoding(m_pimp, "UTF-8"); +#else + tp_SetUseUTF8(m_pimp, true); +#endif + tp_SetNotifyCallback(m_pimp, TRMNotifyCallback, 0); + +#if HAVE_TUNEPIMP < 5 + KProtocolManager::reparseConfiguration(); + + if(KProtocolManager::useProxy()) { + QString noProxiesFor = KProtocolManager::noProxyFor(); + QStringList noProxies = QStringList::split(QRegExp("[',''\t'' ']"), noProxiesFor); + bool useProxy = true; + + char server[255]; + short port; + tp_GetServer(m_pimp, server, 255, &port); + QString tunepimpHost = QString(server); + QString tunepimpHostWithPort = (tunepimpHost + ":%1").arg(port); + + for(QStringList::ConstIterator it = noProxies.constBegin(); it != noProxies.constEnd(); ++it) { + QString normalizedHost = KNetwork::KResolver::normalizeDomain(*it); + if(normalizedHost == tunepimpHost || + tunepimpHost.endsWith('.' + normalizedHost)) { + useProxy = false; + break; + } + + if(normalizedHost == tunepimpHostWithPort || + tunepimpHostWithPort.endsWith('.' + normalizedHost)) { + useProxy = false; + break; + } + } + + if(KProtocolManager::useReverseProxy()) + useProxy = !useProxy; + + if(useProxy) { + KURL proxy = KProtocolManager::proxyFor("http"); + tp_SetProxy(m_pimp, proxy.host().latin1(), short(proxy.port())); + } + } +#else + tp_SetMusicDNSClientId(m_pimp, "0c6019606b1d8a54d0985e448f3603ca"); +#endif + } + + ~KTRMRequestHandler() + { + tp_Delete(m_pimp); + } + +private: + tunepimp_t m_pimp; + QMap<int, KTRMLookup *> m_lookupMap; + QMap<QString, int> m_fileMap; + mutable QMutex m_lookupMapMutex; +}; + + +/** + * A custom event type used for signalling that a TRM lookup is finished. + */ + +class KTRMEvent : public QCustomEvent +{ +public: + enum Status { + Recognized, + Unrecognized, + Collision, + PuidGenerated, + Error + }; + + KTRMEvent(int fileId, Status status) : + QCustomEvent(id), + m_fileId(fileId), + m_status(status) {} + + int fileId() const + { + return m_fileId; + } + + Status status() const + { + return m_status; + } + + static const int id = User + 1984; // random, unique, event id + +private: + int m_fileId; + Status m_status; +}; + +/** + * A helper class to intercept KTRMQueryEvents and call recognized() (from the GUI + * thread) for the lookup. + */ + +class KTRMEventHandler : public QObject +{ +public: + static void send(int fileId, KTRMEvent::Status status) + { + KApplication::postEvent(instance(), new KTRMEvent(fileId, status)); + } + +protected: + KTRMEventHandler() : QObject() {} + + static KTRMEventHandler *instance() + { + static QMutex mutex; + mutex.lock(); + static KTRMEventHandler handler; + mutex.unlock(); + return &handler; + } + + virtual void customEvent(QCustomEvent *event) + { + if(!event->type() == KTRMEvent::id) + return; + + KTRMEvent *e = static_cast<KTRMEvent *>(event); + + static QMutex mutex; + mutex.lock(); + + if(!KTRMRequestHandler::instance()->lookupMapContains(e->fileId())) { + mutex.unlock(); + return; + } + + KTRMLookup *lookup = KTRMRequestHandler::instance()->lookup(e->fileId()); +#if HAVE_TUNEPIMP >= 4 + if ( e->status() != KTRMEvent::Unrecognized) +#endif + KTRMRequestHandler::instance()->removeFromLookupMap(e->fileId()); + + mutex.unlock(); + + switch(e->status()) { + case KTRMEvent::Recognized: + lookup->recognized(); + break; + case KTRMEvent::Unrecognized: + lookup->unrecognized(); + break; + case KTRMEvent::Collision: + lookup->collision(); + break; + case KTRMEvent::PuidGenerated: + lookup->puidGenerated(); + break; + case KTRMEvent::Error: + lookup->error(); + break; + } + } +}; + +/** + * Callback function for TunePimp lookup events. + */ +#if HAVE_TUNEPIMP >= 4 +static void TRMNotifyCallback(tunepimp_t /*pimp*/, void */*data*/, TPCallbackEnum type, int fileId, TPFileStatus status) +#else +static void TRMNotifyCallback(tunepimp_t pimp, void */*data*/, TPCallbackEnum type, int fileId) +#endif +{ + if(type != tpFileChanged) + return; + +#if HAVE_TUNEPIMP < 4 + track_t track = tp_GetTrack(pimp, fileId); + TPFileStatus status = tr_GetStatus(track); +#endif + //debug() << "Status is: " << status << endl; + + switch(status) { + case eRecognized: + KTRMEventHandler::send(fileId, KTRMEvent::Recognized); + break; + case eUnrecognized: + KTRMEventHandler::send(fileId, KTRMEvent::Unrecognized); + break; +#if HAVE_TUNEPIMP >= 5 + case ePUIDLookup: + case ePUIDCollision: + case eFileLookup: + KTRMEventHandler::send(fileId, KTRMEvent::PuidGenerated); + break; +#else + case eTRMCollision: +#if HAVE_TUNEPIMP >= 4 + case eUserSelection: +#endif + KTRMEventHandler::send(fileId, KTRMEvent::Collision); + break; +#endif + case eError: + KTRMEventHandler::send(fileId, KTRMEvent::Error); + break; + default: + break; + } +#if HAVE_TUNEPIMP < 4 + tp_ReleaseTrack(pimp, track); +#endif +} + +//////////////////////////////////////////////////////////////////////////////// +// KTRMResult implementation +//////////////////////////////////////////////////////////////////////////////// + +class KTRMResult::KTRMResultPrivate +{ +public: + KTRMResultPrivate() : track(0), year(0), relevance(0) {} + QString title; + QString artist; + QString album; + int track; + int year; + double relevance; + + bool operator== (const KTRMResultPrivate &r) const; +}; + +bool KTRMResult::KTRMResultPrivate::operator==(const KTRMResultPrivate &r) const +{ + return ( + title == r.title && + artist == r.artist && + album == r.album && + track == r.track && + year == r.year && + relevance == r.relevance + ); +} + +//////////////////////////////////////////////////////////////////////////////// +// KTRMResult public methods +//////////////////////////////////////////////////////////////////////////////// +#endif +KTRMResult::KTRMResult() +{ +#if HAVE_TUNEPIMP + d = new KTRMResultPrivate; +#endif +} + +KTRMResult::KTRMResult(const KTRMResult &result) +{ +#if HAVE_TUNEPIMP + d = new KTRMResultPrivate(*result.d); +#else + Q_UNUSED(result); +#endif +} + +KTRMResult::~KTRMResult() +{ +#if HAVE_TUNEPIMP + delete d; +#endif +} + +QString KTRMResult::title() const +{ +#if HAVE_TUNEPIMP + return d->title; +#else + return QString(); +#endif +} + +QString KTRMResult::artist() const +{ +#if HAVE_TUNEPIMP + return d->artist; +#else + return QString(); +#endif +} + +QString KTRMResult::album() const +{ +#if HAVE_TUNEPIMP + return d->album; +#else + return QString(); +#endif +} + +int KTRMResult::track() const +{ +#if HAVE_TUNEPIMP + return d->track; +#else + return 0; +#endif +} + +int KTRMResult::year() const +{ +#if HAVE_TUNEPIMP + return d->year; +#else + return 0; +#endif +} + +bool KTRMResult::operator<(const KTRMResult &r) const +{ +#if HAVE_TUNEPIMP + return r.d->relevance < d->relevance; +#else + Q_UNUSED(r); + return false; +#endif +} + +bool KTRMResult::operator>(const KTRMResult &r) const +{ +#if HAVE_TUNEPIMP + return r.d->relevance > d->relevance; +#else + Q_UNUSED(r); + return true; +#endif +} + +KTRMResult &KTRMResult::operator= (const KTRMResult &r) +{ +#if HAVE_TUNEPIMP + d = new KTRMResultPrivate(*r.d); +#else + Q_UNUSED(r); +#endif + return *this; +} + +bool KTRMResult::operator== (const KTRMResult &r) const +{ +#if HAVE_TUNEPIMP + return *d == *(r.d); +#else + Q_UNUSED(r); +#endif + return false; +} + + +bool KTRMResult::isEmpty() const +{ +#if HAVE_TUNEPIMP + return d->title.isEmpty() && d->artist.isEmpty() && d->album.isEmpty() && + d->track == 0 && d->year == 0; +#else + return true; +#endif +} +#if HAVE_TUNEPIMP +//////////////////////////////////////////////////////////////////////////////// +// KTRMLookup implementation +//////////////////////////////////////////////////////////////////////////////// + +class KTRMLookup::KTRMLookupPrivate +{ +public: + KTRMLookupPrivate() : + fileId(-1), autoDelete(false) {} + QString file; + QString errorString; + KTRMResultList results; + int fileId; + bool autoDelete; +}; +#endif +//////////////////////////////////////////////////////////////////////////////// +// KTRMLookup public methods +//////////////////////////////////////////////////////////////////////////////// + +KTRMLookup::KTRMLookup(const QString &file, bool autoDelete) + : QObject() +{ +#if HAVE_TUNEPIMP + d = new KTRMLookupPrivate; + d->file = file; + d->autoDelete = autoDelete; + d->fileId = KTRMRequestHandler::instance()->startLookup(this); +#else + Q_UNUSED(file); + Q_UNUSED(autoDelete); +#endif +} + +KTRMLookup::~KTRMLookup() +{ +#if HAVE_TUNEPIMP + KTRMRequestHandler::instance()->endLookup(this); + delete d; +#endif +} + +QString KTRMLookup::file() const +{ +#if HAVE_TUNEPIMP + return d->file; +#else + return QString(); +#endif +} + +int KTRMLookup::fileId() const +{ +#if HAVE_TUNEPIMP + return d->fileId; +#else + return -1; +#endif +} + +void KTRMLookup::recognized() +{ +#if HAVE_TUNEPIMP + debug() << k_funcinfo << d->file << endl; + + d->results.clear(); + + metadata_t *metaData = md_New(); + track_t track = tp_GetTrack(KTRMRequestHandler::instance()->tunePimp(), d->fileId); + tr_Lock(track); + tr_GetServerMetadata(track, metaData); + + KTRMResult result; + + result.d->title = QString::fromUtf8(metaData->track); + result.d->artist = QString::fromUtf8(metaData->artist); + result.d->album = QString::fromUtf8(metaData->album); + result.d->track = metaData->trackNum; + result.d->year = metaData->releaseYear; + + d->results.append(result); + + md_Delete(metaData); + tr_Unlock(track); + finished(); +#endif +} + +void KTRMLookup::unrecognized() +{ +#if HAVE_TUNEPIMP + debug() << k_funcinfo << d->file << endl; + #if HAVE_TUNEPIMP >= 4 + char trm[255]; + bool finish = false; + trm[0] = 0; + track_t track = tp_GetTrack(KTRMRequestHandler::instance()->tunePimp(), d->fileId); + tr_Lock(track); +#if HAVE_TUNEPIMP >= 5 + tr_GetPUID(track, trm, 255); +#else + tr_GetTRM(track, trm, 255); +#endif + if ( !trm[0] ) { + tr_SetStatus(track, ePending); + tp_Wake(KTRMRequestHandler::instance()->tunePimp(), track); + } + else + finish = true; + tr_Unlock(track); + tp_ReleaseTrack(KTRMRequestHandler::instance()->tunePimp(), track); + if ( !finish ) + return; + #endif + d->results.clear(); + finished(); + +#endif +} + +void KTRMLookup::collision() +{ +#if HAVE_TUNEPIMP && HAVE_TUNEPIMP < 5 + debug() << k_funcinfo << d->file << endl; + + track_t track = tp_GetTrack(KTRMRequestHandler::instance()->tunePimp(), d->fileId); + + if(track <= 0) { + debug() << "invalid track number" << endl; + return; + } + + tr_Lock(track); + int resultCount = tr_GetNumResults(track); + + QStringList strList = QStringList::split ( '/', d->file ); + + metadata_t *mdata = md_New(); + strList.append( QString::fromUtf8(mdata->track) ); + strList.append( QString::fromUtf8(mdata->artist) ); + strList.append( QString::fromUtf8(mdata->album) ); + md_Clear(mdata); + + if(resultCount > 0) { + TPResultType type; + result_t *results = new result_t[resultCount]; + tr_GetResults(track, &type, results, &resultCount); + + switch(type) { + case eNone: + debug() << k_funcinfo << "eNone" << endl; + break; + case eArtistList: + debug() << "eArtistList" << endl; + break; + case eAlbumList: + debug() << "eAlbumList" << endl; + break; + case eTrackList: + { + debug() << "eTrackList" << endl; + albumtrackresult_t **tracks = reinterpret_cast<albumtrackresult_t **>( results ); + d->results.clear(); + + for(int i = 0; i < resultCount; i++) { + KTRMResult result; + + result.d->title = QString::fromUtf8(tracks[i]->name); +#if HAVE_TUNEPIMP >= 4 + result.d->artist = QString::fromUtf8(tracks[i]->artist.name); + result.d->album = QString::fromUtf8(tracks[i]->album.name); + result.d->year = tracks[i]->album.releaseYear; +#else + result.d->artist = QString::fromUtf8(tracks[i]->artist->name); + result.d->album = QString::fromUtf8(tracks[i]->album->name); + result.d->year = tracks[i]->album->releaseYear; +#endif + result.d->track = tracks[i]->trackNum; + result.d->relevance = + 4 * stringSimilarity(strList,result.d->title) + + 2 * stringSimilarity(strList,result.d->artist) + + 1 * stringSimilarity(strList,result.d->album); + + if(!d->results.contains(result)) d->results.append(result); + } + break; + } + case eMatchedTrack: + debug() << k_funcinfo << "eMatchedTrack" << endl; + break; + } + + delete [] results; + } + + tr_Unlock(track); + tp_ReleaseTrack(KTRMRequestHandler::instance()->tunePimp(), track); + qHeapSort(d->results); + + finished(); +#endif +} + +void KTRMLookup::puidGenerated() +{ +#if HAVE_TUNEPIMP >= 5 + DEBUG_BLOCK + debug() << k_funcinfo << d->file << endl; + char puid[255] = {0}; + track_t track = tp_GetTrack(KTRMRequestHandler::instance()->tunePimp(), d->fileId); + tr_Lock(track); + + tr_GetPUID(track, puid, 255); + debug() << puid << endl; + tr_Unlock(track); + tp_ReleaseTrack(KTRMRequestHandler::instance()->tunePimp(), track); + d->results.clear(); + + KIO::Job *job = KIO::storedGet( QString( "http://musicbrainz.org/ws/1/track/?type=xml&puid=%1" ).arg( puid ) , false, false ); + + Amarok::StatusBar::instance()->newProgressOperation( job ) + .setDescription( i18n( "MusicBrainz Lookup" ) ); + + connect( job, SIGNAL( result( KIO::Job* ) ), SLOT( lookupResult( KIO::Job* ) ) ); +#endif +} + +void KTRMLookup::lookupResult( KIO::Job* job ) +{ +#if HAVE_TUNEPIMP >= 5 + DEBUG_BLOCK + if ( !job->error() == 0 ) { + warning() << "[MusicBrainzLookup] KIO error! errno: " << job->error() << endl; + Amarok::StatusBar::instance()->longMessage( "Couldn't connect to MusicBrainz server." ); + finished(); + return; + } + KIO::StoredTransferJob* const storedJob = static_cast<KIO::StoredTransferJob*>( job ); + QString xml = QString::fromUtf8( storedJob->data().data(), storedJob->data().size() ); + + QDomDocument doc; + QDomElement e; + + if( !doc.setContent( xml ) ) { + warning() << "[MusicBrainzLookup] Invalid XML" << endl; + Amarok::StatusBar::instance()->longMessage( "MusicBrainz returned invalid content." ); + finished(); + return; + } + + e = doc.namedItem( "metadata" ).toElement().namedItem( "track-list" ).toElement(); + + QStringList strList = QStringList::split ( '/', d->file ); + + QDomNode n = e.namedItem("track"); + for( ; !n.isNull(); n = n.nextSibling() ) { + QDomElement track = n.toElement(); + KTRMResult result; + + result.d->title = track.namedItem( "title" ).toElement().text(); + result.d->artist = track.namedItem( "artist" ).toElement().namedItem( "name" ).toElement().text(); + QDomNode releaseNode = track.namedItem("release-list").toElement().namedItem("release"); + for( ; !releaseNode.isNull(); releaseNode = releaseNode.nextSibling() ) { + KTRMResult tmpResult( result ); + QDomElement release = releaseNode.toElement(); + + tmpResult.d->album = release.namedItem( "title" ).toElement().text(); + QDomNode tracklistN = release.namedItem( "track-list" ); + if ( !tracklistN.isNull() ) { + QDomElement tracklist = tracklistN.toElement(); + if ( !tracklist.attribute( "offset" ).isEmpty() ) + tmpResult.d->track = tracklist.attribute( "offset" ).toInt() + 1; + } + //tmpResult.d->year = ???; + tmpResult.d->relevance = + 4 * stringSimilarity(strList,tmpResult.d->title) + + 2 * stringSimilarity(strList,tmpResult.d->artist) + + 1 * stringSimilarity(strList,tmpResult.d->album); + if( !d->results.contains( tmpResult ) ) + d->results.append( tmpResult ); + } + } + + qHeapSort(d->results); + + finished(); +#else + Q_UNUSED( job ); +#endif +} + +void KTRMLookup::error() +{ +#if HAVE_TUNEPIMP + debug() << k_funcinfo << d->file << endl; + track_t track = tp_GetTrack(KTRMRequestHandler::instance()->tunePimp(), d->fileId); + char error[1000]; + tr_GetError( track, error, 1000); + debug() << "Error: " << error << endl; + d->errorString = error; + d->results.clear(); + finished(); +#endif +} + +KTRMResultList KTRMLookup::results() const +{ +#if HAVE_TUNEPIMP + return d->results; +#else + return KTRMResultList(); +#endif +} + +//////////////////////////////////////////////////////////////////////////////// +// KTRMLookup protected methods +//////////////////////////////////////////////////////////////////////////////// + +void KTRMLookup::finished() + +{ +#if HAVE_TUNEPIMP + emit sigResult( results(), d->errorString ); + + if(d->autoDelete) + deleteLater(); +#endif +} + +//////////////////////////////////////////////////////////////////////////////// +// Helper Functions used for sorting MusicBrainz results +//////////////////////////////////////////////////////////////////////////////// +double stringSimilarity(QStringList &l, QString &s) +{ + double max = 0, current = 0; + for ( QStringList::Iterator it = l.begin(); it != l.end(); ++it ) { + if( max < (current = stringSimilarity((*it),s))) + max = current; + } + return max; +} + +double stringSimilarity(QString s1, QString s2) +{ + s1.remove( QRegExp("[\\s\\t\\r\\n]") ); + s2.remove( QRegExp("[\\s\\t\\r\\n]") ); + + double nCommon = 0; + int p1 = 0, p2 = 0, x1 = 0, x2 = 0; + int l1 = s1.length(), l2 = s2.length(), l3 = l1 + l2; + QChar c1 = 0, c2 = 0; + + while(p1 < l1 && p2 < l2) { + c1 = s1.at(p1); c2 = s2.at(p2); + if( c1.upper() == c2.upper()) { + ++nCommon; + ++p1; ++p2; + } + else { + x1 = s1.find(c2,p1,false); + x2 = s2.find(c1,p2,false); + + if( (x1 == x2 || -1 == x1) || (-1 != x2 && x1 > x2) ) + ++p2; + else + ++p1; + } + } + return l3 ? (double)(nCommon*2) / (double)(l3) : 1; +} + + + + + +#include "ktrm.moc" + diff --git a/amarok/src/ktrm.h b/amarok/src/ktrm.h new file mode 100644 index 00000000..4ebe09ee --- /dev/null +++ b/amarok/src/ktrm.h @@ -0,0 +1,226 @@ +/*************************************************************************** + copyright : (C) 2004 by Scott Wheeler + email : wheeler@kde.org + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Steet, Fifth Floor, Boston, MA 02110-1301 * + * USA * + ***************************************************************************/ + +/* + * At some point this will likely be a library class, as such it's been written + * as such and is LGPL'ed. + */ + +#ifndef KTRM_H +#define KTRM_H + +#include <qobject.h> +#include <qmap.h> +#include <qstring.h> +#include <qvaluelist.h> + +/** + * This represents a potential match for a TRM lookup. KTRMResultList is + * returned from KTRMLookup and will be sorted by relevance (better matches + * at the beginning of the list). + */ + +namespace KIO { class Job; } + +class KTRMResult +{ + friend class KTRMLookup; + +public: + KTRMResult(); + KTRMResult(const KTRMResult &result); + ~KTRMResult(); + + /** + * Returns the title of the track for the potential match. + */ + QString title() const; + + /** + * Returns the artist name of the track for the potential match. + */ + QString artist() const; + + /** + * Returns the album name of the track for the potential match. + */ + QString album() const; + + /** + * Returns the track number of the track for the potential match. + */ + int track() const; + + /** + * Returns the original release year of the track for the potential match. + */ + int year() const; + + /** + * Returns true if all of the values for the result are empty. + */ + bool isEmpty() const; + + /** + * Compares to \a r based on the relevance of the match. Better matches + * will be greater than less accurate matches. + */ + bool operator<(const KTRMResult &r) const; + + /** + * Compares to \a r based on the relevance of the match. Better matches + * will be greater than less accurate matches. + */ + bool operator>(const KTRMResult &r) const; + + /** + * Basic assignment operator; required for the QTL + */ + KTRMResult &operator= (const KTRMResult &r); + + /** + * Basic comparison operator; required for the QTL + */ + bool operator== (const KTRMResult &r) const; + +private: + class KTRMResultPrivate; + KTRMResultPrivate *d; +}; + +typedef QValueList<KTRMResult> KTRMResultList; + +/** + * An abstraction for libtunepimp's TRM based lookup and file recognition. + * + * A lookup is started when the object is created. One of the virtual methods + * -- recognized(), unrecognized(), collision() or error(). Those methods + * should be reimplemented in subclasses to specify what behavior should happen + * for each result. + * + * The lookups themselves happen in a background thread, but the return calls + * are guaranteed to run in the GUI thread. + */ +class KTRMLookup : public QObject +{ +Q_OBJECT + +signals: + void sigResult( KTRMResultList, QString ); + +protected slots: + virtual void lookupResult( KIO::Job* ); + +public: + /** + * Creates and starts a lookup for \a file. If \a autoDelete is set to + * true the lookup will delete itself when it has finished. + */ + KTRMLookup(const QString &file, bool autoDelete = false); + + virtual ~KTRMLookup(); + + /** + * Returns the file name that was specified in the constructor. + */ + QString file() const; + + /** + * Returns the TunePimp file ID for the file. This is of no use to the + * public API. + * + * @internal + */ + int fileId() const; + + /** + * This method is called when the puid was already generated. It will then do + * the lookup with MusicBrainz's server. This may be reimplemented to provide + * specific behavion for the lookup. + */ + virtual void puidGenerated(); + + /** + * This method is called if the track was recognized by the TRM server. + * results() will return just one value. This may be reimplemented to + * provide specific behavion in the case of the track being recognized. + * + * \note The base class call should happen at the beginning of the subclass + * reimplementation; it populates the values of results(). + */ + virtual void recognized(); + + /** + * This method is called if the track was not recognized by the TRM server. + * results() will return an empty set. This may be reimplemented to provide + * specific behavion in the case of the track not being recognized. + */ + virtual void unrecognized(); + + /** + * This method is called if there are multiple potential matches for the TRM + * value. results() will return a list of the potential matches, sorted by + * liklihood. This may be reimplemented to provide + * specific behavion in the case of the track not being recognized. + * + * \note The base class call should happen at the beginning of the subclass + * reimplementation; it populates the values of results(). + */ + virtual void collision(); + + /** + * This method is called if the track was not recognized by the TRM server. + * results() will return an empty set. This may be reimplemented to provide + * specific behavion in the case of the track not being recognized. + */ + virtual void error(); + + /** + * Returns the list of matches found by the lookup. In the case that there + * was a TRM collision this list will contain multiple entries. In the case + * that it was recognized this will only contain one entry. Otherwise it + * will remain empty. + */ + KTRMResultList results() const; + +protected: + /** + * This method is called when any of terminal states (recognized, + * unrecognized, collision or error) has been reached after the specific + * method for the result has been called. + * + * This should be reimplemented in the case that there is some general + * processing to be done for all terminal states. + */ + virtual void finished(); + +private: + class KTRMLookupPrivate; + KTRMLookupPrivate *d; +}; + +/** + * Helper Functions used for sorting MusicBrainz results + */ +double stringSimilarity(QString s1, QString s2); +double stringSimilarity(QStringList &l, QString &s); + +#endif /*KTRM_H*/ diff --git a/amarok/src/lastfm.cpp b/amarok/src/lastfm.cpp new file mode 100644 index 00000000..c35c76f6 --- /dev/null +++ b/amarok/src/lastfm.cpp @@ -0,0 +1,1147 @@ +/*************************************************************************** + * copyright : (C) 2006 Chris Muehlhaeuser <chris@chris.de> * + * : (C) 2006 Seb Ruiz <me@sebruiz.net> * + * : (C) 2006 Ian Monroe <ian@monroe.nu> * + * : (C) 2006 Mark Kretschmann <markey@web.de> * + **************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#define DEBUG_PREFIX "LastFm" + +#include "amarok.h" //APP_VERSION, actioncollection +#include "amarokconfig.h" //last.fm username and passwd +#include "collectiondb.h" +#include "debug.h" +#include "enginecontroller.h" +#include "lastfm.h" +#include "statusbar.h" //showError() + +#include <qdeepcopy.h> +#include <qdom.h> +#include <qhttp.h> +#include <qlabel.h> +#include <qregexp.h> + +#include <kaction.h> +#include <klineedit.h> +#include <kmdcodec.h> //md5sum +#include <kmessagebox.h> +#include <kio/job.h> +#include <kio/jobclasses.h> +#include <kprotocolmanager.h> +#include <kshortcut.h> +#include <kurl.h> + +#include <time.h> +#include <unistd.h> + +using namespace LastFm; + +/////////////////////////////////////////////////////////////////////////////// +// CLASS AmarokHttp +// AmarokHttp is a hack written so that lastfm code could easily use something proxy aware. +// DO NOT use this class for anything else, use KIO directly instead. +//////////////////////////////////////////////////////////////////////////////// +AmarokHttp::AmarokHttp ( const QString& hostname, Q_UINT16 port, + QObject* parent ) + : QObject( parent ), + m_hostname( hostname ), + m_port( port ) +{} + +int +AmarokHttp::get ( const QString & path ) +{ + QString uri = QString( "http://%1:%2/%3" ) + .arg( m_hostname ) + .arg( m_port ) + .arg( path ); + + m_done = false; + m_error = QHttp::NoError; + m_state = QHttp::Connecting; + KIO::TransferJob *job = KIO::get(uri, true, false); + connect(job, SIGNAL(data(KIO::Job*, const QByteArray&)), + this, SLOT(slotData(KIO::Job*, const QByteArray&))); + connect(job, SIGNAL(result(KIO::Job*)), + this, SLOT(slotResult(KIO::Job*))); + + return 0; +} + +QHttp::State +AmarokHttp::state() const +{ + return m_state; +} + +QByteArray +AmarokHttp::readAll () +{ + return m_result; +} + +QHttp::Error +AmarokHttp::error() +{ + return m_error; +} + +void +AmarokHttp::slotData(KIO::Job*, const QByteArray& data) +{ + if( data.size() == 0 ) { + return; + } + else if ( m_result.size() == 0 ) { + m_result = data; + } + else if ( m_result.resize( m_result.size() + data.size() ) ) { + memcpy( m_result.end(), data.data(), data.size() ); + } +} + +void +AmarokHttp::slotResult(KIO::Job* job) +{ + bool err = job->error(); + if( err || m_error != QHttp::NoError ) { + m_error = QHttp::UnknownError; + } + else { + m_error = QHttp::NoError; + } + m_done = true; + m_state = QHttp::Unconnected; + emit( requestFinished( 0, err ) ); +} + + + +/////////////////////////////////////////////////////////////////////////////// +// CLASS Controller +//////////////////////////////////////////////////////////////////////////////// + +Controller *Controller::s_instance = 0; + +Controller::Controller() + : QObject( EngineController::instance(), "lastfmController" ) + , m_service( 0 ) +{ + KActionCollection* ac = Amarok::actionCollection(); + m_actionList.append( new KAction( i18n( "Ban" ), Amarok::icon( "remove" ), + KKey( Qt::CTRL | Qt::Key_B ), this, SLOT( ban() ), ac, "ban" ) ); + + m_actionList.append( new KAction( i18n( "Love" ), Amarok::icon( "love" ), + KKey( Qt::CTRL | Qt::Key_L ), this, SLOT( love() ), ac, "love" ) ); + + m_actionList.append( new KAction( i18n( "Skip" ), Amarok::icon( "next" ), + KKey( Qt::CTRL | Qt::Key_K ), this, SLOT( skip() ), ac, "skip" ) ); + setActionsEnabled( false ); +} + + +Controller* +Controller::instance() +{ + if( !s_instance ) s_instance = new Controller(); + return s_instance; +} + + +KURL +Controller::getNewProxy( QString genreUrl, bool useProxy ) +{ + DEBUG_BLOCK + + m_genreUrl = genreUrl; + + if ( m_service ) playbackStopped(); + + WebService* service; + // m_service might have already been reset until changeStation() and/or handshare() + // calls return + service = m_service = new WebService( this, useProxy ); + + if( checkCredentials() ) + { + QString user = AmarokConfig::scrobblerUsername(); + QString pass = AmarokConfig::scrobblerPassword(); + + if( !user.isEmpty() && !pass.isEmpty() && + service->handshake( user, pass ) ) + { + bool ok = service->changeStation( m_genreUrl ); + if( ok ) // else playbackStopped() + { + if( !AmarokConfig::submitPlayedSongs() ) + m_service->enableScrobbling( false ); + setActionsEnabled( true ); + return KURL( m_service->proxyUrl() ); + } + } + if (service->wasCanceled()) { + // It was canceled before (during kapp->processEvents() loop) + delete service; + return KURL("lastfm://"); // construct invalid url + } + } + + // Some kind of failure happened, so crap out + playbackStopped(); + return KURL(); +} + +int +Controller::changeStation( QString url ) +{ + if (isPlaying()) { + WebService* service = getService(); + if (service->changeStation( url )) { + return 1; // success + } else if (service->wasCanceled()) { + delete service; + return -1; // canceled + } else { + return 0; // failed + } + } else { + return 0; // impossible, failed + } +} + +void +Controller::playbackStopped() //SLOT +{ + setActionsEnabled( false ); + + if (m_service) { + if (m_service->cancel()) + delete m_service;; + m_service = 0; + } +} + + +bool +Controller::checkCredentials() //static +{ + if( AmarokConfig::scrobblerUsername().isEmpty() || AmarokConfig::scrobblerPassword().isEmpty() ) + { + LoginDialog dialog( 0 ); + dialog.setCaption( "last.fm" ); + return dialog.exec() == QDialog::Accepted; + } + return true; +} + + +QString +Controller::createCustomStation() //static +{ + QString token; + CustomStationDialog dialog( 0 ); + + if( dialog.exec() == QDialog::Accepted ) + { + token = dialog.text(); + } + + return token; +} + + +void +Controller::ban() +{ + if( m_service ) + m_service->ban(); +} + + +void +Controller::love() +{ + if( m_service ) + m_service->love(); +} + + +void +Controller::skip() +{ + if( m_service ) + m_service->skip(); +} + + +void +Controller::setActionsEnabled( bool enable ) +{ //pausing last.fm streams doesn't do anything good + Amarok::actionCollection()->action( "play_pause" )->setEnabled( !enable ); + Amarok::actionCollection()->action( "pause" )->setEnabled( !enable ); + + KAction* action; + for( action = m_actionList.first(); action; action = m_actionList.next() ) + action->setEnabled( enable ); +} + +/// return a translatable description of the station we are connected to +QString +Controller::stationDescription( QString url ) +{ + if( url.isEmpty() && instance() && instance()->isPlaying() ) + url = instance()->getService()->currentStation(); + + if( url.isEmpty() ) return QString(); + + QStringList elements = QStringList::split( "/", url ); + + /// TAG RADIOS + // eg: lastfm://globaltag/rock + if ( elements[1] == "globaltags" ) + return i18n( "Global Tag Radio: %1" ).arg( elements[2] ); + + /// ARTIST RADIOS + if ( elements[1] == "artist" ) + { + // eg: lastfm://artist/Queen/similarartists + if ( elements[3] == "similarartists" ) + return i18n( "Similar Artists to %1" ).arg( elements[2] ); + + if ( elements[3] == "fans" ) + return i18n( "Artist Fan Radio: %1" ).arg( elements[2] ); + } + + /// CUSTOM STATION + if ( elements[1] == "artistnames" ) + { + // eg: lastfm://artistnames/genesis,pink floyd,queen + + // turn "genesis,pink floyd,queen" into "Genesis, Pink Floyd, Queen" + QString artists = elements[2]; + artists.replace( ",", ", " ); + const QStringList words = QStringList::split( " ", QString( artists ).remove( "," ) ); + foreach( words ) { + QString capitalized = *it; + capitalized.replace( 0, 1, (*it)[0].upper() ); + artists.replace( *it, capitalized ); + } + + return i18n( "Custom Station: %1" ).arg( artists ); + } + + /// USER RADIOS + else if ( elements[1] == "user" ) + { + // eg: lastfm://user/sebr/neighbours + if ( elements[3] == "neighbours" ) + return i18n( "%1's Neighbor Radio" ).arg( elements[2] ); + + // eg: lastfm://user/sebr/personal + if ( elements[3] == "personal" ) + return i18n( "%1's Personal Radio" ).arg( elements[2] ); + + // eg: lastfm://user/sebr/loved + if ( elements[3] == "loved" ) + return i18n( "%1's Loved Radio" ).arg( elements[2] ); + + // eg: lastfm://user/sebr/recommended/100 : 100 is number for how obscure the music should be + if ( elements[3] == "recommended" ) + return i18n( "%1's Recommended Radio" ).arg( elements[2] ); + } + + /// GROUP RADIOS + //eg: lastfm://group/Amarok%20users + else if ( elements[1] == "group" ) + return i18n( "Group Radio: %1" ).arg( elements[2] ); + + /// TRACK RADIOS + else if ( elements[1] == "play" ) + { + if ( elements[2] == "tracks" ) + return i18n( "Track Radio" ); + else if ( elements[2] == "artists" ) + return i18n( "Artist Radio" ); + } + //kaput! + return url; +} + + + +//////////////////////////////////////////////////////////////////////////////// +// CLASS WebService +//////////////////////////////////////////////////////////////////////////////// + +WebService::WebService( QObject* parent, bool useProxy ) + : QObject( parent, "lastfmParent" ) + , m_useProxy( useProxy ) + , m_deletionUnsafe( false ) + , m_wasCanceled( false ) +{ + debug() << "Initialising Web Service" << endl; +} + + +WebService::~WebService() +{ + DEBUG_BLOCK +} + +bool +WebService::cancel() { + m_wasCanceled = true; + return !m_deletionUnsafe; +} + +void +WebService::readProxy() //SLOT +{ + QString line; + + while( m_server->readln( line ) != -1 ) { + debug() << line << endl; + + if( line == "AMAROK_PROXY: SYNC" ) + requestMetaData(); + } +} + + +bool +WebService::handshake( const QString& username, const QString& password ) +{ + DEBUG_BLOCK + + m_username = username; + m_password = password; + + AmarokHttp http( "ws.audioscrobbler.com", 80 ); + + const QString path = + QString( "/radio/handshake.php?version=%1&platform=%2&username=%3&passwordmd5=%4&debug=%5" ) + .arg( APP_VERSION ) //Muesli-approved: Amarok version, and Amarok-as-platform + .arg( QString("Amarok") ) + .arg( QString( QUrl( username ).encodedPathAndQuery() ) ) + .arg( KMD5( m_password.utf8() ).hexDigest() ) + .arg( "0" ); + + http.get( path ); + + // We don't know what might happen within processEvents() loop. + // Therefore this service instance must be protected from deletion. + m_deletionUnsafe = true; + do + kapp->processEvents(); + while( http.state() != QHttp::Unconnected ); + m_deletionUnsafe = false; + if (this->wasCanceled()) + return false; + + if ( http.error() != QHttp::NoError ) + return false; + + const QString result( QDeepCopy<QString>( http.readAll() ) ); + + debug() << "result: " << result << endl; + + m_session = parameter( "session", result ); + m_baseHost = parameter( "base_url", result ); + m_basePath = parameter( "base_path", result ); + m_subscriber = parameter( "subscriber", result ) == "1"; + m_streamUrl = QUrl( parameter( "stream_url", result ) ); +// bool banned = parameter( "banned", result ) == "1"; + + if ( m_session.lower() == "failed" ) { + Amarok::StatusBar::instance()->longMessage( i18n( + "Amarok failed to establish a session with last.fm. <br>" + "Check if your last.fm user and password are correctly set." + ) ); + return false; + } + + Amarok::config( "Scrobbler" )->writeEntry( "Subscriber", m_subscriber ); + if( m_useProxy ) + { + // Find free port + MyServerSocket* socket = new MyServerSocket(); + const int port = socket->port(); + debug() << "Proxy server using port: " << port << endl; + delete socket; + + m_proxyUrl = QString( "http://localhost:%1/lastfm.mp3" ).arg( port ); + + m_server = new Amarok::ProcIO(); + m_server->setComm( KProcess::Communication( KProcess::AllOutput ) ); + *m_server << "amarok_proxy.rb"; + *m_server << "--lastfm"; + *m_server << QString::number( port ); + *m_server << m_streamUrl.toString(); + *m_server << AmarokConfig::soundSystem(); + *m_server << Amarok::proxyForUrl( m_streamUrl.toString() ); + + if( !m_server->start( KProcIO::NotifyOnExit, true ) ) { + error() << "Failed to start amarok_proxy.rb" << endl; + return false; + } + + QString line; + m_deletionUnsafe = true; + while( true ) { + kapp->processEvents(); + m_server->readln( line ); + if( line == "AMAROK_PROXY: startup" ) break; + } + m_deletionUnsafe = false; + if (this->wasCanceled()) + return false; + + connect( m_server, SIGNAL( readReady( KProcIO* ) ), this, SLOT( readProxy() ) ); + connect( m_server, SIGNAL( processExited( KProcess* ) ), Controller::instance(), SLOT( playbackStopped() ) ); + } + else + m_proxyUrl = m_streamUrl.toString(); + + return true; +} + + +bool +WebService::changeStation( QString url ) +{ + debug() << "Changing station:" << url << endl; + + AmarokHttp http( m_baseHost, 80 ); + + http.get( QString( m_basePath + "/adjust.php?session=%1&url=%2&debug=0" ) + .arg( m_session ) + .arg( url ) ); + + m_deletionUnsafe = true; + do + kapp->processEvents(); + while( http.state() != QHttp::Unconnected ); + m_deletionUnsafe = false; + if (this->wasCanceled()) + return false; + + if ( http.error() != QHttp::NoError ) + { + showError( E_OTHER ); // default error + return false; + } + + const QString result( QDeepCopy<QString>( http.readAll() ) ); + const int errCode = parameter( "error", result ).toInt(); + + if ( errCode ) + { + showError( errCode ); + return false; + } + + const QString _url = parameter( "url", result ); + if ( _url.startsWith( "lastfm://" ) ) + { + m_station = _url; // parse it in stationDescription + emit stationChanged( _url, m_station ); + } + else + emit stationChanged( _url, QString::null ); + + return true; +} + +void +WebService::requestMetaData() //SLOT +{ + AmarokHttp *http = new AmarokHttp( m_baseHost, 80, this ); + connect( http, SIGNAL( requestFinished( int, bool ) ), this, SLOT( metaDataFinished( int, bool ) ) ); + + http->get( QString( m_basePath + "/np.php?session=%1&debug=%2" ) + .arg( m_session ) + .arg( "0" ) ); +} + + +void +WebService::metaDataFinished( int /*id*/, bool error ) //SLOT +{ + DEBUG_BLOCK + + AmarokHttp* http = (AmarokHttp*) sender(); + http->deleteLater(); + if( error ) return; + + const QString result( http->readAll() ); + debug() << result << endl; + + int errCode = parameter( "error", result ).toInt(); + if ( errCode > 0 ) { + debug() << "Metadata failed with error code: " << errCode << endl; + showError( errCode ); + return; + } + + m_metaBundle.setArtist( parameter( "artist", result ) ); + m_metaBundle.setAlbum ( parameter( "album", result ) ); + m_metaBundle.setTitle ( parameter( "track", result ) ); + m_metaBundle.setUrl ( KURL( Controller::instance()->getGenreUrl() ) ); + m_metaBundle.setLength( parameter( "trackduration", result ).toInt() ); + + Bundle lastFmStuff; + QString imageUrl = parameter( "albumcover_medium", result ); + + if( imageUrl == "http://static.last.fm/coverart/" || + imageUrl == "http://static.last.fm/depth/catalogue/no_album_large.gif" ) + imageUrl = QString::null; + + lastFmStuff.setImageUrl ( CollectionDB::instance()->notAvailCover( true ) ); + lastFmStuff.setArtistUrl( parameter( "artist_url", result ) ); + lastFmStuff.setAlbumUrl ( parameter( "album_url", result ) ); + lastFmStuff.setTitleUrl ( parameter( "track_url", result ) ); +// bool discovery = parameter( "discovery", result ) != "-1"; + + m_metaBundle.setLastFmBundle( lastFmStuff ); + + const KURL u( imageUrl ); + if( !u.isValid() ) { + debug() << "imageUrl empty or invalid." << endl; + emit metaDataResult( m_metaBundle ); + return; + } + + KIO::Job* job = KIO::storedGet( u, true, false ); + connect( job, SIGNAL( result( KIO::Job* ) ), this, SLOT( fetchImageFinished( KIO::Job* ) ) ); +} + + +void +WebService::fetchImageFinished( KIO::Job* job ) //SLOT +{ + DEBUG_BLOCK + + if( job->error() == 0 ) { + const QString path = Amarok::saveLocation() + "lastfm_image.png"; + const int size = AmarokConfig::coverPreviewSize(); + + QImage img( static_cast<KIO::StoredTransferJob*>( job )->data() ); + img.smoothScale( size, size ).save( path, "PNG" ); + + m_metaBundle.lastFmBundle()->setImageUrl( CollectionDB::makeShadowedImage( path, false ) ); + } + emit metaDataResult( m_metaBundle ); +} + + +void +WebService::enableScrobbling( bool enabled ) //SLOT +{ + if ( enabled ) + debug() << "Enabling Scrobbling!" << endl; + else + debug() << "Disabling Scrobbling!" << endl; + + AmarokHttp *http = new AmarokHttp( m_baseHost, 80, this ); + connect( http, SIGNAL( requestFinished( int, bool ) ), this, SLOT( enableScrobblingFinished( int, bool ) ) ); + + http->get( QString( m_basePath + "/control.php?session=%1&command=%2&debug=%3" ) + .arg( m_session ) + .arg( enabled ? QString( "rtp" ) : QString( "nortp" ) ) + .arg( "0" ) ); +} + + +void +WebService::enableScrobblingFinished( int /*id*/, bool error ) //SLOT +{ + AmarokHttp* http = (AmarokHttp*) sender(); + http->deleteLater(); + if ( error ) return; + + emit enableScrobblingDone(); +} + + +void +WebService::love() //SLOT +{ + AmarokHttp *http = new AmarokHttp( m_baseHost, 80, this ); + connect( http, SIGNAL( requestFinished( int, bool ) ), this, SLOT( loveFinished( int, bool ) ) ); + + http->get( QString( m_basePath + "/control.php?session=%1&command=love&debug=%2" ) + .arg( m_session ) + .arg( "0" ) ); + Amarok::StatusBar::instance()->shortMessage( i18n("love, as in affection", "Loving song...") ); +} + + +void +WebService::skip() //SLOT +{ + AmarokHttp *http = new AmarokHttp( m_baseHost, 80, this ); + connect( http, SIGNAL( requestFinished( int, bool ) ), this, SLOT( skipFinished( int, bool ) ) ); + + http->get( QString( m_basePath + "/control.php?session=%1&command=skip&debug=%2" ) + .arg( m_session ) + .arg( "0" ) ); + Amarok::StatusBar::instance()->shortMessage( i18n("Skipping song...") ); +} + + +void +WebService::ban() //SLOT +{ + AmarokHttp *http = new AmarokHttp( m_baseHost, 80, this ); + connect( http, SIGNAL( requestFinished( int, bool ) ), this, SLOT( banFinished( int, bool ) ) ); + + http->get( QString( m_basePath + "/control.php?session=%1&command=ban&debug=%2" ) + .arg( m_session ) + .arg( "0" ) ); + Amarok::StatusBar::instance()->shortMessage( i18n("Ban, as in dislike", "Banning song...") ); +} + + +void +WebService::loveFinished( int /*id*/, bool error ) //SLOT +{ + DEBUG_BLOCK + + AmarokHttp* http = (AmarokHttp*) sender(); + http->deleteLater(); + if( error ) return; + + emit loveDone(); +} + + +void +WebService::skipFinished( int /*id*/, bool error ) //SLOT +{ + DEBUG_BLOCK + + AmarokHttp* http = (AmarokHttp*) sender(); + http->deleteLater(); + if( error ) return; + + EngineController::engine()->flushBuffer(); + emit skipDone(); +} + + +void +WebService::banFinished( int /*id*/, bool error ) //SLOT +{ + DEBUG_BLOCK + + AmarokHttp* http = (AmarokHttp*) sender(); + http->deleteLater(); + if( error ) return; + + EngineController::engine()->flushBuffer(); + emit banDone(); + emit skipDone(); +} + + +void +WebService::friends( QString username ) +{ + if ( username.isEmpty() ) + username = m_username; + + AmarokHttp *http = new AmarokHttp( m_baseHost, 80, this ); + connect( http, SIGNAL( requestFinished( bool ) ), this, SLOT( friendsFinished( bool ) ) ); + + http->get( QString( "/1.0/user/%1/friends.xml" ) + .arg( QString( QUrl( username ).encodedPathAndQuery() ) ) ); +} + + +void +WebService::friendsFinished( int /*id*/, bool error ) //SLOT +{ + AmarokHttp* http = (AmarokHttp*) sender(); + http->deleteLater(); + if( error ) return; + + QDomDocument document; + document.setContent( http->readAll() ); + + if ( document.elementsByTagName( "friends" ).length() == 0 ) + { + emit friendsResult( QString( "" ), QStringList() ); + return; + } + + QStringList friends; + QString user = document.elementsByTagName( "friends" ).item( 0 ).attributes().namedItem( "user" ).nodeValue(); + QDomNodeList values = document.elementsByTagName( "user" ); + for ( uint i = 0; i < values.count(); i++ ) + { + friends << values.item( i ).attributes().namedItem( "username" ).nodeValue(); + } + + emit friendsResult( user, friends ); +} + + +void +WebService::neighbours( QString username ) +{ + if ( username.isEmpty() ) + username = m_username; + + AmarokHttp *http = new AmarokHttp( m_baseHost, 80, this ); + connect( http, SIGNAL( requestFinished( bool ) ), this, SLOT( neighboursFinished( bool ) ) ); + + http->get( QString( "/1.0/user/%1/neighbours.xml" ) + .arg( QString( QUrl( username ).encodedPathAndQuery() ) ) ); +} + + +void +WebService::neighboursFinished( int /*id*/, bool error ) //SLOT +{ + AmarokHttp* http = (AmarokHttp*) sender(); + http->deleteLater(); + if( error ) return; + + QDomDocument document; + document.setContent( http->readAll() ); + + if ( document.elementsByTagName( "neighbours" ).length() == 0 ) + { + emit friendsResult( QString( "" ), QStringList() ); + return; + } + + QStringList neighbours; + QString user = document.elementsByTagName( "neighbours" ).item( 0 ).attributes().namedItem( "user" ).nodeValue(); + QDomNodeList values = document.elementsByTagName( "user" ); + for ( uint i = 0; i < values.count(); i++ ) + { + neighbours << values.item( i ).attributes().namedItem( "username" ).nodeValue(); + } + + emit neighboursResult( user, neighbours ); +} + + +void +WebService::userTags( QString username ) +{ + if ( username.isEmpty() ) + username = m_username; + + AmarokHttp *http = new AmarokHttp( m_baseHost, 80, this ); + connect( http, SIGNAL( requestFinished( bool ) ), this, SLOT( userTagsFinished( bool ) ) ); + + http->get( QString( "/1.0/user/%1/tags.xml?debug=%2" ) + .arg( QString( QUrl( username ).encodedPathAndQuery() ) ) ); +} + + +void +WebService::userTagsFinished( int /*id*/, bool error ) //SLOT +{ + AmarokHttp* http = (AmarokHttp*) sender(); + http->deleteLater(); + if( error ) return; + + QDomDocument document; + document.setContent( http->readAll() ); + + if ( document.elementsByTagName( "toptags" ).length() == 0 ) + { + emit userTagsResult( QString(), QStringList() ); + return; + } + + QStringList tags; + QDomNodeList values = document.elementsByTagName( "tag" ); + QString user = document.elementsByTagName( "toptags" ).item( 0 ).attributes().namedItem( "user" ).nodeValue(); + for ( uint i = 0; i < values.count(); i++ ) + { + QDomNode item = values.item( i ).namedItem( "name" ); + tags << item.toElement().text(); + } + emit userTagsResult( user, tags ); +} + + +void +WebService::recentTracks( QString username ) +{ + if ( username.isEmpty() ) + username = m_username; + + AmarokHttp *http = new AmarokHttp( m_baseHost, 80, this ); + connect( http, SIGNAL( requestFinished( bool ) ), this, SLOT( recentTracksFinished( bool ) ) ); + + http->get( QString( "/1.0/user/%1/recenttracks.xml" ) + .arg( QString( QUrl( username ).encodedPathAndQuery() ) ) ); +} + + +void +WebService::recentTracksFinished( int /*id*/, bool error ) //SLOT +{ + AmarokHttp* http = (AmarokHttp*) sender(); + http->deleteLater(); + if( error ) return; + + QValueList< QPair<QString, QString> > songs; + QDomDocument document; + document.setContent( http->readAll() ); + + if ( document.elementsByTagName( "recenttracks" ).length() == 0 ) + { + emit recentTracksResult( QString(), songs ); + return; + } + + QDomNodeList values = document.elementsByTagName( "track" ); + QString user = document.elementsByTagName( "recenttracks" ).item( 0 ).attributes().namedItem( "user" ).nodeValue(); + for ( uint i = 0; i < values.count(); i++ ) + { + QPair<QString, QString> song; + song.first = values.item( i ).namedItem( "artist" ).toElement().text(); + song.second = values.item( i ).namedItem( "name" ).toElement().text(); + + songs << song; + } + emit recentTracksResult( user, songs ); +} + + +void +WebService::recommend( int type, QString username, QString artist, QString token ) +{ + QString modeToken = ""; + switch ( type ) + { + case 0: + modeToken = QString( "artist_name=%1" ).arg( QString( QUrl( artist ).encodedPathAndQuery() ) ); + break; + + case 1: + modeToken = QString( "album_artist=%1&album_name=%2" ) + .arg( QString( QUrl( artist ).encodedPathAndQuery() ) ) + .arg( QString( QUrl( token ).encodedPathAndQuery() ) ); + break; + + case 2: + modeToken = QString( "track_artist=%1&track_name=%2" ) + .arg( QString( QUrl( artist ).encodedPathAndQuery() ) ) + .arg( QString( QUrl( token ).encodedPathAndQuery() ) ); + break; + } + + QHttp *http = new QHttp( "wsdev.audioscrobbler.com", 80, this ); + connect( http, SIGNAL( requestFinished( bool ) ), this, SLOT( recommendFinished( bool ) ) ); + + uint currentTime = QDateTime::currentDateTime( Qt::UTC ).toTime_t(); + QString challenge = QString::number( currentTime ); + + QCString md5pass = KMD5( KMD5( m_password.utf8() ).hexDigest() + currentTime ).hexDigest(); + + token = QString( "user=%1&auth=%2&nonce=%3recipient=%4" ) + .arg( QString( QUrl( currentUsername() ).encodedPathAndQuery() ) ) + .arg( QString( QUrl( md5pass ).encodedPathAndQuery() ) ) + .arg( QString( QUrl( challenge ).encodedPathAndQuery() ) ) + .arg( QString( QUrl( username ).encodedPathAndQuery() ) ); + + QHttpRequestHeader header( "POST", "/1.0/rw/recommend.php?" + token.utf8() ); + header.setValue( "Host", "wsdev.audioscrobbler.com" ); + header.setContentType( "application/x-www-form-urlencoded" ); + http->request( header, modeToken.utf8() ); +} + + +void +WebService::recommendFinished( int /*id*/, bool /*error*/ ) //SLOT +{ + AmarokHttp* http = (AmarokHttp*) sender(); + http->deleteLater(); + + debug() << "Recommendation:" << http->readAll() << endl; +} + + +QString +WebService::parameter( const QString keyName, const QString data ) const +{ + QStringList list = QStringList::split( '\n', data ); + + for ( uint i = 0; i < list.size(); i++ ) + { + QStringList values = QStringList::split( '=', list[i] ); + if ( values[0] == keyName ) + { + values.remove( values.at(0) ); + return QString::fromUtf8( values.join( "=" ).ascii() ); + } + } + + return QString( "" ); +} + + +QStringList +WebService::parameterArray( const QString keyName, const QString data ) const +{ + QStringList result; + QStringList list = QStringList::split( '\n', data ); + + for ( uint i = 0; i < list.size(); i++ ) + { + QStringList values = QStringList::split( '=', list[i] ); + if ( values[0].startsWith( keyName ) ) + { + values.remove( values.at(0) ); + result.append( QString::fromUtf8( values.join( "=" ).ascii() ) ); + } + } + + return result; +} + + +QStringList +WebService::parameterKeys( const QString keyName, const QString data ) const +{ + QStringList result; + QStringList list = QStringList::split( '\n', data ); + + for ( uint i = 0; i < list.size(); i++ ) + { + QStringList values = QStringList::split( '=', list[i] ); + if ( values[0].startsWith( keyName ) ) + { + values = QStringList::split( '[', values[0] ); + values = QStringList::split( ']', values[1] ); + result.append( values[0] ); + } + } + + + return result; +} + +void +WebService::showError( int code, QString message ) +{ + switch ( code ) + { + case E_NOCONTENT: + message = i18n( "There is not enough content to play this station." ); + break; + case E_NOMEMBERS: + message = i18n( "This group does not have enough members for radio." ); + break; + case E_NOFANS: + message = i18n( "This artist does not have enough fans for radio." ); + break; + case E_NOAVAIL: + message = i18n( "This item is not available for streaming." ); + break; + case E_NOSUBSCRIBER: + message = i18n( "This feature is only available to last.fm subscribers." ); + break; + case E_NONEIGHBOURS: + message = i18n( "There are not enough neighbors for this radio." ); + break; + case E_NOSTOPPED: + message = i18n( "This stream has stopped. Please try another station." ); + break; + default: + if( message.isEmpty() ) + message = i18n( "Failed to play this last.fm stream." ); + } + + Amarok::StatusBar::instance()->longMessage( message, KDE::StatusBar::Sorry ); +} + +//////////////////////////////////////////////////////////////////////////////// +// CLASS LastFm::Bundle +//////////////////////////////////////////////////////////////////////////////// + +Bundle::Bundle( const Bundle& lhs ) + : m_imageUrl( lhs.m_imageUrl ) + , m_albumUrl( lhs.m_albumUrl ) + , m_artistUrl( lhs.m_artistUrl ) + , m_titleUrl( lhs.m_titleUrl ) +{} + +void Bundle::detach() { + m_imageUrl = QDeepCopy<QString>(m_imageUrl); + m_albumUrl = QDeepCopy<QString>(m_albumUrl); + m_artistUrl = QDeepCopy<QString>(m_artistUrl); + m_titleUrl = QDeepCopy<QString>(m_titleUrl); +} + +//////////////////////////////////////////////////////////////////////////////// +// CLASS LastFm::LoginDialog +//////////////////////////////////////////////////////////////////////////////// +LoginDialog::LoginDialog( QWidget *parent ) + : KDialogBase( parent, "LastfmLogin", true, QString::null, Ok|Cancel) +{ + makeGridMainWidget( 1, Qt::Horizontal ); + new QLabel( i18n( "To use last.fm with Amarok, you need a last.fm profile." ), mainWidget() ); + + makeGridMainWidget( 2, Qt::Horizontal ); + QLabel *nameLabel = new QLabel( i18n("&Username:"), mainWidget() ); + m_userLineEdit = new KLineEdit( mainWidget() ); + nameLabel->setBuddy( m_userLineEdit ); + + QLabel *passLabel = new QLabel( i18n("&Password:"), mainWidget() ); + m_passLineEdit = new KLineEdit( mainWidget() ); + m_passLineEdit->setEchoMode( QLineEdit::Password ); + passLabel->setBuddy( m_passLineEdit ); + + m_userLineEdit->setFocus(); +} + + +void LoginDialog::slotOk() +{ + AmarokConfig::setScrobblerUsername( m_userLineEdit->text() ); + AmarokConfig::setScrobblerPassword( m_passLineEdit->text() ); + + KDialogBase::slotOk(); +} + + +//////////////////////////////////////////////////////////////////////////////// +// CLASS LastFm::CustomStationDialog +//////////////////////////////////////////////////////////////////////////////// +CustomStationDialog::CustomStationDialog( QWidget *parent ) + : KDialogBase( parent, "LastfmCustomStation", true, i18n( "Create Custom Station" ) , Ok|Cancel) +{ + makeVBoxMainWidget(); + + new QLabel( i18n( "Enter the name of a band or artist you like:" ), mainWidget() ); + + m_edit = new KLineEdit( mainWidget(), "CustomStationEdit" ); + m_edit->setFocus(); +} + + +QString +CustomStationDialog::text() const +{ + return m_edit->text(); +} + + +#include "lastfm.moc" diff --git a/amarok/src/lastfm.h b/amarok/src/lastfm.h new file mode 100644 index 00000000..1948b2d6 --- /dev/null +++ b/amarok/src/lastfm.h @@ -0,0 +1,300 @@ +/*************************************************************************** + * copyright : (C) 2006 Chris Muehlhaeuser <chris@chris.de> * + * : (C) 2006 Seb Ruiz <me@sebruiz.net> * + * : (C) 2006 Ian Monroe <ian@monroe.nu> * + * : (C) 2006 Mark Kretschmann <markey@web.de> * + **************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef AMAROK_LASTFM_H +#define AMAROK_LASTFM_H + +#include "metabundle.h" + +#include <qhttp.h> +#include <qobject.h> +#include <qserversocket.h> +#include <qurl.h> +#include <qvaluelist.h> + +#include <kconfigdialog.h> + +class KLineEdit; +class KAction; +class KProcIO; +class KURL; +class QSocket; +class QTimer; + +namespace KIO { class Job; } + +/* AmarokHttp is a hack written so that lastfm code could easily use something proxy aware. + DO NOT use this class for anything else, use KIO directly instead. */ +class AmarokHttp : public QObject +{ + Q_OBJECT + + public: + AmarokHttp ( const QString & hostname, Q_UINT16 port = 80, QObject* parent = 0 ); + int get ( const QString & path ); + QHttp::State state() const; + QByteArray readAll (); + QHttp::Error error(); + + signals: + void requestFinished ( int id, bool error ); + + protected slots: + void slotData(KIO::Job*, const QByteArray& ); + void slotResult(KIO::Job*); + + protected: + QString m_hostname; + Q_UINT16 m_port; + QString m_path; + QHttp::State m_state; + QHttp::Error m_error; + bool m_done; + QByteArray m_result; +}; + + +namespace LastFm +{ + class WebService; + + class Controller : public QObject + { + Q_OBJECT + + public: + static Controller* instance(); + + KURL getNewProxy( QString genreUrl, bool useProxy ); + int changeStation ( QString url ); + + bool isPlaying() const { return m_service != 0; } + WebService* getService() const { return m_service; } + QString getGenreUrl() const { return m_genreUrl; } + + static bool checkCredentials(); + static QString createCustomStation(); + static QString stationDescription( QString url = QString::null ); // necessary for translation + + public slots: + void playbackStopped(); + void ban(); + void love(); + void skip(); + + private: + Controller(); + void setActionsEnabled( bool enable ); + + static Controller *s_instance; + QPtrList<KAction> m_actionList; + + QString m_genreUrl; + WebService* m_service; + }; + + class WebService : public QObject + { + Q_OBJECT + + public: + enum DataType { Artist, Album, Track }; + + WebService( QObject* parent, bool useProxy ); + ~WebService(); + + bool handshake( const QString& username, const QString& password ); + + bool changeStation( QString url ); + QString currentUsername() const { return m_username; } + QString currentPassword() const { return m_password; } + QString currentStation() const { return m_station; } + QString session() const { return m_session; } + QUrl streamUrl() const { return m_streamUrl; } + + void friends( QString username ); + void neighbours( QString username ); + + void recentTracks( QString username ); + void userTags( QString username ); + + void recommend( int type, QString username, QString artist, QString token = QString() ); + + void recommendArtist( QString username, QString artist ) + { recommend( WebService::Artist, username, artist ); } + + void recommendAlbum( QString username, QString artist, QString album ) + { recommend( WebService::Album, username, artist, album ); } + + void recommendTrack( QString username, QString artist, QString track ) + { recommend( WebService::Track, username, artist, track ); } + + /** + Verify with server that a supplied user/pass combo is valid. Password + should be MD5 hashed. + **/ + void verifyUser( const QString& user, const QString& pass ); + + bool cancel(); + bool wasCanceled() const { return m_wasCanceled; } + + QString proxyUrl() { return m_proxyUrl; } + + public slots: + void requestMetaData(); + void enableScrobbling( bool enabled ); + + void love(); + void skip(); + void ban(); + + signals: + void actionStarted(); + void actionFinished(); + + void stationChanged( QString url, QString name ); + void songQueued(); + + void metaDataResult( const MetaBundle &bundle ); + void enableScrobblingDone(); + + void loveDone(); + void skipDone(); + void banDone(); + + void friendsResult( const QString& username, const QStringList& friends ); + void neighboursResult( const QString& username, const QStringList& friends ); + + void recentTracksResult( const QString& username, QValueList< QPair<QString, QString> > songs ); + void userTagsResult( const QString& username, const QStringList& tags ); + + private: + enum errorCode { E_NOCONTENT = 1, E_NOMEMBERS = 2, E_NOFANS = 3, E_NOAVAIL = 4, E_NOSUBSCRIBER = 5, + E_NONEIGHBOURS = 6, E_NOSTOPPED = 7, E_OTHER = 0 }; + + void showError( int code, QString message = QString::null ); + + bool m_useProxy; + + QString parameter( const QString keyName, const QString data ) const; + QStringList parameterArray( const QString keyName, const QString data ) const; + QStringList parameterKeys( const QString keyName, const QString data ) const; + + QString m_username; // login username + QString m_password; // login password + QString m_station; // the url of the station + QString m_session; // session id that last.fm provides + QString m_baseHost; // who are we connecting to? + QString m_basePath; // where are we connecting to! + QUrl m_streamUrl; // last.fm webserver for direct connection (proxy connects to this) + bool m_subscriber; // self explanatory + + KProcIO* m_server; + + QString m_proxyUrl; + MetaBundle m_metaBundle; + + bool m_deletionUnsafe; + bool m_wasCanceled; + + private slots: + void readProxy(); + void metaDataFinished( int id, bool error ); + void fetchImageFinished( KIO::Job* ); + void enableScrobblingFinished( int id, bool error ); + + void loveFinished( int id, bool error ); + void skipFinished( int id, bool error ); + void banFinished( int id, bool error ); + + void friendsFinished( int id, bool error ); + void neighboursFinished( int id, bool error ); + + void recentTracksFinished( int id, bool error ); + void userTagsFinished( int id, bool error ); + + void recommendFinished( int id, bool error ); + }; + + class Bundle + { + public: + Bundle() {}; + Bundle( const Bundle& bundle); + QString imageUrl() const { return m_imageUrl; } + void setImageUrl( const QString& imageUrl ) { m_imageUrl = imageUrl; } + + QString artistUrl() const { return m_artistUrl; } + void setArtistUrl( const QString& theValue ) { m_artistUrl = theValue; } + + QString albumUrl() const { return m_albumUrl; } + void setAlbumUrl( const QString& theValue ) { m_albumUrl = theValue; } + + QString titleUrl() const { return m_titleUrl; } + void setTitleUrl( const QString& theValue ) { m_titleUrl = theValue; } + + void detach(); // for being able to apply QDeepCopy<> + + private: + QString m_imageUrl; + QString m_albumUrl; + QString m_artistUrl; + QString m_titleUrl; + }; + + // We must implement this because QServerSocket has one pure virtual method. + // It's just used for finding a free port. + class MyServerSocket : public QServerSocket + { + public: + MyServerSocket() : QServerSocket( Q_UINT16( 0 ) ) {} + + private: + void newConnection( int ) {} + + }; + + class LoginDialog : public KDialogBase + { + Q_OBJECT + + public: + LoginDialog( QWidget *parent ); + + protected slots: + void slotOk(); + + private: + KLineEdit *m_userLineEdit; + KLineEdit *m_passLineEdit; + + }; + + class CustomStationDialog : public KDialogBase + { + Q_OBJECT + + public: + CustomStationDialog( QWidget *parent ); + + QString text() const; + + private: + KLineEdit *m_edit; + }; +} + +#endif /*AMAROK_LASTFM_H*/ diff --git a/amarok/src/loader/Makefile.am b/amarok/src/loader/Makefile.am new file mode 100644 index 00000000..6c54ee03 --- /dev/null +++ b/amarok/src/loader/Makefile.am @@ -0,0 +1,16 @@ +bin_PROGRAMS = amarok + +INCLUDES = \ + $(all_includes) + +amarok_SOURCES = \ + loader.cpp + +amarok_LDADD = \ + $(LIB_QT) \ + $(LIB_KDEUI) + +amarok_LDFLAGS = $(all_libraries) $(KDE_RPATH) + +METASOURCES = AUTO + diff --git a/amarok/src/loader/loader.cpp b/amarok/src/loader/loader.cpp new file mode 100644 index 00000000..a17584be --- /dev/null +++ b/amarok/src/loader/loader.cpp @@ -0,0 +1,241 @@ +/*************************************************************************** + loader.cpp - loader application for Amarok + ------------------- + begin : 2004/02/19 + copyright : (C) 2004 by Mark Kretschmann + email : markey@web.de +***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include <cstdlib> +#include <iostream> +#include "loader.h" +#include <qfile.h> +#include <qmessagebox.h> +#include <qprocess.h> +#include <qstring.h> +#include <kinstance.h> +#include <kglobal.h> +#include <kstandarddirs.h> +#include <ksplashscreen.h> + +extern "C" +{ + #include <unistd.h> //usleep +} + + +int +main( int argc, char *argv[] ) +{ + //NOTE this list doesn't include argv[0] ("amarok") + QStringList args; + for( int i = 1; i < argc; i++ ) + args += QString::fromLocal8Bit(argv[i]); + + const bool isRunning = amarokIsRunning(); + + // first check the arguments, we don't need a splashscreen + // for arguments like --help, --version, etc. + + if( !args.isEmpty() ) + { + // These arguments cannot be passed to Amarok, or Amarok will exit + // after processing them. + QStringList longs; longs + << "-help" << "-help-qt" << "-help-kde" << "-help-all" << "-author" << "-version" << "-license" << "-v"; + + // both --arg and -arg are valid + { + QStringList longlongs; + foreach( longs ) + longlongs += QChar('-') + *it; + + longs += longlongs; + } + + foreach( args ) { + const QString arg = *it; + foreach( longs ) + if( arg == *it ) { + // this argument cannot be passed to the running amarokapp + // or KCmdLineArgs would exit the application + + QProcess proc( QString("amarokapp") ); + proc.setCommunication( 0 ); //show everything + proc.addArgument( arg ); + proc.start(); + + while( proc.isRunning() ) + ::usleep( 100 ); + + return 0; //exit success! + } + } + + // these arguments are deemed safe for dcop, but if + // there is no amarokapp running, we'll start a new + // instance and the above checks were not necessary + } + + if ( isRunning ) { + QStringList dcop_args; + dcop_args << "dcop" << "amarok" << "player" << "transferCliArgs" << "["; + + // We transmit our DESKTOP_STARTUP_ID, so amarokapp can stop the startup animation + dcop_args += std::getenv( "DESKTOP_STARTUP_ID" ); //will be interptreted as latin1 + + // relative URLs should be interpreted correctly by amarokapp + // so we need to pass the current working directory + dcop_args << "--cwd" << QDir::currentDirPath(); + + dcop_args += args; + dcop_args += "]"; + + QProcess proc( dcop_args ); + proc.start(); + while( proc.isRunning() ) + ::usleep( 100 ); + + return 0; + } + else { + // no amarokapp is running, start one, show + // a splashscreen and pass it the arguments + + return Loader( args ).exec(); + } +} + +bool +amarokIsRunning() +{ + QProcess proc( QString( "dcop" ) ); + proc.start(); + while( proc.isRunning() ) + ::usleep( 100 ); + + while( proc.canReadLineStdout() ) + if ( proc.readLineStdout() == "amarok" ) + return true; + + return false; +} + + + +static int _argc = 0; + +Loader::Loader( QStringList args ) + : QApplication( _argc, 0 ) + , m_counter( 0 ) + , m_splash( 0 ) +{ + // we transmit the startup_id, so amarokapp can stop the startup animation + //FIXME QCString str( ::getenv( "DESKTOP_STARTUP_ID" ) ); + + if( !QApplication::isSessionRestored()) + { + KInstance instance("amarok"); // KGlobal::dirs() crashes without + if( isSplashEnabled() ) + { + m_splash = new KSplashScreen( QPixmap( KStandardDirs().findResource("data", "amarok/images/splash_screen.jpg"))); + m_splash->show(); + } + } + + args.prepend( "amarokapp" ); + + m_proc = new QProcess( args, this ); + m_proc->setCommunication( QProcess::Stdout ); + + std::cout << "Amarok: [Loader] Starting amarokapp..\n"; + std::cout << "Amarok: [Loader] Don't run gdb, valgrind, etc. against this binary! Use amarokapp.\n"; + + if( !m_proc->start() ) + { + delete m_splash; // hide the splash + + QMessageBox::critical( 0, "Amarok", + "Amarok could not be started!\n" //FIXME this needs to be translated + "This may be because the amarokapp binary is not in your PATH.\n" + "Try locating and running amarokapp from a terminal.", + QMessageBox::Ok, 0 ); + + std::exit( 1 ); //event-loop is not yet being processed + } + + startTimer( INTERVAL ); +} + +Loader::~Loader() +{ + // must be deleted before QApplication closes our Xserver connection + // thus we cannot make it a child of the QApplication and must + // delete it manually + delete m_splash; +} + +void +Loader::timerEvent( QTimerEvent* ) +{ + if( m_proc->isRunning() ) + { + if( ++m_counter == (30000 / INTERVAL) ) + // 30 seconds have passed + std::cerr << "Amarok: [Loader] Amarok is taking a long time to load! Perhaps something has gone wrong?\n"; + + while( m_proc->canReadLineStdout() ) + if( m_proc->readLineStdout() == "STARTUP" ) + QApplication::exit( 0 ); + } + else if( !m_proc->normalExit() ) { + // no reason to show messagebox, as amarokapp should start drkonqi + std::cerr << "Amarok: [Loader] amarokapp probably crashed!\n"; + + QApplication::exit( 3 ); + } + else + // if we get here, then either we didn't receive STARTUP through + // the pipe, or amarokapp exited normally before the STARTUP was + // written to stdout (possibly possible) + QApplication::exit( 0 ); +} + +bool +isSplashEnabled() +{ + //determine whether splash-screen is enabled in amarokrc + (void)KGlobal::config(); // the kubuntu special directory is not present without this + QStringList dirs = KGlobal::dirs()->findAllResources( "config", "amarokrc" ); + + for( QStringList::iterator path = dirs.begin(); + path != dirs.end(); + ++path ) + { + QFile file( *path ); + if ( file.open( IO_ReadOnly ) ) + { + QString line; + while( file.readLine( line, 2000 ) != -1 ) + if ( line.contains( "Show Splashscreen" ) ) + { + if( line.contains( "false" ) ) + return false; + else + return true; + } + } + } + + //if we fail to open anything, just show the splash + return true; +} diff --git a/amarok/src/loader/loader.h b/amarok/src/loader/loader.h new file mode 100644 index 00000000..e379dc7d --- /dev/null +++ b/amarok/src/loader/loader.h @@ -0,0 +1,48 @@ +/*************************************************************************** + loader.h - loader application for Amarok + ------------------- + begin : 2004/02/19 + copyright : (C) 2004 Mark Kretschmann <markey@web.de> + (C) 2005 Max Howell + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef LOADER_H +#define LOADER_H + +#include <qapplication.h> + +class QProcess; +class QStringList; + +class Loader : public QApplication +{ +public: + Loader( QStringList ); + ~Loader(); + +private: + virtual void timerEvent( QTimerEvent* ); + + QProcess *m_proc; + int m_counter; + QWidget *m_splash; + + static const int INTERVAL = 10; //ms +}; + +static bool isSplashEnabled(); +static bool amarokIsRunning(); + +#define foreach( x ) \ + for( QStringList::ConstIterator it = x.begin(), end = x.end(); it != end; ++it ) + +#endif diff --git a/amarok/src/magnatunebrowser/Makefile.am b/amarok/src/magnatunebrowser/Makefile.am new file mode 100644 index 00000000..5d4620a4 --- /dev/null +++ b/amarok/src/magnatunebrowser/Makefile.am @@ -0,0 +1,16 @@ +INCLUDES = -I$(top_builddir)/amarok/src/amarokcore -I$(top_srcdir)/amarok/src/amarokcore -I$(top_srcdir)/amarok/src/analyzers -I$(top_srcdir)/amarok/src/plugin -I$(top_srcdir)/amarok/src/statusbar -I$(top_srcdir)/amarok/src/mediadevice -I$(top_srcdir)/amarok/src/device -I$(top_srcdir)/amarok/src -I$(kde_includes)/arts $(TAGLIB_CFLAGS) $(sqlite_includes) $(mysql_includes) $(postgresql_includes) $(EXSCALIBAR_CFLAGS) $(all_includes) +METASOURCES = AUTO + + +noinst_LTLIBRARIES = libmagnatunebrowser.la +libmagnatunebrowser_la_SOURCES = magnatuneartistinfobox.cpp \ + magnatunebrowser.cpp magnatunedownloaddialogbase.ui magnatunedownloaddialog.cpp \ + magnatunepurchasedialogbase.ui magnatunepurchasedialog.cpp magnatunepurchasehandler.cpp \ + magnatunetypes.cpp magnatunexmlparser.cpp magnatunedatabasehandler.cpp \ + magnatunelistviewitems.cpp magnatunelistview.cpp magnatuneredownloaddialog.cpp \ + magnatuneredownloadhandler.cpp magnatunedownloadinfo.cpp magnatunealbumdownloader.cpp \ + magnatuneredownloaddialogbase.ui +noinst_HEADERS = magnatunedatabasehandler.h magnatunelistviewitems.h \ + magnatunelistview.h magnatuneartistinfobox.h magnatunebrowser.h magnatunedownloaddialog.h \ + magnatunepurchasedialog.h magnatunepurchasehandler.h magnatunetypes.h magnatunexmlparser.h \ + magnatunedownloadinfo.h magnatunealbumdownloader.h diff --git a/amarok/src/magnatunebrowser/magnatunealbumdownloader.cpp b/amarok/src/magnatunebrowser/magnatunealbumdownloader.cpp new file mode 100644 index 00000000..974c85bd --- /dev/null +++ b/amarok/src/magnatunebrowser/magnatunealbumdownloader.cpp @@ -0,0 +1,206 @@ +/* + Copyright (c) 2006 Nikolaj Hald Nielsen <nhnFreespirit@gmail.com> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "amarok.h" +#include "debug.h" +#include "magnatunealbumdownloader.h" +#include "magnatunedatabasehandler.h" +#include "magnatunetypes.h" +#include "statusbar.h" + +#include <stdlib.h> + +MagnatuneAlbumDownloader::MagnatuneAlbumDownloader() +{} + + +MagnatuneAlbumDownloader::~MagnatuneAlbumDownloader() +{} + +void MagnatuneAlbumDownloader::downloadAlbum( MagnatuneDownloadInfo * info ) +{ + + + m_currentAlbumId = info->getAlbumId(); + + KURL downloadUrl = info->getCompleteDownloadUrl(); + m_currentAlbumFileName = downloadUrl.fileName( false ); + + m_currentAlbumUnpackLocation = info->getUnpackLocation(); + + + + debug() << "Download: " << downloadUrl.url() << " to: " << m_currentAlbumUnpackLocation << endl; + debug() << "Using temporary location: " << m_tempDir.name() + m_currentAlbumFileName << endl; + + m_albumDownloadJob = KIO::file_copy( downloadUrl, KURL( m_tempDir.name() + m_currentAlbumFileName ), -1, true, false, false ); + + connect( m_albumDownloadJob, SIGNAL( result( KIO::Job* ) ), SLOT( albumDownloadComplete( KIO::Job* ) ) ); + + Amarok::StatusBar::instance() ->newProgressOperation( m_albumDownloadJob ) + .setDescription( i18n( "Downloading album" ) ) + .setAbortSlot( this, SLOT( albumDownloadAborted() ) ); +} + +void MagnatuneAlbumDownloader::downloadCover( QString albumCoverUrlString, QString fileName ) +{ + KURL downloadUrl( albumCoverUrlString ); + + debug() << "Download Cover: " << downloadUrl.url() << " to: " << m_tempDir.name() << fileName << endl; + + m_albumDownloadJob = KIO::file_copy( downloadUrl, KURL( m_tempDir.name() + fileName ), -1, true, false, false ); + + connect( m_albumDownloadJob, SIGNAL( result( KIO::Job* ) ), SLOT( coverDownloadComplete( KIO::Job* ) ) ); + + Amarok::StatusBar::instance() ->newProgressOperation( m_albumDownloadJob ) + .setDescription( i18n( "Downloading album cover" ) ) + .setAbortSlot( this, SLOT( coverDownloadAborted() ) ); +} + + + +void MagnatuneAlbumDownloader::albumDownloadComplete( KIO::Job * downloadJob ) +{ + + debug() << "album download complete" << endl; + + if ( !downloadJob->error() == 0 ) + { + //TODO: error handling here + return ; + } + if ( downloadJob != m_albumDownloadJob ) + return ; //not the right job, so let's ignore it + + //ok, now we have the .zip file downloaded. All we need is to unpack it to the desired location and add it to the collection. + + QString unzipString = "unzip "+ KProcess::quote( m_tempDir.name() + m_currentAlbumFileName) + " -d " +KProcess::quote( m_currentAlbumUnpackLocation ) + " &"; + + debug() << "unpacking: " << unzipString << endl; + + system( unzipString.ascii() ); + + + + if (m_currentAlbumId != -1 ) { + + //now I really want to add the album cover to the same folder where I just unzipped the album... The + //only way of getting the actual location where the album was unpacked is using the artist and album names + + MagnatuneAlbum album = MagnatuneDatabaseHandler::instance()->getAlbumById( m_currentAlbumId ); + MagnatuneArtist artist = MagnatuneDatabaseHandler::instance()->getArtistById( album.getArtistId() ); + + QString finalAlbumPath = m_currentAlbumUnpackLocation + "/" + artist.getName() + "/" + album.getName(); + QString coverUrlString = album.getCoverURL(); + + + + KURL downloadUrl( coverUrlString ); + + debug() << "Adding cover " << downloadUrl.url() << " to collection at " << finalAlbumPath << endl; + + m_albumDownloadJob = KIO::file_copy( downloadUrl, KURL( finalAlbumPath + "/cover.jpg" ), -1, true, false, false ); + + connect( m_albumDownloadJob, SIGNAL( result( KIO::Job* ) ), SLOT( coverAddComplete( KIO::Job* ) ) ); + + Amarok::StatusBar::instance() ->newProgressOperation( m_albumDownloadJob ) + .setDescription( i18n( "Adding album cover to collection" ) ) + .setAbortSlot( this, SLOT( coverAddAborted() ) ); + + } else { + + //we do not know exactly what album this is (we are most likely using the redownload manager) + emit( downloadComplete( true ) ); + } + +} + +void MagnatuneAlbumDownloader::coverDownloadComplete( KIO::Job * downloadJob ) +{ + debug() << "cover download complete" << endl; + + if ( !downloadJob || !downloadJob->error() == 0 ) + { + //TODO: error handling here + return ; + } + if ( downloadJob != m_albumDownloadJob ) + return ; //not the right job, so let's ignore it + + emit( coverDownloadCompleted( m_tempDir.name() ) ); +} + + +void MagnatuneAlbumDownloader::albumDownloadAborted( ) +{ + Amarok::StatusBar::instance()->endProgressOperation( m_albumDownloadJob ); + m_albumDownloadJob->kill( true ); + delete m_albumDownloadJob; + m_albumDownloadJob = 0; + debug() << "Aborted album download" << endl; + + emit( downloadComplete( false ) ); + +} + +void MagnatuneAlbumDownloader::coverDownloadAborted( ) +{ + Amarok::StatusBar::instance()->endProgressOperation( m_albumDownloadJob ); + m_albumDownloadJob->kill( true ); + delete m_albumDownloadJob; + m_albumDownloadJob = 0; + debug() << "Aborted cover download" << endl; + + emit( coverDownloadComplete( false ) ); +} + +void MagnatuneAlbumDownloader::coverAddComplete(KIO::Job * downloadJob) +{ + + debug() << "cover add complete" << endl; + + if ( !downloadJob || !downloadJob->error() == 0 ) + { + //TODO: error handling here + return ; + } + if ( downloadJob != m_albumDownloadJob ) + return ; //not the right job, so let's ignore it + + emit( downloadComplete( true ) ); //all done, everyone is happy! :-) +} + +void MagnatuneAlbumDownloader::coverAddAborted() +{ + + Amarok::StatusBar::instance()->endProgressOperation( m_albumDownloadJob ); + m_albumDownloadJob->kill( true ); + delete m_albumDownloadJob; + m_albumDownloadJob = 0; + debug() << "Aborted cover add" << endl; + + emit( downloadComplete( true ) ); //the album download still went well, just the cover is missing +} + + + + + + + diff --git a/amarok/src/magnatunebrowser/magnatunealbumdownloader.h b/amarok/src/magnatunebrowser/magnatunealbumdownloader.h new file mode 100644 index 00000000..c247b70a --- /dev/null +++ b/amarok/src/magnatunebrowser/magnatunealbumdownloader.h @@ -0,0 +1,86 @@ +/* + Copyright (c) 2006 Nikolaj Hald Nielsen <nhnFreespirit@gmail.com> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef MAGNATUNEALBUMDOWNLOADER_H +#define MAGNATUNEALBUMDOWNLOADER_H + +#include "magnatunedownloadinfo.h" + +#include <kio/job.h> +#include <kio/jobclasses.h> + +#include <qobject.h> + +#include <ktempdir.h> +/** +This class encapsulates the downloading of an album once all required information has been retrieved + + @author Nikolaj Hald Nielsen <nhnFreespirit@gmail.com> +*/ +class MagnatuneAlbumDownloader: public QObject +{ +Q_OBJECT +public: + MagnatuneAlbumDownloader(); + + ~MagnatuneAlbumDownloader(); + + void downloadCover( QString albumCoverUrlString, QString fileName ); + +signals: + + /** + * This signal is emitted when a download is finished or cancelled + * @param success true is download completed, false if download was cancelled. + */ + void downloadComplete(bool success); + void coverDownloadCompleted(QString coverFileName); + +public slots: + /** + * Initiates the download of an album + * @param url A MagnatuneDownloadInfo object containing all needed information + */ + void downloadAlbum( MagnatuneDownloadInfo * info ); + +protected: + + KIO::FileCopyJob * m_albumDownloadJob; + QString m_currentAlbumUnpackLocation; + QString m_currentAlbumFileName; + int m_currentAlbumId; + KTempDir m_tempDir; + +protected slots: + /** + * Unzip the downloaded album + * @param downLoadJob + */ + void albumDownloadComplete( KIO::Job* downloadJob ); + void albumDownloadAborted(); + + void coverDownloadComplete( KIO::Job* downloadJob ); + void coverDownloadAborted(); + + void coverAddComplete( KIO::Job* downloadJob ); + void coverAddAborted(); + +}; + +#endif diff --git a/amarok/src/magnatunebrowser/magnatuneartistinfobox.cpp b/amarok/src/magnatunebrowser/magnatuneartistinfobox.cpp new file mode 100644 index 00000000..5bee5752 --- /dev/null +++ b/amarok/src/magnatunebrowser/magnatuneartistinfobox.cpp @@ -0,0 +1,155 @@ +/* + Copyright (c) 2006 Nikolaj Hald Nielsen <nhnFreespirit@gmail.com> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "debug.h" +#include "magnatuneartistinfobox.h" +#include "magnatunedatabasehandler.h" + +#include <khtmlview.h> + +#include <qfile.h> + +MagnatuneArtistInfoBox::MagnatuneArtistInfoBox( QWidget *parentWidget, const char *widgetname ) + : KHTMLPart( parentWidget, widgetname ) +{} + + +MagnatuneArtistInfoBox::~MagnatuneArtistInfoBox() +{} + +bool +MagnatuneArtistInfoBox::displayArtistInfo( KURL url ) +{ + debug() << "displayArtistInfo started" << endl; + + // first get the entire artist html page + QString tempFile; + QString orgHtml; + + m_infoDownloadJob = KIO::storedGet( url, false, false ); + Amarok::StatusBar::instance() ->newProgressOperation( m_infoDownloadJob ).setDescription( i18n( "Fetching Artist Info" ) ); + connect( m_infoDownloadJob, SIGNAL( result( KIO::Job* ) ), SLOT( infoDownloadComplete( KIO::Job* ) ) ); + + + return true; +} + +bool +MagnatuneArtistInfoBox::displayAlbumInfo( MagnatuneAlbum *album ) +{ + const MagnatuneArtist artist = MagnatuneDatabaseHandler::instance()->getArtistById( album->getArtistId() ); + const QString artistName = artist.getName(); + + QString infoHtml = "<HTML><HEAD><META HTTP-EQUIV=\"Content-Type\" " + "CONTENT=\"text/html; charset=iso-8859-1\"></HEAD><BODY>"; + + infoHtml += "<div align=\"center\"><strong>"; + infoHtml += artistName; + infoHtml += "</strong><br><em>"; + infoHtml += album->getName(); + infoHtml += "</em><br><br>"; + infoHtml += "<img src=\"" + album->getCoverURL() + + "\" align=\"middle\" border=\"1\">"; + + infoHtml += "<br><br>Genre: " + album->getMp3Genre(); + infoHtml += "<br>Release Year: " + QString::number( album->getLaunchDate().year() ); + infoHtml += "<br><br>From Magnatune.com</div>"; + infoHtml += "</BODY></HTML>"; + + resetScrollBars(); + begin(); + write( infoHtml ); + end(); + show(); + + return true; +} + +void +MagnatuneArtistInfoBox::infoDownloadComplete( KIO::Job * downLoadJob ) +{ + + if ( !downLoadJob->error() == 0 ) + { + //TODO: error handling here + return ; + } + if ( downLoadJob != m_infoDownloadJob ) + return ; //not the right job, so let's ignore it + + KIO::StoredTransferJob* const storedJob = static_cast<KIO::StoredTransferJob*>( downLoadJob ); + QString info = QString( storedJob->data() ); + + QString trimmedInfo = extractArtistInfo( info ); + + //debug() << "html: " << trimmedInfo << endl; + + resetScrollBars(); + this->begin(); + this->write( trimmedInfo ); + this->end(); + this->show(); + + +} + +QString +MagnatuneArtistInfoBox::extractArtistInfo( QString artistPage ) +{ + QString trimmedHtml; + + + int sectionStart = artistPage.find( "<!-- ARTISTBODY -->" ); + int sectionEnd = artistPage.find( "<!-- /ARTISTBODY -->", sectionStart ); + + trimmedHtml = artistPage.mid( sectionStart, sectionEnd - sectionStart ); + + int buyStartIndex = trimmedHtml.find( "<!-- PURCHASE -->" ); + int buyEndIndex; + + //we are going to integrate the buying of music (I hope) so remove these links + + while ( buyStartIndex != -1 ) + { + buyEndIndex = trimmedHtml.find( "<!-- /PURCHASE -->", buyStartIndex ) + 18; + trimmedHtml.remove( buyStartIndex, buyEndIndex - buyStartIndex ); + buyStartIndex = trimmedHtml.find( "<!-- PURCHASE -->", buyStartIndex ); + } + + + QString infoHtml = "<HTML><HEAD><META HTTP-EQUIV=\"Content-Type\" " + "CONTENT=\"text/html; charset=iso-8859-1\"></HEAD><BODY>"; + + infoHtml += trimmedHtml; + infoHtml += "</BODY></HTML>"; + + + return infoHtml; +} + +void MagnatuneArtistInfoBox::resetScrollBars( ) +{ + //note: the scrollbar methods never return 0 + view()->horizontalScrollBar()->setValue(0); + view()->verticalScrollBar()->setValue(0); +} + + +#include "magnatuneartistinfobox.moc" + diff --git a/amarok/src/magnatunebrowser/magnatuneartistinfobox.h b/amarok/src/magnatunebrowser/magnatuneartistinfobox.h new file mode 100644 index 00000000..99c3a850 --- /dev/null +++ b/amarok/src/magnatunebrowser/magnatuneartistinfobox.h @@ -0,0 +1,100 @@ +/* + Copyright (c) 2006 Nikolaj Hald Nielsen <nhnFreespirit@gmail.com> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef MAGNATUNEARTISTINFOBOX_H +#define MAGNATUNEARTISTINFOBOX_H + +#include "amarok.h" +#include "magnatunetypes.h" +#include "statusbar.h" + +#include <khtml_part.h> +#include <kio/jobclasses.h> +#include <kio/job.h> + +/** +A specialized KHTMLPart for displaying html info about a Magnatune artist or album + +@author Nikolaj Hald Nielsen <nhnFreespirit@gmail.com> +*/ +class MagnatuneArtistInfoBox : public KHTMLPart +{ + Q_OBJECT + +public: + /** + * Constructor + * @param parentWidget The parent QWidget + * @param widgetname The name of this widget + * @return New MagnatuneArtistInfoBox object + */ + MagnatuneArtistInfoBox( QWidget *parentWidget, const char *widgetname ); + + /** + * Destructor + * @return Nothing + */ + ~MagnatuneArtistInfoBox(); + + /** + * Fetches Magnatune artist info from the url and formats + * it for display. + * @param url The url of the Magnatune artist page + * @return currently always returns true + */ + bool displayArtistInfo( KURL url ); + + /** + * Display info about a Magnatune album. Retrieves cover url and + * other info from the album. + * @param album The album to display info about. + * @return Always returns true at the moment + */ + bool displayAlbumInfo( MagnatuneAlbum *album ); + +protected: + + KIO::TransferJob *m_infoDownloadJob; + + /** + * Helper function for extracting only the part of the artist page + * that we need. Used by displayArtistInfo + * @param artistPage The artist url + * @return A string containing the artist info as html. + */ + QString extractArtistInfo( QString artistPage ); + + /** + * Helper method to reset the scrollbars when loading a new album + * or artist info page. + */ + void resetScrollBars(); + + +protected slots: + + /** + * Slot for recieving notifications from the download KIO::Job + * @param downLoadJob The job that has completed + */ + void infoDownloadComplete( KIO::Job *downLoadJob ); + +}; + +#endif diff --git a/amarok/src/magnatunebrowser/magnatunebrowser.cpp b/amarok/src/magnatunebrowser/magnatunebrowser.cpp new file mode 100644 index 00000000..3a8ff599 --- /dev/null +++ b/amarok/src/magnatunebrowser/magnatunebrowser.cpp @@ -0,0 +1,523 @@ +/* +Copyright (c) 2006 Nikolaj Hald Nielsen <nhnFreespirit@gmail.com> + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Library General Public +License as published by the Free Software Foundation; either +version 2 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Library General Public License for more details. + +You should have received a copy of the GNU Library General Public License +along with this library; see the file COPYING.LIB. If not, write to +the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, +Boston, MA 02110-1301, USA. +*/ + + +#include "amarok.h" +#include "magnatunebrowser.h" +#include "playlist.h" +#include "magnatunedatabasehandler.h" +#include "debug.h" + +#include <kstandarddirs.h> //locate() +#include <kurl.h> +#include <kiconloader.h> //multiTabBar icons +#include <ktempfile.h> + +#include <qsplitter.h> +#include <qdragobject.h> +#include <qlabel.h> + +MagnatuneBrowser *MagnatuneBrowser::s_instance = 0; + +MagnatuneBrowser::MagnatuneBrowser( const char *name ) + : QVBox( 0, name ) +{ + DEBUG_BLOCK + initTopPanel( ); + + QSplitter *spliter = new QSplitter( Qt::Vertical, this ); + + debug() << "Magnatune browser starting..." << endl; + m_listView = new MagnatuneListView( spliter ); + + m_popupMenu = new QPopupMenu( spliter, "MagnatuneMenu" ); + m_artistInfobox = new MagnatuneArtistInfoBox( spliter, "ArtistInfoBox" ); + + + initBottomPanel(); + + //connect (m_listView, SIGNAL(executed(KListViewItem *)), this, SLOT(itemExecuted(KListViewItem *))); + connect( m_listView, SIGNAL( doubleClicked( QListViewItem * ) ), + this, SLOT( itemExecuted( QListViewItem * ) ) ); + connect( m_listView, SIGNAL( selectionChanged( QListViewItem * ) ), + this, SLOT( selectionChanged( QListViewItem * ) ) ); + connect( m_listView, SIGNAL( rightButtonClicked ( QListViewItem *, const QPoint &, int ) ), + this, SLOT( showPopupMenu( QListViewItem *, const QPoint &, int ) ) ); + connect( m_popupMenu, SIGNAL( aboutToShow() ), + this, SLOT( menuAboutToShow() ) ); + + m_currentInfoUrl = ""; + + m_purchaseHandler = 0; + m_redownloadHandler = 0; + + m_purchaseInProgress = false; + + m_polished = false; + +} + +void MagnatuneBrowser::itemExecuted( QListViewItem * item ) +{ + DEBUG_BLOCK; + switch ( item->depth() ) + { + case 2: + addTrackToPlaylist( dynamic_cast<MagnatuneListViewTrackItem *>( item ) ); + break; + + case 1: + addAlbumToPlaylist( dynamic_cast<MagnatuneListViewAlbumItem *>( item ) ); + break; + + case 0: + addArtistToPlaylist( dynamic_cast<MagnatuneListViewArtistItem *>( item ) ); + break; + + default: + break; + } +} + +void MagnatuneBrowser::addTrackToPlaylist( MagnatuneTrack *item ) +{ + if ( !item ) return ; // sanity check + + debug() << "Magnatune browser: adding single track" << endl; + QString url = item->getHifiURL(); + Playlist * playlist = Playlist::instance(); + playlist->insertMedia( KURL( url ) ); +} + +void MagnatuneBrowser::addAlbumToPlaylist( MagnatuneAlbum * item ) +{ + if ( !item ) return ; // sanity check + debug() << "Magnatune browser: adding album" << endl; + + MagnatuneTrackList tracks = MagnatuneDatabaseHandler::instance() ->getTracksByAlbumId( item->getId() ); + + MagnatuneTrackList::iterator it; + for ( it = tracks.begin(); it != tracks.end(); ++it ) + addTrackToPlaylist( &( *it ) ); + +} + +void MagnatuneBrowser::addArtistToPlaylist( MagnatuneArtist *item ) +{ + if ( !item ) return ; // sanity check + debug() << "Magnatune browser: adding artist" << endl; + + MagnatuneAlbumList albums = MagnatuneDatabaseHandler::instance() ->getAlbumsByArtistId( item->getId(), "" ); + + MagnatuneAlbumList::iterator it; + for ( it = albums.begin(); it != albums.end(); ++it ) + addAlbumToPlaylist( &( *it ) ); +} + +void MagnatuneBrowser::selectionChanged( QListViewItem *item ) +{ + if ( !item ) return ; // sanity check + + debug() << "Selection changed..." << endl; + + + if ( item->depth() == 0 ) + m_purchaseAlbumButton->setEnabled( false ); + else + if ( ! m_purchaseInProgress ) + m_purchaseAlbumButton->setEnabled( true ); + + + if ( !m_isInfoShown ) + return ; + + switch ( item->depth() ) + { + case 0: + { + MagnatuneListViewArtistItem * artistItem = dynamic_cast<MagnatuneListViewArtistItem *>( item ); + if ( artistItem && m_currentInfoUrl != artistItem->getHomeURL() ) + { + m_currentInfoUrl = artistItem->getHomeURL(); + m_artistInfobox->displayArtistInfo( KURL( m_currentInfoUrl ) ); + } + } + break; + + case 1: + { + MagnatuneListViewAlbumItem *albumItem = dynamic_cast<MagnatuneListViewAlbumItem *>( item ); + if ( albumItem && m_currentInfoUrl != albumItem->getCoverURL() ) + { + m_currentInfoUrl = albumItem->getCoverURL(); + m_artistInfobox->displayAlbumInfo( albumItem ); + } + } + break; + + case 2: + { + // a track is selected, show the corrosponding album info! + MagnatuneListViewTrackItem *trackItem = dynamic_cast<MagnatuneListViewTrackItem*>( item ); + if (!trackItem) { + debug() << "dynamic_cast to trackItem failed!" << endl; + return; + } + int albumId = trackItem->getAlbumId(); + MagnatuneAlbum album = MagnatuneDatabaseHandler::instance() ->getAlbumById( albumId ); + m_artistInfobox->displayAlbumInfo( &album ); + } + break; + + default: + break; + } +} + +void MagnatuneBrowser::showPopupMenu( QListViewItem * item, const QPoint & pos, int /*column*/ ) +{ + if ( !item ) return ; + + m_popupMenu->exec( pos ); +} + +void MagnatuneBrowser::addSelectionToPlaylist( ) +{ + QListViewItem * selectedItem = m_listView->selectedItem(); + + switch ( selectedItem->depth() ) + { + case 0: + addArtistToPlaylist( dynamic_cast<MagnatuneListViewArtistItem *>( selectedItem ) ); + break; + case 1: + addAlbumToPlaylist( dynamic_cast<MagnatuneListViewAlbumItem *>( selectedItem ) ); + break; + case 2: + addTrackToPlaylist( dynamic_cast<MagnatuneListViewTrackItem *>( selectedItem ) ); + } +} + +void MagnatuneBrowser::menuAboutToShow( ) +{ + m_popupMenu->clear(); + + QListViewItem *selectedItem = m_listView->selectedItem(); + + if ( !selectedItem ) return ; + + switch ( selectedItem->depth() ) + { + case 0: + m_popupMenu->insertItem( i18n( "Add artist to playlist" ), this, SLOT( addSelectionToPlaylist() ) ); + break; + case 1: + m_popupMenu->insertItem( i18n( "Add album to playlist" ), this, SLOT( addSelectionToPlaylist() ) ); + m_popupMenu->insertItem( i18n( "Purchase album" ), this, SLOT( purchaseSelectedAlbum() ) ); + break; + case 2: + m_popupMenu->insertItem( i18n( "Add track to playlist" ), this, SLOT( addSelectionToPlaylist() ) ); + m_popupMenu->insertItem( i18n( "Purchase album" ), this, SLOT( purchaseAlbumContainingSelectedTrack() ) ); + } +} + +void MagnatuneBrowser::purchaseButtonClicked( ) +{ + + if ( !m_purchaseInProgress ) + { + m_purchaseInProgress = true; + m_purchaseAlbumButton->setEnabled( false ); + + if ( m_listView->selectedItem() ->depth() == 1 ) + purchaseSelectedAlbum( ); + else if ( m_listView->selectedItem() ->depth() == 2 ) + purchaseAlbumContainingSelectedTrack( ); + } +} + +void MagnatuneBrowser::purchaseSelectedAlbum( ) +{ + if ( !m_purchaseHandler ) + { + m_purchaseHandler = new MagnatunePurchaseHandler(); + m_purchaseHandler->setParent( this ); + connect( m_purchaseHandler, SIGNAL( purchaseCompleted( bool ) ), this, SLOT( purchaseCompleted( bool ) ) ); + } + + MagnatuneListViewAlbumItem *selectedAlbum = dynamic_cast<MagnatuneListViewAlbumItem *>( m_listView->selectedItem() ); + + if (selectedAlbum) + m_purchaseHandler->purchaseAlbum( *selectedAlbum ); +} + +void MagnatuneBrowser::purchaseAlbumContainingSelectedTrack( ) +{ + if ( !m_purchaseHandler ) + { + m_purchaseHandler = new MagnatunePurchaseHandler(); + m_purchaseHandler->setParent( this ); + connect( m_purchaseHandler, SIGNAL( purchaseCompleted( bool ) ), this, SLOT( purchaseCompleted( bool ) ) ); + } + + MagnatuneListViewTrackItem *selectedTrack = dynamic_cast<MagnatuneListViewTrackItem *>( m_listView->selectedItem() ); + if (!selectedTrack) { + debug() << "dynamic_cast to selected track failed!" << endl; + return; + } + + MagnatuneAlbum album( MagnatuneDatabaseHandler::instance() ->getAlbumById( selectedTrack->getAlbumId() ) ); + m_purchaseHandler->purchaseAlbum( album ); +} + +void MagnatuneBrowser::initTopPanel( ) +{ + m_topPanel = new QHBox( this, "topPanel", 0 ); + m_topPanel->setMaximumHeight( 24 ); + m_topPanel->setSpacing( 2 ); + m_topPanel->setMargin( 2 ); + + new QLabel ( i18n( "Genre: " ), m_topPanel, "genreLabel", 0 ); + + m_genreComboBox = new QComboBox( false, m_topPanel, "genreComboBox" ); + + updateGenreBox(); + + m_advancedFeaturesButton = new QPushButton( i18n( "Redownload" ), m_topPanel, "advancedButton" ); + connect( m_advancedFeaturesButton, SIGNAL( clicked() ), this, SLOT( processRedownload() ) ); + + connect( m_genreComboBox, SIGNAL( activated ( int ) ), this, SLOT( genreChanged() ) ); +} + +void MagnatuneBrowser::initBottomPanel() +{ + m_bottomPanel = new QVBox( this, "bottomPanel", 0 ); + m_bottomPanel->setMaximumHeight( 54 ); + m_bottomPanel->setSpacing( 2 ); + m_bottomPanel->setMargin( 2 ); + + QHBox *hBox = new QHBox( m_bottomPanel, "bottomHBox", 0 ); + hBox->setMaximumHeight( 24 ); + hBox->setSpacing( 2 ); + //hBox->setMargin( 2 ); + + m_purchaseAlbumButton = new QPushButton( i18n( "Purchase Album" ), m_bottomPanel, "purchaseButton" ); + m_purchaseAlbumButton->setIconSet( SmallIconSet( Amarok::icon( "download" ) ) ); + m_purchaseAlbumButton->setEnabled( false ); + m_purchaseAlbumButton->setMaximumHeight( 24 ); + + m_updateListButton = new QPushButton( i18n( "Update" ), hBox, "updateButton" ); + m_updateListButton->setIconSet( SmallIconSet( Amarok::icon( "rescan" ) ) ); + m_showInfoToggleButton = new QPushButton( i18n( "Show Info" ) , hBox, "showInfoCheckbox" ); + m_showInfoToggleButton->setToggleButton( true ); + m_showInfoToggleButton->setIconSet( SmallIconSet( Amarok::icon( "info" ) ) ); + m_showInfoToggleButton->setOn( true ); + + m_isInfoShown = true; + + connect( m_showInfoToggleButton, SIGNAL( toggled( bool ) ), this, SLOT( showInfo( bool ) ) ); + connect( m_updateListButton, SIGNAL( clicked() ), this, SLOT( updateButtonClicked() ) ); + connect( m_purchaseAlbumButton, SIGNAL( clicked() ) , this, SLOT( purchaseButtonClicked() ) ); +} + +void MagnatuneBrowser::updateButtonClicked() +{ + m_updateListButton->setEnabled( false ); + updateMagnatuneList(); +} + +bool MagnatuneBrowser::updateMagnatuneList() +{ + //download new list from magnatune + + m_listDownloadJob = KIO::storedGet( KURL( "http://magnatune.com/info/album_info.xml" ), false, false ); + Amarok::StatusBar::instance() ->newProgressOperation( m_listDownloadJob ) + .setDescription( i18n( "Downloading Magnatune.com Database" ) ) + .setAbortSlot( this, SLOT( listDownloadCancelled() ) ); + + connect( m_listDownloadJob, SIGNAL( result( KIO::Job* ) ), SLOT( listDownloadComplete( KIO::Job* ) ) ); + + return true; +} + + +void MagnatuneBrowser::listDownloadComplete( KIO::Job * downLoadJob ) +{ + + if ( downLoadJob != m_listDownloadJob ) + return ; //not the right job, so let's ignore it + + m_updateListButton->setEnabled( true ); + if ( !downLoadJob->error() == 0 ) + { + //TODO: error handling here + return ; + } + + + KIO::StoredTransferJob* const storedJob = static_cast<KIO::StoredTransferJob*>( downLoadJob ); + QString list = QString( storedJob->data() ); + + KTempFile tfile; + m_tempFileName = tfile.name(); + QFile file( m_tempFileName ); + + if ( file.open( IO_WriteOnly ) ) + { + QTextStream stream( &file ); + stream << list; + file.close(); + } + + + MagnatuneXmlParser * parser = new MagnatuneXmlParser( m_tempFileName ); + connect( parser, SIGNAL( doneParsing() ), SLOT( doneParsing() ) ); + + ThreadManager::instance() ->queueJob( parser ); +} + +void MagnatuneBrowser::listDownloadCancelled( ) +{ + + + Amarok::StatusBar::instance() ->endProgressOperation( m_listDownloadJob ); + m_listDownloadJob->kill( true ); + delete m_listDownloadJob; + m_listDownloadJob = 0; + debug() << "Aborted xml download" << endl; + + m_updateListButton->setEnabled( true ); +} + +void MagnatuneBrowser::showInfo( bool show ) +{ + if ( show ) + { + m_isInfoShown = true; + m_artistInfobox->widget() ->setMaximumHeight( 2000 ); + } + else + { + m_artistInfobox->widget() ->setMaximumHeight( 0 ); + m_isInfoShown = false; + } +} + +void MagnatuneBrowser::updateList() +{ + + DEBUG_BLOCK + const QString genre = m_genreComboBox->currentText(); + + MagnatuneArtistList artists; + artists = MagnatuneDatabaseHandler::instance() ->getArtistsByGenre( genre ); + + m_listView->clear(); + MagnatuneArtistList::iterator it; + for ( it = artists.begin(); it != artists.end(); ++it ) + new MagnatuneListViewArtistItem( ( *it ), m_listView ); + + m_listView->repaintContents(); +} + +void MagnatuneBrowser::genreChanged() +{ + debug() << "Genre changed..." << endl; + updateList( ); +} + + +void MagnatuneBrowser::doneParsing() +{ + DEBUG_BLOCK + updateList(); + updateGenreBox( ); + updateList(); // stupid stupid hack.... + if( !QFile::remove( m_tempFileName ) ) + debug() << "Couldn't remove temp file at " << m_tempFileName << endl; + m_tempFileName = QString(); +} + +void MagnatuneBrowser::updateGenreBox() +{ + const QStringList genres = MagnatuneDatabaseHandler::instance() ->getAlbumGenres(); + + m_genreComboBox->clear(); + m_genreComboBox->insertItem ( "All" , 0 ); // should not be i18n'ed as it is + //used as a trigger in the code in the database handler. + + foreach( genres ) + m_genreComboBox->insertItem( ( *it ), -1 ); +} + +void MagnatuneBrowser::processRedownload( ) +{ + if ( m_redownloadHandler == 0 ) + { + m_redownloadHandler = new MagnatuneRedownloadHandler( this ); + } + m_redownloadHandler->showRedownloadDialog(); +} + +void MagnatuneBrowser::purchaseCompleted( bool /*success*/ ) +{ + + if ( m_purchaseHandler != 0 ) + { + delete m_purchaseHandler; + m_purchaseHandler = 0; + } + + m_purchaseAlbumButton->setEnabled( true ); + m_purchaseInProgress = false; + + debug() << "Purchase operation complete" << endl; + + //TODO: display some kind of success dialog here? + + +} + +void MagnatuneBrowser::polish( ) +{ + + DEBUG_BLOCK; + + + if (!m_polished) { + m_polished = true; + updateList( ); + m_artistInfobox->begin( KURL( locate( "data", "amarok/data/" ) ) ); + m_artistInfobox->write( "<table align='center' border='0'><tbody align='center' valign='top'>" + "<tr align='center'><td><div align='center'>" + "<IMG src='magnatune_logo.png' width='200' height='36' align='center' border='0'>" + "</div></td></tr><tr><td><BR>" + + i18n( "Welcome to Amarok's integrated Magnatune.com store. If this is the " + "first time you run it, you must update the database by pressing the " + "'Update' button below." ) + + "</td></tr></tbody></table>" ); + m_artistInfobox->end(); + + } + +} + + + +#include "magnatunebrowser.moc" diff --git a/amarok/src/magnatunebrowser/magnatunebrowser.h b/amarok/src/magnatunebrowser/magnatunebrowser.h new file mode 100644 index 00000000..1a374308 --- /dev/null +++ b/amarok/src/magnatunebrowser/magnatunebrowser.h @@ -0,0 +1,253 @@ +/* + Copyright (c) 2006 Nikolaj Hald Nielsen <nhnFreespirit@gmail.com> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef AMAROKMAGNATUNEBROWSER_H +#define AMAROKMAGNATUNEBROWSER_H + + +#include "amarok.h" +#include "magnatuneartistinfobox.h" +#include "magnatunelistview.h" +#include "magnatunelistviewitems.h" +#include "magnatunepurchasedialog.h" +#include "magnatunepurchasehandler.h" +#include "magnatuneredownloadhandler.h" +#include "magnatunexmlparser.h" + +#include <kio/job.h> +#include <kio/jobclasses.h> + +#include <qcheckbox.h> +#include <qcombobox.h> +#include <qhbox.h> +#include <qpopupmenu.h> +#include <qpushbutton.h> +#include <qvbox.h> + + + +/** +Amarok browser that displays all the music available at magnatune.com and makes it available for previewing and purchasing. +Implemented as a singleton + +@author Nikolaj Hald Nielsen <nhnFreespirit@gmail.com> +*/ +class MagnatuneBrowser : public QVBox +{ + Q_OBJECT + +public: + /** + * Destructor + */ + ~MagnatuneBrowser() { } + + /** + * Retrieves the class instance (Singleton pattern) + * @return pointer to the class instance + */ + static MagnatuneBrowser *instance() { + if(!s_instance) s_instance = new MagnatuneBrowser("MagnatuneBrowser"); + return s_instance; + } + +private slots: + + /** + * Slot for recieving aboutToShow signals from the right click menu. + * Inserts items in the menu based on the type of the current selection + */ + void menuAboutToShow(); + + /** + * Slot called when the purchase album button is clicked. Starts a purchase + */ + void purchaseButtonClicked(); + + /** + * Slot for recieving notification from the right click menu that the user + * has chosen to purchase an album. Starts a purchase + */ + void purchaseSelectedAlbum(); + + /** + * Slot for recieving notification from the right click menu that the user + * has chosen to purchase the album contining the selected track. + * Starts a purchase + */ + void purchaseAlbumContainingSelectedTrack(); + + /** + * Slot for recieving notification from the right click menu that the user + * has selected "add to playlist" for the currently selected item, + */ + void addSelectionToPlaylist(); + + /** + * Slot for recieving notification that the user has double clicked an + * item in the list view. Ads item to playlist. + * @param item The item that was double clicked + */ + void itemExecuted( QListViewItem * item); + + /** + * Slot for recieving notification when a new item in the list is selected. + * Adds the corrosponding artist or album info to the info view (if visible) + * @param item The selected item + */ + void selectionChanged( QListViewItem * item); + + /** + * Slot for recieving notifications about right clicks in the list view. + * if selection is valid the popup menu is shown + * @param item The item that was right clicked + * @param pos The position of the cursor at the time of thre right click + * @param column The column of the item that was right clicked (unused) + */ + void showPopupMenu( QListViewItem * item, const QPoint & pos, int column ); + + /** + * Slot for recieving notification that the update button has been clicked. + */ + void updateButtonClicked(); + + /** + * Toggles the info area on and off + * @param show If true the info box is shown, if false it is hidden + */ + void showInfo(bool show); + + /** + * Slot for recieving notification when the Magnatune xml file has been downloaded. + * Triggers a parse of the file to get the info added to the databse + * @param downLoadJob The calling download Job + */ + void listDownloadComplete( KIO::Job* downLoadJob); + + /** + * Slot for catching cancelled list downloads + */ + void listDownloadCancelled(); + + /** + * Slot called when the genre combo box selection changes. Triggers an update of the list view. + */ + void genreChanged(); + + /** + * Slot called when the parsing of the Magnatuin xml file is completed. + * Triggers an update of the list view and the genre combo box + */ + void doneParsing(); + + /** + * Starts the process of redownloading a previously bought album + */ + void processRedownload(); + + /** + * Slot for recieving notifications of completed purchase operations + * @param success Was the operation a success? + */ + void purchaseCompleted( bool success ); + + + /** + * Don not do expensive initializations before we are actually shown + */ + void polish(); + +private: + + MagnatuneBrowser( const char *name ); + + /** + * Helper function that initializes the button panel below the list view + */ + void initBottomPanel(); + + /** + * Helper function that initializes the genre selection panel above the list view + */ + void initTopPanel(); + + /** + * Starts downloading an updated track list xml file from + * http://magnatune.com/info/album_info.xml + * @return Currently always returns true + */ + bool updateMagnatuneList(); + + /** + * Adds a magnatune preview track to the playlist. + * @param item The track to add + */ + void addTrackToPlaylist ( MagnatuneTrack *item ); + + /** + * Adds all preview tracks on a magnatune album to the playlist + * @param item The album to add + */ + void addAlbumToPlaylist ( MagnatuneAlbum *item ); + + /** + * Adds all preview tracks on all albums by a given artist to the playlist + * @param item the artist to add + */ + void addArtistToPlaylist( MagnatuneArtist *item ); + + /** + * Clears the list view and inserts artists based on the currently selected genre + */ + void updateList(); + + /** + * Clears the genre combo box and inserts all genres from the database + */ + void updateGenreBox(); + + + static MagnatuneBrowser *s_instance; + + MagnatuneListView *m_listView; + MagnatuneArtistInfoBox *m_artistInfobox; + QString m_currentInfoUrl; + QPopupMenu *m_popupMenu; + MagnatunePurchaseHandler *m_purchaseHandler; + MagnatuneRedownloadHandler *m_redownloadHandler; + + QHBox *m_topPanel; + QVBox *m_bottomPanel; + QPushButton *m_advancedFeaturesButton; + QPushButton *m_updateListButton; + QPushButton *m_purchaseAlbumButton; + QPushButton *m_showInfoToggleButton; + + QComboBox *m_genreComboBox; + bool m_isInfoShown; + bool m_purchaseInProgress; + bool m_polished; + + QString m_tempFileName; + + KIO::TransferJob * m_listDownloadJob; +}; + + +#endif diff --git a/amarok/src/magnatunebrowser/magnatunedatabasehandler.cpp b/amarok/src/magnatunebrowser/magnatunedatabasehandler.cpp new file mode 100644 index 00000000..48a52599 --- /dev/null +++ b/amarok/src/magnatunebrowser/magnatunedatabasehandler.cpp @@ -0,0 +1,572 @@ +/* + Copyright (c) 2006 Nikolaj Hald Nielsen <nhnFreespirit@gmail.com> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "magnatunedatabasehandler.h" + +#include "debug.h" + + +MagnatuneDatabaseHandler *MagnatuneDatabaseHandler::m_pInstance = 0; + +MagnatuneDatabaseHandler* +MagnatuneDatabaseHandler::instance() +{ + if ( m_pInstance == 0 ) + { + m_pInstance = new MagnatuneDatabaseHandler(); + } + return m_pInstance; +} + + +MagnatuneDatabaseHandler::MagnatuneDatabaseHandler() +{} + + +MagnatuneDatabaseHandler::~MagnatuneDatabaseHandler() +{} + +void +MagnatuneDatabaseHandler::createDatabase( ) +{ + //Get database instance + CollectionDB *db = CollectionDB::instance(); + + QString tracksAutoIncrement = ""; + QString albumsAutoIncrement = ""; + QString artistAutoIncrement = ""; + + if ( db->getDbConnectionType() == DbConnection::postgresql ) + { + db->query( QString( "CREATE SEQUENCE magnatune_track_seq;" ) ); + db->query( QString( "CREATE SEQUENCE magnatune_album_seq;" ) ); + db->query( QString( "CREATE SEQUENCE magnatune_artist_seq;" ) ); + + tracksAutoIncrement = QString( "DEFAULT nextval('magnatune_track_seq')" ); + albumsAutoIncrement = QString( "DEFAULT nextval('magnatune_album_seq')" ); + artistAutoIncrement = QString( "DEFAULT nextval('magnatune_artist_seq')" ); + + } + else if ( db->getDbConnectionType() == DbConnection::mysql ) + { + tracksAutoIncrement = "AUTO_INCREMENT"; + albumsAutoIncrement = "AUTO_INCREMENT"; + artistAutoIncrement = "AUTO_INCREMENT"; + + } + + // create table containing tracks + QString queryString = "CREATE TABLE magnatune_tracks (" + "id INTEGER PRIMARY KEY " + tracksAutoIncrement + ',' + + "name " + db->textColumnType() + ',' + + "track_number INTEGER," + "length INTEGER," + "album_id INTEGER," + "artist_id INTEGER," + "preview_lofi " + db->exactTextColumnType() + ',' + + "preview_hifi " + db->exactTextColumnType() + ");"; + + debug() << "Creating mangnatune_tracks: " << queryString << endl; + + + QStringList result = db->query( queryString ); + + //Create album table + queryString = "CREATE TABLE magnatune_albums (" + "id INTEGER PRIMARY KEY " + albumsAutoIncrement + ',' + + "name " + db->textColumnType() + ',' + + "year INTEGER," + "artist_id INTEGER," + "genre " + db->textColumnType() + ',' + + "album_code " + db->textColumnType() + ',' + + "cover_url " + db->exactTextColumnType() + ");"; + + debug() << "Creating mangnatune_albums: " << queryString << endl; + + result = db->query( queryString ); + + //Create artist table + queryString = "CREATE TABLE magnatune_artists (" + "id INTEGER PRIMARY KEY " + artistAutoIncrement + ',' + + "name " + db->textColumnType() + ',' + + "artist_page " + db->exactTextColumnType() + ',' + + "description " + db->textColumnType() + ',' + + "photo_url " + db->exactTextColumnType() + ");"; + + debug() << "Creating mangnatune_artist: " << queryString << endl; + + result = db->query( queryString ); + + + +} + +void +MagnatuneDatabaseHandler::destroyDatabase( ) +{ + CollectionDB *db = CollectionDB::instance(); + QStringList result = db->query( "DROP TABLE magnatune_tracks;" ); + result = db->query( "DROP TABLE magnatune_albums;" ); + result = db->query( "DROP TABLE magnatune_artists;" ); + + if ( db->getDbConnectionType() == DbConnection::postgresql ) + { + db->query( QString( "DROP SEQUENCE magnatune_track_seq;" ) ); + db->query( QString( "DROP SEQUENCE magnatune_album_seq;" ) ); + db->query( QString( "DROP SEQUENCE magnatune_artist_seq;" ) ); + } +} + +int +MagnatuneDatabaseHandler::insertTrack( MagnatuneTrack *track, int albumId, int artistId ) +{ + QString numberString; + + CollectionDB *db = CollectionDB::instance(); + QString queryString = "INSERT INTO magnatune_tracks ( name, track_number, length, " + "album_id, artist_id, preview_lofi, preview_hifi ) VALUES ( '" + + db->escapeString( track->getName() ) + "', " + + QString::number( track->getTrackNumber() ) + ", " + + QString::number( track->getDuration() ) + ", " + + QString::number( albumId ) + ", " + + QString::number( artistId ) + ", '" + + db->escapeString( track->getLofiURL() ) + "', '" + + db->escapeString( track->getHifiURL() ) + "' );"; + + + // debug() << "Adding Magnatune track " << queryString << endl; + + return db->insert( queryString, NULL ); +} + +int +MagnatuneDatabaseHandler::insertAlbum( MagnatuneAlbum *album, int artistId ) +{ + QString queryString; + CollectionDB *db = CollectionDB::instance(); + queryString = "INSERT INTO magnatune_albums ( name, year, artist_id, " + "genre, album_code, cover_url ) VALUES ( '" + + db->escapeString( db->escapeString( album->getName() ) ) + "', " + + QString::number( album->getLaunchDate().year() ) + ", " + + QString::number( artistId ) + ", '" + + db->escapeString( album->getMp3Genre() ) + "', '" + + album->getAlbumCode() + "', '" + + db->escapeString( album->getCoverURL() ) + "' );"; + + //debug() << "Adding Magnatune album " << queryString << endl; + + return db->insert( queryString, 0 ); +} + + + +int +MagnatuneDatabaseHandler::insertArtist( MagnatuneArtist *artist ) +{ + QString queryString; + CollectionDB *db = CollectionDB::instance(); + queryString = "INSERT INTO magnatune_artists ( name, artist_page, description, " + "photo_url ) VALUES ( '" + + db->escapeString( db->escapeString( artist->getName() ) ) + "', '" + + db->escapeString( artist->getHomeURL() ) + "', '" + + db->escapeString( artist->getDescription() ) + "', '" + + db->escapeString( artist->getPhotoURL() ) + "' );"; + + //debug() << "Adding Magnatune artist " << queryString << endl; + + return db->insert( queryString, 0 ); +} + + +int +MagnatuneDatabaseHandler::getArtistIdByExactName( QString name ) +{ + CollectionDB *db = CollectionDB::instance(); + + QString queryString = "SELECT id from magnatune_artists WHERE name='" + db->escapeString( name ) + "';"; + QStringList result = db->query( queryString ); + + //debug() << "Looking for id of artist " << name << ":" << endl; + + if ( result.size() < 1 ) return -1; + int artistId = result.first().toInt(); + + //debug() << " Found: " << QString::number( artistId ) << ":" << endl; + + return artistId; + +} + +int MagnatuneDatabaseHandler::getAlbumIdByAlbumCode( QString albumcode ) +{ + + CollectionDB *db = CollectionDB::instance(); + + QString queryString = "SELECT id from magnatune_albums WHERE album_code='" + db->escapeString( albumcode ) + "';"; + QStringList result = db->query( queryString ); + + //debug() << "Looking for id of album " << albumcode << ":" << endl; + + if ( result.size() < 1 ) return -1; + int albumId = result.first().toInt(); + + //debug() << " Found: " << QString::number( albumId ) << ":" << endl; + + return albumId; +} + + +MagnatuneArtistList +MagnatuneDatabaseHandler::getArtistsByGenre( QString genre ) +{ + + QString genreSql = ""; + + if ( genre != "All" ) + { + genreSql = "magnatune_albums.genre='" + genre + "' AND "; + } + + CollectionDB *db = CollectionDB::instance(); + + QString queryString; + queryString = "SELECT DISTINCT magnatune_artists.id, " + "magnatune_artists.name, magnatune_artists.artist_page, " + "magnatune_artists.description, magnatune_artists.photo_url " + "FROM magnatune_albums, magnatune_artists " + "WHERE " + genreSql + "magnatune_albums.artist_id " + "= magnatune_artists.id;"; + + QStringList result = db->query( queryString ); + + debug() << "Looking for artist in genre: " << genre << endl; + + MagnatuneArtistList list; + + while ( result.size() > 0 ) + { + MagnatuneArtist artist; + + artist.setId( result.front().toInt() ); + result.pop_front(); + + artist.setName( result.front() ); + result.pop_front(); + + artist.setHomeURL( result.front() ); + result.pop_front(); + + artist.setDescription( result.front() ); + result.pop_front(); + + artist.setPhotoURL( result.front() ); + result.pop_front(); + + list.append( artist ); + } + + return list; + + +} + +MagnatuneAlbumList +MagnatuneDatabaseHandler::getAlbumsByArtistId( int id, QString genre ) +{ + + QString genreSqlString; + + if ( genre.isEmpty() ) + { + genreSqlString = ""; + } + else + { + genreSqlString = " AND magnatune_albums.genre='" + genre + '\''; + } + + CollectionDB *db = CollectionDB::instance(); + + QString queryString; + queryString = "SELECT DISTINCT id, " + "name, year, " + "artist_id, genre, " + "album_code, cover_url " + "FROM magnatune_albums " + "WHERE artist_id = '" + QString::number( id ) + '\''; + + queryString += genreSqlString; + queryString += ';'; + + + QStringList result = db->query( queryString ); + + + MagnatuneAlbumList list; + debug() << "Looking for Albums..." << endl; + debug() << "Query string:" << queryString << endl; + + + while ( result.size() > 0 ) + { + MagnatuneAlbum album; + + album.setId( result.front().toInt() ); + result.pop_front(); + + album.setName( result.front() ); + result.pop_front(); + + album.setLaunchDate( QDate( result.front().toInt(), 1, 1 ) ); + result.pop_front(); + + album.setArtistId( result.front().toInt() ); + result.pop_front(); + + album.setMp3Genre( result.front() ); + result.pop_front(); + + album.setAlbumCode( result.front() ); + result.pop_front(); + + album.setCoverURL( result.front() ); + result.pop_front(); + + list.append( album ); + } + + return list; + + +} + +MagnatuneTrackList +MagnatuneDatabaseHandler::getTracksByAlbumId( int id ) +{ + + CollectionDB *db = CollectionDB::instance(); + + QString queryString; + queryString = "SELECT DISTINCT id, " + "name, track_number, " + "length, album_id, " + "artist_id, preview_lofi, " + "preview_hifi " + "FROM magnatune_tracks " + "WHERE album_id = '" + QString::number( id ) + "';"; + + QStringList result = db->query( queryString ); + + + MagnatuneTrackList list; + + debug() << "Looking for tracks..." << endl; + debug() << "Query string:" << queryString << endl; + + + while ( result.size() > 0 ) + { + + debug() << "track start" << endl; + MagnatuneTrack track; + + track.setId( result.front().toInt() ); + result.pop_front(); + + track.setName( result.front() ); + result.pop_front(); + + track.setTrackNumber( result.front().toInt() ); + result.pop_front(); + + track.setDuration( result.front().toInt() ); + result.pop_front(); + + track.setAlbumId( result.front().toInt() ); + result.pop_front(); + + track.setArtistId( result.front().toInt() ); + result.pop_front(); + + track.setLofiURL( result.front() ); + result.pop_front(); + + track.setHifiURL( result.front() ); + result.pop_front(); + + list.append( track ); + debug() << "track end" << endl; + } + + return list; + +} + +QStringList +MagnatuneDatabaseHandler::getAlbumGenres( ) +{ + + CollectionDB *db = CollectionDB::instance(); + + QString queryString; + queryString = "SELECT DISTINCT genre " + "FROM magnatune_albums " + "ORDER BY genre;"; + + return db->query( queryString ); +} + +void +MagnatuneDatabaseHandler::begin( ) +{ + + CollectionDB *db = CollectionDB::instance(); + + QString queryString = "BEGIN;"; + + db->query( queryString ); +} + +void +MagnatuneDatabaseHandler::commit( ) +{ + CollectionDB *db = CollectionDB::instance(); + QString queryString = "COMMIT;"; + + db->query( queryString ); + +} + +MagnatuneArtist +MagnatuneDatabaseHandler::getArtistById( int id ) +{ + + CollectionDB *db = CollectionDB::instance(); + + QString queryString; + queryString = "SELECT id, " + "name, artist_page, " + "description, photo_url " + "FROM magnatune_artists " + "WHERE id = '" + QString::number( id ) + "';"; + + QStringList result = db->query( queryString ); + + MagnatuneArtist artist; + + if ( result.size() == 5 ) + { + + artist.setId( result.front().toInt() ); + result.pop_front(); + + artist.setName( result.front() ); + result.pop_front(); + + artist.setHomeURL( result.front() ); + result.pop_front(); + + artist.setDescription( result.front() ); + result.pop_front(); + + artist.setPhotoURL( result.front() ); + result.pop_front(); + + } + + + return artist; +} + +MagnatuneAlbum +MagnatuneDatabaseHandler::getAlbumById( int id ) +{ + + + CollectionDB *db = CollectionDB::instance(); + + QString queryString; + queryString = "SELECT id, " + "name, year, " + "artist_id, genre, " + "album_code, cover_url " + "FROM magnatune_albums " + "WHERE id = '" + QString::number( id ) + "';"; + + QStringList result = db->query( queryString ); + + MagnatuneAlbum album; + + if ( result.size() == 7 ) + { + + album.setId( result.front().toInt() ); + result.pop_front(); + + album.setName( result.front() ); + result.pop_front(); + + album.setLaunchDate( QDate( result.front().toInt(), 1, 1 ) ); + result.pop_front(); + + album.setArtistId( result.front().toInt() ); + result.pop_front(); + + album.setMp3Genre( result.front() ); + result.pop_front(); + + album.setAlbumCode( result.front() ); + result.pop_front(); + + album.setCoverURL( result.front() ); + result.pop_front(); + + } + + return album; + +} + + +MagnatuneTrackList +MagnatuneDatabaseHandler::getTracksByArtistId( int id ) +{ + + MagnatuneAlbumList albums = getAlbumsByArtistId( id, "" ); + MagnatuneAlbumList::iterator it; + MagnatuneTrackList tracks; + + + + for ( it = albums.begin(); it != albums.end(); ++it ) + { + + tracks += getTracksByAlbumId( ( *it ).getId() ); + + } + + return tracks; + +} + + + + + + diff --git a/amarok/src/magnatunebrowser/magnatunedatabasehandler.h b/amarok/src/magnatunebrowser/magnatunedatabasehandler.h new file mode 100644 index 00000000..76d8e98c --- /dev/null +++ b/amarok/src/magnatunebrowser/magnatunedatabasehandler.h @@ -0,0 +1,169 @@ +/* + Copyright (c) 2006 Nikolaj Hald Nielsen <nhnFreespirit@gmail.com> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef MAGNATUNEDATABASEHANDLER_H +#define MAGNATUNEDATABASEHANDLER_H + +#include "collectiondb.h" +#include "magnatunetypes.h" + +#include <qstringlist.h> + + +/** +* This class wraps the database operations needed by the MagnatuneBrowser +* Uses the singleton pattern +* +* @author Nikolaj Hald Nielsen <nhnFreespirit@gmail.com> +*/ +class MagnatuneDatabaseHandler{ +public: + + + /** + * Function for retrieving the singleton + * @return pointer to the singleton + */ + static MagnatuneDatabaseHandler * instance(); + + ~MagnatuneDatabaseHandler(); + + /** + * Creates the tables needed to store Magnatune info + */ + void createDatabase(); + + /** + * Destroys Magnatune tables + */ + void destroyDatabase(); + + /** + * Inserts a new track into the Magnatune database + * @param track pointer to the track to insert + * @param albumId id of the album containing the track + * @param artistId id of the artist performing the track + * @return the database id of the newly inserted track + */ + int insertTrack(MagnatuneTrack *track, int albumId, int artistId); + + /** + * inserts a new album into the Magnatune database + * @param album pointer to the album to insert + * @param artistId id of the artist performing the album + * @return the database id of the newly inserted album + */ + int insertAlbum(MagnatuneAlbum *album, int artistId); + + /** + * inserts a new artist into the Magnatune database + * @param artist pointer to the artist to insert + * @return the database id of the newly inserted artist + */ + int insertArtist(MagnatuneArtist *artist); + + //get id, or -1 if artist does not exist + /** + * Retrieves the id of a named artist + * @param name artist name to retrieve + * @return id of artist. -1 if no artist is found + */ + int getArtistIdByExactName(QString name); + + + /** + * Retrieves the id of an album based on its unique album code. + * @param albumcode The album code. + * @return The id of the album, -1 if not foud. + */ + int getAlbumIdByAlbumCode( QString albumcode ); + + /** + * Returns all artist that has albums in a given genre. If an artist has both a Rock + * and a Techno album, he will be included when searching for either + * @param genre the genre + * @return A list of artist in the genre + */ + MagnatuneArtistList getArtistsByGenre(QString genre); + + /** + * Returns the artist with a given id + * @param id The id of the artist to look for + * @return The artist with the given id. Returns an empty artist if not found. + */ + MagnatuneArtist getArtistById(int id); + + /** + * Returns the album with a given id + * @param id The id of the album to look for + * @return The album with the given id. Returns an empty album if not found. + */ + MagnatuneAlbum getAlbumById(int id); + + /** + * Retrieves all albums by a single artist from the database + * @param id The id of the artist + * @param genre Limits the albums to a specific genre. Use "All" to get all albums + * @return List of albums. empty if none are found + */ + MagnatuneAlbumList getAlbumsByArtistId(int id, QString genre); + + /** + * Retrieves all tracks on a given album + * @param id The id of the album + * @return A list of tracks. Empty if album is not found or has no tracks + */ + MagnatuneTrackList getTracksByAlbumId(int id); + + /** + * Retrieves all tracks by given artist + * @param id The id of the artist + * @return A list of tracks. Empty if artist is not found, artist has no albums or albums have no tracks + */ + MagnatuneTrackList getTracksByArtistId(int id); + + /** + * Retrieves a list of all genres present in the databse + * @return A list of genres + */ + QStringList getAlbumGenres(); + + /** + * Begins a database transaction. Must be followed by a later call to commit() + */ + void begin(); + + /** + * Completes (executes) a database transaction. Must be preceded by a call to begin() + */ + void commit(); + + +protected: + + /** + * Private constructor (singleton pattern) + * @return Pointer to new object + */ + MagnatuneDatabaseHandler(); + static MagnatuneDatabaseHandler * m_pInstance; + +}; + +#endif diff --git a/amarok/src/magnatunebrowser/magnatunedownloaddialog.cpp b/amarok/src/magnatunebrowser/magnatunedownloaddialog.cpp new file mode 100644 index 00000000..2c13f56c --- /dev/null +++ b/amarok/src/magnatunebrowser/magnatunedownloaddialog.cpp @@ -0,0 +1,83 @@ +/* +Copyright (c) 2006 Nikolaj Hald Nielsen <nhnFreespirit@gmail.com> + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Library General Public +License as published by the Free Software Foundation; either +version 2 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Library General Public License for more details. + +You should have received a copy of the GNU Library General Public License +along with this library; see the file COPYING.LIB. If not, write to +the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, +Boston, MA 02110-1301, USA. +*/ + + +#include "debug.h" +#include "magnatunedownloaddialog.h" + +#include <kfiledialog.h> +#include <kurlrequester.h> + +#include <qcheckbox.h> +#include <qcombobox.h> +#include <qtextedit.h> + + +MagnatuneDownloadDialog::MagnatuneDownloadDialog( QWidget *parent, const char *name, bool modal, WFlags fl ) + : MagnatuneDownloadDialogBase( parent, name, modal, fl ) +{ + downloadTargetURLRequester->fileDialog() ->setMode( KFile::Directory ); + m_currentDownloadInfo = 0; + +} + +MagnatuneDownloadDialog::~MagnatuneDownloadDialog() +{ + delete m_currentDownloadInfo; +} + + +void MagnatuneDownloadDialog::downloadButtonClicked( ) +{ + + if (m_currentDownloadInfo == 0) return; + + m_currentDownloadInfo->setFormatSelection(formatComboBox->currentText()); + m_currentDownloadInfo->setUnpackUrl(downloadTargetURLRequester->url()); + + emit( downloadAlbum( m_currentDownloadInfo ) ); + + close(); + +} + +void MagnatuneDownloadDialog::setDownloadInfo( MagnatuneDownloadInfo * info ) +{ + delete m_currentDownloadInfo; + + m_currentDownloadInfo = info; + + DownloadFormatMap formatMap = info->getFormatMap(); + + DownloadFormatMap::Iterator it; + + for ( it = formatMap.begin(); it != formatMap.end(); ++it ) + { + formatComboBox->insertItem( it.key() ); + } + + infoEdit->setText( info->getDownloadMessage() ); + +} + +/*$SPECIALIZATION$*/ + + +#include "magnatunedownloaddialog.moc" + diff --git a/amarok/src/magnatunebrowser/magnatunedownloaddialog.h b/amarok/src/magnatunebrowser/magnatunedownloaddialog.h new file mode 100644 index 00000000..726900d2 --- /dev/null +++ b/amarok/src/magnatunebrowser/magnatunedownloaddialog.h @@ -0,0 +1,87 @@ +/* + Copyright (c) 2006 Nikolaj Hald Nielsen <nhnFreespirit@gmail.com> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef MAGNATUNEDOWNLOADDIALOG_H +#define MAGNATUNEDOWNLOADDIALOG_H + +#include "magnatunedownloaddialogbase.h" +#include "magnatunedownloadinfo.h" + +#include <qmap.h> + + +/** + Dialog for choosing download format and location. Also displays additional info from Magnatune.com. + + @author Nikolaj Hald Nielsen <nhnFreespirit@gmail.com> +*/ +class MagnatuneDownloadDialog : public MagnatuneDownloadDialogBase +{ + Q_OBJECT + +public: + /** + * Overridden constructor. + * @param parent Pointer to the parent QWidget. + * @param name Name of this widget. + * @param modal Sets modal state. + * @param fl Additional dialog flags. + */ + MagnatuneDownloadDialog( QWidget* parent = 0, const char* name = 0, bool modal = false, WFlags fl = 0 ); + + /** + * Destructor + */ + ~MagnatuneDownloadDialog(); + /*$PUBLIC_FUNCTIONS$*/ + + /** + * Sets the current download info + * @param the MagnatuneDownloadInfo class containing the information abut the + * download to display + */ + void setDownloadInfo( MagnatuneDownloadInfo * info ); + +signals: + + /** + * Signal emitted when all needed info has been gathered and handler + * should start album download. + * @param completedInfo A DownloadInfo object containing all needed information + */ + void downloadAlbum(MagnatuneDownloadInfo * completedInfo); + +public slots: + /*$PUBLIC_SLOTS$*/ + +protected: + /*$PROTECTED_FUNCTIONS$*/ + MagnatuneDownloadInfo * m_currentDownloadInfo; + +protected slots: + /*$PROTECTED_SLOTS$*/ + /** + * Slot for recieving notification when the download button is clicked. + */ + void downloadButtonClicked(); + +}; + +#endif + diff --git a/amarok/src/magnatunebrowser/magnatunedownloaddialogbase.ui b/amarok/src/magnatunebrowser/magnatunedownloaddialogbase.ui new file mode 100644 index 00000000..3a24d929 --- /dev/null +++ b/amarok/src/magnatunebrowser/magnatunedownloaddialogbase.ui @@ -0,0 +1,120 @@ +<!DOCTYPE UI><UI version="3.3" stdsetdef="1"> +<class>MagnatuneDownloadDialogBase</class> +<widget class="QDialog"> + <property name="name"> + <cstring>MagnatuneDownloadDialogBase</cstring> + </property> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>526</width> + <height>568</height> + </rect> + </property> + <property name="caption"> + <string>Magnatune.com Album Download</string> + </property> + <grid> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QPushButton" row="2" column="0"> + <property name="name"> + <cstring>downloadButton</cstring> + </property> + <property name="text"> + <string>&Download</string> + </property> + </widget> + <widget class="QGroupBox" row="1" column="0"> + <property name="name"> + <cstring>groupBox2</cstring> + </property> + <property name="title"> + <string>Magnatune info</string> + </property> + <grid> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QTextEdit" row="0" column="0"> + <property name="name"> + <cstring>infoEdit</cstring> + </property> + <property name="hScrollBarMode"> + <enum>AlwaysOff</enum> + </property> + </widget> + </grid> + </widget> + <widget class="QGroupBox" row="0" column="0"> + <property name="name"> + <cstring>groupBox1</cstring> + </property> + <property name="title"> + <string>Download options</string> + </property> + <grid> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QComboBox" row="0" column="1"> + <property name="name"> + <cstring>formatComboBox</cstring> + </property> + </widget> + <widget class="KURLRequester" row="1" column="1"> + <property name="name"> + <cstring>downloadTargetURLRequester</cstring> + </property> + </widget> + <widget class="QLabel" row="0" column="0"> + <property name="name"> + <cstring>textLabel1</cstring> + </property> + <property name="text"> + <string>Select Format:</string> + </property> + </widget> + <widget class="QLabel" row="1" column="0"> + <property name="name"> + <cstring>textLabel2</cstring> + </property> + <property name="text"> + <string>Download to:</string> + </property> + </widget> + <widget class="QLabel" row="2" column="0" rowspan="1" colspan="2"> + <property name="name"> + <cstring>textLabel1_2</cstring> + </property> + <property name="text"> + <string>If you download to a location that is already being monitored by Amarok, the album will automatically be added to your collection.</string> + </property> + <property name="alignment"> + <set>WordBreak|AlignVCenter</set> + </property> + </widget> + </grid> + </widget> + </grid> +</widget> +<connections> + <connection> + <sender>downloadButton</sender> + <signal>clicked()</signal> + <receiver>MagnatuneDownloadDialogBase</receiver> + <slot>downloadButtonClicked()</slot> + </connection> +</connections> +<slots> + <slot access="protected">downloadButtonClicked()</slot> +</slots> +<layoutdefaults spacing="6" margin="11"/> +<includehints> + <includehint>kurlrequester.h</includehint> + <includehint>klineedit.h</includehint> + <includehint>kpushbutton.h</includehint> +</includehints> +</UI> diff --git a/amarok/src/magnatunebrowser/magnatunedownloadinfo.cpp b/amarok/src/magnatunebrowser/magnatunedownloadinfo.cpp new file mode 100644 index 00000000..7671ce27 --- /dev/null +++ b/amarok/src/magnatunebrowser/magnatunedownloadinfo.cpp @@ -0,0 +1,264 @@ +/* + Copyright (c) 2006 Nikolaj Hald Nielsen <nhnFreespirit@gmail.com> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + + +#include "magnatunedownloadinfo.h" + + +#include "debug.h" + +#include <qfile.h> + +MagnatuneDownloadInfo::MagnatuneDownloadInfo() +{ + m_selectedDownloadFormat = QString::null; + m_albumId = -1; +} + + +MagnatuneDownloadInfo::~MagnatuneDownloadInfo() +{} + +bool MagnatuneDownloadInfo::initFromFile( QString downloadInfoFileName ) +{ + QString xml; + + QFile file( downloadInfoFileName ); + if ( file.open( IO_ReadOnly ) ) + { + QTextStream stream( &file ); + while ( !stream.atEnd() ) + { + xml += (stream.readLine() + '\n'); + } + file.close(); + } else { + debug() << "Error opening file: '" << downloadInfoFileName << "'" << endl; + return false; + } + + + //debug() << "XML from file: '" << xml << "'" << endl; + return initFromString( xml ); +} + +bool MagnatuneDownloadInfo::initFromString( QString downloadInfoString ) +{ + + //complete overkill to do a full SAX2 parser for this at the moment... I think.... + + // lets make sure that this is actually a valid result + + int testIndex = downloadInfoString.find( "<RESULT>" ); + if ( testIndex == -1 ) + { + return false; + }; + + int startIndex; + int endIndex; + + startIndex = downloadInfoString.find( "<DL_USERNAME>", 0, false ); + if ( startIndex != -1 ) + { + endIndex = downloadInfoString.find( "</DL_USERNAME>", 0, false ); + if ( endIndex != -1 ) + { + startIndex += 13; + + debug() << "found username: " << downloadInfoString.mid( startIndex, endIndex - startIndex ) << endl; + m_userName = downloadInfoString.mid( startIndex, endIndex - startIndex ); + } + else + { + return false; + } + } + else + { + return false; + } + + + startIndex = downloadInfoString.find( "<DL_PASSWORD>", 0, false ); + if ( startIndex != -1 ) + { + endIndex = downloadInfoString.find( "</DL_PASSWORD>", 0, false ); + if ( endIndex != -1 ) + { + startIndex += 13; + debug() << "found password: " << downloadInfoString.mid( startIndex, endIndex - startIndex ) << endl; + m_password = downloadInfoString.mid( startIndex, endIndex - startIndex ); + } + else + { + return false; + } + } + else + { + return false; + } + + + startIndex = downloadInfoString.find( "<URL_WAVZIP>", 0, false ); + if ( startIndex != -1 ) + { + endIndex = downloadInfoString.find( "</URL_WAVZIP>", 0, false ); + if ( endIndex != -1 ) + { + startIndex += 12; + debug() << "found wav" << endl; + m_downloadFormats[ "Wav" ] = downloadInfoString.mid( startIndex, endIndex - startIndex ); + + } + } + + startIndex = downloadInfoString.find( "<URL_128KMP3ZIP>", 0, false ); + if ( startIndex != -1 ) + { + endIndex = downloadInfoString.find( "</URL_128KMP3ZIP>", 0, false ); + if ( endIndex != -1 ) + { + startIndex += 16; + debug() << "found 128k mp3" << endl; + m_downloadFormats[ "128 kbit/s MP3" ] = downloadInfoString.mid( startIndex, endIndex - startIndex ); + + } + } + + startIndex = downloadInfoString.find( "<URL_OGGZIP>", 0, false ); + if ( startIndex != -1 ) + { + endIndex = downloadInfoString.find( "</URL_OGGZIP>", 0, false ); + if ( endIndex != -1 ) + { + startIndex += 12; + debug() << "found ogg-vorbis" << endl; + m_downloadFormats[ "Ogg-Vorbis" ] = downloadInfoString.mid( startIndex, endIndex - startIndex ); + + } + } + + startIndex = downloadInfoString.find( "<URL_VBRZIP>", 0, false ); + if ( startIndex != -1 ) + { + endIndex = downloadInfoString.find( "</URL_VBRZIP>", 0, false ); + if ( endIndex != -1 ) + { + startIndex += 12; + debug() << "found vbr mp3" << endl; + m_downloadFormats[ "VBR MP3" ] = downloadInfoString.mid( startIndex, endIndex - startIndex ); + + } + } + + startIndex = downloadInfoString.find( "<URL_FLACZIP>", 0, false ); + if ( startIndex != -1 ) + { + endIndex = downloadInfoString.find( "</URL_FLACZIP>", 0, false ); + if ( endIndex != -1 ) + { + startIndex += 13; + debug() << "found flac" << endl; + m_downloadFormats[ "FLAC" ] = downloadInfoString.mid( startIndex, endIndex - startIndex ); + + } + } + + startIndex = downloadInfoString.find( "<DL_MSG>", 0, false ); + if ( startIndex != -1 ) + { + endIndex = downloadInfoString.find( "</DL_MSG>", 0, false ); + if ( endIndex != -1 ) + { + startIndex += 9; + debug() << "found dl-message" << endl; + m_downloadMessage = downloadInfoString.mid( startIndex, endIndex - startIndex ); + } + } + + return true; +} + +QString MagnatuneDownloadInfo::getUserName( ) +{ + return m_userName; +} + +QString MagnatuneDownloadInfo::getPassword( ) +{ + return m_password; +} + +QString MagnatuneDownloadInfo::getDownloadMessage( ) +{ + return m_downloadMessage; +} + +DownloadFormatMap MagnatuneDownloadInfo::getFormatMap() +{ + return m_downloadFormats; +} + +void MagnatuneDownloadInfo::setFormatSelection( QString selectedFormat ) +{ + m_selectedDownloadFormat = selectedFormat; +} + +bool MagnatuneDownloadInfo::isReadyForDownload( ) +{ + return !m_selectedDownloadFormat.isEmpty(); +} + +KURL MagnatuneDownloadInfo::getCompleteDownloadUrl( ) +{ + QString url = m_downloadFormats[ m_selectedDownloadFormat ]; + KURL downloadUrl(url); + downloadUrl.setUser(m_userName); + downloadUrl.setPass(m_password); + + return downloadUrl; +} + +void MagnatuneDownloadInfo::setUnpackUrl( QString unpackUrl ) +{ + m_unpackUrl = unpackUrl; +} + +QString MagnatuneDownloadInfo::getUnpackLocation( ) +{ + return m_unpackUrl; +} + + + +int MagnatuneDownloadInfo::getAlbumId() +{ + return m_albumId; +} + + + +void MagnatuneDownloadInfo::setAlbumId(int id) +{ + m_albumId = id; +} + + diff --git a/amarok/src/magnatunebrowser/magnatunedownloadinfo.h b/amarok/src/magnatunebrowser/magnatunedownloadinfo.h new file mode 100644 index 00000000..3e22d709 --- /dev/null +++ b/amarok/src/magnatunebrowser/magnatunedownloadinfo.h @@ -0,0 +1,76 @@ +/* + Copyright (c) 2006 Nikolaj Hald Nielsen <nhnFreespirit@gmail.com> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + + +#ifndef MAGNATUNE_DOWNLOAD_INFO_H +#define MAGNATUNE_DOWNLOAD_INFO_H + +#include <kurl.h> + +#include <qmap.h> +#include <qstring.h> + + +typedef QMap<QString, QString> DownloadFormatMap; + +/** +Class for parsing and storing the info from a download xml file or string + + @author Nikolaj Hald Nielsen +*/ +class MagnatuneDownloadInfo{ +public: + MagnatuneDownloadInfo(); + ~MagnatuneDownloadInfo(); + + bool initFromString( QString downloadInfoString ); + bool initFromFile( QString downloadInfoFileName ); + + DownloadFormatMap getFormatMap(); + QString getUserName(); + QString getPassword(); + QString getDownloadMessage(); + int getAlbumId(); + + + void setFormatSelection(QString selectedFormat); + void setUnpackUrl(QString unpackUrl); + void setAlbumId(int id); + bool isReadyForDownload(); + KURL getCompleteDownloadUrl(); + QString getUnpackLocation(); + + + +protected: + + DownloadFormatMap m_downloadFormats; + QString m_userName; + QString m_password; + QString m_downloadMessage; + + int m_albumId; + + //the following members are for storing the user selections regarding a download + QString m_unpackUrl; + QString m_selectedDownloadFormat; + +}; + +#endif diff --git a/amarok/src/magnatunebrowser/magnatunelistview.cpp b/amarok/src/magnatunebrowser/magnatunelistview.cpp new file mode 100644 index 00000000..cf4bef3c --- /dev/null +++ b/amarok/src/magnatunebrowser/magnatunelistview.cpp @@ -0,0 +1,91 @@ +/* + Copyright (c) 2006 Nikolaj Hald Nielsen <nhnFreespirit@gmail.com> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + + +#include "magnatunedatabasehandler.h" +#include "magnatunelistview.h" + +#include <kdeversion.h> +#include <klocale.h> + +#include <qcolor.h> + +MagnatuneListView::MagnatuneListView( QWidget * parent ) + : KListView( parent ) +{ + + setRootIsDecorated( true ); + addColumn( i18n( "Artist/Album/Track" ) ); + addColumn( i18n( "Duration" ) ); + + setColumnWidthMode( 0, QListView::Maximum ); + setResizeMode( QListView::LastColumn ); + + setShowSortIndicator ( true ); + + #if KDE_VERSION >= KDE_MAKE_VERSION(3,4,0) + setShadeSortColumn( false ); + #endif + +} + +MagnatuneListView::~MagnatuneListView() +{} + +KURLDrag * MagnatuneListView::dragObject( ) +{ + KURL::List urls; + int id; + MagnatuneTrackList tracks; + MagnatuneTrackList::iterator it; + + KListViewItem * pSelectedItem = dynamic_cast<KListViewItem *>( selectedItem() ); + if (!pSelectedItem) { + debug() << "dynamic_cast to pSelectedItem failed!" << endl; + return 0; + } + + switch ( pSelectedItem->depth() ) + { + case 0: + id = ( ( MagnatuneListViewTrackItem * ) pSelectedItem ) ->getId(); + tracks = MagnatuneDatabaseHandler::instance() ->getTracksByArtistId( id ); + for ( it = tracks.begin(); it != tracks.end(); ++it ) + { + urls.append( ( *it ).getHifiURL() ); + } + break; + case 1: + id = ( ( MagnatuneListViewTrackItem * ) pSelectedItem ) ->getId(); + tracks = MagnatuneDatabaseHandler::instance() ->getTracksByAlbumId( id ); + for ( it = tracks.begin(); it != tracks.end(); ++it ) + { + urls.append( ( *it ).getHifiURL() ); + } + break; + case 2: + urls.append( ( ( MagnatuneListViewTrackItem * ) pSelectedItem ) ->getHifiURL( ) ); + break; + } + + KURLDrag* d = new KURLDrag( urls, this ); + return d; +} + + diff --git a/amarok/src/magnatunebrowser/magnatunelistview.h b/amarok/src/magnatunebrowser/magnatunelistview.h new file mode 100644 index 00000000..cb716215 --- /dev/null +++ b/amarok/src/magnatunebrowser/magnatunelistview.h @@ -0,0 +1,47 @@ +/* + Copyright (c) 2006 Nikolaj Hald Nielsen <nhnFreespirit@gmail.com> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef MAGNATUNELISTVIEW_H +#define MAGNATUNELISTVIEW_H + +#include "magnatunelistviewitems.h" + +#include <klistview.h> +#include <kurldrag.h> + +/** +A specialized KListView that provides drag and drop functionality + + @author Nikolaj Hald Nielsen <nhnFreespirit@gmail.com> + +*/ +class MagnatuneListView : public KListView +{ +public: + MagnatuneListView(QWidget * parent); + + ~MagnatuneListView(); + +protected: + + KURLDrag * dragObject(); + +}; + +#endif diff --git a/amarok/src/magnatunebrowser/magnatunelistviewitems.cpp b/amarok/src/magnatunebrowser/magnatunelistviewitems.cpp new file mode 100644 index 00000000..48992884 --- /dev/null +++ b/amarok/src/magnatunebrowser/magnatunelistviewitems.cpp @@ -0,0 +1,180 @@ +/* + Copyright (c) 2006 Nikolaj Hald Nielsen <nhnFreespirit@gmail.com> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "debug.h" +#include "magnatunelistviewitems.h" + +#include <kglobal.h> +#include <kiconloader.h> +#include <klocale.h> + +#include <qdatetime.h> + + + + +MagnatuneListViewArtistItem::MagnatuneListViewArtistItem( MagnatuneArtist artist, KListView * parent ) +: KListViewItem( parent ), MagnatuneArtist( artist ) +{ + KListViewItem::setText( 0, artist.getName() ); + + setPixmap(0, KGlobal::iconLoader()->loadIcon( "personal", KIcon::Toolbar, KIcon::SizeSmall ) ); + + setDragEnabled ( true ); +} + +MagnatuneListViewArtistItem::~ MagnatuneListViewArtistItem( ) +{ +} + +void MagnatuneListViewArtistItem::setOpen( bool o ) +{ + + if ( o && !childCount() ) { + listView()->setUpdatesEnabled( false ); + + MagnatuneAlbumList albums; + albums = MagnatuneDatabaseHandler::instance()->getAlbumsByArtistId( getId(), "" ); + + MagnatuneAlbumList::iterator it; + for ( it = albums.begin(); it != albums.end(); ++it ) { + new MagnatuneListViewAlbumItem( (*it), this ); + } + } + listView()->setUpdatesEnabled( true ); + KListViewItem::setOpen( o ); + invalidateHeight(); + listView()->repaintContents(); + + +} + + +void MagnatuneListViewArtistItem::setup() +{ + setExpandable( true ); + KListViewItem::setup(); +} + + + + + + + + + + + + + +MagnatuneListViewAlbumItem::MagnatuneListViewAlbumItem( MagnatuneAlbum album, KListViewItem * parent ) +: KListViewItem( parent ), MagnatuneAlbum( album ) +{ + KListViewItem::setText( 0, album.getName() ); + setDragEnabled( true ); + + //setPixmap(0, KGlobal::iconLoader()->loadIcon( "cdrom_unmount", KIcon::Toolbar, KIcon::SizeSmall ) ); + + +} + +MagnatuneListViewAlbumItem::~ MagnatuneListViewAlbumItem( ) +{ +} + + +void MagnatuneListViewAlbumItem::setOpen( bool o ) +{ + + if ( o && !childCount() ) { + listView()->setUpdatesEnabled( false ); + + MagnatuneTrackList tracks; + tracks = MagnatuneDatabaseHandler::instance()->getTracksByAlbumId( getId() ); + + MagnatuneTrackList::iterator it; + for ( it = tracks.begin(); it != tracks.end(); ++it ) { + new MagnatuneListViewTrackItem( (*it), this ); + } + } + + listView()->setUpdatesEnabled( true ); + KListViewItem::setOpen( o ); + invalidateHeight(); + listView()->repaintContents(); + + +} + +void MagnatuneListViewAlbumItem::setup( ) +{ + setExpandable( true ); + KListViewItem::setup(); +} + + + + + + + + + + + + + +MagnatuneListViewTrackItem::MagnatuneListViewTrackItem( MagnatuneTrack track, KListViewItem * parent ) +: KListViewItem( parent ), MagnatuneTrack( track ) +{ + + int trackNumber = track.getTrackNumber(); + QString trackNumberString = QString::number( trackNumber ); + if (trackNumber < 10) + trackNumberString = '0' + trackNumberString; + + + + KListViewItem::setText( 0, trackNumberString + " - " + track.getName() ); + + debug() << "track duration: " << QString::number( track.getDuration() ) << endl; + + QTime duration; + duration = duration.addSecs(track.getDuration()); + + if (duration.hour() == 0) + KListViewItem::setText( 1, duration.toString( "m:ss" ) ); + else + KListViewItem::setText( 1, duration.toString( "h:mm:ss" ) ); + + setDragEnabled( true ); + + //setPixmap(0, KGlobal::iconLoader()->loadIcon( "track", KIcon::Toolbar, KIcon::SizeSmall ) ); +} + +MagnatuneListViewTrackItem::~ MagnatuneListViewTrackItem( ) +{ +} + + + + + + diff --git a/amarok/src/magnatunebrowser/magnatunelistviewitems.h b/amarok/src/magnatunebrowser/magnatunelistviewitems.h new file mode 100644 index 00000000..573acb67 --- /dev/null +++ b/amarok/src/magnatunebrowser/magnatunelistviewitems.h @@ -0,0 +1,81 @@ +/* + Copyright (c) 2006 Nikolaj Hald Nielsen <nhnFreespirit@gmail.com> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + + +#ifndef MAGNATUNELISTVIEWITEMS_H +#define MAGNATUNELISTVIEWITEMS_H + +#include "magnatunedatabasehandler.h" +#include "magnatunetypes.h" + +#include <klistview.h> + +/** +A specialized KListViewItem that encapsulates a MagnatuneArtist + +@author Nikolaj Hald Nielsen <nhnFreespirit@gmail.com> +*/ + + +class MagnatuneListViewArtistItem : public KListViewItem, public MagnatuneArtist +{ +public: + MagnatuneListViewArtistItem(MagnatuneArtist artist, KListView * parent); + + ~MagnatuneListViewArtistItem(); + + void setOpen( bool o ); + void setup(); + +}; + + +/** +A specialized KListViewItem that encapsulates a MagnatuneAlbum + +@author Nikolaj Hald Nielsen <nhnFreespirit@gmail.com> + */ +class MagnatuneListViewAlbumItem : public KListViewItem, public MagnatuneAlbum +{ +public: + + MagnatuneListViewAlbumItem(MagnatuneAlbum album, KListViewItem * parent); + ~MagnatuneListViewAlbumItem(); + + void setOpen( bool o ); + void setup(); + +}; + + +/** +A specialized KListViewItem that encapsulates a MagnatuneTrack + +@author Nikolaj Hald Nielsen <nhnFreespirit@gmail.com> + */ +class MagnatuneListViewTrackItem : public KListViewItem, public MagnatuneTrack +{ +public: + + MagnatuneListViewTrackItem(MagnatuneTrack track, KListViewItem * parent); + ~MagnatuneListViewTrackItem(); + +}; + +#endif diff --git a/amarok/src/magnatunebrowser/magnatunepurchasedialog.cpp b/amarok/src/magnatunebrowser/magnatunepurchasedialog.cpp new file mode 100644 index 00000000..440f405e --- /dev/null +++ b/amarok/src/magnatunebrowser/magnatunepurchasedialog.cpp @@ -0,0 +1,154 @@ +/* + Copyright (c) 2006 Nikolaj Hald Nielsen <nhnFreespirit@gmail.com> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "debug.h" +#include "magnatunedatabasehandler.h" +#include "magnatunepurchasedialog.h" + +#include <qcombobox.h> +#include <qlineedit.h> +#include <qmessagebox.h> +#include <qpushbutton.h> +#include <qregexp.h> +#include <qlabel.h> + + +MagnatunePurchaseDialog::MagnatunePurchaseDialog( QWidget* parent, const char* name, bool modal, WFlags fl ) + : magnatunePurchaseDialogBase( parent, name, modal, fl ) +{} + +MagnatunePurchaseDialog::~MagnatunePurchaseDialog() +{} + +void MagnatunePurchaseDialog::setAlbum( const MagnatuneAlbum &album ) +{ + + //albumEdit->setText("Hello!"); + albumEdit->setText( album.getName() ); + + MagnatuneArtist artist = MagnatuneDatabaseHandler::instance()->getArtistById( album.getArtistId() ); + artistEdit->setText( artist.getName() ); + genresEdit->setText( album.getMp3Genre() ); + launchDateEdit->setText( QString::number( album.getLaunchDate().year() ) ); + + m_albumCode = album.getAlbumCode(); + +} + +void MagnatunePurchaseDialog::purchase( ) +{ + + if ( verifyEntries( ) ) + { + + setEnabled( false ); //to prevent accidental double purchases + emit( makePurchase( ccEdit->text(), expYearEdit->text(), expMonthEdit->text(), nameEdit->text(), emailEdit->text(), m_albumCode, amountComboBox->currentText().toInt() ) ); + + //close(); + //hide(); + + } +} + +void MagnatunePurchaseDialog::reject( ) +{ + cancel(); +} + + +void MagnatunePurchaseDialog::cancel( ) +{ + hide(); + emit ( cancelled() ); + +} + +bool MagnatunePurchaseDialog::verifyEntries( ) +{ + + // check all the entries for validity + + //cc number: + QString ccString = ccEdit->text(); + ccString.stripWhiteSpace (); + QRegExp ccExp( "^[\\d]{10,20}$" ); + + if ( !ccExp.exactMatch( ccString ) ) + { + QMessageBox::information( this, "Invalid credit card number", + "The credit card number entered does not appear to be valid\n" ); + return false; + } + + //email + QString emailString = emailEdit->text(); + emailString.stripWhiteSpace (); + QRegExp emailExp( "^\\S+@\\S+\\.\\S+$" ); + + if ( !emailExp.exactMatch( emailString ) ) + { + QMessageBox::information( this, "Invalid email", + "The email address entered does not appear to be valid\n" ); + return false; + } + + //month + QString monthString = expMonthEdit->text(); + monthString.stripWhiteSpace (); + QRegExp monthExp( "^\\d{2}$" ); + + if ( !monthExp.exactMatch( monthString ) ) + { + QMessageBox::information( this, "Invalid expiration month", + "The credit card expiration month does not appear to be valid\n" ); + return false; + } + + //month + QString yearString = expYearEdit->text(); + yearString.stripWhiteSpace (); + QRegExp yearExp( "^\\d{2}$" ); + + if ( !yearExp.exactMatch( yearString ) ) + { + QMessageBox::information( this, "Invalid expiration month", + "The credit card expiration year does not appear to be valid\n" ); + return false; + } + + + + + return true; + +} + + +void MagnatunePurchaseDialog::setCover( QString coverFile ) +{ + coverPixmapLabel->setPixmap( QPixmap( coverFile ) ); +} + + + +/*$SPECIALIZATION$*/ + + +#include "magnatunepurchasedialog.moc" + diff --git a/amarok/src/magnatunebrowser/magnatunepurchasedialog.h b/amarok/src/magnatunebrowser/magnatunepurchasedialog.h new file mode 100644 index 00000000..d87941c3 --- /dev/null +++ b/amarok/src/magnatunebrowser/magnatunepurchasedialog.h @@ -0,0 +1,116 @@ +/* + Copyright (c) 2006 Nikolaj Hald Nielsen <nhnFreespirit@gmail.com> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef MAGNATUNEPURCHASEDIALOG_H +#define MAGNATUNEPURCHASEDIALOG_H + +#include "magnatunepurchasedialogbase.h" +#include "magnatunetypes.h" + +class MagnatunePurchaseDialog : public magnatunePurchaseDialogBase +{ + Q_OBJECT + +public: + + /** + * Overridden constructor. + * @param parent Pointer to the parent QWidget. + * @param name Name of this widget. + * @param modal Sets modal state. + * @param fl Additional dialog flags. + */ + MagnatunePurchaseDialog( QWidget* parent = 0, const char* name = 0, bool modal = false, WFlags fl = 0 ); + + /** + * Destructor + */ + ~MagnatunePurchaseDialog(); + /*$PUBLIC_FUNCTIONS$*/ + + + /** + * Sets the album to process. + * @param album The album to process. + */ + void setAlbum( const MagnatuneAlbum& album ); + + /** + * Loads image into the cover label. + * @param coverFile image file to load. + */ + void setCover( QString coverFile ); + + +signals: + + /** + * Signal emitted when all needed info has been gathered and verified. + * @param ccNumber The credit card number. + * @param expYear The credit card expiration year. + * @param expMonth The credit card expiration month. + * @param name Name of customer. + * @param email Email of customer. Used to send verification email. Can also be used. + * on the Magnatune.com site to re-download any previous purchases. + * @param albumCode The album code of the album. + * @param amount The amount to pay (in us $) + */ + void makePurchase( QString ccNumber, QString expYear, QString expMonth, QString name, QString email, QString albumCode, int amount ); + + /** + * Signal emitted if purchase operation is cancelled + */ + void cancelled(); + +public slots: + /*$PUBLIC_SLOTS$*/ + +private: + /*$PRIVATE_FUNCTIONS$*/ + + QString m_albumCode; + + /** + * Helper function to verify that all entries are valid. + * @return Returns true if all entries are valid and false otherwise. + */ + bool verifyEntries(); + +protected slots: + /*$PROTECTED_SLOTS$*/ + + /** + * Slot for recieving notification when the purchase button is clicked. + */ + void purchase(); + + /** + * Slot for recieving notification when the cancel button is pressed. + */ + void cancel(); + + /** + * Slot called when the dialog is closed without pressing cancel. + */ + void reject (); + +}; + +#endif + diff --git a/amarok/src/magnatunebrowser/magnatunepurchasedialogbase.ui b/amarok/src/magnatunebrowser/magnatunepurchasedialogbase.ui new file mode 100644 index 00000000..467d6f4b --- /dev/null +++ b/amarok/src/magnatunebrowser/magnatunepurchasedialogbase.ui @@ -0,0 +1,706 @@ +<!DOCTYPE UI><UI version="3.3" stdsetdef="1"> +<class>magnatunePurchaseDialogBase</class> +<widget class="QDialog"> + <property name="name"> + <cstring>magnatunePurchaseDialogBase</cstring> + </property> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>686</width> + <height>602</height> + </rect> + </property> + <property name="caption"> + <string>Purchase Album from Magnatune.com</string> + </property> + <grid> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <property name="resizeMode"> + <enum>Minimum</enum> + </property> + <widget class="QGroupBox" row="0" column="0"> + <property name="name"> + <cstring>groupBox1</cstring> + </property> + <property name="title"> + <string>Info</string> + </property> + <grid> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLabel" row="0" column="2" rowspan="5" colspan="1"> + <property name="name"> + <cstring>coverPixmapLabel</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>0</hsizetype> + <vsizetype>0</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>200</width> + <height>201</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>200</width> + <height>201</height> + </size> + </property> + <property name="pixmap"> + <pixmap>image0</pixmap> + </property> + <property name="scaledContents"> + <bool>true</bool> + </property> + </widget> + <widget class="QLabel" row="0" column="0" rowspan="1" colspan="2"> + <property name="name"> + <cstring>infoLabel</cstring> + </property> + <property name="text"> + <string>You have chosen to purchase the following album from Magnatune.com</string> + </property> + <property name="alignment"> + <set>WordBreak|AlignVCenter</set> + </property> + </widget> + <widget class="QLineEdit" row="1" column="1"> + <property name="name"> + <cstring>albumEdit</cstring> + </property> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="readOnly"> + <bool>true</bool> + </property> + </widget> + <widget class="QLabel" row="1" column="0"> + <property name="name"> + <cstring>textLabel13</cstring> + </property> + <property name="text"> + <string>Album:</string> + </property> + </widget> + <widget class="QLineEdit" row="2" column="1"> + <property name="name"> + <cstring>artistEdit</cstring> + </property> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="readOnly"> + <bool>true</bool> + </property> + </widget> + <widget class="QLabel" row="2" column="0"> + <property name="name"> + <cstring>textLabel14</cstring> + </property> + <property name="text"> + <string>Artist:</string> + </property> + </widget> + <widget class="QLineEdit" row="3" column="1"> + <property name="name"> + <cstring>genresEdit</cstring> + </property> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="readOnly"> + <bool>true</bool> + </property> + </widget> + <widget class="QLineEdit" row="4" column="1"> + <property name="name"> + <cstring>launchDateEdit</cstring> + </property> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="readOnly"> + <bool>true</bool> + </property> + </widget> + <widget class="QLabel" row="3" column="0"> + <property name="name"> + <cstring>textLabel15</cstring> + </property> + <property name="text"> + <string>Genre:</string> + </property> + </widget> + <widget class="QLabel" row="4" column="0"> + <property name="name"> + <cstring>textLabel16</cstring> + </property> + <property name="text"> + <string>Launch Year:</string> + </property> + </widget> + </grid> + </widget> + <widget class="QLayoutWidget" row="3" column="0"> + <property name="name"> + <cstring>layout2</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <property name="margin"> + <number>0</number> + </property> + <property name="spacing"> + <number>5</number> + </property> + <spacer> + <property name="name"> + <cstring>spacer3</cstring> + </property> + <property name="orientation"> + <enum>Horizontal</enum> + </property> + <property name="sizeType"> + <enum>Expanding</enum> + </property> + <property name="sizeHint"> + <size> + <width>41</width> + <height>21</height> + </size> + </property> + </spacer> + <widget class="QPushButton"> + <property name="name"> + <cstring>purchaseButton</cstring> + </property> + <property name="text"> + <string>P&urchase</string> + </property> + </widget> + <widget class="QPushButton"> + <property name="name"> + <cstring>cancelButton</cstring> + </property> + <property name="text"> + <string>Ca&ncel</string> + </property> + </widget> + </hbox> + </widget> + <widget class="QGroupBox" row="1" column="0"> + <property name="name"> + <cstring>groupBox2</cstring> + </property> + <property name="title"> + <string>Payment</string> + </property> + <grid> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLabel" row="0" column="0" rowspan="1" colspan="5"> + <property name="name"> + <cstring>textLabel2</cstring> + </property> + <property name="palette"> + <palette> + <active> + <color> + <red>0</red> + <green>0</green> + <blue>0</blue> + </color> + <color> + <red>221</red> + <green>223</green> + <blue>228</blue> + </color> + <color> + <red>255</red> + <green>255</green> + <blue>255</blue> + </color> + <color> + <red>255</red> + <green>255</green> + <blue>255</blue> + </color> + <color> + <red>85</red> + <green>85</green> + <blue>85</blue> + </color> + <color> + <red>199</red> + <green>199</green> + <blue>199</blue> + </color> + <color> + <red>0</red> + <green>0</green> + <blue>0</blue> + </color> + <color> + <red>255</red> + <green>255</green> + <blue>255</blue> + </color> + <color> + <red>0</red> + <green>0</green> + <blue>0</blue> + </color> + <color> + <red>255</red> + <green>255</green> + <blue>255</blue> + </color> + <color> + <red>239</red> + <green>239</green> + <blue>239</blue> + </color> + <color> + <red>0</red> + <green>0</green> + <blue>0</blue> + </color> + <color> + <red>103</red> + <green>141</green> + <blue>178</blue> + </color> + <color> + <red>255</red> + <green>255</green> + <blue>255</blue> + </color> + <color> + <red>0</red> + <green>0</green> + <blue>238</blue> + </color> + <color> + <red>82</red> + <green>24</green> + <blue>139</blue> + </color> + </active> + <disabled> + <color> + <red>128</red> + <green>128</green> + <blue>128</blue> + </color> + <color> + <red>221</red> + <green>223</green> + <blue>228</blue> + </color> + <color> + <red>255</red> + <green>255</green> + <blue>255</blue> + </color> + <color> + <red>255</red> + <green>255</green> + <blue>255</blue> + </color> + <color> + <red>85</red> + <green>85</green> + <blue>85</blue> + </color> + <color> + <red>199</red> + <green>199</green> + <blue>199</blue> + </color> + <color> + <red>199</red> + <green>199</green> + <blue>199</blue> + </color> + <color> + <red>255</red> + <green>255</green> + <blue>255</blue> + </color> + <color> + <red>128</red> + <green>128</green> + <blue>128</blue> + </color> + <color> + <red>255</red> + <green>255</green> + <blue>255</blue> + </color> + <color> + <red>239</red> + <green>239</green> + <blue>239</blue> + </color> + <color> + <red>0</red> + <green>0</green> + <blue>0</blue> + </color> + <color> + <red>86</red> + <green>117</green> + <blue>148</blue> + </color> + <color> + <red>255</red> + <green>255</green> + <blue>255</blue> + </color> + <color> + <red>0</red> + <green>0</green> + <blue>238</blue> + </color> + <color> + <red>82</red> + <green>24</green> + <blue>139</blue> + </color> + </disabled> + <inactive> + <color> + <red>0</red> + <green>0</green> + <blue>0</blue> + </color> + <color> + <red>221</red> + <green>223</green> + <blue>228</blue> + </color> + <color> + <red>255</red> + <green>255</green> + <blue>255</blue> + </color> + <color> + <red>255</red> + <green>255</green> + <blue>255</blue> + </color> + <color> + <red>85</red> + <green>85</green> + <blue>85</blue> + </color> + <color> + <red>199</red> + <green>199</green> + <blue>199</blue> + </color> + <color> + <red>0</red> + <green>0</green> + <blue>0</blue> + </color> + <color> + <red>255</red> + <green>255</green> + <blue>255</blue> + </color> + <color> + <red>0</red> + <green>0</green> + <blue>0</blue> + </color> + <color> + <red>255</red> + <green>255</green> + <blue>255</blue> + </color> + <color> + <red>239</red> + <green>239</green> + <blue>239</blue> + </color> + <color> + <red>0</red> + <green>0</green> + <blue>0</blue> + </color> + <color> + <red>103</red> + <green>141</green> + <blue>178</blue> + </color> + <color> + <red>255</red> + <green>255</green> + <blue>255</blue> + </color> + <color> + <red>0</red> + <green>0</green> + <blue>238</blue> + </color> + <color> + <red>82</red> + <green>24</green> + <blue>139</blue> + </color> + </inactive> + </palette> + </property> + <property name="text"> + <string>VISA and Mastercard accepted.</string> + </property> + </widget> + <widget class="QLabel" row="4" column="0"> + <property name="name"> + <cstring>textLabel4</cstring> + </property> + <property name="text"> + <string>Expiration date:</string> + </property> + </widget> + <widget class="QLabel" row="5" column="0"> + <property name="name"> + <cstring>textLabel7</cstring> + </property> + <property name="text"> + <string>Amount to pay (USD):</string> + </property> + </widget> + <widget class="QLabel" row="1" column="0"> + <property name="name"> + <cstring>textLabel8</cstring> + </property> + <property name="text"> + <string>Name:</string> + </property> + </widget> + <widget class="QLabel" row="2" column="0"> + <property name="name"> + <cstring>textLabel9</cstring> + </property> + <property name="text"> + <string>Email:</string> + </property> + </widget> + <widget class="QLabel" row="3" column="0"> + <property name="name"> + <cstring>textLabel3</cstring> + </property> + <property name="text"> + <string>Credit card number:</string> + </property> + </widget> + <widget class="QLineEdit" row="3" column="1" rowspan="1" colspan="4"> + <property name="name"> + <cstring>ccEdit</cstring> + </property> + </widget> + <widget class="QLineEdit" row="1" column="1" rowspan="1" colspan="4"> + <property name="name"> + <cstring>nameEdit</cstring> + </property> + </widget> + <widget class="QLineEdit" row="2" column="1" rowspan="1" colspan="4"> + <property name="name"> + <cstring>emailEdit</cstring> + </property> + </widget> + <widget class="QComboBox" row="5" column="1" rowspan="1" colspan="2"> + <item> + <property name="text"> + <string>5</string> + </property> + </item> + <item> + <property name="text"> + <string>6</string> + </property> + </item> + <item> + <property name="text"> + <string>7</string> + </property> + </item> + <item> + <property name="text"> + <string>8</string> + </property> + </item> + <item> + <property name="text"> + <string>9</string> + </property> + </item> + <item> + <property name="text"> + <string>10</string> + </property> + </item> + <item> + <property name="text"> + <string>11</string> + </property> + </item> + <item> + <property name="text"> + <string>12</string> + </property> + </item> + <item> + <property name="text"> + <string>13</string> + </property> + </item> + <item> + <property name="text"> + <string>14</string> + </property> + </item> + <item> + <property name="text"> + <string>15</string> + </property> + </item> + <item> + <property name="text"> + <string>16</string> + </property> + </item> + <item> + <property name="text"> + <string>17</string> + </property> + </item> + <item> + <property name="text"> + <string>18</string> + </property> + </item> + <property name="name"> + <cstring>amountComboBox</cstring> + </property> + <property name="currentItem"> + <number>3</number> + </property> + </widget> + <widget class="QLabel" row="6" column="0" rowspan="1" colspan="5"> + <property name="name"> + <cstring>textLabel17</cstring> + </property> + <property name="text"> + <string>The amount you choose to pay will be split 50/50 between the artist and Magnatune.com. Your credit card information is sent directly to Magnatune.com using SSL encryption and is not stored by Amarok.</string> + </property> + <property name="alignment"> + <set>WordBreak|AlignVCenter</set> + </property> + </widget> + <widget class="QLineEdit" row="4" column="4"> + <property name="name"> + <cstring>expYearEdit</cstring> + </property> + </widget> + <widget class="QLineEdit" row="4" column="2"> + <property name="name"> + <cstring>expMonthEdit</cstring> + </property> + </widget> + <widget class="QLabel" row="4" column="1"> + <property name="name"> + <cstring>textLabel6</cstring> + </property> + <property name="text"> + <string>Month (xx):</string> + </property> + </widget> + <widget class="QLabel" row="4" column="3"> + <property name="name"> + <cstring>textLabel5</cstring> + </property> + <property name="text"> + <string>Year (xx):</string> + </property> + </widget> + </grid> + </widget> + <spacer row="2" column="0"> + <property name="name"> + <cstring>spacer1</cstring> + </property> + <property name="orientation"> + <enum>Vertical</enum> + </property> + <property name="sizeType"> + <enum>Minimum</enum> + </property> + <property name="sizeHint"> + <size> + <width>20</width> + <height>20</height> + </size> + </property> + </spacer> + </grid> +</widget> +<images> + <image name="image0"> + <data format="XPM.GZ" length="4833">789c8597596f23470e80dfe75718c3b7c182e9eabb11ec834fc9873cbeaf601fc86ec9966df994cfc5fef794483633934d10c836fcb9582cde55fee5dbd2d9de68e9db2f5f9ee7349fb64bed153d2d7deb5e66b38fdffef3efff7ef99aa64b8baf2c5b4abffeebcb57dc5c6a9720499290640b8613e110ff22eb941b07657e7256f90be742e4a7c6a9ed076791c7a1732efbd784d3f817615a71167db0e55c0a17ce95c88f8ced3c7e74d6f306cea29fd159f583b3e8a75367d18f5bce8df0b63389be67e3ccfcd97716fd78e22cfaf1d459edefedcbedbc63e75af4cf85b33edeb8616cfef1b5b39eb7e72cf6c28b7166fb53678df7b5b3fadf388b3d503a8b3d40c6b9eaa35767d9cf37c6a5e53f73d6f3769d55bedf5f2789ac6bfcf2de1fda33ce4cdfbb716eeb6fce1acf1de3c2f2bbe7acf573605cdafe2b67f5efd359f20bb97165febe38b3c453fd2bfa7cd0a67165fea97c9db416ff1de3b1e9d77a6d12567bf8c159ed9d1ab7969fc2b8d378e2ba304579c93769bd719457fd524f2184cae221f51bd250a8fd3c35ae4cfec1b8b6fa06e3c6eaffc859d651f215b2d0c7efdab8327b969d459e9e8d7bfd57c68df587c43be4c1e20b685c1b9f0b17c1e283cfce927f96f911ca40d63fcbce7afebd31eb3a91b3fa37376ead9e64dea5759606a94f546eb23499883d9bc69932bef4acf22cf594725c1f8bfca9b3c8d3aa711e82c82bb73de3817111a4fe2171d67e1a1b97ba8e87ce5a8f12bfb48b2cfae8ceb85206894f9666135bff2e5c45d6fae894a3393a3f5be13aaeb7729eca4ff2d6ce3f5b701ef2b1c95f0aa7715dfb4de657519675d0fc8d8c9ba0f527f12faab234fd8fc695f1ab716dfa3f84ebb2089dec5f779678d381711d74beae398b7f540b3751bfd6e39ab1c9033b6bbd07e326687dcb3c2da8ecf47cfa301e9bbe37e389c94b3c0a2edba0f5f361dc194b3c8b36eed77acf9c453fab7f6d95587e6e8d83c957c25dc9668fdc5fc538b2c6e3a6e754e7adcc836252b6a9d69bccffb2ad535d67e98fb2ab538befaab3e8439947e5b84e82f6ffa17165f1bd7096fcc1ccb8b67a981b375a0f20f92d2775b078493dd54c75aaef0de9dfba75167fea2eb2f683dc5771388d753fbe396b7fca7c6b52ea2c5e57ce621fcbfdd26464f1812767ed8f63e3ced665de34c464fe9e1bb3d5b3f45bc3dca67a7fc9fba7697b46a9bf66cc13cb8fcc8b66c2960f3832b6f3988dad1e2828b799c53371d6f9766b9c5b7f4afe286983f5c3aeb3be177ace6c1ecc9cb51f6a678def8a7169f351fca710f5a93d9db3d64febacf9981aa76a2f5e3b6b7d5d1af7f6df3aebfb67e4acf93f72d6f972675cd83c7d77d67cac3bebfdfce9acf7eb87b3c66b665cdabc5feed9f44bfd51caade59395dbc4fc3f33cecc9f9b9e35bf786fdccfffc459e7ffc059eb7fe2acefcfd459df13dbceda5f4367bddffed8affdf0665c243a6f769c351f6367f51f7ad6fcefcf9dd57e72567fd959e3dd3a6bbc3b677daf34ce6affadb3dadbdb57263a5f46ce7adf8e9dd5dededfbe5e2f9cb5de5b678df7bb71a5fa59f767dccf77cd57d6f7136c185b7e79d359f3159cf53df7e8acf19e19f7f57ee5acef9b0d67f5b733b6fa844b63f38f07ceea9fcc3fca5b9bef58199b3dbce5acefa53567bd6fee8c73d54f95b3fa3b34b6fcc3b3b3f6dba1b3f6afe6a788fb6bad9f1f3f08f19b90b18ddff0f3dafefc2fe4bb284948f1b7f1e2e73fca4ff012af708ad77883b77f2f8f33bc8b9aeff1011ff1099f718e2ff88a6ff88e1ff8196da33fc92fe30aaee21aaee3060e70889bb885dbb88323dc8d7a407df941be8dd2dfa3ec5e94dac7033cc4233cc6133cc5333cc78bffb327c18029669863812556d1ef1a9ba8168080a1850ec608daaf30814bb882296ec035dc486426700b33b8837b78c0213cc2133ce367af3f7a93c01c5e700b5ee10d6fe11d093ee01396610556f104d6601d36a2d77abf0da2f41036610bb6b1841d18c12e7c873dd887033884233886133885b318297d3fc558e114cee1021208d1e01432c8a180122aa8e3c5190f2322c63bb507995aea62bc37698c9f34a14bba822d9ad235ddd02dcde80e87744f0f7fc823d1233de1363d634d737aa1577aa3ebf8047ba70ffaa4655aa1555aa375b37f0c298e6923ca0fb0a1212cd3266dd136edd08876e93bedd13e1dd0211d997e88f6031dd3099d624e67744e17f15f8b40296594cb273ef61632aa5ff34515d5d43032707c19449fd6e993db283b8217ee62fc0630fa31bf1cdf037c493bb1724ef98a162d30e56bcae185467cc3b731dfa0f935f919dff13daef1033ff2133fc77d039e73d41da55ff90d268b8afba17ea27d30a18edff9833f7999577895d7a88421aff31b6fc0e04ff5c6314acc031ef2266cc4c7ce036ff12ca66a10ff75d95eacfd2ccf3b0bfd38e651ac93f7d83b77f84e47d1e6f1222e0bf9c5ef3ff7a3f690766f4ffd0490aa85affffbf5cbef985d44a8</data> + </image> +</images> +<connections> + <connection> + <sender>cancelButton</sender> + <signal>clicked()</signal> + <receiver>magnatunePurchaseDialogBase</receiver> + <slot>cancel()</slot> + </connection> + <connection> + <sender>purchaseButton</sender> + <signal>clicked()</signal> + <receiver>magnatunePurchaseDialogBase</receiver> + <slot>purchase()</slot> + </connection> +</connections> +<tabstops> + <tabstop>albumEdit</tabstop> + <tabstop>artistEdit</tabstop> + <tabstop>genresEdit</tabstop> + <tabstop>launchDateEdit</tabstop> + <tabstop>nameEdit</tabstop> + <tabstop>emailEdit</tabstop> + <tabstop>ccEdit</tabstop> + <tabstop>expMonthEdit</tabstop> + <tabstop>expYearEdit</tabstop> + <tabstop>amountComboBox</tabstop> + <tabstop>purchaseButton</tabstop> + <tabstop>cancelButton</tabstop> +</tabstops> +<slots> + <slot>purchase()</slot> + <slot>cancel()</slot> +</slots> +<layoutdefaults spacing="6" margin="11"/> +</UI> diff --git a/amarok/src/magnatunebrowser/magnatunepurchasehandler.cpp b/amarok/src/magnatunebrowser/magnatunepurchasehandler.cpp new file mode 100644 index 00000000..6c04147d --- /dev/null +++ b/amarok/src/magnatunebrowser/magnatunepurchasehandler.cpp @@ -0,0 +1,253 @@ +/* +Copyright (c) 2006 Nikolaj Hald Nielsen <nhnFreespirit@gmail.com> + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Library General Public +License as published by the Free Software Foundation; either +version 2 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Library General Public License for more details. + +You should have received a copy of the GNU Library General Public License +along with this library; see the file COPYING.LIB. If not, write to +the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, +Boston, MA 02110-1301, USA. +*/ + +#include "amarok.h" +#include "debug.h" +#include "magnatunedatabasehandler.h" +#include "magnatunepurchasehandler.h" +#include "statusbar.h" + +#include <ktempdir.h> + +#include <qdir.h> +#include <qfile.h> +#include <qmessagebox.h> + +MagnatunePurchaseHandler::MagnatunePurchaseHandler() + : QObject() +{ + + m_downloadDialog = 0; + m_purchaseDialog = 0; + m_albumDownloader = 0; +} + + +MagnatunePurchaseHandler::~MagnatunePurchaseHandler() +{ + delete m_downloadDialog; + delete m_purchaseDialog; + delete m_albumDownloader; +} + + +void MagnatunePurchaseHandler::purchaseAlbum( const MagnatuneAlbum &album ) +{ + m_currentAlbum = album; + + //first lets get the album cover for the album we are about to purchase. + //Then we can show it on the purchase dialog as well as put it in the + //same directory as the album. + + QString albumCoverUrlString = album.getCoverURL(); + + if ( m_albumDownloader == 0 ) + { + m_albumDownloader = new MagnatuneAlbumDownloader(); + connect( m_albumDownloader, SIGNAL( coverDownloadCompleted( QString ) ), this, SLOT( showPurchaseDialog( QString ) ) ); + } + + m_currentAlbumCoverName = album.getName() + " - cover.jpg"; + + + m_albumDownloader->downloadCover( albumCoverUrlString, m_currentAlbumCoverName ); + +} + +void MagnatunePurchaseHandler::showPurchaseDialog( QString coverTempLocation ) +{ + + if ( m_albumDownloader != 0 ) + { + delete m_albumDownloader; + m_albumDownloader = 0; + } + + if ( m_purchaseDialog == 0 ) + { + m_purchaseDialog = new MagnatunePurchaseDialog( m_parent, "PurchaseDialog", true, 0 ); + + connect( m_purchaseDialog, SIGNAL( makePurchase( QString, QString, QString, QString, QString, QString, int ) ), this, SLOT( processPayment( QString, QString, QString, QString, QString, QString, int ) ) ); + connect ( m_purchaseDialog, SIGNAL( cancelled() ), this, SLOT( albumPurchaseCancelled() ) ); + } + + + + + + if ( m_currentAlbum.getId() != 0 ) + { + + KTempDir tempDir; + m_purchaseDialog->setAlbum( m_currentAlbum ); + m_purchaseDialog->setCover( coverTempLocation + m_currentAlbumCoverName ); + m_purchaseDialog->show(); + } +} + +void MagnatunePurchaseHandler::processPayment( QString ccNumber, QString expYear, QString expMonth, QString name, QString email, QString albumCode, int amount ) +{ + QString amountString; + amountString.setNum( amount, 10 ); + + QString purchaseURL = "https://magnatune.com/buy/buy_dl_cc_xml?cc=" + ccNumber + "&mm=" + expMonth + "&yy=" + expYear + "&sku=" + albumCode + "&name=" + name + "&email=" + email + "&id=amarok&amount=" + amountString; + + QString debugPurchaseURL = "https://magnatune.com/buy/buy_dl_cc_xml?cc=**********&mm=**&yy=**&sku=" + albumCode + "&name=" + name + "&email=********&id=amarok&amount=" + amountString; + debug() << "purchase url : " << debugPurchaseURL << endl; + + m_resultDownloadJob = KIO::storedGet( KURL( purchaseURL ), false, false ); + Amarok::StatusBar::instance() ->newProgressOperation( m_resultDownloadJob ).setDescription( i18n( "Processing Payment" ) ); + + connect( m_resultDownloadJob, SIGNAL( result( KIO::Job* ) ), SLOT( xmlDownloadComplete( KIO::Job* ) ) ); + + +} + +void MagnatunePurchaseHandler::xmlDownloadComplete( KIO::Job * downloadJob ) +{ + + debug() << "xml download complete" << endl; + + if ( !downloadJob->error() == 0 ) + { + //TODO: error handling here + return ; + } + if ( downloadJob != m_resultDownloadJob ) + return ; //not the right job, so let's ignore it + + KIO::StoredTransferJob* const storedJob = static_cast<KIO::StoredTransferJob*>( downloadJob ); + QString resultXml = QString( storedJob->data() ); + + debug() << endl << endl << "result: " << resultXml << endl << endl; + + + if ( m_albumDownloader == 0 ) + { + m_albumDownloader = new MagnatuneAlbumDownloader(); + connect( m_albumDownloader, SIGNAL( downloadComplete( bool ) ), this, SLOT( albumDownloadComplete( bool ) ) ); + } + + if ( m_downloadDialog == 0 ) + { + m_downloadDialog = new MagnatuneDownloadDialog( m_parent, "downloaddialog", true, 0 ); + connect( m_downloadDialog, SIGNAL( downloadAlbum( MagnatuneDownloadInfo * ) ), m_albumDownloader, SLOT( downloadAlbum( MagnatuneDownloadInfo * ) ) ); + + } + + + + + MagnatuneDownloadInfo * downloadInfo = new MagnatuneDownloadInfo(); + if ( downloadInfo->initFromString( resultXml ) ) + { + + + downloadInfo->setAlbumId( m_currentAlbum.getId() ); + + saveDownloadInfo( resultXml ); + m_downloadDialog->setDownloadInfo( downloadInfo ); + //m_purchaseDialog->close(); + delete m_purchaseDialog; + m_purchaseDialog = 0; + m_downloadDialog->show(); + } + else + { + + QMessageBox::information( m_parent, "Could not process payment", + "There seems to be an error in the information entered (check the credit card number), please try again\n" ); + m_purchaseDialog->setEnabled( true ); + } +} + + +void MagnatunePurchaseHandler::setParent( QWidget * parent ) +{ + m_parent = parent; + +} + +void MagnatunePurchaseHandler::saveDownloadInfo( QString infoXml ) +{ + + QDir purchaseDir( Amarok::saveLocation( "magnatune.com/purchases/" ) ); + + debug() << "magnatune save location" << purchaseDir.absPath() << endl; + + //if directory does not exist, create it + if ( ! purchaseDir.exists () ) + { + purchaseDir.mkdir( ".", false ); + } + + //Create file name + MagnatuneArtist artist = MagnatuneDatabaseHandler::instance() ->getArtistById( m_currentAlbum.getArtistId() ); + QString artistName = artist.getName(); + QString fileName = artistName + " - " + m_currentAlbum.getName(); + + QFile file( purchaseDir.absPath() + "/" + fileName ); + + //Skip if file already exists + if ( file.exists () ) + return ; + + //write info + if ( file.open( IO_WriteOnly ) ) + { + QTextStream stream( &file ); + stream << infoXml << "\n"; + file.close(); + } +} + +void MagnatunePurchaseHandler::albumDownloadComplete( bool success ) +{ + //cleanup time! + + debug() << "MagnatunePurchaseHandler::albumDownloadComplete" << endl; + + delete m_downloadDialog; + m_downloadDialog = 0; + + emit( purchaseCompleted( success ) ); + +} + +void MagnatunePurchaseHandler::albumPurchaseCancelled( ) +{ + debug() << "Purchased dialog cancelled, deleting..." << endl; + + delete m_purchaseDialog; + m_purchaseDialog = 0; + + + emit( purchaseCompleted( false ) ); +} + + + + + + + +#include "magnatunepurchasehandler.moc" + + + diff --git a/amarok/src/magnatunebrowser/magnatunepurchasehandler.h b/amarok/src/magnatunebrowser/magnatunepurchasehandler.h new file mode 100644 index 00000000..a1bf10b9 --- /dev/null +++ b/amarok/src/magnatunebrowser/magnatunepurchasehandler.h @@ -0,0 +1,97 @@ +/* + Copyright (c) 2006 Nikolaj Hald Nielsen <nhnFreespirit@gmail.com> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef MAGNATUNEPURCHASEHANDLER_H +#define MAGNATUNEPURCHASEHANDLER_H + +#include <qobject.h> +#include <kio/job.h> +#include <kio/jobclasses.h> + +#include "magnatunealbumdownloader.h" +#include "magnatunedownloaddialog.h" +#include "magnatunepurchasedialog.h" +#include "magnatunetypes.h" + + +/** +The main class responcible for handelig of purchases from Magnatune.com + +@author Nikolaj Hald Nielsen +*/ +class MagnatunePurchaseHandler : public QObject +{ +Q_OBJECT +public: + MagnatunePurchaseHandler(); + ~MagnatunePurchaseHandler(); + + void setParent( QWidget * parent ); + /** + * Starts a purchase operation + * @param album The album to purchase + */ + void purchaseAlbum( const MagnatuneAlbum &album ); + +signals: + + void purchaseCompleted( bool success ); + +private: + KIO::TransferJob * m_resultDownloadJob; + + + //need a parent to pass to any dialogs we spawn + QWidget * m_parent; + MagnatunePurchaseDialog * m_purchaseDialog; + MagnatuneDownloadDialog * m_downloadDialog; + MagnatuneAlbumDownloader * m_albumDownloader; + MagnatuneAlbum m_currentAlbum; + QString m_currentAlbumCoverName; + + bool parseDownloadXml( QString xml ); + + /** + * This function saves the xml download info received from Magnatune.com after a + * successful payment. This information can be used to later redownload a lost album, + * or acquire an album in a different file format. Note that no personal information + * or credit card number is stored. The information is saved to the amarok config + * directory in the sub folder magnatune.com/purchases. The name of each info file + * is genereated from the artist and album names. + * @param infoXml The info to store. + */ + void saveDownloadInfo(QString infoXml); + + +protected slots: + + void showPurchaseDialog( QString coverTempLocation ); + void xmlDownloadComplete( KIO::Job* downLoadJob ); + void albumDownloadComplete(bool success); + void albumPurchaseCancelled(); + +public slots: + + void processPayment( QString ccNumber, QString expYear, QString expMonth, QString name, QString email, QString albumCode, int amount ); + + + +}; + +#endif diff --git a/amarok/src/magnatunebrowser/magnatuneredownloaddialog.cpp b/amarok/src/magnatunebrowser/magnatuneredownloaddialog.cpp new file mode 100644 index 00000000..fc9f5aa4 --- /dev/null +++ b/amarok/src/magnatunebrowser/magnatuneredownloaddialog.cpp @@ -0,0 +1,74 @@ +/* + Copyright (c) 2006 Nikolaj Hald Nielsen <nhnFreespirit@gmail.com> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + + +#include "magnatuneredownloaddialog.h" + +#include <qpushbutton.h> +#include <klistview.h> + +MagnatuneRedownloadDialog::MagnatuneRedownloadDialog(QWidget* parent, const char* name, bool modal, WFlags fl) +: magnatuneReDownloadDialogBase(parent,name, modal,fl) +{ + redownloadButton->setEnabled ( false ); + + redownloadListView->setColumnWidthMode( 0, QListView::Manual ); + redownloadListView->setResizeMode( QListView::LastColumn ); +} + +MagnatuneRedownloadDialog::~MagnatuneRedownloadDialog() +{ +} + +void MagnatuneRedownloadDialog::setRedownloadItems( QStringList items ) +{ + + for ( QStringList::Iterator it = items.begin(); it != items.end(); ++it ) { + new KListViewItem(redownloadListView, (*it)); + } + +} + +void MagnatuneRedownloadDialog::redownload( ) +{ + emit ( redownload( redownloadListView->currentItem()->text( 0 ) ) ); + + hide(); +} + +void MagnatuneRedownloadDialog::reject( ) +{ + hide(); + emit(cancelled()); +} + +void MagnatuneRedownloadDialog::selectionChanged( ) +{ + if (redownloadListView->currentItem() != 0) { + redownloadButton->setEnabled ( true ); + } else { + redownloadButton->setEnabled ( false ); + } +} + +/*$SPECIALIZATION$*/ + + +#include "magnatuneredownloaddialog.moc" + diff --git a/amarok/src/magnatunebrowser/magnatuneredownloaddialog.h b/amarok/src/magnatunebrowser/magnatuneredownloaddialog.h new file mode 100644 index 00000000..aaf51fd9 --- /dev/null +++ b/amarok/src/magnatunebrowser/magnatuneredownloaddialog.h @@ -0,0 +1,59 @@ +/* + Copyright (c) 2006 Nikolaj Hald Nielsen <nhnFreespirit@gmail.com> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + + +#ifndef MAGNATUNE_REDOWNLOAD_DIALOG_H +#define MAGNATUNE_REDOWNLOAD_DIALOG_H + +#include "magnatuneredownloaddialogbase.h" + +#include <qstringlist.h> + +class MagnatuneRedownloadDialog : public magnatuneReDownloadDialogBase +{ + Q_OBJECT + +public: + MagnatuneRedownloadDialog( QWidget* parent = 0, const char* name = 0, bool modal = false, WFlags fl = 0 ); + ~MagnatuneRedownloadDialog(); + /*$PUBLIC_FUNCTIONS$*/ + + void setRedownloadItems(QStringList items); + +signals: + + void redownload(QString downloadInfoFileName); + void cancelled(); + +public slots: + /*$PUBLIC_SLOTS$*/ + +protected: + /*$PROTECTED_FUNCTIONS$*/ + +protected slots: + /*$PROTECTED_SLOTS$*/ + void redownload(); + void selectionChanged(); + void reject(); + +}; + +#endif + diff --git a/amarok/src/magnatunebrowser/magnatuneredownloaddialogbase.ui b/amarok/src/magnatunebrowser/magnatuneredownloaddialogbase.ui new file mode 100644 index 00000000..5a2bfc2e --- /dev/null +++ b/amarok/src/magnatunebrowser/magnatuneredownloaddialogbase.ui @@ -0,0 +1,109 @@ +<!DOCTYPE UI><UI version="3.3" stdsetdef="1"> +<class>magnatuneReDownloadDialogBase</class> +<widget class="QDialog"> + <property name="name"> + <cstring>magnatuneReDownloadDialogBase</cstring> + </property> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>466</width> + <height>431</height> + </rect> + </property> + <property name="caption"> + <string>Redownload manager</string> + </property> + <grid> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLabel" row="0" column="0" rowspan="1" colspan="3"> + <property name="name"> + <cstring>textLabel1</cstring> + </property> + <property name="text"> + <string>These are the albums that you have previously downloaded:</string> + </property> + </widget> + <widget class="QPushButton" row="2" column="1"> + <property name="name"> + <cstring>redownloadButton</cstring> + </property> + <property name="text"> + <string>Re&download</string> + </property> + </widget> + <widget class="QPushButton" row="2" column="2"> + <property name="name"> + <cstring>cancelButton</cstring> + </property> + <property name="text"> + <string>&Cancel</string> + </property> + </widget> + <spacer row="2" column="0"> + <property name="name"> + <cstring>spacer1</cstring> + </property> + <property name="orientation"> + <enum>Horizontal</enum> + </property> + <property name="sizeType"> + <enum>Expanding</enum> + </property> + <property name="sizeHint"> + <size> + <width>170</width> + <height>20</height> + </size> + </property> + </spacer> + <widget class="KListView" row="1" column="0" rowspan="1" colspan="3"> + <column> + <property name="text"> + <string>Artist - Album</string> + </property> + <property name="clickable"> + <bool>true</bool> + </property> + <property name="resizable"> + <bool>true</bool> + </property> + </column> + <property name="name"> + <cstring>redownloadListView</cstring> + </property> + </widget> + </grid> +</widget> +<connections> + <connection> + <sender>redownloadButton</sender> + <signal>clicked()</signal> + <receiver>magnatuneReDownloadDialogBase</receiver> + <slot>redownload()</slot> + </connection> + <connection> + <sender>cancelButton</sender> + <signal>clicked()</signal> + <receiver>magnatuneReDownloadDialogBase</receiver> + <slot>reject()</slot> + </connection> + <connection> + <sender>redownloadListView</sender> + <signal>selectionChanged()</signal> + <receiver>magnatuneReDownloadDialogBase</receiver> + <slot>selectionChanged()</slot> + </connection> +</connections> +<slots> + <slot access="protected">redownload()</slot> + <slot access="protected">selectionChanged()</slot> +</slots> +<layoutdefaults spacing="6" margin="11"/> +<includehints> + <includehint>klistview.h</includehint> +</includehints> +</UI> diff --git a/amarok/src/magnatunebrowser/magnatuneredownloadhandler.cpp b/amarok/src/magnatunebrowser/magnatuneredownloadhandler.cpp new file mode 100644 index 00000000..a64819bf --- /dev/null +++ b/amarok/src/magnatunebrowser/magnatuneredownloadhandler.cpp @@ -0,0 +1,160 @@ +/* + Copyright (c) 2006 Nikolaj Hald Nielsen <nhnFreespirit@gmail.com> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "magnatuneredownloadhandler.h" + + +#include "amarok.h" +#include "debug.h" + + +#include "qdir.h" +#include "qmessagebox.h" +#include <klocale.h> + + + +MagnatuneRedownloadHandler::MagnatuneRedownloadHandler(QWidget * parent) +{ + m_parent = parent; + m_redownloadDialog = 0; + m_downloadDialog = 0; + m_albumDownloader = 0; +} + + +MagnatuneRedownloadHandler::~MagnatuneRedownloadHandler() +{ +} + +void MagnatuneRedownloadHandler::showRedownloadDialog( ) +{ + + QStringList previousDownloads = GetPurchaseList(); + + if (previousDownloads.isEmpty()) { + + //No previously purchased track information found. No more to do here... + QMessageBox::information( m_parent, i18n( "No purchases found!" ) , + i18n( "No previous purchases have been found. Nothing to redownload..." ) + "\n" ); + return; + } + + if (m_redownloadDialog == 0) { + m_redownloadDialog = new MagnatuneRedownloadDialog( m_parent ); + connect( m_redownloadDialog, SIGNAL( redownload( QString) ), this, SLOT( redownload( QString ) ) ); + connect( m_redownloadDialog, SIGNAL(cancelled() ), this, SLOT( selectionDialogCancelled() )); + } + + + m_redownloadDialog->setRedownloadItems( previousDownloads ); + + m_redownloadDialog->show(); + +} + +QStringList MagnatuneRedownloadHandler::GetPurchaseList( ) +{ + QDir purchaseInfoDir( Amarok::saveLocation( "magnatune.com/purchases/" ) ); + + purchaseInfoDir.setFilter( QDir::Files); + purchaseInfoDir.setSorting( QDir::Name ); + + const QFileInfoList *list = purchaseInfoDir.entryInfoList(); + QFileInfoListIterator it( *list ); + QFileInfo *fi; + + QStringList returnList; + + while ( (fi = it.current()) != 0 ) { + returnList.append(fi->fileName()); + ++it; + } + + return returnList; + +} + +void MagnatuneRedownloadHandler::redownload( QString storedInfoFileName ) +{ + + QDir purchaseInfoDir( Amarok::saveLocation( "magnatune.com/purchases/" ) ); + QString absFileName = purchaseInfoDir.absPath() + '/' + storedInfoFileName; + + debug() << "Redownload file: " << absFileName << endl; + + if ( m_albumDownloader == 0 ) + { + m_albumDownloader = new MagnatuneAlbumDownloader(); + connect( m_albumDownloader, SIGNAL( downloadComplete( bool ) ), this, SLOT( albumDownloadComplete( bool ) ) ); + } + + + if (m_downloadDialog == 0) { + m_downloadDialog = new MagnatuneDownloadDialog(m_parent); + connect( m_downloadDialog, SIGNAL( downloadAlbum( MagnatuneDownloadInfo * ) ), m_albumDownloader, SLOT( downloadAlbum( MagnatuneDownloadInfo * ) ) ); + } + + + MagnatuneDownloadInfo * downloadInfo = new MagnatuneDownloadInfo(); + if ( downloadInfo->initFromFile( absFileName ) ) + { + + debug() << "Showing download dialog" << endl; + m_downloadDialog->setDownloadInfo( downloadInfo ); + m_downloadDialog->show(); + } + else + { + + QMessageBox::information( m_parent, i18n( "Could not re-download album" ), + i18n( "There seems to be a problem with the selected redownload info file." ) + "\n" ); + + } + +} + +void MagnatuneRedownloadHandler::selectionDialogCancelled( ) +{ + if (m_redownloadDialog != 0) { + m_redownloadDialog->hide(); + delete m_redownloadDialog; + m_redownloadDialog = 0; + } +} + +void MagnatuneRedownloadHandler::albumDownloadComplete( bool success ) +{ + Q_UNUSED( success ); + //cleanup time! + + if (m_downloadDialog != 0) { + delete m_downloadDialog; + m_downloadDialog = 0; + } + if (m_albumDownloader != 0) { + delete m_albumDownloader; + m_albumDownloader = 0; + } + +} + + + + diff --git a/amarok/src/magnatunebrowser/magnatuneredownloadhandler.h b/amarok/src/magnatunebrowser/magnatuneredownloadhandler.h new file mode 100644 index 00000000..d7328895 --- /dev/null +++ b/amarok/src/magnatunebrowser/magnatuneredownloadhandler.h @@ -0,0 +1,70 @@ +/* + Copyright (c) 2006 Nikolaj Hald Nielsen <nhnFreespirit@gmail.com> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef MAGNATUNE_REDOWNLOAD_HANDLER_H +#define MAGNATUNE_REDOWNLOAD_HANDLER_H + +#include "magnatunealbumdownloader.h" +#include "magnatunedownloaddialog.h" +#include "magnatunedownloadinfo.h" +#include "magnatuneredownloaddialog.h" + +#include <qobject.h> + +/** +This class handles the redownloading of previously purchased albums + + @author Nikolaj Hald Nielsen <nhnFreespirit@gmail.com> +*/ + +class MagnatuneRedownloadHandler : public QObject { +Q_OBJECT +public: + MagnatuneRedownloadHandler(QWidget * parent); + + ~MagnatuneRedownloadHandler(); + + /** + * Calls forth the redownload dialog. + */ + void showRedownloadDialog(); + +signals: + + void reDownloadCompleted( bool success ); + +protected: + + QWidget * m_parent; + QStringList GetPurchaseList( ); + MagnatuneRedownloadDialog * m_redownloadDialog; + MagnatuneDownloadDialog * m_downloadDialog; + MagnatuneAlbumDownloader * m_albumDownloader; + +protected slots: + + void redownload(QString storedInfoFileName); + void selectionDialogCancelled(); + void albumDownloadComplete( bool success ); + + + +}; + +#endif diff --git a/amarok/src/magnatunebrowser/magnatunetypes.cpp b/amarok/src/magnatunebrowser/magnatunetypes.cpp new file mode 100644 index 00000000..eac3b6b0 --- /dev/null +++ b/amarok/src/magnatunebrowser/magnatunetypes.cpp @@ -0,0 +1,291 @@ +/* + Copyright (c) 2006 Nikolaj Hald Nielsen <nhnFreespirit@gmail.com> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + + + +#include "magnatunetypes.h" + + +//// MagnatuneArtist //// + +MagnatuneArtist::MagnatuneArtist( ) + : m_id(0) +{ +} + +void MagnatuneArtist::setId( int id ) +{ + m_id = id; +} + +int MagnatuneArtist::getId( ) const +{ + return m_id; +} + +void MagnatuneArtist::setName( QString name ) +{ + m_name = name; +} + +QString MagnatuneArtist::getName( ) const +{ + return m_name; +} + +void MagnatuneArtist::setDescription( QString description ) +{ + m_description = description; +} + +QString MagnatuneArtist::getDescription( ) const +{ + return m_description; +} + +void MagnatuneArtist::setPhotoURL( QString photoURL ) +{ + m_photoURL = photoURL; +} + +QString MagnatuneArtist::getPhotoURL( ) const +{ + return m_photoURL; +} + +void MagnatuneArtist::setHomeURL( QString homeURL ) +{ + m_homeURL = homeURL; +} + +QString MagnatuneArtist::getHomeURL( ) const +{ + return m_homeURL; +} + + + + + + + + +//// MagnatuneAlbum //// + +MagnatuneAlbum::MagnatuneAlbum( ) + : m_id (0) + , m_artistId(-1) +{ +} + +void MagnatuneAlbum::setId( int id ) +{ + m_id = id; +} + +int MagnatuneAlbum::getId( ) const +{ + return m_id; +} + +void MagnatuneAlbum::setArtistId( int artistId ) +{ + m_artistId = artistId; +} + +int MagnatuneAlbum::getArtistId( ) const +{ + return m_artistId; +} + + +void MagnatuneAlbum::setName( QString name ) +{ + m_name = name; +} + +QString MagnatuneAlbum::getName( ) const +{ + return m_name; +} + +void MagnatuneAlbum::setCoverURL( QString coverURL ) +{ + m_coverURL = coverURL; +} + +QString MagnatuneAlbum::getCoverURL( ) const +{ + return m_coverURL; +} + +void MagnatuneAlbum::setLaunchDate( QDate launchDate ) +{ + m_launchDate = launchDate; +} + +QDate MagnatuneAlbum::getLaunchDate( ) const +{ + return m_launchDate; +} + +void MagnatuneAlbum::setAlbumCode( QString albumCode ) +{ + m_albumCode = albumCode; +} + +QString MagnatuneAlbum::getAlbumCode( ) const +{ + return m_albumCode; +} + +void MagnatuneAlbum::setMp3Genre( QString mp3Genre ) +{ + m_mp3Genre = mp3Genre; +} + +QString MagnatuneAlbum::getMp3Genre( ) const +{ + return m_mp3Genre; +} + +void MagnatuneAlbum::setMagnatuneGenres( QString magnatuneGenres ) +{ + m_magnatuneGenres = magnatuneGenres; +} + +QString MagnatuneAlbum::getMagnatuneGenres( ) const +{ + return m_magnatuneGenres; +} + + + + + + + + + +//// MagnatuneTrack //// + +MagnatuneTrack::MagnatuneTrack( ) + : m_id( 0 ) + , m_trackNumber( 0 ) + , m_duration( 0 ) + , m_albumId( 0 ) + , m_artistId( 0 ) +{ +} + +void MagnatuneTrack::setId( int id ) +{ + m_id = id; +} + +int MagnatuneTrack::getId( ) const +{ + return m_id; +} + +void MagnatuneTrack::setArtistId( int artistId ) +{ + m_artistId = artistId; +} + +int MagnatuneTrack::getArtistId( ) const +{ + return m_artistId; +} + +void MagnatuneTrack::setAlbumId( int albumId ) +{ + m_albumId = albumId; +} + +int MagnatuneTrack::getAlbumId( ) const +{ + return m_albumId; +} + + + +void MagnatuneTrack::setName( QString name ) +{ + m_name = name; +} + +QString MagnatuneTrack::getName( ) const +{ + return m_name; +} + +void MagnatuneTrack::setTrackNumber( int trackNumber ) +{ + m_trackNumber = trackNumber; +} + +int MagnatuneTrack::getTrackNumber( ) const +{ + return m_trackNumber; +} + +void MagnatuneTrack::setDuration( int duration ) +{ + m_duration = duration; +} + +int MagnatuneTrack::getDuration( ) const +{ + return m_duration; +} + +void MagnatuneTrack::setHifiURL( QString hifiURL ) +{ + m_hifiURL = hifiURL; +} + +QString MagnatuneTrack::getHifiURL( ) const +{ + return m_hifiURL; +} + +void MagnatuneTrack::setLofiURL( QString lofiURL ) +{ + m_lofiURL = lofiURL; +} + +QString MagnatuneTrack::getLofiURL( ) const +{ + return m_lofiURL; +} + + + + + + + + + + + + + + diff --git a/amarok/src/magnatunebrowser/magnatunetypes.h b/amarok/src/magnatunebrowser/magnatunetypes.h new file mode 100644 index 00000000..d11bef8e --- /dev/null +++ b/amarok/src/magnatunebrowser/magnatunetypes.h @@ -0,0 +1,147 @@ +/* + Copyright (c) 2006 Nikolaj Hald Nielsen <nhnFreespirit@gmail.com> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef MAGNATUNETYPES_H +#define MAGNATUNETYPES_H + + +#include <qdatetime.h> +#include <qstring.h> +#include <qvaluelist.h> + + + +class MagnatuneArtist +{ + +private: + int m_id; + QString m_name; + QString m_genre; + QString m_description; + QString m_photoURL; + QString m_homeURL; + +public: + MagnatuneArtist(); + + void setId( int id ); + int getId() const; + + void setName( QString name ); + QString getName() const; + + void setDescription( QString description ); + QString getDescription() const; + + void setPhotoURL( QString photoURL ); + QString getPhotoURL() const; + + void setHomeURL( QString homeURL ); + QString getHomeURL() const; +}; + + +class MagnatuneAlbum +{ +private: + int m_id; + QString m_name; + QString m_coverURL; + QDate m_launchDate; + QString m_albumCode; + QString m_mp3Genre; + QString m_magnatuneGenres; + int m_artistId; + +public: + MagnatuneAlbum(); + + void setId( int id ); + int getId() const; + + void setName( QString name ); + QString getName() const; + + void setCoverURL( QString coverURL ); + QString getCoverURL() const; + + void setLaunchDate( QDate launchDate ); + QDate getLaunchDate() const; + + void setAlbumCode( QString albumCode ); + QString getAlbumCode() const; + + void setMp3Genre( QString mp3Genre ); + QString getMp3Genre() const; + + void setMagnatuneGenres( QString magnatuneGenres ); + QString getMagnatuneGenres() const; + + void setArtistId( int artistId ); + int getArtistId() const; + + +}; + +class MagnatuneTrack +{ +private: + int m_id; + QString m_name; + int m_trackNumber; + int m_duration; + QString m_hifiURL; + QString m_lofiURL; + int m_albumId; + int m_artistId; + +public: + MagnatuneTrack(); + + void setId( int id ); + int getId() const; + + void setName( QString name ); + QString getName() const; + + void setTrackNumber( int trackNumber ); + int getTrackNumber() const; + + void setDuration( int duration ); + int getDuration() const; + + void setHifiURL( QString hifiURL ); + QString getHifiURL() const; + + void setLofiURL( QString lofiURL ); + QString getLofiURL() const; + + void setAlbumId( int albumId ); + int getAlbumId() const; + + void setArtistId( int artistId ); + int getArtistId() const; +}; + +typedef QValueList<MagnatuneArtist> MagnatuneArtistList; +typedef QValueList<MagnatuneAlbum> MagnatuneAlbumList; +typedef QValueList<MagnatuneTrack> MagnatuneTrackList; + +#endif diff --git a/amarok/src/magnatunebrowser/magnatunexmlparser.cpp b/amarok/src/magnatunebrowser/magnatunexmlparser.cpp new file mode 100644 index 00000000..bff65ab7 --- /dev/null +++ b/amarok/src/magnatunebrowser/magnatunexmlparser.cpp @@ -0,0 +1,282 @@ +/* + Copyright (c) 2006 Nikolaj Hald Nielsen <nhnFreespirit@gmail.com> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "amarok.h" +#include "debug.h" +#include "magnatunedatabasehandler.h" +#include "magnatunexmlparser.h" +#include "statusbar.h" + + +MagnatuneXmlParser::MagnatuneXmlParser( QString filename ) + : ThreadManager::Job( "MagnatuneXmlParser" ) +{ + m_currentArtist = ""; + m_sFileName = filename; +} + + +MagnatuneXmlParser::~MagnatuneXmlParser() +{} + +bool +MagnatuneXmlParser::doJob( ) +{ + readConfigFile( m_sFileName ); + return true; +} + + +void +MagnatuneXmlParser::completeJob( ) +{ + Amarok::StatusBar::instance() ->longMessage( + i18n( "Magnatune.com database update complete. Added %1 tracks on %2 albums from %3 artists" ) + .arg( m_nNumberOfTracks ) + .arg( m_nNumberOfAlbums ) + .arg( m_nNumberOfArtists ), KDE::StatusBar::Information ); + + emit( doneParsing() ); +} + +void +MagnatuneXmlParser::readConfigFile( QString filename ) +{ + m_nNumberOfTracks = 0; + m_nNumberOfAlbums = 0; + m_nNumberOfArtists = 0; + + QDomDocument doc( "config" ); + + QFile file( filename ); + if ( !file.open( IO_ReadOnly ) ) + return ; + if ( !doc.setContent( &file ) ) + { + file.close(); + return ; + } + file.close(); + + + MagnatuneDatabaseHandler::instance() ->destroyDatabase(); + MagnatuneDatabaseHandler::instance() ->createDatabase(); + + //run through all the elements + QDomElement docElem = doc.documentElement(); + + + MagnatuneDatabaseHandler::instance() ->begin(); //start transaction (MAJOR speedup!!) + parseElement( docElem ); + MagnatuneDatabaseHandler::instance() ->commit(); //complete transaction + + return ; +} + + +void +MagnatuneXmlParser::parseElement( QDomElement e ) +{ + QString sElementName = e.tagName(); + + sElementName == "Album" ? + parseAlbum( e ) : + parseChildren( e ); +} + + +void +MagnatuneXmlParser::parseChildren( QDomElement e ) +{ + QDomNode n = e.firstChild(); + + while ( !n.isNull() ) + { + if ( n.isElement() ) + parseElement( n.toElement() ); + + n = n.nextSibling(); + } +} + +void +MagnatuneXmlParser::parseAlbum( QDomElement e ) +{ + m_pCurrentAlbum = new MagnatuneAlbum(); + m_pCurrentArtist = new MagnatuneArtist(); + + QString sElementName; + + QDomNode n = e.firstChild(); + QDomElement childElement; + + while ( !n.isNull() ) + { + if ( n.isElement() ) + { + childElement = n.toElement(); + + QString sElementName = childElement.tagName(); + + + if ( sElementName == "albumname" ) + //printf(("|--+" + childElement.text() + "\n").ascii()); + //m_currentAlbumItem = new MagnatuneListViewAlbumItem( m_currentArtistItem); + m_pCurrentAlbum->setName( childElement.text() ); + + else if ( sElementName == "albumsku" ) + m_pCurrentAlbum->setAlbumCode( childElement.text() ); + + else if ( sElementName == "magnatunegenres" ) + m_pCurrentAlbum->setMagnatuneGenres( childElement.text() ); + + else if ( sElementName == "launchdate" ) + { + QString dateString = childElement.text(); + QDate date = QDate::fromString( dateString, Qt::ISODate ); + m_pCurrentAlbum->setLaunchDate( date ); + } + + else if ( sElementName == "cover_small" ) + m_pCurrentAlbum->setCoverURL( childElement.text() ); + + else if ( sElementName == "artist" ) + m_pCurrentArtist->setName( childElement.text() ); + + else if ( sElementName == "artistdesc" ) + m_pCurrentArtist->setDescription( childElement.text() ); + + else if ( sElementName == "artistphoto" ) + m_pCurrentArtist->setPhotoURL( childElement.text() ); + + else if ( sElementName == "mp3genre" ) + m_pCurrentAlbum->setMp3Genre( childElement.text() ); + + else if ( sElementName == "home" ) + m_pCurrentArtist->setHomeURL( childElement.text() ); + + else if ( sElementName == "Track" ) + parseTrack( childElement ); + + } + + n = n.nextSibling(); + } + + // now we should have gathered all info about current album (and artist)... + //Time to add stuff to the database + + //check if artist already exists, if not, create him/her/them/it + + + int artistId = MagnatuneDatabaseHandler::instance() ->getArtistIdByExactName( m_pCurrentArtist->getName() ); + + if ( artistId == -1 ) + { + //does not exist, lets create it... + + //this is tricky in postgresql, returns id as 0 (we are within a transaction, might be the cause...) + artistId = MagnatuneDatabaseHandler::instance()->insertArtist( m_pCurrentArtist ); + m_nNumberOfArtists++; + + if ( artistId == 0 ) + { + artistId = MagnatuneDatabaseHandler::instance() ->getArtistIdByExactName( m_pCurrentArtist->getName() ); + } + } + + + int albumId = MagnatuneDatabaseHandler::instance()->insertAlbum( m_pCurrentAlbum, artistId ); + if ( albumId == 0 ) // again, postgres can play tricks on us... + { + albumId = MagnatuneDatabaseHandler::instance()->getAlbumIdByAlbumCode( m_pCurrentAlbum->getAlbumCode() ); + } + + m_nNumberOfAlbums++; + + MagnatuneTrackList::iterator it; + for ( it = m_currentAlbumTracksList.begin(); it != m_currentAlbumTracksList.end(); ++it ) + { + MagnatuneDatabaseHandler::instance() ->insertTrack( &( *it ), albumId, artistId ); + m_nNumberOfTracks++; + } + + m_currentAlbumTracksList.clear(); +} + + + +void +MagnatuneXmlParser::parseTrack( QDomElement e ) +{ + + QString trackName; + QString trackNumber; + QString streamingUrl; + + + QString sElementName; + QDomElement childElement; + + MagnatuneTrack currentTrack; + + + QDomNode n = e.firstChild(); + + while ( !n.isNull() ) + { + + if ( n.isElement() ) + { + + childElement = n.toElement(); + + QString sElementName = childElement.tagName(); + + + if ( sElementName == "trackname" ) + { + currentTrack.setName( childElement.text() ); + } + else if ( sElementName == "url" ) + { + currentTrack.setHifiURL( childElement.text() ); + } + else if ( sElementName == "mp3lofi" ) + { + currentTrack.setLofiURL( childElement.text() ); + } + else if ( sElementName == "tracknum" ) + { + currentTrack.setTrackNumber( childElement.text().toInt() ); + } + else if ( sElementName == "seconds" ) + { + currentTrack.setDuration( childElement.text().toInt() ); + } + } + n = n.nextSibling(); + } + + m_currentAlbumTracksList.append( currentTrack ); + +} + +#include "magnatunexmlparser.moc" + diff --git a/amarok/src/magnatunebrowser/magnatunexmlparser.h b/amarok/src/magnatunebrowser/magnatunexmlparser.h new file mode 100644 index 00000000..9a03b820 --- /dev/null +++ b/amarok/src/magnatunebrowser/magnatunexmlparser.h @@ -0,0 +1,123 @@ +/* + Copyright (c) 2006 Nikolaj Hald Nielsen <nhnFreespirit@gmail.com> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef MAGNATUNEXMLPARSER_H +#define MAGNATUNEXMLPARSER_H + + +#include "magnatunetypes.h" +#include "threadmanager.h" + +#include <qdom.h> +#include <qstring.h> +#include <qxml.h> + + +/** +* Parser for the XML file from http://magnatune.com/info/album_info.xml +* +* @author Nikolaj Hald Nielsen +*/ +class MagnatuneXmlParser : public ThreadManager::Job +{ + Q_OBJECT + +public: + + /** + * Constructor + * @param fileName The file to parse + * @return Pointer to new object + */ + MagnatuneXmlParser( QString fileName ); + + /** + * The function that starts the actual work. Inherited fromThreadManager::Job + * Note the work is performed in a separate thread + * @return Returns true on success and false on failure + */ + bool doJob(); + + /** + * Called when the job has completed. Is executed in the GUI thread + */ + void completeJob(); + + /** + * Destructor + * @return none + */ + ~MagnatuneXmlParser(); + + /** + * Reads, and starts parsing, file. Should not be used directly. + * @param filename The file to read + */ + void readConfigFile( QString filename ); + +signals: + + /** + * Signal emmited when parsing is complete. + */ + void doneParsing(); + +private: + + QString m_currentArtist; + QString m_currentArtistGenre; + + /** + * Parses a DOM element + * @param e The element to parse + */ + void parseElement( QDomElement e ); + + /** + * Parses all children of a DOM element + * @param e The element whose children is to be parsed + */ + void parseChildren( QDomElement e ); + + /** + * Parse a DOM element representing an album + * @param e The album element to parse + */ + void parseAlbum( QDomElement e ); + + /** + * Parse a DOM element representing a track + * @param e The track element to parse + */ + void parseTrack( QDomElement e ); + + MagnatuneAlbum *m_pCurrentAlbum; + MagnatuneArtist *m_pCurrentArtist; + MagnatuneTrackList m_currentAlbumTracksList; + + QString m_sFileName; + + int m_nNumberOfTracks; + int m_nNumberOfAlbums; + int m_nNumberOfArtists; + + +}; + +#endif diff --git a/amarok/src/main.cpp b/amarok/src/main.cpp new file mode 100644 index 00000000..b98701a4 --- /dev/null +++ b/amarok/src/main.cpp @@ -0,0 +1,115 @@ +/*************************************************************************** + main.cpp - description + ------------------- + begin : Mit Okt 23 14:35:18 CEST 2002 + copyright : (C) 2002 by Mark Kretschmann + email : markey@web.de +***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "amarok.h" +#include "app.h" +#include "crashhandler.h" +#include <kaboutdata.h> + +#include "metadata/tplugins.h" + +//#define AMAROK_USE_DRKONQI + +extern class KAboutData aboutData; //defined in amarokcore/app.cpp + +int main( int argc, char *argv[] ) +{ + aboutData.addAuthor( "Alexandre '" I18N_NOOP("Ain't afraid of no bugs") "' Oliveira", + I18N_NOOP( "Developer (Untouchable)" ), "aleprj@gmail.com" ); + aboutData.addAuthor( "Christian '" I18N_NOOP("Babe-Magnet") "' Muehlhaeuser", + I18N_NOOP( "Stud (muesli)" ), "chris@chris.de", "http://www.chris.de" ); + aboutData.addAuthor( "Frederik 'Ich bin kein Deustcher!' Holljen", + I18N_NOOP( "733t code, OSD improvement, patches (Larson)" ), "fh@ez.no" ); + aboutData.addAuthor( "Gábor '" I18N_NOOP("Opera owns your mom") "' Lehel", + I18N_NOOP( "Developer (illissius)" ), "illissius@gmail.com" ); + aboutData.addAuthor( "Ian '" I18N_NOOP("The Beard") "' Monroe", + I18N_NOOP( "Developer (eean)" ), "ian@monroe.nu", "http://www.monroe.nu/" ); + aboutData.addAuthor( "Jeff '"I18N_NOOP("IROCKSOHARD") "' Mitchell", + I18N_NOOP( "Developer (jefferai)" ), "kde-dev@emailgoeshere.com" ); + aboutData.addAuthor( "Mark '"I18N_NOOP("It's good, but it's not irssi") "' Kretschmann", + I18N_NOOP( "Project founder (markey)" ), "kretschmann@kde.org" ); + aboutData.addAuthor( "Martin '"I18N_NOOP("Easily the most compile-breaks ever!") "' Aumueller", + I18N_NOOP( "Developer (aumuell)" ), "aumuell@reserv.at" ); + aboutData.addAuthor( "Max '" I18N_NOOP("Turtle-Power") "' Howell", + I18N_NOOP( "Cowboy mxcl" ), "max.howell@methylblue.com", "http://www.methylblue.com" ); + aboutData.addAuthor( "Mike '" I18N_NOOP("Purple is not girly!") "' Diehl", + I18N_NOOP( "DCOP, improvements, Preci-i-o-u-u-s handbook maintainer (madpenguin8)" ), "madpenguin8@yahoo.com" ); + aboutData.addAuthor( "Paul '" I18N_NOOP("Meet me at the Amarok Bar!") "' Cifarelli", + I18N_NOOP( "Developer (foreboy)" ), "paul@cifarelli.net" ); + aboutData.addAuthor( "Pierpaolo '" I18N_NOOP("Spaghetti Coder") "' Di Panfilo", + I18N_NOOP( "Playlist-browser, cover-manager (teax)" ), "pippo_dp@libero.it" ); + aboutData.addAuthor( "Roman '" I18N_NOOP("And God said, let there be Mac") "' Becker", + I18N_NOOP( "Amarok logo, splash screen, icons" ), "roman@formmorf.de", "http://www.formmorf.de" ); + aboutData.addAuthor( "Seb '" I18N_NOOP("Surfin' down under") "' Ruiz", + I18N_NOOP( "Developer (sebr)" ), "ruiz@kde.org", "http://www.sebruiz.net" ); + aboutData.addAuthor( "Stanislav '" I18N_NOOP("All you need is DCOP") "' Karchebny", + I18N_NOOP( "DCOP, improvements, cleanups, i18n (berkus)" ), "berkus@madfire.net" ); + + + aboutData.addCredit( "Adam Pigg", I18N_NOOP( "Analyzers, patches, shoutcast" ), "adam@piggz.co.uk" ); + aboutData.addCredit( "Adeodato Simó", I18N_NOOP( "Patches" ), "asp16@alu.ua.es" ); + aboutData.addCredit( "Andreas Mair", I18N_NOOP( "MySQL support" ), "am_ml@linogate.com" ); + aboutData.addCredit( "Andrew de Quincey", I18N_NOOP( "Postgresql support" ), "adq_dvb@lidskialf.net" ); + aboutData.addCredit( "Andrew Turner", I18N_NOOP( "Patches" ), "andrewturner512@googlemail.com" ); + aboutData.addCredit( "Bart Cerneels", I18N_NOOP( "podcast code improvements" ), "shanachie@yucom.be" ); + aboutData.addCredit( "Christie Harris", I18N_NOOP( "roKymoter (dangle)" ), "dangle.baby@gmail.com" ); + aboutData.addCredit( "Dan Leinir Turthra Jensen", I18N_NOOP( "First-run wizard, usability" ), "admin@REMOVEleinir.dk" ); + aboutData.addCredit( "Dan Meltzer", I18N_NOOP( "roKymoter (hydrogen)" ), "hydrogen@notyetimplemented.com" ); + aboutData.addCredit( "Derek Nelson", I18N_NOOP( "graphics, splash-screen" ), "admrla@gmail.com" ); + aboutData.addCredit( "Enrico Ros", I18N_NOOP( "Analyzers, Context Browser and systray eye-candy" ), "eros.kde@email.it" ); + aboutData.addCredit( "Gérard Dürrmeyer", I18N_NOOP( "icons and image work" ), "gerard@randomtree.com" ); + aboutData.addCredit( "Giovanni Venturi", I18N_NOOP( "dialog to filter the collection titles" ), "giovanni@ksniffer.org" ); + aboutData.addCredit( "Greg Meyer", I18N_NOOP( "Live CD, Bug squashing (oggb4mp3)" ), "greg@gkmweb.com" ); + aboutData.addCredit( "Harald Sitter", I18N_NOOP( "handbook enhancements, translations, bug fixes, screenshots, roKymoter (apachelogger)" ), "harald.sitter@kdemail.net" ); + aboutData.addCredit( "Jarkko Lehti", I18N_NOOP( "Tester, IRC channel operator, whipping" ), "grue@iki.fi" ); + aboutData.addCredit( "Jocke Andersson", I18N_NOOP( "roKymoter, bug fixer and Swedish Bitch (Firetech)" ), "ajocke@gmail.com" ); + aboutData.addCredit( "Kenneth Wesley Wimer II", I18N_NOOP( "Icons" ), "kwwii@bootsplash.org" ); + aboutData.addCredit( "Marco Gulino", I18N_NOOP( "Konqueror Sidebar, some DCOP methods" ), "marco@kmobiletools.org" ); + aboutData.addCredit( "Maximilian Kossick", I18N_NOOP( "Dynamic Collection, label support, patches" ), "maximilian.kossick@gmail.com" ); + aboutData.addCredit( "Melchior Franz", I18N_NOOP( "FHT routine, bugfixes" ), "mfranz@kde.org" ); + aboutData.addCredit( "Michael Pyne", I18N_NOOP( "K3B export code" ), "michael.pyne@kdemail.net" ); + aboutData.addCredit( "Nenad Grujicic", I18N_NOOP( "Splash screen" ), "mchitman@neobee.net" ); + aboutData.addCredit( "Nikolaj Hald Nielsen", I18N_NOOP( "Magnatune.com store integration (nhnFreespirit)" ), "nhnFreespirit@gmail.com" ); + aboutData.addCredit( "Olivier Bédard", I18N_NOOP( "Website hosting" ), "paleo@pwsp.net" ); + aboutData.addCredit( "Peter C. Ndikuwera", I18N_NOOP( "Bugfixes, PostgreSQL support" ), "pndiku@gmail.com" ); + aboutData.addCredit( "Reigo Reinmets", I18N_NOOP( "Wikipedia support, patches" ), "xatax@hot.ee" ); + aboutData.addCredit( "Roland Gigler", I18N_NOOP( "MAS engine" ), "rolandg@web.de" ); + aboutData.addCredit( "Sami Nieminen", I18N_NOOP( "Audioscrobbler support" ), "sami.nieminen@iki.fi" ); + aboutData.addCredit( "Scott Wheeler", I18N_NOOP( "TagLib & ktrm code" ), "wheeler@kde.org" ); + aboutData.addCredit( "Shane King", I18N_NOOP( "Patches" ), "kde@dontletsstart.com" ); + aboutData.addCredit( "Stefan Bogner", I18N_NOOP( "Loadsa stuff" ), "bochi@online.ms" ); + aboutData.addCredit( "Stefan Siegel", I18N_NOOP( "Patches, Bugfixes" ), "kde@sdas.de" ); + aboutData.addCredit( "Sven Krohlas", I18N_NOOP( "roKymoter (sven423)" ), "sven@asbest-online.de" ); + aboutData.addCredit( "Vadim Petrunin", I18N_NOOP( "Graphics, splash-screen (vnizzz)" ), "vnizzz@list.ru" ); + aboutData.addCredit( "Whitehawk Stormchaser", I18N_NOOP( "Tester, patches" ), "zerokode@gmx.net" ); + + registerTaglibPlugins(); + + KApplication::disableAutoDcopRegistration(); + + App::initCliArgs( argc, argv ); + App app; + +#ifdef Q_WS_X11 + #ifndef AMAROK_USE_DRKONQI + KCrash::setCrashHandler( Amarok::Crash::crashHandler ); + #endif +#endif + + + return app.exec(); +} diff --git a/amarok/src/mediabrowser.cpp b/amarok/src/mediabrowser.cpp new file mode 100644 index 00000000..4916784f --- /dev/null +++ b/amarok/src/mediabrowser.cpp @@ -0,0 +1,3824 @@ +// (c) 2004 Christian Muehlhaeuser <chris@chris.de> +// (c) 2005-2006 Martin Aumueller <aumuell@reserv.at> +// (c) 2005 Seb Ruiz <me@sebruiz.net> +// (c) 2006 T.R.Shashwath <trshash84@gmail.com> +// See COPYING file for licensing information + + +#define DEBUG_PREFIX "MediaBrowser" + +#include <config.h> + +#include "amarok.h" +#include "amarokconfig.h" +#include "app.h" +#include "browserToolBar.h" +#include "clicklineedit.h" +#include "collectiondb.h" +#include "colorgenerator.h" +#include "contextbrowser.h" +#include "debug.h" +#include "editfilterdialog.h" +#include "deviceconfiguredialog.h" +#include "mediadevicemanager.h" +#include "expression.h" +#include "hintlineedit.h" +#include "mediabrowser.h" +#include "medium.h" +#include "mediumpluginmanager.h" +#include "metabundle.h" +#include "mountpointmanager.h" +#include "playlist.h" +#include "playlistbrowser.h" +#include "playlistbrowseritem.h" +#include "playlistloader.h" +#include "pluginmanager.h" +#include "podcastbundle.h" +#include "scriptmanager.h" +#include "scrobbler.h" +#include "statusbar.h" +#include "transferdialog.h" +#include "browserToolBar.h" + +#include <qvbuttongroup.h> +#include <qcheckbox.h> +#include <qdatetime.h> +#include <qdir.h> +#include <qdom.h> +#include <qfileinfo.h> +#include <qgroupbox.h> +#include <qheader.h> +#include <qimage.h> +#include <qlabel.h> +#include <qobjectlist.h> +#include <qpainter.h> +#include <qradiobutton.h> +#include <qsimplerichtext.h> +#include <qtimer.h> +#include <qtooltip.h> //QToolTip::add() + +#include <kapplication.h> //kapp +#include <kcombobox.h> +#include <kdirlister.h> +#include <kfiledialog.h> +#include <kglobal.h> +#include <kiconloader.h> +#include <kinputdialog.h> +#include <kio/job.h> +#include <klocale.h> +#include <kmessagebox.h> +#include <kmultipledrag.h> +#include <kpopupmenu.h> +#include <kprocess.h> +#include <kprogress.h> +#include <kpushbutton.h> +#include <krun.h> +#include <kstandarddirs.h> //locate file +#include <ktabbar.h> +#include <ktempfile.h> +#include <ktoolbarbutton.h> //ctor +#include <kurldrag.h> //dragObject() +#include <kactioncollection.h> + + +MediaBrowser *MediaBrowser::s_instance = 0; + +QPixmap *MediaItem::s_pixUnknown = 0; +QPixmap *MediaItem::s_pixArtist = 0; +QPixmap *MediaItem::s_pixAlbum = 0; +QPixmap *MediaItem::s_pixFile = 0; +QPixmap *MediaItem::s_pixTrack = 0; +QPixmap *MediaItem::s_pixPodcast = 0; +QPixmap *MediaItem::s_pixPlaylist = 0; +QPixmap *MediaItem::s_pixInvisible = 0; +QPixmap *MediaItem::s_pixStale = 0; +QPixmap *MediaItem::s_pixOrphaned = 0; +QPixmap *MediaItem::s_pixDirectory = 0; +QPixmap *MediaItem::s_pixRootItem = 0; +QPixmap *MediaItem::s_pixTransferFailed = 0; +QPixmap *MediaItem::s_pixTransferBegin = 0; +QPixmap *MediaItem::s_pixTransferEnd = 0; + +bool MediaBrowser::isAvailable() //static +{ + if( !MediaBrowser::instance() ) + return false; + + return true; + + //to re-enable hiding, uncomment this and get rid of the return true above: + //return MediaBrowser::instance()->m_haveDevices; +} + +class SpaceLabel : public QLabel { + public: + SpaceLabel(QWidget *parent) + : QLabel(parent) + { + m_total = m_used = m_scheduled = 0; + setBackgroundMode(Qt::NoBackground); + } + + void paintEvent(QPaintEvent *e) + { + QPainter p(this); + p.fillRect(e->rect(), colorGroup().brush(QColorGroup::Background)); + + if(m_total > 0) + { + int used = int(float(m_used)/float(m_total)*width()); + int scheduled = int(float(m_used + m_scheduled)/float(m_total)*width()); + + if(m_used > 0) + { + QColor blueish(70,120,255); + if(e->rect().left() < used) + { + int right = used; + if(e->rect().right() < right) + right = e->rect().right(); + p.fillRect(e->rect().left(), e->rect().top(), + used, e->rect().bottom()+1, QBrush(blueish, Qt::SolidPattern)); + } + } + + if(m_scheduled > 0) + { + QColor sched(70, 230, 120); + if(m_used + m_scheduled > m_total - m_total/200) + { + sched.setRgb( 255, 120, 120 ); + } + int left = e->rect().left(); + if(used > left) + left = used; + int right = e->rect().right(); + if(scheduled < right) + right = scheduled; + p.fillRect(left, e->rect().top(), right, e->rect().bottom()+1, QBrush(sched, Qt::SolidPattern)); + } + + if(m_used + m_scheduled < m_total) + { + QColor grey(180, 180, 180); + int left = e->rect().left(); + if(scheduled > left) + left = scheduled; + int right = e->rect().right(); + p.fillRect(left, e->rect().top(), right, e->rect().bottom()+1, colorGroup().brush(QColorGroup::Background)); + } + } + QLabel::paintEvent(e); + } + + KIO::filesize_t m_total; + KIO::filesize_t m_used; + KIO::filesize_t m_scheduled; +}; + +class DummyMediaDevice : public MediaDevice +{ + public: + DummyMediaDevice() : MediaDevice() + { + m_name = i18n( "No Device Available" ); + m_type = "dummy-mediadevice"; + m_medium = Medium( "DummyDevice", "DummyDevice" ); + } + void init( MediaBrowser *browser ) { MediaDevice::init( browser ); } + virtual ~DummyMediaDevice() {} + virtual bool isConnected() { return false; } + virtual MediaItem* trackExists(const MetaBundle&) { return 0; } + virtual bool lockDevice(bool) { return true; } + virtual void unlockDevice() {} + virtual bool openDevice( bool silent ) + { + if( !silent ) + { + //QString msg = i18n( "Sorry, you do not have a supported portable music player." ); + //Amarok::StatusBar::instance()->longMessage( msg, KDE::StatusBar::Sorry ); + } + return false; + } + virtual bool closeDevice() { return false; } + virtual void synchronizeDevice() {} + virtual MediaItem* copyTrackToDevice(const MetaBundle&) { return 0; } + virtual int deleteItemFromDevice(MediaItem*, int) { return -1; } +}; + + +MediaBrowser::MediaBrowser( const char *name ) + : QVBox( 0, name ) + , m_timer( new QTimer( this ) ) + , m_currentDevice( m_devices.end() ) + , m_waitForTranscode( false ) + , m_quitting( false ) +{ + s_instance = this; + + // preload pixmaps used in browser + KIconLoader iconLoader; + MediaItem::s_pixUnknown = new QPixmap(iconLoader.loadIcon( Amarok::icon( "unknown" ), KIcon::Toolbar, KIcon::SizeSmall )); + MediaItem::s_pixTrack = new QPixmap(iconLoader.loadIcon( Amarok::icon( "playlist" ), KIcon::Toolbar, KIcon::SizeSmall )); + MediaItem::s_pixFile = new QPixmap(iconLoader.loadIcon( Amarok::icon( "sound" ), KIcon::Toolbar, KIcon::SizeSmall ) ); + MediaItem::s_pixPodcast = new QPixmap(iconLoader.loadIcon( Amarok::icon( "podcast" ), KIcon::Toolbar, KIcon::SizeSmall ) ); + MediaItem::s_pixPlaylist = new QPixmap(iconLoader.loadIcon( Amarok::icon( "playlist" ), KIcon::Toolbar, KIcon::SizeSmall ) ); + MediaItem::s_pixRootItem = new QPixmap(iconLoader.loadIcon( Amarok::icon( "files2" ), KIcon::Toolbar, KIcon::SizeSmall ) ); + // history + // favorites + // collection + // folder + // folder_red + // player_playlist_2 + // cancel + // sound + MediaItem::s_pixArtist = new QPixmap(iconLoader.loadIcon( Amarok::icon( "personal" ), KIcon::Toolbar, KIcon::SizeSmall ) ); + MediaItem::s_pixAlbum = new QPixmap(iconLoader.loadIcon( Amarok::icon( "cdrom_unmount" ), KIcon::Toolbar, KIcon::SizeSmall ) ); + MediaItem::s_pixInvisible = new QPixmap(iconLoader.loadIcon( Amarok::icon( "cancel" ), KIcon::Toolbar, KIcon::SizeSmall ) ); + MediaItem::s_pixStale = new QPixmap(iconLoader.loadIcon( Amarok::icon( "cancel" ), KIcon::Toolbar, KIcon::SizeSmall ) ); + MediaItem::s_pixOrphaned = new QPixmap(iconLoader.loadIcon( Amarok::icon( "cancel" ), KIcon::Toolbar, KIcon::SizeSmall ) ); + MediaItem::s_pixDirectory = new QPixmap(iconLoader.loadIcon( Amarok::icon( "folder" ), KIcon::Toolbar, KIcon::SizeSmall ) ); + MediaItem::s_pixTransferBegin = new QPixmap(iconLoader.loadIcon( Amarok::icon( "play" ), KIcon::Toolbar, KIcon::SizeSmall ) ); + MediaItem::s_pixTransferEnd = new QPixmap(iconLoader.loadIcon( Amarok::icon( "stop" ), KIcon::Toolbar, KIcon::SizeSmall ) ); + MediaItem::s_pixTransferFailed = new QPixmap(iconLoader.loadIcon( Amarok::icon( "cancel" ), KIcon::Toolbar, KIcon::SizeSmall ) ); + + setSpacing( 4 ); + + m_toolbar = new Browser::ToolBar( this ); + m_toolbar->setIconText( KToolBar::IconTextRight, false ); + + m_toolbar->insertButton( "connect_creating", CONNECT, true, i18n("Connect") ); + QToolTip::add( m_toolbar->getButton(CONNECT), i18n( "Connect media device" ) ); + + m_toolbar->insertButton( "player_eject", DISCONNECT, true, i18n("Disconnect") ); + QToolTip::add( m_toolbar->getButton(DISCONNECT), i18n( "Disconnect media device" ) ); + + m_toolbar->insertButton( "rebuild", TRANSFER, true, i18n("Transfer") ); + QToolTip::add( m_toolbar->getButton(TRANSFER), i18n( "Transfer tracks to media device" ) ); + + m_toolbar->insertLineSeparator(); + + // m_toolbar->setIconText( KToolBar::IconTextRight, true ); + m_toolbar->insertButton( Amarok::icon( "add_playlist" ), CUSTOM, SIGNAL( clicked() ), this, SLOT( customClicked() ), true, "custom" ); + QToolTip::add( m_toolbar->getButton(TRANSFER), i18n( "Transfer tracks to media device" ) ); + + m_toolbar->setIconText( KToolBar::IconOnly, false ); + + m_toolbar->insertButton( Amarok::icon( "configure" ), CONFIGURE, true, i18n("Configure") ); + QToolTip::add( m_toolbar->getButton(CONFIGURE), i18n( "Configure device" ) ); + + + m_deviceCombo = new KComboBox( this ); + + // searching/filtering + { //<Search LineEdit> + KToolBar* searchToolBar = new Browser::ToolBar( this ); + KToolBarButton *button = new KToolBarButton( "locationbar_erase", 0, searchToolBar ); + m_searchEdit = new ClickLineEdit( i18n( "Enter search terms here" ), searchToolBar ); + KPushButton *filterButton = new KPushButton("...", searchToolBar, "filter"); + searchToolBar->setStretchableWidget( m_searchEdit ); + m_searchEdit->setFrame( QFrame::Sunken ); + + connect( button, SIGNAL( clicked() ), m_searchEdit, SLOT( clear() ) ); + connect( filterButton, SIGNAL( clicked() ), SLOT( slotEditFilter() ) ); + + QToolTip::add( button, i18n( "Clear filter" ) ); + QToolTip::add( m_searchEdit, i18n( "Enter space-separated terms to search" ) ); + QToolTip::add( filterButton, i18n( "Click to edit filter" ) ); + } //</Search LineEdit> + + connect( m_timer, SIGNAL( timeout() ), SLOT( slotSetFilter() ) ); + connect( m_searchEdit, SIGNAL( textChanged( const QString& ) ), SLOT( slotSetFilterTimeout() ) ); + connect( m_searchEdit, SIGNAL( returnPressed() ), SLOT( slotSetFilter() ) ); + + // connect to device manager + connect( MediaDeviceManager::instance(), SIGNAL( mediumAdded(const Medium *, QString) ), + SLOT( mediumAdded(const Medium *, QString) ) ); + connect( MediaDeviceManager::instance(), SIGNAL( mediumChanged(const Medium *, QString) ), + SLOT( mediumChanged(const Medium *, QString) ) ); + connect( MediaDeviceManager::instance(), SIGNAL( mediumRemoved(const Medium *, QString) ), + SLOT( mediumRemoved(const Medium *, QString) ) ); + + + // we always have a dummy device + m_pluginName[ i18n( "Disable" ) ] = "dummy-mediadevice"; + m_pluginAmarokName["dummy-mediadevice"] = i18n( "Disable" ); + m_pluginName[ i18n( "Do not handle" ) ] = "ignore"; + m_pluginAmarokName["ignore"] = i18n( "Do not handle" ); + // query available device plugins + m_plugins = PluginManager::query( "[X-KDE-Amarok-plugintype] == 'mediadevice'" ); + for( KTrader::OfferList::ConstIterator it = m_plugins.begin(); it != m_plugins.end(); ++it ) { + // Save name properties in QMap for lookup + m_pluginName[(*it)->name()] = (*it)->property( "X-KDE-Amarok-name" ).toString(); + m_pluginAmarokName[(*it)->property( "X-KDE-Amarok-name" ).toString()] = (*it)->name(); + } + + m_views = new QVBox( this ); + m_queue = new MediaQueue( this ); + m_progressBox = new QHBox( this ); + m_progress = new KProgress( m_progressBox ); + m_cancelButton = new KPushButton( SmallIconSet( Amarok::icon( "cancel" ) ), i18n("Cancel"), m_progressBox ); + + + m_stats = new SpaceLabel(this); + + m_progressBox->hide(); + + MediaDevice *dev = new DummyMediaDevice(); + dev->init( this ); + addDevice( dev ); + activateDevice( 0, false ); + queue()->load( Amarok::saveLocation() + "transferlist.xml" ); + queue()->computeSize(); + + setFocusProxy( m_queue ); + + updateStats(); + + QMap<QString, Medium*> mmap = MediaDeviceManager::instance()->getMediumMap(); + + bool newflag = false; + //This deals with <strike>auto-detectable</strike> ALL devices! + for( QMap<QString, Medium*>::Iterator it = mmap.begin(); + it != mmap.end(); + it++ ) + { + QString handler = Amarok::config( "MediaBrowser" )->readEntry( (*it)->id() ); + //debug() << "[MediaBrowser] (*it)->id() = " << (*it)->id() << ", handler = " << handler << endl; + if( handler.isEmpty() ) + { + //this should probably never be the case with a manually added device, unless amarokrc's been messed with + Amarok::config( "MediaBrowser" )->writeEntry( (*it)->id(), "ignore" ); + newflag = true; + mediumAdded( *it, (*it)->name(), true ); + } + //and this definitely shouldn't! + else if( handler != "deleted" ) + mediumAdded( *it, (*it)->name(), true ); + } + + if ( newflag ) + Amarok::StatusBar::instance()->longMessageThreadSafe( + i18n("Amarok has detected new portable media devices.\n" + "Go to the \"Media Devices\" pane of the configuration\n" + "dialog to choose a plugin for these devices.") ); + + connect( m_toolbar->getButton(CONNECT), SIGNAL( clicked() ), SLOT( connectClicked() ) ); + connect( m_toolbar->getButton(DISCONNECT), SIGNAL( clicked() ), SLOT( disconnectClicked() ) ); + connect( m_toolbar->getButton(TRANSFER), SIGNAL( clicked() ), SLOT( transferClicked() ) ); + connect( m_toolbar->getButton(CONFIGURE), SIGNAL( clicked() ), SLOT( config() ) ); + + connect( m_deviceCombo, SIGNAL( activated( int ) ), SLOT( activateDevice( int ) ) ); + + connect( m_cancelButton, SIGNAL( clicked() ), SLOT( cancelClicked() ) ); + connect( pApp, SIGNAL( prepareToQuit() ), SLOT( prepareToQuit() ) ); + connect( CollectionDB::instance(), SIGNAL( tagsChanged( const MetaBundle& ) ), + SLOT( tagsChanged( const MetaBundle& ) ) ); + + m_haveDevices = false; + QMap<QString,QString> savedDevices = Amarok::config( "MediaBrowser" )->entryMap( "MediaBrowser" ); + for( QMap<QString,QString>::Iterator it = savedDevices.begin(); + it != savedDevices.end(); + ++it ) + { + if( it.data() != "deleted" && it.data() != "ignore" ) + { + m_haveDevices = true; + break; + } + } + emit availabilityChanged( m_haveDevices ); +} + +bool +MediaBrowser::blockQuit() const +{ + for( QValueList<MediaDevice *>::const_iterator it = m_devices.begin(); + it != m_devices.end(); + ++it ) + { + if( *it && (*it)->isConnected() ) + return true; + } + + return false; +} + +void +MediaBrowser::tagsChanged( const MetaBundle &bundle ) +{ + m_itemMapMutex.lock(); + debug() << "tags changed for " << bundle.url().url() << endl; + ItemMap::iterator it = m_itemMap.find( bundle.url().url() ); + if( it != m_itemMap.end() ) + { + MediaItem *item = *it; + m_itemMapMutex.unlock(); + if( item->device() ) + { + item->device()->tagsChanged( item, bundle ); + } + else + { + // it's an item on the transfer queue + item->setBundle( new MetaBundle( bundle ) ); + + QString text = item->bundle()->prettyTitle(); + if( text.isEmpty() || (!item->bundle()->isValidMedia() && !item->bundle()->podcastBundle()) ) + text = item->bundle()->url().prettyURL(); + if( !item->m_playlistName.isNull() ) + { + text += " (" + item->m_playlistName + ')'; + } + item->setText( 0, text); + } + } + else + { + m_itemMapMutex.unlock(); + } +} + +bool +MediaBrowser::getBundle( const KURL &url, MetaBundle *bundle ) const +{ + QMutexLocker locker( &m_itemMapMutex ); + ItemMap::const_iterator it = m_itemMap.find( url.url() ); + if( it == m_itemMap.end() ) + return false; + + if( bundle ) + *bundle = QDeepCopy<MetaBundle>( *(*it)->bundle() ); + + return true; +} + +KURL +MediaBrowser::getProxyUrl( const KURL& daapUrl ) const +{ + DEBUG_BLOCK + KURL url; + MediaDevice* dc = dynamic_cast<MediaDevice*>( queryList( "DaapClient" )->getFirst() ); + if( dc ) + url = dc->getProxyUrl( daapUrl ); + return url; +} + +MediaDevice * +MediaBrowser::currentDevice() const +{ + QValueList<MediaDevice *>::const_iterator current = m_currentDevice; + if( current != m_devices.constEnd() ) + { + return *m_currentDevice; + } + + return 0; +} + +MediaDevice * +MediaBrowser::deviceFromId( const QString &id ) const +{ + for( QValueList<MediaDevice *>::const_iterator it = m_devices.constBegin(); + it != m_devices.end(); + it++ ) + { + if( (*it)->uniqueId() == id ) + return (*it); + } + + return NULL; +} + +void +MediaBrowser::activateDevice( const MediaDevice *dev ) +{ + int index = 0; + for( QValueList<MediaDevice *>::iterator it = m_devices.begin(); + it != m_devices.end(); + it++ ) + { + if( *it == dev ) + { + activateDevice( index ); + break; + } + index++; + } +} + +void +MediaBrowser::activateDevice( int index, bool skipDummy ) +{ + if( currentDevice() && currentDevice()->customAction() ) + { + currentDevice()->customAction()->unplug( m_toolbar ); + m_toolbar->hide(); + m_toolbar->show(); + } + + for( QValueList<MediaDevice *>::iterator it = m_devices.begin(); + it != m_devices.end(); + it++ ) + { + (*it)->view()->hide(); + } + + if( index < 0 ) + { + m_currentDevice = m_devices.end(); + return; + } + + if( skipDummy ) + index++; + + if( (uint)index >= m_devices.count() ) + { + m_currentDevice = m_devices.end(); + updateButtons(); + queue()->computeSize(); + updateStats(); + return; + } + + m_currentDevice = m_devices.at( index ); + if( currentDevice() ) + { + currentDevice()->view()->show(); + if( currentDevice()->customAction() ) + { + m_toolbar->setIconText( KToolBar::IconTextRight, false ); + currentDevice()->customAction()->plug( m_toolbar ); + m_toolbar->hide(); + m_toolbar->show(); + } + } + m_deviceCombo->setCurrentItem( index-1 ); + + updateButtons(); + queue()->computeSize(); + updateStats(); +} + +void +MediaBrowser::addDevice( MediaDevice *device ) +{ + m_devices.append( device ); + + device->loadConfig(); + + if( device->autoConnect() ) + { + device->connectDevice( true ); + updateButtons(); + } + + updateDevices(); +} + +void +MediaBrowser::removeDevice( MediaDevice *device ) +{ + DEBUG_BLOCK + + debug() << "remove device: type=" << device->deviceType() << endl; + + for( QValueList<MediaDevice *>::iterator it = m_devices.begin(); + it != m_devices.end(); + it++ ) + { + if( *it == device ) + { + bool current = (it == m_currentDevice); + m_devices.remove( device ); + if( current ) + activateDevice( 0, false ); + break; + } + } + + if( device->isConnected() ) + { + if( device->disconnectDevice( false /* don't run post-disconnect command */ ) ) + unloadDevicePlugin( device ); + else + { + debug() << "Cannot remove device because disconnect failed" << endl; + Amarok::StatusBar::instance()->longMessage( + i18n( "Cannot remove device because disconnect failed" ), + KDE::StatusBar::Warning ); + } + } + else + unloadDevicePlugin( device ); + + updateDevices(); +} + +void +MediaBrowser::updateDevices() +{ + m_deviceCombo->clear(); + uint i = 0; + for( QValueList<MediaDevice *>::iterator it = m_devices.begin(); + it != m_devices.end(); + it++ ) + { + if( m_devices.count() > 1 && dynamic_cast<DummyMediaDevice *>(*it) ) + continue; + QString name = (*it)->name(); + if( !(*it)->deviceNode().isEmpty() ) + { + name = i18n( "%1 at %2" ).arg( name, (*it)->deviceNode() ); + } + if( (*it)->hasMountPoint() && !(*it)->mountPoint().isEmpty() ) + { + name += i18n( " (mounted at %1)" ).arg( (*it)->mountPoint() ); + } + m_deviceCombo->insertItem( name, i ); + if( it == m_currentDevice ) + { + m_deviceCombo->setCurrentItem( i ); + } + i++; + } + m_deviceCombo->setEnabled( m_devices.count() > 1 ); + m_haveDevices = m_devices.count() > 1; + emit availabilityChanged( m_haveDevices ); +} + +QStringList +MediaBrowser::deviceNames() const +{ + QStringList list; + + for( QValueList<MediaDevice *>::const_iterator it = m_devices.constBegin(); + it != m_devices.constEnd(); + it++ ) + { + QString name = (*it)->name(); + list << name; + } + + return list; +} + +bool +MediaBrowser::deviceSwitch( const QString &name ) +{ + int index = 0; + for( QValueList<MediaDevice *>::iterator it = m_devices.begin(); + it != m_devices.end(); + it++ ) + { + if( (*it)->name() == name ) + { + activateDevice( index, false ); + return true; + } + index++; + } + + return false; +} + +void +MediaBrowser::transcodingFinished( const QString &src, const QString &dst ) +{ + KURL srcJob = KURL::fromPathOrURL( m_transcodeSrc ); + KURL srcResult = KURL::fromPathOrURL( src ); + + if( srcJob.path() == srcResult.path() ) + { + m_transcodedUrl = KURL::fromPathOrURL( dst ); + m_waitForTranscode = false; + } + else + { + debug() << "transcoding for " << src << " finished, " + << "but we are waiting for " << m_transcodeSrc << " -- aborting" << endl; + m_waitForTranscode = false; + } +} + +KURL +MediaBrowser::transcode( const KURL &src, const QString &filetype ) +{ + const ScriptManager* const sm = ScriptManager::instance(); + + if( sm->transcodeScriptRunning().isEmpty() ) + { + debug() << "cannot transcode with no transcoder registered" << endl; + return KURL(); + } + + m_waitForTranscode = true; + m_transcodeSrc = src.url(); + m_transcodedUrl = KURL(); + ScriptManager::instance()->notifyTranscode( src.url(), filetype ); + + while( m_waitForTranscode && sm->transcodeScriptRunning() != QString::null ) + { + usleep( 10000 ); + kapp->processEvents( 100 ); + } + + return m_transcodedUrl; +} + + +void +MediaBrowser::slotSetFilterTimeout() //SLOT +{ + m_timer->start( 280, true ); //stops the timer for us first +} + +void +MediaBrowser::slotSetFilter() //SLOT +{ + m_timer->stop(); + + if( currentDevice() ) + currentDevice()->view()->setFilter( m_searchEdit->text() ); +} + +void +MediaBrowser::slotSetFilter( const QString &text ) +{ + m_searchEdit->setText( text ); + slotSetFilter(); +} + +void +MediaBrowser::slotEditFilter() +{ + EditFilterDialog *fd = new EditFilterDialog( this, true, m_searchEdit->text() ); + connect( fd, SIGNAL(filterChanged(const QString &)), SLOT(slotSetFilter(const QString &)) ); + if( fd->exec() ) + m_searchEdit->setText( fd->filter() ); + delete fd; +} + +void +MediaBrowser::prepareToQuit() +{ + m_waitForTranscode = false; + m_quitting = true; + for( QValueList<MediaDevice *>::iterator it = m_devices.begin(); + it != m_devices.end(); + ++it ) + { + if( (*it)->isConnected() ) + (*it)->disconnectDevice( false /* don't unmount */ ); + } +} + +MediaBrowser::~MediaBrowser() +{ + debug() << "having to remove " << m_devices.count() << " devices" << endl; + while( !m_devices.isEmpty() ) + { + removeDevice( m_devices.last() ); + } + + queue()->save( Amarok::saveLocation() + "transferlist.xml" ); + + delete m_deviceCombo; + delete m_queue; +} + + +MediaItem::MediaItem( QListView* parent ) +: KListViewItem( parent ) +{ + init(); +} + +MediaItem::MediaItem( QListViewItem* parent ) +: KListViewItem( parent ) +{ + init(); +} + +MediaItem::MediaItem( QListView* parent, QListViewItem* after ) +: KListViewItem( parent, after ) +{ + init(); +} + +MediaItem::MediaItem( QListViewItem* parent, QListViewItem* after ) +: KListViewItem( parent, after ) +{ + init(); +} + +MediaItem::~MediaItem() +{ + setBundle( 0 ); +} + +void +MediaItem::init() +{ + m_bundle=0; + m_order=0; + m_type=UNKNOWN; + m_playlistName = QString::null; + m_device=0; + m_flags=0; + setExpandable( false ); + setDragEnabled( true ); + setDropEnabled( true ); +} + +void +MediaItem::setBundle( MetaBundle *bundle ) +{ + MediaBrowser::instance()->m_itemMapMutex.lock(); + if( m_bundle ) + { + QString itemUrl = url().url(); + MediaBrowser::ItemMap::iterator it = MediaBrowser::instance()->m_itemMap.find( itemUrl ); + if( it != MediaBrowser::instance()->m_itemMap.end() && *it == this ) + MediaBrowser::instance()->m_itemMap.remove( itemUrl ); + } + delete m_bundle; + m_bundle = bundle; + + if( m_bundle ) + { + QString itemUrl = url().url(); + MediaBrowser::ItemMap::iterator it = MediaBrowser::instance()->m_itemMap.find( itemUrl ); + if( it == MediaBrowser::instance()->m_itemMap.end() ) + MediaBrowser::instance()->m_itemMap[itemUrl] = this; + } + MediaBrowser::instance()->m_itemMapMutex.unlock(); +} + +void MediaItem::paintCell( QPainter *p, const QColorGroup &cg, int column, int width, int align ) +{ + switch( type() ) + { + case INVISIBLE: + case PODCASTSROOT: + case PLAYLISTSROOT: + case ORPHANEDROOT: + case STALEROOT: + { + QFont font( p->font() ); + font.setBold( true ); + p->setFont( font ); + } + default: + break; + } + + KListViewItem::paintCell( p, cg, column, width, align ); +} + +const MetaBundle * +MediaItem::bundle() const +{ + return m_bundle; +} + +KURL +MediaItem::url() const +{ + if( bundle() ) + return bundle()->url(); + else + return KURL(); +} + +bool +MediaItem::isFileBacked() const +{ + switch( type() ) + { + case ARTIST: + case ALBUM: + case PODCASTSROOT: + case PODCASTCHANNEL: + case PLAYLISTSROOT: + case PLAYLIST: + case PLAYLISTITEM: + case INVISIBLEROOT: + case STALEROOT: + case STALE: + case ORPHANEDROOT: + return false; + + case UNKNOWN: + case TRACK: + case ORPHANED: + case INVISIBLE: + case PODCASTITEM: + case DIRECTORY: + return true; + } + + return false; +} + +long +MediaItem::size() const +{ + if( !isFileBacked() ) + return 0; + + if( bundle() ) + return bundle()->filesize(); + + return 0; +} + +void +MediaItem::setType( Type type ) +{ + m_type=type; + + setDragEnabled(true); + setDropEnabled(false); + + switch(m_type) + { + case UNKNOWN: + setPixmap(0, *s_pixUnknown); + break; + case INVISIBLE: + case TRACK: + setPixmap(0, *s_pixFile); + break; + case PLAYLISTITEM: + setPixmap(0, *s_pixTrack); + setDropEnabled(true); + break; + case ARTIST: + setPixmap(0, *s_pixArtist); + break; + case ALBUM: + setPixmap(0, *s_pixAlbum); + break; + case PODCASTSROOT: + setPixmap(0, *s_pixRootItem); + break; + case PODCASTITEM: + case PODCASTCHANNEL: + setPixmap(0, *s_pixPodcast); + break; + case PLAYLIST: + setPixmap(0, *s_pixPlaylist); + setDropEnabled(true); + break; + case PLAYLISTSROOT: + setPixmap(0, *s_pixRootItem); + setDropEnabled( true ); + break; + case INVISIBLEROOT: + setPixmap(0, *s_pixInvisible); + break; + case STALEROOT: + case STALE: + setPixmap(0, *s_pixStale); + break; + case ORPHANEDROOT: + case ORPHANED: + setPixmap(0, *s_pixOrphaned); + break; + case DIRECTORY: + setExpandable( true ); + setDropEnabled( true ); + setPixmap(0, *s_pixDirectory); + break; + } +} + +void +MediaItem::setFailed( bool failed ) +{ + if( failed ) + { + m_flags &= ~MediaItem::Transferring; + m_flags |= MediaItem::Failed; + setPixmap(0, *MediaItem::s_pixTransferFailed); + } + else + { + m_flags &= ~MediaItem::Failed; + if( m_type == PODCASTITEM ) + setPixmap(0, *s_pixPodcast); + else if( m_type == PLAYLIST ) + setPixmap(0, *s_pixPlaylist); + else + setPixmap(0, QPixmap() ); + } +} + +MediaItem * +MediaItem::lastChild() const +{ + QListViewItem *last = 0; + for( QListViewItem *it = firstChild(); + it; + it = it->nextSibling() ) + { + last = it; + } + + return dynamic_cast<MediaItem *>(last); +} + +bool +MediaItem::isLeafItem() const +{ + switch(type()) + { + case UNKNOWN: + return false; + + case INVISIBLE: + case TRACK: + case PODCASTITEM: + case PLAYLISTITEM: + case STALE: + case ORPHANED: + return true; + + case ARTIST: + case ALBUM: + case PODCASTSROOT: + case PODCASTCHANNEL: + case PLAYLISTSROOT: + case PLAYLIST: + case INVISIBLEROOT: + case STALEROOT: + case ORPHANEDROOT: + case DIRECTORY: + return false; + } + + return false; +} + +MediaItem * +MediaItem::findItem( const QString &key, const MediaItem *after ) const +{ + MediaItem *it = 0; + if( after ) + it = dynamic_cast<MediaItem *>( after->nextSibling() ); + else + it = dynamic_cast<MediaItem *>( firstChild() ); + + for( ; it; it = dynamic_cast<MediaItem *>(it->nextSibling())) + { + if(key == it->text(0)) + return it; + if(key.isEmpty() && it->text(0).isEmpty()) + return it; + } + return 0; +} + +int +MediaItem::compare( QListViewItem *i, int col, bool ascending ) const +{ + MediaItem *item = dynamic_cast<MediaItem *>(i); + if(item && col==0 && item->m_order != m_order) + return m_order-item->m_order; + else if( item && item->type() == MediaItem::ARTIST ) + { + QString key1 = key( col, ascending ); + if( key1.startsWith( "the ", false ) ) + key1 = key1.mid( 4 ); + QString key2 = i->key( col, ascending ); + if( key2.startsWith( "the ", false ) ) + key2 = key2.mid( 4 ); + + return key1.localeAwareCompare( key2 ); + } + + return KListViewItem::compare(i, col, ascending); +} + +class MediaItemTip : public QToolTip +{ + public: + MediaItemTip( QListView *listview ) + : QToolTip( listview->viewport() ) + , m_view( listview ) + {} + virtual ~MediaItemTip() {} + protected: + virtual void maybeTip( const QPoint &p ) + { + MediaItem *i = dynamic_cast<MediaItem *>(m_view->itemAt( m_view->viewportToContents( p ) ) ); + if( !i ) + return; + + QString text; + switch( i->type() ) + { + case MediaItem::TRACK: + { + const MetaBundle *b = i->bundle(); + if( b ) + { + if( b->track() ) + text = QString( "%1 - %2 (%3)" ) + .arg( QString::number(b->track()), b->title(), b->prettyLength() ); + if( !b->genre().isEmpty() ) + { + if( !text.isEmpty() ) + text += "<br>"; + text += QString( "<i>Genre: %1</i>" ) + .arg( b->genre() ); + } + } + } + break; + case MediaItem::PLAYLISTSROOT: + text = i18n( "Drag items here to create new playlist" ); + break; + case MediaItem::PLAYLIST: + text = i18n( "Drag items here to append to this playlist" ); + break; + case MediaItem::PLAYLISTITEM: + text = i18n( "Drag items here to insert before this item" ); + break; + case MediaItem::INVISIBLEROOT: + case MediaItem::INVISIBLE: + text = i18n( "Not visible on media device" ); + break; + case MediaItem::STALEROOT: + case MediaItem::STALE: + text = i18n( "In device database, but file is missing" ); + break; + case MediaItem::ORPHANEDROOT: + case MediaItem::ORPHANED: + text = i18n( "File on device, but not in device database" ); + break; + default: + break; + } + + if( !text.isEmpty() && !text.isNull() ) + tip( m_view->itemRect( i ), text ); + } + + QListView *m_view; +}; + + +MediaView::MediaView( QWidget* parent, MediaDevice *device ) + : KListView( parent ) + , m_parent( parent ) + , m_device( device ) +{ + hide(); + setSelectionMode( QListView::Extended ); + setItemsMovable( false ); + setShowSortIndicator( true ); + setFullWidth( true ); + setRootIsDecorated( true ); + setDragEnabled( true ); + setDropVisualizer( true ); //the visualizer (a line marker) is drawn when dragging over tracks + setDropHighlighter( true ); //and the highligther (a focus rect) is drawn when dragging over playlists + setDropVisualizerWidth( 3 ); + setAcceptDrops( true ); + + header()->hide(); + addColumn( i18n( "Remote Media" ) ); + + KActionCollection* ac = new KActionCollection( this ); + KStdAction::selectAll( this, SLOT( selectAll() ), ac, "mediabrowser_select_all" ); + + connect( this, SIGNAL( contextMenuRequested( QListViewItem*, const QPoint&, int ) ), + this, SLOT( rmbPressed( QListViewItem*, const QPoint&, int ) ) ); + + connect( this, SIGNAL( itemRenamed( QListViewItem* ) ), + this, SLOT( renameItem( QListViewItem* ) ) ); + + connect( this, SIGNAL( expanded( QListViewItem* ) ), + this, SLOT( slotExpand( QListViewItem* ) ) ); + + connect( this, SIGNAL( returnPressed( QListViewItem* ) ), + this, SLOT( invokeItem( QListViewItem* ) ) ); + + connect( this, SIGNAL( doubleClicked( QListViewItem*, const QPoint&, int ) ), + this, SLOT( invokeItem( QListViewItem*, const QPoint &, int ) ) ); + + m_toolTip = new MediaItemTip( this ); +} + +void +MediaView::keyPressEvent( QKeyEvent *e ) +{ + if( e->key() == Key_Delete ) + m_device->deleteFromDevice(); + else + KListView::keyPressEvent( e ); +} + +void +MediaView::invokeItem( QListViewItem* i, const QPoint& point, int column ) //SLOT +{ + if( column == -1 ) + return; + + QPoint p = mapFromGlobal( point ); + if ( p.x() > header()->sectionPos( header()->mapToIndex( 0 ) ) + treeStepSize() * ( i->depth() + ( rootIsDecorated() ? 1 : 0) ) + itemMargin() + || p.x() < header()->sectionPos( header()->mapToIndex( 0 ) ) ) + invokeItem( i ); +} + + +void +MediaView::invokeItem( QListViewItem *i ) +{ + MediaItem *item = dynamic_cast<MediaItem *>( i ); + if( !item ) + return; + + KURL::List urls = nodeBuildDragList( item ); + Playlist::instance()->insertMedia( urls, Playlist::DefaultOptions ); +} + +void +MediaView::renameItem( QListViewItem *item ) +{ + m_device->renameItem( item ); +} + +void +MediaView::slotExpand( QListViewItem *item ) +{ + m_device->expandItem( item ); +} + + +MediaView::~MediaView() +{ + delete m_toolTip; +} + + +QDragObject * +MediaView::dragObject() +{ + KURL::List urls = nodeBuildDragList( 0 ); + KMultipleDrag *md = new KMultipleDrag( viewport() ); + md->addDragObject( KListView::dragObject() ); + KURLDrag* ud = new KURLDrag( urls, viewport() ); + md->addDragObject( ud ); + md->setPixmap( CollectionDB::createDragPixmap( urls ), + QPoint( CollectionDB::DRAGPIXMAP_OFFSET_X, CollectionDB::DRAGPIXMAP_OFFSET_Y ) ); + return md; +} + + +KURL::List +MediaView::nodeBuildDragList( MediaItem* item, int flags ) +{ + KURL::List items; + MediaItem* fi; + + if ( !item ) + { + fi = static_cast<MediaItem*>(firstChild()); + } + else + fi = item; + + while ( fi ) + { + if( fi->isVisible() ) + { + if ( fi->isSelected() || !(flags & OnlySelected ) ) + { + if( fi->isLeafItem() || fi->type() == MediaItem::DIRECTORY ) + items += fi->url(); + else + { + if(fi->childCount() ) + items += nodeBuildDragList( static_cast<MediaItem*>(fi->firstChild()), None ); + } + } + else + { + if ( fi->childCount() ) + items += nodeBuildDragList( static_cast<MediaItem*>(fi->firstChild()), OnlySelected ); + } + } + fi = static_cast<MediaItem*>(fi->nextSibling()); + } + return items; +} + +int +MediaView::getSelectedLeaves( MediaItem *parent, QPtrList<MediaItem> *list, int flags ) +{ + int numFiles = 0; + if( !list ) + list = new QPtrList<MediaItem>; + + MediaItem *it; + if( !parent ) + it = dynamic_cast<MediaItem *>(firstChild()); + else + it = dynamic_cast<MediaItem *>(parent->firstChild()); + + for( ; it; it = dynamic_cast<MediaItem*>(it->nextSibling())) + { + if( it->isVisible() ) + { + if( it->childCount() && !( it->type() == MediaItem::DIRECTORY && it->isSelected() ) ) + { + int f = flags; + if( it->isSelected() ) + f &= ~OnlySelected; + numFiles += getSelectedLeaves(it, list, f ); + } + if( it->isSelected() || !(flags & OnlySelected) ) + { + if( it->type() == MediaItem::TRACK || + it->type() == MediaItem::DIRECTORY || + it->type() == MediaItem::PODCASTITEM || + it->type() == MediaItem::PLAYLISTITEM|| + it->type() == MediaItem::INVISIBLE || + it->type() == MediaItem::ORPHANED ) + { + if( flags & OnlyPlayed ) + { + if( it->played() > 0 ) + numFiles++; + } + else + numFiles++; + } + if( ( it->isLeafItem() && (!(flags & OnlyPlayed) || it->played()>0) ) + || it->type() == MediaItem::DIRECTORY ) + list->append( it ); + } + } + } + return numFiles; +} + + +bool +MediaView::acceptDrag( QDropEvent *e ) const +{ + if( e->source() == MediaBrowser::queue()->viewport() ) + return false; + + QString data; + QCString subtype; + QTextDrag::decode( e, data, subtype ); + + return e->source() == viewport() + || subtype == "amarok-sql" + || KURLDrag::canDecode( e ); +} + +void +MediaView::contentsDropEvent( QDropEvent *e ) +{ + cleanDropVisualizer(); + cleanItemHighlighter(); + + if(e->source() == viewport()) + { + const QPoint p = contentsToViewport( e->pos() ); + MediaItem *item = dynamic_cast<MediaItem *>(itemAt( p )); + + if( !item && MediaBrowser::instance()->currentDevice()->m_type != "generic-mediadevice" ) + return; + + QPtrList<MediaItem> items; + + if( !item || item->type() == MediaItem::DIRECTORY || + item->type() == MediaItem::TRACK ) + { + QPtrList<MediaItem> items; + getSelectedLeaves( 0, &items ); + m_device->addToDirectory( item, items ); + } + else if( item->type() == MediaItem::PLAYLIST ) + { + MediaItem *list = item; + MediaItem *after = 0; + for(MediaItem *it = dynamic_cast<MediaItem *>(item->firstChild()); + it; + it = dynamic_cast<MediaItem *>(it->nextSibling())) + after = it; + + getSelectedLeaves( 0, &items ); + m_device->addToPlaylist( list, after, items ); + } + else if( item->type() == MediaItem::PLAYLISTITEM ) + { + MediaItem *list = dynamic_cast<MediaItem *>(item->parent()); + MediaItem *after = 0; + for(MediaItem *it = dynamic_cast<MediaItem*>(item->parent()->firstChild()); + it; + it = dynamic_cast<MediaItem *>(it->nextSibling())) + { + if(it == item) + break; + after = it; + } + + getSelectedLeaves( 0, &items ); + m_device->addToPlaylist( list, after, items ); + } + else if( item->type() == MediaItem::PLAYLISTSROOT ) + { + QPtrList<MediaItem> items; + getSelectedLeaves( 0, &items ); + QString base( i18n("New Playlist") ); + QString name = base; + int i=1; + while( item->findItem(name) ) + { + QString num; + num.setNum(i); + name = base + ' ' + num; + i++; + } + MediaItem *pl = m_device->newPlaylist(name, item, items); + ensureItemVisible(pl); + rename(pl, 0); + } + } + else + { + QString data; + QCString subtype; + QTextDrag::decode( e, data, subtype ); + KURL::List list; + + if( subtype == "amarok-sql" ) + { + QString playlist = data.section( "\n", 0, 0 ); + QString query = data.section( "\n", 1 ); + QStringList values = CollectionDB::instance()->query( query ); + list = CollectionDB::instance()->URLsFromSqlDrag( values ); + MediaBrowser::queue()->addURLs( list, playlist ); + } + else if ( KURLDrag::decode( e, list ) ) + { + MediaBrowser::queue()->addURLs( list ); + } + } +} + +void +MediaView::viewportPaintEvent( QPaintEvent *e ) +{ + KListView::viewportPaintEvent( e ); + + // Superimpose bubble help: + + if ( !MediaBrowser::instance()->currentDevice() || !MediaBrowser::instance()->currentDevice()->isConnected() ) + { + QPainter p( viewport() ); + + QSimpleRichText t( i18n( + "<div align=center>" + "<h3>Media Device Browser</h3>" + "Configure your media device and then " + "click the Connect button to access your media device. " + "Drag and drop files to enqueue them for transfer." + "</div>" ), QApplication::font() ); + + t.setWidth( width() - 50 ); + + const uint w = t.width() + 20; + const uint h = t.height() + 20; + + p.setBrush( colorGroup().background() ); + p.drawRoundRect( 15, 15, w, h, (8*200)/w, (8*200)/h ); + t.draw( &p, 20, 20, QRect(), colorGroup() ); + } + MediaBrowser::instance()->updateButtons(); +} + +void +MediaView::rmbPressed( QListViewItem *item, const QPoint &p, int i ) +{ + if( m_device->isConnected() ) + m_device->rmbPressed( item, p, i ); +} + +MediaItem * +MediaView::newDirectory( MediaItem *parent ) +{ + bool ok; + const QString name = KInputDialog::getText(i18n("Add Directory"), i18n("Directory Name:"), QString::null, &ok, this); + + if( ok && !name.isEmpty() ) + { + m_device->newDirectory( name, parent ); + } + + return 0; +} + +void +MediaBrowser::mediumAdded( const Medium *medium, QString /*name*/, bool /*constructing*/ ) +{ + debug() << "mediumAdded: " << (medium? medium->properties():"null") << endl; + if( medium ) + { + QString handler = Amarok::config( "MediaBrowser" )->readEntry( medium->id() ); + if( handler.isEmpty() ) + { + //Some people complained about the dialog, boohoo + //Just disable it for now I guess + /*if( !constructing && medium->isAutodetected() ) + { + MediumPluginManagerDialog *mpm = new MediumPluginManagerDialog(); + mpm->exec(); + }*/ + } + //debug() << "id=" << medium->id() << ", handler=" << handler << endl; + MediaDevice *device = loadDevicePlugin( handler ); + if( device ) + { + device->m_medium = *medium; + addDevice( device ); + if( m_currentDevice == m_devices.begin() || m_currentDevice == m_devices.end() ) + activateDevice( m_devices.count()-1, false ); + } + } +} + +void +MediaBrowser::pluginSelected( const Medium *medium, const QString plugin ) +{ + DEBUG_BLOCK + if( !plugin.isEmpty() ) + { + debug() << "Medium id is " << medium->id() << " and plugin selected is: " << plugin << endl; + Amarok::config( "MediaBrowser" )->writeEntry( medium->id(), plugin ); + + bool success = true; + for( QValueList<MediaDevice *>::iterator it = m_devices.begin(); + it != m_devices.end(); + it++ ) + { + if( (*it)->uniqueId() == medium->id() ) + { + debug() << "removing " << medium->deviceNode() << endl; + if( (*it)->isConnected() ) + { + if( (*it)->disconnectDevice( false ) ) + removeDevice( *it ); + else + success = false; + } + else + removeDevice( *it ); + break; + } + } + + if( success ) + { + mediumAdded( medium, "doesntmatter", false ); + } + else + { + debug() << "Cannot change plugin while operation is in progress" << endl; + Amarok::StatusBar::instance()->longMessage( + i18n( "Cannot change plugin while operation is in progress" ), + KDE::StatusBar::Warning ); + } + } + else + debug() << "Medium id is " << medium->id() << " and you opted not to use a plugin" << endl; +} + +void +MediaBrowser::showPluginManager() +{ + MediumPluginManagerDialog* mpm = new MediumPluginManagerDialog(); + mpm->exec(); + delete mpm; +} + +void +MediaBrowser::mediumChanged( const Medium *medium, QString /*name*/ ) +{ + if( medium ) + { + for( QValueList<MediaDevice *>::iterator it = m_devices.begin(); + it != m_devices.end(); + it++ ) + { + if( (*it)->uniqueId() == medium->id() ) + { + (*it)->m_medium = const_cast<Medium *>(medium); + if( !(*it)->isConnected() && medium->isMounted() ) + (*it)->connectDevice(); +#if 0 + else if( (*it)->isConnected() && !medium->isMounted() ) + { + Amarok::StatusBar::instance()->longMessage( + i18n( "The device %1 was unmounted before it was synchronized. " + "In order to avoid data loss, press the \"Disconnect\" button " + "before unmounting the device." ).arg( name ), + KDE::StatusBar::Warning ); + //(*it)->disconnectDevice(); + } +#endif + break; + } + } + } +} + +void +MediaBrowser::mediumRemoved( const Medium *medium, QString name ) +{ + if( medium ) + { + for( QValueList<MediaDevice *>::iterator it = m_devices.begin(); + it != m_devices.end(); + it++ ) + { + if( (*it)->uniqueId() == medium->id() ) + { + if( (*it)->isConnected() ) + { + if( (*it)->disconnectDevice() ) + removeDevice( *it ); + Amarok::StatusBar::instance()->longMessage( + i18n( "The device %1 was removed before it was disconnected. " + "In order to avoid possible data loss, press the \"Disconnect\" " + "button before disconnecting the device." ).arg( name ), + KDE::StatusBar::Warning ); + } + else + removeDevice( *it ); + break; + } + } + } +} + +MediaDevice * +MediaBrowser::loadDevicePlugin( const QString &deviceType ) +{ + DEBUG_BLOCK + + if( deviceType == "ignore" ) + return 0; + + QString query = "[X-KDE-Amarok-plugintype] == 'mediadevice' and [X-KDE-Amarok-name] == '%1'"; + Amarok::Plugin *plugin = PluginManager::createFromQuery( query.arg( deviceType ) ); + + if( plugin ) + { + debug() << "Returning plugin!" << endl; + MediaDevice *device = static_cast<MediaDevice *>( plugin ); + device->init( this ); + device->m_type = deviceType; + return device; + } + + debug() << "no plugin for " << deviceType << endl; + return 0; +} + +void +MediaBrowser::unloadDevicePlugin( MediaDevice *device ) +{ + DEBUG_BLOCK + + if( !device ) + return; + + disconnect( device ); // disconnect all signals + + if( dynamic_cast<DummyMediaDevice *>(device) ) + { + delete device; + } + else + { + PluginManager::unload( device ); + } +} + +bool +MediaBrowser::config() +{ + if( m_deviceCombo->currentText() == "No Device Selected" ) + { + showPluginManager(); + return true; + } + + DeviceConfigureDialog* dcd = new DeviceConfigureDialog( currentDevice()->m_medium ); + dcd->exec(); + bool successful = dcd->successful(); + delete dcd; + return successful; +} + +void +MediaBrowser::configSelectPlugin( int index ) +{ + Q_UNUSED( index ); + + if( m_currentDevice == m_devices.begin() ) + { + AmarokConfig::setDeviceType( m_pluginName[m_configPluginCombo->currentText()] ); + } + else if( currentDevice() ) + { + KConfig *config = Amarok::config( "MediaBrowser" ); + config->writeEntry( currentDevice()->uniqueId(), m_pluginName[m_configPluginCombo->currentText()] ); + } + + if( !currentDevice() ) + activateDevice( 0, false ); + + if( !currentDevice() ) + return; + + if( m_pluginName[m_configPluginCombo->currentText()] != currentDevice()->deviceType() ) + { + MediaDevice *dev = currentDevice(); + dev->removeConfigElements( m_configBox ); + if( dev->isConnected() ) + { + dev->disconnectDevice( false ); + } + unloadDevicePlugin( dev ); + *m_currentDevice = loadDevicePlugin( AmarokConfig::deviceType() ); + if( !*m_currentDevice ) + { + *m_currentDevice = new DummyMediaDevice(); + if( AmarokConfig::deviceType() != "dummy-mediadevice" ) + { + QString msg = i18n( "The requested media device could not be loaded" ); + Amarok::StatusBar::instance()->shortMessage( msg ); + } + } + dev = currentDevice(); + dev->init( this ); + dev->loadConfig(); + + m_configBox->hide(); + dev->addConfigElements( m_configBox ); + m_configBox->show(); + + dev->view()->show(); + + if( dev->autoConnect() ) + { + dev->connectDevice( true ); + updateButtons(); + } + + updateDevices(); + } +} + +void +MediaBrowser::updateButtons() +{ + if( !m_toolbar->getButton(CONNECT) || + !m_toolbar->getButton(DISCONNECT) || + !m_toolbar->getButton(TRANSFER) ) //TODO add CUSTOM + return; + + if( currentDevice() ) + { + if( currentDevice()->m_transfer ) + m_toolbar->showItem( TRANSFER ); + else + m_toolbar->hideItem( TRANSFER ); + + if( currentDevice()->m_customButton ) + m_toolbar->showItem( CUSTOM ); + else + m_toolbar->hideItem( CUSTOM ); + + if( currentDevice()->m_configure ) + m_toolbar->showItem( CONFIGURE ); + else + m_toolbar->hideItem( CONFIGURE ); + + m_toolbar->getButton(CONNECT)->setEnabled( !currentDevice()->isConnected() ); + m_toolbar->getButton(DISCONNECT)->setEnabled( currentDevice()->isConnected() ); + m_toolbar->getButton(TRANSFER)->setEnabled( currentDevice()->isConnected() && m_queue->childCount() > 0 ); + m_toolbar->getButton( CUSTOM )->setEnabled( true ); + } + else + { + m_toolbar->getButton( CONNECT )->setEnabled( false ); + m_toolbar->getButton( DISCONNECT )->setEnabled( false ); + m_toolbar->getButton( TRANSFER )->setEnabled( false ); + m_toolbar->getButton( CUSTOM )->setEnabled( false ); + } +} + +void +MediaBrowser::updateStats() +{ + if( !m_stats ) + return; + + KIO::filesize_t queued = m_queue->totalSize(); + + QString text = i18n( "1 track in queue", "%n tracks in queue", m_queue->childCount() ); + if(m_queue->childCount() > 0) + { + text += i18n(" (%1)").arg( KIO::convertSize( queued ) ); + } + + KIO::filesize_t total, avail; + if( currentDevice() && currentDevice()->getCapacity(&total, &avail) ) + { + text += i18n( " - %1 of %2 available" ).arg( KIO::convertSize( avail ) ).arg( KIO::convertSize( total ) ); + + m_stats->m_used = total-avail; + m_stats->m_total = total; + m_stats->m_scheduled = queued; + } + else + { + m_stats->m_used = 0; + m_stats->m_total = 0; + m_stats->m_scheduled = queued; + } + + m_stats->setText(text); + QToolTip::add( m_stats, text ); +} + + +bool +MediaView::setFilter( const QString &filter, MediaItem *parent ) +{ + bool advanced = ExpressionParser::isAdvancedExpression( filter ); + QValueList<int> defaultColumns; + defaultColumns << MetaBundle::Album; + defaultColumns << MetaBundle::Title; + defaultColumns << MetaBundle::Artist; + + bool root = false; + MediaItem *it; + if( !parent ) + { + root = true; + it = dynamic_cast<MediaItem *>(firstChild()); + } + else + { + it = dynamic_cast<MediaItem *>(parent->firstChild()); + } + + bool childrenVisible = false; + for( ; it; it = dynamic_cast<MediaItem *>(it->nextSibling())) + { + bool visible = true; + if(it->isLeafItem()) + { + if( advanced ) + { + ParsedExpression parsed = ExpressionParser::parse( filter ); + visible = it->bundle() && it->bundle()->matchesParsedExpression( parsed, defaultColumns ); + } + else + { + visible = it->bundle() && it->bundle()->matchesSimpleExpression( filter, defaultColumns ); + } + } + else + { + visible = setFilter(filter, it); + if(it->type()==MediaItem::PLAYLISTSROOT || it->type()==MediaItem::PLAYLIST) + { + visible = true; + } + else if(it->type()==MediaItem::DIRECTORY) + { + bool match = true; + QStringList list = QStringList::split( " ", filter ); + for( QStringList::iterator i = list.begin(); + i != list.end(); + ++i ) + { + if( !(*it).text(0).contains( *i ) ) + { + match = false; + break; + } + } + if( match ) + visible = true; + } + } + if( filter.isEmpty() ) + visible = true; + it->setVisible( visible ); + if(visible) + childrenVisible = true; + } + + if( root && m_device ) + m_device->updateRootItems(); + + return childrenVisible; +} + +MediaDevice::MediaDevice() + : Amarok::Plugin() + , m_hasMountPoint( true ) + , m_autoDeletePodcasts( false ) + , m_syncStats( false ) + , m_transcode( false ) + , m_transcodeAlways( false ) + , m_transcodeRemove( false ) + , sysProc ( 0 ) + , m_parent( 0 ) + , m_view( 0 ) + , m_wait( false ) + , m_requireMount( false ) + , m_canceled( false ) + , m_transferring( false ) + , m_deleting( false ) + , m_deferredDisconnect( false ) + , m_scheduledDisconnect( false ) + , m_transfer( true ) + , m_configure( true ) + , m_customButton( false ) + , m_playlistItem( 0 ) + , m_podcastItem( 0 ) + , m_invisibleItem( 0 ) + , m_staleItem( 0 ) + , m_orphanedItem( 0 ) +{ + sysProc = new KShellProcess(); Q_CHECK_PTR(sysProc); +} + +void MediaDevice::init( MediaBrowser* parent ) +{ + m_parent = parent; + if( !m_view ) + m_view = new MediaView( m_parent->m_views, this ); + m_view->hide(); +} + +MediaDevice::~MediaDevice() +{ + delete m_view; + delete sysProc; +} + +bool +MediaDevice::isSpecialItem( MediaItem *item ) +{ + return (item == m_playlistItem) || + (item == m_podcastItem) || + (item == m_invisibleItem) || + (item == m_staleItem) || + (item == m_orphanedItem); +} + +void +MediaDevice::loadConfig() +{ + m_transcode = configBool( "Transcode" ); + m_transcodeAlways = configBool( "TranscodeAlways" ); + m_transcodeRemove = configBool( "TranscodeRemove" ); + m_preconnectcmd = configString( "PreConnectCommand" ); + if( m_preconnectcmd.isEmpty() ) + m_preconnectcmd = configString( "MountCommand" ); + m_postdisconnectcmd = configString( "PostDisconnectCommand" ); + if( m_postdisconnectcmd.isEmpty() ) + m_postdisconnectcmd = configString( "UmountCommand" ); + if( m_requireMount && m_postdisconnectcmd.isEmpty() ) + m_postdisconnectcmd = "kdeeject -q %d"; +} + +QString +MediaDevice::configString( const QString &name, const QString &defValue ) +{ + QString configName = "MediaDevice"; + if( !uniqueId().isEmpty() ) + configName += '_' + uniqueId(); + KConfig *config = Amarok::config( configName ); + return config->readEntry( name, defValue ); +} + +void +MediaDevice::setConfigString( const QString &name, const QString &value ) +{ + QString configName = "MediaDevice"; + if( !uniqueId().isEmpty() ) + configName += '_' + uniqueId(); + KConfig *config = Amarok::config( configName ); + config->writeEntry( name, value ); +} + +bool +MediaDevice::configBool( const QString &name, bool defValue ) +{ + QString configName = "MediaDevice"; + if( !uniqueId().isEmpty() ) + configName += '_' + uniqueId(); + KConfig *config = Amarok::config( configName ); + return config->readBoolEntry( name, defValue ); +} + +void +MediaDevice::setConfigBool( const QString &name, bool value ) +{ + QString configName = "MediaDevice"; + if( !uniqueId().isEmpty() ) + configName += '_' + uniqueId(); + KConfig *config = Amarok::config( configName ); + config->writeEntry( name, value ); +} + +MediaView * +MediaDevice::view() +{ + return m_view; +} + +void +MediaDevice::hideProgress() +{ + m_parent->m_progressBox->hide(); +} + +void +MediaDevice::updateRootItems() +{ + if(m_podcastItem) + m_podcastItem->setVisible(m_podcastItem->childCount() > 0); + if(m_invisibleItem) + m_invisibleItem->setVisible(m_invisibleItem->childCount() > 0); + if(m_staleItem) + m_staleItem->setVisible(m_staleItem->childCount() > 0); + if(m_orphanedItem) + m_orphanedItem->setVisible(m_orphanedItem->childCount() > 0); +} + +void +MediaQueue::syncPlaylist( const QString &name, const QString &query, bool loading ) +{ + MediaItem* item = new MediaItem( this, lastItem() ); + item->setType( MediaItem::PLAYLIST ); + item->setExpandable( false ); + item->setData( query ); + item->m_playlistName = name; + item->setText( 0, name ); + item->m_flags |= MediaItem::SmartPlaylist; + m_parent->m_progress->setTotalSteps( m_parent->m_progress->totalSteps() + 1 ); + itemCountChanged(); + if( !loading ) + URLsAdded(); +} + +void +MediaQueue::syncPlaylist( const QString &name, const KURL &url, bool loading ) +{ + MediaItem* item = new MediaItem( this, lastItem() ); + item->setType( MediaItem::PLAYLIST ); + item->setExpandable( false ); + item->setData( url.url() ); + item->m_playlistName = name; + item->setText( 0, name ); + m_parent->m_progress->setTotalSteps( m_parent->m_progress->totalSteps() + 1 ); + itemCountChanged(); + if( !loading ) + URLsAdded(); +} + +BundleList +MediaDevice::bundlesToSync( const QString &name, const KURL &url ) +{ + BundleList bundles; + if( !PlaylistFile::isPlaylistFile( url ) ) + { + Amarok::StatusBar::instance()->longMessage( i18n( "Not a playlist file: %1" ).arg( url.path() ), + KDE::StatusBar::Sorry ); + return bundles; + } + + PlaylistFile playlist( url.path() ); + if( playlist.isError() ) + { + Amarok::StatusBar::instance()->longMessage( i18n( "Failed to load playlist: %1" ).arg( url.path() ), + KDE::StatusBar::Sorry ); + return bundles; + } + + for( BundleList::iterator it = playlist.bundles().begin(); + it != playlist.bundles().end(); + ++it ) + { + bundles += MetaBundle( (*it).url() ); + } + preparePlaylistForSync( name, bundles ); + return bundles; +} + +BundleList +MediaDevice::bundlesToSync( const QString &name, const QString &query ) +{ + const QStringList values = CollectionDB::instance()->query( query ); + + BundleList bundles; + for( for_iterators( QStringList, values ); it != end; ++it ) + bundles += CollectionDB::instance()->bundleFromQuery( &it ); + preparePlaylistForSync( name, bundles ); + return bundles; +} + +void +MediaDevice::preparePlaylistForSync( const QString &name, const BundleList &bundles ) +{ + if( ! m_playlistItem ) // might be syncing a new playlist from the playlist browser + return; + MediaItem *pl = m_playlistItem->findItem( name ); + if( pl ) + { + MediaItem *next = 0; + for( MediaItem *it = static_cast<MediaItem *>(pl->firstChild()); + it; + it = next ) + { + next = static_cast<MediaItem *>(it->nextSibling()); + const MetaBundle *bundle = (*it).bundle(); + if( !bundle ) + continue; + if( isOnOtherPlaylist( name, *bundle ) ) + continue; + if( isInBundleList( bundles, *bundle ) ) + continue; + deleteItemFromDevice( it ); + } + deleteItemFromDevice( pl, None ); + } + purgeEmptyItems(); +} + +bool +MediaDevice::bundleMatch( const MetaBundle &b1, const MetaBundle &b2 ) +{ + if( b1.track() != b2.track() ) + return false; + if( b1.title() != b2.title() ) + return false; + if( b1.album() != b2.album() ) + return false; + if( b1.artist() != b2.artist() ) + return false; +#if 0 + if( b1.discNumber() != b2.discNumber() ) + return false; + if( b1.composer() != b2.composer() ) + return false; +#endif + + return true; +} + +bool +MediaDevice::isInBundleList( const BundleList &bundles, const MetaBundle &b ) +{ + for( BundleList::const_iterator it = bundles.begin(); + it != bundles.end(); + ++it ) + { + if( bundleMatch( b, *it ) ) + return true; + } + + return false; +} + +bool +MediaDevice::isOnOtherPlaylist( const QString &playlistToAvoid, const MetaBundle &bundle ) +{ + for( MediaItem *it = static_cast<MediaItem *>(m_playlistItem->firstChild()); + it; + it = static_cast<MediaItem *>(it->nextSibling()) ) + { + if( it->text( 0 ) == playlistToAvoid ) + continue; + if( isOnPlaylist( *it, bundle ) ) + return true; + } + + return false; +} + +bool +MediaDevice::isOnPlaylist( const MediaItem &playlist, const MetaBundle &bundle ) +{ + for( MediaItem *it = static_cast<MediaItem *>(playlist.firstChild()); + it; + it = static_cast<MediaItem *>(it->nextSibling()) ) + { + const MetaBundle *b = (*it).bundle(); + if( !b ) + continue; + if( bundleMatch( *b, bundle ) ) + return true; + } + + return false; +} + +void +MediaQueue::addURL( const KURL& url2, MetaBundle *bundle, const QString &playlistName ) +{ + KURL url = Amarok::mostLocalURL( url2 ); + + if( PlaylistFile::isPlaylistFile( url ) ) + { + QString name = url.path().section( "/", -1 ).section( ".", 0, -2 ).replace( "_", " " ); + PlaylistFile playlist( url.path() ); + + if( playlist.isError() ) + { + Amarok::StatusBar::instance()->longMessage( i18n( "Failed to load playlist: %1" ).arg( url.path() ), + KDE::StatusBar::Sorry ); + return; + } + + for( BundleList::iterator it = playlist.bundles().begin(); + it != playlist.bundles().end(); + ++it ) + { + addURL( (*it).url(), 0, name ); + } + return; + } + else if( ContextBrowser::hasContextProtocol( url ) ) + { + KURL::List urls = ContextBrowser::expandURL( url ); + + for( KURL::List::iterator it = urls.begin(); + it != urls.end(); + ++it ) + { + addURL( *it ); + } + return; + } + else if( url.protocol() == "file" && QFileInfo( url.path() ).isDir() ) + { + KURL::List urls = Amarok::recursiveUrlExpand( url ); + foreachType( KURL::List, urls ) + addURL( *it ); + return; + } + + if( playlistName.isNull() ) + { + for( MediaItem *it = static_cast<MediaItem *>(firstChild()); + it; + it = static_cast<MediaItem *>(it->nextSibling()) ) + { + if( it->url() == url ) + { + Amarok::StatusBar::instance()->shortMessage( + i18n( "Track already queued for transfer: %1" ).arg( url.url() ) ); + return; + } + } + } + + if(!bundle) + bundle = new MetaBundle( url ); + + MediaItem* item = new MediaItem( this, lastItem() ); + item->setExpandable( false ); + item->setDropEnabled( true ); + item->setBundle( bundle ); + if(bundle->podcastBundle() ) + { + item->setType( MediaItem::PODCASTITEM ); + } + item->m_playlistName = playlistName; + + QString text = item->bundle()->prettyTitle(); + if( text.isEmpty() || (!item->bundle()->isValidMedia() && !item->bundle()->podcastBundle()) ) + text = item->bundle()->url().prettyURL(); + if( !item->m_playlistName.isNull() ) + { + text += " (" + item->m_playlistName + ')'; + } + item->setText( 0, text); + + m_parent->updateButtons(); + m_parent->m_progress->setTotalSteps( m_parent->m_progress->totalSteps() + 1 ); + addItemToSize( item ); + itemCountChanged(); +} + +void +MediaQueue::addURL( const KURL &url, MediaItem *item ) +{ + DEBUG_BLOCK + MediaItem *newitem = new MediaItem( this, lastItem() ); + newitem->setExpandable( false ); + newitem->setDropEnabled( true ); + MetaBundle *bundle = new MetaBundle( *item->bundle() ); + KURL filepath(url); + filepath.addPath( bundle->filename() ); + bundle->setUrl( filepath ); + newitem->m_device = item->m_device; + if(bundle->podcastBundle() ) + { + item->setType( MediaItem::PODCASTITEM ); + } + QString text = item->bundle()->prettyTitle(); + if( text.isEmpty() || (!item->bundle()->isValidMedia() && !item->bundle()->podcastBundle()) ) + text = item->bundle()->url().prettyURL(); + if( item->m_playlistName != QString::null ) + { + text += " (" + item->m_playlistName + ')'; + } + newitem->setText( 0, text); + newitem->setBundle( bundle ); + m_parent->updateButtons(); + m_parent->m_progress->setTotalSteps( m_parent->m_progress->totalSteps() + 1 ); + addItemToSize( item ); + itemCountChanged(); + +} + +void +MediaQueue::addURLs( const KURL::List urls, const QString &playlistName ) +{ + KURL::List::ConstIterator it = urls.begin(); + for ( ; it != urls.end(); ++it ) + addURL( *it, 0, playlistName ); + + URLsAdded(); +} + +void +MediaQueue::URLsAdded() +{ + m_parent->updateStats(); + m_parent->updateButtons(); + if( m_parent->currentDevice() + && m_parent->currentDevice()->isConnected() + && m_parent->currentDevice()->asynchronousTransfer() + && !m_parent->currentDevice()->isTransferring() ) + m_parent->currentDevice()->transferFiles(); + + save( Amarok::saveLocation() + "transferlist.xml" ); +} + +void +MediaDevice::copyTrackFromDevice( MediaItem *item ) +{ + debug() << "copyTrackFromDevice: not copying " << item->url() << ": not implemented" << endl; +} + +QDragObject * +MediaQueue::dragObject() +{ + KURL::List urls; + for( QListViewItem *it = firstChild(); it; it = it->nextSibling() ) + { + if( it->isVisible() && it->isSelected() && dynamic_cast<MediaItem *>(it) ) + urls += static_cast<MediaItem *>(it)->url(); + } + + KMultipleDrag *md = new KMultipleDrag( viewport() ); + QDragObject *d = KListView::dragObject(); + KURLDrag* urldrag = new KURLDrag( urls, viewport() ); + md->addDragObject( d ); + md->addDragObject( urldrag ); + md->setPixmap( CollectionDB::createDragPixmap( urls ), + QPoint( CollectionDB::DRAGPIXMAP_OFFSET_X, CollectionDB::DRAGPIXMAP_OFFSET_Y ) ); + return md; +} + +QString +MediaDevice::replaceVariables( const QString &cmd ) +{ + QString result = cmd; + result.replace( "%d", deviceNode() ); + result.replace( "%m", mountPoint() ); + return result; +} + +int MediaDevice::runPreConnectCommand() +{ + if( m_preconnectcmd.isEmpty() ) + return 0; + + QString cmd = replaceVariables( m_preconnectcmd ); + + debug() << "running pre-connect command: [" << cmd << "]" << endl; + int e=sysCall(cmd); + debug() << "pre-connect: e=" << e << endl; + return e; +} + +int MediaDevice::runPostDisconnectCommand() +{ + if( m_postdisconnectcmd.isEmpty() ) + return 0; + + QString cmd = replaceVariables( m_postdisconnectcmd ); + debug() << "running post-disconnect command: [" << cmd << "]" << endl; + int e=sysCall(cmd); + debug() << "post-disconnect: e=" << e << endl; + + return e; +} + +int MediaDevice::sysCall( const QString &command ) +{ + if ( sysProc->isRunning() ) return -1; + + sysProc->clearArguments(); + (*sysProc) << command; + if (!sysProc->start( KProcess::Block, KProcess::AllOutput )) + kdFatal() << i18n("could not execute %1").arg(command.local8Bit().data()) << endl; + + return (sysProc->exitStatus()); +} + +void +MediaDevice::abortTransfer() +{ + setCanceled( true ); + cancelTransfer(); +} + +bool +MediaDevice::kioCopyTrack( const KURL &src, const KURL &dst ) +{ + m_wait = true; + + KIO::FileCopyJob *job = KIO::file_copy( src, dst, + -1 /* permissions */, + false /* overwrite */, + false /* resume */, + false /* show progress */ ); + connect( job, SIGNAL( result( KIO::Job * ) ), + this, SLOT( fileTransferred( KIO::Job * ) ) ); + + bool tryToRemove = false; + while ( m_wait ) + { + if( isCanceled() ) + { + job->kill( false /* still emit result */ ); + tryToRemove = true; + m_wait = false; + } + else + { + usleep(10000); + kapp->processEvents( 100 ); + } + } + + if( !tryToRemove ) + { + if(m_copyFailed) + { + tryToRemove = true; + Amarok::StatusBar::instance()->longMessage( + i18n( "Media Device: Copying %1 to %2 failed" ) + .arg( src.prettyURL(), dst.prettyURL() ), + KDE::StatusBar::Error ); + } + else + { + MetaBundle bundle2(dst); + if(!bundle2.isValidMedia() && bundle2.filesize()==MetaBundle::Undetermined) + { + tryToRemove = true; + // probably s.th. went wrong + Amarok::StatusBar::instance()->longMessage( + i18n( "Media Device: Reading tags from %1 failed" ).arg( dst.prettyURL() ), + KDE::StatusBar::Error ); + } + } + } + + if( tryToRemove ) + { + QFile::remove( dst.path() ); + return false; + } + + return true; +} + +void +MediaDevice::fileTransferred( KIO::Job *job ) //SLOT +{ + if(job->error()) + { + m_copyFailed = true; + debug() << "file transfer failed: " << job->errorText() << endl; + } + else + { + m_copyFailed = false; + } + + m_wait = false; +} + +void +MediaBrowser::cancelClicked() +{ + DEBUG_BLOCK + + m_waitForTranscode = false; + if( currentDevice() ) + currentDevice()->abortTransfer(); +} + +void +MediaBrowser::transferClicked() +{ + m_toolbar->getButton(TRANSFER)->setEnabled( false ); + if( currentDevice() + && currentDevice()->isConnected() + && !currentDevice()->isTransferring() ) + { + if( !currentDevice()->hasTransferDialog() ) + currentDevice()->transferFiles(); + else + { + currentDevice()->runTransferDialog(); + //may not work with non-TransferDialog-class object, but maybe some run time introspection could solve it? + if( currentDevice()->getTransferDialog() && + ( reinterpret_cast<TransferDialog *>(currentDevice()->getTransferDialog()))->isAccepted() ) + currentDevice()->transferFiles(); + else + updateButtons(); + } + } + currentDevice()->m_transferDir = currentDevice()->m_medium.mountPoint(); +} + +void +MediaBrowser::connectClicked() +{ + bool haveToConfig = false; + // it was just clicked, so isOn() == true. + if( currentDevice() && !currentDevice()->isConnected() ) + { + haveToConfig = !currentDevice()->connectDevice(); + } + + haveToConfig |= !currentDevice(); + haveToConfig |= ( currentDevice() && !currentDevice()->isConnected() ); + + if ( !currentDevice()->needsManualConfig() ) + haveToConfig = false; + + if( haveToConfig && *m_devices.at( 0 ) == currentDevice() ) + { + if( config() && currentDevice() && !currentDevice()->isConnected() ) + currentDevice()->connectDevice(); + } + + updateDevices(); + updateButtons(); + updateStats(); +} + + +void +MediaBrowser::disconnectClicked() +{ + if( currentDevice() && currentDevice()->isTransferring() ) + { + int action = KMessageBox::questionYesNoCancel( MediaBrowser::instance(), + i18n( "Transfer in progress. Finish or stop after current track?" ), + i18n( "Stop Transfer?" ), + KGuiItem(i18n("&Finish"), "goto"), + KGuiItem(i18n("&Stop"), "player_eject") ); + if( action == KMessageBox::Cancel ) + { + return; + } + else if( action == KMessageBox::Yes ) + { + currentDevice()->scheduleDisconnect(); + return; + } + } + + m_toolbar->getButton(TRANSFER)->setEnabled( false ); + m_toolbar->getButton(DISCONNECT)->setEnabled( false ); + + if( currentDevice() ) + { + currentDevice()->disconnectDevice( true ); + } + + updateDevices(); + updateButtons(); + updateStats(); +} + +void +MediaBrowser::customClicked() +{ + if( currentDevice() ) + currentDevice()->customClicked(); +} + +bool +MediaDevice::connectDevice( bool silent ) +{ + if( !lockDevice( true ) ) + return false; + + runPreConnectCommand(); + openDevice( silent ); + + if( isConnected() + && MediaBrowser::instance()->currentDevice() != this + && MediaBrowser::instance()->currentDevice() + && !MediaBrowser::instance()->currentDevice()->isConnected() ) + { + MediaBrowser::instance()->activateDevice( this ); + } + m_parent->updateStats(); + m_parent->updateButtons(); + + if( !isConnected() ) + { + unlockDevice(); + return false; + } + + if( m_syncStats ) + { + syncStatsFromDevice( 0 ); + Scrobbler::instance()->m_submitter->syncComplete(); + } + + // delete podcasts already played + if( m_autoDeletePodcasts && m_podcastItem ) + { + QPtrList<MediaItem> list; + //NOTE we assume that currentItem is the main target + int numFiles = m_view->getSelectedLeaves( m_podcastItem, &list, MediaView::OnlyPlayed ); + + if(numFiles > 0) + { + m_parent->m_stats->setText( i18n( "1 track to be deleted", "%n tracks to be deleted", numFiles ) ); + + setProgress( 0, numFiles ); + + int numDeleted = deleteItemFromDevice( m_podcastItem, true ); + purgeEmptyItems(); + if( numDeleted < 0 ) + { + Amarok::StatusBar::instance()->longMessage( + i18n( "Failed to purge podcasts already played" ), + KDE::StatusBar::Sorry ); + } + else if( numDeleted > 0 ) + { + Amarok::StatusBar::instance()->shortMessage( + i18n( "Purged 1 podcasts already played", + "Purged %n podcasts already played", + numDeleted ) ); + } + + synchronizeDevice(); + + QTimer::singleShot( 1500, m_parent->m_progressBox, SLOT(hide()) ); + m_parent->queue()->computeSize(); + m_parent->updateStats(); + } + } + unlockDevice(); + + updateRootItems(); + + if( m_deferredDisconnect ) + { + m_deferredDisconnect = false; + disconnectDevice( m_runDisconnectHook ); + } + + Amarok::StatusBar::instance()->shortMessage( i18n( "Device successfully connected" ) ); + + m_parent->updateDevices(); + + return true; +} + +bool +MediaDevice::disconnectDevice( bool postDisconnectHook ) +{ + DEBUG_BLOCK + + abortTransfer(); + + debug() << "disconnecting: hook=" << postDisconnectHook << endl; + + if( !lockDevice( true ) ) + { + m_runDisconnectHook = postDisconnectHook; + m_deferredDisconnect = true; + debug() << "disconnecting: locked" << endl; + return false; + } + debug() << "disconnecting: ok" << endl; + + if( m_syncStats ) + { + syncStatsToDevice(); + } + + closeDevice(); + unlockDevice(); + + m_parent->updateStats(); + + bool result = true; + if( postDisconnectHook && runPostDisconnectCommand() != 0 ) + { + Amarok::StatusBar::instance()->longMessage( + i18n( "Post-disconnect command failed, before removing device, please make sure that it is safe to do so." ), + KDE::StatusBar::Information ); + result = false; + } + else + Amarok::StatusBar::instance()->shortMessage( i18n( "Device successfully disconnected" ) ); + + m_parent->updateDevices(); + + return result; +} + +void +MediaDevice::syncStatsFromDevice( MediaItem *root ) +{ + MediaItem *it = static_cast<MediaItem *>( m_view->firstChild() ); + if( root ) + { + it = static_cast<MediaItem *>( root->firstChild() ); + } + + kapp->processEvents( 100 ); + + for( ; it; it = static_cast<MediaItem *>( it->nextSibling() ) ) + { + switch( it->type() ) + { + case MediaItem::TRACK: + if( !it->parent() || static_cast<MediaItem *>( it->parent() )->type() != MediaItem::PLAYLIST ) + { + const MetaBundle *bundle = it->bundle(); + for( int i=0; i<it->recentlyPlayed(); i++ ) + { + // submit to last.fm + if( bundle->length() > 30 + && !bundle->artist().isEmpty() && bundle->artist() != i18n( "Unknown" ) + && !bundle->title().isEmpty() && bundle->title() != i18n( "Unknown" ) ) + { + // don't submit tracks shorter than 30 sec or w/o artist/title + debug() << "scrobbling " << bundle->artist() << " - " << bundle->title() << endl; + SubmitItem *sit = new SubmitItem( bundle->artist(), bundle->album(), bundle->title(), bundle->length(), false /* fake time */ ); + Scrobbler::instance()->m_submitter->submitItem( sit ); + } + + // increase Amarok playcount + QString url = CollectionDB::instance()->getURL( *bundle ); + if( url != QString::null ) + { + QDateTime t = it->playTime(); + CollectionDB::instance()->addSongPercentage( url, 100, "mediadevice", t.isValid() ? &t : 0 ); + debug() << "played " << url << endl; + } + } + + if( it->ratingChanged() ) + { + // copy rating from media device to Amarok + QString url = CollectionDB::instance()->getURL( *bundle ); + debug() << "rating changed " << url << ": " << it->rating()/10 << endl; + if( url != QString::null ) + { + CollectionDB::instance()->setSongRating( url, it->rating()/10 ); + it->setRating( it->rating() ); // prevent setting it again next time + } + } + } + break; + case MediaItem::PODCASTITEM: + if( !it->parent() || static_cast<MediaItem *>( it->parent() )->type() != MediaItem::PLAYLIST ) + { + const MetaBundle *bundle = it->bundle(); + if( it->played() || it->recentlyPlayed() ) + { + if( PodcastEpisodeBundle *peb = bundle->podcastBundle() ) + { + debug() << "marking podcast episode as played: " << peb->url() << endl; + if( PlaylistBrowser::instance() ) + { + PodcastEpisode *p = PlaylistBrowser::instance()->findPodcastEpisode( peb->url(), peb->parent() ); + if ( p ) + p->setListened(); + else + debug() << "did not find podcast episode: " << peb->url() << " from " << peb->parent() << endl; + } + } + } + } + break; + + default: + syncStatsFromDevice( it ); + break; + } + } +} + +void +MediaItem::syncStatsFromPath( const QString &url ) +{ + if( url.isEmpty() ) + return; + + // copy Amarok rating, play count and last played time to device + int rating = CollectionDB::instance()->getSongRating( url )*10; + if( rating ) + setRating( rating ); + int playcount = CollectionDB::instance()->getPlayCount( url ); + if( playcount > played() ) + setPlayCount( playcount ); + QDateTime lastplay = CollectionDB::instance()->getLastPlay( url ); + if( lastplay > playTime() ) + setLastPlayed( lastplay.toTime_t() ); +} + +void +MediaDevice::syncStatsToDevice( MediaItem *root ) +{ + MediaItem *it = static_cast<MediaItem *>( m_view->firstChild() ); + if( root ) + { + it = static_cast<MediaItem *>( root->firstChild() ); + } + + kapp->processEvents( 100 ); + + for( ; it; it = static_cast<MediaItem *>( it->nextSibling() ) ) + { + switch( it->type() ) + { + case MediaItem::TRACK: + if( !it->parent() || static_cast<MediaItem *>( it->parent() )->type() != MediaItem::PLAYLIST ) + { + const MetaBundle *bundle = it->bundle(); + QString url = CollectionDB::instance()->getURL( *bundle ); + it->syncStatsFromPath( url ); + } + break; + + case MediaItem::PODCASTITEM: + if( !it->parent() || static_cast<MediaItem *>( it->parent() )->type() != MediaItem::PLAYLIST ) + { + const MetaBundle *bundle = it->bundle(); + if( PodcastEpisodeBundle *peb = bundle->podcastBundle() ) + { + if( PlaylistBrowser::instance() ) + { + PodcastEpisode *p = PlaylistBrowser::instance()->findPodcastEpisode( peb->url(), peb->parent() ); + if( p ) + it->setListened( !p->isNew() ); + } + } + } + break; + + default: + syncStatsToDevice( it ); + break; + } + } +} + +void +MediaDevice::transferFiles() +{ + if( !lockDevice( true ) ) + { + return; + } + + setCanceled( false ); + + m_transferring = true; + m_parent->m_toolbar->getButton(MediaBrowser::TRANSFER)->setEnabled( false ); + + setProgress( 0, m_parent->m_queue->childCount() ); + + // ok, let's copy the stuff to the device + + KURL::List existing, unplayable; + unsigned transcodeFail = 0; + // iterate through items + MediaItem *next = static_cast<MediaItem *>(m_parent->m_queue->firstChild()); + while( next ) + { + MediaItem *transferredItem = next; + transferredItem->setFailed( false ); + transferredItem->m_flags |= MediaItem::Transferring; + next = static_cast<MediaItem *>( transferredItem->nextSibling() ); + + if( transferredItem->device() ) + { + transferredItem->device()->copyTrackFromDevice( transferredItem ); + m_parent->m_queue->subtractItemFromSize( transferredItem, true ); + delete transferredItem; + setProgress( progress() + 1 ); + m_parent->m_queue->itemCountChanged(); + kapp->processEvents( 100 ); + continue; + } + + BundleList bundles; + if( transferredItem->type() == MediaItem::PLAYLIST ) + { + if( transferredItem->flags() & MediaItem::SmartPlaylist ) + bundles = bundlesToSync( transferredItem->text( 0 ), transferredItem->data() ); + else + bundles = bundlesToSync( transferredItem->text( 0 ), KURL::fromPathOrURL( transferredItem->data() ) ); + } + else if( transferredItem->bundle() ) + bundles += *transferredItem->bundle(); + else + { + // this should not happen + debug() << "invalid item in transfer queue" << endl; + m_parent->m_queue->subtractItemFromSize( transferredItem, true ); + delete transferredItem; + m_parent->m_queue->itemCountChanged(); + continue; + } + + if( bundles.count() > 1 ) + setProgress( progress(), MediaBrowser::instance()->m_progress->totalSteps() + bundles.count() - 1 ); + + QString playlist = transferredItem->m_playlistName; + for( BundleList::const_iterator it = bundles.begin(); + it != bundles.end(); + ++it ) + { + if( isCanceled() ) + break; + + const MetaBundle *bundle = &(*it); + + bool transcoding = false; + MediaItem *item = trackExists( *bundle ); + if( item && playlist.isEmpty() ) + { + Amarok::StatusBar::instance()->shortMessage( i18n( "Track already on media device: %1" ). + arg( (*it).url().prettyURL() ), + KDE::StatusBar::Sorry ); + existing += (*it).url(); + setProgress( progress() + 1 ); + continue; + } + else if( !item ) // the item does not yet exist on the media device + { + if( m_transcode && ( !isPlayable( *bundle ) || m_transcodeAlways ) ) + { + QString preferred = supportedFiletypes().isEmpty() ? "mp3" : supportedFiletypes().first(); + debug() << "transcoding " << bundle->url() << " to " << preferred << endl; + KURL transcoded = MediaBrowser::instance()->transcode( bundle->url(), preferred ); + if( isCanceled() ) + break; + if( transcoded.isEmpty() ) + { + debug() << "transcoding failed" << endl; + transcodeFail++; + } + else + { + transcoding = true; + MetaBundle *transcodedBundle = new MetaBundle( transcoded ); + transcodedBundle->setArtist( bundle->artist() ); + transcodedBundle->setTitle( bundle->title() ); + transcodedBundle->setComposer( bundle->composer() ); + transcodedBundle->setAlbum( bundle->album() ); + transcodedBundle->setGenre( bundle->genre() ); + transcodedBundle->setComment( bundle->comment() ); + transcodedBundle->setYear( bundle->year() ); + transcodedBundle->setDiscNumber( bundle->discNumber() ); + transcodedBundle->setTrack( bundle->track() ); + if( bundle->podcastBundle() ) + { + transcodedBundle->setPodcastBundle( *bundle->podcastBundle() ); + transcodedBundle->copyFrom( *bundle->podcastBundle() ); + //change the extension on the localUrl in the podcastbundle + //to make sure it ends up with the correct extension + //in the generic mediadevice. + KURL localUrl = transcodedBundle->podcastBundle()->localUrl(); + QString filename = QFileInfo( localUrl.path() ).baseName() + '.' + preferred; + localUrl.setFileName( filename ); + transcodedBundle->podcastBundle()->setLocalURL( localUrl ); + } + bundle = transcodedBundle; + } + } + + if( !isPlayable( *bundle ) ) + { + Amarok::StatusBar::instance()->shortMessage( i18n( "Track not playable on media device: %1" ).arg( bundle->url().path() ), + KDE::StatusBar::Sorry ); + unplayable += (*it).url(); + transferredItem->setFailed(); + if( transcoding ) + { + delete bundle; + bundle = 0; + } + setProgress( progress() + 1 ); + continue; + } + item = copyTrackToDevice( *bundle ); + } + + if( !item ) // copyTrackToDevice() failed + { + if( !isCanceled() ) + { + Amarok::StatusBar::instance()->longMessage( + i18n( "Failed to copy track to media device: %1" ).arg( bundle->url().path() ), + KDE::StatusBar::Sorry ); + transferredItem->setFailed(); + } + } + + if( transcoding ) + { + if( m_transcodeRemove ) + QFile( bundle->url().path() ).remove(); + + delete bundle; + bundle = 0; + } + + if( isCanceled() ) + break; + + if( !item ) + { + setProgress( progress() + 1 ); + continue; + } + + item->syncStatsFromPath( (*it).url().path() ); + + if( m_playlistItem && !playlist.isEmpty() ) + { + MediaItem *pl = m_playlistItem->findItem( playlist ); + if( !pl ) + { + QPtrList<MediaItem> items; + pl = newPlaylist( playlist, m_playlistItem, items ); + } + if( pl ) + { + QPtrList<MediaItem> items; + items.append( item ); + addToPlaylist( pl, pl->lastChild(), items ); + } + } + + setProgress( progress() + 1 ); + } + + transferredItem->m_flags &= ~MediaItem::Transferring; + + if( isCanceled() ) + { + m_parent->updateStats(); + break; + } + + if( !(transferredItem->flags() & MediaItem::Failed) ) + { + m_parent->m_queue->subtractItemFromSize( transferredItem, true ); + delete transferredItem; + m_parent->m_queue->itemCountChanged(); + } + m_parent->updateStats(); + + kapp->processEvents( 100 ); + } + synchronizeDevice(); + unlockDevice(); + fileTransferFinished(); + + QString msg; + if( unplayable.count() > 0 ) + { + msg = i18n( "One track not playable on media device", + "%n tracks not playable on media device", unplayable.count() ); + } + if( existing.count() > 0 ) + { + if( msg.isEmpty() ) + msg = i18n( "One track already on media device", + "%n tracks already on media device", existing.count() ); + else + msg += i18n( ", one track already on media device", + ", %n tracks already on media device", existing.count() ); + } + if( transcodeFail > 0 ) + { + if( msg.isEmpty() ) + msg = i18n( "One track was not transcoded", + "%n tracks were not transcoded", transcodeFail ); + else + msg += i18n( ", one track was not transcoded", + ", %n tracks were not transcoded", transcodeFail ); + + const ScriptManager* const sm = ScriptManager::instance(); + if( !sm->transcodeScriptRunning().isEmpty() ) + msg += i18n( " (no transcode script running)" ); + } + + if( unplayable.count() + existing.count() > 0 ) + { + QString longMsg = i18n( "The following tracks were not transferred: "); + for( KURL::List::Iterator it = existing.begin(); + it != existing.end(); + it++ ) + { + longMsg += "<br>" + (*it).prettyURL(); + } + for( KURL::List::Iterator it = unplayable.begin(); + it != unplayable.end(); + it++ ) + { + longMsg += "<br>" + (*it).prettyURL(); + } + Amarok::StatusBar::instance()->shortLongMessage( msg, longMsg, KDE::StatusBar::Sorry ); + } + else if( !msg.isEmpty() ) + { + Amarok::StatusBar::instance()->shortMessage( msg, KDE::StatusBar::Sorry ); + } + + m_parent->updateButtons(); + m_parent->queue()->save( Amarok::saveLocation() + "transferlist.xml" ); + m_transferring = false; + + if( m_deferredDisconnect ) + { + m_deferredDisconnect = false; + disconnectDevice( m_runDisconnectHook ); + } + else if( m_scheduledDisconnect ) + { + disconnectDevice( true ); + } + m_scheduledDisconnect = false; +} + +int +MediaDevice::progress() const +{ + return m_parent->m_progress->progress(); +} + +void +MediaDevice::setProgress( const int progress, const int total ) +{ + if( total != -1 ) + m_parent->m_progress->setTotalSteps( total ); + m_parent->m_progress->setProgress( progress ); + m_parent->m_progressBox->show(); +} + +void +MediaDevice::fileTransferFinished() //SLOT +{ + m_parent->updateStats(); + m_parent->m_progressBox->hide(); + m_parent->m_toolbar->getButton(MediaBrowser::TRANSFER)->setEnabled( isConnected() && m_parent->queue()->childCount() > 0 ); + m_wait = false; +} + + +int +MediaDevice::deleteFromDevice(MediaItem *item, int flags ) +{ + MediaItem* fi = item; + int count = 0; + + if ( !(flags & Recursing) ) + { + if( !lockDevice( true ) ) + return 0; + + setCanceled( false ); + + m_deleting = true; + + QPtrList<MediaItem> list; + //NOTE we assume that currentItem is the main target + int numFiles = m_view->getSelectedLeaves(item, &list, MediaView::OnlySelected | ((flags & OnlyPlayed) ? MediaView::OnlyPlayed : MediaView::None) ); + + m_parent->m_stats->setText( i18n( "1 track to be deleted", "%n tracks to be deleted", numFiles ) ); + if( numFiles > 0 && (flags & DeleteTrack) ) + { + int button = KMessageBox::warningContinueCancel( m_parent, + i18n( "<p>You have selected 1 track to be <b>irreversibly</b> deleted.", + "<p>You have selected %n tracks to be <b>irreversibly</b> deleted.", + numFiles + ), + QString::null, + KGuiItem(i18n("&Delete"),"editdelete") ); + + if ( button != KMessageBox::Continue ) + { + m_parent->queue()->computeSize(); + m_parent->updateStats(); + m_deleting = false; + unlockDevice(); + return 0; + } + + if(!isTransferring()) + { + setProgress( 0, numFiles ); + } + + } + // don't return if numFiles==0: playlist items might be to delete + + if( !fi ) + fi = static_cast<MediaItem*>(m_view->firstChild()); + } + + while( fi ) + { + MediaItem *next = static_cast<MediaItem*>(fi->nextSibling()); + + if( isCanceled() ) + { + break; + } + + if( !fi->isVisible() ) + { + fi = next; + continue; + } + + if( fi->isSelected() ) + { + int ret = deleteItemFromDevice(fi, flags); + if( ret >= 0 && count >= 0 ) + count += ret; + else + count = -1; + } + else + { + if( fi->childCount() ) + { + int ret = deleteFromDevice( static_cast<MediaItem*>(fi->firstChild()), flags | Recursing ); + if( ret >= 0 && count >= 0 ) + count += ret; + else + count = -1; + } + } + m_parent->updateStats(); + + fi = next; + } + + if(!(flags & Recursing)) + { + purgeEmptyItems(); + synchronizeDevice(); + m_deleting = false; + unlockDevice(); + + if(!isTransferring()) + { + QTimer::singleShot( 1500, m_parent->m_progressBox, SLOT(hide()) ); + } + + if( m_deferredDisconnect ) + { + m_deferredDisconnect = false; + disconnectDevice( m_runDisconnectHook ); + } + } + m_parent->queue()->computeSize(); + m_parent->updateStats(); + + return count; +} + +void +MediaDevice::purgeEmptyItems( MediaItem *root ) +{ + MediaItem *it = 0; + if( root ) + { + it = static_cast<MediaItem *>(root->firstChild()); + } + else + { + it = static_cast<MediaItem *>(m_view->firstChild()); + } + + MediaItem *next = 0; + for( ; it; it=next ) + { + next = static_cast<MediaItem *>(it->nextSibling()); + purgeEmptyItems( it ); + if( it->childCount() == 0 && + (it->type() == MediaItem::ARTIST || + it->type() == MediaItem::ALBUM || + it->type() == MediaItem::PODCASTCHANNEL) ) + delete it; + } +} + +void +MediaQueue::save( const QString &path ) +{ + QFile file( path ); + + if( !file.open( IO_WriteOnly ) ) return; + + QDomDocument newdoc; + QDomElement transferlist = newdoc.createElement( "playlist" ); + transferlist.setAttribute( "product", "Amarok" ); + transferlist.setAttribute( "version", APP_VERSION ); + newdoc.appendChild( transferlist ); + + for( const MediaItem *item = static_cast<MediaItem *>( firstChild() ); + item; + item = static_cast<MediaItem *>( item->nextSibling() ) ) + { + QDomElement i = newdoc.createElement("item"); + i.setAttribute("url", item->url().url()); + + if( item->bundle() ) + { + QDomElement attr = newdoc.createElement( "Title" ); + QDomText t = newdoc.createTextNode( item->bundle()->title() ); + attr.appendChild( t ); + i.appendChild( attr ); + + attr = newdoc.createElement( "Artist" ); + t = newdoc.createTextNode( item->bundle()->artist() ); + attr.appendChild( t ); + i.appendChild( attr ); + + attr = newdoc.createElement( "Album" ); + t = newdoc.createTextNode( item->bundle()->album() ); + attr.appendChild( t ); + i.appendChild( attr ); + + attr = newdoc.createElement( "Year" ); + t = newdoc.createTextNode( QString::number( item->bundle()->year() ) ); + attr.appendChild( t ); + i.appendChild( attr ); + + attr = newdoc.createElement( "Comment" ); + t = newdoc.createTextNode( item->bundle()->comment() ); + attr.appendChild( t ); + i.appendChild( attr ); + + attr = newdoc.createElement( "Genre" ); + t = newdoc.createTextNode( item->bundle()->genre() ); + attr.appendChild( t ); + i.appendChild( attr ); + + attr = newdoc.createElement( "Track" ); + t = newdoc.createTextNode( QString::number( item->bundle()->track() ) ); + attr.appendChild( t ); + i.appendChild( attr ); + } + + if(item->type() == MediaItem::PODCASTITEM) + { + i.setAttribute( "podcast", "1" ); + } + + if(item->type() == MediaItem::PODCASTITEM + && item->bundle()->podcastBundle()) + { + PodcastEpisodeBundle *peb = item->bundle()->podcastBundle(); + QDomElement attr = newdoc.createElement( "PodcastDescription" ); + QDomText t = newdoc.createTextNode( peb->description() ); + attr.appendChild( t ); + i.appendChild( attr ); + + attr = newdoc.createElement( "PodcastAuthor" ); + t = newdoc.createTextNode( peb->author() ); + attr.appendChild( t ); + i.appendChild( attr ); + + attr = newdoc.createElement( "PodcastRSS" ); + t = newdoc.createTextNode( peb->parent().url() ); + attr.appendChild( t ); + i.appendChild( attr ); + + attr = newdoc.createElement( "PodcastURL" ); + t = newdoc.createTextNode( peb->url().url() ); + attr.appendChild( t ); + i.appendChild( attr ); + } + + if(item->m_playlistName != QString::null) + { + i.setAttribute( "playlist", item->m_playlistName ); + } + + if(item->type() == MediaItem::PLAYLIST) + { + i.setAttribute( "playlistdata", item->data() ); + if( item->flags() & MediaItem::SmartPlaylist ) + i.setAttribute( "smartplaylist", "1" ); + } + + transferlist.appendChild( i ); + } + + QTextStream stream( &file ); + stream.setEncoding( QTextStream::UnicodeUTF8 ); + stream << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"; + stream << newdoc.toString(); +} + + +void +MediaQueue::load( const QString& filename ) +{ + QFile file( filename ); + if( !file.open( IO_ReadOnly ) ) { + return; + } + + clearItems(); + + QTextStream stream( &file ); + stream.setEncoding( QTextStream::UnicodeUTF8 ); + + QDomDocument d; + QString er; + int l, c; + if( !d.setContent( stream.read(), &er, &l, &c ) ) { // return error values + Amarok::StatusBar::instance()->longMessageThreadSafe( i18n( + //TODO add a link to the path to the playlist + "The XML in the transferlist was invalid. Please report this as a bug to the Amarok " + "developers. Thank you." ), KDE::StatusBar::Error ); + error() << "[TRANSFERLISTLOADER]: Error loading xml file: " << filename << "(" << er << ")" + << " at line " << l << ", column " << c << endl; + return; + } + + QValueList<QDomNode> nodes; + const QString ITEM( "item" ); //so we don't construct this QString all the time + for( QDomNode n = d.namedItem( "playlist" ).firstChild(); !n.isNull(); n = n.nextSibling() ) + { + if( n.nodeName() != ITEM ) continue; + + QDomElement elem = n.toElement(); + if( !elem.isNull() ) + nodes += n; + + if( !elem.hasAttribute( "url" ) ) + { + continue; + } + KURL url(elem.attribute("url")); + + bool podcast = elem.hasAttribute( "podcast" ); + PodcastEpisodeBundle peb; + if( url.isLocalFile() ) + peb.setLocalURL( url ); + MetaBundle *bundle = new MetaBundle( url ); + for(QDomNode node = elem.firstChild(); + !node.isNull(); + node = node.nextSibling()) + { + if(node.firstChild().isNull()) + continue; + + if(node.nodeName() == "Title" ) + bundle->setTitle(node.firstChild().toText().nodeValue()); + else if(node.nodeName() == "Artist" ) + bundle->setArtist(node.firstChild().toText().nodeValue()); + else if(node.nodeName() == "Album" ) + bundle->setAlbum(node.firstChild().toText().nodeValue()); + else if(node.nodeName() == "Year" ) + bundle->setYear(node.firstChild().toText().nodeValue().toUInt()); + else if(node.nodeName() == "Genre" ) + bundle->setGenre(node.firstChild().toText().nodeValue()); + else if(node.nodeName() == "Comment" ) + bundle->setComment(node.firstChild().toText().nodeValue()); + else if(node.nodeName() == "PodcastDescription" ) + peb.setDescription( node.firstChild().toText().nodeValue() ); + else if(node.nodeName() == "PodcastAuthor" ) + peb.setAuthor( node.firstChild().toText().nodeValue() ); + else if(node.nodeName() == "PodcastRSS" ) + peb.setParent( KURL::fromPathOrURL( node.firstChild().toText().nodeValue() ) ); + else if(node.nodeName() == "PodcastURL" ) + peb.setURL( KURL::fromPathOrURL( node.firstChild().toText().nodeValue() ) ); + } + + if( podcast ) + { + bundle->setPodcastBundle( peb ); + } + + QString playlist = elem.attribute( "playlist" ); + QString playlistdata = elem.attribute( "playlistdata" ); + if( !playlistdata.isEmpty() ) + { + QString smart = elem.attribute( "smartplaylist" ); + if( smart.isEmpty() ) + syncPlaylist( playlist, KURL::fromPathOrURL( playlistdata ), true ); + else + syncPlaylist( playlist, playlistdata, true ); + } + else + addURL( url, bundle, playlist ); + } + + URLsAdded(); +} + +bool +MediaDevice::isPlayable( const MetaBundle &bundle ) +{ + if( supportedFiletypes().isEmpty() ) + return true; + + QString type = bundle.url().path().section( ".", -1 ).lower(); + return supportedFiletypes().contains( type ); +} + +bool +MediaDevice::isPreferredFormat( const MetaBundle &bundle ) +{ + if( supportedFiletypes().isEmpty() ) + return true; + + QString type = bundle.url().path().section( ".", -1 ).lower(); + return ( type == supportedFiletypes().first() ); +} + + +MediaQueue::MediaQueue(MediaBrowser *parent) + : KListView( parent ), m_parent( parent ) +{ + setFixedHeight( 200 ); + setSelectionMode( QListView::Extended ); + setItemsMovable( true ); + setDragEnabled( true ); + setShowSortIndicator( false ); + setSorting( -1 ); + setFullWidth( true ); + setRootIsDecorated( false ); + setDropVisualizer( true ); //the visualizer (a line marker) is drawn when dragging over tracks + setDropHighlighter( true ); //and the highligther (a focus rect) is drawn when dragging over playlists + setDropVisualizerWidth( 3 ); + setAcceptDrops( true ); + addColumn( i18n( "Transfer Queue" ) ); + + itemCountChanged(); + + KActionCollection* ac = new KActionCollection( this ); + KStdAction::selectAll( this, SLOT( selectAll() ), ac, "MediaQueue" ); + + connect( this, SIGNAL( contextMenuRequested( QListViewItem*, const QPoint&, int ) ), + SLOT( slotShowContextMenu( QListViewItem*, const QPoint&, int ) ) ); + connect( this, SIGNAL( dropped(QDropEvent*, QListViewItem*, QListViewItem*) ), + SLOT( slotDropped(QDropEvent*, QListViewItem*, QListViewItem*) ) ); +} + +bool +MediaQueue::acceptDrag( QDropEvent *e ) const +{ + QString data; + QCString subtype; + QTextDrag::decode( e, data, subtype ); + + return e->source() == viewport() + || subtype == "amarok-sql" + || KURLDrag::canDecode( e ); +} + +void +MediaQueue::slotDropped( QDropEvent* e, QListViewItem* parent, QListViewItem* after) +{ + if( e->source() != viewport() ) + { + QString data; + QCString subtype; + QTextDrag::decode( e, data, subtype ); + KURL::List list; + + if( subtype == "amarok-sql" ) + { + QString playlist = data.section( "\n", 0, 0 ); + QString query = data.section( "\n", 1 ); + QStringList values = CollectionDB::instance()->query( query ); + list = CollectionDB::instance()->URLsFromSqlDrag( values ); + addURLs( list, playlist ); + } + else if ( KURLDrag::decode( e, list ) ) + { + addURLs( list ); + } + } + else if( QListViewItem *i = currentItem() ) + { + moveItem( i, parent, after ); + } +} + +void +MediaQueue::dropProxyEvent( QDropEvent *e ) +{ + slotDropped( e, 0, 0 ); +} + +MediaItem* +MediaQueue::findPath( QString path ) +{ + for( QListViewItem *item = firstChild(); + item; + item = item->nextSibling()) + { + if(static_cast<MediaItem *>(item)->url().path() == path) + return static_cast<MediaItem *>(item); + } + + return 0; +} + +void +MediaQueue::computeSize() const +{ + m_totalSize = 0; + for( QListViewItem *it = firstChild(); + it; + it = it->nextSibling()) + { + MediaItem *item = static_cast<MediaItem *>(it); + + if( item && item->bundle() && + ( !m_parent->currentDevice() + || !m_parent->currentDevice()->isConnected() + || !m_parent->currentDevice()->trackExists(*item->bundle()) ) ) + m_totalSize += ((item->size()+1023)/1024)*1024; + } +} + +KIO::filesize_t +MediaQueue::totalSize() const +{ + return m_totalSize; +} + +void +MediaQueue::addItemToSize( const MediaItem *item ) const +{ + if( item && item->bundle() && + ( !m_parent->currentDevice() + || !m_parent->currentDevice()->isConnected() + || !m_parent->currentDevice()->trackExists(*item->bundle()) ) ) + m_totalSize += ((item->size()+1023)/1024)*1024; +} + +void +MediaQueue::subtractItemFromSize( const MediaItem *item, bool unconditionally ) const +{ + if( item && item->bundle() && + ( !m_parent->currentDevice() + || !m_parent->currentDevice()->isConnected() + || (unconditionally || !m_parent->currentDevice()->trackExists(*item->bundle())) ) ) + m_totalSize -= ((item->size()+1023)/1024)*1024; +} + +void +MediaQueue::removeSelected() +{ + QPtrList<QListViewItem> selected = selectedItems(); + + for( QListViewItem *item = selected.first(); item; item = selected.next() ) + { + if( !(static_cast<MediaItem *>(item)->flags() & MediaItem::Transferring) ) + { + subtractItemFromSize( static_cast<MediaItem *>(item) ); + delete item; + if( m_parent->currentDevice() && m_parent->currentDevice()->isTransferring() ) + { + MediaBrowser::instance()->m_progress->setTotalSteps( MediaBrowser::instance()->m_progress->totalSteps() - 1 ); + } + } + } + + MediaBrowser::instance()->updateStats(); + MediaBrowser::instance()->updateButtons(); + itemCountChanged(); +} + +void +MediaQueue::keyPressEvent( QKeyEvent *e ) +{ + if( e->key() == Key_Delete ) + removeSelected(); + else + KListView::keyPressEvent( e ); +} + +void +MediaQueue::itemCountChanged() +{ + if( childCount() == 0 ) + hide(); + else if( !isShown() ) + show(); +} + +void +MediaQueue::slotShowContextMenu( QListViewItem* item, const QPoint& point, int ) +{ + if( !childCount() ) + return; + + KPopupMenu menu( this ); + + enum Actions { REMOVE_SELECTED, CLEAR_ALL, START_TRANSFER }; + + if( item ) + menu.insertItem( SmallIconSet( Amarok::icon( "remove_from_playlist" ) ), i18n( "&Remove From Queue" ), REMOVE_SELECTED ); + + menu.insertItem( SmallIconSet( Amarok::icon( "playlist_clear" ) ), i18n( "&Clear Queue" ), CLEAR_ALL ); + menu.insertItem( SmallIconSet( Amarok::icon( "playlist_refresh" ) ), i18n( "&Start Transfer" ), START_TRANSFER ); + menu.setItemEnabled( START_TRANSFER, + MediaBrowser::instance()->currentDevice() && + MediaBrowser::instance()->currentDevice()->isConnected() && + MediaBrowser::instance()->currentDevice()->m_transfer ); + + switch( menu.exec( point ) ) + { + case REMOVE_SELECTED: + removeSelected(); + break; + case CLEAR_ALL: + clearItems(); + break; + case START_TRANSFER: + MediaBrowser::instance()->transferClicked(); + break; + } +} + +void +MediaQueue::clearItems() +{ + clear(); + itemCountChanged(); + if(m_parent) + { + computeSize(); + m_parent->updateStats(); + m_parent->updateButtons(); + } +} + + +#include "mediabrowser.moc" diff --git a/amarok/src/mediabrowser.h b/amarok/src/mediabrowser.h new file mode 100644 index 00000000..31eef055 --- /dev/null +++ b/amarok/src/mediabrowser.h @@ -0,0 +1,668 @@ +// (c) 2004 Christian Muehlhaeuser <chris@chris.de> +// (c) 2005 Martin Aumueller <aumuell@reserv.at> +// (c) 2005 Seb Ruiz <me@sebruiz.net> +// (c) 2006 T.R.Shashwath <trshash84@gmail.com> +// See COPYING file for licensing information + +#ifndef AMAROK_MEDIABROWSER_H +#define AMAROK_MEDIABROWSER_H + +#include "amarok.h" +#include "amarok_export.h" +#include "browserToolBar.h" +#include "medium.h" +#include "multitabbar.h" //baseclass +#include "plugin/plugin.h" //baseclass +#include "pluginmanager.h" + +#include <qmutex.h> +#include <qvbox.h> //baseclass +#include <qdatetime.h> + +#include <klistview.h> //baseclass +#include <kurl.h> //stack allocated +#include <kio/global.h> //filesize_t +#include "scrobbler.h" //SubmitItem +#include "metabundle.h" + +class MediaBrowser; +class MediaDevice; +class MediaItemTip; +class MediaView; +class SpaceLabel; +class TransferDialog; + +class KAction; +class KComboBox; +class KDialogBase; +class KProgress; +class KPushButton; +class KShellProcess; + +class QDragObject; +class QLabel; +class QPalette; + +class LIBAMAROK_EXPORT MediaItem : public KListViewItem +{ + public: + MediaItem( QListView* parent ); + MediaItem( QListViewItem* parent ); + MediaItem( QListView* parent, QListViewItem* after ); + MediaItem( QListViewItem* parent, QListViewItem* after ); + void init(); + virtual ~MediaItem(); + + MediaItem *lastChild() const; + + virtual KURL url() const; + const MetaBundle *bundle() const; + void setBundle( MetaBundle *bundle ); + + enum Type { UNKNOWN, ARTIST, ALBUM, TRACK, PODCASTSROOT, PODCASTCHANNEL, + PODCASTITEM, PLAYLISTSROOT, PLAYLIST, PLAYLISTITEM, INVISIBLEROOT, + INVISIBLE, STALEROOT, STALE, ORPHANEDROOT, ORPHANED, DIRECTORY }; + + enum Flags { Failed=1, BeginTransfer=2, StopTransfer=4, Transferring=8, SmartPlaylist=16 }; + + void setType( Type type ); + void setFailed( bool failed=true ); + Type type() const { return m_type; } + MediaItem *findItem(const QString &key, const MediaItem *after=0) const; + const QString &data() const { return m_data; } + void setData( const QString &data ) { m_data = data; } + + virtual bool isLeafItem() const; // A leaf node of the tree + virtual bool isFileBacked() const; // Should the file be deleted of the device when removed + virtual QDateTime playTime() const { return QDateTime(); } + virtual int played() const { return 0; } + virtual int recentlyPlayed() const { return 0; } // no of times played on device since last sync + virtual void setPlayCount( int ) {} + virtual int rating() const { return 0; } // rating on device, normalized to 100 + virtual void setRating( int /*rating*/ ) {} + virtual bool ratingChanged() const { return false; } + virtual void setLastPlayed( uint ) {} + virtual void syncStatsFromPath( const QString &path ); + virtual long size() const; + virtual MediaDevice *device() const { return m_device; } + virtual bool listened() const { return m_listened; } + virtual void setListened( bool listened=true ) { m_listened = listened; } + + int compare( QListViewItem *i, int col, bool ascending ) const; + int flags() const { return m_flags; } + + void paintCell( QPainter *p, const QColorGroup &cg, int column, int width, int align ); + + //attributes: + int m_order; + Type m_type; + QString m_playlistName; + QString m_data; + MediaDevice *m_device; + int m_flags; + bool m_listened; + + static QPixmap *s_pixUnknown; + static QPixmap *s_pixRootItem; + static QPixmap *s_pixFile; + static QPixmap *s_pixArtist; + static QPixmap *s_pixAlbum; + static QPixmap *s_pixPlaylist; + static QPixmap *s_pixPodcast; + static QPixmap *s_pixTrack; + static QPixmap *s_pixInvisible; + static QPixmap *s_pixStale; + static QPixmap *s_pixOrphaned; + static QPixmap *s_pixDirectory; + static QPixmap *s_pixTransferFailed; + static QPixmap *s_pixTransferBegin; + static QPixmap *s_pixTransferEnd; + + private: + mutable MetaBundle *m_bundle; +}; + +class MediaQueue : public KListView, public DropProxyTarget +{ + Q_OBJECT + + public: + MediaQueue(MediaBrowser *parent); + MediaItem *findPath( QString path ); + + KIO::filesize_t totalSize() const; // total size of items to transfer in KB + void computeSize() const; // compute total size of items to transfer in KB + void addItemToSize( const MediaItem *item ) const; + void subtractItemFromSize( const MediaItem *item, bool unconditonally=false ) const; + + void removeSelected(); + void clearItems(); + + void load( const QString &path ); + void save( const QString &path ); + void syncPlaylist( const QString &playlistName, const QString &sql, bool loading=false ); + void syncPlaylist( const QString &playlistName, const KURL &url, bool loading=false ); + void addURL( const KURL& url, MetaBundle *bundle=NULL, const QString &playlistName=QString::null ); + void addURL( const KURL& url, MediaItem *item ); + void addURLs( const KURL::List urls, const QString &playlistName=QString::null ); + + void URLsAdded(); // call after finishing adding single urls + + void dropProxyEvent( QDropEvent *e ); + // Reimplemented from KListView + bool acceptDrag( QDropEvent *e ) const; + QDragObject *dragObject(); + + public slots: + void itemCountChanged(); + + private slots: + void selectAll() {QListView::selectAll(true); } + void slotShowContextMenu( QListViewItem* item, const QPoint& point, int ); + void slotDropped (QDropEvent* e, QListViewItem* parent, QListViewItem* after); + + private: + void keyPressEvent( QKeyEvent *e ); + MediaBrowser *m_parent; + mutable KIO::filesize_t m_totalSize; +}; + + +class MediaBrowser : public QVBox +{ + Q_OBJECT + friend class DeviceConfigureDialog; + friend class MediaDevice; + friend class MediaView; + friend class MediaQueue; + friend class MediumPluginChooser; + friend class MediaItem; + + public: + enum { CONNECT, DISCONNECT, TRANSFER, CONFIGURE, CUSTOM }; + + static bool isAvailable(); + LIBAMAROK_EXPORT static MediaBrowser *instance() { return s_instance; } + LIBAMAROK_EXPORT static MediaQueue *queue() { return s_instance ? s_instance->m_queue : 0; } + + MediaBrowser( const char *name ); + virtual ~MediaBrowser(); + bool blockQuit() const; + MediaDevice *currentDevice() const; + MediaDevice *deviceFromId( const QString &id ) const; + QStringList deviceNames() const; + bool deviceSwitch( const QString &name ); + + QString getInternalPluginName ( const QString string ) { return m_pluginName[string]; } + QString getDisplayPluginName ( const QString string ) { return m_pluginAmarokName[string]; } + const KTrader::OfferList &getPlugins() { return m_plugins; } + void transcodingFinished( const QString &src, const QString &dst ); + bool isTranscoding() const { return m_waitForTranscode; } + void updateStats(); + void updateButtons(); + void updateDevices(); + // return bundle for url if it is known to MediaBrowser + bool getBundle( const KURL &url, MetaBundle *bundle ) const; + bool isQuitting() const { return m_quitting; } + + KURL getProxyUrl( const KURL& daapUrl ) const; + KToolBar* getToolBar() const { return m_toolbar; } + + signals: + void availabilityChanged( bool isAvailable ); + + protected slots: + void transferClicked(); + + private slots: + void slotSetFilterTimeout(); + void slotSetFilter(); + void slotSetFilter( const QString &filter ); + void slotEditFilter(); + void mediumAdded( const Medium *, QString , bool constructing = false); + void mediumChanged( const Medium *, QString ); + void mediumRemoved( const Medium *, QString ); + void activateDevice( const MediaDevice *device ); + void activateDevice( int index, bool skipDummy = true ); + void pluginSelected( const Medium *, const QString ); + void showPluginManager(); + void cancelClicked(); + void connectClicked(); + void disconnectClicked(); + void customClicked(); + void configSelectPlugin( int index ); + bool config(); // false if canceled by user + KURL transcode( const KURL &src, const QString &filetype ); + void tagsChanged( const MetaBundle &bundle ); + void prepareToQuit(); + + private: + MediaDevice *loadDevicePlugin( const QString &deviceName ); + void unloadDevicePlugin( MediaDevice *device ); + + KLineEdit* m_searchEdit; + QTimer *m_timer; + LIBAMAROK_EXPORT static MediaBrowser *s_instance; + + QValueList<MediaDevice *> m_devices; + QValueList<MediaDevice *>::iterator m_currentDevice; + + QMap<QString, QString> m_pluginName; + QMap<QString, QString> m_pluginAmarokName; + void addDevice( MediaDevice *device ); + void removeDevice( MediaDevice *device ); + + MediaQueue* m_queue; + bool m_waitForTranscode; + KURL m_transcodedUrl; + QString m_transcodeSrc; + + SpaceLabel* m_stats; + QHBox* m_progressBox; + KProgress* m_progress; + QVBox* m_views; + KPushButton* m_cancelButton; + //KPushButton* m_playlistButton; + QVBox* m_configBox; + KComboBox* m_configPluginCombo; + KComboBox* m_deviceCombo; + Browser::ToolBar*m_toolbar; + typedef QMap<QString, MediaItem*> ItemMap; + mutable QMutex m_itemMapMutex; + ItemMap m_itemMap; + KTrader::OfferList m_plugins; + bool m_haveDevices; + bool m_quitting; +}; + +class MediaView : public KListView +{ + Q_OBJECT + friend class MediaBrowser; + friend class MediaDevice; + + public: + enum Flags + { + None = 0, + OnlySelected = 1, + OnlyPlayed = 2 + }; + + MediaView( QWidget *parent, MediaDevice *device ); + virtual ~MediaView(); + LIBAMAROK_EXPORT KURL::List nodeBuildDragList( MediaItem* item, int flags=OnlySelected ); + int getSelectedLeaves(MediaItem *parent, QPtrList<MediaItem> *list, int flags=OnlySelected ); + LIBAMAROK_EXPORT MediaItem *newDirectory( MediaItem* parent ); + bool setFilter( const QString &filter, MediaItem *parent=NULL ); + + private slots: + void rmbPressed( QListViewItem*, const QPoint&, int ); + void renameItem( QListViewItem *item ); + void slotExpand( QListViewItem* ); + void selectAll() { QListView::selectAll(true); } + void invokeItem( QListViewItem*, const QPoint &, int column ); + void invokeItem( QListViewItem* ); + + private: + void keyPressEvent( QKeyEvent *e ); + // Reimplemented from KListView + void contentsDropEvent( QDropEvent *e ); + void viewportPaintEvent( QPaintEvent* ); + bool acceptDrag( QDropEvent *e ) const; + QDragObject *dragObject(); + + QWidget *m_parent; + MediaDevice *m_device; + MediaItemTip *m_toolTip; +}; + + +/* at least the pure virtual functions have to be implemented by a media device, + all items are stored in a hierarchy of MediaItems, + when items are manipulated the MediaItems have to be updated accordingly */ + +class LIBAMAROK_EXPORT MediaDevice : public QObject, public Amarok::Plugin +{ + Q_OBJECT + friend class DeviceConfigureDialog; + friend class TransferDialog; + friend class MediaBrowser; + friend class MediaView; + friend class MediaQueue; + + public: + enum Flags + { + None = 0, + OnlyPlayed = 1, + DeleteTrack = 2, + Recursing = 4 + }; + + MediaDevice(); + virtual void init( MediaBrowser* parent ); + virtual ~MediaDevice(); + + MediaView *view(); + + /** + * @retrun a KAction that will be plugged into the media device browser toolbar + */ + virtual KAction *customAction() { return 0; } + + virtual void rmbPressed( QListViewItem *item, const QPoint &point, int ) { (void)item; (void) point; } + + /** + * @return list of filetypes playable on this device + * (empty list is interpreted as all types are good) + */ + virtual QStringList supportedFiletypes() { return QStringList(); } + + /** + * @param bundle describes track that should be checked + * @return true if the device is capable of playing the track referred to by bundle + */ + virtual bool isPlayable( const MetaBundle &bundle ); + + /** + * @param bundle describes track that should be checked + * @return true if the track is in the preferred (first in list) format of the device + */ + virtual bool isPreferredFormat( const MetaBundle &bundle ); + + /** + * @return true if the device is connected + */ + virtual bool isConnected() = 0; + + /** + * Adds particular tracks to a playlist + * @param playlist parent playlist for tracks to be added to + * @param after insert following this item + * @param items tracks to add to playlist + */ + virtual void addToPlaylist(MediaItem *playlist, MediaItem *after, QPtrList<MediaItem> items) { Q_UNUSED(playlist); Q_UNUSED(after); Q_UNUSED(items); } + + /** + * Create a new playlist + * @param name playlist title + * @param parent parent MediaItem of the new playlist + * @param items tracks to add to the new playlist + * @return the newly created playlist + */ + virtual MediaItem *newPlaylist(const QString &name, MediaItem *parent, QPtrList<MediaItem> items) { Q_UNUSED(name); Q_UNUSED(parent); Q_UNUSED(items); return 0; } + + /** + * Move items to a directory + * @param directory new parent of dropped items + * @param items tracks to add to the directory + */ + virtual void addToDirectory( MediaItem *directory, QPtrList<MediaItem> items ) { Q_UNUSED(directory); Q_UNUSED(items); } + + /** + * Create a new directory + * @param name directory title + * @param parent parent MediaItem of the new directory + * @param items tracks to add to the new directory + * @return the newly created directory + */ + virtual MediaItem *newDirectory( const QString &name, MediaItem *parent ) { Q_UNUSED(name); Q_UNUSED(parent); return 0; } + + /** + * Notify device of changed tags + * @param item item to be updated + * @param changed bundle containing new tags + * @return the changed MediaItem + */ + virtual MediaItem *tagsChanged( MediaItem *item, const MetaBundle &changed ) { Q_UNUSED(item); Q_UNUSED(changed); return 0; } + + /** + * Indicate whether the device has a custom transfer dialog + * @return whether there is a custom dialog + */ + virtual bool hasTransferDialog() { return false; } + + /** + * Run the transfer dialog to be used when Transfer is clicked + */ + virtual void runTransferDialog() {} + + /** + * Get the transfer dialog, if any + * @return the transfer dialog, if any, else NULL; + */ + virtual TransferDialog *getTransferDialog() { return NULL; } + + /** + * Can be used to explicitly indicate whether a device needs manual configuration + * @return whether manual configuration is needed + */ + virtual bool needsManualConfig() { return true; } + + virtual void addConfigElements( QWidget * /*parent*/ ) {} + virtual void removeConfigElements( QWidget * /*parent*/ ) {} + virtual void applyConfig() {} + virtual void loadConfig(); + + QString configString( const QString &name, const QString &defValue = QString::null ); + void setConfigString( const QString &name, const QString &value ); + bool configBool( const QString &name, bool defValue=false ); + void setConfigBool( const QString &name, bool value ); + + void setRequireMount( const bool b ) { m_requireMount = b; } + bool hasMountPoint() { return m_hasMountPoint; } + void setDeviceType( const QString &type ) { m_type = type; } + QString deviceType() { return m_type; } + virtual bool autoConnect() { return false; } + virtual bool asynchronousTransfer() { return false; } + bool isTransferring() { return m_transferring; } + bool isDeleting() { return m_deleting; } + bool isCanceled() { return m_canceled; } + void setCanceled( const bool b ) { m_canceled = b; } + + int progress() const; + void setProgress( const int progress, const int total = -1 /* leave total unchanged by default */ ); + void hideProgress(); + + + /** + * @return a unique identifier that is constant across sessions + */ + QString uniqueId() const { return m_medium.id(); } + + /** + * @return the name for the device that should be presented to the user + */ + QString name() const { return m_name; } + + /** + * @return the device node + */ + QString deviceNode() const { return m_medium.deviceNode(); } + + /* + * @return the device mount point (or empty if non-applicable or unknown) + */ + QString mountPoint() const { return m_medium.mountPoint(); } + + QString getTransferDir() { return m_transferDir; } + Medium &getMedium() { return m_medium; } + + void setSpacesToUnderscores( bool yesno ) { m_spacesToUnderscores = yesno; + setConfigBool( "spacesToUnderscores", yesno); } + bool getSpacesToUnderscores() { return m_spacesToUnderscores; } + + void setFirstSort( QString text ) { m_firstSort = text; + setConfigString( "firstGrouping", text ); } + void setSecondSort( QString text ) { m_secondSort = text; + setConfigString( "secondGrouping", text ); } + void setThirdSort( QString text ) { m_thirdSort = text; + setConfigString( "thirdGrouping", text ); } + + virtual KURL getProxyUrl( const KURL& /*url*/) { return KURL(); } + virtual void customClicked() { return; } + + BundleList bundlesToSync( const QString &playlistName, const QString &sql ); + BundleList bundlesToSync( const QString &playlistName, const KURL &url ); + void preparePlaylistForSync( const QString &playlistName, const BundleList &bundles ); + bool isOnOtherPlaylist( const QString &playlistToAvoid, const MetaBundle &bundle ); + bool isOnPlaylist( const MediaItem &playlist, const MetaBundle &bundle ); + bool isInBundleList( const BundleList &bundles, const MetaBundle &bundle ); + bool bundleMatch( const MetaBundle &b1, const MetaBundle &b2 ); + + public slots: + void abortTransfer(); + void transferFiles(); + virtual void renameItem( QListViewItem *item ) {(void)item; } + virtual void expandItem( QListViewItem *item ) {(void)item; } + bool connectDevice( bool silent=false ); + bool disconnectDevice( bool postdisconnecthook=true ); + void scheduleDisconnect() { m_scheduledDisconnect = true; } + + protected slots: + void fileTransferred( KIO::Job *job ); + void fileTransferFinished(); + + private: + int sysCall(const QString & command); + int runPreConnectCommand(); + int runPostDisconnectCommand(); + QString replaceVariables( const QString &cmd ); // replace %m with mount point and %d with device node + + /** + * Find a particular track + * @param bundle The metabundle of the requested media item + * @return The MediaItem of the item if found, otherwise NULL + * @note This may not be worth implementing for non database driven devices, as it could be slow + */ + virtual MediaItem *trackExists( const MetaBundle& bundle ) = 0; + + protected: + /** + * Get the capacity and freespace available on the device, in bytes + * @return true if successful + */ + virtual bool getCapacity( KIO::filesize_t *total, KIO::filesize_t *available ) { Q_UNUSED(total); Q_UNUSED(available); return false; } + + /** + * Lock device for exclusive access if possible + */ + virtual bool lockDevice( bool tryOnly = false ) = 0; + + /** + * Unlock device + */ + virtual void unlockDevice() = 0; + + /** + * Connect to device, and populate m_view with MediaItems + * @return true if successful + */ + virtual bool openDevice( bool silent=false ) = 0; + + /** + * Wrap up any loose ends and close the device + * @return true if successful + */ + virtual bool closeDevice() = 0; + + /** + * Write any pending changes to the device, such as database changes + */ + virtual void synchronizeDevice() = 0; + + /** + * Copy a track to the device + * @param bundle The MetaBundle of the item to transfer. Will move the item specified by bundle().url().path() + * @return If successful, the created MediaItem in the media device view, else 0 + */ + virtual MediaItem *copyTrackToDevice(const MetaBundle& bundle) = 0; + + /** + * Copy track from device to computer + * @param item The MediaItem of the track to transfer. + * @param url The URL to transfer the track to. + * @return The MediaItem transfered. + */ + virtual void copyTrackFromDevice(MediaItem *item); + + /** + * Recursively remove MediaItem from the tracklist and the device + * @param item MediaItem to remove + * @param onlyPlayed True if item should be deleted only if it has been played + * @return -1 on failure, number of files deleted otherwise + */ + virtual int deleteItemFromDevice( MediaItem *item, int flags=DeleteTrack ) = 0; + + /** + * Abort the currently active track transfer + */ + virtual void cancelTransfer() { /* often checking m_cancel is enough */ } + + virtual void updateRootItems(); + + virtual bool isSpecialItem( MediaItem *item ); + + int deleteFromDevice( MediaItem *item=0, int flags=DeleteTrack ); + + void purgeEmptyItems( MediaItem *root=0 ); + void syncStatsFromDevice( MediaItem *root=0 ); + void syncStatsToDevice( MediaItem *root=0 ); + + bool kioCopyTrack( const KURL &src, const KURL &dst ); + + QString m_name; + + bool m_hasMountPoint; + + QString m_preconnectcmd; + QString m_postdisconnectcmd; + bool m_autoDeletePodcasts; + bool m_syncStats; + + bool m_transcode; + bool m_transcodeAlways; + bool m_transcodeRemove; + + KShellProcess *sysProc; + MediaBrowser *m_parent; + MediaView *m_view; + Medium m_medium; + QString m_transferDir; + QString m_firstSort; + QString m_secondSort; + QString m_thirdSort; + bool m_wait; + bool m_waitForDeletion; + bool m_copyFailed; + bool m_requireMount; + bool m_canceled; + bool m_transferring; + bool m_deleting; + bool m_deferredDisconnect; + bool m_scheduledDisconnect; + bool m_runDisconnectHook; + bool m_spacesToUnderscores; + bool m_transfer; + bool m_configure; + bool m_customButton; + + QString m_type; + + // root listview items + MediaItem *m_playlistItem; + MediaItem *m_podcastItem; + // items not on the master playlist and not on the podcast playlist are not visible on the ipod + MediaItem *m_invisibleItem; + // items in the database for which the file is missing + MediaItem *m_staleItem; + // files without database entry + MediaItem *m_orphanedItem; + + // stow away all items below m_rootItems when device is not current + QPtrList<QListViewItem> m_rootItems; +}; + + +#endif /* AMAROK_MEDIABROWSER_H */ diff --git a/amarok/src/mediadevice/Makefile.am b/amarok/src/mediadevice/Makefile.am new file mode 100644 index 00000000..32158127 --- /dev/null +++ b/amarok/src/mediadevice/Makefile.am @@ -0,0 +1,28 @@ +if with_libgpod + IPOD_SUBDIR = ipod +endif + +if with_ifp + IFP_SUBDIR = ifp +endif + +if with_libnjb + NJB_SUBDIR = njb +endif + +if with_libmtp + MTP_SUBDIR = mtp +endif + +if with_libkarma + KARMA_SUBDIR = riokarma +endif + +if with_daap + DAAP_SUBDIR = daap +endif + +METASOURCES = AUTO + +SUBDIRS = generic $(DAAP_SUBDIR) $(IPOD_SUBDIR) $(IFP_SUBDIR) $(NJB_SUBDIR) $(MTP_SUBDIR) $(KARMA_SUBDIR) + diff --git a/amarok/src/mediadevice/daap/Makefile.am b/amarok/src/mediadevice/daap/Makefile.am new file mode 100644 index 00000000..082b4a6d --- /dev/null +++ b/amarok/src/mediadevice/daap/Makefile.am @@ -0,0 +1,45 @@ +kde_module_LTLIBRARIES = libamarok_daap-mediadevice.la +kde_services_DATA = amarok_daap-mediadevice.desktop + +INCLUDES = \ + -I$(top_srcdir)/amarok/src \ + -I$(top_builddir)/amarok/src/amarokcore \ + -I$(top_builddir)/amarok/src \ + -I$(top_srcdir)/amarok/src/amarokcore \ + -I$(top_srcdir)/amarok/src/engine \ + -I$(top_srcdir)/amarok/src/engine \ + -I$(top_srcdir)/amarok/src/mediadevice \ + $(TAGLIB_CFLAGS) \ + $(all_includes) + +METASOURCES = AUTO + +libamarok_daap_mediadevice_la_LIBADD = \ + $(top_builddir)/amarok/src/libamarok.la \ + $(top_builddir)/amarok/src/mediadevice/daap/daapreader/libdaapreader.la \ + $(DNSSD_LIBS) $(LIB_KDEUI) $(LIB_KDECORE) $(LIB_KIO) $(LIB_QT) + +libamarok_daap_mediadevice_la_LDFLAGS = \ + $(KDE_PLUGIN) \ + $(all_libraries) + +libamarok_daap_mediadevice_la_SOURCES = \ + addhostbase.ui \ + daapclient.cpp \ + daapserver.cpp \ + proxy.cpp + +noinst_HEADERS = \ + daapclient.h \ + daapserver.h \ + proxy.h + +bin_SCRIPTS = amarok_daapserver.rb + +SUBDIRS = daapreader mongrel + +amarokrubylibdir = \ + $(kde_datadir)/amarok/ruby_lib + +amarokrubylib_DATA = \ + codes.rb diff --git a/amarok/src/mediadevice/daap/addhostbase.ui b/amarok/src/mediadevice/daap/addhostbase.ui new file mode 100644 index 00000000..5a57d996 --- /dev/null +++ b/amarok/src/mediadevice/daap/addhostbase.ui @@ -0,0 +1,180 @@ +<!DOCTYPE UI><UI version="3.3" stdsetdef="1"> +<class>AddHostBase</class> +<widget class="QWidget"> + <property name="name"> + <cstring>AddHostBase</cstring> + </property> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>503</width> + <height>187</height> + </rect> + </property> + <property name="caption"> + <string>Add Computer</string> + </property> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <property name="margin"> + <number>0</number> + </property> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>layout4</cstring> + </property> + <grid> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLabel" row="0" column="0"> + <property name="name"> + <cstring>m_downloadPixmap</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>5</hsizetype> + <vsizetype>5</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>64</width> + <height>64</height> + </size> + </property> + <property name="text"> + <string></string> + </property> + </widget> + <widget class="KActiveLabel" row="0" column="1" rowspan="2" colspan="1"> + <property name="name"> + <cstring>kActiveLabel1</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>5</hsizetype> + <vsizetype>5</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Amarok can browse music on computers sharing their music via programs such as <a href="http://www.fireflymediaserver.org/">Firefly Media Server</a>, Banshee or iTunes. + +<p>Enter the hostname or IP address of the computer you want to connect to. + +<p>Examples: +<blockquote><strong>mymusic.homelinux.org +<br>192.168.1.21</strong></blockquote></string> + </property> + </widget> + <spacer row="1" column="0"> + <property name="name"> + <cstring>spacer2</cstring> + </property> + <property name="orientation"> + <enum>Vertical</enum> + </property> + <property name="sizeType"> + <enum>Expanding</enum> + </property> + <property name="sizeHint"> + <size> + <width>20</width> + <height>130</height> + </size> + </property> + </spacer> + </grid> + </widget> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>layout5</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLabel"> + <property name="name"> + <cstring>textLabel2</cstring> + </property> + <property name="text"> + <string>Enter host:</string> + </property> + </widget> + <widget class="KLineEdit"> + <property name="name"> + <cstring>m_hostName</cstring> + </property> + </widget> + <spacer> + <property name="name"> + <cstring>spacer2_2</cstring> + </property> + <property name="orientation"> + <enum>Horizontal</enum> + </property> + <property name="sizeType"> + <enum>Fixed</enum> + </property> + <property name="sizeHint"> + <size> + <width>30</width> + <height>20</height> + </size> + </property> + </spacer> + <widget class="QLabel"> + <property name="name"> + <cstring>textLabel1</cstring> + </property> + <property name="text"> + <string>Port:</string> + </property> + </widget> + <widget class="KIntNumInput"> + <property name="name"> + <cstring>m_portInput</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>1</hsizetype> + <vsizetype>0</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="label"> + <string></string> + </property> + <property name="value"> + <number>3689</number> + </property> + <property name="minValue"> + <number>1</number> + </property> + <property name="maxValue"> + <number>65536</number> + </property> + </widget> + </hbox> + </widget> + </vbox> +</widget> +<customwidgets> +</customwidgets> +<layoutdefaults spacing="6" margin="11"/> +<includehints> + <includehint>kactivelabel.h</includehint> + <includehint>klineedit.h</includehint> + <includehint>knuminput.h</includehint> + <includehint>knuminput.h</includehint> +</includehints> +</UI> diff --git a/amarok/src/mediadevice/daap/amarok_daap-mediadevice.desktop b/amarok/src/mediadevice/daap/amarok_daap-mediadevice.desktop new file mode 100644 index 00000000..a3c4b3e5 --- /dev/null +++ b/amarok/src/mediadevice/daap/amarok_daap-mediadevice.desktop @@ -0,0 +1,110 @@ +[Desktop Entry] +Encoding=UTF-8 +Type=Service +Name=Music Sharing +Name[af]=Musiek Deel +Name[ar]=تبادل مشترك للموسيقة +Name[bg]=Споделяне на музика +Name[bn]=সঙ্গীত বিনিময় +Name[ca]=Música compartida +Name[cs]=Sdílení hudby +Name[da]=Musikdeling +Name[de]=Musikfreigabe +Name[el]=Κοινή χρήση μουσικής +Name[eo]=Muziko Fordonado +Name[es]=Cliente DAAP +Name[et]=Muusika jagamine +Name[fa]=اشتراک موسیقی +Name[fi]=Musiikinjako +Name[fr]=Musique partagée +Name[gl]=Compartir Música +Name[hu]=Zenemegosztás +Name[is]=Tónlistardeiling +Name[it]=Condivisione musica +Name[ja]=音楽共有 +Name[ka]=მუსიკის გაზიარება +Name[km]=ការ​ចែករំលែក​តន្ត្រី +Name[lt]=Dalinimasis muzika +Name[mk]=Споделување музика +Name[ms]=Perkongsian Muzik +Name[nb]=Musikkdeling +Name[nds]=Musikfreegaav +Name[ne]=सङ्गीत साझेदारी +Name[nl]=Muziek delen +Name[nn]=Musikkdeling +Name[pa]=ਸੰਗੀਤ ਸਾਂਝ +Name[pl]=Dzielenie się muzyką +Name[pt]=Partilha de Músicas +Name[pt_BR]=Compartilhamento de Música +Name[se]=Musihkkajuogadeapmi +Name[sk]=Zdieľanie hudby +Name[sr]=Дељење музике +Name[sr@Latn]=Deljenje muzike +Name[sv]=Musikdelning +Name[th]=แบ่งปันดนตรี +Name[tr]=Müzik Paylaşımı +Name[uk]=Спільна музика +Name[uz]=Musiqani boshqalar bilan boʻlishish +Name[uz@cyrillic]=Мусиқани бошқалар билан бўлишиш +Name[wa]=Pårtaedjî l' muzike +Name[zh_CN]=音乐共享 +Name[zh_TW]=音樂分享 +X-KDE-Library=libamarok_daap-mediadevice +Comment=Plugin for Amarok +Comment[af]=Inprop module vir Amarok +Comment[ar]= قابس ( برنامج مضاف الى) AmaroK +Comment[bg]=Приставка за Amarok +Comment[bn]=আমারক-এর জন্য প্লাগিন +Comment[br]=Lugent evit Amarok +Comment[ca]=Connector per l'Amarok +Comment[cs]=Modul pro AmaroK +Comment[de]=Modul für Amarok +Comment[el]=Πρόσθετο για το AmaroK +Comment[eo]=Kromaĵo por Amarok +Comment[es]=Extensión para Amarok +Comment[et]=Amaroki plugin +Comment[fa]=وصله برای amaroK +Comment[fi]=Amarok-liitännäinen +Comment[fr]=Module pour Amarok +Comment[ga]=Breiseán AmaroK +Comment[gl]=Extensión para Amarok +Comment[hu]=Bővítőmodul az Amarokhoz +Comment[is]=Íforrit fyrir Amarok +Comment[it]=Plugin per Amarok +Comment[ja]=Amarok のためのプラグイン +Comment[ka]=მოდული Amarok-ისთვის +Comment[km]=កម្មវិធី​ជំនួយ​សម្រាប់ Amarok +Comment[lt]=Amarok įskiepis +Comment[mk]=Приклучок за Амарок +Comment[nb]=Programtillegg for Amarok +Comment[nds]=Moduul för Amarok +Comment[ne]=अमारोकका लागि प्लगइन +Comment[nl]=Plugin voor Amarok +Comment[nn]=Programtillegg for Amarok +Comment[pa]=ਅਮਰੋਕ ਲਈ ਪਲੱਗਇਨ +Comment[pl]=Wtyczka Amaroka +Comment[pt]='Plugin' para o Amarok +Comment[pt_BR]=Plugin para o Amarok +Comment[ru]=Модуль amaroK +Comment[se]=Lassemoduvla Amarok:ii +Comment[sk]=Amarok modul +Comment[sr]=Прикључак за Amarok +Comment[sr@Latn]=Priključak za Amarok +Comment[sv]=Insticksprogram för Amarok +Comment[th]=โปรแกรมเสริมสำหรับ Amarok +Comment[tr]=Amarok için Eklenti +Comment[uk]=Втулок для Amarok +Comment[uz]=Amarok uchun plagin +Comment[uz@cyrillic]=Amarok учун плагин +Comment[wa]=Tchôke-divins po Amarok +Comment[zh_CN]=Amarok 插件 +Comment[zh_TW]=amaroK 插件 +ServiceTypes=Amarok/Plugin + +X-KDE-Amarok-plugintype=mediadevice +X-KDE-Amarok-name=daap-mediadevice +X-KDE-Amarok-authors=Ian Monroe +X-KDE-Amarok-email=ian@monroe.nu +X-KDE-Amarok-rank=100 +X-KDE-Amarok-version=1 +X-KDE-Amarok-framework-version=32 diff --git a/amarok/src/mediadevice/daap/amarok_daapserver.rb b/amarok/src/mediadevice/daap/amarok_daapserver.rb new file mode 100755 index 00000000..27de228b --- /dev/null +++ b/amarok/src/mediadevice/daap/amarok_daapserver.rb @@ -0,0 +1,418 @@ +#!/usr/bin/env ruby + +#A DAAP Server +# (c) 2006 Ian Monroe <ian@monroe.nu> +# License: GNU General Public License V2 +$LOAD_PATH.push(ARGV[0]) +puts "here it is: #{ARGV[0]}" +$LOAD_PATH.push(ARGV[1]) +puts "here it is: #{ARGV[1]}" + +require "codes.rb" +require 'mongrel' +require "#{ARGV[2]}" #debug.rb + +require 'uri' +require 'pp' +#require 'ruby-prof' + +$app_name = "Daap" +$debug_prefix = "Server" + +class Element + attr_accessor :name + + public + def initialize(name, value = Array.new) + @name, @value = name, value + end + + def to_s( codes = nil ) + if @value.nil? then + log @name + ' is null' + @name + Element.long_convert( 0 ) + else + content = valueToString( codes ) + @name + Element.long_convert(content.length) + content + end + end + + def collection? + @value.class == Array + end + + def <<( child ) + @value << child + end + + def size + @value.size + end + + def Element.char_convert( v ) + packing( v, 'c' ) + end + + def Element.short_convert( v ) + packing( v, 'n' ) + end + + def Element.long_convert( v ) + packing( v, 'N' ) + end + + def Element.longlong_convert( v ) + v = v.to_i if( v.is_a?(String) ) + a = Array.new + a[0] = v >> 32 + b = Array.new + b[0] = v & 0xffffffff + a.pack('N') + b.pack('N') + end + + protected + def valueToString( codes ) + case CODE_TYPE[@name] + when :string then + @value + when :long then + Element.long_convert( @value ) + when :container then + values = String.new + @value.each do |i| + values += i.to_s( codes ) + end + values + when :char then + Element.char_convert( @value ) + when :short then + Element.short_convert( @value ) + when :longlong then + Element.longlong_convert( @value ) + when :date then + Element.long_convert( @value ) + when :version then + Element.short_convert( @value ) + else + log "type error! #{@value} #{CODE_TYPE[@name]} #{@name}" + end + end + + def Element.packing( v, packer ) + v = v.to_i if( v.is_a?(String) ) + a = Array.new + a[0] = v + a.pack(packer) + end + +end + +class Mlit < Element + attr_accessor :songformat, :id + def to_s( codes ) + values = String.new + @value.each { |i| + values += i.to_s( codes ) if codes.member?( i.name ) + } + 'mlit' + Element.long_convert(values.length) + values + end +end + +class DatabaseServlet < Mongrel::HttpHandler + include DebugMethods + public + + def initItems + @@sessionId = 42 + artists = Hash.new + albums = Hash.new + genre = Hash.new + year = Hash.new + device_paths = Hash.new + indexes = [ { :dbresult=> query( 'select * from album' ), :indexed => albums }, + { :dbresult=> query( 'select * from artist' ), :indexed => artists }, + { :dbresult=> query( 'select * from genre' ) , :indexed => genre }, + { :dbresult=> query( 'select * from year' ) , :indexed => year }, + { :dbresult=> query( 'select id, lastmountpoint from devices' ), :indexed => device_paths } ] + indexes.each { |h| + 0.step( h[ :dbresult ].size, 2 ) { |i| + h[ :indexed ][ h[ :dbresult ][i].to_i ] = h[ :dbresult ][ i.to_i+1 ] + } + } + + columns = [ "album, ", "artist, ", "genre, ", "year, ", "track, ", "title, ", "length, ", "samplerate, ", "url, ", "deviceid" ] + puts "SQL QUERY: SELECT #{columns.to_s} FROM tags" + @column_keys = [ :songalbum, :songartist, :songgenre, :songyear, :songtracknumber, :itemname, :songtime, :songsamplerate, :url, :deviceid ] + #TODO composer :songcomposer + @music = Array.new + @items = Element.new( 'mlcl' ) + id = 0 + columnIt = 0 + track = Mlit.new( 'mlit' ) + url = String.new + while ( line = $stdin.gets ) && ( line.chop! != '**** END SQL ****' ) + puts "#{columnIt} - #{line}" if id < 10 + case columnIt + when 0..3 + track << Element.new( METAS[ @column_keys[columnIt] ][ :code ], indexes[columnIt][ :indexed ][ line.to_i ] ) + when (4 ..( @column_keys.size-3 ) ) + track << Element.new( METAS[ @column_keys[columnIt] ][ :code ], line ) + when columns.size - 2 + url = line.reverse.chop.reverse + when columns.size - 1 + id += 1 + device_id = line.to_i + if device_id == -1 then + @music[id] = url + else + url[0] = '' + @music[id] = "#{indexes.last[:indexed][ device_id ]}/#{url}" + end + track << Element.new( 'miid', id ) + track << Element.new( 'asfm', File::extname( url ).reverse.chop.reverse ) + @items << track + columnIt = -1 + track = Mlit.new( 'mlit' ) + url = String.new + end + columnIt += 1 + end + @column_keys.push( :itemid ) + @column_keys.push( :songformat ) + end + debugMethod(:new) + + def process( request, response ) + if @items.nil? then + initItems() + end + uri = URI::parse( request.params["REQUEST_URI"] ) + command = File.basename( uri.path ) + output = String.new + case command + #{"mupd"=>{"mstt"=>[200], "musr"=>[2]}} + when "login" then + root = Element.new( 'mlog' ) + root << Element.new( 'mlid', @@sessionId ) + root << Element.new( 'mstt', 200 ) #200, as in the HTTP OK code + write_resp( response, root.to_s ) + @@sessionId += 1 + #{"mupd"=>{"mstt"=>[200], "musr"=>[2]}} + when "update" then + root = Element.new( 'mupd' ) + root << Element.new( 'mstt', 200 ) + root << Element.new( 'musr', 2 ) + write_resp( response, root.to_s ) + when 'server-info' +# SimpleDaapClient output from Banshee server +# {"msrv"=> +# {"mpro"=>[131074], +# "msbr"=>[nil], +# "mslr"=>[nil], +# "msup"=>[nil], +# "msex"=>[nil], +# "msqy"=>[nil], +# "msau"=>[nil], +# "apro"=>[196610], +# "minm"=>["Banshee Music Share"], +# "msdc"=>[1], +# "mstt"=>[200], +# "msal"=>[nil], +# "msrs"=>[nil], +# "mstm"=>[1800], +# "mspi"=>[nil], +# "msix"=>[nil]}} + msrv = Element.new( 'msrv' ) + msrv << Element.new( 'mpro', 0x20002 ) + msrv << Element.new( 'msbr', 1 ) + msrv << Element.new( 'mslr', 1 ) + msrv << Element.new( 'msup', 1 ) + msrv << Element.new( 'msex', 1 ) + msrv << Element.new( 'msqy', 1 ) + msrv << Element.new( 'msau', 0 ) + msrv << Element.new( 'apro', 0x30002 ) + msrv << Element.new( 'minm', "Amarok Music Share" ) + msrv << Element.new( 'msdc', 1 ) + msrv << Element.new( 'mstt', 200 ) + msrv << Element.new( 'msal', 1 ) + msrv << Element.new( 'msrs', 1 ) + msrv << Element.new( 'mstm', 1800 ) + msrv << Element.new( 'mspi', 1 ) + msrv << Element.new( 'msix', 1 ) + write_resp( response, msrv.to_s ) + when 'content-codes' then + write_resp( response, CONTENT_CODES ) #LAAAAAZY + when 'containers' then + # {"aply"=> + # {"muty"=>[nil], + # "mstt"=>[200], + # "mrco"=>[1], + # "mtco"=>[1], + # "mlcl"=> + # [{"mlit"=> + # [{"abpl"=>[nil], + # "miid"=>[1], + # "mper"=>[0], + # "minm"=>["Banshee Music Share"], + # "mimc"=>[2463]}]}]}} + aply = Element.new( 'aply' ) + aply << Element.new( 'muty', nil ) + aply << Element.new( 'mstt', 200 ) + aply << Element.new( 'mrco', 1 ) + mlcl = Element.new( 'mlcl') + aply << mlcl + mlit = Element.new( 'mlit' ) + mlcl << mlit + mlit << Element.new( 'abpl', nil ) + mlit << Element.new( 'miid', 1 ) + mlit << Element.new( 'mper', 0 ) + mlit << Element.new( 'minm', "Amarok Music Share" ) + mlit << Element.new( 'mimc', @items.size ) + write_resp( response, aply.to_s ) + when "databases" then + # {"avdb"=> + # {"muty"=>[nil], + # "mstt"=>[200], + # "mrco"=>[1], + # "mtco"=>[1], + # "mlcl"=> + # [{"mlit"=> + # [{"miid"=>[1], + # "mper"=>[0], + # "minm"=>["Banshee Music Share"], + # "mctc"=>[1], + # "mimc"=>[1360]}]}]}} + avdb = Element.new( 'avdb' ) + avdb << Element.new( 'muty', 0 ) + avdb << Element.new( 'mstt', 200 ) + avdb << Element.new( 'mrco', 1 ) + avdb << Element.new( 'mtco', 1 ) + mlcl = Element.new( 'mlcl' ) + avdb << mlcl + mlit = Element.new( 'mlit' ) + mlcl << mlit + mlit << Element.new( 'miid', 1 ) + mlit << Element.new( 'mper', 0 ) + mlit << Element.new( 'minm', ENV['USER'] + " Amarok" ) + mlit << Element.new( 'mctc', 1 ) + mlit << Element.new( 'mimc', @items.size ) + write_resp( response, avdb.to_s ) + when "items" then + # {"adbs"=> + # {"muty"=>[nil], + # "mstt"=>[200], + # "mrco"=>[1360], + # "mtco"=>[1360], + # "mlcl"=> + # [{"mlit"=> + # {"asal"=>["Be Human: Ghost in the Shell"], + # "miid"=>[581], + # "astm"=>[86000], + # "minm"=>["FAX me"], + # "astn"=>[nil], + # "asar"=>["Yoko Kanno"], + # "ascm"=>[""]}, + # ... + requested = uri.query.nil? ? Array.new : Mongrel::HttpRequest.query_parse( uri.query )['meta'].split(',') + puts "#{request.params.inspect} #{requested.inspect} #{uri.to_s} #{request.params["REQUEST_URI"]}" + toDisplay = Array.new + requested.each { |str| + str[0,5] = '' + index = str.to_sym + if @column_keys.include?( index ) then + if( METAS[ index ] ) + toDisplay.push( METAS[ index ][:code] ) + else + log "not being displayed #{index.to_s}" + end + end + } + adbs = Element.new( 'adbs' ) + adbs << Element.new( 'muty', nil ) + adbs << Element.new( 'mstt', 200 ) + adbs << Element.new( 'mrco', @items.size ) + adbs << Element.new( 'mtco', @items.size ) + adbs << @items + write_resp( response, adbs.to_s( toDisplay ) ) + else if command =~ /([\d]*)\.(.*)$/ #1232.mp3 + log "sending #{@music[ $1.to_i ]}" + file = @music[ $1.to_i ] + response.start(200) do |head,out| + response.send_status(File.size( file )) + response.header['Content-Type'] = "application/#{$2}" + response.send_header + response.send_file(file) + end + response.send_header + response.send_file( file ) + else + response.start( 404 ) do | head, out | + out << "Command not implemented." + end + puts "#{command} not implemented" + end + end + end + debugMethod(:do_GET) + + private + def query( sql ) + out = Array.new + puts "SQL QUERY: #{sql}" + while ( line = $stdin.gets) && (line.chop! != '**** END SQL ****' ) + out.push( line ) + end + out + end + debugMethod(:query) + + def write_resp( response, value ) + response.start do | head, out | + head['DAAP-Server'] = 'amarok-kaylee' + head['Content-Type'] = 'application/x-dmap-tagged' + out << value + end + end +end + +def log( string ) + f = open('/tmp/test.ruby', File::WRONLY | File::APPEND | File::CREAT ) + f.puts( string ) + f.close +end + +class Controller + + def initialize + port = 3689 + no_server = true + while no_server + begin + server = Mongrel::HttpServer.new('0.0.0.0', port) + no_server = false + rescue Errno::EADDRINUSE + if port == 3700 then + fatal( "No ports between 3688 and 3700 are open." ) + end + port += 1 + end + end + ds = DatabaseServlet.new + server.register('/', ds ) + server.register('daap', ds ) + puts "SERVER STARTING: #{port}" + server.run.join + end + +end + +$stdout.sync = true +$stderr.sync = true + +#RubyProf.start + Controller.new + +#result = RubyProf.stop +#printer = RubyProf::GraphHtmlPrinter.new(result) +#f = open('/tmp/test.html', File::WRONLY | File::CREAT ) +#printer.print( f, 3 ) diff --git a/amarok/src/mediadevice/daap/codes.rb b/amarok/src/mediadevice/daap/codes.rb new file mode 100644 index 00000000..b90d7a99 --- /dev/null +++ b/amarok/src/mediadevice/daap/codes.rb @@ -0,0 +1,230 @@ +METAS = { + :specifiedtotalcount=>{:datatype=>:long, :code=>"mtco"}, + :dictionary=>{:datatype=>:container, :code=>"mdcl"}, + :itms_genreid=>{:datatype=>:long, :code=>"aeGI"}, + :norm_volume=>{:datatype=>:long, :code=>"aeNV"}, + :songtracknumber=>{:datatype=>:short, :code=>"astn"}, + :browsealbumlisting=>{:datatype=>:container, :code=>"abal"}, + :songcompilation=>{:datatype=>:char, :code=>"asco"}, + :smart_playlist=>{:datatype=>:char, :code=>"aeSP"}, + :songcomposer=>{:datatype=>:string, :code=>"ascp"}, + :songeqpreset=>{:datatype=>:string, :code=>"aseq"}, + :baseplaylist=>{:datatype=>:char, :code=>"abpl"}, + :supportsquery=>{:datatype=>:char, :code=>"msqy"}, + :itms_composerid=>{:datatype=>:long, :code=>"aeCI"}, + :contentcodesnumber=>{:datatype=>:long, :code=>"mcnm"}, + :databasebrowse=>{:datatype=>:container, :code=>"abro"}, + :songsize=>{:datatype=>:long, :code=>"assz"}, + :browsecomposerlisting=>{:datatype=>:container, :code=>"abcp"}, + :itms_artistid=>{:datatype=>:long, :code=>"aeAI"}, + :has_video=>{:datatype=>:char, :code=>"aeHV"}, + :statusstring=>{:datatype=>:string, :code=>"msts"}, + :authenticationschemes=>{:datatype=>:long, :code=>"msas"}, + :songcontentrating=>{:datatype=>:char, :code=>"ascr"}, + :itms_playlistid=>{:datatype=>:long, :code=>"aePI"}, + :status=>{:datatype=>:long, :code=>"mstt"}, + :supportsindex=>{:datatype=>:char, :code=>"msix"}, + :supportsresolve=>{:datatype=>:char, :code=>"msrs"}, + :contentcodesresponse=>{:datatype=>:container, :code=>"mccr"}, + :songdatakind=>{:datatype=>:char, :code=>"asdk"}, + :songartist=>{:datatype=>:string, :code=>"asar"}, + :songcodecsubtype=>{:datatype=>:long, :code=>"ascs"}, + :authenticationmethod=>{:datatype=>:char, :code=>"msau"}, + :season_num=>{:datatype=>:long, :code=>"aeSU"}, + :resolveinfo=>{:datatype=>:container, :code=>"arif"}, + :songcategory=>{:datatype=>:string, :code=>"asct"}, + :songformat=>{:datatype=>:string, :code=>"asfm"}, + :episode_num_str=>{:datatype=>:string, :code=>"aeEN"}, + :playlistshufflemode=>{:datatype=>:char, :code=>"apsm"}, + :browseartistlisting=>{:datatype=>:container, :code=>"abar"}, + :loginrequired=>{:datatype=>:char, :code=>"mslr"}, + :supportsextensions=>{:datatype=>:char, :code=>"msex"}, + :deletedidlisting=>{:datatype=>:container, :code=>"mudl"}, + :songdatemodified=>{:datatype=>:date, :code=>"asdm"}, + :songkeywords=>{:datatype=>:string, :code=>"asky"}, + :songdataurl=>{:datatype=>:string, :code=>"asul"}, + :music_sharing_version=>{:datatype=>:long, :code=>"aeSV"}, + :haschildcontainers=>{:datatype=>:char, :code=>"f\215ch"}, + :listing=>{:datatype=>:container, :code=>"mlcl"}, + :serverinforesponse=>{:datatype=>:container, :code=>"msrv"}, + :songdiscnumber=>{:datatype=>:short, :code=>"asdn"}, + :songtrackcount=>{:datatype=>:short, :code=>"astc"}, + :playlistsongs=>{:datatype=>:container, :code=>"apso"}, + :songcodectype=>{:datatype=>:long, :code=>"ascd"}, + :itemname=>{:datatype=>:string, :code=>"minm"}, + :itemcount=>{:datatype=>:long, :code=>"mimc"}, + :containercount=>{:datatype=>:long, :code=>"mctc"}, + :itms_storefrontid=>{:datatype=>:long, :code=>"aeSF"}, + :songrelativevolume=>{:datatype=>:char, :code=>"asrv"}, + :supportsupdate=>{:datatype=>:char, :code=>"msup"}, + :contentcodesname=>{:datatype=>:string, :code=>"mcna"}, + :songgrouping=>{:datatype=>:string, :code=>"agrp"}, + :itemkind=>{:datatype=>:char, :code=>"mikd"}, + :updateresponse=>{:datatype=>:container, :code=>"mupd"}, + :network_name=>{:datatype=>:string, :code=>"aeNN"}, + :songyear=>{:datatype=>:short, :code=>"asyr"}, + :episode_sort=>{:datatype=>:long, :code=>"aeES"}, + :itemid=>{:datatype=>:long, :code=>"miid"}, + :supportsbrowse=>{:datatype=>:char, :code=>"msbr"}, + :updatetype=>{:datatype=>:char, :code=>"muty"}, + :contentcodestype=>{:datatype=>:short, :code=>"mcty"}, + :databaseplaylists=>{:datatype=>:container, :code=>"aply"}, + :is_podcast_playlist=>{:datatype=>:char, :code=>"aePP"}, + :itms_songid=>{:datatype=>:long, :code=>"aeSI"}, + :songstoptime=>{:datatype=>:long, :code=>"assp"}, + :songlongcontentdescription=>{:datatype=>:string, :code=>"aslc"}, + :container=>{:datatype=>:container, :code=>"mcon"}, + :listingitem=>{:datatype=>:container, :code=>"mlit"}, + :songuserrating=>{:datatype=>:char, :code=>"asur"}, + :supportspersistentids=>{:datatype=>:char, :code=>"mspi"}, + :songsamplerate=>{:datatype=>:long, :code=>"assr"}, + :songdateadded=>{:datatype=>:date, :code=>"asda"}, + :songbitrate=>{:datatype=>:short, :code=>"asbr"}, + :containeritemid=>{:datatype=>:long, :code=>"mcti"}, + :parentcontainerid=>{:datatype=>:long, :code=>"mpco"}, + :databasescount=>{:datatype=>:long, :code=>"msdc"}, + :loginresponse=>{:datatype=>:container, :code=>"mlog"}, + :sessionid=>{:datatype=>:long, :code=>"mlid"}, + :serverrevision=>{:datatype=>:long, :code=>"musr"}, + :songdisabled=>{:datatype=>:char, :code=>"asdb"}, + :songdescription=>{:datatype=>:string, :code=>"asdt"}, + :bag=>{:datatype=>:container, :code=>"mbcl"}, + :supportsautologout=>{:datatype=>:char, :code=>"msal"}, + :timeoutinterval=>{:datatype=>:long, :code=>"mstm"}, + :songdisccount=>{:datatype=>:short, :code=>"asdc"}, + :songbeatsperminute=>{:datatype=>:short, :code=>"asbt"}, + :songgenre=>{:datatype=>:string, :code=>"asgn"}, + :playlistrepeatmode=>{:datatype=>:char, :code=>"aprm"}, + :songstarttime=>{:datatype=>:long, :code=>"asst"}, + :persistentid=>{:datatype=>:longlong, :code=>"mper"}, + :returnedcount=>{:datatype=>:long, :code=>"mrco"}, + :protocolversion=>{:datatype=>:version, :code=>"mpro"}, + :songcomment=>{:datatype=>:string, :code=>"ascm"}, + :is_podcast=>{:datatype=>:char, :code=>"aePC"}, + :series_name=>{:datatype=>:string, :code=>"aeSN"}, + :resolve=>{:datatype=>:container, :code=>"arsv"}, + :songalbum=>{:datatype=>:string, :code=>"asal"}, + :protocolversion=>{:datatype=>:version, :code=>"apro"}, + :serverdatabases=>{:datatype=>:container, :code=>"avdb"}, + :mediakind=>{:datatype=>:char, :code=>"aeMK"}, + :songtime=>{:datatype=>:long, :code=>"astm"}, + :databasesongs=>{:datatype=>:container, :code=>"adbs"}, + :browsegenrelisting=>{:datatype=>:container, :code=>"abgn"}, + :songcontentdescription=>{:datatype=>:string, :code=>"ascn"} + } + +CODE_TYPE = { +'aeNV'=>:long, +'astn'=>:short, +'aeGI'=>:long, +'mtco'=>:long, +'abal'=>:container, +'asco'=>:char, +'mdcl'=>:container, +'abpl'=>:char, +'aeSP'=>:char, +'ascp'=>:string, +'aseq'=>:string, +'msqy'=>:char, +'aeCI'=>:long, +'assz'=>:long, +'abcp'=>:container, +'mcnm'=>:long, +'aeAI'=>:long, +'abro'=>:container, +'aeHV'=>:char, +'msas'=>:long, +'ascr'=>:char, +'msts'=>:string, +'aePI'=>:long, +'mccr'=>:container, +'asdk'=>:char, +'msix'=>:char, +'msrs'=>:char, +'asar'=>:string, +'ascs'=>:long, +'mstt'=>:long, +'msau'=>:char, +'aeEN'=>:string, +'aeSU'=>:long, +'apsm'=>:char, +'asfm'=>:string, +'asct'=>:string, +'arif'=>:container, +'abar'=>:container, +'mudl'=>:container, +'asdm'=>:date, +'asky'=>:string, +'msex'=>:char, +'asul'=>:string, +'mslr'=>:char, +'aeSV'=>:long, +'astc'=>:short, +'f\215ch'=>:char, +'apso'=>:container, +'mlcl'=>:container, +'ascd'=>:long, +'asdn'=>:short, +'msrv'=>:container, +'mctc'=>:long, +'aeSF'=>:long, +'mimc'=>:long, +'minm'=>:string, +'asrv'=>:char, +'agrp'=>:string, +'mcna'=>:string, +'msup'=>:char, +'aeES'=>:long, +'mikd'=>:char, +'aeNN'=>:string, +'mupd'=>:container, +'asyr'=>:short, +'mcty'=>:short, +'aslc'=>:string, +'aply'=>:container, +'miid'=>:long, +'assp'=>:long, +'aeSI'=>:long, +'aePP'=>:char, +'muty'=>:char, +'msbr'=>:char, +'mlit'=>:container, +'mcon'=>:container, +'asur'=>:char, +'asda'=>:date, +'asbr'=>:short, +'assr'=>:long, +'mspi'=>:char, +'asdt'=>:string, +'mlog'=>:container, +'mlid'=>:long, +'asdb'=>:char, +'musr'=>:long, +'msdc'=>:long, +'mpco'=>:long, +'mcti'=>:long, +'asbt'=>:short, +'mbcl'=>:container, +'asgn'=>:string, +'asdc'=>:short, +'mstm'=>:long, +'msal'=>:char, +'aprm'=>:char, +'asst'=>:long, +'aeSN'=>:string, +'mrco'=>:long, +'arsv'=>:container, +'ascm'=>:string, +'aePC'=>:char, +'mper'=>:longlong, +'asal'=>:string, +'aeMK'=>:char, +'astm'=>:long, +'apro'=>:version, +'mpro'=>:version, +'adbs'=>:container, +'avdb'=>:container, +'abgn'=>:container, +'ascn'=>:string} + +CONTENT_CODES = "mccr\000\000\027\335mstt\000\000\000\004\000\000\000\310mdcl\000\000\0005mcnm\000\000\000\004msexmcna\000\000\000\027dmap.supportsextensionsmcty\000\000\000\002\000\001mdcl\000\000\0001mcnm\000\000\000\004assrmcna\000\000\000\023daap.songsampleratemcty\000\000\000\002\000\005mdcl\000\000\0000mcnm\000\000\000\004asstmcna\000\000\000\022daap.songstarttimemcty\000\000\000\002\000\005mdcl\000\000\0000mcnm\000\000\000\004mrcomcna\000\000\000\022dmap.returnedcountmcty\000\000\000\002\000\005mdcl\000\000\000;mcnm\000\000\000\004aeGImcna\000\000\000\035com.apple.itunes.itms-genreidmcty\000\000\000\002\000\005mdcl\000\000\0006mcnm\000\000\000\004apsmmcna\000\000\000\030daap.playlistshufflemodemcty\000\000\000\002\000\001mdcl\000\000\0005mcnm\000\000\000\004aprmmcna\000\000\000\027daap.playlistrepeatmodemcty\000\000\000\002\000\001mdcl\000\000\0000mcnm\000\000\000\004apsomcna\000\000\000\022daap.playlistsongsmcty\000\000\000\002\000\fmdcl\000\000\0002mcnm\000\000\000\004apromcna\000\000\000\024daap.protocolversionmcty\000\000\000\002\000\vmdcl\000\000\000-mcnm\000\000\000\004asarmcna\000\000\000\017daap.songartistmcty\000\000\000\002\000\tmdcl\000\000\0004mcnm\000\000\000\004aplymcna\000\000\000\026daap.databaseplaylistsmcty\000\000\000\002\000\fmdcl\000\000\0005mcnm\000\000\000\004msalmcna\000\000\000\027dmap.supportsautologoutmcty\000\000\000\002\000\001mdcl\000\000\000@mcnm\000\000\000\004aeSFmcna\000\000\000\"com.apple.itunes.itms-storefrontidmcty\000\000\000\002\000\005mdcl\000\000\000.mcnm\000\000\000\004asbrmcna\000\000\000\020daap.songbitratemcty\000\000\000\002\000\003mdcl\000\000\000,mcnm\000\000\000\004asalmcna\000\000\000\016daap.songalbummcty\000\000\000\002\000\tmdcl\000\000\0008mcnm\000\000\000\004msasmcna\000\000\000\032dmap.authenticationschemesmcty\000\000\000\002\000\005mdcl\000\000\0000mcnm\000\000\000\004ascdmcna\000\000\000\022daap.songcodectypemcty\000\000\000\002\000\005mdcl\000\000\0007mcnm\000\000\000\004msaumcna\000\000\000\031dmap.authenticationmethodmcty\000\000\000\002\000\001mdcl\000\000\0000mcnm\000\000\000\004asdamcna\000\000\000\022daap.songdateaddedmcty\000\000\000\002\000\nmdcl\000\000\0001mcnm\000\000\000\004msbrmcna\000\000\000\023dmap.supportsbrowsemcty\000\000\000\002\000\001mdcl\000\000\0000mcnm\000\000\000\004asdcmcna\000\000\000\022daap.songdisccountmcty\000\000\000\002\000\003mdcl\000\000\0005mcnm\000\000\000\004asbtmcna\000\000\000\027daap.songbeatsperminutemcty\000\000\000\002\000\003mdcl\000\000\000.mcnm\000\000\000\004arifmcna\000\000\000\020daap.resolveinfomcty\000\000\000\002\000\fmdcl\000\000\000.mcnm\000\000\000\004ascmmcna\000\000\000\020daap.songcommentmcty\000\000\000\002\000\tmdcl\000\000\0009mcnm\000\000\000\004ascnmcna\000\000\000\edaap.songcontentdescriptionmcty\000\000\000\002\000\tmdcl\000\000\0002mcnm\000\000\000\004ascomcna\000\000\000\024daap.songcompilationmcty\000\000\000\002\000\001mdcl\000\000\000/mcnm\000\000\000\004ascpmcna\000\000\000\021daap.songcomposermcty\000\000\000\002\000\tmdcl\000\000\000/mcnm\000\000\000\004asdbmcna\000\000\000\021daap.songdisabledmcty\000\000\000\002\000\001mdcl\000\000\0001mcnm\000\000\000\004asdnmcna\000\000\000\023daap.songdiscnumbermcty\000\000\000\002\000\003mdcl\000\000\000/mcnm\000\000\000\004asctmcna\000\000\000\021daap.songcategorymcty\000\000\000\002\000\tmdcl\000\000\0004mcnm\000\000\000\004ascrmcna\000\000\000\026daap.songcontentratingmcty\000\000\000\002\000\001mdcl\000\000\0003mcnm\000\000\000\004ascsmcna\000\000\000\025daap.songcodecsubtypemcty\000\000\000\002\000\005mdcl\000\000\0002mcnm\000\000\000\004asdtmcna\000\000\000\024daap.songdescriptionmcty\000\000\000\002\000\tmdcl\000\000\000/mcnm\000\000\000\004asdkmcna\000\000\000\021daap.songdatakindmcty\000\000\000\002\000\001mdcl\000\000\000-mcnm\000\000\000\004asfmmcna\000\000\000\017daap.songformatmcty\000\000\000\002\000\tmdcl\000\000\000)mcnm\000\000\000\004miidmcna\000\000\000\vdmap.itemidmcty\000\000\000\002\000\005mdcl\000\000\000,mcnm\000\000\000\004asgnmcna\000\000\000\016daap.songgenremcty\000\000\000\002\000\tmdcl\000\000\000:mcnm\000\000\000\004aeNVmcna\000\000\000\034com.apple.itunes.norm-volumemcty\000\000\000\002\000\005mdcl\000\000\000)mcnm\000\000\000\004msttmcna\000\000\000\vdmap.statusmcty\000\000\000\002\000\005mdcl\000\000\000+mcnm\000\000\000\004mikdmcna\000\000\000\rdmap.itemkindmcty\000\000\000\002\000\001mdcl\000\000\0003mcnm\000\000\000\004asdmmcna\000\000\000\025daap.songdatemodifiedmcty\000\000\000\002\000\nmdcl\000\000\0006mcnm\000\000\000\004mtcomcna\000\000\000\030dmap.specifiedtotalcountmcty\000\000\000\002\000\005mdcl\000\000\000/mcnm\000\000\000\004agrpmcna\000\000\000\021daap.songgroupingmcty\000\000\000\002\000\tmdcl\000\000\000/mcnm\000\000\000\004aseqmcna\000\000\000\021daap.songeqpresetmcty\000\000\000\002\000\tmdcl\000\000\0000mcnm\000\000\000\004msixmcna\000\000\000\022dmap.supportsindexmcty\000\000\000\002\000\001mdcl\000\000\000:mcnm\000\000\000\004aeSImcna\000\000\000\034com.apple.itunes.itms-songidmcty\000\000\000\002\000\005mdcl\000\000\000=mcnm\000\000\000\004aeSPmcna\000\000\000\037com.apple.itunes.smart-playlistmcty\000\000\000\002\000\001mdcl\000\000\0000mcnm\000\000\000\004mslrmcna\000\000\000\022dmap.loginrequiredmcty\000\000\000\002\000\001mdcl\000\000\0009mcnm\000\000\000\004aePCmcna\000\000\000\ecom.apple.itunes.is-podcastmcty\000\000\000\002\000\001mdcl\000\000\0003mcnm\000\000\000\004mudlmcna\000\000\000\025dmap.deletedidlistingmcty\000\000\000\002\000\fmdcl\000\000\000Dmcnm\000\000\000\004aeSVmcna\000\000\000&com.apple.itunes.music-sharing-versionmcty\000\000\000\002\000\005mdcl\000\000\000+mcnm\000\000\000\004minmmcna\000\000\000\rdmap.itemnamemcty\000\000\000\002\000\tmdcl\000\000\000/mcnm\000\000\000\004askymcna\000\000\000\021daap.songkeywordsmcty\000\000\000\002\000\tmdcl\000\000\0008mcnm\000\000\000\004mspimcna\000\000\000\032dmap.supportspersistentidsmcty\000\000\000\002\000\001mdcl\000\000\000*mcnm\000\000\000\004arsvmcna\000\000\000\fdaap.resolvemcty\000\000\000\002\000\fmdcl\000\000\0005mcnm\000\000\000\004abalmcna\000\000\000\027daap.browsealbumlistingmcty\000\000\000\002\000\fmdcl\000\000\0002mcnm\000\000\000\004avdbmcna\000\000\000\024daap.serverdatabasesmcty\000\000\000\002\000\fmdcl\000\000\000&mcnm\000\000\000\004mbclmcna\000\000\000\010dmap.bagmcty\000\000\000\002\000\fmdcl\000\000\0006mcnm\000\000\000\004abarmcna\000\000\000\030daap.browseartistlistingmcty\000\000\000\002\000\fmdcl\000\000\0002mcnm\000\000\000\004msrsmcna\000\000\000\024dmap.supportsresolvemcty\000\000\000\002\000\001mdcl\000\000\0000mcnm\000\000\000\004msqymcna\000\000\000\022dmap.supportsquerymcty\000\000\000\002\000\001mdcl\000\000\0005mcnm\000\000\000\004msrvmcna\000\000\000\027dmap.serverinforesponsemcty\000\000\000\002\000\fmdcl\000\000\0002mcnm\000\000\000\004mstmmcna\000\000\000\024dmap.timeoutintervalmcty\000\000\000\002\000\005mdcl\000\000\0008mcnm\000\000\000\004abcpmcna\000\000\000\032daap.browsecomposerlistingmcty\000\000\000\002\000\fmdcl\000\000\000/mcnm\000\000\000\004asspmcna\000\000\000\021daap.songstoptimemcty\000\000\000\002\000\005mdcl\000\000\0005mcnm\000\000\000\004asrvmcna\000\000\000\027daap.songrelativevolumemcty\000\000\000\002\000\002mdcl\000\000\000+mcnm\000\000\000\004astmmcna\000\000\000\rdaap.songtimemcty\000\000\000\002\000\005mdcl\000\000\0002mcnm\000\000\000\004astnmcna\000\000\000\024daap.songtracknumbermcty\000\000\000\002\000\003mdcl\000\000\0001mcnm\000\000\000\004msupmcna\000\000\000\023dmap.supportsupdatemcty\000\000\000\002\000\001mdcl\000\000\000.mcnm\000\000\000\004asulmcna\000\000\000\020daap.songdataurlmcty\000\000\000\002\000\tmdcl\000\000\000,mcnm\000\000\000\004mlidmcna\000\000\000\016dmap.sessionidmcty\000\000\000\002\000\005mdcl\000\000\000+mcnm\000\000\000\004asszmcna\000\000\000\rdaap.songsizemcty\000\000\000\002\000\005mdcl\000\000\000,mcnm\000\000\000\004mimcmcna\000\000\000\016dmap.itemcountmcty\000\000\000\002\000\005mdcl\000\000\0001mcnm\000\000\000\004asurmcna\000\000\000\023daap.songuserratingmcty\000\000\000\002\000\001mdcl\000\000\0005mcnm\000\000\000\004abgnmcna\000\000\000\027daap.browsegenrelistingmcty\000\000\000\002\000\fmdcl\000\000\0001mcnm\000\000\000\004mupdmcna\000\000\000\023dmap.updateresponsemcty\000\000\000\002\000\fmdcl\000\000\0007mcnm\000\000\000\004mccrmcna\000\000\000\031dmap.contentcodesresponsemcty\000\000\000\002\000\fmdcl\000\000\0001mcnm\000\000\000\004astcmcna\000\000\000\023daap.songtrackcountmcty\000\000\000\002\000\003mdcl\000\000\000.mcnm\000\000\000\004mlitmcna\000\000\000\020dmap.listingitemmcty\000\000\000\002\000\fmdcl\000\000\000=mcnm\000\000\000\004aslcmcna\000\000\000\037daap.songlongcontentdescriptionmcty\000\000\000\002\000\tmdcl\000\000\000+mcnm\000\000\000\004asyrmcna\000\000\000\rdaap.songyearmcty\000\000\000\002\000\003mdcl\000\000\000-mcnm\000\000\000\004mdclmcna\000\000\000\017dmap.dictionarymcty\000\000\000\002\000\fmdcl\000\000\0000mcnm\000\000\000\004mlogmcna\000\000\000\022dmap.loginresponsemcty\000\000\000\002\000\fmdcl\000\000\0000mcnm\000\000\000\004adbsmcna\000\000\000\022daap.databasesongsmcty\000\000\000\002\000\fmdcl\000\000\0001mcnm\000\000\000\004musrmcna\000\000\000\023dmap.serverrevisionmcty\000\000\000\002\000\005mdcl\000\000\000Bmcnm\000\000\000\004aePPmcna\000\000\000$com.apple.itunes.is-podcast-playlistmcty\000\000\000\002\000\001mdcl\000\000\0001mcnm\000\000\000\004msdcmcna\000\000\000\023dmap.databasescountmcty\000\000\000\002\000\005mdcl\000\000\0003mcnm\000\000\000\004mcnamcna\000\000\000\025dmap.contentcodesnamemcty\000\000\000\002\000\tmdcl\000\000\000-mcnm\000\000\000\004mutymcna\000\000\000\017dmap.updatetypemcty\000\000\000\002\000\001mdcl\000\000\0005mcnm\000\000\000\004mcnmmcna\000\000\000\027dmap.contentcodesnumbermcty\000\000\000\002\000\005mdcl\000\000\0001mcnm\000\000\000\004abromcna\000\000\000\023daap.databasebrowsemcty\000\000\000\002\000\fmdcl\000\000\000,mcnm\000\000\000\004mconmcna\000\000\000\016dmap.containermcty\000\000\000\002\000\fmdcl\000\000\0001mcnm\000\000\000\004mctcmcna\000\000\000\023dmap.containercountmcty\000\000\000\002\000\005mdcl\000\000\0004mcnm\000\000\000\004mpcomcna\000\000\000\026dmap.parentcontaineridmcty\000\000\000\002\000\005mdcl\000\000\0002mcnm\000\000\000\004mctimcna\000\000\000\024dmap.containeritemidmcty\000\000\000\002\000\005mdcl\000\000\000/mcnm\000\000\000\004mstsmcna\000\000\000\021dmap.statusstringmcty\000\000\000\002\000\tmdcl\000\000\0002mcnm\000\000\000\004mpromcna\000\000\000\024dmap.protocolversionmcty\000\000\000\002\000\vmdcl\000\000\000/mcnm\000\000\000\004abplmcna\000\000\000\021daap.baseplaylistmcty\000\000\000\002\000\001mdcl\000\000\000/mcnm\000\000\000\004mpermcna\000\000\000\021dmap.persistentidmcty\000\000\000\002\000\amdcl\000\000\000*mcnm\000\000\000\004mlclmcna\000\000\000\fdmap.listingmcty\000\000\000\002\000\fmdcl\000\000\0003mcnm\000\000\000\004mctymcna\000\000\000\025dmap.contentcodestypemcty\000\000\000\002\000\003mdcl\000\000\000<mcnm\000\000\000\004aeAImcna\000\000\000\036com.apple.itunes.itms-artistidmcty\000\000\000\002\000\005mdcl\000\000\000>mcnm\000\000\000\004aePImcna\000\000\000 com.apple.itunes.itms-playlistidmcty\000\000\000\002\000\005mdcl\000\000\0005mcnm\000\000\000\004f\215chmcna\000\000\000\027dmap.haschildcontainersmcty\000\000\000\002\000\001mdcl\000\000\000>mcnm\000\000\000\004aeCImcna\000\000\000 com.apple.itunes.itms-composeridmcty\000\000\000\002\000\005" diff --git a/amarok/src/mediadevice/daap/daapclient.cpp b/amarok/src/mediadevice/daap/daapclient.cpp new file mode 100644 index 00000000..1a08c893 --- /dev/null +++ b/amarok/src/mediadevice/daap/daapclient.cpp @@ -0,0 +1,880 @@ +/*************************************************************************** + * copyright: (C) 2006, 2007 Ian Monroe <ian@monroe.nu> * + * (C) 2006 Seb Ruiz <me@sebruiz.net> * + * (C) 2007 Maximilian Kossick <maximilian.kossick@googlemail.com> + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef AMAROK_DAAPCLIENT_CPP +#define AMAROK_DAAPCLIENT_CPP + +#include "addhostbase.h" +#include "collectiondb.h" +#include "collectionbrowser.h" +#include "daapreader/reader.h" +#include "daapreader/authentication/contentfetcher.h" +#include "daapclient.h" +#include "daapserver.h" +#include "debug.h" +#include "mediabrowser.h" +#include "playlist.h" +#include "proxy.h" +#include "statusbar/statusbar.h" +#include "tagdialog.h" + +#include <qcheckbox.h> +#include <qmetaobject.h> +#include <qobjectlist.h> +#include <qlabel.h> +#include <qpixmap.h> +#include <qtimer.h> +#include <qtooltip.h> + +#include <kiconloader.h> +#include <klineedit.h> +#include <knuminput.h> +#include <kpassdlg.h> +#include <kpopupmenu.h> +#include <kresolver.h> +#include <kstandarddirs.h> //loading icons +#include <ktempfile.h> +#include <ktoolbar.h> +#include <ktoolbarbutton.h> + +#if DNSSD_SUPPORT + #include <dnssd/remoteservice.h> + #include <dnssd/servicebase.h> + #include <dnssd/servicebrowser.h> +#endif + +AMAROK_EXPORT_PLUGIN( DaapClient ) + +DaapClient::DaapClient() + : MediaDevice() +#if DNSSD_SUPPORT + , m_browser( 0 ) +#endif + , m_connected( false ) + , m_sharingServer( 0 ) + , m_broadcastServerCheckBox( 0 ) + , m_broadcastServer( false ) // to abide by "all ports closed" policy, we default to not broadcasting music +{ +DEBUG_BLOCK + setName( "daapclient" ); + m_name = i18n( "Shared Music" ); + m_hasMountPoint = false; + m_autoDeletePodcasts = false; + m_syncStats = false; + m_transcode = false; + m_transcodeAlways = false; + m_transcodeRemove = false; + m_configure = false; + m_customButton = true; + m_transfer = false; + + KToolBar *toolbar = MediaBrowser::instance()->getToolBar(); + KToolBarButton *customButton = toolbar->getButton( MediaBrowser::CUSTOM ); + customButton->setText( i18n("Add computer") ); + + toolbar = CollectionBrowser::instance()->getToolBar(); + toolbar->setIconText( KToolBar::IconTextRight, false ); + m_broadcastButton = new KToolBarButton( "connect_creating", 0, toolbar, "broadcast_button", + i18n("Share My Music") ); + m_broadcastButton->setToggle( true ); + + QToolTip::add( customButton, i18n( "List music from a remote host" ) ); + QToolTip::add( m_broadcastButton, i18n( "If this button is checked, then your music will be exported to the network" ) ); + + connect( m_broadcastButton, SIGNAL( toggled(int) ), SLOT( broadcastButtonToggled() ) ); + + MediaBrowser::instance()->insertChild( this ); +} + +DaapClient::~DaapClient() +{ +#if DNSSD_SUPPORT + delete m_browser; +#endif +} + +bool +DaapClient::isConnected() +{ + return m_connected; +} + +bool +DaapClient::getCapacity( KIO::filesize_t* /* total */, KIO::filesize_t* /* available */ ) +{ + return false; +} + +bool +DaapClient::lockDevice(bool /*tryOnly = false*/ ) +{ + return true; +} + +void +DaapClient::unlockDevice() +{ + return; +} + +bool +DaapClient::openDevice(bool /* silent=false */) +{ + DEBUG_BLOCK + m_connected = true; +#if DNSSD_SUPPORT + if ( !m_browser ) + { + m_browser = new DNSSD::ServiceBrowser("_daap._tcp"); + m_browser->setName("daapServiceBrowser"); + connect( m_browser, SIGNAL( serviceAdded( DNSSD::RemoteService::Ptr ) ), + this, SLOT( foundDaap ( DNSSD::RemoteService::Ptr ) ) ); + connect( m_browser, SIGNAL( serviceRemoved( DNSSD::RemoteService::Ptr ) ), + this, SLOT( serverOffline ( DNSSD::RemoteService::Ptr ) ) ); + m_browser->startBrowse(); + } +#endif + QStringList sl = AmarokConfig::manuallyAddedServers(); + foreach( sl ) + { + QStringList current = QStringList::split(":", (*it) ); + QString host = current.first(); + Q_UINT16 port = current.last().toInt(); + QString ip = resolve( host ); + if( ip != "0" ) + { + newHost( host, host, ip, port ); + } + } + + if( m_broadcastServer ) + m_sharingServer = new DaapServer( this, "DaapServer" ); + + return true; +} + +bool +DaapClient::closeDevice() +{ + m_view->clear(); + QObjectList* readers = queryList( "Daap::Reader"); + QObject* itRead; + for( itRead = readers->first(); itRead; itRead = readers->next() ) + { + static_cast<Daap::Reader*>(itRead)->logoutRequest(); + delete m_servers[ itRead->name() ]; + m_servers.remove( itRead->name() ); + } + m_connected = false; + m_servers.clear(); +#if DNSSD_SUPPORT + m_serverItemMap.clear(); + delete m_browser; + m_browser = 0; +#endif + delete m_sharingServer; + m_sharingServer = 0; + + return true; +} + +KURL +DaapClient::getProxyUrl( const KURL& url ) +{ + DEBUG_BLOCK + Daap::Proxy* daapProxy = new Daap::Proxy( url, this, "daapProxy" ); + return daapProxy->proxyUrl(); +} + +void +DaapClient::synchronizeDevice() +{ + return; +} + +MediaItem* +DaapClient::copyTrackToDevice(const MetaBundle& /* bundle */) +{ + return 0; +} + +MediaItem* +DaapClient::trackExists( const MetaBundle& ) +{ + return 0; +} + +int +DaapClient::deleteItemFromDevice( MediaItem* /*item*/, int /*flags*/ ) +{ + return 0; +} + +void +DaapClient::rmbPressed( QListViewItem* qitem, const QPoint& point, int ) +{ + DEBUG_BLOCK + + enum Actions { APPEND, LOAD, QUEUE, INFO, CONNECT, REMOVE, DOWNLOAD }; + + MediaItem *item = dynamic_cast<MediaItem *>(qitem); + ServerItem* sitem = dynamic_cast<ServerItem *>(qitem); + if( !item ) + return; + + KURL::List urls; + + KPopupMenu menu( m_view ); + switch( item->type() ) + { + case MediaItem::DIRECTORY: + menu.insertItem( SmallIconSet( "connect_creating" ), i18n( "&Connect" ), CONNECT ); + if( sitem && !m_serverItemMap.contains( sitem->key() ) ) + { + menu.insertItem( SmallIconSet( "remove" ), i18n("&Remove Computer"), REMOVE ); + } + { + QStringList sl = m_serverItemMap.keys(); + foreach( sl ) + { + debug() << (*it) << endl; + } + debug() << sitem->key() << endl; + } + break; + default: + urls = m_view->nodeBuildDragList( 0 ); + menu.insertItem( SmallIconSet( Amarok::icon( "playlist" ) ), i18n( "&Load" ), LOAD ); + menu.insertItem( SmallIconSet( Amarok::icon( "add_playlist" ) ), i18n( "&Append to Playlist" ), APPEND ); + menu.insertItem( SmallIconSet( Amarok::icon( "fastforward" ) ), i18n( "&Queue Tracks" ), QUEUE ); + menu.insertSeparator(); + menu.insertItem( SmallIconSet( Amarok::icon( "playlist" ) ), i18n( "&Copy Files to Collection..." ), DOWNLOAD ); + + // albums and artists don't have bundles, so they crash... :( + if( item->bundle() ) + { + menu.insertItem( SmallIconSet( Amarok::icon( "info" ) ), i18n( "Track &Information..." ), INFO ); + } + break; + } + + int id = menu.exec( point ); + switch( id ) + { + case CONNECT: + if( ServerItem *s = dynamic_cast<ServerItem *>(item) ) + { + s->reset(); + } + item->setOpen( true ); + break; + case LOAD: + Playlist::instance()->insertMedia( urls, Playlist::Replace ); + break; + case APPEND: + Playlist::instance()->insertMedia( urls, Playlist::Append ); + break; + case QUEUE: + Playlist::instance()->insertMedia( urls, Playlist::Queue ); + break; + case INFO: + { + // The tag dialog automatically disables the widgets if the file is not local, which it is not. + TagDialog *dialog = new TagDialog( *item->bundle(), 0 ); + dialog->show(); + } + break; + case REMOVE: + if( sitem ) + { + QStringList mas = AmarokConfig::manuallyAddedServers(); + mas.remove( sitem->key() ); + AmarokConfig::setManuallyAddedServers( mas ); + delete sitem; + } + break; + case DOWNLOAD: + downloadSongs( urls ); + break; + } +} + +void +DaapClient::downloadSongs( KURL::List urls ) +{ + DEBUG_BLOCK + KURL::List realStreamUrls; + KURL::List::Iterator it; + for( it = urls.begin(); it != urls.end(); ++it ) + realStreamUrls << Daap::Proxy::realStreamUrl( (*it), getSession( (*it).host() + ':' + QString::number( (*it).port() ) ) ); + ThreadManager::instance()->queueJob( new DaapDownloader( realStreamUrls ) ); +} + +void +DaapClient::serverOffline( DNSSD::RemoteService::Ptr service ) +{ +#if DNSSD_SUPPORT + DEBUG_BLOCK + QString key = serverKey( service.data() ); + if( m_serverItemMap.contains( key ) ) + { + ServerItem* removeMe = m_serverItemMap[ key ]; + if( removeMe ) + { + delete removeMe; + removeMe = 0; + } + else + warning() << "root item already null" << endl; + m_serverItemMap.remove( key ); + } + else + warning() << "removing non-existant service" << endl; +#endif +} + +#if DNSSD_SUPPORT +QString +DaapClient::serverKey( const DNSSD::RemoteService* service ) const +{ + return ServerItem::key( service->hostName(), service->port() ); +} +#endif + +void +DaapClient::foundDaap( DNSSD::RemoteService::Ptr service ) +{ +#if DNSSD_SUPPORT + DEBUG_BLOCK + + connect( service, SIGNAL( resolved( bool ) ), this, SLOT( resolvedDaap( bool ) ) ); + service->resolveAsync(); +#endif +} + +void +DaapClient::resolvedDaap( bool success ) +{ +#if DNSSD_SUPPORT + DEBUG_BLOCK + const DNSSD::RemoteService* service = dynamic_cast<const DNSSD::RemoteService*>(sender()); + if( !success || !service ) return; + debug() << service->serviceName() << ' ' << service->hostName() << ' ' << service->domain() << ' ' << service->type() << endl; + + QString ip = resolve( service->hostName() ); + if( ip == "0" || m_serverItemMap.contains(serverKey( service )) ) //same server from multiple interfaces + return; + + m_serverItemMap[ serverKey( service ) ] = newHost( service->serviceName(), service->hostName(), ip, service->port() ); +#endif +} + +void +DaapClient::createTree( const QString& /*host*/, Daap::SongList bundles ) +{ + DEBUG_BLOCK + const Daap::Reader* callback = dynamic_cast<const Daap::Reader*>(sender()); + if( !callback ) + { + debug() << "No callback!" << endl; + return; + } + + { + const QString hostKey = callback->name(); + ServerInfo* si = new ServerInfo(); + si->sessionId = callback->sessionId(); + m_servers[ hostKey ] = si; + } + + ServerItem* root = callback->rootMediaItem(); + QStringList artists = bundles.keys(); + foreach( artists ) + { + MediaItem* parentArtist = new MediaItem( root ); + parentArtist->setType( MediaItem::ARTIST ); + Daap::AlbumList albumMap = *( bundles.find(*it) ); + parentArtist->setText( 0, (*albumMap.begin()).getFirst()->artist() ); //map was made case insensitively + //just get the displayed-case from + //the first track + QStringList albums = albumMap.keys(); + for ( QStringList::Iterator itAlbum = albums.begin(); itAlbum != albums.end(); ++itAlbum ) + { + MediaItem* parentAlbum = new MediaItem( parentArtist ); + parentAlbum->setType( MediaItem::ALBUM ); + MetaBundle* track; + + Daap::TrackList trackList = *albumMap.find(*itAlbum); + parentAlbum->setText( 0, trackList.getFirst()->album() ); + + for( track = trackList.first(); track; track = trackList.next() ) + { + if( m_removeDuplicates && trackExistsInCollection( track ) ) + continue; + MediaItem* childTrack = new MediaItem( parentAlbum ); + childTrack->setText( 0, track->title() ); + childTrack->setType( MediaItem::TRACK ); + childTrack->setBundle( track ); + childTrack->m_order = track->track(); + } + if( !parentAlbum->childCount() ) + delete parentAlbum; + } + if( !parentArtist->childCount() ) + delete parentArtist; + } + root->resetTitle(); + root->stopAnimation(); + root->setOpen( true ); +} + +int +DaapClient::incRevision( const QString& host ) +{ + if( m_servers.contains(host) ) + { + m_servers[host]->revisionID++; + return m_servers[host]->revisionID; + } + else + return 0; +} + +int +DaapClient::getSession( const QString& host ) +{ + if( m_servers.contains(host) ) + return m_servers[host]->sessionId; + else + return -1; +} + +void +DaapClient::customClicked() +{ + class AddHostDialog : public KDialogBase + { + + public: + AddHostDialog( QWidget *parent ) + : KDialogBase( parent, "DaapAddHostDialog", true, i18n( "Add Computer" ) , Ok|Cancel) + { + m_base = new AddHostBase( this, "DaapAddHostBase" ); + m_base->m_downloadPixmap->setPixmap( QPixmap( KGlobal::iconLoader()->iconPath( Amarok::icon( "download" ), -KIcon::SizeEnormous ) ) ); + m_base->m_hostName->setFocus(); + setMainWidget( m_base ); + } + AddHostBase* m_base; + }; + + AddHostDialog dialog( 0 ); + if( dialog.exec() == QDialog::Accepted ) { + QString ip = resolve( dialog.m_base->m_hostName->text() ); + if( ip == "0" ) + Amarok::StatusBar::instance()->shortMessage( i18n("Could not resolve %1.").arg( dialog.m_base->m_hostName->text() ) ); + else + { + QString key = ServerItem::key( dialog.m_base->m_hostName->text(), dialog.m_base->m_portInput->value() ); + if( !AmarokConfig::manuallyAddedServers().contains( key ) ) + { + QStringList mas = AmarokConfig::manuallyAddedServers(); + mas.append( key ); + AmarokConfig::setManuallyAddedServers( mas ); + } + newHost( dialog.m_base->m_hostName->text(), dialog.m_base->m_hostName->text(), ip, dialog.m_base->m_portInput->value() ); + } + } +} + +ServerItem* +DaapClient::newHost( const QString& serviceName, const QString& host, const QString& ip, const Q_INT16 port ) +{ + if( ip.isEmpty() ) return 0; + + return new ServerItem( m_view, this, ip, port, serviceName, host ); +} + +void +DaapClient::passwordPrompt() +{ + class PasswordDialog : public KDialogBase + { + public: + PasswordDialog( QWidget *parent ) + : KDialogBase( parent, "PasswordDialog", true, i18n( "Password Required" ) , Ok|Cancel) + { + makeHBoxMainWidget(); + + KGuiItem ok( KStdGuiItem::ok() ); + ok.setText( i18n( "Login" ) ); + ok.setToolTip( i18n("Login to the music share with the password given.") ); + setButtonOK( ok ); + + QLabel* passIcon = new QLabel( mainWidget(), "passicon" ); + passIcon->setPixmap( QPixmap( KGlobal::iconLoader()->iconPath( "password", -KIcon::SizeHuge ) ) ); + QHBox* loginArea = new QHBox( mainWidget(), "passhbox" ); + new QLabel( i18n( "Password:"), loginArea, "passlabel" ); + m_input = new KPasswordEdit( loginArea, "passedit" ); + m_input->setFocus(); + } + KPasswordEdit* m_input; + }; + + Daap::Reader* callback = dynamic_cast<Daap::Reader*>( const_cast<QObject*>( sender() ) ); + if (!callback) { + debug() << "No callback!" << endl; + return; + } + ServerItem* root = callback->rootMediaItem(); + + PasswordDialog dialog( 0 ); + if( dialog.exec() == QDialog::Accepted ) + { + Daap::Reader* reader = new Daap::Reader( callback->host(), callback->port(), root, QString( dialog.m_input->password() ), this, callback->name() ); + root->setReader( reader ); + connect( reader, SIGNAL( daapBundles( const QString&, Daap::SongList ) ), + this, SLOT( createTree( const QString&, Daap::SongList ) ) ); + connect( reader, SIGNAL( passwordRequired() ), this, SLOT( passwordPrompt() ) ); + connect( reader, SIGNAL( httpError( const QString& ) ), root, SLOT( httpError( const QString& ) ) ); + reader->loginRequest(); + } + else + { + root->setOpen( false ); + root->resetTitle(); + root->unLoaded(); + } + callback->deleteLater(); +} + +QString +DaapClient::resolve( const QString& hostname ) +{ + KNetwork::KResolver resolver( hostname ); + resolver.setFamily( KNetwork::KResolver::KnownFamily ); //A druidic incantation from Thiago. Works around a KResolver bug #132851 + resolver.start(); + if( resolver.wait( 5000 ) ) + { + KNetwork::KResolverResults results = resolver.results(); + if( results.error() ) + debug() << "Error resolving " << hostname << ": (" + << resolver.errorString( results.error() ) << ")" << endl; + if( !results.empty() ) + { + QString ip = results[0].address().asInet().ipAddress().toString(); + debug() << "ip found is " << ip << endl; + return ip; + } + } + return "0"; //error condition +} + +const bool +DaapClient::trackExistsInCollection( MetaBundle *bundle ) +{ + /// FIXME slow. + QueryBuilder qb; + qb.addMatch( QueryBuilder::tabSong , QueryBuilder::valTitle, bundle->title() , true, false ); + qb.addMatch( QueryBuilder::tabArtist, QueryBuilder::valName , bundle->artist(), true, false ); + qb.addMatch( QueryBuilder::tabAlbum , QueryBuilder::valName , bundle->album() , true, false ); + + qb.addReturnFunctionValue( QueryBuilder::funcCount, QueryBuilder::tabSong, QueryBuilder::valURL ); + + QStringList values = qb.run(); + + return ( values[0].toInt() > 0 ); +} + + +/// Configuration Dialog Extension + +void +DaapClient::addConfigElements( QWidget * parent ) +{ + m_broadcastServerCheckBox = new QCheckBox( "Broadcast my music", parent ); + m_broadcastServerCheckBox->setChecked( m_broadcastServer ); + + m_removeDuplicatesCheckBox = new QCheckBox( "Hide songs in my collection", parent ); + m_removeDuplicatesCheckBox->setChecked( m_removeDuplicates ); + + QToolTip::add( m_removeDuplicatesCheckBox, i18n( "Enabling this may reduce connection times" ) ); +} + + +void +DaapClient::removeConfigElements( QWidget * /* parent */ ) +{ + if( m_broadcastServerCheckBox != 0 ) + delete m_broadcastServerCheckBox; + + if( m_removeDuplicatesCheckBox != 0 ) + delete m_removeDuplicatesCheckBox; + + m_broadcastServerCheckBox = 0; + m_removeDuplicatesCheckBox = 0; +} + +void +DaapClient::loadConfig() +{ + MediaDevice::loadConfig(); + + m_broadcastServer = configBool( "broadcastServer", false ); + m_removeDuplicates = configBool( "removeDuplicates", false ); + + // don't undo all the work we just did at startup + m_broadcastButton->blockSignals( true ); + m_broadcastButton->setOn( m_broadcastServer ); + m_broadcastButton->blockSignals( false ); +} + +void +DaapClient::applyConfig() +{ + if( m_broadcastServerCheckBox ) + m_broadcastServer = m_broadcastServerCheckBox->isChecked(); + + if( m_removeDuplicatesCheckBox ) + m_removeDuplicates = m_removeDuplicatesCheckBox->isChecked(); + + setConfigBool( "broadcastServer" , m_broadcastServer ); + setConfigBool( "removeDuplicates", m_removeDuplicates ); +} + +void +DaapClient::broadcastButtonToggled() +{ +DEBUG_BLOCK + m_broadcastServer = !m_broadcastServer; + + switch( m_broadcastServer ) + { + case false: + debug() << "turning daap server off" << endl; + if( m_sharingServer ) + delete m_sharingServer; + m_sharingServer = 0; + break; + + case true: + debug() << "turning daap server on" << endl; + if( !m_sharingServer ) + m_sharingServer = new DaapServer( this, "DaapServer" ); + break; + } +} + +//////////////////////////////////////////////////////////////////////////////// +// CLASS ServerItem +//////////////////////////////////////////////////////////////////////////////// + +ServerItem::ServerItem( QListView* parent, DaapClient* client, const QString& ip, Q_UINT16 port, const QString& title, const QString& host ) + : MediaItem( parent ) + , m_daapClient( client ) + , m_reader( 0 ) + , m_ip( ip ) + , m_port( port ) + , m_title( title ) + , m_host( host ) + , m_loaded( false ) + , m_loading1( new QPixmap( locate("data", "amarok/images/loading1.png" ) ) ) + , m_loading2( new QPixmap( locate("data", "amarok/images/loading2.png" ) ) ) +{ + setText( 0, title ); + setType( MediaItem::DIRECTORY ); +} + +ServerItem::~ServerItem() +{ + delete m_reader; + m_reader = 0; +} + +void +ServerItem::reset() +{ + delete m_reader; + m_reader = 0; + + m_loaded = 0; + + QListViewItem *c = firstChild(); + QListViewItem *n; + while( c ) { + n = c->nextSibling(); + delete c; + c = n; + } +} + +void +ServerItem::setOpen( bool o ) +{ + if( !o ) + { + MediaItem::setOpen( o ); + return; + } + + if( !m_loaded ) + { + //starts loading animation + m_iconCounter = 1; + startAnimation(); + connect( &m_animationTimer, SIGNAL(timeout()), this, SLOT(slotAnimation()) ); + + setText( 0, i18n( "Loading %1").arg( text( 0 ) ) ); + + Daap::Reader* reader = new Daap::Reader( m_ip, m_port, this, + QString::null, m_daapClient, ( m_ip + ":3689" ).ascii() ); + setReader ( reader ); + + connect( reader, SIGNAL( daapBundles( const QString&, Daap::SongList ) ), + m_daapClient, SLOT( createTree( const QString&, Daap::SongList ) ) ); + connect( reader, SIGNAL( passwordRequired() ), m_daapClient, SLOT( passwordPrompt() ) ); + connect( reader, SIGNAL( httpError( const QString& ) ), this, SLOT( httpError( const QString& ) ) ); + reader->loginRequest(); + m_loaded = true; + } + else + MediaItem::setOpen( true ); +} + +void +ServerItem::startAnimation() +{ + if( !m_animationTimer.isActive() ) + m_animationTimer.start( ANIMATION_INTERVAL ); +} + +void +ServerItem::stopAnimation() +{ + m_animationTimer.stop(); + setType( MediaItem::DIRECTORY ); //restore icon +} + +void +ServerItem::slotAnimation() +{ + m_iconCounter % 2 ? + setPixmap( 0, *m_loading1 ): + setPixmap( 0, *m_loading2 ); + + m_iconCounter++; +} + +void +ServerItem::httpError( const QString& errorString ) +{ + stopAnimation(); + resetTitle(); + Amarok::StatusBar::instance()->longMessage( i18n( "The following error occurred while trying to connect to the remote server:<br>%1").arg( errorString ) ); + m_reader->deleteLater(); + m_reader = 0; + m_loaded = false; +} + +//////////////////////////////////////////////////////////////////////////////// +// CLASS DaapDownloader +//////////////////////////////////////////////////////////////////////////////// +DaapDownloader::DaapDownloader( KURL::List urls ) : Job( "DaapDownloader" ) + , m_urls( urls ) + , m_ready( false ) + , m_successful( false ) + , m_errorOccured( false ) +{ + // setDescription( i18n( "Downloading song from remote computer." ) ); //no new strings,uncomment after string freeze + setDescription( i18n( "Downloading Media..." ) ); +} + +bool +DaapDownloader::doJob() +{ + DEBUG_BLOCK + KURL::List::iterator urlIt = m_urls.begin(); + Daap::ContentFetcher* http = new Daap::ContentFetcher( (*urlIt).host(), (*urlIt).port(), QString(), this ); + connect( http, SIGNAL( requestFinished( int, bool ) ), this, SLOT( downloadFinished( int, bool ) ) ); + connect( http, SIGNAL( dataReadProgress( int, int ) ), this, SLOT( dataReadProgress( int, int ) ) ); + connect( http, SIGNAL( httpError( const QString& ) ), this, SLOT( downloadFailed( const QString& ) ) ); + while( !isAborted() && !m_errorOccured && urlIt != m_urls.end() ) + { + m_ready = false; + debug() << "downloading " << (*urlIt).path() << endl; + setProgressTotalSteps( 100 ); + KTempFile* tempNewFile = new KTempFile( QString(), '.' + QFileInfo( (*urlIt).path() ).extension() ); + tempNewFile->setAutoDelete( true ); + m_tempFileList.append( tempNewFile ); + http->getDaap( (*urlIt).path() + (*urlIt).query(), tempNewFile->file() ); + while( !m_ready && !isAborted() ) + { + msleep( 100 ); //Sleep 100 msec + } + debug() << "finished " << (*urlIt).path() << endl; + ++urlIt; + } + debug() << "returning " << m_successful << endl; + http->deleteLater(); + http = 0; + return m_successful; +} + +void +DaapDownloader::downloadFinished( int /*id*/, bool error ) +{ + DEBUG_BLOCK + m_tempFileList.last()->close(); + setProgress100Percent(); //just to make sure + m_successful = !error; + m_ready = true; +} + +void +DaapDownloader::completeJob() +{ + DEBUG_BLOCK + KURL path; + KURL::List tempUrlList; + for( QValueList<KTempFile*>::Iterator itTemps = m_tempFileList.begin(); itTemps != m_tempFileList.end(); ++itTemps ) + { + path.setPath( (*itTemps)->name() ); + tempUrlList << path; + } + CollectionView::instance()->organizeFiles( tempUrlList, i18n( "Copy Files To Collection" ), false ); + for( QValueList<KTempFile*>::Iterator itTemps = m_tempFileList.begin(); itTemps != m_tempFileList.end(); ++itTemps ) + delete (*itTemps); //autodelete is true, so file is unlinked now + m_tempFileList.clear(); +} + +void +DaapDownloader::dataReadProgress( int done, int total ) +{ + setProgress( int( ( float(done) / float(total) ) * 100.0 ) ); +} + +void +DaapDownloader::downloadFailed( const QString & error ) +{ + // Amarok::StatusBar::instance()->longMessageThreadSafe( i18n( "An error occured while downloading from remote music server." ), Amarok::StatusBar::Error ); + DEBUG_BLOCK + debug() << "failed on " << error << endl; + m_successful = false; + m_errorOccured = true; + m_ready = true; +} + +#include "daapclient.moc" + +#endif /* AMAROK_DAAPCLIENT_CPP */ diff --git a/amarok/src/mediadevice/daap/daapclient.h b/amarok/src/mediadevice/daap/daapclient.h new file mode 100644 index 00000000..c3475de7 --- /dev/null +++ b/amarok/src/mediadevice/daap/daapclient.h @@ -0,0 +1,191 @@ +/*************************************************************************** + * copyright : (C) 2006 Ian Monroe <ian@monroe.nu> * + * (C) 2006 Seb Ruiz <me@sebruiz.net> * + **************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +#ifndef AMAROK_DAAPCLIENT_H +#define AMAROK_DAAPCLIENT_H + +#include "daapreader/reader.h" +#include "mediabrowser.h" +#include "threadmanager.h" + +#include <kdeversion.h> +#include <kdialogbase.h> +#include <ktempfile.h> + +#define DNSSD_SUPPORT KDE_IS_VERSION(3,4,0) + +#if DNSSD_SUPPORT + #include <dnssd/remoteservice.h> //for DNSSD::RemoteService::Ptr +#else +namespace DNSSD { + namespace RemoteService { + class Ptr {}; //HACK Dummy class, so that daapclient.moc compiles + } +} +#endif + +namespace DNSSD { + class ServiceBrowser; +} + +class AddHostBase; +class MediaItem; +class ServerItem; +class DaapServer; + +class KURL; + +class QCheckBox; +class QString; +class QTimer; + +class DaapClient : public MediaDevice +{ + Q_OBJECT + public: + struct ServerInfo + { + ServerInfo() : sessionId( -1 ), revisionID( 10 ) { } + int sessionId; + int revisionID; + }; + + DaapClient(); + virtual ~DaapClient(); + bool isConnected(); + + virtual bool needsManualConfig() { return true; } + virtual void addConfigElements ( QWidget * /*parent*/ ); + virtual void removeConfigElements( QWidget * /*parent*/ ); + virtual void applyConfig(); + virtual void loadConfig(); + + + int incRevision( const QString& host ); + int getSession( const QString& host ); + KURL getProxyUrl( const KURL& url ); + void customClicked(); + bool autoConnect() { return true; } + + public slots: + void passwordPrompt(); + + protected: + bool getCapacity( KIO::filesize_t *total, KIO::filesize_t *available ); + void rmbPressed( QListViewItem* qitem, const QPoint& point, int ); + bool lockDevice( bool tryOnly = false ); + void unlockDevice(); + bool openDevice( bool silent=false ); + bool closeDevice(); + void synchronizeDevice(); + MediaItem* copyTrackToDevice(const MetaBundle& bundle); + MediaItem* trackExists( const MetaBundle& ); + virtual int deleteItemFromDevice( MediaItem *item, int flags=DeleteTrack ); + + private slots: + void serverOffline( DNSSD::RemoteService::Ptr ); + void foundDaap( DNSSD::RemoteService::Ptr ); + void resolvedDaap( bool ); + void createTree( const QString& host, Daap::SongList bundles ); + void broadcastButtonToggled(); + + private: + ServerItem* newHost( const QString& serviceName, const QString& host, const QString& ip, const Q_INT16 port ); + void downloadSongs( KURL::List urls ); + QString resolve( const QString& hostname ); +#if DNSSD_SUPPORT + QString serverKey( const DNSSD::RemoteService* service ) const; + DNSSD::ServiceBrowser* m_browser; +#endif + /// @return true if track is already in the local collection + const bool trackExistsInCollection( MetaBundle *bundle ); + + bool m_connected; + QMap<QString, ServerInfo*> m_servers; + QMap<QString, ServerItem*> m_serverItemMap; + + DaapServer *m_sharingServer; + QCheckBox *m_broadcastServerCheckBox; + QCheckBox *m_removeDuplicatesCheckBox; + bool m_broadcastServer; + bool m_removeDuplicates; + KToolBarButton *m_broadcastButton; + + // if set to true, we don't display items that the user already has in the collection + bool m_hideMusicOwned; +}; + +class ServerItem : public QObject, public MediaItem +{ + Q_OBJECT + + public: + ServerItem( QListView* parent, DaapClient* client, const QString& ip, Q_UINT16 port, const QString& title, const QString& host ); + ~ServerItem(); + void setOpen( bool o ); + void resetTitle() { setText( 0, m_title ); } + void unLoaded() { m_loaded = false; } + void setReader( Daap::Reader* reader) { m_reader = reader; } + Daap::Reader* getReader() const { return m_reader; } + + void startAnimation(); + void stopAnimation(); + + QString key() const { return key( m_host, m_port ); } + void reset(); + static QString key( const QString& host, Q_UINT16 port ) { return host + ':' + QString::number( port ); } + public slots: + void httpError( const QString& ); + private slots: + void slotAnimation(); + + private: + DaapClient *m_daapClient; + Daap::Reader *m_reader; + const QString m_ip; + const Q_UINT16 m_port; + const QString m_title; + const QString m_host; + bool m_loaded; + + QPixmap *m_loading1, *m_loading2; //icons for loading animation + QTimer m_animationTimer; + uint m_iconCounter; + + static const int ANIMATION_INTERVAL = 250; +}; + +class DaapDownloader : public ThreadManager::Job +{ +Q_OBJECT +public: + DaapDownloader( KURL::List urls ); + + virtual bool doJob(); + + virtual void completeJob(); + +private slots: + void downloadFinished( int id, bool error ); + void dataReadProgress( int done, int total ); + void downloadFailed( const QString &error ); + +private: + KURL::List m_urls; + QValueList<KTempFile*> m_tempFileList; + bool m_ready; + bool m_successful; + bool m_errorOccured; +}; + +#endif /*AMAROK_DAAPCLIENT_H*/ diff --git a/amarok/src/mediadevice/daap/daapreader/Makefile.am b/amarok/src/mediadevice/daap/daapreader/Makefile.am new file mode 100644 index 00000000..061ef940 --- /dev/null +++ b/amarok/src/mediadevice/daap/daapreader/Makefile.am @@ -0,0 +1,21 @@ +noinst_LTLIBRARIES = libdaapreader.la + +INCLUDES = \ + -I$(top_srcdir)/amarok/src \ + $(all_includes) + +libdaapreader_la_SOURCES = reader.cpp + +libdaapreader_la_LIBADD = \ + $(LIB_QT) \ + $(LIB_KDECORE) \ + $(LIB_KDEUI) \ + $(top_builddir)/amarok/src/mediadevice/daap/daapreader/authentication/libauthentication.la + +libdaapreader_la_LDFLAGS = $(all_libraries) $(KDE_RPATH) + +METASOURCES = AUTO + +noinst_HEADERS = reader.h + +SUBDIRS = authentication diff --git a/amarok/src/mediadevice/daap/daapreader/authentication/Makefile.am b/amarok/src/mediadevice/daap/daapreader/authentication/Makefile.am new file mode 100644 index 00000000..425c1951 --- /dev/null +++ b/amarok/src/mediadevice/daap/daapreader/authentication/Makefile.am @@ -0,0 +1,22 @@ +noinst_LTLIBRARIES = libauthentication.la + +INCLUDES = \ + -I$(top_srcdir)/amarok/src \ + -I$(top_srcdir)/amarok/src/amarokcore \ + -I$(top_srcdir)/amarok/src/mediadevice \ + -I$(top_srcdir)/amarok/src/mediadevice/daap \ + -I$(top_srcdir)/amarok/src/mediadevice/daap/daapreader \ + $(all_includes) + +libauthentication_la_SOURCES = contentfetcher.cpp md5.c hasher.c + +libauthentication_la_LIBADD = \ + $(LIB_QT) \ + $(LIB_KDECORE) \ + $(LIB_KDEUI) + +libauthentication_la_LDFLAGS = $(all_libraries) $(KDE_RPATH) + +METASOURCES = AUTO + +noinst_HEADERS = contentfetcher.h md5.h hasher.h portability.h diff --git a/amarok/src/mediadevice/daap/daapreader/authentication/contentfetcher.cpp b/amarok/src/mediadevice/daap/daapreader/authentication/contentfetcher.cpp new file mode 100644 index 00000000..ced4c825 --- /dev/null +++ b/amarok/src/mediadevice/daap/daapreader/authentication/contentfetcher.cpp @@ -0,0 +1,97 @@ +/*************************************************************************** + * copyright : (C) 2006 Ian Monroe <ian@monroe.nu> * + **************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "contentfetcher.h" +#include "debug.h" +#include "hasher.h" + +#include <qbuffer.h> +#include <qsocket.h> +#include <qdatastream.h> +#include <qhttp.h> + +#include <kfilterdev.h> +#include <kmdcodec.h> + +using namespace Daap; +int ContentFetcher::s_requestId = 10; + +ContentFetcher::ContentFetcher( const QString & hostname, Q_UINT16 port, const QString& password, QObject * parent, const char * name ) + : QHttp(hostname, port, parent, name) + , m_hostname( hostname ) + , m_port( port ) + , m_selfDestruct( false ) +{ + connect( this, SIGNAL( stateChanged( int ) ), this , SLOT( checkForErrors( int ) ) ); + QCString pass = password.utf8(); + if( !password.isNull() ) + { + m_authorize = "Basic " + KCodecs::base64Encode( "none:" + pass ); + } +} + +ContentFetcher::~ContentFetcher() +{ } + +QDataStream& +ContentFetcher::results() +{ + QBuffer* bytes = new QBuffer( readAll() ); + QIODevice* stream = KFilterDev::device( bytes, "application/x-gzip", false ); + stream->open( IO_ReadOnly ); + QDataStream* ds = new QDataStream( stream ); + return *ds; +} + +void +ContentFetcher::getDaap( const QString & command, QIODevice* musicFile /*= 0*/ ) +{ + QHttpRequestHeader header( "GET", command ); + char hash[33] = {0}; + GenerateHash(3, reinterpret_cast<const unsigned char*>(command.ascii()), 2, reinterpret_cast<unsigned char*>(hash), 0 /*s_requestId*/); + + if( !m_authorize.isEmpty() ) + { + header.setValue( "Authorization", m_authorize ); + } + + header.setValue( "Host", m_hostname + QString::number( m_port ) ); + header.setValue( "Client-DAAP-Request-ID", "0"/*QString::number( s_requestId )*/ ); + header.setValue( "Client-DAAP-Access-Index", "2" ); + header.setValue( "Client-DAAP-Validation", hash ); + header.setValue( "Client-DAAP-Version", "3.0" ); + header.setValue( "User-Agent", "iTunes/4.6 (Windows; N)" ); + header.setValue( "Accept", "*/*" ); + header.setValue( "Accept-Encoding", "gzip" ); + + request( header, 0, musicFile ); +} + +/** + * QHttp enjoys forgetting to emit a requestFinished when there's an error + * This gets around that. + */ +void +ContentFetcher::checkForErrors( int /*state*/ ) +{ + if( !m_selfDestruct && error() != 0 ) + { + debug() << "there is an error? " << error() << " " << errorString() << endl; + m_selfDestruct = true; + emit httpError( errorString() ); + } +} + + +#include "contentfetcher.moc" + diff --git a/amarok/src/mediadevice/daap/daapreader/authentication/contentfetcher.h b/amarok/src/mediadevice/daap/daapreader/authentication/contentfetcher.h new file mode 100644 index 00000000..bf299a1f --- /dev/null +++ b/amarok/src/mediadevice/daap/daapreader/authentication/contentfetcher.h @@ -0,0 +1,57 @@ +/*************************************************************************** + * copyright : (C) 2006 Ian Monroe <ian@monroe.nu> * + **************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +#ifndef DAAPCONTENTFETCHER_H +#define DAAPCONTENTFETCHER_H + +#include <qhttp.h> + +class QDataStream; +class QFile; + +namespace Daap { + +/** + Inspired by a daapsharp class of the same name. Basically it adds all the silly headers + that DAAP needs + @author Ian Monroe <ian@monroe.nu> +*/ +class ContentFetcher : public QHttp +{ + Q_OBJECT + + public: + ContentFetcher( const QString & hostname, Q_UINT16 port, const QString& password, QObject * parent = 0, const char * name = 0 ); + ~ContentFetcher(); + + void getDaap( const QString & command, QIODevice* musicFile = 0 ); + QDataStream& results(); + + private slots: + void checkForErrors( int state ); + + signals: + void httpError( const QString& ); + + private: + QString m_hostname; + Q_UINT16 m_port; + QCString m_authorize; + bool m_selfDestruct; + static int s_requestId; //! Apple needs this for some reason +}; + + + +} + +#endif diff --git a/amarok/src/mediadevice/daap/daapreader/authentication/hasher.c b/amarok/src/mediadevice/daap/daapreader/authentication/hasher.c new file mode 100644 index 00000000..daf215b7 --- /dev/null +++ b/amarok/src/mediadevice/daap/daapreader/authentication/hasher.c @@ -0,0 +1,205 @@ +/* Copyright (c) 2004 David Hammerton + * david@crazney.net + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#include <stdio.h> +#include <string.h> +#include "hasher.h" +#include "md5.h" + +static int staticHashDone = 0; +static char staticHash_42[256*65] = {0}; +static char staticHash_45[256*65] = {0}; + +static const char hexchars[] = "0123456789ABCDEF"; +static const char appleCopyright[] = "Copyright 2003 Apple Computer, Inc."; + +static void DigestToString(const unsigned char *digest, char *string) +{ + int i; + for (i = 0; i < 16; i++) + { + unsigned char tmp = digest[i]; + string[i*2+1] = hexchars[tmp & 0x0f]; + string[i*2] = hexchars[(tmp >> 4) & 0x0f]; + } +} + +static void GenerateStatic_42() +{ + MD5_CTX ctx; + unsigned char *p = staticHash_42; + int i; + char buf[16]; + + for (i = 0; i < 256; i++) + { + OpenDaap_MD5Init(&ctx, 0); + +#define MD5_STRUPDATE(str) OpenDaap_MD5Update(&ctx, str, strlen(str)) + + if ((i & 0x80) != 0) + MD5_STRUPDATE("Accept-Language"); + else + MD5_STRUPDATE("user-agent"); + + if ((i & 0x40) != 0) + MD5_STRUPDATE("max-age"); + else + MD5_STRUPDATE("Authorization"); + + if ((i & 0x20) != 0) + MD5_STRUPDATE("Client-DAAP-Version"); + else + MD5_STRUPDATE("Accept-Encoding"); + + if ((i & 0x10) != 0) + MD5_STRUPDATE("daap.protocolversion"); + else + MD5_STRUPDATE("daap.songartist"); + + if ((i & 0x08) != 0) + MD5_STRUPDATE("daap.songcomposer"); + else + MD5_STRUPDATE("daap.songdatemodified"); + + if ((i & 0x04) != 0) + MD5_STRUPDATE("daap.songdiscnumber"); + else + MD5_STRUPDATE("daap.songdisabled"); + + if ((i & 0x02) != 0) + MD5_STRUPDATE("playlist-item-spec"); + else + MD5_STRUPDATE("revision-number"); + + if ((i & 0x01) != 0) + MD5_STRUPDATE("session-id"); + else + MD5_STRUPDATE("content-codes"); +#undef MD5_STRUPDATE + + OpenDaap_MD5Final(&ctx, buf); + DigestToString(buf, p); + p += 65; + } +} + +static void GenerateStatic_45() +{ + MD5_CTX ctx; + unsigned char *p = staticHash_45; + int i; + char buf[16]; + + for (i = 0; i < 256; i++) + { + OpenDaap_MD5Init(&ctx, 1); + +#define MD5_STRUPDATE(str) OpenDaap_MD5Update(&ctx, str, strlen(str)) + + if ((i & 0x40) != 0) + MD5_STRUPDATE("eqwsdxcqwesdc"); + else + MD5_STRUPDATE("op[;lm,piojkmn"); + + if ((i & 0x20) != 0) + MD5_STRUPDATE("876trfvb 34rtgbvc"); + else + MD5_STRUPDATE("=-0ol.,m3ewrdfv"); + + if ((i & 0x10) != 0) + MD5_STRUPDATE("87654323e4rgbv "); + else + MD5_STRUPDATE("1535753690868867974342659792"); + + if ((i & 0x08) != 0) + MD5_STRUPDATE("Song Name"); + else + MD5_STRUPDATE("DAAP-CLIENT-ID:"); + + if ((i & 0x04) != 0) + MD5_STRUPDATE("111222333444555"); + else + MD5_STRUPDATE("4089961010"); + + if ((i & 0x02) != 0) + MD5_STRUPDATE("playlist-item-spec"); + else + MD5_STRUPDATE("revision-number"); + + if ((i & 0x01) != 0) + MD5_STRUPDATE("session-id"); + else + MD5_STRUPDATE("content-codes"); + + if ((i & 0x80) != 0) + MD5_STRUPDATE("IUYHGFDCXWEDFGHN"); + else + MD5_STRUPDATE("iuytgfdxwerfghjm"); + +#undef MD5_STRUPDATE + + OpenDaap_MD5Final(&ctx, buf); + DigestToString(buf, p); + p += 65; + } +} + +void GenerateHash(short version_major, + const unsigned char *url, unsigned char hashSelect, + unsigned char *outhash, + int request_id) +{ + char buf[16]; + MD5_CTX ctx; + + char *hashTable = (version_major == 3) ? + staticHash_45 : staticHash_42; + + if (!staticHashDone) + { + GenerateStatic_42(); + GenerateStatic_45(); + staticHashDone = 1; + } + + OpenDaap_MD5Init(&ctx, (version_major == 3) ? 1 : 0); + + OpenDaap_MD5Update(&ctx, url, strlen(url)); + OpenDaap_MD5Update(&ctx, appleCopyright, strlen(appleCopyright)); + + OpenDaap_MD5Update(&ctx, &hashTable[hashSelect * 65], 32); + + if (request_id && version_major == 3) + { + char scribble[20]; + sprintf(scribble, "%u", request_id); + OpenDaap_MD5Update(&ctx, scribble, strlen(scribble)); + } + + OpenDaap_MD5Final(&ctx, buf); + DigestToString(buf, outhash); +} + diff --git a/amarok/src/mediadevice/daap/daapreader/authentication/hasher.h b/amarok/src/mediadevice/daap/daapreader/authentication/hasher.h new file mode 100644 index 00000000..fa85ab8c --- /dev/null +++ b/amarok/src/mediadevice/daap/daapreader/authentication/hasher.h @@ -0,0 +1,43 @@ +/* Copyright (c) 2004 David Hammerton + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + */ + + +#ifndef _HASHER_H +#define _HASHER_H + +#ifdef __cplusplus + extern "C" { +#endif + +void GenerateHash(short version_major, + const unsigned char *url, unsigned char hashSelect, + unsigned char *outhash, + int request_id); + +#ifdef __cplusplus + } +#endif + +#endif /* _HASHER_H */ + diff --git a/amarok/src/mediadevice/daap/daapreader/authentication/md5.c b/amarok/src/mediadevice/daap/daapreader/authentication/md5.c new file mode 100644 index 00000000..252fff4c --- /dev/null +++ b/amarok/src/mediadevice/daap/daapreader/authentication/md5.c @@ -0,0 +1,288 @@ +/* Parts Copyright 2004 David Hammerton <crazney@crazney.net> + * Parts found in the public domain with no copyright claim. + * + * see rfc1321 + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#include <string.h> +#include "md5.h" + +/* +* This code implements the MD5 message-digest algorithm. +* The algorithm is due to Ron Rivest. This code was +* written by Colin Plumb in 1993, no copyright is claimed. +* This code is in the public domain; do with it what you wish. +* +* Equivalent code is available from RSA Data Security, Inc. +* This code has been tested against that, and is equivalent, +* except that you don't need to include two pages of legalese +* with every copy. +* +* To compute the message digest of a chunk of bytes, declare an MD5Context +* structure, pass it to OpenDaap_MD5Init, call OpenDaap_MD5Update as needed +* on buffers full of bytes, and then call OpenDaap_MD5Final, which will fill +* a supplied 16-byte array with the digest. +*/ +static void MD5Transform(u_int32_t buf[4], u_int32_t const in[16], int apple_ver); +/* for some reason we still have to reverse bytes on bigendian machines + * I don't really know why... but otherwise it fails.. + * Any MD5 gurus out there know why??? + */ +#if 0 /*ndef WORDS_BIGENDIAN was: HIGHFIRST */ +#define byteReverse(buf, len) /* Nothing */ +#else +static void byteReverse(unsigned char *buf, unsigned longs); + +#ifndef ASM_MD5 +/* +* Note: this code is harmless on little-endian machines. +*/ +static void byteReverse(unsigned char *buf, unsigned longs) +{ + u_int32_t t; + do { + t = (u_int32_t) ((unsigned) buf[3] << 8 | buf[2]) << 16 | + ((unsigned) buf[1] << 8 | buf[0]); + *(u_int32_t *) buf = t; + buf += 4; + } while (--longs); +} +#endif +#endif + + +void OpenDaap_MD5Init(MD5_CTX *ctx, int apple_ver) +{ + memset(ctx, 0, sizeof(MD5_CTX)); + ctx->buf[0] = 0x67452301; + ctx->buf[1] = 0xefcdab89; + ctx->buf[2] = 0x98badcfe; + ctx->buf[3] = 0x10325476; + + ctx->bits[0] = 0; + ctx->bits[1] = 0; + + ctx->apple_ver = apple_ver; +} + +void OpenDaap_MD5Update(MD5_CTX *ctx, unsigned char const *buf, unsigned int len) +{ + u_int32_t t; + + /* Update bitcount */ + + t = ctx->bits[0]; + if ((ctx->bits[0] = t + ((u_int32_t) len << 3)) < t) + ctx->bits[1]++; /* Carry from low to high */ + ctx->bits[1] += len >> 29; + + t = (t >> 3) & 0x3f; /* Bytes already in shsInfo->data */ + + /* Handle any leading odd-sized chunks */ + + if (t) { + unsigned char *p = (unsigned char *) ctx->in + t; + + t = 64 - t; + if (len < t) { + memcpy(p, buf, len); + return; + } + memcpy(p, buf, t); + byteReverse(ctx->in, 16); + MD5Transform(ctx->buf, (u_int32_t *) ctx->in, ctx->apple_ver); + buf += t; + len -= t; + } + /* Process data in 64-byte chunks */ + + while (len >= 64) { + memcpy(ctx->in, buf, 64); + byteReverse(ctx->in, 16); + MD5Transform(ctx->buf, (u_int32_t *) ctx->in, ctx->apple_ver); + buf += 64; + len -= 64; + } + + /* Handle any remaining bytes of data. */ + + memcpy(ctx->in, buf, len); + +} + +void OpenDaap_MD5Final(MD5_CTX *ctx, unsigned char digest[16]) +{ + unsigned count; + unsigned char *p; + + /* Compute number of bytes mod 64 */ + count = (ctx->bits[0] >> 3) & 0x3F; + + /* Set the first char of padding to 0x80. This is safe since there is + always at least one byte free */ + p = ctx->in + count; + *p++ = 0x80; + + /* Bytes of padding needed to make 64 bytes */ + count = 64 - 1 - count; + + /* Pad out to 56 mod 64 */ + if (count < 8) { + /* Two lots of padding: Pad the first block to 64 bytes */ + memset(p, 0, count); + byteReverse(ctx->in, 16); + MD5Transform(ctx->buf, (u_int32_t *) ctx->in, ctx->apple_ver); + + /* Now fill the next block with 56 bytes */ + memset(ctx->in, 0, 56); + } else { + /* Pad block to 56 bytes */ + memset(p, 0, count - 8); + } + byteReverse(ctx->in, 14); + + /* Append length in bits and transform */ + ((u_int32_t *) ctx->in)[14] = ctx->bits[0]; + ((u_int32_t *) ctx->in)[15] = ctx->bits[1]; + + MD5Transform(ctx->buf, (u_int32_t *) ctx->in, ctx->apple_ver); + byteReverse((unsigned char *) ctx->buf, 4); + memcpy(digest, ctx->buf, 16); + memset(ctx, 0, sizeof(ctx)); /* In case it's sensitive */ +} + +#ifndef ASM_MD5 + +/* The four core functions - F1 is optimized somewhat */ + +/* #define F1(x, y, z) (x & y | ~x & z) */ +#define F1(x, y, z) (z ^ (x & (y ^ z))) +#define F2(x, y, z) F1(z, x, y) +#define F3(x, y, z) (x ^ y ^ z) +#define F4(x, y, z) (y ^ (x | ~z)) + +/* This is the central step in the MD5 algorithm. */ +#define MD5STEP(f, w, x, y, z, data, s) \ +( w += f(x, y, z) + data, w = w<<s | w>>(32-s), w += x ) + +/* +* The core of the MD5 algorithm, this alters an existing MD5 hash to reflect +* the addition of 16 longwords of new data. OpenDaap_MD5Update blocks the +* data and converts bytes into longwords for this routine. +*/ +static void MD5Transform(u_int32_t buf[4], u_int32_t const in[16], int apple_ver) +{ + u_int32_t a, b, c, d; + + a = buf[0]; + b = buf[1]; + c = buf[2]; + d = buf[3]; + + MD5STEP(F1, a, b, c, d, in[0] + 0xd76aa478, 7); + MD5STEP(F1, d, a, b, c, in[1] + 0xe8c7b756, 12); + MD5STEP(F1, c, d, a, b, in[2] + 0x242070db, 17); + MD5STEP(F1, b, c, d, a, in[3] + 0xc1bdceee, 22); + MD5STEP(F1, a, b, c, d, in[4] + 0xf57c0faf, 7); + MD5STEP(F1, d, a, b, c, in[5] + 0x4787c62a, 12); + MD5STEP(F1, c, d, a, b, in[6] + 0xa8304613, 17); + MD5STEP(F1, b, c, d, a, in[7] + 0xfd469501, 22); + MD5STEP(F1, a, b, c, d, in[8] + 0x698098d8, 7); + MD5STEP(F1, d, a, b, c, in[9] + 0x8b44f7af, 12); + MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17); + MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22); + MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122, 7); + MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12); + MD5STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17); + MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22); + + MD5STEP(F2, a, b, c, d, in[1] + 0xf61e2562, 5); + MD5STEP(F2, d, a, b, c, in[6] + 0xc040b340, 9); + MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14); + MD5STEP(F2, b, c, d, a, in[0] + 0xe9b6c7aa, 20); + MD5STEP(F2, a, b, c, d, in[5] + 0xd62f105d, 5); + MD5STEP(F2, d, a, b, c, in[10] + 0x02441453, 9); + MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14); + MD5STEP(F2, b, c, d, a, in[4] + 0xe7d3fbc8, 20); + MD5STEP(F2, a, b, c, d, in[9] + 0x21e1cde6, 5); + MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6, 9); + MD5STEP(F2, c, d, a, b, in[3] + 0xf4d50d87, 14); + + if (apple_ver == 1) + { + MD5STEP(F2, b, c, d, a, in[8] + 0x445a14ed, 20); + } + else + { + MD5STEP(F2, b, c, d, a, in[8] + 0x455a14ed, 20); + } + MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905, 5); + MD5STEP(F2, d, a, b, c, in[2] + 0xfcefa3f8, 9); + MD5STEP(F2, c, d, a, b, in[7] + 0x676f02d9, 14); + MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20); + + MD5STEP(F3, a, b, c, d, in[5] + 0xfffa3942, 4); + MD5STEP(F3, d, a, b, c, in[8] + 0x8771f681, 11); + MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16); + MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23); + MD5STEP(F3, a, b, c, d, in[1] + 0xa4beea44, 4); + MD5STEP(F3, d, a, b, c, in[4] + 0x4bdecfa9, 11); + MD5STEP(F3, c, d, a, b, in[7] + 0xf6bb4b60, 16); + MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23); + MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6, 4); + MD5STEP(F3, d, a, b, c, in[0] + 0xeaa127fa, 11); + MD5STEP(F3, c, d, a, b, in[3] + 0xd4ef3085, 16); + MD5STEP(F3, b, c, d, a, in[6] + 0x04881d05, 23); + MD5STEP(F3, a, b, c, d, in[9] + 0xd9d4d039, 4); + MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11); + MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16); + MD5STEP(F3, b, c, d, a, in[2] + 0xc4ac5665, 23); + + MD5STEP(F4, a, b, c, d, in[0] + 0xf4292244, 6); + MD5STEP(F4, d, a, b, c, in[7] + 0x432aff97, 10); + MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15); + MD5STEP(F4, b, c, d, a, in[5] + 0xfc93a039, 21); + MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3, 6); + MD5STEP(F4, d, a, b, c, in[3] + 0x8f0ccc92, 10); + MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15); + MD5STEP(F4, b, c, d, a, in[1] + 0x85845dd1, 21); + MD5STEP(F4, a, b, c, d, in[8] + 0x6fa87e4f, 6); + MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10); + MD5STEP(F4, c, d, a, b, in[6] + 0xa3014314, 15); + MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21); + MD5STEP(F4, a, b, c, d, in[4] + 0xf7537e82, 6); + MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10); + MD5STEP(F4, c, d, a, b, in[2] + 0x2ad7d2bb, 15); + MD5STEP(F4, b, c, d, a, in[9] + 0xeb86d391, 21); + + buf[0] += a; + buf[1] += b; + buf[2] += c; + buf[3] += d; +} + +#endif + + + diff --git a/amarok/src/mediadevice/daap/daapreader/authentication/md5.h b/amarok/src/mediadevice/daap/daapreader/authentication/md5.h new file mode 100644 index 00000000..5d86885e --- /dev/null +++ b/amarok/src/mediadevice/daap/daapreader/authentication/md5.h @@ -0,0 +1,17 @@ +#ifndef __MD5_H__ +#define __MD5_H__ + +#include "portability.h" + +typedef struct { + u_int32_t buf[4]; + u_int32_t bits[2]; + unsigned char in[64]; + int apple_ver; +} MD5_CTX; + +void OpenDaap_MD5Init(MD5_CTX *, int apple_ver); +void OpenDaap_MD5Update(MD5_CTX *, const unsigned char *, unsigned int); +void OpenDaap_MD5Final(MD5_CTX *, unsigned char digest[16]); + +#endif /* __WINE_MD5_H__ */ diff --git a/amarok/src/mediadevice/daap/daapreader/authentication/portability.h b/amarok/src/mediadevice/daap/daapreader/authentication/portability.h new file mode 100644 index 00000000..fd19cc90 --- /dev/null +++ b/amarok/src/mediadevice/daap/daapreader/authentication/portability.h @@ -0,0 +1,74 @@ +/* portability stuff + * + * Copyright (c) 2004 David Hammerton + * crazney@crazney.net + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + */ + + +#ifndef _PORTABILITY_H +#define _PORTABILITY_H + +#if !defined(WIN32) /* POSIX */ + +#define SYSTEM_POSIX + +#include <sys/types.h> +#include <config.h> + +#if !defined(HAVE_U_INT64_T) && defined(HAVE_UINT64_T) + typedef uint64_t u_int64_t; +#endif +#if !defined(HAVE_U_INT32_T) && defined(HAVE_UINT32_T) + typedef uint32_t u_int32_t; +#endif +#if !defined(HAVE_U_INT16_T) && defined(HAVE_UINT16_T) + typedef uint16_t u_int16_t; +#endif +#if !defined(HAVE_U_INT8_T) && defined(HAVE_UINT8_T) + typedef uint8_t u_int8_t; +#endif + +#else /* WIN32 */ + +#define SYSTEM_WIN32 + +#include <windows.h> +#include <time.h> +typedef INT64 int64_t; +typedef UINT64 u_int64_t; + +typedef signed int int32_t; +typedef unsigned int u_int32_t; + +typedef signed short int16_t; +typedef unsigned short u_int16_t; + +typedef signed char int8_t; +typedef unsigned char u_int8_t; + +#endif + +#endif /* _PORTABILITY_H */ + + diff --git a/amarok/src/mediadevice/daap/daapreader/reader.cpp b/amarok/src/mediadevice/daap/daapreader/reader.cpp new file mode 100644 index 00000000..1fbbf9a3 --- /dev/null +++ b/amarok/src/mediadevice/daap/daapreader/reader.cpp @@ -0,0 +1,425 @@ +/*************************************************************************** + * copyright : (C) 2006 Ian Monroe <ian@monroe.nu> * + **************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "authentication/contentfetcher.h" +#include "debug.h" +#include "reader.h" +#include "metabundle.h" +#include "qstringx.h" + +#include <qcstring.h> +#include <qdatastream.h> +#include <qptrlist.h> +#include <qvaluevector.h> +#include <qvariant.h> + +using namespace Daap; + +QMap<QString, Code> Reader::s_codes; + + +Reader::Reader(const QString& host, Q_UINT16 port, ServerItem* root, const QString& password, QObject* parent, const char* name) + : QObject(parent, name) + , m_host( host ) + , m_port( port ) + , m_sessionId( -1 ) + , m_root( root ) + , m_password( password ) +{ + + + if( s_codes.size() == 0 ) + { + s_codes["mtco"] = Code( "dmap.specifiedtotalcount", LONG ); + s_codes["mdcl"] = Code( "dmap.dictionary", CONTAINER ); + s_codes["aeGI"] = Code( "com.apple.itunes.itms-genreid", LONG ); + s_codes["aeNV"] = Code( "com.apple.itunes.norm-volume", LONG ); + s_codes["astn"] = Code( "daap.songtracknumber", SHORT ); + s_codes["abal"] = Code( "daap.browsealbumlisting", CONTAINER ); + s_codes["asco"] = Code( "daap.songcompilation", CHAR ); + s_codes["aeSP"] = Code( "com.apple.itunes.smart-playlist", CHAR ); + s_codes["ascp"] = Code( "daap.songcomposer", STRING ); + s_codes["aseq"] = Code( "daap.songeqpreset", STRING ); + s_codes["abpl"] = Code( "daap.baseplaylist", CHAR ); + s_codes["msqy"] = Code( "dmap.supportsquery", CHAR ); + s_codes["aeCI"] = Code( "com.apple.itunes.itms-composerid", LONG ); + s_codes["mcnm"] = Code( "dmap.contentcodesnumber", LONG ); + s_codes["abro"] = Code( "daap.databasebrowse", CONTAINER ); + s_codes["assz"] = Code( "daap.songsize", LONG ); + s_codes["abcp"] = Code( "daap.browsecomposerlisting", CONTAINER ); + s_codes["aeAI"] = Code( "com.apple.itunes.itms-artistid", LONG ); + s_codes["aeHV"] = Code( "com.apple.itunes.has-video", CHAR ); + s_codes["msts"] = Code( "dmap.statusstring", STRING ); + s_codes["msas"] = Code( "dmap.authenticationschemes", LONG ); + s_codes["ascr"] = Code( "daap.songcontentrating", CHAR ); + s_codes["aePI"] = Code( "com.apple.itunes.itms-playlistid", LONG ); + s_codes["mstt"] = Code( "dmap.status", LONG ); + s_codes["msix"] = Code( "dmap.supportsindex", CHAR ); + s_codes["msrs"] = Code( "dmap.supportsresolve", CHAR ); + s_codes["mccr"] = Code( "dmap.contentcodesresponse", CONTAINER ); + s_codes["asdk"] = Code( "daap.songdatakind", CHAR ); + s_codes["asar"] = Code( "daap.songartist", STRING ); + s_codes["ascs"] = Code( "daap.songcodecsubtype", LONG ); + s_codes["msau"] = Code( "dmap.authenticationmethod", CHAR ); + s_codes["aeSU"] = Code( "com.apple.itunes.season-num", LONG ); + s_codes["arif"] = Code( "daap.resolveinfo", CONTAINER ); + s_codes["asct"] = Code( "daap.songcategory", STRING ); + s_codes["asfm"] = Code( "daap.songformat", STRING ); + s_codes["aeEN"] = Code( "com.apple.itunes.episode-num-str", STRING ); + s_codes["apsm"] = Code( "daap.playlistshufflemode", CHAR ); + s_codes["abar"] = Code( "daap.browseartistlisting", CONTAINER ); + s_codes["mslr"] = Code( "dmap.loginrequired", CHAR ); + s_codes["msex"] = Code( "dmap.supportsextensions", CHAR ); + s_codes["mudl"] = Code( "dmap.deletedidlisting", CONTAINER ); + s_codes["asdm"] = Code( "daap.songdatemodified", DATE ); + s_codes["asky"] = Code( "daap.songkeywords", STRING ); + s_codes["asul"] = Code( "daap.songdataurl", STRING ); + s_codes["aeSV"] = Code( "com.apple.itunes.music-sharing-version", LONG ); + s_codes["f\215ch"] = Code( "dmap.haschildcontainers", CHAR ); + s_codes["mlcl"] = Code( "dmap.listing", CONTAINER ); + s_codes["msrv"] = Code( "dmap.serverinforesponse", CONTAINER ); + s_codes["asdn"] = Code( "daap.songdiscnumber", SHORT ); + s_codes["astc"] = Code( "daap.songtrackcount", SHORT ); + s_codes["apso"] = Code( "daap.playlistsongs", CONTAINER ); + s_codes["ascd"] = Code( "daap.songcodectype", LONG ); + s_codes["minm"] = Code( "dmap.itemname", STRING ); + s_codes["mimc"] = Code( "dmap.itemcount", LONG ); + s_codes["mctc"] = Code( "dmap.containercount", LONG ); + s_codes["aeSF"] = Code( "com.apple.itunes.itms-storefrontid", LONG ); + s_codes["asrv"] = Code( "daap.songrelativevolume", SHORT ); + s_codes["msup"] = Code( "dmap.supportsupdate", CHAR ); + s_codes["mcna"] = Code( "dmap.contentcodesname", STRING ); + s_codes["agrp"] = Code( "daap.songgrouping", STRING ); + s_codes["mikd"] = Code( "dmap.itemkind", CHAR ); + s_codes["mupd"] = Code( "dmap.updateresponse", CONTAINER ); + s_codes["aeNN"] = Code( "com.apple.itunes.network-name", STRING ); + s_codes["asyr"] = Code( "daap.songyear", SHORT ); + s_codes["aeES"] = Code( "com.apple.itunes.episode-sort", LONG ); + s_codes["miid"] = Code( "dmap.itemid", LONG ); + s_codes["msbr"] = Code( "dmap.supportsbrowse", CHAR ); + s_codes["muty"] = Code( "dmap.updatetype", CHAR ); + s_codes["mcty"] = Code( "dmap.contentcodestype", SHORT ); + s_codes["aply"] = Code( "daap.databaseplaylists", CONTAINER ); + s_codes["aePP"] = Code( "com.apple.itunes.is-podcast-playlist", CHAR ); + s_codes["aeSI"] = Code( "com.apple.itunes.itms-songid", LONG ); + s_codes["assp"] = Code( "daap.songstoptime", LONG ); + s_codes["aslc"] = Code( "daap.songlongcontentdescription", STRING ); + s_codes["mcon"] = Code( "dmap.container", CONTAINER ); + s_codes["mlit"] = Code( "dmap.listingitem", CONTAINER ); + s_codes["asur"] = Code( "daap.songuserrating", CHAR ); + s_codes["mspi"] = Code( "dmap.supportspersistentids", CHAR ); + s_codes["assr"] = Code( "daap.songsamplerate", LONG ); + s_codes["asda"] = Code( "daap.songdateadded", DATE ); + s_codes["asbr"] = Code( "daap.songbitrate", SHORT ); + s_codes["mcti"] = Code( "dmap.containeritemid", LONG ); + s_codes["mpco"] = Code( "dmap.parentcontainerid", LONG ); + s_codes["msdc"] = Code( "dmap.databasescount", LONG ); + s_codes["mlog"] = Code( "dmap.loginresponse", CONTAINER ); + s_codes["mlid"] = Code( "dmap.sessionid", LONG ); + s_codes["musr"] = Code( "dmap.serverrevision", LONG ); + s_codes["asdb"] = Code( "daap.songdisabled", CHAR ); + s_codes["asdt"] = Code( "daap.songdescription", STRING ); + s_codes["mbcl"] = Code( "dmap.bag", CONTAINER ); + s_codes["msal"] = Code( "dmap.supportsautologout", CHAR ); + s_codes["mstm"] = Code( "dmap.timeoutinterval", LONG ); + s_codes["asdc"] = Code( "daap.songdisccount", SHORT ); + s_codes["asbt"] = Code( "daap.songbeatsperminute", SHORT ); + s_codes["asgn"] = Code( "daap.songgenre", STRING ); + s_codes["aprm"] = Code( "daap.playlistrepeatmode", CHAR ); + s_codes["asst"] = Code( "daap.songstarttime", LONG ); + s_codes["mper"] = Code( "dmap.persistentid", LONGLONG ); + s_codes["mrco"] = Code( "dmap.returnedcount", LONG ); + s_codes["mpro"] = Code( "dmap.protocolversion", DVERSION ); + s_codes["ascm"] = Code( "daap.songcomment", STRING ); + s_codes["aePC"] = Code( "com.apple.itunes.is-podcast", CHAR ); + s_codes["aeSN"] = Code( "com.apple.itunes.series-name", STRING ); + s_codes["arsv"] = Code( "daap.resolve", CONTAINER ); + s_codes["asal"] = Code( "daap.songalbum", STRING ); + s_codes["apro"] = Code( "daap.protocolversion", DVERSION ); + s_codes["avdb"] = Code( "daap.serverdatabases", CONTAINER ); + s_codes["aeMK"] = Code( "com.apple.itunes.mediakind", CHAR ); + s_codes["astm"] = Code( "daap.songtime", LONG ); + s_codes["adbs"] = Code( "daap.databasesongs", CONTAINER ); + s_codes["abgn"] = Code( "daap.browsegenrelisting", CONTAINER ); + s_codes["ascn"] = Code( "daap.songcontentdescription", STRING ); + } +} + +Reader::~Reader() +{ } + +void +Reader::logoutRequest() +{ + ContentFetcher* http = new ContentFetcher( m_host, m_port, m_password, this, "readerLogoutHttp" ); + connect( http, SIGNAL( httpError( const QString& ) ), this, SLOT( fetchingError( const QString& ) ) ); + connect( http, SIGNAL( requestFinished( int, bool ) ), this, SLOT( logoutRequest( int, bool ) ) ); + http->getDaap( "/logout?" + m_loginString ); +} + +void +Reader::logoutRequest( int, bool ) +{ + const_cast<QObject*>(sender())->deleteLater(); + deleteLater(); +} + +void +Reader::loginRequest() +{ + DEBUG_BLOCK + ContentFetcher* http = new ContentFetcher( m_host, m_port, m_password, this, "readerHttp"); + connect( http, SIGNAL( httpError( const QString& ) ), this, SLOT( fetchingError( const QString& ) ) ); + connect( http, SIGNAL( responseHeaderReceived( const QHttpResponseHeader & ) ) + , this, SLOT( loginHeaderReceived( const QHttpResponseHeader & ) ) ); + http->getDaap( "/login" ); +} + +void +Reader::loginHeaderReceived( const QHttpResponseHeader & resp ) +{ + DEBUG_BLOCK + ContentFetcher* http = (ContentFetcher*) sender(); + disconnect( http, SIGNAL( responseHeaderReceived( const QHttpResponseHeader & ) ) + , this, SLOT( loginHeaderReceived( const QHttpResponseHeader & ) ) ); + if( resp.statusCode() == 401 /*authorization required*/) + { + emit passwordRequired(); + http->deleteLater(); + return; + } + connect( http, SIGNAL( requestFinished( int, bool ) ), this, SLOT( loginFinished( int, bool ) ) ); +} + + +void +Reader::loginFinished( int /* id */, bool error ) +{ + DEBUG_BLOCK + ContentFetcher* http = (ContentFetcher*) sender(); + disconnect( http, SIGNAL( requestFinished( int, bool ) ), this, SLOT( loginFinished( int, bool ) ) ); + if( error ) + { + http->deleteLater(); + return; + } + Map loginResults = parse( http->results() , 0 ,true ); + + m_sessionId = loginResults["mlog"].asList()[0].asMap()["mlid"].asList()[0].asInt(); + m_loginString = "session-id=" + QString::number( m_sessionId ); + connect( http, SIGNAL( requestFinished( int, bool ) ), this, SLOT( updateFinished( int, bool ) ) ); + http->getDaap( "/update?" + m_loginString ); +} + +void +Reader::updateFinished( int /*id*/, bool error ) +{ + DEBUG_BLOCK + ContentFetcher* http = (ContentFetcher*) sender(); + disconnect( http, SIGNAL( requestFinished( int, bool ) ), this, SLOT( updateFinished( int, bool ) ) ); + if( error ) + { + http->deleteLater(); + warning() << "what is going on here? " << http->error() << endl; + return; + } + + Map updateResults = parse( http->results(), 0, true ); + m_loginString = m_loginString + "&revision-number=" + + QString::number( updateResults["mupd"].asList()[0].asMap()["musr"].asList()[0].asInt() ); + + connect( http, SIGNAL( requestFinished( int, bool ) ), this, SLOT( databaseIdFinished( int, bool ) ) ); + http->getDaap( "/databases?" + m_loginString ); + +} + +void +Reader::databaseIdFinished( int /*id*/, bool error ) +{ + ContentFetcher* http = (ContentFetcher*) sender(); + disconnect( http, SIGNAL( requestFinished( int, bool ) ), this, SLOT( databaseIdFinished( int, bool ) ) ); + if( error ) + { + http->deleteLater(); + return; + } + + Map dbIdResults = parse( http->results(), 0, true ); + m_databaseId = QString::number( dbIdResults["avdb"].asList()[0].asMap()["mlcl"].asList()[0].asMap()["mlit"].asList()[0].asMap()["miid"].asList()[0].asInt() ); + connect( http, SIGNAL( requestFinished( int, bool ) ), this, SLOT( songListFinished( int, bool ) ) ); + http->getDaap( QString("/databases/%1/items?type=music&meta=dmap.itemid,dmap.itemname,daap.songformat,daap.songartist,daap.songalbum,daap.songtime,daap.songtracknumber,daap.songcomment,daap.songyear,daap.songgenre&%2") + .arg( m_databaseId, m_loginString ) ); + +} + +void +Reader::songListFinished( int /*id*/, bool error ) +{ + ContentFetcher* http = (ContentFetcher*) sender(); + disconnect( http, SIGNAL( requestFinished( int, bool ) ), this, SLOT( songListFinished( int, bool ) ) ); + if( error ) + { + http->deleteLater(); + return; + } + + Map songResults = parse( http->results(), 0, true ); + + SongList result; + QValueList<QVariant> songList; + songList = songResults["adbs"].asList()[0].asMap()["mlcl"].asList()[0].asMap()["mlit"].asList(); + debug() << "songList.count() = " << songList.count() << endl; + QValueList<QVariant>::iterator it; + for( it = songList.begin(); it != songList.end(); ++it ) + { + MetaBundle* bundle = new MetaBundle(); + bundle->setTitle( (*it).asMap()["minm"].asList()[0].toString() ); +//input url: daap://host:port/databaseId/music.ext + bundle->setUrl( Amarok::QStringx("daap://%1:%2/%3/%4.%5").args( + QStringList() << m_host + << QString::number( m_port ) + << m_databaseId + << QString::number( (*it).asMap()["miid"].asList()[0].asInt() ) + << (*it).asMap()["asfm"].asList()[0].asString() ) ); + bundle->setLength( (*it).asMap()["astm"].asList()[0].toInt()/1000 ); + bundle->setTrack( (*it).asMap()["astn"].asList()[0].toInt() ); + + QString album = (*it).asMap()["asal"].asList()[0].toString(); + bundle->setAlbum( album ); + + QString artist = (*it).asMap()["asar"].asList()[0].toString(); + bundle->setArtist( artist ); + result[ artist.lower() ][ album.lower() ].append(bundle); + + bundle->setYear( (*it).asMap()["asyr"].asList()[0].toInt() ); + + bundle->setGenre( (*it).asMap()["asgn"].asList()[0].toString() ); + } + emit daapBundles( m_host , result ); + http->deleteLater(); +} + +Q_UINT32 +Reader::getTagAndLength( QDataStream &raw, char tag[5] ) +{ + tag[4] = 0; + raw.readRawBytes(tag, 4); + Q_UINT32 tagLength = 0; + raw >> tagLength; + return tagLength; +} + +Map +Reader::parse( QDataStream &raw, uint containerLength, bool first ) +{ +//DEBUG_BLOCK +/* http://daap.sourceforge.net/docs/index.html +0-3 Content code OSType (unsigned long), description of the contents of this chunk +4-7 Length Length of the contents of this chunk (not the whole chunk) +8- Data The data contained within the chunk */ + Map childMap; + uint index = 0; + while( (first ? !raw.atEnd() : ( index < containerLength ) ) ) + { + // debug() << "at index " << index << " of a total container size " << containerLength << endl; + char tag[5]; + Q_UINT32 tagLength = getTagAndLength( raw, tag ); + if( tagLength == 0 ) + { +// debug() << "tag " << tag << " has 0 length." << endl; + index += 8; + continue; + } +//#define DEBUGTAG( VAR ) debug() << tag << " has value " << VAR << endl; +#define DEBUGTAG( VAR ) + switch( s_codes[tag].type ) + { + case CHAR: { + Q_INT8 charData; + raw >> charData; DEBUGTAG( charData ) + addElement( childMap, tag, QVariant( static_cast<int>(charData) ) ); + } + break; + case SHORT: { + Q_INT16 shortData; + raw >> shortData; DEBUGTAG( shortData ) + addElement( childMap, tag, QVariant( static_cast<int>(shortData) ) ); + } + break; + case LONG: { + Q_INT32 longData; + raw >> longData; DEBUGTAG( longData ) + addElement( childMap, tag, QVariant( longData ) ); + } + break; + case LONGLONG: { + Q_INT64 longlongData; + raw >> longlongData; DEBUGTAG( longlongData ) + addElement( childMap, tag, QVariant( longlongData ) ); + } + break; + case STRING: { + QByteArray stringData(tagLength); + raw.readRawBytes( stringData.data(), tagLength ); DEBUGTAG( QString::fromUtf8( stringData, tagLength ) ) + addElement( childMap, tag, QVariant( QString::fromUtf8( stringData, tagLength ) ) ); + } + break; + case DATE: { + Q_INT64 dateData; + QDateTime date; + raw >> dateData; DEBUGTAG( dateData ) + date.setTime_t(dateData); + addElement( childMap, tag, QVariant( date ) ); + } + break; + case DVERSION: { + Q_INT16 major; + Q_INT8 minor; + Q_INT8 patchLevel; + raw >> major >> minor >> patchLevel; DEBUGTAG( patchLevel ) + QString version("%1.%2.%3"); + version.arg(major, minor, patchLevel); + addElement( childMap, tag, QVariant(version) ); + } + break; + case CONTAINER: { + DEBUGTAG( 11 ) + addElement( childMap, tag, QVariant( parse( raw, tagLength ) ) ); + } + break; + default: + warning() << tag << " doesn't work" << endl; + break; + } + index += tagLength + 8; + } + return childMap; +} + +void +Reader::addElement( Map &parentMap, char* tag, QVariant element ) +{ + if( !parentMap.contains( tag ) ) + parentMap[tag] = QVariant( QValueList<QVariant>() ); + + parentMap[tag].asList().append(element); +} + +void +Reader::fetchingError( const QString& error ) +{ + const_cast< QObject* >( sender() )->deleteLater(); + emit httpError( error ); +} +#include "reader.moc" + diff --git a/amarok/src/mediadevice/daap/daapreader/reader.h b/amarok/src/mediadevice/daap/daapreader/reader.h new file mode 100644 index 00000000..8b9f533b --- /dev/null +++ b/amarok/src/mediadevice/daap/daapreader/reader.h @@ -0,0 +1,111 @@ +/*************************************************************************** + * copyright : (C) 2006 Ian Monroe <ian@monroe.nu> * + **************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +#ifndef DAAPREADER_H +#define DAAPREADER_H + +#include <qobject.h> + +#include <kurl.h> + +class QString; + +template <class T> +class QPtrList; +class MetaBundle; +class ServerItem; +class QHttpResponseHeader; + +namespace Daap +{ + typedef QMap<QString, QVariant> Map; + + typedef QPtrList< MetaBundle > TrackList; + typedef QMap< QString, TrackList > AlbumList; + typedef QMap< QString, AlbumList > SongList; + +//typedef QMap<QString, QMap<QString, QPtrList<MetaBundle> > > SongList; + + enum ContentTypes { INVALID = 0, CHAR = 1, SHORT = 2, LONG = 5, LONGLONG = 7, + STRING = 9, DATE = 10, DVERSION = 11, CONTAINER = 12 }; + struct Code + { + Code() : type(INVALID) { } + Code( const QString& nName, ContentTypes nType ) : name( nName ), type( nType ) { } + ~Code() { } + QString name; + ContentTypes type; + }; + + + + /** + The nucleus of the DAAP client; composes queries and parses responses. + @author Ian Monroe <ian@monroe.nu> + */ + class Reader : public QObject + { + Q_OBJECT + + public: + Reader( const QString& host, Q_UINT16 port, ServerItem* root, + const QString& password, QObject* parent, const char* name ); + ~Reader(); + + //QPtrList<MetaBundle> getSongList(); + enum Options { SESSION_ID = 1, SERVER_VERSION = 2 }; + void loginRequest(); + void logoutRequest(); + ServerItem* rootMediaItem() const { return m_root; } + + int sessionId() const { return m_sessionId; } + QString host() const { return m_host; } + Q_UINT16 port() const { return m_port; } + public slots: + void logoutRequest(int, bool ); + void loginHeaderReceived( const QHttpResponseHeader& resp ); + void loginFinished( int id , bool error ); + void updateFinished( int id , bool error ); + void databaseIdFinished( int id , bool error ); + void songListFinished( int id, bool error ); + void fetchingError( const QString& error ); + + signals: + void daapBundles( const QString& host, Daap::SongList bundles ); + void httpError( const QString& ); + void passwordRequired(); + + private: + /** + * Make a map-vector tree out of the DAAP binary result + * @param raw stream of DAAP reply + * @param containerLength length of the container (or entire result) being analyzed + */ + static Map parse( QDataStream &raw, uint containerLength, bool first = false ); + static void addElement( Map &parentMap, char* tag, QVariant element ); //! supporter function for parse + static Q_UINT32 getTagAndLength( QDataStream &raw, char tag[5] ); + + static QMap<QString, Code> s_codes; + + QString m_host; + Q_UINT16 m_port; + QString m_loginString; + QString m_databaseId; + int m_sessionId; + ServerItem* m_root; + QString m_password; + + }; + +} + +#endif diff --git a/amarok/src/mediadevice/daap/daapserver.cpp b/amarok/src/mediadevice/daap/daapserver.cpp new file mode 100644 index 00000000..6b9cd8de --- /dev/null +++ b/amarok/src/mediadevice/daap/daapserver.cpp @@ -0,0 +1,87 @@ +/*************************************************************************** + * copyright : (C) 2006 Ian Monroe <ian@monroe.nu> * + **************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "amarok.h" +#include "debug.h" +#include "daapserver.h" +#include "collectiondb.h" + +#include <kstandarddirs.h> +#include <kuser.h> +#if DNSSD_SUPPORT + #include <dnssd/publicservice.h> +#endif +DaapServer::DaapServer(QObject* parent, char* name) + : QObject( parent, name ) + , m_service( 0 ) +{ + DEBUG_BLOCK + + m_server = new KProcIO(); + m_server->setComm( KProcess::All ); + *m_server << "amarok_daapserver.rb"; + *m_server << locate( "data", "amarok/ruby_lib/" ); + *m_server << locate( "lib", "ruby_lib/" ); + *m_server << locate( "data", "amarok/scripts/ruby_debug/debug.rb" ); + if( !m_server->start( KProcIO::NotifyOnExit, true ) ) { + error() << "Failed to start amarok_daapserver.rb" << endl; + return; + } + + connect( m_server, SIGNAL( readReady( KProcIO* ) ), this, SLOT( readSql() ) ); +} + +DaapServer::~DaapServer() +{ + #if DNSSD_SUPPORT + delete m_service; + #endif + delete m_server; +} + +void +DaapServer::readSql() +{ + static const QCString sqlPrefix = "SQL QUERY: "; + static const QCString serverStartPrefix = "SERVER STARTING: "; + QString line; + while( m_server->readln( line ) != -1 ) + { + if( line.startsWith( sqlPrefix ) ) + { + line.remove( 0, sqlPrefix.length() ); + debug() << "sql run " << line << endl; + m_server->writeStdin( CollectionDB::instance()->query( line ).join("\n") ); + m_server->writeStdin( "**** END SQL ****" ); + } + else if( line.startsWith( serverStartPrefix ) ) + { + line.remove( 0, serverStartPrefix.length() ); + debug() << "Server starting on port " << line << '.' << endl; + #if DNSSD_SUPPORT + KUser current; + if( !m_service ) + m_service = new DNSSD::PublicService( i18n("%1's Amarok Share").arg( current.fullName() ), "_daap._tcp", line.toInt() ); + debug() << "port number: " << line.toInt() << endl; + m_service->publishAsync(); + #endif + } + else + debug() << "server says " << line << endl; + } + //m_server->ackRead(); + //m_server->enableReadSignals(true); +} + +#include "daapserver.moc" + diff --git a/amarok/src/mediadevice/daap/daapserver.h b/amarok/src/mediadevice/daap/daapserver.h new file mode 100644 index 00000000..1f1d0773 --- /dev/null +++ b/amarok/src/mediadevice/daap/daapserver.h @@ -0,0 +1,39 @@ +/*************************************************************************** + * copyright : (C) 2006 Ian Monroe <ian@monroe.nu> * + **************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +#ifndef AMAROK_DAAPSERVER_H +#define AMAROK_DAAPSERVER_H + +#include <daapclient.h> + +namespace DNSSD { + class PublicService; +} + +class DaapServer : public QObject +{ + Q_OBJECT + + public: + DaapServer(QObject* parent, char* name); + ~DaapServer(); + public slots: + void readSql(); + private: + KProcIO* m_server; + #ifdef DNSSD_SUPPORT + DNSSD::PublicService* m_service; + #endif +}; + +#endif /* AMAROK_DAAPSERVER_H */ + diff --git a/amarok/src/mediadevice/daap/mongrel/Makefile.am b/amarok/src/mediadevice/daap/mongrel/Makefile.am new file mode 100644 index 00000000..bbfdacf2 --- /dev/null +++ b/amarok/src/mediadevice/daap/mongrel/Makefile.am @@ -0,0 +1,2 @@ +SUBDIRS = http11 lib + diff --git a/amarok/src/mediadevice/daap/mongrel/http11/Makefile.am b/amarok/src/mediadevice/daap/mongrel/http11/Makefile.am new file mode 100644 index 00000000..15cf49ec --- /dev/null +++ b/amarok/src/mediadevice/daap/mongrel/http11/Makefile.am @@ -0,0 +1,29 @@ +amarokrubylibdir = $(libdir)/ruby_lib + +amarokrubylib_LTLIBRARIES = libhttp11.la + +amarokrubylib_DATA = http11.rb + +INCLUDES = -I$(ruby_includes) -Dinline=__inline__ -Dasm=__asm__ + +libhttp11_la_LDFLAGS = \ + -shared \ + $(ruby_ldflags) + +#http11_so_LIBADD = \ +# -lruby18 + +libhttp11_la_SOURCES = \ + http11.c \ + http11_parser.c \ + tst_cleanup.c \ + tst_delete.c \ + tst_grow_node_free_list.c \ + tst_init.c \ + tst_insert.c \ + tst_search.c + +noinst_HEADERS = \ + ext_help.h \ + http11_parser.h \ + tst.h diff --git a/amarok/src/mediadevice/daap/mongrel/http11/ext_help.h b/amarok/src/mediadevice/daap/mongrel/http11/ext_help.h new file mode 100644 index 00000000..8b4d754c --- /dev/null +++ b/amarok/src/mediadevice/daap/mongrel/http11/ext_help.h @@ -0,0 +1,14 @@ +#ifndef ext_help_h +#define ext_help_h + +#define RAISE_NOT_NULL(T) if(T == NULL) rb_raise(rb_eArgError, "NULL found for " # T " when shouldn't be."); +#define DATA_GET(from,type,name) Data_Get_Struct(from,type,name); RAISE_NOT_NULL(name); +#define REQUIRE_TYPE(V, T) if(TYPE(V) != T) rb_raise(rb_eTypeError, "Wrong argument type for " # V " required " # T); + +#ifdef DEBUG +#define TRACE() fprintf(stderr, "> %s:%d:%s\n", __FILE__, __LINE__, __FUNCTION__) +#else +#define TRACE() +#endif + +#endif diff --git a/amarok/src/mediadevice/daap/mongrel/http11/http11.c b/amarok/src/mediadevice/daap/mongrel/http11/http11.c new file mode 100644 index 00000000..a3ad1d5f --- /dev/null +++ b/amarok/src/mediadevice/daap/mongrel/http11/http11.c @@ -0,0 +1,575 @@ +/** + * Copyright (c) 2005 Zed A. Shaw + * You can redistribute it and/or modify it under the same terms as Ruby. + */ +#include "ruby.h" +#include "ext_help.h" +#include <assert.h> +#include <string.h> +#include "http11_parser.h" +#include <ctype.h> +#include "tst.h" + +static VALUE mMongrel; +static VALUE cHttpParser; +static VALUE cURIClassifier; +static VALUE eHttpParserError; + +#define id_handler_map rb_intern("@handler_map") +#define id_http_body rb_intern("@http_body") + +static VALUE global_http_prefix; +static VALUE global_request_method; +static VALUE global_request_uri; +static VALUE global_query_string; +static VALUE global_http_version; +static VALUE global_content_length; +static VALUE global_http_content_length; +static VALUE global_request_path; +static VALUE global_content_type; +static VALUE global_http_content_type; +static VALUE global_gateway_interface; +static VALUE global_gateway_interface_value; +static VALUE global_server_name; +static VALUE global_server_port; +static VALUE global_server_protocol; +static VALUE global_server_protocol_value; +static VALUE global_http_host; +static VALUE global_mongrel_version; +static VALUE global_server_software; +static VALUE global_port_80; + +#define TRIE_INCREASE 30 + +/** Defines common length and error messages for input length validation. */ +#define DEF_MAX_LENGTH(N,length) const size_t MAX_##N##_LENGTH = length; const char *MAX_##N##_LENGTH_ERR = "HTTP element " # N " is longer than the " # length " allowed length." + +/** Validates the max length of given input and throws an HttpParserError exception if over. */ +#define VALIDATE_MAX_LENGTH(len, N) if(len > MAX_##N##_LENGTH) { rb_raise(eHttpParserError, MAX_##N##_LENGTH_ERR); } + +/** Defines global strings in the init method. */ +#define DEF_GLOBAL(N, val) global_##N = rb_obj_freeze(rb_str_new2(val)); rb_global_variable(&global_##N) + + +/* Defines the maximum allowed lengths for various input elements.*/ +DEF_MAX_LENGTH(FIELD_NAME, 256); +DEF_MAX_LENGTH(FIELD_VALUE, 80 * 1024); +DEF_MAX_LENGTH(REQUEST_URI, 1024 * 12); +DEF_MAX_LENGTH(REQUEST_PATH, 1024); +DEF_MAX_LENGTH(QUERY_STRING, (1024 * 10)); +DEF_MAX_LENGTH(HEADER, (1024 * (80 + 32))); + + +void http_field(void *data, const char *field, size_t flen, const char *value, size_t vlen) +{ + char *ch, *end; + VALUE req = (VALUE)data; + VALUE v = Qnil; + VALUE f = Qnil; + + VALIDATE_MAX_LENGTH(flen, FIELD_NAME); + VALIDATE_MAX_LENGTH(vlen, FIELD_VALUE); + + v = rb_str_new(value, vlen); + f = rb_str_dup(global_http_prefix); + f = rb_str_buf_cat(f, field, flen); + + for(ch = RSTRING(f)->ptr, end = ch + RSTRING(f)->len; ch < end; ch++) { + if(*ch == '-') { + *ch = '_'; + } else { + *ch = toupper(*ch); + } + } + + rb_hash_aset(req, f, v); +} + +void request_method(void *data, const char *at, size_t length) +{ + VALUE req = (VALUE)data; + VALUE val = Qnil; + + val = rb_str_new(at, length); + rb_hash_aset(req, global_request_method, val); +} + +void request_uri(void *data, const char *at, size_t length) +{ + VALUE req = (VALUE)data; + VALUE val = Qnil; + + VALIDATE_MAX_LENGTH(length, REQUEST_URI); + + val = rb_str_new(at, length); + rb_hash_aset(req, global_request_uri, val); +} + +void request_path(void *data, const char *at, size_t length) +{ + VALUE req = (VALUE)data; + VALUE val = Qnil; + + VALIDATE_MAX_LENGTH(length, REQUEST_PATH); + + val = rb_str_new(at, length); + rb_hash_aset(req, global_request_path, val); +} + +void query_string(void *data, const char *at, size_t length) +{ + VALUE req = (VALUE)data; + VALUE val = Qnil; + + VALIDATE_MAX_LENGTH(length, QUERY_STRING); + + val = rb_str_new(at, length); + rb_hash_aset(req, global_query_string, val); +} + +void http_version(void *data, const char *at, size_t length) +{ + VALUE req = (VALUE)data; + VALUE val = rb_str_new(at, length); + rb_hash_aset(req, global_http_version, val); +} + +/** Finalizes the request header to have a bunch of stuff that's + needed. */ + +void header_done(void *data, const char *at, size_t length) +{ + VALUE req = (VALUE)data; + VALUE temp = Qnil; + VALUE ctype = Qnil; + VALUE clen = Qnil; + char *colon = NULL; + + clen = rb_hash_aref(req, global_http_content_length); + if(clen != Qnil) { + rb_hash_aset(req, global_content_length, clen); + } + + ctype = rb_hash_aref(req, global_http_content_type); + if(ctype != Qnil) { + rb_hash_aset(req, global_content_type, ctype); + } + + rb_hash_aset(req, global_gateway_interface, global_gateway_interface_value); + if((temp = rb_hash_aref(req, global_http_host)) != Qnil) { + colon = strchr(RSTRING(temp)->ptr, ':'); + if(colon != NULL) { + rb_hash_aset(req, global_server_name, rb_str_substr(temp, 0, colon - RSTRING(temp)->ptr)); + rb_hash_aset(req, global_server_port, + rb_str_substr(temp, colon - RSTRING(temp)->ptr+1, + RSTRING(temp)->len)); + } else { + rb_hash_aset(req, global_server_name, temp); + rb_hash_aset(req, global_server_port, global_port_80); + } + } + + rb_ivar_set(req, id_http_body, rb_str_new(at, length)); + rb_hash_aset(req, global_server_protocol, global_server_protocol_value); + rb_hash_aset(req, global_server_software, global_mongrel_version); +} + + +void HttpParser_free(void *data) { + TRACE(); + + if(data) { + free(data); + } +} + + +VALUE HttpParser_alloc(VALUE klass) +{ + VALUE obj; + http_parser *hp = ALLOC_N(http_parser, 1); + TRACE(); + hp->http_field = http_field; + hp->request_method = request_method; + hp->request_uri = request_uri; + hp->request_path = request_path; + hp->query_string = query_string; + hp->http_version = http_version; + hp->header_done = header_done; + http_parser_init(hp); + + obj = Data_Wrap_Struct(klass, NULL, HttpParser_free, hp); + + return obj; +} + + +/** + * call-seq: + * parser.new -> parser + * + * Creates a new parser. + */ +VALUE HttpParser_init(VALUE self) +{ + http_parser *http = NULL; + DATA_GET(self, http_parser, http); + http_parser_init(http); + + return self; +} + + +/** + * call-seq: + * parser.reset -> nil + * + * Resets the parser to it's initial state so that you can reuse it + * rather than making new ones. + */ +VALUE HttpParser_reset(VALUE self) +{ + http_parser *http = NULL; + DATA_GET(self, http_parser, http); + http_parser_init(http); + + return Qnil; +} + + +/** + * call-seq: + * parser.finish -> true/false + * + * Finishes a parser early which could put in a "good" or bad state. + * You should call reset after finish it or bad things will happen. + */ +VALUE HttpParser_finish(VALUE self) +{ + http_parser *http = NULL; + DATA_GET(self, http_parser, http); + http_parser_finish(http); + + return http_parser_is_finished(http) ? Qtrue : Qfalse; +} + + +/** + * call-seq: + * parser.execute(req_hash, data, start) -> Integer + * + * Takes a Hash and a String of data, parses the String of data filling in the Hash + * returning an Integer to indicate how much of the data has been read. No matter + * what the return value, you should call HttpParser#finished? and HttpParser#error? + * to figure out if it's done parsing or there was an error. + * + * This function now throws an exception when there is a parsing error. This makes + * the logic for working with the parser much easier. You can still test for an + * error, but now you need to wrap the parser with an exception handling block. + * + * The third argument allows for parsing a partial request and then continuing + * the parsing from that position. It needs all of the original data as well + * so you have to append to the data buffer as you read. + */ +VALUE HttpParser_execute(VALUE self, VALUE req_hash, VALUE data, VALUE start) +{ + http_parser *http = NULL; + int from = 0; + char *dptr = NULL; + long dlen = 0; + + DATA_GET(self, http_parser, http); + + from = FIX2INT(start); + dptr = RSTRING(data)->ptr; + dlen = RSTRING(data)->len; + + if(from >= dlen) { + rb_raise(eHttpParserError, "Requested start is after data buffer end."); + } else { + http->data = (void *)req_hash; + http_parser_execute(http, dptr, dlen, from); + + VALIDATE_MAX_LENGTH(http_parser_nread(http), HEADER); + + if(http_parser_has_error(http)) { + rb_raise(eHttpParserError, "Invalid HTTP format, parsing fails."); + } else { + return INT2FIX(http_parser_nread(http)); + } + } +} + + + +/** + * call-seq: + * parser.error? -> true/false + * + * Tells you whether the parser is in an error state. + */ +VALUE HttpParser_has_error(VALUE self) +{ + http_parser *http = NULL; + DATA_GET(self, http_parser, http); + + return http_parser_has_error(http) ? Qtrue : Qfalse; +} + + +/** + * call-seq: + * parser.finished? -> true/false + * + * Tells you whether the parser is finished or not and in a good state. + */ +VALUE HttpParser_is_finished(VALUE self) +{ + http_parser *http = NULL; + DATA_GET(self, http_parser, http); + + return http_parser_is_finished(http) ? Qtrue : Qfalse; +} + + +/** + * call-seq: + * parser.nread -> Integer + * + * Returns the amount of data processed so far during this processing cycle. It is + * set to 0 on initialize or reset calls and is incremented each time execute is called. + */ +VALUE HttpParser_nread(VALUE self) +{ + http_parser *http = NULL; + DATA_GET(self, http_parser, http); + + return INT2FIX(http->nread); +} + + +void URIClassifier_free(void *data) +{ + TRACE(); + + if(data) { + tst_cleanup((struct tst *)data); + } +} + + + +VALUE URIClassifier_alloc(VALUE klass) +{ + VALUE obj; + struct tst *tst = tst_init(TRIE_INCREASE); + TRACE(); + assert(tst && "failed to initialize trie structure"); + + obj = Data_Wrap_Struct(klass, NULL, URIClassifier_free, tst); + + return obj; +} + +/** + * call-seq: + * URIClassifier.new -> URIClassifier + * + * Initializes a new URIClassifier object that you can use to associate URI sequences + * with objects. You can actually use it with any string sequence and any objects, + * but it's mostly used with URIs. + * + * It uses TST from http://www.octavian.org/cs/software.html to build an ternary search + * trie to hold all of the URIs. It uses this to do an initial search for the a URI + * prefix, and then to break the URI into SCRIPT_NAME and PATH_INFO portions. It actually + * will do two searches most of the time in order to find the right handler for the + * registered prefix portion. + * + */ +VALUE URIClassifier_init(VALUE self) +{ + VALUE hash; + + hash = rb_hash_new(); + rb_ivar_set(self, id_handler_map, hash); + + return self; +} + + +/** + * call-seq: + * uc.register("/someuri", SampleHandler.new) -> nil + * + * Registers the SampleHandler (one for all requests) with the "/someuri". + * When URIClassifier::resolve is called with "/someuri" it'll return + * SampleHandler immediately. When called with "/someuri/iwant" it'll also + * return SomeHandler immediatly, with no additional searches, but it will + * return path info with "/iwant". + * + * You actually can reuse this class to register nearly anything and + * quickly resolve it. This could be used for caching, fast mapping, etc. + * The downside is it uses much more memory than a Hash, but it can be + * a lot faster. It's main advantage is that it works on prefixes, which + * is damn hard to get right with a Hash. + */ +VALUE URIClassifier_register(VALUE self, VALUE uri, VALUE handler) +{ + int rc = 0; + void *ptr = NULL; + struct tst *tst = NULL; + DATA_GET(self, struct tst, tst); + + rc = tst_insert((unsigned char *)StringValueCStr(uri), (void *)handler , tst, 0, &ptr); + + if(rc == TST_DUPLICATE_KEY) { + rb_raise(rb_eStandardError, "Handler already registered with that name"); + } else if(rc == TST_ERROR) { + rb_raise(rb_eStandardError, "Memory error registering handler"); + } else if(rc == TST_NULL_KEY) { + rb_raise(rb_eStandardError, "URI was empty"); + } + + rb_hash_aset(rb_ivar_get(self, id_handler_map), uri, handler); + + return Qnil; +} + + +/** + * call-seq: + * uc.unregister("/someuri") + * + * Yep, just removes this uri and it's handler from the trie. + */ +VALUE URIClassifier_unregister(VALUE self, VALUE uri) +{ + void *handler = NULL; + struct tst *tst = NULL; + DATA_GET(self, struct tst, tst); + + handler = tst_delete((unsigned char *)StringValueCStr(uri), tst); + + if(handler) { + rb_hash_delete(rb_ivar_get(self, id_handler_map), uri); + + return (VALUE)handler; + } else { + return Qnil; + } +} + + +/** + * call-seq: + * uc.resolve("/someuri") -> "/someuri", "", handler + * uc.resolve("/someuri/pathinfo") -> "/someuri", "/pathinfo", handler + * uc.resolve("/notfound/orhere") -> nil, nil, nil + * uc.resolve("/") -> "/", "/", handler # if uc.register("/", handler) + * uc.resolve("/path/from/root") -> "/", "/path/from/root", handler # if uc.register("/", handler) + * + * Attempts to resolve either the whole URI or at the longest prefix, returning + * the prefix (as script_info), path (as path_info), and registered handler + * (usually an HttpHandler). If it doesn't find a handler registered at the longest + * match then it returns nil,nil,nil. + * + * Because the resolver uses a trie you are able to register a handler at *any* character + * in the URI and it will be handled as long as it's the longest prefix. So, if you + * registered handler #1 at "/something/lik", and #2 at "/something/like/that", then a + * a search for "/something/like" would give you #1. A search for "/something/like/that/too" + * would give you #2. + * + * This is very powerful since it means you can also attach handlers to parts of the ; + * (semi-colon) separated path params, any part of the path, use off chars, anything really. + * It also means that it's very efficient to do this only taking as long as the URI has + * characters. + * + * A slight modification to the CGI 1.2 standard is given for handlers registered to "/". + * CGI expects all CGI scripts to be at some script path, so it doesn't really say anything + * about a script that handles the root. To make this work, the resolver will detect that + * the requested handler is at "/", and return that for script_name, and then simply return + * the full URI back as path_info. + * + * It expects strings with no embedded '\0' characters. Don't try other string-like stuff yet. + */ +VALUE URIClassifier_resolve(VALUE self, VALUE uri) +{ + void *handler = NULL; + int pref_len = 0; + struct tst *tst = NULL; + VALUE result; + unsigned char *uri_str = NULL; + + DATA_GET(self, struct tst, tst); + uri_str = (unsigned char *)StringValueCStr(uri); + + handler = tst_search(uri_str, tst, &pref_len); + + result = rb_ary_new(); + + if(handler) { + rb_ary_push(result, rb_str_substr (uri, 0, pref_len)); + if(pref_len == 1 && uri_str[0] == '/') { + rb_ary_push(result, uri); + } else { + rb_ary_push(result, rb_str_substr(uri, pref_len, RSTRING(uri)->len)); + } + + rb_ary_push(result, (VALUE)handler); + } else { + rb_ary_push(result, Qnil); + rb_ary_push(result, Qnil); + rb_ary_push(result, Qnil); + } + + return result; +} + + +void Init_libhttp11() +{ + + mMongrel = rb_define_module("Mongrel"); + + DEF_GLOBAL(http_prefix, "HTTP_"); + DEF_GLOBAL(request_method, "REQUEST_METHOD"); + DEF_GLOBAL(request_uri, "REQUEST_URI"); + DEF_GLOBAL(query_string, "QUERY_STRING"); + DEF_GLOBAL(http_version, "HTTP_VERSION"); + DEF_GLOBAL(request_path, "REQUEST_PATH"); + DEF_GLOBAL(content_length, "CONTENT_LENGTH"); + DEF_GLOBAL(http_content_length, "HTTP_CONTENT_LENGTH"); + DEF_GLOBAL(content_type, "CONTENT_TYPE"); + DEF_GLOBAL(http_content_type, "HTTP_CONTENT_TYPE"); + DEF_GLOBAL(gateway_interface, "GATEWAY_INTERFACE"); + DEF_GLOBAL(gateway_interface_value, "CGI/1.2"); + DEF_GLOBAL(server_name, "SERVER_NAME"); + DEF_GLOBAL(server_port, "SERVER_PORT"); + DEF_GLOBAL(server_protocol, "SERVER_PROTOCOL"); + DEF_GLOBAL(server_protocol_value, "HTTP/1.1"); + DEF_GLOBAL(http_host, "HTTP_HOST"); + DEF_GLOBAL(mongrel_version, "Mongrel 1.0.1"); + DEF_GLOBAL(server_software, "SERVER_SOFTWARE"); + DEF_GLOBAL(port_80, "80"); + + eHttpParserError = rb_define_class_under(mMongrel, "HttpParserError", rb_eIOError); + + cHttpParser = rb_define_class_under(mMongrel, "HttpParser", rb_cObject); + rb_define_alloc_func(cHttpParser, HttpParser_alloc); + rb_define_method(cHttpParser, "initialize", HttpParser_init,0); + rb_define_method(cHttpParser, "reset", HttpParser_reset,0); + rb_define_method(cHttpParser, "finish", HttpParser_finish,0); + rb_define_method(cHttpParser, "execute", HttpParser_execute,3); + rb_define_method(cHttpParser, "error?", HttpParser_has_error,0); + rb_define_method(cHttpParser, "finished?", HttpParser_is_finished,0); + rb_define_method(cHttpParser, "nread", HttpParser_nread,0); + + cURIClassifier = rb_define_class_under(mMongrel, "URIClassifier", rb_cObject); + rb_define_alloc_func(cURIClassifier, URIClassifier_alloc); + rb_define_method(cURIClassifier, "initialize", URIClassifier_init, 0); + rb_define_method(cURIClassifier, "register", URIClassifier_register, 2); + rb_define_method(cURIClassifier, "unregister", URIClassifier_unregister, 1); + rb_define_method(cURIClassifier, "resolve", URIClassifier_resolve, 1); +} + + diff --git a/amarok/src/mediadevice/daap/mongrel/http11/http11.rb b/amarok/src/mediadevice/daap/mongrel/http11/http11.rb new file mode 100644 index 00000000..f3667d03 --- /dev/null +++ b/amarok/src/mediadevice/daap/mongrel/http11/http11.rb @@ -0,0 +1,2 @@ +require 'libhttp11' + diff --git a/amarok/src/mediadevice/daap/mongrel/http11/http11_parser.c b/amarok/src/mediadevice/daap/mongrel/http11/http11_parser.c new file mode 100644 index 00000000..8343127a --- /dev/null +++ b/amarok/src/mediadevice/daap/mongrel/http11/http11_parser.c @@ -0,0 +1,1096 @@ +#line 1 "ext/http11/http11_parser.rl" +/** + * Copyright (c) 2005 Zed A. Shaw + * You can redistribute it and/or modify it under the same terms as Ruby. + */ +#include "http11_parser.h" +#include <stdio.h> +#include <assert.h> +#include <stdlib.h> +#include <ctype.h> +#include <string.h> + +#define LEN(AT, FPC) (FPC - buffer - parser->AT) +#define MARK(M,FPC) (parser->M = (FPC) - buffer) +#define PTR_TO(F) (buffer + parser->F) + +/** machine **/ +#line 114 "ext/http11/http11_parser.rl" + + +/** Data **/ + +#line 24 "ext/http11/http11_parser.c" +static const int http_parser_start = 0; + +static const int http_parser_first_final = 53; + +static const int http_parser_error = 1; + +#line 118 "ext/http11/http11_parser.rl" + +int http_parser_init(http_parser *parser) { + int cs = 0; + +#line 36 "ext/http11/http11_parser.c" + { + cs = http_parser_start; + } +#line 122 "ext/http11/http11_parser.rl" + parser->cs = cs; + parser->body_start = 0; + parser->content_len = 0; + parser->mark = 0; + parser->nread = 0; + parser->field_len = 0; + parser->field_start = 0; + + return(1); +} + + +/** exec **/ +size_t http_parser_execute(http_parser *parser, const char *buffer, size_t len, size_t off) { + const char *p, *pe; + int cs = parser->cs; + + assert(off <= len && "offset past end of buffer"); + + p = buffer+off; + pe = buffer+len; + + assert(*pe == '\0' && "pointer does not end on NUL"); + assert(pe - p == len - off && "pointers aren't same distance"); + + + +#line 68 "ext/http11/http11_parser.c" + { + if ( p == pe ) + goto _out; + switch ( cs ) + { +case 0: + switch( (*p) ) { + case 36: goto tr14; + case 95: goto tr14; + } + if ( (*p) < 48 ) { + if ( 45 <= (*p) && (*p) <= 46 ) + goto tr14; + } else if ( (*p) > 57 ) { + if ( 65 <= (*p) && (*p) <= 90 ) + goto tr14; + } else + goto tr14; + goto st1; +st1: + goto _out1; +tr14: +#line 20 "ext/http11/http11_parser.rl" + {MARK(mark, p); } + goto st2; +st2: + if ( ++p == pe ) + goto _out2; +case 2: +#line 98 "ext/http11/http11_parser.c" + switch( (*p) ) { + case 32: goto tr17; + case 36: goto st34; + case 95: goto st34; + } + if ( (*p) < 48 ) { + if ( 45 <= (*p) && (*p) <= 46 ) + goto st34; + } else if ( (*p) > 57 ) { + if ( 65 <= (*p) && (*p) <= 90 ) + goto st34; + } else + goto st34; + goto st1; +tr17: +#line 34 "ext/http11/http11_parser.rl" + { + if(parser->request_method != NULL) + parser->request_method(parser->data, PTR_TO(mark), LEN(mark, p)); + } + goto st3; +st3: + if ( ++p == pe ) + goto _out3; +case 3: +#line 124 "ext/http11/http11_parser.c" + switch( (*p) ) { + case 42: goto tr10; + case 43: goto tr11; + case 47: goto tr12; + case 58: goto tr13; + } + if ( (*p) < 65 ) { + if ( 45 <= (*p) && (*p) <= 57 ) + goto tr11; + } else if ( (*p) > 90 ) { + if ( 97 <= (*p) && (*p) <= 122 ) + goto tr11; + } else + goto tr11; + goto st1; +tr10: +#line 20 "ext/http11/http11_parser.rl" + {MARK(mark, p); } + goto st4; +st4: + if ( ++p == pe ) + goto _out4; +case 4: +#line 148 "ext/http11/http11_parser.c" + if ( (*p) == 32 ) + goto tr19; + goto st1; +tr19: +#line 38 "ext/http11/http11_parser.rl" + { + if(parser->request_uri != NULL) + parser->request_uri(parser->data, PTR_TO(mark), LEN(mark, p)); + } + goto st5; +tr28: +#line 44 "ext/http11/http11_parser.rl" + { + if(parser->query_string != NULL) + parser->query_string(parser->data, PTR_TO(query_start), LEN(query_start, p)); + } +#line 38 "ext/http11/http11_parser.rl" + { + if(parser->request_uri != NULL) + parser->request_uri(parser->data, PTR_TO(mark), LEN(mark, p)); + } + goto st5; +tr31: +#line 54 "ext/http11/http11_parser.rl" + { + if(parser->request_path != NULL) + parser->request_path(parser->data, PTR_TO(mark), LEN(mark,p)); + } +#line 38 "ext/http11/http11_parser.rl" + { + if(parser->request_uri != NULL) + parser->request_uri(parser->data, PTR_TO(mark), LEN(mark, p)); + } + goto st5; +tr40: +#line 43 "ext/http11/http11_parser.rl" + {MARK(query_start, p); } +#line 44 "ext/http11/http11_parser.rl" + { + if(parser->query_string != NULL) + parser->query_string(parser->data, PTR_TO(query_start), LEN(query_start, p)); + } +#line 38 "ext/http11/http11_parser.rl" + { + if(parser->request_uri != NULL) + parser->request_uri(parser->data, PTR_TO(mark), LEN(mark, p)); + } + goto st5; +st5: + if ( ++p == pe ) + goto _out5; +case 5: +#line 201 "ext/http11/http11_parser.c" + if ( (*p) == 72 ) + goto tr3; + goto st1; +tr3: +#line 20 "ext/http11/http11_parser.rl" + {MARK(mark, p); } + goto st6; +st6: + if ( ++p == pe ) + goto _out6; +case 6: +#line 213 "ext/http11/http11_parser.c" + if ( (*p) == 84 ) + goto st7; + goto st1; +st7: + if ( ++p == pe ) + goto _out7; +case 7: + if ( (*p) == 84 ) + goto st8; + goto st1; +st8: + if ( ++p == pe ) + goto _out8; +case 8: + if ( (*p) == 80 ) + goto st9; + goto st1; +st9: + if ( ++p == pe ) + goto _out9; +case 9: + if ( (*p) == 47 ) + goto st10; + goto st1; +st10: + if ( ++p == pe ) + goto _out10; +case 10: + if ( 48 <= (*p) && (*p) <= 57 ) + goto st11; + goto st1; +st11: + if ( ++p == pe ) + goto _out11; +case 11: + if ( (*p) == 46 ) + goto st12; + if ( 48 <= (*p) && (*p) <= 57 ) + goto st11; + goto st1; +st12: + if ( ++p == pe ) + goto _out12; +case 12: + if ( 48 <= (*p) && (*p) <= 57 ) + goto st13; + goto st1; +st13: + if ( ++p == pe ) + goto _out13; +case 13: + if ( (*p) == 13 ) + goto tr22; + if ( 48 <= (*p) && (*p) <= 57 ) + goto st13; + goto st1; +tr22: +#line 49 "ext/http11/http11_parser.rl" + { + if(parser->http_version != NULL) + parser->http_version(parser->data, PTR_TO(mark), LEN(mark, p)); + } + goto st14; +tr36: +#line 29 "ext/http11/http11_parser.rl" + { + if(parser->http_field != NULL) { + parser->http_field(parser->data, PTR_TO(field_start), parser->field_len, PTR_TO(mark), LEN(mark, p)); + } + } + goto st14; +st14: + if ( ++p == pe ) + goto _out14; +case 14: +#line 289 "ext/http11/http11_parser.c" + if ( (*p) == 10 ) + goto st15; + goto st1; +st15: + if ( ++p == pe ) + goto _out15; +case 15: + switch( (*p) ) { + case 13: goto st16; + case 33: goto tr21; + case 124: goto tr21; + case 126: goto tr21; + } + if ( (*p) < 45 ) { + if ( (*p) > 39 ) { + if ( 42 <= (*p) && (*p) <= 43 ) + goto tr21; + } else if ( (*p) >= 35 ) + goto tr21; + } else if ( (*p) > 46 ) { + if ( (*p) < 65 ) { + if ( 48 <= (*p) && (*p) <= 57 ) + goto tr21; + } else if ( (*p) > 90 ) { + if ( 94 <= (*p) && (*p) <= 122 ) + goto tr21; + } else + goto tr21; + } else + goto tr21; + goto st1; +st16: + if ( ++p == pe ) + goto _out16; +case 16: + if ( (*p) == 10 ) + goto tr25; + goto st1; +tr25: +#line 59 "ext/http11/http11_parser.rl" + { + parser->body_start = p - buffer + 1; + if(parser->header_done != NULL) + parser->header_done(parser->data, p + 1, pe - p - 1); + goto _out53; + } + goto st53; +st53: + if ( ++p == pe ) + goto _out53; +case 53: +#line 341 "ext/http11/http11_parser.c" + goto st1; +tr21: +#line 23 "ext/http11/http11_parser.rl" + { MARK(field_start, p); } + goto st17; +st17: + if ( ++p == pe ) + goto _out17; +case 17: +#line 351 "ext/http11/http11_parser.c" + switch( (*p) ) { + case 33: goto st17; + case 58: goto tr16; + case 124: goto st17; + case 126: goto st17; + } + if ( (*p) < 45 ) { + if ( (*p) > 39 ) { + if ( 42 <= (*p) && (*p) <= 43 ) + goto st17; + } else if ( (*p) >= 35 ) + goto st17; + } else if ( (*p) > 46 ) { + if ( (*p) < 65 ) { + if ( 48 <= (*p) && (*p) <= 57 ) + goto st17; + } else if ( (*p) > 90 ) { + if ( 94 <= (*p) && (*p) <= 122 ) + goto st17; + } else + goto st17; + } else + goto st17; + goto st1; +tr16: +#line 24 "ext/http11/http11_parser.rl" + { + parser->field_len = LEN(field_start, p); + } + goto st18; +tr38: +#line 28 "ext/http11/http11_parser.rl" + { MARK(mark, p); } + goto st18; +st18: + if ( ++p == pe ) + goto _out18; +case 18: +#line 390 "ext/http11/http11_parser.c" + switch( (*p) ) { + case 13: goto tr36; + case 32: goto tr38; + } + goto tr37; +tr37: +#line 28 "ext/http11/http11_parser.rl" + { MARK(mark, p); } + goto st19; +st19: + if ( ++p == pe ) + goto _out19; +case 19: +#line 404 "ext/http11/http11_parser.c" + if ( (*p) == 13 ) + goto tr36; + goto st19; +tr11: +#line 20 "ext/http11/http11_parser.rl" + {MARK(mark, p); } + goto st20; +st20: + if ( ++p == pe ) + goto _out20; +case 20: +#line 416 "ext/http11/http11_parser.c" + switch( (*p) ) { + case 43: goto st20; + case 58: goto st21; + } + if ( (*p) < 48 ) { + if ( 45 <= (*p) && (*p) <= 46 ) + goto st20; + } else if ( (*p) > 57 ) { + if ( (*p) > 90 ) { + if ( 97 <= (*p) && (*p) <= 122 ) + goto st20; + } else if ( (*p) >= 65 ) + goto st20; + } else + goto st20; + goto st1; +tr13: +#line 20 "ext/http11/http11_parser.rl" + {MARK(mark, p); } + goto st21; +st21: + if ( ++p == pe ) + goto _out21; +case 21: +#line 441 "ext/http11/http11_parser.c" + switch( (*p) ) { + case 32: goto tr19; + case 37: goto st22; + case 60: goto st1; + case 62: goto st1; + case 127: goto st1; + } + if ( (*p) > 31 ) { + if ( 34 <= (*p) && (*p) <= 35 ) + goto st1; + } else if ( (*p) >= 0 ) + goto st1; + goto st21; +st22: + if ( ++p == pe ) + goto _out22; +case 22: + if ( (*p) < 65 ) { + if ( 48 <= (*p) && (*p) <= 57 ) + goto st23; + } else if ( (*p) > 70 ) { + if ( 97 <= (*p) && (*p) <= 102 ) + goto st23; + } else + goto st23; + goto st1; +st23: + if ( ++p == pe ) + goto _out23; +case 23: + if ( (*p) < 65 ) { + if ( 48 <= (*p) && (*p) <= 57 ) + goto st21; + } else if ( (*p) > 70 ) { + if ( 97 <= (*p) && (*p) <= 102 ) + goto st21; + } else + goto st21; + goto st1; +tr12: +#line 20 "ext/http11/http11_parser.rl" + {MARK(mark, p); } + goto st24; +st24: + if ( ++p == pe ) + goto _out24; +case 24: +#line 489 "ext/http11/http11_parser.c" + switch( (*p) ) { + case 32: goto tr31; + case 37: goto st25; + case 59: goto tr33; + case 60: goto st1; + case 62: goto st1; + case 63: goto tr34; + case 127: goto st1; + } + if ( (*p) > 31 ) { + if ( 34 <= (*p) && (*p) <= 35 ) + goto st1; + } else if ( (*p) >= 0 ) + goto st1; + goto st24; +st25: + if ( ++p == pe ) + goto _out25; +case 25: + if ( (*p) < 65 ) { + if ( 48 <= (*p) && (*p) <= 57 ) + goto st26; + } else if ( (*p) > 70 ) { + if ( 97 <= (*p) && (*p) <= 102 ) + goto st26; + } else + goto st26; + goto st1; +st26: + if ( ++p == pe ) + goto _out26; +case 26: + if ( (*p) < 65 ) { + if ( 48 <= (*p) && (*p) <= 57 ) + goto st24; + } else if ( (*p) > 70 ) { + if ( 97 <= (*p) && (*p) <= 102 ) + goto st24; + } else + goto st24; + goto st1; +tr33: +#line 54 "ext/http11/http11_parser.rl" + { + if(parser->request_path != NULL) + parser->request_path(parser->data, PTR_TO(mark), LEN(mark,p)); + } + goto st27; +st27: + if ( ++p == pe ) + goto _out27; +case 27: +#line 542 "ext/http11/http11_parser.c" + switch( (*p) ) { + case 32: goto tr19; + case 37: goto st28; + case 60: goto st1; + case 62: goto st1; + case 63: goto st30; + case 127: goto st1; + } + if ( (*p) > 31 ) { + if ( 34 <= (*p) && (*p) <= 35 ) + goto st1; + } else if ( (*p) >= 0 ) + goto st1; + goto st27; +st28: + if ( ++p == pe ) + goto _out28; +case 28: + if ( (*p) < 65 ) { + if ( 48 <= (*p) && (*p) <= 57 ) + goto st29; + } else if ( (*p) > 70 ) { + if ( 97 <= (*p) && (*p) <= 102 ) + goto st29; + } else + goto st29; + goto st1; +st29: + if ( ++p == pe ) + goto _out29; +case 29: + if ( (*p) < 65 ) { + if ( 48 <= (*p) && (*p) <= 57 ) + goto st27; + } else if ( (*p) > 70 ) { + if ( 97 <= (*p) && (*p) <= 102 ) + goto st27; + } else + goto st27; + goto st1; +tr34: +#line 54 "ext/http11/http11_parser.rl" + { + if(parser->request_path != NULL) + parser->request_path(parser->data, PTR_TO(mark), LEN(mark,p)); + } + goto st30; +st30: + if ( ++p == pe ) + goto _out30; +case 30: +#line 594 "ext/http11/http11_parser.c" + switch( (*p) ) { + case 32: goto tr40; + case 37: goto tr41; + case 60: goto st1; + case 62: goto st1; + case 127: goto st1; + } + if ( (*p) > 31 ) { + if ( 34 <= (*p) && (*p) <= 35 ) + goto st1; + } else if ( (*p) >= 0 ) + goto st1; + goto tr39; +tr39: +#line 43 "ext/http11/http11_parser.rl" + {MARK(query_start, p); } + goto st31; +st31: + if ( ++p == pe ) + goto _out31; +case 31: +#line 616 "ext/http11/http11_parser.c" + switch( (*p) ) { + case 32: goto tr28; + case 37: goto st32; + case 60: goto st1; + case 62: goto st1; + case 127: goto st1; + } + if ( (*p) > 31 ) { + if ( 34 <= (*p) && (*p) <= 35 ) + goto st1; + } else if ( (*p) >= 0 ) + goto st1; + goto st31; +tr41: +#line 43 "ext/http11/http11_parser.rl" + {MARK(query_start, p); } + goto st32; +st32: + if ( ++p == pe ) + goto _out32; +case 32: +#line 638 "ext/http11/http11_parser.c" + if ( (*p) < 65 ) { + if ( 48 <= (*p) && (*p) <= 57 ) + goto st33; + } else if ( (*p) > 70 ) { + if ( 97 <= (*p) && (*p) <= 102 ) + goto st33; + } else + goto st33; + goto st1; +st33: + if ( ++p == pe ) + goto _out33; +case 33: + if ( (*p) < 65 ) { + if ( 48 <= (*p) && (*p) <= 57 ) + goto st31; + } else if ( (*p) > 70 ) { + if ( 97 <= (*p) && (*p) <= 102 ) + goto st31; + } else + goto st31; + goto st1; +st34: + if ( ++p == pe ) + goto _out34; +case 34: + switch( (*p) ) { + case 32: goto tr17; + case 36: goto st35; + case 95: goto st35; + } + if ( (*p) < 48 ) { + if ( 45 <= (*p) && (*p) <= 46 ) + goto st35; + } else if ( (*p) > 57 ) { + if ( 65 <= (*p) && (*p) <= 90 ) + goto st35; + } else + goto st35; + goto st1; +st35: + if ( ++p == pe ) + goto _out35; +case 35: + switch( (*p) ) { + case 32: goto tr17; + case 36: goto st36; + case 95: goto st36; + } + if ( (*p) < 48 ) { + if ( 45 <= (*p) && (*p) <= 46 ) + goto st36; + } else if ( (*p) > 57 ) { + if ( 65 <= (*p) && (*p) <= 90 ) + goto st36; + } else + goto st36; + goto st1; +st36: + if ( ++p == pe ) + goto _out36; +case 36: + switch( (*p) ) { + case 32: goto tr17; + case 36: goto st37; + case 95: goto st37; + } + if ( (*p) < 48 ) { + if ( 45 <= (*p) && (*p) <= 46 ) + goto st37; + } else if ( (*p) > 57 ) { + if ( 65 <= (*p) && (*p) <= 90 ) + goto st37; + } else + goto st37; + goto st1; +st37: + if ( ++p == pe ) + goto _out37; +case 37: + switch( (*p) ) { + case 32: goto tr17; + case 36: goto st38; + case 95: goto st38; + } + if ( (*p) < 48 ) { + if ( 45 <= (*p) && (*p) <= 46 ) + goto st38; + } else if ( (*p) > 57 ) { + if ( 65 <= (*p) && (*p) <= 90 ) + goto st38; + } else + goto st38; + goto st1; +st38: + if ( ++p == pe ) + goto _out38; +case 38: + switch( (*p) ) { + case 32: goto tr17; + case 36: goto st39; + case 95: goto st39; + } + if ( (*p) < 48 ) { + if ( 45 <= (*p) && (*p) <= 46 ) + goto st39; + } else if ( (*p) > 57 ) { + if ( 65 <= (*p) && (*p) <= 90 ) + goto st39; + } else + goto st39; + goto st1; +st39: + if ( ++p == pe ) + goto _out39; +case 39: + switch( (*p) ) { + case 32: goto tr17; + case 36: goto st40; + case 95: goto st40; + } + if ( (*p) < 48 ) { + if ( 45 <= (*p) && (*p) <= 46 ) + goto st40; + } else if ( (*p) > 57 ) { + if ( 65 <= (*p) && (*p) <= 90 ) + goto st40; + } else + goto st40; + goto st1; +st40: + if ( ++p == pe ) + goto _out40; +case 40: + switch( (*p) ) { + case 32: goto tr17; + case 36: goto st41; + case 95: goto st41; + } + if ( (*p) < 48 ) { + if ( 45 <= (*p) && (*p) <= 46 ) + goto st41; + } else if ( (*p) > 57 ) { + if ( 65 <= (*p) && (*p) <= 90 ) + goto st41; + } else + goto st41; + goto st1; +st41: + if ( ++p == pe ) + goto _out41; +case 41: + switch( (*p) ) { + case 32: goto tr17; + case 36: goto st42; + case 95: goto st42; + } + if ( (*p) < 48 ) { + if ( 45 <= (*p) && (*p) <= 46 ) + goto st42; + } else if ( (*p) > 57 ) { + if ( 65 <= (*p) && (*p) <= 90 ) + goto st42; + } else + goto st42; + goto st1; +st42: + if ( ++p == pe ) + goto _out42; +case 42: + switch( (*p) ) { + case 32: goto tr17; + case 36: goto st43; + case 95: goto st43; + } + if ( (*p) < 48 ) { + if ( 45 <= (*p) && (*p) <= 46 ) + goto st43; + } else if ( (*p) > 57 ) { + if ( 65 <= (*p) && (*p) <= 90 ) + goto st43; + } else + goto st43; + goto st1; +st43: + if ( ++p == pe ) + goto _out43; +case 43: + switch( (*p) ) { + case 32: goto tr17; + case 36: goto st44; + case 95: goto st44; + } + if ( (*p) < 48 ) { + if ( 45 <= (*p) && (*p) <= 46 ) + goto st44; + } else if ( (*p) > 57 ) { + if ( 65 <= (*p) && (*p) <= 90 ) + goto st44; + } else + goto st44; + goto st1; +st44: + if ( ++p == pe ) + goto _out44; +case 44: + switch( (*p) ) { + case 32: goto tr17; + case 36: goto st45; + case 95: goto st45; + } + if ( (*p) < 48 ) { + if ( 45 <= (*p) && (*p) <= 46 ) + goto st45; + } else if ( (*p) > 57 ) { + if ( 65 <= (*p) && (*p) <= 90 ) + goto st45; + } else + goto st45; + goto st1; +st45: + if ( ++p == pe ) + goto _out45; +case 45: + switch( (*p) ) { + case 32: goto tr17; + case 36: goto st46; + case 95: goto st46; + } + if ( (*p) < 48 ) { + if ( 45 <= (*p) && (*p) <= 46 ) + goto st46; + } else if ( (*p) > 57 ) { + if ( 65 <= (*p) && (*p) <= 90 ) + goto st46; + } else + goto st46; + goto st1; +st46: + if ( ++p == pe ) + goto _out46; +case 46: + switch( (*p) ) { + case 32: goto tr17; + case 36: goto st47; + case 95: goto st47; + } + if ( (*p) < 48 ) { + if ( 45 <= (*p) && (*p) <= 46 ) + goto st47; + } else if ( (*p) > 57 ) { + if ( 65 <= (*p) && (*p) <= 90 ) + goto st47; + } else + goto st47; + goto st1; +st47: + if ( ++p == pe ) + goto _out47; +case 47: + switch( (*p) ) { + case 32: goto tr17; + case 36: goto st48; + case 95: goto st48; + } + if ( (*p) < 48 ) { + if ( 45 <= (*p) && (*p) <= 46 ) + goto st48; + } else if ( (*p) > 57 ) { + if ( 65 <= (*p) && (*p) <= 90 ) + goto st48; + } else + goto st48; + goto st1; +st48: + if ( ++p == pe ) + goto _out48; +case 48: + switch( (*p) ) { + case 32: goto tr17; + case 36: goto st49; + case 95: goto st49; + } + if ( (*p) < 48 ) { + if ( 45 <= (*p) && (*p) <= 46 ) + goto st49; + } else if ( (*p) > 57 ) { + if ( 65 <= (*p) && (*p) <= 90 ) + goto st49; + } else + goto st49; + goto st1; +st49: + if ( ++p == pe ) + goto _out49; +case 49: + switch( (*p) ) { + case 32: goto tr17; + case 36: goto st50; + case 95: goto st50; + } + if ( (*p) < 48 ) { + if ( 45 <= (*p) && (*p) <= 46 ) + goto st50; + } else if ( (*p) > 57 ) { + if ( 65 <= (*p) && (*p) <= 90 ) + goto st50; + } else + goto st50; + goto st1; +st50: + if ( ++p == pe ) + goto _out50; +case 50: + switch( (*p) ) { + case 32: goto tr17; + case 36: goto st51; + case 95: goto st51; + } + if ( (*p) < 48 ) { + if ( 45 <= (*p) && (*p) <= 46 ) + goto st51; + } else if ( (*p) > 57 ) { + if ( 65 <= (*p) && (*p) <= 90 ) + goto st51; + } else + goto st51; + goto st1; +st51: + if ( ++p == pe ) + goto _out51; +case 51: + switch( (*p) ) { + case 32: goto tr17; + case 36: goto st52; + case 95: goto st52; + } + if ( (*p) < 48 ) { + if ( 45 <= (*p) && (*p) <= 46 ) + goto st52; + } else if ( (*p) > 57 ) { + if ( 65 <= (*p) && (*p) <= 90 ) + goto st52; + } else + goto st52; + goto st1; +st52: + if ( ++p == pe ) + goto _out52; +case 52: + if ( (*p) == 32 ) + goto tr17; + goto st1; + } + _out1: cs = 1; goto _out; + _out2: cs = 2; goto _out; + _out3: cs = 3; goto _out; + _out4: cs = 4; goto _out; + _out5: cs = 5; goto _out; + _out6: cs = 6; goto _out; + _out7: cs = 7; goto _out; + _out8: cs = 8; goto _out; + _out9: cs = 9; goto _out; + _out10: cs = 10; goto _out; + _out11: cs = 11; goto _out; + _out12: cs = 12; goto _out; + _out13: cs = 13; goto _out; + _out14: cs = 14; goto _out; + _out15: cs = 15; goto _out; + _out16: cs = 16; goto _out; + _out53: cs = 53; goto _out; + _out17: cs = 17; goto _out; + _out18: cs = 18; goto _out; + _out19: cs = 19; goto _out; + _out20: cs = 20; goto _out; + _out21: cs = 21; goto _out; + _out22: cs = 22; goto _out; + _out23: cs = 23; goto _out; + _out24: cs = 24; goto _out; + _out25: cs = 25; goto _out; + _out26: cs = 26; goto _out; + _out27: cs = 27; goto _out; + _out28: cs = 28; goto _out; + _out29: cs = 29; goto _out; + _out30: cs = 30; goto _out; + _out31: cs = 31; goto _out; + _out32: cs = 32; goto _out; + _out33: cs = 33; goto _out; + _out34: cs = 34; goto _out; + _out35: cs = 35; goto _out; + _out36: cs = 36; goto _out; + _out37: cs = 37; goto _out; + _out38: cs = 38; goto _out; + _out39: cs = 39; goto _out; + _out40: cs = 40; goto _out; + _out41: cs = 41; goto _out; + _out42: cs = 42; goto _out; + _out43: cs = 43; goto _out; + _out44: cs = 44; goto _out; + _out45: cs = 45; goto _out; + _out46: cs = 46; goto _out; + _out47: cs = 47; goto _out; + _out48: cs = 48; goto _out; + _out49: cs = 49; goto _out; + _out50: cs = 50; goto _out; + _out51: cs = 51; goto _out; + _out52: cs = 52; goto _out; + + _out: {} + } +#line 149 "ext/http11/http11_parser.rl" + + parser->cs = cs; + parser->nread += p - (buffer + off); + + assert(p <= pe && "buffer overflow after parsing execute"); + assert(parser->nread <= len && "nread longer than length"); + assert(parser->body_start <= len && "body starts after buffer end"); + assert(parser->mark < len && "mark is after buffer end"); + assert(parser->field_len <= len && "field has length longer than whole buffer"); + assert(parser->field_start < len && "field starts after buffer end"); + + if(parser->body_start) { + /* final \r\n combo encountered so stop right here */ + +#line 1064 "ext/http11/http11_parser.c" +#line 163 "ext/http11/http11_parser.rl" + parser->nread++; + } + + return(parser->nread); +} + +int http_parser_finish(http_parser *parser) +{ + int cs = parser->cs; + + +#line 1077 "ext/http11/http11_parser.c" +#line 174 "ext/http11/http11_parser.rl" + + parser->cs = cs; + + if (http_parser_has_error(parser) ) { + return -1; + } else if (http_parser_is_finished(parser) ) { + return 1; + } else { + return 0; + } +} + +int http_parser_has_error(http_parser *parser) { + return parser->cs == http_parser_error; +} + +int http_parser_is_finished(http_parser *parser) { + return parser->cs == http_parser_first_final; +} diff --git a/amarok/src/mediadevice/daap/mongrel/http11/http11_parser.h b/amarok/src/mediadevice/daap/mongrel/http11/http11_parser.h new file mode 100644 index 00000000..cd6692dc --- /dev/null +++ b/amarok/src/mediadevice/daap/mongrel/http11/http11_parser.h @@ -0,0 +1,48 @@ +/** + * Copyright (c) 2005 Zed A. Shaw + * You can redistribute it and/or modify it under the same terms as Ruby. + */ + +#ifndef http11_parser_h +#define http11_parser_h + +#include <sys/types.h> + +#if defined(_WIN32) +#include <stddef.h> +#endif + +typedef void (*element_cb)(void *data, const char *at, size_t length); +typedef void (*field_cb)(void *data, const char *field, size_t flen, const char *value, size_t vlen); + +typedef struct http_parser { + int cs; + size_t body_start; + int content_len; + size_t nread; + size_t mark; + size_t field_start; + size_t field_len; + size_t query_start; + + void *data; + + field_cb http_field; + element_cb request_method; + element_cb request_uri; + element_cb request_path; + element_cb query_string; + element_cb http_version; + element_cb header_done; + +} http_parser; + +int http_parser_init(http_parser *parser); +int http_parser_finish(http_parser *parser); +size_t http_parser_execute(http_parser *parser, const char *data, size_t len, size_t off); +int http_parser_has_error(http_parser *parser); +int http_parser_is_finished(http_parser *parser); + +#define http_parser_nread(parser) (parser)->nread + +#endif diff --git a/amarok/src/mediadevice/daap/mongrel/http11/http11_parser.rl b/amarok/src/mediadevice/daap/mongrel/http11/http11_parser.rl new file mode 100644 index 00000000..90bb63f9 --- /dev/null +++ b/amarok/src/mediadevice/daap/mongrel/http11/http11_parser.rl @@ -0,0 +1,192 @@ +/** + * Copyright (c) 2005 Zed A. Shaw + * You can redistribute it and/or modify it under the same terms as Ruby. + */ +#include "http11_parser.h" +#include <stdio.h> +#include <assert.h> +#include <stdlib.h> +#include <ctype.h> +#include <string.h> + +#define LEN(AT, FPC) (FPC - buffer - parser->AT) +#define MARK(M,FPC) (parser->M = (FPC) - buffer) +#define PTR_TO(F) (buffer + parser->F) + +/** machine **/ +%%{ + machine http_parser; + + action mark {MARK(mark, fpc); } + + + action start_field { MARK(field_start, fpc); } + action write_field { + parser->field_len = LEN(field_start, fpc); + } + + action start_value { MARK(mark, fpc); } + action write_value { + if(parser->http_field != NULL) { + parser->http_field(parser->data, PTR_TO(field_start), parser->field_len, PTR_TO(mark), LEN(mark, fpc)); + } + } + action request_method { + if(parser->request_method != NULL) + parser->request_method(parser->data, PTR_TO(mark), LEN(mark, fpc)); + } + action request_uri { + if(parser->request_uri != NULL) + parser->request_uri(parser->data, PTR_TO(mark), LEN(mark, fpc)); + } + + action start_query {MARK(query_start, fpc); } + action query_string { + if(parser->query_string != NULL) + parser->query_string(parser->data, PTR_TO(query_start), LEN(query_start, fpc)); + } + + action http_version { + if(parser->http_version != NULL) + parser->http_version(parser->data, PTR_TO(mark), LEN(mark, fpc)); + } + + action request_path { + if(parser->request_path != NULL) + parser->request_path(parser->data, PTR_TO(mark), LEN(mark,fpc)); + } + + action done { + parser->body_start = fpc - buffer + 1; + if(parser->header_done != NULL) + parser->header_done(parser->data, fpc + 1, pe - fpc - 1); + fbreak; + } + + +#### HTTP PROTOCOL GRAMMAR +# line endings + CRLF = "\r\n"; + +# character types + CTL = (cntrl | 127); + safe = ("$" | "-" | "_" | "."); + extra = ("!" | "*" | "'" | "(" | ")" | ","); + reserved = (";" | "/" | "?" | ":" | "@" | "&" | "=" | "+"); + unsafe = (CTL | " " | "\"" | "#" | "%" | "<" | ">"); + national = any -- (alpha | digit | reserved | extra | safe | unsafe); + unreserved = (alpha | digit | safe | extra | national); + escape = ("%" xdigit xdigit); + uchar = (unreserved | escape); + pchar = (uchar | ":" | "@" | "&" | "=" | "+"); + tspecials = ("(" | ")" | "<" | ">" | "@" | "," | ";" | ":" | "\\" | "\"" | "/" | "[" | "]" | "?" | "=" | "{" | "}" | " " | "\t"); + +# elements + token = (ascii -- (CTL | tspecials)); + +# URI schemes and absolute paths + scheme = ( alpha | digit | "+" | "-" | "." )* ; + absolute_uri = (scheme ":" (uchar | reserved )*); + + path = (pchar+ ( "/" pchar* )*) ; + query = ( uchar | reserved )* %query_string ; + param = ( pchar | "/" )* ; + params = (param ( ";" param )*) ; + rel_path = (path? %request_path (";" params)?) ("?" %start_query query)?; + absolute_path = ("/"+ rel_path); + + Request_URI = ("*" | absolute_uri | absolute_path) >mark %request_uri; + Method = (upper | digit | safe){1,20} >mark %request_method; + + http_number = (digit+ "." digit+) ; + HTTP_Version = ("HTTP/" http_number) >mark %http_version ; + Request_Line = (Method " " Request_URI " " HTTP_Version CRLF) ; + + field_name = (token -- ":")+ >start_field %write_field; + + field_value = any* >start_value %write_value; + + message_header = field_name ":" " "* field_value :> CRLF; + + Request = Request_Line (message_header)* ( CRLF @done); + +main := Request; +}%% + +/** Data **/ +%% write data; + +int http_parser_init(http_parser *parser) { + int cs = 0; + %% write init; + parser->cs = cs; + parser->body_start = 0; + parser->content_len = 0; + parser->mark = 0; + parser->nread = 0; + parser->field_len = 0; + parser->field_start = 0; + + return(1); +} + + +/** exec **/ +size_t http_parser_execute(http_parser *parser, const char *buffer, size_t len, size_t off) { + const char *p, *pe; + int cs = parser->cs; + + assert(off <= len && "offset past end of buffer"); + + p = buffer+off; + pe = buffer+len; + + assert(*pe == '\0' && "pointer does not end on NUL"); + assert(pe - p == len - off && "pointers aren't same distance"); + + + %% write exec; + + parser->cs = cs; + parser->nread += p - (buffer + off); + + assert(p <= pe && "buffer overflow after parsing execute"); + assert(parser->nread <= len && "nread longer than length"); + assert(parser->body_start <= len && "body starts after buffer end"); + assert(parser->mark < len && "mark is after buffer end"); + assert(parser->field_len <= len && "field has length longer than whole buffer"); + assert(parser->field_start < len && "field starts after buffer end"); + + if(parser->body_start) { + /* final \r\n combo encountered so stop right here */ + %%write eof; + parser->nread++; + } + + return(parser->nread); +} + +int http_parser_finish(http_parser *parser) +{ + int cs = parser->cs; + + %%write eof; + + parser->cs = cs; + + if (http_parser_has_error(parser) ) { + return -1; + } else if (http_parser_is_finished(parser) ) { + return 1; + } else { + return 0; + } +} + +int http_parser_has_error(http_parser *parser) { + return parser->cs == http_parser_error; +} + +int http_parser_is_finished(http_parser *parser) { + return parser->cs == http_parser_first_final; +} diff --git a/amarok/src/mediadevice/daap/mongrel/http11/tst.h b/amarok/src/mediadevice/daap/mongrel/http11/tst.h new file mode 100644 index 00000000..8e5ab2c2 --- /dev/null +++ b/amarok/src/mediadevice/daap/mongrel/http11/tst.h @@ -0,0 +1,40 @@ + + +struct node +{ + unsigned char value; + struct node *left; + struct node *middle; + struct node *right; +}; + +struct tst +{ + int node_line_width; + struct node_lines *node_lines; + struct node *free_list; + struct node *head[127]; +}; + +struct node_lines +{ + struct node *node_line; + struct node_lines *next; +}; + +enum tst_constants +{ + TST_OK, TST_ERROR, TST_NULL_KEY, TST_DUPLICATE_KEY, TST_REPLACE +}; + +struct tst *tst_init(int node_line_width); + +int tst_insert(unsigned char *key, void *data, struct tst *tst, int option, void **exist_ptr); + +void *tst_search(unsigned char *key, struct tst *tst, int *prefix_len); + +void *tst_delete(unsigned char *key, struct tst *tst); + +void tst_cleanup(struct tst *tst); + + diff --git a/amarok/src/mediadevice/daap/mongrel/http11/tst_cleanup.c b/amarok/src/mediadevice/daap/mongrel/http11/tst_cleanup.c new file mode 100644 index 00000000..b574f9f7 --- /dev/null +++ b/amarok/src/mediadevice/daap/mongrel/http11/tst_cleanup.c @@ -0,0 +1,24 @@ + +#include "tst.h" +#include <stdio.h> +#include <stdlib.h> + +void tst_cleanup(struct tst *tst) +{ + struct node_lines *current_line; + struct node_lines *next_line; + + next_line = tst->node_lines; + + do + { + current_line = next_line; + next_line = current_line->next; + free(current_line->node_line); + free(current_line); + } + while(next_line != NULL); + + free(tst); +} + diff --git a/amarok/src/mediadevice/daap/mongrel/http11/tst_delete.c b/amarok/src/mediadevice/daap/mongrel/http11/tst_delete.c new file mode 100644 index 00000000..cb18f16a --- /dev/null +++ b/amarok/src/mediadevice/daap/mongrel/http11/tst_delete.c @@ -0,0 +1,146 @@ + +#include "tst.h" +#include <stdio.h> +#include <stdlib.h> + +void *tst_delete(unsigned char *key, struct tst *tst) +{ + struct node *current_node; + struct node *current_node_parent; + struct node *last_branch; + struct node *last_branch_parent; + struct node *next_node; + struct node *last_branch_replacement; + struct node *last_branch_dangling_child; + int key_index; + + + if(key[0] == 0) + return NULL; + if(tst->head[(int)key[0]] == NULL) + return NULL; + + last_branch = NULL; + last_branch_parent = NULL; + current_node = tst->head[(int)key[0]]; + current_node_parent = NULL; + key_index = 1; + while(current_node != NULL) + { + if(key[key_index] == current_node->value) + { + + if( (current_node->left != NULL) || (current_node->right != NULL) ) + { + last_branch = current_node; + last_branch_parent = current_node_parent; + } + if(key[key_index] == 0) + break; + else + { + current_node_parent = current_node; + current_node = current_node->middle; + key_index++; + continue; + } + } + else if( ((current_node->value == 0) && (key[key_index] < 64)) || + ((current_node->value != 0) && (key[key_index] < + current_node->value)) ) + { + last_branch_parent = current_node; + current_node_parent = current_node; + current_node = current_node->left; + last_branch = current_node; + continue; + } + else + { + last_branch_parent = current_node; + current_node_parent = current_node; + current_node = current_node->right; + last_branch = current_node; + continue; + } + + } + if(current_node == NULL) + return NULL; + + if(last_branch == NULL) + { + + next_node = tst->head[(int)key[0]]; + tst->head[(int)key[0]] = NULL; + } + else if( (last_branch->left == NULL) && (last_branch->right == NULL) ) + { + + if(last_branch_parent->left == last_branch) + last_branch_parent->left = NULL; + else + last_branch_parent->right = NULL; + + next_node = last_branch; + } + else + { + + if( (last_branch->left != NULL) && (last_branch->right != NULL) ) + { + last_branch_replacement = last_branch->right; + last_branch_dangling_child = last_branch->left; + } + else if(last_branch->right != NULL) + { + last_branch_replacement = last_branch->right; + last_branch_dangling_child = NULL; + } + else + { + last_branch_replacement = last_branch->left; + last_branch_dangling_child = NULL; + } + + if(last_branch_parent == NULL) + tst->head[(int)key[0]]=last_branch_replacement; + else + { + if (last_branch_parent->left == last_branch) + last_branch_parent->left = last_branch_replacement; + else if (last_branch_parent->right == last_branch) + last_branch_parent->right = last_branch_replacement; + else + last_branch_parent->middle = last_branch_replacement; + } + + if(last_branch_dangling_child != NULL) + { + current_node = last_branch_replacement; + + while (current_node->left != NULL) + current_node = current_node->left; + + current_node->left = last_branch_dangling_child; + } + + next_node = last_branch; + } + + do + { + current_node = next_node; + next_node = current_node->middle; + + current_node->left = NULL; + current_node->right = NULL; + current_node->middle = tst->free_list; + tst->free_list = current_node; + } + while(current_node->value != 0); + + return next_node; + +} + diff --git a/amarok/src/mediadevice/daap/mongrel/http11/tst_grow_node_free_list.c b/amarok/src/mediadevice/daap/mongrel/http11/tst_grow_node_free_list.c new file mode 100644 index 00000000..da21333f --- /dev/null +++ b/amarok/src/mediadevice/daap/mongrel/http11/tst_grow_node_free_list.c @@ -0,0 +1,38 @@ + +#include "tst.h" +#include <stdio.h> +#include <stdlib.h> + +int tst_grow_node_free_list(struct tst *tst) +{ + struct node *current_node; + struct node_lines *new_line; + int i; + + + if((new_line = (struct node_lines *) malloc(sizeof(struct node_lines))) == NULL) + return TST_ERROR; + + if((new_line->node_line = (struct node *) + calloc(tst->node_line_width, sizeof(struct node))) == NULL) + { + free(new_line); + return TST_ERROR; + } + else + { + new_line->next = tst->node_lines; + tst->node_lines = new_line; + } + + current_node = tst->node_lines->node_line; + tst->free_list = current_node; + for (i = 1; i < tst->node_line_width; i++) + { + current_node->middle = &(tst->node_lines->node_line[i]); + current_node = current_node->middle; + } + current_node->middle = NULL; + return 1; +} + diff --git a/amarok/src/mediadevice/daap/mongrel/http11/tst_init.c b/amarok/src/mediadevice/daap/mongrel/http11/tst_init.c new file mode 100644 index 00000000..259734ab --- /dev/null +++ b/amarok/src/mediadevice/daap/mongrel/http11/tst_init.c @@ -0,0 +1,41 @@ + +#include "tst.h" +#include <stdio.h> +#include <stdlib.h> + +struct tst *tst_init(int width) +{ + struct tst *tst; + struct node *current_node; + int i; + + +if((tst = (struct tst *) calloc(1, sizeof(struct tst))) == NULL) + return NULL; + +if ((tst->node_lines = (struct node_lines *) calloc(1, sizeof(struct node_lines))) == NULL) +{ + free(tst); + return NULL; +} + +tst->node_line_width = width; +tst->node_lines->next = NULL; +if ((tst->node_lines->node_line = (struct node *) calloc(width, sizeof(struct node))) == NULL) +{ + free(tst->node_lines); + free(tst); + return NULL; +} + +current_node = tst->node_lines->node_line; +tst->free_list = current_node; +for (i = 1; i < width; i++) +{ + current_node->middle = &(tst->node_lines->node_line[i]); + current_node = current_node->middle; +} +current_node->middle = NULL; +return tst; +} + diff --git a/amarok/src/mediadevice/daap/mongrel/http11/tst_insert.c b/amarok/src/mediadevice/daap/mongrel/http11/tst_insert.c new file mode 100644 index 00000000..0f83849f --- /dev/null +++ b/amarok/src/mediadevice/daap/mongrel/http11/tst_insert.c @@ -0,0 +1,192 @@ + +#include "tst.h" +#include <stdio.h> +#include <stdlib.h> + + +int tst_grow_node_free_list(struct tst *tst); +int tst_insert(unsigned char *key, void *data, struct tst *tst, int option, void **exist_ptr) +{ + struct node *current_node; + struct node *new_node_tree_begin = NULL; + int key_index; + int perform_loop = 1; + + + if (key == NULL) + return TST_NULL_KEY; + + if(key[0] == 0) + return TST_NULL_KEY; + + if(tst->head[(int)key[0]] == NULL) + { + + if(tst->free_list == NULL) + { + if(tst_grow_node_free_list(tst) != 1) + return TST_ERROR; + } + tst->head[(int)key[0]] = tst->free_list; + + tst->free_list = tst->free_list->middle; + current_node = tst->head[(int)key[0]]; + current_node->value = key[1]; + if(key[1] == 0) + { + current_node->middle = data; + return TST_OK; + } + else + perform_loop = 0; + } + + current_node = tst->head[(int)key[0]]; + key_index = 1; + while(perform_loop == 1) + { + if(key[key_index] == current_node->value) + { + + if(key[key_index] == 0) + { + if (option == TST_REPLACE) + { + if (exist_ptr != NULL) + *exist_ptr = current_node->middle; + + current_node->middle = data; + return TST_OK; + } + else + { + if (exist_ptr != NULL) + *exist_ptr = current_node->middle; + return TST_DUPLICATE_KEY; + } + } + else + { + if(current_node->middle == NULL) + { + + if(tst->free_list == NULL) + { + if(tst_grow_node_free_list(tst) != 1) + return TST_ERROR; + } + current_node->middle = tst->free_list; + + tst->free_list = tst->free_list->middle; + new_node_tree_begin = current_node; + current_node = current_node->middle; + current_node->value = key[key_index]; + break; + } + else + { + current_node = current_node->middle; + key_index++; + continue; + } + } + } + + if( ((current_node->value == 0) && (key[key_index] < 64)) || + ((current_node->value != 0) && (key[key_index] < + current_node->value)) ) + { + + if (current_node->left == NULL) + { + + if(tst->free_list == NULL) + { + if(tst_grow_node_free_list(tst) != 1) + return TST_ERROR; + } + current_node->left = tst->free_list; + + tst->free_list = tst->free_list->middle; + new_node_tree_begin = current_node; + current_node = current_node->left; + current_node->value = key[key_index]; + if(key[key_index] == 0) + { + current_node->middle = data; + return TST_OK; + } + else + break; + } + else + { + current_node = current_node->left; + continue; + } + } + else + { + + if (current_node->right == NULL) + { + + if(tst->free_list == NULL) + { + if(tst_grow_node_free_list(tst) != 1) + return TST_ERROR; + } + current_node->right = tst->free_list; + + tst->free_list = tst->free_list->middle; + new_node_tree_begin = current_node; + current_node = current_node->right; + current_node->value = key[key_index]; + break; + } + else + { + current_node = current_node->right; + continue; + } + } + } + + do + { + key_index++; + + if(tst->free_list == NULL) + { + if(tst_grow_node_free_list(tst) != 1) + { + current_node = new_node_tree_begin->middle; + + while (current_node->middle != NULL) + current_node = current_node->middle; + + current_node->middle = tst->free_list; + tst->free_list = new_node_tree_begin->middle; + new_node_tree_begin->middle = NULL; + + return TST_ERROR; + } + } + + + if(tst->free_list == NULL) + { + if(tst_grow_node_free_list(tst) != 1) + return TST_ERROR; + } + current_node->middle = tst->free_list; + + tst->free_list = tst->free_list->middle; + current_node = current_node->middle; + current_node->value = key[key_index]; + } while(key[key_index] !=0); + + current_node->middle = data; + return TST_OK; +} + diff --git a/amarok/src/mediadevice/daap/mongrel/http11/tst_search.c b/amarok/src/mediadevice/daap/mongrel/http11/tst_search.c new file mode 100644 index 00000000..44aee7ac --- /dev/null +++ b/amarok/src/mediadevice/daap/mongrel/http11/tst_search.c @@ -0,0 +1,68 @@ + +#include "tst.h" +#include <stdio.h> +#include <stdlib.h> +#include <assert.h> + +void *tst_search(unsigned char *key, struct tst *tst, int *prefix_len) +{ + struct node *current_node; + void *longest_match = NULL; + int key_index; + + assert(key != NULL && "key can't be NULL"); + assert(tst != NULL && "tst can't be NULL"); + + if(key[0] == 0) + return NULL; + + if(tst->head[(int)key[0]] == NULL) + return NULL; + + + if(prefix_len) *prefix_len = 0; + current_node = tst->head[(int)key[0]]; + key_index = 1; + + while (current_node != NULL) + { + if(key[key_index] == current_node->value) + { + if(current_node->value == 0) { + if(prefix_len) *prefix_len = key_index; + return current_node->middle; + } else { + current_node = current_node->middle; + if(current_node && current_node->value == 0) { + if(prefix_len) *prefix_len = key_index+1; + longest_match = current_node->middle; + } + + key_index++; + continue; + } + } + else if( ((current_node->value == 0) && (key[key_index] < 64)) || + ((current_node->value != 0) && (key[key_index] < + current_node->value)) ) + { + if(current_node->value == 0) { + if(prefix_len) *prefix_len = key_index; + longest_match = current_node->middle; + } + current_node = current_node->left; + continue; + } + else + { + if(current_node->value == 0) { + if(prefix_len) *prefix_len = key_index; + longest_match = current_node->middle; + } + current_node = current_node->right; + continue; + } + } + + return longest_match; +} diff --git a/amarok/src/mediadevice/daap/mongrel/lib/Makefile.am b/amarok/src/mediadevice/daap/mongrel/lib/Makefile.am new file mode 100644 index 00000000..d0f92bf6 --- /dev/null +++ b/amarok/src/mediadevice/daap/mongrel/lib/Makefile.am @@ -0,0 +1,11 @@ +SUBDIRS = mongrel rubygems rbconfig + +amarokrubylibdir = \ + $(kde_datadir)/amarok/ruby_lib + +amarokrubylib_DATA = \ + gem_plugin.rb \ + gemconfigure.rb \ + mongrel.rb \ + rubygems.rb + diff --git a/amarok/src/mediadevice/daap/mongrel/lib/README b/amarok/src/mediadevice/daap/mongrel/lib/README new file mode 100644 index 00000000..9fd39bcc --- /dev/null +++ b/amarok/src/mediadevice/daap/mongrel/lib/README @@ -0,0 +1,5 @@ +The following Ruby libraries are included here, both licensed under the Ruby +License, which has a dual-licensed that includes the GPL. +*Mongrel 1.0.1 http://mongrel.rubyforge.org/ - a web server library +*RubyGems 0.9.0 http://www.rubygems.org/ - a Ruby package system needed by Mongrel + diff --git a/amarok/src/mediadevice/daap/mongrel/lib/gem_plugin.rb b/amarok/src/mediadevice/daap/mongrel/lib/gem_plugin.rb new file mode 100644 index 00000000..27d9f28a --- /dev/null +++ b/amarok/src/mediadevice/daap/mongrel/lib/gem_plugin.rb @@ -0,0 +1,287 @@ +require 'singleton' +require 'rubygems' + +# Implements a dynamic plugin loading, configuration, and discovery system +# based on RubyGems and a simple additional name space that looks like a URI. +# +# A plugin is created and put into a category with the following code: +# +# class MyThing < GemPlugin::Plugin "/things" +# ... +# end +# +# What this does is sets up your MyThing in the plugin registry via GemPlugin::Manager. +# You can then later get this plugin with GemPlugin::Manager.create("/things/mything") +# and can also pass in options as a second parameter. +# +# This isn't such a big deal, but the power is really from the GemPlugin::Manager.load +# method. This method will go through the installed gems and require_gem any +# that depend on the gem_plugin RubyGem. You can arbitrarily include or exclude +# gems based on what they also depend on, thus letting you load these gems when appropriate. +# +# Since this system was written originally for the Mongrel project that'll be the +# best example of using it. +# +# Imagine you have a neat plugin for Mongrel called snazzy_command that gives the +# mongrel_rails a new command snazzy (like: mongrel_rails snazzy). You'd like +# people to be able to grab this plugin if they want and use it, because it's snazzy. +# +# First thing you do is create a gem of your project and make sure that it depends +# on "mongrel" AND "gem_plugin". This signals to the GemPlugin system that this is +# a plugin for mongrel. +# +# Next you put this code into a file like lib/init.rb (can be anything really): +# +# class Snazzy < GemPlugin::Plugin "/commands" +# ... +# end +# +# Then when you create your gem you have the following bits in your Rakefile: +# +# spec.add_dependency('mongrel', '>= 0.3.9') +# spec.add_dependency('gem_plugin', '>= 0.1') +# spec.autorequire = 'init.rb' +# +# Finally, you just have to now publish this gem for people to install and Mongrel +# will "magically" be able to install it. +# +# The "magic" part though is pretty simple and done via the GemPlugin::Manager.load +# method. Read that to see how it is really done. +module GemPlugin + + EXCLUDE = true + INCLUDE = false + + class PluginNotLoaded < StandardError; end + + + # This class is used by people who use gem plugins (but don't necessarily make them) + # to add plugins to their own systems. It provides a way to load plugins, list them, + # and create them as needed. + # + # It is a singleton so you use like this: GemPlugins::Manager.instance.load + class Manager + include Singleton + attr_reader :plugins + attr_reader :gems + + + def initialize + # plugins that have been loaded + @plugins = {} + + # keeps track of gems which have been loaded already by the manager *and* + # where they came from so that they can be referenced later + @gems = {} + end + + + # Responsible for going through the list of available gems and loading + # any plugins requested. It keeps track of what it's loaded already + # and won't load them again. + # + # It accepts one parameter which is a hash of gem depends that should include + # or exclude a gem from being loaded. A gem must depend on gem_plugin to be + # considered, but then each system has to add it's own INCLUDE to make sure + # that only plugins related to it are loaded. + # + # An example again comes from Mongrel. In order to load all Mongrel plugins: + # + # GemPlugin::Manager.instance.load "mongrel" => GemPlugin::INCLUDE + # + # Which will load all plugins that depend on mongrel AND gem_plugin. Now, one + # extra thing we do is we delay loading Rails Mongrel plugins until after rails + # is configured. Do do this the mongrel_rails script has: + # + # GemPlugin::Manager.instance.load "mongrel" => GemPlugin::INCLUDE, "rails" => GemPlugin::EXCLUDE + # The only thing to remember is that this is saying "include a plugin if it + # depends on gem_plugin, mongrel, but NOT rails". If a plugin also depends on other + # stuff then it's loaded just fine. Only gem_plugin, mongrel, and rails are + # ever used to determine if it should be included. + # + # NOTE: Currently RubyGems will run autorequire on gems that get required AND + # on their dependencies. This really messes with people running edge rails + # since activerecord or other stuff gets loaded for just touching a gem plugin. + # To prevent this load requires the full path to the "init.rb" file, which + # avoids the RubyGems autorequire magic. + def load(needs = {}) + sdir = File.join(Gem.dir, "specifications") + gems = Gem::SourceIndex.from_installed_gems(sdir) + needs = needs.merge({"gem_plugin" => INCLUDE}) + + gems.each do |path, gem| + # don't load gems more than once + next if @gems.has_key? gem.name + check = needs.dup + + # rolls through the depends and inverts anything it finds + gem.dependencies.each do |dep| + # this will fail if a gem is depended more than once + if check.has_key? dep.name + check[dep.name] = !check[dep.name] + end + end + + # now since excluded gems start as true, inverting them + # makes them false so we'll skip this gem if any excludes are found + if (check.select {|name,test| !test}).length == 0 + # looks like no needs were set to false, so it's good + gem_dir = File.join(Gem.dir, "gems", "#{gem.name}-#{gem.version}") + require File.join(gem_dir, "lib", gem.name, "init.rb") + @gems[gem.name] = gem_dir + end + end + + return nil + end + + + # Not necessary for you to call directly, but this is + # how GemPlugin::Base.inherited actually adds a + # plugin to a category. + def register(category, name, klass) + @plugins[category] ||= {} + @plugins[category][name.downcase] = klass + end + + + # Resolves the given name (should include /category/name) to + # find the plugin class and create an instance. You can + # pass a second hash option that is then given to the Plugin + # to configure it. + def create(name, options = {}) + last_slash = name.rindex("/") + category = name[0 ... last_slash] + plugin = name[last_slash .. -1] + + map = @plugins[category] + if not map + raise "Plugin category #{category} does not exist" + elsif not map.has_key? plugin + raise "Plugin #{plugin} does not exist in category #{category}" + else + map[plugin].new(options) + end + end + + + # Simply says whether the given gem has been loaded yet or not. + def loaded?(gem_name) + @gems.has_key? gem_name + end + + + # GemPlugins can have a 'resources' directory which is packaged with them + # and can hold any data resources the plugin may need. The main problem + # is that it's difficult to figure out where these resources are + # actually located on the file system. The resource method tries to + # locate the real path for a given resource path. + # + # Let's say you have a 'resources/stylesheets/default.css' file in your + # gem distribution, then finding where this file really is involves: + # + # Manager.instance.resource("mygem", "/stylesheets/default.css") + # + # You either get back the full path to the resource or you get a nil + # if it doesn't exist. + # + # If you request a path for a GemPlugin that hasn't been loaded yet + # then it will throw an PluginNotLoaded exception. The gem may be + # present on your system in this case, but you just haven't loaded + # it with Manager.instance.load properly. + def resource(gem_name, path) + if not loaded? gem_name + raise PluginNotLoaded.new("Plugin #{gem_name} not loaded when getting resource #{path}") + end + + file = File.join(@gems[gem_name], "resources", path) + + if File.exist? file + return file + else + return nil + end + end + + + # While Manager.resource will find arbitrary resources, a special + # case is when you need to load a set of configuration defaults. + # GemPlugin normalizes this to be if you have a file "resources/defaults.yaml" + # then you'll be able to load them via Manager.config. + # + # How you use the method is you get the options the user wants set, pass + # them to Manager.instance.config, and what you get back is a new Hash + # with the user's settings overriding the defaults. + # + # opts = Manager.instance.config "mygem", :age => 12, :max_load => .9 + # + # In the above case, if defaults had {:age => 14} then it would be + # changed to 12. + # + # This loads the .yaml file on the fly every time, so doing it a + # whole lot is very stupid. If you need to make frequent calls to + # this, call it once with no options (Manager.instance.config) then + # use the returned defaults directly from then on. + def config(gem_name, options={}) + config_file = Manager.instance.resource(gem_name, "/defaults.yaml") + if config_file + begin + defaults = YAML.load_file(config_file) + return defaults.merge(options) + rescue + raise "Error loading config #{config_file} for gem #{gem_name}" + end + else + return options + end + end + end + + # This base class for plugins really does nothing + # more than wire up the new class into the right category. + # It is not thread-safe yet but will be soon. + class Base + + attr_reader :options + + + # See Mongrel::Plugin for an explanation. + def Base.inherited(klass) + name = "/" + klass.to_s.downcase + Manager.instance.register(@@category, name, klass) + @@category = nil + end + + # See Mongrel::Plugin for an explanation. + def Base.category=(category) + @@category = category + end + + def initialize(options = {}) + @options = options + end + + end + + # This nifty function works with the GemPlugin::Base to give you + # the syntax: + # + # class MyThing < GemPlugin::Plugin "/things" + # ... + # end + # + # What it does is temporarily sets the GemPlugin::Base.category, and then + # returns GemPlugin::Base. Since the next immediate thing Ruby does is + # use this returned class to create the new class, GemPlugin::Base.inherited + # gets called. GemPlugin::Base.inherited then uses the set category, class name, + # and class to register the plugin in the right way. + def GemPlugin::Plugin(c) + Base.category = c + Base + end + +end + + + + diff --git a/amarok/src/mediadevice/daap/mongrel/lib/gemconfigure.rb b/amarok/src/mediadevice/daap/mongrel/lib/gemconfigure.rb new file mode 100644 index 00000000..d9c7351b --- /dev/null +++ b/amarok/src/mediadevice/daap/mongrel/lib/gemconfigure.rb @@ -0,0 +1,24 @@ +#-- +# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others. +# All rights reserved. +# See LICENSE.txt for permissions. +#++ + +module Gem + + # Activate the gems specfied by the gem_pairs list. + # + # gem_pairs :: + # List of gem/version pairs. + # Eg. [['rake', '= 0.8.15'], ['RedCloth', '~> 3.0']] + # options :: + # options[:verbose] => print gems as they are required. + # + def self.configure(gem_pairs, options={}) + gem_pairs.each do |name, version| + require 'rubygems' + puts "Requiring gem #{name} (version #{version})" if options[:verbose] + require_gem name, version + end + end +end diff --git a/amarok/src/mediadevice/daap/mongrel/lib/mongrel.rb b/amarok/src/mediadevice/daap/mongrel/lib/mongrel.rb new file mode 100644 index 00000000..6c348fbc --- /dev/null +++ b/amarok/src/mediadevice/daap/mongrel/lib/mongrel.rb @@ -0,0 +1,806 @@ +# Copyright (c) 2005 Zed A. Shaw +# You can redistribute it and/or modify it under the same terms as Ruby. +# +# Additional work donated by contributors. See http://mongrel.rubyforge.org/attributions.html +# for more information. + +$mongrel_debug_client = false + +require 'rubygems' +require 'socket' +require 'http11' +require 'tempfile' +begin + require 'fastthread' +rescue RuntimeError => e + warn "fastthread not loaded: #{ e.message }" +rescue LoadError +ensure + require 'thread' +end +require 'stringio' +require 'mongrel/cgi' +require 'mongrel/handlers' +require 'mongrel/command' +require 'mongrel/tcphack' +require 'yaml' +require 'mongrel/configurator' +require 'time' +require 'etc' +require 'uri' + + +# Mongrel module containing all of the classes (include C extensions) for running +# a Mongrel web server. It contains a minimalist HTTP server with just enough +# functionality to service web application requests fast as possible. +module Mongrel + + class URIClassifier + attr_reader :handler_map + + # Returns the URIs that have been registered with this classifier so far. + # The URIs returned should not be modified as this will cause a memory leak. + # You can use this to inspect the contents of the URIClassifier. + def uris + @handler_map.keys + end + + # Simply does an inspect that looks like a Hash inspect. + def inspect + @handler_map.inspect + end + end + + + # Used to stop the HttpServer via Thread.raise. + class StopServer < Exception; end + + + # Thrown at a thread when it is timed out. + class TimeoutError < Exception; end + + + # Every standard HTTP code mapped to the appropriate message. These are + # used so frequently that they are placed directly in Mongrel for easy + # access rather than Mongrel::Const. + HTTP_STATUS_CODES = { + 100 => 'Continue', + 101 => 'Switching Protocols', + 200 => 'OK', + 201 => 'Created', + 202 => 'Accepted', + 203 => 'Non-Authoritative Information', + 204 => 'No Content', + 205 => 'Reset Content', + 206 => 'Partial Content', + 300 => 'Multiple Choices', + 301 => 'Moved Permanently', + 302 => 'Moved Temporarily', + 303 => 'See Other', + 304 => 'Not Modified', + 305 => 'Use Proxy', + 400 => 'Bad Request', + 401 => 'Unauthorized', + 402 => 'Payment Required', + 403 => 'Forbidden', + 404 => 'Not Found', + 405 => 'Method Not Allowed', + 406 => 'Not Acceptable', + 407 => 'Proxy Authentication Required', + 408 => 'Request Time-out', + 409 => 'Conflict', + 410 => 'Gone', + 411 => 'Length Required', + 412 => 'Precondition Failed', + 413 => 'Request Entity Too Large', + 414 => 'Request-URI Too Large', + 415 => 'Unsupported Media Type', + 500 => 'Internal Server Error', + 501 => 'Not Implemented', + 502 => 'Bad Gateway', + 503 => 'Service Unavailable', + 504 => 'Gateway Time-out', + 505 => 'HTTP Version not supported' + } + + + # Frequently used constants when constructing requests or responses. Many times + # the constant just refers to a string with the same contents. Using these constants + # gave about a 3% to 10% performance improvement over using the strings directly. + # Symbols did not really improve things much compared to constants. + # + # While Mongrel does try to emulate the CGI/1.2 protocol, it does not use the REMOTE_IDENT, + # REMOTE_USER, or REMOTE_HOST parameters since those are either a security problem or + # too taxing on performance. + module Const + DATE = "Date".freeze + + # This is the part of the path after the SCRIPT_NAME. URIClassifier will determine this. + PATH_INFO="PATH_INFO".freeze + + # This is the initial part that your handler is identified as by URIClassifier. + SCRIPT_NAME="SCRIPT_NAME".freeze + + # The original URI requested by the client. Passed to URIClassifier to build PATH_INFO and SCRIPT_NAME. + REQUEST_URI='REQUEST_URI'.freeze + REQUEST_PATH='REQUEST_PATH'.freeze + + MONGREL_VERSION="1.0.1".freeze + + MONGREL_TMP_BASE="mongrel".freeze + + # The standard empty 404 response for bad requests. Use Error4040Handler for custom stuff. + ERROR_404_RESPONSE="HTTP/1.1 404 Not Found\r\nConnection: close\r\nServer: Mongrel #{MONGREL_VERSION}\r\n\r\nNOT FOUND".freeze + + CONTENT_LENGTH="CONTENT_LENGTH".freeze + + # A common header for indicating the server is too busy. Not used yet. + ERROR_503_RESPONSE="HTTP/1.1 503 Service Unavailable\r\n\r\nBUSY".freeze + + # The basic max request size we'll try to read. + CHUNK_SIZE=(16 * 1024) + + # This is the maximum header that is allowed before a client is booted. The parser detects + # this, but we'd also like to do this as well. + MAX_HEADER=1024 * (80 + 32) + + # Maximum request body size before it is moved out of memory and into a tempfile for reading. + MAX_BODY=MAX_HEADER + + # A frozen format for this is about 15% faster + STATUS_FORMAT = "HTTP/1.1 %d %s\r\nConnection: close\r\n".freeze + CONTENT_TYPE = "Content-Type".freeze + LAST_MODIFIED = "Last-Modified".freeze + ETAG = "ETag".freeze + SLASH = "/".freeze + REQUEST_METHOD="REQUEST_METHOD".freeze + GET="GET".freeze + HEAD="HEAD".freeze + # ETag is based on the apache standard of hex mtime-size-inode (inode is 0 on win32) + ETAG_FORMAT="\"%x-%x-%x\"".freeze + HEADER_FORMAT="%s: %s\r\n".freeze + LINE_END="\r\n".freeze + REMOTE_ADDR="REMOTE_ADDR".freeze + HTTP_X_FORWARDED_FOR="HTTP_X_FORWARDED_FOR".freeze + HTTP_IF_MODIFIED_SINCE="HTTP_IF_MODIFIED_SINCE".freeze + HTTP_IF_NONE_MATCH="HTTP_IF_NONE_MATCH".freeze + REDIRECT = "HTTP/1.1 302 Found\r\nLocation: %s\r\nConnection: close\r\n\r\n".freeze + HOST = "HOST".freeze + end + + + # Basically a Hash with one extra parameter for the HTTP body, mostly used internally. + class HttpParams < Hash + attr_accessor :http_body + end + + + # When a handler is found for a registered URI then this class is constructed + # and passed to your HttpHandler::process method. You should assume that + # *one* handler processes all requests. Included in the HttpRequest is a + # HttpRequest.params Hash that matches common CGI params, and a HttpRequest.body + # which is a string containing the request body (raw for now). + # + # The HttpRequest.initialize method will convert any request that is larger than + # Const::MAX_BODY into a Tempfile and use that as the body. Otherwise it uses + # a StringIO object. To be safe, you should assume it works like a file. + # + # The HttpHandler.request_notify system is implemented by having HttpRequest call + # HttpHandler.request_begins, HttpHandler.request_progress, HttpHandler.process during + # the IO processing. This adds a small amount of overhead but lets you implement + # finer controlled handlers and filters. + class HttpRequest + attr_reader :body, :params + + # You don't really call this. It's made for you. + # Main thing it does is hook up the params, and store any remaining + # body data into the HttpRequest.body attribute. + def initialize(params, socket, dispatchers) + @params = params + @socket = socket + @dispatchers = dispatchers + content_length = @params[Const::CONTENT_LENGTH].to_i + remain = content_length - @params.http_body.length + + # tell all dispatchers the request has begun + @dispatchers.each do |dispatcher| + dispatcher.request_begins(@params) + end unless @dispatchers.nil? || @dispatchers.empty? + + # Some clients (like FF1.0) report 0 for body and then send a body. This will probably truncate them but at least the request goes through usually. + if remain <= 0 + # we've got everything, pack it up + @body = StringIO.new + @body.write @params.http_body + update_request_progress(0, content_length) + elsif remain > 0 + # must read more data to complete body + if remain > Const::MAX_BODY + # huge body, put it in a tempfile + @body = Tempfile.new(Const::MONGREL_TMP_BASE) + @body.binmode + else + # small body, just use that + @body = StringIO.new + end + + @body.write @params.http_body + read_body(remain, content_length) + end + + @body.rewind if @body + end + + # updates all dispatchers about our progress + def update_request_progress(clen, total) + return if @dispatchers.nil? || @dispatchers.empty? + @dispatchers.each do |dispatcher| + dispatcher.request_progress(@params, clen, total) + end + end + private :update_request_progress + + # Does the heavy lifting of properly reading the larger body requests in + # small chunks. It expects @body to be an IO object, @socket to be valid, + # and will set @body = nil if the request fails. It also expects any initial + # part of the body that has been read to be in the @body already. + def read_body(remain, total) + begin + # write the odd sized chunk first + @params.http_body = read_socket(remain % Const::CHUNK_SIZE) + + remain -= @body.write(@params.http_body) + + update_request_progress(remain, total) + + # then stream out nothing but perfectly sized chunks + until remain <= 0 or @socket.closed? + # ASSUME: we are writing to a disk and these writes always write the requested amount + @params.http_body = read_socket(Const::CHUNK_SIZE) + remain -= @body.write(@params.http_body) + + update_request_progress(remain, total) + end + rescue Object + STDERR.puts "ERROR reading http body: #$!" + $!.backtrace.join("\n") + # any errors means we should delete the file, including if the file is dumped + @socket.close rescue Object + @body.delete if @body.class == Tempfile + @body = nil # signals that there was a problem + end + end + + def read_socket(len) + if !@socket.closed? + data = @socket.read(len) + if !data + raise "Socket read return nil" + elsif data.length != len + raise "Socket read returned insufficient data: #{data.length}" + else + data + end + else + raise "Socket already closed when reading." + end + end + + # Performs URI escaping so that you can construct proper + # query strings faster. Use this rather than the cgi.rb + # version since it's faster. (Stolen from Camping). + def self.escape(s) + s.to_s.gsub(/([^ a-zA-Z0-9_.-]+)/n) { + '%'+$1.unpack('H2'*$1.size).join('%').upcase + }.tr(' ', '+') + end + + + # Unescapes a URI escaped string. (Stolen from Camping). + def self.unescape(s) + s.tr('+', ' ').gsub(/((?:%[0-9a-fA-F]{2})+)/n){ + [$1.delete('%')].pack('H*') + } + end + + # Parses a query string by breaking it up at the '&' + # and ';' characters. You can also use this to parse + # cookies by changing the characters used in the second + # parameter (which defaults to '&;'. + def self.query_parse(qs, d = '&;') + params = {} + (qs||'').split(/[#{d}] */n).inject(params) { |h,p| + k, v=unescape(p).split('=',2) + if cur = params[k] + if cur.class == Array + params[k] << v + else + params[k] = [cur, v] + end + else + params[k] = v + end + } + + return params + end + end + + + # This class implements a simple way of constructing the HTTP headers dynamically + # via a Hash syntax. Think of it as a write-only Hash. Refer to HttpResponse for + # information on how this is used. + # + # One consequence of this write-only nature is that you can write multiple headers + # by just doing them twice (which is sometimes needed in HTTP), but that the normal + # semantics for Hash (where doing an insert replaces) is not there. + class HeaderOut + attr_reader :out + attr_accessor :allowed_duplicates + + def initialize(out) + @sent = {} + @allowed_duplicates = {"Set-Cookie" => true, "Set-Cookie2" => true, + "Warning" => true, "WWW-Authenticate" => true} + @out = out + end + + # Simply writes "#{key}: #{value}" to an output buffer. + def[]=(key,value) + if not @sent.has_key?(key) or @allowed_duplicates.has_key?(key) + @sent[key] = true + @out.write(Const::HEADER_FORMAT % [key, value]) + end + end + end + + + # Writes and controls your response to the client using the HTTP/1.1 specification. + # You use it by simply doing: + # + # response.start(200) do |head,out| + # head['Content-Type'] = 'text/plain' + # out.write("hello\n") + # end + # + # The parameter to start is the response code--which Mongrel will translate for you + # based on HTTP_STATUS_CODES. The head parameter is how you write custom headers. + # The out parameter is where you write your body. The default status code for + # HttpResponse.start is 200 so the above example is redundant. + # + # As you can see, it's just like using a Hash and as you do this it writes the proper + # header to the output on the fly. You can even intermix specifying headers and + # writing content. The HttpResponse class with write the things in the proper order + # once the HttpResponse.block is ended. + # + # You may also work the HttpResponse object directly using the various attributes available + # for the raw socket, body, header, and status codes. If you do this you're on your own. + # A design decision was made to force the client to not pipeline requests. HTTP/1.1 + # pipelining really kills the performance due to how it has to be handled and how + # unclear the standard is. To fix this the HttpResponse gives a "Connection: close" + # header which forces the client to close right away. The bonus for this is that it + # gives a pretty nice speed boost to most clients since they can close their connection + # immediately. + # + # One additional caveat is that you don't have to specify the Content-length header + # as the HttpResponse will write this for you based on the out length. + class HttpResponse + attr_reader :socket + attr_reader :body + attr_writer :body + attr_reader :header + attr_reader :status + attr_writer :status + attr_reader :body_sent + attr_reader :header_sent + attr_reader :status_sent + + def initialize(socket) + @socket = socket + @body = StringIO.new + @status = 404 + @header = HeaderOut.new(StringIO.new) + @header[Const::DATE] = Time.now.httpdate + @body_sent = false + @header_sent = false + @status_sent = false + end + + # Receives a block passing it the header and body for you to work with. + # When the block is finished it writes everything you've done to + # the socket in the proper order. This lets you intermix header and + # body content as needed. Handlers are able to modify pretty much + # any part of the request in the chain, and can stop further processing + # by simple passing "finalize=true" to the start method. By default + # all handlers run and then mongrel finalizes the request when they're + # all done. + def start(status=200, finalize=false) + @status = status.to_i + yield @header, @body + finished if finalize + end + + # Primarily used in exception handling to reset the response output in order to write + # an alternative response. It will abort with an exception if you have already + # sent the header or the body. This is pretty catastrophic actually. + def reset + if @body_sent + raise "You have already sent the request body." + elsif @header_sent + raise "You have already sent the request headers." + else + @header.out.truncate(0) + @body.close + @body = StringIO.new + end + end + + def send_status(content_length=@body.length) + if not @status_sent + @header['Content-Length'] = content_length unless @status == 304 + write(Const::STATUS_FORMAT % [@status, HTTP_STATUS_CODES[@status]]) + @status_sent = true + end + end + + def send_header + if not @header_sent + @header.out.rewind + write(@header.out.read + Const::LINE_END) + @header_sent = true + end + end + + def send_body + if not @body_sent + @body.rewind + write(@body.read) + @body_sent = true + end + end + + # Appends the contents of +path+ to the response stream. The file is opened for binary + # reading and written in chunks to the socket. + # + # Sendfile API support has been removed in 0.3.13.4 due to stability problems. + def send_file(path, small_file = false) + if small_file + File.open(path, "rb") {|f| @socket << f.read } + else + File.open(path, "rb") do |f| + while chunk = f.read(Const::CHUNK_SIZE) and chunk.length > 0 + begin + write(chunk) + rescue Object => exc + break + end + end + end + end + @body_sent = true + end + + def socket_error(details) + # ignore these since it means the client closed off early + @socket.close rescue Object + done = true + raise details + end + + def write(data) + @socket.write(data) + rescue => details + socket_error(details) + end + + # This takes whatever has been done to header and body and then writes it in the + # proper format to make an HTTP/1.1 response. + def finished + send_status + send_header + send_body + end + + # Used during error conditions to mark the response as "done" so there isn't any more processing + # sent to the client. + def done=(val) + @status_sent = true + @header_sent = true + @body_sent = true + end + + def done + (@status_sent and @header_sent and @body_sent) + end + + end + + + # This is the main driver of Mongrel, while the Mongrel::HttpParser and Mongrel::URIClassifier + # make up the majority of how the server functions. It's a very simple class that just + # has a thread accepting connections and a simple HttpServer.process_client function + # to do the heavy lifting with the IO and Ruby. + # + # You use it by doing the following: + # + # server = HttpServer.new("0.0.0.0", 3000) + # server.register("/stuff", MyNiftyHandler.new) + # server.run.join + # + # The last line can be just server.run if you don't want to join the thread used. + # If you don't though Ruby will mysteriously just exit on you. + # + # Ruby's thread implementation is "interesting" to say the least. Experiments with + # *many* different types of IO processing simply cannot make a dent in it. Future + # releases of Mongrel will find other creative ways to make threads faster, but don't + # hold your breath until Ruby 1.9 is actually finally useful. + class HttpServer + attr_reader :acceptor + attr_reader :workers + attr_reader :classifier + attr_reader :host + attr_reader :port + attr_reader :timeout + attr_reader :num_processors + + # Creates a working server on host:port (strange things happen if port isn't a Number). + # Use HttpServer::run to start the server and HttpServer.acceptor.join to + # join the thread that's processing incoming requests on the socket. + # + # The num_processors optional argument is the maximum number of concurrent + # processors to accept, anything over this is closed immediately to maintain + # server processing performance. This may seem mean but it is the most efficient + # way to deal with overload. Other schemes involve still parsing the client's request + # which defeats the point of an overload handling system. + # + # The timeout parameter is a sleep timeout (in hundredths of a second) that is placed between + # socket.accept calls in order to give the server a cheap throttle time. It defaults to 0 and + # actually if it is 0 then the sleep is not done at all. + def initialize(host, port, num_processors=(2**30-1), timeout=0) + @socket = TCPServer.new(host, port) + @classifier = URIClassifier.new + @host = host + @port = port + @workers = ThreadGroup.new + @timeout = timeout + @num_processors = num_processors + @death_time = 60 + end + + # Does the majority of the IO processing. It has been written in Ruby using + # about 7 different IO processing strategies and no matter how it's done + # the performance just does not improve. It is currently carefully constructed + # to make sure that it gets the best possible performance, but anyone who + # thinks they can make it faster is more than welcome to take a crack at it. + def process_client(client) + begin + parser = HttpParser.new + params = HttpParams.new + request = nil + data = client.readpartial(Const::CHUNK_SIZE) + nparsed = 0 + + # Assumption: nparsed will always be less since data will get filled with more + # after each parsing. If it doesn't get more then there was a problem + # with the read operation on the client socket. Effect is to stop processing when the + # socket can't fill the buffer for further parsing. + while nparsed < data.length + nparsed = parser.execute(params, data, nparsed) + + if parser.finished? + if not params[Const::REQUEST_PATH] + # it might be a dumbass full host request header + uri = URI.parse(params[Const::REQUEST_URI]) + params[Const::REQUEST_PATH] = uri.request_uri + end + + raise "No REQUEST PATH" if not params[Const::REQUEST_PATH] + + script_name, path_info, handlers = @classifier.resolve(params[Const::REQUEST_PATH]) + + if handlers + params[Const::PATH_INFO] = path_info + params[Const::SCRIPT_NAME] = script_name + params[Const::REMOTE_ADDR] = params[Const::HTTP_X_FORWARDED_FOR] || client.peeraddr.last + + # select handlers that want more detailed request notification + notifiers = handlers.select { |h| h.request_notify } + request = HttpRequest.new(params, client, notifiers) + + # in the case of large file uploads the user could close the socket, so skip those requests + break if request.body == nil # nil signals from HttpRequest::initialize that the request was aborted + + # request is good so far, continue processing the response + response = HttpResponse.new(client) + + # Process each handler in registered order until we run out or one finalizes the response. + handlers.each do |handler| + handler.process(request, response) + break if response.done or client.closed? + end + + # And finally, if nobody closed the response off, we finalize it. + unless response.done or client.closed? + response.finished + end + else + # Didn't find it, return a stock 404 response. + client.write(Const::ERROR_404_RESPONSE) + end + + break #done + else + # Parser is not done, queue up more data to read and continue parsing + chunk = client.readpartial(Const::CHUNK_SIZE) + break if !chunk or chunk.length == 0 # read failed, stop processing + + data << chunk + if data.length >= Const::MAX_HEADER + raise HttpParserError.new("HEADER is longer than allowed, aborting client early.") + end + end + end + rescue EOFError,Errno::ECONNRESET,Errno::EPIPE,Errno::EINVAL,Errno::EBADF + client.close rescue Object + rescue HttpParserError + if $mongrel_debug_client + STDERR.puts "#{Time.now}: BAD CLIENT (#{params[Const::HTTP_X_FORWARDED_FOR] || client.peeraddr.last}): #$!" + STDERR.puts "#{Time.now}: REQUEST DATA: #{data.inspect}\n---\nPARAMS: #{params.inspect}\n---\n" + end + rescue Errno::EMFILE + reap_dead_workers('too many files') + rescue Object + STDERR.puts "#{Time.now}: ERROR: #$!" + STDERR.puts $!.backtrace.join("\n") if $mongrel_debug_client + ensure + client.close rescue Object + request.body.delete if request and request.body.class == Tempfile + end + end + + # Used internally to kill off any worker threads that have taken too long + # to complete processing. Only called if there are too many processors + # currently servicing. It returns the count of workers still active + # after the reap is done. It only runs if there are workers to reap. + def reap_dead_workers(reason='unknown') + if @workers.list.length > 0 + STDERR.puts "#{Time.now}: Reaping #{@workers.list.length} threads for slow workers because of '#{reason}'" + error_msg = "Mongrel timed out this thread: #{reason}" + mark = Time.now + @workers.list.each do |w| + w[:started_on] = Time.now if not w[:started_on] + + if mark - w[:started_on] > @death_time + @timeout + STDERR.puts "Thread #{w.inspect} is too old, killing." + w.raise(TimeoutError.new(error_msg)) + end + end + end + + return @workers.list.length + end + + # Performs a wait on all the currently running threads and kills any that take + # too long. Right now it just waits 60 seconds, but will expand this to + # allow setting. The @timeout setting does extend this waiting period by + # that much longer. + def graceful_shutdown + while reap_dead_workers("shutdown") > 0 + STDERR.print "Waiting for #{@workers.list.length} requests to finish, could take #{@death_time + @timeout} seconds." + sleep @death_time / 10 + end + end + + def configure_socket_options + case RUBY_PLATFORM + when /linux/ + # 9 is currently TCP_DEFER_ACCEPT + $tcp_defer_accept_opts = [Socket::SOL_TCP, 9, 1] + $tcp_cork_opts = [Socket::SOL_TCP, 3, 1] + when /freebsd/ + # Use the HTTP accept filter if available. + # The struct made by pack() is defined in /usr/include/sys/socket.h as accept_filter_arg + unless `/sbin/sysctl -nq net.inet.accf.http`.empty? + $tcp_defer_accept_opts = [Socket::SOL_SOCKET, Socket::SO_ACCEPTFILTER, ['httpready', nil].pack('a16a240')] + end + end + end + + # Runs the thing. It returns the thread used so you can "join" it. You can also + # access the HttpServer::acceptor attribute to get the thread later. + def run + BasicSocket.do_not_reverse_lookup=true + + configure_socket_options + + if $tcp_defer_accept_opts + @socket.setsockopt(*$tcp_defer_accept_opts) rescue nil + end + + @acceptor = Thread.new do + while true + begin + client = @socket.accept + + if $tcp_cork_opts + client.setsockopt(*$tcp_cork_opts) rescue nil + end + + worker_list = @workers.list + + if worker_list.length >= @num_processors + STDERR.puts "Server overloaded with #{worker_list.length} processors (#@num_processors max). Dropping connection." + client.close rescue Object + reap_dead_workers("max processors") + else + thread = Thread.new(client) {|c| process_client(c) } + thread[:started_on] = Time.now + @workers.add(thread) + + sleep @timeout/100 if @timeout > 0 + end + rescue StopServer + @socket.close rescue Object + break + rescue Errno::EMFILE + reap_dead_workers("too many open files") + sleep 0.5 + rescue Errno::ECONNABORTED + # client closed the socket even before accept + client.close rescue Object + rescue Object => exc + STDERR.puts "!!!!!! UNHANDLED EXCEPTION! #{exc}. TELL ZED HE'S A MORON." + STDERR.puts $!.backtrace.join("\n") if $mongrel_debug_client + end + end + graceful_shutdown + end + + return @acceptor + end + + # Simply registers a handler with the internal URIClassifier. When the URI is + # found in the prefix of a request then your handler's HttpHandler::process method + # is called. See Mongrel::URIClassifier#register for more information. + # + # If you set in_front=true then the passed in handler will be put in front in the list. + # Otherwise it's placed at the end of the list. + def register(uri, handler, in_front=false) + script_name, path_info, handlers = @classifier.resolve(uri) + + if not handlers + @classifier.register(uri, [handler]) + else + if path_info.length == 0 or (script_name == Const::SLASH and path_info == Const::SLASH) + if in_front + handlers.unshift(handler) + else + handlers << handler + end + else + @classifier.register(uri, [handler]) + end + end + + handler.listener = self + end + + # Removes any handlers registered at the given URI. See Mongrel::URIClassifier#unregister + # for more information. Remember this removes them *all* so the entire + # processing chain goes away. + def unregister(uri) + @classifier.unregister(uri) + end + + # Stops the acceptor thread and then causes the worker threads to finish + # off the request queue before finally exiting. + def stop + stopper = Thread.new do + exc = StopServer.new + @acceptor.raise(exc) + end + stopper.priority = 10 + end + + end +end diff --git a/amarok/src/mediadevice/daap/mongrel/lib/mongrel/Makefile.am b/amarok/src/mediadevice/daap/mongrel/lib/mongrel/Makefile.am new file mode 100644 index 00000000..ea11e171 --- /dev/null +++ b/amarok/src/mediadevice/daap/mongrel/lib/mongrel/Makefile.am @@ -0,0 +1,15 @@ +amarokmongreldir = \ + $(kde_datadir)/amarok/ruby_lib/mongrel + +amarokmongrel_DATA = \ + cgi.rb \ + command.rb \ + configurator.rb \ + debug.rb \ + handlers.rb \ + init.rb \ + mime_types.yml \ + stats.rb\ + tcphack.rb + + diff --git a/amarok/src/mediadevice/daap/mongrel/lib/mongrel/cgi.rb b/amarok/src/mediadevice/daap/mongrel/lib/mongrel/cgi.rb new file mode 100644 index 00000000..fa5c9c4e --- /dev/null +++ b/amarok/src/mediadevice/daap/mongrel/lib/mongrel/cgi.rb @@ -0,0 +1,181 @@ +# Copyright (c) 2005 Zed A. Shaw +# You can redistribute it and/or modify it under the same terms as Ruby. +# +# Additional work donated by contributors. See http://mongrel.rubyforge.org/attributions.html +# for more information. + +require 'cgi' + +module Mongrel + # The beginning of a complete wrapper around Mongrel's internal HTTP processing + # system but maintaining the original Ruby CGI module. Use this only as a crutch + # to get existing CGI based systems working. It should handle everything, but please + # notify me if you see special warnings. This work is still very alpha so I need + # testers to help work out the various corner cases. + # + # The CGIWrapper.handler attribute is normally not set and is available for + # frameworks that need to get back to the handler. Rails uses this to give + # people access to the RailsHandler#files (DirHandler really) so they can + # look-up paths and do other things with the files managed there. + # + # In Rails you can get the real file for a request with: + # + # path = @request.cgi.handler.files.can_serve(@request['PATH_INFO']) + # + # Which is ugly but does the job. Feel free to write a Rails helper for that. + # Refer to DirHandler#can_serve for more information on this. + class CGIWrapper < ::CGI + public :env_table + attr_reader :options + attr_accessor :handler + # Set this to false if you want calls to CGIWrapper.out to not actually send + # the response until you force it. + attr_accessor :default_really_final + + # these are stripped out of any keys passed to CGIWrapper.header function + REMOVED_KEYS = [ "nph","status","server","connection","type", + "charset","length","language","expires"] + + # Takes an HttpRequest and HttpResponse object, plus any additional arguments + # normally passed to CGI. These are used internally to create a wrapper around + # the real CGI while maintaining Mongrel's view of the world. + def initialize(request, response, *args) + @request = request + @response = response + @args = *args + @input = request.body + @head = {} + @out_called = false + @default_really_final=true + super(*args) + end + + # The header is typically called to send back the header. In our case we + # collect it into a hash for later usage. + # + # nph -- Mostly ignored. It'll output the date. + # connection -- Completely ignored. Why is CGI doing this? + # length -- Ignored since Mongrel figures this out from what you write to output. + # + def header(options = "text/html") + # if they pass in a string then just write the Content-Type + if options.class == String + @head['Content-Type'] = options unless @head['Content-Type'] + else + # convert the given options into what Mongrel wants + @head['Content-Type'] = options['type'] || "text/html" + @head['Content-Type'] += "; charset=" + options['charset'] if options.has_key? "charset" if options['charset'] + + # setup date only if they use nph + @head['Date'] = CGI::rfc1123_date(Time.now) if options['nph'] + + # setup the server to use the default or what they set + @head['Server'] = options['server'] || env_table['SERVER_SOFTWARE'] + + # remaining possible options they can give + @head['Status'] = options['status'] if options['status'] + @head['Content-Language'] = options['language'] if options['language'] + @head['Expires'] = options['expires'] if options['expires'] + + # drop the keys we don't want anymore + REMOVED_KEYS.each {|k| options.delete(k) } + + # finally just convert the rest raw (which puts 'cookie' directly) + # 'cookie' is translated later as we write the header out + options.each{|k,v| @head[k] = v} + end + + # doing this fakes out the cgi library to think the headers are empty + # we then do the real headers in the out function call later + "" + end + + # Takes any 'cookie' setting and sends it over the Mongrel header, + # then removes the setting from the options. If cookie is an + # Array or Hash then it sends those on with .to_s, otherwise + # it just calls .to_s on it and hopefully your "cookie" can + # write itself correctly. + def send_cookies(to) + # convert the cookies based on the myriad of possible ways to set a cookie + if @head['cookie'] + cookie = @head['cookie'] + case cookie + when Array + cookie.each {|c| to['Set-Cookie'] = c.to_s } + when Hash + cookie.each_value {|c| to['Set-Cookie'] = c.to_s} + else + to['Set-Cookie'] = options['cookie'].to_s + end + + @head.delete('cookie') + + # @output_cookies seems to never be used, but we'll process it just in case + @output_cookies.each {|c| to['Set-Cookie'] = c.to_s } if @output_cookies + end + end + + # The dumb thing is people can call header or this or both and in any order. + # So, we just reuse header and then finalize the HttpResponse the right way. + # Status is taken from the various options and converted to what Mongrel needs + # via the CGIWrapper.status function. + # + # We also prevent Rails from actually doing the final send by adding a + # second parameter "really_final". Only Mongrel calls this after Rails + # is done. Since this will break other frameworks, it defaults to + # a different setting for rails (false) and (true) for others. + def out(options = "text/html", really_final=@default_really_final) + if @out_called || !really_final + # don't do it more than once or if it's not the really final call + return + end + + header(options) + + @response.start status do |head, body| + send_cookies(head) + + @head.each {|k,v| head[k] = v} + body.write(yield || "") + end + + @out_called = true + end + + # Computes the status once, but lazily so that people who call header twice + # don't get penalized. Because CGI insists on including the options status + # message in the status we have to do a bit of parsing. + def status + if not @status + stat = @head["Status"] + stat = stat.split(' ')[0] if stat + + @status = stat || "200" + end + + @status + end + + # Used to wrap the normal args variable used inside CGI. + def args + @args + end + + # Used to wrap the normal env_table variable used inside CGI. + def env_table + @request.params + end + + # Used to wrap the normal stdinput variable used inside CGI. + def stdinput + @input + end + + # The stdoutput should be completely bypassed but we'll drop a warning just in case + def stdoutput + STDERR.puts "WARNING: Your program is doing something not expected. Please tell Zed that stdoutput was used and what software you are running. Thanks." + @response.body + end + + end +end diff --git a/amarok/src/mediadevice/daap/mongrel/lib/mongrel/command.rb b/amarok/src/mediadevice/daap/mongrel/lib/mongrel/command.rb new file mode 100644 index 00000000..8f654263 --- /dev/null +++ b/amarok/src/mediadevice/daap/mongrel/lib/mongrel/command.rb @@ -0,0 +1,221 @@ +# Copyright (c) 2005 Zed A. Shaw +# You can redistribute it and/or modify it under the same terms as Ruby. +# +# Additional work donated by contributors. See http://mongrel.rubyforge.org/attributions.html +# for more information. + +require 'rubygems' +require 'singleton' +require 'optparse' +require 'gem_plugin' + +module Mongrel + + # Contains all of the various commands that are used with + # Mongrel servers. + + module Command + + BANNER = "Usage: mongrel_rails <command> [options]" + + # A Command pattern implementation used to create the set of command available to the user + # from Mongrel. The script uses objects which implement this interface to do the + # user's bidding. + module Base + + attr_reader :valid, :done_validating, :original_args + + # Called by the implemented command to set the options for that command. + # Every option has a short and long version, a description, a variable to + # set, and a default value. No exceptions. + def options(opts) + # process the given options array + opts.each do |short, long, help, variable, default| + self.instance_variable_set(variable, default) + @opt.on(short, long, help) do |arg| + self.instance_variable_set(variable, arg) + end + end + end + + # Called by the subclass to setup the command and parse the argv arguments. + # The call is destructive on argv since it uses the OptionParser#parse! function. + def initialize(options={}) + argv = options[:argv] || [] + @opt = OptionParser.new + @opt.banner = Mongrel::Command::BANNER + @valid = true + # this is retarded, but it has to be done this way because -h and -v exit + @done_validating = false + @original_args = argv.dup + + configure + + # I need to add my own -h definition to prevent the -h by default from exiting. + @opt.on_tail("-h", "--help", "Show this message") do + @done_validating = true + puts @opt + end + + # I need to add my own -v definition to prevent the -v from exiting by default as well. + @opt.on_tail("--version", "Show version") do + @done_validating = true + if VERSION + puts "Version #{Mongrel::Const::MONGREL_VERSION}" + end + end + + @opt.parse! argv + end + + def configure + options [] + end + + # Returns true/false depending on whether the command is configured properly. + def validate + return @valid + end + + # Returns a help message. Defaults to OptionParser#help which should be good. + def help + @opt.help + end + + # Runs the command doing it's job. You should implement this otherwise it will + # throw a NotImplementedError as a reminder. + def run + raise NotImplementedError + end + + + # Validates the given expression is true and prints the message if not, exiting. + def valid?(exp, message) + if not @done_validating and (not exp) + failure message + @valid = false + @done_validating = true + end + end + + # Validates that a file exists and if not displays the message + def valid_exists?(file, message) + valid?(file != nil && File.exist?(file), message) + end + + + # Validates that the file is a file and not a directory or something else. + def valid_file?(file, message) + valid?(file != nil && File.file?(file), message) + end + + # Validates that the given directory exists + def valid_dir?(file, message) + valid?(file != nil && File.directory?(file), message) + end + + def valid_user?(user) + valid?(@group, "You must also specify a group.") + begin + Etc.getpwnam(user) + rescue + failure "User does not exist: #{user}" + @valid = false + end + end + + def valid_group?(group) + valid?(@user, "You must also specify a user.") + begin + Etc.getgrnam(group) + rescue + failure "Group does not exist: #{group}" + @valid = false + end + end + + # Just a simple method to display failure until something better is developed. + def failure(message) + STDERR.puts "!!! #{message}" + end + end + + # A Singleton class that manages all of the available commands + # and handles running them. + class Registry + include Singleton + + # Builds a list of possible commands from the Command derivates list + def commands + pmgr = GemPlugin::Manager.instance + list = pmgr.plugins["/commands"].keys + return list.sort + end + + # Prints a list of available commands. + def print_command_list + puts "#{Mongrel::Command::BANNER}\nAvailable commands are:\n\n" + + self.commands.each do |name| + if /mongrel::/ =~ name + name = name[9 .. -1] + end + + puts " - #{name[1 .. -1]}\n" + end + + puts "\nEach command takes -h as an option to get help." + + end + + + # Runs the args against the first argument as the command name. + # If it has any errors it returns a false, otherwise it return true. + def run(args) + # find the command + cmd_name = args.shift + + if !cmd_name or cmd_name == "?" or cmd_name == "help" + print_command_list + return true + elsif cmd_name == "--version" + STDERR.puts "Mongrel Web Server #{Mongrel::Const::MONGREL_VERSION}" + return true + end + + begin + # quick hack so that existing commands will keep working but the Mongrel:: ones can be moved + if ["start", "stop", "restart"].include? cmd_name + cmd_name = "mongrel::" + cmd_name + end + + command = GemPlugin::Manager.instance.create("/commands/#{cmd_name}", :argv => args) + rescue OptionParser::InvalidOption + STDERR.puts "#$! for command '#{cmd_name}'" + STDERR.puts "Try #{cmd_name} -h to get help." + return false + rescue + STDERR.puts "ERROR RUNNING '#{cmd_name}': #$!" + STDERR.puts "Use help command to get help" + return false + end + + # Normally the command is NOT valid right after being created + # but sometimes (like with -h or -v) there's no further processing + # needed so the command is already valid so we can skip it. + if not command.done_validating + if not command.validate + STDERR.puts "#{cmd_name} reported an error. Use mongrel_rails #{cmd_name} -h to get help." + return false + else + command.run + end + end + + return true + end + + end + end +end + diff --git a/amarok/src/mediadevice/daap/mongrel/lib/mongrel/configurator.rb b/amarok/src/mediadevice/daap/mongrel/lib/mongrel/configurator.rb new file mode 100644 index 00000000..223ad846 --- /dev/null +++ b/amarok/src/mediadevice/daap/mongrel/lib/mongrel/configurator.rb @@ -0,0 +1,378 @@ +require 'yaml' +require 'etc' + + +module Mongrel + # Implements a simple DSL for configuring a Mongrel server for your + # purposes. More used by framework implementers to setup Mongrel + # how they like, but could be used by regular folks to add more things + # to an existing mongrel configuration. + # + # It is used like this: + # + # require 'mongrel' + # config = Mongrel::Configurator.new :host => "127.0.0.1" do + # listener :port => 3000 do + # uri "/app", :handler => Mongrel::DirHandler.new(".", load_mime_map("mime.yaml")) + # end + # run + # end + # + # This will setup a simple DirHandler at the current directory and load additional + # mime types from mimy.yaml. The :host => "127.0.0.1" is actually not + # specific to the servers but just a hash of default parameters that all + # server or uri calls receive. + # + # When you are inside the block after Mongrel::Configurator.new you can simply + # call functions that are part of Configurator (like server, uri, daemonize, etc) + # without having to refer to anything else. You can also call these functions on + # the resulting object directly for additional configuration. + # + # A major thing about Configurator is that it actually lets you configure + # multiple listeners for any hosts and ports you want. These are kept in a + # map config.listeners so you can get to them. + # + # * :pid_file => Where to write the process ID. + class Configurator + attr_reader :listeners + attr_reader :defaults + attr_reader :needs_restart + + # You pass in initial defaults and then a block to continue configuring. + def initialize(defaults={}, &blk) + @listener = nil + @listener_name = nil + @listeners = {} + @defaults = defaults + @needs_restart = false + @pid_file = defaults[:pid_file] + + if blk + cloaker(&blk).bind(self).call + end + end + + # Change privilege of the process to specified user and group. + def change_privilege(user, group) + begin + if group + log "Changing group to #{group}." + Process::GID.change_privilege(Etc.getgrnam(group).gid) + end + + if user + log "Changing user to #{user}." + Process::UID.change_privilege(Etc.getpwnam(user).uid) + end + rescue Errno::EPERM + log "FAILED to change user:group #{user}:#{group}: #$!" + exit 1 + end + end + + def remove_pid_file + File.unlink(@pid_file) if @pid_file and File.exists?(@pid_file) + end + + # Writes the PID file but only if we're on windows. + def write_pid_file + if RUBY_PLATFORM !~ /mswin/ + log "Writing PID file to #{@pid_file}" + open(@pid_file,"w") {|f| f.write(Process.pid) } + end + end + + # generates a class for cloaking the current self and making the DSL nicer + def cloaking_class + class << self + self + end + end + + # Do not call this. You were warned. + def cloaker(&blk) + cloaking_class.class_eval do + define_method :cloaker_, &blk + meth = instance_method( :cloaker_ ) + remove_method :cloaker_ + meth + end + end + + # This will resolve the given options against the defaults. + # Normally just used internally. + def resolve_defaults(options) + options.merge(@defaults) + end + + # Starts a listener block. This is the only one that actually takes + # a block and then you make Configurator.uri calls in order to setup + # your URIs and handlers. If you write your Handlers as GemPlugins + # then you can use load_plugins and plugin to load them. + # + # It expects the following options (or defaults): + # + # * :host => Host name to bind. + # * :port => Port to bind. + # * :num_processors => The maximum number of concurrent threads allowed. (950 default) + # * :timeout => 1/100th of a second timeout between requests. (10 is 1/10th, 0 is timeout) + # * :user => User to change to, must have :group as well. + # * :group => Group to change to, must have :user as well. + # + def listener(options={},&blk) + raise "Cannot call listener inside another listener block." if (@listener or @listener_name) + ops = resolve_defaults(options) + ops[:num_processors] ||= 950 + ops[:timeout] ||= 0 + + @listener = Mongrel::HttpServer.new(ops[:host], ops[:port].to_i, ops[:num_processors].to_i, ops[:timeout].to_i) + @listener_name = "#{ops[:host]}:#{ops[:port]}" + @listeners[@listener_name] = @listener + + if ops[:user] and ops[:group] + change_privilege(ops[:user], ops[:group]) + end + + # Does the actual cloaking operation to give the new implicit self. + if blk + cloaker(&blk).bind(self).call + end + + # all done processing this listener setup, reset implicit variables + @listener = nil + @listener_name = nil + end + + + # Called inside a Configurator.listener block in order to + # add URI->handler mappings for that listener. Use this as + # many times as you like. It expects the following options + # or defaults: + # + # * :handler => HttpHandler -- Handler to use for this location. + # * :in_front => true/false -- Rather than appending, it prepends this handler. + def uri(location, options={}) + ops = resolve_defaults(options) + @listener.register(location, ops[:handler], ops[:in_front]) + end + + + # Daemonizes the current Ruby script turning all the + # listeners into an actual "server" or detached process. + # You must call this *before* frameworks that open files + # as otherwise the files will be closed by this function. + # + # Does not work for Win32 systems (the call is silently ignored). + # + # Requires the following options or defaults: + # + # * :cwd => Directory to change to. + # * :log_file => Where to write STDOUT and STDERR. + # + # It is safe to call this on win32 as it will only require the daemons + # gem/library if NOT win32. + def daemonize(options={}) + ops = resolve_defaults(options) + # save this for later since daemonize will hose it + if RUBY_PLATFORM !~ /mswin/ + require 'daemons/daemonize' + + logfile = ops[:log_file] + if logfile[0].chr != "/" + logfile = File.join(ops[:cwd],logfile) + if not File.exist?(File.dirname(logfile)) + log "!!! Log file directory not found at full path #{File.dirname(logfile)}. Update your configuration to use a full path." + exit 1 + end + end + + Daemonize.daemonize(logfile) + + # change back to the original starting directory + Dir.chdir(ops[:cwd]) + + else + log "WARNING: Win32 does not support daemon mode." + end + end + + + # Uses the GemPlugin system to easily load plugins based on their + # gem dependencies. You pass in either an :includes => [] or + # :excludes => [] setting listing the names of plugins to include + # or exclude from the when determining the dependencies. + def load_plugins(options={}) + ops = resolve_defaults(options) + + load_settings = {} + if ops[:includes] + ops[:includes].each do |plugin| + load_settings[plugin] = GemPlugin::INCLUDE + end + end + + if ops[:excludes] + ops[:excludes].each do |plugin| + load_settings[plugin] = GemPlugin::EXCLUDE + end + end + + GemPlugin::Manager.instance.load(load_settings) + end + + + # Easy way to load a YAML file and apply default settings. + def load_yaml(file, default={}) + default.merge(YAML.load_file(file)) + end + + + # Loads the MIME map file and checks that it is correct + # on loading. This is commonly passed to Mongrel::DirHandler + # or any framework handler that uses DirHandler to serve files. + # You can also include a set of default MIME types as additional + # settings. See Mongrel::DirHandler for how the MIME types map + # is organized. + def load_mime_map(file, mime={}) + # configure any requested mime map + mime = load_yaml(file, mime) + + # check all the mime types to make sure they are the right format + mime.each {|k,v| log "WARNING: MIME type #{k} must start with '.'" if k.index(".") != 0 } + + return mime + end + + + # Loads and creates a plugin for you based on the given + # name and configured with the selected options. The options + # are merged with the defaults prior to passing them in. + def plugin(name, options={}) + ops = resolve_defaults(options) + GemPlugin::Manager.instance.create(name, ops) + end + + # Lets you do redirects easily as described in Mongrel::RedirectHandler. + # You use it inside the configurator like this: + # + # redirect("/test", "/to/there") # simple + # redirect("/to", /t/, 'w') # regexp + # redirect("/hey", /(w+)/) {|match| ...} # block + # + def redirect(from, pattern, replacement = nil, &block) + uri from, :handler => Mongrel::RedirectHandler.new(pattern, replacement, &block) + end + + # Works like a meta run method which goes through all the + # configured listeners. Use the Configurator.join method + # to prevent Ruby from exiting until each one is done. + def run + @listeners.each {|name,s| + s.run + } + + $mongrel_sleeper_thread = Thread.new { loop { sleep 1 } } + end + + # Calls .stop on all the configured listeners so they + # stop processing requests (gracefully). By default it + # assumes that you don't want to restart. + def stop(needs_restart=false) + @listeners.each {|name,s| + s.stop + } + + @needs_restart = needs_restart + end + + + # This method should actually be called *outside* of the + # Configurator block so that you can control it. In other words + # do it like: config.join. + def join + @listeners.values.each {|s| s.acceptor.join } + end + + + # Calling this before you register your URIs to the given location + # will setup a set of handlers that log open files, objects, and the + # parameters for each request. This helps you track common problems + # found in Rails applications that are either slow or become unresponsive + # after a little while. + # + # You can pass an extra parameter *what* to indicate what you want to + # debug. For example, if you just want to dump rails stuff then do: + # + # debug "/", what = [:rails] + # + # And it will only produce the log/mongrel_debug/rails.log file. + # Available options are: :access, :files, :objects, :threads, :rails + # + # NOTE: Use [:files] to get accesses dumped to stderr like with WEBrick. + def debug(location, what = [:access, :files, :objects, :threads, :rails]) + require 'mongrel/debug' + handlers = { + :access => "/handlers/requestlog::access", + :files => "/handlers/requestlog::files", + :objects => "/handlers/requestlog::objects", + :threads => "/handlers/requestlog::threads", + :rails => "/handlers/requestlog::params" + } + + # turn on the debugging infrastructure, and ObjectTracker is a pig + MongrelDbg.configure + + # now we roll through each requested debug type, turn it on and load that plugin + what.each do |type| + MongrelDbg.begin_trace type + uri location, :handler => plugin(handlers[type]) + end + end + + # Used to allow you to let users specify their own configurations + # inside your Configurator setup. You pass it a script name and + # it reads it in and does an eval on the contents passing in the right + # binding so they can put their own Configurator statements. + def run_config(script) + open(script) {|f| eval(f.read, proc {self}) } + end + + # Sets up the standard signal handlers that are used on most Ruby + # It only configures if the platform is not win32 and doesn't do + # a HUP signal since this is typically framework specific. + # + # Requires a :pid_file option given to Configurator.new to indicate a file to delete. + # It sets the MongrelConfig.needs_restart attribute if + # the start command should reload. It's up to you to detect this + # and do whatever is needed for a "restart". + # + # This command is safely ignored if the platform is win32 (with a warning) + def setup_signals(options={}) + ops = resolve_defaults(options) + + # forced shutdown, even if previously restarted (actually just like TERM but for CTRL-C) + trap("INT") { log "INT signal received."; stop(false) } + + # clean up the pid file always + at_exit { remove_pid_file } + + if RUBY_PLATFORM !~ /mswin/ + # graceful shutdown + trap("TERM") { log "TERM signal received."; stop } + trap("USR1") { log "USR1 received, toggling $mongrel_debug_client to #{!$mongrel_debug_client}"; $mongrel_debug_client = !$mongrel_debug_client } + # restart + trap("USR2") { log "USR2 signal received."; stop(true) } + + log "Signals ready. TERM => stop. USR2 => restart. INT => stop (no restart)." + else + log "Signals ready. INT => stop (no restart)." + end + end + + # Logs a simple message to STDERR (or the mongrel log if in daemon mode). + def log(msg) + STDERR.print "** ", msg, "\n" + end + + end +end diff --git a/amarok/src/mediadevice/daap/mongrel/lib/mongrel/debug.rb b/amarok/src/mediadevice/daap/mongrel/lib/mongrel/debug.rb new file mode 100644 index 00000000..572696e8 --- /dev/null +++ b/amarok/src/mediadevice/daap/mongrel/lib/mongrel/debug.rb @@ -0,0 +1,201 @@ +# Copyright (c) 2005 Zed A. Shaw +# You can redistribute it and/or modify it under the same terms as Ruby. +# +# Additional work donated by contributors. See http://mongrel.rubyforge.org/attributions.html +# for more information. + +require 'logger' +require 'set' +require 'socket' + + + +$mongrel_debugging=true + +module MongrelDbg + SETTINGS = { :tracing => {}} + LOGGING = { } + + def MongrelDbg::configure(log_dir = File.join("log","mongrel_debug")) + Dir.mkdir(log_dir) if not File.exist?(log_dir) + @log_dir = log_dir + $objects_out=open(File.join("log","mongrel_debug","objects.log"),"w") + $objects_out.puts "run,classname,last,count,delta,lenmean,lensd,lenmax" + $objects_out.sync = true + $last_stat = nil + $run_count = 0 + end + + + def MongrelDbg::trace(target, message) + if SETTINGS[:tracing][target] and LOGGING[target] + LOGGING[target].log(Logger::DEBUG, message) + end + end + + def MongrelDbg::begin_trace(target) + SETTINGS[:tracing][target] = true + if not LOGGING[target] + LOGGING[target] = Logger.new(File.join(@log_dir, "#{target.to_s}.log")) + end + MongrelDbg::trace(target, "TRACING ON #{Time.now}") + end + + def MongrelDbg::end_trace(target) + SETTINGS[:tracing][target] = false + MongrelDbg::trace(target, "TRACING OFF #{Time.now}") + LOGGING[target].close + LOGGING[target] = nil + end + + def MongrelDbg::tracing?(target) + SETTINGS[:tracing][target] + end +end + + + +$open_files = {} + +class IO + alias_method :orig_open, :open + alias_method :orig_close, :close + + def open(*arg, &blk) + $open_files[self] = args.inspect + orig_open(*arg,&blk) + end + + def close(*arg,&blk) + $open_files.delete self + orig_close(*arg,&blk) + end +end + + +module Kernel + alias_method :orig_open, :open + + def open(*arg, &blk) + $open_files[self] = arg[0] + orig_open(*arg,&blk) + end + + def log_open_files + open_counts = {} + $open_files.each do |f,args| + open_counts[args] ||= 0 + open_counts[args] += 1 + end + MongrelDbg::trace(:files, open_counts.to_yaml) + end +end + + + +module RequestLog + + # Just logs whatever requests it gets to STDERR (which ends up in the mongrel + # log when daemonized). + class Access < GemPlugin::Plugin "/handlers" + include Mongrel::HttpHandlerPlugin + + def process(request,response) + p = request.params + STDERR.puts "#{p['REMOTE_ADDR']} - [#{Time.now.httpdate}] \"#{p['REQUEST_METHOD']} #{p["REQUEST_URI"]} HTTP/1.1\"" + end + end + + + class Files < GemPlugin::Plugin "/handlers" + include Mongrel::HttpHandlerPlugin + + def process(request, response) + MongrelDbg::trace(:files, "#{Time.now} FILES OPEN BEFORE REQUEST #{request.params['PATH_INFO']}") + log_open_files + end + + end + + # stolen from Robert Klemme + class Objects < GemPlugin::Plugin "/handlers" + include Mongrel::HttpHandlerPlugin + + def process(request,response) + begin + stats = Hash.new(0) + lengths = {} + ObjectSpace.each_object do |o| + begin + if o.respond_to? :length + len = o.length + lengths[o.class] ||= Mongrel::Stats.new(o.class) + lengths[o.class].sample(len) + end + rescue Object + end + + stats[o.class] += 1 + end + + stats.sort {|(k1,v1),(k2,v2)| v2 <=> v1}.each do |k,v| + if $last_stat + delta = v - $last_stat[k] + if v > 10 and delta != 0 + if lengths[k] + $objects_out.printf "%d,%s,%d,%d,%d,%f,%f,%f\n", $run_count, k, $last_stat[k], v, delta,lengths[k].mean,lengths[k].sd,lengths[k].max + else + $objects_out.printf "%d,%s,%d,%d,%d,,,\n", $run_count, k, $last_stat[k], v, delta + end + end + end + end + + $run_count += 1 + $last_stat = stats + rescue Object + STDERR.puts "object.log ERROR: #$!" + end + end + end + + class Params < GemPlugin::Plugin "/handlers" + include Mongrel::HttpHandlerPlugin + + def process(request, response) + MongrelDbg::trace(:rails, "#{Time.now} REQUEST #{request.params['PATH_INFO']}") + MongrelDbg::trace(:rails, request.params.to_yaml) + end + + end + + class Threads < GemPlugin::Plugin "/handlers" + include Mongrel::HttpHandlerPlugin + + def process(request, response) + MongrelDbg::trace(:threads, "#{Time.now} REQUEST #{request.params['PATH_INFO']}") + ObjectSpace.each_object do |obj| + begin + if obj.class == Mongrel::HttpServer + worker_list = obj.workers.list + + if worker_list.length > 0 + keys = "-----\n\tKEYS:" + worker_list.each {|t| keys << "\n\t\t-- #{t}: #{t.keys.inspect}" } + end + + MongrelDbg::trace(:threads, "#{obj.host}:#{obj.port} -- THREADS: #{worker_list.length} #{keys}") + end + rescue Object + # ignore since obj.class can sometimes take parameters + end + end + end + end +end + + +END { + MongrelDbg::trace(:files, "FILES OPEN AT EXIT") + log_open_files +} diff --git a/amarok/src/mediadevice/daap/mongrel/lib/mongrel/handlers.rb b/amarok/src/mediadevice/daap/mongrel/lib/mongrel/handlers.rb new file mode 100644 index 00000000..be7860a9 --- /dev/null +++ b/amarok/src/mediadevice/daap/mongrel/lib/mongrel/handlers.rb @@ -0,0 +1,453 @@ +# Copyright (c) 2005 Zed A. Shaw +# You can redistribute it and/or modify it under the same terms as Ruby. +# +# Additional work donated by contributors. See http://mongrel.rubyforge.org/attributions.html +# for more information. + +require 'mongrel/stats' +require 'zlib' +require 'yaml' + + +module Mongrel + + # You implement your application handler with this. It's very light giving + # just the minimum necessary for you to handle a request and shoot back + # a response. Look at the HttpRequest and HttpResponse objects for how + # to use them. + # + # This is used for very simple handlers that don't require much to operate. + # More extensive plugins or those you intend to distribute as GemPlugins + # should be implemented using the HttpHandlerPlugin mixin. + # + class HttpHandler + attr_reader :request_notify + attr_accessor :listener + + # This will be called by Mongrel if HttpHandler.request_notify set to *true*. + # You only get the parameters for the request, with the idea that you'd "bound" + # the beginning of the request processing and the first call to process. + def request_begins(params) + end + + # Called by Mongrel for each IO chunk that is received on the request socket + # from the client, allowing you to track the progress of the IO and monitor + # the input. This will be called by Mongrel only if HttpHandler.request_notify + # set to *true*. + def request_progress(params, clen, total) + end + + def process(request, response) + end + + end + + + # This is used when your handler is implemented as a GemPlugin. + # The plugin always takes an options hash which you can modify + # and then access later. They are stored by default for + # the process method later. + module HttpHandlerPlugin + attr_reader :options + attr_reader :request_notify + attr_accessor :listener + + def request_begins(params) + end + + def request_progress(params, clen, total) + end + + def initialize(options={}) + @options = options + @header_only = false + end + + def process(request, response) + end + + end + + + # The server normally returns a 404 response if an unknown URI is requested, but it + # also returns a lame empty message. This lets you do a 404 response + # with a custom message for special URIs. + class Error404Handler < HttpHandler + + # Sets the message to return. This is constructed once for the handler + # so it's pretty efficient. + def initialize(msg) + @response = Const::ERROR_404_RESPONSE + msg + end + + # Just kicks back the standard 404 response with your special message. + def process(request, response) + response.socket.write(@response) + end + + end + + + # Serves the contents of a directory. You give it the path to the root + # where the files are located, and it tries to find the files based on + # the PATH_INFO inside the directory. If the requested path is a + # directory then it returns a simple directory listing. + # + # It does a simple protection against going outside it's root path by + # converting all paths to an absolute expanded path, and then making sure + # that the final expanded path includes the root path. If it doesn't + # than it simply gives a 404. + # + # The default content type is "text/plain; charset=ISO-8859-1" but you + # can change it anything you want using the DirHandler.default_content_type + # attribute. + class DirHandler < HttpHandler + attr_accessor :default_content_type + attr_reader :path + + MIME_TYPES_FILE = "mime_types.yml" + MIME_TYPES = YAML.load_file(File.join(File.dirname(__FILE__), MIME_TYPES_FILE)) + + ONLY_HEAD_GET="Only HEAD and GET allowed.".freeze + + # You give it the path to the directory root and an (optional) + def initialize(path, listing_allowed=true, index_html="index.html") + @path = File.expand_path(path) + @listing_allowed=listing_allowed + @index_html = index_html + @default_content_type = "application/octet-stream".freeze + end + + # Checks if the given path can be served and returns the full path (or nil if not). + def can_serve(path_info) + req_path = File.expand_path(File.join(@path,HttpRequest.unescape(path_info)), @path) + + if req_path.index(@path) == 0 and File.exist? req_path + # it exists and it's in the right location + if File.directory? req_path + # the request is for a directory + index = File.join(req_path, @index_html) + if File.exist? index + # serve the index + return index + elsif @listing_allowed + # serve the directory + return req_path + else + # do not serve anything + return nil + end + else + # it's a file and it's there + return req_path + end + else + # does not exist or isn't in the right spot + return nil + end + end + + + # Returns a simplistic directory listing if they're enabled, otherwise a 403. + # Base is the base URI from the REQUEST_URI, dir is the directory to serve + # on the file system (comes from can_serve()), and response is the HttpResponse + # object to send the results on. + def send_dir_listing(base, dir, response) + # take off any trailing / so the links come out right + base = HttpRequest.unescape(base) + base.chop! if base[-1] == "/"[-1] + + if @listing_allowed + response.start(200) do |head,out| + head[Const::CONTENT_TYPE] = "text/html" + out << "<html><head><title>Directory Listing" + Dir.entries(dir).each do |child| + next if child == "." + out << "" + out << (child == ".." ? "Up to parent.." : child) + out << "
    " + end + out << "" + end + else + response.start(403) do |head,out| + out.write("Directory listings not allowed") + end + end + end + + + # Sends the contents of a file back to the user. Not terribly efficient since it's + # opening and closing the file for each read. + def send_file(req_path, request, response, header_only=false) + + stat = File.stat(req_path) + + # Set the last modified times as well and etag for all files + mtime = stat.mtime + # Calculated the same as apache, not sure how well the works on win32 + etag = Const::ETAG_FORMAT % [mtime.to_i, stat.size, stat.ino] + + modified_since = request.params[Const::HTTP_IF_MODIFIED_SINCE] + none_match = request.params[Const::HTTP_IF_NONE_MATCH] + + # test to see if this is a conditional request, and test if + # the response would be identical to the last response + same_response = case + when modified_since && !last_response_time = Time.httpdate(modified_since) rescue nil : false + when modified_since && last_response_time > Time.now : false + when modified_since && mtime > last_response_time : false + when none_match && none_match == '*' : false + when none_match && !none_match.strip.split(/\s*,\s*/).include?(etag) : false + else modified_since || none_match # validation successful if we get this far and at least one of the header exists + end + + header = response.header + header[Const::ETAG] = etag + + if same_response + response.start(304) {} + else + # first we setup the headers and status then we do a very fast send on the socket directly + response.status = 200 + header[Const::LAST_MODIFIED] = mtime.httpdate + + # set the mime type from our map based on the ending + dot_at = req_path.rindex('.') + if dot_at + header[Const::CONTENT_TYPE] = MIME_TYPES[req_path[dot_at .. -1]] || @default_content_type + else + header[Const::CONTENT_TYPE] = @default_content_type + end + + # send a status with out content length + response.send_status(stat.size) + response.send_header + + if not header_only + response.send_file(req_path, stat.size < Const::CHUNK_SIZE * 2) + end + end + end + + # Process the request to either serve a file or a directory listing + # if allowed (based on the listing_allowed parameter to the constructor). + def process(request, response) + req_method = request.params[Const::REQUEST_METHOD] || Const::GET + req_path = can_serve request.params[Const::PATH_INFO] + if not req_path + # not found, return a 404 + response.start(404) do |head,out| + out << "File not found" + end + else + begin + if File.directory? req_path + send_dir_listing(request.params[Const::REQUEST_URI], req_path, response) + elsif req_method == Const::HEAD + send_file(req_path, request, response, true) + elsif req_method == Const::GET + send_file(req_path, request, response, false) + else + response.start(403) {|head,out| out.write(ONLY_HEAD_GET) } + end + rescue => details + STDERR.puts "Error sending file #{req_path}: #{details}" + end + end + end + + # There is a small number of default mime types for extensions, but + # this lets you add any others you'll need when serving content. + def DirHandler::add_mime_type(extension, type) + MIME_TYPES[extension] = type + end + + end + + + # When added to a config script (-S in mongrel_rails) it will + # look at the client's allowed response types and then gzip + # compress anything that is going out. + # + # Valid option is :always_deflate => false which tells the handler to + # deflate everything even if the client can't handle it. + class DeflateFilter < HttpHandler + include Zlib + HTTP_ACCEPT_ENCODING = "HTTP_ACCEPT_ENCODING" + + def initialize(ops={}) + @options = ops + @always_deflate = ops[:always_deflate] || false + end + + def process(request, response) + accepts = request.params[HTTP_ACCEPT_ENCODING] + # only process if they support compression + if @always_deflate or (accepts and (accepts.include? "deflate" and not response.body_sent)) + response.header["Content-Encoding"] = "deflate" + response.body = deflate(response.body) + end + end + + private + def deflate(stream) + deflater = Deflate.new( + DEFAULT_COMPRESSION, + # drop the zlib header which causes both Safari and IE to choke + -MAX_WBITS, + DEF_MEM_LEVEL, + DEFAULT_STRATEGY) + + stream.rewind + gzout = StringIO.new(deflater.deflate(stream.read, FINISH)) + stream.close + gzout.rewind + gzout + end + end + + + # Implements a few basic statistics for a particular URI. Register it anywhere + # you want in the request chain and it'll quickly gather some numbers for you + # to analyze. It is pretty fast, but don't put it out in production. + # + # You should pass the filter to StatusHandler as StatusHandler.new(:stats_filter => stats). + # This lets you then hit the status URI you want and get these stats from a browser. + # + # StatisticsFilter takes an option of :sample_rate. This is a number that's passed to + # rand and if that number gets hit then a sample is taken. This helps reduce the load + # and keeps the statistics valid (since sampling is a part of how they work). + # + # The exception to :sample_rate is that inter-request time is sampled on every request. + # If this wasn't done then it wouldn't be accurate as a measure of time between requests. + class StatisticsFilter < HttpHandler + attr_reader :stats + + def initialize(ops={}) + @sample_rate = ops[:sample_rate] || 300 + + @processors = Mongrel::Stats.new("processors") + @reqsize = Mongrel::Stats.new("request Kb") + @headcount = Mongrel::Stats.new("req param count") + @respsize = Mongrel::Stats.new("response Kb") + @interreq = Mongrel::Stats.new("inter-request time") + end + + + def process(request, response) + if rand(@sample_rate)+1 == @sample_rate + @processors.sample(listener.workers.list.length) + @headcount.sample(request.params.length) + @reqsize.sample(request.body.length / 1024.0) + @respsize.sample((response.body.length + response.header.out.length) / 1024.0) + end + @interreq.tick + end + + def dump + "#{@processors.to_s}\n#{@reqsize.to_s}\n#{@headcount.to_s}\n#{@respsize.to_s}\n#{@interreq.to_s}" + end + end + + + # The :stats_filter is basically any configured stats filter that you've added to this same + # URI. This lets the status handler print out statistics on how Mongrel is doing. + class StatusHandler < HttpHandler + def initialize(ops={}) + @stats = ops[:stats_filter] + end + + def table(title, rows) + results = "
    \n" + "\n" + "\n" + "\n" + "\n" + "
    \n" + "\n" + "\n" + "\n" + "
    \n" + ) + .args( QStringList() + << escapeHTML( currentTrack.title() ) + << escapeHTML( currentTrack.artist() ) + << escapeHTML( currentTrack.album() ) + << ( isCompilation ? "" : escapeHTMLAttr( currentTrack.artist() ) ) + << escapeHTMLAttr( currentTrack.album() ) + << escapeHTMLAttr( albumImage ) + << albumImageTitleAttr + << i18n( "Look up this track at musicbrainz.org" ) + << escapeHTMLAttr( currentTrack.artist() ) + << escapeHTMLAttr( currentTrack.album() ) + << escapeHTMLAttr( currentTrack.title() ) + << escapeHTML( m_musicBrainIconPath ) ) + : QString ( //no title + "%1 " + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "
    \n" + "\n" + "\n" + "\n" + "\n" + ) + .arg( escapeHTML( currentTrack.prettyTitle() ) ) + .arg( escapeHTMLAttr( currentTrack.artist() ) ) + .arg( escapeHTMLAttr( currentTrack.album() ) ) + .arg( escapeHTMLAttr( albumImage ) ) + .arg( albumImageTitleAttr ) + ) ); + + if ( !values.isEmpty() && values[2].toInt() ) + { + QDateTime firstPlay = QDateTime(); + firstPlay.setTime_t( values[0].toUInt() ); + QDateTime lastPlay = QDateTime(); + lastPlay.setTime_t( values[1].toUInt() ); + + const uint playtimes = values[2].toInt(); + const uint score = static_cast( values[3].toFloat() ); + const uint rating = values[4].toInt(); + + //SAFE = .arg( x, y ) + //UNSAFE = .arg( x ).arg( y ) + m_HTMLSource.append( QString( + "%1
    \n" + "
    %2
    \n" + "%3
    \n" + "%4\n" + ) + .arg( i18n( "Track played once", "Track played %n times", playtimes ), + statsHTML( score, rating, false ), + i18n( "Last played: %1" ).arg( Amarok::verboseTimeSince( lastPlay ) ), + i18n( "First played: %1" ).arg( Amarok::verboseTimeSince( firstPlay ) ) ) ); + } + else + m_HTMLSource.append( i18n( "Never played before" ) ); + + m_HTMLSource.append( + "
    \n" + "\n" ); + // + + if ( currentTrack.url().isLocalFile() && !CollectionDB::instance()->isFileInCollection( currentTrack.url().path() ) ) + { + m_HTMLSource.append( + "
    \n" + "
    \n" + "\n" + + i18n( "This file is not in your Collection!" ) + + "\n" + "
    \n" + "
    \n" + "

    \n" + + i18n( "If you would like to see contextual information about this track," + " you should add it to your Collection." ) + + "

    \n" + "
    \n" + "

    \n" + "
    \n" + "
    \n" + ); + } + + /* cue file code */ + if ( b->m_cuefile && (b->m_cuefile->count() > 0) ) { + m_HTMLSource.append( + "
    \n" + "
    \n" + "\n" + + i18n( "Cue File" ) + + "\n" + "
    \n" + "\n" ); + CueFile::Iterator it; + uint i = 0; + for ( it = b->m_cuefile->begin(); it != b->m_cuefile->end(); ++it ) { + m_HTMLSource.append( + "\n" + "\n" + "" + ); + } + m_HTMLSource.append( + "
    \n" + "\n" + "\n" + QString::number(it.data().getTrackNumber()) + " \n" + "\n" + escapeHTML( it.data().getTitle() ) + "\n" + "\n" + + i18n(" – ") + + "\n" + "\n" + escapeHTML( it.data().getArtist() ) + "\n" + " (" + MetaBundle::prettyTime( it.data().getLength()/1000, false ) + ")\n" + "\n" + "
    \n" + "
    \n" + ); + } +} + + +void CurrentTrackJob::showRelatedArtists( const QString &artist, const QStringList &relArtists ) +{ + // + m_HTMLSource.append( QString( + "\n" ); + + if ( !b->m_relatedOpen ) + m_HTMLSource.append( "\n" ); + // +} + +void CurrentTrackJob::showSuggestedSongs( const QStringList &relArtists ) +{ + QString token; + + QueryBuilder qb; + QStringList values; + qb.clear(); + qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valURL ); + qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valTitle ); + qb.addReturnValue( QueryBuilder::tabArtist, QueryBuilder::valName ); + qb.addReturnValue( QueryBuilder::tabStats, QueryBuilder::valScore ); + qb.addReturnValue( QueryBuilder::tabStats, QueryBuilder::valRating ); + qb.addMatches( QueryBuilder::tabArtist, relArtists ); + qb.sortByFavorite(); + qb.setLimit( 0, 10 ); + values = qb.run(); + + // + if ( !values.isEmpty() ) + { + m_HTMLSource.append( + "
    \n" + "
    \n" + "\n" + + i18n( "Suggested Songs" ) + + "\n" + "
    \n" + "\n" ); + + for ( uint i = 0; i < values.count(); i += 5 ) + m_HTMLSource.append( + "\n" + "\n" + "\n" + "\n" + "\n" ); + + m_HTMLSource.append( + "
    \n" + "\n" + "\n"+ escapeHTML( values[i + 2] ) + "\n" + "\n" + + i18n(" – ") + + "\n" + escapeHTML( values[i + 1] ) + "\n" + "\n" + "\n" + statsHTML( static_cast( values[i + 3].toFloat() ), values[i + 4].toInt() ) + "
    \n" + "
    \n" ); + + if ( !b->m_suggestionsOpen ) + m_HTMLSource.append( "\n" ); + } + //
    +} + +void +CurrentTrackJob::showSongsWithLabel( const QString &label ) +{ + QueryBuilder qb; + QStringList values; + qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valURL ); + qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valTitle ); + qb.addReturnValue( QueryBuilder::tabArtist, QueryBuilder::valName ); + qb.addReturnValue( QueryBuilder::tabStats, QueryBuilder::valScore ); + qb.addReturnValue( QueryBuilder::tabStats, QueryBuilder::valRating ); + qb.addMatch( QueryBuilder::tabLabels, QueryBuilder::valType, QString::number( CollectionDB::typeUser ) ); + qb.addMatch( QueryBuilder::tabLabels, QueryBuilder::valName, label ); + qb.sortByFavorite(); + qb.setOptions( QueryBuilder::optRandomize ); + qb.setLimit( 0, 30 ); + values = qb.run(); + + if ( !values.isEmpty() ) + { + m_HTMLSource.append( + "
    \n" + "
    \n" + "\n" + + i18n( "Songs with label %1" ).arg( label ) + + "\n" + "
    \n" + "\n" ); + + for ( uint i = 0; i < values.count(); i += 5 ) + m_HTMLSource.append( + "\n" + "\n" + "\n" + "\n" + "\n" ); + + m_HTMLSource.append( + "
    \n" + "\n" + "\n"+ escapeHTML( values[i + 2] ) + "\n" + "\n" + + i18n(" – ") + + "\n" + escapeHTML( values[i + 1] ) + "\n" + "\n" + "\n" + statsHTML( static_cast( values[i + 3].toFloat() ), values[i + 4].toInt() ) + "
    \n" + "
    \n" ); + } +} + +void +CurrentTrackJob::showUserLabels( const MetaBundle ¤tTrack ) +{ + QueryBuilder qb; + qb.addReturnValue( QueryBuilder::tabLabels, QueryBuilder::valName, true ); + qb.addMatch( QueryBuilder::tabSong, QueryBuilder::valURL, currentTrack.url().path() ); + qb.addMatch( QueryBuilder::tabLabels, QueryBuilder::valType, QString::number( CollectionDB::typeUser ) ); + qb.setLimit( 0, 10 ); + qb.sortBy( QueryBuilder::tabLabels, QueryBuilder::valName, false ); + qb.buildQuery(); + QStringList values = qb.run(); + + QString title; + if ( currentTrack.title().isEmpty() ) + title = currentTrack.veryNiceTitle(); + else + title = currentTrack.title(); + + m_HTMLSource.append( + "
    \n" + "
    \n" + "\n" + + i18n( " Labels for %1 " ).arg( escapeHTML( title ) ) + + "\n" + "
    \n" + "\n" ); + m_HTMLSource.append( "\n" ); + m_HTMLSource.append( "\n" ); + m_HTMLSource.append( + "
    \n" ); + if ( !values.isEmpty() ) + { + foreach( values ) + { + if( it != values.begin() ) + m_HTMLSource.append( ", \n" ); + m_HTMLSource.append( "" + escapeHTML( *it ) + "" ); + } + } + m_HTMLSource.append( "
    " + i18n( "Add labels to %1" ).arg( escapeHTML( title ) ) + "
    \n" + "
    \n" ); + + if ( !b->m_labelsOpen ) + m_HTMLSource.append( "\n" ); +} + +void CurrentTrackJob::showArtistsFaves( const QString &artist, uint artist_id ) +{ + QString artistName = artist.isEmpty() ? escapeHTML( i18n( "This Artist" ) ) : escapeHTML( artist ); + QueryBuilder qb; + QStringList values; + + qb.clear(); + qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valTitle ); + qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valURL ); + qb.addReturnValue( QueryBuilder::tabStats, QueryBuilder::valScore ); + qb.addReturnValue( QueryBuilder::tabStats, QueryBuilder::valRating ); + qb.addNumericFilter( QueryBuilder::tabStats, QueryBuilder::valPlayCounter, "0", QueryBuilder::modeGreater ); + qb.addMatch( QueryBuilder::tabSong, QueryBuilder::valArtistID, QString::number( artist_id ) ); + qb.sortByFavorite(); + qb.setLimit( 0, 10 ); + values = qb.run(); + usleep( 10000 ); + + if ( !values.isEmpty() ) + { + m_HTMLSource.append( + "
    \n" + "
    \n" + "\n" + + i18n( "Favorite Tracks by %1" ).arg( artistName ) + + "\n" + "
    \n" + "\n" ); + + for ( uint i = 0; i < values.count(); i += 4 ) + m_HTMLSource.append( + "\n" + "\n" + "\n" + "\n" + "\n" + ); + + m_HTMLSource.append( + "
    \n" + "\n" + "\n" + escapeHTML( values[i] ) + "\n" + "\n" + "\n" + statsHTML( static_cast( values[i + 2].toFloat() ), values[i + 3].toInt() ) + "
    \n" + "
    \n" ); + + if ( !b->m_favoritesOpen ) + m_HTMLSource.append( "\n" ); + + } +} + +void CurrentTrackJob::showArtistsAlbums( const QString &artist, uint artist_id, uint album_id ) +{ + DEBUG_BLOCK + QString artistName = artist.isEmpty() ? escapeHTML( i18n( "This Artist" ) ) : escapeHTML( artist ); + QueryBuilder qb; + QStringList values; + // + qb.clear(); + qb.addReturnValue( QueryBuilder::tabAlbum, QueryBuilder::valName ); + qb.addReturnValue( QueryBuilder::tabAlbum, QueryBuilder::valID ); + qb.addReturnFunctionValue( QueryBuilder::funcMax, QueryBuilder::tabYear, QueryBuilder::valName ); + qb.addMatch( QueryBuilder::tabSong, QueryBuilder::valArtistID, QString::number( artist_id ) ); + qb.groupBy( QueryBuilder::tabAlbum, QueryBuilder::valName ); + qb.groupBy( QueryBuilder::tabAlbum, QueryBuilder::valID ); + qb.sortByFunction( QueryBuilder::funcMax, QueryBuilder::tabYear, QueryBuilder::valName, true ); + qb.sortBy( QueryBuilder::tabAlbum, QueryBuilder::valName, true ); + qb.setOptions( QueryBuilder::optNoCompilations ); + values = qb.run(); + + if ( !values.isEmpty() ) + { + // write the script to toggle blocks visibility + m_HTMLSource.append( + "
    \n" + "
    \n" + "\n" + + i18n( "Albums by %1" ).arg( artistName ) + + "\n" + "
    \n" + "\n" ); + + uint vectorPlace = 0; + // find album of the current track (if it exists) + while ( vectorPlace < values.count() && values[ vectorPlace+1 ] != QString::number( album_id ) ) + vectorPlace += 3; + for ( uint i = 0; i < values.count(); i += 3 ) + { + qb.clear(); + qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valTitle ); + qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valURL ); + qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valTrack ); + qb.addReturnValue( QueryBuilder::tabYear, QueryBuilder::valName ); + qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valLength ); + qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valDiscNumber ); + qb.addMatch( QueryBuilder::tabSong, QueryBuilder::valAlbumID, values[ i + 1 ] ); + qb.addMatch( QueryBuilder::tabSong, QueryBuilder::valArtistID, QString::number( artist_id ) ); + qb.sortBy( QueryBuilder::tabSong, QueryBuilder::valDiscNumber ); + qb.sortBy( QueryBuilder::tabSong, QueryBuilder::valTrack ); + qb.sortBy( QueryBuilder::tabSong, QueryBuilder::valTitle ); + qb.setOptions( QueryBuilder::optNoCompilations ); + QStringList albumValues = qb.run(); + usleep( 10000 ); + + QString albumYear; + if ( !albumValues.isEmpty() ) + { + albumYear = albumValues[ 3 ]; + for ( uint j = 0; j < albumValues.count(); j += qb.countReturnValues() ) + if ( albumValues[j + 3] != albumYear || albumYear == "0" ) + { + albumYear = QString::null; + break; + } + } + + uint i_albumLength = 0; + for ( uint j = 0; j < albumValues.count(); j += qb.countReturnValues() ) + i_albumLength += QString(albumValues[j + 4]).toInt(); + + QString albumLength = ( i_albumLength==0 ? i18n( "Unknown" ) : MetaBundle::prettyTime( i_albumLength, true ) ); + QString albumImage = ContextBrowser::getEncodedImage( CollectionDB::instance()->albumImage( artist, values[ i ], true, 50 ) ); + QString albumImageTitleAttr = albumImageTooltip( albumImage, 50 ); + + m_HTMLSource.append( QStringx ( + "\n" + "\n" + "\n" ); + } + m_HTMLSource.append( + "
    \n" + "
    \n" + "\n" + "\n" + "\n" + "\n" + "\n" + "
    \n" + "\n" + "\n" + "\n" + "\n" + "%6 " + "%9\n" + "
    \n" + "%10\n" + "%11\n" + "
    \n" + "
    \n" + "
    \n" ) + .args( QStringList() + << values[ i + 1 ] + << escapeHTMLAttr( artist ) // artist name + << escapeHTMLAttr( values[ i ].isEmpty() ? i18n( "Unknown" ) : values[ i ] ) // album.name + << albumImageTitleAttr + << escapeHTMLAttr( albumImage ) + << i18n( "Single", "%n Tracks", albumValues.count() / qb.countReturnValues() ) + << QString::number( artist_id ) + << values[ i + 1 ] //album.id + << escapeHTML( values[ i ].isEmpty() ? i18n( "Unknown" ) : values[ i ] ) + << albumYear + << albumLength + << ( i!=vectorPlace ? "none" : "block" ) /* shows it if it's the current track album */ + << values[ i + 1 ] ) ); + + QString discNumber; + if ( !albumValues.isEmpty() ) + for ( uint j = 0; j < albumValues.count(); j += qb.countReturnValues() ) + { + QString newDiscNumber = albumValues[ j + 5 ].stripWhiteSpace(); + if( discNumber != newDiscNumber && newDiscNumber.toInt() > 0) + { + discNumber = newDiscNumber; + m_HTMLSource.append( QStringx ( + "
    \n" + "\n" + "%4" + "\n" + "
    \n" ) + .args( QStringList() + << QString::number( artist_id ) + << values[ i + 1 ] //album.id + << escapeHTMLAttr( discNumber ) + << i18n( "Disc %1" ).arg( discNumber ) ) ); + } + QString track = albumValues[j + 2].stripWhiteSpace(); + if( track.length() > 0 ) { + if( track.length() == 1 ) + track.prepend( "0" ); + + track = "\n" + track + " \n"; + } + + QString length; + if( albumValues[j + 4] != "0" ) + length = "(" + MetaBundle::prettyTime( QString(albumValues[j + 4]).toInt(), true ) + ")\n"; + + bool current = false; + if( i==vectorPlace && albumValues[j + 2].toInt() == m_currentTrack.track() && discNumber.toInt() == m_currentTrack.discNumber() ) + current = true; + m_HTMLSource.append( + "\n" ); + } + + m_HTMLSource.append( + "
    \n" + "
    \n" + "
    \n" ); + } + //
    +} + +void CurrentTrackJob::showArtistsCompilations( const QString &artist, uint artist_id, uint album_id ) +{ + QString artistName = artist.isEmpty() ? escapeHTML( i18n( "This Artist" ) ) : escapeHTML( artist ); + QueryBuilder qb; + QStringList values; + // + qb.clear(); + qb.addReturnValue( QueryBuilder::tabAlbum, QueryBuilder::valName ); + qb.addReturnValue( QueryBuilder::tabAlbum, QueryBuilder::valID ); + qb.addMatch( QueryBuilder::tabSong, QueryBuilder::valArtistID, QString::number( artist_id ) ); + qb.sortBy( QueryBuilder::tabYear, QueryBuilder::valName, true ); + qb.sortBy( QueryBuilder::tabAlbum, QueryBuilder::valName, true ); + qb.setOptions( QueryBuilder::optRemoveDuplicates ); + qb.setOptions( QueryBuilder::optOnlyCompilations ); + values = qb.run(); + + if ( !values.isEmpty() ) + { + // write the script to toggle blocks visibility + m_HTMLSource.append( + "
    \n" + "
    \n" + "\n" + + i18n( "Compilations with %1" ).arg( artistName ) + + "\n" + "
    \n" + "\n" ); + + uint vectorPlace = 0; + // find album of the current track (if it exists) + while ( vectorPlace < values.count() && values[ vectorPlace+1 ] != QString::number( album_id ) ) + vectorPlace += 2; + for ( uint i = 0; i < values.count(); i += 2 ) + { + qb.clear(); + qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valTitle ); + qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valURL ); + qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valTrack ); + qb.addReturnValue( QueryBuilder::tabYear, QueryBuilder::valName ); + qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valLength ); + qb.addReturnValue( QueryBuilder::tabArtist, QueryBuilder::valName ); + qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valDiscNumber ); + qb.addMatch( QueryBuilder::tabSong, QueryBuilder::valAlbumID, values[ i + 1 ] ); + qb.sortBy( QueryBuilder::tabSong, QueryBuilder::valDiscNumber ); + qb.sortBy( QueryBuilder::tabSong, QueryBuilder::valTrack ); + qb.setOptions( QueryBuilder::optOnlyCompilations ); + QStringList albumValues = qb.run(); + usleep( 10000 ); + + QString albumYear; + if ( !albumValues.isEmpty() ) + { + albumYear = albumValues[ 3 ]; + for ( uint j = 0; j < albumValues.count(); j += qb.countReturnValues() ) + if ( albumValues[j + 3] != albumYear || albumYear == "0" ) + { + albumYear = QString::null; + break; + } + } + + uint i_albumLength = 0; + for ( uint j = 0; j < albumValues.count(); j += qb.countReturnValues() ) + i_albumLength += QString(albumValues[j + 4]).toInt(); + + QString albumLength = ( i_albumLength==0 ? i18n( "Unknown" ) : MetaBundle::prettyTime( i_albumLength, true ) ); + QString albumImage = ContextBrowser::getEncodedImage( CollectionDB::instance()->albumImage( artist, values[ i ], true, 50 ) ); + QString albumImageTitleAttr = albumImageTooltip( albumImage, 50 ); + + m_HTMLSource.append( QStringx ( + "\n" + "\n" + "\n" ); + } + m_HTMLSource.append( + "
    \n" + "
    \n" + "\n" + "\n" + "\n" + "\n" + "\n" + "
    \n" + "\n" + "\n" + "\n" + "\n" + "%5 " + "%7\n" + "
    \n" + "%8\n" + "%9\n" + "
    \n" + "
    \n" + "
    \n" ) + .args( QStringList() + << values[ i + 1 ] + << escapeHTMLAttr( values[ i ].isEmpty() ? i18n( "Unknown" ) : values[ i ] ) // album.name + << albumImageTitleAttr + << escapeHTMLAttr( albumImage ) + << i18n( "Single", "%n Tracks", albumValues.count() / qb.countReturnValues() ) + << values[ i + 1 ] //album.id + << escapeHTML( values[ i ].isEmpty() ? i18n( "Unknown" ) : values[ i ] ) + << albumYear + << albumLength + << ( i!=vectorPlace ? "none" : "block" ) /* shows it if it's the current track album */ + << values[ i + 1 ] ) ); + + QString discNumber; + if ( !albumValues.isEmpty() ) + for ( uint j = 0; j < albumValues.count(); j += qb.countReturnValues() ) + { + QString newDiscNumber = albumValues[ j + 6 ].stripWhiteSpace(); + if( discNumber != newDiscNumber && newDiscNumber.toInt() > 0) + { + discNumber = newDiscNumber; + m_HTMLSource.append( QStringx ( + "
    \n" + "\n" + "%3" + "\n" + "
    \n" ) + .args( QStringList() + << values[ i + 1 ] //album.id + << escapeHTMLAttr( discNumber ) + << i18n( "Disc %1" ).arg( discNumber ) ) ); + } + + QString track = albumValues[j + 2].stripWhiteSpace(); + if( track.length() > 0 ) { + if( track.length() == 1 ) + track.prepend( "0" ); + + track = "\n" + track + " \n"; + } + + QString length; + if( albumValues[j + 4] != "0" ) + length = "(" + MetaBundle::prettyTime( QString(albumValues[j + 4]).toInt(), true ) + ")\n"; + + QString tracktitle_formated; + QString tracktitle; + tracktitle = escapeHTML( i18n("%1 - %2").arg( albumValues[j + 5], albumValues[j] ) ); + tracktitle_formated = "\n"; + if( i==vectorPlace && albumValues[j + 2].toInt() == m_currentTrack.track() && discNumber.toInt() == m_currentTrack.discNumber() ) + tracktitle_formated += "\n"; + if ( artist == albumValues[j + 5] ) + tracktitle_formated += "\n"; + tracktitle_formated += tracktitle; + if ( artist == albumValues[j + 5] ) + tracktitle_formated += "\n"; + if( i==vectorPlace && track.toInt() == m_currentTrack.track() && discNumber.toInt() == m_currentTrack.discNumber() ) + tracktitle_formated += "\n"; + tracktitle_formated += " "; + m_HTMLSource.append( + "\n" ); + } + + m_HTMLSource.append( + "
    \n" + "
    \n" + "
    \n" ); + } + //
    +} + +QString CurrentTrackJob::statsHTML( int score, int rating, bool statsbox ) //static +{ + if( !AmarokConfig::useScores() && !AmarokConfig::useRatings() ) + return ""; + + if ( rating < 0 ) + rating = 0; + if ( rating > 10 ) + rating = 10; + + QString table = QString( "%2
    \n" ) + .arg( statsbox ? "class='statsBox'" : "" ); + QString contents; + + if( AmarokConfig::useScores() ) + contents += QString( "
    \n" + QString::number( score ) + "\n" + "
    \n" + "
    \n" + "
    \n" + "
    \n"; + if( rating ) + { + bool half = rating%2; + contents += "\n"; + + QImageIO fullStarIO; + fullStarIO.setImage( StarManager::instance()->getStarImage( half ? rating/2 + 1 : rating/2 ) ); + fullStarIO.setFormat( "PNG" ); + QBuffer fullStarBuf; + fullStarBuf.open( IO_WriteOnly ); + fullStarIO.setIODevice( &fullStarBuf ); + fullStarIO.write(); + fullStarBuf.close(); + QCString fullStar = KCodecs::base64Encode( fullStarBuf.buffer(), true ); + + const QString img = "\n"; + for( int i = 0, n = rating / 2; i < n; ++i ) + contents += img.arg( "data:image/png;base64," + fullStar ); + if( rating % 2 ) + { + QImageIO halfStarIO; + halfStarIO.setImage( StarManager::instance()->getHalfStarImage( half ? rating/2 + 1 : rating/2 ) ); + halfStarIO.setFormat( "PNG" ); + QBuffer halfStarBuf; + halfStarBuf.open( IO_WriteOnly ); + halfStarIO.setIODevice( &halfStarBuf ); + halfStarIO.write(); + halfStarBuf.close(); + QCString halfStar = KCodecs::base64Encode( halfStarBuf.buffer(), true ); + contents += img.arg( "data:image/png;base64," + halfStar ); + } + contents += "\n"; + } + else + contents += i18n( "Not rated" ); + contents += "
    " + rows.each do |cols| + results << "" + cols.each {|col| results << "" } + results << "" + end + results + "
    #{title}
    #{col}
    " + end + + def describe_listener + results = "" + results << "

    Listener #{listener.host}:#{listener.port}

    " + results << table("settings", [ + ["host",listener.host], + ["port",listener.port], + ["timeout",listener.timeout], + ["workers max",listener.num_processors], + ]) + + if @stats + results << "

    Statistics

    N means the number of samples, pay attention to MEAN, SD, MIN and MAX." + results << "

    #{@stats.dump}
    " + end + + results << "

    Registered Handlers

    " + uris = listener.classifier.handler_map + results << table("handlers", uris.map {|uri,handlers| + [uri, + "
    " + 
    +            handlers.map {|h| h.class.to_s }.join("\n") + 
    +            "
    " + ] + }) + + results + end + + def process(request, response) + response.start do |head,out| + out.write <<-END + Mongrel Server Status + #{describe_listener} + + END + end + end + end + + # This handler allows you to redirect one url to another. + # You can use it like String#gsub, where the string is the REQUEST_URI. + # REQUEST_URI is the full path with GET parameters. + # + # Eg. /test/something?help=true&disclaimer=false + # + # == Examples + # + # h = Mongrel::HttpServer.new('0.0.0.0') + # h.register '/test', Mongrel::RedirectHandler.new('/to/there') # simple + # h.register '/to', Mongrel::RedirectHandler.new(/t/, 'w') # regexp + # # and with a block + # h.register '/hey', Mongrel::RedirectHandler.new(/(\w+)/) { |match| ... } + # + class RedirectHandler < Mongrel::HttpHandler + # You set the rewrite rules when building the object. + # + # pattern => What to look for or replacement if used alone + # + # replacement, block => One of them is used to replace the found text + + def initialize(pattern, replacement = nil, &block) + unless replacement or block + @replacement = pattern + else + @pattern, @replacement, @block = pattern, replacement, block + end + end + + # Process the request and return a redirect response + def process(request, response) + unless @pattern + response.socket.write(Mongrel::Const::REDIRECT % @replacement) + else + if @block + new_path = request.params['REQUEST_URI'].gsub(@pattern, &@block) + else + new_path = request.params['REQUEST_URI'].gsub(@pattern, @replacement) + end + response.socket.write(Mongrel::Const::REDIRECT % new_path) + end + end + end +end diff --git a/amarok/src/mediadevice/daap/mongrel/lib/mongrel/init.rb b/amarok/src/mediadevice/daap/mongrel/lib/mongrel/init.rb new file mode 100644 index 00000000..7199d0c3 --- /dev/null +++ b/amarok/src/mediadevice/daap/mongrel/lib/mongrel/init.rb @@ -0,0 +1,13 @@ +# Copyright (c) 2005 Zed A. Shaw +# You can redistribute it and/or modify it under the same terms as Ruby. +# +# Additional work donated by contributors. See http://mongrel.rubyforge.org/attributions.html +# for more information. + +require 'rubygems' +require 'gem_plugin' + +# file is just a stub that makes sure the mongrel_plugins gem is loaded and ready + + + diff --git a/amarok/src/mediadevice/daap/mongrel/lib/mongrel/mime_types.yml b/amarok/src/mediadevice/daap/mongrel/lib/mongrel/mime_types.yml new file mode 100644 index 00000000..a17207be --- /dev/null +++ b/amarok/src/mediadevice/daap/mongrel/lib/mongrel/mime_types.yml @@ -0,0 +1,615 @@ +--- +.a: application/octet-stream +.abc: text/vnd.abc +.acgi: text/html +.afl: video/animaflex +.ai: application/postscript +.aif: audio/aiff +.aifc: audio/aiff +.aiff: audio/aiff +.aip: text/x-audiosoft-intra +.ani: application/x-navi-animation +.aps: application/mime +.arc: application/octet-stream +.arj: application/octet-stream +.art: image/x-jg +.asf: video/x-ms-asf +.asm: text/x-asm +.asp: text/asp +.asr: video/x-ms-asf +.asx: video/x-ms-asf +.atom: application/xml+atom +.au: audio/basic +.au: audio/x-au +.avi: video/avi +.avs: video/avs-video +.axs: application/olescript +.bas: text/plain +.bcpio: application/x-bcpio +.bin: application/octet-stream +.bm: image/bmp +.bmp: image/bmp +.boo: application/book +.book: application/book +.boz: application/x-bzip2 +.bsh: application/x-bsh +.bz2: application/x-bzip2 +.bz: application/x-bzip +.c: text/plain +.cat: application/octet-stream +.cc: text/plain +.ccad: application/clariscad +.cco: application/x-cocoa +.cdf: application/cdf +.cer: application/x-x509-ca-cert +.cha: application/x-chat +.chat: application/x-chat +.class: application/java +.class: application/octet-stream +.clp: application/x-msclip +.cmx: image/x-cmx +.cod: image/cis-cod +.com: application/octet-stream +.com: text/plain +.conf: text/plain +.cpio: application/x-cpio +.cpp: text/x-c +.cpt: application/x-cpt +.crd: application/x-mscardfile +.crl: application/pkcs-crl +.crl: application/pkix-crl +.crt: application/x-x509-ca-cert +.csh: application/x-csh +.csh: text/x-script.csh +.css: text/css +.cxx: text/plain +.dcr: application/x-director +.deb: application/octet-stream +.deepv: application/x-deepv +.def: text/plain +.der: application/x-x509-ca-cert +.dhh: application/david-heinemeier-hansson +.dif: video/x-dv +.dir: application/x-director +.dl: video/dl +.dll: application/octet-stream +.dmg: application/octet-stream +.dms: application/octet-stream +.doc: application/msword +.dp: application/commonground +.drw: application/drafting +.dump: application/octet-stream +.dv: video/x-dv +.dvi: application/x-dvi +.dwg: application/acad +.dwg: image/x-dwg +.dxf: application/dxf +.dxf: image/x-dwg +.dxr: application/x-director +.ear: application/java-archive +.el: text/x-script.elisp +.elc: application/x-bytecode.elisp (compiled elisp) +.elc: application/x-elc +.env: application/x-envoy +.eot: application/octet-stream +.eps: application/postscript +.es: application/x-esrehber +.etx: text/x-setext +.evy: application/envoy +.evy: application/x-envoy +.exe: application/octet-stream +.f77: text/x-fortran +.f90: text/plain +.f90: text/x-fortran +.f: text/x-fortran +.fdf: application/vnd.fdf +.fif: application/fractals +.fif: image/fif +.fli: video/fli +.fli: video/x-fli +.flo: image/florian +.flr: x-world/x-vrml +.flv: video/x-flv +.flx: text/vnd.fmi.flexstor +.fmf: video/x-atomic3d-feature +.for: text/plain +.for: text/x-fortran +.fpx: image/vnd.fpx +.fpx: image/vnd.net-fpx +.frl: application/freeloader +.funk: audio/make +.g3: image/g3fax +.g: text/plain +.gif: image/gif +.gl: video/gl +.gl: video/x-gl +.gsd: audio/x-gsm +.gsm: audio/x-gsm +.gsp: application/x-gsp +.gss: application/x-gss +.gtar: application/x-gtar +.gz: application/x-compressed +.gzip: application/x-gzip +.h: text/plain +.hdf: application/x-hdf +.help: application/x-helpfile +.hgl: application/vnd.hp-hpgl +.hh: text/plain +.hlb: text/x-script +.hlp: application/hlp +.hpg: application/vnd.hp-hpgl +.hpgl: application/vnd.hp-hpgl +.hqx: application/binhex +.hta: application/hta +.htc: text/x-component +.htm: text/html +.html: text/html +.htmls: text/html +.htt: text/webviewhtml +.htx: text/html +.ico: image/x-icon +.idc: text/plain +.ief: image/ief +.iefs: image/ief +.iges: application/iges +.igs: application/iges +.iii: application/x-iphone +.ima: application/x-ima +.imap: application/x-httpd-imap +.img: application/octet-stream +.inf: application/inf +.ins: application/x-internet-signup +.ins: application/x-internett-signup +.ip: application/x-ip2 +.iso: application/octet-stream +.isp: application/x-internet-signup +.isu: video/x-isvideo +.it: audio/it +.iv: application/x-inventor +.ivr: i-world/i-vrml +.ivy: application/x-livescreen +.jam: audio/x-jam +.jar: application/java-archive +.jardiff: application/x-java-archive-diff +.jav: text/plain +.jav: text/x-java-source +.java: text/plain +.java: text/x-java-source +.jcm: application/x-java-commerce +.jfif-tbnl: image/jpeg +.jfif: image/jpeg +.jfif: image/pipeg +.jfif: image/pjpeg +.jng: image/x-jng +.jnlp: application/x-java-jnlp-file +.jpe: image/jpeg +.jpeg: image/jpeg +.jpg: image/jpeg +.jps: image/x-jps +.js: application/x-javascript +.js: text/javascript +.jut: image/jutvision +.kar: audio/midi +.kar: music/x-karaoke +.ksh: application/x-ksh +.ksh: text/x-script.ksh +.la: audio/nspaudio +.la: audio/x-nspaudio +.lam: audio/x-liveaudio +.latex: application/x-latex +.lha: application/lha +.lha: application/octet-stream +.lha: application/x-lha +.lhx: application/octet-stream +.list: text/plain +.lma: audio/nspaudio +.lma: audio/x-nspaudio +.log: text/plain +.lsf: video/x-la-asf +.lsp: application/x-lisp +.lsp: text/x-script.lisp +.lst: text/plain +.lsx: text/x-la-asf +.lsx: video/x-la-asf +.ltx: application/x-latex +.lzh: application/octet-stream +.lzh: application/x-lzh +.lzx: application/lzx +.lzx: application/octet-stream +.lzx: application/x-lzx +.m13: application/x-msmediaview +.m14: application/x-msmediaview +.m1v: video/mpeg +.m2a: audio/mpeg +.m2v: video/mpeg +.m3u: audio/x-mpegurl +.m: text/x-m +.man: application/x-troff-man +.map: application/x-navimap +.mar: text/plain +.mbd: application/mbedlet +.mc: application/x-magic-cap-package-1.0 +.mcd: application/mcad +.mcd: application/x-mathcad +.mcf: image/vasa +.mcf: text/mcf +.mcp: application/netmc +.mdb: application/x-msaccess +.me: application/x-troff-me +.mht: message/rfc822 +.mhtml: message/rfc822 +.mid: audio/mid +.mid: audio/midi +.mid: audio/x-mid +.mid: audio/x-midi +.midi: audio/midi +.midi: audio/x-mid +.midi: audio/x-midi +.mif: application/x-frame +.mif: application/x-mif +.mime: message/rfc822 +.mime: www/mime +.mjf: audio/x-vnd.audioexplosion.mjuicemediafile +.mjpg: video/x-motion-jpeg +.mm: application/base64 +.mm: application/x-meme +.mme: application/base64 +.mml: text/mathml +.mng: video/x-mng +.mod: audio/mod +.moov: video/quicktime +.mov: video/quicktime +.movie: video/x-sgi-movie +.mp2: audio/mpeg +.mp3: audio/mpeg +.mpa: audio/mpeg +.mpc: application/x-project +.mpe: video/mpeg +.mpeg: video/mpeg +.mpg: video/mpeg +.mpga: audio/mpeg +.mpp: application/vnd.ms-project +.mpt: application/x-project +.mpv2: video/mpeg +.mpv: application/x-project +.mpx: application/x-project +.mrc: application/marc +.ms: application/x-troff-ms +.msi: application/octet-stream +.msm: application/octet-stream +.msp: application/octet-stream +.mv: video/x-sgi-movie +.mvb: application/x-msmediaview +.my: audio/make +.mzz: application/x-vnd.audioexplosion.mzz +.nap: image/naplps +.naplps: image/naplps +.nc: application/x-netcdf +.ncm: application/vnd.nokia.configuration-message +.nif: image/x-niff +.niff: image/x-niff +.nix: application/x-mix-transfer +.nsc: application/x-conference +.nvd: application/x-navidoc +.nws: message/rfc822 +.o: application/octet-stream +.oda: application/oda +.omc: application/x-omc +.omcd: application/x-omcdatamaker +.omcr: application/x-omcregerator +.p10: application/pkcs10 +.p10: application/x-pkcs10 +.p12: application/pkcs-12 +.p12: application/x-pkcs12 +.p7a: application/x-pkcs7-signature +.p7b: application/x-pkcs7-certificates +.p7c: application/pkcs7-mime +.p7c: application/x-pkcs7-mime +.p7m: application/pkcs7-mime +.p7m: application/x-pkcs7-mime +.p7r: application/x-pkcs7-certreqresp +.p7s: application/pkcs7-signature +.p7s: application/x-pkcs7-signature +.p: text/x-pascal +.part: application/pro_eng +.pas: text/pascal +.pbm: image/x-portable-bitmap +.pcl: application/vnd.hp-pcl +.pcl: application/x-pcl +.pct: image/x-pict +.pcx: image/x-pcx +.pdb: application/x-pilot +.pdf: application/pdf +.pem: application/x-x509-ca-cert +.pfunk: audio/make +.pfunk: audio/make.my.funk +.pfx: application/x-pkcs12 +.pgm: image/x-portable-graymap +.pgm: image/x-portable-greymap +.pic: image/pict +.pict: image/pict +.pkg: application/x-newton-compatible-pkg +.pko: application/vnd.ms-pki.pko +.pko: application/ynd.ms-pkipko +.pl: application/x-perl +.pl: text/plain +.pl: text/x-script.perl +.plx: application/x-pixclscript +.pm4: application/x-pagemaker +.pm5: application/x-pagemaker +.pm: application/x-perl +.pm: image/x-xpixmap +.pm: text/x-script.perl-module +.pma: application/x-perfmon +.pmc: application/x-perfmon +.pml: application/x-perfmon +.pmr: application/x-perfmon +.pmw: application/x-perfmon +.png: image/png +.pnm: application/x-portable-anymap +.pnm: image/x-portable-anymap +.pot,: application/vnd.ms-powerpoint +.pot: application/mspowerpoint +.pot: application/vnd.ms-powerpoint +.pov: model/x-pov +.ppa: application/vnd.ms-powerpoint +.ppm: image/x-portable-pixmap +.pps: application/mspowerpoint +.ppt: application/mspowerpoint +.ppz: application/mspowerpoint +.prc: application/x-pilot +.pre: application/x-freelance +.prf: application/pics-rules +.prt: application/pro_eng +.ps: application/postscript +.psd: application/octet-stream +.pub: application/x-mspublisher +.pvu: paleovu/x-pv +.pwz: application/vnd.ms-powerpoint +.py: text/x-script.phyton +.pyc: applicaiton/x-bytecode.python +.qcp: audio/vnd.qcelp +.qd3: x-world/x-3dmf +.qd3d: x-world/x-3dmf +.qif: image/x-quicktime +.qt: video/quicktime +.qtc: video/x-qtc +.qti: image/x-quicktime +.qtif: image/x-quicktime +.ra: audio/x-pn-realaudio +.ra: audio/x-pn-realaudio-plugin +.ra: audio/x-realaudio +.ram: audio/x-pn-realaudio +.rar: application/x-rar-compressed +.ras: application/x-cmu-raster +.ras: image/cmu-raster +.ras: image/x-cmu-raster +.rast: image/cmu-raster +.rexx: text/x-script.rexx +.rf: image/vnd.rn-realflash +.rgb: image/x-rgb +.rm: application/vnd.rn-realmedia +.rm: audio/x-pn-realaudio +.rmi: audio/mid +.rmm: audio/x-pn-realaudio +.rmp: audio/x-pn-realaudio +.rmp: audio/x-pn-realaudio-plugin +.rng: application/ringing-tones +.rng: application/vnd.nokia.ringing-tone +.rnx: application/vnd.rn-realplayer +.roff: application/x-troff +.rp: image/vnd.rn-realpix +.rpm: application/x-redhat-package-manager +.rpm: audio/x-pn-realaudio-plugin +.rss: text/xml +.rt: text/richtext +.rt: text/vnd.rn-realtext +.rtf: application/rtf +.rtf: application/x-rtf +.rtf: text/richtext +.rtx: application/rtf +.rtx: text/richtext +.run: application/x-makeself +.rv: video/vnd.rn-realvideo +.s3m: audio/s3m +.s: text/x-asm +.saveme: application/octet-stream +.sbk: application/x-tbook +.scd: application/x-msschedule +.scm: application/x-lotusscreencam +.scm: text/x-script.guile +.scm: text/x-script.scheme +.scm: video/x-scm +.sct: text/scriptlet +.sdml: text/plain +.sdp: application/sdp +.sdp: application/x-sdp +.sdr: application/sounder +.sea: application/sea +.sea: application/x-sea +.set: application/set +.setpay: application/set-payment-initiation +.setreg: application/set-registration-initiation +.sgm: text/sgml +.sgm: text/x-sgml +.sgml: text/sgml +.sgml: text/x-sgml +.sh: application/x-bsh +.sh: application/x-sh +.sh: application/x-shar +.sh: text/x-script.sh +.shar: application/x-bsh +.shar: application/x-shar +.shtml: text/html +.shtml: text/x-server-parsed-html +.sid: audio/x-psid +.sit: application/x-sit +.sit: application/x-stuffit +.skd: application/x-koan +.skm: application/x-koan +.skp: application/x-koan +.skt: application/x-koan +.sl: application/x-seelogo +.smi: application/smil +.smil: application/smil +.snd: audio/basic +.snd: audio/x-adpcm +.sol: application/solids +.spc: application/x-pkcs7-certificates +.spc: text/x-speech +.spl: application/futuresplash +.spr: application/x-sprite +.sprite: application/x-sprite +.src: application/x-wais-source +.ssi: text/x-server-parsed-html +.ssm: application/streamingmedia +.sst: application/vnd.ms-pki.certstore +.sst: application/vnd.ms-pkicertstore +.step: application/step +.stl: application/sla +.stl: application/vnd.ms-pki.stl +.stl: application/vnd.ms-pkistl +.stl: application/x-navistyle +.stm: text/html +.stp: application/step +.sv4cpio: application/x-sv4cpio +.sv4crc: application/x-sv4crc +.svf: image/vnd.dwg +.svf: image/x-dwg +.svg: image/svg+xml +.svr: application/x-world +.svr: x-world/x-svr +.swf: application/x-shockwave-flash +.t: application/x-troff +.talk: text/x-speech +.tar: application/x-tar +.tbk: application/toolbook +.tbk: application/x-tbook +.tcl: application/x-tcl +.tcl: text/x-script.tcl +.tcsh: text/x-script.tcsh +.tex: application/x-tex +.texi: application/x-texinfo +.texinfo: application/x-texinfo +.text: application/plain +.text: text/plain +.tgz: application/gnutar +.tgz: application/x-compressed +.tif: image/tiff +.tiff: image/tiff +.tk: application/x-tcl +.tr: application/x-troff +.trm: application/x-msterminal +.tsi: audio/tsp-audio +.tsp: application/dsptype +.tsp: audio/tsplayer +.tsv: text/tab-separated-values +.turbot: image/florian +.txt: text/plain +.uil: text/x-uil +.uls: text/iuls +.uni: text/uri-list +.unis: text/uri-list +.unv: application/i-deas +.uri: text/uri-list +.uris: text/uri-list +.ustar: application/x-ustar +.ustar: multipart/x-ustar +.uu: application/octet-stream +.uu: text/x-uuencode +.uue: text/x-uuencode +.vcd: application/x-cdlink +.vcf: text/x-vcard +.vcs: text/x-vcalendar +.vda: application/vda +.vdo: video/vdo +.vew: application/groupwise +.viv: video/vivo +.viv: video/vnd.vivo +.vivo: video/vivo +.vivo: video/vnd.vivo +.vmd: application/vocaltec-media-desc +.vmf: application/vocaltec-media-file +.voc: audio/voc +.voc: audio/x-voc +.vos: video/vosaic +.vox: audio/voxware +.vqe: audio/x-twinvq-plugin +.vqf: audio/x-twinvq +.vql: audio/x-twinvq-plugin +.vrml: application/x-vrml +.vrml: model/vrml +.vrml: x-world/x-vrml +.vrt: x-world/x-vrt +.vsd: application/x-visio +.vst: application/x-visio +.vsw: application/x-visio +.w60: application/wordperfect6.0 +.w61: application/wordperfect6.1 +.w6w: application/msword +.war: application/java-archive +.wav: audio/wav +.wav: audio/x-wav +.wb1: application/x-qpro +.wbmp: image/vnd.wap.wbmp +.wbmp: image/vnd.wap.wbmp +.wcm: application/vnd.ms-works +.wdb: application/vnd.ms-works +.web: application/vnd.xara +.wiz: application/msword +.wk1: application/x-123 +.wks: application/vnd.ms-works +.wmf: application/x-msmetafile +.wmf: windows/metafile +.wml: text/vnd.wap.wml +.wmlc: application/vnd.wap.wmlc +.wmls: text/vnd.wap.wmlscript +.wmlsc: application/vnd.wap.wmlscriptc +.wmv: video/x-ms-wmv +.word: application/msword +.wp5: application/wordperfect +.wp6: application/wordperfect +.wp: application/wordperfect +.wpd: application/wordperfect +.wps: application/vnd.ms-works +.wq1: application/x-lotus +.wri: application/mswrite +.wrl: application/x-world +.wsc: text/scriplet +.wsrc: application/x-wais-source +.wtk: application/x-wintalk +.x-png: image/png +.xaf: x-world/x-vrml +.xbm: image/xbm +.xdr: video/x-amt-demorun +.xgz: xgl/drawing +.xhtml: application/xhtml+xml +.xif: image/vnd.xiff +.xl: application/excel +.xla: application/excel +.xlb: application/excel +.xlc: application/excel +.xld: application/excel +.xlk: application/excel +.xll: application/excel +.xlm: application/excel +.xls: application/excel +.xlt: application/excel +.xlv: application/excel +.xlw: application/excel +.xm: audio/xm +.xml: text/xml +.xmz: xgl/movie +.xof: x-world/x-vrml +.xpi: application/x-xpinstall +.xpix: application/x-vnd.ls-xpix +.xpm: image/x-xpixmap +.xpm: image/xpm +.xsr: video/x-amt-showrun +.xwd: image/x-xwd +.xwd: image/x-xwindowdump +.xyz: chemical/x-pdb +.z: application/x-compressed +.zip: application/zip +.zoo: application/octet-stream +.zsh: text/x-script.zsh diff --git a/amarok/src/mediadevice/daap/mongrel/lib/mongrel/stats.rb b/amarok/src/mediadevice/daap/mongrel/lib/mongrel/stats.rb new file mode 100644 index 00000000..f6cf5ab6 --- /dev/null +++ b/amarok/src/mediadevice/daap/mongrel/lib/mongrel/stats.rb @@ -0,0 +1,89 @@ +# Copyright (c) 2005 Zed A. Shaw +# You can redistribute it and/or modify it under the same terms as Ruby. +# +# Additional work donated by contributors. See http://mongrel.rubyforge.org/attributions.html +# for more information. + +# A very simple little class for doing some basic fast statistics sampling. +# You feed it either samples of numeric data you want measured or you call +# Stats.tick to get it to add a time delta between the last time you called it. +# When you're done either call sum, sumsq, n, min, max, mean or sd to get +# the information. The other option is to just call dump and see everything. +# +# It does all of this very fast and doesn't take up any memory since the samples +# are not stored but instead all the values are calculated on the fly. +module Mongrel + class Stats + attr_reader :sum, :sumsq, :n, :min, :max + + def initialize(name) + @name = name + reset + end + + # Resets the internal counters so you can start sampling again. + def reset + @sum = 0.0 + @sumsq = 0.0 + @last_time = Time.new + @n = 0.0 + @min = 0.0 + @max = 0.0 + end + + # Adds a sampling to the calculations. + def sample(s) + @sum += s + @sumsq += s * s + if @n == 0 + @min = @max = s + else + @min = s if @min > s + @max = s if @max < s + end + @n+=1 + end + + # Dump this Stats object with an optional additional message. + def dump(msg = "", out=STDERR) + out.puts "#{msg}: #{self.to_s}" + end + + # Returns a common display (used by dump) + def to_s + "[#{@name}]: SUM=%0.4f, SUMSQ=%0.4f, N=%0.4f, MEAN=%0.4f, SD=%0.4f, MIN=%0.4f, MAX=%0.4f" % [@sum, @sumsq, @n, mean, sd, @min, @max] + end + + + # Calculates and returns the mean for the data passed so far. + def mean + @sum / @n + end + + # Calculates the standard deviation of the data so far. + def sd + # (sqrt( ((s).sumsq - ( (s).sum * (s).sum / (s).n)) / ((s).n-1) )) + begin + return Math.sqrt( (@sumsq - ( @sum * @sum / @n)) / (@n-1) ) + rescue Errno::EDOM + return 0.0 + end + end + + + # Adds a time delta between now and the last time you called this. This + # will give you the average time between two activities. + # + # An example is: + # + # t = Stats.new("do_stuff") + # 10000.times { do_stuff(); t.tick } + # t.dump("time") + # + def tick + now = Time.now + sample(now - @last_time) + @last_time = now + end + end +end diff --git a/amarok/src/mediadevice/daap/mongrel/lib/mongrel/tcphack.rb b/amarok/src/mediadevice/daap/mongrel/lib/mongrel/tcphack.rb new file mode 100644 index 00000000..634f9dd0 --- /dev/null +++ b/amarok/src/mediadevice/daap/mongrel/lib/mongrel/tcphack.rb @@ -0,0 +1,18 @@ +# Copyright (c) 2005 Zed A. Shaw +# You can redistribute it and/or modify it under the same terms as Ruby. +# +# Additional work donated by contributors. See http://mongrel.rubyforge.org/attributions.html +# for more information. + + +# A modification proposed by Sean Treadway that increases the default accept +# queue of TCPServer to 1024 so that it handles more concurrent requests. +class TCPServer + def initialize_with_backlog(*args) + initialize_without_backlog(*args) + listen(1024) + end + + alias_method :initialize_without_backlog, :initialize + alias_method :initialize, :initialize_with_backlog +end diff --git a/amarok/src/mediadevice/daap/mongrel/lib/rbconfig/Makefile.am b/amarok/src/mediadevice/daap/mongrel/lib/rbconfig/Makefile.am new file mode 100644 index 00000000..25c9389c --- /dev/null +++ b/amarok/src/mediadevice/daap/mongrel/lib/rbconfig/Makefile.am @@ -0,0 +1,5 @@ +amarokrbconfigdir = \ + $(kde_datadir)/amarok/ruby_lib/rbconfig + +amarokrbconfig_DATA = \ + datadir.rb diff --git a/amarok/src/mediadevice/daap/mongrel/lib/rbconfig/datadir.rb b/amarok/src/mediadevice/daap/mongrel/lib/rbconfig/datadir.rb new file mode 100644 index 00000000..7d4fe23a --- /dev/null +++ b/amarok/src/mediadevice/daap/mongrel/lib/rbconfig/datadir.rb @@ -0,0 +1,23 @@ +#-- +# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others. +# All rights reserved. +# See LICENSE.txt for permissions. +#++ + + +module Config + + # Only define datadir if it doesn't already exist. + unless Config.respond_to?(:datadir) + + # Return the path to the data directory associated with the given + # package name. Normally this is just + # "#{Config::CONFIG['datadir']}/#{package_name}", but may be + # modified by packages like RubyGems to handle versioned data + # directories. + def Config.datadir(package_name) + File.join(CONFIG['datadir'], package_name) + end + + end +end diff --git a/amarok/src/mediadevice/daap/mongrel/lib/rubygems.rb b/amarok/src/mediadevice/daap/mongrel/lib/rubygems.rb new file mode 100644 index 00000000..9f0686b0 --- /dev/null +++ b/amarok/src/mediadevice/daap/mongrel/lib/rubygems.rb @@ -0,0 +1,466 @@ +# -*- ruby -*- + +#-- +# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others. +# All rights reserved. +# See LICENSE.txt for permissions. +#++ + +require 'rbconfig' + +module Gem + class LoadError < ::LoadError + attr_accessor :name, :version_requirement + end +end + +module Kernel + + # Adds a Ruby Gem to the $LOAD_PATH. Before a Gem is loaded, its + # required Gems are loaded. If the version information is omitted, + # the highest version Gem of the supplied name is loaded. If a Gem + # is not found that meets the version requirement and/or a required + # Gem is not found, a Gem::LoadError is raised. More information on + # version requirements can be found in the Gem::Version + # documentation. + # + # The +gem+ directive should be executed *before* any require + # statements (otherwise rubygems might select a conflicting library + # version). + # + # You can define the environment variable GEM_SKIP as a way to not + # load specified gems. you might do this to test out changes that + # haven't been intsalled yet. Example: + # + # GEM_SKIP=libA:libB ruby-I../libA -I../libB ./mycode.rb + # + # gem:: [String or Gem::Dependency] The gem name or dependency + # instance. + # + # version_requirement:: [default=">= 0.0.0"] The version + # requirement. + # + # return:: [Boolean] true if the Gem is loaded, otherwise false. + # + # raises:: [Gem::LoadError] if Gem cannot be found, is listed in + # GEM_SKIP, or version requirement not met. + # + def gem(gem_name, *version_requirements) + active_gem_with_options(gem_name, version_requirements) + end + + # Same as the +gem+ command, but will also require a file if the gem + # provides an auto-required file name. + # + # DEPRECATED! Use +gem+ instead. + # + def require_gem(gem_name, *version_requirements) + active_gem_with_options(gem_name, + version_requirements, :auto_require=>true) + end + + def active_gem_with_options(gem_name, version_requirements, options={}) + skip_list = (ENV['GEM_SKIP'] || "").split(/:/) + raise Gem::LoadError, "skipping #{gem_name}" if skip_list.include? gem_name + Gem.activate(gem_name, options[:auto_require], *version_requirements) + end + private :active_gem_with_options +end + + +# Main module to hold all RubyGem classes/modules. +# +module Gem + require 'rubygems/rubygems_version.rb' + + class Exception < RuntimeError + end + + class OperationNotSupportedError < Gem::Exception + end + + RubyGemsPackageVersion = RubyGemsVersion + + DIRECTORIES = ['cache', 'doc', 'gems', 'specifications'] + + @@source_index = nil + + class << self + + def manage_gems + require 'rubygems/user_interaction' + require 'rubygems/builder' + require 'rubygems/format' + require 'rubygems/remote_installer' + require 'rubygems/installer' + require 'rubygems/validator' + require 'rubygems/doc_manager' + require 'rubygems/cmd_manager' + require 'rubygems/gem_runner' + require 'rubygems/config_file' + end + + # Returns an Cache of specifications that are in the Gem.path + # + # return:: [Gem::SourceIndex] Index of installed Gem::Specifications + # + def source_index + @@source_index ||= SourceIndex.from_installed_gems + end + + # Provide an alias for the old name. + alias cache source_index + + # The directory path where Gems are to be installed. + # + # return:: [String] The directory path + # + def dir + @gem_home ||= nil + set_home(ENV['GEM_HOME'] || default_dir) unless @gem_home + @gem_home + end + + # List of directory paths to search for Gems. + # + # return:: [List] List of directory paths. + # + def path + @gem_path ||= nil + set_paths(ENV['GEM_PATH']) unless @gem_path + @gem_path + end + + # The home directory for the user. + def user_home + @user_home ||= find_home + end + + # Return the path to standard location of the users .gemrc file. + def config_file + File.join(Gem.user_home, '.gemrc') + end + + # The standard configuration object for gems. + def configuration + @configuration ||= {} + end + + # Use the given configuration object (which implements the + # ConfigFile protocol) as the standard configuration object. + def configuration=(config) + @configuration = config + end + + # Return the path the the data directory specified by the gem + # name. If the package is not available as a gem, return nil. + def datadir(gem_name) + return nil if @loaded_specs.nil? + spec = @loaded_specs[gem_name] + return nil if spec.nil? + File.join(spec.full_gem_path, 'data', gem_name) + end + + # Return the Ruby command to use to execute the Ruby interpreter. + def ruby + "ruby" + end + + # Activate a gem (i.e. add it to the Ruby load path). The gem + # must satisfy all the specified version constraints. If + # +autorequire+ is true, then automatically require the specified + # autorequire file in the gem spec. + # + # Returns true if the gem is loaded by this call, false if it is + # already loaded, or an exception otherwise. + # + def activate(gem, autorequire, *version_requirements) + @loaded_specs ||= Hash.new + unless version_requirements.size > 0 + version_requirements = [">= 0.0.0"] + end + unless gem.respond_to?(:name) && gem.respond_to?(:version_requirements) + gem = Gem::Dependency.new(gem, version_requirements) + end + + matches = Gem.source_index.find_name(gem.name, gem.version_requirements) + report_activate_error(gem) if matches.empty? + + if @loaded_specs[gem.name] + # This gem is already loaded. If the currently loaded gem is + # not in the list of candidate gems, then we have a version + # conflict. + existing_spec = @loaded_specs[gem.name] + if ! matches.any? { |spec| spec.version == existing_spec.version } + fail Gem::Exception, "can't activate #{gem}, already activated #{existing_spec.full_name}]" + end + return false + end + + # new load + spec = matches.last + if spec.loaded? + return false unless autorequire + result = spec.autorequire ? require(spec.autorequire) : false + return result || false + end + + spec.loaded = true + @loaded_specs[spec.name] = spec + + # Load dependent gems first + spec.dependencies.each do |dep_gem| + activate(dep_gem, autorequire) + end + + # add bin dir to require_path + if(spec.bindir) then + spec.require_paths << spec.bindir + end + + # Now add the require_paths to the LOAD_PATH + spec.require_paths.each do |path| + $:.unshift File.join(spec.full_gem_path, path) + end + + if autorequire && spec.autorequire then + Array(spec.autorequire).each do |a_lib| + require a_lib + end + end + + return true + end + + # Report a load error during activation. The message of load + # error depends on whether it was a version mismatch or if there + # are not gems of any version by the requested name. + def report_activate_error(gem) + matches = Gem.source_index.find_name(gem.name) + if matches.size==0 + error = Gem::LoadError.new( + "Could not find RubyGem #{gem.name} (#{gem.version_requirements})\n") + else + error = Gem::LoadError.new( + "RubyGem version error: " + + "#{gem.name}(#{matches.first.version} not #{gem.version_requirements})\n") + end + error.name = gem.name + error.version_requirement = gem.version_requirements + raise error + end + private :report_activate_error + + # Reset the +dir+ and +path+ values. The next time +dir+ or +path+ + # is requested, the values will be calculated from scratch. This is + # mainly used by the unit tests to provide test isolation. + # + def clear_paths + @gem_home = nil + @gem_path = nil + @@source_index = nil + end + + # Use the +home+ and (optional) +paths+ values for +dir+ and +path+. + # Used mainly by the unit tests to provide environment isolation. + # + def use_paths(home, paths=[]) + clear_paths + set_home(home) if home + set_paths(paths.join(File::PATH_SEPARATOR)) if paths + end + + # Return a list of all possible load paths for all versions for + # all gems in the Gem installation. + # + def all_load_paths + result = [] + Gem.path.each do |gemdir| + each_load_path(all_partials(gemdir)) do |load_path| + result << load_path + end + end + result + end + + # Return a list of all possible load paths for the latest version + # for all gems in the Gem installation. + def latest_load_paths + result = [] + Gem.path.each do |gemdir| + each_load_path(latest_partials(gemdir)) do |load_path| + result << load_path + end + end + result + end + + def required_location(gemname, libfile, *version_constraints) + version_constraints = [">0"] if version_constraints.empty? + matches = Gem.source_index.find_name(gemname, version_constraints) + return nil if matches.empty? + spec = matches.last + spec.require_paths.each do |path| + result = File.join(spec.full_gem_path, path, libfile) + return result if File.exists?(result) + end + nil + end + + + private + + # Return all the partial paths in the given +gemdir+. + def all_partials(gemdir) + Dir[File.join(gemdir, 'gems/*')] + end + + # Return only the latest partial paths in the given +gemdir+. + def latest_partials(gemdir) + latest = {} + all_partials(gemdir).each do |gp| + base = File.basename(gp) + matches = /(.*)-((\d+\.)*\d+)/.match(base) + name, version = [matches[1], matches[2]] + ver = Gem::Version.new(version) + if latest[name].nil? || ver > latest[name][0] + latest[name] = [ver, gp] + end + end + latest.collect { |k,v| v[1] } + end + + # Expand each partial gem path with each of the required paths + # specified in the Gem spec. Each expanded path is yielded. + def each_load_path(partials) + partials.each do |gp| + base = File.basename(gp) + specfn = File.join(dir, "specifications", base + ".gemspec") + if File.exist?(specfn) + spec = eval(File.read(specfn)) + spec.require_paths.each do |rp| + yield(File.join(gp, rp)) + end + else + filename = File.join(gp, 'lib') + yield(filename) if File.exist?(filename) + end + end + end + + # Set the Gem home directory (as reported by +dir+). + def set_home(home) + @gem_home = home + ensure_gem_subdirectories(@gem_home) + end + + # Set the Gem search path (as reported by +path+). + def set_paths(gpaths) + if gpaths + @gem_path = gpaths.split(File::PATH_SEPARATOR) + @gem_path << Gem.dir + else + @gem_path = [Gem.dir] + end + @gem_path.uniq! + @gem_path.each do |gp| ensure_gem_subdirectories(gp) end + end + + # Some comments from the ruby-talk list regarding finding the home + # directory: + # + # I have HOME, USERPROFILE and HOMEDRIVE + HOMEPATH. Ruby seems + # to be depending on HOME in those code samples. I propose that + # it should fallback to USERPROFILE and HOMEDRIVE + HOMEPATH (at + # least on Win32). + # + def find_home + ['HOME', 'USERPROFILE'].each do |homekey| + return ENV[homekey] if ENV[homekey] + end + if ENV['HOMEDRIVE'] && ENV['HOMEPATH'] + return "#{ENV['HOMEDRIVE']}:#{ENV['HOMEPATH']}" + end + begin + File.expand_path("~") + rescue StandardError => ex + if File::ALT_SEPARATOR + "C:/" + else + "/" + end + end + end + + public + + # Default home directory path to be used if an alternate value is + # not specified in the environment. + def default_dir + ## rbconfig = Dir.glob("{#{($LOAD_PATH).join(',')}}/rbconfig.rb").first + ## if rbconfig + ## module_eval File.read(rbconfig) unless const_defined?("Config") + ## else + ## require 'rbconfig' + ## end + # + # Note on above code: we have an issue if a Config class is + # already defined and we load 'rbconfig'. The above code is + # supposed to work around that but it's been commented out. In + # any case, I moved "require 'rbconfig'" to the top of this + # file, because there was a circular dependency between this + # method and our custom require. In any case, rbconfig is a + # fundamental RubyGems dependency, so it might as well be up the + # top. -- Gavin Sinclair, 2004-12-12 + # + if defined? RUBY_FRAMEWORK_VERSION + return File.join(File.dirname(Config::CONFIG["sitedir"]), "Gems") + else + File.join(Config::CONFIG['libdir'], 'ruby', 'gems', Config::CONFIG['ruby_version']) + end + end + + private + + # Quietly ensure the named Gem directory contains all the proper + # subdirectories. If we can't create a directory due to a + # permission problem, then we will silently continue. + def ensure_gem_subdirectories(gemdir) + DIRECTORIES.each do |filename| + fn = File.join(gemdir, filename) + unless File.exists?(fn) + require 'fileutils' + FileUtils.mkdir_p(fn) rescue nil + end + end + end + + end +end + + +# Modify the non-gem version of datadir to handle gem package names. + +require 'rbconfig/datadir' +module Config + class << self + alias gem_original_datadir datadir + + # Return the path to the data directory associated with the named + # package. If the package is loaded as a gem, return the gem + # specific data directory. Otherwise return a path to the share + # area as define by "#{Config::CONFIG['datadir']}/#{package_name}". + def datadir(package_name) + Gem.datadir(package_name) || Config.gem_original_datadir(package_name) + end + end +end + + +require 'rubygems/source_index' +require 'rubygems/specification' +require 'rubygems/security' +require 'rubygems/version' +#require 'rubygems/loadpath_manager' # custom_require replaces this +require 'rubygems/custom_require' + diff --git a/amarok/src/mediadevice/daap/mongrel/lib/rubygems/._gem_commands.rb b/amarok/src/mediadevice/daap/mongrel/lib/rubygems/._gem_commands.rb new file mode 100644 index 00000000..cde4853a Binary files /dev/null and b/amarok/src/mediadevice/daap/mongrel/lib/rubygems/._gem_commands.rb differ diff --git a/amarok/src/mediadevice/daap/mongrel/lib/rubygems/Makefile.am b/amarok/src/mediadevice/daap/mongrel/lib/rubygems/Makefile.am new file mode 100644 index 00000000..e3d29a85 --- /dev/null +++ b/amarok/src/mediadevice/daap/mongrel/lib/rubygems/Makefile.am @@ -0,0 +1,31 @@ +amarokrubygemsdir = \ + $(kde_datadir)/amarok/ruby_lib/rubygems + +amarokrubygems_DATA = \ + ._gem_commands.rb \ + builder.rb \ + cmd_manager.rb \ + command.rb \ + config_file.rb \ + custom_require.rb \ + dependency_list.rb \ + doc_manager.rb \ + format.rb \ + gem_commands.rb \ + gem_openssl.rb \ + gem_runner.rb \ + incremental_fetcher.rb \ + installer.rb \ + loadpath_manager.rb \ + old_format.rb \ + open-uri.rb \ + package.rb \ + remote_installer.rb \ + rubygems_version.rb \ + security.rb \ + source_index.rb \ + specification.rb \ + timer.rb \ + user_interaction.rb \ + validator.rb \ + version.rb diff --git a/amarok/src/mediadevice/daap/mongrel/lib/rubygems/builder.rb b/amarok/src/mediadevice/daap/mongrel/lib/rubygems/builder.rb new file mode 100644 index 00000000..e2dc2fe4 --- /dev/null +++ b/amarok/src/mediadevice/daap/mongrel/lib/rubygems/builder.rb @@ -0,0 +1,73 @@ +#-- +# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others. +# All rights reserved. +# See LICENSE.txt for permissions. +#++ + +require "rubygems/package" +require "rubygems/security" +require "yaml" +require 'rubygems/gem_openssl' + +module Gem + + ## + # The Builder class processes RubyGem specification files + # to produce a .gem file. + # + class Builder + + include UserInteraction + ## + # Constructs a builder instance for the provided specification + # + # spec:: [Gem::Specification] The specification instance + # + def initialize(spec) + @spec = spec + end + + ## + # Builds the gem from the specification. Returns the name of the file written. + # + def build + @spec.mark_version + @spec.validate + + # if the signing key was specified, then load the file, and swap + # to the public key (TODO: we should probably just omit the + # signing key in favor of the signing certificate, but that's for + # the future, also the signature algorihtm should be configurable) + signer = nil + if @spec.respond_to?(:signing_key) && @spec.signing_key + signer = Gem::Security::Signer.new(@spec.signing_key, @spec.cert_chain) + @spec.signing_key = nil + @spec.cert_chain = signer.cert_chain.map { |cert| cert.to_s } + end + + file_name = @spec.full_name+".gem" + + Package.open(file_name, "w", signer) do |pkg| + pkg.metadata = @spec.to_yaml + @spec.files.each do |file| + next if File.directory? file + pkg.add_file_simple(file, File.stat(file_name).mode & 0777, + File.size(file)) do |os| + os.write File.open(file, "rb"){|f|f.read} + end + end + end + say success + file_name + end + + def success + <<-EOM + Successfully built RubyGem + Name: #{@spec.name} + Version: #{@spec.version} + File: #{@spec.full_name+'.gem'} +EOM + end + end +end diff --git a/amarok/src/mediadevice/daap/mongrel/lib/rubygems/cmd_manager.rb b/amarok/src/mediadevice/daap/mongrel/lib/rubygems/cmd_manager.rb new file mode 100644 index 00000000..b84b6f7c --- /dev/null +++ b/amarok/src/mediadevice/daap/mongrel/lib/rubygems/cmd_manager.rb @@ -0,0 +1,137 @@ +#-- +# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others. +# All rights reserved. +# See LICENSE.txt for permissions. +#++ + +require 'rubygems' +require 'rubygems/command' +require 'rubygems/user_interaction' +require 'rubygems/gem_commands' +require 'timeout' + +module Gem + + ################################################################### + # Signals that local installation will not proceed, not that it has + # been tried and failed. TODO: better name. + class LocalInstallationError < Gem::Exception; end + + #################################################################### + # Signals that a file permission error is preventing the user from + # installing in the requested directories. + class FilePermissionError < Gem::Exception + def initialize(path) + super("You don't have write permissions into the #{path} directory.") + end + end + + #################################################################### + # Signals that a remote operation cannot be conducted, probably due + # to not being connected (or just not finding host). + # + # TODO: create a method that tests connection to the preferred gems + # server. All code dealing with remote operations will want this. + # Failure in that method should raise this error. + class RemoteError < Gem::Exception; end + + #################################################################### + # The command manager registers and installs all the individual + # sub-commands supported by the gem command. + class CommandManager + include UserInteraction + + # Return the authoratative instance of the command manager. + def self.instance + @cmd_manager ||= CommandManager.new + end + + # Register all the subcommands supported by the gem command. + def initialize + @commands = {} + register_command HelpCommand.new + register_command InstallCommand.new + register_command UninstallCommand.new + register_command CheckCommand.new + register_command BuildCommand.new + register_command DependencyCommand.new + register_command QueryCommand.new + register_command ListCommand.new + register_command SearchCommand.new + register_command UpdateCommand.new + register_command CleanupCommand.new + register_command RDocCommand.new + register_command EnvironmentCommand.new + register_command SpecificationCommand.new + register_command UnpackCommand.new + register_command CertCommand.new + register_command ContentsCommand.new + end + + # Register the command object. + def register_command(command_obj) + @commands[command_obj.command.intern] = command_obj + end + + # Return the registered command from the command name. + def [](command_name) + @commands[command_name.intern] + end + + # Return a list of all command names (as strings). + def command_names + @commands.keys.collect {|key| key.to_s}.sort + end + + # Run the config specificed by +args+. + def run(args) + process_args(args) + rescue StandardError, Timeout::Error => ex + alert_error "While executing gem ... (#{ex.class})\n #{ex.to_s}" + puts ex.backtrace if Gem.configuration.backtrace + terminate_interaction(1) + rescue Interrupt + alert_error "Interrupted" + terminate_interaction(1) + end + + def process_args(args) + args = args.to_str.split(/\s+/) if args.respond_to?(:to_str) + if args.size == 0 + say Gem::HELP + terminate_interaction(1) + end + case args[0] + when '-h', '--help' + say Gem::HELP + terminate_interaction(0) + when '-v', '--version' + say Gem::RubyGemsPackageVersion + terminate_interaction(0) + when /^-/ + alert_error "Invalid option: #{args[0]}. See 'gem --help'." + terminate_interaction(1) + else + cmd_name = args.shift.downcase + cmd = find_command(cmd_name) + cmd.invoke(*args) + end + end + + def find_command(cmd_name) + possibilities = find_command_possibilities(cmd_name) + if possibilities.size > 1 + raise "Ambiguous command #{cmd_name} matches [#{possibilities.join(', ')}]" + end + if possibilities.size < 1 + raise "Unknown command #{cmd_name}" + end + self[possibilities.first] + end + + def find_command_possibilities(cmd_name) + len = cmd_name.length + self.command_names.select { |n| cmd_name == n[0,len] } + end + end +end diff --git a/amarok/src/mediadevice/daap/mongrel/lib/rubygems/command.rb b/amarok/src/mediadevice/daap/mongrel/lib/rubygems/command.rb new file mode 100644 index 00000000..31e7e085 --- /dev/null +++ b/amarok/src/mediadevice/daap/mongrel/lib/rubygems/command.rb @@ -0,0 +1,280 @@ +#-- +# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others. +# All rights reserved. +# See LICENSE.txt for permissions. +#++ + +require 'rubygems/user_interaction' + +module Gem + + #################################################################### + # Base class for all Gem commands. + class Command + include UserInteraction + + Option = Struct.new(:short, :long, :description, :handler) + + attr_reader :command, :options + attr_accessor :summary, :defaults, :program_name + + # Initialize a generic gem command. + def initialize(command, summary=nil, defaults={}) + @command = command + @summary = summary + @program_name = "gem #{command}" + @defaults = defaults + @options = defaults.dup + @option_list = [] + @parser = nil + end + + # Override to provide command handling. + def execute + fail "Generic command has no actions" + end + + # Override to display the usage for an individual gem command. + def usage + "#{program_name}" + end + + # Override to provide details of the arguments a command takes. + # It should return a left-justified string, one argument per line. + def arguments + "" + end + + # Override to display the default values of the command + # options. (similar to +arguments+, but displays the default + # values). + def defaults_str + "" + end + + # Display the help message for this command. + def show_help + parser.program_name = usage + say parser + end + + # Invoke the command with the given list of arguments. + def invoke(*args) + handle_options(args) + if options[:help] + show_help + elsif @when_invoked + @when_invoked.call(options) + else + execute + end + end + + # Call the given block when invoked. + # + # Normal command invocations just executes the +execute+ method of + # the command. Specifying an invocation block allows the test + # methods to override the normal action of a command to determine + # that it has been invoked correctly. + def when_invoked(&block) + @when_invoked = block + end + + # Add a option (and a handler) to this command. + def add_option(*args, &handler) + @option_list << [args, handler] + end + + # Remove a previously defined command option. + def remove_option(name) + @option_list.reject! { |args, handler| args.any? { |x| x =~ /^#{name}/ } } + end + + # Merge a set of command options with the set of default options + # (without modifying the default option hash). + def merge_options(new_options) + @options = @defaults.clone + new_options.each do |k,v| @options[k] = v end + end + + # True if the command handles the given argument list. + def handles?(args) + begin + parser.parse!(args.dup) + return true + rescue + return false + end + end + + private + + # Return the command manager instance. + def command_manager + Gem::CommandManager.instance + end + + # Handle the given list of arguments by parsing them and recording + # the results. + def handle_options(args) + args = add_extra_args(args) + @options = @defaults.clone + parser.parse!(args) + @options[:args] = args + end + + def add_extra_args(args) + result = [] + s_extra = Command.specific_extra_args(@command) + extra = Command.extra_args + s_extra + while ! extra.empty? + ex = [] + ex << extra.shift + ex << extra.shift if extra.first.to_s =~ /^[^-]/ + result << ex if handles?(ex) + end + result.flatten! + result.concat(args) + result + end + + # Create on demand parser. + def parser + create_option_parser if @parser.nil? + @parser + end + + def create_option_parser + require 'optparse' + @parser = OptionParser.new + option_names = {} + @parser.separator("") + unless @option_list.empty? + @parser.separator(" Options:") + configure_options(@option_list, option_names) + @parser.separator("") + end + @parser.separator(" Common Options:") + configure_options(Command.common_options, option_names) + @parser.separator("") + unless arguments.empty? + @parser.separator(" Arguments:") + arguments.split(/\n/).each do |arg_desc| + @parser.separator(" #{arg_desc}") + end + @parser.separator("") + end + @parser.separator(" Summary:") + @parser.separator(" #@summary") + unless defaults_str.empty? + @parser.separator("") + @parser.separator(" Defaults:") + defaults_str.split(/\n/).each do |line| + @parser.separator(" #{line}") + end + end + end + + def configure_options(option_list, option_names) + option_list.each do |args, handler| + dashes = args.select { |arg| arg =~ /^-/ } + next if dashes.any? { |arg| option_names[arg] } + @parser.on(*args) do |value| + handler.call(value, @options) + end + dashes.each do |arg| option_names[arg] = true end + end + end + + ################################################################## + # Class methods for Command. + class << self + def common_options + @common_options ||= [] + end + + def add_common_option(*args, &handler) + Gem::Command.common_options << [args, handler] + end + + def extra_args + @extra_args ||= [] + end + + def extra_args=(value) + case value + when Array + @extra_args = value + when String + @extra_args = value.split + end + end + + # Return an array of extra arguments for the command. The extra + # arguments come from the gem configuration file read at program + # startup. + def specific_extra_args(cmd) + specific_extra_args_hash[cmd] + end + + # Add a list of extra arguments for the given command. +args+ + # may be an array or a string to be split on white space. + def add_specific_extra_args(cmd,args) + args = args.split(/\s+/) if args.kind_of? String + specific_extra_args_hash[cmd] = args + end + + # Accessor for the specific extra args hash (self initializing). + def specific_extra_args_hash + @specific_extra_args_hash ||= Hash.new do |h,k| + h[k] = Array.new + end + end + end + + # ---------------------------------------------------------------- + # Add the options common to all commands. + + add_common_option('--source URL', + 'Use URL as the remote source for gems') do + |value, options| + require_gem("sources") + Gem.sources.clear + Gem.sources << value + end + + add_common_option('-p', '--[no-]http-proxy [URL]', + 'Use HTTP proxy for remote operations') do + |value, options| + options[:http_proxy] = (value == false) ? :no_proxy : value + end + + add_common_option('-h', '--help', + 'Get help on this command') do + |value, options| + options[:help] = true + end + + add_common_option('-v', '--verbose', + 'Set the verbose level of output') do + |value, options| + Gem.configuration.verbose = value + end + + # Backtrace and config-file are added so they show up in the help + # commands. Both options are actually handled before the other + # options get parsed. + + add_common_option('--config-file FILE', + "Use this config file instead of default") do + end + + add_common_option('--backtrace', + 'Show stack backtrace on errors') do + end + + add_common_option('--debug', + 'Turn on Ruby debugging') do + end + end # class +end # module diff --git a/amarok/src/mediadevice/daap/mongrel/lib/rubygems/config_file.rb b/amarok/src/mediadevice/daap/mongrel/lib/rubygems/config_file.rb new file mode 100644 index 00000000..04c43336 --- /dev/null +++ b/amarok/src/mediadevice/daap/mongrel/lib/rubygems/config_file.rb @@ -0,0 +1,98 @@ +#-- +# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others. +# All rights reserved. +# See LICENSE.txt for permissions. +#++ + + +require 'yaml' + +module Gem + + #################################################################### + # Store the gem command options specified in the configuration file. + # The config file object acts much like a hash. + # + class ConfigFile + # True if the backtrace option has been specified. + attr_reader :backtrace + + # List of arguments supplied to the config file object. + attr_reader :args + + # Verbose level of output: + # * false -- No output + # * true -- Normal output + # * :loud -- Extra output + attr_accessor :verbose + + # Create the config file object. +args+ is the list of arguments + # from the command line. + # + # The following command line options are handled early here rather + # than later at the time most command options are processed. + # + # * --config-file and --config-file==NAME -- Obviously these need + # to be handled by the ConfigFile object to ensure we get the + # right config file. + # + # * --backtrace -- Backtrace needs to be turned on early so that + # errors before normal option parsing can be properly handled. + # + # * --debug -- Enable Ruby level debug messages. Handled early + # for the same reason as --backtrace. + # + def initialize(arg_list) + @verbose = true + handle_arguments(arg_list) + begin + @hash = open(config_file_name) {|f| YAML.load(f) } + rescue ArgumentError + warn "Failed to load #{config_file_name}" + rescue Errno::ENOENT + # Ignore missing config file error. + rescue Errno::EACCES + warn "Failed to load #{config_file_name} due to permissions problem." + end + @hash ||= {} + end + + # The name of the configuration file. + def config_file_name + @config_file_name || Gem.config_file + end + + # Return the configuration information for +key+. + def [](key) + @hash[key.to_s] + end + + private + + # Handle the command arguments. + def handle_arguments(arg_list) + need_cfg_name = false + @args = [] + arg_list.each do |arg| + if need_cfg_name + @config_file_name = arg + need_cfg_name = false + else + case arg + when /^--(traceback|backtrace)$/ + @backtrace = true + when /^--debug$/ + $DEBUG = true + when /^--config-file$/ + need_cfg_name = true + when /^--config-file=(.+)$/ + @config_file_name = $1 + else + @args << arg + end + end + end + end + end + +end diff --git a/amarok/src/mediadevice/daap/mongrel/lib/rubygems/custom_require.rb b/amarok/src/mediadevice/daap/mongrel/lib/rubygems/custom_require.rb new file mode 100644 index 00000000..c68a7dda --- /dev/null +++ b/amarok/src/mediadevice/daap/mongrel/lib/rubygems/custom_require.rb @@ -0,0 +1,124 @@ +#-- +# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others. +# All rights reserved. +# See LICENSE.txt for permissions. +#++ + +require 'rubygems/source_index' + +module Kernel + alias gem_original_require require + + # + # We replace Ruby's require with our own, which is capable of + # loading gems on demand. + # + # When you call require 'x', this is what happens: + # * If the file can be loaded from the existing Ruby loadpath, it + # is. + # * Otherwise, installed gems are searched for a file that matches. + # If it's found in gem 'y', that gem is activated (added to the + # loadpath). + # + # The normal require functionality of returning false if + # that file has already been loaded is preserved. + # + def require(path) + gem_original_require path + rescue LoadError => load_error + begin + @gempath_searcher ||= Gem::GemPathSearcher.new + if spec = @gempath_searcher.find(path) + Gem.activate(spec.name, false, "= #{spec.version}") + gem_original_require path + else + raise load_error + end + end + end +end # module Kernel + + +module Gem + + # + # GemPathSearcher has the capability to find loadable files inside + # gems. It generates data up front to speed up searches later. + # + class GemPathSearcher + + # + # Initialise the data we need to make searches later. + # + def initialize + # We want a record of all the installed gemspecs, in the order + # we wish to examine them. + @gemspecs = init_gemspecs + # Map gem spec to glob of full require_path directories. + # Preparing this information may speed up searches later. + @lib_dirs = {} + @gemspecs.each do |spec| + @lib_dirs[spec.object_id] = lib_dirs(spec) + end + end + + # + # Look in all the installed gems until a matching _path_ is found. + # Return the _gemspec_ of the gem where it was found. If no match + # is found, return nil. + # + # The gems are searched in alphabetical order, and in reverse + # version order. + # + # For example: + # + # find('log4r') # -> (log4r-1.1 spec) + # find('log4r.rb') # -> (log4r-1.1 spec) + # find('rake/rdoctask') # -> (rake-0.4.12 spec) + # find('foobarbaz') # -> nil + # + # Matching paths can have various suffixes ('.rb', '.so', and + # others), which may or may not already be attached to _file_. + # This method doesn't care about the full filename that matches; + # only that there is a match. + # + def find(path) + @gemspecs.each do |spec| + return spec if matching_file(spec, path) + end + nil + end + + private + + SUFFIX_PATTERN = "{,.rb,.rbw,.so,.bundle,.dll,.sl}" + + # + # Attempts to find a matching path using the require_paths of the + # given _spec_. + # + # Some of the intermediate results are cached in @lib_dirs for + # speed. + # + def matching_file(spec, path) # :doc: + glob = "#{@lib_dirs[spec.object_id]}/#{path}#{SUFFIX_PATTERN}" + return true unless Dir[glob].select { |f| File.file?(f.untaint) }.empty? + end + + # Return a list of all installed gemspecs, sorted by alphabetical + # order and in reverse version order. + def init_gemspecs + Gem.source_index.map { |_, spec| spec }.sort { |a,b| + (a.name <=> b.name).nonzero? || (b.version <=> a.version) + } + end + + # Returns library directories glob for a gemspec. For example, + # '/usr/local/lib/ruby/gems/1.8/gems/foobar-1.0/{lib,ext}' + def lib_dirs(spec) + "#{spec.full_gem_path}/{#{spec.require_paths.join(',')}}" + end + + end # class Gem::GemPathLoader + +end # module Gem diff --git a/amarok/src/mediadevice/daap/mongrel/lib/rubygems/dependency_list.rb b/amarok/src/mediadevice/daap/mongrel/lib/rubygems/dependency_list.rb new file mode 100644 index 00000000..8c2cd903 --- /dev/null +++ b/amarok/src/mediadevice/daap/mongrel/lib/rubygems/dependency_list.rb @@ -0,0 +1,136 @@ +#-- +# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others. +# All rights reserved. +# See LICENSE.txt for permissions. +#++ + + +module Gem + class DependencyList + def self.from_source_index(src_index) + deps = DependencyList.new + src_index.each do |full_name, spec| + deps.add(spec) + end + deps + end + + def initialize + @specs = [] + end + + # Are all the dependencies in the list satisfied? + def ok? + @specs.all? { |spec| + spec.dependencies.all? { |dep| + @specs.find { |s| s.satisfies_requirement?(dep) } + } + } + end + + # Add a gemspec to the dependency list. + def add(gemspec) + @specs << gemspec + end + + def find_name(full_name) + @specs.find { |spec| spec.full_name == full_name } + end + + def remove_by_name(full_name) + @specs.delete_if { |spec| spec.full_name == full_name } + end + + # Is is ok to remove a gem from the dependency list? + # + # If removing the gemspec creates breaks a currently ok dependency, + # then it is NOT ok to remove the gem. + def ok_to_remove?(full_name) + gem_to_remove = find_name(full_name) + siblings = @specs.find_all { |s| + s.name == gem_to_remove.name && + s.full_name != gem_to_remove.full_name + } + deps = [] + @specs.each do |spec| + spec.dependencies.each do |dep| + deps << dep if gem_to_remove.satisfies_requirement?(dep) + end + end + deps.all? { |dep| + siblings.any? { |s| + s.satisfies_requirement?(dep) + } + } + end + + # Return a list of the specifications in the dependency list, + # sorted in order so that no spec in the list depends on a gem + # earlier in the list. + # + # This is useful when removing gems from a set of installed gems. + # By removing them in the returned order, you don't get into as + # many dependency issues. + # + # If there are circular dependencies (yuck!), then gems will be + # returned in order until only the circular dependents and anything + # they reference are left. Then arbitrary gemspecs will be returned + # until the circular dependency is broken, after which gems will be + # returned in dependency order again. + def dependency_order + result = [] + disabled = {} + predecessors = build_predecessors + while disabled.size < @specs.size + candidate = @specs.find { |spec| + ! disabled[spec.full_name] && + active_count(predecessors[spec.full_name], disabled) == 0 + } + if candidate + disabled[candidate.full_name] = true + result << candidate + elsif candidate = @specs.find { |spec| ! disabled[spec.full_name] } + # This case handles circular dependencies. Just choose a + # candidate and move on. + disabled[candidate.full_name] = true + result << candidate + else + # We should never get here, but just in case we will terminate + # the loop. + break + end + end + result + end + + private + + # Count the number of gemspecs in the list +specs+ that are still + # active (e.g. not listed in the ignore hash). + def active_count(specs, ignored) + result = 0 + specs.each do |spec| + result += 1 unless ignored[spec.full_name] + end + result + end + + # Return a hash of predecessors. E.g. results[spec.full_name] is a + # list of gemspecs that have a dependency satisfied by spec. + def build_predecessors + result = Hash.new { |h,k| h[k] = [] } + @specs.each do |spec| + @specs.each do |other| + next if spec.full_name == other.full_name + other.dependencies.each do |dep| + if spec.satisfies_requirement?(dep) + result[spec.full_name] << other + end + end + end + end + result + end + + end +end diff --git a/amarok/src/mediadevice/daap/mongrel/lib/rubygems/doc_manager.rb b/amarok/src/mediadevice/daap/mongrel/lib/rubygems/doc_manager.rb new file mode 100644 index 00000000..e838c30e --- /dev/null +++ b/amarok/src/mediadevice/daap/mongrel/lib/rubygems/doc_manager.rb @@ -0,0 +1,139 @@ +#-- +# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others. +# All rights reserved. +# See LICENSE.txt for permissions. +#++ + +module Gem + + class DocumentError < Gem::Exception; end + + class DocManager + + include UserInteraction + + # Create a document manager for the given gem spec. + # + # spec:: The Gem::Specification object representing the gem. + # rdoc_args:: Optional arguments for RDoc (template etc.) as a String. + # + def initialize(spec, rdoc_args="") + @spec = spec + @doc_dir = File.join(spec.installation_path, "doc", spec.full_name) + Gem::FilePermissionError.new(spec.installation_path) unless File.writable?(spec.installation_path) + @rdoc_args = rdoc_args.nil? ? [] : rdoc_args.split + end + + # Is the RDoc documentation installed? + def rdoc_installed? + return File.exist?(File.join(@doc_dir, "rdoc")) + end + + # Generate the RI documents for this gem spec. + # + # Note that if both RI and RDoc documents are generated from the + # same process, the RI docs should be done first (a likely bug in + # RDoc will cause RI docs generation to fail if run after RDoc). + def generate_ri + require 'fileutils' + + if @spec.has_rdoc then + load_rdoc + install_ri # RDoc bug, ri goes first + end + + FileUtils.mkdir_p @doc_dir unless File.exist?(@doc_dir) + end + + # Generate the RDoc documents for this gem spec. + # + # Note that if both RI and RDoc documents are generated from the + # same process, the RI docs should be done first (a likely bug in + # RDoc will cause RI docs generation to fail if run after RDoc). + def generate_rdoc + require 'fileutils' + + if @spec.has_rdoc then + load_rdoc + install_rdoc + end + + FileUtils.mkdir_p @doc_dir unless File.exist?(@doc_dir) + end + + # Load the RDoc documentation generator library. + def load_rdoc + if File.exist?(@doc_dir) && !File.writable?(@doc_dir) + Gem::FilePermissionError.new(@doc_dir) + end + FileUtils.mkdir_p @doc_dir unless File.exist?(@doc_dir) + begin + require 'rdoc/rdoc' + rescue LoadError => e + raise DocumentError, + "ERROR: RDoc documentation generator not installed!" + end + end + + def install_rdoc + say "Installing RDoc documentation for #{@spec.full_name}..." + run_rdoc '--op', File.join(@doc_dir, 'rdoc') + end + + def install_ri + say "Installing ri documentation for #{@spec.full_name}..." + run_rdoc '--ri', '--op', File.join(@doc_dir, 'ri') + end + + def run_rdoc(*args) + args << @spec.rdoc_options + args << DocManager.configured_args + args << '--quiet' + args << @spec.require_paths.clone + args << @spec.extra_rdoc_files + args.flatten! + + r = RDoc::RDoc.new + + old_pwd = Dir.pwd + Dir.chdir(@spec.full_gem_path) + begin + r.document args + rescue Errno::EACCES => e + dirname = File.dirname e.message.split("-")[1].strip + raise Gem::FilePermissionError.new(dirname) + rescue RuntimeError => ex + STDERR.puts "While generating documentation for #{@spec.full_name}" + STDERR.puts "... MESSAGE: #{ex}" + STDERR.puts "... RDOC args: #{args.join(' ')}" + STDERR.puts ex.backtrace if Gem.configuration.backtrace + STDERR.puts "(continuing with the rest of the installation)" + ensure + Dir.chdir(old_pwd) + end + end + + def uninstall_doc + doc_dir = File.join(@spec.installation_path, "doc", @spec.full_name) + FileUtils.rm_rf doc_dir + ri_dir = File.join(@spec.installation_path, "ri", @spec.full_name) + FileUtils.rm_rf ri_dir + end + + class << self + def configured_args + @configured_args ||= [] + end + + def configured_args=(args) + case args + when Array + @configured_args = args + when String + @configured_args = args.split + end + end + end + + end +end diff --git a/amarok/src/mediadevice/daap/mongrel/lib/rubygems/format.rb b/amarok/src/mediadevice/daap/mongrel/lib/rubygems/format.rb new file mode 100644 index 00000000..897c0a26 --- /dev/null +++ b/amarok/src/mediadevice/daap/mongrel/lib/rubygems/format.rb @@ -0,0 +1,71 @@ +#-- +# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others. +# All rights reserved. +# See LICENSE.txt for permissions. +#++ + +require 'rubygems/package' + +module Gem + + ## + # The format class knows the guts of the RubyGem .gem file format + # and provides the capability to read gem files + # + class Format + attr_accessor :spec, :file_entries, :gem_path + extend Gem::UserInteraction + + ## + # Constructs an instance of a Format object, representing the gem's + # data structure. + # + # gem:: [String] The file name of the gem + # + def initialize(gem_path) + @gem_path = gem_path + end + + ## + # Reads the named gem file and returns a Format object, representing + # the data from the gem file + # + # file_path:: [String] Path to the gem file + # + def self.from_file_by_path(file_path, security_policy = nil) + unless File.exist?(file_path) + raise Gem::Exception, "Cannot load gem at [#{file_path}]" + end + require 'fileutils' + # check for old version gem + if File.read(file_path, 20).include?("MD5SUM =") + #alert_warning "Gem #{file_path} is in old format." + require 'rubygems/old_format' + return OldFormat.from_file_by_path(file_path) + else + f = File.open(file_path, 'rb') + return from_io(f, file_path, security_policy) + end + end + + ## + # Reads a gem from an io stream and returns a Format object, representing + # the data from the gem file + # + # io:: [IO] Stream from which to read the gem + # + def self.from_io(io, gem_path="(io)", security_policy = nil) + format = self.new(gem_path) + Package.open_from_io(io, 'r', security_policy) do |pkg| + format.spec = pkg.metadata + format.file_entries = [] + pkg.each do |entry| + format.file_entries << [{"size", entry.size, "mode", entry.mode, + "path", entry.full_name}, entry.read] + end + end + format + end + + end +end diff --git a/amarok/src/mediadevice/daap/mongrel/lib/rubygems/gem_commands.rb b/amarok/src/mediadevice/daap/mongrel/lib/rubygems/gem_commands.rb new file mode 100644 index 00000000..ec1d33c0 --- /dev/null +++ b/amarok/src/mediadevice/daap/mongrel/lib/rubygems/gem_commands.rb @@ -0,0 +1,1441 @@ +#-- +# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others. +# All rights reserved. +# See LICENSE.txt for permissions. +#++ + + +module Gem + + class CommandLineError < Gem::Exception; end + + #################################################################### + # The following mixin methods aid in the retrieving of information + # from the command line. + # + module CommandAids + + # Get the single gem name from the command line. Fail if there is + # no gem name or if there is more than one gem name given. + def get_one_gem_name + args = options[:args] + if args.nil? or args.empty? + fail Gem::CommandLineError, + "Please specify a gem name on the command line (e.g. gem build GEMNAME)" + end + if args.size > 1 + fail Gem::CommandLineError, + "Too many gem names (#{args.join(', ')}); please specify only one" + end + args.first + end + + # Get a single optional argument from the command line. If more + # than one argument is given, return only the first. Return nil if + # none are given. + def get_one_optional_argument + args = options[:args] || [] + args.first + end + + # True if +long+ begins with the characters from +short+. + def begins?(long, short) + return false if short.nil? + long[0, short.length] == short + end + end + + #################################################################### + # Mixin methods for handling the local/remote command line options. + # + module LocalRemoteOptions + + # Add the local/remote options to the command line parser. + def add_local_remote_options + add_option('-l', '--local', + 'Restrict operations to the LOCAL domain (default)') do + |value, options| + options[:domain] = :local + end + + add_option('-r', '--remote', + 'Restrict operations to the REMOTE domain') do + |value, options| + options[:domain] = :remote + end + + add_option('-b', '--both', + 'Allow LOCAL and REMOTE operations') do + |value, options| + options[:domain] = :both + end + end + + # Is local fetching enabled? + def local? + options[:domain] == :local || options[:domain] == :both + end + + # Is remote fetching enabled? + def remote? + options[:domain] == :remote || options[:domain] == :both + end + end + + #################################################################### + # Mixin methods and OptionParser options specific to the gem install + # command. + # + module InstallUpdateOptions + + # Add the install/update options to the option parser. + def add_install_update_options + add_option('-i', '--install-dir DIR', + 'Gem repository directory to get installed gems.') do + |value, options| + options[:install_dir] = File.expand_path(value) + end + + add_option('-d', '--[no-]rdoc', + 'Generate RDoc documentation for the gem on install') do + |value, options| + options[:generate_rdoc] = value + end + + add_option('--[no-]ri', + 'Generate RI documentation for the gem on install') do + |value, options| + options[:generate_ri] = value + end + + add_option('-f', '--[no-]force', + 'Force gem to install, bypassing dependency checks') do + |value, options| + options[:force] = value + end + + add_option('-t', '--[no-]test', + 'Run unit tests prior to installation') do + |value, options| + options[:test] = value + end + + add_option('-w', '--[no-]wrappers', + 'Use bin wrappers for executables', + 'Not available on dosish platforms') do + |value, options| + options[:wrappers] = value + end + + add_option('-P', '--trust-policy POLICY', + 'Specify gem trust policy.') do + |value, options| + options[:security_policy] = value + end + + add_option('--ignore-dependencies', + 'Do not install any required dependent gems') do + |value, options| + options[:ignore_dependencies] = value + end + + add_option('-y', '--include-dependencies', + 'Unconditionally install the required dependent gems') do + |value, options| + options[:include_dependencies] = value + end + end + + # Default options for the gem install command. + def install_update_defaults_str + '--rdoc --no-force --no-test --wrappers' + end + end + + #################################################################### + # Mixin methods for the version command. + # + module VersionOption + + # Add the options to the option parser. + def add_version_option(taskname) + add_option('-v', '--version VERSION', + "Specify version of gem to #{taskname}") do + |value, options| + options[:version] = value + end + end + end + + #################################################################### + # Gem install command. + # + class InstallCommand < Command + include CommandAids + include VersionOption + include LocalRemoteOptions + include InstallUpdateOptions + + def initialize + super( + 'install', + 'Install a gem into the local repository', + { + :domain => :both, + :generate_rdoc => true, + :generate_ri => true, + :force => false, + :test => false, + :wrappers => true, + :version => "> 0", + :install_dir => Gem.dir, + :security_policy => nil, + }) + add_version_option('install') + add_local_remote_options + add_install_update_options + end + + def usage + "#{program_name} GEMNAME [options] + or: #{program_name} GEMNAME [options] -- --build-flags" + end + + def arguments + "GEMNAME name of gem to install" + end + + def defaults_str + "--both --version '> 0' --rdoc --ri --no-force --no-test\n" + + "--install-dir #{Gem.dir}" + end + + def execute + ENV['GEM_PATH'] = options[:install_dir] + if(options[:args].empty?) + fail Gem::CommandLineError, + "Please specify a gem name on the command line (e.g. gem build GEMNAME)" + end + options[:args].each do |gem_name| + if local? + begin + entries = [] + if(File.exist?(gem_name) && !File.directory?(gem_name)) + entries << gem_name + else + filepattern = gem_name + "*.gem" + entries = Dir[filepattern] + end + unless entries.size > 0 + if options[:domain] == :local + alert_error "Local gem file not found: #{filepattern}" + end + else + result = Gem::Installer.new(entries.last, options).install( + options[:force], + options[:install_dir]) + installed_gems = [result].flatten + say "Successfully installed #{installed_gems[0].name}, " + + "version #{installed_gems[0].version}" if installed_gems + end + rescue LocalInstallationError => e + say " -> Local installation can't proceed: #{e.message}" + rescue Gem::LoadError => e + say " -> Local installation can't proceed due to LoadError: #{e.message}" + rescue => e + # TODO: Fix this handle to allow the error to propagate to + # the top level handler. Examine the other errors as + # well. This implementation here looks suspicious to me -- + # JimWeirich (4/Jan/05) + alert_error "Error installing gem #{gem_name}[.gem]: #{e.message}" + return + end + end + + if remote? && installed_gems.nil? + installer = Gem::RemoteInstaller.new(options) + installed_gems = installer.install( + gem_name, + options[:version], + options[:force], + options[:install_dir]) + if installed_gems + installed_gems.compact! + installed_gems.each do |spec| + say "Successfully installed #{spec.full_name}" + end + end + end + + unless installed_gems + alert_error "Could not install a local " + + "or remote copy of the gem: #{gem_name}" + terminate_interaction(1) + end + + # NOTE: *All* of the RI documents must be generated first. + # For some reason, RI docs cannot be generated after any RDoc + # documents are generated. + + if options[:generate_ri] + installed_gems.each do |gem| + Gem::DocManager.new(gem, options[:rdoc_args]).generate_ri + end + end + + if options[:generate_rdoc] + installed_gems.each do |gem| + Gem::DocManager.new(gem, options[:rdoc_args]).generate_rdoc + end + end + + if options[:test] + installed_gems.each do |spec| + gem_spec = Gem::SourceIndex.from_installed_gems.search(spec.name, spec.version.version).first + result = Gem::Validator.new.unit_test(gem_spec) + unless result.passed? + unless ask_yes_no("...keep Gem?", true) then + Gem::Uninstaller.new(spec.name, spec.version.version).uninstall + end + end + end + end + end + end + + end + + #################################################################### + class UninstallCommand < Command + include VersionOption + include CommandAids + + def initialize + super('uninstall', 'Uninstall a gem from the local repository', {:version=>"> 0"}) + add_option('-a', '--[no-]all', + 'Uninstall all matching versions' + ) do |value, options| + options[:all] = value + end + add_option('-i', '--[no-]ignore-dependencies', + 'Ignore dependency requirements while uninstalling' + ) do |value, options| + options[:ignore] = value + end + add_option('-x', '--[no-]executables', + 'Uninstall applicable executables without confirmation' + ) do |value, options| + options[:executables] = value + end + add_version_option('uninstall') + end + + def defaults_str + "--version '> 0' --no-force" + end + + def usage + "#{program_name} GEMNAME" + end + + def arguments + "GEMNAME name of gem to uninstall" + end + + def execute + gem_name = get_one_gem_name + Gem::Uninstaller.new(gem_name, options).uninstall + end + end + + class CertCommand < Command + include CommandAids + + def initialize + super( + 'cert', + 'Adjust RubyGems certificate settings', + { + }) + + add_option('-a', '--add CERT', 'Add a trusted certificate.') do |value, options| + cert = OpenSSL::X509::Certificate.new(File.read(value)) + Gem::Security.add_trusted_cert(cert) + puts "Added #{cert.subject.to_s}" + end + + add_option('-l', '--list', 'List trusted certificates.') do |value, options| + glob_str = File::join(Gem::Security::OPT[:trust_dir], '*.pem') + Dir::glob(glob_str) do |path| + cert = OpenSSL::X509::Certificate.new(File.read(path)) + # this could proably be formatted more gracefully + puts cert.subject.to_s + end + end + + add_option('-r', '--remove STRING', 'Remove trusted certificates containing STRING.') do |value, options| + trust_dir = Gem::Security::OPT[:trust_dir] + glob_str = File::join(trust_dir, '*.pem') + + Dir::glob(glob_str) do |path| + cert = OpenSSL::X509::Certificate.new(File.read(path)) + if cert.subject.to_s.downcase.index(value) + puts "Removing '#{cert.subject.to_s}'" + File.unlink(path) + end + end + end + + add_option('-b', '--build EMAIL_ADDR', + 'Build private key and self-signed certificate for EMAIL_ADDR.' + ) do |value, options| + vals = Gem::Security::build_self_signed_cert(value) + File::chmod(0600, vals[:key_path]) + puts "Public Cert: #{vals[:cert_path]}", + "Private Key: #{vals[:key_path]}", + "Don't forget to move the key file to somewhere private..." + end + + add_option('-C', '--certificate CERT', + 'Certificate for --sign command.' + ) do |value, options| + cert = OpenSSL::X509::Certificate.new(File.read(value)) + Gem::Security::OPT[:issuer_cert] = cert + end + + add_option('-K', '--private-key KEY', + 'Private key for --sign command.' + ) do |value, options| + key = OpenSSL::PKey::RSA.new(File.read(value)) + Gem::Security::OPT[:issuer_key] = key + end + + + add_option('-s', '--sign NEWCERT', + 'Sign a certificate with my key and certificate.' + ) do |value, options| + cert = OpenSSL::X509::Certificate.new(File.read(value)) + my_cert = Gem::Security::OPT[:issuer_cert] + my_key = Gem::Security::OPT[:issuer_key] + cert = Gem::Security.sign_cert(cert, my_key, my_cert) + File::open(value, 'wb') { |file| file.write(cert.to_pem) } + end + + end + + def execute + end + end + + #################################################################### + class DependencyCommand < Command + include VersionOption + include CommandAids + + def initialize + super('dependency', + 'Show the dependencies of an installed gem', + {:version=>"> 0"}) + add_version_option('uninstall') + add_option('-r', '--[no-]reverse-dependencies', + 'Include reverse dependencies in the output' + ) do |value, options| + options[:reverse_dependencies] = value + end + add_option('-p', '--pipe', "Pipe Format (name --version ver)") do |value, options| + options[:pipe_format] = value + end + end + + def defaults_str + "--version '> 0' --no-reverse" + end + + def usage + "#{program_name} GEMNAME" + end + + def arguments + "GEMNAME name of gems to show" + end + + def execute + specs = {} + srcindex = SourceIndex.from_installed_gems + options[:args] << '.' if options[:args].empty? + options[:args].each do |name| + speclist = srcindex.search(name, options[:version]) + if speclist.empty? + say "No match found for #{name} (#{options[:version]})" + else + speclist.each do |spec| + specs[spec.full_name] = spec + end + end + end + reverse = Hash.new { |h, k| h[k] = [] } + if options[:reverse_dependencies] + specs.values.each do |spec| + reverse[spec.full_name] = find_reverse_dependencies(spec, srcindex) + end + end + if options[:pipe_format] + specs.values.sort.each do |spec| + unless spec.dependencies.empty? + spec.dependencies.each do |dep| + puts "#{dep.name} --version '#{dep.version_requirements}'" + end + end + end + else + response = '' + specs.values.sort.each do |spec| + response << print_dependencies(spec) + unless reverse[spec.full_name].empty? + response << " Used by\n" + reverse[spec.full_name].each do |sp, dep| + response << " #{sp} (#{dep})\n" + end + end + response << "\n" + end + say response + end + end + + def print_dependencies(spec, level = 0) + response = '' + response << ' ' * level + "Gem #{spec.full_name}\n" + unless spec.dependencies.empty? +# response << ' ' * level + " Requires\n" + spec.dependencies.each do |dep| + response << ' ' * level + " #{dep}\n" + end + end + response + end + + # Retuns list of [specification, dep] that are satisfied by spec. + def find_reverse_dependencies(spec, srcindex) + result = [] + srcindex.each do |name, sp| + sp.dependencies.each do |dep| + if spec.name == dep.name && + dep.version_requirements.satisfied_by?(spec.version) + result << [sp.full_name, dep] + end + end + end + result + end + + end + + #################################################################### + class CheckCommand < Command + include CommandAids + + def initialize + super('check', 'Check installed gems', {:verify => false, :alien => false}) + add_option('-v', '--verify FILE', 'Verify gem file against its internal checksum') do |value, options| + options[:verify] = value + end + add_option('-a', '--alien', "Report 'unmanaged' or rogue files in the gem repository") do |value, options| + options[:alien] = true + end + add_option('-t', '--test', "Run unit tests for gem") do |value, options| + options[:test] = true + end + add_option('-V', '--version', "Specify version for which to run unit tests") do |value, options| + options[:version] = value + end + end + + def execute + if options[:test] + version = options[:version] || "> 0.0.0" + gem_spec = Gem::SourceIndex.from_installed_gems.search(get_one_gem_name, version).first + Gem::Validator.new.unit_test(gem_spec) + end + if options[:alien] + say "Performing the 'alien' operation" + Gem::Validator.new.alien.each do |key, val| + if(val.size > 0) + say "#{key} has #{val.size} problems" + val.each do |error_entry| + say "\t#{error_entry.path}:" + say "\t#{error_entry.problem}" + say + end + else + say "#{key} is error-free" + end + say + end + end + if options[:verify] + gem_name = options[:verify] + unless gem_name + alert_error "Must specify a .gem file with --verify NAME" + return + end + unless File.exist?(gem_name) + alert_error "Unknown file: #{gem_name}." + return + end + say "Verifying gem: '#{gem_name}'" + begin + Gem::Validator.new.verify_gem_file(gem_name) + rescue Exception => e + alert_error "#{gem_name} is invalid." + end + end + end + + end # class + + #################################################################### + class BuildCommand < Command + include CommandAids + + def initialize + super('build', 'Build a gem from a gemspec') + end + + def usage + "#{program_name} GEMSPEC_FILE" + end + + def arguments + "GEMSPEC_FILE name of gemspec file used to build the gem" + end + + def execute + gemspec = get_one_gem_name + if File.exist?(gemspec) + specs = load_gemspecs(gemspec) + specs.each do |spec| + Gem::Builder.new(spec).build + end + return + else + alert_error "Gemspec file not found: #{gemspec}" + end + end + + def load_gemspecs(filename) + if yaml?(filename) + require 'yaml' + result = [] + open(filename) do |f| + begin + while spec = Gem::Specification.from_yaml(f) + result << spec + end + rescue EndOfYAMLException => e + # OK + end + end + else + result = [Gem::Specification.load(filename)] + end + result + end + + def yaml?(filename) + line = open(filename) { |f| line = f.gets } + result = line =~ %r{^--- *!ruby/object:Gem::Specification} + result + end + + end + + #################################################################### + class QueryCommand < Command + include LocalRemoteOptions + + def initialize(name='query', summary='Query gem information in local or remote repositories') + super(name, + summary, + {:name=>/.*/, :domain=>:local, :details=>false} + ) + add_option('-n', '--name-matches REGEXP', 'Name of gem(s) to query on matches the provided REGEXP') do |value, options| + options[:name] = /#{value}/i + end + add_option('-d', '--[no-]details', 'Display detailed information of gem(s)') do |value, options| + options[:details] = value + end + add_local_remote_options + end + + def defaults_str + "--local --name-matches '.*' --no-details" + end + + def execute + if local? + say + say "*** LOCAL GEMS ***" + output_query_results(Gem::cache.search(options[:name])) + end + if remote? + say + say "*** REMOTE GEMS ***" + output_query_results(Gem::RemoteInstaller.new(options).search(options[:name])) + end + end + + private + + def output_query_results(gemspecs) + gem_list_with_version = {} + gemspecs.flatten.each do |gemspec| + gem_list_with_version[gemspec.name] ||= [] + gem_list_with_version[gemspec.name] << gemspec + end + + gem_list_with_version = gem_list_with_version.sort do |first, second| + first[0].downcase <=> second[0].downcase + end + gem_list_with_version.each do |gem_name, list_of_matching| + say + list_of_matching = list_of_matching.sort_by { |x| x.version }.reverse + seen_versions = [] + list_of_matching.delete_if do |item| + if(seen_versions.member?(item.version)) + true + else + seen_versions << item.version + false + end + end + say "#{gem_name} (#{list_of_matching.map{|gem| gem.version.to_s}.join(", ")})" + say format_text(list_of_matching[0].summary, 68, 4) + end + end + + ## + # Used for wrapping and indenting text + # + def format_text(text, wrap, indent=0) + result = [] + pattern = Regexp.new("^(.{0,#{wrap}})[ \n]") + work = text.dup + while work.length > wrap + if work =~ pattern + result << $1 + work.slice!(0, $&.length) + else + result << work.slice!(0, wrap) + end + end + result << work if work.length.nonzero? + result.join("\n").gsub(/^/, " " * indent) + end + end + + #################################################################### + class ListCommand < QueryCommand + include CommandAids + + def initialize + super( + 'list', + 'Display all gems whose name starts with STRING' + ) + remove_option('--name-matches') + end + + def defaults_str + "--local --no-details" + end + + def usage + "#{program_name} [STRING]" + end + + def arguments + "STRING start of gem name to look for" + end + + def execute + string = get_one_optional_argument || '' + options[:name] = /^#{string}/i + super + end + end + + #################################################################### + class SearchCommand < QueryCommand + include CommandAids + + def initialize + super( + 'search', + 'Display all gems whose name contains STRING' + ) + remove_option('--name-matches') + end + + def defaults_str + "--local --no-details" + end + + def usage + "#{program_name} [STRING]" + end + + def arguments + "STRING fragment of gem name to look for" + end + + def execute + string = get_one_optional_argument + options[:name] = /#{string}/i + super + end + end + + #################################################################### + class UpdateCommand < Command + include InstallUpdateOptions + + def initialize + super( + 'update', + 'Update the named gem (or all installed gems) in the local repository', + { + :generate_rdoc => true, + :generate_ri => true, + :force => false, + :test => false, + :install_dir => Gem.dir + }) + add_install_update_options + add_option('--system', + 'Update the RubyGems system software') do |value, options| + options[:system] = value + end + end + + def defaults_str + "--rdoc --ri --no-force --no-test\n" + + "--install-dir #{Gem.dir}" + end + + def arguments + "GEMNAME(s) name of gem(s) to update" + end + + + def execute + if options[:system] + say "Updating RubyGems..." + if ! options[:args].empty? + fail "No gem names are allowed with the --system option" + end + options[:args] = ["rubygems-update"] + else + say "Updating installed gems..." + end + hig = highest_installed_gems = {} + Gem::SourceIndex.from_installed_gems.each do |name, spec| + if hig[spec.name].nil? or hig[spec.name].version < spec.version + hig[spec.name] = spec + end + end + remote_gemspecs = Gem::RemoteInstaller.new(options).search(//) + # For some reason, this is an array of arrays. The actual list + # of specifications is the first and only element. If there + # were more remote sources, perhaps there would be more. + remote_gemspecs = remote_gemspecs.flatten + gems_to_update = if(options[:args].empty?) then + which_to_update(highest_installed_gems, remote_gemspecs) + else + options[:args] + end + options[:domain] = :remote # install from remote source + install_command = command_manager['install'] + gems_to_update.uniq.sort.each do |name| + say "Attempting remote update of #{name}" + options[:args] = [name] + install_command.merge_options(options) + install_command.execute + end + if gems_to_update.include?("rubygems-update") + latest_ruby_gem = remote_gemspecs.select { |s| + s.name == 'rubygems-update' + }.sort_by { |s| + s.version + }.last + say "Updating version of RubyGems to #{latest_ruby_gem.version}" + do_rubygems_update(latest_ruby_gem.version.to_s) + end + if(options[:system]) then + say "RubyGems system software updated" + else + say "Gems: [#{gems_to_update.uniq.sort.collect{|g| g.to_s}.join(', ')}] updated" + end + end + + def do_rubygems_update(version_string) + update_dir = File.join(Gem.dir, "gems", "rubygems-update-#{version_string}") + Dir.chdir(update_dir) do + puts "Installing RubyGems #{version_string}" + system "#{Gem.ruby} setup.rb" + end + end + + def which_to_update(highest_installed_gems, remote_gemspecs) + result = [] + highest_installed_gems.each do |l_name, l_spec| + highest_remote_gem = + remote_gemspecs.select { |spec| spec.name == l_name }. + sort_by { |spec| spec.version }. + last + if highest_remote_gem and l_spec.version < highest_remote_gem.version + result << l_name + end + end + result + end + end + + #################################################################### + class CleanupCommand < Command + def initialize + super( + 'cleanup', + 'Cleanup old versions of installed gems in the local repository', + { + :force => false, + :test => false, + :install_dir => Gem.dir + }) + add_option('-d', '--dryrun', "") do |value, options| + options[:dryrun] = true + end + end + + def defaults_str + "--no-dryrun" + end + + def arguments + "GEMNAME(s) name of gem(s) to cleanup" + end + + def execute + say "Cleaning up installed gems..." + srcindex = Gem::SourceIndex.from_installed_gems + primary_gems = {} + srcindex.each do |name, spec| + if primary_gems[spec.name].nil? or primary_gems[spec.name].version < spec.version + primary_gems[spec.name] = spec + end + end + gems_to_cleanup = [] + if ! options[:args].empty? + options[:args].each do |gem_name| + specs = Gem.cache.search(/^#{gem_name}$/i) + specs.each do |spec| + gems_to_cleanup << spec + end + end + else + srcindex.each do |name, spec| + gems_to_cleanup << spec + end + end + gems_to_cleanup = gems_to_cleanup.select { |spec| + primary_gems[spec.name].version != spec.version + } + uninstall_command = command_manager['uninstall'] + deplist = DependencyList.new + gems_to_cleanup.uniq.each do |spec| deplist.add(spec) end + deplist.dependency_order.each do |spec| + if options[:dryrun] + say "Dry Run Mode: Would uninstall #{spec.full_name}" + else + say "Attempting uninstall on #{spec.full_name}" + options[:args] = [spec.name] + options[:version] = "= #{spec.version}" + options[:executables] = true + uninstall_command.merge_options(options) + begin + uninstall_command.execute + rescue Gem::DependencyRemovalException => ex + say "Unable to uninstall #{spec.full_name} ... continuing with remaining gems" + end + end + end + say "Clean Up Complete" + end + end + + #################################################################### + class RDocCommand < Command + include VersionOption + include CommandAids + + def initialize + super('rdoc', + 'Generates RDoc for pre-installed gems', + { + :version => "> 0.0.0", + :include_rdoc => true, + :include_ri => true, + }) + add_option('--all', + 'Generate RDoc/RI documentation for all installed gems' + ) do |value, options| + options[:all] = value + end + add_option('--[no-]rdoc', + 'Include RDoc generated documents') do + |value, options| + options[:include_rdoc] = value + end + add_option('--[no-]ri', + 'Include RI generated documents' + ) do |value, options| + options[:include_ri] = value + end + add_version_option('rdoc') + end + + def defaults_str + "--version '> 0.0.0' --rdoc --ri" + end + + def usage + "#{program_name} [args]" + end + + def arguments + "GEMNAME The gem to generate RDoc for (unless --all)" + end + + def execute + if options[:all] + specs = Gem::SourceIndex.from_installed_gems.collect { |name, spec| + spec + } + else + gem_name = get_one_gem_name + specs = Gem::SourceIndex.from_installed_gems.search( + gem_name, options[:version]) + end + + if specs.empty? + fail "Failed to find gem #{gem_name} to generate RDoc for #{options[:version]}" + end + if options[:include_ri] + specs.each do |spec| + Gem::DocManager.new(spec).generate_ri + end + end + if options[:include_rdoc] + specs.each do |spec| + Gem::DocManager.new(spec).generate_rdoc + end + end + + true + end + end + + #################################################################### + class EnvironmentCommand < Command + include CommandAids + + def initialize + super('environment', 'Display RubyGems environmental information') + end + + def usage + "#{program_name} [args]" + end + + def arguments + args = <<-EOF + packageversion display the package version + gemdir display the path where gems are installed + gempath display path used to search for gems + version display the gem format version + remotesources display the remote gem servers + display everything + EOF + return args.gsub(/^\s+/, '') + end + + def execute + out = '' + arg = options[:args][0] + if begins?("packageversion", arg) + out = Gem::RubyGemsPackageVersion.to_s + elsif begins?("version", arg) + out = Gem::RubyGemsVersion.to_s + elsif begins?("gemdir", arg) + out = Gem.dir + elsif begins?("gempath", arg) + Gem.path.collect { |p| out << "#{p}\n" } + elsif begins?("remotesources", arg) + Gem::RemoteInstaller.new.sources.collect do |s| + out << "#{s}\n" + end + elsif arg + fail Gem::CommandLineError, "Unknown enviroment option [#{arg}]" + else + out = "Rubygems Environment:\n" + out << " - VERSION: #{Gem::RubyGemsVersion} (#{Gem::RubyGemsPackageVersion})\n" + out << " - INSTALLATION DIRECTORY: #{Gem.dir}\n" + out << " - GEM PATH:\n" + Gem.path.collect { |p| out << " - #{p}\n" } + out << " - REMOTE SOURCES:\n" + Gem::RemoteInstaller.new.sources.collect do |s| + out << " - #{s}\n" + end + end + say out + true + end + end + + #################################################################### + class SpecificationCommand < Command + include VersionOption + include LocalRemoteOptions + include CommandAids + + def initialize + super('specification', 'Display gem specification (in yaml)', {:domain=>:local, :version=>"> 0.0.0"}) + add_version_option('examine') + add_local_remote_options + add_option('--all', 'Output specifications for all versions of the gem') do + options[:all] = true + end + end + + def defaults_str + "--local --version '(latest)'" + end + + def usage + "#{program_name} GEMFILE" + end + + def arguments + "GEMFILE Name of a .gem file to examine" + end + + def execute + if local? + gem = get_one_gem_name + gem_specs = Gem::SourceIndex.from_installed_gems.search(gem, options[:version]) + unless gem_specs.empty? + require 'yaml' + output = lambda { |spec| say spec.to_yaml; say "\n" } + if options[:all] + gem_specs.each(&output) + else + spec = gem_specs.sort_by { |spec| spec.version }.last + output[spec] + end + else + alert_error "Unknown gem #{gem}" + end + end + + if remote? + say "(Remote 'info' operation is not yet implemented.)" + # NOTE: when we do implement remote info, make sure we don't + # duplicate huge swabs of local data. If it's the same, just + # say it's the same. + end + end + end + + #################################################################### + class UnpackCommand < Command + include VersionOption + include CommandAids + + def initialize + super( + 'unpack', + 'Unpack an installed gem to the current directory', + { :version => '> 0' } + ) + add_version_option('unpack') + end + + def defaults_str + "--version '> 0'" + end + + def usage + "#{program_name} GEMNAME" + end + + def arguments + "GEMNAME Name of the gem to unpack" + end + + # TODO: allow, e.g., 'gem unpack rake-0.3.1'. Find a general + # solution for this, so that it works for uninstall as well. (And + # check other commands at the same time.) + def execute + gemname = get_one_gem_name + path = get_path(gemname, options[:version]) + if path + require 'fileutils' + target_dir = File.basename(path).sub(/\.gem$/, '') + FileUtils.mkdir_p target_dir + Installer.new(path).unpack(target_dir) + say "Unpacked gem: '#{target_dir}'" + else + alert_error "Gem '#{gemname}' not installed." + end + end + + # Return the full path to the cached gem file matching the given + # name and version requirement. Returns 'nil' if no match. + # Example: + # + # get_path('rake', '> 0.4') # -> '/usr/lib/ruby/gems/1.8/cache/rake-0.4.2.gem' + # get_path('rake', '< 0.1') # -> nil + # get_path('rak') # -> nil (exact name required) + # + # TODO: This should be refactored so that it's a general service. + # I don't think any of our existing classes are the right place + # though. Just maybe 'Cache'? + # + # TODO: It just uses Gem.dir for now. What's an easy way to get + # the list of source directories? + # + def get_path(gemname, version_req) + return gemname if gemname =~ /\.gem$/i + specs = SourceIndex.from_installed_gems.search(gemname, version_req) + selected = specs.sort_by { |s| s.version }.last + return nil if selected.nil? + # We expect to find (basename).gem in the 'cache' directory. + # Furthermore, the name match must be exact (ignoring case). + if gemname =~ /^#{selected.name}$/i + filename = selected.full_name + '.gem' + return File.join(Gem.dir, 'cache', filename) + else + return nil + end + end + end + + #################################################################### + class HelpCommand < Command + include CommandAids + + def initialize + super('help', "Provide help on the 'gem' command") + end + + def usage + "#{program_name} ARGUMENT" + end + + def arguments + args = <<-EOF + commands List all 'gem' commands + examples Show examples of 'gem' usage + Show specific help for + EOF + return args.gsub(/^\s+/, '') + end + + def execute + arg = options[:args][0] + if begins?("commands", arg) + require 'stringio' + out = StringIO.new + out.puts "\nGEM commands are:\n\n" + desc_indent = command_manager.command_names.collect {|n| n.size}.max + 4 + format = " %-#{desc_indent}s %s\n" + command_manager.command_names.each do |cmd_name| + out.printf format, "#{cmd_name}", command_manager[cmd_name].summary + end + out.puts "\nFor help on a particular command, use 'gem help COMMAND'." + out.puts "\nCommands may be abbreviated, so long as they are unambiguous." + out.puts "e.g. 'gem i rake' is short for 'gem install rake'." + say out.string + elsif begins?("options", arg) + say Gem::HELP + elsif begins?("examples", arg) + say Gem::EXAMPLES + elsif options[:help] + command = command_manager[options[:help]] + if command + # help with provided command + command.invoke("--help") + else + alert_error "Unknown command #{options[:help]}. Try 'gem help commands'" + end + elsif arg + possibilities = command_manager.find_command_possibilities(arg.downcase) + if possibilities.size == 1 + command = command_manager[possibilities.first] + command.invoke("--help") + elsif possibilities.size > 1 + alert_warning "Ambiguous command #{arg} (#{possibilities.join(', ')})" + else + alert_warning "Unknown command #{arg}. Try gem help commands" + end + else + say Gem::HELP + end + end + end + + #################################################################### + class ContentsCommand < Command + include CommandAids + def initialize + super('contents','Display the contents of the installed gems', {:list => true, :specdirs => [] }) + add_option("-l","--list",'List the files inside a Gem') do |v,o| + o[:list] = true + end + + add_option("-V","--version","Specify version for gem to view") do |v,o| + o[:version] = v + end + + add_option('-s','--spec-dir a,b,c', Array, "Search for gems under specific paths") do |v,o| + o[:specdirs] = v + end + + add_option('-v','--verbose','Be verbose when showing status') do |v,o| + o[:verbose] = v + end + end + + def execute(io=STDOUT) + if options[:list] + version = options[:version] || "> 0.0.0" + gem = get_one_gem_name + + s = options[:specdirs].map do |i| + [i, File.join(i,"specifications")] + end.flatten + + if s.empty? + path_kind = "default gem paths" + system = true + else + path_kind = "specified path" + system = false + end + + si = Gem::SourceIndex.from_gems_in(*s) + + gem_spec = si.search(gem, version).first + unless gem_spec + io.puts "Unable to find gem '#{gem}' in #{path_kind}" + if options[:verbose] + io.puts "\nDirectories searched:" + if system + Gem.path.each do |p| + io.puts p + end + else + s.each do |p| + io.puts p + end + end + end + return + end + # show the list of files. + gem_spec.files.each do |f| + io.puts File.join(gem_spec.full_gem_path, f) + end + end + end + end + +end # module + +###################################################################### +# Documentation Constants +# +module Gem + + HELP = %{ + RubyGems is a sophisticated package manager for Ruby. This is a + basic help message containing pointers to more information. + + Usage: + gem -h/--help + gem -v/--version + gem command [arguments...] [options...] + + Examples: + gem install rake + gem list --local + gem build package.gemspec + gem help install + + Further help: + gem help commands list all 'gem' commands + gem help examples show some examples of usage + gem help show help on COMMAND + (e.g. 'gem help install') + Further information: + http://rubygems.rubyforge.org + }.gsub(/^ /, "") + + EXAMPLES = %{ + Some examples of 'gem' usage. + + * Install 'rake', either from local directory or remote server: + + gem install rake + + * Install 'rake', only from remote server: + + gem install rake --remote + + * Install 'rake' from remote server, and run unit tests, + and generate RDocs: + + gem install --remote rake --test --rdoc --ri + + * Install 'rake', but only version 0.3.1, even if dependencies + are not met, and into a specific directory: + + gem install rake --version 0.3.1 --force --install-dir $HOME/.gems + + * List local gems whose name begins with 'D': + + gem list D + + * List local and remote gems whose name contains 'log': + + gem search log --both + + * List only remote gems whose name contains 'log': + + gem search log --remote + + * Uninstall 'rake': + + gem uninstall rake + + * Create a gem: + + See http://rubygems.rubyforge.org/wiki/wiki.pl?CreateAGemInTenMinutes + + * See information about RubyGems: + + gem environment + + }.gsub(/^ /, "") + +end diff --git a/amarok/src/mediadevice/daap/mongrel/lib/rubygems/gem_openssl.rb b/amarok/src/mediadevice/daap/mongrel/lib/rubygems/gem_openssl.rb new file mode 100644 index 00000000..ac99f90d --- /dev/null +++ b/amarok/src/mediadevice/daap/mongrel/lib/rubygems/gem_openssl.rb @@ -0,0 +1,46 @@ +#-- +# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others. +# All rights reserved. +# See LICENSE.txt for permissions. +#++ + + +# Some system might not have OpenSSL installed, therefore the core +# library file openssl might not be available. We localize testing +# for the presence of OpenSSL in this file. + +module Gem + class << self + # Is SSL (used by the signing commands) available on this + # platform? + def ssl_available? + require 'rubygems/gem_openssl' + @ssl_available + end + + # Set the value of the ssl_avilable flag. + attr_writer :ssl_available + + # Ensure that SSL is available. Throw an exception if it is not. + def ensure_ssl_available + unless ssl_available? + fail Gem::Exception, "SSL is not installed on this system" + end + end + end +end + +begin + require 'openssl' + + # Reference a constant defined in the .rb portion of ssl (just to + # make sure that part is loaded too). + + dummy = OpenSSL::Digest::SHA1 + + Gem.ssl_available = true +rescue LoadError + Gem.ssl_available = false +rescue + Gem.ssl_available = false +end diff --git a/amarok/src/mediadevice/daap/mongrel/lib/rubygems/gem_runner.rb b/amarok/src/mediadevice/daap/mongrel/lib/rubygems/gem_runner.rb new file mode 100644 index 00000000..283c6787 --- /dev/null +++ b/amarok/src/mediadevice/daap/mongrel/lib/rubygems/gem_runner.rb @@ -0,0 +1,41 @@ +#-- +# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others. +# All rights reserved. +# See LICENSE.txt for permissions. +#++ + + +module Gem + + #################################################################### + # Run an instance of the gem program. + # + class GemRunner + + def initialize(options={}) + @cmd_manager_class = options[:command_manager] || Gem::CommandManager + @config_file_class = options[:config_file] || Gem::ConfigFile + @doc_manager_class = options[:doc_manager] || Gem::DocManager + end + + # Run the gem command with the following arguments. + def run(args) + do_configuration(args) + cmd = @cmd_manager_class.instance + cmd.command_names.each do |c| + Command.add_specific_extra_args c, Array(Gem.configuration[c]) + end + cmd.run(Gem.configuration.args) + end + + private + + def do_configuration(args) + Gem.configuration = @config_file_class.new(args) + Gem.use_paths(Gem.configuration[:gemhome], Gem.configuration[:gempath]) + Command.extra_args = Gem.configuration[:gem] + @doc_manager_class.configured_args = Gem.configuration[:rdoc] + end + + end # class +end # module diff --git a/amarok/src/mediadevice/daap/mongrel/lib/rubygems/incremental_fetcher.rb b/amarok/src/mediadevice/daap/mongrel/lib/rubygems/incremental_fetcher.rb new file mode 100644 index 00000000..19acd8a1 --- /dev/null +++ b/amarok/src/mediadevice/daap/mongrel/lib/rubygems/incremental_fetcher.rb @@ -0,0 +1,136 @@ +#-- +# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others. +# All rights reserved. +# See LICENSE.txt for permissions. +#++ + +module Gem + + #################################################################### + class IncrementalFetcher + include Gem::DefaultUserInteraction + + # Initialize an incremental source fetcher. + def initialize(source_uri, fetcher, cache_manager) + @source_uri = source_uri + @fetcher = fetcher + @manager = cache_manager + end + + # Return the size of the source index for the gem source. + def size + @fetcher.size + end + + # Return the source index for the gem source. + def source_index + entry = get_entry + if entry.nil? + entry = + @manager.cache_data[@source_uri] = + SourceInfoCacheEntry.new(SourceIndex.new,0) + end + update_cache(entry) if entry.size != remote_size + entry.source_index + end + + # Fetch the given path from the gem source. + def fetch_path(path) + @fetcher.fetch_path(path) + end + + private + + def get_entry + result = @manager.cache_data[@source_uri] + case result + when SourceInfoCacheEntry, nil + # do nothing ... everything is fine + when Hash + result = SourceInfoCacheEntry.new(result['source_index'], result['size']) + @manager.cache_data[@source_uri] = result + else + fail "Unexpected type (#{result.class}) for SourceInfoCache entry" + end + result + end + + # Return the size of the remote source index. Cache the value for later use. + def remote_size + @remote_size ||= @fetcher.size + end + + INCREMENTAL_THRESHHOLD = 50 + + # Update the cache entry. Use the incremental method unless + def update_cache(entry) + use_incremental = false + begin + index_list = get_quick_index + remove_extra(entry.source_index, index_list) + missing_list = find_missing(entry.source_index, index_list) + use_incremental = missing_list.size <= INCREMENTAL_THRESHHOLD + rescue OperationNotSupportedError => ex + use_incremental = false + end + if use_incremental + update_with_missing(entry.source_index, missing_list) + @manager.flush + else + si = @fetcher.source_index + entry.replace_source_index(si, remote_size) + end + end + + # Remove extra entries from the cached source index. + def remove_extra(source_index, spec_names) + dictionary = spec_names.inject({}) { |h, k| h[k] = true; h } + source_index.each do |name, spec| + if dictionary[name].nil? + source_index.remove_spec(name) + @manager.update + end + end + end + + # Make a list of full names for all the missing gemspecs. + def find_missing(source_index, spec_names) + spec_names.find_all { |full_name| + source_index.specification(full_name).nil? + } + end + + # Update the cached source index with the missing names. + def update_with_missing(source_index, missing_names) + progress = ui.progress_reporter(missing_names.size, + "Need to update #{missing_names.size} gems from #{@source_uri}") + missing_names.each do |spec_name| + begin + zipped_yaml = fetch_path("/quick/" + spec_name + ".gemspec.rz") + gemspec = YAML.load(unzip(zipped_yaml)) + source_index.add_spec(gemspec) + @manager.update + progress.updated(spec_name) + rescue RuntimeError => ex + ui.say "Failed to download spec for #{spec_name} from #{@source_uri}" + end + end + progress.done + progress.count + end + + # Get the quick index needed for incremental updates. + def get_quick_index + zipped_index = fetch_path("/quick/index.rz") + unzip(zipped_index).split("\n") + rescue ::Exception => ex + fail OperationNotSupportedError.new("No quick index found: " + ex.message) + end + + # Unzip the given string. + def unzip(string) + require 'zlib' + Zlib::Inflate.inflate(string) + end + end +end diff --git a/amarok/src/mediadevice/daap/mongrel/lib/rubygems/installer.rb b/amarok/src/mediadevice/daap/mongrel/lib/rubygems/installer.rb new file mode 100644 index 00000000..496383d2 --- /dev/null +++ b/amarok/src/mediadevice/daap/mongrel/lib/rubygems/installer.rb @@ -0,0 +1,613 @@ +#-- +# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others. +# All rights reserved. +# See LICENSE.txt for permissions. +#++ + +$TESTING = false unless defined? $TESTING + +require 'pathname' +require 'rbconfig' +require 'rubygems/format' +require 'rubygems/dependency_list' + +module Gem + + class DependencyRemovalException < Gem::Exception; end + + ## + # The installer class processes RubyGem .gem files and installs the + # files contained in the .gem into the Gem.path. + # + class Installer + + include UserInteraction + + ## + # Constructs an Installer instance + # + # gem:: [String] The file name of the gem + # + def initialize(gem, options={}) + @gem = gem + @options = options + end + + ## + # Installs the gem in the Gem.path. This will fail (unless + # force=true) if a Gem has a requirement on another Gem that is + # not installed. The installation will install in the following + # structure: + # + # Gem.path/ + # specifications/.gemspec #=> the extracted YAML gemspec + # gems//... #=> the extracted Gem files + # cache/.gem #=> a cached copy of the installed Gem + # + # force:: [default = false] if false will fail if a required Gem is not installed, + # or if the Ruby version is too low for the gem + # install_dir:: [default = Gem.dir] directory that Gem is to be installed in + # + # return:: [Gem::Specification] The specification for the newly installed Gem. + # + def install(force=false, install_dir=Gem.dir, ignore_this_parameter=false) + require 'fileutils' + + # if we're forcing the install, then disable security, _unless_ + # the security policy says that we only install singed gems + # (this includes Gem::Security::HighSecurity) + security_policy = @options[:security_policy] + security_policy = nil if force && security_policy && security_policy.only_signed != true + + format = Gem::Format.from_file_by_path(@gem, security_policy) + unless force + spec = format.spec + # Check the Ruby version. + if (rrv = spec.required_ruby_version) + unless rrv.satisfied_by?(Gem::Version.new(RUBY_VERSION)) + raise "#{spec.name} requires Ruby version #{rrv}" + end + end + unless @options[:ignore_dependencies] + spec.dependencies.each do |dep_gem| + ensure_dependency!(spec, dep_gem) + end + end + end + + raise Gem::FilePermissionError.new(install_dir) unless File.writable?(install_dir) + + # Build spec dir. + @directory = File.join(install_dir, "gems", format.spec.full_name).untaint + FileUtils.mkdir_p @directory + + extract_files(@directory, format) + generate_bin(format.spec, install_dir) + build_extensions(@directory, format.spec) + + # Build spec/cache/doc dir. + build_support_directories(install_dir) + + # Write the spec and cache files. + write_spec(format.spec, File.join(install_dir, "specifications")) + unless File.exist? File.join(install_dir, "cache", @gem.split(/\//).pop) + FileUtils.cp @gem, File.join(install_dir, "cache") + end + + puts format.spec.post_install_message unless format.spec.post_install_message.nil? + + format.spec.loaded_from = File.join(install_dir, 'specifications', format.spec.full_name+".gemspec") + return format.spec + end + + ## + # Ensure that the dependency is satisfied by the current + # installation of gem. If it is not, then fail (i.e. throw and + # exception). + # + # spec :: Gem::Specification + # dependency :: Gem::Dependency + def ensure_dependency!(spec, dependency) + raise "#{spec.name} requires #{dependency.name} #{dependency.version_requirements} " unless + installation_satisfies_dependency?(dependency) + end + + ## + # True if the current installed gems satisfy the given dependency. + # + # dependency :: Gem::Dependency + def installation_satisfies_dependency?(dependency) + current_index = SourceIndex.from_installed_gems + current_index.find_name(dependency.name, dependency.version_requirements).size > 0 + end + + ## + # Unpacks the gem into the given directory. + # + def unpack(directory) + format = Gem::Format.from_file_by_path(@gem, @options[:security_policy]) + extract_files(directory, format) + end + + ## + # Given a root gem directory, build supporting directories for gem + # if they do not already exist + def build_support_directories(install_dir) + unless File.exist? File.join(install_dir, "specifications") + FileUtils.mkdir_p File.join(install_dir, "specifications") + end + unless File.exist? File.join(install_dir, "cache") + FileUtils.mkdir_p File.join(install_dir, "cache") + end + unless File.exist? File.join(install_dir, "doc") + FileUtils.mkdir_p File.join(install_dir, "doc") + end + end + + ## + # Writes the .gemspec specification (in Ruby) to the supplied + # spec_path. + # + # spec:: [Gem::Specification] The Gem specification to output + # spec_path:: [String] The location (path) to write the gemspec to + # + def write_spec(spec, spec_path) + rubycode = spec.to_ruby + file_name = File.join(spec_path, spec.full_name+".gemspec").untaint + File.open(file_name, "w") do |file| + file.puts rubycode + end + end + + ## + # Creates windows .cmd files for easy running of commands + # + def generate_windows_script(bindir, filename) + if Config::CONFIG["arch"] =~ /dos|win32/i + script_name = filename + ".cmd" + File.open(File.join(bindir, File.basename(script_name)), "w") do |file| + file.puts "@#{Gem.ruby} \"#{File.join(bindir,filename)}\" %*" + end + end + end + + ## + # Determines the directory for binaries + # + def bindir(install_dir=Gem.dir) + if(install_dir == Gem.default_dir) + # mac framework support + if defined? RUBY_FRAMEWORK_VERSION + File.join(File.dirname(Config::CONFIG["sitedir"]), File.basename(Config::CONFIG["bindir"])) + else # generic install + Config::CONFIG['bindir'] + end + else + File.join(install_dir, "bin") + end + end + + def generate_bin(spec, install_dir=Gem.dir) + return unless spec.executables && ! spec.executables.empty? + + # If the user has asked for the gem to be installed in + # a directory that is the system gem directory, then + # use the system bin directory, else create (or use) a + # new bin dir under the install_dir. + bindir = bindir(install_dir) + + Dir.mkdir bindir unless File.exist? bindir + raise Gem::FilePermissionError.new(bindir) unless File.writable?(bindir) + + spec.executables.each do |filename| + if @options[:wrappers] then + generate_bin_script spec, filename, bindir, install_dir + else + generate_bin_symlink spec, filename, bindir, install_dir + end + end + end + + ## + # Creates the scripts to run the applications in the gem. + # + def generate_bin_script(spec, filename, bindir, install_dir) + File.open(File.join(bindir, File.basename(filename)), "w", 0755) do |file| + file.print app_script_text(spec, install_dir, filename) + end + generate_windows_script bindir, filename + end + + ## + # Creates the symlinks to run the applications in the gem. Moves + # the symlink if the gem being installed has a newer version. + # + def generate_bin_symlink(spec, filename, bindir, install_dir) + if Config::CONFIG["arch"] =~ /dos|win32/i then + warn "Unable to use symlinks on win32, installing wrapper" unless $TESTING # HACK + generate_bin_script spec, filename, bindir, install_dir + return + end + + src = File.join @directory, 'bin', filename + dst = File.join bindir, File.basename(filename) + + if File.exist? dst then + if File.symlink? dst then + link = File.readlink(dst).split File::SEPARATOR + cur_version = Gem::Version.create(link[-3].sub(/^.*-/, '')) + return if spec.version < cur_version + end + File.unlink dst + end + + File.symlink src, dst + end + + def shebang(spec, install_dir, bin_file_name) + path = File.join(install_dir, "gems", spec.full_name, spec.bindir, bin_file_name) + File.open(path, "rb") do |file| + first_line = file.readlines("\n").first + path_to_ruby = File.join(Config::CONFIG['bindir'], Config::CONFIG['ruby_install_name']) + if first_line =~ /^#!/ + # Preserve extra words on shebang line, like "-w". Thanks RPA. + shebang = first_line.sub(/\A\#!\s*\S*ruby\S*/, "#!" + path_to_ruby) + else + # Create a plain shebang line. + shebang = "#!" + path_to_ruby + end + return shebang.strip # Avoid nasty ^M issues. + end + end + + ## + # Returns the text for an application file. + # + def app_script_text(spec, install_dir, filename) + text = <<-TEXT +#{shebang(spec, install_dir, filename)} +# +# This file was generated by RubyGems. +# +# The application '#{spec.name}' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +require 'rubygems' +version = "> 0" +if ARGV.size > 0 && ARGV[0][0]==95 && ARGV[0][-1]==95 + if Gem::Version.correct?(ARGV[0][1..-2]) + version = ARGV[0][1..-2] + ARGV.shift + end +end +require_gem '#{spec.name}', version +load '#{filename}' +TEXT + text + end + + def build_extensions(directory, spec) + return unless spec.extensions.size > 0 + say "Building native extensions. This could take a while..." + start_dir = Dir.pwd + dest_path = File.join(directory, spec.require_paths[0]) + + results = [] + spec.extensions.each do |extension| + case extension + when /extconf/ then + builder = ExtExtConfBuilder + when /configure/ then + builder = ExtConfigureBuilder + when /rakefile/i then + builder = ExtRakeBuilder + else + builder = nil + results = ["No builder for extension '#{extension}'"] + end + + begin + err = false + Dir.chdir File.join(directory, File.dirname(extension)) + results = builder.build(extension, directory, dest_path) + rescue => ex + err = true + end + + say results.join("\n") + File.open('gem_make.out', 'wb') {|f| f.puts results.join("\n")} + + if err + raise "ERROR: Failed to build gem native extension.\nGem files will remain installed in #{directory} for inspection.\n #{results.join('\n')}\n\nResults logged to #{File.join(Dir.pwd, 'gem_make.out')}" + end + end + Dir.chdir start_dir + end + + ## + # Reads the YAML file index and then extracts each file + # into the supplied directory, building directories for the + # extracted files as needed. + # + # directory:: [String] The root directory to extract files into + # file:: [IO] The IO that contains the file data + # + def extract_files(directory, format) + require 'fileutils' + wd = Dir.getwd + Dir.chdir directory do + format.file_entries.each do |entry, file_data| + path = entry['path'].untaint + FileUtils.mkdir_p File.dirname(path) + File.open(path, "wb") do |out| + out.write file_data + end + end + end + end + end # class Installer + + + ## + # The Uninstaller class uninstalls a Gem + # + class Uninstaller + + include UserInteraction + + ## + # Constructs an Uninstaller instance + # + # gem:: [String] The Gem name to uninstall + # + def initialize(gem, options) + @gem = gem + @version = options[:version] || "> 0" + @force_executables = options[:executables] + @force_all = options[:all] + @force_ignore = options[:ignore] + end + + ## + # Performs the uninstall of the Gem. This removes the spec, the + # Gem directory, and the cached .gem file, + # + # Application stubs are (or should be) removed according to what + # is still installed. + # + # XXX: Application stubs refer to specific gem versions, which + # means things may get inconsistent after an uninstall + # (i.e. referring to a version that no longer exists). + # + def uninstall + require 'fileutils' + list = Gem.source_index.search(@gem, @version) + if list.empty? + raise "Unknown RubyGem: #{@gem} (#{@version})" + elsif list.size > 1 && @force_all + remove_all(list.dup) + remove_executables(list.last) + elsif list.size > 1 + say + gem_names = list.collect {|gem| gem.full_name} + ["All versions"] + gem_name, index = + choose_from_list("Select RubyGem to uninstall:", gem_names) + if index == list.size + remove_all(list.dup) + remove_executables(list.last) + elsif index >= 0 && index < list.size + to_remove = list[index] + remove(to_remove, list) + remove_executables(to_remove) + else + say "Error: must enter a number [1-#{list.size+1}]" + end + else + remove(list[0], list.dup) + remove_executables(list.last) + end + end + + ## + # Remove executables and batch files (windows only) for the gem as + # it is being installed + # + # gemspec::[Specification] the gem whose executables need to be removed. + # + def remove_executables(gemspec) + return if gemspec.nil? + if(gemspec.executables.size > 0) + raise Gem::FilePermissionError.new(Config::CONFIG['bindir']) unless + File.writable?(Config::CONFIG['bindir']) + list = Gem.source_index.search(gemspec.name).delete_if { |spec| + spec.version == gemspec.version + } + executables = gemspec.executables.clone + list.each do |spec| + spec.executables.each do |exe_name| + executables.delete(exe_name) + end + end + return if executables.size == 0 + answer = @force_executables || ask_yes_no( + "Remove executables and scripts for\n" + + "'#{gemspec.executables.join(", ")}' in addition to the gem?", + true) # " # appease ruby-mode - don't ask + unless answer + say "Executables and scripts will remain installed." + return + else + bindir = Config::CONFIG['bindir'] + gemspec.executables.each do |exe_name| + say "Removing #{exe_name}" + File.unlink(File.join(bindir, exe_name)) rescue nil + File.unlink(File.join(bindir, exe_name + ".cmd")) rescue nil + end + end + end + end + + # + # list:: the list of all gems to remove + # + # Warning: this method modifies the +list+ parameter. Once it has + # uninstalled a gem, it is removed from that list. + # + def remove_all(list) + list.dup.each { |gem| remove(gem, list) } + end + + # + # spec:: the spec of the gem to be uninstalled + # list:: the list of all such gems + # + # Warning: this method modifies the +list+ parameter. Once it has + # uninstalled a gem, it is removed from that list. + # + def remove(spec, list) + if( ! ok_to_remove?(spec)) then + raise DependencyRemovalException.new( + "Uninstallation aborted due to dependent gem(s)") + end + raise Gem::FilePermissionError.new(spec.installation_path) unless + File.writable?(spec.installation_path) + FileUtils.rm_rf spec.full_gem_path + FileUtils.rm_rf File.join( + spec.installation_path, + 'specifications', + "#{spec.full_name}.gemspec") + FileUtils.rm_rf File.join( + spec.installation_path, + 'cache', + "#{spec.full_name}.gem") + DocManager.new(spec).uninstall_doc + #remove_stub_files(spec, list - [spec]) + say "Successfully uninstalled #{spec.name} version #{spec.version}" + list.delete(spec) + end + + def ok_to_remove?(spec) + return true if @force_ignore + srcindex= Gem::SourceIndex.from_installed_gems + deplist = Gem::DependencyList.from_source_index(srcindex) + deplist.ok_to_remove?(spec.full_name) || + ask_if_ok(spec) + end + + def ask_if_ok(spec) + msg = [''] + msg << 'You have requested to uninstall the gem:' + msg << "\t#{spec.full_name}" + spec.dependent_gems.each do |gem,dep,satlist| + msg << + ("#{gem.name}-#{gem.version} depends on " + + "[#{dep.name} (#{dep.version_requirements})]") + end + msg << 'If you remove this gems, one or more dependencies will not be met.' + msg << 'Continue with Uninstall?' + return ask_yes_no(msg.join("\n"), true) + end + + private + + ## + # Remove application stub files. These are detected by the line + # # This file was generated by RubyGems. + # + # spec:: the spec of the gem that is being uninstalled + # other_specs:: any other installed specs for this gem + # (i.e. different versions) + # + # Both parameters are necessary to ensure that the correct files + # are uninstalled. It is assumed that +other_specs+ contains only + # *installed* gems, except the one that's about to be uninstalled. + # + def remove_stub_files(spec, other_specs) + remove_app_stubs(spec, other_specs) + end + + def remove_app_stubs(spec, other_specs) + # App stubs are tricky, because each version of an app gem could + # install different applications. We need to make sure that + # what we delete isn't needed by any remaining versions of the + # gem. + # + # There's extra trickiness, too, because app stubs 'require_gem' + # a specific version of the gem. If we uninstall the latest + # gem, we should ensure that there is a sensible app stub(s) + # installed after the removal of the current one. + # + # Perhaps the best way to approach this is: + # * remove all application stubs for this gemspec + # * regenerate the app stubs for the latest remaining version + # (you always want to have the latest version of an app, + # don't you?) + # + # The Installer class doesn't really support this approach very + # well at the moment. + end + + end # class Uninstaller + + class ExtConfigureBuilder + def self.build(extension, directory, dest_path) + results = [] + unless File.exist?('Makefile') then + cmd = "sh ./configure --prefix=#{dest_path}" + results << cmd + results << `#{cmd}` + end + + results.push(*ExtExtConfBuilder.make(dest_path)) + results + end + end + + class ExtExtConfBuilder + def self.build(extension, directory, dest_path) + results = ["#{Gem.ruby} #{File.basename(extension)} #{ARGV.join(" ")}"] + results << `#{Gem.ruby} #{File.basename(extension)} #{ARGV.join(" ")}` + results.push(*make(dest_path)) + results + end + + def self.make(dest_path) + results = [] + raise unless File.exist?('Makefile') + mf = File.read('Makefile') + mf = mf.gsub(/^RUBYARCHDIR\s*=\s*\$[^$]*/, "RUBYARCHDIR = #{dest_path}") + mf = mf.gsub(/^RUBYLIBDIR\s*=\s*\$[^$]*/, "RUBYLIBDIR = #{dest_path}") + File.open('Makefile', 'wb') {|f| f.print mf} + + make_program = ENV['make'] + unless make_program + make_program = (/mswin/ =~ RUBY_PLATFORM) ? 'nmake' : 'make' + end + + ['', 'install', 'clean'].each do |target| + results << "#{make_program} #{target}".strip + results << `#{make_program} #{target}` + end + + results + end + + end + + class ExtRakeBuilder + def ExtRakeBuilder.build(ext, directory, dest_path) + make_program = ENV['rake'] || 'rake' + make_program += " RUBYARCHDIR=#{dest_path} RUBYLIBDIR=#{dest_path}" + + results = [] + + ['', 'install', 'clean'].each do |target| + results << "#{make_program} #{target}".strip + results << `#{make_program} #{target}` + end + + results + end + end +end # module Gem diff --git a/amarok/src/mediadevice/daap/mongrel/lib/rubygems/loadpath_manager.rb b/amarok/src/mediadevice/daap/mongrel/lib/rubygems/loadpath_manager.rb new file mode 100644 index 00000000..e372e2c7 --- /dev/null +++ b/amarok/src/mediadevice/daap/mongrel/lib/rubygems/loadpath_manager.rb @@ -0,0 +1,114 @@ +#-- +# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others. +# All rights reserved. +# See LICENSE.txt for permissions. +#++ + +module Kernel + alias require__ require + def require(file) + Gem::LoadPathManager.search_loadpath(file) || Gem::LoadPathManager.search_gempath(file) + require__(file) + end +end + +module Gem + module LoadPathManager + @paths = nil + + # These local versions of Gem::Version and Gem::Specification are + # used by the load path manager because they are faster than the + # fully functional ones. Full functionality is not required + # during the load phase. + module Gem + class Version + class Requirement + def initialize(string) + end + end + end + class Specification + def initialize(&block) + @require_paths = ['lib'] + @platform = nil + yield self + end + attr_reader :version + attr_accessor :files, :require_paths, :name + def platform=(platform) + @platform = platform unless platform == "ruby" + end + def requirements; []; end + def version=(version) + @version = ::Gem::Version.create(version) + end + def full_name + @full_name ||= + if @platform.nil? || @platform == "ruby" || @platform == "" + "#{@name}-#{@version}" + else + "#{@name}-#{@version}-#{@platform}" + end + end + def method_missing(method, *args) + end + def <=>(other) + r = @name<=>other.name + r = other.version<=>@version if r == 0 + r + end + def to_s + "#" + end + end + end + + def self.paths + @paths + end + + # Prep the list of potential paths for require file resolution. + def self.build_paths + @specs ||= [] + @paths = [] + ::Gem.path.each do |gempath| + newspecs = Dir.glob("#{gempath}/specifications/*.gemspec").collect { |specfile| + eval(File.read(specfile)) + }.sort + @specs.concat(newspecs) + newspecs.each do |spec| + spec.require_paths.each {|path| @paths << "#{gempath}/gems/#{spec.full_name}/#{path}"} + end + end + end + + # True if the file can be resolved with the existing load path. + def self.search_loadpath(file) + glob_over($LOAD_PATH, file).size > 0 + end + + def self.search_gempath(file) + build_paths unless @paths + fullname = glob_over(@paths, file).first + return false unless fullname + @specs.each do |spec| + if fullname.include?("/#{spec.full_name}/") + ::Gem.activate(spec.name, false, spec.version.to_s) + return true + end + end + false + end + + private + + SUFFIX_PATTERN = "{,.rb,.so,.bundle,.dll,.sl}" + + def self.glob_over(list, file) + files = Dir.glob("{#{(list).join(',')}}/#{file}#{SUFFIX_PATTERN}").map{|x| Marshal.load(Marshal.dump(x))} + files.delete_if { |f| File.directory?(f) } + end + + end +end + diff --git a/amarok/src/mediadevice/daap/mongrel/lib/rubygems/old_format.rb b/amarok/src/mediadevice/daap/mongrel/lib/rubygems/old_format.rb new file mode 100644 index 00000000..31e803d5 --- /dev/null +++ b/amarok/src/mediadevice/daap/mongrel/lib/rubygems/old_format.rb @@ -0,0 +1,156 @@ +#-- +# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others. +# All rights reserved. +# See LICENSE.txt for permissions. +#++ + +module Gem + + ## + # Used to raise parsing and loading errors + # + class FormatException < Gem::Exception + attr_accessor :file_path + #I go back and forth on whether or not to create custom exception classes + end + + ## + # The format class knows the guts of the RubyGem .gem file format + # and provides the capability to read gem files + # + class OldFormat + attr_accessor :spec, :file_entries, :gem_path + + ## + # Constructs an instance of a Format object, representing the gem's + # data structure. + # + # gem:: [String] The file name of the gem + # + def initialize(gem_path) + @gem_path = gem_path + end + + ## + # Reads the named gem file and returns a Format object, representing + # the data from the gem file + # + # file_path:: [String] Path to the gem file + # + def self.from_file_by_path(file_path) + unless File.exist?(file_path) + raise Gem::Exception, "Cannot load gem file [#{file_path}]" + end + require 'fileutils' + File.open(file_path, 'r') do |file| + from_io(file, file_path) + end + end + + ## + # Reads a gem from an io stream and returns a Format object, representing + # the data from the gem file + # + # io:: [IO] Stream from which to read the gem + # + def self.from_io(io, gem_path="(io)") + format = self.new(gem_path) + skip_ruby(io) + format.spec = read_spec(io) + format.file_entries = [] + read_files_from_gem(io) do |entry, file_data| + format.file_entries << [entry, file_data] + end + format + end + + private + ## + # Skips the Ruby self-install header. After calling this method, the + # IO index will be set after the Ruby code. + # + # file:: [IO] The IO to process (skip the Ruby code) + # + def self.skip_ruby(file) + end_seen = false + loop { + line = file.gets + if(line == nil || line.chomp == "__END__") then + end_seen = true + break + end + } + if(end_seen == false) then + raise Gem::Exception.new("Failed to find end of ruby script while reading gem") + end + end + + ## + # Reads the specification YAML from the supplied IO and constructs + # a Gem::Specification from it. After calling this method, the + # IO index will be set after the specification header. + # + # file:: [IO] The IO to process + # + def self.read_spec(file) + require 'yaml' + yaml = '' + begin + read_until_dashes(file) do |line| + yaml << line + end + Specification.from_yaml(yaml) + rescue YAML::Error => e + raise Gem::Exception.new("Failed to parse gem specification out of gem file") + rescue ArgumentError => e + raise Gem::Exception.new("Failed to parse gem specification out of gem file") + end + end + + ## + # Reads lines from the supplied IO until a end-of-yaml (---) is + # reached + # + # file:: [IO] The IO to process + # block:: [String] The read line + # + def self.read_until_dashes(file) + while((line = file.gets) && line.chomp.strip != "---") do + yield line + end + end + + + ## + # Reads the embedded file data from a gem file, yielding an entry + # containing metadata about the file and the file contents themselves + # for each file that's archived in the gem. + # NOTE: Many of these methods should be extracted into some kind of + # Gem file read/writer + # + # gem_file:: [IO] The IO to process + # + def self.read_files_from_gem(gem_file) + require 'zlib' + require 'yaml' + errstr = "Error reading files from gem" + header_yaml = '' + begin + self.read_until_dashes(gem_file) do |line| + header_yaml << line + end + header = YAML.load(header_yaml) + raise Gem::Exception.new(errstr) unless header + header.each do |entry| + file_data = '' + self.read_until_dashes(gem_file) do |line| + file_data << line + end + yield [entry, Zlib::Inflate.inflate(file_data.strip.unpack("m")[0])] + end + rescue Exception,Zlib::DataError => e + raise Gem::Exception.new(errstr) + end + end + end +end diff --git a/amarok/src/mediadevice/daap/mongrel/lib/rubygems/open-uri.rb b/amarok/src/mediadevice/daap/mongrel/lib/rubygems/open-uri.rb new file mode 100644 index 00000000..93927ca3 --- /dev/null +++ b/amarok/src/mediadevice/daap/mongrel/lib/rubygems/open-uri.rb @@ -0,0 +1,756 @@ +#= open-uri.rb +# +#open-uri.rb is easy-to-use wrapper for net/http, net/https and net/ftp. +# +#== Example +# +#It is possible to open http/https/ftp URL as usual a file: +# +# open("http://www.ruby-lang.org/") {|f| +# f.each_line {|line| p line} +# } +# +#The opened file has several methods for meta information as follows since +#it is extended by OpenURI::Meta. +# +# open("http://www.ruby-lang.org/en") {|f| +# f.each_line {|line| p line} +# p f.base_uri # +# p f.content_type # "text/html" +# p f.charset # "iso-8859-1" +# p f.content_encoding # [] +# p f.last_modified # Thu Dec 05 02:45:02 UTC 2002 +# } +# +#Additional header fields can be specified by an optional hash argument. +# +# open("http://www.ruby-lang.org/en/", +# "User-Agent" => "Ruby/#{RUBY_VERSION}", +# "From" => "foo@bar.invalid", +# "Referer" => "http://www.ruby-lang.org/") {|f| +# ... +# } +# +#The environment variables such as http_proxy, https_proxy and ftp_proxy +#are in effect by default. :proxy => nil disables proxy. +# +# open("http://www.ruby-lang.org/en/raa.html", +# :proxy => nil) {|f| +# ... +# } +# +#URI objects can be opened in similar way. +# +# uri = URI.parse("http://www.ruby-lang.org/en/") +# uri.open {|f| +# ... +# } +# +#URI objects can be read directly. +#The returned string is also extended by OpenURI::Meta. +# +# str = uri.read +# p str.base_uri +# +#Author:: Tanaka Akira +#-- +# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others. +# All rights reserved. +# See LICENSE.txt for permissions. +#++ + + +require 'uri' +require 'stringio' +require 'time' + +module Kernel + private + alias open_uri_original_open open # :nodoc: + + # makes possible to open various resources including URIs. + # If the first argument respond to `open' method, + # the method is called with the rest arguments. + # + # If the first argument is a string which begins with xxx://, + # it is parsed by URI.parse. If the parsed object respond to `open' method, + # the method is called with the rest arguments. + # + # Otherwise original open is called. + # + # Since open-uri.rb provides URI::HTTP#open, URI::HTTPS#open and + # URI::FTP#open, + # Kernel[#.]open can accepts such URIs and strings which begins with + # http://, https:// and ftp://. + # In these case, the opened file object is extended by OpenURI::Meta. + def open(name, *rest, &block) # :doc: + if name.respond_to?(:open) + name.open(*rest, &block) + elsif name.respond_to?(:to_str) && + %r{\A[A-Za-z][A-Za-z0-9+\-\.]*://} =~ name && + (uri = URI.parse(name)).respond_to?(:open) + uri.open(*rest, &block) + else + open_uri_original_open(name, *rest, &block) + end + end + module_function :open +end + +module OpenURI + Options = { + :proxy => true, + :proxy_http_basic_authentication => true, + :progress_proc => true, + :content_length_proc => true, + :http_basic_authentication => true, + :read_timeout => true, + } + + def OpenURI.check_options(options) # :nodoc: + options.each {|k, v| + next unless Symbol === k + unless Options.include? k + raise ArgumentError, "unrecognized option: #{k}" + end + } + end + + def OpenURI.scan_open_optional_arguments(*rest) # :nodoc: + if !rest.empty? && (String === rest.first || Integer === rest.first) + mode = rest.shift + if !rest.empty? && Integer === rest.first + perm = rest.shift + end + end + return mode, perm, rest + end + + def OpenURI.open_uri(name, *rest) # :nodoc: + uri = URI::Generic === name ? name : URI.parse(name) + mode, perm, rest = OpenURI.scan_open_optional_arguments(*rest) + options = rest.shift if !rest.empty? && Hash === rest.first + raise ArgumentError.new("extra arguments") if !rest.empty? + options ||= {} + OpenURI.check_options(options) + + unless mode == nil || + mode == 'r' || mode == 'rb' || + mode == File::RDONLY + raise ArgumentError.new("invalid access mode #{mode} (#{uri.class} resource is read only.)") + end + + io = open_loop(uri, options) + if block_given? + begin + yield io + ensure + io.close + end + else + io + end + end + + def OpenURI.open_loop(uri, options) # :nodoc: + proxy_opts = [] + proxy_opts << :proxy_http_basic_authentication if options.include? :proxy_http_basic_authentication + proxy_opts << :proxy if options.include? :proxy + proxy_opts.compact! + if 1 < proxy_opts.length + raise ArgumentError, "multiple proxy options specified" + end + case proxy_opts.first + when :proxy_http_basic_authentication + opt_proxy, proxy_user, proxy_pass = options.fetch(:proxy_http_basic_authentication) + proxy_user = proxy_user.to_str + proxy_pass = proxy_pass.to_str + if opt_proxy == true + raise ArgumentError.new("Invalid authenticated proxy option: #{options[:proxy_http_basic_authentication].inspect}") + end + when :proxy + opt_proxy = options.fetch(:proxy) + proxy_user = nil + proxy_pass = nil + when nil + opt_proxy = true + proxy_user = nil + proxy_pass = nil + end + case opt_proxy + when true + find_proxy = lambda {|u| pxy = u.find_proxy; pxy ? [pxy, nil, nil] : nil} + when nil, false + find_proxy = lambda {|u| nil} + when String + opt_proxy = URI.parse(opt_proxy) + find_proxy = lambda {|u| [opt_proxy, proxy_user, proxy_pass]} + when URI::Generic + find_proxy = lambda {|u| [opt_proxy, proxy_user, proxy_pass]} + else + raise ArgumentError.new("Invalid proxy option: #{opt_proxy}") + end + + uri_set = {} + buf = nil + while true + redirect = catch(:open_uri_redirect) { + buf = Buffer.new + uri.buffer_open(buf, find_proxy.call(uri), options) + nil + } + if redirect + if redirect.relative? + # Although it violates RFC2616, Location: field may have relative + # URI. It is converted to absolute URI using uri as a base URI. + redirect = uri + redirect + end + unless OpenURI.redirectable?(uri, redirect) + raise "redirection forbidden: #{uri} -> #{redirect}" + end + if options.include? :http_basic_authentication + # send authentication only for the URI directly specified. + options = options.dup + options.delete :http_basic_authentication + end + uri = redirect + raise "HTTP redirection loop: #{uri}" if uri_set.include? uri.to_s + uri_set[uri.to_s] = true + else + break + end + end + io = buf.io + io.base_uri = uri + io + end + + def OpenURI.redirectable?(uri1, uri2) # :nodoc: + # This test is intended to forbid a redirection from http://... to + # file:///etc/passwd. + # However this is ad hoc. It should be extensible/configurable. + uri1.scheme.downcase == uri2.scheme.downcase || + (/\A(?:http|ftp)\z/i =~ uri1.scheme && /\A(?:http|ftp)\z/i =~ uri2.scheme) + end + + def OpenURI.open_http(buf, target, proxy, options) # :nodoc: + if proxy + proxy_uri, proxy_user, proxy_pass = proxy + raise "Non-HTTP proxy URI: #{proxy_uri}" if proxy_uri.class != URI::HTTP + end + + if target.userinfo && "1.9.0" <= RUBY_VERSION + # don't raise for 1.8 because compatibility. + raise ArgumentError, "userinfo not supported. [RFC3986]" + end + + header = {} + options.each {|k, v| header[k] = v if String === k } + + require 'net/http' + klass = Net::HTTP + if URI::HTTP === target + # HTTP or HTTPS + if proxy + if proxy_user && proxy_pass + klass = Net::HTTP::Proxy(proxy_uri.host, proxy_uri.port, proxy_user, proxy_pass) + else + klass = Net::HTTP::Proxy(proxy_uri.host, proxy_uri.port) + end + end + target_host = target.host + target_port = target.port + request_uri = target.request_uri + else + # FTP over HTTP proxy + target_host = proxy_uri.host + target_port = proxy_uri.port + request_uri = target.to_s + if proxy_user && proxy_pass + header["Proxy-Authorization"] = 'Basic ' + ["#{proxy_user}:#{proxy_pass}"].pack('m').delete("\r\n") + end + end + + http = klass.new(target_host, target_port) + if target.class == URI::HTTPS + require 'net/https' + http.use_ssl = true + http.verify_mode = OpenSSL::SSL::VERIFY_PEER + store = OpenSSL::X509::Store.new + store.set_default_paths + http.cert_store = store + end + if options.include? :read_timeout + http.read_timeout = options[:read_timeout] + end + + resp = nil + http.start { + if target.class == URI::HTTPS + # xxx: information hiding violation + sock = http.instance_variable_get(:@socket) + if sock.respond_to?(:io) + sock = sock.io # 1.9 + else + sock = sock.instance_variable_get(:@socket) # 1.8 + end + sock.post_connection_check(target_host) + end + req = Net::HTTP::Get.new(request_uri, header) + if options.include? :http_basic_authentication + user, pass = options[:http_basic_authentication] + req.basic_auth user, pass + end + http.request(req) {|response| + resp = response + if options[:content_length_proc] && Net::HTTPSuccess === resp + if resp.key?('Content-Length') + options[:content_length_proc].call(resp['Content-Length'].to_i) + else + options[:content_length_proc].call(nil) + end + end + resp.read_body {|str| + buf << str + if options[:progress_proc] && Net::HTTPSuccess === resp + options[:progress_proc].call(buf.size) + end + } + } + } + io = buf.io + io.rewind + io.status = [resp.code, resp.message] + resp.each {|name,value| buf.io.meta_add_field name, value } + case resp + when Net::HTTPSuccess + when Net::HTTPMovedPermanently, # 301 + Net::HTTPFound, # 302 + Net::HTTPSeeOther, # 303 + Net::HTTPTemporaryRedirect # 307 + throw :open_uri_redirect, URI.parse(resp['location']) + else + raise OpenURI::HTTPError.new(io.status.join(' '), io) + end + end + + class HTTPError < StandardError + def initialize(message, io) + super(message) + @io = io + end + attr_reader :io + end + + class Buffer # :nodoc: + def initialize + @io = StringIO.new + @size = 0 + end + attr_reader :size + + StringMax = 10240 + def <<(str) + @io << str + @size += str.length + if StringIO === @io && StringMax < @size + require 'tempfile' + io = Tempfile.new('open-uri') + io.binmode + Meta.init io, @io if @io.respond_to? :meta + io << @io.string + @io = io + end + end + + def io + Meta.init @io unless @io.respond_to? :meta + @io + end + end + + # Mixin for holding meta-information. + module Meta + def Meta.init(obj, src=nil) # :nodoc: + obj.extend Meta + obj.instance_eval { + @base_uri = nil + @meta = {} + } + if src + obj.status = src.status + obj.base_uri = src.base_uri + src.meta.each {|name, value| + obj.meta_add_field(name, value) + } + end + end + + # returns an Array which consists status code and message. + attr_accessor :status + + # returns a URI which is base of relative URIs in the data. + # It may differ from the URI supplied by a user because redirection. + attr_accessor :base_uri + + # returns a Hash which represents header fields. + # The Hash keys are downcased for canonicalization. + attr_reader :meta + + def meta_add_field(name, value) # :nodoc: + @meta[name.downcase] = value + end + + # returns a Time which represents Last-Modified field. + def last_modified + if v = @meta['last-modified'] + Time.httpdate(v) + else + nil + end + end + + RE_LWS = /[\r\n\t ]+/n + RE_TOKEN = %r{[^\x00- ()<>@,;:\\"/\[\]?={}\x7f]+}n + RE_QUOTED_STRING = %r{"(?:[\r\n\t !#-\[\]-~\x80-\xff]|\\[\x00-\x7f])*"}n + RE_PARAMETERS = %r{(?:;#{RE_LWS}?#{RE_TOKEN}#{RE_LWS}?=#{RE_LWS}?(?:#{RE_TOKEN}|#{RE_QUOTED_STRING})#{RE_LWS}?)*}n + + def content_type_parse # :nodoc: + v = @meta['content-type'] + # The last (?:;#{RE_LWS}?)? matches extra ";" which violates RFC2045. + if v && %r{\A#{RE_LWS}?(#{RE_TOKEN})#{RE_LWS}?/(#{RE_TOKEN})#{RE_LWS}?(#{RE_PARAMETERS})(?:;#{RE_LWS}?)?\z}no =~ v + type = $1.downcase + subtype = $2.downcase + parameters = [] + $3.scan(/;#{RE_LWS}?(#{RE_TOKEN})#{RE_LWS}?=#{RE_LWS}?(?:(#{RE_TOKEN})|(#{RE_QUOTED_STRING}))/no) {|att, val, qval| + val = qval.gsub(/[\r\n\t !#-\[\]-~\x80-\xff]+|(\\[\x00-\x7f])/) { $1 ? $1[1,1] : $& } if qval + parameters << [att.downcase, val] + } + ["#{type}/#{subtype}", *parameters] + else + nil + end + end + + # returns "type/subtype" which is MIME Content-Type. + # It is downcased for canonicalization. + # Content-Type parameters are stripped. + def content_type + type, *parameters = content_type_parse + type || 'application/octet-stream' + end + + # returns a charset parameter in Content-Type field. + # It is downcased for canonicalization. + # + # If charset parameter is not given but a block is given, + # the block is called and its result is returned. + # It can be used to guess charset. + # + # If charset parameter and block is not given, + # nil is returned except text type in HTTP. + # In that case, "iso-8859-1" is returned as defined by RFC2616 3.7.1. + def charset + type, *parameters = content_type_parse + if pair = parameters.assoc('charset') + pair.last.downcase + elsif block_given? + yield + elsif type && %r{\Atext/} =~ type && + @base_uri && /\Ahttp\z/i =~ @base_uri.scheme + "iso-8859-1" # RFC2616 3.7.1 + else + nil + end + end + + # returns a list of encodings in Content-Encoding field + # as an Array of String. + # The encodings are downcased for canonicalization. + def content_encoding + v = @meta['content-encoding'] + if v && %r{\A#{RE_LWS}?#{RE_TOKEN}#{RE_LWS}?(?:,#{RE_LWS}?#{RE_TOKEN}#{RE_LWS}?)*}o =~ v + v.scan(RE_TOKEN).map {|content_coding| content_coding.downcase} + else + [] + end + end + end + + # Mixin for HTTP and FTP URIs. + module OpenRead + # OpenURI::OpenRead#open provides `open' for URI::HTTP and URI::FTP. + # + # OpenURI::OpenRead#open takes optional 3 arguments as: + # OpenURI::OpenRead#open([mode [, perm]] [, options]) [{|io| ... }] + # + # `mode', `perm' is same as Kernel#open. + # + # However, `mode' must be read mode because OpenURI::OpenRead#open doesn't + # support write mode (yet). + # Also `perm' is just ignored because it is meaningful only for file + # creation. + # + # `options' must be a hash. + # + # Each pairs which key is a string in the hash specify a extra header + # field for HTTP. + # I.e. it is ignored for FTP without HTTP proxy. + # + # The hash may include other options which key is a symbol: + # + # [:proxy] + # Synopsis: + # :proxy => "http://proxy.foo.com:8000/" + # :proxy => URI.parse("http://proxy.foo.com:8000/") + # :proxy => true + # :proxy => false + # :proxy => nil + # + # If :proxy option is specified, the value should be String, URI, + # boolean or nil. + # When String or URI is given, it is treated as proxy URI. + # When true is given or the option itself is not specified, + # environment variable `scheme_proxy' is examined. + # `scheme' is replaced by `http', `https' or `ftp'. + # When false or nil is given, the environment variables are ignored and + # connection will be made to a server directly. + # + # [:proxy_http_basic_authentication] + # Synopsis: + # :proxy_http_basic_authentication => ["http://proxy.foo.com:8000/", "proxy-user", "proxy-password"] + # :proxy_http_basic_authentication => [URI.parse("http://proxy.foo.com:8000/"), "proxy-user", "proxy-password"] + # + # If :proxy option is specified, the value should be an Array with 3 elements. + # It should contain a proxy URI, a proxy user name and a proxy password. + # The proxy URI should be a String, an URI or nil. + # The proxy user name and password should be a String. + # + # If nil is given for the proxy URI, this option is just ignored. + # + # If :proxy and :proxy_http_basic_authentication is specified, + # ArgumentError is raised. + # + # [:http_basic_authentication] + # Synopsis: + # :http_basic_authentication=>[user, password] + # + # If :http_basic_authentication is specified, + # the value should be an array which contains 2 strings: + # username and password. + # It is used for HTTP Basic authentication defined by RFC 2617. + # + # [:content_length_proc] + # Synopsis: + # :content_length_proc => lambda {|content_length| ... } + # + # If :content_length_proc option is specified, the option value procedure + # is called before actual transfer is started. + # It takes one argument which is expected content length in bytes. + # + # If two or more transfer is done by HTTP redirection, the procedure + # is called only one for a last transfer. + # + # When expected content length is unknown, the procedure is called with + # nil. + # It is happen when HTTP response has no Content-Length header. + # + # [:progress_proc] + # Synopsis: + # :progress_proc => lambda {|size| ...} + # + # If :progress_proc option is specified, the proc is called with one + # argument each time when `open' gets content fragment from network. + # The argument `size' `size' is a accumulated transfered size in bytes. + # + # If two or more transfer is done by HTTP redirection, the procedure + # is called only one for a last transfer. + # + # :progress_proc and :content_length_proc are intended to be used for + # progress bar. + # For example, it can be implemented as follows using Ruby/ProgressBar. + # + # pbar = nil + # open("http://...", + # :content_length_proc => lambda {|t| + # if t && 0 < t + # pbar = ProgressBar.new("...", t) + # pbar.file_transfer_mode + # end + # }, + # :progress_proc => lambda {|s| + # pbar.set s if pbar + # }) {|f| ... } + # + # [:read_timeout] + # Synopsis: + # :read_timeout=>nil (no timeout) + # :read_timeout=>10 (10 second) + # + # :read_timeout option specifies a timeout of read for http connections. + # + # OpenURI::OpenRead#open returns an IO like object if block is not given. + # Otherwise it yields the IO object and return the value of the block. + # The IO object is extended with OpenURI::Meta. + def open(*rest, &block) + OpenURI.open_uri(self, *rest, &block) + end + + # OpenURI::OpenRead#read([options]) reads a content referenced by self and + # returns the content as string. + # The string is extended with OpenURI::Meta. + # The argument `options' is same as OpenURI::OpenRead#open. + def read(options={}) + self.open(options) {|f| + str = f.read + Meta.init str, f + str + } + end + end +end + +module URI + class Generic + # returns a proxy URI. + # The proxy URI is obtained from environment variables such as http_proxy, + # ftp_proxy, no_proxy, etc. + # If there is no proper proxy, nil is returned. + # + # Note that capitalized variables (HTTP_PROXY, FTP_PROXY, NO_PROXY, etc.) + # are examined too. + # + # But http_proxy and HTTP_PROXY is treated specially under CGI environment. + # It's because HTTP_PROXY may be set by Proxy: header. + # So HTTP_PROXY is not used. + # http_proxy is not used too if the variable is case insensitive. + # CGI_HTTP_PROXY can be used instead. + def find_proxy + name = self.scheme.downcase + '_proxy' + proxy_uri = nil + if name == 'http_proxy' && ENV.include?('REQUEST_METHOD') # CGI? + # HTTP_PROXY conflicts with *_proxy for proxy settings and + # HTTP_* for header information in CGI. + # So it should be careful to use it. + pairs = ENV.reject {|k, v| /\Ahttp_proxy\z/i !~ k } + case pairs.length + when 0 # no proxy setting anyway. + proxy_uri = nil + when 1 + k, v = pairs.shift + if k == 'http_proxy' && ENV[k.upcase] == nil + # http_proxy is safe to use because ENV is case sensitive. + proxy_uri = ENV[name] + else + proxy_uri = nil + end + else # http_proxy is safe to use because ENV is case sensitive. + proxy_uri = ENV[name] + end + if !proxy_uri + # Use CGI_HTTP_PROXY. cf. libwww-perl. + proxy_uri = ENV["CGI_#{name.upcase}"] + end + elsif name == 'http_proxy' + unless proxy_uri = ENV[name] + if proxy_uri = ENV[name.upcase] + warn 'The environment variable HTTP_PROXY is discouraged. Use http_proxy.' + end + end + else + proxy_uri = ENV[name] || ENV[name.upcase] + end + + if proxy_uri && self.host + require 'socket' + begin + addr = IPSocket.getaddress(self.host) + proxy_uri = nil if /\A127\.|\A::1\z/ =~ addr + rescue SocketError + end + end + + if proxy_uri + proxy_uri = URI.parse(proxy_uri) + name = 'no_proxy' + if no_proxy = ENV[name] || ENV[name.upcase] + no_proxy.scan(/([^:,]*)(?::(\d+))?/) {|host, port| + if /(\A|\.)#{Regexp.quote host}\z/i =~ self.host && + (!port || self.port == port.to_i) + proxy_uri = nil + break + end + } + end + proxy_uri + else + nil + end + end + end + + class HTTP + def buffer_open(buf, proxy, options) # :nodoc: + OpenURI.open_http(buf, self, proxy, options) + end + + include OpenURI::OpenRead + end + + class FTP + def buffer_open(buf, proxy, options) # :nodoc: + if proxy + OpenURI.open_http(buf, self, proxy, options) + return + end + require 'net/ftp' + + directories = self.path.split(%r{/}, -1) + directories.shift if directories[0] == '' # strip a field before leading slash + directories.each {|d| + d.gsub!(/%([0-9A-Fa-f][0-9A-Fa-f])/) { [$1].pack("H2") } + } + unless filename = directories.pop + raise ArgumentError, "no filename: #{self.inspect}" + end + directories.each {|d| + if /[\r\n]/ =~ d + raise ArgumentError, "invalid directory: #{d.inspect}" + end + } + if /[\r\n]/ =~ filename + raise ArgumentError, "invalid filename: #{filename.inspect}" + end + typecode = self.typecode + if typecode && /\A[aid]\z/ !~ typecode + raise ArgumentError, "invalid typecode: #{typecode.inspect}" + end + + # The access sequence is defined by RFC 1738 + ftp = Net::FTP.open(self.host) + # todo: extract user/passwd from .netrc. + user = 'anonymous' + passwd = nil + user, passwd = self.userinfo.split(/:/) if self.userinfo + ftp.login(user, passwd) + directories.each {|cwd| + ftp.voidcmd("CWD #{cwd}") + } + if typecode + # xxx: typecode D is not handled. + ftp.voidcmd("TYPE #{typecode.upcase}") + end + if options[:content_length_proc] + options[:content_length_proc].call(ftp.size(filename)) + end + ftp.retrbinary("RETR #{filename}", 4096) { |str| + buf << str + options[:progress_proc].call(buf.size) if options[:progress_proc] + } + ftp.close + buf.io.rewind + end + + include OpenURI::OpenRead + end +end diff --git a/amarok/src/mediadevice/daap/mongrel/lib/rubygems/package.rb b/amarok/src/mediadevice/daap/mongrel/lib/rubygems/package.rb new file mode 100644 index 00000000..8067d1ef --- /dev/null +++ b/amarok/src/mediadevice/daap/mongrel/lib/rubygems/package.rb @@ -0,0 +1,852 @@ +# +# Copyright (C) 2004 Mauricio Julio Fernndez Pradier +# See LICENSE.txt for additional licensing information. +# + +require 'yaml' +require 'yaml/syck' +require 'fileutils' +require 'zlib' +require 'digest/md5' +require 'fileutils' +require 'find' +require 'stringio' + +require 'rubygems/specification' +require 'rubygems/security' + +module Gem + +# Wrapper for FileUtils meant to provide logging and additional operations if +# needed. +class FileOperations + extend FileUtils + class << self + # additional methods not implemented in FileUtils + end + def initialize(logger = nil) + @logger = logger + end + + def method_missing(meth, *args, &block) + case + when FileUtils.respond_to?(meth) + @logger.log "#{meth}: #{args}" if @logger + FileUtils.send meth, *args, &block + when FileOperations.respond_to?(meth) + @logger.log "#{meth}: #{args}" if @logger + FileOperations.send meth, *args, &block + else + super + end + end +end + + +module Package + +class NonSeekableIO < StandardError; end +class ArgumentError < ::ArgumentError; end +class ClosedIO < StandardError; end +class BadCheckSum < StandardError; end +class TooLongFileName < StandardError; end + +module FSyncDir + private + def fsync_dir(dirname) + # make sure this hits the disc + begin + dir = open(dirname, "r") + dir.fsync + rescue # ignore IOError if it's an unpatched (old) Ruby + ensure + dir.close if dir rescue nil + end + end +end + +class TarHeader + FIELDS = [:name, :mode, :uid, :gid, :size, :mtime, :checksum, :typeflag, + :linkname, :magic, :version, :uname, :gname, :devmajor, + :devminor, :prefix] + FIELDS.each {|x| attr_reader x} + + def self.new_from_stream(stream) + data = stream.read(512) + fields = data.unpack( "A100" + # record name + "A8A8A8" + # mode, uid, gid + "A12A12" + # size, mtime + "A8A" + # checksum, typeflag + "A100" + # linkname + "A6A2" + # magic, version + "A32" + # uname + "A32" + # gname + "A8A8" + # devmajor, devminor + "A155" # prefix + ) + name = fields.shift + mode = fields.shift.oct + uid = fields.shift.oct + gid = fields.shift.oct + size = fields.shift.oct + mtime = fields.shift.oct + checksum = fields.shift.oct + typeflag = fields.shift + linkname = fields.shift + magic = fields.shift + version = fields.shift.oct + uname = fields.shift + gname = fields.shift + devmajor = fields.shift.oct + devminor = fields.shift.oct + prefix = fields.shift + + empty = (data == "\0" * 512) + + new(:name=>name, :mode=>mode, :uid=>uid, :gid=>gid, :size=>size, + :mtime=>mtime, :checksum=>checksum, :typeflag=>typeflag, :magic=>magic, + :version=>version, :uname=>uname, :gname=>gname, :devmajor=>devmajor, + :devminor=>devminor, :prefix=>prefix, :empty => empty ) + end + + def initialize(vals) + unless vals[:name] && vals[:size] && vals[:prefix] && vals[:mode] + raise Package::ArgumentError + end + vals[:uid] ||= 0 + vals[:gid] ||= 0 + vals[:mtime] ||= 0 + vals[:checksum] ||= "" + vals[:typeflag] ||= "0" + vals[:magic] ||= "ustar" + vals[:version] ||= "00" + vals[:uname] ||= "wheel" + vals[:gname] ||= "wheel" + vals[:devmajor] ||= 0 + vals[:devminor] ||= 0 + FIELDS.each {|x| instance_variable_set "@#{x.to_s}", vals[x]} + @empty = vals[:empty] + end + + def empty? + @empty + end + + def to_s + update_checksum + header(checksum) + end + + def update_checksum + h = header(" " * 8) + @checksum = oct(calculate_checksum(h), 6) + end + + private + def oct(num, len) + "%0#{len}o" % num + end + + def calculate_checksum(hdr) + hdr.unpack("C*").inject{|a,b| a+b} + end + + def header(chksum) +# struct tarfile_entry_posix { +# char name[100]; # ASCII + (Z unless filled) +# char mode[8]; # 0 padded, octal, null +# char uid[8]; # ditto +# char gid[8]; # ditto +# char size[12]; # 0 padded, octal, null +# char mtime[12]; # 0 padded, octal, null +# char checksum[8]; # 0 padded, octal, null, space +# char typeflag[1]; # file: "0" dir: "5" +# char linkname[100]; # ASCII + (Z unless filled) +# char magic[6]; # "ustar\0" +# char version[2]; # "00" +# char uname[32]; # ASCIIZ +# char gname[32]; # ASCIIZ +# char devmajor[8]; # 0 padded, octal, null +# char devminor[8]; # o padded, octal, null +# char prefix[155]; # ASCII + (Z unless filled) +# }; + arr = [name, oct(mode, 7), oct(uid, 7), oct(gid, 7), oct(size, 11), + oct(mtime, 11), chksum, " ", typeflag, linkname, magic, version, + uname, gname, oct(devmajor, 7), oct(devminor, 7), prefix] + str = arr.pack("a100a8a8a8a12a12" + # name, mode, uid, gid, size, mtime + "a7aaa100a6a2" + # chksum, typeflag, linkname, magic, version + "a32a32a8a8a155") # uname, gname, devmajor, devminor, prefix + str + "\0" * ((512 - str.size) % 512) + end +end + +class TarWriter + class FileOverflow < StandardError; end + class BlockNeeded < StandardError; end + + class BoundedStream + attr_reader :limit, :written + def initialize(io, limit) + @io = io + @limit = limit + @written = 0 + end + + def write(data) + if data.size + @written > @limit + raise FileOverflow, + "You tried to feed more data than fits in the file." + end + @io.write data + @written += data.size + data.size + end + end + class RestrictedStream + def initialize(anIO) + @io = anIO + end + + def write(data) + @io.write data + end + end + + def self.new(anIO) + writer = super(anIO) + return writer unless block_given? + begin + yield writer + ensure + writer.close + end + nil + end + + def initialize(anIO) + @io = anIO + @closed = false + end + + def add_file_simple(name, mode, size) + raise BlockNeeded unless block_given? + raise ClosedIO if @closed + name, prefix = split_name(name) + header = TarHeader.new(:name => name, :mode => mode, + :size => size, :prefix => prefix).to_s + @io.write header + os = BoundedStream.new(@io, size) + yield os + #FIXME: what if an exception is raised in the block? + min_padding = size - os.written + @io.write("\0" * min_padding) + remainder = (512 - (size % 512)) % 512 + @io.write("\0" * remainder) + end + + def add_file(name, mode) + raise BlockNeeded unless block_given? + raise ClosedIO if @closed + raise NonSeekableIO unless @io.respond_to? :pos= + name, prefix = split_name(name) + init_pos = @io.pos + @io.write "\0" * 512 # placeholder for the header + yield RestrictedStream.new(@io) + #FIXME: what if an exception is raised in the block? + #FIXME: what if an exception is raised in the block? + size = @io.pos - init_pos - 512 + remainder = (512 - (size % 512)) % 512 + @io.write("\0" * remainder) + final_pos = @io.pos + @io.pos = init_pos + header = TarHeader.new(:name => name, :mode => mode, + :size => size, :prefix => prefix).to_s + @io.write header + @io.pos = final_pos + end + + def mkdir(name, mode) + raise ClosedIO if @closed + name, prefix = split_name(name) + header = TarHeader.new(:name => name, :mode => mode, :typeflag => "5", + :size => 0, :prefix => prefix).to_s + @io.write header + nil + end + + def flush + raise ClosedIO if @closed + @io.flush if @io.respond_to? :flush + end + + def close + #raise ClosedIO if @closed + return if @closed + @io.write "\0" * 1024 + @closed = true + end + + private + def split_name name + raise TooLongFileName if name.size > 256 + if name.size <= 100 + prefix = "" + else + parts = name.split(/\//) + newname = parts.pop + nxt = "" + loop do + nxt = parts.pop + break if newname.size + 1 + nxt.size > 100 + newname = nxt + "/" + newname + end + prefix = (parts + [nxt]).join "/" + name = newname + raise TooLongFileName if name.size > 100 || prefix.size > 155 + end + return name, prefix + end +end + +class TarReader + include Gem::Package + class UnexpectedEOF < StandardError; end + module InvalidEntry + def read(len=nil); raise ClosedIO; end + def getc; raise ClosedIO; end + def rewind; raise ClosedIO; end + end + class Entry + TarHeader::FIELDS.each{|x| attr_reader x} + + def initialize(header, anIO) + @io = anIO + @name = header.name + @mode = header.mode + @uid = header.uid + @gid = header.gid + @size = header.size + @mtime = header.mtime + @checksum = header.checksum + @typeflag = header.typeflag + @linkname = header.linkname + @magic = header.magic + @version = header.version + @uname = header.uname + @gname = header.gname + @devmajor = header.devmajor + @devminor = header.devminor + @prefix = header.prefix + @read = 0 + @orig_pos = @io.pos + end + + def read(len = nil) + return nil if @read >= @size + len ||= @size - @read + max_read = [len, @size - @read].min + ret = @io.read(max_read) + @read += ret.size + ret + end + + def getc + return nil if @read >= @size + ret = @io.getc + @read += 1 if ret + ret + end + + def is_directory? + @typeflag == "5" + end + + def is_file? + @typeflag == "0" + end + + def eof? + @read >= @size + end + + def pos + @read + end + + def rewind + raise NonSeekableIO unless @io.respond_to? :pos= + @io.pos = @orig_pos + @read = 0 + end + + alias_method :is_directory, :is_directory? + alias_method :is_file, :is_file + + def bytes_read + @read + end + + def full_name + if @prefix != "" + File.join(@prefix, @name) + else + @name + end + end + + def close + invalidate + end + + private + def invalidate + extend InvalidEntry + end + end + + def self.new(anIO) + reader = super(anIO) + return reader unless block_given? + begin + yield reader + ensure + reader.close + end + nil + end + + def initialize(anIO) + @io = anIO + @init_pos = anIO.pos + end + + def each(&block) + each_entry(&block) + end + + # do not call this during a #each or #each_entry iteration + def rewind + if @init_pos == 0 + raise NonSeekableIO unless @io.respond_to? :rewind + @io.rewind + else + raise NonSeekableIO unless @io.respond_to? :pos= + @io.pos = @init_pos + end + end + + def each_entry + loop do + return if @io.eof? + header = TarHeader.new_from_stream(@io) + return if header.empty? + entry = Entry.new header, @io + size = entry.size + yield entry + skip = (512 - (size % 512)) % 512 + if @io.respond_to? :seek + # avoid reading... + @io.seek(size - entry.bytes_read, IO::SEEK_CUR) + else + pending = size - entry.bytes_read + while pending > 0 + bread = @io.read([pending, 4096].min).size + raise UnexpectedEOF if @io.eof? + pending -= bread + end + end + @io.read(skip) # discard trailing zeros + # make sure nobody can use #read, #getc or #rewind anymore + entry.close + end + end + + def close + end +end + +class TarInput + include FSyncDir + include Enumerable + attr_reader :metadata + class << self; private :new end + + def initialize(io, security_policy = nil) + @io = io + @tarreader = TarReader.new(@io) + has_meta = false + data_sig, meta_sig, data_dgst, meta_dgst = nil, nil, nil, nil + dgst_algo = security_policy ? Gem::Security::OPT[:dgst_algo] : nil + + @tarreader.each do |entry| + case entry.full_name + when "metadata" + # (GS) Changed to line below: @metadata = YAML.load(entry.read) rescue nil + @metadata = load_gemspec(entry.read) + has_meta = true + break + when "metadata.gz" + begin + # if we have a security_policy, then pre-read the + # metadata file and calculate it's digest + sio = nil + if security_policy + Gem.ensure_ssl_available + sio = StringIO.new(entry.read) + meta_dgst = dgst_algo.digest(sio.string) + sio.rewind + end + + gzis = Zlib::GzipReader.new(sio || entry) + # YAML wants an instance of IO + # (GS) Changed to line below: @metadata = YAML.load(gzis) rescue nil + @metadata = load_gemspec(gzis) + has_meta = true + ensure + gzis.close + end + when 'metadata.gz.sig' + meta_sig = entry.read + when 'data.tar.gz.sig' + data_sig = entry.read + when 'data.tar.gz' + if security_policy + Gem.ensure_ssl_available + data_dgst = dgst_algo.digest(entry.read) + end + end + end + + if security_policy + Gem.ensure_ssl_available + # map trust policy from string to actual class (or a + # serialized YAML file, if that exists) + if (security_policy.is_a?(String)) + if Gem::Security.constants.index(security_policy) + # load one of the pre-defined security policies + security_policy = Gem::Security.const_get(security_policy) + elsif File.exists?(security_policy) + # FIXME: this doesn't work yet + security_policy = YAML::load(File.read(security_policy)) + else + raise Gem::Exception, "Unknown trust policy '#{security_policy}'" + end + end + + if data_sig && data_dgst && meta_sig && meta_dgst + # the user has a trust policy, and we have a signed gem + # file, so use the trust policy to verify the gem signature + + begin + security_policy.verify_gem(data_sig, data_dgst, @metadata.cert_chain) + rescue Exception => e + raise "Couldn't verify data signature: #{e}" + end + + begin + security_policy.verify_gem(meta_sig, meta_dgst, @metadata.cert_chain) + rescue Exception => e + raise "Couldn't verify metadata signature: #{e}" + end + elsif security_policy.only_signed + raise Gem::Exception, "Unsigned gem" + else + # FIXME: should display warning here (trust policy, but + # either unsigned or badly signed gem file) + end + end + + @tarreader.rewind + @fileops = FileOperations.new + raise RuntimeError, "No metadata found!" unless has_meta + end + + # Attempt to YAML-load a gemspec from the given _io_ parameter. Return nil if it fails. + def load_gemspec(io) + Gem::Specification.from_yaml(io) + rescue Gem::Exception + nil + end + + def self.open(filename, security_policy = nil, &block) + open_from_io(File.open(filename, "rb"), security_policy, &block) + end + + def self.open_from_io(io, security_policy = nil, &block) + raise "Want a block" unless block_given? + begin + is = new(io, security_policy) + yield is + ensure + is.close if is + end + end + + def each(&block) + @tarreader.each do |entry| + next unless entry.full_name == "data.tar.gz" + is = zipped_stream(entry) + begin + TarReader.new(is) do |inner| + inner.each(&block) + end + ensure + is.close if is + end + end + @tarreader.rewind + end + + # Return an IO stream for the zipped entry. + # + # If zlib is earlier than 1.2.1, then read the entry into memory + # and create a string IO object from it. This avoids a "buffer + # error" problem on windows when using an earlier version of zlib. + # This problem has not been observed in versions of zlib 1.2.1 or + # later. + def zipped_stream(entry) + if Zlib::ZLIB_VERSION < '1.2.1' + zis = Zlib::GzipReader.new entry + dis = zis.read + is = StringIO.new(dis) + else + is = Zlib::GzipReader.new entry + end + ensure + zis.finish if zis + end + + def extract_entry(destdir, entry, expected_md5sum = nil) + if entry.is_directory? + dest = File.join(destdir, entry.full_name) + if file_class.dir? dest + @fileops.chmod entry.mode, dest, :verbose=>false + else + @fileops.mkdir_p(dest, :mode => entry.mode, :verbose=>false) + end + fsync_dir dest + fsync_dir File.join(dest, "..") + return + end + # it's a file + md5 = Digest::MD5.new if expected_md5sum + destdir = File.join(destdir, File.dirname(entry.full_name)) + @fileops.mkdir_p(destdir, :mode => 0755, :verbose=>false) + destfile = File.join(destdir, File.basename(entry.full_name)) + @fileops.chmod(0600, destfile, :verbose=>false) rescue nil # Errno::ENOENT + file_class.open(destfile, "wb", entry.mode) do |os| + loop do + data = entry.read(4096) + break unless data + md5 << data if expected_md5sum + os.write(data) + end + os.fsync + end + @fileops.chmod(entry.mode, destfile, :verbose=>false) + fsync_dir File.dirname(destfile) + fsync_dir File.join(File.dirname(destfile), "..") + if expected_md5sum && expected_md5sum != md5.hexdigest + raise BadCheckSum + end + end + + def close + @io.close + @tarreader.close + end + + private + + def file_class + File + end +end + +class TarOutput + + class << self; private :new end + + class << self + + end + + def initialize(io) + @io = io + @external = TarWriter.new @io + end + + def external_handle + @external + end + + def self.open(filename, signer = nil, &block) + io = File.open(filename, "wb") + open_from_io(io, signer, &block) + nil + end + + def self.open_from_io(io, signer = nil, &block) + outputter = new(io) + metadata = nil + set_meta = lambda{|x| metadata = x} + raise "Want a block" unless block_given? + begin + data_sig, meta_sig = nil, nil + + outputter.external_handle.add_file("data.tar.gz", 0644) do |inner| + begin + sio = signer ? StringIO.new : nil + os = Zlib::GzipWriter.new(sio || inner) + + TarWriter.new(os) do |inner_tar_stream| + klass = class <= "1.9" then + klass.funcall(:define_method, :metadata=, &set_meta) + else + klass.send(:define_method, :metadata=, &set_meta) + end + block.call inner_tar_stream + end + ensure + os.flush + os.finish + #os.close + + # if we have a signing key, then sign the data + # digest and return the signature + data_sig = nil + if signer + dgst_algo = Gem::Security::OPT[:dgst_algo] + dig = dgst_algo.digest(sio.string) + data_sig = signer.sign(dig) + inner.write(sio.string) + end + end + end + + # if we have a data signature, then write it to the gem too + if data_sig + sig_file = 'data.tar.gz.sig' + outputter.external_handle.add_file(sig_file, 0644) do |os| + os.write(data_sig) + end + end + + outputter.external_handle.add_file("metadata.gz", 0644) do |os| + begin + sio = signer ? StringIO.new : nil + gzos = Zlib::GzipWriter.new(sio || os) + gzos.write metadata + ensure + gzos.flush + gzos.finish + + # if we have a signing key, then sign the metadata + # digest and return the signature + if signer + dgst_algo = Gem::Security::OPT[:dgst_algo] + dig = dgst_algo.digest(sio.string) + meta_sig = signer.sign(dig) + os.write(sio.string) + end + end + end + + # if we have a metadata signature, then write to the gem as + # well + if meta_sig + sig_file = 'metadata.gz.sig' + outputter.external_handle.add_file(sig_file, 0644) do |os| + os.write(meta_sig) + end + end + + ensure + outputter.close + end + nil + end + + def close + @external.close + @io.close + end +end +end # module Package + + +module Package + #FIXME: refactor the following 2 methods + def self.open(dest, mode = "r", signer = nil, &block) + raise "Block needed" unless block_given? + + case mode + when "r" + security_policy = signer + TarInput.open(dest, security_policy, &block) + when "w" + TarOutput.open(dest, signer, &block) + else + raise "Unknown Package open mode" + end + end + + def self.open_from_io(io, mode = "r", signer = nil, &block) + raise "Block needed" unless block_given? + + case mode + when "r" + security_policy = signer + TarInput.open_from_io(io, security_policy, &block) + when "w" + TarOutput.open_from_io(io, signer, &block) + else + raise "Unknown Package open mode" + end + end + + def self.pack(src, destname, signer = nil) + TarOutput.open(destname, signer) do |outp| + dir_class.chdir(src) do + outp.metadata = (file_class.read("RPA/metadata") rescue nil) + find_class.find('.') do |entry| + case + when file_class.file?(entry) + entry.sub!(%r{\./}, "") + next if entry =~ /\ARPA\// + stat = File.stat(entry) + outp.add_file_simple(entry, stat.mode, stat.size) do |os| + file_class.open(entry, "rb") do |f| + os.write(f.read(4096)) until f.eof? + end + end + when file_class.dir?(entry) + entry.sub!(%r{\./}, "") + next if entry == "RPA" + outp.mkdir(entry, file_class.stat(entry).mode) + else + raise "Don't know how to pack this yet!" + end + end + end + end + end + + class << self + def file_class + File + end + + def dir_class + Dir + end + + def find_class + Find + end + end +end + +end + diff --git a/amarok/src/mediadevice/daap/mongrel/lib/rubygems/remote_installer.rb b/amarok/src/mediadevice/daap/mongrel/lib/rubygems/remote_installer.rb new file mode 100644 index 00000000..4027d65d --- /dev/null +++ b/amarok/src/mediadevice/daap/mongrel/lib/rubygems/remote_installer.rb @@ -0,0 +1,582 @@ +#-- +# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others. +# All rights reserved. +# See LICENSE.txt for permissions. +#++ + +require 'rubygems' +require 'socket' +require 'fileutils' + +module Gem + class DependencyError < Gem::Exception; end + class RemoteSourceException < Gem::Exception; end + class GemNotFoundException < Gem::Exception; end + class RemoteInstallationCancelled < Gem::Exception; end + + #################################################################### + # RemoteSourceFetcher handles the details of fetching gems and gem + # information from a remote source. + class RemoteSourceFetcher + include UserInteraction + + # Initialize a remote fetcher using the source URI (and possible + # proxy information). + # +proxy+ + # * [String]: explicit specification of proxy; overrides any + # environment variable setting + # * nil: respect environment variables (HTTP_PROXY, HTTP_PROXY_USER, HTTP_PROXY_PASS) + # * :no_proxy: ignore environment variables and _don't_ + # use a proxy + def initialize(source_uri, proxy) + @uri = normalize_uri(source_uri) + @proxy_uri = + case proxy + when :no_proxy + nil + when nil + env_proxy = ENV['http_proxy'] || ENV['HTTP_PROXY'] + uri = env_proxy ? URI.parse(env_proxy) : nil + if uri and uri.user.nil? and uri.password.nil? + #Probably we have http_proxy_* variables? + uri.user = ENV['http_proxy_user'] || ENV['HTTP_PROXY_USER'] + uri.password = ENV['http_proxy_pass'] || ENV['HTTP_PROXY_PASS'] + end + uri + else + URI.parse(proxy.to_str) + end + end + + # The uncompressed +size+ of the source's directory (e.g. source + # info). + def size + @size ||= get_size("/yaml") + end + + # Fetch the data from the source at the given path. + def fetch_path(path="") + read_data(@uri + path) + end + + # Get the source index from the gem source. The source index is a + # directory of the gems available on the source, formatted as a + # Gem::Cache object. The cache object allows easy searching for + # gems by name and version requirement. + # + # Notice that the gem specs in the cache are adequate for searches + # and queries, but may have some information elided (hence + # "abbreviated"). + def source_index + say "Bulk updating Gem source index for: #{@uri}" + begin + require 'zlib' + yaml_spec = fetch_path("/yaml.Z") + yaml_spec = Zlib::Inflate.inflate(yaml_spec) + rescue + yaml_spec = nil + end + begin + yaml_spec = fetch_path("/yaml") unless yaml_spec + convert_spec(yaml_spec) + rescue SocketError => e + raise RemoteSourceException.new("Error fetching remote gem cache: #{e.to_s}") + end + end + + private + + # Normalize the URI by adding "http://" if it is missing. + def normalize_uri(uri) + (uri =~ /^(https?|ftp|file):/) ? uri : "http://#{uri}" + end + + # Connect to the source host/port, using a proxy if needed. + def connect_to(host, port) + if @proxy_uri + Net::HTTP::Proxy(@proxy_uri.host, @proxy_uri.port, @proxy_uri.user, @proxy_uri.password).new(host, port) + else + Net::HTTP.new(host, port) + end + end + + # Get the size of the (non-compressed) data from the source at the + # given path. + def get_size(path) + read_size(@uri + path) + end + + # Read the size of the (source based) URI using an HTTP HEAD + # command. + def read_size(uri) + return File.size(get_file_uri_path(uri)) if is_file_uri(uri) + require 'net/http' + require 'uri' + u = URI.parse(uri) + http = connect_to(u.host, u.port) + path = (u.path == "") ? "/" : u.path + resp = http.head(path) + fail RemoteSourceException, "HTTP Response #{resp.code}" if resp.code !~ /^2/ + resp['content-length'].to_i + end + + # Read the data from the (source based) URI. + def read_data(uri) + begin + open_uri_or_path(uri) do |input| + input.read + end + rescue + old_uri = uri + uri = uri.downcase + retry if old_uri != uri + raise + end + end + + # Read the data from the (source based) URI, but if it is a + # file:// URI, read from the filesystem instead. + def open_uri_or_path(uri, &block) + require 'rubygems/open-uri' + if is_file_uri(uri) + open(get_file_uri_path(uri), &block) + else + connection_options = {"User-Agent" => "RubyGems/#{Gem::RubyGemsVersion}"} + if @proxy_uri + http_proxy_url = "#{@proxy_uri.scheme}://#{@proxy_uri.host}:#{@proxy_uri.port}" + connection_options[:proxy_http_basic_authentication] = [http_proxy_url, @proxy_uri.user||'', @proxy_uri.password||''] + end + + open(uri, connection_options, &block) + end + end + + # Checks if the provided string is a file:// URI. + def is_file_uri(uri) + uri =~ %r{\Afile://} + end + + # Given a file:// URI, returns its local path. + def get_file_uri_path(uri) + uri.sub(%r{\Afile://}, '') + end + + # Convert the yamlized string spec into a real spec (actually, + # these are hashes of specs.). + def convert_spec(yaml_spec) + YAML.load(reduce_spec(yaml_spec)) or + fail "Didn't get a valid YAML document" + end + + # This reduces the source spec in size so that YAML bugs with + # large data sets will be dodged. Obviously this is a workaround, + # but it allows Gems to continue to work until the YAML bug is + # fixed. + def reduce_spec(yaml_spec) + result = "" + state = :copy + yaml_spec.each do |line| + if state == :copy && line =~ /^\s+files:\s*$/ + state = :skip + result << line.sub(/$/, " []") + elsif state == :skip + if line !~ /^\s+-/ + state = :copy + end + end + result << line if state == :copy + end + result + end + + class << self + # Sent by the client when it is done with all the sources, + # allowing any cleanup activity to take place. + def finish + # Nothing to do + end + end + end + + #################################################################### + # Entrys held by a SourceInfoCache. + class SourceInfoCacheEntry + # The source index for this cache entry. + attr_reader :source_index + + # The size of the of the source entry. Used to determine if the + # source index has changed. + attr_reader :size + + # Create a cache entry. + def initialize(si, size) + replace_source_index(si, size) + end + + # Replace the source index and the index size with given values. + def replace_source_index(si, size) + @source_index = si || SourceIndex.new({}) + @size = size + end + end + + #################################################################### + # SourceInfoCache implements the cache management policy on where + # the source info is stored on local file system. There are two + # possible cache locations: (1) the system wide cache, and (2) the + # user specific cache. + # + # * The system cache is prefered if it is writable (or can be + # created). + # * The user cache is used if the system cache is not writable (or + # can not be created). + # + # Once a cache is selected, it will be used for all operations. It + # will not switch between cache files dynamically. + # + # Cache data is a simple hash indexed by the source URI. Retrieving + # and entry from the cache data will return a SourceInfoCacheEntry. + # + class SourceInfoCache + + # The most recent cache data. + def cache_data + @dirty = false + @cache_data ||= read_cache + end + + # Write data to the proper cache. + def write_cache + data = cache_data + open(writable_file, "wb") do |f| + f.write Marshal.dump(data) + end + end + + # The name of the system cache file. + def system_cache_file + @sysetm_cache ||= File.join(Gem.dir, "source_cache") + end + + # The name of the user cache file. + def user_cache_file + @user_cache ||= + ENV['GEMCACHE'] || File.join(Gem.user_home, ".gem/source_cache") + end + + # Mark the cache as updated (i.e. dirty). + def update + @dirty = true + end + + # Write the cache to a local file (if it is dirty). + def flush + write_cache if @dirty + @dirty = false + end + + private + + # Find a writable cache file. + def writable_file + @cache_file + end + + # Read the most current cache data. + def read_cache + @cache_file = select_cache_file + begin + open(@cache_file, "rb") { |f| load_local_cache(f) } || {} + rescue StandardError => ex + {} + end + end + + def load_local_cache(f) + Marshal.load(f) + end + + # Select a writable cache file + def select_cache_file + try_file(system_cache_file) or + try_file(user_cache_file) or + fail "Unable to locate a writable cache file." + end + + # Determine if +fn+ is a candidate for a cache file. Return fn if + # it is. Return nil if it is not. + def try_file(fn) + return fn if File.writable?(fn) + return nil if File.exist?(fn) + dir = File.dirname(fn) + if ! File.exist? dir + begin + FileUtils.mkdir_p(dir) + rescue RuntimeError + return nil + end + end + if File.writable?(dir) + FileUtils.touch fn + return fn + end + nil + end + end + + #################################################################### + # CachedFetcher is a decorator that adds local file caching to + # RemoteSourceFetcher objects. + class CachedFetcher + + # Create a cached fetcher (based on a RemoteSourceFetcher) for the + # source at +source_uri+ (through the proxy +proxy+). + def initialize(source_uri, proxy) + require 'rubygems/incremental_fetcher' + @source_uri = source_uri + rsf = RemoteSourceFetcher.new(source_uri, proxy) + @fetcher = IncrementalFetcher.new(source_uri, rsf, manager) + end + + # The uncompressed +size+ of the source's directory (e.g. source + # info). + def size + @fetcher.size + end + + # Fetch the data from the source at the given path. + def fetch_path(path="") + @fetcher.fetch_path(path) + end + + # Get the source index from the gem source. The source index is a + # directory of the gems available on the source, formatted as a + # Gem::Cache object. The cache object allows easy searching for + # gems by name and version requirement. + # + # Notice that the gem specs in the cache are adequate for searches + # and queries, but may have some information elided (hence + # "abbreviated"). + def source_index + cache = manager.cache_data[@source_uri] + if cache && cache.size == @fetcher.size + cache.source_index + else + result = @fetcher.source_index + manager.cache_data[@source_uri] = SourceInfoCacheEntry.new(result, @fetcher.size) + manager.update + result + end + end + + # Flush the cache to a local file, if needed. + def flush + manager.flush + end + + private + + # The cache manager for this cached source. + def manager + self.class.manager + end + + # The cache is shared between all caching fetchers, so the cache + # is put in the class object. + class << self + + # The Cache manager for all instances of this class. + def manager + @manager ||= SourceInfoCache.new + end + + # Sent by the client when it is done with all the sources, + # allowing any cleanup activity to take place. + def finish + manager.flush + end + end + + end + + class RemoteInstaller + include UserInteraction + + # options[:http_proxy]:: + # * [String]: explicit specification of proxy; overrides any + # environment variable setting + # * nil: respect environment variables (HTTP_PROXY, HTTP_PROXY_USER, HTTP_PROXY_PASS) + # * :no_proxy: ignore environment variables and _don't_ + # use a proxy + # + def initialize(options={}) + require 'uri' + + # Ensure http_proxy env vars are used if no proxy explicitly supplied. + @options = options + @fetcher_class = CachedFetcher + end + + # This method will install package_name onto the local system. + # + # gem_name:: + # [String] Name of the Gem to install + # + # version_requirement:: + # [default = "> 0.0.0"] Gem version requirement to install + # + # Returns:: + # an array of Gem::Specification objects, one for each gem installed. + # + def install(gem_name, version_requirement = "> 0.0.0", force=false, install_dir=Gem.dir, install_stub=true) + unless version_requirement.respond_to?(:satisfied_by?) + version_requirement = Version::Requirement.new(version_requirement) + end + installed_gems = [] + caches = source_index_hash + spec, source = find_gem_to_install(gem_name, version_requirement, caches) + dependencies = find_dependencies_not_installed(spec.dependencies) + installed_gems << install_dependencies(dependencies, force, install_dir) + cache_dir = File.join(install_dir, "cache") + destination_file = File.join(cache_dir, spec.full_name + ".gem") + download_gem(destination_file, source, spec) + installer = new_installer(destination_file) + installed_gems.unshift installer.install(force, install_dir, install_stub) + installed_gems.flatten + end + + # Search Gem repository for a gem by specifying all or part of + # the Gem's name + def search(pattern_to_match) + results = [] + caches = source_index_hash + caches.each do |cache| + results << cache[1].search(pattern_to_match) + end + results + end + + # Return a list of the sources that we can download gems from + def sources + unless @sources + require 'sources' + @sources = Gem.sources + end + @sources + end + + # Return a hash mapping the available source names to the source + # index of that source. + def source_index_hash + result = {} + sources.each do |source| + result[source] = fetch_source(source) + end + @fetcher_class.finish + result + end + + # Return the source info for the given source. The + def fetch_source(source) + rsf = @fetcher_class.new(source, @options[:http_proxy]) + rsf.source_index + end + + # Find a gem to be installed by interacting with the user. + def find_gem_to_install(gem_name, version_requirement, caches) + specs_n_sources = [] + + caches.each do |source, cache| + cache.each do |name, spec| + if /^#{gem_name}$/i === spec.name && + version_requirement.satisfied_by?(spec.version) then + specs_n_sources << [spec, source] + end + end + end + + if specs_n_sources.empty? then + raise GemNotFoundException.new("Could not find #{gem_name} (#{version_requirement}) in the repository") + end + + specs_n_sources = specs_n_sources.sort_by { |gs,| gs.version }.reverse + + non_binary_gems = specs_n_sources.reject { |item| + item[0].platform.nil? || item[0].platform==Platform::RUBY + } + + # only non-binary gems...return latest + return specs_n_sources.first if non_binary_gems.empty? + + list = specs_n_sources.collect { |item| + "#{item[0].name} #{item[0].version} (#{item[0].platform.to_s})" + } + + list << "Cancel installation" + + string, index = choose_from_list( + "Select which gem to install for your platform (#{RUBY_PLATFORM})", + list) + + if index == (list.size - 1) then + raise RemoteInstallationCancelled, "Installation of #{gem_name} cancelled." + end + + specs_n_sources[index] + end + + def find_dependencies_not_installed(dependencies) + to_install = [] + dependencies.each do |dependency| + srcindex = Gem::SourceIndex.from_installed_gems + matches = srcindex.find_name(dependency.name, dependency.requirement_list) + to_install.push dependency if matches.empty? + end + to_install + end + + # Install all the given dependencies. Returns an array of + # Gem::Specification objects, one for each dependency installed. + # + # TODO: For now, we recursively install, but this is not the right + # way to do things (e.g. if a package fails to download, we + # shouldn't install anything). + def install_dependencies(dependencies, force, install_dir) + return if @options[:ignore_dependencies] + installed_gems = [] + dependencies.each do |dependency| + if @options[:include_dependencies] || + ask_yes_no("Install required dependency #{dependency.name}?", true) + remote_installer = RemoteInstaller.new(@options) + installed_gems << remote_installer.install( + dependency.name, + dependency.version_requirements, + force, + install_dir) + else + raise DependencyError.new("Required dependency #{dependency.name} not installed") + end + end + installed_gems + end + + def download_gem(destination_file, source, spec) + rsf = @fetcher_class.new(source, @proxy_uri) + path = "/gems/#{spec.full_name}.gem" + response = rsf.fetch_path(path) + write_gem_to_file(response, destination_file) + end + + def write_gem_to_file(body, destination_file) + FileUtils.mkdir_p(File.dirname(destination_file)) unless File.exist?(destination_file) + File.open(destination_file, 'wb') do |out| + out.write(body) + end + end + + def new_installer(gem) + return Installer.new(gem, @options) + end + end + +end diff --git a/amarok/src/mediadevice/daap/mongrel/lib/rubygems/rubygems_version.rb b/amarok/src/mediadevice/daap/mongrel/lib/rubygems/rubygems_version.rb new file mode 100644 index 00000000..61a4fc00 --- /dev/null +++ b/amarok/src/mediadevice/daap/mongrel/lib/rubygems/rubygems_version.rb @@ -0,0 +1,6 @@ +# DO NOT EDIT +# This file is auto-generated by build scripts. +# See: rake update_version +module Gem + RubyGemsVersion = '0.9.0' +end diff --git a/amarok/src/mediadevice/daap/mongrel/lib/rubygems/security.rb b/amarok/src/mediadevice/daap/mongrel/lib/rubygems/security.rb new file mode 100644 index 00000000..f7264438 --- /dev/null +++ b/amarok/src/mediadevice/daap/mongrel/lib/rubygems/security.rb @@ -0,0 +1,483 @@ +#-- +# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others. +# All rights reserved. +# See LICENSE.txt for permissions. +#++ + + +require 'rubygems/gem_openssl' + +module Gem + module SSL + + # We make our own versions of the constants here. This allows us + # to reference the constants, even though some systems might not + # have SSL installed in the Ruby core package. + # + # These constants are only used during load time. At runtime, any + # method that makes a direct reference to SSL software must be + # protected with a Gem.ensure_ssl_available call. + # + if Gem.ssl_available? + PKEY_RSA = OpenSSL::PKey::RSA + DIGEST_SHA1 = OpenSSL::Digest::SHA1 + else + PKEY_RSA = :rsa + DIGEST_SHA1 = :sha1 + end + end +end + +module OpenSSL + module X509 + class Certificate + # + # Check the validity of this certificate. + # + def check_validity(issuer_cert = nil, time = Time.now) + ret = if @not_before && @not_before > time + [false, :expired, "not valid before '#@not_before'"] + elsif @not_after && @not_after < time + [false, :expired, "not valid after '#@not_after'"] + elsif issuer_cert && !verify(issuer_cert.public_key) + [false, :issuer, "#{issuer_cert.subject} is not issuer"] + else + [true, :ok, 'Valid certificate'] + end + + # return hash + { :is_valid => ret[0], :error => ret[1], :desc => ret[2] } + end + end + end +end + +module Gem + # + # Security: a set of methods, classes, and security policies for + # checking the validity of signed gem files. + # + module Security + class Exception < Exception; end + + # + # default options for most of the methods below + # + OPT = { + # private key options + :key_algo => Gem::SSL::PKEY_RSA, + :key_size => 2048, + + # public cert options + :cert_age => 365 * 24 * 3600, # 1 year + :dgst_algo => Gem::SSL::DIGEST_SHA1, + + # x509 certificate extensions + :cert_exts => { + 'basicConstraints' => 'CA:FALSE', + 'subjectKeyIdentifier' => 'hash', + 'keyUsage' => 'keyEncipherment,dataEncipherment,digitalSignature', + }, + + # save the key and cert to a file in build_self_signed_cert()? + :save_key => true, + :save_cert => true, + + # if you define either of these, then they'll be used instead of + # the output_fmt macro below + :save_key_path => nil, + :save_cert_path => nil, + + # output name format for self-signed certs + :output_fmt => 'gem-%s.pem', + :munge_re => Regexp.new(/[^a-z0-9_.-]+/), + + # output directory for trusted certificate checksums + :trust_dir => File::join(Gem.user_home, '.gem', 'trust'), + } + + # + # A Gem::Security::Policy object encapsulates the settings for + # verifying signed gem files. This is the base class. You can + # either declare an instance of this or use one of the preset + # security policies below. + # + class Policy + attr_accessor :verify_data, :verify_signer, :verify_chain, + :verify_root, :only_trusted, :only_signed + + # + # Create a new Gem::Security::Policy object with the given mode + # and options. + # + def initialize(policy = {}, opt = {}) + # set options + @opt = Gem::Security::OPT.merge(opt) + + # build policy + policy.each_pair do |key, val| + case key + when :verify_data then @verify_data = val + when :verify_signer then @verify_signer = val + when :verify_chain then @verify_chain = val + when :verify_root then @verify_root = val + when :only_trusted then @only_trusted = val + when :only_signed then @only_signed = val + end + end + end + + # + # Get the path to the file for this cert. + # + def self.trusted_cert_path(cert, opt = {}) + opt = Gem::Security::OPT.merge(opt) + + # get digest algorithm, calculate checksum of root.subject + algo = opt[:dgst_algo] + dgst = algo.hexdigest(cert.subject.to_s) + + # build path to trusted cert file + name = "cert-#{dgst}.pem" + + # join and return path components + File::join(opt[:trust_dir], name) + end + + # + # Verify that the gem data with the given signature and signing + # chain matched this security policy at the specified time. + # + def verify_gem(signature, data, chain, time = Time.now) + Gem.ensure_ssl_available + cert_class = OpenSSL::X509::Certificate + exc = Gem::Security::Exception + chain ||= [] + + chain = chain.map{ |str| cert_class.new(str) } + signer, ch_len = chain[-1], chain.size + + # make sure signature is valid + if @verify_data + # get digest algorithm (TODO: this should be configurable) + dgst = @opt[:dgst_algo] + + # verify the data signature (this is the most important part, + # so don't screw it up :D) + v = signer.public_key.verify(dgst.new, signature, data) + raise exc, "Invalid Gem Signature" unless v + + # make sure the signer is valid + if @verify_signer + # make sure the signing cert is valid right now + v = signer.check_validity(nil, time) + raise exc, "Invalid Signature: #{v[:desc]}" unless v[:is_valid] + end + end + + # make sure the certificate chain is valid + if @verify_chain + # iterate down over the chain and verify each certificate + # against it's issuer + (ch_len - 1).downto(1) do |i| + issuer, cert = chain[i - 1, 2] + v = cert.check_validity(issuer, time) + raise exc, "%s: cert = '%s', error = '%s'" % [ + 'Invalid Signing Chain', cert.subject, v[:desc] + ] unless v[:is_valid] + end + + # verify root of chain + if @verify_root + # make sure root is self-signed + root = chain[0] + raise exc, "%s: %s (subject = '%s', issuer = '%s')" % [ + 'Invalid Signing Chain Root', + 'Subject does not match Issuer for Gem Signing Chain', + root.subject.to_s, + root.issuer.to_s, + ] unless root.issuer.to_s == root.subject.to_s + + # make sure root is valid + v = root.check_validity(root, time) + raise exc, "%s: cert = '%s', error = '%s'" % [ + 'Invalid Signing Chain Root', root.subject, v[:desc] + ] unless v[:is_valid] + + # verify that the chain root is trusted + if @only_trusted + # get digest algorithm, calculate checksum of root.subject + algo = @opt[:dgst_algo] + path = Gem::Security::Policy.trusted_cert_path(root, @opt) + + # check to make sure trusted path exists + raise exc, "%s: cert = '%s', error = '%s'" % [ + 'Untrusted Signing Chain Root', + root.subject.to_s, + "path \"#{path}\" does not exist", + ] unless File.exists?(path) + + # load calculate digest from saved cert file + save_cert = OpenSSL::X509::Certificate.new(File.read(path)) + save_dgst = algo.digest(save_cert.public_key.to_s) + + # create digest of public key + pkey_str = root.public_key.to_s + cert_dgst = algo.digest(pkey_str) + + # now compare the two digests, raise exception + # if they don't match + raise exc, "%s: %s (saved = '%s', root = '%s')" % [ + 'Invalid Signing Chain Root', + "Saved checksum doesn't match root checksum", + save_dgst, cert_dgst, + ] unless save_dgst == cert_dgst + end + end + + # return the signing chain + chain.map { |cert| cert.subject } + end + end + end + + # + # No security policy: all package signature checks are disabled. + # + NoSecurity = Policy.new({ + :verify_data => false, + :verify_signer => false, + :verify_chain => false, + :verify_root => false, + :only_trusted => false, + :only_signed => false, + }) + + # + # AlmostNo security policy: only verify that the signing certificate + # is the one that actually signed the data. Make no attempt to + # verify the signing certificate chain. + # + # This policy is basically useless. better than nothing, but can still be easily + # spoofed, and is not recommended. + # + AlmostNoSecurity = Policy.new({ + :verify_data => true, + :verify_signer => false, + :verify_chain => false, + :verify_root => false, + :only_trusted => false, + :only_signed => false, + }) + + # + # Low security policy: only verify that the signing certificate is + # actually the gem signer, and that the signing certificate is + # valid. + # + # This policy is better than nothing, but can still be easily + # spoofed, and is not recommended. + # + LowSecurity = Policy.new({ + :verify_data => true, + :verify_signer => true, + :verify_chain => false, + :verify_root => false, + :only_trusted => false, + :only_signed => false, + }) + + # + # Medium security policy: verify the signing certificate, verify the + # signing certificate chain all the way to the root certificate, and + # only trust root certificates that we have explicity allowed trust + # for. + # + # This security policy is reasonable, but it allows unsigned + # packages, so a malicious person could simply delete the package + # signature and pass the gem off as unsigned. + # + MediumSecurity = Policy.new({ + :verify_data => true, + :verify_signer => true, + :verify_chain => true, + :verify_root => true, + :only_trusted => true, + :only_signed => false, + }) + + # + # High security policy: only allow signed gems to be installed, + # verify the signing certificate, verify the signing certificate + # chain all the way to the root certificate, and only trust root + # certificates that we have explicity allowed trust for. + # + # This security policy is significantly more difficult to bypass, + # and offers a reasonable guarantee that the contents of the gem + # have not been altered. + # + HighSecurity = Policy.new({ + :verify_data => true, + :verify_signer => true, + :verify_chain => true, + :verify_root => true, + :only_trusted => true, + :only_signed => true, + }) + + # + # Sign the cert cert with @signing_key and @signing_cert, using the + # digest algorithm opt[:dgst_algo]. Returns the newly signed + # certificate. + # + def self.sign_cert(cert, signing_key, signing_cert, opt = {}) + opt = OPT.merge(opt) + + # set up issuer information + cert.issuer = signing_cert.subject + cert.sign(signing_key, opt[:dgst_algo].new) + + cert + end + + # + # Build a certificate from the given DN and private key. + # + def self.build_cert(name, key, opt = {}) + Gem.ensure_ssl_available + opt = OPT.merge(opt) + + # create new cert + ret = OpenSSL::X509::Certificate.new + + # populate cert attributes + ret.version = 2 + ret.serial = 0 + ret.public_key = key.public_key + ret.not_before = Time.now + ret.not_after = Time.now + opt[:cert_age] + ret.subject = name + + # add certificate extensions + ef = OpenSSL::X509::ExtensionFactory.new(nil, ret) + ret.extensions = opt[:cert_exts].map { |k, v| ef.create_extension(k, v) } + + # sign cert + i_key, i_cert = opt[:issuer_key] || key, opt[:issuer_cert] || ret + ret = sign_cert(ret, i_key, i_cert, opt) + + # return cert + ret + end + + # + # Build a self-signed certificate for the given email address. + # + def self.build_self_signed_cert(email_addr, opt = {}) + Gem.ensure_ssl_available + opt = OPT.merge(opt) + path = { :key => nil, :cert => nil } + + # split email address up + cn, dcs = email_addr.split('@') + dcs = dcs.split('.') + + # munge email CN and DCs + cn = cn.gsub(opt[:munge_re], '_') + dcs = dcs.map { |dc| dc.gsub(opt[:munge_re], '_') } + + # create DN + name = "CN=#{cn}/" << dcs.map { |dc| "DC=#{dc}" }.join('/') + name = OpenSSL::X509::Name::parse(name) + + # build private key + key = opt[:key_algo].new(opt[:key_size]) + + # create the trust directory if it doesn't exist + FileUtils::mkdir_p(opt[:trust_dir]) unless File.exists?(opt[:trust_dir]) + + # if we're saving the key, then write it out + if opt[:save_key] + path[:key] = opt[:save_key_path] || (opt[:output_fmt] % 'private_key') + File.open(path[:key], 'wb') { |file| file.write(key.to_pem) } + end + + # build self-signed public cert from key + cert = build_cert(name, key, opt) + + # if we're saving the cert, then write it out + if opt[:save_cert] + path[:cert] = opt[:save_cert_path] || (opt[:output_fmt] % 'public_cert') + File.open(path[:cert], 'wb') { |file| file.write(cert.to_pem) } + end + + # return key, cert, and paths (if applicable) + { :key => key, :cert => cert, + :key_path => path[:key], :cert_path => path[:cert] } + end + + # + # Add certificate to trusted cert list. + # + # Note: At the moment these are stored in OPT[:trust_dir], although + # that directory may change in the future. + # + def self.add_trusted_cert(cert, opt = {}) + opt = OPT.merge(opt) + + # get destination path + path = Gem::Security::Policy.trusted_cert_path(cert, opt) + + # write cert to output file + File.open(path, 'wb') { |file| file.write(cert.to_pem) } + + # return nil + nil + end + + # + # Basic OpenSSL-based package signing class. + # + class Signer + attr_accessor :key, :cert_chain + + def initialize(key, cert_chain) + Gem.ensure_ssl_available + @algo = Gem::Security::OPT[:dgst_algo] + @key, @cert_chain = key, cert_chain + + # check key, if it's a file, and if it's key, leave it alone + if @key && !@key.kind_of?(OpenSSL::PKey::PKey) + @key = OpenSSL::PKey::RSA.new(File.read(@key)) + end + + # check cert chain, if it's a file, load it, if it's cert data, convert + # it into a cert object, and if it's a cert object, leave it alone + if @cert_chain + @cert_chain = @cert_chain.map do |cert| + # check cert, if it's a file, load it, if it's cert data, + # convert it into a cert object, and if it's a cert object, + # leave it alone + if cert && !cert.kind_of?(OpenSSL::X509::Certificate) + cert = File.read(cert) if File::exists?(cert) + cert = OpenSSL::X509::Certificate.new(cert) + end + cert + end + end + end + + # + # Sign data with given digest algorithm + # + def sign(data) + @key.sign(@algo.new, data) + end + + # moved to security policy (see above) + # def verify(sig, data) + # @cert.public_key.verify(@algo.new, sig, data) + # end + end + end +end diff --git a/amarok/src/mediadevice/daap/mongrel/lib/rubygems/source_index.rb b/amarok/src/mediadevice/daap/mongrel/lib/rubygems/source_index.rb new file mode 100644 index 00000000..8d638c21 --- /dev/null +++ b/amarok/src/mediadevice/daap/mongrel/lib/rubygems/source_index.rb @@ -0,0 +1,207 @@ +#-- +# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others. +# All rights reserved. +# See LICENSE.txt for permissions. +#++ + +require 'rubygems/user_interaction' + +require 'forwardable' +require 'digest/sha2' +require 'time' + +module Gem + + # The SourceIndex object indexes all the gems available from a + # particular source (e.g. a list of gem directories, or a remote + # source). A SourceIndex maps a gem full name to a gem + # specification. + # + # NOTE:: The class used to be named Cache, but that became + # confusing when cached source fetchers where introduced. The + # constant Gem::Cache is an alias for this class to allow old + # YAMLized source index objects to load properly. + # + class SourceIndex + extend Forwardable + include Enumerable + + # Class Methods. ------------------------------------------------- + class << self + include Gem::UserInteraction + + # Factory method to construct a source index instance for a given + # path. + # + # deprecated:: + # If supplied, from_installed_gems will act just like + # +from_gems_in+. This argument is deprecated and is provided + # just for backwards compatibility, and should not generally + # be used. + # + # return:: + # SourceIndex instance + # + def from_installed_gems(*deprecated) + if deprecated.empty? + from_gems_in(*installed_spec_directories) + else + from_gems_in(*deprecated) + end + end + + # Return a list of directories in the current gem path that + # contain specifications. + # + # return:: + # List of directory paths (all ending in "../specifications"). + # + def installed_spec_directories + Gem.path.collect { |dir| File.join(dir, "specifications") } + end + + # Factory method to construct a source index instance for a + # given path. + # + # spec_dirs:: + # List of directories to search for specifications. Each + # directory should have a "specifications" subdirectory + # containing the gem specifications. + # + # return:: + # SourceIndex instance + # + def from_gems_in(*spec_dirs) + self.new.load_gems_in(*spec_dirs) + end + + # Load a specification from a file (eval'd Ruby code) + # + # file_name:: [String] The .gemspec file + # return:: Specification instance or nil if an error occurs + # + def load_specification(file_name) + begin + spec_code = File.read(file_name).untaint + gemspec = eval(spec_code) + if gemspec.is_a?(Gem::Specification) + gemspec.loaded_from = file_name + return gemspec + end + alert_warning "File '#{file_name}' does not evaluate to a gem specification" + rescue SyntaxError => e + alert_warning e + alert_warning spec_code + rescue Exception => e + alert_warning(e.inspect.to_s + "\n" + spec_code) + alert_warning "Invalid .gemspec format in '#{file_name}'" + end + return nil + end + + end + + # Instance Methods ----------------------------------------------- + + # Constructs a source index instance from the provided + # specifications + # + # specifications:: + # [Hash] hash of [Gem name, Gem::Specification] pairs + # + def initialize(specifications={}) + @gems = specifications + end + + # Reconstruct the source index from the list of source + # directories. + def load_gems_in(*spec_dirs) + @gems.clear + Dir.glob("{#{spec_dirs.join(',')}}/*.gemspec").each do |file_name| + gemspec = self.class.load_specification(file_name.untaint) + add_spec(gemspec) if gemspec + end + self + end + + # Add a gem specification to the source index. + def add_spec(gem_spec) + @gems[gem_spec.full_name] = gem_spec + end + + # Remove a gem specification named +full_name+. + def remove_spec(full_name) + @gems.delete(full_name) + end + + # Iterate over the specifications in the source index. + # + # &block:: [yields gem.full_name, Gem::Specification] + # + def each(&block) + @gems.each(&block) + end + + # The gem specification given a full gem spec name. + def specification(full_name) + @gems[full_name] + end + + # The signature for the source index. Changes in the signature + # indicate a change in the index. + def index_signature + Digest::SHA256.new(@gems.keys.sort.join(',')).to_s + end + + # The signature for the given gem specification. + def gem_signature(gem_full_name) + Digest::SHA256.new(@gems[gem_full_name].to_yaml).to_s + end + + def_delegators :@gems, :size, :length + + # Find a gem by an exact match on the short name. + def find_name(gem_name, version_requirement=Version::Requirement.new(">= 0")) + search(/^#{gem_name}$/, version_requirement) + end + + # Search for a gem by short name pattern and optional version + # + # gem_name:: + # [String] a partial for the (short) name of the gem, or + # [Regex] a pattern to match against the short name + # version_requirement:: + # [String | default=Version::Requirement.new(">= 0")] version to + # find + # return:: + # [Array] list of Gem::Specification objects in sorted (version) + # order. Empty if not found. + # + def search(gem_pattern, version_requirement=Version::Requirement.new(">= 0")) + #FIXME - remove duplication between this and RemoteInstaller.search + gem_pattern = /#{ gem_pattern }/i if String === gem_pattern + version_requirement = Gem::Version::Requirement.create(version_requirement) + result = [] + @gems.each do |full_spec_name, spec| + next unless spec.name =~ gem_pattern + result << spec if version_requirement.satisfied_by?(spec.version) + end + result = result.sort + result + end + + # Refresh the source index from the local file system. + # + # return:: Returns a pointer to itself. + # + def refresh! + load_gems_in(self.class.installed_spec_directories) + end + + end + + # Cache is an alias for SourceIndex to allow older YAMLized source + # index objects to load properly. + Cache = SourceIndex + +end diff --git a/amarok/src/mediadevice/daap/mongrel/lib/rubygems/specification.rb b/amarok/src/mediadevice/daap/mongrel/lib/rubygems/specification.rb new file mode 100644 index 00000000..b47a1b08 --- /dev/null +++ b/amarok/src/mediadevice/daap/mongrel/lib/rubygems/specification.rb @@ -0,0 +1,660 @@ +#-- +# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others. +# All rights reserved. +# See LICENSE.txt for permissions. +#++ + +require 'time' +require 'rubygems' +require 'rubygems/version' + +class Time + def self.today + Time.parse Time.now.strftime("%Y-%m-%d") + end +end + +module Gem + + # == Gem::Platform + # + # Available list of platforms for targeting Gem installations. + # Platform::RUBY is the default platform (pure Ruby Gem). + # + module Platform + RUBY = 'ruby' + WIN32 = 'mswin32' + LINUX_586 = 'i586-linux' + DARWIN = 'powerpc-darwin' + CURRENT = 'current' + end + + # Potentially raised when a specification is validated. + class InvalidSpecificationException < Gem::Exception; end + class EndOfYAMLException < Gem::Exception; end + + # == Gem::Specification + # + # The Specification class contains the metadata for a Gem. Typically defined in a + # .gemspec file or a Rakefile, and looks like this: + # + # spec = Gem::Specification.new do |s| + # s.name = 'rfoo' + # s.version = '1.0' + # s.summary = 'Example gem specification' + # ... + # end + # + # There are many gemspec attributes, and the best place to learn about them in + # the "Gemspec Reference" linked from the RubyGems wiki. + # + class Specification + + # ------------------------- Specification version contstants. + + # The the version number of a specification that does not specify one (i.e. RubyGems 0.7 + # or earlier). + NONEXISTENT_SPECIFICATION_VERSION = -1 + + # The specification version applied to any new Specification instances created. This + # should be bumped whenever something in the spec format changes. + CURRENT_SPECIFICATION_VERSION = 1 + + # An informal list of changes to the specification. The highest-valued key should be + # equal to the CURRENT_SPECIFICATION_VERSION. + SPECIFICATION_VERSION_HISTORY = { + -1 => ['(RubyGems versions up to and including 0.7 did not have versioned specifications)'], + 1 => [ + 'Deprecated "test_suite_file" in favor of the new, but equivalent, "test_files"', + '"test_file=x" is a shortcut for "test_files=[x]"' + ] + } + + # ------------------------- Class variables. + + # List of Specification instances. + @@list = [] + + # Optional block used to gather newly defined instances. + @@gather = nil + + # List of attribute names: [:name, :version, ...] + @@required_attributes = [] + + # List of _all_ attributes and default values: [[:name, nil], [:bindir, 'bin'], ...] + @@attributes = [] + + # List of array attributes + @@array_attributes = [] + + # Map of attribute names to default values. + @@default_value = {} + + # ------------------------- Convenience class methods. + + def self.attribute_names + @@attributes.map { |name, default| name } + end + + def self.attribute_defaults + @@attributes.dup + end + + def self.default_value(name) + @@default_value[name] + end + + def self.required_attributes + @@required_attributes.dup + end + + def self.required_attribute?(name) + @@required_attributes.include? name.to_sym + end + + def self.array_attributes + @@array_attributes.dup + end + + # ------------------------- Infrastructure class methods. + + # A list of Specification instances that have been defined in this Ruby instance. + def self.list + @@list + end + + # Used to specify the name and default value of a specification + # attribute. The side effects are: + # * the name and default value are added to the @@attributes list + # and @@default_value map + # * a standard _writer_ method (attribute=) is created + # * a non-standard _reader method (attribute) is created + # + # The reader method behaves like this: + # def attribute + # @attribute ||= (copy of default value) + # end + # + # This allows lazy initialization of attributes to their default + # values. + # + def self.attribute(name, default=nil) + @@attributes << [name, default] + @@default_value[name] = default + attr_accessor(name) + end + + # Same as :attribute, but ensures that values assigned to the + # attribute are array values by applying :to_a to the value. + def self.array_attribute(name) + @@array_attributes << name + @@attributes << [name, []] + @@default_value[name] = [] + module_eval %{ + def #{name} + @#{name} ||= [] + end + def #{name}=(value) + @#{name} = value.to_a + end + } + end + + # Same as attribute above, but also records this attribute as mandatory. + def self.required_attribute(*args) + @@required_attributes << args.first + attribute(*args) + end + + # Sometimes we don't want the world to use a setter method for a particular attribute. + # +read_only+ makes it private so we can still use it internally. + def self.read_only(*names) + names.each do |name| + private "#{name}=" + end + end + + # Shortcut for creating several attributes at once (each with a default value of + # +nil+). + def self.attributes(*args) + args.each do |arg| + attribute(arg, nil) + end + end + + # Some attributes require special behaviour when they are accessed. This allows for + # that. + def self.overwrite_accessor(name, &block) + remove_method name + define_method(name, &block) + end + + # Defines a _singular_ version of an existing _plural_ attribute + # (i.e. one whose value is expected to be an array). This means + # just creating a helper method that takes a single value and + # appends it to the array. These are created for convenience, so + # that in a spec, one can write + # + # s.require_path = 'mylib' + # + # instead of + # + # s.require_paths = ['mylib'] + # + # That above convenience is available courtesy of + # + # attribute_alias_singular :require_path, :require_paths + # + def self.attribute_alias_singular(singular, plural) + define_method("#{singular}=") { |val| + send("#{plural}=", [val]) + } + define_method("#{singular}") { + val = send("#{plural}") + val.nil? ? nil : val.first + } + end + + def warn_deprecated(old, new) + # How (if at all) to implement this? We only want to warn when + # a gem is being built, I should think. + end + + # REQUIRED gemspec attributes ------------------------------------ + + required_attribute :rubygems_version, RubyGemsVersion + required_attribute :specification_version, CURRENT_SPECIFICATION_VERSION + required_attribute :name + required_attribute :version + required_attribute :date + required_attribute :summary + required_attribute :require_paths, ['lib'] + + read_only :specification_version + + # OPTIONAL gemspec attributes ------------------------------------ + + attributes :email, :homepage, :rubyforge_project, :description + attributes :autorequire, :default_executable + attribute :bindir, 'bin' + attribute :has_rdoc, false + attribute :required_ruby_version, Gem::Version::Requirement.default + attribute :platform, Gem::Platform::RUBY + + attribute :signing_key, nil + attribute :cert_chain, nil + attribute :post_install_message, nil + + array_attribute :authors + array_attribute :files + array_attribute :test_files + array_attribute :rdoc_options + array_attribute :extra_rdoc_files + array_attribute :executables + array_attribute :extensions + array_attribute :requirements + array_attribute :dependencies + + read_only :dependencies + + # ALIASED gemspec attributes ------------------------------------- + + attribute_alias_singular :executable, :executables + attribute_alias_singular :author, :authors + attribute_alias_singular :require_path, :require_paths + attribute_alias_singular :test_file, :test_files + + # DEPRECATED gemspec attributes ---------------------------------- + + def test_suite_file + warn_deprecated(:test_suite_file, :test_files) + test_files.first + end + + def test_suite_file=(val) + warn_deprecated(:test_suite_file, :test_files) + @test_files << val + end + + # RUNTIME attributes (not persisted) ----------------------------- + + attr_writer :loaded + attr_accessor :loaded_from + + # Special accessor behaviours (overwriting default) -------------- + + overwrite_accessor :version= do |version| + @version = Version.create(version) + end + + overwrite_accessor :platform= do |platform| + # Checks the provided platform for the special value + # Platform::CURRENT and changes it to be binary specific to the + # current platform (i386-mswin32, etc). + @platform = (platform == Platform::CURRENT ? RUBY_PLATFORM : platform) + end + + overwrite_accessor :required_ruby_version= do |value| + @required_ruby_version = Version::Requirement.create(value) + end + + overwrite_accessor :date= do |date| + # We want to end up with a Time object with one-day resolution. + # This is the cleanest, most-readable, faster-than-using-Date + # way to do it. + case date + when String then + @date = Time.parse date + when Time then + @date = Time.parse date.strftime("%Y-%m-%d") + when Date then + @date = Time.parse date.to_s + else + @date = Time.today + end + end + + overwrite_accessor :date do + self.date = nil if @date.nil? # HACK Sets the default value for date + @date + end + + overwrite_accessor :summary= do |str| + if str + @summary = str.strip. + gsub(/(\w-)\n[ \t]*(\w)/, '\1\2'). + gsub(/\n[ \t]*/, " ") + end + end + + overwrite_accessor :description= do |str| + if str + @description = str.strip. + gsub(/(\w-)\n[ \t]*(\w)/, '\1\2'). + gsub(/\n[ \t]*/, " ") + end + end + + overwrite_accessor :default_executable do + return @default_executable if @default_executable + # Special case: if there is only one executable specified, then + # that's obviously the default one. + return @executables.first if @executables.size == 1 + nil + end + + def add_bindir(executables) + if(@executables.nil?) + return nil + end + if(@bindir) + @executables.map {|e| File.join(@bindir, e) } + else + @executables + end + end + + overwrite_accessor :files do + (@files || []) | (@test_files || []) | (add_bindir(@executables) || []) | + (@extra_rdoc_files || []) | (@extensions || []) + end + + overwrite_accessor :test_files do + # Handle the possibility that we have @test_suite_file but not + # @test_files. This will happen when an old gem is loaded via + # YAML. + if @test_suite_file + @test_files = [@test_suite_file].flatten + @test_suite_file = nil + end + @test_files ||= [] + end + + # Predicates ----------------------------------------------------- + + def loaded?; @loaded ? true : false ; end + def has_rdoc?; has_rdoc ? true : false ; end + def has_unit_tests?; not test_files.empty?; end + alias has_test_suite? has_unit_tests? # (deprecated) + + # Constructors --------------------------------------------------- + + # Specification constructor. Assigns the default values to the + # attributes, adds this spec to the list of loaded specs (see + # Specification.list), and yields itself for further initialization. + # + def initialize + # Each attribute has a default value (possibly nil). Here, we + # initialize all attributes to their default value. This is + # done through the accessor methods, so special behaviours will + # be honored. Furthermore, we take a _copy_ of the default so + # each specification instance has its own empty arrays, etc. + @@attributes.each do |name, default| + if RUBY_VERSION >= "1.9" then + self.funcall "#{name}=", copy_of(default) + else + self.send "#{name}=", copy_of(default) + end + end + @loaded = false + @@list << self + yield self if block_given? + @@gather.call(self) if @@gather + end + + # Special loader for YAML files. When a Specification object is + # loaded from a YAML file, it bypasses the normal Ruby object + # initialization routine (#initialize). This method makes up for + # that and deals with gems of different ages. + # + # 'input' can be anything that YAML.load() accepts: String or IO. + # + def Specification.from_yaml(input) + input = normalize_yaml_input(input) + spec = YAML.load(input) + if(spec.class == FalseClass) then + raise Gem::EndOfYAMLException + end + unless Specification === spec + raise Gem::Exception, "YAML data doesn't evaluate to gem specification" + end + unless spec.instance_variable_get :@specification_version + spec.instance_variable_set :@specification_version, + NONEXISTENT_SPECIFICATION_VERSION + end + spec + end + + def Specification.load(filename) + gemspec = nil + fail "NESTED Specification.load calls not allowed!" if @@gather + @@gather = proc { |gs| gemspec = gs } + data = File.read(filename) + eval(data) + gemspec + ensure + @@gather = nil + end + + # Make sure the yaml specification is properly formatted with dashes. + def Specification.normalize_yaml_input(input) + result = input.respond_to?(:read) ? input.read : input + result = "--- " + result unless result =~ /^--- / + result + end + + # Instance methods ----------------------------------------------- + + # Sets the rubygems_version to Gem::RubyGemsVersion. + # + def mark_version + @rubygems_version = RubyGemsVersion + end + + # Adds a dependency to this Gem. For example, + # + # spec.add_dependency('jabber4r', '> 0.1', '<= 0.5') + # + # gem:: [String or Gem::Dependency] The Gem name/dependency. + # requirements:: [default="> 0.0.0"] The version requirements. + # + def add_dependency(gem, *requirements) + requirements = ['> 0.0.0'] if requirements.empty? + requirements.flatten! + unless gem.respond_to?(:name) && gem.respond_to?(:version_requirements) + gem = Dependency.new(gem, requirements) + end + dependencies << gem + end + + # Returns the full name (name-version) of this Gem. Platform information + # is included (name-version-platform) if it is specified (and not the + # default Ruby platform). + # + def full_name + if platform == Gem::Platform::RUBY || platform.nil? + "#{@name}-#{@version}" + else + "#{@name}-#{@version}-#{platform}" + end + end + + # The full path to the gem (install path + full name). + # + # return:: [String] the full gem path + # + def full_gem_path + File.join(installation_path, "gems", full_name) + end + + # The root directory that the gem was installed into. + # + # return:: [String] the installation path + # + def installation_path + (File.dirname(@loaded_from).split(File::SEPARATOR)[0..-2]). + join(File::SEPARATOR) + end + + # Checks if this Specification meets the requirement of the supplied + # dependency. + # + # dependency:: [Gem::Dependency] the dependency to check + # return:: [Boolean] true if dependency is met, otherwise false + # + def satisfies_requirement?(dependency) + return @name == dependency.name && + dependency.version_requirements.satisfied_by?(@version) + end + + # Comparison methods --------------------------------------------- + + # Compare specs (name then version). + def <=>(other) + [@name, @version] <=> [other.name, other.version] + end + + # Tests specs for equality (across all attributes). + def ==(other) + @@attributes.each do |name, default| + return false unless self.send(name) == other.send(name) + end + true + end + + # Export methods (YAML and Ruby code) ---------------------------- + + # Returns an array of attribute names to be used when generating a + # YAML representation of this object. If an attribute still has + # its default value, it is omitted. + def to_yaml_properties + mark_version + @@attributes.map { |name, default| "@#{name}" } + end + + # Returns a Ruby code representation of this specification, such + # that it can be eval'ed and reconstruct the same specification + # later. Attributes that still have their default values are + # omitted. + def to_ruby + mark_version + result = "Gem::Specification.new do |s|\n" + @@attributes.each do |name, default| + # TODO better implementation of next line (read_only_attribute? ... something like that) + next if name == :dependencies or name == :specification_version + current_value = self.send(name) + result << " s.#{name} = #{ruby_code(current_value)}\n" unless current_value == default + end + dependencies.each do |dep| + version_reqs_param = dep.requirements_list.inspect + result << " s.add_dependency(%q<#{dep.name}>, #{version_reqs_param})\n" + end + result << "end\n" + end + + # Validation and normalization methods --------------------------- + + # Checks that the specification contains all required fields, and + # does a very basic sanity check. + # + # Raises InvalidSpecificationException if the spec does not pass + # the checks.. + def validate + normalize + if rubygems_version != RubyGemsVersion + raise InvalidSpecificationException.new(%[ + Expected RubyGems Version #{RubyGemsVersion}, was #{rubygems_version} + ].strip) + end + @@required_attributes.each do |symbol| + unless self.send(symbol) + raise InvalidSpecificationException.new("Missing value for attribute #{symbol}") + end + end + if require_paths.empty? + raise InvalidSpecificationException.new("Gem spec needs to have at least one require_path") + end + end + + # Normalize the list of files so that: + # * All file lists have redundancies removed. + # * Files referenced in the extra_rdoc_files are included in the + # package file list. + # + # Also, the summary and description are converted to a normal + # format. + def normalize + if @extra_rdoc_files + @extra_rdoc_files.uniq! + @files ||= [] + @files.concat(@extra_rdoc_files) + end + @files.uniq! if @files + end + + # Dependency methods --------------------------------------------- + + # Return a list of all gems that have a dependency on this + # gemspec. The list is structured with entries that conform to: + # + # [depending_gem, dependency, [list_of_gems_that_satisfy_dependency]] + # + # return:: [Array] [[dependent_gem, dependency, [list_of_satisfiers]]] + # + def dependent_gems + out = [] + Gem.source_index.each do |name,gem| + gem.dependencies.each do |dep| + if self.satisfies_requirement?(dep) then + sats = [] + find_all_satisfiers(dep) do |sat| + sats << sat + end + out << [gem, dep, sats] + end + end + end + out + end + + def to_s + "#" + end + + private + + def find_all_satisfiers(dep) + Gem.source_index.each do |name,gem| + if(gem.satisfies_requirement?(dep)) then + yield gem + end + end + end + + # Duplicate an object unless it's an immediate value. + def copy_of(obj) + case obj + when Numeric, Symbol, true, false, nil then obj + else obj.dup + end + end + + # Return a string containing a Ruby code representation of the + # given object. + def ruby_code(obj) + case obj + when String then '%q{' + obj + '}' + when Array then obj.inspect + when Gem::Version then obj.to_s.inspect + when Date, Time then '%q{' + obj.strftime('%Y-%m-%d') + '}' + when Numeric then obj.inspect + when true, false, nil then obj.inspect + when Gem::Version::Requirement then "Gem::Version::Requirement.new(#{obj.to_s.inspect})" + else raise Exception, "ruby_code case not handled: #{obj.class}" + end + end + + end + +end + diff --git a/amarok/src/mediadevice/daap/mongrel/lib/rubygems/timer.rb b/amarok/src/mediadevice/daap/mongrel/lib/rubygems/timer.rb new file mode 100644 index 00000000..06250f26 --- /dev/null +++ b/amarok/src/mediadevice/daap/mongrel/lib/rubygems/timer.rb @@ -0,0 +1,25 @@ +# +# This file defines a $log variable for logging, and a time() method for recording timing +# information. +# +#-- +# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others. +# All rights reserved. +# See LICENSE.txt for permissions. +#++ + + +$log = Object.new +def $log.debug(str) + STDERR.puts str +end + +def time(msg, width=25) + t = Time.now + return_value = yield + elapsed = Time.now.to_f - t.to_f + elapsed = sprintf("%3.3f", elapsed) + $log.debug "#{msg.ljust(width)}: #{elapsed}s" + return_value +end + diff --git a/amarok/src/mediadevice/daap/mongrel/lib/rubygems/user_interaction.rb b/amarok/src/mediadevice/daap/mongrel/lib/rubygems/user_interaction.rb new file mode 100644 index 00000000..aa53b8a4 --- /dev/null +++ b/amarok/src/mediadevice/daap/mongrel/lib/rubygems/user_interaction.rb @@ -0,0 +1,260 @@ +#-- +# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others. +# All rights reserved. +# See LICENSE.txt for permissions. +#++ + +module Gem + + #################################################################### + # Module that defines the default UserInteraction. Any class + # including this module will have access to the +ui+ method that + # returns the default UI. + module DefaultUserInteraction + + # Return the default UI. + def ui + DefaultUserInteraction.ui + end + + # Set the default UI. If the default UI is never explicity set, a + # simple console based UserInteraction will be used automatically. + def ui=(new_ui) + DefaultUserInteraction.ui = new_ui + end + + def use_ui(new_ui, &block) + DefaultUserInteraction.use_ui(new_ui, &block) + end + + # The default UI is a class variable of the singleton class for + # this module. + class << self + def ui + @ui ||= Gem::ConsoleUI.new + end + def ui=(new_ui) + @ui = new_ui + end + def use_ui(new_ui) + old_ui = @ui + @ui = new_ui + yield + ensure + @ui = old_ui + end + end + end + + #################################################################### + # Make the default UI accessable without the "ui." prefix. Classes + # including this module may use the interaction methods on the + # default UI directly. Classes may also reference the +ui+ and + # ui= methods. + # + # Example: + # + # class X + # include Gem::UserInteraction + # + # def get_answer + # n = ask("What is the meaning of life?") + # end + # end + module UserInteraction + include DefaultUserInteraction + [ + :choose_from_list, :ask, :ask_yes_no, :say, :alert, :alert_warning, + :alert_error, :terminate_interaction!, :terminate_interaction + ].each do |methname| + class_eval %{ + def #{methname}(*args) + ui.#{methname}(*args) + end + } + end + end + + #################################################################### + # StreamUI implements a simple stream based user interface. + class StreamUI + def initialize(in_stream, out_stream, err_stream=STDERR) + @ins = in_stream + @outs = out_stream + @errs = err_stream + end + + # Choose from a list of options. +question+ is a prompt displayed + # above the list. +list+ is a list of option strings. Returns + # the pair [option_name, option_index]. + def choose_from_list(question, list) + @outs.puts question + list.each_with_index do |item, index| + @outs.puts " #{index+1}. #{item}" + end + @outs.print "> " + @outs.flush + result = @ins.gets.strip.to_i - 1 + return list[result], result + end + + # Ask a question. Returns a true for yes, false for no. + def ask_yes_no(question, default=nil) + qstr = case default + when nil + 'yn' + when true + 'Yn' + else + 'yN' + end + result = nil + while result.nil? + result = ask("#{question} [#{qstr}]") + result = case result + when /^[Yy].*/ + true + when /^[Nn].*/ + false + else + default + end + end + return result + end + + # Ask a question. Returns an answer. + def ask(question) + @outs.print(question + " ") + @outs.flush + result = @ins.gets + result.chomp! if result + result + end + + # Display a statement. + def say(statement="") + @outs.puts statement + end + + # Display an informational alert. + def alert(statement, question=nil) + @outs.puts "INFO: #{statement}" + return ask(question) if question + end + + # Display a warning in a location expected to get error messages. + def alert_warning(statement, question=nil) + @errs.puts "WARNING: #{statement}" + ask(question) if question + end + + # Display an error message in a location expected to get error + # messages. + def alert_error(statement, question=nil) + @errs.puts "ERROR: #{statement}" + ask(question) if question + end + + # Terminate the application immediately without running any exit + # handlers. + def terminate_interaction!(status=-1) + exit!(status) + end + + # Terminate the appliation normally, running any exit handlers + # that might have been defined. + def terminate_interaction(status=0) + exit(status) + end + + # Return a progress reporter object + def progress_reporter(*args) + case Gem.configuration.verbose + when nil, false + SilentProgressReporter.new(@outs, *args) + when true + SimpleProgressReporter.new(@outs, *args) + else + VerboseProgressReporter.new(@outs, *args) + end + end + + class SilentReporter + attr_reader :count + + def initialize(out_stream, size, initial_message) + end + + def updated(message) + end + + def done + end + end + + class SimpleProgressReporter + include DefaultUserInteraction + + attr_reader :count + + def initialize(out_stream, size, initial_message) + @out = out_stream + @total = size + @count = 0 + ui.say initial_message + end + + def updated(message) + @count += 1 + @out.print "." + @out.flush + end + + def done + @out.puts "\ncomplete" + end + end + + class VerboseProgressReporter + include DefaultUserInteraction + + attr_reader :count + + def initialize(out_stream, size, initial_message) + @out = out_stream + @total = size + @count = 0 + @out.puts initial_message + end + + def updated(message) + @count += 1 + @out.puts "#{@count}/#{@total}: #{message}" + end + + def done + @out.puts "complete" + end + end + end + + + #################################################################### + # Subclass of StreamUI that instantiates the user interaction using + # standard in, out and error. + class ConsoleUI < StreamUI + def initialize + super(STDIN, STDOUT, STDERR) + end + end + + #################################################################### + # SilentUI is a UI choice that is absolutely silent. + class SilentUI + def method_missing(sym, *args, &block) + self + end + end +end + diff --git a/amarok/src/mediadevice/daap/mongrel/lib/rubygems/validator.rb b/amarok/src/mediadevice/daap/mongrel/lib/rubygems/validator.rb new file mode 100644 index 00000000..b1df1244 --- /dev/null +++ b/amarok/src/mediadevice/daap/mongrel/lib/rubygems/validator.rb @@ -0,0 +1,155 @@ +#-- +# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others. +# All rights reserved. +# See LICENSE.txt for permissions. +#++ + +module Gem + + class VerificationError < Gem::Exception; end + + ## + # Validator performs various gem file and gem database validation + class Validator + include UserInteraction + + ## + # Given a gem file's contents, validates against its own MD5 checksum + # gem_data:: [String] Contents of the gem file + def verify_gem(gem_data) + if(gem_data.size == 0) then + raise VerificationError.new("Empty Gem file") + end + require 'md5' + unless(gem_data =~ /MD5SUM/m) + return # Don't worry about it...this sucks. Need to fix MD5 stuff for + # new format + # FIXME + end + unless (MD5.md5(gem_data.gsub(/MD5SUM = "([a-z0-9]+)"/, "MD5SUM = \"" + ("F" * 32) + "\"")) == $1.to_s) + raise VerificationError.new("Invalid checksum for Gem file") + end + end + + ## + # Given the path to a gem file, validates against its own MD5 checksum + # + # gem_path:: [String] Path to gem file + def verify_gem_file(gem_path) + begin + File.open(gem_path, 'rb') do |file| + gem_data = file.read + verify_gem(gem_data) + end + rescue Errno::ENOENT + raise Gem::VerificationError.new("Missing gem file #{gem_path}") + end + end + + private + def find_files_for_gem(gem_directory) + installed_files = [] + Find.find(gem_directory) {|file_name| + fn = file_name.slice((gem_directory.size)..(file_name.size-1)).sub(/^\//, "") + if(!(fn =~ /CVS/ || File.directory?(fn) || fn == "")) then + installed_files << fn + end + + } + installed_files + end + + + public + ErrorData = Struct.new(:path, :problem) + + ## + # Checks the gem directory for the following potential + # inconsistencies/problems: + # * Checksum gem itself + # * For each file in each gem, check consistency of installed versions + # * Check for files that aren't part of the gem but are in the gems directory + # * 1 cache - 1 spec - 1 directory. + # + # returns a hash of ErrorData objects, keyed on the problem gem's name. + def alien + require 'rubygems/installer' + require 'find' + require 'md5' + errors = {} + Gem::SourceIndex.from_installed_gems.each do |gem_name, gem_spec| + errors[gem_name] ||= [] + gem_path = File.join(Gem.dir, "cache", gem_spec.full_name) + ".gem" + spec_path = File.join(Gem.dir, "specifications", gem_spec.full_name) + ".gemspec" + gem_directory = File.join(Gem.dir, "gems", gem_spec.full_name) + installed_files = find_files_for_gem(gem_directory) + + if(!File.exist?(spec_path)) then + errors[gem_name] << ErrorData.new(spec_path, "Spec file doesn't exist for installed gem") + end + + begin + require 'rubygems/format.rb' + verify_gem_file(gem_path) + File.open(gem_path) do |file| + format = Gem::Format.from_file_by_path(gem_path) + format.file_entries.each do |entry, data| + # Found this file. Delete it from list + installed_files.delete remove_leading_dot_dir(entry['path']) + File.open(File.join(gem_directory, entry['path']), 'rb') do |f| + unless MD5.md5(f.read).to_s == MD5.md5(data).to_s + errors[gem_name] << ErrorData.new(entry['path'], "installed file doesn't match original from gem") + end + end + end + end + rescue VerificationError => e + errors[gem_name] << ErrorData.new(gem_path, e.message) + end + # Clean out directories that weren't explicitly included in the gemspec + # FIXME: This still allows arbitrary incorrect directories. + installed_files.delete_if {|potential_directory| + File.directory?(File.join(gem_directory, potential_directory)) + } + if(installed_files.size > 0) then + errors[gem_name] << ErrorData.new(gem_path, "Unmanaged files in gem: #{installed_files.inspect}") + end + end + errors + end + + ## + # Runs unit tests for a given gem specification + def unit_test(gem_spec) + start_dir = Dir.pwd + Dir.chdir(gem_spec.full_gem_path) + $: << File.join(Gem.dir, "gems", gem_spec.full_name) + # XXX: why do we need this gem_spec when we've already got 'spec'? + test_files = gem_spec.test_files + if test_files.empty? + say "There are no unit tests to run for #{gem_spec.name}-#{gem_spec.version}" + return + end + require_gem gem_spec.name, "= #{gem_spec.version.version}" + test_files.each do |f| require f end + require 'test/unit/ui/console/testrunner' + suite = Test::Unit::TestSuite.new("#{gem_spec.name}-#{gem_spec.version}") + ObjectSpace.each_object(Class) do |klass| + suite << klass.suite if (klass < Test::Unit::TestCase) + end + result = Test::Unit::UI::Console::TestRunner.run(suite, Test::Unit::UI::SILENT) + unless result.passed? + alert_error(result.to_s) + #unless ask_yes_no(result.to_s + "...keep Gem?", true) then + #Gem::Uninstaller.new(gem_spec.name, gem_spec.version.version).uninstall + #end + end + Dir.chdir(start_dir) + result + end + + def remove_leading_dot_dir(path) + path.sub(/^\.\//, "") + end + end +end diff --git a/amarok/src/mediadevice/daap/mongrel/lib/rubygems/version.rb b/amarok/src/mediadevice/daap/mongrel/lib/rubygems/version.rb new file mode 100644 index 00000000..8bacba20 --- /dev/null +++ b/amarok/src/mediadevice/daap/mongrel/lib/rubygems/version.rb @@ -0,0 +1,306 @@ +#-- +# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others. +# All rights reserved. +# See LICENSE.txt for permissions. +#++ + +module Gem + + #################################################################### + # The Dependency class holds a Gem name and Version::Requirement + # + class Dependency + attr_accessor :name, :version_requirements + + def <=>(other) + [@name] <=> [other.name] + end + + ## + # Constructs the dependency + # + # name:: [String] name of the Gem + # version_requirements:: [String Array] version requirement (e.g. ["> 1.2"]) + # + def initialize(name, version_requirements) + @name = name + @version_requirements = Version::Requirement.new(version_requirements) + @version_requirement = nil # Avoid warnings. + end + + undef version_requirements + def version_requirements + normalize if @version_requirement + @version_requirements + end + + def requirement_list + version_requirements.as_list + end + + alias requirements_list requirement_list + + def normalize + ver = @version_requirement.instance_eval { @version } + @version_requirements = Version::Requirement.new([ver]) + @version_requirement = nil + end + + def to_s + "#{name} (#{version_requirements})" + end + + def ==(other) + self.name = other.name and + self.version_requirements == other.version_requirements + end + end + + #################################################################### + # The Version class processes string versions into comparable values + # + class Version + include Comparable + + # The originating definition of Requirement is left nested in + # Version for compatibility. The full definition is given in + # Gem::Requirement. + class Requirement + end + + attr_accessor :version + + NUM_RE = /\s*(\d+(\.\d+)*)*\s*/ + + ## + # Checks if version string is valid format + # + # str:: [String] the version string + # return:: [Boolean] true if the string format is correct, otherwise false + # + def self.correct?(str) + /^#{NUM_RE}$/.match(str) + end + + ## + # Factory method to create a Version object. Input may be a Version or a + # String. Intended to simplify client code. + # + # ver1 = Version.create('1.3.17') # -> (Version object) + # ver2 = Version.create(ver1) # -> (ver1) + # ver3 = Version.create(nil) # -> nil + # + def self.create(input) + if input.respond_to? :version + return input + elsif input.nil? + return nil + else + return Version.new(input) + end + end + + ## + # Constructs a version from the supplied string + # + # version:: [String] The version string. Format is digit.digit... + # + def initialize(version) + raise ArgumentError, + "Malformed version number string #{version}" unless Version.correct?(version) + @version = version + end + + ## + # Returns the text representation of the version + # + # return:: [String] version as string + # + def to_s + @version + end + + ## + # Convert version to integer array + # + # return:: [Array] list of integers + # + def to_ints + @version.scan(/\d+/).map {|s| s.to_i} + end + + ## + # Compares two versions + # + # other:: [Version or .to_ints] other version to compare to + # return:: [Fixnum] -1, 0, 1 + # + def <=>(other) + return 1 unless other + rnums, vnums = to_ints, other.to_ints + [rnums.size, vnums.size].max.times {|i| + rnums[i] ||= 0 + vnums[i] ||= 0 + } + + begin + r,v = rnums.shift, vnums.shift + end until (r != v || rnums.empty?) + + return r <=> v + end + + # Return a new version object where the next to the last revision + # number is one greater. (e.g. 5.3.1 => 5.4) + def bump + ints = to_ints + ints.pop if ints.size > 1 + ints[-1] += 1 + self.class.new(ints.join(".")) + end + + end + + # Class Requirement's original definition is nested in Version. + # Although an probably inappropriate place, current gems specs + # reference the nested class name explicitly. To remain compatible + # with old software loading gemspecs, we leave the original + # definition in Version, but define an alias Gem::Requirement for + # use everywhere else. + Requirement = ::Gem::Version::Requirement + + ################################################################## + # Requirement version includes a prefaced comparator in addition + # to a version number. + # + # A Requirement object can actually contain multiple, er, + # requirements, as in (> 1.2, < 2.0). + # + class Requirement + include Comparable + + OPS = { + "=" => lambda { |v, r| v == r }, + "!=" => lambda { |v, r| v != r }, + ">" => lambda { |v, r| v > r }, + "<" => lambda { |v, r| v < r }, + ">=" => lambda { |v, r| v >= r }, + "<=" => lambda { |v, r| v <= r }, + "~>" => lambda { |v, r| v >= r && v < r.bump } + } + + OP_RE = Regexp.new(OPS.keys.collect{|k| Regexp.quote(k)}.join("|")) + REQ_RE = /\s*(#{OP_RE})\s*/ + + ## + # Factory method to create a Version::Requirement object. Input may be a + # Version, a String, or nil. Intended to simplify client code. + # + # If the input is "weird", the default version requirement is returned. + # + def self.create(input) + if input.kind_of?(Requirement) + return input + elsif input.kind_of?(Array) + return self.new(input) + elsif input.respond_to? :to_str + return self.new([input.to_str]) + else + return self.default + end + end + + ## + # A default "version requirement" can surely _only_ be '> 0'. + # + def self.default + self.new(['> 0.0.0']) + end + + ## + # Constructs a version requirement instance + # + # str:: [String Array] the version requirement string (e.g. ["> 1.23"]) + # + def initialize(reqs) + @requirements = reqs.collect do |rq| + op, version_string = parse(rq) + [op, Version.new(version_string)] + end + @version = nil # Avoid warnings. + end + + ## + # Overrides to check for comparator + # + # str:: [String] the version requirement string + # return:: [Boolean] true if the string format is correct, otherwise false + # + # NOTE: Commented out because I don't think it is used. + # def correct?(str) + # /^#{REQ_RE}#{NUM_RE}$/.match(str) + # end + + def to_s + as_list.join(", ") + end + + def as_list + normalize + @requirements.collect { |req| + "#{req[0]} #{req[1]}" + } + end + + def normalize + return if @version.nil? + @requirements = [parse(@version)] + @nums = nil + @version = nil + @op = nil + end + + ## + # Is the requirement satifised by +version+. + # + # version:: [Gem::Version] the version to compare against + # return:: [Boolean] true if this requirement is satisfied by + # the version, otherwise false + # + def satisfied_by?(version) + normalize + @requirements.all? { |op, rv| satisfy?(op, version, rv) } + end + + private + + ## + # Is "version op required_version" satisfied? + # + def satisfy?(op, version, required_version) + OPS[op].call(version, required_version) + end + + ## + # Parse the version requirement string. Return the operator and + # version strings. + # + def parse(str) + if md = /^\s*(#{OP_RE})\s*([0-9.]+)\s*$/.match(str) + [md[1], md[2]] + elsif md = /^\s*([0-9.]+)\s*$/.match(str) + ["=", md[1]] + elsif md = /^\s*(#{OP_RE})\s*$/.match(str) + [md[1], "0"] + else + fail ArgumentError, "Illformed requirement [#{str}]" + end + end + + def <=>(other) + to_s <=> other.to_s + end + + end + +end diff --git a/amarok/src/mediadevice/daap/proxy.cpp b/amarok/src/mediadevice/daap/proxy.cpp new file mode 100644 index 00000000..9ab3885d --- /dev/null +++ b/amarok/src/mediadevice/daap/proxy.cpp @@ -0,0 +1,120 @@ +/*************************************************************************** + * copyright : (C) 2006 Ian Monroe * + **************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +#include "amarok.h" +#include "amarokconfig.h" +#include "daapclient.h" +#include "daapreader/authentication/hasher.h" +#include "debug.h" +#include "proxy.h" + +#include + +using namespace Daap; + +//input url: daap://host:port/databaseId/music.ext +/* + bundle->setUrl( Amarok::QStringx("http://%1:3689/databases/%2/items/%3.%4?%5").args( + QStringList() << m_host + << m_databaseId + << QString::number( (*it).asMap()["miid"].asList()[0].asInt() ) + << (*it).asMap()["asfm"].asList()[0].asString() + << m_loginString ) ); + +*/ +Proxy::Proxy(KURL stream, DaapClient* client, const char* name) + : QObject(client, name) + , m_proxy( new Amarok::ProcIO() ) +{ + DEBUG_BLOCK + //find the request id and increment it + const QString hostKey = stream.host() + ':' + QString::number(stream.port()); + const int revisionId = client->incRevision( hostKey ); + const int sessionId = client->getSession( hostKey ); + //compose URL + KURL realStream = realStreamUrl( stream, sessionId ); + + //get hash + char hash[33] = {0}; + GenerateHash( 3 + , reinterpret_cast((realStream.path() + realStream.query()).ascii()) + , 2 + , reinterpret_cast(hash) + , revisionId ); + + // Find free port + MyServerSocket* socket = new MyServerSocket(); + const int port = socket->port(); + debug() << "Proxy server using port: " << port << endl; + delete socket; + m_proxyUrl = KURL( QString("http://localhost:%1/daap.mp3").arg( port ) ); + //start proxy + m_proxy->setComm( KProcess::Communication( KProcess::AllOutput ) ); + *m_proxy << "amarok_proxy.rb"; + *m_proxy << "--daap"; + *m_proxy << QString::number( port ); + *m_proxy << realStream.url(); + *m_proxy << AmarokConfig::soundSystem(); + *m_proxy << hash; + *m_proxy << QString::number( revisionId ); + *m_proxy << Amarok::proxyForUrl( realStream.url() ); + + if( !m_proxy->start( KProcIO::NotifyOnExit, true ) ) { + error() << "Failed to start amarok_proxy.rb" << endl; + return; + } + + QString line; + while( true ) { + kapp->processEvents(); + m_proxy->readln( line ); + if( line == "AMAROK_PROXY: startup" ) break; + } + debug() << "started amarok_proxy.rb --daap " << QString::number( port ) << ' ' << realStream.url() << ' ' << AmarokConfig::soundSystem() << ' ' << hash << ' ' << revisionId << endl; + connect( m_proxy, SIGNAL( processExited( KProcess* ) ), this, SLOT( playbackStopped() ) ); + connect( m_proxy, SIGNAL( readReady( KProcIO* ) ), this, SLOT( readProxy() ) ); +} + +Proxy::~Proxy() +{ + delete m_proxy; +} + +void +Proxy::playbackStopped() +{ + deleteLater(); +} + +void +Proxy::readProxy() +{ + QString line; + + while( m_proxy->readln( line ) != -1 ) + { + debug() << line << endl; + } +} + +KURL Proxy::realStreamUrl( KURL fakeStream, int sessionId ) +{ + KURL realStream; + realStream.setProtocol( "http" ); + realStream.setHost(fakeStream.host()); + realStream.setPort(fakeStream.port()); + realStream.setPath( "/databases" + fakeStream.directory() + "/items/" + fakeStream.fileName() ); + realStream.setQuery( QString("?session-id=") + QString::number(sessionId) ); + return realStream; +} + +#include "proxy.moc" diff --git a/amarok/src/mediadevice/daap/proxy.h b/amarok/src/mediadevice/daap/proxy.h new file mode 100644 index 00000000..9757871a --- /dev/null +++ b/amarok/src/mediadevice/daap/proxy.h @@ -0,0 +1,55 @@ +/*************************************************************************** + * copyright : (C) 2006 Ian Monroe * + **************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +#ifndef DAAPPROXY_H +#define DAAPPROXY_H + +#include +#include +class DaapClient; + +namespace Daap { + + class Proxy : public QObject + { + Q_OBJECT + + public: + Proxy(KURL stream, DaapClient* client, const char* name); + ~Proxy(); + KURL proxyUrl() { return m_proxyUrl; } + static KURL realStreamUrl( KURL fakeStream, int sessionId ); + + public slots: + void playbackStopped(); + void readProxy(); + + private: + KURL m_proxyUrl; + Amarok::ProcIO* m_proxy; + }; + + // We must implement this because QServerSocket has one pure virtual method. + // It's just used for finding a free port. + class MyServerSocket : public QServerSocket + { + public: + MyServerSocket() : QServerSocket( Q_UINT16( 0 ) ) {} + + private: + void newConnection( int ) {} + + }; + +} + +#endif diff --git a/amarok/src/mediadevice/generic/Makefile.am b/amarok/src/mediadevice/generic/Makefile.am new file mode 100644 index 00000000..63863c10 --- /dev/null +++ b/amarok/src/mediadevice/generic/Makefile.am @@ -0,0 +1,31 @@ +kde_module_LTLIBRARIES = libamarok_generic-mediadevice.la +kde_services_DATA = amarok_generic-mediadevice.desktop + +INCLUDES = \ + -I$(top_srcdir)/amarok/src \ + -I$(top_srcdir)/amarok/src/amarokcore \ + -I$(top_builddir)/amarok/src/amarokcore \ + -I$(top_srcdir)/amarok/src/engine \ + -I$(top_srcdir)/amarok/src/engine \ + -I$(top_srcdir)/amarok/src/mediadevice \ + $(TAGLIB_CFLAGS) \ + $(all_includes) + +METASOURCES = AUTO + +libamarok_generic_mediadevice_la_LIBADD = \ + $(top_builddir)/amarok/src/libamarok.la \ + $(LIB_KDEUI) $(LIB_KDECORE) $(LIB_KIO) $(LIB_QT) + +libamarok_generic_mediadevice_la_LDFLAGS = \ + $(KDE_PLUGIN) \ + $(all_libraries) + +libamarok_generic_mediadevice_la_SOURCES = \ + genericmediadeviceconfigdialog.ui\ + genericmediadevice.cpp + +noinst_HEADERS = \ + genericmediadeviceconfigdialog.ui.h\ + genericmediadevice.h + diff --git a/amarok/src/mediadevice/generic/amarok_generic-mediadevice.desktop b/amarok/src/mediadevice/generic/amarok_generic-mediadevice.desktop new file mode 100644 index 00000000..7307527b --- /dev/null +++ b/amarok/src/mediadevice/generic/amarok_generic-mediadevice.desktop @@ -0,0 +1,111 @@ +[Desktop Entry] +Encoding=UTF-8 +Type=Service +Name=Generic Audio Player +Name[af]=Generiese Oudio Speler +Name[ar]= قارئ صوتي عام +Name[bg]=Аудио плеър +Name[bn]=সাধারণ অডিও প্লেয়ার +Name[ca]=Reproductor d'àudio genèric +Name[cs]=Běžný audio přehrávač +Name[da]=Generisk lydafspiller +Name[de]=Generischer Media-Player +Name[el]=Γενικός αναπαραγωγέας ήχου +Name[eo]=Ĝenerika Aŭdludilo +Name[es]=Reproductor de audio genérico +Name[et]=Üldine helifailide mängija +Name[fa]=پخش‌کنندۀ صوتی عمومی +Name[fi]=Yleinen musiikkisoitin +Name[fr]=Lecteur audio générique +Name[ga]=Seinnteoir Ginearálta Fuaime +Name[gl]=Reprodutor de Áudio Xenérico +Name[hu]=Általános zenelejátszó +Name[is]=Almennur tónlistarspilari +Name[it]=Lettore audio generico +Name[ja]=一般的なオーディオプレーヤ +Name[km]=កម្មវិធី​ចាក់​អូឌីយ៉ូ​ទូទៅ +Name[lt]=Bendrasis muzikos grotuvas +Name[mk]=Аудио-изведувач +Name[ms]=Pemain Audio Generik +Name[nb]=Musikkavspiller +Name[nds]=Klangafspeler +Name[ne]=जेनेरिक अडियो प्लेयर +Name[nl]=Generieke audiospeler +Name[nn]=Generell lydspelar +Name[pa]=ਆਮ ਆਡੀਓ ਪਲੇਅਰ +Name[pl]=Odtwarzacz audio +Name[pt]=Leitor de Áudio Genérico +Name[pt_BR]=Reprodutor de Áudio Genérico +Name[ru]=Проигрыватель +Name[se]=Jietnačuojaheaddji +Name[sk]=Všeobecný audio prehrávač +Name[sr]=Генерички аудио плејер +Name[sr@Latn]=Generički audio plejer +Name[sv]=Generell ljudspelare +Name[th]=โปรแกรมเล่นเสียงทั่วไป +Name[tr]=Genel Ses Çalar +Name[uk]=Загальний аудіопрогравач +Name[uz]=Oddiy audio-pleyer +Name[uz@cyrillic]=Оддий аудио-плейер +Name[wa]=Djouweu d' son djenerike +Name[zh_CN]=通用音频播放器 +Name[zh_TW]=通用音樂播放程式 +X-KDE-Library=libamarok_generic-mediadevice +Comment=Plugin for Amarok +Comment[af]=Inprop module vir Amarok +Comment[ar]= قابس ( برنامج مضاف الى) AmaroK +Comment[bg]=Приставка за Amarok +Comment[bn]=আমারক-এর জন্য প্লাগিন +Comment[br]=Lugent evit Amarok +Comment[ca]=Connector per l'Amarok +Comment[cs]=Modul pro AmaroK +Comment[de]=Modul für Amarok +Comment[el]=Πρόσθετο για το AmaroK +Comment[eo]=Kromaĵo por Amarok +Comment[es]=Extensión para Amarok +Comment[et]=Amaroki plugin +Comment[fa]=وصله برای amaroK +Comment[fi]=Amarok-liitännäinen +Comment[fr]=Module pour Amarok +Comment[ga]=Breiseán AmaroK +Comment[gl]=Extensión para Amarok +Comment[hu]=Bővítőmodul az Amarokhoz +Comment[is]=Íforrit fyrir Amarok +Comment[it]=Plugin per Amarok +Comment[ja]=Amarok のためのプラグイン +Comment[ka]=მოდული Amarok-ისთვის +Comment[km]=កម្មវិធី​ជំនួយ​សម្រាប់ Amarok +Comment[lt]=Amarok įskiepis +Comment[mk]=Приклучок за Амарок +Comment[nb]=Programtillegg for Amarok +Comment[nds]=Moduul för Amarok +Comment[ne]=अमारोकका लागि प्लगइन +Comment[nl]=Plugin voor Amarok +Comment[nn]=Programtillegg for Amarok +Comment[pa]=ਅਮਰੋਕ ਲਈ ਪਲੱਗਇਨ +Comment[pl]=Wtyczka Amaroka +Comment[pt]='Plugin' para o Amarok +Comment[pt_BR]=Plugin para o Amarok +Comment[ru]=Модуль amaroK +Comment[se]=Lassemoduvla Amarok:ii +Comment[sk]=Amarok modul +Comment[sr]=Прикључак за Amarok +Comment[sr@Latn]=Priključak za Amarok +Comment[sv]=Insticksprogram för Amarok +Comment[th]=โปรแกรมเสริมสำหรับ Amarok +Comment[tr]=Amarok için Eklenti +Comment[uk]=Втулок для Amarok +Comment[uz]=Amarok uchun plagin +Comment[uz@cyrillic]=Amarok учун плагин +Comment[wa]=Tchôke-divins po Amarok +Comment[zh_CN]=Amarok 插件 +Comment[zh_TW]=amaroK 插件 +ServiceTypes=Amarok/Plugin + +X-KDE-Amarok-plugintype=mediadevice +X-KDE-Amarok-name=generic-mediadevice +X-KDE-Amarok-authors=Jeff Mitchell +X-KDE-Amarok-email=kde-dev@emailgoeshere.com +X-KDE-Amarok-rank=100 +X-KDE-Amarok-version=1 +X-KDE-Amarok-framework-version=32 diff --git a/amarok/src/mediadevice/generic/genericmediadevice.cpp b/amarok/src/mediadevice/generic/genericmediadevice.cpp new file mode 100644 index 00000000..9a27b12e --- /dev/null +++ b/amarok/src/mediadevice/generic/genericmediadevice.cpp @@ -0,0 +1,1093 @@ +/**************************************************************************** + * copyright :(C) 2006 Roel Meeuws * + * (C) 2005 Seb Ruiz * + * * + * With some code helpers from KIO_VFAT * + * (c) 2004 Thomas Loeber * + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + + +#define DEBUG_PREFIX "GenericMediaDevice" + +#include "genericmediadevice.h" + +AMAROK_EXPORT_PLUGIN( GenericMediaDevice ) + +#include "amarok.h" +#include "debug.h" +#include "medium.h" +#include "metabundle.h" +#include "collectiondb.h" +#include "collectionbrowser.h" +#include "k3bexporter.h" +#include "playlist.h" +#include "podcastbundle.h" +#include "statusbar/statusbar.h" +#include "transferdialog.h" +#include "genericmediadeviceconfigdialog.h" + +#include +#include //download saveLocation +#include +#include //smallIcon +#include +#include +#include +#include +#include +#include +#include //downloadSelectedItems() +#include //downloadSelectedItems() + +#include + +#include //usleep() + +#include +#include +#include +#include + +#include +#include +#include + +typedef QPtrList MediaFileList; +typedef QPtrListIterator MediaFileListIterator; + +/** + * GenericMediaItem Class + */ + +class GenericMediaItem : public MediaItem +{ + public: + GenericMediaItem( QListView *parent, QListViewItem *after = 0 ) + : MediaItem( parent, after ) + { } + + GenericMediaItem( QListViewItem *parent, QListViewItem *after = 0 ) + : MediaItem( parent, after ) + { } + + // List directories first, always + int + compare( QListViewItem *i, int col, bool ascending ) const + { + #define i static_cast(i) + switch( type() ) + { + case MediaItem::DIRECTORY: + if( i->type() == MediaItem::DIRECTORY ) + break; + return -1; + + default: + if( i->type() == MediaItem::DIRECTORY ) + return 1; + } + #undef i + + return MediaItem::compare(i, col, ascending); + } + + private: + bool m_dir; +}; + +class GenericMediaFile +{ + public: + GenericMediaFile( GenericMediaFile *parent, QString basename, GenericMediaDevice *device ) + : m_parent( parent ) + , m_device( device ) + { + m_listed = false; + m_children = new MediaFileList(); + + if( m_parent ) + { + if( m_parent == m_device->getInitialFile() ) + m_viewItem = new GenericMediaItem( m_device->view() ); + else + m_viewItem = new GenericMediaItem( m_parent->getViewItem() ); + setNamesFromBase( basename ); + m_viewItem->setText( 0, m_baseName ); + m_parent->getChildren()->append( this ); + } + else + { + m_viewItem = 0; + setNamesFromBase( basename ); + } + + m_device->getItemMap()[m_viewItem] = this; + + if( m_device->getFileMap()[m_fullName] ) + { + debug() << "Trying to create two GenericMediaFile items with same fullName!" << endl; + debug() << "name already existing: " << m_device->getFileMap()[m_fullName]->getFullName() << endl; + delete this; + } + else + { + m_device->getFileMap()[m_fullName] = this; + } + + } + + ~GenericMediaFile() + { + if( m_parent ) + m_parent->removeChild( this ); + m_device->getItemMap().erase( m_viewItem ); + m_device->getFileMap().erase( m_fullName ); + if ( m_children ) + delete m_children; + if ( m_viewItem ) + delete m_viewItem; + } + + GenericMediaFile* + getParent() { return m_parent; } + + void + setParent( GenericMediaFile* parent ) + { + m_device->getFileMap().erase( m_fullName ); + m_parent->getChildren()->remove( this ); + m_parent = parent; + if( m_parent ) + m_parent->getChildren()->append( this ); + setNamesFromBase( m_baseName ); + m_device->getFileMap()[m_fullName] = this; + } + + void + removeChild( GenericMediaFile* childToDelete ) { m_children->remove( childToDelete ); } + + GenericMediaItem* + getViewItem() { return m_viewItem; } + + bool + getListed() { return m_listed; } + + void + setListed( bool listed ) { m_listed = listed; } + + QString + getFullName() { return m_fullName; } + + QString + getBaseName() { return m_baseName; } + + //always follow this function with setNamesFromBase() + void + setBaseName( QString &name ) { m_baseName = name; } + + void + setNamesFromBase( const QString &name = QString::null ) + { + if( name != QString::null ) + m_baseName = name; + if( m_parent ) + m_fullName = m_parent->getFullName() + '/' + m_baseName; + else + m_fullName = m_baseName; + if( m_viewItem ) + m_viewItem->setBundle( new MetaBundle( KURL::fromPathOrURL( m_fullName ), true, TagLib::AudioProperties::Fast ) ); + } + + MediaFileList* + getChildren() { return m_children; } + + void + deleteAll( bool onlyChildren ) + { + GenericMediaFile *vmf; + if( m_children && !m_children->isEmpty() ) + { + MediaFileListIterator it( *m_children ); + while( ( vmf = it.current() ) != 0 ) + { + ++it; + vmf->deleteAll( true ); + } + } + if( onlyChildren ) + delete this; + } + + void + renameAllChildren() + { + GenericMediaFile *vmf; + if( m_children && !m_children->isEmpty() ) + { + for( vmf = m_children->first(); vmf; vmf = m_children->next() ) + vmf->renameAllChildren(); + } + setNamesFromBase(); + } + + private: + QString m_fullName; + QString m_baseName; + GenericMediaFile *m_parent; + MediaFileList *m_children; + GenericMediaItem *m_viewItem; + GenericMediaDevice* m_device; + bool m_listed; +}; + +QString +GenericMediaDevice::fileName( const MetaBundle &bundle ) +{ + QString result = cleanPath( bundle.artist() ); + + if( !result.isEmpty() ) + { + if( m_spacesToUnderscores ) + result += "_-_"; + else + result += " - "; + } + + if( bundle.track() ) + { + result.sprintf( "%02d", bundle.track() ); + + if( m_spacesToUnderscores ) + result += '_'; + else + result += ' '; + } + + result += cleanPath( bundle.title() + '.' + bundle.type() ); + + return result; +} + + +/** + * GenericMediaDevice Class + */ + +GenericMediaDevice::GenericMediaDevice() + : MediaDevice() + , m_kBSize( 0 ) + , m_kBAvail( 0 ) + , m_connected( false ) +{ + DEBUG_BLOCK + m_name = i18n("Generic Audio Player"); + m_dirLister = new KDirLister(); + m_dirLister->setNameFilter( "*.mp3 *.wav *.asf *.flac *.wma *.ogg *.aac *.m4a *.mp4 *.mp2 *.ac3" ); + m_dirLister->setAutoUpdate( false ); + + m_spacesToUnderscores = false; + m_ignoreThePrefix = false; + m_asciiTextOnly = false; + + m_songLocation = QString::null; + m_podcastLocation = QString::null; + + m_supportedFileTypes.clear(); + + m_configDialog = 0; + + connect( m_dirLister, SIGNAL( newItems(const KFileItemList &) ), this, SLOT( newItems(const KFileItemList &) ) ); + connect( m_dirLister, SIGNAL( completed() ), this, SLOT( dirListerCompleted() ) ); + connect( m_dirLister, SIGNAL( clear() ), this, SLOT( dirListerClear() ) ); + connect( m_dirLister, SIGNAL( clear(const KURL &) ), this, SLOT( dirListerClear(const KURL &) ) ); + connect( m_dirLister, SIGNAL( deleteItem(KFileItem *) ), this, SLOT( dirListerDeleteItem(KFileItem *) ) ); +} + +void +GenericMediaDevice::init( MediaBrowser* parent ) +{ + MediaDevice::init( parent ); +} + +GenericMediaDevice::~GenericMediaDevice() +{ + closeDevice(); +} + +void +GenericMediaDevice::applyConfig() +{ + if( m_configDialog != 0) + { + m_supportedFileTypes.clear(); + for( uint i = 0; i < m_configDialog->m_supportedListBox->count(); i++ ) + { + QString currentText = m_configDialog->m_supportedListBox->item( i )->text(); + + if( currentText == m_configDialog->m_convertComboBox->currentText() ) + m_supportedFileTypes.prepend( currentText ); + else + m_supportedFileTypes.append( currentText ); + } + + m_spacesToUnderscores = m_configDialog->m_spaceCheck->isChecked(); + m_asciiTextOnly = m_configDialog->m_asciiCheck->isChecked(); + m_vfatTextOnly = m_configDialog->m_vfatCheck->isChecked(); + m_ignoreThePrefix = m_configDialog->m_ignoreTheCheck->isChecked(); + + m_songLocation = m_configDialog->m_songLocationBox->text(); + m_podcastLocation = m_configDialog->m_podcastLocationBox->text(); + } + + + setConfigString( "songLocation" , m_songLocation ); + setConfigString( "podcastLocation" , m_podcastLocation ); + setConfigBool( "spacesToUnderscores", m_spacesToUnderscores ); + setConfigBool( "ignoreThePrefix" , m_ignoreThePrefix ); + setConfigBool( "asciiTextOnly" , m_asciiTextOnly ); + setConfigBool( "vfatTextOnly" , m_vfatTextOnly ); + setConfigString( "supportedFiletypes" , m_supportedFileTypes.join( ", " ) ); +} + + +void +GenericMediaDevice::loadConfig() +{ + MediaDevice::loadConfig(); + + m_spacesToUnderscores = configBool( "spacesToUnderscores", false ); + m_ignoreThePrefix = configBool( "ignoreThePrefix", false); + m_asciiTextOnly = configBool( "asciiTextOnly", false ); + m_vfatTextOnly = configBool( "vfatTextOnly", false ); + + m_songLocation = configString( "songLocation", "/%artist/%album/%title.%filetype" ); + m_podcastLocation = configString( "podcastLocation", "/podcasts/" ); + + m_supportedFileTypes = QStringList::split( ", ", configString( "supportedFiletypes", "mp3"), true); +} + +bool +GenericMediaDevice::openDevice( bool /*silent*/ ) +{ + DEBUG_BLOCK + if( !m_medium.mountPoint() ) + { + Amarok::StatusBar::instance()->longMessage( i18n( "Devices handled by this plugin must be mounted first.\n" + "Please mount the device and click \"Connect\" again." ), + KDE::StatusBar::Sorry ); + return false; + } + + KMountPoint::List currentmountpoints = KMountPoint::currentMountPoints(); + KMountPoint::List::Iterator mountiter = currentmountpoints.begin(); + for(; mountiter != currentmountpoints.end(); ++mountiter) + { + if( m_medium.mountPoint() == (*mountiter)->mountPoint() ) + m_medium.setFsType( (*mountiter)->mountType() ); + } + m_actuallyVfat = (m_medium.fsType() == "vfat" || m_medium.fsType() == "msdosfs") ? + true : false; + m_connected = true; + KURL tempurl = KURL::fromPathOrURL( m_medium.mountPoint() ); + QString newMountPoint = tempurl.isLocalFile() ? tempurl.path( -1 ) : tempurl.prettyURL( -1 ); //no trailing slash + m_transferDir = newMountPoint; + m_initialFile = new GenericMediaFile( 0, newMountPoint, this ); + listDir( newMountPoint ); + connect( this, SIGNAL( startTransfer() ), MediaBrowser::instance(), SLOT( transferClicked() ) ); + return true; +} + +bool +GenericMediaDevice::closeDevice() //SLOT +{ + if( m_connected ) + { + m_initialFile->deleteAll( true ); + m_view->clear(); + m_connected = false; + + } + //delete these? + m_mfm.clear(); + m_mim.clear(); + return true; +} + +/// Renaming + +void +GenericMediaDevice::renameItem( QListViewItem *item ) // SLOT +{ + + if( !item ) + return; + + #define item static_cast(item) + + QString src = m_mim[item]->getFullName(); + QString dst = m_mim[item]->getParent()->getFullName() + '/' + item->text(0); + + debug() << "Renaming: " << src << " to: " << dst << endl; + + //do we want a progress dialog? If so, set last false to true + if( KIO::NetAccess::file_move( KURL::fromPathOrURL(src), KURL::fromPathOrURL(dst), -1, false, false, false ) ) + { + m_mfm.erase( m_mim[item]->getFullName() ); + m_mim[item]->setNamesFromBase( item->text(0) ); + m_mfm[m_mim[item]->getFullName()] = m_mim[item]; + } + else + { + debug() << "Renaming FAILED!" << endl; + //failed, so set the item's text back to how it should be + item->setText( 0, m_mim[item]->getBaseName() ); + } + + + refreshDir( m_mim[item]->getParent()->getFullName() ); + m_mim[item]->renameAllChildren(); + + #undef item + +} + +/// Creating a directory + +MediaItem * +GenericMediaDevice::newDirectory( const QString &name, MediaItem *parent ) +{ + if( !m_connected || name.isEmpty() ) return 0; + + #define parent static_cast(parent) + + QString fullName = m_mim[parent]->getFullName(); + QString cleanedName = cleanPath( name ); + QString fullPath = fullName + '/' + cleanedName; + debug() << "Creating directory: " << fullPath << endl; + const KURL url( fullPath ); + + if( !KIO::NetAccess::mkdir( url, m_parent ) ) //failed + { + debug() << "Failed to create directory " << fullPath << endl; + return 0; + } + + refreshDir( m_mim[parent]->getFullName() ); + + #undef parent + + return 0; +} + +void +GenericMediaDevice::addToDirectory( MediaItem *directory, QPtrList items ) +{ + if( items.isEmpty() ) return; + + GenericMediaFile *dropDir; + if( !directory ) + dropDir = m_initialFile; + else + { + if( directory->type() == MediaItem::TRACK ) + #define directory static_cast(directory) + dropDir = m_mim[directory]->getParent(); + else + dropDir = m_mim[directory]; + } + + for( QPtrListIterator it(items); *it; ++it ) + { + GenericMediaItem *currItem = static_cast(*it); + QString src = m_mim[currItem]->getFullName(); + QString dst = dropDir->getFullName() + '/' + currItem->text(0); + debug() << "Moving: " << src << " to: " << dst << endl; + + const KURL srcurl(src); + const KURL dsturl(dst); + + if ( !KIO::NetAccess::file_move( srcurl, dsturl, -1, false, false, m_parent ) ) + debug() << "Failed moving " << src << " to " << dst << endl; + else + { + refreshDir( m_mim[currItem]->getParent()->getFullName() ); + refreshDir( dropDir->getFullName() ); + //smb: urls don't seem to refresh correctly, but this seems to be a samba issue? + } + } + #undef directory +} + +/// Uploading + +QString +GenericMediaDevice::buildDestination( const QString &format, const MetaBundle &mb ) +{ + bool isCompilation = mb.compilation() == MetaBundle::CompilationYes; + QMap args; + QString artist = mb.artist(); + QString albumartist = artist; + if( isCompilation ) + albumartist = i18n( "Various Artists" ); + args["theartist"] = cleanPath( artist ); + args["thealbumartist"] = cleanPath( albumartist ); + if( m_ignoreThePrefix && artist.startsWith( "The " ) ) + CollectionView::instance()->manipulateThe( artist, true ); + artist = cleanPath( artist ); + if( m_ignoreThePrefix && albumartist.startsWith( "The " ) ) + CollectionView::instance()->manipulateThe( albumartist, true ); + + albumartist = cleanPath( albumartist ); + for( int i = 0; i < MetaBundle::NUM_COLUMNS; i++ ) + { + if( i == MetaBundle::Score || i == MetaBundle::PlayCount || i == MetaBundle::LastPlayed ) + continue; + args[mb.exactColumnName( i ).lower()] = cleanPath( mb.prettyText( i ) ); + } + args["artist"] = artist; + args["albumartist"] = albumartist; + args["initial"] = albumartist.mid( 0, 1 ).upper(); + args["filetype"] = mb.url().pathOrURL().section( ".", -1 ).lower(); + QString track; + if ( mb.track() ) + track.sprintf( "%02d", mb.track() ); + args["track"] = track; + + Amarok::QStringx formatx( format ); + QString result = formatx.namedOptArgs( args ); + if( !result.startsWith( "/" ) ) + result.prepend( "/" ); + + return result.replace( QRegExp( "/\\.*" ), "/" ); +} + +void +GenericMediaDevice::checkAndBuildLocation( const QString& location ) +{ + // check for every directory from the mount point to the location + // whether they exist or not. + int mountPointDepth = m_medium.mountPoint().contains( '/', false ); + int locationDepth = location.contains( '/', false ); + + if( m_medium.mountPoint().endsWith( "/" ) ) + mountPointDepth--; + + if( location.endsWith( "/") ) + locationDepth--; + + // the locationDepth indicates the filename, in the following loop + // however, we only look at the direcories. hence i < locationDepth + // instead of '<=' + for( int i = mountPointDepth; + i < locationDepth; + i++ ) + { + + QString firstpart = location.section( '/', 0, i-1 ); + QString secondpart = cleanPath( location.section( '/', i, i ) ); + KURL url = KURL::fromPathOrURL( firstpart + '/' + secondpart ); + + if( !KIO::NetAccess::exists( url, false, m_parent ) ) + { + debug() << "directory does not exist, creating..." << url << endl; + if( !KIO::NetAccess::mkdir(url, m_view ) ) //failed + { + debug() << "Failed to create directory " << url << endl; + return; + } + } + } +} + +QString +GenericMediaDevice::buildPodcastDestination( const PodcastEpisodeBundle *bundle ) +{ + QString location = m_podcastLocation.endsWith("/") ? m_podcastLocation : m_podcastLocation + '/'; + // get info about the PodcastChannel + QString parentUrl = bundle->parent().url(); + QString sql = "SELECT title,parent FROM podcastchannels WHERE url='" + CollectionDB::instance()->escapeString( parentUrl ) + "';"; + QStringList values = CollectionDB::instance()->query( sql ); + QString channelTitle; + int parent = 0; + channelTitle = values.first(); + parent = values.last().toInt(); + // Put the file in a directory tree like in the playlistbrowser + sql = "SELECT name,parent FROM podcastfolders WHERE id=%1;"; + QString name; + while ( parent > 0 ) + { + values = CollectionDB::instance()->query( sql.arg( parent ) ); + name = values.first(); + parent = values.last().toInt(); + location += cleanPath( name ) + '/'; + } + location += cleanPath( channelTitle ) + '/' + cleanPath( bundle->localUrl().filename() ); + return location; +} + + +MediaItem * +GenericMediaDevice::copyTrackToDevice( const MetaBundle& bundle ) +{ + if( !m_connected ) return 0; + + // use different naming schemes for differen kinds of tracks + QString path = m_transferDir; + debug() << "bundle exists: " << bundle.podcastBundle() << endl; + if( bundle.podcastBundle() ) + path += buildPodcastDestination( bundle.podcastBundle() ); + else + path += buildDestination( m_songLocation, bundle ); + + checkAndBuildLocation( path ); + + const KURL desturl = KURL::fromPathOrURL( path ); + + //kapp->processEvents( 100 ); + + if( !kioCopyTrack( bundle.url(), desturl ) ) + { + debug() << "Failed to copy track: " << bundle.url().pathOrURL() << " to " << desturl.pathOrURL() << endl; + return 0; + } + + refreshDir( m_transferDir ); + + //the return value just can't be null, as nothing is done with it + //other than to see if it is NULL or not + //if we're here the transfer shouldn't have failed, so we shouldn't get into a loop by waiting... + while( !m_view->firstChild() ) + kapp->processEvents( 100 ); + return static_cast(m_view->firstChild()); +} + +//Somewhat related... + +MediaItem * +GenericMediaDevice::trackExists( const MetaBundle& bundle ) +{ + QString key; + QString path = buildDestination( m_songLocation, bundle); + KURL url( path ); + QStringList directories = QStringList::split( "/", url.directory(1,1), false ); + + QListViewItem *it = view()->firstChild(); + for( QStringList::Iterator directory = directories.begin(); + directory != directories.end(); + directory++ ) + { + key = *directory; + while( it && it->text( 0 ) != key ) + it = it->nextSibling(); + if( !it ) + return 0; + if( !it->childCount() ) + expandItem( it ); + it = it->firstChild(); + } + + key = url.fileName( true ); + key = key.isEmpty() ? fileName( bundle ) : key; + while( it && it->text( 0 ) != key ) + it = it->nextSibling(); + + return dynamic_cast( it ); +} + +/// File transfer methods + + +void +GenericMediaDevice::downloadSelectedItems() +{ + KURL::List urls = getSelectedItems(); + + CollectionView::instance()->organizeFiles( urls, i18n("Copy Files to Collection"), true ); + + hideProgress(); +} + +KURL::List +GenericMediaDevice::getSelectedItems() +{ + return m_view->nodeBuildDragList( static_cast(m_view->firstChild()), true ); +} + +/// Deleting + +int +GenericMediaDevice::deleteItemFromDevice( MediaItem *item, int /*flags*/ ) +{ + if( !item || !m_connected ) return -1; + + #define item static_cast(item) + + QString path = m_mim[item]->getFullName(); + debug() << "Deleting path: " << path << endl; + + if ( !KIO::NetAccess::del( KURL::fromPathOrURL(path), m_view )) + { + debug() << "Could not delete!" << endl; + return -1; + } + + if( m_mim[item] == m_initialFile ) + { + m_mim[item]->deleteAll( false ); + debug() << "Not deleting root directory of mount!" << endl; + path = m_initialFile->getFullName(); + } + else + { + path = m_mim[item]->getParent()->getFullName(); + m_mim[item]->deleteAll( true ); + } + refreshDir( path ); + + setProgress( progress() + 1 ); + + #undef item + return 1; +} + +/// Directory Reading + +void +GenericMediaDevice::expandItem( QListViewItem *item ) // SLOT +{ + if( !item || !item->isExpandable() ) return; + + #define item static_cast(item) + m_dirListerComplete = false; + listDir( m_mim[item]->getFullName() ); + #undef item + + while( !m_dirListerComplete ) + { + kapp->processEvents( 100 ); + usleep(10000); + } +} + +void +GenericMediaDevice::listDir( const QString &dir ) +{ + m_dirListerComplete = false; + if( m_mfm[dir]->getListed() ) + m_dirLister->updateDirectory( KURL::fromPathOrURL(dir) ); + else + { + m_dirLister->openURL( KURL::fromPathOrURL(dir), true, true ); + m_mfm[dir]->setListed( true ); + } +} + +void +GenericMediaDevice::refreshDir( const QString &dir ) +{ + m_dirListerComplete = false; + m_dirLister->updateDirectory( KURL::fromPathOrURL(dir) ); +} + +void +GenericMediaDevice::newItems( const KFileItemList &items ) +{ + QPtrListIterator it( items ); + KFileItem *kfi; + while ( (kfi = it.current()) != 0 ) { + ++it; + addTrackToList( kfi->isFile() ? MediaItem::TRACK : MediaItem::DIRECTORY, kfi->url(), 0 ); + } +} + +void +GenericMediaDevice::dirListerCompleted() +{ + m_dirListerComplete = true; +} + +void +GenericMediaDevice::dirListerClear() +{ + m_initialFile->deleteAll( true ); + + m_view->clear(); + m_mfm.clear(); + m_mim.clear(); + + KURL tempurl = KURL::fromPathOrURL( m_medium.mountPoint() ); + QString newMountPoint = tempurl.isLocalFile() ? tempurl.path( -1 ) : tempurl.prettyURL( -1 ); //no trailing slash + m_initialFile = new GenericMediaFile( 0, newMountPoint, this ); +} + +void +GenericMediaDevice::dirListerClear( const KURL &url ) +{ + QString directory = url.pathOrURL(); + GenericMediaFile *vmf = m_mfm[directory]; + if( vmf ) + vmf->deleteAll( false ); +} + +void +GenericMediaDevice::dirListerDeleteItem( KFileItem *fileitem ) +{ + QString filename = fileitem->url().pathOrURL(); + GenericMediaFile *vmf = m_mfm[filename]; + if( vmf ) + vmf->deleteAll( true ); +} + +int +GenericMediaDevice::addTrackToList( int type, KURL url, int /*size*/ ) +{ + QString path = url.isLocalFile() ? url.path( -1 ) : url.prettyURL( -1 ); //no trailing slash + int index = path.findRev( '/', -1 ); + QString baseName = path.right( path.length() - index - 1 ); + QString parentName = path.left( index ); + + GenericMediaFile* parent = m_mfm[parentName]; + GenericMediaFile* newItem = new GenericMediaFile( parent, baseName, this ); + + if( type == MediaItem::DIRECTORY ) //directory + newItem->getViewItem()->setType( MediaItem::DIRECTORY ); + //TODO: this logic could maybe be taken out later...or the dirlister shouldn't + //filter, one or the other...depends if we want to allow viewing any files + //or just update the list in the plugin as appropriate + else if( type == MediaItem::TRACK ) //file + { + if( baseName.endsWith( "mp3", false ) || baseName.endsWith( "wma", false ) || + baseName.endsWith( "wav", false ) || baseName.endsWith( "ogg", false ) || + baseName.endsWith( "asf", false ) || baseName.endsWith( "flac", false ) || + baseName.endsWith( "aac", false ) || baseName.endsWith( "m4a", false ) ) + + newItem->getViewItem()->setType( MediaItem::TRACK ); + + else + newItem->getViewItem()->setType( MediaItem::UNKNOWN ); + } + + refreshDir( parent->getFullName() ); + + return 0; +} + +/// Capacity, in kB + +bool +GenericMediaDevice::getCapacity( KIO::filesize_t *total, KIO::filesize_t *available ) +{ + if( !m_connected || !KURL::fromPathOrURL( m_medium.mountPoint() ).isLocalFile() ) return false; + + KDiskFreeSp* kdf = new KDiskFreeSp( m_parent, "generic_kdf" ); + kdf->readDF( m_medium.mountPoint() ); + connect(kdf, SIGNAL(foundMountPoint( const QString &, unsigned long, unsigned long, unsigned long )), + SLOT(foundMountPoint( const QString &, unsigned long, unsigned long, unsigned long ))); + + int count = 0; + + while( m_kBSize == 0 && m_kBAvail == 0){ + usleep( 10000 ); + kapp->processEvents( 100 ); + count++; + if (count > 120){ + debug() << "KDiskFreeSp taking too long. Returning false from getCapacity()" << endl; + return false; + } + } + + *total = m_kBSize*1024; + *available = m_kBAvail*1024; + unsigned long localsize = m_kBSize; + m_kBSize = 0; + m_kBAvail = 0; + + return localsize > 0; +} + +void +GenericMediaDevice::foundMountPoint( const QString & mountPoint, unsigned long kBSize, unsigned long /*kBUsed*/, unsigned long kBAvail ) +{ + if ( mountPoint == m_medium.mountPoint() ){ + m_kBSize = kBSize; + m_kBAvail = kBAvail; + } +} + +/// Helper functions + +void +GenericMediaDevice::rmbPressed( QListViewItem* qitem, const QPoint& point, int ) +{ + enum Actions { APPEND, LOAD, QUEUE, + DOWNLOAD, + BURN_DATACD, BURN_AUDIOCD, + DIRECTORY, RENAME, + DELETE, TRANSFER_HERE }; + + MediaItem *item = static_cast(qitem); + if ( item ) + { + KPopupMenu menu( m_view ); + menu.insertItem( SmallIconSet( Amarok::icon( "playlist" ) ), i18n( "&Load" ), LOAD ); + menu.insertItem( SmallIconSet( Amarok::icon( "1downarrow" ) ), i18n( "&Append to Playlist" ), APPEND ); + menu.insertItem( SmallIconSet( Amarok::icon( "fastforward" ) ), i18n( "&Queue Tracks" ), QUEUE ); + menu.insertSeparator(); + menu.insertItem( SmallIconSet( Amarok::icon( "collection" ) ), i18n( "&Copy Files to Collection..." ), DOWNLOAD ); + menu.insertItem( SmallIconSet( Amarok::icon( "cdrom_unmount" ) ), i18n( "Burn to CD as Data" ), BURN_DATACD ); + menu.setItemEnabled( BURN_DATACD, K3bExporter::isAvailable() ); + menu.insertItem( SmallIconSet( Amarok::icon( "cdaudio_unmount" ) ), i18n( "Burn to CD as Audio" ), BURN_AUDIOCD ); + menu.setItemEnabled( BURN_AUDIOCD, K3bExporter::isAvailable() ); + menu.insertSeparator(); + menu.insertItem( SmallIconSet( Amarok::icon( "folder" ) ), i18n( "Add Directory" ), DIRECTORY ); + menu.insertItem( SmallIconSet( Amarok::icon( "edit" ) ), i18n( "Rename" ), RENAME ); + menu.insertItem( SmallIconSet( Amarok::icon( "remove" ) ), i18n( "Delete" ), DELETE ); + menu.insertSeparator(); + // NOTE: need better icon + menu.insertItem( SmallIconSet( Amarok::icon( "add_playlist" ) ), i18n( "Transfer Queue to Here..." ), TRANSFER_HERE ); + menu.setItemEnabled( TRANSFER_HERE, MediaBrowser::queue()->childCount() ); + + int id = menu.exec( point ); + switch( id ) + { + case LOAD: + Playlist::instance()->insertMedia( getSelectedItems(), Playlist::Replace ); + break; + case APPEND: + Playlist::instance()->insertMedia( getSelectedItems(), Playlist::Append ); + break; + case QUEUE: + Playlist::instance()->insertMedia( getSelectedItems(), Playlist::Queue ); + break; + case DOWNLOAD: + downloadSelectedItems(); + break; + case BURN_DATACD: + K3bExporter::instance()->exportTracks( getSelectedItems(), K3bExporter::DataCD ); + break; + case BURN_AUDIOCD: + K3bExporter::instance()->exportTracks( getSelectedItems(), K3bExporter::AudioCD ); + break; + + case DIRECTORY: + if( item->type() == MediaItem::DIRECTORY ) + m_view->newDirectory( static_cast(item) ); + else + m_view->newDirectory( static_cast(item->parent()) ); + break; + + case RENAME: + m_view->rename( item, 0 ); + break; + + case DELETE: + deleteFromDevice(); + break; + + case TRANSFER_HERE: + #define item static_cast(item) + if( item->type() == MediaItem::DIRECTORY ) + m_transferDir = m_mim[item]->getFullName(); + else + m_transferDir = m_mim[item]->getParent()->getFullName(); + #undef item + emit startTransfer(); + break; + } + return; + } + + if( isConnected() ) + { + KPopupMenu menu( m_view ); + menu.insertItem( SmallIconSet( Amarok::icon( "folder" ) ), i18n("Add Directory" ), DIRECTORY ); + if ( MediaBrowser::queue()->childCount()) + { + menu.insertSeparator(); + menu.insertItem( SmallIconSet( Amarok::icon( "add_playlist" ) ), i18n(" Transfer queue to here..." ), TRANSFER_HERE ); + } + int id = menu.exec( point ); + switch( id ) + { + case DIRECTORY: + m_view->newDirectory( 0 ); + break; + + case TRANSFER_HERE: + m_transferDir = m_medium.mountPoint(); + emit startTransfer(); + break; + + } + } +} + + +QString GenericMediaDevice::cleanPath( const QString &component ) +{ + QString result = Amarok::cleanPath( component ); + + if( m_asciiTextOnly ) + result = Amarok::asciiPath( result ); + + result.simplifyWhiteSpace(); + if( m_spacesToUnderscores ) + result.replace( QRegExp( "\\s" ), "_" ); + if( m_actuallyVfat || m_vfatTextOnly ) + result = Amarok::vfatPath( result ); + + result.replace( "/", "-" ); + + return result; +} + +/// File Information functions + +bool GenericMediaDevice::isPlayable( const MetaBundle& bundle ) +{ + for( QStringList::Iterator it = m_supportedFileTypes.begin(); it != m_supportedFileTypes.end() ; it++ ) + { + if( bundle.type().lower() == (*it).lower() ) + return true; + } + + return false; +} + + +bool GenericMediaDevice::isPreferredFormat( const MetaBundle &bundle ) +{ + return m_supportedFileTypes.first().lower() == bundle.type().lower(); +} + +/// Configuration Dialog Extension + +void GenericMediaDevice::addConfigElements( QWidget * parent ) +{ + m_configDialog = new GenericMediaDeviceConfigDialog( parent ); + + m_configDialog->setDevice( this ); +} + + +void GenericMediaDevice::removeConfigElements( QWidget * /* parent */ ) +{ + if( m_configDialog != 0 ) + delete m_configDialog; + + m_configDialog = 0; + +} + + +#include "genericmediadevice.moc" diff --git a/amarok/src/mediadevice/generic/genericmediadevice.h b/amarok/src/mediadevice/generic/genericmediadevice.h new file mode 100644 index 00000000..ab034273 --- /dev/null +++ b/amarok/src/mediadevice/generic/genericmediadevice.h @@ -0,0 +1,150 @@ +/**************************************************************************** + * copyright :(C) 2006 Roel Meeuws * + * (C) 2005 Jeff Mitchell * + * (C) 2005 Seb Ruiz * + * * + * With some code helpers from KIO_GENERIC * + * (c) 2004 Thomas Loeber * + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + + +#ifndef AMAROK_GENERICMEDIADEVICE_H +#define AMAROK_GENERICMEDIADEVICE_H + +#include "mediabrowser.h" +#include "transferdialog.h" + +#include +#include + +#include + +class GenericMediaItem; +class GenericMediaFile; +class GenericMediaDeviceConfigDialog; +class PodcastEpisodeBundle; + +class QStringList; + +typedef QMap MediaFileMap; +typedef QMap MediaItemMap; + +class GenericMediaDevice : public MediaDevice +{ + Q_OBJECT + + friend class GenericMediaDeviceConfigDialog; + + public: + GenericMediaDevice(); + void init( MediaBrowser* parent ); + virtual ~GenericMediaDevice(); + + bool isConnected() { return m_connected; } + + void rmbPressed( QListViewItem* qitem, const QPoint& point, int ); + + QStringList supportedFiletypes() { return m_supportedFileTypes; } + bool isPlayable( const MetaBundle& bundle ); + bool isPreferredFormat( const MetaBundle &bundle ); + + bool needsManualConfig() { return false; } + void addConfigElements( QWidget * parent ); + void removeConfigElements( QWidget * /* parent */); + + void applyConfig(); + void loadConfig(); + + MediaFileMap &getFileMap() { return m_mfm; } + MediaItemMap &getItemMap() { return m_mim; } + GenericMediaFile *getInitialFile() { return m_initialFile; } + + protected: + bool openDevice( bool silent=false ); + bool closeDevice(); + + MediaItem *copyTrackToDevice( const MetaBundle& bundle ); + int deleteItemFromDevice( MediaItem *item, int flags=DeleteTrack ); + MediaItem *newDirectory( const QString &name, MediaItem *parent ); + void addToDirectory( MediaItem *directory, QPtrList items ); + + bool getCapacity( KIO::filesize_t *total, KIO::filesize_t *available ); + QString fileName( const MetaBundle & ); + + //methods not implemented/needed + bool lockDevice( bool ) { return true; } + void unlockDevice() {} + void synchronizeDevice() {} + void addToPlaylist( MediaItem *, MediaItem *, QPtrList ) {} + MediaItem *newPlaylist( const QString &, MediaItem *, QPtrList ) { return 0; } + + + signals: + void startTransfer(); + + + protected slots: + void renameItem( QListViewItem *item ); + void expandItem( QListViewItem *item ); + void foundMountPoint( const QString & mountPoint, unsigned long kBSize, unsigned long kBUsed, unsigned long kBAvail ); + void refreshDir( const QString &dir ); + + void newItems( const KFileItemList &items ); + void dirListerCompleted(); + void dirListerClear(); + void dirListerClear( const KURL &url ); + void dirListerDeleteItem( KFileItem *fileitem ); + + + private: + enum Error { ERR_ACCESS_DENIED, ERR_CANNOT_RENAME, ERR_DISK_FULL, ERR_COULD_NOT_WRITE }; + + MediaItem *trackExists( const MetaBundle& ); + + QString buildDestination( const QString &format, const MetaBundle &mb ); + QString buildPodcastDestination( const PodcastEpisodeBundle *bundle ); + void checkAndBuildLocation( const QString& location ); + + KURL::List getSelectedItems(); + void downloadSelectedItems(); + void copyTrackSortHelper( const MetaBundle& bundle, QString& sort, QString& base ); + + void listDir( const QString &dir ); + int addTrackToList( int type, KURL name, int size=0 ); + + QString cleanPath( const QString &component ); + + GenericMediaFile *m_initialFile; + + KIO::filesize_t m_kBSize; + KIO::filesize_t m_kBAvail; + + KDirLister *m_dirLister; + + bool m_actuallyVfat; + bool m_dirListerComplete; + bool m_connected; + KURL::List m_downloadList; + MediaFileMap m_mfm; + MediaItemMap m_mim; + + QStringList m_supportedFileTypes; + QString m_songLocation; + QString m_podcastLocation; + bool m_asciiTextOnly; + bool m_vfatTextOnly; + bool m_ignoreThePrefix; + + GenericMediaDeviceConfigDialog *m_configDialog; +}; + +#endif /*AMAROK_GENERICMEDIADEVICE_H*/ diff --git a/amarok/src/mediadevice/generic/genericmediadeviceconfigdialog.ui b/amarok/src/mediadevice/generic/genericmediadeviceconfigdialog.ui new file mode 100644 index 00000000..9782071a --- /dev/null +++ b/amarok/src/mediadevice/generic/genericmediadeviceconfigdialog.ui @@ -0,0 +1,463 @@ + +GenericMediaDeviceConfigDialog + + + GenericMediaDeviceConfigDialog + + + + 0 + 0 + 486 + 577 + + + + + 7 + 7 + 0 + 0 + + + + GenericMediaDeviceConfigDialog1 + + + + unnamed + + + 0 + + + 0 + + + + layout88 + + + + unnamed + + + 0 + + + + groupBox1 + + + + 7 + 3 + 0 + 0 + + + + GroupBoxPanel + + + Sunken + + + Transferring files to media device + + + false + + + + unnamed + + + + textLabel2 + + + NoFrame + + + Plain + + + The following formats will be transferred directly: + + + + + layout24 + + + + unnamed + + + + layout23 + + + + unnamed + + + + m_supportedListBox + + + Multi + + + The formats supported by the generic media device. + + + + + textLabel1 + + + + 7 + 0 + 0 + 0 + + + + Other formats will be converted to: + + + + + + + layout19 + + + + unnamed + + + + m_addSupportedButton + + + &Add format... + + + Add the above format to the list. + + + + + m_removeSupportedButton + + + Remove selected + + + Remove the selected formats from the list. + + + + + spacer1 + + + Vertical + + + Expanding + + + + 20 + 93 + + + + + + m_convertComboBox + + + + 1 + 0 + 0 + 0 + + + + The preferred format for transcoding files. + + + + + + + + + + + groupBox2 + + + + 7 + 0 + 0 + 0 + + + + Transfered files locations + + + + unnamed + + + + layout120 + + + + unnamed + + + + layout119 + + + + unnamed + + + + m_ignoreTheCheck + + + Ig&nore "The" + + + + + m_spaceCheck + + + Convert spaces + + + + + + + + m_asciiCheck + + + ASCII te&xt + + + + + m_vfatCheck + + + Always use &VFAT-safe names + + + Always use VFAT-safe names even on devices with non-VFAT filesystems. + + + + + + + layout96 + + + + unnamed + + + + textLabel1_2_2 + + + Song location: + + + + + layout86 + + + + unnamed + + + + m_songLocationBox + + + + + + The location of the transfered songs relative to the device mount point. + + + + + m_formatHelp + + + + 1 + 7 + 0 + 0 + + + + <p align="center">(help)</p> + + + + + + + textLabel2_2 + + + Example song location: + + + + + m_previewLabel + + + + 7 + 5 + 0 + 0 + + + + StyledPanel + + + Plain + + + 2 + + + + + + + + textLabel2_2_2 + + + Podcast location: + + + + + m_podcastLocationBox + + + + + + The location of the transfered podcasts relative to the device mount point. + + + + + + + + + + + + + + + m_removeSupportedButton + clicked() + GenericMediaDeviceConfigDialog + removeSupportedButtonClicked() + + + m_supportedListBox + doubleClicked(QListBoxItem*) + GenericMediaDeviceConfigDialog + supportedListBoxDoubleClicked(QListBoxItem*) + + + m_songLocationBox + textChanged(const QString&) + GenericMediaDeviceConfigDialog + updatePreviewLabel(const QString&) + + + m_asciiCheck + toggled(bool) + GenericMediaDeviceConfigDialog + updatePreviewLabel() + + + m_spaceCheck + toggled(bool) + GenericMediaDeviceConfigDialog + updatePreviewLabel() + + + m_ignoreTheCheck + toggled(bool) + GenericMediaDeviceConfigDialog + updatePreviewLabel() + + + + metabundle.h + qstringx.h + collectionbrowser.h + genericmediadevice.h + qpopupmenu.h + + + QPopupMenu *m_unsupportedMenu; + MetaBundle *m_previewBundle; + GenericMediaDevice *m_device; + + + addSupportedButtonClicked( int id ) + removeSupportedButtonClicked() + supportedListBoxDoubleClicked( QListBoxItem * item ) + updatePreviewLabel() + updatePreviewLabel( const QString & format ) + + + updateConfigDialogLists( const QStringList & supportedFileTypes ) + buildDestination( const QString & format, const MetaBundle & mb ) const + cleanPath( const QString & component ) const + setDevice( GenericMediaDevice * device ) + buildFormatTip() const + init() + destroy() + + + + kactivelabel.h + + diff --git a/amarok/src/mediadevice/generic/genericmediadeviceconfigdialog.ui.h b/amarok/src/mediadevice/generic/genericmediadeviceconfigdialog.ui.h new file mode 100644 index 00000000..8d266f56 --- /dev/null +++ b/amarok/src/mediadevice/generic/genericmediadeviceconfigdialog.ui.h @@ -0,0 +1,324 @@ +/* + (c) 2006 Roel Meeuws + + *************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + + +/**************************************************************************** +** ui.h extension file, included from the uic-generated form implementation. +** +** If you wish to add, delete or rename functions or slots use +** Qt Designer which will update this file, preserving your code. Create an +** init() function in place of a constructor, and a destroy() function in +** place of a destructor. +*****************************************************************************/ + + +/// Configuration Dialog Extension slots + +void +GenericMediaDeviceConfigDialog::addSupportedButtonClicked( int id ) +{ + QPopupMenu *unsupported = m_addSupportedButton->popup(); + QComboBox *convert = m_convertComboBox; + QListBox *supported = m_supportedListBox; + + QString text = unsupported->text( id ); + if( text.startsWith( "&" ) ) + supported->insertItem( text.right( text.length() - 1 ) ); + else + supported->insertItem( text ); + + QString temp = convert->currentText(); + convert->insertItem( unsupported->text( id ) ); + + unsupported->removeItem( id ); + + supported->sort(); + convert->listBox()->sort(); + + convert->setCurrentText( temp ); +} + + +void +GenericMediaDeviceConfigDialog::removeSupportedButtonClicked() +{ + QStringList unsupported; + + QComboBox *convert = m_convertComboBox; + QListBox *supported = m_supportedListBox; + + for( uint i = 0; i < m_addSupportedButton->popup()->count(); i++ ) + { + int id = m_addSupportedButton->popup()->idAt( i ); + unsupported.append( m_addSupportedButton->popup()->text( id ) ); + } + + for( uint i = 0; i < supported->count() ; /* nothing */) + { + QListBoxItem *item = supported->item( i ); + + if( item->isSelected() ) + { + QString temp; + + unsupported.append( item->text() ); + + temp = convert->currentText(); + + convert->setCurrentText( item->text() ); + convert->removeItem( convert->currentItem() ); + + if( temp == item->text() ) + convert->setCurrentItem( 0 ); + else + convert->setCurrentText( temp ); + + item = 0; + supported->removeItem( i ); + + continue; + } + + i++; + } + + // at least support mp3 format. + if( supported->count() <= 0 ) + { + supported->insertItem( "mp3" ); + convert->insertItem( "mp3" ); + convert->setCurrentItem( 0 ); + unsupported.remove( "mp3" ); + } + + unsupported.sort(); + m_addSupportedButton->popup()->clear(); + for( QStringList::Iterator it = unsupported.begin(); it != unsupported.end(); it++ ) + { + m_addSupportedButton->popup()->insertItem( *it ); + } +} + + +void +GenericMediaDeviceConfigDialog::supportedListBoxDoubleClicked( QListBoxItem* item ) +{ + m_convertComboBox->setCurrentText( item->text() ); +} + + + + +void +GenericMediaDeviceConfigDialog::updateConfigDialogLists( const QStringList & supportedFileTypes ) +{ + QStringList allTypes; + allTypes << "mp3" << "ogg" << "wma" << "mp4" << "aac" << "m4a" << "ac3"; + allTypes << "wav" << "flac" << "asf" << "asx" << "mpg" << "mp4v" << "mpeg"; + allTypes << "aa" << "3gp" << "mp2" << "ape" << "mpc"; + + QStringList unsupported; + QComboBox *convert = m_convertComboBox; + QListBox *supported = m_supportedListBox; + + for( QStringList::Iterator it = allTypes.begin(); it != allTypes.end(); it++ ) + { + if( supportedFileTypes.contains( *it ) ) + { + supported->insertItem( *it ); + convert->insertItem( *it ); + } + else + { + unsupported.append( *it ); + } + } + + supported->sort(); + unsupported.sort(); + + m_addSupportedButton->popup()->clear(); + + for( QStringList::Iterator it = unsupported.begin(); it != unsupported.end(); it++ ) + { + m_addSupportedButton->popup()->insertItem( *it ); + } + convert->listBox()->sort(); + convert->setCurrentText( supportedFileTypes.first() ); +} + +QString +GenericMediaDeviceConfigDialog::buildDestination( const QString &format, const MetaBundle &mb ) const +{ + bool isCompilation = mb.compilation() > 0; + QMap args; + QString artist = mb.artist(); + QString albumartist = artist; + if( isCompilation ) + albumartist = i18n( "Various Artists" ); + args["theartist"] = cleanPath( artist ); + args["thealbumartist"] = cleanPath( albumartist ); + if( m_ignoreTheCheck->isChecked() && artist.startsWith( "The " ) ) + CollectionView::instance()->manipulateThe( artist, true ); + artist = cleanPath( artist ); + if( m_ignoreTheCheck->isChecked() && albumartist.startsWith( "The " ) ) + CollectionView::instance()->manipulateThe( albumartist, true ); + + albumartist = cleanPath( albumartist ); + for( int i = 0; i < MetaBundle::NUM_COLUMNS; i++ ) + { + if( i == MetaBundle::Score || i == MetaBundle::PlayCount || i == MetaBundle::LastPlayed ) + continue; + args[mb.exactColumnName( i ).lower()] = cleanPath( mb.prettyText( i ) ); + } + args["artist"] = artist; + args["albumartist"] = albumartist; + args["initial"] = albumartist.mid( 0, 1 ).upper(); + args["filetype"] = mb.url().path().section( ".", -1 ).lower(); + QString track; + if ( mb.track() ) + track.sprintf( "%02d", mb.track() ); + args["track"] = track; + + Amarok::QStringx formatx( format ); + QString result = m_device->mountPoint().append( formatx.namedOptArgs( args ) ); + QString tail = result.mid( m_device->mountPoint().length() ); + if( !tail.startsWith( "/" ) ) + tail.prepend( "/" ); + + return m_device->mountPoint() + tail.replace( QRegExp( "/\\.*" ), "/" ); +} + +QString GenericMediaDeviceConfigDialog::cleanPath( const QString &component ) const +{ + QString result = Amarok::cleanPath( component ); + + if( m_asciiCheck->isChecked() ) + result = Amarok::asciiPath( result ); + + result.simplifyWhiteSpace(); + if( m_spaceCheck->isChecked() ) + result.replace( QRegExp( "\\s" ), "_" ); + if( m_device->m_actuallyVfat || m_vfatCheck->isChecked() ) + result = Amarok::vfatPath( result ); + + result.replace( "/", "-" ); + + return result; +} + + +void +GenericMediaDeviceConfigDialog::updatePreviewLabel() +{ + m_previewLabel->setText( buildDestination( m_songLocationBox->text(), *m_previewBundle ) ); +} + +void +GenericMediaDeviceConfigDialog::updatePreviewLabel( const QString& format) +{ + m_previewLabel->setText( buildDestination( format , *m_previewBundle ) ); +} + +void +GenericMediaDeviceConfigDialog::setDevice( GenericMediaDevice* device ) +{ + m_device = device; + m_songLocationBox->setText( m_device->m_songLocation ); + m_podcastLocationBox->setText( m_device->m_podcastLocation ); + + updatePreviewLabel( m_device->m_songLocation ); + + updateConfigDialogLists( m_device->m_supportedFileTypes ); + m_asciiCheck->setChecked( m_device->m_asciiTextOnly ); + m_vfatCheck->setChecked( m_device->m_vfatTextOnly ); + m_spaceCheck->setChecked( m_device->m_spacesToUnderscores ); + m_ignoreTheCheck->setChecked( m_device->m_ignoreThePrefix ); +} + +QString +GenericMediaDeviceConfigDialog::buildFormatTip() const +{ + QMap args; + for( int i = 0; i < MetaBundle::NUM_COLUMNS; i++ ) + { + if( i == MetaBundle::Score || i == MetaBundle::PlayCount || i == MetaBundle::LastPlayed ) + continue; + args[MetaBundle::exactColumnName( i ).lower()] = MetaBundle::prettyColumnName( i ); + } + args["albumartist"] = i18n( "%1 or %2" ).arg( "Album Artist, The" , "The Album Artist" ); + args["thealbumartist"] = "The Album Artist"; + args["theartist"] = "The Artist"; + args["artist"] = i18n( "%1 or %2" ).arg( "Artist, The" , "The Artist" ); + args["initial"] = i18n( "Artist's Initial" ); + args["filetype"] = i18n( "File Extension of Source" ); + args["track"] = i18n( "Track Number" ); + + QString tooltip = i18n( "

    Custom Format String

    " ); + tooltip += i18n( "You can use the following tokens:" ); + tooltip += "
      "; + for( QMap::iterator it = args.begin(); + it != args.end(); + ++it ) + { + tooltip += QString( "
    • %1 - %2" ).arg( it.data(), "%" + it.key() ); + } + tooltip += "
    "; + + tooltip += i18n( "If you surround sections of text that contain a token with curly-braces, " + "that section will be hidden if the token is empty." ); + + return tooltip; +} + +void +GenericMediaDeviceConfigDialog::init() +{ + m_previewBundle = new MetaBundle(); + m_previewBundle->setAlbum( AtomicString( "Some Album" ) ); + m_previewBundle->setArtist( AtomicString( "The One Artist" ) ); + m_previewBundle->setBitrate( 128 ); + m_previewBundle->setComment( AtomicString( "Some Comment" ) ); + m_previewBundle->setCompilation( 0 ); + m_previewBundle->setComposer( AtomicString( "The One Composer" ) ); + m_previewBundle->setDiscNumber( 1 ); + m_previewBundle->setFileType( 2 ); + m_previewBundle->setFilesize( 1003264 ); + m_previewBundle->setGenre( AtomicString( "Some Genre" ) ); + m_previewBundle->setLength( 193 ); + m_previewBundle->setPlayCount( 2 ); + m_previewBundle->setRating( 3 ); + m_previewBundle->setSampleRate( 44100 ); + m_previewBundle->setScore( 3.f ); + m_previewBundle->setTitle( AtomicString( "Some Title" ) ); + m_previewBundle->setTrack( 7 ); + m_previewBundle->setUrl( "/some%20directory/some%20file.mp3" ); + m_previewBundle->setYear( 2006 ); + + m_formatHelp->setText( QString( "%2" ). + arg( Amarok::escapeHTMLAttr( buildFormatTip() ), i18n( "(Help)" ) ) ); + + m_unsupportedMenu = new QPopupMenu( m_addSupportedButton, "unsupported" ); + + m_addSupportedButton->setPopup( m_unsupportedMenu ); + + connect( m_unsupportedMenu, SIGNAL( activated( int ) ), this, SLOT( addSupportedButtonClicked( int ) ) ); +} + + +void GenericMediaDeviceConfigDialog::destroy() +{ + if( m_unsupportedMenu != NULL ) + delete m_unsupportedMenu; + + m_unsupportedMenu = NULL; +} diff --git a/amarok/src/mediadevice/ifp/Makefile.am b/amarok/src/mediadevice/ifp/Makefile.am new file mode 100644 index 00000000..aeeef7ed --- /dev/null +++ b/amarok/src/mediadevice/ifp/Makefile.am @@ -0,0 +1,27 @@ +kde_module_LTLIBRARIES = libamarok_ifp-mediadevice.la +kde_services_DATA = amarok_ifp-mediadevice.desktop + +INCLUDES = \ + -I$(top_srcdir)/amarok/src \ + -I$(top_srcdir)/amarok/src/amarokcore \ + -I$(top_srcdir)/amarok/src/statusbar \ + -I$(top_srcdir)/amarok/src/engine \ + -I$(top_srcdir)/amarok/src/plugin \ + -I$(top_srcdir)/amarok/src/mediadevice \ + $(IFP_CFLAGS) \ + $(TAGLIB_CFLAGS) \ + $(all_includes) + +libamarok_ifp_mediadevice_la_LIBADD = \ + $(top_builddir)/amarok/src/libamarok.la \ + -lusb -lifp \ + $(LIB_KDEUI) $(LIB_KDECORE) $(LIB_KIO) $(LIB_QT) + +libamarok_ifp_mediadevice_la_LDFLAGS = \ + $(KDE_PLUGIN) \ + $(all_libraries) + +libamarok_ifp_mediadevice_la_SOURCES = \ + ifpmediadevice.cpp + +METASOURCES = AUTO diff --git a/amarok/src/mediadevice/ifp/amarok_ifp-mediadevice.desktop b/amarok/src/mediadevice/ifp/amarok_ifp-mediadevice.desktop new file mode 100644 index 00000000..0507ae35 --- /dev/null +++ b/amarok/src/mediadevice/ifp/amarok_ifp-mediadevice.desktop @@ -0,0 +1,112 @@ +[Desktop Entry] +Encoding=UTF-8 +Type=Service +Name=iRiver iFP Media Device +Name[af]=iRiver iFP media toestel +Name[ar]=جهاز اخراج وسائط iRiver iFP +Name[bg]=Устройство iRiver iFP +Name[bn]=আই-রিভার আইএফপি মিডিয়া ডিভাইস +Name[br]=Trobarzhell media iRiver iFP +Name[ca]=Dispositiu de suports iRiver iFP +Name[cs]=Mediální zařízení iRiver iFP +Name[da]=iRiver iFP-medieenhed +Name[de]=iRiver iFP +Name[el]=Συσκευή πολυμέσων iRiver iFP +Name[eo]=iRiver iFP Media Ekipaĵo +Name[es]=Dispositivo de medios iRiver iFP +Name[et]=iRiver iFP meediaseade +Name[fa]=دستگاه رسانۀ iRiver iFP +Name[fi]=iRiver iFP -medialaite +Name[fr]=Périphérique de média iRiver iFP +Name[ga]=Gléas Meán iFP iRiver +Name[gl]=iRiver iFP +Name[hu]=iRiver iFP médiaeszköz +Name[is]=iRiver iFP +Name[it]=Dispositivo multimediale iRiver iFP +Name[ja]=iRiver iFP メディアデバイス +Name[ka]=მედია მოწყობილობა iRiver iFP +Name[km]=ឧបករណ៍​មេឌៀ iFP របស់ iRiver +Name[lt]=iRiver iFP media įrenginys +Name[mk]=Уред за мултимедија iRiver iFP +Name[nb]=iRiver iFP medieenhet +Name[nds]=iFP-Medienreedschap vun iRiver +Name[ne]=iRiver iFP मिडिया यन्त्र +Name[nl]=iRiver iFP media-apparaat +Name[nn]=iRiver iFP-medieeining +Name[pa]=iRiver iFP ਮੀਡਿਆ ਜੰਤਰ +Name[pl]=Urządzenie mediów iRiver iFP +Name[pt]=Dispositivo Multimédia iRiver iFP +Name[pt_BR]=Dispositivo de Mídia iRiver iFP +Name[ru]=iRiver iFP +Name[se]=iRiver iFP-mediaovttadat +Name[sk]=iRiver iFP Media zariadenie +Name[sr]=iRiver-ов iFP, мултимедијални уређај +Name[sr@Latn]=iRiver-ov iFP, multimedijalni uređaj +Name[sv]=iRiver iFP-mediaenhet +Name[th]=อุปกรณ์เล่นสื่อ iRiver iFP +Name[tr]=iRiver iFP Ortam Aygıtı +Name[uk]=Пристрій носія iRiver iFP +Name[uz]=iRiver iFP media-uskunasi +Name[uz@cyrillic]=iRiver iFP медиа-ускунаси +Name[wa]=Édjin d' media iRiver iFP +Name[zh_CN]=iRiver iFP 媒体设备 +Name[zh_TW]=iRiver iFP 媒體裝置 +X-KDE-Library=libamarok_ifp-mediadevice +Comment=Plugin for Amarok +Comment[af]=Inprop module vir Amarok +Comment[ar]= قابس ( برنامج مضاف الى) AmaroK +Comment[bg]=Приставка за Amarok +Comment[bn]=আমারক-এর জন্য প্লাগিন +Comment[br]=Lugent evit Amarok +Comment[ca]=Connector per l'Amarok +Comment[cs]=Modul pro AmaroK +Comment[de]=Modul für Amarok +Comment[el]=Πρόσθετο για το AmaroK +Comment[eo]=Kromaĵo por Amarok +Comment[es]=Extensión para Amarok +Comment[et]=Amaroki plugin +Comment[fa]=وصله برای amaroK +Comment[fi]=Amarok-liitännäinen +Comment[fr]=Module pour Amarok +Comment[ga]=Breiseán AmaroK +Comment[gl]=Extensión para Amarok +Comment[hu]=Bővítőmodul az Amarokhoz +Comment[is]=Íforrit fyrir Amarok +Comment[it]=Plugin per Amarok +Comment[ja]=Amarok のためのプラグイン +Comment[ka]=მოდული Amarok-ისთვის +Comment[km]=កម្មវិធី​ជំនួយ​សម្រាប់ Amarok +Comment[lt]=Amarok įskiepis +Comment[mk]=Приклучок за Амарок +Comment[nb]=Programtillegg for Amarok +Comment[nds]=Moduul för Amarok +Comment[ne]=अमारोकका लागि प्लगइन +Comment[nl]=Plugin voor Amarok +Comment[nn]=Programtillegg for Amarok +Comment[pa]=ਅਮਰੋਕ ਲਈ ਪਲੱਗਇਨ +Comment[pl]=Wtyczka Amaroka +Comment[pt]='Plugin' para o Amarok +Comment[pt_BR]=Plugin para o Amarok +Comment[ru]=Модуль amaroK +Comment[se]=Lassemoduvla Amarok:ii +Comment[sk]=Amarok modul +Comment[sr]=Прикључак за Amarok +Comment[sr@Latn]=Priključak za Amarok +Comment[sv]=Insticksprogram för Amarok +Comment[th]=โปรแกรมเสริมสำหรับ Amarok +Comment[tr]=Amarok için Eklenti +Comment[uk]=Втулок для Amarok +Comment[uz]=Amarok uchun plagin +Comment[uz@cyrillic]=Amarok учун плагин +Comment[wa]=Tchôke-divins po Amarok +Comment[zh_CN]=Amarok 插件 +Comment[zh_TW]=amaroK 插件 +ServiceTypes=Amarok/Plugin + +X-KDE-Amarok-plugintype=mediadevice +X-KDE-Amarok-name=ifp-mediadevice +X-KDE-Amarok-authors=Seb Ruiz +X-KDE-Amarok-email=me@sebruiz.net +X-KDE-Amarok-rank=100 +X-KDE-Amarok-version=1 +X-KDE-Amarok-framework-version=32 diff --git a/amarok/src/mediadevice/ifp/ifpmediadevice.cpp b/amarok/src/mediadevice/ifp/ifpmediadevice.cpp new file mode 100644 index 00000000..daa529a5 --- /dev/null +++ b/amarok/src/mediadevice/ifp/ifpmediadevice.cpp @@ -0,0 +1,714 @@ +/*************************************************************************** + * copyright : (C) 2005-2006 Seb Ruiz * + * * + * With some code helpers from KIO_IFP * + * (c) 2004 Thomas Loeber * + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + + /** + * iRiver ifp media device code + * @author Seb Ruiz + * @see http://ifp-driver.sourceforge.net/libifp/docs/ifp_8h.html + * @note ifp uses a backslash '\' as a directory delimiter for _remote_ files + */ + +#define DEBUG_PREFIX "IfpMediaDevice" + +#include "ifpmediadevice.h" + +AMAROK_EXPORT_PLUGIN( IfpMediaDevice ) + +#include "debug.h" +#include "metabundle.h" +#include "collectiondb.h" +#include "statusbar/statusbar.h" +#include "transferdialog.h" + +#include +#include //download saveLocation +#include //smallIcon +#include +#include +#include //downloadSelectedItems() +#include //downloadSelectedItems() + +#include +#include +#include + +namespace Amarok { + extern KConfig *config( const QString& ); + extern QString cleanPath( const QString&, bool ); +} + +/** + * IfpMediaItem Class + */ + +class IfpMediaItem : public MediaItem +{ + public: + IfpMediaItem( QListView *parent, QListViewItem *after = 0 ) + : MediaItem( parent, after ) + {} + + IfpMediaItem( QListViewItem *parent, QListViewItem *after = 0 ) + : MediaItem( parent, after ) + {} + + void + setEncodedName( QString &name ) + { + m_encodedName = QFile::encodeName( name ); + } + + void + setEncodedName( QCString &name ) { m_encodedName = name; } + + QCString + encodedName() { return m_encodedName; } + + // List directories first, always + int + compare( QListViewItem *i, int col, bool ascending ) const + { + #define i static_cast(i) + switch( type() ) + { + case MediaItem::DIRECTORY: + if( i->type() == MediaItem::DIRECTORY ) + break; + return -1; + + default: + if( i->type() == MediaItem::DIRECTORY ) + return 1; + } + #undef i + + return MediaItem::compare(i, col, ascending); + } + + private: + bool m_dir; + QCString m_encodedName; +}; + + +/** + * IfpMediaDevice Class + */ + +IfpMediaDevice::IfpMediaDevice() + : MediaDevice() + , m_dev( 0 ) + , m_dh( 0 ) + , m_connected( false ) + , m_last( 0 ) + , m_tmpParent( 0 ) + , m_td( 0 ) +{ + m_name = "iRiver"; + m_hasMountPoint = false; + + m_spacesToUnderscores = configBool("spacesToUnderscores"); + m_firstSort = configString( "firstGrouping", i18n("None") ); + m_secondSort = configString( "secondGrouping", i18n("None") ); + m_thirdSort = configString( "thirdGrouping", i18n("None") ); +} + +void +IfpMediaDevice::init( MediaBrowser* parent ) +{ + MediaDevice::init( parent ); +} + +IfpMediaDevice::~IfpMediaDevice() +{ + setConfigString( "firstGrouping" , m_firstSort ); + setConfigString( "secondGrouping" , m_secondSort ); + setConfigString( "thirdGrouping" , m_thirdSort ); + setConfigBool( "spacesToUnderscores", m_spacesToUnderscores ); + + closeDevice(); +} + +bool +IfpMediaDevice::checkResult( int result, QString message ) +{ + if( result == 0 ) + return true; + + error() << result << ": " << message << endl; + return false; +} + + +bool +IfpMediaDevice::openDevice( bool /*silent*/ ) +{ + DEBUG_BLOCK + + usb_init(); + + m_dh = (usb_dev_handle*)ifp_find_device(); + + QString genericError = i18n( "Could not connect to iFP device" ); + + if( m_dh == NULL ) + { + error() << "A suitable iRiver iFP device couldn't be found" << endl; + Amarok::StatusBar::instance()->shortLongMessage( genericError, + i18n("iFP: A suitable iRiver iFP device could not be found") + , KDE::StatusBar::Error ); + return false; + } + + m_dev = usb_device( m_dh ); + if( m_dev == NULL ) + { + error() << "Could not get usb_device()" << endl; + Amarok::StatusBar::instance()->shortLongMessage( genericError, + i18n("iFP: Could not get a USB device handle"), KDE::StatusBar::Error ); + if( ifp_release_device( m_dh ) ) + error() << "warning: release_device failed." << endl; + return false; + } + + /* "must be called" written in the libusb documentation */ + if( usb_claim_interface( m_dh, m_dev->config->interface->altsetting->bInterfaceNumber ) ) + { + error() << "Device is busy. (I was unable to claim its interface.)" << endl; + Amarok::StatusBar::instance()->shortLongMessage( genericError, + i18n("iFP: Device is busy"), KDE::StatusBar::Error ); + if( ifp_release_device( m_dh ) ) + error() << "warning: release_device failed." << endl; + return false; + } + + int i = ifp_init( &m_ifpdev, m_dh ); + if( i ) + { + error() << "iFP device: Device cannot be opened." << endl; + Amarok::StatusBar::instance()->shortLongMessage( genericError, + i18n("iFP: Could not open device"), KDE::StatusBar::Error ); + usb_release_interface( m_dh, m_dev->config->interface->altsetting->bInterfaceNumber ); + return false; + } + + m_connected = true; + + char info[20]; + ifp_model( &m_ifpdev, info, 20 ); + m_transferDir = QString(info); + debug() << "Successfully connected to: " << info << endl; + + listDir( "" ); + + return true; +} + +bool +IfpMediaDevice::closeDevice() //SLOT +{ + DEBUG_BLOCK + + if( m_connected ) + { + if( m_dh ) + { + usb_release_interface( m_dh, m_dev->config->interface->altsetting->bInterfaceNumber ); + + if( ifp_release_device( m_dh ) ) + error() << "warning: release_device failed." << endl; + + ifp_finalize( &m_ifpdev ); + m_dh = 0; + } + + m_view->clear(); + + m_connected = false; + } + + return true; +} + +void +IfpMediaDevice::runTransferDialog() +{ + m_td = new TransferDialog( this ); + m_td->exec(); +} + +/// Renaming + +void +IfpMediaDevice::renameItem( QListViewItem *item ) // SLOT +{ + if( !item ) + return; + + #define item static_cast(item) + + QCString src = QFile::encodeName( getFullPath( item, false ) ); + src.append( item->encodedName() ); + + //the rename line edit has already changed the QListViewItem text + QCString dest = QFile::encodeName( getFullPath( item ) ); + + debug() << "Renaming " << src << " to: " << dest << endl; + + if( ifp_rename( &m_ifpdev, src, dest ) ) //success == 0 + //rename failed + item->setText( 0, item->encodedName() ); + + #undef item +} + +/// Creating a directory + +MediaItem * +IfpMediaDevice::newDirectory( const QString &name, MediaItem *parent ) +{ + if( !m_connected || name.isEmpty() ) return 0; + + QString cleanedName = cleanPath( name ); + + const QCString dirPath = QFile::encodeName( getFullPath( parent ) + "\\" + cleanedName ); + debug() << "Creating directory: " << dirPath << endl; + int err = ifp_mkdir( &m_ifpdev, dirPath ); + + if( err ) //failed + return 0; + + m_tmpParent = parent; + addTrackToList( IFP_DIR, cleanedName ); + return m_last; +} + +MediaItem * +IfpMediaDevice::newDirectoryRecursive( const QString &name, MediaItem *parent ) +{ + QStringList folders = QStringList::split( '\\', name ); + QString progress = ""; + + if( parent ) + progress += getFullPath( parent ) + "\\"; + else + progress += "\\"; + + foreach( folders ) + { + debug() << "Checking folder: " << progress << endl; + progress += *it; + const QCString dirPath = QFile::encodeName( progress ); + + if( ifp_exists( &m_ifpdev, dirPath ) == IFP_DIR ) + { + m_tmpParent = parent; + parent = findChildItem( *it, parent ); + if( !parent ) + { + addTrackToList( IFP_DIR, *it ); + parent = m_last; + } + } + else + { + parent = newDirectory( *it, parent ); + if( !parent ) //failed + return 0; + } + progress += "\\"; + } + return parent; +} + +MediaItem * +IfpMediaDevice::findChildItem( const QString &name, MediaItem *parent ) +{ + QListViewItem *child; + + parent ? + child = parent->firstChild(): + child = m_view->firstChild(); + + while( child ) + { + if( child->text(0) == name ) + return static_cast(child); + child = child->nextSibling(); + } + return 0; +} + +void +IfpMediaDevice::addToDirectory( MediaItem *directory, QPtrList items ) +{ + if( !directory || items.isEmpty() ) return; + + m_tmpParent = directory; + for( QPtrListIterator it(items); *it; ++it ) + { + QCString src = QFile::encodeName( getFullPath( *it ) ); + QCString dest = QFile::encodeName( getFullPath( directory ) + "\\" + (*it)->text(0) ); + debug() << "Moving: " << src << " to: " << dest << endl; + + int err = ifp_rename( &m_ifpdev, src, dest ); + if( err ) //failed + continue; + + m_view->takeItem( *it ); + directory->insertItem( *it ); + } +} + +/// Uploading + +MediaItem * +IfpMediaDevice::copyTrackToDevice( const MetaBundle& bundle ) +{ + if( !m_connected ) return 0; + m_transferring = true; + + const QCString src = QFile::encodeName( bundle.url().path() ); + + QString directory = "\\"; //root + bool cleverFilename = false; + bool addFileToView = true; + if( m_firstSort != i18n("None") ) + { + addFileToView = false; + directory += bundle.prettyText( bundle.columnIndex(m_firstSort) ) + "\\"; + + if( m_secondSort != i18n("None") ) + { + directory += bundle.prettyText( bundle.columnIndex(m_secondSort) ) + "\\"; + + if( m_thirdSort != i18n("None") ) + directory += bundle.prettyText( bundle.columnIndex(m_thirdSort) ) + "\\"; + } + if( m_firstSort == i18n("Album") || m_secondSort == i18n("Album") || m_thirdSort == i18n("Album") ) + cleverFilename = true; + } + + m_tmpParent = newDirectoryRecursive( directory, 0 ); // recursively create folders as required. + + QString newFilename; + // we don't put this in cleanPath because of remote directory delimiters + const QString title = bundle.title().replace( '\\', '-' ); + if( cleverFilename && !title.isEmpty() ) + { + if( bundle.track() > 0 ) + newFilename = cleanPath( QString::number(bundle.track()) + " - " + title ) + '.' + bundle.type(); + else + newFilename = cleanPath( title ) + '.' + bundle.type(); + } + else + newFilename = cleanPath( bundle.prettyTitle() ) + '.' + bundle.type(); + + const QCString dest = QFile::encodeName( cleanPath(directory + newFilename) ); + + kapp->processEvents( 100 ); + int result = uploadTrack( src, dest ); + + if( !result ) //success + { + addTrackToList( IFP_FILE, cleanPath( newFilename ) ); + return m_last; + } + return 0; +} + +/// File transfer methods + +int +IfpMediaDevice::uploadTrack( const QCString& src, const QCString& dest ) +{ + debug() << "Transferring " << src << " to: " << dest << endl; + + return ifp_upload_file( &m_ifpdev, src, dest, filetransferCallback, this ); +} + +int +IfpMediaDevice::downloadTrack( const QCString& src, const QCString& dest ) +{ + debug() << "Downloading " << src << " to: " << dest << endl; + + return ifp_download_file( &m_ifpdev, src, dest, filetransferCallback, this ); +} + +void +IfpMediaDevice::downloadSelectedItems() +{ +// KConfig *config = Amarok::config( "MediaDevice" ); +// QString save = config->readEntry( "DownloadLocation", QString::null ); //restore the save directory + QString save = QString::null; + + KURLRequesterDlg dialog( save, 0, 0 ); + dialog.setCaption( kapp->makeStdCaption( i18n( "Choose a Download Directory" ) ) ); + dialog.urlRequester()->setMode( KFile::Directory | KFile::ExistingOnly ); + dialog.exec(); + + KURL destDir = dialog.selectedURL(); + if( destDir.isEmpty() ) + return; + + destDir.adjustPath( 1 ); //add trailing slash + +// if( save != destDir.path() ) +// config->writeEntry( "DownloadLocation", destDir.path() ); + + QListViewItemIterator it( m_view, QListViewItemIterator::Selected ); + for( ; it.current(); ++it ) + { + QCString dest = QFile::encodeName( destDir.path() + (*it)->text(0) ); + QCString src = QFile::encodeName( getFullPath( *it ) ); + + downloadTrack( src, dest ); + } + + hideProgress(); +} + +int +IfpMediaDevice::filetransferCallback( void *pData, struct ifp_transfer_status *progress ) +{ + // will be called by 'ifp_upload_file' by callback + + kapp->processEvents( 100 ); + + IfpMediaDevice *that = static_cast(pData); + + if( that->isCanceled() ) + { + debug() << "Canceling transfer operation" << endl; + that->setCanceled( false ); + that->setProgress( progress->file_bytes, progress->file_bytes ); + return 1; //see ifp docs, return 1 for user cancel request + } + + return that->setProgressInfo( progress ); +} +int +IfpMediaDevice::setProgressInfo( struct ifp_transfer_status *progress ) +{ + setProgress( progress->file_bytes, progress->file_total ); + return 0; +} + + +/// Deleting + +int +IfpMediaDevice::deleteItemFromDevice( MediaItem *item, int /*flags*/ ) +{ + if( !item || !m_connected ) return -1; + + QString path = getFullPath( item ); + + QCString encodedPath = QFile::encodeName( path ); + int err; + int count = 0; + + switch( item->type() ) + { + case MediaItem::DIRECTORY: + err = ifp_delete_dir_recursive( &m_ifpdev, encodedPath ); + debug() << "Deleting folder: " << encodedPath << endl; + checkResult( err, i18n("Directory cannot be deleted: '%1'").arg(encodedPath) ); + break; + + default: + err = ifp_delete( &m_ifpdev, encodedPath ); + debug() << "Deleting file: " << encodedPath << endl; + count += 1; + checkResult( err, i18n("File does not exist: '%1'").arg(encodedPath) ); + break; + } + if( err == 0 ) //success + delete item; + + return (err == 0) ? count : -1; +} + +/// Directory Reading + +void +IfpMediaDevice::expandItem( QListViewItem *item ) // SLOT +{ + if( !item || !item->isExpandable() || m_transferring ) return; + + while( item->firstChild() ) + delete item->firstChild(); + + m_tmpParent = item; + + QString path = getFullPath( item ); + listDir( path ); + + m_tmpParent = 0; +} + +void +IfpMediaDevice::listDir( const QString &dir ) +{ + int err = ifp_list_dirs( &m_ifpdev, QFile::encodeName( dir ), listDirCallback, this ); + checkResult( err, i18n("Cannot enter directory: '%1'").arg(dir) ); +} + +// will be called by 'ifp_list_dirs' +int +IfpMediaDevice::listDirCallback( void *pData, int type, const char *name, int size ) +{ + QString qName = QFile::decodeName( name ); + return static_cast(pData)->addTrackToList( type, qName, size ); +} + +int +IfpMediaDevice::addTrackToList( int type, QString name, int /*size*/ ) +{ + m_tmpParent ? + m_last = new IfpMediaItem( m_tmpParent ): + m_last = new IfpMediaItem( m_view ); + + if( type == IFP_DIR ) //directory + m_last->setType( MediaItem::DIRECTORY ); + + else if( type == IFP_FILE ) //file + { + if( name.endsWith( "mp3", false ) || name.endsWith( "wma", false ) || + name.endsWith( "wav", false ) || name.endsWith( "ogg", false ) || + name.endsWith( "asf", false ) ) + + m_last->setType( MediaItem::TRACK ); + + else + m_last->setType( MediaItem::UNKNOWN ); + } + m_last->setEncodedName( name ); + m_last->setText( 0, name ); + return 0; +} + +/// Capacity, in kB + +bool +IfpMediaDevice::getCapacity( KIO::filesize_t *total, KIO::filesize_t *available ) +{ + if( !m_connected ) return false; + + int totalBytes = ifp_capacity( &m_ifpdev ); + int freeBytes = ifp_freespace( &m_ifpdev ); + + *total = totalBytes; + *available = freeBytes; + + return totalBytes > 0; +} + +/// Helper functions + +QString +IfpMediaDevice::getFullPath( const QListViewItem *item, const bool getFilename ) +{ + if( !item ) return QString(); + + QString path; + + if( getFilename ) path = item->text(0); + + QListViewItem *parent = item->parent(); + while( parent ) + { + path.prepend( "\\" ); + path.prepend( parent->text(0) ); + parent = parent->parent(); + } + path.prepend( "\\" ); + + return path; +} + + +void +IfpMediaDevice::rmbPressed( QListViewItem* qitem, const QPoint& point, int ) +{ + enum Actions { DOWNLOAD, DIRECTORY, RENAME, DELETE }; + + MediaItem *item = static_cast(qitem); + if ( item ) + { + KPopupMenu menu( m_view ); + menu.insertItem( SmallIconSet( Amarok::icon( "collection" ) ), i18n( "Download" ), DOWNLOAD ); + menu.insertSeparator(); + menu.insertItem( SmallIconSet( Amarok::icon( "folder" ) ), i18n("Add Directory" ), DIRECTORY ); + menu.insertItem( SmallIconSet( Amarok::icon( "edit" ) ), i18n( "Rename" ), RENAME ); + menu.insertItem( SmallIconSet( Amarok::icon( "remove" ) ), i18n( "Delete" ), DELETE ); + + int id = menu.exec( point ); + switch( id ) + { + case DOWNLOAD: + downloadSelectedItems(); + break; + + case DIRECTORY: + if( item->type() == MediaItem::DIRECTORY ) + m_view->newDirectory( static_cast(item) ); + else + m_view->newDirectory( static_cast(item->parent()) ); + break; + + case RENAME: + m_view->rename( item, 0 ); + break; + + case DELETE: + deleteFromDevice(); + break; + } + return; + } + + if( isConnected() ) + { + KPopupMenu menu( m_view ); + menu.insertItem( SmallIconSet( Amarok::icon( "folder" ) ), i18n("Add Directory" ), DIRECTORY ); + int id = menu.exec( point ); + switch( id ) + { + case DIRECTORY: + m_view->newDirectory( 0 ); + break; + } + } +} + +QString IfpMediaDevice::cleanPath( const QString &component ) +{ + QString result = Amarok::asciiPath( component ); + + result.simplifyWhiteSpace(); + + result.remove( "?" ).replace( "*", " " ).replace( ":", " " ); + +// if( m_spacesToUnderscores ) +// result.replace( QRegExp( "\\s" ), "_" ); + + result.replace( "/", "-" ); + + return result; +} + +#include "ifpmediadevice.moc" diff --git a/amarok/src/mediadevice/ifp/ifpmediadevice.h b/amarok/src/mediadevice/ifp/ifpmediadevice.h new file mode 100644 index 00000000..d2721dd6 --- /dev/null +++ b/amarok/src/mediadevice/ifp/ifpmediadevice.h @@ -0,0 +1,110 @@ +/*************************************************************************** + * copyright : (C) 2005-2006 Seb Ruiz * + * * + * With some code helpers from KIO_IFP * + * (c) 2004 Thomas Loeber * + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef AMAROK_IFPMEDIADEVICE_H +#define AMAROK_IFPMEDIADEVICE_H + +extern "C" { + #include + #include +} + +#include "mediabrowser.h" + +#include + +#include + +class IfpMediaItem; +class TransferDialog; + +class IfpMediaDevice : public MediaDevice +{ + Q_OBJECT + + public: + IfpMediaDevice(); + void init( MediaBrowser* parent ); + virtual ~IfpMediaDevice(); + + bool isConnected() { return m_connected; } + void rmbPressed( QListViewItem* qitem, const QPoint& point, int ); + bool hasTransferDialog() { return true; } + void runTransferDialog(); + TransferDialog *getTransferDialog() { return m_td; } + + protected: + bool openDevice( bool silent=false ); + bool closeDevice(); + + bool lockDevice( bool ) { return true; } + void unlockDevice() {} + void synchronizeDevice() {} + + MediaItem *copyTrackToDevice( const MetaBundle& bundle); + int deleteItemFromDevice( MediaItem *item, int flags=DeleteTrack ); + bool getCapacity( KIO::filesize_t *total, KIO::filesize_t *available ); + MediaItem *newDirectory( const QString &name, MediaItem *parent ); + void addToDirectory( MediaItem *directory, QPtrList items ); + + protected slots: + void renameItem( QListViewItem *item ); + void expandItem( QListViewItem *item ); + + private: + enum Error { ERR_ACCESS_DENIED, ERR_CANNOT_RENAME, ERR_DISK_FULL, ERR_COULD_NOT_WRITE }; + + // Too expensive to implement on a non-database device + MediaItem *trackExists( const MetaBundle& ) { return 0; } + + bool checkResult( int result, QString message ); + + // file transfer + MediaItem *newDirectoryRecursive( const QString &name, MediaItem *parent ); + int uploadTrack( const QCString& src, const QCString& dest ); + void downloadSelectedItems(); + int downloadTrack( const QCString& src, const QCString& dest ); + + // listDir + void listDir( const QString &dir ); + static int listDirCallback( void *pData, int type, const char *name, int size ); + int addTrackToList( int type, QString name, int size=0 ); + + // miscellaneous methods + static int filetransferCallback( void *pData, struct ifp_transfer_status *progress ); + int setProgressInfo( struct ifp_transfer_status *progress ); + // Will iterate over parents and add directory name to the item. + // getFilename = false will return only parent structure, as opposed to returning the filename as well + QString getFullPath( const QListViewItem *item, const bool getFilename = true ); + QString cleanPath( const QString &component ); + + MediaItem *findChildItem( const QString &name, MediaItem *parent ); + + // IFP device + struct usb_device *m_dev; + usb_dev_handle *m_dh; + struct ifp_device m_ifpdev; + + bool m_connected; + + IfpMediaItem *m_last; + //used to specify new IfpMediaItem parent. Make sure it is restored to 0 (m_listview) + QListViewItem *m_tmpParent; + TransferDialog *m_td; +}; + +#endif /*AMAROK_IFPMEDIADEVICE_H*/ + diff --git a/amarok/src/mediadevice/ipod/Makefile.am b/amarok/src/mediadevice/ipod/Makefile.am new file mode 100644 index 00000000..02fccb84 --- /dev/null +++ b/amarok/src/mediadevice/ipod/Makefile.am @@ -0,0 +1,31 @@ +kde_module_LTLIBRARIES = libamarok_ipod-mediadevice.la +kde_services_DATA = amarok_ipod-mediadevice.desktop + +INCLUDES = \ + -I$(top_srcdir)/amarok/src \ + -I$(top_builddir)/amarok/src \ + -I$(top_srcdir)/amarok/src/amarokcore \ + -I$(top_builddir)/amarok/src/amarokcore \ + -I$(top_srcdir)/amarok/src/engine \ + -I$(top_builddir)/amarok/src/engine \ + -I$(top_srcdir)/amarok/src/mediadevice \ + $(LIBGPOD_CFLAGS) \ + $(TAGLIB_CFLAGS) \ + $(all_includes) + +METASOURCES = AUTO + +libamarok_ipod_mediadevice_la_LIBADD = \ + $(top_builddir)/amarok/src/libamarok.la $(LIB_QT) $(LIB_KDEUI) $(LIB_KDECORE) $(LIB_KIO) + +libamarok_ipod_mediadevice_la_LDFLAGS = \ + $(KDE_PLUGIN) \ + $(LIBGPOD_LIBS) \ + $(all_libraries) + +libamarok_ipod_mediadevice_la_SOURCES = \ + ipodmediadevice.cpp + +noinst_HEADERS = \ + ipodmediadevice.h + diff --git a/amarok/src/mediadevice/ipod/amarok_ipod-mediadevice.desktop b/amarok/src/mediadevice/ipod/amarok_ipod-mediadevice.desktop new file mode 100644 index 00000000..8f84a757 --- /dev/null +++ b/amarok/src/mediadevice/ipod/amarok_ipod-mediadevice.desktop @@ -0,0 +1,112 @@ +[Desktop Entry] +Encoding=UTF-8 +Type=Service +Name=Apple iPod Media Device +Name[af]=Apple iPod media toestel +Name[ar]=جهاز اخراج وسائط Apple iPod +Name[bg]=Устройство Apple iPod +Name[bn]=অ্যাপেল আইপড মিডিয়া ডিভাইস +Name[br]=Trobarzhell media Apple iPod +Name[ca]=Dispositiu de suports Apple iPod +Name[cs]=Mediální zařízení Apple iPod +Name[da]=Apple iPod-medieenhed +Name[de]=Apple iPod +Name[el]=Συσκευή πολυμέσων Apple iPod +Name[eo]=Apple iPod Media Ekipaĵo +Name[es]=Dispositivo de medios Apple iPod +Name[et]=Apple iPod meediaseade +Name[fa]=دستگاه رسانۀ Apple iPod +Name[fi]=Apple iPod -medialaite +Name[fr]=Périphérique de média Apple iPod +Name[ga]=Gléas Meán iPod Apple +Name[gl]=Apple iPod +Name[hu]=Apple iPod médiaeszköz +Name[is]=Apple iPod +Name[it]=Dispositivo multimediale Apple iPod +Name[ja]=Apple iPod メディアデバイス +Name[ka]=მედია მოწყობილობა Apple iPod +Name[km]=ឧបករណ៍​មេឌៀ iPod របស់​អ៊េផល +Name[lt]=Apple iPod daugialypės terpės įrenginys +Name[mk]=Уред за мултимедија Apple iPod +Name[nb]=Apple iPod medieenhet +Name[nds]=Apple-iPott +Name[ne]=एप्पल iPod मिडिया यन्त्र +Name[nl]=Apple iPod media-apparaat +Name[nn]=Apple iPod-medieeining +Name[pa]=Apple iPod ਮੀਡਿਆ ਜੰਤਰ +Name[pl]=Urządzenie mediów Apple iPod +Name[pt]=Dispositivo Multimédia Apple iPod +Name[pt_BR]=Dispositivo de Mídia Apple iPod +Name[ru]=Apple iPod +Name[se]=Apple iPod-mediaovttadat +Name[sk]=Apple iPod Media zariadenie +Name[sr]=Apple-ов iPod, мултимедијални уређај +Name[sr@Latn]=Apple-ov iPod, multimedijalni uređaj +Name[sv]=Apple iPod-mediaenhet +Name[th]=อุปกรณ์เล่นสื่อไอพ็อด ของแอปเปิล +Name[tr]=Apple iPod Ortam Aygıtı +Name[uk]=Пристрій носія Apple iPod +Name[uz]=Apple iPod media-uskunasi +Name[uz@cyrillic]=Apple iPod медиа-ускунаси +Name[wa]=Édjin d' media Apple iPod +Name[zh_CN]=Apple iPod 媒体设备 +Name[zh_TW]=Apple iPod 媒體裝置 +X-KDE-Library=libamarok_ipod-mediadevice +Comment=Plugin for Amarok +Comment[af]=Inprop module vir Amarok +Comment[ar]= قابس ( برنامج مضاف الى) AmaroK +Comment[bg]=Приставка за Amarok +Comment[bn]=আমারক-এর জন্য প্লাগিন +Comment[br]=Lugent evit Amarok +Comment[ca]=Connector per l'Amarok +Comment[cs]=Modul pro AmaroK +Comment[de]=Modul für Amarok +Comment[el]=Πρόσθετο για το AmaroK +Comment[eo]=Kromaĵo por Amarok +Comment[es]=Extensión para Amarok +Comment[et]=Amaroki plugin +Comment[fa]=وصله برای amaroK +Comment[fi]=Amarok-liitännäinen +Comment[fr]=Module pour Amarok +Comment[ga]=Breiseán AmaroK +Comment[gl]=Extensión para Amarok +Comment[hu]=Bővítőmodul az Amarokhoz +Comment[is]=Íforrit fyrir Amarok +Comment[it]=Plugin per Amarok +Comment[ja]=Amarok のためのプラグイン +Comment[ka]=მოდული Amarok-ისთვის +Comment[km]=កម្មវិធី​ជំនួយ​សម្រាប់ Amarok +Comment[lt]=Amarok įskiepis +Comment[mk]=Приклучок за Амарок +Comment[nb]=Programtillegg for Amarok +Comment[nds]=Moduul för Amarok +Comment[ne]=अमारोकका लागि प्लगइन +Comment[nl]=Plugin voor Amarok +Comment[nn]=Programtillegg for Amarok +Comment[pa]=ਅਮਰੋਕ ਲਈ ਪਲੱਗਇਨ +Comment[pl]=Wtyczka Amaroka +Comment[pt]='Plugin' para o Amarok +Comment[pt_BR]=Plugin para o Amarok +Comment[ru]=Модуль amaroK +Comment[se]=Lassemoduvla Amarok:ii +Comment[sk]=Amarok modul +Comment[sr]=Прикључак за Amarok +Comment[sr@Latn]=Priključak za Amarok +Comment[sv]=Insticksprogram för Amarok +Comment[th]=โปรแกรมเสริมสำหรับ Amarok +Comment[tr]=Amarok için Eklenti +Comment[uk]=Втулок для Amarok +Comment[uz]=Amarok uchun plagin +Comment[uz@cyrillic]=Amarok учун плагин +Comment[wa]=Tchôke-divins po Amarok +Comment[zh_CN]=Amarok 插件 +Comment[zh_TW]=amaroK 插件 +ServiceTypes=Amarok/Plugin + +X-KDE-Amarok-plugintype=mediadevice +X-KDE-Amarok-name=ipod-mediadevice +X-KDE-Amarok-authors=Martin Aumueller +X-KDE-Amarok-email=aumuell@reserv.at +X-KDE-Amarok-rank=100 +X-KDE-Amarok-version=1 +X-KDE-Amarok-framework-version=32 diff --git a/amarok/src/mediadevice/ipod/ipodmediadevice.cpp b/amarok/src/mediadevice/ipod/ipodmediadevice.cpp new file mode 100644 index 00000000..0d09a1f7 --- /dev/null +++ b/amarok/src/mediadevice/ipod/ipodmediadevice.cpp @@ -0,0 +1,2706 @@ +/*************************************************************************** + copyright : (C) 2005, 2006 by Martin Aumueller + email : aumuell@reserv.at + + copyright : (C) 2004 by Christian Muehlhaeuser + email : chris@chris.de + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License version 2 as * + * published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301 USA * + ***************************************************************************/ + +#define DEBUG_PREFIX "IpodMediaDevice" + +#include +#include "ipodmediadevice.h" + +AMAROK_EXPORT_PLUGIN( IpodMediaDevice ) + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_STATVFS +#include +#include +#endif + +#include +#include + +#ifndef HAVE_ITDB_MEDIATYPE +#define mediatype unk208 +#endif + + +#include "metadata/audible/taglib_audiblefile.h" + +struct PodcastInfo +{ + // per show + QString url; + QString description; + QDateTime date; + QString author; + bool listened; + + // per channel + QString rss; + + PodcastInfo() { listened = false; } +}; + +class TrackList : public QPtrList +{ + int compareItems ( QPtrCollection::Item track1, QPtrCollection::Item track2 ) + { + Itdb_Track *t1 = (Itdb_Track *)track1; + Itdb_Track *t2 = (Itdb_Track *)track2; + + if(t1->track_nr != t2->track_nr) + return t1->track_nr - t2->track_nr; + + return strcasecmp(t1->title, t2->title); + } +}; + +class IpodMediaItem : public MediaItem +{ + public: + IpodMediaItem( QListView *parent, MediaDevice *dev ) + : MediaItem( parent ) { init( dev ); } + + IpodMediaItem( QListViewItem *parent, MediaDevice *dev ) + : MediaItem( parent ) { init( dev ); } + + IpodMediaItem( QListView *parent, QListViewItem *after, MediaDevice *dev ) + : MediaItem( parent, after ) { init( dev ); } + + IpodMediaItem( QListViewItem *parent, QListViewItem *after, MediaDevice *dev ) + : MediaItem( parent, after ) { init( dev ); } + + virtual ~IpodMediaItem() { delete m_podcastInfo; } + + void init( MediaDevice *dev ) + { + m_track = 0; + m_playlist = 0; + m_device = dev; + m_podcastInfo = 0; + } + + void bundleFromTrack( Itdb_Track *track, const QString& path ) + { + MetaBundle *bundle = new MetaBundle(); + + bundle->setArtist ( QString::fromUtf8( track->artist ) ); + bundle->setComposer ( QString::fromUtf8( track->composer ) ); + bundle->setAlbum ( QString::fromUtf8( track->album ) ); + bundle->setTitle ( QString::fromUtf8( track->title ) ); + bundle->setComment ( QString::fromUtf8( track->comment ) ); + bundle->setGenre ( QString::fromUtf8( track->genre ) ); + bundle->setYear ( track->year ); + bundle->setTrack ( track->track_nr ); + bundle->setDiscNumber( track->cd_nr ); + bundle->setBpm ( track->BPM ); + bundle->setLength ( track->tracklen/1000 ); + bundle->setBitrate ( track->bitrate ); + bundle->setSampleRate( track->samplerate ); + bundle->setPath ( path ); + bundle->setFilesize ( track->size ); + + QString rss( track->podcastrss ); + QString url( track->podcasturl ); + QString desc( track->description ); + QString subtitle( track->subtitle ); + QDateTime date; + date.setTime_t( itdb_time_mac_to_host( track->time_released) ); + + if( !rss.isEmpty() || !url.isEmpty() ) + { + PodcastEpisodeBundle peb( KURL::fromPathOrURL(url), KURL::fromPathOrURL(rss), + track->title, track->artist, desc, date.toString(Qt::ISODate), QString::null /*type*/, + bundle->length(), QString::null /*guid*/, track->playcount<=0 ); + bundle->setPodcastBundle( peb ); + } + + setBundle( bundle ); + } + + Itdb_Track *m_track; + Itdb_Playlist *m_playlist; + PodcastInfo *m_podcastInfo; + + int played() const { return m_track ? m_track->playcount : 0; } + int recentlyPlayed() const { return m_track ? m_track->recent_playcount : 0; } + int rating() const { return m_track ? m_track->rating : 0; } + + void setRating( int rating ) + { + if( m_track ) m_track->rating = m_track->app_rating = rating; + if( dynamic_cast(device()) ) + static_cast(device())->m_dbChanged = true; + } + + void setPlayCount( int playcount ) + { + if ( m_track ) + m_track->playcount = playcount; + if( dynamic_cast(device()) ) + static_cast(device())->m_dbChanged = true; + } + + void setLastPlayed( uint lastplay ) + { + if ( m_track ) + m_track->time_played = itdb_time_host_to_mac( lastplay ); + if( dynamic_cast(device()) ) + static_cast(device())->m_dbChanged = true; + } + + bool ratingChanged() const { return m_track ? m_track->rating != m_track->app_rating : false; } + + void setListened( bool l ) + { + MediaItem::setListened( l ); + if( type() == PODCASTITEM ) + { + if( m_podcastInfo ) + m_podcastInfo->listened = listened(); + if( m_track ) + m_track->mark_unplayed = listened() ? 0x01 : 0x02; + } + } + + QDateTime playTime() const + { + QDateTime t; + if( m_track ) + t.setTime_t( itdb_time_mac_to_host( m_track->time_played ) ); + return t; + } + + IpodMediaItem *findTrack( Itdb_Track *track ) + { + if( m_track == track ) + return this; + + for( IpodMediaItem *it = dynamic_cast( firstChild() ); + it; + it = dynamic_cast( it->nextSibling()) ) + { + IpodMediaItem *found = it->findTrack(track); + if( found ) + return found; + } + + return 0; + } +}; + + +IpodMediaDevice::IpodMediaDevice() + : MediaDevice() + , m_masterPlaylist( 0 ) + , m_podcastPlaylist( 0 ) + , m_lockFile( 0 ) + , m_customAction( 0 ) +{ + registerTaglibPlugins(); + + m_podcastItem = 0; + m_staleItem = 0; + m_orphanedItem = 0; + m_invisibleItem = 0; + m_playlistItem = 0; + + m_dbChanged = false; + m_itdb = 0; + m_podcastItem = 0; + m_staleItem = 0; + m_orphanedItem = 0; + m_invisibleItem = 0; + m_playlistItem = 0; + m_supportsArtwork = true; + m_supportsVideo = false; + m_rockboxFirmware = false; + m_isShuffle = false; + m_isMobile = false; + m_isIPhone = false; + m_needsFirewireGuid = false; + + m_requireMount = true; + m_name = "iPod"; + + // config stuff + m_autoConnect = true; + m_syncStatsCheck = 0; + m_autoDeletePodcastsCheck = 0; + + KActionCollection *ac = new KActionCollection( this ); + KActionMenu *am = new KActionMenu( i18n( "iPod" ), Amarok::icon( "device" ), ac ); + m_customAction = am; + m_customAction->setEnabled( false ); + am->setDelayed( false ); + KPopupMenu *menu = am->popupMenu(); + connect( menu, SIGNAL(activated(int)), SLOT(slotIpodAction(int)) ); + menu->insertItem( i18n( "Stale and Orphaned" ), CHECK_INTEGRITY ); + menu->insertItem( i18n( "Update Artwork" ), UPDATE_ARTWORK ); + + KPopupMenu *ipodGen = new KPopupMenu( menu ); + menu->insertItem( i18n( "Set iPod Model" ), ipodGen ); + const Itdb_IpodInfo *table = itdb_info_get_ipod_info_table(); + if( !table ) + return; + + bool infoFound = false; + int generation = ITDB_IPOD_GENERATION_FIRST; + do + { + const Itdb_IpodInfo *info = table; + infoFound = false; + KPopupMenu *gen = 0; + int index = SET_IPOD_MODEL; + while( info->model_number ) + { + if( info->ipod_generation == generation ) + { + if (!infoFound) + { + infoFound = true; + gen = new KPopupMenu( ipodGen ); + connect( gen, SIGNAL(activated(int)), SLOT(slotIpodAction(int)) ); + ipodGen->insertItem( + itdb_info_get_ipod_generation_string( info->ipod_generation), + gen ); + } + if( info->capacity > 0.f ) + gen->insertItem( i18n( "%1 GB %2 (x%3)" ) + .arg( QString::number( info->capacity ), + itdb_info_get_ipod_model_name_string( info->ipod_model ), + info->model_number ), + index ); + else + gen->insertItem( i18n( "%1 (x%2)" ) + .arg( itdb_info_get_ipod_model_name_string( info->ipod_model ), + info->model_number ), + index ); + } + ++info; + ++index; + } + ++generation; + } + while( infoFound ); +} + +void +IpodMediaDevice::slotIpodAction( int id ) +{ + switch( id ) + { + case CHECK_INTEGRITY: + checkIntegrity(); + break; + case UPDATE_ARTWORK: + updateArtwork(); + break; + default: + if( const Itdb_IpodInfo *table = itdb_info_get_ipod_info_table() ) + { + int index = id - SET_IPOD_MODEL; + if( m_itdb && m_itdb->device ) + { + gchar model[PATH_MAX]; + g_snprintf (model, PATH_MAX, "x%s", table[index].model_number); + + itdb_device_set_sysinfo( m_itdb->device, "ModelNumStr", model ); + detectModel(); + + if( m_isIPhone ) + { + m_autoConnect = false; + setConfigBool( "AutoConnect", m_autoConnect ); + } + + // try to make sure that the Device directory exists + QDir dir; + QString realPath; + if(!pathExists( itunesDir(), &realPath) ) + { + dir.setPath(realPath); + dir.mkdir(dir.absPath()); + } + if(!pathExists( itunesDir( "Device" ), &realPath) ) + { + dir.setPath(realPath); + dir.mkdir(dir.absPath()); + } + + GError *err = 0; + gboolean success = itdb_device_write_sysinfo(m_itdb->device, &err); + debug() << "success writing sysinfo to ipod? (return value " << success << ")" << endl; + if( !success && err ) + { + g_error_free(err); + //FIXME: update i18n files for next message + Amarok::StatusBar::instance()->longMessage( + i18n( "Could not write SysInfo file to iPod (check the permissions of the file \"%1\" on your iPod)" ).arg( itunesDir( "Device:SysInfo" ) ) ); + + //FIXME: update i18n files for next message + Amarok::StatusBar::instance()->shortMessage( + i18n( "Unable to set iPod model to %1 GB %2 (x%3)" ) + .arg( QString::number( table[index].capacity ), + itdb_info_get_ipod_model_name_string( table[index].ipod_model ), + table[index].model_number ) ); + } + else + { + Amarok::StatusBar::instance()->shortMessage( + i18n( "Setting iPod model to %1 GB %2 (x%3)" ) + .arg( QString::number( table[index].capacity ), + itdb_info_get_ipod_model_name_string( table[index].ipod_model ), + table[index].model_number ) ); + } + MediaBrowser::instance()->updateDevices(); + } + } + break; + } +} + +void +IpodMediaDevice::init( MediaBrowser* parent ) +{ + MediaDevice::init( parent ); +} + +IpodMediaDevice::~IpodMediaDevice() +{ + if( m_itdb ) + itdb_free(m_itdb); + + m_files.clear(); +} + +bool +IpodMediaDevice::isConnected() +{ + return ( m_itdb != 0 ); +} + +MediaItem * +IpodMediaDevice::insertTrackIntoDB( const QString &pathname, + const MetaBundle &metaBundle, const MetaBundle &propertiesBundle, + const PodcastInfo *podcastInfo ) +{ + return updateTrackInDB( 0, pathname, metaBundle, propertiesBundle, podcastInfo ); +} + +MediaItem * +IpodMediaDevice::updateTrackInDB( IpodMediaItem *item, const QString &pathname, + const MetaBundle &metaBundle, const MetaBundle &propertiesBundle, + const PodcastInfo *podcastInfo ) +{ + if( !m_itdb ) + return 0; + + Itdb_Track *track = 0; + if( item ) + track = item->m_track; + if( !track ) + track = itdb_track_new(); + if( !track ) + { + delete item; + return 0; + } + + QString type = pathname.section('.', -1).lower(); + + track->ipod_path = g_strdup( ipodPath(pathname).latin1() ); + debug() << "on iPod: " << track->ipod_path << ", podcast=" << podcastInfo << endl; + + if( metaBundle.isValidMedia() || !metaBundle.title().isEmpty() ) + track->title = g_strdup( metaBundle.title().utf8() ); + else + track->title = g_strdup( metaBundle.url().filename().utf8() ); + track->album = g_strdup( metaBundle.album()->utf8() ); + track->artist = g_strdup( metaBundle.artist()->utf8() ); + track->genre = g_strdup( metaBundle.genre()->utf8() ); + + track->mediatype = ITDB_MEDIATYPE_AUDIO; + bool audiobook = false; + if(type=="wav") + { + track->filetype = g_strdup( "wav" ); + } + else if(type=="mp3" || type=="mpeg") + { + track->filetype = g_strdup( "mpeg" ); + } + else if(type=="aac" || type=="m4a" || (!m_supportsVideo && type=="mp4")) + { + track->filetype = g_strdup( "mp4" ); + } + else if(type=="m4b") + { + audiobook = true; + track->filetype = g_strdup( "mp4" ); + } + else if(type=="m4v" || type=="mp4v" || type=="mov" || type=="mpg" || type=="mp4") + { + track->filetype = g_strdup( "m4v video" ); + track->movie_flag = 0x01; // for videos + track->mediatype = ITDB_MEDIATYPE_MOVIE; + } + else if(type=="aa") + { + audiobook = true; + track->filetype = g_strdup( "audible" ); + + TagLib::Audible::File f( QFile::encodeName( propertiesBundle.url().path() ) ); + TagLib::Audible::Tag *t = f.getAudibleTag(); + if( t ) + track->drm_userid = t->userID(); + // libgpod also tries to set those, but this won't work + track->unk126 = 0x01; + track->unk144 = 0x0029; + + } + else + { + track->filetype = g_strdup( type.utf8() ); + } + + + QString genre = metaBundle.genre(); + if( genre.startsWith("audiobook", false) ) + audiobook = true; + if( audiobook ) + { + track->remember_playback_position |= 0x01; + track->skip_when_shuffling |= 0x01; + track->mediatype = ITDB_MEDIATYPE_AUDIOBOOK; + } + + track->composer = g_strdup( metaBundle.composer()->utf8() ); + track->comment = g_strdup( metaBundle.comment()->utf8() ); + track->track_nr = metaBundle.track(); + track->cd_nr = metaBundle.discNumber(); + track->BPM = static_cast( metaBundle.bpm() ); + track->year = metaBundle.year(); + track->size = propertiesBundle.filesize(); + if( track->size == 0 ) + { + debug() << "filesize is zero for " << track->ipod_path << ", expect strange problems with your ipod" << endl; + } + track->bitrate = propertiesBundle.bitrate(); + track->samplerate = propertiesBundle.sampleRate(); + track->tracklen = propertiesBundle.length()*1000; + + //Get the createdate from database + QueryBuilder qb; + qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valCreateDate ); + qb.addMatch( QueryBuilder::tabSong, QueryBuilder::valURL, metaBundle.url().path() ); + QStringList values = qb.run(); + + //Add to track info if present + if ( values.count() ) { + uint createdate = values.first().toUInt(); + track->time_added = itdb_time_host_to_mac( createdate ); + track->time_modified = itdb_time_host_to_mac( createdate ); + } + + if(podcastInfo) + { + track->skip_when_shuffling = 0x01; // skip when shuffling + track->remember_playback_position = 0x01; // remember playback position + // FIXME: track->unk176 = 0x00020000; // for podcasts + track->mark_unplayed = podcastInfo->listened ? 0x01 : 0x02; + track->mediatype = + track->mediatype==ITDB_MEDIATYPE_MOVIE + ? ITDB_MEDIATYPE_PODCAST | ITDB_MEDIATYPE_MOVIE + : ITDB_MEDIATYPE_PODCAST; + + track->flag4 = 0x01; // also show description on iPod + QString plaindesc = podcastInfo->description; + plaindesc.replace( QRegExp("<[^>]*>"), "" ); + track->description = g_strdup( plaindesc.utf8() ); + track->subtitle = g_strdup( plaindesc.utf8() ); + track->podcasturl = g_strdup( podcastInfo->url.utf8() ); + track->podcastrss = g_strdup( podcastInfo->rss.utf8() ); + //track->category = g_strdup( i18n( "Unknown" ) ); + track->time_released = itdb_time_host_to_mac( podcastInfo->date.toTime_t() ); + //track->compilation = 0x01; // this should have made the ipod play a sequence of podcasts + } + else + { + if( metaBundle.compilation() == MetaBundle::CompilationYes ) + { + track->compilation = 0x01; + } + else + { + track->compilation = 0x00; + } + } + + m_dbChanged = true; + + if( m_supportsArtwork ) + { + QString image; + if( metaBundle.podcastBundle() ) + { + PodcastChannelBundle pcb; + if( CollectionDB::instance()->getPodcastChannelBundle( metaBundle.podcastBundle()->parent(), &pcb ) ) + image = CollectionDB::instance()->podcastImage( pcb.imageURL().url(), 0 ); + } + if( image.isEmpty() ) + image = CollectionDB::instance()->albumImage(metaBundle.artist(), metaBundle.album(), false, 0); + if( !image.endsWith( "@nocover.png" ) ) + { + debug() << "adding image " << image << " to " << metaBundle.artist() << ":" << metaBundle.album() << endl; + itdb_track_set_thumbnails( track, g_strdup( QFile::encodeName(image) ) ); + } + } + + if( item ) + { + MediaItem *parent = dynamic_cast(item->parent()); + if( parent ) + { + parent->takeItem( item ); + if( parent->childCount() == 0 && !isSpecialItem( parent ) ) + { + MediaItem *pp = dynamic_cast(parent->parent()); + delete parent; + if( pp && pp->childCount() == 0 && !isSpecialItem( pp ) ) + delete pp; + } + } + } + else + { + itdb_track_add(m_itdb, track, -1); + if(podcastInfo) + { + Itdb_Playlist *podcasts = itdb_playlist_podcasts(m_itdb); + if(!podcasts) + { + podcasts = itdb_playlist_new("Podcasts", false); + itdb_playlist_add(m_itdb, podcasts, -1); + itdb_playlist_set_podcasts(podcasts); + addPlaylistToView( podcasts ); + } + itdb_playlist_add_track(podcasts, track, -1); + } + else + { + // gtkpod 0.94 does not like if not all songs in the db are on the master playlist + // but we try anyway + Itdb_Playlist *mpl = itdb_playlist_mpl(m_itdb); + if( !mpl ) + { + mpl = itdb_playlist_new( "iPod", false ); + itdb_playlist_add( m_itdb, mpl, -1 ); + itdb_playlist_set_mpl( mpl ); + addPlaylistToView( mpl ); + } + itdb_playlist_add_track(mpl, track, -1); + } + } + + return addTrackToView( track, item ); +} + +MediaItem * +IpodMediaDevice::copyTrackToDevice(const MetaBundle &bundle) +{ + KURL url = determineURLOnDevice(bundle); + + // check if path exists and make it if needed + QFileInfo finfo( url.path() ); + QDir dir = finfo.dir(); + while ( !dir.exists() ) + { + QString path = dir.absPath(); + QDir parentdir; + QDir create; + do + { + create.setPath(path); + path = path.section("/", 0, path.contains('/')-1); + parentdir.setPath(path); + } + while( !path.isEmpty() && !(path==mountPoint()) && !parentdir.exists() ); + debug() << "trying to create \"" << path << "\"" << endl; + if(!create.mkdir( create.absPath() )) + { + break; + } + } + if ( !dir.exists() ) + { + Amarok::StatusBar::instance()->longMessage( + i18n( "Media Device: Creating directory for file %1 failed" ).arg( url.path() ), + KDE::StatusBar::Error ); + return NULL; + } + + if( !kioCopyTrack( bundle.url(), url ) ) + { + return NULL; + } + + PodcastInfo *podcastInfo = 0; + if( bundle.podcastBundle() ) + { + PodcastEpisodeBundle *peb = bundle.podcastBundle(); + podcastInfo = new PodcastInfo; + podcastInfo->url = peb->url().url(); + podcastInfo->description = peb->description(); + podcastInfo->author = peb->author(); + podcastInfo->rss = peb->parent().url(); + podcastInfo->date = peb->dateTime(); + podcastInfo->listened = !peb->isNew(); + } + + MetaBundle propertiesBundle( url ); + MediaItem *ret = insertTrackIntoDB( url.path(), bundle, propertiesBundle, podcastInfo ); + delete podcastInfo; + return ret; +} + +MediaItem * +IpodMediaDevice::tagsChanged( MediaItem *item, const MetaBundle &bundle ) +{ + return updateTrackInDB( dynamic_cast(item), item->url().path(), bundle, bundle, NULL ); +} + +void +IpodMediaDevice::synchronizeDevice() +{ +#if 1 + debug() << "Syncing iPod!" << endl; + Amarok::StatusBar::instance()->newProgressOperation( this ) + .setDescription( i18n( "Flushing iPod filesystem transfer cache" ) ) + .setTotalSteps( 1 ); + writeITunesDB(); + Amarok::StatusBar::instance()->endProgressOperation( this ); +#else + m_dbChanged = true; + debug() << "Deferring sync of iPod!" << endl; +#endif +} + +MediaItem * +IpodMediaDevice::trackExists( const MetaBundle& bundle ) +{ + return getTrack( bundle.artist(), + bundle.album(), + bundle.title(), + bundle.discNumber(), + bundle.track(), + bundle.podcastBundle() ); +} + +MediaItem * +IpodMediaDevice::newPlaylist(const QString &name, MediaItem *parent, QPtrList items) +{ + m_dbChanged = true; + IpodMediaItem *item = new IpodMediaItem(parent, this); + item->setType(MediaItem::PLAYLIST); + item->setText(0, name); + + addToPlaylist(item, 0, items); + + return item; +} + + +void +IpodMediaDevice::addToPlaylist(MediaItem *mlist, MediaItem *after, QPtrList items) +{ + IpodMediaItem *list = dynamic_cast(mlist); + if(!list) + return; + + m_dbChanged = true; + + if(list->m_playlist) + { + itdb_playlist_remove(list->m_playlist); + list->m_playlist = 0; + } + + int order; + IpodMediaItem *it; + if(after) + { + order = after->m_order + 1; + it = dynamic_cast(after->nextSibling()); + } + else + { + order = 0; + it = dynamic_cast(list->firstChild()); + } + + for( ; it; it = dynamic_cast(it->nextSibling())) + { + it->m_order += items.count(); + } + + for(IpodMediaItem *it = dynamic_cast(items.first()); + it; + it = dynamic_cast(items.next()) ) + { + if(!it->m_track) + continue; + + IpodMediaItem *add; + if(it->parent() == list) + { + add = it; + if(after) + { + it->moveItem(after); + } + else + { + list->takeItem(it); + list->insertItem(it); + } + } + else + { + if(after) + { + add = new IpodMediaItem(list, after, this); + } + else + { + add = new IpodMediaItem(list, this); + } + } + after = add; + + add->setType(MediaItem::PLAYLISTITEM); + add->m_track = it->m_track; + add->bundleFromTrack( add->m_track, realPath(add->m_track->ipod_path) ); + add->setText(0, QString::fromUtf8(it->m_track->artist) + " - " + QString::fromUtf8(it->m_track->title) ); + add->m_order = order; + order++; + } + + // make numbering consecutive + int i=0; + for(IpodMediaItem *it = dynamic_cast(list->firstChild()); + it; + it = dynamic_cast(it->nextSibling())) + { + it->m_order = i; + i++; + } + + playlistFromItem(list); +} + +int +IpodMediaDevice::deleteItemFromDevice(MediaItem *mediaitem, int flags ) +{ + IpodMediaItem *item = dynamic_cast(mediaitem); + if(!item) + return -1; + + if( isCanceled() ) + return 0; + + if( !item->isVisible() ) + return 0; + + int count = 0; + + switch(item->type()) + { + case MediaItem::PLAYLISTITEM: + if( !(flags & DeleteTrack) ) + { + // FIXME possibly wrong instance of track is removed + itdb_playlist_remove_track(item->m_playlist, item->m_track); + delete item; + m_dbChanged = true; + break; + } + // else fall through + case MediaItem::STALE: + case MediaItem::TRACK: + case MediaItem::INVISIBLE: + case MediaItem::PODCASTITEM: + if(!(flags & OnlyPlayed) || item->played() > 0) + { + bool stale = item->type()==MediaItem::STALE; + Itdb_Track *track = item->m_track; + delete item; + + // delete from playlists + for( IpodMediaItem *it = static_cast(m_playlistItem)->findTrack(track); + it; + it = static_cast(m_playlistItem)->findTrack(track) ) + { + delete it; + } + + // delete all other occurrences + for( IpodMediaItem *it = getTrack( track ); + it; + it = getTrack( track ) ) + { + delete it; + } + + if( !stale ) + { + // delete file + KURL url; + url.setPath(realPath(track->ipod_path)); + deleteFile( url ); + count++; + } + + // remove from database + if( !removeDBTrack(track) ) + count = -1; + } + break; + case MediaItem::ORPHANED: + deleteFile( item->url() ); + delete item; + if( count >= 0 ) + count++; + break; + case MediaItem::PLAYLISTSROOT: + case MediaItem::PODCASTSROOT: + case MediaItem::INVISIBLEROOT: + case MediaItem::STALEROOT: + case MediaItem::ORPHANEDROOT: + case MediaItem::ARTIST: + case MediaItem::ALBUM: + case MediaItem::PODCASTCHANNEL: + case MediaItem::PLAYLIST: + // just recurse + { + IpodMediaItem *next = 0; + for(IpodMediaItem *it = dynamic_cast(item->firstChild()); + it; + it = next) + { + if( isCanceled() ) + break; + + next = dynamic_cast(it->nextSibling()); + int ret = deleteItemFromDevice(it, flags); + if( ret >= 0 && count >= 0 ) + count += ret; + else + count = -1; + } + } + if(item->type() == MediaItem::PLAYLIST && !isCanceled()) + { + m_dbChanged = true; + itdb_playlist_remove(item->m_playlist); + } + if(item->type() != MediaItem::PLAYLISTSROOT + && item->type() != MediaItem::PODCASTSROOT + && item->type() != MediaItem::INVISIBLEROOT + && item->type() != MediaItem::STALEROOT + && item->type() != MediaItem::ORPHANEDROOT) + { + if(!(flags & OnlyPlayed) || item->played() > 0 || item->childCount() == 0) + { + if(item->childCount() > 0) + debug() << "recursive deletion should have removed all children from " << item << "(" << item->text(0) << ")" << endl; + else + delete item; + } + } + break; + case MediaItem::DIRECTORY: + case MediaItem::UNKNOWN: + // this should not happen + count = -1; + break; + } + + updateRootItems(); + + return count; +} + +bool +IpodMediaDevice::createLockFile( bool silent ) +{ + QString lockFilePath; + pathExists( itunesDir( "iTunes:iTunesLock" ), &lockFilePath ); + + m_lockFile = new QFile( lockFilePath ); + QString msg; + bool ok = true; + if( m_lockFile->exists() ) + { + ok = false; + msg = i18n( "Media Device: iPod mounted at %1 already locked. " ).arg( mountPoint() ); + msg += i18n( "If you are sure that this is an error, then remove the file %1 and try again." ) + .arg( lockFilePath ); + + if( !silent ) + { + if( KMessageBox::warningContinueCancel( m_parent, msg, i18n( "Remove iTunes Lock File?" ), + KGuiItem(i18n("&Remove"), "editdelete"), QString::null, KMessageBox::Dangerous ) + == KMessageBox::Continue ) + { + msg = i18n( "Media Device: removing lockfile %1 failed: %2. " ) + .arg( lockFilePath, m_lockFile->errorString() ); + ok = m_lockFile->remove(); + } + else + { + msg = ""; + } + } + } + + if( ok && !m_lockFile->open( IO_WriteOnly ) ) + { + ok = false; + msg = i18n( "Media Device: failed to create lockfile on iPod mounted at %1: %2" ) + .arg(mountPoint(), m_lockFile->errorString()); + } + + if( ok ) + return true; + + delete m_lockFile; + m_lockFile = 0; + + if( !msg.isEmpty() ) + Amarok::StatusBar::instance()->longMessage( msg, KDE::StatusBar::Sorry ); + return false; +} + +bool +IpodMediaDevice::initializeIpod() +{ + QDir dir( mountPoint() ); + if( !dir.exists() ) + { + Amarok::StatusBar::instance()->longMessage( + i18n("Media device: Mount point %1 does not exist").arg(mountPoint()), + KDE::StatusBar::Error ); + return false; + } + + debug() << "initializing iPod mounted at " << mountPoint() << endl; + + // initialize iPod + m_itdb = itdb_new(); + if( m_itdb == 0 ) + return false; + + // in order to get directories right + detectModel(); + + itdb_set_mountpoint(m_itdb, QFile::encodeName(mountPoint())); + + Itdb_Playlist *mpl = itdb_playlist_new("iPod", false); + itdb_playlist_set_mpl(mpl); + Itdb_Playlist *podcasts = itdb_playlist_new("Podcasts", false); + itdb_playlist_set_podcasts(podcasts); + itdb_playlist_add(m_itdb, podcasts, -1); + itdb_playlist_add(m_itdb, mpl, 0); + + QString realPath; + if(!pathExists( itunesDir(), &realPath) ) + { + dir.setPath(realPath); + dir.mkdir(dir.absPath()); + } + if(!dir.exists()) + return false; + + if(!pathExists( itunesDir( "Music" ), &realPath) ) + { + dir.setPath(realPath); + dir.mkdir(dir.absPath()); + } + if(!dir.exists()) + return false; + + if(!pathExists( itunesDir( "iTunes" ), &realPath) ) + { + dir.setPath(realPath); + dir.mkdir(dir.absPath()); + } + if(!dir.exists()) + return false; + + if( !writeITunesDB( false ) ) + return false; + + Amarok::StatusBar::instance()->longMessage( + i18n("Media Device: Initialized iPod mounted at %1").arg(mountPoint()), + KDE::StatusBar::Information ); + + return true; +} + +bool +IpodMediaDevice::openDevice( bool silent ) +{ + m_isShuffle = false; + m_isMobile = false; + m_isIPhone = false; + m_supportsArtwork = false; + m_supportsVideo = false; + m_needsFirewireGuid = false; + m_rockboxFirmware = false; + m_dbChanged = false; + m_files.clear(); + + if( m_itdb ) + { + Amarok::StatusBar::instance()->longMessage( + i18n("Media Device: iPod at %1 already opened").arg(mountPoint()), + KDE::StatusBar::Sorry ); + return false; + } + + // try to find a mounted ipod + bool ipodFound = false; + bool canInitialize = false; + KMountPoint::List currentmountpoints = KMountPoint::currentMountPoints(); + for( KMountPoint::List::Iterator mountiter = currentmountpoints.begin(); + mountiter != currentmountpoints.end(); + ++mountiter ) + { + canInitialize = false; + QString devicenode = (*mountiter)->mountedFrom(); + QString mountpoint = (*mountiter)->mountPoint(); + + if( mountpoint.startsWith( "/proc" ) || + mountpoint.startsWith( "/sys" ) || + mountpoint.startsWith( "/dev" ) || + mountpoint.startsWith( "/boot" ) ) + continue; + + if( !mountPoint().isEmpty() ) + { + if( mountpoint != mountPoint() ) + continue; + canInitialize = true; + } + + else if( !deviceNode().isEmpty() ) + { + if( devicenode != deviceNode() ) + continue; + canInitialize = true; + } + + GError *err = 0; + m_itdb = itdb_parse(QFile::encodeName(mountpoint), &err); + if( err ) + { + g_error_free(err); + if( m_itdb ) + { + itdb_free( m_itdb ); + m_itdb = 0; + } + + if( !canInitialize ) + continue; + } + + if( mountPoint().isEmpty() ) + m_medium.setMountPoint( mountpoint ); + ipodFound = true; + break; + } + + if( !ipodFound && !canInitialize ) + { + if( !silent ) + { + Amarok::StatusBar::instance()->longMessage( + i18n("Media Device: No mounted iPod found" ), + KDE::StatusBar::Sorry ); + } + return false; + } + + if( !m_itdb && canInitialize ) + { + QString msg = i18n( "Media Device: could not find iTunesDB on device mounted at %1. " + "Should I try to initialize your iPod?" ).arg( mountPoint() ); + + if( !silent + && KMessageBox::warningContinueCancel( m_parent, msg, i18n( "Initialize iPod?" ), + KGuiItem(i18n("&Initialize"), "new") ) == KMessageBox::Continue ) + { + if( !initializeIpod() ) + { + if( m_itdb ) + { + itdb_free( m_itdb ); + m_itdb = 0; + } + + Amarok::StatusBar::instance()->longMessage( + i18n("Media Device: Failed to initialize iPod mounted at %1").arg(mountPoint()), + KDE::StatusBar::Sorry ); + + return false; + } + } + else + return false; + } + + detectModel(); + + if( !createLockFile( silent ) ) + { + if( m_itdb ) + { + itdb_free( m_itdb ); + m_itdb = 0; + } + return false; + } + + for( int i=0; i < itdb_musicdirs_number(m_itdb); i++) + { + QString real; + QString ipod; + ipod.sprintf( itunesDir( "Music:f%02d" ).latin1(), i ); + if(!pathExists( ipod, &real ) ) + { + QDir dir( real ); + dir.mkdir( real ); + dir.setPath( real ); + if( !dir.exists() ) + { + debug() << "failed to create hash dir " << real << endl; + Amarok::StatusBar::instance()->longMessage( + i18n("Media device: Failed to create directory %1").arg(real), + KDE::StatusBar::Error ); + return false; + } + } + } + + if( !silent ) + kapp->processEvents( 100 ); + + initView(); + GList *cur = m_itdb->playlists; + for( ; cur; cur = cur->next ) + { + Itdb_Playlist *playlist = (Itdb_Playlist *)cur->data; + addPlaylistToView( playlist ); + } + + if( !silent ) + kapp->processEvents( 100 ); + + for( cur = m_itdb->tracks; cur; cur = cur->next ) + { + Itdb_Track *track = (Itdb_Track *)cur->data; + addTrackToView( track, 0 /*parent*/, false /*checkintegrity*/, true /*batchmode*/ ); + } + + if( !silent ) + kapp->processEvents( 100 ); + + updateRootItems(); + m_customAction->setEnabled( true ); + + m_dbChanged = true; // write at least once for synchronising new stats + + return true; +} + +void +IpodMediaDevice::detectModel() +{ + // set some sane default values + m_isShuffle = false; + m_supportsArtwork = true; + m_supportsVideo = false; + m_isIPhone = false; + m_needsFirewireGuid = false; + m_rockboxFirmware = false; + + // needs recent libgpod-0.3.3 from cvs + bool guess = false; + if( m_itdb && m_itdb->device ) + { + const Itdb_IpodInfo *ipodInfo = itdb_device_get_ipod_info( m_itdb->device ); + const gchar *modelString = 0; + + m_supportsArtwork = itdb_device_supports_artwork( m_itdb->device ); + + if( ipodInfo ) + { + modelString = itdb_info_get_ipod_model_name_string ( ipodInfo->ipod_model ); + + switch( ipodInfo->ipod_model ) + { + case ITDB_IPOD_MODEL_SHUFFLE: +#ifdef HAVE_LIBGPOD_060 + case ITDB_IPOD_MODEL_SHUFFLE_SILVER: + case ITDB_IPOD_MODEL_SHUFFLE_PINK: + case ITDB_IPOD_MODEL_SHUFFLE_BLUE: + case ITDB_IPOD_MODEL_SHUFFLE_GREEN: + case ITDB_IPOD_MODEL_SHUFFLE_ORANGE: + case ITDB_IPOD_MODEL_SHUFFLE_PURPLE: +#endif + m_isShuffle = true; + break; +#ifdef HAVE_LIBGPOD_060 + case ITDB_IPOD_MODEL_IPHONE_1: + case ITDB_IPOD_MODEL_TOUCH_BLACK: + m_isIPhone = true; + debug() << "detected iPhone/iPod Touch" << endl; + break; + case ITDB_IPOD_MODEL_CLASSIC_SILVER: + case ITDB_IPOD_MODEL_CLASSIC_BLACK: +#endif + case ITDB_IPOD_MODEL_VIDEO_WHITE: + case ITDB_IPOD_MODEL_VIDEO_BLACK: + case ITDB_IPOD_MODEL_VIDEO_U2: + m_supportsVideo = true; + debug() << "detected video-capable iPod" << endl; + break; + case ITDB_IPOD_MODEL_MOBILE_1: + m_isMobile = true; + m_supportsArtwork = true; + debug() << "detected iTunes phone" << endl; + break; + case ITDB_IPOD_MODEL_INVALID: + case ITDB_IPOD_MODEL_UNKNOWN: + modelString = 0; + guess = true; + break; + default: + break; + } + +#ifdef HAVE_LIBGPOD_060 + switch( ipodInfo->ipod_generation ) + { + case ITDB_IPOD_GENERATION_CLASSIC_1: + case ITDB_IPOD_GENERATION_NANO_3: + case ITDB_IPOD_GENERATION_TOUCH_1: + m_needsFirewireGuid = true; + m_supportsVideo = true; + break; + case ITDB_IPOD_GENERATION_VIDEO_1: + case ITDB_IPOD_GENERATION_VIDEO_2: + m_supportsVideo = true; + break; + case ITDB_IPOD_GENERATION_SHUFFLE_1: + case ITDB_IPOD_GENERATION_SHUFFLE_2: + case ITDB_IPOD_GENERATION_SHUFFLE_3: + m_isShuffle = true; + break; + default: + break; + } +#endif + } + if( modelString ) + m_name = QString( "iPod %1" ).arg( QString::fromUtf8( modelString ) ); + + if( m_needsFirewireGuid ) + { + gchar *fwid = itdb_device_get_sysinfo( m_itdb->device, "FirewireGuid" ); + if( !fwid ) + { + Amarok::StatusBar::instance()->longMessage( + i18n("Your iPod's Firewire GUID is required for correctly updating its music database, but it is not known. See %1 for more information.").arg( "http://amarok.kde.org/wiki/Media_Device:IPod" ) ); + } + else + g_free( fwid ); + } + } + else + { + debug() << "iPod type detection failed, no video support" << endl; + Amarok::StatusBar::instance()->longMessage( + i18n("iPod type detection failed: no support for iPod Shuffle, for artwork or video") ); + guess = true; + } + + if( guess ) + { + if( pathExists( ":iTunes:iTunes_Control" ) ) + { + debug() << "iTunes/iTunes_Control found - assuming itunes phone" << endl; + m_isMobile = true; + } + else if( pathExists( ":iTunes_Control" ) ) + { + debug() << "iTunes_Control found - assuming iPhone/iPod Touch" << endl; + m_isIPhone = true; + } + } + + if( m_isIPhone ) + { + m_supportsVideo = true; + m_supportsArtwork = true; + } + + if( pathExists( ":.rockbox" ) ) + { + debug() << "RockBox firmware detected" << endl; + m_rockboxFirmware = true; + } +} + +void +IpodMediaDevice::initView() +{ + m_view->clear(); + + m_playlistItem = new IpodMediaItem( m_view, this ); + m_playlistItem->setText( 0, i18n("Playlists") ); + m_playlistItem->m_order = -6; + m_playlistItem->setType( MediaItem::PLAYLISTSROOT ); + + m_podcastItem = new IpodMediaItem( m_view, this ); + m_podcastItem->setText( 0, i18n("Podcasts") ); + m_podcastItem->m_order = -5; + m_podcastItem->setType( MediaItem::PODCASTSROOT ); + + m_invisibleItem = new IpodMediaItem( m_view, this ); + m_invisibleItem->setText( 0, i18n("Invisible") ); + m_invisibleItem->m_order = -4; + m_invisibleItem->setType( MediaItem::INVISIBLEROOT ); + + m_staleItem = new IpodMediaItem( m_view, this ); + m_staleItem->setText( 0, i18n("Stale") ); + m_staleItem->m_order = -3; + m_staleItem->setType( MediaItem::STALEROOT ); + + m_orphanedItem = new IpodMediaItem( m_view, this ); + m_orphanedItem->setText( 0, i18n("Orphaned") ); + m_orphanedItem->m_order = -2; + m_orphanedItem->setType( MediaItem::ORPHANEDROOT ); + + updateRootItems(); +} + +void +IpodMediaDevice::updateArtwork() +{ + if( !m_supportsArtwork ) + return; + + QPtrList items; + m_view->getSelectedLeaves( 0, &items, false ); + + int updateCount = 0; + for( QPtrList::iterator it = items.begin(); + it != items.end(); + it++ ) + { + IpodMediaItem *i = dynamic_cast( *it ); + if( !i || i->type() == MediaItem::PLAYLISTITEM ) + continue; + + const MetaBundle *bundle = i->bundle(); + QString image; + if( i->m_podcastInfo && !i->m_podcastInfo->rss.isEmpty() ) + { + PodcastChannelBundle pcb; + if( CollectionDB::instance()->getPodcastChannelBundle( i->m_podcastInfo->rss, &pcb ) ) + image = CollectionDB::instance()->podcastImage( pcb.imageURL().url(), 0 ); + } + if( image.isEmpty() ) + image = CollectionDB::instance()->albumImage(bundle->artist(), bundle->album(), false, 0); + if( !image.endsWith( "@nocover.png" ) ) + { + debug() << "adding image " << image << " to " << bundle->artist() << ":" + << bundle->album() << endl; + itdb_track_set_thumbnails( i->m_track, g_strdup( QFile::encodeName(image) ) ); + ++updateCount; + } + } + + Amarok::StatusBar::instance()->shortMessage( + i18n( "Updated artwork for one track", "Updated artwork for %n tracks", updateCount ) ); + + if(!m_dbChanged) + m_dbChanged = updateCount > 0; +} + + +bool +IpodMediaDevice::checkIntegrity() +{ + if( !m_itdb ) + return false; + + initView(); + + GList *cur = m_itdb->tracks; + while(cur) + { + Itdb_Track *track = (Itdb_Track *)cur->data; + + addTrackToView( track, 0, true ); + + cur = cur->next; + } + + cur = m_itdb->playlists; + for( ; cur; cur = cur->next ) + { + Itdb_Playlist *playlist = (Itdb_Playlist *)cur->data; + addPlaylistToView( playlist ); + } + + QString musicpath; + if (!pathExists( itunesDir( "Music" ), &musicpath )) + return false; + + QDir dir( musicpath, QString::null, QDir::Unsorted, QDir::Dirs ); + for(unsigned i=0; isetType(MediaItem::ORPHANED); + KURL url = KURL::fromPathOrURL(filename); + MetaBundle *bundle = new MetaBundle(url); + item->setBundle( bundle ); + QString title = bundle->artist() + " - " + bundle->title(); + item->setText(0, title); + } + } + } + + updateRootItems(); + + Amarok::StatusBar::instance()->shortMessage( + i18n( "Scanning for stale and orphaned tracks finished" ) ); + + return true; +} + +bool +IpodMediaDevice::closeDevice() //SLOT +{ + m_customAction->setEnabled( false ); + + writeITunesDB(); + + m_view->clear(); + m_podcastItem = 0; + m_playlistItem = 0; + m_orphanedItem = 0; + m_staleItem = 0; + m_invisibleItem = 0; + + if( m_lockFile ) + { + m_lockFile->remove(); + m_lockFile->close(); + delete m_lockFile; + m_lockFile = 0; + } + + m_files.clear(); + itdb_free(m_itdb); + m_itdb = 0; + m_masterPlaylist = 0; + m_podcastPlaylist = 0; + + m_name = "iPod"; + + return true; +} + +void +IpodMediaDevice::renameItem( QListViewItem *i ) // SLOT +{ + IpodMediaItem *item = dynamic_cast(i); + if(!item) + return; + + if(!item->type() == MediaItem::PLAYLIST) + return; + + m_dbChanged = true; + + g_free(item->m_playlist->name); + item->m_playlist->name = g_strdup( item->text( 0 ).utf8() ); +} + +void +IpodMediaDevice::playlistFromItem(IpodMediaItem *item) +{ + if( !m_itdb ) + return; + + m_dbChanged = true; + + item->m_playlist = itdb_playlist_new(item->text(0).utf8(), false /* dumb playlist */ ); + itdb_playlist_add(m_itdb, item->m_playlist, -1); + for(IpodMediaItem *it = dynamic_cast(item->firstChild()); + it; + it = dynamic_cast(it->nextSibling()) ) + { + itdb_playlist_add_track(item->m_playlist, it->m_track, -1); + it->m_playlist = item->m_playlist; + } +} + + +IpodMediaItem * +IpodMediaDevice::addTrackToView(Itdb_Track *track, IpodMediaItem *item, bool checkIntegrity, bool batchmode ) +{ + bool visible = false; + bool stale = false; + + if( checkIntegrity ) + { + if( !pathExists( track->ipod_path ) ) + { + stale = true; + debug() << "track: " << track->artist << " - " << track->album << " - " << track->title << " is stale: " << track->ipod_path << " does not exist" << endl; + if( item ) + m_staleItem->insertItem( item ); + else + item = new IpodMediaItem(m_staleItem, this); + item->setType(MediaItem::STALE); + QString title = QString::fromUtf8(track->artist) + " - " + + QString::fromUtf8(track->title); + item->setText( 0, title ); + item->m_track = track; + } + else + { + m_files.insert( QString(track->ipod_path).lower(), track ); + } + } + + if(!stale && m_masterPlaylist && itdb_playlist_contains_track(m_masterPlaylist, track) + && (!m_podcastPlaylist || !itdb_playlist_contains_track(m_podcastPlaylist, track))) + { + visible = true; + + QString artistName; + if( track->compilation ) + artistName = i18n( "Various Artists" ); + else + artistName = QString::fromUtf8(track->artist); + + IpodMediaItem *artist = getArtist(artistName); + if(!artist) + { + artist = new IpodMediaItem(m_view, this); + artist->setText( 0, artistName ); + artist->setType( MediaItem::ARTIST ); + if( artistName == i18n( "Various Artists" ) ) + artist->m_order = -1; + } + + QString albumName(QString::fromUtf8(track->album)); + MediaItem *album = artist->findItem(albumName); + if(!album) + { + album = new IpodMediaItem( artist, this ); + album->setText( 0, albumName ); + album->setType( MediaItem::ALBUM ); + } + + if( item ) + album->insertItem( item ); + else + { + item = new IpodMediaItem( album, this ); + } + QString titleName = QString::fromUtf8(track->title); + if( track->compilation ) + item->setText( 0, QString::fromUtf8(track->artist) + i18n( " - " ) + titleName ); + else + item->setText( 0, titleName ); + item->setType( MediaItem::TRACK ); + item->m_track = track; + item->bundleFromTrack( track, realPath(track->ipod_path) ); + item->m_order = track->track_nr; + } + + if(!stale && m_podcastPlaylist && itdb_playlist_contains_track(m_podcastPlaylist, track)) + { + visible = true; + + QString channelName(QString::fromUtf8(track->album)); + IpodMediaItem *channel = dynamic_cast(m_podcastItem->findItem(channelName)); + if(!channel) + { + channel = new IpodMediaItem(m_podcastItem, this); + channel->setText( 0, channelName ); + channel->setType( MediaItem::PODCASTCHANNEL ); + channel->m_podcastInfo = new PodcastInfo; + } + + if( item ) + channel->insertItem( item ); + else + item = new IpodMediaItem( channel, this ); + item->setText( 0, QString::fromUtf8(track->title) ); + item->setType( MediaItem::PODCASTITEM ); + item->m_track = track; + item->bundleFromTrack( track, realPath(track->ipod_path) ); + + PodcastInfo *info = new PodcastInfo; + item->m_podcastInfo = info; + info->url = QString::fromUtf8( track->podcasturl ); + info->rss = QString::fromUtf8( track->podcastrss ); + info->description = QString::fromUtf8( track->description ); + info->date.setTime_t( itdb_time_mac_to_host( track->time_released) ); + + if( !info->rss.isEmpty() && channel->m_podcastInfo->rss.isEmpty() ) + { + channel->m_podcastInfo->rss = info->rss; + } + } + + if( !stale && !visible ) + { + debug() << "invisible, title=" << track->title << endl; + if( item ) + m_invisibleItem->insertItem( item ); + else + item = new IpodMediaItem(m_invisibleItem, this); + QString title = QString::fromUtf8(track->artist) + " - " + + QString::fromUtf8(track->title); + item->setText( 0, title ); + item->setType( MediaItem::INVISIBLE ); + item->m_track = track; + item->bundleFromTrack( track, realPath(track->ipod_path) ); + } + + if ( !batchmode ) + updateRootItems(); + + return item; +} + +void +IpodMediaDevice::addPlaylistToView( Itdb_Playlist *pl ) +{ + if( itdb_playlist_is_mpl( pl ) ) + { + m_masterPlaylist = pl; + return; + } + + if( itdb_playlist_is_podcasts( pl ) ) + { + m_podcastPlaylist = pl; + return; + } + + if( pl->is_spl ) + { + debug() << "playlist " << pl->name << " is a smart playlist" << endl; + } + + QString name( QString::fromUtf8(pl->name) ); + IpodMediaItem *playlist = dynamic_cast(m_playlistItem->findItem(name)); + if( !playlist ) + { + playlist = new IpodMediaItem( m_playlistItem, this ); + playlist->setText( 0, name ); + playlist->setType( MediaItem::PLAYLIST ); + playlist->m_playlist = pl; + } + + int i=0; + GList *cur = pl->members; + while( cur ) + { + Itdb_Track *track = (Itdb_Track *)cur->data; + IpodMediaItem *item = new IpodMediaItem(playlist, this); + QString title = QString::fromUtf8(track->artist) + " - " + + QString::fromUtf8(track->title); + item->setText( 0, title ); + item->setType( MediaItem::PLAYLISTITEM ); + item->m_playlist = pl; + item->m_track = track; + item->bundleFromTrack( track, realPath(track->ipod_path) ); + item->m_order = i; + + cur = cur->next; + i++; + } +} + +QString +IpodMediaDevice::itunesDir(const QString &p) const +{ + QString base( ":iPod_Control" ); + if( m_isMobile ) + base = ":iTunes:iTunes_Control"; + else if( m_isIPhone ) + base = ":iTunes_Control"; + + if( !p.startsWith( ":" ) ) + base += ':'; + return base + p; +} + +QString +IpodMediaDevice::realPath(const char *ipodPath) +{ + QString path; + if(m_itdb) + { + path = QFile::decodeName(itdb_get_mountpoint(m_itdb)); + path.append(QString(ipodPath).replace(':', "/")); + } + + return path; +} + +QString +IpodMediaDevice::ipodPath(const QString &realPath) +{ + if(m_itdb) + { + QString mp = QFile::decodeName(itdb_get_mountpoint(m_itdb)); + if(realPath.startsWith(mp)) + { + QString path = realPath; + path = path.mid(mp.length()); + path = path.replace('/', ":"); + return path; + } + } + + return QString(); +} + +class IpodWriteDBJob : public ThreadManager::DependentJob +{ + public: + IpodWriteDBJob( QObject *parent, Itdb_iTunesDB *itdb, bool isShuffle, bool *resultPtr ) + : ThreadManager::DependentJob( parent, "IpodWriteDBJob" ) + , m_itdb( itdb ) + , m_isShuffle( isShuffle ) + , m_resultPtr( resultPtr ) + , m_return( true ) + {} + + private: + virtual bool doJob() + { + if( !m_itdb ) + { + m_return = false; + } + + GError *error = 0; + if (m_return && !itdb_write (m_itdb, &error)) + { /* an error occurred */ + m_return = false; + if(error) + { + if (error->message) + debug() << "itdb_write error: " << error->message << endl; + else + debug() << "itdb_write error: " << "error->message == 0!" << endl; + g_error_free (error); + } + error = 0; + } + + if( m_return && m_isShuffle ) + { + /* write shuffle data */ + if (!itdb_shuffle_write (m_itdb, &error)) + { /* an error occurred */ + m_return = false; + if(error) + { + if (error->message) + debug() << "itdb_shuffle_write error: " << error->message << endl; + else + debug() << "itdb_shuffle_write error: " << "error->message == 0!" << endl; + g_error_free (error); + } + error = 0; + } + } + + return true; + } + + virtual void completeJob() + { + *m_resultPtr = m_return; + } + + Itdb_iTunesDB *m_itdb; + bool m_isShuffle; + bool *m_resultPtr; + bool m_return; +}; + +bool +IpodMediaDevice::writeITunesDB( bool threaded ) +{ + if(!m_itdb) + return false; + + if(m_dbChanged) + { + bool ok = false; + if( !threaded || MediaBrowser::instance()->isQuitting() ) + { + if( !m_itdb ) + { + return false; + } + + ok = true; + GError *error = 0; + if ( !itdb_write (m_itdb, &error) ) + { /* an error occurred */ + if(error) + { + if (error->message) + debug() << "itdb_write error: " << error->message << endl; + else + debug() << "itdb_write error: " << "error->message == 0!" << endl; + g_error_free (error); + } + error = 0; + ok = false; + } + + if( m_isShuffle ) + { + /* write shuffle data */ + if (!itdb_shuffle_write (m_itdb, &error)) + { /* an error occurred */ + if(error) + { + if (error->message) + debug() << "itdb_shuffle_write error: " << error->message << endl; + else + debug() << "itdb_shuffle_write error: " << "error->message == 0!" << endl; + g_error_free (error); + } + error = 0; + ok = false; + } + } + } + else + { + ThreadManager::instance()->queueJob( new IpodWriteDBJob( this, m_itdb, m_isShuffle, &ok ) ); + while( ThreadManager::instance()->isJobPending( "IpodWriteDBJob" ) ) + { + kapp->processEvents(); + usleep( 10000 ); + } + } + + if( ok ) + { + m_dbChanged = false; + } + else + { + Amarok::StatusBar::instance()->longMessage( + i18n("Media device: failed to write iPod database"), + KDE::StatusBar::Error ); + } + + return ok; + } + return true; +} + + +IpodMediaItem * +IpodMediaDevice::getArtist(const QString &artist) +{ + for(IpodMediaItem *it = dynamic_cast(m_view->firstChild()); + it; + it = dynamic_cast(it->nextSibling())) + { + if(it->m_type==MediaItem::ARTIST && artist == it->text(0)) + return it; + } + + return 0; +} + +IpodMediaItem * +IpodMediaDevice::getAlbum(const QString &artist, const QString &album) +{ + IpodMediaItem *item = getArtist(artist); + if(item) + return dynamic_cast(item->findItem(album)); + + return 0; +} + +IpodMediaItem * +IpodMediaDevice::getTrack(const QString &artist, const QString &album, const QString &title, + int discNumber, int trackNumber, const PodcastEpisodeBundle *peb) +{ + IpodMediaItem *item = getAlbum(artist, album); + if(item) + { + for( IpodMediaItem *track = dynamic_cast(item->findItem(title)); + track; + track = dynamic_cast(item->findItem(title, track)) ) + { + if( ( discNumber==-1 || track->bundle()->discNumber()==discNumber ) + && ( trackNumber==-1 || track->bundle()->track()==trackNumber ) ) + return track; + } + } + + item = getAlbum( i18n( "Various Artists" ), album ); + if( item ) + { + QString t = artist + i18n(" - ") + title; + for( IpodMediaItem *track = dynamic_cast(item->findItem(t)); + track; + track = dynamic_cast(item->findItem(t, track)) ) + { + if( ( discNumber==-1 || track->bundle()->discNumber()==discNumber ) + && ( trackNumber==-1 || track->bundle()->track()==trackNumber ) ) + return track; + } + } + + if(m_podcastItem) + { + item = dynamic_cast(m_podcastItem->findItem(album)); + if(item) + { + for( IpodMediaItem *track = dynamic_cast(item->findItem(title)); + track; + track = dynamic_cast(item->findItem(title, track)) ) + { + if( ( discNumber==-1 || track->bundle()->discNumber()==discNumber ) + && ( trackNumber==-1 || track->bundle()->track()==trackNumber ) + && ( !track->bundle()->podcastBundle() || !peb + || track->bundle()->podcastBundle()->url() == peb->url() ) ) + return track; + } + } + } + + return 0; +} + +IpodMediaItem * +IpodMediaDevice::getTrack( const Itdb_Track *itrack ) +{ + QString artist = QString::fromUtf8( itrack->artist ); + QString album = QString::fromUtf8( itrack->album ); + QString title = QString::fromUtf8( itrack->title ); + + IpodMediaItem *item = getAlbum( artist, album ); + if(item) + { + for( IpodMediaItem *track = dynamic_cast(item->findItem( title ) ); + track; + track = dynamic_cast(item->findItem(title, track)) ) + { + if( track->m_track == itrack ) + return track; + } + } + + item = getAlbum( i18n( "Various Artists" ), album ); + if( item ) + { + QString t = artist + i18n(" - ") + title; + for( IpodMediaItem *track = dynamic_cast(item->findItem(t)); + track; + track = dynamic_cast(item->findItem(t, track)) ) + { + if( track->m_track == itrack ) + return track; + } + } + + if(m_podcastItem) + { + item = dynamic_cast(m_podcastItem->findItem(album)); + if(item) + { + for( IpodMediaItem *track = dynamic_cast(item->findItem(title)); + track; + track = dynamic_cast(item->findItem(title, track)) ) + { + if( track->m_track == itrack ) + return track; + } + } + } + + return 0; +} + + +KURL +IpodMediaDevice::determineURLOnDevice(const MetaBundle &bundle) +{ + if( !m_itdb ) + { + debug() << "m_itdb is NULL" << endl; + return KURL(); + } + + QString local = bundle.filename(); + QString type = local.section('.', -1).lower(); + + QString trackpath; + QString realpath; + do + { + int num = std::rand() % 1000000; + int music_dirs = itdb_musicdirs_number(m_itdb) > 1 ? itdb_musicdirs_number(m_itdb) : 20; + int dir = num % music_dirs; + QString dirname; + dirname.sprintf( "%s:Music:f%02d", itunesDir().latin1(), dir ); + if( !pathExists( dirname ) ) + { + QString realdir = realPath(dirname.latin1()); + QDir qdir( realdir ); + qdir.mkdir( realdir ); + } + QString filename; + filename.sprintf( ":kpod%07d.%s", num, type.latin1() ); + trackpath = dirname + filename; + } + while( pathExists( trackpath, &realpath ) ); + + return realpath; +} + +bool +IpodMediaDevice::removeDBTrack(Itdb_Track *track) +{ + if(!m_itdb) + return false; + + if(!track) + return false; + + if(track->itdb != m_itdb) + { + return false; + } + + m_dbChanged = true; + + Itdb_Playlist *mpl = itdb_playlist_mpl(m_itdb); + while(itdb_playlist_contains_track(mpl, track)) + { + itdb_playlist_remove_track(mpl, track); + } + + GList *cur = m_itdb->playlists; + while(cur) + { + Itdb_Playlist *pl = (Itdb_Playlist *)cur->data; + while(itdb_playlist_contains_track(pl, track)) + { + itdb_playlist_remove_track(pl, track); + } + cur = cur->next; + } + + // also frees track's memory + itdb_track_remove(track); + + return true; +} + +bool +IpodMediaDevice::getCapacity( KIO::filesize_t *total, KIO::filesize_t *available ) +{ + if(!m_itdb) + return false; + +#ifdef HAVE_STATVFS + QString path; + if ( !pathExists( itunesDir(), &path ) ) + return false; + + struct statvfs buf; + if(statvfs(QFile::encodeName(path), &buf) != 0) + { + *total = 0; + *available = 0; + return false; + } + + *total = buf.f_blocks * (KIO::filesize_t)buf.f_frsize; + *available = buf.f_bavail * (KIO::filesize_t)buf.f_frsize; + + return *total > 0; +#else + return false; +#endif +} + +void +IpodMediaDevice::rmbPressed( QListViewItem* qitem, const QPoint& point, int ) +{ + MediaItem *item = dynamic_cast(qitem); + bool locked = m_mutex.locked(); + + KURL::List urls = m_view->nodeBuildDragList( 0 ); + KPopupMenu menu( m_view ); + + enum Actions { CREATE_PLAYLIST, APPEND, LOAD, QUEUE, + COPY_TO_COLLECTION, + BURN_ARTIST, BURN_ALBUM, BURN_DATACD, BURN_AUDIOCD, + RENAME, SUBSCRIBE, + MAKE_PLAYLIST, ADD_TO_PLAYLIST, ADD, + DELETE_PLAYED, DELETE_FROM_IPOD, REMOVE_FROM_PLAYLIST, + FIRST_PLAYLIST}; + + KPopupMenu *playlistsMenu = 0; + if ( item ) + { + if( item->type() == MediaItem::PLAYLISTSROOT ) + { + menu.insertItem( SmallIconSet(Amarok::icon( "add_playlist" )), + i18n("Create Playlist..."), CREATE_PLAYLIST ); + } + else + { + menu.insertItem( SmallIconSet( Amarok::icon( "playlist" ) ), i18n( "&Load" ), LOAD ); + menu.insertItem( SmallIconSet( Amarok::icon( "add_playlist" ) ), i18n( "&Append to Playlist" ), APPEND ); + menu.insertItem( SmallIconSet( Amarok::icon( "fastforward" ) ), i18n( "&Queue Tracks" ), QUEUE ); + } + menu.insertSeparator(); + + menu.insertItem( SmallIconSet( Amarok::icon( "collection" ) ), i18n( "&Copy Files to Collection..." ), COPY_TO_COLLECTION ); + switch( item->type() ) + { + case MediaItem::ARTIST: + menu.insertItem( SmallIconSet( Amarok::icon( "cdrom_unmount" ) ), i18n( "Burn All Tracks by This Artist" ), BURN_ARTIST ); + menu.setItemEnabled( BURN_ARTIST, K3bExporter::isAvailable() ); + break; + + case MediaItem::ALBUM: + menu.insertItem( SmallIconSet( Amarok::icon( "cdrom_unmount" ) ), i18n( "Burn This Album" ), BURN_ALBUM ); + menu.setItemEnabled( BURN_ALBUM, K3bExporter::isAvailable() ); + break; + + default: + menu.insertItem( SmallIconSet( Amarok::icon( "cdrom_unmount" ) ), i18n( "Burn to CD as Data" ), BURN_DATACD ); + menu.setItemEnabled( BURN_DATACD, K3bExporter::isAvailable() ); + menu.insertItem( SmallIconSet( Amarok::icon( "cdaudio_unmount" ) ), i18n( "Burn to CD as Audio" ), BURN_AUDIOCD ); + menu.setItemEnabled( BURN_AUDIOCD, K3bExporter::isAvailable() ); + break; + } + + menu.insertSeparator(); + + if( (item->type() == MediaItem::PODCASTITEM + || item->type() == MediaItem::PODCASTCHANNEL) ) + { + IpodMediaItem *it = static_cast(item); + menu.insertItem( SmallIconSet( Amarok::icon( "podcast" ) ), i18n( "Subscribe to This Podcast" ), SUBSCRIBE ); + //menu.setItemEnabled( SUBSCRIBE, item->bundle()->podcastBundle() && item->bundle()->podcastBundle()->parent().isValid() ); + menu.setItemEnabled( SUBSCRIBE, it->m_podcastInfo && !it->m_podcastInfo->rss.isEmpty() ); + menu.insertSeparator(); + } + + switch( item->type() ) + { + case MediaItem::ARTIST: + case MediaItem::ALBUM: + case MediaItem::TRACK: + case MediaItem::PODCASTCHANNEL: + case MediaItem::PODCASTSROOT: + case MediaItem::PODCASTITEM: + if(m_playlistItem) + { + menu.insertItem( SmallIconSet( Amarok::icon( "playlist" ) ), i18n( "Make Media Device Playlist" ), MAKE_PLAYLIST ); + menu.setItemEnabled( MAKE_PLAYLIST, !locked ); + + playlistsMenu = new KPopupMenu(&menu); + int i=0; + for(MediaItem *it = dynamic_cast(m_playlistItem->firstChild()); + it; + it = dynamic_cast(it->nextSibling())) + { + playlistsMenu->insertItem( SmallIconSet( Amarok::icon( "playlist" ) ), it->text(0), FIRST_PLAYLIST+i ); + i++; + } + menu.insertItem( SmallIconSet( Amarok::icon( "playlist" ) ), i18n("Add to Playlist"), playlistsMenu, ADD_TO_PLAYLIST ); + menu.setItemEnabled( ADD_TO_PLAYLIST, !locked && m_playlistItem->childCount()>0 ); + menu.insertSeparator(); + } + + if( item->type() == MediaItem::ARTIST || + item->type() == MediaItem::ALBUM || + item->type() == MediaItem::TRACK || + item->type() == MediaItem::ORPHANED ) + { + menu.insertItem( SmallIconSet( Amarok::icon( "edit" ) ), + i18n( "Edit &Information...", "Edit &Information for %n Tracks...", urls.count()), + RENAME ); + } + break; + + case MediaItem::ORPHANED: + case MediaItem::ORPHANEDROOT: + menu.insertItem( SmallIconSet( Amarok::icon( "edit" ) ), i18n( "Add to Database" ), ADD ); + menu.setItemEnabled( ADD, !locked ); + break; + + case MediaItem::PLAYLIST: + menu.insertItem( SmallIconSet( Amarok::icon( "edit" ) ), i18n( "Rename" ), RENAME ); + menu.setItemEnabled( RENAME, !locked ); + break; + + default: + break; + } + + if( item->type() == MediaItem::PLAYLIST || item->type() == MediaItem::PLAYLISTITEM ) + { + menu.insertItem( SmallIconSet( Amarok::icon( "remove_from_playlist" ) ), + item->type()==MediaItem::PLAYLIST ? i18n( "Remove Playlist" ) : i18n( "Remove from Playlist" ), + REMOVE_FROM_PLAYLIST ); + menu.setItemEnabled( REMOVE_FROM_PLAYLIST, !locked ); + } + if( item->type() == MediaItem::PODCASTSROOT || item->type() == MediaItem::PODCASTCHANNEL ) + { + menu.insertItem( SmallIconSet( Amarok::icon( "remove" ) ), i18n( "Delete Podcasts Already Played" ), DELETE_PLAYED ); + menu.setItemEnabled( DELETE_PLAYED, !locked ); + } + menu.insertItem( SmallIconSet( Amarok::icon( "remove" ) ), + i18n( "Delete Track from iPod", "Delete %n Tracks from iPod", urls.count() ), + DELETE_FROM_IPOD ); + menu.setItemEnabled( DELETE_FROM_IPOD, !locked && urls.count() > 0 ); + } + + int id = menu.exec( point ); + switch( id ) + { + case CREATE_PLAYLIST: + break; + case LOAD: + Playlist::instance()->insertMedia( urls, Playlist::Replace ); + break; + case APPEND: + Playlist::instance()->insertMedia( urls, Playlist::Append ); + break; + case QUEUE: + Playlist::instance()->insertMedia( urls, Playlist::Queue ); + break; + case COPY_TO_COLLECTION: + { + QPtrList items; + m_view->getSelectedLeaves( 0, &items ); + + KURL::List urls; + for( MediaItem *it = items.first(); + it; + it = items.next() ) + { + if( it->url().isValid() ) + urls << it->url(); + } + + CollectionView::instance()->organizeFiles( urls, i18n("Copy Files To Collection"), true ); + } + break; + case BURN_ARTIST: + K3bExporter::instance()->exportArtist( item->text(0) ); + break; + case BURN_ALBUM: + K3bExporter::instance()->exportAlbum( item->text(0) ); + break; + case BURN_DATACD: + K3bExporter::instance()->exportTracks( urls, K3bExporter::DataCD ); + break; + case BURN_AUDIOCD: + K3bExporter::instance()->exportTracks( urls, K3bExporter::AudioCD ); + break; + case SUBSCRIBE: + PlaylistBrowser::instance()->addPodcast( static_cast(item)->m_podcastInfo->rss ); + break; + case RENAME: + if( item->type() == MediaItem::PLAYLIST ) + { + m_view->rename(item, 0); + } + else + { + TagDialog *dialog = NULL; + if( urls.count() == 1 ) + dialog = new TagDialog( urls.first(), m_view ); + else + dialog = new TagDialog( urls, m_view ); + dialog->show(); + } + break; + default: + break; + } + + if( !m_mutex.locked() ) + { + switch( id ) + { + case CREATE_PLAYLIST: + case MAKE_PLAYLIST: + { + QPtrList items; + if( id == MAKE_PLAYLIST ) + m_view->getSelectedLeaves( 0, &items ); + QString base(i18n("New Playlist")); + QString name = base; + int i=1; + while(m_playlistItem->findItem(name)) + { + QString num; + num.setNum(i); + name = base + ' ' + num; + i++; + } + MediaItem *pl = newPlaylist(name, m_playlistItem, items); + m_view->ensureItemVisible(pl); + m_view->rename(pl, 0); + } + break; + case ADD: + { + int dupes = 0; + + if(item->type() == MediaItem::ORPHANEDROOT) + { + MediaItem *next = 0; + for(MediaItem *it = dynamic_cast(item->firstChild()); + it; + it = next) + { + next = dynamic_cast(it->nextSibling()); + if( trackExists( *it->bundle() ) ) + { + dupes++; + continue; + } + + item->takeItem(it); + insertTrackIntoDB(it->url().path(), *it->bundle(), *it->bundle(), 0); + delete it; + } + } + else + { + QPtrList items; + m_view->getSelectedLeaves( 0, &items ); + for( QPtrList::iterator it = items.begin(); + it != items.end(); + it++ ) + { + IpodMediaItem *i = dynamic_cast( *it ); + if( !i || i->type() != MediaItem::ORPHANED ) + continue; + + if( trackExists( *i->bundle() ) ) + { + dupes++; + continue; + } + + i->parent()->takeItem(i); + insertTrackIntoDB(i->url().path(), *i->bundle(), *i->bundle(), 0); + delete i; + } + } + + if( dupes > 0 ) + Amarok::StatusBar::instance()->shortMessage( i18n( + "One duplicate track not added to database", + "%n duplicate tracks not added to database", dupes ) ); + } + break; + case DELETE_PLAYED: + { + MediaItem *podcasts = 0; + if(item->type() == MediaItem::PODCASTCHANNEL) + podcasts = dynamic_cast(item->parent()); + else + podcasts = item; + deleteFromDevice( podcasts, true ); + } + break; + case REMOVE_FROM_PLAYLIST: + deleteFromDevice(m_playlistItem, None); + break; + case DELETE_FROM_IPOD: + deleteFromDevice(); + break; + default: + if( playlistsMenu && id >= FIRST_PLAYLIST ) + { + QString name = playlistsMenu->text(id); + if( name != QString::null ) + { + MediaItem *list = m_playlistItem->findItem(name); + if(list) + { + MediaItem *after = 0; + for(MediaItem *it = dynamic_cast(list->firstChild()); + it; + it = dynamic_cast(it->nextSibling())) + after = it; + QPtrList items; + m_view->getSelectedLeaves( 0, &items ); + addToPlaylist( list, after, items ); + } + } + } + break; + } + + if( m_dbChanged && lockDevice( true ) ) + { + synchronizeDevice(); + unlockDevice(); + } + } +} + +QStringList +IpodMediaDevice::supportedFiletypes() +{ + QStringList list; + list << "mp3"; + list << "m4a"; + list << "m4b"; + list << "wav"; + list << "mp4"; + list << "aa"; + + if( m_supportsVideo ) + { + list << "m4v"; + list << "mp4v"; + list << "mov"; + list << "mpg"; + } + + if( m_rockboxFirmware ) + { + list << "ogg"; + list << "mpc"; + list << "ac3"; + list << "adx"; + list << "aiff"; + list << "flac"; + list << "mid"; + list << "midi"; + list << "shn"; + list << "wv"; + list << "ape"; + list << "tta"; + } + + return list; +} + +void +IpodMediaDevice::addConfigElements( QWidget *parent ) +{ + m_autoDeletePodcastsCheck = new QCheckBox( parent ); + m_autoDeletePodcastsCheck->setText( i18n( "&Automatically delete podcasts" ) ); + QToolTip::add( m_autoDeletePodcastsCheck, i18n( "Automatically delete podcast shows already played when connecting device" ) ); + m_autoDeletePodcastsCheck->setChecked( m_autoDeletePodcasts ); + + m_syncStatsCheck = new QCheckBox( parent ); + m_syncStatsCheck->setText( i18n( "&Synchronize with Amarok statistics" ) ); + QToolTip::add( m_syncStatsCheck, i18n( "Synchronize with Amarok statistics and submit tracks played to last.fm" ) ); + m_syncStatsCheck->setChecked( m_syncStats ); +} + +void +IpodMediaDevice::removeConfigElements( QWidget * /*parent*/ ) +{ + delete m_syncStatsCheck; + m_syncStatsCheck = 0; + + delete m_autoDeletePodcastsCheck; + m_autoDeletePodcastsCheck = 0; + +} + +void +IpodMediaDevice::applyConfig() +{ + m_autoDeletePodcasts = m_autoDeletePodcastsCheck->isChecked(); + m_syncStats = m_syncStatsCheck->isChecked(); + + setConfigBool( "SyncStats", m_syncStats ); + setConfigBool( "AutoDeletePodcasts", m_autoDeletePodcasts ); +} + +void +IpodMediaDevice::loadConfig() +{ + MediaDevice::loadConfig(); + + m_syncStats = configBool( "SyncStats", false ); + m_autoDeletePodcasts = configBool( "AutoDeletePodcasts", false ); + m_autoConnect = configBool( "AutoConnect", true ); +} + +bool +IpodMediaDevice::pathExists( const QString &ipodPath, QString *realPath ) +{ + QDir curDir( mountPoint() ); + curDir.setFilter(curDir.filter() | QDir::Hidden); + QString curPath = mountPoint(); + QStringList components = QStringList::split( ":", ipodPath ); + + bool found = false; + QStringList::iterator it = components.begin(); + for( ; it != components.end(); ++it ) + { + found = false; + for(uint i=0; ierror()) + { + debug() << "file deletion failed: " << job->errorText() << endl; + } + m_waitForDeletion = false; + m_parent->updateStats(); +} + +void +IpodMediaDevice::deleteFile( const KURL &url ) +{ + debug() << "deleting " << url.prettyURL() << endl; + m_waitForDeletion = true; + KIO::Job *job = KIO::file_delete( url, false ); + connect( job, SIGNAL( result( KIO::Job * ) ), + this, SLOT( fileDeleted( KIO::Job * ) ) ); + do + { + kapp->processEvents( 100 ); + if( isCanceled() ) + break; + usleep( 10000 ); + } while( m_waitForDeletion ); + + if(!isTransferring()) + setProgress( progress() + 1 ); +} + +#include "ipodmediadevice.moc" diff --git a/amarok/src/mediadevice/ipod/ipodmediadevice.h b/amarok/src/mediadevice/ipod/ipodmediadevice.h new file mode 100644 index 00000000..bb38fea8 --- /dev/null +++ b/amarok/src/mediadevice/ipod/ipodmediadevice.h @@ -0,0 +1,176 @@ +/*************************************************************************** + copyright : (C) 2005, 2006 by Martin Aumueller + email : aumuell@reserv.at + + copyright : (C) 2004 by Christian Muehlhaeuser + email : chris@chris.de + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License version 2 as * + * published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301 USA * + ***************************************************************************/ + +#ifndef AMAROK_IPODMEDIADEVICE_H +#define AMAROK_IPODMEDIADEVICE_H + +extern "C" { +#include +} + + +#include "mediabrowser.h" + +#include +#include +#include + +#include + +class QCheckBox; +class QLabel; +class QLineEdit; +class QFile; + +class KAction; + +class IpodMediaItem; +class PodcastInfo; + +class IpodMediaDevice : public MediaDevice +{ + friend class IpodMediaItem; + Q_OBJECT + + public: + IpodMediaDevice(); + void init( MediaBrowser* parent ); + virtual ~IpodMediaDevice(); + virtual bool autoConnect() { return m_autoConnect; } + virtual bool asynchronousTransfer() { return false; /* kernel buffer flushes freeze Amarok */ } + QStringList supportedFiletypes(); + + bool isConnected(); + + virtual void addConfigElements( QWidget *parent ); + virtual void removeConfigElements( QWidget *parent ); + virtual void applyConfig(); + virtual void loadConfig(); + virtual MediaItem*tagsChanged( MediaItem *item, const MetaBundle &bundle ); + + virtual KAction *customAction() { return m_customAction; } + + protected: + MediaItem *trackExists( const MetaBundle& bundle ); + + bool openDevice( bool silent=false ); + bool closeDevice(); + bool lockDevice(bool tryLock=false ) { if( tryLock ) { return m_mutex.tryLock(); } else { m_mutex.lock(); return true; } } + void unlockDevice() { m_mutex.unlock(); } + void initView(); + void detectModel(); + + virtual MediaItem *copyTrackToDevice( const MetaBundle& bundle ); + /** + * Insert track already located on media device into the device's database + * @param pathname Location of file on the device to add to the database + * @param bundle MetaBundle of track + * @param podcastInfo PodcastInfo of track if it is a podcast, 0 otherwise + * @return If successful, the created MediaItem in the media device view, else 0 + */ + virtual MediaItem *insertTrackIntoDB( const QString &pathname, + const MetaBundle &metaBundle, const MetaBundle &propertiesBundle, + const PodcastInfo *podcastInfo ); + + virtual MediaItem *updateTrackInDB( IpodMediaItem *item, const QString &pathname, + const MetaBundle &metaBundle, const MetaBundle &propertiesBundle, + const PodcastInfo *podcastInfo ); + + /** + * Determine the url for which a track should be uploaded to on the device + * @param bundle MetaBundle of track to base pathname creation on + * @return the url to upload the track to + */ + virtual KURL determineURLOnDevice( const MetaBundle& bundle ); + + void synchronizeDevice(); + int deleteItemFromDevice( MediaItem *item, int flags=DeleteTrack ); + virtual void deleteFile( const KURL &url ); + void addToPlaylist( MediaItem *list, MediaItem *after, QPtrList items ); + MediaItem *newPlaylist( const QString &name, MediaItem *list, QPtrList items ); + bool getCapacity( KIO::filesize_t *total, KIO::filesize_t *available ); + void rmbPressed( QListViewItem* qitem, const QPoint& point, int ); + bool checkIntegrity(); + void updateArtwork(); + + protected slots: + void renameItem( QListViewItem *item ); + virtual void fileDeleted( KIO::Job *job ); + + private: + bool initializeIpod(); + bool writeITunesDB( bool threaded=true ); + bool createLockFile( bool silent ); + IpodMediaItem *addTrackToView( Itdb_Track *track, IpodMediaItem *item = 0, + bool checkIntegrity = false, bool batchmode = false ); + void addPlaylistToView( Itdb_Playlist *playlist ); + void playlistFromItem( IpodMediaItem *item ); + + QString itunesDir( const QString &path = QString::null ) const; + QString realPath( const char *ipodPath ); + QString ipodPath( const QString &realPath ); + bool pathExists( const QString &ipodPath, QString *realPath=0 ); + + // ipod database + Itdb_iTunesDB *m_itdb; + Itdb_Playlist *m_masterPlaylist; + QDict m_files; + + // podcasts + Itdb_Playlist* m_podcastPlaylist; + + bool m_isShuffle; + bool m_isMobile; + bool m_isIPhone; + bool m_supportsArtwork; + bool m_supportsVideo; + bool m_rockboxFirmware; + bool m_needsFirewireGuid; + bool m_autoConnect; + + IpodMediaItem *getArtist( const QString &artist ); + IpodMediaItem *getAlbum( const QString &artist, const QString &album ); + IpodMediaItem *getTrack( const QString &artist, const QString &album, + const QString &title, + int discNumber = -1, int trackNumber = -1, + const PodcastEpisodeBundle *peb = 0 ); + IpodMediaItem *getTrack( const Itdb_Track *itrack ); + + bool removeDBTrack( Itdb_Track *track ); + + bool m_dbChanged; + + QCheckBox *m_syncStatsCheck; + QCheckBox *m_autoDeletePodcastsCheck; + QFile *m_lockFile; + QMutex m_mutex; + + KAction *m_customAction; + enum { CHECK_INTEGRITY, UPDATE_ARTWORK, SET_IPOD_MODEL }; + + private slots: + void slotIpodAction( int ); +}; + +#endif diff --git a/amarok/src/mediadevice/mtp/Makefile.am b/amarok/src/mediadevice/mtp/Makefile.am new file mode 100644 index 00000000..587e426b --- /dev/null +++ b/amarok/src/mediadevice/mtp/Makefile.am @@ -0,0 +1,30 @@ +kde_module_LTLIBRARIES = libamarok_mtp-mediadevice.la +kde_services_DATA = amarok_mtp-mediadevice.desktop + +INCLUDES = \ + -I$(top_srcdir)/amarok/src \ + -I$(top_builddir)/amarok/src \ + -I$(top_srcdir)/amarok/src/amarokcore \ + -I$(top_builddir)/amarok/src/amarokcore \ + -I$(top_srcdir)/amarok/src/engine \ + -I$(top_builddir)/amarok/src/engine \ + -I$(top_srcdir)/amarok/src/mediadevice \ + $(TAGLIB_CFLAGS) \ + $(all_includes) + +METASOURCES = AUTO + +libamarok_mtp_mediadevice_la_LIBADD = \ + $(top_builddir)/amarok/src/libamarok.la \ + $(LIBMTP_LIBS) $(LIB_QT) $(LIB_KDECORE) $(LIB_KDEUI) + +libamarok_mtp_mediadevice_la_LDFLAGS = \ + $(KDE_PLUGIN) \ + $(all_libraries) + +libamarok_mtp_mediadevice_la_SOURCES = \ + mtpmediadevice.cpp + +noinst_HEADERS = \ + mtpmediadevice.h + diff --git a/amarok/src/mediadevice/mtp/amarok_mtp-mediadevice.desktop b/amarok/src/mediadevice/mtp/amarok_mtp-mediadevice.desktop new file mode 100644 index 00000000..c27f7e5f --- /dev/null +++ b/amarok/src/mediadevice/mtp/amarok_mtp-mediadevice.desktop @@ -0,0 +1,110 @@ +[Desktop Entry] +Encoding=UTF-8 +Type=Service +Name=MTP Media Device +Name[af]=MTP media toestel +Name[bg]=Устройство MTP +Name[bn]=এমটিপি মিডিয়া ডিভাইস +Name[ca]=Dispositiu de suports MTP +Name[cs]=Mediální zařízení MTP +Name[da]=MTP-medieenhed +Name[de]=MTP Media-Player +Name[el]=Συσκευή πολυμέσων MTP +Name[eo]=MTP Media Ekipaĵo +Name[es]=Dispositivo MTP +Name[et]=MTP meediaseade +Name[fa]=دستگاه رسانۀ MTP +Name[fi]=MTP-medialaite +Name[fr]=Périphérique de média MTP +Name[ga]=Gléas Meán MTP +Name[gl]=Dispositivo de Médios MTP +Name[hu]=MTP médiaeszköz +Name[is]=MTP tæki +Name[it]=Dispositivo multimediale MTP +Name[ja]=MTP メディアデバイス +Name[ka]=MTP მედია მოწყობილობა +Name[km]=ឧបករណ៍​មេឌៀ MTP +Name[lt]=MTP daugialypės terpės įrenginys +Name[mk]=Уред за мултимедија MTP +Name[ms]=Peranti Media MTP +Name[nb]=MTP medieenhet +Name[nds]=MTP-Medienreedschap +Name[ne]=MTP मिडिया यन्त्र +Name[nl]=MTP media-apparaat +Name[nn]=MTP-medieeining +Name[pa]=MTP ਮੀਡਿਆ ਜੰਤਰ +Name[pl]=Urządzenie mediów MTP +Name[pt]=Dispositivo Multimédia MTP +Name[pt_BR]=Dispositivo de Mídia MTP +Name[se]=MTP-mediaovttadat +Name[sk]=MTP Media zariadenie +Name[sr]=MTP, мултимедијални уређај +Name[sr@Latn]=MTP, multimedijalni uređaj +Name[sv]=MTP-mediaenhet +Name[th]=อุปกรณ์เล่นสื่อ MTP +Name[tr]=MTP Ortam Aygıtı +Name[uk]=Пристрій носія MTP +Name[uz]=MTP media-uskunasi +Name[uz@cyrillic]=MTP медиа-ускунаси +Name[wa]=Édjin d' media MTP +Name[zh_CN]=MTP 媒体设备 +Name[zh_TW]=MTP 媒體裝置 +X-KDE-Library=libamarok_mtp-mediadevice +Comment=Plugin for Amarok +Comment[af]=Inprop module vir Amarok +Comment[ar]= قابس ( برنامج مضاف الى) AmaroK +Comment[bg]=Приставка за Amarok +Comment[bn]=আমারক-এর জন্য প্লাগিন +Comment[br]=Lugent evit Amarok +Comment[ca]=Connector per l'Amarok +Comment[cs]=Modul pro AmaroK +Comment[de]=Modul für Amarok +Comment[el]=Πρόσθετο για το AmaroK +Comment[eo]=Kromaĵo por Amarok +Comment[es]=Extensión para Amarok +Comment[et]=Amaroki plugin +Comment[fa]=وصله برای amaroK +Comment[fi]=Amarok-liitännäinen +Comment[fr]=Module pour Amarok +Comment[ga]=Breiseán AmaroK +Comment[gl]=Extensión para Amarok +Comment[hu]=Bővítőmodul az Amarokhoz +Comment[is]=Íforrit fyrir Amarok +Comment[it]=Plugin per Amarok +Comment[ja]=Amarok のためのプラグイン +Comment[ka]=მოდული Amarok-ისთვის +Comment[km]=កម្មវិធី​ជំនួយ​សម្រាប់ Amarok +Comment[lt]=Amarok įskiepis +Comment[mk]=Приклучок за Амарок +Comment[nb]=Programtillegg for Amarok +Comment[nds]=Moduul för Amarok +Comment[ne]=अमारोकका लागि प्लगइन +Comment[nl]=Plugin voor Amarok +Comment[nn]=Programtillegg for Amarok +Comment[pa]=ਅਮਰੋਕ ਲਈ ਪਲੱਗਇਨ +Comment[pl]=Wtyczka Amaroka +Comment[pt]='Plugin' para o Amarok +Comment[pt_BR]=Plugin para o Amarok +Comment[ru]=Модуль amaroK +Comment[se]=Lassemoduvla Amarok:ii +Comment[sk]=Amarok modul +Comment[sr]=Прикључак за Amarok +Comment[sr@Latn]=Priključak za Amarok +Comment[sv]=Insticksprogram för Amarok +Comment[th]=โปรแกรมเสริมสำหรับ Amarok +Comment[tr]=Amarok için Eklenti +Comment[uk]=Втулок для Amarok +Comment[uz]=Amarok uchun plagin +Comment[uz@cyrillic]=Amarok учун плагин +Comment[wa]=Tchôke-divins po Amarok +Comment[zh_CN]=Amarok 插件 +Comment[zh_TW]=amaroK 插件 +ServiceTypes=Amarok/Plugin + +X-KDE-Amarok-plugintype=mediadevice +X-KDE-Amarok-name=mtp-mediadevice +X-KDE-Amarok-authors=Andy Kelk +X-KDE-Amarok-email=andy@mopoke.co.uk +X-KDE-Amarok-rank=100 +X-KDE-Amarok-version=1 +X-KDE-Amarok-framework-version=32 diff --git a/amarok/src/mediadevice/mtp/mtpmediadevice.cpp b/amarok/src/mediadevice/mtp/mtpmediadevice.cpp new file mode 100644 index 00000000..ca015273 --- /dev/null +++ b/amarok/src/mediadevice/mtp/mtpmediadevice.cpp @@ -0,0 +1,1669 @@ +/*************************************************************************** + * copyright : (C) 2006 Andy Kelk * + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + + /** + * Based on njb mediadevice with some code hints from the libmtp + * example tools + */ + + /** + * MTP media device + * @author Andy Kelk + * @see http://libmtp.sourceforge.net/ + */ + +#define DEBUG_PREFIX "MtpMediaDevice" + +#include +#include "mtpmediadevice.h" + +AMAROK_EXPORT_PLUGIN( MtpMediaDevice ) + +// Amarok +#include +#include +#include +#include +#include + +// KDE +#include +#include +#include +#include +#include +#include + +// Qt +#include +#include +#include +#include +#include +#include +#include + +/** + * MtpMediaDevice Class + */ + +MtpMediaDevice::MtpMediaDevice() : MediaDevice() +{ + m_name = i18n("MTP Media Device"); + m_device = 0; + m_folders = 0; + m_playlistItem = 0; + setDisconnected(); + m_hasMountPoint = false; + m_syncStats = false; + m_transcode = false; + m_transcodeAlways = false; + m_transcodeRemove = false; + m_configure = false; + m_customButton = true; + m_transfer = true; + + KToolBarButton *customButton = MediaBrowser::instance()->getToolBar()->getButton( MediaBrowser::CUSTOM ); + customButton->setText( i18n("Special device functions") ); + QToolTip::remove( customButton ); + QToolTip::add( customButton, i18n( "Special functions of your device" ) ); + + mtpFileTypes[LIBMTP_FILETYPE_WAV] = "wav"; + mtpFileTypes[LIBMTP_FILETYPE_MP3] = "mp3"; + mtpFileTypes[LIBMTP_FILETYPE_WMA] = "wma"; + mtpFileTypes[LIBMTP_FILETYPE_OGG] = "ogg"; + mtpFileTypes[LIBMTP_FILETYPE_AUDIBLE] = "aa"; // audible + mtpFileTypes[LIBMTP_FILETYPE_MP4] = "mp4"; + mtpFileTypes[LIBMTP_FILETYPE_UNDEF_AUDIO] = "undef-audio"; + mtpFileTypes[LIBMTP_FILETYPE_WMV] = "wmv"; + mtpFileTypes[LIBMTP_FILETYPE_AVI] = "avi"; + mtpFileTypes[LIBMTP_FILETYPE_MPEG] = "mpg"; + mtpFileTypes[LIBMTP_FILETYPE_ASF] = "asf"; + mtpFileTypes[LIBMTP_FILETYPE_QT] = "mov"; + mtpFileTypes[LIBMTP_FILETYPE_UNDEF_VIDEO] = "undef-video"; + mtpFileTypes[LIBMTP_FILETYPE_JPEG] = "jpg"; + mtpFileTypes[LIBMTP_FILETYPE_JFIF] = "jpg"; + mtpFileTypes[LIBMTP_FILETYPE_TIFF] = "tiff"; + mtpFileTypes[LIBMTP_FILETYPE_BMP] = "bmp"; + mtpFileTypes[LIBMTP_FILETYPE_GIF] = "gif"; + mtpFileTypes[LIBMTP_FILETYPE_PICT] = "pict"; + mtpFileTypes[LIBMTP_FILETYPE_PNG] = "png"; + mtpFileTypes[LIBMTP_FILETYPE_VCALENDAR1] = "vcs"; // vcal1 + mtpFileTypes[LIBMTP_FILETYPE_VCALENDAR2] = "vcs"; // vcal2 + mtpFileTypes[LIBMTP_FILETYPE_VCARD2] = "vcf"; // vcard2 + mtpFileTypes[LIBMTP_FILETYPE_VCARD3] = "vcf"; // vcard3 + mtpFileTypes[LIBMTP_FILETYPE_WINDOWSIMAGEFORMAT] = "wim"; // windows image format + mtpFileTypes[LIBMTP_FILETYPE_WINEXEC] = "exe"; + mtpFileTypes[LIBMTP_FILETYPE_TEXT] = "txt"; + mtpFileTypes[LIBMTP_FILETYPE_HTML] = "html"; + mtpFileTypes[LIBMTP_FILETYPE_UNKNOWN] = "unknown"; + + m_newTracks = new QPtrList; +} + +void +MtpMediaDevice::init( MediaBrowser *parent ) +{ + MediaDevice::init( parent ); +} + +bool +MtpMediaDevice::isConnected() +{ + return !( m_device == 0 ); +} + +/** + * File types that we support + */ +QStringList +MtpMediaDevice::supportedFiletypes() +{ + return m_supportedFiles; +} + + +int +MtpMediaDevice::progressCallback( uint64_t const sent, uint64_t const total, void const * const data ) +{ + Q_UNUSED( sent ); + Q_UNUSED( total ); + + kapp->processEvents( 100 ); + + MtpMediaDevice *dev = (MtpMediaDevice*)(data); + + if( dev->isCanceled() ) + { + debug() << "Canceling transfer operation" << endl; + dev->setCanceled( true ); + return 1; + } + + return 0; +} + +/** + * Copy a track to the device + */ +MediaItem +*MtpMediaDevice::copyTrackToDevice( const MetaBundle &bundle ) +{ + DEBUG_BLOCK + + QString genericError = i18n( "Could not send track" ); + + LIBMTP_track_t *trackmeta = LIBMTP_new_track_t(); + trackmeta->item_id = 0; + debug() << "filetype : " << bundle.fileType() << endl; + if( bundle.fileType() == MetaBundle::mp3 ) + { + trackmeta->filetype = LIBMTP_FILETYPE_MP3; + } + else if( bundle.fileType() == MetaBundle::ogg ) + { + trackmeta->filetype = LIBMTP_FILETYPE_OGG; + } + else if( bundle.fileType() == MetaBundle::wma ) + { + trackmeta->filetype = LIBMTP_FILETYPE_WMA; + } + else if( bundle.fileType() == MetaBundle::mp4 ) + { + trackmeta->filetype = LIBMTP_FILETYPE_MP4; + } + else + { + // Couldn't recognise an Amarok filetype. + // fallback to checking the extension (e.g. .wma, .ogg, etc) + debug() << "No filetype found by Amarok filetype" << endl; + + const QString extension = bundle.url().path().section( ".", -1 ).lower(); + + int libmtp_type = m_supportedFiles.findIndex( extension ); + if( libmtp_type >= 0 ) + { + int keyIndex = mtpFileTypes.values().findIndex( extension ); + libmtp_type = mtpFileTypes.keys()[keyIndex]; + trackmeta->filetype = (LIBMTP_filetype_t) libmtp_type; + debug() << "set filetype to " << libmtp_type << " based on extension of ." << extension << endl; + } + else + { + debug() << "We don't support the extension ." << extension << endl; + Amarok::StatusBar::instance()->shortLongMessage( + genericError, + i18n( "Cannot determine a valid file type" ), + KDE::StatusBar::Error + ); + return 0; + } + } + + if( bundle.title().isEmpty() ) + { + trackmeta->title = qstrdup( i18n( "Unknown title" ).utf8() ); + } + else + { + trackmeta->title = qstrdup( bundle.title().utf8() ); + } + + if( bundle.album().isEmpty() ) + { + trackmeta->album = qstrdup( i18n( "Unknown album" ).utf8() ); + } + else + { + trackmeta->album = qstrdup( bundle.album().string().utf8() ); + } + + if( bundle.artist().isEmpty() ) + { + trackmeta->artist = qstrdup( i18n( "Unknown artist" ).utf8() ); + } + else + { + trackmeta->artist = qstrdup( bundle.artist().string().utf8() ); + } + + if( bundle.genre().isEmpty() ) + { + trackmeta->genre = qstrdup( i18n( "Unknown genre" ).utf8() ); + } + else + { + trackmeta->genre = qstrdup( bundle.genre().string().utf8() ); + } + + if( bundle.year() > 0 ) + { + QString date; + QTextOStream( &date ) << bundle.year() << "0101T0000.0"; + trackmeta->date = qstrdup( date.utf8() ); + } + else + { + trackmeta->date = qstrdup( "00010101T0000.0" ); + } + + if( bundle.track() > 0 ) + { + trackmeta->tracknumber = bundle.track(); + } + if( bundle.length() > 0 ) + { + // Multiply by 1000 since this is in milliseconds + trackmeta->duration = bundle.length() * 1000; + } + if( !bundle.filename().isEmpty() ) + { + trackmeta->filename = qstrdup( bundle.filename().utf8() ); + } + trackmeta->filesize = bundle.filesize(); + + // try and create the requested folder structure + uint32_t parent_id = 0; + if( !m_folderStructure.isEmpty() ) + { + parent_id = checkFolderStructure( bundle ); + if( parent_id == 0 ) + { + debug() << "Couldn't create new parent (" << m_folderStructure << ")" << endl; + Amarok::StatusBar::instance()->shortLongMessage( + genericError, + i18n( "Cannot create parent folder. Check your structure." ), + KDE::StatusBar::Error + ); + return 0; + } + } + else + { + parent_id = getDefaultParentId(); + } + debug() << "Parent id : " << parent_id << endl; + trackmeta->parent_id = parent_id; + + m_critical_mutex.lock(); + debug() << "Sending track... " << bundle.url().path().utf8() << endl; + int ret = LIBMTP_Send_Track_From_File( + m_device, bundle.url().path().utf8(), trackmeta, + progressCallback, this + ); + m_critical_mutex.unlock(); + + if( ret < 0 ) + { + debug() << "Could not write file " << ret << endl; + Amarok::StatusBar::instance()->shortLongMessage( + genericError, + i18n( "File write failed" ), + KDE::StatusBar::Error + ); + return 0; + } + + MetaBundle temp( bundle ); + MtpTrack *taggedTrack = new MtpTrack( trackmeta ); + taggedTrack->setBundle( temp ); + taggedTrack->setFolderId( parent_id ); + + LIBMTP_destroy_track_t( trackmeta ); + + kapp->processEvents( 100 ); + + // add track to view and to new tracks list + MediaItem *newItem = addTrackToView( taggedTrack ); + m_newTracks->append( newItem ); + return newItem; +} + +/** + * Get the cover image for a track, convert it to a format supported on the + * device and set it as the cover art. + */ +void +MtpMediaDevice::sendAlbumArt( QPtrList *items ) +{ + QString image; + image = CollectionDB::instance()->albumImage(items->first()->bundle()->artist(), items->first()->bundle()->album(), false, 100); + if( ! image.endsWith( "@nocover.png" ) ) + { + debug() << "image " << image << " found for " << items->first()->bundle()->album() << endl; + QByteArray *imagedata = getSupportedImage( image ); + if( imagedata == 0 ) + { + debug() << "Cannot generate a supported image format" << endl; + return; + } + if( imagedata->size() ) + { + m_critical_mutex.lock(); + LIBMTP_album_t *album_object = getOrCreateAlbum( items ); + if( album_object ) + { + LIBMTP_filesampledata_t *imagefile = LIBMTP_new_filesampledata_t(); + imagefile->data = (char *) imagedata->data(); + imagefile->size = imagedata->size(); + imagefile->filetype = LIBMTP_FILETYPE_JPEG; + int ret = LIBMTP_Send_Representative_Sample( m_device, album_object->album_id, imagefile ); + if( ret != 0 ) + { + debug() << "image send failed : " << ret << endl; + } + } + m_critical_mutex.unlock(); + } + } +} + +uint32_t +MtpMediaDevice::getDefaultParentId( void ) +{ + // Decide which folder to send it to: + // If the device gave us a parent_folder setting, we use it + uint32_t parent_id = 0; + if( m_default_parent_folder ) + { + parent_id = m_default_parent_folder; + } + // Otherwise look for a folder called "Music" + else if( m_folders != 0 ) + { + parent_id = folderNameToID( "Music", m_folders ); + if( !parent_id ) + { + debug() << "Parent folder could not be found. Going to use top level." << endl; + } + } + // Give up and don't set a parent folder, let the device deal with it + else + { + debug() << "No folders found. Going to use top level." << endl; + } + return parent_id; +} + +/** + * Takes path to an existing cover image and converts it to a format + * supported on the device + */ +QByteArray +*MtpMediaDevice::getSupportedImage( QString path ) +{ + if( m_format == 0 ) + return 0; + + debug() << "Will convert image to " << m_format << endl; + + // open image + const QImage original( path ); + + // save as new image + QImage newformat( original ); + QByteArray *newimage = new QByteArray(); + QBuffer buffer( *newimage ); + buffer.open( IO_WriteOnly ); + if( newformat.save( &buffer, m_format.ascii() ) ) + { + buffer.close(); + return newimage; + } + return 0; +} + +/** + * Update cover art for a number of tracks + */ +void +MtpMediaDevice::updateAlbumArt( QPtrList *items ) +{ + DEBUG_BLOCK + + if( m_format == 0 ) // no supported image types. Don't even bother. + return; + + setCanceled( false ); + + kapp->processEvents( 100 ); + QMap< QString, QPtrList > albumList; + + for( MtpMediaItem *it = dynamic_cast(items->first()); it && !(m_canceled); it = dynamic_cast(items->next()) ) + { + // build album list + if( it->type() == MediaItem::TRACK ) + { + albumList[ it->bundle()->album() ].append( it ); + } + if( it->type() == MediaItem::ALBUM ) + { + debug() << "look, we get albums too!" << endl; + } + } + int i = 0; + setProgress( i, albumList.count() ); + kapp->processEvents( 100 ); + QMap< QString, QPtrList >::Iterator it; + for( it = albumList.begin(); it != albumList.end(); ++it ) + { + sendAlbumArt( &it.data() ); + setProgress( ++i ); + if( i % 20 == 0 ) + kapp->processEvents( 100 ); + } + hideProgress(); +} + +/** + * Retrieve existing or create new album object. + */ +LIBMTP_album_t +*MtpMediaDevice::getOrCreateAlbum( QPtrList *items )//uint32_t track_id, const MetaBundle &bundle ) +{ + LIBMTP_album_t *album_object = 0; + uint32_t albumid = 0; + int ret; + QMap::Iterator it; + for( it = m_idToAlbum.begin(); it != m_idToAlbum.end(); ++it ) + { + if( it.data()->album() == items->first()->bundle()->album() ) + { + albumid = it.data()->id(); + break; + } + } + if( albumid ) + { + debug() << "reusing existing album " << albumid << endl; + album_object = LIBMTP_Get_Album( m_device, albumid ); + if( album_object == 0 ) + { + debug() << "retrieving album failed." << endl; + return 0; + } + uint32_t i; + uint32_t trackCount = album_object->no_tracks; + for( MtpMediaItem *it = dynamic_cast(items->first()); it; it = dynamic_cast(items->next()) ) + { + bool exists = false; + for( i = 0; i < album_object->no_tracks; i++ ) + { + if( album_object->tracks[i] == it->track()->id() ) + { + exists = true; + break; + } + } + if( ! exists ) + { + debug() << "adding track " << it->track()->id() << " to existing album " << albumid << endl; + album_object->no_tracks++; + album_object->tracks = (uint32_t *)realloc( album_object->tracks, album_object->no_tracks * sizeof( uint32_t ) ); + album_object->tracks[ ( album_object->no_tracks - 1 ) ] = it->track()->id(); + } + } + if( trackCount != album_object->no_tracks ) // album needs an update + { + ret = LIBMTP_Update_Album( m_device, album_object ); + if( ret != 0 ) + debug() << "updating album failed : " << ret << endl; + } + } + else + { + debug() << "creating new album " << endl; + album_object = LIBMTP_new_album_t(); + album_object->name = qstrdup( items->first()->bundle()->album().string().utf8() ); + album_object->tracks = (uint32_t *) malloc(items->count() * sizeof(uint32_t)); + int i = 0; + for( MtpMediaItem *it = dynamic_cast(items->first()); it; it = dynamic_cast(items->next()) ) + album_object->tracks[i++] = it->track()->id(); + album_object->no_tracks = items->count(); + ret = LIBMTP_Create_New_Album( m_device, album_object ); + if( ret != 0 ) + { + debug() << "creating album failed : " << ret << endl; + return 0; + } + m_idToAlbum[ album_object->album_id ] = new MtpAlbum( album_object ); + } + return album_object; +} + +/** + * Check (and optionally create) the folder structure to put a + * track into. Return the (possibly new) parent folder ID + */ +uint32_t +MtpMediaDevice::checkFolderStructure( const MetaBundle &bundle, bool create ) +{ + QString artist = bundle.artist(); + if( artist.isEmpty() ) + artist = i18n( "Unknown Artist" ); + if( bundle.compilation() == MetaBundle::CompilationYes ) + artist = i18n( "Various Artists" ); + QString album = bundle.album(); + if( album.isEmpty() ) + album = i18n( "Unknown Album" ); + QString genre = bundle.genre(); + if( genre.isEmpty() ) + genre = i18n( "Unknown Genre" ); + m_critical_mutex.lock(); + uint32_t parent_id = getDefaultParentId(); + QStringList folders = QStringList::split( "/", m_folderStructure ); // use slash as a dir separator + QString completePath; + for( QStringList::Iterator it = folders.begin(); it != folders.end(); ++it ) + { + if( (*it).isEmpty() ) + continue; + // substitute %a , %b , %g + (*it).replace( QRegExp( "%a" ), artist ) + .replace( QRegExp( "%b" ), album ) + .replace( QRegExp( "%g" ), genre ); + // check if it exists + uint32_t check_folder = subfolderNameToID( (*it).utf8(), m_folders, parent_id ); + // create if not exists (if requested) + if( check_folder == 0 ) + { + if( create ) + { + check_folder = createFolder( (*it).utf8() , parent_id ); + if( check_folder == 0 ) + { + m_critical_mutex.unlock(); + return 0; + } + } + else + { + m_critical_mutex.unlock(); + return 0; + } + } + completePath += (*it).utf8() + '/'; + // set new parent + parent_id = check_folder; + } + m_critical_mutex.unlock(); + debug() << "Folder path : " << completePath << endl; + // return parent + return parent_id; +} + +/** + * Create a new mtp folder + */ +uint32_t +MtpMediaDevice::createFolder( const char *name, uint32_t parent_id ) +{ + debug() << "Creating new folder '" << name << "' as a child of "<< parent_id << endl; + char *name_copy = qstrdup( name ); + uint32_t new_folder_id = LIBMTP_Create_Folder( m_device, name_copy, parent_id, 0 ); + delete(name_copy); + debug() << "New folder ID: " << new_folder_id << endl; + if( new_folder_id == 0 ) + { + debug() << "Attempt to create folder '" << name << "' failed." << endl; + return 0; + } + updateFolders(); + + return new_folder_id; +} + +/** + * Recursively search the folder list for a matching one under the specified + * parent ID and return the child's ID + */ +uint32_t +MtpMediaDevice::subfolderNameToID( const char *name, LIBMTP_folder_t *folderlist, uint32_t parent_id ) +{ + uint32_t i; + + if( folderlist == 0 ) + return 0; + + if( !strcasecmp( name, folderlist->name ) && folderlist->parent_id == parent_id ) + return folderlist->folder_id; + + if( ( i = ( subfolderNameToID( name, folderlist->child, parent_id ) ) ) ) + return i; + if( ( i = ( subfolderNameToID( name, folderlist->sibling, parent_id ) ) ) ) + return i; + + return 0; +} + +/** + * Recursively search the folder list for a matching one + * and return its ID + */ +uint32_t +MtpMediaDevice::folderNameToID( char *name, LIBMTP_folder_t *folderlist ) +{ + uint32_t i; + + if( folderlist == 0 ) + return 0; + + if( !strcasecmp( name, folderlist->name ) ) + return folderlist->folder_id; + + if( ( i = ( folderNameToID( name, folderlist->child ) ) ) ) + return i; + if( ( i = ( folderNameToID( name, folderlist->sibling ) ) ) ) + return i; + + return 0; +} + +/** + * Get a list of selected items, download them to a temporary location and + * organize. + */ +int +MtpMediaDevice::downloadSelectedItemsToCollection() +{ + QPtrList items; + m_view->getSelectedLeaves( 0, &items ); + + KTempDir tempdir( QString::null ); + tempdir.setAutoDelete( true ); + KURL::List urls; + QString genericError = i18n( "Could not copy track from device." ); + + int total,progress; + total = items.count(); + progress = 0; + + if( total == 0 ) + return 0; + + setProgress( progress, total ); + for( MtpMediaItem *it = dynamic_cast(items.first()); it && !(m_canceled); it = dynamic_cast(items.next()) ) + { + if( it->type() == MediaItem::TRACK ) + { + QString filename = tempdir.name() + it->bundle()->filename(); + int ret = LIBMTP_Get_Track_To_File( + m_device, it->track()->id(), filename.utf8(), + progressCallback, this + ); + if( ret != 0 ) + { + debug() << "Get Track failed: " << ret << endl; + Amarok::StatusBar::instance()->shortLongMessage( + genericError, + i18n( "Could not copy track from device." ), + KDE::StatusBar::Error + ); + } + else + { + urls << filename; + progress++; + setProgress( progress ); + } + } + else + { + total--; + setProgress( progress, total ); + } + } + hideProgress(); + CollectionView::instance()->organizeFiles( urls, i18n( "Move Files To Collection" ), false ); + return 0; +} + +/** + * Write any pending changes to the device, such as database changes + */ +void +MtpMediaDevice::synchronizeDevice() +{ + updateAlbumArt( m_newTracks ); + m_newTracks->clear(); + return; +} + +/** + * Find an existing track + */ +MediaItem +*MtpMediaDevice::trackExists( const MetaBundle &bundle ) +{ + MediaItem *artist = dynamic_cast( m_view->findItem( bundle.artist(), 0 ) ); + if( artist ) + { + MediaItem *album = dynamic_cast( artist->findItem( bundle.album() ) ); + if( album ) + { + MediaItem *track = dynamic_cast( album->findItem( bundle.title() ) ); + if( track ) + return track; + } + } + uint32_t folderId = checkFolderStructure( bundle, false ); + MediaItem *file = m_fileNameToItem[ QString( "%1/%2" ).arg( folderId ).arg( bundle.filename() ) ]; + if( file != 0 ) + return file; + return 0; +} + +/** + * Create a new playlist + */ +MtpMediaItem +*MtpMediaDevice::newPlaylist( const QString &name, MediaItem *parent, QPtrList items ) +{ + DEBUG_BLOCK + MtpMediaItem *item = new MtpMediaItem( parent, this ); + item->setType( MediaItem::PLAYLIST ); + item->setText( 0, name ); + item->setPlaylist( new MtpPlaylist() ); + + addToPlaylist( item, 0, items ); + + if( ! isTransferring() ) + m_view->rename( item, 0 ); + + return item; +} + +/** + * Add an item to a playlist + */ +void +MtpMediaDevice::addToPlaylist( MediaItem *mlist, MediaItem *after, QPtrList items ) +{ + DEBUG_BLOCK + MtpMediaItem *list = dynamic_cast( mlist ); + if( !list ) + return; + + int order; + MtpMediaItem *it; + if( after ) + { + order = after->m_order + 1; + it = dynamic_cast(after->nextSibling()); + } + else + { + order = 0; + it = dynamic_cast( list->firstChild() ); + } + + for( ; it; it = dynamic_cast( it->nextSibling() ) ) + { + it->m_order += items.count(); + } + + for( MtpMediaItem *it = dynamic_cast(items.first() ); + it; + it = dynamic_cast( items.next() ) ) + { + if( !it->track() ) + continue; + + MtpMediaItem *add; + if( it->parent() == list ) + { + add = it; + if( after ) + { + it->moveItem(after); + } + else + { + list->takeItem(it); + list->insertItem(it); + } + } + else + { + if( after ) + { + add = new MtpMediaItem( list, after ); + } + else + { + add = new MtpMediaItem( list, this ); + } + } + after = add; + + add->setType( MediaItem::PLAYLISTITEM ); + add->setTrack( it->track() ); + add->setBundle( new MetaBundle( *(it->bundle()) ) ); + add->m_device = this; + add->setText( 0, it->bundle()->artist() + " - " + it->bundle()->title() ); + add->m_order = order; + order++; + } + + // make numbering consecutive + int i = 0; + for( MtpMediaItem *it = dynamic_cast( list->firstChild() ); + it; + it = dynamic_cast( it->nextSibling() ) ) + { + it->m_order = i; + i++; + } + + playlistFromItem( list ); +} + +/** + * When a playlist has been renamed, we must save it + */ +void +MtpMediaDevice::playlistRenamed( QListViewItem *item, const QString &, int ) // SLOT +{ + DEBUG_BLOCK + MtpMediaItem *playlist = static_cast( item ); + if( playlist->type() == MediaItem::PLAYLIST ) + playlistFromItem( playlist ); +} + +/** + * Save a playlist + */ +void +MtpMediaDevice::playlistFromItem( MtpMediaItem *item ) +{ + if( item->childCount() == 0 ) + return; + m_critical_mutex.lock(); + LIBMTP_playlist_t *metadata = LIBMTP_new_playlist_t(); + metadata->name = qstrdup( item->text( 0 ).utf8() ); + const int trackCount = item->childCount(); + if (trackCount > 0) { + uint32_t *tracks = ( uint32_t* )malloc( sizeof( uint32_t ) * trackCount ); + uint32_t i = 0; + for( MtpMediaItem *it = dynamic_cast(item->firstChild()); + it; + it = dynamic_cast(it->nextSibling()) ) + { + tracks[i] = it->track()->id(); + i++; + } + metadata->tracks = tracks; + metadata->no_tracks = i; + } else { + debug() << "no tracks available for playlist " << metadata->name + << endl; + metadata->no_tracks = 0; + } + + QString genericError = i18n( "Could not save playlist." ); + + if( item->playlist()->id() == 0 ) + { + debug() << "creating new playlist : " << metadata->name << endl; + int ret = LIBMTP_Create_New_Playlist( m_device, metadata ); + if( ret == 0 ) + { + item->playlist()->setId( metadata->playlist_id ); + debug() << "playlist saved : " << metadata->playlist_id << endl; + } + else + { + Amarok::StatusBar::instance()->shortLongMessage( + genericError, + i18n( "Could not create new playlist on device." ), + KDE::StatusBar::Error + ); + } + } + else + { + metadata->playlist_id = item->playlist()->id(); + debug() << "updating playlist : " << metadata->name << endl; + int ret = LIBMTP_Update_Playlist( m_device, metadata ); + if( ret != 0 ) + { + Amarok::StatusBar::instance()->shortLongMessage( + genericError, + i18n( "Could not update playlist on device." ), + KDE::StatusBar::Error + ); + } + } + m_critical_mutex.unlock(); +} + +/** + * Recursively remove MediaItem from the device and media view + */ +int +MtpMediaDevice::deleteItemFromDevice(MediaItem* item, int flags ) +{ + + int result = 0; + if( isCanceled() || !item ) + { + return -1; + } + MediaItem *next = 0; + + switch( item->type() ) + { + case MediaItem::PLAYLIST: + case MediaItem::TRACK: + if( isCanceled() ) + break; + if( item ) + { + int res = deleteObject( dynamic_cast ( item ) ); + if( res >=0 && result >= 0 ) + result += res; + else + result = -1; + } + break; + case MediaItem::PLAYLISTITEM: + if( isCanceled() ) + break; + if( item ) + { + MtpMediaItem *parent = dynamic_cast ( item->parent() ); + if( parent && parent->type() == MediaItem::PLAYLIST ) { + delete( item ); + playlistFromItem( parent ); + } + } + break; + case MediaItem::ALBUM: + case MediaItem::ARTIST: + // Recurse through the lists + next = 0; + + if( isCanceled() ) + break; + + for( MediaItem *it = dynamic_cast( item->firstChild() ); it ; it = next ) + { + next = dynamic_cast( it->nextSibling() ); + int res = deleteItemFromDevice( it, flags ); + if( res >= 0 && result >= 0 ) + result += res; + else + result = -1; + + } + if( item ) + delete dynamic_cast( item ); + break; + default: + result = 0; + } + return result; +} + +/** + * Actually delete a track or playlist + */ +int +MtpMediaDevice::deleteObject( MtpMediaItem *deleteItem ) +{ + DEBUG_BLOCK + + u_int32_t object_id; + if( deleteItem->type() == MediaItem::PLAYLIST ) + object_id = deleteItem->playlist()->id(); + else + object_id = deleteItem->track()->id(); + + QString genericError = i18n( "Could not delete item" ); + + debug() << "delete this id : " << object_id << endl; + + m_critical_mutex.lock(); + int status = LIBMTP_Delete_Object( m_device, object_id ); + m_critical_mutex.unlock(); + + if( status != 0 ) + { + debug() << "delete object failed" << endl; + Amarok::StatusBar::instance()->shortLongMessage( + genericError, + i18n( "Delete failed" ), + KDE::StatusBar::Error + ); + return -1; + } + debug() << "object deleted" << endl; + + // clear cached filename + if( deleteItem->type() == MediaItem::TRACK ) + m_fileNameToItem.remove( QString( "%1/%2" ).arg( deleteItem->track()->folderId() ).arg( deleteItem->bundle()->filename() ) ); + // remove from the media view + delete deleteItem; + kapp->processEvents( 100 ); + + return 1; +} + +/** + * Update local cache of mtp folders + */ +void +MtpMediaDevice::updateFolders( void ) +{ + LIBMTP_destroy_folder_t( m_folders ); + m_folders = 0; + m_folders = LIBMTP_Get_Folder_List( m_device ); +} + +/** + * Set cancellation of an operation + */ +void +MtpMediaDevice::cancelTransfer() +{ + m_canceled = true; +} + +/** + * Connect to device, and populate m_view with MediaItems + */ +bool +MtpMediaDevice::openDevice( bool silent ) +{ + DEBUG_BLOCK + + Q_UNUSED( silent ); + + if( m_device != 0 ) + return true; + + + QString genericError = i18n( "Could not connect to MTP Device" ); + + m_critical_mutex.lock(); + LIBMTP_Init(); + m_device = LIBMTP_Get_First_Device(); + m_critical_mutex.unlock(); + if( m_device == 0 ) { + debug() << "No devices." << endl; + Amarok::StatusBar::instance()->shortLongMessage( + genericError, + i18n( "MTP device could not be opened" ), + KDE::StatusBar::Error + ); + setDisconnected(); + return false; + } + + connect( + m_view, SIGNAL( itemRenamed( QListViewItem*, const QString&, int ) ), + this, SLOT( playlistRenamed( QListViewItem*, const QString&, int ) ) + ); + + QString modelname = QString( LIBMTP_Get_Modelname( m_device ) ); + QString ownername = QString( LIBMTP_Get_Friendlyname( m_device ) ); + m_name = modelname; + if(! ownername.isEmpty() ) + m_name += " (" + ownername + ')'; + + m_default_parent_folder = m_device->default_music_folder; + debug() << "setting default parent : " << m_default_parent_folder << endl; + + MtpMediaDevice::readMtpMusic(); + + m_critical_mutex.lock(); + m_folders = LIBMTP_Get_Folder_List( m_device ); + uint16_t *filetypes; + uint16_t filetypes_len; + int ret = LIBMTP_Get_Supported_Filetypes( m_device, &filetypes, &filetypes_len ); + if( ret == 0 ) + { + uint16_t i; + for( i = 0; i < filetypes_len; i++ ) + m_supportedFiles << mtpFileTypes[ filetypes[ i ] ]; + } + // find supported image types (for album art). + if( m_supportedFiles.findIndex( "jpg" ) ) + m_format = "JPEG"; + else if( m_supportedFiles.findIndex( "png" ) ) + m_format = "PNG"; + else if( m_supportedFiles.findIndex( "gif" ) ) + m_format = "GIF"; + free( filetypes ); + m_critical_mutex.unlock(); + + return true; +} + +/** + * Start the view (add default folders such as for playlists) + */ +void +MtpMediaDevice::initView() +{ + if( ! isConnected() ) + return; + m_playlistItem = new MtpMediaItem( m_view, this ); + m_playlistItem->setText( 0, i18n("Playlists") ); + m_playlistItem->setType( MediaItem::PLAYLISTSROOT ); + m_playlistItem->m_order = -1; +} + +/** + * Wrap up any loose ends and close the device + */ +bool +MtpMediaDevice::closeDevice() +{ + DEBUG_BLOCK + + // clear folder structure + if( m_folders != 0 ) + { + m_critical_mutex.lock(); + LIBMTP_destroy_folder_t( m_folders ); + m_critical_mutex.unlock(); + m_folders = 0; + debug() << "Folders destroyed" << endl; + } + + // release device + if( m_device != 0 ) + { + m_critical_mutex.lock(); + LIBMTP_Release_Device( m_device ); + m_critical_mutex.unlock(); + setDisconnected(); + debug() << "Device released" << endl; + } + + // clear the cached mappings + m_idToAlbum.clear(); + m_idToTrack.clear(); + m_fileNameToItem.clear(); + + // clean up the view + clearItems(); + + return true; +} + +/** + * Get the capacity and freespace available on the device, in KB + */ +bool +MtpMediaDevice::getCapacity( KIO::filesize_t *total, KIO::filesize_t *available ) +{ + if( !isConnected() ) + return false; + + // TODO : Follow the links so we sum up all the device's storage. + *total = m_device->storage->MaxCapacity; + *available = m_device->storage->FreeSpaceInBytes; + return true; +} + +/** + * Get custom information about the device via MTP + */ +void +MtpMediaDevice::customClicked() +{ + QString Information; + if( isConnected() ) + { + QString batteryLevel; + QString secureTime; + QString supportedFiles; + + uint8_t maxbattlevel; + uint8_t currbattlevel; + char *sectime; + + + m_critical_mutex.lock(); + LIBMTP_Get_Batterylevel( m_device, &maxbattlevel, &currbattlevel ); + LIBMTP_Get_Secure_Time( m_device, §ime ); + m_critical_mutex.unlock(); + + batteryLevel = i18n("Battery level: ") + + QString::number( (int) ( (float) currbattlevel / (float) maxbattlevel * 100.0 ) ) + + '%'; + secureTime = i18n("Secure time: ") + sectime; + supportedFiles = i18n("Supported file types: ") + + m_supportedFiles.join( ", " ); + + Information = ( i18n( "Player Information for " ) + + m_name + '\n' + batteryLevel + + '\n' + secureTime + '\n' + + supportedFiles ); + free(sectime); + } + else + { + Information = i18n( "Player not connected" ); + } + + KMessageBox::information( 0, Information, i18n( "Device information" ) ); +} + +/** + * Current device + */ +LIBMTP_mtpdevice_t +*MtpMediaDevice::current_device() +{ + return m_device; +} + +/** + * We use a 0 device to show a disconnected device. + * This sets the device to that. + */ +void +MtpMediaDevice::setDisconnected() +{ + m_device = 0; +} + +/** + * Handle clicking of the right mouse button + */ +void +MtpMediaDevice::rmbPressed( QListViewItem *qitem, const QPoint &point, int ) +{ + + enum Actions {RENAME, DOWNLOAD, DELETE, MAKE_PLAYLIST, UPDATE_ALBUM_ART}; + + MtpMediaItem *item = static_cast( qitem ); + if( item ) + { + KPopupMenu menu( m_view ); + switch( item->type() ) + { + case MediaItem::ARTIST: + case MediaItem::ALBUM: + case MediaItem::TRACK: + menu.insertItem( SmallIconSet( Amarok::icon( "collection" ) ), i18n("&Copy Files to Collection..."), DOWNLOAD ); + menu.insertItem( SmallIconSet( Amarok::icon( "playlist" ) ), i18n( "Make Media Device Playlist" ), MAKE_PLAYLIST ); + menu.insertItem( SmallIconSet( Amarok::icon( "covermanager" ) ), i18n( "Refresh Cover Images" ), UPDATE_ALBUM_ART ); + break; + case MediaItem::PLAYLIST: + menu.insertItem( SmallIconSet( Amarok::icon( "edit" ) ), i18n( "Rename" ), RENAME ); + break; + default: + break; + } + + menu.insertItem( SmallIconSet( Amarok::icon( "remove" ) ), i18n( "Delete from device" ), DELETE ); + + int id = menu.exec( point ); + switch( id ) + { + case MAKE_PLAYLIST: + { + QPtrList items; + m_view->getSelectedLeaves( 0, &items ); + QString name = i18n( "New Playlist" ); + newPlaylist( name, m_playlistItem, items ); + } + break; + case DELETE: + MediaDevice::deleteFromDevice(); + break; + case RENAME: + if( item->type() == MediaItem::PLAYLIST && ! isTransferring() ) + { + m_view->rename( item, 0 ); + } + break; + case DOWNLOAD: + downloadSelectedItemsToCollection(); + break; + case UPDATE_ALBUM_ART: + { + QPtrList *items = new QPtrList; + m_view->getSelectedLeaves( 0, items ); + + if( items->count() > 100 ) + { + int button = KMessageBox::warningContinueCancel( m_parent, + i18n( "

    You are updating cover art for 1 track. This may take some time.", + "

    You are updating cover art for %n tracks. This may take some time.", + items->count() + ), + QString::null ); + + if( button != KMessageBox::Continue ) + return; + } + updateAlbumArt( items ); + break; + } + } + } + return; +} + +/** + * Add gui elements to the device configuration + */ +void +MtpMediaDevice::addConfigElements( QWidget *parent ) +{ + + m_folderLabel = new QLabel( parent ); + m_folderLabel->setText( i18n( "Folder structure:" ) ); + + m_folderStructureBox = new QLineEdit( parent ); + m_folderStructureBox->setText( m_folderStructure ); + QToolTip::add( m_folderStructureBox, + i18n( "Files copied to the device will be placed in this folder." ) + '\n' + + i18n( "/ is used as folder separator." ) + '\n' + + i18n( "%a will be replaced with the artist name, ") + + i18n( "%b with the album name," ) + '\n' + + i18n( "%g with the genre.") + '\n' + + i18n( "An empty path means the files will be placed unsorted in the default music folder." ) ); +} + +/** + * Remove gui elements from the device configuration + */ +void +MtpMediaDevice::removeConfigElements( QWidget *parent) +{ + Q_UNUSED(parent) + + delete m_folderStructureBox; + m_folderStructureBox = 0; + + delete m_folderLabel; + m_folderLabel = 0; +} + +/** + * Save changed config after dialog commit + */ +void +MtpMediaDevice::applyConfig() +{ + m_folderStructure = m_folderStructureBox->text(); + setConfigString( "FolderStructure", m_folderStructure ); +} + +/** + * Load config from the amarokrc file + */ +void +MtpMediaDevice::loadConfig() +{ + m_folderStructure = configString( "FolderStructure","%a - %b" ); +} + +/** + * Add a track to the current list view + */ +MtpMediaItem +*MtpMediaDevice::addTrackToView( MtpTrack *track, MtpMediaItem *item ) +{ + QString artistName = track->bundle()->artist(); + + MtpMediaItem *artist = dynamic_cast( m_view->findItem( artistName, 0 ) ); + if( !artist ) + { + artist = new MtpMediaItem(m_view); + artist->m_device = this; + artist->setText( 0, artistName ); + artist->setType( MediaItem::ARTIST ); + } + + QString albumName = track->bundle()->album(); + MtpMediaItem *album = dynamic_cast( artist->findItem( albumName ) ); + if( !album ) + { + album = new MtpMediaItem( artist ); + album->setText( 0, albumName ); + album->setType( MediaItem::ALBUM ); + album->m_device = this; + } + + if( item ) + album->insertItem( item ); + else + { + item = new MtpMediaItem( album ); + item->m_device = this; + QString titleName = track->bundle()->title(); + item->setTrack( track ); + item->m_order = track->bundle()->track(); + item->setText( 0, titleName ); + item->setType( MediaItem::TRACK ); + item->setBundle( track->bundle() ); + item->track()->setId( track->id() ); + m_fileNameToItem[ QString( "%1/%2" ).arg( track->folderId() ).arg( track->bundle()->filename() ) ] = item; + m_idToTrack[ track->id() ] = track; + } + return item; +} + +/** + * Get tracks and add them to the listview + */ +int +MtpMediaDevice::readMtpMusic() +{ + DEBUG_BLOCK + + clearItems(); + + m_critical_mutex.lock(); + + QString genericError = i18n( "Could not get music from MTP Device" ); + + int total = 100; + int progress = 0; + setProgress( progress, total ); // we don't know how many tracks. fake progress bar. + + kapp->processEvents( 100 ); + + LIBMTP_track_t *tracks = LIBMTP_Get_Tracklisting_With_Callback( m_device, progressCallback, this ); + + debug() << "Got tracks from device" << endl; + + if( tracks == 0 ) + { + debug() << "0 tracks returned. Empty device..." << endl; + } + else + { + LIBMTP_track_t *tmp = tracks; + total = 0; + // spin through once to determine size of the list + while( tracks != 0 ) + { + tracks = tracks->next; + total++; + } + setProgress( progress, total ); + tracks = tmp; + // now process the tracks + while( tracks != 0 ) + { + MtpTrack *mtp_track = new MtpTrack( tracks ); + mtp_track->readMetaData( tracks ); + addTrackToView( mtp_track ); + tmp = tracks; + tracks = tracks->next; + LIBMTP_destroy_track_t( tmp ); + progress++; + setProgress( progress ); + if( progress % 50 == 0 ) + kapp->processEvents( 100 ); + } + } + + readPlaylists(); + readAlbums(); + + setProgress( total ); + hideProgress(); + + m_critical_mutex.unlock(); + + return 0; +} + +/** + * Populate playlists + */ +void +MtpMediaDevice::readPlaylists() +{ + LIBMTP_playlist_t *playlists = LIBMTP_Get_Playlist_List( m_device ); + + if( playlists != 0 ) + { + LIBMTP_playlist_t *tmp; + while( playlists != 0 ) + { + MtpMediaItem *playlist = new MtpMediaItem( m_playlistItem, this ); + playlist->setText( 0, QString::fromUtf8( playlists->name ) ); + playlist->setType( MediaItem::PLAYLIST ); + playlist->setPlaylist( new MtpPlaylist() ); + playlist->playlist()->setId( playlists->playlist_id ); + uint32_t i; + for( i = 0; i < playlists->no_tracks; i++ ) + { + MtpTrack *track = m_idToTrack[ playlists->tracks[i] ]; + if( track == 0 ) // skip invalid playlist entries + continue; + MtpMediaItem *item = new MtpMediaItem( playlist ); + item->setText( 0, track->bundle()->artist() + " - " + track->bundle()->title() ); + item->setType( MediaItem::PLAYLISTITEM ); + item->setBundle( track->bundle() ); + item->setTrack( track ); + item->m_order = i; + item->m_device = this; + } + tmp = playlists; + playlists = playlists->next; + LIBMTP_destroy_playlist_t( tmp ); + kapp->processEvents( 50 ); + } + } +} + + +/** + * Read existing albums + */ +void +MtpMediaDevice::readAlbums() +{ + LIBMTP_album_t *albums = LIBMTP_Get_Album_List( m_device ); + + if( albums != 0 ) + { + LIBMTP_album_t *tmp; + while( albums != 0 ) + { + m_idToAlbum[ albums->album_id ] = new MtpAlbum( albums ); + tmp = albums; + albums = albums->next; + LIBMTP_destroy_album_t( tmp ); + kapp->processEvents( 50 ); + } + } +} + +/** + * Clear the current listview + */ +void +MtpMediaDevice::clearItems() +{ + m_view->clear(); + initView(); +} + +/** + * MtpTrack Class + */ +MtpTrack::MtpTrack( LIBMTP_track_t *track ) +{ + m_id = track->item_id; + m_folder_id = 0; +} + +/** + * Read track properties from the device and set it on the track + */ +void +MtpTrack::readMetaData( LIBMTP_track_t *track ) +{ + MetaBundle *bundle = new MetaBundle(); + + if( track->genre != 0 ) + bundle->setGenre( AtomicString( QString::fromUtf8( track->genre ) ) ); + if( track->artist != 0 ) + bundle->setArtist( AtomicString( QString::fromUtf8( track->artist ) ) ); + if( track->album != 0 ) + bundle->setAlbum( AtomicString( QString::fromUtf8( track->album ) ) ); + if( track->title != 0 ) + bundle->setTitle( AtomicString( QString::fromUtf8( track->title ) ) ); + if( track->filename != 0 ) + bundle->setPath( AtomicString( QString::fromUtf8( track->filename ) ) ); + + // translate codecs to file types + if( track->filetype == LIBMTP_FILETYPE_MP3 ) + bundle->setFileType( MetaBundle::mp3 ); + else if( track->filetype == LIBMTP_FILETYPE_WMA ) + bundle->setFileType( MetaBundle::wma ); + else if( track->filetype == LIBMTP_FILETYPE_OGG ) + bundle->setFileType( MetaBundle::ogg ); + else + bundle->setFileType( MetaBundle::other ); + + if( track->date != 0 ) + bundle->setYear( QString( QString::fromUtf8( track->date ) ).mid( 0, 4 ).toUInt() ); + if( track->tracknumber > 0 ) + bundle->setTrack( track->tracknumber ); + if( track->duration > 0 ) + bundle->setLength( track->duration / 1000 ); // Divide by 1000 since this is in milliseconds + + this->setFolderId( track->parent_id ); + + this->setBundle( *bundle ); +} + +/** + * Set this track's metabundle + */ +void +MtpTrack::setBundle( MetaBundle &bundle ) +{ + m_bundle = bundle; +} + +/** + * MtpAlbum Class + */ +MtpAlbum::MtpAlbum( LIBMTP_album_t *album ) +{ + m_id = album->album_id; + m_album = QString::fromUtf8( album->name ); +} diff --git a/amarok/src/mediadevice/mtp/mtpmediadevice.h b/amarok/src/mediadevice/mtp/mtpmediadevice.h new file mode 100644 index 00000000..3f60a89a --- /dev/null +++ b/amarok/src/mediadevice/mtp/mtpmediadevice.h @@ -0,0 +1,205 @@ +/*************************************************************************** + * copyright : (C) 2006 Andy Kelk * + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + + /** + * Based on njb mediadevice with some code hints from the libmtp + * example tools + */ + + /** + * MTP media device + * @author Andy Kelk + * @see http://libmtp.sourceforge.net/ + */ + +#ifndef AMAROK_MTPMEDIADEVICE_H +#define AMAROK_MTPMEDIADEVICE_H + +#include "mediabrowser.h" + +#include +#include + +#include + +class MtpMediaDevice; +class MtpMediaItem; + +class MtpTrack { + friend class MediaItem; + public: + MtpTrack( LIBMTP_track_t* track ); + ~MtpTrack() {}; + bool operator==( const MtpTrack& second ) const { return m_id == second.m_id; } + + public: + u_int32_t id() const { return m_id; } + MetaBundle *bundle() { return new MetaBundle( m_bundle ); } + uint32_t folderId() const { return m_folder_id; } + void setBundle( MetaBundle &bundle ); + void setId( int id ) { m_id = id; } + void setFolderId( const uint32_t folder_id ) { m_folder_id = folder_id; } + void readMetaData( LIBMTP_track_t *track ); + + private: + u_int32_t m_id; + MetaBundle m_bundle; + uint32_t m_folder_id; +}; + +class MtpPlaylist { + friend class MediaItem; + public: + bool operator==( const MtpPlaylist& second ) const { return m_id == second.m_id; } + + public: + u_int32_t id() const { return m_id; } + void setId( int id ) { m_id = id; } + + private: + u_int32_t m_id; +}; + + +class MtpAlbum { + friend class MediaItem; + public: + MtpAlbum( LIBMTP_album_t* album ); + ~MtpAlbum(); + bool operator==( const MtpAlbum& second ) const { return m_id == second.m_id; } + + public: + u_int32_t id() const { return m_id; } + void setId( int id ) { m_id = id; } + QString album() const { return m_album; } + + private: + u_int32_t m_id; + QString m_album; +}; + +class MtpMediaItem : public MediaItem +{ + public: + MtpMediaItem( QListView *parent, QListViewItem *after = 0 ) + : MediaItem( parent, after ) {} + MtpMediaItem( QListViewItem *parent, QListViewItem *after = 0 ) + : MediaItem( parent, after ) {} + MtpMediaItem( QListView *parent, MediaDevice *dev ) + : MediaItem( parent ) { init( dev ); } + MtpMediaItem( QListViewItem *parent, MediaDevice *dev ) + : MediaItem( parent ) { init( dev ); } + + void init( MediaDevice *dev ) + { + m_track = 0; + m_playlist = 0; + m_device = dev; + } + + ~MtpMediaItem() + { + //m_track->removeItem(this); + } + void setTrack( MtpTrack *track ) { m_track = track; } + MtpTrack *track() { return m_track; } + void setPlaylist( MtpPlaylist *playlist ) { m_playlist = playlist; } + MtpPlaylist *playlist() { return m_playlist; } + QString filename() { return m_track->bundle()->url().path(); } + + private: + MtpTrack *m_track; + MtpPlaylist *m_playlist; +}; + +class MtpMediaDevice : public MediaDevice +{ + Q_OBJECT + + public: + MtpMediaDevice(); + virtual bool autoConnect() { return false; } + virtual bool asynchronousTransfer() { return false; } + bool isConnected(); + LIBMTP_mtpdevice_t *current_device(); + void setDisconnected(); + virtual void rmbPressed( QListViewItem *qitem, const QPoint &point, int arg1 ); + virtual void init( MediaBrowser* parent ); + virtual QStringList supportedFiletypes(); + void setFolders( LIBMTP_folder_t *folders ); + void cancelTransfer(); + void customClicked(); + virtual void addConfigElements( QWidget *parent ); + virtual void removeConfigElements( QWidget *parent ); + virtual void applyConfig(); + virtual void loadConfig(); + static int progressCallback( uint64_t const sent, uint64_t const total, void const * const data ); + + protected: + MediaItem* trackExists( const MetaBundle &bundle ); + + bool openDevice( bool silent ); + bool closeDevice(); + bool lockDevice( bool tryLock=false ) { if( tryLock ) { return m_mutex.tryLock(); } else { m_mutex.lock(); return true; } } + void unlockDevice() { m_mutex.unlock(); } + + MediaItem *copyTrackToDevice( const MetaBundle &bundle ); + int downloadSelectedItemsToCollection(); + + void synchronizeDevice(); + int deleteItemFromDevice( MediaItem *mediaitem, int flags=DeleteTrack ); + void addToPlaylist( MediaItem *list, MediaItem *after, QPtrList items ); + MtpMediaItem *newPlaylist( const QString &name, MediaItem *list, QPtrList items ); + bool getCapacity( KIO::filesize_t *total, KIO::filesize_t *available ); + virtual void updateRootItems() {}; + + private slots: + void playlistRenamed( QListViewItem *item, const QString &, int ); + + private: + MtpMediaItem *addTrackToView(MtpTrack *track, MtpMediaItem *item=0 ); + int readMtpMusic( void ); + void clearItems(); + int deleteObject( MtpMediaItem *deleteItem ); + uint32_t checkFolderStructure( const MetaBundle &bundle, bool create = true ); + uint32_t createFolder( const char *name, uint32_t parent_id ); + uint32_t getDefaultParentId( void ); + uint32_t folderNameToID( char *name, LIBMTP_folder_t *folderlist ); + uint32_t subfolderNameToID( const char *name, LIBMTP_folder_t *folderlist, uint32_t parent_id ); + void updateFolders( void ); + void initView( void ); + void readPlaylists( void ); + void readAlbums( void ); + void playlistFromItem( MtpMediaItem *item); + QByteArray *getSupportedImage( QString path ); + void sendAlbumArt( QPtrList *items ); + void updateAlbumArt( QPtrList *items ); + LIBMTP_album_t *getOrCreateAlbum( QPtrList *items ); + LIBMTP_mtpdevice_t *m_device; + QMutex m_mutex; + QMutex m_critical_mutex; + LIBMTP_folder_t *m_folders; + uint32_t m_default_parent_folder; + QString m_folderStructure; + QLineEdit *m_folderStructureBox; + QLabel *m_folderLabel; + QStringList m_supportedFiles; + QPtrList *m_newTracks; + QMap mtpFileTypes; + QMap m_idToTrack; + QMap m_fileNameToItem; + QMap m_idToAlbum; + QString m_format; +}; + +#endif diff --git a/amarok/src/mediadevice/njb/Makefile.am b/amarok/src/mediadevice/njb/Makefile.am new file mode 100644 index 00000000..8d4a1d69 --- /dev/null +++ b/amarok/src/mediadevice/njb/Makefile.am @@ -0,0 +1,26 @@ +kde_services_DATA = amarok_njb-mediadevice.desktop + + +INCLUDES = \ + -I$(top_srcdir)/amarok/src \ + -I$(top_builddir)/amarok/src \ + -I$(top_srcdir)/amarok/src/amarokcore \ + -I$(top_builddir)/amarok/src/amarokcore \ + -I$(top_srcdir)/amarok/src/engine \ + -I$(top_builddir)/amarok/src/engine \ + -I$(top_srcdir)/amarok/src/mediadevice \ + $(TAGLIB_CFLAGS) \ + $(all_includes) + +METASOURCES = AUTO +kde_module_LTLIBRARIES = libamarok_njb-mediadevice.la + +libamarok_njb_mediadevice_la_LIBADD = $(top_builddir)/amarok/src/libamarok.la \ + $(LIB_KDEUI) $(LIB_KDECORE) -lnjb $(LIB_QT) $(LIB_KIO) + +libamarok_njb_mediadevice_la_LDFLAGS = $(KDE_PLUGIN) $(all_libraries) + +noinst_HEADERS = njbmediadevice.h playlist.h track.h + +libamarok_njb_mediadevice_la_SOURCES = njbmediadevice.cpp playlist.cpp \ + track.cpp diff --git a/amarok/src/mediadevice/njb/amarok_njb-mediadevice.desktop b/amarok/src/mediadevice/njb/amarok_njb-mediadevice.desktop new file mode 100644 index 00000000..97ad2023 --- /dev/null +++ b/amarok/src/mediadevice/njb/amarok_njb-mediadevice.desktop @@ -0,0 +1,109 @@ +[Desktop Entry] +Encoding=UTF-8 +Type=Service +Name=Creative Nomad Jukebox Media Device +Name[af]=Creative Nomad Jukebox media toestel +Name[ar]=جهاز اخراج وسائط Creative Nomad Jukebox +Name[bg]=Устройство Creative Nomad Jukebox +Name[bn]=ক্রিয়েটিভ নোমাড জুকবক্স মিডিয়া ডিভাইস +Name[ca]=Dispositiu de suports Creative Nomad Jukebox +Name[cs]=Mediální zařízení Creative Nomad Jukebox +Name[da]=Creative Nomad Jukebox-medieenhed +Name[de]=Creative Nomad Jukebox +Name[el]=Συσκευή πολυμέσων Creative Nomad Jukebox +Name[eo]=Creative Nomad Jukebox Media Ekipaĵo +Name[es]=Dispositivo de medios VFAT genérico +Name[et]=Creative Nomad Jukebox meediaseade +Name[fa]=دستگاه رسانۀ Creative Nomad Jukebox +Name[fi]=Creative Nomad Jukebox -medialaite +Name[fr]=Périphérique Creative Nomad Jukebox +Name[gl]=Dispositivo Multimedio Creative Nomad Jukebox +Name[hu]=Creative Nomad Jukebox médiaeszköz +Name[is]=Creative Nomad Jukebox +Name[it]=Dispositivo multimediale Creative Nomad Jukebox +Name[ja]=Creative Nomad Jukebox メディアデバイス +Name[ka]=Creative Nomad Jukebox მედია მოწყობილობა +Name[km]=ឧបករណ៍​មេឌៀ Nomad Jukebox របស់ Creative +Name[lt]=Creative Nomad Jukebox daugialypės terpės įrenginys +Name[mk]=Уред за мултимедија Creative Nomad Jukebox +Name[nb]=Creative Nomad Jukebox medieenhet +Name[nds]=Creative-Medienreedschap Nomad Jukebox +Name[ne]=सिर्जानात्मक नोम्याड जुकेबक्स मिडिया यन्त्र +Name[nl]=Creative Nomad Jukebox media-apparaat +Name[nn]=Creative Nomad Jukebox-medieeining +Name[pa]=ਕਰੀਏਟਵ ਨੋਮਡ ਜੂਕਬਕਸ ਮੀਡਿਆ ਜੰਤਰ +Name[pl]=Urządzenie mediów Creative Nomad Jukebox +Name[pt]=Dispositivo Multimédia Creative Nomad Jukebox +Name[pt_BR]=Dispositivo de Mídia Creative Nomad Jukebox +Name[ru]=Creative Nomad Jukebox +Name[se]=Creative Nomad Jukebox-mediaovttadat +Name[sk]=Creative Nomad Jukebox Media zariadenie +Name[sr]=Creative-ов мултимедијални уређај Nomad Jukebox +Name[sr@Latn]=Creative-ov multimedijalni uređaj Nomad Jukebox +Name[sv]=Creative Nomad Jukebox-mediaenhet +Name[th]=อุปกรณ์เล่นสื่อ Creative Nomad Jukebox +Name[tr]=Creative Nomad Jukebox Ortam Aygıtı +Name[uk]=Пристрій носія Nomad Jukebox +Name[uz]=Creative Nomad Jukebox media-uskunasi +Name[uz@cyrillic]=Creative Nomad Jukebox медиа-ускунаси +Name[wa]=Édjin d' media Creative Nomad Jukebox +Name[zh_CN]=Creative Nomad Jukebox 媒体设备 +Name[zh_TW]=Creative Nomad Jukebox 媒體裝置 +X-KDE-Library=libamarok_njb-mediadevice +Comment=Plugin for Amarok +Comment[af]=Inprop module vir Amarok +Comment[ar]= قابس ( برنامج مضاف الى) AmaroK +Comment[bg]=Приставка за Amarok +Comment[bn]=আমারক-এর জন্য প্লাগিন +Comment[br]=Lugent evit Amarok +Comment[ca]=Connector per l'Amarok +Comment[cs]=Modul pro AmaroK +Comment[de]=Modul für Amarok +Comment[el]=Πρόσθετο για το AmaroK +Comment[eo]=Kromaĵo por Amarok +Comment[es]=Extensión para Amarok +Comment[et]=Amaroki plugin +Comment[fa]=وصله برای amaroK +Comment[fi]=Amarok-liitännäinen +Comment[fr]=Module pour Amarok +Comment[ga]=Breiseán AmaroK +Comment[gl]=Extensión para Amarok +Comment[hu]=Bővítőmodul az Amarokhoz +Comment[is]=Íforrit fyrir Amarok +Comment[it]=Plugin per Amarok +Comment[ja]=Amarok のためのプラグイン +Comment[ka]=მოდული Amarok-ისთვის +Comment[km]=កម្មវិធី​ជំនួយ​សម្រាប់ Amarok +Comment[lt]=Amarok įskiepis +Comment[mk]=Приклучок за Амарок +Comment[nb]=Programtillegg for Amarok +Comment[nds]=Moduul för Amarok +Comment[ne]=अमारोकका लागि प्लगइन +Comment[nl]=Plugin voor Amarok +Comment[nn]=Programtillegg for Amarok +Comment[pa]=ਅਮਰੋਕ ਲਈ ਪਲੱਗਇਨ +Comment[pl]=Wtyczka Amaroka +Comment[pt]='Plugin' para o Amarok +Comment[pt_BR]=Plugin para o Amarok +Comment[ru]=Модуль amaroK +Comment[se]=Lassemoduvla Amarok:ii +Comment[sk]=Amarok modul +Comment[sr]=Прикључак за Amarok +Comment[sr@Latn]=Priključak za Amarok +Comment[sv]=Insticksprogram för Amarok +Comment[th]=โปรแกรมเสริมสำหรับ Amarok +Comment[tr]=Amarok için Eklenti +Comment[uk]=Втулок для Amarok +Comment[uz]=Amarok uchun plagin +Comment[uz@cyrillic]=Amarok учун плагин +Comment[wa]=Tchôke-divins po Amarok +Comment[zh_CN]=Amarok 插件 +Comment[zh_TW]=amaroK 插件 +ServiceTypes=Amarok/Plugin +X-KDE-Amarok-plugintype=mediadevice +X-KDE-Amarok-name=njb-mediadevice +X-KDE-Amarok-authors=Andres Oton, T.R.Shashwath +X-KDE-Amarok-email=andres.oton@gmail.com, trshash84@gmail.com +X-KDE-Amarok-rank=100 +X-KDE-Amarok-version=1 +X-KDE-Amarok-framework-version=32 diff --git a/amarok/src/mediadevice/njb/njbmediadevice.cpp b/amarok/src/mediadevice/njb/njbmediadevice.cpp new file mode 100644 index 00000000..07d0dfec --- /dev/null +++ b/amarok/src/mediadevice/njb/njbmediadevice.cpp @@ -0,0 +1,912 @@ +/*************************************************************************** + copyright : (C) 2006 by Andres Oton + email : andres.oton@gmail.com + + copyright : (C) 2006 by T.R.Shashwath + email : trshash84@gmail.com + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License version 2 as * + * published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301 USA * + ***************************************************************************/ + +#include "njbmediadevice.h" + +AMAROK_EXPORT_PLUGIN( NjbMediaDevice ) + + +// Amarok +#include +#include +#include +#include +#include +#include + + +// KDE +#include +#include +#include +#include //smallIcon +#include +#include +#include +#include +#include +#include +#include +#include //downloadSelectedItems() +#include //downloadSelectedItems() + + +// Qt +#include +#include +#include +#include +#include + +// posix +#include +#include +#include +#include + +namespace Amarok { extern KConfig *config( const QString& ); } +njb_t *NjbMediaDevice::m_njb = 0; +// This function has NOT handled the request, so other functions may be called +// upon to do so +const int NJB_NOTHANDLED = 0; + +// This function has handled the request, so no further processing is needed. +const int NJB_HANDLED = -1; + +NjbMediaDevice::NjbMediaDevice(): MediaDevice() +{ + + // listAmarokPlayLists = new QListView(); + m_name = i18n("NJB Media device"); + m_njb = 0; + m_connected = false; + m_libcount = 0; + m_connected = false; + m_customButton = true; + m_td = 0; + NJB_Set_Debug(0); // or try DD_SUBTRACE + KToolBarButton* customButton = MediaBrowser::instance()->getToolBar()->getButton( MediaBrowser::CUSTOM ); + customButton->setText( i18n("Special device functions") ); + QToolTip::remove( customButton ); + QToolTip::add( customButton, i18n( "Special functions of your jukebox" ) ); +} + + +NjbMediaDevice::~NjbMediaDevice() +{ + +} + +bool +NjbMediaDevice::closeDevice() +{ + DEBUG_BLOCK + + if(m_connected) + { + NJB_Release( m_njb); + m_connected = false; + } + m_connected = false; + + if( m_njb ) + { + NJB_Close( m_njb); + + m_njb = 0; + + } + + debug()<< "Disconnected NJB device" << endl; + + clearItems(); + + m_name = i18n("NJB Media device"); + debug() << "Done" << endl; + return true; +} + +void +NjbMediaDevice::unlockDevice() +{ +} + +bool +NjbMediaDevice::getCapacity(KIO::filesize_t* total, KIO::filesize_t* available) +{ + if(!m_connected) + return false; + + + u_int64_t itotal; + u_int64_t ifree; + + if(NJB_Get_Disk_Usage(m_njb, &itotal, &ifree) == -1) + return false; + + *total = itotal; + *available = ifree; + + return true; + +} + + +bool +NjbMediaDevice::isConnected() +{ + return m_connected; + +} + +bool +NjbMediaDevice::isPlayable(const MetaBundle& bundle) +{ + DEBUG_BLOCK + ; + if(bundle.fileType() == MetaBundle::mp3 + || bundle.fileType() == MetaBundle::wma) + return true; + + return false; +} + +bool +NjbMediaDevice::isPreferredFormat(const MetaBundle& bundle) +{ + DEBUG_BLOCK + + if(bundle.fileType() == MetaBundle::mp3) + return true; + else + return false; +} + +bool +NjbMediaDevice::lockDevice(bool tryOnly) +{ + // The device is "locked" upon connection - there's very little else we can do here. + Q_UNUSED(tryOnly); + return true; +} + +bool +NjbMediaDevice::openDevice(bool) +{ + DEBUG_BLOCK + + + if( m_njb ) + return true; + + QString genericError = i18n( "Could not connect to Nomad device" ); + NJB_Set_Unicode( NJB_UC_UTF8 ); // I assume that UTF-8 is fine with everyone... + + int n; + if( NJB_Discover( njbs, 0, &n) == -1 || n == 0 ) + { + Amarok::StatusBar::instance()->shortLongMessage( genericError, i18n("A suitable Nomad device could not be found"), KDE::StatusBar::Error ); + debug() << ": no NJBs found\n"; + + return false; + } + m_njb = &njbs[0]; + + + if( NJB_Open( m_njb ) == -1 ) + { + Amarok::StatusBar::instance()->shortLongMessage( genericError, i18n("Nomad device could not be opened"), KDE::StatusBar::Error ); + + + return false; + } + + QString deviceName = NJB_Get_Device_Name( m_njb, 1 ); + QString owner = NJB_Get_Owner_String( m_njb ); + m_name = deviceName + " (Owned by " + owner + ')'; + + + if( NJB_Capture(m_njb) == -1) + { + debug() << ": couldn't capture\n"; + m_connected = false; + } + else + { + m_connected = true; + readJukeboxMusic(); + } + + return true; + +} + +int +NjbMediaDevice::deleteFromDevice(unsigned id) +{ + int status = NJB_Delete_Track( m_njb, id ); + + if( status != NJB_SUCCESS) + { + debug() << ": NJB_Delete_Track failed" << endl; + return -1; + } + + // remove from the cache + trackList.remove(trackList.findTrackById( id ) ); + + return 1; +} + +int +NjbMediaDevice::deleteItemFromDevice(MediaItem* item, int flags ) +{ + DEBUG_BLOCK + int result = 0; + if ( isCanceled() || !item ) + { + return -1; + } + MediaItem *next = 0; + + switch( item->type() ) + { + case MediaItem::TRACK: + if( isCanceled() ) + break; + if( item ) + { + deleteTrack( dynamic_cast (item) ); + result++; + } + break; + case MediaItem::ALBUM: + case MediaItem::ARTIST: + // Recurse through the lists, slashing and burning. + + if( isCanceled() ) + break; + + expandItem( dynamic_cast(item) ); + + for( MediaItem *it = dynamic_cast( item->firstChild() ); it ; it = next ) + { + + next = dynamic_cast(it->nextSibling()); + int res = deleteItemFromDevice( it, flags ); + if( res >= 0 && result >= 0 ) + result += res; + else + result = -1; + + } + if( item ) + delete dynamic_cast( item ); + break; + default: + result = 0; + } + + return result; +} + +int +NjbMediaDevice::deleteTrack(NjbMediaItem *trackItem) +{ + int status = NJB_Delete_Track( m_njb, trackItem->track()->id() ); + + if( status != NJB_SUCCESS) + { + debug() << ": NJB_Delete_Track failed" << endl; + Amarok::StatusBar::instance()->shortLongMessage( i18n( "Deleting failed" ), i18n( "Deleting track(s) failed." ), KDE::StatusBar::Error ); + return -1; + } + + debug() << ": NJB_Delete_Track track deleted" << endl; + + // remove from the cache + trackList.remove(trackList.findTrackById( trackItem->track()->id() ) ); + delete trackItem; + return 1; +} + +int +NjbMediaDevice::downloadSelectedItems() +{ + /* Copied from ifpmediadevice */ + QString save = QString::null; + + KURLRequesterDlg dialog( save, 0, 0 ); + dialog.setCaption( kapp->makeStdCaption( i18n( "Choose a Download Directory" ) ) ); + dialog.urlRequester()->setMode( KFile::Directory | KFile::ExistingOnly ); + dialog.exec(); + + KURL destDir = dialog.selectedURL(); + if( destDir.isEmpty() ) + return -1; + + destDir.adjustPath( 1 ); //add trailing slash + QDir dir; + QString path; + + QPtrList items; + m_view->getSelectedLeaves( 0, &items ); + int result = 0; + + for( MediaItem *it = items.first(); it && !(m_canceled); it = items.next() ) + { + path = destDir.path(); + if( it->type() == MediaItem::TRACK ) + { + dynamic_cast( parent() )->queue()->addURL(path, dynamic_cast(it) ); + + } + } + return result; + +} + + +/** + * Download the selected items and put them into the collection DB. + * @return The number of files downloaded. +*/ +int +NjbMediaDevice::downloadToCollection() +{ + // We will first download all files into a temp dir, and then call move to collection. + + QPtrList items; + m_view->getSelectedLeaves( 0, &items ); + + KTempDir tempdir( QString::null ); // Default prefix is fine with us + tempdir.setAutoDelete( true ); // We don't need it once the work is done. + QString path = tempdir.name(), filepath; + KURL::List urls; + for( MediaItem *it = items.first(); it && !(m_canceled); it = items.next() ) + { + if( (it->type() == MediaItem::TRACK) ) + { + NjbMediaItem* auxItem = dynamic_cast( (it) ); + if (!auxItem) { + debug() << "Dynamic cast to NJB media item failed. " << endl; + return -1; + } + QString track_id; + track_id.setNum( auxItem->track()->id() ); + filepath = path + auxItem->bundle()->url().path(); + + if( NJB_Get_Track( m_njb, auxItem->track()->id(), auxItem->bundle()->filesize(), filepath.utf8(), progressCallback, this) + != NJB_SUCCESS ) + { + debug() << "Get Track failed. " << endl; + if( NJB_Error_Pending(m_njb) ) + { + const char *njbError; + while( (njbError = NJB_Error_Geterror(m_njb) ) ) + error() << njbError << endl; + } + else + debug() << "No reason to report for failure" << endl; + } + urls << filepath; + } + } + // Now, call the collection organizer. + CollectionView::instance()->organizeFiles( urls, i18n( "Move Files To Collection" ), false ); + return 0; +} + +MediaItem* +NjbMediaDevice::copyTrackToDevice(const MetaBundle& bundle) +{ + DEBUG_BLOCK + + if( m_canceled ) + return 0; + trackValueList::const_iterator it_track = trackList.findTrackByName( bundle.filename() ); + if( it_track != trackList.end() ) + { + deleteFromDevice( (*it_track)->id() ); + } + + // read the mp3 header + int duration = bundle.length(); + + if( !duration ) + { + m_errMsg = i18n( "Not a valid mp3 file"); + return 0; + } + MetaBundle temp( bundle ); + + NjbTrack *taggedTrack = new NjbTrack(); + + taggedTrack->setBundle( temp ); + + u_int32_t id; + m_progressStart = time( 0); + m_progressMessage = i18n("Copying / Sent %1%..."); + + njb_songid_t* songid = NJB_Songid_New(); + taggedTrack->writeToSongid( songid ); + m_busy = true; + kapp->processEvents( 100 ); + if(NJB_Send_Track (m_njb, bundle.url().path().utf8(), songid, progressCallback, this, &id) != NJB_SUCCESS) + { + debug() << ": NJB_Send_Track failed\n"; + if (NJB_Error_Pending(m_njb)) + { + const char* njbError; + while ((njbError = NJB_Error_Geterror(m_njb))) + warning() << ": " << njbError << endl; + } + else + debug() << ": No reason for failure reported.\n"; + + m_busy = false; + NJB_Songid_Destroy( songid ); + + return 0; + } + + m_busy = false; + NJB_Songid_Destroy( songid ); + + taggedTrack->setId( id ); + + // cache the track + trackList.append( taggedTrack );; + + return addTrackToView( taggedTrack ); +} + +void +NjbMediaDevice::copyTrackFromDevice( MediaItem *item ) +{ + DEBUG_BLOCK + trackValueList::iterator it; + for( it = trackList.begin(); it != trackList.end(); it++) + if( (*(*it)->bundle()) == *(item->bundle()) ) + break; + + NjbTrack *track((*it)); + + QString filename = item->bundle()->directory() + track->bundle()->filename(); + if( NJB_Get_Track( m_njb, track->id(), track->bundle()->filesize(), filename.utf8(), progressCallback, this) + != NJB_SUCCESS ) + { + debug() << "Get Track failed. " << endl; + if( NJB_Error_Pending(m_njb) ) + { + const char *njbError; + while( (njbError = NJB_Error_Geterror(m_njb) ) ) + error() << njbError << endl; + } + else + debug() << "No reason to report for failure" << endl; + } +} + +MediaItem* +NjbMediaDevice::newPlaylist(const QString& name, MediaItem* parent, QPtrList< MediaItem > items) +{ + DEBUG_BLOCK + + Q_UNUSED(parent); + //MediaItem* newplaylist = new MediaItem(parent); + + NjbPlaylist playlist; + int status = playlist.setName( name ); + + if(status == NJB_SUCCESS) + { + //NjbMediaItem *newNjbPlayList = new NjbMediaItem(listAmarokPlayLists); + for(MediaItem *item=items.first();item;item=items.next()) + { + + status = playlist.addTrack( item->bundle()->filename() ); + if( status == NJB_FAILURE) + { + //TODO: Show a message with the error. + } + else if( status != NJB_SUCCESS) + { + return 0; + } + + + //NjbMediaItem *nitem = dynamic_cast(item); + + } + + status = playlist.update(); + if( status != NJB_SUCCESS) + { + return 0; + } + + } + + //TODO:Crear un conversor de Playlists a las listas de Amarok + return 0; +} + +QStringList +NjbMediaDevice::supportedFiletypes() +{ + QStringList supportedFiles; + supportedFiles << "mp3"; + supportedFiles << "wav"; + supportedFiles << "wma"; + return supportedFiles; +} + +TransferDialog* +NjbMediaDevice::getTransferDialog() +{ + return m_td; +} + +void +NjbMediaDevice::addConfigElements(QWidget* arg1) +{ + MediaDevice::addConfigElements(arg1); +} + +void +NjbMediaDevice::addToPlaylist(MediaItem* playlist, MediaItem* after, QPtrList< MediaItem > items) +{ + MediaDevice::addToPlaylist(playlist, after, items); +} + +void +NjbMediaDevice::applyConfig() +{ + MediaDevice::applyConfig(); +} + +void +NjbMediaDevice::cancelTransfer() +{ + m_canceled = true; +} + +void +NjbMediaDevice::init(MediaBrowser* parent) +{ + MediaDevice::init(parent); +} + +void +NjbMediaDevice::loadConfig() +{ + MediaDevice::loadConfig(); +} + +void +NjbMediaDevice::removeConfigElements(QWidget* arg1) +{ + MediaDevice::removeConfigElements(arg1); +} + +MediaItem * +NjbMediaDevice::trackExists( const MetaBundle & bundle ) +{ + MediaItem *artist = dynamic_cast( m_view->findItem( bundle.artist(), 0 ) ); + if ( artist ) + { + MediaItem *album = dynamic_cast( artist->findItem( bundle.album() ) ); + if( album ) + { + return dynamic_cast( album->findItem( bundle.title() ) ); + } + } + return 0; +} + +void +NjbMediaDevice::rmbPressed(QListViewItem* qitem, const QPoint& point, int ) +{ + + enum Actions { DOWNLOAD, DOWNLOAD_TO_COLLECTION, RENAME, DELETE}; + + NjbMediaItem *item = static_cast(qitem); + if ( item ) + { + KPopupMenu menu( m_view); + menu.insertItem( SmallIconSet( Amarok::icon( "collection" ) ), i18n("Download file"), DOWNLOAD ); + menu.insertItem( SmallIconSet( Amarok::icon( "collection" ) ), i18n("Download to collection"), DOWNLOAD_TO_COLLECTION ); + menu.insertSeparator(); + //menu.insertItem( SmallIconSet( Amarok::icon( "edit" ) ), i18n( "Rename" ), RENAME ); + menu.insertItem( SmallIconSet( Amarok::icon( "remove" ) ), i18n( "Delete from device" ), DELETE ); + + + int id = menu.exec( point ); + MediaItem *i; + QPtrList items; + switch( id ) + { + case DOWNLOAD: + downloadSelectedItems(); + + break; + + case RENAME: + //TODO: Implement rename tracks + //m_view->rename( item, 0 ); + break; + + case DELETE: + m_view->getSelectedLeaves( 0, &items ); + while( !items.isEmpty() ) + { + i = items.first(); + MediaDevice::deleteFromDevice( i ); + items.remove(i); + } + readJukeboxMusic(); + break; + case DOWNLOAD_TO_COLLECTION: + downloadToCollection(); + break; + } + return; + } + +} + +void +NjbMediaDevice::runTransferDialog() +{ + m_td = new TransferDialog( this ); + m_td->exec(); +} + +int +NjbMediaDevice::progressCallback( u_int64_t sent, u_int64_t total, const char* /*buf*/, unsigned /*len*/, void* data) +{ + kapp->processEvents( 100 ); + + NjbMediaDevice *njb_media = reinterpret_cast(data); + + if( njb_media->isCanceled() ) + { + debug() << "Canceling transfer operation" << endl; + njb_media->setCanceled( false ); + njb_media->setProgress( sent, total ); + return 1; + } + + njb_media->setProgress( sent, total); + + return 0; +} + +void +NjbMediaDevice::clearItems() +{ + m_view->clear(); +} + +/** Transfer musical info from the njb to local structures */ +int +NjbMediaDevice::readJukeboxMusic( void ) +{ +// DEBUG_BLOCK + int result = NJB_SUCCESS; + + // First, read jukebox tracks + if(trackList.isEmpty()) + { + + result = trackList.readFromDevice(); + } + + if(result == NJB_SUCCESS) + { + clearItems(); + + /*m_playlistItem = new NjbMediaItem( m_view ); + m_playlistItem->setText( 0, i18n("Playlists") ); + m_playlistItem->m_order = -5; + m_playlistItem->setType( MediaItem::PLAYLISTSROOT );*/ + + kapp->processEvents( 100 ); + + for( trackValueList::iterator it = trackList.begin(); it != trackList.end(); it++ ) + { + if( m_view->findItem( ((*it)->bundle()->artist().string()), 0 ) == 0 ) + { + NjbMediaItem *artist = new NjbMediaItem( m_view ); + artist->setText( 0, (*it)->bundle()->artist() ); + artist->setType( MediaItem::ARTIST ); + artist->setExpandable( true ); + artist->setBundle( (*it)->bundle() ); + artist->m_device = this; + } + } + } + debug() << ": return " << result << endl; + return result; +} + +NjbMediaItem * +NjbMediaDevice::addTrackToView( NjbTrack *track, NjbMediaItem *item ) +{ + QString artistName = track->bundle()->artist(); + + NjbMediaItem *artist = dynamic_cast( m_view->findItem( artistName, 0 ) ); + if(!artist) + { + artist = new NjbMediaItem(m_view); + artist->m_device = this; + artist->setText( 0, artistName ); + artist->setType( MediaItem::ARTIST ); + } + + QString albumName = track->bundle()->album(); + NjbMediaItem *album = dynamic_cast( artist->findItem( albumName ) ); + if(!album) + { + album = new NjbMediaItem( artist ); + album->setText( 0, albumName ); + album->setType( MediaItem::ALBUM ); + album->m_device = this; + } + + if( item ) + album->insertItem( item ); + else + { + item = new NjbMediaItem( album ); + item->m_device = this; + QString titleName = track->bundle()->title(); + item->setTrack( track ); + + item->setText( 0, titleName ); + item->setType( MediaItem::TRACK ); + item->setBundle( track->bundle() ); + item->track()->setId( track->id() ); + } + return item; + +} + +njb_t * +NjbMediaDevice::theNjb() +{ + return NjbMediaDevice::m_njb; +} + +void +NjbMediaDevice::expandItem( QListViewItem *item ) +{ + DEBUG_BLOCK + // First clear the item's children to repopulate. + while( item->firstChild() ) + delete item->firstChild(); + + NjbMediaItem *it = dynamic_cast( item ); + + if (!it) { + debug() << "Dynamic cast to NJB media item failed" << endl; + return; + } + + switch( it->type() ) + { + case MediaItem::ARTIST: + if( it->childCount() == 0 ) // Just to be sure + addAlbums( item->text( 0 ), it ); + break; + case MediaItem::ALBUM: + if( it->childCount() == 0 ) + addTracks( it->bundle()->artist(), item->text( 0 ), it ); + break; + default: + break; + } +} + +NjbMediaItem* +NjbMediaDevice::addAlbums(const QString &artist, NjbMediaItem *item) +{ + for( trackValueList::iterator it = trackList.begin(); it != trackList.end(); it++ ) + { + if( item->findItem( (*it)->bundle()->album() ) == 0 && ( (*it)->bundle()->artist().string() == artist ) ) + { + NjbMediaItem *album = new NjbMediaItem( item ); + album->setText( 0, (*it)->bundle()->album() ); + album->setType( MediaItem::ALBUM ); + album->setExpandable( true ); + album->setBundle( (*it)->bundle() ); + album->m_device = this; + } + } + return item; +} + +NjbMediaItem* +NjbMediaDevice::addTracks(const QString &artist, const QString &album, NjbMediaItem *item) +{ + for( trackValueList::iterator it = trackList.begin(); it != trackList.end(); it++ ) + { + if( ( (*it)->bundle()->album().string() == album ) && ( (*it)->bundle()->artist().string() == artist )) + { + NjbMediaItem *track = new NjbMediaItem( item ); + track->setText( 0, (*it)->bundle()->title() ); + track->setType( MediaItem::TRACK ); + track->setBundle( (*it)->bundle() ); + track->setTrack( (*it) ); + track->m_device = this; + } + } + return item; +} + +NjbMediaItem* +NjbMediaDevice::addArtist( NjbTrack *track ) +{ + if( m_view->findItem( track->bundle()->artist().string(), 0 ) == 0 ) + { + NjbMediaItem *artist = new NjbMediaItem( m_view ); + artist->setText( 0, track->bundle()->artist() ); + artist->setType( MediaItem::ARTIST ); + artist->setExpandable( true ); + artist->setBundle( track->bundle() ); + artist->m_device = this; + } + return dynamic_cast( m_view->findItem( track->bundle()->artist().string(), 0 ) ); +} + +void +NjbMediaDevice::customClicked() +{ + QString Information; + QString tracksFound; + QString powerStatus; + QString batteryLevel; + QString batteryCharging; + + if( m_connected ) + { + tracksFound = i18n( "1 track found on device", + "%n tracks found on device ", trackList.size() ); + powerStatus = ( (NJB_Get_Auxpower( m_njb ) == 1) ? i18n("On auxiliary power") : i18n("On main power") ); + batteryCharging = ( (NJB_Get_Battery_Charging( m_njb ) == 1) ? i18n("Battery charging") : i18n("Battery not charging") ); + batteryLevel = (i18n("Battery level: ") + QString::number( NJB_Get_Battery_Level( m_njb ) ) ); + + Information = ( i18n("Player Information for ") + m_name +'\n' + + i18n("Power status: ") + powerStatus + '\n' + + i18n("Battery status: ") + batteryLevel + " (" + + batteryCharging + ')' ); + } + else + { + Information = i18n("Player not connected"); + } + + KMessageBox::information(0, Information, i18n("Device information") ); +} diff --git a/amarok/src/mediadevice/njb/njbmediadevice.h b/amarok/src/mediadevice/njb/njbmediadevice.h new file mode 100644 index 00000000..277c96cf --- /dev/null +++ b/amarok/src/mediadevice/njb/njbmediadevice.h @@ -0,0 +1,187 @@ +/*************************************************************************** + copyright : (C) 2006 by Andres Oton + email : andres.oton@gmail.com + + copyright : (C) 2006 by T.R.Shashwath + email : trshash84@gmail.com + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License version 2 as * + * published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301 USA * + ***************************************************************************/ + +#ifndef NJBMEDIADEVICE_H +#define NJBMEDIADEVICE_H + +#include +#include +#include + +#include +#include + +#include "playlist.h" +#include "track.h" + +#include +#include +#include + +// kde +#include +#include +#include + +/** + This class is used to manipulate Nomad Creative Jukebox and others media player that works with the njb libraries. + + You can find the njb libs at : http://libnjb.sourceforge.net + + Based on kionjb + + @author Andres Oton + @author T.R.Shashwath + */ + +const int NJB_SUCCESS = 0; +const int NJB_FAILURE = -1; + +// Global track list +extern trackValueList* theTracks; + +class NjbMediaItem : public MediaItem +{ + public: + NjbMediaItem( QListView *parent, QListViewItem *after = 0 ) : MediaItem( parent, after ) + {} + + NjbMediaItem( QListViewItem *parent, QListViewItem *after = 0 ) : MediaItem( parent, after ) + {} + + ~NjbMediaItem() + { + //m_track->removeItem(this); + } + + void setTrack( NjbTrack *track ) { m_track = track; m_track->addItem(this); } + NjbTrack *track() { return m_track; } + QString filename() { return m_track->bundle()->url().path(); } + private: + NjbTrack *m_track; +}; + +class NjbMediaDevice : public MediaDevice +{ + Q_OBJECT + + public: + NjbMediaDevice(); + + ~NjbMediaDevice(); + + virtual bool isConnected(); + virtual bool isPlayable(const MetaBundle& bundle); + virtual bool isPreferredFormat(const MetaBundle& bundle); + + // virtual bool needsManualConfig(); + + virtual MediaItem* newPlaylist(const QString& name, MediaItem* parent, QPtrList< MediaItem > items); + + // virtual MediaItem* tagsChanged(MediaItem* item, const MetaBundle& changed); + + virtual QStringList supportedFiletypes(); + + virtual bool hasTransferDialog() { return true; } + virtual TransferDialog* getTransferDialog(); + virtual void addConfigElements(QWidget* arg1); + virtual void addToDirectory(MediaItem* directory, QPtrList< MediaItem > items) { Q_UNUSED(directory) Q_UNUSED(items) } + virtual void addToPlaylist(MediaItem* playlist, MediaItem* after, QPtrList< MediaItem > items); + virtual void applyConfig(); + virtual void init(MediaBrowser* parent); + virtual void loadConfig(); + virtual void removeConfigElements(QWidget* arg1); + virtual void rmbPressed(QListViewItem* qitem, const QPoint& point, int arg1); + virtual void runTransferDialog(); + virtual void customClicked(); + + void setDeviceType(const QString& type); + void setSpacesToUnderscores(bool yesno); + static njb_t *theNjb(); + public slots: + void expandItem( QListViewItem *item ); + + protected: + + virtual bool closeDevice(); + virtual bool getCapacity(KIO::filesize_t* total, KIO::filesize_t* available); + + // virtual bool isSpecialItem(MediaItem* item); + + virtual bool lockDevice(bool tryOnly); + virtual bool openDevice(bool silent); + + int deleteFromDevice(unsigned id); + int deleteItemFromDevice(MediaItem* item, int flags=DeleteTrack ); + int deleteTrack(NjbMediaItem *trackItem); + + int downloadSelectedItems(); + int downloadToCollection(); + + virtual MediaItem* copyTrackToDevice(const MetaBundle& bundle); + virtual void copyTrackFromDevice(MediaItem *item); + + virtual void cancelTransfer(); + virtual void synchronizeDevice() {}; + + virtual void unlockDevice(); + + virtual void updateRootItems() {}; + + private: + // TODO: + MediaItem *trackExists( const MetaBundle& ); + // miscellaneous methods + static int progressCallback( u_int64_t sent, u_int64_t total, const char* /*buf*/, unsigned /*len*/, void* data); + + int readJukeboxMusic( void); + void clearItems(); + + NjbMediaItem *addTrackToView(NjbTrack *track, NjbMediaItem *item=0); + NjbMediaItem* addAlbums( const QString &artist, NjbMediaItem *item ); + NjbMediaItem* addTracks( const QString &artist, const QString &track, NjbMediaItem *item ); + NjbMediaItem* addTrack( NjbTrack *track ); + NjbMediaItem* addArtist( NjbTrack *track ); + TransferDialog *m_td; + + QListView *listAmarokPlayLists; + QString devNode; + QString m_errMsg; + bool m_connected; // Replaces m_captured from the original code. + + njb_t njbs[NJB_MAX_DEVICES]; + static njb_t* m_njb; + trackValueList trackList; + + int m_libcount; + bool m_busy; + unsigned m_progressStart; + QString m_progressMessage; + NjbMediaItem *m_artistItem; + NjbMediaItem *m_albumItem; + NjbMediaItem *m_allTracksItem; +}; + +#endif + diff --git a/amarok/src/mediadevice/njb/playlist.cpp b/amarok/src/mediadevice/njb/playlist.cpp new file mode 100644 index 00000000..dd2cd3bb --- /dev/null +++ b/amarok/src/mediadevice/njb/playlist.cpp @@ -0,0 +1,306 @@ +/*************************************************************************** + playlist.cpp - description + ------------------- + begin : 2001-07-24 + copyright : (C) 2001 by Shaun Jackman (sjackman@debian.org) + modify by: : Andres Oton + email : andres.oton@gmail.com + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + + +#include "debug.h" + + +// kionjb +#include "njbmediadevice.h" +#include "playlist.h" + +#define NJB_SUCCESS 0 +#define NJB_FAILURE -1 + + + +// KDE +#include +using namespace KIO; + +// POSIX +#include + +NjbPlaylist::NjbPlaylist() +{ + m_playlist = NJB_Playlist_New(); + if( !m_playlist) + debug() << "putPlaylist: playlist_new failed\n"; +} + +NjbPlaylist::NjbPlaylist(njb_playlist_t* playlist): + m_playlist(0) +{ + // debug() << __PRETTY_FUNCTION__ << " this=" << this << " playlist=" << playlist << endl; + setPlaylist( playlist ); +} + +NjbPlaylist::NjbPlaylist(const NjbPlaylist& _copy): + m_playlist(0) +{ + // debug() << __PRETTY_FUNCTION__ << " this=" << this << " m_playlist=" << m_playlist << " playlist=" << _copy.m_playlist << endl; + setPlaylist( _copy.m_playlist ); +} + +NjbPlaylist::~NjbPlaylist( void) +{ + // debug() << __PRETTY_FUNCTION__ << " this=" << this << " m_playlist=" << m_playlist << endl; + + if ( m_playlist ) + NJB_Playlist_Destroy( m_playlist); +} + +void +NjbPlaylist::operator=( const NjbPlaylist& _copy) +{ + // debug() << __PRETTY_FUNCTION__ << " this=" << this << " m_playlist " << m_playlist << " playlist=" << _copy.m_playlist << endl; + + setPlaylist( _copy.m_playlist ); +} + +void +NjbPlaylist::setPlaylist( njb_playlist_t* _newlist ) +{ + // debug() << __PRETTY_FUNCTION__ << " this=" << this << endl; + + // + // This function copys the new playlist BY VALUE over our current list. + // It's needed so that objects of this class can be stored in a QValueList + // + + // First, kill the existing list + if ( m_playlist ) + { + NJB_Playlist_Destroy( m_playlist); + } + + // Get a new one + m_playlist = NJB_Playlist_New(); + + // Set the name + NJB_Playlist_Set_Name( m_playlist, _newlist->name ); + + // Set the id + m_playlist->plid = _newlist->plid; + + // Iterate through the new list + NJB_Playlist_Reset_Gettrack( _newlist ); + njb_playlist_track_t* track = NJB_Playlist_Gettrack( _newlist ); + while ( track ) + { + // Add a new playlist track to our list + NJB_Playlist_Addtrack( m_playlist, NJB_Playlist_Track_New( track->trackid ), NJB_PL_END ); + + // And move on... + track = NJB_Playlist_Gettrack( _newlist ); + } + debug() << __PRETTY_FUNCTION__ << " OK" << endl; +} + +QString +NjbPlaylist::unescapefilename( const QString& _in ) +{ + QString result = _in; + + result.replace("%2f","/"); + + return result; +} + +QString +NjbPlaylist::escapefilename( const QString& _in ) +{ + QString result = _in; + + result.replace("/","%2f"); + + return result; +} + +int +NjbPlaylist::setName( const QString& fileName) +{ + QString playlistName = fileName; + if( fileName.right( 4) == ".m3u") + playlistName.truncate( playlistName.length() - 4); + + /* char** result; + int nrow, ncolumn; + char* errmsg; + sqlite_get_table_printf( m_kionjb->m_db, + "SELECT id FROM playlists WHERE name='%q'", + &result, &nrow, &ncolumn, &errmsg, + playlistName.latin1()); + if( errmsg) { + m_kionjb->warning( errmsg); + free( errmsg); + return ERR_COULD_NOT_READ; + } + + if( nrow) { + m_playlist->_state = NJB_PL_CHTRACKS; + m_playlist->plid = atoi( result[ 1]); + } else { + m_playlist->_state = NJB_PL_NEW; + m_playlist->plid = 0; + } + + if( playlist_set_name( m_playlist, playlistName) == -1) { + debug() << "putPlaylist: playlist_set_name failed\n"; + return ERR_COULD_NOT_WRITE; + }*/ + + if ( NJB_Playlist_Set_Name( m_playlist, unescapefilename(fileName).latin1() ) == NJB_FAILURE ) + { + debug() << __PRETTY_FUNCTION__ << ": NJB_Playlist_Set_Name failed\n"; + return ERR_COULD_NOT_WRITE; + } + + return NJB_SUCCESS; +} + + +int +NjbPlaylist::addTrack( const QString& fileName) +{ + debug() << __PRETTY_FUNCTION__ << " filename=" << fileName << endl; +/* + trackValueList::const_iterator it_track = theTracks->findTrackByName( fileName ); + if( it_track == theTracks->end() ) { + // couldn't find this track, skip it + debug() << "putPlaylist: couldn't find " << fileName << endl; + return NJB_FAILURE; + } + njb_playlist_track_t* pl_track = NJB_Playlist_Track_New( (*it_track)->id()); + if( !pl_track) { + debug() << "putPlaylist: playlist_track_new failed\n"; + return ERR_COULD_NOT_WRITE; + } + NJB_Playlist_Addtrack( m_playlist, pl_track, NJB_PL_END);*/ + return NJB_SUCCESS; +} + +void +playlist_dump( njb_playlist_t* playlist ) +{ + debug() << __PRETTY_FUNCTION__ << endl; + + debug() << "name: " << playlist->name << endl; + debug() << "state: " << playlist->_state << endl; + debug() << "ntracks: " << playlist->ntracks << endl; + debug() << "plid: " << playlist->plid << endl; + + NJB_Playlist_Reset_Gettrack( playlist ); + njb_playlist_track_t* track = NJB_Playlist_Gettrack( playlist ); + while ( track ) + { + debug() << "track: " << track->trackid << endl; + track = NJB_Playlist_Gettrack( playlist ); + } + debug() << __PRETTY_FUNCTION__ << " done" << endl; +} + + +int +NjbPlaylist::update( void) +{ + // debug() << "putPlaylist: state = " << m_playlist->_state << endl; + // debug() << "putPlaylist: id = " << m_playlist->plid << endl; + debug() << "putPlaylist: sending...\n"; + + playlist_dump( m_playlist ); + int status = NJB_Update_Playlist( NjbMediaDevice::theNjb(), m_playlist); + if( status == -1) { + debug() << "putPlaylist: NJB_Update_Playlist failed\n"; + if (NJB_Error_Pending(NjbMediaDevice::theNjb())) + { + const char* error; + while ((error = NJB_Error_Geterror(NjbMediaDevice::theNjb()))) + kdError( 7182) << __func__ << ": " << error << endl; + } + else + debug() << __func__ << ": No reason for failure reported.\n"; + return ERR_COULD_NOT_WRITE; + } + return NJB_SUCCESS; +} + +QStringList +NjbPlaylist::trackNames( void ) const +{ + QStringList result; +/* + // find tracks in trackList by their ID + MetaBundle bundle; + NjbTrack track; + njb_playlist_track_t *it_pltrack = m_playlist->first; + while ( it_pltrack) + { + trackValueList::const_iterator it_track = theTracks->findTrackById( it_pltrack->trackid ); + if ( it_track != theTracks->end() ) + { +// result += it_track.item().bundle()->title(); + } + it_pltrack = it_pltrack->next; + }*/ + + return result; +} + +bool +NjbPlaylist::operator==(const QString& name) const +{ + return escapefilename(m_playlist->name) == name; +} + +bool +NjbPlaylist::operator==(const NjbPlaylist& rval) const +{ + return getName() == rval.getName(); +} + +QString +NjbPlaylist::getName(void) const +{ + debug() << __PRETTY_FUNCTION__ << " this=" << this << " list=" << m_playlist << endl; + + return escapefilename(m_playlist->name); +} + +/* ------------------------------------------------------------------------ */ +/** Transfer playlists info from the njb to a local structure */ +int +playlistValueList::readFromDevice( void) +{ + + + // ONLY read from the device if this list is empty. + + int playlists = 0; + NJB_Reset_Get_Playlist( NjbMediaDevice::theNjb()); + while( njb_playlist_t* pl = NJB_Get_Playlist( NjbMediaDevice::theNjb()) ) { + // FIXME (acejones) Make this a signal + // infoMessage( i18n( "Downloading playlist %1...").arg( ++playlists)); + ++playlists; + append( NjbPlaylist(pl)); + NJB_Playlist_Destroy( pl); + } + + debug() << __func__ << ": cached " << playlists << " playlist(s)\n"; + return NJB_SUCCESS; +} diff --git a/amarok/src/mediadevice/njb/playlist.h b/amarok/src/mediadevice/njb/playlist.h new file mode 100644 index 00000000..d419cb60 --- /dev/null +++ b/amarok/src/mediadevice/njb/playlist.h @@ -0,0 +1,64 @@ +/*************************************************************************** + playlist.h - description + ------------------- + begin : 2001-07-24 + copyright : (C) 2001 by Shaun Jackman (sjackman@debian.org) + modify by: : Andres Oton + email : andres.oton@gmail.com + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef __playlist_h__ +#define __playlist_h__ + +// libnjb +#include + +// Qt +#include + + +class NjbPlaylist { + public: + NjbPlaylist(); + NjbPlaylist( njb_playlist_t* playlist); + NjbPlaylist(const NjbPlaylist& _copy); + ~NjbPlaylist( void); + + QStringList trackNames( void) const; + void operator=(const NjbPlaylist& _copy); + bool operator==(const QString& name) const; + bool operator==(const NjbPlaylist& rval) const; + QString getName(void) const; + + int setName( const QString& fileName); + int addTrack( const QString& fileName); + int update( void); + + u_int32_t getId(void) const { return m_playlist->plid; } + + protected: + void setPlaylist( njb_playlist_t* _newlist ); + + static QString unescapefilename( const QString& ); + static QString escapefilename( const QString& ); + + private: + njb_playlist_t* m_playlist; +}; + +class playlistValueList: public QValueList +{ + public: + int readFromDevice( void); +}; + +#endif diff --git a/amarok/src/mediadevice/njb/track.cpp b/amarok/src/mediadevice/njb/track.cpp new file mode 100644 index 00000000..e1545516 --- /dev/null +++ b/amarok/src/mediadevice/njb/track.cpp @@ -0,0 +1,284 @@ +/*************************************************************************** + track.cpp - description + ------------------- + begin : 2001-10-13 + copyright : (C) 2001 by Shaun Jackman (sjackman@debian.org) + modify by : Andres Oton + email : andres.oton@gmail.com + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +static const char* rcsid __attribute__((unused)) = +"$Id: track.cpp,v 1.10.2.8 2005/07/05 00:25:09 acejones Exp $"; + +#include "debug.h" +#include "track.h" +#include "njbmediadevice.h" + +#include +#include + +#include + + +/* ------------------------------------------------------------------------ */ +NjbTrack::NjbTrack( njb_songid_t* song) +{ + njb_songid_frame_t* frame; + + m_id = song->trid; + + MetaBundle *bundle = new MetaBundle(); + frame = NJB_Songid_Findframe( song, FR_SIZE ); + if ( frame->type == NJB_TYPE_UINT32 ) + bundle->setFilesize( frame->data.u_int32_val ); + else + { + bundle->setFilesize( 0 ); + error() << " Unexpected frame type:" << frame->type << endl; + } + + frame = NJB_Songid_Findframe( song, FR_LENGTH ); + if ( frame->type == NJB_TYPE_UINT16 ) + bundle->setLength( frame->data.u_int16_val ); + else + { + bundle->setLength( 0 ); + error() << " Unexpected frame type:" << frame->type << endl; + } + + frame = NJB_Songid_Findframe( song, FR_GENRE ); + if( frame ) + { + bundle->setGenre( AtomicString( frame->data.strval ) ); + } + + frame = NJB_Songid_Findframe( song, FR_ARTIST ); + if( frame ) + { + QString artist = QString::fromUtf8( frame->data.strval ); + artist.replace( QRegExp( "/" ), "-" ); + bundle->setArtist( artist ); + } + else + bundle->setArtist( i18n("Unknown artist") ); + + frame = NJB_Songid_Findframe( song, FR_ALBUM ); + if( frame) + { + QString album = QString::fromUtf8( frame->data.strval ); + album.replace( QRegExp( "/" ), "-" ); + bundle->setAlbum( album ); + } + else + bundle->setAlbum( i18n("Unknown album") ); + + frame = NJB_Songid_Findframe( song, FR_TITLE); + if( frame ) + { + QString title = QString::fromUtf8( frame->data.strval ); + title.replace( QRegExp( "/"), "-"); + bundle->setTitle( title ); + } + else + bundle->setTitle( i18n("Unknown title") ); + + frame = NJB_Songid_Findframe( song, FR_TRACK ); + if( frame ) + { + switch ( frame->type ) + { + case NJB_TYPE_UINT16: + bundle->setTrack( frame->data.u_int16_val ); + break; + case NJB_TYPE_UINT32: + bundle->setTrack( frame->data.u_int32_val ); + break; + case NJB_TYPE_STRING: + bundle->setTrack( QString::fromUtf8(frame->data.strval).toUInt() ); + break; + default: + bundle->setTrack( 0 ); + } + } + + QString codec; + frame = NJB_Songid_Findframe( song, FR_CODEC); + if( frame ) + { + codec = QCString( frame->data.strval).lower(); + if( codec == "mp3" ) + bundle->setFileType( MetaBundle::mp3 ); + else if (codec == "wma" ) + bundle->setFileType( MetaBundle::wma ); + else + bundle->setFileType( MetaBundle::other ); // It's a wav... + } + else + { + bundle->setFileType( MetaBundle::mp3 ); //Assumed... + codec = "mp3"; // Used for the next frame as a fallback + } + + frame = NJB_Songid_Findframe( song, FR_FNAME ); + QString filename; + if( frame ) + { + //bundle->setUrl( KURL( frame->data.strval ) ); + filename = QString::fromUtf8( frame->data.strval ); + + } + if( filename.isEmpty() ) + { + //bundle->setUrl( bundle->artist() + " - " + bundle->title() + '.' + codec ); + filename = bundle->artist() + " - " + bundle->title() + '.' + codec; + + } + bundle->setPath( filename ); + + frame = NJB_Songid_Findframe( song, FR_YEAR ); + if( frame ) + { + switch ( frame->type ) + { + case NJB_TYPE_UINT16: + bundle->setYear( frame->data.u_int16_val ); + break; + case NJB_TYPE_UINT32: + bundle->setYear( frame->data.u_int32_val ); + break; + case NJB_TYPE_STRING: + bundle->setYear( QString::fromUtf8( frame->data.strval ).toInt() ); + break; + default: + bundle->setYear( 0 ); + } + } + this->setBundle( *bundle ); +} + +NjbTrack::~NjbTrack() +{ + ItemList.setAutoDelete( true ); + while( ItemList.count() > 0 ) + { + delete ItemList.first(); + } +} + +void +NjbTrack::writeToSongid( njb_songid_t *songid ) +{ + NJB_Songid_Addframe( songid, NJB_Songid_Frame_New_Filename( m_bundle.filename().utf8() ) ); + NJB_Songid_Addframe( songid, NJB_Songid_Frame_New_Filesize( m_bundle.filesize() ) ); + NJB_Songid_Addframe( songid, NJB_Songid_Frame_New_Codec( "mp3" ) ); //for now + NJB_Songid_Addframe( songid, NJB_Songid_Frame_New_Title( m_bundle.title().utf8() ) ); + NJB_Songid_Addframe( songid, NJB_Songid_Frame_New_Album( m_bundle.album().ptr()->utf8() ) ); + NJB_Songid_Addframe( songid, NJB_Songid_Frame_New_Genre( m_bundle.genre().ptr()->utf8() ) ); + NJB_Songid_Addframe( songid, NJB_Songid_Frame_New_Artist( m_bundle.artist().ptr()->utf8() ) ); + NJB_Songid_Addframe( songid, NJB_Songid_Frame_New_Length( m_bundle.length() ) ); + NJB_Songid_Addframe( songid, NJB_Songid_Frame_New_Tracknum( m_bundle.track() ) ); + NJB_Songid_Addframe( songid, NJB_Songid_Frame_New_Year( m_bundle.year() ) ); +} + +njb_songid_t * +NjbTrack::newSongid() +{ + njb_songid_t *songid = new njb_songid_t; + writeToSongid( songid ); + return songid; +} + +void +NjbTrack::setBundle( MetaBundle &bundle ) +{ + if( bundle.title().isEmpty() ) + bundle.setTitle( i18n( "Unknown title" ) ); + if( bundle.artist().isEmpty() ) + bundle.setArtist( i18n( "Unknown artist" ) ); + if( bundle.album().isEmpty() ) + bundle.setAlbum( i18n( "Unknown album" ) ); + if( bundle.genre().isEmpty() ) + bundle.setGenre( i18n( "Unknown genre" ) ); + + m_bundle = bundle; +} + +void +NjbTrack::addItem( const NjbMediaItem *item ) +{ + ItemList.append( item ); +} + +bool +NjbTrack::removeItem( const NjbMediaItem *item ) +{ + ItemList.remove( item ); + debug() << "Removed item\n"; + return ItemList.isEmpty(); +} + +/* ------------------------------------------------------------------------ */ +trackValueList::iterator +trackValueList::findTrackByName( const QString& _filename ) +{ + trackValueList::iterator it; + for( it = begin(); it != end(); it++) + if( (*it)->bundle()->url().path() == _filename) + break; + return it; +} + +/* ------------------------------------------------------------------------ */ +trackValueList::iterator +trackValueList::findTrackById( unsigned _id ) +{ + trackValueList::iterator it; + for( it = begin(); it != end(); it++) + if( (*it)->id() == _id) + break; + return it; +} + +/* ------------------------------------------------------------------------ */ +trackValueList::const_iterator +trackValueList::findTrackById( unsigned _id ) const +{ + trackValueList::const_iterator it; + for( it = begin(); it != end(); it++) + if( (*it)->id() == _id) + break; + return it; +} + +/* ------------------------------------------------------------------------ */ +/** Transfer musical info from the njb to local structures */ +int +trackValueList::readFromDevice( void ) +{ + int i = 0; + + // Don't get extended metadatas + + NJB_Get_Extended_Tags(NjbMediaDevice::theNjb(), 0); + NJB_Reset_Get_Track_Tag( NjbMediaDevice::theNjb()); + while( njb_songid_t* song = NJB_Get_Track_Tag( NjbMediaDevice::theNjb())) + { + NjbTrack *track = new NjbTrack( song ); + append( track ); + NJB_Songid_Destroy( song ); + + ++i; + } + debug() << ": " << i << " jukebox tracks loaded from device." << endl; + + return NJB_SUCCESS; +} diff --git a/amarok/src/mediadevice/njb/track.h b/amarok/src/mediadevice/njb/track.h new file mode 100644 index 00000000..fc6db0d9 --- /dev/null +++ b/amarok/src/mediadevice/njb/track.h @@ -0,0 +1,72 @@ +/*************************************************************************** + track.h - description + ------------------- + begin : 2001-07-24 + copyright : (C) 2001 by Shaun Jackman (sjackman@debian.org) + modify by : Andres Oton + email : andres.oton@gmail.com + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef __track_h__ +#define __track_h__ + + +// qt +#include + +// libnjb +#include + +#include "metabundle.h" + +class NjbMediaItem; + +class NjbTrack { + friend class NjbMediaItem; + public: + NjbTrack() + : m_id( 0 ) + , m_bundle() + { } + NjbTrack( njb_songid_t* song ); + ~NjbTrack(); + + bool operator==( const NjbTrack& second ) const { return m_id == second.m_id; } + + public: + unsigned int id() const { return m_id; } + MetaBundle* bundle() { return new MetaBundle( m_bundle ); } + const MetaBundle & getBundle() { return m_bundle; } + void setBundle( MetaBundle &bundle ); + void addItem( const NjbMediaItem *item ); + bool removeItem( const NjbMediaItem * ); + void setId( int id ) { m_id = id; } + void writeToSongid( njb_songid_t *songid ); + njb_songid_t *newSongid(); + private: + unsigned int m_id; + MetaBundle m_bundle; + QPtrList ItemList; +}; + +class trackValueList: public QValueList +{ + public: + trackValueList::iterator findTrackByName( const QString& ); + trackValueList::const_iterator findTrackByName( const QString& ) const; + trackValueList::iterator findTrackById( unsigned ); + trackValueList::const_iterator findTrackById( unsigned ) const; + + int readFromDevice( void ); +}; + +#endif diff --git a/amarok/src/mediadevice/riokarma/Makefile.am b/amarok/src/mediadevice/riokarma/Makefile.am new file mode 100644 index 00000000..747fe1c1 --- /dev/null +++ b/amarok/src/mediadevice/riokarma/Makefile.am @@ -0,0 +1,30 @@ +kde_module_LTLIBRARIES = libamarok_riokarma-mediadevice.la +kde_services_DATA = amarok_riokarma-mediadevice.desktop + +INCLUDES = \ + -I$(top_srcdir)/amarok/src \ + -I$(top_builddir)/amarok/src \ + -I$(top_srcdir)/amarok/src/amarokcore \ + -I$(top_builddir)/amarok/src/amarokcore \ + -I$(top_srcdir)/amarok/src/engine \ + -I$(top_builddir)/amarok/src/engine \ + -I$(top_srcdir)/amarok/src/mediadevice \ + $(TAGLIB_CFLAGS) \ + $(all_includes) + +METASOURCES = AUTO + +libamarok_riokarma_mediadevice_la_LIBADD = \ + $(top_builddir)/amarok/src/libamarok.la \ + -lkarma $(LIB_QT) $(LIB_KDECORE) $(LIB_KDEUI) + +libamarok_riokarma_mediadevice_la_LDFLAGS = \ + $(KDE_PLUGIN) \ + $(all_libraries) + +libamarok_riokarma_mediadevice_la_SOURCES = \ + riokarmamediadevice.cpp + +noinst_HEADERS = \ + riokarmamediadevice.h + diff --git a/amarok/src/mediadevice/riokarma/amarok_riokarma-mediadevice.desktop b/amarok/src/mediadevice/riokarma/amarok_riokarma-mediadevice.desktop new file mode 100644 index 00000000..0f61041c --- /dev/null +++ b/amarok/src/mediadevice/riokarma/amarok_riokarma-mediadevice.desktop @@ -0,0 +1,109 @@ +[Desktop Entry] +Encoding=UTF-8 +Type=Service +Name=Rio Karma Media Device +Name[af]=Rio Karma media toestel +Name[bg]=Устройство Rio Karma +Name[bn]=রিও কর্মা মিডিয়া ডিভাইস +Name[ca]=Dispositiu de suports Rio Karma +Name[cs]=Mediální zařízení Rio Karma +Name[da]=Rio Karma-medieenhed +Name[de]=Rio Karma Media-Player +Name[el]=Συσκευή πολυμέσων Rio Karma +Name[eo]=Rio Karma Media Ekipaĵo +Name[es]=Dispositivo Rio Karma +Name[et]=Rio Karma meediaseade +Name[fa]=دستگاه رسانۀ Rio Karma +Name[fi]=Rio Karma -medialaite +Name[fr]=Périphérique de média Rio Karma +Name[ga]=Gléas Meán Rio Karma +Name[hu]=Rio Karma médiaeszköz +Name[is]=Rio Karma +Name[it]=Dispositivo multimediale Rio Karma +Name[ja]=Rio Karma メディアデバイス +Name[ka]=Rio Karma მედია მოწყობილობა +Name[km]=ឧបករណ៍​មេឌៀ Rio Karma +Name[lt]=Rio Karma daugialypės terpės įrenginys +Name[mk]=Уред за мултимедија Rio Karma +Name[ms]=Peranti Media Rio Karma +Name[nb]=Rio Karma medieenhet +Name[nds]=Medienreedschap Rio Karma +Name[ne]=रियो कर्म मिडिया यन्त्र +Name[nl]=Rio Karma media-apparaat +Name[nn]=Rio Karma-medieeining +Name[pa]=Rio ਕਰਮਾ ਮੀਡਿਆ ਜੰਤਰ +Name[pl]=Urządzenie mediów Rio Karma +Name[pt]=Dispositivo Multimédia Rio Karma +Name[pt_BR]=Dispositivo de Mídia Rio Karma +Name[se]=Rio Karma-mediaovttadat +Name[sk]=Rio Karma Media zariadenie +Name[sr]=Rio Karma, мултимедијални уређај +Name[sr@Latn]=Rio Karma, multimedijalni uređaj +Name[sv]=Rio Karma-mediaenhet +Name[th]=อุปกรณ์เล่นสื่อ Rio Karma +Name[tr]=Rio Karma Ortam Aygıtı +Name[uk]=Пристрій носія Rio Karma +Name[uz]=Rio Karma media-uskunasi +Name[uz@cyrillic]=Rio Karma медиа-ускунаси +Name[wa]=Édjin d' media Rio Karma +Name[zh_CN]=Rio Karma 媒体设备 +Name[zh_TW]=Rio Karma 媒體裝置 +X-KDE-Library=libamarok_riokarma-mediadevice +Comment=Plugin for Amarok +Comment[af]=Inprop module vir Amarok +Comment[ar]= قابس ( برنامج مضاف الى) AmaroK +Comment[bg]=Приставка за Amarok +Comment[bn]=আমারক-এর জন্য প্লাগিন +Comment[br]=Lugent evit Amarok +Comment[ca]=Connector per l'Amarok +Comment[cs]=Modul pro AmaroK +Comment[de]=Modul für Amarok +Comment[el]=Πρόσθετο για το AmaroK +Comment[eo]=Kromaĵo por Amarok +Comment[es]=Extensión para Amarok +Comment[et]=Amaroki plugin +Comment[fa]=وصله برای amaroK +Comment[fi]=Amarok-liitännäinen +Comment[fr]=Module pour Amarok +Comment[ga]=Breiseán AmaroK +Comment[gl]=Extensión para Amarok +Comment[hu]=Bővítőmodul az Amarokhoz +Comment[is]=Íforrit fyrir Amarok +Comment[it]=Plugin per Amarok +Comment[ja]=Amarok のためのプラグイン +Comment[ka]=მოდული Amarok-ისთვის +Comment[km]=កម្មវិធី​ជំនួយ​សម្រាប់ Amarok +Comment[lt]=Amarok įskiepis +Comment[mk]=Приклучок за Амарок +Comment[nb]=Programtillegg for Amarok +Comment[nds]=Moduul för Amarok +Comment[ne]=अमारोकका लागि प्लगइन +Comment[nl]=Plugin voor Amarok +Comment[nn]=Programtillegg for Amarok +Comment[pa]=ਅਮਰੋਕ ਲਈ ਪਲੱਗਇਨ +Comment[pl]=Wtyczka Amaroka +Comment[pt]='Plugin' para o Amarok +Comment[pt_BR]=Plugin para o Amarok +Comment[ru]=Модуль amaroK +Comment[se]=Lassemoduvla Amarok:ii +Comment[sk]=Amarok modul +Comment[sr]=Прикључак за Amarok +Comment[sr@Latn]=Priključak za Amarok +Comment[sv]=Insticksprogram för Amarok +Comment[th]=โปรแกรมเสริมสำหรับ Amarok +Comment[tr]=Amarok için Eklenti +Comment[uk]=Втулок для Amarok +Comment[uz]=Amarok uchun plagin +Comment[uz@cyrillic]=Amarok учун плагин +Comment[wa]=Tchôke-divins po Amarok +Comment[zh_CN]=Amarok 插件 +Comment[zh_TW]=amaroK 插件 +ServiceTypes=Amarok/Plugin + +X-KDE-Amarok-plugintype=mediadevice +X-KDE-Amarok-name=riokarma-mediadevice +X-KDE-Amarok-authors=Andy Kelk +X-KDE-Amarok-email=andy@mopoke.co.uk +X-KDE-Amarok-rank=100 +X-KDE-Amarok-version=1 +X-KDE-Amarok-framework-version=32 diff --git a/amarok/src/mediadevice/riokarma/riokarmamediadevice.cpp b/amarok/src/mediadevice/riokarma/riokarmamediadevice.cpp new file mode 100644 index 00000000..361da74c --- /dev/null +++ b/amarok/src/mediadevice/riokarma/riokarmamediadevice.cpp @@ -0,0 +1,610 @@ +/*************************************************************************** + * copyright : (C) 2006 Andy Kelk * + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + + /** + * Rio Karma media device + * @author Andy Kelk + * @see http://linux-karma.sourceforge.net/ + */ + +#define DEBUG_PREFIX "RioKarmaMediaDevice" + +#include +#include "riokarmamediadevice.h" + +AMAROK_EXPORT_PLUGIN( RioKarmaMediaDevice ) + +// Amarok +#include +#include +#include +#include + +// KDE +#include +#include +#include + +// Qt +#include +#include +#include + + +/** + * RioKarmaMediaDevice Class + */ + +RioKarmaMediaDevice::RioKarmaMediaDevice() : MediaDevice() +{ + m_name = "Rio Karma"; + setDisconnected(); + m_hasMountPoint = true; + m_syncStats = false; + m_transcode = false; + m_transcodeAlways = false; + m_transcodeRemove = false; + m_configure = false; + m_customButton = false; + m_transfer = true; +} + +void +RioKarmaMediaDevice::init( MediaBrowser *parent ) +{ + MediaDevice::init( parent ); +} + +bool +RioKarmaMediaDevice::isConnected() +{ + return m_rio >= 0 ? true : false; +} + +/** + * File types that we support + */ +QStringList +RioKarmaMediaDevice::supportedFiletypes() +{ + QStringList supportedFiles; + supportedFiles << "mp3"; + supportedFiles << "ogg"; + supportedFiles << "wma"; + supportedFiles << "flac"; + return supportedFiles; +} + +/** + * Copy a track to the device + */ +MediaItem +*RioKarmaMediaDevice::copyTrackToDevice( const MetaBundle &bundle ) +{ + DEBUG_BLOCK + + QString genericError = i18n( "Could not send track" ); + + if( m_fileNameToItem[ bundle.filename() ] != 0 ) + { + // track already exists. don't do anything (for now). + debug() << "Track already exists on device." << endl; + Amarok::StatusBar::instance()->shortLongMessage( + genericError, + i18n( "Track already exists on device" ), + KDE::StatusBar::Error + ); + return 0; + } + + int fid = lk_rio_write( m_rio, bundle.url().path().utf8() ); + + if( fid < 0 ) + { + debug() << "Could not write file" << fid << endl; + return 0; + } + + MetaBundle temp( bundle ); + RioKarmaTrack *taggedTrack = new RioKarmaTrack( fid ); + taggedTrack->setBundle( temp ); + + // cache the track + updateRootItems(); + return addTrackToView( taggedTrack ); +} + +/** + * Write any pending changes to the device, such as database changes + */ +void +RioKarmaMediaDevice::synchronizeDevice() +{ + DEBUG_BLOCK + int ret; + ret = lk_karma_write_smalldb(); + if( ret ) + debug() << "error writing smalldb file" << endl; +} + +/** + * Find an existing track + */ +MediaItem +*RioKarmaMediaDevice::trackExists( const MetaBundle &bundle ) +{ + MediaItem *artist = dynamic_cast( m_view->findItem( bundle.artist(), 0 ) ); + if( artist ) + { + MediaItem *album = dynamic_cast( artist->findItem( bundle.album() ) ); + if( album ) + { + MediaItem *track = dynamic_cast( album->findItem( bundle.title() ) ); + if( track ) + { + if( track->bundle()->track() == bundle.track() ) + return track; + } + } + } + return 0; +} + +/** + * Create a new playlist + * @note Playlists not implemented yet... :-) + */ +RioKarmaMediaItem +*RioKarmaMediaDevice::newPlaylist( const QString &name, MediaItem *parent, QPtrList items ) +{ + Q_UNUSED( name ); + Q_UNUSED( parent ); + Q_UNUSED( items ); + return 0; +} + +/** + * Add an item to a playlist + * @note Playlists not implemented yet... :-) + */ +void +RioKarmaMediaDevice::addToPlaylist( MediaItem *mlist, MediaItem *after, QPtrList items ) +{ + Q_UNUSED( mlist ); + Q_UNUSED( after ); + Q_UNUSED( items ); +} + +/** + * Recursively remove MediaItem from the device + */ +int +RioKarmaMediaDevice::deleteItemFromDevice(MediaItem* item, int flags ) +{ + + int result = 0; + if( isCanceled() ) + { + return -1; + } + MediaItem *next = 0; + + switch( item->type() ) + { + case MediaItem::TRACK: + if( isCanceled() ) + break; + if( item ) + { + int res = deleteRioTrack( dynamic_cast ( item ) ); + if( res >=0 && result >= 0 ) + result += res; + else + result = -1; + } + break; + case MediaItem::ALBUM: + case MediaItem::ARTIST: + // Recurse through the lists + next = 0; + + if( isCanceled() ) + break; + + for( MediaItem *it = dynamic_cast( item->firstChild() ); it ; it = next ) + { + next = dynamic_cast( it->nextSibling() ); + int res = deleteItemFromDevice( it, flags ); + if( res >= 0 && result >= 0 ) + result += res; + else + result = -1; + + } + if( item ) + delete dynamic_cast( item ); + break; + default: + result = 0; + } + return result; +} + +/** + * Actually delete a track from the Rio + */ +int +RioKarmaMediaDevice::deleteRioTrack( RioKarmaMediaItem *trackItem ) +{ + + DEBUG_BLOCK + + debug() << "delete this fid : " << trackItem->track()->id() << endl; + + // delete the file + int status = lk_karma_delete_file( m_rio, trackItem->track()->id() ); + if( status < 0 ) { + debug() << "delete track failed" << endl; + return -1; + } + debug() << "track deleted" << endl; + + // delete the properties (db entry) + status = lk_properties_del_property( trackItem->track()->id() ); + if( status < 0 ) { + debug() << "delete property failed" << endl; + return -1; + } + debug() << "property deleted" << endl; + + // remove from the listview + delete trackItem; + kapp->processEvents( 100 ); + + return 1; +} + +/** + * Connect to device, and populate m_view with MediaItems + */ +bool +RioKarmaMediaDevice::openDevice( bool silent ) +{ + DEBUG_BLOCK + + Q_UNUSED( silent ); + + QDir dir( mountPoint() ); + if( !dir.exists() ) + { + Amarok::StatusBar::instance()->longMessage( + i18n( "Media device: Mount point %1 does not exist" ).arg( mountPoint() ), + KDE::StatusBar::Error ); + return false; + } + + if( m_rio >= 0 ) + return true; + + QString genericError = i18n( "Could not connect to Rio Karma" ); + + char *mount = qstrdup( mountPoint().utf8() ); + m_rio = lk_karma_connect( mount ); + + debug() << "Rio karma : " << m_rio << endl; + + if( m_rio < 0 ) + { + debug()<< "Error connecting" << endl; + Amarok::StatusBar::instance()->shortLongMessage( genericError, i18n( "Rio Karma could not be opened" ), KDE::StatusBar::Error ); + setDisconnected(); + return false; + } + + lk_karma_use_smalldb(); + + lk_karma_write_dupes( 1 ); + + RioKarmaMediaDevice::readKarmaMusic(); + + return true; +} + +/** + * Wrap up any loose ends and close the device + */ +bool +RioKarmaMediaDevice::closeDevice() +{ + DEBUG_BLOCK + clearItems(); + setDisconnected(); + return true; +} + +/** + * Get the capacity and freespace available on the device, in KB + */ +bool +RioKarmaMediaDevice::getCapacity( KIO::filesize_t *total, KIO::filesize_t *available ) +{ + if( !isConnected() ) + return false; + + uint32_t numfiles; + uint64_t disksize; + uint64_t freespace; + uint32_t maxfileid; + + if( lk_karma_get_storage_details( m_rio, 0, &numfiles, &disksize, &freespace, &maxfileid ) == 0 ) + { + *total = disksize; + *available = freespace; + + return true; + } + else + { + return false; + } +} + +/** + * Current device ID (usually starts at 0) + */ +int +RioKarmaMediaDevice::current_id() +{ + return m_rio; +} + +/** + * We use -1 device ID to show a disconnected device. + * This just sets the device ID to that. + */ +void +RioKarmaMediaDevice::setDisconnected() +{ + m_rio = -1; +} + +/** + * Handle clicking of the right mouse button + */ +void +RioKarmaMediaDevice::rmbPressed( QListViewItem *qitem, const QPoint &point, int ) +{ + + enum Actions {DELETE}; + + RioKarmaMediaItem *item = static_cast( qitem ); + if( item ) + { + KPopupMenu menu( m_view ); + menu.insertItem( SmallIconSet( Amarok::icon( "remove" ) ), i18n( "Delete from device" ), DELETE ); + + int id = menu.exec( point ); + switch( id ) + { + case DELETE: + MediaDevice::deleteFromDevice(); + break; + } + return; + } + +} + +/** + * Add a track to the current list view + */ +RioKarmaMediaItem +*RioKarmaMediaDevice::addTrackToView( RioKarmaTrack *track, RioKarmaMediaItem *item ) +{ + QString artistName = track->bundle()->artist(); + + RioKarmaMediaItem *artist = dynamic_cast( m_view->findItem( artistName, 0 ) ); + if( !artist ) + { + artist = new RioKarmaMediaItem( m_view ); + artist->m_device = this; + artist->setText( 0, artistName ); + artist->setType( MediaItem::ARTIST ); + } + + QString albumName = track->bundle()->album(); + RioKarmaMediaItem *album = dynamic_cast( artist->findItem( albumName ) ); + if( !album ) + { + album = new RioKarmaMediaItem( artist ); + album->setText( 0, albumName ); + album->setType( MediaItem::ALBUM ); + album->m_device = this; + } + + if( item ) + album->insertItem( item ); + else + { + item = new RioKarmaMediaItem( album ); + item->m_device = this; + QString titleName = track->bundle()->title(); + item->setTrack( track ); + item->m_order = track->bundle()->track(); + item->setText( 0, titleName ); + item->setType( MediaItem::TRACK ); + item->setBundle( track->bundle() ); + item->track()->setId( track->id() ); + m_fileNameToItem[ track->bundle()->filename() ] = item; + } + return item; +} + +/** + * Get karma tracks and add them to the listview + */ +int +RioKarmaMediaDevice::readKarmaMusic() +{ + + DEBUG_BLOCK + + clearItems(); + + + QString genericError = i18n( "Could not get music from Rio Karma" ); + + int total = 100; + int progress = 0; + setProgress( progress, total ); // we don't know how many tracks. fake progress bar. + + kapp->processEvents( 100 ); + + lk_karma_load_database( m_rio ); + + int i; + uint32_t *ret; + + kapp->processEvents( 100 ); + + ret = lk_properties_andOrSearch( 0, 0, "fid", "" ); + + if( ret == 0 ) + { + debug()<< "Error reading tracks. NULL returned." << endl; + Amarok::StatusBar::instance()->shortLongMessage( genericError, i18n( "Could not read Rio Karma tracks" ), KDE::StatusBar::Error ); + setDisconnected(); + hideProgress(); + return -1; + } + + total = 0; + // spin through once to determine size of the list + for( i=0; ret[i] != 0; i++ ) + { + total++; + } + setProgress( progress, total ); + // now process the tracks + for( i=0; ret[i] != 0; i++ ) + { + // check playlist + if( qstrcmp( "playlist", lk_properties_get_property( ret[i], "type" ) ) == 0 ) + { + // nothing for now... + debug() << "Found a playlist at fid " << ret[i] << ". Skipping." << endl; + } + else + { + RioKarmaTrack *track = new RioKarmaTrack( ret[i] ); + track->readMetaData(); + addTrackToView( track ); + } + progress++; + setProgress( progress ); + if( progress % 50 == 0 ) + kapp->processEvents( 100 ); + } + setProgress( total ); + hideProgress(); + + return 0; +} + +/** + * Clear the current listview + */ +void +RioKarmaMediaDevice::clearItems() +{ + m_view->clear(); +} + +/** + * RioKarmaTrack Class + */ +RioKarmaTrack::RioKarmaTrack( int Fid ) +{ + m_id = Fid; +} + + +RioKarmaTrack::~RioKarmaTrack() +{ + m_itemList.setAutoDelete( true ); + while( m_itemList.count() > 0 ) + { + delete m_itemList.first(); + } +} + +/** + * Read track properties from the Karma and set it on the track + */ +void +RioKarmaTrack::readMetaData() +{ + MetaBundle *bundle = new MetaBundle(); + + bundle->setGenre( AtomicString( QString::fromUtf8( lk_properties_get_property( m_id, "genre" ) ) ) ); + bundle->setArtist( AtomicString( QString::fromUtf8( lk_properties_get_property( m_id, "artist" ) ) ) ); + bundle->setAlbum( AtomicString( QString::fromUtf8( lk_properties_get_property( m_id, "source" ) ) ) ); + bundle->setTitle( AtomicString( QString::fromUtf8( lk_properties_get_property( m_id, "title" ) ) ) ); + + // translate codecs to file types + QString codec = QCString( lk_properties_get_property( m_id, "codec" ) ); + if( codec == "mp3" ) + bundle->setFileType( MetaBundle::mp3 ); + else if( codec == "wma" ) + bundle->setFileType( MetaBundle::wma ); + else if( codec == "flac" ) + bundle->setFileType( MetaBundle::flac ); + else if( codec == "vorbis" ) + bundle->setFileType( MetaBundle::ogg ); + else + bundle->setFileType( MetaBundle::other ); + + bundle->setYear( QString( lk_properties_get_property( m_id, "year" ) ).toUInt() ); + bundle->setTrack( QString( lk_properties_get_property( m_id, "tracknr" ) ).toUInt() ); + bundle->setLength( QString( lk_properties_get_property( m_id, "duration" ) ).toUInt() ); + + this->setBundle( *bundle ); +} + +/** + * Set this track's metabundle + */ +void +RioKarmaTrack::setBundle( MetaBundle &bundle ) +{ + m_bundle = bundle; +} + +/** + * Add a child item + */ +void +RioKarmaTrack::addItem( const RioKarmaMediaItem *item ) +{ + m_itemList.append( item ); +} + +/** + * Remove a child item + */ +bool +RioKarmaTrack::removeItem( const RioKarmaMediaItem *item ) +{ + m_itemList.remove( item ); + return m_itemList.isEmpty(); +} diff --git a/amarok/src/mediadevice/riokarma/riokarmamediadevice.h b/amarok/src/mediadevice/riokarma/riokarmamediadevice.h new file mode 100644 index 00000000..237ab84e --- /dev/null +++ b/amarok/src/mediadevice/riokarma/riokarmamediadevice.h @@ -0,0 +1,116 @@ +/*************************************************************************** + * copyright : (C) 2006 Andy Kelk * + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + + /** + * Rio Karma media device + * @author Andy Kelk + * @see http://linux-karma.sourceforge.net/ + */ + +#ifndef AMAROK_RIOKARMAMEDIADEVICE_H +#define AMAROK_RIOKARMAMEDIADEVICE_H + +#include "mediabrowser.h" + +#include +#include + +#include "libkarma/lkarma.h" + +class RioKarmaMediaDevice; +class RioKarmaMediaItem; + +class RioKarmaTrack { + friend class MediaItem; + public: + RioKarmaTrack( int Fid ); + ~RioKarmaTrack(); + bool operator==( const RioKarmaTrack &second ) const { return m_id == second.m_id; } + + public: + unsigned int id() const { return m_id; } + MetaBundle *bundle() { return new MetaBundle( m_bundle ); } + void setBundle( MetaBundle &bundle ); + void setId( int id ) { m_id = id; } + void readMetaData(); + void addItem( const RioKarmaMediaItem *item ); + bool removeItem( const RioKarmaMediaItem *item ); + private: + unsigned int m_id; + MetaBundle m_bundle; + QPtrList m_itemList; +}; + + +class RioKarmaMediaItem : public MediaItem +{ + public: + RioKarmaMediaItem( QListView *parent, QListViewItem *after = 0 ) : MediaItem( parent, after ) + {} + RioKarmaMediaItem( QListViewItem *parent, QListViewItem *after = 0 ) : MediaItem( parent, after ) + {} + ~RioKarmaMediaItem() + { + //m_track->removeItem(this); + } + void setTrack( RioKarmaTrack *track ) { m_track = track; m_track->addItem( this ); } + RioKarmaTrack *track() { return m_track; } + QString filename() { return m_track->bundle()->url().path(); } + private: + RioKarmaTrack *m_track; +}; + +class RioKarmaMediaDevice : public MediaDevice +{ + Q_OBJECT + + public: + RioKarmaMediaDevice(); + virtual bool autoConnect() { return true; } + virtual bool asynchronousTransfer() { return false; } + bool isConnected(); + int current_id(); + void setDisconnected(); + virtual void rmbPressed( QListViewItem *qitem, const QPoint &point, int arg1 ); + virtual void init( MediaBrowser *parent ); + virtual QStringList supportedFiletypes(); + + protected: + MediaItem* trackExists( const MetaBundle &bundle ); + + bool openDevice( bool silent ); + bool closeDevice(); + bool lockDevice( bool tryLock=false ) { if( tryLock ) { return m_mutex.tryLock(); } else { m_mutex.lock(); return true; } } + void unlockDevice() { m_mutex.unlock(); } + + virtual MediaItem *copyTrackToDevice( const MetaBundle &bundle ); + + void synchronizeDevice(); + int deleteItemFromDevice( MediaItem *item, int flags=DeleteTrack ); + void addToPlaylist( MediaItem *list, MediaItem *after, QPtrList items ); + RioKarmaMediaItem *newPlaylist( const QString &name, MediaItem *list, QPtrList items ); + bool getCapacity( KIO::filesize_t *total, KIO::filesize_t *available ); + virtual void updateRootItems() {}; + + private: + RioKarmaMediaItem *addTrackToView( RioKarmaTrack *track, RioKarmaMediaItem *item=0 ); + int readKarmaMusic( void ); + void clearItems(); + int deleteRioTrack( RioKarmaMediaItem *trackItem ); + int m_rio; + QMutex m_mutex; + QMap m_fileNameToItem; + +}; + +#endif diff --git a/amarok/src/mediadevicemanager.cpp b/amarok/src/mediadevicemanager.cpp new file mode 100644 index 00000000..21bfb79a --- /dev/null +++ b/amarok/src/mediadevicemanager.cpp @@ -0,0 +1,174 @@ +// +// C++ Implementation: mediadevicemanager +// +// Description: Controls device/medium object handling, providing +// helper functions for other objects +// +// +// Author: Jeff Mitchell , (C) 2006 +// +// Copyright: See COPYING file that comes with this distribution +// +// + +#include "amarok.h" +#include "amarokconfig.h" +#include "debug.h" +#include "devicemanager.h" +#include "mediadevicemanager.h" +#include "medium.h" + +#include +#include + +#include +#include +#include + +typedef Medium::List MediumList; + +MediaDeviceManager* MediaDeviceManager::instance() +{ + static MediaDeviceManager dw; + return &dw; +} + +MediaDeviceManager::MediaDeviceManager() +{ + DEBUG_BLOCK + connect( DeviceManager::instance(), SIGNAL( mediumAdded( const Medium*, QString ) ), SLOT( slotMediumAdded( const Medium*, QString ) ) ); + connect( DeviceManager::instance(), SIGNAL( mediumChanged( const Medium*, QString ) ), SLOT( slotMediumChanged( const Medium*, QString ) ) ); + connect( DeviceManager::instance(), SIGNAL( mediumRemoved( const Medium*, QString ) ), SLOT( slotMediumRemoved( const Medium*, QString ) ) ); + Medium::List mediums = DeviceManager::instance()->getDeviceList(); + foreachType( Medium::List, mediums ) + { + slotMediumAdded( &(*it), (*it).id() ); + } + if( !mediums.count() ) + { + debug() << "DeviceManager didn't return any devices, we are probably running on a non-KDE system. Trying to reinit media devices later" << endl; + QTimer::singleShot( 4000, this, SLOT( reinitDevices() ) ); + } + //load manual devices + QStringList manualDevices; + KConfig *config = Amarok::config( "MediaBrowser" ); + QMap savedDevices = config->entryMap( "MediaBrowser" ); + QMap::Iterator qit; + QString curr, currMountPoint, currName; + for( qit = savedDevices.begin(); qit != savedDevices.end(); ++qit ) + { + //only handle manual devices, autodetected devices should be added on the fly + if( qit.key().startsWith( "manual|" ) ) + { + curr = qit.key(); + curr = curr.remove( "manual|" ); + currName = curr.left( curr.find( '|' ) ); + currMountPoint = curr.remove( currName + '|' ); + manualDevices.append( "false" ); //autodetected + manualDevices.append( qit.key() ); //id + manualDevices.append( currName ); //name + manualDevices.append( currName ); //label + manualDevices.append( QString::null ); //userLabel + manualDevices.append( "unknown" ); //mountable? + manualDevices.append( QString::null ); //device node + manualDevices.append( currMountPoint ); //mountPoint + manualDevices.append( "manual" ); //fsType + manualDevices.append( "unknown" ); //mounted + manualDevices.append( QString::null ); //baseURL + manualDevices.append( QString::null ); //MIMEtype + manualDevices.append( QString::null ); //iconName + manualDevices.append( "false" ); //encrypted + manualDevices.append( QString::null ); //clearDeviceUdi + manualDevices.append( "---" ); //separator + } + } + Medium::List manualMediums = Medium::createList( manualDevices ); + foreachType( Medium::List, manualMediums ) + { + slotMediumAdded( &(*it), (*it).id() ); + } +} + +MediaDeviceManager::~MediaDeviceManager() +{ +} + +void +MediaDeviceManager::addManualDevice( Medium* added ) +{ + m_mediumMap[added->name()] = added; + added->setFsType( "manual" ); + emit mediumAdded( added, added->name() ); +} + +void +MediaDeviceManager::removeManualDevice( Medium* removed ) +{ + emit mediumRemoved( removed, removed->name() ); + if( m_mediumMap.contains( removed->name() ) ) + m_mediumMap.remove( removed->name() ); +} + +void MediaDeviceManager::slotMediumAdded( const Medium *m, QString id) +{ + DEBUG_BLOCK + if ( m ) + { + if ( m->fsType() == "manual" || + ( !m->deviceNode().startsWith( "/dev/hd" ) && + (m->fsType() == "vfat" || m->fsType() == "hfsplus" || m->fsType() == "msdosfs" ) ) ) + // add other fsTypes that should be auto-detected here later + { + if ( m_mediumMap.contains( m->name() ) ) + { + Medium *tempMedium = m_mediumMap[m->name()]; + m_mediumMap.remove( m->name() ); + delete tempMedium; + } + m_mediumMap[m->name()] = new Medium( m ); + emit mediumAdded( m, id ); + } + } +} + +void MediaDeviceManager::slotMediumChanged( const Medium *m, QString id ) +{ + //nothing to do here + emit mediumChanged( m, id); +} + +void MediaDeviceManager::slotMediumRemoved( const Medium* , QString id ) +{ + DEBUG_BLOCK + Medium* removedMedium = 0; + if ( m_mediumMap.contains(id) ) + removedMedium = m_mediumMap[id]; + if ( removedMedium ) + debug() << "[MediaDeviceManager::slotMediumRemoved] Obtained medium name is " << id << ", id is: " << removedMedium->id() << endl; + else + debug() << "[MediaDeviceManager::slotMediumRemoved] Medium was unknown and is null; name was " << id << endl; + //if you get a null pointer from this signal, it means we did not know about the device + //before it was removed, i.e. the removal was the first event for the device received while amarok + //has been running + //There is no point in calling getDevice, since it will not be in the list anyways + emit mediumRemoved( removedMedium, id ); + if ( m_mediumMap.contains(id) ) + m_mediumMap.remove(id); + delete removedMedium; +} + +Medium* MediaDeviceManager::getDevice( QString name ) +{ + return DeviceManager::instance()->getDevice( name ); +} + +void MediaDeviceManager::reinitDevices( ) +{ + Medium::List mediums = DeviceManager::instance()->getDeviceList(); + foreachType( Medium::List, mediums ) + { + slotMediumAdded( &(*it), (*it).id() ); + } +} + +#include "mediadevicemanager.moc" diff --git a/amarok/src/mediadevicemanager.h b/amarok/src/mediadevicemanager.h new file mode 100644 index 00000000..4915b898 --- /dev/null +++ b/amarok/src/mediadevicemanager.h @@ -0,0 +1,66 @@ +// +// C++ Interface: mediadevicemanager +// +// Description: Controls device/medium object handling, providing +// helper functions for other objects +// +// +// Author: Jeff Mitchell , (C) 2006 +// +// Copyright: See COPYING file that comes with this distribution +// +// + + +#ifndef AMAROK_MEDIA_DEVICE_MANAGER_H +#define AMAROK_MEDIA_DEVICE_MANAGER_H + +#include "medium.h" + +#include + +#include + +typedef QMap MediumMap; + +class MediaDeviceManager : public QObject +{ + + //static const uint GENERIC = 0; + //static const uint APPLE = 1; + //static const uint IFP = 2; + + Q_OBJECT + public: + MediaDeviceManager(); + ~MediaDeviceManager(); + static MediaDeviceManager *instance(); + + Medium* getDevice( QString name ); + MediumMap getMediumMap() { return m_mediumMap; } + + void addManualDevice( Medium* added ); + void removeManualDevice( Medium* removed ); + + + signals: + void mediumAdded( const Medium*, QString ); + void mediumChanged( const Medium*, QString ); + void mediumRemoved( const Medium*, QString ); + + public slots: + void slotMediumAdded( const Medium*, QString ); + void slotMediumChanged( const Medium*, QString ); + void slotMediumRemoved( const Medium*, QString ); + + private slots: + void reinitDevices(); + + private: + + MediumMap m_mediumMap; + +}; + +#endif + diff --git a/amarok/src/medium.cpp b/amarok/src/medium.cpp new file mode 100644 index 00000000..cafb9b98 --- /dev/null +++ b/amarok/src/medium.cpp @@ -0,0 +1,273 @@ +/* This file is part of the KDE Project + Copyright (c) 2004 Kévin Ottens + + This library 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 library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "debug.h" +#include "medium.h" + +#include +#include + +const QString Medium::SEPARATOR = "---"; + +Medium::Medium(const QString &id, const QString &name) +{ + m_properties+= "false"; /* AUTODETECTED */ + m_properties+= id; /* ID */ + m_properties+= name; /* NAME */ + m_properties+= name; /* LABEL */ + m_properties+= QString::null; /* USER_LABEL */ + + m_properties+= "false"; /* MOUNTABLE */ + m_properties+= QString::null; /* DEVICE_NODE */ + m_properties+= QString::null; /* MOUNT_POINT */ + m_properties+= QString::null; /* FS_TYPE */ + m_properties+= "false"; /* MOUNTED */ + m_properties+= QString::null; /* BASE_URL */ + m_properties+= QString::null; /* MIME_TYPE */ + m_properties+= QString::null; /* ICON_NAME */ + m_properties+= "false"; /* ENCRYPTED */ + m_properties+= QString::null; /* CLEAR_DEVICE_UDI */ + + loadUserLabel(); +} + +Medium::Medium(const Medium *medium) +{ + m_properties += ( medium->isAutodetected() ? "true" : "false" ); + m_properties += medium->id(); + m_properties += medium->name(); + m_properties += medium->label(); + m_properties += medium->userLabel(); + m_properties += ( medium->isMountable() ? "true" : "false" ); + m_properties += medium->deviceNode(); + m_properties += medium->mountPoint(); + m_properties += medium->fsType(); + m_properties += ( medium->isMounted() ? "true" : "false" ); + m_properties += medium->baseURL(); + m_properties += medium->mimeType(); + m_properties += medium->iconName(); + loadUserLabel(); +} + +Medium::Medium() +{ + m_properties+= QString::null; /* AUTODETECTED */ + m_properties+= QString::null; /* ID */ + m_properties+= QString::null; /* NAME */ + m_properties+= QString::null; /* LABEL */ + m_properties+= QString::null; /* USER_LABEL */ + + m_properties+= QString::null; /* MOUNTABLE */ + m_properties+= QString::null; /* DEVICE_NODE */ + m_properties+= QString::null; /* MOUNT_POINT */ + m_properties+= QString::null; /* FS_TYPE */ + m_properties+= QString::null; /* MOUNTED */ + m_properties+= QString::null; /* BASE_URL */ + m_properties+= QString::null; /* MIME_TYPE */ + m_properties+= QString::null; /* ICON_NAME */ + m_properties+= QString::null; /* ENCRYPTED */ + m_properties+= QString::null; /* CLEAR_DEVICE_UDI */ +} + +const Medium Medium::create(const QStringList &properties) +{ + Medium m; + + if ( properties.size() >= PROPERTIES_COUNT ) + { + m.m_properties[AUTODETECTED] = properties[AUTODETECTED]; + m.m_properties[ID] = properties[ID]; + m.m_properties[NAME] = properties[NAME]; + m.m_properties[LABEL] = properties[LABEL]; + m.m_properties[USER_LABEL] = properties[USER_LABEL]; + + m.m_properties[MOUNTABLE] = properties[MOUNTABLE]; + m.m_properties[DEVICE_NODE] = properties[DEVICE_NODE]; + m.m_properties[MOUNT_POINT] = properties[MOUNT_POINT]; + m.m_properties[FS_TYPE] = properties[FS_TYPE]; + m.m_properties[MOUNTED] = properties[MOUNTED]; + m.m_properties[BASE_URL] = properties[BASE_URL]; + m.m_properties[MIME_TYPE] = properties[MIME_TYPE]; + m.m_properties[ICON_NAME] = properties[ICON_NAME]; + m.m_properties[ENCRYPTED] = properties[ENCRYPTED]; + m.m_properties[CLEAR_DEVICE_UDI] = properties[CLEAR_DEVICE_UDI]; + } + + return m; +} + +Medium::List Medium::createList(const QStringList &properties) +{ + List l; + if ( properties.size() % (PROPERTIES_COUNT+1) == 0 ) + { + int media_count = properties.size()/(PROPERTIES_COUNT+1); + + QStringList props = properties; + + for(int i=0; i + + This library 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 library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef _MEDIUM_H_ +#define _MEDIUM_H_ + +#include "amarok_export.h" + +#include +#include +#include + +class LIBAMAROK_EXPORT Medium +{ +public: + typedef QValueList List; + + static const uint AUTODETECTED = 0; + static const uint ID = 1; + static const uint NAME = 2; + static const uint LABEL = 3; + static const uint USER_LABEL = 4; + static const uint MOUNTABLE = 5; + static const uint DEVICE_NODE = 6; + static const uint MOUNT_POINT = 7; + static const uint FS_TYPE = 8; + static const uint MOUNTED = 9; + static const uint BASE_URL = 10; + static const uint MIME_TYPE = 11; + static const uint ICON_NAME = 12; + static const uint ENCRYPTED = 13; + static const uint CLEAR_DEVICE_UDI = 14; + static const uint PROPERTIES_COUNT = 15; + static const QString SEPARATOR; + + Medium(); + Medium(const Medium *medium); + Medium(const QString &id, const QString &name); + static const Medium create(const QStringList &properties); + static List createList(const QStringList &properties); + + const QStringList &properties() const { return m_properties; }; + + bool isAutodetected() const { return m_properties[AUTODETECTED]=="true"; }; + QString id() const { return m_properties[ID]; }; + QString name() const { return m_properties[NAME]; }; + QString label() const { return m_properties[LABEL]; }; + QString userLabel() const { return m_properties[USER_LABEL]; }; + bool isMountable() const { return m_properties[MOUNTABLE]=="true"; }; + QString deviceNode() const { return m_properties[DEVICE_NODE]; }; + QString mountPoint() const { return m_properties[MOUNT_POINT]; }; + QString fsType() const { return m_properties[FS_TYPE]; }; + bool isMounted() const { return m_properties[MOUNTED]=="true"; }; + QString baseURL() const { return m_properties[BASE_URL]; }; + QString mimeType() const { return m_properties[MIME_TYPE]; }; + QString iconName() const { return m_properties[ICON_NAME]; }; + bool isEncrypted() const { return m_properties[ENCRYPTED]=="true"; }; + QString clearDeviceUdi() const { return m_properties[CLEAR_DEVICE_UDI]; }; + + bool needMounting() const; + KURL prettyBaseURL() const; + QString prettyLabel() const; + + void setAutodetected(bool autodetected); + void setId(const QString &id); + void setMountPoint(const QString &mountPoint); + void setName(const QString &name); + void setLabel(const QString &label); + void setUserLabel(const QString &label); + void setFsType(const QString &type); + + bool mountableState(bool mounted); + void mountableState(const QString &deviceNode, + const QString &mountPoint, + const QString &fsType, bool mounted); + void unmountableState(const QString &baseURL = QString::null); + + void setMimeType(const QString &mimeType); + void setIconName(const QString &iconName); + +private: + void loadUserLabel(); + + QStringList m_properties; + +friend class QValueListNode; +}; + +#endif diff --git a/amarok/src/mediumpluginmanager.cpp b/amarok/src/mediumpluginmanager.cpp new file mode 100644 index 00000000..0e427121 --- /dev/null +++ b/amarok/src/mediumpluginmanager.cpp @@ -0,0 +1,523 @@ +// +// C++ Implementation: mediumpluginmanager +// +// Description: +// +// +// Authors: Jeff Mitchell , (C) 2005, 2006 +// Martin Aumueller , (C) 2006 +// +// Copyright: See COPYING file that comes with this distribution +// +// +#include "amarok.h" +#include "debug.h" +#include "deviceconfiguredialog.h" +#include "mediadevicemanager.h" +#include "devicemanager.h" +#include "hintlineedit.h" +#include "mediabrowser.h" +#include "medium.h" +#include "mediumpluginmanager.h" +#include "plugin/pluginconfig.h" +#include "pluginmanager.h" +#include "statusbar.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +using Amarok::escapeHTML; +using Amarok::escapeHTMLAttr; + +typedef QMap MediumMap; + +MediumPluginManagerDialog::MediumPluginManagerDialog() + : KDialogBase( Amarok::mainWindow(), "mediumpluginmanagerdialog", false, QString::null, Ok|Cancel, Ok ) +{ + kapp->setTopWidget( this ); + setCaption( kapp->makeStdCaption( i18n( "Manage Devices and Plugins" ) ) ); + + QVBox* vbox = makeVBoxMainWidget(); + vbox->setSpacing( KDialog::spacingHint() ); + vbox->setSizePolicy( QSizePolicy( QSizePolicy::Expanding, QSizePolicy::Expanding ) ); + + m_location = new QGroupBox( 1, Qt::Vertical, i18n( "Devices" ), vbox ); + m_location->setSizePolicy( QSizePolicy( QSizePolicy::Expanding, QSizePolicy::Preferred ) ); + m_devicesBox = new QVBox( m_location ); + m_devicesBox->setSizePolicy( QSizePolicy( QSizePolicy::Expanding, QSizePolicy::Expanding ) ); + + m_manager = new MediumPluginManager( m_devicesBox ); + + QHBox *hbox = new QHBox( vbox ); + KPushButton *detectDevices = new KPushButton( i18n( "Autodetect Devices" ), hbox); + detectDevices->setSizePolicy( QSizePolicy( QSizePolicy::Expanding, QSizePolicy::Fixed ) ); + connect( detectDevices, SIGNAL( clicked() ), m_manager, SLOT( redetectDevices() ) ); + + KPushButton *addButton = new KPushButton( i18n( "Add Device..." ), hbox ); + addButton->setSizePolicy( QSizePolicy( QSizePolicy::Expanding, QSizePolicy::Fixed ) ); + connect( addButton, SIGNAL( clicked() ), m_manager, SLOT( newDevice() ) ); +} + +MediumPluginManagerDialog::~MediumPluginManagerDialog() +{ + delete m_manager; +} + +void +MediumPluginManagerDialog::slotOk() +{ + m_manager->finished(); + KDialogBase::slotOk(); +} + +MediumPluginManager::MediumPluginManager( QWidget *widget, const bool nographics ) +: m_widget( widget ) +{ + detectDevices( false, nographics ); + + connect( this, SIGNAL( selectedPlugin( const Medium*, const QString ) ), MediaBrowser::instance(), SLOT( pluginSelected( const Medium*, const QString ) ) ); +} + +MediumPluginManager::~MediumPluginManager() +{ +} + +bool +MediumPluginManager::hasChanged() +{ + bool temp = m_hasChanged; + m_hasChanged = false; + return temp; +} + +void +MediumPluginManager::slotChanged()//slot +{ + m_hasChanged = true; + emit changed(); +} + +bool +MediumPluginManager::detectDevices( const bool redetect, const bool nographics ) +{ + bool foundNew = false; + KConfig *config = Amarok::config( "MediaBrowser" ); + if( redetect ) + DeviceManager::instance()->reconcileMediumMap(); + MediumMap mmap = MediaDeviceManager::instance()->getMediumMap(); + for( MediumMap::Iterator it = mmap.begin(); it != mmap.end(); it++ ) + { + if( !config->readEntry( (*it)->id() ).isEmpty() && + config->readEntry( (*it)->id() ) == "deleted" && !redetect) + { + debug() << "skipping: deleted" << endl; + continue; + } + + bool skipflag = false; + + for( DeviceList::Iterator dit = m_deviceList.begin(); + dit != m_deviceList.end(); + dit++ ) + { + if( (*it)->id() == (*dit)->medium()->id() ) + { + skipflag = true; + debug() << "skipping: already listed" << endl; + } + } + + if( m_deletedMap.contains( (*it)->id() ) && !(*it)->isAutodetected() ) + { + skipflag = true; + debug() << "skipping: deleted & not autodetect" << endl; + } + + if( skipflag ) + continue; + + if( m_deletedMap.contains( (*it)->id() ) ) + m_deletedMap.remove( (*it)->id() ); + + MediaDeviceConfig *dev = new MediaDeviceConfig( *it, this, nographics, m_widget ); + m_deviceList.append( dev ); + connect( dev, SIGNAL(deleteMedium(Medium *)), SLOT(deleteMedium(Medium *)) ); + + foundNew = true; + } + + return foundNew; +} + +void +MediumPluginManager::redetectDevices() +{ + if( !detectDevices( true ) ) + { + Amarok::StatusBar::instance()->longMessageThreadSafe( i18n("No new media devices were found. If you feel this is an\n" + "error, ensure that the DBUS and HAL daemons are running\n" + "and KDE was built with support for them. You can test this\n" + "by running\n" + " \"dcop kded mediamanager fullList\"\n" + "in a Konsole window.") ); + } + else + slotChanged(); +} + +void +MediumPluginManager::deleteMedium( Medium *medium ) +{ + for( DeviceList::Iterator it = m_deviceList.begin(); + it != m_deviceList.end(); + it++ ) + { + if( (*it)->medium() == medium ) + { + m_deletedMap[medium->id()] = medium; + m_deviceList.remove( *it ); + break; + } + } + slotChanged(); +} + +void +MediumPluginManager::finished() +{ + for( DeviceList::Iterator it = m_deviceList.begin(); + it != m_deviceList.end(); + it++ ) + { + if( (*it)->plugin() != (*it)->oldPlugin() ) + { + (*it)->setOldPlugin( (*it)->plugin() ); + emit selectedPlugin( (*it)->medium(), (*it)->plugin() ); + } + (*it)->configButton()->setEnabled( (*it)->pluginCombo()->currentText() != i18n( "Do not handle" ) ); + } + + KConfig *config = Amarok::config( "MediaBrowser" ); + for( DeletedMap::Iterator dit = m_deletedMap.begin(); + dit != m_deletedMap.end(); + ++dit ) + { + if( dit.data()->isAutodetected() ) + config->writeEntry( dit.data()->id(), "deleted" ); + else + config->deleteEntry( dit.data()->id() ); + MediaDeviceManager::instance()->removeManualDevice( dit.data() ); + } + m_deletedMap.clear(); +} + +void +MediumPluginManager::newDevice() +{ + DEBUG_BLOCK + ManualDeviceAdder* mda = new ManualDeviceAdder( this ); + if( mda->exec() == QDialog::Accepted && mda->successful() ) + { + if( !Amarok::config( "MediaBrowser" )->readEntry( mda->getMedium()->id() ).isNull() ) + { + //abort! Can't have the same device defined twice...should never + //happen due to name checking earlier...right? + Amarok::StatusBar::instance()->longMessageThreadSafe( i18n("Sorry, you cannot define two devices\n" + "with the same name and mountpoint!") ); + } + else + { + Medium *newdev = new Medium( mda->getMedium() ); + Amarok::config( "MediaBrowser" )->writeEntry( newdev->id(), mda->getPlugin() ); + MediaDeviceManager::instance()->addManualDevice( newdev ); + detectDevices(); + } + } + delete mda; + slotChanged(); +} + +///////////////////////////////////////////////////////////////////// + +ManualDeviceAdder::ManualDeviceAdder( MediumPluginManager* mpm ) +: KDialogBase( Amarok::mainWindow(), "manualdeviceadder", true, QString::null, Ok|Cancel, Ok ) +{ + m_mpm = mpm; + m_successful = false; + m_newMed = 0; + + kapp->setTopWidget( this ); + setCaption( kapp->makeStdCaption( i18n( "Add New Device") ) ); + + QHBox* hbox = makeHBoxMainWidget(); + hbox->setSpacing( KDialog::spacingHint() ); + + QVBox* vbox1 = new QVBox( hbox ); + + new QLabel( i18n( "Select the plugin to use with this device:"), vbox1 ); + m_mdaCombo = new KComboBox( false, vbox1, "m_mdacombo" ); + m_mdaCombo->insertItem( i18n( "Do not handle" ) ); + for( KTrader::OfferList::ConstIterator it = MediaBrowser::instance()->getPlugins().begin(); + it != MediaBrowser::instance()->getPlugins().end(); + ++it ) + m_mdaCombo->insertItem( (*it)->name() ); + + new QLabel( "", vbox1 ); + QLabel* nameLabel = new QLabel( vbox1 ); + nameLabel->setText( i18n( "Enter a &name for this device (required):" ) ); + m_mdaName = new HintLineEdit( QString::null, vbox1); + nameLabel->setBuddy( m_mdaName ); + m_mdaName->setHint( i18n( "Example: My_Ipod" ) ); + QToolTip::add( m_mdaName, i18n( "Enter a name for the device. The name must be unique across all devices, including autodetected devices. It must not contain the pipe ( | ) character." ) ); + + new QLabel( "", vbox1 ); + QLabel* mpLabel = new QLabel( vbox1 ); + mpLabel->setText( i18n( "Enter the &mount point of the device, if applicable:" ) ); + m_mdaMountPoint = new HintLineEdit( QString::null, vbox1); + mpLabel->setBuddy( m_mdaMountPoint ); + m_mdaMountPoint->setHint( i18n( "Example: /mnt/ipod" ) ); + QToolTip::add( m_mdaMountPoint, i18n( "Enter the device's mount point. Some devices (such as iRiver iFP devices) may not have a mount point and this can be ignored. All other devices (iPods, UMS/VFAT devices) should enter the mount point here." ) ); + + connect( m_mdaCombo, SIGNAL( activated(const QString&) ), this, SLOT( comboChanged(const QString&) ) ); +} + +ManualDeviceAdder::~ManualDeviceAdder() +{ + delete m_newMed; + delete m_mdaName; + delete m_mdaMountPoint; +} + +void +ManualDeviceAdder::slotCancel() +{ + KDialogBase::slotCancel( ); +} + +void +ManualDeviceAdder::slotOk() +{ + if( getMedium( true ) && !getMedium()->name().isEmpty() && + MediaDeviceManager::instance()->getDevice( getMedium()->name() ) == NULL ) + { + m_successful = true; + KDialogBase::slotOk( ); + } + else + { + Amarok::StatusBar::instance()->longMessageThreadSafe( i18n("Sorry, every device must have a name and\n" + "you cannot define two devices with the\n" + "same name. These names must be unique\n" + "across autodetected devices as well.\n") ); + } +} + +void +ManualDeviceAdder::comboChanged( const QString &string ) +{ + //best thing to do here would be to find out if the plugin selected + //has m_hasMountPoint set to false...but any way to do this + //without instantiating it? This way will suffice for now... + if( MediaBrowser::instance()->getInternalPluginName( string ) == "ifp-mediadevice" || + MediaBrowser::instance()->getInternalPluginName( string ) == "daap-mediadevice" || + MediaBrowser::instance()->getInternalPluginName( string ) == "mtp-mediadevice" || + MediaBrowser::instance()->getInternalPluginName( string ) == "njb-mediadevice" ) + { + m_comboOldText = m_mdaMountPoint->text(); + m_mdaMountPoint->setText( QString::null ); + m_mdaMountPoint->setEnabled(false); + } + else if( m_mdaMountPoint->isEnabled() == false ) + { + m_mdaMountPoint->setText( m_comboOldText ); + m_mdaMountPoint->setEnabled(true); + } + m_selectedPlugin = MediaBrowser::instance()->getInternalPluginName( string ); +} + +Medium* +ManualDeviceAdder::getMedium( bool recreate ) +{ + if( !recreate ) + return m_newMed; + + if( m_newMed && recreate ) + { + delete m_newMed; + m_newMed = 0; + } + + if( m_mdaMountPoint->isEnabled() == false && + m_mdaName->text().isNull() ) + return NULL; + if( m_mdaMountPoint->text().isNull() && + m_mdaName->text().isNull() ) + return NULL; + QString id = "manual|" + m_mdaName->text() + '|' + + ( m_mdaMountPoint->text().isNull() || + m_mdaMountPoint->isEnabled() == false ? + "(null)" : m_mdaMountPoint->text() ); + m_newMed = new Medium( id, m_mdaName->text() ); + m_newMed->setAutodetected( false ); + m_newMed->setMountPoint( m_mdaMountPoint->text() ); + return m_newMed; +} + +MediaDeviceConfig::MediaDeviceConfig( Medium *medium, MediumPluginManager *mgr, const bool nographics, QWidget *parent, const char *name ) +: QHBox( parent, name ) +, m_manager( mgr ) +, m_medium( medium ) +, m_configButton( 0 ) +, m_removeButton( 0 ) +, m_new( true ) +{ + if( !m_medium ) + return; + + KConfig *config = Amarok::config( "MediaBrowser" ); + m_oldPlugin = config->readEntry( m_medium->id() ); + if( !m_oldPlugin.isEmpty() ) + m_new = false; + + setSizePolicy( QSizePolicy( QSizePolicy::Expanding, QSizePolicy::Fixed ) ); + setSpacing( 5 ); + + const QString labelTextNone = i18n( "(none)" ); + QString row = "%1%2"; + QString table; + table += row.arg( escapeHTML( i18n( "Autodetected:" ) ), + escapeHTML( medium->isAutodetected() ? i18n("Yes") : i18n("No") ) ); + table += row.arg( escapeHTML( i18n( "ID:" ) ), + escapeHTML( medium->id() ) ); + table += row.arg( escapeHTML( i18n( "Name:" ) ), + escapeHTML( medium->name() ) ); + table += row.arg( escapeHTML( i18n( "Label:" ) ), + escapeHTML( medium->label().isEmpty() ? labelTextNone : medium->label() ) ); + table += row.arg( escapeHTML( i18n( "User Label:" ) ), + escapeHTML( medium->userLabel().isEmpty() ? labelTextNone : medium->userLabel() ) ); + table += row.arg( escapeHTML( i18n( "Device Node:" ) ), + escapeHTML( medium->deviceNode().isEmpty() ? labelTextNone : medium->deviceNode() ) ); + table += row.arg( escapeHTML( i18n( "Mount Point:" ) ), + escapeHTML( medium->mountPoint().isEmpty() ? labelTextNone : medium->mountPoint() ) ); + table += row.arg( escapeHTML( i18n( "Mime Type:" ) ), + escapeHTML( medium->mimeType().isEmpty() ? labelTextNone : medium->mimeType() ) ); + + QString title = escapeHTML( i18n( "Device information for %1").arg(medium->name() ) ); + QString details = QString( "%1
    " "%2
    " ).arg( title, table ); + + (void)new QLabel( i18n("Name: "), this ); + (void)new QLabel( medium->name(), this ); + (void)new KActiveLabel( i18n( "(Details)" ) + .arg( Amarok::escapeHTMLAttr( details ) ), this ); + + (void)new QLabel( i18n("Plugin:"), this ); + m_pluginCombo = new KComboBox( false, this ); + m_pluginCombo->insertItem( i18n( "Do not handle" ) ); + + for( KTrader::OfferList::ConstIterator it = MediaBrowser::instance()->getPlugins().begin(); + it != MediaBrowser::instance()->getPlugins().end(); + ++it ){ + m_pluginCombo->insertItem( (*it)->name() ); + if ( (*it)->property( "X-KDE-Amarok-name" ).toString() == config->readEntry( medium->id() ) ) + m_pluginCombo->setCurrentItem( (*it)->name() ); + } + + m_configButton = new KPushButton( SmallIconSet( Amarok::icon( "configure" ) ), QString::null, this ); + connect( m_configButton, SIGNAL(clicked()), SLOT(configureDevice()) ); + m_configButton->setEnabled( !m_new && m_pluginCombo->currentText() != i18n( "Do not handle" ) ); + QToolTip::add( m_configButton, i18n( "Configure device settings" ) ); + + m_removeButton = new KPushButton( i18n( "Remove" ), this ); + connect( m_removeButton, SIGNAL(clicked()), SLOT(deleteDevice()) ); + QToolTip::add( m_removeButton, i18n( "Remove entries corresponding to this device from configuration file" ) ); + + connect( m_pluginCombo, SIGNAL(activated(const QString&)), m_manager, SLOT(slotChanged()) ); + connect( this, SIGNAL(changed()), m_manager, SLOT(slotChanged()) ); + + if( !nographics ) + show(); +} + +MediaDeviceConfig::~MediaDeviceConfig() +{ +} + +bool +MediaDeviceConfig::isNew() +{ + return m_new; +} + +Medium * +MediaDeviceConfig::medium() +{ + return m_medium; +} + +QString +MediaDeviceConfig::plugin() +{ + return MediaBrowser::instance()->getInternalPluginName( m_pluginCombo->currentText() ); +} + +QString +MediaDeviceConfig::oldPlugin() +{ + return m_oldPlugin; +} + +void +MediaDeviceConfig::setOldPlugin( const QString &oldPlugin ) +{ + m_oldPlugin = oldPlugin; +} + +QButton * +MediaDeviceConfig::configButton() +{ + return m_configButton; +} + +QButton * +MediaDeviceConfig::removeButton() +{ + return m_removeButton; +} + +KComboBox * +MediaDeviceConfig::pluginCombo() +{ + return m_pluginCombo; +} + +void +MediaDeviceConfig::configureDevice() //slot +{ + DeviceConfigureDialog* dcd = new DeviceConfigureDialog( m_medium ); + dcd->exec(); + delete dcd; +} + +void +MediaDeviceConfig::deleteDevice() //slot +{ + //TODO:save something in amarokrc such that it's not shown again until hit autoscan + //m_deletedMap[medium->id()] = medium; + emit deleteMedium( medium() ); + delete this; +} + +#include "mediumpluginmanager.moc" diff --git a/amarok/src/mediumpluginmanager.h b/amarok/src/mediumpluginmanager.h new file mode 100644 index 00000000..bcbe03f5 --- /dev/null +++ b/amarok/src/mediumpluginmanager.h @@ -0,0 +1,160 @@ +// +// C++ Interface: mediumpluginmanager +// +// Description: +// +// +// Author: Jeff Mitchell , (C) 2005 +// +// Copyright: See COPYING file that comes with this distribution +// +// +#ifndef MEDIUMPLUGINMANAGER_H +#define MEDIUMPLUGINMANAGER_H + +#include "amarok.h" +#include "hintlineedit.h" +#include "plugin/pluginconfig.h" + +#include +#include +#include + +#include +#include +#include + +class QButton; +class QGroupBox; +class QLabel; +class QSignalMapper; +class QVBox; +class KComboBox; +class KLineEdit; +class Medium; +class MediumPluginManager; + +typedef QMap DeletedMap; + +/** + @author Jeff Mitchell + @author Martin Aumueller +*/ + +class MediaDeviceConfig : public QHBox +{ + Q_OBJECT + + public: + MediaDeviceConfig( Medium *medium, MediumPluginManager *mgr, const bool nographics=false, QWidget *parent=0, const char *name=0 ); + ~MediaDeviceConfig(); + QString oldPlugin(); + void setOldPlugin( const QString &oldPlugin ); + QString plugin(); + KComboBox *pluginCombo(); + QButton *configButton(); + QButton *removeButton(); + Medium *medium(); + bool isNew(); + + public slots: + void configureDevice(); + void deleteDevice(); + + signals: + void deleteMedium( Medium *medium ); + void changed(); + + protected: + MediumPluginManager *m_manager; + Medium *m_medium; + QString m_oldPlugin; + KComboBox * m_pluginCombo; + QButton *m_configButton; + QButton *m_removeButton; + bool m_new; +}; + +typedef QValueList DeviceList; + +class MediumPluginManager : public QObject +{ + Q_OBJECT + + friend class DeviceManager; + + public: + //nographics only for the initial run of detectDevices...pass in + //directly to detectDevices after + MediumPluginManager( QWidget *widget, const bool nographics=false ); + ~MediumPluginManager(); + void finished(); + bool hasChanged(); + + signals: + void selectedPlugin( const Medium*, const QString ); + void changed(); + + public slots: + void redetectDevices(); + void newDevice(); + void deleteMedium( Medium *medium ); + void slotChanged(); + + private: + bool detectDevices( bool redetect=false, bool nographics=false ); + DeletedMap m_deletedMap; + DeviceList m_deviceList; + QWidget *m_widget; + bool m_hasChanged; + +}; + +class MediumPluginManagerDialog : public KDialogBase +{ + Q_OBJECT + + public: + MediumPluginManagerDialog(); + ~MediumPluginManagerDialog(); + + private slots: + void slotOk(); + + private: + + QVBox *m_devicesBox; + QGroupBox *m_location; + MediumPluginManager *m_manager; +}; + +class ManualDeviceAdder : public KDialogBase +{ + Q_OBJECT + + public: + ManualDeviceAdder( MediumPluginManager* mdm ); + ~ManualDeviceAdder(); + bool successful() const { return m_successful; } + Medium* getMedium( bool recreate = false ); + QString getPlugin() const { return m_selectedPlugin; } + + private slots: + void slotCancel(); + void slotOk(); + void comboChanged( const QString & ); + + private: + MediumPluginManager* m_mpm; + bool m_successful; + QString m_comboOldText; + QString m_selectedPlugin; + Medium *m_newMed; + + KComboBox* m_mdaCombo; + HintLineEdit* m_mdaName; + HintLineEdit* m_mdaMountPoint; +}; + +#endif + diff --git a/amarok/src/metabundle.cpp b/amarok/src/metabundle.cpp new file mode 100644 index 00000000..00fd42c5 --- /dev/null +++ b/amarok/src/metabundle.cpp @@ -0,0 +1,1883 @@ +// Max Howell , (C) 2004 +// Alexandre Pereira de Oliveira , (C) 2005, 2006 +// Gábor Lehel , (C) 2005, 2006 +// Shane King , (C) 2006 +// Peter C. Ndikuwera , (C) 2006 +// License: GNU General Public License V2 + + +#define DEBUG_PREFIX "MetaBundle" + +#include +#include +#include +#include +#include +#include +#include + +#include "amarok.h" +#include "amarokconfig.h" +#include "debug.h" +#include "collectiondb.h" +#include "metabundlesaver.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include //decodePath() +#include +#include +#include //used to load genre list +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#ifdef HAVE_MP4V2 +#include "metadata/mp4/mp4file.h" +#include "metadata/mp4/mp4tag.h" +#else +#include "metadata/m4a/mp4file.h" +#include "metadata/m4a/mp4itunestag.h" +#endif + +#include "lastfm.h" +#include "metabundle.h" +#include "podcastbundle.h" + + +namespace Amarok { + KURL detachedKURL( const KURL &url ) { + KURL urlCopy; + if (!url.isEmpty()) + urlCopy = KURL(url.url()); + return urlCopy; + } +} + + +MetaBundle::EmbeddedImage::EmbeddedImage( const TagLib::ByteVector& data, const TagLib::String& description ) + : m_description( TStringToQString( description ) ) +{ + m_data.duplicate( data.data(), data.size() ); +} + +const QCString &MetaBundle::EmbeddedImage::hash() const +{ + if( m_hash.isEmpty() ) { + m_hash = KMD5( m_data ).hexDigest(); + } + return m_hash; +} + +bool MetaBundle::EmbeddedImage::save( const QDir& dir ) const +{ + QFile file( dir.filePath( hash() ) ); + + if( file.open( IO_WriteOnly | IO_Raw ) ) { + const Q_LONG s = file.writeBlock( m_data.data(), m_data.size() ); + if( s >= 0 && Q_ULONG( s ) == m_data.size() ) { + debug() << "EmbeddedImage::save " << file.name() << endl; + return true; + } + file.remove(); + } + debug() << "EmbeddedImage::save failed! " << file.name() << endl; + return false; +} + +/// These are untranslated and used for storing/retrieving XML playlist +const QString &MetaBundle::exactColumnName( int c ) //static +{ + // construct static qstrings to avoid constructing them all the time + static QString columns[] = { + "Filename", "Title", "Artist", "AlbumArtist", "Composer", "Year", "Album", "DiscNumber", "Track", "BPM", "Genre", "Comment", + "Directory", "Type", "Length", "Bitrate", "SampleRate", "Score", "Rating", "PlayCount", "LastPlayed", + "Mood", "Filesize" }; + static QString error( "ERROR" ); + + if ( c >= 0 && c < NUM_COLUMNS ) + return columns[c]; + else + return error; +} + +const QString MetaBundle::prettyColumnName( int index ) //static +{ + switch( index ) + { + case Filename: return i18n( "Filename" ); + case Title: return i18n( "Title" ); + case Artist: return i18n( "Artist" ); + case AlbumArtist:return i18n( "Album Artist"); + case Composer: return i18n( "Composer" ); + case Year: return i18n( "Year" ); + case Album: return i18n( "Album" ); + case DiscNumber: return i18n( "Disc Number" ); + case Track: return i18n( "Track" ); + case Bpm: return i18n( "BPM" ); + case Genre: return i18n( "Genre" ); + case Comment: return i18n( "Comment" ); + case Directory: return i18n( "Directory" ); + case Type: return i18n( "Type" ); + case Length: return i18n( "Length" ); + case Bitrate: return i18n( "Bitrate" ); + case SampleRate: return i18n( "Sample Rate" ); + case Score: return i18n( "Score" ); + case Rating: return i18n( "Rating" ); + case PlayCount: return i18n( "Play Count" ); + case LastPlayed: return i18n( "Column name", "Last Played" ); + case Mood: return i18n( "Mood" ); + case Filesize: return i18n( "File Size" ); + } + return "This is a bug."; +} + +int MetaBundle::columnIndex( const QString &name ) +{ + for( int i = 0; i < NUM_COLUMNS; ++i ) + if( exactColumnName( i ).lower() == name.lower() ) + return i; + return -1; +} + +MetaBundle::MetaBundle() + : m_uniqueId( QString::null ) + , m_year( Undetermined ) + , m_discNumber( Undetermined ) + , m_track( Undetermined ) + , m_bpm( Undetermined ) + , m_bitrate( Undetermined ) + , m_length( Undetermined ) + , m_sampleRate( Undetermined ) + , m_score( Undetermined ) + , m_rating( Undetermined ) + , m_playCount( Undetermined ) + , m_lastPlay( abs( Undetermined ) ) + , m_filesize( Undetermined ) + , m_moodbar( 0 ) + , m_type( other ) + , m_exists( true ) + , m_isValidMedia( true ) + , m_isCompilation( false ) + , m_notCompilation( false ) + , m_safeToSave( false ) + , m_waitingOnKIO( 0 ) + , m_tempSavePath( QString::null ) + , m_origRenamedSavePath( QString::null ) + , m_tempSaveDigest( 0 ) + , m_saveFileref( 0 ) + , m_podcastBundle( 0 ) + , m_lastFmBundle( 0 ) + , m_isSearchDirty(true) + , m_searchColumns( Undetermined ) +{ + init(); +} + +MetaBundle::MetaBundle( const KURL &url, bool noCache, TagLib::AudioProperties::ReadStyle readStyle, EmbeddedImageList* images ) + : m_url( url ) + , m_uniqueId( QString::null ) + , m_year( Undetermined ) + , m_discNumber( Undetermined ) + , m_track( Undetermined ) + , m_bpm( Undetermined ) + , m_bitrate( Undetermined ) + , m_length( Undetermined ) + , m_sampleRate( Undetermined ) + , m_score( Undetermined ) + , m_rating( Undetermined ) + , m_playCount( Undetermined ) + , m_lastPlay( abs( Undetermined ) ) + , m_filesize( Undetermined ) + , m_moodbar( 0 ) + , m_type( other ) + , m_exists( isFile() && QFile::exists( url.path() ) ) + , m_isValidMedia( false ) + , m_isCompilation( false ) + , m_notCompilation( false ) + , m_safeToSave( false ) + , m_waitingOnKIO( 0 ) + , m_tempSavePath( QString::null ) + , m_origRenamedSavePath( QString::null ) + , m_tempSaveDigest( 0 ) + , m_saveFileref( 0 ) + , m_podcastBundle( 0 ) + , m_lastFmBundle( 0 ) + , m_isSearchDirty(true) + , m_searchColumns( Undetermined ) +{ + if ( exists() ) + { + if ( !noCache ) + m_isValidMedia = CollectionDB::instance()->bundleForUrl( this ); + + if ( !isValidMedia() || ( !m_podcastBundle && m_length <= 0 ) ) + readTags( readStyle, images ); + } + else + { + // if it's a podcast we might get some info this way + CollectionDB::instance()->bundleForUrl( this ); + m_bitrate = m_length = m_sampleRate = Unavailable; + } +} + +//StreamProvider ctor +MetaBundle::MetaBundle( const QString& title, + const QString& streamUrl, + const int bitrate, + const QString& genre, + const QString& streamName, + const KURL& url ) + : m_url ( url ) + , m_genre ( genre ) + , m_streamName( streamName ) + , m_streamUrl ( streamUrl ) + , m_uniqueId( QString::null ) + , m_year( 0 ) + , m_discNumber( 0 ) + , m_track( 0 ) + , m_bpm( Undetermined ) + , m_bitrate( bitrate ) + , m_length( Irrelevant ) + , m_sampleRate( Unavailable ) + , m_score( Undetermined ) + , m_rating( Undetermined ) + , m_playCount( Undetermined ) + , m_lastPlay( abs( Undetermined ) ) + , m_filesize( Undetermined ) + , m_moodbar( 0 ) + , m_type( other ) + , m_exists( true ) + , m_isValidMedia( false ) + , m_isCompilation( false ) + , m_notCompilation( false ) + , m_safeToSave( false ) + , m_waitingOnKIO( 0 ) + , m_tempSavePath( QString::null ) + , m_origRenamedSavePath( QString::null ) + , m_tempSaveDigest( 0 ) + , m_saveFileref( 0 ) + , m_podcastBundle( 0 ) + , m_lastFmBundle( 0 ) + , m_isSearchDirty( true ) + , m_searchColumns( Undetermined ) +{ + if( title.contains( '-' ) ) + { + m_title = title.section( '-', 1, 1 ).stripWhiteSpace(); + m_artist = title.section( '-', 0, 0 ).stripWhiteSpace(); + } + else + { + m_title = title; + m_artist = streamName; //which is sort of correct.. + } +} + +MetaBundle::MetaBundle( const MetaBundle &bundle ) + : m_moodbar( 0 ) +{ + *this = bundle; +} + +MetaBundle::~MetaBundle() +{ + delete m_podcastBundle; + delete m_lastFmBundle; + + if( m_moodbar != 0 ) + delete m_moodbar; +} + +MetaBundle& +MetaBundle::operator=( const MetaBundle& bundle ) +{ + m_url = bundle.m_url; + m_title = bundle.m_title; + m_artist = bundle.m_artist; + m_albumArtist = bundle.m_albumArtist; + m_composer = bundle.m_composer; + m_album = bundle.m_album; + m_comment = bundle.m_comment; + m_genre = bundle.m_genre; + m_streamName = bundle.m_streamName; + m_streamUrl = bundle.m_streamUrl; + m_uniqueId = bundle.m_uniqueId; + m_year = bundle.m_year; + m_discNumber = bundle.m_discNumber; + m_track = bundle.m_track; + m_bpm = bundle.m_bpm; + m_bitrate = bundle.m_bitrate; + m_length = bundle.m_length; + m_sampleRate = bundle.m_sampleRate; + m_score = bundle.m_score; + m_rating = bundle.m_rating; + m_playCount = bundle.m_playCount; + m_lastPlay = bundle.m_lastPlay; + m_filesize = bundle.m_filesize; + m_type = bundle.m_type; + m_exists = bundle.m_exists; + m_isValidMedia = bundle.m_isValidMedia; + m_isCompilation = bundle.m_isCompilation; + m_notCompilation = bundle.m_notCompilation; + m_safeToSave = bundle.m_safeToSave; + m_waitingOnKIO = bundle.m_waitingOnKIO; + m_tempSavePath = bundle.m_tempSavePath; + m_origRenamedSavePath = bundle.m_origRenamedSavePath; + m_tempSaveDigest = bundle.m_tempSaveDigest; + m_saveFileref = bundle.m_saveFileref; + + if( bundle.m_moodbar != 0) + { + if( m_moodbar == 0 ) + m_moodbar = new Moodbar( this ); + *m_moodbar = *bundle.m_moodbar; + } + else + { + // If m_moodbar != 0, it's initialized for a reason + // Deleting it makes the PrettySlider code more ugly, + // since it'd have to reconnect the jobEvent() signal. + if( m_moodbar != 0 ) + m_moodbar->reset(); + } + + +// delete m_podcastBundle; why does this crash Amarok? apparently m_podcastBundle isn't always initialized. + m_podcastBundle = 0; + if( bundle.m_podcastBundle ) + setPodcastBundle( *bundle.m_podcastBundle ); + +// delete m_lastFmBundle; same as above + m_lastFmBundle = 0; + if( bundle.m_lastFmBundle ) + setLastFmBundle( *bundle.m_lastFmBundle ); + + m_isSearchDirty = true; + return *this; +} + + +bool +MetaBundle::checkExists() +{ + m_exists = !isFile() || QFile::exists( url().path() ); + + return m_exists; +} + +bool +MetaBundle::operator==( const MetaBundle& bundle ) const +{ + return uniqueId() == bundle.uniqueId() && //first, since if using IDs will return faster + artist() == bundle.artist() && + albumArtist() == bundle.albumArtist() && + title() == bundle.title() && + composer() == bundle.composer() && + album() == bundle.album() && + year() == bundle.year() && + comment() == bundle.comment() && + genre() == bundle.genre() && + track() == bundle.track() && + discNumber() == bundle.discNumber() && + bpm() == bundle.bpm() && + length() == bundle.length() && + bitrate() == bundle.bitrate() && + sampleRate() == bundle.sampleRate(); + // FIXME: check for size equality? +} + +void +MetaBundle::clear() +{ + *this = MetaBundle(); +} + +void +MetaBundle::init( TagLib::AudioProperties *ap ) +{ + if ( ap ) + { + m_bitrate = ap->bitrate(); + m_length = ap->length(); + m_sampleRate = ap->sampleRate(); + } + else + m_bitrate = m_length = m_sampleRate = Undetermined; +} + +void +MetaBundle::init( const KFileMetaInfo& info ) +{ + if( info.isValid() && !info.isEmpty() ) + { + m_artist = info.item( "Artist" ).string(); + m_album = info.item( "Album" ).string(); + m_comment = info.item( "Comment" ).string(); + m_genre = info.item( "Genre" ).string(); + m_year = info.item( "Year" ).string().toInt(); + m_track = info.item( "Track" ).string().toInt(); + m_bitrate = info.item( "Bitrate" ).value().toInt(); + m_length = info.item( "Length" ).value().toInt(); + m_sampleRate = info.item( "Sample Rate" ).value().toInt(); + + // For title, check if it is valid. If not, use prettyTitle. + // @see bug:83650 + const KFileMetaInfoItem itemtitle = info.item( "Title" ); + m_title = itemtitle.isValid() ? itemtitle.string() : prettyTitle( m_url.fileName() ); + + const KFileMetaInfoItem itemid = info.item( "Unique ID" ); + m_uniqueId = itemid.isValid() ? itemid.string() : QString::null; + + // because whoever designed KMetaInfoItem is a donkey + #define makeSane( x ) if( x == "---" ) x = null; + QString null; + makeSane( m_artist ); + makeSane( m_album ); + makeSane( m_comment ); + makeSane( m_genre ); + makeSane( m_title ); + #undef makeSane + + m_isValidMedia = true; + } + else + { + m_bitrate = m_length = m_sampleRate = m_filesize = Undetermined; + m_isValidMedia = false; + } +} + +void +MetaBundle::embeddedImages( MetaBundle::EmbeddedImageList& images ) const +{ + if ( isFile() ) + { + TagLib::FileRef fileref = TagLib::FileRef( QFile::encodeName( url().path() ), false ); + if ( !fileref.isNull() ) { + if ( TagLib::MPEG::File *file = dynamic_cast( fileref.file() ) ) { + if ( file->ID3v2Tag() ) + loadImagesFromTag( *file->ID3v2Tag(), images ); + } else if ( TagLib::FLAC::File *file = dynamic_cast( fileref.file() ) ) { + if ( file->ID3v2Tag() ) + loadImagesFromTag( *file->ID3v2Tag(), images ); + } else if ( TagLib::MP4::File *file = dynamic_cast( fileref.file() ) ) { + TagLib::MP4::Tag *mp4tag = dynamic_cast( file->tag() ); + if( mp4tag && mp4tag->cover().size() ) { + images.push_back( EmbeddedImage( mp4tag->cover(), "" ) ); + } + } + } + } +} + +void +MetaBundle::readTags( TagLib::AudioProperties::ReadStyle readStyle, EmbeddedImageList* images ) +{ + if( !isFile() ) + return; + + const QString path = url().path(); + + TagLib::FileRef fileref; + TagLib::Tag *tag = 0; + fileref = TagLib::FileRef( QFile::encodeName( path ), true, readStyle ); + + if( !fileref.isNull() ) + { + setUniqueId( readUniqueId( &fileref ) ); + m_filesize = QFile( path ).size(); + + tag = fileref.tag(); + if ( tag ) + { + #define strip( x ) TStringToQString( x ).stripWhiteSpace() + setTitle( strip( tag->title() ) ); + setArtist( strip( tag->artist() ) ); + setAlbum( strip( tag->album() ) ); + setComment( strip( tag->comment() ) ); + setGenre( strip( tag->genre() ) ); + setYear( tag->year() ); + setTrack( tag->track() ); + #undef strip + + m_isValidMedia = true; + } + + + /* As mpeg implementation on TagLib uses a Tag class that's not defined on the headers, + we have to cast the files, not the tags! */ + + QString disc; + QString compilation; + if ( TagLib::MPEG::File *file = dynamic_cast( fileref.file() ) ) + { + m_type = mp3; + if ( file->ID3v2Tag() ) + { + if ( !file->ID3v2Tag()->frameListMap()["TPOS"].isEmpty() ) + disc = TStringToQString( file->ID3v2Tag()->frameListMap()["TPOS"].front()->toString() ).stripWhiteSpace(); + + if ( !file->ID3v2Tag()->frameListMap()["TBPM"].isEmpty() ) + setBpm( TStringToQString( file->ID3v2Tag()->frameListMap()["TBPM"].front()->toString() ).stripWhiteSpace().toFloat() ); + + if ( !file->ID3v2Tag()->frameListMap()["TCOM"].isEmpty() ) + setComposer( TStringToQString( file->ID3v2Tag()->frameListMap()["TCOM"].front()->toString() ).stripWhiteSpace() ); + + if ( !file->ID3v2Tag()->frameListMap()["TPE2"].isEmpty() ) // non-standard: Apple, Microsoft + setAlbumArtist( TStringToQString( file->ID3v2Tag()->frameListMap()["TPE2"].front()->toString() ).stripWhiteSpace() ); + + if ( !file->ID3v2Tag()->frameListMap()["TCMP"].isEmpty() ) + compilation = TStringToQString( file->ID3v2Tag()->frameListMap()["TCMP"].front()->toString() ).stripWhiteSpace(); + + if(images) { + loadImagesFromTag( *file->ID3v2Tag(), *images ); + } + } + } + else if ( TagLib::Ogg::Vorbis::File *file = dynamic_cast( fileref.file() ) ) + { + m_type = ogg; + if ( file->tag() ) + { + if ( !file->tag()->fieldListMap()[ "COMPOSER" ].isEmpty() ) + setComposer( TStringToQString( file->tag()->fieldListMap()["COMPOSER"].front() ).stripWhiteSpace() ); + + if ( !file->tag()->fieldListMap()[ "BPM" ].isEmpty() ) + setBpm( TStringToQString( file->tag()->fieldListMap()["BPM"].front() ).stripWhiteSpace().toFloat() ); + + if ( !file->tag()->fieldListMap()[ "DISCNUMBER" ].isEmpty() ) + disc = TStringToQString( file->tag()->fieldListMap()["DISCNUMBER"].front() ).stripWhiteSpace(); + + if ( !file->tag()->fieldListMap()[ "COMPILATION" ].isEmpty() ) + compilation = TStringToQString( file->tag()->fieldListMap()["COMPILATION"].front() ).stripWhiteSpace(); + } + } + else if ( TagLib::FLAC::File *file = dynamic_cast( fileref.file() ) ) + { + m_type = flac; + if ( file->xiphComment() ) + { + if ( !file->xiphComment()->fieldListMap()[ "COMPOSER" ].isEmpty() ) + setComposer( TStringToQString( file->xiphComment()->fieldListMap()["COMPOSER"].front() ).stripWhiteSpace() ); + + if ( !file->xiphComment()->fieldListMap()[ "BPM" ].isEmpty() ) + setBpm( TStringToQString( file->xiphComment()->fieldListMap()["BPM"].front() ).stripWhiteSpace().toFloat() ); + + if ( !file->xiphComment()->fieldListMap()[ "DISCNUMBER" ].isEmpty() ) + disc = TStringToQString( file->xiphComment()->fieldListMap()["DISCNUMBER"].front() ).stripWhiteSpace(); + + if ( !file->xiphComment()->fieldListMap()[ "COMPILATION" ].isEmpty() ) + compilation = TStringToQString( file->xiphComment()->fieldListMap()["COMPILATION"].front() ).stripWhiteSpace(); + } + + if ( images && file->ID3v2Tag() ) { + loadImagesFromTag( *file->ID3v2Tag(), *images ); + } + } + else if ( TagLib::MP4::File *file = dynamic_cast( fileref.file() ) ) + { + m_type = mp4; + TagLib::MP4::Tag *mp4tag = dynamic_cast( file->tag() ); + if( mp4tag ) + { + setComposer( TStringToQString( mp4tag->composer() ) ); + setBpm( QString::number( mp4tag->bpm() ).toFloat() ); + disc = QString::number( mp4tag->disk() ); + compilation = QString::number( mp4tag->compilation() ); + if ( images && mp4tag->cover().size() ) { + images->push_back( EmbeddedImage( mp4tag->cover(), "" ) ); + } + } + } + + if ( !disc.isEmpty() ) + { + int i = disc.find ('/'); + if ( i != -1 ) + // disc.right( i ).toInt() is total number of discs, we don't use this at the moment + setDiscNumber( disc.left( i ).toInt() ); + else + setDiscNumber( disc.toInt() ); + } + + if ( compilation.isEmpty() ) { + // well, it wasn't set, but if the artist is VA assume it's a compilation + if ( artist().string() == i18n( "Various Artists" ) ) + setCompilation( CompilationYes ); + } else { + int i = compilation.toInt(); + if ( i == CompilationNo ) + setCompilation( CompilationNo ); + else if ( i == CompilationYes ) + setCompilation( CompilationYes ); + } + + init( fileref.audioProperties() ); + } + + //FIXME disabled for beta4 as it's simpler to not got 100 bug reports + //else if( KMimeType::findByUrl( m_url )->is( "audio" ) ) + // init( KFileMetaInfo( m_url, QString::null, KFileMetaInfo::Everything ) ); +} + +void MetaBundle::updateFilesize() +{ + if( !isFile() ) + { + m_filesize = Undetermined; + return; + } + + const QString path = url().path(); + m_filesize = QFile( path ).size(); +} + +float MetaBundle::score( bool ensureCached ) const +{ + if( m_score == Undetermined && !ensureCached ) + //const_cast is ugly, but other option was mutable, and then we lose const correctness checking + //everywhere else + *const_cast(&m_score) = CollectionDB::instance()->getSongPercentage( m_url.path() ); + return m_score; +} + +int MetaBundle::rating( bool ensureCached ) const +{ + if( m_rating == Undetermined && !ensureCached ) + *const_cast(&m_rating) = CollectionDB::instance()->getSongRating( m_url.path() ); + return m_rating; +} + +int MetaBundle::playCount( bool ensureCached ) const +{ + if( m_playCount == Undetermined && !ensureCached ) + *const_cast(&m_playCount) = CollectionDB::instance()->getPlayCount( m_url.path() ); + return m_playCount; +} + +uint MetaBundle::lastPlay( bool ensureCached ) const +{ + if( (int)m_lastPlay == abs(Undetermined) && !ensureCached ) + *const_cast(&m_lastPlay) = CollectionDB::instance()->getLastPlay( m_url.path() ).toTime_t(); + return m_lastPlay; +} + +void MetaBundle::copyFrom( const MetaBundle &bundle ) +{ + setTitle( bundle.title() ); + setArtist( bundle.artist() ); + setAlbumArtist( bundle.albumArtist() ); + setComposer( bundle.composer() ); + setAlbum( bundle.album() ); + setYear( bundle.year() ); + setDiscNumber( bundle.discNumber() ); + setBpm( bundle.bpm() ); + setComment( bundle.comment() ); + setGenre( bundle.genre() ); + setTrack( bundle.track() ); + setLength( bundle.length() ); + setBitrate( bundle.bitrate() ); + setSampleRate( bundle.sampleRate() ); + setScore( bundle.score() ); + setRating( bundle.rating() ); + setPlayCount( bundle.playCount() ); + setLastPlay( bundle.lastPlay() ); + setFileType( bundle.fileType() ); + setFilesize( bundle.filesize() ); + if( bundle.m_podcastBundle ) + setPodcastBundle( *bundle.m_podcastBundle ); + else + { + delete m_podcastBundle; + m_podcastBundle = 0; + } + + if( bundle.m_lastFmBundle ) + setLastFmBundle( *bundle.m_lastFmBundle ); + else + { + delete m_lastFmBundle; + m_lastFmBundle = 0; + } +} + +void MetaBundle::copyFrom( const PodcastEpisodeBundle &peb ) +{ + setPodcastBundle( peb ); + setTitle( peb.title() ); + setArtist( peb.author() ); + PodcastChannelBundle pcb; + if( CollectionDB::instance()->getPodcastChannelBundle( peb.parent(), &pcb ) ) + { + if( !pcb.title().isEmpty() ) + setAlbum( pcb.title() ); + } + setGenre( QString ( "Podcast" ) ); +} + +void MetaBundle::setExactText( int column, const QString &newText ) +{ + switch( column ) + { + case Title: setTitle( newText ); break; + case Artist: setArtist( newText ); break; + case AlbumArtist: setAlbumArtist( newText ); break; + case Composer: setComposer( newText ); break; + case Year: setYear( newText.toInt() ); break; + case Album: setAlbum( newText ); break; + case DiscNumber: setDiscNumber( newText.toInt() ); break; + case Track: setTrack( newText.toInt() ); break; + case Bpm: setBpm( newText.toFloat() ); break; + case Genre: setGenre( newText ); break; + case Comment: setComment( newText ); break; + case Length: setLength( newText.toInt() ); break; + case Bitrate: setBitrate( newText.toInt() ); break; + case SampleRate: setSampleRate( newText.toInt() ); break; + case Score: setScore( newText.toFloat() ); break; + case Rating: setRating( newText.toInt() ); break; + case PlayCount: setPlayCount( newText.toInt() ); break; + case LastPlayed: setLastPlay( newText.toInt() ); break; + case Filesize: setFilesize( newText.toInt() ); break; + case Type: setFileType( newText.toInt() ); break; + default: warning() << "Tried to set the text of an immutable or nonexistent column! [" << column << endl; + } +} + +QString MetaBundle::exactText( int column, bool ensureCached ) const +{ + switch( column ) + { + case Filename: return filename(); + case Title: return title(); + case Artist: return artist(); + case AlbumArtist: return albumArtist(); + case Composer: return composer(); + case Year: return QString::number( year() ); + case Album: return album(); + case DiscNumber: return QString::number( discNumber() ); + case Track: return QString::number( track() ); + case Bpm: return QString::number( bpm() ); + case Genre: return genre(); + case Comment: return comment(); + case Directory: return directory(); + case Type: return QString::number( fileType() ); + case Length: return QString::number( length() ); + case Bitrate: return QString::number( bitrate() ); + case SampleRate: return QString::number( sampleRate() ); + case Score: return QString::number( score( ensureCached ) ); + case Rating: return QString::number( rating( ensureCached ) ); + case PlayCount: return QString::number( playCount( ensureCached ) ); + case LastPlayed: return QString::number( lastPlay( ensureCached ) ); + case Filesize: return QString::number( filesize() ); + case Mood: return QString(); + default: warning() << "Tried to get the text of a nonexistent column! [" << column << endl; + } + + return QString(); //shouldn't happen +} + +QString MetaBundle::prettyText( int column ) const +{ + QString text; + switch( column ) + { + case Filename: text = isFile() ? MetaBundle::prettyTitle(filename()) : url().prettyURL(); break; + case Title: text = title().isEmpty() ? MetaBundle::prettyTitle( filename() ) : title(); break; + case Artist: text = artist(); break; + case AlbumArtist: text = albumArtist(); break; + case Composer: text = composer(); break; + case Year: text = year() ? QString::number( year() ) : QString::null; break; + case Album: text = album(); break; + case DiscNumber: text = discNumber() ? QString::number( discNumber() ) : QString::null; break; + case Bpm: text = bpm() ? QString::number( bpm() ) : QString::null; break; + case Track: text = track() ? QString::number( track() ) : QString::null; break; + case Genre: text = genre(); break; + case Comment: text = comment(); break; + case Directory: text = url().isEmpty() ? QString() : directory(); break; + case Type: text = url().isEmpty() ? QString() : type(); break; + case Length: text = prettyLength( length(), true ); break; + case Bitrate: text = prettyBitrate( bitrate() ); break; + case SampleRate: text = prettySampleRate(); break; + case Score: text = QString::number( static_cast( score() ) ); break; + case Rating: text = prettyRating(); break; + case PlayCount: text = QString::number( playCount() ); break; + case LastPlayed: text = Amarok::verboseTimeSince( lastPlay() ); break; + case Filesize: text = prettyFilesize(); break; + case Mood: + text = moodbar_const().state() == Moodbar::JobRunning ? i18n( "Calculating..." ) + : moodbar_const().state() == Moodbar::JobQueued ? i18n( "Queued..." ) + : QString::null; + break; + default: warning() << "Tried to get the text of a nonexistent column!" << endl; break; + } + + return text.stripWhiteSpace(); +} + +bool MetaBundle::matchesSimpleExpression( const QString &expression, const QValueList &columns ) const +{ + const QStringList terms = QStringList::split( ' ', expression.lower() ); + bool matches = true; + for( uint x = 0; matches && x < terms.count(); ++x ) + { + uint y = 0, n = columns.count(); + for(; y < n; ++y ) + if ( prettyText( columns[y] ).lower().contains( terms[x] ) ) + break; + matches = ( y < n ); + } + + return matches; +} + +void MetaBundle::reactToChanges( const QValueList& columns) +{ + // mark search dirty if we need to + for (uint i = 0; !m_isSearchDirty && i < columns.count(); i++) + if ((m_searchColumns & (1 << columns[i])) > 0) + m_isSearchDirty = true; +} + +bool MetaBundle::matchesFast(const QStringList &terms, ColumnMask columnMask) const +{ + // simple search for rating, last played, etc. makes no sense and it hurts us a + // lot if we have to fetch it from the db. so zero them out + columnMask &= ~( 1< 0) { + if (!m_searchStr.isEmpty()) m_searchStr += ' '; + m_searchStr += prettyText(i).lower(); + } + } + } + + // now search + for (uint i = 0; i < terms.count(); i++) { + if (!m_searchStr.contains(terms[i])) return false; + } + + return true; +} + + +bool MetaBundle::matchesExpression( const QString &expression, const QValueList &defaultColumns ) const +{ + return matchesParsedExpression( ExpressionParser::parse( expression ), defaultColumns ); +} + +bool MetaBundle::matchesParsedExpression( const ParsedExpression &data, const QValueList &defaults ) const +{ + for( uint i = 0, n = data.count(); i < n; ++i ) //check each part for matchiness + { + bool b = false; //whether at least one matches + for( uint ii = 0, count = data[i].count(); ii < count; ++ii ) + { + expression_element e = data[i][ii]; + int column = -1; + if( !e.field.isEmpty() ) + { + QString field = e.field.lower(); + column = columnIndex( field ); + if( column == -1 ) + { + column = -2; + if( field == "size" ) + field = "filesize"; + else if( field == "filetype" ) + field = "type"; + else if( field == "disc" ) + field = "discnumber"; + else + column = -1; + + if( column == -2 ) + column = columnIndex( field ); + } + } + if( column >= 0 ) //a field was specified and it exists + { + QString q = e.text, v = prettyText( column ).lower(), w = q.lower(); + //q = query, v = contents of the field, w = match against it + bool condition; //whether it matches, not taking e.negateation into account + + bool numeric; + switch( column ) + { + case Year: + case DiscNumber: + case Track: + case Bpm: + case Bitrate: + case SampleRate: + case Score: + case PlayCount: + case LastPlayed: + case Filesize: + numeric = true; + break; + default: + numeric = false; + } + + if( column == Filesize ) + { + v = QString::number( filesize() ); + if( w.endsWith( "m" ) ) + w = QString::number( w.left( w.length()-1 ).toLong() * 1024 * 1024 ); + else if( w.endsWith( "k" ) ) + w = QString::number( w.left( w.length()-1 ).toLong() * 1024 ); + } + + if( e.match == expression_element::More ) + { + if( numeric ) + condition = v.toInt() > w.toInt(); + else if( column == Rating ) + condition = v.toFloat() > w.toFloat(); + else if( column == Length ) + { + int g = v.find( ':' ), h = w.find( ':' ); + condition = v.left( g ).toInt() > w.left( h ).toInt() || + ( v.left( g ).toInt() == w.left( h ).toInt() && + v.mid( g + 1 ).toInt() > w.mid( h + 1 ).toInt() ); + } + else + condition = v > w; //compare the strings + } + else if( e.match == expression_element::Less ) + { + if( numeric ) + condition = v.toInt() < w.toInt(); + else if( column == Rating ) + condition = v.toFloat() < w.toFloat(); + else if( column == Length ) + { + int g = v.find( ':' ), h = w.find( ':' ); + condition = v.left( g ).toInt() < w.left( h ).toInt() || + ( v.left( g ).toInt() == w.left( h ).toInt() && + v.mid( g + 1 ).toInt() < w.mid( h + 1 ).toInt() ); + } + else + condition = v < w; + } + else + { + if( numeric ) + condition = v.toInt() == w.toInt(); + else if( column == Rating ) + condition = v.toFloat() == w.toFloat(); + else if( column == Length ) + { + int g = v.find( ':' ), h = w.find( ':' ); + condition = v.left( g ).toInt() == w.left( h ).toInt() && + v.mid( g + 1 ).toInt() == w.mid( h + 1 ).toInt(); + } + else + condition = v.contains( q, false ); + } + if( condition == ( e.negate ? false : true ) ) + { + b = true; + break; + } + } + else //check just the default fields + { + for( int it = 0, end = defaults.size(); it != end; ++it ) + { + b = prettyText( defaults[it] ).contains( e.text, false ) == ( e.negate ? false : true ); + if( ( e.negate && !b ) || ( !e.negate && b ) ) + break; + } + if( b ) + break; + } + } + if( !b ) + return false; + } + + return true; +} + +QString +MetaBundle::prettyTitle() const +{ + QString s = artist(); + + //NOTE this gets regressed often, please be careful! + // whatever you do, handle the stream case, streams have no artist but have an excellent title + + //FIXME doesn't work for resume playback + + if( s.isEmpty() ) + s = title(); + else + s = i18n("%1 - %2").arg( artist(), title() ); + + if( s.isEmpty() ) s = prettyTitle( filename() ); + + return s; +} + +QString +MetaBundle::veryNiceTitle() const +{ + QString s; + //NOTE I'm not sure, but the notes and FIXME's in the prettyTitle function should be fixed now. + // If not then they do apply to this function also! + if( !title().isEmpty() ) + { + if( !artist().isEmpty() ) + s = i18n( "%1 by %2" ).arg( title(), artist() ); + else + s = title(); + } + else + { + s = prettyTitle( filename() ); + } + return s; +} + +QString +MetaBundle::prettyTitle( const QString &filename ) //static +{ + QString s = filename; //just so the code is more readable + + //remove .part extension if it exists + if (s.endsWith( ".part" )) + s = s.left( s.length() - 5 ); + + //remove file extension, s/_/ /g and decode %2f-like sequences + s = s.left( s.findRev( '.' ) ).replace( '_', ' ' ); + s = KURL::decode_string( s ); + + return s; +} + +QString +MetaBundle::prettyLength( int seconds, bool showHours ) //static +{ + if( seconds > 0 ) return prettyTime( seconds, showHours ); + if( seconds == Undetermined ) return "?"; + if( seconds == Irrelevant ) return "-"; + + return QString(); //Unavailable = "" +} + +QString +MetaBundle::prettyTime( uint seconds, bool showHours ) //static +{ + QString s = QChar( ':' ); + s.append( zeroPad( seconds % 60 ) ); //seconds + seconds /= 60; + + if( showHours && seconds >= 60) + { + s.prepend( zeroPad( seconds % 60 ) ); //minutes + s.prepend( ':' ); + seconds /= 60; + } + + //don't zeroPad the last one, as it can be greater than 2 digits + s.prepend( QString::number( seconds ) ); //hours or minutes depending on above if block + + return s; +} + +QString +MetaBundle::veryPrettyTime( int time ) +{ + if( time == Undetermined ) + return i18n( "?" ); + if( time == Irrelevant ) + return i18n( "-" ); + + QStringList s; + s << QString::number( time % 60 ); //seconds + time /= 60; + if( time ) + s << QString::number( time % 60 ); //minutes + time /= 60; + if( time ) + s << QString::number( time % 24 ); //hours + time /= 24; + if( time ) + s << QString::number( time ); //days + + switch( s.count() ) + { + case 1: return i18n( "seconds", "%1s" ).arg( s[0] ); + case 2: return i18n( "minutes, seconds", "%2m %1s" ).arg( s[0], s[1] ); + case 3: return i18n( "hours, minutes, seconds", "%3h %2m %1s" ).arg( s[0], s[1], s[2] ); + case 4: return i18n( "days, hours, minutes, seconds", "%4d %3h %2m %1s" ).arg( s[0], s[1], s[2], s[3] ); + default: return "omg bug!"; + } +} + +QString +MetaBundle::fuzzyTime( int time ) +{ + QString s; + int secs=0, min=0, hr=0, day=0, week=0; + + if( time == Undetermined ) + return i18n( "?" ); + if( time == Irrelevant ) + return i18n( "-" ); + + secs = time % 60; //seconds + time /= 60; + min = time % 60; //minutes + time /= 60; + hr = time % 24 ; //hours + time /= 24; + day = time % 7 ; //days + time /= 7; + week = time; //weeks + + if( week && hr >= 12 ) + { + day++; + if( day == 7 ) + { + week++; + day = 0; + } + } + else if( day && min >= 30 ) + { + hr++; + if( hr == 24 ) + { + day++; + hr = 0; + } + } + else if( hr && secs >= 30 ) + { + min++; + if( min == 60 ) + { + hr++; + min = 0; + } + } + + QString weeks = i18n( "1 week %1", "%n weeks %1", week ); + QString days = i18n( "1 day %1", "%n days %1", day ); + QString hours = i18n( "1 hour", "%n hours", hr ); + + if( week ) + return weeks.arg( day ? days.arg("") : "" ).simplifyWhiteSpace(); + else if ( day ) + return days.arg( hr ? hours : "" ).simplifyWhiteSpace(); + else if ( hr ) + return i18n( "%1:%2 hours" ).arg( hr ).arg( zeroPad( min ) ); + else + return i18n( "%1:%2").arg( min ).arg( zeroPad( secs ) ); +} + +QString +MetaBundle::prettyBitrate( int i ) +{ + //the point here is to force sharing of these strings returned from prettyBitrate() + static const QString bitrateStore[9] = { + "?", "32", "64", "96", "128", "160", "192", "224", "256" }; + + return (i >=0 && i <= 256 && i % 32 == 0) + ? bitrateStore[ i / 32 ] + : prettyGeneric( "%1", i ); +} + +QString +MetaBundle::prettyFilesize( int s ) +{ + return KIO::convertSize( s ); +} + +QString +MetaBundle::prettyRating( int r, bool trailingzero ) //static +{ + if( trailingzero ) + return QString::number( float( r ) / 2, 'f', 1 ); + else + return r ? QString::number( float( r ) / 2 ) : QString(); +} + +QString +MetaBundle::ratingDescription( int r ) +{ + switch( r ) + { + case 1: return i18n( "Awful" ); + case 2: return i18n( "Bad" ); + case 3: return i18n( "Barely tolerable" ); + case 4: return i18n( "Tolerable" ); + case 5: return i18n( "Okay" ); + case 6: return i18n( "Good" ); + case 7: return i18n( "Very good" ); + case 8: return i18n( "Excellent" ); + case 9: return i18n( "Amazing" ); + case 10: return i18n( "Favorite" ); + case 0: default: return i18n( "Not rated" ); // assume weird values as not rated + } + return "if you can see this, then that's a bad sign."; +} + +QStringList +MetaBundle::ratingList() +{ + QString s = i18n( "rating - description", "%1 - %2" ); + QStringList list; + list += ratingDescription( 0 ); + for ( int i = 1; i<=10; i++ ) + list += s.arg( prettyRating( i, true ) ).arg( ratingDescription( i ) ); + return list; +} + +QStringList +MetaBundle::genreList() //static +{ + QStringList list; + + TagLib::StringList genres = TagLib::ID3v1::genreList(); + for( TagLib::StringList::ConstIterator it = genres.begin(), end = genres.end(); it != end; ++it ) + list += TStringToQString( (*it) ); + + list.sort(); + + return list; +} + +void +MetaBundle::setExtendedTag( TagLib::File *file, int tag, const QString value ) +{ + const char *id = 0; + + if ( m_type == mp3 ) + { + switch( tag ) + { + case ( composerTag ): id = "TCOM"; break; + case ( discNumberTag ): id = "TPOS"; break; + case ( bpmTag ): id = "TBPM"; break; + case ( compilationTag ): id = "TCMP"; break; + case ( albumArtistTag ): id = "TPE2"; break; // non-standard: Apple, Microsoft + } + fprintf(stderr, "Setting extended tag %s to %s\n", id, value.utf8().data()); + TagLib::MPEG::File *mpegFile = dynamic_cast( file ); + if ( mpegFile && mpegFile->ID3v2Tag() ) + { + if ( value.isEmpty() ) + mpegFile->ID3v2Tag()->removeFrames( id ); + else + { + if( !mpegFile->ID3v2Tag()->frameListMap()[id].isEmpty() ) + mpegFile->ID3v2Tag()->frameListMap()[id].front()->setText( QStringToTString( value ) ); + else + { + TagLib::ID3v2::TextIdentificationFrame *frame = new TagLib::ID3v2::TextIdentificationFrame( id, TagLib::ID3v2::FrameFactory::instance()->defaultTextEncoding() ); + frame->setText( QStringToTString( value ) ); + mpegFile->ID3v2Tag()->addFrame( frame ); + } + } + } + } + else if ( m_type == ogg ) + { + switch( tag ) + { + case ( composerTag ): id = "COMPOSER"; break; + case ( discNumberTag ): id = "DISCNUMBER"; break; + case ( bpmTag ): id = "BPM"; break; + case ( compilationTag ): id = "COMPILATION"; break; + case ( albumArtistTag ): id = "ALBUMARTIST"; break; // non-standard: Amarok + } + TagLib::Ogg::Vorbis::File *oggFile = dynamic_cast( file ); + if ( oggFile && oggFile->tag() ) + { + value.isEmpty() ? + oggFile->tag()->removeField( id ): + oggFile->tag()->addField( id, QStringToTString( value ), true ); + } + } + else if ( m_type == flac ) + { + switch( tag ) + { + case ( composerTag ): id = "COMPOSER"; break; + case ( discNumberTag ): id = "DISCNUMBER"; break; + case ( bpmTag ): id = "BPM"; break; + case ( compilationTag ): id = "COMPILATION"; break; + case ( albumArtistTag ): id = "ALBUMARTIST"; break; // non-standard: Amarok + } + TagLib::FLAC::File *flacFile = dynamic_cast( file ); + if ( flacFile && flacFile->xiphComment() ) + { + value.isEmpty() ? + flacFile->xiphComment()->removeField( id ): + flacFile->xiphComment()->addField( id, QStringToTString( value ), true ); + } + } + else if ( m_type == mp4 ) + { + TagLib::MP4::Tag *mp4tag = dynamic_cast( file->tag() ); + if( mp4tag ) + { + switch( tag ) + { + case ( composerTag ): mp4tag->setComposer( QStringToTString( value ) ); break; + case ( discNumberTag ): mp4tag->setDisk( value.toInt() ); + case ( bpmTag ): mp4tag->setBpm( value.toInt() ); // mp4 doesn't support float bpm + case ( compilationTag ): mp4tag->setCompilation( value.toInt() == CompilationYes ); + } + } + } +} + +void +MetaBundle::setPodcastBundle( const PodcastEpisodeBundle &peb ) +{ + delete m_podcastBundle; + m_podcastBundle = new PodcastEpisodeBundle; + *m_podcastBundle = peb; +} + +void +MetaBundle::setLastFmBundle( const LastFm::Bundle &last ) +{ + delete m_lastFmBundle; + // m_lastFmBundle = new LastFm::Bundle(last); + m_lastFmBundle = new LastFm::Bundle; + *m_lastFmBundle = last; +} + +void MetaBundle::loadImagesFromTag( const TagLib::ID3v2::Tag &tag, EmbeddedImageList& images ) const +{ + TagLib::ID3v2::FrameList l = tag.frameListMap()[ "APIC" ]; + foreachType( TagLib::ID3v2::FrameList, l ) { + debug() << "Found APIC frame" << endl; + TagLib::ID3v2::AttachedPictureFrame *ap = static_cast( *it ); + + const TagLib::ByteVector &imgVector = ap->picture(); + debug() << "Size of image: " << imgVector.size() << " byte" << endl; + // ignore APIC frames without picture and those with obviously bogus size + if( imgVector.size() > 0 && imgVector.size() < 10000000 /*10MB*/ ) { + images.push_back( EmbeddedImage( imgVector, ap->description() ) ); + } + } +} + +bool +MetaBundle::safeSave() +{ + bool noproblem; + MetaBundleSaver mbs( this ); + TagLib::FileRef* fileref = mbs.prepareToSave(); + if( !fileref ) + { + debug() << "Could not get a fileref!" << endl; + mbs.cleanupSave(); + return false; + } + + noproblem = save( fileref ); + + if( !noproblem ) + { + debug() << "MetaBundle::save() didn't work!" << endl; + mbs.cleanupSave(); + return false; + } + + noproblem = mbs.doSave(); + + if( !noproblem ) + { + debug() << "Something failed during the save, cleaning up and exiting!" << endl; + mbs.cleanupSave(); + return false; + } + + setUniqueId( readUniqueId() ); + if( CollectionDB::instance()->isFileInCollection( url().path() ) ) + CollectionDB::instance()->doAFTStuff( this, false ); + + noproblem = mbs.cleanupSave(); + + return noproblem; +} + +bool +MetaBundle::save( TagLib::FileRef* fileref ) +{ + DEBUG_BLOCK + if( !isFile() ) + return false; + + //Set default codec to UTF-8 (see bugs 111246 and 111232) + TagLib::ID3v2::FrameFactory::instance()->setDefaultTextEncoding(TagLib::String::UTF8); + + bool passedin = fileref; + bool returnval = false; + + TagLib::FileRef* f; + + if( !passedin ) + f = new TagLib::FileRef( QFile::encodeName( url().path() ), false ); + else + f = fileref; + + if ( f && !f->isNull() ) + { + TagLib::Tag * t = f->tag(); + if ( t ) { // f.tag() can return null if the file couldn't be opened for writing + t->setTitle( QStringToTString( title().stripWhiteSpace() ) ); + t->setArtist( QStringToTString( artist().string().stripWhiteSpace() ) ); + t->setAlbum( QStringToTString( album().string().stripWhiteSpace() ) ); + t->setTrack( track() ); + t->setYear( year() ); + t->setComment( QStringToTString( comment().string().stripWhiteSpace() ) ); + t->setGenre( QStringToTString( genre().string().stripWhiteSpace() ) ); + + if ( hasExtendedMetaInformation() ) + { + setExtendedTag( f->file(), albumArtistTag, albumArtist() ); + setExtendedTag( f->file(), composerTag, composer().string().stripWhiteSpace() ); + setExtendedTag( f->file(), discNumberTag, discNumber() ? QString::number( discNumber() ) : QString() ); + setExtendedTag( f->file(), bpmTag, bpm() ? QString::number( bpm() ) : QString() ); + if ( compilation() != CompilationUnknown ) + setExtendedTag( f->file(), compilationTag, QString::number( compilation() ) ); + } + if( !passedin ) + { + returnval = f->save(); + setUniqueId( readUniqueId() ); + if( returnval && CollectionDB::instance()->isFileInCollection( url().path() ) ) + CollectionDB::instance()->doAFTStuff( this, false ); + } + else + returnval = true; + } + } + if ( !passedin ) + delete f; + + return returnval; +} + +// QT's version encodeAttr is 1) private to QDom, and 2) a litte slow. This +// one can be made public if needed. It happens to be on a critical path +// (each char of playlist / undo save). QStyleSheet::escape does not deal with +// unicode chars illegal for XML. There's a lot of junk in those tags +static inline void xmlEncode(QTextStream &stream, const QString &str) +{ + QString tmp; + const QString *cur = &str; + uint i = 0; + while ( i < cur->length() ) + { + uint uc = (*cur)[i].unicode(); + // we try to accumulate unescaped chars before writing to stream + const char *escaped; + // careful about the order of tests, common before less common + if ( 'a' <= uc && uc <= 'z' || '0' <= uc && uc <= '9' || 'A' <= uc && uc <= 'Z' ) + // most common case + escaped = NULL; + else if ( uc == '<' ) escaped = "<"; + else if ( uc == '>' ) escaped = ">"; + else if ( uc == '&' ) escaped = "&"; + else if ( uc == '"' ) escaped = """; + else + { + // see if it's a XML-valid unicode char at all + if ( (0x20 <= uc && uc <= 0xD7FF || 0xE000 <= uc && uc <= 0xFFFD + || uc == 0x9 || uc == 0xA || uc == 0xD) ) + // fairly common, other ascii chars + escaped = NULL; + else + escaped = ""; // special char, will write later + } + if ( escaped ) + { + // flush previous unescaped chars + if ( i > 0 ) stream << cur->left(i); + tmp = cur->right(cur->length() - i - 1); + cur = &tmp; + i = 0; + // now write escaped string + if ( escaped[0] ) stream << escaped; + else stream << "&#x" << QString::number(uc, 16) << ';'; + } else i++; + } + + if (!cur->isEmpty()) stream << *cur; +} + +bool MetaBundle::save(QTextStream &stream, const QStringList &attributes) const +{ + // QDom is too slow to use here + stream << " \n"; // end of + for (int i = 0; i < NUM_COLUMNS; ++i) { + if ( i == Filename ) continue; // the file name is already in the URL + const QString &tag = exactColumnName( i ); + // 2 indents + stream << " <" << tag << ">"; + xmlEncode( stream, exactText( i, true ) ); + stream << "\n"; + } + stream << " \n"; + return true; +} + +void MetaBundle::setUrl( const KURL &url ) +{ + QValueList changes; + for( int i = 0; i < NUM_COLUMNS; ++i ) changes << i; + aboutToChange( changes ); m_url = url; reactToChanges( changes ); + + setUniqueId(); +} + +void MetaBundle::setPath( const QString &path ) +{ + QValueList changes; + for( int i = 0; i < NUM_COLUMNS; ++i ) changes << i; + aboutToChange( changes ); m_url.setPath( path ); reactToChanges( changes ); + + setUniqueId(); +} + +void MetaBundle::setUniqueId() +{ + if( !isFile() ) + return; + + m_uniqueId = CollectionDB::instance()->uniqueIdFromUrl( url() ); +} + +void MetaBundle::setUniqueId( const QString &id ) +{ + //WARNING WARNING WARNING + //Don't call this function if you don't know what you're doing! + m_uniqueId = id; +} + +const TagLib::ByteVector +MetaBundle::readUniqueIdHelper( TagLib::FileRef fileref ) const +{ + if ( TagLib::MPEG::File *file = dynamic_cast( fileref.file() ) ) + { + if( file->ID3v2Tag() ) + return file->ID3v2Tag()->render(); + else if( file->ID3v1Tag() ) + return file->ID3v1Tag()->render(); + else if( file->APETag() ) + return file->APETag()->render(); + } + else if ( TagLib::Ogg::Vorbis::File *file = dynamic_cast( fileref.file() ) ) + { + if( file->tag() ) + return file->tag()->render(); + } + else if ( TagLib::FLAC::File *file = dynamic_cast( fileref.file() ) ) + { + if( file->ID3v2Tag() ) + return file->ID3v2Tag()->render(); + else if( file->ID3v1Tag() ) + return file->ID3v1Tag()->render(); + else if( file->xiphComment() ) + return file->xiphComment()->render(); + } + else if ( TagLib::Ogg::FLAC::File *file = dynamic_cast( fileref.file() ) ) + { + if( file->tag() ) + return file->tag()->render(); + } + else if ( TagLib::MPC::File *file = dynamic_cast( fileref.file() ) ) + { + if( file->ID3v1Tag() ) + return file->ID3v1Tag()->render(); + else if( file->APETag() ) + return file->APETag()->render(); + } + TagLib::ByteVector bv; + return bv; +} + +QString +MetaBundle::readUniqueId( TagLib::FileRef* fileref ) +{ + //This is used in case we don't get given a fileref + TagLib::FileRef tmpfileref; + + if( !fileref && isFile() ) + { + const QString path = url().path(); + //Make it get cleaned up at the end of the function automagically + tmpfileref = TagLib::FileRef( QFile::encodeName( path ), true, TagLib::AudioProperties::Fast ); + fileref = &tmpfileref; + } + + if( !fileref || fileref->isNull() ) + return QString(); + + TagLib::ByteVector bv = readUniqueIdHelper( *fileref ); + + //get our unique id + KMD5 md5( 0, 0 ); + + QFile qfile( url().path() ); + + char databuf[8192]; + int readlen = 0; + QCString size = 0; + QString returnval; + + md5.update( bv.data(), bv.size() ); + + if( qfile.open( IO_Raw | IO_ReadOnly ) ) + { + if( ( readlen = qfile.readBlock( databuf, 8192 ) ) > 0 ) + { + md5.update( databuf, readlen ); + md5.update( size.setNum( (ulong)qfile.size() ) ); + return QString( md5.hexDigest().data() ); + } + else + return QString(); + } + + return QString::null; +} + +int +MetaBundle::getRand() +{ + //KRandom supposedly exists in SVN, although it's not checked out on my machine, and it's certainly not in 3.3, so I'm just going to steal its code + + unsigned int seed; + int fd = open("/dev/urandom", O_RDONLY); + if (fd < 0 || ::read(fd, &seed, sizeof(seed)) != sizeof(seed)) + { + // No /dev/urandom... try something else. + srand(getpid()); + seed = rand()+time(0); + } + if (fd >= 0) close(fd); + srand(seed); + return rand(); +} + +QString +MetaBundle::getRandomString( int size, bool numbersOnly ) +{ + if( size != 8 ) + { + debug() << "Wrong size passed in!" << endl; + return QString(); + } + + QString str; + //do a memory op once, much faster than doing multiple later, especially since we know how big it will be + str.reserve( size ); + int i = getRand(); //seed it + i = 0; + while (size--) + { + // check your ASCII tables + // we want characters you can see...93 is the range from ! to ~ + int r=rand() % 94; + // shift the value to the visible characters + r+=33; + // we don't want ", %, ', <, >, \, `, or & + // so that we don't have issues with escaping/quoting in QStrings, + // and so that we don't have <> in our XML files where they might cause issues + // hopefully this list is final, as once users really start using this + // it will be a pain to change...however, there is an ATF version in CollectionDB + // which will help if this ever needs to change + // In addition we can change our vendor string + while ( r==34 || r==37 || r == 38 || r==39 || r==60 ||r == 62 || r==92 || r==96 ) + r++; + + if( numbersOnly && ( r < 48 || r > 57 ) ) + { + size++; + continue; + } + + str[i++] = char(r); + // this next comment kept in for fun, as it was from the source of KRandomString, where I got + // most of this code from to start with :-) + // so what if I work backwards? + } + return str; +} + +void MetaBundle::setTitle( const QString &title ) +{ aboutToChange( Title ); m_title = title; reactToChange( Title ); } + +void MetaBundle::setArtist( const AtomicString &artist ) +{ aboutToChange( Artist ); m_artist = artist; reactToChange( Artist ); } + +void MetaBundle::setAlbum( const AtomicString &album ) +{ aboutToChange( Album ); m_album = album; reactToChange( Album ); } + +void MetaBundle::setComment( const AtomicString &comment ) +{ aboutToChange( Comment ); m_comment = comment; reactToChange( Comment ); } + +void MetaBundle::setGenre( const AtomicString &genre ) +{ aboutToChange( Genre ); m_genre = genre; reactToChange( Genre ); } + +void MetaBundle::setYear( int year) +{ aboutToChange( Year ); m_year = year; reactToChange( Year ); } + +void MetaBundle::setTrack( int track ) +{ aboutToChange( Track ); m_track = track; reactToChange( Track ); } + +void MetaBundle::setCompilation( int compilation ) +{ + switch( compilation ) + { + case CompilationYes: + m_isCompilation = true; + m_notCompilation = false; + break; + case CompilationNo: + m_isCompilation = false; + m_notCompilation = true; + break; + case CompilationUnknown: + m_isCompilation = m_notCompilation = false; + break; + } +} + +void MetaBundle::setLength( int length ) +{ aboutToChange( Length ); m_length = length; reactToChange( Length ); } + +void MetaBundle::setBitrate( int bitrate ) +{ aboutToChange( Bitrate ); m_bitrate = bitrate; reactToChange( Bitrate ); } + +void MetaBundle::setSampleRate( int sampleRate ) +{ aboutToChange( SampleRate ); m_sampleRate = sampleRate; reactToChange( SampleRate ); } + +void MetaBundle::setDiscNumber( int discnumber ) +{ aboutToChange( DiscNumber ); m_discNumber = discnumber; reactToChange( DiscNumber ); } + +void MetaBundle::setBpm( float bpm ) +{ aboutToChange( Bpm ); m_bpm = bpm; reactToChange( Bpm ); } + +void MetaBundle::setComposer( const AtomicString &composer ) +{ aboutToChange( Composer ); m_composer = composer; reactToChange( Composer ); } + +void MetaBundle::setAlbumArtist( const AtomicString &albumArtist ) +{ aboutToChange( AlbumArtist ); m_albumArtist = albumArtist; reactToChange( AlbumArtist ); } + +void MetaBundle::setPlayCount( int playcount ) +{ aboutToChange( PlayCount ); m_playCount = playcount; reactToChange( PlayCount ); } + +void MetaBundle::setLastPlay( uint lastplay ) +{ aboutToChange( LastPlayed ); m_lastPlay = lastplay; reactToChange( LastPlayed ); } + +void MetaBundle::setRating( int rating ) +{ aboutToChange( Rating ); m_rating = rating; reactToChange( Rating ); } + +void MetaBundle::setScore( float score ) +{ aboutToChange( Score ); m_score = score; reactToChange( Score ); } + +void MetaBundle::setFilesize( int bytes ) +{ aboutToChange( Filesize ); m_filesize = bytes; reactToChange( Filesize ); } + +void MetaBundle::setFileType( int type ) { m_type = type; } + +void MetaBundle::detach() +{ + // FIXME: we'd do that, but unfortunately it does not exist + //m_url.detach(); + m_url = Amarok::detachedKURL( m_url ); + + m_title = QDeepCopy(m_title); + m_artist = m_artist.deepCopy(); + m_albumArtist = m_albumArtist.deepCopy(); + m_album = m_album.deepCopy(); + m_comment = m_comment.deepCopy(); + m_composer = m_composer.deepCopy(); + m_genre = m_genre.deepCopy(); + m_streamName = QDeepCopy(m_streamName); + m_streamUrl = QDeepCopy(m_streamUrl); + + if( m_moodbar != 0 ) + m_moodbar->detach(); + + m_uniqueId = QDeepCopy( m_uniqueId ); + + if ( m_podcastBundle ) + setPodcastBundle( QDeepCopy( *m_podcastBundle ) ); + if ( m_lastFmBundle ) + setLastFmBundle( QDeepCopy( *m_lastFmBundle ) ); +} + + +void PodcastEpisodeBundle::detach() +{ + m_url = Amarok::detachedKURL( m_url ); + m_localUrl = Amarok::detachedKURL( m_localUrl ); + m_parent = Amarok::detachedKURL( m_parent ); + + m_author = QDeepCopy(m_author); + m_title = QDeepCopy(m_title); + m_subtitle = QDeepCopy(m_subtitle); + m_description = QDeepCopy(m_subtitle); + m_date = QDeepCopy(m_date); + m_type = QDeepCopy(m_type); + m_guid = QDeepCopy(m_guid); +} diff --git a/amarok/src/metabundle.h b/amarok/src/metabundle.h new file mode 100644 index 00000000..2bfc6f63 --- /dev/null +++ b/amarok/src/metabundle.h @@ -0,0 +1,545 @@ +// Max Howell , (C) 2004 +// Alexandre Pereira de Oliveira , (C) 2005 +// Shane King , (C) 2006 +// Peter C. Ndikuwera , (C) 2006 +// License: GNU General Public License V2 + +#ifndef METABUNDLE_H +#define METABUNDLE_H + +#if __GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 3) +#define PRETTY_TITLE_CACHE +#endif + +#include +#include //inline functions +#include //inline functions +#include +#include "expression.h" +#include "atomicstring.h" +#include "moodbar.h" + +#include "amarok_export.h" + +class KFileMetaInfo; +class QDir; +class QTextStream; +template class QValueList; +namespace TagLib { + class ByteVector; + class File; + class FileRef; + class String; + namespace ID3v2 { + class UniqueFileIdentifierFrame; + class Tag; + } + namespace MPEG { + class File; + } +} +class PodcastEpisodeBundle; + +namespace LastFm { + class Bundle; +} + +/** + * @class MetaBundle + * @author Max Howell + * + * If this class doesn't work for you in some way, extend it sensibly :) + * + */ + +class LIBAMAROK_EXPORT MetaBundle +{ + +public: + enum Column + { + Filename = 0, + Title, + Artist, + AlbumArtist, + Composer, + Year, + Album, + DiscNumber, + Track, + Bpm, + Genre, + Comment, + Directory, + Type, + Length, + Bitrate, + SampleRate, + Score, + Rating, + PlayCount, + LastPlayed, + Mood, + Filesize, + NUM_COLUMNS + }; + + class LIBAMAROK_EXPORT EmbeddedImage { + public: + EmbeddedImage() {} + EmbeddedImage( const TagLib::ByteVector& data, const TagLib::String& description ); + + const QCString &hash() const; + const QString &description() const { return m_description; } + bool save( const QDir& dir ) const; + + private: + QByteArray m_data; + QString m_description; + mutable QCString m_hash; + }; + + typedef QValueList EmbeddedImageList; + + /** This is a bit vector for selecting columns. It's very fast to compare + in matchFast. It might be a good idea to replace the QValue + column masks with this eventually. */ + typedef Q_UINT32 ColumnMask; + + /** Returns the name of the column at \p index as a string -- not i18ned, for internal purposes. */ + static const QString &exactColumnName( int index ); + /** Returns the name of the column at \p index as a string -- i18ned, for display purposes. */ + static const QString prettyColumnName( int index ); + /** Returns the index of the column with the not i18ned name \p name. */ + static int columnIndex( const QString &name ); + + // These values are stored on the Database, so, don't change the order. Only append new ones to the end. + enum FileType { other, mp3, ogg, wma, mp4, flac, ra, rv, rm, rmj, rmvb, asf }; + + //for the audioproperties + static const int Undetermined = -2; /// we haven't yet read the tags + static const int Irrelevant = -1; /// not applicable to this stream/media type, eg length for http streams + static const int Unavailable = 0; /// cannot be obtained + + // whether file is part of a compilation + enum Compilation { CompilationNo = 0, CompilationYes = 1, CompilationUnknown = -1 }; + + /// Creates an empty MetaBundle + LIBAMAROK_EXPORT MetaBundle(); + + /// Creates a MetaBundle for url, tags will be obtained and set + LIBAMAROK_EXPORT explicit MetaBundle( const KURL &url, + bool noCache = false, + TagLib::AudioProperties::ReadStyle = TagLib::AudioProperties::Fast, + EmbeddedImageList* images = 0 ); + + /** For the StreamProvider */ + LIBAMAROK_EXPORT MetaBundle( const QString &title, + const QString &streamUrl, + const int bitrate, + const QString &genre, + const QString &streamName, + const KURL &url ); + + LIBAMAROK_EXPORT MetaBundle( const MetaBundle &bundle ); + + ~MetaBundle(); + + MetaBundle& operator=( const MetaBundle& bundle ); + bool operator==( const MetaBundle& bundle ) const; + bool operator!=( const MetaBundle& bundle ) const; + + /** Test for an empty metabundle */ + bool isEmpty() const; + + /** Empty the metabundle */ + void clear(); + + /** Is it media that has metadata? Note currently we don't check for an audio mimetype */ + bool isValidMedia() const; + + /** The bundle doesn't yet know its audioProperties */ + bool audioPropertiesUndetermined() const; + + /** The embedded artwork in the file (loaded from file into images variable, unmodified if no images present/loadable) */ + void embeddedImages(EmbeddedImageList &images) const; + + /** If you want Accurate reading say so. If EmbeddedImageList != NULL, embedded art is loaded into it */ + void readTags( TagLib::AudioProperties::ReadStyle = TagLib::AudioProperties::Fast, EmbeddedImageList* images = 0 ); + + /** Saves the changes to the file using the transactional algorithm for safety. */ + bool safeSave(); + + /** Saves the changes to the file. Returns false on error. */ + bool save( TagLib::FileRef* fileref = 0 ); + + /** Saves the MetaBundle's data as XML to a text stream. */ + bool save( QTextStream &stream, const QStringList &attributes = QStringList() ) const; + + /** Returns whether the url referred to is a local file */ + bool isFile() const; + + /** Returns whether the url referred to can be accessed via kio slaves */ + bool isKioUrl() const; + + /** Returns whether url can be accessed via kio slaves */ + static bool isKioUrl( const KURL &url ); + + /** Returns whether composer, disc number and bpm fields are available. */ + bool hasExtendedMetaInformation() const; + + void copyFrom( const MetaBundle& bundle ); + + void copyFrom( const PodcastEpisodeBundle &peb ); + + /** Returns a string representation of the tag at \p column, in a format suitable for internal purposes. + For example, for a track 3:24 long, it'll return "204" (seconds). + This should not be used for displaying the tag to the user. */ + QString exactText( int column, bool ensureCached = false ) const; + + /** Sets the tag at \p column from a string in the same format as returned by exactText(). */ + void setExactText( int column, const QString &text ); + + /** Returns the tag at \p column in a format suitable for displaying to the user. */ + QString prettyText( int column ) const; + + /** Returns whether the bundle matches \p expression. + This is fast and doesn't take advanced syntax into account, + and should only be used when it is certain none is present. + The tags in \p columns are checked for matches. + @see ExpressionParser::isAdvancedExpression() */ + bool matchesSimpleExpression( const QString &expression, const QValueList &columns ) const; + + /** A faster version of the above, that pre-caches all the data to be + searched in a single string, to avoid re-building integer and lower + case strings over and over. It is designed to be called from a + playlist search *only* -- it is not entirely thread-safe for efficiency, + although it's highly unlikely to crash. Consider this the beginning + of a real super-efficient index (e.g. suffix tree). + \p terms is a list of lower-case words. */ + bool matchesFast(const QStringList &terms, ColumnMask columns) const; + + /** Returns whether the bundle matches \p expression. + This takes advanced syntax into account, and is slightly slower than matchesSimpleExpression(). + The tags in \p defaultColumns are checked for matches where the expression doesn't specify any manually. */ + bool matchesExpression( const QString &expression, const QValueList &defaultColumns ) const; + + /** Returns whether the bundle matches the pre-parsed expression \p parsedData. + The tags in \p defaultColumns are checked for matches where the expression doesn't specify any manually. + @see ExpressionParser */ + bool matchesParsedExpression( const ParsedExpression &parsedData, const QValueList &defaultColumns ) const; + + /** PlaylistItem reimplements this so it can be informed of moodbar + data events without having to use signals */ + virtual void moodbarJobEvent( int newState ) + { (void) newState; } + +public: + /** + * A class to load MetaBundles from XML. + * #include "xmlloader.h" + */ + class XmlLoader; + +public: //accessors + const KURL &url() const; + QString title() const; + AtomicString artist() const; + AtomicString albumArtist() const; + AtomicString composer() const; + AtomicString album() const; + AtomicString genre() const; + AtomicString comment() const; + QString filename() const; + QString directory() const; + QString type() const; + int year() const; + int discNumber() const; + int track() const; + float bpm() const; + int length() const; + int bitrate() const; + int sampleRate() const; + float score( bool ensureCached = false ) const; + int rating( bool ensureCached = false ) const; //returns rating * 2, to accommodate .5 ratings + int playCount( bool ensureCached = false ) const; + uint lastPlay( bool ensureCached = false ) const; + + Moodbar &moodbar(); + const Moodbar &moodbar_const() const; + + int filesize() const; + + int compilation() const; + int fileType() const; // returns a value from enum FileType + bool exists() const; // true for everything but local files that aren't there + PodcastEpisodeBundle *podcastBundle() const; + LastFm::Bundle *lastFmBundle() const; + QString streamName() const; + QString streamUrl() const; + QString uniqueId() const; + + QString prettyTitle() const; + QString veryNiceTitle() const; + QString prettyURL() const; + QString prettyBitrate() const; + QString prettyLength() const; + QString prettySampleRate( bool shortened = false ) const; + QString prettyFilesize() const; + QString prettyRating() const; + + bool safeToSave() { return m_safeToSave; } + + QString getRandomString( int size, bool numbersOnly = false ); + +public: //modifiers + void setUrl( const KURL &url ); + void setPath( const QString &path ); + void setTitle( const QString &title ); + void setArtist( const AtomicString &artist ); + void setAlbumArtist( const AtomicString &albumArtist ); + void setComposer( const AtomicString &composer ); + void setAlbum( const AtomicString &album ); + void setGenre( const AtomicString &genre ); + void setComment( const AtomicString &comment ); + void setYear( int year ); + void setDiscNumber( int discNumber ); + void setTrack( int track ); + void setBpm( float bpm ); + void setLength( int length ); + void setBitrate( int bitrate ); + void setSampleRate( int sampleRate ); + void setScore( float score ); + void setRating( int rating ); + void setPlayCount( int playcount ); + void setLastPlay( uint lastplay ); + void setFilesize( int bytes ); + // No direct moodbar mutator -- moodbar should not be separated + // from the metabundle + + void updateFilesize(); + void setFileType( int type ); + void setCompilation( int compilation ); + bool checkExists(); + void setPodcastBundle( const PodcastEpisodeBundle &peb ); + void setLastFmBundle( const LastFm::Bundle &last ); + void setUniqueId(); //uses database for lookup + void setUniqueId( const QString &id ); //SEE COMMENT in .CPP + const TagLib::ByteVector readUniqueIdHelper( TagLib::FileRef fileref ) const; + QString readUniqueId( TagLib::FileRef *fileref = 0 ); + void scannerAcknowledged() {} + + void detach(); // for being able to apply QDeepCopy<> + +public: //static helper functions + static QString prettyBitrate( int ); + static QString prettyLength( int, bool showHours = false ); //must be int, see Unavailable, etc. above + static QString prettyFilesize( int ); + static QString prettyRating( int rating, bool trailingzero = false ); + static QString ratingDescription( int ); + static QStringList ratingList(); + static QString prettyTime( uint, bool showHours = true ); + static QString fuzzyTime( int ); + static QString veryPrettyTime( int ); + static QString zeroPad( uint i ); + static QString prettyTitle( const QString &filename ); + static QStringList genreList(); + +protected: + enum ExtendedTags { composerTag, albumArtistTag, discNumberTag, bpmTag, compilationTag }; + + /** Called before the tags in \p columns are changed. */ + virtual void aboutToChange( const QValueList &columns ); + + /** Convenience method. */ + void aboutToChange( int column ); + + /** Called after the tags in \p columns are changed. */ + virtual void reactToChanges( const QValueList &columns ); + + /** Convenience method. */ + void reactToChange( int column ); + + KURL m_url; + QString m_title; + AtomicString m_artist; + AtomicString m_albumArtist; + AtomicString m_composer; + AtomicString m_album; + AtomicString m_comment; + AtomicString m_genre; + QString m_streamName; + QString m_streamUrl; + QString m_uniqueId; + + int m_year; + int m_discNumber; + int m_track; + float m_bpm; + int m_bitrate; + int m_length; + int m_sampleRate; + + float m_score; + int m_rating; + int m_playCount; + uint m_lastPlay; + int m_filesize; + + Moodbar *m_moodbar; + + int m_type; + + bool m_exists: 1; + bool m_isValidMedia: 1; + bool m_isCompilation: 1; + bool m_notCompilation: 1; + bool m_safeToSave: 1; + int m_waitingOnKIO; + QString m_tempSavePath; + QString m_origRenamedSavePath; + QCString m_tempSaveDigest; + TagLib::FileRef* m_saveFileref; + + PodcastEpisodeBundle *m_podcastBundle; + LastFm::Bundle *m_lastFmBundle; + + // The vars below are used to optimize search by storing + // the full text to be searched. They are mutable, as they + // act like a sort of cache for the const method matchesFast + + // whether the search text should be rebuilt + volatile mutable bool m_isSearchDirty; + // which columns the search string contains + mutable ColumnMask m_searchColumns; + // the search string: textualized columns separated by space + // note that matchFast searches by words, hence a word cannot span + // space-separated columns + mutable QString m_searchStr; +private: + + static inline QString prettyGeneric( const QString &s, const int i ) + { + return (i > 0) ? s.arg( i ) : (i == Undetermined) ? "?" : "-"; + } + + void init( TagLib::AudioProperties *ap = 0 ); + void init( const KFileMetaInfo& info ); + + void setExtendedTag( TagLib::File *file, int tag, const QString value ); + + void loadImagesFromTag( const TagLib::ID3v2::Tag &tag, EmbeddedImageList& images ) const; + + int getRand(); +}; + +/// for your convenience +typedef QValueList BundleList; + + + +inline bool MetaBundle::operator!=(const MetaBundle &bundle) const { return !operator==( bundle ); } + +inline bool MetaBundle::isEmpty() const { return url().isEmpty(); } + +inline bool MetaBundle::isValidMedia() const { return m_isValidMedia; } + +inline bool MetaBundle::audioPropertiesUndetermined() const +{ + return m_bitrate == Undetermined || m_sampleRate == Undetermined || m_length == Undetermined; +} + +inline void MetaBundle::aboutToChange( const QValueList& ) { } +inline void MetaBundle::aboutToChange( int column ) { aboutToChange( QValueList() << column ); } +inline void MetaBundle::reactToChange( int column ) { reactToChanges( QValueList() << column ); } + +inline bool MetaBundle::exists() const { return m_exists; } + +inline bool MetaBundle::isFile() const { return url().isLocalFile(); } +inline bool MetaBundle::isKioUrl() const { return isKioUrl( url() ); } +inline bool MetaBundle::isKioUrl( const KURL &url ) { return url.protocol() != "daap" && url.protocol() != "cdda" && url.protocol() != "lastfm"; } + +inline int MetaBundle::track() const { return m_track == Undetermined ? 0 : m_track; } +inline int MetaBundle::year() const { return m_year == Undetermined ? 0 : m_year; } +inline int MetaBundle::length() const { return m_length > 0 ? m_length : 0; } +inline int MetaBundle::bitrate() const { return m_bitrate == Undetermined ? 0 : m_bitrate; } +inline int MetaBundle::sampleRate() const { return m_sampleRate == Undetermined ? 0 : m_sampleRate; } +inline int MetaBundle::filesize() const { return m_filesize == Undetermined ? 0 : m_filesize; } +inline int MetaBundle::fileType() const { return m_type; } + +inline Moodbar &MetaBundle::moodbar() +{ + if( m_moodbar == 0 ) m_moodbar = new Moodbar( this ); + return *m_moodbar; +} +inline const Moodbar &MetaBundle::moodbar_const() const +{ + // Anyone know of a better way to do this? + if( m_moodbar == 0 ) + const_cast(this)->m_moodbar + = new Moodbar( const_cast(this) ); + return *m_moodbar; +} + +inline const KURL& MetaBundle::url() const { return m_url; } +inline QString MetaBundle::filename() const { return url().fileName(); } +inline QString MetaBundle::directory() const +{ + return url().isLocalFile() ? url().directory() : url().upURL().prettyURL(); +} +inline QString MetaBundle::title() const { return m_title; } +inline AtomicString MetaBundle::artist() const { return m_artist; } +inline AtomicString MetaBundle::album() const { return m_album; } +inline AtomicString MetaBundle::comment() const { return m_comment; } +inline AtomicString MetaBundle::genre() const { return m_genre; } +inline AtomicString MetaBundle::composer() const { return m_composer; } +inline AtomicString MetaBundle::albumArtist() const { return m_albumArtist; } +inline QString MetaBundle::streamName() const { return m_streamName; } +inline QString MetaBundle::streamUrl() const { return m_streamUrl; } +inline QString MetaBundle::uniqueId() const { return m_uniqueId; } + +inline int MetaBundle::discNumber() const { return m_discNumber == Undetermined ? 0 : m_discNumber; } +inline float MetaBundle::bpm() const { return m_bpm == Undetermined ? 0 : m_bpm; } +inline int MetaBundle::compilation() const +{ + if( m_isCompilation ) + return CompilationYes; + else if( m_notCompilation ) + return CompilationNo; + else + return CompilationUnknown; +} + + +inline QString MetaBundle::type() const +{ + return isFile() + ? filename().mid( filename().findRev( '.' ) + 1 ) + : i18n( "Stream" ); +} +inline PodcastEpisodeBundle *MetaBundle::podcastBundle() const { return m_podcastBundle; } +inline LastFm::Bundle *MetaBundle::lastFmBundle() const { return m_lastFmBundle; } + +inline QString MetaBundle::prettyURL() const { return url().prettyURL(); } +inline QString MetaBundle::prettyBitrate() const { return prettyBitrate( m_bitrate ); } +inline QString MetaBundle::prettyLength() const { return prettyLength( m_length, true ); } +inline QString MetaBundle::prettyFilesize() const { return prettyFilesize( filesize() ); } +inline QString MetaBundle::prettyRating() const { return prettyRating( rating() ); } +inline QString MetaBundle::prettySampleRate( bool shortened ) const + { + if ( shortened ) + return prettyGeneric( i18n( "SampleRate", "%1 kHz" ), m_sampleRate / 1000 ); + else + return prettyGeneric( i18n( "SampleRate", "%1 Hz" ), m_sampleRate ); + } + +inline QString MetaBundle::zeroPad( uint i ) { return ( i < 10 ) ? QString( "0%1" ).arg( i ) : QString::number( i ); } + +inline bool MetaBundle::hasExtendedMetaInformation() const +{ + return ( m_type == mp3 || m_type == ogg || + m_type== mp4 || m_type == flac ); +} + + +#endif diff --git a/amarok/src/metabundlesaver.cpp b/amarok/src/metabundlesaver.cpp new file mode 100644 index 00000000..45edb530 --- /dev/null +++ b/amarok/src/metabundlesaver.cpp @@ -0,0 +1,308 @@ +// Jeff Mitchell , (C) 2006 +// License: GNU General Public License V2 + + +#define DEBUG_PREFIX "MetaBundleSaver" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "amarok.h" +#include "amarokconfig.h" +#include "collectiondb.h" +#include "debug.h" +#include "metabundlesaver.h" +#include "scancontroller.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include //decodePath() +#include +#include +#include + +#include + +#include "metabundle.h" + +MetaBundleSaver::MetaBundleSaver( MetaBundle *bundle ) + : QObject() + , m_bundle( bundle ) + , m_tempSavePath( QString::null ) + , m_origRenamedSavePath( QString::null ) + , m_tempSaveDigest( 0 ) + , m_saveFileref( 0 ) + , m_maxlen( 8192 ) + , m_cleanupNeeded( false ) + +{ + DEBUG_BLOCK +} + +MetaBundleSaver::~MetaBundleSaver() +{ + DEBUG_BLOCK + if( m_cleanupNeeded ) + cleanupSave(); +} + +TagLib::FileRef * +MetaBundleSaver::prepareToSave() +{ + DEBUG_BLOCK + + m_cleanupNeeded = true; + KMD5 md5sum( 0, 0 ); + const KURL origPath = m_bundle->url(); + char hostbuf[32]; + hostbuf[0] = '\0'; + int hostname = gethostname( hostbuf, 32 ); + hostbuf[31] = '\0'; + if( hostname != 0 ) + { + debug() << "Could not determine hostname!" << endl; + return 0; + } + QString pid; + QString randomString = m_bundle->getRandomString( 8, true ); + m_tempSavePath = origPath.path() + ".amaroktemp.host-" + QString( hostbuf ) + + ".pid-" + pid.setNum( getpid() ) + ".random-" + randomString + '.' + m_bundle->type(); + m_origRenamedSavePath = origPath.path() + ".amarokoriginal.host-" + QString( hostbuf ) + + ".pid-" + pid.setNum( getpid() ) + ".random-" + randomString + '.' + m_bundle->type(); + + + //The next long step is to copy the file over. We can't use KIO because it's not thread save, + //and std and QFile only have provisions for renaming and removing, so manual it is + //doing it block-by-block results it not needing a huge amount of memory overhead + + debug() << "Copying original file to copy and caluclating MD5" << endl; + + if( QFile::exists( m_tempSavePath ) ) + { + debug() << "Temp file already exists!" << endl; + return 0; + } + + QFile orig( m_bundle->url().path() ); + QFile copy( m_tempSavePath ); + + if( !orig.open( IO_Raw | IO_ReadOnly ) ) + { + debug() << "Could not open original file!" << endl; + return 0; + } + + //Do this separately so as not to create a zero-length file if you can't read from input + if( !copy.open( IO_Raw | IO_WriteOnly | IO_Truncate ) ) + { + debug() << "Could not create file copy" << endl; + return 0; + } + + Q_LONG actualreadlen, actualwritelen; + + while( ( actualreadlen = orig.readBlock( m_databuf, m_maxlen ) ) > 0 ) + { + md5sum.update( m_databuf, actualreadlen ); + if( ( actualwritelen = copy.writeBlock( m_databuf, actualreadlen ) ) != actualreadlen ) + { + debug() << "Error during copying of original file data to copy!" << endl; + return 0; + } + } + + if( actualreadlen == -1 ) + { + debug() << "Error during reading original file!" << endl; + return 0; + } + + m_tempSaveDigest = md5sum.hexDigest(); + + //By this point, we have the following: + //The original file is copied at path m_tempSavePath + //We have generated what will be the filename to rename the original to in m_origRenamedSavePath + //We have successfully copied the original file to the temp location + //We've calculated the md5sum of the original file + + debug() << "MD5 sum of temp file: " << m_tempSaveDigest.data() << endl; + + //Now, we have a MD5 sum of the original file at the time of copying saved in m_tempSaveDigest + //Create a fileref on the copied file, for modification + + m_saveFileref = new TagLib::FileRef( QFile::encodeName( m_tempSavePath ), false ); + + if( m_saveFileref && !m_saveFileref->isNull() ) + return m_saveFileref; + + debug() << "Error creating temp file's fileref!" << endl; + return 0; +} + +bool +MetaBundleSaver::doSave() +{ + //TODO: much commenting needed. For now this pretty much follows algorithm laid out in bug 131353, + //but isn't useable since I need to find a good way to switch the file path with taglib, or a good way + //to get all the metadata copied over. + + DEBUG_BLOCK + m_cleanupNeeded = true; + bool revert = false; + + QFile origRenamedFile( m_origRenamedSavePath ); + KMD5 md5sum( 0, 0 ); + Q_LONG actualreadlen; + + int errcode; + + QCString origRenamedDigest; + + if( !m_saveFileref || m_tempSavePath.isEmpty() || m_tempSaveDigest.isEmpty() || m_origRenamedSavePath.isEmpty() ) + { + debug() << "You must run prepareToSave() and it must return successfully before calling doSave()!" << endl; + return false; + } + + debug() << "Saving tag changes to the temporary file..." << endl; + + //We've made our changes to the fileref; save it first, then do the logic to move the correct file back + if( !m_saveFileref->save() ) + { + debug() << "Could not save the new file!" << endl; + goto fail_remove_copy; + } + + debug() << "Renaming original file to temporary name " << m_origRenamedSavePath << endl; + + errcode = std::rename( QFile::encodeName( m_bundle->url().path() ).data(), + QFile::encodeName( m_origRenamedSavePath ).data() ); + if( errcode != 0 ) + { + debug() << "Could not move original!" << endl; + perror( "Could not move original!" ); + goto fail_remove_copy; + } + + revert = true; + + debug() << "Calculating MD5 of " << m_origRenamedSavePath << endl; + + if( !origRenamedFile.open( IO_Raw | IO_ReadOnly ) ) + { + debug() << "Could not open temporary file!" << endl; + goto fail_remove_copy; + } + + while( ( actualreadlen = origRenamedFile.readBlock( m_databuf, m_maxlen ) ) > 0 ) + md5sum.update( m_databuf, actualreadlen ); + + if( actualreadlen == -1 ) + { + debug() << "Error during checksumming temp file!" << endl; + goto fail_remove_copy; + } + + origRenamedDigest = md5sum.hexDigest(); + + debug() << "md5sum of original file: " << origRenamedDigest.data() << endl; + + if( origRenamedDigest != m_tempSaveDigest ) + { + debug() << "Original checksum did not match current checksum!" << endl; + goto fail_remove_copy; + } + + debug() << "Renaming temp file to original's filename" << endl; + + errcode = std::rename( QFile::encodeName( m_tempSavePath ).data(), + QFile::encodeName( m_bundle->url().path() ).data() ); + if( errcode != 0 ) + { + debug() << "Could not rename newly-tagged file to original!" << endl; + perror( "Could not rename newly-tagged file to original!" ); + goto fail_remove_copy; + } + + debug() << "Deleting original" << endl; + + errcode = std::remove( QFile::encodeName( m_origRenamedSavePath ) ); + if( errcode != 0 ) + { + debug() << "Could not delete the original file!" << endl; + perror( "Could not delete the original file!" ); + return false; + } + + debug() << "Save done, returning true!" << endl; + + return true; + + fail_remove_copy: + + debug() << "Deleting temporary file..." << endl; + errcode = std::remove( QFile::encodeName( m_tempSavePath ).data() ); + if( errcode != 0 ) + { + debug() << "Could not delete the temporary file!" << endl; + perror( "Could not delete the temporary file!" ); + } + + if( !revert ) + return false; + + debug() << "Reverting original file to original filename!" << endl; + errcode = std::rename( QFile::encodeName( m_origRenamedSavePath ).data(), + QFile::encodeName( m_bundle->url().path() ).data() ); + if( errcode != 0 ) + { + debug() << "Could not revert file to original filename!" << endl; + perror( "Could not revert file to original filename!" ); + } + + return false; +} + +bool +MetaBundleSaver::cleanupSave() +{ + DEBUG_BLOCK + + bool dirty = false; + + if( !m_tempSavePath.isEmpty() && QFile::exists( m_tempSavePath ) ) + { + int errcode; + errcode = std::remove( QFile::encodeName( m_tempSavePath ).data() ); + if( errcode != 0 ) + { + dirty = true; + debug() << "Could not delete the temporary file!" << endl; + } + } + + m_tempSavePath = QString::null; + m_origRenamedSavePath = QString::null; + m_tempSaveDigest = QCString( 0 ); + if( m_saveFileref ) + { + delete m_saveFileref; + m_saveFileref = 0; + } + + m_cleanupNeeded = false; + return !dirty; +} + +#include "metabundlesaver.moc" diff --git a/amarok/src/metabundlesaver.h b/amarok/src/metabundlesaver.h new file mode 100644 index 00000000..38a90848 --- /dev/null +++ b/amarok/src/metabundlesaver.h @@ -0,0 +1,51 @@ +// Jeff Mitchell , (C) 2006 +// License: GNU General Public License V2 + +#ifndef METABUNDLESAVER_H +#define METABUNDLESAVER_H + +#include +#include +#include //inline functions +#include //inline functions +#include +#include "expression.h" +#include "atomicstring.h" +#include "atomicurl.h" + +#include "amarok_export.h" + +namespace TagLib { + class FileRef; +} + +/** + * @class MetaBundleSaver + * @author Jeff Mitchell + */ + +class LIBAMAROK_EXPORT MetaBundleSaver : public QObject +{ + Q_OBJECT +public: + MetaBundleSaver( MetaBundle *bundle ); + ~MetaBundleSaver(); + + //bool scannerSafeSave( TagLib::File* file ); + TagLib::FileRef* prepareToSave(); + bool doSave(); + bool cleanupSave(); + void abortSave( const QString message ); + +private: + MetaBundle *m_bundle; + QString m_tempSavePath; + QString m_origRenamedSavePath; + QCString m_tempSaveDigest; + TagLib::FileRef* m_saveFileref; + char m_databuf[8192]; + Q_ULONG m_maxlen; + bool m_cleanupNeeded; +}; + +#endif diff --git a/amarok/src/metadata/Makefile.am b/amarok/src/metadata/Makefile.am new file mode 100644 index 00000000..617bb9d8 --- /dev/null +++ b/amarok/src/metadata/Makefile.am @@ -0,0 +1,35 @@ +if with_mp4v2 +MP4_SUBDIR = mp4 +MP4_LDADD = mp4/libtagmp4.la +else +MP4_SUBDIR = m4a +MP4_LDADD = m4a/libtagm4a.la +endif + +if !TAGLIB_15_FOUND +TAGLIB14_SUBDIRS = speex wavpack trueaudio +TAGLIB14_LDADD = speex/libtagspeex.la trueaudio/libtagtrueaudio.la wavpack/libtagwavpack.la +endif + +SUBDIRS = $(TAGLIB14_SUBDIRS) asf audible rmff $(MP4_SUBDIR) aac wav + + +INCLUDES = -I$(top_srcdir)/amarok/src $(all_includes) $(TAGLIB_CFLAGS) +METASOURCES = AUTO +libmetadata_la_LDFLAGS = $(all_libraries) +noinst_LTLIBRARIES = libmetadata.la + +libmetadata_la_SOURCES = \ + tplugins.cpp + +noinst_HEADERS = \ + tplugins.h + +libmetadata_la_LIBADD = \ + $(TAGLIB14_LDADD) \ + asf/libtagasf.la \ + wav/libtagwav.la \ + rmff/libtagrealmedia.la \ + $(MP4_LDADD) \ + aac/libtagaac.la \ + audible/libtagaudible.la diff --git a/amarok/src/metadata/aac/Makefile.am b/amarok/src/metadata/aac/Makefile.am new file mode 100644 index 00000000..30349d04 --- /dev/null +++ b/amarok/src/metadata/aac/Makefile.am @@ -0,0 +1,12 @@ +SUBDIRS = +METASOURCES = AUTO +INCLUDES = $(all_includes) $(TAGLIB_CFLAGS) + +libtagaac_la_LDFLAGS = $(all_libraries) +noinst_LTLIBRARIES = libtagaac.la + +libtagaac_la_SOURCES = \ + aacfiletyperesolver.cpp + +noinst_HEADERS = \ + aacfiletyperesolver.h diff --git a/amarok/src/metadata/aac/aacfiletyperesolver.cpp b/amarok/src/metadata/aac/aacfiletyperesolver.cpp new file mode 100644 index 00000000..fcc2a867 --- /dev/null +++ b/amarok/src/metadata/aac/aacfiletyperesolver.cpp @@ -0,0 +1,38 @@ +/*************************************************************************** + copyright : (C) 2006 by Martin Aumueller + email : aumuell@reserv.at + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301 USA * + ***************************************************************************/ + +#include "aacfiletyperesolver.h" +#include + +#include + +TagLib::File *AACFileTypeResolver::createFile(const char *fileName, + bool readProperties, + TagLib::AudioProperties::ReadStyle propertiesStyle) const +{ + const char *ext = strrchr(fileName, '.'); + if(ext && !strcasecmp(ext, ".aac")) + { + return new TagLib::MPEG::File(fileName, readProperties, propertiesStyle); + } + + return 0; +} diff --git a/amarok/src/metadata/aac/aacfiletyperesolver.h b/amarok/src/metadata/aac/aacfiletyperesolver.h new file mode 100644 index 00000000..6c33b718 --- /dev/null +++ b/amarok/src/metadata/aac/aacfiletyperesolver.h @@ -0,0 +1,36 @@ +/*************************************************************************** + copyright : (C) 2006 by Martin Aumueller + email : aumuell@reserv.at + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301 USA * + ***************************************************************************/ + +#ifndef TAGLIB_AACFILETYPERESOLVER_H +#define TAGLIB_AACFILETYPERESOLVER_H + +#include +#include + + +class AACFileTypeResolver : public TagLib::FileRef::FileTypeResolver +{ + TagLib::File *createFile(const char *fileName, + bool readAudioProperties, + TagLib::AudioProperties::ReadStyle audioPropertiesStyle) const; +}; + +#endif diff --git a/amarok/src/metadata/asf/Makefile.am b/amarok/src/metadata/asf/Makefile.am new file mode 100644 index 00000000..8f42b9b2 --- /dev/null +++ b/amarok/src/metadata/asf/Makefile.am @@ -0,0 +1,20 @@ +SUBDIRS = + +INCLUDES = $(all_includes) $(TAGLIB_CFLAGS) +METASOURCES = AUTO +libtagasf_la_LDFLAGS = $(all_libraries) +noinst_LTLIBRARIES = libtagasf.la + +libtagasf_la_SOURCES = \ + asfattribute.cpp \ + asfproperties.cpp \ + asftag.cpp \ + asffile.cpp \ + taglib_asffiletyperesolver.cpp + +noinst_HEADERS = \ + asfattribute.h \ + asfproperties.h \ + asftag.h \ + asffile.h \ + taglib_asffiletyperesolver.h diff --git a/amarok/src/metadata/asf/asfattribute.cpp b/amarok/src/metadata/asf/asfattribute.cpp new file mode 100644 index 00000000..bfe81c57 --- /dev/null +++ b/amarok/src/metadata/asf/asfattribute.cpp @@ -0,0 +1,304 @@ +/************************************************************************** + copyright : (C) 2005-2007 by Lukáš Lalinský + email : lalinsky@gmail.com + **************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 * + * USA * + ***************************************************************************/ + +#include +#include "asfattribute.h" +#include "asffile.h" + +using namespace TagLib; + +class ASF::Attribute::AttributePrivate : public RefCounter +{ +public: + AttributePrivate() + : stream(0), + language(0) {} + AttributeTypes type; + String stringValue; + ByteVector byteVectorValue; + union { + unsigned int intValue; + unsigned short shortValue; + unsigned long long longLongValue; + bool boolValue; + }; + int stream; + int language; +}; + +//////////////////////////////////////////////////////////////////////////////// +// public members +//////////////////////////////////////////////////////////////////////////////// + +ASF::Attribute::Attribute() +{ + d = new AttributePrivate; + d->type = UnicodeType; +} + +ASF::Attribute::Attribute(const ASF::Attribute &other) + : d(other.d) +{ + d->ref(); +} + +ASF::Attribute & +ASF::Attribute::operator=(const ASF::Attribute &other) +{ + if(d->deref()) + delete d; + d = other.d; + d->ref(); + return *this; +} + +ASF::Attribute::~Attribute() +{ + if(d->deref()) + delete d; +} + +ASF::Attribute::Attribute(const String &value) +{ + d = new AttributePrivate; + d->type = UnicodeType; + d->stringValue = value; +} + +ASF::Attribute::Attribute(const ByteVector &value) +{ + d = new AttributePrivate; + d->type = BytesType; + d->byteVectorValue = value; +} + +ASF::Attribute::Attribute(unsigned int value) +{ + d = new AttributePrivate; + d->type = DWordType; + d->intValue = value; +} + +ASF::Attribute::Attribute(unsigned long long value) +{ + d = new AttributePrivate; + d->type = QWordType; + d->longLongValue = value; +} + +ASF::Attribute::Attribute(unsigned short value) +{ + d = new AttributePrivate; + d->type = WordType; + d->shortValue = value; +} + +ASF::Attribute::Attribute(bool value) +{ + d = new AttributePrivate; + d->type = BoolType; + d->boolValue = value; +} + +ASF::Attribute::AttributeTypes +ASF::Attribute::type() const +{ + return d->type; +} + +String +ASF::Attribute::toString() const +{ + return d->stringValue; +} + +ByteVector +ASF::Attribute::toByteVector() const +{ + return d->byteVectorValue; +} + +unsigned short +ASF::Attribute::toBool() const +{ + return d->shortValue; +} + +unsigned short +ASF::Attribute::toUShort() const +{ + return d->shortValue; +} + +unsigned int +ASF::Attribute::toUInt() const +{ + return d->intValue; +} + +unsigned long long +ASF::Attribute::toULongLong() const +{ + return d->longLongValue; +} + +String +ASF::Attribute::parse(ASF::File &f, int kind) +{ + int size, nameLength; + String name; + + // extended content descriptor + if(kind == 0) { + nameLength = f.readWORD(); + name = f.readString(nameLength); + d->type = ASF::Attribute::AttributeTypes(f.readWORD()); + size = f.readWORD(); + } + // metadata & metadata library + else { + int temp = f.readWORD(); + // metadata library + if(kind == 2) { + d->language = temp; + } + d->stream = f.readWORD(); + nameLength = f.readWORD(); + d->type = ASF::Attribute::AttributeTypes(f.readWORD()); + size = f.readDWORD(); + name = f.readString(nameLength); + } + + switch(d->type) { + case WordType: + d->shortValue = f.readWORD(); + break; + + case BoolType: + if(kind == 0) { + d->boolValue = f.readDWORD() == 1; + } + else { + d->boolValue = f.readWORD() == 1; + } + break; + + case DWordType: + d->intValue = f.readDWORD(); + break; + + case QWordType: + d->longLongValue = f.readQWORD(); + break; + + case UnicodeType: + d->stringValue = f.readString(size); + break; + + case BytesType: + case GuidType: + d->byteVectorValue = f.readBlock(size); + break; + } + + return name; +} + +ByteVector +ASF::Attribute::render(const String &name, int kind) const +{ + ByteVector data; + + switch (d->type) { + case WordType: + data.append(ByteVector::fromShort(d->shortValue, false)); + break; + + case BoolType: + if(kind == 0) { + data.append(ByteVector::fromUInt(d->boolValue ? 1 : 0, false)); + } + else { + data.append(ByteVector::fromShort(d->boolValue ? 1 : 0, false)); + } + break; + + case DWordType: + data.append(ByteVector::fromUInt(d->intValue, false)); + break; + + case QWordType: + data.append(ByteVector::fromLongLong(d->longLongValue, false)); + break; + + case UnicodeType: + data.append(File::renderString(d->stringValue)); + break; + + case BytesType: + case GuidType: + data.append(d->byteVectorValue); + break; + } + + if(kind == 0) { + data = File::renderString(name, true) + + ByteVector::fromShort((int)d->type, false) + + ByteVector::fromShort(data.size(), false) + + data; + } + else { + ByteVector nameData = File::renderString(name); + data = ByteVector::fromShort(kind == 2 ? d->language : 0, false) + + ByteVector::fromShort(d->stream, false) + + ByteVector::fromShort(nameData.size(), false) + + ByteVector::fromShort((int)d->type, false) + + ByteVector::fromUInt(data.size(), false) + + nameData + + data; + } + + return data; +} + +int +ASF::Attribute::language() const +{ + return d->language; +} + +void +ASF::Attribute::setLanguage(int value) +{ + d->language = value; +} + +int +ASF::Attribute::stream() const +{ + return d->stream; +} + +void +ASF::Attribute::setStream(int value) +{ + d->stream = value; +} diff --git a/amarok/src/metadata/asf/asfattribute.h b/amarok/src/metadata/asf/asfattribute.h new file mode 100644 index 00000000..9a8e3c90 --- /dev/null +++ b/amarok/src/metadata/asf/asfattribute.h @@ -0,0 +1,176 @@ +/************************************************************************** + copyright : (C) 2005-2007 by Lukáš Lalinský + email : lalinsky@gmail.com + **************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 * + * USA * + ***************************************************************************/ + +#ifndef TAGLIB_ASFATTRIBUTE_H +#define TAGLIB_ASFATTRIBUTE_H + +#include +#include + +namespace TagLib +{ + + namespace ASF + { + + class File; + + class Attribute + { + public: + + /*! + * Enum of types an Attribute can have. + */ + enum AttributeTypes { + UnicodeType = 0, + BytesType = 1, + BoolType = 2, + DWordType = 3, + QWordType = 4, + WordType = 5, + GuidType = 6 + }; + + /*! + * Constructs an empty attribute. + */ + Attribute(); + + /*! + * Constructs an attribute with \a key and a UnicodeType \a value. + */ + Attribute(const String &value); + + /*! + * Constructs an attribute with \a key and a BytesType \a value. + */ + Attribute(const ByteVector &value); + + /*! + * Constructs an attribute with \a key and a DWordType \a value. + */ + Attribute(unsigned int value); + + /*! + * Constructs an attribute with \a key and a QWordType \a value. + */ + Attribute(unsigned long long value); + + /*! + * Constructs an attribute with \a key and a WordType \a value. + */ + Attribute(unsigned short value); + + /*! + * Constructs an attribute with \a key and a BoolType \a value. + */ + Attribute(bool value); + + /*! + * Construct an attribute as a copy of \a other. + */ + Attribute(const Attribute &item); + + /*! + * Copies the contents of \a other into this item. + */ + ASF::Attribute &operator=(const Attribute &other); + + /*! + * Destroys the attribute. + */ + virtual ~Attribute(); + + /*! + * Returns type of the value. + */ + AttributeTypes type() const; + + /*! + * Returns the BoolType \a value. + */ + unsigned short toBool() const; + + /*! + * Returns the WordType \a value. + */ + unsigned short toUShort() const; + + /*! + * Returns the DWordType \a value. + */ + unsigned int toUInt() const; + + /*! + * Returns the QWordType \a value. + */ + unsigned long long toULongLong() const; + + /*! + * Returns the UnicodeType \a value. + */ + String toString() const; + + /*! + * Returns the BytesType \a value. + */ + ByteVector toByteVector() const; + + /*! + * Returns the language number, or 0 is no stream number was set. + */ + int language() const; + + /*! + * Sets the language number. + */ + void setLanguage(int value); + + /*! + * Returns the stream number, or 0 is no stream number was set. + */ + int stream() const; + + /*! + * Sets the stream number. + */ + void setStream(int value); + +#ifndef DO_NOT_DOCUMENT + /* THIS IS PRIVATE, DON'T TOUCH IT! */ + String parse(ASF::File &file, int kind = 0); +#endif + + private: + friend class File; + + ByteVector render(const String &name, int kind = 0) const; + + class AttributePrivate; + AttributePrivate *d; + }; + + } + +} + +#endif diff --git a/amarok/src/metadata/asf/asffile.cpp b/amarok/src/metadata/asf/asffile.cpp new file mode 100644 index 00000000..55a12ccc --- /dev/null +++ b/amarok/src/metadata/asf/asffile.cpp @@ -0,0 +1,547 @@ +/************************************************************************** + copyright : (C) 2005-2007 by Lukáš Lalinský + email : lalinsky@gmail.com + **************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 * + * USA * + ***************************************************************************/ + +#include +#include +#include "asffile.h" +#include "asftag.h" +#include "asfproperties.h" + +using namespace TagLib; + +class ASF::File::FilePrivate +{ +public: + FilePrivate(): + size(0), + tag(0), + properties(0), + contentDescriptionObject(0), + extendedContentDescriptionObject(0), + headerExtensionObject(0), + metadataObject(0), + metadataLibraryObject(0) {} + unsigned long long size; + ASF::Tag *tag; + ASF::Properties *properties; + List objects; + ASF::File::ContentDescriptionObject *contentDescriptionObject; + ASF::File::ExtendedContentDescriptionObject *extendedContentDescriptionObject; + ASF::File::HeaderExtensionObject *headerExtensionObject; + ASF::File::MetadataObject *metadataObject; + ASF::File::MetadataLibraryObject *metadataLibraryObject; +}; + +static ByteVector headerGuid("\x30\x26\xB2\x75\x8E\x66\xCF\x11\xA6\xD9\x00\xAA\x00\x62\xCE\x6C", 16); +static ByteVector filePropertiesGuid("\xA1\xDC\xAB\x8C\x47\xA9\xCF\x11\x8E\xE4\x00\xC0\x0C\x20\x53\x65", 16); +static ByteVector streamPropertiesGuid("\x91\x07\xDC\xB7\xB7\xA9\xCF\x11\x8E\xE6\x00\xC0\x0C\x20\x53\x65", 16); +static ByteVector contentDescriptionGuid("\x33\x26\xB2\x75\x8E\x66\xCF\x11\xA6\xD9\x00\xAA\x00\x62\xCE\x6C", 16); +static ByteVector extendedContentDescriptionGuid("\x40\xA4\xD0\xD2\x07\xE3\xD2\x11\x97\xF0\x00\xA0\xC9\x5E\xA8\x50", 16); +static ByteVector headerExtensionGuid("\xb5\x03\xbf_.\xa9\xcf\x11\x8e\xe3\x00\xc0\x0c Se", 16); +static ByteVector metadataGuid("\xEA\xCB\xF8\xC5\xAF[wH\204g\xAA\214D\xFAL\xCA", 16); +static ByteVector metadataLibraryGuid("\224\034#D\230\224\321I\241A\x1d\x13NEpT", 16); + +class ASF::File::BaseObject +{ +public: + ByteVector data; + virtual ~BaseObject() {} + virtual ByteVector guid() = 0; + virtual void parse(ASF::File *file, unsigned int size); + virtual ByteVector render(ASF::File *file); +}; + +class ASF::File::UnknownObject : public ASF::File::BaseObject +{ + ByteVector myGuid; +public: + UnknownObject(const ByteVector &guid); + ByteVector guid(); +}; + +class ASF::File::FilePropertiesObject : public ASF::File::BaseObject +{ +public: + ByteVector guid(); + void parse(ASF::File *file, uint size); +}; + +class ASF::File::StreamPropertiesObject : public ASF::File::BaseObject +{ +public: + ByteVector guid(); + void parse(ASF::File *file, uint size); +}; + +class ASF::File::ContentDescriptionObject : public ASF::File::BaseObject +{ +public: + ByteVector guid(); + void parse(ASF::File *file, uint size); + ByteVector render(ASF::File *file); +}; + +class ASF::File::ExtendedContentDescriptionObject : public ASF::File::BaseObject +{ +public: + ByteVectorList attributeData; + ByteVector guid(); + void parse(ASF::File *file, uint size); + ByteVector render(ASF::File *file); +}; + +class ASF::File::MetadataObject : public ASF::File::BaseObject +{ +public: + ByteVectorList attributeData; + ByteVector guid(); + void parse(ASF::File *file, uint size); + ByteVector render(ASF::File *file); +}; + +class ASF::File::MetadataLibraryObject : public ASF::File::BaseObject +{ +public: + ByteVectorList attributeData; + ByteVector guid(); + void parse(ASF::File *file, uint size); + ByteVector render(ASF::File *file); +}; + +class ASF::File::HeaderExtensionObject : public ASF::File::BaseObject +{ +public: + List objects; + ByteVector guid(); + void parse(ASF::File *file, uint size); + ByteVector render(ASF::File *file); +}; + +void +ASF::File::BaseObject::parse(ASF::File *file, unsigned int size) +{ + data = file->readBlock(size - 24); +} + +ByteVector +ASF::File::BaseObject::render(ASF::File * /*file*/) +{ + return guid() + ByteVector::fromLongLong(data.size() + 24, false) + data; +} + +ASF::File::UnknownObject::UnknownObject(const ByteVector &guid) : myGuid(guid) +{ +} + +ByteVector +ASF::File::UnknownObject::guid() +{ + return myGuid; +} + +ByteVector +ASF::File::FilePropertiesObject::guid() +{ + return filePropertiesGuid; +} + +void +ASF::File::FilePropertiesObject::parse(ASF::File *file, uint size) +{ + BaseObject::parse(file, size); + file->d->properties->setLength((int)(data.mid(40, 8).toLongLong(false) / 10000000L - data.mid(56, 8).toLongLong(false) / 1000L)); +} + +ByteVector +ASF::File::StreamPropertiesObject::guid() +{ + return streamPropertiesGuid; +} + +void +ASF::File::StreamPropertiesObject::parse(ASF::File *file, uint size) +{ + BaseObject::parse(file, size); + file->d->properties->setChannels(data.mid(56, 2).toShort(false)); + file->d->properties->setSampleRate(data.mid(58, 4).toUInt(false)); + file->d->properties->setBitrate(data.mid(62, 4).toUInt(false) * 8 / 1000); +} + +ByteVector +ASF::File::ContentDescriptionObject::guid() +{ + return contentDescriptionGuid; +} + +void +ASF::File::ContentDescriptionObject::parse(ASF::File *file, uint /*size*/) +{ + file->d->contentDescriptionObject = this; + int titleLength = file->readWORD(); + int artistLength = file->readWORD(); + int copyrightLength = file->readWORD(); + int commentLength = file->readWORD(); + int ratingLength = file->readWORD(); + file->d->tag->setTitle(file->readString(titleLength)); + file->d->tag->setArtist(file->readString(artistLength)); + file->d->tag->setCopyright(file->readString(copyrightLength)); + file->d->tag->setComment(file->readString(commentLength)); + file->d->tag->setRating(file->readString(ratingLength)); +} + +ByteVector +ASF::File::ContentDescriptionObject::render(ASF::File *file) +{ + ByteVector v1 = file->renderString(file->d->tag->title()); + ByteVector v2 = file->renderString(file->d->tag->artist()); + ByteVector v3 = file->renderString(file->d->tag->copyright()); + ByteVector v4 = file->renderString(file->d->tag->comment()); + ByteVector v5 = file->renderString(file->d->tag->rating()); + data.clear(); + data.append(ByteVector::fromShort(v1.size(), false)); + data.append(ByteVector::fromShort(v2.size(), false)); + data.append(ByteVector::fromShort(v3.size(), false)); + data.append(ByteVector::fromShort(v4.size(), false)); + data.append(ByteVector::fromShort(v5.size(), false)); + data.append(v1); + data.append(v2); + data.append(v3); + data.append(v4); + data.append(v5); + return BaseObject::render(file); +} + +ByteVector +ASF::File::ExtendedContentDescriptionObject::guid() +{ + return extendedContentDescriptionGuid; +} + +void +ASF::File::ExtendedContentDescriptionObject::parse(ASF::File *file, uint /*size*/) +{ + file->d->extendedContentDescriptionObject = this; + int count = file->readWORD(); + while(count--) { + ASF::Attribute attribute; + String name = attribute.parse(*file); + file->d->tag->addAttribute(name, attribute); + } +} + +ByteVector +ASF::File::ExtendedContentDescriptionObject::render(ASF::File *file) +{ + data.clear(); + data.append(ByteVector::fromShort(attributeData.size(), false)); + data.append(attributeData.toByteVector(ByteVector::null)); + return BaseObject::render(file); +} + +ByteVector +ASF::File::MetadataObject::guid() +{ + return metadataGuid; +} + +void +ASF::File::MetadataObject::parse(ASF::File *file, uint /*size*/) +{ + file->d->metadataObject = this; + int count = file->readWORD(); + while(count--) { + ASF::Attribute attribute; + String name = attribute.parse(*file, 1); + file->d->tag->addAttribute(name, attribute); + } +} + +ByteVector +ASF::File::MetadataObject::render(ASF::File *file) +{ + data.clear(); + data.append(ByteVector::fromShort(attributeData.size(), false)); + data.append(attributeData.toByteVector(ByteVector::null)); + return BaseObject::render(file); +} + +ByteVector +ASF::File::MetadataLibraryObject::guid() +{ + return metadataLibraryGuid; +} + +void +ASF::File::MetadataLibraryObject::parse(ASF::File *file, uint /*size*/) +{ + file->d->metadataLibraryObject = this; + int count = file->readWORD(); + while(count--) { + ASF::Attribute attribute; + String name = attribute.parse(*file, 2); + file->d->tag->addAttribute(name, attribute); + } +} + +ByteVector +ASF::File::MetadataLibraryObject::render(ASF::File *file) +{ + data.clear(); + data.append(ByteVector::fromShort(attributeData.size(), false)); + data.append(attributeData.toByteVector(ByteVector::null)); + return BaseObject::render(file); +} + +ByteVector +ASF::File::HeaderExtensionObject::guid() +{ + return headerExtensionGuid; +} + +void +ASF::File::HeaderExtensionObject::parse(ASF::File *file, uint /*size*/) +{ + file->d->headerExtensionObject = this; + file->seek(18, File::Current); + long long dataSize = file->readDWORD(); + long long dataPos = 0; + while(dataPos < dataSize) { + ByteVector guid = file->readBlock(16); + long long size = file->readQWORD(); + BaseObject *obj; + if(guid == metadataGuid) { + obj = new MetadataObject(); + } + else if(guid == metadataLibraryGuid) { + obj = new MetadataLibraryObject(); + } + else { + obj = new UnknownObject(guid); + } + obj->parse(file, size); + objects.append(obj); + dataPos += size; + } +} + +ByteVector +ASF::File::HeaderExtensionObject::render(ASF::File *file) +{ + data.clear(); + for(unsigned int i = 0; i < objects.size(); i++) { + data.append(objects[i]->render(file)); + } + data = ByteVector("\x11\xD2\xD3\xAB\xBA\xA9\xcf\x11\x8E\xE6\x00\xC0\x0C\x20\x53\x65\x06\x00", 18) + ByteVector::fromUInt(data.size(), false) + data; + return BaseObject::render(file); +} + +//////////////////////////////////////////////////////////////////////////////// +// public members +//////////////////////////////////////////////////////////////////////////////// + +ASF::File::File(const char *file, bool readProperties, Properties::ReadStyle propertiesStyle) + : TagLib::File(file) +{ + d = new FilePrivate; + read(readProperties, propertiesStyle); +} + +ASF::File::~File() +{ + for(unsigned int i = 0; i < d->objects.size(); i++) { + delete d->objects[i]; + } + if(d->tag) { + delete d->tag; + } + if(d->properties) { + delete d->properties; + } + delete d; +} + +ASF::Tag *ASF::File::tag() const +{ + return d->tag; +} + +ASF::Properties *ASF::File::audioProperties() const +{ + return d->properties; +} + +void ASF::File::read(bool /*readProperties*/, Properties::ReadStyle /*propertiesStyle*/) +{ + if(!isValid()) + return; + + ByteVector guid = readBlock(16); + if(guid != headerGuid) { + return; + } + + d->tag = new ASF::Tag(); + d->properties = new ASF::Properties(); + + d->size = readQWORD(); + int numObjects = readDWORD(); + seek(2, Current); + + for(int i = 0; i < numObjects; i++) { + ByteVector guid = readBlock(16); + long size = (long)readQWORD(); + BaseObject *obj; + if(guid == filePropertiesGuid) { + obj = new FilePropertiesObject(); + } + else if(guid == streamPropertiesGuid) { + obj = new StreamPropertiesObject(); + } + else if(guid == contentDescriptionGuid) { + obj = new ContentDescriptionObject(); + } + else if(guid == extendedContentDescriptionGuid) { + obj = new ExtendedContentDescriptionObject(); + } + else if(guid == headerExtensionGuid) { + obj = new HeaderExtensionObject(); + } + else { + obj = new UnknownObject(guid); + } + obj->parse(this, size); + d->objects.append(obj); + } +} + +bool ASF::File::save() +{ + if(readOnly()) { + return false; + } + + if(!d->contentDescriptionObject) { + d->contentDescriptionObject = new ContentDescriptionObject(); + d->objects.append(d->contentDescriptionObject); + } + if(!d->extendedContentDescriptionObject) { + d->extendedContentDescriptionObject = new ExtendedContentDescriptionObject(); + d->objects.append(d->extendedContentDescriptionObject); + } + if(!d->headerExtensionObject) { + d->headerExtensionObject = new HeaderExtensionObject(); + d->objects.append(d->headerExtensionObject); + } + if(!d->metadataObject) { + d->metadataObject = new MetadataObject(); + d->headerExtensionObject->objects.append(d->metadataObject); + } + if(!d->metadataLibraryObject) { + d->metadataLibraryObject = new MetadataLibraryObject(); + d->headerExtensionObject->objects.append(d->metadataLibraryObject); + } + + ASF::AttributeListMap::ConstIterator it = d->tag->attributeListMap().begin(); + for(; it != d->tag->attributeListMap().end(); it++) { + const String &name = it->first; + const AttributeList &attributes = it->second; + bool inExtendedContentDescriptionObject = false; + bool inMetadataObject = false; + for(unsigned int j = 0; j < attributes.size(); j++) { + const Attribute &attribute = attributes[j]; + if(!inExtendedContentDescriptionObject && attribute.language() == 0 && attribute.stream() == 0) { + d->extendedContentDescriptionObject->attributeData.append(attribute.render(name)); + inExtendedContentDescriptionObject = true; + } + else if(!inMetadataObject && attribute.language() == 0 && attribute.stream() != 0) { + d->metadataObject->attributeData.append(attribute.render(name, 1)); + inMetadataObject = true; + } + else { + d->metadataLibraryObject->attributeData.append(attribute.render(name, 2)); + } + } + } + + ByteVector data; + for(unsigned int i = 0; i < d->objects.size(); i++) { + data.append(d->objects[i]->render(this)); + } + data = headerGuid + ByteVector::fromLongLong(data.size() + 30, false) + ByteVector::fromUInt(d->objects.size(), false) + ByteVector("\x01\x02", 2) + data; + insert(data, 0, d->size); + + return true; +} + +//////////////////////////////////////////////////////////////////////////////// +// protected members +//////////////////////////////////////////////////////////////////////////////// + +int ASF::File::readBYTE() +{ + ByteVector v = readBlock(1); + return v[0]; +} + +int ASF::File::readWORD() +{ + ByteVector v = readBlock(2); + return v.toShort(false); +} + +unsigned int ASF::File::readDWORD() +{ + ByteVector v = readBlock(4); + return v.toUInt(false); +} + +long long ASF::File::readQWORD() +{ + ByteVector v = readBlock(8); + return v.toLongLong(false); +} + +String +ASF::File::readString(int length) +{ + ByteVector data = readBlock(length); + unsigned int size = data.size(); + while (size >= 2) { + if(data[size - 1] != '\0' || data[size - 2] != '\0') { + break; + } + size -= 2; + } + if(size != data.size()) { + data.resize(size); + } + return String(data, String::UTF16LE); +} + +ByteVector +ASF::File::renderString(const String &str, bool includeLength) +{ + ByteVector data = str.data(String::UTF16LE) + ByteVector::fromShort(0, false); + if(includeLength) { + data = ByteVector::fromShort(data.size(), false) + data; + } + return data; +} diff --git a/amarok/src/metadata/asf/asffile.h b/amarok/src/metadata/asf/asffile.h new file mode 100644 index 00000000..db973313 --- /dev/null +++ b/amarok/src/metadata/asf/asffile.h @@ -0,0 +1,114 @@ +/************************************************************************** + copyright : (C) 2005-2007 by Lukáš Lalinský + email : lalinsky@gmail.com + **************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 * + * USA * + ***************************************************************************/ + +#ifndef TAGLIB_ASFFILE_H +#define TAGLIB_ASFFILE_H + +#include +#include +#include "asfproperties.h" +#include "asftag.h" + +namespace TagLib { + + //! An implementation of ASF (WMA) metadata + namespace ASF { + + /*! + * This implements and provides an interface for ASF files to the + * TagLib::Tag and TagLib::AudioProperties interfaces by way of implementing + * the abstract TagLib::File API as well as providing some additional + * information specific to ASF files. + */ + class File : public TagLib::File + { + public: + + /*! + * Contructs an ASF file from \a file. If \a readProperties is true the + * file's audio properties will also be read using \a propertiesStyle. If + * false, \a propertiesStyle is ignored. + * + * \note In the current implementation, both \a readProperties and + * \a propertiesStyle are ignored. + */ + File(const char *file, bool readProperties = true, Properties::ReadStyle propertiesStyle = Properties::Average); + + /*! + * Destroys this instance of the File. + */ + virtual ~File(); + + /*! + * Returns a pointer to the ASF tag of the file. + * + * ASF::Tag implements the tag interface, so this serves as the + * reimplementation of TagLib::File::tag(). + * + * \note The Tag is still owned by the ASF::File and should not be + * deleted by the user. It will be deleted when the file (object) is + * destroyed. + */ + virtual Tag *tag() const; + + /*! + * Returns the ASF audio properties for this file. + */ + virtual Properties *audioProperties() const; + + /*! + * Save the file. + * + * This returns true if the save was successful. + */ + virtual bool save(); + + private: + + int readBYTE(); + int readWORD(); + unsigned int readDWORD(); + long long readQWORD(); + static ByteVector renderString(const String &str, bool includeLength = false); + String readString(int len); + void read(bool readProperties, Properties::ReadStyle propertiesStyle); + + friend class Attribute; + + class BaseObject; + class UnknownObject; + class FilePropertiesObject; + class StreamPropertiesObject; + class ContentDescriptionObject; + class ExtendedContentDescriptionObject; + class HeaderExtensionObject; + class MetadataObject; + class MetadataLibraryObject; + + class FilePrivate; + FilePrivate *d; + }; + + } + +} + +#endif diff --git a/amarok/src/metadata/asf/asfproperties.cpp b/amarok/src/metadata/asf/asfproperties.cpp new file mode 100644 index 00000000..ca1d9830 --- /dev/null +++ b/amarok/src/metadata/asf/asfproperties.cpp @@ -0,0 +1,95 @@ +/************************************************************************** + copyright : (C) 2005-2007 by Lukáš Lalinský + email : lalinsky@gmail.com + **************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 * + * USA * + ***************************************************************************/ + +#include +#include "asfproperties.h" + +using namespace TagLib; + +class ASF::Properties::PropertiesPrivate +{ +public: + PropertiesPrivate(): length(0), bitrate(0), sampleRate(0), channels(0) {} + int length; + int bitrate; + int sampleRate; + int channels; +}; + +//////////////////////////////////////////////////////////////////////////////// +// public members +//////////////////////////////////////////////////////////////////////////////// + +ASF::Properties::Properties() : AudioProperties(AudioProperties::Average) +{ + d = new PropertiesPrivate; +} + +ASF::Properties::~Properties() +{ + if(d) + delete d; +} + +int ASF::Properties::length() const +{ + return d->length; +} + +int ASF::Properties::bitrate() const +{ + return d->bitrate; +} + +int ASF::Properties::sampleRate() const +{ + return d->sampleRate; +} + +int ASF::Properties::channels() const +{ + return d->channels; +} + +//////////////////////////////////////////////////////////////////////////////// +// private members +//////////////////////////////////////////////////////////////////////////////// + +void ASF::Properties::setLength(int length) +{ + d->length = length; +} + +void ASF::Properties::setBitrate(int length) +{ + d->bitrate = length; +} + +void ASF::Properties::setSampleRate(int length) +{ + d->sampleRate = length; +} + +void ASF::Properties::setChannels(int length) +{ + d->channels = length; +} + diff --git a/amarok/src/metadata/asf/asfproperties.h b/amarok/src/metadata/asf/asfproperties.h new file mode 100644 index 00000000..150db8ab --- /dev/null +++ b/amarok/src/metadata/asf/asfproperties.h @@ -0,0 +1,69 @@ +/************************************************************************** + copyright : (C) 2005-2007 by Lukáš Lalinský + email : lalinsky@gmail.com + **************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 * + * USA * + ***************************************************************************/ + +#ifndef TAGLIB_ASFPROPERTIES_H +#define TAGLIB_ASFPROPERTIES_H + +#include +#include + +namespace TagLib { + + namespace ASF { + + //! An implementation of ASF audio properties + class Properties : public AudioProperties + { + public: + + /*! + * Create an instance of ASF::Properties. + */ + Properties(); + + /*! + * Destroys this ASF::Properties instance. + */ + virtual ~Properties(); + + // Reimplementations. + virtual int length() const; + virtual int bitrate() const; + virtual int sampleRate() const; + virtual int channels() const; + +#ifndef DO_NOT_DOCUMENT + void setLength(int value); + void setBitrate(int value); + void setSampleRate(int value); + void setChannels(int value); +#endif + + private: + class PropertiesPrivate; + PropertiesPrivate *d; + }; + + } + +} + +#endif diff --git a/amarok/src/metadata/asf/asftag.cpp b/amarok/src/metadata/asf/asftag.cpp new file mode 100644 index 00000000..300887e6 --- /dev/null +++ b/amarok/src/metadata/asf/asftag.cpp @@ -0,0 +1,202 @@ +/************************************************************************** + copyright : (C) 2005-2007 by Lukáš Lalinský + email : lalinsky@gmail.com + **************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 * + * USA * + ***************************************************************************/ + +#include "asftag.h" + +using namespace TagLib; + +class ASF::Tag::TagPrivate +{ +public: + String title; + String artist; + String copyright; + String comment; + String rating; + AttributeListMap attributeListMap; +}; + +ASF::Tag::Tag() +: TagLib::Tag() +{ + d = new TagPrivate; +} + +ASF::Tag::~Tag() +{ + if(d) + delete d; +} + +String +ASF::Tag::title() const +{ + return d->title; +} + +String +ASF::Tag::artist() const +{ + return d->artist; +} + +String +ASF::Tag::album() const +{ + if(d->attributeListMap.contains("WM/AlbumTitle")) + return d->attributeListMap["WM/AlbumTitle"][0].toString(); + return String::null; +} + +String +ASF::Tag::copyright() const +{ + return d->copyright; +} + +String +ASF::Tag::comment() const +{ + return d->comment; +} + +String +ASF::Tag::rating() const +{ + return d->rating; +} + +unsigned int +ASF::Tag::year() const +{ + if(d->attributeListMap.contains("WM/Year")) + return d->attributeListMap["WM/Year"][0].toString().toInt(); + return 0; +} + +unsigned int +ASF::Tag::track() const +{ + if(d->attributeListMap.contains("WM/TrackNumber")) + return d->attributeListMap["WM/TrackNumber"][0].toString().toInt(); + if(d->attributeListMap.contains("WM/Track")) + return d->attributeListMap["WM/Track"][0].toUInt(); + return 0; +} + +String +ASF::Tag::genre() const +{ + if(d->attributeListMap.contains("WM/Genre")) + return d->attributeListMap["WM/Genre"][0].toString(); + return String::null; +} + +void +ASF::Tag::setTitle(const String &value) +{ + d->title = value; +} + +void +ASF::Tag::setArtist(const String &value) +{ + d->artist = value; +} + +void +ASF::Tag::setCopyright(const String &value) +{ + d->copyright = value; +} + +void +ASF::Tag::setComment(const String &value) +{ + d->comment = value; +} + +void +ASF::Tag::setRating(const String &value) +{ + d->rating = value; +} + +void +ASF::Tag::setAlbum(const String &value) +{ + setAttribute("WM/AlbumTitle", value); +} + +void +ASF::Tag::setGenre(const String &value) +{ + setAttribute("WM/Genre", value); +} + +void +ASF::Tag::setYear(uint value) +{ + setAttribute("WM/Year", String::number(value)); +} + +void +ASF::Tag::setTrack(uint value) +{ + setAttribute("WM/TrackNumber", String::number(value)); +} + +ASF::AttributeListMap& +ASF::Tag::attributeListMap() +{ + return d->attributeListMap; +} + +void ASF::Tag::removeItem(const String &key) +{ + AttributeListMap::Iterator it = d->attributeListMap.find(key); + if(it != d->attributeListMap.end()) + d->attributeListMap.erase(it); +} + +void ASF::Tag::setAttribute(const String &name, const Attribute &attribute) +{ + AttributeList value; + value.append(attribute); + d->attributeListMap.insert(name, value); +} + +void ASF::Tag::addAttribute(const String &name, const Attribute &attribute) +{ + if(d->attributeListMap.contains(name)) { + d->attributeListMap[name].append(attribute); + } + else { + setAttribute(name, attribute); + } +} + +bool ASF::Tag::isEmpty() const { + return TagLib::Tag::isEmpty() && + copyright().isEmpty() && + rating().isEmpty() && + d->attributeListMap.isEmpty(); +} diff --git a/amarok/src/metadata/asf/asftag.h b/amarok/src/metadata/asf/asftag.h new file mode 100644 index 00000000..f282dee9 --- /dev/null +++ b/amarok/src/metadata/asf/asftag.h @@ -0,0 +1,181 @@ +/************************************************************************** + copyright : (C) 2005-2007 by Lukáš Lalinský + email : lalinsky@gmail.com + **************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 * + * USA * + ***************************************************************************/ + +#ifndef TAGLIB_ASFTAG_H +#define TAGLIB_ASFTAG_H + +#include +#include +#include +#include "asfattribute.h" + +namespace TagLib { + + namespace ASF { + + typedef List AttributeList; + typedef Map AttributeListMap; + + class Tag : public TagLib::Tag { + + friend class File; + + public: + + Tag(); + + virtual ~Tag(); + + /*! + * Returns the track name. + */ + virtual String title() const; + + /*! + * Returns the artist name. + */ + virtual String artist() const; + + /*! + * Returns the album name; if no album name is present in the tag + * String::null will be returned. + */ + virtual String album() const; + + /*! + * Returns the track comment. + */ + virtual String comment() const; + + /*! + * Returns the genre name; if no genre is present in the tag String::null + * will be returned. + */ + virtual String genre() const; + + /*! + * Returns the rating. + */ + virtual String rating() const; + + /*! + * Returns the genre name; if no genre is present in the tag String::null + * will be returned. + */ + virtual String copyright() const; + + /*! + * Returns the year; if there is no year set, this will return 0. + */ + virtual uint year() const; + + /*! + * Returns the track number; if there is no track number set, this will + * return 0. + */ + virtual uint track() const; + + /*! + * Sets the title to \a s. + */ + virtual void setTitle(const String &s); + + /*! + * Sets the artist to \a s. + */ + virtual void setArtist(const String &s); + + /*! + * Sets the album to \a s. If \a s is String::null then this value will be + * cleared. + */ + virtual void setAlbum(const String &s); + + /*! + * Sets the comment to \a s. + */ + virtual void setComment(const String &s); + + /*! + * Sets the rating to \a s. + */ + virtual void setRating(const String &s); + + /*! + * Sets the copyright to \a s. + */ + virtual void setCopyright(const String &s); + + /*! + * Sets the genre to \a s. + */ + virtual void setGenre(const String &s); + + /*! + * Sets the year to \a i. If \a s is 0 then this value will be cleared. + */ + virtual void setYear(uint i); + + /*! + * Sets the track to \a i. If \a s is 0 then this value will be cleared. + */ + virtual void setTrack(uint i); + + /*! + * Returns true if the tag does not contain any data. This should be + * reimplemented in subclasses that provide more than the basic tagging + * abilities in this class. + */ + virtual bool isEmpty() const; + + /*! + * Returns a reference to the item list map. This is an AttributeListMap of + * all of the items in the tag. + * + * This is the most powerfull structure for accessing the items of the tag. + */ + AttributeListMap &attributeListMap(); + + /*! + * Removes the \a key attribute from the tag + */ + void removeItem(const String &name); + + /*! + * Sets the \a key attribute to the value of \a attribute. If an attribute + * with the \a key is already present, it will be replaced. + */ + void setAttribute(const String &name, const Attribute &attribute); + + /*! + * Sets the \a key attribute to the value of \a attribute. If an attribute + * with the \a key is already present, it will be added to the list. + */ + void addAttribute(const String &name, const Attribute &attribute); + + private: + + class TagPrivate; + TagPrivate *d; + }; + } +} +#endif diff --git a/amarok/src/metadata/asf/taglib_asffiletyperesolver.cpp b/amarok/src/metadata/asf/taglib_asffiletyperesolver.cpp new file mode 100644 index 00000000..f9ed059c --- /dev/null +++ b/amarok/src/metadata/asf/taglib_asffiletyperesolver.cpp @@ -0,0 +1,47 @@ +/*************************************************************************** + copyright : (C) 2005 by Martin Aumueller + email : aumuell@reserv.at + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301 USA * + ***************************************************************************/ + +// (c) 2005 Martin Aumueller +// See COPYING file for licensing information + +#include "taglib_asffiletyperesolver.h" +#include "asffile.h" + +#include + +TagLib::File *ASFFileTypeResolver::createFile(const char *fileName, + bool readProperties, + TagLib::AudioProperties::ReadStyle propertiesStyle) const +{ + const char *ext = strrchr(fileName, '.'); + if(ext && (!strcasecmp(ext, ".wma") || !strcasecmp(ext, ".asf"))) + { + TagLib::ASF::File *f = new TagLib::ASF::File(fileName, readProperties, propertiesStyle); + if(f->isValid()) + return f; + else + { + delete f; + } + } + + return 0; +} diff --git a/amarok/src/metadata/asf/taglib_asffiletyperesolver.h b/amarok/src/metadata/asf/taglib_asffiletyperesolver.h new file mode 100644 index 00000000..ab524b43 --- /dev/null +++ b/amarok/src/metadata/asf/taglib_asffiletyperesolver.h @@ -0,0 +1,42 @@ +/*************************************************************************** + copyright : (C) 2005 by Martin Aumueller + email : aumuell@reserv.at + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301 USA * + ***************************************************************************/ + +// (c) 2005 Martin Aumueller +// See COPYING file for licensing information + +#ifndef TAGLIB_ASFFILETYPERESOLVER_H +#define TAGLIB_ASFFILETYPERESOLVER_H + +#include +#include + + +class ASFFileTypeResolver : public TagLib::FileRef::FileTypeResolver +{ + TagLib::File *createFile(const char *fileName, + bool readAudioProperties, + TagLib::AudioProperties::ReadStyle audioPropertiesStyle) const; + +public: + virtual ~ASFFileTypeResolver() {} +}; + +#endif diff --git a/amarok/src/metadata/audible/Makefile.am b/amarok/src/metadata/audible/Makefile.am new file mode 100644 index 00000000..302eeb88 --- /dev/null +++ b/amarok/src/metadata/audible/Makefile.am @@ -0,0 +1,17 @@ +SUBDIRS = + +INCLUDES = $(all_includes) $(TAGLIB_CFLAGS) +METASOURCES = AUTO +libtagaudible_la_LDFLAGS = $(all_libraries) +noinst_LTLIBRARIES = libtagaudible.la + +libtagaudible_la_SOURCES = audibleproperties.cpp \ + audibletag.cpp \ + taglib_audiblefile.cpp \ + taglib_audiblefiletyperesolver.cpp + +noinst_HEADERS = audibleproperties.h \ + audibletag.h \ + taglib_audiblefile.h \ + taglib_audiblefiletyperesolver.h + diff --git a/amarok/src/metadata/audible/audibleproperties.cpp b/amarok/src/metadata/audible/audibleproperties.cpp new file mode 100644 index 00000000..4f393220 --- /dev/null +++ b/amarok/src/metadata/audible/audibleproperties.cpp @@ -0,0 +1,86 @@ +/*************************************************************************** + copyright : (C) 2005 by Martin Aumueller + email : aumuell@reserv.at + + copyright : (C) 2005 by Andy Leadbetter + email : andrew.leadbetter@gmail.com + (original mp4 implementation) + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301 USA * + ***************************************************************************/ + +#include + +#include "audibleproperties.h" + +#include + +#include "taglib_audiblefile.h" + +#include // ntohl + +using namespace TagLib; + + +//////////////////////////////////////////////////////////////////////////////// +// public members +//////////////////////////////////////////////////////////////////////////////// + +Audible::Properties::Properties(Properties::ReadStyle style) : AudioProperties(style) +{ + m_length = 0; + m_bitrate = 0; + m_sampleRate = 0; + m_channels = 0; +} + +Audible::Properties::~Properties() +{ +} + +int Audible::Properties::length() const +{ + return m_length; +} + +int Audible::Properties::bitrate() const +{ + return m_bitrate; +} + +int Audible::Properties::sampleRate() const +{ + return m_sampleRate; +} + +int Audible::Properties::channels() const +{ + return m_channels; +} + +#define LENGTH_OFF 61 + +void Audible::Properties::readAudibleProperties( FILE *fp, int off ) +{ + fseek(fp, off+LENGTH_OFF, SEEK_SET ); + fread(&m_length, sizeof(m_length), 1, fp); + m_length = ntohl(m_length); + //fprintf(stderr, "len (sec): %d\n", m_length); + m_bitrate = 0; + m_sampleRate = 0; + m_channels = 1; +} diff --git a/amarok/src/metadata/audible/audibleproperties.h b/amarok/src/metadata/audible/audibleproperties.h new file mode 100644 index 00000000..948fc303 --- /dev/null +++ b/amarok/src/metadata/audible/audibleproperties.h @@ -0,0 +1,85 @@ +/*************************************************************************** + copyright : (C) 2005 by Martin Aumueller + email : aumuell@reserv.at + + copyright : (C) 2005 by Andy Leadbetter + email : andrew.leadbetter@gmail.com + (original mp4 implementation) + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301 USA * + ***************************************************************************/ + +#ifndef TAGLIB_AUDIBLEPROPERTIES_H +#define TAGLIB_AUDIBLEPROPERTIES_H + +#include + +#include +#include + +namespace TagLib { + + namespace Audible { + + class File; + + /*! + * This reads the data from a Audible stream to support the + * AudioProperties API. + */ + + class Properties : public AudioProperties + { + public: + /*! + * Initialize this structure + */ + Properties(Properties::ReadStyle style); + + /*! + * Destroys this Audible Properties instance. + */ + virtual ~Properties(); + + // Reimplementations. + + virtual int length() const; + virtual int bitrate() const; + virtual int sampleRate() const; + virtual int channels() const; + + void readAudibleProperties(FILE *file, int off); + + + private: + void readAudioTrackProperties(FILE *file); + friend class Audible::File; + + int m_length; + int m_bitrate; + int m_sampleRate; + int m_channels; + + Properties(const Properties &); + Properties &operator=(const Properties &); + + void read(); + }; + } +} + +#endif diff --git a/amarok/src/metadata/audible/audibletag.cpp b/amarok/src/metadata/audible/audibletag.cpp new file mode 100644 index 00000000..f62f2155 --- /dev/null +++ b/amarok/src/metadata/audible/audibletag.cpp @@ -0,0 +1,162 @@ +/*************************************************************************** + copyright : (C) 2005 by Martin Aumueller + email : aumuell@reserv.at + + copyright : (C) 2005 by Andy Leadbetter + email : andrew.leadbetter@gmail.com + (original mp4 implementation) + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301 USA * + ***************************************************************************/ +#include + +#include "audibletag.h" + +#include + +#include // ntohl +#include +#include + +using namespace TagLib; + +Audible::Tag::Tag() : TagLib::Tag::Tag() { + m_title = String::null; + m_artist = String::null; + m_album = String::null; + m_comment = String::null; + m_genre = String::null; + m_year = 0; + m_track = 0; + m_userID = 0; + m_tagsEndOffset = -1; +} + +Audible::Tag::~Tag() { +} + +bool Audible::Tag::isEmpty() const { + return m_title == String::null && + m_artist == String::null && + m_album == String::null && + m_comment == String::null && + m_genre == String::null && + m_year == 0 && + m_track == 0 && + m_userID == 0; +} + +void Audible::Tag::duplicate(const Tag *source, Tag *target, bool overwrite) { + // No nonstandard information stored yet + Tag::duplicate(source, target, overwrite); +} + +#define OFF_PRODUCT_ID 197 +#define OFF_TAGS 189 + +void Audible::Tag::readTags( FILE *fp ) +{ + char buf[1023]; + fseek(fp, OFF_PRODUCT_ID, SEEK_SET); + fread(buf, strlen("product_id"), 1, fp); + if(memcmp(buf, "product_id", strlen("product_id"))) + { + buf[20]='\0'; + fprintf(stderr, "no valid Audible aa file: %s\n", buf); + return; + } + + // Now parse tag. + + fseek(fp, OFF_TAGS, SEEK_SET); + char *name, *value; + + m_tagsEndOffset = OFF_TAGS; + + bool lasttag = false; + while(!lasttag) + { + lasttag = !readTag(fp, &name, &value); + if(!strcmp(name, "title")) + { + m_title = String(value, String::Latin1); + } + else if(!strcmp(name, "author")) + { + m_artist = String(value, String::Latin1); + } + else if(!strcmp(name, "long_description")) + { + m_comment = String(value, String::Latin1); + } + else if(!strcmp(name, "description")) + { + if( m_comment.isNull() ) + m_comment = String(value, String::Latin1); + } + else if(!strcmp(name, "pubdate")) + { + m_year = 0; + char *p = strrchr(value, '-'); + if(p) + m_year = strtol(p+1, NULL, 10); + } + else if(!strcmp(name, "user_id")) + { + m_userID = strtol(value, NULL, 10); + } + + delete[] name; + delete[] value; + } + + m_album = String("", String::Latin1); + m_track = 0; + m_genre = String("Audiobook", String::Latin1); +} + +bool Audible::Tag::readTag( FILE *fp, char **name, char **value) +{ + uint32_t nlen; + fread(&nlen, sizeof(nlen), 1, fp); + nlen = ntohl(nlen); + //fprintf(stderr, "tagname len=%x\n", (unsigned)nlen); + *name = new char[nlen+1]; + (*name)[nlen] = '\0'; + + uint32_t vlen; + fread(&vlen, sizeof(vlen), 1, fp); + vlen = ntohl(vlen); + //fprintf(stderr, "tag len=%x\n", (unsigned)vlen); + *value = new char[vlen+1]; + (*value)[vlen] = '\0'; + + fread(*name, nlen, 1, fp); + fread(*value, vlen, 1, fp); + char lasttag; + fread(&lasttag, 1, 1, fp); + //fprintf(stderr, "%s: \"%s\"\n", *name, *value); + + m_tagsEndOffset += 2 * 4 + nlen + vlen + 1; + + return !lasttag; +} + +int Audible::Tag::getTagsEndOffset() +{ + return m_tagsEndOffset; +} diff --git a/amarok/src/metadata/audible/audibletag.h b/amarok/src/metadata/audible/audibletag.h new file mode 100644 index 00000000..f03966fa --- /dev/null +++ b/amarok/src/metadata/audible/audibletag.h @@ -0,0 +1,185 @@ +/*************************************************************************** + copyright : (C) 2005 by Martin Aumueller + email : aumuell@reserv.at + + copyright : (C) 2005 by Andy Leadbetter + email : andrew.leadbetter@gmail.com + (original mp4 implementation) + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301 USA * + ***************************************************************************/ + +#ifndef TAGLIB_AUDIBLETAG_H +#define TAGLIB_AUDIBLETAG_H + +#include + +#include +#include "taglib_audiblefile.h" + +namespace TagLib { + + namespace Audible { + /*! + * This implements the generic TagLib::Tag API + */ + class Tag : public TagLib::Tag + { + public: + Tag(); + + /*! + * read tags from the aa file. + */ + void readTags( FILE *file ); + + /*! + * Destroys this AudibleTag instance. + */ + virtual ~Tag(); + + /*! + * Returns the track name; if no track name is present in the tag + * String::null will be returned. + */ + virtual String title() const { return m_title; } + + /*! + * Returns the artist name; if no artist name is present in the tag + * String::null will be returned. + */ + virtual String artist() const { return m_artist; } + + /*! + * Returns the album name; if no album name is present in the tag + * String::null will be returned. + */ + virtual String album() const { return m_album; } + + /*! + * Returns the track comment; if no comment is present in the tag + * String::null will be returned. + */ + virtual String comment() const { return m_comment; } + + /*! + * Returns the genre name; if no genre is present in the tag String::null + * will be returned. + */ + virtual String genre() const { return m_genre; } + + /*! + * Returns the year; if there is no year set, this will return 0. + */ + virtual uint year() const { return m_year; } + + /*! + * Returns the track number; if there is no track number set, this will + * return 0. + */ + virtual uint track() const { return m_track; } + + /*! + * Returns the user id for this file. + */ + virtual uint userID() const { return m_userID; } + + /*! + * Sets the title to \a s. If \a s is String::null then this value will be + * cleared. + */ + virtual void setTitle(const String &s) { m_title = s; } + + /*! + * Sets the artist to \a s. If \a s is String::null then this value will be + * cleared. + */ + virtual void setArtist(const String &s) { m_artist = s; } + + /*! + * Sets the album to \a s. If \a s is String::null then this value will be + * cleared. + */ + virtual void setAlbum(const String &s) { m_album = s; } + + /*! + * Sets the album to \a s. If \a s is String::null then this value will be + * cleared. + */ + virtual void setComment(const String &s) { m_comment = s; } + + /*! + * Sets the genre to \a s. If \a s is String::null then this value will be + * cleared. For tag formats that use a fixed set of genres, the appropriate + * value will be selected based on a string comparison. A list of available + * genres for those formats should be available in that type's + * implementation. + */ + virtual void setGenre(const String &s) { m_genre = s; } + + /*! + * Sets the year to \a i. If \a s is 0 then this value will be cleared. + */ + virtual void setYear(uint i) { m_year = i; } + + /*! + * Sets the track to \a i. If \a s is 0 then this value will be cleared. + */ + virtual void setTrack(uint i) { m_track = i; } + + /*! + * Returns true if the tag does not contain any data. This should be + * reimplemented in subclasses that provide more than the basic tagging + * abilities in this class. + */ + virtual bool isEmpty() const; + + /*! + * Copies the generic data from one tag to another. + * + * \note This will not affect any of the lower level details of the tag. For + * instance if any of the tag type specific data (maybe a URL for a band) is + * set, this will not modify or copy that. This just copies using the API + * in this class. + * + * If \a overwrite is true then the values will be unconditionally copied. + * If false only empty values will be overwritten. + */ + static void duplicate(const Tag *source, Tag *target, bool overwrite = true); + + virtual void setUserID(uint id) { m_userID = id; } + + int getTagsEndOffset(); + + + + protected: + String m_title; + String m_artist; + String m_album; + String m_comment; + String m_genre; + uint m_year; + uint m_track; + uint m_userID; + bool readTag( FILE *fp, char **name, char **value); + int m_tagsEndOffset; + }; + } +} + +#endif diff --git a/amarok/src/metadata/audible/taglib_audiblefile.cpp b/amarok/src/metadata/audible/taglib_audiblefile.cpp new file mode 100644 index 00000000..47f5182f --- /dev/null +++ b/amarok/src/metadata/audible/taglib_audiblefile.cpp @@ -0,0 +1,121 @@ +/*************************************************************************** + copyright : (C) 2005 by Martin Aumueller + email : aumuell@reserv.at + + copyright : (C) 2005 by Andy Leadbetter + email : andrew.leadbetter@gmail.com + (original mp4 implementation) + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301 USA * + ***************************************************************************/ + +#include + +#include "taglib_audiblefile.h" + +#include "audibletag.h" +#include +#include + +namespace TagLib { +//////////////////////////////////////////////////////////////////////////////// +// public members +//////////////////////////////////////////////////////////////////////////////// + +Audible::File::File(const char *file, + bool readProperties, + Properties::ReadStyle propertiesStyle, + FILE *fp) + : TagLib::File(file) + , audibletag( NULL ) + , properties( NULL ) +{ + + // debug ("Audible::File: create new file object."); + //debug ( file ); + + /** + * Create the Audible file. + */ + + if(fp) + audiblefile = fp; + else + audiblefile = fopen(file, "rb"); + + if( isOpen() ) + { + read(readProperties, propertiesStyle ); + } +} + +Audible::File::~File() +{ + if(audiblefile) + fclose(audiblefile); + delete audibletag; + delete properties; +} + +TagLib::Tag *Audible::File::tag() const +{ + return audibletag; +} + +TagLib::Audible::Tag *Audible::File::getAudibleTag() const +{ + return audibletag; +} + +Audible::Properties *Audible::File::audioProperties() const +{ + return properties; +} + +bool Audible::File::save() +{ + return false; +} + +bool Audible::File::isOpen() +{ + return audiblefile != NULL; +} + +//////////////////////////////////////////////////////////////////////////////// +// private members +//////////////////////////////////////////////////////////////////////////////// + +void Audible::File::read(bool readProperties, Properties::ReadStyle propertiesStyle) +{ + properties = new Audible::Properties(propertiesStyle); + audibletag = new Audible::Tag(); + + if (audiblefile != NULL) { + audibletag->readTags( audiblefile ); + int off = audibletag->getTagsEndOffset(); + //fprintf(stderr, "off=%d\n", off); + + if(readProperties) + { + // Parse bitrate etc. + properties->readAudibleProperties( audiblefile, off ); + } + } +} + +} diff --git a/amarok/src/metadata/audible/taglib_audiblefile.h b/amarok/src/metadata/audible/taglib_audiblefile.h new file mode 100644 index 00000000..c1860ae0 --- /dev/null +++ b/amarok/src/metadata/audible/taglib_audiblefile.h @@ -0,0 +1,93 @@ +/*************************************************************************** + copyright : (C) 2005 by Martin Aumueller + email : aumuell@reserv.at + + copyright : (C) 2005 by Andy Leadbetter + email : andrew.leadbetter@gmail.com + (original mp4 implementation) + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301 USA * + ***************************************************************************/ + +#ifndef TAGLIB_AUDIBLEFILE_H +#define TAGLIB_AUDIBLEFILE_H + +#include +#include "audibleproperties.h" +#include "audibletag.h" + +namespace TagLib { + + namespace Audible { + + class Tag; + + class File : public TagLib::File + { + public: + /*! + * Contructs a Audible file from \a file. If \a readProperties is true the + * file's audio properties will also be read using \a propertiesStyle. If + * false, \a propertiesStyle is ignored. + */ + File(const char *file, bool readProperties = true, + Properties::ReadStyle propertiesStyle = Properties::Average, + FILE *fp=NULL); + + /*! + * Destroys this instance of the File. + */ + virtual ~File(); + + + virtual TagLib::Tag *tag() const; + + /*! + * Returns the Audible::Properties for this file. If no audio properties + * were read then this will return a null pointer. + */ + virtual Audible::Properties *audioProperties() const; + + /*! + * Save the file. + * This is the same as calling save(AllTags); + * + * \note As of now, saving Audible tags is not supported. + */ + virtual bool save(); + + void read(bool readProperties, Properties::ReadStyle propertiesStyle); + + Audible::Tag *getAudibleTag() const; + + bool isAudibleFile() const; + + protected: + File(const File &); + File &operator=(const File &); + bool isOpen(); + + + Audible::Tag *audibletag; + Audible::Properties *properties; + + FILE *audiblefile; + }; + } +} + +#endif diff --git a/amarok/src/metadata/audible/taglib_audiblefiletyperesolver.cpp b/amarok/src/metadata/audible/taglib_audiblefiletyperesolver.cpp new file mode 100644 index 00000000..152b17cb --- /dev/null +++ b/amarok/src/metadata/audible/taglib_audiblefiletyperesolver.cpp @@ -0,0 +1,44 @@ +/*************************************************************************** + copyright : (C) 2005 by Martin Aumueller + email : aumuell@reserv.at + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301 USA * + ***************************************************************************/ + +#include + +#include "taglib_audiblefiletyperesolver.h" +#include "taglib_audiblefile.h" + +#include + +TagLib::File *AudibleFileTypeResolver::createFile(const char *fileName, + bool readProperties, + TagLib::AudioProperties::ReadStyle propertiesStyle) const +{ + const char *ext = strrchr(fileName, '.'); + if(ext && !strcasecmp(ext, ".aa")) + { + FILE *fp = fopen(fileName, "rb"); + if(!fp) + return 0; + + return new TagLib::Audible::File(fileName, readProperties, propertiesStyle, fp); + } + + return 0; +} diff --git a/amarok/src/metadata/audible/taglib_audiblefiletyperesolver.h b/amarok/src/metadata/audible/taglib_audiblefiletyperesolver.h new file mode 100644 index 00000000..6f1e7059 --- /dev/null +++ b/amarok/src/metadata/audible/taglib_audiblefiletyperesolver.h @@ -0,0 +1,36 @@ +/*************************************************************************** + copyright : (C) 2005 by Martin Aumueller + email : aumuell@reserv.at + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301 USA * + ***************************************************************************/ + +#ifndef TAGLIB_AUDIBLEFILETYPERESOLVER_H +#define TAGLIB_AUDIBLEFILETYPERESOLVER_H + +#include +#include + + +class AudibleFileTypeResolver : public TagLib::FileRef::FileTypeResolver +{ + TagLib::File *createFile(const char *fileName, + bool readAudioProperties, + TagLib::AudioProperties::ReadStyle audioPropertiesStyle) const; +}; + +#endif diff --git a/amarok/src/metadata/m4a/Makefile.am b/amarok/src/metadata/m4a/Makefile.am new file mode 100644 index 00000000..534fbcae --- /dev/null +++ b/amarok/src/metadata/m4a/Makefile.am @@ -0,0 +1,84 @@ +SUBDIRS = +METASOURCES = AUTO +INCLUDES = $(all_includes) $(TAGLIB_CFLAGS) + +libtagm4a_la_LDFLAGS = $(all_libraries) +noinst_LTLIBRARIES = libtagm4a.la + +libtagm4a_la_SOURCES = \ + taglib_mp4filetyperesolver.cpp \ + mp4file.cpp \ + mp4itunestag.cpp \ + mp4isobox.cpp \ + mp4isofullbox.cpp \ + mp4skipbox.cpp \ + mp4moovbox.cpp \ + mp4mvhdbox.cpp \ + mp4ilstbox.cpp \ + boxfactory.cpp \ + mp4fourcc.cpp \ + mp4udtabox.cpp \ + mp4metabox.cpp \ + mp4tagsproxy.cpp \ + mp4mdiabox.cpp \ + mp4minfbox.cpp \ + mp4audioproperties.cpp \ + mp4hdlrbox.cpp \ + mp4stblbox.cpp \ + mp4audiosampleentry.cpp \ + mp4stsdbox.cpp \ + mp4sampleentry.cpp \ + mp4trakbox.cpp \ + mp4propsproxy.cpp \ + itunesnambox.cpp \ + itunesartbox.cpp \ + itunesalbbox.cpp \ + itunescvrbox.cpp \ + itunesgenbox.cpp \ + itunestrknbox.cpp \ + itunesdaybox.cpp \ + itunescmtbox.cpp \ + itunesgrpbox.cpp \ + ituneswrtbox.cpp \ + itunesdiskbox.cpp \ + itunestmpobox.cpp \ + itunesdatabox.cpp + +noinst_HEADERS = \ + taglib_mp4filetyperesolver.h \ + mp4file.h \ + mp4itunestag.h \ + mp4isobox.h \ + mp4isofullbox.h \ + mp4skipbox.h \ + mp4moovbox.h \ + mp4mvhdbox.h \ + mp4ilstbox.h \ + boxfactory.h \ + mp4fourcc.h \ + mp4udtabox.h \ + mp4metabox.h \ + mp4tagsproxy.h \ + mp4audioproperties.h \ + mp4hdlrbox.h \ + mp4propsproxy.h \ + mp4mdiabox.h \ + mp4stsdbox.h \ + mp4trakbox.h \ + mp4stblbox.h \ + mp4audiosampleentry.h \ + mp4minfbox.h \ + mp4sampleentry.h \ + itunesnambox.h \ + itunesartbox.h \ + itunesalbbox.h \ + itunesgenbox.h \ + itunestrknbox.h \ + itunesdaybox.h \ + itunescmtbox.h \ + itunescvrbox.h \ + itunesgrpbox.h \ + ituneswrtbox.h \ + itunesdiskbox.h \ + itunestmpobox.h \ + itunesdatabox.h diff --git a/amarok/src/metadata/m4a/boxfactory.cpp b/amarok/src/metadata/m4a/boxfactory.cpp new file mode 100644 index 00000000..0fc8eb4d --- /dev/null +++ b/amarok/src/metadata/m4a/boxfactory.cpp @@ -0,0 +1,150 @@ +/*************************************************************************** + copyright : (C) 2002, 2003, 2006 by Jochen Issing + email : jochen.issing@isign-softart.de + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301 USA * + ***************************************************************************/ + +#include +#include "tstring.h" +#include "boxfactory.h" +#include "mp4skipbox.h" +#include "mp4moovbox.h" +#include "mp4mvhdbox.h" +#include "mp4trakbox.h" +#include "mp4mdiabox.h" +#include "mp4minfbox.h" +#include "mp4stblbox.h" +#include "mp4stsdbox.h" +#include "mp4hdlrbox.h" +#include "mp4udtabox.h" +#include "mp4metabox.h" +#include "mp4ilstbox.h" +#include "itunesnambox.h" +#include "itunesartbox.h" +#include "itunesalbbox.h" +#include "itunesgenbox.h" +#include "itunesdaybox.h" +#include "itunestrknbox.h" +#include "itunescmtbox.h" +#include "itunesgrpbox.h" +#include "ituneswrtbox.h" +#include "itunesdiskbox.h" +#include "itunestmpobox.h" +#include "itunescvrbox.h" +#include "itunesdatabox.h" + +using namespace TagLib; + +MP4::BoxFactory::BoxFactory() +{ +} + +MP4::BoxFactory::~BoxFactory() +{ +} + +//! factory function +MP4::Mp4IsoBox* MP4::BoxFactory::createInstance( TagLib::File* anyfile, MP4::Fourcc fourcc, uint size, long offset ) const +{ + MP4::File * file = dynamic_cast(anyfile); + if(!file) + return 0; + + //std::cout << "creating box for: " << fourcc.toString() << std::endl; + + switch( fourcc ) + { + case 0x6d6f6f76: // 'moov' + return new MP4::Mp4MoovBox( file, fourcc, size, offset ); + break; + case 0x6d766864: // 'mvhd' + return new MP4::Mp4MvhdBox( file, fourcc, size, offset ); + break; + case 0x7472616b: // 'trak' + return new MP4::Mp4TrakBox( file, fourcc, size, offset ); + break; + case 0x6d646961: // 'mdia' + return new MP4::Mp4MdiaBox( file, fourcc, size, offset ); + break; + case 0x6d696e66: // 'minf' + return new MP4::Mp4MinfBox( file, fourcc, size, offset ); + break; + case 0x7374626c: // 'stbl' + return new MP4::Mp4StblBox( file, fourcc, size, offset ); + break; + case 0x73747364: // 'stsd' + return new MP4::Mp4StsdBox( file, fourcc, size, offset ); + break; + case 0x68646c72: // 'hdlr' + return new MP4::Mp4HdlrBox( file, fourcc, size, offset ); + break; + case 0x75647461: // 'udta' + return new MP4::Mp4UdtaBox( file, fourcc, size, offset ); + break; + case 0x6d657461: // 'meta' + return new MP4::Mp4MetaBox( file, fourcc, size, offset ); + break; + case 0x696c7374: // 'ilst' + return new MP4::Mp4IlstBox( file, fourcc, size, offset ); + break; + case 0xa96e616d: // '_nam' + return new MP4::ITunesNamBox( file, fourcc, size, offset ); + break; + case 0xa9415254: // '_ART' + return new MP4::ITunesArtBox( file, fourcc, size, offset ); + break; + case 0xa9616c62: // '_alb' + return new MP4::ITunesAlbBox( file, fourcc, size, offset ); + break; + case 0xa967656e: // '_gen' + return new MP4::ITunesGenBox( file, fourcc, size, offset ); + break; + case 0x676e7265: // 'gnre' + return new MP4::ITunesGenBox( file, fourcc, size, offset ); + break; + case 0xa9646179: // '_day' + return new MP4::ITunesDayBox( file, fourcc, size, offset ); + break; + case 0x74726b6e: // 'trkn' + return new MP4::ITunesTrknBox( file, fourcc, size, offset ); + break; + case 0xa9636d74: // '_cmt' + return new MP4::ITunesCmtBox( file, fourcc, size, offset ); + break; + case 0xa9677270: // '_grp' + return new MP4::ITunesGrpBox( file, fourcc, size, offset ); + break; + case 0xa9777274: // '_wrt' + return new MP4::ITunesWrtBox( file, fourcc, size, offset ); + break; + case 0x6469736b: // 'disk' + return new MP4::ITunesDiskBox( file, fourcc, size, offset ); + break; + case 0x746d706f: // 'tmpo' + return new MP4::ITunesTmpoBox( file, fourcc, size, offset ); + break; + case 0x636f7672: // 'covr' + return new MP4::ITunesCvrBox( file, fourcc, size, offset ); + break; + case 0x64616461: // 'data' + return new MP4::ITunesDataBox( file, fourcc, size, offset ); + break; + default: + return new MP4::Mp4SkipBox( file, fourcc, size, offset ); + } +} diff --git a/amarok/src/metadata/m4a/boxfactory.h b/amarok/src/metadata/m4a/boxfactory.h new file mode 100644 index 00000000..7edcd6c9 --- /dev/null +++ b/amarok/src/metadata/m4a/boxfactory.h @@ -0,0 +1,45 @@ +/*************************************************************************** + copyright : (C) 2002, 2003, 2006 by Jochen Issing + email : jochen.issing@isign-softart.de + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301 USA * + ***************************************************************************/ + +#ifndef BOXFACTORY_H +#define BOXFACTORY_H + +#include "taglib.h" +#include "mp4isobox.h" + +namespace TagLib +{ + namespace MP4 + { + class BoxFactory + { + public: + BoxFactory(); + ~BoxFactory(); + + //! factory function + Mp4IsoBox* createInstance( TagLib::File* anyfile, MP4::Fourcc fourcc, uint size, long offset ) const; + }; // class BoxFactory + + } // namepace MP4 +} // namepace TagLib + +#endif // BOXFACTORY_H diff --git a/amarok/src/metadata/m4a/itunesalbbox.cpp b/amarok/src/metadata/m4a/itunesalbbox.cpp new file mode 100644 index 00000000..5832fa0b --- /dev/null +++ b/amarok/src/metadata/m4a/itunesalbbox.cpp @@ -0,0 +1,89 @@ +/*************************************************************************** + copyright : (C) 2002, 2003, 2006 by Jochen Issing + email : jochen.issing@isign-softart.de + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301 USA * + ***************************************************************************/ + +#include +#include "itunesalbbox.h" +#include "itunesdatabox.h" +#include "mp4isobox.h" +#include "mp4file.h" +#include "tfile.h" +#include "mp4tagsproxy.h" + +using namespace TagLib; + +class MP4::ITunesAlbBox::ITunesAlbBoxPrivate +{ +public: + ITunesDataBox* dataBox; +}; + +MP4::ITunesAlbBox::ITunesAlbBox( TagLib::File* file, MP4::Fourcc fourcc, uint size, long offset ) + :Mp4IsoBox(file, fourcc, size, offset) +{ + d = new MP4::ITunesAlbBox::ITunesAlbBoxPrivate(); + d->dataBox = 0; +} + +MP4::ITunesAlbBox::~ITunesAlbBox() +{ + if( d->dataBox != 0 ) + delete d->dataBox; + delete d; +} + +//! parse the content of the box +void MP4::ITunesAlbBox::parse() +{ + TagLib::MP4::File* mp4file = static_cast( file() ); + + // parse data box + TagLib::uint size; + MP4::Fourcc fourcc; + + if(mp4file->readSizeAndType( size, fourcc ) == true) + { + // check for type - must be 'data' + if( fourcc != MP4::Fourcc("data") ) + { + std::cerr << "bad atom in itunes tag - skipping it." << std::endl; + // jump over data tag + mp4file->seek( size-8, TagLib::File::Current ); + return; + } + d->dataBox = new ITunesDataBox( mp4file, fourcc, size, mp4file->tell() ); + d->dataBox->parsebox(); + } + else + { + // reading unsuccessful - serious error! + std::cerr << "Error in parsing ITunesAlbBox - serious Error in taglib!" << std::endl; + return; + } + // register data box + mp4file->tagProxy()->registerBox( Mp4TagsProxy::album, d->dataBox ); + +#if 0 + // get data pointer - just for debugging... + TagLib::String dataString( d->dataBox->data() ); + std::cout << "Content of album box: " << dataString << std::endl; +#endif +} + diff --git a/amarok/src/metadata/m4a/itunesalbbox.h b/amarok/src/metadata/m4a/itunesalbbox.h new file mode 100644 index 00000000..f2462c28 --- /dev/null +++ b/amarok/src/metadata/m4a/itunesalbbox.h @@ -0,0 +1,50 @@ +/*************************************************************************** + copyright : (C) 2002, 2003, 2006 by Jochen Issing + email : jochen.issing@isign-softart.de + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301 USA * + ***************************************************************************/ + +#ifndef ITUNESALBBOX_H +#define ITUNESALBBOX_H + +#include "mp4isobox.h" +#include "mp4fourcc.h" + +namespace TagLib +{ + namespace MP4 + { + class ITunesAlbBox: public Mp4IsoBox + { + public: + ITunesAlbBox( TagLib::File* file, MP4::Fourcc fourcc, uint size, long offset ); + ~ITunesAlbBox(); + + private: + //! parse the content of the box + virtual void parse(); + + protected: + class ITunesAlbBoxPrivate; + ITunesAlbBoxPrivate* d; + }; // class ITunesAlbBox + + } // namespace MP4 +} // namespace TagLib + +#endif // ITUNESALBBOX_H diff --git a/amarok/src/metadata/m4a/itunesartbox.cpp b/amarok/src/metadata/m4a/itunesartbox.cpp new file mode 100644 index 00000000..19e717d1 --- /dev/null +++ b/amarok/src/metadata/m4a/itunesartbox.cpp @@ -0,0 +1,89 @@ +/*************************************************************************** + copyright : (C) 2002, 2003, 2006 by Jochen Issing + email : jochen.issing@isign-softart.de + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301 USA * + ***************************************************************************/ + +#include +#include "itunesartbox.h" +#include "itunesdatabox.h" +#include "mp4isobox.h" +#include "mp4file.h" +#include "tfile.h" +#include "mp4tagsproxy.h" + +using namespace TagLib; + +class MP4::ITunesArtBox::ITunesArtBoxPrivate +{ +public: + ITunesDataBox* dataBox; +}; + +MP4::ITunesArtBox::ITunesArtBox( TagLib::File* file, MP4::Fourcc fourcc, uint size, long offset ) + :Mp4IsoBox(file, fourcc, size, offset) +{ + d = new MP4::ITunesArtBox::ITunesArtBoxPrivate(); + d->dataBox = 0; +} + +MP4::ITunesArtBox::~ITunesArtBox() +{ + if( d->dataBox != 0 ) + delete d->dataBox; + delete d; +} + +//! parse the content of the box +void MP4::ITunesArtBox::parse() +{ + TagLib::MP4::File* mp4file = static_cast( file() ); + + // parse data box + TagLib::uint size; + MP4::Fourcc fourcc; + + if(mp4file->readSizeAndType( size, fourcc ) == true) + { + // check for type - must be 'data' + if( fourcc != MP4::Fourcc("data") ) + { + std::cerr << "bad atom in itunes tag - skipping it." << std::endl; + // jump over data tag + mp4file->seek( size-8, TagLib::File::Current ); + return; + } + d->dataBox = new ITunesDataBox( mp4file, fourcc, size, mp4file->tell() ); + d->dataBox->parsebox(); + } + else + { + // reading unsuccessful - serious error! + std::cerr << "Error in parsing ITunesArtBox - serious Error in taglib!" << std::endl; + return; + } + // register data box + mp4file->tagProxy()->registerBox( Mp4TagsProxy::artist, d->dataBox ); + +#if 0 + // get data pointer - just for debugging... + TagLib::String dataString( d->dataBox->data() ); + std::cout << "Content of artist box: " << dataString << std::endl; +#endif +} + diff --git a/amarok/src/metadata/m4a/itunesartbox.h b/amarok/src/metadata/m4a/itunesartbox.h new file mode 100644 index 00000000..5d197aac --- /dev/null +++ b/amarok/src/metadata/m4a/itunesartbox.h @@ -0,0 +1,50 @@ +/*************************************************************************** + copyright : (C) 2002, 2003, 2006 by Jochen Issing + email : jochen.issing@isign-softart.de + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301 USA * + ***************************************************************************/ + +#ifndef ITUNESARTBOX_H +#define ITUNESARTBOX_H + +#include "mp4isobox.h" +#include "mp4fourcc.h" + +namespace TagLib +{ + namespace MP4 + { + class ITunesArtBox: public Mp4IsoBox + { + public: + ITunesArtBox( TagLib::File* file, MP4::Fourcc fourcc, uint size, long offset ); + ~ITunesArtBox(); + + private: + //! parse the content of the box + virtual void parse(); + + protected: + class ITunesArtBoxPrivate; + ITunesArtBoxPrivate* d; + }; // class ITunesArtBox + + } // namespace MP4 +} // namespace TagLib + +#endif // ITUNESARTBOX_H diff --git a/amarok/src/metadata/m4a/itunescmtbox.cpp b/amarok/src/metadata/m4a/itunescmtbox.cpp new file mode 100644 index 00000000..c79f0f7e --- /dev/null +++ b/amarok/src/metadata/m4a/itunescmtbox.cpp @@ -0,0 +1,89 @@ +/*************************************************************************** + copyright : (C) 2002, 2003, 2006 by Jochen Issing + email : jochen.issing@isign-softart.de + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301 USA * + ***************************************************************************/ + +#include +#include "itunescmtbox.h" +#include "itunesdatabox.h" +#include "mp4isobox.h" +#include "mp4file.h" +#include "tfile.h" +#include "mp4tagsproxy.h" + +using namespace TagLib; + +class MP4::ITunesCmtBox::ITunesCmtBoxPrivate +{ +public: + ITunesDataBox* dataBox; +}; + +MP4::ITunesCmtBox::ITunesCmtBox( TagLib::File* file, MP4::Fourcc fourcc, uint size, long offset ) + :Mp4IsoBox(file, fourcc, size, offset) +{ + d = new MP4::ITunesCmtBox::ITunesCmtBoxPrivate(); + d->dataBox = 0; +} + +MP4::ITunesCmtBox::~ITunesCmtBox() +{ + if( d->dataBox != 0 ) + delete d->dataBox; + delete d; +} + +//! parse the content of the box +void MP4::ITunesCmtBox::parse() +{ + TagLib::MP4::File* mp4file = static_cast( file() ); + + // parse data box + TagLib::uint size; + MP4::Fourcc fourcc; + + if(mp4file->readSizeAndType( size, fourcc ) == true) + { + // check for type - must be 'data' + if( fourcc != MP4::Fourcc("data") ) + { + std::cerr << "bad atom in itunes tag - skipping it." << std::endl; + // jump over data tag + mp4file->seek( size-8, TagLib::File::Current ); + return; + } + d->dataBox = new ITunesDataBox( mp4file, fourcc, size, mp4file->tell() ); + d->dataBox->parsebox(); + } + else + { + // reading unsuccessful - serious error! + std::cerr << "Error in parsing ITunesCmtBox - serious Error in taglib!" << std::endl; + return; + } + // register data box + mp4file->tagProxy()->registerBox( Mp4TagsProxy::comment, d->dataBox ); + +#if 0 + // get data pointer - just for debugging... + TagLib::String dataString( d->dataBox->data() ); + std::cout << "Content of title box: " << dataString << std::endl; +#endif +} + diff --git a/amarok/src/metadata/m4a/itunescmtbox.h b/amarok/src/metadata/m4a/itunescmtbox.h new file mode 100644 index 00000000..83ad65d6 --- /dev/null +++ b/amarok/src/metadata/m4a/itunescmtbox.h @@ -0,0 +1,50 @@ +/*************************************************************************** + copyright : (C) 2002, 2003, 2006 by Jochen Issing + email : jochen.issing@isign-softart.de + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301 USA * + ***************************************************************************/ + +#ifndef ITUNESCMTBOX_H +#define ITUNESCMTBOX_H + +#include "mp4isobox.h" +#include "mp4fourcc.h" + +namespace TagLib +{ + namespace MP4 + { + class ITunesCmtBox: public Mp4IsoBox + { + public: + ITunesCmtBox( TagLib::File* file, MP4::Fourcc fourcc, uint size, long offset ); + ~ITunesCmtBox(); + + private: + //! parse the content of the box + virtual void parse(); + + protected: + class ITunesCmtBoxPrivate; + ITunesCmtBoxPrivate* d; + }; // class ITunesCmtBox + + } // namespace MP4 +} // namespace TagLib + +#endif // ITUNESCMTBOX_H diff --git a/amarok/src/metadata/m4a/itunescvrbox.cpp b/amarok/src/metadata/m4a/itunescvrbox.cpp new file mode 100644 index 00000000..4a7b3dbb --- /dev/null +++ b/amarok/src/metadata/m4a/itunescvrbox.cpp @@ -0,0 +1,89 @@ +/*************************************************************************** + copyright : (C) 2002, 2003, 2006 by Jochen Issing + email : jochen.issing@isign-softart.de + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301 USA * + ***************************************************************************/ + +#include +#include "itunescvrbox.h" +#include "itunesdatabox.h" +#include "mp4isobox.h" +#include "mp4file.h" +#include "tfile.h" +#include "mp4tagsproxy.h" + +using namespace TagLib; + +class MP4::ITunesCvrBox::ITunesCvrBoxPrivate +{ +public: + ITunesDataBox* dataBox; +}; + +MP4::ITunesCvrBox::ITunesCvrBox( TagLib::File* file, MP4::Fourcc fourcc, uint size, long offset ) + :Mp4IsoBox(file, fourcc, size, offset) +{ + d = new MP4::ITunesCvrBox::ITunesCvrBoxPrivate(); + d->dataBox = 0; +} + +MP4::ITunesCvrBox::~ITunesCvrBox() +{ + if( d->dataBox != 0 ) + delete d->dataBox; + delete d; +} + +//! parse the content of the box +void MP4::ITunesCvrBox::parse() +{ + TagLib::MP4::File* mp4file = static_cast( file() ); + + // parse data box + TagLib::uint size; + MP4::Fourcc fourcc; + + if(mp4file->readSizeAndType( size, fourcc ) == true) + { + // check for type - must be 'data' + if( fourcc != MP4::Fourcc("data") ) + { + std::cerr << "bad atom in itunes tag - skipping it." << std::endl; + // jump over data tag + mp4file->seek( size-8, TagLib::File::Current ); + return; + } + d->dataBox = new ITunesDataBox( mp4file, fourcc, size, mp4file->tell() ); + d->dataBox->parsebox(); + } + else + { + // reading unsuccessful - serious error! + std::cerr << "Error in parsing ITunesCvrBox - serious Error in taglib!" << std::endl; + return; + } + // register data box + mp4file->tagProxy()->registerBox( Mp4TagsProxy::cover, d->dataBox ); + +#if 0 + // get data pointer - just for debugging... + TagLib::String dataString( d->dataBox->data() ); + std::cout << "Content of album box: " << dataString << std::endl; +#endif +} + diff --git a/amarok/src/metadata/m4a/itunescvrbox.h b/amarok/src/metadata/m4a/itunescvrbox.h new file mode 100644 index 00000000..a2693c26 --- /dev/null +++ b/amarok/src/metadata/m4a/itunescvrbox.h @@ -0,0 +1,50 @@ +/*************************************************************************** + copyright : (C) 2002, 2003, 2006 by Jochen Issing + email : jochen.issing@isign-softart.de + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301 USA * + ***************************************************************************/ + +#ifndef ITUNESCVRBOX_H +#define ITUNESCVRBOX_H + +#include "mp4isobox.h" +#include "mp4fourcc.h" + +namespace TagLib +{ + namespace MP4 + { + class ITunesCvrBox: public Mp4IsoBox + { + public: + ITunesCvrBox( TagLib::File* file, MP4::Fourcc fourcc, uint size, long offset ); + ~ITunesCvrBox(); + + private: + //! parse the content of the box + virtual void parse(); + + protected: + class ITunesCvrBoxPrivate; + ITunesCvrBoxPrivate* d; + }; // class ITunesCvrBox + + } // namespace MP4 +} // namespace TagLib + +#endif // ITUNESCVRBOX_H diff --git a/amarok/src/metadata/m4a/itunesdatabox.cpp b/amarok/src/metadata/m4a/itunesdatabox.cpp new file mode 100644 index 00000000..7565a42a --- /dev/null +++ b/amarok/src/metadata/m4a/itunesdatabox.cpp @@ -0,0 +1,63 @@ +/*************************************************************************** + copyright : (C) 2002, 2003, 2006 by Jochen Issing + email : jochen.issing@isign-softart.de + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301 USA * + ***************************************************************************/ + +#include +#include "itunesdatabox.h" +#include "tbytevector.h" +#include "mp4isobox.h" +#include "tfile.h" + +using namespace TagLib; + +class MP4::ITunesDataBox::ITunesDataBoxPrivate +{ +public: + ByteVector data; +}; + +MP4::ITunesDataBox::ITunesDataBox( TagLib::File* file, MP4::Fourcc fourcc, uint size, long offset ) + :Mp4IsoFullBox(file, fourcc, size, offset) +{ + d = new MP4::ITunesDataBox::ITunesDataBoxPrivate(); +} + +MP4::ITunesDataBox::~ITunesDataBox() +{ + delete d; +} + +ByteVector MP4::ITunesDataBox::data() const +{ + return d->data; +} + +//! parse the content of the box +void MP4::ITunesDataBox::parse() +{ + // skip first 4 byte - don't know what they are supposed to be for - simply 4 zeros + file()->seek( 4, TagLib::File::Current ); + // read contents - remaining size is box_size-12-4 (12:fullbox header, 4:starting zeros of data box) +#if 0 + std::cout << " reading data box with data length: " << size()-16 << std::endl; +#endif + d->data = file()->readBlock( size()-12-4 ); +} + diff --git a/amarok/src/metadata/m4a/itunesdatabox.h b/amarok/src/metadata/m4a/itunesdatabox.h new file mode 100644 index 00000000..d0c802ca --- /dev/null +++ b/amarok/src/metadata/m4a/itunesdatabox.h @@ -0,0 +1,53 @@ +/*************************************************************************** + copyright : (C) 2002, 2003, 2006 by Jochen Issing + email : jochen.issing@isign-softart.de + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301 USA * + ***************************************************************************/ + +#ifndef ITUNESDATABOX_H +#define ITUNESDATABOX_H + +#include "mp4isofullbox.h" +#include "mp4fourcc.h" + +namespace TagLib +{ + namespace MP4 + { + class ITunesDataBox: public Mp4IsoFullBox + { + public: + ITunesDataBox( TagLib::File* file, MP4::Fourcc fourcc, uint size, long offset ); + ~ITunesDataBox(); + + //! get the internal data, which can be txt or binary data as well + ByteVector data() const; + + private: + //! parse the content of the box + virtual void parse(); + + protected: + class ITunesDataBoxPrivate; + ITunesDataBoxPrivate* d; + }; // class ITunesDataBox + + } // namespace MP4 +} // namespace TagLib + +#endif // ITUNESDATABOX_H diff --git a/amarok/src/metadata/m4a/itunesdaybox.cpp b/amarok/src/metadata/m4a/itunesdaybox.cpp new file mode 100644 index 00000000..16568d74 --- /dev/null +++ b/amarok/src/metadata/m4a/itunesdaybox.cpp @@ -0,0 +1,89 @@ +/*************************************************************************** + copyright : (C) 2002, 2003, 2006 by Jochen Issing + email : jochen.issing@isign-softart.de + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301 USA * + ***************************************************************************/ + +#include +#include "itunesdaybox.h" +#include "itunesdatabox.h" +#include "mp4isobox.h" +#include "mp4file.h" +#include "tfile.h" +#include "mp4tagsproxy.h" + +using namespace TagLib; + +class MP4::ITunesDayBox::ITunesDayBoxPrivate +{ +public: + ITunesDataBox* dataBox; +}; + +MP4::ITunesDayBox::ITunesDayBox( TagLib::File* file, MP4::Fourcc fourcc, uint size, long offset ) + :Mp4IsoBox(file, fourcc, size, offset) +{ + d = new MP4::ITunesDayBox::ITunesDayBoxPrivate(); + d->dataBox = 0; +} + +MP4::ITunesDayBox::~ITunesDayBox() +{ + if( d->dataBox != 0 ) + delete d->dataBox; + delete d; +} + +//! parse the content of the box +void MP4::ITunesDayBox::parse() +{ + TagLib::MP4::File* mp4file = static_cast( file() ); + + // parse data box + TagLib::uint size; + MP4::Fourcc fourcc; + + if(mp4file->readSizeAndType( size, fourcc ) == true) + { + // check for type - must be 'data' + if( fourcc != MP4::Fourcc("data") ) + { + std::cerr << "bad atom in itunes tag - skipping it." << std::endl; + // jump over data tag + mp4file->seek( size-8, TagLib::File::Current ); + return; + } + d->dataBox = new ITunesDataBox( mp4file, fourcc, size, mp4file->tell() ); + d->dataBox->parsebox(); + } + else + { + // reading unsuccessful - serious error! + std::cerr << "Error in parsing ITunesDayBox - serious Error in taglib!" << std::endl; + return; + } + // register data box + mp4file->tagProxy()->registerBox( Mp4TagsProxy::year, d->dataBox ); + +#if 0 + // get data pointer - just for debugging... + TagLib::String dataString( d->dataBox->data() ); + std::cout << "Content of day box: " << dataString.substr(0,4) << std::endl; +#endif +} + diff --git a/amarok/src/metadata/m4a/itunesdaybox.h b/amarok/src/metadata/m4a/itunesdaybox.h new file mode 100644 index 00000000..72913638 --- /dev/null +++ b/amarok/src/metadata/m4a/itunesdaybox.h @@ -0,0 +1,50 @@ +/*************************************************************************** + copyright : (C) 2002, 2003, 2006 by Jochen Issing + email : jochen.issing@isign-softart.de + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301 USA * + ***************************************************************************/ + +#ifndef ITUNESDAYBOX_H +#define ITUNESDAYBOX_H + +#include "mp4isobox.h" +#include "mp4fourcc.h" + +namespace TagLib +{ + namespace MP4 + { + class ITunesDayBox: public Mp4IsoBox + { + public: + ITunesDayBox( TagLib::File* file, MP4::Fourcc fourcc, uint size, long offset ); + ~ITunesDayBox(); + + private: + //! parse the content of the box + virtual void parse(); + + protected: + class ITunesDayBoxPrivate; + ITunesDayBoxPrivate* d; + }; // class ITunesDayBox + + } // namespace MP4 +} // namespace TagLib + +#endif // ITUNESDAYBOX_H diff --git a/amarok/src/metadata/m4a/itunesdiskbox.cpp b/amarok/src/metadata/m4a/itunesdiskbox.cpp new file mode 100644 index 00000000..93c47f2b --- /dev/null +++ b/amarok/src/metadata/m4a/itunesdiskbox.cpp @@ -0,0 +1,93 @@ +/*************************************************************************** + copyright : (C) 2002, 2003, 2006 by Jochen Issing + email : jochen.issing@isign-softart.de + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301 USA * + ***************************************************************************/ + +#include +#include "itunesdiskbox.h" +#include "itunesdatabox.h" +#include "mp4isobox.h" +#include "mp4file.h" +#include "tfile.h" +#include "mp4tagsproxy.h" + +using namespace TagLib; + +class MP4::ITunesDiskBox::ITunesDiskBoxPrivate +{ +public: + ITunesDataBox* dataBox; +}; + +MP4::ITunesDiskBox::ITunesDiskBox( TagLib::File* file, MP4::Fourcc fourcc, uint size, long offset ) + :Mp4IsoBox(file, fourcc, size, offset) +{ + d = new MP4::ITunesDiskBox::ITunesDiskBoxPrivate(); + d->dataBox = 0; +} + +MP4::ITunesDiskBox::~ITunesDiskBox() +{ + if( d->dataBox != 0 ) + delete d->dataBox; + delete d; +} + +//! parse the content of the box +void MP4::ITunesDiskBox::parse() +{ + TagLib::MP4::File* mp4file = static_cast( file() ); + + // parse data box + TagLib::uint size; + MP4::Fourcc fourcc; + + if(mp4file->readSizeAndType( size, fourcc ) == true) + { + // check for type - must be 'data' + if( fourcc != MP4::Fourcc("data") ) + { + std::cerr << "bad atom in itunes tag - skipping it." << std::endl; + // jump over data tag + mp4file->seek( size-8, TagLib::File::Current ); + return; + } + d->dataBox = new ITunesDataBox( mp4file, fourcc, size, mp4file->tell() ); + d->dataBox->parsebox(); + } + else + { + // reading unsuccessful - serious error! + std::cerr << "Error in parsing ITunesDiskBox - serious Error in taglib!" << std::endl; + return; + } + // register data box + mp4file->tagProxy()->registerBox( Mp4TagsProxy::disk, d->dataBox ); + +#if 0 + // get data pointer - just for debugging... + TagLib::ByteVector trknData = d->dataBox->data(); + TagLib::String trknumber = TagLib::String::number( static_cast( static_cast(trknData[0]) << 24 | + static_cast(trknData[1]) << 16 | + static_cast(trknData[2]) << 8 | + static_cast(trknData[3]) ) ); + std::cout << "Content of tracknumber box: " << trknumber << std::endl; +#endif +} + diff --git a/amarok/src/metadata/m4a/itunesdiskbox.h b/amarok/src/metadata/m4a/itunesdiskbox.h new file mode 100644 index 00000000..bad73da5 --- /dev/null +++ b/amarok/src/metadata/m4a/itunesdiskbox.h @@ -0,0 +1,50 @@ +/*************************************************************************** + copyright : (C) 2002, 2003, 2006 by Jochen Issing + email : jochen.issing@isign-softart.de + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301 USA * + ***************************************************************************/ + +#ifndef ITUNESDISKBOX_H +#define ITUNESDISKBOX_H + +#include "mp4isobox.h" +#include "mp4fourcc.h" + +namespace TagLib +{ + namespace MP4 + { + class ITunesDiskBox: public Mp4IsoBox + { + public: + ITunesDiskBox( TagLib::File* file, MP4::Fourcc fourcc, uint size, long offset ); + ~ITunesDiskBox(); + + private: + //! parse the content of the box + virtual void parse(); + + protected: + class ITunesDiskBoxPrivate; + ITunesDiskBoxPrivate* d; + }; // class ITunesDiskBox + + } // namespace MP4 +} // namespace TagLib + +#endif // ITUNESDISKBOX_H diff --git a/amarok/src/metadata/m4a/itunesgenbox.cpp b/amarok/src/metadata/m4a/itunesgenbox.cpp new file mode 100644 index 00000000..08708bc4 --- /dev/null +++ b/amarok/src/metadata/m4a/itunesgenbox.cpp @@ -0,0 +1,89 @@ +/*************************************************************************** + copyright : (C) 2002, 2003, 2006 by Jochen Issing + email : jochen.issing@isign-softart.de + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301 USA * + ***************************************************************************/ + +#include +#include "itunesgenbox.h" +#include "itunesdatabox.h" +#include "mp4isobox.h" +#include "mp4file.h" +#include "tfile.h" +#include "mp4tagsproxy.h" + +using namespace TagLib; + +class MP4::ITunesGenBox::ITunesGenBoxPrivate +{ +public: + ITunesDataBox* dataBox; +}; + +MP4::ITunesGenBox::ITunesGenBox( TagLib::File* file, MP4::Fourcc fourcc, uint size, long offset ) + :Mp4IsoBox(file, fourcc, size, offset) +{ + d = new MP4::ITunesGenBox::ITunesGenBoxPrivate(); + d->dataBox = 0; +} + +MP4::ITunesGenBox::~ITunesGenBox() +{ + if( d->dataBox != 0 ) + delete d->dataBox; + delete d; +} + +//! parse the content of the box +void MP4::ITunesGenBox::parse() +{ + TagLib::MP4::File* mp4file = static_cast( file() ); + + // parse data box + TagLib::uint size; + MP4::Fourcc fourcc; + + if(mp4file->readSizeAndType( size, fourcc ) == true) + { + // check for type - must be 'data' + if( fourcc != MP4::Fourcc("data") ) + { + std::cerr << "bad atom in itunes tag - skipping it." << std::endl; + // jump over data tag + mp4file->seek( size-8, TagLib::File::Current ); + return; + } + d->dataBox = new ITunesDataBox( mp4file, fourcc, size, mp4file->tell() ); + d->dataBox->parsebox(); + } + else + { + // reading unsuccessful - serious error! + std::cerr << "Error in parsing ITunesGenBox - serious Error in taglib!" << std::endl; + return; + } + // register data box + mp4file->tagProxy()->registerBox( Mp4TagsProxy::genre, d->dataBox ); + +#if 0 + // get data pointer - just for debugging... + TagLib::String dataString( d->dataBox->data() ); + std::cout << "Content of genre box: " << dataString << std::endl; +#endif +} + diff --git a/amarok/src/metadata/m4a/itunesgenbox.h b/amarok/src/metadata/m4a/itunesgenbox.h new file mode 100644 index 00000000..deb7fe36 --- /dev/null +++ b/amarok/src/metadata/m4a/itunesgenbox.h @@ -0,0 +1,50 @@ +/*************************************************************************** + copyright : (C) 2002, 2003, 2006 by Jochen Issing + email : jochen.issing@isign-softart.de + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301 USA * + ***************************************************************************/ + +#ifndef ITUNESGENBOX_H +#define ITUNESGENBOX_H + +#include "mp4isobox.h" +#include "mp4fourcc.h" + +namespace TagLib +{ + namespace MP4 + { + class ITunesGenBox: public Mp4IsoBox + { + public: + ITunesGenBox( TagLib::File* file, MP4::Fourcc fourcc, uint size, long offset ); + ~ITunesGenBox(); + + private: + //! parse the content of the box + virtual void parse(); + + protected: + class ITunesGenBoxPrivate; + ITunesGenBoxPrivate* d; + }; // class ITunesGenBox + + } // namespace MP4 +} // namespace TagLib + +#endif // ITUNESGENBOX_H diff --git a/amarok/src/metadata/m4a/itunesgrpbox.cpp b/amarok/src/metadata/m4a/itunesgrpbox.cpp new file mode 100644 index 00000000..061b6bda --- /dev/null +++ b/amarok/src/metadata/m4a/itunesgrpbox.cpp @@ -0,0 +1,89 @@ +/*************************************************************************** + copyright : (C) 2002, 2003, 2006 by Jochen Issing + email : jochen.issing@isign-softart.de + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301 USA * + ***************************************************************************/ + +#include +#include "itunesgrpbox.h" +#include "itunesdatabox.h" +#include "mp4isobox.h" +#include "mp4file.h" +#include "tfile.h" +#include "mp4tagsproxy.h" + +using namespace TagLib; + +class MP4::ITunesGrpBox::ITunesGrpBoxPrivate +{ +public: + ITunesDataBox* dataBox; +}; + +MP4::ITunesGrpBox::ITunesGrpBox( TagLib::File* file, MP4::Fourcc fourcc, uint size, long offset ) + :Mp4IsoBox(file, fourcc, size, offset) +{ + d = new MP4::ITunesGrpBox::ITunesGrpBoxPrivate(); + d->dataBox = 0; +} + +MP4::ITunesGrpBox::~ITunesGrpBox() +{ + if( d->dataBox != 0 ) + delete d->dataBox; + delete d; +} + +//! parse the content of the box +void MP4::ITunesGrpBox::parse() +{ + TagLib::MP4::File* mp4file = static_cast( file() ); + + // parse data box + TagLib::uint size; + MP4::Fourcc fourcc; + + if(mp4file->readSizeAndType( size, fourcc ) == true) + { + // check for type - must be 'data' + if( fourcc != MP4::Fourcc("data") ) + { + std::cerr << "bad atom in itunes tag - skipping it." << std::endl; + // jump over data tag + mp4file->seek( size-8, TagLib::File::Current ); + return; + } + d->dataBox = new ITunesDataBox( mp4file, fourcc, size, mp4file->tell() ); + d->dataBox->parsebox(); + } + else + { + // reading unsuccessful - serious error! + std::cerr << "Error in parsing ITunesGrpBox - serious Error in taglib!" << std::endl; + return; + } + // register data box + mp4file->tagProxy()->registerBox( Mp4TagsProxy::grouping, d->dataBox ); + +#if 0 + // get data pointer - just for debugging... + TagLib::String dataString( d->dataBox->data() ); + std::cout << "Content of title box: " << dataString << std::endl; +#endif +} + diff --git a/amarok/src/metadata/m4a/itunesgrpbox.h b/amarok/src/metadata/m4a/itunesgrpbox.h new file mode 100644 index 00000000..62922f0c --- /dev/null +++ b/amarok/src/metadata/m4a/itunesgrpbox.h @@ -0,0 +1,50 @@ +/*************************************************************************** + copyright : (C) 2002, 2003, 2006 by Jochen Issing + email : jochen.issing@isign-softart.de + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301 USA * + ***************************************************************************/ + +#ifndef ITUNESGRPBOX_H +#define ITUNESGRPBOX_H + +#include "mp4isobox.h" +#include "mp4fourcc.h" + +namespace TagLib +{ + namespace MP4 + { + class ITunesGrpBox: public Mp4IsoBox + { + public: + ITunesGrpBox( TagLib::File* file, MP4::Fourcc fourcc, uint size, long offset ); + ~ITunesGrpBox(); + + private: + //! parse the content of the box + virtual void parse(); + + protected: + class ITunesGrpBoxPrivate; + ITunesGrpBoxPrivate* d; + }; // class ITunesGrpBox + + } // namespace MP4 +} // namespace TagLib + +#endif // ITUNESGRPBOX_H diff --git a/amarok/src/metadata/m4a/itunesnambox.cpp b/amarok/src/metadata/m4a/itunesnambox.cpp new file mode 100644 index 00000000..6cc954bb --- /dev/null +++ b/amarok/src/metadata/m4a/itunesnambox.cpp @@ -0,0 +1,89 @@ +/*************************************************************************** + copyright : (C) 2002, 2003, 2006 by Jochen Issing + email : jochen.issing@isign-softart.de + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301 USA * + ***************************************************************************/ + +#include +#include "itunesnambox.h" +#include "itunesdatabox.h" +#include "mp4isobox.h" +#include "mp4file.h" +#include "tfile.h" +#include "mp4tagsproxy.h" + +using namespace TagLib; + +class MP4::ITunesNamBox::ITunesNamBoxPrivate +{ +public: + ITunesDataBox* dataBox; +}; + +MP4::ITunesNamBox::ITunesNamBox( TagLib::File* file, MP4::Fourcc fourcc, uint size, long offset ) + :Mp4IsoBox(file, fourcc, size, offset) +{ + d = new MP4::ITunesNamBox::ITunesNamBoxPrivate(); + d->dataBox = 0; +} + +MP4::ITunesNamBox::~ITunesNamBox() +{ + if( d->dataBox != 0 ) + delete d->dataBox; + delete d; +} + +//! parse the content of the box +void MP4::ITunesNamBox::parse() +{ + TagLib::MP4::File* mp4file = static_cast( file() ); + + // parse data box + TagLib::uint size; + MP4::Fourcc fourcc; + + if(mp4file->readSizeAndType( size, fourcc ) == true) + { + // check for type - must be 'data' + if( fourcc != MP4::Fourcc("data") ) + { + std::cerr << "bad atom in itunes tag - skipping it." << std::endl; + // jump over data tag + mp4file->seek( size-8, TagLib::File::Current ); + return; + } + d->dataBox = new ITunesDataBox( mp4file, fourcc, size, mp4file->tell() ); + d->dataBox->parsebox(); + } + else + { + // reading unsuccessful - serious error! + std::cerr << "Error in parsing ITunesNamBox - serious Error in taglib!" << std::endl; + return; + } + // register data box + mp4file->tagProxy()->registerBox( Mp4TagsProxy::title, d->dataBox ); + +#if 0 + // get data pointer - just for debugging... + TagLib::String dataString( d->dataBox->data() ); + std::cout << "Content of title box: " << dataString << std::endl; +#endif +} + diff --git a/amarok/src/metadata/m4a/itunesnambox.h b/amarok/src/metadata/m4a/itunesnambox.h new file mode 100644 index 00000000..434fd84e --- /dev/null +++ b/amarok/src/metadata/m4a/itunesnambox.h @@ -0,0 +1,50 @@ +/*************************************************************************** + copyright : (C) 2002, 2003, 2006 by Jochen Issing + email : jochen.issing@isign-softart.de + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301 USA * + ***************************************************************************/ + +#ifndef ITUNESNAMBOX_H +#define ITUNESNAMBOX_H + +#include "mp4isobox.h" +#include "mp4fourcc.h" + +namespace TagLib +{ + namespace MP4 + { + class ITunesNamBox: public Mp4IsoBox + { + public: + ITunesNamBox( TagLib::File* file, MP4::Fourcc fourcc, uint size, long offset ); + ~ITunesNamBox(); + + private: + //! parse the content of the box + virtual void parse(); + + protected: + class ITunesNamBoxPrivate; + ITunesNamBoxPrivate* d; + }; // class ITunesNamBox + + } // namespace MP4 +} // namespace TagLib + +#endif // ITUNESNAMBOX_H diff --git a/amarok/src/metadata/m4a/itunestmpobox.cpp b/amarok/src/metadata/m4a/itunestmpobox.cpp new file mode 100644 index 00000000..3d0ad2da --- /dev/null +++ b/amarok/src/metadata/m4a/itunestmpobox.cpp @@ -0,0 +1,93 @@ +/*************************************************************************** + copyright : (C) 2002, 2003, 2006 by Jochen Issing + email : jochen.issing@isign-softart.de + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301 USA * + ***************************************************************************/ + +#include +#include "itunestmpobox.h" +#include "itunesdatabox.h" +#include "mp4isobox.h" +#include "mp4file.h" +#include "tfile.h" +#include "mp4tagsproxy.h" + +using namespace TagLib; + +class MP4::ITunesTmpoBox::ITunesTmpoBoxPrivate +{ +public: + ITunesDataBox* dataBox; +}; + +MP4::ITunesTmpoBox::ITunesTmpoBox( TagLib::File* file, MP4::Fourcc fourcc, uint size, long offset ) + :Mp4IsoBox(file, fourcc, size, offset) +{ + d = new MP4::ITunesTmpoBox::ITunesTmpoBoxPrivate(); + d->dataBox = 0; +} + +MP4::ITunesTmpoBox::~ITunesTmpoBox() +{ + if( d->dataBox != 0 ) + delete d->dataBox; + delete d; +} + +//! parse the content of the box +void MP4::ITunesTmpoBox::parse() +{ + TagLib::MP4::File* mp4file = static_cast( file() ); + + // parse data box + TagLib::uint size; + MP4::Fourcc fourcc; + + if(mp4file->readSizeAndType( size, fourcc ) == true) + { + // check for type - must be 'data' + if( fourcc != MP4::Fourcc("data") ) + { + std::cerr << "bad atom in itunes tag - skipping it." << std::endl; + // jump over data tag + mp4file->seek( size-8, TagLib::File::Current ); + return; + } + d->dataBox = new ITunesDataBox( mp4file, fourcc, size, mp4file->tell() ); + d->dataBox->parsebox(); + } + else + { + // reading unsuccessful - serious error! + std::cerr << "Error in parsing ITunesTmpoBox - serious Error in taglib!" << std::endl; + return; + } + // register data box + mp4file->tagProxy()->registerBox( Mp4TagsProxy::bpm, d->dataBox ); + +#if 0 + // get data pointer - just for debugging... + TagLib::ByteVector trknData = d->dataBox->data(); + TagLib::String trknumber = TagLib::String::number( static_cast( static_cast(trknData[0]) << 24 | + static_cast(trknData[1]) << 16 | + static_cast(trknData[2]) << 8 | + static_cast(trknData[3]) ) ); + std::cout << "Content of tracknumber box: " << trknumber << std::endl; +#endif +} + diff --git a/amarok/src/metadata/m4a/itunestmpobox.h b/amarok/src/metadata/m4a/itunestmpobox.h new file mode 100644 index 00000000..981f9384 --- /dev/null +++ b/amarok/src/metadata/m4a/itunestmpobox.h @@ -0,0 +1,50 @@ +/*************************************************************************** + copyright : (C) 2002, 2003, 2006 by Jochen Issing + email : jochen.issing@isign-softart.de + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301 USA * + ***************************************************************************/ + +#ifndef ITUNESTMPOBOX_H +#define ITUNESTMPOBOX_H + +#include "mp4isobox.h" +#include "mp4fourcc.h" + +namespace TagLib +{ + namespace MP4 + { + class ITunesTmpoBox: public Mp4IsoBox + { + public: + ITunesTmpoBox( TagLib::File* file, MP4::Fourcc fourcc, uint size, long offset ); + ~ITunesTmpoBox(); + + private: + //! parse the content of the box + virtual void parse(); + + protected: + class ITunesTmpoBoxPrivate; + ITunesTmpoBoxPrivate* d; + }; // class ITunesTmpoBox + + } // namespace MP4 +} // namespace TagLib + +#endif // ITUNESTMPOBOX_H diff --git a/amarok/src/metadata/m4a/itunestrknbox.cpp b/amarok/src/metadata/m4a/itunestrknbox.cpp new file mode 100644 index 00000000..f8d36cb1 --- /dev/null +++ b/amarok/src/metadata/m4a/itunestrknbox.cpp @@ -0,0 +1,93 @@ +/*************************************************************************** + copyright : (C) 2002, 2003, 2006 by Jochen Issing + email : jochen.issing@isign-softart.de + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301 USA * + ***************************************************************************/ + +#include +#include "itunestrknbox.h" +#include "itunesdatabox.h" +#include "mp4isobox.h" +#include "mp4file.h" +#include "tfile.h" +#include "mp4tagsproxy.h" + +using namespace TagLib; + +class MP4::ITunesTrknBox::ITunesTrknBoxPrivate +{ +public: + ITunesDataBox* dataBox; +}; + +MP4::ITunesTrknBox::ITunesTrknBox( TagLib::File* file, MP4::Fourcc fourcc, uint size, long offset ) + :Mp4IsoBox(file, fourcc, size, offset) +{ + d = new MP4::ITunesTrknBox::ITunesTrknBoxPrivate(); + d->dataBox = 0; +} + +MP4::ITunesTrknBox::~ITunesTrknBox() +{ + if( d->dataBox != 0 ) + delete d->dataBox; + delete d; +} + +//! parse the content of the box +void MP4::ITunesTrknBox::parse() +{ + TagLib::MP4::File* mp4file = static_cast( file() ); + + // parse data box + TagLib::uint size; + MP4::Fourcc fourcc; + + if(mp4file->readSizeAndType( size, fourcc ) == true) + { + // check for type - must be 'data' + if( fourcc != MP4::Fourcc("data") ) + { + std::cerr << "bad atom in itunes tag - skipping it." << std::endl; + // jump over data tag + mp4file->seek( size-8, TagLib::File::Current ); + return; + } + d->dataBox = new ITunesDataBox( mp4file, fourcc, size, mp4file->tell() ); + d->dataBox->parsebox(); + } + else + { + // reading unsuccessful - serious error! + std::cerr << "Error in parsing ITunesTrknBox - serious Error in taglib!" << std::endl; + return; + } + // register data box + mp4file->tagProxy()->registerBox( Mp4TagsProxy::trackno, d->dataBox ); + +#if 0 + // get data pointer - just for debugging... + TagLib::ByteVector trknData = d->dataBox->data(); + TagLib::String trknumber = TagLib::String::number( static_cast( static_cast(trknData[0]) << 24 | + static_cast(trknData[1]) << 16 | + static_cast(trknData[2]) << 8 | + static_cast(trknData[3]) ) ); + std::cout << "Content of tracknumber box: " << trknumber << std::endl; +#endif +} + diff --git a/amarok/src/metadata/m4a/itunestrknbox.h b/amarok/src/metadata/m4a/itunestrknbox.h new file mode 100644 index 00000000..f603e1f7 --- /dev/null +++ b/amarok/src/metadata/m4a/itunestrknbox.h @@ -0,0 +1,50 @@ +/*************************************************************************** + copyright : (C) 2002, 2003, 2006 by Jochen Issing + email : jochen.issing@isign-softart.de + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301 USA * + ***************************************************************************/ + +#ifndef ITUNESTRKNBOX_H +#define ITUNESTRKNBOX_H + +#include "mp4isobox.h" +#include "mp4fourcc.h" + +namespace TagLib +{ + namespace MP4 + { + class ITunesTrknBox: public Mp4IsoBox + { + public: + ITunesTrknBox( TagLib::File* file, MP4::Fourcc fourcc, uint size, long offset ); + ~ITunesTrknBox(); + + private: + //! parse the content of the box + virtual void parse(); + + protected: + class ITunesTrknBoxPrivate; + ITunesTrknBoxPrivate* d; + }; // class ITunesTrknBox + + } // namespace MP4 +} // namespace TagLib + +#endif // ITUNESTRKNBOX_H diff --git a/amarok/src/metadata/m4a/ituneswrtbox.cpp b/amarok/src/metadata/m4a/ituneswrtbox.cpp new file mode 100644 index 00000000..ecf3c43e --- /dev/null +++ b/amarok/src/metadata/m4a/ituneswrtbox.cpp @@ -0,0 +1,89 @@ +/*************************************************************************** + copyright : (C) 2002, 2003, 2006 by Jochen Issing + email : jochen.issing@isign-softart.de + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301 USA * + ***************************************************************************/ + +#include +#include "ituneswrtbox.h" +#include "itunesdatabox.h" +#include "mp4isobox.h" +#include "mp4file.h" +#include "tfile.h" +#include "mp4tagsproxy.h" + +using namespace TagLib; + +class MP4::ITunesWrtBox::ITunesWrtBoxPrivate +{ +public: + ITunesDataBox* dataBox; +}; + +MP4::ITunesWrtBox::ITunesWrtBox( TagLib::File* file, MP4::Fourcc fourcc, uint size, long offset ) + :Mp4IsoBox(file, fourcc, size, offset) +{ + d = new MP4::ITunesWrtBox::ITunesWrtBoxPrivate(); + d->dataBox = 0; +} + +MP4::ITunesWrtBox::~ITunesWrtBox() +{ + if( d->dataBox != 0 ) + delete d->dataBox; + delete d; +} + +//! parse the content of the box +void MP4::ITunesWrtBox::parse() +{ + TagLib::MP4::File* mp4file = static_cast( file() ); + + // parse data box + TagLib::uint size; + MP4::Fourcc fourcc; + + if(mp4file->readSizeAndType( size, fourcc ) == true) + { + // check for type - must be 'data' + if( fourcc != MP4::Fourcc("data") ) + { + std::cerr << "bad atom in itunes tag - skipping it." << std::endl; + // jump over data tag + mp4file->seek( size-8, TagLib::File::Current ); + return; + } + d->dataBox = new ITunesDataBox( mp4file, fourcc, size, mp4file->tell() ); + d->dataBox->parsebox(); + } + else + { + // reading unsuccessful - serious error! + std::cerr << "Error in parsing ITunesWrtBox - serious Error in taglib!" << std::endl; + return; + } + // register data box + mp4file->tagProxy()->registerBox( Mp4TagsProxy::composer, d->dataBox ); + +#if 0 + // get data pointer - just for debugging... + TagLib::String dataString( d->dataBox->data() ); + std::cout << "Content of title box: " << dataString << std::endl; +#endif +} + diff --git a/amarok/src/metadata/m4a/ituneswrtbox.h b/amarok/src/metadata/m4a/ituneswrtbox.h new file mode 100644 index 00000000..740d94c6 --- /dev/null +++ b/amarok/src/metadata/m4a/ituneswrtbox.h @@ -0,0 +1,50 @@ +/*************************************************************************** + copyright : (C) 2002, 2003, 2006 by Jochen Issing + email : jochen.issing@isign-softart.de + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301 USA * + ***************************************************************************/ + +#ifndef ITUNESWRTBOX_H +#define ITUNESWRTBOX_H + +#include "mp4isobox.h" +#include "mp4fourcc.h" + +namespace TagLib +{ + namespace MP4 + { + class ITunesWrtBox: public Mp4IsoBox + { + public: + ITunesWrtBox( TagLib::File* file, MP4::Fourcc fourcc, uint size, long offset ); + ~ITunesWrtBox(); + + private: + //! parse the content of the box + virtual void parse(); + + protected: + class ITunesWrtBoxPrivate; + ITunesWrtBoxPrivate* d; + }; // class ITunesWrtBox + + } // namespace MP4 +} // namespace TagLib + +#endif // ITUNESWRTBOX_H diff --git a/amarok/src/metadata/m4a/mp4audioproperties.cpp b/amarok/src/metadata/m4a/mp4audioproperties.cpp new file mode 100644 index 00000000..77afab15 --- /dev/null +++ b/amarok/src/metadata/m4a/mp4audioproperties.cpp @@ -0,0 +1,75 @@ +/*************************************************************************** + copyright : (C) 2002, 2003, 2006 by Jochen Issing + email : jochen.issing@isign-softart.de + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301 USA * + ***************************************************************************/ + +#include "mp4audioproperties.h" +#include "mp4propsproxy.h" + +using namespace TagLib; + +class MP4::AudioProperties::AudioPropertiesPrivate +{ +public: + MP4::Mp4PropsProxy* propsproxy; +}; // AudioPropertiesPrivate + +MP4::AudioProperties::AudioProperties():TagLib::AudioProperties(TagLib::AudioProperties::Average) +{ + d = new MP4::AudioProperties::AudioPropertiesPrivate(); +} + +MP4::AudioProperties::~AudioProperties() +{ + delete d; +} + +void MP4::AudioProperties::setProxy( Mp4PropsProxy* proxy ) +{ + d->propsproxy = proxy; +} + +int MP4::AudioProperties::length() const +{ + if( d->propsproxy == 0 ) + return 0; + return d->propsproxy->seconds(); +} + +int MP4::AudioProperties::bitrate() const +{ + if( d->propsproxy == 0 ) + return 0; + return d->propsproxy->bitRate()/1000; +} + +int MP4::AudioProperties::sampleRate() const +{ + if( d->propsproxy == 0 ) + return 0; + return d->propsproxy->sampleRate(); +} + +int MP4::AudioProperties::channels() const +{ + if( d->propsproxy == 0 ) + return 0; + return d->propsproxy->channels(); +} + diff --git a/amarok/src/metadata/m4a/mp4audioproperties.h b/amarok/src/metadata/m4a/mp4audioproperties.h new file mode 100644 index 00000000..4e617905 --- /dev/null +++ b/amarok/src/metadata/m4a/mp4audioproperties.h @@ -0,0 +1,73 @@ +/*************************************************************************** + copyright : (C) 2002, 2003, 2006 by Jochen Issing + email : jochen.issing@isign-softart.de + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301 USA * + ***************************************************************************/ + +#ifndef MP4AUDIOPROPERTIES_H +#define MP4AUDIOPROPERTIES_H MP4AUDIOPROPERTIES_H + +#include "audioproperties.h" + +namespace TagLib +{ + namespace MP4 + { + class Mp4PropsProxy; + + class AudioProperties : public TagLib::AudioProperties + { + public: + //! constructor + AudioProperties(); + //! destructor + ~AudioProperties(); + + //! function to set the proxy + void setProxy( Mp4PropsProxy* proxy ); + + /*! + * Returns the length of the file in seconds. + */ + int length() const; + + /*! + * Returns the most appropriate bit rate for the file in kb/s. For constant + * bitrate formats this is simply the bitrate of the file. For variable + * bitrate formats this is either the average or nominal bitrate. + */ + int bitrate() const; + + /*! + * Returns the sample rate in Hz. + */ + int sampleRate() const; + + /*! + * Returns the number of audio channels. + */ + int channels() const; + + private: + class AudioPropertiesPrivate; + AudioPropertiesPrivate* d; + }; + } // namespace MP4 +} // namespace TagLib + +#endif // MP4AUDIOPROPERTIES_H diff --git a/amarok/src/metadata/m4a/mp4audiosampleentry.cpp b/amarok/src/metadata/m4a/mp4audiosampleentry.cpp new file mode 100644 index 00000000..fb87547e --- /dev/null +++ b/amarok/src/metadata/m4a/mp4audiosampleentry.cpp @@ -0,0 +1,146 @@ +/*************************************************************************** + copyright : (C) 2002, 2003, 2006 by Jochen Issing + email : jochen.issing@isign-softart.de + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301 USA * + ***************************************************************************/ + +#include +#include "mp4audiosampleentry.h" +#include "mp4isobox.h" +#include "mp4file.h" +#include "mp4propsproxy.h" + +using namespace TagLib; + +class MP4::Mp4AudioSampleEntry::Mp4AudioSampleEntryPrivate +{ +public: + TagLib::uint channelcount; + TagLib::uint samplerate; + TagLib::uint bitrate; +}; + +MP4::Mp4AudioSampleEntry::Mp4AudioSampleEntry( TagLib::File* file, MP4::Fourcc fourcc, uint size, long offset ) + :Mp4SampleEntry(file, fourcc, size, offset) +{ + d = new MP4::Mp4AudioSampleEntry::Mp4AudioSampleEntryPrivate(); +} + +MP4::Mp4AudioSampleEntry::~Mp4AudioSampleEntry() +{ + delete d; +} + +TagLib::uint MP4::Mp4AudioSampleEntry::channels() const +{ + return d->channelcount; +} + +TagLib::uint MP4::Mp4AudioSampleEntry::samplerate() const +{ + return d->samplerate; +} + +TagLib::uint MP4::Mp4AudioSampleEntry::bitrate() const +{ + return d->bitrate; +} + +void MP4::Mp4AudioSampleEntry::parseEntry() +{ + TagLib::MP4::File* mp4file = dynamic_cast(file()); + if(!mp4file) + return; + + // read 8 reserved bytes + mp4file->seek( 8, TagLib::File::Current ); + // read channelcount + if(!mp4file->readShort( d->channelcount )) + return; + // seek over samplesize, pre_defined and reserved + mp4file->seek( 6, TagLib::File::Current ); + // read samplerate + if(!mp4file->readInt( d->samplerate )) + return; + + // register box at proxy + mp4file->propProxy()->registerAudioSampleEntry( this ); + + + //std::cout << "fourcc of audio sample entry: " << fourcc().toString() << std::endl; + // check for both mp4a (plain files) and drms (encrypted files) + if( (fourcc() == MP4::Fourcc("mp4a")) || + (fourcc() == MP4::Fourcc("drms")) ) + { + TagLib::MP4::Fourcc fourcc; + TagLib::uint esds_size; + + if (!mp4file->readSizeAndType( esds_size, fourcc )) + return; + + // read esds' main parts + if( size()-48 > 0 ) + ByteVector flags_version = mp4file->readBlock(4); + else + return; + + ByteVector EsDescrTag = mp4file->readBlock(1); + // first 4 bytes contain full box specifics (version & flags) + // upcoming byte must be ESDescrTag (0x03) + if( EsDescrTag[0] == 0x03 ) + { + TagLib::uint descr_len = mp4file->readSystemsLen(); + TagLib::uint EsId; + if( !mp4file->readShort( EsId ) ) + return; + ByteVector priority = mp4file->readBlock(1); + if( descr_len < 20 ) + return; + } + else + { + TagLib::uint EsId; + if( !mp4file->readShort( EsId ) ) + return; + } + // read decoder configuration tag (0x04) + ByteVector DecCfgTag = mp4file->readBlock(1); + if( DecCfgTag[0] != 0x04 ) + return; + // read decoder configuration length + // TagLib::uint deccfg_len = mp4file->readSystemsLen(); + // read object type Id + ByteVector objId = mp4file->readBlock(1); + // read stream type id + ByteVector strId = mp4file->readBlock(1); + // read buffer Size DB + ByteVector bufferSizeDB = mp4file->readBlock(3); + // read max bitrate + TagLib::uint max_bitrate; + if( !mp4file->readInt( max_bitrate ) ) + return; + // read average bitrate + if( !mp4file->readInt( d->bitrate ) ) + return; + // skip the rest + mp4file->seek( offset()+size()-8, File::Beginning ); + } + else + mp4file->seek( size()-36, File::Current ); +} + diff --git a/amarok/src/metadata/m4a/mp4audiosampleentry.h b/amarok/src/metadata/m4a/mp4audiosampleentry.h new file mode 100644 index 00000000..c39c5e30 --- /dev/null +++ b/amarok/src/metadata/m4a/mp4audiosampleentry.h @@ -0,0 +1,57 @@ +/*************************************************************************** + copyright : (C) 2002, 2003, 2006 by Jochen Issing + email : jochen.issing@isign-softart.de + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301 USA * + ***************************************************************************/ + +#ifndef MP4AUDIOSAMPLEENTRY_H +#define MP4AUDIOSAMPLEENTRY_H + +#include "mp4sampleentry.h" +#include "mp4fourcc.h" + +namespace TagLib +{ + namespace MP4 + { + class Mp4AudioSampleEntry: public Mp4SampleEntry + { + public: + Mp4AudioSampleEntry( TagLib::File* file, MP4::Fourcc fourcc, uint size, long offset ); + ~Mp4AudioSampleEntry(); + + //! function to get the number of channels + TagLib::uint channels() const; + //! function to get the sample rate + TagLib::uint samplerate() const; + //! function to get the average bitrate of the audio stream + TagLib::uint bitrate() const; + + private: + //! parse the content of the box + void parseEntry(); + + protected: + class Mp4AudioSampleEntryPrivate; + Mp4AudioSampleEntryPrivate* d; + }; // class Mp4AudioSampleEntry + + } // namespace MP4 +} // namespace TagLib + +#endif // MP4AUDIOSAMPLEENTRY_H diff --git a/amarok/src/metadata/m4a/mp4file.cpp b/amarok/src/metadata/m4a/mp4file.cpp new file mode 100644 index 00000000..7d37d597 --- /dev/null +++ b/amarok/src/metadata/m4a/mp4file.cpp @@ -0,0 +1,377 @@ +/*************************************************************************** + copyright : (C) 2002, 2003, 2006 by Jochen Issing + email : jochen.issing@isign-softart.de + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301 USA * + ***************************************************************************/ + +#include +#include +#include "tlist.h" + +#include "mp4itunestag.h" +#include "mp4file.h" +#include "boxfactory.h" +#include "mp4tagsproxy.h" +#include "mp4propsproxy.h" +#include "mp4audioproperties.h" +#include "itunesdatabox.h" + +using namespace TagLib; + +class MP4::File::FilePrivate +{ +public: + //! list for all boxes of the mp4 file + TagLib::List boxes; + //! the box factory - will create all boxes by tag and size + MP4::BoxFactory boxfactory; + //! proxy for the tags is filled after parsing + MP4::Mp4TagsProxy tagsProxy; + //! proxy for audio properties + MP4::Mp4PropsProxy propsProxy; + //! the tag returned by tag() function + MP4::Tag mp4tag; + //! container for the audio properties returned by properties() function + MP4::AudioProperties mp4audioproperties; + //! is set to valid after successfully parsing + bool isValid; +}; + +//! function to fill the tags with converted proxy data, which has been parsed out of the file previously +static void fillTagFromProxy( MP4::Mp4TagsProxy& proxy, MP4::Tag& mp4tag ); + +MP4::File::File(const char *file, bool , AudioProperties::ReadStyle ) + :TagLib::File( file ) +{ + // create member container + d = new MP4::File::FilePrivate(); + + + d->isValid = false; + TagLib::uint size; + MP4::Fourcc fourcc; + + while( readSizeAndType( size, fourcc ) == true ) + { + // create the appropriate subclass and parse it + MP4::Mp4IsoBox* curbox = d->boxfactory.createInstance( this, fourcc, size, tell() ); + curbox->parsebox(); + d->boxes.append( curbox ); + } + + for( TagLib::List::Iterator iter = d->boxes.begin(); + iter != d->boxes.end(); + iter++ ) + { + if( (*iter)->fourcc() == MP4::Fourcc("moov") ) + { + d->isValid = true; + break; + } + } + + //if( d->isValid ) + //debug( "file is valid" ); + //else + //debug( "file is NOT valid" ); + + // fill tags from proxy data + fillTagFromProxy( d->tagsProxy, d->mp4tag ); +} + +MP4::File::~File() +{ + TagLib::List::Iterator delIter; + for( delIter = d->boxes.begin(); + delIter != d->boxes.end(); + delIter++ ) + { + delete *delIter; + } + delete d; +} + +Tag *MP4::File::tag() const +{ + return &d->mp4tag; +} + +AudioProperties * MP4::File::audioProperties() const +{ + d->mp4audioproperties.setProxy( &d->propsProxy ); + return &d->mp4audioproperties; +} + +bool MP4::File::save() +{ + return false; +} + +void MP4::File::remove() +{ +} + +TagLib::uint MP4::File::readSystemsLen() +{ + TagLib::uint length = 0; + TagLib::uint nbytes = 0; + ByteVector input; + TagLib::uchar tmp_input; + + do + { + input = readBlock(1); + tmp_input = static_cast(input[0]); + nbytes++; + length = (length<<7) | (tmp_input&0x7F); + } while( (tmp_input&0x80) && (nbytes<4) ); + + return length; +} + +bool MP4::File::readSizeAndType( TagLib::uint& size, MP4::Fourcc& fourcc ) +{ + // read the two blocks from file + ByteVector readsize = readBlock(4); + ByteVector readtype = readBlock(4); + + if( (readsize.size() != 4) || (readtype.size() != 4) ) + return false; + + // set size + size = static_cast(readsize[0]) << 24 | + static_cast(readsize[1]) << 16 | + static_cast(readsize[2]) << 8 | + static_cast(readsize[3]); + + // type and size seem to be part of the stored size + if( size < 8 ) + return false; + + // set fourcc + fourcc = readtype.data(); + + return true; +} + +bool MP4::File::readInt( TagLib::uint& toRead ) +{ + ByteVector readbuffer = readBlock(4); + if( readbuffer.size() != 4 ) + return false; + + toRead = static_cast(readbuffer[0]) << 24 | + static_cast(readbuffer[1]) << 16 | + static_cast(readbuffer[2]) << 8 | + static_cast(readbuffer[3]); + return true; +} + +bool MP4::File::readShort( TagLib::uint& toRead ) +{ + ByteVector readbuffer = readBlock(2); + if( readbuffer.size() != 2 ) + return false; + + toRead = static_cast(readbuffer[0]) << 8 | + static_cast(readbuffer[1]); + return true; +} + +bool MP4::File::readLongLong( TagLib::ulonglong& toRead ) +{ + ByteVector readbuffer = readBlock(8); + if( readbuffer.size() != 8 ) + return false; + + toRead = static_cast(static_cast(readbuffer[0])) << 56 | + static_cast(static_cast(readbuffer[1])) << 48 | + static_cast(static_cast(readbuffer[2])) << 40 | + static_cast(static_cast(readbuffer[3])) << 32 | + static_cast(static_cast(readbuffer[4])) << 24 | + static_cast(static_cast(readbuffer[5])) << 16 | + static_cast(static_cast(readbuffer[6])) << 8 | + static_cast(static_cast(readbuffer[7])); + return true; +} + +bool MP4::File::readFourcc( TagLib::MP4::Fourcc& fourcc ) +{ + ByteVector readtype = readBlock(4); + + if( readtype.size() != 4) + return false; + + // set fourcc + fourcc = readtype.data(); + + return true; +} + +MP4::Mp4TagsProxy* MP4::File::tagProxy() const +{ + return &d->tagsProxy; +} + +MP4::Mp4PropsProxy* MP4::File::propProxy() const +{ + return &d->propsProxy; +} + +void fillTagFromProxy( MP4::Mp4TagsProxy& proxy, MP4::Tag& mp4tag ) +{ + // tmp buffer for each tag + MP4::ITunesDataBox* databox; + + databox = proxy.titleData(); + if( databox != 0 ) + { + // convert data to string + TagLib::String datastring( databox->data(), String::UTF8 ); + // check if string was set + if( !datastring.isEmpty() ) + mp4tag.setTitle( datastring ); + } + + databox = proxy.artistData(); + if( databox != 0 ) + { + // convert data to string + TagLib::String datastring( databox->data(), String::UTF8 ); + // check if string was set + if( !datastring.isEmpty() ) + mp4tag.setArtist( datastring ); + } + + databox = proxy.albumData(); + if( databox != 0 ) + { + // convert data to string + TagLib::String datastring( databox->data(), String::UTF8 ); + // check if string was set + if( !datastring.isEmpty() ) + mp4tag.setAlbum( datastring ); + } + + databox = proxy.genreData(); + if( databox != 0 ) + { + // convert data to string + TagLib::String datastring( databox->data(), String::UTF8 ); + // check if string was set + if( !datastring.isEmpty() ) + mp4tag.setGenre( datastring ); + } + + databox = proxy.yearData(); + if( databox != 0 ) + { + // convert data to string + TagLib::String datastring( databox->data(), String::UTF8 ); + // check if string was set + if( !datastring.isEmpty() ) + mp4tag.setYear( datastring.toInt() ); + } + + databox = proxy.trknData(); + if( databox != 0 ) + { + // convert data to uint + TagLib::ByteVector datavec = databox->data(); + if( datavec.size() >= 4 ) + { + TagLib::uint trackno = static_cast( static_cast(datavec[0]) << 24 | + static_cast(datavec[1]) << 16 | + static_cast(datavec[2]) << 8 | + static_cast(datavec[3]) ); + mp4tag.setTrack( trackno ); + } + else + mp4tag.setTrack( 0 ); + } + + databox = proxy.commentData(); + if( databox != 0 ) + { + // convert data to string + TagLib::String datastring( databox->data(), String::UTF8 ); + // check if string was set + if( !datastring.isEmpty() ) + mp4tag.setComment( datastring ); + } + + databox = proxy.groupingData(); + if( databox != 0 ) + { + // convert data to string + TagLib::String datastring( databox->data(), String::UTF8 ); + // check if string was set + if( !datastring.isEmpty() ) + mp4tag.setGrouping( datastring ); + } + + databox = proxy.composerData(); + if( databox != 0 ) + { + // convert data to string + TagLib::String datastring( databox->data(), String::UTF8 ); + // check if string was set + if( !datastring.isEmpty() ) + mp4tag.setComposer( datastring ); + } + + databox = proxy.diskData(); + if( databox != 0 ) + { + // convert data to uint + TagLib::ByteVector datavec = databox->data(); + if( datavec.size() >= 4 ) + { + TagLib::uint discno = static_cast( static_cast(datavec[0]) << 24 | + static_cast(datavec[1]) << 16 | + static_cast(datavec[2]) << 8 | + static_cast(datavec[3]) ); + mp4tag.setDisk( discno ); + } + else + mp4tag.setDisk( 0 ); + } + + databox = proxy.bpmData(); + if( databox != 0 ) + { + // convert data to uint + TagLib::ByteVector datavec = databox->data(); + + if( datavec.size() >= 2 ) + { + TagLib::uint bpm = static_cast( static_cast(datavec[0]) << 8 | + static_cast(datavec[1]) ); + mp4tag.setBpm( bpm ); + } + else + mp4tag.setBpm( 0 ); + } + + databox = proxy.coverData(); + if( databox != 0 ) + { + // get byte vector + mp4tag.setCover( databox->data() ); + } +} diff --git a/amarok/src/metadata/m4a/mp4file.h b/amarok/src/metadata/m4a/mp4file.h new file mode 100644 index 00000000..9e40dbc9 --- /dev/null +++ b/amarok/src/metadata/m4a/mp4file.h @@ -0,0 +1,169 @@ +/*************************************************************************** + copyright : (C) 2002, 2003, 2006 by Jochen Issing + email : jochen.issing@isign-softart.de + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301 USA * + ***************************************************************************/ + +/*************************************************************************** + copyright : (C) 2002, 2003 by Jochen Issing + email : jochen.issing@isign-softart.de + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 * + * USA * + ***************************************************************************/ + +#ifndef TAGLIB_MP4FILE_H +#define TAGLIB_MP4FILE_H + +#include +#include + +#include "mp4fourcc.h" + +namespace TagLib { + + typedef unsigned long long ulonglong; + + class Tag; + + namespace MP4 + { + class Mp4TagsProxy; + class Mp4PropsProxy; + + //! An implementation of TagLib::File with mp4 itunes specific methods + + /*! + * This implements and provides an interface for mp4 itunes files to the + * TagLib::Tag and TagLib::AudioProperties interfaces by way of implementing + * the abstract TagLib::File API as well as providing some additional + * information specific to mp4 itunes files. (TODO) + */ + + class File : public TagLib::File + { + public: + /*! + * Contructs an mp4 itunes file from \a file. If \a readProperties is true the + * file's audio properties will also be read using \a propertiesStyle. If + * false, \a propertiesStyle is ignored. + */ + File(const char *file, bool readProperties = true, + AudioProperties::ReadStyle propertiesStyle = AudioProperties::Average); + + /*! + * Destroys this instance of the File. + */ + virtual ~File(); + + /*! + * Returns the Tag for this file. This will be an APE tag, an ID3v1 tag + * or a combination of the two. + */ + virtual TagLib::Tag *tag() const; + + /*! + * Returns the mp4 itunes::Properties for this file. If no audio properties + * were read then this will return a null pointer. + */ + virtual AudioProperties *audioProperties() const; + + /*! + * Saves the file. + */ + virtual bool save(); + + /*! + * This will remove all tags. + * + * \note This will also invalidate pointers to the tags + * as their memory will be freed. + * \note In order to make the removal permanent save() still needs to be called + */ + void remove(); + + /*! + * Helper function for parsing the MP4 file - reads the size and type of the next box. + * Returns true if read succeeded - not at EOF + */ + bool readSizeAndType( TagLib::uint& size, MP4::Fourcc& fourcc ); + + /*! + * Helper function to read the length of an descriptor in systems manner + */ + TagLib::uint readSystemsLen(); + + /*! + * Helper function for reading an unsigned int out of the file (big endian method) + */ + bool readInt( TagLib::uint& toRead ); + + /*! + * Helper function for reading an unsigned short out of the file (big endian method) + */ + bool readShort( TagLib::uint& toRead ); + + /*! + * Helper function for reading an unsigned long long (64bit) out of the file (big endian method) + */ + bool readLongLong( TagLib::ulonglong& toRead ); + + /*! + * Helper function to read a fourcc code + */ + bool readFourcc( TagLib::MP4::Fourcc& fourcc ); + + /*! + * Function to get the tags proxy for registration of the tags boxes. + * The proxy provides direct access to the data boxes of the certain tags - normally + * covered by several levels of subboxes + */ + Mp4TagsProxy* tagProxy() const; + + /*! + * Function to get the properties proxy for registration of the properties boxes. + * The proxy provides direct access to the needed boxes describing audio properties. + */ + Mp4PropsProxy* propProxy() const; + + private: + File(const File &); + File &operator=(const File &); + + class FilePrivate; + FilePrivate *d; + }; + + } // namespace MP4 + +} // namespace TagLib + +#endif // TAGLIB_MP4FILE_H diff --git a/amarok/src/metadata/m4a/mp4fourcc.cpp b/amarok/src/metadata/m4a/mp4fourcc.cpp new file mode 100644 index 00000000..dac0f99e --- /dev/null +++ b/amarok/src/metadata/m4a/mp4fourcc.cpp @@ -0,0 +1,84 @@ +/*************************************************************************** + copyright : (C) 2002, 2003, 2006 by Jochen Issing + email : jochen.issing@isign-softart.de + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301 USA * + ***************************************************************************/ + +#include "mp4fourcc.h" + +using namespace TagLib; + +MP4::Fourcc::Fourcc() +{ + m_fourcc = 0U; +} + +MP4::Fourcc::Fourcc( TagLib::String fourcc ) +{ + m_fourcc = 0U; + + if( fourcc.size() >= 4 ) + m_fourcc = static_cast(fourcc[0]) << 24 | + static_cast(fourcc[1]) << 16 | + static_cast(fourcc[2]) << 8 | + static_cast(fourcc[3]); +} + +MP4::Fourcc::~Fourcc() +{} + +TagLib::String MP4::Fourcc::toString() const +{ + TagLib::String fourcc; + fourcc.append(static_cast(m_fourcc >> 24 & 0xFF)); + fourcc.append(static_cast(m_fourcc >> 16 & 0xFF)); + fourcc.append(static_cast(m_fourcc >> 8 & 0xFF)); + fourcc.append(static_cast(m_fourcc & 0xFF)); + + return fourcc; +} + +MP4::Fourcc::operator unsigned int() const +{ + return m_fourcc; +} + +bool MP4::Fourcc::operator == (unsigned int fourccB ) const +{ + return (m_fourcc==fourccB); +} + +bool MP4::Fourcc::operator != (unsigned int fourccB ) const +{ + return (m_fourcc!=fourccB); +} + +MP4::Fourcc& MP4::Fourcc::operator = (unsigned int fourcc ) +{ + m_fourcc = fourcc; + return *this; +} + +MP4::Fourcc& MP4::Fourcc::operator = (char fourcc[4]) +{ + m_fourcc = static_cast(fourcc[0]) << 24 | + static_cast(fourcc[1]) << 16 | + static_cast(fourcc[2]) << 8 | + static_cast(fourcc[3]); + return *this; +} diff --git a/amarok/src/metadata/m4a/mp4fourcc.h b/amarok/src/metadata/m4a/mp4fourcc.h new file mode 100644 index 00000000..90e498ad --- /dev/null +++ b/amarok/src/metadata/m4a/mp4fourcc.h @@ -0,0 +1,63 @@ +/*************************************************************************** + copyright : (C) 2002, 2003, 2006 by Jochen Issing + email : jochen.issing@isign-softart.de + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301 USA * + ***************************************************************************/ + +#ifndef MP4FOURCC_H +#define MP4FOURCC_H + +#include "tstring.h" + +namespace TagLib +{ + namespace MP4 + { + /*! union for easy fourcc / type handling */ + class Fourcc + { + public: + //! std constructor + Fourcc(); + //! string constructor + Fourcc(TagLib::String fourcc); + + //! destructor + ~Fourcc(); + + //! function to get a string version of the fourcc + TagLib::String toString() const; + //! cast operator to unsigned int + operator unsigned int() const; + //! comparison operator + bool operator == (unsigned int fourccB ) const; + //! comparison operator + bool operator != (unsigned int fourccB ) const; + //! assigment operator for unsigned int + Fourcc& operator = (unsigned int fourcc ); + //! assigment operator for character string + Fourcc& operator = (char fourcc[4]); + + private: + uint m_fourcc; /*!< integer code of the fourcc */ + }; + + } // namespace MP4 +} // namespace TagLib + +#endif // MP4FOURCC_H diff --git a/amarok/src/metadata/m4a/mp4hdlrbox.cpp b/amarok/src/metadata/m4a/mp4hdlrbox.cpp new file mode 100644 index 00000000..a3ec5f72 --- /dev/null +++ b/amarok/src/metadata/m4a/mp4hdlrbox.cpp @@ -0,0 +1,75 @@ +/*************************************************************************** + copyright : (C) 2002, 2003, 2006 by Jochen Issing + email : jochen.issing@isign-softart.de + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301 USA * + ***************************************************************************/ + +#include +#include +#include "mp4hdlrbox.h" +#include "boxfactory.h" +#include "mp4file.h" + +using namespace TagLib; + +class MP4::Mp4HdlrBox::Mp4HdlrBoxPrivate +{ +public: + TagLib::uint pre_defined; + MP4::Fourcc handler_type; + TagLib::String hdlr_string; +}; // class Mp4HdlrBoxPrivate + +MP4::Mp4HdlrBox::Mp4HdlrBox( TagLib::File* file, MP4::Fourcc fourcc, TagLib::uint size, long offset ) + : Mp4IsoFullBox( file, fourcc, size, offset ) +{ + d = new MP4::Mp4HdlrBox::Mp4HdlrBoxPrivate(); +} + +MP4::Mp4HdlrBox::~Mp4HdlrBox() +{ + delete d; +} + +MP4::Fourcc MP4::Mp4HdlrBox::hdlr_type() const +{ + return d->handler_type; +} + +TagLib::String MP4::Mp4HdlrBox::hdlr_string() const +{ + return d->hdlr_string; +} + +void MP4::Mp4HdlrBox::parse() +{ + TagLib::uint totalread = 12+20; + + TagLib::MP4::File* mp4file = static_cast( file() ); + if( mp4file->readInt( d->pre_defined ) == false ) + return; + if( mp4file->readFourcc( d->handler_type ) == false ) + return; + + // read reserved into trash + mp4file->seek( 3*4, TagLib::File::Current ); + + // check if there are bytes remaining - used for hdlr string + if( size() - totalread != 0 ) + d->hdlr_string = mp4file->readBlock( size()-totalread ); +} diff --git a/amarok/src/metadata/m4a/mp4hdlrbox.h b/amarok/src/metadata/m4a/mp4hdlrbox.h new file mode 100644 index 00000000..0a6bf542 --- /dev/null +++ b/amarok/src/metadata/m4a/mp4hdlrbox.h @@ -0,0 +1,53 @@ +/*************************************************************************** + copyright : (C) 2002, 2003, 2006 by Jochen Issing + email : jochen.issing@isign-softart.de + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301 USA * + ***************************************************************************/ + +#ifndef MP4HDLRBOX_H +#define MP4HDLRBOX_H + +#include "mp4isofullbox.h" +#include "mp4fourcc.h" + +namespace TagLib +{ + namespace MP4 + { + class Mp4HdlrBox: public Mp4IsoFullBox + { + public: + Mp4HdlrBox( TagLib::File* file, MP4::Fourcc fourcc, TagLib::uint size, long offset ); + ~Mp4HdlrBox(); + + //! parse hdlr contents + void parse(); + //! get the handler type + MP4::Fourcc hdlr_type() const; + //! get the hdlr string + TagLib::String hdlr_string() const; + + private: + class Mp4HdlrBoxPrivate; + Mp4HdlrBoxPrivate* d; + }; // Mp4HdlrBox + + } // namespace MP4 +} // namespace TagLib + +#endif // MP4HDLRBOX_H diff --git a/amarok/src/metadata/m4a/mp4ilstbox.cpp b/amarok/src/metadata/m4a/mp4ilstbox.cpp new file mode 100644 index 00000000..1d5ae9a3 --- /dev/null +++ b/amarok/src/metadata/m4a/mp4ilstbox.cpp @@ -0,0 +1,97 @@ +/*************************************************************************** + copyright : (C) 2002, 2003, 2006 by Jochen Issing + email : jochen.issing@isign-softart.de + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301 USA * + ***************************************************************************/ + +#include "tlist.h" +#include +#include "mp4ilstbox.h" +#include "boxfactory.h" +#include "mp4file.h" + +using namespace TagLib; + +class MP4::Mp4IlstBox::Mp4IlstBoxPrivate +{ +public: + //! container for all boxes in ilst box + TagLib::List ilstBoxes; + //! a box factory for creating the appropriate boxes + MP4::BoxFactory boxfactory; +}; // class Mp4IlstBoxPrivate + +MP4::Mp4IlstBox::Mp4IlstBox( TagLib::File* file, MP4::Fourcc fourcc, TagLib::uint size, long offset ) + : Mp4IsoBox( file, fourcc, size, offset ) +{ + d = new MP4::Mp4IlstBox::Mp4IlstBoxPrivate(); +} + +MP4::Mp4IlstBox::~Mp4IlstBox() +{ + TagLib::List::Iterator delIter; + for( delIter = d->ilstBoxes.begin(); + delIter != d->ilstBoxes.end(); + delIter++ ) + { + delete *delIter; + } + delete d; +} + +void MP4::Mp4IlstBox::parse() +{ +#if 0 + std::cout << " parsing ilst box" << std::endl; +#endif + + TagLib::MP4::File* mp4file = static_cast( file() ); + + TagLib::uint totalsize = 8; + // parse all contained boxes + TagLib::uint size; + MP4::Fourcc fourcc; + +#if 0 + std::cout << " "; +#endif + while( (mp4file->readSizeAndType( size, fourcc ) == true) ) + { + totalsize += size; + + // check for errors + if( totalsize > MP4::Mp4IsoBox::size() ) + { + std::cerr << "Error in mp4 file " << mp4file->name() << " ilst box contains bad box with name: " << fourcc.toString() << std::endl; + return; + } + + // create the appropriate subclass and parse it + MP4::Mp4IsoBox* curbox = d->boxfactory.createInstance( mp4file, fourcc, size, mp4file->tell() ); + curbox->parsebox(); + d->ilstBoxes.append( curbox ); + + // check for end of ilst box + if( totalsize == MP4::Mp4IsoBox::size() ) + break; + +#if 0 + std::cout << " "; +#endif + } +} diff --git a/amarok/src/metadata/m4a/mp4ilstbox.h b/amarok/src/metadata/m4a/mp4ilstbox.h new file mode 100644 index 00000000..9e7ad1c4 --- /dev/null +++ b/amarok/src/metadata/m4a/mp4ilstbox.h @@ -0,0 +1,49 @@ +/*************************************************************************** + copyright : (C) 2002, 2003, 2006 by Jochen Issing + email : jochen.issing@isign-softart.de + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301 USA * + ***************************************************************************/ + +#ifndef MP4ILSTBOX_H +#define MP4ILSTBOX_H + +#include "mp4isobox.h" +#include "mp4fourcc.h" + +namespace TagLib +{ + namespace MP4 + { + class Mp4IlstBox: public Mp4IsoBox + { + public: + Mp4IlstBox( TagLib::File* file, MP4::Fourcc fourcc, TagLib::uint size, long offset ); + ~Mp4IlstBox(); + + //! parse ilst contents + void parse(); + + private: + class Mp4IlstBoxPrivate; + Mp4IlstBoxPrivate* d; + }; // Mp4IlstBox + + } // namespace MP4 +} // namespace TagLib + +#endif // MP4ILSTBOX_H diff --git a/amarok/src/metadata/m4a/mp4isobox.cpp b/amarok/src/metadata/m4a/mp4isobox.cpp new file mode 100644 index 00000000..7f089241 --- /dev/null +++ b/amarok/src/metadata/m4a/mp4isobox.cpp @@ -0,0 +1,76 @@ +/*************************************************************************** + copyright : (C) 2002, 2003, 2006 by Jochen Issing + email : jochen.issing@isign-softart.de + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301 USA * + ***************************************************************************/ + +#include "mp4isobox.h" +#include "tfile.h" + +using namespace TagLib; + +class MP4::Mp4IsoBox::Mp4IsoBoxPrivate +{ +public: + MP4::Fourcc fourcc; + TagLib::uint size; + long offset; + TagLib::File* file; +}; + +MP4::Mp4IsoBox::Mp4IsoBox( TagLib::File* file, MP4::Fourcc fourcc, TagLib::uint size, long offset ) +{ + d = new MP4::Mp4IsoBox::Mp4IsoBoxPrivate(); + d->file = file; + d->fourcc = fourcc; + d->size = size; + d->offset = offset; +} + +MP4::Mp4IsoBox::~Mp4IsoBox() +{ + delete d; +} + +void MP4::Mp4IsoBox::parsebox() +{ + // seek to offset + file()->seek( offset(), File::Beginning ); + // simply call parse method of sub class + parse(); +} + +MP4::Fourcc MP4::Mp4IsoBox::fourcc() const +{ + return d->fourcc; +} + +TagLib::uint MP4::Mp4IsoBox::size() const +{ + return d->size; +} + +long MP4::Mp4IsoBox::offset() const +{ + return d->offset; +} + +TagLib::File* MP4::Mp4IsoBox::file() const +{ + return d->file; +} diff --git a/amarok/src/metadata/m4a/mp4isobox.h b/amarok/src/metadata/m4a/mp4isobox.h new file mode 100644 index 00000000..4d4edc9c --- /dev/null +++ b/amarok/src/metadata/m4a/mp4isobox.h @@ -0,0 +1,67 @@ +/*************************************************************************** + copyright : (C) 2002, 2003, 2006 by Jochen Issing + email : jochen.issing@isign-softart.de + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301 USA * + ***************************************************************************/ + +#ifndef MP4ISOBOX_H +#define MP4ISOBOX_H + +#include "taglib.h" +#include "mp4fourcc.h" + +namespace TagLib +{ + class File; + + namespace MP4 + { + class Mp4IsoBox + { + public: + //! constructor for base class + Mp4IsoBox( TagLib::File* file, MP4::Fourcc fourcc, uint size, long offset ); + //! destructor - simply freeing private ptr + virtual ~Mp4IsoBox(); + + //! function to get the fourcc code + MP4::Fourcc fourcc() const; + //! function to get the size of tha atom/box + uint size() const; + //! function to get the offset of the atom in the mp4 file + long offset() const; + + //! parse wrapper to get common interface for both box and fullbox + virtual void parsebox(); + //! pure virtual function for all subclasses to implement + virtual void parse() = 0; + + protected: + //! function to get the file pointer + TagLib::File* file() const; + + protected: + class Mp4IsoBoxPrivate; + Mp4IsoBoxPrivate* d; + }; + + } // namespace MP4 +} // namespace TagLib + +#endif // MP4ISOBOX_H + diff --git a/amarok/src/metadata/m4a/mp4isofullbox.cpp b/amarok/src/metadata/m4a/mp4isofullbox.cpp new file mode 100644 index 00000000..f938ad44 --- /dev/null +++ b/amarok/src/metadata/m4a/mp4isofullbox.cpp @@ -0,0 +1,67 @@ +/*************************************************************************** + copyright : (C) 2002, 2003, 2006 by Jochen Issing + email : jochen.issing@isign-softart.de + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301 USA * + ***************************************************************************/ + +#include "mp4isofullbox.h" +#include "tfile.h" + +using namespace TagLib; + +class MP4::Mp4IsoFullBox::Mp4IsoFullBoxPrivate +{ +public: + uchar version; + uint flags; +}; // Mp4IsoFullBoxPrivate + + +MP4::Mp4IsoFullBox::Mp4IsoFullBox( TagLib::File* file, MP4::Fourcc fourcc, uint size, long offset ) +: Mp4IsoBox( file, fourcc, size, offset ) +{ + d = new MP4::Mp4IsoFullBox::Mp4IsoFullBoxPrivate(); +} + +MP4::Mp4IsoFullBox::~Mp4IsoFullBox() +{ + delete d; +} + +void MP4::Mp4IsoFullBox::parsebox() +{ + // seek to offset + Mp4IsoBox::file()->seek(Mp4IsoBox::offset(), File::Beginning ); + // parse version and flags + ByteVector version_flags = Mp4IsoBox::file()->readBlock(4); + d->version = version_flags[0]; + d->flags = version_flags[1] << 16 || version_flags[2] << 8 || version_flags[3]; + // call parse method of subclass + parse(); +} + +TagLib::uchar MP4::Mp4IsoFullBox::version() +{ + return d->version; +} + +TagLib::uint MP4::Mp4IsoFullBox::flags() +{ + return d->flags; +} + diff --git a/amarok/src/metadata/m4a/mp4isofullbox.h b/amarok/src/metadata/m4a/mp4isofullbox.h new file mode 100644 index 00000000..bc01b619 --- /dev/null +++ b/amarok/src/metadata/m4a/mp4isofullbox.h @@ -0,0 +1,57 @@ +/*************************************************************************** + copyright : (C) 2002, 2003, 2006 by Jochen Issing + email : jochen.issing@isign-softart.de + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301 USA * + ***************************************************************************/ + +#ifndef MP4ISOFULLBOX_H +#define MP4ISOFULLBOX_H + +#include "mp4isobox.h" +#include "mp4fourcc.h" + +namespace TagLib +{ + namespace MP4 + { + class Mp4IsoFullBox : public Mp4IsoBox + { + public: + //! constructor for full box + Mp4IsoFullBox( TagLib::File* file, MP4::Fourcc fourcc, uint size, long offset ); + //! destructor for mp4 iso full box + virtual ~Mp4IsoFullBox(); + + //! function to get the version of box + uchar version(); + //! function to get the flag map + uint flags(); + + //! parse wrapper to get common interface for both box and fullbox + virtual void parsebox(); + + protected: + class Mp4IsoFullBoxPrivate; + Mp4IsoFullBoxPrivate* d; + }; + + } // namespace MP4 +} // namespace TagLib + +#endif // MP4ISOFULLBOX_H + diff --git a/amarok/src/metadata/m4a/mp4itunestag.cpp b/amarok/src/metadata/m4a/mp4itunestag.cpp new file mode 100644 index 00000000..aabd6449 --- /dev/null +++ b/amarok/src/metadata/m4a/mp4itunestag.cpp @@ -0,0 +1,197 @@ +/*************************************************************************** + copyright : (C) 2002, 2003, 2006 by Jochen Issing + email : jochen.issing@isign-softart.de + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301 USA * + ***************************************************************************/ + +#include "mp4itunestag.h" + +using namespace TagLib; + +class MP4::Tag::TagPrivate +{ +public: + MP4::File* mp4file; + TagLib::String title; + TagLib::String artist; + TagLib::String album; + TagLib::String genre; + TagLib::uint year; + TagLib::uint track; + TagLib::String comment; + TagLib::String grouping; + TagLib::String composer; + TagLib::uint disk; + TagLib::uint bpm; + bool isEmpty; + TagLib::ByteVector cover; +}; + + +MP4::Tag::Tag( ) +{ + d = new TagPrivate(); + d->year = 0; + d->track = 0; + d->disk = 0; + d->bpm = 0; + d->isEmpty = true; +} + +MP4::Tag::~Tag() +{ + delete d; +} + +String MP4::Tag::title() const +{ + return d->title; +} + +String MP4::Tag::artist() const +{ + return d->artist; +} + +String MP4::Tag::album() const +{ + return d->album; +} + +String MP4::Tag::comment() const +{ + return d->comment; +} + +String MP4::Tag::genre() const +{ + return d->genre; +} + +TagLib::uint MP4::Tag::year() const +{ + return d->year; +} + +TagLib::uint MP4::Tag::track() const +{ + return d->track; +} + +String MP4::Tag::grouping() const +{ + return d->grouping; +} + +String MP4::Tag::composer() const +{ + return d->composer; +} + +TagLib::uint MP4::Tag::disk() const +{ + return d->disk; +} + +TagLib::uint MP4::Tag::bpm() const +{ + return d->bpm; +} + +TagLib::ByteVector MP4::Tag::cover() const +{ + return d->cover; +} + +void MP4::Tag::setTitle(const String &s) +{ + d->title = s; + d->isEmpty = false; +} + +void MP4::Tag::setArtist(const String &s) +{ + d->artist = s; + d->isEmpty = false; +} + +void MP4::Tag::setAlbum(const String &s) +{ + d->album = s; + d->isEmpty = false; +} + +void MP4::Tag::setComment(const String &s) +{ + d->comment = s; + d->isEmpty = false; +} + +void MP4::Tag::setGenre(const String &s) +{ + d->genre = s; + d->isEmpty = false; +} + +void MP4::Tag::setYear(const TagLib::uint i) +{ + d->year = i; + d->isEmpty = false; +} + +void MP4::Tag::setTrack(const TagLib::uint i) +{ + d->track = i; + d->isEmpty = false; +} + +void MP4::Tag::setGrouping(const String &s) +{ + d->grouping = s; + d->isEmpty = false; +} + +void MP4::Tag::setComposer(const String &s) +{ + d->composer = s; + d->isEmpty = false; +} + +void MP4::Tag::setDisk(const TagLib::uint i) +{ + d->disk = i; + d->isEmpty = false; +} + +void MP4::Tag::setBpm(const TagLib::uint i) +{ + d->bpm = i; + d->isEmpty = false; +} + +void MP4::Tag::setCover(const TagLib::ByteVector& c) +{ + d->cover = c; + d->isEmpty = false; +} + +bool MP4::Tag::isEmpty() const +{ + return d->isEmpty; +} + diff --git a/amarok/src/metadata/m4a/mp4itunestag.h b/amarok/src/metadata/m4a/mp4itunestag.h new file mode 100644 index 00000000..9e572a7f --- /dev/null +++ b/amarok/src/metadata/m4a/mp4itunestag.h @@ -0,0 +1,95 @@ +/*************************************************************************** + copyright : (C) 2002, 2003, 2006 by Jochen Issing + email : jochen.issing@isign-softart.de + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301 USA * + ***************************************************************************/ + +#ifndef MP4ITUNESTAG_H +#define MP4ITUNESTAG_H + +#include "taglib.h" +#include "tstring.h" +#include "tag.h" + +namespace TagLib +{ + namespace MP4 + { + class File; + + class Tag : public TagLib::Tag + { + public: + /*! + * Constructs an empty MP4 iTunes tag. + */ + Tag( ); + + /*! + * Destroys this Tag instance. + */ + virtual ~Tag(); + + // Reimplementations. + + virtual String title() const; + virtual String artist() const; + virtual String album() const; + virtual String comment() const; + virtual String genre() const; + virtual uint year() const; + virtual uint track() const; + + virtual void setTitle(const String &s); + virtual void setArtist(const String &s); + virtual void setAlbum(const String &s); + virtual void setComment(const String &s); + virtual void setGenre(const String &s); + virtual void setYear(const uint i); + virtual void setTrack(const uint i); + + // MP4 specific fields + + String grouping() const; + String composer() const; + uint disk() const; + uint bpm() const; + ByteVector cover() const; + int compilation() const { return -1; } + + void setGrouping(const String &s); + void setComposer(const String &s); + void setDisk(const uint i); + void setBpm(const uint i); + void setCover( const ByteVector& cover ); + void setCompilation( bool /*isCompilation*/ ) {} + + virtual bool isEmpty() const; + + private: + Tag(const Tag &); + Tag &operator=(const Tag &); + + class TagPrivate; + TagPrivate *d; + }; + } // namespace MP4 + +} // namespace TagLib + +#endif // MP4ITUNESTAG_H diff --git a/amarok/src/metadata/m4a/mp4mdiabox.cpp b/amarok/src/metadata/m4a/mp4mdiabox.cpp new file mode 100644 index 00000000..c3b30ad3 --- /dev/null +++ b/amarok/src/metadata/m4a/mp4mdiabox.cpp @@ -0,0 +1,111 @@ +/*************************************************************************** + copyright : (C) 2002, 2003, 2006 by Jochen Issing + email : jochen.issing@isign-softart.de + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301 USA * + ***************************************************************************/ + +#include "tlist.h" +#include +#include "mp4mdiabox.h" +#include "mp4hdlrbox.h" +#include "mp4minfbox.h" +#include "boxfactory.h" +#include "mp4file.h" + +using namespace TagLib; + +class MP4::Mp4MdiaBox::Mp4MdiaBoxPrivate +{ +public: + //! container for all boxes in mdia box + TagLib::List mdiaBoxes; + //! a box factory for creating the appropriate boxes + MP4::BoxFactory boxfactory; +}; // class Mp4MdiaBoxPrivate + +MP4::Mp4MdiaBox::Mp4MdiaBox( TagLib::File* file, MP4::Fourcc fourcc, TagLib::uint size, long offset ) + : Mp4IsoBox( file, fourcc, size, offset ) +{ + d = new MP4::Mp4MdiaBox::Mp4MdiaBoxPrivate(); +} + +MP4::Mp4MdiaBox::~Mp4MdiaBox() +{ + TagLib::List::Iterator delIter; + for( delIter = d->mdiaBoxes.begin(); + delIter != d->mdiaBoxes.end(); + delIter++ ) + { + delete *delIter; + } + delete d; +} + +void MP4::Mp4MdiaBox::parse() +{ + TagLib::MP4::File* mp4file = static_cast( file() ); + + TagLib::uint totalsize = 8; + // parse all contained boxes + TagLib::uint size; + MP4::Fourcc fourcc; + + // stores the current handler type + TagLib::MP4::Fourcc hdlrtype; + + while( (mp4file->readSizeAndType( size, fourcc ) == true) ) + { + totalsize += size; + + // check for errors + if( totalsize > MP4::Mp4IsoBox::size() ) + { + std::cerr << "Error in mp4 file " << mp4file->name() << " mdia box contains bad box with name: " << fourcc.toString() << std::endl; + return; + } + + // create the appropriate subclass and parse it + MP4::Mp4IsoBox* curbox = d->boxfactory.createInstance( mp4file, fourcc, size, mp4file->tell() ); + if( static_cast( fourcc ) == 0x6d696e66 /*"minf"*/ ) + { + // cast to minf + Mp4MinfBox* minfbox = dynamic_cast( curbox ); + if(!minfbox) + return; + // set handler type + minfbox->setHandlerType( hdlrtype ); + } + + curbox->parsebox(); + d->mdiaBoxes.append( curbox ); + + if(static_cast( fourcc ) == 0x68646c72 /*"hdlr"*/ ) + { + // cast to hdlr box + Mp4HdlrBox* hdlrbox = dynamic_cast( curbox ); + if(!hdlrbox) + return; + // get handler type + hdlrtype = hdlrbox->hdlr_type(); + } + // check for end of mdia box + if( totalsize == MP4::Mp4IsoBox::size() ) + break; + + } +} diff --git a/amarok/src/metadata/m4a/mp4mdiabox.h b/amarok/src/metadata/m4a/mp4mdiabox.h new file mode 100644 index 00000000..16503bdb --- /dev/null +++ b/amarok/src/metadata/m4a/mp4mdiabox.h @@ -0,0 +1,49 @@ +/*************************************************************************** + copyright : (C) 2002, 2003, 2006 by Jochen Issing + email : jochen.issing@isign-softart.de + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301 USA * + ***************************************************************************/ + +#ifndef MP4MDIABOX_H +#define MP4MDIABOX_H + +#include "mp4isobox.h" +#include "mp4fourcc.h" + +namespace TagLib +{ + namespace MP4 + { + class Mp4MdiaBox: public Mp4IsoBox + { + public: + Mp4MdiaBox( TagLib::File* file, MP4::Fourcc fourcc, TagLib::uint size, long offset ); + ~Mp4MdiaBox(); + + //! parse mdia contents + void parse(); + + private: + class Mp4MdiaBoxPrivate; + Mp4MdiaBoxPrivate* d; + }; // Mp4MdiaBox + + } // namespace MP4 +} // namespace TagLib + +#endif // MP4MDIABOX_H diff --git a/amarok/src/metadata/m4a/mp4metabox.cpp b/amarok/src/metadata/m4a/mp4metabox.cpp new file mode 100644 index 00000000..30eebf2b --- /dev/null +++ b/amarok/src/metadata/m4a/mp4metabox.cpp @@ -0,0 +1,86 @@ +/*************************************************************************** + copyright : (C) 2002, 2003, 2006 by Jochen Issing + email : jochen.issing@isign-softart.de + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301 USA * + ***************************************************************************/ + +#include +#include +#include "mp4metabox.h" +#include "boxfactory.h" +#include "mp4file.h" + +using namespace TagLib; + +class MP4::Mp4MetaBox::Mp4MetaBoxPrivate +{ +public: + //! container for all boxes in meta box + TagLib::List metaBoxes; + //! a box factory for creating the appropriate boxes + MP4::BoxFactory boxfactory; +}; // class Mp4MetaBoxPrivate + +MP4::Mp4MetaBox::Mp4MetaBox( TagLib::File* file, MP4::Fourcc fourcc, TagLib::uint size, long offset ) + : Mp4IsoFullBox( file, fourcc, size, offset ) +{ + d = new MP4::Mp4MetaBox::Mp4MetaBoxPrivate(); +} + +MP4::Mp4MetaBox::~Mp4MetaBox() +{ + TagLib::List::Iterator delIter; + for( delIter = d->metaBoxes.begin(); + delIter != d->metaBoxes.end(); + delIter++ ) + { + delete *delIter; + } + delete d; +} + +void MP4::Mp4MetaBox::parse() +{ + TagLib::MP4::File* mp4file = static_cast( file() ); + + TagLib::uint totalsize = 12; // initial size of box + // parse all contained boxes + TagLib::uint size; + MP4::Fourcc fourcc; + + while( (mp4file->readSizeAndType( size, fourcc ) == true) ) + { + totalsize += size; + + // check for errors + if( totalsize > MP4::Mp4IsoBox::size() ) + { + std::cerr << "Error in mp4 file " << mp4file->name() << " meta box contains bad box with name: " << fourcc.toString() << std::endl; + return; + } + + // create the appropriate subclass and parse it + MP4::Mp4IsoBox* curbox = d->boxfactory.createInstance( mp4file, fourcc, size, mp4file->tell() ); + curbox->parsebox(); + d->metaBoxes.append( curbox ); + + // check for end of meta box + if( totalsize == MP4::Mp4IsoBox::size() ) + break; + } +} diff --git a/amarok/src/metadata/m4a/mp4metabox.h b/amarok/src/metadata/m4a/mp4metabox.h new file mode 100644 index 00000000..58d96670 --- /dev/null +++ b/amarok/src/metadata/m4a/mp4metabox.h @@ -0,0 +1,49 @@ +/*************************************************************************** + copyright : (C) 2002, 2003, 2006 by Jochen Issing + email : jochen.issing@isign-softart.de + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301 USA * + ***************************************************************************/ + +#ifndef MP4METABOX_H +#define MP4METABOX_H + +#include "mp4isofullbox.h" +#include "mp4fourcc.h" + +namespace TagLib +{ + namespace MP4 + { + class Mp4MetaBox: public Mp4IsoFullBox + { + public: + Mp4MetaBox( TagLib::File* file, MP4::Fourcc fourcc, TagLib::uint size, long offset ); + ~Mp4MetaBox(); + + //! parse meta contents + void parse(); + + private: + class Mp4MetaBoxPrivate; + Mp4MetaBoxPrivate* d; + }; // Mp4MetaBox + + } // namespace MP4 +} // namespace TagLib + +#endif // MP4METABOX_H diff --git a/amarok/src/metadata/m4a/mp4minfbox.cpp b/amarok/src/metadata/m4a/mp4minfbox.cpp new file mode 100644 index 00000000..0081dda7 --- /dev/null +++ b/amarok/src/metadata/m4a/mp4minfbox.cpp @@ -0,0 +1,104 @@ +/*************************************************************************** + copyright : (C) 2002, 2003, 2006 by Jochen Issing + email : jochen.issing@isign-softart.de + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301 USA * + ***************************************************************************/ + +#include "tlist.h" +#include +#include "mp4minfbox.h" +#include "mp4stblbox.h" +#include "boxfactory.h" +#include "mp4file.h" + +using namespace TagLib; + +class MP4::Mp4MinfBox::Mp4MinfBoxPrivate +{ +public: + //! container for all boxes in minf box + TagLib::List minfBoxes; + //! a box factory for creating the appropriate boxes + MP4::BoxFactory boxfactory; + //! stores the handler type of the current trak + MP4::Fourcc handler_type; +}; // class Mp4MinfBoxPrivate + +MP4::Mp4MinfBox::Mp4MinfBox( TagLib::File* file, MP4::Fourcc fourcc, TagLib::uint size, long offset ) + : Mp4IsoBox( file, fourcc, size, offset ) +{ + d = new MP4::Mp4MinfBox::Mp4MinfBoxPrivate(); +} + +MP4::Mp4MinfBox::~Mp4MinfBox() +{ + TagLib::List::Iterator delIter; + for( delIter = d->minfBoxes.begin(); + delIter != d->minfBoxes.end(); + delIter++ ) + { + delete *delIter; + } + delete d; +} + +void MP4::Mp4MinfBox::setHandlerType( MP4::Fourcc fourcc ) +{ + d->handler_type = fourcc; +} + +void MP4::Mp4MinfBox::parse() +{ + TagLib::MP4::File* mp4file = static_cast( file() ); + + TagLib::uint totalsize = 8; + // parse all contained boxes + TagLib::uint size; + MP4::Fourcc fourcc; + + while( (mp4file->readSizeAndType( size, fourcc ) == true) ) + { + totalsize += size; + + // check for errors + if( totalsize > MP4::Mp4IsoBox::size() ) + { + std::cerr << "Error in mp4 file " << mp4file->name() << " minf box contains bad box with name: " << fourcc.toString() << std::endl; + return; + } + + // create the appropriate subclass and parse it + MP4::Mp4IsoBox* curbox = d->boxfactory.createInstance( mp4file, fourcc, size, mp4file->tell() ); + if(static_cast( fourcc ) == 0x7374626c /*stbl*/ ) + { + // cast to hdlr box + Mp4StblBox* stblbox = dynamic_cast( curbox ); + if(!stblbox) + return; + // set handler type + stblbox->setHandlerType( d->handler_type ); + } + + curbox->parsebox(); + d->minfBoxes.append( curbox ); + + // check for end of minf box + if( totalsize == MP4::Mp4IsoBox::size() ) + break; + } +} diff --git a/amarok/src/metadata/m4a/mp4minfbox.h b/amarok/src/metadata/m4a/mp4minfbox.h new file mode 100644 index 00000000..9195d307 --- /dev/null +++ b/amarok/src/metadata/m4a/mp4minfbox.h @@ -0,0 +1,51 @@ +/*************************************************************************** + copyright : (C) 2002, 2003, 2006 by Jochen Issing + email : jochen.issing@isign-softart.de + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301 USA * + ***************************************************************************/ + +#ifndef MP4MINFBOX_H +#define MP4MINFBOX_H + +#include "mp4isobox.h" +#include "mp4fourcc.h" + +namespace TagLib +{ + namespace MP4 + { + class Mp4MinfBox: public Mp4IsoBox + { + public: + Mp4MinfBox( TagLib::File* file, MP4::Fourcc fourcc, TagLib::uint size, long offset ); + ~Mp4MinfBox(); + + //! parse minf contents + void parse(); + //! set the handler type - needed for stsd + void setHandlerType( MP4::Fourcc fourcc ); + + private: + class Mp4MinfBoxPrivate; + Mp4MinfBoxPrivate* d; + }; // Mp4MinfBox + + } // namespace MP4 +} // namespace TagLib + +#endif // MP4MINFBOX_H diff --git a/amarok/src/metadata/m4a/mp4moovbox.cpp b/amarok/src/metadata/m4a/mp4moovbox.cpp new file mode 100644 index 00000000..24826ec2 --- /dev/null +++ b/amarok/src/metadata/m4a/mp4moovbox.cpp @@ -0,0 +1,86 @@ +/*************************************************************************** + copyright : (C) 2002, 2003, 2006 by Jochen Issing + email : jochen.issing@isign-softart.de + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301 USA * + ***************************************************************************/ + +#include "tlist.h" +#include +#include "mp4moovbox.h" +#include "boxfactory.h" +#include "mp4file.h" + +using namespace TagLib; + +class MP4::Mp4MoovBox::Mp4MoovBoxPrivate +{ +public: + //! container for all boxes in moov box + TagLib::List moovBoxes; + //! a box factory for creating the appropriate boxes + MP4::BoxFactory boxfactory; +}; // class Mp4MoovBoxPrivate + +MP4::Mp4MoovBox::Mp4MoovBox( TagLib::File* file, MP4::Fourcc fourcc, TagLib::uint size, long offset ) + : Mp4IsoBox( file, fourcc, size, offset ) +{ + d = new MP4::Mp4MoovBox::Mp4MoovBoxPrivate(); +} + +MP4::Mp4MoovBox::~Mp4MoovBox() +{ + TagLib::List::Iterator delIter; + for( delIter = d->moovBoxes.begin(); + delIter != d->moovBoxes.end(); + delIter++ ) + { + delete *delIter; + } + delete d; +} + +void MP4::Mp4MoovBox::parse() +{ + TagLib::MP4::File* mp4file = static_cast( file() ); + + TagLib::uint totalsize = 8; + // parse all contained boxes + TagLib::uint size; + MP4::Fourcc fourcc; + + while( (mp4file->readSizeAndType( size, fourcc ) == true) ) + { + totalsize += size; + + // check for errors + if( totalsize > MP4::Mp4IsoBox::size() ) + { + std::cerr << "Error in mp4 file " << mp4file->name() << " moov box contains bad box with name: " << fourcc.toString() << std::endl; + return; + } + + // create the appropriate subclass and parse it + MP4::Mp4IsoBox* curbox = d->boxfactory.createInstance( mp4file, fourcc, size, mp4file->tell() ); + curbox->parsebox(); + d->moovBoxes.append( curbox ); + + // check for end of moov box + if( totalsize == MP4::Mp4IsoBox::size() ) + break; + } +} diff --git a/amarok/src/metadata/m4a/mp4moovbox.h b/amarok/src/metadata/m4a/mp4moovbox.h new file mode 100644 index 00000000..390953f7 --- /dev/null +++ b/amarok/src/metadata/m4a/mp4moovbox.h @@ -0,0 +1,49 @@ +/*************************************************************************** + copyright : (C) 2002, 2003, 2006 by Jochen Issing + email : jochen.issing@isign-softart.de + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301 USA * + ***************************************************************************/ + +#ifndef MP4MOOVBOX_H +#define MP4MOOVBOX_H + +#include "mp4isobox.h" +#include "mp4fourcc.h" + +namespace TagLib +{ + namespace MP4 + { + class Mp4MoovBox: public Mp4IsoBox + { + public: + Mp4MoovBox( TagLib::File* file, MP4::Fourcc fourcc, TagLib::uint size, long offset ); + ~Mp4MoovBox(); + + //! parse moov contents + void parse(); + + private: + class Mp4MoovBoxPrivate; + Mp4MoovBoxPrivate* d; + }; // Mp4MoovBox + + } // namespace MP4 +} // namespace TagLib + +#endif // MP4MOOVBOX_H diff --git a/amarok/src/metadata/m4a/mp4mvhdbox.cpp b/amarok/src/metadata/m4a/mp4mvhdbox.cpp new file mode 100644 index 00000000..36053e4b --- /dev/null +++ b/amarok/src/metadata/m4a/mp4mvhdbox.cpp @@ -0,0 +1,140 @@ +/*************************************************************************** + copyright : (C) 2002, 2003, 2006 by Jochen Issing + email : jochen.issing@isign-softart.de + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301 USA * + ***************************************************************************/ + +#include +#include +#include "mp4mvhdbox.h" +#include "boxfactory.h" +#include "mp4file.h" +#include "mp4propsproxy.h" + +using namespace TagLib; + +class MP4::Mp4MvhdBox::Mp4MvhdBoxPrivate +{ +public: + //! creation time of the file + TagLib::ulonglong creationTime; + //! modification time of the file - since midnight, Jan. 1, 1904, UTC-time + TagLib::ulonglong modificationTime; + //! timescale for the file - referred by all time specifications in this box + TagLib::uint timescale; + //! duration of presentation + TagLib::ulonglong duration; + //! playout speed + TagLib::uint rate; + //! volume for entire presentation + TagLib::uint volume; + //! track ID for an additional track (next new track) + TagLib::uint nextTrackID; +}; // class Mp4MvhdBoxPrivate + +MP4::Mp4MvhdBox::Mp4MvhdBox( TagLib::File* file, MP4::Fourcc fourcc, TagLib::uint size, long offset ) + : Mp4IsoFullBox( file, fourcc, size, offset ) +{ + d = new MP4::Mp4MvhdBox::Mp4MvhdBoxPrivate(); +} + +MP4::Mp4MvhdBox::~Mp4MvhdBox() +{ + delete d; +} + +TagLib::ulonglong MP4::Mp4MvhdBox::creationTime() const +{ + return d->creationTime; +} + +TagLib::ulonglong MP4::Mp4MvhdBox::modificationTime() const +{ + return d->modificationTime; +} + +TagLib::uint MP4::Mp4MvhdBox::timescale() const +{ + return d->timescale; +} + +TagLib::ulonglong MP4::Mp4MvhdBox::duration() const +{ + return d->duration; +} + +TagLib::uint MP4::Mp4MvhdBox::rate() const +{ + return d->rate; +} + +TagLib::uint MP4::Mp4MvhdBox::volume() const +{ + return d->volume; +} + +TagLib::uint MP4::Mp4MvhdBox::nextTrackID() const +{ + return d->nextTrackID; +} + + +void MP4::Mp4MvhdBox::parse() +{ + TagLib::MP4::File* mp4file = static_cast( file() ); + + if( version() == 1 ) + { + if( !mp4file->readLongLong( d->creationTime ) ) + return; + if( !mp4file->readLongLong( d->modificationTime ) ) + return; + if( !mp4file->readInt( d->timescale ) ) + return; + if( !mp4file->readLongLong( d->duration ) ) + return; + } + else + { + TagLib::uint creationTime_tmp, modificationTime_tmp, duration_tmp; + + if( !mp4file->readInt( creationTime_tmp ) ) + return; + if( !mp4file->readInt( modificationTime_tmp ) ) + return; + if( !mp4file->readInt( d->timescale ) ) + return; + if( !mp4file->readInt( duration_tmp ) ) + return; + + d->creationTime = creationTime_tmp; + d->modificationTime = modificationTime_tmp; + d->duration = duration_tmp; + } + if( !mp4file->readInt( d->rate ) ) + return; + if( !mp4file->readInt( d->volume ) ) + return; + // jump over unused fields + mp4file->seek( 68, File::Current ); + + if( !mp4file->readInt( d->nextTrackID ) ) + return; + // register at proxy + mp4file->propProxy()->registerMvhd( this ); +} diff --git a/amarok/src/metadata/m4a/mp4mvhdbox.h b/amarok/src/metadata/m4a/mp4mvhdbox.h new file mode 100644 index 00000000..b133485e --- /dev/null +++ b/amarok/src/metadata/m4a/mp4mvhdbox.h @@ -0,0 +1,65 @@ +/*************************************************************************** + copyright : (C) 2002, 2003, 2006 by Jochen Issing + email : jochen.issing@isign-softart.de + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301 USA * + ***************************************************************************/ + +#ifndef MP4MVHDBOX_H +#define MP4MVHDBOX_H + +#include "mp4isofullbox.h" +#include "mp4fourcc.h" +#include "mp4file.h" // ulonglong + +namespace TagLib +{ + namespace MP4 + { + class Mp4MvhdBox: public Mp4IsoFullBox + { + public: + Mp4MvhdBox( TagLib::File* file, MP4::Fourcc fourcc, TagLib::uint size, long offset ); + ~Mp4MvhdBox(); + + //! function to get the creation time of the mp4 file + ulonglong creationTime() const; + //! function to get the modification time of the mp4 file + ulonglong modificationTime() const; + //! function to get the timescale referenced by the above timestamps + uint timescale() const; + //! function to get the presentation duration in the mp4 file + ulonglong duration() const; + //! function to get the rate (playout speed) - typically 1.0; + uint rate() const; + //! function to get volume level for presentation - typically 1.0; + uint volume() const; + //! function to get the track ID for adding new tracks - useless for this lib + uint nextTrackID() const; + + //! parse mvhd contents + void parse(); + + private: + class Mp4MvhdBoxPrivate; + Mp4MvhdBoxPrivate* d; + }; // Mp4MvhdBox + + } // namespace MP4 +} // namespace TagLib + +#endif // MP4MVHDBOX_H diff --git a/amarok/src/metadata/m4a/mp4propsproxy.cpp b/amarok/src/metadata/m4a/mp4propsproxy.cpp new file mode 100644 index 00000000..ebee9c29 --- /dev/null +++ b/amarok/src/metadata/m4a/mp4propsproxy.cpp @@ -0,0 +1,89 @@ +/*************************************************************************** + copyright : (C) 2002, 2003, 2006 by Jochen Issing + email : jochen.issing@isign-softart.de + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301 USA * + ***************************************************************************/ + +#include "mp4propsproxy.h" + +using namespace TagLib; + +class MP4::Mp4PropsProxy::Mp4PropsProxyPrivate +{ +public: + //! the movie header box + MP4::Mp4MvhdBox* mvhdbox; + //! the sample table box + MP4::Mp4AudioSampleEntry* audiosampleentry; +}; + + +MP4::Mp4PropsProxy::Mp4PropsProxy() +{ + d = new MP4::Mp4PropsProxy::Mp4PropsProxyPrivate(); + d->mvhdbox = 0; + d->audiosampleentry = 0; +} + +MP4::Mp4PropsProxy::~Mp4PropsProxy() +{ + delete d; +} + +TagLib::uint MP4::Mp4PropsProxy::seconds() const +{ + if( d->mvhdbox ) + return static_cast( d->mvhdbox->duration() / d->mvhdbox->timescale() ); + else + return 0; +} + +TagLib::uint MP4::Mp4PropsProxy::channels() const +{ + if( d->audiosampleentry ) + return d->audiosampleentry->channels(); + else + return 0; +} + +TagLib::uint MP4::Mp4PropsProxy::sampleRate() const +{ + if( d->audiosampleentry ) + return (d->audiosampleentry->samplerate()>>16); + else + return 0; +} + +TagLib::uint MP4::Mp4PropsProxy::bitRate() const +{ + if( d->audiosampleentry ) + return (d->audiosampleentry->bitrate()); + else + return 0; +} + +void MP4::Mp4PropsProxy::registerMvhd( MP4::Mp4MvhdBox* mvhdbox ) +{ + d->mvhdbox = mvhdbox; +} + +void MP4::Mp4PropsProxy::registerAudioSampleEntry( MP4::Mp4AudioSampleEntry* audioSampleEntry ) +{ + d->audiosampleentry = audioSampleEntry; +} + diff --git a/amarok/src/metadata/m4a/mp4propsproxy.h b/amarok/src/metadata/m4a/mp4propsproxy.h new file mode 100644 index 00000000..0ea29e2b --- /dev/null +++ b/amarok/src/metadata/m4a/mp4propsproxy.h @@ -0,0 +1,65 @@ +/*************************************************************************** + copyright : (C) 2002, 2003, 2006 by Jochen Issing + email : jochen.issing@isign-softart.de + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301 USA * + ***************************************************************************/ + +#ifndef MP4PROPSPROXY_H +#define MP4PROPSPROXY_H MP4PROPSPROXY_H +#include "mp4mvhdbox.h" +#include "mp4audiosampleentry.h" + +namespace TagLib +{ + namespace MP4 + { + //! Mp4PropsProxy is used to access the stsd box and mvhd box directly + /*! this class works as a shortcut to avoid stepping through all parent boxes + * to access the boxes in question + */ + class Mp4PropsProxy + { + public: + //! constructor for properties proxy + Mp4PropsProxy(); + //! destructor + ~Mp4PropsProxy(); + + //! function to get length of media in seconds + TagLib::uint seconds() const; + //! function to get the nunmber of channels + TagLib::uint channels() const; + //! function to get the sample rate + TagLib::uint sampleRate() const; + //! function to get the bitrate rate + TagLib::uint bitRate() const; + + //! function to register the movie header box - mvhd + void registerMvhd( MP4::Mp4MvhdBox* mvhdbox ); + //! function to register the sample description box + void registerAudioSampleEntry( MP4::Mp4AudioSampleEntry* audiosampleentry ); + + private: + class Mp4PropsProxyPrivate; + Mp4PropsProxyPrivate* d; + + }; // Mp4PropsProxy + } // MP4 +} // TagLib + +#endif // MP4PROPSPROXY_H diff --git a/amarok/src/metadata/m4a/mp4sampleentry.cpp b/amarok/src/metadata/m4a/mp4sampleentry.cpp new file mode 100644 index 00000000..e5619eaa --- /dev/null +++ b/amarok/src/metadata/m4a/mp4sampleentry.cpp @@ -0,0 +1,59 @@ +/*************************************************************************** + copyright : (C) 2002, 2003, 2006 by Jochen Issing + email : jochen.issing@isign-softart.de + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301 USA * + ***************************************************************************/ + +#include "mp4sampleentry.h" +#include "mp4isobox.h" +#include "mp4file.h" + +using namespace TagLib; + +class MP4::Mp4SampleEntry::Mp4SampleEntryPrivate +{ +public: + TagLib::uint data_reference_index; +}; + +MP4::Mp4SampleEntry::Mp4SampleEntry( TagLib::File* file, MP4::Fourcc fourcc, uint size, long offset ) + :Mp4IsoBox(file, fourcc, size, offset) +{ + d = new MP4::Mp4SampleEntry::Mp4SampleEntryPrivate(); +} + +MP4::Mp4SampleEntry::~Mp4SampleEntry() +{ + delete d; +} + +//! parse the content of the box +void MP4::Mp4SampleEntry::parse() +{ + TagLib::MP4::File* mp4file = dynamic_cast(file()); + if(!mp4file) + return; + + // skip the first 6 bytes + mp4file->seek( 6, TagLib::File::Current ); + // read data reference index + if(!mp4file->readShort( d->data_reference_index)) + return; + parseEntry(); +} + diff --git a/amarok/src/metadata/m4a/mp4sampleentry.h b/amarok/src/metadata/m4a/mp4sampleentry.h new file mode 100644 index 00000000..39475f7c --- /dev/null +++ b/amarok/src/metadata/m4a/mp4sampleentry.h @@ -0,0 +1,54 @@ +/*************************************************************************** + copyright : (C) 2002, 2003, 2006 by Jochen Issing + email : jochen.issing@isign-softart.de + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301 USA * + ***************************************************************************/ + +#ifndef MP4SAMPLEENTRY_H +#define MP4SAMPLEENTRY_H + +#include "mp4isobox.h" +#include "mp4fourcc.h" + +namespace TagLib +{ + namespace MP4 + { + class Mp4SampleEntry: public Mp4IsoBox + { + public: + Mp4SampleEntry( TagLib::File* file, MP4::Fourcc fourcc, uint size, long offset ); + ~Mp4SampleEntry(); + + public: + //! parse the content of the box + virtual void parse(); + + private: + //! function to be implemented in subclass + virtual void parseEntry() = 0; + + protected: + class Mp4SampleEntryPrivate; + Mp4SampleEntryPrivate* d; + }; // class Mp4SampleEntry + + } // namespace MP4 +} // namespace TagLib + +#endif // MP4SAMPLEENTRY_H diff --git a/amarok/src/metadata/m4a/mp4skipbox.cpp b/amarok/src/metadata/m4a/mp4skipbox.cpp new file mode 100644 index 00000000..8cb52180 --- /dev/null +++ b/amarok/src/metadata/m4a/mp4skipbox.cpp @@ -0,0 +1,50 @@ +/*************************************************************************** + copyright : (C) 2002, 2003, 2006 by Jochen Issing + email : jochen.issing@isign-softart.de + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301 USA * + ***************************************************************************/ + +#include "mp4skipbox.h" +#include "mp4isobox.h" +#include "tfile.h" + +using namespace TagLib; + +class MP4::Mp4SkipBox::Mp4SkipBoxPrivate +{ +public: +}; + +MP4::Mp4SkipBox::Mp4SkipBox( TagLib::File* file, MP4::Fourcc fourcc, uint size, long offset ) + :Mp4IsoBox(file, fourcc, size, offset) +{ + d = new MP4::Mp4SkipBox::Mp4SkipBoxPrivate(); +} + +MP4::Mp4SkipBox::~Mp4SkipBox() +{ + delete d; +} + +//! parse the content of the box +void MP4::Mp4SkipBox::parse() +{ + // skip contents + file()->seek( size() - 8, TagLib::File::Current ); +} + diff --git a/amarok/src/metadata/m4a/mp4skipbox.h b/amarok/src/metadata/m4a/mp4skipbox.h new file mode 100644 index 00000000..896fcaa7 --- /dev/null +++ b/amarok/src/metadata/m4a/mp4skipbox.h @@ -0,0 +1,50 @@ +/*************************************************************************** + copyright : (C) 2002, 2003, 2006 by Jochen Issing + email : jochen.issing@isign-softart.de + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301 USA * + ***************************************************************************/ + +#ifndef MP4SKIPBOX_H +#define MP4SKIPBOX_H + +#include "mp4isobox.h" +#include "mp4fourcc.h" + +namespace TagLib +{ + namespace MP4 + { + class Mp4SkipBox: public Mp4IsoBox + { + public: + Mp4SkipBox( TagLib::File* file, MP4::Fourcc fourcc, uint size, long offset ); + ~Mp4SkipBox(); + + private: + //! parse the content of the box + virtual void parse(); + + protected: + class Mp4SkipBoxPrivate; + Mp4SkipBoxPrivate* d; + }; // class Mp4SkipBox + + } // namespace MP4 +} // namespace TagLib + +#endif // MP4SKIPBOX_H diff --git a/amarok/src/metadata/m4a/mp4stblbox.cpp b/amarok/src/metadata/m4a/mp4stblbox.cpp new file mode 100644 index 00000000..f67de597 --- /dev/null +++ b/amarok/src/metadata/m4a/mp4stblbox.cpp @@ -0,0 +1,105 @@ +/*************************************************************************** + copyright : (C) 2002, 2003, 2006 by Jochen Issing + email : jochen.issing@isign-softart.de + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301 USA * + ***************************************************************************/ + +#include "tlist.h" +#include +#include "mp4stblbox.h" +#include "mp4stsdbox.h" +#include "boxfactory.h" +#include "mp4file.h" + +using namespace TagLib; + +class MP4::Mp4StblBox::Mp4StblBoxPrivate +{ +public: + //! container for all boxes in stbl box + TagLib::List stblBoxes; + //! a box factory for creating the appropriate boxes + MP4::BoxFactory boxfactory; + //! the handler type for the current trak + MP4::Fourcc handler_type; +}; // class Mp4StblBoxPrivate + +MP4::Mp4StblBox::Mp4StblBox( TagLib::File* file, MP4::Fourcc fourcc, TagLib::uint size, long offset ) + : Mp4IsoBox( file, fourcc, size, offset ) +{ + d = new MP4::Mp4StblBox::Mp4StblBoxPrivate(); +} + +MP4::Mp4StblBox::~Mp4StblBox() +{ + TagLib::List::Iterator delIter; + for( delIter = d->stblBoxes.begin(); + delIter != d->stblBoxes.end(); + delIter++ ) + { + delete *delIter; + } + delete d; +} + +void MP4::Mp4StblBox::setHandlerType( MP4::Fourcc fourcc ) +{ + d->handler_type = fourcc; +} + +void MP4::Mp4StblBox::parse() +{ + TagLib::MP4::File* mp4file = static_cast( file() ); + + TagLib::uint totalsize = 8; + // parse all contained boxes + TagLib::uint size; + MP4::Fourcc fourcc; + + while( (mp4file->readSizeAndType( size, fourcc ) == true) ) + { + totalsize += size; + + // check for errors + if( totalsize > MP4::Mp4IsoBox::size() ) + { + std::cerr << "Error in mp4 file " << mp4file->name() << " stbl box contains bad box with name: " << fourcc.toString() << std::endl; + return; + } + + // create the appropriate subclass and parse it + MP4::Mp4IsoBox* curbox = d->boxfactory.createInstance( mp4file, fourcc, size, mp4file->tell() ); + + // check for stsd + if( static_cast(fourcc) == 0x73747364 /*'stsd'*/ ) + { + // cast to stsd box + MP4::Mp4StsdBox* stsdbox = dynamic_cast(curbox); + if(!stsdbox) + return; + // set the handler type + stsdbox->setHandlerType( d->handler_type ); + } + curbox->parsebox(); + d->stblBoxes.append( curbox ); + + // check for end of stbl box + if( totalsize == MP4::Mp4IsoBox::size() ) + break; + } +} diff --git a/amarok/src/metadata/m4a/mp4stblbox.h b/amarok/src/metadata/m4a/mp4stblbox.h new file mode 100644 index 00000000..39619a6e --- /dev/null +++ b/amarok/src/metadata/m4a/mp4stblbox.h @@ -0,0 +1,51 @@ +/*************************************************************************** + copyright : (C) 2002, 2003, 2006 by Jochen Issing + email : jochen.issing@isign-softart.de + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301 USA * + ***************************************************************************/ + +#ifndef MP4STBLBOX_H +#define MP4STBLBOX_H + +#include "mp4isobox.h" +#include "mp4fourcc.h" + +namespace TagLib +{ + namespace MP4 + { + class Mp4StblBox: public Mp4IsoBox + { + public: + Mp4StblBox( TagLib::File* file, MP4::Fourcc fourcc, TagLib::uint size, long offset ); + ~Mp4StblBox(); + + //! parse stbl contents + void parse(); + //! set the handler type - needed for stsd + void setHandlerType( MP4::Fourcc fourcc ); + + private: + class Mp4StblBoxPrivate; + Mp4StblBoxPrivate* d; + }; // Mp4StblBox + + } // namespace MP4 +} // namespace TagLib + +#endif // MP4STBLBOX_H diff --git a/amarok/src/metadata/m4a/mp4stsdbox.cpp b/amarok/src/metadata/m4a/mp4stsdbox.cpp new file mode 100644 index 00000000..6bb8cbe6 --- /dev/null +++ b/amarok/src/metadata/m4a/mp4stsdbox.cpp @@ -0,0 +1,91 @@ +/*************************************************************************** + copyright : (C) 2002, 2003, 2006 by Jochen Issing + email : jochen.issing@isign-softart.de + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301 USA * + ***************************************************************************/ + +#include "tlist.h" +#include +#include "mp4stsdbox.h" +#include "mp4audiosampleentry.h" +#include "mp4file.h" + +using namespace TagLib; + +class MP4::Mp4StsdBox::Mp4StsdBoxPrivate +{ +public: + //! the handler type for the current trak + MP4::Fourcc handler_type; + //! the audio sample entry + MP4::Mp4AudioSampleEntry* audioSampleEntry; +}; // class Mp4StsdBoxPrivate + +MP4::Mp4StsdBox::Mp4StsdBox( TagLib::File* file, MP4::Fourcc fourcc, TagLib::uint size, long offset ) + : Mp4IsoFullBox( file, fourcc, size, offset ) +{ + d = new MP4::Mp4StsdBox::Mp4StsdBoxPrivate(); +} + +MP4::Mp4StsdBox::~Mp4StsdBox() +{ + delete d; +} + +void MP4::Mp4StsdBox::setHandlerType( MP4::Fourcc fourcc ) +{ + d->handler_type = fourcc; +} + +void MP4::Mp4StsdBox::parse() +{ + MP4::File* mp4file = dynamic_cast( file() ); + if(!mp4file) + return; + + TagLib::uint totalsize = 12; // initial size of box + + // check for handler type - only parse if 'soun': + if( static_cast(d->handler_type) == 0x736f756e ) + { + // read entry count + TagLib::uint entry_count; + if(!mp4file->readInt( entry_count )) + return; + + // simply read first entry and skip all following + // read size and type + TagLib::uint cursize; + MP4::Fourcc fourcc; + if( !mp4file->readSizeAndType( cursize, fourcc )) + return; + + totalsize += 12; + // alocate an AudioSampleEntry + d->audioSampleEntry = new MP4::Mp4AudioSampleEntry( mp4file, fourcc, cursize, mp4file->tell() ); + // parse the AudioSampleEntry + d->audioSampleEntry->parse(); + totalsize += cursize-8; + // skip the remaining box contents + mp4file->seek( size()-totalsize, TagLib::File::Current ); + } + else + { + mp4file->seek( size()-totalsize, TagLib::File::Current ); + } +} diff --git a/amarok/src/metadata/m4a/mp4stsdbox.h b/amarok/src/metadata/m4a/mp4stsdbox.h new file mode 100644 index 00000000..90bc0147 --- /dev/null +++ b/amarok/src/metadata/m4a/mp4stsdbox.h @@ -0,0 +1,51 @@ +/*************************************************************************** + copyright : (C) 2002, 2003, 2006 by Jochen Issing + email : jochen.issing@isign-softart.de + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301 USA * + ***************************************************************************/ + +#ifndef MP4STSDBOX_H +#define MP4STSDBOX_H + +#include "mp4isofullbox.h" +#include "mp4fourcc.h" + +namespace TagLib +{ + namespace MP4 + { + class Mp4StsdBox: public Mp4IsoFullBox + { + public: + Mp4StsdBox( TagLib::File* file, MP4::Fourcc fourcc, TagLib::uint size, long offset ); + ~Mp4StsdBox(); + + //! parse stsd contents + void parse(); + //! set the handler type - needed for stsd + void setHandlerType( MP4::Fourcc fourcc ); + + private: + class Mp4StsdBoxPrivate; + Mp4StsdBoxPrivate* d; + }; // Mp4StsdBox + + } // namespace MP4 +} // namespace TagLib + +#endif // MP4STSDBOX_H diff --git a/amarok/src/metadata/m4a/mp4tagsproxy.cpp b/amarok/src/metadata/m4a/mp4tagsproxy.cpp new file mode 100644 index 00000000..0a77427c --- /dev/null +++ b/amarok/src/metadata/m4a/mp4tagsproxy.cpp @@ -0,0 +1,168 @@ +/*************************************************************************** + copyright : (C) 2002, 2003, 2006 by Jochen Issing + email : jochen.issing@isign-softart.de + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301 USA * + ***************************************************************************/ + +#include "mp4tagsproxy.h" +#include "itunesdatabox.h" + +using namespace TagLib; + +class MP4::Mp4TagsProxy::Mp4TagsProxyPrivate +{ +public: + ITunesDataBox* titleData; + ITunesDataBox* artistData; + ITunesDataBox* albumData; + ITunesDataBox* coverData; + ITunesDataBox* genreData; + ITunesDataBox* yearData; + ITunesDataBox* trknData; + ITunesDataBox* commentData; + ITunesDataBox* groupingData; + ITunesDataBox* composerData; + ITunesDataBox* diskData; + ITunesDataBox* bpmData; +}; + +MP4::Mp4TagsProxy::Mp4TagsProxy() +{ + d = new MP4::Mp4TagsProxy::Mp4TagsProxyPrivate(); + d->titleData = 0; + d->artistData = 0; + d->albumData = 0; + d->coverData = 0; + d->genreData = 0; + d->yearData = 0; + d->trknData = 0; + d->commentData = 0; + d->groupingData = 0; + d->composerData = 0; + d->diskData = 0; + d->bpmData = 0; +} + +MP4::Mp4TagsProxy::~Mp4TagsProxy() +{ + delete d; +} + +MP4::ITunesDataBox* MP4::Mp4TagsProxy::titleData() const +{ + return d->titleData; +} + +MP4::ITunesDataBox* MP4::Mp4TagsProxy::artistData() const +{ + return d->artistData; +} + +MP4::ITunesDataBox* MP4::Mp4TagsProxy::albumData() const +{ + return d->albumData; +} + +MP4::ITunesDataBox* MP4::Mp4TagsProxy::genreData() const +{ + return d->genreData; +} + +MP4::ITunesDataBox* MP4::Mp4TagsProxy::yearData() const +{ + return d->yearData; +} + +MP4::ITunesDataBox* MP4::Mp4TagsProxy::trknData() const +{ + return d->trknData; +} + +MP4::ITunesDataBox* MP4::Mp4TagsProxy::commentData() const +{ + return d->commentData; +} + +MP4::ITunesDataBox* MP4::Mp4TagsProxy::groupingData() const +{ + return d->groupingData; +} + +MP4::ITunesDataBox* MP4::Mp4TagsProxy::composerData() const +{ + return d->composerData; +} + +MP4::ITunesDataBox* MP4::Mp4TagsProxy::diskData() const +{ + return d->diskData; +} + +MP4::ITunesDataBox* MP4::Mp4TagsProxy::bpmData() const +{ + return d->bpmData; +} + +MP4::ITunesDataBox* MP4::Mp4TagsProxy::coverData() const +{ + return d->coverData; +} + +void MP4::Mp4TagsProxy::registerBox( EBoxType boxtype, ITunesDataBox* databox ) +{ + switch( boxtype ) + { + case title: + d->titleData = databox; + break; + case artist: + d->artistData = databox; + break; + case album: + d->albumData = databox; + break; + case cover: + d->coverData = databox; + break; + case genre: + d->genreData = databox; + break; + case year: + d->yearData = databox; + break; + case trackno: + d->trknData = databox; + break; + case comment: + d->commentData = databox; + break; + case grouping: + d->groupingData = databox; + break; + case composer: + d->composerData = databox; + break; + case disk: + d->diskData = databox; + break; + case bpm: + d->bpmData = databox; + break; + } +} + diff --git a/amarok/src/metadata/m4a/mp4tagsproxy.h b/amarok/src/metadata/m4a/mp4tagsproxy.h new file mode 100644 index 00000000..c8bcbf06 --- /dev/null +++ b/amarok/src/metadata/m4a/mp4tagsproxy.h @@ -0,0 +1,99 @@ +/*************************************************************************** + copyright : (C) 2002, 2003, 2006 by Jochen Issing + email : jochen.issing@isign-softart.de + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301 USA * + ***************************************************************************/ + +#ifndef MP4TAGSPROXY_H +#define MP4TAGSPROXY_H + +namespace TagLib +{ + namespace MP4 + { + // forward declaration(s) + class ITunesDataBox; + /*! proxy for mp4 itunes tag relevant boxes + * + * this class works as a proxy for the specific tag boxes + * in an mp4 itunes file. the boxes are mired in + * the mp4 file structure and stepping through all box layers + * is avoided by registration at the proxy object. + */ + class Mp4TagsProxy + { + public: + /*! enum for all supported box types */ + typedef enum + { + title = 0, + artist, + album, + cover, + genre, + year, + trackno, + comment, + grouping, + composer, + disk, + bpm + } EBoxType; + + //! constructor + Mp4TagsProxy(); + //! destructor + ~Mp4TagsProxy(); + + //! function to get the data box for the title + ITunesDataBox* titleData() const; + //! function to get the data box for the artist + ITunesDataBox* artistData() const; + //! function to get the data box for the album + ITunesDataBox* albumData() const; + //! function to get the data box for the genre + ITunesDataBox* genreData() const; + //! function to get the data box for the year + ITunesDataBox* yearData() const; + //! function to get the data box for the track number + ITunesDataBox* trknData() const; + //! function to get the data box for the comment + ITunesDataBox* commentData() const; + //! function to get the data box for the grouping + ITunesDataBox* groupingData() const; + //! function to get the data box for the composer + ITunesDataBox* composerData() const; + //! function to get the data box for the disk number + ITunesDataBox* diskData() const; + //! function to get the data box for the bpm + ITunesDataBox* bpmData() const; + //! function to get the data box for the cover + ITunesDataBox* coverData() const; + + //! function to register a data box for a certain box type + void registerBox( EBoxType boxtype, ITunesDataBox* databox ); + + private: + class Mp4TagsProxyPrivate; + //! private data of tags proxy + Mp4TagsProxyPrivate* d; + }; // class Mp4TagsProxy + } // namespace MP4 +} // namespace TagLib + +#endif // MP4TAGSPROXY_H diff --git a/amarok/src/metadata/m4a/mp4trakbox.cpp b/amarok/src/metadata/m4a/mp4trakbox.cpp new file mode 100644 index 00000000..c573ec7a --- /dev/null +++ b/amarok/src/metadata/m4a/mp4trakbox.cpp @@ -0,0 +1,86 @@ +/*************************************************************************** + copyright : (C) 2002, 2003, 2006 by Jochen Issing + email : jochen.issing@isign-softart.de + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301 USA * + ***************************************************************************/ + +#include "tlist.h" +#include +#include "mp4trakbox.h" +#include "boxfactory.h" +#include "mp4file.h" + +using namespace TagLib; + +class MP4::Mp4TrakBox::Mp4TrakBoxPrivate +{ +public: + //! container for all boxes in trak box + TagLib::List trakBoxes; + //! a box factory for creating the appropriate boxes + MP4::BoxFactory boxfactory; +}; // class Mp4TrakBoxPrivate + +MP4::Mp4TrakBox::Mp4TrakBox( TagLib::File* file, MP4::Fourcc fourcc, TagLib::uint size, long offset ) + : Mp4IsoBox( file, fourcc, size, offset ) +{ + d = new MP4::Mp4TrakBox::Mp4TrakBoxPrivate(); +} + +MP4::Mp4TrakBox::~Mp4TrakBox() +{ + TagLib::List::Iterator delIter; + for( delIter = d->trakBoxes.begin(); + delIter != d->trakBoxes.end(); + delIter++ ) + { + delete *delIter; + } + delete d; +} + +void MP4::Mp4TrakBox::parse() +{ + TagLib::MP4::File* mp4file = static_cast( file() ); + + TagLib::uint totalsize = 8; + // parse all contained boxes + TagLib::uint size; + MP4::Fourcc fourcc; + + while( (mp4file->readSizeAndType( size, fourcc ) == true) ) + { + totalsize += size; + + // check for errors + if( totalsize > MP4::Mp4IsoBox::size() ) + { + std::cerr << "Error in mp4 file " << mp4file->name() << " trak box contains bad box with name: " << fourcc.toString() << std::endl; + return; + } + + // create the appropriate subclass and parse it + MP4::Mp4IsoBox* curbox = d->boxfactory.createInstance( mp4file, fourcc, size, mp4file->tell() ); + curbox->parsebox(); + d->trakBoxes.append( curbox ); + + // check for end of trak box + if( totalsize == MP4::Mp4IsoBox::size() ) + break; + } +} diff --git a/amarok/src/metadata/m4a/mp4trakbox.h b/amarok/src/metadata/m4a/mp4trakbox.h new file mode 100644 index 00000000..b362fa32 --- /dev/null +++ b/amarok/src/metadata/m4a/mp4trakbox.h @@ -0,0 +1,49 @@ +/*************************************************************************** + copyright : (C) 2002, 2003, 2006 by Jochen Issing + email : jochen.issing@isign-softart.de + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301 USA * + ***************************************************************************/ + +#ifndef MP4TRAKBOX_H +#define MP4TRAKBOX_H + +#include "mp4isobox.h" +#include "mp4fourcc.h" + +namespace TagLib +{ + namespace MP4 + { + class Mp4TrakBox: public Mp4IsoBox + { + public: + Mp4TrakBox( TagLib::File* file, MP4::Fourcc fourcc, TagLib::uint size, long offset ); + ~Mp4TrakBox(); + + //! parse trak contents + void parse(); + + private: + class Mp4TrakBoxPrivate; + Mp4TrakBoxPrivate* d; + }; // Mp4TrakBox + + } // namespace MP4 +} // namespace TagLib + +#endif // MP4TRAKBOX_H diff --git a/amarok/src/metadata/m4a/mp4udtabox.cpp b/amarok/src/metadata/m4a/mp4udtabox.cpp new file mode 100644 index 00000000..268d1c1b --- /dev/null +++ b/amarok/src/metadata/m4a/mp4udtabox.cpp @@ -0,0 +1,95 @@ +/*************************************************************************** + copyright : (C) 2002, 2003, 2006 by Jochen Issing + email : jochen.issing@isign-softart.de + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301 USA * + ***************************************************************************/ + +#include "tlist.h" +#include +#include "mp4udtabox.h" +#include "boxfactory.h" +#include "mp4file.h" + +using namespace TagLib; + +class MP4::Mp4UdtaBox::Mp4UdtaBoxPrivate +{ +public: + //! container for all boxes in udta box + TagLib::List udtaBoxes; + //! a box factory for creating the appropriate boxes + MP4::BoxFactory boxfactory; +}; // class Mp4UdtaBoxPrivate + +MP4::Mp4UdtaBox::Mp4UdtaBox( TagLib::File* file, MP4::Fourcc fourcc, TagLib::uint size, long offset ) + : Mp4IsoBox( file, fourcc, size, offset ) +{ + d = new MP4::Mp4UdtaBox::Mp4UdtaBoxPrivate(); +} + +MP4::Mp4UdtaBox::~Mp4UdtaBox() +{ + TagLib::List::Iterator delIter; + for( delIter = d->udtaBoxes.begin(); + delIter != d->udtaBoxes.end(); + delIter++ ) + { + delete *delIter; + } + delete d; +} + +void MP4::Mp4UdtaBox::parse() +{ +#if 0 + std::cout << " parsing udta box" << std::endl; +#endif + TagLib::MP4::File* mp4file = static_cast( file() ); + + TagLib::uint totalsize = 8; + // parse all contained boxes + TagLib::uint size; + MP4::Fourcc fourcc; + +#if 0 + std::cout << " "; +#endif + while( (mp4file->readSizeAndType( size, fourcc ) == true) ) + { + totalsize += size; + + // check for errors + if( totalsize > MP4::Mp4IsoBox::size() ) + { + std::cerr << "Error in mp4 file " << mp4file->name() << " udta box contains bad box with name: " << fourcc.toString() << std::endl; + return; + } + + // create the appropriate subclass and parse it + MP4::Mp4IsoBox* curbox = d->boxfactory.createInstance( mp4file, fourcc, size, mp4file->tell() ); + curbox->parsebox(); + d->udtaBoxes.append( curbox ); + + // check for end of udta box + if( totalsize == MP4::Mp4IsoBox::size() ) + break; +#if 0 + std::cout << " "; +#endif + } +} diff --git a/amarok/src/metadata/m4a/mp4udtabox.h b/amarok/src/metadata/m4a/mp4udtabox.h new file mode 100644 index 00000000..4873e321 --- /dev/null +++ b/amarok/src/metadata/m4a/mp4udtabox.h @@ -0,0 +1,49 @@ +/*************************************************************************** + copyright : (C) 2002, 2003, 2006 by Jochen Issing + email : jochen.issing@isign-softart.de + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301 USA * + ***************************************************************************/ + +#ifndef MP4UDTABOX_H +#define MP4UDTABOX_H + +#include "mp4isobox.h" +#include "mp4fourcc.h" + +namespace TagLib +{ + namespace MP4 + { + class Mp4UdtaBox: public Mp4IsoBox + { + public: + Mp4UdtaBox( TagLib::File* file, MP4::Fourcc fourcc, TagLib::uint size, long offset ); + ~Mp4UdtaBox(); + + //! parse moov contents + void parse(); + + private: + class Mp4UdtaBoxPrivate; + Mp4UdtaBoxPrivate* d; + }; // Mp4UdtaBox + + } // namespace MP4 +} // namespace TagLib + +#endif // MP4UDTABOX_H diff --git a/amarok/src/metadata/m4a/taglib_mp4filetyperesolver.cpp b/amarok/src/metadata/m4a/taglib_mp4filetyperesolver.cpp new file mode 100644 index 00000000..9566a934 --- /dev/null +++ b/amarok/src/metadata/m4a/taglib_mp4filetyperesolver.cpp @@ -0,0 +1,42 @@ +/*************************************************************************** + copyright : (C) 2006 by Martin Aumueller + email : aumuell@reserv.at + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301 USA * + ***************************************************************************/ + +#include "taglib_mp4filetyperesolver.h" +#include "mp4file.h" + +#include + +TagLib::File *MP4FileTypeResolver::createFile(const char *fileName, + bool readProperties, + TagLib::AudioProperties::ReadStyle propertiesStyle) const +{ +// fprintf(stderr, "mp4?: %s\n", fileName); + const char *ext = strrchr(fileName, '.'); + if(ext && (!strcasecmp(ext, ".m4a") + || !strcasecmp(ext, ".m4b") || !strcasecmp(ext, ".m4p") + || !strcasecmp(ext, ".mp4") + || !strcasecmp(ext, ".m4v") || !strcasecmp(ext, ".mp4v"))) + { + return new TagLib::MP4::File(fileName, readProperties, propertiesStyle); + } + + return 0; +} diff --git a/amarok/src/metadata/m4a/taglib_mp4filetyperesolver.h b/amarok/src/metadata/m4a/taglib_mp4filetyperesolver.h new file mode 100644 index 00000000..fbc3dd4d --- /dev/null +++ b/amarok/src/metadata/m4a/taglib_mp4filetyperesolver.h @@ -0,0 +1,36 @@ +/*************************************************************************** + copyright : (C) 2006 by Martin Aumueller + email : aumuell@reserv.at + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301 USA * + ***************************************************************************/ + +#ifndef TAGLIB_MP4FILETYPERESOLVER_H +#define TAGLIB_MP4FILETYPERESOLVER_H + +#include +#include + + +class MP4FileTypeResolver : public TagLib::FileRef::FileTypeResolver +{ + TagLib::File *createFile(const char *fileName, + bool readAudioProperties, + TagLib::AudioProperties::ReadStyle audioPropertiesStyle) const; +}; + +#endif diff --git a/amarok/src/metadata/mp4/Makefile.am b/amarok/src/metadata/mp4/Makefile.am new file mode 100644 index 00000000..c48ccc5f --- /dev/null +++ b/amarok/src/metadata/mp4/Makefile.am @@ -0,0 +1,18 @@ +SUBDIRS = + +INCLUDES = $(all_includes) $(MP4V2_INCLUDES) $(TAGLIB_CFLAGS) +METASOURCES = AUTO +libtagmp4_la_LDFLAGS = $(all_libraries) $(MP4V2_LIBS) +noinst_LTLIBRARIES = libtagmp4.la + +libtagmp4_la_SOURCES = \ + mp4properties.cpp \ + mp4tag.cpp \ + mp4file.cpp \ + taglib_mp4filetyperesolver.cpp + +noinst_HEADERS = \ + mp4properties.h \ + mp4tag.h \ + mp4file.h \ + taglib_mp4filetyperesolver.h diff --git a/amarok/src/metadata/mp4/mp4file.cpp b/amarok/src/metadata/mp4/mp4file.cpp new file mode 100644 index 00000000..ec8feb04 --- /dev/null +++ b/amarok/src/metadata/mp4/mp4file.cpp @@ -0,0 +1,197 @@ +/*************************************************************************** +copyright : (C) 2005 by Andy Leadbetter +email : andrew.leadbetter@gmail.com + +copyright : (C) 2005 by Martin Aumueller +email : aumuell@reserv.at + (write support) + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301 USA * + ***************************************************************************/ + +#include "mp4file.h" + +#include "mp4tag.h" +#include +#include + +#include + +#define MP4V2_HAS_WRITE_BUG 1 + +namespace TagLib { +//////////////////////////////////////////////////////////////////////////////// +// public members +//////////////////////////////////////////////////////////////////////////////// + +MP4::File::File(const char *file, + bool readProperties, + Properties::ReadStyle propertiesStyle, + MP4FileHandle handle) : TagLib::File(file), + mp4tag(NULL), properties(NULL) +{ + + // debug ("MP4::File: create new file object."); + //debug ( file ); + /** + * Create the MP4 file. + */ + + if(handle == MP4_INVALID_FILE_HANDLE) + { + mp4file = MP4Read(file); + } + else + { + mp4file = handle; + } + + if( isOpen() ) + { + read(readProperties, propertiesStyle ); + } +} + +MP4::File::~File() +{ + MP4Close(mp4file); + delete mp4tag; + delete properties; +} + +TagLib::Tag *MP4::File::tag() const +{ + return mp4tag; +} + +TagLib::MP4::Tag *MP4::File::getMP4Tag() const +{ + return mp4tag; +} + +MP4::Properties *MP4::File::audioProperties() const +{ + return properties; +} + +bool MP4::File::save() +{ + MP4Close(mp4file); + + MP4FileHandle handle = MP4Modify(name()); + if(handle == MP4_INVALID_FILE_HANDLE) + { + mp4file = MP4Read(name()); + return false; + } + +#ifdef MP4V2_HAS_WRITE_BUG + /* according to gtkpod we have to delete all meta data before modifying it, + save the stuff we would not touch */ + + // need to fetch/rewrite this only if we aren't going to anyway + uint8_t compilation = 0; + bool has_compilation = mp4tag->compilation() == MP4::Tag::Undefined ? MP4GetMetadataCompilation(handle, &compilation) : false; + + char *tool = NULL; + MP4GetMetadataTool(handle, &tool); + + MP4MetadataDelete(handle); +#endif + + + +#define setmeta(val, tag) \ + if(mp4tag->val().isNull()) { \ + /*MP4DeleteMetadata##tag(handle);*/ \ + MP4SetMetadata##tag(handle, ""); \ + } else { \ + MP4SetMetadata##tag(handle, mp4tag->val().toCString(true)); \ + } + + setmeta(title, Name); + setmeta(artist, Artist); + setmeta(album, Album); + setmeta(comment, Comment); + setmeta(genre, Genre); + + char buf[100] = ""; + if(mp4tag->year()) + snprintf(buf, sizeof(buf), "%u", mp4tag->year()); + MP4SetMetadataYear(handle, buf); + u_int16_t t1, t2; + MP4GetMetadataTrack(handle, &t1, &t2); + MP4SetMetadataTrack(handle, mp4tag->track(), t2); + if(mp4tag->bpm() != 0) + MP4SetMetadataTempo(handle, mp4tag->bpm()); + if(mp4tag->compilation() != MP4::Tag::Undefined) { + MP4SetMetadataCompilation(handle, mp4tag->compilation()); + } + + MP4SetMetadataCoverArt(handle, mp4tag->cover().size() ? const_cast( reinterpret_cast( mp4tag->cover().data() ) ) : 0, mp4tag->cover().size()); + +#ifdef MP4V2_HAS_WRITE_BUG + // set the saved data again + + if(has_compilation) + MP4SetMetadataCompilation(handle, compilation); + if(tool) + { + MP4SetMetadataTool(handle, tool); + free(tool); + } +#endif + + MP4Close(handle); + + mp4file = MP4Read(name()); + if(mp4file == MP4_INVALID_FILE_HANDLE) + { + fprintf(stderr, "reopen failed\n"); + return false; + } + + return true; +} + +bool MP4::File::isOpen() +{ + return mp4file != MP4_INVALID_FILE_HANDLE; +} + +//////////////////////////////////////////////////////////////////////////////// +// private members +//////////////////////////////////////////////////////////////////////////////// + +void MP4::File::read(bool readProperties, Properties::ReadStyle propertiesStyle) +{ + properties = new MP4::Properties(propertiesStyle); + mp4tag = new MP4::Tag(); + + if (mp4file != MP4_INVALID_FILE_HANDLE) { + + if(readProperties) + { + // Parse bitrate etc. + properties->readMP4Properties( mp4file ); + } + + mp4tag->readTags( mp4file ); + } +} + +} diff --git a/amarok/src/metadata/mp4/mp4file.h b/amarok/src/metadata/mp4/mp4file.h new file mode 100644 index 00000000..6eb0c1a0 --- /dev/null +++ b/amarok/src/metadata/mp4/mp4file.h @@ -0,0 +1,86 @@ +/*************************************************************************** +copyright : (C) 2005 by Andy Leadbetter +email : andrew.leadbetter@gmail.com + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301 USA * + ***************************************************************************/ + +#ifndef TAGLIB_MP4FILE_H +#define TAGLIB_MP4FILE_H + +#include +#include "mp4properties.h" + +namespace TagLib { + + namespace MP4 { + + class Tag; + + class File : public TagLib::File + { + public: + /*! + * Contructs a MP4 file from \a file. If \a readProperties is true the + * file's audio properties will also be read using \a propertiesStyle. If + * false, \a propertiesStyle is ignored. + */ + File(const char *file, bool readProperties = true, + Properties::ReadStyle propertiesStyle = Properties::Average, + MP4FileHandle handle=MP4_INVALID_FILE_HANDLE); + + /*! + * Destroys this instance of the File. + */ + virtual ~File(); + + + virtual TagLib::Tag *tag() const; + + /*! + * Returns the MP4::Properties for this file. If no audio properties + * were read then this will return a null pointer. + */ + virtual MP4::Properties *audioProperties() const; + + /*! + * Save the file. + * This is the same as calling save(AllTags); + * + * \note As of now, saving MP4 tags is not supported. + */ + virtual bool save(); + + void read(bool readProperties, Properties::ReadStyle propertiesStyle); + + MP4::Tag *getMP4Tag() const; + + protected: + File(const File &); + File &operator=(const File &); + bool isOpen(); + + + MP4::Tag *mp4tag; + MP4::Properties *properties; + MP4FileHandle mp4file; + + }; + } +} + +#endif diff --git a/amarok/src/metadata/mp4/mp4properties.cpp b/amarok/src/metadata/mp4/mp4properties.cpp new file mode 100644 index 00000000..978dfc54 --- /dev/null +++ b/amarok/src/metadata/mp4/mp4properties.cpp @@ -0,0 +1,120 @@ +/*************************************************************************** +copyright : (C) 2005 by Andy Leadbetter +email : andrew.leadbetter@gmail.com + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301 USA * + ***************************************************************************/ + +#include "mp4properties.h" + + +#include + +#include +#ifdef HAVE_SYSTEMS_H +#include +#endif + +#include + +#ifndef UINT64_TO_DOUBLE +#define UINT64_TO_DOUBLE(a) ((double)((int64_t)(a))) +#endif + +using namespace TagLib; + + +//////////////////////////////////////////////////////////////////////////////// +// public members +//////////////////////////////////////////////////////////////////////////////// + +MP4::Properties::Properties(Properties::ReadStyle style) : AudioProperties(style) +{ + m_length = 0; + m_bitrate = 0; + m_sampleRate = 0; + m_channels = 0; +} + +MP4::Properties::~Properties() +{ +} + +int MP4::Properties::length() const +{ + return m_length; +} + +int MP4::Properties::bitrate() const +{ + return m_bitrate; +} + +int MP4::Properties::sampleRate() const +{ + return m_sampleRate; +} + +int MP4::Properties::channels() const +{ + return m_channels; +} + +void MP4::Properties::readMP4Properties( MP4FileHandle mp4File ) +{ + u_int32_t numTracks = MP4GetNumberOfTracks(mp4File); + + for (u_int32_t i = 0; i < numTracks; i++) + { + MP4TrackId trackId = MP4FindTrackId(mp4File, i); + + const char* trackType = + MP4GetTrackType(mp4File, trackId); + + if (!strcmp(trackType, MP4_AUDIO_TRACK_TYPE)) + { + // OK we found an audio track so + // decode it. + readAudioTrackProperties(mp4File, trackId ); + } + } +} + +void MP4::Properties::readAudioTrackProperties(MP4FileHandle mp4File, MP4TrackId trackId ) +{ + + u_int32_t timeScale = + MP4GetTrackTimeScale(mp4File, trackId); + + MP4Duration trackDuration = + MP4GetTrackDuration(mp4File, trackId); + + double msDuration = + UINT64_TO_DOUBLE(MP4ConvertFromTrackDuration(mp4File, trackId, + trackDuration, MP4_MSECS_TIME_SCALE)); + + u_int32_t avgBitRate = + MP4GetTrackBitRate(mp4File, trackId); + + m_bitrate = (avgBitRate + 500) / 1000; + m_sampleRate = timeScale; + m_length = (int)(msDuration / 1000.0); + m_channels = 2; + + + +} diff --git a/amarok/src/metadata/mp4/mp4properties.h b/amarok/src/metadata/mp4/mp4properties.h new file mode 100644 index 00000000..472be659 --- /dev/null +++ b/amarok/src/metadata/mp4/mp4properties.h @@ -0,0 +1,86 @@ +/*************************************************************************** +copyright : (C) 2005 by Andy Leadbetter +email : andrew.leadbetter@gmail.com + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301 USA * + ***************************************************************************/ + +#ifndef TAGLIB_MP4PROPERTIES_H +#define TAGLIB_MP4PROPERTIES_H + +#include +#include +#include +// mp4.h drags in mp4_config.h that defines these +// get rid of them so they don't conflict with our config.h +#undef VERSION +#undef PACKAGE + +namespace TagLib { + + namespace MP4 { + + class File; + + /*! + * This reads the data from a MP4 stream to support the + * AudioProperties API. + */ + + class Properties : public AudioProperties + { + public: + /*! + * Initialize this structure + */ + Properties(Properties::ReadStyle style); + + /*! + * Destroys this MP4 Properties instance. + */ + virtual ~Properties(); + + // Reimplementations. + + virtual int length() const; + virtual int bitrate() const; + virtual int sampleRate() const; + virtual int channels() const; + + void readMP4Properties(MP4FileHandle mp4File); + + + private: + void readAudioTrackProperties(MP4FileHandle mp4File, MP4TrackId trackId ); + friend class MP4::File; + + int m_length; + int m_bitrate; + int m_sampleRate; + int m_channels; + + Properties(const Properties &); + Properties &operator=(const Properties &); + + void read(); + }; + + } + +} + +#endif diff --git a/amarok/src/metadata/mp4/mp4tag.cpp b/amarok/src/metadata/mp4/mp4tag.cpp new file mode 100644 index 00000000..611aaa09 --- /dev/null +++ b/amarok/src/metadata/mp4/mp4tag.cpp @@ -0,0 +1,127 @@ + +/*************************************************************************** +copyright : (C) 2005 by Andy Leadbetter +email : andrew.leadbetter@gmail.com + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301 USA * + ***************************************************************************/ + +#include "mp4tag.h" + +#include +#include + +using namespace TagLib; + +MP4::Tag::Tag() : TagLib::Tag::Tag() { + m_title = String::null; + m_artist = String::null; + m_album = String::null; + m_comment = String::null; + m_genre = String::null; + m_composer = String::null; + m_year = 0; + m_track = 0; + m_disk = 0; + m_bpm = 0; + m_compilation = Undefined; +} + +MP4::Tag::~Tag() { +} + +bool MP4::Tag::isEmpty() const { + return m_title == String::null && + m_artist == String::null && + m_album == String::null && + m_comment == String::null && + m_genre == String::null && + m_composer == String::null && + m_year == 0 && + m_track == 0 && + m_disk == 0 && + m_bpm == 0 && + m_compilation == Undefined && + m_image.size() == 0; +} + +void MP4::Tag::duplicate(const Tag *source, Tag *target, bool overwrite) { + // Duplicate standard information + Tag::duplicate(source, target, overwrite); + + if (overwrite || target->compilation() == Undefined && source->compilation() != Undefined) + target->setCompilation(source->compilation()); + + if (overwrite || target->cover().size() == 0) + target->setCover(source->cover()); +} + +void MP4::Tag::readTags( MP4FileHandle mp4file ) +{ + // Now parse tag. + char *value; + uint8_t boolvalue; + uint16_t numvalue, numvalue2; + uint8_t *image; + uint32_t imageSize; + if (MP4GetMetadataName(mp4file, &value) && value != NULL) { + m_title = String(value, String::UTF8); + free(value); + } + if (MP4GetMetadataArtist(mp4file, &value) && value != NULL) { + m_artist = String(value, String::UTF8); + free(value); + } + + if (MP4GetMetadataComment(mp4file, &value) && value != NULL) { + m_comment = String(value, String::UTF8); + free(value); + } + + if (MP4GetMetadataYear(mp4file, &value) && value != NULL) { + m_year = strtol(value, NULL,0); + free(value); + } + if (MP4GetMetadataAlbum(mp4file, &value) && value != NULL) { + m_album = String(value, String::UTF8); + free(value); + } + if (MP4GetMetadataTrack(mp4file, &numvalue, &numvalue2)) { + m_track = numvalue; + } + if (MP4GetMetadataDisk(mp4file, &numvalue, &numvalue2)) { + m_disk = numvalue; + } + if (MP4GetMetadataTempo(mp4file, &numvalue)) { + m_bpm = numvalue; + } + if (MP4GetMetadataCompilation(mp4file, &boolvalue)) { + m_compilation = boolvalue; + } + if (MP4GetMetadataGenre(mp4file, &value) && value != NULL) { + m_genre = String(value, String::UTF8); + free(value); + } + if (MP4GetMetadataWriter(mp4file, &value) && value != NULL) { + m_composer = String(value, String::UTF8); + free(value); + } + if (MP4GetMetadataCoverArt(mp4file, &image, &imageSize) && image && imageSize) { + m_image.setData(reinterpret_cast( image ), imageSize); + free(image); + } +} diff --git a/amarok/src/metadata/mp4/mp4tag.h b/amarok/src/metadata/mp4/mp4tag.h new file mode 100644 index 00000000..8cb1827a --- /dev/null +++ b/amarok/src/metadata/mp4/mp4tag.h @@ -0,0 +1,227 @@ +/*************************************************************************** +copyright : (C) 2005 by Andy Leadbetter +email : andrew.leadbetter@gmail.com + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301 USA * + ***************************************************************************/ +#ifndef TAGLIB_MP4TAG_H +#define TAGLIB_MP4TAG_H + +#include +#include "mp4file.h" +#include + +namespace TagLib { + + namespace MP4 { + /*! + * This implements the generic TagLib::Tag API + */ + class Tag : public TagLib::Tag + { + public: + static const int Undefined = -1; + + Tag(); + + /*! + * read tags from the mp4 file. + */ + void readTags( MP4FileHandle mp4file); + + /*! + * Destroys this MP4Tag instance. + */ + virtual ~Tag(); + + /*! + * Returns the track name; if no track name is present in the tag + * String::null will be returned. + */ + virtual String title() const { return m_title; } + + /*! + * Returns the artist name; if no artist name is present in the tag + * String::null will be returned. + */ + virtual String artist() const { return m_artist; } + + /*! + * Returns the album name; if no album name is present in the tag + * String::null will be returned. + */ + virtual String album() const { return m_album; } + + /*! + * Returns the track comment; if no comment is present in the tag + * String::null will be returned. + */ + virtual String comment() const { return m_comment; } + + /*! + * Returns the genre name; if no genre is present in the tag String::null + * will be returned. + */ + virtual String genre() const { return m_genre; } + + /*! + * Returns the composer name; if no composer is present in the tag String::null + * will be returned. + */ + virtual String composer() const { return m_composer; } + + /*! + * Returns the year; if there is no year set, this will return 0. + */ + virtual uint year() const { return m_year; } + + /*! + * Returns the track number; if there is no track number set, this will + * return 0. + */ + virtual uint track() const { return m_track; } + + /*! + * Returns the disc number; if there is no disc number set, this will + * return 0. + */ + virtual uint disk() const { return m_disk; } + + /*! + * Returns the BPM (tempo); if there is no BPM, this will return 0. + */ + virtual uint bpm() const { return m_bpm; } + + /*! + * Returns the embedded cover image; if there is no cover set, this will + * return an empty ByteVector. + */ + virtual const ByteVector &cover() const { return m_image; } + + /*! + * Returns whether this is part of a compilation; if this flag is not set, + * this will return the Undefined constant. + */ + virtual int compilation() const { return m_compilation; } + + /*! + * Sets the title to \a s. If \a s is String::null then this value will be + * cleared. + */ + virtual void setTitle(const String &s) { m_title = s; } + + /*! + * Sets the artist to \a s. If \a s is String::null then this value will be + * cleared. + */ + virtual void setArtist(const String &s) { m_artist = s; } + + /*! + * Sets the album to \a s. If \a s is String::null then this value will be + * cleared. + */ + virtual void setAlbum(const String &s) { m_album = s; } + + /*! + * Sets the album to \a s. If \a s is String::null then this value will be + * cleared. + */ + virtual void setComment(const String &s) { m_comment = s; } + + /*! + * Sets the genre to \a s. If \a s is String::null then this value will be + * cleared. For tag formats that use a fixed set of genres, the appropriate + * value will be selected based on a string comparison. A list of available + * genres for those formats should be available in that type's + * implementation. + */ + virtual void setGenre(const String &s) { m_genre = s; } + + /*! + * Sets the year to \a i. If \a s is 0 then this value will be cleared. + */ + virtual void setYear(uint i) { m_year = i; } + + /*! + * Sets the track to \a i. If \a i is 0 then this value will be cleared. + */ + virtual void setTrack(uint i) { m_track = i; } + + /*! + * Sets the disc to \a i. If \a i is 0 then this value will be cleared. + */ + virtual void setDisk(uint i) { m_disk = i; } + + /*! + * Sets the BPM (tempo) to \a i. It \a i is 0 then this value will be cleared. + */ + virtual void setBpm(uint i) { m_bpm = i; } + + /*! + * Sets whether this is part of a compilation. + */ + virtual void setCompilation(bool compilation) { m_compilation = compilation ? 1 : 0; } + + /*! + * Sets the composer to \a s. If \a s is String::null then this value will + * be cleared. + */ + virtual void setComposer(const String &s) { m_composer = s; } + + /*! + * Sets the embedded cover image to \a i. If \a i is empty then this value + * will be cleared. + */ + virtual void setCover(const ByteVector &i) { m_image = i; } + + /*! + * Returns true if the tag does not contain any data. This should be + * reimplemented in subclasses that provide more than the basic tagging + * abilities in this class. + */ + virtual bool isEmpty() const; + + /*! + * Copies the generic data from one tag to another. + * + * \note This will not affect any of the lower level details of the tag. For + * instance if any of the tag type specific data (maybe a URL for a band) is + * set, this will not modify or copy that. This just copies using the API + * in this class. + * + * If \a overwrite is true then the values will be unconditionally copied. + * If false only empty values will be overwritten. + */ + static void duplicate(const Tag *source, Tag *target, bool overwrite = true); + + protected: + String m_title; + String m_artist; + String m_album; + String m_comment; + String m_genre; + String m_composer; + uint m_year; + uint m_track; + uint m_disk; + uint m_bpm; + int m_compilation; + ByteVector m_image; + }; + } +} +#endif diff --git a/amarok/src/metadata/mp4/taglib_mp4filetyperesolver.cpp b/amarok/src/metadata/mp4/taglib_mp4filetyperesolver.cpp new file mode 100644 index 00000000..af41fa13 --- /dev/null +++ b/amarok/src/metadata/mp4/taglib_mp4filetyperesolver.cpp @@ -0,0 +1,48 @@ +/*************************************************************************** + copyright : (C) 2005 by Martin Aumueller + email : aumuell@reserv.at + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301 USA * + ***************************************************************************/ + +// (c) 2005 Martin Aumueller +// See COPYING file for licensing information + +#include "taglib_mp4filetyperesolver.h" +#include "mp4file.h" + +TagLib::File *MP4FileTypeResolver::createFile(const char *fileName, + bool readProperties, + TagLib::AudioProperties::ReadStyle propertiesStyle) const +{ + const char *ext = strrchr(fileName, '.'); + if(ext && (!strcasecmp(ext, ".m4a") + || !strcasecmp(ext, ".m4b") || !strcasecmp(ext, ".m4p") + || !strcasecmp(ext, ".mp4") + || !strcasecmp(ext, ".m4v") || !strcasecmp(ext, ".mp4v"))) + { + MP4FileHandle h = MP4Read(fileName, 0); + if(MP4_INVALID_FILE_HANDLE == h) + { + return 0; + } + + return new TagLib::MP4::File(fileName, readProperties, propertiesStyle, h); + } + + return 0; +} diff --git a/amarok/src/metadata/mp4/taglib_mp4filetyperesolver.h b/amarok/src/metadata/mp4/taglib_mp4filetyperesolver.h new file mode 100644 index 00000000..4b6a54e8 --- /dev/null +++ b/amarok/src/metadata/mp4/taglib_mp4filetyperesolver.h @@ -0,0 +1,42 @@ +/*************************************************************************** + copyright : (C) 2005 by Martin Aumueller + email : aumuell@reserv.at + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301 USA * + ***************************************************************************/ + +// (c) 2005 Martin Aumueller +// See COPYING file for licensing information + +#ifndef TAGLIB_MP4FILETYPERESOLVER_H +#define TAGLIB_MP4FILETYPERESOLVER_H + +#include +#include + + +class MP4FileTypeResolver : public TagLib::FileRef::FileTypeResolver +{ + TagLib::File *createFile(const char *fileName, + bool readAudioProperties, + TagLib::AudioProperties::ReadStyle audioPropertiesStyle) const; + +public: + virtual ~MP4FileTypeResolver() {} +}; + +#endif diff --git a/amarok/src/metadata/rmff/Makefile.am b/amarok/src/metadata/rmff/Makefile.am new file mode 100644 index 00000000..3eb68788 --- /dev/null +++ b/amarok/src/metadata/rmff/Makefile.am @@ -0,0 +1,15 @@ +SUBDIRS = + +INCLUDES = $(all_includes) $(TAGLIB_CFLAGS) +METASOURCES = AUTO +libtagrealmedia_la_LDFLAGS = $(all_libraries) +noinst_LTLIBRARIES = libtagrealmedia.la + +libtagrealmedia_la_SOURCES = rmff.cpp \ + taglib_realmediafile.cpp \ + taglib_realmediafiletyperesolver.cpp + +noinst_HEADERS = rmff.h \ + taglib_realmediafile.h \ + taglib_realmediafiletyperesolver.h + diff --git a/amarok/src/metadata/rmff/rmff.cpp b/amarok/src/metadata/rmff/rmff.cpp new file mode 100644 index 00000000..da0edd55 --- /dev/null +++ b/amarok/src/metadata/rmff/rmff.cpp @@ -0,0 +1,998 @@ +/*************************************************************************** + copyright : (C) 2005 by Paul Cifarelli + email : paulc2@optonline.net + ***************************************************************************/ + +/*************************************************************************** + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin St, 5th fl, Boston, MA 02110-1301, * + * USA, or check http://www.fsf.org/about/contact.html * + * * + * Note that no RealNetworks code appears or is duplicated, copied, or * + + used as a template in this code. The code was written from scratch * + * using the reference documentation found at: * + * * + * https://common.helixcommunity.org/nonav/2003/HCS_SDK_r5/helixsdk.htm * + * * + ***************************************************************************/ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "rmff.h" + +#define UNPACK4(a, buf, i) memcpy((void *)&a, (void *) &buf[i], 4),i+=4,a=ntohl(a) +#define UNPACK2(a, buf, i) memcpy((void *)&a, (void *) &buf[i], 2),i+=2,a=ntohs(a) + +using namespace TagLib; +using namespace TagLib::RealMedia; + +RMFFile::RMFFile(const char *filename) : File(filename), m_id3tag(0) +{ + if (isOpen()) + m_id3tag = new ID3v1::Tag(this, length() - 128); +} + +RMFFile::~RMFFile() +{ + delete m_id3tag; +} + +bool RMFFile::save() +{ + ByteVector bv = m_id3tag->render(); //TODO finish this + return false; +} + + +String RealMediaFF::title () const +{ + return !m_err && m_id3v1tag ? m_id3v1tag->tag()->title() : ""; +} + +String RealMediaFF::artist () const +{ + return !m_err && m_id3v1tag ? m_id3v1tag->tag()->artist() : ""; +} + +String RealMediaFF::album () const +{ + return !m_err && m_id3v1tag ? m_id3v1tag->tag()->album() : ""; +} + +String RealMediaFF::comment() const +{ + return !m_err && m_id3v1tag ? m_id3v1tag->tag()->comment() : ""; +} + +String RealMediaFF::genre() const +{ + return !m_err && m_id3v1tag ? m_id3v1tag->tag()->genre() : ""; +} + +TagLib::uint RealMediaFF::year() const +{ + return !m_err && m_id3v1tag ? m_id3v1tag->tag()->year() : 0; +} + +TagLib::uint RealMediaFF::track() const +{ + return !m_err && m_id3v1tag ? m_id3v1tag->tag()->track() : 0; +} + +// properties +int RealMediaFF::length () const +{ + return m_readProperties && !m_err && m_props ? m_props->duration : 0; +} + +int RealMediaFF::bitrate () const +{ + return m_readProperties && !m_err && m_props ? m_props->avg_bit_rate : 0; +} + +int RealMediaFF::sampleRate () const +{ + return 0; +} + +int RealMediaFF::channels () const +{ + return 0; +} + + +RealMediaFF::RealMediaFF(const char *file, bool readProperties, AudioProperties::ReadStyle /*propertiesStyle*/) +: m_filename(0) +, m_head(0) +, m_tail(0) +, m_err(0) +, m_hdr(0) +, m_props(0) +, media_hdrs(0) +, m_contenthdr(0) +, m_md(0) +, m_title(0) +, m_author(0) +, m_copyright(0) +, m_comment(0) +, m_id3v1tag(0) +, m_flipYearInMetadataSection(0) +, m_readProperties(readProperties) +{ + m_filename = strdup(file); + + m_fd = open(m_filename, O_RDONLY); + if (m_fd < 0) + { + m_err = -1; + return; + } + + // ok, for RM files, the properties are embedded, so we ignore propertiesStyle + if (m_readProperties) + { + init(); + + // and now for the really complicated stuff... + if (initMetadataSection()) + std::cerr << "ERROR reading Metadata\n"; + } + + // now get the ID3v1 tag at the end of this file + m_id3v1tag = new RMFFile(m_filename); +} + + +RealMediaFF::RealMediaFF(RealMediaFF &src) +: m_filename(0) +, m_head(0) +, m_tail(0) +, m_err(0) +, m_hdr(0) +, m_props(0) +, media_hdrs(0) +, m_contenthdr(0) +, m_md(0) +, m_title(0) +, m_author(0) +, m_copyright(0) +, m_comment(0) +, m_id3v1tag(0) +, m_flipYearInMetadataSection(0) +, m_readProperties(src.m_readProperties) +{ + m_filename=strdup(src.m_filename); + + m_fd = open(m_filename, O_RDONLY); + if (m_fd < 0) + { + m_err = -1; + return; + } + + // ok, for RM files, the properties are embedded, so we ignore propertiesStyle + if (m_readProperties) + { + init(); + + // and now for the really complicated stuff... + if (initMetadataSection()) + std::cerr << "ERROR reading Metadata\n"; + } + + // now get the ID3v1 tag at the end of this file + m_id3v1tag = new RMFFile(m_filename); +} + +RealMediaFF::~RealMediaFF() +{ + ::free(m_filename); + + Collectable *hdr = m_head, *next; + while (hdr) + { + next = hdr->fwd; + delete hdr; + hdr = next; + } + + delete m_id3v1tag; + delete m_md; + + close(m_fd); +} + +bool RealMediaFF::isEmpty() const +{ + return m_id3v1tag->tag()->isEmpty(); +} + + +void RealMediaFF::saveHeader(Collectable *hdr) +{ + hdr->fwd = 0; + if (!m_head) + m_head = m_tail = hdr; + else + { + m_tail->fwd = hdr; + m_tail = hdr; + } +} + + +int RealMediaFF::init() +{ + int nbytes; + unsigned char buf[65536]; + UINT32 object_id; + UINT32 sz; + UINT32 consumed = 0; + + off_t s; + if ( (s = lseek(m_fd, 0, SEEK_SET)) ) + { + m_err = -1; + return m_err; + } + + m_hdr = new File_Header_v0_v1; + nbytes = getChunk(buf, 65536, m_hdr->s.object_id, m_hdr->s.size, consumed); + if (nbytes < 0 || m_hdr->s.size != consumed || memcmp((void *)&m_hdr->s.object_id, ".RMF", 4)) + { + //std::cerr << "SERIOUS ERROR - not likely a RealMedia file\n"; + m_err = -1; + return m_err; + } + if (!getRealFileHeader(m_hdr, buf, m_hdr->s.object_id, m_hdr->s.size)) + { + saveHeader(m_hdr); + consumed = 0; + nbytes = getChunk(buf, 65536, object_id, sz, consumed); + if (nbytes < 0 || sz != consumed) + { + m_err = -1; + return m_err; + } + + while (!m_err && memcmp((void *)&object_id, "DATA", 4)) + { + char oid[5]; + memcpy((void *)oid, (void *)&object_id, 4); + oid[4] = 0; + if (!memcmp((void *)&object_id, "PROP", 4)) + { + m_props = new RMProperties; + getRealPropertyHeader(m_props, buf, object_id, sz); + saveHeader(m_props); + } + + if (!memcmp((void *)&object_id, "MDPR", 4)) + { + media_hdrs = new MediaProperties; + getMediaPropHeader(media_hdrs, buf, object_id, sz); + saveHeader(media_hdrs); + } + + if (!memcmp((void *)&object_id, "CONT", 4)) + { + m_contenthdr = new ContentDescription; + getContentDescription(m_contenthdr, buf, object_id, sz); + saveHeader(m_contenthdr); + } + + consumed = 0; + do + { + nbytes = getChunk(buf, 65536, object_id, sz, consumed); + } while ( !m_err && memcmp((void *)&object_id, "DATA", 4) && (consumed < sz) ); + } + } + + return 0; +} + +int RealMediaFF::getHdr(unsigned char *buf, size_t sz, UINT32 &fourcc, UINT32 &csz) +{ + int nbytes = 0, i = 0; + + if (sz < (size_t)RMFF_HDR_SIZE) + return 0; + + if ( (nbytes = read(m_fd, (void *) buf, RMFF_HDR_SIZE)) != RMFF_HDR_SIZE ) + { + m_err = -1; + + return (nbytes); + } + + memcpy((void *)&fourcc, buf, 4); i+=4; + UNPACK4(csz,buf,i); + + return nbytes; +} + +int RealMediaFF::getChunk(unsigned char *buf, size_t sz, UINT32 &fourcc, UINT32 &csz, UINT32 &alreadyconsumed) +{ + int nbytes = 0, i = 0, readamount; + csz = 0; + + if (!alreadyconsumed) + { + if ( (nbytes = getHdr(buf, sz, fourcc, csz)) != RMFF_HDR_SIZE ) + { + m_err = -1; + alreadyconsumed += nbytes > 0 ? nbytes : 0; + return nbytes; + } + alreadyconsumed += RMFF_HDR_SIZE; + readamount = csz - RMFF_HDR_SIZE; + i = RMFF_HDR_SIZE; + } + else + readamount = csz - alreadyconsumed; + + if ( (nbytes = read(m_fd, (void *) &buf[i], readamount > (int)sz - i ? (int)sz - i : readamount )) != readamount ) + { + if (nbytes < 0) + { + m_err = -1; + } + else + alreadyconsumed += nbytes; + + return (nbytes<0 ? i : i + nbytes); + } + + alreadyconsumed += nbytes; + return (csz); +} + +int RealMediaFF::getRealFileHeader(File_Header_v0_v1 *hdr, const unsigned char *buf, UINT32 object_id, int sz) +{ + int i = 0; + + // RealMedia header + hdr->s.object_id = object_id; + hdr->s.size = sz; + + i = RMFF_HDR_SIZE; + UNPACK2(hdr->object_version, buf, i); + + if ( !strncmp((const char *) &hdr->s.object_id, ".RMF", 4) && + (hdr->object_version == 0 || hdr->object_version == 1) ) + { + UNPACK4(hdr->file_version, buf, i); + UNPACK4(hdr->num_headers, buf, i); + } + return 0; +} + +int RealMediaFF::getRealPropertyHeader(RMProperties *props, const unsigned char *buf, UINT32 object_id, int sz) +{ + int i = 0; + + // Properties + props->s.object_id = object_id; + props->s.size = sz; + + i = RMFF_HDR_SIZE; + UNPACK2(props->object_version, buf, i); + + if ( !strncmp((const char *)&props->s.object_id,"PROP",4) && (props->object_version == 0) ) + { + UNPACK4(props->max_bit_rate, buf, i); + UNPACK4(props->avg_bit_rate, buf, i); + UNPACK4(props->max_packet_size, buf, i); + UNPACK4(props->avg_packet_size, buf, i); + UNPACK4(props->num_packets, buf, i); + UNPACK4(props->duration, buf, i); + UNPACK4(props->preroll, buf, i); + UNPACK4(props->index_offset, buf, i); + UNPACK4(props->data_offset, buf, i); + UNPACK2(props->num_streams, buf, i); + UNPACK2(props->flags, buf, i); + } + return 0; +} + + +int RealMediaFF::getMediaPropHeader(MediaProperties *media_hdr, const unsigned char *buf, UINT32 object_id, int sz) +{ + int i = 0; + + // Properties + media_hdr->s.object_id = object_id; + media_hdr->s.size = sz; + + i = RMFF_HDR_SIZE; + UNPACK2(media_hdr->object_version, buf, i); + + if ( !strncmp((const char *)&media_hdr->s.object_id, "MDPR", 4) && media_hdr->object_version == 0) + { + UNPACK2(media_hdr->stream_number, buf, i); + UNPACK4(media_hdr->max_bit_rate, buf, i); + UNPACK4(media_hdr->avg_bit_rate, buf, i); + UNPACK4(media_hdr->max_packet_size, buf, i); + UNPACK4(media_hdr->avg_packet_size, buf, i); + UNPACK4(media_hdr->start_time, buf, i); + UNPACK4(media_hdr->preroll, buf, i); + UNPACK4(media_hdr->duration, buf, i); + media_hdr->stream_name_size = buf[i]; i++; + memcpy(media_hdr->stream_name, &buf[i], media_hdr->stream_name_size); + media_hdr->stream_name[media_hdr->stream_name_size] = 0; + i += media_hdr->stream_name_size; + media_hdr->mime_type_size = buf[i]; i++; + memcpy(media_hdr->mime_type, &buf[i], media_hdr->mime_type_size); + i += media_hdr->mime_type_size; + UNPACK4(media_hdr->type_specific_len, buf, i); + if (media_hdr->type_specific_len) + { + media_hdr->type_specific_data = new UINT8[media_hdr->type_specific_len]; + memcpy(media_hdr->type_specific_data, &buf[i], media_hdr->type_specific_len); + + if (!strncmp((const char *)media_hdr->mime_type, "logical-fileinfo", 16)) + { + media_hdr->lstr = new LogicalStream; + UNPACK4(media_hdr->lstr->size, buf, i); + UNPACK2(media_hdr->lstr->object_version, buf, i); + if (media_hdr->lstr->object_version == 0) + { + UNPACK2(media_hdr->lstr->num_physical_streams, buf, i); + if (media_hdr->lstr->num_physical_streams > 0) + { + media_hdr->lstr->physical_stream_numbers = new UINT16[ media_hdr->lstr->num_physical_streams ]; + media_hdr->lstr->data_offsets = new UINT32[ media_hdr->lstr->num_physical_streams ]; + for (int j=0; jlstr->num_physical_streams; j++) + { + UNPACK2(media_hdr->lstr->physical_stream_numbers[j], buf, i); + } + for (int j=0; jlstr->num_physical_streams; j++) + { + UNPACK4(media_hdr->lstr->data_offsets[j], buf, i); + } + } + + UNPACK2(media_hdr->lstr->num_rules, buf, i); + if (media_hdr->lstr->num_rules > 0) + { + media_hdr->lstr->rule_to_physical_stream_number_map = new UINT16[ media_hdr->lstr->num_rules ]; + for (int j=0; jlstr->num_rules; j++) + { + UNPACK2(media_hdr->lstr->rule_to_physical_stream_number_map[j], buf, i); + } + } + UNPACK2(media_hdr->lstr->num_properties, buf, i); + if (media_hdr->lstr->num_properties > 0) + { + media_hdr->lstr->properties = new NameValueProperty[ media_hdr->lstr->num_properties ]; + for (int j=0; jlstr->num_properties; j++) + { + UNPACK4(media_hdr->lstr->properties[j].size, buf, i); + UNPACK2(media_hdr->lstr->properties[j].object_version, buf, i); + if (media_hdr->lstr->properties[j].object_version == 0) + { + media_hdr->lstr->properties[j].name_length = buf[i]; i++; + if (media_hdr->lstr->properties[j].name_length) + { + media_hdr->lstr->properties[j].name = new UINT8[ media_hdr->lstr->properties[j].name_length + 1]; + memcpy((void *)media_hdr->lstr->properties[j].name, (void *)&buf[i], + media_hdr->lstr->properties[j].name_length); + media_hdr->lstr->properties[j].name[ media_hdr->lstr->properties[j].name_length ] = 0; + i+=media_hdr->lstr->properties[j].name_length; + } + + UNPACK4(media_hdr->lstr->properties[j].type, buf, i); + UNPACK2(media_hdr->lstr->properties[j].value_length, buf, i); + if (media_hdr->lstr->properties[j].value_length) + { + media_hdr->lstr->properties[j].value_data = new UINT8[ media_hdr->lstr->properties[j].value_length + 1]; + memcpy((void *)media_hdr->lstr->properties[j].value_data, (void *)&buf[i], + media_hdr->lstr->properties[j].value_length); + media_hdr->lstr->properties[j].value_data[ media_hdr->lstr->properties[j].value_length ] = 0; + i+=media_hdr->lstr->properties[j].value_length; + } + } + } + } + } + else + media_hdr->lstr = 0; + } + } + else + media_hdr->type_specific_data = 0; + } + else + { + m_err = -1; + return m_err; + } + + return 0; +} + + +int RealMediaFF::getContentDescription(ContentDescription *cont, const unsigned char *buf, UINT32 object_id, int sz) +{ + int i = 0; + + // Properties + cont->s.object_id = object_id; + cont->s.size = sz; + + i = RMFF_HDR_SIZE; + UNPACK2(cont->object_version, buf, i); + + if ( !strncmp((const char *)&cont->s.object_id, "CONT", 4) && cont->object_version == 0) + { + UNPACK2(cont->title_len, buf, i); + cont->title = new UINT8[cont->title_len + 1]; + memcpy((void *)cont->title, (void *)&buf[i], cont->title_len); i+=cont->title_len; + m_title = (char *)cont->title; + m_title[cont->title_len] = 0; + + UNPACK2(cont->author_len, buf, i); + cont->author = new UINT8[cont->author_len + 1]; + memcpy((void *)cont->author, (void *)&buf[i], cont->author_len); i+=cont->author_len; + m_author = (char *)cont->author; + m_author[cont->author_len] = 0; + + UNPACK2(cont->copyright_len, buf, i); + cont->copyright = new UINT8[cont->copyright_len + 1]; + memcpy((void *)cont->copyright, (void *)&buf[i], cont->copyright_len); i+=cont->copyright_len; + m_copyright = (char *)cont->copyright; + m_copyright[cont->copyright_len] = 0; + + UNPACK2(cont->comment_len, buf, i); + cont->comment = new UINT8[cont->comment_len + 1]; + memcpy((void *)cont->comment, (void *)&buf[i], cont->comment_len); i+=cont->comment_len; + m_comment = (char *)cont->comment; + m_comment[cont->comment_len] = 0; + } + else + { + m_err = -1; + return m_err; + } + + return 0; +} + + +int RealMediaFF::seekChunk(UINT32 object_id) +{ + if (!m_err) + { + off_t s, tot; + UINT32 oid = 0, sz = 0; + unsigned char buf[255]; + int nbytes = 0; + + if ( (s = lseek(m_fd, 0, SEEK_SET)) != 0) + return -1; + + tot = 0; + while( (nbytes = getHdr(buf, 255, oid, sz)) == RMFF_HDR_SIZE && memcmp((void *)&oid, (void *)&object_id, 4) ) + { + tot += sz; + if (sz > (unsigned) RMFF_HDR_SIZE) + { + if ( (s = lseek(m_fd, sz - RMFF_HDR_SIZE, SEEK_CUR)) != tot ) + return -1; + } + else + return -1; // bail in this case, since the chuck sz includes the header size + } + if ( (s = lseek(m_fd, -RMFF_HDR_SIZE, SEEK_CUR)) != tot ) + return -1; + + return s; + } + return -1; +} + +int RealMediaFF::getMDProperties(MDProperties *props, const unsigned char *buf) +{ + int i = 0; + + int start = i; + + UNPACK4(props->size, buf, i); + UNPACK4(props->type, buf, i); + UNPACK4(props->flags, buf, i); + UNPACK4(props->value_offset, buf, i); + UNPACK4(props->subproperties_offset, buf, i); + UNPACK4(props->num_subproperties, buf, i); + UNPACK4(props->name_length, buf, i); + props->name = new UINT8[ props->name_length + 1 ]; + memcpy((void *)props->name, (void *)&buf[i], props->name_length); + props->name[ props->name_length ] = 0; + i+=props->name_length; + + i = start + props->value_offset; + UNPACK4(props->value_length, buf, i); + props->value = new UINT8[ props->value_length ]; + memcpy( (void *) props->value, (void *)&buf[i], props->value_length ); + + if ( (props->type == MPT_ULONG) || (props->type == MPT_FLAG && props->value_length == 4) ) + { + // wOOt! the Year is a ULONG, and its stored little endian?! my guess is this is a bug in + // RealPlayer 10 for Windows (where I created my test files) + // This hack is intended to ensure that we at least interpret the Year properly. + if (!strcmp((char *)props->name, "Year")) + { + if ( *(unsigned long *)props->value > 65536 ) + { + *(unsigned long *)(props->value) = ntohl(*(unsigned long *)(props->value)); + m_flipYearInMetadataSection = true; + } + else + m_flipYearInMetadataSection = false; + } + else + *(unsigned long *)(props->value) = ntohl(*(unsigned long *)(props->value)); + } + + i += props->value_length; + + i = start + props->subproperties_offset; + props->subproperties_list = new PropListEntry[ props->num_subproperties ]; + for (int j=0; j<(int)props->num_subproperties; j++) + { + UNPACK4(props->subproperties_list[j].offset, buf, i); + UNPACK4(props->subproperties_list[j].num_props_for_name, buf, i); + } + + props->subproperties = new MDProperties[ props->num_subproperties ]; + for (int j=0; j<(int)props->num_subproperties; j++) + { + i = start + props->subproperties_list[j].offset; + getMDProperties(&props->subproperties[j], &buf[i]); + } + + return 0; +} + +int RealMediaFF::initMetadataSection() +{ + UINT32 object_id; + off_t s; + int nbytes; + unsigned char buf[65536]; + UINT32 consumed; + + memcpy((void *)&object_id, "RMMD", 4); + if ( (s = seekChunk(object_id)) < 0 ) + { + m_err = -1; + return m_err; + } + + m_md = new MetadataSection; + consumed = 0; + nbytes = getChunk(buf, 65536, m_md->s.object_id, m_md->s.size, consumed); + if (nbytes < 0 || m_md->s.size != consumed || memcmp((void *)&m_md->s.object_id, "RMMD", 4)) + { + //std::cerr << "SERIOUS ERROR - not able to find the chunk I just seek'd to!\n"; + m_err = -1; + return m_err; + } + // Properties + int i = RMFF_HDR_SIZE; + memcpy((void *)&m_md->object_id, (void *)&buf[i], 4); i+=4; + UNPACK4(m_md->object_version, buf, i); + if ( !strncmp((const char *)&m_md->s.object_id, "RMMD", 4) ) + { + if (!getMDProperties(&m_md->properties, &buf[i])) + saveHeader(m_md); + } + else + { + m_err = -1; + return m_err; + } + + return 0; +} + +#ifdef TESTING + +void RealMediaFF::printRealFileHeader(std::ostream &os) +{ + char object_id[5]; + + if (m_hdr) + { + strncpy(object_id, (const char *)&m_hdr->s.object_id, 4); + object_id[4]=0; + + os << "HDR object_id: " << object_id << std::endl; + os << "HDR size: " << m_hdr->s.size << std::endl; + os << "HDR object version: " << m_hdr->object_version << std::endl; + os << "HDR file version: " << m_hdr->file_version << std::endl; + os << "HDR num headers: " << m_hdr->num_headers << std::endl; + } +} + + +void RealMediaFF::printRealPropHeader(std::ostream &os) +{ + char object_id[5]; + + if (m_props) + { + strncpy(object_id, (const char *)&m_props->s.object_id, 4); + object_id[4]=0; + + os << "PROPS object_id: " << object_id << std::endl; + os << "PROPS size: " << m_props->s.size << std::endl; + os << "PROPS object_version: " << m_props->object_version << std::endl; + + os << "PROPS max_bit_rate: " << m_props->max_bit_rate << std::endl; + os << "PROPS avg_bit_rate: " << m_props->avg_bit_rate << std::endl; + os << "PROPS max_packet_size: " << m_props->max_packet_size << std::endl; + os << "PROPS avg_packet_size: " << m_props->avg_packet_size << std::endl; + os << "PROPS num_packets: " << m_props->num_packets << std::endl; + os << "PROPS duration: " << m_props->duration << std::endl; + os << "PROPS preroll: " << m_props->preroll << std::endl; + os << "PROPS index_offset: " << m_props->index_offset << std::endl; + os << "PROPS data_offset: " << m_props->data_offset << std::endl; + os << "PROPS num_streams: " << m_props->num_streams << std::endl; + os << "PROPS flags: " << m_props->flags << std::endl; + } +} + + +void RealMediaFF::printMediaPropHeaders(std::ostream &os) +{ + int i = 0; + char object_id[5]; + MediaProperties *media_hdr = (MediaProperties *)m_head; + + while (media_hdr) + { + strncpy(object_id, (const char *)&media_hdr->s.object_id, 4); + object_id[4]=0; + + if (!strncmp(object_id, "MDPR", 4)) + { + os << "MEDIA HDR" << i << " object_id: " << object_id << std::endl; + os << "MEDIA HDR" << i << " size: " << media_hdr->s.size << std::endl; + os << "MEDIA HDR" << i << " max_bit_rate: " << media_hdr->max_bit_rate << std::endl; + os << "MEDIA HDR" << i << " avg_bit_rate: " << media_hdr->avg_bit_rate << std::endl; + os << "MEDIA HDR" << i << " max_packet_size: " << media_hdr->max_packet_size << std::endl; + os << "MEDIA HDR" << i << " avg_packet_size: " << media_hdr->avg_packet_size << std::endl; + os << "MEDIA HDR" << i << " start_time: " << media_hdr->start_time << std::endl; + os << "MEDIA HDR" << i << " preroll: " << media_hdr->preroll << std::endl; + os << "MEDIA HDR" << i << " duration: " << media_hdr->duration << std::endl; + os << "MEDIA HDR" << i << " stream_name: " << media_hdr->stream_name << std::endl; + os << "MEDIA HDR" << i << " mime type: " << media_hdr->mime_type << std::endl; + + + if (media_hdr->lstr) + { + os << "MEDIA HDR" << i << " LOGSTR info size: " << media_hdr->lstr->size << std::endl; + os << "MEDIA HDR" << i << " LOGSTR info num_physical_streams: " << media_hdr->lstr->num_physical_streams << std::endl; + os << "MEDIA HDR" << i << " LOGSTR info num_rules: " << media_hdr->lstr->num_rules << std::endl; + os << "MEDIA HDR" << i << " LOGSTR info num_properties: " << media_hdr->lstr->num_properties << std::endl; + for (int j=0; media_hdr->lstr->properties && jlstr->num_properties; j++) + { + if (media_hdr->lstr->properties[j].name) + os << "MEDIA HDR" << i << " LOGSTR info prop name: " << media_hdr->lstr->properties[j].name << std::endl; + os << "MEDIA HDR" << i << " LOGSTR info prop type: " << media_hdr->lstr->properties[j].type << std::endl; + os << "MEDIA HDR" << i << " LOGSTR info prop value_length: " << media_hdr->lstr->properties[j].value_length << std::endl; + if (media_hdr->lstr->properties[j].value_data) + { + if (media_hdr->lstr->properties[j].type == 0) + os << "MEDIA HDR" << i << " LOGSTR info prop value: " << + *(unsigned long *)media_hdr->lstr->properties[j].value_data << std::endl; + else if (media_hdr->lstr->properties[j].type == 2) + os << "MEDIA HDR" << i << " LOGSTR info prop value: " << media_hdr->lstr->properties[j].value_data << std::endl; + else + os << "MEDIA HDR" << i << " LOGSTR info prop value: \n"; + } + } + } + + i++; + } + media_hdr = (MediaProperties *)media_hdr->fwd; + } +} + + +void RealMediaFF::printContentDescription(std::ostream &os) +{ + char object_id[5]; + + if (m_contenthdr) + { + strncpy(object_id, (const char *)&m_contenthdr->s.object_id, 4); + object_id[4]=0; + + os << "CONT object_id: " << object_id << std::endl; + os << "CONT title(" << m_contenthdr->title_len << "):\t\t<" << m_contenthdr->title << ">" << std::endl; + os << "CONT author(" << m_contenthdr->author_len << "):\t\t<" << m_contenthdr->author << ">" << std::endl; + os << "CONT copyright(" << m_contenthdr->copyright_len << "):\t\t<" << m_contenthdr->copyright << ">" << std::endl; + os << "CONT comment(" << m_contenthdr->comment_len << "):\t\t<" << m_contenthdr->comment << ">" << std::endl; + } +} + +void RealMediaFF::printID3v1Tag(std::ostream &os) +{ + if (m_id3v1tag) + { + os << "ID3 tag : " << ID3v1::Tag::fileIdentifier() << std::endl; + os << "ID3 title : " << m_id3v1tag->tag()->title() << std::endl; + os << "ID3 artist : " << m_id3v1tag->tag()->artist() << std::endl; + os << "ID3 album : " << m_id3v1tag->tag()->album() << std::endl; + os << "ID3 year : " << m_id3v1tag->tag()->year() << std::endl; + os << "ID3 comment : " << m_id3v1tag->tag()->comment() << std::endl; + os << "ID3 track : " << m_id3v1tag->tag()->track() << std::endl; + os << "ID3 genre : " << m_id3v1tag->tag()->genre() << std::endl; + } +} + + +void RealMediaFF::printMDProperties(std::ostream &os, char *nam, MDProperties *props) +{ + char name[8192]; + + strcpy(name, nam); + os << "MDP subproperties for: " << name << std::endl; + + os << "MD properties.size: " << props->size << std::endl; + os << "MD properties.type: " << props->type << std::endl; + os << "MD properties.flags: " << props->flags << std::endl; + os << "MD properties.value_offset: " << props->value_offset << std::endl; + os << "MD properties.subproperties_offset: " << props->subproperties_offset << std::endl; + os << "MD properties.num_subproperties: " << props->num_subproperties << std::endl; + os << "MD properties.name_length: " << props->name_length << std::endl; + os << "MD properties.name: " << (char *)props->name << std::endl; + + os << "MD properties.value_length: " << props->value_length << std::endl; + + switch (props->type) + { + case MPT_TEXT: + case MPT_TEXTLIST: + case MPT_URL: + case MPT_DATE: + case MPT_FILENAME: + os << "MD properties.value: " << (char *)props->value << std::endl; + break; + case MPT_FLAG: + if (props->value_length == 4) + os << "MD properties.value: " << *(unsigned long *)props->value << std::endl; + else + os << "MD properties.value: " << *props->value << std::endl; + break; + case MPT_ULONG: + os << "MD properties.value: " << *(unsigned long *)props->value << std::endl; + break; + case MPT_BINARY: + os << "MD properties.value: " << std::endl; + break; + case MPT_GROUPING: + os << "MD properties.value: " << std::endl; + break; + case MPT_REFERENCE: + os << "MD properties.value: " << std::endl; + break; + } + + if (props->num_subproperties) + { + strcat(name, (char *)props->name); + strcat(name, "/"); + } + for (int j=0; jnum_subproperties; j++) + { + os << "MD properties.sub_properties_list[" << j << "].offset: " << + props->subproperties_list[j].offset << std::endl; + os << "MD properties.sub_properties_list[" << j << "].num_props_for_name: " << + props->subproperties_list[j].num_props_for_name << std::endl; + + os << std::endl; + + printMDProperties(os, name, &props->subproperties[j]); + } +} + + +void RealMediaFF::printMetadataSection(std::ostream &os) +{ + char name[8192]; + char oid[5]; + + memcpy((void *)oid, (void *)&m_md->s.object_id, 4); + oid[4] = 0; + + os << "MetadataSection: "; + os << "MS object_id: " << oid << std::endl; + os << "MS SIZE: " << m_md->s.size << std::endl; + os << "MD object_id: " << (char *)&m_md->object_id << std::endl; + os << "MD object_version: " << m_md->object_version << std::endl; + os << std::endl; + + strcpy(name, ""); + printMDProperties(os, name, &m_md->properties); +} + + +std::ostream &RealMediaFF::operator<<(std::ostream &os) +{ + if (m_readProperties) + { + printRealFileHeader(os); + printRealPropHeader(os); + printMediaPropHeaders(os); + printContentDescription(os); + printMetadataSection(os); + } + printID3v1Tag(os); + + return os; +} + +std::ostream &operator<<(std::ostream &os, RealMediaFF &rmff) +{ + rmff.operator<<(os); + + return os; +} + + +int main(int argc, char *argv[]) +{ + char *m_filen; + + if (argc > 1) + m_filen = argv[1]; + else + m_filen = "./Drown.ra"; + + RealMediaFF rmff(m_filen); + + if (!rmff.err()) + std::cout << rmff; + + /* + UINT32 oid = 0; + memcpy( (void *)&oid, (void *) ".RMF", 4); + off_t pos = rmff.seekChunk(oid); + std::cout << "POS=" << pos << std::endl; + + memcpy( (void *)&oid, (void *) "MDPR", 4); + pos = rmff.seekChunk(oid); + std::cout << "POS=" << pos << std::endl; + + memcpy( (void *)&oid, (void *) "RMMD", 4); + pos = rmff.seekChunk(oid); + std::cout << "POS=" << pos << std::endl; + */ +} +#endif diff --git a/amarok/src/metadata/rmff/rmff.h b/amarok/src/metadata/rmff/rmff.h new file mode 100644 index 00000000..aab22abe --- /dev/null +++ b/amarok/src/metadata/rmff/rmff.h @@ -0,0 +1,376 @@ +/*************************************************************************** + copyright : (C) 2005 by Paul Cifarelli + email : paulc2@optonline.net + ***************************************************************************/ + +/*************************************************************************** + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin St, 5th fl, Boston, MA 02110-1301, * + * USA, or check http://www.fsf.org/about/contact.html * + * * + * Note that no RealNetworks code appears or is duplicated, copied, or * + + used as a template in this code. The code was written from scratch * + * using the reference documentation found at: * + * * + * https://common.helixcommunity.org/nonav/2003/HCS_SDK_r5/helixsdk.htm * + * * + ***************************************************************************/ +#ifndef _RMFF_H_INCLUDED_ +#define _RMFF_H_INCLUDED_ + +#include + +#include + +namespace TagLib +{ + namespace RealMedia + { +#if SIZEOF_LONG == 4 + typedef unsigned long UINT32; +#elif SIZEOF_INT == 4 + typedef unsigned int UINT32; +#else +#error At least 1 builtin type needs to be 4 bytes!! +#endif + typedef unsigned short UINT16; + typedef unsigned char UINT8; + + static const int RMFF_HDR_SIZE = 8; // packed hdr size + + // some assumptions on these 2 enum defs, based solely on the order they are listed on the website + enum PROPERTY_TYPES + { + MPT_TEXT = 1, // The value is string data. + MPT_TEXTLIST, // The value is a separated list of strings, + // separator specified as sub-property/type descriptor. + MPT_FLAG, // The value is a boolean flag-either 1 byte or 4 bytes, check size value. + MPT_ULONG, // The value is a four-byte integer. + MPT_BINARY, // The value is a byte stream. + MPT_URL, // The value is string data. + MPT_DATE, // The value is a string representation of the date in the form: + // YYYYmmDDHHMMSS (m = month, M = minutes). + MPT_FILENAME, // The value is string data. + MPT_GROUPING, // This property has subproperties, but its own value is empty. + MPT_REFERENCE // The value is a large buffer of data, use sub-properties/type + // descriptors to identify mime-type. + }; + + enum PROPERTY_FLAGS + { + MPT_READONLY = 1, // Read only, cannot be modified. + MPT_PRIVATE = 2, // Private, do not expose to users. + MPT_TYPE_DESCRIPTOR = 4 // Type descriptor used to further define type of value. + }; + + struct Collectable + { + Collectable() : fwd(0) {} + virtual ~Collectable() {} + Collectable *fwd; + }; + + struct File_Header_Start + { + File_Header_Start() : object_id(0), size(0) {} + UINT32 object_id; + UINT32 size; + }; + + struct File_Header_v0_v1 : public Collectable + { + File_Header_Start s; + UINT16 object_version; + + UINT32 file_version; + UINT32 num_headers; + }; + + struct RMProperties : public Collectable + { + File_Header_Start s; + UINT16 object_version; + + UINT32 max_bit_rate; + UINT32 avg_bit_rate; + UINT32 max_packet_size; + UINT32 avg_packet_size; + UINT32 num_packets; + UINT32 duration; + UINT32 preroll; + UINT32 index_offset; + UINT32 data_offset; + UINT16 num_streams; + UINT16 flags; + }; + + + struct NameValueProperty + { + NameValueProperty() + : size(0), object_version(0), name_length(0), name(0), type(0) + , value_length(0), value_data(0) {} + virtual ~NameValueProperty() { delete [] name; delete [] value_data; } + + UINT32 size; + UINT16 object_version; + + UINT8 name_length; + UINT8 *name; + UINT32 type; + UINT16 value_length; + UINT8 *value_data; + }; + + + struct LogicalStream + { + LogicalStream() + : size(0), object_version(0), num_physical_streams(0) + , physical_stream_numbers(0), data_offsets(0), num_rules(0) + , rule_to_physical_stream_number_map(0), num_properties(0) + , properties(0) {} + virtual ~LogicalStream() + { delete [] physical_stream_numbers; delete [] data_offsets; + delete [] rule_to_physical_stream_number_map; delete [] properties; } + + UINT32 size; + UINT16 object_version; + + UINT16 num_physical_streams; + UINT16 *physical_stream_numbers; + UINT32 *data_offsets; + UINT16 num_rules; + UINT16 *rule_to_physical_stream_number_map; + UINT16 num_properties; + NameValueProperty *properties; + }; + + struct MediaProperties : public Collectable + { + MediaProperties() + : object_version(0), stream_number(0), max_bit_rate(0) + , avg_bit_rate(0), max_packet_size(0), avg_packet_size(0) + , start_time(0), preroll(0), duration(0), stream_name_size(0) + , mime_type_size(0), type_specific_len(0), type_specific_data(0), lstr(0) + { + memset(stream_name, 0, sizeof(UINT8) * 256); + memset(mime_type, 0, sizeof(UINT8) * 256); + } + virtual ~MediaProperties() { delete lstr; delete [] type_specific_data; } + + File_Header_Start s; + UINT16 object_version; + + UINT16 stream_number; + UINT32 max_bit_rate; + UINT32 avg_bit_rate; + UINT32 max_packet_size; + UINT32 avg_packet_size; + UINT32 start_time; + UINT32 preroll; + UINT32 duration; + UINT8 stream_name_size; + UINT8 stream_name[256]; + UINT8 mime_type_size; + UINT8 mime_type[256]; + UINT32 type_specific_len; + UINT8 *type_specific_data; + + LogicalStream *lstr; // only one of these + }; + + + struct ContentDescription : public Collectable + { + ContentDescription() + : object_version(0), title_len(0), title(0), author_len(0) + , author(0), copyright_len(0), copyright(0), comment_len(0) + , comment(0) {} + virtual ~ContentDescription() + { + delete [] title; delete [] author; delete [] copyright; + delete [] comment; + } + + File_Header_Start s; + UINT16 object_version; + + UINT16 title_len; + UINT8 *title; + UINT16 author_len; + UINT8 *author; + UINT16 copyright_len; + UINT8 *copyright; + UINT16 comment_len; + UINT8 *comment; + }; + + + struct PropListEntry + { + UINT32 offset; + UINT32 num_props_for_name; + }; + + struct MDProperties + { + MDProperties() + : size(0), type(0), flags(0), value_offset(0) + , subproperties_offset(0), num_subproperties(0), name_length(0) + , name(0), value_length(0), value(0), subproperties_list(0) + , subproperties(0) {} + virtual ~MDProperties() + { delete [] name; delete [] value; delete [] subproperties_list; delete [] subproperties; } + + UINT32 size; + UINT32 type; + UINT32 flags; + UINT32 value_offset; + UINT32 subproperties_offset; + UINT32 num_subproperties; + UINT32 name_length; + UINT8 *name; + UINT32 value_length; + UINT8 *value; + PropListEntry *subproperties_list; // num_subproperties + MDProperties *subproperties; // num_subproperties + }; + + struct MetadataSection : public Collectable + { + File_Header_Start s; + + UINT32 object_id; + UINT32 object_version; + + // this is the 1 "unnamed root property" + MDProperties properties; + }; + + class Tag; + class File; + + // RealMedia File Format contains a normal ID3v1 Tag at the end of the file + // no sense reinventing the wheel, so this class is just so we can use TagLib + // to manage it + class RMFFile : public TagLib::File + { + public: + RMFFile(const char *filename); + virtual ~RMFFile(); + bool save(); + TagLib::Tag *tag() const { return m_id3tag; } + TagLib::AudioProperties *audioProperties() const { return 0; } + + private: + TagLib::ID3v1::Tag *m_id3tag; + }; + + class TagLib::AudioProperties; + + class RealMediaFF + { + public: + RealMediaFF(const char *file, bool readProperties = true, + TagLib::AudioProperties::ReadStyle propertiesStyle = TagLib::AudioProperties::Average); + RealMediaFF(RealMediaFF &src); + ~RealMediaFF(); + + int err() const { return m_err; } + bool isEmpty() const; + + // tag + TagLib::String title () const; + TagLib::String artist () const; + TagLib::String album () const; + TagLib::String comment () const; + TagLib::String genre () const; + TagLib::uint year () const; + TagLib::uint track () const; + // TODO write support + //void setTitle (const String &s); + //void setArtist (const String &s); + //void setAlbum (const String &s); + //void setComment (const String &s); + //void setGenre (const String &s); + //void setYear (uint i); + //void setTrack (uint i); + + // properties + int length () const; + int bitrate () const; + int sampleRate () const; + int channels () const; + +#ifdef TESTING + std::ostream &operator<<(std::ostream &os); +#endif + + private: + RealMediaFF(); + char *m_filename; + Collectable *m_head; + Collectable *m_tail; + int m_fd; + int m_err; + + File_Header_v0_v1 *m_hdr; + RMProperties *m_props; + MediaProperties *media_hdrs; + ContentDescription *m_contenthdr; + MetadataSection *m_md; + + char *m_title; + char *m_author; + char *m_copyright; + char *m_comment; + + RMFFile *m_id3v1tag; + + bool m_flipYearInMetadataSection; + bool m_readProperties; + + int init(); + int initMetadataSection(); + void saveHeader(Collectable *hdr); + int seekChunk(UINT32 object_id); + + int getHdr(unsigned char *buf, size_t sz, UINT32 &fourcc, UINT32 &csz); + int getChunk(unsigned char *buf, size_t sz, UINT32 &fourcc, UINT32 &csz, UINT32 &consumed); + int getRealFileHeader(File_Header_v0_v1 *hdr, const unsigned char *buf, UINT32 object_id, int sz); + int getRealPropertyHeader(RMProperties *props, const unsigned char *buf, UINT32 object_id, int sz); + int getMediaPropHeader(MediaProperties *mh, const unsigned char *buf, UINT32 object_id, int sz); + int getContentDescription(ContentDescription *cont, const unsigned char *buf, UINT32 object_id, int sz); + int getMDProperties(MDProperties *md, const unsigned char *buf); + +#ifdef TESTING + void printRealFileHeader(std::ostream &os); + void printRealPropHeader(std::ostream &os); + void printMediaPropHeaders(std::ostream &os); + void printContentDescription(std::ostream &os); + void printID3v1Tag(std::ostream &os); + void printMetadataSection(std::ostream &os); + void printMDProperties(std::ostream &os, char *name, MDProperties *props); +#endif + }; + + } // namespace RealMedia +} // namespace TagLib + +#ifdef TESTING +std::ostream &operator<<(std::ostream &os, TagLib::RealMedia::RealMediaFF &rmff); +#endif + +#endif diff --git a/amarok/src/metadata/rmff/taglib_realmediafile.cpp b/amarok/src/metadata/rmff/taglib_realmediafile.cpp new file mode 100644 index 00000000..b28c8467 --- /dev/null +++ b/amarok/src/metadata/rmff/taglib_realmediafile.cpp @@ -0,0 +1,213 @@ +/*************************************************************************** + copyright : (C) 2005 by Paul Cifarelli + email : paulc2@optonline.net + + copyright : (C) 2005 by Lukas Lalinsky + email : lalinsky@gmail.com + (portions) + ***************************************************************************/ + +/*************************************************************************** + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2 or higher as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin St, 5th fl, Boston, MA 02110-1301, * + * USA, or check http://www.fsf.org/about/contact.html * + ***************************************************************************/ + +#include +#include +#include +#include +#include "rmff.h" +#include "taglib_realmediafile.h" + +using namespace TagLib; +using namespace TagLib::RealMedia; + + +RealMedia::Tag::Tag(RealMediaFF *rmff, bool allocnew) : m_rmff(rmff), m_owner(allocnew) +{ + if (m_owner) + m_rmff = new RealMediaFF(*rmff); +} + +RealMedia::Tag::~Tag () +{ + if (m_owner) + delete m_rmff; +} + +String RealMedia::Tag::title () const +{ + return m_rmff->title(); +} + +String RealMedia::Tag::artist () const +{ + return m_rmff->artist(); +} + +String RealMedia::Tag::album () const +{ + return m_rmff->album(); +} + +String RealMedia::Tag::comment () const +{ + return m_rmff->comment(); +} + +String RealMedia::Tag::genre () const +{ + return m_rmff->genre(); +} + +TagLib::uint RealMedia::Tag::year () const +{ + return m_rmff->year(); +} + +TagLib::uint RealMedia::Tag::track () const +{ + return m_rmff->track(); +} + +void RealMedia::Tag::setTitle (const String &) +{ +// TODO: write support +} + +void RealMedia::Tag::setArtist (const String &) +{ +// TODO: write support +} + +void RealMedia::Tag::setAlbum (const String &) +{ +// TODO: write support +} + +void RealMedia::Tag::setComment (const String &) +{ +// TODO: write support +} + +void RealMedia::Tag::setGenre (const String &) +{ +// TODO: write support +} + +void RealMedia::Tag::setYear (uint) +{ +// TODO: write support +} + +void RealMedia::Tag::setTrack (uint) +{ +// TODO: write support +} + +bool RealMedia::Tag::isEmpty() const +{ + return TagLib::Tag::isEmpty() && m_rmff->isEmpty(); +} + +void RealMedia::Tag::duplicate(const Tag *source, Tag *target, bool overwrite) +{ + TagLib::Tag::duplicate(source, target, overwrite); + if (overwrite) + { + if (target->m_owner) + { + delete target->m_rmff; + target->m_rmff = new RealMediaFF(*source->m_rmff); + } + else + target->m_rmff = source->m_rmff; + } + else + { + if (target->isEmpty()) + if (target->m_owner) + { + delete target->m_rmff; + target->m_rmff = new RealMediaFF(*source->m_rmff); + } + else + target->m_rmff = source->m_rmff; + } +} + + + +int RealMedia::Properties::length () const +{ + return (m_rmff->length() / 1000); +} + +int RealMedia::Properties::bitrate () const +{ + return (m_rmff->bitrate() / 1000); +} + +int RealMedia::Properties::sampleRate () const +{ + return m_rmff->sampleRate(); +} + +int RealMedia::Properties::channels () const +{ + return m_rmff->channels(); +} + + +RealMedia::File::File(const char *file, bool readProperties, Properties::ReadStyle propertiesStyle) + : TagLib::File(file), m_rmfile(0), m_tag(0), m_props(0) +{ + m_rmfile = new RealMediaFF(file, readProperties, propertiesStyle); + m_tag = new RealMedia::Tag(m_rmfile); + m_props = new RealMedia::Properties(m_rmfile); +} + +RealMedia::File::~File() +{ + delete m_props; + delete m_tag; + delete m_rmfile; +} + +TagLib::Tag *RealMedia::File::tag() const +{ + return m_tag; +} + +RealMedia::Tag *RealMedia::File::RealMediaTag() const +{ + return m_tag; +} + +RealMedia::Properties *RealMedia::File::audioProperties() const +{ + return m_props; // m_rmfile->properties; +} + + + + + diff --git a/amarok/src/metadata/rmff/taglib_realmediafile.h b/amarok/src/metadata/rmff/taglib_realmediafile.h new file mode 100644 index 00000000..0f0ca58d --- /dev/null +++ b/amarok/src/metadata/rmff/taglib_realmediafile.h @@ -0,0 +1,133 @@ +/*************************************************************************** + copyright : (C) 2005 by Paul Cifarelli + email : paulc2@optonline.net + + copyright : (C) 2005 by Lukas Lalinsky + email : lalinsky@gmail.com + (portions) + ***************************************************************************/ + +/*************************************************************************** + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2 or higher as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin St, 5th fl, Boston, MA 02110-1301, * + * USA, or check http://www.fsf.org/about/contact.html * + ***************************************************************************/ +#ifndef _TAGLIB_REALMEDIAFILE_H_ +#define _TAGLIB_REALMEDIAFILE_H_ + +#include +#include +#include + +#include + +class RealMediaFF; +namespace TagLib { + + namespace RealMedia { + + class Tag : public TagLib::Tag + { + public: + Tag(RealMediaFF *rmff, bool allocnew = false); + virtual ~Tag (); + virtual String title () const; + virtual String artist () const; + virtual String album () const; + virtual String comment () const; + virtual String genre () const; + virtual uint year () const; + virtual uint track () const; + virtual void setTitle (const String &s); + virtual void setArtist (const String &s); + virtual void setAlbum (const String &s); + virtual void setComment (const String &s); + virtual void setGenre (const String &s); + virtual void setYear (uint i); + virtual void setTrack (uint i); + + bool isEmpty() const; + void duplicate(const Tag *source, Tag *target, bool overwrite); + + private: + Tag(); + RealMediaFF *m_rmff; + bool m_owner; + }; + + + class Properties : public TagLib::AudioProperties + { + public: + Properties(RealMediaFF *rmff) : TagLib::AudioProperties(Average), m_rmff(rmff) {} + virtual ~Properties() {} // you don't own rmff + virtual int length () const; + virtual int bitrate () const; + virtual int sampleRate () const; + virtual int channels () const; + + private: + Properties(); + RealMediaFF *m_rmff; + }; + + class File : public TagLib::File + { + public: + + File(const char *file, bool readProperties = true, Properties::ReadStyle propertiesStyle = Properties::Average); + + virtual ~File(); + + /* + * Returns the TagLib::Tag for this file. + */ + virtual TagLib::Tag *tag() const; + + /* + * Returns the RealMedia::RealMediaTag for this file. + */ + virtual Tag *RealMediaTag() const; + + /* + * Returns the RealMedia::Properties for this file. + */ + virtual Properties *audioProperties() const; + + + /* + * Save the file. + * + * This returns true if the save was successful. + */ + virtual bool save() { return false; } // for now + + private: + + RealMediaFF *m_rmfile; + Tag *m_tag; + Properties *m_props; + }; + + } + +} + +#endif diff --git a/amarok/src/metadata/rmff/taglib_realmediafiletyperesolver.cpp b/amarok/src/metadata/rmff/taglib_realmediafiletyperesolver.cpp new file mode 100644 index 00000000..a8660a63 --- /dev/null +++ b/amarok/src/metadata/rmff/taglib_realmediafiletyperesolver.cpp @@ -0,0 +1,53 @@ +/*************************************************************************** + copyright : (C) 2005 by Paul Cifarelli + email : paulc2@optonline.net + ***************************************************************************/ + +/*************************************************************************** + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +/*************************************************************************** + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin St, 5th fl, Boston, MA 02110-1301, * + * USA, or check http://www.fsf.org/about/contact.html * + * * + ***************************************************************************/ + +#include +#include +#include +#include "taglib_realmediafiletyperesolver.h" +#include "taglib_realmediafile.h" +#include "rmff.h" + +#include + +TagLib::File *RealMediaFileTypeResolver::createFile(const char *fileName, + bool readProperties, + TagLib::AudioProperties::ReadStyle propertiesStyle) const +{ + const char *ext = strrchr(fileName, '.'); + if(ext && (!strcasecmp(ext, ".ra") || !strcasecmp(ext, ".rv") || !strcasecmp(ext, ".rm") || + !strcasecmp(ext, ".rmj") || !strcasecmp(ext, ".rmvb") )) + { + TagLib::RealMedia::File *f = new TagLib::RealMedia::File(fileName, readProperties, propertiesStyle); + if(f->isValid()) + return f; + else + { + delete f; + } + } + + return 0; +} diff --git a/amarok/src/metadata/rmff/taglib_realmediafiletyperesolver.h b/amarok/src/metadata/rmff/taglib_realmediafiletyperesolver.h new file mode 100644 index 00000000..b292e108 --- /dev/null +++ b/amarok/src/metadata/rmff/taglib_realmediafiletyperesolver.h @@ -0,0 +1,39 @@ +/*************************************************************************** + copyright : (C) 2005 by Paul Cifarelli + email : paulc2@optonline.net + ***************************************************************************/ + +/*************************************************************************** + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +/*************************************************************************** + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin St, 5th fl, Boston, MA 02110-1301, * + * USA, or check http://www.fsf.org/about/contact.html * + * * + ***************************************************************************/ +#ifndef _TAGLIB_REALMEDIAFILETYPERESOLVER_H_ +#define _TAGLIB_REALMEDIAFILETYPERESOLVER_H_ + +#include +#include + + +class RealMediaFileTypeResolver : public TagLib::FileRef::FileTypeResolver +{ + TagLib::File *createFile(const char *fileName, + bool readAudioProperties, + TagLib::AudioProperties::ReadStyle audioPropertiesStyle) const; +}; + +#endif diff --git a/amarok/src/metadata/speex/Makefile.am b/amarok/src/metadata/speex/Makefile.am new file mode 100644 index 00000000..a333d850 --- /dev/null +++ b/amarok/src/metadata/speex/Makefile.am @@ -0,0 +1,16 @@ +SUBDIRS = + +INCLUDES = $(all_includes) $(TAGLIB_CFLAGS) +METASOURCES = AUTO +libtagspeex_la_LDFLAGS = $(all_libraries) +noinst_LTLIBRARIES = libtagspeex.la + +libtagspeex_la_SOURCES = \ + speexfile.cpp \ + speexproperties.cpp \ + taglib_speexfiletyperesolver.cpp + +noinst_HEADERS = speexfile.h \ + speexproperties.h \ + taglib_speexfiletyperesolver.h + diff --git a/amarok/src/metadata/speex/speexfile.cpp b/amarok/src/metadata/speex/speexfile.cpp new file mode 100644 index 00000000..eccadedd --- /dev/null +++ b/amarok/src/metadata/speex/speexfile.cpp @@ -0,0 +1,111 @@ +/*************************************************************************** + copyright : (C) 2006 by Lukáš Lalinský + email : lalinsky@gmail.com + + copyright : (C) 2002 by Scott Wheeler + email : wheeler@kde.org + (original Vorbis implementation) + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301 USA * + ***************************************************************************/ + +#include + +#include +#if 0 +#include +#endif + +#include "speexfile.h" + +using namespace TagLib; + +class Speex::File::FilePrivate +{ +public: + FilePrivate() : + comment(0), + properties(0) {} + + ~FilePrivate() + { + delete comment; + delete properties; + } + + Ogg::XiphComment *comment; + Properties *properties; +}; + +//////////////////////////////////////////////////////////////////////////////// +// public members +//////////////////////////////////////////////////////////////////////////////// + +Speex::File::File(const char *file, bool readProperties, + Properties::ReadStyle propertiesStyle) : Ogg::File(file) +{ + d = new FilePrivate; + read(readProperties, propertiesStyle); +} + +Speex::File::~File() +{ + delete d; +} + +Ogg::XiphComment *Speex::File::tag() const +{ + return d->comment; +} + +Speex::Properties *Speex::File::audioProperties() const +{ + return d->properties; +} + +bool Speex::File::save() +{ + if(!d->comment) + d->comment = new Ogg::XiphComment; + + setPacket(1, d->comment->render()); + + return Ogg::File::save(); +} + +//////////////////////////////////////////////////////////////////////////////// +// private members +//////////////////////////////////////////////////////////////////////////////// + +void Speex::File::read(bool readProperties, Properties::ReadStyle propertiesStyle) +{ + ByteVector speexHeaderData = packet(0); + + if(!speexHeaderData.startsWith("Speex ")) { +#if 0 + debug("Speex::File::read() -- invalid Speex identification header"); +#endif + return; + } + + ByteVector commentHeaderData = packet(1); + + d->comment = new Ogg::XiphComment(commentHeaderData); + + if(readProperties) + d->properties = new Properties(this, propertiesStyle); +} diff --git a/amarok/src/metadata/speex/speexfile.h b/amarok/src/metadata/speex/speexfile.h new file mode 100644 index 00000000..d300ac3c --- /dev/null +++ b/amarok/src/metadata/speex/speexfile.h @@ -0,0 +1,93 @@ +/*************************************************************************** + copyright : (C) 2006 by Lukáš Lalinský + email : lalinsky@gmail.com + + copyright : (C) 2002 by Scott Wheeler + email : wheeler@kde.org + (original Vorbis implementation) + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301 USA * + ***************************************************************************/ + +#ifndef TAGLIB_SPEEXFILE_H +#define TAGLIB_SPEEXFILE_H + +#include +#include + +#include "speexproperties.h" + +namespace TagLib { + + //! A namespace containing classes for Speex metadata + + namespace Speex { + + //! An implementation of Ogg::File with Speex specific methods + + /*! + * This is the central class in the Ogg Speex metadata processing collection + * of classes. It's built upon Ogg::File which handles processing of the Ogg + * logical bitstream and breaking it down into pages which are handled by + * the codec implementations, in this case Speex specifically. + */ + + class File : public Ogg::File + { + public: + /*! + * Contructs a Speex file from \a file. If \a readProperties is true the + * file's audio properties will also be read using \a propertiesStyle. If + * false, \a propertiesStyle is ignored. + */ + File(const char *file, bool readProperties = true, + Properties::ReadStyle propertiesStyle = Properties::Average); + + /*! + * Destroys this instance of the File. + */ + virtual ~File(); + + /*! + * Returns the XiphComment for this file. XiphComment implements the tag + * interface, so this serves as the reimplementation of + * TagLib::File::tag(). + */ + virtual Ogg::XiphComment *tag() const; + + /*! + * Returns the Speex::Properties for this file. If no audio properties + * were read then this will return a null pointer. + */ + virtual Properties *audioProperties() const; + + virtual bool save(); + + private: + File(const File &); + File &operator=(const File &); + + void read(bool readProperties, Properties::ReadStyle propertiesStyle); + + class FilePrivate; + FilePrivate *d; + }; + } + +} + +#endif diff --git a/amarok/src/metadata/speex/speexproperties.cpp b/amarok/src/metadata/speex/speexproperties.cpp new file mode 100644 index 00000000..edd2b710 --- /dev/null +++ b/amarok/src/metadata/speex/speexproperties.cpp @@ -0,0 +1,171 @@ +/*************************************************************************** + copyright : (C) 2006 by Lukáš Lalinský + email : lalinsky@gmail.com + + copyright : (C) 2002 by Scott Wheeler + email : wheeler@kde.org + (original Vorbis implementation) + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301 USA * + ***************************************************************************/ + +#include +#if 0 +#include +#endif + +#include + +#include "speexproperties.h" +#include "speexfile.h" + +using namespace TagLib; + +class Speex::Properties::PropertiesPrivate +{ +public: + PropertiesPrivate(File *f, ReadStyle s) : + file(f), + style(s), + length(0), + bitrate(0), + sampleRate(0), + channels(0), + speexVersion(0), + vbr(false), + mode(0) {} + + File *file; + ReadStyle style; + int length; + int bitrate; + int sampleRate; + int channels; + int speexVersion; + bool vbr; + int mode; +}; + +//////////////////////////////////////////////////////////////////////////////// +// public members +//////////////////////////////////////////////////////////////////////////////// + +Speex::Properties::Properties(File *file, ReadStyle style) : AudioProperties(style) +{ + d = new PropertiesPrivate(file, style); + read(); +} + +Speex::Properties::~Properties() +{ + delete d; +} + +int Speex::Properties::length() const +{ + return d->length; +} + +int Speex::Properties::bitrate() const +{ + return int(float(d->bitrate) / float(1000) + 0.5); +} + +int Speex::Properties::sampleRate() const +{ + return d->sampleRate; +} + +int Speex::Properties::channels() const +{ + return d->channels; +} + +int Speex::Properties::speexVersion() const +{ + return d->speexVersion; +} + +//////////////////////////////////////////////////////////////////////////////// +// private members +//////////////////////////////////////////////////////////////////////////////// + +void Speex::Properties::read() +{ + // Get the identification header from the Ogg implementation. + + ByteVector data = d->file->packet(0); + + int pos = 28; + + // speex_version_id; /**< Version for Speex (for checking compatibility) */ + d->speexVersion = data.mid(pos, 4).toUInt(false); + pos += 4; + + // header_size; /**< Total size of the header ( sizeof(SpeexHeader) ) */ + pos += 4; + + // rate; /**< Sampling rate used */ + d->sampleRate = data.mid(pos, 4).toUInt(false); + pos += 4; + + // mode; /**< Mode used (0 for narrowband, 1 for wideband) */ + d->mode = data.mid(pos, 4).toUInt(false); + pos += 4; + + // mode_bitstream_version; /**< Version ID of the bit-stream */ + pos += 4; + + // nb_channels; /**< Number of channels encoded */ + d->channels = data.mid(pos, 4).toUInt(false); + pos += 4; + + // bitrate; /**< Bit-rate used */ + d->bitrate = data.mid(pos, 4).toUInt(false); + pos += 4; + + // frame_size; /**< Size of frames */ + //unsigned int frameSize = data.mid(pos, 4).toUInt(false); + pos += 4; + + // vbr; /**< 1 for a VBR encoding, 0 otherwise */ + d->vbr = data.mid(pos, 4).toUInt(false) == 1; + pos += 4; + + // frames_per_packet; /**< Number of frames stored per Ogg packet */ + //unsigned int framesPerPacket = data.mid(pos, 4).toUInt(false); + + const Ogg::PageHeader *first = d->file->firstPageHeader(); + const Ogg::PageHeader *last = d->file->lastPageHeader(); + + if(first && last) { + long long start = first->absoluteGranularPosition(); + long long end = last->absoluteGranularPosition(); + + if(start >= 0 && end >= 0 && d->sampleRate > 0) + d->length = (end - start) / (long long) d->sampleRate; +#if 0 + else + debug("Speex::Properties::read() -- Either the PCM values for the start or " + "end of this file was incorrect or the sample rate is zero."); +#endif + } +#if 0 + else + debug("Speex::Properties::read() -- Could not find valid first and last Ogg pages."); +#endif +} diff --git a/amarok/src/metadata/speex/speexproperties.h b/amarok/src/metadata/speex/speexproperties.h new file mode 100644 index 00000000..355ff3cb --- /dev/null +++ b/amarok/src/metadata/speex/speexproperties.h @@ -0,0 +1,83 @@ +/*************************************************************************** + copyright : (C) 2006 by Lukáš Lalinský + email : lalinsky@gmail.com + + copyright : (C) 2002 by Scott Wheeler + email : wheeler@kde.org + (original Vorbis implementation) + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301 USA * + ***************************************************************************/ + +#ifndef TAGLIB_SPEEXPROPERTIES_H +#define TAGLIB_SPEEXPROPERTIES_H + +#include + +namespace TagLib { + + namespace Speex { + + class File; + + //! An implementation of audio property reading for Ogg Speex + + /*! + * This reads the data from an Ogg Speex stream found in the AudioProperties + * API. + */ + + class Properties : public AudioProperties + { + public: + /*! + * Create an instance of Vorbis::Properties with the data read from the + * Vorbis::File \a file. + */ + Properties(File *file, ReadStyle style = Average); + + /*! + * Destroys this VorbisProperties instance. + */ + virtual ~Properties(); + + // Reimplementations. + + virtual int length() const; + virtual int bitrate() const; + virtual int sampleRate() const; + virtual int channels() const; + + /*! + * Returns the Vorbis version, currently "0" (as specified by the spec). + */ + int speexVersion() const; + + private: + Properties(const Properties &); + Properties &operator=(const Properties &); + + void read(); + + class PropertiesPrivate; + PropertiesPrivate *d; + }; + } + +} + +#endif diff --git a/amarok/src/metadata/speex/taglib_speexfiletyperesolver.cpp b/amarok/src/metadata/speex/taglib_speexfiletyperesolver.cpp new file mode 100644 index 00000000..9a5de0d7 --- /dev/null +++ b/amarok/src/metadata/speex/taglib_speexfiletyperesolver.cpp @@ -0,0 +1,44 @@ +/*************************************************************************** + copyright : (C) 2006 by Martin Aumueller + email : aumuell@reserv.at + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301 USA * + ***************************************************************************/ + +#include "taglib_speexfiletyperesolver.h" +#include "speexfile.h" + +#include + +TagLib::File *SpeexFileTypeResolver::createFile(const char *fileName, + bool readProperties, + TagLib::AudioProperties::ReadStyle propertiesStyle) const +{ + const char *ext = strrchr(fileName, '.'); + if(ext && !strcasecmp(ext, ".spx")) + { + TagLib::Speex::File *f = new TagLib::Speex::File(fileName, readProperties, propertiesStyle); + if(f->isValid()) + return f; + else + { + delete f; + } + } + + return 0; +} diff --git a/amarok/src/metadata/speex/taglib_speexfiletyperesolver.h b/amarok/src/metadata/speex/taglib_speexfiletyperesolver.h new file mode 100644 index 00000000..b5c5c2c4 --- /dev/null +++ b/amarok/src/metadata/speex/taglib_speexfiletyperesolver.h @@ -0,0 +1,36 @@ +/*************************************************************************** + copyright : (C) 2006 by Martin Aumueller + email : aumuell@reserv.at + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301 USA * + ***************************************************************************/ + +#ifndef TAGLIB_SPEEXFILETYPERESOLVER_H +#define TAGLIB_SPEEXFILETYPERESOLVER_H + +#include +#include + + +class SpeexFileTypeResolver : public TagLib::FileRef::FileTypeResolver +{ + TagLib::File *createFile(const char *fileName, + bool readAudioProperties, + TagLib::AudioProperties::ReadStyle audioPropertiesStyle) const; +}; + +#endif diff --git a/amarok/src/metadata/tplugins.cpp b/amarok/src/metadata/tplugins.cpp new file mode 100644 index 00000000..2e176255 --- /dev/null +++ b/amarok/src/metadata/tplugins.cpp @@ -0,0 +1,146 @@ +/*************************************************************************** + copyright : (C) 2005, 2006 by Martin Aumueller + email : aumuell@reserv.at + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301 USA * + ***************************************************************************/ + +#include +#include + +#include +#include + +#include +#include + +#ifdef HAVE_MP4V2 +#include "mp4/taglib_mp4filetyperesolver.h" +#include "mp4/mp4file.h" +#else +#include "m4a/taglib_mp4filetyperesolver.h" +#include "m4a/mp4file.h" +#endif + +#ifndef TAGLIB_15 +#include "trueaudio/taglib_trueaudiofiletyperesolver.h" +#include "trueaudio/ttafile.h" +#include "wavpack/taglib_wavpackfiletyperesolver.h" +#include "wavpack/wvfile.h" +#include "speex/taglib_speexfiletyperesolver.h" +#include "speex/speexfile.h" +#endif + +#include "asf/taglib_asffiletyperesolver.h" +#include "asf/asffile.h" +#include "rmff/taglib_realmediafiletyperesolver.h" +#include "rmff/taglib_realmediafile.h" +#include "audible/taglib_audiblefiletyperesolver.h" +#include "audible/taglib_audiblefile.h" +#include "wav/wavfiletyperesolver.h" +#include "wav/wavfile.h" +#include "aac/aacfiletyperesolver.h" + +#include +#include +#include +#include +#include +#include + + +class MimeTypeFileTypeResolver : public TagLib::FileRef::FileTypeResolver +{ + TagLib::File *createFile(const char *fileName, + bool readAudioProperties, + TagLib::AudioProperties::ReadStyle audioPropertiesStyle) const; +}; + +TagLib::File *MimeTypeFileTypeResolver::createFile(const char *fileName, + bool readProperties, + TagLib::AudioProperties::ReadStyle propertiesStyle) const +{ + QString fn = QFile::decodeName( fileName ); + int accuracy = 0; + + KMimeType::Ptr mimetype = KMimeType::findByFileContent( fn, &accuracy ); + if( accuracy <= 0 ) + mimetype = KMimeType::findByPath( fn ); + + if( mimetype->is( "audio/aac" ) + || mimetype->is( "audio/mpeg" ) + || mimetype->is( "audio/mpegurl" ) + || mimetype->is( "audio/x-mpegurl" ) + || mimetype->is( "audio/x-mp3" )) + { + return new TagLib::MPEG::File(fileName, readProperties, propertiesStyle); + } + else if( mimetype->is( "audio/mp4" ) || mimetype->is( "video/mp4" ) ) + { + return new TagLib::MP4::File(fileName, readProperties, propertiesStyle); + } + else if( mimetype->is( "audio/x-ms-wma" ) + || mimetype->is( "video/x-ms-asf" ) + || mimetype->is( "video/x-msvideo" ) + || mimetype->is( "video/x-ms-wmv" ) ) + { + return new TagLib::ASF::File(fileName, readProperties, propertiesStyle); + } + else if( mimetype->is( "audio/vnd.rn-realaudio" ) + || mimetype->is( "audio/x-pn-realaudio" ) + || mimetype->is( "audio/x-pn-realaudioplugin" ) + || mimetype->is( "audio/vnd.rn-realvideo" ) ) + { + return new TagLib::RealMedia::File(fileName, readProperties, propertiesStyle); + } + else if( mimetype->is( "audio/vorbis" ) ) + { + return new TagLib::Ogg::Vorbis::File(fileName, readProperties, propertiesStyle); + } + else if( mimetype->is( "audio/x-oggflac" ) ) + { + return new TagLib::Ogg::FLAC::File(fileName, readProperties, propertiesStyle); + } + else if( mimetype->is( "audio/x-flac" ) ) + { + return new TagLib::FLAC::File(fileName, readProperties, propertiesStyle); + } + else if( mimetype->is( "audio/x-musepack" ) ) + { + return new TagLib::MPC::File(fileName, readProperties, propertiesStyle); + } + + debug() << "kmimetype filetype guessing failed for" << fileName << endl; + + return 0; +} + +void registerTaglibPlugins() +{ + //TagLib::FileRef::addFileTypeResolver(new MimeTypeFileTypeResolver); + TagLib::FileRef::addFileTypeResolver(new MP4FileTypeResolver); + TagLib::FileRef::addFileTypeResolver(new ASFFileTypeResolver); + TagLib::FileRef::addFileTypeResolver(new RealMediaFileTypeResolver); + TagLib::FileRef::addFileTypeResolver(new AudibleFileTypeResolver); + TagLib::FileRef::addFileTypeResolver(new AACFileTypeResolver); + TagLib::FileRef::addFileTypeResolver(new WavFileTypeResolver); +#ifndef TAGLIB_15 + TagLib::FileRef::addFileTypeResolver(new WavPackFileTypeResolver); + TagLib::FileRef::addFileTypeResolver(new SpeexFileTypeResolver); + TagLib::FileRef::addFileTypeResolver(new TTAFileTypeResolver); +#endif +} diff --git a/amarok/src/metadata/tplugins.h b/amarok/src/metadata/tplugins.h new file mode 100644 index 00000000..1ef356fc --- /dev/null +++ b/amarok/src/metadata/tplugins.h @@ -0,0 +1,27 @@ +/*************************************************************************** + copyright : (C) 2005 by Martin Aumueller + email : aumuell@reserv.at + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301 USA * + ***************************************************************************/ + +#ifndef METADATA_TPLUGIN_H +#define METADATA_TPLUGIN_H + +void registerTaglibPlugins(); + +#endif diff --git a/amarok/src/metadata/trueaudio/Makefile.am b/amarok/src/metadata/trueaudio/Makefile.am new file mode 100644 index 00000000..6b095938 --- /dev/null +++ b/amarok/src/metadata/trueaudio/Makefile.am @@ -0,0 +1,16 @@ +SUBDIRS = + +INCLUDES = $(all_includes) $(TAGLIB_CFLAGS) +METASOURCES = AUTO +libtagtrueaudio_la_LDFLAGS = $(all_libraries) +noinst_LTLIBRARIES = libtagtrueaudio.la + +libtagtrueaudio_la_SOURCES = \ + ttafile.cpp \ + ttaproperties.cpp \ + taglib_trueaudiofiletyperesolver.cpp + +noinst_HEADERS = ttafile.h \ + ttaproperties.h \ + taglib_trueaudiofiletyperesolver.h + diff --git a/amarok/src/metadata/trueaudio/combinedtag.h b/amarok/src/metadata/trueaudio/combinedtag.h new file mode 100644 index 00000000..7d530d29 --- /dev/null +++ b/amarok/src/metadata/trueaudio/combinedtag.h @@ -0,0 +1,171 @@ +/*************************************************************************** + copyright : (C) 2004 by Allan Sandfeld Jensen + email : kde@carewolf.org + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301 USA * + ***************************************************************************/ + +#ifndef DO_NOT_DOCUMENT // Tell Doxygen not to document this header + +#ifndef TAGLIB_COMBINEDTAG_H +#define TAGLIB_COMBINEDTAG_H + +//////////////////////////////////////////////////////////////////////////////// +// Note that this header is not installed. +//////////////////////////////////////////////////////////////////////////////// + +#include + +namespace TagLib { + + /*! + * A union of two TagLib::Tags. + */ + class CombinedTag : public TagLib::Tag + { + public: + CombinedTag(Tag *tag1 = 0, Tag *tag2 = 0) + : TagLib::Tag(), + tag1(tag1), tag2(tag2) {} + + virtual String title() const { + if(tag1 && !tag1->title().isEmpty()) + return tag1->title(); + + if(tag2) + return tag2->title(); + + return String::null; + } + + virtual String artist() const { + if(tag1 && !tag1->artist().isEmpty()) + return tag1->artist(); + + if(tag2) + return tag2->artist(); + + return String::null; + } + + virtual String album() const { + if(tag1 && !tag1->album().isEmpty()) + return tag1->album(); + + if(tag2) + return tag2->album(); + + return String::null; + } + + virtual String comment() const { + if(tag1 && !tag1->comment().isEmpty()) + return tag1->comment(); + + if(tag2) + return tag2->comment(); + + return String::null; + } + + virtual String genre() const { + if(tag1 && !tag1->genre().isEmpty()) + return tag1->genre(); + + if(tag2) + return tag2->genre(); + + return String::null; + } + + virtual uint year() const { + if(tag1 && tag1->year() > 0) + return tag1->year(); + + if(tag2) + return tag2->year(); + + return 0; + } + + virtual uint track() const { + if(tag1 && tag1->track() > 0) + return tag1->track(); + + if(tag2) + return tag2->track(); + + return 0; + } + + virtual void setTitle(const String &s) { + if(tag1) + tag1->setTitle(s); + if(tag2) + tag2->setTitle(s); + } + + virtual void setArtist(const String &s) { + if(tag1) + tag1->setArtist(s); + if(tag2) + tag2->setArtist(s); + } + + virtual void setAlbum(const String &s) { + if(tag1) + tag1->setAlbum(s); + if(tag2) + tag2->setAlbum(s); + } + + virtual void setComment(const String &s) { + if(tag1) + tag1->setComment(s); + if(tag2) + tag2->setComment(s); + } + + virtual void setGenre(const String &s) { + if(tag1) + tag1->setGenre(s); + if(tag2) + tag2->setGenre(s); + } + + virtual void setYear(uint i) { + if(tag1) + tag1->setYear(i); + if(tag2) + tag2->setYear(i); + } + + virtual void setTrack(uint i) { + if(tag1) + tag1->setTrack(i); + if(tag2) + tag2->setTrack(i); + } + + private: + Tag *tag1; + Tag *tag2; + }; +} + +#endif +#endif diff --git a/amarok/src/metadata/trueaudio/taglib_trueaudiofiletyperesolver.cpp b/amarok/src/metadata/trueaudio/taglib_trueaudiofiletyperesolver.cpp new file mode 100644 index 00000000..e84e501a --- /dev/null +++ b/amarok/src/metadata/trueaudio/taglib_trueaudiofiletyperesolver.cpp @@ -0,0 +1,44 @@ +/*************************************************************************** + copyright : (C) 2006 by Martin Aumueller + email : aumuell@reserv.at + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301 USA * + ***************************************************************************/ + +#include "taglib_trueaudiofiletyperesolver.h" +#include "ttafile.h" + +#include + +TagLib::File *TTAFileTypeResolver::createFile(const char *fileName, + bool readProperties, + TagLib::AudioProperties::ReadStyle propertiesStyle) const +{ + const char *ext = strrchr(fileName, '.'); + if(ext && !strcasecmp(ext, ".tta")) + { + TagLib::TTA::File *f = new TagLib::TTA::File(fileName, readProperties, propertiesStyle); + if(f->isValid()) + return f; + else + { + delete f; + } + } + + return 0; +} diff --git a/amarok/src/metadata/trueaudio/taglib_trueaudiofiletyperesolver.h b/amarok/src/metadata/trueaudio/taglib_trueaudiofiletyperesolver.h new file mode 100644 index 00000000..e436e438 --- /dev/null +++ b/amarok/src/metadata/trueaudio/taglib_trueaudiofiletyperesolver.h @@ -0,0 +1,36 @@ +/*************************************************************************** + copyright : (C) 2006 by Martin Aumueller + email : aumuell@reserv.at + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301 USA * + ***************************************************************************/ + +#ifndef TAGLIB_TRUEAUDIOFILETYPERESOLVER_H +#define TAGLIB_TRUEAUDIOFILETYPERESOLVER_H + +#include +#include + + +class TTAFileTypeResolver : public TagLib::FileRef::FileTypeResolver +{ + TagLib::File *createFile(const char *fileName, + bool readAudioProperties, + TagLib::AudioProperties::ReadStyle audioPropertiesStyle) const; +}; + +#endif diff --git a/amarok/src/metadata/trueaudio/ttafile.cpp b/amarok/src/metadata/trueaudio/ttafile.cpp new file mode 100644 index 00000000..3a02e209 --- /dev/null +++ b/amarok/src/metadata/trueaudio/ttafile.cpp @@ -0,0 +1,307 @@ +/*************************************************************************** + copyright : (C) 2006 by Lukáš Lalinský + email : lalinsky@gmail.com + + copyright : (C) 2004 by Allan Sandfeld Jensen + email : kde@carewolf.org + (original MPC implementation) + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301 USA * + ***************************************************************************/ + +#include +#include +#if 0 +#include +#endif + +#include "ttafile.h" +#include "id3v1tag.h" +#include "id3v2tag.h" +#include "id3v2header.h" +#include "combinedtag.h" + +using namespace TagLib; + +class TTA::File::FilePrivate +{ +public: + FilePrivate(const ID3v2::FrameFactory *frameFactory = ID3v2::FrameFactory::instance()) : + ID3v2FrameFactory(frameFactory), + ID3v2Tag(0), + ID3v2Location(-1), + ID3v2OriginalSize(0), + ID3v1Tag(0), + ID3v1Location(-1), + tag(0), + properties(0), + scanned(false), + hasID3v1(false), + hasID3v2(false) {} + + ~FilePrivate() + { + if (tag != ID3v1Tag && tag != ID3v2Tag) delete tag; + delete ID3v1Tag; + delete ID3v2Tag; + delete properties; + } + + const ID3v2::FrameFactory *ID3v2FrameFactory; + ID3v2::Tag *ID3v2Tag; + long ID3v2Location; + uint ID3v2OriginalSize; + + ID3v1::Tag *ID3v1Tag; + long ID3v1Location; + + Tag *tag; + + Properties *properties; + bool scanned; + + // These indicate whether the file *on disk* has these tags, not if + // this data structure does. This is used in computing offsets. + + bool hasID3v1; + bool hasID3v2; +}; + +//////////////////////////////////////////////////////////////////////////////// +// public members +//////////////////////////////////////////////////////////////////////////////// + +TTA::File::File(const char *file, bool readProperties, + Properties::ReadStyle propertiesStyle) : TagLib::File(file) +{ + d = new FilePrivate; + if(isOpen()) + read(readProperties, propertiesStyle); +} + +TTA::File::File(const char *file, ID3v2::FrameFactory *frameFactory, + bool readProperties, Properties::ReadStyle propertiesStyle) : + TagLib::File(file) +{ + d = new FilePrivate(frameFactory); + if(isOpen()) + read(readProperties, propertiesStyle); +} + +TTA::File::~File() +{ + delete d; +} + +TagLib::Tag *TTA::File::tag() const +{ + return d->tag; +} + +TTA::Properties *TTA::File::audioProperties() const +{ + return d->properties; +} + +void TTA::File::setID3v2FrameFactory(const ID3v2::FrameFactory *factory) +{ + d->ID3v2FrameFactory = factory; +} + +bool TTA::File::save() +{ + if(readOnly()) { +#if 0 + debug("TTA::File::save() -- File is read only."); +#endif + return false; + } + + // Update ID3v2 tag + + if(d->ID3v2Tag) { + if(!d->hasID3v2) { + d->ID3v2Location = 0; + d->ID3v2OriginalSize = 0; + } + insert(d->ID3v2Tag->render(), d->ID3v2Location, d->ID3v2OriginalSize); + d->hasID3v2 = true; + } + else if(d->hasID3v2) { + removeBlock(d->ID3v2Location, d->ID3v2OriginalSize); + d->hasID3v2 = false; + } + + // Update ID3v1 tag + + if(d->ID3v1Tag) { + if(!d->hasID3v1) { + seek(0, End); + d->ID3v1Location = tell(); + } + else + seek(d->ID3v1Location); + writeBlock(d->ID3v1Tag->render()); + d->hasID3v1 = true; + } + else if(d->hasID3v1) { + removeBlock(d->ID3v1Location, 128); + d->hasID3v1 = false; + } + + return true; +} + +ID3v1::Tag *TTA::File::ID3v1Tag(bool create) +{ + if(!create || d->ID3v1Tag) + return d->ID3v1Tag; + + // no ID3v1 tag exists and we've been asked to create one + + d->ID3v1Tag = new ID3v1::Tag; + + if(d->ID3v2Tag) + d->tag = new CombinedTag(d->ID3v2Tag, d->ID3v1Tag); + else + d->tag = d->ID3v1Tag; + + return d->ID3v1Tag; +} + +ID3v2::Tag *TTA::File::ID3v2Tag(bool create) +{ + if(!create || d->ID3v2Tag) + return d->ID3v2Tag; + + // no ID3v2 tag exists and we've been asked to create one + + d->ID3v2Tag = new ID3v2::Tag; + + if(d->ID3v1Tag) + d->tag = new CombinedTag(d->ID3v2Tag, d->ID3v1Tag); + else + d->tag = d->ID3v2Tag; + + return d->ID3v2Tag; +} + +void TTA::File::remove(int tags) +{ + if(tags & ID3v1) { + delete d->ID3v1Tag; + d->ID3v1Tag = 0; + + if(d->ID3v2Tag) + d->tag = d->ID3v2Tag; + else + d->tag = d->ID3v2Tag = new ID3v2::Tag; + } + + if(tags & ID3v2) { + delete d->ID3v2Tag; + d->ID3v2Tag = 0; + + if(d->ID3v1Tag) + d->tag = d->ID3v1Tag; + else + d->tag = d->ID3v2Tag = new ID3v2::Tag; + } +} + + +//////////////////////////////////////////////////////////////////////////////// +// private members +//////////////////////////////////////////////////////////////////////////////// + +void TTA::File::read(bool readProperties, Properties::ReadStyle /* propertiesStyle */) +{ + // Look for an ID3v2 tag + + d->ID3v2Location = findID3v2(); + + if(d->ID3v2Location >= 0) { + + d->ID3v2Tag = new ID3v2::Tag(this, d->ID3v2Location, d->ID3v2FrameFactory); + + d->ID3v2OriginalSize = d->ID3v2Tag->header()->completeTagSize(); + + if(d->ID3v2Tag->header()->tagSize() <= 0) { + delete d->ID3v2Tag; + d->ID3v2Tag = 0; + } + else + d->hasID3v2 = true; + } + + // Look for an ID3v1 tag + + d->ID3v1Location = findID3v1(); + + if(d->ID3v1Location >= 0) { + d->ID3v1Tag = new ID3v1::Tag(this, d->ID3v1Location); + d->hasID3v1 = true; + } + + if(d->hasID3v1 && d->hasID3v2) + d->tag = new CombinedTag(d->ID3v2Tag, d->ID3v1Tag); + else { + if(d->hasID3v1) + d->tag = d->ID3v1Tag; + else { + if(d->hasID3v2) + d->tag = d->ID3v2Tag; + else + d->tag = d->ID3v2Tag = new ID3v2::Tag; + } + } + + // Look for TTA metadata + + if(readProperties) { + seek(d->ID3v2Location + d->ID3v2OriginalSize); + d->properties = new Properties(readBlock(TTA::HeaderSize), + length() - d->ID3v2OriginalSize); + } +} + +long TTA::File::findID3v1() +{ + if(!isValid()) + return -1; + + seek(-128, End); + long p = tell(); + + if(readBlock(3) == ID3v1::Tag::fileIdentifier()) + return p; + + return -1; +} + +long TTA::File::findID3v2() +{ + if(!isValid()) + return -1; + + seek(0); + + if(readBlock(3) == ID3v2::Header::fileIdentifier()) + return 0; + + return -1; +} diff --git a/amarok/src/metadata/trueaudio/ttafile.h b/amarok/src/metadata/trueaudio/ttafile.h new file mode 100644 index 00000000..2cb07856 --- /dev/null +++ b/amarok/src/metadata/trueaudio/ttafile.h @@ -0,0 +1,178 @@ +/*************************************************************************** + copyright : (C) 2006 by Lukáš Lalinský + email : lalinsky@gmail.com + + copyright : (C) 2004 by Allan Sandfeld Jensen + email : kde@carewolf.org + (original MPC implementation) + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301 USA * + ***************************************************************************/ + +#ifndef TAGLIB_TTAFILE_H +#define TAGLIB_TTAFILE_H + +#include "tfile.h" + +#include "ttaproperties.h" + +namespace TagLib { + + class Tag; + + namespace ID3v2 { class Tag; class FrameFactory; } + namespace ID3v1 { class Tag; } + + //! An implementation of TTA metadata + + /*! + * This is implementation of TTA metadata. + * + * This supports ID3v1 and ID3v2 tags as well as reading stream + * properties from the file. + */ + + namespace TTA { + + //! An implementation of TagLib::File with TTA specific methods + + /*! + * This implements and provides an interface for TTA files to the + * TagLib::Tag and TagLib::AudioProperties interfaces by way of implementing + * the abstract TagLib::File API as well as providing some additional + * information specific to TTA files. + */ + + class File : public TagLib::File + { + public: + /*! + * This set of flags is used for various operations and is suitable for + * being OR-ed together. + */ + enum TagTypes { + //! Empty set. Matches no tag types. + NoTags = 0x0000, + //! Matches ID3v1 tags. + ID3v1 = 0x0001, + //! Matches ID3v2 tags. + ID3v2 = 0x0002, + //! Matches all tag types. + AllTags = 0xffff + }; + + /*! + * Contructs an MPC file from \a file. If \a readProperties is true the + * file's audio properties will also be read using \a propertiesStyle. If + * false, \a propertiesStyle is ignored. + */ + File(const char *file, bool readProperties = true, + Properties::ReadStyle propertiesStyle = Properties::Average); + + /*! + * Contructs an MPC file from \a file. If \a readProperties is true the + * file's audio properties will also be read using \a propertiesStyle. If + * false, \a propertiesStyle is ignored. The frames will be created using + * \a frameFactory. + */ + File(const char *file, ID3v2::FrameFactory *frameFactory, + bool readProperties = true, + Properties::ReadStyle propertiesStyle = Properties::Average); + + /*! + * Destroys this instance of the File. + */ + virtual ~File(); + + /*! + * Returns the Tag for this file. This will be an APE tag, an ID3v1 tag + * or a combination of the two. + */ + virtual TagLib::Tag *tag() const; + + /*! + * Returns the MPC::Properties for this file. If no audio properties + * were read then this will return a null pointer. + */ + virtual Properties *audioProperties() const; + + /*! + * Set the ID3v2::FrameFactory to something other than the default. + * + * \see ID3v2FrameFactory + */ + void setID3v2FrameFactory(const ID3v2::FrameFactory *factory); + + /*! + * Saves the file. + */ + virtual bool save(); + + /*! + * Returns a pointer to the ID3v2 tag of the file. + * + * If \a create is false (the default) this will return a null pointer + * if there is no valid ID3v2 tag. If \a create is true it will create + * an ID3v1 tag if one does not exist. If there is already an APE tag, the + * new ID3v1 tag will be placed after it. + * + * \note The Tag is still owned by the TTA::File and should not be + * deleted by the user. It will be deleted when the file (object) is + * destroyed. + */ + ID3v1::Tag *ID3v1Tag(bool create = false); + + /*! + * Returns a pointer to the ID3v1 tag of the file. + * + * If \a create is false (the default) this will return a null pointer + * if there is no valid ID3v1 tag. If \a create is true it will create + * an ID3v1 tag if one does not exist. If there is already an APE tag, the + * new ID3v1 tag will be placed after it. + * + * \note The Tag is still owned by the TTA::File and should not be + * deleted by the user. It will be deleted when the file (object) is + * destroyed. + */ + ID3v2::Tag *ID3v2Tag(bool create = false); + + /*! + * This will remove the tags that match the OR-ed together TagTypes from the + * file. By default it removes all tags. + * + * \note This will also invalidate pointers to the tags + * as their memory will be freed. + * \note In order to make the removal permanent save() still needs to be called + */ + void remove(int tags = AllTags); + + private: + File(const File &); + File &operator=(const File &); + + void read(bool readProperties, Properties::ReadStyle propertiesStyle); + void scan(); + long findID3v1(); + long findID3v2(); + + class FilePrivate; + FilePrivate *d; + }; + } +} + +#endif diff --git a/amarok/src/metadata/trueaudio/ttaproperties.cpp b/amarok/src/metadata/trueaudio/ttaproperties.cpp new file mode 100644 index 00000000..c4f6a29b --- /dev/null +++ b/amarok/src/metadata/trueaudio/ttaproperties.cpp @@ -0,0 +1,134 @@ +/*************************************************************************** + copyright : (C) 2006 by Lukáš Lalinský + email : lalinsky@gmail.com + + copyright : (C) 2004 by Allan Sandfeld Jensen + email : kde@carewolf.org + (original MPC implementation) + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301 USA * + ***************************************************************************/ + +#include +#if 0 +#include +#endif +#include + +#include "ttaproperties.h" +#include "ttafile.h" + +using namespace TagLib; + +class TTA::Properties::PropertiesPrivate +{ +public: + PropertiesPrivate(const ByteVector &d, long length, ReadStyle s) : + data(d), + streamLength(length), + style(s), + version(0), + length(0), + bitrate(0), + sampleRate(0), + channels(0), + bitsPerSample(0) {} + + ByteVector data; + long streamLength; + ReadStyle style; + int version; + int length; + int bitrate; + int sampleRate; + int channels; + int bitsPerSample; +}; + +//////////////////////////////////////////////////////////////////////////////// +// public members +//////////////////////////////////////////////////////////////////////////////// + +TTA::Properties::Properties(const ByteVector &data, long streamLength, ReadStyle style) : AudioProperties(style) +{ + d = new PropertiesPrivate(data, streamLength, style); + read(); +} + +TTA::Properties::~Properties() +{ + delete d; +} + +int TTA::Properties::length() const +{ + return d->length; +} + +int TTA::Properties::bitrate() const +{ + return d->bitrate; +} + +int TTA::Properties::sampleRate() const +{ + return d->sampleRate; +} + +int TTA::Properties::bitsPerSample() const +{ + return d->bitsPerSample; +} + +int TTA::Properties::channels() const +{ + return d->channels; +} + +int TTA::Properties::ttaVersion() const +{ + return d->version; +} + +//////////////////////////////////////////////////////////////////////////////// +// private members +//////////////////////////////////////////////////////////////////////////////// + +void TTA::Properties::read() +{ + if(!d->data.startsWith("TTA")) + return; + + int pos = 3; + + d->version = d->data[pos] - '0'; + pos += 1 + 2; + + d->channels = d->data.mid(pos, 2).toShort(false); + pos += 2; + + d->bitsPerSample = d->data.mid(pos, 2).toShort(false); + pos += 2; + + d->sampleRate = d->data.mid(pos, 4).toUInt(false); + pos += 4; + + unsigned long samples = d->data.mid(pos, 4).toUInt(false); + d->length = samples / d->sampleRate; + + d->bitrate = d->length > 0 ? ((d->streamLength * 8L) / d->length) / 1000 : 0; +} diff --git a/amarok/src/metadata/trueaudio/ttaproperties.h b/amarok/src/metadata/trueaudio/ttaproperties.h new file mode 100644 index 00000000..e694e3df --- /dev/null +++ b/amarok/src/metadata/trueaudio/ttaproperties.h @@ -0,0 +1,86 @@ +/*************************************************************************** + copyright : (C) 2006 by Lukáš Lalinský + email : lalinsky@gmail.com + + copyright : (C) 2004 by Allan Sandfeld Jensen + email : kde@carewolf.org + (original MPC implementation) + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301 USA * + ***************************************************************************/ + +#ifndef TAGLIB_TTAPROPERTIES_H +#define TAGLIB_TTAPROPERTIES_H + +#include "audioproperties.h" + +namespace TagLib { + + namespace TTA { + + class File; + + static const uint HeaderSize = 18; + + //! An implementation of audio property reading for TTA + + /*! + * This reads the data from an TTA stream found in the AudioProperties + * API. + */ + + class Properties : public AudioProperties + { + public: + /*! + * Create an instance of TTA::Properties with the data read from the + * ByteVector \a data. + */ + Properties(const ByteVector &data, long streamLength, ReadStyle style = Average); + + /*! + * Destroys this TTA::Properties instance. + */ + virtual ~Properties(); + + // Reimplementations. + + virtual int length() const; + virtual int bitrate() const; + virtual int sampleRate() const; + virtual int channels() const; + + /*! + * Returns number of bits per sample. + */ + int bitsPerSample() const; + + /*! + * Returns the major version number. + */ + int ttaVersion() const; + + private: + void read(); + + class PropertiesPrivate; + PropertiesPrivate *d; + }; + } +} + +#endif diff --git a/amarok/src/metadata/wav/Makefile.am b/amarok/src/metadata/wav/Makefile.am new file mode 100644 index 00000000..9c152946 --- /dev/null +++ b/amarok/src/metadata/wav/Makefile.am @@ -0,0 +1,15 @@ +SUBDIRS = + +INCLUDES = $(all_includes) $(TAGLIB_CFLAGS) +METASOURCES = AUTO +libtagwav_la_LDFLAGS = $(all_libraries) +noinst_LTLIBRARIES = libtagwav.la + +libtagwav_la_SOURCES = wavproperties.cpp \ + wavfile.cpp \ + wavfiletyperesolver.cpp + +noinst_HEADERS = wavproperties.h \ + wavfile.h \ + wavfiletyperesolver.h + diff --git a/amarok/src/metadata/wav/wavfile.cpp b/amarok/src/metadata/wav/wavfile.cpp new file mode 100644 index 00000000..2ee99595 --- /dev/null +++ b/amarok/src/metadata/wav/wavfile.cpp @@ -0,0 +1,114 @@ +/*************************************************************************** + copyright : (C) 2006 by Martin Aumueller + email : aumuell@reserv.at + + copyright : (C) 2005 by Andy Leadbetter + email : andrew.leadbetter@gmail.com + (original mp4 implementation) + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301 USA * + ***************************************************************************/ + +#include +#include "wavfile.h" + +#include +#include +#include + +namespace TagLib { +//////////////////////////////////////////////////////////////////////////////// +// public members +//////////////////////////////////////////////////////////////////////////////// + +Wav::File::File(const char *file, + bool readProperties, + Properties::ReadStyle propertiesStyle, + FILE *fp) + : TagLib::File(file) + , wavtag( NULL ) + , properties( NULL ) +{ + + // debug ("Wav::File: create new file object."); + //debug ( file ); + + /** + * Create the Wav file. + */ + + if(fp) + wavfile = fp; + else + wavfile = fopen(file, "rb"); + + if( isOpen() ) + { + read(readProperties, propertiesStyle ); + } +} + +Wav::File::~File() +{ + if(wavfile) + fclose(wavfile); + delete properties; +} + +TagLib::Tag *Wav::File::tag() const +{ + return NULL; +} + +TagLib::Tag *Wav::File::getWavTag() const +{ + return NULL; +} + +Wav::Properties *Wav::File::audioProperties() const +{ + return properties; +} + +bool Wav::File::save() +{ + return false; +} + +bool Wav::File::isOpen() +{ + return wavfile != NULL; +} + +//////////////////////////////////////////////////////////////////////////////// +// private members +//////////////////////////////////////////////////////////////////////////////// + +void Wav::File::read(bool readProperties, Properties::ReadStyle propertiesStyle) +{ + properties = new Wav::Properties(propertiesStyle); + + if (wavfile != NULL) { + if(readProperties) + { + // Parse bitrate etc. + properties->readWavProperties( wavfile ); + } + } +} + +} diff --git a/amarok/src/metadata/wav/wavfile.h b/amarok/src/metadata/wav/wavfile.h new file mode 100644 index 00000000..e47fdfbd --- /dev/null +++ b/amarok/src/metadata/wav/wavfile.h @@ -0,0 +1,92 @@ +/*************************************************************************** + copyright : (C) 2006 by Martin Aumueller + email : aumuell@reserv.at + + copyright : (C) 2005 by Andy Leadbetter + email : andrew.leadbetter@gmail.com + (original mp4 implementation) + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301 USA * + ***************************************************************************/ + +#ifndef TAGLIB_WAVFILE_H +#define TAGLIB_WAVFILE_H + +#include +#include "wavproperties.h" + +namespace TagLib { + + namespace Wav { + + class Tag; + + class File : public TagLib::File + { + public: + /*! + * Contructs a Wav file from \a file. If \a readProperties is true the + * file's audio properties will also be read using \a propertiesStyle. If + * false, \a propertiesStyle is ignored. + */ + File(const char *file, bool readProperties = true, + Properties::ReadStyle propertiesStyle = Properties::Average, + FILE *fp=NULL); + + /*! + * Destroys this instance of the File. + */ + virtual ~File(); + + + virtual TagLib::Tag *tag() const; + + /*! + * Returns the Wav::Properties for this file. If no audio properties + * were read then this will return a null pointer. + */ + virtual Wav::Properties *audioProperties() const; + + /*! + * Save the file. + * This is the same as calling save(AllTags); + * + * \note As of now, saving Wav tags is not supported. + */ + virtual bool save(); + + void read(bool readProperties, Properties::ReadStyle propertiesStyle); + + TagLib::Tag *getWavTag() const; + + bool isWavFile() const; + + protected: + File(const File &); + File &operator=(const File &); + bool isOpen(); + + + TagLib::Tag *wavtag; + Wav::Properties *properties; + + FILE *wavfile; + }; + } +} + +#endif diff --git a/amarok/src/metadata/wav/wavfiletyperesolver.cpp b/amarok/src/metadata/wav/wavfiletyperesolver.cpp new file mode 100644 index 00000000..2fbd5e9b --- /dev/null +++ b/amarok/src/metadata/wav/wavfiletyperesolver.cpp @@ -0,0 +1,43 @@ +/*************************************************************************** + copyright : (C) 2006 by Martin Aumueller + email : aumuell@reserv.at + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301 USA * + ***************************************************************************/ + +#include +#include "wavfiletyperesolver.h" +#include "wavfile.h" + +#include + +TagLib::File *WavFileTypeResolver::createFile(const char *fileName, + bool readProperties, + TagLib::AudioProperties::ReadStyle propertiesStyle) const +{ + const char *ext = strrchr(fileName, '.'); + if(ext && !strcasecmp(ext, ".wav")) + { + FILE *fp = fopen(fileName, "rb"); + if(!fp) + return 0; + + return new TagLib::Wav::File(fileName, readProperties, propertiesStyle, fp); + } + + return 0; +} diff --git a/amarok/src/metadata/wav/wavfiletyperesolver.h b/amarok/src/metadata/wav/wavfiletyperesolver.h new file mode 100644 index 00000000..de818c99 --- /dev/null +++ b/amarok/src/metadata/wav/wavfiletyperesolver.h @@ -0,0 +1,36 @@ +/*************************************************************************** + copyright : (C) 2006 by Martin Aumueller + email : aumuell@reserv.at + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301 USA * + ***************************************************************************/ + +#ifndef TAGLIB_WAVFILETYPERESOLVER_H +#define TAGLIB_WAVFILETYPERESOLVER_H + +#include +#include + + +class WavFileTypeResolver : public TagLib::FileRef::FileTypeResolver +{ + TagLib::File *createFile(const char *fileName, + bool readAudioProperties, + TagLib::AudioProperties::ReadStyle audioPropertiesStyle) const; +}; + +#endif diff --git a/amarok/src/metadata/wav/wavproperties.cpp b/amarok/src/metadata/wav/wavproperties.cpp new file mode 100644 index 00000000..20ba1ab1 --- /dev/null +++ b/amarok/src/metadata/wav/wavproperties.cpp @@ -0,0 +1,108 @@ +/*************************************************************************** + copyright : (C) 2006 by Martin Aumueller + email : aumuell@reserv.at + + copyright : (C) 2005 by Andy Leadbetter + email : andrew.leadbetter@gmail.com + (original mp4 implementation) + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301 USA * + ***************************************************************************/ + +#include + +#include "wavproperties.h" + +#include + +#include "wavfile.h" + +#include // ntohl + +using namespace TagLib; + +struct WavHeader +{ + uint32_t riff_id; + uint32_t riff_size; + uint32_t wave_id; + uint32_t format_id; + uint32_t format_size; + uint16_t format_tag; + uint16_t num_channels; + uint32_t num_samples_per_sec; + uint32_t num_avg_bytes_per_sec; + uint16_t num_block_align; + uint16_t bits_per_sample; + uint32_t data_id; + uint32_t num_data_bytes; +}; + + +//////////////////////////////////////////////////////////////////////////////// +// public members +//////////////////////////////////////////////////////////////////////////////// + +Wav::Properties::Properties(Properties::ReadStyle style) : AudioProperties(style) +{ + m_length = 0; + m_bitrate = 0; + m_sampleRate = 0; + m_channels = 0; +} + +Wav::Properties::~Properties() +{ +} + +int Wav::Properties::length() const +{ + return m_length; +} + +int Wav::Properties::bitrate() const +{ + return m_bitrate; +} + +int Wav::Properties::sampleRate() const +{ + return m_sampleRate; +} + +int Wav::Properties::channels() const +{ + return m_channels; +} + +#define swap16(x) ((((x)&0xff00)>>8) | (((x)&0x00ff)<<8)) +#define swap32(x) ((swap16((x)&0x0000ffff)<<16) | swap16(((x)&0xffff0000)>>16)) + +void Wav::Properties::readWavProperties( FILE *fp ) +{ + fseek(fp, 0, SEEK_SET ); + WavHeader header; + if( fread(&header, sizeof(header), 1, fp) != 1 ) + { + return; + } + + m_channels = ntohs(swap16(header.num_channels)); + m_sampleRate = ntohl(swap32(header.num_samples_per_sec)); + m_bitrate = ntohl(swap32(header.num_avg_bytes_per_sec)) * 8 / 1000; + m_length = ntohl(swap32(header.num_data_bytes))/ntohl(swap32(header.num_avg_bytes_per_sec)); +} diff --git a/amarok/src/metadata/wav/wavproperties.h b/amarok/src/metadata/wav/wavproperties.h new file mode 100644 index 00000000..a02e7346 --- /dev/null +++ b/amarok/src/metadata/wav/wavproperties.h @@ -0,0 +1,85 @@ +/*************************************************************************** + copyright : (C) 2006 by Martin Aumueller + email : aumuell@reserv.at + + copyright : (C) 2005 by Andy Leadbetter + email : andrew.leadbetter@gmail.com + (original mp4 implementation) + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301 USA * + ***************************************************************************/ + +#ifndef TAGLIB_WAVPROPERTIES_H +#define TAGLIB_WAVPROPERTIES_H + +#include + +#include +#include + +namespace TagLib { + + namespace Wav { + + class File; + + /*! + * This reads the data from a Wav stream to support the + * AudioProperties API. + */ + + class Properties : public AudioProperties + { + public: + /*! + * Initialize this structure + */ + Properties(Properties::ReadStyle style); + + /*! + * Destroys this Wav Properties instance. + */ + virtual ~Properties(); + + // Reimplementations. + + virtual int length() const; + virtual int bitrate() const; + virtual int sampleRate() const; + virtual int channels() const; + + void readWavProperties(FILE *file); + + + private: + void readAudioTrackProperties(FILE *file); + friend class Wav::File; + + int m_length; + int m_bitrate; + int m_sampleRate; + int m_channels; + + Properties(const Properties &); + Properties &operator=(const Properties &); + + void read(); + }; + } +} + +#endif diff --git a/amarok/src/metadata/wavpack/Makefile.am b/amarok/src/metadata/wavpack/Makefile.am new file mode 100644 index 00000000..8a4c2d7a --- /dev/null +++ b/amarok/src/metadata/wavpack/Makefile.am @@ -0,0 +1,16 @@ +SUBDIRS = + +INCLUDES = $(all_includes) $(TAGLIB_CFLAGS) +METASOURCES = AUTO +libtagwavpack_la_LDFLAGS = $(all_libraries) +noinst_LTLIBRARIES = libtagwavpack.la + +libtagwavpack_la_SOURCES = \ + wvfile.cpp \ + wvproperties.cpp \ + taglib_wavpackfiletyperesolver.cpp + +noinst_HEADERS = wvfile.h \ + wvproperties.h \ + taglib_wavpackfiletyperesolver.h + diff --git a/amarok/src/metadata/wavpack/combinedtag.h b/amarok/src/metadata/wavpack/combinedtag.h new file mode 100644 index 00000000..7d530d29 --- /dev/null +++ b/amarok/src/metadata/wavpack/combinedtag.h @@ -0,0 +1,171 @@ +/*************************************************************************** + copyright : (C) 2004 by Allan Sandfeld Jensen + email : kde@carewolf.org + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301 USA * + ***************************************************************************/ + +#ifndef DO_NOT_DOCUMENT // Tell Doxygen not to document this header + +#ifndef TAGLIB_COMBINEDTAG_H +#define TAGLIB_COMBINEDTAG_H + +//////////////////////////////////////////////////////////////////////////////// +// Note that this header is not installed. +//////////////////////////////////////////////////////////////////////////////// + +#include + +namespace TagLib { + + /*! + * A union of two TagLib::Tags. + */ + class CombinedTag : public TagLib::Tag + { + public: + CombinedTag(Tag *tag1 = 0, Tag *tag2 = 0) + : TagLib::Tag(), + tag1(tag1), tag2(tag2) {} + + virtual String title() const { + if(tag1 && !tag1->title().isEmpty()) + return tag1->title(); + + if(tag2) + return tag2->title(); + + return String::null; + } + + virtual String artist() const { + if(tag1 && !tag1->artist().isEmpty()) + return tag1->artist(); + + if(tag2) + return tag2->artist(); + + return String::null; + } + + virtual String album() const { + if(tag1 && !tag1->album().isEmpty()) + return tag1->album(); + + if(tag2) + return tag2->album(); + + return String::null; + } + + virtual String comment() const { + if(tag1 && !tag1->comment().isEmpty()) + return tag1->comment(); + + if(tag2) + return tag2->comment(); + + return String::null; + } + + virtual String genre() const { + if(tag1 && !tag1->genre().isEmpty()) + return tag1->genre(); + + if(tag2) + return tag2->genre(); + + return String::null; + } + + virtual uint year() const { + if(tag1 && tag1->year() > 0) + return tag1->year(); + + if(tag2) + return tag2->year(); + + return 0; + } + + virtual uint track() const { + if(tag1 && tag1->track() > 0) + return tag1->track(); + + if(tag2) + return tag2->track(); + + return 0; + } + + virtual void setTitle(const String &s) { + if(tag1) + tag1->setTitle(s); + if(tag2) + tag2->setTitle(s); + } + + virtual void setArtist(const String &s) { + if(tag1) + tag1->setArtist(s); + if(tag2) + tag2->setArtist(s); + } + + virtual void setAlbum(const String &s) { + if(tag1) + tag1->setAlbum(s); + if(tag2) + tag2->setAlbum(s); + } + + virtual void setComment(const String &s) { + if(tag1) + tag1->setComment(s); + if(tag2) + tag2->setComment(s); + } + + virtual void setGenre(const String &s) { + if(tag1) + tag1->setGenre(s); + if(tag2) + tag2->setGenre(s); + } + + virtual void setYear(uint i) { + if(tag1) + tag1->setYear(i); + if(tag2) + tag2->setYear(i); + } + + virtual void setTrack(uint i) { + if(tag1) + tag1->setTrack(i); + if(tag2) + tag2->setTrack(i); + } + + private: + Tag *tag1; + Tag *tag2; + }; +} + +#endif +#endif diff --git a/amarok/src/metadata/wavpack/taglib_wavpackfiletyperesolver.cpp b/amarok/src/metadata/wavpack/taglib_wavpackfiletyperesolver.cpp new file mode 100644 index 00000000..fe289a27 --- /dev/null +++ b/amarok/src/metadata/wavpack/taglib_wavpackfiletyperesolver.cpp @@ -0,0 +1,44 @@ +/*************************************************************************** + copyright : (C) 2006 by Martin Aumueller + email : aumuell@reserv.at + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301 USA * + ***************************************************************************/ + +#include "taglib_wavpackfiletyperesolver.h" +#include "wvfile.h" + +#include + +TagLib::File *WavPackFileTypeResolver::createFile(const char *fileName, + bool readProperties, + TagLib::AudioProperties::ReadStyle propertiesStyle) const +{ + const char *ext = strrchr(fileName, '.'); + if(ext && !strcasecmp(ext, ".wv")) + { + TagLib::WavPack::File *f = new TagLib::WavPack::File(fileName, readProperties, propertiesStyle); + if(f->isValid()) + return f; + else + { + delete f; + } + } + + return 0; +} diff --git a/amarok/src/metadata/wavpack/taglib_wavpackfiletyperesolver.h b/amarok/src/metadata/wavpack/taglib_wavpackfiletyperesolver.h new file mode 100644 index 00000000..b9d4500a --- /dev/null +++ b/amarok/src/metadata/wavpack/taglib_wavpackfiletyperesolver.h @@ -0,0 +1,36 @@ +/*************************************************************************** + copyright : (C) 2006 by Martin Aumueller + email : aumuell@reserv.at + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301 USA * + ***************************************************************************/ + +#ifndef TAGLIB_WAVPACKFILETYPERESOLVER_H +#define TAGLIB_WAVPACKFILETYPERESOLVER_H + +#include +#include + + +class WavPackFileTypeResolver : public TagLib::FileRef::FileTypeResolver +{ + TagLib::File *createFile(const char *fileName, + bool readAudioProperties, + TagLib::AudioProperties::ReadStyle audioPropertiesStyle) const; +}; + +#endif diff --git a/amarok/src/metadata/wavpack/wvfile.cpp b/amarok/src/metadata/wavpack/wvfile.cpp new file mode 100644 index 00000000..3890bc9a --- /dev/null +++ b/amarok/src/metadata/wavpack/wvfile.cpp @@ -0,0 +1,311 @@ +/*************************************************************************** + copyright : (C) 2006 by Lukáš Lalinský + email : lalinsky@gmail.com + + copyright : (C) 2004 by Allan Sandfeld Jensen + email : kde@carewolf.org + (original MPC implementation) + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301 USA * + ***************************************************************************/ + +#include +#include +#if 0 +#include +#endif + +#include "wvfile.h" +#include "id3v1tag.h" +#include "id3v2header.h" +#include "apetag.h" +#include "apefooter.h" +#include "combinedtag.h" + +using namespace TagLib; + +class WavPack::File::FilePrivate +{ +public: + FilePrivate() : + APETag(0), + APELocation(-1), + APESize(0), + ID3v1Tag(0), + ID3v1Location(-1), + tag(0), + properties(0), + scanned(false), + hasAPE(false), + hasID3v1(false) {} + + ~FilePrivate() + { + if (tag != ID3v1Tag && tag != APETag) delete tag; + delete ID3v1Tag; + delete APETag; + delete properties; + } + + APE::Tag *APETag; + long APELocation; + uint APESize; + + ID3v1::Tag *ID3v1Tag; + long ID3v1Location; + + Tag *tag; + + Properties *properties; + bool scanned; + + // These indicate whether the file *on disk* has these tags, not if + // this data structure does. This is used in computing offsets. + + bool hasAPE; + bool hasID3v1; +}; + +//////////////////////////////////////////////////////////////////////////////// +// public members +//////////////////////////////////////////////////////////////////////////////// + +WavPack::File::File(const char *file, bool readProperties, + Properties::ReadStyle propertiesStyle) : TagLib::File(file) +{ + d = new FilePrivate; + read(readProperties, propertiesStyle); +} + +WavPack::File::~File() +{ + delete d; +} + +TagLib::Tag *WavPack::File::tag() const +{ + return d->tag; +} + +WavPack::Properties *WavPack::File::audioProperties() const +{ + return d->properties; +} + +bool WavPack::File::save() +{ + if(readOnly()) { +#if 0 + debug("WavPack::File::save() -- File is read only."); +#endif + return false; + } + + // Update ID3v1 tag + + if(d->ID3v1Tag) { + if(d->hasID3v1) { + seek(d->ID3v1Location); + writeBlock(d->ID3v1Tag->render()); + } + else { + seek(0, End); + d->ID3v1Location = tell(); + writeBlock(d->ID3v1Tag->render()); + d->hasID3v1 = true; + } + } else + if(d->hasID3v1) { + removeBlock(d->ID3v1Location, 128); + d->hasID3v1 = false; + if(d->hasAPE) { + if(d->APELocation > d->ID3v1Location) + d->APELocation -= 128; + } + } + + // Update APE tag + + if(d->APETag) { + if(d->hasAPE) + insert(d->APETag->render(), d->APELocation, d->APESize); + else { + if(d->hasID3v1) { + insert(d->APETag->render(), d->ID3v1Location, 0); + d->APESize = d->APETag->footer()->completeTagSize(); + d->hasAPE = true; + d->APELocation = d->ID3v1Location; + d->ID3v1Location += d->APESize; + } + else { + seek(0, End); + d->APELocation = tell(); + writeBlock(d->APETag->render()); + d->APESize = d->APETag->footer()->completeTagSize(); + d->hasAPE = true; + } + } + } + else + if(d->hasAPE) { + removeBlock(d->APELocation, d->APESize); + d->hasAPE = false; + if(d->hasID3v1) { + if (d->ID3v1Location > d->APELocation) + d->ID3v1Location -= d->APESize; + } + } + + return true; +} + +ID3v1::Tag *WavPack::File::ID3v1Tag(bool create) +{ + if(!create || d->ID3v1Tag) + return d->ID3v1Tag; + + // no ID3v1 tag exists and we've been asked to create one + + d->ID3v1Tag = new ID3v1::Tag; + + if(d->APETag) + d->tag = new CombinedTag(d->APETag, d->ID3v1Tag); + else + d->tag = d->ID3v1Tag; + + return d->ID3v1Tag; +} + +APE::Tag *WavPack::File::APETag(bool create) +{ + if(!create || d->APETag) + return d->APETag; + + // no APE tag exists and we've been asked to create one + + d->APETag = new APE::Tag; + + if(d->ID3v1Tag) + d->tag = new CombinedTag(d->APETag, d->ID3v1Tag); + else + d->tag = d->APETag; + + return d->APETag; +} + +void WavPack::File::remove(int tags) +{ + if(tags & ID3v1) { + delete d->ID3v1Tag; + d->ID3v1Tag = 0; + + if(d->APETag) + d->tag = d->APETag; + else + d->tag = d->APETag = new APE::Tag; + } + + if(tags & APE) { + delete d->APETag; + d->APETag = 0; + + if(d->ID3v1Tag) + d->tag = d->ID3v1Tag; + else + d->tag = d->APETag = new APE::Tag; + } +} + +//////////////////////////////////////////////////////////////////////////////// +// private members +//////////////////////////////////////////////////////////////////////////////// + +void WavPack::File::read(bool readProperties, Properties::ReadStyle /* propertiesStyle */) +{ + // Look for an ID3v1 tag + + d->ID3v1Location = findID3v1(); + + if(d->ID3v1Location >= 0) { + d->ID3v1Tag = new ID3v1::Tag(this, d->ID3v1Location); + d->hasID3v1 = true; + } + + // Look for an APE tag + + d->APELocation = findAPE(); + + if(d->APELocation >= 0) { + d->APETag = new APE::Tag(this, d->APELocation); + d->APESize = d->APETag->footer()->completeTagSize(); + d->APELocation = d->APELocation + d->APETag->footer()->size() - d->APESize; + d->hasAPE = true; + } + + if(d->hasID3v1 && d->hasAPE) + d->tag = new CombinedTag(d->APETag, d->ID3v1Tag); + else { + if(d->hasID3v1) + d->tag = d->ID3v1Tag; + else { + if(d->hasAPE) + d->tag = d->APETag; + else + d->tag = d->APETag = new APE::Tag; + } + } + + // Look for WavPack audio properties + + if(readProperties) { + seek(0); + d->properties = new Properties(readBlock(WavPack::HeaderSize), + length() - d->APESize); + } +} + +long WavPack::File::findAPE() +{ + if(!isValid()) + return -1; + + if(d->hasID3v1) + seek(-160, End); + else + seek(-32, End); + + long p = tell(); + + if(readBlock(8) == APE::Tag::fileIdentifier()) + return p; + + return -1; +} + +long WavPack::File::findID3v1() +{ + if(!isValid()) + return -1; + + seek(-128, End); + long p = tell(); + + if(readBlock(3) == ID3v1::Tag::fileIdentifier()) + return p; + + return -1; +} diff --git a/amarok/src/metadata/wavpack/wvfile.h b/amarok/src/metadata/wavpack/wvfile.h new file mode 100644 index 00000000..6c57f6d7 --- /dev/null +++ b/amarok/src/metadata/wavpack/wvfile.h @@ -0,0 +1,160 @@ +/*************************************************************************** + copyright : (C) 2006 by Lukáš Lalinský + email : lalinsky@gmail.com + + copyright : (C) 2004 by Allan Sandfeld Jensen + email : kde@carewolf.org + (original MPC implementation) + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301 USA * + ***************************************************************************/ + +#ifndef TAGLIB_WVFILE_H +#define TAGLIB_WVFILE_H + +#include "tfile.h" + +#include "wvproperties.h" + +namespace TagLib { + + class Tag; + + namespace ID3v1 { class Tag; } + namespace APE { class Tag; } + + //! An implementation of WavPack metadata + + /*! + * This is implementation of WavPack metadata. + * + * This supports ID3v1 and APE (v1 and v2) style comments as well as reading stream + * properties from the file. + */ + + namespace WavPack { + + //! An implementation of TagLib::File with WavPack specific methods + + /*! + * This implements and provides an interface for WavPack files to the + * TagLib::Tag and TagLib::AudioProperties interfaces by way of implementing + * the abstract TagLib::File API as well as providing some additional + * information specific to WavPack files. + */ + + class File : public TagLib::File + { + public: + /*! + * This set of flags is used for various operations and is suitable for + * being OR-ed together. + */ + enum TagTypes { + //! Empty set. Matches no tag types. + NoTags = 0x0000, + //! Matches ID3v1 tags. + ID3v1 = 0x0001, + //! Matches APE tags. + APE = 0x0002, + //! Matches all tag types. + AllTags = 0xffff + }; + + /*! + * Contructs an WavPack file from \a file. If \a readProperties is true the + * file's audio properties will also be read using \a propertiesStyle. If + * false, \a propertiesStyle is ignored. + */ + File(const char *file, bool readProperties = true, + Properties::ReadStyle propertiesStyle = Properties::Average); + + /*! + * Destroys this instance of the File. + */ + virtual ~File(); + + /*! + * Returns the Tag for this file. This will be an APE tag, an ID3v1 tag + * or a combination of the two. + */ + virtual TagLib::Tag *tag() const; + + /*! + * Returns the MPC::Properties for this file. If no audio properties + * were read then this will return a null pointer. + */ + virtual Properties *audioProperties() const; + + /*! + * Saves the file. + */ + virtual bool save(); + + /*! + * Returns a pointer to the ID3v1 tag of the file. + * + * If \a create is false (the default) this will return a null pointer + * if there is no valid ID3v1 tag. If \a create is true it will create + * an ID3v1 tag if one does not exist. If there is already an APE tag, the + * new ID3v1 tag will be placed after it. + * + * \note The Tag is still owned by the APE::File and should not be + * deleted by the user. It will be deleted when the file (object) is + * destroyed. + */ + ID3v1::Tag *ID3v1Tag(bool create = false); + + /*! + * Returns a pointer to the APE tag of the file. + * + * If \a create is false (the default) this will return a null pointer + * if there is no valid APE tag. If \a create is true it will create + * a APE tag if one does not exist. + * + * \note The Tag is still owned by the APE::File and should not be + * deleted by the user. It will be deleted when the file (object) is + * destroyed. + */ + APE::Tag *APETag(bool create = false); + + /*! + * This will remove the tags that match the OR-ed together TagTypes from the + * file. By default it removes all tags. + * + * \note This will also invalidate pointers to the tags + * as their memory will be freed. + * \note In order to make the removal permanent save() still needs to be called + */ + void remove(int tags = AllTags); + + private: + File(const File &); + File &operator=(const File &); + + void read(bool readProperties, Properties::ReadStyle propertiesStyle); + void scan(); + long findID3v1(); + long findAPE(); + + class FilePrivate; + FilePrivate *d; + }; + } +} + +#endif diff --git a/amarok/src/metadata/wavpack/wvproperties.cpp b/amarok/src/metadata/wavpack/wvproperties.cpp new file mode 100644 index 00000000..376824d9 --- /dev/null +++ b/amarok/src/metadata/wavpack/wvproperties.cpp @@ -0,0 +1,141 @@ +/*************************************************************************** + copyright : (C) 2006 by Lukáš Lalinský + email : lalinsky@gmail.com + + copyright : (C) 2004 by Allan Sandfeld Jensen + email : kde@carewolf.org + (original MPC implementation) + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301 USA * + ***************************************************************************/ + +#include +#if 0 +#include +#endif +#include + +#include "wvproperties.h" +#include "wvfile.h" + +using namespace TagLib; + +class WavPack::Properties::PropertiesPrivate +{ +public: + PropertiesPrivate(const ByteVector &d, long length, ReadStyle s) : + data(d), + streamLength(length), + style(s), + length(0), + bitrate(0), + sampleRate(0), + channels(0), + version(0), + bitsPerSample(0) {} + + ByteVector data; + long streamLength; + ReadStyle style; + int length; + int bitrate; + int sampleRate; + int channels; + int version; + int bitsPerSample; +}; + +//////////////////////////////////////////////////////////////////////////////// +// public members +//////////////////////////////////////////////////////////////////////////////// + +WavPack::Properties::Properties(const ByteVector &data, long streamLength, ReadStyle style) : AudioProperties(style) +{ + d = new PropertiesPrivate(data, streamLength, style); + read(); +} + +WavPack::Properties::~Properties() +{ + delete d; +} + +int WavPack::Properties::length() const +{ + return d->length; +} + +int WavPack::Properties::bitrate() const +{ + return d->bitrate; +} + +int WavPack::Properties::sampleRate() const +{ + return d->sampleRate; +} + +int WavPack::Properties::channels() const +{ + return d->channels; +} + +int WavPack::Properties::version() const +{ + return d->version; +} + +int WavPack::Properties::bitsPerSample() const +{ + return d->bitsPerSample; +} + +//////////////////////////////////////////////////////////////////////////////// +// private members +//////////////////////////////////////////////////////////////////////////////// + +static const unsigned int sample_rates[] = { 6000, 8000, 9600, 11025, 12000, + 16000, 22050, 24000, 32000, 44100, 48000, 64000, 88200, 96000, 192000 }; + +#define BYTES_STORED 3 +#define MONO_FLAG 4 + +#define SHIFT_LSB 13 +#define SHIFT_MASK (0x1fL << SHIFT_LSB) + +#define SRATE_LSB 23 +#define SRATE_MASK (0xfL << SRATE_LSB) + +void WavPack::Properties::read() +{ + if(!d->data.startsWith("wvpk")) + return; + + d->version = d->data.mid(8, 2).toShort(false); + + unsigned int flags = d->data.mid(24, 4).toUInt(false); + d->bitsPerSample = ((flags & BYTES_STORED) + 1) * 8 - + ((flags & SHIFT_MASK) >> SHIFT_LSB); + d->sampleRate = sample_rates[(flags & SRATE_MASK) >> SRATE_LSB]; + d->channels = (flags & MONO_FLAG) ? 1 : 2; + + unsigned int samples = d->data.mid(12, 4).toUInt(false); + d->length = d->sampleRate > 0 ? (samples + (d->sampleRate / 2)) / d->sampleRate : 0; + + d->bitrate = d->length > 0 ? ((d->streamLength * 8L) / d->length) / 1000 : 0; +} + diff --git a/amarok/src/metadata/wavpack/wvproperties.h b/amarok/src/metadata/wavpack/wvproperties.h new file mode 100644 index 00000000..b615adfa --- /dev/null +++ b/amarok/src/metadata/wavpack/wvproperties.h @@ -0,0 +1,86 @@ +/*************************************************************************** + copyright : (C) 2006 by Lukáš Lalinský + email : lalinsky@gmail.com + + copyright : (C) 2004 by Allan Sandfeld Jensen + email : kde@carewolf.org + (original MPC implementation) + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301 USA * + ***************************************************************************/ + +#ifndef TAGLIB_WVPROPERTIES_H +#define TAGLIB_WVPROPERTIES_H + +#include "audioproperties.h" + +namespace TagLib { + + namespace WavPack { + + class File; + + static const uint HeaderSize = 32; + + //! An implementation of audio property reading for WavPack + + /*! + * This reads the data from an WavPack stream found in the AudioProperties + * API. + */ + + class Properties : public AudioProperties + { + public: + /*! + * Create an instance of WavPack::Properties with the data read from the + * ByteVector \a data. + */ + Properties(const ByteVector &data, long streamLength, ReadStyle style = Average); + + /*! + * Destroys this WavPack::Properties instance. + */ + virtual ~Properties(); + + // Reimplementations. + + virtual int length() const; + virtual int bitrate() const; + virtual int sampleRate() const; + virtual int channels() const; + + /*! + * Returns number of bits per sample. + */ + int bitsPerSample() const; + + /*! + * Returns WavPack version. + */ + int version() const; + + private: + void read(); + + class PropertiesPrivate; + PropertiesPrivate *d; + }; + } +} + +#endif diff --git a/amarok/src/moodbar.cpp b/amarok/src/moodbar.cpp new file mode 100644 index 00000000..16cd0872 --- /dev/null +++ b/amarok/src/moodbar.cpp @@ -0,0 +1,1386 @@ +/*************************************************************************** + moodbar.cpp - description + ------------------- + begin : 6th Nov 2005 + copyright : (C) 2006 by Joseph Rabinoff + copyright : (C) 2005 by Gav Wood + email : bobqwatson@yahoo.com +***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +// Although the current incarnation of moodbar.cpp shares bits and +// pieces of code with Gav Wood's original, it has been completely +// rewritten -- the only code I kept was purely algorithmic. Also +// lots of Moodbar-related functionality has been moved from other +// places to here (all of it really). + +// The Moodbar is used by small amounts of code in playlistitem.cpp +// and sliderwidget.cpp. There are also trivial amounts of support +// code in other places. + +// Moodbar usage +// ------------- +// +// The Moodbar is part of the track's metadata, so it's held by a +// MetaBundle. The actual Moodbar object is only used to draw a +// QPixmap, which it does efficiently -- it caches a pixmap of the +// last thing it drew, and just copies that pixmap if the dimensions +// have not changed. To use the moodbar, one just needs a few lines of +// code, such as the following, based on PrettySlider: +// +// void MyClass::MyClass( void ) +// { +// // This only needs to be done once! +// connect( &m_bundle.moodbar(), SIGNAL( jobEvent( int ) ), +// SLOT( newMoodData( int ) ) ); +// } +// +// void MyClass::newMetaBundle( const MetaBundle &b ) +// { +// m_bundle = b; +// +// if( !m_bundle.moodbar().dataExists() ) +// m_bundle.moodbar().load(); +// else +// update(); +// } +// +// void MyClass::draw( void ) +// { +// QPixmap toDraw; +// if( m_bundle.moodbar().dataExists() ) +// toDraw = m_bundle.moodbar().draw( width(), height() ); +// // else draw something else... +// } +// +// void MyClass::newMoodData( int newState ) +// { +// if( newState == Moodbar::JobStateSucceeded ) +// update(); +// } +// +// Explanation: +// +// * In the constructor we listen for the jobEvent() signal from the +// Moodbar. The Moodbar emits this signal when an analyzer process +// has started or completed and it has loaded its moodbar data. +// (This connection will exist for the lifetime of the instance of +// MyClass and hence only needs to be created once.) +// +// * Whenever the MetaBundle associated with this instance of MyClass +// is changed, so does the moodbar, so we should reload it. The +// dataExists() method is meant to return whether the mood has +// already been analyzed for that track (it will always return false +// for streaming bundles and the like). If it returns true then the +// moodbar has already loaded its data, and can draw it. +// +// * Otherwise we run the Moodbar's load() method. This method may +// be called many times; it will only actually do anything the first +// time it's called (unless the moodbar is reset()). Hence it's +// totally reasonable to call load() in the draw() method too; this +// is in fact what the PlaylistItem does. When load() has completed, +// it emits a jobEvent() signal. +// +// * Note that jobEvent() will also be emitted if there is an error +// in analyzing or loading the data, with a state indicating failure. +// In this case, subsequent calls to dataExists() will still return +// false, and subsequent calls to load() will do nothing. +// + +// Implementation +// -------------- +// +// There are two new classes, namely the Moodbar (a member of +// MetaBundle), and the MoodServer. The former is the only public +// class. In a nutshell, the Moodbar is responsible for reading +// and drawing mood data, and the MoodServer is in charge of +// queueing analyzer jobs and notifying interested Moodbar's when +// their job is done. + + +// The Moodbar class -- +// +// The only public interface to the moodbar system. An unloaded +// Moodbar is meant to have a very small footprint, since there are +// lots of MetaBundle's floating around that aren't going to be +// displayed. Most of the data in loaded Moodbars is implicitly +// shared anyway (unless you call detach()), so it's reasonable to +// pass them around by value. +// +// Much care has been taken to absolutely minimize the amount of time +// a Moodbar is listening for a signal. The only signal a Moodbar +// will connect to is MoodServer::jobEvent; this connection is made +// when MoodServer::queueJob() is called, and is disconnected in +// slotJobEvent(). The reason for this care is because MetaBundle's, +// and hence Moodbar's, are copied around and passed-by-value all the +// time, so I wanted to reduce overhead; also QObject::disconnect() is +// not reentrant (from what I understand), so we don't want that being +// called every time a Moodbar is destroyed! For the same reason, the +// PlaylistItem does not listen for the jobEvent() signal; instead it +// reimplements the MetaBundle::moodbarJobEvent() virtual method. +// +// Again for this reason, the individual Moodbar's don't listen for +// the App::moodbarPrefs() signal (which is emitted every time the +// configuration is changed); thus Moodbar's aren't automatically +// updated when the AlterMood variable is changed, for instance. This +// is a small annoyance, as the owner of the Moodbar has to listen for +// that signal and call reset(). This happens in sliderwidget.cpp and +// playlist.cpp. +// +// A moodbar is always in one of the following states: +// +// Unloaded: A newly-created (or newly reset()) Moodbar is in this +// state. The Moodbar remains in this state until +// dataExists() or load() is called. Note that load() +// will return immediately unless the state is Unloaded. +// CantLoad: For some reason we know that we'll never be able to +// load the Moodbar, for instance if the parent bundle +// describes a streaming source. Most methods will return +// immediately in this state. +// JobQueued: At some point load() was called, so we queued a job with +// the MoodServer which hasn't started yet. In this state, +// ~Moodbar(), reset(), etc. knows to dequeue jobs and +// disconnect signals. +// JobRunning: Our analyzer job is actually running. The moodbar behaves +// basically the same as in the JobQueued state; this state +// exists so the PlaylistItem knows the difference. +// JobFailed: The MoodServer has tried to run our job (or gave up before +// trying), and came up empty. This state behaves basically +// the same as CantLoad. +// Loaded: This is the only state in which draw() will work. +// +// +// Note that nothing is done to load until dataExists() is called; this +// is because there may very well be MetaBundle's floating around that +// aren't displayed in the GUI. +// +// Important members: +// m_bundle: link to the parent bundle +// m_data: if we are loaded, this is the contents of the .mood file +// m_pixmap: the last time draw() was called, we cached what we drew +// here +// m_url: cache the URL of our queued job for de-queueing +// m_state: our current state +// m_mutex: lock for the entire object. The Moodbar object should +// be entirely reentrant (but see below), so most methods lock the +// object before doing anything. (Of course the calling code has to +// be threadsafe for this to mean anything.) +// +// Important methods: +// +// dataExists(): When this is called, we check if the .mood file +// exists for our bundle. If so, we load the corresponding file, +// and if all goes well, return true. If our bundle is a streaming +// track, or is otherwise unloadable, always return false. +// +// load(): First run readFile() to see if we can load. If not, then +// ask MoodServer to run a job for us. Always changes the state +// from Unloaded so subsequent calls to load() do nothing. +// +// draw(): Draw the moodbar onto a QPixmap. Cache what we drew +// so that if draw() is called again with the same dimensions +// we don't have to redraw. +// +// reset(): Reset to the unloaded state. This is basically the same +// as calling moodbar = Moodbar(). +// +// (protected) slotJobEvent(): Only run by MoodServer, to notify us +// when a job is started or completed. Emits the jobEvent() +// signal. +// +// (private) readFile(): When we think there's a file available, this +// method tries to load it. We also do the display-independent +// analysis here, namely, calculating the sorting index (for sort- +// by-hue in the Playlist), and Making Moodier. + + +// The MoodServer class -- +// +// This is a singleton class. It is responsible for queueing analyzer +// jobs requested by Moodbar's, running them, and notifying the +// Moodbar's when the job has started and completed, successful or no. +// This class is also responsible for remembering if the moodbar +// system is totally broken (e.g. if the GStreamer plugins are +// missing), notifying the user if such is the case, and refusing to +// queue any more jobs. MoodServer should be threadsafe, in that you +// should be able to run queueJob() from any thread. +// +// Jobs are referenced by URL. If a Moodbar tries to queue a job +// with the same URL as an existing job, the job will not be re-queued; +// instead, each queued job has a refcount, which is increased. This +// is to support the de-queueing of jobs when Moodbar's are destroyed; +// the use case I have in mind is if the user has the moodbar column +// displayed in the playlist, he/she adds 1000 tracks to the playlist +// (at which point all the displayed tracks queue moodbar jobs), and +// then decides to clear the playlist again. The jobEvent() signal +// passes the URL of the job that was completed. +// +// The analyzer is actually run using a KProcess. ThreadManager::Job +// is not a good solution, since we need more flexibility in the +// queuing process, and in addition, KProcess'es must be started from +// the GUI thread! +// +// Important members: +// m_jobQueue: this is a list of MoodServer::ProcData structures, +// which contain the data needed to start and reference +// a process, as well as a refcount. +// m_currentProcess: the currently-running KProcess, if any. +// m_currentData: the ProcData structure for the currently-running +// process. +// m_moodbarBroken: this is set when there's an error running the analyzer +// that indicates the analyzer will never be able to run. +// When m_moodbarBroken == true, the MoodServer will refuse +// to queue new jobs. +// m_mutex: you should be able to run queueJob() from any thread, +// so most methods lock the object. +// +// Important methods: +// +// queueJob(): Add a job to the queue. If the job is being run, do nothing; +// if the job is already queued, increase its refcount, and if +// m_moodbarBroken == true, do nothing. +// +// deQueueJob(): Called from ~Moodbar(), for instance. Decreases +// the refcount of a job, removing it from the queue when the +// refcount hits zero. This won't kill a running process. +// +// (private slot) slotJobCompleted(): Called when a job finishes. Do some +// cleanup, and notify the interested parties. Set m_moodbarBroken if +// necessary; otherwise call slotNewJob(). +// +// (private slot) slotNewJob(): Called by slotJobCompleted() and queueJob(). +// Take a job off the queue and start the KProcess. +// +// (private slot) slotMoodbarPrefs(): Called when the Amarok config changes. +// If the moodbar has been disabled completely, kill the current job +// (if any), clear the queue, and notify the interested Moodbar's. +// +// (private slot) slotFileDeleted(): Called when a music file is deleted, so +// we can delete the associated moodbar +// +// (private slot) slotFileMoved(): Called when a music file is moved, so +// we can move the associated moodbar + +// TODO: off-color single bars in dark areas -- do some interpolation when +// averaging. Big jumps in hues when near black. +// +// BUGS: + +#define DEBUG_PREFIX "Moodbar" + +#include + +#include "amarok.h" +#include "amarokconfig.h" +#include "app.h" +#include "collectiondb.h" +#include "debug.h" +#include "metabundle.h" +#include "mountpointmanager.h" +#include "statusbar.h" + +#include +#include // For QDir::rename() +#include +#include + +#include + +#include // for memset() + + +#define CLAMP(n, v, x) ((v) < (n) ? (n) : (v) > (x) ? (x) : (v)) + +#define WEBPAGE "http://amarok.kde.org/wiki/Moodbar" + + +/////////////////////////////////////////////////////////////////////////////// +// MoodServer class +/////////////////////////////////////////////////////////////////////////////// + + +MoodServer * +MoodServer::instance( void ) +{ + static MoodServer m; + return &m; +} + + +MoodServer::MoodServer( void ) + : m_moodbarBroken( false ) + , m_currentProcess( 0 ) +{ + connect( App::instance(), SIGNAL( moodbarPrefs( bool, bool, int, bool ) ), + SLOT( slotMoodbarPrefs( bool, bool, int, bool ) ) ); + connect( CollectionDB::instance(), + SIGNAL( fileMoved( const QString &, const QString & ) ), + SLOT( slotFileMoved( const QString &, const QString & ) ) ); + connect( CollectionDB::instance(), + SIGNAL( fileMoved( const QString &, const QString &, const QString & ) ), + SLOT( slotFileMoved( const QString &, const QString & ) ) ); + connect( CollectionDB::instance(), + SIGNAL( fileDeleted( const QString & ) ), + SLOT( slotFileDeleted( const QString & ) ) ); + connect( CollectionDB::instance(), + SIGNAL( fileDeleted( const QString &, const QString & ) ), + SLOT( slotFileDeleted( const QString & ) ) ); +} + + +// Queue a job, but not before checking if the moodbar is enabled +// in the config, if the moodbar analyzer appears to be working, +// and if a job for that URL isn't already queued. Returns true +// if the job is already running, false otherwise. +bool +MoodServer::queueJob( MetaBundle *bundle ) +{ + if( m_moodbarBroken || !AmarokConfig::showMoodbar() ) + return false; + + m_mutex.lock(); + + // Check if the currently running job is for that URL + if( m_currentProcess != 0 && + m_currentData.m_url == bundle->url() ) + { + debug() << "MoodServer::queueJob: Not re-queueing already-running job " + << bundle->url().path() << endl; + m_mutex.unlock(); + return true; + } + + // Check if there's already a job in the queue for that URL + QValueList::iterator it; + for( it = m_jobQueue.begin(); it != m_jobQueue.end(); ++it ) + { + if( (*it).m_url == bundle->url() ) + { + (*it).m_refcount++; + debug() << "MoodServer::queueJob: Job for " << bundle->url().path() + << " already in queue, increasing refcount to " + << (*it).m_refcount << endl; + m_mutex.unlock(); + return false; + } + } + + m_jobQueue.append( ProcData( bundle->url(), + bundle->url().path(), + bundle->moodbar().moodFilename( bundle->url() ) ) ); + + debug() << "MoodServer::queueJob: Queued job for " << bundle->url().path() + << ", " << m_jobQueue.size() << " jobs in queue." << endl; + + m_mutex.unlock(); + + // New jobs *must* be started from the GUI thread! + QTimer::singleShot( 1000, this, SLOT( slotNewJob( void ) ) ); + + return false; +} + + +// Decrements the refcount of the job for the given URL +// and deletes that job if necessary. +void +MoodServer::deQueueJob( KURL url ) +{ + m_mutex.lock(); + + // Can't de-queue running jobs + if( m_currentProcess != 0 && + m_currentData.m_url == url ) + { + debug() << "MoodServer::deQueueJob: Not de-queueing already-running job " + << url.path() << endl; + m_mutex.unlock(); + return; + } + + // Check if there's already a job in the queue for that URL + QValueList::iterator it; + for( it = m_jobQueue.begin(); it != m_jobQueue.end(); ++it ) + { + if( (*it).m_url == url ) + { + (*it).m_refcount--; + + if( (*it).m_refcount == 0 ) + { + debug() << "MoodServer::deQueueJob: nobody cares about " + << (*it).m_url.path() + << " anymore, deleting from queue" << endl; + m_jobQueue.erase( it ); + } + + else + debug() << "MoodServer::deQueueJob: decrementing refcount of " + << (*it).m_url.path() << " to " << (*it).m_refcount + << endl; + + m_mutex.unlock(); + return; + } + } + + debug() << "MoodServer::deQueueJob: tried to delete nonexistent job " + << url.path() << endl; + + m_mutex.unlock(); +} + + +// This slot exists so that jobs can be started from the GUI thread, +// just in case queueJob() is run from another thread. Only run +// directly if you're in the GUI thread! +void +MoodServer::slotNewJob( void ) +{ + if( m_moodbarBroken ) + return; + + m_mutex.lock(); + + // Are we already running a process? + if( m_jobQueue.isEmpty() || m_currentProcess != 0 ) + { + m_mutex.unlock(); + return; + } + + m_currentData = m_jobQueue.first(); + m_jobQueue.pop_front(); + + debug() << "MoodServer::slotNewJob: starting new analyzer process: " + << "moodbar -o " << m_currentData.m_outfile << ".tmp " + << m_currentData.m_infile << endl; + debug() << "MoodServer::slotNewJob: " << m_jobQueue.size() + << " jobs left in queue." << endl; + + + // Write to outfile.mood.tmp so that new Moodbar instances + // don't think the mood data exists while the analyzer is + // running. Then rename the file later. + m_currentProcess = new Amarok::Process( this ); + m_currentProcess->setPriority( 18 ); // Nice the process + *m_currentProcess << KStandardDirs::findExe( "moodbar" ) << "-o" + << (m_currentData.m_outfile + ".tmp") + << m_currentData.m_infile; + + connect( m_currentProcess, SIGNAL( processExited( KProcess* ) ), + SLOT( slotJobCompleted( KProcess* ) ) ); + + // We have to enable KProcess::Stdout (even though we don't monitor + // it) since otherwise the child process crashes every time in + // KProcess::start() (but only when started from the loader!). I + // have no idea why, but I imagine it's a bug in KDE. + if( !m_currentProcess->start( KProcess::NotifyOnExit, KProcess::AllOutput ) ) + { + // If we have an error starting the process, it's never + // going to work, so call moodbarBroken() + warning() << "Can't start moodbar analyzer process!" << endl; + delete m_currentProcess; + m_currentProcess = 0; + m_mutex.unlock(); + setMoodbarBroken(); + return; + } + + // Extreme reentrancy pedatry :) + KURL url = m_currentData.m_url; + m_mutex.unlock(); + + emit jobEvent( url, Moodbar::JobStateRunning ); +} + + +// This always run in the GUI thread. It is called +// when an analyzer process terminates +void +MoodServer::slotJobCompleted( KProcess *proc ) +{ + m_mutex.lock(); + + // Pedantry + if( proc != m_currentProcess ) + warning() << "MoodServer::slotJobCompleted: proc != m_currentProcess!" << endl; + + ReturnStatus returnval; + if( !m_currentProcess->normalExit() ) + returnval = Crash; + else + returnval = (ReturnStatus) m_currentProcess->exitStatus(); + + bool success = (returnval == Success); + KURL url = m_currentData.m_url; + + if( success ) + { + QString file = m_currentData.m_outfile; + QString dir = file.left( file.findRev( '/' ) ); + file = file.right( file.length() - file.findRev( '/' ) - 1 ); + QDir( dir ).rename( file + ".tmp", file ); + } + else + QFile::remove( m_currentData.m_outfile + ".tmp" ); + + delete m_currentProcess; + m_currentProcess = 0; + + + // If the moodbar was disabled, we killed the process + if( !AmarokConfig::showMoodbar() ) + { + debug() << "MoodServer::slotJobCompleted: moodbar disabled, job killed" << endl; + m_mutex.unlock(); + emit jobEvent( url, Moodbar::JobStateFailed ); + return; + } + + + switch( returnval ) + { + case Success: + debug() << "MoodServer::slotJobCompleted: job completed successfully" << endl; + m_mutex.unlock(); + slotNewJob(); + break; + + // Crash and NoFile don't mean that moodbar is broken. + // Something bad happened, but it's probably a problem with this file + // Just log an error message and emit jobEvent(). + case Crash: + debug() << "MoodServer::slotJobCompleted: moodbar crashed on " + << m_currentData.m_infile << endl; + m_mutex.unlock(); + slotNewJob(); + break; + + case NoFile: + debug() << "MoodServer::slotJobCompleted: moodbar had a problem with " + << m_currentData.m_infile << endl; + m_mutex.unlock(); + slotNewJob(); + break; + + // NoPlugin and CommandLine mean the moodbar is broken + // The moodbar analyzer is not likely to work ever, so let the + // user know about it and disable new jobs. + default: + m_mutex.unlock(); + setMoodbarBroken(); + break; + + } + + emit jobEvent( url, success ? Moodbar::JobStateSucceeded + : Moodbar::JobStateFailed ); +} + + +// This is called whenever "Ok" or "Apply" is pressed on the configuration +// dialog. If the moodbar is disabled, kill the current process and +// clear the queue +void +MoodServer::slotMoodbarPrefs( bool show, bool moodier, int alter, bool withMusic ) +{ + if( show == true) + return; + + (void) moodier; (void) alter; (void) withMusic; + + // If we have a current process, kill it. Cleanup happens in + // slotJobCompleted() above. We do *not* want to lock the + // mutex when calling this! + if( m_currentProcess != 0 ) + m_currentProcess->kill(); + + clearJobs(); +} + + +// When a file is deleted, either manually using Organize Collection or +// automatically detected using AFT, delete the corresponding mood file. +void +MoodServer::slotFileDeleted( const QString &path ) +{ + QString mood = Moodbar::moodFilename( KURL::fromPathOrURL( path ) ); + if( mood.isEmpty() || !QFile::exists( mood ) ) + return; + + debug() << "MoodServer::slotFileDeleted: deleting " << mood << endl; + QFile::remove( mood ); +} + + +// When a file is moved, either manually using Organize Collection or +// automatically using AFT, move the corresponding mood file. +void +MoodServer::slotFileMoved( const QString &srcPath, const QString &dstPath ) +{ + QString srcMood = Moodbar::moodFilename( KURL::fromPathOrURL( srcPath ) ); + QString dstMood = Moodbar::moodFilename( KURL::fromPathOrURL( dstPath ) ); + + if( srcMood.isEmpty() || dstMood.isEmpty() || + srcMood == dstMood || !QFile::exists( srcMood ) ) + return; + + debug() << "MoodServer::slotFileMoved: moving " << srcMood << " to " + << dstMood << endl; + + Moodbar::copyFile( srcMood, dstMood ); + QFile::remove( srcMood ); +} + + +// This is called when we decide that the moodbar analyzer is +// never going to work. Disable further jobs, and let the user +// know about it. This should only be called when m_currentProcess == 0. +void +MoodServer::setMoodbarBroken( void ) +{ + warning() << "Uh oh, it looks like the moodbar analyzer is not going to work" + << endl; + + Amarok::StatusBar::instance()->longMessage( i18n( + "The Amarok moodbar analyzer program seems to be broken. " + "This is probably because the moodbar package is not installed " + "correctly. The moodbar package, installation instructions, and " + "troubleshooting help can be found on the wiki page at " WEBPAGE ". " + "When the problem is fixed, please restart Amarok."), + KDE::StatusBar::Error ); + + + m_moodbarBroken = true; + clearJobs(); +} + + +// Clear the job list and emit signals +void +MoodServer::clearJobs( void ) +{ + // We don't want to emit jobEvent (or really do anything + // external) while the mutex is locked. + m_mutex.lock(); + QValueList queueCopy + = QDeepCopy< QValueList > ( m_jobQueue ); + m_jobQueue.clear(); + m_mutex.unlock(); + + QValueList::iterator it; + for( it = queueCopy.begin(); it != queueCopy.end(); ++it ) + emit jobEvent( (*it).m_url, Moodbar::JobStateFailed ); +} + + + +/////////////////////////////////////////////////////////////////////////////// +// Moodbar class +/////////////////////////////////////////////////////////////////////////////// + + +// The moodbar behavior is nearly identical in the JobQueued and +// JobRunning states, but we have to keep track anyway so the +// PlaylistItem knows what do display + +#define JOB_PENDING(state) ((state)==JobQueued||(state)==JobRunning) + + +// The passed MetaBundle _must_ be non-NULL, and the pointer must be valid +// as long as this instance is alive. The Moodbar is only meant to be a +// member of a MetaBundle, in other words. + +Moodbar::Moodbar( MetaBundle *mb ) + : QObject ( ) + , m_bundle ( mb ) + , m_hueSort ( 0 ) + , m_state ( Unloaded ) +{ +} + + +// If we have any pending jobs, de-queue them. The use case I +// have in mind is if the user has the moodbar column displayed +// and adds all his/her tracks to the playlist, then deletes +// them again. +Moodbar::~Moodbar( void ) +{ + if( JOB_PENDING( m_state ) ) + MoodServer::instance()->deQueueJob( m_url ); +} + + +// MetaBundle's are often assigned using operator=, so so are we. +Moodbar& +Moodbar::operator=( const Moodbar &mood ) +{ + // Need to check this before locking both! + if( &mood == this ) + return *this; + + m_mutex.lock(); + mood.m_mutex.lock(); + + State oldState = m_state; + KURL oldURL = m_url; + + m_data = mood.m_data; + m_pixmap = mood.m_pixmap; + m_state = mood.m_state; + m_url = mood.m_url; + // DO NOT overwrite m_bundle! That should never change. + + // Signal connections and job queues are part of our "state", + // so those should be updated too. + if( JOB_PENDING( m_state ) && !JOB_PENDING( oldState ) ) + { + connect( MoodServer::instance(), + SIGNAL( jobEvent( KURL, int ) ), + SLOT( slotJobEvent( KURL, int ) ) ); + // Increase the refcount for this job. Use mood.m_bundle + // since that one's already initialized. + MoodServer::instance()->queueJob( mood.m_bundle ); + } + + // If we had a job pending, de-queue it + if( !JOB_PENDING( m_state ) && JOB_PENDING( oldState ) ) + { + MoodServer::instance()->disconnect( this, SLOT( slotJobEvent( KURL, int ) ) ); + MoodServer::instance()->deQueueJob( oldURL ); + } + + mood.m_mutex.unlock(); + m_mutex.unlock(); + + return *this; +} + + +// Reset the moodbar to its Unloaded state. This is useful when +// the configuration is changed, and all the moodbars need to be +// reloaded. +void +Moodbar::reset( void ) +{ + m_mutex.lock(); + + debug() << "Resetting moodbar: " << m_bundle->url().path() << endl; + + if( JOB_PENDING( m_state ) ) + { + MoodServer::instance()->disconnect( this, SLOT( slotJobEvent( KURL, int ) ) ); + MoodServer::instance()->deQueueJob( m_url ); + } + + m_data.clear(); + m_pixmap = QPixmap(); + m_url = KURL(); + m_hueSort = 0; + m_state = Unloaded; + + m_mutex.unlock(); +} + + +// Make a copy of all of our implicitly shared data +void +Moodbar::detach( void ) +{ + m_mutex.lock(); + + m_data = QDeepCopy(m_data); + m_pixmap.detach(); + + // Apparently this is the wrong hack -- don't detach urls + //QString url( QDeepCopy( m_url.url() ) ); + //m_url = KURL::fromPathOrURL( url ); + + m_mutex.unlock(); +} + + +// If possible, try to open the bundle's .mood file. When this method +// returns true, this instance must be able to draw(). This may +// change the state to CantLoad, but usually leaves the state +// untouched. +bool +Moodbar::dataExists( void ) +{ + // Put this first for efficiency + if( m_state == Loaded ) + return true; + + // Should we bother checking for the file? + if( m_state == CantLoad || + JOB_PENDING( m_state ) || + m_state == JobFailed || + !canHaveMood() ) + return false; + + m_mutex.lock(); + bool res = readFile(); + m_mutex.unlock(); + + return res; +} + + +// If m_bundle is not a local file or for some other reason cannot +// have mood data, return false, and set the state to CantLoad to +// save future checks. Note that MoodServer::m_moodbarBroken == true +// does not mean we can't have a mood file; it just means that we +// can't generate new ones. +bool +Moodbar::canHaveMood( void ) +{ + if( m_state == CantLoad ) + return false; + + // Don't try to analyze it if we can't even determine it has a length + // If for some reason we can't determine a file name, give up + // If the moodbar is disabled, set to CantLoad -- if the user re-enables + // the moodbar, we'll be reset() anyway. + if( !AmarokConfig::showMoodbar() || + !m_bundle->url().isLocalFile() || + !m_bundle->length() || + moodFilename( m_bundle->url() ).isEmpty() ) + { + m_state = CantLoad; + return false; + } + + return true; +} + + +// Ask MoodServer to queue an analyzer job for us if necessary. This +// method will only do something the first time it's called, as it's +// guaranteed to change the state from Unloaded. +void +Moodbar::load( void ) +{ + if( m_state != Unloaded ) + return; + + m_mutex.lock(); + + if( !canHaveMood() ) + { + // State is now CantLoad + m_mutex.unlock(); + return; + } + + if( readFile() ) + { + // State is now Loaded + m_mutex.unlock(); + return; + } + + if( MoodServer::instance()->moodbarBroken() ) + { + m_state = JobFailed; + m_mutex.unlock(); + return; + } + + // Ok no more excuses, we have to queue a job + connect( MoodServer::instance(), + SIGNAL( jobEvent( KURL, int ) ), + SLOT( slotJobEvent( KURL, int ) ) ); + bool isRunning = MoodServer::instance()->queueJob( m_bundle ); + m_state = isRunning ? JobRunning : JobQueued; + m_url = m_bundle->url(); // Use this URL for MoodServer::deQueueJob + + m_mutex.unlock(); +} + + +// This is called by MoodServer when our moodbar analyzer job starts +// or finishes. It may change the state from JobQueued / JobRunning +// to JobRunning, Loaded, or JobFailed. It may emit a jobEvent() +void +Moodbar::slotJobEvent( KURL url, int newState ) +{ + // Is this job for us? + if( !JOB_PENDING( m_state ) || url != m_bundle->url() ) + return; + + bool success = ( newState == JobStateSucceeded ); + + // We don't really care about this, but our listeners might + if( newState == JobStateRunning ) + { + m_state = JobRunning; + goto out; + } + + m_mutex.lock(); + + // Disconnect the signal for efficiency's sake + MoodServer::instance()->disconnect( this, SLOT( slotJobEvent( KURL, int ) ) ); + + if( !success ) + { + m_state = JobFailed; + m_mutex.unlock(); + goto out; + } + + if( readFile() ) + { + // m_state is now Loaded + m_mutex.unlock(); + goto out; + } + + // If we get here it means the analyzer job went wrong, but + // somehow the MoodServer didn't know about it + debug() << "WARNING: Failed to open file " << moodFilename( m_bundle->url() ) + << " -- something is very wrong" << endl; + m_state = JobFailed; + m_mutex.unlock(); + + out: + emit jobEvent( newState ); + // This is a cheat for PlaylistItem so it doesn't have to + // use signals + m_bundle->moodbarJobEvent( newState ); +} + + +// Draw the moodbar onto a pixmap of the given dimensions and return +// it. This is mostly Gav's original code, cut and pasted from +// various places. This will not change the state. +QPixmap +Moodbar::draw( int width, int height ) +{ + if( m_state != Loaded || !AmarokConfig::showMoodbar() ) // Naughty caller! + return QPixmap(); + + m_mutex.lock(); + + // Do we have to repaint, or can we use the cache? + if( m_pixmap.width() == width && m_pixmap.height() == height ) + { + m_mutex.unlock(); + return m_pixmap; + } + + m_pixmap = QPixmap( width, height ); + QPainter paint( &m_pixmap ); + + // First average the moodbar samples that will go into each + // vertical bar on the screen. + + if( m_data.size() == 0 ) // Play it safe -- see below + return QPixmap(); + + ColorList screenColors; + QColor bar; + float r, g, b; + int h, s, v; + + for( int i = 0; i < width; i++ ) + { + r = 0.f; g = 0.f; b = 0.f; + + // m_data.size() needs to be at least 1 for this not to crash! + uint start = i * m_data.size() / width; + uint end = (i + 1) * m_data.size() / width; + if( start == end ) + end = start + 1; + + for( uint j = start; j < end; j++ ) + { + r += m_data[j].red(); + g += m_data[j].green(); + b += m_data[j].blue(); + } + + uint n = end - start; + bar = QColor( int( r / float( n ) ), + int( g / float( n ) ), + int( b / float( n ) ), QColor::Rgb ); + + /* Snap to the HSV values for later */ + bar.getHsv(&h, &s, &v); + bar.setHsv(h, s, v); + + screenColors.push_back( bar ); + } + + // Paint the bars. This is Gav's painting code -- it breaks up the + // monotony of solid-color vertical bars by playing with the saturation + // and value. + + for( int x = 0; x < width; x++ ) + { + screenColors[x].getHsv( &h, &s, &v ); + + for( int y = 0; y <= height / 2; y++ ) + { + float coeff = float(y) / float(height / 2); + float coeff2 = 1.f - ((1.f - coeff) * (1.f - coeff)); + coeff = 1.f - (1.f - coeff) / 2.f; + coeff2 = 1.f - (1.f - coeff2) / 2.f; + paint.setPen( QColor( h, + CLAMP( 0, int( float( s ) * coeff ), 255 ), + CLAMP( 0, int( 255.f - (255.f - float( v )) * coeff2), 255 ), + QColor::Hsv ) ); + paint.drawPoint(x, y); + paint.drawPoint(x, height - 1 - y); + } + } + + m_mutex.unlock(); + + return m_pixmap; +} + + +#define NUM_HUES 12 + +// Read the .mood file. Returns true if the read was successful +// and changes the state to Loaded; returns false and leaves the +// state untouched otherwise. +// +// This is based on Gav's original code. We do the mood altering +// (AmarokConfig::AlterMood()) here, as well as calculating the +// hue-based sort. All displayed moodbars will be reset() when +// the config is changed, so there's no harm in doing it here. +// +// This method must be called with the instance locked. +bool +Moodbar::readFile( void ) +{ + if( !AmarokConfig::showMoodbar() ) + return false; + + if( m_state == Loaded ) + return true; + + QString path = moodFilename( m_bundle->url() ); + if( path.isEmpty() ) + return false; + + debug() << "Moodbar::readFile: Trying to read " << path << endl; + + QFile moodFile( path ); + + if( !QFile::exists( path ) || + !moodFile.open( IO_ReadOnly ) ) + { + // If the user has changed his/her preference about where to + // store the mood files, he/she might have the .mood file + // in the other place, so we should check there before giving + // up. + + QString path2 = moodFilename( m_bundle->url(), + !AmarokConfig::moodsWithMusic() ); + moodFile.setName( path2 ); + + if( !QFile::exists( path2 ) || + !moodFile.open( IO_ReadOnly ) ) + return false; + + debug() << "Moodbar::readFile: Found a file at " << path2 + << " instead, using that and copying." << endl; + + moodFile.close(); + if( !copyFile( path2, path ) ) + return false; + moodFile.setName( path ); + if( !moodFile.open( IO_ReadOnly ) ) + return false; + } + + int r, g, b, samples = moodFile.size() / 3; + debug() << "Moodbar::readFile: File " << path + << " opened. Proceeding to read contents... s=" << samples << endl; + + // This would be bad. + if( samples == 0 ) + { + debug() << "Moodbar::readFile: File " << moodFile.name() + << " is corrupted, removing." << endl; + moodFile.remove(); + return false; + } + + int huedist[360], mx = 0; // For alterMood + int modalHue[NUM_HUES]; // For m_hueSort + int h, s, v; + + memset( modalHue, 0, sizeof( modalHue ) ); + memset( huedist, 0, sizeof( huedist ) ); + + // Read the file, keeping track of some histograms + for( int i = 0; i < samples; i++ ) + { + r = moodFile.getch(); + g = moodFile.getch(); + b = moodFile.getch(); + + m_data.push_back( QColor( CLAMP( 0, r, 255 ), + CLAMP( 0, g, 255 ), + CLAMP( 0, b, 255 ), QColor::Rgb ) ); + + // Make a histogram of hues + m_data.last().getHsv( &h, &s, &v ); + modalHue[CLAMP( 0, h * NUM_HUES / 360, NUM_HUES - 1 )] += v; + + if( h < 0 ) h = 0; else h = h % 360; + huedist[h]++; + } + + // Make moodier -- copied straight from Gav Wood's code + // Here's an explanation of the algorithm: + // + // The "input" hue for each bar is mapped to a hue between + // rangeStart and (rangeStart + rangeDelta). The mapping is + // determined by the hue histogram, huedist[], which is calculated + // above by putting each sample into one of 360 hue bins. The + // mapping is such that if your histogram is concentrated on a few + // hues that are close together, then these hues are separated, + // and the space between spikes in the hue histogram is + // compressed. Here we consider a hue value to be a "spike" in + // the hue histogram if the number of samples in that bin is + // greater than the threshold variable. + // + // As an example, suppose we have 100 samples, and that + // threshold = 10 rangeStart = 0 rangeDelta = 288 + // Suppose that we have 10 samples at each of 99,100,101, and 200. + // Suppose that there are 20 samples < 99, 20 between 102 and 199, + // and 20 above 201, with no spikes. There will be five hues in + // the output, at hues 0, 72, 144, 216, and 288, containing the + // following number of samples: + // 0: 20 + 10 = 30 (range 0 - 99 ) + // 72: 10 (range 100 - 100) + // 144: 10 (range 101 - 101) + // 216: 10 + 20 = 30 (range 102 - 200) + // 288: 20 (range 201 - 359) + // The hues are now much more evenly distributed. + // + // After the hue redistribution is calculated, the saturation and + // value are scaled by sat and val, respectively, which are percentage + // values. + + if( AmarokConfig::makeMoodier() ) + { + // Explanation of the parameters: + // + // threshold: A hue value is considered to be a "spike" in the + // histogram if it's above this value. Setting this value + // higher will tend to make the hue distribution more uniform + // + // rangeStart, rangeDelta: output hues will be more or less + // evenly spaced between rangeStart and (rangeStart + rangeDelta) + // + // sat, val: the saturation and value are scaled by these integral + // percentage values + + int threshold, rangeStart, rangeDelta, sat, val; + int total = 0; + memset( modalHue, 0, sizeof( modalHue ) ); // Recalculate this + + switch( AmarokConfig::alterMood() ) + { + case 1: // Angry + threshold = samples / 360 * 9; + rangeStart = 45; + rangeDelta = -45; + sat = 200; + val = 100; + break; + + case 2: // Frozen + threshold = samples / 360 * 1; + rangeStart = 140; + rangeDelta = 160; + sat = 50; + val = 100; + break; + + default: // Happy + threshold = samples / 360 * 2; + rangeStart = 0; + rangeDelta = 359; + sat = 150; + val = 250; + } + + debug() << "ReadMood: Applying filter t=" << threshold + << ", rS=" << rangeStart << ", rD=" << rangeDelta + << ", s=" << sat << "%, v=" << val << "%" << endl; + + // On average, huedist[i] = samples / 360. This counts the + // number of samples over the threshold, which is usually + // 1, 2, 9, etc. times the average samples in each bin. + // The total determines how many output hues there are, + // evenly spaced between rangeStart and rangeStart + rangeDelta. + for( int i = 0; i < 360; i++ ) + if( huedist[i] > threshold ) + total++; + + if( total < 360 && total > 0 ) + { + // Remap the hue values to be between rangeStart and + // rangeStart + rangeDelta. Every time we see an input hue + // above the threshold, increment the output hue by + // (1/total) * rangeDelta. + for( int i = 0, n = 0; i < 360; i++ ) + huedist[i] = ( ( huedist[i] > threshold ? n++ : n ) + * rangeDelta / total + rangeStart ) % 360; + + // Now huedist is a hue mapper: huedist[h] is the new hue value + // for a bar with hue h + + for(uint i = 0; i < m_data.size(); i++) + { + m_data[i].getHsv( &h, &s, &v ); + if( h < 0 ) h = 0; else h = h % 360; + m_data[i].setHsv( CLAMP( 0, huedist[h], 359 ), + CLAMP( 0, s * sat / 100, 255 ), + CLAMP( 0, v * val / 100, 255 ) ); + + modalHue[CLAMP(0, huedist[h] * NUM_HUES / 360, NUM_HUES - 1)] + += (v * val / 100); + } + } + } + + // Calculate m_hueSort. This is a 3-digit number in base NUM_HUES, + // where the most significant digit is the first strongest hue, the + // second digit is the second strongest hue, and the third digit + // is the third strongest. This code was written by Gav Wood. + + m_hueSort = 0; + mx = 0; + for( int i = 1; i < NUM_HUES; i++ ) + if( modalHue[i] > modalHue[mx] ) + mx = i; + m_hueSort = mx * NUM_HUES * NUM_HUES; + modalHue[mx] = 0; + + mx = 0; + for( int i = 1; i < NUM_HUES; i++ ) + if( modalHue[i] > modalHue[mx] ) + mx = i; + m_hueSort += mx * NUM_HUES; + modalHue[mx] = 0; + + mx = 0; + for( int i = 1; i < NUM_HUES; i++ ) + if( modalHue[i] > modalHue[mx] ) + mx = i; + m_hueSort += mx; + + + debug() << "Moodbar::readFile: All done." << endl; + + moodFile.close(); + m_state = Loaded; + + return true; +} + + +// Returns where the mood file for this bundle should be located, +// based on the user preferences. If no location can be determined, +// return QString::null. + +QString +Moodbar::moodFilename( const KURL &url ) +{ + return moodFilename( url, AmarokConfig::moodsWithMusic() ); +} + +QString +Moodbar::moodFilename( const KURL &url, bool withMusic ) +{ + // No need to lock the object + + QString path; + + if( withMusic ) + { + path = url.path(); + path.truncate(path.findRev('.')); + + if (path.isEmpty()) // Weird... + return QString(); + + path += ".mood"; + int slash = path.findRev('/') + 1; + QString dir = path.left(slash); + QString file = path.right(path.length() - slash); + path = dir + '.' + file; + } + + else + { + // The moodbar file is {device id},{relative path}.mood} + int deviceid = MountPointManager::instance()->getIdForUrl( url ); + KURL relativePath; + MountPointManager::instance()->getRelativePath( deviceid, + url, relativePath ); + path = relativePath.path(); + path.truncate(path.findRev('.')); + + if (path.isEmpty()) // Weird... + return QString(); + + path = QString::number( deviceid ) + ',' + + path.replace('/', ',') + ".mood"; + + // Creates the path if necessary + path = ::locateLocal( "data", "amarok/moods/" + path ); + } + + return path; +} + + +// Quick-n-dirty -->synchronous<-- file copy (the GUI needs its +// moodbars immediately!) +bool +Moodbar::copyFile( const QString &srcPath, const QString &dstPath ) +{ + QFile file( srcPath ); + if( !file.open( IO_ReadOnly ) ) + return false; + QByteArray contents = file.readAll(); + file.close(); + file.setName( dstPath ); + if( !file.open( IO_WriteOnly | IO_Truncate ) ) + return false; + bool res = ( uint( file.writeBlock( contents ) ) == contents.size() ); + file.close(); + return res; +} + + + +// Can we find the moodbar program? +bool +Moodbar::executableExists( void ) +{ + return !(KStandardDirs::findExe( "moodbar" ).isNull()); +} + + +#include "moodbar.moc" + diff --git a/amarok/src/moodbar.h b/amarok/src/moodbar.h new file mode 100644 index 00000000..7263be40 --- /dev/null +++ b/amarok/src/moodbar.h @@ -0,0 +1,181 @@ +/*************************************************************************** -*- c++ -*- + moodbar.h - description + ------------------- + begin : 6th Nov 2005 + copyright : (C) 2006 by Joseph Rabinoff + copyright : (C) 2005 by Gav Wood + email : bobqwatson@yahoo.com +***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +// Aug 5 2006 (Joe Rabinoff): Rewrote everything. This file bears +// no resemblance to Gav's original code. + +// See moodbar.cpp for usage and implementation notes. + +#ifndef MOODBAR_H +#define MOODBAR_H + +#include +#include +#include +#include +#include +#include + +#include + + +class MetaBundle; + +class Moodbar : public QObject +{ + Q_OBJECT + +public: + typedef QValueVector ColorList; + + typedef enum + { + Unloaded, // Haven't tried to load yet + CantLoad, // For some reason we'll never be able to load + JobQueued, // An analysis job is pending + JobRunning, // An analysis job is running + JobFailed, // Our job has returned and failed + Loaded // Can draw() + } State; + + // These are the state changes we emit in jobEvent + enum + { + JobStateRunning, + JobStateSucceeded, + JobStateFailed + }; + + // Construct an empty, small-footprint instance + Moodbar( MetaBundle *mb ); + // This is for de-queueing jobs + ~Moodbar( void ); + + Moodbar& operator=( const Moodbar &mood ); + void reset( void ); + void detach( void ); + + bool dataExists( void ); + bool canHaveMood( void ); + void load( void ); + QPixmap draw( int width, int height ); + + int hueSort( void ) const + { return m_hueSort; } + State state( void ) const + { return m_state; } + + // Where are we storing the .mood file? + static QString moodFilename( const KURL &url ); + static QString moodFilename( const KURL &url, bool withMusic ); + static bool copyFile( const QString &srcPath, const QString &dstPath ); + + static bool executableExists( void ); + +public slots: + void slotJobEvent( KURL url, int newState ); + +signals: + void jobEvent( int newState ); + +private: + // Undefined! We can't construct unless we know what + // *our* parent bundle is. + Moodbar( const Moodbar& ); + + bool readFile( void ); + + MetaBundle *m_bundle; // Parent bundle + ColorList m_data; // .mood file contents + QPixmap m_pixmap; // Cached from the last time draw() was called + KURL m_url; // Keep a copy of this, mainly for dtor + mutable QMutex m_mutex; // Locks the whole object + int m_hueSort; // For PlaylistItem sorting + State m_state; +}; + + +class KProcess; + +// For internal use only (well, mostly) +class MoodServer : public QObject +{ + Q_OBJECT + +public: + static MoodServer *instance( void ); + + bool queueJob( MetaBundle *bundle ); + void deQueueJob( KURL url ); + + bool moodbarBroken( void ) const + { return m_moodbarBroken; } + +signals: + void jobEvent( KURL url, int newState ); + +private slots: + void slotJobCompleted( KProcess *proc ); + void slotNewJob( void ); + void slotMoodbarPrefs( bool show, bool moodier, int alter, bool withMusic ); + +public slots: + // Moodbar file organization slots + void slotFileDeleted( const QString &absPath ); + void slotFileMoved( const QString &srcPath, const QString &dstPath ); + +private: + + class ProcData + { + public: + ProcData( KURL url, QString infile, QString outfile ) + : m_url( url ), m_infile( infile ), m_outfile( outfile ) + , m_refcount( 1 ) + {} + ProcData( void ) : m_refcount( 0 ) {} + + KURL m_url; + QString m_infile; + QString m_outfile; + // Keep track of how many Moodbars are waiting on this URL + int m_refcount; + }; + + typedef enum + { + Crash = -1, + Success = 0, + NoPlugin = 1, + NoFile = 2, + CommandLine = 3 + } ReturnStatus; + + MoodServer( void ); + void setMoodbarBroken( void ); + void clearJobs( void ); + + QValueList m_jobQueue; + bool m_moodbarBroken; + KProcess *m_currentProcess; + ProcData m_currentData; + mutable QMutex m_mutex; +}; + + +#endif // MOODBAR_H diff --git a/amarok/src/mountpointmanager.cpp b/amarok/src/mountpointmanager.cpp new file mode 100644 index 00000000..a497f489 --- /dev/null +++ b/amarok/src/mountpointmanager.cpp @@ -0,0 +1,604 @@ +/* + * Copyright (c) 2006-2007 Maximilian Kossick + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU 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. + */ + +#define DEBUG_PREFIX "MountPointManager" + +#include "debug.h" + +#include "amarok.h" +#include "amarokconfig.h" //used in init() +#include "collectiondb.h" +#include "devicemanager.h" +#include "mountpointmanager.h" +#include "pluginmanager.h" +#include "statusbar.h" + +#include //used in init() +#include + +#include +#include +#include +#include + +typedef Medium::List MediumList; + +MountPointManager::MountPointManager() + : QObject( 0, "MountPointManager" ) + , m_noDeviceManager( false ) +{ + + if ( !Amarok::config( "Collection" )->readBoolEntry( "DynamicCollection", true ) ) + { + debug() << "Dynamic Collection deactivated in amarokrc, not loading plugins, not connecting signals" << endl; + return; + } + //we are only interested in the mounting or unmounting of mediums + //therefore it is enough to listen to DeviceManager's mediumChanged signal + if (DeviceManager::instance()->isValid() ) + { + connect( DeviceManager::instance(), SIGNAL( mediumAdded( const Medium*, QString ) ), SLOT( mediumAdded( const Medium* ) ) ); + connect( DeviceManager::instance(), SIGNAL( mediumChanged( const Medium*, QString ) ), SLOT( mediumChanged( const Medium* ) ) ); + connect( DeviceManager::instance(), SIGNAL( mediumRemoved( const Medium*, QString ) ), SLOT( mediumRemoved( const Medium* ) ) ); + } + else + { + handleMissingMediaManager(); + } + + m_mediumFactories.setAutoDelete( true ); + m_remoteFactories.setAutoDelete( true ); + init(); + + CollectionDB *collDB = CollectionDB::instance(); + + if ( collDB->adminValue( "Database Stats Version" ).toInt() >= 9 && /* make sure that deviceid actually exists*/ + collDB->query( "SELECT COUNT(url) FROM statistics WHERE deviceid = -2;" ).first().toInt() != 0 ) + { + connect( this, SIGNAL( mediumConnected( int ) ), SLOT( migrateStatistics() ) ); + QTimer::singleShot( 0, this, SLOT( migrateStatistics() ) ); + } + connect( this, SIGNAL( mediumConnected( int ) ), SLOT( updateStatisticsURLs() ) ); + updateStatisticsURLs(); +} + + +MountPointManager::~MountPointManager() +{ + m_handlerMapMutex.lock(); + foreachType( HandlerMap, m_handlerMap ) + { + delete it.data(); + } + m_handlerMapMutex.unlock(); +} + +MountPointManager * MountPointManager::instance( ) +{ + static MountPointManager instance; + return &instance; +} + +void +MountPointManager::init() +{ + DEBUG_BLOCK + KTrader::OfferList plugins = PluginManager::query( "[X-KDE-Amarok-plugintype] == 'device'" ); + debug() << "Received [" << QString::number( plugins.count() ) << "] device plugin offers" << endl; + foreachType( KTrader::OfferList, plugins ) + { + Amarok::Plugin *plugin = PluginManager::createFromService( *it ); + if( plugin ) + { + DeviceHandlerFactory *factory = static_cast( plugin ); + if ( factory->canCreateFromMedium() ) + m_mediumFactories.append( factory ); + else if (factory->canCreateFromConfig() ) + m_remoteFactories.append( factory ); + else + //FIXME max: better error message + debug() << "Unknown DeviceHandlerFactory" << endl; + } + else debug() << "Plugin could not be loaded" << endl; + } + //we need access to the unfiltered data + MediumList list = DeviceManager::instance()->getDeviceList(); + foreachType ( MediumList, list ) + { + mediumChanged( &(*it) ); + } + if( !KGlobal::config()->hasGroup( "Collection Folders" ) ) + { + QStringList folders = AmarokConfig::collectionFolders(); + if( !folders.isEmpty() ) + setCollectionFolders( folders ); + } +} + +int +MountPointManager::getIdForUrl( KURL url ) +{ + uint mountPointLength = 0; + int id = -1; + m_handlerMapMutex.lock(); + foreachType( HandlerMap, m_handlerMap ) + { + if ( url.path().startsWith( it.data()->getDevicePath() ) && mountPointLength < it.data()->getDevicePath().length() ) + { + id = it.key(); + mountPointLength = it.data()->getDevicePath().length(); + } + } + m_handlerMapMutex.unlock(); + if ( mountPointLength > 0 ) + { + return id; + } + else + { + //default fallback if we could not identify the mount point. + //treat -1 as mount point / in al other methods + return -1; + } +} + +int +MountPointManager::getIdForUrl( const QString &url ) +{ + return getIdForUrl( KURL::fromPathOrURL( url ) ); +} + +bool +MountPointManager::isMounted ( const int deviceId ) const { + m_handlerMapMutex.lock(); + bool result = m_handlerMap.contains( deviceId ); + m_handlerMapMutex.unlock(); + return result; +} + +QString +MountPointManager::getMountPointForId( const int id ) const +{ + QString mountPoint; + if ( isMounted( id ) ) + { + m_handlerMapMutex.lock(); + mountPoint = m_handlerMap[id]->getDevicePath(); + m_handlerMapMutex.unlock(); + } + else + //TODO better error handling + mountPoint = "/"; + return mountPoint; +} + +void +MountPointManager::getAbsolutePath( const int deviceId, const KURL& relativePath, KURL& absolutePath) const +{ + //debug() << "id is " << deviceId << ", relative path is " << relativePath.path() << endl; + if ( deviceId == -1 ) + { + absolutePath.setPath( "/" ); + absolutePath.addPath( relativePath.path() ); + absolutePath.cleanPath(); + //debug() << "Deviceid is -1, using relative Path as absolute Path, returning " << absolutePath.path() << endl; + return; + } + m_handlerMapMutex.lock(); + if ( m_handlerMap.contains( deviceId ) ) + { + m_handlerMap[deviceId]->getURL( absolutePath, relativePath ); + m_handlerMapMutex.unlock(); + } + else + { + m_handlerMapMutex.unlock(); + QStringList lastMountPoint = CollectionDB::instance()->query( + QString( "SELECT lastmountpoint FROM devices WHERE id = %1" ) + .arg( deviceId ) ); + if ( lastMountPoint.count() == 0 ) + { + //hmm, no device with that id in the DB...serious problem + absolutePath.setPath( "/" ); + absolutePath.addPath( relativePath.path() ); + absolutePath.cleanPath(); + warning() << "Device " << deviceId << " not in database, this should never happen! Returning " << absolutePath.path() << endl; + } + else + { + absolutePath.setPath( lastMountPoint.first() ); + absolutePath.addPath( relativePath.path() ); + absolutePath.cleanPath(); +// debug() << "Device " << deviceId << " not mounted, using last mount point and returning " << absolutePath.path() << endl; + } + } +} + +QString +MountPointManager::getAbsolutePath( const int deviceId, const QString& relativePath ) const +{ + KURL rpath; + rpath.setProtocol("file"); + rpath.setPath( relativePath ); + KURL url; + getAbsolutePath( deviceId, rpath, url ); + return url.path(); +} + +void +MountPointManager::getRelativePath( const int deviceId, const KURL& absolutePath, KURL& relativePath ) const +{ + m_handlerMapMutex.lock(); + if ( deviceId != -1 && m_handlerMap.contains( deviceId ) ) + { + //FIXME max: returns garbage if the absolute path is actually not under the device's mount point + QString rpath = KURL::relativePath( m_handlerMap[deviceId]->getDevicePath(), absolutePath.path() ); + m_handlerMapMutex.unlock(); + relativePath.setPath( rpath ); + } + else + { + m_handlerMapMutex.unlock(); + //TODO: better error handling + QString rpath = KURL::relativePath( "/", absolutePath.path() ); + relativePath.setPath( rpath ); + } +} + +QString +MountPointManager::getRelativePath( const int deviceId, const QString& absolutePath ) const +{ + KURL url; + getRelativePath( deviceId, KURL::fromPathOrURL( absolutePath ), url ); + return url.path(); +} + +void +MountPointManager::mediumChanged( const Medium *m ) +{ + DEBUG_BLOCK + if ( !m ) return; + if ( m->isMounted() ) + { + foreachType( FactoryList, m_mediumFactories ) + { + if ( (*it)->canHandle ( m ) ) + { + debug() << "found handler for " << m->id() << endl; + DeviceHandler *handler = (*it)->createHandler( m ); + if( !handler ) + { + debug() << "Factory " << (*it)->type() << "could not create device handler" << endl; + break; + } + int key = handler->getDeviceID(); + m_handlerMapMutex.lock(); + if ( m_handlerMap.contains( key ) ) + { + debug() << "Key " << key << " already exists in handlerMap, replacing" << endl; + delete m_handlerMap[key]; + m_handlerMap.erase( key ); + } + m_handlerMap.insert( key, handler ); + m_handlerMapMutex.unlock(); + debug() << "added device " << key << " with mount point " << m->mountPoint() << endl; + emit mediumConnected( key ); + break; //we found the added medium and don't have to check the other device handlers + } + } + } + else + { + m_handlerMapMutex.lock(); + foreachType( HandlerMap, m_handlerMap ) + { + if ( it.data()->deviceIsMedium( m ) ) + { + delete it.data(); + int key = it.key(); + m_handlerMap.erase( key ); + debug() << "removed device " << key << endl; + m_handlerMapMutex.unlock(); + emit mediumRemoved( key ); + //we found the medium which was removed, so we can abort the loop + return; + } + } + m_handlerMapMutex.unlock(); + } +} + +void +MountPointManager::mediumRemoved( const Medium *m ) +{ + DEBUG_BLOCK + if ( !m ) + { + //reinit? + } + else + { + //this works for USB devices, special cases might be required for other devices + m_handlerMapMutex.lock(); + foreachType( HandlerMap, m_handlerMap ) + { + if ( it.data()->deviceIsMedium( m ) ) + { + delete it.data(); + int key = it.key(); + m_handlerMap.erase( key ); + debug() << "removed device " << key << endl; + m_handlerMapMutex.unlock(); + emit mediumRemoved( key ); + //we found the medium which was removed, so we can abort the loop + return; + } + } + m_handlerMapMutex.unlock(); + } +} + +void +MountPointManager::mediumAdded( const Medium *m ) +{ + DEBUG_BLOCK + if ( !m ) return; + if ( m->isMounted() ) + { + debug() << "Device added and mounted, checking handlers" << endl; + foreachType( FactoryList, m_mediumFactories ) + { + if ( (*it)->canHandle ( m ) ) + { + debug() << "found handler for " << m->id() << endl; + DeviceHandler *handler = (*it)->createHandler( m ); + if( !handler ) + { + debug() << "Factory " << (*it)->type() << "could not create device handler" << endl; + break; + } + int key = handler->getDeviceID(); + m_handlerMapMutex.lock(); + if ( m_handlerMap.contains( key ) ) + { + debug() << "Key " << key << " already exists in handlerMap, replacing" << endl; + delete m_handlerMap[key]; + m_handlerMap.erase( key ); + } + m_handlerMap.insert( key, handler ); + m_handlerMapMutex.unlock(); + debug() << "added device " << key << " with mount point " << m->mountPoint() << endl; + emit mediumConnected( key ); + break; //we found the added medium and don't have to check the other device handlers + } + } + } +} + +IdList +MountPointManager::getMountedDeviceIds() const { + m_handlerMapMutex.lock(); + IdList list( m_handlerMap.keys() ); + m_handlerMapMutex.unlock(); + list.append( -1 ); + return list; +} + +QStringList +MountPointManager::collectionFolders( ) +{ + //TODO max: cache data + QStringList result; + KConfig* const folders = Amarok::config( "Collection Folders" ); + IdList ids = getMountedDeviceIds(); + foreachType( IdList, ids ) + { + QStringList rpaths = folders->readListEntry( QString::number( *it ) ); + for( QStringList::ConstIterator strIt = rpaths.begin(), end = rpaths.end(); strIt != end; ++strIt ) + { + QString absPath; + if ( *strIt == "./" ) + { + absPath = getMountPointForId( *it ); + } + else + { + absPath = getAbsolutePath( *it, *strIt ); + } + if ( !result.contains( absPath ) ) + result.append( absPath ); + } + } + return result; +} + +void +MountPointManager::setCollectionFolders( const QStringList &folders ) +{ + //TODO max: cache data + typedef QMap FolderMap; + KConfig* const folderConf = Amarok::config( "Collection Folders" ); + FolderMap folderMap; + foreach( folders ) + { + int id = getIdForUrl( *it ); + QString rpath = getRelativePath( id, *it ); + if ( folderMap.contains( id ) ) { + if ( !folderMap[id].contains( rpath ) ) + folderMap[id].append( rpath ); + } + else + folderMap[id] = QStringList( rpath ); + } + //make sure that collection folders on devices which are not in foldermap are deleted + IdList ids = getMountedDeviceIds(); + foreachType( IdList, ids ) + { + if( !folderMap.contains( *it ) ) + { + folderConf->deleteEntry( QString::number( *it ) ); + } + } + foreachType( FolderMap, folderMap ) + { + folderConf->writeEntry( QString::number( it.key() ), it.data() ); + } +} + +void +MountPointManager::migrateStatistics() +{ + QStringList urls = CollectionDB::instance()->query( "SELECT url FROM statistics WHERE deviceid = -2;" ); + foreach( urls ) + { + if ( QFile::exists( *it) ) + { + int deviceid = getIdForUrl( *it ); + QString rpath = getRelativePath( deviceid, *it ); + QString update = QString( "UPDATE statistics SET deviceid = %1, url = '%2'" ) + .arg( deviceid ) + .arg( CollectionDB::instance()->escapeString( rpath ) ); + update += QString( " WHERE url = '%1' AND deviceid = -2;" ) + .arg( CollectionDB::instance()->escapeString( *it ) ); + CollectionDB::instance()->query( update ); + } + } +} + +void +MountPointManager::updateStatisticsURLs( bool changed ) +{ + if ( changed ) + QTimer::singleShot( 0, this, SLOT( startStatisticsUpdateJob() ) ); +} + +void +MountPointManager::startStatisticsUpdateJob() +{ + ThreadManager::instance()->queueJob( new UrlUpdateJob( this ) ); +} + +void +MountPointManager::handleMissingMediaManager() +{ + //TODO this method should activate a fallback mode which simply shows all songs and uses the + //device's last mount point to build the absolute path + m_noDeviceManager = true; + //Amarok::StatusBar::instance()->longMessage( i18n( "BlaBla" ), KDE::StatusBar::Warning ); +} + +void +MountPointManager::checkDeviceAvailability() +{ + //code to actively scan for devices which are not supported by KDE mediamanager should go here + //method is not actually called yet +} + +bool UrlUpdateJob::doJob( ) +{ + DEBUG_BLOCK + updateStatistics(); + updateLabels(); + return true; +} + +void UrlUpdateJob::updateStatistics( ) +{ + CollectionDB *collDB = CollectionDB::instance(); + MountPointManager *mpm = MountPointManager::instance(); + QStringList urls = collDB->query( "SELECT s.deviceid,s.url " + "FROM statistics AS s LEFT JOIN tags AS t ON s.deviceid = t.deviceid AND s.url = t.url " + "WHERE t.url IS NULL AND s.deviceid != -2;" ); + debug() << "Trying to update " << urls.count() / 2 << " statistics rows" << endl; + foreach( urls ) + { + int deviceid = (*it).toInt(); + QString rpath = *++it; + QString realURL = mpm->getAbsolutePath( deviceid, rpath ); + if( QFile::exists( realURL ) ) + { + int newDeviceid = mpm->getIdForUrl( realURL ); + if( newDeviceid == deviceid ) + continue; + QString newRpath = mpm->getRelativePath( newDeviceid, realURL ); + + int statCount = collDB->query( + QString( "SELECT COUNT( url ) FROM statistics WHERE deviceid = %1 AND url = '%2';" ) + .arg( newDeviceid ) + .arg( collDB->escapeString( newRpath ) ) ).first().toInt(); + if( statCount ) + continue; //statistics row with new URL/deviceid values already exists + + QString sql = QString( "UPDATE statistics SET deviceid = %1, url = '%2'" ) + .arg( newDeviceid ).arg( collDB->escapeString( newRpath ) ); + sql += QString( " WHERE deviceid = %1 AND url = '%2';" ) + .arg( deviceid ).arg( collDB->escapeString( rpath ) ); + collDB->query( sql ); + } + } +} + +void UrlUpdateJob::updateLabels( ) +{ + CollectionDB *collDB = CollectionDB::instance(); + MountPointManager *mpm = MountPointManager::instance(); + QStringList labels = collDB->query( "SELECT l.deviceid,l.url " + "FROM tags_labels AS l LEFT JOIN tags as t ON l.deviceid = t.deviceid AND l.url = t.url " + "WHERE t.url IS NULL;" ); + debug() << "Trying to update " << labels.count() / 2 << " tags_labels rows" << endl; + foreach( labels ) + { + int deviceid = (*it).toInt(); + QString rpath = *++it; + QString realUrl = mpm->getAbsolutePath( deviceid, rpath ); + if( QFile::exists( realUrl ) ) + { + int newDeviceid = mpm->getIdForUrl( realUrl ); + if( newDeviceid == deviceid ) + continue; + QString newRpath = mpm->getRelativePath( newDeviceid, realUrl ); + + //only update rows if there is not already a row with the new deviceid/rpath and the same labelid + QStringList labelids = collDB->query( + QString( "SELECT labelid FROM tags_labels WHERE deviceid = %1 AND url = '%2';" ) + .arg( QString::number( newDeviceid ), collDB->escapeString( newRpath ) ) ); + QString existingLabelids; + if( !labelids.isEmpty() ) + { + existingLabelids = " AND labelid NOT IN ("; + foreach( labelids ) + { + if( it != labelids.begin() ) + existingLabelids += ','; + existingLabelids += *it; + } + existingLabelids += ')'; + } + QString sql = QString( "UPDATE tags_labels SET deviceid = %1, url = '%2' " + "WHERE deviceid = %3 AND url = '%4'%5;" ) + .arg( newDeviceid ) + .arg( collDB->escapeString( newRpath ), + QString::number( deviceid ), + collDB->escapeString( rpath ), + existingLabelids ); + collDB->query( sql ); + } + } +} + +#include "mountpointmanager.moc" diff --git a/amarok/src/mountpointmanager.h b/amarok/src/mountpointmanager.h new file mode 100644 index 00000000..a7f470d3 --- /dev/null +++ b/amarok/src/mountpointmanager.h @@ -0,0 +1,259 @@ +/* + * Copyright (c) 2006-2007 Maximilian Kossick + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU 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 AMAROK_MOUNTPOINTMANAGER_H +#define AMAROK_MOUNTPOINTMANAGER_H + +#include "amarok.h" +#include "amarok_export.h" +#include "collectiondb.h" +#include "medium.h" +#include "plugin/plugin.h" +#include "pluginmanager.h" +#include "threadmanager.h" + +#include +#include + +#include +#include +#include +#include +#include + +class DeviceHandler; +class DeviceHandlerFactory; + +typedef QValueList IdList; +typedef QPtrList FactoryList; +typedef QMap HandlerMap; + + +class LIBAMAROK_EXPORT DeviceHandlerFactory : public Amarok::Plugin +{ +public: + DeviceHandlerFactory() {}; + virtual ~DeviceHandlerFactory() {}; + + /** + * checks whether a DeviceHandler subclass can handle a given Medium. + * @param m the connected medium + * @return true if the DeviceHandler implementation can handle the medium, + * false otherwise + */ + virtual bool canHandle( const Medium* m ) const = 0; + + /** + * tells the MountPointManager whether it makes sense to ask the factory to + * create a Devicehandler when a new Medium was connected + * @return true if the factory can create DeviceHandlers from Medium instances + */ + virtual bool canCreateFromMedium() const = 0; + + /** + * creates a DeviceHandler which represents the Medium. + * @param c the Medium for which a DeviceHandler is required + * @return a DeviceHandler or 0 if the factory cannot handle the Medium + */ + virtual DeviceHandler* createHandler( const Medium* m ) const = 0; + + virtual bool canCreateFromConfig() const = 0; + + virtual DeviceHandler* createHandler( const KConfig* c ) const = 0; + + /** + * returns the type of the DeviceHandler. Should be the same as the value used in + * ~/.kde/share/config/amarokrc + * @return a QString describing the type of the DeviceHandler + */ + virtual QString type() const = 0; + +}; + +/** + * + * + */ +class DeviceHandler +{ +public: + DeviceHandler() {}; + virtual ~DeviceHandler() {}; + + + virtual bool isAvailable() const = 0; + + /** + * returns the type of the DeviceHandler. Should be the same as the value used in + * ~/.kde/share/config/amarokrc + * @return a QString describing the type of the DeviceHandler + */ + virtual QString type() const = 0; + + /** + * returns an absolute path which is guaranteed to be playable by amarok's current engine. (based on an + * idea by andrewt512: this method would only be called when we actually want to play the file, not when we + * simply want to show it to the user. It could for example download a file using KIO and return a path to a + * temporary file. Needs some more thought and is not actually used at the moment. + * @param absolutePath + * @param relativePath + */ + virtual void getPlayableURL( KURL &absolutePath, const KURL &relativePath ) = 0; + + /** + * builds an absolute path from a relative path and DeviceHandler specific information. The absolute path + * is not necessarily playable! (based on an idea by andrewt512: allows better handling of files stored in remote * collections. this method would return a "pretty" URL which might not be playable by amarok's engines. + * @param absolutePath the not necessarily playbale absolute path + * @param relativePath the device specific relative path + */ + virtual void getURL( KURL &absolutePath, const KURL &relativePath ) = 0; + + /** + * retrieves the unique database id of a given Medium. Implementations are responsible + * for generating a (sufficiently) unique value which identifies the Medium. + * Additionally, implementations must recognize unknown mediums and store the necessary + * information to recognize them the next time they are connected in the database. + * @return unique identifier which can be used as a foreign key to the media table. + */ + virtual int getDeviceID() = 0; + + virtual const QString &getDevicePath() const = 0; + + /** + * allows MountPointManager to check if a device handler handles a specific medium. + * @param m + * @return true if the device handler handles the Medium m + */ + virtual bool deviceIsMedium( const Medium *m ) const = 0; +}; + +/** + * @author Maximilian Kossick + */ +class MountPointManager : public QObject { +Q_OBJECT + +signals: + void mediumConnected( int deviceid ); + void mediumRemoved( int deviceid ); + +public: + //the methods of this class a called *very* often. make sure they are as fast as possible + // (inline them?) + + /** + * factory method. + * @return a MountPointManager instance + */ + static MountPointManager *instance(); + + /** + * + * @param url + * @return + */ + int getIdForUrl( KURL url ); + int getIdForUrl( const QString &url ); + /** + * + * @param id + * @return + */ + QString getMountPointForId( const int id ) const; + /** + * builds the absolute path from the mount point of the medium and the given relative + * path. + * @param deviceId the medium(device)'s unique id + * @param relativePath relative path on the medium + * @return the absolute path + */ + void getAbsolutePath( const int deviceId, const KURL& relativePath, KURL& absolutePath ) const; + QString getAbsolutePath ( const int deviceId, const QString& relativePath ) const; + /** + * calculates a file's/directory's relative path on a given device. + * @param deviceId the unique id which identifies the device the file/directory is supposed to be on + * @param absolutePath the file's/directory's absolute path + * @param relativePath the calculated relative path + */ + void getRelativePath( const int deviceId, const KURL& absolutePath, KURL& relativePath ) const; + QString getRelativePath( const int deviceId, const QString& absolutePath ) const; + /** + * allows calling code to access the ids of all active devices + * @return the ids of all devices which are currently mounted or otherwise accessible + */ + IdList getMountedDeviceIds() const; + + QStringList collectionFolders(); + void setCollectionFolders( const QStringList &folders ); + +public slots: + void mediumAdded( const Medium *m ); + /** + * initiates the update of the class' internal list of mounted mediums. + * @param m the medium whose status changed + */ + void mediumChanged( const Medium* m ); + void mediumRemoved( const Medium* m ); + + void updateStatisticsURLs( bool changed = true ); + +private slots: + void migrateStatistics(); + void checkDeviceAvailability(); + void startStatisticsUpdateJob(); + +private: + MountPointManager(); + + ~MountPointManager(); + + /** + * checks whether a medium identified by its unique database id is currently mounted. + * Note: does not handle deviceId = -1! It only checks real devices + * @param deviceId the mediums unique id + * @return true if the medium is mounted, false otherwise + */ + bool isMounted ( const int deviceId ) const; + void init(); + void handleMissingMediaManager(); + /** + * maps a device id to a mount point. does only work for mountable filesystems and needs to be + * changed for the real Dynamic Collection implementation. + */ + HandlerMap m_handlerMap; + mutable QMutex m_handlerMapMutex; + FactoryList m_mediumFactories; + FactoryList m_remoteFactories; + bool m_noDeviceManager; + +}; + +class UrlUpdateJob : public ThreadManager::DependentJob +{ +public: + UrlUpdateJob( QObject *dependent ) : DependentJob( dependent, "UrlUpdateJob" ) {} + + virtual bool doJob(); + + virtual void completeJob() {} + +private: + void updateStatistics(); + void updateLabels(); +}; + +#endif diff --git a/amarok/src/multitabbar.cpp b/amarok/src/multitabbar.cpp new file mode 100644 index 00000000..c374f7cf --- /dev/null +++ b/amarok/src/multitabbar.cpp @@ -0,0 +1,1308 @@ +/*************************************************************************** + kmultitabbar.cpp - description + ------------------- + begin : 2001 + copyright : (C) 2001,2002,2003 by Joseph Wenninger + (C) 2005 by Mark Kretschmann +***************************************************************************/ + +/*************************************************************************** + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +***************************************************************************/ + +#include "debug.h" +#include "multitabbar.h" +#include "multitabbar.moc" +#include "multitabbar_p.h" +#include "multitabbar_p.moc" + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#define NEARBYINT(i) ((int(float(i) + 0.5))) + +namespace Amarok { extern KConfig *config( const QString& ); } + +class MultiTabBarTabPrivate +{ + public: + QPixmap pix; +}; + +class MultiTabBarButtonPrivate +{ + public: + MultiTabBarButtonPrivate() : finalDropTarget( 0 ) {} + DropProxyTarget *finalDropTarget; +}; + + +MultiTabBarInternal::MultiTabBarInternal( QWidget *parent, MultiTabBar::MultiTabBarMode bm ) : QScrollView( parent ) +{ + m_expandedTabSize = -1; + m_showActiveTabTexts = false; + m_tabs.setAutoDelete( true ); + m_barMode = bm; + setHScrollBarMode( AlwaysOff ); + setVScrollBarMode( AlwaysOff ); + if ( bm == MultiTabBar::Vertical ) { + box = new QWidget( viewport() ); + mainLayout = new QVBoxLayout( box ); + mainLayout->setAutoAdd( true ); + box->setFixedWidth( 24 ); + setFixedWidth( 24 ); + } else { + box = new QWidget( viewport() ); + mainLayout = new QHBoxLayout( box ); + mainLayout->setAutoAdd( true ); + box->setFixedHeight( 24 ); + setFixedHeight( 24 ); + } + addChild( box ); + setFrameStyle( NoFrame ); + viewport() ->setBackgroundMode( Qt::PaletteBackground ); + /* box->setPaletteBackgroundColor(Qt::red); + setPaletteBackgroundColor(Qt::green);*/ +} + +void MultiTabBarInternal::setStyle( enum MultiTabBar::MultiTabBarStyle style ) +{ + m_style = style; + for ( uint i = 0;i < m_tabs.count();i++ ) + m_tabs.at( i ) ->setStyle( m_style ); + + if ( ( m_style == MultiTabBar::KDEV3 ) || + ( m_style == MultiTabBar::KDEV3ICON ) || + ( m_style == MultiTabBar::AMAROK ) ) { + delete mainLayout; + mainLayout = 0; + resizeEvent( 0 ); + } else if ( mainLayout == 0 ) { + if ( m_barMode == MultiTabBar::Vertical ) { + box = new QWidget( viewport() ); + mainLayout = new QVBoxLayout( box ); + box->setFixedWidth( 24 ); + setFixedWidth( 24 ); + } else { + box = new QWidget( viewport() ); + mainLayout = new QHBoxLayout( box ); + box->setFixedHeight( 24 ); + setFixedHeight( 24 ); + } + addChild( box ); + for ( uint i = 0;i < m_tabs.count();i++ ) + mainLayout->add( m_tabs.at( i ) ); + mainLayout->setAutoAdd( true ); + + } + viewport() ->repaint(); +} + +void MultiTabBarInternal::drawContents ( QPainter * paint, int clipx, int clipy, int clipw, int cliph ) +{ + QScrollView::drawContents ( paint , clipx, clipy, clipw, cliph ); + + if ( m_position == MultiTabBar::Right ) { + + paint->setPen( colorGroup().shadow() ); + paint->drawLine( 0, 0, 0, viewport() ->height() ); + paint->setPen( colorGroup().background().dark( 120 ) ); + paint->drawLine( 1, 0, 1, viewport() ->height() ); + + + } else + if ( m_position == MultiTabBar::Left ) { + paint->setPen( colorGroup().light() ); + paint->drawLine( 23, 0, 23, viewport() ->height() ); + paint->drawLine( 22, 0, 22, viewport() ->height() ); + + paint->setPen( colorGroup().shadow() ); + paint->drawLine( 0, 0, 0, viewport() ->height() ); + } else + if ( m_position == MultiTabBar::Bottom ) { + paint->setPen( colorGroup().shadow() ); + paint->drawLine( 0, 0, viewport() ->width(), 0 ); + paint->setPen( colorGroup().background().dark( 120 ) ); + paint->drawLine( 0, 1, viewport() ->width(), 1 ); + } else { + paint->setPen( colorGroup().light() ); + paint->drawLine( 0, 23, viewport() ->width(), 23 ); + paint->drawLine( 0, 22, viewport() ->width(), 22 ); + + /* paint->setPen(colorGroup().shadow()); + paint->drawLine(0,0,0,viewport()->height());*/ + } +} + +void MultiTabBarInternal::contentsMousePressEvent( QMouseEvent *ev ) +{ + ev->ignore(); +} + + +void MultiTabBarInternal::showTabSelectionMenu(QPoint pos) +{ + + KPopupMenu popup; + popup.insertTitle( i18n("Browsers") , /*id*/ -1, /*index*/ 1 ); + popup.setCheckable( true ); + for( uint i = 0; i < m_tabs.count(); i++ ) { + MultiTabBarTab* tab = m_tabs.at( i ); + popup.insertItem( tab->text(), i ); + popup.setItemChecked(i, tab->visible() ? true : false); + } + + int col = popup.exec(pos); + if ( col >= 0 ) + setTabVisible( col, !popup.isItemChecked(col) ); + +} + +void MultiTabBarInternal::mousePressEvent( QMouseEvent *ev ) +{ + if ( ev->button() != QMouseEvent::RightButton ){ + ev->ignore(); + return; + } + + // right button pressed + showTabSelectionMenu(ev->globalPos()); + +} + + +#define CALCDIFF(m_tabs,diff,i) if (m_lines>(int)lines) {\ + /*kdDebug()<<"i="<geometry()<setGeometry( 0, 0, width(), height() ); + int lines = 1; + uint space; + float tmp = 0; + if ( ( m_position == MultiTabBar::Bottom ) || ( m_position == MultiTabBar::Top ) ) + space = width(); + else + space = height(); // made space for tab management button + + int cnt = 0; + //CALCULATE LINES + const uint tabCount = m_tabs.count(); + + for ( uint i = 0;i < tabCount;i++ ) { + if ( ! m_tabs.at( i ) ->visible() ) continue; + cnt++; + tmp += sizePerTab(); + if ( tmp > space ) { + if ( cnt > 1 ) i--; + else if ( i == ( tabCount - 1 ) ) break; + cnt = 0; + tmp = 0; + lines++; + } + } + //SET SIZE & PLACE + float diff = 0; + cnt = 0; + + if ( ( m_position == MultiTabBar::Bottom ) || ( m_position == MultiTabBar::Top ) ) { + + setFixedHeight( lines * 24 ); + box->setFixedHeight( lines * 24 ); + m_lines = height() / 24 - 1; + lines = 0; + CALCDIFF( m_tabs, diff, 0 ) + tmp = -diff; + + //kdDebug()<<"m_lines recalculated="<visible() ) continue; + cnt++; + tmp += sizePerTab() + diff; + if ( tmp > space ) { + //kdDebug()<<"about to start new line"< 1 ) { + CALCDIFF( m_tabs, diff, i ) + i--; + } else { + //kdDebug()<<"placing line on old line"<neededSize()<<"/"<height() : m_tabs.at( i ) ->width() ); + if ( ( m_position == MultiTabBar::Bottom ) || ( m_position == MultiTabBar::Top ) ) + box->setGeometry( 0, 0, size, height() ); + else box->setGeometry( 0, 0, width(), size ); + + } +} + + +void MultiTabBarInternal::showActiveTabTexts( bool show ) +{ + m_showActiveTabTexts = show; +} + +MultiTabBarTab* MultiTabBarInternal::tab( int id ) const +{ + for ( QPtrListIterator it( m_tabs );it.current();++it ) { + if ( it.current() ->id() == id ) return it.current(); + } + return 0; +} + +bool MultiTabBarInternal::eventFilter( QObject *, QEvent *e ) +{ + if ( e->type() == QEvent::Resize ) + resizeEvent( 0 ); + + //PATCH by markey: Allow switching of tabs with mouse wheel + if ( e->type() == QEvent::Wheel ) { + QWheelEvent* event = static_cast( e ); + const int delta = event->delta() / 120; + + // Determine which tab is currently active + uint i; + for( i = 0; i < m_tabs.count(); i++ ) + if ( m_tabs.at( i )->isOn() ) break; + + // Calculate index of the new tab to activate + int newTab = i - delta; + while (true) { + if ( newTab < 0 ) { + newTab = i; + break; + } + if ( newTab > (int)m_tabs.count() - 1 ) { + newTab = i; + break; + } + if ( m_tabs.at( newTab )->visible() && m_tabs.at( newTab )->isEnabled() ) + break; + // try one tab more + newTab -= delta; + } + + if ( i < m_tabs.count() && newTab != (int)i ) + m_tabs.at( newTab )->animateClick(); + + // Must return true here for the wheel to work properly + return true; + } + + return false; +} + +int MultiTabBarInternal::appendTab( const QPixmap &pic , int id, const QString& text, const QString& identifier ) +{ + MultiTabBarTab * tab; + m_tabs.append( tab = new MultiTabBarTab( pic, text, id, box, m_position, m_style ) ); + tab->setIdentifier( identifier ); + tab->installEventFilter( this ); + tab->showActiveTabText( m_showActiveTabTexts ); + tab->setVisible( Amarok::config( "BrowserBar" )->readBoolEntry( identifier, true ) ); + + if ( m_style == MultiTabBar::KONQSBC ) { + if ( m_expandedTabSize < tab->neededSize() ) { + m_expandedTabSize = tab->neededSize(); + for ( uint i = 0;i < m_tabs.count();i++ ) + m_tabs.at( i ) ->setSize( m_expandedTabSize ); + + } else tab->setSize( m_expandedTabSize ); + } else tab->updateState(); + + if ( tab->visible() ) { + tab->show(); + resizeEvent( 0 ); + } else tab->hide(); + + return 0; +} + +void MultiTabBarInternal::removeTab( int id ) +{ + for ( uint pos = 0;pos < m_tabs.count();pos++ ) { + if ( m_tabs.at( pos ) ->id() == id ) { + m_tabs.remove( pos ); + resizeEvent( 0 ); + break; + } + } +} + +void MultiTabBarInternal::setTabVisible( int id, bool visible ) +{ + for ( uint pos = 0;pos < m_tabs.count();pos++ ) { + if ( m_tabs.at( pos ) ->id() == id ) { + MultiTabBarTab* tab = m_tabs.at( pos ); + + tab->setVisible( visible ); + Amarok::config( "BrowserBar" )->writeEntry( tab->identifier(), tab->visible() ); + + if ( tab->visible() ) + tab->show(); + else { + tab->hide(); + // if the user wants to hide the currently active tab + // turn on another tab + if ( tab->isOn() ) + for( uint i = 0; i < m_tabs.count(); i++ ) { + if ( m_tabs.at( i )->visible() ) { + m_tabs.at( i )->animateClick(); + break; + } + } + } + // redraw the bar + resizeEvent( 0 ); + } + } +} + +void MultiTabBarInternal::setPosition( enum MultiTabBar::MultiTabBarPosition pos ) +{ + m_position = pos; + for ( uint i = 0;i < m_tabs.count();i++ ) + m_tabs.at( i ) ->setTabsPosition( m_position ); + viewport() ->repaint(); +} + + +uint MultiTabBarInternal::visibleTabCount() +{ + uint visibleTabCount = 0; + for ( uint i = 0; i < m_tabs.count(); i++ ) + if ( m_tabs.at( i ) ->visible() ) visibleTabCount++; + + return visibleTabCount; +} + +uint MultiTabBarInternal::sizePerTab() +{ + uint size; + if( m_position == MultiTabBar::Left || m_position == MultiTabBar::Right ) + /* HACK: width() is the "Manage Tabs" button size :-( */ + size = (height() - 3 - width() ) / visibleTabCount(); + else + size = (width() - 3 ) / visibleTabCount(); + + return size; +} + + +MultiTabBarButton::MultiTabBarButton( const QPixmap& pic, const QString& text, QPopupMenu *popup, + int id, QWidget *parent, MultiTabBar::MultiTabBarPosition pos, MultiTabBar::MultiTabBarStyle style ) + : QPushButton( QIconSet(), text, parent ) + , m_position( pos ) + , m_style( style ) + , m_id( id ) + , m_animCount( 0 ) + , m_animTimer( new QTimer( this ) ) + , m_dragSwitchTimer( new QTimer( this ) ) +{ + setAcceptDrops( true ); + setIconSet( pic ); + setText( text ); + if ( popup ) setPopup( popup ); + setFlat( true ); + setFixedHeight( 24 ); + setFixedWidth( 24 ); + +// QToolTip::add( this, text ); // Deactivated cause it's annoying + connect( this, SIGNAL( clicked() ), this, SLOT( slotClicked() ) ); + connect( m_animTimer, SIGNAL( timeout() ), this, SLOT( slotAnimTimer() ) ); + connect( m_dragSwitchTimer, SIGNAL( timeout() ), this, SLOT( slotDragSwitchTimer() ) ); +} + +MultiTabBarButton::MultiTabBarButton( const QString& text, QPopupMenu *popup, + int id, QWidget *parent, MultiTabBar::MultiTabBarPosition pos, MultiTabBar::MultiTabBarStyle style ) + : QPushButton( QIconSet(), text, parent ), m_style( style ) + , m_animCount( 0 ) + , m_animTimer( new QTimer( this ) ) + , m_dragSwitchTimer( new QTimer( this ) ) +{ + d = new MultiTabBarButtonPrivate; + setAcceptDrops( true ); + setText( text ); + m_position = pos; + if ( popup ) setPopup( popup ); + setFlat( true ); + setFixedHeight( 24 ); + setFixedWidth( 24 ); + m_id = id; +// QToolTip::add( this, text ); + + connect( this, SIGNAL( clicked() ), this, SLOT( slotClicked() ) ); + connect( m_animTimer, SIGNAL( timeout() ), this, SLOT( slotAnimTimer() ) ); + connect( m_dragSwitchTimer, SIGNAL( timeout() ), this, SLOT( slotDragSwitchTimer() ) ); +} + +MultiTabBarButton::~MultiTabBarButton() +{ + delete d; +} + +int MultiTabBarButton::id() const +{ + return m_id; +} + +void MultiTabBarButton::setText( const QString& text ) +{ + QPushButton::setText( text ); + m_text = text; +// QToolTip::add( this, text ); +} + +void MultiTabBarButton::proxyDrops( DropProxyTarget *finalDropTarget ) +{ + d->finalDropTarget = finalDropTarget; +} + +void MultiTabBarButton::slotClicked() +{ + emit clicked( m_id ); +} + +void MultiTabBarButton::setPosition( MultiTabBar::MultiTabBarPosition pos ) +{ + m_position = pos; + repaint(); +} + +void MultiTabBarButton::setStyle( MultiTabBar::MultiTabBarStyle style ) +{ + m_style = style; + repaint(); +} + +void MultiTabBarButton::hideEvent( QHideEvent* he ) +{ + QPushButton::hideEvent( he ); + MultiTabBar *tb = dynamic_cast( parentWidget() ); + if ( tb ) tb->updateSeparator(); +} + +void MultiTabBarButton::showEvent( QShowEvent* he ) +{ + QPushButton::showEvent( he ); + MultiTabBar *tb = dynamic_cast( parentWidget() ); + if ( tb ) tb->updateSeparator(); +} + +void MultiTabBarButton::enterEvent( QEvent* ) +{ + m_animEnter = true; + m_animCount = 0; + + m_animTimer->start( ANIM_INTERVAL ); +} + +void MultiTabBarButton::leaveEvent( QEvent* ) +{ + // This can happen if you enter and leave the tab quickly + if ( m_animCount == 0 ) + m_animCount = 1; + + m_animEnter = false; + m_animTimer->start( ANIM_INTERVAL ); +} + +void MultiTabBarButton::dragEnterEvent ( QDragEnterEvent *e ) +{ + enterEvent ( e ); + e->accept( d->finalDropTarget ); +} + +void MultiTabBarButton::dragMoveEvent ( QDragMoveEvent * ) +{ + if ( !m_dragSwitchTimer->isActive() ) + m_dragSwitchTimer->start( ANIM_INTERVAL * ANIM_MAX + 300, true ); +} + +void MultiTabBarButton::dragLeaveEvent ( QDragLeaveEvent *e ) +{ + m_dragSwitchTimer->stop(); + leaveEvent( e ); +} + +void MultiTabBarButton::dropEvent( QDropEvent *e ) +{ + m_dragSwitchTimer->stop(); + if( d->finalDropTarget ) + d->finalDropTarget->dropProxyEvent( e ); + leaveEvent( e ); +} + +void MultiTabBarButton::slotDragSwitchTimer() +{ + emit ( initiateDrag ( m_id ) ); +} + +void MultiTabBarButton::slotAnimTimer() +{ + if ( m_animEnter ) { + m_animCount += 1; + repaint( false ); + if ( m_animCount >= ANIM_MAX ) + m_animTimer->stop(); + } else { + m_animCount -= 1; + repaint( false ); + if ( m_animCount <= 0 ) + m_animTimer->stop(); + } +} + +QSize MultiTabBarButton::sizeHint() const +{ + constPolish(); + + int w = 0, h = 0; + + // calculate contents size... +#ifndef QT_NO_ICONSET + if ( iconSet() && !iconSet() ->isNull() ) { + int iw = iconSet() ->pixmap( QIconSet::Small, QIconSet::Normal ).width() + 4; + int ih = iconSet() ->pixmap( QIconSet::Small, QIconSet::Normal ).height(); + w += iw; + h = QMAX( h, ih ); + } +#endif + if ( isMenuButton() ) + w += style().pixelMetric( QStyle::PM_MenuButtonIndicator, this ); + + if ( pixmap() ) { + QPixmap * pm = const_cast< QPixmap * >( pixmap() ); + w += pm->width(); + h += pm->height(); + } else { + QString s( text() ); + bool empty = s.isEmpty(); + if ( empty ) + s = QString::fromLatin1( "XXXX" ); + QFontMetrics fm = fontMetrics(); + QSize sz = fm.size( ShowPrefix, s ); + if ( !empty || !w ) + w += sz.width(); + if ( !empty || !h ) + h = QMAX( h, sz.height() ); + } + +// //PATCH by markey +// if ( ( m_style == MultiTabBar::AMAROK ) ) { +// if( m_position == MultiTabBar::Left || m_position == MultiTabBar::Right ) +// w = ( parentWidget()->height() - 3 ) / NUM_TABS; +// else +// h = ( parentWidget()->width() - 3 ) / NUM_TABS; +// } + + return ( style().sizeFromContents( QStyle::CT_ToolButton, this, QSize( w, h ) ). + expandedTo( QApplication::globalStrut() ) ); +} + + +MultiTabBarTab::MultiTabBarTab( const QPixmap& pic, const QString& text, + int id, QWidget *parent, MultiTabBar::MultiTabBarPosition pos, + MultiTabBar::MultiTabBarStyle style ) + : MultiTabBarButton( text, 0, id, parent, pos, style ), + m_visible(true), + m_showActiveTabText( false ) +{ + d = new MultiTabBarTabPrivate(); + setIcon( pic ); + setIdentifier( text ); + m_expandedSize = 24; + setToggleButton( true ); + + // Prevent flicker on redraw + setWFlags( getWFlags() | Qt::WNoAutoErase ); +} + +MultiTabBarTab::~MultiTabBarTab() +{ + delete d; +} + + +void MultiTabBarTab::setTabsPosition( MultiTabBar::MultiTabBarPosition pos ) +{ + if ( ( pos != m_position ) && ( ( pos == MultiTabBar::Left ) || ( pos == MultiTabBar::Right ) ) ) { + if ( !d->pix.isNull() ) { + QWMatrix temp; // (1.0F, 0.0F, 0.0F, -1.0F, 0.0F, 0.0F); + temp.rotate( 180 ); + d->pix = d->pix.xForm( temp ); + setIconSet( d->pix ); + } + } + + setPosition( pos ); + // repaint(); +} + +void MultiTabBarTab::setIcon( const QString& icon ) +{ + QPixmap pic = SmallIcon( icon ); + setIcon( pic ); +} + +void MultiTabBarTab::setIcon( const QPixmap& icon ) +{ + + if ( m_style != MultiTabBar::KDEV3 ) { + if ( ( m_position == MultiTabBar::Left ) || ( m_position == MultiTabBar::Right ) ) { + QWMatrix rotateMatrix; + if ( m_position == MultiTabBar::Left ) + rotateMatrix.rotate( 90 ); + else + rotateMatrix.rotate( -90 ); + QPixmap pic = icon.xForm( rotateMatrix ); //TODO FIX THIS, THIS SHOWS WINDOW + d->pix = pic; + setIconSet( pic ); + } else setIconSet( icon ); + } +} + +void MultiTabBarTab::slotClicked() +{ + if ( m_animTimer->isActive() ) { + m_animCount = ANIM_MAX; + m_animTimer->stop(); + repaint(); + } + + updateState(); + MultiTabBarButton::slotClicked(); +} + +void MultiTabBarTab::setState( bool b ) +{ + setOn( b ); + updateState(); +} + +void MultiTabBarTab::updateState() +{ + + if ( m_style != MultiTabBar::KONQSBC ) { + if ( ( m_style == MultiTabBar::KDEV3 ) || ( m_style == MultiTabBar::KDEV3ICON ) || ( m_style == MultiTabBar::AMAROK ) || ( isOn() ) ) { + QPushButton::setText( m_text ); + } else { + kdDebug() << "MultiTabBarTab::updateState(): setting text to an empty QString***************" << endl; + QPushButton::setText( QString::null ); + } + + if ( ( m_position == MultiTabBar::Right || m_position == MultiTabBar::Left ) ) { + setFixedWidth( 24 ); + if ( ( m_style == MultiTabBar::KDEV3 ) || ( m_style == MultiTabBar::KDEV3ICON ) || ( m_style == MultiTabBar::AMAROK ) || ( isOn() ) ) { + setFixedHeight( MultiTabBarButton::sizeHint().width() ); + } else setFixedHeight( 36 ); + } else { + setFixedHeight( 24 ); + if ( ( m_style == MultiTabBar::KDEV3 ) || ( m_style == MultiTabBar::KDEV3ICON ) || ( m_style == MultiTabBar::AMAROK ) || ( isOn() ) ) { + setFixedWidth( MultiTabBarButton::sizeHint().width() ); + } else setFixedWidth( 36 ); + } + } else { + if ( ( !isOn() ) || ( !m_showActiveTabText ) ) { + setFixedWidth( 24 ); + setFixedHeight( 24 ); + return ; + } + if ( ( m_position == MultiTabBar::Right || m_position == MultiTabBar::Left ) ) + setFixedHeight( m_expandedSize ); + else + setFixedWidth( m_expandedSize ); + } + QApplication::sendPostedEvents( 0, QEvent::Paint | QEvent::Move | QEvent::Resize | QEvent::LayoutHint ); + QApplication::flush(); +} + +int MultiTabBarTab::neededSize() +{ + return ( ( ( m_style != MultiTabBar::KDEV3 ) ? 24 : 0 ) + QFontMetrics( QFont() ).width( m_text ) + 6 ); +} + +void MultiTabBarTab::setSize( int size ) +{ + m_expandedSize = size; + updateState(); +} + +void MultiTabBarTab::showActiveTabText( bool show ) +{ + m_showActiveTabText = show; +} + +void MultiTabBarTab::drawButtonLabel( QPainter *p ) +{ + drawButton( p ); +} +void MultiTabBarTab::drawButton( QPainter *paint ) +{ + if ( m_style == MultiTabBar::AMAROK ) drawButtonAmarok( paint ); + else if ( m_style != MultiTabBar::KONQSBC ) drawButtonStyled( paint ); + else drawButtonClassic( paint ); +} + +void MultiTabBarTab::drawButtonStyled( QPainter *paint ) +{ + + QSize sh; + const int width = 36; // rotated + const int height = 24; + if ( ( m_style == MultiTabBar::KDEV3 ) || ( m_style == MultiTabBar::KDEV3ICON ) || ( m_style == MultiTabBar::AMAROK ) || ( isOn() ) ) { + if ( ( m_position == MultiTabBar::Left ) || ( m_position == MultiTabBar::Right ) ) + sh = QSize( this->height(), this->width() ); //MultiTabBarButton::sizeHint(); + else + sh = QSize( this->width(), this->height() ); + } else + sh = QSize( width, height ); + + QPixmap pixmap( sh.width(), height ); ///,sh.height()); + pixmap.fill( eraseColor() ); + QPainter painter( &pixmap ); + + + QStyle::SFlags st = QStyle::Style_Default; + + st |= QStyle::Style_Enabled; + + if ( isOn() ) st |= QStyle::Style_On; + + style().drawControl( QStyle::CE_PushButton, &painter, this, QRect( 0, 0, pixmap.width(), pixmap.height() ), colorGroup(), st ); + style().drawControl( QStyle::CE_PushButtonLabel, &painter, this, QRect( 0, 0, pixmap.width(), pixmap.height() ), colorGroup(), st ); + + switch ( m_position ) { + case MultiTabBar::Left: + paint->rotate( -90 ); + paint->drawPixmap( 1 - pixmap.width(), 0, pixmap ); + break; + case MultiTabBar::Right: + paint->rotate( 90 ); + paint->drawPixmap( 0, 1 - pixmap.height(), pixmap ); + break; + + default: + paint->drawPixmap( 0, 0, pixmap ); + break; + } + // style().drawControl(QStyle::CE_PushButtonLabel,painter,this, QRect(0,0,pixmap.width(),pixmap.height()), + // colorGroup(),QStyle::Style_Enabled); +} + +void MultiTabBarTab::drawButtonClassic( QPainter *paint ) +{ + QPixmap pixmap; + if ( iconSet() ) + pixmap = iconSet() ->pixmap( QIconSet::Small, QIconSet::Normal ); + paint->fillRect( 0, 0, 24, 24, colorGroup().background() ); + + if ( !isOn() ) { + + if ( m_position == MultiTabBar::Right ) { + paint->fillRect( 0, 0, 21, 21, QBrush( colorGroup().background() ) ); + + paint->setPen( colorGroup().background().dark( 150 ) ); + paint->drawLine( 0, 22, 23, 22 ); + + paint->drawPixmap( 12 - pixmap.width() / 2, 12 - pixmap.height() / 2, pixmap ); + + paint->setPen( colorGroup().shadow() ); + paint->drawLine( 0, 0, 0, 23 ); + paint->setPen( colorGroup().background().dark( 120 ) ); + paint->drawLine( 1, 0, 1, 23 ); + + } else + if ( ( m_position == MultiTabBar::Bottom ) || ( m_position == MultiTabBar::Top ) ) { + paint->fillRect( 0, 1, 23, 22, QBrush( colorGroup().background() ) ); + + paint->drawPixmap( 12 - pixmap.width() / 2, 12 - pixmap.height() / 2, pixmap ); + + paint->setPen( colorGroup().background().dark( 120 ) ); + paint->drawLine( 23, 0, 23, 23 ); + + + paint->setPen( colorGroup().light() ); + paint->drawLine( 0, 22, 23, 22 ); + paint->drawLine( 0, 23, 23, 23 ); + paint->setPen( colorGroup().shadow() ); + paint->drawLine( 0, 0, 23, 0 ); + paint->setPen( colorGroup().background().dark( 120 ) ); + paint->drawLine( 0, 1, 23, 1 ); + + } else { + paint->setPen( colorGroup().background().dark( 120 ) ); + paint->drawLine( 0, 23, 23, 23 ); + paint->fillRect( 0, 0, 23, 21, QBrush( colorGroup().background() ) ); + paint->drawPixmap( 12 - pixmap.width() / 2, 12 - pixmap.height() / 2, pixmap ); + + paint->setPen( colorGroup().light() ); + paint->drawLine( 23, 0, 23, 23 ); + paint->drawLine( 22, 0, 22, 23 ); + + paint->setPen( colorGroup().shadow() ); + paint->drawLine( 0, 0, 0, 23 ); + + } + + + } else { + if ( m_position == MultiTabBar::Right ) { + paint->setPen( colorGroup().shadow() ); + paint->drawLine( 0, height() - 1, 23, height() - 1 ); + paint->drawLine( 0, height() - 2, 23, height() - 2 ); + paint->drawLine( 23, 0, 23, height() - 1 ); + paint->drawLine( 22, 0, 22, height() - 1 ); + paint->fillRect( 0, 0, 21, height() - 3, QBrush( colorGroup().light() ) ); + paint->drawPixmap( 10 - pixmap.width() / 2, 10 - pixmap.height() / 2, pixmap ); + + if ( m_showActiveTabText ) { + if ( height() < 25 + 4 ) return ; + + QPixmap tpixmap( height() - 25 - 3, width() - 2 ); + QPainter painter( &tpixmap ); + + painter.fillRect( 0, 0, tpixmap.width(), tpixmap.height(), QBrush( colorGroup().light() ) ); + + painter.setPen( colorGroup().text() ); + painter.drawText( 0, + width() / 2 + QFontMetrics( QFont() ).height() / 2, m_text ); + + paint->rotate( 90 ); + kdDebug() << "tpixmap.width:" << tpixmap.width() << endl; + paint->drawPixmap( 25, -tpixmap.height() + 1, tpixmap ); + } + + } else + if ( m_position == MultiTabBar::Top ) { + paint->fillRect( 0, 0, width() - 1, 23, QBrush( colorGroup().light() ) ); + paint->drawPixmap( 10 - pixmap.width() / 2, 10 - pixmap.height() / 2, pixmap ); + if ( m_showActiveTabText ) { + paint->setPen( colorGroup().text() ); + paint->drawText( 25, height() / 2 + QFontMetrics( QFont() ).height() / 2, m_text ); + } + } else + if ( m_position == MultiTabBar::Bottom ) { + paint->setPen( colorGroup().shadow() ); + paint->drawLine( 0, 23, width() - 1, 23 ); + paint->drawLine( 0, 22, width() - 1, 22 ); + paint->fillRect( 0, 0, width() - 1, 21, QBrush( colorGroup().light() ) ); + paint->drawPixmap( 10 - pixmap.width() / 2, 10 - pixmap.height() / 2, pixmap ); + if ( m_showActiveTabText ) { + paint->setPen( colorGroup().text() ); + paint->drawText( 25, height() / 2 + QFontMetrics( QFont() ).height() / 2, m_text ); + } + + } else { + + + paint->setPen( colorGroup().shadow() ); + paint->drawLine( 0, height() - 1, 23, height() - 1 ); + paint->drawLine( 0, height() - 2, 23, height() - 2 ); + paint->fillRect( 0, 0, 23, height() - 3, QBrush( colorGroup().light() ) ); + paint->drawPixmap( 10 - pixmap.width() / 2, 10 - pixmap.height() / 2, pixmap ); + if ( m_showActiveTabText ) { + + if ( height() < 25 + 4 ) return ; + + QPixmap tpixmap( height() - 25 - 3, width() - 2 ); + QPainter painter( &tpixmap ); + + painter.fillRect( 0, 0, tpixmap.width(), tpixmap.height(), QBrush( colorGroup().light() ) ); + + painter.setPen( colorGroup().text() ); + painter.drawText( tpixmap.width() - QFontMetrics( QFont() ).width( m_text ), + width() / 2 + QFontMetrics( QFont() ).height() / 2, m_text ); + + paint->rotate( -90 ); + kdDebug() << "tpixmap.width:" << tpixmap.width() << endl; + + paint->drawPixmap( -24 - tpixmap.width(), 2, tpixmap ); + + } + + } + + } +} + +void MultiTabBarTab::drawButtonAmarok( QPainter *paint ) +{ + QColor fillColor, textColor; + if ( isOn() ) { + fillColor = blendColors( colorGroup().highlight(), colorGroup().background(), static_cast( m_animCount * 3.5 ) ); + textColor = blendColors( colorGroup().highlightedText(), colorGroup().text(), static_cast( m_animCount * 4.5 ) ); + } else if ( isEnabled() ) { + fillColor = blendColors( colorGroup().background(), colorGroup().highlight(), static_cast( m_animCount * 3.5 ) ); + textColor = blendColors( colorGroup().text(), colorGroup().highlightedText(), static_cast( m_animCount * 4.5 ) ); + } else { + fillColor = colorGroup().background(); + textColor = colorGroup().text(); + } + +#ifndef QT_NO_ICONSET + if ( iconSet() && !iconSet() ->isNull() ) + { + QPixmap icon = iconSet()->pixmap( QIconSet::Small, QIconSet::Normal ); + + // Apply icon effect when widget disabled. Should really be cached, but *shrug*. + if( !isEnabled() ) + icon = kapp->iconLoader()->iconEffect()->apply( icon, KIcon::Small, KIcon::DisabledState ); + + if( m_position == MultiTabBar::Left || m_position == MultiTabBar::Right ) { + QPixmap pixmap( height(), width() ); + pixmap.fill( fillColor ); + QPainter painter( &pixmap ); + + // Draw the frame + painter.setPen( colorGroup().mid() ); + /*if ( m_id != bar->visibleTabCount() - 1 )*/ painter.drawLine( 0, 0, 0, pixmap.height() - 1 ); + painter.drawLine( 0, pixmap.height() - 1, pixmap.width() - 1, pixmap.height() - 1 ); + + // Draw the text + QFont font; + painter.setFont( font ); + QString text = KStringHandler::rPixelSqueeze( m_text, QFontMetrics( font ), pixmap.width() - icon.width() - 3 ); + text.replace( "...", ".." ); + const int textX = pixmap.width() / 2 - QFontMetrics( font ).width( text ) / 2; + painter.setPen( textColor ); + const QRect rect( textX + icon.width() / 2 + 2, 0, pixmap.width(), pixmap.height() ); + painter.drawText( rect, Qt::AlignLeft | Qt::AlignVCenter, text ); + + // Draw the icon + painter.drawPixmap( textX - icon.width() / 2 - 2, pixmap.height() / 2 - icon.height() / 2, icon ); + + // Paint to widget + paint->rotate( -90 ); + paint->drawPixmap( 1 - pixmap.width(), 0, pixmap ); + + } else { // Horizontal + + QPixmap pixmap( width(), height() ); + pixmap.fill( fillColor ); + QPainter painter( &pixmap ); + + // Draw the frame + painter.setPen( colorGroup().mid() ); + /*if ( m_id != bar->visibleTabCount() - 1 )*/ painter.drawLine( 0, 0, 0, pixmap.height() - 1 ); + painter.drawLine( 0, pixmap.height() - 1, pixmap.width() - 1, pixmap.height() - 1 ); + + // Draw the text + QFont font; + painter.setFont( font ); + QString text = KStringHandler::rPixelSqueeze( m_text, QFontMetrics( font ), pixmap.width() - icon.width() - 3 ); + text.replace( "...", ".." ); + const int textX = pixmap.width() / 2 - QFontMetrics( font ).width( text ) / 2; + painter.setPen( textColor ); + const QRect rect( textX + icon.width() / 2 + 2, 0, pixmap.width(), pixmap.height() ); + painter.drawText( rect, Qt::AlignLeft | Qt::AlignVCenter, text ); + + // Draw the icon + painter.drawPixmap( textX - icon.width() / 2 - 2, pixmap.height() / 2 - icon.height() / 2, icon ); + + // Paint to widget + paint->drawPixmap( 0, 0, pixmap ); + } + } +#endif +} + + +QColor MultiTabBarTab::blendColors( const QColor& color1, const QColor& color2, int percent ) +{ + const float factor1 = ( 100 - ( float ) percent ) / 100; + const float factor2 = ( float ) percent / 100; + + const int r = static_cast( color1.red() * factor1 + color2.red() * factor2 ); + const int g = static_cast( color1.green() * factor1 + color2.green() * factor2 ); + const int b = static_cast( color1.blue() * factor1 + color2.blue() * factor2 ); + + QColor result; + result.setRgb( r, g, b ); + + return result; +} + + + + +MultiTabBar::MultiTabBar( MultiTabBarMode bm, QWidget *parent, const char *name ) : QWidget( parent, name ) +{ + m_buttons.setAutoDelete( false ); + if ( bm == Vertical ) { + m_l = new QVBoxLayout( this ); + setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Expanding, true ); + // setFixedWidth(24); + } else { + m_l = new QHBoxLayout( this ); + setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Fixed, true ); + // setFixedHeight(24); + } + m_l->setMargin( 0 ); + m_l->setAutoAdd( false ); + + m_internal = new MultiTabBarInternal( this, bm ); + setPosition( ( bm == MultiTabBar::Vertical ) ? MultiTabBar::Right : MultiTabBar::Bottom ); + setStyle( VSNET ); + // setStyle(KDEV3); + //setStyle(KONQSBC); + m_l->insertWidget( 0, m_internal ); + m_l->insertWidget( 0, m_btnTabSep = new QFrame( this ) ); + m_btnTabSep->setFixedHeight( 4 ); + m_btnTabSep->setFrameStyle( QFrame::Panel | QFrame::Sunken ); + m_btnTabSep->setLineWidth( 2 ); + m_btnTabSep->hide(); + + updateGeometry(); +} + +MultiTabBar::~MultiTabBar() +{} + +/*int MultiTabBar::insertButton(QPixmap pic,int id ,const QString&) +{ +(new KToolbarButton(pic,id,m_internal))->show(); +return 0; +}*/ + +int MultiTabBar::appendButton( const QPixmap &pic , int id, QPopupMenu *popup, const QString& ) +{ + MultiTabBarButton * btn; + m_buttons.append( btn = new MultiTabBarButton( pic, QString::null, + popup, id, this, m_position, m_internal->m_style ) ); + m_l->insertWidget( 0, btn ); + btn->show(); + m_btnTabSep->show(); + return 0; +} + +void MultiTabBar::updateSeparator() +{ + bool hideSep = true; + for ( QPtrListIterator it( m_buttons );it.current();++it ) { + if ( it.current() ->isVisibleTo( this ) ) { + hideSep = false; + break; + } + } + if ( hideSep ) m_btnTabSep->hide(); + else m_btnTabSep->show(); + +} + +int MultiTabBar::appendTab( const QPixmap &pic , int id , const QString& text, const QString& identifier ) +{ + m_internal->appendTab( pic, id, text, identifier ); + return 0; +} + +MultiTabBarButton* MultiTabBar::button( int id ) const +{ + for ( QPtrListIterator it( m_buttons );it.current();++it ) { + if ( it.current() ->id() == id ) return it.current(); + } + return 0; +} + +MultiTabBarTab* MultiTabBar::tab( int id ) const +{ + return m_internal->tab( id ); +} + + + +void MultiTabBar::removeButton( int id ) +{ + for ( uint pos = 0;pos < m_buttons.count();pos++ ) { + if ( m_buttons.at( pos ) ->id() == id ) { + m_buttons.take( pos ) ->deleteLater(); + break; + } + } + if ( m_buttons.count() == 0 ) m_btnTabSep->hide(); +} + +void MultiTabBar::removeTab( int id ) +{ + m_internal->removeTab( id ); +} + +void MultiTabBar::setTab( int id, bool state ) +{ + MultiTabBarTab * ttab = tab( id ); + if ( ttab ) { + ttab->setState( state ); + if( state && !ttab->visible() ) + m_internal->setTabVisible( id, true ); + } +} + +bool MultiTabBar::isTabRaised( int id ) const +{ + MultiTabBarTab * ttab = tab( id ); + if ( ttab ) { + return ttab->isOn(); + } + + return false; +} + + +void MultiTabBar::showActiveTabTexts( bool show ) +{ + m_internal->showActiveTabTexts( show ); +} + +uint MultiTabBar::visibleTabCount() +{ + return m_internal->visibleTabCount( ); +} + +uint MultiTabBar::sizePerTab() +{ + return m_internal->sizePerTab( ); +} + +void MultiTabBar::setStyle( MultiTabBarStyle style ) +{ + m_internal->setStyle( style ); +} + +void MultiTabBar::setPosition( MultiTabBarPosition pos ) +{ + m_position = pos; + m_internal->setPosition( pos ); + for ( uint i = 0;i < m_buttons.count();i++ ) + m_buttons.at( i ) ->setPosition( pos ); +} +void MultiTabBar::fontChange( const QFont& /* oldFont */ ) +{ + for ( uint i = 0;i < tabs() ->count();i++ ) + tabs() ->at( i ) ->resize(); + repaint(); +} + +QPtrList* MultiTabBar::tabs() { return m_internal->tabs();} +QPtrList* MultiTabBar::buttons() { return & m_buttons;} + +void MultiTabBar::showTabSelectionMenu(QPoint pos) +{ + m_internal->showTabSelectionMenu(pos); +} + diff --git a/amarok/src/multitabbar.h b/amarok/src/multitabbar.h new file mode 100644 index 00000000..7f4eed96 --- /dev/null +++ b/amarok/src/multitabbar.h @@ -0,0 +1,297 @@ +/*************************************************************************** + kmultitabbar.h - description + ------------------- + begin : 2001 + copyright : (C) 2001,2002,2003 by Joseph Wenninger + (C) 2005 by Mark Kretschmann +***************************************************************************/ + +/*************************************************************************** + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +***************************************************************************/ + +#ifndef _Multitabbar_h_ +#define _Multitabbar_h_ + +#include +#include +#include +#include +#include +#include +#include + +class QPixmap; +class QPainter; +class QFrame; + +class MultiTabBarPrivate; +class MultiTabBarTabPrivate; +class MultiTabBarButtonPrivate; +class MultiTabBarInternal; + +// this exists only because dropEvent is protected +class DropProxyTarget +{ + public: + virtual void dropProxyEvent( QDropEvent *e ) = 0; +}; + +/** +* A Widget for horizontal and vertical tabs. +* It is possible to add normal buttons to the top/left +* The handling if only one tab at a time or multiple tabs +* should be raisable is left to the "user". +*@author Joseph Wenninger +*/ +class MultiTabBar: public QWidget +{ + Q_OBJECT + public: + enum MultiTabBarMode{Horizontal, Vertical}; + enum MultiTabBarPosition{Left, Right, Top, Bottom}; + + /** + * VSNET == Visual Studio .Net like (only show the text of active tabs + * KDEV3 == Kdevelop 3 like (always show the text) + * KONQSBC == konqy's classic sidebar style (unthemed), this one is disabled + * at the moment, but will be renabled soon too + */ + enum MultiTabBarStyle{VSNET = 0, KDEV3 = 1, KONQSBC = 2, KDEV3ICON = 3, AMAROK = 4, STYLELAST = 0xffff}; + + MultiTabBar( MultiTabBarMode bm, QWidget *parent = 0, const char *name = 0 ); + virtual ~MultiTabBar(); + + /** + * append a new button to the button area. The button can later on be accessed with button(ID) + * eg for connecting signals to it + * @param pic a pixmap for the button + * @param id an arbitraty ID value. It will be emitted in the clicked signal for identifying the button + * if more than one button is connected to a signals. + * @param popup A popup menu which should be displayed if the button is clicked + * @param not_used_yet will be used for a popup text in the future + */ + int appendButton( const QPixmap &pic, int id = -1, QPopupMenu* popup = 0, const QString& not_used_yet = QString::null ); + /** + * remove a button with the given ID + */ + void removeButton( int id ); + /** + * append a new tab to the tab area. It can be accessed lateron with tabb(id); + * @param pic a bitmap for the tab + * @param id an arbitrary ID which can be used later on to identify the tab + * @param text if a mode with text is used it will be the tab text, otherwise a mouse over hint + * @param identifier for storing visibility to config file + */ + int appendTab( const QPixmap &pic, int id = -1, const QString& text = QString::null, const QString& identifier = QString::null ); + /** + * remove a tab with a given ID + */ + void removeTab( int id ); + /** + * set a tab to "raised" + * @param id The ID of the tab to manipulate + * @param state true == activated/raised, false == not active + */ + void setTab( int id , bool state ); + /** + * return the state of a tab, identified by it's ID + */ + bool isTabRaised( int id ) const; + /** + * get a pointer to a button within the button area identified by its ID + */ + class MultiTabBarButton *button( int id ) const; + + /** + * get a pointer to a tab within the tab area, identiifed by its ID + */ + class MultiTabBarTab *tab( int id ) const; + /** + * set the real position of the widget. + * @param pos if the mode is horizontal, only use top, bottom, if it is vertical use left or right + */ + void setPosition( MultiTabBarPosition pos ); + /** + * set the display style of the tabs + */ + void setStyle( MultiTabBarStyle style ); + /** + * be carefull, don't delete tabs yourself and don't delete the list itself + */ + QPtrList* tabs(); + /** + * be carefull, don't delete buttons yourself and don't delete the list itself + */ + QPtrList* buttons(); + + /** + * might vanish, not sure yet + */ + void showActiveTabTexts( bool show = true ); + + /** + * @return number of visible tabs + */ + uint visibleTabCount(); + /** + * @return height per tab + */ + uint sizePerTab(); + + void showTabSelectionMenu(QPoint pos); + + protected: + friend class MultiTabBarButton; + virtual void fontChange( const QFont& ); + void updateSeparator(); + private: + class MultiTabBarInternal *m_internal; + QBoxLayout *m_l; + QFrame *m_btnTabSep; + QPtrList m_buttons; + MultiTabBarPosition m_position; + MultiTabBarPrivate *d; +}; + +/** +* This class should never be created except with the appendButton call of MultiTabBar +*/ +class MultiTabBarButton: public QPushButton +{ + Q_OBJECT + public: + MultiTabBarButton( const QPixmap& pic, const QString&, QPopupMenu *popup, + int id, QWidget *parent, MultiTabBar::MultiTabBarPosition pos, MultiTabBar::MultiTabBarStyle style ); + MultiTabBarButton( const QString&, QPopupMenu *popup, + int id, QWidget *parent, MultiTabBar::MultiTabBarPosition pos, MultiTabBar::MultiTabBarStyle style ); + virtual ~MultiTabBarButton(); + int id() const; + + public slots: + /** + * this is used internaly, but can be used by the user, if (s)he wants to + * It the according call of MultiTabBar is invoked though this modifications will be overwritten + */ + void setPosition( MultiTabBar::MultiTabBarPosition ); + /** + * this is used internaly, but can be used by the user, if (s)he wants to + * It the according call of MultiTabBar is invoked though this modifications will be overwritten + */ + void setStyle( MultiTabBar::MultiTabBarStyle ); + + /** + * modify the text of the button + */ + void setText( const QString & ); + + /** + * make this a drop proxy for finalDropTarget + */ + void proxyDrops( DropProxyTarget *finalDropTarget ); + + QSize sizeHint() const; + + protected: + static const int ANIM_INTERVAL = 18; + static const int ANIM_MAX = 20; + + MultiTabBar::MultiTabBarPosition m_position; + MultiTabBar::MultiTabBarStyle m_style; + QString m_text; + int m_id; + + bool m_animEnter; + int m_animCount; + class QTimer* m_animTimer; + class QTimer* m_dragSwitchTimer; + + virtual void hideEvent( class QHideEvent* ); + virtual void showEvent( class QShowEvent* ); + virtual void enterEvent( class QEvent* ); + virtual void leaveEvent( class QEvent* ); + + virtual void dragEnterEvent ( class QDragEnterEvent * ); + virtual void dragMoveEvent ( class QDragMoveEvent * ); + virtual void dragLeaveEvent ( class QDragLeaveEvent * ); + virtual void dropEvent( class QDropEvent * ); + private: + MultiTabBarButtonPrivate *d; + signals: + /** + * this is emitted if the button is clicked + * @param id the ID identifying the button + */ + void clicked( int id ); + void initiateDrag ( int id ); + protected slots: + virtual void slotClicked(); + virtual void slotAnimTimer(); + virtual void slotDragSwitchTimer(); +}; + +/** +* This class should never be created except with the appendTab call of MultiTabBar +*/ +class MultiTabBarTab: public MultiTabBarButton +{ + Q_OBJECT + public: + MultiTabBarTab( const QPixmap& pic, const QString&, int id, QWidget *parent, + MultiTabBar::MultiTabBarPosition pos, MultiTabBar::MultiTabBarStyle style ); + virtual ~MultiTabBarTab(); + /** + * set the active state of the tab + * @param state true==active false==not active + */ + void setState( bool state ); + /** + * choose if the text should always be displayed + * this is only used in classic mode if at all + */ + void showActiveTabText( bool show ); + void resize() { setSize( neededSize() ); } + bool visible() { return m_visible; }; + void setVisible( bool visible) { m_visible = visible; }; + void setIdentifier( const QString &id ) { m_identifier = id; } + const QString &identifier() const { return m_identifier; } + private: + bool m_visible; + bool m_showActiveTabText; + int m_expandedSize; + QString m_identifier; + MultiTabBarTabPrivate *d; + protected: + friend class MultiTabBarInternal; + void setSize( int ); + int neededSize(); + void updateState(); + virtual void drawButton( QPainter * ); + virtual void drawButtonLabel( QPainter * ); + void drawButtonStyled( QPainter * ); + void drawButtonClassic( QPainter * ); + void drawButtonAmarok( QPainter * ); + QColor blendColors( const QColor& color1, const QColor& color2, int percent ); + protected slots: + virtual void slotClicked(); + void setTabsPosition( MultiTabBar::MultiTabBarPosition ); + + public slots: + virtual void setIcon( const QString& ); + virtual void setIcon( const QPixmap& ); +}; + +#endif diff --git a/amarok/src/multitabbar_p.h b/amarok/src/multitabbar_p.h new file mode 100644 index 00000000..05bd2e12 --- /dev/null +++ b/amarok/src/multitabbar_p.h @@ -0,0 +1,73 @@ +/*************************************************************************** + kmultitabbar_p.h - description + ------------------- + begin : 2003 + copyright : (C) 2003 by Joseph Wenninger +***************************************************************************/ + +/*************************************************************************** + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +***************************************************************************/ + +#ifndef MULTI_TAB_BAR_P_H +#define MULTI_TAB_BAR_P_H +#include +#include + +class MultiTabBarInternal: public QScrollView +{ + Q_OBJECT +public: + MultiTabBarInternal(QWidget *parent,MultiTabBar::MultiTabBarMode bm); + int appendTab(const QPixmap &,int=-1,const QString& =QString::null, const QString&identifier=QString::null); + MultiTabBarTab *tab(int) const; + void removeTab(int); + void setTabVisible(int id, bool visible); + void setPosition(enum MultiTabBar::MultiTabBarPosition pos); + void setStyle(enum MultiTabBar::MultiTabBarStyle style); + void showActiveTabTexts(bool show); + QPtrList* tabs(){return &m_tabs;} + uint visibleTabCount(); + uint sizePerTab(); + void showTabSelectionMenu(QPoint pos); + +private: + friend class MultiTabBar; + QWidget *box; + QBoxLayout *mainLayout; + QPtrList m_tabs; + enum MultiTabBar::MultiTabBarPosition m_position; + bool m_showActiveTabTexts; + enum MultiTabBar::MultiTabBarStyle m_style; + int m_expandedTabSize; + int m_lines; + MultiTabBar::MultiTabBarMode m_barMode; + +protected: + virtual bool eventFilter(QObject *,QEvent*); + virtual void drawContents ( QPainter *, int, int, int, int); + + /** + * [contentsM|m]ousePressEvent are reimplemented from QScrollView + * in order to ignore all mouseEvents on the viewport, so that the + * parent can handle them. + */ + virtual void contentsMousePressEvent(QMouseEvent *); + virtual void mousePressEvent(QMouseEvent *); + virtual void resizeEvent(QResizeEvent *); +}; +#endif + diff --git a/amarok/src/mydirlister.h b/amarok/src/mydirlister.h new file mode 100644 index 00000000..eeb8c55a --- /dev/null +++ b/amarok/src/mydirlister.h @@ -0,0 +1,37 @@ +#ifndef MYDIRLISTER_H +#define MYDIRLISTER_H + +//TODO wait for lister to finish, if there are no files shown, but there are +// media files in that directory show a longMessage (preferably one that disappears when given a signal) + +#include "enginecontroller.h" +#include "playlistloader.h" + +#include +#include + +class MyDirLister : public KDirLister { +public: + MyDirLister( bool delayedMimeTypes ) : KDirLister( delayedMimeTypes ) { } + +protected: + virtual bool matchesMimeFilter( const KFileItem *item ) const { + return + item->isDir() || + EngineController::canDecode( item->url() ) || + item->url().protocol() == "audiocd" || + PlaylistFile::isPlaylistFile( item->name() ) || + item->name().endsWith( ".mp3", false ) || //for now this is less confusing for the user + item->name().endsWith( ".aa", false ) || //for adding to iPod + item->name().endsWith( ".mp4", false ) || //for adding to iPod + item->name().endsWith( ".m4v", false ) || //for adding to iPod + item->name().endsWith( ".m4b", false ) || //for adding to iPod + item->name().endsWith( ".ogg", false ) || + item->name().endsWith( ".flac", false ) || + item->name().endsWith( ".wma", false ) || + item->name().endsWith( ".asf", false ); + + } +}; + +#endif diff --git a/amarok/src/mydiroperator.cpp b/amarok/src/mydiroperator.cpp new file mode 100644 index 00000000..718d3dfb --- /dev/null +++ b/amarok/src/mydiroperator.cpp @@ -0,0 +1,47 @@ +#include "medium.h" +#include "mydiroperator.h" + +#include + +#include + +MyDirOperator::MyDirOperator ( const KURL &url, QWidget *parent, Medium *medium ) : KDirOperator( url, parent ) +{ + m_medium = medium; + setDirLister( new MyDirLister( true ) ); + reenableDeleteKey(); +} + +void +MyDirOperator::myHome() +{ + KURL u; + u.setPath( m_medium ? m_medium->mountPoint() : QDir::homeDirPath() ); + setURL(u, true); +} + +void +MyDirOperator::myCdUp() +{ + KURL tmp( url() ); + tmp.cd( QString::fromLatin1("..")); + if( m_medium && !tmp.path().startsWith( m_medium->mountPoint() ) ) + tmp.setPath( m_medium->mountPoint() ); + setURL(tmp, true); +} + + +//BEGIN private methods +void +MyDirOperator::reenableDeleteKey() +{ + KActionCollection* dirActionCollection = static_cast(KDirOperator::child("KDirOperator::myActionCollection")); + if( dirActionCollection ) + { + KAction* trash = dirActionCollection->action("trash"); + if(trash) + trash->setEnabled(false); + } +} +//END private methods +#include "mydiroperator.moc" diff --git a/amarok/src/mydiroperator.h b/amarok/src/mydiroperator.h new file mode 100644 index 00000000..d28bf85e --- /dev/null +++ b/amarok/src/mydiroperator.h @@ -0,0 +1,35 @@ +#ifndef MYDIROPERATOR_H +#define MYDIROPERATOR_H + +#include "mydirlister.h" + +#include +#include +#include + +class Medium; + +class MyDirOperator : public KDirOperator { + + Q_OBJECT + + public: + MyDirOperator( const KURL &url, QWidget *parent, Medium *medium = 0 ); + + public slots: + //reimplemented due to a bug in KDirOperator::activatedMenu ( KDE 3.4.2 ) - See Bug #103305 + virtual void activatedMenu (const KFileItem *, const QPoint &pos) { + updateSelectionDependentActions(); + reenableDeleteKey(); + static_cast(actionCollection()->action("popupMenu"))->popupMenu()->popup( pos ); + } + void myHome(); + void myCdUp(); + + private: + void reenableDeleteKey(); + Medium *m_medium; + +}; + +#endif diff --git a/amarok/src/newdynamic.ui b/amarok/src/newdynamic.ui new file mode 100644 index 00000000..e667768e --- /dev/null +++ b/amarok/src/newdynamic.ui @@ -0,0 +1,360 @@ + +NewDynamic + + + NewDynamic + + + + 0 + 0 + 560 + 287 + + + + + unnamed + + + 0 + + + + textLabel1 + + + Played tracks to show: + + + How many played items to show before removal + + + How many played items to show before removal + + + + + spacer5_2 + + + Vertical + + + MinimumExpanding + + + + 21 + 5 + + + + + + spacer3 + + + Horizontal + + + Preferred + + + + 80 + 21 + + + + + + m_upcomingIntSpinBox + + + + 5 + 0 + 0 + 0 + + + + 50 + + + 1 + + + 10 + + + 10 + + + Minimum number of upcoming tracks to keep in the playlist + + + Minimum number of upcoming tracks to keep in the playlist + + + + + textLabel2 + + + Upcoming tracks: + + + Minimum number of upcoming tracks to keep in the playlist + + + Minimum number of upcoming tracks to keep in the playlist + + + + + m_playlistName_label + + + Dynamic playlist name: + + + + + m_name + + + + 5 + 0 + 0 + 0 + + + + Untitled + + + + + spacer4 + + + Horizontal + + + Preferred + + + + 110 + 21 + + + + + + m_previousIntSpinBox + + + + 5 + 0 + 0 + 0 + + + + + 60 + 0 + + + + UpDownArrows + + + 100 + + + 0 + + + 5 + + + 10 + + + How many played items to show before removal + + + How many played items to show before removal + + + + + spacer5 + + + Horizontal + + + Preferred + + + + 90 + 21 + + + + + + m_cycleTracks + + + Remove pla&yed tracks + + + true + + + Automatically remove played tracks from the playlist + + + Automatically remove played tracks from the playlist + + + + + selectPlaylist + + + + 1 + 5 + 1 + 0 + + + + + 250 + 250 + + + + + + line1 + + + + 5 + 0 + 0 + 0 + + + + HLine + + + Sunken + + + Horizontal + + + + + line1_2 + + + + 5 + 0 + 0 + 0 + + + + HLine + + + Sunken + + + Horizontal + + + + + m_mixLabel + + + + 1 + + + + + + + AlignVCenter + + + + + + + PlaylistSelection +

    playlistselection.h
    + + 170 + 300 + + 1 + + 5 + 5 + 0 + 0 + + image0 + + + + + 89504e470d0a1a0a0000000d4948445200000016000000160806000000c4b46c3b000003b149444154388dad945f4c5b551cc73fe7dc4b7b4bcba0762d45c43114323599ee6192609c51d883892ce083f1718b3ebb185f8dc91e972cf39d2d2a2f1af664b6f1e0fe3863a0718969700eb0c52142da0242a1bd6d696f7bcff101585203ceb8fd9ece39f99dcff9fe7edf939f88c562ec465f5f9fe609442c161362173c3e3eae7b7a7ac8e7f36432196cdbfe4f907c3e4f2291201e8fe338cec3737357e9e8e828aded1e229d650e1f2d51754b082110124c13a4dc5ea341eb9dc284c0558a853f3ce8cb0677ef500fde7d39d2596679e326597b8e9abb85d7a770ab16ab6983ec5a05b487a70e36f0f4e10afe408d6a558310980108478dba4a1e8233990c5d474b64ed39aa3a8fe5f3317fbf81dbd70bccfeb205947632fd74f6589c1c6ea2f70d03a58ba0c1f2c9bdc1b66de3b8256a6e11cbe7e3ee1d181b590124fe2693aeee08d223c82c3a2c24b7b874bec8f26288774f7bd054504aef0dde6e99c0eb83f9fb266323cb80a27fb0958141836044605a2ee5523393371cc646fee2da37195aa35d0c0c5b4859ac03d7e91712dcaac5adab3650a3ff9d08ef7dd8404bb48869e5d958b5b87dadc4c9a1464e9f0d0326df7ebd86bd2e310cb1bf62d384d59441f2d70a070e1c60e09489929b988681bdd9cc97170bcc4c65595f71f8e0e3301337fc24a7732467831875a47f289652b0be5e4151e6d07316c1b0c0340d8ab92023e76d66a6b2840e36d2fb7a13fee632475e6edc367ea98a90fb98b7dd6310ca0328a44761582e1bab41befabcc0ec940d28bc5e93b68e064cab84e1d9beaeb48934eac1f53b01c1b000fca496aa54b61a99fcde61662a4b4b4b23d1680be9d426173e4df3602a48ea411989a4fd590f52a8fd156b05ed9d350e3defe3cfdf4b4c7ce770ea7d3fb9f520afbe1620daeee5c26735d20b9b9cfb6811a754a439e4e5c5639a4caa1e5caf586bfc0197b78702005cb9b4cae4cd3267ce8638fe964bd72b393e39d74928d242617303a756a37f284447770dcdbffc6384a05a85de1306e9a52057c7527c7131c3c42d3f475eb2303c82d4fc3276d6811db37efeb148723082d9b08f79f97c1e5729109a9a28307cc622d2d6cdf52b2b24efe548dedb00142009862cfa879ee1a71f6cec928353511472fbf4389148b0b0e0c108081412458dfe21c9f11351e67e7358595468246d1d1e5e38a6e9e851bc39d84ab502a669331dafec0d8ec7e3e8cb06e1a881d727d1ae40180a434a8c9db129a54126ad48a7358c2b4c5352c8c374bcccdab2bb37d8719cba79fab8211f9df218e0582c261e95f8bfc04f1a1e8bc5c4dfe0a190172af6a9690000000049454e44ae426082 + + + + + m_cycleTracks + toggled(bool) + textLabel1 + setEnabled(bool) + + + m_cycleTracks + toggled(bool) + m_previousIntSpinBox + setEnabled(bool) + + + + m_name + m_cycleTracks + m_previousIntSpinBox + m_upcomingIntSpinBox + + + + knuminput.h + knuminput.h + playlistselection.h + + diff --git a/amarok/src/organizecollectiondialog.ui b/amarok/src/organizecollectiondialog.ui new file mode 100644 index 00000000..0466bcea --- /dev/null +++ b/amarok/src/organizecollectiondialog.ui @@ -0,0 +1,596 @@ + +OrganizeCollectionDialog +<aumuell@reserv.at> + + + organizeCollectionDialog + + + + 0 + 0 + 476 + 549 + + + + + 3 + 3 + 0 + 0 + + + + Organize Files + + + + unnamed + + + + folderLayout + + + + unnamed + + + 5 + + + + folderLabel + + + + 1 + 5 + 0 + 0 + + + + C&ollection Folder: + + + folderCombo + + + Base directory under which to put files + + + + + folderCombo + + + + 7 + 0 + 0 + 0 + + + + + + + + + + + layout5 + + + + unnamed + + + + coverCheck + + + &Use Cover Art for Folder Icons + + + + + ignoreTheCheck + + + I&gnore 'The' in Artist Names + + + If checked, postfix artists' names starting with 'The' with ', The'. + + + + + + + spacer2_5 + + + Vertical + + + Expanding + + + + 20 + 16 + + + + + + groupingGroup + + + 0 + + + File Naming Scheme + + + + unnamed + + + 5 + + + + customschemeCheck + + + Custo&m Format + + + If checked, use a custom format string for naming the files in the collection + + + + + filetypeCheck + + + Group b&y File Type + + + If checked, create a directory hierarchy using the filename extension. + + + + + initialCheck + + + Group &by Artist's Initial + + + If checked, introduce another directory hierarchy for the artists' initials. + + + + + customFormatLayout + + + + unnamed + + + + formatLabel + + + false + + + F&ilename Format: + + + formatEdit + + + + + formatEdit + + + false + + + + + + + + formatHelp + + + + 5 + 5 + 0 + 0 + + + + (Help) + + + + + + + + + spacer2_4 + + + Vertical + + + Expanding + + + + 20 + 16 + + + + + + replacementGroup + + + + 5 + 1 + 0 + 0 + + + + 0 + + + Character Replacement + + + + unnamed + + + 5 + + + + spaceCheck + + + &Replace Spaces with Underscores + + + If checked, convert spaces to underscores. + + + + + asciiCheck + + + Restrict to &ASCII + + + If checked, replace characters that are unavailable in the 7-bit ASCII code. + + + + + vfatCheck + + + VFAT Safe &Names + + + If checked, replace characters that are incompatible with MS-DOS/VFAT file systems. + + + + + layout4 + + + + unnamed + + + + textLabel1 + + + Replace + + + + + regexpEdit + + + Regular expression + + + + + textLabel2 + + + with + + + + + replaceEdit + + + Character string + + + + + + + + + spacer2 + + + Vertical + + + Expanding + + + + 20 + 16 + + + + + + previewBox + + + Destination Preview + + + + unnamed + + + 5 + + + 0 + + + + previewText + + + + 7 + 5 + 0 + 0 + + + + + + + This is what the file names will look like after renaming. + + + + + + + layout7 + + + + unnamed + + + + spacer2_2 + + + Horizontal + + + Expanding + + + + 347 + 20 + + + + + + overwriteCheck + + + Overwrite &Destination + + + If checked, overwrite files of the same name without asking. + + + + + + + spacer2_3 + + + Vertical + + + Expanding + + + + 20 + 16 + + + + + + + + + + customschemeCheck + toggled(bool) + formatEdit + setEnabled(bool) + + + customschemeCheck + toggled(bool) + formatLabel + setEnabled(bool) + + + customschemeCheck + toggled(bool) + filetypeCheck + setDisabled(bool) + + + customschemeCheck + toggled(bool) + initialCheck + setDisabled(bool) + + + formatEdit + textChanged(const QString&) + organizeCollectionDialog + preview(const QString&) + + + organizeCollectionDialog + updatePreview( const QString & ) + previewText + setText(const QString&) + + + customschemeCheck + stateChanged(int) + organizeCollectionDialog + update(int) + + + filetypeCheck + stateChanged(int) + organizeCollectionDialog + update(int) + + + initialCheck + stateChanged(int) + organizeCollectionDialog + update(int) + + + spaceCheck + stateChanged(int) + organizeCollectionDialog + update(int) + + + asciiCheck + stateChanged(int) + organizeCollectionDialog + update(int) + + + vfatCheck + stateChanged(int) + organizeCollectionDialog + update(int) + + + ignoreTheCheck + stateChanged(int) + organizeCollectionDialog + update(int) + + + folderCombo + activated(int) + organizeCollectionDialog + update(int) + + + regexpEdit + textChanged(const QString&) + organizeCollectionDialog + update(const QString&) + + + replaceEdit + textChanged(const QString&) + organizeCollectionDialog + update(const QString&) + + + + metabundle.h + qdialog.h + qstring.h + + + MetaBundle previewBundle; + bool detailed; + + + updatePreview( const QString & ) + + + preview( const QString & format ) + update( int dummy ) + update( const QString & dummy ) + slotDetails() + + + buildDestination( const QString & format, const MetaBundle & mb ) const + buildFormatTip() const + buildFormatString() const + setPreviewBundle( const MetaBundle & bundle ) + cleanPath( const QString & component ) const + init() + + + + kcombobox.h + klineedit.h + kactivelabel.h + klineedit.h + klineedit.h + + diff --git a/amarok/src/organizecollectiondialog.ui.h b/amarok/src/organizecollectiondialog.ui.h new file mode 100644 index 00000000..4ca3fc03 --- /dev/null +++ b/amarok/src/organizecollectiondialog.ui.h @@ -0,0 +1,217 @@ +#include "amarok.h" +#include "collectionbrowser.h" +#include "collectiondb.h" +#include "qstringx.h" + + +QString OrganizeCollectionDialog::buildDestination( const QString &format, const MetaBundle &mb ) const +{ + bool isCompilation = false; + if( !mb.album().isEmpty() ) + { + const int albumId = CollectionDB::instance()->albumID( mb.album() ); + isCompilation = CollectionDB::instance()->albumIsCompilation( QString::number(albumId) ); + } + + QMap args; + QString artist = mb.artist(); + QString albumartist = artist; + if( isCompilation ) + albumartist = i18n( "Various Artists" ); + args["theartist"] = cleanPath( artist ); + args["thealbumartist"] = cleanPath( albumartist ); + if( ignoreTheCheck->isChecked() && artist.startsWith( "The " ) ) + CollectionView::instance()->manipulateThe( artist, true ); + artist = cleanPath( artist ); + if( ignoreTheCheck->isChecked() && albumartist.startsWith( "The " ) ) + CollectionView::instance()->manipulateThe( albumartist, true ); + albumartist = cleanPath( albumartist ); + for( int i = 0; i < MetaBundle::NUM_COLUMNS; i++ ) + { + if( i == MetaBundle::Score || i == MetaBundle::PlayCount + || i == MetaBundle::LastPlayed || i == MetaBundle::Mood ) + continue; + args[mb.exactColumnName( i ).lower()] = cleanPath( mb.prettyText( i ) ); + } + args["artist"] = artist; + args["albumartist"] = albumartist; + args["folder"] = folderCombo->currentText(); + args["initial"] = albumartist.mid( 0, 1 ).upper(); + args["filetype"] = mb.url().path().section( ".", -1 ).lower(); + QString track; + if ( mb.track() ) + track.sprintf( "%02d", mb.track() ); + args["track"] = track; + + Amarok::QStringx formatx( format ); + QString result = formatx.namedOptArgs( args ); + if( result.startsWith( folderCombo->currentText() ) ) + { + QString tail = result.mid( folderCombo->currentText().length() ); + if( !tail.startsWith( "/" ) ) + tail.prepend( "/" ); + return folderCombo->currentText() + tail.replace( QRegExp( "/\\.*" ), "/" ); + } + return result.replace( QRegExp( "/\\.*" ), "/" ); +} + +QString OrganizeCollectionDialog::buildFormatTip() const +{ + QMap args; + for( int i = 0; i < MetaBundle::NUM_COLUMNS; i++ ) + { + if( i == MetaBundle::Score || i == MetaBundle::PlayCount + || i == MetaBundle::LastPlayed || i == MetaBundle::Mood ) + continue; + args[MetaBundle::exactColumnName( i ).lower()] = MetaBundle::prettyColumnName( i ); + } + args["albumartist"] = i18n( "%1 or %2" ).arg( i18n("This feature only works with \"The\", so either don't translate it at all, or only translate artist and album", "Album Artist, The") , i18n("The Album Artist") ); + args["thealbumartist"] = i18n( "The Album Artist" ); + args["theartist"] = i18n( "The Artist" ); + args["artist"] = i18n( "%1 or %2" ).arg( i18n( "This feature only works with \"The\", so either don't translate it at all, or only translate Artist", "Artist, The") , i18n( "The Artist") ); + args["folder"] = i18n( "Collection Base Folder" ); + args["initial"] = i18n( "Artist's Initial" ); + args["filetype"] = i18n( "File Extension of Source" ); + args["track"] = i18n( "Track Number" ); + + QString tooltip = i18n( "

    Custom Format String

    " ); + tooltip += i18n( "You can use the following tokens:" ); + tooltip += "
      "; + for( QMap::iterator it = args.begin(); + it != args.end(); + ++it ) + { + tooltip += QString( "
    • %1 - %2" ).arg( it.data(), "%" + it.key() ); + } + tooltip += "
    "; + + tooltip += i18n( "If you surround sections of text that contain a token with curly-braces, " + "that section will be hidden if the token is empty." ); + + return tooltip; +} + + +QString OrganizeCollectionDialog::buildFormatString() const +{ + QString format = "%folder/"; + if( filetypeCheck->isChecked() ) + format += "%filetype/"; + if( initialCheck->isChecked() ) + format += "%initial/"; + + format += "%albumartist/"; + if( spaceCheck->isChecked() ) + { + format += "%album{_(Disc_%discnumber)}/"; + format += "{%track_-_}%title.%filetype"; + } + else + { + format += "%album{ (Disc %discnumber)}/"; + format += "{%track - }%title.%filetype"; + } + + if( customschemeCheck->isChecked() ) + format = formatEdit->text(); + + return format; +} + + +void OrganizeCollectionDialog::setPreviewBundle( const MetaBundle &bundle ) +{ + previewBundle = bundle; +} + + +void OrganizeCollectionDialog::preview( const QString &format ) +{ + emit updatePreview( buildDestination( format, previewBundle ) ); +} + + +QString OrganizeCollectionDialog::cleanPath( const QString &component ) const +{ + QString result = component; + + if( asciiCheck->isChecked() ) + { + result = Amarok::cleanPath( result ); + result = Amarok::asciiPath( result ); + } + + if( !regexpEdit->text().isEmpty() ) + result.replace( QRegExp( regexpEdit->text() ), replaceEdit->text() ); + + result.simplifyWhiteSpace(); + if( spaceCheck->isChecked() ) + result.replace( QRegExp( "\\s" ), "_" ); + if( vfatCheck->isChecked() ) + result = Amarok::vfatPath( result ); + + result.replace( "/", "-" ); + + return result; +} + + +void OrganizeCollectionDialog::update( int dummy ) +{ + Q_UNUSED( dummy ); + + QString oldFormat = formatEdit->text(); + if( !customschemeCheck->isChecked() ) + formatEdit->setText( buildFormatString() ); + + if( customschemeCheck->isChecked() || oldFormat==formatEdit->text() ) + emit updatePreview( buildDestination( formatEdit->text(), previewBundle ) ); +} + + +void OrganizeCollectionDialog::update( const QString & dummy ) +{ + Q_UNUSED( dummy ); + + update( 0 ); +} + + + +void OrganizeCollectionDialog::slotDetails() +{ + detailed = !detailed; + + if( detailed ) + { + ignoreTheCheck->show(); + customschemeCheck->show(); + replacementGroup->show(); + formatLabel->show(); + formatEdit->show(); + formatHelp->show(); + } + else + { + ignoreTheCheck->hide(); + customschemeCheck->hide(); + replacementGroup->hide(); + formatLabel->hide(); + formatEdit->hide(); + formatHelp->hide(); + } + + if( dynamic_cast(parent()) ) { + static_cast(parent())->adjustSize(); + static_cast(parent())->updateGeometry(); + } +} + + +void OrganizeCollectionDialog::init() +{ + detailed = true; + + formatHelp->setText( QString( "%2" ). + arg( Amarok::escapeHTMLAttr( buildFormatTip() ), i18n( "(Help)" ) ) ); +} diff --git a/amarok/src/osd.cpp b/amarok/src/osd.cpp new file mode 100644 index 00000000..da50436f --- /dev/null +++ b/amarok/src/osd.cpp @@ -0,0 +1,948 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * osd.cpp: Shows some text in a pretty way independent to the WM + * begin: Fre Sep 26 2003 + * copyright: (C) 2004 Christian Muehlhaeuser + * (C) 2004-2006 Seb Ruiz + * (C) 2004, 2005 Max Howell + * (C) 2005 Gábor Lehel + */ + +#include "amarok.h" +#include "amarokconfig.h" +#include "collectiondb.h" //for albumCover location +#include "debug.h" +#include "enginecontroller.h" +#include "osd.h" +#include "playlist.h" //if osdUsePlaylistColumns() +#include "playlistitem.h" //ditto +#include "podcastbundle.h" +#include "qstringx.h" +#include "starmanager.h" + +#include +#include +#include +#include //locate + +#include +#include +#include +#include +#include +#include + +namespace ShadowEngine +{ + QImage makeShadow( const QPixmap &textPixmap, const QColor &bgColor ); +} + + +#define MOODBAR_HEIGHT 20 + + +OSDWidget::OSDWidget( QWidget *parent, const char *name ) + : QWidget( parent, name, WType_TopLevel | WNoAutoErase | WStyle_Customize | WX11BypassWM | WStyle_StaysOnTop ) + , m_duration( 2000 ) + , m_timer( new QTimer( this ) ) + , m_alignment( Middle ) + , m_screen( 0 ) + , m_y( MARGIN ) + , m_drawShadow( false ) + , m_translucency( false ) + , m_rating( 0 ) + , m_volume( false ) +{ + setFocusPolicy( NoFocus ); + setBackgroundMode( NoBackground ); + unsetColors(); + + connect( m_timer, SIGNAL(timeout()), SLOT(hide()) ); + connect( CollectionDB::instance(), SIGNAL( ratingChanged( const QString&, int ) ), + this, SLOT( ratingChanged( const QString&, int ) ) ); + + //or crashes, KWin bug I think, crashes in QWidget::icon() + kapp->setTopWidget( this ); +} + +void +OSDWidget::show( const QString &text, QImage newImage ) +{ +#ifdef Q_WS_X11 + m_text = text; + if ( !newImage.isNull() ) + { + m_cover = newImage; + int w = m_scaledCover.width(); + int h = m_scaledCover.height(); + m_scaledCover = m_cover.smoothScale(w, h); + } + show(); +#else + Q_UNUSED( text ); + Q_UNUSED( newImage ); +#endif +} + +void +OSDWidget::ratingChanged( const short rating ) +{ + //m_text = '\n' + i18n( "Rating changed" ); + setRating( rating ); //Checks isEnabled() before doing anything + + if( useMoodbar() ) + OSDWidget::setMoodbar( EngineController::instance()->bundle() ); + if( isShown() ) + show(); +} + +void +OSDWidget::ratingChanged( const QString& path, int rating ) +{ + const MetaBundle ¤tTrack = EngineController::instance()->bundle(); + if( currentTrack.isFile() && currentTrack.url().path() == path ) + ratingChanged( rating ); +} + +void +OSDWidget::volChanged( unsigned char volume ) +{ + if ( isEnabled() ) + { + m_volume = true; + m_newvolume = volume; + m_text = m_newvolume ? i18n("Volume: %1%").arg( m_newvolume ) : i18n("Mute"); + + show(); + } +} + +void +OSDWidget::show() //virtual +{ +#ifdef Q_WS_X11 + if ( !isEnabled() || m_text.isEmpty() ) + return; + + const uint M = fontMetrics().width( 'x' ); + + const QRect oldGeometry = QRect( pos(), size() ); + const QRect newGeometry = determineMetrics( M ); + + if( m_translucency && !isShown() || !newGeometry.intersects( oldGeometry ) ) + m_screenshot = QPixmap::grabWindow( qt_xrootwin(), + newGeometry.x(), newGeometry.y(), + newGeometry.width(), newGeometry.height() ); + + + else if( m_translucency ) + { + const QRect unite = oldGeometry.unite( newGeometry ); + KPixmap pix = QPixmap::grabWindow( qt_xrootwin(), unite.x(), unite.y(), unite.width(), unite.height() ); + + QPoint p = oldGeometry.topLeft() - unite.topLeft(); + bitBlt( &pix, p, &m_screenshot ); + + m_screenshot.resize( newGeometry.size() ); + + p = newGeometry.topLeft() - unite.topLeft(); + bitBlt( &m_screenshot, 0, 0, &pix, p.x(), p.y() ); + } + + if( newGeometry.width() > 0 && newGeometry.height() > 0 ) + { + render( M, newGeometry.size() ); + setGeometry( newGeometry ); + QWidget::show(); + bitBlt( this, 0, 0, &m_buffer ); + + if( m_duration ) //duration 0 -> stay forever + m_timer->start( m_duration, true ); //calls hide() + } + else + warning() << "Attempted to make an invalid sized OSD\n"; +#endif +} + +QRect +OSDWidget::determineMetrics( const uint M ) +{ + // sometimes we only have a tiddly cover + const QSize minImageSize = m_cover.size().boundedTo( QSize(100,100) ); + + // determine a sensible maximum size, don't cover the whole desktop or cross the screen + const QSize margin( (M + MARGIN) * 2, (M + MARGIN) * 2 ); //margins + const QSize image = m_cover.isNull() ? QSize( 0, 0 ) : minImageSize; + const QSize max = QApplication::desktop()->screen( m_screen )->size() - margin; + + // If we don't do that, the boundingRect() might not be suitable for drawText() (Qt issue N67674) + m_text.replace( QRegExp(" +\n"), "\n" ); + // remove consecutive line breaks + m_text.replace( QRegExp("\n+"), "\n" ); + + // The osd cannot be larger than the screen + QRect rect = fontMetrics().boundingRect( 0, 0, + max.width() - image.width(), max.height(), + AlignCenter | WordBreak, m_text ); + + if( m_volume ) + { + static const QString tmp = QString ("******").insert( 3, + ( i18n("Volume: 100%").length() >= i18n("Mute").length() )? + i18n("Volume: 100%") : i18n("Mute") ); + + QRect tmpRect = fontMetrics().boundingRect( 0, 0, + max.width() - image.width(), max.height() - fontMetrics().height(), + AlignCenter | WordBreak, tmp ); + tmpRect.setHeight( tmpRect.height() + fontMetrics().height() / 2 ); + + rect = tmpRect; + } + + if( m_rating ) + { + QPixmap* star = StarManager::instance()->getStar( 1, true ); + if( rect.width() < star->width() * 5 ) + rect.setWidth( star->width() * 5 ); //changes right edge position + rect.setHeight( rect.height() + star->height() + M ); //changes bottom edge pos + } + + if( useMoodbar() ) + rect.setHeight( rect.height() + MOODBAR_HEIGHT + M ); + + if( !m_cover.isNull() ) + { + const int availableWidth = max.width() - rect.width() - M; //WILL be >= (minImageSize.width() - M) + + m_scaledCover = m_cover.smoothScale( + QMIN( availableWidth, m_cover.width() ), + QMIN( rect.height(), m_cover.height() ), + QImage::ScaleMin ); //this will force us to be with our bounds + + int shadowWidth = 0; + if( m_drawShadow && !m_scaledCover.hasAlpha() && + ( m_scaledCover.width() > 22 || m_scaledCover.height() > 22 ) ) + shadowWidth = static_cast( m_scaledCover.width() / 100.0 * 6.0 ); + + const int widthIncludingImage = rect.width() + + m_scaledCover.width() + + shadowWidth + + M; //margin between text + image + + rect.setWidth( widthIncludingImage ); + } + + // expand in all directions by M + rect.addCoords( -M, -M, M, M ); + + const QSize newSize = rect.size(); + const QRect screen = QApplication::desktop()->screenGeometry( m_screen ); + QPoint newPos( MARGIN, m_y ); + + switch( m_alignment ) + { + case Left: + break; + + case Right: + newPos.rx() = screen.width() - MARGIN - newSize.width(); + break; + + case Center: + newPos.ry() = (screen.height() - newSize.height()) / 2; + + //FALL THROUGH + + case Middle: + newPos.rx() = (screen.width() - newSize.width()) / 2; + break; + } + + //ensure we don't dip below the screen + if ( newPos.y() + newSize.height() > screen.height() - MARGIN ) + newPos.ry() = screen.height() - MARGIN - newSize.height(); + + // correct for screen position + newPos += screen.topLeft(); + + return QRect( newPos, rect.size() ); +} + +void +OSDWidget::render( const uint M, const QSize &size ) +{ + /// render with margin/spacing @param M and @param size + + QPoint point; + QRect rect( point, size ); + + // From qt sources + const uint xround = (M * 200) / size.width(); + const uint yround = (M * 200) / size.height(); + + { /// apply the mask + static QBitmap mask; + + mask.resize( size ); + mask.fill( Qt::black ); + + QPainter p( &mask ); + p.setBrush( Qt::white ); + p.drawRoundRect( rect, xround, yround ); + setMask( mask ); + } + + QColor shadowColor; + { + int h,s,v; + foregroundColor().getHsv( &h, &s, &v ); + shadowColor = v > 128 ? Qt::black : Qt::white; + } + + int align = Qt::AlignCenter | WordBreak; + + m_buffer.resize( rect.size() ); + QPainter p( &m_buffer ); + + if( m_translucency ) + { + KPixmap background( m_screenshot ); + KPixmapEffect::fade( background, 0.80, backgroundColor() ); + p.drawPixmap( 0, 0, background ); + } + else + p.fillRect( rect, backgroundColor() ); + + p.setPen( backgroundColor().dark() ); + p.drawRoundRect( rect, xround, yround ); + + rect.addCoords( M, M, -M, -M ); + + if( !m_cover.isNull() ) + { + QRect r( rect ); + r.setTop( (size.height() - m_scaledCover.height()) / 2 ); + r.setSize( m_scaledCover.size() ); + + if( !m_scaledCover.hasAlpha() && m_drawShadow && + ( m_scaledCover.width() > 22 || m_scaledCover.height() > 22 ) ) { + // don't draw a shadow for eg, the Amarok icon + QImage shadow; + const uint shadowSize = static_cast( m_scaledCover.width() / 100.0 * 6.0 ); + + const QString folder = Amarok::saveLocation( "covershadow-cache/" ); + const QString file = QString( "shadow_albumcover%1x%2.png" ).arg( m_scaledCover.width() + shadowSize ) + .arg( m_scaledCover.height() + shadowSize ); + if ( QFile::exists( folder + file ) ) + shadow.load( folder + file ); + else { + shadow.load( locate( "data", "amarok/images/shadow_albumcover.png" ) ); + shadow = shadow.smoothScale( m_scaledCover.width() + shadowSize, m_scaledCover.height() + shadowSize ); + shadow.save( folder + file, "PNG" ); + } + + QPixmap target; + target.convertFromImage( shadow ); //FIXME slow + copyBlt( &target, 0, 0, &m_scaledCover ); + m_scaledCover = target; + r.setTop( (size.height() - m_scaledCover.height()) / 2 ); + r.setSize( m_scaledCover.size() ); + } + + p.drawPixmap( r.topLeft(), m_scaledCover ); + + rect.rLeft() += m_scaledCover.width() + M; + } + + if( m_volume ) + { + QPixmap vol; + vol = QPixmap( rect.width(), rect.height() + fontMetrics().height() / 4 ); + + QPixmap buf( vol.size() ); + QRect r( rect ); + r.setLeft( rect.left() + rect.width() / 2 - vol.width() / 2 ); + r.setTop( size.height() / 2 - vol.height() / 2); + + KPixmap pixmapGradient; + { // gradient + QBitmap mask; + mask.resize( vol.size() ); + mask.fill( Qt::black ); + + QPainter p( &mask ); + p.setBrush( Qt::white ); + p.drawRoundRect ( 3, 3, vol.width() - 6, vol.height() - 6, + M * 300 / vol.width(), 99 ); + p.end(); + + pixmapGradient = QPixmap( vol.size() ); + KPixmapEffect::gradient( pixmapGradient, colorGroup().background(), + colorGroup().highlight(), KPixmapEffect::EllipticGradient ); + pixmapGradient.setMask( mask ); + } + + if( m_translucency ) + { + KPixmap background( m_screenshot ); + KPixmapEffect::fade( background, 0.80, backgroundColor() ); + bitBlt( &vol, -r.left(), -r.top(), &background ); + } + else + vol.fill( backgroundColor() ); + + { // vol ( bg-alpha ) + static QBitmap mask; + mask.resize( vol.size() ); + mask.fill( Qt::white ); + + QPainter p( &mask ); + p.setBrush( Qt::black ); + p.drawRoundRect ( 1, 1, rect.width()-2, rect.height() + fontMetrics().height() / 4 - 2, + M * 300 / vol.width(), 99 ); + p.setBrush( Qt::white ); + p.drawRoundRect ( 3, 3, vol.width() - 6, vol.height() - 6, + M * 300 / vol.width(), 99 ); + p.end(); + vol.setMask( mask ); + } + buf.fill( backgroundColor().dark() ); + + const int offset = int( double( vol.width() * m_newvolume ) / 100 ); + + bitBlt( &buf, 0, 0, &vol ); // bg + bitBlt( &buf, 0, 0, &pixmapGradient, 0, 0, offset ); + + p.drawPixmap( r.left(), r.top(), buf ); + m_volume = false; + } + + QPixmap* star = StarManager::instance()->getStar( m_rating/2, true ); + int graphicsHeight = 0; + + if( useMoodbar() ) + { + QPixmap moodbar + = m_moodbarBundle.moodbar().draw( rect.width(), MOODBAR_HEIGHT ); + QRect r( rect ); + r.setTop( rect.bottom() - moodbar.height() + - (m_rating ? star->height() + M : 0) ); + graphicsHeight += moodbar.height() + M; + + p.drawPixmap( r.left(), r.top(), moodbar ); + m_moodbarBundle = MetaBundle(); + } + + if( m_rating > 0 ) + { + QRect r( rect ); + + //Align to center... + r.setLeft(( rect.left() + rect.width() / 2 ) - star->width() * m_rating / 4 ); + r.setTop( rect.bottom() - star->height() ); + graphicsHeight += star->height() + M; + + bool half = m_rating%2; + + if( half ) + { + QPixmap* halfStar = StarManager::instance()->getHalfStar( m_rating/2 + 1, true ); + p.drawPixmap( r.left() + star->width() * ( m_rating / 2 ), r.top(), *halfStar ); + star = StarManager::instance()->getStar( m_rating/2 + 1, true ); + } + + for( int i = 0; i < m_rating/2; i++ ) + { + p.drawPixmap( r.left() + i * star->width(), r.top(), *star ); + } + + m_rating = 0; + } + + rect.setBottom( rect.bottom() - graphicsHeight ); + + if( m_drawShadow ) + { + QPixmap pixmap( rect.size() + QSize(10,10) ); + pixmap.fill( Qt::black ); + pixmap.setMask( pixmap.createHeuristicMask( true ) ); + + QPainter p2( &pixmap ); + p2.setFont( font() ); + p2.setPen( Qt::white ); + p2.setBrush( Qt::white ); + p2.drawText( QRect(QPoint(5,5), rect.size()), align , m_text ); + p2.end(); + + p.drawImage( rect.topLeft() - QPoint(5,5), ShadowEngine::makeShadow( pixmap, shadowColor ) ); + } + + p.setPen( foregroundColor() ); + p.setFont( font() ); + p.drawText( rect, align, m_text ); + p.end(); +} + +bool +OSDWidget::event( QEvent *e ) +{ + switch( e->type() ) + { + case QEvent::ApplicationPaletteChange: + if( !AmarokConfig::osdUseCustomColors() ) + unsetColors(); //use new palette's colours + return true; + case QEvent::Paint: + bitBlt( this, 0, 0, &m_buffer ); + return true; + default: + return QWidget::event( e ); + } +} + +void +OSDWidget::mousePressEvent( QMouseEvent* ) +{ + hide(); +} + +void +OSDWidget::unsetColors() +{ + const QColorGroup c = QApplication::palette().active(); + + setPaletteForegroundColor( c.highlightedText() ); + setPaletteBackgroundColor( c.highlight() ); +} + +void +OSDWidget::setScreen( int screen ) +{ + const int n = QApplication::desktop()->numScreens(); + m_screen = (screen >= n) ? n-1 : screen; +} + +bool +OSDWidget::useMoodbar( void ) +{ + return (m_moodbarBundle.moodbar().state() == Moodbar::Loaded && + AmarokConfig::showMoodbar() ); +} + +////// OSDPreviewWidget below ///////////////////// + +#include +#include +#include + +namespace Amarok +{ + QImage icon() { return QImage( KIconLoader().iconPath( "amarok", -KIcon::SizeHuge ) ); } +} + +OSDPreviewWidget::OSDPreviewWidget( QWidget *parent ) + : OSDWidget( parent, "osdpreview" ) + , m_dragging( false ) +{ + m_text = i18n( "OSD Preview - drag to reposition" ); + m_duration = 0; + m_cover = Amarok::icon(); +} + +void OSDPreviewWidget::mousePressEvent( QMouseEvent *event ) +{ + m_dragOffset = event->pos(); + + if( event->button() == LeftButton && !m_dragging ) { + grabMouse( KCursor::sizeAllCursor() ); + m_dragging = true; + } +} + + +void OSDPreviewWidget::mouseReleaseEvent( QMouseEvent * /*event*/ ) +{ + if( m_dragging ) + { + m_dragging = false; + releaseMouse(); + + // compute current Position && offset + QDesktopWidget *desktop = QApplication::desktop(); + int currentScreen = desktop->screenNumber( pos() ); + + if( currentScreen != -1 ) { + // set new data + m_screen = currentScreen; + m_y = QWidget::y(); + + emit positionChanged(); + } + } +} + + +void OSDPreviewWidget::mouseMoveEvent( QMouseEvent *e ) +{ + if( m_dragging && this == mouseGrabber() ) + { + // Here we implement a "snap-to-grid" like positioning system for the preview widget + + const QRect screen = QApplication::desktop()->screenGeometry( m_screen ); + const uint hcenter = screen.width() / 2; + const uint eGlobalPosX = e->globalPos().x() - screen.left(); + const uint snapZone = screen.width() / 24; + + QPoint destination = e->globalPos() - m_dragOffset - screen.topLeft(); + int maxY = screen.height() - height() - MARGIN; + if( destination.y() < MARGIN ) destination.ry() = MARGIN; + if( destination.y() > maxY ) destination.ry() = maxY; + + if( eGlobalPosX < (hcenter-snapZone) ) { + m_alignment = Left; + destination.rx() = MARGIN; + } + else if( eGlobalPosX > (hcenter+snapZone) ) { + m_alignment = Right; + destination.rx() = screen.width() - MARGIN - width(); + } + else { + const uint eGlobalPosY = e->globalPos().y() - screen.top(); + const uint vcenter = screen.height()/2; + + destination.rx() = hcenter - width()/2; + + if( eGlobalPosY >= (vcenter-snapZone) && eGlobalPosY <= (vcenter+snapZone) ) + { + m_alignment = Center; + destination.ry() = vcenter - height()/2; + } + else m_alignment = Middle; + } + + destination += screen.topLeft(); + + move( destination ); + } +} + + + +////// Amarok::OSD below ///////////////////// + +#include "enginecontroller.h" +#include "metabundle.h" +#include + +Amarok::OSD::OSD(): OSDWidget( 0 ) +{ + connect( CollectionDB::instance(), SIGNAL( coverChanged( const QString&, const QString& ) ), + this, SLOT( slotCoverChanged( const QString&, const QString& ) ) ); + connect( CollectionDB::instance(), SIGNAL( imageFetched( const QString& ) ), + this, SLOT( slotImageChanged( const QString& ) ) ); +} + +void +Amarok::OSD::show( const MetaBundle &bundle ) //slot +{ +#ifdef Q_WS_X11 + QString text = ""; + if( bundle.url().isEmpty() ) + text = i18n( "No track playing" ); + + else + { + QValueVector tags; + tags.append(bundle.prettyTitle()); + for( int i = 0; i < PlaylistItem::NUM_COLUMNS; ++i ) + tags.append(bundle.prettyText( i )); + + if( bundle.length() <= 0 ) + tags[PlaylistItem::Length+1] = QString::null; + + if( AmarokConfig::osdUsePlaylistColumns() ) + { + QString tag; + QValueVector availableTags; //eg, ones that aren't empty + static const QValueList parens = //display these in parentheses + QValueList() << PlaylistItem::PlayCount << PlaylistItem::Year << PlaylistItem::Comment + << PlaylistItem::Genre << PlaylistItem::Length << PlaylistItem::Bitrate + << PlaylistItem::LastPlayed << PlaylistItem::Score << PlaylistItem::Filesize; + OSDWidget::setMoodbar(); + OSDWidget::setRating( 0 ); + for( int i = 0, n = Playlist::instance()->numVisibleColumns(); i < n; ++i ) + { + const int column = Playlist::instance()->mapToLogicalColumn( i ); + if( !tags.at( column + 1 ).isEmpty() && column != PlaylistItem::Rating ) + availableTags.append(column); + if( column == PlaylistItem::Rating ) + OSDWidget::setRating( bundle.rating() ); + else if( column == PlaylistItem::Mood ) + OSDWidget::setMoodbar( bundle ); + } + + for( int n = availableTags.count(), i = 0; i < n; ++i ) + { + const int column = availableTags.at( i ); + QString append = ( i == 0 ) ? "" + : ( n > 1 && i == n / 2 ) ? "\n" + : ( parens.contains( column ) || parens.contains( availableTags.at( i - 1 ) ) ) ? " " + : i18n(" - "); + append += ( parens.contains( column ) ? "(%1)" : "%1" ); + text += append.arg( tags.at( column + 1 ) ); + } + } + else + { + QMap args; + args["prettytitle"] = bundle.prettyTitle(); + for( int i = 0; i < PlaylistItem::NUM_COLUMNS; ++i ) + args[bundle.exactColumnName( i ).lower()] = bundle.prettyText( i ); + + if( bundle.length() <= 0 ) + args["length"] = QString::null; + + + uint time=EngineController::instance()->engine()->position(); + uint sec=(time/1000)%60; //is there a better way to calculate the time? + time /= 1000; + uint min=(time/60)%60; + time /= 60; + uint hour=(time/60)%60; + QString timeformat=""; + if(hour!=0) + { + timeformat += QString::number(hour); + timeformat +=":"; + } + timeformat +=QString::number(min); + timeformat +=":"; + if(sec<10) + timeformat +="0"; + timeformat +=QString::number(sec); + args["elapsed"]=timeformat; + QStringx osd = AmarokConfig::osdText(); + + // hacky, but works... + if( osd.contains( "%rating" ) ) + OSDWidget::setRating( AmarokConfig::useRatings() ? bundle.rating() : 0 ); + else + OSDWidget::setRating( 0 ); + + osd.replace( "%rating", "" ); + + if( osd.contains( "%moodbar" ) && AmarokConfig::showMoodbar() ) + OSDWidget::setMoodbar( bundle ); + osd.replace( "%moodbar", "" ); + + text = osd.namedOptArgs( args ); + + // KDE 3.3 rejects \n in the .kcfg file, and KConfig turns \n into \\n, so... + text.replace( "\\n", "\n" ); + } + + if ( AmarokConfig::osdCover() ) { + //avoid showing the generic cover. we can overwrite this by passing an arg. + //get large cover for scaling if big cover needed + + QString location = QString::null; + if( bundle.podcastBundle() ) + location = CollectionDB::instance()->podcastImage( bundle, false, 0 ); + else + location = CollectionDB::instance()->albumImage( bundle, false, 0 ); + + if ( location.find( "nocover" ) != -1 ) + setImage( Amarok::icon() ); + else + setImage( location ); + } + + text = text.stripWhiteSpace(); + } + + if( text.isEmpty() ) + text = MetaBundle::prettyTitle( bundle.url().fileName() ).stripWhiteSpace(); + + if( text.startsWith( "- " ) ) //When we only have a title tag, _something_ prepends a fucking hyphen. Remove that. + text = text.mid( 2 ); + + if( text.isEmpty() ) //still + text = i18n("No information available for this track"); + + OSDWidget::show( text ); +#else + Q_UNUSED( bundle ); +#endif +} + +void +Amarok::OSD::applySettings() +{ + setAlignment( static_cast( AmarokConfig::osdAlignment() ) ); + setDuration( AmarokConfig::osdDuration() ); +#ifdef Q_WS_X11 + setEnabled( AmarokConfig::osdEnabled() ); +#else + setEnabled( false ); +#endif + setOffset( AmarokConfig::osdYOffset() ); + setScreen( AmarokConfig::osdScreen() ); + setFont( AmarokConfig::osdFont() ); + setDrawShadow( AmarokConfig::osdDrawShadow() ); + setTranslucency( AmarokConfig::osdUseFakeTranslucency() ); + + if( AmarokConfig::osdUseCustomColors() ) + { + setTextColor( AmarokConfig::osdTextColor() ); + setBackgroundColor( AmarokConfig::osdBackgroundColor() ); + } + else unsetColors(); +} + +void +Amarok::OSD::forceToggleOSD() +{ +#ifdef Q_WS_X11 + if ( !isShown() ) { + const bool b = isEnabled(); + setEnabled( true ); + show( EngineController::instance()->bundle() ); + setEnabled( b ); + } + else + hide(); +#endif +} + +void +Amarok::OSD::slotCoverChanged( const QString &artist, const QString &album ) +{ + if( AmarokConfig::osdCover() && artist == EngineController::instance()->bundle().artist() + && album == EngineController::instance()->bundle().album() ) + { + QString location = CollectionDB::instance()->albumImage( artist, album, false, 0 ); + + if( location.find( "nocover" ) != -1 ) + setImage( Amarok::icon() ); + else + setImage( location ); + } +} + +void +Amarok::OSD::slotImageChanged( const QString &remoteURL ) +{ + QString url = EngineController::instance()->bundle().url().url(); + PodcastEpisodeBundle peb; + if( CollectionDB::instance()->getPodcastEpisodeBundle( url, &peb ) ) + { + PodcastChannelBundle pcb; + if( CollectionDB::instance()->getPodcastChannelBundle( peb.parent().url(), &pcb ) ) + { + if( pcb.imageURL().url() == remoteURL ) + { + QString location = CollectionDB::instance()->podcastImage( remoteURL, false, 0 ); + if( location == CollectionDB::instance()->notAvailCover( false, 0 ) ) + setImage( Amarok::icon() ); + else + setImage( location ); + } + } + } +} + + +/* Code copied from kshadowengine.cpp + * + * Copyright (C) 2003 Laur Ivan + * + * Many thanks to: + * - Bernardo Hung for the enhanced shadow + * algorithm (currently used) + * - Tim Jansen for the API updates and fixes. + * + * This library 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 library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Steet, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +namespace ShadowEngine +{ + // Not sure, doesn't work above 10 + static const int MULTIPLICATION_FACTOR = 3; + // Multiplication factor for pixels directly above, under, or next to the text + static const double AXIS_FACTOR = 2.0; + // Multiplication factor for pixels diagonal to the text + static const double DIAGONAL_FACTOR = 0.1; + // Self explanatory + static const int MAX_OPACITY = 200; + + double decay( QImage&, int, int ); + + QImage makeShadow( const QPixmap& textPixmap, const QColor &bgColor ) + { + QImage result; + + const int w = textPixmap.width(); + const int h = textPixmap.height(); + const int bgr = bgColor.red(); + const int bgg = bgColor.green(); + const int bgb = bgColor.blue(); + + int alphaShadow; + + // This is the source pixmap + QImage img = textPixmap.convertToImage().convertDepth( 32 ); + + result.create( w, h, 32 ); + result.fill( 0 ); // fill with black + result.setAlphaBuffer( true ); + + static const int M = 5; + for( int i = M; i < w - M; i++) { + for( int j = M; j < h - M; j++ ) + { + alphaShadow = (int) decay( img, i, j ); + + result.setPixel( i,j, qRgba( bgr, bgg , bgb, QMIN( MAX_OPACITY, alphaShadow ) ) ); + } + } + + return result; + } + + double decay( QImage& source, int i, int j ) + { + //if ((i < 1) || (j < 1) || (i > source.width() - 2) || (j > source.height() - 2)) + // return 0; + + double alphaShadow; + alphaShadow =(qGray(source.pixel(i-1,j-1)) * DIAGONAL_FACTOR + + qGray(source.pixel(i-1,j )) * AXIS_FACTOR + + qGray(source.pixel(i-1,j+1)) * DIAGONAL_FACTOR + + qGray(source.pixel(i ,j-1)) * AXIS_FACTOR + + 0 + + qGray(source.pixel(i ,j+1)) * AXIS_FACTOR + + qGray(source.pixel(i+1,j-1)) * DIAGONAL_FACTOR + + qGray(source.pixel(i+1,j )) * AXIS_FACTOR + + qGray(source.pixel(i+1,j+1)) * DIAGONAL_FACTOR) / MULTIPLICATION_FACTOR; + + return alphaShadow; + } +} + +#include "osd.moc" diff --git a/amarok/src/osd.h b/amarok/src/osd.h new file mode 100644 index 00000000..5ad577e7 --- /dev/null +++ b/amarok/src/osd.h @@ -0,0 +1,184 @@ +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. +*/ + +/* + osd.h - Provides an interface to a plain QWidget, which is independent of KDE (bypassed to X11) + begin: Fre Sep 26 2003 + copyright: (C) 2003 by Christian Muehlhaeuser + email: chris@chris.de +*/ + +#ifndef AMAROK_OSD_H +#define AMAROK_OSD_H + +#include "metabundle.h" + +#include +#include +#include +#include //baseclass + + +class OSDWidget : public QWidget +{ + Q_OBJECT + + public: + enum Alignment { Left, Middle, Center, Right }; + + OSDWidget( QWidget *parent, const char *name = "osd" ); + + /** resets the colours to defaults */ + void unsetColors(); + + public slots: + /** calls setText() then show(), after setting image if needed */ + void show( const QString &text, QImage newImage = QImage::QImage() ); + void ratingChanged( const short rating ); + void ratingChanged( const QString& path, int rating ); + void volChanged( unsigned char volume ); + + /** reimplemented, shows the OSD */ + virtual void show(); + + /** + * For the sake of simplicity, when these settings are + * changed they do not take effect until the next time + * the OSD is shown! + * + * To force an update call show(); + */ + void setDuration( int ms ) { m_duration = ms; } + void setTextColor( const QColor &color ) { setPaletteForegroundColor( color ); } + void setBackgroundColor(const QColor &color ) { setPaletteBackgroundColor( color ); } + void setOffset( int y ) { m_y = y; } + void setAlignment( Alignment alignment ) { m_alignment = alignment; } + void setImage( const QImage &image ) { m_cover = image; } + void setScreen( int screen ); + void setText( const QString &text ) { m_text = text; } + void setDrawShadow( const bool b ) { m_drawShadow = b; } + void setTranslucency( const bool b ) { m_translucency = b; } + void setRating( const short rating ) { if ( isEnabled() ) m_rating = rating; } + void setMoodbar( void ) { m_moodbarBundle = MetaBundle(); } + void setMoodbar( const MetaBundle &bundle ) + { m_moodbarBundle = bundle; m_moodbarBundle.moodbar().load(); } + + protected: + /** determine new size and position */ + QRect determineMetrics( const uint marginMetric ); + + /** render OSD */ + void render( const uint marginMetric, const QSize &size ); + + /** reimplemented */ + virtual void mousePressEvent( QMouseEvent* ); + virtual bool event( QEvent* ); + + bool useMoodbar( void ); + + /** distance from screen edge */ + static const int MARGIN = 15; + + int m_duration; + QTimer *m_timer; + Alignment m_alignment; + int m_screen; + uint m_y; + bool m_drawShadow; + bool m_translucency; + short m_rating; + unsigned char m_newvolume; + bool m_volume; + QString m_text; + QImage m_cover; + // need a whole MetaBundle to draw the moodbar on the fly + MetaBundle m_moodbarBundle; + QPixmap m_scaledCover; + KPixmap m_screenshot; + QPixmap m_buffer; +}; + + + +class OSDPreviewWidget : public OSDWidget +{ + Q_OBJECT + +public: + OSDPreviewWidget( QWidget *parent ); + + int screen() { return m_screen; } + int alignment() { return m_alignment; } + int y() { return m_y; } + +public slots: + void setTextColor( const QColor &color ) { OSDWidget::setTextColor( color ); doUpdate(); } + void setBackgroundColor(const QColor &color ) { OSDWidget::setBackgroundColor( color ); doUpdate(); } + void setDrawShadow( bool b ) { OSDWidget::setDrawShadow( b ); doUpdate(); } + void setFont( const QFont &font ) { OSDWidget::setFont( font ); doUpdate(); } + void setScreen( int screen ) { OSDWidget::setScreen( screen ); doUpdate(); } + void setUseCustomColors( const bool use, const QColor &fg, const QColor &bg ) + { + if( use ) { + OSDWidget::setTextColor( fg ); + OSDWidget::setBackgroundColor( bg ); + } else + unsetColors(); + doUpdate(); + } + +private: + inline void doUpdate() { if( isShown() ) show(); } + +signals: + void positionChanged(); + +protected: + void mousePressEvent( QMouseEvent * ); + void mouseReleaseEvent( QMouseEvent * ); + void mouseMoveEvent( QMouseEvent * ); + +private: + bool m_dragging; + QPoint m_dragOffset; +}; + + + +namespace Amarok +{ + class OSD : public OSDWidget + { + Q_OBJECT + + public: + static OSD *instance() + { + static OSD *s_instance = new OSD; + return s_instance; + } + + void applySettings(); + void show( const MetaBundle &bundle ); + + public slots: + /** + * When user pushs global shortcut or uses DCOP OSD is toggle + * even if it is disabled() + */ + void forceToggleOSD(); + + private: + OSD(); + + private slots: + void slotCoverChanged( const QString &artist, const QString &album ); + void slotImageChanged( const QString &remoteURL ); + }; +} + +#endif /*AMAROK_OSD_H*/ diff --git a/amarok/src/pixmapviewer.cpp b/amarok/src/pixmapviewer.cpp new file mode 100644 index 00000000..764aedd3 --- /dev/null +++ b/amarok/src/pixmapviewer.cpp @@ -0,0 +1,65 @@ +/*************************************************************************** + * Copyright (C) 2005 Eyal Lotem * + * Copyright (C) 2005 Alexandre Oliveira * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Steet, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ +#include "pixmapviewer.h" +#include +#include +#include + +PixmapViewer::PixmapViewer(QWidget *widget, const QPixmap &pixmap) + : QScrollView(widget, 0, WNoAutoErase) + , m_isDragging(false) + , m_pixmap(pixmap) +{ + resizeContents( m_pixmap.width(), m_pixmap.height() ); +} + +void PixmapViewer::drawContents( QPainter * p, int clipx, int clipy, int clipw, int cliph ) { + p->drawPixmap(QPoint(clipx, clipy), + m_pixmap, + QRect(clipx, clipy, clipw, cliph)); +} + +void PixmapViewer::contentsMousePressEvent(QMouseEvent *event) { + if(LeftButton == event->button()) { + m_currentPos = event->globalPos(); + m_isDragging = true; + } +} + +void PixmapViewer::contentsMouseReleaseEvent(QMouseEvent *event) { + if(LeftButton == event->button()) { + m_currentPos = event->globalPos(); + m_isDragging = false; + } +} + +void PixmapViewer::contentsMouseMoveEvent(QMouseEvent *event) { + if(m_isDragging) { + QPoint delta = m_currentPos - event->globalPos(); + scrollBy(delta.x(), delta.y()); + m_currentPos = event->globalPos(); + } +} + +QSize PixmapViewer::maximalSize() { + return m_pixmap.size().boundedTo( KApplication::desktop()->size() ) + size() - viewport()->size(); +} + +#include "pixmapviewer.moc" diff --git a/amarok/src/pixmapviewer.h b/amarok/src/pixmapviewer.h new file mode 100644 index 00000000..28a63d1e --- /dev/null +++ b/amarok/src/pixmapviewer.h @@ -0,0 +1,45 @@ +/*************************************************************************** + * Copyright (C) 2005 Eyal Lotem * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Steet, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ +#ifndef PIXMAPVIEWER_H +#define PIXMAPVIEWER_H + +#include + +class PixmapViewer : public QScrollView { + Q_OBJECT + +public: + PixmapViewer(QWidget *widget, const QPixmap &pixmap); + + // The size of this widget that requires no scrollbars + QSize maximalSize(); + + void drawContents( QPainter * p, int clipx, int clipy, int clipw, int cliph ); + + void contentsMousePressEvent(QMouseEvent *event); + void contentsMouseReleaseEvent(QMouseEvent *event); + void contentsMouseMoveEvent(QMouseEvent *event); + +private: + bool m_isDragging; + QPoint m_currentPos; + const QPixmap &m_pixmap; +}; + +#endif diff --git a/amarok/src/playerwindow.cpp b/amarok/src/playerwindow.cpp new file mode 100644 index 00000000..a4a2339a --- /dev/null +++ b/amarok/src/playerwindow.cpp @@ -0,0 +1,955 @@ +/*************************************************************************** + playerwidget.cpp - description + ------------------- +begin : Mit Nov 20 2002 +copyright : (C) 2002 by Mark Kretschmann +email : markey@web.de +***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "actionclasses.h" +#include "amarok.h" +#include "amarokconfig.h" +#include "analyzerbase.h" +#include "debug.h" +#include "enginecontroller.h" +#include "metabundle.h" //setScroll() +#include "playerwindow.h" +#include "sliderwidget.h" +#include "tracktooltip.h" //setScroll() + +#include //our quit shortcut in the ctor +#include //various events +#include +#include +#include +#include +#include +#include +#include +#include //analyzer tooltip + +#include +#include +#include +#include +#include +#include +#include //eventFilter() + +//simple function for fetching amarok images +namespace Amarok +{ + //TODO remove these, they suck, do a generic getImage + + QPixmap getPNG( const QString &filename ) + { + QString file = !filename.endsWith( ".png", false ) ? "amarok/images/%1.png" : "amarok/images/%1"; + + return QPixmap( locate( "data", file.arg( filename ) ), "PNG" ); + } + + QPixmap getJPG( const QString &filename ) + { + QString file = !filename.endsWith( ".jpg", false ) ? "amarok/images/%1.jpg" : "amarok/images/%1"; + + return QPixmap( locate( "data", QString( "amarok/images/%1.jpg" ).arg( filename ) ), "JPEG" ); + } +} + +using Amarok::getPNG; + + +//fairly pointless template which was designed to make the ctor clearer, +//but probably achieves the opposite. Still, the code is neater.. +template static inline W* +createWidget( const QRect &r, QWidget *parent, const char *name = 0, Qt::WFlags f = 0 ) +{ + W *w = new W( parent, name, f ); + w->setGeometry( r ); + return w; +} + + +PlayerWidget::PlayerWidget( QWidget *parent, const char *name, bool enablePlaylist ) + : QWidget( parent, name, Qt::WType_TopLevel ) + , EngineObserver( EngineController::instance() ) + , m_minimalView( false ) + , m_pAnimTimer( new QTimer( this ) ) + , m_scrollBuffer( 291, 16 ) + , m_plusPixmap( getPNG( "time_plus" ) ) + , m_minusPixmap( getPNG( "time_minus" ) ) + , m_pAnalyzer( 0 ) +{ + //the createWidget template function is used here + //createWidget just creates a widget which has it's geometry set too + + // Sets caption and icon correctly (needed e.g. for GNOME) + kapp->setTopWidget( this ); + + parent->installEventFilter( this ); //for hidePLaylistWithMainWindow mode + + //if this is the first time we have ever been run we let KWin place us + if ( AmarokConfig::playerPos() != QPoint(-1,-1) ) + move( AmarokConfig::playerPos() ); + + setModifiedPalette(); + setFixedSize( 311, 140 ); + setCaption( "Amarok" ); + setAcceptDrops( true ); + + //another quit shortcut because the other window has all the accels + QAccel *accel = new QAccel( this ); + accel->insertItem( CTRL + Key_Q ); + connect( accel, SIGNAL( activated( int ) ), kapp, SLOT( quit() ) ); + + QFont font; + font.setBold( true ); + font.setPixelSize( 10 ); + setFont( font ); + + { // + //NOTE we use a layout for the buttons so resizing will be possible + m_pFrameButtons = createWidget( QRect(0, 118, 311, 22), this ); + + KActionCollection *ac =Amarok::actionCollection(); + + //FIXME change the names of the icons to reflect kde names so we can fall back to them if necessary + new NavButton( m_pFrameButtons, "prev", ac->action( "prev" ) ); + m_pButtonPlay = new NavButton( m_pFrameButtons, "play", ac->action( "play" ) ); + m_pButtonPause = new NavButton( m_pFrameButtons, "pause", ac->action( "pause" ) ); + new NavButton( m_pFrameButtons, "stop", ac->action( "stop" ) ); + new NavButton( m_pFrameButtons, "next", ac->action( "next" ) ); + + KPushButton *switchView = new KPushButton( KGuiItem( "", "mini_dock" ), m_pFrameButtons ); + switchView->setSizePolicy( QSizePolicy::Maximum, QSizePolicy::Preferred ); // too big! + switchView->setFocusPolicy( QWidget::NoFocus ); + connect( switchView, SIGNAL( clicked() ), SLOT( toggleView() ) ); + + + m_pButtonPlay->setToggleButton( true ); + m_pButtonPause->setToggleButton( true ); + } // + + { // + m_pSlider = new Amarok::PrettySlider( Qt::Horizontal, + Amarok::PrettySlider::Pretty, this ); + m_pVolSlider = new Amarok::PrettySlider( Qt::Vertical, + Amarok::PrettySlider::Pretty, this, + Amarok::VOLUME_MAX ); + + m_pSlider->setGeometry( 4,103, 303, 12 ); + m_pVolSlider->setGeometry( 294,18, 12,79 ); + m_pVolSlider->setValue( AmarokConfig::masterVolume() ); + + EngineController* const ec = EngineController::instance(); + connect( m_pSlider, SIGNAL(sliderReleased( int )), ec, SLOT(seek( int )) ); + connect( m_pSlider, SIGNAL(valueChanged( int )), SLOT(timeDisplay( int )) ); + connect( m_pVolSlider, SIGNAL(sliderMoved( int )), ec, SLOT(setVolume( int )) ); + connect( m_pVolSlider, SIGNAL(sliderReleased( int )), ec, SLOT(setVolume( int )) ); + } // + + { // + font.setPixelSize( 11 ); + const int fontHeight = QFontMetrics( font ).height(); //the real height is more like 13px + + m_pScrollFrame = createWidget( QRect(6,18, 285,fontHeight), this ); + m_pScrollFrame->setFont( font ); + { // + + } // + font.setPixelSize( 18 ); + + m_pTimeLabel = createWidget( QRect(16,36, 9*12+2,18), this, 0, Qt::WNoAutoErase ); + m_pTimeLabel->setFont( font ); + + m_timeBuffer.resize( m_pTimeLabel->size() ); + m_timeBuffer.fill( backgroundColor() ); + } // + + + m_pButtonEq = new IconButton( this, "eq", this, SLOT(slotShowEqualizer( bool )) ); + m_pButtonEq->setGeometry( 34,85, 28,13 ); + //TODO set isOn() + + m_pPlaylistButton = new IconButton( this, "pl", SIGNAL(playlistToggled( bool )) ); + m_pPlaylistButton->setGeometry( 5,85, 28,13 ); + m_pPlaylistButton->setOn( parent->isShown() || enablePlaylist ); + + + m_pDescription = createWidget( QRect(4,6, 250,10), this ); + m_pTimeSign = createWidget( QRect(6,40, 10,10), this, 0, Qt::WRepaintNoErase ); + m_pVolSign = createWidget( QRect(295,7, 9,8), this ); + + m_pDescription->setText( i18n( "Artist-Title|Album|Length" ) ); + m_pVolSign ->setPixmap( getPNG( "vol_speaker" ) ); + + + //do before we set the widget's state + applySettings(); + + //set interface to correct state + engineStateChanged( EngineController::engine()->state() ); + + createAnalyzer( 0 ); + + //so we get circulation events to x11Event() + //XSelectInput( x11Display(), winId(), StructureNotifyMask ); + + //Yagami mode! + //KWin::setState( winId(), NET::KeepBelow | NET::SkipTaskbar | NET::SkipPager ); + //KWin::setType( winId(), NET::Override ); + //KWin::setOnAllDesktops( winId(), true ); + + connect( m_pAnimTimer, SIGNAL( timeout() ), SLOT( drawScroll() ) ); + + TrackToolTip::instance()->addToWidget( m_pScrollFrame ); +} + + +PlayerWidget::~PlayerWidget() +{ + AmarokConfig::setPlayerPos( pos() ); + AmarokConfig::setPlaylistWindowEnabled( m_pPlaylistButton->isOn() ); + TrackToolTip::instance()->removeFromWidget( m_pScrollFrame ); +} + + +// METHODS ---------------------------------------------------------------- + +void PlayerWidget::setScroll( const QStringList &list ) +{ + QString text; + QStringList list2( list ); + QStringList::Iterator end( list2.end() ); + for( QStringList::Iterator it = list2.begin(); it != end; ) + { + if( !(*it).isEmpty() ) + { + text.append( *it ); + ++it; + } + else it = list2.remove( it ); + } + + //FIXME empty QString would crash due to NULL Pixmaps + if( text.isEmpty() ) text = i18n( "Please report this message to amarok@kde.org, thanks!" ); + + QFont font( m_pScrollFrame->font() ); + QFontMetrics fm( font ); + const uint separatorWidth = 21; + const uint baseline = font.pixelSize(); //the font actually extends below its pixelHeight + const uint separatorYPos = baseline - fm.boundingRect( "x" ).height() + 1; + + m_scrollTextPixmap.resize( fm.width( text ) + list2.count() * separatorWidth, m_pScrollFrame->height() ); + m_scrollTextPixmap.fill( backgroundColor() ); + + QPainter p( &m_scrollTextPixmap ); + p.setPen( foregroundColor() ); + p.setFont( font ); + uint x = 0; + + for( QStringList::ConstIterator it = list2.constBegin(); + it != list2.constEnd(); + ++it ) + { + p.drawText( x, baseline, *it ); + x += fm.width( *it ); + p.fillRect( x + 8, separatorYPos, 4, 4, Amarok::ColorScheme::Foreground ); + x += separatorWidth; + } + + drawScroll(); +} + + +void PlayerWidget::drawScroll() +{ + static uint phase = 0; + + QPixmap* const buffer = &m_scrollBuffer; + QPixmap* const scroll = &m_scrollTextPixmap; + + const uint topMargin = 0; //moved margins into widget placement + const uint leftMargin = 0; //as this makes it easier to fiddle + const uint w = m_scrollTextPixmap.width(); + const uint h = m_scrollTextPixmap.height(); + + phase += SCROLL_RATE; + if( phase >= w ) phase = 0; + + int subs = 0; + int dx = leftMargin; + uint phase2 = phase; + + while( dx < m_pScrollFrame->width() ) + { + subs = -m_pScrollFrame->width() + topMargin; + subs += dx + ( w - phase2 ); + if( subs < 0 ) subs = 0; + + bitBlt( buffer, dx, topMargin, scroll, phase2, 0, w - phase2 - subs, h, Qt::CopyROP ); + + dx += w - phase2; + phase2 += w - phase2; + + if( phase2 >= w ) phase2 = 0; + } + + bitBlt( m_pScrollFrame, 0, 0, buffer ); +} + + +void PlayerWidget::engineStateChanged( Engine::State state, Engine::State /*oldState*/ ) +{ + DEBUG_BLOCK + + switch( state ) + { + case Engine::Empty: + m_pButtonPlay->setOn( false ); + m_pButtonPause->setOn( false ); + m_pSlider->setValue( 0 ); + m_pSlider->setMinValue( 0 ); + m_pSlider->setMaxValue( 0 ); + m_pSlider->newBundle( MetaBundle() ); // Set an empty bundle for no moodbar + m_pTimeLabel->hide(); + m_pTimeSign->hide(); + m_rateString = QString::null; + m_pSlider->setEnabled( false ); + setScroll( i18n( "Welcome to Amarok" ) ); + update(); + break; + + case Engine::Playing: + if( !m_minimalView ) + { + m_pTimeLabel->show(); + m_pTimeSign->show(); + } + m_pButtonPlay->setOn( true ); + m_pButtonPause->setOn( false ); + break; + + case Engine::Paused: + m_pButtonPause->setOn( true ); + break; + + case Engine::Idle: //don't really want to do anything when idle + break; + } +} + + +void PlayerWidget::engineVolumeChanged( int percent ) +{ + m_pVolSlider->setValue( percent ); +} + + +void PlayerWidget::engineNewMetaData( const MetaBundle &bundle, bool ) +{ + m_currentURL == bundle.url().path(); + + m_pSlider->setMinValue( 0 ); // Important. minValue could have been changed by bogus maxValues + m_pSlider->setMaxValue( bundle.length() * 1000 ); + m_pSlider->setEnabled( bundle.length() > 0 ); + m_pSlider->newBundle( bundle ); + + m_rateString = bundle.prettyBitrate(); + QString Hz = bundle.prettySampleRate( true ); + if( !Hz.isEmpty() ) + { + if( m_rateString.isEmpty() ) + m_rateString = Hz; + else + m_rateString = i18n("%1 kBit - %2").arg( m_rateString, Hz ); + } + + QStringList list( bundle.prettyTitle() ); + list << bundle.album(); + if( bundle.length() ) list << bundle.prettyLength(); + setScroll( list ); + + update(); //we need to update rateString +} + + +void PlayerWidget::engineTrackPositionChanged( long position, bool /*userSeek*/ ) +{ + m_pSlider->setValue( position ); + + if( !m_pSlider->isEnabled() ) timeDisplay( position ); +} + + +void PlayerWidget::engineTrackLengthChanged( long length ) +{ + m_pSlider->setMaxValue( length * 1000 ); +} + + +void PlayerWidget::timeDisplay( int ms ) +{ + int seconds = ms / 1000; + const int songLength = EngineController::instance()->bundle().length(); + const bool showRemaining = AmarokConfig::leftTimeDisplayRemaining() && songLength > 0; + + if( showRemaining ) seconds = songLength - seconds; + + m_timeBuffer.fill( backgroundColor() ); + QPainter p( &m_timeBuffer ); + p.setPen( foregroundColor() ); + p.setFont( m_pTimeLabel->font() ); + p.drawText( 0, 16, MetaBundle::prettyTime( seconds ) ); //FIXME remove padding, instead move()! + bitBlt( m_pTimeLabel, 0, 0, &m_timeBuffer ); + + m_pTimeSign->setPixmap( showRemaining ? m_minusPixmap : m_plusPixmap ); +} + + +static inline QColor comodulate( int hue, QColor target ) +{ + ///this function is only used by determineAmarokColors() + int ignore, s, v; + target.getHsv( &ignore, &s, &v ); + return QColor( hue, s, v, QColor::Hsv ); +} + +void PlayerWidget::determineAmarokColors() //static +{ + int hue, s, v; + + (!AmarokConfig::schemeKDE() + ? AmarokConfig::playlistWindowBgColor() + : KGlobalSettings::highlightColor() + ).getHsv( &hue, &s, &v ); + + using namespace Amarok::ColorScheme; + + Text = Qt::white; + Background = comodulate( hue, 0x002090 ); + Foreground = comodulate( hue, 0x80A0FF ); + + //ensure the base colour does not conflict with the window decoration colour + //however generally it is nice to leave the other colours with the highlight hue + //because the scheme is then "complimentary" + //TODO schemes that have totally different active/inactive decoration colours need to be catered for too! + if ( AmarokConfig::schemeKDE() ) { + int h; + KGlobalSettings::activeTitleColor().getHsv( &h, &s, &v ); + if( QABS( hue - h ) > 120 ) + hue = h; + } + + Base = comodulate( hue, Amarok::blue ); +} + +void PlayerWidget::setModifiedPalette() +{ + QPalette p = QApplication::palette(); + QColorGroup cg = p.active(); + cg.setColor( QColorGroup::Background, Amarok::ColorScheme::Base ); + cg.setColor( QColorGroup::Foreground, Amarok::ColorScheme::Text ); + setPalette( QPalette(cg, p.disabled(), cg) ); +} + +void PlayerWidget::applySettings() +{ + //NOTE DON'T use unsetFont(), we use custom font sizes (for now) + QFont phont = font(); + phont.setFamily( AmarokConfig::useCustomFonts() + ? AmarokConfig::playerWidgetFont().family() + : QApplication::font().family() ); + setFont( phont ); + + setModifiedPalette(); + + //update the scroller + switch( EngineController::engine()->state() ) { + case Engine::Empty: + m_scrollTextPixmap.fill( Amarok::ColorScheme::Base ); + update(); + break; + default: + engineNewMetaData( EngineController::instance()->bundle(), false ); + } + + if(m_pAnalyzer) + setMinimalView(m_minimalView); +} + +void PlayerWidget::setMinimalView( bool enable ) +{ + m_pAnalyzer->setHidden( enable ); + m_pTimeLabel->setHidden( enable ); + m_pTimeSign->setHidden( enable ); + m_pDescription->setHidden( enable ); + m_pButtonEq->setHidden( enable ); + m_pPlaylistButton->setHidden( enable ); + m_pVolSlider->setHidden( enable ); + + if( enable ) + { + uint space = 2; + m_pScrollFrame->setGeometry ( 6,space, m_pScrollFrame->width(), m_pScrollFrame->height() ); + m_pSlider->setGeometry ( 4,space + m_pScrollFrame->height(), 303, 12 ); + m_pFrameButtons->setGeometry( 0,space + m_pScrollFrame->height() + m_pSlider->height(), 311,22 ); + uint height = m_pFrameButtons->height() + m_pScrollFrame->height() + m_pSlider->height() + space; + setFixedSize( 311, height ); + AmarokConfig::setPlayerWindowMinimalView( true ); + } + else + { + m_pScrollFrame->setGeometry( 6,18, m_pScrollFrame->width(),m_pScrollFrame->height() ); + m_pSlider->setGeometry( 4,103, 303,12 ); + m_pFrameButtons->setGeometry(0,118, 311,22); + setFixedSize( 311, 140 ); + AmarokConfig::setPlayerWindowMinimalView( false ); + } + + m_minimalView = enable; + update(); +} + + +// EVENTS ----------------------------------------------------------------- + +static bool dontChangeButtonState = false; //FIXME I hate this hack + +bool PlayerWidget::event( QEvent *e ) +{ + switch( e->type() ) + { + case QEvent::Wheel: + case QEvent::DragEnter: + case QEvent::Drop: + case QEvent::Close: + + Amarok::genericEventHandler( this, e ); + return true; //we handled it + + case QEvent::ApplicationPaletteChange: + + if( AmarokConfig::schemeKDE() ) + { + determineAmarokColors(); + applySettings(); + } + return true; + + case 6/*QEvent::KeyPress*/: + if (static_cast(e)->key() == Qt::Key_D/* && (m_pAnalyzer->inherits("QGLWidget")*/) + { + if( m_pAnalyzer->parent() ) + { + m_pAnalyzer->reparent( 0, QPoint(50,50), true ); + m_pAnalyzer->setCaption( kapp->makeStdCaption( i18n("Analyzer") ) ); + m_pAnalyzer->installEventFilter( this ); + m_pAnalyzer->setPaletteBackgroundColor( paletteBackgroundColor() ); + QToolTip::remove( m_pAnalyzer ); + } + else + createAnalyzer( 0 ); + + return true; //eat event + } + return false; //don't eat event + + case QEvent::Show: + + m_pAnimTimer->start( ANIM_TIMER ); + + if( m_pPlaylistButton->isOn() ) + { + //IMPORTANT! If the PlaylistButton is on then we MUST be shown + //we leave the PlaylistButton "on" to signify that we should restore it here + //we leave it on when we do a hidePlaylistWithPlayerWindow type action + + //IMPORTANT - I beg of you! Please leave all this alone, it was hell to + //create! If you have an issue with the behaviour bring it up on the mailing + //list before you even think about committing. Thanks! (includes case Hide) + + const WId id = parentWidget()->winId(); + const uint desktop = KWin::windowInfo( winId() ).desktop(); + const KWin::WindowInfo info = KWin::windowInfo( id ); + + //check the Playlist Window is on the correct desktop + if( !info.isOnDesktop( desktop ) ) KWin::setOnDesktop( id, desktop ); + + if( info.mappingState() == NET::Withdrawn ) + { + //extern Atom qt_wm_state; //XAtom defined by Qt + + //TODO prevent the active Window flicker from playlist to player window please! + //TODO look at code for QWidget::show(); + + //XDeleteProperty( qt_xdisplay(), id, qt_wm_state ); + + //parentWidget()->show(); + //if( !parentWidget()->isShown() ) XMapWindow( qt_xdisplay(), id ); +// unsigned long data[2]; +// data[0] = (unsigned long) NormalState; +// data[1] = (unsigned long) None; +// +// XChangeProperty( qt_xdisplay(), id, qt_wm_state, qt_wm_state, 32, +// PropModeReplace, (unsigned char *)data, 2); +// +// KWin::clearState( id, NET::Hidden ); +// +// XMapWindow( qt_xdisplay(), id ); +// + //KWin::deIconifyWindow( id, false ); + parentWidget()->show(); + } + + + if( info.isMinimized() ) + { + //then the user will expect us to deiconify the Playlist Window + //the PlaylistButton would be off otherwise (honest!) + KWin::deIconifyWindow( id, false ); + + } + } + + return false; + + case QEvent::Hide: + m_pAnimTimer->stop(); + + { + //this prevents the PlaylistButton being set to off (see the eventFilter) + //by leaving it on we ensure that we show the Playlist Window again when + //we are next shown (see Show event handler above) + if( parentWidget()->isShown() ) dontChangeButtonState = true; + + if( e->spontaneous() ) //the window system caused the event + { + //if we have been iconified, iconify the Playlist Window too + //if we have been shaded, hide the PlaylistWindow + //if the user is on another desktop to Amarok, do nothing + + const KWin::WindowInfo info = KWin::windowInfo( winId() ); + + if( info.isMinimized() ) KWin::iconifyWindow( parentWidget()->winId(), false ); + else + //this may seem strange, but it is correct + //we have a handler in eventFilter for all other eventualities + dontChangeButtonState = false; + + } + else + //we caused Amarok to hide, so we should hide the Playlist Window + //NOTE we "override" closeEvents and thus they count as non-spontaneous + //hideEvents; which frankly is a huge relief! + parentWidget()->hide(); + } + + return false; + + default: + return QWidget::event( e ); + } +} + +// bool +// PlayerWidget::x11Event( XEvent *e ) +// { +// if( e->type == ConfigureNotify ) +// { +// kdDebug() << "CirculateNotify\n"; +// XRaiseWindow( x11Display(), playlistWindow()->winId() ); +// } +// +// return false; +// } + +bool +PlayerWidget::eventFilter( QObject *o, QEvent *e ) +{ + //NOTE we only monitor for parent() - which is the PlaylistWindow + + if( o == m_pAnalyzer ) + { + //delete analyzer, create same one back in Player Window + if( e->type() == QEvent::Close ) + { + createAnalyzer( 0 ); + return true; + } + return false; + } + + switch( e->type() ) + { + case QEvent::Close: + + static_cast(e)->accept(); //close the window! + return true; //don't let PlaylistWindow have the event - see PlaylistWindow::closeEvent() + + case QEvent::Hide: + + if( dontChangeButtonState ) + { + //we keep the PlaylistButton set to "on" - see event() for more details + //NOTE the Playlist Window will still be hidden + + dontChangeButtonState = false; + break; + } + + if( e->spontaneous() ) + { + //we want to avoid setting the button for most spontaneous events + //since they are not user driven, two are however: + + KWin::WindowInfo info = KWin::windowInfo( parentWidget()->winId() ); + + if( !info.isMinimized() ) break; + } + + //FALL THROUGH + + case QEvent::Show: + + if( isShown() ) + { + //only when shown means thaman:mkreiserfst using the global Show/Hide Playlist shortcut + //when in the tray doesn't effect the state of the PlaylistButton + //this is a good thing, but we have to set the state correctly when we are shown + + m_pPlaylistButton->blockSignals( true ); + m_pPlaylistButton->setOn( e->type() == QEvent::Show ); + m_pPlaylistButton->blockSignals( false ); + } + break; + + default: + break; + } + + return false; +} + + +void PlayerWidget::paintEvent( QPaintEvent* ) +{ + //uses widget's font and foregroundColor() - see ctor + QPainter p( this ); + if( !m_minimalView ) + p.drawText( 6, 68, m_rateString ); + + bitBlt( m_pScrollFrame, 0, 0, &m_scrollBuffer ); + bitBlt( m_pTimeLabel, 0, 0, &m_timeBuffer ); +} + + +void PlayerWidget::contextMenuEvent( QMouseEvent *e ) +{ + Amarok::Menu::instance()->exec( e->globalPos() ); +} + +void PlayerWidget::mousePressEvent( QMouseEvent *e ) +{ + if ( e->button() == QMouseEvent::RightButton ) + { + //Amarok::Menu::instance()->exec( e->globalPos() ); + } + else if ( m_pAnalyzer->geometry().contains( e->pos() ) ) + { + createAnalyzer( e->state() & Qt::ControlButton ? -1 : +1 ); + } + else + { + QRect + rect = m_pTimeLabel->geometry(); + rect |= m_pTimeSign->geometry(); + + if ( rect.contains( e->pos() ) ) + { + AmarokConfig::setLeftTimeDisplayRemaining( !AmarokConfig::leftTimeDisplayRemaining() ); + timeDisplay( EngineController::engine()->position() ); + } + else m_startDragPos = e->pos(); + } +} + + +void PlayerWidget::mouseMoveEvent( QMouseEvent *e ) +{ + if( e->state() & Qt::LeftButton ) + { + const int distance = (e->pos() - m_startDragPos).manhattanLength(); + + if( distance > QApplication::startDragDistance() ) startDrag(); + } +} + + +// SLOTS --------------------------------------------------------------------- + +void PlayerWidget::createAnalyzer( int increment ) +{ + AmarokConfig::setCurrentAnalyzer( AmarokConfig::currentAnalyzer() + increment ); + + delete m_pAnalyzer; + + m_pAnalyzer = Analyzer::Factory::createAnalyzer( this ); + m_pAnalyzer->setGeometry( 120,40, 168,56 ); + QToolTip::add( m_pAnalyzer, i18n( "Click for more analyzers, press 'd' to detach." ) ); + m_pAnalyzer->show(); + +} + +void PlayerWidget::startDrag() +{ + QDragObject *d = new QTextDrag( EngineController::instance()->bundle().prettyTitle(), this ); + d->dragCopy(); + // Qt will delete d for us. +} + + +void PlayerWidget::slotShowEqualizer( bool show ) //SLOT +{ + if( show ) + { + m_pButtonEq->setOff(); + + if ( !EngineController::hasEngineProperty( "HasEqualizer" ) ) + KMessageBox::sorry( 0, i18n( "Equalizer is not available with this engine." ) ); + + else + QTimer::singleShot( 0, kapp, SLOT( slotConfigEqualizer() ) ); + } +} + + +////////////////////////////////////////////////////////////////////////////////////////// +// CLASS NavButton +////////////////////////////////////////////////////////////////////////////////////////// + +#include +#include +NavButton::NavButton( QWidget *parent, const QString &icon, KAction *action ) + : QToolButton( parent ) + , m_glowIndex( 0 ) +{ + // Prevent flicker + setWFlags( Qt::WNoAutoErase ); + + QPixmap pixmap( getPNG( "b_" + icon ) ); + KIconEffect ie; + + // Tint icon blueish for "off" state + m_pixmapOff = ie.apply( pixmap, KIconEffect::Colorize, 0.5, QColor( 0x30, 0x10, 0xff ), false ); + // Tint gray and make pseudo-transparent for "disabled" state + m_pixmapDisabled = ie.apply( pixmap, KIconEffect::ToGray, 0.7, QColor(), true ); + + int r = 0x20, g = 0x10, b = 0xff; + float percentRed = 0.0; + QPixmap temp; + // Precalculate pixmaps for "on" icon state + for ( int i = 0; i < NUMPIXMAPS; i++ ) { + QImage img = pixmap.convertToImage(); + temp = KImageEffect::channelIntensity( img, percentRed, KImageEffect::Red ); + temp = ie.apply( temp, KIconEffect::Colorize, 1.0, QColor( r, 0x10, 0x30 ), false ); + temp = ie.apply( temp, KIconEffect::Colorize, 1.0, QColor( r, g, b ), false ); + + // Create new pixmap on the heap and add pointer to list + m_glowPixmaps.append( temp ); + + percentRed = percentRed + 1.0 / NUMPIXMAPS; + r += 14; + g += 2; + b -= 0; + } + // And the the same reversed + for ( int i = NUMPIXMAPS - 1; i > 0; i-- ) + { + QPixmap temp = m_glowPixmaps[i]; + m_glowPixmaps.append(temp); + } + + // This is just for initialization + QIconSet iconSet; + iconSet.setPixmap( pixmap, QIconSet::Automatic, QIconSet::Normal, QIconSet::Off ); + iconSet.setPixmap( pixmap, QIconSet::Automatic, QIconSet::Normal, QIconSet::On ); + iconSet.setPixmap( pixmap, QIconSet::Automatic, QIconSet::Disabled, QIconSet::Off ); + setIconSet( iconSet ); + + setFocusPolicy( QWidget::NoFocus ); + setEnabled( action->isEnabled() ); + + connect( action, SIGNAL( enabled( bool ) ), SLOT( setEnabled( bool ) ) ); + connect( this, SIGNAL( clicked() ), action, SLOT( activate() ) ); + startTimer( GLOW_INTERVAL ); +} + + +void NavButton::timerEvent( QTimerEvent* ) +{ + if ( isOn() ) { + m_glowIndex++; + m_glowIndex %= NUMPIXMAPS * 2 - 1; + + // Repaint widget with new pixmap + update(); + } +} + + +void NavButton::drawButtonLabel( QPainter* p ) +{ + int x = width() / 2 - m_pixmapOff.width() / 2; + int y = height() / 2 - m_pixmapOff.height() / 2; + + if ( !isEnabled() ) + p->drawPixmap( x, y, m_pixmapDisabled ); + else if ( isOn() ) + p->drawPixmap( x + 2, y + 1, m_glowPixmaps[m_glowIndex] ); + else + p->drawPixmap( x, y, m_pixmapOff ); +} + + +////////////////////////////////////////////////////////////////////////////////////////// +// CLASS IconButton +////////////////////////////////////////////////////////////////////////////////////////// + +IconButton::IconButton( QWidget *parent, const QString &icon, const char *signal ) + : QButton( parent ) + , m_up( getPNG( icon + "_active2" ) ) //TODO rename files better (like the right way round for one!) + , m_down( getPNG( icon + "_inactive2" ) ) +{ + connect( this, SIGNAL(toggled( bool )), parent, signal ); + + setToggleButton( true ); + setFocusPolicy( NoFocus ); //we have no way to show focus on these widgets currently +} + +IconButton::IconButton( QWidget *parent, const QString &icon, QObject* receiver, const char *slot ) + : QButton( parent ) + , m_up( getPNG( icon + "_active2" ) ) //TODO rename files better (like the right way round for one!) + , m_down( getPNG( icon + "_inactive2" ) ) +{ + connect( this, SIGNAL(toggled( bool )), receiver, slot ); + + setToggleButton( true ); + setFocusPolicy( NoFocus ); //we have no way to show focus on these widgets currently +} + +void IconButton::drawButton( QPainter *p ) +{ + p->drawPixmap( 0, 0, (isOn()||isDown()) ? m_down : m_up ); +} + +#include "playerwindow.moc" diff --git a/amarok/src/playerwindow.h b/amarok/src/playerwindow.h new file mode 100644 index 00000000..659d6c9e --- /dev/null +++ b/amarok/src/playerwindow.h @@ -0,0 +1,174 @@ +/*************************************************************************** + playerwidget.h - description + ------------------- + begin : Mit Nov 20 2002 + copyright : (C) 2002 by Mark Kretschmann + email : markey@web.de +***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef PLAYERWIDGET_H +#define PLAYERWIDGET_H + +#include //stack allocated +#include //baseclass +#include //stack allocated +#include //baseclass + +#include "engineobserver.h" //baseclass + +namespace Amarok { class PrettySlider; } +class KAction; +class MetaBundle; +class PlayerWidget; +class QBitmap; +class QButton; +class QHBox; +class QLabel; +class QString; +class QStringList; +class QTimerEvent; + + +class NavButton : public QToolButton //no QOBJECT macro - why bother? +{ +public: + NavButton( QWidget*, const QString&, KAction* ); + +protected: + void timerEvent( QTimerEvent* ); + void drawButtonLabel( QPainter* ); + + static const int GLOW_INTERVAL = 35; + static const int NUMPIXMAPS = 16; + + QPixmap m_pixmapOff; + QPixmap m_pixmapDisabled; + + QValueVector m_glowPixmaps; + int m_glowIndex; +}; + + +class IconButton : public QButton +{ + Q_OBJECT + +public: + IconButton( QWidget*, const QString&, const char *signal ); + IconButton( QWidget*, const QString&, QObject* receiver, const char *slot ); + +public slots: + void setOn( bool b ) { QButton::setOn( b ); } + void setOff() { QButton::setOn( false ); } + +private: + void drawButton( QPainter* ); + + const QPixmap m_up; + const QPixmap m_down; +}; + + +class PlayerWidget : public QWidget, public EngineObserver +{ + Q_OBJECT + + public: + PlayerWidget( QWidget* = 0, const char* = 0, bool enablePlaylist = false ); + ~PlayerWidget(); + + /** Set modified Amarok palette */ + void setModifiedPalette(); + /** Call after some Amarok setting have changed */ + void applySettings(); + + bool isMinimalView() { return m_minimalView; } + void setMinimalView( bool enable ); + + virtual void startDrag(); + + /** Determines Amarok colours for current KDE scheme */ + static void determineAmarokColors(); + + public slots: + void createAnalyzer( int = 0 ); + void toggleView() { setMinimalView( !m_minimalView ); } + + + protected: + /** Observer reimpls **/ + void engineStateChanged( Engine::State state, Engine::State oldstate = Engine::Empty ); + void engineVolumeChanged( int percent ); + void engineNewMetaData( const MetaBundle &/*bundle*/, bool /*trackChanged*/ ); + void engineTrackPositionChanged( long /*position*/, bool /*userSeek*/ ); + void engineTrackLengthChanged( long length ); + + signals: + void playlistToggled( bool on ); + + private slots: + void drawScroll(); + void timeDisplay( int ); + void slotShowEqualizer( bool show ); + + private: + void setScroll( const QStringList& ); + + virtual bool event( QEvent* ); + virtual bool eventFilter( QObject*, QEvent* ); + //virtual bool x11Event( XEvent* ); + virtual void paintEvent( QPaintEvent* ); + virtual void contextMenuEvent( QMouseEvent* ); + virtual void mousePressEvent( QMouseEvent* ); + virtual void mouseMoveEvent( QMouseEvent* ); + + ///to make the code clearer to n00bies ;) + QWidget *playlistWindow() { return parentWidget(); } + + static const int SCROLL_RATE = 1; + static const int ANIM_TIMER = 30; + + // ATTRIBUTES ------ + bool m_minimalView; + + QTimer *m_pAnimTimer; + + QPixmap m_scrollTextPixmap; + QPixmap m_scrollBuffer; + QPixmap m_timeBuffer; + QPixmap m_plusPixmap; + QPixmap m_minusPixmap; + + QPoint m_startDragPos; //for drag behaviour + + //widgets + QString m_rateString; + QWidget *m_pAnalyzer; + IconButton *m_pButtonEq; + IconButton *m_pPlaylistButton; + QLabel *m_pTimeLabel; + QLabel *m_pTimeSign; + + QFrame *m_pScrollFrame; + QLabel *m_pVolSign; + QLabel *m_pDescription; + QHBox *m_pFrameButtons; + + Amarok::PrettySlider *m_pSlider; + Amarok::PrettySlider *m_pVolSlider; + QToolButton *m_pButtonPlay; + QToolButton *m_pButtonPause; + + QString m_currentURL; +}; + +#endif diff --git a/amarok/src/playlist.cpp b/amarok/src/playlist.cpp new file mode 100644 index 00000000..2e88c374 --- /dev/null +++ b/amarok/src/playlist.cpp @@ -0,0 +1,4981 @@ +/* Copyright 2002-2004 Mark Kretschmann, Max Howell, Christian Muehlhaeuser + * Copyright 2005-2006 Seb Ruiz, Mike Diehl, Ian Monroe, Gábor Lehel, Alexandre Pereira de Oliveira + * Licensed as described in the COPYING file found in the root of this distribution + * Maintainer: Max Howell + + * NOTES + * + * The PlaylistWindow handles some Playlist events. Thanks! + * This class has a QOBJECT but it's private so you can only connect via PlaylistWindow::PlaylistWindow + * Mostly it's sensible to implement playlist functionality in this class + * TODO Obtaining information about the playlist is currently hard, we need the playlist to be globally + * available and have some more useful public functions + */ + +#define DEBUG_PREFIX "Playlist" + +#include +#include "amarok.h" +#include "amarokconfig.h" +#include "app.h" +#include "debug.h" +#include "collectiondb.h" +#include "collectionbrowser.h" +#include "columnlist.h" +#include "deletedialog.h" +#include "enginecontroller.h" +#include "expression.h" +#include "k3bexporter.h" +#include "metabundle.h" +#include "mountpointmanager.h" +#include "osd.h" +#include "playerwindow.h" +#include "playlistitem.h" +#include "playlistbrowser.h" +#include "playlistbrowseritem.h" //for stream editor dialog +#include "playlistloader.h" +#include "playlistselection.h" +#include "queuemanager.h" +#include "prettypopupmenu.h" +#include "scriptmanager.h" +#include "sliderwidget.h" +#include "starmanager.h" +#include "statusbar.h" //for status messages +#include "tagdialog.h" +#include "threadmanager.h" +#include "xspfplaylist.h" + +#include //for pow() in playNextTrack() + +#include +#include //copyToClipboard(), slotMouseButtonPressed() +#include +#include +#include //undo system +#include //eventFilter() +#include //showUsageMessage() +#include +#include //slotGlowTimer() +#include //toolTipText() +#include +#include +#include +#include //addHybridTracks() +#include //playNextTrack() +#include + +#include +#include +#include //setOverrideCursor() +#include +#include //rename() +#include +#include //slotShowContextMenu() +#include //deleteSelectedFiles() +#include //setCurrentTrack() +#include +#include +#include +#include //random Mode +#include //KGlobal::dirs() +#include +#include //::showContextMenu() +#include + +#include // abs + +extern "C" +{ + #if KDE_VERSION < KDE_MAKE_VERSION(3,3,91) + #include //ControlMask in contentsDragMoveEvent() + #endif +} + +#include "playlist.h" + +namespace Amarok +{ + const DynamicMode *dynamicMode() { return Playlist::instance() ? Playlist::instance()->dynamicMode() : 0; } +} + +typedef PlaylistIterator MyIt; + + +////////////////////////////////////////////////////////////////////////////////////////// +/// CLASS TagWriter : Threaded tag-updating +////////////////////////////////////////////////////////////////////////////////////////// + +class TagWriter : public ThreadManager::Job +{ //TODO make this do all tags at once when you split playlist.cpp up +public: + TagWriter( PlaylistItem*, const QString &oldTag, const QString &newTag, const int, const bool updateView = true ); + ~TagWriter(); + bool doJob(); + void completeJob(); +private: + PlaylistItem* const m_item; + bool m_failed; + + QString m_oldTagString; + QString m_newTagString; + int m_tagType; + bool m_updateView; +}; + +////////////////////////////////////////////////////////////////////////////////////////// +/// Glow +////////////////////////////////////////////////////////////////////////////////////////// + +namespace Glow +{ + namespace Text + { + static float dr, dg, db; + static int r, g, b; + } + namespace Base + { + static float dr, dg, db; + static int r, g, b; + } + + static const uint STEPS = 13; + static uint counter; + static QTimer timer; + + inline void startTimer() + { + counter = 0; + timer.start( 40 ); + } + + inline void reset() + { + counter = 0; + timer.stop(); + } +} + +////////////////////////////////////////////////////////////////////////////////////////// +/// CLASS Playlist +////////////////////////////////////////////////////////////////////////////////////////// + +QMutex* Playlist::s_dynamicADTMutex = new QMutex(); +Playlist *Playlist::s_instance = 0; + +Playlist::Playlist( QWidget *parent ) + : KListView( parent, "ThePlaylist" ) + , EngineObserver( EngineController::instance() ) + , m_startupTime_t( QDateTime::currentDateTime().toTime_t() ) + , m_oldestTime_t( CollectionDB::instance()->query( "SELECT MIN( createdate ) FROM statistics;" ).first().toInt() ) + , m_currentTrack( 0 ) + , m_marker( 0 ) + , m_hoveredRating( 0 ) + , m_firstColumn( 0 ) + , m_totalCount( 0 ) + , m_totalLength( 0 ) + , m_selCount( 0 ) + , m_selLength( 0 ) + , m_visCount( 0 ) + , m_visLength( 0 ) + , m_total( 0 ) + , m_itemCountDirty( false ) + , m_undoButton( 0 ) + , m_redoButton( 0 ) + , m_clearButton( 0 ) + , m_undoDir( Amarok::saveLocation( "undo/" ) ) + , m_undoCounter( 0 ) + , m_dynamicMode( 0 ) + , m_stopAfterTrack( 0 ) + , m_stopAfterMode( DoNotStop ) + , m_showHelp( true ) + , m_dynamicDirt( false ) + , m_queueDirt( false ) + , m_undoDirt( false ) + , m_insertFromADT( 0 ) + , m_itemToReallyCenter( 0 ) + , m_renameItem( 0 ) + , m_lockStack( 0 ) + , m_columnFraction( PlaylistItem::NUM_COLUMNS, 0 ) + , m_oldRandom( 0 ) + , m_oldRepeat( 0 ) + , m_playlistName( i18n( "Untitled" ) ) + , m_proposeOverwriting( false ) + , m_urlIndex( &PlaylistItem::url ) + +{ + s_instance = this; + + connect( CollectionDB::instance(), SIGNAL(fileMoved(const QString&, + const QString&, const QString&)), SLOT(updateEntriesUrl(const QString&, + const QString&, const QString&)) ); + connect( CollectionDB::instance(), SIGNAL(uniqueIdChanged(const QString&, + const QString&, const QString&)), SLOT(updateEntriesUniqueId(const QString&, + const QString&, const QString&)) ); + connect( CollectionDB::instance(), SIGNAL(fileDeleted(const QString&, + const QString&)), SLOT(updateEntriesStatusDeleted(const QString&, const QString&)) ); + connect( CollectionDB::instance(), SIGNAL(fileAdded(const QString&, + const QString&)), SLOT(updateEntriesStatusAdded(const QString&, const QString&)) ); + connect( CollectionDB::instance(), SIGNAL(filesAdded(const QMap&)), + SLOT(updateEntriesStatusAdded(const QMap&)) ); + + + initStarPixmaps(); + + EngineController* const ec = EngineController::instance(); + connect( ec, SIGNAL(orderPrevious()), SLOT(playPrevTrack()) ); + connect( ec, SIGNAL(orderNext( const bool )), SLOT(playNextTrack( const bool )) ); + connect( ec, SIGNAL(orderCurrent()), SLOT(playCurrentTrack()) ); + + connect( this, SIGNAL( itemCountChanged( int, int, int, int, int, int ) ), ec, SLOT( playlistChanged() ) ); + + + setShowSortIndicator( true ); + setDropVisualizer( false ); //we handle the drawing for ourselves + setDropVisualizerWidth( 3 ); + + // FIXME: This doesn't work, and steals focus when an item is clicked twice. + //setItemsRenameable( true ); + + setAcceptDrops( true ); + setSelectionMode( QListView::Extended ); + setAllColumnsShowFocus( true ); + //setItemMargin( 1 ); //aesthetics + + setMouseTracking( true ); + + #if KDE_IS_VERSION( 3, 3, 91 ) + setShadeSortColumn( true ); + #endif + + for( int i = 0; i < MetaBundle::NUM_COLUMNS; ++i ) + { + addColumn( PlaylistItem::prettyColumnName( i ), 0 ); + switch( i ) + { + case PlaylistItem::Title: + case PlaylistItem::Artist: + case PlaylistItem::Composer: + case PlaylistItem::Year: + case PlaylistItem::Album: + case PlaylistItem::DiscNumber: + case PlaylistItem::Track: + case PlaylistItem::Bpm: + case PlaylistItem::Genre: + case PlaylistItem::Comment: + case PlaylistItem::Score: + case PlaylistItem::Rating: + setRenameable( i, true ); + continue; + default: + setRenameable( i, false ); + } + } + + setColumnWidth( PlaylistItem::Title, 200 ); + setColumnWidth( PlaylistItem::Artist, 100 ); + setColumnWidth( PlaylistItem::Album, 100 ); + setColumnWidth( PlaylistItem::Length, 80 ); + if( AmarokConfig::showMoodbar() ) + setColumnWidth( PlaylistItem::Mood, 120 ); + if( AmarokConfig::useRatings() ) + setColumnWidth( PlaylistItem::Rating, PlaylistItem::ratingColumnWidth() ); + + setColumnAlignment( PlaylistItem::Length, Qt::AlignRight ); + setColumnAlignment( PlaylistItem::Track, Qt::AlignCenter ); + setColumnAlignment( PlaylistItem::DiscNumber, Qt::AlignCenter ); + setColumnAlignment( PlaylistItem::Bpm, Qt::AlignRight ); + setColumnAlignment( PlaylistItem::Year, Qt::AlignCenter ); + setColumnAlignment( PlaylistItem::Bitrate, Qt::AlignCenter ); + setColumnAlignment( PlaylistItem::SampleRate, Qt::AlignCenter ); + setColumnAlignment( PlaylistItem::Filesize, Qt::AlignCenter ); + setColumnAlignment( PlaylistItem::Score, Qt::AlignCenter ); + setColumnAlignment( PlaylistItem::Type, Qt::AlignCenter ); + setColumnAlignment( PlaylistItem::PlayCount, Qt::AlignCenter ); + + + connect( this, SIGNAL( doubleClicked( QListViewItem* ) ), + this, SLOT( doubleClicked( QListViewItem* ) ) ); + connect( this, SIGNAL( returnPressed( QListViewItem* ) ), + this, SLOT( activate( QListViewItem* ) ) ); + connect( this, SIGNAL( mouseButtonPressed( int, QListViewItem*, const QPoint&, int ) ), + this, SLOT( slotMouseButtonPressed( int, QListViewItem*, const QPoint&, int ) ) ); + connect( this, SIGNAL( queueChanged( const PLItemList &, const PLItemList & ) ), + this, SLOT( slotQueueChanged( const PLItemList &, const PLItemList & ) ) ); + connect( this, SIGNAL( itemRenamed( QListViewItem*, const QString&, int ) ), + this, SLOT( writeTag( QListViewItem*, const QString&, int ) ) ); + connect( this, SIGNAL( aboutToClear() ), + this, SLOT( saveUndoState() ) ); + connect( CollectionDB::instance(), SIGNAL( scoreChanged( const QString&, float ) ), + this, SLOT( scoreChanged( const QString&, float ) ) ); + connect( CollectionDB::instance(), SIGNAL( ratingChanged( const QString&, int ) ), + this, SLOT( ratingChanged( const QString&, int ) ) ); + connect( CollectionDB::instance(), SIGNAL( fileMoved( const QString&, const QString& ) ), + this, SLOT( fileMoved( const QString&, const QString& ) ) ); + connect( header(), SIGNAL( indexChange( int, int, int ) ), + this, SLOT( columnOrderChanged() ) ), + + + connect( &Glow::timer, SIGNAL(timeout()), SLOT(slotGlowTimer()) ); + + + KActionCollection* const ac = Amarok::actionCollection(); + KAction *copy = KStdAction::copy( this, SLOT( copyToClipboard() ), ac, "playlist_copy" ); + KStdAction::selectAll( this, SLOT( selectAll() ), ac, "playlist_select_all" ); + + m_clearButton = new KAction( i18n( "clear playlist", "&Clear" ), Amarok::icon( "playlist_clear" ), 0, this, SLOT( clear() ), ac, "playlist_clear" ); + m_undoButton = KStdAction::undo( this, SLOT( undo() ), ac, "playlist_undo" ); + m_redoButton = KStdAction::redo( this, SLOT( redo() ), ac, "playlist_redo" ); + m_undoButton ->setIcon( Amarok::icon( "undo" ) ); + m_redoButton ->setIcon( Amarok::icon( "redo" ) ); + + new KAction( i18n( "&Repopulate" ), Amarok::icon( "playlist_refresh" ), 0, this, SLOT( repopulate() ), ac, "repopulate" ); + new KAction( i18n( "S&huffle" ), "rebuild", CTRL+Key_H, this, SLOT( shuffle() ), ac, "playlist_shuffle" ); + KAction *gotoCurrent = new KAction( i18n( "&Go To Current Track" ), Amarok::icon( "music" ), CTRL+Key_J, this, SLOT( showCurrentTrack() ), ac, "playlist_show" ); + new KAction( i18n( "&Remove Duplicate && Dead Entries" ), 0, this, SLOT( removeDuplicates() ), ac, "playlist_remove_duplicates" ); + new KAction( i18n( "&Queue Selected Tracks" ), Amarok::icon( "queue_track" ), CTRL+Key_D, this, SLOT( queueSelected() ), ac, "queue_selected" ); + KToggleAction *stopafter = new KToggleAction( i18n( "&Stop Playing After Track" ), Amarok::icon( "stop" ), CTRL+ALT+Key_V, + this, SLOT( toggleStopAfterCurrentItem() ), ac, "stop_after" ); + + { // KAction idiocy -- shortcuts don't work until they've been plugged into a menu + KPopupMenu asdf; + + copy->plug( &asdf ); + stopafter->plug( &asdf ); + gotoCurrent->plug( &asdf ); + + copy->unplug( &asdf ); + stopafter->unplug( &asdf ); + gotoCurrent->unplug( &asdf ); + } + + //ensure we update action enabled states when repeat Playlist is toggled + connect( ac->action( "repeat" ), SIGNAL(activated( int )), SLOT(updateNextPrev()) ); + connect( ac->action( "repeat" ), SIGNAL( activated( int ) ), SLOT( generateInfo() ) ); + connect( ac->action( "favor_tracks" ), SIGNAL( activated( int ) ), SLOT( generateInfo() ) ); + connect( ac->action( "random_mode" ), SIGNAL( activated( int ) ), SLOT( generateInfo() ) ); + + + // undostates are written in chronological order, so this is a clever way to get them back in the correct order :) + QStringList undos = m_undoDir.entryList( QString("*.xml"), QDir::Files, QDir::Time ); + + foreach( undos ) + m_undoList.append( m_undoDir.absPath() + '/' + (*it) ); + + m_undoCounter = m_undoList.count(); + + m_undoButton->setEnabled( !m_undoList.isEmpty() ); + m_redoButton->setEnabled( false ); + + engineStateChanged( EngineController::engine()->state() ); //initialise state of UI + paletteChange( palette() ); //sets up glowColors + restoreLayout( KGlobal::config(), "PlaylistColumnsLayout" ); + + // Sorting must be disabled when current.xml is being loaded. See BUG 113042 + KListView::setSorting( NO_SORT ); //use base so we don't saveUndoState() too + + setDynamicMode( 0 ); + + m_smartResizing = Amarok::config( "PlaylistWindow" )->readBoolEntry( "Smart Resizing", true ); + + columnOrderChanged(); + //cause the column fractions to be updated, but in a safe way, ie no specific column + columnResizeEvent( header()->count(), 0, 0 ); + + //do after you resize all the columns + connect( header(), SIGNAL(sizeChange( int, int, int )), SLOT(columnResizeEvent( int, int, int )) ); + + connect( this, SIGNAL( contentsMoving( int, int ) ), SLOT( slotContentsMoving() ) ); + + connect( App::instance(), SIGNAL( useScores( bool ) ), this, SLOT( slotUseScores( bool ) ) ); + connect( App::instance(), SIGNAL( useRatings( bool ) ), this, SLOT( slotUseRatings( bool ) ) ); + connect( App::instance(), SIGNAL( moodbarPrefs( bool, bool, int, bool ) ), + this, SLOT( slotMoodbarPrefs( bool, bool, int, bool ) ) ); + + Amarok::ToolTip::add( this, viewport() ); + + header()->installEventFilter( this ); + renameLineEdit()->installEventFilter( this ); + setTabOrderedRenaming( false ); + + m_filtertimer = new QTimer( this ); + connect( m_filtertimer, SIGNAL(timeout()), this, SLOT(setDelayedFilter()) ); + + connect( MountPointManager::instance(), SIGNAL(mediumConnected( int )), + SLOT(mediumChange( int )) ); + connect( MountPointManager::instance(), SIGNAL(mediumRemoved( int )), + SLOT(mediumChange( int )) ); + + m_clicktimer = new QTimer( this ); + connect( m_clicktimer, SIGNAL(timeout()), this, SLOT(slotSingleClick()) ); +} + +Playlist::~Playlist() +{ + saveLayout( KGlobal::config(), "PlaylistColumnsLayout" ); + + if( AmarokConfig::savePlaylist() && m_lockStack == 0 ) saveXML( defaultPlaylistPath() ); + + //speed up quit a little + safeClear(); //our implementation is slow + Amarok::ToolTip::remove( viewport() ); + blockSignals( true ); //might help + s_instance = 0; +} + + +//////////////////////////////////////////////////////////////////////////////// +/// Media Handling +//////////////////////////////////////////////////////////////////////////////// + +void +Playlist::mediumChange( int deviceid ) // SLOT +{ + Q_UNUSED( deviceid ); + + for( QListViewItem *it = firstChild(); + it; + it = it->nextSibling() ) + { + PlaylistItem *p = dynamic_cast( it ); + if( p ) + { + bool exist = p->exists(); + if( exist != p->checkExists() ) + { + p->setFilestatusEnabled( p->checkExists() ); + p->update(); + } + } + } +} + +void +Playlist::insertMedia( const KURL::List &list, int options ) +{ + if( list.isEmpty() ) { + Amarok::StatusBar::instance()->shortMessage( i18n("Attempted to insert nothing into playlist.") ); + return; // don't add empty items + } + + const bool isPlaying = EngineController::engine()->state() == Engine::Playing; + if( isPlaying ) + options &= ~Playlist::StartPlay; + bool directPlay = options & (Playlist::DirectPlay | Playlist::StartPlay); + + if( options & Replace ) + clear(); + else + options |= Playlist::Colorize; + + PlaylistItem *after = lastItem(); + + KURL::List addMe; + QPtrList alreadyHave; + + // Filter out duplicates + foreachType( KURL::List, list ) { + PlaylistItem *item = m_urlIndex.getFirst( *it ); + if ( item ) + alreadyHave.append( item ); + else + addMe.append( *it ); + } + + if( options & Queue ) + { + if ( addMe.isEmpty() ) // all songs to be queued are already in the playlist + { + // queue all the songs + foreachType( QPtrList, alreadyHave ) + queue( *it, false, false ); + return; + } else { + // We add the track after the last track on queue, or after current if the queue is empty + after = m_nextTracks.isEmpty() ? currentTrack() : m_nextTracks.getLast(); + // If there's no tracks on the queue, and there's no current track, fall back to the last item + if ( !after ) + after = lastItem(); + } + } + else if( options & Unique ) { + int alreadyOnPlaylist = alreadyHave.count(); + if ( alreadyOnPlaylist ) + { + if (directPlay) activate( alreadyHave.getFirst() ); + Amarok::StatusBar::instance()->shortMessage( + i18n("One track was already in the playlist, so it was not added.", + "%n tracks were already in the playlist, so they were not added.", + alreadyOnPlaylist ) ); + } + } + + if( options & Unique || options & Queue ) + insertMediaInternal( addMe, after, options ); + else + insertMediaInternal( list, after, options ); +} + +void +Playlist::insertMediaInternal( const KURL::List &list, PlaylistItem *after, int options ) +{ + if ( !list.isEmpty() ) { + setSorting( NO_SORT ); + + // prevent association with something that is about to be deleted + // TODO improve the playlist with a list of items that are volatile or something + while( after && after->url().isEmpty() ) + after = static_cast( after->itemAbove() ); + + ThreadManager::instance()->queueJob( new UrlLoader( list, after, options ) ); + ScriptManager::instance()->notifyPlaylistChange("changed"); + } +} + +void +Playlist::insertMediaSql( const QString& sql, int options ) +{ + const bool isPlaying = EngineController::engine()->state() == Engine::Playing; + if( isPlaying ) + options &= ~Playlist::StartPlay; + + // TODO Implement more options + PlaylistItem *after = 0; + + if ( options & Replace ) + clear(); + if ( options & Append ) + after = lastItem(); + + setSorting( NO_SORT ); + ThreadManager::instance()->queueJob( new SqlLoader( sql, after, options ) ); + ScriptManager::instance()->notifyPlaylistChange("changed"); +} + +void +Playlist::addDynamicModeTracks( uint songCount ) +{ + if( songCount < 1 ) return; + + int currentPos = 0; + for( MyIt it( this, MyIt::Visible ); *it; ++it ) + { + if( m_currentTrack && *it == m_currentTrack ) + break; + else if( !m_currentTrack && (*it)->isDynamicEnabled() ) + break; + + ++currentPos; + } + currentPos++; + + int required = currentPos + dynamicMode()->upcomingCount(); // currentPos handles currentTrack + int remainder = totalTrackCount(); + + if( required > remainder ) + songCount = required - remainder; + + DynamicMode *m = modifyDynamicMode(); + KURL::List tracksToInsert = m->retrieveTracks( songCount ); + Playlist::instance()->finishedModifying( m ); + + insertMedia( tracksToInsert, Playlist::Unique ); +} + + +/** + * @param songCount : Number of tracks to be shown after the current track + */ + +void +Playlist::adjustDynamicUpcoming( bool saveUndo ) +{ + /** + * If m_currentTrack exists, we iterate until we find it + * Else, we iterate until we find an item which is enabled + **/ + MyIt it( this, MyIt::Visible ); //Notice we'll use this up to the end of the function! + //Skip previously played + for( ; *it; ++it ) + { + if( m_currentTrack && *it == m_currentTrack ) + break; + else if( !m_currentTrack && (*it)->isDynamicEnabled() ) + break; + } + //Skip current + if( m_currentTrack ) + ++it; + + int x = 0; + for ( ; *it && x < dynamicMode()->upcomingCount() ; ++it, ++x ); + + if ( x < dynamicMode()->upcomingCount() ) + { + addDynamicModeTracks( dynamicMode()->upcomingCount() - x ); + ScriptManager::instance()->notifyPlaylistChange("changed"); + } + + if( saveUndo ) + saveUndoState(); +} + +/** + * @param songCount : Number of tracks to be shown before the current track + */ + +void +Playlist::adjustDynamicPrevious( uint songCount, bool saveUndo ) +{ + int current = currentTrackIndex(); + int x = current - songCount; + + QPtrList list; + int y=0; + for( QListViewItemIterator it( firstChild() ); y < x ; list.prepend( *it ), ++it, y++ ); + + if( list.isEmpty() ) return; + if ( saveUndo ) + saveUndoState(); + + //remove the items + for( QListViewItem *item = list.first(); item; item = list.next() ) + { + removeItem( static_cast( item ) ); + delete item; + } + ScriptManager::instance()->notifyPlaylistChange("changed"); +} + +void +Playlist::setDynamicHistory( bool enable /*false*/ ) +{ + if( !m_currentTrack ) + return; + + for( PlaylistIterator it( this, PlaylistIterator::All ) ; *it ; ++it ) + { + if( *it == m_currentTrack ) break; + + //avoid repainting if we can. + if( (*it)->isDynamicEnabled() == enable ) + { + (*it)->setDynamicEnabled( !enable ); + (*it)->update(); + } + } +} + +QString +Playlist::defaultPlaylistPath() //static +{ + return Amarok::saveLocation() + "current.xml"; +} + +void +Playlist::restoreSession() +{ + KURL url; + + if ( Amarok::config()->readBoolEntry( "First 1.4 Run", true ) ) { + // On first startup of 1.4, we load a special playlist with an intro track + url.setPath( locate( "data", "amarok/data/firstrun.m3u" ) ); + Amarok::config()->writeEntry( "First 1.4 Run", false ); + } + else + url.setPath( Amarok::saveLocation() + "current.xml" ); + + // check it exists, because on the first ever run it doesn't and + // it looks bad to show "some URLs were not suitable.." on the + // first ever-run + if( QFile::exists( url.path() ) ) + { + ThreadManager::instance()->queueJob( new UrlLoader( url, 0, 0 ) ); + } +} + +/* + The following two functions (saveLayout(), restoreLayout()), taken from klistview.cpp, are largely + Copyright (C) 2000 Reginald Stadlbauer + Copyright (C) 2000,2003 Charles Samuels + Copyright (C) 2000 Peter Putzer +*/ +void Playlist::saveLayout(KConfig *config, const QString &group) const +{ + KConfigGroupSaver saver(config, group); + QStringList names, widths, order; + + const int colCount = columns(); + QHeader* const thisHeader = header(); + for (int i = 0; i < colCount; ++i) + { + names << PlaylistItem::exactColumnName(i); + widths << QString::number(columnWidth(i)); + order << QString::number(thisHeader->mapToIndex(i)); + } + config->writeEntry("ColumnsVersion", 1); + config->writeEntry("ColumnNames", names); + config->writeEntry("ColumnWidths", widths); + config->writeEntry("ColumnOrder", order); + config->writeEntry("SortColumn", columnSorted()); + config->writeEntry("SortAscending", ascendingSort()); +} + +void Playlist::restoreLayout(KConfig *config, const QString &group) +{ + KConfigGroupSaver saver(config, group); + int version = config->readNumEntry("ColumnsVersion", 0); + + QValueList iorder; //internal ordering + if( version ) + { + QStringList names = config->readListEntry("ColumnNames"); + for( int i = 0, n = names.count(); i < n; ++i ) + { + bool found = false; + for( int ii = i; ii < PlaylistItem::NUM_COLUMNS; ++ii ) //most likely, it's where we left it + { + if( names[i] == PlaylistItem::exactColumnName(ii) ) + { + iorder.append(ii); + found = true; + break; + } + } + if( !found ) + { + for( int ii = 0; ii < i; ++ii ) //but maybe it's not + if( names[i] == PlaylistItem::exactColumnName(ii) ) + { + iorder.append(ii); + found = true; + break; + } + } + if( !found ) + return; //oops? -- revert to the default. + } + } + else + { + int oldorder[] = { 0, 1, 2, 5, 4, 9, 8, 7, 10, 12, 13, 15, 16, 11, 17, 18, 19, 3, 6, 20 }; + for( int i = 0; i != 20; ++i ) + iorder.append(oldorder[i]); + } + + + QStringList cols = config->readListEntry("ColumnWidths"); + int i = 0; + { // scope the iterators + QStringList::ConstIterator it = cols.constBegin(); + const QStringList::ConstIterator itEnd = cols.constEnd(); + for (; it != itEnd; ++it) + setColumnWidth(iorder[i++], (*it).toInt()); + } + + + // move sections in the correct sequence: from lowest to highest index position + // otherwise we move a section from an index, which modifies + // all index numbers to the right of the moved one + cols = config->readListEntry("ColumnOrder"); + const int colCount = columns(); + for (i = 0; i < colCount; ++i) // final index positions from lowest to highest + { + QStringList::ConstIterator it = cols.constBegin(); + const QStringList::ConstIterator itEnd = cols.constEnd(); + + int section = 0; + for (; (it != itEnd) && (iorder[(*it).toInt()] != i); ++it, ++section) ; + + if ( it != itEnd ) { + // found the section to move to position i + header()->moveSection(iorder[section], i); + } + } + + if ( config->hasKey("SortColumn") ) + { + const int sort = config->readNumEntry("SortColumn"); + if( sort >= 0 && uint(sort) < iorder.count() ) + setSorting(iorder[config->readNumEntry("SortColumn")], config->readBoolEntry("SortAscending", true)); + } + + if( !AmarokConfig::useScores() ) + hideColumn( PlaylistItem::Score ); + if( !AmarokConfig::useRatings() ) + hideColumn( PlaylistItem::Rating ); + if( !AmarokConfig::showMoodbar() ) + hideColumn( PlaylistItem::Mood ); +} + +void +Playlist::addToUniqueMap( const QString uniqueid, PlaylistItem* item ) +{ + QPtrList *list; + if( m_uniqueMap.contains( uniqueid ) ) + list = m_uniqueMap[uniqueid]; + else + list = new QPtrList(); + list->append( item ); + if( !m_uniqueMap.contains( uniqueid ) ) + m_uniqueMap[uniqueid] = list; +} + +void +Playlist::removeFromUniqueMap( const QString uniqueid, PlaylistItem* item ) +{ + if( !m_uniqueMap.contains( uniqueid ) ) + return; + + QPtrList *list; + list = m_uniqueMap[uniqueid]; + + list->remove( item ); //don't care about return value + + if( list->isEmpty() ) + { + delete list; + m_uniqueMap.remove( uniqueid ); + } +} + +void +Playlist::updateEntriesUrl( const QString &oldUrl, const QString &newUrl, const QString &uniqueid ) +{ + // Make sure the MoodServer gets this signal first! + MoodServer::instance()->slotFileMoved( oldUrl, newUrl ); + + QPtrList *list; + if( m_uniqueMap.contains( uniqueid ) ) + { + list = m_uniqueMap[uniqueid]; + PlaylistItem *item; + for( item = list->first(); item; item = list->next() ) + { + item->setUrl( KURL( newUrl ) ); + item->setFilestatusEnabled( item->checkExists() ); + } + } +} + +void +Playlist::updateEntriesUniqueId( const QString &/*url*/, const QString &oldid, const QString &newid ) +{ + QPtrList *list, *oldlist; + if( m_uniqueMap.contains( oldid ) ) + { + list = m_uniqueMap[oldid]; + m_uniqueMap.remove( oldid ); + PlaylistItem *item; + for( item = list->first(); item; item = list->next() ) + { + item->setUniqueId( newid ); + item->readTags(); + } + if( !m_uniqueMap.contains( newid ) ) + m_uniqueMap[newid] = list; + else + { + oldlist = m_uniqueMap[newid]; + for( item = list->first(); item; item = list->next() ) + oldlist->append( item ); + delete list; + } + } +} + +void +Playlist::updateEntriesStatusDeleted( const QString &/*absPath*/, const QString &uniqueid ) +{ + QPtrList *list; + if( m_uniqueMap.contains( uniqueid ) ) + { + list = m_uniqueMap[uniqueid]; + PlaylistItem *item; + for( item = list->first(); item; item = list->next() ) + item->setFilestatusEnabled( false ); + } +} + +void +Playlist::updateEntriesStatusAdded( const QString &absPath, const QString &uniqueid ) +{ + QPtrList *list; + if( m_uniqueMap.contains( uniqueid ) ) + { + list = m_uniqueMap[uniqueid]; + if( !list ) + return; + PlaylistItem *item; + for( item = list->first(); item; item = list->next() ) + { + if( absPath != item->url().path() ) + item->setPath( absPath ); //in case the UID was the same, but the path has changed + item->setFilestatusEnabled( true ); + } + } +} + +void +Playlist::updateEntriesStatusAdded( const QMap &map ) +{ + QMap*> uniquecopy( m_uniqueMap ); + + QMap*>::Iterator it; + for( it = uniquecopy.begin(); it != uniquecopy.end(); ++it ) + { + if( map.contains( it.key() )) + { + updateEntriesStatusAdded( map[it.key()], it.key() ); + uniquecopy.remove( it ); + } + } + + for( it = uniquecopy.begin(); it != uniquecopy.end(); ++it ) + updateEntriesStatusDeleted( QString::null, it.key() ); +} + +//////////////////////////////////////////////////////////////////////////////// +/// Current Track Handling +//////////////////////////////////////////////////////////////////////////////// + +void +Playlist::playNextTrack( bool forceNext ) +{ + PlaylistItem *item = currentTrack(); + + if( !m_visCount || stopAfterMode() == StopAfterCurrent ) + { + if( dynamicMode() && m_visCount ) + { + item->setDynamicEnabled( false ); + advanceDynamicTrack(); + m_dynamicDirt = false; + } + + EngineController::instance()->stop(); + setStopAfterMode( DoNotStop ); + + if( !AmarokConfig::randomMode() ) { + item = MyIt::nextVisible( item ); + while( item && ( !checkFileStatus( item ) || !item->exists() ) ) + item = MyIt::nextVisible( item ); + setCurrentTrack( item ); + } + + return; + } + + if( !Amarok::repeatTrack() || forceNext ) + { + if( !m_nextTracks.isEmpty() ) + { + item = m_nextTracks.first(); + m_nextTracks.remove(); + if ( dynamicMode() ) + // move queued track to the top of the playlist, to prevent it from being played twice + // this is done automatically by most queue changing functions, but not if the user manually moves the track + moveItem( item, 0, m_currentTrack ); + emit queueChanged( PLItemList(), PLItemList( item ) ); + } + + else if( Amarok::entireAlbums() && m_currentTrack && m_currentTrack->nextInAlbum() ) + item = m_currentTrack->nextInAlbum(); + + else if( Amarok::repeatAlbum() && + repeatAlbumTrackCount() && ( repeatAlbumTrackCount() > 1 || !forceNext ) ) + item = m_currentTrack->m_album->tracks.getFirst(); + + else if( AmarokConfig::randomMode() ) + { + QValueVector tracks; + + //make a list of everything we can play + if( Amarok::randomAlbums() ) // add the first visible track from every unplayed album + { + for( ArtistAlbumMap::const_iterator it = m_albums.constBegin(), end = m_albums.constEnd(); it != end; ++it ) + for( AlbumMap::const_iterator it2 = (*it).constBegin(), end2 = (*it).constEnd(); it2 != end2; ++it2 ) + if( m_prevAlbums.findRef( *it2 ) == -1 ) { + if ( (*it2)->tracks.getFirst() ) + tracks.append( (*it2)->tracks.getFirst() ); + } + } + else + for( MyIt it( this ); *it; ++it ) + if ( !m_prevTracks.containsRef( *it ) && checkFileStatus( *it ) && (*it)->exists() ) + tracks.push_back( *it ); + if( tracks.isEmpty() ) + { + //we have played everything + + item = 0; + + if( Amarok::randomAlbums() ) + { + if ( m_prevAlbums.count() <= 8 ) { + m_prevAlbums.first(); + while( m_prevAlbums.count() ) + removeFromPreviousAlbums(); + + if( m_currentTrack ) + { + // don't add it to previous albums if we only have one album in the playlist + // would loop infinitely otherwise + QPtrList albums; + for( PlaylistIterator it( this, PlaylistIterator::Visible ); *it && albums.count() <= 1; ++it ) + if( albums.findRef( (*it)->m_album ) == -1 ) + albums.append( (*it)->m_album ); + + if ( albums.count() > 1 ) + appendToPreviousAlbums( m_currentTrack->m_album ); + } + } + else { + m_prevAlbums.first(); //set's current item to first item + + //keep 80 tracks in the previous list so item time user pushes play + //we don't risk playing anything too recent + while( m_prevAlbums.count() > 8 ) + removeFromPreviousAlbums(); //removes current item + } + } + + else + { + if ( m_prevTracks.count() <= 80 ) { + m_prevTracks.first(); + while( m_prevTracks.count() ) + removeFromPreviousTracks(); + + if( m_currentTrack ) + { + // don't add it to previous tracks if we only have one file in the playlist + // would loop infinitely otherwise + int count = 0; + for( PlaylistIterator it( this, PlaylistIterator::Visible ); *it && count <= 1; ++it ) + ++count; + + if ( count > 1 ) + appendToPreviousTracks( m_currentTrack ); + } + } + else { + m_prevTracks.first(); //set's current item to first item + + //keep 80 tracks in the previous list so item time user pushes play + //we don't risk playing anything too recent + while( m_prevTracks.count() > 80 ) + removeFromPreviousTracks(); //removes current item + } + } + + if( Amarok::repeatPlaylist() ) + { + playNextTrack(); + return; + } + //else we stop via activate( 0 ) below + } + else + { + if( Amarok::favorNone() ) + item = tracks.at( KApplication::random() % tracks.count() ); //is O(1) + else + { + const uint currenttime_t = QDateTime::currentDateTime().toTime_t(); + QValueVector weights( tracks.size() ); + Q_INT64 total = m_total; + if( Amarok::randomAlbums() ) + { + for( int i = 0, n = tracks.count(); i < n; ++i ) + { + weights[i] = tracks.at( i )->m_album->total; + if( Amarok::favorLastPlay() ) + { + const int inc = int( float( ( currenttime_t - m_startupTime_t ) + * tracks.at( i )->m_album->tracks.count() + 0.5 ) + / tracks.at( i )->m_album->tracks.count() ); + weights[i] += inc; + total += inc; + } + } + } + else + { + for( int i = 0, n = tracks.count(); i < n; ++i ) + { + weights[i] = tracks.at( i )->totalIncrementAmount(); + if( Amarok::favorLastPlay() ) + weights[i] += currenttime_t - m_startupTime_t; + } + if( Amarok::favorLastPlay() ) + total += ( currenttime_t - m_startupTime_t ) * weights.count(); + } + + Q_INT64 random; + if( Amarok::favorLastPlay() ) //really big huge numbers + { + Q_INT64 r = Q_INT64( ( KApplication::random() / pow( 2, sizeof( int ) * 8 ) ) + * pow( 2, 64 ) ); + random = r % total; + } + else + random = KApplication::random() % total; + int i = 0; + for( int n = tracks.count(); i < n && random >= 0; ++i ) + random -= weights.at( i ); + item = tracks.at( i-1 ); + } + } + } + else if( item ) + { + item = MyIt::nextVisible( item ); + while( item && ( !checkFileStatus( item ) || !item->exists() ) ) + item = MyIt::nextVisible( item ); + } + else + { + item = *MyIt( this ); //ie. first visible item + while( item && ( !checkFileStatus( item ) || !item->exists() ) ) + item = item->nextSibling(); + } + + + if ( dynamicMode() && item != firstChild() ) + { + if( currentTrack() ) + currentTrack()->setDynamicEnabled( false ); + advanceDynamicTrack(); + } + + if ( !item && Amarok::repeatPlaylist() ) + item = *MyIt( this ); //ie. first visible item + } + + + if ( EngineController::engine()->loaded() ) + activate( item ); + else + setCurrentTrack( item ); +} + +//This is called before setCurrentItem( item ); +void +Playlist::advanceDynamicTrack() +{ + int x = currentTrackIndex(); + bool didDelete = false; + if( dynamicMode()->cycleTracks() ) + { + if( x >= dynamicMode()->previousCount() ) + { + PlaylistItem *first = firstChild(); + removeItem( first ); + delete first; + didDelete = true; + } + } + + const int upcomingTracks = childCount() - x - 1; + + // Just starting to play from stopped, don't append something needlessely + // or, we have more than enough items in the queue. + bool dontAppend = ( !didDelete && + ( EngineController::instance()->engine()->state() == Engine::Empty ) ) || + upcomingTracks > dynamicMode()->upcomingCount(); + + //keep upcomingTracks requirement, this seems to break StopAfterCurrent + if( !dontAppend && stopAfterMode() != StopAfterCurrent ) + { + s_dynamicADTMutex->lock(); + m_insertFromADT++; + s_dynamicADTMutex->unlock(); + addDynamicModeTracks( 1 ); + } + m_dynamicDirt = true; +} + +void +Playlist::playPrevTrack() +{ + PlaylistItem *item = currentTrack(); + + if( Amarok::entireAlbums() ) + { + item = 0; + if( m_currentTrack ) + { + item = m_currentTrack->prevInAlbum(); + if( !item && Amarok::repeatAlbum() && m_currentTrack->m_album->tracks.count() ) + item = m_currentTrack->m_album->tracks.getLast(); + } + if( !item ) + { + PlaylistAlbum* a = m_prevAlbums.last(); + while( a && !a->tracks.count() ) + { + removeFromPreviousAlbums(); + a = m_prevAlbums.last(); + } + if( a ) + { + item = a->tracks.getLast(); + removeFromPreviousAlbums(); + } + } + if( !item ) + { + item = *static_cast(--MyIt( item )); + while( item && !checkFileStatus( item ) ) + item = *static_cast(--MyIt( item )); + } + } + else + { + if( dynamicMode() ) + { + } + else if( !AmarokConfig::randomMode() || m_prevTracks.count() <= 1 ) + { + if( item ) + { + item = MyIt::prevVisible( item ); + while( item && ( !checkFileStatus( item ) || !item->isEnabled() ) ) + item = MyIt::prevVisible( item ); + } + else + { + item = *MyIt( this ); //ie. first visible item + while( item && ( !checkFileStatus( item ) || !item->isEnabled() ) ) + item = item->nextSibling(); + } + } + else { + // if enough songs in buffer, jump to the previous one + m_prevTracks.last(); + removeFromPreviousTracks(); //remove the track playing now + item = m_prevTracks.last(); + + // we need to remove this item now, since it will be added in activate() again + removeFromPreviousTracks(); + } + } + + if ( !item && Amarok::repeatPlaylist() ) + item = *MyIt( lastItem() ); //TODO check this works! + + if ( EngineController::engine()->loaded() ) + activate( item ); + else + setCurrentTrack( item ); +} + +void +Playlist::playCurrentTrack() +{ + if ( !currentTrack() ) + playNextTrack( Amarok::repeatTrack() ); + + //we must do this even if the above is correct + //since the engine is not loaded the first time the user presses play + //then calling the next() function wont play it + activate( currentTrack() ); +} + +void +Playlist::setSelectedRatings( int rating ) +{ + if( !m_selCount && currentItem() && currentItem()->isVisible() ) + CollectionDB::instance()->setSongRating( currentItem()->url().path(), rating, true ); + + else + for( MyIt it( this, MyIt::Selected ); *it; ++it ) + CollectionDB::instance()->setSongRating( (*it)->url().path(), rating, true ); +} + +void +Playlist::queueSelected() +{ + PLItemList in, out; + QPtrList dynamicList; + + for( MyIt it( this, MyIt::Selected ); *it; ++it ) + { + // Dequeuing selection with dynamic doesn't work due to the moving of the track after the last queued + if( dynamicMode() ) + { + ( !m_nextTracks.containsRef( *it ) ? in : out ).append( *it ); + dynamicList.append( *it ); + } + else + { + queue( *it, true ); + ( m_nextTracks.containsRef( *it ) ? in : out ).append( *it ); + } + + } + + if( dynamicMode() ) + { + QListViewItem *item = dynamicList.first(); + if( m_nextTracks.containsRef( static_cast(item) ) ) + { + for( item = dynamicList.last(); item; item = dynamicList.prev() ) + queue( item, true ); + } + else + { + for( ; item; item = dynamicList.next() ) + queue( item, true ); + } + } + + emit queueChanged( in, out ); +} + +void +Playlist::queue( QListViewItem *item, bool multi, bool invertQueue ) +{ + #define item static_cast(item) + + const int queueIndex = m_nextTracks.findRef( item ); + const bool isQueued = queueIndex != -1; + + if( isQueued ) + { + if( invertQueue ) + { + //remove the item, this is better way than remove( item ) + m_nextTracks.remove( queueIndex ); //sets current() to next item + + if( dynamicMode() ) // we move the item after the last queued item to preserve the ordered 'queue'. + { + PlaylistItem *after = m_nextTracks.last(); + + if( after ) + moveItem( item, 0, after ); + } + } + } + else if( !dynamicMode() ) + m_nextTracks.append( item ); + + else // Dynamic mode + { + PlaylistItem *after; + m_nextTracks.isEmpty() ? + after = m_currentTrack : + after = m_nextTracks.last(); + + if( !after ) + { + after = firstChild(); + while( after && !after->isDynamicEnabled() ) + { + if( after->nextSibling()->isDynamicEnabled() ) + break; + after = after->nextSibling(); + } + } + + if( item->isDynamicEnabled() && item != m_currentTrack ) + { + this->moveItem( item, 0, after ); + m_nextTracks.append( item ); + } + else + { + /// we do the actual queuing through customEvent, since insertMedia is threaded + m_queueDirt = true; + insertMediaInternal( item->url(), after ); + } + } + + if( !multi ) + { + if( isQueued ) //no longer + { + if( invertQueue ) + emit queueChanged( PLItemList(), PLItemList( item ) ); + } + else + emit queueChanged( PLItemList( item ), PLItemList() ); + } + + #undef item +} + +void +Playlist::sortQueuedItems() // used by dynamic mode +{ + PlaylistItem *last = m_currentTrack; + for( PlaylistItem *item = m_nextTracks.first(); item; item = m_nextTracks.next() ) + { + if( item->itemAbove() != last ) + item->moveItem( last ); + last = item; + } +} + +void Playlist::setStopAfterCurrent( bool on ) +{ + PlaylistItem *prev_stopafter = m_stopAfterTrack; + + if( on ) { + setStopAfterItem( m_currentTrack ); + } + else { + setStopAfterMode( DoNotStop ); + } + + if( m_stopAfterTrack ) + m_stopAfterTrack->update(); + if( prev_stopafter ) + prev_stopafter->update(); +} + +void Playlist::setStopAfterItem( PlaylistItem *item ) +{ + if( !item ) { + setStopAfterMode( DoNotStop ); + return; + } + else if( item == m_currentTrack ) + setStopAfterMode( StopAfterCurrent ); + else if( item == m_nextTracks.getLast() ) + setStopAfterMode( StopAfterQueue ); + else + setStopAfterMode( StopAfterQueue ); + m_stopAfterTrack = item; +} + +void Playlist::toggleStopAfterCurrentItem() +{ + PlaylistItem *item = currentItem(); + if( !item && m_selCount == 1 ) + item = *MyIt( this, MyIt::Visible | MyIt::Selected ); + if( !item ) + return; + + PlaylistItem *prev_stopafter = m_stopAfterTrack; + if( m_stopAfterTrack == item ) { + m_stopAfterTrack = 0; + setStopAfterMode( DoNotStop ); + } + else + { + setStopAfterItem( item ); + item->setSelected( false ); + item->update(); + } + + if( prev_stopafter ) + prev_stopafter->update(); +} + +void Playlist::toggleStopAfterCurrentTrack() +{ + PlaylistItem *item = currentTrack(); + if( !item ) + return; + + PlaylistItem *prev_stopafter = m_stopAfterTrack; + if( m_stopAfterTrack == item ) { + setStopAfterMode( DoNotStop ); + Amarok::OSD::instance()->OSDWidget::show( i18n("Stop Playing After Track: Off") ); + } + else + { + setStopAfterItem( item ); + item->setSelected( false ); + item->update(); + Amarok::OSD::instance()->OSDWidget::show( i18n("Stop Playing After Track: On") ); + } + + if( prev_stopafter ) + prev_stopafter->update(); +} + +void Playlist::setStopAfterMode( int mode ) +{ + PlaylistItem *prevStopAfter = m_stopAfterTrack; + m_stopAfterMode = mode; + switch( mode ) + { + case DoNotStop: + m_stopAfterTrack = 0; + break; + case StopAfterCurrent: + m_stopAfterTrack = m_currentTrack; + break; + case StopAfterQueue: + m_stopAfterTrack = m_nextTracks.count() ? m_nextTracks.getLast() : m_currentTrack; + break; + } + + if( prevStopAfter ) + prevStopAfter->update(); + if( m_stopAfterTrack ) + m_stopAfterTrack->update(); +} + +int Playlist::stopAfterMode() +{ + if ( m_stopAfterMode != DoNotStop + && m_stopAfterTrack && m_stopAfterTrack == m_currentTrack ) { + m_stopAfterMode = StopAfterCurrent; + } + + return m_stopAfterMode; +} + +void Playlist::generateInfo() +{ + m_albums.clear(); + if( Amarok::entireAlbums() ) + for( MyIt it( this, MyIt::All ); *it; ++it ) + (*it)->refAlbum(); + m_total = 0; + if( Amarok::entireAlbums() || AmarokConfig::favorTracks() ) + for( MyIt it( this, MyIt::Visible ); *it; ++it ) + (*it)->incrementTotals(); +} + +void Playlist::doubleClicked( QListViewItem *item ) +{ + /* We have to check if the item exists before calling activate, otherwise clicking on an empty + playlist space would stop playing (check BR #105106)*/ + if( item && m_hoveredRating != item ) + activate( item ); +} + +void +Playlist::slotCountChanged() +{ + if( m_itemCountDirty ) + emit itemCountChanged( totalTrackCount(), m_totalLength, + m_visCount, m_visLength, + m_selCount, m_selLength ); + + m_itemCountDirty = false; +} + +bool +Playlist::checkFileStatus( PlaylistItem * item ) +{ + //DEBUG_BLOCK + //debug() << "uniqueid of item = " << item->uniqueId() << ", url = " << item->url().path() << endl; + if( !item->checkExists() ) + { + //debug() << "not found, finding new url" << endl; + QString path = QString::null; + if( !item->uniqueId().isEmpty() ) + { + path = CollectionDB::instance()->urlFromUniqueId( item->uniqueId() ); + //debug() << "found path = " << path << endl; + } + else + { + //debug() << "Setting uniqueid of item and trying again" << endl; + item->setUniqueId(); + if( !item->uniqueId().isEmpty() ) + path = CollectionDB::instance()->urlFromUniqueId( item->uniqueId() ); + } + if( !path.isEmpty() ) + { + item->setUrl( KURL( path ) ); + if( item->checkExists() ) + item->setFilestatusEnabled( true ); + else + item->setFilestatusEnabled( false ); + } + else + item->setFilestatusEnabled( false ); + } + else if( !item->isFilestatusEnabled() ) + item->setFilestatusEnabled( true ); + + bool returnValue = item->isFilestatusEnabled(); + + return returnValue; +} + +void +Playlist::activate( QListViewItem *item ) +{ + ///item will be played if possible, the playback may be delayed + ///so we start the glow anyway and hope + + //All internal requests for playback should come via + //this function please! + + if( !item ) + { + //we have reached the end of the playlist + EngineController::instance()->stop(); + setCurrentTrack( 0 ); + Amarok::OSD::instance()->OSDWidget::show( i18n("Playlist finished"), + QImage( KIconLoader().iconPath( "amarok", -KIcon::SizeHuge ) ) ); + return; + } + + #define item static_cast(item) + + if ( !checkFileStatus( item ) ) + { + Amarok::StatusBar::instance()->shortMessage( i18n("Local file does not exist.") ); + return; + } + + if( dynamicMode() && !Amarok::repeatTrack() ) + { + if( m_currentTrack && item->isDynamicEnabled() ) + { + if( item != m_currentTrack ) + this->moveItem( item, 0, m_currentTrack ); + } + else + { + MyIt it( this, MyIt::Visible ); + bool hasHistory = false; + if ( *it && !(*it)->isDynamicEnabled() ) + { + hasHistory = true; + for( ; *it && !(*it)->isDynamicEnabled() ; ++it ); + } + + if( item->isDynamicEnabled() ) + { + hasHistory ? + this->moveItem( item, 0, *it ) : + this->moveItem( item, 0, 0 ); + } + else // !item->isDynamicEnabled() + { + hasHistory ? + insertMediaInternal( item->url(), *it ): + insertMediaInternal( item->url(), 0 ); + m_dynamicDirt = true; + return; + } + + } + if( !m_dynamicDirt && m_currentTrack && m_currentTrack != item ) + { + m_currentTrack->setDynamicEnabled( false ); + advanceDynamicTrack(); + } + } + + if( Amarok::entireAlbums() ) + { + if( !item->nextInAlbum() ) + appendToPreviousAlbums( item->m_album ); + } + else + appendToPreviousTracks( item ); + + //if we are playing something from the next tracks + //list, remove it from the list + if( m_nextTracks.removeRef( item ) ) + emit queueChanged( PLItemList(), PLItemList( item ) ); + + //looks bad painting selected and glowing + //only do when user explicitly activates an item though + item->setSelected( false ); + + setCurrentTrack( item ); + + m_dynamicDirt = false; + + //use PlaylistItem::MetaBundle as it also updates the audioProps + EngineController::instance()->play( *item ); + #undef item +} + +QPair Playlist::toolTipText( QWidget*, const QPoint &pos ) const +{ + PlaylistItem *item = static_cast( itemAt( pos ) ); + if( !item ) + return QPair( QString::null, QRect() ); + + const QPoint contentsPos = viewportToContents( pos ); + const int col = header()->sectionAt( contentsPos.x() ); + + if( item == m_renameItem && col == m_renameColumn ) + return QPair( QString::null, QRect() ); + + QString text; + if( col == PlaylistItem::Rating ) + text = item->ratingDescription( item->rating() ); + else + text = item->text( col ); + + QRect irect = itemRect( item ); + const int headerPos = header()->sectionPos( col ); + irect.setLeft( headerPos - 1 ); + irect.setRight( headerPos + header()->sectionSize( col ) ); + + static QFont f; + static int minbearing = 1337 + 666; //can be 0 or negative, 2003 is less likely + if( minbearing == 2003 || f != font() ) + { + f = font(); //getting your bearings can be expensive, so we cache them + minbearing = fontMetrics().minLeftBearing() + fontMetrics().minRightBearing(); + } + + int itemWidth = irect.width() - itemMargin() * 2 + minbearing - 2; + if( item->pixmap( col ) ) + itemWidth -= item->pixmap( col )->width(); + if( item == m_currentTrack ) + { + if( col == m_firstColumn ) + itemWidth -= 12; + if( col == mapToLogicalColumn( numVisibleColumns() - 1 ) ) + itemWidth -= 12; + } + + if( col != PlaylistItem::Rating && fontMetrics().width( text ) <= itemWidth ) + return QPair( QString::null, QRect() ); + + QRect globalRect( viewport()->mapToGlobal( irect.topLeft() ), irect.size() ); + QSimpleRichText t( text, font() ); + int dright = QApplication::desktop()->screenGeometry( qscrollview() ).topRight().x(); + t.setWidth( dright - globalRect.left() ); + if( col == PlaylistItem::Rating ) + globalRect.setRight( kMin( dright, kMax( globalRect.left() + t.widthUsed(), globalRect.left() + ( StarManager::instance()->getGreyStar()->width() + 1 ) * ( ( item->rating() + 1 ) / 2 ) ) ) ); + else + globalRect.setRight( kMin( globalRect.left() + t.widthUsed(), dright ) ); + globalRect.setBottom( globalRect.top() + kMax( irect.height(), t.height() ) - 1 ); + + if( ( col == PlaylistItem::Rating && PlaylistItem::ratingAtPoint( contentsPos.x() ) <= item->rating() + 1 ) || + ( col != PlaylistItem::Rating ) ) + { + text = text.replace( "&", "&" ).replace( "<", "<" ).replace( ">", ">" ); + if( item->isCurrent() ) + { + text = QString("%1").arg( text ); + Amarok::ToolTip::s_hack = 1; //HACK for precise positioning + } + return QPair( text, globalRect ); + } + + return QPair( QString::null, QRect() ); +} + +void +Playlist::activateByIndex( int index ) +{ + QListViewItem* item = itemAtIndex( index ); + + if ( item ) + activate(item); +} + +void +Playlist::setCurrentTrack( PlaylistItem *item ) +{ + ///mark item as the current track and make it glow + + PlaylistItem *prev = m_currentTrack; + + //FIXME best method would be to observe usage, especially don't shift if mouse is moving nearby + if( item && ( !prev || prev == currentItem() ) && !renameLineEdit()->isVisible() && m_selCount < 2 ) + { + if( !prev ) + //if nothing is current and then playback starts, we must show the currentTrack + ensureItemCentered( item ); //handles 0 gracefully + + else { + const int prevY = itemPos( prev ); + const int prevH = prev->height(); + + // check if the previous track is visible + if( prevY <= contentsY() + visibleHeight() && prevY + prevH >= contentsY() ) + { + // in random mode always jump, if previous track is visible + if( AmarokConfig::randomMode() ) + ensureItemCentered( item ); + else if( prev && prev == currentItem() ) + setCurrentItem( item ); + + //FIXME would be better to just never be annoying + // so if the user caused the track change, always show the new track + // but if it is automatic be careful + + // if old item in view then try to keep the new one near the middle + const int y = itemPos( item ); + const int h = item->height(); + const int vh = visibleHeight(); + const int amount = h * 3; + + int d = y - contentsY(); + + if( d > 0 ) { + d += h; + d -= vh; + + if( d > 0 && d <= amount ) + // scroll down + setContentsPos( contentsX(), y - vh + amount ); + } + else if( d >= -amount ) + // scroll up + setContentsPos( contentsX(), y - amount ); + } + } + } + + m_currentTrack = item; + if ( m_currentTrack ) + m_currentTrack->setIsNew(false); + + if ( prev ) { + //reset to normal height + prev->invalidateHeight(); + prev->setup(); + //remove pixmap in first column + prev->setPixmap( m_firstColumn, QPixmap() ); + } + + updateNextPrev(); + + setCurrentTrackPixmap(); + + Glow::reset(); + slotGlowTimer(); +} + +int +Playlist::currentTrackIndex( bool onlyCountVisible ) +{ + int index = 0; + for( MyIt it( this, onlyCountVisible ? MyIt::Visible : MyIt::All ); *it; ++it ) + { + if ( *it == m_currentTrack ) + return index; + ++index; + } + + return -1; +} + +int +Playlist::totalTrackCount() const +{ + return m_totalCount; +} + +BundleList +Playlist::nextTracks() const +{ + BundleList list; + for( QPtrListIterator it( m_nextTracks ); *it; ++it ) + list << (**it); + return list; +} + +uint +Playlist::repeatAlbumTrackCount() const +{ + if ( m_currentTrack && m_currentTrack->m_album ) + return m_currentTrack->m_album->tracks.count(); + else + return 0; +} + +const DynamicMode* +Playlist::dynamicMode() const +{ + return m_dynamicMode; +} + +DynamicMode* +Playlist::modifyDynamicMode() +{ + DynamicMode *m = m_dynamicMode; + if( !m ) + return 0; + m_dynamicMode = new DynamicMode( *m ); + return m; +} + +void +Playlist::finishedModifying( DynamicMode *mode ) +{ + DynamicMode *m = m_dynamicMode; + setDynamicMode( mode ); + delete m; +} + +void +Playlist::setCurrentTrackPixmap( int state ) +{ + if( !m_currentTrack ) + return; + + QString pixmap = QString::null; + + if( state < 0 ) + state = EngineController::engine()->state(); + + if( state == Engine::Paused ) + pixmap = "currenttrack_pause"; + else if( state == Engine::Playing ) + pixmap = "currenttrack_play"; + + m_currentTrack->setPixmap( m_firstColumn, pixmap.isNull() ? QPixmap() : Amarok::getPNG( pixmap ) ); + PlaylistItem::setPixmapChanged(); +} + +PlaylistItem* +Playlist::restoreCurrentTrack() +{ + ///It is always possible that the current track has been lost + ///eg it was removed and then reinserted, here we check + + const KURL url = EngineController::instance()->playingURL(); + + if ( !(m_currentTrack && ( m_currentTrack->url() == url || !m_currentTrack->url().isEmpty() && url.isEmpty() ) ) ) + { + PlaylistItem* item; + + for( item = firstChild(); + item && item->url() != url; + item = item->nextSibling() ) + {} + + setCurrentTrack( item ); //set even if NULL + } + + if( m_currentTrack && EngineController::instance()->engine()->state() == Engine::Playing && !Glow::timer.isActive() ) + Glow::startTimer(); + + return m_currentTrack; +} + +void +Playlist::countChanged() +{ + if( !m_itemCountDirty ) + { + m_itemCountDirty = true; + QTimer::singleShot( 0, this, SLOT( slotCountChanged() ) ); + } +} + +bool +Playlist::isTrackAfter() const +{ + ///Is there a track after the current track? + //order is carefully crafted, remember count() is O(n) + //TODO randomMode will end if everything is in prevTracks + + return !currentTrack() && !isEmpty() || + !m_nextTracks.isEmpty() || + currentTrack() && currentTrack()->itemBelow() || + totalTrackCount() > 1 && ( AmarokConfig::randomMode() || Amarok::repeatPlaylist() + || Amarok::repeatAlbum() && repeatAlbumTrackCount() > 1 ); +} + +bool +Playlist::isTrackBefore() const +{ + //order is carefully crafted, remember count() is O(n) + + return !isEmpty() && + ( + currentTrack() && (currentTrack()->itemAbove() || Amarok::repeatPlaylist() && totalTrackCount() > 1) + || + AmarokConfig::randomMode() && totalTrackCount() > 1 + ); +} + +void +Playlist::updateNextPrev() +{ + Amarok::actionCollection()->action( "play" )->setEnabled( !isEmpty() ); + Amarok::actionCollection()->action( "prev" )->setEnabled( isTrackBefore() ); + Amarok::actionCollection()->action( "next" )->setEnabled( isTrackAfter() ); + Amarok::actionCollection()->action( "playlist_clear" )->setEnabled( !isEmpty() ); + Amarok::actionCollection()->action( "playlist_show" )->setEnabled( m_currentTrack ); + + if( m_currentTrack ) + // ensure currentTrack is shown at correct height + m_currentTrack->setup(); +} + + +//////////////////////////////////////////////////////////////////////////////// +/// EngineObserver Reimplementation +//////////////////////////////////////////////////////////////////////////////// + +void +Playlist::engineNewMetaData( const MetaBundle &bundle, bool trackChanged ) +{ + if ( !bundle.podcastBundle() ) + { + if ( m_currentTrack && !trackChanged ) { + //if the track hasn't changed then this is a meta-data update + if( stopAfterMode() == StopAfterCurrent || !m_nextTracks.isEmpty() ) + Playlist::instance()->playNextTrack( true ); + //this is a hack, I repeat a hack! FIXME FIXME + //we do it because often the stream title is from the pls file and is informative + //we don't want to lose it when we get the meta data + else if ( m_currentTrack->artist().isEmpty() ) { + QString comment = m_currentTrack->title(); + m_currentTrack->copyFrom( bundle ); + m_currentTrack->setComment( comment ); + } + else + m_currentTrack->copyFrom( bundle ); + } + else + //ensure the currentTrack is set correctly and highlight it + restoreCurrentTrack(); + } + else + //ensure the currentTrack is set correctly and highlight it + restoreCurrentTrack(); + + if( m_currentTrack ) + m_currentTrack->filter( m_filter ); +} + +void +Playlist::engineStateChanged( Engine::State state, Engine::State /*oldState*/ ) +{ + switch( state ) + { + case Engine::Playing: + Amarok::actionCollection()->action( "pause" )->setEnabled( true ); + Amarok::actionCollection()->action( "stop" )->setEnabled( true ); + + Glow::startTimer(); + + break; + + case Engine::Paused: + Amarok::actionCollection()->action( "pause" )->setEnabled( false ); + Amarok::actionCollection()->action( "stop" )->setEnabled( true ); + + Glow::reset(); + + if( m_currentTrack ) + slotGlowTimer(); //update glow state + + break; + + case Engine::Empty: + Amarok::actionCollection()->action( "pause" )->setEnabled( false ); + Amarok::actionCollection()->action( "stop" )->setEnabled( false ); + + //leave the glow state at full colour + Glow::reset(); + + if ( m_currentTrack ) + { + //remove pixmap in all columns + QPixmap null; + for( int i = 0; i < header()->count(); i++ ) + m_currentTrack->setPixmap( i, null ); + + PlaylistItem::setPixmapChanged(); + + //reset glow state + slotGlowTimer(); + } + + case Engine::Idle: + slotGlowTimer(); + + break; + } + + //POSSIBLYAHACK + //apparently you can't rely on EngineController::engine()->state() == state here, so pass it explicitly + setCurrentTrackPixmap( state ); +} + + +//////////////////////////////////////////////////////////////////////////////// +/// KListView Reimplementation +//////////////////////////////////////////////////////////////////////////////// + +void +Playlist::appendMedia( const QString &path ) +{ + appendMedia( KURL::fromPathOrURL( path ) ); +} + +void +Playlist::appendMedia( const KURL &url ) +{ + insertMedia( KURL::List( url ) ); +} + +void +Playlist::clear() //SLOT +{ + if( isLocked() || renameLineEdit()->isVisible() ) return; + + disableDynamicMode(); + + emit aboutToClear(); //will saveUndoState() + + setCurrentTrack( 0 ); + m_prevTracks.clear(); + m_prevAlbums.clear(); + + if (m_stopAfterTrack) { + m_stopAfterTrack = 0; + if ( stopAfterMode() != StopAfterCurrent ) { + setStopAfterMode( DoNotStop ); + } + } + const PLItemList prev = m_nextTracks; + m_nextTracks.clear(); + emit queueChanged( PLItemList(), prev ); + + // Update player button states + Amarok::actionCollection()->action( "play" )->setEnabled( false ); + Amarok::actionCollection()->action( "prev" )->setEnabled( false ); + Amarok::actionCollection()->action( "next" )->setEnabled( false ); + Amarok::actionCollection()->action( "playlist_clear" )->setEnabled( false ); + + ThreadManager::instance()->abortAllJobsNamed( "TagWriter" ); + + // something to bear in mind, if there is any event in the loop + // that depends on a PlaylistItem, we are about to crash Amarok + // never unlock() the Playlist until it is safe! + safeClear(); + m_total = 0; + m_albums.clear(); + + setPlaylistName( i18n( "Untitled" ) ); + ScriptManager::instance()->notifyPlaylistChange("cleared"); +} + +/** + * Workaround for Qt 3.3.5 bug in QListView::clear() + * @see http://lists.kde.org/?l=kde-devel&m=113113845120155&w=2 + * @see BUG 116004 + */ +void +Playlist::safeClear() +{ + /* 3.3.5 and 3.3.6 have bad KListView::clear() functions. + 3.3.5 forgets to clear the pointer to the highlighted item. + 3.3.6 forgets to clear the pointer to the last dragged item */ + if ( strcmp( qVersion(), "3.3.5" ) == 0 + || strcmp( qVersion(), "3.3.6" ) == 0 ) + { + bool block = signalsBlocked(); + blockSignals( true ); + clearSelection(); + + QListViewItem *c = firstChild(); + QListViewItem *n; + while( c ) { + n = c->nextSibling(); + if ( !static_cast( c )->isEmpty() ) //avoid deleting markers + delete c; + c = n; + } + blockSignals( block ); + triggerUpdate(); + } + else + KListView::clear(); +} + +void +Playlist::setSorting( int col, bool b ) +{ + saveUndoState(); + + //HACK There are reasons to allow sorting in dynamic mode, but + //it breaks other things that I don't have the time or patience + //to figure out...at least right now + + if( !dynamicMode() ) + KListView::setSorting( col, b ); +} + +void +Playlist::setColumnWidth( int col, int width ) +{ + + KListView::setColumnWidth( col, width ); + + //FIXME this is because Qt doesn't by default disable resizing width 0 columns. GRRR! + //NOTE default column sizes are stored in default amarokrc so that restoreLayout() in ctor will + // call this function. This is necessary because addColumn() doesn't call setColumnWidth() GRRR! + header()->setResizeEnabled( width != 0, col ); +} + +void +Playlist::rename( QListViewItem *item, int column ) //SLOT +{ + if( !item ) + return; + + switch( column ) + { + case PlaylistItem::Artist: + renameLineEdit()->completionObject()->setItems( CollectionDB::instance()->artistList() ); + break; + + case PlaylistItem::Album: + renameLineEdit()->completionObject()->setItems( CollectionDB::instance()->albumList() ); + break; + + case PlaylistItem::Genre: + renameLineEdit()->completionObject()->setItems( CollectionDB::instance()->genreList() ); + break; + + case PlaylistItem::Composer: + renameLineEdit()->completionObject()->setItems( CollectionDB::instance()->composerList() ); + break; + + default: + renameLineEdit()->completionObject()->clear(); + break; + } + + renameLineEdit()->completionObject()->setCompletionMode( KGlobalSettings::CompletionPopupAuto ); + renameLineEdit()->completionObject()->setIgnoreCase( true ); + + m_editOldTag = static_cast(item)->exactText( column ); + + if( m_selCount <= 1 ) + { + if( currentItem() ) + currentItem()->setSelected( false ); + item->setSelected( true ); + } + setCurrentItem( item ); + KListView::rename( item, column ); + + m_renameItem = item; + m_renameColumn = column; + + static_cast(item)->setIsBeingRenamed( true ); + +} + +void +Playlist::writeTag( QListViewItem *qitem, const QString &, int column ) //SLOT +{ + const bool dynamicEnabled = static_cast(qitem)->isDynamicEnabled(); + + if( m_itemsToChangeTagsFor.isEmpty() ) + m_itemsToChangeTagsFor.append( static_cast( qitem ) ); + + const QString newTag = static_cast( qitem )->exactText( column ); + + for( PlaylistItem *item = m_itemsToChangeTagsFor.first(); item; item = m_itemsToChangeTagsFor.next() ) + { + if( !checkFileStatus( item ) ) + continue; + + const QString oldTag = item == qitem ? m_editOldTag : item->exactText(column); + + if( column == PlaylistItem::Score ) + CollectionDB::instance()->setSongPercentage( item->url().path(), newTag.toInt() ); + else if( column == PlaylistItem::Rating ) + CollectionDB::instance()->setSongRating( item->url().path(), newTag.toInt() ); + else + if (oldTag != newTag) + ThreadManager::instance()->queueJob( new TagWriter( item, oldTag, newTag, column ) ); + else if( item->deleteAfterEditing() ) + { + removeItem( item ); + delete item; + } + } + + if( dynamicMode() ) + static_cast(qitem)->setDynamicEnabled( dynamicEnabled ); + + m_itemsToChangeTagsFor.clear(); + m_editOldTag = QString::null; +} + +void +Playlist::columnOrderChanged() //SLOT +{ + const uint prevColumn = m_firstColumn; + + //determine first visible column + for ( m_firstColumn = 0; m_firstColumn < header()->count(); m_firstColumn++ ) + if ( header()->sectionSize( header()->mapToSection( m_firstColumn ) ) ) + break; + + //convert to logical column + m_firstColumn = header()->mapToSection( m_firstColumn ); + + //force redraw of currentTrack + if( m_currentTrack ) + { + m_currentTrack->setPixmap( prevColumn, QPixmap() ); + setCurrentTrackPixmap(); + } + QResizeEvent e( size(), QSize() ); + viewportResizeEvent( &e ); + emit columnsChanged(); +} + +void +Playlist::paletteChange( const QPalette &p ) +{ + using namespace Glow; + + QColor fg; + QColor bg; + + { + using namespace Base; + + const uint steps = STEPS+5+5; //so we don't fade all the way to base, and all the way up to highlight either + + fg = colorGroup().highlight(); + bg = colorGroup().base(); + + dr = double(bg.red() - fg.red()) / steps; + dg = double(bg.green() - fg.green()) / steps; + db = double(bg.blue() - fg.blue()) / steps; + + r = fg.red() + int(dr*5.0); //we add 5 steps so the default colour is slightly different to highlight + g = fg.green() + int(dg*5.0); + b = fg.blue() + int(db*5.0); + } + + { + using namespace Text; + + const uint steps = STEPS + 5; //so we don't fade all the way to base + + fg = colorGroup().highlightedText(); + bg = colorGroup().text(); + + dr = double(bg.red() - fg.red()) / steps; + dg = double(bg.green() - fg.green()) / steps; + db = double(bg.blue() - fg.blue()) / steps; + + r = fg.red(); + g = fg.green(); + b = fg.blue(); + } + + KListView::paletteChange( p ); + + counter = 0; // reset the counter or apparently the text lacks contrast + slotGlowTimer(); // repaint currentTrack marker +} + +void +Playlist::contentsDragEnterEvent( QDragEnterEvent *e ) +{ + QString data; + QCString subtype; + QTextDrag::decode( e, data, subtype ); + + e->accept( + e->source() == viewport() || + subtype == "amarok-sql" || + subtype == "uri-list" || //this is to prevent DelayedUrlLists from performing their queries + KURLDrag::canDecode( e ) ); +} + +void +Playlist::contentsDragMoveEvent( QDragMoveEvent* e ) +{ + if( !e->isAccepted() ) return; + + #if KDE_IS_VERSION( 3, 3, 91 ) + const bool ctrlPressed = KApplication::keyboardMouseState() & Qt::ControlButton; + #else + const bool ctrlPressed = KApplication::keyboardModifiers() & ControlMask; + #endif + + //Get the closest item _before_ the cursor + const QPoint p = contentsToViewport( e->pos() ); + QListViewItem *item = itemAt( p ); + if( !item || ctrlPressed ) item = lastItem(); + else if( p.y() - itemRect( item ).top() < (item->height()/2) ) item = item->itemAbove(); + + if( item != m_marker ) { + //NOTE this if block prevents flicker + slotEraseMarker(); + m_marker = item; //NOTE this is the correct place to set m_marker + viewportPaintEvent( 0 ); + } +} + +void +Playlist::contentsDragLeaveEvent( QDragLeaveEvent* ) +{ + slotEraseMarker(); +} + +void +Playlist::contentsDropEvent( QDropEvent *e ) +{ + DEBUG_BLOCK + + //NOTE parent is always 0 currently, but we support it in case we start using trees + QListViewItem *parent = 0; + QListViewItem *after = m_marker; + + //make sure to disable only if in dynamic mode and you're inserting + //at the beginning or in the middle of the disabled tracks + //Also, that the dynamic playlist has any tracks (suggested may not) + if( dynamicMode() && Playlist::instance()->firstChild() && + ( !m_marker || !( static_cast(m_marker)->isDynamicEnabled() ) ) && + currentTrackIndex() != -1 ) + { + // If marker is disabled, and there is a current track, or marker is not the last enabled track + // don't allow inserting + if( ( m_marker && ( m_currentTrack || ( m_marker->itemBelow() && + !( static_cast(m_marker->itemBelow())->isDynamicEnabled() ) ) ) ) + || ( !m_marker ) ) + { + slotEraseMarker(); + return; + } + } + + if( !after ) + findDrop( e->pos(), parent, after ); //shouldn't happen, but you never know! + + slotEraseMarker(); + + if ( e->source() == viewport() ) { + setSorting( NO_SORT ); //disableSorting and saveState() + movableDropEvent( parent, after ); + QPtrList items = selectedItems(); + if( dynamicMode() && after ) + { + QListViewItem *item; + bool enabled = static_cast(after)->isDynamicEnabled(); + for( item = items.first(); item; item = items.next() ) + static_cast(item)->setDynamicEnabled( enabled ); + } + ScriptManager::instance()->notifyPlaylistChange("reordered"); + } + + else { + QString data; + QCString subtype; + QTextDrag::decode( e, data, subtype ); + + debug() << "QTextDrag::subtype(): " << subtype << endl; + + if( subtype == "amarok-sql" ) { + setSorting( NO_SORT ); + QString query = data.section( "\n", 1 ); + ThreadManager::instance()->queueJob( new SqlLoader( query, after ) ); + ScriptManager::instance()->notifyPlaylistChange("changed"); + } + + else if( subtype == "dynamic" ) { + // Deserialize pointer + DynamicEntry* entry = reinterpret_cast( data.toULongLong() ); + + loadDynamicMode( entry ); + } + + else if( KURLDrag::canDecode( e ) ) + { + debug() << "KURLDrag::canDecode" << endl; + + KURL::List list; + KURLDrag::decode( e, list ); + insertMediaInternal( list, static_cast( after ) ); + } + else + e->ignore(); + } + + updateNextPrev(); +} + +QDragObject* +Playlist::dragObject() +{ + DEBUG_THREAD_FUNC_INFO + + KURL::List list; + + for( MyIt it( this, MyIt::Selected ); *it; ++it ) + { + const PlaylistItem *item = static_cast( *it ); + const KURL url = item->url(); + list += url; + } + + KURLDrag *drag = new KURLDrag( list, viewport() ); + drag->setPixmap( CollectionDB::createDragPixmap( list ), + QPoint( CollectionDB::DRAGPIXMAP_OFFSET_X, CollectionDB::DRAGPIXMAP_OFFSET_Y ) ); + return drag; +} + +#include +void +Playlist::viewportPaintEvent( QPaintEvent *e ) +{ + if( e ) KListView::viewportPaintEvent( e ); //we call with 0 in contentsDropEvent() + + if ( m_marker ) { + QPainter p( viewport() ); + p.fillRect( + drawDropVisualizer( 0, 0, m_marker ), + QBrush( colorGroup().highlight().dark(), QBrush::Dense4Pattern ) ); + } + else if( m_showHelp && isEmpty() ) { + QPainter p( viewport() ); + QString minimumText(i18n( + "
    " + "

    The Playlist

    " + "This is the playlist. " + "To create a listing, " + "drag tracks from the browser-panels on the left, " + "drop them here and then double-click them to start playback." + "
    " ) ); + QSimpleRichText *t = new QSimpleRichText( minimumText + + i18n( "
    " + "

    The Browsers

    " + "The browsers are the source of all your music. " + "The collection-browser holds your collection. " + "The playlist-browser holds your pre-set playlistings. " + "The file-browser shows a file-selector which you can use to access any music on your computer. " + "
    " ), QApplication::font() ); + + if ( t->width()+30 >= viewport()->width() || t->height()+30 >= viewport()->height() ) { + // too big for the window, so let's cut part of the text + delete t; + t = new QSimpleRichText( minimumText, QApplication::font()); + if ( t->width()+30 >= viewport()->width() || t->height()+30 >= viewport()->height() ) { + //still too big, giving up + delete t; + return; + } + } + + const uint w = t->width(); + const uint h = t->height(); + const uint x = (viewport()->width() - w - 30) / 2 ; + const uint y = (viewport()->height() - h - 30) / 2 ; + + p.setBrush( colorGroup().background() ); + p.drawRoundRect( x, y, w+30, h+30, (8*200)/w, (8*200)/h ); + t->draw( &p, x+15, y+15, QRect(), colorGroup() ); + delete t; + } +} + +static uint negativeWidth = 0; + +void +Playlist::viewportResizeEvent( QResizeEvent *e ) +{ + if ( !m_smartResizing ) { + KListView::viewportResizeEvent( e ); + return; + } + //only be clever with the sizing if there is not many items + //TODO don't allow an item to be made too small (ie less than 50% of ideal width) + + //makes this much quicker + header()->blockSignals( true ); + + const double W = (double)e->size().width() - negativeWidth; + + for( uint c = 0; c < m_columnFraction.size(); ++c ) { + switch( c ) { + case PlaylistItem::Track: + case PlaylistItem::Bitrate: + case PlaylistItem::SampleRate: + case PlaylistItem::Filesize: + case PlaylistItem::Score: + case PlaylistItem::Rating: + case PlaylistItem::Type: + case PlaylistItem::PlayCount: + case PlaylistItem::Length: + case PlaylistItem::Year: + case PlaylistItem::DiscNumber: + case PlaylistItem::Bpm: + break; //these columns retain their width - their items tend to have uniform size + default: + if( m_columnFraction[c] > 0 ) + setColumnWidth( c, int(W * m_columnFraction[c]) ); + } + } + + header()->blockSignals( false ); + + //ensure that the listview scrollbars are updated etc. + triggerUpdate(); +} + +void +Playlist::columnResizeEvent( int col, int oldw, int neww ) +{ + if ( !m_smartResizing ) + return; + //prevent recursion + header()->blockSignals( true ); + + //qlistview is stupid sometimes + if ( neww < 0 ) + setColumnWidth( col, 0 ); + + if ( neww == 0 ) { + //the column in question has been hidden + //we need to adjust the other columns to fit + + const double W = (double)width() - negativeWidth; + + for( uint c = 0; c < m_columnFraction.size(); ++c ) { + if( c == (uint)col ) + continue; + switch( c ) { + case PlaylistItem::Track: + case PlaylistItem::Bitrate: + case PlaylistItem::SampleRate: + case PlaylistItem::Filesize: + case PlaylistItem::Score: + case PlaylistItem::Rating: + case PlaylistItem::Type: + case PlaylistItem::PlayCount: + case PlaylistItem::Length: + case PlaylistItem::Year: + case PlaylistItem::DiscNumber: + case PlaylistItem::Bpm: + break; + default: + if( m_columnFraction[c] > 0 ) + setColumnWidth( c, int(W * m_columnFraction[c]) ); + } + } + } + + else if( oldw != 0 ) { + //adjust the size of the column on the right side of this one + + for( int section = col, index = header()->mapToIndex( section ); index < header()->count(); ) { + section = header()->mapToSection( ++index ); + + if ( header()->sectionSize( section ) ) { + int newSize = header()->sectionSize( section ) + oldw - neww; + if ( newSize > 5 ) { + setColumnWidth( section, newSize ); + //we only want to adjust one column! + break; + } + } + } + } + + header()->blockSignals( false ); + + negativeWidth = 0; + uint w = 0; + + //determine width excluding the columns that have static size + for( uint x = 0; x < m_columnFraction.size(); ++x ) { + switch( x ) { + case PlaylistItem::Track: + case PlaylistItem::Bitrate: + case PlaylistItem::SampleRate: + case PlaylistItem::Filesize: + case PlaylistItem::Score: + case PlaylistItem::Rating: + case PlaylistItem::Type: + case PlaylistItem::PlayCount: + case PlaylistItem::Length: + case PlaylistItem::Year: + case PlaylistItem::DiscNumber: + case PlaylistItem::Bpm: + break; + default: + w += columnWidth( x ); + } + + negativeWidth += columnWidth( x ); + } + + //determine the revised column fractions + for( uint x = 0; x < m_columnFraction.size(); ++x ) + m_columnFraction[x] = (double)columnWidth( x ) / double(w); + + //negative width is an important property, honest! + negativeWidth -= w; + + //we have to do this after we have established negativeWidth and set the columnFractions + if( neww == 0 || oldw == 0 ) { + //then this column has been inserted or removed, we need to update all the column widths + QResizeEvent e( size(), QSize() ); + viewportResizeEvent( &e ); + emit columnsChanged(); + } +} + +bool +Playlist::eventFilter( QObject *o, QEvent *e ) +{ + #define me static_cast(e) + #define ke static_cast(e) + + if( o == header() && e->type() == QEvent::MouseButtonPress && me->button() == Qt::RightButton ) + { + enum { HIDE = 1000, SELECT, CUSTOM, SMARTRESIZING }; + + const int mouseOverColumn = header()->sectionAt( me->pos().x() ); + + KPopupMenu popup; + if( mouseOverColumn >= 0 ) + popup.insertItem( i18n("&Hide %1").arg( columnText( mouseOverColumn ) ), HIDE ); //TODO + + KPopupMenu sub; + for( int i = 0; i < columns(); ++i ) //columns() references a property + if( !columnWidth( i ) ) + sub.insertItem( columnText( i ), i, i + 1 ); + sub.setItemVisible( PlaylistItem::Score, AmarokConfig::useScores() ); + sub.setItemVisible( PlaylistItem::Rating, AmarokConfig::useRatings() ); + sub.setItemVisible( PlaylistItem::Mood, AmarokConfig::showMoodbar() ); + + popup.insertItem( i18n("&Show Column" ), &sub ); + + popup.insertItem( i18n("Select &Columns..."), SELECT ); + + popup.insertItem( i18n("&Fit to Width"), SMARTRESIZING ); + popup.setItemChecked( SMARTRESIZING, m_smartResizing ); + + int col = popup.exec( static_cast(e)->globalPos() ); + + switch( col ) { + case HIDE: + { + hideColumn( mouseOverColumn ); + QResizeEvent e( size(), QSize() ); + viewportResizeEvent( &e ); + } + break; + + case SELECT: + ColumnsDialog::display(); + break; + + case CUSTOM: + addCustomColumn(); + break; + + case SMARTRESIZING: + m_smartResizing = !m_smartResizing; + Amarok::config( "PlaylistWindow" )->writeEntry( "Smart Resizing", m_smartResizing ); + if ( m_smartResizing ) + columnResizeEvent( 0, 0, 0 ); //force refit. FIXME: It doesn't work perfectly + break; + + default: + if( col != -1 ) + { + adjustColumn( col ); + header()->setResizeEnabled( true, col ); + } + } + + //determine first visible column again, since it has changed + columnOrderChanged(); + //eat event + return true; + } + + // not in slotMouseButtonPressed because we need to disable normal usage. + if( o == viewport() && e->type() == QEvent::MouseButtonPress && me->state() == Qt::ControlButton && me->button() == RightButton ) + { + PlaylistItem *item = static_cast( itemAt( me->pos() ) ); + + if( !item ) + return true; + + item->isSelected() ? + queueSelected(): + queue( item ); + + return true; //yum! + } + + // trigger in-place tag editing + else if( o == viewport() && e->type() == QEvent::MouseButtonPress && me->button() == LeftButton ) + { + m_clicktimer->stop(); + m_itemToRename = 0; + int col = header()->sectionAt( viewportToContents( me->pos() ).x() ); + if( col != PlaylistItem::Rating ) + { + PlaylistItem *item = static_cast( itemAt( me->pos() ) ); + bool edit = item + && item->isSelected() + && selectedItems().count()==1 + && (me->state() & ~LeftButton) == 0 + && item->url().isLocalFile(); + if( edit ) + { + m_clickPos = me->pos(); + m_itemToRename = item; + m_columnToRename = col; + //return true; + } + } + } + + else if( o == viewport() && e->type() == QEvent::MouseButtonRelease && me->button() == LeftButton ) + { + int col = header()->sectionAt( viewportToContents( me->pos() ).x() ); + if( col != PlaylistItem::Rating ) + { + PlaylistItem *item = static_cast( itemAt( me->pos() ) ); + if( item == m_itemToRename && me->pos() == m_clickPos ) + { + m_clicktimer->start( int( QApplication::doubleClickInterval() ), true ); + return true; + } + else + { + m_itemToRename = 0; + } + } + } + + // avoid in-place tag editing upon double-clicks + else if( e->type() == QEvent::MouseButtonDblClick && me->button() == Qt::LeftButton ) + { + m_itemToRename = 0; + m_clicktimer->stop(); + } + + // Toggle play/pause if user middle-clicks on current track + else if( o == viewport() && e->type() == QEvent::MouseButtonPress && me->button() == MidButton ) + { + PlaylistItem *item = static_cast( itemAt( me->pos() ) ); + + if( item && item == m_currentTrack ) + { + EngineController::instance()->playPause(); + return true; //yum! + } + } + + else if( o == renameLineEdit() && e->type() == 6 /*QEvent::KeyPress*/ && m_renameItem ) + { + const int visibleCols = numVisibleColumns(); + int physicalColumn = visibleCols - 1; + + while( mapToLogicalColumn( physicalColumn ) != m_renameColumn && physicalColumn >= 0 ) + physicalColumn--; + if( physicalColumn < 0 ) + { + warning() << "the column counting code is wrong! tell illissius." << endl; + return false; + } + + int column = m_renameColumn; + QListViewItem *item = m_renameItem; + + if( ke->state() & Qt::AltButton ) + { + if( ke->key() == Qt::Key_Up && m_visCount > 1 ) + if( !( item = m_renameItem->itemAbove() ) ) + { + item = *MyIt( this, MyIt::Visible ); + while( item->itemBelow() ) + item = item->itemBelow(); + } + if( ke->key() == Qt::Key_Down && m_visCount > 1 ) + if( !( item = m_renameItem->itemBelow() ) ) + item = *MyIt( this, MyIt::Visible ); + if( ke->key() == Qt::Key_Left ) + do + { + if( physicalColumn == 0 ) + physicalColumn = visibleCols - 1; + else + physicalColumn--; + column = mapToLogicalColumn( physicalColumn ); + } while( !isRenameable( column ) ); + if( ke->key() == Qt::Key_Right ) + do + { + if( physicalColumn == visibleCols - 1 ) + physicalColumn = 0; + else + physicalColumn++; + column = mapToLogicalColumn( physicalColumn ); + } while( !isRenameable( column ) ); + } + + if( ke->key() == Qt::Key_Tab ) + do + { + if( physicalColumn == visibleCols - 1 ) + { + if( !( item = m_renameItem->itemBelow() ) ) + item = *MyIt( this, MyIt::Visible ); + physicalColumn = 0; + } + else + physicalColumn++; + column = mapToLogicalColumn( physicalColumn ); + } while( !isRenameable( column ) ); + if( ke->key() == Qt::Key_Backtab ) + do + { + if( physicalColumn == 0 ) + { + if( !( item = m_renameItem->itemAbove() ) ) + { + item = *MyIt( this, MyIt::Visible ); + while( item->itemBelow() ) + item = item->itemBelow(); + } + physicalColumn = visibleCols - 1; + } + else + physicalColumn--; + column = mapToLogicalColumn( physicalColumn ); + } while( !isRenameable( column ) ); + + if( item != m_renameItem || column != m_renameColumn ) + { + if( !item->isSelected() ) + m_itemsToChangeTagsFor.clear(); + //the item that actually got changed will get added back, in writeTag() + m_renameItem->setText( m_renameColumn, renameLineEdit()->text() ); + doneEditing( m_renameItem, m_renameColumn ); + rename( item, column ); + return true; + } + } + + else if( o == renameLineEdit() && ( e->type() == QEvent::Hide || e->type() == QEvent::Close ) ) + { + m_renameItem = 0; + } + + //allow the header to process this + return KListView::eventFilter( o, e ); + + #undef me + #undef ke +} + +void +Playlist::slotSingleClick() +{ + if( m_itemToRename ) + { + rename( m_itemToRename, m_columnToRename ); + } + + m_itemToRename = 0; +} + +void +Playlist::customEvent( QCustomEvent *e ) +{ + if( e->type() == (int)UrlLoader::JobFinishedEvent ) { + refreshNextTracks( 0 ); + PLItemList in, out; + + // Disable help if playlist is populated + if ( !isEmpty() ) + m_showHelp = false; + + if ( !m_queueList.isEmpty() ) { + KURL::List::Iterator jt; + for( MyIt it( this, MyIt::All ); *it; ++it ) { + jt = m_queueList.find( (*it)->url() ); + + if ( jt != m_queueList.end() ) { + queue( *it ); + ( m_nextTracks.containsRef( *it ) ? in : out ).append( *it ); + m_queueList.remove( jt ); + } + } + m_queueList.clear(); + } + + if( m_dynamicDirt ) + { + PlaylistItem *after = m_currentTrack; + if( !after ) + { + after = firstChild(); + while( after && !after->isDynamicEnabled() ) + after = after->nextSibling(); + } + else + after = static_cast( after->itemBelow() ); + + if( after ) + { + PlaylistItem *prev = static_cast( after->itemAbove() ); + if( prev && dynamicMode() ) + prev->setDynamicEnabled( false ); + + s_dynamicADTMutex->lock(); + if( m_insertFromADT > 0 ) + { + if( EngineController::engine()->state() == Engine::Playing ) + activate( after ); + m_insertFromADT--; + } + else + activate( after ); + s_dynamicADTMutex->unlock(); + if( dynamicMode() && dynamicMode()->cycleTracks() ) + adjustDynamicPrevious( dynamicMode()->previousCount() ); + } + } + + if( m_queueDirt ) + { + PlaylistItem *after = 0; + + m_nextTracks.isEmpty() ? + after = m_currentTrack : + after = m_nextTracks.last(); + + if( !after ) + { + after = firstChild(); + while( after && !after->isDynamicEnabled() ) + after = after->nextSibling(); + } + else + after = static_cast( after->itemBelow() ); + + if( after ) + { + m_nextTracks.append( after ); + + in.append( after ); + } + + m_queueDirt = false; + } + + if( !in.isEmpty() || !out.isEmpty() ) + emit queueChanged( in, out ); + + //force redraw of currentTrack marker, play icon, etc. + restoreCurrentTrack(); + } + + updateNextPrev(); +} + + + +//////////////////////////////////////////////////////////////////////////////// +/// Misc Public Methods +//////////////////////////////////////////////////////////////////////////////// + +bool +Playlist::saveM3U( const QString &path, bool relative ) const +{ + QValueList urls; + QValueList titles; + QValueList lengths; + for( MyIt it( firstChild(), MyIt::Visible ); *it; ++it ) + { + urls << (*it)->url(); + titles << (*it)->title(); + lengths << (*it)->length(); + } + return PlaylistBrowser::savePlaylist( path, urls, titles, lengths, relative ); +} + +void +Playlist::saveXML( const QString &path ) +{ + DEBUG_BLOCK + + QFile file( path ); + if( !file.open( IO_WriteOnly | IO_Truncate | IO_Raw ) ) return; + + // Manual buffering since QFile's is slow for whatever reason + const uint kWriteSize = 256 * 1024; + + QBuffer buffer; + buffer.open(IO_WriteOnly); + + QTextStream stream( &buffer ); + stream.setEncoding( QTextStream::UnicodeUTF8 ); + stream << "\n"; + + QString dynamic; + if( dynamicMode() ) + { + const QString title = ( dynamicMode()->title() ).replace( "&", "&" ) + .replace( "<", "<" ) + .replace( ">", ">" ); + dynamic = QString(" dynamicMode=\"%1\"").arg( title ); + } + stream << QString( "\n" ) + .arg( "Amarok" ).arg( Amarok::xmlVersion() ).arg( dynamic ); + + for( MyIt it( this, MyIt::All ); *it; ++it ) + { + const PlaylistItem *item = *it; + if( item->isEmpty() ) continue; // Skip marker items and such + + QStringList attributes; + const int queueIndex = m_nextTracks.findRef( item ); + if ( queueIndex != -1 ) + attributes << "queue_index" << QString::number( queueIndex + 1 ); + else if ( item == currentTrack() ) + attributes << "queue_index" << QString::number( 0 ); + + if( !item->isDynamicEnabled() ) + attributes << "dynamicdisabled" << "true"; + + if( m_stopAfterTrack == item ) + attributes << "stop_after" << "true"; + + item->save( stream, attributes ); + + if ( buffer.at() >= kWriteSize ) + { + file.writeBlock( buffer.buffer().data(), buffer.at() ); + buffer.reset(); + } + } + + stream << "\n"; + file.writeBlock(buffer.buffer().data(), buffer.at()); + file.close(); +} + +void +Playlist::burnPlaylist( int projectType ) +{ + KURL::List list; + + QListViewItemIterator it( this ); + for( ; it.current(); ++it ) { + PlaylistItem *item = static_cast(*it); + KURL url = item->url(); + if( url.isLocalFile() ) + list << url; + } + + K3bExporter::instance()->exportTracks( list, projectType ); +} + +void +Playlist::burnSelectedTracks( int projectType ) +{ + KURL::List list; + + QListViewItemIterator it( this, QListViewItemIterator::Selected ); + for( ; it.current(); ++it ) { + PlaylistItem *item = static_cast(*it); + KURL url = item->url(); + if( url.isLocalFile() ) + list << url; + } + + K3bExporter::instance()->exportTracks( list, projectType ); +} + +void +Playlist::addCustomMenuItem( const QString &submenu, const QString &itemTitle ) //for dcop +{ + m_customSubmenuItem[submenu] << itemTitle; +} + +bool +Playlist::removeCustomMenuItem( const QString &submenu, const QString &itemTitle ) //for dcop +{ + if( !m_customSubmenuItem.contains(submenu) ) + return false; + if( m_customSubmenuItem[submenu].remove( itemTitle ) != 0 ) + { + if( m_customSubmenuItem[submenu].count() == 0 ) + m_customSubmenuItem.remove( submenu ); + return true; + return true; + } + else + return false; +} + +void +Playlist::customMenuClicked(int id) //adapted from burnSelectedTracks +{ + QString message = m_customIdItem[id]; + QListViewItemIterator it( this, QListViewItemIterator::Selected ); + for( ; it.current(); ++it ) { + PlaylistItem *item = static_cast(*it); + KURL url = item->url().url(); + message += ' ' + url.url(); + } + ScriptManager::instance()->customMenuClicked( message ); +} + +void +Playlist::setDynamicMode( DynamicMode *mode ) //SLOT +{ + // if mode == 0, then dynamic mode was just turned off. + + DynamicMode* const prev = m_dynamicMode; + m_dynamicMode = mode; + if( mode ) + AmarokConfig::setLastDynamicMode( mode->title() ); + emit dynamicModeChanged( mode ); + + if( mode ) + { + m_oldRandom = AmarokConfig::randomMode(); + m_oldRepeat = AmarokConfig::repeat(); + } + Amarok::actionCollection()->action( "random_mode" )->setEnabled( !mode ); + Amarok::actionCollection()->action( "repeat" )->setEnabled( !mode ); + Amarok::actionCollection()->action( "playlist_shuffle" )->setEnabled( !mode ); + Amarok::actionCollection()->action( "repopulate" )->setEnabled( mode ); + if( prev && mode ) + { + if( prev->previousCount() != mode->previousCount() ) + adjustDynamicPrevious( mode->previousCount(), true ); + if( prev->upcomingCount() != mode->upcomingCount() ) + adjustDynamicUpcoming( true ); + } + else if( !prev ) + { + if( mode ) + adjustDynamicPrevious( mode->previousCount(), true ); + setDynamicHistory( true ); // disable items! + } + + else if( !mode ) // enable items again, dynamic mode is no more + setDynamicHistory( false ); +} + +void +Playlist::loadDynamicMode( DynamicMode *mode ) //SLOT +{ + saveUndoState(); + setDynamicMode( mode ); + if( isEmpty() ) + repopulate(); +} + +void +Playlist::editActiveDynamicMode() //SLOT +{ + if( !m_dynamicMode ) + return; + + DynamicMode *m = modifyDynamicMode(); + ConfigDynamic::editDynamicPlaylist( PlaylistWindow::self(), m ); + m->rebuildCachedItemSet(); + finishedModifying( m ); +} + +void +Playlist::disableDynamicMode() //SLOT +{ + if( !m_dynamicMode ) + return; + setDynamicMode( 0 ); + AmarokConfig::setRandomMode( m_oldRandom ); + AmarokConfig::setRepeat( m_oldRepeat ); + static_cast(Amarok::actionCollection()->action( "random_mode" ))->setCurrentItem( m_oldRandom ); + static_cast(Amarok::actionCollection()->action( "repeat" ))->setCurrentItem( m_oldRepeat ); +} + +void +Playlist::rebuildDynamicModeCache() //SLOT +{ + if( !m_dynamicMode ) + return; + + DynamicMode *m = modifyDynamicMode(); + m->rebuildCachedItemSet(); + finishedModifying( m ); +} + +void +Playlist::repopulate() //SLOT +{ + if( !m_dynamicMode ) + return; + + // Repopulate the upcoming tracks + MyIt it( this, MyIt::All ); + QPtrList list; + + for( ; *it; ++it ) + { + PlaylistItem *item = static_cast(*it); + int queueIndex = m_nextTracks.findRef( item ); + bool isQueued = queueIndex != -1; + bool isMarker = item->isEmpty(); + // markers are used by playlistloader, and removing them is not good + + if( !item->isDynamicEnabled() || item == m_currentTrack || isQueued || isMarker ) + continue; + + list.prepend( *it ); + } + + saveUndoState(); + + //remove the items + for( QListViewItem *item = list.first(); item; item = list.next() ) + { + removeItem( static_cast( item ) ); + delete item; + } + + //calling advanceDynamicTrack will remove an item too, which is undesirable + //block signals to avoid saveUndoState being called + blockSignals( true ); + addDynamicModeTracks( dynamicMode()->upcomingCount() ); + blockSignals( false ); +} + +void +Playlist::shuffle() //SLOT +{ + if( dynamicMode() ) + return; + + QPtrList list; + + setSorting( NO_SORT ); + + // shuffle only VISIBLE entries + for( MyIt it( this ); *it; ++it ) + list.append( *it ); + + // we do it in two steps because the iterator doesn't seem + // to like it when we do takeItem and ++it in the same loop + for( QListViewItem *item = list.first(); item; item = list.next() ) + takeItem( item ); + + //shuffle + KRandomSequence( (long)KApplication::random() ).randomize( &list ); + + //reinsert in new order + for( QListViewItem *item = list.first(); item; item = list.next() ) + insertItem( item ); + + updateNextPrev(); + ScriptManager::instance()->notifyPlaylistChange("reordered"); +} + +void +Playlist::removeSelectedItems() //SLOT +{ + if( isLocked() ) return; + + //assemble a list of what needs removing + //calling removeItem() iteratively is more efficient if they are in _reverse_ order, hence the prepend() + PLItemList queued, list; + int dontReplaceDynamic = 0; + + for( PlaylistIterator it( this, MyIt::Selected ); *it; ++it ) + { + if( !(*it)->isDynamicEnabled() ) + dontReplaceDynamic++; + ( m_nextTracks.contains( *it ) ? queued : list ).prepend( *it ); + } + + if( (int)list.count() == childCount() ) + { + //clear() will saveUndoState for us. + clear(); // faster + return; + } + + if( list.isEmpty() && queued.isEmpty() ) return; + saveUndoState(); + + if( dynamicMode() ) + { + int currentTracks = childCount(); + int minTracks = dynamicMode()->upcomingCount(); + + if( m_currentTrack ) + currentTracks -= currentTrackIndex() + 1; + + int difference = currentTracks - minTracks; + + if( difference >= 0 ) + difference -= list.count(); + + if( difference < 0 ) + { + addDynamicModeTracks( -difference ); + } + } + + //remove the items + if( queued.count() ) + { + for( QListViewItem *item = queued.first(); item; item = queued.next() ) + removeItem( static_cast( item ), true ); + + emit queueChanged( PLItemList(), queued ); + + for( QListViewItem *item = queued.first(); item; item = queued.next() ) + delete item; + } + + for( QListViewItem *item = list.first(); item; item = list.next() ) + { + removeItem( static_cast( item ) ); + delete item; + } + + updateNextPrev(); + + ScriptManager::instance()->notifyPlaylistChange("changed"); + //NOTE no need to emit childCountChanged(), removeItem() does that for us + + //select next item in list + setSelected( currentItem(), true ); +} + +void +Playlist::deleteSelectedFiles() //SLOT +{ + if( isLocked() ) return; + + KURL::List urls; + + //assemble a list of what needs removing + for( MyIt it( this, MyIt::Selected ); + it.current(); + urls << static_cast( *it )->url(), ++it ); + if( DeleteDialog::showTrashDialog(this, urls) ) + { + CollectionDB::instance()->removeSongs( urls ); + removeSelectedItems(); + foreachType( KURL::List, urls ) + CollectionDB::instance()->emitFileDeleted( (*it).path() ); + QTimer::singleShot( 0, CollectionView::instance(), SLOT( renderView() ) ); + } +} + +void +Playlist::removeDuplicates() //SLOT +{ + // Remove dead entries: + + for( QListViewItemIterator it( this ); it.current(); ) { + PlaylistItem* item = static_cast( *it ); + const KURL url = item->url(); + if ( url.isLocalFile() && !QFile::exists( url.path() ) ) { + removeItem( item ); + ++it; + delete item; + } + else ++it; + } + + // Remove dupes: + + QSortedList list; + for( QListViewItemIterator it( this ); it.current(); ++it ) + list.prepend( static_cast( it.current() ) ); + + list.sort(); + + QPtrListIterator it( list ); + PlaylistItem *item; + while( (item = it.current()) ) { + const KURL &compare = item->url(); + ++it; + if ( *it && compare == it.current()->url() ) { + removeItem( item ); + delete item; + } + } +} + +void +Playlist::copyToClipboard( const QListViewItem *item ) const //SLOT +{ + if( !item ) item = currentTrack(); + + if( item ) + { + const PlaylistItem* playlistItem = static_cast( item ); + + QString text = playlistItem->prettyTitle(); + // For streams add the streamtitle too + //TODO make prettyTitle do this + if ( playlistItem->url().protocol() == "http" ) + text.append( " :: " + playlistItem->url().url() ); + + // Copy both to clipboard and X11-selection + QApplication::clipboard()->setText( text, QClipboard::Clipboard ); + QApplication::clipboard()->setText( text, QClipboard::Selection ); + + Amarok::OSD::instance()->OSDWidget::show( i18n( "Copied: %1" ).arg( text ), + QImage(CollectionDB::instance()->albumImage(*playlistItem )) ); + } +} + +void Playlist::undo() //SLOT +{ + if( !isLocked() ) + switchState( m_undoList, m_redoList ); +} + +void Playlist::redo() //SLOT +{ + if( !isLocked() ) + switchState( m_redoList, m_undoList ); +} + +void +Playlist::updateMetaData( const MetaBundle &mb ) //SLOT +{ + SHOULD_BE_GUI + for( MyIt it( this, MyIt::All ); *it; ++it ) + if( mb.url() == (*it)->url() ) + { + (*it)->copyFrom( mb ); + (*it)->filter( m_filter ); + } +} + +void +Playlist::adjustColumn( int n ) +{ + if( n == PlaylistItem::Rating ) + setColumnWidth( n, PlaylistItem::ratingColumnWidth() ); + else if( n == PlaylistItem::Mood ) + setColumnWidth( n, 120 ); + else + KListView::adjustColumn( n ); +} + +void +Playlist::showQueueManager() +{ + DEBUG_BLOCK + + // Only show the dialog once + if( QueueManager::instance() ) { + QueueManager::instance()->raise(); + return; + } + + QueueManager dialog; + if( dialog.exec() == QDialog::Accepted ) + { + changeFromQueueManager(dialog.newQueue()); + } +} + +void +Playlist::changeFromQueueManager(QPtrList list) +{ + PLItemList oldQueue = m_nextTracks; + m_nextTracks = list; + + PLItemList in, out; + // make sure we repaint items no longer queued + for( PlaylistItem* item = oldQueue.first(); item; item = oldQueue.next() ) + if( !m_nextTracks.containsRef( item ) ) + out << item; + for( PlaylistItem* item = m_nextTracks.first(); item; item = m_nextTracks.next() ) + if( !oldQueue.containsRef( item ) ) + in << item; + + emit queueChanged( in, out ); + + // repaint newly queued or altered queue items + if( dynamicMode() ) + sortQueuedItems(); + else + refreshNextTracks(); +} + +void +Playlist::setFilterSlot( const QString &query ) //SLOT +{ + m_filtertimer->stop(); + if( m_filter != query ) + { + m_prevfilter = m_filter; + m_filter = query; + } + m_filtertimer->start( 50, true ); +} + +void +Playlist::setDelayedFilter() //SLOT +{ + setFilter( m_filter ); + + //to me it seems sensible to do this, BUT if it seems annoying to you, remove it + showCurrentTrack(); +} + +void +Playlist::setFilter( const QString &query ) //SLOT +{ + const bool advanced = ExpressionParser::isAdvancedExpression( query ); + MyIt it( this, ( !advanced && query.lower().contains( m_prevfilter.lower() ) ) + ? MyIt::Visible + : MyIt::All ); + + + if( advanced ) + { + ParsedExpression parsed = ExpressionParser::parse( query ); + QValueList visible = visibleColumns(); + for(; *it; ++it ) + (*it)->setVisible( (*it)->matchesParsedExpression( parsed, visible ) ); + } + else { + // optimized path + const QStringList terms = QStringList::split( ' ', query.lower() ); + const MetaBundle::ColumnMask visible = getVisibleColumnMask(); + for(; *it; ++it ) { + (*it)->setVisible( (*it)->matchesFast(terms, visible)); + } + } + + if( m_filter != query ) + { + m_prevfilter = m_filter; + m_filter = query; + } + updateNextPrev(); +} + +void +Playlist::scoreChanged( const QString &path, float score ) +{ + for( MyIt it( this, MyIt::All ); *it; ++it ) + { + PlaylistItem *item = static_cast( *it ); + if ( item->url().path() == path ) + { + item->setScore( score ); + item->setPlayCount( CollectionDB::instance()->getPlayCount( path ) ); + item->setLastPlay( CollectionDB::instance()->getLastPlay( path ).toTime_t() ); + item->filter( m_filter ); + } + } +} + +void +Playlist::ratingChanged( const QString &path, int rating ) +{ + for( MyIt it( this, MyIt::All ); *it; ++it ) + { + PlaylistItem *item = static_cast( *it ); + if ( item->url().path() == path ) + { + item->setRating( rating ); + item->filter( m_filter ); + } + } +} + +void +Playlist::fileMoved( const QString &srcPath, const QString &dstPath ) +{ + // Make sure the MoodServer gets this signal first! + MoodServer::instance()->slotFileMoved( srcPath, dstPath ); + + for( MyIt it( this, MyIt::All ); *it; ++it ) + { + PlaylistItem *item = static_cast( *it ); + if ( item->url().path() == srcPath ) + { + item->setUrl( KURL::fromPathOrURL( dstPath ) ); + item->filter( m_filter ); + } + } +} + +void +Playlist::appendToPreviousTracks( PlaylistItem *item ) +{ + if( !m_prevTracks.containsRef( item ) ) + { + m_total -= item->totalIncrementAmount(); + m_prevTracks.append( item ); + } +} + +void +Playlist::appendToPreviousAlbums( PlaylistAlbum *album ) +{ + if( !m_prevAlbums.containsRef( album ) ) + { + m_total -= album->total; + m_prevAlbums.append( album ); + } +} + +void +Playlist::removeFromPreviousTracks( PlaylistItem *item ) +{ + if( item ) + { + if( m_prevTracks.removeRef( item ) ) + m_total += item->totalIncrementAmount(); + } + else if( (item = m_prevTracks.current()) != 0 ) + if( m_prevTracks.remove() ) + m_total += item->totalIncrementAmount(); +} + +void +Playlist::removeFromPreviousAlbums( PlaylistAlbum *album ) +{ + if( album ) + { + if( m_prevAlbums.removeRef( album ) ) + m_total += album->total; + } + else if( (album = m_prevAlbums.current()) != 0 ) + if( m_prevAlbums.remove() ) + m_total += album->total; +} + + +void +Playlist::showContextMenu( QListViewItem *item, const QPoint &p, int col ) //SLOT +{ + //if clicked on an empty area + enum { REPOPULATE, ENABLEDYNAMIC }; + if( item == 0 ) + { + KPopupMenu popup; + Amarok::actionCollection()->action("playlist_save")->plug( &popup ); + Amarok::actionCollection()->action("playlist_clear")->plug( &popup ); + DynamicMode *m = 0; + if(dynamicMode()) + popup.insertItem( SmallIconSet( Amarok::icon( "dynamic" ) ), i18n("Repopulate"), REPOPULATE); + else + { + Amarok::actionCollection()->action("playlist_shuffle")->plug( &popup ); + m = PlaylistBrowser::instance()->findDynamicModeByTitle( AmarokConfig::lastDynamicMode() ); + if( m ) + popup.insertItem( SmallIconSet( Amarok::icon( "dynamic" ) ), i18n("L&oad %1").arg( m->title().replace( '&', "&&" ) ), ENABLEDYNAMIC); + } + switch(popup.exec(p)) + { + case ENABLEDYNAMIC: + loadDynamicMode( m ); + break; + case REPOPULATE: repopulate(); break; + } + return; + } + + #define item static_cast(item) + + enum { + PLAY, PLAY_NEXT, STOP_DONE, VIEW, EDIT, FILL_DOWN, COPY, CROP_PLAYLIST, SAVE_PLAYLIST, REMOVE, FILE_MENU, ORGANIZE, MOVE_TO_COLLECTION, COPY_TO_COLLECTION, DELETE, + TRASH, REPEAT, LAST }; //keep LAST last + + const bool canRename = isRenameable( col ) && item->url().isLocalFile(); + const bool isCurrent = (item == m_currentTrack); + const bool isPlaying = EngineController::engine()->state() == Engine::Playing; + const bool trackColumn = col == PlaylistItem::Track; + const bool isLastFm = item->url().protocol() == "lastfm"; + const QString tagName = columnText( col ); + const QString tag = item->text( col ); + + uint itemCount = 0; + for( MyIt it( this, MyIt::Selected ); *it; ++it ) + itemCount++; + + PrettyPopupMenu popup; + +// if(itemCount==1) +// popup.insertTitle( KStringHandler::rsqueeze( MetaBundle( item ).prettyTitle(), 50 )); +// else +// popup.insertTitle(i18n("1 Track", "%n Selected Tracks", itemCount)); + + if( isCurrent && isLastFm ) + { + KActionCollection *ac = Amarok::actionCollection(); + if( ac->action( "skip" ) ) ac->action( "skip" )->plug( &popup ); + if( ac->action( "love" ) ) ac->action( "love" )->plug( &popup ); + if( ac->action( "ban" ) ) ac->action( "ban" )->plug( &popup ); + popup.insertSeparator(); + } + + if( !isCurrent || !isPlaying ) + popup.insertItem( SmallIconSet( Amarok::icon( "play" ) ), isCurrent && isPlaying + ? i18n( "&Restart" ) + : i18n( "&Play" ), PLAY ); + if( isCurrent && !isLastFm && isPlaying ) + Amarok::actionCollection()->action( "pause" )->plug( &popup ); + + // Begin queue entry logic + popup.insertItem( SmallIconSet( Amarok::icon( "queue_track" ) ), i18n("&Queue Selected Tracks"), PLAY_NEXT ); + + bool queueToggle = false; + MyIt it( this, MyIt::Selected ); + bool firstQueued = ( m_nextTracks.findRef( *it ) != -1 ); + + for( ++it ; *it; ++it ) { + if ( ( m_nextTracks.findRef( *it ) != -1 ) != firstQueued ) { + queueToggle = true; + break; + } + } + if( itemCount == 1 ) + { + if ( !firstQueued ) + popup.changeItem( PLAY_NEXT, i18n( "&Queue Track" ) ); + else + popup.changeItem( PLAY_NEXT, SmallIconSet( Amarok::icon( "dequeue_track" ) ), i18n("&Dequeue Track") ); + } else { + if ( queueToggle ) + popup.changeItem( PLAY_NEXT, i18n( "Toggle &Queue Status (1 track)", "Toggle &Queue Status (%n tracks)", itemCount ) ); + else + // remember, queueToggled only gets set to false if there are items queued and not queued. + // so, if queueToggled is false, all items have the same queue status as the first item. + if ( !firstQueued ) + popup.changeItem( PLAY_NEXT, i18n( "&Queue Selected Tracks" ) ); + else + popup.changeItem( PLAY_NEXT, SmallIconSet( Amarok::icon( "dequeue_track" ) ), i18n("&Dequeue Selected Tracks") ); + } + // End queue entry logic + + bool afterCurrent = false; + if( !m_nextTracks.isEmpty() ? m_nextTracks.getLast() : m_currentTrack ) + for( MyIt it( !m_nextTracks.isEmpty() ? m_nextTracks.getLast() : m_currentTrack, MyIt::Visible ); *it; ++it ) + if( *it == item ) + { + afterCurrent = true; + break; + } + + if( itemCount == 1 ) + { + Amarok::actionCollection()->action( "stop_after" )->plug( &popup ); + dynamic_cast( Amarok::actionCollection()->action( "stop_after" ) )->setChecked( m_stopAfterTrack == item ); + } + + if( isCurrent && itemCount == 1 ) + { + popup.insertItem( SmallIconSet( Amarok::icon( "repeat_track" ) ), i18n( "&Repeat Track" ), REPEAT ); + popup.setItemChecked( REPEAT, Amarok::repeatTrack() ); + } + + popup.insertSeparator(); + + if( itemCount > 1 ) + { + popup.insertItem( SmallIconSet( Amarok::icon( "playlist" ) ), i18n("&Set as Playlist (Crop)"), CROP_PLAYLIST ); + popup.insertItem( SmallIconSet( Amarok::icon( "save" ) ), i18n("S&ave as Playlist..."), SAVE_PLAYLIST ); + } + + popup.insertItem( SmallIconSet( Amarok::icon( "remove_from_playlist" ) ), i18n( "Re&move From Playlist" ), this, SLOT( removeSelectedItems() ), Key_Delete, REMOVE ); + + popup.insertSeparator(); + + KPopupMenu fileMenu; + if( CollectionDB::instance()->isDirInCollection( item->url().directory() ) ) + { + fileMenu.insertItem( SmallIconSet( "filesaveas" ), i18n("&Organize File...", "&Organize %n Files...", itemCount), ORGANIZE ); + } + else + { + fileMenu.insertItem( SmallIconSet( "filesaveas" ), i18n("&Copy Track to Collection...", "&Copy %n Tracks to Collection...", itemCount), COPY_TO_COLLECTION ); + fileMenu.insertItem( SmallIconSet( "filesaveas" ), i18n("&Move Track to Collection...", "&Move %n Tracks to Collection...", itemCount), MOVE_TO_COLLECTION ); + } + fileMenu.insertItem( SmallIconSet( Amarok::icon( "remove" ) ), i18n("&Delete File...", "&Delete %n Selected Files...", itemCount ), this, SLOT( deleteSelectedFiles() ), SHIFT+Key_Delete, DELETE ); + popup.insertItem( SmallIconSet( Amarok::icon( "files" ) ), i18n("Manage &Files"), &fileMenu, FILE_MENU ); + + if( itemCount == 1 ) + popup.insertItem( SmallIconSet( Amarok::icon( "editcopy" ) ), i18n( "&Copy Tags to Clipboard" ), COPY ); + + if( itemCount > 1 ) + popup.insertItem( trackColumn + ? i18n("Iteratively Assign Track &Numbers") + : i18n("&Write '%1' for Selected Tracks") + .arg( KStringHandler::rsqueeze( tag, 30 ).replace( "&", "&&" ) ), FILL_DOWN ); + + popup.insertItem( SmallIconSet( Amarok::icon( "edit" ) ), (itemCount == 1 + ? i18n( "&Edit Tag '%1'" ) + : i18n( "&Edit '%1' Tag for Selected Tracks" )).arg( tagName ), EDIT ); + + popup.insertItem( SmallIconSet( Amarok::icon( "info" ) ) + , item->url().isLocalFile() ? + i18n( "Edit Track &Information...", "Edit &Information for %n Tracks...", itemCount): + i18n( "Track &Information...", "&Information for %n Tracks...", itemCount) + , VIEW ); + + popup.setItemEnabled( EDIT, canRename ); //only enable for columns that have editable tags + popup.setItemEnabled( FILL_DOWN, canRename ); + popup.setItemEnabled( REMOVE, !isLocked() ); // can't remove things when playlist is locked, + popup.setItemEnabled( DELETE, !isLocked() && item->url().isLocalFile() ); + popup.setItemEnabled( ORGANIZE, !isLocked() && item->isKioUrl() ); + popup.setItemEnabled( MOVE_TO_COLLECTION, !isLocked() && item->isKioUrl() ); + popup.setItemEnabled( COPY_TO_COLLECTION, !isLocked() && item->isKioUrl() ); + popup.setItemEnabled( VIEW, item->url().isLocalFile() || itemCount == 1 ); // disable for CDAudio multiselection + + if( m_customSubmenuItem.count() > 0 ) + popup.insertSeparator(); + QValueList submenuTexts = m_customSubmenuItem.keys(); + for( QValueList::Iterator keyIt =submenuTexts.begin(); keyIt != submenuTexts.end(); ++keyIt ) + { + KPopupMenu* menu; + if( (*keyIt) == "root") + menu = &popup; + else + { + menu = new KPopupMenu(); + popup.insertItem( *keyIt, menu); + } + foreach(m_customSubmenuItem[*keyIt]) + { + int id; + if(m_customIdItem.isEmpty()) + id=LAST; + else + id=m_customIdItem.keys().last()+1; + menu->insertItem( (*it), id ); + m_customIdItem[id]= (*keyIt) + ' ' + (*it); + } + } + + const QPoint pos( p.x() - popup.sidePixmapWidth(), p.y() + 3 ); + int menuItemId = popup.exec( pos ); + PLItemList in, out; + + switch( menuItemId ) + { + case PLAY: + if( itemCount == 1 ) + { + //Restarting track on dynamic mode + if( isCurrent && isPlaying && dynamicMode() ) + m_dynamicDirt = true; + activate( item ); + } + else + { + MyIt it( this, MyIt::Selected ); + activate( *it ); + ++it; + for( int i = 0; *it; ++i, ++it ) + { + in.append( *it ); + m_nextTracks.insert( i, *it ); + } + emit queueChanged( in, out ); + } + break; + + case PLAY_NEXT: + queueSelected(); + break; + + case VIEW: + showTagDialog( selectedItems() ); + break; + + case EDIT: + // do this because QListView sucks, if track change occurs during + // an edit event, the rename operation ends, BUT, the list is not + // cleared because writeTag is never called. Q/K ListView sucks + m_itemsToChangeTagsFor.clear(); + + if( !item->isSelected() ) + m_itemsToChangeTagsFor.append( item ); + else + for( MyIt it( this, MyIt::Selected ); *it; ++it ) + m_itemsToChangeTagsFor.append( *it ); + + rename( item, col ); + break; + + case FILL_DOWN: + //Spreadsheet like fill-down + { + QString newTag = item->exactText( col ); + MyIt it( this, MyIt::Selected ); + + //special handling for track column + uint trackNo = (*it)->track(); + + //we should start at the next row if we are doing track number + //and the first row has a number set + if ( trackColumn && trackNo > 0 ) + ++it; + + ThreadManager::JobList jobs; + bool updateView = true; + for( ; *it; ++it ) { + if ( trackColumn ) + //special handling for track column + newTag = QString::number( ++trackNo ); + + else if ( *it == item ) + //skip the one we are copying + continue; + + else if( col == PlaylistItem::Score ) + { + CollectionDB::instance()->setSongPercentage( (*it)->url().path(), newTag.toInt() ); + continue; + } + else if( col == PlaylistItem::Rating ) + { + CollectionDB::instance()->setSongRating( (*it)->url().path(), newTag.toInt() ); + continue; + } + + if ( !(*it)->isEditing( col ) ) + jobs.prepend( new TagWriter( *it, (*it)->exactText( col ), newTag, col, updateView ) ); + + updateView = false; + } + + ThreadManager::instance()->queueJobs( jobs ); + } + break; + + case COPY: + copyToClipboard( item ); + break; + + case CROP_PLAYLIST: + if( !isLocked() ) + { + //use "in" for the other just because it's there and not used otherwise + for( MyIt it( this, MyIt::Unselected | MyIt::Visible ); *it; ++it ) + ( m_nextTracks.containsRef( *it ) ? in : out ).append( *it ); + + if( !in.isEmpty() || !out.isEmpty() ) + { + saveUndoState(); + + for( PlaylistItem *it = out.first(); it; it = out.next() ) + removeItem( it, true ); + if( !out.isEmpty() ) + emit queueChanged( PLItemList(), out ); + for( PlaylistItem *it = out.first(); it; it = out.next() ) + delete it; + + for( PlaylistItem *it = in.first(); it; it = in.next() ) + { + removeItem( it ); + delete it; + } + ScriptManager::instance()->notifyPlaylistChange("cleared"); + } + } + break; + + case SAVE_PLAYLIST: + saveSelectedAsPlaylist(); + break; + + case REPEAT: + // FIXME HACK Accessing AmarokConfig::Enum* yields compile errors with GCC 3.3. + static_cast( Amarok::actionCollection()->action( "repeat" ) ) + ->setCurrentItem( Amarok::repeatTrack() + ? 0 /*AmarokConfig::EnumRepeat::Off*/ + : 1 /*AmarokConfig::EnumRepeat::Track*/ ); + break; + + case ORGANIZE: + case MOVE_TO_COLLECTION: + case COPY_TO_COLLECTION: + { + KURL::List list; + + for( QListViewItemIterator it( this, QListViewItemIterator::Selected ); + it.current(); + ++it ) + { + PlaylistItem *i= static_cast(*it); + KURL url = i->url(); + list << url; + } + bool organize = CollectionDB::instance()->isDirInCollection( item->url().directory() ); + bool move = menuItemId==MOVE_TO_COLLECTION; + CollectionView::instance()->organizeFiles( list, + organize ? i18n( "Organize Files" ) : move ? i18n( "Move Tracks to Collection" ) : i18n( "Copy Tracks to Collection"), + !organize && !move ); + } + break; + + default: + if(menuItemId < LAST) + break; + customMenuClicked(menuItemId); + break; + } + + #undef item +} + +//////////////////////////////////////////////////////////////////////////////// +/// Misc Protected Methods +//////////////////////////////////////////////////////////////////////////////// + +void +Playlist::fontChange( const QFont &old ) +{ + KListView::fontChange( old ); + initStarPixmaps(); + triggerUpdate(); +} + +void +Playlist::contentsMouseMoveEvent( QMouseEvent *e ) +{ + if( e ) + KListView::contentsMouseMoveEvent( e ); + PlaylistItem *prev = m_hoveredRating; + const QPoint pos = e ? e->pos() : viewportToContents( viewport()->mapFromGlobal( QCursor::pos() ) ); + + PlaylistItem *item = static_cast( itemAt( contentsToViewport( pos ) ) ); + if( item && pos.x() > header()->sectionPos( PlaylistItem::Rating ) && + pos.x() < header()->sectionPos( PlaylistItem::Rating ) + header()->sectionSize( PlaylistItem::Rating ) ) + { + m_hoveredRating = item; + m_hoveredRating->updateColumn( PlaylistItem::Rating ); + } + else + m_hoveredRating = 0; + + if( prev ) + { + if( m_selCount > 1 && prev->isSelected() ) + QScrollView::updateContents( header()->sectionPos( PlaylistItem::Rating ) + 1, contentsY(), + header()->sectionSize( PlaylistItem::Rating ) - 2, visibleHeight() ); + else + prev->updateColumn( PlaylistItem::Rating ); + } +} + +void Playlist::leaveEvent( QEvent *e ) +{ + KListView::leaveEvent( e ); + + PlaylistItem *prev = m_hoveredRating; + m_hoveredRating = 0; + if( prev ) + prev->updateColumn( PlaylistItem::Rating ); +} + +void Playlist::contentsMousePressEvent( QMouseEvent *e ) +{ + PlaylistItem *item = static_cast( itemAt( contentsToViewport( e->pos() ) ) ); + + int beginRatingSection = header()->sectionPos( PlaylistItem::Rating ); + int endRatingSection = beginRatingSection + header()->sectionSize( PlaylistItem::Rating ); + + /// Conditions on setting the rating of an item + if( item && + !( e->state() & Qt::ControlButton || e->state() & Qt::ShiftButton ) && // skip if ctrl or shift held + ( e->button() & Qt::LeftButton ) && // only on a left click + ( e->pos().x() > beginRatingSection && e->pos().x() < endRatingSection ) ) // mouse over rating column + { + int rating = item->ratingAtPoint( e->pos().x() ); + + if( item->isSelected() ) + setSelectedRatings( rating ); + else // toggle half star + CollectionDB::instance()->setSongRating( item->url().path(), rating, true ); + } + else + KListView::contentsMousePressEvent( e ); +} + +void Playlist::contentsWheelEvent( QWheelEvent *e ) +{ + PlaylistItem* const item = static_cast( itemAt( contentsToViewport( e->pos() ) ) ); + const int column = header()->sectionAt( e->pos().x() ); + const int distance = header()->sectionPos( column ) + header()->sectionSize( column ) - e->pos().x(); + const int maxdistance = fontMetrics().width( QString::number( m_nextTracks.count() ) ) + 7; + if( item && column == m_firstColumn && distance <= maxdistance && item->isQueued() ) + { + const int n = e->delta() / 120, + s = n / abs(n), + pos = item->queuePosition(); + PLItemList changed; + for( int i = 1; i <= abs(n); ++i ) + { + const int dest = pos + s*i; + if( kClamp( dest, 0, int( m_nextTracks.count() ) - 1 ) != dest ) + break; + PlaylistItem* const p = m_nextTracks.at( dest ); + if( changed.findRef( p ) == -1 ) + changed << p; + if( changed.findRef( m_nextTracks.at( dest - s ) ) == -1 ) + changed << m_nextTracks.at( dest - s ); + m_nextTracks.replace( dest, m_nextTracks.at( dest - s ) ); + m_nextTracks.replace( dest - s, p ); + } + + for( int i = 0, n = changed.count(); i < n; ++i ) + changed.at(i)->update(); + } + else + KListView::contentsWheelEvent( e ); +} + +//////////////////////////////////////////////////////////////////////////////// +/// Misc Private Methods +//////////////////////////////////////////////////////////////////////////////// + +void +Playlist::lock() +{ + if( m_lockStack == 0 ) { + m_clearButton->setEnabled( false ); + m_undoButton->setEnabled( false ); + m_redoButton->setEnabled( false ); + } + + m_lockStack++; +} + +void +Playlist::unlock() +{ + Q_ASSERT( m_lockStack > 0 ); + + m_lockStack--; + + if( m_lockStack == 0 ) { + m_clearButton->setEnabled( true ); + m_undoButton->setEnabled( !m_undoList.isEmpty() ); + m_redoButton->setEnabled( !m_redoList.isEmpty() ); + } +} + +int +Playlist::numVisibleColumns() const +{ + int r = 0, i = 1; + for( const int n = columns(); i <= n; ++i) + if( columnWidth( i - 1 ) ) + ++r; + return r; +} + +QValueList Playlist::visibleColumns() const +{ + QValueList r; + for( int i = 0, n = columns(); i < n; ++i) + if( columnWidth( i ) ) + r.append( i ); + return r; +} + +MetaBundle::ColumnMask Playlist::getVisibleColumnMask() const { + MetaBundle::ColumnMask mask = 0; + for( int i = 0, n = columns(); i < n; ++i) + if( columnWidth( i ) ) mask = mask | (1 << i); + return mask; +} + + +int +Playlist::mapToLogicalColumn( int physical ) const +{ + int logical = header()->mapToSection( physical ); + + //skip hidden columns + int n = 0; + for( int i = 0; i <= physical; ++i ) + if( !header()->sectionSize( header()->mapToSection( physical - i ) ) ) + ++n; + while( n ) + { + logical = header()->mapToSection( ++physical ); + if( logical < 0 ) + { + logical = header()->mapToSection( physical - 1 ); + break; + } + if( header()->sectionSize( logical ) ) + --n; + } + + return logical; +} + +void +Playlist::setColumns( QValueList order, QValueList visible ) +{ + for( int i = order.count() - 1; i >= 0; --i ) + header()->moveSection( order[i], i ); + for( int i = 0; i < PlaylistItem::NUM_COLUMNS; ++i ) + { + if( visible.contains( i ) ) + adjustColumn( i ); + else + hideColumn( i ); + } + columnOrderChanged(); +} + +void +Playlist::removeItem( PlaylistItem *item, bool multi ) +{ + // NOTE we don't check isLocked() here as it is assumed that if you call this function you + // really want to remove the item, there is no way the user can reach here without passing + // a lock() check, (currently...) + + //this function ensures we don't have dangling pointers to items that are about to be removed + //for some reason using QListView::takeItem() and QListViewItem::takeItem() was ineffective + //NOTE we don't delete item for you! You must call delete item yourself :) + + //TODO there must be a way to do this without requiring notification from the item dtor! + //NOTE orginally this was in ~PlaylistItem(), but that caused crashes due to clear() *shrug* + //NOTE items already removed by takeItem() will crash if you call nextSibling() on them + // taken items return 0 from listView() + //FIXME if you remove a series of items including the currentTrack and all the nextTracks + // then no new nextTrack will be selected and the playlist will resume from the begging + // next time + + if( m_currentTrack == item ) + { + setCurrentTrack( 0 ); + + //ensure the playlist doesn't start at the beginning after the track that's playing ends + //we don't need to do that in random mode, it's getting randomly selected anyways + if( m_nextTracks.isEmpty() && !AmarokConfig::randomMode() ) + { + //*MyIt( item ) returns either "item" or if item is hidden, the next visible playlistitem + PlaylistItem* const next = *MyIt( item ); + if( next ) + { + m_nextTracks.append( next ); + next->update(); + } + } + } + + if( m_stopAfterTrack == item ) { + m_stopAfterTrack = 0; //to be safe + if (stopAfterMode() != StopAfterCurrent) + setStopAfterMode( DoNotStop ); + } + + //cancel rename if it is pending (Bug: #147587) + if ( m_itemToRename == item ) { + m_clicktimer->stop(); + m_itemToRename = 0; + } + + //keep m_nextTracks queue synchronized + if( m_nextTracks.removeRef( item ) && !multi ) + emit queueChanged( PLItemList(), PLItemList( item ) ); + + //keep recent buffer synchronized + removeFromPreviousTracks( item ); //removes all pointers to item + + updateNextPrev(); +} + +void Playlist::ensureItemCentered( QListViewItem *item ) +{ + if( !item ) + return; + + //HACK -- apparently the various metrics aren't reliable while the UI is still updating & stuff + m_itemToReallyCenter = item; + QTimer::singleShot( 0, this, SLOT( reallyEnsureItemCentered() ) ); +} + +void +Playlist::reallyEnsureItemCentered() +{ + if( QListViewItem *item = m_itemToReallyCenter ) + { + m_itemToReallyCenter = 0; + if( m_selCount == 1 ) + { + PlaylistItem *previtem = *MyIt( this, MyIt::Selected ); + if( previtem && previtem != item ) + previtem->setSelected( false ); + } + setCurrentItem( item ); + ensureVisible( contentsX(), item->itemPos() + item->height() / 2, 0, visibleHeight() / 2 ); + triggerUpdate(); + } +} + +void +Playlist::refreshNextTracks( int from ) +{ + // This function scans the m_nextTracks list starting from the 'from' + // position and from there on updates the progressive numbering on related + // items and repaints them. In short it performs an update subsequent to + // a renumbering/order changing at some point of the m_nextTracks list. + + //start on the 'from'-th item of the list + + for( PlaylistItem* item = (from == -1) ? m_nextTracks.current() : m_nextTracks.at( from ); + item; + item = m_nextTracks.next() ) + { + item->update(); + } +} + +void +Playlist::saveUndoState() //SLOT +{ + if( saveState( m_undoList ) ) + { + m_redoList.clear(); + + m_undoButton->setEnabled( true ); + m_redoButton->setEnabled( false ); + } +} + +bool +Playlist::saveState( QStringList &list ) +{ + //used by undo system, save state of playlist to undo/redo list + + //do not change this! It's required by the undo/redo system to work! + //if you must change this, fix undo/redo first. Ask me what needs fixing + if( !isEmpty() ) + { + QString fileName; + m_undoCounter %= AmarokConfig::undoLevels(); + fileName.setNum( m_undoCounter++ ); + fileName.prepend( m_undoDir.absPath() + '/' ); + fileName.append( ".xml" ); + + if ( list.count() >= (uint)AmarokConfig::undoLevels() ) + { + m_undoDir.remove( list.first() ); + list.pop_front(); + } + + saveXML( fileName ); + list.append( fileName ); + + // Reset isNew state of all items in the playlist (determines font coloring) + PlaylistItem* item = static_cast( firstChild() ); + while( item ) { + item->setIsNew( false ); + item = item->nextSibling(); + } + triggerUpdate(); + + return true; + } + + return false; +} + +void +Playlist::switchState( QStringList &loadFromMe, QStringList &saveToMe ) +{ + m_undoDirt = true; + //switch to a previously saved state, remember current state + KURL url; url.setPath( loadFromMe.last() ); + loadFromMe.pop_back(); + + //save current state + saveState( saveToMe ); + + //this is clear() minus some parts, for instance we don't want to cause a saveUndoState() here + m_currentTrack = 0; + disableDynamicMode(); + Glow::reset(); + m_prevTracks.clear(); + m_prevAlbums.clear(); + const PLItemList prev = m_nextTracks; + m_nextTracks.clear(); + emit queueChanged( PLItemList(), prev ); + ThreadManager::instance()->abortAllJobsNamed( "TagWriter" ); + safeClear(); + m_total = 0; + m_albums.clear(); + + insertMediaInternal( url, 0, 0 ); //because the listview is empty, undoState won't be forced + + m_undoButton->setEnabled( !m_undoList.isEmpty() ); + m_redoButton->setEnabled( !m_redoList.isEmpty() ); + + if( dynamicMode() ) setDynamicHistory( true ); + m_undoDirt = false; +} + +void +Playlist::saveSelectedAsPlaylist() +{ + MyIt it( this, MyIt::Visible | MyIt::Selected ); + if( !(*it) ) + return; //safety + const QString album = (*it)->album(), + artist = (*it)->artist(); + int suggestion = !album.stripWhiteSpace().isEmpty() ? 1 : !artist.stripWhiteSpace().isEmpty() ? 2 : 3; + while( *it ) + { + if( suggestion == 1 && (*it)->album()->lower().stripWhiteSpace() != album.lower().stripWhiteSpace() ) + suggestion = 2; + if( suggestion == 2 && (*it)->artist()->lower().stripWhiteSpace() != artist.lower().stripWhiteSpace() ) + suggestion = 3; + if( suggestion == 3 ) + break; + ++it; + } + QString path = PlaylistDialog::getSaveFileName( suggestion == 1 ? album + : suggestion == 2 ? artist + : i18n( "Untitled" ) ); + + if( path.isEmpty() ) + return; + + QValueList urls; + QValueList titles; + QValueList lengths; + for( it = MyIt( this, MyIt::Visible | MyIt::Selected ); *it; ++it ) + { + urls << (*it)->url(); + titles << (*it)->title(); + lengths << (*it)->length(); + } + + if( PlaylistBrowser::savePlaylist( path, urls, titles, lengths ) ) + PlaylistWindow::self()->showBrowser( "PlaylistBrowser" ); +} + +void Playlist::initStarPixmaps() +{ + StarManager::instance()->reinitStars( fontMetrics().height(), itemMargin() ); +} + +void +Playlist::slotMouseButtonPressed( int button, QListViewItem *after, const QPoint &p, int col ) //SLOT +{ + switch( button ) + { + case Qt::MidButton: + { + const QString path = QApplication::clipboard()->text( QClipboard::Selection ); + const KURL url = KURL::fromPathOrURL( path ); + + if( url.isValid() ) + insertMediaInternal( url, static_cast(after ? after : lastItem()) ); + + break; + } + + case Qt::RightButton: + showContextMenu( after, p, col ); + break; + + default: + ; + } +} + +void Playlist::slotContentsMoving() +{ + Amarok::ToolTip::hideTips(); + QTimer::singleShot( 0, this, SLOT( contentsMouseMoveEvent() ) ); +} + +void +Playlist::slotQueueChanged( const PLItemList &/*in*/, const PLItemList &out) +{ + for( QPtrListIterator it( out ); *it; ++it ) + (*it)->update(); + refreshNextTracks( 0 ); + updateNextPrev(); +} + +void +Playlist::slotUseScores( bool use ) +{ + if( !use && columnWidth( MetaBundle::Score ) ) + hideColumn( MetaBundle::Score ); +} + +void +Playlist::slotUseRatings( bool use ) +{ + if( use && !columnWidth( MetaBundle::Rating ) ) + adjustColumn( MetaBundle::Rating ); + else if( !use && columnWidth( MetaBundle::Rating ) ) + hideColumn( MetaBundle::Rating ); +} + + +// This gets called when the user presses "Ok" or "Apply" in the +// config dialog. +void +Playlist::slotMoodbarPrefs( bool show, bool moodier, int alter, bool withMusic ) +{ + (void) moodier; (void) alter; (void) withMusic; + + if( !show && columnWidth( MetaBundle::Mood ) ) + hideColumn( MetaBundle::Mood ); + + // Reset all of our moodbars, since they may have been permanently + // disabled before because the Moodbar was disabled. We need to + // do this even if the column is hidden. + if( show ) + { + // No need to call moodbar().load(), since that will happen + // automatically next time it's displayed. We do have to + // repaint so that they get displayed though. + + for( PlaylistIterator it( this, PlaylistIterator::All ) ; *it ; ++it ) + { + (*it)->moodbar().reset(); + repaintItem(*it); + } + } +} + +void +Playlist::slotGlowTimer() //SLOT +{ + if( !currentTrack() ) return; + + using namespace Glow; + + if( counter <= STEPS*2 ) + { + // 0 -> STEPS -> 0 + const double d = (counter > STEPS) ? 2*STEPS-counter : counter; + + { + using namespace Base; + PlaylistItem::glowIntensity = d; + PlaylistItem::glowBase = QColor( r, g, b ); + } + + { + using namespace Text; + PlaylistItem::glowText = QColor( r + int(d*dr), g + int(d*dg), b + int(d*db) ); + } + + if( currentTrack() ) + currentTrack()->update(); + } + + ++counter &= 63; //built in bounds checking with &= +} + +void +Playlist::slotRepeatTrackToggled( int /* mode */ ) +{ + if( m_currentTrack ) + m_currentTrack->update(); +} + +void +Playlist::slotEraseMarker() //SLOT +{ + if( m_marker ) + { + const QRect spot = drawDropVisualizer( 0, 0, m_marker ); + m_marker = 0; + viewport()->repaint( spot, false ); + } +} + +void +Playlist::showTagDialog( QPtrList items ) +{ + /// the tag dialog was once modal, because we thought that damage would occur + /// when passing playlist items into the editor and it was removed from the playlist. + /// This is simply not the case, information is written to the URL, not the item. + + // Playlist::lock(); + + if( items.isEmpty() ) return; + + if ( items.count() == 1 ) + { + PlaylistItem *item = static_cast( items.first() ); + bool isDaap = item->url().protocol() == "daap"; + if ( !item->url().isLocalFile() && !isDaap ) + { + StreamEditor dialog( this, item->title(), item->url().prettyURL(), true ); + if( item->url().protocol() == "cdda" ) + dialog.setCaption( i18n( "CD Audio" ) ); + else + dialog.setCaption( i18n( "Remote Media" ) ); + dialog.exec(); + } + else if ( isDaap ) // don't check if exists + { + // The tag dialog automatically disables the widgets if the file is not local, which it is not. + TagDialog *dialog = new TagDialog( *item, item, instance() ); + dialog->show(); + } + else if ( checkFileStatus( item ) ) + { + TagDialog *dialog = new TagDialog( *item, item, instance() ); + dialog->show(); + } + else + KMessageBox::sorry( this, i18n( "This file does not exist:" ) + ' ' + item->url().path() ); + } + else { + //edit multiple tracks in tag dialog + KURL::List urls; + for( QListViewItem *item = items.first(); item; item = items.next() ) + if ( item->isVisible() ) + urls << static_cast( item )->url(); + + TagDialog *dialog = new TagDialog( urls, instance() ); + dialog->show(); + } + + // Playlist::unlock(); +} + +#include +#include +#include +#include +#include +#include +#include +#include //usleep() + +// Moved outside the only function that uses it because +// gcc 2.95 doesn't like class declarations there. + class CustomColumnDialog : public KDialog + { + public: + CustomColumnDialog( QWidget *parent ) + : KDialog( parent ) + { + QLabel *textLabel1, *textLabel2, *textLabel3; + QLineEdit *lineEdit1, *lineEdit2; + QGroupBox *groupBox1; + + textLabel1 = new QLabel( i18n( + "

    You can create a custom column that runs a shell command against each item in the playlist. " + "The shell command is run as the user nobody, this is for security reasons.\n" + "

    You can only run the command against local files for the time being. " + "The fullpath is inserted at the position %f in the string. " + "If you do not specify %f it is appended." ), this ); + textLabel2 = new QLabel( i18n( "Column &name:" ), this ); + textLabel3 = new QLabel( i18n( "&Command:" ), this ); + + lineEdit1 = new QLineEdit( this, "ColumnName" ); + lineEdit2 = new QLineEdit( this, "Command" ); + + groupBox1 = new QGroupBox( 1, Qt::Vertical, i18n( "Examples" ), this ); + groupBox1->layout()->setMargin( 11 ); + new KActiveLabel( i18n( "file --brief %f\n" "ls -sh %f\n" "basename %f\n" "dirname %f" ), groupBox1 ); + + // buddies + textLabel2->setBuddy( lineEdit1 ); + textLabel3->setBuddy( lineEdit2 ); + + // layouts + QHBoxLayout *layout1 = new QHBoxLayout( 0, 0, 6 ); + layout1->addItem( new QSpacerItem( 181, 20, QSizePolicy::Expanding, QSizePolicy::Minimum ) ); + layout1->addWidget( new KPushButton( KStdGuiItem::ok(), this, "OkButton" ) ); + layout1->addWidget( new KPushButton( KStdGuiItem::cancel(), this, "CancelButton" ) ); + + QGridLayout *layout2 = new QGridLayout( 0, 2, 2, 0, 6 ); + layout2->QLayout::add( textLabel2 ); + layout2->QLayout::add( lineEdit1 ); + layout2->QLayout::add( textLabel3 ); + layout2->QLayout::add( lineEdit2 ); + + QVBoxLayout *Form1Layout = new QVBoxLayout( this, 11, 6, "Form1Layout"); + Form1Layout->addWidget( textLabel1 ); + Form1Layout->addWidget( groupBox1 ); + Form1Layout->addLayout( layout2 ); + Form1Layout->addLayout( layout1 ); + Form1Layout->addItem( new QSpacerItem( 20, 231, QSizePolicy::Minimum, QSizePolicy::Expanding ) ); + + // properties + setCaption( i18n("Add Custom Column") ); + + // connects + connect( + child( "OkButton" ), + SIGNAL(clicked()), + SLOT(accept()) ); + connect( + child( "CancelButton" ), + SIGNAL(clicked()), + SLOT(reject()) ); + } + + QString command() { return static_cast(child("Command"))->text(); } + QString name() { return static_cast(child("ColumnName"))->text(); } + }; + +void +Playlist::addCustomColumn() +{ + CustomColumnDialog dialog( this ); + + if ( dialog.exec() == QDialog::Accepted ) { + const int index = addColumn( dialog.name(), 100 ); + QStringList args = QStringList::split( ' ', dialog.command() ); + + QStringList::Iterator pcf = args.find( "%f" ); + if ( pcf == args.end() ) { + //there is no %f, so add one on the end + //TODO prolly this is confusing, instead ask the user if we should add one + args += "%f"; + --pcf; + } + + debug() << args << endl; + + //TODO need to do it with a %u for url and %f for file + //FIXME gets stuck it seems if you submit broken commands + + //FIXME issues with the column resize stuff that cause freezing in eventFilters + + for( MyIt it( this ); *it; ++it ) { + if( (*it)->url().protocol() != "file" ) + continue; + + *pcf = (*it)->url().path(); + + debug() << args << endl; + + QProcess p( args ); + for( p.start(); p.isRunning(); /*kapp->processEvents()*/ ) + ::usleep( 5000 ); + + (*it)->setExactText( index, p.readStdout() ); + } + } +} + +#include +#include + +TagWriter::TagWriter( PlaylistItem *item, const QString &oldTag, const QString &newTag, const int col, const bool updateView ) + : ThreadManager::Job( "TagWriter" ) + , m_item( item ) + , m_failed( true ) + , m_oldTagString( oldTag ) + , m_newTagString( newTag ) + , m_tagType( col ) + , m_updateView( updateView ) +{ + Playlist::instance()->lock(); + + item->setEditing( col ); +} + +TagWriter::~TagWriter() +{ + Playlist::instance()->unlock(); +} + +bool +TagWriter::doJob() +{ + MetaBundle mb( m_item->url(), true ); + + switch ( m_tagType ) + { + case PlaylistItem::Title: + mb.setTitle( m_newTagString ); + break; + case PlaylistItem::Artist: + mb.setArtist( m_newTagString ); + break; + case PlaylistItem::Composer: + if ( !mb.hasExtendedMetaInformation() ) + return true; + mb.setComposer( m_newTagString ); + break; + case PlaylistItem::DiscNumber: + if ( !mb.hasExtendedMetaInformation() ) + return true; + mb.setDiscNumber( m_newTagString.toInt() ); + break; + case PlaylistItem::Bpm: + if ( !mb.hasExtendedMetaInformation() ) + return true; + mb.setBpm( m_newTagString.toFloat() ); + break; + case PlaylistItem::Album: + mb.setAlbum( m_newTagString ); + break; + case PlaylistItem::Year: + mb.setYear( m_newTagString.toInt() ); + break; + case PlaylistItem::Comment: + //FIXME how does this work for vorbis files? + //Are we likely to overwrite some other comments? + //Vorbis can have multiple comment fields.. + mb.setComment( m_newTagString ); + break; + case PlaylistItem::Genre: + mb.setGenre( m_newTagString ); + break; + case PlaylistItem::Track: + mb.setTrack( m_newTagString.toInt() ); + break; + + default: + return true; + } + + m_failed = !mb.save(); + return true; +} + +void +TagWriter::completeJob() +{ + switch( m_failed ) { + case true: + // we write a space for some reason I cannot recall + m_item->setExactText( m_tagType, m_oldTagString.isEmpty() ? " " : m_oldTagString ); + Amarok::StatusBar::instance()->longMessage( i18n( + "Sorry, the tag for %1 could not be changed." ).arg( m_item->url().fileName() ), KDE::StatusBar::Sorry ); + break; + + case false: + m_item->setExactText( m_tagType, m_newTagString.isEmpty() ? " " : m_newTagString ); + CollectionDB::instance()->updateURL( m_item->url().path(), m_updateView ); + + } + m_item->setIsBeingRenamed( false ); + m_item->filter( Playlist::instance()->m_filter ); + if( m_item->deleteAfterEditing() ) + { + Playlist::instance()->removeItem( m_item ); + delete m_item; + } +} + + +#include "playlist.moc" diff --git a/amarok/src/playlist.h b/amarok/src/playlist.h new file mode 100644 index 00000000..c7c1dd49 --- /dev/null +++ b/amarok/src/playlist.h @@ -0,0 +1,549 @@ +/*************************************************************************** + Playlist.h - description + ------------------- + begin : Don Dez 5 2002 + copyright : (C) 2002 by Mark Kretschmann + (C) 2005 Ian Monroe + (C) 2005 by Gábor Lehel +***************************************************************************/ + +/*************************************************************************** +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +***************************************************************************/ + +#ifndef AMAROK_PLAYLIST_H +#define AMAROK_PLAYLIST_H + +#include +#include "amarok_export.h" +#include "amarokconfig.h" +#include "amarokdcophandler.h" +#include "engineobserver.h" //baseclass +#include "dynamicmode.h" +#include "playlistwindow.h" //friend +#include "playlistitem.h" +#include "metabundle.h" +#include "tooltip.h" //baseclass +#include "tracktooltip.h" + +#include //baseclass +#include //KURL::List +#include //stack allocated +#include //stack allocated +#include //stack allocated +#include //stack allocated +#include //stack allocated + +class KAction; +class KActionCollection; +class PlaylistItem; +class PlaylistEntry; +class PlaylistLoader; +class PlaylistAlbum; +class TagWriter; +class QBoxLayout; +class QLabel; +class QTimer; + +class Medium; + +/** + * @authors Mark Kretschmann && Max Howell + * + * Playlist inherits KListView privately and thus is no longer a ListView + * Instead it is a part of PlaylistWindow and they interact in harmony. The change + * was necessary as it is too dangerous to allow public access to PlaylistItems + * due to the multi-threading environment. + * + * Unfortunately, since QObject is now inaccessible you have to connect slots + * via one of PlaylistWindow's friend members or in Playlist + * + * If you want to add new playlist type functionality you should implement it + * inside this class or inside PlaylistWindow. + * + */ + + +// template +// AtomicString Index::fieldString(const FieldType &field) { return AtomicString(field); } + +// template<> +// AtomicString Index::fieldString(const KURL &field); + + +class Playlist : private KListView, public EngineObserver, public Amarok::ToolTipClient +{ + Q_OBJECT + + public: + ~Playlist(); + + LIBAMAROK_EXPORT static Playlist *instance() { return s_instance; } + static QString defaultPlaylistPath(); + static const int NO_SORT = 200; + + static const int Append = 1; /// inserts media after the last item in the playlist + static const int Queue = 2; /// inserts media after the currentTrack + static const int Clear = 4; /// clears the playlist first + static const int Replace = Clear; + static const int DirectPlay = 8; /// start playback of the first item in the list + static const int Unique = 16; /// don't insert anything already in the playlist + static const int StartPlay = 32; /// start playback of the first item in the list if nothing else playing + static const int Colorize = 64; /// colorize newly added items + static const int DefaultOptions = Append | Unique | StartPlay; + + // it's really just the *ListView parts we want to hide... + QScrollView *qscrollview() const + { + return reinterpret_cast( const_cast( this ) ); + } + + /** Add media to the playlist + * @param options you can OR these together, see the enum + * @param sql Sql program to execute */ + LIBAMAROK_EXPORT void insertMedia( const KURL::List &, int options = Append ); + void insertMediaSql( const QString& sql, int options = Append ); + + // Dynamic mode functions + void addDynamicModeTracks( uint songCount ); + void adjustDynamicUpcoming( bool saveUndo = false ); + void adjustDynamicPrevious( uint songCount, bool saveUndo = false ); + void advanceDynamicTrack(); + void setDynamicHistory( bool enable = true ); + + void burnPlaylist ( int projectType = -1 ); + void burnSelectedTracks( int projectType = -1 ); + int currentTrackIndex( bool onlyCountVisible = true ); + bool isEmpty() const { return childCount() == 0; } + LIBAMAROK_EXPORT bool isTrackBefore() const; + LIBAMAROK_EXPORT bool isTrackAfter() const; + void restoreSession(); // called during initialisation + void setPlaylistName( const QString &name, bool proposeOverwriting = false ) { m_playlistName = name; m_proposeOverwriting = proposeOverwriting; } + void proposePlaylistName( const QString &name, bool proposeOverwriting = false ) { if( isEmpty() || m_playlistName==i18n("Untitled") ) m_playlistName = name; m_proposeOverwriting = proposeOverwriting; } + const QString &playlistName() const { return m_playlistName; } + bool proposeOverwriteOnSave() const { return m_proposeOverwriting; } + bool saveM3U( const QString&, bool relative = AmarokConfig::relativePlaylist() ) const; + void saveXML( const QString& ); + int totalTrackCount() const; + BundleList nextTracks() const; + uint repeatAlbumTrackCount() const; //returns number of tracks from same album + //as current track that are in playlist (may require Play Albums in Order on). + //If the information is not available, returns 0. + + //const so you don't change it behind Playlist's back, use modifyDynamicMode() for that + const DynamicMode *dynamicMode() const; + + //modify the returned DynamicMode, then finishedModifying() it when done + DynamicMode *modifyDynamicMode(); + + //call this every time you modifyDynamicMode(), otherwise you'll get memory leaks and/or crashes + void finishedModifying( DynamicMode *mode ); + + int stopAfterMode(); + + void addCustomMenuItem ( const QString &submenu, const QString &itemTitle ); + void customMenuClicked ( int id ); + bool removeCustomMenuItem( const QString &submenu, const QString &itemTitle ); + + void setFont( const QFont &f ) { KListView::setFont( f ); } //made public for convenience + void unsetFont() { KListView::unsetFont(); } + + PlaylistItem *firstChild() const { return static_cast( KListView::firstChild() ); } + PlaylistItem *lastItem() const { return static_cast( KListView::lastItem() ); } + PlaylistItem *currentItem() const { return static_cast( KListView::currentItem() ); } + + int numVisibleColumns() const; + QValueList visibleColumns() const; + MetaBundle::ColumnMask getVisibleColumnMask() const; + int mapToLogicalColumn( int physical ) const; // Converts physical PlaylistItem column position to logical + QString columnText( int c ) const { return KListView::columnText( c ); }; + void setColumns( QValueList order, QValueList visible ); + + /** Call this to prevent items being removed from the playlist, it is mostly for internal use only + * Don't forget to unlock() !! */ + void lock(); + void unlock(); + + //reimplemented to save columns by name instead of index, to be more resilient to reorderings and such + void saveLayout(KConfig *config, const QString &group) const; + void restoreLayout(KConfig *config, const QString &group); + + //AFT-related functions + bool checkFileStatus( PlaylistItem * item ); + void addToUniqueMap( const QString uniqueid, PlaylistItem* item ); + void removeFromUniqueMap( const QString uniqueid, PlaylistItem* item ); + + enum RequestType { Prev = -1, Current = 0, Next = 1 }; + enum StopAfterMode { DoNotStop, StopAfterCurrent, StopAfterQueue, StopAfterOther }; + + class QDragObject *dragObject(); + friend class PlaylistItem; + friend class UrlLoader; + friend class QueueManager; + friend class QueueLabel; + friend class PlaylistWindow; + friend class ColumnList; + friend void Amarok::DcopPlaylistHandler::removeCurrentTrack(); //calls removeItem() and currentTrack() + friend void Amarok::DcopPlaylistHandler::removeByIndex( int ); //calls removeItem() + friend class TagWriter; //calls removeItem() + friend void PlaylistWindow::init(); //setting up connections etc. + friend TrackToolTip::TrackToolTip(); + friend bool PlaylistWindow::eventFilter( QObject*, QEvent* ); //for convenience we handle some playlist events here + + public: + QPair toolTipText( QWidget*, const QPoint &pos ) const; + + signals: + void aboutToClear(); + void itemCountChanged( int newCount, int newLength, int visCount, int visLength, int selCount, int selLength ); + void queueChanged( const PLItemList &queued, const PLItemList &dequeued ); + void columnsChanged(); + void dynamicModeChanged( const DynamicMode *newMode ); + + public slots: + void activateByIndex(int); + void addCustomColumn(); + void appendMedia( const KURL &url ); + void appendMedia( const QString &path ); + void clear(); + void copyToClipboard( const QListViewItem* = 0 ) const; + void deleteSelectedFiles(); + void ensureItemCentered( QListViewItem* item ); + void playCurrentTrack(); + void playNextTrack( const bool forceNext = true ); + void playPrevTrack(); + void queueSelected(); + void setSelectedRatings( int rating ); + void redo(); + void removeDuplicates(); + void removeSelectedItems(); + void setDynamicMode( DynamicMode *mode ); + void loadDynamicMode( DynamicMode *mode ); //saveUndoState() + setDynamicMode() + void disableDynamicMode(); + void editActiveDynamicMode(); + void rebuildDynamicModeCache(); + void repopulate(); + void safeClear(); + void scoreChanged( const QString &path, float score ); + void ratingChanged( const QString &path, int rating ); + void fileMoved( const QString &srcPath, const QString &dstPath ); + void selectAll() { QListView::selectAll( true ); } + void setFilter( const QString &filter ); + void setFilterSlot( const QString &filter ); //uses a delay where applicable + void setStopAfterCurrent( bool on ); + void setStopAfterItem( PlaylistItem *item ); + void toggleStopAfterCurrentItem(); + void toggleStopAfterCurrentTrack(); + void setStopAfterMode( int mode ); + void showCurrentTrack() { ensureItemCentered( m_currentTrack ); } + void showQueueManager(); + void changeFromQueueManager(QPtrList list); + void shuffle(); + void undo(); + void updateMetaData( const MetaBundle& ); + void adjustColumn( int n ); + void updateEntriesUrl( const QString &oldUrl, const QString &newUrl, const QString &uniqueid ); + void updateEntriesUniqueId( const QString &url, const QString &oldid, const QString &newid ); + void updateEntriesStatusDeleted( const QString &absPath, const QString &uniqueid ); + void updateEntriesStatusAdded( const QString &absPath, const QString &uniqueid ); + void updateEntriesStatusAdded( const QMap &map ); + + protected: + virtual void fontChange( const QFont &old ); + + protected slots: + void contentsMouseMoveEvent( QMouseEvent *e = 0 ); + void leaveEvent( QEvent *e ); + void contentsMousePressEvent( QMouseEvent *e ); + void contentsWheelEvent( QWheelEvent *e ); + + private slots: + void mediumChange( int ); + void slotCountChanged(); + void activate( QListViewItem* ); + void columnOrderChanged(); + void columnResizeEvent( int, int, int ); + void doubleClicked( QListViewItem* ); + + void generateInfo(); //generates info for Random Albums + + /* the only difference multi makes is whether it emits queueChanged(). (if multi, then no) + if you're queue()ing many items, consider passing true and emitting queueChanged() yourself. */ + /* if invertQueue then queueing an already queued song dequeues it */ + void queue( QListViewItem*, bool multi = false, bool invertQueue = true ); + + void saveUndoState(); + void setDelayedFilter(); //after the delay is over + void showContextMenu( QListViewItem*, const QPoint&, int ); + void slotEraseMarker(); + void slotGlowTimer(); + void reallyEnsureItemCentered(); + void slotMouseButtonPressed( int, QListViewItem*, const QPoint&, int ); + void slotSingleClick(); + void slotContentsMoving(); + void slotRepeatTrackToggled( int mode ); + void slotQueueChanged( const PLItemList &in, const PLItemList &out); + void slotUseScores( bool use ); + void slotUseRatings( bool use ); + void slotMoodbarPrefs( bool show, bool moodier, int alter, bool withMusic ); + void updateNextPrev(); + void writeTag( QListViewItem*, const QString&, int ); + + private: + Playlist( QWidget* ); + Playlist( const Playlist& ); //not defined + + LIBAMAROK_EXPORT static Playlist *s_instance; + + void countChanged(); + + PlaylistItem *currentTrack() const { return m_currentTrack; } + PlaylistItem *restoreCurrentTrack(); + + void insertMediaInternal( const KURL::List&, PlaylistItem*, int options = 0 ); + bool isAdvancedQuery( const QString &query ); + void refreshNextTracks( int = -1 ); + void removeItem( PlaylistItem*, bool = false ); + bool saveState( QStringList& ); + void setCurrentTrack( PlaylistItem* ); + void setCurrentTrackPixmap( int state = -1 ); + void showTagDialog( QPtrList items ); + void sortQueuedItems(); + void switchState( QStringList&, QStringList& ); + void saveSelectedAsPlaylist(); + void initStarPixmaps(); + + //engine observer functions + void engineNewMetaData( const MetaBundle&, bool ); + void engineStateChanged( Engine::State, Engine::State = Engine::Empty ); + + /// KListView Overloaded functions + void contentsDropEvent ( QDropEvent* ); + void contentsDragEnterEvent( QDragEnterEvent* ); + void contentsDragMoveEvent ( QDragMoveEvent* ); + void contentsDragLeaveEvent( QDragLeaveEvent* ); + + #ifdef PURIST //KListView imposes hand cursor so override it + void contentsMouseMoveEvent( QMouseEvent *e ) { QListView::contentsMouseMoveEvent( e ); } + #endif + + void customEvent( QCustomEvent* ); + bool eventFilter( QObject*, QEvent* ); + void paletteChange( const QPalette& ); + void rename( QListViewItem*, int ); + void setColumnWidth( int, int ); + void setSorting( int, bool = true ); + + void viewportPaintEvent( QPaintEvent* ); + void viewportResizeEvent( QResizeEvent* ); + + void appendToPreviousTracks( PlaylistItem *item ); + void appendToPreviousAlbums( PlaylistAlbum *album ); + void removeFromPreviousTracks( PlaylistItem *item = 0 ); + void removeFromPreviousAlbums( PlaylistAlbum *album = 0 ); + + typedef QMap AlbumMap; + typedef QMap ArtistAlbumMap; + ArtistAlbumMap m_albums; + uint m_startupTime_t; //QDateTime::currentDateTime().toTime_t as of startup + uint m_oldestTime_t; //the createdate of the oldest song in the collection + + + /// ATTRIBUTES + + PlaylistItem *m_currentTrack; //the track that is playing + QListViewItem *m_marker; //track that has the drag/drop marker under it + PlaylistItem *m_hoveredRating; //if the mouse is hovering over the rating of an item + + //NOTE these container types were carefully chosen + QPtrList m_prevAlbums; //the previously played albums in Entire Albums mode + PLItemList m_prevTracks; //the previous history + PLItemList m_nextTracks; //the tracks to be played after the current track + + QString m_filter; + QString m_prevfilter; + QTimer *m_filtertimer; + + PLItemList m_itemsToChangeTagsFor; + + bool m_smartResizing; + + int m_firstColumn; + int m_totalCount; + int m_totalLength; + int m_selCount; + int m_selLength; + int m_visCount; + int m_visLength; + Q_INT64 m_total; //for Favor Tracks + bool m_itemCountDirty; + + KAction *m_undoButton; + KAction *m_redoButton; + KAction *m_clearButton; + + QDir m_undoDir; + QStringList m_undoList; + QStringList m_redoList; + uint m_undoCounter; + + DynamicMode *m_dynamicMode; + KURL::List m_queueList; + PlaylistItem *m_stopAfterTrack; + int m_stopAfterMode; + bool m_showHelp; + bool m_dynamicDirt; //So we don't call advanceDynamicTrack() on activate() + bool m_queueDirt; //When queuing disabled items, we need to place the marker on the newly inserted item + bool m_undoDirt; //Make sure we don't repopulate the playlist when dynamic mode and undo() + int m_insertFromADT; //Don't automatically start playing if a user hits Next in dynamic mode when not already playing + static QMutex *s_dynamicADTMutex; + + QListViewItem *m_itemToReallyCenter; + QListViewItem *m_renameItem; + int m_renameColumn; + QTimer *m_clicktimer; + QListViewItem *m_itemToRename; + QPoint m_clickPos; + int m_columnToRename; + + QMap m_customSubmenuItem; + QMap m_customIdItem; + + bool isLocked() const { return m_lockStack > 0; } + + /// stack counter for PLaylist::lock() and unlock() + int m_lockStack; + + QString m_editOldTag; //text before inline editing ( the new tag is written only if it's changed ) + + std::vector m_columnFraction; + + QMap*> m_uniqueMap; + int m_oldRandom; + int m_oldRepeat; + + QString m_playlistName; + bool m_proposeOverwriting; + + // indexing stuff + // An index of playlist items by some field. The index is backed by AtomicStrings, to avoid + // duplication thread-safely. + template + class Index : private QMap > + { + public: + // constructors take the PlaylistItem getter to index by + Index( FieldType (PlaylistItem::*getter)( ) const) + : m_getter( getter ), m_useGetter( true ) { }; + Index( const FieldType &(PlaylistItem::*refGetter)() const) + : m_refGetter( refGetter ), m_useGetter( false ) { }; + + // we specialize this method, below, for KURLs + AtomicString fieldString( const FieldType &field) { return AtomicString( field ); } + + AtomicString keyOf( const PlaylistItem &item) { + return m_useGetter ? fieldString( ( item.*m_getter ) () ) + : fieldString( ( item.*m_refGetter ) () ); + } + + bool contains( const FieldType &key ) { return contains( fieldString( key ) ); } + + // Just first match, or NULL + PlaylistItem *getFirst( const FieldType &field ) { + Iterator it = find( fieldString( field ) ); + return it == end() || it.data().isEmpty() ? 0 : it.data().getFirst(); + } + + void add( PlaylistItem *item ) { + QPtrList &row = operator[]( keyOf( *item ) ); // adds one if needed + if ( !row.containsRef(item) ) row.append( item ); + } + + void remove( PlaylistItem *item ) { + Iterator it = find( keyOf( *item ) ); + if (it != end()) { + while ( it.data().removeRef( item ) ) { }; + if ( it.data().isEmpty() ) erase( it ); + } + } + + private: + FieldType (PlaylistItem::*m_getter) () const; + const FieldType &(PlaylistItem::*m_refGetter) () const; + bool m_useGetter; // because a valid *member can be zero in C++ + }; + + Index m_urlIndex; + // TODO: we can convert m_unique to this, to remove some code and for uniformity and thread + // safety + // TODO: we should just store the url() as AtomicString, it will save headaches (e.g. at least a + // crash with multicore enabled traces back to KURL refcounting) + //Index m_uniqueIndex; +}; + +class PlaylistAlbum +{ +public: + PLItemList tracks; + int refcount; + Q_INT64 total; //for Favor Tracks + PlaylistAlbum(): refcount( 0 ), total( 0 ) { } +}; + +/** + * Iterator class that only edits visible items! Preferentially always use + * this! Invisible items should not be operated on! To iterate over all + * items use MyIt::All as the flags parameter. MyIt::All cannot be OR'd, + * sorry. + */ + +class PlaylistIterator : public QListViewItemIterator +{ +public: + PlaylistIterator( QListViewItem *item, int flags = 0 ) + //QListViewItemIterator is not great and doesn't allow you to see everything if you + //mask both Visible and Invisible :( instead just visible items are returned + : QListViewItemIterator( item, flags == All ? 0 : flags | Visible ) + {} + + PlaylistIterator( QListView *view, int flags = 0 ) + : QListViewItemIterator( view, flags == All ? 0 : flags | Visible ) + {} + + //FIXME! Dirty hack for enabled/disabled items. + enum IteratorFlag { + Visible = QListViewItemIterator::Visible, + All = QListViewItemIterator::Invisible + }; + + inline PlaylistItem *operator*() { return static_cast( QListViewItemIterator::operator*() ); } + + /// @return the next visible PlaylistItem after item + static PlaylistItem *nextVisible( PlaylistItem *item ) + { + PlaylistIterator it( item ); + return (*it == item) ? *static_cast(++it) : *it; + } + + static PlaylistItem *prevVisible( PlaylistItem *item ) + { + PlaylistIterator it( item ); + return (*it == item) ? *static_cast(--it) : *it; + } + +}; + +// Specialization of Index::fieldString for URLs +template<> +inline AtomicString Playlist::Index::fieldString( const KURL &url ) +{ + return AtomicString( url.url() ); +} + +#endif //AMAROK_PLAYLIST_H + diff --git a/amarok/src/playlistbrowser.cpp b/amarok/src/playlistbrowser.cpp new file mode 100644 index 00000000..35ddfecd --- /dev/null +++ b/amarok/src/playlistbrowser.cpp @@ -0,0 +1,3250 @@ +/*************************************************************************** + * copyright : (c) 2004 Pierpaolo Di Panfilo * + * (c) 2004 Mark Kretschmann * + * (c) 2005-2006 Seb Ruiz * + * (c) 2005 Gábor Lehel * + * (c) 2005 Christian Muehlhaeuser * + * (c) 2006 Alexandre Oliveira * + * (c) 2006 Adam Pigg * + * See COPYING file for licensing information * + ***************************************************************************/ + +#define DEBUG_PREFIX "PlaylistBrowser" + +#include "amarok.h" //actionCollection() +#include "browserToolBar.h" +#include "collectiondb.h" //smart playlists +#include "debug.h" +#include "htmlview.h" +#include "k3bexporter.h" +#include "mediabrowser.h" +#include "dynamicmode.h" +#include "lastfm.h" +#include "playlist.h" +#include "playlistbrowser.h" +#include "playlistbrowseritem.h" +#include "playlistselection.h" +#include "podcastbundle.h" +#include "podcastsettings.h" +#include "scancontroller.h" +#include "smartplaylisteditor.h" +#include "tagdialog.h" //showContextMenu() +#include "threadmanager.h" +#include "statusbar.h" +#include "contextbrowser.h" +#include "xspfplaylist.h" + +#include //customEvent() +#include //mousePressed() +#include +#include //paintCell() +#include //paintCell() +#include //loadPlaylists(), saveM3U(), savePLS() + +#include +#include +#include +#include +#include //openPlaylist() +#include //deleteSelectedPlaylists() +#include //smallIcon +#include +#include //rename() +#include +#include //renamePlaylist(), deleteSelectedPlaylist() +#include +#include //dragObject() +#include +#include +#include //KGlobal::dirs() +#include //dragObject() + +#include //rename() in renamePlaylist() + + + +namespace Amarok { + QListViewItem* + findItemByPath( QListView *view, QString name ) + { + const static QString escaped( "\\/" ); + const static QChar sep( '/' ); + + debug() << "Searching " << name << endl; + QStringList path = splitPath( name ); + + QListViewItem *prox = view->firstChild(); + QListViewItem *item = 0; + + foreach( path ) { + item = prox; + QString text( *it ); + text.replace( escaped, sep ); + + for ( ; item; item = item->nextSibling() ) { + if ( text == item->text(0) ) { + break; + } + } + + if ( !item ) + return 0; + prox = item->firstChild(); + } + return item; + } + + QStringList + splitPath( QString path ) { + QStringList list; + + const static QChar sep( '/' ); + int bOffset = 0, sOffset = 0; + + int pos = path.find( sep, bOffset ); + + while ( pos != -1 ) { + if ( pos > sOffset && pos <= (int)path.length() ) { + if ( pos > 0 && path[pos-1] != '\\' ) { + list << path.mid( sOffset, pos - sOffset ); + sOffset = pos + 1; + } + } + bOffset = pos + 1; + pos = path.find( sep, bOffset ); + } + + int length = path.length() - 1; + if ( path.mid( sOffset, length - sOffset + 1 ).length() > 0 ) + list << path.mid( sOffset, length - sOffset + 1 ); + + return list; + } +} + + +inline QString +fileExtension( const QString &fileName ) +{ + return Amarok::extension( fileName ); +} + +PlaylistBrowser *PlaylistBrowser::s_instance = 0; + + +PlaylistBrowser::PlaylistBrowser( const char *name ) + : QVBox( 0, name ) + , m_polished( false ) + , m_playlistCategory( 0 ) + , m_streamsCategory( 0 ) + , m_smartCategory( 0 ) + , m_dynamicCategory( 0 ) + , m_podcastCategory( 0 ) + , m_coolStreams( 0 ) + , m_smartDefaults( 0 ) + , m_lastfmCategory( 0 ) + , m_shoutcastCategory( 0 ) + , m_lastPlaylist( 0 ) + , m_coolStreamsOpen( false ) + , m_smartDefaultsOpen( false ) + , m_lastfmOpen( false ) + , m_ac( new KActionCollection( this ) ) + , m_podcastTimer( new QTimer( this ) ) + + +{ + s_instance = this; + + QVBox *browserBox = new QVBox( this ); + browserBox->setSpacing( 3 ); + + // + addMenuButton = new KActionMenu( i18n("Add"), Amarok::icon( "add_playlist" ), m_ac ); + addMenuButton->setDelayed( false ); + + KPopupMenu *playlistMenu = new KPopupMenu( this ); + playlistMenu->insertItem( i18n("New..."), PLAYLIST ); + playlistMenu->insertItem( i18n("Import Existing..."), PLAYLIST_IMPORT ); + connect( playlistMenu, SIGNAL( activated(int) ), SLOT( slotAddPlaylistMenu(int) ) ); + + KPopupMenu *addMenu = addMenuButton->popupMenu(); + addMenu->insertItem( i18n("Playlist"), playlistMenu ); + addMenu->insertItem( i18n("Smart Playlist..."), SMARTPLAYLIST ); + addMenu->insertItem( i18n("Dynamic Playlist..."), ADDDYNAMIC); + addMenu->insertItem( i18n("Radio Stream..."), STREAM ); + addMenu->insertItem( i18n("Podcast..."), PODCAST ); + connect( addMenu, SIGNAL( activated(int) ), SLOT( slotAddMenu(int) ) ); + + renameButton = new KAction( i18n("Rename"), "editclear", 0, this, SLOT( renameSelectedItem() ), m_ac ); + removeButton = new KAction( i18n("Delete"), Amarok::icon( "remove" ), 0, this, SLOT( removeSelectedItems() ), m_ac ); + + m_toolbar = new Browser::ToolBar( browserBox ); + m_toolbar->setIconText( KToolBar::IconTextRight, false ); //we want the open button to have text on right + addMenuButton->plug( m_toolbar ); + + m_toolbar->setIconText( KToolBar::IconOnly, false ); //default appearance + m_toolbar->insertLineSeparator(); + renameButton->plug( m_toolbar); + removeButton->plug( m_toolbar ); + + renameButton->setEnabled( false ); + removeButton->setEnabled( false ); + // + + m_splitter = new QSplitter( Qt::Vertical, browserBox ); + m_splitter->setChildrenCollapsible( false ); // hiding the InfoPane entirely can only be confusing + + m_listview = new PlaylistBrowserView( m_splitter ); + + int sort = Amarok::config( "PlaylistBrowser" )->readNumEntry( "Sorting", Qt::Ascending ); + m_listview->setSorting( 0, sort == Qt::Ascending ? true : false ); + + m_podcastTimerInterval = Amarok::config( "PlaylistBrowser" )->readNumEntry( "Podcast Interval", 14400000 ); + connect( m_podcastTimer, SIGNAL(timeout()), this, SLOT(scanPodcasts()) ); + + // signals and slots connections + connect( m_listview, SIGNAL( contextMenuRequested( QListViewItem *, const QPoint &, int ) ), + this, SLOT( showContextMenu( QListViewItem *, const QPoint &, int ) ) ); + connect( m_listview, SIGNAL( doubleClicked( QListViewItem *, const QPoint &, int ) ), + this, SLOT( invokeItem( QListViewItem *, const QPoint &, int ) ) ); + connect( m_listview, SIGNAL( itemRenamed( QListViewItem*, const QString&, int ) ), + this, SLOT( renamePlaylist( QListViewItem*, const QString&, int ) ) ); + connect( m_listview, SIGNAL( currentChanged( QListViewItem * ) ), + this, SLOT( currentItemChanged( QListViewItem * ) ) ); + connect( CollectionDB::instance(), SIGNAL( scanDone( bool ) ), SLOT( collectionScanDone() ) ); + + setMinimumWidth( m_toolbar->sizeHint().width() ); + + m_infoPane = new InfoPane( m_splitter ); + + m_podcastCategory = loadPodcasts(); + + setSpacing( 4 ); + setFocusProxy( m_listview ); +} + + +void +PlaylistBrowser::polish() +{ + // we make startup faster by doing the slow bits for this + // only when we are shown on screen + + DEBUG_FUNC_INFO + + Amarok::OverrideCursor cursor; + +// blockSignals( true ); +// BrowserBar::instance()->restoreWidth(); +// blockSignals( false ); + + QVBox::polish(); + + /// Podcasting is always initialised in the ctor because of autoscanning + + m_polished = true; + + m_playlistCategory = loadPlaylists(); + if( !CollectionDB::instance()->isEmpty() ) + { + m_smartCategory = loadSmartPlaylists(); + loadDefaultSmartPlaylists(); + } +#define config Amarok::config( "PlaylistBrowser" ) + + m_dynamicCategory = loadDynamics(); + m_randomDynamic = new DynamicEntry( m_dynamicCategory, 0, i18n("Random Mix") ); + m_randomDynamic->setKept( false ); //don't save it + m_randomDynamic->setCycleTracks( config->readBoolEntry( "Dynamic Random Remove Played", true ) ); + m_randomDynamic->setUpcomingCount( config->readNumEntry ( "Dynamic Random Upcoming Count", 15 ) ); + m_randomDynamic->setPreviousCount( config->readNumEntry ( "Dynamic Random Previous Count", 5 ) ); + + m_suggestedDynamic = new DynamicEntry( m_dynamicCategory, m_randomDynamic, i18n("Suggested Songs" ) ); + m_suggestedDynamic->setKept( false ); //don't save it + m_suggestedDynamic->setAppendType( DynamicMode::SUGGESTION ); + m_suggestedDynamic->setCycleTracks( config->readBoolEntry( "Dynamic Suggest Remove Played", true ) ); + m_suggestedDynamic->setUpcomingCount( config->readNumEntry ( "Dynamic Suggest Upcoming Count", 15 ) ); + m_suggestedDynamic->setPreviousCount( config->readNumEntry ( "Dynamic Suggest Previous Count", 5 ) ); + +#undef config + + m_streamsCategory = loadStreams(); + loadCoolStreams(); + m_shoutcastCategory = new ShoutcastBrowser( m_streamsCategory ); + + if( !AmarokConfig::scrobblerUsername().isEmpty() ) + { + const bool subscriber = Amarok::config( "Scrobbler" )->readBoolEntry( "Subscriber", false ); + loadLastfmStreams( subscriber ); + } + + markDynamicEntries(); + + // ListView item state restoration: + // First we check if the number of items in the listview is the same as it was on last + // application exit. If true, we iterate over all items and restore their open/closed state. + // Note: We ignore podcast items, because they are added dynamically added to the ListView. + QValueList stateList = Amarok::config( "PlaylistBrowser" )->readIntListEntry( "Item State" ); + QListViewItemIterator it( m_listview ); + uint count = 0; + while ( it.current() ) { + if( !isPodcastEpisode( it.current() ) ) + ++count; + ++it; + } + + if ( count == stateList.count() ) { + uint index = 0; + it = QListViewItemIterator( m_listview ); + while ( it.current() ) { + if( !isPodcastEpisode( it.current() ) ) { + it.current()->setOpen( stateList[index] ); + ++index; + } + ++it; + } + } + + // Set height of InfoPane + m_infoPane->setStoredHeight( Amarok::config( "PlaylistBrowser" )->readNumEntry( "InfoPane Height", 200 ) ); +} + + +PlaylistBrowser::~PlaylistBrowser() +{ + DEBUG_BLOCK + + s_instance = 0; + + if( m_polished ) + { + savePlaylists(); + saveSmartPlaylists(); + saveDynamics(); + saveStreams(); + saveLastFm(); + + savePodcastFolderStates( m_podcastCategory ); + + QStringList list; + for( uint i=0; i < m_dynamicEntries.count(); i++ ) + { + QListViewItem *item = m_dynamicEntries.at( i ); + list.append( item->text(0) ); + } + + Amarok::config( "PlaylistBrowser" )->writeEntry( "Sorting", m_listview->sortOrder() ); + Amarok::config( "PlaylistBrowser" )->writeEntry( "Podcast Interval", m_podcastTimerInterval ); + Amarok::config( "PlaylistBrowser" )->writeEntry( "Podcast Folder Open", m_podcastCategory->isOpen() ); + Amarok::config( "PlaylistBrowser" )->writeEntry( "InfoPane Height", m_infoPane->getHeight() ); + } +} + + +void +PlaylistBrowser::setInfo( const QString &title, const QString &info ) +{ + m_infoPane->setInfo( title, info ); +} + +void +PlaylistBrowser::resizeEvent( QResizeEvent * ) +{ + if( static_cast( m_infoPane->child( "container" ) )->isShown() ) + m_infoPane->setMaximumHeight( ( int )( m_splitter->height() / 1.5 ) ); +} + + +void +PlaylistBrowser::markDynamicEntries() +{ + if( Amarok::dynamicMode() ) + { + QStringList playlists = Amarok::dynamicMode()->items(); + + for( uint i=0; i < playlists.count(); i++ ) + { + PlaylistBrowserEntry *item = dynamic_cast( Amarok::findItemByPath( m_listview, playlists[i] ) ); + + if( item ) + { + m_dynamicEntries.append( item ); + if( item->rtti() == PlaylistEntry::RTTI ) + static_cast( item )->setDynamic( true ); + if( item->rtti() == SmartPlaylist::RTTI ) + static_cast( item )->setDynamic( true ); + } + } + } +} + +/** + ************************************************************************* + * STREAMS + ************************************************************************* + **/ + +QString PlaylistBrowser::streamBrowserCache() const +{ + return Amarok::saveLocation() + "streambrowser_save.xml"; +} + + +PlaylistCategory* PlaylistBrowser::loadStreams() +{ + QFile file( streamBrowserCache() ); + + QTextStream stream( &file ); + stream.setEncoding( QTextStream::UnicodeUTF8 ); + + QDomDocument d; + QDomElement e; + + QListViewItem *after = m_dynamicCategory; + + if( !file.open( IO_ReadOnly ) || !d.setContent( stream.read() ) ) + { /*Couldn't open the file or it had invalid content, so let's create an empty element*/ + return new PlaylistCategory( m_listview, after , i18n("Radio Streams") ); + } + else { + e = d.namedItem( "category" ).toElement(); + if ( e.attribute("formatversion") =="1.1" ) { + PlaylistCategory* p = new PlaylistCategory( m_listview, after, e ); + p->setText(0, i18n("Radio Streams") ); + return p; + } + else { // Old unversioned format + PlaylistCategory* p = new PlaylistCategory( m_listview, after, i18n("Radio Streams") ); + QListViewItem *last = 0; + QDomNode n = d.namedItem( "streambrowser" ).namedItem("stream"); + for( ; !n.isNull(); n = n.nextSibling() ) { + last = new StreamEntry( p, last, n.toElement() ); + } + return p; + } + } +} + +void PlaylistBrowser::loadCoolStreams() +{ + QFile file( locate( "data","amarok/data/Cool-Streams.xml" ) ); + if( !file.open( IO_ReadOnly ) ) + return; + + QTextStream stream( &file ); + stream.setEncoding( QTextStream::UnicodeUTF8 ); + + QDomDocument d; + + if( !d.setContent( stream.read() ) ) + { + error() << "Bad Cool Streams XML file" << endl; + return; + } + + m_coolStreams = new PlaylistCategory( m_streamsCategory, 0, i18n("Cool-Streams") ); + m_coolStreams->setOpen( m_coolStreamsOpen ); + m_coolStreams->setKept( false ); + StreamEntry *last = 0; + + QDomNode n = d.namedItem( "coolstreams" ).firstChild(); + + for( ; !n.isNull(); n = n.nextSibling() ) + { + QDomElement e = n.toElement(); + QString name = e.attribute( "name" ); + e = n.namedItem( "url" ).toElement(); + KURL url( e.text() ); + last = new StreamEntry( m_coolStreams, last, url, name ); + last->setKept( false ); + } +} + +void PlaylistBrowser::addStream( QListViewItem *parent ) +{ + StreamEditor dialog( this, i18n( "Radio Stream" ), QString::null ); + dialog.setCaption( i18n( "Add Radio Stream" ) ); + + if( !parent ) parent = static_cast(m_streamsCategory); + + if( dialog.exec() == QDialog::Accepted ) + { + new StreamEntry( parent, 0, dialog.url(), dialog.name() ); + parent->sortChildItems( 0, true ); + parent->setOpen( true ); + + saveStreams(); + } +} + + +void PlaylistBrowser::editStreamURL( StreamEntry *item, const bool readonly ) +{ + StreamEditor dialog( this, item->title(), item->url().prettyURL(), readonly ); + dialog.setCaption( readonly ? i18n( "Radio Stream" ) : i18n( "Edit Radio Stream" ) ); + + if( dialog.exec() == QDialog::Accepted ) + { + item->setTitle( dialog.name() ); + item->setURL( dialog.url() ); + item->setText(0, dialog.name() ); + } +} + +void PlaylistBrowser::saveStreams() +{ + QFile file( streamBrowserCache() ); + + QDomDocument doc; + QDomElement streamB = m_streamsCategory->xml(); + streamB.setAttribute( "product", "Amarok" ); + streamB.setAttribute( "version", APP_VERSION ); + streamB.setAttribute( "formatversion", "1.1" ); + QDomNode streamsNode = doc.importNode( streamB, true ); + doc.appendChild( streamsNode ); + + QString temp( doc.toString() ); + + // Only open the file after all data is ready. If it crashes, data is not lost! + if ( !file.open( IO_WriteOnly ) ) return; + + QTextStream stream( &file ); + stream.setEncoding( QTextStream::UnicodeUTF8 ); + stream << "\n"; + stream << temp; +} + +/** + ************************************************************************* + * LAST.FM + ************************************************************************* + **/ + +void PlaylistBrowser::loadLastfmStreams( const bool subscriber /*false*/ ) +{ + QFile file( Amarok::saveLocation() + "lastfmbrowser_save.xml" ); + + QTextStream stream( &file ); + stream.setEncoding( QTextStream::UnicodeUTF8 ); + + QDomDocument d; + QDomElement e; + + QListViewItem *after = m_streamsCategory; + + if( !file.open( IO_ReadOnly ) || !d.setContent( stream.read() ) ) + { /*Couldn't open the file or it had invalid content, so let's create an empty element*/ + m_lastfmCategory = new PlaylistCategory( m_listview, after , i18n("Last.fm Radio") ); + } + else { + e = d.namedItem( "category" ).toElement(); + m_lastfmCategory = new PlaylistCategory( m_listview, after, e ); + m_lastfmCategory->setText( 0, i18n("Last.fm Radio") ); + } + + /// Load the default items + + QStringList globaltags; + globaltags << "Alternative" << "Ambient" << "Chill Out" << "Classical" << "Dance" + << "Electronica" << "Favorites" << "Heavy Metal" << "Hip Hop" << "Indie Rock" + << "Industrial" << "Japanese" << "Pop" << "Psytrance" << "Rap" << "Rock" + << "Soundtrack" << "Techno" << "Trance"; + + PlaylistCategory *tagsFolder = new PlaylistCategory( m_lastfmCategory, 0, i18n("Global Tags") ); + tagsFolder->setKept( false ); + LastFmEntry *last = 0; + + foreach( globaltags ) + { + const KURL url( "lastfm://globaltags/" + *it ); + last = new LastFmEntry( tagsFolder, last, url, *it ); + last->setKept( false ); + } + + QString user = AmarokConfig::scrobblerUsername(); + KURL url( QString("lastfm://user/%1/neighbours").arg( user ) ); + last = new LastFmEntry( m_lastfmCategory, tagsFolder, url, i18n( "Neighbor Radio" ) ); + last->setKept( false ); + + url = KURL::fromPathOrURL( QString("lastfm://user/%1/recommended/100").arg( user ) ); + last = new LastFmEntry( m_lastfmCategory, last, url, i18n( "Recommended Radio" ) ); + last->setKept( false ); + + if( subscriber ) + { + url = KURL::fromPathOrURL( QString("lastfm://user/%1/personal").arg( user ) ); + last = new LastFmEntry( m_lastfmCategory, last, url, i18n( "Personal Radio" ) ); + last->setKept( false ); + + url = KURL::fromPathOrURL( QString("lastfm://user/%1/loved").arg( user ) ); + last = new LastFmEntry( m_lastfmCategory, last, url, i18n( "Loved Radio" ) ); + last->setKept( false ); + } +} + +void PlaylistBrowser::addLastFmRadio( QListViewItem *parent ) +{ + StreamEditor dialog( this, i18n( "Last.fm Radio" ), QString::null ); + dialog.setCaption( i18n( "Add Last.fm Radio" ) ); + + if( !parent ) parent = static_cast(m_lastfmCategory); + + if( dialog.exec() == QDialog::Accepted ) + { + new LastFmEntry( parent, 0, dialog.url(), dialog.name() ); + parent->sortChildItems( 0, true ); + parent->setOpen( true ); + saveLastFm(); + } +} + +void PlaylistBrowser::addLastFmCustomRadio( QListViewItem *parent ) +{ + QString token = LastFm::Controller::createCustomStation(); + if( token.isEmpty() ) return; + token.replace( "/", "%252" ); + + const QString text = "lastfm://artistnames/" + token; + const KURL url( text ); + + QString name = LastFm::Controller::stationDescription( text ); + name.replace( "%252", "/" ); + new LastFmEntry( parent, 0, url, name ); + saveLastFm(); +} + +void PlaylistBrowser::saveLastFm() +{ + if ( !m_lastfmCategory ) + return; + + QFile file( Amarok::saveLocation() + "lastfmbrowser_save.xml" ); + + QDomDocument doc; + QDomElement lastfmB = m_lastfmCategory->xml(); + lastfmB.setAttribute( "product", "Amarok" ); + lastfmB.setAttribute( "version", APP_VERSION ); + lastfmB.setAttribute( "formatversion", "1.1" ); + QDomNode lastfmNode = doc.importNode( lastfmB, true ); + doc.appendChild( lastfmNode ); + + QString temp( doc.toString() ); + + // Only open the file after all data is ready. If it crashes, data is not lost! + if ( !file.open( IO_WriteOnly ) ) return; + + QTextStream stream( &file ); + stream.setEncoding( QTextStream::UnicodeUTF8 ); + stream << "\n"; + stream << temp; +} + + +/** + ************************************************************************* + * SMART-PLAYLISTS + ************************************************************************* + **/ + +QString PlaylistBrowser::smartplaylistBrowserCache() const +{ + return Amarok::saveLocation() + "smartplaylistbrowser_save.xml"; +} + +void PlaylistBrowser::addSmartPlaylist( QListViewItem *parent ) //SLOT +{ + if( CollectionDB::instance()->isEmpty() || !m_smartCategory ) + return; + + if( !parent ) parent = static_cast(m_smartCategory); + + + SmartPlaylistEditor dialog( i18n("Untitled"), this ); + if( dialog.exec() == QDialog::Accepted ) { + + PlaylistCategory *category = dynamic_cast(parent); + for( QListViewItem *item = category->firstChild(); item; item = item->nextSibling() ) { + SmartPlaylist *sp = dynamic_cast(item); + if ( sp && sp->title() == dialog.name() ) { + if( KMessageBox::warningContinueCancel( + PlaylistWindow::self(), + i18n( "A Smart Playlist named \"%1\" already exists. Do you want to overwrite it?" ).arg( dialog.name() ), + i18n( "Overwrite Playlist?" ), i18n( "Overwrite" ) ) == KMessageBox::Continue ) + { + delete item; + break; + } + else + return; + } + } + new SmartPlaylist( parent, 0, dialog.result() ); + parent->sortChildItems( 0, true ); + parent->setOpen( true ); + + saveSmartPlaylists(); + } +} + +PlaylistCategory* PlaylistBrowser::loadSmartPlaylists() +{ + + QFile file( smartplaylistBrowserCache() ); + QTextStream stream( &file ); + stream.setEncoding( QTextStream::UnicodeUTF8 ); + QListViewItem *after = m_playlistCategory; + + QDomDocument d; + QDomElement e; + + if( !file.open( IO_ReadOnly ) || !d.setContent( stream.read() ) ) + { /*Couldn't open the file or it had invalid content, so let's create an empty element*/ + return new PlaylistCategory(m_listview, after, i18n("Smart Playlists") ); + } + else { + e = d.namedItem( "category" ).toElement(); + QString version = e.attribute("formatversion"); + float fversion = e.attribute("formatversion").toFloat(); + if ( version == "1.8" ) + { + PlaylistCategory* p = new PlaylistCategory(m_listview, after, e ); + p->setText( 0, i18n("Smart Playlists") ); + return p; + } + else if ( fversion > 1.0f ) + { + PlaylistCategory* p = new PlaylistCategory(m_listview, after, e ); + p->setText( 0, i18n("Smart Playlists") ); + debug() << "loading old format smart playlists, converted to new format" << endl; + updateSmartPlaylists( p ); + saveSmartPlaylists( p ); + return p; + } + else { // Old unversioned format + PlaylistCategory* p = new PlaylistCategory(m_listview, after , i18n("Smart Playlists") ); + QListViewItem *last = 0; + QDomNode n = d.namedItem( "smartplaylists" ).namedItem("smartplaylist"); + for( ; !n.isNull(); n = n.nextSibling() ) { + last = new SmartPlaylist( p, last, n.toElement() ); + } + return p; + } + } +} + +void PlaylistBrowser::updateSmartPlaylists( QListViewItem *p ) +{ + if( !p ) + return; + + for( QListViewItem *it = p->firstChild(); + it; + it = it->nextSibling() ) + { + SmartPlaylist *spl = dynamic_cast( it ); + if( spl ) + { + QDomElement xml = spl->xml(); + QDomElement query = xml.namedItem( "sqlquery" ).toElement(); + QDomElement expandBy = xml.namedItem( "expandby" ).toElement(); + updateSmartPlaylistElement( query ); + updateSmartPlaylistElement( expandBy ); + spl->setXml( xml ); + } + else + updateSmartPlaylists( it ); + } +} + +void PlaylistBrowser::updateSmartPlaylistElement( QDomElement& query ) +{ + QRegExp limitSearch( "LIMIT.*(\\d+)\\s*,\\s*(\\d+)" ); + QRegExp selectFromSearch( "SELECT[^'\"]*FROM" ); + for(QDomNode child = query.firstChild(); + !child.isNull(); + child = child.nextSibling() ) + { + if( child.isText() ) + { + //HACK this should be refactored to just regenerate the SQL from the 's + QDomText text = child.toText(); + QString sql = text.data(); + if ( selectFromSearch.search( sql ) != -1 ) + sql.replace( selectFromSearch, "SELECT (*ListOfFields*) FROM" ); + if ( limitSearch.search( sql ) != -1 ) + sql.replace( limitSearch, + QString( "LIMIT %1 OFFSET %2").arg( limitSearch.capturedTexts()[2].toInt() ).arg( limitSearch.capturedTexts()[1].toInt() ) ); + + text.setData( sql ); + break; + } + } +} + +void PlaylistBrowser::loadDefaultSmartPlaylists() +{ + DEBUG_BLOCK + + const QStringList genres = CollectionDB::instance()->query( "SELECT DISTINCT name FROM genre;" ); + const QStringList artists = CollectionDB::instance()->artistList(); + SmartPlaylist *item; + QueryBuilder qb; + SmartPlaylist *last = 0; + m_smartDefaults = new PlaylistCategory( m_smartCategory, 0, i18n("Collection") ); + m_smartDefaults->setOpen( m_smartDefaultsOpen ); + m_smartDefaults->setKept( false ); + /********** All Collection **************/ + qb.initSQLDrag(); + qb.sortBy( QueryBuilder::tabArtist, QueryBuilder::valName ); + qb.sortBy( QueryBuilder::tabAlbum, QueryBuilder::valName ); + qb.sortBy( QueryBuilder::tabSong, QueryBuilder::valTrack ); + + item = new SmartPlaylist( m_smartDefaults, 0, i18n( "All Collection" ), qb.query() ); + item->setPixmap( 0, SmallIcon( Amarok::icon( "collection" ) ) ); + item->setKept( false ); + /********** Favorite Tracks **************/ + qb.initSQLDrag(); + qb.sortByFavorite(); + qb.setLimit( 0, 15 ); + item = new SmartPlaylist( m_smartDefaults, item, i18n( "Favorite Tracks" ), qb.query() ); + item->setKept( false ); + last = 0; + + qb.initSQLDrag(); + qb.sortByFavorite(); + qb.setLimit( 0, 15 ); + foreach( artists ) { + QueryBuilder qbTemp( qb ); + qbTemp.addMatch( QueryBuilder::tabArtist, *it ); + + last = new SmartPlaylist( item, last, i18n( "By %1" ).arg( *it ), qbTemp.query() ); + last->setKept( false ); + } + + /********** Most Played **************/ + qb.initSQLDrag(); + qb.sortBy( QueryBuilder::tabStats, QueryBuilder::valPlayCounter, true ); + qb.setLimit( 0, 15 ); + + item = new SmartPlaylist( m_smartDefaults, item, i18n( "Most Played" ), qb.query() ); + item->setKept( false ); + last = 0; + + qb.initSQLDrag(); + qb.sortBy( QueryBuilder::tabStats, QueryBuilder::valPlayCounter, true ); + qb.setLimit( 0, 15 ); + foreach( artists ) { + QueryBuilder qbTemp( qb ); + qbTemp.addMatch( QueryBuilder::tabArtist, *it ); + + last = new SmartPlaylist( item, last, i18n( "By %1" ).arg( *it ), qbTemp.query() ); + last->setKept( false ); + } + + /********** Newest Tracks **************/ + qb.initSQLDrag(); + qb.sortBy( QueryBuilder::tabSong, QueryBuilder::valCreateDate, true ); + qb.setLimit( 0, 15 ); + + item = new SmartPlaylist( m_smartDefaults, item, i18n( "Newest Tracks" ), qb.query() ); + item->setKept( false ); + last = 0; + + qb.initSQLDrag(); + qb.sortBy( QueryBuilder::tabSong, QueryBuilder::valCreateDate, true ); + qb.setLimit( 0, 15 ); + foreach( artists ) { + QueryBuilder qbTemp( qb ); + qbTemp.addMatch( QueryBuilder::tabArtist, *it ); + + last = new SmartPlaylist( item, last, i18n( "By %1" ).arg( *it ), qbTemp.query( true ) ); + last->setKept( false ); + } + + /********** Last Played **************/ + qb.initSQLDrag(); + qb.sortBy( QueryBuilder::tabStats, QueryBuilder::valAccessDate, true ); + qb.setLimit( 0, 15 ); + + item = new SmartPlaylist( m_smartDefaults, item, i18n( "Last Played" ), qb.query( true ) ); + item->setKept( false ); + + /********** Never Played **************/ + qb.initSQLDrag(); + qb.addNumericFilter( QueryBuilder::tabStats, QueryBuilder::valPlayCounter, "0" ); + qb.sortBy( QueryBuilder::tabArtist, QueryBuilder::valName ); + qb.sortBy( QueryBuilder::tabAlbum, QueryBuilder::valName ); + qb.sortBy( QueryBuilder::tabSong, QueryBuilder::valTrack ); + + item = new SmartPlaylist( m_smartDefaults, item, i18n( "Never Played" ), qb.query( true ) ); + item->setKept( false ); + + /********** Ever Played **************/ + qb.initSQLDrag(); + qb.excludeFilter( QueryBuilder::tabStats, QueryBuilder::valPlayCounter, "1", QueryBuilder::modeLess ); + qb.sortBy( QueryBuilder::tabArtist, QueryBuilder::valName ); + qb.sortBy( QueryBuilder::tabAlbum, QueryBuilder::valName ); + qb.sortBy( QueryBuilder::tabSong, QueryBuilder::valTrack ); + qb.sortBy( QueryBuilder::tabStats, QueryBuilder::valScore ); + + item = new SmartPlaylist( m_smartDefaults, item, i18n( "Ever Played" ), qb.query( true ) ); + item->setKept( false ); + + /********** Genres **************/ + item = new SmartPlaylist( m_smartDefaults, item, i18n( "Genres" ), QString() ); + item->setKept( false ); + last = 0; + + qb.initSQLDrag(); + qb.sortBy( QueryBuilder::tabArtist, QueryBuilder::valName ); + qb.sortBy( QueryBuilder::tabAlbum, QueryBuilder::valName ); + qb.sortBy( QueryBuilder::tabSong, QueryBuilder::valTrack ); + foreach( genres ) { + QueryBuilder qbTemp( qb ); + qbTemp.addMatch( QueryBuilder::tabGenre, *it ); + + last = new SmartPlaylist( item, last, i18n( "%1" ).arg( *it ), qbTemp.query( true ) ); + last->setKept( false ); + } + + /********** 50 Random Tracks **************/ + qb.initSQLDrag(); + qb.setOptions( QueryBuilder::optRandomize ); + qb.setLimit( 0, 50 ); + item = new SmartPlaylist( m_smartDefaults, item, i18n( "50 Random Tracks" ), qb.query( true ) ); + item->setKept( false ); +} + +void PlaylistBrowser::editSmartPlaylist( SmartPlaylist* item ) +{ + SmartPlaylistEditor dialog( this, item->xml() ); + + if( dialog.exec() == QDialog::Accepted ) + { + item->setXml ( dialog.result() ); + item->setText( 0, dialog.name() ); + + if( item->isDynamic() ) // rebuild the cache if the smart playlist has changed + Playlist::instance()->rebuildDynamicModeCache(); + } +} + +void PlaylistBrowser::saveSmartPlaylists( PlaylistCategory *smartCategory ) +{ + QFile file( smartplaylistBrowserCache() ); + + if( !smartCategory ) + smartCategory = m_smartCategory; + + // If the user hadn't set a collection, we didn't create the Smart Playlist Item + if( !smartCategory ) return; + + QDomDocument doc; + QDomElement smartB = smartCategory->xml(); + smartB.setAttribute( "product", "Amarok" ); + smartB.setAttribute( "version", APP_VERSION ); + smartB.setAttribute( "formatversion", "1.8" ); + QDomNode smartplaylistsNode = doc.importNode( smartB, true ); + doc.appendChild( smartplaylistsNode ); + + QString temp( doc.toString() ); + + // Only open the file after all data is ready. If it crashes, data is not lost! + if ( !file.open( IO_WriteOnly ) ) return; + + QTextStream smart( &file ); + smart.setEncoding( QTextStream::UnicodeUTF8 ); + smart << "\n"; + smart << temp; +} + +/** + ************************************************************************* + * PARTIES + ************************************************************************* + **/ + +QString PlaylistBrowser::dynamicBrowserCache() const +{ + return Amarok::saveLocation() + "dynamicbrowser_save.xml"; +} + +PlaylistCategory* PlaylistBrowser::loadDynamics() +{ + QFile file( dynamicBrowserCache() ); + + QTextStream stream( &file ); + stream.setEncoding( QTextStream::UnicodeUTF8 ); + + QDomDocument d; + QDomElement e; + + PlaylistCategory *after = m_smartCategory; + if( CollectionDB::instance()->isEmpty() || !m_smartCategory ) // incase of no collection + after = m_playlistCategory; + + if( !file.open( IO_ReadOnly ) || !d.setContent( stream.read() ) ) + { /*Couldn't open the file or it had invalid content, so let's create some defaults*/ + PlaylistCategory *p = new PlaylistCategory( m_listview, after, i18n("Dynamic Playlists") ); + return p; + } + else { + e = d.namedItem( "category" ).toElement(); + QString version = e.attribute("formatversion"); + if ( version == "1.2" ) { + PlaylistCategory* p = new PlaylistCategory( m_listview, after, e ); + p->setText( 0, i18n("Dynamic Playlists") ); + return p; + } + else if ( version == "1.1" ) { + // In 1.1, playlists would be referred only by its name. + // TODO: We can *try* to convert by using findItem + PlaylistCategory* p = new PlaylistCategory( m_listview, after, e ); + p->setText( 0, i18n("Dynamic Playlists") ); + fixDynamicPlaylistPath( p ); + return p; + } + else { // Old unversioned format + PlaylistCategory* p = new PlaylistCategory( m_listview, after, i18n("Dynamic Playlists") ); + QListViewItem *last = 0; + QDomNode n = d.namedItem( "dynamicbrowser" ).namedItem("dynamic"); + for( ; !n.isNull(); n = n.nextSibling() ) { + last = new DynamicEntry( p, last, n.toElement() ); + } + return p; + } + } +} + + +void +PlaylistBrowser::fixDynamicPlaylistPath( QListViewItem *item ) +{ + DynamicEntry *entry = dynamic_cast( item ); + if ( entry ) { + QStringList names = entry->items(); + QStringList paths; + foreach( names ) { + QString path = guessPathFromPlaylistName( *it ); + if ( !path.isNull() ) + paths+=path; + } + entry->setItems( paths ); + } + PlaylistCategory *cat = dynamic_cast( item ); + if ( cat ) { + QListViewItem *it = cat->firstChild(); + for( ; it; it = it->nextSibling() ) { + fixDynamicPlaylistPath( it ); + } + } +} + +QString +PlaylistBrowser::guessPathFromPlaylistName( QString name ) +{ + QListViewItem *item = m_listview->findItem( name, 0, Qt::ExactMatch ); + PlaylistBrowserEntry *entry = dynamic_cast( item ); + if ( entry ) + return entry->name(); + return QString(); +} + +void PlaylistBrowser::saveDynamics() +{ + Amarok::config( "PlaylistBrowser" )->writeEntry( "Dynamic Random Remove Played", m_randomDynamic->cycleTracks() ); + Amarok::config( "PlaylistBrowser" )->writeEntry( "Dynamic Random Upcoming Count", m_randomDynamic->upcomingCount() ); + Amarok::config( "PlaylistBrowser" )->writeEntry( "Dynamic Random Previous Count", m_randomDynamic->previousCount() ); + + Amarok::config( "PlaylistBrowser" )->writeEntry( "Dynamic Suggest Remove Played", m_suggestedDynamic->cycleTracks() ); + Amarok::config( "PlaylistBrowser" )->writeEntry( "Dynamic Suggest Upcoming Count", m_suggestedDynamic->upcomingCount() ); + Amarok::config( "PlaylistBrowser" )->writeEntry( "Dynamic Suggest Previous Count", m_suggestedDynamic->previousCount() ); + + QFile file( dynamicBrowserCache() ); + QTextStream stream( &file ); + + QDomDocument doc; + QDomElement dynamicB = m_dynamicCategory->xml(); + dynamicB.setAttribute( "product", "Amarok" ); + dynamicB.setAttribute( "version", APP_VERSION ); + dynamicB.setAttribute( "formatversion", "1.2" ); + QDomNode dynamicsNode = doc.importNode( dynamicB, true ); + doc.appendChild( dynamicsNode ); + + QString temp( doc.toString() ); + + // Only open the file after all data is ready. If it crashes, data is not lost! + if ( !file.open( IO_WriteOnly ) ) return; + + stream.setEncoding( QTextStream::UnicodeUTF8 ); + stream << "\n"; + stream << temp; +} + +void PlaylistBrowser::loadDynamicItems() +{ + // Make sure all items are unmarked + for( uint i=0; i < m_dynamicEntries.count(); i++ ) + { + QListViewItem *it = m_dynamicEntries.at( i ); + + if( it ) + static_cast(it)->setDynamic( false ); + } + m_dynamicEntries.clear(); // Don't use remove(), since we do i++, which would cause skip overs!!! + + // Mark appropriate items as used + markDynamicEntries(); +} + +/** + ************************************************************************* + * PODCASTS + ************************************************************************* + **/ + +QString PlaylistBrowser::podcastBrowserCache() const +{ + //returns the playlists stats cache file + return Amarok::saveLocation() + "podcastbrowser_save.xml"; +} + +PlaylistCategory* PlaylistBrowser::loadPodcasts() +{ + DEBUG_BLOCK + + QFile file( podcastBrowserCache() ); + QTextStream stream( &file ); + stream.setEncoding( QTextStream::UnicodeUTF8 ); + + QDomDocument d; + QDomElement e; + + QListViewItem *after = 0; + + if( !file.open( IO_ReadOnly ) || !d.setContent( stream.read() ) ) + { /*Couldn't open the file or it had invalid content, so let's create an empty element*/ + PlaylistCategory *p = new PlaylistCategory( m_listview, after, i18n("Podcasts") ); + p->setId( 0 ); + loadPodcastsFromDatabase( p ); + return p; + } + else { + e = d.namedItem( "category" ).toElement(); + + if ( e.attribute("formatversion") == "1.1" ) { + debug() << "Podcasts are being moved to the database..." << endl; + m_podcastItemsToScan.clear(); + + PlaylistCategory *p = new PlaylistCategory( m_listview, after, e ); + p->setId( 0 ); + //delete the file, it is deprecated + KIO::del( KURL::fromPathOrURL( podcastBrowserCache() ) ); + + if( !m_podcastItemsToScan.isEmpty() ) + m_podcastTimer->start( m_podcastTimerInterval ); + + return p; + } + } + PlaylistCategory *p = new PlaylistCategory( m_listview, after, i18n("Podcasts") ); + p->setId( 0 ); + return p; +} + +void PlaylistBrowser::loadPodcastsFromDatabase( PlaylistCategory *p ) +{ +DEBUG_BLOCK + if( !p ) p = m_podcastCategory; + m_podcastItemsToScan.clear(); + + while( p->firstChild() ) + delete p->firstChild(); + + QMap folderMap = loadPodcastFolders( p ); + + QValueList channels; + + channels = CollectionDB::instance()->getPodcastChannels(); + + PodcastChannel *channel = 0; + + foreachType( QValueList, channels ) + { + PlaylistCategory *parent = p; + const int parentId = (*it).parentId(); + if( parentId > 0 && folderMap.find( parentId ) != folderMap.end() ) + parent = folderMap[parentId]; + + channel = new PodcastChannel( parent, channel, *it ); + + bool hasNew = CollectionDB::instance()->query( QString("SELECT COUNT(parent) FROM podcastepisodes WHERE ( parent='%1' AND isNew=%2 ) LIMIT 1" ) + .arg( (*it).url().url(), CollectionDB::instance()->boolT() ) ) + .first().toInt() > 0; + + channel->setNew( hasNew ); + + if( channel->autoscan() ) + m_podcastItemsToScan.append( channel ); + } + + if( !m_podcastItemsToScan.isEmpty() ) + m_podcastTimer->start( m_podcastTimerInterval ); +} + +QMap +PlaylistBrowser::loadPodcastFolders( PlaylistCategory *p ) +{ +DEBUG_BLOCK + QString sql = "SELECT * FROM podcastfolders ORDER BY parent ASC;"; + QStringList values = CollectionDB::instance()->query( sql ); + + // store the folder and IDs so finding a parent is fast + QMap folderMap; + PlaylistCategory *folder = 0; + foreach( values ) + { + const int id = (*it).toInt(); + const QString t = *++it; + const int parentId = (*++it).toInt(); + const bool isOpen = ( (*++it) == CollectionDB::instance()->boolT() ? true : false ); + + PlaylistCategory *parent = p; + if( parentId > 0 && folderMap.find( parentId ) != folderMap.end() ) + parent = folderMap[parentId]; + + folder = new PlaylistCategory( parent, folder, t, id ); + folder->setOpen( isOpen ); + + folderMap[id] = folder; + } + // check if the base folder exists + p->setOpen( Amarok::config( "PlaylistBrowser" )->readBoolEntry( "Podcast Folder Open", true ) ); + + return folderMap; +} + +void PlaylistBrowser::savePodcastFolderStates( PlaylistCategory *folder ) +{ + if( !folder ) return; + + PlaylistCategory *child = static_cast(folder->firstChild()); + while( child ) + { + if( isCategory( child ) ) + savePodcastFolderStates( child ); + else + break; + + child = static_cast(child->nextSibling()); + } + if( folder != m_podcastCategory ) + { + if( folder->id() < 0 ) // probably due to a 1.3->1.4 migration + { // we add the folder to the db, set the id and then update all the children + int parentId = static_cast(folder->parent())->id(); + int newId = CollectionDB::instance()->addPodcastFolder( folder->text(0), parentId, folder->isOpen() ); + folder->setId( newId ); + PodcastChannel *chan = static_cast(folder->firstChild()); + while( chan ) + { + if( isPodcastChannel( chan ) ) + // will update the database so child has correct parentId. + chan->setParent( folder ); + chan = static_cast(chan->nextSibling()); + } + } + else + { + CollectionDB::instance()->updatePodcastFolder( folder->id(), folder->text(0), + static_cast(folder->parent())->id(), folder->isOpen() ); + } + } +} + +void PlaylistBrowser::scanPodcasts() +{ + //don't want to restart timer unnecessarily. addPodcast will start it if it is necessary + if( m_podcastItemsToScan.isEmpty() ) return; + + for( uint i=0; i < m_podcastItemsToScan.count(); i++ ) + { + QListViewItem *item = m_podcastItemsToScan.at( i ); + PodcastChannel *pc = static_cast(item); + pc->rescan(); + } + //restart timer + m_podcastTimer->start( m_podcastTimerInterval ); +} + +void PlaylistBrowser::refreshPodcasts( QListViewItem *parent ) +{ + for( QListViewItem *child = parent->firstChild(); + child; + child = child->nextSibling() ) + { + if( isPodcastChannel( child ) ) + static_cast( child )->rescan(); + else if( isCategory( child ) ) + refreshPodcasts( child ); + } +} + +void PlaylistBrowser::addPodcast( QListViewItem *parent ) +{ + bool ok; + const QString name = KInputDialog::getText(i18n("Add Podcast"), i18n("Enter Podcast URL:"), QString::null, &ok, this); + + if( ok && !name.isEmpty() ) + { + addPodcast( KURL::fromPathOrURL( name ), parent ); + } +} + +void PlaylistBrowser::configurePodcasts( QListViewItem *parent ) +{ + QPtrList podcastChannelList; + for( QListViewItem *child = parent->firstChild(); + child; + child = child->nextSibling() ) + { + if( isPodcastChannel( child ) ) + { + podcastChannelList.append( static_cast( child ) ); + } + } + if( !podcastChannelList.isEmpty() ) + configurePodcasts( podcastChannelList, i18n( "Podcasts contained in %1", "All in %1").arg( parent->text( 0 ) ) ); +} + +void PlaylistBrowser::configureSelectedPodcasts() +{ + QPtrList selected; + QListViewItemIterator it( m_listview, QListViewItemIterator::Selected); + for( ; it.current(); ++it ) + { + if( isPodcastChannel( (*it) ) ) + selected.append( static_cast(*it) ); + } + if (selected.isEmpty() ) + return; //shouldn't happen + + if( selected.count() == 1 ) + selected.getFirst()->configure(); + else + configurePodcasts( selected, i18n("1 Podcast", "%n Podcasts", selected.count() ) ); + + if( m_podcastItemsToScan.isEmpty() ) + m_podcastTimer->stop(); + + else if( m_podcastItemsToScan.count() == 1 ) + m_podcastTimer->start( m_podcastTimerInterval ); + // else timer is already running +} + +void PlaylistBrowser::configurePodcasts( QPtrList &podcastChannelList, + const QString &caption ) +{ + + if( podcastChannelList.isEmpty() ) + { + debug() << "BUG: podcastChannelList is empty" << endl; + return; + } + QPtrList podcastSettingsList; + foreachType( QPtrList, podcastChannelList) + { + podcastSettingsList.append( (*it)->getSettings() ); + } + PodcastSettingsDialog *dialog = new PodcastSettingsDialog( podcastSettingsList, caption ); + if( dialog->configure() ) + { + PodcastChannel *channel = podcastChannelList.first(); + foreachType( QPtrList, podcastSettingsList ) + { + if ( (*it)->title() == channel->title() ) + { + channel->setSettings( *it ); + } + else + debug() << " BUG in playlistbrowser.cpp:configurePodcasts( )" << endl; + + channel = podcastChannelList.next(); + } + } +} + +PodcastChannel * +PlaylistBrowser::findPodcastChannel( const KURL &feed, QListViewItem *parent ) const +{ + if( !parent ) parent = static_cast(m_podcastCategory); + + for( QListViewItem *it = parent->firstChild(); + it; + it = it->nextSibling() ) + { + if( isPodcastChannel( it ) ) + { + PodcastChannel *channel = static_cast( it ); + if( channel->url().prettyURL() == feed.prettyURL() ) + { + return channel; + } + } + else if( isCategory( it ) ) + { + PodcastChannel *channel = findPodcastChannel( feed, it ); + if( channel ) + return channel; + } + } + + return 0; +} + +PodcastEpisode * +PlaylistBrowser::findPodcastEpisode( const KURL &episode, const KURL &feed ) const +{ + PodcastChannel *channel = findPodcastChannel( feed ); + if( !channel ) + return 0; + + if( !channel->isPolished() ) + channel->load(); + + QListViewItem *child = channel->firstChild(); + while( child ) + { + #define child static_cast(child) + if( child->url() == episode ) + return child; + #undef child + child = child->nextSibling(); + } + + return 0; +} + +void PlaylistBrowser::addPodcast( const KURL& origUrl, QListViewItem *parent ) +{ + if( !parent ) parent = static_cast(m_podcastCategory); + + KURL url( origUrl ); + if( url.protocol() == "itpc" || url.protocol() == "pcast" ) + url.setProtocol( "http" ); + + PodcastChannel *channel = findPodcastChannel( url ); + if( channel ) + { + Amarok::StatusBar::instance()->longMessage( + i18n( "Already subscribed to feed %1 as %2" ) + .arg( url.prettyURL(), channel->title() ), + KDE::StatusBar::Sorry ); + return; + } + + PodcastChannel *pc = new PodcastChannel( parent, 0, url ); + + if( m_podcastItemsToScan.isEmpty() ) + { + m_podcastItemsToScan.append( pc ); + m_podcastTimer->start( m_podcastTimerInterval ); + } + else + { + m_podcastItemsToScan.append( pc ); + } + + parent->sortChildItems( 0, true ); + parent->setOpen( true ); +} + +void PlaylistBrowser::changePodcastInterval() +{ + double time = static_cast(m_podcastTimerInterval / ( 60 * 60 * 1000 )); + bool ok; + double interval = KInputDialog::getDouble( i18n("Download Interval"), + i18n("Scan interval (hours):"), time, + 0.5, 100.0, .5, 1, // min, max, step, base + &ok, this); + int milliseconds = static_cast(interval*60.0*60.0*1000.0); + if( ok ) + { + if( milliseconds != m_podcastTimerInterval ) + { + m_podcastTimerInterval = milliseconds; + m_podcastTimer->changeInterval( m_podcastTimerInterval ); + } + } +} + +bool PlaylistBrowser::deleteSelectedPodcastItems( const bool removeItem, const bool silent ) +{ + KURL::List urls; + QListViewItemIterator it( m_podcastCategory, QListViewItemIterator::Selected ); + QPtrList erasedItems; + + for( ; it.current(); ++it ) + { + if( isPodcastEpisode( *it ) ) + { + #define item static_cast(*it) + if( item->isOnDisk() ) { + urls.append( item->localUrl() ); + erasedItems.append( item ); + } + #undef item + } + } + + if( urls.isEmpty() ) return false; + int button; + if( !silent ) + button = KMessageBox::warningContinueCancel( this, + i18n( "

    You have selected 1 podcast episode to be irreversibly deleted. ", + "

    You have selected %n podcast episodes to be irreversibly deleted. ", + urls.count() ), QString::null, KStdGuiItem::del() ); + if( silent || button != KMessageBox::Continue ) + return false; + + KIO::Job *job = KIO::del( urls ); + + PodcastEpisode *item; + for ( item = erasedItems.first(); item; item = erasedItems.next() ) + { + if( removeItem ) + { + CollectionDB::instance()->removePodcastEpisode( item->dBId() ); + delete item; + } + else + connect( job, SIGNAL( result( KIO::Job* ) ), item, SLOT( isOnDisk() ) );; + } + return true; +} + +bool PlaylistBrowser::deletePodcasts( QPtrList items ) +{ + if( items.isEmpty() ) return false; + + KURL::List urls; + foreachType( QPtrList, items ) + { + for( QListViewItem *ch = (*it)->firstChild(); ch; ch = ch->nextSibling() ) + { + #define ch static_cast(ch) + if( ch->isOnDisk() ) + { + //delete downloaded media + urls.append( ch->localUrl() ); + } + #undef ch + /// we don't need to delete from the database, because removing the channel from the database + /// automatically removes the children as well. + m_podcastItemsToScan.remove( static_cast(*it) ); + } + CollectionDB::instance()->removePodcastChannel( static_cast(*it)->url() ); + + } + // TODO We need to check which files have been deleted successfully + if ( urls.count() ) + KIO::del( urls ); + return true; +} + +void PlaylistBrowser::downloadSelectedPodcasts() +{ + QListViewItemIterator it( m_listview, QListViewItemIterator::Selected ); + + for( ; it.current(); ++it ) + { + if( isPodcastEpisode( *it ) ) + { + #define item static_cast(*it) + if( !item->isOnDisk() ) + m_podcastDownloadQueue.append( item ); + #undef item + } + } + downloadPodcastQueue(); +} + +void PlaylistBrowser::downloadPodcastQueue() //SLOT +{ + if( m_podcastDownloadQueue.isEmpty() ) return; + + PodcastEpisode *first = m_podcastDownloadQueue.first(); + first->downloadMedia(); + m_podcastDownloadQueue.removeFirst(); + + connect( first, SIGNAL( downloadFinished() ), this, SLOT( downloadPodcastQueue() ) ); + connect( first, SIGNAL( downloadAborted() ), this, SLOT( abortPodcastQueue() ) ); +} + +void PlaylistBrowser::abortPodcastQueue() //SLOT +{ + m_podcastDownloadQueue.clear(); +} + +void PlaylistBrowser::registerPodcastSettings( const QString &title, const PodcastSettings *settings ) +{ + m_podcastSettings.insert( title, settings ); +} + +/** + ************************************************************************* + * PLAYLISTS + ************************************************************************* + **/ + +QString PlaylistBrowser::playlistBrowserCache() const +{ + //returns the playlists stats cache file + return Amarok::saveLocation() + "playlistbrowser_save.xml"; +} + +PlaylistCategory* PlaylistBrowser::loadPlaylists() +{ + QFile file( playlistBrowserCache() ); + + QTextStream stream( &file ); + stream.setEncoding( QTextStream::UnicodeUTF8 ); + + QDomDocument d; + QDomElement e; + + if( !file.open( IO_ReadOnly ) || !d.setContent( stream.read() ) ) + { /*Couldn't open the file or it had invalid content, so let's create an empty element*/ + return new PlaylistCategory(m_listview, 0 , i18n("Playlists") ); + } + else { + e = d.namedItem( "category" ).toElement(); + if ( e.attribute("formatversion") =="1.1" ) + { + PlaylistCategory* p = new PlaylistCategory( m_listview, 0 , e ); + p->setText( 0, i18n("Playlists") ); + return p; + } + else { // Old unversioned format + PlaylistCategory* p = new PlaylistCategory( m_listview, 0 , i18n("Playlists") ); + QListViewItem *last = 0; + QDomNode n = d.namedItem( "playlistbrowser" ).namedItem("playlist"); + + for ( ; !n.isNull(); n = n.nextSibling() ) + last = new PlaylistEntry( p, last, n.toElement() ); + + return p; + } + } +} + +QListViewItem * +PlaylistBrowser::findItemInTree( const QString &searchstring, int c ) const +{ + QStringList list = QStringList::split( "/", searchstring, true ); + + // select the 1st level + QStringList::Iterator it = list.begin(); + QListViewItem *pli = findItem (*it, c); + if ( !pli ) return pli; + + for ( ++it ; it != list.end(); ++it ) + { + + QListViewItemIterator it2( pli ); + for( ++it2 ; it2.current(); ++it2 ) + { + if ( *it == (*it2)->text(0) ) + { + pli = *it2; + break; + } + // test, to not go over into the next category + if ( isCategory( *it2 ) && (pli->nextSibling() == *it2) ) + return 0; + } + if ( ! it2.current() ) + return 0; + + } + return pli; +} + +DynamicMode *PlaylistBrowser::findDynamicModeByTitle( const QString &title ) +{ + if( !m_polished ) + polish(); + + for ( QListViewItem *item = m_dynamicCategory->firstChild(); item; item = item->nextSibling() ) + { + DynamicEntry *entry = dynamic_cast( item ); + if ( entry && entry->title() == title ) + return entry; + } + + return 0; +} + +PlaylistEntry * +PlaylistBrowser::findPlaylistEntry( const QString &url, QListViewItem *parent ) const +{ + if( !parent ) parent = static_cast(m_playlistCategory); + + for( QListViewItem *it = parent->firstChild(); + it; + it = it->nextSibling() ) + { + if( isPlaylist( it ) ) + { + PlaylistEntry *pl = static_cast( it ); + debug() << pl->url().path() << " == " << url << endl; + if( pl->url().path() == url ) + { + debug() << "ok!" << endl; + return pl; + } + } + else if( isCategory( it ) ) + { + PlaylistEntry *pl = findPlaylistEntry( url, it ); + if( pl ) + return pl; + } + } + + return 0; +} + +int PlaylistBrowser::loadPlaylist( const QString &playlist, bool /*force*/ ) +{ + // roland + DEBUG_BLOCK + + QListViewItem *pli = findItemInTree( playlist, 0 ); + if ( ! pli ) return -1; + + slotDoubleClicked( pli ); + return 0; + // roland +} + +void PlaylistBrowser::addPlaylist( const QString &path, QListViewItem *parent, bool force, bool imported ) +{ + // this function adds a playlist to the playlist browser + + if( !m_polished ) + polish(); + + QFile file( path ); + if( !file.exists() ) return; + + PlaylistEntry *playlist = findPlaylistEntry( path ); + + if( playlist && force ) + playlist->load(); //reload the playlist + + if( imported ) { + QListViewItem *playlistImports = 0; + //First try and find the imported folder + for ( QListViewItem *it = m_playlistCategory->firstChild(); it; it = it->nextSibling() ) + { + if ( dynamic_cast( it ) && static_cast( it )->isFolder() && + it->text( 0 ) == i18n( "Imported" ) ) + { + playlistImports = it; + break; + } + } + if ( !playlistImports ) //We didn't find the Imported folder, so create it. + playlistImports = new PlaylistCategory( m_playlistCategory, 0, i18n("Imported") ); + parent = playlistImports; + } + else if( !parent ) parent = static_cast(m_playlistCategory); + + if( !playlist ) { + if( !m_playlistCategory || !m_playlistCategory->childCount() ) { //first child + removeButton->setEnabled( true ); + renameButton->setEnabled( true ); + } + + KURL auxKURL; + auxKURL.setPath(path); + m_lastPlaylist = playlist = new PlaylistEntry( parent, 0, auxKURL ); + } + + parent->setOpen( true ); + parent->sortChildItems( 0, true ); + m_listview->clearSelection(); + playlist->setSelected( true ); +} + +bool PlaylistBrowser::savePlaylist( const QString &path, const QValueList &in_urls, + const QValueList &titles, const QValueList &lengths, + bool relative ) +{ + if( path.isEmpty() ) + return false; + + QFile file( path ); + + if( !file.open( IO_WriteOnly ) ) + { + KMessageBox::sorry( PlaylistWindow::self(), i18n( "Cannot write playlist (%1).").arg(path) ); + return false; + } + + QTextStream stream( &file ); + stream << "#EXTM3U\n"; + + KURL::List urls; + for( int i = 0, n = in_urls.count(); i < n; ++i ) + { + const KURL &url = in_urls[i]; + if( url.isLocalFile() && QFileInfo( url.path() ).isDir() ) + urls += recurse( url ); + else + urls += url; + } + + for( int i = 0, n = urls.count(); i < n; ++i ) + { + const KURL &url = urls[i]; + + if( !titles.isEmpty() && !lengths.isEmpty() ) + { + stream << "#EXTINF:"; + stream << QString::number( lengths[i] ); + stream << ','; + stream << titles[i]; + stream << '\n'; + } + if (url.protocol() == "file" ) { + if ( relative ) { + const QFileInfo fi(file); + stream << KURL::relativePath(fi.dirPath(), url.path()); + } else + stream << url.path(); + } else { + stream << url.url(); + } + stream << "\n"; + } + + file.close(); // Flushes the file, before we read it + PlaylistBrowser::instance()->addPlaylist( path, 0, true ); + + return true; +} + +void PlaylistBrowser::openPlaylist( QListViewItem *parent ) //SLOT +{ + // open a file selector to add playlists to the playlist browser + QStringList files; + files = KFileDialog::getOpenFileNames( QString::null, "*.m3u *.pls *.xspf|" + i18n("Playlist Files"), this, i18n("Import Playlists") ); + + const QStringList::ConstIterator end = files.constEnd(); + for( QStringList::ConstIterator it = files.constBegin(); it != end; ++it ) + addPlaylist( *it, parent ); + + savePlaylists(); +} + +void PlaylistBrowser::savePlaylists() +{ + QFile file( playlistBrowserCache() ); + + QDomDocument doc; + QDomElement playlistsB = m_playlistCategory->xml(); + playlistsB.setAttribute( "product", "Amarok" ); + playlistsB.setAttribute( "version", APP_VERSION ); + playlistsB.setAttribute( "formatversion", "1.1" ); + QDomNode playlistsNode = doc.importNode( playlistsB, true ); + doc.appendChild( playlistsNode ); + + QString temp( doc.toString() ); + + // Only open the file after all data is ready. If it crashes, data is not lost! + if ( !file.open( IO_WriteOnly ) ) return; + + QTextStream stream( &file ); + stream.setEncoding( QTextStream::UnicodeUTF8 ); + stream << "\n"; + stream << temp; +} + +bool PlaylistBrowser::deletePlaylists( QPtrList items ) +{ + KURL::List urls; + foreachType( QPtrList, items ) + { + urls.append( (*it)->url() ); + } + if( !urls.isEmpty() ) + return deletePlaylists( urls ); + + return false; +} + +bool PlaylistBrowser::deletePlaylists( KURL::List items ) +{ + if ( items.isEmpty() ) return false; + + // TODO We need to check which files have been deleted successfully + // Avoid deleting dirs. See bug #122480 + for ( KURL::List::iterator it = items.begin(), end = items.end(); it != end; ++it ) { + if ( QFileInfo( (*it).path() ).isDir() ) { + it = items.remove( it ); + continue; + } + } + KIO::del( items ); + return true; +} + +void PlaylistBrowser::savePlaylist( PlaylistEntry *item ) +{ + bool append = false; + + if( item->trackList().count() == 0 ) //the playlist hasn't been loaded so we append the dropped tracks + append = true; + + //save the modified playlist in m3u or pls format + const QString ext = fileExtension( item->url().path() ); + if( ext.lower() == "m3u" ) + saveM3U( item, append ); + else if ( ext.lower() == "xspf" ) + saveXSPF( item, append ); + else + savePLS( item, append ); +} + +/** + ************************************************************************* + * General Methods + ************************************************************************* + **/ + +PlaylistBrowserEntry * +PlaylistBrowser::findItem( QString &t, int c ) const +{ + return static_cast( m_listview->findItem( t, c, Qt::ExactMatch ) ); +} + +bool PlaylistBrowser::createPlaylist( QListViewItem *parent, bool current, QString title ) +{ + if( title.isEmpty() ) title = i18n("Untitled"); + + const QString path = PlaylistDialog::getSaveFileName( title ); + if( path.isEmpty() ) + return false; + + if( !parent ) + parent = static_cast( m_playlistCategory ); + + if( current ) + { + if ( !Playlist::instance()->saveM3U( path ) ) { + return false; + } + } + else + { + //Remove any items in Listview that have the same path as this one + // Should only happen when overwriting a playlist + QListViewItem *item = parent->firstChild(); + while( item ) + { + if( static_cast( item )->url() == path ) + { + QListViewItem *todelete = item; + item = item->nextSibling(); + delete todelete; + } + else + item = item->nextSibling(); + } + + //Remove existing playlist if it exists + if ( QFileInfo( path ).exists() ) + QFileInfo( path ).dir().remove( path ); + + m_lastPlaylist = new PlaylistEntry( parent, 0, path ); + parent->sortChildItems( 0, true ); + } + + savePlaylists(); + + return true; +} + +void PlaylistBrowser::addSelectedToPlaylist( int options ) +{ + if ( options == -1 ) + options = Playlist::Unique | Playlist::Append; + + KURL::List list; + + QListViewItemIterator it( m_listview, QListViewItemIterator::Selected ); + for( ; it.current(); ++it ) + { + #define item (*it) + if ( isPlaylist( item ) ) + list << static_cast(item)->url(); + + else if( isLastFm( item ) ) + list << static_cast(item)->url(); + + else if ( isStream( item ) ) + list << static_cast(item)->url(); + + else if ( isPodcastChannel( item ) ) + { + #define channel static_cast(item) + if( !channel->isPolished() ) + channel->load(); + #undef channel + KURL::List _list; + QListViewItem *child = item->firstChild(); + while( child ) + { + #define child static_cast(child) + child->isOnDisk() ? + _list.prepend( child->localUrl() ): + _list.prepend( child->url() ); + #undef child + child = child->nextSibling(); + } + list += _list ; + } + + else if ( isPodcastEpisode( item ) ) + { + #define pod static_cast(item) + if( pod->isOnDisk() ) + list << pod->localUrl(); + else + list << pod->url(); + #undef pod + } + + else if ( isPlaylistTrackItem( item ) ) + list << static_cast(item)->url(); + #undef item + } + + if( !list.isEmpty() ) + Playlist::instance()->insertMedia( list, options ); +} + +void +PlaylistBrowser::invokeItem( QListViewItem* i, const QPoint& point, int column ) //SLOT +{ + if( column == -1 ) + return; + + PlaylistBrowserView *view = getListView(); + + QPoint p = mapFromGlobal( point ); + if ( p.x() > view->header()->sectionPos( view->header()->mapToIndex( 0 ) ) + view->treeStepSize() * ( i->depth() + ( view->rootIsDecorated() ? 1 : 0) ) + view->itemMargin() + || p.x() < view->header()->sectionPos( view->header()->mapToIndex( 0 ) ) ) + slotDoubleClicked( i ); +} + +void PlaylistBrowser::slotDoubleClicked( QListViewItem *item ) //SLOT +{ + if( !item ) return; + PlaylistBrowserEntry *entry = dynamic_cast(item); + if ( entry ) + entry->slotDoubleClicked(); +} + +void PlaylistBrowser::collectionScanDone() +{ + if( !m_polished || CollectionDB::instance()->isEmpty() ) + { + return; + } + else if( !m_smartCategory ) + { + m_smartCategory = loadSmartPlaylists(); + loadDefaultSmartPlaylists(); + m_smartCategory->setOpen( true ); + } +} + +void PlaylistBrowser::removeSelectedItems() //SLOT +{ + // this function remove selected playlists and tracks + + int playlistCount = 0; + int trackCount = 0; + int streamCount = 0; + int smartyCount = 0; + int dynamicCount = 0; + int podcastCount = 0; + int folderCount = 0; + int lastfmCount = 0; + + QPtrList playlistsToDelete; + QPtrList podcastsToDelete; + + QPtrList playlistFoldersToDelete; + QPtrList podcastFoldersToDelete; + + //remove currentItem, no matter if selected or not + m_listview->setSelected( m_listview->currentItem(), true ); + + QPtrList selected; + QListViewItemIterator it( m_listview, QListViewItemIterator::Selected ); + for( ; it.current(); ++it ) + { + if( !static_cast(*it)->isKept() ) + continue; + + if( isCategory( *it ) && !static_cast(*it)->isFolder() ) //its a base category + continue; + + // if the playlist containing this item is already selected the current item will be skipped + // it will be deleted from the parent + QListViewItem *parent = it.current()->parent(); + + if( parent && parent->isSelected() ) //parent will remove children + continue; + + if (parent) { + while( parent->parent() && static_cast(parent)->isKept() ) + parent = parent->parent(); + } + + if( parent && !static_cast(parent)->isKept() ) + continue; + + switch( (*it)->rtti() ) + { + case PlaylistEntry::RTTI: + playlistsToDelete.append( static_cast(*it) ); + playlistCount++; + continue; // don't add the folder to selected, else it will be deleted twice + + case PlaylistTrackItem::RTTI: + trackCount++; + break; + + case LastFmEntry::RTTI: + lastfmCount++; + break; + + case StreamEntry::RTTI: + streamCount++; + break; + + case DynamicEntry::RTTI: + dynamicCount++; + break; + + case SmartPlaylist::RTTI: + smartyCount++; + break; + + case PodcastChannel::RTTI: + podcastCount++; + podcastsToDelete.append( static_cast(*it) ); + case PodcastEpisode::RTTI: //episodes can't be removed + continue; // don't add the folder to selected, else it will be deleted twice + + case PlaylistCategory::RTTI: + folderCount++; + if( parent == m_playlistCategory ) + { + for( QListViewItem *ch = (*it)->firstChild(); ch; ch = ch->nextSibling() ) + { + if( isCategory( ch ) ) + { + folderCount++; + playlistFoldersToDelete.append( static_cast(ch) ); + } + else + { + playlistCount++; + playlistsToDelete.append( static_cast(ch) ); + } + } + playlistFoldersToDelete.append( static_cast(*it) ); + continue; // don't add the folder to selected, else it will be deleted twice + } + else if( parent == m_podcastCategory ) + { + for( QListViewItem *ch = (*it)->firstChild(); ch; ch = ch->nextSibling() ) + { + if( isCategory( ch ) ) + { + folderCount++; + podcastFoldersToDelete.append( static_cast(ch) ); + } + else + { + podcastCount++; + podcastsToDelete.append( static_cast(ch) ); + } + } + podcastFoldersToDelete.append( static_cast(*it) ); + continue; // don't add the folder to selected, else it will be deleted twice + } + + default: + break; + } + + selected.append( it.current() ); + } + + int totalCount = playlistCount + smartyCount + dynamicCount + + streamCount + podcastCount + folderCount + lastfmCount; + + if( selected.isEmpty() && !totalCount ) return; + + QString message = i18n( "

    You have selected:

      " ); + + if( playlistCount ) message += "
    • " + i18n( "1 playlist", "%n playlists", playlistCount ) + "
    • "; + + if( smartyCount ) message += "
    • " + i18n( "1 smart playlist", "%n smart playlists", smartyCount ) + "
    • "; + + if( dynamicCount ) message += "
    • " + i18n( "1 dynamic playlist", "%n dynamic playlists", dynamicCount ) + "
    • "; + + if( streamCount ) message += "
    • " + i18n( "1 stream", "%n streams", streamCount ) + "
    • "; + + if( podcastCount ) message += "
    • " + i18n( "1 podcast", "%n podcasts", podcastCount ) + "
    • "; + + if( folderCount ) message += "
    • " + i18n( "1 folder", "%n folders", folderCount ) + "
    • "; + + if( lastfmCount ) message += "
    • " + i18n( "1 last.fm stream", "%n last.fm streams", lastfmCount ) + "
    • "; + + message += i18n( "

    to be irreversibly deleted.

    " ); + + if( podcastCount ) + message += i18n( "

    All downloaded podcast episodes will also be deleted.

    " ); + + if( totalCount > 0 ) + { + int button = KMessageBox::warningContinueCancel( this, message, QString::null, KStdGuiItem::del() ); + if( button != KMessageBox::Continue ) + return; + } + + foreachType( QPtrList, selected ) + { + if ( isPlaylistTrackItem( *it ) ) + { + static_cast( (*it)->parent() )->removeTrack( (*it) ); + continue; + } + if ( isDynamic( *it ) ) + static_cast( *it )->deleting(); + delete (*it); + } + + // used for deleting playlists first, then folders. + if( playlistCount ) + { + if( deletePlaylists( playlistsToDelete ) ) + { + foreachType( QPtrList, playlistsToDelete ) + { + m_dynamicEntries.remove(*it); + delete (*it); + } + } + } + + if( podcastCount ) + { + if( deletePodcasts( podcastsToDelete ) ) + foreachType( QPtrList, podcastsToDelete ) + delete (*it); + } + + foreachType( QPtrList, playlistFoldersToDelete ) + delete (*it); + + foreachType( QPtrList, podcastFoldersToDelete ) + removePodcastFolder( *it ); + + if( playlistCount || trackCount ) + savePlaylists(); + + if( streamCount ) saveStreams(); + if( smartyCount ) saveSmartPlaylists(); + if( dynamicCount ) saveDynamics(); + if( lastfmCount ) saveLastFm(); +} + +// remove podcast folders. we need to do this recursively to ensure all children are removed from the db +void PlaylistBrowser::removePodcastFolder( PlaylistCategory *item ) +{ + if( !item ) return; + if( !item->childCount() ) + { + CollectionDB::instance()->removePodcastFolder( item->id() ); + delete item; + return; + } + + QListViewItem *child = item->firstChild(); + while( child ) + { + QListViewItem *nextChild = 0; + if( isPodcastChannel( child ) ) + { + #define child static_cast(child) + nextChild = child->nextSibling(); + CollectionDB::instance()->removePodcastChannel( child->url() ); + m_podcastItemsToScan.remove( child ); + #undef child + } + else if( isCategory( child ) ) + { + nextChild = child->nextSibling(); + removePodcastFolder( static_cast(child) ); + } + + child = nextChild; + } + CollectionDB::instance()->removePodcastFolder( item->id() ); + delete item; +} + +void PlaylistBrowser::renameSelectedItem() //SLOT +{ + QListViewItem *item = m_listview->currentItem(); + if( !item ) return; + + if( item == m_randomDynamic || item == m_suggestedDynamic ) + return; + + PlaylistBrowserEntry *entry = dynamic_cast( item ); + if ( entry ) + entry->slotRenameItem(); +} + + +void PlaylistBrowser::renamePlaylist( QListViewItem* item, const QString& newName, int ) //SLOT +{ + PlaylistBrowserEntry *entry = dynamic_cast( item ); + if ( entry ) + entry->slotPostRenameItem( newName ); +} + + +void PlaylistBrowser::saveM3U( PlaylistEntry *item, bool append ) +{ + QFile file( item->url().path() ); + + if( append ? file.open( IO_WriteOnly | IO_Append ) : file.open( IO_WriteOnly ) ) + { + QTextStream stream( &file ); + if( !append ) + stream << "#EXTM3U\n"; + QPtrList trackList = append ? item->droppedTracks() : item->trackList(); + for( TrackItemInfo *info = trackList.first(); info; info = trackList.next() ) + { + stream << "#EXTINF:"; + stream << info->length(); + stream << ','; + stream << info->title(); + stream << '\n'; + stream << (info->url().protocol() == "file" ? info->url().path() : info->url().url()); + stream << "\n"; + } + + file.close(); + } +} + +void PlaylistBrowser::saveXSPF( PlaylistEntry *item, bool append ) +{ + XSPFPlaylist* playlist = new XSPFPlaylist(); + + playlist->setCreator( "Amarok" ); + playlist->setTitle( item->text(0) ); + + XSPFtrackList list; + + QPtrList trackList = append ? item->droppedTracks() : item->trackList(); + for( TrackItemInfo *info = trackList.first(); info; info = trackList.next() ) + { + XSPFtrack track; + MetaBundle b( info->url() ); + track.creator = b.artist(); + track.title = b.title(); + track.location = b.url().url(); + list.append( track ); + } + + playlist->setTrackList( list, append ); + + QFile file( item->url().path() ); + if ( !file.open( IO_WriteOnly ) ) + warning() << "Could not open file " << file.name() + << " write-only" << endl; + else { + QTextStream stream ( &file ); + playlist->save( stream, 2 ); + file.close(); + } +} + + +void PlaylistBrowser::savePLS( PlaylistEntry *item, bool append ) +{ + QFile file( item->url().path() ); + + if( append ? file.open( IO_WriteOnly | IO_Append ) : file.open( IO_WriteOnly ) ) + { + QTextStream stream( &file ); + QPtrList trackList = append ? item->droppedTracks() : item->trackList(); + stream << "NumberOfEntries=" << trackList.count() << endl; + int c=1; + for( TrackItemInfo *info = trackList.first(); info; info = trackList.next(), ++c ) + { + stream << "File" << c << "="; + stream << (info->url().protocol() == "file" ? info->url().path() : info->url().url()); + stream << "\nTitle" << c << "="; + stream << info->title(); + stream << "\nLength" << c << "="; + stream << info->length(); + stream << "\n"; + } + + stream << "Version=2\n"; + file.close(); + } +} + +#include +#include +#include "playlistloader.h" +//this function (C) Copyright 2003-4 Max Howell, (C) Copyright 2004 Mark Kretschmann +KURL::List PlaylistBrowser::recurse( const KURL &url ) +{ + typedef QMap FileMap; + + KDirLister lister( false ); + lister.setAutoUpdate( false ); + lister.setAutoErrorHandlingEnabled( false, 0 ); + lister.openURL( url ); + + while( !lister.isFinished() ) + kapp->eventLoop()->processEvents( QEventLoop::ExcludeUserInput ); + + KFileItemList items = lister.items(); //returns QPtrList, so we MUST only do it once! + KURL::List urls; + FileMap files; + for( KFileItem *item = items.first(); item; item = items.next() ) { + if( item->isFile() ) { files[item->name()] = item->url(); continue; } + if( item->isDir() ) urls += recurse( item->url() ); + } + + foreachType( FileMap, files ) + // users often have playlist files that reflect directories + // higher up, or stuff in this directory. Don't add them as + // it produces double entries + if( !PlaylistFile::isPlaylistFile( (*it).fileName() ) ) + urls += *it; + + return urls; +} + + +void PlaylistBrowser::currentItemChanged( QListViewItem *item ) //SLOT +{ + // rename remove and delete buttons are disabled if there are no playlists + // rename and delete buttons are disabled for track items + + bool enable_remove = false; + bool enable_rename = false; + + if ( !item ) + goto enable_buttons; + + if ( isCategory( item ) ) + { + if( static_cast(item)->isFolder() && + static_cast(item)->isKept() ) + enable_remove = enable_rename = true; + } + else if ( isPodcastChannel( item ) ) + { + enable_remove = true; + enable_rename = false; + } + else if ( !isPodcastEpisode( item ) ) + enable_remove = enable_rename = static_cast(item)->isKept(); + + static_cast(item)->updateInfo(); + + enable_buttons: + + removeButton->setEnabled( enable_remove ); + renameButton->setEnabled( enable_rename ); +} + + +void PlaylistBrowser::customEvent( QCustomEvent *e ) +{ + // If a playlist is found in collection folders it will be automatically added to the playlist browser + // The ScanController sends a PlaylistFoundEvent when a playlist is found. + + ScanController::PlaylistFoundEvent* p = static_cast( e ); + addPlaylist( p->path(), 0, false, true ); +} + + +void PlaylistBrowser::slotAddMenu( int id ) //SLOT +{ + switch( id ) + { + case STREAM: + addStream(); + break; + + case SMARTPLAYLIST: + addSmartPlaylist(); + break; + + case PODCAST: + addPodcast(); + break; + + case ADDDYNAMIC: + ConfigDynamic::dynamicDialog(this); + break; + } +} + + +void PlaylistBrowser::slotAddPlaylistMenu( int id ) //SLOT +{ + switch( id ) + { + case PLAYLIST: + createPlaylist( 0/*base cat*/, false/*make empty*/ ); + break; + + case PLAYLIST_IMPORT: + openPlaylist(); + break; + } +} + + +/** + ************************ + * Context Menu Entries + ************************ + **/ + +void PlaylistBrowser::showContextMenu( QListViewItem *item, const QPoint &p, int ) //SLOT +{ + if( !item ) return; + + PlaylistBrowserEntry *entry = dynamic_cast( item ); + if ( entry ) + entry->showContextMenu( p ); +} + +///////////////////////////////////////////////////////////////////////////// +// CLASS PlaylistBrowserView +//////////////////////////////////////////////////////////////////////////// + +PlaylistBrowserView::PlaylistBrowserView( QWidget *parent, const char *name ) + : KListView( parent, name ) + , m_marker( 0 ) +{ + addColumn( i18n("Playlists") ); + + setSelectionMode( QListView::Extended ); + setResizeMode( QListView::AllColumns ); + setShowSortIndicator( true ); + setRootIsDecorated( true ); + + setDropVisualizer( true ); //the visualizer (a line marker) is drawn when dragging over tracks + setDropHighlighter( true ); //and the highligther (a focus rect) is drawn when dragging over playlists + setDropVisualizerWidth( 3 ); + setAcceptDrops( true ); + + setTreeStepSize( 20 ); + + connect( this, SIGNAL( mouseButtonPressed ( int, QListViewItem *, const QPoint &, int ) ), + this, SLOT( mousePressed( int, QListViewItem *, const QPoint &, int ) ) ); + + //TODO moving tracks + //connect( this, SIGNAL( moved(QListViewItem *, QListViewItem *, QListViewItem * )), + // this, SLOT( itemMoved(QListViewItem *, QListViewItem *, QListViewItem * ))); +} + +PlaylistBrowserView::~PlaylistBrowserView() { } + +void PlaylistBrowserView::contentsDragEnterEvent( QDragEnterEvent *e ) +{ + e->accept( e->source() == viewport() || KURLDrag::canDecode( e ) ); +} + +void PlaylistBrowserView::contentsDragMoveEvent( QDragMoveEvent* e ) +{ + //Get the closest item _before_ the cursor + const QPoint p = contentsToViewport( e->pos() ); + QListViewItem *item = itemAt( p ); + if( !item ) { + eraseMarker(); + return; + } + + //only for track items (for playlist items we draw the highlighter) + if( isPlaylistTrackItem( item ) ) + item = item->itemAbove(); + + if( item != m_marker ) + { + eraseMarker(); + m_marker = item; + viewportPaintEvent( 0 ); + } +} + +void PlaylistBrowserView::contentsDragLeaveEvent( QDragLeaveEvent* ) +{ + eraseMarker(); +} + + +void PlaylistBrowserView::contentsDropEvent( QDropEvent *e ) +{ + QListViewItem *parent = 0; + QListViewItem *after; + + const QPoint p = contentsToViewport( e->pos() ); + QListViewItem *item = itemAt( p ); + if( !item ) { + eraseMarker(); + return; + } + + if( !isPlaylist( item ) ) + findDrop( e->pos(), parent, after ); + + eraseMarker(); + + if( e->source() == this ) + { + moveSelectedItems( item ); // D&D sucks, do it ourselves + } + else { + KURL::List decodedList; + QValueList bundles; + if( KURLDrag::decode( e, decodedList ) ) + { + KURL::List::ConstIterator it = decodedList.begin(); + MetaBundle first( *it ); + const QString album = first.album(); + const QString artist = first.artist(); + + int suggestion = !album.stripWhiteSpace().isEmpty() ? 1 : !artist.stripWhiteSpace().isEmpty() ? 2 : 3; + + for ( ; it != decodedList.end(); ++it ) + { + if( isCategory(item) ) + { // check if it is podcast category + QListViewItem *cat = item; + while( isCategory(cat) && cat!=PlaylistBrowser::instance()->podcastCategory() ) + cat = cat->parent(); + + if( cat == PlaylistBrowser::instance()->podcastCategory() ) + PlaylistBrowser::instance()->addPodcast(*it, item); + continue; + } + + QString filename = (*it).fileName(); + + if( filename.endsWith("m3u") || filename.endsWith("pls") ) + PlaylistBrowser::instance()->addPlaylist( (*it).path() ); + else if( ContextBrowser::hasContextProtocol( *it ) ) + { + KURL::List urls = ContextBrowser::expandURL( *it ); + for( KURL::List::iterator i = urls.begin(); + i != urls.end(); + i++ ) + { + MetaBundle mb(*i); + bundles.append( mb ); + } + } + else //TODO: check canDecode ? + { + MetaBundle mb(*it); + bundles.append( mb ); + if( suggestion == 1 && mb.album()->lower().stripWhiteSpace() != album.lower().stripWhiteSpace() ) + suggestion = 2; + if( suggestion == 2 && mb.artist()->lower().stripWhiteSpace() != artist.lower().stripWhiteSpace() ) + suggestion = 3; + } + } + + if( bundles.isEmpty() ) return; + + if( parent && isPlaylist( parent ) ) { + //insert the dropped tracks + PlaylistEntry *playlist = static_cast( parent ); + playlist->insertTracks( after, bundles ); + } + else //dropped on a playlist item + { + QListViewItem *parent = item; + bool isPlaylistFolder = false; + + while( parent ) + { + if( parent == PlaylistBrowser::instance()->m_playlistCategory ) + { + isPlaylistFolder = true; + break; + } + parent = parent->parent(); + } + + if( isPlaylist( item ) ) { + PlaylistEntry *playlist = static_cast( item ); + //append the dropped tracks + playlist->insertTracks( 0, bundles ); + } + else if( isCategory( item ) && isPlaylistFolder ) + { + PlaylistBrowser *pb = PlaylistBrowser::instance(); + QString title = suggestion == 1 ? album + : suggestion == 2 ? artist + : QString::null; + if ( pb->createPlaylist( item, false, title ) ) + pb->m_lastPlaylist->insertTracks( 0, bundles ); + } + } + } + else + e->ignore(); + } + +} + +void PlaylistBrowserView::eraseMarker() //SLOT +{ + if( m_marker ) + { + QRect spot; + if( isPlaylist( m_marker ) ) + spot = drawItemHighlighter( 0, m_marker ); + else + spot = drawDropVisualizer( 0, 0, m_marker ); + + m_marker = 0; + viewport()->repaint( spot, false ); + } +} + +void PlaylistBrowserView::viewportPaintEvent( QPaintEvent *e ) +{ + if( e ) KListView::viewportPaintEvent( e ); //we call with 0 in contentsDropEvent() + + if( m_marker ) + { + QPainter painter( viewport() ); + if( isPlaylist( m_marker ) ) //when dragging on a playlist we draw a focus rect + drawItemHighlighter( &painter, m_marker ); + else //when dragging on a track we draw a line marker + painter.fillRect( drawDropVisualizer( 0, 0, m_marker ), + QBrush( colorGroup().highlight(), QBrush::Dense4Pattern ) ); + } +} + +void PlaylistBrowserView::mousePressed( int button, QListViewItem *item, const QPoint &pnt, int ) //SLOT +{ + // this function expande/collapse the playlist if the +/- symbol has been pressed + // and show the save menu if the save icon has been pressed + + if( !item || button != LeftButton ) return; + + if( isPlaylist( item ) ) + { + QPoint p = mapFromGlobal( pnt ); + p.setY( p.y() - header()->height() ); + + QRect itemrect = itemRect( item ); + + QRect expandRect = QRect( 4, itemrect.y() + (item->height()/2) - 5, 15, 15 ); + if( expandRect.contains( p ) ) { //expand symbol clicked + setOpen( item, !item->isOpen() ); + return; + } + } +} + +void PlaylistBrowserView::moveSelectedItems( QListViewItem *newParent ) +{ + if( !newParent ) + return; + + QListViewItem *after=0; + if( isDynamic( newParent ) || isPodcastChannel( newParent ) || + isSmartPlaylist( newParent ) || isPodcastEpisode( newParent ) || isStream( newParent ) ) + { + after = newParent; + newParent = newParent->parent(); + } + + #define newParent static_cast(newParent) + if( !newParent->isKept() ) + return; + #undef newParent + + QPtrList selected; + QListViewItemIterator it( this, QListViewItemIterator::Selected ); + for( ; it.current(); ++it ) + { + if( !(*it)->parent() ) //must be a base category we are draggin' + continue; + + selected.append( *it ); + } + + for( QListViewItem *item = selected.first(); item; item = selected.next() ) + { + QListViewItem *itemParent = item->parent(); + if( isPlaylistTrackItem( item ) ) + { + if( isPlaylistTrackItem( newParent ) ) + { + if( !after && newParent != newParent->parent()->firstChild() ) + after = newParent->itemAbove(); + + newParent = static_cast(newParent->parent()); + } + else if( !isPlaylist( newParent ) ) + continue; + + + #define newParent static_cast(newParent) + newParent->insertTracks( after, KURL::List( static_cast(item)->url() )); + #undef newParent + #define itemParent static_cast(itemParent) + itemParent->removeTrack( static_cast(item) ); + #undef itemParent + continue; + } + else if( !isCategory( newParent ) ) + continue; + + QListViewItem *base = newParent; + while( base->parent() ) + base = base->parent(); + + if( base == PlaylistBrowser::instance()->m_playlistCategory && isPlaylist( item ) || + base == PlaylistBrowser::instance()->m_streamsCategory && isStream( item ) || + base == PlaylistBrowser::instance()->m_smartCategory && isSmartPlaylist( item ) || + base == PlaylistBrowser::instance()->m_dynamicCategory && isDynamic( item ) ) + { + // if the item is from the cool streams dir, copy it. + if( item->parent() == PlaylistBrowser::instance()->m_coolStreams ) + { + #define item static_cast(item) + new StreamEntry( newParent, after, item->url(), item->title() ); + #undef item + } + else // otherwise, we move it + { + itemParent->takeItem( item ); + newParent->insertItem( item ); + } + newParent->sortChildItems( 0, true ); + } + else if( base == PlaylistBrowser::instance()->m_podcastCategory && isPodcastChannel( item ) ) + { + #define item static_cast(item) + item->setParent( static_cast(newParent) ); + #undef item + } + } +} + +void PlaylistBrowserView::rename( QListViewItem *item, int c ) +{ + KListView::rename( item, c ); + + QRect rect( itemRect( item ) ); + int fieldX = rect.x() + treeStepSize() + 2; + int fieldW = rect.width() - treeStepSize() - 2; + + KLineEdit *renameEdit = renameLineEdit(); + renameEdit->setGeometry( fieldX, rect.y(), fieldW, rect.height() ); + renameEdit->show(); +} + +void PlaylistBrowserView::keyPressEvent( QKeyEvent *e ) +{ + switch( e->key() ) { + case Key_Space: //load + PlaylistBrowser::instance()->slotDoubleClicked( currentItem() ); + break; + + case SHIFT+Key_Delete: //delete + case Key_Delete: //remove + PlaylistBrowser::instance()->removeSelectedItems(); + break; + + case Key_F2: //rename + PlaylistBrowser::instance()->renameSelectedItem(); + break; + + default: + KListView::keyPressEvent( e ); + break; + } +} + + +void PlaylistBrowserView::startDrag() +{ + KURL::List urls; + KURL::List itemList; // this is for CollectionDB::createDragPixmap() + KURL::List podList; // used to add podcast episodes of the same channel in reverse order (usability) + PodcastEpisode *lastPodcastEpisode = 0; // keep track of the last podcastepisode we visited. + KMultipleDrag *drag = new KMultipleDrag( this ); + + QListViewItemIterator it( this, QListViewItemIterator::Selected ); + QString pixText = QString::null; + uint count = 0; + + for( ; it.current(); ++it ) + { + if( !isPodcastEpisode( *it ) && !podList.isEmpty() ) + { // we left the podcast channel, so append those items we iterated over + urls += podList; + podList.clear(); + } + + if( isPlaylist( *it ) ) + { + urls += static_cast(*it)->url(); + itemList += static_cast(*it)->url(); + pixText = (*it)->text(0); + } + + else if( isStream( *it ) ) + { + urls += static_cast(*it)->url(); + itemList += KURL::fromPathOrURL( "stream://" ); + pixText = (*it)->text(0); + } + + else if( isLastFm( *it ) ) + { + urls += static_cast(*it)->url(); + itemList += static_cast(*it)->url(); + pixText = (*it)->text(0); + } + + else if( isPodcastEpisode( *it ) ) + { + if( (*it)->parent()->isSelected() ) continue; + if( !podList.isEmpty() && lastPodcastEpisode && lastPodcastEpisode->QListViewItem::parent() != (*it)->parent() ) + { // we moved onto a new podcast channel + urls += podList; + podList.clear(); + } + #define item static_cast(*it) + if( item->isOnDisk() ) + { + podList.prepend( item->localUrl() ); + itemList += item->url(); + } + else + { + podList.prepend( item->url() ); + itemList += item->url(); + } + lastPodcastEpisode = item; + pixText = (*it)->text(0); + #undef item + } + else if( isPodcastChannel( *it ) ) + { + #define item static_cast(*it) + if( !item->isPolished() ) + item->load(); + + QListViewItem *child = item->firstChild(); + KURL::List tmp; + // we add the podcasts in reverse, its much nicer to add them chronologically :) + while( child ) + { + PodcastEpisode *pe = static_cast( child ); + if( pe->isOnDisk() ) + tmp.prepend( pe->localUrl() ); + else + tmp.prepend( pe->url() ); + child = child->nextSibling(); + } + urls += tmp; + itemList += KURL::fromPathOrURL( item->url().url() ); + pixText = (*it)->text(0); + #undef item + } + + else if( isSmartPlaylist( *it ) ) + { + SmartPlaylist *item = static_cast( *it ); + + if( !item->query().isEmpty() ) + { + QTextDrag *textdrag = new QTextDrag( item->text(0) + '\n' + item->query(), 0 ); + textdrag->setSubtype( "amarok-sql" ); + drag->addDragObject( textdrag ); + } + itemList += KURL::fromPathOrURL( QString("smartplaylist://%1").arg( item->text(0) ) ); + pixText = (*it)->text(0); + } + + else if( isDynamic( *it ) ) + { + DynamicEntry *item = static_cast( *it ); + + // Serialize pointer to string + const QString str = QString::number( reinterpret_cast( item ) ); + + QTextDrag *textdrag = new QTextDrag( str, 0 ); + textdrag->setSubtype( "dynamic" ); + drag->addDragObject( textdrag ); + itemList += KURL::fromPathOrURL( QString("dynamic://%1").arg( item->text(0) ) ); + pixText = (*it)->text(0); + } + + else if( isPlaylistTrackItem( *it ) ) + { + if( (*it)->parent()->isSelected() ) continue; + urls += static_cast(*it)->url(); + itemList += static_cast(*it)->url(); + } + count++; + } + + if( !podList.isEmpty() ) + urls += podList; + + if( count > 1 ) pixText = QString::null; + + drag->addDragObject( new KURLDrag( urls, viewport() ) ); + drag->setPixmap( CollectionDB::createDragPixmap( itemList, pixText ), + QPoint( CollectionDB::DRAGPIXMAP_OFFSET_X, CollectionDB::DRAGPIXMAP_OFFSET_Y ) ); + drag->dragCopy(); +} + +///////////////////////////////////////////////////////////////////////////// +// CLASS PlaylistDialog +//////////////////////////////////////////////////////////////////////////// + +QString PlaylistDialog::getSaveFileName( const QString &suggestion, bool proposeOverwriting ) //static +{ + PlaylistDialog dialog; + if( !suggestion.isEmpty() ) + { + QString path = Amarok::saveLocation("playlists/") + "%1" + ".m3u"; + if( QFileInfo( path.arg( suggestion ) ).exists() && !proposeOverwriting ) + { + int n = 2; + while( QFileInfo( path.arg( i18n( "%1 (%2)" ).arg( suggestion, QString::number( n ) ) ) ).exists() ) + n++; + dialog.edit->setText( i18n( "%1 (%2)" ).arg( suggestion, QString::number( n ) ) ); + } + else + dialog.edit->setText( suggestion ); + } + if( dialog.exec() == Accepted ) + return dialog.result; + return QString(); +} + +PlaylistDialog::PlaylistDialog() + : KDialogBase( PlaylistWindow::self(), "saveplaylist", true /*modal*/, + i18n( "Save Playlist" ), Ok | Cancel | User1, Ok, false /*separator*/, + KGuiItem( i18n( "Save to location..." ), SmallIconSet( Amarok::icon( "files" ) ) ) ) + , customChosen( false ) +{ + QVBox *vbox = makeVBoxMainWidget(); + QLabel *label = new QLabel( i18n( "&Enter a name for the playlist:" ), vbox ); + edit = new KLineEdit( vbox ); + edit->setFocus(); + label->setBuddy( edit ); + enableButtonOK( false ); + connect( edit, SIGNAL( textChanged( const QString & ) ), + this, SLOT( slotTextChanged( const QString& ) ) ); + connect( this, SIGNAL( user1Clicked() ), SLOT( slotCustomPath() ) ); +} + +void PlaylistDialog::slotOk() +{ + // TODO Remove this hack for 1.2. It's needed because playlists was a file once. + QString folder = Amarok::saveLocation( "playlists" ); + QFileInfo info( folder ); + if ( !info.isDir() ) QFile::remove( folder ); + + if( !customChosen && !edit->text().isEmpty() ) + result = Amarok::saveLocation( "playlists/" ) + edit->text() + ".m3u"; + + if( !QFileInfo( result ).exists() || + KMessageBox::warningContinueCancel( + PlaylistWindow::self(), + i18n( "A playlist named \"%1\" already exists. Do you want to overwrite it?" ).arg( edit->text() ), + i18n( "Overwrite Playlist?" ), i18n( "Overwrite" ) ) == KMessageBox::Continue ) + { + KDialogBase::slotOk(); + } +} + +void PlaylistDialog::slotTextChanged( const QString &s ) +{ + enableButtonOK( !s.isEmpty() ); +} + +void PlaylistDialog::slotCustomPath() +{ + result = KFileDialog::getSaveFileName( ":saveplaylists", "*.m3u" ); + if( !result.isNull() ) + { + edit->setText( result ); + edit->setReadOnly( true ); + enableButtonOK( true ); + customChosen = true; + } +} + + +InfoPane::InfoPane( QWidget *parent ) + : QVBox( parent ), + m_enable( false ), + m_storedHeight( 100 ) +{ + QFrame *container = new QVBox( this, "container" ); + container->hide(); + + { + QFrame *box = new QHBox( container ); + box->setMargin( 3 ); + box->setBackgroundMode( Qt::PaletteBase ); + + m_infoBrowser = new HTMLView( box, "extended_info", false /*DNDEnabled*/, false /*JS + enabled*/ ); + + container->setFrameStyle( QFrame::StyledPanel ); + container->setMargin( 3 ); + container->setBackgroundMode( Qt::PaletteBase ); + } + + m_pushButton = new KPushButton( KGuiItem( i18n("&Show Extended Info"), "info" ), this ); + m_pushButton->setToggleButton( true ); + m_pushButton->setEnabled( m_enable ); + connect( m_pushButton, SIGNAL(toggled( bool )), SLOT(toggle( bool )) ); + + //Set the height to fixed. The button shouldn't be resized. + setFixedHeight( m_pushButton->sizeHint().height() ); +} + +InfoPane::~InfoPane() +{ + // Ensure the KHTMLPart dies before its KHTMLView dies, + // because KHTMLPart's dtoring relies on its KHTMLView still being alive + // (see bug 130494). + delete m_infoBrowser; +} + +int +InfoPane::getHeight() +{ + if( static_cast( child( "container" ) )->isShown() ) + { + //If the InfoPane is shown, return true height. + return static_cast( parentWidget() )->sizes().last(); + } + + return m_storedHeight; +} + +void +InfoPane::setStoredHeight( const int newHeight ) { + m_storedHeight = newHeight; +} + +void +InfoPane::toggle( bool toggled ) +{ + QSplitter *splitter = static_cast( parentWidget() ); + + if ( !toggled ) + { + //Save the height for later + setStoredHeight( splitter->sizes().last() ); + + //Set the height to fixed. The button shouldn't be resized. + setFixedHeight( m_pushButton->sizeHint().height() ); + + //Now the info pane is not shown, we can disable the button if necessary + m_pushButton->setEnabled( m_enable ); + } + else { + setMaximumHeight( ( int )( parentWidget()->height() / 1.5 ) ); + + //Restore the height of the InfoPane (change the splitter properties) + //Done every time since the pane forgets its height if you try to resize it while the info is hidden. + QValueList sizes = splitter->sizes(); + const int sizeOffset = getHeight() - sizes.last(); + sizes.first() -= sizeOffset; + sizes.last() += sizeOffset; + splitter->setSizes( sizes ); + + setMinimumHeight( 150 ); + } + + static_cast( child( "container" ) )->setShown( toggled ); +} + +void +InfoPane::setInfo( const QString &title, const QString &info ) +{ + //If the info pane is not shown, we can enable or disable the button depending on + //whether there is content to show. Otherwise, just remember what we wanted to do + //so we can do it later, when the user does hide the pane. + m_enable = !( info.isEmpty() && title.isEmpty() ); + if ( !static_cast(child("container"))->isShown() ) + m_pushButton->setEnabled( m_enable ); + + if( m_pushButton->isOn() ) + toggle( !(info.isEmpty() && title.isEmpty()) ); + + QString info_ = info; + info_.replace( "\n", "
    " ); + + m_infoBrowser->set( + m_enable ? + QString( "
    " + "
    " + "" + " %1 " + "" + "
    " + "" + "" + "" + "" + "
    " + " %2 " + "
    " + "
    " ).arg( title, info_ ) : + QString::null ); +} + +#include "playlistbrowser.moc" diff --git a/amarok/src/playlistbrowser.h b/amarok/src/playlistbrowser.h new file mode 100644 index 00000000..7ec42dd0 --- /dev/null +++ b/amarok/src/playlistbrowser.h @@ -0,0 +1,414 @@ +/*************************************************************************** + * copyright : (c) 2004 Pierpaolo Di Panfilo * + * (c) 2004 Mark Kretschmann * + * (c) 2005-2006 Seb Ruiz * + * (c) 2005 Gábor Lehel * + * (c) 2006 Adam Pigg * + * See COPYING file for licensing information * + ***************************************************************************/ + +#ifndef PLAYLISTBROWSER_H +#define PLAYLISTBROWSER_H + +#include "amarokconfig.h" +#include "playlistbrowseritem.h" +#include "podcastsettings.h" + +#include +#include +#include +#include +#include +#include +#include + +class KTextBrowser; +class KToolBar; + +class QCustomEvent; +class QColorGroup; +class QDragObject; +class QPainter; +class QPixmap; +class QPoint; +class QSplitter; +class QTimer; + +class HTMLView; +class InfoPane; +class PlaylistBrowserView; +class PlaylistTrackItem; + + +class PlaylistBrowser : public QVBox +{ + Q_OBJECT + friend class DynamicMode; + friend class PlaylistBrowserView; + + friend class PlaylistBrowserEntry; + friend class PlaylistCategory; + friend class PlaylistEntry; + friend class PlaylistTrackItem; + friend class PodcastChannel; //for changing podcast timer list + friend class PodcastEpisode; + friend class DynamicEntry; + friend class StreamEntry; + friend class SmartPlaylist; + + + public: + enum AddMode { PLAYLIST, PLAYLIST_IMPORT, STREAM, SMARTPLAYLIST, PODCAST, ADDDYNAMIC }; + + ~PlaylistBrowser(); + + void setInfo( const QString &title, const QString &info ); + + void addStream( QListViewItem *parent = 0 ); + void addSmartPlaylist( QListViewItem *parent = 0 ); + void addDynamic( QListViewItem *parent = 0 ); + void addPlaylist( const QString &path, QListViewItem *parent = 0, bool force=false, bool imported=false ); + PlaylistEntry *findPlaylistEntry( const QString &url, QListViewItem *parent=0 ) const; + int loadPlaylist( const QString &playlist, bool force=false ); + + void addPodcast( QListViewItem *parent = 0 ); + void addPodcast( const KURL &url, QListViewItem *parent = 0 ); + void loadPodcastsFromDatabase( PlaylistCategory *p = 0 ); + void registerPodcastSettings( const QString &title, const PodcastSettings *settings ); + + static bool savePlaylist( const QString &path, const QValueList &urls, + const QValueList &titles = QValueList(), + const QValueList &lengths = QValueList(), + bool relative = AmarokConfig::relativePlaylist() ); + + QString dynamicBrowserCache() const; + QString playlistBrowserCache() const; + QString podcastBrowserCache() const; + QString streamBrowserCache() const; + QString smartplaylistBrowserCache() const; + + PlaylistBrowserEntry *findItem( QString &t, int c ) const; + QListViewItem *findItemInTree( const QString &searchstring, int c ) const; + PodcastEpisode *findPodcastEpisode( const KURL &episode, const KURL &feed ) const; + + QPtrList dynamicEntries() const { return m_dynamicEntries; } + DynamicMode *findDynamicModeByTitle( const QString &title ); + QListViewItem *podcastCategory() const { return m_podcastCategory; } + + static PlaylistBrowser *instance() { + if(!s_instance) s_instance = new PlaylistBrowser("PlaylistBrowser"); + return s_instance; + } + + //following used by PlaylistSelection.cpp + PlaylistBrowserView* getListView() const { return m_listview; } + PlaylistCategory* getDynamicCategory() const { return m_dynamicCategory; } + void saveDynamics(); + + protected: + virtual void resizeEvent( QResizeEvent * ); + + signals: + void selectionChanged(); + + public slots: + void openPlaylist( QListViewItem *parent = 0 ); + void scanPodcasts(); + + private slots: + void abortPodcastQueue(); + void addSelectedToPlaylist( int options = -1 ); + void collectionScanDone(); + void currentItemChanged( QListViewItem * ); + void downloadPodcastQueue(); + void editStreamURL( StreamEntry *item, const bool readOnly=false ); + void removeSelectedItems(); + void renamePlaylist( QListViewItem*, const QString&, int ); + void renameSelectedItem(); + void invokeItem( QListViewItem*, const QPoint &, int column ); + void slotDoubleClicked( QListViewItem *item ); + + void slotAddMenu( int id ); + void slotAddPlaylistMenu( int id ); + void showContextMenu( QListViewItem*, const QPoint&, int ); + + void loadDynamicItems(); + + private: + PlaylistBrowser( const char* name=0 ); + void polish(); + + bool m_polished; + + PlaylistCategory* loadStreams(); + void loadCoolStreams(); + void saveStreams(); + + void loadLastfmStreams( const bool subscriber = false ); + void addLastFmRadio( QListViewItem *parent ); + void addLastFmCustomRadio( QListViewItem *parent ); + void saveLastFm(); + + PlaylistCategory* loadSmartPlaylists(); + void loadDefaultSmartPlaylists(); + void editSmartPlaylist( SmartPlaylist* ); + void saveSmartPlaylists( PlaylistCategory *smartCategory = NULL ); + void updateSmartPlaylists( QListViewItem *root ); + void updateSmartPlaylistElement( QDomElement& query ); + + PlaylistCategory* loadDynamics(); + void fixDynamicPlaylistPath( QListViewItem *item ); + QString guessPathFromPlaylistName( QString name ); + + PlaylistCategory* loadPodcasts(); + QMap loadPodcastFolders( PlaylistCategory *p ); + void changePodcastInterval(); + void configurePodcasts( QListViewItem *parent ); + void configurePodcasts( QPtrList &podcastChannelList, const QString &caption ); + void configureSelectedPodcasts(); + bool deleteSelectedPodcastItems( const bool removeItem=false, const bool silent=false ); + bool deletePodcasts( QPtrList items ); + void downloadSelectedPodcasts(); + void refreshPodcasts( QListViewItem *category ); + void removePodcastFolder( PlaylistCategory *item ); + void savePodcastFolderStates( PlaylistCategory *folder ); + PodcastChannel *findPodcastChannel( const KURL &feed, QListViewItem *parent=0 ) const; + + void markDynamicEntries(); + PlaylistBrowserEntry* findByName( QString name ); + + PlaylistCategory* loadPlaylists(); + void savePlaylists(); + void savePlaylist( PlaylistEntry * ); + bool createPlaylist( QListViewItem *parent = 0, bool current = true, QString title = 0 ); + bool deletePlaylists( QPtrList items ); + bool deletePlaylists( KURL::List items ); + + void customEvent( QCustomEvent* e ); + void saveM3U( PlaylistEntry *, bool append ); + void savePLS( PlaylistEntry *, bool append ); + void saveXSPF( PlaylistEntry *, bool append ); + + static KURL::List recurse( const KURL &url ); + + static PlaylistBrowser *s_instance; + + PlaylistCategory *m_playlistCategory; + PlaylistCategory *m_streamsCategory; + PlaylistCategory *m_smartCategory; + PlaylistCategory *m_dynamicCategory; + PlaylistCategory *m_podcastCategory; + PlaylistCategory *m_coolStreams; + PlaylistCategory *m_smartDefaults; + PlaylistCategory *m_lastfmCategory; + ShoutcastBrowser *m_shoutcastCategory; + PlaylistEntry *m_lastPlaylist; + + DynamicEntry *m_randomDynamic; + DynamicEntry *m_suggestedDynamic; + + bool m_coolStreamsOpen; + bool m_smartDefaultsOpen; + bool m_lastfmOpen; + + PlaylistBrowserView *m_listview; + KActionCollection *m_ac; + KAction *removeButton; + KAction *renameButton; + KActionMenu *viewMenuButton; + KActionMenu *addMenuButton; + KToolBar *m_toolbar; + QValueList m_dynamicSizeSave; + + QDict m_podcastSettings; + QPtrList m_dynamicEntries; + + QTimer *m_podcastTimer; + int m_podcastTimerInterval; //in ms + QPtrList m_podcastItemsToScan; + QPtrList m_podcastDownloadQueue; + + InfoPane *m_infoPane; + + bool m_removeDirt; + + QSplitter *m_splitter; +}; + + + +class PlaylistBrowserView : public KListView +{ + Q_OBJECT + + friend class PlaylistEntry; + + public: + PlaylistBrowserView( QWidget *parent, const char *name=0 ); + ~PlaylistBrowserView(); + + void rename( QListViewItem *item, int c ); + + protected: + virtual void keyPressEvent( QKeyEvent * ); + + private slots: + void mousePressed( int, QListViewItem *, const QPoint &, int ); + void moveSelectedItems( QListViewItem* newParent ); + + private: + void startDrag(); + void contentsDropEvent( QDropEvent* ); + void contentsDragEnterEvent( QDragEnterEvent* ); + void contentsDragMoveEvent( QDragMoveEvent* ); + void contentsDragLeaveEvent( QDragLeaveEvent* ); + void viewportPaintEvent( QPaintEvent* ); + void eraseMarker(); + + QListViewItem *m_marker; //track that has the drag/drop marker under it +}; + +class PlaylistDialog: public KDialogBase +{ + Q_OBJECT + public: + static QString getSaveFileName( const QString &suggestion = QString::null, bool proposeOverwriting = false ); + + private: + KLineEdit *edit; + bool customChosen; + QString result; + PlaylistDialog(); + + private slots: + void slotOk(); + + void slotTextChanged( const QString &s ); + + void slotCustomPath(); +}; + +// Returns true if item is Playlist, Stream, Smart Playlist or DynamicMode. +inline bool +isElement( QListViewItem *item ) +{ + if( !item ) + return false; + return item->rtti() == ( PlaylistEntry::RTTI || StreamEntry::RTTI || + SmartPlaylist::RTTI /*|| DynamicEntry::RTTI */) ? true : false; +} + +inline bool +isCategory( QListViewItem *item ) +{ + if( !item ) + return false; + return item->rtti() == PlaylistCategory::RTTI ? true : false; +} + +inline bool +isDynamic( QListViewItem *item ) +{ + if( !item ) + return false; + return item->rtti() == DynamicEntry::RTTI ? true : false; +} + +inline bool +isPlaylist( QListViewItem *item ) +{ + if( !item ) + return false; + return item->rtti() == PlaylistEntry::RTTI ? true : false; +} + +inline bool +isSmartPlaylist( QListViewItem *item ) +{ + if( !item ) + return false; + return item->rtti() == SmartPlaylist::RTTI ? true : false; +} + +inline bool +isPlaylistTrackItem( QListViewItem *item ) +{ + if( !item ) + return false; + return item->rtti() == PlaylistTrackItem::RTTI ? true : false; +} + +inline bool +isPodcastChannel( QListViewItem *item ) +{ + if( !item ) + return false; + return item->rtti() == PodcastChannel::RTTI ? true : false; +} + +inline bool +isPodcastEpisode( QListViewItem *item ) +{ + if( !item ) + return false; + return item->rtti() == PodcastEpisode::RTTI ? true : false; +} + +inline bool +isStream( QListViewItem *item ) +{ + if( !item ) + return false; + return item->rtti() == StreamEntry::RTTI ? true : false; +} + +inline bool +isLastFm( QListViewItem *item ) +{ + if( !item ) + return false; + return item->rtti() == LastFmEntry::RTTI ? true : false; +} + +inline QString +fileBaseName( const QString &filePath ) +{ + // this function returns the file name without extension + // (e.g. if the file path is "/home/user/playlist.m3u", "playlist" is returned + QString fileName = filePath.right( filePath.length() - filePath.findRev( '/' ) - 1 ); + return fileName.mid( 0, fileName.findRev( '.' ) ); +} + +inline QString +fileDirPath( const QString &filePath ) +{ + return filePath.left( filePath.findRev( '/' )+1 ); +} + + + +class InfoPane : public QVBox +{ + Q_OBJECT + +public: + InfoPane( QWidget *parent ); + ~InfoPane(); + int getHeight(); + void setStoredHeight( const int newHeight ); + +public slots: + void setInfo( const QString &title, const QString &info ); + +private slots: + void toggle( bool ); + +private: + HTMLView *m_infoBrowser; + KPushButton *m_pushButton; + bool m_enable; + int m_storedHeight; +}; + + +#endif diff --git a/amarok/src/playlistbrowseritem.cpp b/amarok/src/playlistbrowseritem.cpp new file mode 100644 index 00000000..058a0d67 --- /dev/null +++ b/amarok/src/playlistbrowseritem.cpp @@ -0,0 +1,3746 @@ +/*************************************************************************** + * copyright : (c) 2004 Pierpaolo Di Panfilo * + * (c) 2004 Mark Kretschmann * + * (c) 2005-2006 Seb Ruiz * + * (c) 2005 Christian Muehlhaeuser * + * (c) 2006 Bart Cerneels * + * (c) 2006 Ian Monroe * + * (c) 2006 Alexandre Oliveira * + * (c) 2006 Adam Pigg * + * (c) 2006 Bonne Eggleston * + * See COPYING file for licensing information * + ***************************************************************************/ + +#include "amarok.h" +#include "collectiondb.h" +#include "debug.h" +#include "dynamicmode.h" +#include "k3bexporter.h" +#include "playlist.h" +#include "playlistbrowser.h" +#include "playlistbrowseritem.h" +#include "playlistloader.h" //load() +#include "playlistselection.h" +#include "podcastbundle.h" +#include "podcastsettings.h" +#include "progressBar.h" +#include "metabundle.h" +#include "statusbar.h" +#include "tagdialog.h" +#include "threadmanager.h" +#include "mediabrowser.h" + +#include +#include +#include +#include //paintCell() +#include //paintCell() +#include + +#include //Used for Shoutcast random name generation +#include //KDE_VERSION ifndefs. Remove this once we reach a kde 4 dep +#include //smallIcon +#include //podcast retrieval +#include //podcast retrieval +#include +#include //podcast info box +#include +#include +#include +#include //podcast loading icons +#include +#include +#include + +#include //rename + + +///////////////////////////////////////////////////////////////////////////// +/// CLASS PlaylistReader +//////////////////////////////////////////////////////////////////////////// + +class PlaylistReader : public ThreadManager::DependentJob +{ + public: + PlaylistReader( QObject *recipient, const QString &path ) + : ThreadManager::DependentJob( recipient, "PlaylistReader" ) + , m_path( QDeepCopy( path ) ) {} + + virtual bool doJob() { + DEBUG_BLOCK + PlaylistFile pf = PlaylistFile( m_path ); + title = pf.title(); + for( BundleList::iterator it = pf.bundles().begin(); + it != pf.bundles().end(); + ++it ) + bundles += MetaBundle( (*it).url() ); + return true; + } + + virtual void completeJob() { + DEBUG_BLOCK + PlaylistFile pf = PlaylistFile( m_path ); + bundles = QDeepCopy( bundles ); + title = QDeepCopy( title ); + for( BundleList::iterator it = bundles.begin(); + it != bundles.end(); + ++it ) + *it = QDeepCopy( *it ); + ThreadManager::DependentJob::completeJob(); + } + + BundleList bundles; + QString title; + + private: + const QString m_path; +}; + +///////////////////////////////////////////////////////////////////////////// +/// CLASS PlaylistBrowserEntry +//////////////////////////////////////////////////////////////////////////// + +int +PlaylistBrowserEntry::compare( QListViewItem* item, int col, bool ascending ) const +{ + bool i1 = rtti() == PlaylistCategory::RTTI; + bool i2 = item->rtti() == PlaylistCategory::RTTI; + + // If only one of them is a category, make it show up before + if ( i1 != i2 ) + return i1 ? -1 : 1; + else if ( i1 ) //both are categories + { + PlaylistBrowser * const pb = PlaylistBrowser::instance(); + + QValueList toplevels; //define a static order for the toplevel categories + toplevels << pb->m_playlistCategory + << pb->m_smartCategory + << pb->m_dynamicCategory + << pb->m_streamsCategory + << pb->m_podcastCategory; + + for( int i = 0, n = toplevels.count(); i < n; ++i ) + { + if( this == toplevels[i] ) + return ascending ? -1 : 1; //same order whether or not it's ascending + if( item == toplevels[i] ) + return ascending ? 1 : -1; + } + } + + return KListViewItem::compare(item, col, ascending); +} + +void +PlaylistBrowserEntry::setKept( bool k ) +{ + m_kept = k; + if ( !k ) //Disable renaming by two single clicks + setRenameEnabled( 0, false ); +} + +void +PlaylistBrowserEntry::updateInfo() +{ + PlaylistBrowser::instance()->setInfo( QString::null, QString::null ); + return; +} + +void +PlaylistBrowserEntry::slotDoubleClicked() +{ + warning() << "No functionality for item double click implemented" << endl; +} + +void +PlaylistBrowserEntry::slotRenameItem() +{ + QListViewItem *parent = KListViewItem::parent(); + + while( parent ) + { + if( !static_cast( parent )->isKept() ) + return; + if( !parent->parent() ) + break; + parent = parent->parent(); + } + + setRenameEnabled( 0, true ); + static_cast( listView() )->rename( this, 0 ); +} + +void +PlaylistBrowserEntry::slotPostRenameItem( const QString /*newName*/ ) +{ + setRenameEnabled( 0, false ); +} + +///////////////////////////////////////////////////////////////////////////// +/// CLASS PlaylistCategory +//////////////////////////////////////////////////////////////////////////// + +PlaylistCategory::PlaylistCategory( QListView *parent, QListViewItem *after, const QString &t, bool isFolder ) + : PlaylistBrowserEntry( parent, after ) + , m_title( t ) + , m_id( -1 ) + , m_folder( isFolder ) +{ + setDragEnabled( false ); + setRenameEnabled( 0, isFolder ); + setPixmap( 0, SmallIcon( Amarok::icon( "files2" ) ) ); + setText( 0, t ); +} + + +PlaylistCategory::PlaylistCategory( PlaylistCategory *parent, QListViewItem *after, const QString &t, bool isFolder ) + : PlaylistBrowserEntry( parent, after ) + , m_title( t ) + , m_id( -1 ) + , m_folder( isFolder ) +{ + setDragEnabled( false ); + setRenameEnabled( 0, isFolder ); + setPixmap( 0, SmallIcon( Amarok::icon( "files" ) ) ); + setText( 0, t ); +} + + +PlaylistCategory::PlaylistCategory( QListView *parent, QListViewItem *after, const QDomElement &xmlDefinition, bool isFolder ) + : PlaylistBrowserEntry( parent, after ) + , m_id( -1 ) + , m_folder( isFolder ) +{ + setXml( xmlDefinition ); + setDragEnabled( false ); + setRenameEnabled( 0, isFolder ); + setPixmap( 0, SmallIcon( Amarok::icon( "files2") ) ); +} + + +PlaylistCategory::PlaylistCategory( PlaylistCategory *parent, QListViewItem *after, const QDomElement &xmlDefinition ) + : PlaylistBrowserEntry( parent, after ) + , m_id( -1 ) + , m_folder( true ) +{ + setXml( xmlDefinition ); + setDragEnabled( false ); + setRenameEnabled( 0, true ); + setPixmap( 0, SmallIcon( Amarok::icon( "files" ) ) ); +} + +PlaylistCategory::PlaylistCategory( PlaylistCategory *parent, QListViewItem *after, const QString &t, const int id ) + : PlaylistBrowserEntry( parent, after ) + , m_title( t ) + , m_id( id ) + , m_folder( true ) +{ + setDragEnabled( false ); + setRenameEnabled( 0, true ); + setPixmap( 0, SmallIcon( Amarok::icon( "files" ) ) ); + setText( 0, t ); +} + +void PlaylistCategory::okRename( int col ) +{ + QListViewItem::okRename( col ); + + if( m_id < 0 ) return; + + // update the database entry to have the correct name + const int parentId = parent() ? static_cast(parent())->id() : 0; + CollectionDB::instance()->updatePodcastFolder( m_id, text(0), parentId, isOpen() ); +} + +void PlaylistCategory::setXml( const QDomElement &xml ) +{ + PlaylistBrowser *pb = PlaylistBrowser::instance(); + QString tname = xml.tagName(); + if ( tname == "category" ) + { + setOpen( xml.attribute( "isOpen" ) == "true" ); + m_title = xml.attribute( "name" ); + setText( 0, m_title ); + QListViewItem *last = 0; + for( QDomNode n = xml.firstChild() ; !n.isNull(); n = n.nextSibling() ) + { + QDomElement e = n.toElement(); + if ( e.tagName() == "category" ) + last = new PlaylistCategory( this, last, e); + + else if ( e.tagName() == "default" ) { + if( e.attribute( "type" ) == "stream" ) + pb->m_coolStreamsOpen = (e.attribute( "isOpen" ) == "true"); + if( e.attribute( "type" ) == "smartplaylist" ) + pb->m_smartDefaultsOpen = (e.attribute( "isOpen" ) == "true"); + if( e.attribute( "type" ) == "lastfm" ) + pb->m_lastfmOpen = (e.attribute( "isOpen" ) == "true"); + continue; + } + else if ( e.tagName() == "stream" ) + last = new StreamEntry( this, last, e ); + + else if ( e.tagName() == "smartplaylist" ) + last = new SmartPlaylist( this, last, e ); + + else if ( e.tagName() == "playlist" ) + last = new PlaylistEntry( this, last, e ); + + else if ( e.tagName() == "lastfm" ) + last = new LastFmEntry( this, last, e ); + + else if ( e.tagName() == "dynamic" ) { + if ( e.attribute( "name" ) == i18n("Random Mix") || e.attribute( "name" ) == i18n("Suggested Songs" ) ) + continue; + last = new DynamicEntry( this, last, e ); + } + else if ( e.tagName() == "podcast" ) + { + const KURL url( n.namedItem( "url").toElement().text() ); + QString xmlLocation = Amarok::saveLocation( "podcasts/" ); + xmlLocation += n.namedItem( "cache" ).toElement().text(); + + QDomDocument xml; + QFile xmlFile( xmlLocation ); + QTextStream stream( &xmlFile ); + stream.setEncoding( QTextStream::UnicodeUTF8 ); + + if( !xmlFile.open( IO_ReadOnly ) || !xml.setContent( stream.read() ) ) + { + // Invalid podcasts should still be added to the browser, which means there is no cached xml. + last = new PodcastChannel( this, last, url, n ); + } + else + last = new PodcastChannel( this, last, url, n, xml ); + + #define item static_cast(last) + if( item->autoscan() ) + pb->m_podcastItemsToScan.append( item ); + #undef item + } + else if ( e.tagName() == "settings" ) + PlaylistBrowser::instance()->registerPodcastSettings( title(), new PodcastSettings( e, title() ) ); + + if( !e.attribute( "isOpen" ).isNull() && last ) + last->setOpen( e.attribute( "isOpen" ) == "true" ); //settings doesn't have an attribute "isOpen" + } + setText( 0, xml.attribute("name") ); + } +} + + +QDomElement PlaylistCategory::xml() const +{ + QDomDocument d; + QDomElement i = d.createElement("category"); + i.setAttribute( "name", text(0) ); + if( isOpen() ) + i.setAttribute( "isOpen", "true" ); + for( PlaylistBrowserEntry *it = static_cast( firstChild() ); it; + it = static_cast( it->nextSibling() ) ) + { + if( it == PlaylistBrowser::instance()->m_coolStreams ) + { + QDomDocument doc; + QDomElement e = doc.createElement("default"); + e.setAttribute( "type", "stream" ); + if( it->isOpen() ) + e.setAttribute( "isOpen", "true" ); + i.appendChild( d.importNode( e, true ) ); + } + else if( it == PlaylistBrowser::instance()->m_lastfmCategory ) + { + QDomDocument doc; + QDomElement e = doc.createElement("default"); + e.setAttribute( "type", "lastfm" ); + if( it->isOpen() ) + e.setAttribute( "isOpen", "true" ); + i.appendChild( d.importNode( e, true ) ); + } + else if( it == PlaylistBrowser::instance()->m_smartDefaults ) + { + QDomDocument doc; + QDomElement e = doc.createElement("default"); + e.setAttribute( "type", "smartplaylist" ); + if( it->isOpen() ) + e.setAttribute( "isOpen", "true" ); + i.appendChild( d.importNode( e, true ) ); + } + else if( it->isKept() ) + i.appendChild( d.importNode( it->xml(), true ) ); + } + return i; +} + +void +PlaylistCategory::slotDoubleClicked() +{ + setOpen( !isOpen() ); +} + +void +PlaylistCategory::slotRenameItem() +{ + if ( isKept() ) { + setRenameEnabled( 0, true ); + static_cast( listView() )->rename( this, 0 ); + } +} + + +void +PlaylistCategory::showContextMenu( const QPoint &position ) +{ + KPopupMenu menu( listView() ); + + if( !isKept() ) return; + + enum Actions { RENAME, REMOVE, CREATE, PLAYLIST, PLAYLIST_IMPORT, SMART, STREAM, DYNAMIC, + LASTFM, LASTFMCUSTOM, PODCAST, REFRESH, CONFIG, INTERVAL }; + + QListViewItem *parentCat = this; + + while( parentCat->parent() ) + parentCat = parentCat->parent(); + + bool isPodcastFolder = false; + + if( isFolder() ) { + menu.insertItem( SmallIconSet( Amarok::icon("edit") ), i18n( "&Rename" ), RENAME ); + menu.insertItem( SmallIconSet( Amarok::icon("remove") ), i18n( "&Delete" ), REMOVE ); + menu.insertSeparator(); + } + + if( parentCat == static_cast( PlaylistBrowser::instance()->m_playlistCategory) ) + { + menu.insertItem( SmallIconSet(Amarok::icon( "add_playlist" )), i18n("Create Playlist..."), PLAYLIST ); + menu.insertItem( SmallIconSet(Amarok::icon( "add_playlist" )), i18n("Import Playlist..."), PLAYLIST_IMPORT ); + } + + else if( parentCat == static_cast(PlaylistBrowser::instance()->m_smartCategory) ) + menu.insertItem( SmallIconSet(Amarok::icon( "add_playlist" )), i18n("New Smart Playlist..."), SMART ); + + else if( parentCat == static_cast(PlaylistBrowser::instance()->m_dynamicCategory) ) + menu.insertItem( SmallIconSet(Amarok::icon( "add_playlist" )), i18n("New Dynamic Playlist..."), DYNAMIC ); + + else if( parentCat == static_cast(PlaylistBrowser::instance()->m_streamsCategory) ) + menu.insertItem( SmallIconSet(Amarok::icon( "add_playlist" )), i18n("Add Radio Stream..."), STREAM ); + + else if( parentCat == static_cast(PlaylistBrowser::instance()->m_lastfmCategory) ) + { + menu.insertItem( SmallIconSet(Amarok::icon( "add_playlist" )), i18n("Add Last.fm Radio..."), LASTFM ); + menu.insertItem( SmallIconSet(Amarok::icon( "add_playlist" )), i18n("Add Custom Last.fm Radio..."), LASTFMCUSTOM ); + } + + else if( parentCat == static_cast(PlaylistBrowser::instance()->m_podcastCategory) ) + { + isPodcastFolder = true; + menu.insertItem( SmallIconSet( Amarok::icon( "add_playlist" ) ), i18n("Add Podcast..."), PODCAST ); + menu.insertItem( SmallIconSet( Amarok::icon( "refresh" ) ), i18n("Refresh All Podcasts"), REFRESH ); + menu.insertSeparator(); + menu.insertItem( SmallIconSet( Amarok::icon( "configure" ) ), i18n( "&Configure Podcasts..." ), CONFIG ); + if( parentCat->childCount() == 0 ) + menu.setItemEnabled( CONFIG, false ); + if( parentCat == this ) + menu.insertItem( SmallIconSet( Amarok::icon( "configure" ) ), i18n("Scan Interval..."), INTERVAL ); + } + + menu.insertSeparator(); + menu.insertItem( SmallIconSet( Amarok::icon( "files" ) ), i18n("Create Sub-Folder"), CREATE ); + + QListViewItem *tracker = 0; + PlaylistCategory *newFolder = 0; + int c; + QString name; + + switch( menu.exec( position ) ) { + case RENAME: + PlaylistBrowser::instance()->renameSelectedItem(); + break; + + case REMOVE: + PlaylistBrowser::instance()->removeSelectedItems(); + break; + + case PLAYLIST: + PlaylistBrowser::instance()->createPlaylist( this, false ); + break; + + case PLAYLIST_IMPORT: + PlaylistBrowser::instance()->openPlaylist( this ); + break; + + case SMART: + PlaylistBrowser::instance()->addSmartPlaylist( this ); + break; + + case STREAM: + PlaylistBrowser::instance()->addStream( this ); + break; + + case DYNAMIC: + ConfigDynamic::dynamicDialog( PlaylistBrowser::instance() ); + break; + + case LASTFM: + PlaylistBrowser::instance()->addLastFmRadio( this ); + break; + + case LASTFMCUSTOM: + PlaylistBrowser::instance()->addLastFmCustomRadio( this ); + break; + + case PODCAST: + PlaylistBrowser::instance()->addPodcast( this ); + break; + + case REFRESH: + PlaylistBrowser::instance()->refreshPodcasts( this ); + break; + + case CONFIG: + PlaylistBrowser::instance()->configurePodcasts( this ); + break; + + case CREATE: + tracker = firstChild(); + + for( c = 0 ; isCategory( tracker ); tracker = tracker->nextSibling() ) + { + if( tracker->text(0).startsWith( i18n("Folder") ) ) + c++; + if( !isCategory( tracker->nextSibling() ) ) + break; + } + name = i18n("Folder"); + if( c ) name = i18n("Folder %1").arg(c); + if( tracker == firstChild() && !isCategory( tracker ) ) tracker = 0; + + newFolder = new PlaylistCategory( this, tracker, name, true ); + newFolder->startRename( 0 ); + if( isPodcastFolder ) + { + c = CollectionDB::instance()->addPodcastFolder( newFolder->text(0), id(), false ); + newFolder->setId( c ); + } + + break; + + case INTERVAL: + PlaylistBrowser::instance()->changePodcastInterval(); + break; + } +} + + +void +PlaylistCategory::paintCell( QPainter *p, const QColorGroup &cg, int column, int width, int align ) +{ + QFont font( p->font() ); + + if( !m_folder ) { + font.setBold( true ); + } + + p->setFont( font ); + + KListViewItem::paintCell( p, cg, column, width, align ); +} + + +///////////////////////////////////////////////////////////////////////////// +/// CLASS PlaylistEntry +//////////////////////////////////////////////////////////////////////////// + +PlaylistEntry::PlaylistEntry( QListViewItem *parent, QListViewItem *after, const KURL &url, int tracks, int length ) + : PlaylistBrowserEntry( parent, after ) + , m_url( url ) + , m_length( length ) + , m_trackCount( tracks ) + , m_loading( false ) + , m_loaded( false ) + , m_dynamic( false ) + , m_loading1( new QPixmap( locate("data", "amarok/images/loading1.png" ) ) ) + , m_loading2( new QPixmap( locate("data", "amarok/images/loading2.png" ) ) ) + , m_lastTrack( 0 ) +{ + m_trackList.setAutoDelete( true ); + tmp_droppedTracks.setAutoDelete( false ); + + setDragEnabled( true ); + setRenameEnabled( 0, false ); + setExpandable( true ); + + setPixmap( 0, SmallIcon( Amarok::icon( "playlist" ) ) ); + + if( !m_trackCount ) + { + setText(0, i18n("Loading Playlist") ); + load(); //load the playlist file + } + // set text is called from within customEvent() +} + + +PlaylistEntry::PlaylistEntry( QListViewItem *parent, QListViewItem *after, const QDomElement &xmlDefinition ) + : PlaylistBrowserEntry( parent, after ) + , m_loading( false ) + , m_loaded( false ) + , m_dynamic( false ) + , m_loading1( new QPixmap( locate("data", "amarok/images/loading1.png" ) ) ) + , m_loading2( new QPixmap( locate("data", "amarok/images/loading2.png" ) ) ) + , m_lastTrack( 0 ) +{ + m_url.setPath( xmlDefinition.attribute( "file" ) ); + m_trackCount = xmlDefinition.namedItem( "tracks" ).toElement().text().toInt(); + m_length = xmlDefinition.namedItem( "length" ).toElement().text().toInt(); + + QString title = xmlDefinition.attribute( "title" ); + if( title.isEmpty() ) + { + title = fileBaseName( m_url.path() ); + title.replace( '_', ' ' ); + } + setText( 0, title ); + + m_trackList.setAutoDelete( true ); + tmp_droppedTracks.setAutoDelete( false ); + + setDragEnabled( true ); + setRenameEnabled( 0, false ); + setExpandable( true ); + + setPixmap( 0, SmallIcon( Amarok::icon( "playlist" ) ) ); + + if( !m_trackCount ) + { + setText(0, i18n("Loading Playlist") ); + load(); //load the playlist file + } + // set text is called from within customEvent() +} + + +PlaylistEntry::~PlaylistEntry() +{ + m_trackList.clear(); + tmp_droppedTracks.setAutoDelete( true ); + tmp_droppedTracks.clear(); +} + +void PlaylistEntry::load() +{ + if( m_loading ) return; + m_trackList.clear(); + m_length = 0; + m_loaded = false; + m_loading = true; + + //starts loading animation + m_iconCounter = 1; + startAnimation(); + connect( &m_animationTimer, SIGNAL(timeout()), this, SLOT(slotAnimation()) ); + + //delete all children, so that we don't duplicate things + while( firstChild() ) + delete firstChild(); + + //read the playlist file in a thread + ThreadManager::instance()->queueJob( new PlaylistReader( this, m_url.path() ) ); +} + +void PlaylistEntry::startAnimation() +{ + if( !m_animationTimer.isActive() ) + m_animationTimer.start( ANIMATION_INTERVAL ); +} + +void PlaylistEntry::stopAnimation() +{ + m_animationTimer.stop(); + m_dynamic ? + setPixmap( 0, SmallIcon( Amarok::icon( "favorites" ) ) ): + setPixmap( 0, SmallIcon( Amarok::icon( "playlist" ) ) ); +} + +void PlaylistEntry::slotAnimation() +{ + m_iconCounter % 2 ? + setPixmap( 0, *m_loading1 ): + setPixmap( 0, *m_loading2 ); + + m_iconCounter++; +} + +void PlaylistEntry::insertTracks( QListViewItem *after, KURL::List list ) +{ + QValueList bundles; + + foreachType( KURL::List, list ) + bundles += MetaBundle( *it ); + + insertTracks( after, bundles ); +} + +void PlaylistEntry::insertTracks( QListViewItem *after, QValueList bundles ) +{ + int pos = 0; + if( after ) { + pos = m_trackList.find( static_cast(after)->trackInfo() ) + 1; + if( pos == -1 ) + return; + } + + uint k = 0; + foreachType( QValueList, bundles ) + { + TrackItemInfo *newInfo = new TrackItemInfo( *it ); + m_length += newInfo->length(); + m_trackCount++; + + if( after ) { + m_trackList.insert( pos+k, newInfo ); + if( isOpen() ) + after = new PlaylistTrackItem( this, after, newInfo ); + } + else { + if( m_loaded && !m_loading ) { + m_trackList.append( newInfo ); + if( isOpen() ) //append the track item to the playlist + m_lastTrack = new PlaylistTrackItem( this, m_lastTrack, newInfo ); + } + else + tmp_droppedTracks.append( newInfo ); + } + ++k; + } + + if ( !m_loading ) { + PlaylistBrowser::instance()->savePlaylist( this ); + if ( !m_loaded ) + tmp_droppedTracks.clear(); // after saving, dropped tracks are on the file + } +} + + +void PlaylistEntry::removeTrack( QListViewItem *item, bool isLast ) +{ + #define item static_cast(item) + //remove a track and update playlist stats + TrackItemInfo *info = item->trackInfo(); + m_length -= info->length(); + m_trackCount--; + m_trackList.remove( info ); + if( item == m_lastTrack ) { + QListViewItem *above = item->itemAbove(); + m_lastTrack = above ? static_cast( above ) : 0; + } + delete item; + + #undef item + + if( isLast ) + PlaylistBrowser::instance()->savePlaylist( this ); +} + + +void PlaylistEntry::customEvent( QCustomEvent *e ) +{ + if( e->type() != (int)PlaylistReader::JobFinishedEvent ) + return; + +#define playlist static_cast(e) + QString str = playlist->title; + + if ( str.isEmpty() ) + str = fileBaseName( m_url.path() ); + + str.replace( '_', ' ' ); + setText( 0, str ); + + foreachType( BundleList, playlist->bundles ) + { + const MetaBundle &b = *it; + TrackItemInfo *info = new TrackItemInfo( b ); + m_trackList.append( info ); + m_length += info->length(); + if( isOpen() ) + m_lastTrack = new PlaylistTrackItem( this, m_lastTrack, info ); + } +#undef playlist + + //the tracks dropped on the playlist while it wasn't loaded are added to the track list + if( tmp_droppedTracks.count() ) { + + for ( TrackItemInfo *info = tmp_droppedTracks.first(); info; info = tmp_droppedTracks.next() ) { + m_trackList.append( info ); + } + tmp_droppedTracks.clear(); + } + + m_loading = false; + m_loaded = true; + stopAnimation(); //stops the loading animation + + if( m_trackCount && !m_dynamic && !isDynamic() ) setOpen( true ); + else listView()->repaintItem( this ); + + m_trackCount = m_trackList.count(); +} + +/** + * We destroy the tracks on collapsing the entry. However, if we are using dynamic mode, then we leave them + * because adding from a custom list is problematic if the entry has no children. Using load() is not effective + * since this is a threaded operation and would require pulling apart the entire class to make it work. + */ + +void PlaylistEntry::setOpen( bool open ) +{ + if( open == isOpen()) + return; + + if( open ) { //expand + + if( m_loaded ) { + //create track items + for ( TrackItemInfo *info = m_trackList.first(); info; info = m_trackList.next() ) + m_lastTrack = new PlaylistTrackItem( this, m_lastTrack, info ); + } + else if( !isDynamic() || !m_dynamic ) { + load(); + return; + } + } + else if( !isDynamic() || !m_dynamic ) { //collapse + + //delete all children + while( firstChild() ) + delete firstChild(); + + m_lastTrack = 0; + } + + QListViewItem::setOpen( open ); + PlaylistBrowser::instance()->savePlaylists(); +} + + +int PlaylistEntry::compare( QListViewItem* i, int /*col*/ ) const +{ + PlaylistEntry* item = static_cast(i); + + // Compare case-insensitive + return QString::localeAwareCompare( text( 0 ).lower(), item->text( 0 ).lower() ); +} + + +KURL::List PlaylistEntry::tracksURL() +{ + KURL::List list; + + if( m_loaded ) { //playlist loaded + for( TrackItemInfo *info = m_trackList.first(); info; info = m_trackList.next() ) + list += info->url(); + } + else + list = m_url; //playlist url + + return list; +} + +void PlaylistEntry::updateInfo() +{ + const QString body = "%1%2"; + + QString str = ""; + + str += body.arg( i18n( "Playlist" ), text(0) ); + str += body.arg( i18n( "Number of tracks" ), QString::number(m_trackCount) ); + str += body.arg( i18n( "Length" ), MetaBundle::prettyTime( m_length ) ); + str += body.arg( i18n( "Location" ), m_url.prettyURL() ); + str += "
    "; + + PlaylistBrowser::instance()->setInfo( text(0), str ); +} + +void PlaylistEntry::slotDoubleClicked() +{ + Playlist::instance()->proposePlaylistName( text(0), true ); + Playlist::instance()->insertMedia( url(), Playlist::DefaultOptions ); +} + + +void PlaylistEntry::showContextMenu( const QPoint &position ) +{ + KPopupMenu menu( listView() ); + + enum Id { LOAD, APPEND, QUEUE, RENAME, DELETE, MEDIADEVICE_COPY, MEDIADEVICE_SYNC }; + + menu.insertItem( SmallIconSet( Amarok::icon( "files" ) ), i18n( "&Load" ), LOAD ); + menu.insertItem( SmallIconSet( Amarok::icon( "add_playlist" ) ), i18n( "&Append to Playlist" ), APPEND ); + menu.insertItem( SmallIconSet( Amarok::icon( "queue_track" ) ), i18n( "&Queue Tracks" ), QUEUE ); + + if( MediaBrowser::isAvailable() ) + { + menu.insertSeparator(); + menu.insertItem( SmallIconSet( Amarok::icon( "device" ) ), + i18n( "&Transfer to Media Device" ), MEDIADEVICE_COPY ); + menu.insertItem( SmallIconSet( Amarok::icon( "device" ) ), + i18n( "&Synchronize to Media Device" ), MEDIADEVICE_SYNC ); + } + + menu.insertSeparator(); + menu.insertItem( SmallIconSet( Amarok::icon("edit") ), i18n( "&Rename" ), RENAME ); + menu.insertItem( SmallIconSet( Amarok::icon("remove_from_playlist") ), i18n( "&Delete" ), DELETE ); + menu.setAccel( Key_L, LOAD ); + menu.setAccel( Key_F2, RENAME ); + menu.setAccel( SHIFT+Key_Delete, DELETE ); + + switch( menu.exec( position ) ) + { + case LOAD: + Playlist::instance()->clear(); + Playlist::instance()->setPlaylistName( text(0), true ); + //FALL THROUGH + case APPEND: + PlaylistBrowser::instance()->addSelectedToPlaylist( Playlist::Append ); + break; + case QUEUE: + PlaylistBrowser::instance()->addSelectedToPlaylist( Playlist::Queue ); + break; + case RENAME: + PlaylistBrowser::instance()->renameSelectedItem(); + break; + case DELETE: + PlaylistBrowser::instance()->removeSelectedItems(); + break; + case MEDIADEVICE_COPY: + MediaBrowser::queue()->addURLs( tracksURL(), text(0) ); + break; + case MEDIADEVICE_SYNC: + MediaBrowser::queue()->syncPlaylist( text(0), url() ); + break; + } + +} + +void PlaylistEntry::slotPostRenameItem( const QString newName ) +{ + QString oldPath = url().path(); + QString newPath = fileDirPath( oldPath ) + newName + '.' + Amarok::extension( oldPath ); + + if ( std::rename( QFile::encodeName( oldPath ), QFile::encodeName( newPath ) ) == -1 ) + KMessageBox::error( listView(), i18n("Error renaming the file.") ); + else + setUrl( newPath ); +} + +void PlaylistEntry::setDynamic( bool enable ) +{ + if( enable != m_dynamic ) + { + if( enable ) + { + if( !m_loaded ) load(); // we need to load it to ensure that we can read the contents + setPixmap( 0, SmallIcon( Amarok::icon( "favorites" ) ) ); + } + else + setPixmap( 0, SmallIcon( Amarok::icon( "playlist" ) ) ); + + m_dynamic = enable; + } + + listView()->repaintItem( this ); +} + +void PlaylistEntry::setup() +{ + QFontMetrics fm( listView()->font() ); + int margin = listView()->itemMargin()*2; + int h = fm.lineSpacing(); + if ( h % 2 > 0 ) h++; + setHeight( h + margin ); +} + + +void PlaylistEntry::paintCell( QPainter *p, const QColorGroup &cg, int column, int width, int align ) +{ + //flicker-free drawing + static QPixmap buffer; + buffer.resize( width, height() ); + + if( buffer.isNull() ) + { + KListViewItem::paintCell( p, cg, column, width, align ); + return; + } + + QPainter pBuf( &buffer, true ); + // use alternate background +#if KDE_VERSION < KDE_MAKE_VERSION(3,3,91) + pBuf.fillRect( buffer.rect(), isSelected() ? cg.highlight() : backgroundColor() ); +#else + pBuf.fillRect( buffer.rect(), isSelected() ? cg.highlight() : backgroundColor(0) ); +#endif + + KListView *lv = static_cast( listView() ); + + QFont font( p->font() ); + QFontMetrics fm( p->fontMetrics() ); + + int text_x = 0;// lv->treeStepSize() + 3; + int textHeight; + + textHeight = height(); + + pBuf.setPen( isSelected() ? cg.highlightedText() : cg.text() ); + + if( pixmap( column ) ) + { + int y = (textHeight - pixmap(column)->height())/2; + pBuf.drawPixmap( text_x, y, *pixmap(column) ); + text_x += pixmap(column)->width()+4; + } + + pBuf.setFont( font ); + QFontMetrics fmName( font ); + + QString name = text(column); + const int _width = width - text_x - lv->itemMargin()*2; + if( fmName.width( name ) > _width ) + { + name = KStringHandler::rPixelSqueeze( name, pBuf.fontMetrics(), _width ); + } + + pBuf.drawText( text_x, 0, width - text_x, textHeight, AlignVCenter, name ); + + pBuf.end(); + p->drawPixmap( 0, 0, buffer ); +} + + +QDomElement PlaylistEntry::xml() const +{ + QDomDocument doc; + QDomElement i = doc.createElement("playlist"); + i.setAttribute( "file", url().path() ); + i.setAttribute( "title", text(0) ); + if( isOpen() ) + i.setAttribute( "isOpen", "true" ); + + QDomElement attr = doc.createElement( "tracks" ); + QDomText t = doc.createTextNode( QString::number( trackCount() ) ); + attr.appendChild( t ); + i.appendChild( attr ); + + attr = doc.createElement( "length" ); + t = doc.createTextNode( QString::number( length() ) ); + attr.appendChild( t ); + i.appendChild( attr ); + + QFileInfo fi( url().path() ); + attr = doc.createElement( "modified" ); + t = doc.createTextNode( QString::number( fi.lastModified().toTime_t() ) ); + attr.appendChild( t ); + i.appendChild( attr ); + + return i; +} + + +////////////////////////////////////////////////////////////////////////////////// +/// CLASS PlaylistTrackItem +//////////////////////////////////////////////////////////////////////////////// + +PlaylistTrackItem::PlaylistTrackItem( QListViewItem *parent, QListViewItem *after, TrackItemInfo *info ) + : PlaylistBrowserEntry( parent, after ) + , m_trackInfo( info ) +{ + setDragEnabled( true ); + setRenameEnabled( 0, false ); + PlaylistEntry *p = dynamic_cast(parent); + if(!p) + debug() << "parent: " << parent << " is not a PlaylistEntry" << endl; + if( p && p->text( 0 ).contains( info->artist() ) ) + setText( 0, info->title() ); + else + setText( 0, i18n("%1 - %2").arg( info->artist(), info->title() ) ); +} + +const KURL &PlaylistTrackItem::url() +{ + return m_trackInfo->url(); +} + +void PlaylistTrackItem::slotDoubleClicked() +{ + Playlist::instance()->insertMedia( url(), Playlist::DefaultOptions ); +} + + +void PlaylistTrackItem::showContextMenu( const QPoint &position ) +{ + KPopupMenu menu( listView() ); + enum Actions { LOAD, APPEND, QUEUE, BURN, REMOVE, INFO }; + + menu.insertItem( SmallIconSet( Amarok::icon( "files" ) ), i18n( "&Load" ), LOAD ); + menu.insertItem( SmallIconSet( Amarok::icon( "add_playlist" ) ), i18n( "&Append to Playlist" ), APPEND ); + menu.insertItem( SmallIconSet( Amarok::icon( "queue_track" ) ), i18n( "&Queue Track" ), QUEUE ); + + + menu.insertSeparator(); + + menu.insertItem( SmallIconSet( Amarok::icon( "burn" ) ), i18n("Burn to CD"), BURN ); + menu.setItemEnabled( BURN, K3bExporter::isAvailable() && url().isLocalFile() ); + + menu.insertSeparator(); + + menu.insertItem( SmallIconSet( Amarok::icon( "remove_from_playlist" ) ), i18n( "&Remove" ), REMOVE ); + menu.insertItem( SmallIconSet( Amarok::icon( "info" ) ), i18n( "Edit Track &Information..." ), INFO ); + + switch( menu.exec( position ) ) { + case LOAD: + Playlist::instance()->clear(); //FALL THROUGH + case APPEND: + PlaylistBrowser::instance()->addSelectedToPlaylist( Playlist::Append ); + break; + case QUEUE: + PlaylistBrowser::instance()->addSelectedToPlaylist( Playlist::Queue ); + break; + case BURN: + K3bExporter::instance()->exportTracks( url() ); + break; + case REMOVE: + PlaylistBrowser::instance()->removeSelectedItems(); + break; + case INFO: + if( !url().isLocalFile() ) + KMessageBox::sorry( PlaylistBrowser::instance(), i18n( "Track information is not available for remote media." ) ); + else if( QFile::exists( url().path() ) ) { + TagDialog* dialog = new TagDialog( url() ); + dialog->show(); + } + else KMessageBox::sorry( PlaylistBrowser::instance(), i18n( "This file does not exist: %1" ).arg( url().path() ) ); + } +} + + +////////////////////////////////////////////////////////////////////////////////// +/// CLASS TrackItemInfo +//////////////////////////////////////////////////////////////////////////////// + +TrackItemInfo::TrackItemInfo( const MetaBundle &mb ) +{ + m_url = mb.url(); + + if( mb.isValidMedia() ) + { + m_title = mb.title(); + m_artist = mb.artist(); + m_album = mb.album(); + m_length = mb.length(); + } + else + { + m_title = MetaBundle::prettyTitle( fileBaseName( m_url.path() ) ); + m_length = 0; + } + + if( m_length < 0 ) + m_length = 0; +} + +///////////////////////////////////////////////////////////////////////////// +/// CLASS StreamEntry +//////////////////////////////////////////////////////////////////////////// + +StreamEntry::StreamEntry( QListViewItem *parent, QListViewItem *after, const KURL &u, const QString &t ) + : PlaylistBrowserEntry( parent, after ) + , m_title( t ) + , m_url( u ) +{ + setDragEnabled( true ); + setRenameEnabled( 0, true ); + setExpandable( false ); + + if( m_title.isEmpty() ) + m_title = fileBaseName( m_url.prettyURL() ); + + setPixmap( 0, SmallIcon( Amarok::icon( "playlist" ) ) ); + + setText( 0, m_title ); +} + +StreamEntry::StreamEntry( QListViewItem *parent, QListViewItem *after, const QDomElement &xmlDefinition ) + : PlaylistBrowserEntry( parent, after ) +{ + setDragEnabled( true ); + setRenameEnabled( 0, true ); + setExpandable( false ); + + m_title = xmlDefinition.attribute( "name" ); + QDomElement e = xmlDefinition.namedItem( "url" ).toElement(); + m_url = KURL::fromPathOrURL( e.text() ); + + + if( m_title.isEmpty() ) + m_title = fileBaseName( m_url.prettyURL() ); + + setPixmap( 0, SmallIcon( Amarok::icon( "playlist" ) ) ); + + setText( 0, m_title ); +} + + +QDomElement StreamEntry::xml() const +{ + QDomDocument doc; + QDomElement i = doc.createElement("stream"); + i.setAttribute( "name", title() ); + if( isOpen() ) + i.setAttribute( "isOpen", "true" ); + QDomElement url = doc.createElement( "url" ); + url.appendChild( doc.createTextNode( m_url.prettyURL() )); + i.appendChild( url ); + return i; +} + +void StreamEntry::updateInfo() +{ + const QString body = "%1%2"; + + QString str = ""; + + str += body.arg( i18n( "URL" ), m_url.prettyURL() ); + str += "
    "; + + PlaylistBrowser::instance()->setInfo( text(0), str ); +} + +void StreamEntry::slotDoubleClicked() +{ + Playlist::instance()->proposePlaylistName( text(0) ); + Playlist::instance()->insertMedia( url(), Playlist::DefaultOptions ); +} + +void StreamEntry::setup() +{ + QFontMetrics fm( listView()->font() ); + int margin = listView()->itemMargin()*2; + int h = fm.lineSpacing(); + if ( h % 2 > 0 ) h++; + setHeight( h + margin ); +} + +void StreamEntry::paintCell( QPainter *p, const QColorGroup &cg, int column, int width, int align ) +{ + //flicker-free drawing + static QPixmap buffer; + buffer.resize( width, height() ); + + if( buffer.isNull() ) + { + KListViewItem::paintCell( p, cg, column, width, align ); + return; + } + + QPainter pBuf( &buffer, true ); + // use alternate background +#if KDE_VERSION < KDE_MAKE_VERSION(3,3,91) + pBuf.fillRect( buffer.rect(), isSelected() ? cg.highlight() : backgroundColor() ); +#else + pBuf.fillRect( buffer.rect(), isSelected() ? cg.highlight() : backgroundColor(0) ); +#endif + + KListView *lv = static_cast( listView() ); + + QFont font( p->font() ); + QFontMetrics fm( p->fontMetrics() ); + + int text_x = 0;// lv->treeStepSize() + 3; + int textHeight; + + textHeight = height(); + + pBuf.setPen( isSelected() ? cg.highlightedText() : cg.text() ); + + if( pixmap(column) ) { + int y = (textHeight - pixmap(column)->height())/2; + pBuf.drawPixmap( text_x, y, *pixmap(column) ); + text_x += pixmap(column)->width()+4; + } + + pBuf.setFont( font ); + QFontMetrics fmName( font ); + + QString name = text(column); + const int _width = width - text_x - lv->itemMargin()*2; + if( fmName.width( name ) > _width ) + { + name = KStringHandler::rPixelSqueeze( name, pBuf.fontMetrics(), _width ); + } + + pBuf.drawText( text_x, 0, width - text_x, textHeight, AlignVCenter, name ); + + pBuf.end(); + p->drawPixmap( 0, 0, buffer ); +} + +void +StreamEntry::showContextMenu( const QPoint &position ) +{ + KPopupMenu menu( listView() ); + enum Actions { LOAD, APPEND, QUEUE, EDIT, REMOVE }; + + menu.insertItem( SmallIconSet( Amarok::icon( "files" ) ), i18n( "&Load" ), LOAD ); + menu.insertItem( SmallIconSet( Amarok::icon( "add_playlist" ) ), i18n( "&Append to Playlist" ), APPEND ); + menu.insertItem( SmallIconSet( Amarok::icon( "queue_track" ) ), i18n( "&Queue Tracks" ), QUEUE ); + menu.insertSeparator(); + + // Forbid editing non removable items + if( isKept() ) + { + menu.insertItem( SmallIconSet( Amarok::icon("edit") ), i18n( "E&dit" ), EDIT ); + menu.insertItem( SmallIconSet( Amarok::icon("remove_from_playlist") ), i18n( "&Delete" ), REMOVE ); + } + else + menu.insertItem( SmallIconSet( Amarok::icon( "info" ) ), i18n( "Show &Information" ), EDIT ); + + switch( menu.exec( position ) ) + { + case LOAD: + Playlist::instance()->clear(); + Playlist::instance()->setPlaylistName( text(0) ); + //FALL THROUGH + case APPEND: + PlaylistBrowser::instance()->addSelectedToPlaylist( Playlist::Append ); + break; + case QUEUE: + PlaylistBrowser::instance()->addSelectedToPlaylist( Playlist::Queue ); + break; + case EDIT: + PlaylistBrowser::instance()->editStreamURL( this, !isKept() ); //only editable if we keep it + if( dynamic_cast(this) ) + PlaylistBrowser::instance()->saveLastFm(); + else + PlaylistBrowser::instance()->saveStreams(); + break; + case REMOVE: + PlaylistBrowser::instance()->removeSelectedItems(); + break; + } +} + + +///////////////////////////////////////////////////////////////////////////// +/// CLASS LastFmEntry +//////////////////////////////////////////////////////////////////////////// + +QDomElement LastFmEntry::xml() const +{ + QDomDocument doc; + QDomElement i = doc.createElement("lastfm"); + i.setAttribute( "name", title() ); + if( isOpen() ) + i.setAttribute( "isOpen", "true" ); + QDomElement url = doc.createElement( "url" ); + url.appendChild( doc.createTextNode( m_url.prettyURL() )); + i.appendChild( url ); + return i; +} + +///////////////////////////////////////////////////////////////////////////// +/// CLASS StreamEditor +//////////////////////////////////////////////////////////////////////////// + +StreamEditor::StreamEditor( QWidget *parent, const QString &title, const QString &url, bool readonly ) + : KDialogBase( parent, "StreamEditor", true, QString::null, Ok|Cancel) +{ + makeGridMainWidget( 2, Qt::Horizontal ); + + QLabel *nameLabel = new QLabel( i18n("&Name:"), mainWidget() ); + m_nameLineEdit = new KLineEdit( title, mainWidget() ); + m_nameLineEdit->setReadOnly( readonly ); + nameLabel->setBuddy( m_nameLineEdit ); + + QLabel *urlLabel = new QLabel( i18n("&Url:"), mainWidget() ); + m_urlLineEdit = new KLineEdit( url, mainWidget() ); + m_urlLineEdit->setReadOnly( readonly ); + urlLabel->setBuddy( m_urlLineEdit ); + + if( !readonly ) + m_nameLineEdit->setFocus(); + else + { + // In case of readonly ok button makes no sense + showButtonOK( false ); + // Change Cancel to Close button + setButtonCancel( KStdGuiItem::close() ); + } + + QSize min( 480, 110 ); + setInitialSize( min ); +} + + +///////////////////////////////////////////////////////////////////////////// +/// CLASS DynamicEntry +//////////////////////////////////////////////////////////////////////////// +DynamicEntry::DynamicEntry( QListViewItem *parent, QListViewItem *after, const QString &name ) + : PlaylistBrowserEntry( parent, after, name ) + , DynamicMode( name ) +{ + setPixmap( 0, SmallIcon( Amarok::icon( "dynamic" ) ) ); + setDragEnabled( true ); +} + +DynamicEntry::DynamicEntry( QListViewItem *parent, QListViewItem *after, const QDomElement &xmlDefinition ) + : PlaylistBrowserEntry( parent, after ) + , DynamicMode( xmlDefinition.attribute( "name" ) ) +{ + setPixmap( 0, SmallIcon( Amarok::icon( "dynamic" ) ) ); + setDragEnabled( true ); + + QDomElement e; + + setCycleTracks ( xmlDefinition.namedItem( "cycleTracks" ).toElement().text() == "true" ); + setUpcomingCount( xmlDefinition.namedItem( "upcoming" ).toElement().text().toInt() ); + setPreviousCount( xmlDefinition.namedItem( "previous" ).toElement().text().toInt() ); + + setAppendType( xmlDefinition.namedItem( "appendType" ).toElement().text().toInt() ); + + if ( appendType() == 2 ) { + setItems( QStringList::split( ',', xmlDefinition.namedItem( "items" ).toElement().text() ) ); + } +} + +QString DynamicEntry::text( int column ) const +{ + if( column == 0 ) + return title(); + return PlaylistBrowserEntry::text( column ); +} + +QDomElement DynamicEntry::xml() const +{ + QDomDocument doc; + QDomElement i; + + i = doc.createElement("dynamic"); + i.setAttribute( "name", title() ); + if( isOpen() ) + i.setAttribute( "isOpen", "true" ); + + QDomElement attr = doc.createElement( "cycleTracks" ); + QDomText t = doc.createTextNode( cycleTracks() ? "true" : "false" ); + attr.appendChild( t ); + i.appendChild( attr ); + + attr = doc.createElement( "upcoming" ); + t = doc.createTextNode( QString::number( upcomingCount() ) ); + attr.appendChild( t ); + i.appendChild( attr ); + + attr = doc.createElement( "previous" ); + t = doc.createTextNode( QString::number( previousCount() ) ); + attr.appendChild( t ); + i.appendChild( attr ); + + attr = doc.createElement( "appendType" ); + t = doc.createTextNode( QString::number( appendType() ) ); + attr.appendChild( t ); + i.appendChild( attr ); + + QString list; + if( appendType() == 2 ) { + QStringList itemsl = items(); + for( uint c = 0; c < itemsl.count(); c = c + 2 ) { + list.append( itemsl[c] ); + list.append( ',' ); + list.append( itemsl[c+1] ); + if ( c < itemsl.count()-1 ) + list.append( ',' ); + } + } + + attr = doc.createElement( "items" ); + t = doc.createTextNode( list ); + attr.appendChild( t ); + i.appendChild( attr ); + return i; +} + +void +DynamicEntry::slotDoubleClicked() +{ + Playlist::instance()->loadDynamicMode( this ); + Playlist::instance()->setPlaylistName( text(0) ); +} + + +void +DynamicEntry::showContextMenu( const QPoint &position ) +{ + KPopupMenu menu( listView() ); + + enum Actions { LOAD, RENAME, REMOVE, EDIT }; + menu.insertItem( SmallIconSet( Amarok::icon( "files" ) ), i18n( "&Load" ), LOAD ); + menu.insertSeparator(); + menu.insertItem( SmallIconSet( Amarok::icon("edit") ), i18n( "E&dit" ), EDIT ); + menu.insertItem( SmallIconSet( Amarok::icon("remove_from_playlist") ), i18n( "&Delete" ), REMOVE ); + + if( !isKept() ) + menu.setItemEnabled( REMOVE, false ); + + switch( menu.exec( position ) ) + { + case LOAD: + slotDoubleClicked(); + break; + case EDIT: + edit(); + break; + case REMOVE: + PlaylistBrowser::instance()->removeSelectedItems(); + break; + } +} + +///////////////////////////////////////////////////////////////////////////// +/// CLASS PodcastChannel +//////////////////////////////////////////////////////////////////////////// + +PodcastChannel::PodcastChannel( QListViewItem *parent, QListViewItem *after, const KURL &url ) + : PlaylistBrowserEntry( parent, after ) + , m_polished( true ) // we get the items immediately if url is given + , m_url( url ) + , m_fetching( false ) + , m_updating( false ) + , m_new( false ) + , m_hasProblem( false ) + , m_parent( static_cast(parent) ) + , m_settingsValid( false ) +{ + setDragEnabled( true ); + setRenameEnabled( 0, false ); + + setText(0, i18n("Retrieving Podcast...") ); //HACK to fill loading time space + setPixmap( 0, SmallIcon( Amarok::icon( "podcast" ) ) ); + + fetch(); +} + +PodcastChannel::PodcastChannel( QListViewItem *parent, QListViewItem *after, const KURL &url, + const QDomNode &channelSettings ) + : PlaylistBrowserEntry( parent, after ) + , m_polished( true ) // we get the items immediately if url is given + , m_url( url ) + , m_fetching( false ) + , m_updating( false ) + , m_new( false ) + , m_hasProblem( false ) + , m_parent( static_cast(parent) ) + , m_settingsValid( true ) +{ + setDragEnabled( true ); + setRenameEnabled( 0, false ); + + setDOMSettings( channelSettings ); + + setText(0, i18n("Retrieving Podcast...") ); //HACK to fill loading time space + setPixmap( 0, SmallIcon( Amarok::icon( "podcast" ) ) ); + + fetch(); +} + +PodcastChannel::PodcastChannel( QListViewItem *parent, QListViewItem *after, + const KURL &url, const QDomNode &channelSettings, + const QDomDocument &xmlDefinition ) + : PlaylistBrowserEntry( parent, after ) + , m_polished( true ) //automatically load the channel + , m_url( url ) + , m_fetching( false ) + , m_updating( false ) + , m_new( false ) + , m_hasProblem( false ) + , m_parent( static_cast(parent) ) + , m_settingsValid( true ) +{ + QDomNode type = xmlDefinition.namedItem("rss"); + if( !type.isNull() ) + setXml( type.namedItem("channel"), RSS ); + else + setXml( type, ATOM ); + + setDOMSettings( channelSettings ); + + setDragEnabled( true ); + setRenameEnabled( 0, false ); + + setPixmap( 0, SmallIcon( Amarok::icon( "podcast" ) ) ); +} + +PodcastChannel::PodcastChannel( QListViewItem *parent, QListViewItem *after, const PodcastChannelBundle &pcb ) + : PlaylistBrowserEntry( parent, after ) + , m_bundle( pcb ) + , m_polished( false ) + , m_url( pcb.url() ) + , m_fetching( false ) + , m_updating( false ) + , m_new( false ) + , m_hasProblem( false ) + , m_parent( static_cast(parent) ) + , m_settingsValid( true ) +{ + setText( 0, title() ); + setDragEnabled( true ); + setRenameEnabled( 0, false ); + setPixmap( 0, SmallIcon( Amarok::icon( "podcast" ) ) ); + setExpandable( true ); +} + +void +PodcastChannel::setDOMSettings( const QDomNode &channelSettings ) +{ + QString save = channelSettings.namedItem("savelocation").toElement().text(); + bool scan = channelSettings.namedItem("autoscan").toElement().text() == "true"; + bool hasPurge = channelSettings.namedItem("purge").toElement().text() == "true"; + int purgeCount = channelSettings.namedItem("purgecount").toElement().text().toInt(); + int fetchType = STREAM; + + if( channelSettings.namedItem( "fetch").toElement().text() == "automatic" ) + fetchType = AUTOMATIC; + + KURL saveURL; + QString t = title(); + if( save.isEmpty() ) + save = Amarok::saveLocation( "podcasts/" + Amarok::vfatPath( t ) ); + + PodcastSettings *settings = new PodcastSettings( t, save, scan, fetchType, false/*transfer*/, hasPurge, purgeCount ); + m_bundle.setSettings( settings ); +} + +void +PodcastChannel::configure() +{ + PodcastSettingsDialog *dialog = new PodcastSettingsDialog( m_bundle.getSettings() ); + + if( dialog->configure() ) + { + setSettings( dialog->getSettings() ); + } + + delete dialog->getSettings(); + delete dialog; +} + +void +PodcastChannel::checkAndSetNew() +{ + for( QListViewItem *child = firstChild(); child; child = child->nextSibling() ) + { + if( static_cast(child)->isNew() ) + { + setNew( true ); + return; + } + } + setNew( false ); +} + +void +PodcastChannel::setListened( const bool n /*true*/ ) +{ + if( !isPolished() ) + load(); + + QListViewItem *child = firstChild(); + while( child ) + { + static_cast(child)->setListened( n ); + child = child->nextSibling(); + } + + setNew( !n ); +} + +void +PodcastChannel::setOpen( bool b ) +{ + if( b == isOpen()) + return; + + if( isPolished() ) + { + QListViewItem::setOpen( b ); + return; + } + // not polished + if( b ) load(); + QListViewItem::setOpen( b ); +} + +void +PodcastChannel::load() +{ + m_polished = true; + + bool hasNew = m_new; + int episodeCount = hasPurge() ? purgeCount() : -1; + QValueList episodes; + episodes = CollectionDB::instance()->getPodcastEpisodes( url(), false, episodeCount ); + + PodcastEpisodeBundle bundle; + + // podcasts are hopefully returned chronologically, insert them in reverse + while( !episodes.isEmpty() ) + { + bundle = episodes.first(); + new PodcastEpisode( this, 0, bundle ); + + if( bundle.isNew() ) + hasNew = true; + + episodes.pop_front(); + } + sortChildItems( 0, true ); + setNew( hasNew ); +} + +void +PodcastChannel::setSettings( PodcastSettings *newSettings ) +{ + bool downloadMedia = ( (fetchType() != newSettings->fetchType()) && (newSettings->fetchType() == AUTOMATIC) ); + + /** + * Rewrite local url + * Move any downloaded media to the new location + */ + if( saveLocation() != newSettings->saveLocation() ) + { + KURL::List copyList; + + PodcastEpisode *item = static_cast( firstChild() ); + // get a list of the urls of already downloaded items + while( item ) + { + if( item->isOnDisk() ) + { + copyList << item->localUrl(); + item->setLocalUrlBase( newSettings->saveLocation() ); + } + item = static_cast( item->nextSibling() ); + } + // move the items + if( !copyList.isEmpty() ) + { + //create the local directory first + PodcastEpisode::createLocalDir( newSettings->saveLocation() ); + KIO::CopyJob* m_podcastMoveJob = KIO::move( copyList, KURL::fromPathOrURL( newSettings->saveLocation() ), false ); + Amarok::StatusBar::instance()->newProgressOperation( m_podcastMoveJob ) + .setDescription( i18n( "Moving Podcasts" ) ); + } + } + + if( newSettings->autoscan() != autoscan() ) + { + if( autoscan() ) + PlaylistBrowser::instance()->m_podcastItemsToScan.append( this ); + else + PlaylistBrowser::instance()->m_podcastItemsToScan.remove( this ); + } + + m_bundle.setSettings( newSettings ); + CollectionDB::instance()->updatePodcastChannel( m_bundle ); + + if( hasPurge() && purgeCount() != childCount() && purgeCount() != 0 ) + purge(); + + if( downloadMedia ) + downloadChildren(); +} + +void +PodcastChannel::downloadChildren() +{ + QListViewItem *item = firstChild(); + while( item ) + { + #define item static_cast(item) + if( item->isNew() ) + m_podcastDownloadQueue.append( item ); + #undef item + + item = item->nextSibling(); + } + downloadChildQueue(); +} + +void +PodcastChannel::downloadChildQueue() +{ + if( m_podcastDownloadQueue.isEmpty() ) return; + + PodcastEpisode *first = m_podcastDownloadQueue.first(); + first->downloadMedia(); + m_podcastDownloadQueue.removeFirst(); + + connect( first, SIGNAL( downloadFinished() ), this, SLOT( downloadChildQueue() ) ); +} + +void +PodcastChannel::fetch() +{ + setText( 0, i18n( "Retrieving Podcast..." ) ); + + m_iconCounter = 1; + startAnimation(); + connect( &m_animationTimer, SIGNAL(timeout()), this, SLOT(slotAnimation()) ); + + m_podcastJob = KIO::storedGet( m_url, false, false ); + + Amarok::StatusBar::instance()->newProgressOperation( m_podcastJob ) + .setDescription( i18n( "Fetching Podcast" ) ) + .setAbortSlot( this, SLOT( abortFetch() ) ); + + connect( m_podcastJob, SIGNAL( result( KIO::Job* ) ), SLOT( fetchResult( KIO::Job* ) ) ); +} + +void +PodcastChannel::abortFetch() +{ + m_podcastJob->kill(); + + stopAnimation(); + title().isEmpty() ? + setText( 0, m_url.prettyURL() ) : + setText( 0, title() ); +} + +void +PodcastChannel::fetchResult( KIO::Job* job ) //SLOT +{ + stopAnimation(); + if ( job->error() != 0 ) + { + Amarok::StatusBar::instance()->shortMessage( i18n( "Unable to connect to Podcast server." ) ); + debug() << "Unable to retrieve podcast information. KIO Error: " << job->error() << endl; + + title().isEmpty() ? + setText( 0, m_url.prettyURL() ) : + setText( 0, title() ); + setPixmap( 0, SmallIcon("cancel") ); + + return; + } + + KIO::StoredTransferJob* const storedJob = static_cast( job ); + + QDomDocument d; + + QString data = QString( storedJob->data() ); + QString error; + int errorline, errorcolumn; + if( !d.setContent( storedJob->data(), false /* disable namespace processing */, + &error, &errorline, &errorcolumn ) ) + { + Amarok::StatusBar::instance()->shortMessage( i18n("Podcast returned invalid data.") ); + debug() << "Podcast DOM failure in line " << errorline << ", column " << errorcolumn << ": " << error << endl; + + title().isEmpty() ? + setText( 0, m_url.prettyURL() ) : + setText( 0, title() ); + setPixmap( 0, SmallIcon("cancel") ); + return; + } + + QDomNode type = d.elementsByTagName("rss").item( 0 ); + if( type.isNull() || type.toElement().attribute( "version" ) != "2.0" ) + { + type = d.elementsByTagName("feed").item( 0 ); + if( type.isNull() ) + { + Amarok::StatusBar::instance()->shortMessage( i18n("Sorry, only RSS 2.0 or Atom feeds for podcasts!") ); + + if( title().isEmpty() ) + setText( 0, m_url.prettyURL() ); + + setPixmap( 0, SmallIcon("cancel") ); + return; + } + // feed is ATOM + else + { + setXml( type, ATOM ); + } + } + // feed is rss 2.0 + else + setXml( type.namedItem("channel"), RSS ); +} + +void +PodcastChannel::removeChildren() +{ + QListViewItem *child, *next; + if ( (child = firstChild()) ) + { + while ( (next = child->nextSibling()) ) + { + delete child; + child=next; + } + delete child; + } +} + +void +PodcastChannel::rescan() +{ + m_updating = true; + fetch(); +} + +void +PodcastChannel::setNew( bool n ) +{ + if( n ) + setPixmap( 0, SmallIcon( Amarok::icon( "podcast2" ) ) ); + else if( m_hasProblem ) + setPixmap( 0, SmallIcon("cancel") ); + else + setPixmap( 0, SmallIcon( Amarok::icon( "podcast" ) ) ); + + m_new = n; +} + + +/// DON'T TOUCH m_url!!! The podcast has no mention to the location of the xml file. +void +PodcastChannel::setXml( const QDomNode &xml, const int feedType ) +{ + /// Podcast Channel information + const bool isAtom = ( feedType == ATOM ); + + QString t = xml.namedItem( "title" ).toElement().text().remove("\n"); + + QString a = xml.namedItem( "author" ).toElement().text().remove("\n"); + + setText( 0, t ); + + QString l = QString::null; + + if( isAtom ) + l = xml.namedItem( "link" ).toElement().attribute( "rel" ); + else + l = xml.namedItem( "link" ).toElement().text(); + + QString d = xml.namedItem( "description" ).toElement().text(); + QString id = xml.namedItem( "itunes:summary" ).toElement().text(); + if( id.length() > d.length() ) + d = id; + QString c = xml.namedItem( "copyright" ).toElement().text(); + QString img = xml.namedItem( "image" ).toElement().namedItem( "url" ).toElement().text(); + if( img.isEmpty() ) + img = xml.namedItem( "itunes:image" ).toElement().namedItem( "url" ).toElement().text(); + if( img.isEmpty() ) + img = xml.namedItem( "itunes:image" ).toElement().attribute( "href" ); + if( img.isEmpty() ) + img = xml.namedItem( "itunes:image" ).toElement().text(); + + PodcastSettings * settings = 0; + if( m_settingsValid ) + { + settings = m_bundle.getSettings(); + } + else + { + settings = new PodcastSettings( t ); + m_settingsValid = true; + } + + m_bundle = PodcastChannelBundle( m_url, t, a, l, d, c, settings ); + delete settings; + m_bundle.setImageURL( KURL::fromPathOrURL( img ) ); + + m_bundle.setParentId( m_parent->id() ); + if( !m_updating ) + { // don't reinsert on a refresh + debug() << "Adding podcast to database" << endl; + CollectionDB::instance()->addPodcastChannel( m_bundle ); + } + else + { + debug() << "Updating podcast in database: " << endl; + CollectionDB::instance()->updatePodcastChannel( m_bundle ); + } + + /// Podcast Episodes information + + QDomNode n; + if( isAtom ) + n = xml.namedItem( "entry" ); + else + n = xml.namedItem( "item" ); + + bool hasNew = false; + bool downloadMedia = ( fetchType() == AUTOMATIC ); + QDomNode node; + + // We use an auto-increment id in the database, so we must insert podcasts in the reverse order + // to ensure we can pull them out reliably. + + QPtrList eList; + + for( ; !n.isNull(); n = n.nextSibling() ) + { + if( !n.namedItem( "enclosure" ).toElement().attribute( "url" ).isEmpty() ) + { + //prepending ensures correct order in 99% of the channels, except those who use chronological order + eList.prepend( new QDomElement( n.toElement() ) ); + } + else if( isAtom ) + { + // Atom feeds have multiple nodes called link, only one which has an enclosure. + QDomNode nodes = n.namedItem("link"); + for( ; !nodes.isNull(); nodes = nodes.nextSibling() ) + { + if( nodes.toElement().attribute("rel") == "enclosure" ) + { + eList.prepend( new QDomElement( n.toElement() ) ); + break; + } + } + } + } + + uint i = m_bundle.hasPurge() ? m_bundle.purgeCount() : eList.count(); + foreachType( QPtrList, eList ) + { + if( !m_updating || ( ( i++ >= eList.count() ) && !episodeExists( (**it), feedType ) ) ) + { + if( !isPolished() ) + load(); + PodcastEpisode *ep = new PodcastEpisode( this, 0, (**it), feedType, m_updating/*new*/ ); + if( m_updating ) + { + ep->setNew( true ); + hasNew = true; + } + } + } + + if( hasPurge() && purgeCount() != 0 && childCount() > purgeCount() ) + purge(); + + //sortChildItems( 0, true ); // ensure the correct date order + + if( downloadMedia ) + downloadChildren(); + + if( m_updating && hasNew ) + { + setNew(); + Amarok::StatusBar::instance()->shortMessage( i18n("New podcasts have been retrieved!") ); + } +} + +const bool +PodcastChannel::episodeExists( const QDomNode &xml, const int feedType ) +{ + QString command; + if( feedType == RSS ) + { + //check id + QString guid = xml.namedItem( "guid" ).toElement().text(); + if( !guid.isEmpty() ) + { + command = QString("SELECT id FROM podcastepisodes WHERE parent='%1' AND guid='%2';") + .arg( CollectionDB::instance()->escapeString( url().url() ), + CollectionDB::instance()->escapeString( guid ) ); + QStringList values = CollectionDB::instance()->query( command ); + return !values.isEmpty(); + } + + QString episodeTitle = xml.namedItem( "title" ).toElement().text(); + KURL episodeURL = xml.namedItem( "enclosure" ).toElement().attribute( "url" ); + command = QString("SELECT id FROM podcastepisodes WHERE parent='%1' AND url='%2' AND title='%3';") + .arg( CollectionDB::instance()->escapeString( url().url() ), + CollectionDB::instance()->escapeString( episodeURL.url() ), + CollectionDB::instance()->escapeString( episodeTitle ) ); + QStringList values = CollectionDB::instance()->query( command ); + return !values.isEmpty(); + } + + else if( feedType == ATOM ) + { + //check id + QString guid = xml.namedItem( "id" ).toElement().text(); + if( !guid.isEmpty() ) + { + command = QString("SELECT id FROM podcastepisodes WHERE parent='%1' AND guid='%2';") + .arg( CollectionDB::instance()->escapeString( url().url() ), + CollectionDB::instance()->escapeString( guid ) ); + QStringList values = CollectionDB::instance()->query( command ); + return !values.isEmpty(); + } + + QString episodeTitle = xml.namedItem("title").toElement().text(); + QString episodeURL = QString::null; + QDomNode n = xml.namedItem("link"); + for( ; !n.isNull(); n = n.nextSibling() ) + { + if( n.nodeName() == "link" && n.toElement().attribute("rel") == "enclosure" ) + { + episodeURL = n.toElement().attribute( "href" ); + break; + } + } + + command = QString("SELECT id FROM podcastepisodes WHERE parent='%1' AND url='%2' AND title='%3';") + .arg( CollectionDB::instance()->escapeString( url().url() ), + CollectionDB::instance()->escapeString( episodeURL ), + CollectionDB::instance()->escapeString( episodeTitle ) ); + QStringList values = CollectionDB::instance()->query( command ); + + return !values.isEmpty(); + } + + return false; +} + +void +PodcastChannel::setParent( PlaylistCategory *newParent ) +{ + if( newParent != m_parent ) + { + m_parent->takeItem( this ); + newParent->insertItem( this ); + newParent->sortChildItems( 0, true ); + + m_parent = newParent; + } + m_bundle.setParentId( m_parent->id() ); + + CollectionDB::instance()->updatePodcastChannel( m_bundle ); +} + +void +PodcastChannel::updateInfo() +{ + if( !isPolished() ) + load(); + + const QString body = "%1%2"; + + QString str = ""; + + str += body.arg( i18n( "Description" ), description() ); + str += body.arg( i18n( "Website" ), link().prettyURL() ); + str += body.arg( i18n( "Copyright" ), copyright() ); + str += body.arg( i18n( "URL" ), m_url.prettyURL() ); + str += "
    "; + str += i18n( "

     Episodes

      " ); + for( QListViewItem *c = firstChild(); c; c = c->nextSibling() ) + { + str += QString("
    • %1
    • ").arg( static_cast(c)->title() ); + } + + str += "
    "; + + PlaylistBrowser::instance()->setInfo( text(0), str ); +} + +void +PodcastChannel::slotDoubleClicked() +{ + if( !isPolished() ) + load(); + KURL::List list; + QListViewItem *child = firstChild(); + while( child ) + { + #define child static_cast(child) + child->isOnDisk() ? + list.prepend( child->localUrl() ): + list.prepend( child->url() ); + #undef child + child = child->nextSibling(); + } + + Playlist::instance()->proposePlaylistName( text(0) ); + Playlist::instance()->insertMedia( list, Playlist::DefaultOptions ); + setNew( false ); +} + +//maintain max items property +void +PodcastChannel::purge() +{ + // if the user wants to increase the max items shown, we should find those items and add them + // back to the episode list. + if( childCount() - purgeCount() <= 0 ) + { + restorePurged(); + return; + } + + KURL::List urlsToDelete; + QValueList purgedItems; + + QListViewItem *current = firstChild(); + for( int i=0; current && i < childCount(); current = current->nextSibling(), i++ ) + { + if( i < purgeCount() ) + continue; + + purgedItems.append( current ); + } + + foreachType( QValueList, purgedItems ) + { + QListViewItem *item = *it; + + #define item static_cast(item) + if( item->isOnDisk() ) + urlsToDelete.append( item->localUrl() ); + +// CollectionDB::instance()->removePodcastEpisode( item->dBId() ); + m_podcastDownloadQueue.remove( item ); + #undef item + delete item; + } + + if( !urlsToDelete.isEmpty() ) + KIO::del( urlsToDelete ); +} + +void +PodcastChannel::restorePurged() +{ + DEBUG_BLOCK + int restoreCount = purgeCount() - childCount(); + + if( restoreCount <= 0 ) return; + + QValueList episodes; + episodes = CollectionDB::instance()->getPodcastEpisodes( url() ); + + QValueList possibleEntries; + + int i = 0; + + // qvaluelist has no reverse iterator :-( + for( ; !episodes.isEmpty(); ) + { + PodcastEpisodeBundle episode = episodes.last(); + if ( i >= restoreCount ) break; + + PodcastEpisode *existingItem = static_cast( firstChild() ); + bool skip = false; + while ( existingItem ) + { + if ( episode.url() == existingItem->url() && + episode.title() == existingItem->title() && + episode.date() == existingItem->date() && + episode.guid() == existingItem->guid() ) { + skip = true; + break; + } + existingItem = static_cast( existingItem->nextSibling() ); + } + if( !skip ) + { + possibleEntries.append( episode ); + i++; + } + episodes.pop_back(); + } + + // the sorting of the channels automatically means the new episodes gets placed at the end + for( QValueList::Iterator it = possibleEntries.begin(), end = possibleEntries.end(); + it != end; ++it ) + new PodcastEpisode( this, 0, (*it) ); + + sortChildItems( 0, true ); +} + +void +PodcastChannel::startAnimation() +{ + if( !m_animationTimer.isActive() ) + m_animationTimer.start( ANIMATION_INTERVAL ); +} + +void +PodcastChannel::stopAnimation() +{ + m_animationTimer.stop(); + + hasNew() ? + setPixmap( 0, SmallIcon( Amarok::icon( "podcast2" ) ) ): + setPixmap( 0, SmallIcon( Amarok::icon( "podcast" ) ) ); +} + +void +PodcastChannel::slotAnimation() +{ + m_iconCounter % 2 ? + setPixmap( 0, SmallIcon( Amarok::icon( "podcast" ) ) ): + setPixmap( 0, SmallIcon( Amarok::icon( "podcast2" ) ) ); + + m_iconCounter++; +} + + +void +PodcastChannel::showContextMenu( const QPoint &position ) +{ + KPopupMenu menu( listView() ); + + enum Actions { LOAD, APPEND, QUEUE, DELETE, RESCAN, LISTENED, NEW, CONFIG }; + + menu.insertItem( SmallIconSet( Amarok::icon( "files" ) ), i18n( "&Load" ), LOAD ); + menu.insertItem( SmallIconSet( Amarok::icon( "add_playlist" ) ), i18n( "&Append to Playlist" ), APPEND ); + menu.insertItem( SmallIconSet( Amarok::icon( "queue_track" ) ), i18n( "&Queue Tracks" ), QUEUE ); + menu.insertSeparator(); + menu.insertItem( SmallIconSet( Amarok::icon( "remove" ) ), i18n( "&Delete" ), DELETE ); + menu.insertItem( SmallIconSet( Amarok::icon( "refresh" ) ), i18n( "&Check for Updates" ), RESCAN ); + menu.insertItem( SmallIconSet( Amarok::icon( "artist" ) ), i18n( "Mark as &Listened" ), LISTENED ); + menu.insertItem( SmallIconSet( Amarok::icon( "artist" ) ), i18n( "Mark as &New" ), NEW ); + menu.insertItem( SmallIconSet( Amarok::icon( "configure" ) ), i18n( "&Configure..." ), CONFIG ); + menu.setItemEnabled( LISTENED, hasNew() ); + menu.setItemEnabled( CONFIG, m_settingsValid ); + + switch( menu.exec( position ) ) + { + case LOAD: + Playlist::instance()->clear(); + Playlist::instance()->setPlaylistName( text(0) ); + //FALL THROUGH + case APPEND: + PlaylistBrowser::instance()->addSelectedToPlaylist( Playlist::Append ); + break; + + case QUEUE: + PlaylistBrowser::instance()->addSelectedToPlaylist( Playlist::Queue ); + break; + + case RESCAN: + rescan(); + break; + + case LISTENED: + setListened(); + break; + + case NEW: + setListened(false); + break; + case DELETE: + PlaylistBrowser::instance()->removeSelectedItems(); + break; + + case CONFIG: + { + PlaylistBrowser::instance()->configureSelectedPodcasts(); + break; + } + } +} + +///////////////////////////////////////////////////////////////////////////// +/// CLASS PodcastEpisode +/// @note we fucking hate itunes for taking over podcasts and inserting +/// their own attributes. +//////////////////////////////////////////////////////////////////////////// +PodcastEpisode::PodcastEpisode( QListViewItem *parent, QListViewItem *after, + const QDomElement &xml, const int feedType, const bool &isNew ) + : PlaylistBrowserEntry( parent, after ) + , m_parent( parent ) + , m_fetching( false ) + , m_onDisk( false ) + , m_localUrl( KURL() ) +{ + const bool isAtom = ( feedType == ATOM ); + QString title = xml.namedItem( "title" ).toElement().text().remove("\n"); + QString subtitle; + + QString description, author, date, guid, type; + int duration = 0; + uint size = 0; + KURL link; + + if( isAtom ) + { + for( QDomNode n = xml.firstChild(); !n.isNull(); n = n.nextSibling() ) + { + if ( n.nodeName() == "summary" ) description = n.toElement().text(); + else if ( n.nodeName() == "author" ) author = n.toElement().text().remove("\n"); + else if ( n.nodeName() == "published" ) date = n.toElement().text(); + else if ( n.nodeName() == "id" ) guid = n.toElement().text(); + else if ( n.nodeName() == "link" ) + { + if( n.toElement().attribute( "rel" ) == "enclosure" ) + { + const QString weblink = n.toElement().attribute( "href" ); + link = KURL::fromPathOrURL( weblink ); + } + } + } + } + else + { + description = xml.namedItem( "description" ).toElement().text(); + QString idescription = xml.namedItem( "itunes:summary" ).toElement().text(); + if( idescription.length() > description.length() ) + description = idescription; + + if( subtitle.isEmpty() ) + subtitle = xml.namedItem( "itunes:subtitle" ).toElement().text(); + + author = xml.namedItem( "author" ).toElement().text().remove("\n"); + if( author.isEmpty() ) + author = xml.namedItem( "itunes:author" ).toElement().text().remove("\n"); + + date = xml.namedItem( "pubDate" ).toElement().text(); + if( date.isEmpty() ) + date = xml.namedItem( "dc:date" ).toElement().text(); + + QString ds = xml.namedItem( "itunes:duration" ).toElement().text(); + QString secs = ds.section( ":", -1, -1 ); + duration = secs.toInt(); + QString min = ds.section( ":", -2, -2 ); + duration += min.toInt() * 60; + QString h = ds.section( ":", -3, -3 ); + duration += h.toInt() * 3600; + + size = xml.namedItem( "enclosure" ).toElement().attribute( "length" ).toInt(); + type = xml.namedItem( "enclosure" ).toElement().attribute( "type" ); + guid = xml.namedItem( "guid" ).toElement().text(); + + const QString weblink = xml.namedItem( "enclosure" ).toElement().attribute( "url" ); + + link = KURL::fromPathOrURL( weblink ); + } + + if( title.isEmpty() ) + title = link.fileName(); + + KURL parentUrl = static_cast(parent)->url(); + m_bundle.setDBId( -1 ); + m_bundle.setURL( link ); + m_bundle.setParent( parentUrl ); + m_bundle.setTitle( title ); + m_bundle.setSubtitle( subtitle ); + m_bundle.setAuthor( author ); + m_bundle.setDescription( description ); + m_bundle.setDate( date ); + m_bundle.setType( type ); + m_bundle.setDuration( duration ); + m_bundle.setSize( size ); + m_bundle.setGuid( guid ); + m_bundle.setNew( isNew ); + + int id = CollectionDB::instance()->addPodcastEpisode( m_bundle ); + m_bundle.setDBId( id ); + + setText( 0, title ); + updatePixmap(); + setDragEnabled( true ); + setRenameEnabled( 0, false ); +} + +PodcastEpisode::PodcastEpisode( QListViewItem *parent, QListViewItem *after, PodcastEpisodeBundle &bundle ) + : PlaylistBrowserEntry( parent, after ) + , m_parent( parent ) + , m_bundle( bundle ) + , m_fetching( false ) + , m_onDisk( false ) +{ + m_localUrl = m_bundle.localUrl(); + isOnDisk(); + + setText( 0, bundle.title() ); + updatePixmap(); + setDragEnabled( true ); + setRenameEnabled( 0, false ); +} + +int +PodcastEpisode::compare( QListViewItem* item, int col, bool ascending ) const +{ + if ( item->rtti() == PodcastEpisode::RTTI ) + { + int ret; + #define item static_cast(item) + // date is priority + bool thisHasDate = m_bundle.dateTime().isValid(); + bool thatHasDate = item->m_bundle.dateTime().isValid(); + if( thisHasDate && thatHasDate ) + { + ret = m_bundle.dateTime() < item->m_bundle.dateTime() ? 1 : -1; + if ( !ascending ) ret *= -1; + return ret; + } + + // if neither has a date, then we order upon the id in the database. This + // should be the order in which it arrives in the feed. + if( !thisHasDate && !thatHasDate ) + { + ret = m_bundle.dBId() < item->m_bundle.dBId() ? 1 : -1; + if ( !ascending ) ret *= -1; + return ret; + } + + // if one has a date, and the other doesn't, always keep non-dated at the bottom. + // hypothetically, this should never happen, but it might. + ret = thisHasDate ? 1 : -1; + if ( !ascending ) ret *= -1; + return ret; + #undef item + } + + return PlaylistBrowserEntry::compare( item, col, ascending ); +} + +void +PodcastEpisode::updatePixmap() +{ + if( isNew() ) + setPixmap( 0, SmallIcon( Amarok::icon( "podcast2" ) ) ); + else if( m_onDisk ) + setPixmap( 0, SmallIcon( "down" ) ); + else + setPixmap( 0, SmallIcon( Amarok::icon( "podcast" ) ) ); +} + +const bool +PodcastEpisode::isOnDisk() +{ + if( m_localUrl.isEmpty() ) + return false; + else + { +// bool oldOnDisk = m_onDisk; + m_onDisk = QFile::exists( m_localUrl.path() ); + updatePixmap(); +// m_bundle.setLocalURL( m_onDisk ? m_localUrl : KURL() ); +// if( oldOnDisk != m_onDisk && dBId() ) +// CollectionDB::instance()->updatePodcastEpisode( dBId(), m_bundle ); + return m_onDisk; + } +} + +void +PodcastEpisode::downloadMedia() +{ + DEBUG_BLOCK + DEBUG_THREAD_FUNC_INFO + SHOULD_BE_GUI + + if( isOnDisk() ) + return; + + setText( 0, i18n( "Downloading Media..." ) ); + + m_iconCounter = 1; + startAnimation(); + connect( &m_animationTimer, SIGNAL(timeout()), this, SLOT(slotAnimation()) ); + + KURL localDir; + PodcastChannel *channel = dynamic_cast(m_parent); + if( channel ) + localDir = KURL::fromPathOrURL( channel->saveLocation() ); + else + localDir = KURL::fromPathOrURL( PodcastSettings("Podcasts").saveLocation() ); + createLocalDir( localDir ); + + //filename might get changed by redirects later. + m_filename = url().fileName(); + m_localUrl = localDir; + m_podcastEpisodeJob = KIO::storedGet( url().url(), false, false); + + Amarok::StatusBar::instance()->newProgressOperation( m_podcastEpisodeJob ) + .setDescription( title().isEmpty() + ? i18n( "Downloading Podcast Media" ) + : i18n( "Downloading Podcast \"%1\"" ).arg( title() ) ) + .setAbortSlot( this, SLOT( abortDownload()) ) + .setProgressSignal( m_podcastEpisodeJob, SIGNAL( percent( KIO::Job *, unsigned long ) ) ); + + connect( m_podcastEpisodeJob, SIGNAL( result( KIO::Job * ) ), SLOT( downloadResult( KIO::Job * ) ) ); + connect( m_podcastEpisodeJob, SIGNAL( redirection( KIO::Job *,const KURL& ) ), SLOT( redirected( KIO::Job *,const KURL& ) ) ); +} + +/* change the localurl if redirected, allows us to use the original filename to transfer to mediadevices*/ +void PodcastEpisode::redirected( KIO::Job *, const KURL & redirectedUrl ) +{ + debug() << "redirecting to " << redirectedUrl << ". filename: " << redirectedUrl.fileName() << endl; + m_filename = redirectedUrl.fileName(); +} + +void PodcastEpisode::createLocalDir( const KURL &localDir ) +{ + if( localDir.isEmpty() ) return; + + QString localDirString = localDir.path(); + if( !QFile::exists( localDirString ) ) + { + QString parentDirString = localDir.directory( true, true ); + createLocalDir( parentDirString ); + QDir dir( localDirString ); + dir.mkdir( localDirString ); + } +} + +void +PodcastEpisode::abortDownload() //SLOT +{ + emit downloadAborted(); + if( m_podcastEpisodeJob ) + m_podcastEpisodeJob->kill( false ); + + //don't delete m_podcastFetcher yet, kill() is async + stopAnimation(); + setText( 0, title() ); + m_onDisk = false; + updatePixmap(); +} + +void PodcastEpisode::downloadResult( KIO::Job * transferJob ) +{ + emit downloadFinished(); + stopAnimation(); + setText( 0, title() ); + + if( transferJob->error() ) + { + Amarok::StatusBar::instance()->shortMessage( i18n( "Media download aborted, unable to connect to server." ) ); + debug() << "Unable to retrieve podcast media. KIO Error: " << transferJob->error() << endl; + + m_localUrl = KURL(); + setPixmap( 0, SmallIcon("cancel") ); + } + else + { + + m_localUrl.addPath( m_filename ); + QFile *localFile = new QFile( m_localUrl.path() ); + localFile->open( IO_WriteOnly ); + localFile->writeBlock( m_podcastEpisodeJob->data() ); + localFile->close(); + + setLocalUrl( m_localUrl ); + + PodcastChannel *channel = dynamic_cast( m_parent ); + if( channel && channel->autotransfer() && MediaBrowser::isAvailable() ) + { + addToMediaDevice(); + MediaBrowser::queue()->URLsAdded(); + } + + updatePixmap(); + } + return; +} + +void +PodcastEpisode::setLocalUrl( const KURL &localUrl ) +{ + m_localUrl = localUrl; + m_bundle.setLocalURL( m_localUrl ); + CollectionDB::instance()->updatePodcastEpisode( dBId(), m_bundle ); + isOnDisk(); +} + +void +PodcastEpisode::addToMediaDevice() +{ + MetaBundle *bundle = new MetaBundle( localUrl() ); + PodcastChannel *channel = dynamic_cast( m_parent ); + if(channel && !channel->title().isEmpty()) + bundle->setAlbum(channel->title()); + if(!title().isEmpty()) + bundle->setTitle(title()); + + MediaBrowser::queue()->addURL( localUrl(), bundle ); +} + +void +PodcastEpisode::setLocalUrlBase( const QString &s ) +{ + if ( !m_localUrl.isEmpty() ) + { + QString filename = m_localUrl.filename(); + QString newL = s + filename; + m_localUrl = KURL::fromPathOrURL( newL ); + } +} + +void +PodcastEpisode::setNew( const bool &n ) +{ + if( n == isNew() ) return; + + m_bundle.setNew( n ); + updatePixmap(); + CollectionDB::instance()->updatePodcastEpisode( dBId(), m_bundle ); + + // if we mark an item as listened, we might need to update the parent + if( n == true ) + static_cast(m_parent)->setNew( true ); + else + static_cast(m_parent)->checkAndSetNew(); +} + +void +PodcastEpisode::startAnimation() +{ + if( !m_animationTimer.isActive() ) + m_animationTimer.start( ANIMATION_INTERVAL ); +} + +void +PodcastEpisode::stopAnimation() +{ + m_animationTimer.stop(); + updatePixmap(); +} + +void +PodcastEpisode::slotAnimation() +{ + m_iconCounter % 2 ? + setPixmap( 0, SmallIcon( Amarok::icon( "podcast") ) ): + setPixmap( 0, SmallIcon( Amarok::icon( "podcast2") ) ); + + m_iconCounter++; +} + +void +PodcastEpisode::setup() +{ + QFontMetrics fm( listView()->font() ); + int margin = listView()->itemMargin()*2; + int h = fm.lineSpacing(); + if ( h % 2 > 0 ) h++; + setHeight( h + margin ); +} + +void +PodcastEpisode::paintCell( QPainter *p, const QColorGroup &cg, int column, int width, int align ) +{ + //flicker-free drawing + static QPixmap buffer; + buffer.resize( width, height() ); + + if( buffer.isNull() ) + { + KListViewItem::paintCell( p, cg, column, width, align ); + return; + } + + QPainter pBuf( &buffer, true ); + // use alternate background +#if KDE_VERSION < KDE_MAKE_VERSION(3,3,91) + pBuf.fillRect( buffer.rect(), isSelected() ? cg.highlight() : backgroundColor() ); +#else + pBuf.fillRect( buffer.rect(), isSelected() ? cg.highlight() : backgroundColor(0) ); +#endif + + KListView *lv = static_cast( listView() ); + + QFont font( p->font() ); + QFontMetrics fm( p->fontMetrics() ); + + int text_x = 0;// lv->treeStepSize() + 3; + int textHeight; + + textHeight = height(); + + pBuf.setPen( isSelected() ? cg.highlightedText() : cg.text() ); + + if( pixmap( column ) ) + { + int y = (textHeight - pixmap(column)->height())/2; + pBuf.drawPixmap( text_x, y, *pixmap(column) ); + text_x += pixmap(column)->width()+4; + } + + pBuf.setFont( font ); + QFontMetrics fmName( font ); + + QString name = text(column); + const int _width = width - text_x - lv->itemMargin()*2; + if( fmName.width( name ) > _width ) + { + //decapitateString removes the channels title from the epsiodes title + name = Amarok::decapitateString( name, static_cast(m_parent)->title() ); + if( fmName.width( name ) > _width ) + name = KStringHandler::rPixelSqueeze( name, pBuf.fontMetrics(), _width ); + } + + pBuf.drawText( text_x, 0, width - text_x, textHeight, AlignVCenter, name ); + + pBuf.end(); + p->drawPixmap( 0, 0, buffer ); +} + +void +PodcastEpisode::updateInfo() +{ + const QString body = "%1%2"; + + QString str = ""; + + //str += body.arg( i18n( "Title" ), m_bundle.title() ); + str += body.arg( i18n( "Description" ), m_bundle.description() ); + str += body.arg( i18n( "Date" ), m_bundle.date() ); + str += body.arg( i18n( "Author" ), m_bundle.author() ); + str += body.arg( i18n( "Type" ), m_bundle.type() ); + str += body.arg( i18n( "URL" ), m_bundle.url().prettyURL() ); + str += body.arg( i18n( "Local URL" ), isOnDisk() ? localUrl().prettyURL() : i18n( "n/a" ) ); + str += "
    "; + + PlaylistBrowser::instance()->setInfo( text(0), str ); +} + + +void +PodcastEpisode::slotDoubleClicked() +{ + KURL::List list; + + isOnDisk() ? + list.append( localUrl() ): + list.append( url() ); + + Playlist::instance()->insertMedia( list, Playlist::DefaultOptions ); + setListened(); +} + + +void +PodcastEpisode::showContextMenu( const QPoint &position ) +{ + KPopupMenu menu( listView() ); + + enum Actions { LOAD, APPEND, QUEUE, GET, ASSOCIATE, DELETE, MEDIA_DEVICE, LISTENED, NEW, OPEN_WITH /* has to be last */ }; + menu.insertItem( SmallIconSet( Amarok::icon( "files" ) ), i18n( "&Load" ), LOAD ); + menu.insertItem( SmallIconSet( Amarok::icon( "add_playlist" ) ), i18n( "&Append to Playlist" ), APPEND ); + menu.insertItem( SmallIconSet( Amarok::icon( "queue_track" ) ), i18n( "&Queue Track" ), QUEUE ); + + int accuracy = 0; + KMimeType::Ptr mimetype; + if( isOnDisk() ) + mimetype = KMimeType::findByFileContent( localUrl().path(), &accuracy ); + if( accuracy <= 0 ) + mimetype = KMimeType::findByURL( url() ); + KTrader::OfferList offers = KTrader::self()->query( mimetype->name(), "Type == 'Application'" ); + if( offers.empty() || (offers.size()==1 && offers.first()->name()=="Amarok") ) + { + menu.insertItem( SmallIconSet( Amarok::icon( "run" ) ), i18n( "&Open With..."), OPEN_WITH ); + } + else + { + int i = 1; + KPopupMenu *openMenu = new KPopupMenu; + for( KTrader::OfferList::iterator it = offers.begin(); + it != offers.end(); + ++it ) + { + if( (*it)->name() != "Amarok" ) + openMenu->insertItem( SmallIconSet( (*it)->icon() ), (*it)->name(), OPEN_WITH+i ); + ++i; + } + openMenu->insertSeparator(); + openMenu->insertItem( SmallIconSet( Amarok::icon( "run" ) ), i18n( "&Other..."), OPEN_WITH ); + menu.insertItem( SmallIconSet( Amarok::icon( "run" ) ), i18n("&Open With"), openMenu, OPEN_WITH ); + } + + if( MediaBrowser::isAvailable() ) + { + menu.insertSeparator(); + menu.insertItem( SmallIconSet( Amarok::icon( "device" ) ), + i18n( "&Transfer to Media Device" ), MEDIA_DEVICE ); + menu.setItemEnabled( MEDIA_DEVICE, isOnDisk() ); + } + + menu.insertSeparator(); + menu.insertItem( SmallIconSet( Amarok::icon( "download" ) ), i18n( "&Download Media" ), GET ); + menu.insertItem( SmallIconSet( Amarok::icon( "attach" ) ), i18n( "&Associate with Local File" ), ASSOCIATE ); + menu.insertItem( SmallIconSet( Amarok::icon( "artist" ) ), i18n( "Mark as &Listened" ), LISTENED ); + menu.insertItem( SmallIconSet( Amarok::icon( "artist" ) ), i18n( "Mark as &New" ), NEW ); + menu.insertItem( SmallIconSet( Amarok::icon("remove") ), i18n( "De&lete Downloaded Podcast" ), DELETE ); + + menu.setItemEnabled( GET, !isOnDisk() ); + menu.setItemEnabled( ASSOCIATE, !isOnDisk() ); + menu.setItemEnabled( DELETE, isOnDisk() ); + menu.setItemVisible( LISTENED, isNew() ); + menu.setItemVisible( NEW, !isNew() ); + + uint id = menu.exec( position ); + switch( id ) + { + case LOAD: + Playlist::instance()->clear(); + Playlist::instance()->setPlaylistName( text(0) ); + //FALL THROUGH + case APPEND: + PlaylistBrowser::instance()->addSelectedToPlaylist( Playlist::Append ); + break; + + case QUEUE: + PlaylistBrowser::instance()->addSelectedToPlaylist( Playlist::Queue ); + break; + + case GET: + PlaylistBrowser::instance()->downloadSelectedPodcasts(); + break; + + case ASSOCIATE: + associateWithLocalFile(); + break; + + case DELETE: + PlaylistBrowser::instance()->deleteSelectedPodcastItems(); + break; + + case LISTENED: + for ( QListViewItemIterator it( listView(), QListViewItemIterator::Selected); *it; ++it ) + { + if ( isPodcastEpisode( *it ) ) + static_cast(*it)->setListened(); + } + break; + + case NEW: + for ( QListViewItemIterator it( listView(), QListViewItemIterator::Selected); *it; ++it ) + { + if ( isPodcastEpisode( *it ) ) + static_cast(*it)->setListened(false); + } + break; + + case MEDIA_DEVICE: + // tags on podcasts are sometimes bad, thus use other meta information if available + if( isSelected() ) + { + for ( QListViewItemIterator it( listView(), QListViewItemIterator::Selected); *it; ++it) + { + if( isPodcastEpisode( *it ) ) + { + PodcastEpisode *podcast = static_cast(*it); + if( podcast->isOnDisk() ) + podcast->addToMediaDevice(); + } + } + } + else + addToMediaDevice(); + + MediaBrowser::queue()->URLsAdded(); + break; + case OPEN_WITH: + { + KURL::List urlList; + urlList.append( isOnDisk() ? localUrl() : url() ); + KRun::displayOpenWithDialog( urlList ); + } + break; + + default: + if( id >= OPEN_WITH+1 && id <= OPEN_WITH + offers.size() ) + { + KTrader::OfferList::iterator it = offers.begin(); + for(uint i = OPEN_WITH+1; i < id && i < OPEN_WITH+offers.size(); ++i ) + { + ++it; + } + KService::Ptr ptr = offers.first(); + KURL::List urlList; + urlList.append( isOnDisk() ? localUrl() : url() ); + if( it != offers.end() ) + { + KRun::run(**it, urlList); + } + } + break; + } +} + + +class AssociatePodcastDialog : public KDialogBase +{ + KURLRequester *m_urlRequester; + + public: + AssociatePodcastDialog( PodcastEpisode *item ) + : KDialogBase( Amarok::mainWindow(), "associatepodcastdialog", true, i18n("Select Local File for %1").arg(item->title()), Ok|Cancel, Ok, false ) + { + QVBox* vbox = makeVBoxMainWidget(); + vbox->setSpacing( KDialog::spacingHint() ); + + m_urlRequester = new KURLRequester( vbox ); + if( dynamic_cast(item->parent()) ) + m_urlRequester->setURL( static_cast(item->parent())->saveLocation() ); + } + KURL url() const { return KURL::fromPathOrURL( m_urlRequester->url() ); } +}; + +void +PodcastEpisode::associateWithLocalFile() +{ + AssociatePodcastDialog d( this ); + if( d.exec() == KDialogBase::Accepted ) + { + if( !d.url().isLocalFile() || !QFileInfo( d.url().path() ).isFile() ) + Amarok::StatusBar::instance()->shortMessage( i18n( "Invalid local podcast URL." ) ); + else + setLocalUrl( d.url() ); + } +} + + +///////////////////////////////////////////////////////////////////////////// +/// CLASS SmartPlaylist +//////////////////////////////////////////////////////////////////////////// + +SmartPlaylist::SmartPlaylist( QListViewItem *parent, QListViewItem *after, const QString &name, const QString &query ) + : PlaylistBrowserEntry( parent, after, name ) + , m_sqlForTags( query ) + , m_title( name ) + , m_dynamic( false ) +{ + setPixmap( 0, SmallIcon( Amarok::icon( "playlist" ) ) ); + setDragEnabled( query.isEmpty() ? false : true ); + + setText( 0, name ); +} + +SmartPlaylist::SmartPlaylist( QListViewItem *parent, QListViewItem *after, const QString &name, const QString &urls, const QString &tags ) + : PlaylistBrowserEntry( parent, after, name ) + , m_sqlForTags( tags ) + , m_title( name ) + , m_dynamic( false ) +{ + setPixmap( 0, SmallIcon( Amarok::icon( "playlist" ) ) ); + setDragEnabled( !urls.isEmpty() && !tags.isEmpty() ); + + setText( 0, name ); +} + + +SmartPlaylist::SmartPlaylist( QListViewItem *parent, QListViewItem *after, const QDomElement &xmlDefinition ) + : PlaylistBrowserEntry( parent, after ) + , m_after( after ) + , m_dynamic( false ) +{ + setPixmap( 0, SmallIcon( Amarok::icon( "playlist" ) ) ); + setXml( xmlDefinition ); + setDragEnabled( true ); +} + +int SmartPlaylist::length() +{ + QString sql = query(); + sql.replace(QRegExp("SELECT.*FROM"), "SELECT COUNT(*) FROM"); + CollectionDB *db = CollectionDB::instance(); + QStringList result = db->query( sql ); + + if (! result.isEmpty()) + return result.first().toInt(); + else return 0; +} + +void SmartPlaylist::setXml( const QDomElement &xml ) +{ + m_xml = xml; + m_title = xml.attribute( "name" ); + setText( 0, m_title ); + // ignore query, we now compute it when needed + //m_sqlForTags = xml.namedItem( "sqlquery" ).toElement().text(); + m_sqlForTags = ""; + static QStringList genres; + static QStringList artists; + static QStringList composers; + static QStringList albums; + static QStringList years; + static QStringList labels; + + //Delete all children before + while( firstChild() ) + delete firstChild(); + + QDomNode expandN = xml.namedItem( "expandby" ); + if ( !expandN.isNull() ) { + // precompute query + QString queryChildren = xmlToQuery( m_xml, true ); + + QDomElement expand = expandN.toElement(); + QString field = expand.attribute( "field" ); + SmartPlaylist *item = this; + if ( field == i18n("Genre") ) { + if ( genres.isEmpty() ) { + genres = CollectionDB::instance()->genreList(); + } + foreach( genres ) { + m_after = new SmartPlaylist( item, m_after, i18n( "%1" ).arg( *it ), + QString(queryChildren).replace( + "(*ExpandString*)", *it) ); + } + } + if ( field == i18n("Artist") ) { + if ( artists.isEmpty() ) { + artists = CollectionDB::instance()->artistList(); + } + foreach( artists ) { + m_after = new SmartPlaylist( item, m_after, i18n( "By %1" ).arg( *it ), + QString(queryChildren).replace( + "(*ExpandString*)", *it) ); + } + } + if ( field == i18n("Composer") ) { + if ( composers.isEmpty() ) { + composers = CollectionDB::instance()->composerList(); + } + foreach( composers ) { + m_after = new SmartPlaylist( item, m_after, i18n( "By %1" ).arg( *it ), + QString(queryChildren).replace( + "(*ExpandString*)", *it) ); + } + } + if ( field == i18n("Album") ) { + if ( albums.isEmpty() ) { + albums = CollectionDB::instance()->albumList(); + } + foreach( albums ) { + m_after = new SmartPlaylist( item, m_after, i18n( "%1" ).arg( *it ), + QString(queryChildren).replace( + "(*ExpandString*)", *it) ); + } + } + if ( field == i18n("Year") ) { + if ( years.isEmpty() ) { + years = CollectionDB::instance()->yearList(); + } + foreach( years ) { + m_after = new SmartPlaylist( item, m_after, i18n( "%1" ).arg( *it ), + QString(queryChildren).replace( + "(*ExpandString*)", *it) ); + } + } + if ( field == i18n("Label") ) { + if (labels.isEmpty() ) { + labels = CollectionDB::instance()->labelList(); + } + foreach( labels ) { + m_after = new SmartPlaylist( item, m_after, i18n( "%1" ).arg( *it ), QString(queryChildren).replace("(*ExpandString*)", *it) ); + } + } + } + +} + +QString SmartPlaylist::query() +{ + if ( m_sqlForTags.isEmpty() ) m_sqlForTags = xmlToQuery( m_xml ); + // duplicate string, thread-safely (QDeepCopy is not thread-safe) + return QString( m_sqlForTags.unicode(), m_sqlForTags.length() ) + .replace( "(*CurrentTimeT*)" , + QString::number(QDateTime::currentDateTime().toTime_t()) ) + .replace( "(*ListOfFields*)" , QueryBuilder::dragSQLFields() ) + .replace( "(*MountedDeviceSelection*)" , + CollectionDB::instance()->deviceidSelection() ); +} + +// static +QString +SmartPlaylist::xmlToQuery(const QDomElement &xml, bool forExpand /* = false */) { + QueryBuilder qb; + + qb.initSQLDrag(); + // This code is partly copied from SmartPlaylistEditor -- but refactoring + // to have it common would involve adding an internal data structure for smart + // playlist queries. I think having the XML be that data structure is almost as good, + // it's just a little more verbose when iterating. + + // Add filters + QDomNodeList matchesList = xml.elementsByTagName( "matches" ); + for ( uint i = 0; i < matchesList.count(); i++ ) { + QDomElement matches = matchesList.item( i ).toElement(); + QDomNodeList criteriaList = matches.elementsByTagName( "criteria" ); + + if ( matches.attribute( "glue" ) == "OR" ) + qb.beginOR(); + else + qb.beginAND(); + + for ( uint j = 0; j < criteriaList.count(); j++ ) { + QDomElement criteria = criteriaList.item( j ).toElement(); + QString field = criteria.attribute( "field" ); + int table; + Q_INT64 value; + if ( !qb.getField( field, &table, &value ) ) continue; + + QStringList filters; + // name conflict :) XML "value" -> QueryBuilder "filter" + QDomNodeList domFilterList = criteria.elementsByTagName( "value" ); + for ( uint k = 0 ; k < domFilterList.count(); k++ ) { + filters << domFilterList.item(k).toElement().text(); + } + + QString condition = criteria.attribute( "condition" ); + + // Interpret dates + bool isDate = (value & (QueryBuilder::valCreateDate + | QueryBuilder::valAccessDate)) > 0; + + if ( isDate ) { + QDateTime dt1, dt2; + if ( condition == i18n( "is in the last" ) + || condition == i18n( "is not in the last" ) ) { + QString period = criteria.attribute( "period" ); + uint time = filters[0].toInt(); + if ( period == "days" ) time *= 86400; + else if ( period == "months" ) time *= 86400 * 30; + else if ( period == "years" ) time *= 86400 * 365; + filters[0] = "(*CurrentTimeT*) - " + QString::number( time ); + if ( filters.count() == 1 ) filters.push_back( "" ); + filters[1] = "(*CurrentTimeT*)"; + + } + else { + dt1.setTime_t( filters[0].toInt() ); + // truncate to midnight + if ( condition == i18n( "is after" ) ) + dt1.setTime( QTime().addSecs(-1) ); // 11:59:59 pm + else + dt1.setTime( QTime() ); + if ( filters.count() > 1 ) { + dt2.setTime_t( filters[1].toInt() ); + // this is a "between", so always go till right before midnight + dt2.setTime( QTime().addSecs( -1 ) ); + } + } + } + + if ( value & QueryBuilder::valLength ) { + QString period = criteria.attribute( "period" ); + uint time1 = filters[0].toInt(); + if ( period == "minutes" ) + time1 *= 60; + else if ( period == "hours" ) + time1 *= 3600; + filters[0] = QString::number( time1 ); + if ( condition == i18n( "is between" ) ) + { + uint time2 = filters[1].toInt(); + if ( period == "minutes" ) + time2 *= 60; + else if ( period == "hours" ) + time2 *= 3600; + filters[1] = QString::number( time2 ); + } + } + + + if ( condition == i18n( "contains" ) ) + qb.addFilter( table, value, filters[0] ); + else if ( condition == i18n( "does not contain" ) ) + qb.excludeFilter( table, value, filters[0]) ; + else if ( condition == i18n( "is") ) + qb.addFilter( table, value, filters[0], QueryBuilder::modeNormal, true); + else if ( condition == i18n( "is not" ) ) + qb.excludeFilter( table, value, filters[0], QueryBuilder::modeNormal, + true); + else if ( condition == i18n( "starts with" ) ) + { + // need to take care of absolute paths + if ( field == "tags.url" ) + if ( filters[0].startsWith( "/" ) ) + filters[0].prepend( '.' ); + else if ( !filters[0].startsWith( "./" ) ) + filters[0].prepend( "./" ); + qb.addFilter( table, value, filters[0], QueryBuilder::modeBeginMatch ); + } + else if ( condition == i18n( "does not start with" ) ) + { + // need to take care of absolute paths + if ( field == "tags.url" ) + if ( filters[0].startsWith( "/" ) ) + filters[0].prepend( '.' ); + else if ( !filters[0].startsWith( "./" ) ) + filters[0].prepend( "./" ); + qb.excludeFilter( table, value, filters[0], QueryBuilder::modeBeginMatch ); + } + else if ( condition == i18n( "ends with" ) ) + qb.addFilter( table, value, filters[0], QueryBuilder::modeEndMatch ); + else if ( condition == i18n( "does not end with" ) ) + qb.excludeFilter( table, value, filters[0], QueryBuilder::modeEndMatch ); + else if ( condition == i18n( "is greater than") || condition == i18n( "is after" ) ) + qb.addNumericFilter( table, value, filters[0], QueryBuilder::modeGreater ); + else if ( condition == i18n( "is smaller than") || condition == i18n( "is before" ) ) + qb.addNumericFilter( table, value, filters[0], QueryBuilder::modeLess ); + else if ( condition == i18n( "is between" ) + || condition == i18n( "is in the last" ) ) + qb.addNumericFilter( table, value, filters[0], QueryBuilder::modeBetween, + filters[1] ); + else if ( condition == i18n( "is not between" ) + || condition == i18n( "is not in the last" ) ) + qb.addNumericFilter( table, value, filters[0], + QueryBuilder::modeNotBetween, filters[1] ); + } + + if ( matches.attribute( "glue" ) == "OR" ) + qb.endOR(); + else + qb.endAND(); + + } + + // order by + QDomNodeList orderbyList = xml.elementsByTagName( "orderby" ); + for ( uint i = 0; i < orderbyList.count(); i++ ) { + QDomElement orderby = orderbyList.item( i ).toElement(); + QString field = orderby.attribute( "field" ); + if ( field == "random" ) + { + // shuffle + if ( orderby.attribute("order" ) == "weighted" ) + qb.shuffle( QueryBuilder::tabStats, QueryBuilder::valScore ); + else if ( orderby.attribute("order" ) == "ratingweighted" ) + qb.shuffle( QueryBuilder::tabStats, QueryBuilder::valRating ); + else + qb.shuffle(); + } else { + // normal sort + int table; + Q_INT64 value; + if ( !qb.getField( field, &table, &value ) ) continue; + qb.sortBy( table, value, orderby.attribute( "order" ) == "DESC" ); + } + } + + if ( xml.hasAttribute( "maxresults" ) ) + qb.setLimit(0, xml.attribute( "maxresults" ).toInt() ); + + // expand by, if needed + if ( forExpand ) { + // TODO: The most efficient way would be to pass the children the XML + // and what to expand by, then have the children compute the query as needed. + // This could save a few megs of RAM for queries, but this patch is getting + // too big already, right now. Ovy + QDomNodeList expandbyList = xml.elementsByTagName( "expandby" ); + for ( uint i = 0; i < expandbyList.count(); i++ ) { + QDomElement expandby = expandbyList.item( i ).toElement(); + QString field = expandby.attribute( "field" ); + int table = QueryBuilder::tabGenre; // make compiler happy + if ( field == i18n( "Genre" ) ) + table = QueryBuilder::tabGenre; + else if ( field == i18n( "Artist" ) ) + table = QueryBuilder::tabArtist; + else if ( field == i18n( "Composer" ) ) + table = QueryBuilder::tabComposer; + else if ( field == i18n( "Album" ) ) + table = QueryBuilder::tabAlbum; + else if ( field == i18n( "Year" ) ) + table = QueryBuilder::tabYear; + else if ( field == i18n( "Label" ) ) + table = QueryBuilder::tabLabels; + + qb.addFilter( table, QueryBuilder::valName, + "(*ExpandString*)", + QueryBuilder::modeNormal, true); + } + } + + return qb.query( true ); +} + + +void SmartPlaylist::setDynamic( bool enable ) +{ + enable ? + setPixmap( 0, SmallIcon( "favorites" ) ) : + setPixmap( 0, SmallIcon( Amarok::icon( "playlist" ) ) ); + m_dynamic = enable; +} + +bool SmartPlaylist::isTimeOrdered() +{ + // matches statistics.createdate (firstplayed) and tags.createdate (modified date) + QRegExp createDate( "ORDER BY.*createdate" ); + // matches last played + QRegExp accessDate( "ORDER BY.*accessdate" ); + + const QString sql = query(); + + return ! ( ( sql.find( createDate, false ) == -1 ) /*not create ordered*/ && + ( sql.find( accessDate, false ) == -1 ) /*not access ordered*/ ); +} + +void SmartPlaylist::slotDoubleClicked() +{ + if( !query().isEmpty() ) + { + Playlist::instance()->proposePlaylistName( text(0) ); + Playlist::instance()->insertMediaSql( query(), Playlist::DefaultOptions ); + } +} + +void SmartPlaylist::showContextMenu( const QPoint &position ) +{ + KPopupMenu menu( listView() ); + + enum Actions { LOAD, ADD, QUEUE, EDIT, REMOVE, MEDIADEVICE_COPY, MEDIADEVICE_SYNC }; + + menu.insertItem( SmallIconSet( Amarok::icon( "files" ) ), i18n( "&Load" ), LOAD ); + menu.insertItem( SmallIconSet( Amarok::icon( "add_playlist" ) ), i18n( "&Append to Playlist" ), ADD ); + menu.insertItem( SmallIconSet( Amarok::icon( "queue_track" ) ), i18n( "&Queue Tracks" ), QUEUE ); + if( MediaBrowser::isAvailable() ) + { + menu.insertSeparator(); + menu.insertItem( SmallIconSet( Amarok::icon( "device" ) ), + i18n( "&Transfer to Media Device" ), MEDIADEVICE_COPY ); + menu.insertItem( SmallIconSet( Amarok::icon( "device" ) ), + i18n( "&Synchronize to Media Device" ), MEDIADEVICE_SYNC ); + } + + // Forbid removal of Collection + if( isKept() ) + { + menu.insertSeparator(); + if ( isEditable() ) + menu.insertItem( SmallIconSet( Amarok::icon("edit") ), i18n( "E&dit..." ), EDIT ); + menu.insertItem( SmallIconSet( Amarok::icon("remove_from_playlist") ), i18n( "&Delete" ), REMOVE ); + } + + switch( menu.exec( position ) ) + { + case LOAD: + Playlist::instance()->clear(); + Playlist::instance()->setPlaylistName( text(0) ); + //FALL THROUGH + case ADD: + Playlist::instance()->insertMediaSql( query(), Playlist::Append ); + break; + case QUEUE: + Playlist::instance()->insertMediaSql( query(), Playlist::Queue ); + break; + case EDIT: + PlaylistBrowser::instance()->editSmartPlaylist( this ); + PlaylistBrowser::instance()->saveSmartPlaylists(); + break; + case REMOVE: + PlaylistBrowser::instance()->removeSelectedItems(); + break; + case MEDIADEVICE_COPY: + { + const QString playlist = text(0); + const QStringList values = CollectionDB::instance()->query( query() ); + MediaBrowser::queue()->addURLs( CollectionDB::instance()->URLsFromSqlDrag( values ), playlist ); + } + break; + case MEDIADEVICE_SYNC: + MediaBrowser::queue()->syncPlaylist( text(0), query() ); + break; + } +} + +void SmartPlaylist::slotPostRenameItem( const QString newName ) +{ + xml().setAttribute( "name", newName ); +} + +ShoutcastBrowser::ShoutcastBrowser( PlaylistCategory *parent ) + : PlaylistCategory( parent, 0, i18n( "Shoutcast Streams" ) ) + , m_downloading( false ) + , m_cj( 0 ) + , m_loading1( new QPixmap( locate("data", "amarok/images/loading1.png" ) ) ) + , m_loading2( new QPixmap( locate("data", "amarok/images/loading2.png" ) ) ) +{ + setExpandable( true ); + setKept( false ); +} + +void ShoutcastBrowser::slotDoubleClicked() +{ + setOpen( !isOpen() ); +} + +void ShoutcastBrowser::setOpen( bool open ) +{ + if( open == isOpen()) + return; + + if ( firstChild() ) // don't redownload everything + { + QListViewItem::setOpen( open ); + return; + } + + if( !m_animationTimer.isActive() ) + m_animationTimer.start( ANIMATION_INTERVAL ); + connect( &m_animationTimer, SIGNAL(timeout()), this, SLOT(slotAnimation()) ); + + QStringList tmpdirs = KGlobal::dirs()->resourceDirs( "tmp" ); + QString tmpfile = tmpdirs[0]; + tmpfile += "/amarok-genres-" + KApplication::randomString(10) + ".xml-"; + + //get the genre list + if ( !m_downloading ) + { + m_downloading = true; + m_cj = KIO::copy( "http://www.shoutcast.com/sbin/newxml.phtml", tmpfile, false ); + connect( m_cj, SIGNAL( copyingDone( KIO::Job*, const KURL&, const KURL&, bool, bool)) + , this, SLOT(doneGenreDownload(KIO::Job*, const KURL&, const KURL&, bool, bool ))); + connect( m_cj, SIGNAL( result( KIO::Job* )), this, SLOT( jobFinished( KIO::Job* ))); + } + + QListViewItem::setOpen( open ); +} + +void ShoutcastBrowser::slotAnimation() +{ + static int s_iconCounter = 0; + s_iconCounter % 2 ? + setPixmap( 0, *m_loading1 ): + setPixmap( 0, *m_loading2 ); + + s_iconCounter++; +} + +void ShoutcastBrowser::doneGenreDownload( KIO::Job *job, const KURL &from, const KURL &to, bool directory, bool renamed ) +{ + Q_UNUSED( job ); Q_UNUSED( from ); Q_UNUSED( directory ); Q_UNUSED( renamed ); + + QDomDocument doc( "genres" ); + QFile file( to.path() ); + if ( !file.open( IO_ReadOnly ) ) + { + warning() << "Cannot open shoutcast genre xml" << endl; + m_downloading = false; + return; + } + if ( !doc.setContent( &file ) ) + { + warning() << "Cannot set shoutcast genre xml" << endl; + file.close(); + m_downloading = false; + return; + } + + file.close(); + + KIO::del( to, false, false ); + + // We use this list to filter out some obscure genres + QStringList bannedGenres; + bannedGenres << "alles" << "any" << "anything" << "autopilot" << "backup" << "bandas" << "beer"; + bannedGenres << "catholic" << "chr" << "das" << "domaca" << "everything" << "fire" << "her" << "hollands"; + bannedGenres << "http" << "just" << "lokale" << "middle" << "noticias" << "only" << "scanner" << "shqip"; + bannedGenres << "good" << "super" << "wusf" << "www" << "zabavna" << "zouk" << "whatever" << "varios"; + bannedGenres << "varius" << "video" << "opm" << "non" << "narodna" << "muzyka" << "muzica" << "muzika"; + bannedGenres << "musique" << "music" << "multi" << "online" << "mpb" << "musica" << "musik" << "manele"; + bannedGenres << "paranormal" << "todos" << "soca" << "the" << "toda" << "trova" << "italo"; + bannedGenres << "auto" << "alternativo" << "best" << "clasicos" << "der" << "desi" << "die" << "emisora"; + bannedGenres << "voor" << "post" << "playlist" << "ned" << "gramy" << "deportes" << "bhangra" << "exitos"; + bannedGenres << "doowop" << "radio" << "radyo" << "railroad" << "program" << "mostly" << "hot"; + bannedGenres << "deejay" << "cool" << "big" << "exitos" << "mp3" << "muzyczne" << "nederlandstalig"; + bannedGenres << "max" << "informaci" << "halk" << "dobra" << "welcome" << "genre"; + + // This maps genres that should be combined together + QMap genreMapping; + genreMapping["Romania"] = "Romanian"; + genreMapping["Turk"] = "Turkish"; + genreMapping["Turkce"] = "Turkish"; + genreMapping["Polskie"] = "Polska"; + genreMapping["Polski"] = "Polish"; + genreMapping["Greece"] = "Greek"; + genreMapping["Dnb"] = "Drum&bass"; + genreMapping["Classic"] = "Classical"; + genreMapping["Goth"] = "Gothic"; + genreMapping["Alt"] = "Alternative"; + genreMapping["Italiana"] = "Italian"; + genreMapping["Japan"] = "Japanese"; + genreMapping["Oldie"] = "Oldies"; + genreMapping["Nederlands"] = "Dutch"; + genreMapping["Variety"] = "Various"; + genreMapping["Soundtracks"] = "Soundtrack"; + genreMapping["Gaming"] = "Game"; + genreMapping["Sports"] = "Sport"; + genreMapping["Spain"] = "Spanish"; + + QDomElement docElem = doc.documentElement(); + QDomNode n = docElem.firstChild(); + QListViewItem *last = 0; + QMap genreCache; // maps names to the listview item + while( !n.isNull() ) + { + QDomElement e = n.toElement(); // try to convert the node to an element. + const QString name = e.attribute( "name" ); + if( !name.isNull() && !bannedGenres.contains( name.lower() ) && !genreMapping.contains( name ) ) + { + last = new ShoutcastGenre( this, last, name ); + genreCache[ name ] = last; // so we can append genres later if needed + } + n = n.nextSibling(); + } + // Process the mapped (alternate) genres + for( QMap::iterator it = genreMapping.begin(); it != genreMapping.end(); ++it ) + { + // Find the target genre + ShoutcastGenre *existingGenre = dynamic_cast ( genreCache[ it.data() ] ); + if( existingGenre != 0 ) + existingGenre->appendAlternateGenre( it.key() ); + } + m_downloading = false; + m_animationTimer.stop(); + setPixmap( 0, SmallIcon( Amarok::icon( "files" ) ) ); + setOpen( true ); +} + +void ShoutcastBrowser::jobFinished( KIO::Job *job ) +{ + m_downloading = false; + m_animationTimer.stop(); + setPixmap( 0, SmallIcon( Amarok::icon( "files" ) ) ); + + if ( job->error() ) + job->showErrorDialog( 0 ); +} + +ShoutcastGenre::ShoutcastGenre( ShoutcastBrowser *browser, QListViewItem *after, QString genre ) + : PlaylistCategory( browser, after, genre ) + , m_downloading( false ) + , m_loading1( new QPixmap( locate("data", "amarok/images/loading1.png" ) ) ) + , m_loading2( new QPixmap( locate("data", "amarok/images/loading2.png" ) ) ) +{ + setExpandable( true ); + setKept( false ); + m_genre = genre.replace( "&", "%26" ); //fix & +} + +void ShoutcastGenre::slotDoubleClicked() +{ + setOpen( !isOpen() ); +} + +void ShoutcastGenre::setOpen( bool open ) +{ + if( open == isOpen()) + return; + + if( firstChild() ) // don't redownload everything + { + QListViewItem::setOpen( open ); + return; + } + + if( !m_animationTimer.isActive() ) + m_animationTimer.start( ANIMATION_INTERVAL ); + connect( &m_animationTimer, SIGNAL(timeout()), this, SLOT(slotAnimation()) ); + + QStringList tmpdirs = KGlobal::dirs()->resourceDirs( "tmp" ); + + //get the genre list from shoutcast async, and when its done call the finish up functions to process + if( !m_downloading) + { + m_downloading = true; + m_totalJobs = 0; + m_completedJobs = 0; + startGenreDownload( m_genre, tmpdirs[0] ); + for( QStringList::iterator it = m_alternateGenres.begin(); it != m_alternateGenres.end(); ++it ) + startGenreDownload( *it, tmpdirs[0] ); + } +} + +void ShoutcastGenre::startGenreDownload( QString genre, QString tmppath ) +{ + QString tmpfile = tmppath + "/amarok-list-" + genre + "-" + KApplication::randomString(10) + ".xml"; + KIO::CopyJob *cj = KIO::copy( "http://www.shoutcast.com/sbin/newxml.phtml?genre=" + genre, tmpfile, false ); + connect( cj, SIGNAL( copyingDone ( KIO::Job*, const KURL&, const KURL&, bool, bool ) ), + this, SLOT( doneListDownload( KIO::Job*, const KURL&, const KURL&, bool, bool ) ) ); + connect( cj, SIGNAL( result ( KIO::Job* ) ), + this, SLOT( jobFinished( KIO::Job* ) ) ); + m_totalJobs++; +} + +void ShoutcastGenre::slotAnimation() +{ + static int s_iconCounter = 0; + s_iconCounter % 2 ? + setPixmap( 0, *m_loading1 ): + setPixmap( 0, *m_loading2 ); + + s_iconCounter++; +} + +void ShoutcastGenre::doneListDownload( KIO::Job *job, const KURL &from, const KURL &to, bool directory, bool renamed ) +{ + Q_UNUSED( job ); Q_UNUSED( from ); Q_UNUSED( directory ); Q_UNUSED( renamed ); + + m_completedJobs++; + + QDomDocument doc( "list" ); + QFile file( to.path() ); + if ( !file.open( IO_ReadOnly ) ) + { + warning() << "Cannot open shoutcast playlist xml" << endl; + m_downloading = false; + return; + } + if ( !doc.setContent( &file ) ) + { + warning() << "Cannot set shoutcast playlist xml" << endl; + file.close(); + m_downloading = false; + return; + } + + file.close(); + + KIO::del(to, false, false); + + //Go through the XML file and add all the stations + QDomElement docElem = doc.documentElement(); + QDomNode n = docElem.firstChild(); + while( !n.isNull() ) + { + QDomElement e = n.toElement(); // try to convert the node to an element. + if( e.hasAttribute( "name" ) ) + { + if( !e.attribute( "name" ).isNull() && ! m_stations.contains( e.attribute( "name" ) ) ) + { + m_stations << e.attribute( "name" ); + StreamEntry* entry = new StreamEntry( this, this, + "http://www.shoutcast.com/sbin/shoutcast-playlist.pls?rn=" + + e.attribute( "id" ) + "&file=filename.pls", e.attribute( "name" )); + + entry->setKept( false ); + } + } + n = n.nextSibling(); + } + if( m_completedJobs == m_totalJobs ) + { + setOpen( true ); + m_downloading = false; + m_animationTimer.stop(); + setPixmap( 0, SmallIcon( Amarok::icon( "files" ) ) ); + } +} + +void ShoutcastGenre::jobFinished( KIO::Job *job ) +{ + m_downloading = false; + m_animationTimer.stop(); + setPixmap( 0, SmallIcon( Amarok::icon( "files" ) ) ); + + if( job->error() ) + job->showErrorDialog( 0 ); +} + +#include "playlistbrowseritem.moc" diff --git a/amarok/src/playlistbrowseritem.h b/amarok/src/playlistbrowseritem.h new file mode 100644 index 00000000..7cba8ddc --- /dev/null +++ b/amarok/src/playlistbrowseritem.h @@ -0,0 +1,637 @@ +/*************************************************************************** + * copyright : (c) 2004 Pierpaolo Di Panfilo * + * (c) 2005-2006 Seb Ruiz * + * (c) 2006 Bart Cerneels * + * (c) 2006 Adam Pigg * + * (c) 2006 Bonne Eggleston * + * See COPYING file for licensing information * + ***************************************************************************/ + +#ifndef PLAYLISTBROWSERITEM_H +#define PLAYLISTBROWSERITEM_H + +#include "dynamicmode.h" +#include "podcastbundle.h" +#include "podcastsettings.h" + +#include // StreamEditor baseclass +#include +#include +#include +#include + +#include +#include +#include +#include +#include // Podcast loading animation +#include + +class MetaBundle; +class PlaylistTrackItem; +class TrackItemInfo; + +namespace KIO { class Job; class TransferJob; class CopyJob; } //podcast downloads + +/** + * RTTI VALUES + * 1000 - PlaylistCategory + * 1001 - PlaylistEntry + * 1002 - PlaylistTrackItem + * 1003 - StreamEntry + * 1004 - SmartPlaylist + * 1005 - DynamicEntry (Dynamic) + * 1006 - PodcastChannel + * 1007 - PodcastEpisode + */ + + +/* A base class to be able to use polymorphism and avoid tons of casts */ +class PlaylistBrowserEntry : public QObject, public KListViewItem +{ + Q_OBJECT + public: + PlaylistBrowserEntry( QListViewItem *parent, QListViewItem *after ) + : KListViewItem( parent, after) { m_kept = true; } + PlaylistBrowserEntry( QListView *parent, QListViewItem *after ) + : KListViewItem( parent, after) { m_kept = true; } + PlaylistBrowserEntry( QListViewItem *parent, QListViewItem *after, const QString &name ) + : KListViewItem( parent, after, name) { m_kept = true; } + + virtual QDomElement xml() const { return QDomElement(); } + QListViewItem* parent() const { return KListViewItem::parent(); } + + bool isKept() const { return m_kept; } // if kept == true, then it will be saved + void setKept( bool k ); // to the cache files. If false, non-renameable + + virtual void updateInfo(); + virtual void setDynamic( bool ) {}; + + public slots: + virtual void slotDoubleClicked(); + virtual void slotRenameItem(); + virtual void slotPostRenameItem( const QString newName ); + virtual void showContextMenu( const QPoint & ) {}; + + protected: + virtual int compare( QListViewItem*, int, bool ) const; //reimplemented + + /** Interval of the download pixmap animation, in milliseconds */ + static const int ANIMATION_INTERVAL = 250; + + private: + bool m_kept; +}; + +class DynamicEntry : public PlaylistBrowserEntry, public DynamicMode +{ + Q_OBJECT + public: + DynamicEntry( QListViewItem *parent, QListViewItem *after, const QString &title ); + DynamicEntry( QListViewItem *parent, QListViewItem *after, const QDomElement &xmlDefinition ); + ~DynamicEntry() { }; + + virtual QString text( int column ) const; + + virtual QDomElement xml() const; + + static const int RTTI = 1005; + int rtti() const { return RTTI; } + + public slots: + virtual void slotDoubleClicked(); + virtual void showContextMenu( const QPoint & ); +}; + +class PlaylistCategory : public PlaylistBrowserEntry +{ + Q_OBJECT + public: + PlaylistCategory( QListView *parent, QListViewItem *after, const QString &, bool isFolder=false ); + PlaylistCategory( PlaylistCategory *parent, QListViewItem *after, const QString &, bool isFolder=true ); + PlaylistCategory( QListView *parent, QListViewItem *after, const QDomElement &xmlDefinition, bool isFolder=false); + PlaylistCategory( PlaylistCategory *parent, QListViewItem *after, const QDomElement &xmlDefinition ); + PlaylistCategory( PlaylistCategory *parent, QListViewItem *after, const QString &t, const int id ); + + ~PlaylistCategory() { }; + + const QString &title() const { return m_title; } + bool isFolder() { return m_folder; } + + void paintCell( QPainter*, const QColorGroup&, int, int, int ); + + void setId( const int id ) { m_id = id; } + const int id() const { return m_id; } + + virtual QDomElement xml() const; + + int rtti() const { return RTTI; } + static const int RTTI = 1000; //category item + + public slots: + virtual void slotDoubleClicked(); + virtual void slotRenameItem(); + virtual void showContextMenu( const QPoint & ); + + protected: + void okRename( int col ); + + private: + + void setXml( const QDomElement &xml ); + + QString m_title; + int m_id; + bool m_folder; +}; + + +class PlaylistEntry : public PlaylistBrowserEntry +{ + Q_OBJECT + + friend class PlaylistTrackItem; + friend class TrackItemInfo; + friend class PlaylistCategory; + + public: + PlaylistEntry( QListViewItem *parent, QListViewItem *after, const KURL &, int tracks=0, int length=0 ); + PlaylistEntry( QListViewItem *parent, QListViewItem *after, const QDomElement &xmlDefinition ); + ~PlaylistEntry(); + + void sortChildItems ( int /*column*/, bool /*ascending*/ ) { /* Don't sort its children */ }; //reimplemented + + void load(); + + const KURL &url() const { return m_url; } + void setUrl( const QString &u ) { m_url.setPath( u ); } + int trackCount() const { return m_trackCount; } + int length() const { return m_length; } + bool isDynamic() const { return m_dynamic; } + bool isLoaded() const { return m_loaded; } + + void setDynamic( bool ); + + int compare( QListViewItem* i, int col ) const; //reimpl. + KURL::List tracksURL(); //returns the list of tracks url + void insertTracks( QListViewItem *after, KURL::List list ); + void insertTracks( QListViewItem *after, QValueList bundles ); + // isLast is used to avoid saving the playlist to disk every time a track is removed + // when removing a list of tracks from the playlist + void removeTrack( QListViewItem *item, bool isLast = true ); + + //returns a list of track information + QPtrList trackList() const { return m_trackList; } + QPtrList droppedTracks() const { return tmp_droppedTracks; } + + void setOpen( bool ); + void setup(); + void paintCell( QPainter*, const QColorGroup&, int, int, int ); + + virtual QDomElement xml() const; + + virtual void updateInfo(); + + //rtti is used to distinguish different kinds of list view items + int rtti() const { return RTTI; } + static const int RTTI = 1001; //playlist item + + public slots: + virtual void slotDoubleClicked(); + virtual void slotPostRenameItem( const QString newName ); + virtual void showContextMenu( const QPoint & ); + + signals: + void startingLoading(); + void loaded(); + + private slots: + void slotAnimation(); + + private: + void customEvent( QCustomEvent* e ); + void startAnimation(); + void stopAnimation(); + + KURL m_url; //playlist url + int m_length; //total length in seconds + int m_trackCount; //track counter + QPtrList m_trackList; //tracks in playlist + QPtrList tmp_droppedTracks; //tracks dropped to the playlist while it wasn't been loaded + bool m_loading; + bool m_loaded; //playlist loaded + bool m_dynamic; //the playlist is scheduled for dynamic mode rotation + QPixmap *m_loading1, *m_loading2; //icons for loading animation + QTimer m_animationTimer; + uint m_iconCounter; + PlaylistTrackItem *m_lastTrack; +}; + +class PlaylistTrackItem : public PlaylistBrowserEntry +{ + Q_OBJECT + friend class TrackItemInfo; + + public: + PlaylistTrackItem( QListViewItem *parent, QListViewItem *after, TrackItemInfo *info ); + + const KURL &url(); + TrackItemInfo *trackInfo() const { return m_trackInfo; } + + int rtti() const { return RTTI; } + static const int RTTI = 1002; //track item + + public slots: + virtual void slotDoubleClicked(); + virtual void slotRenameItem() { /* Do nothing */ }; + virtual void showContextMenu( const QPoint & ); + + private: + TrackItemInfo *m_trackInfo; +}; + +/// Stored in the database +class PodcastEpisode : public PlaylistBrowserEntry +{ + Q_OBJECT + + public: + PodcastEpisode( QListViewItem *parent, QListViewItem *after, const QDomElement &xml, + const int feedType, const bool &isNew=false ); + PodcastEpisode( QListViewItem *parent, QListViewItem *after, PodcastEpisodeBundle &bundle ); + + void downloadMedia(); + void setOnDisk( bool d = true ); + QListViewItem *itemChannel() { return m_parent; } + + + const bool isNew() const { return m_bundle.isNew(); } + + void setNew( const bool &n = true ); + void setListened( const bool &n = true ) { setNew( !n ); } + + // for convenience + const int dBId() const { return m_bundle.dBId(); } + const KURL url() const { return m_bundle.url(); } + const QString title() const { return m_bundle.title(); } + const QString author() const { return m_bundle.author(); } + const QString date() const { return m_bundle.date(); } + const QString type() const { return m_bundle.type(); } + const QString description() const { return m_bundle.description(); } + const QString guid() const { return m_bundle.guid(); } + const int duration() const { return m_bundle.duration(); } + const KURL &localUrl() const { return m_bundle.localUrl(); } + void setLocalUrlBase( const QString &s ); + void setLocalUrl( const KURL &localUrl ); + + void setup(); + void paintCell( QPainter*, const QColorGroup&, int, int, int ); + + virtual void updateInfo(); + + void addToMediaDevice(); + + int rtti() const { return RTTI; } + static const int RTTI = 1007; //PodcastEpisode + static void createLocalDir( const KURL &localDir ); + + signals: + void downloadFinished(); + void downloadAborted(); + + public slots: + const bool isOnDisk(); + virtual void slotDoubleClicked(); + virtual void slotRenameItem() { /* Do nothing */ }; + virtual void showContextMenu( const QPoint & ); + + private slots: + void abortDownload(); + void downloadResult( KIO::Job * transferJob ); + void slotAnimation(); + void redirected( KIO::Job * job,const KURL & redirectedUrl ); + + private: + enum FeedType{ RSS=0, ATOM=1 }; + + virtual int compare( QListViewItem*, int, bool ) const; //reimplemented + + void associateWithLocalFile(); + + void startAnimation(); + void stopAnimation(); + void updatePixmap(); + + QListViewItem *m_parent; //podcast channel it belongs to + PodcastEpisodeBundle m_bundle; + KURL m_localUrl; + + bool m_fetching; + QTimer m_animationTimer; + uint m_iconCounter; + + KIO::StoredTransferJob* m_podcastEpisodeJob; + QString m_filename; + + bool m_downloaded; //marked as downloaded in cached xml + bool m_onDisk; +}; + +/// Stored in the database +class PodcastChannel : public PlaylistBrowserEntry +{ + Q_OBJECT + + public: + PodcastChannel( QListViewItem *parent, QListViewItem *after, const KURL &url, + const QDomNode &channelSettings ); + PodcastChannel( QListViewItem *parent, QListViewItem *after, const KURL &url ); + PodcastChannel( QListViewItem *parent, QListViewItem *after, const KURL &url, + const QDomNode &channelSettings, const QDomDocument &xml ); + PodcastChannel( QListViewItem *parent, QListViewItem *after, const PodcastChannelBundle &pcb ); + + enum MediaFetch{ STREAM=0, AUTOMATIC=1 }; + + void setNew( const bool n = true ); + bool hasNew() const { return m_new; } + // iterate over all children and explicitly check if there are any episodes which have not been listened + // to. Mark the channel as new/listened after doing this. + void checkAndSetNew(); + + void setListened( const bool n = true ); // over rides each child so it has been listened + + void setOpen( bool open ); // if !m_polished, load the children. Lazy loading to improve start times + void load(); + const bool isPolished() const { return m_polished; } + + void configure(); + void fetch(); + void rescan(); + + const KURL &url() const { return m_url; } + const KURL link() const { return m_bundle.link(); } + const QString title() const { return m_bundle.title(); } + const QString description() const { return m_bundle.description(); } + const QString copyright() const { return m_bundle.copyright(); } + + const bool autoscan() const { return m_bundle.autoscan(); } + const bool autotransfer() const { return m_bundle.autotransfer(); } + const int fetchType() const { return m_bundle.fetchType(); } + const bool hasPurge() const { return m_bundle.hasPurge(); } + const int purgeCount() const { return m_bundle.purgeCount(); } + const QString &saveLocation() const { return m_bundle.saveLocation(); } + PodcastSettings *getSettings() const + { + return new PodcastSettings( title(), saveLocation(), + autoscan(), fetchType(), autotransfer(), + hasPurge(), purgeCount() ); + } + + void setParent( PlaylistCategory *newParent ); + void setSettings( PodcastSettings *settings ); + void setXml( const QDomNode &xml, const int feedType ); + + virtual void updateInfo(); + + int rtti() const { return RTTI; } + static const int RTTI = 1006; //podcastchannel + + public slots: + virtual void slotDoubleClicked(); + virtual void slotRenameItem() { /* Do nothing */ }; + virtual void showContextMenu( const QPoint & ); + + private slots: + void abortFetch(); + void downloadChildQueue(); + void fetchResult( KIO::Job* job ); + void slotAnimation(); + + private: + enum FeedType{ RSS=0, ATOM=1 }; + static const int EPISODE_LIMIT = 10; //Maximum number of episodes initially shown + + bool containsItem( QDomElement xml ); + void downloadChildren(); + const bool episodeExists( const QDomNode &xml, const int feedType ); + void purge(); + void restorePurged(); + void removeChildren(); + void setDOMSettings( const QDomNode &channelSettings ); + void startAnimation(); + void stopAnimation(); + + PodcastChannelBundle m_bundle; + + /// loading all of the podcast episodes during startup can be very inefficient. + /// When the user expands the podcast for the first time, we load up the episodes. + bool m_polished; + + KURL m_url; //remote xml url + bool m_fetching; + bool m_updating; + QTimer m_animationTimer; + uint m_iconCounter; + bool m_new; + bool m_hasProblem; + + KIO::TransferJob *m_podcastJob; + PlaylistCategory *m_parent; // category it belongs to + QString m_podcastCurrentUrl; + QPtrList m_podcastDownloadQueue; + bool m_settingsValid; +}; + +class StreamEntry : public PlaylistBrowserEntry +{ + Q_OBJECT + public: + StreamEntry( QListViewItem *parent, QListViewItem *after, const KURL &, const QString &t ); + StreamEntry( QListViewItem *parent, QListViewItem *after, const QDomElement &xmlDefinition ); + ~StreamEntry() { }; + + void setURL ( KURL u ) { m_url = u; } + void setTitle( QString t ) { m_title = t; } + + void setup(); + void paintCell( QPainter*, const QColorGroup&, int, int, int ); + + const KURL &url() const { return m_url; } + const QString &title() const { return m_title; } + + virtual QDomElement xml() const; + + virtual void updateInfo(); + + int rtti() const { return RTTI; } + static const int RTTI = 1003; //stream item + + public slots: + virtual void slotDoubleClicked(); + virtual void showContextMenu( const QPoint & ); + + protected: + QString m_title; + KURL m_url; +}; + +class LastFmEntry : public StreamEntry +{ + Q_OBJECT + public: + LastFmEntry( QListViewItem *parent, QListViewItem *after, const KURL &u, const QString &t ) + : StreamEntry( parent, after, u, t ) { } + LastFmEntry( QListViewItem *parent, QListViewItem *after, const QDomElement &xmlDefinition ) + : StreamEntry( parent, after, xmlDefinition ) { } + virtual QDomElement xml() const; + + public slots: + virtual void slotRenameItem() { /* Do nothing */ } + + public: + int rtti() const { return RTTI; } + static const int RTTI = 1008; //lastfm item +}; + +class StreamEditor : public KDialogBase +{ + public: + StreamEditor( QWidget *parent, const QString &title, const QString &url, bool readonly = false ); + + KURL url() const { return KURL( m_urlLineEdit->text() ); } + QString name() const { return m_nameLineEdit->text().replace( "\n", " " ); } + + private: + KLineEdit *m_urlLineEdit; + KLineEdit *m_nameLineEdit; + +}; + +class SmartPlaylist : public PlaylistBrowserEntry +{ + Q_OBJECT + public: + SmartPlaylist( QListViewItem *parent, QListViewItem *after, const QString &name, const QString &query ); + SmartPlaylist( QListViewItem *parent, QListViewItem *after, const QString &name, + const QString &urls, const QString &tags ); + SmartPlaylist( QListViewItem *parent, QListViewItem *after, const QDomElement &xmlDefinition ); + + bool isDynamic() const { return m_dynamic; } + bool isEditable() const { return !m_xml.isNull(); } + bool isTimeOrdered(); //returns yes if the ordering is based on a time attribute + QString query(); + QString title() const { return m_title; } + virtual QDomElement xml() const { return m_xml; } + + int length(); + void setDynamic( bool ); + void setXml( const QDomElement &xml ); + + int rtti() const { return RTTI; } + static const int RTTI = 1004; //smart playlist item + + public slots: + virtual void slotDoubleClicked(); + virtual void slotPostRenameItem( const QString newName ); + virtual void showContextMenu( const QPoint & ); + + private: + // for xml playlists, this member is computed on demand + QString m_sqlForTags; + + QString m_title; + QDomElement m_xml; + QListViewItem *m_after; + bool m_dynamic; + + // Build the query for a given xml object. If \p for expand is true, + // insert (*ExpandString*) as placeholders for childrens' filters + static QString xmlToQuery( const QDomElement &xml, bool forExpand = false ); +}; + +//this class is used to store information of a playlist track +class TrackItemInfo +{ + public: + TrackItemInfo( const MetaBundle &mb ); + ~TrackItemInfo() {} + const KURL &url() const { return m_url; } + const QString &album() const { return m_album; } + const QString &artist() const { return m_artist; } + const QString &title() const { return m_title; } + const int length() const { return m_length; } + + private: + KURL m_url; + QString m_artist; + QString m_album; + QString m_title; + int m_length; +}; + +/*! + @brief Implement a shoutcast playlist category + + On open, download the shoutcast genre XML file. + + Process the file and add each genre as a ShoutcastGenre + style PlaylistCategory +*/ +class ShoutcastBrowser : public PlaylistCategory +{ + Q_OBJECT + public: + ShoutcastBrowser( PlaylistCategory* parent ); + void setOpen( bool open ); + + public slots: + virtual void slotDoubleClicked(); + + private slots: + void doneGenreDownload( KIO::Job *job, const KURL &from, const KURL &to, bool directory, bool renamed ); + void jobFinished( KIO::Job *job ); + void slotAnimation(); + + private: + bool m_downloading; + KIO::CopyJob *m_cj; + QPixmap *m_loading1, *m_loading2; //icons for loading animation + QTimer m_animationTimer; +}; + +/*! + @brief Implement a shoutcast genre category + + On open, download the shoutcast station list XML file. + + Process the file and add each station as a StreamEntry +*/ +class ShoutcastGenre : public PlaylistCategory +{ + Q_OBJECT + public: + ShoutcastGenre( ShoutcastBrowser *browser, QListViewItem *after, QString genre ); + void setOpen( bool open ); + void appendAlternateGenre( QString alternateGenre ) { m_alternateGenres << alternateGenre; } + + public slots: + virtual void slotDoubleClicked(); + + private slots: + void doneListDownload( KIO::Job *job, const KURL &from, const KURL &to, bool directory, bool renamed ); + void jobFinished( KIO::Job *job ); + void slotAnimation(); + + private: + void startGenreDownload( QString genre, QString tmppath ); + bool m_downloading; + QString m_genre; + QPixmap *m_loading1, *m_loading2; //icons for loading animation + QTimer m_animationTimer; + QStringList m_alternateGenres; + QStringList m_stations; + int m_totalJobs; + int m_completedJobs; +}; + +#endif diff --git a/amarok/src/playlistitem.cpp b/amarok/src/playlistitem.cpp new file mode 100644 index 00000000..cf63e62b --- /dev/null +++ b/amarok/src/playlistitem.cpp @@ -0,0 +1,1152 @@ +/*************************************************************************** + playlistitem.cpp - description + ------------------- + begin : Die Dez 3 2002 + copyright : (C) 2002 by Mark Kretschmann + email : markey@web.de + copyright : (C) 2005 by Alexandre Oliveira + email : aleprj@gmail.com +***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#define DEBUG_PREFIX "PlaylistItem" + +#include +#include "amarok.h" +#include "amarokconfig.h" +#include "collectiondb.h" +#include "debug.h" +#include "enginecontroller.h" +#include "playlist.h" +#include "sliderwidget.h" +#include "starmanager.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "playlistitem.h" + +double PlaylistItem::glowIntensity; +QColor PlaylistItem::glowText = Qt::white; +QColor PlaylistItem::glowBase = Qt::white; +bool PlaylistItem::s_pixmapChanged = false; + + +PlaylistItem::PlaylistItem( QListView *listview, QListViewItem *item ) + : KListViewItem( listview, item ) + , m_album( 0 ) +{ + KListViewItem::setVisible( false ); +} + +PlaylistItem::PlaylistItem( const MetaBundle &bundle, QListViewItem *lvi, bool enabled ) + : MetaBundle( bundle ), KListViewItem( lvi->listView(), lvi->itemAbove() ) + , m_album( 0 ) + , m_deleteAfterEdit( false ) + , m_isBeingRenamed( false ) + , m_isNew( true ) +{ + setDragEnabled( true ); + + Playlist::instance()->m_urlIndex.add( this ); + if( !uniqueId().isEmpty() ) + Playlist::instance()->addToUniqueMap( uniqueId(), this ); + + + refAlbum(); + + incrementCounts(); + incrementLengths(); + + filter( listView()->m_filter ); + + listView()->countChanged(); + setAllCriteriaEnabled( enabled ); +} + +PlaylistItem::~PlaylistItem() +{ + if( isEmpty() ) //constructed with the generic constructor, for PlaylistLoader's marker item + return; + + decrementCounts(); + decrementLengths(); + + derefAlbum(); + + listView()->countChanged(); + + if( listView()->m_hoveredRating == this ) + listView()->m_hoveredRating = 0; + + Playlist::instance()->removeFromUniqueMap( uniqueId(), this ); + Playlist::instance()->m_urlIndex.remove(this); + + +} + + +///////////////////////////////////////////////////////////////////////////////////// +// PUBLIC METHODS +///////////////////////////////////////////////////////////////////////////////////// + + +void PlaylistItem::setText( int column, const QString &text ) +{ + if( column == Rating ) + setExactText( column, QString::number( int( text.toFloat() * 2 ) ) ); + else + setExactText( column, text ); +} + +QString PlaylistItem::text( int column ) const +{ + if( column == Title && listView()->header()->sectionSize( Filename ) ) //don't show the filename twice + return exactText( column ); + else switch ( column ) + { + case Artist: + case Composer: + case Album: + case Genre: + case Comment: + return exactText( column ); //HACK + case Rating: + return isEditing( column ) ? exactText( column ) : prettyText( column ); + default: + { + if( column != Title && isEditing( column ) ) + return editingText(); + else + return prettyText( column ); + } + } +} + +void PlaylistItem::aboutToChange( const QValueList &columns ) +{ + bool totals = false, ref = false, length = false, url = false; + for( int i = 0, n = columns.count(); i < n; ++i ) + switch( columns[i] ) + { + case Length: length = true; break; + case Artist: case Album: ref = true; //note, no breaks + case Track: case Rating: case Score: case LastPlayed: + totals = true; break; + case Filename: case Directory: url = true; break; + } + if ( length ) + decrementLengths(); + if( totals ) + decrementTotals(); + if( ref ) + derefAlbum(); + if ( url ) + Playlist::instance()->m_urlIndex.remove(this); +} + +void PlaylistItem::reactToChanges( const QValueList &columns ) +{ + MetaBundle::reactToChanges(columns); + bool totals = false, ref = false, length = false, url = false; + for( int i = 0, n = columns.count(); i < n; ++i ) + { + if( columns[i] == Mood ) + moodbar().reset(); + if ( !length && columns[i] == Length ) { + length = true; + incrementLengths(); + listView()->countChanged(); + } + switch( columns[i] ) + { + case Artist: case Album: ref = true; //note, no breaks + case Track: case Rating: case Score: case LastPlayed: + totals = true; break; + case Filename: case Directory: url = true; + } + updateColumn( columns[i] ); + } + if ( url ) + Playlist::instance()->m_urlIndex.add(this); + if( ref ) + refAlbum(); + if( totals ) + incrementTotals(); +} + +void PlaylistItem::filter( const QString &expression ) +{ + setVisible( matchesExpression( expression, listView()->visibleColumns() ) ); +} + +bool PlaylistItem::isCurrent() const +{ + return this == listView()->currentTrack(); +} + +bool PlaylistItem::isQueued() const +{ + return queuePosition() != -1; +} + +int PlaylistItem::queuePosition() const +{ + return listView()->m_nextTracks.findRef( this ); +} + +void PlaylistItem::setEnabled() +{ + m_enabled = m_filestatusEnabled && m_dynamicEnabled; + setDropEnabled( m_enabled ); // this forbids items to be dropped into a history queue. + + update(); +} + +void PlaylistItem::setDynamicEnabled( bool enabled ) +{ + m_dynamicEnabled = enabled; + setEnabled(); +} + +void PlaylistItem::setFilestatusEnabled( bool enabled ) +{ + m_filestatusEnabled = enabled; + checkExists(); + setEnabled(); +} + +void PlaylistItem::setAllCriteriaEnabled( bool enabled ) +{ + m_filestatusEnabled = enabled; + m_dynamicEnabled = enabled; + checkExists(); + setEnabled(); +} + +void PlaylistItem::setSelected( bool selected ) +{ + if( isEmpty() ) + return; + + if( isVisible() ) + { + const bool prevSelected = isSelected(); + KListViewItem::setSelected( selected ); + if( prevSelected && !isSelected() ) + { + listView()->m_selCount--; + listView()->m_selLength -= length(); + listView()->countChanged(); + } + else if( !prevSelected && isSelected() ) + { + listView()->m_selCount++; + listView()->m_selLength += length(); + listView()->countChanged(); + } + } +} + +void PlaylistItem::setVisible( bool visible ) +{ + if( isEmpty() ) + return; + + if( !visible && isSelected() ) + { + listView()->m_selCount--; + listView()->m_selLength -= length(); + KListViewItem::setSelected( false ); + listView()->countChanged(); + } + + const bool prevVisible = isVisible(); + KListViewItem::setVisible( visible ); + if( prevVisible && !isVisible() ) + { + listView()->m_visCount--; + listView()->m_visLength -= length(); + listView()->countChanged(); + decrementTotals(); + } + else if( !prevVisible && isVisible() ) + { + listView()->m_visCount++; + listView()->m_visLength += length(); + listView()->countChanged(); + incrementTotals(); + } +} + +void PlaylistItem::setEditing( int column ) +{ + switch( column ) + { + case Title: + case Artist: + case Composer: + case Album: + case Genre: + case Comment: + setExactText( column, editingText() ); + break; + case Year: m_year = -1; break; + case DiscNumber: m_discNumber = -1; break; + case Track: m_track = -1; break; + case Bpm: m_bpm = -1; break; + case Length: m_length = -1; break; + case Bitrate: m_bitrate = -1; break; + case SampleRate: m_sampleRate = -1; break; + case Score: m_score = -1; break; + case Rating: m_rating = -1; break; + case PlayCount: m_playCount = -1; break; + case LastPlayed: m_lastPlay = 1; break; + default: warning() << "Tried to set the text of an immutable or nonexistent column!" << endl; + } + + update(); +} + +bool PlaylistItem::isEditing( int column ) const +{ + switch( column ) + { + case Title: + case Artist: + case Composer: + case Album: + case Genre: + case Comment: //FIXME fix this hack! + return exactText( column ) == editingText(); + case Year: return m_year == -1; + case DiscNumber: return m_discNumber == -1; + case Track: return m_track == -1; + case Bpm: return m_bpm == -1; + case Length: return m_length == -1; + case Bitrate: return m_bitrate == -1; + case SampleRate: return m_sampleRate == -1; + case Score: return m_score == -1; + case Rating: return m_rating == -1; + case PlayCount: return m_playCount == -1; + case LastPlayed: return m_lastPlay == 1; + default: return false; + } +} + +bool PlaylistItem::anyEditing() const +{ + for( int i = 0; i < NUM_COLUMNS; i++ ) + { + if( isEditing( i ) ) + return true; + } + return false; +} + +int PlaylistItem::ratingAtPoint( int x ) //static +{ + Playlist* const pl = Playlist::instance(); + x -= pl->header()->sectionPos( Rating ); + return kClamp( ( x - 1 ) / ( StarManager::instance()->getGreyStar()->width() + pl->itemMargin() ) + 1, 1, 5 ) * 2; +} + +int PlaylistItem::ratingColumnWidth() //static +{ + return StarManager::instance()->getGreyStar()->width() * 5 + Playlist::instance()->itemMargin() * 6; +} + +void PlaylistItem::update() const +{ + listView()->repaintItem( this ); +} + +void PlaylistItem::updateColumn( int column ) const +{ + const QRect r = listView()->itemRect( this ); + if( !r.isValid() ) + return; + + listView()->viewport()->update( listView()->header()->sectionPos( column ) - listView()->contentsX() + 1, + r.y() + 1, + listView()->header()->sectionSize( column ) - 2, height() - 2 ); +} + +bool +PlaylistItem::operator== ( const PlaylistItem & item ) const +{ + return item.url() == this->url(); +} + +bool +PlaylistItem::operator< ( const PlaylistItem & item ) const +{ + return item.url() < this->url(); +} + +PlaylistItem* +PlaylistItem::nextInAlbum() const +{ + if( !m_album ) + return 0; + const int index = m_album->tracks.findRef( this ); + if( index == int(m_album->tracks.count() - 1) ) + return 0; + if( index != -1 ) + return m_album->tracks.at( index + 1 ); + if( track() ) + for( int i = 0, n = m_album->tracks.count(); i < n; ++i ) + if( m_album->tracks.at( i )->discNumber() > discNumber() || + ( m_album->tracks.at( i )->discNumber() == discNumber() && m_album->tracks.at( i )->track() > track() ) ) + return m_album->tracks.at( i ); + else + for( QListViewItemIterator it( const_cast(this), QListViewItemIterator::Visible ); *it; ++it ) + #define pit static_cast( *it ) + if( pit != this && pit->m_album == m_album && !pit->track() ) + return pit; + #undef pit + return 0; +} + +PlaylistItem* +PlaylistItem::prevInAlbum() const +{ + if( !m_album ) + return 0; + const int index = m_album->tracks.findRef( this ); + if( index == 0 ) + return 0; + if( index != -1 ) + return m_album->tracks.at( index - 1 ); + if( track() ) + for( int i = m_album->tracks.count() - 1; i >= 0; --i ) + if( m_album->tracks.at( i )->track() && + ( m_album->tracks.at( i )->discNumber() < discNumber() || + ( m_album->tracks.at( i )->discNumber() == discNumber() && m_album->tracks.at( i )->track() < track() ) ) ) + return m_album->tracks.at( i ); + else + for( QListViewItemIterator it( const_cast(this), QListViewItemIterator::Visible ); *it; --it ) + #define pit static_cast( *it ) + if( pit != this && pit->m_album == m_album && !pit->track() ) + return pit; + #undef pit + return 0; +} + +///////////////////////////////////////////////////////////////////////////////////// +// PRIVATE METHODS +///////////////////////////////////////////////////////////////////////////////////// + +int +PlaylistItem::compare( QListViewItem *i, int col, bool ascending ) const +{ + #define i static_cast(i) + if( Playlist::instance()->dynamicMode() && (isEnabled() != i->isEnabled()) ) + return isEnabled() ? 1 : -1; + + //damn C++ and its lack of operator<=> + #define cmp(a,b) ( (a < b ) ? -1 : ( a > b ) ? 1 : 0 ) + switch( col ) + { + case Track: return cmp( track(), i->track() ); + case Score: return cmp( score(), i->score() ); + case Rating: return cmp( rating(), i->rating() ); + case Length: return cmp( length(), i->length() ); + case PlayCount: return cmp( playCount(), i->playCount() ); + case LastPlayed: return cmp( lastPlay(), i->lastPlay() ); + case Bitrate: return cmp( bitrate(), i->bitrate() ); + case Bpm: return cmp( bpm(), i->bpm() ); + case Filesize: return cmp( filesize(), i->filesize() ); + case Mood: + return cmp( moodbar_const().hueSort(), i->moodbar_const().hueSort() ); + case Year: + if( year() == i->year() ) + return compare( i, Artist, ascending ); + return cmp( year(), i->year() ); + case DiscNumber: + if( discNumber() == i->discNumber() ) + return compare( i, Track, true ) * (ascending ? 1 : -1); + return cmp( discNumber(), i->discNumber() ); + } + #undef cmp + #undef i + + QString a = text( col ).lower(); + QString b = i->text( col ).lower(); + + switch( col ) + { + case Type: + a = a.rightJustify( b.length(), '0' ); + b = b.rightJustify( a.length(), '0' ); + break; + + case Artist: + if( a == b ) //if same artist, try to sort by album + return compare( i, Album, ascending ); + else + { + if( a.startsWith( "the ", false ) ) + a = a.mid( 4 ); + if( b.startsWith( "the ", false ) ) + b = b.mid( 4 ); + } + break; + + case Album: + if( a == b ) //if same album, try to sort by track + //TODO only sort in ascending order? + return compare( i, DiscNumber, true ) * (ascending ? 1 : -1); + break; + } + + return QString::localeAwareCompare( a, b ); +} + +void PlaylistItem::paintCell( QPainter *painter, const QColorGroup &cg, int column, int width, int align ) +{ + //TODO add spacing on either side of items + //p->translate( 2, 0 ); width -= 3; + + // Don't try to draw if width or height is 0, as this crashes Qt + if( !painter || !listView() || width <= 0 || height() == 0 ) + return; + + static const QImage currentTrackLeft = locate( "data", "amarok/images/currenttrack_bar_left.png" ); + static const QImage currentTrackMid = locate( "data", "amarok/images/currenttrack_bar_mid.png" ); + static const QImage currentTrackRight = locate( "data", "amarok/images/currenttrack_bar_right.png" ); + + if( column == Mood && !moodbar().dataExists() ) + moodbar().load(); // Only has an effect the first time + // The moodbar column can have text in it, like "Calculating". + // moodbarType is 0 if column != Mood, 1 if we're displaying + // a moodbar, and 2 if we're displaying text + const int moodbarType = + column != Mood ? 0 : moodbar().state() == Moodbar::Loaded ? 1 : 2; + + const QString colText = text( column ); + const bool isCurrent = this == listView()->currentTrack(); + + QPixmap buf( width, height() ); + QPainter p( &buf, true ); + + if( isCurrent ) + { + static paintCacheItem paintCache[NUM_COLUMNS]; + + // Convert intensity to string, so we can use it as a key + const QString colorKey = QString::number( glowIntensity ); + + const bool cacheValid = + paintCache[column].width == width && + paintCache[column].height == height() && + paintCache[column].text == colText && + paintCache[column].font == painter->font() && + paintCache[column].color == glowBase && + paintCache[column].selected == isSelected() && + !s_pixmapChanged; + + // If any parameter changed, we must regenerate all pixmaps + if ( !cacheValid ) + { + for( int i = 0; i < NUM_COLUMNS; ++i) + paintCache[i].map.clear(); + s_pixmapChanged = false; + } + + // Determine if we need to repaint the pixmap, or paint from cache + if ( paintCache[column].map.find( colorKey ) == paintCache[column].map.end() ) + { + // Update painting cache + paintCache[column].width = width; + paintCache[column].height = height(); + paintCache[column].text = colText; + paintCache[column].font = painter->font(); + paintCache[column].color = glowBase; + paintCache[column].selected = isSelected(); + + QColor bg; + if( isSelected() ) + bg = listView()->colorGroup().highlight(); + else + bg = isAlternate() ? listView()->alternateBackground() : + listView()->viewport()->backgroundColor(); + + buf.fill( bg ); + + // Draw column divider line + p.setPen( listView()->viewport()->colorGroup().mid() ); + p.drawLine( width - 1, 0, width - 1, height() - 1 ); + + // Here we draw the background bar graphics for the current track: + // + // Illustration of design, L = Left, M = Middle, R = Right: + // + + int leftOffset = 0; + int rightOffset = 0; + int margin = listView()->itemMargin(); + + const float colorize = 0.8; + const double intensity = 1.0 - glowIntensity * 0.021; + + // Left part + if( column == listView()->m_firstColumn ) { + QImage tmpImage = currentTrackLeft.smoothScale( 1, height(), QImage::ScaleMax ); + KIconEffect::colorize( tmpImage, glowBase, colorize ); + imageTransparency( tmpImage, intensity ); + p.drawImage( 0, 0, tmpImage, 0, 0, tmpImage.width() - 1 ); //HACK + leftOffset = tmpImage.width() - 1; //HACK Subtracting 1, to work around the black line bug + margin += 6; + } + + // Right part + else + if( column == Playlist::instance()->mapToLogicalColumn( Playlist::instance()->numVisibleColumns() - 1 ) ) + { + QImage tmpImage = currentTrackRight.smoothScale( 1, height(), QImage::ScaleMax ); + KIconEffect::colorize( tmpImage, glowBase, colorize ); + imageTransparency( tmpImage, intensity ); + p.drawImage( width - tmpImage.width(), 0, tmpImage ); + rightOffset = tmpImage.width(); + margin += 6; + } + + // Middle part + // Here we scale the one pixel wide middel image to stretch to the full column width. + QImage tmpImage = currentTrackMid.copy(); + KIconEffect::colorize( tmpImage, glowBase, colorize ); + imageTransparency( tmpImage, intensity ); + tmpImage = tmpImage.smoothScale( width - leftOffset - rightOffset, height() ); + p.drawImage( leftOffset, 0, tmpImage ); + + + // Draw the pixmap, if present + int leftMargin = margin; + if ( pixmap( column ) ) { + p.drawPixmap( leftMargin, height() / 2 - pixmap( column )->height() / 2, *pixmap( column ) ); + leftMargin += pixmap( column )->width() + 2; + } + + if( align != Qt::AlignCenter ) + align |= Qt::AlignVCenter; + + if( column != Rating && + moodbarType != 1 ) + { + // Draw the text + static QFont font; + static int minbearing = 1337 + 666; + if( minbearing == 2003 || font != painter->font() ) + { + font = painter->font(); + minbearing = painter->fontMetrics().minLeftBearing() + + painter->fontMetrics().minRightBearing(); + } + const bool italic = font.italic(); + int state = EngineController::engine()->state(); + if( state == Engine::Playing || state == Engine::Paused ) + font.setItalic( !italic ); + p.setFont( font ); + p.setPen( cg.highlightedText() ); +// paint.setPen( glowText ); + const int _width = width - leftMargin - margin + minbearing - 1; // -1 seems to be necessary + const QString _text = KStringHandler::rPixelSqueeze( colText, painter->fontMetrics(), _width ); + p.drawText( leftMargin, 0, _width, height(), align, _text ); + font.setItalic( italic ); + p.setFont( font ); + } + + paintCache[column].map[colorKey] = buf; + } + else + p.drawPixmap( 0, 0, paintCache[column].map[colorKey] ); + if( column == Rating ) + drawRating( &p ); + if( moodbarType == 1 ) + drawMood( &p, width, height() ); + } + else + { + const QColorGroup _cg = ( !exists() || !isEnabled() ) + ? listView()->palette().disabled() + : listView()->palette().active(); + + QColor bg = isSelected() ? _cg.highlight() + : isAlternate() ? listView()->alternateBackground() + : listView()->viewport()->backgroundColor(); + #if KDE_IS_VERSION( 3, 3, 91 ) + if( listView()->shadeSortColumn() && !isSelected() && listView()->columnSorted() == column ) + { + /* from klistview.cpp + Copyright (C) 2000 Reginald Stadlbauer + Copyright (C) 2000,2003 Charles Samuels + Copyright (C) 2000 Peter Putzer */ + if ( bg == Qt::black ) + bg = QColor(55, 55, 55); // dark gray + else + { + int h,s,v; + bg.hsv(&h, &s, &v); + if ( v > 175 ) + bg = bg.dark(104); + else + bg = bg.light(120); + } + } + #endif + + const QColor textc = isSelected() ? _cg.highlightedText() : _cg.text(); + + buf.fill( bg ); + + // Draw column divider line + if( !isSelected() ) + { + p.setPen( listView()->viewport()->colorGroup().mid() ); + p.drawLine( width - 1, 0, width - 1, height() - 1 ); + } + + // Draw the pixmap, if present + int margin = listView()->itemMargin(), leftMargin = margin; + if ( pixmap( column ) ) { + p.drawPixmap( leftMargin, height() / 2 - pixmap( column )->height() / 2, *pixmap( column ) ); + leftMargin += pixmap( column )->width(); + } + + if( align != Qt::AlignCenter ) + align |= Qt::AlignVCenter; + + if( column == Rating ) + drawRating( &p ); + else if( moodbarType == 1 ) + drawMood( &p, width, height() ); + else + { + // Draw the text + static QFont font; + static int minbearing = 1337 + 666; //can be 0 or negative, 2003 is less likely + if( minbearing == 2003 || font != painter->font() ) + { + font = painter->font(); //getting your bearings can be expensive, so we cache them + minbearing = painter->fontMetrics().minLeftBearing() + + painter->fontMetrics().minRightBearing(); + } + p.setFont( font ); + p.setPen( ( m_isNew && isEnabled() && !isSelected() ) ? AmarokConfig::newPlaylistItemsColor() : textc ); + + const int _width = width - leftMargin - margin + minbearing - 1; // -1 seems to be necessary + const QString _text = KStringHandler::rPixelSqueeze( colText, painter->fontMetrics(), _width ); + p.drawText( leftMargin, 0, _width, height(), align, _text ); + } + } + /// Track action symbols + const int queue = listView()->m_nextTracks.findRef( this ) + 1; + const bool stop = ( this == listView()->m_stopAfterTrack ); + const bool repeat = Amarok::repeatTrack() && isCurrent; + + const uint num = ( queue ? 1 : 0 ) + ( stop ? 1 : 0 ) + ( repeat ? 1 : 0 ); + + static const QPixmap pixstop = Amarok::getPNG( "currenttrack_stop_small" ), + pixrepeat = Amarok::getPNG( "currenttrack_repeat_small" ); + + //figure out if we are in the actual physical first column + if( column == listView()->m_firstColumn && num ) + { + //margin, height + const uint m = 2, h = height() - m; + + const QString str = QString::number( queue ); + + const uint qw = painter->fontMetrics().width( str ), sw = pixstop.width(), rw = pixrepeat.width(), + qh = painter->fontMetrics().height(), sh = pixstop.height(), rh = pixrepeat.height(); + + //maxwidth + const uint mw = kMax( qw, kMax( rw, sw ) ); + + //width of first & second column of pixmaps + const uint w1 = ( num == 3 ) ? kMax( qw, rw ) + : ( num == 2 && isCurrent ) ? kMax( repeat ? rw : 0, kMax( stop ? sw : 0, queue ? qw : 0 ) ) + : ( num == 2 ) ? qw + : queue ? qw : repeat ? rw : stop ? sw : 0, + w2 = ( num == 3 ) ? sw + : ( num == 2 && !isCurrent ) ? sw + : 0; //phew + + //ellipse width, total width + const uint ew = 16, tw = w1 + w2 + m * ( w2 ? 2 : 1 ); + p.setBrush( cg.highlight() ); + p.setPen( cg.highlight().dark() ); //TODO blend with background color + p.drawEllipse( width - tw - ew/2, m / 2, ew, h ); + p.drawRect( width - tw, m / 2, tw, h ); + p.setPen( cg.highlight() ); + p.drawLine( width - tw, m/2 + 1, width - tw, h - m/2 ); + + int x = width - m - mw, y = height() / 2, tmp = 0; + const bool multi = ( isCurrent && num >= 2 ); + if( queue ) + { + //draw the shadowed inner text + //NOTE we can't set an arbituary font size or family, these settings are already optional + //and user defaults should also take presidence if no playlist font has been selected + //const QFont smallFont( "Arial", (playNext > 9) ? 9 : 12 ); + //p->setFont( smallFont ); + //TODO the shadow is hard to do well when using a dark font color + //TODO it also looks cluttered for small font sizes + //p->setPen( cg.highlightedText().dark() ); + //p->drawText( width - w + 2, 3, w, h-1, Qt::AlignCenter, str ); + + if( !multi ) + tmp = -(qh / 2); + y += tmp; + p.setPen( cg.highlightedText() ); + p.drawText( x, y, -x + width, multi ? h/2 : qh, Qt::AlignCenter, str ); + y -= tmp; + if( isCurrent ) + y -= height() / 2; + else + x -= m + w2; + } + if( repeat ) + { + if( multi ) + tmp = (h/2 - rh)/2 + ( num == 2 && stop ? 0 : 1 ); + else + tmp = -(rh / 2); + y += tmp; + p.drawPixmap( x, y, pixrepeat ); + y -= tmp; + if( num == 3 ) + { + x -= m + w2 + 2; + y = height() / 2; + } + else + y -= height() / 2; + } + if( stop ) + { + if( multi && num != 3 ) + tmp = m + (h/2 - sh)/2; + else + tmp = -(sh / 2); + y += tmp; + p.drawPixmap( x, y, pixstop ); + y -= tmp; + } + } + + if( this != listView()->currentTrack() && !isSelected() ) + { + p.setPen( QPen( cg.mid(), 0, Qt::SolidLine ) ); + p.drawLine( width - 1, 0, width - 1, height() - 1 ); + } + + p.end(); + painter->drawPixmap( 0, 0, buf ); +} + +void PlaylistItem::drawRating( QPainter *p ) +{ + int gray = 0; + if( this == listView()->m_hoveredRating || ( isSelected() && listView()->m_selCount > 1 && + listView()->m_hoveredRating && listView()->m_hoveredRating->isSelected() ) ) + { + const int pos = listView()->viewportToContents( listView()->viewport()->mapFromGlobal( QCursor::pos() ) ).x(); + gray = ratingAtPoint( pos ); + } + + drawRating( p, ( rating() + 1 ) / 2, gray / 2, rating() % 2 ); +} + +void PlaylistItem::drawRating( QPainter *p, int stars, int greystars, bool half ) +{ + int i = 1, x = 1; + const int y = height() / 2 - StarManager::instance()->getGreyStar()->height() / 2; + if( half ) + i++; + //We use multiple pre-colored stars instead of coloring here to keep things speedy + for(; i <= stars; ++i ) + { + bitBlt( p->device(), x, y, StarManager::instance()->getStar( stars ) ); + x += StarManager::instance()->getGreyStar()->width() + listView()->itemMargin(); + } + if( half ) + { + bitBlt( p->device(), x, y, StarManager::instance()->getHalfStar( stars ) ); + x += StarManager::instance()->getGreyStar()->width() + listView()->itemMargin(); + } + for(; i <= greystars; ++i ) + { + bitBlt( p->device(), x, y, StarManager::instance()->getGreyStar() ); + x += StarManager::instance()->getGreyStar()->width() + listView()->itemMargin(); + } +} + +#define MOODBAR_SPACING 2 // The distance from the moodbar pixmap to each side + +void PlaylistItem::drawMood( QPainter *p, int width, int height ) +{ + // In theory, if AmarokConfig::showMoodbar() == false, then the + // moodbar column should be hidden and we shouldn't be here. + if( !AmarokConfig::showMoodbar() ) + return; + + // Due to the logic of the calling code, this should always return true + if( moodbar().dataExists() ) + { + QPixmap mood = moodbar().draw( width - MOODBAR_SPACING*2, + height - MOODBAR_SPACING*2 ); + p->drawPixmap( MOODBAR_SPACING, MOODBAR_SPACING, mood ); + } + + else + moodbar().load(); // This only has any effect the first time it's run + + // We don't have to listen for the jobEvent() signal since we + // inherit MetaBundle, and the moodbar lets the MetaBundle know + // about new data directly via moodbarJobEvent() below. +} + +// This is run when a job starts or finishes +void PlaylistItem::moodbarJobEvent( int newState ) +{ + (void) newState; // want to redraw nomatter what the new state is + if( AmarokConfig::showMoodbar() ) + repaint(); + // Don't automatically resort because it's annoying +} + + +void PlaylistItem::setup() +{ + KListViewItem::setup(); + + // We make the current track item a bit taller than ordinary items + if( this == listView()->currentTrack() ) + setHeight( int( float( listView()->fontMetrics().height() ) * 1.53 ) ); +} + + +void PlaylistItem::paintFocus( QPainter* p, const QColorGroup& cg, const QRect& r ) +{ + if( this != listView()->currentTrack() ) + KListViewItem::paintFocus( p, cg, r ); +} + +const QString &PlaylistItem::editingText() +{ + static const QString text = i18n( "Writing tag..." ); + return text; +} + + +/** + * Changes the transparency (alpha component) of an image. + * @param image Image to be manipulated. Must be true color (8 bit per channel). + * @param factor > 1.0 == more transparency, < 1.0 == less transparency. + */ +void PlaylistItem::imageTransparency( QImage& image, float factor ) //static +{ + uint *data = reinterpret_cast( image.bits() ); + const int pixels = image.width() * image.height(); + uint table[256]; + register int c; + + // Precalculate lookup table + for( int i = 0; i < 256; ++i ) { + c = int( double( i ) * factor ); + if( c > 255 ) c = 255; + table[i] = c; + } + + // Process all pixels. Highly optimized. + for( int i = 0; i < pixels; ++i ) { + c = data[i]; // Memory access is slow, so do it only once + data[i] = qRgba( qRed( c ), qGreen( c ), qBlue( c ), table[qAlpha( c )] ); + } +} + +AtomicString PlaylistItem::artist_album() const +{ + static const AtomicString various_artist = QString( "Various Artists (INTERNAL) [ASDF!]" ); + if( compilation() == CompilationYes ) + return various_artist; + else + return artist(); +} + +void PlaylistItem::refAlbum() +{ + if( Amarok::entireAlbums() ) + { + if( listView()->m_albums[artist_album()].find( album() ) == listView()->m_albums[artist_album()].end() ) + listView()->m_albums[artist_album()][album()] = new PlaylistAlbum; + m_album = listView()->m_albums[artist_album()][album()]; + m_album->refcount++; + } +} + +void PlaylistItem::derefAlbum() +{ + if( Amarok::entireAlbums() && m_album ) + { + m_album->refcount--; + if( !m_album->refcount ) + { + if (!listView()->m_prevAlbums.removeRef( m_album )) + warning() << "Unable to remove album reference from " + << "listView.m_prevAlbums" << endl; + listView()->m_albums[artist_album()].remove( album() ); + if( listView()->m_albums[artist_album()].isEmpty() ) + listView()->m_albums.remove( artist_album() ); + delete m_album; + } + } +} + +void PlaylistItem::incrementTotals() +{ + if( Amarok::entireAlbums() && m_album ) + { + const uint prevCount = m_album->tracks.count(); + // Multiple tracks with same track number are possible; + // e.g. with "various_artist" albums or albums with multiple disks. + // We are trying to keep m_album->tracks sorted first by discNumber() and then by track(). + // It seems that the following assumption is made for the following: + // ``Either all the tracks in an album have their track()'s set (to nonzero) or none of them have.'' + if( !track() || !m_album->tracks.count() || + ( m_album->tracks.getLast()->track() && + ( ( m_album->tracks.getLast()->discNumber() < discNumber() ) || + ( m_album->tracks.getLast()->discNumber() == discNumber() && m_album->tracks.getLast()->track() < track() ) ) ) ) + m_album->tracks.append( this ); + else + for( int i = 0, n = m_album->tracks.count(); i < n; ++i ) + if(!m_album->tracks.at(i)->track() || m_album->tracks.at(i)->discNumber() > discNumber() || + ( m_album->tracks.at(i)->discNumber() == discNumber() && m_album->tracks.at(i)->track() > track() ) ) + { + m_album->tracks.insert( i, this ); + break; + } + const Q_INT64 prevTotal = m_album->total; + Q_INT64 total = m_album->total * prevCount; + total += totalIncrementAmount(); + m_album->total = Q_INT64( double( total + 0.5 ) / m_album->tracks.count() ); + if( listView()->m_prevAlbums.findRef( m_album ) == -1 ) + listView()->m_total = listView()->m_total - prevTotal + m_album->total; + } + else if( listView()->m_prevTracks.findRef( this ) == -1 ) + listView()->m_total += totalIncrementAmount(); +} + +void PlaylistItem::decrementTotals() +{ + if( Amarok::entireAlbums() && m_album ) + { + const Q_INT64 prevTotal = m_album->total; + Q_INT64 total = m_album->total * m_album->tracks.count(); + if (!m_album->tracks.removeRef( this )) + warning() << "Unable to remove myself from m_album" << endl; + total -= totalIncrementAmount(); + m_album->total = Q_INT64( double( total + 0.5 ) / m_album->tracks.count() ); + if( listView()->m_prevAlbums.findRef( m_album ) == -1 ) + listView()->m_total = listView()->m_total - prevTotal + m_album->total; + } + else if( listView()->m_prevTracks.findRef( this ) == -1 ) + listView()->m_total -= totalIncrementAmount(); +} + +int PlaylistItem::totalIncrementAmount() const +{ + switch( AmarokConfig::favorTracks() ) + { + case AmarokConfig::EnumFavorTracks::Off: return 0; + case AmarokConfig::EnumFavorTracks::HigherScores: return score() > 0.f ? static_cast( score() ) : 50; + case AmarokConfig::EnumFavorTracks::HigherRatings: return rating() ? rating() : 5; // 2.5 + case AmarokConfig::EnumFavorTracks::LessRecentlyPlayed: + { + if( lastPlay() ) + return listView()->m_startupTime_t - lastPlay(); + else if( listView()->m_oldestTime_t ) + return ( listView()->m_startupTime_t - listView()->m_oldestTime_t ) * 2; + else + return listView()->m_startupTime_t - 1058652000; //july 20, 2003, when Amarok was first released. + } + default: return 0; + } +} + +void PlaylistItem::incrementCounts() +{ + listView()->m_totalCount++; + if( isSelected() ) + { + listView()->m_selCount++; + } + if( isVisible() ) + { + listView()->m_visCount++; + incrementTotals(); + } +} + +void PlaylistItem::decrementCounts() +{ + listView()->m_totalCount--; + if( isSelected() ) + { + listView()->m_selCount--; + } + if( isVisible() ) + { + listView()->m_visCount--; + decrementTotals(); + } +} + +void PlaylistItem::incrementLengths() +{ + listView()->m_totalLength += length(); + if( isSelected() ) + { + listView()->m_selLength += length(); + } + if( isVisible() ) + { + listView()->m_visLength += length(); + } +} + +void PlaylistItem::decrementLengths() +{ + listView()->m_totalLength -= length(); + if( isSelected() ) + { + listView()->m_selLength -= length(); + } + if( isVisible() ) + { + listView()->m_visLength -= length(); + } +} diff --git a/amarok/src/playlistitem.h b/amarok/src/playlistitem.h new file mode 100644 index 00000000..5f255821 --- /dev/null +++ b/amarok/src/playlistitem.h @@ -0,0 +1,180 @@ +//Maintainer: Max Howell +//Copyright: GPL v2 + +//NOTE please show restraint when adding data members to this class! +// some users have playlists with 20,000 items or more in, one 32 bit int adds up rapidly! +// -- on second thought, 80KB isn't all that much. be careful with QStrings, though. + + +#ifndef PLAYLISTITEM_H +#define PLAYLISTITEM_H + +#include "metabundle.h" //baseclass +#include "amarok_export.h" + +#include //baseclass +#include //stack allocated + +#include //stack allocated +#include //stack allocated +#include +#include +#include + +class QColorGroup; +class QDomNode; +class QImage; +class QListViewItem; +class QPainter; +class MetaBundle; +class Playlist; +class PlaylistAlbum; + +class LIBAMAROK_EXPORT PlaylistItem : public MetaBundle, public KListViewItem +{ + typedef MetaBundle super; + public: + /// Indicates that the current-track pixmap has changed. Animation must be redrawn. + static void setPixmapChanged() { s_pixmapChanged = true; } + + /// For the glow colouration stuff + static double glowIntensity; + static QColor glowText; + static QColor glowBase; + + public: + PlaylistItem( QListView*, QListViewItem* ); //used by PlaylistLoader + PlaylistItem( const MetaBundle&, QListViewItem*, bool enabled = true ); + ~PlaylistItem(); + + /// pass 'raw' data here, for example "92" for Length, and not "1:32" + virtual void setText( int column, const QString& ); + + /** + * @return The text of the column @p column, formatted for display purposes. + * (For example, if the Length is 92, "1:32".) + */ + virtual QString text( int column ) const; + + void filter( const QString &expression ); //makes visible depending on whether it matches + + bool isCurrent() const; + + bool isQueued() const; + int queuePosition() const; + + bool isEnabled() const { return m_enabled; } + bool isDynamicEnabled() const { return m_dynamicEnabled; } + bool isFilestatusEnabled() const { return m_filestatusEnabled; } + void setEnabled(); + void setDynamicEnabled( bool enabled ); + void setFilestatusEnabled( bool enabled ); + void setAllCriteriaEnabled( bool enabled ); + + void setSelected( bool selected ); + void setVisible( bool visible ); + + void setEditing( int column ); + bool isEditing( int column ) const; + bool anyEditing() const; + void setIsBeingRenamed( bool renaming ) { m_isBeingRenamed = renaming; } + bool isBeingRenamed() const { return m_isBeingRenamed; } + void setDeleteAfterEditing( bool dae ) { m_deleteAfterEdit = dae; } + bool deleteAfterEditing() const { return m_deleteAfterEdit; } + void setIsNew( bool is ) { m_isNew = is; } + + /// convenience functions + Playlist *listView() const { return reinterpret_cast( KListViewItem::listView() ); } + PlaylistItem *nextSibling() const { return static_cast( KListViewItem::nextSibling() ); } + + static int ratingAtPoint( int x ); + static int ratingColumnWidth(); + + /// like QWidget::update() + void update() const; + + //updates only the area of a specific column, avoids flickering of the current item marker + void updateColumn( int column ) const; + + virtual void setup(); // from QListViewItem + + virtual bool operator== ( const PlaylistItem & item ) const; + virtual bool operator< ( const PlaylistItem & item ) const; + + PlaylistItem *nextInAlbum() const; + PlaylistItem *prevInAlbum() const; + + protected: + virtual void aboutToChange( const QValueList &columns ); + virtual void reactToChanges( const QValueList &columns ); + + private: + friend class Playlist; + + + struct paintCacheItem { + int width; + int height; + QString text; + QFont font; + QColor color; + bool selected; + QMap map; + }; + + virtual void paintCell( QPainter*, const QColorGroup&, int, int, int ); + void drawRating( QPainter *p ); + void drawRating( QPainter *p, int stars, int greystars, bool half ); + void drawMood( QPainter *p, int width, int height ); + virtual void moodbarJobEvent( int newState ); + + // Used for sorting + virtual int compare( QListViewItem*, int, bool ) const; + + /** + * Paints a focus indicator on the rectangle (current item). We disable it + * over the currentTrack, cause it would look like crap and flicker. + */ + void paintFocus( QPainter*, const QColorGroup&, const QRect& ); + + static void imageTransparency( QImage& image, float factor ); + + AtomicString artist_album() const; // returns a placeholder 'artist' for compilations + + void refAlbum(); + void derefAlbum(); + + void decrementTotals(); + void incrementTotals(); + + void incrementCounts(); + void decrementCounts(); + void incrementLengths(); + void decrementLengths(); + + int totalIncrementAmount() const; + + PlaylistAlbum *m_album; + bool m_enabled; + bool m_dynamicEnabled; + bool m_filestatusEnabled; + bool m_deleteAfterEdit; + bool m_isBeingRenamed; + bool m_isNew; //New items will be assigned a different color + + static bool s_pixmapChanged; + static const QString &editingText(); +}; + +class PLItemList: public QPtrList +{ + public: + PLItemList() : QPtrList() { } + PLItemList( const QPtrList &list ) : QPtrList( list ) { } + PLItemList( PlaylistItem *item ) : QPtrList() { append( item ); } + + inline PLItemList &operator<<( PlaylistItem *item ) { append( item ); return *this; } +}; + + +#endif diff --git a/amarok/src/playlistloader.cpp b/amarok/src/playlistloader.cpp new file mode 100644 index 00000000..b79ceba0 --- /dev/null +++ b/amarok/src/playlistloader.cpp @@ -0,0 +1,1108 @@ +// Author: Max Howell (C) Copyright 2003-4 +// Author: Mark Kretschmann (C) Copyright 2004 +// .ram file support from Kaffeine 0.5, Copyright (C) 2004 by Jürgen Kofler (GPL 2 or later) +// .asx file support added by Michael Seiwert Copyright (C) 2006 +// .asx file support from Kaffeine, Copyright (C) 2004-2005 by Jürgen Kofler (GPL 2 or later) +// .smil file support from Kaffeine 0.7 +// .pls parser (C) Copyright 2005 by Michael Buesch +// .xspf file support added by Mattias Fliesberg Copyright (C) 2006 +// Copyright: See COPYING file that comes with this distribution +// + +///For pls and m3u specifications see: +///http://forums.winamp.com/showthread.php?s=dbec47f3a05d10a3a77959f17926d39c&threadid=65772 + +#define DEBUG_PREFIX "PlaylistLoader" + +#include "amarok.h" +#include "collectiondb.h" +#include "debug.h" +#include "enginecontroller.h" +#include "mountpointmanager.h" +#include "mydirlister.h" +#include "playlist.h" +#include "playlistbrowser.h" +#include "playlistitem.h" +#include "playlistloader.h" +#include "statusbar.h" +#include "contextbrowser.h" +#include "xspfplaylist.h" + +#include //::recurse() +#include //::recurse() +#include //::loadPlaylist() +#include +#include +#include +#include //::loadPlaylist() + +#include +#include +#include + + + +//TODO playlists within playlists, local or remote are legal entries in m3u and pls +//TODO directories from inside playlists + +struct XMLData +{ + MetaBundle bundle; + int queue; + bool stopafter; + bool dynamicdisabled; + XMLData(): queue(-1), stopafter(false), dynamicdisabled(false) { } +}; + +class TagsEvent : public QCustomEvent { +public: + TagsEvent( const QValueList &x ) : QCustomEvent( 1001 ), xml( QDeepCopy >( x ) ) { } + TagsEvent( const BundleList &bees ) : QCustomEvent( 1000 ), bundles( QDeepCopy( bees ) ) { + for( BundleList::Iterator it = bundles.begin(), end = bundles.end(); it != end; ++it ) + { + (*it).detach(); + /// @see MetaBundle for explanation of audioproperties < 0 + if( (*it).length() <= 0 || (*it).bitrate() <= 0 ) + (*it).readTags( TagLib::AudioProperties::Fast, 0 ); + } + } + + QValueList xml; + BundleList bundles; +}; + + +UrlLoader::UrlLoader( const KURL::List &urls, QListViewItem *after, int options ) + : ThreadManager::DependentJob( Playlist::instance(), "UrlLoader" ) + , m_markerListViewItem( new PlaylistItem( Playlist::instance(), after ) ) + , m_playFirstUrl( options & (Playlist::StartPlay | Playlist::DirectPlay) ) + , m_coloring( options & Playlist::Colorize ) + , m_options( options ) + , m_block( "UrlLoader" ) + , m_oldQueue( Playlist::instance()->m_nextTracks ) + , m_xmlSource( 0 ) +{ + + connect( this, SIGNAL( queueChanged( const PLItemList &, const PLItemList & ) ), + Playlist::instance(), SIGNAL( queueChanged( const PLItemList &, const PLItemList & ) ) ); + + Playlist::instance()->lock(); // prevent user removing items as this could be bad + + Amarok::OverrideCursor cursor; + + setDescription( i18n("Populating playlist") ); + + Amarok::StatusBar::instance()->newProgressOperation( this ) + .setDescription( m_description ) + .setStatus( i18n("Preparing") ) + .setAbortSlot( this, SLOT(abort()) ) + .setTotalSteps( 100 ); + + foreachType( KURL::List, urls ) { + const KURL url = Amarok::detachedKURL(Amarok::mostLocalURL( *it )); + const QString protocol = url.protocol(); + + if( protocol == "seek" ) + continue; + + else if( ContextBrowser::hasContextProtocol( url ) ) + { + DEBUG_BLOCK + debug() << "context expandurl" << endl; + + m_URLs += ContextBrowser::expandURL(Amarok::detachedKURL(url)); + } + + else if( !MetaBundle::isKioUrl( url ) ) + { + m_URLs += url; + } + + else if( protocol == "file" ) { + if( QFileInfo( url.path() ).isDir() ) + m_URLs += recurse( url ); + else + m_URLs += url; + } + + // Note: remove for kde 4 - we don't need to be hacking around KFileDialog, + // it has been fixed for kde 3.5.3 + else if( protocol == "media" || url.url().startsWith( "system:/media/" ) ) + { + QString path = url.path( -1 ); + if( url.url().startsWith( "system:/media/" ) ) + path = path.mid( 6 ); + // url looks like media:/device/path + DCOPRef mediamanager( "kded", "mediamanager" ); + QString device = path.mid( 1 ); // remove first slash + const int slash = device.find( '/' ); + const QString filePath = device.mid( slash ); // extract relative path + device = device.left( slash ); // extract device + DCOPReply reply = mediamanager.call( "properties(QString)", device ); + + if( reply.isValid() ) { + const QStringList properties = reply; + // properties[6] is the mount point + KURL localUrl = KURL( properties[6] + filePath ); + + // add urls + if( QFileInfo( localUrl.path() ).isDir() ) + m_URLs += recurse( localUrl ); + else + m_URLs += localUrl; + } + } + + else if( PlaylistFile::isPlaylistFile( url ) ) { + debug() << "remote playlist" << endl; + new RemotePlaylistFetcher( url, after, m_options ); + m_playFirstUrl = false; + } + + else { + // this is the best way I found for recursing if required + // and not recusring if not required + const KURL::List urls = recurse( url ); + + // recurse only works on directories, else it swallows the URL + if( urls.isEmpty() ) + m_URLs += url; + else + m_URLs += urls; + } + } +} + +UrlLoader::~UrlLoader() +{ + if( Playlist::instance() ) + { + Playlist::instance()->unlock(); + if( m_markerListViewItem ) + delete m_markerListViewItem; + } + + delete m_xmlSource; +} + +bool +UrlLoader::doJob() +{ + setProgressTotalSteps( m_URLs.count() ); + + KURL::List urls; + for( for_iterators( KURL::List, m_URLs ); it != end && !isAborted(); ++it ) + { + const KURL &url = *it; + + incrementProgress(); + + switch( PlaylistFile::format( url.fileName() ) ) + { + case PlaylistFile::XML: + loadXml( url ); + break; + + default: { + PlaylistFile playlist( url.path() ); + + if( !playlist.isError() ) + QApplication::postEvent( this, new TagsEvent( playlist.bundles()) ); + else + m_badURLs += url; + + } break; + + case PlaylistFile::NotPlaylist: + (EngineController::canDecode( url ) ? urls : m_badURLs) += url; + } + + if( urls.count() == OPTIMUM_BUNDLE_COUNT || it == last ) { + QApplication::postEvent( this, new TagsEvent( CollectionDB::instance()->bundlesByUrls( urls ) ) ); + urls.clear(); + } + } + + return true; +} + +void +UrlLoader::customEvent( QCustomEvent *e) +{ + //DEBUG_BLOCK + #define e static_cast(e) + switch( e->type() ) { + case 1000: + foreachType( BundleList, e->bundles ) + { + int alreadyOnPlaylist = 0; + + PlaylistItem *item = 0; + if( m_options & (Playlist::Unique | Playlist::Queue) ) + { + item = Playlist::instance()->m_urlIndex.getFirst( (*it).url() ); + } + + if( item ) + alreadyOnPlaylist++; + else + item = new PlaylistItem( *it, m_markerListViewItem, (*it).exists() ); + + if( m_options & Playlist::Queue ) + Playlist::instance()->queue( item ); + + if( m_playFirstUrl && (*it).exists() ) + { + Playlist::instance()->activate( item ); + m_playFirstUrl = false; + } + } + break; + + case 1001: + { + foreachType( QValueList, e->xml ) + { + if( (*it).bundle.isEmpty() ) //safety + continue; + + PlaylistItem* const item = new PlaylistItem( (*it).bundle, m_markerListViewItem ); + item->setIsNew( m_coloring ); + + //TODO scrollbar position + //TODO previous tracks queue + //TODO current track position, even if user doesn't have resume playback turned on + + if( (*it).queue >= 0 ) { + if( (*it).queue == 0 ) + Playlist::instance()->setCurrentTrack( item ); + + else if( (*it).queue > 0 ) { + PLItemList &m_nextTracks = Playlist::instance()->m_nextTracks; + int count = m_nextTracks.count(); + + for( int c = count; c < (*it).queue; c++ ) + // Append foo values and replace with correct values later. + m_nextTracks.append( item ); + + m_nextTracks.replace( (*it).queue - 1, item ); + } + } + if( (*it).stopafter ) + Playlist::instance()->m_stopAfterTrack = item; + + item->setFilestatusEnabled( (*it).bundle.exists() ); + item->setDynamicEnabled( !( (*it).dynamicdisabled ) ); + } + break; + } + + default: + DependentJob::customEvent( e ); + return; + } + #undef e +} + +void +UrlLoader::completeJob() +{ + DEBUG_BLOCK + const PLItemList &newQueue = Playlist::instance()->m_nextTracks; + QPtrListIterator it( newQueue ); + PLItemList added; + for( it.toFirst(); *it; ++it ) + if( !m_oldQueue.containsRef( *it ) ) + added << (*it); + + if( !added.isEmpty() ) + emit queueChanged( added, PLItemList() ); + + if ( !m_badURLs.isEmpty() ) { + QString text = i18n("These media could not be loaded into the playlist: " ); + debug() << "The following urls were not suitable for the playlist:" << endl; + for ( uint it = 0; it < m_badURLs.count(); it++ ) + { + if( it < 5 ) + text += QString("
    %1").arg( m_badURLs[it].prettyURL() ); + else if( it == 5 ) + text += QString("
    Plus %1 more").arg( m_badURLs.count() - it ); + debug() << "\t" << m_badURLs[it] << endl; + } + + Amarok::StatusBar::instance()->shortLongMessage( + i18n("Some media could not be loaded (not playable)."), text ); + } + + if( !m_dynamicMode.isEmpty() ) + Playlist::instance()->setDynamicMode( PlaylistBrowser::instance()->findDynamicModeByTitle( m_dynamicMode ) ); + + //synchronous, ie not using eventLoop + QApplication::sendEvent( dependent(), this ); +} + +KURL::List +UrlLoader::recurse( const KURL &url ) +{ + typedef QMap FileMap; + + KDirLister lister( false ); + lister.setAutoUpdate( false ); + lister.setAutoErrorHandlingEnabled( false, 0 ); + if ( !lister.openURL( url ) ) + return KURL::List(); + + // Fucking KDirLister sometimes hangs on remote media, so we add a timeout + const int timeout = 3000; // ms + QTime watchdog; + watchdog.start(); + + while( !lister.isFinished() && !isAborted() && watchdog.elapsed() < timeout ) + kapp->eventLoop()->processEvents( QEventLoop::ExcludeUserInput ); + + KFileItemList items = lister.items(); //returns QPtrList, so we MUST only do it once! + KURL::List urls; + FileMap files; + for( KFileItem *item = items.first(); item; item = items.next() ) { + if( item->isFile() ) { files[item->name()] = item->url(); continue; } + if( item->isDir() ) urls += recurse( item->url() ); + } + + foreachType( FileMap, files ) + // users often have playlist files that reflect directories + // higher up, or stuff in this directory. Don't add them as + // it produces double entries + if( !PlaylistFile::isPlaylistFile( (*it).fileName() ) ) + urls += *it; + + return urls; +} + +namespace Amarok +{ + +// almost the same as UrlLoader::recurse, but global +KURL::List +recursiveUrlExpand( const KURL &url, int maxURLs ) +{ + typedef QMap FileMap; + + if( url.protocol() != "file" || !QFileInfo( url.path() ).isDir() ) + return KURL::List( url ); + + MyDirLister lister( false ); + lister.setAutoUpdate( false ); + lister.setAutoErrorHandlingEnabled( false, 0 ); + if ( !lister.openURL( url ) ) + return KURL::List(); + + // Fucking KDirLister sometimes hangs on remote media, so we add a timeout + const int timeout = 3000; // ms + QTime watchdog; + watchdog.start(); + + while( !lister.isFinished() && watchdog.elapsed() < timeout ) + kapp->eventLoop()->processEvents( QEventLoop::ExcludeUserInput ); + + KFileItemList items = lister.items(); //returns QPtrList, so we MUST only do it once! + KURL::List urls; + FileMap files; + for( KFileItem *item = items.first(); item; item = items.next() ) { + if( maxURLs >= 0 && (int)(urls.count() + files.count()) >= maxURLs ) + break; + if( item->isFile() + && !PlaylistFile::isPlaylistFile( item->url().fileName() ) + ) + { + files[item->name()] = item->url(); + continue; + } + if( item->isDir() ) + urls += recursiveUrlExpand( item->url(), maxURLs - urls.count() - files.count() ); + } + + foreachType( FileMap, files ) + // users often have playlist files that reflect directories + // higher up, or stuff in this directory. Don't add them as + // it produces double entries + urls += *it; + + return urls; +} + +KURL::List +recursiveUrlExpand( const KURL::List &list, int maxURLs ) +{ + KURL::List urls; + foreachType( KURL::List, list ) + { + if( maxURLs >= 0 && (int)urls.count() >= maxURLs ) + break; + urls += recursiveUrlExpand( *it, maxURLs - urls.count() ); + } + + return urls; +} + +} // Amarok + +void +UrlLoader::loadXml( const KURL &url ) +{ + QFile file( url.path() ); + if( !file.open( IO_ReadOnly ) ) { + m_badURLs += url; + return; + } + m_currentURL = url; + + delete m_xmlSource; + m_xmlSource = new QXmlInputSource( file ); + MyXmlLoader loader; + connect( &loader, SIGNAL( newBundle( const MetaBundle&, const XmlAttributeList& ) ), + this, SLOT( slotNewBundle( const MetaBundle&, const XmlAttributeList& ) ) ); + connect( &loader, SIGNAL( playlistInfo( const QString&, const QString&, const QString& ) ), + this, SLOT( slotPlaylistInfo( const QString&, const QString&, const QString& ) ) ); + loader.load( m_xmlSource ); + if( !m_xml.isEmpty() ) + { + QApplication::postEvent( this, new TagsEvent( m_xml ) ); + m_xml.clear(); + } + if( !loader.lastError().isEmpty() ) + { + Amarok::StatusBar::instance()->longMessageThreadSafe( i18n( + //TODO add a link to the path to the playlist + "The XML in the playlist was invalid. Please report this as a bug to the Amarok " + "developers. Thank you." ), KDE::StatusBar::Error ); + ::error() << "[PLAYLISTLOADER]: Error in " << m_currentURL.prettyURL() << ": " << loader.lastError() << endl; + } +} + +void UrlLoader::slotNewBundle( const MetaBundle &bundle, const XmlAttributeList &atts ) +{ + XMLData data; + data.bundle = QDeepCopy( bundle ); + for( int i = 0, n = atts.count(); i < n; ++i ) + { + if( atts[i].first == "queue_index" ) + { + bool ok = true; + data.queue = atts[i].second.toInt( &ok ); + if( !ok ) + data.queue = -1; + } + else if( atts[i].first == "stop_after" ) + data.stopafter = true; + else if( atts[i].first == "dynamicdisabled" ) + data.dynamicdisabled = true; + } + data.bundle.checkExists(); + m_xml.append( data ); + if( m_xml.count() == OPTIMUM_BUNDLE_COUNT ) + { + QApplication::postEvent( this, new TagsEvent( m_xml ) ); + m_xml.clear(); + } +} + +void UrlLoader::slotPlaylistInfo( const QString &, const QString &version, const QString &dynamicMode ) +{ + if( version != Amarok::xmlVersion() ) + { + Amarok::StatusBar::instance()->longMessageThreadSafe( i18n( + "Your last playlist was saved with a different version of Amarok than this one, " + "and this version can no longer read it.\n" + "You will have to create a new one.\n" + "Sorry :(" ) ); + static_cast( const_cast( sender() ) )->abort(); //HACK? + return; + } + else + m_dynamicMode = dynamicMode; +} + +/// @class PlaylistFile + +PlaylistFile::PlaylistFile( const QString &path ) + : m_path( path ) +{ + QFile file( path ); + if( !file.open( IO_ReadOnly ) ) { + m_error = i18n( "Amarok could not open the file." ); + return; + } + + QTextStream stream( &file ); + + switch( format( m_path ) ) { + case M3U: loadM3u( stream ); break; + case PLS: loadPls( stream ); break; + case XML: + m_error = i18n( "This component of Amarok cannot translate XML playlists." ); + return; + case RAM: loadRealAudioRam( stream ); break; + case ASX: loadASX( stream ); break; + case SMIL: loadSMIL( stream ); break; + case XSPF: loadXSPF( stream ); break; + default: + m_error = i18n( "Amarok does not support this playlist format." ); + return; + } + + if( m_error.isEmpty() && m_bundles.isEmpty() ) + m_error = i18n( "The playlist did not contain any references to files." ); + debug() << m_error << endl; +} + +bool +PlaylistFile::loadM3u( QTextStream &stream ) +{ + const QString directory = m_path.left( m_path.findRev( '/' ) + 1 ); + MetaBundle b; + + for( QString line; !stream.atEnd(); ) + { + line = stream.readLine(); + + if( line.startsWith( "#EXTINF" ) ) { + const QString extinf = line.section( ':', 1 ); + const int length = extinf.section( ',', 0, 0 ).toInt(); + b.setTitle( extinf.section( ',', 1 ) ); + b.setLength( length <= 0 ? /*MetaBundle::Undetermined HACK*/ -2 : length ); + } + + else if( !line.startsWith( "#" ) && !line.isEmpty() ) + { + // KURL::isRelativeURL() expects absolute URLs to start with a protocol, so prepend it if missing + QString url = line; + if( url.startsWith( "/" ) ) + url.prepend( "file://" ); + + if( KURL::isRelativeURL( url ) ) { + KURL kurl( KURL::fromPathOrURL( directory + line ) ); + kurl.cleanPath(); + b.setPath( kurl.path() ); + } + else { + b.setUrl( KURL::fromPathOrURL( line ) ); + } + + // Ensure that we always have a title: use the URL as fallback + if( b.title().isEmpty() ) + b.setTitle( url ); + + m_bundles += b; + b = MetaBundle(); + } + } + + return true; +} + +bool +PlaylistFile::loadPls( QTextStream &stream ) +{ + // Counted number of "File#=" lines. + unsigned int entryCnt = 0; + // Value of the "NumberOfEntries=#" line. + unsigned int numberOfEntries = 0; + // Does the file have a "[playlist]" section? (as it's required by the standard) + bool havePlaylistSection = false; + QString tmp; + QStringList lines; + + const QRegExp regExp_NumberOfEntries("^NumberOfEntries\\s*=\\s*\\d+$"); + const QRegExp regExp_File("^File\\d+\\s*="); + const QRegExp regExp_Title("^Title\\d+\\s*="); + const QRegExp regExp_Length("^Length\\d+\\s*=\\s*\\d+$"); + const QRegExp regExp_Version("^Version\\s*=\\s*\\d+$"); + const QString section_playlist("[playlist]"); + + /* Preprocess the input data. + * Read the lines into a buffer; Cleanup the line strings; + * Count the entries manually and read "NumberOfEntries". + */ + while (!stream.atEnd()) { + tmp = stream.readLine(); + tmp = tmp.stripWhiteSpace(); + if (tmp.isEmpty()) + continue; + lines.append(tmp); + + if (tmp.contains(regExp_File)) { + entryCnt++; + continue; + } + if (tmp == section_playlist) { + havePlaylistSection = true; + continue; + } + if (tmp.contains(regExp_NumberOfEntries)) { + numberOfEntries = tmp.section('=', -1).stripWhiteSpace().toUInt(); + continue; + } + } + if (numberOfEntries != entryCnt) { + warning() << ".pls playlist: Invalid \"NumberOfEntries\" value. " + << "NumberOfEntries=" << numberOfEntries << " counted=" + << entryCnt << endl; + /* Corrupt file. The "NumberOfEntries" value is + * not correct. Fix it by setting it to the manually + * counted number and go on parsing. + */ + numberOfEntries = entryCnt; + } + if (!numberOfEntries) + return true; + + unsigned int index; + bool ok = false; + bool inPlaylistSection = false; + + Q_ASSERT(m_bundles.isEmpty()); + m_bundles.insert(m_bundles.begin(), numberOfEntries, MetaBundle()); + /* Now iterate through all beautified lines in the buffer + * and parse the playlist data. + */ + QStringList::const_iterator i = lines.begin(), end = lines.end(); + for ( ; i != end; ++i) { + if (!inPlaylistSection && havePlaylistSection) { + /* The playlist begins with the "[playlist]" tag. + * Skip everything before this. + */ + if ((*i) == section_playlist) + inPlaylistSection = true; + continue; + } + if ((*i).contains(regExp_File)) { + // Have a "File#=XYZ" line. + index = loadPls_extractIndex(*i); + if (index > numberOfEntries || index == 0) + continue; + tmp = (*i).section('=', 1).stripWhiteSpace(); + m_bundles[index - 1].setUrl(KURL::fromPathOrURL(tmp)); + // Ensure that if the entry has no title, we show at least the URL as title + m_bundles[index - 1].setTitle(tmp); + continue; + } + if ((*i).contains(regExp_Title)) { + // Have a "Title#=XYZ" line. + index = loadPls_extractIndex(*i); + if (index > numberOfEntries || index == 0) + continue; + tmp = (*i).section('=', 1).stripWhiteSpace(); + m_bundles[index - 1].setTitle(tmp); + continue; + } + if ((*i).contains(regExp_Length)) { + // Have a "Length#=XYZ" line. + index = loadPls_extractIndex(*i); + if (index > numberOfEntries || index == 0) + continue; + tmp = (*i).section('=', 1).stripWhiteSpace(); + m_bundles[index - 1].setLength(tmp.toInt(&ok)); + Q_ASSERT(ok); + continue; + } + if ((*i).contains(regExp_NumberOfEntries)) { + // Have the "NumberOfEntries=#" line. + continue; + } + if ((*i).contains(regExp_Version)) { + // Have the "Version=#" line. + tmp = (*i).section('=', 1).stripWhiteSpace(); + // We only support Version=2 + if (tmp.toUInt(&ok) != 2) + warning() << ".pls playlist: Unsupported version." << endl; + Q_ASSERT(ok); + continue; + } + warning() << ".pls playlist: Unrecognized line: \"" << *i << "\"" << endl; + } + return true; +} + +bool +PlaylistFile::loadXSPF( QTextStream &stream ) +{ + XSPFPlaylist* doc = new XSPFPlaylist( stream ); + + XSPFtrackList trackList = doc->trackList(); + + foreachType( XSPFtrackList, trackList ) + { + KURL location = (*it).location; + QString artist = (*it).creator; + QString title = (*it).title; + QString album = (*it).album; + + if( location.isEmpty() || ( location.isLocalFile() && !QFile::exists( location.url() ) ) ) + { + QueryBuilder qb; + qb.addMatch( QueryBuilder::tabArtist, QueryBuilder::valName, artist ); + qb.addMatch( QueryBuilder::tabSong, QueryBuilder::valTitle, title ); + if( !album.isEmpty() ) + qb.addMatch( QueryBuilder::valName, album ); + qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valURL ); + + QStringList values = qb.run(); + + if( values.isEmpty() ) continue; + + MetaBundle b( values[0] ); + + m_bundles += b; + } + else + { + debug() << location << ' ' << artist << ' ' << title << ' ' << album << endl; + MetaBundle b; + b.setUrl( location ); + b.setArtist( artist ); + b.setTitle( title ); + b.setAlbum( album ); + b.setComment( (*it).annotation ); + b.setLength( (*it).duration / 1000 ); + m_bundles += b; + } + } + + m_title = doc->title(); + + return true; +} + +unsigned int +PlaylistFile::loadPls_extractIndex( const QString &str ) const +{ + /* Extract the index number out of a .pls line. + * Example: + * loadPls_extractIndex("File2=foobar") == 2 + */ + bool ok = false; + unsigned int ret; + QString tmp(str.section('=', 0, 0)); + tmp.remove(QRegExp("^\\D*")); + ret = tmp.stripWhiteSpace().toUInt(&ok); + Q_ASSERT(ok); + return ret; +} + +bool +PlaylistFile::loadRealAudioRam( QTextStream &stream ) +{ + MetaBundle b; + QString url; + //while loop adapted from Kaffeine 0.5 + while (!stream.atEnd()) + { + url = stream.readLine(); + if (url[0] == '#') continue; /* ignore comments */ + if (url == "--stop--") break; /* stop line */ + if ((url.left(7) == "rtsp://") || (url.left(6) == "pnm://") || (url.left(7) == "http://")) + { + b.setUrl(KURL(url)); + m_bundles += b; + b = MetaBundle(); + } + } + + return true; +} + +bool +PlaylistFile::loadASX( QTextStream &stream ) +{ + //adapted from Kaffeine 0.7 + MetaBundle b; + QDomDocument doc; + QString errorMsg; + int errorLine, errorColumn; + stream.setEncoding( QTextStream::UnicodeUTF8 ); + + QString content = stream.read(); + + //ASX looks a lot like xml, but doesn't require tags to be case sensitive, + //meaning we have to accept things like: ... + //We use a dirty way to achieve this: we make all tags lower case + QRegExp ex("(<[/]?[^>]*[A-Z]+[^>]*>)"); + ex.setCaseSensitive(true); + while ( (ex.search(content)) != -1 ) + content.replace(ex.cap( 1 ), ex.cap( 1 ).lower()); + + + if (!doc.setContent(content, &errorMsg, &errorLine, &errorColumn)) + { + debug() << "Error loading xml file: " "(" << errorMsg << ")" + << " at line " << errorLine << ", column " << errorColumn << endl; + return false; + } + + QDomElement root = doc.documentElement(); + + QString url; + QString title; + QString author; + QTime length; + QString duration; + + if (root.nodeName().lower() != "asx") return false; + + QDomNode node = root.firstChild(); + QDomNode subNode; + QDomElement element; + + while (!node.isNull()) + { + url = QString::null; + title = QString::null; + author = QString::null; + length = QTime(); + if (node.nodeName().lower() == "entry") + { + subNode = node.firstChild(); + while (!subNode.isNull()) + { + if ((subNode.nodeName().lower() == "ref") && (subNode.isElement()) && (url.isNull())) + { + element = subNode.toElement(); + if (element.hasAttribute("href")) + url = element.attribute("href"); + if (element.hasAttribute("HREF")) + url = element.attribute("HREF"); + if (element.hasAttribute("Href")) + url = element.attribute("Href"); + if (element.hasAttribute("HRef")) + url = element.attribute("HRef"); + } + if ((subNode.nodeName().lower() == "duration") && (subNode.isElement())) + { + duration = QString::null; + element = subNode.toElement(); + if (element.hasAttribute("value")) + duration = element.attribute("value"); + if (element.hasAttribute("Value")) + duration = element.attribute("Value"); + if (element.hasAttribute("VALUE")) + duration = element.attribute("VALUE"); + + if (!duration.isNull()) + length = PlaylistFile::stringToTime(duration); + } + + if ((subNode.nodeName().lower() == "title") && (subNode.isElement())) + { + title = subNode.toElement().text(); + } + if ((subNode.nodeName().lower() == "author") && (subNode.isElement())) + { + author = subNode.toElement().text(); + } + subNode = subNode.nextSibling(); + } + if (!url.isNull()) + { + if (title.isNull()) + title = url; + b.setUrl(KURL(url)); + m_bundles += b; + b = MetaBundle(); + } + } + node = node.nextSibling(); + } + return true; +} + +bool +PlaylistFile::loadSMIL( QTextStream &stream ) +{ + // adapted from Kaffeine 0.7 + QDomDocument doc; + if( !doc.setContent( stream.read() ) ) + { + debug() << "Could now read smil playlist" << endl; + return false; + } + QDomElement root = doc.documentElement(); + stream.setEncoding ( QTextStream::UnicodeUTF8 ); + + if( root.nodeName().lower() != "smil" ) + return false; + + KURL kurl; + QString url; + QDomNodeList nodeList; + QDomNode node; + QDomElement element; + + //audio sources... + nodeList = doc.elementsByTagName( "audio" ); + for( uint i = 0; i < nodeList.count(); i++ ) + { + MetaBundle b; + node = nodeList.item(i); + url = QString::null; + if( (node.nodeName().lower() == "audio") && (node.isElement()) ) + { + element = node.toElement(); + if( element.hasAttribute("src") ) + url = element.attribute("src"); + + else if( element.hasAttribute("Src") ) + url = element.attribute("Src"); + + else if( element.hasAttribute("SRC") ) + url = element.attribute("SRC"); + } + if( !url.isNull() ) + { + b.setUrl( url ); + m_bundles += b; + } + } + + return true; +} + + +/// @class RemotePlaylistFetcher + +#include +#include +#include + +RemotePlaylistFetcher::RemotePlaylistFetcher( const KURL &source, QListViewItem *after, int options ) + : QObject( Playlist::instance()->qscrollview() ) + , m_source( source ) + , m_after( after ) + , m_playFirstUrl( options & (Playlist::StartPlay | Playlist::DirectPlay) ) + , m_options( options ) +{ + //We keep the extension so the UrlLoader knows what file type it is + const QString path = source.path(); + m_temp = new KTempFile( QString::null /*use default prefix*/, path.mid( path.findRev( '.' ) ) ); + m_temp->setAutoDelete( true ); + + m_destination.setPath( m_temp->name() ); + + KIO::Job *job = KIO::file_copy( m_source, m_destination, + -1, /* permissions, this means "do what you think" */ + true, /* overwrite */ + false, /* resume download */ + false ); /* don't show stupid UIServer dialog */ + + Amarok::StatusBar::instance()->newProgressOperation( job ) + .setDescription( i18n("Retrieving Playlist") ); + + connect( job, SIGNAL(result( KIO::Job* )), SLOT(result( KIO::Job* )) ); + + Playlist::instance()->lock(); +} + +RemotePlaylistFetcher::~RemotePlaylistFetcher() +{ + Playlist::instance()->unlock(); + + delete m_temp; +} + +void +RemotePlaylistFetcher::result( KIO::Job *job ) +{ + if( job->error() ) { + error() << "Couldn't download remote playlist\n"; + deleteLater(); + } + + else { + debug() << "Playlist was downloaded successfully\n"; + + UrlLoader *loader = new UrlLoader( m_destination, m_after, m_options ); + ThreadManager::instance()->queueJob( loader ); + + // we mustn't get deleted until the loader is finished + // or the playlist we downloaded will be deleted before + // it can be parsed! + loader->insertChild( this ); + } +} + + + +/// @class SqlLoader + +SqlLoader::SqlLoader( const QString &sql, QListViewItem *after, int options ) + : UrlLoader( KURL::List(), after, options ) + , m_sql( QDeepCopy( sql ) ) +{ + // Ovy: just until we make sure every SQL query from dynamic playlists is handled + // correctly + debug() << "Sql loader: query is: " << sql << "\n"; +} + +bool +SqlLoader::doJob() +{ + DEBUG_BLOCK + const QStringList values = CollectionDB::instance()->query( m_sql ); + + setProgressTotalSteps( values.count() ); + + BundleList bundles; + uint x = 0; + for( for_iterators( QStringList, values ); it != end && !isAborted(); ++it ) { + setProgress( x += QueryBuilder::dragFieldCount ); + + bundles += CollectionDB::instance()->bundleFromQuery( &it ); + + if( bundles.count() == OPTIMUM_BUNDLE_COUNT || it == last ) { + QApplication::postEvent( this, new TagsEvent( bundles ) ); + bundles.clear(); + } + } + + setProgress100Percent(); + + return true; +} + +QTime PlaylistFile::stringToTime(const QString& timeString) +{ + int sec = 0; + bool ok = false; + QStringList tokens = QStringList::split(':',timeString); + + sec += tokens[0].toInt(&ok)*3600; //hours + sec += tokens[1].toInt(&ok)*60; //minutes + sec += tokens[2].toInt(&ok); //secs + + if (ok) + return QTime().addSecs(sec); + else + return QTime(); +} + +bool MyXmlLoader::startElement( const QString &a, const QString &name, const QString &b, const QXmlAttributes &atts ) +{ + if( name == "playlist" ) + { + QString product, version, dynamic; + for( int i = 0, n = atts.count(); i < n; ++i ) + { + if( atts.localName( i ) == "product" ) + product = atts.value( i ); + else if( atts.localName( i ) == "version" ) + version = atts.value( i ); + else if( atts.localName( i ) == "dynamicMode" ) + dynamic = atts.value( i ); + } + emit playlistInfo( product, version, dynamic ); + return !m_aborted; + } + else + return XmlLoader::startElement( a, name, b, atts ); +} + +#include "playlistloader.moc" diff --git a/amarok/src/playlistloader.h b/amarok/src/playlistloader.h new file mode 100644 index 00000000..ec47789b --- /dev/null +++ b/amarok/src/playlistloader.h @@ -0,0 +1,207 @@ +// Author: Max Howell (C) Copyright 2003-4 +// Author: Mark Kretschmann (C) Copyright 2004 +// Copyright: See COPYING file that comes with this distribution +// + +#ifndef UrlLoader_H +#define UrlLoader_H + +#include "amarok.h" +#include "debug.h" //stack allocated +#include +#include //baseclass +#include //KURL::List +#include "metabundle.h" //stack allocated +#include "threadmanager.h" //baseclass +#include "xmlloader.h" //baseclass + +class QListViewItem; +class QTextStream; +class PlaylistItem; +class PLItemList; +class XMLData; + +namespace KIO { class Job; } + + +/** + * @class PlaylistFile + * @author Max Howell + * @short Allocate on the stack, the contents are immediately available from bundles() + * + * Note, it won't do anything with XML playlists + * + * TODO be able to load directories too, it's in the spec + * TODO and playlists within playlists, remote and local + */ +class PlaylistFile +{ +public: + PlaylistFile( const QString &path ); + + enum Format { M3U, PLS, XML, RAM, SMIL, ASX, XSPF, Unknown, NotPlaylist = Unknown }; + + /// the bundles from this playlist, they only contain + /// the information that can be extracted from the playlists + BundleList &bundles() { return m_bundles; } + + /// the name of the playlist. often stored in the document (eg xspf) or derived from the filename + QString &title() { return m_title; } + + ///@return true if couldn't load the playlist's contents + bool isError() const { return !m_error.isEmpty(); } + + /// if start returns false this has a translated error description + QString error() const { return m_error; } + + + static inline bool isPlaylistFile( const KURL &url ) { return isPlaylistFile( url.fileName() ); } + static inline bool isPlaylistFile( const QString &fileName ) { return format( fileName ) != Unknown; } + static inline Format format( const QString &fileName ); + static QTime stringToTime(const QString&); + +protected: + /// make these virtual if you need to + bool loadM3u( QTextStream& ); + bool loadPls( QTextStream& ); + unsigned int loadPls_extractIndex( const QString &str ) const; + bool loadRealAudioRam( QTextStream& ); + bool loadASX( QTextStream& ); + bool loadSMIL( QTextStream& ); + bool loadXSPF( QTextStream& ); + QString m_path; + QString m_error; + BundleList m_bundles; + QString m_title; +}; + +inline PlaylistFile::Format +PlaylistFile::format( const QString &fileName ) +{ + const QString ext = Amarok::extension( fileName ); + + if( ext == "m3u" ) return M3U; + if( ext == "pls" ) return PLS; + if( ext == "ram" ) return RAM; + if( ext == "smil") return SMIL; + if( ext == "asx" || ext == "wax" ) return ASX; + if( ext == "xml" ) return XML; + if( ext == "xspf" ) return XSPF; + + return Unknown; +} + +/** + * @author Max Howell + * @author Mark Kretschmann + * @short Populates the Playlist-view with URLs + * + * + Load playlists, remote and local + * + List directories, remote and local + * + Read tags, from file:/// and from DB + */ +class UrlLoader : public ThreadManager::DependentJob +{ +Q_OBJECT + +public: + UrlLoader( const KURL::List&, QListViewItem*, int options = 0 ); + ~UrlLoader(); + + static const uint OPTIMUM_BUNDLE_COUNT = 200; + +signals: + void queueChanged( const PLItemList &, const PLItemList & ); + +protected: + /// reimplemented from ThreadManager::Job + virtual bool doJob(); + virtual void completeJob(); + virtual void customEvent( QCustomEvent* ); + + void loadXml( const KURL& ); + +private slots: + void slotNewBundle( const MetaBundle &bundle, const XmlAttributeList &attributes ); + void slotPlaylistInfo( const QString &product, const QString &version, const QString &dynamicMode ); + +private: + KURL::List recurse( const KURL& ); + +private: + KURL::List m_badURLs; + KURL::List m_URLs; + PlaylistItem *m_markerListViewItem; + bool m_playFirstUrl; + bool m_coloring; + int m_options; + Debug::Block m_block; + QPtrList m_oldQueue; + QXmlInputSource *m_xmlSource; + QValueList m_xml; + KURL m_currentURL; + QString m_dynamicMode; + +protected: + UrlLoader( const UrlLoader& ); //undefined + UrlLoader &operator=( const UrlLoader& ); //undefined +}; + + + +/** + * @author Max Howell + * @short Populates the Playlist-view using the result of a single SQL query + * + * The format of the query must be in a set order, see doJob() + */ +class SqlLoader : public UrlLoader +{ + const QString m_sql; + +public: + SqlLoader( const QString &sql, QListViewItem *after, int options = 0 ); + + virtual bool doJob(); +}; + + + +/** + * @author Max Howell + * @short Fetches a playlist-file from any location, and then loads it into the Playlist-view + */ +class RemotePlaylistFetcher : public QObject +{ + Q_OBJECT + + const KURL m_source; + KURL m_destination; + QListViewItem *m_after; + bool m_playFirstUrl; + int m_options; + class KTempFile *m_temp; + +public: + RemotePlaylistFetcher( const KURL &source, QListViewItem *after, int options = 0 ); + ~RemotePlaylistFetcher(); + +private slots: + void result( KIO::Job* ); + void abort() { delete this; } +}; + +// PRIVATE -- should be in the .cpp, but fucking moc. + +class MyXmlLoader: public MetaBundle::XmlLoader +{ + Q_OBJECT +public: + MyXmlLoader() { } + virtual bool startElement( const QString&, const QString&, const QString &, const QXmlAttributes& ); +signals: + void playlistInfo( const QString &product, const QString &version, const QString &dynamicMode ); +}; + + +#endif diff --git a/amarok/src/playlistselection.cpp b/amarok/src/playlistselection.cpp new file mode 100644 index 00000000..4e5dc69b --- /dev/null +++ b/amarok/src/playlistselection.cpp @@ -0,0 +1,222 @@ +/*************************************************************************** + * Copyright (C) 2005 Ian Monroe * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#define DEBUG_PREFIX "PlaylistSelection" + +#include "amarok.h" //foreach +#include "debug.h" +#include "newdynamic.h" +#include "dynamicmode.h" +#include "playlistbrowser.h" +#include "playlistselection.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +PlaylistSelection::PlaylistSelection( QWidget* parent, const char* name ) + : KListView( parent, name ) +{ + addColumn( i18n("Select Playlists") ); + setRootIsDecorated( true ); + PlaylistBrowserView* browserTree = PlaylistBrowser::instance()->getListView(); + QListViewItem* browserItem = browserTree->firstChild(); + //load into the tree the first two items, which is the smart playlist and the playlist + for( int i = 0; i<2; i++ ) + { + QListViewItem* newItem = new QListViewItem( this, browserItem->text(0) ); + newItem->setPixmap( 0, *browserItem->pixmap(0) ); + loadChildren( browserItem, newItem ); + newItem->setOpen( true ); + browserItem = browserItem->nextSibling(); + } +} + +void PlaylistSelection::loadChildren( QListViewItem* browserParent, QListViewItem* selectionParent ) +{ + QListViewItem* browserChild = browserParent->firstChild(); + + while( browserChild ) + { + SelectionListItem* selectionChild = new SelectionListItem( selectionParent, browserChild->text(0), browserChild ); + if ( browserChild->pixmap(0) ) + selectionChild->setPixmap( 0, *browserChild->pixmap(0) ); + + if( browserChild->childCount() > 0 ) + loadChildren( browserChild, selectionChild ); + + browserChild = browserChild->nextSibling(); + } +} + +//////////////////////////////// +/// ConfigDynamic +//////////////////////////////// +namespace ConfigDynamic +{ + KDialogBase* basicDialog( QWidget* parent ) + { + KDialogBase* dialog = new KDialogBase( parent, "new dynamic", true, + i18n("Create Dynamic Playlist"), + KDialogBase::Ok | KDialogBase::Cancel, + KDialogBase::Ok, true ); + kapp->setTopWidget( dialog ); + dialog->setCaption( i18n("Dynamic Mode") ); + NewDynamic* nd = new NewDynamic( dialog, "new dynamic"); + //QSizePolicy policy; + //policy.setHorData(QSizePolicy::Maximum); + //dialog->setSizePolicy(policy); + dialog->setMainWidget( nd ); + return dialog; + } + + void dynamicDialog( QWidget* parent ) + { + KDialogBase* dialog = basicDialog( parent ); + NewDynamic* nd = static_cast(dialog->mainWidget()); + nd->m_mixLabel->setText( i18n("Add Dynamic Playlist") ); + + if( dialog->exec() == QDialog::Accepted ) + addDynamic( nd ); + } + + void editDynamicPlaylist( QWidget* parent, DynamicMode* mode ) + { + KDialogBase* dialog = basicDialog( parent ); + NewDynamic* nd = static_cast(dialog->mainWidget()); + + nd->m_name->setText( mode->title() ); + nd->m_cycleTracks->setChecked( mode->cycleTracks() ); + nd->m_upcomingIntSpinBox->setValue( mode->upcomingCount() ); + nd->m_previousIntSpinBox->setValue( mode->previousCount() ); + + if( mode->appendType() == DynamicMode::CUSTOM ) + { + //check items in the custom playlist + nd->m_mixLabel->setText( i18n("Edit Dynamic Playlist") ); + QStringList items = mode->items(); + foreach( items ) + { + QCheckListItem* current = dynamic_cast( + Amarok::findItemByPath(nd->selectPlaylist, (*it)) ); + if( current ) + current->setOn(true); + } + } + else //if its a suggested song or a random mix... + { + nd->selectPlaylist->hide(); + nd->layout()->remove( nd->selectPlaylist ); + // don't allow editing the name of the default random and suggested dynamics + nd->m_name->hide(); + nd->m_playlistName_label->hide(); + if( mode->appendType() == DynamicMode::RANDOM ) + { + nd->m_mixLabel->setText( i18n("Random Mix") ); + } + else + { + nd->m_mixLabel->setText( i18n("Suggested Songs") ); + } + } + + nd->updateGeometry(); + dialog->resize( nd->minimumSizeHint() ); + + if( dialog->exec() == QDialog::Accepted ) + { + loadDynamicMode( mode, nd ); + PlaylistBrowser::instance()->getDynamicCategory()->sortChildItems( 0, true ); + PlaylistBrowser::instance()->saveDynamics(); + } + + } + + void loadDynamicMode( DynamicMode* saveMe, NewDynamic* dialog ) + { + saveMe->setTitle( dialog->m_name->text().replace( "\n", " " ) ); + saveMe->setCycleTracks( dialog->m_cycleTracks->isChecked() ); + saveMe->setUpcomingCount( dialog->m_upcomingIntSpinBox->value() ); + saveMe->setPreviousCount( dialog->m_previousIntSpinBox->value() ); + + QStringList list; + debug() << "Saving custom list..." << endl; + QListViewItemIterator it( dialog->selectPlaylist, QListViewItemIterator::Checked ); + + while( it.current() ) + { + SelectionListItem *item = static_cast(it.current()); + list.append( item->name() ); + ++it; + } + saveMe->setItems( list ); + } + + void addDynamic( NewDynamic* dialog ) + { + QListViewItem *parent = PlaylistBrowser::instance()->getDynamicCategory(); + DynamicEntry *saveMe = new DynamicEntry( parent, 0, dialog->m_name->text().replace( "\n", " " ) ); + saveMe->setAppendType( DynamicMode::CUSTOM ); + + loadDynamicMode( saveMe, dialog ); + + parent->sortChildItems( 0, true ); + parent->setOpen( true ); + + PlaylistBrowser::instance()->saveDynamics(); + } + +} +//////////////////////////////// +/// SelectionListItem +//////////////////////////////// +SelectionListItem::SelectionListItem( QCheckListItem * parent, const QString& text, QListViewItem* browserEquivalent ) + : QCheckListItem( parent, text, QCheckListItem::CheckBox ) + , m_browserEquivalent( browserEquivalent ) +{ } + +SelectionListItem::SelectionListItem( QListViewItem * parent, const QString& text, QListViewItem* browserEquivalent ) + : QCheckListItem( parent, text, QCheckListItem::CheckBox ) + , m_browserEquivalent( browserEquivalent ) +{ } + +void SelectionListItem::stateChange( bool b ) +{ + QListViewItem* it = firstChild(); + while( it ) + { + static_cast(it)->setOn( b ); //calls stateChange, so is recursive + it = it->nextSibling(); + } +} + +QString +SelectionListItem::name() const +{ + QString fullName = text(0).replace('/', "\\/"); + QListViewItem *p = parent(); + while ( p ) { + fullName.prepend( p->text(0).replace('/', "\\/") + "/" ); + p = p->parent(); + } + return fullName; +} + +#include "playlistselection.moc" diff --git a/amarok/src/playlistselection.h b/amarok/src/playlistselection.h new file mode 100644 index 00000000..ce13d424 --- /dev/null +++ b/amarok/src/playlistselection.h @@ -0,0 +1,59 @@ +/*************************************************************************** + * Copyright (C) 2005 Ian Monroe * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +#ifndef PLAYLISTSELECTION_H +#define PLAYLISTSELECTION_H + +#include + +class NewDynamic; +class KDialogBase; +class DynamicMode; + +//this is a widget used in newdynamic.ui +class PlaylistSelection : public KListView +{ + Q_OBJECT + public: + PlaylistSelection(QWidget* parent, const char* name); + virtual QSize sizeHint() const + { + return minimumSizeHint(); + } + + private: + void loadChildren(QListViewItem* browserParent, QListViewItem* selectionParent); +}; + +namespace ConfigDynamic +{ + void addDynamic( NewDynamic* dialog ); + void dynamicDialog( QWidget* parent ); + void editDynamicPlaylist( QWidget* parent, DynamicMode* mode ); + void loadDynamicMode( DynamicMode* saveMe, NewDynamic* dialog ); + + KDialogBase* basicDialog( QWidget* parent ); +} + +class SelectionListItem : public QCheckListItem +{ + public: + SelectionListItem( QListViewItem * parent, const QString& text, QListViewItem* browserEquivalent ); + SelectionListItem( QCheckListItem * parent, const QString& text, QListViewItem* browserEquivalent ); + + virtual QString name() const; + + protected: + virtual void stateChange( bool ); + + private: + QListViewItem* m_browserEquivalent; +}; + +#endif /*PLAYLISTSELECTION_H*/ diff --git a/amarok/src/playlistwindow.cpp b/amarok/src/playlistwindow.cpp new file mode 100644 index 00000000..f243e8e6 --- /dev/null +++ b/amarok/src/playlistwindow.cpp @@ -0,0 +1,1243 @@ +/*************************************************************************** + begin : Fre Nov 15 2002 + copyright : (C) Mark Kretschmann + : (C) Max Howell + : (C) G??bor Lehel +***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "config.h" //HAVE_LIBVISUAL definition + +#include "actionclasses.h" //see toolbar construction +#include "amarok.h" +#include "amarokconfig.h" +#include "browserbar.h" +#include "clicklineedit.h" //m_lineEdit +#include "collectionbrowser.h" +#include "contextbrowser.h" +#include "debug.h" +#include "mediadevicemanager.h" +#include "editfilterdialog.h" +#include "enginecontroller.h" //for actions in ctor +#include "filebrowser.h" +#include "k3bexporter.h" +#include "lastfm.h" //check credentials when adding lastfm streams +#include "mediabrowser.h" +#include "dynamicmode.h" +#include "playlist.h" +#include "playlistbrowser.h" +#include "playlistwindow.h" +#include "scriptmanager.h" +#include "statistics.h" +#include "statusbar.h" +#include "threadmanager.h" +#include "magnatunebrowser/magnatunebrowser.h" + +#include //eventFilter() +#include +#include +#include +#include //search filter label + +#include //dynamic title +#include + +#include //qspaceritem in dynamic bar +#include //search filter timer +#include //QToolTip::add() +#include //contains the playlist + +#include //m_actionCollection +#include //kapp +#include //savePlaylist(), openPlaylist() +#include +#include //Welcome Tab +#include //ClearFilter button +#include //slotAddStream() +#include +#include +#include //savePlaylist() +#include +#include +#include //Welcome Tab, locate welcome.html +#include +#include //createGUI() +#include //XMLGUI +#include //XMLGUI + +#include + + + +////////////////////////////////////////////////////////////////////////////////////////// +/// CLASS Amarok::ToolBar +////////////////////////////////////////////////////////////////////////////////////////// + +namespace Amarok +{ + class ToolBar : public KToolBar + { + public: + ToolBar( QWidget *parent, const char *name ) + : KToolBar( parent, name ) + {} + + protected: + virtual void + contextMenuEvent( QContextMenuEvent *e ) { + Amarok::Menu::instance()->popup( e->globalPos() ); + } + + virtual void + wheelEvent( QWheelEvent *e ) { + EngineController::instance()->increaseVolume( e->delta() / Amarok::VOLUME_SENSITIVITY ); + } + }; +} + +PlaylistWindow *PlaylistWindow::s_instance = 0; + +PlaylistWindow::PlaylistWindow() + : QWidget( 0, "PlaylistWindow", Qt::WGroupLeader ) + , KXMLGUIClient() + , m_lastBrowser( 0 ) +{ + s_instance = this; + + // Sets caption and icon correctly (needed e.g. for GNOME) + kapp->setTopWidget( this ); + + KActionCollection* const ac = actionCollection(); + const EngineController* const ec = EngineController::instance(); + + ac->setAutoConnectShortcuts( false ); + ac->setWidget( this ); + + new K3bExporter(); + + KStdAction::configureToolbars( kapp, SLOT( slotConfigToolBars() ), ac ); + KStdAction::keyBindings( kapp, SLOT( slotConfigShortcuts() ), ac ); + KStdAction::keyBindings( kapp, SLOT( slotConfigGlobalShortcuts() ), ac, "options_configure_globals" ); + KStdAction::preferences( kapp, SLOT( slotConfigAmarok() ), ac ); + ac->action("options_configure_globals")->setIcon( Amarok::icon( "configure" ) ); + ac->action(KStdAction::name(KStdAction::KeyBindings))->setIcon( Amarok::icon( "configure" ) ); + ac->action(KStdAction::name(KStdAction::ConfigureToolbars))->setIcon( Amarok::icon( "configure" ) ); + ac->action(KStdAction::name(KStdAction::Preferences))->setIcon( Amarok::icon( "configure" ) ); + + KStdAction::quit( kapp, SLOT( quit() ), ac ); + KStdAction::open( this, SLOT(slotAddLocation()), ac, "playlist_add" )->setText( i18n("&Add Media...") ); + ac->action( "playlist_add" )->setIcon( Amarok::icon( "files" ) ); + KStdAction::open( this, SLOT(slotAddStream()), ac, "stream_add" )->setText( i18n("&Add Stream...") ); + ac->action( "stream_add" )->setIcon( Amarok::icon( "files" ) ); + KStdAction::save( this, SLOT(savePlaylist()), ac, "playlist_save" )->setText( i18n("&Save Playlist As...") ); + ac->action( "playlist_save" )->setIcon( Amarok::icon( "save" ) ); + + //FIXME: after string freeze rename to "Burn Current Playlist"? + new KAction( i18n("Burn to CD"), Amarok::icon( "burn" ), 0, this, SLOT(slotBurnPlaylist()), ac, "playlist_burn" ); + actionCollection()->action("playlist_burn")->setEnabled( K3bExporter::isAvailable() ); + new KAction( i18n("Play Media..."), Amarok::icon( "files" ), 0, this, SLOT(slotPlayMedia()), ac, "playlist_playmedia" ); + new KAction( i18n("Play Audio CD"), Amarok::icon( "album" ), 0, this, SLOT(playAudioCD()), ac, "play_audiocd" ); + KAction *playPause = new KAction( i18n( "&Play/Pause" ), Amarok::icon( "play" ), Key_Space, ec, SLOT( playPause() ), ac, "play_pause" ); + new KAction( i18n("Script Manager"), Amarok::icon( "scripts" ), 0, this, SLOT(showScriptSelector()), ac, "script_manager" ); + new KAction( i18n("Queue Manager"), Amarok::icon( "queue" ), 0, this, SLOT(showQueueManager()), ac, "queue_manager" ); + KAction *seekForward = new KAction( i18n( "&Seek Forward" ), Amarok::icon( "fastforward" ), Key_Right, ec, SLOT( seekForward() ), ac, "seek_forward" ); + KAction *seekBackward = new KAction( i18n( "&Seek Backward" ), Amarok::icon( "rewind" ), Key_Left, ec, SLOT( seekBackward() ), ac, "seek_backward" ); + new KAction( i18n("Statistics"), Amarok::icon( "info" ), 0, this, SLOT(showStatistics()), ac, "statistics" ); + new KAction( i18n("Update Collection"), Amarok::icon( "refresh" ), 0, CollectionDB::instance(), SLOT( scanModifiedDirs() ), actionCollection(), "update_collection" ); + + m_lastfmTags << "Alternative" << "Ambient" << "Chill Out" << "Classical" << "Dance" + << "Electronica" << "Favorites" << "Heavy Metal" << "Hip Hop" << "Indie Rock" + << "Industrial" << "Japanese" << "Pop" << "Psytrance" << "Rap" << "Rock" + << "Soundtrack" << "Techno" << "Trance"; + + KPopupMenu* playTagRadioMenu = new KPopupMenu( this ); + int id = 0; + foreach( m_lastfmTags ) { + playTagRadioMenu->insertItem( *it, this, SLOT( playLastfmGlobaltag( int ) ), 0, id ); + ++id; + } + + KPopupMenu* addTagRadioMenu = new KPopupMenu( this ); + id = 0; + foreach( m_lastfmTags ) { + addTagRadioMenu->insertItem( *it, this, SLOT( addLastfmGlobaltag( int ) ), 0, id ); + ++id; + } + + KActionMenu* playLastfm = new KActionMenu( i18n( "Play las&t.fm Stream" ), Amarok::icon( "audioscrobbler" ), ac, "lastfm_play" ); + QPopupMenu* playLastfmMenu = playLastfm->popupMenu(); + playLastfmMenu->insertItem( i18n( "Personal Radio" ), this, SLOT( playLastfmPersonal() ) ); + playLastfmMenu->insertItem( i18n( "Neighbor Radio" ), this, SLOT( playLastfmNeighbor() ) ); + playLastfmMenu->insertItem( i18n( "Custom Station" ), this, SLOT( playLastfmCustom() ) ); + playLastfmMenu->insertItem( i18n( "Global Tag Radio" ), playTagRadioMenu ); + + KActionMenu* addLastfm = new KActionMenu( i18n( "Add las&t.fm Stream" ), Amarok::icon( "audioscrobbler" ), ac, "lastfm_add" ); + QPopupMenu* addLastfmMenu = addLastfm->popupMenu(); + addLastfmMenu->insertItem( i18n( "Personal Radio" ), this, SLOT( addLastfmPersonal() ) ); + addLastfmMenu->insertItem( i18n( "Neighbor Radio" ), this, SLOT( addLastfmNeighbor() ) ); + addLastfmMenu->insertItem( i18n( "Custom Station" ), this, SLOT( addLastfmCustom() ) ); + addLastfmMenu->insertItem( i18n( "Global Tag Radio" ), addTagRadioMenu ); + + ac->action( "options_configure_globals" )->setText( i18n( "Configure &Global Shortcuts..." ) ); + + new KAction( i18n( "Previous Track" ), Amarok::icon( "back" ), 0, ec, SLOT( previous() ), ac, "prev" ); + new KAction( i18n( "Play" ), Amarok::icon( "play" ), 0, ec, SLOT( play() ), ac, "play" ); + new KAction( i18n( "Pause" ), Amarok::icon( "pause" ), 0, ec, SLOT( pause() ), ac, "pause" ); + new KAction( i18n( "Next Track" ), Amarok::icon( "next" ), 0, ec, SLOT( next() ), ac, "next" ); + + KAction *toggleFocus = new KAction( i18n( "Toggle Focus" ), "reload", CTRL+Key_Tab, this, SLOT( slotToggleFocus() ), ac, "toggle_focus" ); + + + { // KAction idiocy -- shortcuts don't work until they've been plugged into a menu + KPopupMenu asdf; + + playPause->plug( &asdf ); + seekForward->plug( &asdf ); + seekBackward->plug( &asdf ); + toggleFocus->plug( &asdf ); + + playPause->unplug( &asdf ); + seekForward->unplug( &asdf ); + seekBackward->unplug( &asdf ); + toggleFocus->unplug( &asdf ); + } + + + new Amarok::MenuAction( ac ); + new Amarok::StopAction( ac ); + new Amarok::PlayPauseAction( ac ); + new Amarok::AnalyzerAction( ac ); + new Amarok::RepeatAction( ac ); + new Amarok::RandomAction( ac ); + new Amarok::FavorAction( ac ); + new Amarok::VolumeAction( ac ); + + if( K3bExporter::isAvailable() ) + new Amarok::BurnMenuAction( ac ); + + if( AmarokConfig::playlistWindowSize().isValid() ) { + // if first ever run, use sizeHint(), and let + // KWin place us otherwise use the stored values + resize( AmarokConfig::playlistWindowSize() ); + move( AmarokConfig::playlistWindowPos() ); + } +} + +PlaylistWindow::~PlaylistWindow() +{ + AmarokConfig::setPlaylistWindowPos( pos() ); //TODO de XT? + AmarokConfig::setPlaylistWindowSize( size() ); //TODO de XT? +} + + +///////// public interface + +/** + * This function will initialize the playlist window. + */ +void PlaylistWindow::init() +{ + DEBUG_BLOCK + + //this function is necessary because Amarok::actionCollection() returns our actionCollection + //via the App::m_pPlaylistWindow pointer since App::m_pPlaylistWindow is not defined until + //the above ctor returns it causes a crash unless we do the initialisation in 2 stages. + + m_browsers = new BrowserBar( this ); + + // + DynamicBar *dynamicBar = new DynamicBar( m_browsers->container()); + + QFrame *playlist; + + { // + KToolBar *bar = new KToolBar( m_browsers->container(), "NotMainToolBar" ); + bar->setIconSize( 22, false ); //looks more sensible + bar->setFlat( true ); //removes the ugly frame + bar->setMovingEnabled( false ); //removes the ugly frame + + playlist = new Playlist( m_browsers->container() ); + actionCollection()->action( "playlist_clear")->plug( bar ); + actionCollection()->action( "playlist_save")->plug( bar ); + bar->addSeparator(); + actionCollection()->action( "playlist_undo")->plug( bar ); + actionCollection()->action( "playlist_redo")->plug( bar ); + bar->boxLayout()->addStretch(); + QWidget *button = new KToolBarButton( "locationbar_erase", 1, bar ); + QLabel *filter_label = new QLabel( i18n("S&earch:") + ' ', bar ); + m_lineEdit = new ClickLineEdit( i18n( "Playlist Search" ), bar ); + filter_label->setBuddy( m_lineEdit ); + bar->setStretchableWidget( m_lineEdit ); + KPushButton *filterButton = new KPushButton("...", bar, "filter"); + filterButton->setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Fixed ); + + m_lineEdit->setFrame( QFrame::Sunken ); + m_lineEdit->installEventFilter( this ); //we intercept keyEvents + + connect( button, SIGNAL(clicked()), m_lineEdit, SLOT(clear()) ); + connect( m_lineEdit, SIGNAL(textChanged( const QString& )), + playlist, SLOT(setFilterSlot( const QString& )) ); + connect( filterButton, SIGNAL( clicked() ), SLOT( slotEditFilter() ) ); + + QToolTip::add( button, i18n( "Clear search field" ) ); + QString filtertip = i18n( "Enter space-separated terms to search in the playlist.\n\n" + "Advanced, Google-esque syntax is also available;\n" + "see the handbook (The Playlist section of chapter 4) for details." ); + + QToolTip::add( m_lineEdit, filtertip ); + QToolTip::add( filterButton, i18n( "Click to edit playlist filter" ) ); + } // + + + dynamicBar->init(); + m_toolbar = new Amarok::ToolBar( m_browsers->container(), "mainToolBar" ); +#ifndef Q_WS_MAC + m_toolbar->setShown( AmarokConfig::showToolbar() ); +#endif + QWidget *statusbar = new Amarok::StatusBar( this ); + + KAction* repeatAction = Amarok::actionCollection()->action( "repeat" ); + connect( repeatAction, SIGNAL( activated( int ) ), playlist, SLOT( slotRepeatTrackToggled( int ) ) ); + + m_menubar = new KMenuBar( this ); + + //BEGIN Actions menu + KPopupMenu *actionsMenu = new KPopupMenu( m_menubar ); + actionCollection()->action("playlist_playmedia")->plug( actionsMenu ); + actionCollection()->action("lastfm_play")->plug( actionsMenu ); + actionCollection()->action("play_audiocd")->plug( actionsMenu ); + actionsMenu->insertSeparator(); + actionCollection()->action("prev")->plug( actionsMenu ); + actionCollection()->action("play_pause")->plug( actionsMenu ); + actionCollection()->action("stop")->plug( actionsMenu ); + actionCollection()->action("next")->plug( actionsMenu ); + actionsMenu->insertSeparator(); + actionCollection()->action(KStdAction::name(KStdAction::Quit))->plug( actionsMenu ); + + connect( actionsMenu, SIGNAL( aboutToShow() ), SLOT( actionsMenuAboutToShow() ) ); + //END Actions menu + + //BEGIN Playlist menu + KPopupMenu *playlistMenu = new KPopupMenu( m_menubar ); + actionCollection()->action("playlist_add")->plug( playlistMenu ); + actionCollection()->action("stream_add")->plug( playlistMenu ); + actionCollection()->action("lastfm_add")->plug( playlistMenu ); + actionCollection()->action("playlist_save")->plug( playlistMenu ); + actionCollection()->action("playlist_burn")->plug( playlistMenu ); + playlistMenu->insertSeparator(); + actionCollection()->action("playlist_undo")->plug( playlistMenu ); + actionCollection()->action("playlist_redo")->plug( playlistMenu ); + playlistMenu->insertSeparator(); + actionCollection()->action("playlist_clear")->plug( playlistMenu ); + actionCollection()->action("playlist_shuffle")->plug( playlistMenu ); + //this one has no real context with regard to the menu + //actionCollection()->action("playlist_copy")->plug( playlistMenu ); + playlistMenu->insertSeparator(); + actionCollection()->action("queue_selected")->plug( playlistMenu ); + actionCollection()->action("playlist_remove_duplicates")->plug( playlistMenu ); + actionCollection()->action("playlist_select_all")->plug( playlistMenu ); + //END Playlist menu + + //BEGIN Mode menu + KPopupMenu *modeMenu = new KPopupMenu( m_menubar ); + actionCollection()->action("repeat")->plug( modeMenu ); + KSelectAction *random = static_cast( actionCollection()->action("random_mode") ); + random->plug( modeMenu ); + random->popupMenu()->insertSeparator(); + actionCollection()->action("favor_tracks")->plug( random->popupMenu() ); + //END Mode menu + + //BEGIN Tools menu + m_toolsMenu = new KPopupMenu( m_menubar ); + m_toolsMenu->insertItem( SmallIconSet( Amarok::icon( "covermanager" ) ), i18n("&Cover Manager"), Amarok::Menu::ID_SHOW_COVER_MANAGER ); + actionCollection()->action("queue_manager")->plug( m_toolsMenu ); + m_toolsMenu->insertItem( SmallIconSet( Amarok::icon( "visualizations" ) ), i18n("&Visualizations"), Amarok::Menu::ID_SHOW_VIS_SELECTOR ); + m_toolsMenu->insertItem( SmallIconSet( Amarok::icon( "equalizer") ), i18n("&Equalizer"), kapp, SLOT( slotConfigEqualizer() ), 0, Amarok::Menu::ID_CONFIGURE_EQUALIZER ); + actionCollection()->action("script_manager")->plug( m_toolsMenu ); + actionCollection()->action("statistics")->plug( m_toolsMenu ); + m_toolsMenu->insertSeparator(); + actionCollection()->action("update_collection")->plug( m_toolsMenu ); + m_toolsMenu->insertItem( SmallIconSet( Amarok::icon( "rescan" ) ), i18n("&Rescan Collection"), Amarok::Menu::ID_RESCAN_COLLECTION ); + + #if defined HAVE_LIBVISUAL + m_toolsMenu->setItemEnabled( Amarok::Menu::ID_SHOW_VIS_SELECTOR, true ); + #else + m_toolsMenu->setItemEnabled( Amarok::Menu::ID_SHOW_VIS_SELECTOR, false ); + #endif + + connect( m_toolsMenu, SIGNAL( aboutToShow() ), SLOT( toolsMenuAboutToShow() ) ); + connect( m_toolsMenu, SIGNAL( activated(int) ), SLOT( slotMenuActivated(int) ) ); + //END Tools menu + + //BEGIN Settings menu + m_settingsMenu = new KPopupMenu( m_menubar ); + //TODO use KStdAction or KMainWindow +#ifndef Q_WS_MAC + m_settingsMenu->insertItem( AmarokConfig::showToolbar() ? i18n( "Hide Toolbar" ) : i18n("Show Toolbar"), ID_SHOW_TOOLBAR ); + m_settingsMenu->insertItem( AmarokConfig::showPlayerWindow() ? i18n("Hide Player &Window") : i18n("Show Player &Window"), ID_SHOW_PLAYERWINDOW ); + m_settingsMenu->insertSeparator(); +#endif + +#ifdef Q_WS_MAC + // plug it first, as this item will be moved to the applications first menu + actionCollection()->action(KStdAction::name(KStdAction::Preferences))->plug( m_settingsMenu ); +#endif + actionCollection()->action("options_configure_globals")->plug( m_settingsMenu ); + actionCollection()->action(KStdAction::name(KStdAction::KeyBindings))->plug( m_settingsMenu ); + actionCollection()->action(KStdAction::name(KStdAction::ConfigureToolbars))->plug( m_settingsMenu ); + actionCollection()->action(KStdAction::name(KStdAction::Preferences))->plug( m_settingsMenu ); + + connect( m_settingsMenu, SIGNAL( activated(int) ), SLOT( slotMenuActivated(int) ) ); + //END Settings menu + + m_menubar->insertItem( i18n( "E&ngage" ), actionsMenu ); + m_menubar->insertItem( i18n( "&Playlist" ), playlistMenu ); + m_menubar->insertItem( i18n( "&Mode" ), modeMenu ); + m_menubar->insertItem( i18n( "&Tools" ), m_toolsMenu ); + m_menubar->insertItem( i18n( "&Settings" ), m_settingsMenu ); + m_menubar->insertItem( i18n( "&Help" ), Amarok::Menu::helpMenu() ); + + + QBoxLayout *layV = new QVBoxLayout( this ); + layV->addWidget( m_menubar ); + layV->addWidget( m_browsers, 1 ); + layV->addWidget( m_toolbar ); + layV->addSpacing( 2 ); + layV->addWidget( statusbar ); + + //The volume slider later becomes our FocusProxy, so all wheelEvents get redirected to it + m_toolbar->setFocusPolicy( QWidget::WheelFocus ); + m_toolbar->setFlat( true ); + m_toolbar->setMovingEnabled( false ); + playlist->setMargin( 2 ); + playlist->installEventFilter( this ); //we intercept keyEvents + + + // + { + QString xmlFile = Amarok::config()->readEntry( "XMLFile", "amarokui.rc" ); + + // this bug can bite you if you are a pre 1.2 user, we + // deleted amarokui_first.rc, but we must still support it + // NOTE 1.4.1 we remove amarokui_xmms.rc too, so we can only be this ui.rc + xmlFile = "amarokui.rc"; + + setXMLFile( xmlFile ); + createGUI(); //NOTE we implement this + } + // + + + // + { + Debug::Block block( "Creating browsers. Please report long start times!" ); + + #define addBrowserMacro( Type, name, text, icon ) { \ + Debug::Block block( name ); \ + m_browsers->addBrowser( name, new Type( name ), text, icon ); } + + #define addInstBrowserMacro( Type, name, text, icon ) { \ + Debug::Block block( name ); \ + m_browsers->addBrowser( name, Type::instance(), text, icon ); } + + addBrowserMacro( ContextBrowser, "ContextBrowser", i18n("Context"), Amarok::icon( "info" ) ) + addBrowserMacro( CollectionBrowser, "CollectionBrowser", i18n("Collection"), Amarok::icon( "collection" ) ) + m_browsers->makeDropProxy( "CollectionBrowser", CollectionView::instance() ); + addInstBrowserMacro( PlaylistBrowser, "PlaylistBrowser", i18n("Playlists"), Amarok::icon( "playlist" ) ) + + //DEBUG: Comment out the addBrowserMacro line and uncomment the m_browsers line (passing in a vfat device name) to see the "virtual root" functionality + + addBrowserMacro( FileBrowser, "FileBrowser", i18n("Files"), Amarok::icon( "files" ) ) + //Add Magnatune browser + addInstBrowserMacro( MagnatuneBrowser, "MagnatuneBrowser", i18n("Magnatune"), Amarok::icon( "magnatune" ) ) + + new MediaBrowser( "MediaBrowser" ); + if( MediaBrowser::isAvailable() ) + { + addInstBrowserMacro( MediaBrowser, "MediaBrowser", i18n("Devices"), Amarok::icon( "device" ) ) + //to re-enable mediabrowser hiding, uncomment this: + //connect( MediaBrowser::instance(), SIGNAL( availabilityChanged( bool ) ), + // this, SLOT( mbAvailabilityChanged( bool ) ) ); + m_browsers->makeDropProxy( "MediaBrowser", MediaBrowser::queue() ); + + } + #undef addBrowserMacro + #undef addInstBrowserMacro + } + // + + connect( Playlist::instance()->qscrollview(), SIGNAL( dynamicModeChanged( const DynamicMode* ) ), + PlaylistBrowser::instance(), SLOT( loadDynamicItems() ) ); + + + qApp->installEventFilter( this ); // keyboards shortcuts for the browsers + + connect( playlist, SIGNAL( itemCountChanged( int, int, int, int, int, int ) ), + statusbar, SLOT( slotItemCountChanged( int, int, int, int, int, int ) ) ); + connect( playlist, SIGNAL( queueChanged( const PLItemList &, const PLItemList & ) ), + statusbar, SLOT( updateQueueLabel() ) ); + connect( playlist, SIGNAL( aboutToClear() ), m_lineEdit, SLOT( clear() ) ); + Amarok::MessageQueue::instance()->sendMessages(); +} + +void PlaylistWindow::slotSetFilter( const QString &filter ) //SLOT +{ + m_lineEdit->setText( filter ); +} + +void PlaylistWindow::slotEditFilter() //SLOT +{ + EditFilterDialog *fd = new EditFilterDialog( this, true, m_lineEdit->text() ); + connect( fd, SIGNAL(filterChanged(const QString &)), SLOT(slotSetFilter(const QString &)) ); + if( fd->exec() ) + m_lineEdit->setText( fd->filter() ); + delete fd; +} + +void PlaylistWindow::addBrowser( const QString &name, QWidget *browser, const QString &text, const QString &icon ) +{ + if( !m_browsers->browser( name ) ) + m_browsers->addBrowser( name, browser, text, icon ); + if( name == "MediaBrowser" ) + { + m_browsers->makeDropProxy( "MediaBrowser", MediaBrowser::queue() ); + } +} + + +/** + * Reload the amarokui.rc xml file. + * mainly just used by amarok::Menu + */ +void PlaylistWindow::recreateGUI() +{ + reloadXML(); + createGUI(); +} + + +/** + * Create the amarok gui from the xml file. + */ +void PlaylistWindow::createGUI() +{ + setUpdatesEnabled( false ); + + LastFm::Controller::instance(); // create love/ban/skip actions + + m_toolbar->clear(); + + //KActions don't unplug themselves when the widget that is plugged is deleted! + //we need to unplug to detect if the menu is plugged in App::applySettings() + //TODO report to bugs.kde.org + //we unplug after clear as otherwise it crashes! dunno why.. + KActionPtrList actions = actionCollection()->actions(); + for( KActionPtrList::Iterator it = actions.begin(), end = actions.end(); it != end; ++it ) + (*it)->unplug( m_toolbar ); + + KXMLGUIBuilder builder( this ); + KXMLGUIFactory factory( &builder, this ); + + //build Toolbar, plug actions + factory.addClient( this ); + + //TEXT ON RIGHT HACK + //KToolBarButtons have independent settings for their appearance. + //KToolBarButton::modeChange() causes that button to set its mode to that of its parent KToolBar + //KToolBar::setIconText() calls modeChange() for children, unless 2nd param is false + + QStringList list; + list << "toolbutton_playlist_add" +// << "toolbutton_playlist_clear" +// << "toolbutton_playlist_shuffle" +// << "toolbutton_playlist_show" + << "toolbutton_burn_menu" + << "toolbutton_amarok_menu"; + + m_toolbar->setIconText( KToolBar::IconTextRight, false ); //we want some buttons to have text on right + + const QStringList::ConstIterator end = list.constEnd(); + const QStringList::ConstIterator last = list.fromLast(); + for( QStringList::ConstIterator it = list.constBegin(); it != end; ++it ) + { + KToolBarButton* const button = static_cast( m_toolbar->child( (*it).latin1() ) ); + + if ( button ) { + button->modeChange(); + button->setFocusPolicy( QWidget::NoFocus ); + } + } + + m_toolbar->setIconText( KToolBar::IconOnly, false ); //default appearance + conserveMemory(); + setUpdatesEnabled( true ); +} + + +/** + * Apply the loaded settings on the playlist window. + * this function loads the custom fonts (if chosen) and than calls PlayList::instance()->applySettings(); + */ +void PlaylistWindow::applySettings() +{ + switch( AmarokConfig::useCustomFonts() ) + { + case true: + Playlist::instance()->setFont( AmarokConfig::playlistWindowFont() ); + ContextBrowser::instance()->setFont( AmarokConfig::contextBrowserFont() ); + break; + case false: + Playlist::instance()->unsetFont(); + ContextBrowser::instance()->unsetFont(); + break; + } +} + + +/** + * @param o The object + * @param e The event + * + * Here we filter some events for the Playlist Search LineEdit and the Playlist. @n + * this makes life easier since we have more useful functions available from this class + */ +bool PlaylistWindow::eventFilter( QObject *o, QEvent *e ) +{ + + + Playlist* const pl = Playlist::instance(); + typedef QListViewItemIterator It; + + switch( e->type() ) + { + case 6/*QEvent::KeyPress*/: + + //there are a few keypresses that we intercept + + #define e static_cast(e) + + if( e->key() == Key_F2 ) + { + // currentItem is ALWAYS visible. + QListViewItem *item = pl->currentItem(); + + // intercept F2 for inline tag renaming + // NOTE: tab will move to the next tag + // NOTE: if item is still null don't select first item in playlist, user wouldn't want that. It's silly. + // TODO: berkus has solved the "inability to cancel" issue with KListView, but it's not in kdelibs yet.. + + // item may still be null, but this is safe + // NOTE: column 0 cannot be edited currently, hence we pick column 1 + pl->rename( item, 1 ); //TODO what if this column is hidden? + + return true; + } + + if( e->state() & ControlButton ) + { + int n = -1; + switch( e->key() ) + { + case Key_0: n = 0; break; + case Key_1: n = 1; break; + case Key_2: n = 2; break; + case Key_3: n = 3; break; + case Key_4: n = 4; break; + case Key_5: n = 5; break; + } + if( n == 0 ) + { + m_browsers->closeCurrentBrowser(); + return true; + } + else if( n > 0 && n <= m_browsers->visibleCount() ) + { + m_browsers->showHideVisibleBrowser( n - 1 ); + return true; + } + } + + if( o == m_lineEdit ) //the search lineedit + { + QListViewItem *item; + switch( e->key() ) + { + case Key_Up: + case Key_Down: + case Key_PageDown: + case Key_PageUp: + pl->setFocus(); + QApplication::sendEvent( pl, e ); + return true; + + case Key_Return: + case Key_Enter: + item = *It( pl, It::Visible ); + m_lineEdit->clear(); + pl->m_filtertimer->stop(); //HACK HACK HACK + if( e->state() & ControlButton ) + { + PLItemList in, out; + if( e->state() & ShiftButton ) + for( It it( pl, It::Visible ); PlaylistItem *x = static_cast( *it ); ++it ) + { + pl->queue( x, true ); + ( pl->m_nextTracks.contains( x ) ? in : out ).append( x ); + } + else + { + It it( pl, It::Visible ); + pl->activate( *it ); + ++it; + for( int i = 0; PlaylistItem *x = static_cast( *it ); ++i, ++it ) + { + in.append( x ); + pl->m_nextTracks.insert( i, x ); + } + } + if( !in.isEmpty() || !out.isEmpty() ) + emit pl->queueChanged( in, out ); + pl->setFilter( "" ); + pl->ensureItemCentered( ( e->state() & ShiftButton ) ? item : pl->currentTrack() ); + } + else + { + pl->setFilter( "" ); + if( ( e->state() & ShiftButton ) && item ) + { + pl->queue( item ); + pl->ensureItemCentered( item ); + } + else + { + pl->activate( item ); + pl->showCurrentTrack(); + } + } + return true; + + case Key_Escape: + m_lineEdit->clear(); + return true; + + default: + return false; + } + } + + //following are for Playlist::instance() only + //we don't handle these in the playlist because often we manipulate the lineEdit too + + if( o == pl ) + { + if( pl->currentItem() && ( e->key() == Key_Up && pl->currentItem()->itemAbove() == 0 && !(e->state() & Qt::ShiftButton) ) ) + { + QListViewItem *lastitem = *It( pl, It::Visible ); + if ( !lastitem ) + return false; + while( lastitem->itemBelow() ) + lastitem = lastitem->itemBelow(); + pl->currentItem()->setSelected( false ); + pl->setCurrentItem( lastitem ); + lastitem->setSelected( true ); + pl->ensureItemVisible( lastitem ); + return true; + } + if( pl->currentItem() && ( e->key() == Key_Down && pl->currentItem()->itemBelow() == 0 && !(e->state() & Qt::ShiftButton) ) ) + { + pl->currentItem()->setSelected( false ); + pl->setCurrentItem( *It( pl, It::Visible ) ); + (*It( pl, It::Visible ))->setSelected( true ); + pl->ensureItemVisible( *It( pl, It::Visible ) ); + return true; + } + if( e->key() == Key_Delete ) + { + pl->removeSelectedItems(); + return true; + } + if( ( ( e->key() >= Key_0 && e->key() <= Key_Z ) || e->key() == Key_Backspace || e->key() == Key_Escape ) && ( !e->state() || e->state() == Qt::ShiftButton ) ) //only if shift or no modifier key is pressed and 0-Z or backspace or escape + { + m_lineEdit->setFocus(); + QApplication::sendEvent( m_lineEdit, e ); + return true; + } + } + #undef e + break; + + default: + break; + } + + return QWidget::eventFilter( o, e ); +} + + +void PlaylistWindow::closeEvent( QCloseEvent *e ) +{ +#ifdef Q_WS_MAC + Q_UNUSED( e ); + hide(); +#else + Amarok::genericEventHandler( this, e ); +#endif +} + + +void PlaylistWindow::showEvent( QShowEvent* ) +{ + static bool firstTime = true; + if( firstTime ) + Playlist::instance()->setFocus(); + firstTime = false; +} + +#include +QSize PlaylistWindow::sizeHint() const +{ + return QApplication::desktop()->screenGeometry( (QWidget*)this ).size() / 1.5; +} + + +void PlaylistWindow::savePlaylist() const //SLOT +{ + Playlist *pl = Playlist::instance(); + + PlaylistItem *item = pl->firstChild(); + if( item && !item->isVisible() ) + item = static_cast( item->itemBelow() ); + + QString title = pl->playlistName(); + + if( item && title == i18n( "Untitled" ) ) + { + QString artist = item->artist(); + QString album = item->album(); + + bool useArtist = true, useAlbum = true; + + item = static_cast( item->itemBelow() ); + + for( ; item; item = static_cast( item->itemBelow() ) ) + { + if( artist != item->artist() ) + useArtist = false; + if( album != item->album() ) + useAlbum = false; + + if( !useArtist && !useAlbum ) + break; + } + + if( useArtist && useAlbum ) + title = i18n("%1 - %2").arg( artist, album ); + else if( useArtist ) + title = artist; + else if( useAlbum ) + title = album; + } + + QString path = PlaylistDialog::getSaveFileName( title, pl->proposeOverwriteOnSave() ); + + if( !path.isEmpty() && Playlist::instance()->saveM3U( path ) ) + PlaylistWindow::self()->showBrowser( "PlaylistBrowser" ); +} + + +void PlaylistWindow::slotBurnPlaylist() const //SLOT +{ + K3bExporter::instance()->exportCurrentPlaylist(); +} + + +void PlaylistWindow::slotPlayMedia() //SLOT +{ + // Request location and immediately start playback + slotAddLocation( true ); +} + + +void PlaylistWindow::slotAddLocation( bool directPlay ) //SLOT +{ + // open a file selector to add media to the playlist + KURL::List files; + //files = KFileDialog::getOpenURLs( QString::null, "*.*|" + i18n("All Files"), this, i18n("Add Media") ); + KFileDialog dlg( QString::null, "*.*|", this, "openMediaDialog", true ); + dlg.setCaption( directPlay ? i18n("Play Media (Files or URLs)") : i18n("Add Media (Files or URLs)") ); + dlg.setMode( KFile::Files | KFile::Directory ); + dlg.exec(); + files = dlg.selectedURLs(); + if( files.isEmpty() ) return; + const int options = directPlay ? Playlist::Append | Playlist::DirectPlay : Playlist::Append; + + const KURL::List::ConstIterator end = files.constEnd(); + + for( KURL::List::ConstIterator it = files.constBegin(); it != end; ++it ) + if( it == files.constBegin() ) + Playlist::instance()->insertMedia( *it, options ); + else + Playlist::instance()->insertMedia( *it, Playlist::Append ); +} + +void PlaylistWindow::slotAddStream() //SLOT +{ + bool ok; + QString url = KInputDialog::getText( i18n("Add Stream"), i18n("URL"), QString::null, &ok, this ); + + if ( !ok ) return; + + KURL::List media( KURL::fromPathOrURL( url ) ); + Playlist::instance()->insertMedia( media ); +} + + +void PlaylistWindow::playLastfmPersonal() //SLOT +{ + if( !LastFm::Controller::checkCredentials() ) return; + + const KURL url( QString( "lastfm://user/%1/personal" ) + .arg( AmarokConfig::scrobblerUsername() ) ); + + Playlist::instance()->insertMedia( url, Playlist::Append|Playlist::DirectPlay ); +} + + +void PlaylistWindow::addLastfmPersonal() //SLOT +{ + if( !LastFm::Controller::checkCredentials() ) return; + + const KURL url( QString( "lastfm://user/%1/personal" ) + .arg( AmarokConfig::scrobblerUsername() ) ); + + Playlist::instance()->insertMedia( url ); +} + + +void PlaylistWindow::playLastfmNeighbor() //SLOT +{ + if( !LastFm::Controller::checkCredentials() ) return; + + const KURL url( QString( "lastfm://user/%1/neighbours" ) + .arg( AmarokConfig::scrobblerUsername() ) ); + + Playlist::instance()->insertMedia( url, Playlist::Append|Playlist::DirectPlay ); +} + + +void PlaylistWindow::addLastfmNeighbor() //SLOT +{ + if( !LastFm::Controller::checkCredentials() ) return; + + const KURL url( QString( "lastfm://user/%1/neighbours" ) + .arg( AmarokConfig::scrobblerUsername() ) ); + + Playlist::instance()->insertMedia( url ); +} + + +void PlaylistWindow::playLastfmCustom() //SLOT +{ + const QString token = LastFm::Controller::createCustomStation(); + if( token.isEmpty() ) return; + + const KURL url( "lastfm://artist/" + token + "/similarartists" ); + Playlist::instance()->insertMedia( url, Playlist::Append|Playlist::DirectPlay ); +} + + +void PlaylistWindow::addLastfmCustom() //SLOT +{ + const QString token = LastFm::Controller::createCustomStation(); + if( token.isEmpty() ) return; + + const KURL url( "lastfm://artist/" + token + "/similarartists" ); + Playlist::instance()->insertMedia( url ); +} + + +void PlaylistWindow::playLastfmGlobaltag( int id ) //SLOT +{ + if( !LastFm::Controller::checkCredentials() ) return; + + const QString tag = m_lastfmTags[id].lower(); + const KURL url( "lastfm://globaltags/" + tag ); + + Playlist::instance()->insertMedia( url, Playlist::Append|Playlist::DirectPlay ); +} + + +void PlaylistWindow::addLastfmGlobaltag( int id ) //SLOT +{ + if( !LastFm::Controller::checkCredentials() ) return; + + const QString tag = m_lastfmTags[id].lower(); + const KURL url( "lastfm://globaltags/" + tag ); + + Playlist::instance()->insertMedia( url ); +} + + +void PlaylistWindow::playAudioCD() //SLOT +{ + KURL::List urls; + if( EngineController::engine()->getAudioCDContents(QString::null, urls) ) + { + if (!urls.isEmpty()) + Playlist::instance()->insertMedia(urls, Playlist::Replace); + } + else + { // Default behaviour + m_browsers->showBrowser( "FileBrowser" ); + FileBrowser *fb = static_cast( m_browsers->browser("FileBrowser") ); + fb->setUrl( KURL("audiocd:/Wav/") ); + } +} + +void PlaylistWindow::showScriptSelector() //SLOT +{ + ScriptManager::instance()->show(); + ScriptManager::instance()->raise(); +} + +void PlaylistWindow::showQueueManager() //SLOT +{ + Playlist::instance()->showQueueManager(); +} + +void PlaylistWindow::showStatistics() //SLOT +{ + if( Statistics::instance() ) { + Statistics::instance()->raise(); + return; + } + Statistics dialog; + dialog.exec(); +} + +void PlaylistWindow::slotToggleFocus() //SLOT +{ + if( m_browsers->currentBrowser() && ( Playlist::instance()->hasFocus() || m_lineEdit->hasFocus() ) ) + m_browsers->currentBrowser()->setFocus(); + else + Playlist::instance()->setFocus(); +} + +void PlaylistWindow::slotMenuActivated( int index ) //SLOT +{ + switch( index ) + { + default: + //saves duplicating the code and header requirements + Amarok::Menu::instance()->slotActivated( index ); + break; + case ID_SHOW_TOOLBAR: + m_toolbar->setShown( !m_toolbar->isShown() ); + AmarokConfig::setShowToolbar( !AmarokConfig::showToolbar() ); + m_settingsMenu->changeItem( index, m_toolbar->isShown() ? i18n("Hide Toolbar") : i18n("Show Toolbar") ); + break; + case ID_SHOW_PLAYERWINDOW: + AmarokConfig::setShowPlayerWindow( !AmarokConfig::showPlayerWindow() ); + m_settingsMenu->changeItem( index, AmarokConfig::showPlayerWindow() ? i18n("Hide Player &Window") : i18n("Show Player &Window") ); + QTimer::singleShot( 0, kapp, SLOT( applySettings() ) ); + break; + case Amarok::Menu::ID_RESCAN_COLLECTION: + CollectionDB::instance()->startScan(); + break; + } +} + +void PlaylistWindow::actionsMenuAboutToShow() //SLOT +{ +} + +void PlaylistWindow::toolsMenuAboutToShow() //SLOT +{ + m_toolsMenu->setItemEnabled( Amarok::Menu::ID_CONFIGURE_EQUALIZER, EngineController::hasEngineProperty( "HasEqualizer" ) ); + m_toolsMenu->setItemEnabled( Amarok::Menu::ID_RESCAN_COLLECTION, !ThreadManager::instance()->isJobPending( "CollectionScanner" ) ); +} + + +#include +#include +/** + * Show/hide playlist global shortcut and PlayerWindow PlaylistButton connect to this slot + * RULES: + * 1. hidden & iconified -> deiconify & show @n + * 2. hidden & deiconified -> show @n + * 3. shown & iconified -> deiconify @n + * 4. shown & deiconified -> hide @n + * 5. don't hide if there is no tray icon or playerWindow. todo (I can't be arsed) @n + * + * @note isMinimized() can only be true if the window isShown() + * this has taken me hours to get right, change at your peril! + * there are more contingencies than you can believe + */ +void PlaylistWindow::showHide() //SLOT +{ +#ifdef Q_WS_X11 + const KWin::WindowInfo info = KWin::windowInfo( winId() ); + const uint desktop = KWin::currentDesktop(); + const bool isOnThisDesktop = info.isOnDesktop( desktop ); + const bool isShaded = false; + + if( isShaded ) + { + KWin::clearState( winId(), NET::Shaded ); + setShown( true ); + } + + if( !isOnThisDesktop ) + { + KWin::setOnDesktop( winId(), desktop ); + setShown( true ); + } + else if( !info.isMinimized() && !isShaded ) setShown( !isShown() ); + + if( isShown() ) KWin::deIconifyWindow( winId() ); +#else + setShown( !isShown() ); +#endif +} + +void PlaylistWindow::activate() +{ +#ifdef Q_WS_X11 + const KWin::WindowInfo info = KWin::windowInfo( winId() ); + + if( KWinModule( NULL, KWinModule::INFO_DESKTOP ).activeWindow() != winId()) + setShown( true ); + else if( !info.isMinimized() ) + setShown( true ); + if( isShown() ) + KWin::activateWindow( winId() ); +#else + setShown( true ); +#endif +} + +bool PlaylistWindow::isReallyShown() const +{ +#ifdef Q_WS_X11 + const KWin::WindowInfo info = KWin::windowInfo( winId() ); + return isShown() && !info.isMinimized() && info.isOnDesktop( KWin::currentDesktop() ); +#else + return isShown(); +#endif +} + +void +PlaylistWindow::mbAvailabilityChanged( bool isAvailable ) //SLOT +{ + if( isAvailable ) + { + if( m_browsers->indexForName( "MediaBrowser" ) == -1 ) + m_browsers->addBrowser( "MediaBrowser", MediaBrowser::instance(), i18n( "Media Device" ), Amarok::icon( "device" ) ); + } + else + { + if( m_browsers->indexForName( "MediaBrowser" ) != -1 ) + { + showBrowser( "CollectionBrowser" ); + m_browsers->removeMediaBrowser( MediaBrowser::instance() ); + } + } +} + +////////////////////////////////////////////////////////////////////////////////////////// +/// DynamicBar +////////////////////////////////////////////////////////////////////////////////////////// +DynamicBar::DynamicBar(QWidget* parent) + : QHBox( parent, "DynamicModeStatusBar" ) +{ + m_titleWidget = new DynamicTitle(this); + + setSpacing( KDialog::spacingHint() ); + QWidget *spacer = new QWidget( this ); + setStretchFactor( spacer, 10 ); +} + +// necessary because it has to be constructed before Playlist::instance(), but also connect to it +void DynamicBar::init() +{ + connect(Playlist::instance()->qscrollview(), SIGNAL(dynamicModeChanged(const DynamicMode*)), + SLOT(slotNewDynamicMode(const DynamicMode*))); + + KPushButton* editDynamicButton = new KPushButton( i18n("Edit"), this, "DynamicModeEdit" ); + connect( editDynamicButton, SIGNAL(clicked()), Playlist::instance()->qscrollview(), SLOT(editActiveDynamicMode()) ); + + KPushButton* repopButton = new KPushButton( i18n("Repopulate"), this, "DynamicModeRepopulate" ); + connect( repopButton, SIGNAL(clicked()), Playlist::instance()->qscrollview(), SLOT(repopulate()) ); + + KPushButton* disableButton = new KPushButton( i18n("Turn Off"), this, "DynamicModeDisable" ); + connect( disableButton, SIGNAL(clicked()), Playlist::instance()->qscrollview(), SLOT(disableDynamicMode()) ); + + slotNewDynamicMode( Playlist::instance()->dynamicMode() ); +} + +void DynamicBar::slotNewDynamicMode(const DynamicMode* mode) +{ + setShown(mode); + if (mode) + changeTitle(mode->title()); +} + +void DynamicBar::changeTitle(const QString& title) +{ + m_titleWidget->setTitle(title); +} + +////////////////////////////////////////////////////////////////////////////////////////// +/// DynamicTitle +////////////////////////////////////////////////////////////////////////////////////////// +DynamicTitle::DynamicTitle(QWidget* w) + : QWidget(w, "dynamic title") +{ + m_font.setBold( true ); + setTitle(""); +} + +void DynamicTitle::setTitle(const QString& newTitle) +{ + m_title = newTitle; + QFontMetrics fm(m_font); + setMinimumWidth( s_curveWidth*3 + fm.width(m_title) + s_imageSize ); + setMinimumHeight( fm.height() ); +} + +void DynamicTitle::paintEvent(QPaintEvent* /*e*/) +{ + QPainter p; + p.begin( this, false ); + QPen pen( colorGroup().highlightedText(), 0, Qt::NoPen ); + p.setPen( pen ); + p.setBrush( colorGroup().highlight() ); + p.setFont(m_font); + + QFontMetrics fm(m_font); + int textHeight = fm.height(); + if (textHeight < s_imageSize) + textHeight = s_imageSize; + int textWidth = fm.width(m_title); + int yStart = (height() - textHeight) / 2; + if(yStart < 0) + yStart = 0; + + p.drawEllipse( 0, yStart, s_curveWidth * 2, textHeight); + p.drawEllipse( s_curveWidth + textWidth + s_imageSize, yStart, s_curveWidth*2, textHeight); + p.fillRect( s_curveWidth, yStart, textWidth + s_imageSize + s_curveWidth, textHeight + , QBrush( colorGroup().highlight()) ); + p.drawPixmap( s_curveWidth, yStart + ((textHeight - s_imageSize) /2), SmallIcon("dynamic") ); + //not sure why first arg of Rect shouldn't add @curveWidth + p.drawText( QRect(s_imageSize, yStart, s_curveWidth + textWidth +s_imageSize, textHeight), Qt::AlignCenter, m_title); +} + +#include "playlistwindow.moc" diff --git a/amarok/src/playlistwindow.h b/amarok/src/playlistwindow.h new file mode 100644 index 00000000..1867b5ff --- /dev/null +++ b/amarok/src/playlistwindow.h @@ -0,0 +1,156 @@ +/*************************************************************************** + begin : Fre Nov 15 2002 + copyright : (C) Mark Kretschmann + : (C) Max Howell +***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef AMAROK_PLAYLISTWINDOW_H +#define AMAROK_PLAYLISTWINDOW_H + +#include "browserbar.h" + +#include //baseclass for DynamicBox +#include //baseclass +#include //baseclass (for XMLGUI) + +class ClickLineEdit; +class CollectionBrowser; +class ContextBrowser; +class MediaBrowser; +class QMenuBar; +class KPopupMenu; +class KToolBar; +class QLabel; +class QTimer; + +/** + * @class PlaylistWindow + * @short The PlaylistWindow widget class. + * + * This is the main window widget (the Playlist not Player). + */ +class PlaylistWindow : public QWidget, public KXMLGUIClient +{ + Q_OBJECT + + public: + PlaylistWindow(); + ~PlaylistWindow(); + + void init(); + void applySettings(); + + void createGUI(); //should be private but App::slowConfigToolbars requires it + void recreateGUI(); + + //allows us to switch browsers from within other browsers etc + void showBrowser( const QString& name ) { m_browsers->showBrowser( name ); } + void addBrowser( const QString &name, QWidget *widget, const QString &text, const QString &icon ); + + //takes into account minimized, multiple desktops, etc. + bool isReallyShown() const; + + virtual bool eventFilter( QObject*, QEvent* ); + + //instance is declared in KXMLGUI + static PlaylistWindow *self() { return s_instance; } + + void activate(); + + public slots: + void showHide(); + void mbAvailabilityChanged( bool isAvailable ); + + private slots: + void savePlaylist() const; + void slotBurnPlaylist() const; + void slotPlayMedia(); + void slotAddLocation( bool directPlay = false ); + void slotAddStream(); + void playLastfmPersonal(); + void addLastfmPersonal(); + void playLastfmNeighbor(); + void addLastfmNeighbor(); + void playLastfmCustom(); + void addLastfmCustom(); + void playLastfmGlobaltag( int ); + void addLastfmGlobaltag( int ); + void playAudioCD(); + void showQueueManager(); + void showScriptSelector(); + void showStatistics(); + void slotMenuActivated( int ); + void actionsMenuAboutToShow(); + void toolsMenuAboutToShow(); + void slotToggleFocus(); + void slotEditFilter(); + void slotSetFilter( const QString &filter ); + + protected: + virtual void closeEvent( QCloseEvent* ); + virtual void showEvent( QShowEvent* ); + virtual QSize sizeHint() const; + + private: + enum MenuId { ID_SHOW_TOOLBAR = 2000, ID_SHOW_PLAYERWINDOW }; + + QMenuBar *m_menubar; + KPopupMenu *m_toolsMenu; + KPopupMenu *m_settingsMenu; + BrowserBar *m_browsers; + KPopupMenu *m_searchMenu; + ClickLineEdit *m_lineEdit; + KToolBar *m_toolbar; + QTimer *m_timer; //search filter timer + QStringList m_lastfmTags; + MediaBrowser *m_currMediaBrowser; + + int m_lastBrowser; + int m_searchField; + + static PlaylistWindow *s_instance; +}; + +class DynamicTitle : public QWidget +{ + Q_OBJECT + public: + DynamicTitle( QWidget* parent ); + void setTitle( const QString& newTitle ); + + protected: + virtual void paintEvent( QPaintEvent* e ); + + private: + static const int s_curveWidth = 5; + static const int s_imageSize = 16; + QString m_title; + QFont m_font; +}; + +class DynamicBar : public QHBox +{ + Q_OBJECT + public: + DynamicBar( QWidget* parent ); + void init(); + + public slots: + void slotNewDynamicMode( const DynamicMode* mode ); + void changeTitle( const QString& title ); + + private: + DynamicTitle* m_titleWidget; +}; + + +#endif //AMAROK_PLAYLISTWINDOW_H diff --git a/amarok/src/plugin/Makefile.am b/amarok/src/plugin/Makefile.am new file mode 100644 index 00000000..c63da0a9 --- /dev/null +++ b/amarok/src/plugin/Makefile.am @@ -0,0 +1,16 @@ +noinst_LTLIBRARIES = \ + libplugin.la + +INCLUDES = \ + -I$(top_srcdir)/amarok/src \ + $(all_includes) + +libplugin_la_SOURCES = \ + plugin.cpp + +noinst_HEADERS = \ + plugin.h \ + pluginconfig.h + +METASOURCES = \ + AUTO diff --git a/amarok/src/plugin/plugin.cpp b/amarok/src/plugin/plugin.cpp new file mode 100644 index 00000000..0d305f0e --- /dev/null +++ b/amarok/src/plugin/plugin.cpp @@ -0,0 +1,41 @@ +// Author: Mark Kretschmann (C) Copyright 2004 +// Copyright: See COPYING file that comes with this distribution + +#include "plugin.h" + + +namespace Amarok { + + +Plugin::Plugin() +{} + + +Plugin::~Plugin() +{} + + +void +Plugin::addPluginProperty( const QString& key, const QString& value ) +{ + m_properties[key.lower()] = value; +} + + +QString +Plugin::pluginProperty( const QString& key ) +{ + if ( m_properties.find( key.lower() ) == m_properties.end() ) + return "false"; + + return m_properties[key.lower()]; +} + + +bool +Plugin::hasPluginProperty( const QString& key ) +{ + return m_properties.find( key.lower() ) != m_properties.end(); +} + +} diff --git a/amarok/src/plugin/plugin.h b/amarok/src/plugin/plugin.h new file mode 100644 index 00000000..2d905676 --- /dev/null +++ b/amarok/src/plugin/plugin.h @@ -0,0 +1,52 @@ +// Author: Mark Kretschmann (C) Copyright 2004 +// Copyright: See COPYING file that comes with this distribution + +#ifndef AMAROK_PLUGIN_H +#define AMAROK_PLUGIN_H + +#include +#include "amarok_export.h" + +#define AMAROK_EXPORT_PLUGIN( classname ) \ + extern "C" { \ + KDE_EXPORT Amarok::Plugin* create_plugin() { return new classname; } \ + } + +#include +#include + +class QWidget; + +namespace Amarok +{ + class PluginConfig; + + class LIBAMAROK_EXPORT Plugin + { + public: + virtual ~Plugin(); + + /** + * TODO @param parent you must parent the widget to parent + * @return the configure widget for your plugin, create it on the heap! + */ + //TODO rename configureWidget( QWidget *parent ) + virtual PluginConfig* configure() const { return 0; } + + void addPluginProperty( const QString& key, const QString& value ); + QString pluginProperty( const QString& key ); + bool hasPluginProperty( const QString& key ); + + protected: + Plugin(); + + private: + QMap m_properties; + }; + +} //namespace Amarok + + +#endif /* AMAROK_PLUGIN_H */ + + diff --git a/amarok/src/plugin/pluginconfig.h b/amarok/src/plugin/pluginconfig.h new file mode 100644 index 00000000..477f55cc --- /dev/null +++ b/amarok/src/plugin/pluginconfig.h @@ -0,0 +1,48 @@ +// (c) 2004 Mark Kretschmann +// See COPYING file for licensing information. + +#ifndef AMAROK_PLUGINCONFIG_H +#define AMAROK_PLUGINCONFIG_H + +#include + +class QWidget; + +namespace Amarok +{ + /** + * Class to allow user configuration of your plugin; you provide a GUI widget via view() + */ + + class PluginConfig : public QObject + { + Q_OBJECT + + signals: + /** Emit whenever some view setting is changed by the user */ + void viewChanged(); + + /** Emit after settings have been saved to config. Can be used for updating engine state. */ + void settingsSaved(); + + public: + /** Return the view widget, + * The PluginConfig object owns this pointer, nobody else will delete it for you + */ + virtual QWidget* view() = 0; + + /** Return true if any of the view settings are different to the currently saved state */ + virtual bool hasChanged() const = 0; + + /** Return true if all view settings are in their default states */ + virtual bool isDefault() const = 0; + + public slots: + /** Save view state using, eg KConfig */ + virtual void save() = 0; + }; +} + + +#endif /*AMAROK_PLUGINCONFIG_H*/ + diff --git a/amarok/src/pluginmanager.cpp b/amarok/src/pluginmanager.cpp new file mode 100644 index 00000000..4398e1ea --- /dev/null +++ b/amarok/src/pluginmanager.cpp @@ -0,0 +1,226 @@ +/*************************************************************************** +begin : 2004/03/12 +copyright : (C) Mark Kretschmann +email : markey@web.de +***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#define DEBUG_PREFIX "PluginManager" + +#include "debug.h" +#include "plugin.h" +#include "pluginmanager.h" + +#include + +#include +#include + +#include +#include +#include + +using std::vector; +using Amarok::Plugin; + + +vector +PluginManager::m_store; + + +///////////////////////////////////////////////////////////////////////////////////// +// PUBLIC INTERFACE +///////////////////////////////////////////////////////////////////////////////////// + +KTrader::OfferList +PluginManager::query( const QString& constraint ) +{ + // Add versioning constraint + QString + str = "[X-KDE-Amarok-framework-version] == "; + str += QString::number( FrameworkVersion ); + if ( !constraint.stripWhiteSpace().isEmpty() ) + str += " and " + constraint; + str += " and "; + str += "[X-KDE-Amarok-rank] > 0"; + + debug() << "Plugin trader constraint: " << str << endl; + + return KTrader::self()->query( "Amarok/Plugin", str ); +} + + +Plugin* +PluginManager::createFromQuery( const QString &constraint ) +{ + Debug::Block block( __PRETTY_FUNCTION__ ); + + KTrader::OfferList offers = query( constraint ); + + if ( offers.isEmpty() ) { + warning() << k_funcinfo << "No matching plugin found.\n"; + return 0; + } + + // Select plugin with highest rank + int rank = 0; + uint current = 0; + for ( uint i = 0; i < offers.count(); i++ ) { + if ( offers[i]->property( "X-KDE-Amarok-rank" ).toInt() > rank ) + current = i; + } + + return createFromService( offers[current] ); +} + + +Plugin* +PluginManager::createFromService( const KService::Ptr service ) +{ + debug() << "Trying to load: " << service->library() << endl; + + //get the library loader instance + KLibLoader *loader = KLibLoader::self(); + //try to load the specified library + KLibrary *lib = loader->globalLibrary( QFile::encodeName( service->library() ) ); + + if ( !lib ) { + KMessageBox::error( 0, i18n( "

    KLibLoader could not load the plugin:
    %1

    " + "

    Error message:
    %2

    " ) + .arg( service->library() ) + .arg( loader->lastErrorMessage() ) ); + return 0; + } + //look up address of init function and cast it to pointer-to-function + Plugin* (*create_plugin)() = ( Plugin* (*)() ) lib->symbol( "create_plugin" ); + + if ( !create_plugin ) { + warning() << k_funcinfo << "create_plugin == NULL\n"; + return 0; + } + //create plugin on the heap + Plugin* plugin = create_plugin(); + + //put plugin into store + StoreItem item; + item.plugin = plugin; + item.library = lib; + item.service = service; + m_store.push_back( item ); + + dump( service ); + return plugin; +} + + +void +PluginManager::unload( Plugin* plugin ) +{ + DEBUG_FUNC_INFO + + vector::iterator iter = lookupPlugin( plugin ); + + if ( iter != m_store.end() ) { + delete (*iter).plugin; + debug() << "Unloading library: "<< (*iter).service->library() << endl; + (*iter).library->unload(); + + m_store.erase( iter ); + } + else + warning() << k_funcinfo << "Could not unload plugin (not found in store).\n"; +} + + +KService::Ptr +PluginManager::getService( const Plugin* plugin ) +{ + if ( !plugin ) { + warning() << k_funcinfo << "pointer == NULL\n"; + return 0; + } + + //search plugin in store + vector::const_iterator iter = lookupPlugin( plugin ); + + if ( iter == m_store.end() ) { + warning() << k_funcinfo << "Plugin not found in store.\n"; + return 0; + } + + return (*iter).service; +} + + +void +PluginManager::showAbout( const QString &constraint ) +{ + KTrader::OfferList offers = query( constraint ); + + if ( offers.isEmpty() ) + return; + + KService::Ptr s = offers.front(); + + const QString body = "%1%2"; + + QString str = ""; + + str += body.arg( i18n( "Name" ), s->name() ); + str += body.arg( i18n( "Library" ), s->library() ); + str += body.arg( i18n( "Authors" ), s->property( "X-KDE-Amarok-authors" ).toStringList().join( "\n" ) ); + str += body.arg( i18n( "Email" ), s->property( "X-KDE-Amarok-email" ).toStringList().join( "\n" ) ); + str += body.arg( i18n( "Version" ), s->property( "X-KDE-Amarok-version" ).toString() ); + str += body.arg( i18n( "Framework Version" ), s->property( "X-KDE-Amarok-framework-version" ).toString() ); + + str += "
    "; + + KMessageBox::information( 0, str, i18n( "Plugin Information" ) ); +} + + +void +PluginManager::dump( const KService::Ptr service ) +{ + debug() << "PluginManager Service Info:" << endl; + debug() << "---------------------------" << endl; + debug() << "name : " << service->name() << endl; + debug() << "library : " << service->library() << endl; + debug() << "desktopEntryPath : " << service->desktopEntryPath() << endl; + debug() << "X-KDE-Amarok-plugintype : " << service->property( "X-KDE-Amarok-plugintype" ).toString() << endl; + debug() << "X-KDE-Amarok-name : " << service->property( "X-KDE-Amarok-name" ).toString() << endl; + debug() << "X-KDE-Amarok-authors : " << service->property( "X-KDE-Amarok-authors" ).toStringList() << endl; + debug() << "X-KDE-Amarok-rank : " << service->property( "X-KDE-Amarok-rank" ).toString() << endl; + debug() << "X-KDE-Amarok-version : " << service->property( "X-KDE-Amarok-version" ).toString() << endl; + debug() << "X-KDE-Amarok-framework-version: " << service->property( "X-KDE-Amarok-framework-version" ).toString() << endl; +} + + +///////////////////////////////////////////////////////////////////////////////////// +// PRIVATE INTERFACE +///////////////////////////////////////////////////////////////////////////////////// + +vector::iterator +PluginManager::lookupPlugin( const Plugin* plugin ) +{ + vector::iterator iter; + + //search plugin pointer in store + vector::iterator iterEnd(m_store.end() ); + for ( iter = m_store.begin(); iter != iterEnd; ++iter ) { + if ( (*iter).plugin == plugin ) + break; + } + + return iter; +} + + diff --git a/amarok/src/pluginmanager.h b/amarok/src/pluginmanager.h new file mode 100644 index 00000000..787829e2 --- /dev/null +++ b/amarok/src/pluginmanager.h @@ -0,0 +1,114 @@ +/*************************************************************************** +begin : 2004/03/12 +copyright : (C) Mark Kretschmann +email : markey@web.de +***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef AMAROK_PLUGINMANAGER_H +#define AMAROK_PLUGINMANAGER_H + +#include + +#include +#include + +namespace Amarok { class Plugin; } +class KLibrary; + +class PluginManager +{ + public: + /** Bump this number whenever the plugin framework gets incompatible with older versions */ + static const int FrameworkVersion = 32; + + /** + * It will return a list of services that match your + * specifications. The only required parameter is the service + * type. This is something like 'text/plain' or 'text/html'. The + * constraint parameter is used to limit the possible choices + * returned based on the constraints you give it. + * + * The @p constraint language is rather full. The most common + * keywords are AND, OR, NOT, IN, and EXIST, all used in an + * almost spoken-word form. An example is: + * \code + * (Type == 'Service') and (('KParts/ReadOnlyPart' in ServiceTypes) or (exist Exec)) + * \endcode + * + * The keys used in the query (Type, ServiceType, Exec) are all + * fields found in the .desktop files. + * + * @param constraint A constraint to limit the choices returned, QString::null to + * get all services of the given @p servicetype + * + * @return A list of services that satisfy the query + * @see http://developer.kde.org/documentation/library/kdeqt/tradersyntax.html + */ + static KTrader::OfferList query( const QString& constraint = QString::null ); + + /** + * Load and instantiate plugin from query + * @param constraint A constraint to limit the choices returned, QString::null to + * get all services of the given @p servicetype + * @return Pointer to Plugin, or NULL if error + * @see http://developer.kde.org/documentation/library/kdeqt/tradersyntax.html + */ + static Amarok::Plugin* createFromQuery( const QString& constraint = QString::null ); + + /** + * Load and instantiate plugin from service + * @param service Pointer to KService + * @return Pointer to Plugin, or NULL if error + */ + static Amarok::Plugin* createFromService( const KService::Ptr service ); + + /** + * Remove library and delete plugin + * @param plugin Pointer to plugin + */ + static void unload( Amarok::Plugin* plugin ); + + /** + * Look up service for loaded plugin from store + * @param pointer Pointer to plugin + * @return KService, or 0 if not found + */ + static KService::Ptr getService( const Amarok::Plugin* plugin ); + + /** + * Dump properties from a service to stdout for debugging + * @param service Pointer to KService + */ + static void dump( const KService::Ptr service ); + + /** + * Show modal info dialog about plugin + * @param constraint A constraint to limit the choices returned + */ + static void showAbout( const QString& constraint ); + + private: + struct StoreItem { + Amarok::Plugin* plugin; + KLibrary* library; + KService::Ptr service; + }; + + static std::vector::iterator lookupPlugin( const Amarok::Plugin* plugin ); + + //attributes: + static std::vector m_store; +}; + + +#endif /* AMAROK_PLUGINMANAGER_H */ + diff --git a/amarok/src/podcastbundle.h b/amarok/src/podcastbundle.h new file mode 100644 index 00000000..704f022a --- /dev/null +++ b/amarok/src/podcastbundle.h @@ -0,0 +1,264 @@ +// (c) 2006 Seb Ruiz +// See COPYING file for licensing information + +#ifndef AMAROK_PODCASTBUNDLE_H +#define AMAROK_PODCASTBUNDLE_H + +#include "podcastsettings.h" +#include +#include +#include + +class PodcastChannelBundle +{ + public: + PodcastChannelBundle() + : m_parentId( -1 ) + , m_autoscan( false ) + , m_fetchType( -1 ) + , m_autotransfer( false ) + , m_purge( false ) + , m_purgeCount( -1 ) { } + + PodcastChannelBundle( const KURL &url, const QString &title, const QString &author, const KURL &link, + const QString &desc, const QString ©, PodcastSettings *settings ) + { m_url = url; + m_title = title; + m_author = author; + m_link = link; + m_description = desc; + m_copyright = copy; + m_parentId = -1; + setSettings( settings ); + } + + void setSettings( PodcastSettings *settings ) + { + m_saveLocation = settings->saveLocation(); + m_autoscan = settings->autoscan(); + m_fetchType = settings->fetchType(); + m_autotransfer = settings->autoTransfer(); + m_purge = settings->hasPurge(); + m_purgeCount = settings->purgeCount(); + } + + PodcastSettings * getSettings() + { + return new PodcastSettings( m_title, m_saveLocation, m_autoscan, m_fetchType, + m_autotransfer, m_purge, m_purgeCount ); + } + + /// Return the url of the podcast feed + const KURL &url() const; + /// The title of the Podcast channel + const QString &title() const; + /// The author of the Podcast channel + const QString &author() const; + /// A url to the webpage of the podcast + const KURL &link() const; + /// A url to the image of the podcast + const KURL &imageURL() const; + const QString &description() const; + const QString ©right() const; + /// The id which the parent folder has in the database + int parentId() const; + + void setURL( const KURL &u ); + void setTitle( const QString &t ); + void setAuthor( const QString &a ); + void setLink( const KURL &l ); + void setImageURL( const KURL &i ); + void setDescription( const QString &d ); + void setCopyright( const QString &c ); + void setParentId( const int p ); + + void setSaveLocation( const QString &s ); + void setAutoScan( const bool b ); + void setFetchType( const int i ); + void setAutoTransfer( const bool b ); + void setPurge( const bool b ); + void setPurgeCount( const int i ); + + //settings + const QString& saveLocation() const; + bool autoscan() const; + int fetchType() const; + bool autotransfer() const; + bool hasPurge() const; + int purgeCount() const; + + private: + KURL m_url; + QString m_title; + QString m_author; + KURL m_link; + KURL m_imageUrl; + QString m_description; + QString m_copyright; + int m_parentId; + + QString m_saveLocation; + bool m_autoscan; + int m_fetchType; + bool m_autotransfer; + bool m_purge; + int m_purgeCount; +}; + +inline const KURL &PodcastChannelBundle::url() const { return m_url; } +inline const QString &PodcastChannelBundle::title() const { return m_title; } +inline const QString &PodcastChannelBundle::author() const { return m_author; } +inline const KURL &PodcastChannelBundle::link() const { return m_link; } +inline const KURL &PodcastChannelBundle::imageURL() const { return m_imageUrl; } +inline const QString &PodcastChannelBundle::description() const { return m_description; } +inline const QString &PodcastChannelBundle::copyright() const { return m_copyright; } +inline int PodcastChannelBundle::parentId() const { return m_parentId; } + +inline void PodcastChannelBundle::setURL ( const KURL &u ) { m_url = u; } +inline void PodcastChannelBundle::setTitle ( const QString &t ) { m_title = t; } +inline void PodcastChannelBundle::setAuthor ( const QString &a ) { m_author = a; } +inline void PodcastChannelBundle::setLink ( const KURL &l ) { m_link = l; } +inline void PodcastChannelBundle::setImageURL ( const KURL &i ) { m_imageUrl = i; } +inline void PodcastChannelBundle::setDescription ( const QString &d ) { m_description = d; } +inline void PodcastChannelBundle::setCopyright ( const QString &c ) { m_copyright = c; } +inline void PodcastChannelBundle::setParentId ( const int p ) { m_parentId = p; } + +inline void PodcastChannelBundle::setSaveLocation( const QString &s ) { m_saveLocation = s; } +inline void PodcastChannelBundle::setAutoScan( const bool b ) { m_autoscan = b; } +inline void PodcastChannelBundle::setFetchType( const int i ) { m_fetchType = i; } +inline void PodcastChannelBundle::setAutoTransfer( const bool b ) { m_autotransfer = b; } +inline void PodcastChannelBundle::setPurge( const bool b ) { m_purge = b; } +inline void PodcastChannelBundle::setPurgeCount( const int i ) { m_purgeCount = i; } + +inline const QString &PodcastChannelBundle::saveLocation() const { return m_saveLocation; } +inline bool PodcastChannelBundle::autoscan() const { return m_autoscan; } +inline int PodcastChannelBundle::fetchType() const { return m_fetchType; } +inline bool PodcastChannelBundle::autotransfer() const { return m_autotransfer; } +inline bool PodcastChannelBundle::hasPurge() const { return m_purge; } +inline int PodcastChannelBundle::purgeCount() const { return m_purgeCount; } + + + +class PodcastEpisodeBundle +{ + public: + PodcastEpisodeBundle() + : m_id( 0 ) + , m_duration( 0 ) + , m_size( 0 ) + , m_isNew( false ) + { + } + PodcastEpisodeBundle( const KURL &url, const KURL &parent, const QString &title, + const QString &author, const QString &desc, const QString &date, + const QString &type, const int duration, const QString &guid, + const bool isNew ) + : m_id( 0 ) + , m_size( 0 ) + { + m_url = url; + m_parent = parent; + m_author = author; + m_title = title; + m_description = desc; + m_type = type; + m_date = date; + m_duration = duration < 0 ? 0 : duration; + m_guid = guid; + m_isNew = isNew; + + if( !date.isEmpty() ) + m_dateTime.setTime_t( KRFCDate::parseDate( date ) ); + } + + /// The row id which this podcast episode has in the database + int dBId() const; + /// The remote url to the podcast episode + const KURL &url() const; + /// The local url of the podcast episode (if it has been downloaded, an invalid url otherwise) + const KURL &localUrl() const; + /// The url of the podcast channel + const KURL &parent() const; + const QString &author() const; + const QString &title() const; + const QString &subtitle() const; + const QString &description() const; + const QString &date() const; + QDateTime dateTime() const; + /// File type of the podcast episode, eg ogg, mp3 etc + const QString &type() const; + int duration() const; // duration in seconds + uint size() const; // file/stream size in bytes + /// unique identifier that should be available in the feed (RSS 2.0: guid ATOM: id) + const QString &guid() const; + /// Has this particular podcast episode been listened to? + bool isNew() const; + + void setDBId( const int i ); + void setURL( const KURL &u ); + void setLocalURL( const KURL &u ); + void setParent( const KURL &u ); + void setAuthor( const QString &a ); + void setTitle( const QString &t ); + void setSubtitle( const QString &s ); + void setDescription( const QString &d ); + void setDate( const QString &d ); + void setType( const QString &t ); + void setDuration( const int i ); + void setSize( const uint i ); + void setGuid( const QString &g ); + void setNew( const bool &b ); + + void detach(); // for being able to apply QDeepCopy<> + + private: + int m_id; + KURL m_url; + KURL m_localUrl; + KURL m_parent; + QString m_author; + QString m_title; + QString m_subtitle; + QString m_description; + QString m_date; + QDateTime m_dateTime; + QString m_type; + int m_duration; + uint m_size; + QString m_guid; + bool m_isNew; +}; + +inline int PodcastEpisodeBundle::dBId() const { return m_id; } +inline const KURL &PodcastEpisodeBundle::url() const { return m_url; } +inline const KURL &PodcastEpisodeBundle::localUrl() const { return m_localUrl; } +inline const KURL &PodcastEpisodeBundle::parent() const { return m_parent; } +inline const QString &PodcastEpisodeBundle::author() const { return m_author; } +inline const QString &PodcastEpisodeBundle::title() const { return m_title; } +inline const QString &PodcastEpisodeBundle::subtitle() const { return m_subtitle; } +inline const QString &PodcastEpisodeBundle::description() const { return m_description; } +inline const QString &PodcastEpisodeBundle::date() const { return m_date; } +inline QDateTime PodcastEpisodeBundle::dateTime() const { return m_dateTime; } +inline const QString &PodcastEpisodeBundle::type() const { return m_type; } +inline int PodcastEpisodeBundle::duration() const { return m_duration; } +inline uint PodcastEpisodeBundle::size() const { return m_size; } +inline const QString &PodcastEpisodeBundle::guid() const { return m_guid; } +inline bool PodcastEpisodeBundle::isNew() const { return m_isNew; } + +inline void PodcastEpisodeBundle::setDBId( const int i ) { m_id = i; } +inline void PodcastEpisodeBundle::setURL( const KURL &u ) { m_url = u; } +inline void PodcastEpisodeBundle::setLocalURL( const KURL &u ) { m_localUrl = u; } +inline void PodcastEpisodeBundle::setParent( const KURL &u ) { m_parent = u; } +inline void PodcastEpisodeBundle::setAuthor( const QString &a ) { m_author = a; } +inline void PodcastEpisodeBundle::setTitle( const QString &t ) { m_title = t; } +inline void PodcastEpisodeBundle::setSubtitle( const QString &t ) { m_subtitle = t; } +inline void PodcastEpisodeBundle::setDescription( const QString &d ) { m_description = d; } +inline void PodcastEpisodeBundle::setDate( const QString &d ) + { m_date = d; if( !d.isEmpty() ) m_dateTime.setTime_t( KRFCDate::parseDate( d ) );} +inline void PodcastEpisodeBundle::setType( const QString &t ) { m_type = t; } +inline void PodcastEpisodeBundle::setDuration( const int i ) { m_duration = i; } +inline void PodcastEpisodeBundle::setSize( const uint i ) { m_size = i; } +inline void PodcastEpisodeBundle::setGuid( const QString &g ) { m_guid = g; } +inline void PodcastEpisodeBundle::setNew( const bool &b ) { m_isNew = b; } + +#endif /* AMAROK_PODCASTBUNDLE_H */ diff --git a/amarok/src/podcastsettings.cpp b/amarok/src/podcastsettings.cpp new file mode 100644 index 00000000..4ee5af19 --- /dev/null +++ b/amarok/src/podcastsettings.cpp @@ -0,0 +1,225 @@ +// (c) 2005-2006 Seb Ruiz +// (c) 2006 Bart Cerneels +// See COPYING file for licensing information. + +#include "mediabrowser.h" +#include "podcastsettingsbase.h" +#include "podcastsettings.h" + +#include +#include +#include //global changes confirmation +#include +#include +#include +#include + +#include +#include +#include +#include + +PodcastSettings::PodcastSettings( const QDomNode &channelSettings, const QString &title ) + : m_title( title ) +{ + m_saveLocation = channelSettings.namedItem( "savelocation").toElement().text(); + m_autoScan = channelSettings.namedItem( "autoscan").toElement().text() == "true"; + m_fetch = channelSettings.namedItem("fetch").toElement().text() == "automatic"?AUTOMATIC:STREAM; + m_addToMediaDevice = channelSettings.namedItem( "autotransfer").toElement().text() == "true"; + m_purge = channelSettings.namedItem( "purge").toElement().text() == "true"; + m_purgeCount = channelSettings.namedItem( "purgecount").toElement().text().toInt(); +} + +// default settings +PodcastSettings::PodcastSettings( const QString &title ) + : m_title( title ) +{ + m_saveLocation = Amarok::saveLocation( "podcasts/" ); + m_saveLocation += Amarok::vfatPath( m_title ); + m_autoScan = true; + m_fetch = STREAM; + m_addToMediaDevice = false; + m_purge = false; + m_purgeCount = 0; +} + +PodcastSettings::PodcastSettings( const QString &title, const QString &save, const bool autoScan, + const int fetchType, const bool autotransfer, const bool purge, const int purgecount ) +{ + m_title = title; + if( save.isEmpty() ) + { + m_saveLocation = Amarok::saveLocation( "podcasts/" ); + m_saveLocation += Amarok::vfatPath( m_title ); + } + else + m_saveLocation = save; + + m_autoScan = autoScan; + m_fetch = fetchType; + m_addToMediaDevice = autotransfer; + m_purge = purge; + m_purgeCount = purgecount; +} + +PodcastSettingsDialog::PodcastSettingsDialog( PodcastSettings *settings, QWidget* parent ) + : KDialogBase( parent, 0, true, i18n("change options", "Configure %1").arg( settings->m_title ) + , KDialogBase::User1|KDialogBase::Ok|KDialogBase::Cancel + , KDialogBase::Ok, true + , KGuiItem(i18n("Reset"), "reset" ) ) + , m_settings( settings ) +{ + init(); + setSettings( settings ); +} + +PodcastSettingsDialog::PodcastSettingsDialog( const QPtrList &list, const QString &caption, QWidget* parent ) + : KDialogBase( parent, 0, true, i18n("change options", "Configure %1").arg( caption ) + , KDialogBase::User1|KDialogBase::Ok|KDialogBase::Cancel + , KDialogBase::Ok, true + , KGuiItem(i18n("Reset"), "reset" ) ) + , m_settingsList( list ) +{ + init(); + m_settings = m_settingsList.first(); + if( !m_settings->m_saveLocation.endsWith( "/" ) ) + m_settings->m_saveLocation = m_settings->m_saveLocation.section( "/", 0, -2 ); + setSettings( m_settings ); +} + +void +PodcastSettingsDialog::init() +{ + m_ps = new PodcastSettingsDialogBase(this); + + KWin::setState( winId(), NET::SkipTaskbar ); + + setMainWidget(m_ps); + m_ps->m_saveLocation->setMode( KFile::Directory | KFile::ExistingOnly ); + + m_ps->m_addToMediaDeviceCheck->setEnabled( MediaBrowser::isAvailable() ); + + enableButtonOK( false ); + + // Connects for modification check + connect( m_ps->m_purgeCountSpinBox->child( "qt_spinbox_edit" ), SIGNAL(textChanged( const QString& )), SLOT(checkModified()) ); + connect( m_ps->m_saveLocation, SIGNAL(textChanged( const QString& )), SLOT(checkModified()) ); + connect( m_ps->m_autoFetchCheck, SIGNAL(clicked()), SLOT(checkModified()) ); + connect( m_ps->m_streamRadio, SIGNAL(clicked()), SLOT(checkModified()) ); + connect( m_ps->m_addToMediaDeviceCheck, SIGNAL(clicked()), SLOT(checkModified()) ); + connect( m_ps->m_downloadRadio, SIGNAL(clicked()), SLOT(checkModified()) ); + connect( m_ps->m_purgeCheck, SIGNAL(clicked()), SLOT(checkModified()) ); +} + +bool +PodcastSettingsDialog::hasChanged() +{ + bool fetchTypeChanged = true; + + if( m_ps->m_streamRadio->isChecked() && m_settings->m_fetch == STREAM || + m_ps->m_downloadRadio->isChecked() && m_settings->m_fetch == AUTOMATIC ) + + fetchTypeChanged = false; + + return( m_settings->m_saveLocation != requesterSaveLocation() || + m_settings->m_autoScan != m_ps->m_autoFetchCheck->isChecked() || + m_settings->m_addToMediaDevice != m_ps->m_addToMediaDeviceCheck->isChecked() || + m_settings->m_purge != m_ps->m_purgeCheck->isChecked() || + m_settings->m_purgeCount != m_ps->m_purgeCountSpinBox->value() || + fetchTypeChanged ); +} + +void +PodcastSettingsDialog::checkModified() //slot +{ + enableButtonOK( hasChanged() ); +} + +void PodcastSettingsDialog::slotOk() //slot +{ + enableButtonOK( false ); //visual feedback + + if ( !m_settingsList.isEmpty() ) + { + foreachType( QPtrList, m_settingsList) + { + (*it)->m_saveLocation = requesterSaveLocation().append( Amarok::vfatPath( (*it)->title() ) ); + (*it)->m_autoScan = m_ps->m_autoFetchCheck->isChecked(); + (*it)->m_addToMediaDevice = m_ps->m_addToMediaDeviceCheck->isChecked(); + (*it)->m_purge = m_ps->m_purgeCheck->isChecked(); + (*it)->m_purgeCount = m_ps->m_purgeCountSpinBox->value(); + if( m_ps->m_streamRadio->isChecked() ) + (*it)->m_fetch = STREAM; + else + (*it)->m_fetch = AUTOMATIC; + } + } + else + { + m_settings->m_saveLocation = requesterSaveLocation(); + m_settings->m_autoScan = m_ps->m_autoFetchCheck->isChecked(); + m_settings->m_addToMediaDevice = m_ps->m_addToMediaDeviceCheck->isChecked(); + m_settings->m_purge = m_ps->m_purgeCheck->isChecked(); + m_settings->m_purgeCount = m_ps->m_purgeCountSpinBox->value(); + + if( m_ps->m_streamRadio->isChecked() ) + m_settings->m_fetch = STREAM; + else + m_settings->m_fetch = AUTOMATIC; + } + + KDialogBase::slotOk(); +} + +// KUrlRequester doesn't provide us with convenient functions for adding trailing slashes +QString PodcastSettingsDialog::requesterSaveLocation() +{ + QString url = m_ps->m_saveLocation->url(); + if( url.endsWith( "/" ) ) + return url; + else + return url + '/'; +} + +void PodcastSettingsDialog::setSettings( PodcastSettings *settings ) +{ + QString saveLocation = settings->m_saveLocation; + + m_ps->m_saveLocation->setURL( saveLocation ); + m_ps->m_autoFetchCheck->setChecked( settings->m_autoScan ); + + if( settings->m_fetch == STREAM ) + { + m_ps->m_streamRadio->setChecked( true ); + m_ps->m_downloadRadio->setChecked( false ); + } + else if( settings->m_fetch == AUTOMATIC ) + { + m_ps->m_streamRadio->setChecked( false ); + m_ps->m_downloadRadio->setChecked( true ); + } + + m_ps->m_addToMediaDeviceCheck->setChecked( settings->m_addToMediaDevice ); + m_ps->m_purgeCheck->setChecked( settings->m_purge ); + m_ps->m_purgeCountSpinBox->setValue( settings->m_purgeCount ); + + if( !settings->m_purge ) + { + m_ps->m_purgeCountSpinBox->setEnabled( false ); + m_ps->m_purgeCountLabel->setEnabled( false ); + } +} + +//reset to default settings button +void PodcastSettingsDialog::slotUser1() //slot +{ + setSettings( new PodcastSettings(m_settings->m_title) ); + checkModified(); +} + +bool PodcastSettingsDialog::configure() +{ + return exec() == QDialog::Accepted; +} + +#include "podcastsettings.moc" diff --git a/amarok/src/podcastsettings.h b/amarok/src/podcastsettings.h new file mode 100644 index 00000000..7bb65cc0 --- /dev/null +++ b/amarok/src/podcastsettings.h @@ -0,0 +1,77 @@ +// (c) 2005 Seb Ruiz +// See COPYING file for licensing information. + +#ifndef AMAROK_PODCASTSETTINGS_H +#define AMAROK_PODCASTSETTINGS_H + +#include "kdialogbase.h" //baseclass + +#include + +#include + +class PodcastChannel; +class PodcastEpisode; +class PodcastSettingsDialogBase; +class QDomNode; +class QDomElement; + +enum MediaFetch{ STREAM=0, AUTOMATIC=1 }; + +class PodcastSettings +{ + public: + PodcastSettings( const QDomNode &channelSettings, const QString &title ); + PodcastSettings( const PodcastSettings *parentSettings, const QString &title ); + PodcastSettings( const QString &title ); // standard settings + PodcastSettings( const QString &title, const QString &save, const bool autoScan, + const int fetchType, const bool autotransfer, const bool purge, const int purgecount ); + + const QString &saveLocation() { return m_saveLocation; } + const QString &title() { return m_title; } + bool autoscan() { return m_autoScan; } + int fetchType() { return m_fetch; } + bool autoTransfer() { return m_addToMediaDevice; } + bool hasPurge() { return m_purge; } + int purgeCount() { return m_purgeCount; } + + QString m_title; //the title of the podcast or category these settings belong to + QString m_saveLocation; + bool m_autoScan; + int m_fetch; + bool m_addToMediaDevice; + bool m_purge; + int m_purgeCount; +}; + + +class PodcastSettingsDialog : public KDialogBase +{ + Q_OBJECT + + public: + PodcastSettingsDialog( PodcastSettings *list, QWidget* parent=0 ); + PodcastSettingsDialog( const QPtrList &list, const QString &caption, QWidget* parent=0 ); + + bool configure(); + PodcastSettings *getSettings() { return m_settings; } + + protected: + bool hasChanged(); + + protected slots: + void checkModified(); + void slotOk(); + void slotUser1(); + + private: + void init(); + void setSettings( PodcastSettings *settings ); + QString requesterSaveLocation(); + + PodcastSettingsDialogBase *m_ps; + QPtrList m_settingsList; + PodcastSettings *m_settings; +}; + +#endif /*AMAROK_PODCASTSETTINGS_H*/ diff --git a/amarok/src/podcastsettingsbase.ui b/amarok/src/podcastsettingsbase.ui new file mode 100644 index 00000000..db3389e2 --- /dev/null +++ b/amarok/src/podcastsettingsbase.ui @@ -0,0 +1,219 @@ + +PodcastSettingsDialogBase +Seb Ruiz + + + Form1 + + + + 0 + 0 + 499 + 213 + + + + + 3 + 3 + 0 + 0 + + + + Podcast Configuration + + + + unnamed + + + 0 + + + + buttonGroup1 + + + 1 + + + 6 + + + Media Download + + + + unnamed + + + + m_streamRadio + + + Stream or download on re&quest + + + Media must be explicitly downloaded, otherwise the podcast will be played from the remote server. + + + Media must be explicitly downloaded, otherwise the podcast will be played from the remote server. + + + + + m_downloadRadio + + + Download when a&vailable + + + Download media as soon as it becomes available + + + Download media as soon as it becomes available + + + + + m_addToMediaDeviceCheck + + + Add to media device &transfer queue + + + When checked, Amarok will automatically add newly downloaded podcast shows to the media device transfer queue + + + When checked, Amarok will automatically add newly downloaded podcast shows to the media device transfer queue + + + + + + + m_purgeCheck + + + Limit &number of episodes + + + If checked, Amarok will throw away old podcast episodes + + + If checked, Amarok will throw away old podcast episodes + + + + + spacer16 + + + Horizontal + + + Expanding + + + + 111 + 21 + + + + + + m_purgeCountLabel + + + Keep maximum of: + + + + + m_purgeCountSpinBox + + + Items + + + 1 + + + The maximum number of podcast items to store + + + The maximum number of podcast items to store + + + + + m_autoFetchCheck + + + Automatically scan for updates + + + When checked, Amarok will automatically scan the podcast for updates + + + When checked, Amarok will automatically scan the podcast for updates + + + + + textLabel1 + + + Save location: + + + + + m_saveLocation + + + + + spacer17 + + + Vertical + + + MinimumExpanding + + + + 20 + 5 + + + + + + + + + + m_purgeCheck + toggled(bool) + m_purgeCountSpinBox + setEnabled(bool) + + + m_purgeCheck + toggled(bool) + m_purgeCountLabel + setEnabled(bool) + + + + + kurlrequester.h + klineedit.h + kpushbutton.h + + diff --git a/amarok/src/prettypopupmenu.cpp b/amarok/src/prettypopupmenu.cpp new file mode 100644 index 00000000..799cc693 --- /dev/null +++ b/amarok/src/prettypopupmenu.cpp @@ -0,0 +1,184 @@ +/*************************************************************************** + * Copyright (C) 1996-2000 the kicker authors. * + * Copyright (C) 2005 Mark Kretschmann * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Steet, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "prettypopupmenu.h" + +#include +#include +#include + +#include +#include +#include +#include + + +QImage PrettyPopupMenu::s_sidePixmap; +QColor PrettyPopupMenu::s_sidePixmapColor; + +//////////////////////////////////////////////////////////////////////////////// +// public +//////////////////////////////////////////////////////////////////////////////// + +PrettyPopupMenu::PrettyPopupMenu( QWidget* parent, const char* name ) + : KPopupMenu( parent, name ) +{ + // Must be initialized so that we know the size on first invocation + if ( s_sidePixmap.isNull() ) + generateSidePixmap(); +} + + +//////////////////////////////////////////////////////////////////////////////// +// private +//////////////////////////////////////////////////////////////////////////////// + +void +PrettyPopupMenu::generateSidePixmap() +{ + const QColor newColor = calcPixmapColor(); + + if ( newColor != s_sidePixmapColor ) { + s_sidePixmapColor = newColor; + s_sidePixmap.load( locate( "data","amarok/images/menu_sidepixmap.png" ) ); + KIconEffect::colorize( s_sidePixmap, newColor, 1.0 ); + } +} + +QRect +PrettyPopupMenu::sideImageRect() const +{ + return QStyle::visualRect( QRect( frameWidth(), frameWidth(), s_sidePixmap.width(), + height() - 2*frameWidth() ), this ); +} + +QColor +PrettyPopupMenu::calcPixmapColor() +{ + KConfig *config = KGlobal::config(); + config->setGroup("WM"); + QColor color = QApplication::palette().active().highlight(); +// QColor activeTitle = QApplication::palette().active().background(); +// QColor inactiveTitle = QApplication::palette().inactive().background(); + QColor activeTitle = config->readColorEntry("activeBackground", &color); + QColor inactiveTitle = config->readColorEntry("inactiveBackground", &color); + + // figure out which color is most suitable for recoloring to + int h1, s1, v1, h2, s2, v2, h3, s3, v3; + activeTitle.hsv(&h1, &s1, &v1); + inactiveTitle.hsv(&h2, &s2, &v2); + QApplication::palette().active().background().hsv(&h3, &s3, &v3); + + if ( (kAbs(h1-h3)+kAbs(s1-s3)+kAbs(v1-v3) < kAbs(h2-h3)+kAbs(s2-s3)+kAbs(v2-v3)) && + ((kAbs(h1-h3)+kAbs(s1-s3)+kAbs(v1-v3) < 32) || (s1 < 32)) && (s2 > s1)) + color = inactiveTitle; + else + color = activeTitle; + + // limit max/min brightness + int r, g, b; + color.rgb(&r, &g, &b); + int gray = qGray(r, g, b); + if (gray > 180) { + r = (r - (gray - 180) < 0 ? 0 : r - (gray - 180)); + g = (g - (gray - 180) < 0 ? 0 : g - (gray - 180)); + b = (b - (gray - 180) < 0 ? 0 : b - (gray - 180)); + } else if (gray < 76) { + r = (r + (76 - gray) > 255 ? 255 : r + (76 - gray)); + g = (g + (76 - gray) > 255 ? 255 : g + (76 - gray)); + b = (b + (76 - gray) > 255 ? 255 : b + (76 - gray)); + } + color.setRgb(r, g, b); + + return color; +} + +void +PrettyPopupMenu::setMinimumSize(const QSize & s) +{ + KPopupMenu::setMinimumSize(s.width() + s_sidePixmap.width(), s.height()); +} + +void +PrettyPopupMenu::setMaximumSize(const QSize & s) +{ + KPopupMenu::setMaximumSize(s.width() + s_sidePixmap.width(), s.height()); +} + +void +PrettyPopupMenu::setMinimumSize(int w, int h) +{ + KPopupMenu::setMinimumSize(w + s_sidePixmap.width(), h); +} + +void +PrettyPopupMenu::setMaximumSize(int w, int h) +{ + KPopupMenu::setMaximumSize(w + s_sidePixmap.width(), h); +} + +void PrettyPopupMenu::resizeEvent(QResizeEvent * e) +{ + KPopupMenu::resizeEvent( e ); + + setFrameRect( QStyle::visualRect( QRect( s_sidePixmap.width(), 0, + width() - s_sidePixmap.width(), height() ), this ) ); +} + +//Workaround Qt3.3.x sizing bug, by ensuring we're always wide enough. +void PrettyPopupMenu::resize( int width, int height ) +{ + width = kMax(width, maximumSize().width()); + KPopupMenu::resize(width, height); +} + +void +PrettyPopupMenu::paintEvent( QPaintEvent* e ) +{ + generateSidePixmap(); + + QPainter p( this ); + + QRect r = sideImageRect(); + r.setTop( r.bottom() - s_sidePixmap.height() ); + if ( r.intersects( e->rect() ) ) + { + QRect drawRect = r.intersect( e->rect() ).intersect( sideImageRect() ); + QRect pixRect = drawRect; + pixRect.moveBy( -r.left(), -r.top() ); + p.drawImage( drawRect.topLeft(), s_sidePixmap, pixRect ); + } + + p.setClipRegion( e->region() ); + + + //NOTE The order is important here. drawContents() must be called before drawPrimitive(), + // otherwise we get rendering glitches. + + drawContents( &p ); + + style().drawPrimitive( QStyle::PE_PanelPopup, &p, + QRect( 0, 0, width(), height() ), + colorGroup(), QStyle::Style_Default, + QStyleOption( frameWidth(), 0 ) ); +} + + +#include "prettypopupmenu.moc" diff --git a/amarok/src/prettypopupmenu.h b/amarok/src/prettypopupmenu.h new file mode 100644 index 00000000..848188f3 --- /dev/null +++ b/amarok/src/prettypopupmenu.h @@ -0,0 +1,74 @@ +/*************************************************************************** + * Copyright (C) 1996-2000 the kicker authors. * + * Copyright (C) 2005 Mark Kretschmann * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Steet, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef AMAROK_PRETTYPOPUPMENU_H +#define AMAROK_PRETTYPOPUPMENU_H + +#include + +#include +#include +#include + +class QSize; + +/** + * @class PrettyPopup + * @short KPopupMenu with a pixmap at the left side + * @author Mark Kretschmann + * + * This class behaves just like KPopupMenu, but adds a decorative banner + * graphic at the left border of the menu. + * + * The idea and the code are based on the Kicker start menu from KDE. + */ +class PrettyPopupMenu : public KPopupMenu +{ + Q_OBJECT + + public: + PrettyPopupMenu( QWidget *parent = 0, const char *name = 0 ); + + int sidePixmapWidth() const { return s_sidePixmap.width(); } + + private: + /** Loads and prepares the sidebar image */ + void generateSidePixmap(); + /** Returns the available size for the image */ + QRect sideImageRect() const; + /** Calculates a color that matches the current colorscheme */ + QColor calcPixmapColor(); + + void setMinimumSize( const QSize& s ); + void setMaximumSize( const QSize& s ); + void setMinimumSize( int w, int h ); + void setMaximumSize( int w, int h ); + + void resizeEvent( QResizeEvent* e ); + void resize( int width, int height ); + + void paintEvent( QPaintEvent* e ); + + static QImage s_sidePixmap; + static QColor s_sidePixmapColor; +}; + + +#endif /*AMAROK_PRETTYPOPUPMENU_H*/ diff --git a/amarok/src/qstringx.h b/amarok/src/qstringx.h new file mode 100644 index 00000000..81fc1adf --- /dev/null +++ b/amarok/src/qstringx.h @@ -0,0 +1,106 @@ +// Copyright (C) 2004 Shintaro Matsuoka +// Copyright (C) 2006 Martin Aumueller +// See COPYING file for licensing information + +#ifndef AMAROK_QSTRINGX_H +#define AMAROK_QSTRINGX_H + +#include +#include +#include +#include +#include + +namespace Amarok +{ + +class QStringx : public QString +{ +public: + QStringx() {}; + QStringx( QChar ch ) : QString( ch ) {}; + QStringx( const QString& s ) : QString( s ) {}; + QStringx( const QByteArray& ba ) : QString( ba ) {}; + QStringx( const QChar* unicode, uint length ) : QString( unicode, length ) {}; + QStringx( const char* str ) : QString( str ) {}; + virtual ~QStringx() {}; + + // the numbers following % obviously are not taken into account + QString args( const QStringList& args ) const + { + const QStringList text = QStringList::split( QRegExp( "%\\d+" ), *this, true ); + + QValueListConstIterator itrText = text.begin(); + QValueListConstIterator itrArgs = args.begin(); + QString merged = (*itrText); + ++itrText; + while ( itrText != text.end() && itrArgs != args.end() ) + { + merged += (*itrArgs) + (*itrText); + ++itrText; + ++itrArgs; + } + + Q_ASSERT( itrText == text.end() && itrArgs == args.end() ); + + return merged; + } + + // %something gets replaced by the value corresponding to key "something" in args + QString namedArgs( const QMap args, bool opt=false ) const + { + QRegExp rxArg( "%[a-zA-Z0-9]+" ); + + QString result; + int start = 0; + for( int pos = rxArg.search( *this ); + pos != -1; + pos = rxArg.search( *this, start ) ) + { + int len = rxArg.matchedLength(); + QString p = rxArg.capturedTexts()[0].mid(1, len-1); + + result += mid( start, pos-start ); + if( args[p] != QString::null ) + result += args[p]; + else if( opt ) + return QString(); + + start = pos + len; + } + result += mid( start ); + + return result; + } + + // %something gets replaced by the value corresponding to key "something" in args, + // however, if key "something" is not available, + // then replace everything within surrounding { } by an empty string + QString namedOptArgs( const QMap args ) const + { + QRegExp rxOptArg( "\\{.*%[a-zA-Z0-9_]+.*\\}" ); + rxOptArg.setMinimal( true ); + + QString result; + int start = 0; + for( int pos = rxOptArg.search( *this ); + pos != -1; + pos = rxOptArg.search( *this, start ) ) + { + int len = rxOptArg.matchedLength(); + QStringx opt = rxOptArg.capturedTexts()[0].mid(1, len-2); + + result += QStringx(mid( start, pos-start )).namedArgs( args ); + result += opt.namedArgs( args, true ); + + start = pos + len; + } + result += QStringx( mid( start ) ).namedArgs( args ); + + return result; + } +}; + +} // namespace Amarok + +#endif // AMAROK_QSTRINGX_H diff --git a/amarok/src/queuemanager.cpp b/amarok/src/queuemanager.cpp new file mode 100644 index 00000000..dd2708a6 --- /dev/null +++ b/amarok/src/queuemanager.cpp @@ -0,0 +1,536 @@ +/*************************************************************************** + * copyright : (C) 2005 Seb Ruiz * + **************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#define DEBUG_PREFIX "QueueManager" +#include "debug.h" + +#include "amarok.h" +#include "amarokconfig.h" //check if dynamic mode +#include "playlist.h" +#include "queuemanager.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +////////////////////////////////////////////////////////////////////////////////////////// +/// CLASS QueueItem +////////////////////////////////////////////////////////////////////////////////////////// +void +QueueItem::paintCell( QPainter *p, const QColorGroup &cg, int column, int width, int align ) +{ + KListViewItem::paintCell( p, cg, column, width, align ); + + QString str = QString::number( ( static_cast( listView() ) )->itemIndex( this ) + 1 ); + + //draw the symbol's outline + uint fw = p->fontMetrics().width( str ) + 2; + const uint w = 16; //keep this even + const uint h = height() - 2; + + p->setBrush( cg.highlight() ); + p->setPen( cg.highlight().dark() ); //TODO blend with background color + p->drawEllipse( width - fw - w/2, 1, w, h ); + p->drawRect( width - fw, 1, fw, h ); + p->setPen( cg.highlight() ); + p->drawLine( width - fw, 2, width - fw, h - 1 ); + + fw += 2; //add some more padding + p->setPen( cg.highlightedText() ); + p->drawText( width - fw, 2, fw, h-1, Qt::AlignCenter, str ); +} + + +////////////////////////////////////////////////////////////////////////////////////////// +/// CLASS QueueList +////////////////////////////////////////////////////////////////////////////////////////// + +QueueList::QueueList( QWidget *parent, const char *name ) + : KListView( parent, name ) +{ + addColumn( i18n("Name") ); + setResizeMode( QListView::LastColumn ); + setSelectionMode( QListView::Extended ); + setSorting( -1 ); + + setAcceptDrops( true ); + setDragEnabled( true ); + setDropVisualizer( true ); //the visualizer (a line marker) is drawn when dragging over tracks + setDropVisualizerWidth( 3 ); +} + +void +QueueList::viewportPaintEvent( QPaintEvent *e ) +{ + if( e ) KListView::viewportPaintEvent( e ); + + if( !childCount() && e ) + { + QPainter p( viewport() ); + QString minimumText(i18n( + "
    " + "

    The Queue Manager

    " + "To create a queue, " + "drag tracks from the playlist, and " + "drop them here.

    " + "Drag and drop tracks within the manager to resort queue orders." + "
    " ) ); + QSimpleRichText t( minimumText, QApplication::font() ); + + if ( t.width()+30 >= viewport()->width() || t.height()+30 >= viewport()->height() ) + //too big, giving up + return; + + const uint w = t.width(); + const uint h = t.height(); + const uint x = (viewport()->width() - w - 30) / 2 ; + const uint y = (viewport()->height() - h - 30) / 2 ; + + p.setBrush( colorGroup().background() ); + p.drawRoundRect( x, y, w+30, h+30, (8*200)/w, (8*200)/h ); + t.draw( &p, x+15, y+15, QRect(), colorGroup() ); + } +} + +void +QueueList::keyPressEvent( QKeyEvent *e ) +{ + switch( e->key() ) { + + case Key_Delete: //remove + removeSelected(); + break; + + case CTRL+Key_Up: + moveSelectedUp(); + break; + + case CTRL+Key_Down: + moveSelectedDown(); + break; + } +} + +bool +QueueList::hasSelection() +{ + QListViewItemIterator it( this, QListViewItemIterator::Selected ); + + if( !it.current() ) + return false; + + return true; +} + +QPtrList +QueueList::selectedItems() +{ + QPtrList selected; + QListViewItemIterator it( this, QListViewItemIterator::Selected ); + + for( ; it.current(); ++it ) + selected.append( it.current() ); + + return selected; +} + +void +QueueList::moveSelectedUp() // SLOT +{ + QPtrList selected = selectedItems(); + bool item_moved = false; + + // Whilst it would be substantially faster to do this: ((*it)->itemAbove())->move( *it ), + // this would only work for sequentially ordered items + for( QListViewItem *item = selected.first(); item; item = selected.next() ) + { + if( item == itemAtIndex(0) ) + continue; + + QListViewItem *after; + + item == itemAtIndex(1) ? + after = 0: + after = ( item->itemAbove() )->itemAbove(); + + moveItem( item, 0, after ); + item_moved = true; + } + + ensureItemVisible( selected.first() ); + + if( item_moved ) + emit changed(); +} + +void +QueueList::moveSelectedDown() // SLOT +{ + QPtrList list = selectedItems(); + bool item_moved = false; + + for( QListViewItem *item = list.last(); item; item = list.prev() ) + { + QListViewItem *after = item->nextSibling(); + + if( !after ) + continue; + + moveItem( item, 0, after ); + item_moved = true; + } + + ensureItemVisible( list.last() ); + + if( item_moved ) + emit changed(); +} + +void +QueueList::removeSelected() //SLOT +{ + setSelected( currentItem(), true ); + + bool item_removed = false; + QPtrList selected = selectedItems(); + + for( QListViewItem *item = selected.first(); item; item = selected.next() ) + { + delete item; + item_removed = true; + } + + if( isEmpty() ) + QueueManager::instance()->updateButtons(); + + if( item_removed ) + emit changed(); +} + +void +QueueList::clear() // SLOT +{ + KListView::clear(); + emit changed(); +} + +void +QueueList::contentsDragEnterEvent( QDragEnterEvent *e ) +{ + debug() << "contentsDrageEnterEvent()" << endl; + e->accept( e->source() == reinterpret_cast( Playlist::instance() )->viewport() ); +} + +void +QueueList::contentsDragMoveEvent( QDragMoveEvent *e ) +{ + debug() << "contentsDrageMoveEvent()" << endl; + KListView::contentsDragMoveEvent( e ); + + // Must be overloaded for dnd to work + e->accept( ( e->source() == reinterpret_cast( Playlist::instance() )->viewport() ) || + e->source() == viewport() ); +} + +void +QueueList::contentsDropEvent( QDropEvent *e ) +{ + debug() << "contentsDragDropEvent()" << endl; + if( e->source() == viewport() ) + { + KListView::contentsDropEvent( e ); + emit changed(); + } + else + { + QListViewItem *parent = 0; + QListViewItem *after; + + findDrop( e->pos(), parent, after ); + + QueueManager::instance()->addItems( after ); + } +} + + +////////////////////////////////////////////////////////////////////////////////////////// +/// CLASS QueueManager +////////////////////////////////////////////////////////////////////////////////////////// + +QueueManager *QueueManager::s_instance = 0; + +QueueManager::QueueManager( QWidget *parent, const char *name ) + : KDialogBase( KDialogBase::Swallow, 0, parent, name, false, 0, Ok|Apply|Cancel ) +{ + s_instance = this; + + // Gives the window a small title bar, and skips a taskbar entry + KWin::setType( winId(), NET::Utility ); + KWin::setState( winId(), NET::SkipTaskbar ); + + kapp->setTopWidget( this ); + setCaption( kapp->makeStdCaption( i18n("Queue Manager") ) ); + setInitialSize( QSize( 400, 260 ) ); + + QVBox *mainBox = new QVBox( this ); + setMainWidget( mainBox ); + + QHBox *box = new QHBox( mainWidget() ); + box->setSpacing( 5 ); + m_listview = new QueueList( box ); + + QVBox *buttonBox = new QVBox( box ); + m_up = new KPushButton( KGuiItem( QString::null, "up" ), buttonBox ); + m_down = new KPushButton( KGuiItem( QString::null, "down" ), buttonBox ); + m_remove = new KPushButton( KGuiItem( QString::null, Amarok::icon( "dequeue_track" ) ), buttonBox ); + m_add = new KPushButton( KGuiItem( QString::null, Amarok::icon( "queue_track" ) ), buttonBox ); + m_clear = new KPushButton( KGuiItem( QString::null, Amarok::icon( "playlist_clear" ) ), buttonBox ); + + QToolTip::add( m_up, i18n( "Move up" ) ); + QToolTip::add( m_down, i18n( "Move down" ) ); + QToolTip::add( m_remove, i18n( "Remove" ) ); + QToolTip::add( m_add, i18n( "Enqueue track" ) ); + QToolTip::add( m_clear, i18n( "Clear queue" ) ); + + m_up->setEnabled( false ); + m_down->setEnabled( false ); + m_remove->setEnabled( false ); + m_add->setEnabled( false ); + m_clear->setEnabled( false ); + + connect( m_up, SIGNAL( clicked() ), m_listview, SLOT( moveSelectedUp() ) ); + connect( m_down, SIGNAL( clicked() ), m_listview, SLOT( moveSelectedDown() ) ); + connect( m_remove, SIGNAL( clicked() ), this, SLOT( removeSelected() ) ); + connect( m_add, SIGNAL( clicked() ), this, SLOT( addItems() ) ); + connect( m_clear, SIGNAL( clicked() ), m_listview, SLOT( clear() ) ); + + Playlist *pl = Playlist::instance(); + connect( pl, SIGNAL( selectionChanged() ), SLOT( updateButtons() ) ); + connect( m_listview, SIGNAL( selectionChanged() ), SLOT( updateButtons() ) ); + connect( pl, SIGNAL( queueChanged(const PLItemList &, const PLItemList &) ), + SLOT( changeQueuedItems(const PLItemList &, const PLItemList &) ) ); + connect( this, SIGNAL( applyClicked()), SLOT( applyNow() ) ); + connect( m_listview, SIGNAL( changed() ), this, SLOT ( changed() ) ); + s_instance->enableButtonApply(false); + + insertItems(); +} + +QueueManager::~QueueManager() +{ + s_instance = 0; +} + +void +QueueManager::applyNow() +{ + Playlist *pl = Playlist::instance(); + pl->changeFromQueueManager( newQueue() ); + s_instance->enableButtonApply(false); +} + +void +QueueManager::addItems( QListViewItem *after ) +{ + /* + HACK!!!!! We can know which items where dragged since they should still be selected + I do this, because: + - Dragging items from the playlist provides urls + - Providing urls, requires iterating through the entire list in order to find which + item was selected. Possibly a very expensive task - worst case: O(n) + - After a drag, those items are still selected in the playlist, so we can find out + which PlaylistItems were dragged by selectedItems(); + */ + if( !after ) + after = m_listview->lastChild(); + + QPtrList list = Playlist::instance()->selectedItems(); + + bool item_added = false; + for( QListViewItem *item = list.first(); item; item = list.next() ) + { + #define item static_cast(item) + QValueList current = m_map.values(); + + if( current.find( item ) == current.end() ) //avoid duplication + { + QString title = i18n("%1 - %2").arg( item->artist(), item->title() ); + + after = new QueueItem( m_listview, after, title ); + m_map[ after ] = item; + item_added = true; + } + #undef item + } + + if( item_added ) + emit m_listview->changed(); +} + +void +QueueManager::changeQueuedItems( const PLItemList &in, const PLItemList &out ) //SLOT +{ + QPtrListIterator it(in); + for( it.toFirst(); it; ++it ) addQueuedItem( *it ); + it = QPtrListIterator(out); + for( it.toFirst(); it; ++it ) removeQueuedItem( *it ); +} + +void +QueueManager::addQueuedItem( PlaylistItem *item ) +{ + Playlist *pl = Playlist::instance(); + if( !pl ) return; //should never happen + + const int index = pl->m_nextTracks.findRef( item ); + + QListViewItem *after; + if( !index ) after = 0; + else + { + int find = m_listview->childCount(); + if( index - 1 <= find ) + find = index - 1; + after = m_listview->itemAtIndex( find ); + } + + QValueList current = m_map.values(); + QValueListIterator newItem = current.find( item ); + + QString title = i18n("%1 - %2").arg( item->artist(), item->title() ); + + if( newItem == current.end() ) //avoid duplication + { + after = new QueueItem( m_listview, after, title ); + m_map[ after ] = item; + } +} + +void +QueueManager::removeQueuedItem( PlaylistItem *item ) +{ + Playlist *pl = Playlist::instance(); + if( !pl ) return; //should never happen + + QValueList current = m_map.values(); + QValueListIterator newItem = current.find( item ); + + QString title = i18n("%1 - %2").arg( item->artist(), item->title() ); + + QListViewItem *removableItem = m_listview->findItem( title, 0 ); + + if( removableItem ) + { + //Remove the key from the map, so we can re-queue the item + QMapIterator end( m_map.end() ); + for( QMapIterator it = m_map.begin(); it != end; ++it ) + { + if( it.data() == item ) + { + m_map.remove( it ); + + //Remove the item from the queuelist + m_listview->takeItem( removableItem ); + delete removableItem; + return; + } + } + } +} + +/// Playlist uses this to determine the altered queue and reflect the changes. + +QPtrList +QueueManager::newQueue() +{ + QPtrList queue; + for( QListViewItem *key = m_listview->firstChild(); key; key = key->nextSibling() ) + { + queue.append( m_map[ key ] ); + } + return queue; +} + +void +QueueManager::insertItems() +{ + QPtrList list = Playlist::instance()->m_nextTracks; + QListViewItem *last = 0; + + for( PlaylistItem *item = list.first(); item; item = list.next() ) + { + QString title = i18n("%1 - %2").arg( item->artist(), item->title() ); + + last = new QueueItem( m_listview, last, title ); + m_map[ last ] = item; + } + + updateButtons(); +} + +void +QueueManager::changed() // SLOT +{ + s_instance->enableButtonApply(true); +} + + +void +QueueManager::removeSelected() //SLOT +{ + QPtrList selected = m_listview->selectedItems(); + + bool item_removed = false; + + for( QListViewItem *item = selected.first(); item; item = selected.next() ) + { + //Remove the key from the map, so we can re-queue the item + QMapIterator it = m_map.find( item ); + + m_map.remove( it ); + + //Remove the item from the queuelist + m_listview->takeItem( item ); + delete item; + item_removed = true; + } + + if( item_removed ) + emit m_listview->changed(); +} + +void +QueueManager::updateButtons() //SLOT +{ + const bool enablePL = !Playlist::instance()->selectedItems().isEmpty(); + const bool emptyLV = m_listview->isEmpty(); + const bool enableQL = m_listview->hasSelection() && !emptyLV; + + m_up->setEnabled( enableQL ); + m_down->setEnabled( enableQL ); + m_remove->setEnabled( enableQL ); + m_add->setEnabled( enablePL ); + m_clear->setEnabled( !emptyLV ); +} + +#include "queuemanager.moc" + diff --git a/amarok/src/queuemanager.h b/amarok/src/queuemanager.h new file mode 100644 index 00000000..fe721620 --- /dev/null +++ b/amarok/src/queuemanager.h @@ -0,0 +1,106 @@ +/*************************************************************************** + * copyright : (C) 2005 Seb Ruiz * + **************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef AMAROK_QUEUEMANAGER_H +#define AMAROK_QUEUEMANAGER_H + +#include "playlistitem.h" + +#include //baseclass +#include //baseclass + +#include + +class KPushButton; + +class QueueItem : public KListViewItem +{ + public: + QueueItem( QListView *parent, QListViewItem *after, QString t ) + : KListViewItem( parent, after, t ) + { }; + + void paintCell( QPainter *p, const QColorGroup &cg, int column, int width, int align ); + +}; + +class QueueList : public KListView +{ + Q_OBJECT + + friend class QueueManager; + + public: + QueueList( QWidget *parent, const char *name = 0 ); + ~QueueList() {}; + + bool hasSelection(); + bool isEmpty() { return ( childCount() == 0 ); } + QPtrList selectedItems(); + + public slots: + void moveSelectedUp(); + void moveSelectedDown(); + void removeSelected(); + virtual void clear(); + + private: + void contentsDragEnterEvent( QDragEnterEvent *e ); + void contentsDragMoveEvent( QDragMoveEvent* e ); + void contentsDropEvent( QDropEvent *e ); + void keyPressEvent( QKeyEvent *e ); + void viewportPaintEvent( QPaintEvent* ); + + signals: + void changed(); +}; + +class QueueManager : public KDialogBase +{ + Q_OBJECT + + public: + QueueManager( QWidget *parent = 0, const char *name = 0 ); + ~QueueManager(); + + QPtrList newQueue(); + + static QueueManager *instance() { return s_instance; } + + public slots: + void applyNow(); + void addItems( QListViewItem *after = 0 ); /// For the add button (uses selected playlist tracks) + void changeQueuedItems( const PLItemList &in, const PLItemList &out ); /// For keeping queue/dequeue in sync + void updateButtons(); + + private slots: + void removeSelected(); + void changed(); + + private: + void insertItems(); + void addQueuedItem( PlaylistItem *item ); + void removeQueuedItem( PlaylistItem *item ); + + QMap m_map; + QueueList *m_listview; + KPushButton *m_up; + KPushButton *m_down; + KPushButton *m_remove; + KPushButton *m_add; + KPushButton *m_clear; + + static QueueManager *s_instance; +}; + +#endif /* AMAROK_QUEUEMANAGER_H */ diff --git a/amarok/src/refreshimages.cpp b/amarok/src/refreshimages.cpp new file mode 100644 index 00000000..61d3897b --- /dev/null +++ b/amarok/src/refreshimages.cpp @@ -0,0 +1,163 @@ +// (c) 2005 Ian Monroe +// See COPYING file for licensing information. + +#define DEBUG_PREFIX "RefreshImages" + +#include "amarok.h" +#include "collectiondb.h" +#include "debug.h" +#include "refreshimages.h" +#include "statusbar.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + + +RefreshImages::RefreshImages() +{ + //"SELECT asin, locale, filename FROM amazon WHERE refetchdate > %1 ;" + const QStringList staleImages = CollectionDB::instance()->staleImages(); + QStringList::ConstIterator it = staleImages.begin(); + QStringList::ConstIterator end = staleImages.end(); + + while( it != end ) + { + QString asin=*it; + it++; + QString locale = *it; + it++; + QString md5sum = *it; + if ( asin.isEmpty() || locale.isEmpty() || md5sum.isEmpty() ) + { + //somehow we have entries without ASIN + if ( !md5sum.isEmpty() ) //I've never seen this, just to be sure + CollectionDB::instance()->removeInvalidAmazonInfo(md5sum); + it++; + if ( it==end ) + deleteLater(); + + continue; + } + + QString url = + QString("http://webservices.amazon.%1/onca/xml?Service=AWSECommerceService&SubscriptionId=%2&Operation=ItemLookup&ItemId=%3&ResponseGroup=Small,Images") + .arg(localeToTLD(locale)) + .arg("0RQSQ0B8CRY7VX2VF3G2") //Ian Monroe + .arg(asin); + + debug() << url << endl; + + KIO::TransferJob* job = KIO::storedGet( url, false, false ); + KIO::Scheduler::scheduleJob( job ); + + //Amarok::StatusBar::instance()->newProgressOperation( job ); + job->setName( md5sum.ascii() ); + it++; //iterate to the next set + + m_jobInfo[md5sum] = JobInfo( asin, locale, it == end ); + connect( job, SIGNAL( result( KIO::Job* ) ), SLOT( finishedXmlFetch( KIO::Job* ) ) ); + } +} + +void +RefreshImages::finishedXmlFetch( KIO::Job* xmlJob ) //SLOT +{ + if ( xmlJob->error() ) + { + Amarok::StatusBar::instance()->shortMessage( i18n( "There was an error communicating with Amazon." ) ); + if ( m_jobInfo[ xmlJob->name() ].m_last ) + deleteLater(); + + return; + } + + KIO::StoredTransferJob* const storedJob = static_cast( xmlJob ); + const QString xml = QString::fromUtf8( storedJob->data().data(), storedJob->data().size() ); + + QDomDocument doc; + if ( !doc.setContent( xml ) ) + return; + + QStringList imageSizes; + imageSizes << "LargeImage" << "MediumImage" << "SmallImage"; + QString imageUrl; + foreach( imageSizes ) + { + QDomNode imageNode = doc.documentElement() + .namedItem( "Items" ) + .namedItem( "Item" ) + .namedItem( *it ); + if ( !imageNode.isNull() ) + { + imageUrl = imageNode.namedItem( "URL" ).firstChild().toText().data(); + if( !imageUrl.isEmpty() ) + break; + } + } + debug() << imageUrl << endl; + KURL testUrl( imageUrl ); + if( !testUrl.isValid() ) //KIO crashs on empty strings!!! + { + //Amazon sometimes takes down covers + CollectionDB::instance()->removeInvalidAmazonInfo(xmlJob->name()); + return; + } + + KIO::TransferJob* imageJob = KIO::storedGet( imageUrl, false, false ); + KIO::Scheduler::scheduleJob(imageJob); + //Amarok::StatusBar::instance()->newProgressOperation( imageJob ); + imageJob->setName(xmlJob->name()); + //get the URL of the detail page + m_jobInfo[xmlJob->name()].m_detailUrl = doc.documentElement() + .namedItem( "Items" ) + .namedItem( "Item" ) + .namedItem( "DetailPageURL" ).firstChild().toText().data(); + connect( imageJob, SIGNAL( result(KIO::Job*) ), SLOT( finishedImageFetch(KIO::Job*) ) ); +} + +void RefreshImages::finishedImageFetch(KIO::Job* imageJob) +{ + if( imageJob->error() ) { + Amarok::StatusBar::instance()->shortMessage(i18n("There was an error communicating with Amazon.")); + if(m_jobInfo[imageJob->name()].m_last) + deleteLater(); + + return; + } + QImage img; + img.loadFromData(static_cast(imageJob)->data()); + img.setText( "amazon-url", 0, m_jobInfo[imageJob->name()].m_detailUrl); + img.save( Amarok::saveLocation("albumcovers/large/") + imageJob->name(), "PNG"); + + CollectionDB::instance()->newAmazonReloadDate( m_jobInfo[imageJob->name()].m_asin + , m_jobInfo[imageJob->name()].m_locale + , imageJob->name()); + + if(m_jobInfo[imageJob->name()].m_last) + deleteLater(); +} + +QString RefreshImages::localeToTLD(const QString& locale) +{ + if(locale=="us") + return "com"; + else if(locale=="jp") + return "co.jp"; + else if(locale=="uk") + return "co.uk"; + else + return locale; +} + +#include "refreshimages.moc" diff --git a/amarok/src/refreshimages.h b/amarok/src/refreshimages.h new file mode 100644 index 00000000..02064fdb --- /dev/null +++ b/amarok/src/refreshimages.h @@ -0,0 +1,38 @@ +// (c) 2005 Ian Monroe +// See COPYING file for licensing information. + +#ifndef AMAROK_REFRESHIMAGES_H +#define AMAROK_REFRESHIMAGES_H + +#include +namespace KIO { + class StoredTransferJob; + class Job; +} +class QStringList; + +class JobInfo +{ + public: + JobInfo() : m_last(false) { } //for QMap + JobInfo(const QString& asin, const QString& locale, bool last) : + m_asin(asin), m_locale(locale), m_last(last) { } + QString m_asin; + QString m_locale; + QString m_detailUrl; + bool m_last; +}; + +class RefreshImages : public QObject +{ + Q_OBJECT + public: + RefreshImages(); + private slots: + void finishedXmlFetch( KIO::Job* ); + void finishedImageFetch( KIO::Job* ); + private: + static QString localeToTLD(const QString& locale); + QMap m_jobInfo; +}; +#endif diff --git a/amarok/src/scancontroller.cpp b/amarok/src/scancontroller.cpp new file mode 100644 index 00000000..7a981dd9 --- /dev/null +++ b/amarok/src/scancontroller.cpp @@ -0,0 +1,553 @@ +/*************************************************************************** + * Copyright (C) 2003-2005 by The Amarok Developers * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Steet, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#define DEBUG_PREFIX "ScanController" + +#include "amarok.h" +#include "amarokconfig.h" +#include "collectiondb.h" +#include "debug.h" +#include "metabundle.h" +#include "mountpointmanager.h" +#include "playlist.h" +#include "playlistbrowser.h" +#include "scancontroller.h" +#include "statusbar.h" + +#include +#include +#include + +#include +#include +#include +#include + +//////////////////////////////////////////////////////////////////////////////// +// class ScanController +//////////////////////////////////////////////////////////////////////////////// + +ScanController* ScanController::currController = 0; + +ScanController* ScanController::instance() +{ + return currController; +} + +void ScanController::setInstance( ScanController* curr ) +{ + currController = curr; +} + +ScanController::ScanController( CollectionDB* parent, bool incremental, const QStringList& folders ) + : DependentJob( parent, "CollectionScanner" ) + , QXmlDefaultHandler() + , m_scanner( new Amarok::ProcIO() ) + , m_folders( QDeepCopy( folders ) ) + , m_incremental( incremental ) + , m_hasChanged( false ) + , m_source( new QXmlInputSource() ) + , m_reader( new QXmlSimpleReader() ) + , m_waitingBundle( 0 ) + , m_lastCommandPaused( false ) + , m_isPaused( false ) + , m_tablesCreated( false ) + , m_scanCount( 0 ) +{ + DEBUG_BLOCK + + ScanController::setInstance( this ); + m_reader->setContentHandler( this ); + m_reader->parse( m_source, true ); + + connect( this, SIGNAL( scanDone( bool ) ), MountPointManager::instance(), SLOT( updateStatisticsURLs( bool ) ) ); + + connect( m_scanner, SIGNAL( readReady( KProcIO* ) ), SLOT( slotReadReady() ) ); + + *m_scanner << "amarokcollectionscanner"; + *m_scanner << "--nocrashhandler"; // We want to be able to catch SIGSEGV + + // KProcess must be started from the GUI thread, so we're invoking the scanner + // here in the ctor: + if( incremental ) + { + setDescription( i18n( "Updating Collection" ) ); + initIncremental(); + } + else + { + setDescription( i18n( "Building Collection" ) ); + *m_scanner << "-p"; + if( AmarokConfig::scanRecursively() ) *m_scanner << "-r"; + *m_scanner << m_folders; + m_scanner->start(); + } +} + + +ScanController::~ScanController() +{ + DEBUG_BLOCK + + if( !isAborted() && !m_crashedFiles.empty() ) { + KMessageBox::information( 0, i18n( "

    The Collection Scanner was unable to process these files:

    " ) + + "" + m_crashedFiles.join( "
    " ) + "
    ", + i18n( "Collection Scan Report" ) ); + } + else if( m_crashedFiles.size() >= MAX_RESTARTS ) { + KMessageBox::error( 0, i18n( "

    Sorry, the Collection Scan was aborted, since too many problems were encountered.

    " ) + + "

    Advice: A common source for this problem is a broken 'TagLib' package on your computer. Replacing this package may help fixing the issue.

    " + "

    The following files caused problems:

    " + + "" + m_crashedFiles.join( "
    " ) + "
    ", + i18n( "Collection Scan Error" ) ); + } + + m_scanner->kill(); + delete m_scanner; + delete m_reader; + delete m_source; + ScanController::setInstance( 0 ); +} + + +// Cause the CollectionDB to emit fileDeleted() signals +void +ScanController::completeJob( void ) +{ + m_fileMapsMutex.lock(); + + QMap::Iterator it; + if( !m_incremental ) + { + CollectionDB::instance()->emitFilesAdded( m_filesAdded ); + } + else + { + for( it = m_filesAdded.begin(); it != m_filesAdded.end(); ++it ) + { + if( m_filesDeleted.contains( it.key() ) ) + m_filesDeleted.remove( it.key() ); + } + for( it = m_filesAdded.begin(); it != m_filesAdded.end(); ++it ) + CollectionDB::instance()->emitFileAdded( it.data(), it.key() ); + for( it = m_filesDeleted.begin(); it != m_filesDeleted.end(); ++it ) + CollectionDB::instance()->emitFileDeleted( it.data(), it.key() ); + } + + m_fileMapsMutex.unlock(); + + emit scanDone( !m_incremental || m_hasChanged ); + + ThreadManager::DependentJob::completeJob(); +} + + +/** + * The Incremental Scanner works as follows: Here we check the mtime of every directory in the "directories" + * table and store all changed directories in m_folders. + * + * These directories are then scanned in CollectionReader::doJob(), with m_recursively set according to the + * user's preference, so the user can add directories or whole directory trees, too. Since we don't want to + * rescan unchanged subdirectories, CollectionReader::readDir() checks if we are scanning recursively and + * prevents that. + */ +void +ScanController::initIncremental() +{ + DEBUG_BLOCK + + connect( CollectionDB::instance(), + SIGNAL( fileMoved( const QString &, const QString & ) ), + SLOT( slotFileMoved( const QString &, const QString & ) ) ); + connect( CollectionDB::instance(), + SIGNAL( fileMoved( const QString &, const QString &, const QString & ) ), + SLOT( slotFileMoved( const QString &, const QString & ) ) ); + + IdList list = MountPointManager::instance()->getMountedDeviceIds(); + QString deviceIds; + foreachType( IdList, list ) + { + if ( !deviceIds.isEmpty() ) deviceIds += ','; + deviceIds += QString::number(*it); + } + + const QStringList values = CollectionDB::instance()->query( + QString( "SELECT deviceid, dir, changedate FROM directories WHERE deviceid IN (%1);" ) + .arg( deviceIds ) ); + + foreach( values ) + { + int id = (*it).toInt(); + const QString folder = MountPointManager::instance()->getAbsolutePath( id, (*++it) ); + const QString mtime = *++it; + + const QFileInfo info( folder ); + if( info.exists() ) + { + if( info.lastModified().toTime_t() != mtime.toUInt() ) + { + m_folders << folder; + debug() << "Collection dir changed: " << folder << endl; + } + } + else + { + // this folder has been removed + m_folders << folder; + debug() << "Collection dir removed: " << folder << endl; + } + + kapp->processEvents(); // Don't block the GUI + } + + if ( !m_folders.isEmpty() ) + { + debug() << "Collection was modified." << endl; + m_hasChanged = true; + Amarok::StatusBar::instance()->shortMessage( i18n( "Updating Collection..." ) ); + + // Start scanner process + if( AmarokConfig::scanRecursively() ) *m_scanner << "-r"; + *m_scanner << "-i"; + *m_scanner << m_folders; + m_scanner->start(); + } +} + + +bool +ScanController::doJob() +{ + DEBUG_BLOCK + + if( !CollectionDB::instance()->isConnected() ) + return false; + if( m_incremental && !m_hasChanged ) + return true; + + CollectionDB::instance()->createTables( true ); + m_tablesCreated = true; + + //For a full rescan, we might not have cleared tags table (for devices not plugged + //in), so preserve the necessary other tables (eg artist) + CollectionDB::instance()->prepareTempTables(); + + CollectionDB::instance()->invalidateArtistAlbumCache(); + +main_loop: + uint delayCount = 100; + + /// Main Loop + while( !isAborted() ) { + if( m_xmlData.isNull() ) { + if( !m_scanner->isRunning() ) + delayCount--; + // Wait a bit after process has exited, so that we have time to parse all data + if( delayCount == 0 ) + break; + msleep( 15 ); + } + else { + m_dataMutex.lock(); + + QDeepCopy data = m_xmlData; + m_source->setData( data ); + m_xmlData = QString::null; + + m_dataMutex.unlock(); + + if( !m_reader->parseContinue() ) + ::warning() << "parseContinue() failed: " << errorString() << endl << data << endl; + } + } + + if( !isAborted() ) { + if( m_scanner->normalExit() && !m_scanner->signalled() ) { + CollectionDB::instance()->sanitizeCompilations(); + if ( m_incremental ) { + m_foldersToRemove += m_folders; + foreach( m_foldersToRemove ) { + m_fileMapsMutex.lock(); + CollectionDB::instance()->removeSongsInDir( *it, &m_filesDeleted ); + m_fileMapsMutex.unlock(); + CollectionDB::instance()->removeDirFromCollection( *it ); + } + CollectionDB::instance()->removeOrphanedEmbeddedImages(); + } + else + CollectionDB::instance()->clearTables( false ); // empty permanent tables + + CollectionDB::instance()->copyTempTables(); // copy temp into permanent tables + + //Clean up unused entries in the main tables (eg artist, composer) + CollectionDB::instance()->deleteAllRedundant( "artist" ); + CollectionDB::instance()->deleteAllRedundant( "composer" ); + CollectionDB::instance()->deleteAllRedundant( "year" ); + CollectionDB::instance()->deleteAllRedundant( "genre" ); + CollectionDB::instance()->deleteAllRedundant( "album" ); + + //Remove free space and fragmentation in the DB. Used to run on shutdown, but + //that took too long, sometimes causing Amarok to be killed. + CollectionDB::instance()->vacuum(); + + } + else { + if( m_crashedFiles.size() <= MAX_RESTARTS || + m_crashedFiles.size() <= (m_scanCount * MAX_FAILURE_PERCENTAGE) / 100 ) { + kapp->postEvent( this, new RestartEvent() ); + sleep( 3 ); + } + else + m_aborted = true; + + goto main_loop; + } + } + + if( CollectionDB::instance()->isConnected() ) + { + m_tablesCreated = false; + CollectionDB::instance()->dropTables( true ); // drop temp tables + } + + setProgress100Percent(); + return !isAborted(); +} + + +void +ScanController::slotReadReady() +{ + QString line; + + m_dataMutex.lock(); + + while( m_scanner->readln( line, true, 0 ) != -1 ) { + if( !line.startsWith( "exepath=" ) ) // skip binary location info from scanner + m_xmlData += line; + } + m_dataMutex.unlock(); +} + +bool +ScanController::requestPause() +{ + DEBUG_BLOCK + debug() << "Attempting to pause the collection scanner..." << endl; + DCOPRef dcopRef( "amarokcollectionscanner", "scanner" ); + m_lastCommandPaused = true; + return dcopRef.send( "pause" ); +} + +bool +ScanController::requestUnpause() +{ + DEBUG_BLOCK + debug() << "Attempting to unpause the collection scanner..." << endl; + DCOPRef dcopRef( "amarokcollectionscanner", "scanner" ); + m_lastCommandPaused = false; + return dcopRef.send( "unpause" ); +} + +void +ScanController::requestAcknowledged() +{ + DEBUG_BLOCK + if( m_waitingBundle ) + m_waitingBundle->scannerAcknowledged(); + if( m_lastCommandPaused ) + m_isPaused = true; + else + m_isPaused = false; +} + +void +ScanController::slotFileMoved( const QString &/*src*/, const QString &/*dest*/) +{ + //why is this needed? QBob, take a look at this + /* + if( m_incremental ) // pedantry + { + m_fileMapsMutex.lock(); + m_filesFound[ src ] = true; + m_fileMapsMutex.unlock(); + } + */ +} + +void +ScanController::notifyThisBundle( MetaBundle* bundle ) +{ + DEBUG_BLOCK + m_waitingBundle = bundle; + debug() << "will notify " << m_waitingBundle << endl; +} + +bool +ScanController::startElement( const QString&, const QString& localName, const QString&, const QXmlAttributes& attrs ) +{ + // List of entity names: + // + // itemcount Number of files overall + // folder Folder which is being processed + // dud Invalid audio file + // tags Valid audio file with metadata + // playlist Playlist file + // image Cover image + // compilation Folder to check for compilation + // filesize Size of the track in bytes + + + if( localName == "dud" || localName == "tags" || localName == "playlist" ) { + incrementProgress(); + } + + if( localName == "itemcount") { + const int totalSteps = attrs.value( "count" ).toInt(); + debug() << "itemcount event: " << totalSteps << endl; + setProgressTotalSteps( totalSteps ); + } + + else if( localName == "tags") { + MetaBundle bundle; + bundle.setPath ( attrs.value( "path" ) ); + bundle.setTitle ( attrs.value( "title" ) ); + bundle.setArtist ( attrs.value( "artist" ) ); + bundle.setComposer ( attrs.value( "composer" ) ); + bundle.setAlbum ( attrs.value( "album" ) ); + bundle.setComment ( attrs.value( "comment" ) ); + bundle.setGenre ( attrs.value( "genre" ) ); + bundle.setYear ( attrs.value( "year" ).toInt() ); + bundle.setTrack ( attrs.value( "track" ).toInt() ); + bundle.setDiscNumber( attrs.value( "discnumber" ).toInt() ); + bundle.setBpm ( attrs.value( "bpm" ).toFloat() ); + bundle.setFileType( attrs.value( "filetype" ).toInt() ); + bundle.setUniqueId( attrs.value( "uniqueid" ) ); + bundle.setCompilation( attrs.value( "compilation" ).toInt() ); + + if( attrs.value( "audioproperties" ) == "true" ) { + bundle.setBitrate ( attrs.value( "bitrate" ).toInt() ); + bundle.setLength ( attrs.value( "length" ).toInt() ); + bundle.setSampleRate( attrs.value( "samplerate" ).toInt() ); + } + + if( !attrs.value( "filesize" ).isNull() + && !attrs.value( "filesize" ).isEmpty() ) + { + bundle.setFilesize( attrs.value( "filesize" ).toInt() ); + } + + CollectionDB::instance()->addSong( &bundle, m_incremental ); + if( !bundle.uniqueId().isEmpty() ) + { + m_fileMapsMutex.lock(); + m_filesAdded[bundle.uniqueId()] = bundle.url().path(); + m_fileMapsMutex.unlock(); + } + + m_scanCount++; + } + + else if( localName == "folder" ) { + const QString folder = attrs.value( "path" ); + const QFileInfo info( folder ); + + // Update dir statistics for rescanning purposes + if( info.exists() ) + CollectionDB::instance()->updateDirStats( folder, info.lastModified().toTime_t(), true); + + if( m_incremental ) { + m_foldersToRemove += folder; + } + } + + else if( localName == "playlist" ) + QApplication::postEvent( PlaylistBrowser::instance(), new PlaylistFoundEvent( attrs.value( "path" ) ) ); + + else if( localName == "compilation" ) + CollectionDB::instance()->checkCompilations( attrs.value( "path" ), !m_incremental); + + else if( localName == "image" ) { + // Deserialize CoverBundle list + QStringList list = QStringList::split( "AMAROK_MAGIC", attrs.value( "list" ), true ); + QValueList< QPair > covers; + + for( uint i = 0; i < list.count(); ) { + covers += qMakePair( list[i], list[i + 1] ); + i += 2; + } + + CollectionDB::instance()->addImageToAlbum( attrs.value( "path" ), covers, CollectionDB::instance()->isConnected() ); + } + + else if( localName == "embed" ) { + CollectionDB::instance()->addEmbeddedImage( attrs.value( "path" ), attrs.value( "hash" ), attrs.value( "description" ) ); + } + + return true; +} + + +void +ScanController::customEvent( QCustomEvent* e ) +{ + if( e->type() == RestartEventType ) + { + debug() << "RestartEvent received." << endl; + + QFile log( Amarok::saveLocation( QString::null ) + "collection_scan.log" ); + if ( !log.open( IO_ReadOnly ) ) + ::warning() << "Failed opening log file " << log.name() << endl; + else { + QCString path = QCString(log.readAll()); + m_crashedFiles << QString::fromUtf8( path, path.length() ); + + } + + m_dataMutex.lock(); + m_xmlData = QString::null; + delete m_source; + m_source = new QXmlInputSource(); + m_dataMutex.unlock(); + + delete m_reader; + m_reader = new QXmlSimpleReader(); + + m_reader->setContentHandler( this ); + m_reader->parse( m_source, true ); + + delete m_scanner; // Reusing doesn't work, so we have to destroy and reinstantiate + m_scanner = new Amarok::ProcIO(); + connect( m_scanner, SIGNAL( readReady( KProcIO* ) ), SLOT( slotReadReady() ) ); + + *m_scanner << "amarokcollectionscanner"; + *m_scanner << "--nocrashhandler"; // We want to be able to catch SIGSEGV + if( m_incremental ) + *m_scanner << "-i"; + + *m_scanner << "-p"; + *m_scanner << "-s"; + m_scanner->start(); + } + else + ThreadManager::Job::customEvent( e ); +} + + +#include "scancontroller.moc" diff --git a/amarok/src/scancontroller.h b/amarok/src/scancontroller.h new file mode 100644 index 00000000..7b46af0a --- /dev/null +++ b/amarok/src/scancontroller.h @@ -0,0 +1,141 @@ +/*************************************************************************** + * Copyright (C) 2003-2005 by The Amarok Developers * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Steet, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef AMAROK_SCANCONTROLLER_H +#define AMAROK_SCANCONTROLLER_H + +#include +#include //baseclass + +#include "threadmanager.h" //baseclass + +class CollectionDB; +class KProcIO; + +/** + * @class ScanController + * @short Starts and controls the external amarokcollectionscanner application. + * @author Mark Kretschmann + * + * The collection scanner itself is run in an external process, unlike before, where it + * used to be thread. The advantage is that the scanner cannot crash the Amarok main + * application any more. If it crashes we can simply restart it. + * + * Amarok communicates with the scanner via the ScanController class, which processes + * XML entities written to stdout by the scanner process. For XML parsing an event + * driven SAX2 parser is used, which can process the entities as they arrive, without + * the need for a DOM document structure. + */ + +class ScanController : public ThreadManager::DependentJob, public QXmlDefaultHandler +{ + Q_OBJECT + + public: + static const int RestartEventType = 8891; + + class RestartEvent : public QCustomEvent { + public: + RestartEvent() : QCustomEvent( RestartEventType ) {} + }; + + static const int PlaylistFoundEventType = 8890; + + class PlaylistFoundEvent : public QCustomEvent { + public: + PlaylistFoundEvent( QString path ) + : QCustomEvent( PlaylistFoundEventType ) + , m_path( path ) {} + QString path() { return m_path; } + private: + QString m_path; + }; + + public: + ScanController( CollectionDB* parent, bool incremental, const QStringList& folders = QStringList() ); + ~ScanController(); + static ScanController* instance(); + + virtual void completeJob( void ); + + bool isIncremental() const { return m_incremental; } + bool hasChanged() const { return m_hasChanged; } + + void notifyThisBundle( MetaBundle* bundle ); + bool isPaused() { return m_isPaused; } + bool tablesCreated() { return m_tablesCreated; } + + signals: + void scannerAcknowledged(); + void scanDone( bool changed ); + + public slots: + bool requestPause(); + bool requestUnpause(); + void requestAcknowledged(); + void slotFileMoved( const QString &src, const QString &dest ); + + private slots: + void slotReadReady(); + + private: + void initIncremental(); + virtual bool doJob(); + static void setInstance( ScanController* instance ); + + bool startElement( const QString&, const QString &localName, const QString&, const QXmlAttributes &attrs ); + void customEvent( QCustomEvent* ); + + // Member variables: + static const uint MAX_RESTARTS = 80; + static const uint MAX_FAILURE_PERCENTAGE = 5; + + KProcIO* m_scanner; + QStringList m_folders; + QStringList m_foldersToRemove; + bool m_incremental; + bool m_hasChanged; + + QString m_xmlData; + QMutex m_dataMutex; + QXmlInputSource* m_source; + QXmlSimpleReader* m_reader; + + QStringList m_crashedFiles; + + // Every file that the collection scanner finds is marked + // here, as well as the source of all files that the AFT code + // detects as having been moved. These are the files that + // have definitely not been deleted. The key is the absolute + // path. + QMap m_filesAdded; + QMap m_filesDeleted; + QMutex m_fileMapsMutex; + + static ScanController* currController; + + MetaBundle* m_waitingBundle; + bool m_lastCommandPaused; + bool m_isPaused; + bool m_tablesCreated; + int m_scanCount; +}; + + +#endif // AMAROK_SCANCONTROLLER_H diff --git a/amarok/src/scriptmanager.cpp b/amarok/src/scriptmanager.cpp new file mode 100644 index 00000000..da318fae --- /dev/null +++ b/amarok/src/scriptmanager.cpp @@ -0,0 +1,952 @@ +/*************************************************************************** + * Copyright (C) 2004-2006 by Mark Kretschmann * + * 2005 by Seb Ruiz * + * 2006 by Alexandre Oliveira * + * 2006 by Martin Ellis * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Steet, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#define DEBUG_PREFIX "ScriptManager" + +#include "amarok.h" +#include "amarokconfig.h" +#include "contextbrowser.h" +#include "debug.h" +#include "enginecontroller.h" +#include "metabundle.h" +#include "scriptmanager.h" +#include "scriptmanagerbase.h" +#include "statusbar.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include // knewstuff script fetching +#include // " +#include // " +#include // " + + +namespace Amarok { + void closeOpenFiles(int out, int in, int err) { + for(int i = sysconf(_SC_OPEN_MAX) - 1; i > 2; i--) + if(i!=out && i!=in && i!=err) + close(i); + } + + /** + * This constructor is needed so that the correct codec is used. KProcIO defaults + * to latin1, while the scanner uses UTF-8. + */ + ProcIO::ProcIO() : KProcIO( QTextCodec::codecForName( "UTF-8" ) ) {} + + QString + proxyForUrl(const QString& url) + { + KURL kurl( url ); + + QString proxy; + + if ( KProtocolManager::proxyForURL( kurl ) != + QString::fromLatin1( "DIRECT" ) ) { + KProtocolManager::slaveProtocol ( kurl, proxy ); + } + + return proxy; + } + + QString + proxyForProtocol(const QString& protocol) + { + return KProtocolManager::proxyFor( protocol ); + } + + +} + +//////////////////////////////////////////////////////////////////////////////// +// class AmarokScriptNewStuff +//////////////////////////////////////////////////////////////////////////////// + +/** + * GHNS Customised Download implementation. + */ +class AmarokScriptNewStuff : public KNewStuff +{ + public: + AmarokScriptNewStuff(const QString &type, QWidget *parentWidget=0) + : KNewStuff( type, parentWidget ) + {} + + bool install( const QString& fileName ) + { + return ScriptManager::instance()->slotInstallScript( fileName ); + } + + virtual bool createUploadFile( const QString& ) { return false; } //make compile on kde 3.5 +}; + + +//////////////////////////////////////////////////////////////////////////////// +// class ScriptManager +//////////////////////////////////////////////////////////////////////////////// + +ScriptManager* ScriptManager::s_instance = 0; + + +ScriptManager::ScriptManager( QWidget *parent, const char *name ) + : KDialogBase( parent, name, false, QString::null, Close, Close, true ) + , EngineObserver( EngineController::instance() ) + , m_gui( new ScriptManagerBase( this ) ) +{ + DEBUG_BLOCK + + s_instance = this; + + kapp->setTopWidget( this ); + setCaption( kapp->makeStdCaption( i18n( "Script Manager" ) ) ); + + // Gives the window a small title bar, and skips a taskbar entry + KWin::setType( winId(), NET::Utility ); + KWin::setState( winId(), NET::SkipTaskbar ); + + setMainWidget( m_gui ); + m_gui->listView->setRootIsDecorated( true ); + m_gui->listView->setFullWidth( true ); + m_gui->listView->setShowSortIndicator( true ); + + + /// Category items + m_generalCategory = new KListViewItem( m_gui->listView, i18n( "General" ) ); + m_lyricsCategory = new KListViewItem( m_gui->listView, i18n( "Lyrics" ) ); + m_scoreCategory = new KListViewItem( m_gui->listView, i18n( "Score" ) ); + m_transcodeCategory = new KListViewItem( m_gui->listView, i18n( "Transcoding" ) ); + + m_generalCategory ->setSelectable( false ); + m_lyricsCategory ->setSelectable( false ); + m_scoreCategory ->setSelectable( false ); + m_transcodeCategory->setSelectable( false ); + + m_generalCategory ->setPixmap( 0, SmallIcon( Amarok::icon( "files" ) ) ); + m_lyricsCategory ->setPixmap( 0, SmallIcon( Amarok::icon( "files" ) ) ); + m_scoreCategory ->setPixmap( 0, SmallIcon( Amarok::icon( "files" ) ) ); + m_transcodeCategory->setPixmap( 0, SmallIcon( Amarok::icon( "files" ) ) ); + + // Restore the open/closed state of the category items + KConfig* const config = Amarok::config( "ScriptManager" ); + m_generalCategory ->setOpen( config->readBoolEntry( "General category open" ) ); + m_lyricsCategory ->setOpen( config->readBoolEntry( "Lyrics category open" ) ); + m_scoreCategory ->setOpen( config->readBoolEntry( "Score category State" ) ); + m_transcodeCategory->setOpen( config->readBoolEntry( "Transcode category open" ) ); + + connect( m_gui->listView, SIGNAL( currentChanged( QListViewItem* ) ), SLOT( slotCurrentChanged( QListViewItem* ) ) ); + connect( m_gui->listView, SIGNAL( doubleClicked ( QListViewItem*, const QPoint&, int ) ), SLOT( slotRunScript() ) ); + connect( m_gui->listView, SIGNAL( contextMenuRequested ( QListViewItem*, const QPoint&, int ) ), SLOT( slotShowContextMenu( QListViewItem*, const QPoint& ) ) ); + + connect( m_gui->installButton, SIGNAL( clicked() ), SLOT( slotInstallScript() ) ); + connect( m_gui->retrieveButton, SIGNAL( clicked() ), SLOT( slotRetrieveScript() ) ); + connect( m_gui->uninstallButton, SIGNAL( clicked() ), SLOT( slotUninstallScript() ) ); + connect( m_gui->runButton, SIGNAL( clicked() ), SLOT( slotRunScript() ) ); + connect( m_gui->stopButton, SIGNAL( clicked() ), SLOT( slotStopScript() ) ); + connect( m_gui->configureButton, SIGNAL( clicked() ), SLOT( slotConfigureScript() ) ); + connect( m_gui->aboutButton, SIGNAL( clicked() ), SLOT( slotAboutScript() ) ); + + m_gui->installButton ->setIconSet( SmallIconSet( Amarok::icon( "files" ) ) ); + m_gui->retrieveButton ->setIconSet( SmallIconSet( Amarok::icon( "download" ) ) ); + m_gui->uninstallButton->setIconSet( SmallIconSet( Amarok::icon( "remove" ) ) ); + m_gui->runButton ->setIconSet( SmallIconSet( Amarok::icon( "play" ) ) ); + m_gui->stopButton ->setIconSet( SmallIconSet( Amarok::icon( "stop" ) ) ); + m_gui->configureButton->setIconSet( SmallIconSet( Amarok::icon( "configure" ) ) ); + m_gui->aboutButton ->setIconSet( SmallIconSet( Amarok::icon( "info" ) ) ); + + QSize sz = sizeHint(); + setMinimumSize( kMax( 350, sz.width() ), kMax( 250, sz.height() ) ); + resize( sizeHint() ); + + connect( this, SIGNAL(lyricsScriptChanged()), ContextBrowser::instance(), SLOT( lyricsScriptChanged() ) ); + + // Delay this call via eventloop, because it's a bit slow and would block + QTimer::singleShot( 0, this, SLOT( findScripts() ) ); +} + + +ScriptManager::~ScriptManager() +{ + DEBUG_BLOCK + + QStringList runningScripts; + ScriptMap::Iterator it; + ScriptMap::Iterator end( m_scripts.end() ); + for( it = m_scripts.begin(); it != end; ++it ) { + if( it.data().process ) { + terminateProcess( &it.data().process ); + runningScripts << it.key(); + } + } + + // Save config + KConfig* const config = Amarok::config( "ScriptManager" ); + config->writeEntry( "Running Scripts", runningScripts ); + + // Save the open/closed state of the category items + config->writeEntry( "General category open", m_generalCategory->isOpen() ); + config->writeEntry( "Lyrics category open", m_lyricsCategory->isOpen() ); + config->writeEntry( "Score category open", m_scoreCategory->isOpen() ); + config->writeEntry( "Transcode category open", m_transcodeCategory->isOpen() ); + + s_instance = 0; +} + + +//////////////////////////////////////////////////////////////////////////////// +// public +//////////////////////////////////////////////////////////////////////////////// + +bool +ScriptManager::runScript( const QString& name, bool silent ) +{ + if( !m_scripts.contains( name ) ) + return false; + + m_gui->listView->setCurrentItem( m_scripts[name].li ); + return slotRunScript( silent ); +} + + +bool +ScriptManager::stopScript( const QString& name ) +{ + if( !m_scripts.contains( name ) ) + return false; + + m_gui->listView->setCurrentItem( m_scripts[name].li ); + slotStopScript(); + + return true; +} + + +QStringList +ScriptManager::listRunningScripts() +{ + QStringList runningScripts; + foreachType( ScriptMap, m_scripts ) + if( it.data().process ) + runningScripts << it.key(); + + return runningScripts; +} + + +void +ScriptManager::customMenuClicked( const QString& message ) +{ + notifyScripts( "customMenuClicked: " + message ); +} + + +QString +ScriptManager::specForScript( const QString& name ) +{ + if( !m_scripts.contains( name ) ) + return QString(); + QFileInfo info( m_scripts[name].url.path() ); + const QString specPath = info.dirPath() + '/' + info.baseName( true ) + ".spec"; + + return specPath; +} + + +void +ScriptManager::notifyFetchLyrics( const QString& artist, const QString& title ) +{ + const QString args = KURL::encode_string( artist ) + ' ' + KURL::encode_string( title ); + notifyScripts( "fetchLyrics " + args ); +} + + +void +ScriptManager::notifyFetchLyricsByUrl( const QString& url ) +{ + notifyScripts( "fetchLyricsByUrl " + url ); +} + + +void ScriptManager::notifyTranscode( const QString& srcUrl, const QString& filetype ) +{ + notifyScripts( "transcode " + srcUrl + ' ' + filetype ); +} + + +void +ScriptManager::notifyPlaylistChange( const QString& change) +{ + notifyScripts( "playlistChange: " + change ); +} + + +void +ScriptManager::requestNewScore( const QString &url, double prevscore, int playcount, int length, float percentage, const QString &reason ) +{ + const QString script = ensureScoreScriptRunning(); + if( script.isNull() ) + { + Amarok::StatusBar::instance()->longMessage( + i18n( "No score scripts were found, or none of them worked. Automatic scoring will be disabled. Sorry." ), + KDE::StatusBar::Sorry ); + return; + } + + m_scripts[script].process->writeStdin( + QString( "requestNewScore %6 %1 %2 %3 %4 %5" ) + .arg( prevscore ) + .arg( playcount ) + .arg( length ) + .arg( percentage ) + .arg( reason ) + .arg( KURL::encode_string( url ) ) ); //last because it might have %s +} + +//////////////////////////////////////////////////////////////////////////////// +// private slots +//////////////////////////////////////////////////////////////////////////////// + +void +ScriptManager::findScripts() //SLOT +{ + const QStringList allFiles = kapp->dirs()->findAllResources( "data", "amarok/scripts/*", true ); + + // Add found scripts to listview: + { + foreach( allFiles ) + if( QFileInfo( *it ).isExecutable() ) + loadScript( *it ); + } + + // Handle auto-run: + + KConfig* const config = Amarok::config( "ScriptManager" ); + const QStringList runningScripts = config->readListEntry( "Running Scripts" ); + + { + foreach( runningScripts ) + if( m_scripts.contains( *it ) ) { + debug() << "Auto-running script: " << *it << endl; + m_gui->listView->setCurrentItem( m_scripts[*it].li ); + slotRunScript(); + } + } + + m_gui->listView->setCurrentItem( m_gui->listView->firstChild() ); + slotCurrentChanged( m_gui->listView->currentItem() ); +} + + +void +ScriptManager::slotCurrentChanged( QListViewItem* item ) +{ + const bool isCategory = item == m_generalCategory || + item == m_lyricsCategory || + item == m_scoreCategory || + item == m_transcodeCategory; + + if( item && !isCategory ) { + const QString name = item->text( 0 ); + m_gui->uninstallButton->setEnabled( true ); + m_gui->runButton->setEnabled( !m_scripts[name].process ); + m_gui->stopButton->setEnabled( m_scripts[name].process ); + m_gui->configureButton->setEnabled( m_scripts[name].process ); + m_gui->aboutButton->setEnabled( true ); + } + else { + m_gui->uninstallButton->setEnabled( false ); + m_gui->runButton->setEnabled( false ); + m_gui->stopButton->setEnabled( false ); + m_gui->configureButton->setEnabled( false ); + m_gui->aboutButton->setEnabled( false ); + } +} + + +bool +ScriptManager::slotInstallScript( const QString& path ) +{ + QString _path = path; + + if( path.isNull() ) { + _path = KFileDialog::getOpenFileName( QString::null, + "*.amarokscript.tar *.amarokscript.tar.bz2 *.amarokscript.tar.gz|" + + i18n( "Script Packages (*.amarokscript.tar, *.amarokscript.tar.bz2, *.amarokscript.tar.gz)" ) + , this + , i18n( "Select Script Package" ) ); + if( _path.isNull() ) return false; + } + + KTar archive( _path ); + if( !archive.open( IO_ReadOnly ) ) { + KMessageBox::sorry( 0, i18n( "Could not read this package." ) ); + return false; + } + + QString destination = Amarok::saveLocation( "scripts/" ); + const KArchiveDirectory* const archiveDir = archive.directory(); + + // Prevent installing a script that's already installed + const QString scriptFolder = destination + archiveDir->entries().first(); + if( QFile::exists( scriptFolder ) ) { + KMessageBox::error( 0, i18n( "A script with the name '%1' is already installed. " + "Please uninstall it first." ).arg( archiveDir->entries().first() ) ); + return false; + } + + archiveDir->copyTo( destination ); + m_installSuccess = false; + recurseInstall( archiveDir, destination ); + + if( m_installSuccess ) { + KMessageBox::information( 0, i18n( "Script successfully installed." ) ); + return true; + } + else { + KMessageBox::sorry( 0, i18n( "

    Script installation failed.

    " + "

    The package did not contain an executable file. " + "Please inform the package maintainer about this error.

    " ) ); + + // Delete directory recursively + KIO::NetAccess::del( KURL::fromPathOrURL( scriptFolder ), 0 ); + } + + return false; +} + + +void +ScriptManager::recurseInstall( const KArchiveDirectory* archiveDir, const QString& destination ) +{ + const QStringList entries = archiveDir->entries(); + + foreach( entries ) { + const QString entry = *it; + const KArchiveEntry* const archEntry = archiveDir->entry( entry ); + + if( archEntry->isDirectory() ) { + const KArchiveDirectory* const dir = static_cast( archEntry ); + recurseInstall( dir, destination + entry + '/' ); + } + else { + ::chmod( QFile::encodeName( destination + entry ), archEntry->permissions() ); + + if( QFileInfo( destination + entry ).isExecutable() ) { + loadScript( destination + entry ); + m_installSuccess = true; + } + } + } +} + + +void +ScriptManager::slotRetrieveScript() +{ + // Delete KNewStuff's configuration entries. These entries reflect which scripts + // are already installed. As we cannot yet keep them in sync after uninstalling + // scripts, we deactivate the check marks entirely. + Amarok::config()->deleteGroup( "KNewStuffStatus" ); + + // we need this because KNewStuffGeneric's install function isn't clever enough + AmarokScriptNewStuff *kns = new AmarokScriptNewStuff( "amarok/script", this ); + KNS::Engine *engine = new KNS::Engine( kns, "amarok/script", this ); + KNS::DownloadDialog *d = new KNS::DownloadDialog( engine, this ); + d->setType( "amarok/script" ); + // you have to do this by hand when providing your own Engine + KNS::ProviderLoader *p = new KNS::ProviderLoader( this ); + QObject::connect( p, SIGNAL( providersLoaded(Provider::List*) ), d, SLOT( slotProviders (Provider::List *) ) ); + p->load( "amarok/script", "http://amarok.kde.org/knewstuff/amarokscripts-providers.xml" ); + + d->exec(); +} + + +void +ScriptManager::slotUninstallScript() +{ + const QString name = m_gui->listView->currentItem()->text( 0 ); + + if( KMessageBox::warningContinueCancel( 0, i18n( "Are you sure you want to uninstall the script '%1'?" ).arg( name ), i18n("Uninstall Script"), i18n("Uninstall") ) == KMessageBox::Cancel ) + return; + + if( m_scripts.find( name ) == m_scripts.end() ) + return; + + KURL scriptDirURL( m_scripts[name].url.upURL() ); + + // find if the script is installed in the global or local scripts directory + KURL scriptsDirURL; + QStringList dirs = KGlobal::dirs()->findDirs( "data", "amarok/scripts/" ); + for ( QStringList::Iterator it = dirs.begin(); it != dirs.end(); ++it ) { + scriptsDirURL = KURL::fromPathOrURL( *it ); + if ( scriptsDirURL.isParentOf( scriptDirURL ) ) + break; + } + + // find the begining of this script directory tree + KURL scriptDirUpURL = scriptDirURL.upURL(); + while ( ! scriptsDirURL.equals( scriptDirUpURL, true ) && scriptsDirURL.isParentOf( scriptDirUpURL ) ) { + scriptDirURL = scriptDirUpURL; + scriptDirUpURL = scriptDirURL.upURL(); + } + + // Delete script directory recursively + if( !KIO::NetAccess::del( scriptDirURL, 0 ) ) { + KMessageBox::sorry( 0, i18n( "

    Could not uninstall this script.

    The ScriptManager can only uninstall scripts which have been installed as packages.

    " ) ); // only true when not running as root (which is reasonable) + return; + } + + QStringList keys; + + // Find all scripts that were in the uninstalled directory + { + foreachType( ScriptMap, m_scripts ) + if( scriptDirURL.isParentOf( it.data().url ) ) + keys << it.key(); + } + + // Terminate script processes, remove entries from script list + { + foreach( keys ) { + delete m_scripts[*it].li; + terminateProcess( &m_scripts[*it].process ); + m_scripts.erase( *it ); + } + } +} + + +bool +ScriptManager::slotRunScript( bool silent ) +{ + if( !m_gui->runButton->isEnabled() ) return false; + + QListViewItem* const li = m_gui->listView->currentItem(); + const QString name = li->text( 0 ); + + if( m_scripts[name].type == "lyrics" && lyricsScriptRunning() != QString::null ) { + if( !silent ) + KMessageBox::sorry( 0, i18n( "Another lyrics script is already running. " + "You may only run one lyrics script at a time." ) ); + return false; + } + + if( m_scripts[name].type == "transcode" && transcodeScriptRunning() != QString::null ) { + if( !silent ) + KMessageBox::sorry( 0, i18n( "Another transcode script is already running. " + "You may only run one transcode script at a time." ) ); + return false; + } + + // Don't start a script twice + if( m_scripts[name].process ) return false; + + Amarok::ProcIO* script = new Amarok::ProcIO(); + script->setComm( static_cast( KProcess::All ) ); + const KURL url = m_scripts[name].url; + *script << url.path(); + script->setWorkingDirectory( Amarok::saveLocation( "scripts-data/" ) ); + + connect( script, SIGNAL( receivedStderr( KProcess*, char*, int ) ), SLOT( slotReceivedStderr( KProcess*, char*, int ) ) ); + connect( script, SIGNAL( receivedStdout( KProcess*, char*, int ) ), SLOT( slotReceivedStdout( KProcess*, char*, int ) ) ); + connect( script, SIGNAL( processExited( KProcess* ) ), SLOT( scriptFinished( KProcess* ) ) ); + + if( script->start( KProcess::NotifyOnExit ) ) + { + if( m_scripts[name].type == "score" && !scoreScriptRunning().isNull() ) + { + stopScript( scoreScriptRunning() ); + m_gui->listView->setCurrentItem( li ); + } + AmarokConfig::setLastScoreScript( name ); + } + else + { + if( !silent ) + KMessageBox::sorry( 0, i18n( "

    Could not start the script %1.

    " + "

    Please make sure that the file has execute (+x) permissions.

    " ).arg( name ) ); + delete script; + return false; + } + + li->setPixmap( 0, SmallIcon( Amarok::icon( "play" ) ) ); + debug() << "Running script: " << url.path() << endl; + + m_scripts[name].process = script; + slotCurrentChanged( m_gui->listView->currentItem() ); + if( m_scripts[name].type == "lyrics" ) + emit lyricsScriptChanged(); + + return true; +} + + +void +ScriptManager::slotStopScript() +{ + QListViewItem* const li = m_gui->listView->currentItem(); + const QString name = li->text( 0 ); + + // Just a sanity check + if( m_scripts.find( name ) == m_scripts.end() ) + return; + + terminateProcess( &m_scripts[name].process ); + m_scripts[name].log = QString::null; + slotCurrentChanged( m_gui->listView->currentItem() ); + + li->setPixmap( 0, QPixmap() ); +} + + +void +ScriptManager::slotConfigureScript() +{ + const QString name = m_gui->listView->currentItem()->text( 0 ); + if( !m_scripts[name].process ) return; + + const KURL url = m_scripts[name].url; + QDir::setCurrent( url.directory() ); + + m_scripts[name].process->writeStdin( "configure" ); +} + + +void +ScriptManager::slotAboutScript() +{ + const QString name = m_gui->listView->currentItem()->text( 0 ); + QFile readme( m_scripts[name].url.directory( false ) + "README" ); + QFile license( m_scripts[name].url.directory( false ) + "COPYING" ); + + if( !readme.open( IO_ReadOnly ) ) { + KMessageBox::sorry( 0, i18n( "There is no information available for this script." ) ); + return; + } + + KAboutDialog* about = new KAboutDialog( KAboutDialog::AbtTabbed|KAboutDialog::AbtProduct, + QString::null, + KDialogBase::Ok, KDialogBase::Ok, this ); + kapp->setTopWidget( about ); + about->setCaption( kapp->makeStdCaption( i18n( "About %1" ).arg( name ) ) ); + about->setProduct( "", "", "", "" ); + // Get rid of the confusing KDE version text + QLabel* product = static_cast( about->mainWidget()->child( "version" ) ); + if( product ) product->setText( i18n( "%1 Amarok Script" ).arg( name ) ); + + about->addTextPage( i18n( "About" ), readme.readAll(), true ); + if( license.open( IO_ReadOnly ) ) + about->addLicensePage( i18n( "License" ), license.readAll() ); + + about->setInitialSize( QSize( 500, 350 ) ); + about->show(); +} + + +void +ScriptManager::slotShowContextMenu( QListViewItem* item, const QPoint& pos ) +{ + const bool isCategory = item == m_generalCategory || + item == m_lyricsCategory || + item == m_scoreCategory || + item == m_transcodeCategory; + + if( !item || isCategory ) return; + + // Look up script entry in our map + ScriptMap::Iterator it; + ScriptMap::Iterator end( m_scripts.end() ); + for( it = m_scripts.begin(); it != end; ++it ) + if( it.data().li == item ) break; + + enum { SHOW_LOG, EDIT }; + KPopupMenu menu; + menu.insertTitle( i18n( "Debugging" ) ); + menu.insertItem( SmallIconSet( Amarok::icon( "clock" ) ), i18n( "Show Output &Log" ), SHOW_LOG ); + menu.insertItem( SmallIconSet( Amarok::icon( "edit" ) ), i18n( "&Edit" ), EDIT ); + menu.setItemEnabled( SHOW_LOG, it.data().process ); + const int id = menu.exec( pos ); + + switch( id ) + { + case EDIT: + KRun::runCommand( "kwrite " + KProcess::quote(it.data().url.path()) ); + break; + + case SHOW_LOG: + QString line; + while( it.data().process->readln( line ) != -1 ) + it.data().log += line; + + KTextEdit* editor = new KTextEdit( it.data().log ); + kapp->setTopWidget( editor ); + editor->setCaption( kapp->makeStdCaption( i18n( "Output Log for %1" ).arg( it.key() ) ) ); + editor->setReadOnly( true ); + + QFont font( "fixed" ); + font.setFixedPitch( true ); + font.setStyleHint( QFont::TypeWriter ); + editor->setFont( font ); + + editor->setTextFormat( QTextEdit::PlainText ); + editor->resize( 500, 380 ); + editor->show(); + break; + } +} + + +/* This is just a workaround, some scripts crash for some people if stdout is not handled. */ +void +ScriptManager::slotReceivedStdout( KProcess*, char* buf, int len ) +{ + debug() << QString::fromLatin1( buf, len ) << endl; +} + + +void +ScriptManager::slotReceivedStderr( KProcess* process, char* buf, int len ) +{ + // Look up script entry in our map + ScriptMap::Iterator it; + ScriptMap::Iterator end( m_scripts.end() ); + for( it = m_scripts.begin(); it != end; ++it ) + if( it.data().process == process ) break; + + const QString text = QString::fromLatin1( buf, len ); + error() << it.key() << ":\n" << text << endl; + + if( it.data().log.length() > 20000 ) + it.data().log = "==== LOG TRUNCATED HERE ====\n"; + it.data().log += text; +} + + +void +ScriptManager::scriptFinished( KProcess* process ) //SLOT +{ + // Look up script entry in our map + ScriptMap::Iterator it; + ScriptMap::Iterator end( m_scripts.end() ); + for( it = m_scripts.begin(); it != end; ++it ) + if( it.data().process == process ) break; + + // Check if there was an error on exit + if( process->normalExit() && process->exitStatus() != 0 ) + KMessageBox::detailedError( 0, i18n( "The script '%1' exited with error code: %2" ) + .arg( it.key() ).arg( process->exitStatus() ) + ,it.data().log ); + + // Destroy script process + delete it.data().process; + it.data().process = 0; + it.data().log = QString::null; + it.data().li->setPixmap( 0, QPixmap() ); + slotCurrentChanged( m_gui->listView->currentItem() ); +} + + +//////////////////////////////////////////////////////////////////////////////// +// private +//////////////////////////////////////////////////////////////////////////////// + +QStringList +ScriptManager::scriptsOfType( const QString &type ) const +{ + QStringList scripts; + foreachType( ScriptMap, m_scripts ) + if( it.data().type == type ) + scripts += it.key(); + + return scripts; +} + + +QString +ScriptManager::scriptRunningOfType( const QString &type ) const +{ + foreachType( ScriptMap, m_scripts ) + if( it.data().process ) + if( it.data().type == type ) + return it.key(); + + return QString(); +} + + +QString +ScriptManager::ensureScoreScriptRunning() +{ + QString s = scoreScriptRunning(); + if( !s.isNull() ) + return s; + + if( runScript( AmarokConfig::lastScoreScript(), true /*silent*/ ) ) + return AmarokConfig::lastScoreScript(); + + const QString def = i18n( "Score" ) + ": " + "Default"; + if( runScript( def, true ) ) + return def; + + const QStringList scripts = scoreScripts(); + for( QStringList::const_iterator it = scripts.begin(), end = scripts.end(); it != end; ++it ) + if( runScript( *it, true ) ) + return *it; + + return QString(); +} + + +void +ScriptManager::terminateProcess( KProcIO** proc ) +{ + if( *proc ) { + (*proc)->kill(); // Sends SIGTERM + (*proc)->detach(); + + delete *proc; + *proc = 0; + } +} + + +void +ScriptManager::notifyScripts( const QString& message ) +{ + foreachType( ScriptMap, m_scripts ) { + KProcIO* const proc = it.data().process; + if( proc ) proc->writeStdin( message ); + } +} + + +void +ScriptManager::loadScript( const QString& path ) +{ + if( !path.isEmpty() ) { + const KURL url = KURL::fromPathOrURL( path ); + QString name = url.fileName(); + QString type = "generic"; + + // Read and parse .spec file, if exists + QFileInfo info( path ); + KListViewItem* li = 0; + const QString specPath = info.dirPath() + '/' + info.baseName( true ) + ".spec"; + if( QFile::exists( specPath ) ) { + KConfig spec( specPath, true, false ); + if( spec.hasKey( "name" ) ) + name = spec.readEntry( "name" ); + if( spec.hasKey( "type" ) ) { + type = spec.readEntry( "type" ); + if( type == "lyrics" ) + li = new KListViewItem( m_lyricsCategory, name ); + if( type == "transcode" ) + li = new KListViewItem( m_transcodeCategory, name ); + if( type == "score" ) + li = new KListViewItem( m_scoreCategory, name ); + } + } + + if( !li ) + li = new KListViewItem( m_generalCategory, name ); + + li->setPixmap( 0, QPixmap() ); + + ScriptItem item; + item.url = url; + item.type = type; + item.process = 0; + item.li = li; + + m_scripts[name] = item; + debug() << "Loaded: " << name << endl; + + slotCurrentChanged( m_gui->listView->currentItem() ); + } +} + + +void +ScriptManager::engineStateChanged( Engine::State state, Engine::State /*oldState*/ ) +{ + switch( state ) + { + case Engine::Empty: + notifyScripts( "engineStateChange: empty" ); + break; + + case Engine::Idle: + notifyScripts( "engineStateChange: idle" ); + break; + + case Engine::Paused: + notifyScripts( "engineStateChange: paused" ); + break; + + case Engine::Playing: + notifyScripts( "engineStateChange: playing" ); + break; + } +} + + +void +ScriptManager::engineNewMetaData( const MetaBundle& /*bundle*/, bool /*trackChanged*/ ) +{ + notifyScripts( "trackChange" ); +} + + +void +ScriptManager::engineVolumeChanged( int newVolume ) +{ + notifyScripts( "volumeChange: " + QString::number( newVolume ) ); +} + +#include "scriptmanager.moc" diff --git a/amarok/src/scriptmanager.h b/amarok/src/scriptmanager.h new file mode 100644 index 00000000..5d872cf0 --- /dev/null +++ b/amarok/src/scriptmanager.h @@ -0,0 +1,211 @@ +/*************************************************************************** + * Copyright (C) 2004-2006 by Mark Kretschmann * + * 2005 by Seb Ruiz * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Steet, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef AMAROK_SCRIPTMANAGER_H +#define AMAROK_SCRIPTMANAGER_H + +#include "engineobserver.h" //baseclass +#include "playlistwindow.h" + +#include + +#include //baseclass +#include + +class MetaBundle; +class ScriptManagerBase; +class QListViewItem; +class KArchiveDirectory; +class KProcess; +class KProcIO; + + +/** + * @class ScriptManager + * @short Script management widget and backend + * @author Mark Kretschmann + * + * Script notifications, sent to stdin: + * configure + * engineStateChange: {empty|idle|paused|playing} + * trackChange + * volumeChange: newVolume (range: 0-100) + * fetchLyrics: artist title + * fetchLyricsByUrl: url + * + * @see http://amarok.kde.org/amarokwiki/index.php/Script-Writing_HowTo + */ + +class ScriptManager : public KDialogBase, public EngineObserver +{ + Q_OBJECT + + friend class AmarokScriptNewStuff; + + public: + ScriptManager( QWidget *parent = 0, const char *name = 0 ); + virtual ~ScriptManager(); + + static ScriptManager* instance() { return s_instance ? s_instance : new ScriptManager( PlaylistWindow::self() ); } + + /** + * Runs the script with the given name. Used by the DCOP handler. + * @param name The name of the script. + * @return True if successful. + */ + bool runScript( const QString& name, bool silent = false ); + + /** + * Stops the script with the given name. Used by the DCOP handler. + * @param name The name of the script. + * @return True if successful. + */ + bool stopScript( const QString& name ); + + /** Returns a list of all currently running scripts. Used by the DCOP handler. */ + QStringList listRunningScripts(); + + /** Custom Menu Click */ + void customMenuClicked( const QString& message ); + + /** Returns the path of the spec file of the given script */ + QString specForScript( const QString& name ); + + /** Return name of the lyrics script currently running, or QString::null if none */ + QString lyricsScriptRunning() const; + + /** Returns a list of all lyrics scripts */ + QStringList lyricsScripts() const; + + /** Sends a fetchLyrics notification to all scripts */ + void notifyFetchLyrics( const QString& artist, const QString& title ); + + /** Sends a fetchLyrics notification to retrieve lyrics from a specific page */ + void notifyFetchLyricsByUrl( const QString& url ); + + /** Sends a playlistChange notification to all scripts */ + void notifyPlaylistChange( const QString& change ); + + /** Return name of the transcode script currently running, or QString::null if none */ + QString transcodeScriptRunning() const; + + /** Sends a transcode notification to all scripts */ + void notifyTranscode( const QString& srcUrl, const QString& filetype ); + + /** Return name of the scoring script currently running, or QString::null if none */ + QString scoreScriptRunning() const; + + /** Returns a list of all scoring scripts */ + QStringList scoreScripts() const; + + /** Asks the current score script to give a new score based on the parameters. */ + void requestNewScore( const QString &url, double prevscore, int playcount, int length, float percentage, const QString &reason ); + + signals: + /** Emitted when the lyrics script changes, so that a lyrics retry can be made */ + void lyricsScriptChanged(); + + private slots: + /** Finds all installed scripts and adds them to the listview */ + void findScripts(); + + /** Enables/disables the buttons */ + void slotCurrentChanged( QListViewItem* ); + + bool slotInstallScript( const QString& path = QString::null ); + void slotRetrieveScript(); + void slotUninstallScript(); + bool slotRunScript( bool silent = false ); + void slotStopScript(); + void slotConfigureScript(); + void slotAboutScript(); + void slotShowContextMenu( QListViewItem*, const QPoint& ); + + void slotReceivedStdout( KProcess*, char*, int ); + void slotReceivedStderr( KProcess*, char*, int ); + void scriptFinished( KProcess* process ); + + private: + /** Returns all scripts of the given \p type */ + QStringList scriptsOfType( const QString &type ) const; + + /** Returns the first running script found of \p type */ + QString scriptRunningOfType( const QString &type ) const; + + QString ensureScoreScriptRunning(); + + /** Terminates a process with SIGTERM and deletes the KProcIO object */ + void terminateProcess( KProcIO** proc ); + + /** Sends a string message to all running scripts */ + void notifyScripts( const QString& message ); + + /** Adds a script to the listview */ + void loadScript( const QString& path ); + + /** Copies the file permissions from the tarball and loads the script */ + void recurseInstall( const KArchiveDirectory* archiveDir, const QString& destination ); + + /** EngineObserver reimplementations **/ + void engineStateChanged( Engine::State state, Engine::State oldState = Engine::Empty ); + void engineNewMetaData( const MetaBundle& /*bundle*/, bool /*trackChanged*/ ); + void engineVolumeChanged( int newVolume ); + + ///////////////////////////////////////////////////////////////////////////////////// + // DATA MEMBERS + ///////////////////////////////////////////////////////////////////////////////////// + static ScriptManager* s_instance; + ScriptManagerBase* m_gui; + + QListViewItem* m_generalCategory; + QListViewItem* m_lyricsCategory; + QListViewItem* m_scoreCategory; + QListViewItem* m_transcodeCategory; + + bool m_installSuccess; + + struct ScriptItem { + KURL url; + QString type; + KProcIO* process; + QListViewItem* li; + QString log; + ScriptItem() : process( 0 ), li( 0 ) {} + }; + + typedef QMap ScriptMap; + + ScriptMap m_scripts; +}; + + +inline QStringList ScriptManager::lyricsScripts() const { return scriptsOfType( "lyrics" ); } + +inline QString ScriptManager::lyricsScriptRunning() const { return scriptRunningOfType( "lyrics" ); } + +inline QString ScriptManager::transcodeScriptRunning() const { return scriptRunningOfType( "transcode" ); } + +inline QStringList ScriptManager::scoreScripts() const { return scriptsOfType( "score" ); } + +inline QString ScriptManager::scoreScriptRunning() const { return scriptRunningOfType( "score" ); } + +#endif /* AMAROK_SCRIPTMANAGER_H */ + + diff --git a/amarok/src/scriptmanagerbase.ui b/amarok/src/scriptmanagerbase.ui new file mode 100644 index 00000000..14445660 --- /dev/null +++ b/amarok/src/scriptmanagerbase.ui @@ -0,0 +1,135 @@ + +ScriptManagerBase + + + ScriptManagerBase + + + + 0 + 0 + 367 + 237 + + + + DirectoryListBase + + + + unnamed + + + + + Scripts + + + true + + + true + + + + listView + + + These scripts are currently known to Amarok. + + + + + spacer2 + + + Vertical + + + Expanding + + + + 20 + 30 + + + + + + retrieveButton + + + &Get More Scripts + + + + + installButton + + + &Install Script + + + + + line2_2 + + + HLine + + + Sunken + + + Horizontal + + + + + aboutButton + + + &About + + + + + runButton + + + &Run + + + + + stopButton + + + &Stop + + + + + configureButton + + + &Configure + + + + + uninstallButton + + + &Uninstall + + + + + + kdialog.h + + + + diff --git a/amarok/src/scripts/Makefile.am b/amarok/src/scripts/Makefile.am new file mode 100644 index 00000000..22d91e59 --- /dev/null +++ b/amarok/src/scripts/Makefile.am @@ -0,0 +1,10 @@ +SUBDIRS = \ + common \ + lyrics_astraweb \ + lyrics_lyrc \ + playlist2html \ + ruby_debug \ + score_default \ + score_impulsive \ + templates \ + webcontrol diff --git a/amarok/src/scripts/ScriptWriting-HOWTO b/amarok/src/scripts/ScriptWriting-HOWTO new file mode 100644 index 00000000..6eb6a335 --- /dev/null +++ b/amarok/src/scripts/ScriptWriting-HOWTO @@ -0,0 +1,7 @@ +Amarok-WRITING HOWTO +==================== + + +This document has been moved to the Amarok Wiki. Please refer to: + + http://amarok.kde.org/wiki/Script-Writing_HowTo diff --git a/amarok/src/scripts/alarm/COPYING b/amarok/src/scripts/alarm/COPYING new file mode 100644 index 00000000..0fc8a215 --- /dev/null +++ b/amarok/src/scripts/alarm/COPYING @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 51 Franklin Steet, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) 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 +this service 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 make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. 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. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute 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 and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +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 +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the 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 a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, 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. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE 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. + + 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 +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Steet, Fifth Floor, Boston, MA 02110-1301 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision 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, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This 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 Library General +Public License instead of this License. diff --git a/amarok/src/scripts/alarm/README b/amarok/src/scripts/alarm/README new file mode 100644 index 00000000..b9560780 --- /dev/null +++ b/amarok/src/scripts/alarm/README @@ -0,0 +1,13 @@ +
    Alarm Script
    +About:
    +Makes Amarok play music at a given time. Can be used as an alarm clock for waking up.
    +
    +Dependencies:
    +Python 2.2
    +PyQt Bindings
    +
    +License:
    +GPL V2
    +
    +Author:
    +Mark Kretschmann (markey@web.de) \ No newline at end of file diff --git a/amarok/src/scripts/alarm/alarm.py b/amarok/src/scripts/alarm/alarm.py new file mode 100755 index 00000000..97152c73 --- /dev/null +++ b/amarok/src/scripts/alarm/alarm.py @@ -0,0 +1,163 @@ +#!/usr/bin/env python + +############################################################################ +# Config dialog for alarm script +# (c) 2005 Mark Kretschmann +# +# Depends on: Python 2.2, PyQt +############################################################################ +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +############################################################################ + +from ConfigParser import * +import Queue +import os.path +import sys +import threading +from os import * + +try: + from qt import * +except: + popen( "kdialog --sorry 'PyQt (Qt bindings for Python) is required for this script.'" ) + raise + + +class ConfigDialog( QDialog ): + + def __init__( self ): + QDialog.__init__( self ) + self.setWFlags( Qt.WDestructiveClose ) + self.setCaption( "Alarm Script - Amarok" ) + + self.lay = QHBoxLayout( self ) + + self.vbox = QVBox( self ) + self.lay.addWidget( self.vbox ) + + self.htopbox = QHBox( self.vbox ) + QLabel( "Alarm time: ", self.htopbox ) + self.timeEdit = QTimeEdit( self.htopbox ) + + self.hbox = QHBox( self.vbox ) + + self.ok = QPushButton( self.hbox ) + self.ok.setText( "Ok" ) + + self.cancel = QPushButton( self.hbox ) + self.cancel.setText( "Cancel" ) + self.cancel.setDefault( True ) + + self.connect( self.ok, SIGNAL( "clicked()" ), self.save ) + self.connect( self.cancel, SIGNAL( "clicked()" ), self, SLOT( "reject()" ) ) + + self.adjustSize() + + def __del__( self ): + print "ConfigDialog dtor" + + def save( self ): + wakeTime = str( self.timeEdit.time().toString() ) + print wakeTime + + self.file = file( "alarmrc", 'w' ) + + self.config = ConfigParser() + self.config.add_section( "General" ) + self.config.set( "General", "alarmtime", wakeTime) + self.config.write( self.file ) + self.file.close() + + self.accept() + + +class Alarm( QApplication ): + + def __init__( self, args ): + QApplication.__init__( self, args ) + + self.queue = Queue.Queue() + self.startTimer( 100 ) + + self.t = threading.Thread( target = self.readStdin ) + self.t.start() + + self.alarmTimer = QTimer() + self.connect( self.alarmTimer, SIGNAL( "timeout()" ), self.wakeup ) + + self.readSettings() + + def __del__( self ): + print "Alarm dtor" + + def wakeup( self ): + popen( "dcop amarok player play" ) + self.quit() + + + def readSettings( self ): + config = ConfigParser() + config.read( "alarmrc" ) + + try: + timestr = config.get( "General", "alarmtime" ) + print "Alarm Time: " + timestr + + time = QTime.fromString( timestr ) + secondsleft = QTime.currentTime().secsTo( time ) + + if secondsleft > 0: + self.alarmTimer.start( secondsleft * 1000, True ) + except: + pass + + +############################################################################ +# Stdin-Reader Thread +############################################################################ + + def readStdin( self ): + while True: + line = sys.stdin.readline() + + if line: + self.queue.put_nowait( line ) + else: + break + + +############################################################################ +# Command Handling +############################################################################ + + def timerEvent( self, event ): + if not self.queue.empty(): + string = QString( self.queue.get_nowait() ) + print "[Alarm Script] Received notification: " + str( string ) + + if string.contains( "configure" ): + self.configure() + + def configure( self ): + print "Alarm Script: configuration" + + self.dia = ConfigDialog() + self.dia.show() + self.connect( self.dia, SIGNAL( "destroyed()" ), self.readSettings ) + + +############################################################################ + +def main( args ): + app = Alarm( args ) + + app.exec_loop() + +if __name__ == "__main__": + main( sys.argv ) + diff --git a/amarok/src/scripts/amarok-svn/INFO b/amarok/src/scripts/amarok-svn/INFO new file mode 100644 index 00000000..3fe032e6 --- /dev/null +++ b/amarok/src/scripts/amarok-svn/INFO @@ -0,0 +1,8 @@ +This bash script (not for Amarok itself) installs the current development version of Amarok (from SVN) on your computer. + +There will be a later version (4.0, I guess) written in ruby (maybe python) instead of bash, that version will probably be split into smaller parts +to make the +script able to update itself on each run. That's the main reason why I put it in the SVN repository (besides making others able to change it, that +is...) + +For now, use the stable version if you want reliability. It's available at http://amarok.kde.org/amarokwiki/index.php/AmaroK-svn diff --git a/amarok/src/scripts/amarok-svn/README b/amarok/src/scripts/amarok-svn/README new file mode 100644 index 00000000..c45d0e57 --- /dev/null +++ b/amarok/src/scripts/amarok-svn/README @@ -0,0 +1,9 @@ +Amarok-svn (development version) notes +======================================== + +Please try this version out and report any bugs to ajocke AT gmail DOT com. + +Read the INFO file for more information about this script and why it's here. + +Read the ChangeLog on http://amarok.kde.org/amarokwiki/index.php/AmaroK-svn#ChangeLog +It contains some useful information about this version. diff --git a/amarok/src/scripts/amarok-svn/amarok-bench.sh b/amarok/src/scripts/amarok-svn/amarok-bench.sh new file mode 100755 index 00000000..90260a18 --- /dev/null +++ b/amarok/src/scripts/amarok-svn/amarok-bench.sh @@ -0,0 +1,72 @@ +#!/bin/bash + +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# Amarok-bench +# ================== +# Measures the time it takes to compile Amarok, meant to be run in a directory controlled by +# Amarok-svn (also by me). +# +# If you run it with a number as parameter (I.E. `./amarok-bench.sh 2`), that number will be +# passed to unsermake's -j option, this is useful for SMP systems. +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # + +echo +echo "Amarok-bench (Version 1.0)" +echo "============================" +echo + +## Define functions +function Error { + echo + echo -e "ERROR: $1" + exit 1 #Exit with error +} + +function Clean { + unsermake clean > /dev/null + if [ "$?" != "0" ]; then #If the command didn't finish successfully + Error "Failed to clean the source tree!" + else + echo "Done." + fi +} + +function Compile { + COMP_START=`date +%s` + #Run unsermake twice because of some files that gets forgotten sometimes + #(We don't want to compile as root in unsermake install, and it doesn't hurt) + unsermake -j $1 && unsermake -j $1 + if [ "$?" = "0" ]; then #If the command did finish successfully + #stopwatch. + let COMP_TIME=`date +%s`-COMP_START + let COMP_M=COMP_TIME/60 + let COMP_S=COMP_TIME%60 + echo + echo "FINISHED! Timer stopped at $COMP_M minute(s) and $COMP_S second(s)." + else + Error "Compilation failed! Amarok was NOT compiled." + fi +} + +JOBS=$1 +[ "0$JOBS" -lt "1" ] && JOBS=1 + +echo "# Cleaning source tree" +Clean + +echo +echo "# Compilation" +echo "Ready..." +sleep 1 + +echo "Set..." +sleep 1 + +echo "COMPILE!" + +echo +Compile $JOBS + +echo +echo "Now get something real to do..." + diff --git a/amarok/src/scripts/amarok-svn/amarok-svn.sh b/amarok/src/scripts/amarok-svn/amarok-svn.sh new file mode 100755 index 00000000..e6ffd7d3 --- /dev/null +++ b/amarok/src/scripts/amarok-svn/amarok-svn.sh @@ -0,0 +1,606 @@ +#!/bin/bash + +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# Amarok-svn +# ============ +# This script installs the current development (SVN) version of Amarok on your computer. +# If you've run it once, and then run it again, it will update your version of Amarok and only compile the new files. +# +# Made by Jocke "Firetech" Andersson. +# Idea and inspiration from a small script by Greg "oggb4mp3" Meyer. +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # + +echo +echo "Amarok-svn (Version 3.2.1)" +echo "============================" +echo + +## Define global variables +LANG="C" #Make outputs in English, like the script itself. +RCFILE="amarok-svnrc" #Settings file, will end up in '`kde-config --localprefix`/share/config/'. +C_STEP="1" #The current step of the installation process +S_STEPS="11" #Number of steps in the installation process +TMP_FILES="" #Will be filled with URLs to temporary files + +## Define functions +function Dialog { + kdialog --icon "amarok" --title "Amarok-svn$KDTITLE" "$@" +} + +function RemoveTemp { + rm -f $TMP_FILES +} + +function Error { + RemoveTemp + echo + echo -e "ERROR: $1" + if [ "$2" != "--no-dialog" ]; then + Dialog --error "$1" + fi + exit 1 #Exit with error +} + +function ReadConfig { + kreadconfig --file "$RCFILE" --group Settings --key "$1" +} + +function WriteConfig { + kwriteconfig --file "$RCFILE" --group Settings --key "$1" "$2" +} + +function CheckBinary { + if [ ! -x "`which $1`" ]; then #check if $1 is in $PATH and can be executed + ERROR_TEXT="Amarok-svn requires $1, which wasn't found in your \$PATH!" + if [ "$2" ]; then + ERROR_TEXT="$ERROR_TEXT ($2)" + fi + Error "$ERROR_TEXT" $3 + fi +} + +function FlagUsage { + echo "Usage: $0 [options] [builddir]" + echo + echo "Amarok-svn installs the current development (SVN) version of Amarok on your computer." + echo + echo "Options:" + echo -e " -r, --reset\t\tAsk for settings again." + echo -e " -s, --select-server\tAsk which SVN server to use. (Only needed if you want to use your SVN account.)" + echo -e " -c, --clean\t\tClean the source tree before compiling Amarok." + echo -e " -h, --help\t\tShow this message." + echo + echo "Arguments:" + echo -e " builddir\t\tDownload and build Amarok in this directory. Default is '[your current dir]/amarok-svn'." + echo + echo "Notes:" + echo " * Options can NOT be fused together! (I.E. You can't use '-cr', you have to use '-c -r'.)" + echo " * Amarok-svn will download the Amarok sources directly into the folder you choose, without creating any subdirectory!" + echo + exit 1 +} + +function Clean { + unsermake clean + if [ "$?" != "0" ]; then #If the command didn't finish successfully + Error "Failed to clean the source tree!" + fi +} + +function Compile { + COMP_START=`date +%s` + #Run unsermake twice because of some files that gets forgotten sometimes + #(We don't want to compile as root in unsermake install, and it doesn't hurt) + unsermake && unsermake + if [ "$?" = "0" ]; then #If the command did finish successfully + echo + #stopwatch. + let COMP_TIME=`date +%s`-COMP_START + let COMP_M=COMP_TIME/60 + let COMP_S=COMP_TIME%60 + echo "Compilation successful after $COMP_M minute(s) and $COMP_S second(s)." + else + echo + if [ "$CLEAN_BUILD" = "0" ]; then + echo "Compilation Failed!" + Dialog --warningyesno "Compilation failed!\nSometimes, a clean rebuild can fix this.\nDo you want to retry compiling with a clean source tree?\nIf you answer No, Amarok-svn will exit." + if [ "$?" = "0" ]; then #If user said yes + echo "Retrying with a clean source tree." + CLEAN_BUILD="1" + Clean + Compile + else + Error "Compilation failed! Amarok was NOT installed/updated." + fi + else + Error "Compilation failed! Amarok was NOT installed/updated." + fi + fi +} + +## Handle --flags +# set default values +RESET_CONF="0" +CLEAN_BUILD="0" +SELECT_SERVER="0" +BUILD_DIR="`pwd`/amarok-svn" +# read flags values +BUILD_DIR_SET="0" +for flag; do + case "$flag" in + -r|--reset) + RESET_CONF="1" + ;; + -c|--clean) + CLEAN_BUILD="1" + let S_STEPS=S_STEPS+1 + ;; + -s|--select-server) + SELECT_SERVER="1" + ;; + -h|--help) + FlagUsage + ;; + -*) + echo "Unknown option '$flag'." + echo + echo "---------------------- ---- --- -- -- -- - -" + echo + FlagUsage + ;; + *) + if [ "$BUILD_DIR_SET" = "0" ]; then + BUILD_DIR="$flag" + BUILD_DIR_SET="1" + else + echo "Excessive argument: '$flag'." + echo + echo "---------------------- ---- --- -- -- -- - -" + echo + FlagUsage + fi + ;; + esac +done + +## Check requirements +CheckBinary kdialog "" --no-dialog +CheckBinary svn "Version 1.1 or newer is needed." +CheckBinary kde-config "kde-config sometimes falls out of the \$PATH for some reason." +CheckBinary kreadconfig +CheckBinary kwriteconfig + +## Check the build directory +if [ -e "$BUILD_DIR" -a ! -f "$BUILD_DIR/.amarok-svn-dir" ]; then #if directory exist and isn't watermarked + if [ -d "$BUILD_DIR" ]; then + Dialog --warningyesno "The directory you choosed to build in ($BUILD_DIR) already exists, and it wasn't detected as an Amarok-svn directory.\nFiles in this directory can possibly be overwritten by the Amarok-svn procedures.\nDo you want to use this directory anyway?" + if [ "$?" != "0" ]; then #If the user said no, exit. + exit 1 + fi + else + Error "The build directory you have chosen exists, but it's not a directory!" + fi +fi + +## Check if user is root +if [ "`id -u`" = "0" ]; then #if user is root + Dialog --warningcontinuecancel "You are running Amarok-svn as root! This is not required, and generally not a good idea.\n(Amarok-svn will get root privileges by itself when needed, see the settings for details.)\nAre you sure you want to continue anyway?" --dontagain $RCFILE:root_warning + if [ "$?" != "0" ]; then #If the user said cancel. + exit 1 + fi +fi + + +## Settings +if [ -s "`kde-config --localprefix`/share/config/$RCFILE" -a "$RESET_CONF" != "1" ]; then #If the settings exists and the user doesn't want to change them. + + GET_LANG="`ReadConfig get_lang`" + if [ -z "$GET_LANG" ]; then #Save default value if empty + GET_LANG="en_US" + WriteConfig get_lang "$GET_LANG" + fi + CONF_FLAGS_RAW="`ReadConfig conf_flags`" + CONF_FLAGS="" + for flag in $CONF_FLAGS_RAW; do + CONF_FLAGS="$CONF_FLAGS `echo $flag | sed -e \"s/__/--/\"`" + done + HOW_ROOT="`ReadConfig how_root`" + if [ -z "$HOW_ROOT" ]; then #Save default value if empty + HOW_ROOT="kdesu" + WriteConfig how_root "$HOW_ROOT" + fi + USE_ID="`ReadConfig use_id`" + if [ -z "$USE_ID" ]; then #Save default value if empty + USE_ID="0" + WriteConfig use_id "$USE_ID" + fi + +else + + ## Language + AUTO_LANG="`kreadconfig --group Locale --key Language | sed -re \"s/:.+//\"`" + if [ "$AUTO_LANG" != "" ]; then #Generally, if the user is running KDE + Dialog --yesno "I detected that you are running KDE with language '$AUTO_LANG'.\nIf this is correct (and you want it that way), I will download localization and documentation for Amarok in that language.\nDo you want this language?" + fi + if [ "$?" = "0" -a "$AUTO_LANG" != "" ]; then #If the user said yes, and is running KDE... + GET_LANG="$AUTO_LANG" + else + Dialog --msgbox "Which language do you want to download localization and documentation for?\nA list of available languages is available at http://websvn.kde.org/trunk/l10n/ (It is CaSe sensitive!)\nIf you want to use the default language (American English), either leave this empty or set it to 'en_US' (it's not in the list above).\n(Click Ok to get to the input box.)" + GET_LANG="`Dialog --inputbox \"Specify language to download localization and documentation for\"`" + fi + if [ -z "$GET_LANG" ]; then + GET_LANG="en_US" + fi + WriteConfig get_lang "$GET_LANG" + + ## ./configure flags + Dialog --yesno "Do you want to use any extra configuration options (in addition to '--prefix=`kde-config --prefix` --enable-debug=full')?\nNo extra options is the default, and that works fine.\n(For a list of available flags, say yes and enter 'help' (CaSe insensitive) in the box, then wait for Amarok-svn to get to the configuration step (step 8).)" + if [ "$?" = "0" ]; then #If the user said yes + CONF_FLAGS_RAW="`Dialog --inputbox \"Specify extra configuration options to use\"`" + if [ "`echo $CONF_FLAGS_RAW | tr A-Z a-z`" != "help" ]; then + CONF_FLAGS="" + CONF_FLAGS_SAVE="" + for flag in $CONF_FLAGS_RAW; do + if [ "$flag" != "--help" ]; then + CONF_FLAGS="$CONF_FLAGS $flag" + CONF_FLAGS_SAVE="$CONF_FLAGS_SAVE `echo $flag | sed -e \"s/--/__/\"`" + fi + done + WriteConfig conf_flags "$CONF_FLAGS_SAVE" + else + CONF_HELP="true" + fi + fi + + ## What to use to get root privileges for installation? + HOW_ROOT="`kdialog --radiolist \"How do you want Amarok-svn to get root privileges for the install/uninstall commands?\" kdesu \"With 'kdesu' (default, choose this if unsure)\" on sudo \"With 'sudo'\" off \"su -c\" \"With 'su -c'\" off`" + if [ -z "$HOW_ROOT" ]; then #Fallback if the user pressed cancel + HOW_ROOT="kdesu" + fi + WriteConfig how_root "$HOW_ROOT" + + USE_ID="0" #Assume default answer + Dialog --yesno "Do you want to use build ID?\nThis feature is generally not needed, and often makes the compiling time longer.\nIf you use it, you can tell from the About box in Amarok when your revision was compiled.\nDefault answer is No, you can get this information from other places." + if [ "$?" = "0" ]; then #If the user said yes + USE_ID="1" + fi + WriteConfig use_id "$USE_ID" +fi + +if [ "$SELECT_SERVER" = "1" ]; then #SVN server selection + WriteConfig svn_server "`kdialog --title \"$KD_TITLE\" --inputbox \"Specify which SVN server you want to use. Default is 'svn://anonsvn.kde.org'.\"`" +fi + +SVN_SERVER="`ReadConfig svn_server`" +if [ -z "$SVN_SERVER" ]; then #Save default value if empty + SVN_SERVER="svn://anonsvn.kde.org" + WriteConfig svn_server "$SVN_SERVER" +fi + +## Start the installation process and show the settings +INST_START=`date +%s` +echo "Used configuration" +echo "--------------------" +echo "(Use --help to get information on how to change it.)" +echo + +echo -e "SVN server:\t\t\t\t\t$SVN_SERVER" +echo -e "Language for localization and documentation:\t$GET_LANG" +if [ "$CONF_HELP" != "true" -a "`echo $CONF_FLAGS`" ]; then + echo -e "Extra configuration options:\t\t\t`echo $CONF_FLAGS`" #`echo ...` strips the preceeding space. +fi +echo -e "Command for getting root privileges:\t\t$HOW_ROOT" +echo -en "Build ID:\t\t\t\t\t" +if [ "$USE_ID" = "1" ]; then + echo "On" +else + echo "Off" +fi +echo -e "Build directory:\t\t\t\t$BUILD_DIR" +echo -ne "Clean source tree:\t\t\t\t" +if [ "$CLEAN_BUILD" = "1" ]; then + echo "On" +else + echo "Off" +fi + +echo +echo "###################### #### ### ## ## ## # #" + +## Base checkout +echo +echo "# $C_STEP/$S_STEPS - Checking out base files." +svn co -N $SVN_SERVER/home/kde/branches/stable/extragear/multimedia $BUILD_DIR +if [ "$?" != "0" ]; then #If the command didn't finish successfully + Error "The SVN transfer didn't finish successfully." +fi +cd $BUILD_DIR +touch .amarok-svn-dir #Watermark the directory + +## Get unsermake, if not installed already +echo +let C_STEP=C_STEP+1 +echo "# $C_STEP/$S_STEPS - Getting unsermake." +if [ -x "`which unsermake`" ]; then #unsermake installed system wide? + echo "Unsermake is already installed system wide." +else + PATH="$BUILD_DIR/unsermake:$PATH" + if [ -x "`which unsermake`" ]; then #Is unsermake installed and in the $PATH now? + echo "Unsermake downloaded by this script. Checking for update." + else + echo "Unsermake wasn't found. Downloading it to '$BUILD_DIR/unsermake'." + fi + svn co -N $SVN_SERVER/home/kde/tags/unmaintained/3/unsermake + if [ "$?" != "0" ]; then #If the command didn't finish successfully + Error "The SVN transfer failed." + fi +fi + +## Store the old uninstall commands +echo +let C_STEP=C_STEP+1 +echo "# $C_STEP/$S_STEPS - Saving uninstall commands for your current revision." +if [ ! -f "Makefile" ]; then + echo "No current revision was found." +else + TMP_OLD_UNINFO="`mktemp`" + TEMP_FILES="$TMP_OLD_UNINFO" + unsermake -n uninstall > $TMP_OLD_UNINFO #Stored in a file so we can run it later, in a simple manner. + if [ "$?" != "0" ]; then #If the command didn't finish successfully + Error "Couldn't get uninstall commands for your current revision." + else + echo "Done." + fi +fi + +## Continue checkout +echo +let C_STEP=C_STEP+1 +echo "# $C_STEP/$S_STEPS - Checking out common SVN files." +svn co $SVN_SERVER/home/kde/branches/KDE/3.5/kde-common/admin #URL changed since KDE 3.5 branching +if [ "$?" != "0" ]; then #If the command didn't finish successfully + RemoveTemp + Error "The SVN transfer failed." +fi +echo +let C_STEP=C_STEP+1 +echo "# $C_STEP/$S_STEPS - Updating Amarok files." +svn up amarok +if [ "$?" != "0" ]; then #If the command didn't finish successfully + RemoveTemp + Error "The SVN transfer failed.\nIf the message from svn (in console) is something like 'amarok is not under version control', you need a never version of svn.\nAt least version 1.1 is needed." +fi +if [ "$USE_ID" = "1" ]; then + #Append build ID (date and time with no punctuation) to version + sed -re "s/^#define APP_VERSION \"(.*)-SVN.*\"/#define APP_VERSION \"\1-SVN-`date +%y%m%d%H%M`\"/" -i amarok/src/amarok.h + echo "Appended build ID to version number." +fi + +echo +let C_STEP=C_STEP+1 +echo "# $C_STEP/$S_STEPS - Getting localization and documentation:" +ENGD_STEP="" + +if [ "$GET_LANG" != "en_US" ]; then #If a language (not en_US) is selected + + ## Localization + echo "- # 1/4 - Checking if localization for selected language exists." + svn ls $SVN_SERVER/home/kde/trunk/l10n/$GET_LANG/messages/extragear-multimedia/amarok.po > /dev/null 2>&1 #Check if language exists + if [ "$?" != "0" ]; then #If the localization wasn't found + GET_LANG="en_US" + echo "WARNING: Localization for selected language was not found. Reverting to 'en_US'." + else #If localization exists + echo "- # 2/4 - Updating localization file." + if [ ! -d "po" ]; then + mkdir po + fi + cd po + echo "SUBDIRS = $GET_LANG" > Makefile.am + if [ ! -d $GET_LANG ]; then + mkdir $GET_LANG + fi + cd $GET_LANG + echo "KDE_LANG = $GET_LANG" > Makefile.am + echo "SUBDIRS = \$(AUTODIRS)" >> Makefile.am + echo "POFILES = AUTO" >> Makefile.am + echo "Downloading current localization file from server." + TMP_L10N="`mktemp`" + TMP_FILES="$TMP_FILES $TMP_L10N" + svn cat $SVN_SERVER/home/kde/trunk/l10n/$GET_LANG/messages/extragear-multimedia/amarok.po 2> /dev/null | tee $TMP_L10N > /dev/null + if [ "$?" != "0" ]; then #If the command didn't finish successfully + Error "The SVN transfer didn't finish successfully." + fi + if [ ! -s "$TMP_L10N" ]; then + echo "WARNING: The downloaded file was empty!" + echo "Some error must have occured." + rm -f Makefile.am + rm -f ../Makefile.am + echo "No localization downloaded, so no localization will be installed!" + elif [ -f "amarok.po" ]; then + if [ -z "`diff -q $TMP_L10N amarok.po 2>&1`" ]; then + echo "You already have the current version of the localization file." + else + echo "New localization file downloaded." + rm -f amarok.po + mv -f $TMP_L10N amarok.po + echo "Replaced the old copy with the downloaded version." + fi + else + mv -f $TMP_L10N amarok.po + echo "Localization file downloaded." + fi + cd ../.. + + ## Localized documentation + echo "- # 3/4 - Checking if localized documentation for selected language exists." + svn ls $SVN_SERVER/home/kde/trunk/l10n/$GET_LANG/docs/extragear-multimedia/amarok > /dev/null 2>&1 #Check if localized documentation exists + if [ "$?" != "0" ]; then #If the localized documentation wasn't found + GET_LANG="en_US" + echo "WARNING: Localized documentation for selected language was not found. Reverting to default (American English) documentation." + ENGD_STEP=" 4/4 -" + else #If a localized documentation exists + echo "- # 4/4 - Checking out localized documentation." + if [ ! -d "doc" ]; then + mkdir doc + fi + cd doc + echo "SUBDIRS = $GET_LANG" > Makefile.am + svn co $SVN_SERVER/home/kde/trunk/l10n/$GET_LANG/docs/extragear-multimedia/amarok $GET_LANG + if [ "$?" != "0" ]; then #If the command didn't finish successfully + Error "The SVN transfer failed." + fi + cd $GET_LANG + echo "KDE_LANG = $GET_LANG" > Makefile.am + echo "KDE_DOCS = amarok" >> Makefile.am + cd ../.. + + fi + fi +fi + +if [ "$GET_LANG" = "en_US" ]; then #If no language (en_US) is selected. This is a stand alone if-statement to enable fallbacks when selected language isn't found. + + ## Default (American English) documentation + echo "- #$ENGD_STEP Checking out default (American English) documentation." + if [ ! -d "doc" ]; then + mkdir doc + fi + cd doc + echo "SUBDIRS = en" > Makefile.am + svn co $SVN_SERVER/home/kde/trunk/extragear/multimedia/doc/amarok en + if [ "$?" != "0" ]; then #If the command didn't finish successfully + Error "The SVN transfer failed." + fi + cd en + echo "KDE_LANG = en" > Makefile.am + echo "KDE_DOCS = amarok" >> Makefile.am + cd ../.. + +fi + +## Preparation +echo +let C_STEP=C_STEP+1 +echo "# $C_STEP/$S_STEPS - Preparing for configuration. (This will take a while.)" +WANT_AUTOCONF="2.5" unsermake -f Makefile.cvs +if [ "$?" != "0" ]; then #If the command didn't finish successfully + Error "Preparation failed.\nProblems at this step are quite certainly problems with either unsermake or your automake configuration." +fi +echo -n "*** Patching the configure script to show when it fails... " +echo -e "\nif test \"\$all_tests\" = \"bad\"; then\n exit 1\nfi" >> configure #Does its job, albeit a little bit ugly... +if [ "$?" != "0" ]; then #If the command didn't finish successfully + echo "Fail. Amarok will compile even if some dependencies aren't met!" +else + echo "Done." +fi + +## Configuration help +if [ "$CONF_HELP" = "true" ]; then + TMP_CONF="`mktemp`" + TMP_FILES="$TMP_FILES $TMP_CONF" + echo -e "Configuration options\n" > $TMP_CONF + ./configure --help >> $TMP_CONF + KDTITLE=" :: Configuration options" Dialog --textbox $TMP_CONF 600 800 & #800x600 should be enough. Put it in the background (&) to make the user able to watch it while typing the selected options into the input box. + Dialog --yesno "Do you want to use any extra configuration options (in addition to '--prefix=`kde-config --prefix` --enable-debug=full')?\nNo extra options is the default, and that works fine.\n(Available options are displayed in another window right now.)" + if [ "$?" = "0" ]; then #If the user said yes + CONF_FLAGS_RAW="`kdialog --title \"$KD_TITLE\" --inputbox \"Specify extra configuration options to use\"`" + CONF_FLAGS="" + CONF_FLAGS_SAVE="" + for flag in $CONF_FLAGS_RAW; do + if [ "$flag" != "--help" ]; then + CONF_FLAGS="$CONF_FLAGS $flag" + CONF_FLAGS_SAVE="$CONF_FLAGS_SAVE `echo $flag | sed -e \"s/--/__/\"`" + fi + done + WriteConfig conf_flags "$CONF_FLAGS_SAVE" + if [ "`echo $CONF_FLAGS`" ]; then + echo -e "Extra configuration options:\t'`echo $CONF_FLAGS`'" + fi + fi +fi + +## Configuration +echo +let C_STEP=C_STEP+1 +echo "# $C_STEP/$S_STEPS - Configuring. (This will also take a while.)" +./configure --prefix=`kde-config --prefix` --enable-debug=full$CONF_FLAGS +if [ "$?" != "0" ]; then #If the command didn't finish successfully + Error "Configuration failed. Amarok was NOT installed/upgraded." +fi + +## Clean build dir if user wanted to. +if [ "$CLEAN_BUILD" = "1" ]; then + let C_STEP=C_STEP+1 + echo "# $C_STEP/$S_STEPS - Cleaning the source tree." + Clean +fi + +## Compilation +echo +let C_STEP=C_STEP+1 +echo "# $C_STEP/$S_STEPS - Compiling. (The time of this step depends on the number of new source files that were downloaded.)" +Compile + +## Compare uninstall commands and , if they differ, uninstall the old revision. +echo +let C_STEP=C_STEP+1 +echo "# $C_STEP/$S_STEPS - Comparing uninstall commands." +if [ ! -f "$TMP_OLD_UNINFO" ]; then + echo "No older revision was found." +else + TMP_NEW_UNINFO="`mktemp`" + TMP_FILES="$TMP_FILES $TMP_NEW_UNINFO" + unsermake -n uninstall > $TMP_NEW_UNINFO + if [ "$?" != "0" ]; then #If the command didn't finish successfully. + Error "Couldn't get uninstall commands for the new revision." + fi + UN_DIFF="`diff -q $TMP_OLD_UNINFO $TMP_NEW_UNINFO 2>&1`" + if [ "$?" != "0" -a "$?" != "1" ]; then #If the command didn't finish successfully. diff strangely (?) returns 1 when there are differences. + Error "Couldn't compare the uninstall commands." + elif [ -z "$UN_DIFF" ]; then #If diff was quiet + echo "No differences between your current revision and the new one were found." + else + echo "Differences in the uninstall commands were found, uninstalling your current revision." + echo "Executing '$HOW_ROOT' to get root privileges for uninstallation." + if [ "$HOW_ROOT" = "sudo" ]; then + echo "(You might need to enter your password now.)" + sudo bash $TMP_OLD_UNINFO + elif [ "$HOW_ROOT" = "su -c" ]; then + echo "(You probably have to enter the root password now.)" + su -c "bash $TMP_OLD_UNINFO" + else + kdesu -t bash $TMP_OLD_UNINFO + fi + #No error check here because it seems to trigger even when there is no error, I can't figure out why... + #It doesn't matter much if this step fails anyway... + fi +fi +RemoveTemp #From here on, no temp files are needed. + +## Installation +echo +let C_STEP=C_STEP+1 +echo "# $C_STEP/$S_STEPS - Installing files." +echo "Executing '$HOW_ROOT' to get root privileges for installation." +if [ "$HOW_ROOT" = "sudo" ]; then + echo "(You might need to enter your password now.)" + sudo `which unsermake` install #"which" here (and below) is needed because root doesn't use the user's $PATH +elif [ "$HOW_ROOT" = "su -c" ]; then + echo "(You probably have to enter the root password now.)" + su -c "`which unsermake` install" +else + kdesu -t `which unsermake` install +fi +if [ "$?" = "0" ]; then #If the command did finish successfully + #stopwatch. + let INST_TIME=`date +%s`-INST_START + let INST_M=INST_TIME/60 + let INST_S=INST_TIME%60 + echo + echo -e "# DONE - Amarok was successfully installed/updated after $INST_M minute(s) and $INST_S second(s)." + Dialog --msgbox "Done!\nAmarok was successfully installed/updated after $INST_M minute(s) and $INST_S second(s).\nCompilation took $COMP_M minute(s) and $COMP_S second(s).\nStart Amarok from your menu or by running the command \"amarok\"." + exit 0 #Exit succsessfully +else + echo + Error "Amarok was compiled but NOT installed.\nIf the errors above seem to be permission errors, you could try installing Amarok manually.\n(You also might want to have a look at the settings for this script, use --help to see how.)\nTo install manually, get root privileges in some way and then run 'unsermake install' in the '$BUILD_DIR' directory." +fi diff --git a/amarok/src/scripts/amarok_live/Makefile.am_ b/amarok/src/scripts/amarok_live/Makefile.am_ new file mode 100644 index 00000000..4a039610 --- /dev/null +++ b/amarok/src/scripts/amarok_live/Makefile.am_ @@ -0,0 +1,9 @@ +livedir = $(kde_datadir)/amarok/scripts/amarok_live + +live_SCRIPTS = amarok_live.py + +live_DATA = README \ + amarok.live.remaster.part1.sh \ + amarok.live.remaster.part2.sh + + diff --git a/amarok/src/scripts/amarok_live/README b/amarok/src/scripts/amarok_live/README new file mode 100644 index 00000000..7ff4e9f0 --- /dev/null +++ b/amarok/src/scripts/amarok_live/README @@ -0,0 +1,44 @@ +
    Amarok Live
    + +About:
    +This script lets you create your own Amarok live! cd with music on it. It is very simple to use: click configure, and choose the iso as well as a temporary work directory. Clicking ok will start the unpacking, and when it is done, you can add music to the cd by selecting tracks and using the right click menu.
    +
    +When you are done selecting tracks, click Create Remastered CD. the iso will be created in the work dir that you selected during configure.
    +
    +Dependencies:
    +A kernel with squashfs as either a module or compiled in.
    +squashfs-tools (see NOTE below)
    +cdrtools (for mkisofs)
    +Python 2.2
    +PyQt +1.5 gb of free space
    +Original Amarok live! iso
    +K3b (optional) for automatic burning
    +
    +License:
    +GPL V2
    +
    +Note:
    +If you are using the amarok_live-1.3.iso cd, (not amarok_live-1.3-update1.iso) then you will not be able to remaster successfully unless you are using squashfs-tools version 2.1. Version 2.2 will create a cd that cannot boot. Please make sure to downgrade, or download the updated version of the iso. An xdelta (binary patch) is available too instead of the full iso.
    +
    +CHANGELOG:
    +Version 0.3:
    + Added new Qt config dialog, much faster and more streamlined.
    +
    +Version 0.2:
    + Support for playlist transferring - shows up as a playlist on the livecd too!
    + Added Clear Music menu option.
    +
    +Version 0.1:
    + First released version
    + Support for most features.
    + K3B support.
    + BUG: If cancel is clicked on the initial dialogs, does not exit cleanly.
    +
    +Author:
    +Leo Franchi (lfranchi at gmail dot com)
    +Greg Meyer (greg at gkmweb dot com)
    +Mike Diehl (madpenguin8 at yahoo dot com)
    +
    +Based on a script by:
    +Ivan Kerekes (ikerekes at gmail dot com)
    diff --git a/amarok/src/scripts/amarok_live/amarok.live.remaster.part1.sh b/amarok/src/scripts/amarok_live/amarok.live.remaster.part1.sh new file mode 100644 index 00000000..2ae79905 --- /dev/null +++ b/amarok/src/scripts/amarok_live/amarok.live.remaster.part1.sh @@ -0,0 +1,151 @@ +#!/usr/bin/env bash +#set -x + +############################################################################ +# Disassemble an Amarok LiveCD iso file into a specified directory +# for the purpose of adding a users own music and putting it back together +# +# Based on a script authored by Ivan Kerekes +# and modified by Leo Franchi , Mike Diehl +# and Greg Meyer +# +# See the file called README for more information +############################################################################ +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +############################################################################ + + +cleanup () { + rm -rf $WORK/mklivecd + rm -rf $WORK/amarok.live + rm -rf $WORK/livecd_data* +} + +# We need to be root to mount the loopback +# check for root and exit if not + +if [ `/usr/bin/whoami` = 'root' ]; then + +iso=$2 + +if [ $iso = 0 ] ; then + exit +fi + +WORK=$1 + +if [ $WORK = 0 ] ; then + exit +fi + +# Determine if enough space + +redo=0 +enough=0 +tmp=$WORK +while [ "$redo" = "0" ]; do + while [ "$enough" = "0" ]; do # loops until found something + if [[ -n `df | grep $tmp` ]] ; then # we got it in df, find the space left + anotmp=`df | grep $tmp | sed "s~^\([^ ]*\) *\([^ ]*\) *\([^ ]*\) *\([^ ]*\) *\([^ ]*\) *\([^ ]*\)$~\4~"` # now we have a string, first item is free space in / + free=`echo $anotmp | sed "s~^\([^ ]*\) \(.*\)~\1~"` # get first space-delimited item +# echo "comparing" $free "to 1572864" + if [[ $free -gt 1572864 ]] ; then + enough=1 + redo=1 + break + else + dcop --all-users amarok playlist popupMessage "Not enough free space. Please select another folder." + break + fi + else + res=`echo "$tmp" | sed "s~/\(.*\)/\([^/]*\)$~/\1~"` + if [[ "$tmp" = "$res" ]]; then # last one, regexp fails here + tmp="/" + else #normal, removes one dir from path + tmp=$res + fi +# echo "new tmp: " $tmp + fi + done + if [[ "$redo" = "0" ]]; then + WORK=`kdialog --title "Choose working directory" --getexistingdirectory .` + tmp=$WORK + if [[ "$?" == 0 ]]; then + break + else + exit + fi + enough=0 + fi +done + +#Mount the iso if not already mounted +if [ ! -d "$DATADIR" ]; then + DATADIR=$WORK/livecd_data$$ + mkdir -p "$DATADIR" + mount -o loop "$iso" "$DATADIR" +fi + +# Make the working directories and blow out the initrd.gz into a separate directory + +mkdir -p $WORK/mklivecd/livecd +cp -a --preserve "$DATADIR"/* $WORK/mklivecd/livecd/ +mkdir -p $WORK/mklivecd/initrd.dir +mkdir -p $WORK/mklivecd/initrd.mnt +gunzip -c $WORK/mklivecd/livecd/isolinux/initrd.gz > $WORK/mklivecd/livecd/isolinux/initrd +mount -o loop $WORK/mklivecd/livecd/isolinux/initrd $WORK/mklivecd/initrd.mnt +(cd $WORK/mklivecd/initrd.mnt ; tar cf - .) | (cd $WORK/mklivecd/initrd.dir ; tar xf -) +umount $WORK/mklivecd/initrd.mnt +rm -f $WORK/mklivecd/livecd/isolinux/initrd + + +# cleanup all temporary files and directories + +umount "$DATADIR" 2>/dev/null >/dev/null +if [ "$?" = "0" ]; then rmdir $DATADIR; fi + +# at this point we unsquash the fs so the user can add their own music + +if [[ `cat /proc/filesystems | grep squash | wc -l` = 0 ]]; then + modprobe squashfs + if [[ `cat /proc/filesystems | grep squash | wc -l` = 0 ]]; then + + dcop --all-users amarok playlist popupMessage "You do not have squashfs support enabled. You need to have a patched kernel with squashfs. You can find more info about squashfs, and how to patch your kernel, here: http://tldp.org/HOWTO/SquashFS-HOWTO/" + + rm -rf $WORK/mklivecd + fi +fi + +mkdir $WORK/amarok.livecd/ +mount -o loop -t squashfs $WORK/mklivecd/livecd/livecd.sqfs $WORK/amarok.livecd/ + +# gotta copy it locally so the user can add files to it + +mkdir $WORK/amarok.live/ +dcop --all-users amarok playlist shortStatusMessage "Copying files now. Please be patient, this step takes a long time." +#echo +#echo "Please wait, copying in progress." +#echo +cp -a $WORK/amarok.livecd/* $WORK/amarok.live/ +umount $WORK/amarok.livecd/ +rmdir $WORK/amarok.livecd + +mkdir $WORK/amarok.live/home/amarok/.kde/share/apps/amarok/playlists +chown 500:500 $WORK/amarok.live/home/amarok/.kde/share/apps/amarok/playlists +find $WORK/amarok.live/home/amarok/.kde/share/apps/ -type d -print0 | xargs -0 chmod +x +chmod -R 777 $WORK/amarok.live/music/ +chmod -R 777 $WORK/amarok.live/home/amarok/.kde/share/apps/amarok +#chmod -R 777 $WORK/mklivecd/ + +dcop --all-users amarok playlist popupMessage "Copying done. To add music to the Amarok livecd, select the tracks you wish to add in the playlist, and select \"Add to livecd\" from the right click menu. Please do not add more than about 380 mb, as then the resulting ISO will be too large to fit on a CD-ROM. Once you are done, select Create Remastered CD. Enjoy!" + +else + +kdialog --title "Amarok livecd remaster" --sorry "You must run this script as root. Try running 'kdesu sh amarok.live.remaster.part1.sh' instead." + +fi diff --git a/amarok/src/scripts/amarok_live/amarok.live.remaster.part2.sh b/amarok/src/scripts/amarok_live/amarok.live.remaster.part2.sh new file mode 100644 index 00000000..dc0d421d --- /dev/null +++ b/amarok/src/scripts/amarok_live/amarok.live.remaster.part2.sh @@ -0,0 +1,79 @@ +#!/usr/bin/env bash + +############################################################################ +# Second part of the Amarok LiveCD remastering scripts +# Reassemble the iso after adding media files +# +# Based on a script authored by Ivan Kerekes +# and modified by Leo Franchi , Mike Diehl +# and Greg Meyer +# +# See the file called README for more information +############################################################################ +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +############################################################################ + +# script must be run as root, first check for and exit if not +# then resquash the users' new fs + +if [ `/usr/bin/whoami` = 'root' ]; then + + WORK=$1 + + dcop --all-users amarok playlist popupMessage "The music that you added is now being squashed and added to the livecd. Please be VERY patient as this step can take a LONG time." + + which mksquashfs + if [[ $? == 0 ]]; then + mksquashfs $WORK/amarok.live/ $WORK/mklivecd/livecd/livecd.sqfs -noappend + else + dcop --all-users amarok playlist popupMessage "Squashfs-tools not found! Make sure mksquashfs is in your root \$PATH" + exit + fi + + olddir=`pwd` + cd $WORK/mklivecd + dd if=/dev/zero of=livecd/isolinux/initrd bs=1k count=6646 + mke2fs -q -m 0 -F -N 1250 -s 1 livecd/isolinux/initrd + mount -o loop -t ext2 livecd/isolinux/initrd initrd.mnt + rm -rf initrd.mnt/lost+found + (cd initrd.dir ; tar cf - .) | (cd initrd.mnt ; tar xf -) + umount initrd.mnt + cd livecd/isolinux + gzip -f -9 initrd + cd .. + ll + rm -f livecd.iso + mkisofs -J -R -V "Livecd Test" -o $WORK/Amarok.live.custom.iso -b isolinux/isolinux.bin -c isolinux/boot.cat -no-emul-boot -boot-load-size 4 -boot-info-table . + cd $olddir +# mv $WORK/Amarok.live.custom.iso . + + +# Let's test for the presence of k3b, and if it is there load it up +# and burn the cd iso right away. If k3b is not present, show a kde +# dialog telling the user where the iso is. + + which k3b + + if [[ $? == 0 ]] ; then + k3b --cdimage $WORK/Amarok.live.custom.iso + else + kdialog --title "Amarok livecd remaster" --msgbox "Livecd creation is done. The new Amarok live image is in `pwd`, called Amarok.live.custom.iso. You can burn that with any standard burner and enjoy." + fi + + kdialog --title "Amarok livecd remaster" --yesno "Do you want to make more cds later? If so, please click yes, and you can simply add more songs. If you are done, click no and the temporary files will be erased. You will need to rerun configure to make another cd." + + if [[ $? = 1 ]]; then + rm -rf $WORK/mklivecd + rm -rf $WORK/amarok.* + echo "end" > /tmp/amarok.script + fi + + +else + kdialog --title "Amarok livecd remaster" --sorry "You must run this script as root. Try running 'kdesu sh amarok.live.remaster.part1.sh' instead." +fi diff --git a/amarok/src/scripts/amarok_live/amarok_live.py b/amarok/src/scripts/amarok_live/amarok_live.py new file mode 100755 index 00000000..2a1fca82 --- /dev/null +++ b/amarok/src/scripts/amarok_live/amarok_live.py @@ -0,0 +1,427 @@ +#!/usr/bin/env python + +############################################################################ +# Python wrapper script for running the Amarok LiveCD remastering scripts +# from within Amarok. Based on the Python-Qt template script for Amarok +# (c) 2005 Mark Kretschmann +# +# (c) 2005 Leo Franchi +# +# Depends on: Python 2.2, PyQt +############################################################################ +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +############################################################################ + +import ConfigParser +import os +import sys +import threading +import signal +from time import sleep + +try: + from qt import * +except: + os.popen( "kdialog --sorry 'PyQt (Qt bindings for Python) is required for this script.'" ) + raise + + +# Replace with real name +debug_prefix = "LiveCD Remastering" + + +class ConfigDialog ( QDialog ): + """ Configuration widget """ + + def __init__( self ): + QDialog.__init__( self ) + self.setWFlags( Qt.WDestructiveClose ) + self.setCaption("Amarok Live! Configuration") + + self.lay = QGridLayout( self, 3, 2) + + self.lay.addColSpacing( 0, 300 ) + + self.isopath = QLineEdit( self ) + self.isopath.setText( "Path to Amarok Live! iso" ) + self.tmppath = QLineEdit( self ) + self.tmppath.setText( "Temporary directory used, 2.5gb free needed" ) + + self.lay.addWidget( self.isopath, 0, 0 ) + self.lay.addWidget( self.tmppath, 1, 0 ) + + self.isobutton = QPushButton( self ) + self.isobutton.setText("Browse..." ) + self.tmpbutton = QPushButton( self ) + self.tmpbutton.setText("Browse..." ) + + self.cancel = QPushButton( self ) + self.cancel.setText( "Cancel" ) + self.ok = QPushButton( self ) + self.ok.setText( "Ok" ) + + self.lay.addWidget( self.isobutton, 0, 1 ) + self.lay.addWidget( self.tmpbutton, 1, 1 ) + self.lay.addWidget( self.cancel, 2, 1 ) + self.lay.addWidget( self.ok, 2, 0) + + self.connect( self.isobutton, SIGNAL( "clicked()" ), self.browseISO ) + self.connect( self.tmpbutton, SIGNAL( "clicked()" ), self.browsePath ) + + self.connect( self.ok, SIGNAL( "clicked()" ), self.save ) + self.connect( self.ok, SIGNAL( "clicked()" ), self.unpack ) +# self.connect( self.ok, SIGNAL( "clicked()" ), self.destroy ) + self.connect( self.cancel, SIGNAL( "clicked()" ), self, SLOT("reject()") ) + + self.adjustSize() + + path = None + try: + config = ConfigParser.ConfigParser() + config.read( "remasterrc" ) + path = config.get( "General", "path" ) + iso = config.get( "General", "iso") + + if not path == "": self.tmppath.setText(path) + if not iso == "": self.isopath.setText(iso) + except: + pass + + + + def save( self ): + """ Saves configuration to file """ + self.file = file( "remasterrc", 'w' ) + self.config = ConfigParser.ConfigParser() + self.config.add_section( "General" ) + self.config.set( "General", "path", self.tmppath.text() ) + self.config.set( "General", "iso", self.isopath.text() ) + self.config.write( self.file ) + self.file.close() + + self.accept() + + def clear(): + + self.file = file( "remasterrc", 'w' ) + self.config = ConfigParser.ConfigParser() + self.config.add_section( "General" ) + self.config.set( "General", "path", "" ) + self.config.set( "General", "iso", "" ) + self.config.write( self.file ) + self.file.close() + + def browseISO( self ): + + path = QFileDialog.getOpenFileName( "/home", + "CD Images (*.iso)", + self, + "iso choose dialogr", + "Choose ISO to remaster") + self.isopath.setText( path ) + + def browsePath( self ): + + tmp = QFileDialog.getExistingDirectory( "/home", + self, + "get tmp dir", + "Choose working directory", + 1) + self.tmppath.setText( tmp ) + + + def unpack( self ): + + # now the fun part, we run part 1 + fd = os.popen("kde-config --prefix", "r") + kdedir = fd.readline() + kdedir = kdedir.strip() + scriptdir = kdedir + "/share/apps/amarok/scripts/amarok_live" + fd.close() + + path, iso = self.readConfig() + os.system("kdesu -t sh %s/amarok.live.remaster.part1.sh %s %s" % (scriptdir, path, iso)) + #os.wait() + print "got path: %s" % path + + + + + def readConfig( self ) : + path = "" + iso = "" + try: + config = ConfigParser.ConfigParser() + config.read("remasterrc") + path = config.get("General", "path") + iso = config.get("General", "iso") + except: + pass + return (path, iso) + + + +class Notification( QCustomEvent ): + __super_init = QCustomEvent.__init__ + def __init__( self, str ): + + + self.__super_init(QCustomEvent.User + 1) + self.string = str + +class Remasterer( QApplication ): + """ The main application, also sets up the Qt event loop """ + + def __init__( self, args ): + QApplication.__init__( self, args ) + debug( "Started." ) + + # Start separate thread for reading data from stdin + self.stdinReader = threading.Thread( target = self.readStdin ) + self.stdinReader.start() + + self.readSettings() + + + # ugly hack, thanks mp8 anyway + os.system("dcop amarok script removeCustomMenuItem \"Amarok live\" \"Add playlist to livecd\"") + os.system("dcop amarok script removeCustomMenuItem \"Amarok live\" \"Add selected to livecd\"") + os.system("dcop amarok script removeCustomMenuItem \"Amarok live\" \"Create Remastered CD\"") + os.system("dcop amarok script removeCustomMenuItem \"Amarok live\" \"Clear Music on livecd\"") + + os.system("dcop amarok script addCustomMenuItem \"Amarok live\" \"Add playlist to livecd\"") + os.system("dcop amarok script addCustomMenuItem \"Amarok live\" \"Add selected to livecd\"") + os.system("dcop amarok script addCustomMenuItem \"Amarok live\" \"Create Remastered CD\"") + os.system("dcop amarok script addCustomMenuItem \"Amarok live\" \"Clear Music on livecd\"") + + + def readSettings( self ): + """ Reads settings from configuration file """ + + try: + path = config.get( "General", "path" ) + + except: + debug( "No config file found, using defaults." ) + + +############################################################################ +# Stdin-Reader Thread +############################################################################ + + def readStdin( self ): + """ Reads incoming notifications from stdin """ + + while True: + # Read data from stdin. Will block until data arrives. + line = sys.stdin.readline() + + if line: + qApp.postEvent( self, Notification(line) ) + else: + break + + +############################################################################ +# Notification Handling +############################################################################ + + def customEvent( self, notification ): + """ Handles notifications """ + + string = QString(notification.string) + debug( "Received notification: " + str( string ) ) + + if string.contains( "configure" ): + self.configure() + if string.contains( "stop"): + self.stop() + + elif string.contains( "customMenuClicked" ): + if "selected" in string: + self.copyTrack( string ) + elif "playlist" in string: + self.copyPlaylist() + elif "Create" in string: + self.createCD() + elif "Clear" in string: + self.clearCD() + + +# Notification callbacks. Implement these functions to react to specific notification +# events from Amarok: + + def configure( self ): + debug( "configuration" ) + + self.dia = ConfigDialog() + self.dia.show() + #self.connect( self.dia, SIGNAL( "destroyed()" ), self.readSettings ) + + def clearCD( self ): + + self.dia = ConfigDialog() + path, iso = self.dia.readConfig() + + os.system("rm -rf %s/amarok.live/music/* %s/amarok.live/playlist/* %s/amarok.live/home/amarok/.kde/share/apps/amarok/current.xml" % (path, path, path)) + + def onSignal( self, signum, stackframe ): + stop() + + def stop( self ): + + fd = open("/tmp/amarok.stop", "w") + fd.write( "stopping") + fd.close() + + os.system("dcop amarok script removeCustomMenuItem \"Amarok live\" \"Add playlist to livecd\"") + os.system("dcop amarok script removeCustomMenuItem \"Amarok live\" \"Add selected to livecd\"") + os.system("dcop amarok script removeCustomMenuItem \"Amarok live\" \"Create Remastered CD\"") + os.system("dcop amarok script removeCustomMenuItem \"Amarok live\" \"Clear Music on livecd\"") + + + def copyPlaylist( self ): + + self.dia = ConfigDialog() + path, iso = self.dia.readConfig() + if path == "": + os.system("dcop amarok playlist popupMessage 'Please run configure first.'") + return + + tmpfileloc = os.tmpnam() + os.system("dcop amarok playlist saveM3u '%s' false" % tmpfileloc) + tmpfile = open(tmpfileloc) + + import urllib + + files = "" + m3u = "" + for line in tmpfile.readlines(): + if line[0] != "#": + + line = line.strip() + + # get filename + name = line.split("/")[-1] + + #make url + url = "file://" + urllib.quote(line) + + #make path on livecd + livecdpath = "/music/" + name + + files += url + " " + m3u += livecdpath + "\n" + + tmpfile.close() + + files = files.strip() + + os.system("kfmclient copy %s file://%s/amarok.live/music/" % (files, path)) + + import random + suffix = random.randint(0,10000) +# os.system("mkdir %s/amarok.live/home/amarok/.kde/share/apps/amarok/playlists/" % path) + m3uOut = open("/tmp/amarok.live.%s.m3u" % suffix, 'w') + + m3u = m3u.strip() + m3uOut.write(m3u) + + m3uOut.close() + + os.system("mv /tmp/amarok.live.%s.m3u %s/amarok.live/playlist/" % (suffix,path)) + os.system("rm /tmp/amarok.live.%s.m3u" % suffix) + + + os.remove(tmpfileloc) + + def copyTrack( self, menuEvent ): + + event = str( menuEvent ) + debug( event ) + self.dia = ConfigDialog() + + path,iso = self.dia.readConfig() + if path == "": + os.system("kdialog --sorry 'You have not specified where the Amarok live iso is. Please click configure and do so first.'") + else: + # get the list of files. yes, its ugly. it works though. + #files = event.split(":")[-1][2:-1].split()[2:] + #trying out a new one + #files = event.split(":")[-1][3:-2].replace("\"Amarok live!\" \"add to livecd\" ", "").split("\" \"") + #and another + + files = event.replace("customMenuClicked: Amarok live Add selected to livecd", "").split() + + allfiles = "" + for file in files: + allfiles += file + " " + allfiles = allfiles.strip() + os.system("kfmclient copy %s file://%s/amarok.live/music/" % (allfiles, path)) + + def createCD( self ): + + self.dia = ConfigDialog() + path,iso = self.dia.readConfig() + if path == "": + os.system("kdialog --sorry 'You have not configured Amarok live! Please run configure.") + + fd = os.popen("kde-config --prefix", "r") + kdedir = fd.readline() + kdedir = kdedir.strip() + scriptdir = kdedir + "/share/apps/amarok/scripts/amarok_live" + fd.close() + + os.system("kdesu sh %s/amarok.live.remaster.part2.sh %s" % (scriptdir, path)) + + fd = open("/tmp/amarok.script", 'r') + y = fd.readline() + y = y.strip() + if y == "end": # user said no more, clear path + self.dia.clear() + fd.close() + + +############################################################################ + +def onSignal( signum, stackframe ): + fd = open("/tmp/amarok.stop", "w") + fd.write( "stopping") + fd.close() + + print 'STOPPING' + + os.system("dcop amarok script removeCustomMenuItem \"Amarok live\" \"Add playlist to livecd\"") + os.system("dcop amarok script removeCustomMenuItem \"Amarok live\" \"Add selected to livecd\"") + os.system("dcop amarok script removeCustomMenuItem \"Amarok live\" \"Create Remastered CD\"") + os.system("dcop amarok script removeCustomMenuItem \"Amarok live\" \"Clear Music on livecd\"") + + +def debug( message ): + """ Prints debug message to stdout """ + + print debug_prefix + " " + message + +def main(): + app = Remasterer( sys.argv ) + + # not sure if it works or not... playing it safe + dia = ConfigDialog() + + app.exec_loop() + +if __name__ == "__main__": + + mainapp = threading.Thread(target=main) + mainapp.start() + signal.signal(15, onSignal) + print signal.getsignal(15) + while 1: sleep(120) + + #main( sys.argv ) + diff --git a/amarok/src/scripts/amarok_live/livecd/amarok_live_scan.sh b/amarok/src/scripts/amarok_live/livecd/amarok_live_scan.sh new file mode 100755 index 00000000..d905c0da --- /dev/null +++ b/amarok/src/scripts/amarok_live/livecd/amarok_live_scan.sh @@ -0,0 +1,24 @@ +#!/bin/bash +############################################################################ +# Copyright (C) 2005 by Mike Diehl # +# madpenguin8@yahoo.com # +# # +# This program is free software; you can redistribute it and#or modify # +# it under the terms of the GNU General Public License as published by # +# the Free Software Foundation; either version 2 of the License, or # +# (at your option) any later version. # +# # +# This program is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU 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. # +############################################################################ + +sleep 5 +dcop amarok collection scanCollection +dcop amarok script stopScript amarok_live_scan.sh diff --git a/amarok/src/scripts/amarok_live/standalone/amarok.live.remaster.part1.sh b/amarok/src/scripts/amarok_live/standalone/amarok.live.remaster.part1.sh new file mode 100644 index 00000000..1376cf29 --- /dev/null +++ b/amarok/src/scripts/amarok_live/standalone/amarok.live.remaster.part1.sh @@ -0,0 +1,139 @@ +#!/usr/bin/env bash +#set -x + +############################################################################ +# Disassemble an amaroK LiveCD iso file into a specified directory +# for the purpose of adding a users own music and putting it back together +# +# Based on a script authored by Ivan Kerekes +# and modified by Leo Franchi , Mike Diehl +# and Greg Meyer +# +# See the file called README for more information +############################################################################ +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +############################################################################ + +# We need to be root to mount the loopback +# check for root and exit if not + +if [ `/usr/bin/whoami` = 'root' ]; then + +kdialog --title "amaroK livecd remaster" --yesno "Welcome to the amaroK live cd remaster utility. The first step is to select the iso image, would you like to continue." + +if [ $? = 0 ] ; then + +iso=`kdialog --getopenfilename /home "*.iso"` + +WORK=`kdialog --title "Choose working directory" --getexistingdirectory .` + +if [ $WORK = 0 ] ; then + exit; +fi + +# Determine if enough space + +redo=0 +enough=0 +tmp=$WORK +while [ "$redo" = "0" ]; do + while [ "$enough" = "0" ]; do # loops until found something + if [[ -n `df | grep $tmp` ]] ; then # we got it in df, find the space left + anotmp=`df | grep $tmp | sed "s~^\([^ ]*\) *\([^ ]*\) *\([^ ]*\) *\([^ ]*\) *\([^ ]*\) *\([^ ]*\)$~\4~"` # now we have a string, first item is free space in / + free=`echo $anotmp | sed "s~^\([^ ]*\) \(.*\)~\1~"` # get first space-delimited item + echo "comparing" $free "to 1572864" + if [[ $free -gt 1572864 ]] ; then + enough=1 + redo=1 + break + else + kdialog --sorry "Not enough free space. Please select another folder." + break + fi + else + res=`echo "$tmp" | sed "s~/\(.*\)/\([^/]*\)$~/\1~"` + if [[ "$tmp" = "$res" ]]; then # last one, regexp fails here + tmp="/" + else #normal, removes one dir from path + tmp=$res + fi + echo "new tmp: " $tmp + fi + done + if [[ "$redo" = "0" ]]; then + WORK=`kdialog --title "Choose working directory" --getexistingdirectory .` + tmp=$WORK + if [[ "$?" == 0 ]]; then + break + else + exit + fi + enough=0 + fi +done + +# Mount the iso if not already mounted +if [ ! -d "$DATADIR" ]; then + DATADIR=$WORK/livecd_data$$ + mkdir -p "$DATADIR" + mount -o loop "$iso" "$DATADIR" +fi + +# Make the working directories and blow out the initrd.gz into a separate directory + +mkdir -p $WORK/mklivecd/livecd +cp -a --preserve "$DATADIR"/* $WORK/mklivecd/livecd/ +mkdir -p $WORK/mklivecd/initrd.dir +mkdir -p $WORK/mklivecd/initrd.mnt +gunzip -c $WORK/mklivecd/livecd/isolinux/initrd.gz > $WORK/mklivecd/livecd/isolinux/initrd +mount -o loop $WORK/mklivecd/livecd/isolinux/initrd $WORK/mklivecd/initrd.mnt +(cd $WORK/mklivecd/initrd.mnt ; tar cf - .) | (cd $WORK/mklivecd/initrd.dir ; tar xf -) +umount $WORK/mklivecd/initrd.mnt +rm -f $WORK/mklivecd/livecd/isolinux/initrd + + +# cleanup all temporary files and directories + +umount "$DATADIR" 2>/dev/null >/dev/null +if [ "$?" = "0" ]; then rmdir $DATADIR; fi + +# at this point we unsquash the fs so the user can add their own music + +if [[ `cat /proc/filesystems | grep squash | wc -l` = 0 ]]; then + modprobe squashfs + if [[ `cat /proc/filesystems | grep squash | wc -l` = 0 ]]; then + + kdialog --title "amaroK livecd remaster" --error "You do not have squashfs support enabled. You need to have a patched kernel with squashfs. You can find more info about squashfs, and how to patch your kernel, here: http://tldp.org/HOWTO/SquashFS-HOWTO/" + + rm -rf $WORK/mklivecd + fi +fi + +mkdir $WORK/amarok.livecd/ +mount -o loop -t squashfs $WORK/mklivecd/livecd/livecd.sqfs $WORK/amarok.livecd/ + +# gotta copy it locally so the user can add files to it + +mkdir $WORK/amarok.live/ +kdialog --title "amaroK livecd remaster" --msgbox "Copying files now. Please be patient, this step takes a long time." +echo +echo "Please wait, copying in progress." +echo +cp -a $WORK/amarok.livecd/* $WORK/amarok.live/ +umount $WORK/amarok.livecd/ +rmdir $WORK/amarok.livecd + +kdialog --title "amaroK livecd remaster" --msgbox "Copying done. To add music to the amaroK livecd, place additional music in /tmp/amarok.live/music/ Please do not add more than about 380 mb, as then the resulting ISO will be too large to fit on a CD-ROM. Once you are done, run the amarok.live.remaster.part2.sh script and you are finished!." + +fi + +else + +kdialog --title "amaroK livecd remaster" --sorry "You must run this script as root. Try running 'kdesu sh amarok.live.remaster.part1.sh' instead." + +fi diff --git a/amarok/src/scripts/amarok_live/standalone/amarok.live.remaster.part2.sh b/amarok/src/scripts/amarok_live/standalone/amarok.live.remaster.part2.sh new file mode 100644 index 00000000..9aadade9 --- /dev/null +++ b/amarok/src/scripts/amarok_live/standalone/amarok.live.remaster.part2.sh @@ -0,0 +1,63 @@ +#!/usr/bin/env bash + +############################################################################ +# Second part of the amaroK LiveCD remastering scripts +# Reassemble the iso after adding media files +# +# Based on a script authored by Ivan Kerekes +# and modified by Leo Franchi , Mike Diehl +# and Greg Meyer +# +# See the file called README for more information +############################################################################ +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +############################################################################ + +# script must be run as root, first check for and exit if not +# then resquash the users' new fs + +if [ `/usr/bin/whoami` = 'root' ]; then + + WORK=`kdialog --title "Choose working directory" --getexistingdirectory .` + + kdialog --title "amaroK livecd remaster" --msgbox "The music that you added is now being squashed and added to the livecd. Please be VERY patient as this step can take a LONG time." + + mksquashfs $WORK/amarok.live/ $WORK/mklivecd/livecd/livecd.sqfs -noappend + + + olddir=`pwd` + cd $WORK/mklivecd + dd if=/dev/zero of=livecd/isolinux/initrd bs=1k count=4646 + mke2fs -q -m 0 -F -N 1250 -s 1 livecd/isolinux/initrd + mount -o loop -t ext2 livecd/isolinux/initrd initrd.mnt + rm -rf initrd.mnt/lost+found + (cd initrd.dir ; tar cf - .) | (cd initrd.mnt ; tar xf -) + umount initrd.mnt + cd livecd/isolinux + gzip -f -9 initrd + cd .. + ll + rm -f livecd.iso + mkisofs -J -R -V "Livecd Test" -o $WORK/amaroK.live.custom.iso -b isolinux/isolinux.bin -c isolinux/boot.cat -no-emul-boot -boot-load-size 4 -boot-info-table . + cd $olddir + mv $WORK/amaroK.live.custom.iso . + + kdialog --title "amaroK livecd remaster" --yesno "Livecd creation is done. The new amaroK live image is in `pwd`, called amaroK.live.custom.iso. You can burn that with any standard burner and enjoy. + + Do you want to make more cds later? If so, please click yes, and you can simply re-run part2 of this script. If you are done, click no and the temporary files will be erased. You will need to rerun part1 to make another cd." + + if [[ $? = 1 ]]; then + rm -rf $WORK/mklivecd/ + rm -rf $WORK/amarok.live/ + fi + + +else + kdialog --title "amaroK livecd remaster" --sorry "You must run this script as root. Try running 'kdesu sh amarok.live.remaster.part1.sh' instead." + +fi diff --git a/amarok/src/scripts/common/Makefile.am b/amarok/src/scripts/common/Makefile.am new file mode 100644 index 00000000..259d9b45 --- /dev/null +++ b/amarok/src/scripts/common/Makefile.am @@ -0,0 +1,5 @@ +commondir = $(kde_datadir)/amarok/scripts/common + +common_DATA = Zeroconf.py Publisher.py + + diff --git a/amarok/src/scripts/common/Publisher.py b/amarok/src/scripts/common/Publisher.py new file mode 100644 index 00000000..06f28e59 --- /dev/null +++ b/amarok/src/scripts/common/Publisher.py @@ -0,0 +1,56 @@ +############################################################################ +# Zeroconf support - publishing configured streams +# (c) 2005 Jakub Stachowski +# +# Depends on: Python 2.2, pyzeroconf 0.12+metaservice patch +############################################################################ +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +############################################################################ +import Zeroconf +import socket +from string import split + +publisher = None # SIGTERM handler must be able to reach this + +class Publisher: + + active = False + zeroconf = None + localip = None + localhostname = None + + def services(self): # override this to provide list of services to register + return [] + + def run(self): + + self.localhostname = split(socket.gethostname(),'.')[0]+'.local.' + try: + self.localip = socket.gethostbyname(self.localhostname) + self.zeroconf = Zeroconf.Zeroconf(self.localip) + except: + return + self.active = True + + toRegister = self.services() + for i in toRegister: + service = Zeroconf.ServiceInfo( + i["type"]+".local.", + i["name"]+"."+i["type"]+".local.", + socket.inet_aton(self.localip), + i["port"], + 0, + 0, + i["properties"], + self.localhostname) + self.zeroconf.registerService(service) + + def shutdown(self): + if self.active: self.zeroconf.close() + + diff --git a/amarok/src/scripts/common/Zeroconf.py b/amarok/src/scripts/common/Zeroconf.py new file mode 100644 index 00000000..fa508393 --- /dev/null +++ b/amarok/src/scripts/common/Zeroconf.py @@ -0,0 +1,1578 @@ +""" Multicast DNS Service Discovery for Python, v0.12 + Copyright (C) 2003, Paul Scott-Murphy + + This module provides a framework for the use of DNS Service Discovery + using IP multicast. It has been tested against the JRendezvous + implementation from StrangeBerry, + and against the mDNSResponder from Mac OS X 10.3.8. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Steet, Fifth Floor, Boston, MA 02110-1301 USA + +""" + +""" patch for meta-service (_services._dns-sd._udp) publishing """ + +"""0.12 update - allow selection of binding interface + typo fix - Thanks A. M. Kuchlingi + removed all use of word 'Rendezvous' - this is an API change""" + +"""0.11 update - correction to comments for addListener method + support for new record types seen from OS X + - IPv6 address + - hostinfo + ignore unknown DNS record types + fixes to name decoding + works alongside other processes using port 5353 (e.g. on Mac OS X) + tested against Mac OS X 10.3.2's mDNSResponder + corrections to removal of list entries for service browser""" + +"""0.10 update - Jonathon Paisley contributed these corrections: + always multicast replies, even when query is unicast + correct a pointer encoding problem + can now write records in any order + traceback shown on failure + better TXT record parsing + server is now separate from name + can cancel a service browser + + modified some unit tests to accommodate these changes""" + +"""0.09 update - remove all records on service unregistration + fix DOS security problem with readName""" + +"""0.08 update - changed licensing to LGPL""" + +"""0.07 update - faster shutdown on engine + pointer encoding of outgoing names + ServiceBrowser now works + new unit tests""" + +"""0.06 update - small improvements with unit tests + added defined exception types + new style objects + fixed hostname/interface problem + fixed socket timeout problem + fixed addServiceListener() typo bug + using select() for socket reads + tested on Debian unstable with Python 2.2.2""" + +"""0.05 update - ensure case insensitivty on domain names + support for unicast DNS queries""" + +"""0.04 update - added some unit tests + added __ne__ adjuncts where required + ensure names end in '.local.' + timeout on receiving socket for clean shutdown""" + +__author__ = "Paul Scott-Murphy" +__email__ = "paul at scott dash murphy dot com" +__version__ = "0.12" + +import string +import time +import struct +import socket +import threading +import select +import traceback + +__all__ = ["Zeroconf", "ServiceInfo", "ServiceBrowser"] + +# hook for threads + +globals()['_GLOBAL_DONE'] = 0 + +# Some timing constants + +_UNREGISTER_TIME = 125 +_CHECK_TIME = 175 +_REGISTER_TIME = 225 +_LISTENER_TIME = 200 +_BROWSER_TIME = 500 + +# Some DNS constants + +_MDNS_ADDR = '224.0.0.251' +_MDNS_PORT = 5353; +_DNS_PORT = 53; +_DNS_TTL = 60 * 60; # one hour default TTL + +_MAX_MSG_TYPICAL = 1460 # unused +_MAX_MSG_ABSOLUTE = 8972 + +_FLAGS_QR_MASK = 0x8000 # query response mask +_FLAGS_QR_QUERY = 0x0000 # query +_FLAGS_QR_RESPONSE = 0x8000 # response + +_FLAGS_AA = 0x0400 # Authorative answer +_FLAGS_TC = 0x0200 # Truncated +_FLAGS_RD = 0x0100 # Recursion desired +_FLAGS_RA = 0x8000 # Recursion available + +_FLAGS_Z = 0x0040 # Zero +_FLAGS_AD = 0x0020 # Authentic data +_FLAGS_CD = 0x0010 # Checking disabled + +_CLASS_IN = 1 +_CLASS_CS = 2 +_CLASS_CH = 3 +_CLASS_HS = 4 +_CLASS_NONE = 254 +_CLASS_ANY = 255 +_CLASS_MASK = 0x7FFF +_CLASS_UNIQUE = 0x8000 + +_TYPE_A = 1 +_TYPE_NS = 2 +_TYPE_MD = 3 +_TYPE_MF = 4 +_TYPE_CNAME = 5 +_TYPE_SOA = 6 +_TYPE_MB = 7 +_TYPE_MG = 8 +_TYPE_MR = 9 +_TYPE_NULL = 10 +_TYPE_WKS = 11 +_TYPE_PTR = 12 +_TYPE_HINFO = 13 +_TYPE_MINFO = 14 +_TYPE_MX = 15 +_TYPE_TXT = 16 +_TYPE_AAAA = 28 +_TYPE_SRV = 33 +_TYPE_ANY = 255 + +# Mapping constants to names + +_CLASSES = { _CLASS_IN : "in", + _CLASS_CS : "cs", + _CLASS_CH : "ch", + _CLASS_HS : "hs", + _CLASS_NONE : "none", + _CLASS_ANY : "any" } + +_TYPES = { _TYPE_A : "a", + _TYPE_NS : "ns", + _TYPE_MD : "md", + _TYPE_MF : "mf", + _TYPE_CNAME : "cname", + _TYPE_SOA : "soa", + _TYPE_MB : "mb", + _TYPE_MG : "mg", + _TYPE_MR : "mr", + _TYPE_NULL : "null", + _TYPE_WKS : "wks", + _TYPE_PTR : "ptr", + _TYPE_HINFO : "hinfo", + _TYPE_MINFO : "minfo", + _TYPE_MX : "mx", + _TYPE_TXT : "txt", + _TYPE_AAAA : "quada", + _TYPE_SRV : "srv", + _TYPE_ANY : "any" } + +# utility functions + +def currentTimeMillis(): + """Current system time in milliseconds""" + return time.time() * 1000 + +# Exceptions + +class NonLocalNameException(Exception): + pass + +class NonUniqueNameException(Exception): + pass + +class NamePartTooLongException(Exception): + pass + +class AbstractMethodException(Exception): + pass + +class BadTypeInNameException(Exception): + pass + +# implementation classes + +class DNSEntry(object): + """A DNS entry""" + + def __init__(self, name, type, clazz): + self.key = string.lower(name) + self.name = name + self.type = type + self.clazz = clazz & _CLASS_MASK + self.unique = (clazz & _CLASS_UNIQUE) != 0 + + def __eq__(self, other): + """Equality test on name, type, and class""" + if isinstance(other, DNSEntry): + return self.name == other.name and self.type == other.type and self.clazz == other.clazz + return 0 + + def __ne__(self, other): + """Non-equality test""" + return not self.__eq__(other) + + def getClazz(self, clazz): + """Class accessor""" + try: + return _CLASSES[clazz] + except: + return "?(%s)" % (clazz) + + def getType(self, type): + """Type accessor""" + try: + return _TYPES[type] + except: + return "?(%s)" % (type) + + def toString(self, hdr, other): + """String representation with additional information""" + result = "%s[%s,%s" % (hdr, self.getType(self.type), self.getClazz(self.clazz)) + if self.unique: + result += "-unique," + else: + result += "," + result += self.name + if other is not None: + result += ",%s]" % (other) + else: + result += "]" + return result + +class DNSQuestion(DNSEntry): + """A DNS question entry""" + + def __init__(self, name, type, clazz): + if not name.endswith(".local."): + raise NonLocalNameException + DNSEntry.__init__(self, name, type, clazz) + + def answeredBy(self, rec): + """Returns true if the question is answered by the record""" + return self.clazz == rec.clazz and (self.type == rec.type or self.type == _TYPE_ANY) and self.name == rec.name + + def __repr__(self): + """String representation""" + return DNSEntry.toString(self, "question", None) + + +class DNSRecord(DNSEntry): + """A DNS record - like a DNS entry, but has a TTL""" + + def __init__(self, name, type, clazz, ttl): + DNSEntry.__init__(self, name, type, clazz) + self.ttl = ttl + self.created = currentTimeMillis() + + def __eq__(self, other): + """Tests equality as per DNSRecord""" + if isinstance(other, DNSRecord): + return DNSEntry.__eq__(self, other) + return 0 + + def suppressedBy(self, msg): + """Returns true if any answer in a message can suffice for the + information held in this record.""" + for record in msg.answers: + if self.suppressedByAnswer(record): + return 1 + return 0 + + def suppressedByAnswer(self, other): + """Returns true if another record has same name, type and class, + and if its TTL is at least half of this record's.""" + if self == other and other.ttl > (self.ttl / 2): + return 1 + return 0 + + def getExpirationTime(self, percent): + """Returns the time at which this record will have expired + by a certain percentage.""" + return self.created + (percent * self.ttl * 10) + + def getRemainingTTL(self, now): + """Returns the remaining TTL in seconds.""" + return max(0, (self.getExpirationTime(100) - now) / 1000) + + def isExpired(self, now): + """Returns true if this record has expired.""" + return self.getExpirationTime(100) <= now + + def isStale(self, now): + """Returns true if this record is at least half way expired.""" + return self.getExpirationTime(50) <= now + + def resetTTL(self, other): + """Sets this record's TTL and created time to that of + another record.""" + self.created = other.created + self.ttl = other.ttl + + def write(self, out): + """Abstract method""" + raise AbstractMethodException + + def toString(self, other): + """String representation with addtional information""" + arg = "%s/%s,%s" % (self.ttl, self.getRemainingTTL(currentTimeMillis()), other) + return DNSEntry.toString(self, "record", arg) + +class DNSAddress(DNSRecord): + """A DNS address record""" + + def __init__(self, name, type, clazz, ttl, address): + DNSRecord.__init__(self, name, type, clazz, ttl) + self.address = address + + def write(self, out): + """Used in constructing an outgoing packet""" + out.writeString(self.address, len(self.address)) + + def __eq__(self, other): + """Tests equality on address""" + if isinstance(other, DNSAddress): + return self.address == other.address + return 0 + + def __repr__(self): + """String representation""" + try: + return socket.inet_ntoa(self.address) + except: + return self.address + +class DNSHinfo(DNSRecord): + """A DNS host information record""" + + def __init__(self, name, type, clazz, ttl, cpu, os): + DNSRecord.__init__(self, name, type, clazz, ttl) + self.cpu = cpu + self.os = os + + def write(self, out): + """Used in constructing an outgoing packet""" + out.writeString(self.cpu, len(self.cpu)) + out.writeString(self.os, len(self.os)) + + def __eq__(self, other): + """Tests equality on cpu and os""" + if isinstance(other, DNSHinfo): + return self.cpu == other.cpu and self.os == other.os + return 0 + + def __repr__(self): + """String representation""" + return self.cpu + " " + self.os + +class DNSPointer(DNSRecord): + """A DNS pointer record""" + + def __init__(self, name, type, clazz, ttl, alias): + DNSRecord.__init__(self, name, type, clazz, ttl) + self.alias = alias + + def write(self, out): + """Used in constructing an outgoing packet""" + out.writeName(self.alias) + + def __eq__(self, other): + """Tests equality on alias""" + if isinstance(other, DNSPointer): + return self.alias == other.alias + return 0 + + def __repr__(self): + """String representation""" + return self.toString(self.alias) + +class DNSText(DNSRecord): + """A DNS text record""" + + def __init__(self, name, type, clazz, ttl, text): + DNSRecord.__init__(self, name, type, clazz, ttl) + self.text = text + + def write(self, out): + """Used in constructing an outgoing packet""" + out.writeString(self.text, len(self.text)) + + def __eq__(self, other): + """Tests equality on text""" + if isinstance(other, DNSText): + return self.text == other.text + return 0 + + def __repr__(self): + """String representation""" + if len(self.text) > 10: + return self.toString(self.text[:7] + "...") + else: + return self.toString(self.text) + +class DNSService(DNSRecord): + """A DNS service record""" + + def __init__(self, name, type, clazz, ttl, priority, weight, port, server): + DNSRecord.__init__(self, name, type, clazz, ttl) + self.priority = priority + self.weight = weight + self.port = port + self.server = server + + def write(self, out): + """Used in constructing an outgoing packet""" + out.writeShort(self.priority) + out.writeShort(self.weight) + out.writeShort(self.port) + out.writeName(self.server) + + def __eq__(self, other): + """Tests equality on priority, weight, port and server""" + if isinstance(other, DNSService): + return self.priority == other.priority and self.weight == other.weight and self.port == other.port and self.server == other.server + return 0 + + def __repr__(self): + """String representation""" + return self.toString("%s:%s" % (self.server, self.port)) + +class DNSIncoming(object): + """Object representation of an incoming DNS packet""" + + def __init__(self, data): + """Constructor from string holding bytes of packet""" + self.offset = 0 + self.data = data + self.questions = [] + self.answers = [] + self.numQuestions = 0 + self.numAnswers = 0 + self.numAuthorities = 0 + self.numAdditionals = 0 + + self.readHeader() + self.readQuestions() + self.readOthers() + + def readHeader(self): + """Reads header portion of packet""" + format = '!HHHHHH' + length = struct.calcsize(format) + info = struct.unpack(format, self.data[self.offset:self.offset+length]) + self.offset += length + + self.id = info[0] + self.flags = info[1] + self.numQuestions = info[2] + self.numAnswers = info[3] + self.numAuthorities = info[4] + self.numAdditionals = info[5] + + def readQuestions(self): + """Reads questions section of packet""" + format = '!HH' + length = struct.calcsize(format) + for i in range(0, self.numQuestions): + name = self.readName() + info = struct.unpack(format, self.data[self.offset:self.offset+length]) + self.offset += length + + question = DNSQuestion(name, info[0], info[1]) + self.questions.append(question) + + def readInt(self): + """Reads an integer from the packet""" + format = '!I' + length = struct.calcsize(format) + info = struct.unpack(format, self.data[self.offset:self.offset+length]) + self.offset += length + return info[0] + + def readCharacterString(self): + """Reads a character string from the packet""" + length = ord(self.data[self.offset]) + self.offset += 1 + return self.readString(length) + + def readString(self, len): + """Reads a string of a given length from the packet""" + format = '!' + str(len) + 's' + length = struct.calcsize(format) + info = struct.unpack(format, self.data[self.offset:self.offset+length]) + self.offset += length + return info[0] + + def readUnsignedShort(self): + """Reads an unsigned short from the packet""" + format = '!H' + length = struct.calcsize(format) + info = struct.unpack(format, self.data[self.offset:self.offset+length]) + self.offset += length + return info[0] + + def readOthers(self): + """Reads the answers, authorities and additionals section of the packet""" + format = '!HHiH' + length = struct.calcsize(format) + n = self.numAnswers + self.numAuthorities + self.numAdditionals + for i in range(0, n): + domain = self.readName() + info = struct.unpack(format, self.data[self.offset:self.offset+length]) + self.offset += length + + rec = None + if info[0] == _TYPE_A: + rec = DNSAddress(domain, info[0], info[1], info[2], self.readString(4)) + elif info[0] == _TYPE_CNAME or info[0] == _TYPE_PTR: + rec = DNSPointer(domain, info[0], info[1], info[2], self.readName()) + elif info[0] == _TYPE_TXT: + rec = DNSText(domain, info[0], info[1], info[2], self.readString(info[3])) + elif info[0] == _TYPE_SRV: + rec = DNSService(domain, info[0], info[1], info[2], self.readUnsignedShort(), self.readUnsignedShort(), self.readUnsignedShort(), self.readName()) + elif info[0] == _TYPE_HINFO: + rec = DNSHinfo(domain, info[0], info[1], info[2], self.readCharacterString(), self.readCharacterString()) + elif info[0] == _TYPE_AAAA: + rec = DNSAddress(domain, info[0], info[1], info[2], self.readString(16)) + else: + # Try to ignore types we don't know about + # this may mean the rest of the name is + # unable to be parsed, and may show errors + # so this is left for debugging. New types + # encountered need to be parsed properly. + # + #print "UNKNOWN TYPE = " + str(info[0]) + #raise BadTypeInNameException + pass + + if rec is not None: + self.answers.append(rec) + + def isQuery(self): + """Returns true if this is a query""" + return (self.flags & _FLAGS_QR_MASK) == _FLAGS_QR_QUERY + + def isResponse(self): + """Returns true if this is a response""" + return (self.flags & _FLAGS_QR_MASK) == _FLAGS_QR_RESPONSE + + def readUTF(self, offset, len): + """Reads a UTF-8 string of a given length from the packet""" + result = self.data[offset:offset+len].decode('utf-8') + return result + + def readName(self): + """Reads a domain name from the packet""" + result = '' + off = self.offset + next = -1 + first = off + + while 1: + len = ord(self.data[off]) + off += 1 + if len == 0: + break + t = len & 0xC0 + if t == 0x00: + result = ''.join((result, self.readUTF(off, len) + '.')) + off += len + elif t == 0xC0: + if next < 0: + next = off + 1 + off = ((len & 0x3F) << 8) | ord(self.data[off]) + if off >= first: + raise "Bad domain name (circular) at " + str(off) + first = off + else: + raise "Bad domain name at " + str(off) + + if next >= 0: + self.offset = next + else: + self.offset = off + + return result + + +class DNSOutgoing(object): + """Object representation of an outgoing packet""" + + def __init__(self, flags, multicast = 1): + self.finished = 0 + self.id = 0 + self.multicast = multicast + self.flags = flags + self.names = {} + self.data = [] + self.size = 12 + + self.questions = [] + self.answers = [] + self.authorities = [] + self.additionals = [] + + def addQuestion(self, record): + """Adds a question""" + self.questions.append(record) + + def addAnswer(self, inp, record): + """Adds an answer""" + if not record.suppressedBy(inp): + self.addAnswerAtTime(record, 0) + + def addAnswerAtTime(self, record, now): + """Adds an answer if if does not expire by a certain time""" + if record is not None: + if now == 0 or not record.isExpired(now): + self.answers.append((record, now)) + + def addAuthorativeAnswer(self, record): + """Adds an authoritative answer""" + self.authorities.append(record) + + def addAdditionalAnswer(self, record): + """Adds an additional answer""" + self.additionals.append(record) + + def writeByte(self, value): + """Writes a single byte to the packet""" + format = '!c' + self.data.append(struct.pack(format, chr(value))) + self.size += 1 + + def insertShort(self, index, value): + """Inserts an unsigned short in a certain position in the packet""" + format = '!H' + self.data.insert(index, struct.pack(format, value)) + self.size += 2 + + def writeShort(self, value): + """Writes an unsigned short to the packet""" + format = '!H' + self.data.append(struct.pack(format, value)) + self.size += 2 + + def writeInt(self, value): + """Writes an unsigned integer to the packet""" + format = '!I' + self.data.append(struct.pack(format, value)) + self.size += 4 + + def writeString(self, value, length): + """Writes a string to the packet""" + format = '!' + str(length) + 's' + self.data.append(struct.pack(format, value)) + self.size += length + + def writeUTF(self, s): + """Writes a UTF-8 string of a given length to the packet""" + utfstr = s.encode('utf-8') + length = len(utfstr) + if length > 64: + raise NamePartTooLongException + self.writeByte(length) + self.writeString(utfstr, length) + + def writeName(self, name): + """Writes a domain name to the packet""" + + try: + # Find existing instance of this name in packet + # + index = self.names[name] + except KeyError: + # No record of this name already, so write it + # out as normal, recording the location of the name + # for future pointers to it. + # + self.names[name] = self.size + parts = name.split('.') + if parts[-1] == '': + parts = parts[:-1] + for part in parts: + self.writeUTF(part) + self.writeByte(0) + return + + # An index was found, so write a pointer to it + # + self.writeByte((index >> 8) | 0xC0) + self.writeByte(index) + + def writeQuestion(self, question): + """Writes a question to the packet""" + self.writeName(question.name) + self.writeShort(question.type) + self.writeShort(question.clazz) + + def writeRecord(self, record, now): + """Writes a record (answer, authoritative answer, additional) to + the packet""" + self.writeName(record.name) + self.writeShort(record.type) + if record.unique and self.multicast: + self.writeShort(record.clazz | _CLASS_UNIQUE) + else: + self.writeShort(record.clazz) + if now == 0: + self.writeInt(record.ttl) + else: + self.writeInt(record.getRemainingTTL(now)) + index = len(self.data) + # Adjust size for the short we will write before this record + # + self.size += 2 + record.write(self) + self.size -= 2 + + length = len(''.join(self.data[index:])) + self.insertShort(index, length) # Here is the short we adjusted for + + def packet(self): + """Returns a string containing the packet's bytes + + No further parts should be added to the packet once this + is done.""" + if not self.finished: + self.finished = 1 + for question in self.questions: + self.writeQuestion(question) + for answer, time in self.answers: + self.writeRecord(answer, time) + for authority in self.authorities: + self.writeRecord(authority, 0) + for additional in self.additionals: + self.writeRecord(additional, 0) + + self.insertShort(0, len(self.additionals)) + self.insertShort(0, len(self.authorities)) + self.insertShort(0, len(self.answers)) + self.insertShort(0, len(self.questions)) + self.insertShort(0, self.flags) + if self.multicast: + self.insertShort(0, 0) + else: + self.insertShort(0, self.id) + return ''.join(self.data) + + +class DNSCache(object): + """A cache of DNS entries""" + + def __init__(self): + self.cache = {} + + def add(self, entry): + """Adds an entry""" + try: + list = self.cache[entry.key] + except: + list = self.cache[entry.key] = [] + list.append(entry) + + def remove(self, entry): + """Removes an entry""" + try: + list = self.cache[entry.key] + list.remove(entry) + except: + pass + + def get(self, entry): + """Gets an entry by key. Will return None if there is no + matching entry.""" + try: + list = self.cache[entry.key] + return list[list.index(entry)] + except: + return None + + def getByDetails(self, name, type, clazz): + """Gets an entry by details. Will return None if there is + no matching entry.""" + entry = DNSEntry(name, type, clazz) + return self.get(entry) + + def entriesWithName(self, name): + """Returns a list of entries whose key matches the name.""" + try: + return self.cache[name] + except: + return [] + + def entries(self): + """Returns a list of all entries""" + def add(x, y): return x+y + try: + return reduce(add, self.cache.values()) + except: + return [] + + +class Engine(threading.Thread): + """An engine wraps read access to sockets, allowing objects that + need to receive data from sockets to be called back when the + sockets are ready. + + A reader needs a handle_read() method, which is called when the socket + it is interested in is ready for reading. + + Writers are not implemented here, because we only send short + packets. + """ + + def __init__(self, zeroconf): + threading.Thread.__init__(self) + self.zeroconf = zeroconf + self.readers = {} # maps socket to reader + self.timeout = 5 + self.condition = threading.Condition() + self.start() + + def run(self): + while not globals()['_GLOBAL_DONE']: + rs = self.getReaders() + if len(rs) == 0: + # No sockets to manage, but we wait for the timeout + # or addition of a socket + # + self.condition.acquire() + self.condition.wait(self.timeout) + self.condition.release() + else: + try: + rr, wr, er = select.select(rs, [], [], self.timeout) + for socket in rr: + try: + self.readers[socket].handle_read() + except: + traceback.print_exc() + except: + pass + + def getReaders(self): + result = [] + self.condition.acquire() + result = self.readers.keys() + self.condition.release() + return result + + def addReader(self, reader, socket): + self.condition.acquire() + self.readers[socket] = reader + self.condition.notify() + self.condition.release() + + def delReader(self, socket): + self.condition.acquire() + del(self.readers[socket]) + self.condition.notify() + self.condition.release() + + def notify(self): + self.condition.acquire() + self.condition.notify() + self.condition.release() + +class Listener(object): + """A Listener is used by this module to listen on the multicast + group to which DNS messages are sent, allowing the implementation + to cache information as it arrives. + + It requires registration with an Engine object in order to have + the read() method called when a socket is availble for reading.""" + + def __init__(self, zeroconf): + self.zeroconf = zeroconf + self.zeroconf.engine.addReader(self, self.zeroconf.socket) + + def handle_read(self): + data, (addr, port) = self.zeroconf.socket.recvfrom(_MAX_MSG_ABSOLUTE) + self.data = data + msg = DNSIncoming(data) + if msg.isQuery(): + # Always multicast responses + # + if port == _MDNS_PORT: + self.zeroconf.handleQuery(msg, _MDNS_ADDR, _MDNS_PORT) + # If it's not a multicast query, reply via unicast + # and multicast + # + elif port == _DNS_PORT: + self.zeroconf.handleQuery(msg, addr, port) + self.zeroconf.handleQuery(msg, _MDNS_ADDR, _MDNS_PORT) + else: + self.zeroconf.handleResponse(msg) + + +class Reaper(threading.Thread): + """A Reaper is used by this module to remove cache entries that + have expired.""" + + def __init__(self, zeroconf): + threading.Thread.__init__(self) + self.zeroconf = zeroconf + self.start() + + def run(self): + while 1: + self.zeroconf.wait(10 * 1000) + if globals()['_GLOBAL_DONE']: + return + now = currentTimeMillis() + for record in self.zeroconf.cache.entries(): + if record.isExpired(now): + self.zeroconf.updateRecord(now, record) + self.zeroconf.cache.remove(record) + + +class ServiceBrowser(threading.Thread): + """Used to browse for a service of a specific type. + + The listener object will have its addService() and + removeService() methods called when this browser + discovers changes in the services availability.""" + + def __init__(self, zeroconf, type, listener): + """Creates a browser for a specific type""" + threading.Thread.__init__(self) + self.zeroconf = zeroconf + self.type = type + self.listener = listener + self.services = {} + self.nextTime = currentTimeMillis() + self.delay = _BROWSER_TIME + self.list = [] + + self.done = 0 + + self.zeroconf.addListener(self, DNSQuestion(self.type, _TYPE_PTR, _CLASS_IN)) + self.start() + + def updateRecord(self, zeroconf, now, record): + """Callback invoked by Zeroconf when new information arrives. + + Updates information required by browser in the Zeroconf cache.""" + if record.type == _TYPE_PTR and record.name == self.type: + expired = record.isExpired(now) + try: + oldrecord = self.services[record.alias.lower()] + if not expired: + oldrecord.resetTTL(record) + else: + del(self.services[record.alias.lower()]) + callback = lambda x: self.listener.removeService(x, self.type, record.alias) + self.list.append(callback) + return + except: + if not expired: + self.services[record.alias.lower()] = record + callback = lambda x: self.listener.addService(x, self.type, record.alias) + self.list.append(callback) + + expires = record.getExpirationTime(75) + if expires < self.nextTime: + self.nextTime = expires + + def cancel(self): + self.done = 1 + self.zeroconf.notifyAll() + + def run(self): + while 1: + event = None + now = currentTimeMillis() + if len(self.list) == 0 and self.nextTime > now: + self.zeroconf.wait(self.nextTime - now) + if globals()['_GLOBAL_DONE'] or self.done: + return + now = currentTimeMillis() + + if self.nextTime <= now: + out = DNSOutgoing(_FLAGS_QR_QUERY) + out.addQuestion(DNSQuestion(self.type, _TYPE_PTR, _CLASS_IN)) + for record in self.services.values(): + if not record.isExpired(now): + out.addAnswerAtTime(record, now) + self.zeroconf.send(out) + self.nextTime = now + self.delay + self.delay = min(20 * 1000, self.delay * 2) + + if len(self.list) > 0: + event = self.list.pop(0) + + if event is not None: + event(self.zeroconf) + + +class ServiceInfo(object): + """Service information""" + + def __init__(self, type, name, address=None, port=None, weight=0, priority=0, properties=None, server=None): + """Create a service description. + + type: fully qualified service type name + name: fully qualified service name + address: IP address as unsigned short, network byte order + port: port that the service runs on + weight: weight of the service + priority: priority of the service + properties: dictionary of properties (or a string holding the bytes for the text field) + server: fully qualified name for service host (defaults to name)""" + + if not name.endswith(type): + raise BadTypeInNameException + self.type = type + self.name = name + self.address = address + self.port = port + self.weight = weight + self.priority = priority + if server: + self.server = server + else: + self.server = name + self.setProperties(properties) + + def setProperties(self, properties): + """Sets properties and text of this info from a dictionary""" + if isinstance(properties, dict): + self.properties = properties + list = [] + result = '' + for key in properties: + value = properties[key] + if value is None: + suffix = ''.encode('utf-8') + elif isinstance(value, str): + suffix = value.encode('utf-8') + elif isinstance(value, int): + if value: + suffix = 'true' + else: + suffix = 'false' + else: + suffix = ''.encode('utf-8') + list.append('='.join((key, suffix))) + for item in list: + result = ''.join((result, struct.pack('!c', chr(len(item))), item)) + self.text = result + else: + self.text = properties + + def setText(self, text): + """Sets properties and text given a text field""" + self.text = text + try: + result = {} + end = len(text) + index = 0 + strs = [] + while index < end: + length = ord(text[index]) + index += 1 + strs.append(text[index:index+length]) + index += length + + for s in strs: + eindex = s.find('=') + if eindex == -1: + # No equals sign at all + key = s + value = 0 + else: + key = s[:eindex] + value = s[eindex+1:] + if value == 'true': + value = 1 + elif value == 'false' or not value: + value = 0 + + # Only update non-existent properties + if key and result.get(key) == None: + result[key] = value + + self.properties = result + except: + traceback.print_exc() + self.properties = None + + def getType(self): + """Type accessor""" + return self.type + + def getName(self): + """Name accessor""" + if self.type is not None and self.name.endswith("." + self.type): + return self.name[:len(self.name) - len(self.type) - 1] + return self.name + + def getAddress(self): + """Address accessor""" + return self.address + + def getPort(self): + """Port accessor""" + return self.port + + def getPriority(self): + """Pirority accessor""" + return self.priority + + def getWeight(self): + """Weight accessor""" + return self.weight + + def getProperties(self): + """Properties accessor""" + return self.properties + + def getText(self): + """Text accessor""" + return self.text + + def getServer(self): + """Server accessor""" + return self.server + + def updateRecord(self, zeroconf, now, record): + """Updates service information from a DNS record""" + if record is not None and not record.isExpired(now): + if record.type == _TYPE_A: + if record.name == self.name: + self.address = record.address + elif record.type == _TYPE_SRV: + if record.name == self.name: + self.server = record.server + self.port = record.port + self.weight = record.weight + self.priority = record.priority + self.address = None + self.updateRecord(zeroconf, now, zeroconf.cache.getByDetails(self.server, _TYPE_A, _CLASS_IN)) + elif record.type == _TYPE_TXT: + if record.name == self.name: + self.setText(record.text) + + def request(self, zeroconf, timeout): + """Returns true if the service could be discovered on the + network, and updates this object with details discovered. + """ + now = currentTimeMillis() + delay = _LISTENER_TIME + next = now + delay + last = now + timeout + result = 0 + try: + zeroconf.addListener(self, DNSQuestion(self.name, _TYPE_ANY, _CLASS_IN)) + while self.server is None or self.address is None or self.text is None: + if last <= now: + return 0 + if next <= now: + out = DNSOutgoing(_FLAGS_QR_QUERY) + out.addQuestion(DNSQuestion(self.name, _TYPE_SRV, _CLASS_IN)) + out.addAnswerAtTime(zeroconf.cache.getByDetails(self.name, _TYPE_SRV, _CLASS_IN), now) + out.addQuestion(DNSQuestion(self.name, _TYPE_TXT, _CLASS_IN)) + out.addAnswerAtTime(zeroconf.cache.getByDetails(self.name, _TYPE_TXT, _CLASS_IN), now) + if self.server is not None: + out.addQuestion(DNSQuestion(self.server, _TYPE_A, _CLASS_IN)) + out.addAnswerAtTime(zeroconf.cache.getByDetails(self.server, _TYPE_A, _CLASS_IN), now) + zeroconf.send(out) + next = now + delay + delay = delay * 2 + + zeroconf.wait(min(next, last) - now) + now = currentTimeMillis() + result = 1 + finally: + zeroconf.removeListener(self) + + return result + + def __eq__(self, other): + """Tests equality of service name""" + if isinstance(other, ServiceInfo): + return other.name == self.name + return 0 + + def __ne__(self, other): + """Non-equality test""" + return not self.__eq__(other) + + def __repr__(self): + """String representation""" + result = "service[%s,%s:%s," % (self.name, socket.inet_ntoa(self.getAddress()), self.port) + if self.text is None: + result += "None" + else: + if len(self.text) < 20: + result += self.text + else: + result += self.text[:17] + "..." + result += "]" + return result + + +class Zeroconf(object): + """Implementation of Zeroconf Multicast DNS Service Discovery + + Supports registration, unregistration, queries and browsing. + """ + def __init__(self, bindaddress=None): + """Creates an instance of the Zeroconf class, establishing + multicast communications, listening and reaping threads.""" + globals()['_GLOBAL_DONE'] = 0 + if bindaddress is None: + self.intf = socket.gethostbyname(socket.gethostname()) + else: + self.intf = bindaddress + self.group = ('', _MDNS_PORT) + self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + try: + self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1) + except: + # SO_REUSEADDR should be equivalent to SO_REUSEPORT for + # multicast UDP sockets (p 731, "TCP/IP Illustrated, + # Volume 2"), but some BSD-derived systems require + # SO_REUSEPORT to be specified explicity. Also, not all + # versions of Python have SO_REUSEPORT available. So + # if you're on a BSD-based system, and haven't upgraded + # to Python 2.3 yet, you may find this library doesn't + # work as expected. + # + pass + self.socket.setsockopt(socket.SOL_IP, socket.IP_MULTICAST_TTL, 255) + self.socket.setsockopt(socket.SOL_IP, socket.IP_MULTICAST_LOOP, 1) + try: + self.socket.bind(self.group) + except: + # Some versions of linux raise an exception even though + # the SO_REUSE* options have been set, so ignore it + # + pass + self.socket.setsockopt(socket.SOL_IP, socket.IP_MULTICAST_IF, socket.inet_aton(self.intf) + socket.inet_aton('0.0.0.0')) + self.socket.setsockopt(socket.SOL_IP, socket.IP_ADD_MEMBERSHIP, socket.inet_aton(_MDNS_ADDR) + socket.inet_aton('0.0.0.0')) + + self.listeners = [] + self.browsers = [] + self.services = {} + self.servicetypes = {} + + self.cache = DNSCache() + + self.condition = threading.Condition() + + self.engine = Engine(self) + self.listener = Listener(self) + self.reaper = Reaper(self) + + def __del__(self): + self.close() + + def isLoopback(self): + return self.intf.startswith("127.0.0.1") + + def isLinklocal(self): + return self.intf.startswith("169.254.") + + def wait(self, timeout): + """Calling thread waits for a given number of milliseconds or + until notified.""" + self.condition.acquire() + self.condition.wait(timeout/1000) + self.condition.release() + + def notifyAll(self): + """Notifies all waiting threads""" + self.condition.acquire() + self.condition.notifyAll() + self.condition.release() + + def getServiceInfo(self, type, name, timeout=3000): + """Returns network's service information for a particular + name and type, or None if no service matches by the timeout, + which defaults to 3 seconds.""" + info = ServiceInfo(type, name) + if info.request(self, timeout): + return info + return None + + def addServiceListener(self, type, listener): + """Adds a listener for a particular service type. This object + will then have its updateRecord method called when information + arrives for that type.""" + self.removeServiceListener(listener) + self.browsers.append(ServiceBrowser(self, type, listener)) + + def removeServiceListener(self, listener): + """Removes a listener from the set that is currently listening.""" + for browser in self.browsers: + if browser.listener == listener: + browser.cancel() + del(browser) + + def registerService(self, info, ttl=_DNS_TTL): + """Registers service information to the network with a default TTL + of 60 seconds. Zeroconf will then respond to requests for + information for that service. The name of the service may be + changed if needed to make it unique on the network.""" + self.checkService(info) + self.services[info.name.lower()] = info + if self.servicetypes.has_key(info.type): + self.servicetypes[info.type]+=1 + else: + self.servicetypes[info.type]=1 + now = currentTimeMillis() + nextTime = now + i = 0 + while i < 3: + if now < nextTime: + self.wait(nextTime - now) + now = currentTimeMillis() + continue + out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA) + out.addAnswerAtTime(DNSPointer(info.type, _TYPE_PTR, _CLASS_IN, ttl, info.name), 0) + out.addAnswerAtTime(DNSService(info.name, _TYPE_SRV, _CLASS_IN, ttl, info.priority, info.weight, info.port, info.server), 0) + out.addAnswerAtTime(DNSText(info.name, _TYPE_TXT, _CLASS_IN, ttl, info.text), 0) + if info.address: + out.addAnswerAtTime(DNSAddress(info.server, _TYPE_A, _CLASS_IN, ttl, info.address), 0) + self.send(out) + i += 1 + nextTime += _REGISTER_TIME + + def unregisterService(self, info): + """Unregister a service.""" + try: + del(self.services[info.name.lower()]) + if self.servicetypes[info.type]>1: + self.servicetypes[info.type]-=1 + else: + del self.servicetypes[info.type] + except: + pass + now = currentTimeMillis() + nextTime = now + i = 0 + while i < 3: + if now < nextTime: + self.wait(nextTime - now) + now = currentTimeMillis() + continue + out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA) + out.addAnswerAtTime(DNSPointer(info.type, _TYPE_PTR, _CLASS_IN, 0, info.name), 0) + out.addAnswerAtTime(DNSService(info.name, _TYPE_SRV, _CLASS_IN, 0, info.priority, info.weight, info.port, info.name), 0) + out.addAnswerAtTime(DNSText(info.name, _TYPE_TXT, _CLASS_IN, 0, info.text), 0) + if info.address: + out.addAnswerAtTime(DNSAddress(info.server, _TYPE_A, _CLASS_IN, 0, info.address), 0) + self.send(out) + i += 1 + nextTime += _UNREGISTER_TIME + + def unregisterAllServices(self): + """Unregister all registered services.""" + print 'Unregistering ',len(self.services),' services' + if len(self.services) > 0: + now = currentTimeMillis() + nextTime = now + i = 0 + while i < 3: + if now < nextTime: + self.wait(nextTime - now) + now = currentTimeMillis() + continue + out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA) + for info in self.services.values(): + out.addAnswerAtTime(DNSPointer(info.type, _TYPE_PTR, _CLASS_IN, 0, info.name), 0) + out.addAnswerAtTime(DNSService(info.name, _TYPE_SRV, _CLASS_IN, 0, info.priority, info.weight, info.port, info.server), 0) + out.addAnswerAtTime(DNSText(info.name, _TYPE_TXT, _CLASS_IN, 0, info.text), 0) + if info.address: + out.addAnswerAtTime(DNSAddress(info.server, _TYPE_A, _CLASS_IN, 0, info.address), 0) + self.send(out) + i += 1 + nextTime += _UNREGISTER_TIME + + def checkService(self, info): + """Checks the network for a unique service name, modifying the + ServiceInfo passed in if it is not unique.""" + now = currentTimeMillis() + nextTime = now + i = 0 + while i < 3: + for record in self.cache.entriesWithName(info.type): + if record.type == _TYPE_PTR and not record.isExpired(now) and record.alias == info.name: + if (info.name.find('.') < 0): + info.name = info.name + ".[" + info.address + ":" + info.port + "]." + info.type + self.checkService(info) + return + raise NonUniqueNameException + if now < nextTime: + self.wait(nextTime - now) + now = currentTimeMillis() + continue + out = DNSOutgoing(_FLAGS_QR_QUERY | _FLAGS_AA) + self.debug = out + out.addQuestion(DNSQuestion(info.type, _TYPE_PTR, _CLASS_IN)) + out.addAuthorativeAnswer(DNSPointer(info.type, _TYPE_PTR, _CLASS_IN, _DNS_TTL, info.name)) + self.send(out) + i += 1 + nextTime += _CHECK_TIME + + def addListener(self, listener, question): + """Adds a listener for a given question. The listener will have + its updateRecord method called when information is available to + answer the question.""" + now = currentTimeMillis() + self.listeners.append(listener) + if question is not None: + for record in self.cache.entriesWithName(question.name): + if question.answeredBy(record) and not record.isExpired(now): + listener.updateRecord(self, now, record) + self.notifyAll() + + def removeListener(self, listener): + """Removes a listener.""" + try: + self.listeners.remove(listener) + self.notifyAll() + except: + pass + + def updateRecord(self, now, rec): + """Used to notify listeners of new information that has updated + a record.""" + for listener in self.listeners: + listener.updateRecord(self, now, rec) + self.notifyAll() + + def handleResponse(self, msg): + """Deal with incoming response packets. All answers + are held in the cache, and listeners are notified.""" + now = currentTimeMillis() + for record in msg.answers: + expired = record.isExpired(now) + if record in self.cache.entries(): + if expired: + self.cache.remove(record) + else: + entry = self.cache.get(record) + if entry is not None: + entry.resetTTL(record) + record = entry + else: + self.cache.add(record) + + self.updateRecord(now, record) + + def handleQuery(self, msg, addr, port): + """Deal with incoming query packets. Provides a response if + possible.""" + out = None + + # Support unicast client responses + # + if port != _MDNS_PORT: + out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA, 0) + for question in msg.questions: + out.addQuestion(question) + + for question in msg.questions: + if question.type == _TYPE_PTR: + if question.name == "_services._dns-sd._udp.local.": + for stype in self.servicetypes.keys(): + if out is None: + out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA) + out.addAnswer(msg, DNSPointer("_services._dns-sd._udp.local.", _TYPE_PTR, _CLASS_IN, _DNS_TTL, stype)) + for service in self.services.values(): + if question.name == service.type: + if out is None: + out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA) + out.addAnswer(msg, DNSPointer(service.type, _TYPE_PTR, _CLASS_IN, _DNS_TTL, service.name)) + else: + try: + if out is None: + out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA) + + # Answer A record queries for any service addresses we know + if question.type == _TYPE_A or question.type == _TYPE_ANY: + for service in self.services.values(): + if service.server == question.name.lower(): + out.addAnswer(msg, DNSAddress(question.name, _TYPE_A, _CLASS_IN | _CLASS_UNIQUE, _DNS_TTL, service.address)) + + service = self.services.get(question.name.lower(), None) + if not service: continue + + if question.type == _TYPE_SRV or question.type == _TYPE_ANY: + out.addAnswer(msg, DNSService(question.name, _TYPE_SRV, _CLASS_IN | _CLASS_UNIQUE, _DNS_TTL, service.priority, service.weight, service.port, service.server)) + if question.type == _TYPE_TXT or question.type == _TYPE_ANY: + out.addAnswer(msg, DNSText(question.name, _TYPE_TXT, _CLASS_IN | _CLASS_UNIQUE, _DNS_TTL, service.text)) + if question.type == _TYPE_SRV: + out.addAdditionalAnswer(DNSAddress(service.server, _TYPE_A, _CLASS_IN | _CLASS_UNIQUE, _DNS_TTL, service.address)) + except: + traceback.print_exc() + + if out is not None and out.answers: + out.id = msg.id + self.send(out, addr, port) + + def send(self, out, addr = _MDNS_ADDR, port = _MDNS_PORT): + """Sends an outgoing packet.""" + # This is a quick test to see if we can parse the packets we generate + #temp = DNSIncoming(out.packet()) + try: + bytes_sent = self.socket.sendto(out.packet(), 0, (addr, port)) + except: + # Ignore this, it may be a temporary loss of network connection + pass + + def close(self): + """Ends the background threads, and prevent this instance from + servicing further queries.""" + print 'in close' + if globals()['_GLOBAL_DONE'] == 0: + globals()['_GLOBAL_DONE'] = 1 + print 'closing globals' + self.notifyAll() + self.engine.notify() + self.unregisterAllServices() + self.socket.setsockopt(socket.SOL_IP, socket.IP_DROP_MEMBERSHIP, socket.inet_aton(_MDNS_ADDR) + socket.inet_aton('0.0.0.0')) + self.socket.close() + +# Test a few module features, including service registration, service +# query (for Zoe), and service unregistration. + +if __name__ == '__main__': + print "Multicast DNS Service Discovery for Python, version", __version__ + r = Zeroconf() + print "1. Testing registration of a service..." + desc = {'version':'0.10','a':'test value', 'b':'another value'} + info = ServiceInfo("_http._tcp.local.", "My Service Name._http._tcp.local.", socket.inet_aton("127.0.0.1"), 1234, 0, 0, desc) + print " Registering service..." + r.registerService(info) + print " Registration done." + print "2. Testing query of service information..." + print " Getting ZOE service:", str(r.getServiceInfo("_http._tcp.local.", "ZOE._http._tcp.local.")) + print " Query done." + print "3. Testing query of own service..." + print " Getting self:", str(r.getServiceInfo("_http._tcp.local.", "My Service Name._http._tcp.local.")) + print " Query done." + print "4. Testing unregister of service information..." + r.unregisterService(info) + print " Unregister done." + r.close() diff --git a/amarok/src/scripts/copy_icons.rb b/amarok/src/scripts/copy_icons.rb new file mode 100644 index 00000000..2fdd62f2 --- /dev/null +++ b/amarok/src/scripts/copy_icons.rb @@ -0,0 +1,28 @@ +#!/usr/bin/env ruby +# +# Transforms icon filenames from an icon tarball to correct format for committing +# +# (c) 2006 Mark Kretschmann +# Licensed under GPL V2. + + +require 'fileutils' + + +def copy_icons( res ) + folder = "#{res}x#{res}/actions" + + Dir.foreach( folder ) do |file| + next if file[0, 1] == "." + name = File.basename( file, ".png" ) + FileUtils.cp( "#{folder}/#{file}", "hi#{res}-action-#{name}.png" ) + end +end + + + +copy_icons( "16" ) +copy_icons( "22" ) +copy_icons( "32" ) +copy_icons( "48" ) +copy_icons( "64" ) diff --git a/amarok/src/scripts/databasescripts/README b/amarok/src/scripts/databasescripts/README new file mode 100644 index 00000000..d61d188f --- /dev/null +++ b/amarok/src/scripts/databasescripts/README @@ -0,0 +1,63 @@ +
    DatabaseScripts (v0.2)
    + +

    +About:
    +DatabaseScripts is a collection of scripts for general manipulation of the amarok collection +database. Currently, there are two scripts in the bundle. +

    + +

    +Backup Collection:
    +Specify the target directory in the configuration dialog, and the database will be copied to +the specified directory. This will work for SQLite or MySQL database backends. If you are +interested in extending this to support Postgresql, it would be appreciated! +

    + +

    +The backup will be saved as collection.db.date for sqlite, or amarokdb.mysql.date with mysql +databases. +

    +

    +Note: For mysql users, it is necessary to give the database user all +database action permissions. This is required for mysqldump. This can be achieved by issuing +the following command: +

    + $> mysql -u root -p
    + mysql> GRANT ALL ON amarokdb.* TO amarok@localhost IDENTIFIED BY 'xxxx'; +
    +

    + +

    +Optimise Database:
    +This script will remove all entries in the database which do not exist in the filesystem. +

    +

    +Warning: Just to say I told you so, please use the Backup Collection script prior to +executing this script. +

    + +

    +Notes:
    +If you would like to write any additional scripts, or have any suggestions, please email them to me +so i can include them in the release! +

    + +

    +Dependencies:
    +

      +
    • Amarok 1.3.1
    • +
    • Ruby 1.8
    • +
    • Korundum Ruby/Qt bindings
    • +
    +

    + +

    +License:
    +GNU General Public License V2 +

    + +

    +Author:
    +Seb Ruiz (me@sebruiz.net) +

    + diff --git a/amarok/src/scripts/databasescripts/TODO b/amarok/src/scripts/databasescripts/TODO new file mode 100644 index 00000000..76457207 --- /dev/null +++ b/amarok/src/scripts/databasescripts/TODO @@ -0,0 +1,10 @@ +** TODO (databasescripts.rb) ** + +Script Ideas: +* Script to restore database backup +* Migration script (move directories) + +General: +* Properly use the ui file, to make it easier to maintain later +* Maybe pass error messages back to the parent dialog to abstract scripts away from amarok? + diff --git a/amarok/src/scripts/databasescripts/backupDatabase.rb b/amarok/src/scripts/databasescripts/backupDatabase.rb new file mode 100644 index 00000000..f8129a00 --- /dev/null +++ b/amarok/src/scripts/databasescripts/backupDatabase.rb @@ -0,0 +1,76 @@ +#!/usr/bin/env ruby +# +# +# Ruby database backup script +# (c) 2005 Seb Ruiz +# Released under the GPL v2 license + + +def getFilename( input ) + puts input + date = `date +%Y%m%d` + i = 1 + input = input + "." + date + original = input + + # don't overwrite a previously written backup + loop do + file = File.dirname( File.expand_path( __FILE__ ) ) + "/" + input + + if not FileTest.exist?( file ) + return input + end + + i = i + 1 + newName = original + "." + i + input = newname + end +end + +if $*.empty?() or $*[0] == "--help" + puts( "Usage: backupDatabase.rb saveLocation" ) + puts() + puts( "Backup your Amarok database!" ) + exit( 1 ) +end + +destination = $*[0] + "/" + +unless FileTest.directory?( destination ) + system("dcop", "amarok", "playlist", "popupMessage", "Error: Save destination must be a directory") + exit( 1 ) +end + +unless FileTest.writable_real?( destination ) + system("dcop", "amarok", "playlist", "popupMessage", "Error: Destination directory not writeable.") + exit( 1 ) +end + +filename = "" +database = `dcop amarok script readConfig DatabaseEngine`.chomp!() + +case database + + when "0" # sqlite + filename = "collection.db" + filename = getFilename( filename ) + dest = destination + "/" + filename + puts dest + `cp ~/.kde/share/apps/amarok/collection.db #{dest}` + + when "1" # mysql + filename = "amarokdb.mysql" + filename = getFilename( filename ) + dest = destination + "/" + filename + puts dest + db = `dcop amarok script readConfig MySqlDbName`.chomp!() + user = `dcop amarok script readConfig MySqlUser`.chomp!() + pass = `dcop amarok script readConfig MySqlPassword`.chomp!() + system("mysqldump", "-u", user, "-p", pass, db, "-r", dest); + + when "2" # postgres + system("dcop", "amarok", "playlist", "popupMessage", "Sorry, postgresql database backups have not been implemented.") + exit( 1 ) +end + +system("dcop", "amarok", "playlist", "popupMessage", "Database backup saved to: #{destination}/#{filename}") diff --git a/amarok/src/scripts/databasescripts/databaseScripts.rb b/amarok/src/scripts/databasescripts/databaseScripts.rb new file mode 100644 index 00000000..5d38de77 --- /dev/null +++ b/amarok/src/scripts/databasescripts/databaseScripts.rb @@ -0,0 +1,145 @@ +#!/usr/bin/env ruby +# +# +# Form implementation generated from reading ui file 'selector.ui' +# +# Created: Fri Dec 2 23:40:46 2005 +# by: The QtRuby User Interface Compiler (rbuic) +# +# WARNING! All changes made in this file will be lost! +# +# Ruby script for generic amarok database scripts +# (c) 2005 Seb Ruiz +# Released under the GPL v2 license + +begin + require 'Korundum' +rescue LoadError + error = 'Korundum (KDE bindings for ruby) from kdebindings v3.4 is required for this script.' + system("dcop", "amarok", "playlist", "popupMessage", "DatabaseScripts: #{error}") + exit +end + +class DatabaseScriptChooser < Qt::Dialog + + attr_reader :m_optionCombo + attr_reader :m_saveText + attr_reader :m_saveDir + attr_reader :m_okayButton + + slots 'optionChanged(int)', 'textChanged(const QString &)', 'accept()', 'cancel()' + + def initialize(parent = nil, name = nil, modal = false, fl = 0) + super + + if name.nil? + setName("Database Script Chooser") + end + + @Form1Layout = Qt::GridLayout.new(self, 1, 1, 2, 2, 'Form1Layout') + + @layout3 = Qt::VBoxLayout.new(nil, 0, 2, 'layout3') + + @m_optionCombo = Qt::ComboBox.new(false, self, "m_optionCombo") + @layout3.addWidget(@m_optionCombo) + + @layout1 = Qt::HBoxLayout.new(nil, 0, 2, 'layout1') + + @m_saveText = Qt::Label.new(self, "m_saveText") + @layout1.addWidget(@m_saveText) + + @m_saveDir = KDE::URLRequester.new(self, "m_saveDir") + @m_saveDir.setMode( KDE::File::Directory | KDE::File::ExistingOnly ); + @m_saveDir.setURL( ENV["HOME"] ) + + @layout1.addWidget(@m_saveDir) + @layout3.addLayout(@layout1) + @spacer1 = Qt::SpacerItem.new(20, 21, Qt::SizePolicy::Minimum, Qt::SizePolicy::Expanding) + @layout3.addItem(@spacer1) + + @layout2 = Qt::HBoxLayout.new(nil, 0, 2, 'layout2') + + @m_cancelButton = Qt::PushButton.new(self, "@m_cancelButton") + @layout2.addWidget(@m_cancelButton) + + @spacer2 = Qt::SpacerItem.new(61, 20, Qt::SizePolicy::Expanding, Qt::SizePolicy::Minimum) + @layout2.addItem(@spacer2) + + @m_okayButton = Qt::PushButton.new(self, "m_okayButton") + @layout2.addWidget(@m_okayButton) + @layout3.addLayout(@layout2) + + connect( @m_optionCombo, SIGNAL( "activated(int)" ), self, SLOT( "optionChanged(int)" ) ); + connect( @m_okayButton, SIGNAL( "clicked()" ), self, SLOT( "accept()" ) ) + connect( @m_cancelButton, SIGNAL( "clicked()" ), self, SLOT( "cancel()" ) ) + + connect( @m_saveDir, SIGNAL( "textChanged(const QString &)" ), + self, SLOT( "textChanged(const QString &)" ) ); + + @Form1Layout.addLayout(@layout3, 0, 0) + languageChange() + + resize( Qt::Size.new(356, 137).expandedTo(minimumSizeHint()) ) + clearWState( WState_Polished ) + end + + def optionChanged( i ) + @m_saveDir.setEnabled( i == 0 ) + @m_saveText.setEnabled( i == 0 ) + end + + def textChanged(s) + @m_okayButton.setEnabled( !s.empty?() ) + end + + def accept() + arg = "" + case @m_optionCombo.currentItem() + when 0 # Backup + filename = File.dirname( File.expand_path( __FILE__ ) ) + "/backupDatabase.rb" + arg = @m_saveDir.url() + + when 1 # Optimise + filename = File.dirname( File.expand_path( __FILE__ ) ) + "/staleStatistics.rb" + end + + system("ruby", filename, arg) + + done( 0 ) + end + + def cancel() + done( 0 ) + end + + + # + # Sets the strings of the subwidgets using the current + # language. + # + def languageChange() + setCaption( trUtf8("Database Scripts") ) + @m_optionCombo.clear() + + # add combo box items + @m_optionCombo.insertItem( trUtf8("Backup Database") ) + @m_optionCombo.insertItem( trUtf8("Optimise Database") ) + + @m_saveText.setText( trUtf8("Save location:") ) + @m_cancelButton.setText( trUtf8("Cancel") ) + @m_okayButton.setText( trUtf8("Go!") ) + end + protected :languageChange + + +end + +if $0 == __FILE__ + about = KDE::AboutData.new("databaseScriptChooser", "DatabaseScriptChooser", "0.1") + KDE::CmdLineArgs.init(ARGV, about) + a = KDE::Application.new + w = DatabaseScriptChooser.new + a.mainWidget = w + w.show + a.exec +end diff --git a/amarok/src/scripts/databasescripts/redoPodcasts.rb b/amarok/src/scripts/databasescripts/redoPodcasts.rb new file mode 100644 index 00000000..4c629c69 --- /dev/null +++ b/amarok/src/scripts/databasescripts/redoPodcasts.rb @@ -0,0 +1,17 @@ +#!/usr/bin/env ruby + +query = `dcop amarok collection query "SELECT url FROM podcastchannels;"` +print "Grabbing podcast feeds from database...\n" + +podcasts = query.split("\n") + +print "Delete all the podcasts from the playlist browser. Once done, press something. Don't screw this up." +message = gets() +print "Sure you have removed them?" +message = gets() + +podcasts.each do |channel| + print "Adding podcast: #{channel}\n" + system("dcop", "amarok", "playlistbrowser", "addPodcast", channel) +end +print "Done.\n" diff --git a/amarok/src/scripts/databasescripts/staleAlbums.rb b/amarok/src/scripts/databasescripts/staleAlbums.rb new file mode 100755 index 00000000..93c63453 --- /dev/null +++ b/amarok/src/scripts/databasescripts/staleAlbums.rb @@ -0,0 +1,40 @@ +#!/usr/bin/env ruby +# +# script to remove stale entries in some database tables (album) +# +# (c) 2006 Roland Gigler +# License: GNU General Public License V2 + +class String + def shellquote + return "'" + self.gsub("'", "'\\\\''") + "'" + end +end + +system("dcop", "amarok", "playlist", "shortStatusMessage", "Removing stale 'album' entries from the database") + +qresult = `dcop amarok collection query #{"SELECT id FROM album;".shellquote}` +result = qresult.split( "\n" ) + +i = 0 + +result.each do |id| + print "Checking: #{id}, " + qresult2 = `dcop amarok collection query #{"SELECT COUNT(*) FROM tags where album = #{id};".shellquote}` + count = qresult2.chomp() + printf "count: %s", count + if count == "0" + i = i + 1 + qresult3 = `dcop amarok collection query #{"SELECT name FROM album where id = #{id} ;".shellquote}` + result3 = qresult3.split( "\n" ) + puts "==>: Deleting: #{id}, #{result3}" + system("dcop", "amarok", "collection", "query", "DELETE FROM album WHERE id = '#{id}'") + end + print "\n" +end +puts "removed #{i} albums." + +if i > 0 + system("dcop", "amarok", "playlist", "popupMessage", "Removed #{i.shellquote} stale 'album' entries from the database") +end + diff --git a/amarok/src/scripts/databasescripts/staleArtists.rb b/amarok/src/scripts/databasescripts/staleArtists.rb new file mode 100755 index 00000000..8612657f --- /dev/null +++ b/amarok/src/scripts/databasescripts/staleArtists.rb @@ -0,0 +1,33 @@ +#!/usr/bin/env ruby +# +# script to remove stale entries in some database tables (artist) +# +# (c) 2006 Roland Gigler +# License: GNU General Public License V2 + +system("dcop", "amarok", "playlist", "shortStatusMessage", "Removing stale 'artist' entries from the database") + +qresult = `dcop amarok collection query "SELECT id FROM artist;"` +result = qresult.split( "\n" ) + +i = 0 + +result.each do |id| + print "Checking: #{id}, " + qresult2 = `dcop amarok collection query "SELECT COUNT(*) FROM tags where artist = #{id};"` + count = qresult2.chomp() + printf "count: %s\n", count + if count == "0" + i = i + 1 + qresult3 = `dcop amarok collection query "SELECT name FROM artist where id = #{id} ;"` + result3 = qresult3.split( "\n" ) + puts "==>: Deleting: #{id}, #{result3}" + system("dcop", "amarok", "collection", "query", "DELETE FROM artist WHERE id = '#{id}'") + end +end +puts "i: #{i}" + +if i > 0 + system("dcop", "amarok", "playlist", "popupMessage", "Removed #{i} stale 'artist' entries from the database") +end + diff --git a/amarok/src/scripts/databasescripts/staleImages.rb b/amarok/src/scripts/databasescripts/staleImages.rb new file mode 100755 index 00000000..2697d2c6 --- /dev/null +++ b/amarok/src/scripts/databasescripts/staleImages.rb @@ -0,0 +1,27 @@ +#!/usr/bin/env ruby +# +# script to remove stale images in the database +# +# (c) 2006 Roland Gigler +# License: GNU General Public License V2 + +system("dcop", "amarok", "playlist", "shortStatusMessage", "Removing stale 'images' entries from the database") + +qresult = `dcop amarok collection query "SELECT path FROM images;"` +result = qresult.split( "\n" ) + +i = 0 + +result.each do |url| + #puts "url: #{url}" + unless FileTest.exist?( url ) + i = i + 1 + url.gsub!(/[']/, '\\\\\'') + puts "Deleting: #{url}" + system("dcop", "amarok", "collection", "query", "DELETE FROM images WHERE path = '#{url}'") + end +end + +if i > 0 + system("dcop", "amarok", "playlist", "popupMessage" "Removed #{i} stale 'images' entries from the database") +end diff --git a/amarok/src/scripts/databasescripts/staleStatistics.rb b/amarok/src/scripts/databasescripts/staleStatistics.rb new file mode 100755 index 00000000..8d067045 --- /dev/null +++ b/amarok/src/scripts/databasescripts/staleStatistics.rb @@ -0,0 +1,25 @@ +#!/usr/bin/env ruby +# +# Ruby script to remove stale statistics in the database +# (c) 2005 Seb Ruiz +# License: GNU General Public License V2 + +system("dcop", "amarok", "playlist", "shortStatusMessage", "Removing stale entries from the database") + +qresult = `dcop amarok collection query "SELECT url FROM statistics;"` +result = qresult.split( "\n" ) + +i = 0 + +result.each do |url| + unless FileTest.exist?( url ) + i = i + 1 + url.gsub!(/[']/, '\\\\\'') + puts "Deleting: #{url}" + system("dcop", "amarok", "collection", "query", "DELETE FROM statistics WHERE url = '#{url}'") + end +end + +if i > 0 + system("dcop", "amarok", "playlist", "popupMessage", "Removed #{i} stale entries from the database") +end diff --git a/amarok/src/scripts/embedcover/COPYING b/amarok/src/scripts/embedcover/COPYING new file mode 100644 index 00000000..6822c1a4 --- /dev/null +++ b/amarok/src/scripts/embedcover/COPYING @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 51 Franklin Steet, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) 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 +this service 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 make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. 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. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute 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 and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +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 +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the 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 a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, 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. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE 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. + + 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 +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Steet, Fifth Floor, Boston, MA 02110-1301 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision 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, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This 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 Library General +Public License instead of this License. diff --git a/amarok/src/scripts/embedcover/README b/amarok/src/scripts/embedcover/README new file mode 100644 index 00000000..7f7e2e9a --- /dev/null +++ b/amarok/src/scripts/embedcover/README @@ -0,0 +1,52 @@ +
    EmbedCover (v0.8)
    + +

    +About:
    +EmbedCover is a script for embedding album cover images in MP3 files. The script will +automatically look up the image associated to it in Amarok and embed this image in the file. +

    +

    +The MP3 file must have an ID3-V2 tag, otherwise the script will abort, and the file must be +in your Collection. +

    +

    +It is recommended that you back up your MP3 file before using this script on it. Although the +script is designed not to harm your file, the operation is not reversible and should therefore +be used with caution. +

    + +

    +Usage:
    +Select a track in the playlist, then start EmbedCover from the context-menu (right mouse click). +

    +

    +It is also possible to use EmbedCover as a command line tool, by calling the "addimage2mp3.rb" +script contained in this package. Addimage2mp3 only requires Ruby to be installed, not Amarok. +The EmbedCover Amarok Script is a frontend for this tool. +

    + +

    +Dependencies:
    +

      +
    • Amarok 1.4.5
    • +
    • Ruby 1.8
    • +
    +

    + +

    +ChangeLog:
    +Version 0.8:
    +

      +
    • Make it work with Amarok's Dynamic Collection.
    • +
    +Version 0.7:
    +
      +
    • Initial release.
    • +
    +

    + +

    +Author:
    +Mark Kretschmann (markey@web.de) +

    + diff --git a/amarok/src/scripts/embedcover/addimage2mp3.rb b/amarok/src/scripts/embedcover/addimage2mp3.rb new file mode 100644 index 00000000..f5fd46d3 --- /dev/null +++ b/amarok/src/scripts/embedcover/addimage2mp3.rb @@ -0,0 +1,219 @@ +#!/usr/bin/env ruby +# +# Script for embedding album cover images in MP3 files. This requires an existing +# ID3-V2 tag in the file. +# +# (c) 2005-2006 Mark Kretschmann +# License: GNU General Public License V2 + + +# +# Returns the size of the ID3-V2 tag. In other words, the offset from +# where the real mp3 data starts. +# @see http://id3lib.sourceforge.net/id3/id3v2com-00.html#sec3.1 +# +def getId3v2Size( data ) + return data[6]*2**21 + data[7]*2**14 + data[8]*2**7 + data[9] +end + +# +# Sets the size of the ID3-V2 tag. +# @see http://id3lib.sourceforge.net/id3/id3v2com-00.html#sec3.1 +# +def setId3v2Size( data, size ) + data[6] = size >> 21 & 0x7f + data[7] = size >> 14 & 0x7f + data[8] = size >> 7 & 0x7f + data[9] = size >> 0 & 0x7f +end + +# +# Unsynchronize the entire ID3-V2 tag, frame by frame (replace 0xfff* with 0xff00f*) +# +def unsynchronize( data ) + data[5] |= 0b10000000 # Set Unsychronization global tag flag + index = 10 # Skip ID3 Header + + until data[index] == 0 or data[index..index+2] == "3DI" + frametype = data[index..index+3] + framesize = data[index+4]*2**21 + data[index+5]*2**14 + data[index+6]*2**7 + data[index+7] + + # Check if frame is already unsynced + if data[index+9] & 0x02 == 0 + puts( "#{frametype} unsynced " ) + data[index+9] |= 0x02 + index += 10 + + framesize.times() do + sync = data[index] == 0xff + sync1 = data[index+1] & 0b11100000 == 0b11100000 + sync2 = data[index+1] & 0b11111111 == 0b00000000 + + if sync and ( sync1 or sync2 ) and index < framesize - 2 + print( "." ) if sync1 + print( "O" ) if sync2 + data[index+1, 0] = "\0" + framesize += 1 + index += 1 + end + + index += 1 + end + else + puts( "#{frametype} synced" ) + index += framesize + 10 + end + end + + # Index == new tag length + return index - 10 +end + + +############################################################################ +# MAIN +############################################################################ + +path = "" +destination = "" + +if $*.length() < 2 or $*[0] == "--help" + puts( "Usage: addimage2mp3.rb image source [destination]" ) + puts() + puts( "Adds an image to the ID3-V2 tag of a MP3 file. Requires an existing ID3-V2 tag in" ) + puts( "the file." ) + puts() + exit( 1 ) +end + +imagepath = $*[0] +path = $*[1] +destination = path + + +if $*.length() == 3 + destination = $*[2] +end + + +unless FileTest::readable_real?( imagepath ) + puts( "Error: Image not found or not readable." ) + exit( 1 ) +end + +unless FileTest::readable_real?( path ) + puts( "Error: Source not found or not readable." ) + exit( 1 ) +end + +unless File.extname( path ).downcase() == ".mp3" + puts( "Error: File is not mp3." ) + exit( 1 ) +end + + +mimetype = case File.extname( imagepath ).downcase() +when ".bmp" then "image/bmp" +when ".gif" then "image/gif" +when ".jpg" then "image/jpeg" +when ".jpeg" then "image/jpeg" +when ".png" then "image/png" +when "" then "image/png" # Amarok's cover images are always PNG + +else + puts( "Error: Image type invalid." ) + exit( 1 ) +end + + +file = File.new( path, "r" ) +data = file.read() +id3length = 0 + + +if data[0,3] == "ID3" + id3length = getId3v2Size( data ) + puts( "ID3-V2 detected. Tag size: #{id3length}" ) +else + puts( "ID3-V1 detected." ) + puts( "Error: File does not have a ID3-V2 tag." ) + exit( 1 ) +end + + +file = File.new( imagepath, "r" ) +image = file.read() + + +apicframe = String.new() +apicframe << 0x00 # text encoding +apicframe << mimetype +apicframe << 0x00 # mimetype end +apicframe << 0x03 # Picture type (Cover front) +apicframe << 0x00 # Description (empty) +apicframe << image + +apicheader = String.new() +apicheader << "APIC" +apicheader << ( ( apicframe.length() >> 21 ) & 0x7f ) +apicheader << ( ( apicframe.length() >> 14 ) & 0x7f ) +apicheader << ( ( apicframe.length() >> 7 ) & 0x7f ) +apicheader << ( ( apicframe.length() >> 0 ) & 0x7f ) +apicheader << 0x00 # Flags +apicheader << 0x00 # Flags + + +# Find end of last ID3 frame, before padding or footer (if present) + +puts() +puts( "Locating end of last ID3 frame.." ) + +index = 10 # Skip ID3 Header +until data[index] == 0 or data[index..index+2] == "3DI" + frametype = data[index..index+3] + puts( "Frame Type : #{frametype}" ) + if frametype == "APIC" + puts( "Error: File already contains an image." ) + exit( 1 ) + end + framesize = data[index+4]*2**21 + data[index+5]*2**14 + data[index+6]*2**7 + data[index+7] + index += 10 + framesize +end + +index += 10 if data[index..index+2] == "3DI" # Footer + + +# Insert APIC frame into string, after the last ID3 frame +data[index, 0] = apicheader + apicframe +id3length += apicheader.length() + apicframe.length() + + +# Unsynchronization isn't supported by TagLib at this point :| +# +# puts() +# puts( "Unsynchronizing tag.." ) +# id3length = unsynchronize( data ) +# data[5] |= 0b10000000 # Set ID3 Unsychronization flag + + +# Adjust ID3V2 tag size +setId3v2Size( data, id3length ) +puts() +puts( "Adjusting new ID3 size: #{id3length + 10}" ) + + +puts() +print( "Writing file.. " ) +destfile = File::open( destination, File::CREAT|File::TRUNC|File::WRONLY ) + +if destfile == nil + puts( "Error: Destination file is not writable." ) + exit( 1 ) +end + +destfile << data +puts( "done." ) + + +puts() +puts( "done." ) diff --git a/amarok/src/scripts/embedcover/embedcover.rb b/amarok/src/scripts/embedcover/embedcover.rb new file mode 100755 index 00000000..88f11696 --- /dev/null +++ b/amarok/src/scripts/embedcover/embedcover.rb @@ -0,0 +1,111 @@ +#!/usr/bin/env ruby +# +# Amarok Script for embedding album cover images in MP3 files. +# +# (c) 2005-2006 Mark Kretschmann +# License: GNU General Public License V2 + + +require 'md5' +require "uri" + +$stdout.sync = true +MenuItemName = "EmbedCover DoIt!" + + +def cleanup() + `dcop amarok script removeCustomMenuItem #{MenuItemName}` +end + +def sql_escape( string ) + return string.gsub( /[']/, "''" ) +end + + +trap( "SIGTERM" ) { cleanup() } + +`dcop amarok script addCustomMenuItem #{MenuItemName}` + + +loop do + message = gets().chomp() + command = /[A-Za-z]*/.match( message ).to_s() + + case command + when "configure" + msg = 'EmbedCover does not have configuration options. Simply select a track in the ' + msg += 'playlist, then start EmbedCover from the context-menu (right mouse click).' + + `dcop amarok playlist popupMessage "#{msg}"` + + when "customMenuClicked" + if message.include?( MenuItemName ) + args = message.split() + # Remove the command args + 3.times() { args.delete_at( 0 ) } + + # Iterate over all selected files + args.each() do |arg| + uri = URI.parse( arg ) + file = URI.unescape( uri.path() ) + + puts( "Path: #{file}" ) + + backend = File.dirname( File.expand_path( __FILE__ ) ) + "/addimage2mp3.rb" + + # In the database we store relative URLs (for dynamic collection), so we need to convert + file_relative = `dcop amarok collection relativePath "#{file}"`.chomp + + # Query is two parts, first ID, then name + artist_id = `dcop amarok collection query "SELECT DISTINCT artist FROM tags WHERE url = '#{sql_escape( file_relative )}'"`.chomp + artist = `dcop amarok collection query "SELECT DISTINCT artist.name FROM artist WHERE id = '#{artist_id}'"`.chomp + + album_id = `dcop amarok collection query "SELECT DISTINCT album FROM tags WHERE url = '#{sql_escape( file_relative )}'"`.chomp + album = `dcop amarok collection query "SELECT DISTINCT album.name FROM album WHERE id = '#{album_id}'"`.chomp + + puts( "ArtistId : #{artist_id}" ) + puts( "Artist : #{artist}" ) + puts( "AlbumId : #{album_id}" ) + puts( "Album : #{album}" ) + + if artist_id.empty?() and album_id.empty?() + `dcop amarok playlist popupMessage "EmbedCover Error: This track is not in your Collection."` + next + end + + md5sum = MD5.hexdigest( "#{artist.downcase()}#{album.downcase()}" ) + imagefolder = "#{ENV['HOME']}/.kde/share/apps/amarok/albumcovers/large/" + image = "#{imagefolder}#{md5sum}" + + puts( "Imagepath: #{image}" ) + + unless FileTest.exist?( image ) + # If there is no imported image, check if there is an image associated + # in the music folder + + sql = "SELECT path FROM images WHERE artist LIKE #{sql_escape( artist )} AND album LIKE #{sql_escape( album )} ORDER BY path;" + images = `dcop amarok collection query #{sql}`.split( "\n" ) + + # FIXME select best image from array, like CollectionDB does + image = images.first() + + if image == nil or not FileTest.exist?( image ) + `dcop amarok playlist popupMessage "EmbedCover: No image found for this track."` + next + end + end + + output = `ruby #{backend} "#{image}" "#{file}"` + + if $?.success?() + `dcop amarok playlist popupMessage "EmbedCover has successfully embedded the image."` + else + reg = Regexp.new( "Error:.*", Regexp::MULTILINE ) + errormsg = reg.match( output ) + + `dcop amarok playlist popupMessage "EmbedCover #{errormsg}"` + end + end + end + end +end diff --git a/amarok/src/scripts/gnome_media_keys/README b/amarok/src/scripts/gnome_media_keys/README new file mode 100644 index 00000000..c55d48e5 --- /dev/null +++ b/amarok/src/scripts/gnome_media_keys/README @@ -0,0 +1,24 @@ +
    Gnome Multimedia Key's
    Version: 0.1
    + +

    +About:
    +This script will allow the use of keyboard multimedia keys in Gnome 2.18 and above (eg. ubuntu feisty) to control playback. This includes most multimedia buttons found on many laptops.
    +Currently supported operations are:
    +

    	Play/Pause
    +	Pause
    +	Stop
    +	Next Track
    +	Previous Track
    +

    +

    +Usage:
    +Simply run the script, no configuration of the script itself is required.
    The desired keyboard shortcuts can be configured by selecting "System-->Prefrences-->Keyboard Shortcuts" from the Gnome pannel. +

    +

    +Author:
    +Chris Brown: chris.scotland[at]gmail.com
    +This is my first ever script and would love your feedback and bug reports. +

    +

    ENJOY!

    + + diff --git a/amarok/src/scripts/gnome_media_keys/gnome_media_keys.py b/amarok/src/scripts/gnome_media_keys/gnome_media_keys.py new file mode 100644 index 00000000..170afdaa --- /dev/null +++ b/amarok/src/scripts/gnome_media_keys/gnome_media_keys.py @@ -0,0 +1,45 @@ +#!/usr/bin/env python +#Gnome Multimedia Key's event handler script. +#This script will allow the use of multimedia buttons in amarok as configured using gnome keyboard shortcuts. +#Author: Chris Brown +#Date: 22June2007 +#Version: 0.3 +#bug reports to: chris.scotland[at]gmail.com + +import os, gobject + +try: + import dbus +except: + os.system("kdialog --sorry 'the package python-dbus (DBUS bindings for Python) is required for this script.'") + raise + +try: + from dbus import glib +except: + os.system("kdialog --sorry 'the package libdbus-glib (Glib-based shared library for dbus) is required for this script.'") + raise + +bus = dbus.SessionBus() +object = bus.get_object('org.gnome.SettingsDaemon', '/org/gnome/SettingsDaemon') + +def signal_handler(*mmkeys): + for mmk in mmkeys: + if mmk == "Play": + os.system("dcop amarok player playPause") + elif mmk == "Pause": + os.system("dcop amarok player pause") + elif mmk == "Stop": + os.system("dcop amarok player stop") + elif mmk == "Next": + os.system("dcop amarok player next") + elif mmk == "Previous": + os.system("dcop amarok player prev") + + +object.connect_to_signal("MediaPlayerKeyPressed", signal_handler, dbus_interface='org.gnome.SettingsDaemon') + +gobject.timeout_add(5000, signal_handler) + +loop = gobject.MainLoop() +loop.run() diff --git a/amarok/src/scripts/gnome_media_keys/gnome_media_keys.spec b/amarok/src/scripts/gnome_media_keys/gnome_media_keys.spec new file mode 100644 index 00000000..0cd2ffbd --- /dev/null +++ b/amarok/src/scripts/gnome_media_keys/gnome_media_keys.spec @@ -0,0 +1,2 @@ +name = Gnome Multimedia Key's +type = {generic} diff --git a/amarok/src/scripts/graphequalizer/Makefile.am b/amarok/src/scripts/graphequalizer/Makefile.am new file mode 100644 index 00000000..8b2acb43 --- /dev/null +++ b/amarok/src/scripts/graphequalizer/Makefile.am @@ -0,0 +1,27 @@ +graphequalizerdir = $(kde_datadir)/amarok/scripts/graphequalizer + +graphequalizer_PROGRAMS = graphequalizer + +INCLUDES = \ + $(all_includes) + +# eqdialog.ui.h removed because of automake turning it into just '.h' for some reason +# and then complaining about its inexistence + +graphequalizer_SOURCES = \ + equalizercanvasview.cpp \ + eqdialog.ui \ + stdinreader.h \ + main.cpp + +graphequalizer_DATA = README + +graphequalizer_LDADD = \ + $(LIB_QT) \ + $(LIB_KDEUI) \ + $(LIB_KDECORE) + +graphequalizer_LDFLAGS = $(all_libraries) $(KDE_RPATH) + +METASOURCES = AUTO + diff --git a/amarok/src/scripts/graphequalizer/README b/amarok/src/scripts/graphequalizer/README new file mode 100644 index 00000000..b0999260 --- /dev/null +++ b/amarok/src/scripts/graphequalizer/README @@ -0,0 +1,14 @@ +
    Graph Equalizer by Ian Monroe
    +About:
    +Idea comes from an aRTs plugin; it's an equalizer that works by plotting and dragging +points on a graph. Gives a lot more control in fewer clicks. I'm releasing it as a +"script" with the idea of integrating it into Amarok 1.3 after user feedback.
    +
    +Using:
    +Just click 'Run' in the Script Manager and the equalizer will open. To +open the dialog again click 'Configure.' Points are added by double clicking on the +line. The equalizer settings take affect once you release the point from a drag.
    +
    +TODO:
    +*delete points
    +*remember settings between runs
    diff --git a/amarok/src/scripts/graphequalizer/eqdialog.ui b/amarok/src/scripts/graphequalizer/eqdialog.ui new file mode 100644 index 00000000..81f98b6c --- /dev/null +++ b/amarok/src/scripts/graphequalizer/eqdialog.ui @@ -0,0 +1,212 @@ + +EqDialog + + + EqDialog + + + + 0 + 0 + 542 + 365 + + + + Graph Equalizer + + + + eqGroupBox + + + + 20 + 20 + 511 + 331 + + + + Enable Equalizer + + + true + + + true + + + + textLabel2 + + + + 10 + 288 + 474 + 35 + + + + <p align="center">The blue drag points can be dragged to adjust the equalizer. Double-click on the line to add a new drag point.</p> + + + + + layout7 + + + + 10 + 20 + 474 + 262 + + + + + unnamed + + + + layout3 + + + + unnamed + + + + layout2 + + + + unnamed + + + + spacer2 + + + Horizontal + + + Maximum + + + + 20 + 20 + + + + + + preampSlider + + + -100 + + + 100 + + + Vertical + + + + + spacer1 + + + Horizontal + + + Maximum + + + + 20 + 20 + + + + + + + + preampLabel + + + <p align="center">Pre-amp</p> + + + + + + + canvasView + + + + 404 + 200 + + + + + 404 + 200 + + + + + + + + + + EqualizerCanvasView +
    equalizercanvasview.h
    + + 400 + 200 + + 0 + + 0 + 0 + 0 + 0 + + image0 +
    +
    + + + 89504e470d0a1a0a0000000d4948445200000016000000160806000000c4b46c3b000003b149444154388dad945f4c5b551cc73fe7dc4b7b4bcba0762d45c43114323599ee6192609c51d883892ce083f1718b3ebb185f8dc91e972cf39d2d2a2f1af664b6f1e0fe3863a0718969700eb0c52142da0242a1bd6d696f7bcff101585203ceb8fd9ece39f99dcff9fe7edf939f88c562ec465f5f9fe609442c161362173c3e3eae7b7a7ac8e7f36432196cdbfe4f907c3e4f2291201e8fe338cec3737357e9e8e828aded1e229d650e1f2d51754b082110124c13a4dc5ea341eb9dc284c0558a853f3ce8cb0677ef500fde7d39d2596679e326597b8e9abb85d7a770ab16ab6983ec5a05b487a70e36f0f4e10afe408d6a558310980108478dba4a1e8233990c5d474b64ed39aa3a8fe5f3317fbf81dbd70bccfeb205947632fd74f6589c1c6ea2f70d03a58ba0c1f2c9bdc1b66de3b8256a6e11cbe7e3ee1d181b590124fe2693aeee08d223c82c3a2c24b7b874bec8f26288774f7bd054504aef0dde6e99c0eb83f9fb266323cb80a27fb0958141836044605a2ee5523393371cc646fee2da37195aa35d0c0c5b4859ac03d7e91712dcaac5adab3650a3ff9d08ef7dd8404bb48869e5d958b5b87dadc4c9a1464e9f0d0326df7ebd86bd2e310cb1bf62d384d59441f2d70a070e1c60e09489929b988681bdd9cc97170bcc4c65595f71f8e0e3301337fc24a7732467831875a47f289652b0be5e4151e6d07316c1b0c0340d8ab92023e76d66a6b2840e36d2fb7a13fee632475e6edc367ea98a90fb98b7dd6310ca0328a44761582e1bab41befabcc0ec940d28bc5e93b68e064cab84e1d9beaeb48934eac1f53b01c1b000fca496aa54b61a99fcde61662a4b4b4b23d1680be9d426173e4df3602a48ea411989a4fd590f52a8fd156b05ed9d350e3defe3cfdf4b4c7ce770ea7d3fb9f520afbe1620daeee5c26735d20b9b9cfb6811a754a439e4e5c5639a4caa1e5caf586bfc0197b78702005cb9b4cae4cd3267ce8638fe964bd72b393e39d74928d242617303a756a37f284447770dcdbffc6384a05a85de1306e9a52057c7527c7131c3c42d3f475eb2303c82d4fc3276d6811db37efeb148723082d9b08f79f97c1e5729109a9a28307cc622d2d6cdf52b2b24efe548dedb00142009862cfa879ee1a71f6cec928353511472fbf4389148b0b0e0c108081412458dfe21c9f11351e67e7358595468246d1d1e5e38a6e9e851bc39d84ab502a669331dafec0d8ec7e3e8cb06e1a881d727d1ae40180a434a8c9db129a54126ad48a7358c2b4c5352c8c374bcccdab2bb37d8719cba79fab8211f9df218e0582c261e95f8bfc04f1a1e8bc5c4dfe0a190172af6a9690000000049454e44ae426082 + + + + + eqGroupBox + toggled(bool) + EqDialog + eqGroupBox_toggled(bool) + + + + eqdialog.ui.h + + + eqGroupBox_toggled( bool eqEnabled ) + + + init() + + + + equalizercanvasview.h + +
    diff --git a/amarok/src/scripts/graphequalizer/eqdialog.ui.h b/amarok/src/scripts/graphequalizer/eqdialog.ui.h new file mode 100644 index 00000000..c36287ed --- /dev/null +++ b/amarok/src/scripts/graphequalizer/eqdialog.ui.h @@ -0,0 +1,46 @@ +/**************************************************************************** +** ui.h extension file, included from the uic-generated form implementation. +** +** If you want to add, delete, or rename functions or slots, use +** Qt Designer to update this file, preserving your code. +** +** You should not define a constructor or destructor in this file. +** Instead, write your code in functions called init() and destroy(). +** These will automatically be called by the form's constructor and +** destructor. +*****************************************************************************/ +#include +#include +//#include + +void EqDialog::init() +{ + //kdDebug() << "start" << endl; + QCanvas* canvas = new QCanvas(); + canvas->resize(400, 200); + canvasView->setVScrollBarMode(QScrollView::AlwaysOff); + canvasView->setHScrollBarMode(QScrollView::AlwaysOff); + canvasView->setCanvas(canvas); + canvasView->init(); + QByteArray send_data, reply_data; + QCString reply_type; + //kdDebug() << "continue" << endl; + if(!KApplication::dcopClient()->call("amarok","player","equalizerEnabled()", send_data, reply_type, reply_data,true,1000)); + //kdDebug() << "called" << endl; + QDataStream answer(reply_data, IO_ReadOnly); + //kdDebug() << "answer created" << answer << endl; + bool eqEnabled; + answer >> eqEnabled; + //kdDebug() << "eqEnabled set to " << eqEnabled << endl; + eqGroupBox->setChecked(eqEnabled); + //kdDebug() << "end" << endl; +} + + +void EqDialog::eqGroupBox_toggled( bool eqEnabled) +{ + QByteArray data; + QDataStream arg(data, IO_WriteOnly); + arg << eqEnabled; + KApplication::dcopClient()->send("amarok", "player", "setEqualizerEnabled(bool)" , data); +} diff --git a/amarok/src/scripts/graphequalizer/equalizercanvasview.cpp b/amarok/src/scripts/graphequalizer/equalizercanvasview.cpp new file mode 100644 index 00000000..ab5b01d9 --- /dev/null +++ b/amarok/src/scripts/graphequalizer/equalizercanvasview.cpp @@ -0,0 +1,289 @@ +/* + * Copyright (C) 2005 by Ian Monroe + * Released under GPL 2 or later, see COPYING + */ + + +#include "equalizercanvasview.h" + +#include +#include +#include + +#include +#include +#include +#include +#include + +EqualizerCanvasView::EqualizerCanvasView(QWidget *parent = 0, const char *name = 0) + : QCanvasView(parent, name) +{ + m_pen.setWidth(5); + m_circleList = new QPtrList(); +} + +//called after setCanvas +void +EqualizerCanvasView::init() +{ + +// QCanvasLine* line = new QCanvasLine(this->canvas()); +// line->setPoints(0,100,400,100); +// line->setPen(QPen(m_pen)); +// line->setBrush(QBrush(Qt::black)); + QCanvasLine* lineLeft = makeLine(QPoint(0,canvas()->height()/2) + ,QPoint(canvas()->width()/2,canvas()->height()/2)); + QCanvasLine* lineRight= makeLine(QPoint(canvas()->width()/2,canvas()->height()/2) + ,QPoint(canvas()->width(),canvas()->height()/2)); + EqualizerCircle* circleMid = new EqualizerCircle(canvas()->width()/2 + ,canvas()->height()/2 + ,canvas() + ,lineLeft + ,lineRight + ,m_circleList); + EqualizerCircle* circleLeft = new EqualizerCircle(0,100,this->canvas(),NULL,lineLeft,m_circleList); + EqualizerCircle* circleRight = new EqualizerCircle(400,100,this->canvas(),lineRight,NULL,m_circleList); + circleLeft->show(); + circleRight->show(); + circleMid->show(); +// line->show(); + this->canvas()->update(); +} + +void +EqualizerCanvasView::contentsMousePressEvent(QMouseEvent *event) +{ + QCanvasItemList items = canvas()->collisions(event->pos()); + if (items.empty()) + m_selectedItem = 0; + else + m_selectedItem = *(items.begin()); +} + + +void +EqualizerCanvasView::contentsMouseDoubleClickEvent(QMouseEvent *event) +{ + if (event->button() == LeftButton && m_selectedItem + && m_selectedItem->rtti() == QCanvasLine::RTTI) + { + QCanvasLine* line = (QCanvasLine*) m_selectedItem; + int y = getY(event->x()); + + EqualizerCircle* circle = new EqualizerCircle(event->x(), y + ,canvas() + ,makeLine(line->startPoint(),QPoint(event->x(),event->y())) + ,makeLine(QPoint(event->x(),event->y()),line->endPoint()) + ,m_circleList); + //kdDebug() << event->x() << 'x' << y << " cursor at " << event->x() << 'x' << event->y() << endl; + circle->show(); + canvas()->update(); + m_selectedItem=0; + delete line; + canvas()->update(); + } +} +void +EqualizerCanvasView::contentsMouseMoveEvent(QMouseEvent *event) +{ + if ((event->state() & LeftButton) && m_selectedItem ) { + // kdDebug() << "dragging " << m_selectedItem->rtti() << endl; + if (m_selectedItem->rtti() == QCanvasEllipse::RTTI) + { + EqualizerCircle* circle = (EqualizerCircle*) m_selectedItem; + circle->setLocation(event->pos()); + // kdDebug() << "dragged" <rtti() == QCanvasEllipse::RTTI) + { + emit eqChanged(); + } +} + +/** + * m =(y1-y2)/(x1-x2) + * b = m*x1 - y1 + * y = m*xCoord -b + */ +int +EqualizerCanvasView::getY(int xCoord) +{ +// int y1 = line->startPoint().y(); +// int y2 = line->endPoint().y(); +// int x1 = line->startPoint().x(); +// int x2 = line->endPoint().x(); +// double slope = double(y1-y2) / double(x1-x2); +// kdDebug() << slope << "= (" << y1 << " - " << y2 << ")/(" << x1 << " - " << x2 << ')' << endl; +// double b = slope*double(x1 - y1); +// kdDebug() << b << " = " << slope <<" * "<< x1 << " - " << y1 << endl; +// double y = double(xCoord)*slope - b; +// kdDebug() << y << " = " << xCoord << '*' << slope << " - " << b << endl; + //QPointArray* yAxis = new QPointArray(canvas->height()); + //yAxis->makeEllipse(xCoord, canvas()->height()/2,1,canvas->height()); + //canvas()->collisions(yAxis,0,false); + //delete yAxis; + QMemArray collidedPoints(20); + unsigned int collisionNum = 0; + for(int i=0; iheight();i++) + { + QCanvasItemList list = canvas()->collisions(QPoint(xCoord,i)); + if( ! list.isEmpty()) + { + if(collidedPoints.size() <= collisionNum) + collidedPoints.resize(collidedPoints.size()+100);//for the steep slops + collidedPoints[collisionNum] = i; + collisionNum++; + } + } + collisionNum--; + return collidedPoints[collisionNum] - collisionNum/2; +} + +QCanvasLine* +EqualizerCanvasView::makeLine(QPoint startPoint, QPoint endPoint) +{ + QCanvasLine* line = new QCanvasLine(canvas()); + line->setPoints(startPoint.x(), + startPoint.y(), + endPoint.x(), + endPoint.y()); + line->setZ(-50); + line->setPen(QPen(m_pen)); + line->show(); + return line; +} +QValueList +EqualizerCanvasView::currentSettings() +{ + int step = canvas()->width()/10; + int i; + QValueList ret; + ret << (int)(m_circleList->first()->y() - 100)*-1; + for(i=1; i<9; i++) + ret << (getY(i*step) - 100)*-1; + ret << (int)(m_circleList->last()->y() - 100)*-1; +// kdDebug() << "sending " << ret << endl; + return ret; +} + +////////////////////// +//EqualizerCircle +////////////////////// +EqualizerCircle::EqualizerCircle(int x, int y, QCanvas *canvas, QCanvasLine* line1, QCanvasLine* line2, +QPtrList* circleList ) +: QCanvasEllipse(15, 15, canvas) +{ + m_line1 = line1; + m_line2 = line2; + setBrush(QBrush(Qt::blue)); + move(x,y); + m_circleList = circleList; + + EqualizerCircle* it; + int index = -1; + bool inserted = false; + for ( it = m_circleList->first(); it; it = m_circleList->next() ) + { + if(it->x() > x) + { + index = m_circleList->find(it); + //if(index != 0) + //{ + m_circleList->insert(index,this); + inserted = true; + //} + break; + } + } + if( !inserted ) + m_circleList->append(this); + + //clean up the loose pointer for the line that was split + unsigned int circleIndex = m_circleList->find(this); + if(circleIndex > 0) + m_circleList->at(circleIndex-1)->setLine(RIGHT,m_line1); + if(circleIndex < m_circleList->count()-1) + m_circleList->at(circleIndex+1)->setLine(LEFT,m_line2); +} + +void EqualizerCircle::setLocation(const QPoint &newLocation) +{ + unsigned int circleIndex = m_circleList->find(this); + double xMin, xMax; + QPoint correctedLoc(newLocation); + if(m_line1 == 0 || m_line2 == 0) + {//if the line is on the edges of the board, make it only move vertically + correctedLoc.setX( (int) x()); + } + else + { + xMin = 0; xMax = canvas()->width(); + if(circleIndex > 0) + { + // kdDebug() << "circleIndex " << circleIndex << endl; + xMin = m_circleList->at(circleIndex - 1)->x(); + } + if(circleIndex < m_circleList->count()-1) + { + //kdDebug() << "circleIndex " << circleIndex << " count " << m_circleList->count() << endl; + xMax = m_circleList->at(circleIndex + 1)->x(); + } + if(newLocation.x() < xMin) + { + //kdDebug() << "set " << newLocation.x() << " to xMin " << xMin << endl; + correctedLoc.setX( (int) (xMin + 1.0) ); + } + else if (newLocation.x() > xMax) + { + //kdDebug() << "set " << newLocation.x() << " to xMax " << xMax << endl; + correctedLoc.setX( (int) (xMax - 1.0) ); + } + } + if(newLocation.y() > canvas()->height()) + correctedLoc.setY(canvas()->height()); + else if (newLocation.y() < 0) + correctedLoc.setY(0); + move(correctedLoc.x(), correctedLoc.y()); + if (m_line1) //account for circles on the edge + m_line1->setPoints( m_line1->startPoint().x() + ,m_line1->startPoint().y() + ,correctedLoc.x() + ,correctedLoc.y()); + if(m_line2) + m_line2->setPoints(correctedLoc.x() + , correctedLoc.y() + , m_line2->endPoint().x() + , m_line2->endPoint().y()); + canvas()->update(); +} + +void EqualizerCircle::setLine(WhichLine lineNum, QCanvasLine* line) +{ + if(lineNum == LEFT) + m_line1 = line; + else + m_line2 = line; +} + +void CallAmarok::updateEq() +{ + QByteArray data; + QDataStream arg(data, IO_WriteOnly); + arg << m_preampSlider->value() *-1; + QValueList cs = m_canvasView->currentSettings(); + QValueList::iterator it; + for ( it = cs.begin(); it != cs.end(); ++it ) + arg << (*it); + KApplication::dcopClient()->send("amarok", "player", + "setEqualizer(int,int,int,int,int,int,int,int,int,int,int)" + , data); +} + + +#include "equalizercanvasview.moc" diff --git a/amarok/src/scripts/graphequalizer/equalizercanvasview.h b/amarok/src/scripts/graphequalizer/equalizercanvasview.h new file mode 100644 index 00000000..1f8d8828 --- /dev/null +++ b/amarok/src/scripts/graphequalizer/equalizercanvasview.h @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2005 by Ian Monroe + * Released under GPL 2 or later, see COPYING + */ + + +#ifndef _EQUALIZERCANVASVIEW_H_ +#define _EQUALIZERCANVASVIEW_H_ + +#ifdef HAVE_CONFIG_H +#include +#include +#include +#include + +#endif + +#include + +class EqualizerCircle : public QCanvasEllipse +{ + +public: + enum { RTTI = 1001 }; + + + EqualizerCircle(int x, int y, QCanvas *canvas, QCanvasLine* line1, QCanvasLine* line2, QPtrList* circleList ); + void setLocation(const QPoint &newLocation); + int rtti() { return RTTI; } +private: + enum WhichLine { LEFT = 1, RIGHT = 2 }; + void setLine(WhichLine lineNum, QCanvasLine* line); + + QCanvasLine *m_line1; + QCanvasLine *m_line2; + QPtrList* m_circleList; +}; + +/** + * @short An equalizer widget for Amarok, using a line graph + * @author Ian Monroe + */ +class EqualizerCanvasView : public QCanvasView +{ + Q_OBJECT +public: + EqualizerCanvasView(QWidget *parent, const char *name); + void init(); + void contentsMousePressEvent(QMouseEvent *event); + void contentsMouseDoubleClickEvent(QMouseEvent *event); + void contentsMouseMoveEvent(QMouseEvent *event); + void contentsMouseReleaseEvent(QMouseEvent *event); + QValueList currentSettings(); +signals: + void eqChanged(); +private: + int getY(int xCoord); + QCanvasLine* makeLine(QPoint startPoint, QPoint endPoint); + QPen m_pen; + QCanvasItem* m_selectedItem; + QPtrList* m_circleList; +}; + +class CallAmarok : public QObject +{ + Q_OBJECT +public: + CallAmarok(QObject* parent, const char *name, + EqualizerCanvasView* canvasView, QSlider* preampSlider) + : QObject(parent, name) + { + m_canvasView = canvasView; + m_preampSlider = preampSlider; + } +public slots: + void updateEq(); +private: + QSlider* m_preampSlider; + EqualizerCanvasView* m_canvasView; +}; + +#endif // _EQUALIZERCANVASVIEW_H_ diff --git a/amarok/src/scripts/graphequalizer/equalizerdialog.cpp b/amarok/src/scripts/graphequalizer/equalizerdialog.cpp new file mode 100644 index 00000000..4ebe22d0 --- /dev/null +++ b/amarok/src/scripts/graphequalizer/equalizerdialog.cpp @@ -0,0 +1,53 @@ +/*************************************************************************** + * Copyright (C) 2005 by Ian Monroe * + * ian@monroe.nu * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Steet, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ +#include "equalizerdialog.h" +#include "equalizercanvasview.h" + +#include +#include +#include + +EqualizerDialog::EqualizerDialog() +{ + QBoxLayout* overallLayout = new QHBoxLayout(this, 5, -1, "overallLayout"); + QBoxLayout* sliderLayout = new QVBoxLayout(this, 5, -1, "sliderLayout"); + + QSlider* preampSlider = new QSlider(-100,100,1,0,QSlider::Vertical,this, "preampSlider"); + QLabel* preampLabel = new QLabel("Pre-amp",this,"preampLabel"); + + sliderLayout->addWidget(preampSlider); + sliderLayout->addWidget(preampLabel); + + QCanvas* canvas = new QCanvas(); + canvas->resize(400, 200); + + EqualizerCanvasView* canvasView = new EqualizerCanvasView(canvas, this, "canvasView"); + + overallLayout->addLayout(sliderLayout); + overallLayout->addWidget(canvasView); +} + + +EqualizerDialog::~EqualizerDialog() +{ +} + + +#include "equalizerdialog.moc" diff --git a/amarok/src/scripts/graphequalizer/equalizerdialog.h b/amarok/src/scripts/graphequalizer/equalizerdialog.h new file mode 100644 index 00000000..81f20195 --- /dev/null +++ b/amarok/src/scripts/graphequalizer/equalizerdialog.h @@ -0,0 +1,39 @@ +/*************************************************************************** + * Copyright (C) 2005 by Ian Monroe * + * ian@monroe.nu * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Steet, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ +#ifndef EQUALIZERDIALOG_H +#define EQUALIZERDIALOG_H + +#include + + +/** +@author Ian Monroe +*/ +class EqualizerDialog : public QDialog +{ +Q_OBJECT +public: + EqualizerDialog(); + + ~EqualizerDialog(); + +}; + +#endif diff --git a/amarok/src/scripts/graphequalizer/main.cpp b/amarok/src/scripts/graphequalizer/main.cpp new file mode 100644 index 00000000..6f540427 --- /dev/null +++ b/amarok/src/scripts/graphequalizer/main.cpp @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2005 by Ian Monroe + * Released under GPL 2 or later, see COPYING + */ + +#include "eqdialog.h" +#include "equalizercanvasview.h" +#include "stdinreader.h" +#include +#include +#include +#include +#include + + +#include +#include + +#include +#include + + +static const char description[] = + I18N_NOOP("An Amarok Equalizer using a line graph"); + +static const char version[] = "0.5"; + +int main(int argc, char **argv) +{ + KAboutData about("Graph Equalizer", I18N_NOOP("Graph Equalizer"), version, description, + KAboutData::License_GPL, "(C) 2005 Ian Monroe", 0, 0, "ian@monroe.nu"); + about.addAuthor( "Ian Monroe", 0, "ian@monroe.nu" ); + KCmdLineArgs::init( argc, argv, &about ); + KApplication app; + EqDialog *mainWin = new EqDialog(); +// mainWin = new EqualizerGraph(0,"equalizerdialog"); +// QCanvas canvas; +// canvas.resize(400, 200); +// EqualizerCanvasView* eq = new EqualizerCanvasView(&canvas,mainWin,"eqcanvasview"); +// mainWin->getHLayout()->addWidget(eq); +// app.setMainWidget( mainWin ); + mainWin->show(); + StdinReader* listen = new StdinReader(mainWin, "ioListener"); + CallAmarok* ca = new CallAmarok(mainWin,"ca", mainWin->canvasView,mainWin->preampSlider); + mainWin->connect(listen, SIGNAL(openWindow()), mainWin, SLOT(show())); + mainWin->connect(mainWin->canvasView,SIGNAL(eqChanged()),ca, SLOT(updateEq())); + mainWin->connect(mainWin->preampSlider,SIGNAL(sliderReleased()),ca, SLOT(updateEq())); + return app.exec(); +} + diff --git a/amarok/src/scripts/graphequalizer/stdinreader.h b/amarok/src/scripts/graphequalizer/stdinreader.h new file mode 100644 index 00000000..20b583ae --- /dev/null +++ b/amarok/src/scripts/graphequalizer/stdinreader.h @@ -0,0 +1,41 @@ +#ifndef _STDINREADER_H_ +#define _STDINREADER_H_ + +/* + * Copyright (C) 2005 by Ian Monroe + * Released under GPL 2 or later, see COPYING + */ +#include +#include +#include + +class StdinReader : public QObject +{ +Q_OBJECT + public: + StdinReader(QObject * parent = 0, const char * name = 0) + :QObject(parent,name) + { + QSocketNotifier* streamListener = new QSocketNotifier(0, QSocketNotifier::Read, this, "stdinWatcher"); + connect(streamListener, SIGNAL(activated(int)), this, SLOT(dataRecieved()) ); + + } + ~StdinReader() { } + signals: + void openWindow(); + public slots: + void dataRecieved() + { + //separate stdin pointer necesary for OS X for reasons unknown + FILE * stdin_ptr = stdin; + QString signal; + QTextIStream( stdin_ptr ) >> signal; + if(signal == "configure") + emit openWindow(); + } + + +}; + +#endif + diff --git a/amarok/src/scripts/lyrics_astraweb/COPYING b/amarok/src/scripts/lyrics_astraweb/COPYING new file mode 100644 index 00000000..6822c1a4 --- /dev/null +++ b/amarok/src/scripts/lyrics_astraweb/COPYING @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 51 Franklin Steet, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) 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 +this service 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 make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. 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. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute 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 and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +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 +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the 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 a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, 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. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE 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. + + 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 +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Steet, Fifth Floor, Boston, MA 02110-1301 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision 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, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This 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 Library General +Public License instead of this License. diff --git a/amarok/src/scripts/lyrics_astraweb/Makefile.am b/amarok/src/scripts/lyrics_astraweb/Makefile.am new file mode 100644 index 00000000..537c0782 --- /dev/null +++ b/amarok/src/scripts/lyrics_astraweb/Makefile.am @@ -0,0 +1,8 @@ +lyricsastrawebdir = \ + $(kde_datadir)/amarok/scripts/lyrics_astraweb + +lyricsastraweb_DATA = \ + COPYING \ + README \ + lyrics_astraweb.rb \ + lyrics_astraweb.spec diff --git a/amarok/src/scripts/lyrics_astraweb/README b/amarok/src/scripts/lyrics_astraweb/README new file mode 100644 index 00000000..de7f9183 --- /dev/null +++ b/amarok/src/scripts/lyrics_astraweb/README @@ -0,0 +1,30 @@ +
    lyrics_astraweb
    + +

    +About:
    +A lyrics script that interfaces with http://lyrics.astraweb.com. +

    + +

    +Usage:
    +Just run the script, and then use the Lyrics tab in the Context sidebar. +

    + +

    +Dependencies:
    +

      +
    • Amarok 1.4
    • +
    • Ruby 1.8
    • +
    +

    + +

    +Thanks to:
    +Many thanks to lyrics.astraweb.com for providing their service. +

    + +

    +Author:
    +Mark Kretschmann (markey@web.de) +

    + diff --git a/amarok/src/scripts/lyrics_astraweb/THIS_SCRIPT_WAS_INTENTIONALLY_DISABLED b/amarok/src/scripts/lyrics_astraweb/THIS_SCRIPT_WAS_INTENTIONALLY_DISABLED new file mode 100644 index 00000000..e69de29b diff --git a/amarok/src/scripts/lyrics_astraweb/lyrics_astraweb.rb b/amarok/src/scripts/lyrics_astraweb/lyrics_astraweb.rb new file mode 100755 index 00000000..c5285950 --- /dev/null +++ b/amarok/src/scripts/lyrics_astraweb/lyrics_astraweb.rb @@ -0,0 +1,147 @@ +#!/usr/bin/env ruby +# +# Amarok Script for fetching song lyrics from http://lyrics.astraweb.com. +# +# (c) 2006 Mark Kretschmann +# +# License: GNU General Public License V2 + + +require "net/http" +require "net/telnet" +require "rexml/document" +require "uri" + +def showLyrics( lyrics ) + system("dcop", "amarok", "contextbrowser", "showLyrics", lyrics) +end + + +def fetchLyrics( artist, title ) + # Astraweb search term is just a number of words separated by "+" + artist.gsub!( " ", "+" ) + title.gsub!( " ", "+" ) + + host = "search.lyrics.astraweb.com" + path = "/?word=#{artist}+#{title}" + page_url = "http://" + host + path + + h = Net::HTTP.new( host, 80 ) + response = h.get( path ) + + unless response.code == "200" +# lyrics = "HTTP Error: #{response.message}" + system("dcop", "amarok", "contextbrowser", "showLyrics") + return + end + + body = response.body() + + body.gsub!( "\n", "" ) # No need for \n, just complicates our RegExps + + md = /(" ) + root = doc.add_element( "suggestions" ) + root.add_attribute( "page_url", page_url ) + xml = "" + + if not md == nil + + body = md[1].to_s() + + entries = body.split( ')([^<]*)/.match( entry )[2].to_s() + title = /(display\.lyrics.*?>)([^<]*)/.match( entry )[2].to_s() + # album = /(Album:.*?">)([^<]*)/.match( entry )[2].to_s() + + suggestion = root.add_element( "suggestion" ) + suggestion.add_attribute( "url", url ) + suggestion.add_attribute( "artist", artist.unpack("C*").pack("U*") ) if artist + suggestion.add_attribute( "title", title.unpack("C*").pack("U*") ) if title + end + end + doc.write( xml ) +# puts( xml ) + showLyrics( xml ) + + rescue SocketError + showLyrics( "" ) +end + + +def fetchLyricsByUrl( url ) + # Note: Using telnet here cause the fucking site has a broken cgi script, delivering + # a broken header, which makes Net::HTTP::get() crap out + + host = "display.lyrics.astraweb.com" + port = 2000 + page_url = "http://#{host}:#{port}#{url}" + + h = Net::Telnet.new( "Host" => host, "Port" => port ) + + body = h.cmd( "GET #{url}\n" ) + body.gsub!( "\n", "" ) # No need for \n, just complicates our RegExps + + artist_title = /(Lyrics: )([^<]*)/.match( body )[2].to_s() + artist = artist_title.split( " - " )[0] + title = artist_title.split( " - " )[1] + + lyrics = /(<font face=arial size=2>)(.*)(<br><br><br><center>)/.match( body )[2].to_s() + lyricstwo = /(SPONSORS<\/font><br><\/center>)(.*?)(<\/font>)/.match( body )[2].to_s() + lyrics.concat(lyricstwo) + lyrics.gsub!( /<[Bb][Rr][^>]*>/, "\n" ) # HTML -> Plaintext + doc = REXML::Document.new( "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>" ) + root = doc.add_element( "lyrics" ) + root.add_attribute( "page_url", page_url ) + root.add_attribute( "artist", artist.unpack("C*").pack("U*") ) if artist + root.add_attribute( "title", title.unpack("C*").pack("U*") ) if title + root.text = lyrics.unpack("C*").pack("U*") if lyrics + + xml = "" + doc.write( xml ) + +# puts( xml ) + showLyrics( xml ) + + rescue SocketError + showLyrics( "" ) +end + + +################################################################## +# MAIN +################################################################## + +# fetchLyrics( "The Cardigans", "Lovefool" ) +# fetchLyricsByUrl( '/display.cgi?whiskeytown..faithless_street..faithless_street' ) +# exit() + + +loop do + message = gets().chomp() + command = /[A-Za-z]*/.match( message ).to_s() + + case command + when "configure" + system("dcop", "amarok", "playlist", "popupMessage", 'This script does not require any configuration.') + + when "fetchLyrics" + args = message.split() + + artist = args[1] + title = args[2] + + fetchLyrics( URI.unescape( artist ), URI.unescape( title ) ) + + when "fetchLyricsByUrl" + url = message.split()[1] + + fetchLyricsByUrl( url ) + end +end + diff --git a/amarok/src/scripts/lyrics_astraweb/lyrics_astraweb.spec b/amarok/src/scripts/lyrics_astraweb/lyrics_astraweb.spec new file mode 100644 index 00000000..f75e0bca --- /dev/null +++ b/amarok/src/scripts/lyrics_astraweb/lyrics_astraweb.spec @@ -0,0 +1,6 @@ +name = Astraweb +type = lyrics + +[Lyrics] +site = Astraweb +site_url = http://lyrics.astraweb.com diff --git a/amarok/src/scripts/lyrics_lyrc/COPYING b/amarok/src/scripts/lyrics_lyrc/COPYING new file mode 100644 index 00000000..6822c1a4 --- /dev/null +++ b/amarok/src/scripts/lyrics_lyrc/COPYING @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 51 Franklin Steet, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) 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 +this service 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 make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. 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. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute 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 and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +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 +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the 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 a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, 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. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE 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. + + 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 +convey 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 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Steet, Fifth Floor, Boston, MA 02110-1301 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision 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, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + <signature of Ty Coon>, 1 April 1989 + Ty Coon, President of Vice + +This 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 Library General +Public License instead of this License. diff --git a/amarok/src/scripts/lyrics_lyrc/Makefile.am b/amarok/src/scripts/lyrics_lyrc/Makefile.am new file mode 100644 index 00000000..4a677192 --- /dev/null +++ b/amarok/src/scripts/lyrics_lyrc/Makefile.am @@ -0,0 +1,10 @@ +lyricslyrcdir = \ + $(kde_datadir)/amarok/scripts/lyrics_lyrc + +lyricslyrc_SCRIPTS = \ + lyrics_lyrc.rb + +lyricslyrc_DATA = \ + COPYING \ + README \ + lyrics_lyrc.spec diff --git a/amarok/src/scripts/lyrics_lyrc/README b/amarok/src/scripts/lyrics_lyrc/README new file mode 100644 index 00000000..5f924ed3 --- /dev/null +++ b/amarok/src/scripts/lyrics_lyrc/README @@ -0,0 +1,30 @@ +<div align="center"><b>lyrics_lyrc</b></div> + +<p> +<b>About:</b><br> +A lyrics script that interfaces with http://lyrc.com.ar. +</p> + +<p> +<b>Usage:</b><br> +Just run the script, and then use the Lyrics tab in the Context sidebar. +</p> + +<p> +<b>Dependencies:</b><br> +<ul> +<li>Amarok 1.4</li> +<li>Ruby 1.8</li> +</ul> +</p> + +<p> +<b>Thanks to:</b><br> +Many thanks to lyrc.com.ar for providing their service. +</p> + +<p> +<b>Author:</b><br> +Mark Kretschmann (markey@web.de) +</p> + diff --git a/amarok/src/scripts/lyrics_lyrc/lyrics_lyrc.rb b/amarok/src/scripts/lyrics_lyrc/lyrics_lyrc.rb new file mode 100755 index 00000000..c37ce292 --- /dev/null +++ b/amarok/src/scripts/lyrics_lyrc/lyrics_lyrc.rb @@ -0,0 +1,201 @@ +#!/usr/bin/env ruby +# +# Amarok Script for fetching song lyrics from http://lyrc.com.ar. +# Ported from Amarok's contextbrowser.cpp. +# +# (c) 2006 Mark Kretschmann <markey@web.de> +# (c) 2004 Christian Muehlhaeuser <chris@chris.de> +# (c) 2005 Reigo Reinmets <xatax@hot.ee> +# (c) 2005 Alexandre Pereira de Oliveira <aleprj@gmail.com> +# +# License: GNU General Public License V2 + +require "net/http" +require "rexml/document" +# require File.dirname( File.expand_path( __FILE__ ) ) + "/../ruby_debug/debug.rb" +require "uri" + +@app_name = "Lyrics_Lyrc" + +class String + def shellquote + return "'" + self.gsub("'", "'\\\\''") + "'" + end +end + +def showLyrics( lyrics ) + system("dcop", "amarok", "contextbrowser", "showLyrics", lyrics) +end + + +def parseLyrics( lyrics ) + if lyrics.include?( "<p><hr" ) + lyrics = lyrics[0, lyrics.index( "<p><hr" )] + else + lyrics = lyrics[0, lyrics.index( "<br><br>" )] + end + + lyrics.gsub!( /<[fF][oO][nN][tT][^>]*>/, "" ) + +# doc = REXML::Document.new() + doc = REXML::Document.new( "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>" ) + root = doc.add_element( "lyrics" ) + + root.add_attribute( "page_url", @page_url ) + title = /(<b>)([^<]*)/.match( lyrics )[2].to_s() + root.add_attribute( "title", title.unpack("C*").pack("U*") ) if title + artist = /(<u>)([^<]*)/.match( lyrics )[2] + root.add_attribute( "artist", artist.to_s().unpack("C*").pack("U*") ) if artist + + lyrics = /(<\/u><\/font>)(.*)/.match( lyrics )[2].to_s() + lyrics.gsub!( /<[Bb][Rr][^>]*>/, "\n" ) # HTML -> Plaintext + + root.text = lyrics.unpack("C*").pack("U*") if lyrics #Convert to UTF-8 + + xml = "" + doc.write( xml ) + +# debug xml + showLyrics( xml ) +end + + +def notFound() + doc = REXML::Document.new( "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>" ) + root = doc.add_element( "suggestions" ) + root.add_attribute( "page_url", @page_url ) + xml = "" + doc.write( xml ) + showLyrics( xml ) +end + +def parseSuggestions( lyrics ) + lyrics = lyrics[lyrics.index( "Suggestions : " )..lyrics.index( "<br><br>" )] + + lyrics.gsub!( "<font color='white'>", "" ) + lyrics.gsub!( "</font>", "" ) + lyrics.gsub!( "<br /><br />", "" ) + + doc = REXML::Document.new( "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>" ) + root = doc.add_element( "suggestions" ) + root.add_attribute( "page_url", @page_url ) + + entries = lyrics.split( "<br>" ) + entries.delete_at( 0 ) + + entries.each() do |entry| + url = /(<a href=")([^"]*)/.match( entry )[2].to_s() + artist_title = /(<a href=.*?>)([^<]*)/.match( entry )[2].to_s() + artist = artist_title.split( " - " )[0] + title = artist_title.split( " - " )[1] + + suggestion = root.add_element( "suggestion" ) + suggestion.add_attribute( "url", url ) + suggestion.add_attribute( "artist", artist.unpack("C*").pack("U*") ) if artist + suggestion.add_attribute( "title", title.unpack("C*").pack("U*") ) if title + end + + xml = "" + doc.write( xml ) + +# debug xml + showLyrics( xml ) +end + + +def fetchLyrics( artist, title, url ) + + host = "lyrc.com.ar" + path = url.empty? ? "/en/tema1en.php?artist=#{artist}&songname=#{title}" : "/en/#{url}" + @page_url = "http://" + host + path + + proxy_host = nil + proxy_port = nil + proxy_user = nil + proxy_pass = nil + if ( @proxy == nil ) + @proxy = `dcop amarok script proxyForUrl #{@page_url.shellquote}` + end + proxy_uri = URI.parse( @proxy ) + if ( proxy_uri.class != URI::Generic ) + proxy_host = proxy_uri.host + proxy_port = proxy_uri.port + proxy_user, proxy_pass = proxy_uri.userinfo.split(':') unless proxy_uri.userinfo.nil? + end + + h = Net::HTTP.new( host, 80, proxy_host, proxy_port, proxy_user, proxy_pass ) + response = h.get( path ) + + unless response.code == "200" +# error "HTTP Error: #{response.message}" + `dcop amarok contextbrowser showLyrics ""` + return + end + + lyrics = response.body() + +# puts( lyrics ) + + lyrics.gsub!( "\n", "" ) # No need for LF, just complicates our RegExps + lyrics.gsub!( "\r", "" ) # No need for CR, just complicates our RegExps +# lyrics.gsub!( '', "'" ) # Lyrc has weird encodings + + # Remove images, links, scripts, styles, fonts and tables + lyrics.gsub!( /<[iI][mM][gG][^>]*>/, "" ) + lyrics.gsub!( /<[aA][^>]*>[^<]*<\/[aA]>/, "" ) + lyrics.gsub!( /<[sS][cC][rR][iI][pP][tT][^>]*>[^<]*(<!--[^>]*>)*[^<]*<\/[sS][cC][rR][iI][pP][tT]>/, "" ) + lyrics.gsub!( /<[sS][tT][yY][lL][eE][^>]*>[^<]*(<!--[^>]*>)*[^<]*<\/[sS][tT][yY][lL][eE]>/, "" ) + # remove the leftover from the above subs. + lyrics.gsub!( "<table align=\"left\"><tr><td></td></tr></table>", "" ) + + lyricsPos = lyrics.index( /<[fF][oO][nN][tT][ ]*[sS][iI][zZ][eE][ ]*='2'[ ]*>/ ) + + if lyricsPos + parseLyrics( lyrics[lyricsPos..lyrics.length()] ) + return + + elsif lyrics.include?( "Suggestions : " ) + parseSuggestions( lyrics ) + + else + notFound() + end + + rescue SocketError + showLyrics( "" ) +end + + +################################################################## +# MAIN +################################################################## + +# fetchLyrics( "Cardigans", "Lovefool", "" ) +# fetchLyrics( "queen", "mama", "" ) +# fetchLyrics( "station_rose_", "_dave_(original_1992)", "" ) +# exit() + + +loop do + message = gets().chomp() + command = /[A-Za-z]*/.match( message ).to_s() + + case command + when "configure" + `dcop amarok playlist popupMessage "This script does not require any configuration."` + + when "fetchLyrics" + args = message.split() + + artist = args[1] + title = args[2] + + fetchLyrics( artist, title, "" ) + + when "fetchLyricsByUrl" + url = message.split()[1] + + fetchLyrics( "", "", url ) + end +end + diff --git a/amarok/src/scripts/lyrics_lyrc/lyrics_lyrc.spec b/amarok/src/scripts/lyrics_lyrc/lyrics_lyrc.spec new file mode 100644 index 00000000..491c53a1 --- /dev/null +++ b/amarok/src/scripts/lyrics_lyrc/lyrics_lyrc.spec @@ -0,0 +1,7 @@ +name = Lyrc +type = lyrics + +[Lyrics] +add_url = http://lyrc.com.ar/en/add/add.php?grupo=MAGIC_ARTIST&tema=MAGIC_TITLE&disco=MAGIC_ALBUM&ano=MAGIC_YEAR +site = Lyrc +site_url = http://lyrc.com.ar diff --git a/amarok/src/scripts/mp3fix/COPYING b/amarok/src/scripts/mp3fix/COPYING new file mode 100644 index 00000000..6822c1a4 --- /dev/null +++ b/amarok/src/scripts/mp3fix/COPYING @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 51 Franklin Steet, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) 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 +this service 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 make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. 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. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute 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 and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +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 +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the 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 a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, 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. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE 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. + + 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 +convey 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 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Steet, Fifth Floor, Boston, MA 02110-1301 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision 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, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + <signature of Ty Coon>, 1 April 1989 + Ty Coon, President of Vice + +This 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 Library General +Public License instead of this License. diff --git a/amarok/src/scripts/mp3fix/README b/amarok/src/scripts/mp3fix/README new file mode 100644 index 00000000..b60393e7 --- /dev/null +++ b/amarok/src/scripts/mp3fix/README @@ -0,0 +1,75 @@ +<div align="center"><b>Mp3Fixer (v0.9.4)</b></div> + +<p> +<b>About:</b><br> +Mp3Fixer is a script for repairing MP3 files which display a bogus track length or bitrate in your +audio player. +</p> +<p> +For example, if you have a track that you know is really <i>five minutes</i> long, but +Amarok shows the length as <i>one hour</i>, then this script is for you. Mp3Fixer calculates the +real track length and adds the missing XING header to the file. Additionally, Mp3Fixer is capable +of repairing broken MPEG header frames. +</p> +<p> +It is recommended that you <i>back up</i> your MP3 file before using this script on it. Although the +script is designed not to harm your file, the operation is not reversible and should therefore +be used with caution. +</p> + +<p> +<b>Usage:</b><br> +Select a track in the playlist, then start Mp3Fixer from the context-menu (right mouse click). +</p> +<p> +It is also possible to use Mp3Fixer as a command line tool, by calling the mp3fix.rb script +contained in this package. mp3fix.rb only requires Ruby to be installed, not Amarok. +The Mp3Fixer Amarok Script is a frontend for this tool. +</p> + +<p> +<b>Dependencies:</b><br> +<ul> +<li>Amarok 1.3.1</li> +<li>Ruby 1.8</li> +</ul> +</p> + +<p> +<b>ChangeLog:</b><br> +Version 0.9.4:<br> +<ul> +<li>Fixed potential runtime error with some files.</li> +</ul> +Version 0.9.3:<br> +<ul> +<li>Show detailed repair summary.</li> +<li>Some mp3 encoders (e.g. lame) add a block of zeros as padding to the beginning +of the file. This is now correctly handled.</li> +<li>mp3fix.rb did not work with source != destination.</li> +</ul> +Version 0.9.2:<br> +<ul> +<li>Automatically update the collection after repairing files.</li> +<li>Automatically refresh the playlist.</li> +</ul> +Version 0.9.1:<br> +<ul> +<li>Fixed a bug that could make the script crap out with a runtime error.</li> +<li>Show current filename in the statusbar.</li> +</ul> +Version 0.9:<br> +<ul> +<li>mp3fix.rb can now repair broken MPEG frames.</li> +<li>Batch processing of multiple selected files.</li> +<li>Using Amarok's statusbar for feedback instead of kdialog. This means, KDE-base is +no longer required.</li> +<li>More reliable, better error checking.</li> +</ul> +</p> + +<p> +<b>Author:</b><br> +Mark Kretschmann (markey@web.de) +</p> + diff --git a/amarok/src/scripts/mp3fix/TODO b/amarok/src/scripts/mp3fix/TODO new file mode 100644 index 00000000..8912db00 --- /dev/null +++ b/amarok/src/scripts/mp3fix/TODO @@ -0,0 +1,6 @@ + TODO MP3FIXER +=============== + +08:58 < sebr> markey: won't fix a file with a $ sign in the filename + + diff --git a/amarok/src/scripts/mp3fix/mp3fix.rb b/amarok/src/scripts/mp3fix/mp3fix.rb new file mode 100644 index 00000000..c4359b9c --- /dev/null +++ b/amarok/src/scripts/mp3fix/mp3fix.rb @@ -0,0 +1,278 @@ +#!/usr/bin/env ruby +# +# Script for fixing mp3 files which show a bogus track length. Calculates the real +# track length and adds the XING header to the file, and repairs broken MPEG headers. +# +# (c) 2005 Mark Kretschmann <markey@web.de> +# License: GNU General Public License V2 + + +class NumArray < Array + # + # Return the nearest matching number from the array + # + def nearest_match( num ) + smallest = 0 + smallestDiff = nil + + each() do |x| + next if x == nil + diff = ( x - num ).abs() + + if smallestDiff == nil or diff < smallestDiff + smallestDiff = diff + smallest = x + end + end + + return smallest + end +end + + +# +# Returns the size of the entire ID3-V2 tag. In other words, the offset from +# where the real mp3 data starts. +# @see http://id3lib.sourceforge.net/id3/id3v2com-00.html#sec3.1 +# +def calcId3v2Size( data ) + size = data[6]*2**21 + data[7]*2**14 + data[8]*2**7 + data[9] + size = size + 10 # Header + + return size +end + + +############################################################################ +# MAIN +############################################################################ + +path = "" +destination = "" +repairLog = [] + + +if $*.empty?() or $*[0] == "--help" + puts( "Usage: mp3fix.rb source [destination]" ) + puts() + puts( "Mp3fix is a tool for fixing VBR encoded mp3 that show a bogus track length in your" ) + puts( "audio player. Mp3fix calculates the real track length and adds the missing XING" ) + puts( "header to the file. " ) + puts() + puts( "Additionally, Mp3Fix can repair files with broken MPEG frame headers. This is useful " ) + puts( "for tracks which show an invalid bitrate and samplerate, which also results in bogus " ) + puts( "track length." ) + exit( 1 ) +end + +path = $*[0] +destination = path + +if $*.length() == 2 + destination = $*[1] +end + + +unless FileTest::readable_real?( path ) + puts( "Error: File not found or not readable." ) + exit( 1 ) +end + +unless File.extname( path ) == ".mp3" or File.extname( path ) == ".MP3" + puts( "Error: File is not mp3." ) + exit( 1 ) +end + +if destination == path and not FileTest.writable_real?( destination ) + puts( "Error: Destination file not writable." ) + exit( 1 ) +end + + + +file = File.new( path, "r" ) + +data = file.read() +id3length = 0 +offset = 0 + +if data[0,3] == "ID3" + id3length = calcId3v2Size( data ) + puts( "ID3-V2 detected. Tag size: #{id3length}" ) +else + puts( "ID3-V1 detected." ) +end + +offset = id3length + +SamplesPerFrame = 1152 # Constant for MPEG1 layer 3 +BitrateTable = NumArray.new() +BitrateTable << nil << 32000 << 40000 << 48000 << 56000 << 64000 << 80000 << 96000 +BitrateTable << 112000 << 128000 << 160000 << 192000 << 224000 << 256000 << 320000 +SamplerateTable = NumArray.new() +SamplerateTable << 44100 << 48000 << 32100 + +ChannelMode = data[offset + 3] & 0xc0 >> 6 +XingOffset = ChannelMode == 0x03 ? 17 : 32 + + +puts() +puts( "Analyzing mpeg frames.." ) +puts() + +frameCount = 0 +bitrateCount = 0 +samplerateCount = 0 +firstFrameBroken = false +firstFrameOffset = nil +offset = 0 +header = 0 + +# Iterate over all frames: +# +loop do + loop do + # Find frame sync + offset = data.index( 0xff, offset ) + break if offset == nil or offset > data.length() - 4 + header = data[offset+0]*2**24 + data[offset+1]*2**16 + data[offset+2]*2**8 + data[offset+3] + if header & 0xfff00000 == 0xfff00000 + firstFrameOffset = offset if firstFrameOffset == nil + break + end + offset += 1 + end + + break if offset == nil + validHeader = true + + bitrate = BitrateTable[( header & 0x0000f000 ) >> 12] + if bitrate == nil + validHeader = false + puts( "Bitrate invalid." ) + end + + samplerate = SamplerateTable[( header & 0x00000c00 ) >> 10] + if samplerate == nil + validHeader = false + puts( "Samplerate invalid." ) + end + + padding = ( header & 0x00000200 ) >> 9 + + if validHeader + frameSize = ( SamplesPerFrame / 8 * bitrate ) / samplerate + padding + +# puts( "bitrate : #{bitrate.to_s()}" ) +# puts( "samplerate : #{samplerate.to_s()}" ) +# puts( "padding : #{padding.to_s()}" ) +# puts( "framesize : #{frameSize}" ) +# puts() + + frameCount += 1 + bitrateCount += bitrate + samplerateCount += samplerate + offset += frameSize + + else + firstFrameBroken = true if frameCount == 0 + offset += 1 + end +end + + +AverageBitrate = bitrateCount / frameCount +Length = data.length() / AverageBitrate * 8 + +puts( "Number of frames : #{frameCount}" ) +puts( "Average bitrate : #{AverageBitrate}" ) +puts( "Length (seconds) : #{Length}" ) +puts() + + +# Repair first frame header, if it is broken: +# +if firstFrameBroken + puts() + puts( "Repairing broken header in first frame." ) + repairLog << "* Fixed broken MPEG header\n" + + firstHeader = data[firstFrameOffset+0]*2**24 + data[firstFrameOffset+1]*2**16 + data[firstFrameOffset+2]*2**8 + data[firstFrameOffset+3] + firstHeader |= 0xfff00000 # Frame sync + + # MPEG ID, Layer, Protection + firstHeader &= 0xfff0ffff + firstHeader |= 0x000b0000 + + br = BitrateTable.nearest_match( ( bitrateCount / frameCount ).round() ) + sr = SamplerateTable.nearest_match( ( samplerateCount / frameCount ).round() ) + + puts( "Setting bitrate : #{br}" ) + puts( "Setting samplerate : #{sr}" ) + + firstHeader &= 0xffff00ff + firstHeader |= BitrateTable.index( br ) << 12 + firstHeader |= SamplerateTable.index( sr ) << 10 + + # Write header back + data[firstFrameOffset+0] = ( firstHeader >> 24 ) & 0xff + data[firstFrameOffset+1] = ( firstHeader >> 16 ) & 0xff + data[firstFrameOffset+2] = ( firstHeader >> 8 ) & 0xff + data[firstFrameOffset+3] = ( firstHeader >> 0 ) & 0xff +end + + +unless data[firstFrameOffset + 4 + XingOffset, 4] == "Xing" + puts() + puts( "Adding XING header." ) + repairLog << "* Added XING header\n" + + xing = String.new() + xing << "Xing" + + Flags = 0x0001 | 0x0002 | 0x0004 # Frames and Bytes fields valid + xing << 0 << 0 << 0 << Flags + + xing << ( ( frameCount & 0xff000000 ) >> 24 ) + xing << ( ( frameCount & 0x00ff0000 ) >> 16 ) + xing << ( ( frameCount & 0x0000ff00 ) >> 8 ) + xing << ( ( frameCount & 0x000000ff ) >> 0 ) + + xing << ( ( data.length() & 0xff000000 ) >> 24 ) + xing << ( ( data.length() & 0x00ff0000 ) >> 16 ) + xing << ( ( data.length() & 0x0000ff00 ) >> 8 ) + xing << ( ( data.length() & 0x000000ff ) >> 0 ) + + + # Insert XING header into string, after the first MPEG header + data[firstFrameOffset + 4 + XingOffset, 0] = xing +end + + +unless repairLog.empty?() + puts() + print( "Writing file.. " ) + destfile = File::open( destination, File::CREAT|File::TRUNC|File::WRONLY ) + + if destfile == nil + puts( "Error: Destination file is not writable." ) + exit( 1 ) + end + + destfile << data + puts( "done." ) +end + + +puts() +puts() +puts( "MP3FIX REPAIR SUMMARY:" ) +puts( "======================" ) +unless repairLog.empty?() + puts( repairLog ) +else + puts( "Mp3Fix did not find any defects.") +end +puts() + + diff --git a/amarok/src/scripts/mp3fix/mp3fixer.rb b/amarok/src/scripts/mp3fix/mp3fixer.rb new file mode 100755 index 00000000..1880a29b --- /dev/null +++ b/amarok/src/scripts/mp3fix/mp3fixer.rb @@ -0,0 +1,93 @@ +#!/usr/bin/env ruby +# +# Amarok-Script for fixing VBR encoded mp3 files without XING header. Calculates the real +# track length and adds the XING header to the file. This script is a frontend for the +# mp3fix.rb tool. +# +# (c) 2005 Mark Kretschmann <markey@web.de> +# License: GNU General Public License V2 + + +require "uri" + + +MenuItemName = "MP3Fixer FixIt!" + + +def cleanup() + system("dcop", "amarok", "script", "removeCustomMenuItem", MenuItemName) + # use unlink ??! + system("rm", Dir.getwd() + "/mp3fixer_playlist.m3u") +end + +class String + def shellquote + return "'" + self.gsub("'", "'\\\\''") + "'" + end +end + +trap( "SIGTERM" ) { cleanup() } + +system("dcop", "amarok", "script", "addCustomMenuItem", MenuItemName) + +loop do + message = gets().chomp() + command = /[A-Za-z]*/.match( message ).to_s() + + case command + when "configure" + msg = 'Mp3Fixer does not have configuration options. Simply select a track in the ' + msg += 'playlist, then start Mp3Fixer from the context-menu (right mouse click).' + + system("dcop", "amarok", "playlist", "popupMessage", msg) + + when "customMenuClicked" + if message.include?( MenuItemName ) + args = message.split() + folders = [] + + # Remove the command args + 3.times() { args.delete_at( 0 ) } + + # Iterate over all selected files + args.each() do |arg| + uri = URI.parse( arg ) + path = URI.unescape( uri.path() ) + filename = path.split( "/" ).last() + + puts( "Path: #{path}" ) + + mp3fix = File.dirname( File.expand_path( __FILE__ ) ) + "/mp3fix.rb" + + system("dcop", "amarok", "playlist", "shortStatusMessage", "Mp3Fixer is analyzing the file '#{filename}'...") + output = `env ruby #{mp3fix.shellquote} #{path.shellquote}` + + if $?.success?() + reg = Regexp.new( "MP3FIX REPAIR SUMMARY:.*", Regexp::MULTILINE ) + report = reg.match( output ).to_s() + report.gsub!( "\n", "<BR/>" ) + system("dcop", "amarok", "playlist", "popupMessage", report) + + folders << File.dirname( path ) unless folders.include?( File.dirname( path ) ) + else + reg = Regexp.new( "Error:.*", Regexp::MULTILINE ) + errormsg = reg.match( output ) + + system("dcop", "amarok", "playlist", "popupMessage", "Mp3Fixer #{errormsg}") + end + end + + # Touch all folders of the modified files, so that the scanner picks then up + folders.each do |folder| + system("touch", folder) + end + system("dcop", "amarok", "collection", "scanCollectionChanges") + + # Refresh the playlist + system("dcop", "amarok", "playlist", "saveM3u", Dir.getwd() + "/mp3fixer_playlist.m3u", "false") + system("dcop", "amarok", "playlist", "clearPlaylist") + system("dcop", "amarok", "playlist", "addMedia", Dir.getwd() + "/mp3fixer_playlist.m3u") + end + end +end + diff --git a/amarok/src/scripts/nowplaying/amaroknowplaying.rb b/amarok/src/scripts/nowplaying/amaroknowplaying.rb new file mode 100755 index 00000000..af99d152 --- /dev/null +++ b/amarok/src/scripts/nowplaying/amaroknowplaying.rb @@ -0,0 +1,52 @@ +#!/usr/bin/env ruby +# +# Now playing script for IRC. + +# Use with the "/exec -o" command of your client. You can bind an alias like this: +# /alias np exec -o /home/myself/amaroknowplaying.rb +# +# (c) 2005-2006 Mark Kretschmann <markey@web.de> +# License: GNU General Public License V2 + + +title = `dcop amarok player title 2> /dev/null`.chomp +exit( 1 ) unless $?.success? # Abort if Amarok isn't running +artist = `dcop amarok player artist`.chomp +album = `dcop amarok player album`.chomp +year = `dcop amarok player year`.chomp +lastfm = `dcop amarok player lastfmStation`.chomp + +output = "" + + +if title.empty? + output += `dcop amarok player nowPlaying`.chomp +else + # Strip file extension + extensions = ".ogg", ".mp3", ".wav", ".flac", ".fla", ".wma", ".mpc" + ext = File.extname( title ).downcase + + if extensions.include?( ext ) + title = title[0, title.length - ext.length] + end + + if artist.empty? + output += "#{title}" + else + output += "#{artist} - #{title}" + end + + unless album.empty? + output += " [#{album}" + output += ", #{year}" unless year == "0" + output += "]" + end + + unless lastfm.empty? + output += " (Last.fm #{lastfm})" + end +end + + +puts( "np: #{output}" ) unless output.empty? + diff --git a/amarok/src/scripts/playlist2html/Makefile.am b/amarok/src/scripts/playlist2html/Makefile.am new file mode 100644 index 00000000..bbff12cf --- /dev/null +++ b/amarok/src/scripts/playlist2html/Makefile.am @@ -0,0 +1,10 @@ +playlistexporterdir = $(kde_datadir)/amarok/scripts/playlist2html + +playlistexporter_SCRIPTS = playlist2html.py PlaylistServer.py + +playlistexporter_DATA = \ + Playlist.py \ + README \ + playlist2html.spec \ + PlaylistServer.spec + diff --git a/amarok/src/scripts/playlist2html/Playlist.py b/amarok/src/scripts/playlist2html/Playlist.py new file mode 100644 index 00000000..ff1779c5 --- /dev/null +++ b/amarok/src/scripts/playlist2html/Playlist.py @@ -0,0 +1,153 @@ +#!/usr/bin/env python +# -*- coding: Latin-1 -*- +import user +import os +from xml.dom import minidom + + +# the current.xml file +PLAYLISTFILE = "%s/.kde/share/apps/amarok/current.xml"%(user.home) + +# the fields to be shown via http +FIELDS = ("Artist", "Title", "Album", "Track", "Length", "Genre", "Score" ) + +class Track(object): + """Class that holds the information of one track in the current playlist""" + __slots__ = FIELDS + + def __init__(self, **kwargs): + for key,value in kwargs.iteritems(): + setattr(self, key, value) + + + def toRow(self, style=''): + """Returns the a html-table-row with member values of this class""" + tmp = ['<td>%s</td>'%(i) for i in [getattr(self,f) for f in \ + self.__slots__ ]] + tr_style = '' + if style: + tr_style='class="%s"'%(style) + return '<tr %s>%s</tr>'%(tr_style, ''.join(tmp)) + + +class Playlist: + """The Playlist class represents the Playlist as one object. It holds all + needed information and does most of the work""" + + def __init__(self): + """The Constructor takes no arguments.""" + self.sync() + self.tracks = [] + self.mtime = self._getMtime() + self.firstRun = 1 + self.code=""" <html>\n + <head> + <meta http-equiv="content-type" content="text/html; charset=UTF-8"> + <style> + body { + font-size:10pt; + margin-left:5%%; + margin-right:5%%; + background-color: #9cb2cd; + color: white; + } + table{border:1px white;} + .tr_one { background-color:#394062; } + .tr_two { background-color:#202050;} + th{color:dark-grey;} + .window {background-color:#d9d9d9; border: 1px solid black; + padding:15px;} + a:link { color:#074876; border:0px; text-decoration:none; } + a:visited { color:#074876; text-decoration:none; text-decoration:none; } + + a:active { color:#074876; text-decoration:none; text-decoration:none; } + a:hover { text-decoration:none; text-decoration:none; background-color:#074876; + color:white; } + </style> + + </head> + <title>AmaroK playlist\n +
    current + amarok + playlist of %s
    + \n +
    %s
    \n + \n + """ + self.encoding = "UTF-8" + self.fullPage = "" + self._buildDoc() + + + + def toHtml(self): + """Returns a html representation of the whole Playlist""" + if self.firstRun: + self._setFullPage() + self.firstRun = 0 + else: + newmtime = self._getMtime() + if newmtime !=self.mtime: + self._buildDoc() + self._setFullPage() + self.mtime = newmtime + return self.fullPage + + + def _setFullPage(self): + self.fullPage = self.code%(os.environ['LOGNAME'],(self._createTable()).encode(self.encoding,'replace')) + + + def _getMtime(self): + """gets the mtime from the current.xml file, to check if the current.xml + has been modified or not""" + return os.stat(PLAYLISTFILE)[-2] + + + def _buildDoc(self): + """Build the DOM-doc and calls the _extractTracks-Method""" + self.doc = minidom.parse(PLAYLISTFILE) + self._extractTracks() + + def _extractTracks(self): + """extracts all "item"-elements from the doc and creates an Track-object + to store the associated information""" + append = self.tracks.append + for item in self.doc.getElementsByTagName("item"): + curTrack = Track() + for elem in FIELDS: + try: + value = item.getElementsByTagName(elem)[0].firstChild.nodeValue + except: + value = ' ' + setattr(curTrack, elem, value) + append(curTrack) + + + def _createTable(self): + """Returns the HTML-Table""" + + tbl = """ + + %s%s
    """ + rows = self._createRows() + thead = "".join(['%s'%(i) for i in FIELDS]) + return tbl %(thead,"".join(rows)) + + def _createRows(self): + """Returns the table rows""" + retval = [] + i = 1 + for track in self.tracks: + style = 'tr_one' + if i %2 == 0: + style = 'tr_two' + retval.append(track.toRow(style=style)) + i = i+1 + return retval + + def sync(self): + """Saves the current Amarok-Playlist to the current.xml file. Calls + amarok via dcop""" + os.system("dcop amarok playlist saveCurrentPlaylist") diff --git a/amarok/src/scripts/playlist2html/PlaylistServer.py b/amarok/src/scripts/playlist2html/PlaylistServer.py new file mode 100644 index 00000000..8fb2d640 --- /dev/null +++ b/amarok/src/scripts/playlist2html/PlaylistServer.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python +# -*- coding: Latin-1 -*- + +""" + This scripts starts a small http-server that serves the current-playlist as + HTML. Start it and point your browser to http://localhost:4773/ + + Author: Andr Kelpe fs111 at web dot de + License: GPL + +""" +import SimpleHTTPServer +import BaseHTTPServer +from Playlist import Playlist + +# the port number to listen to +PORT = 4773 + + +PLIST = None + +class RequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler): + """We need our own 'RequestHandler, to handle the requests, that arrive at + our server.""" + + def do_GET(self): + """Overwrite the do_GET-method of the super class.""" + self.send_response(200) + self.send_header("content-type","text/html") + self.end_headers() + self.wfile.write(PLIST.toHtml()) + + +def main(): + """main is the starting-point for our script.""" + global PLIST + PLIST = Playlist() + srv = BaseHTTPServer.HTTPServer(('',PORT),RequestHandler) + srv.serve_forever() + + +if __name__ == "__main__": + main() + diff --git a/amarok/src/scripts/playlist2html/PlaylistServer.spec b/amarok/src/scripts/playlist2html/PlaylistServer.spec new file mode 100644 index 00000000..3cfcbdbd --- /dev/null +++ b/amarok/src/scripts/playlist2html/PlaylistServer.spec @@ -0,0 +1,2 @@ +name = PlaylistServer +type = generic diff --git a/amarok/src/scripts/playlist2html/README b/amarok/src/scripts/playlist2html/README new file mode 100644 index 00000000..327945c1 --- /dev/null +++ b/amarok/src/scripts/playlist2html/README @@ -0,0 +1,18 @@ +
    PLAYLIST2HTML and PLAYLISTSERVER
    + +About: PLAYLIST2HTML
    +
    +Generates a HTML file with the contents of the current playlist.
    +
    +About: PLAYLISTSERVER
    +This scripts starts a small http-server that serves the current-playlist as +HTML. Start it and point your browser to http://localhost:4773/
    +
    +Dependencies:
    +Python 2.3
    +
    +License:
    +GPL V2
    +
    +Author:
    +Andr Kelpe ( fs111 at web dot de )
    diff --git a/amarok/src/scripts/playlist2html/playlist2html.py b/amarok/src/scripts/playlist2html/playlist2html.py new file mode 100644 index 00000000..1b868f07 --- /dev/null +++ b/amarok/src/scripts/playlist2html/playlist2html.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python +# -*- coding: Latin-1 -*- +import user +import os +from Playlist import Playlist + + + +def main(): + stdin = os.popen("kdialog --getsaveurl %s"%(user.home)) + dest = stdin.readline().strip()[5:] + plist = Playlist() + print dest + try: + f = open(dest, "w") + f.write(plist.toHtml()) + f.close() + os.system("kdialog --msgbox 'Amarok-playlist saved to %s'"%(dest)) + except IOError: + os.system('kdialog --error "Sorry, could not save the playlist to %s"'%(dest)) + + + +if __name__ == "__main__": + main() diff --git a/amarok/src/scripts/playlist2html/playlist2html.spec b/amarok/src/scripts/playlist2html/playlist2html.spec new file mode 100644 index 00000000..85556bd2 --- /dev/null +++ b/amarok/src/scripts/playlist2html/playlist2html.spec @@ -0,0 +1,2 @@ +name = Playlist2HTML +type = generic diff --git a/amarok/src/scripts/rbeautify.rb b/amarok/src/scripts/rbeautify.rb new file mode 100755 index 00000000..41749bc0 --- /dev/null +++ b/amarok/src/scripts/rbeautify.rb @@ -0,0 +1,160 @@ +#!/usr/bin/ruby -w + +# Ruby beautifier, version 2.1, 09/11/2006 +# Copyright (c) 2006, P. Lutus +# Released under the GPL + +$tabSize = 2 +$tabStr = " " + +# indent regexp tests + +$indentExp = [ + /^module\b/, + /^if\b/, + /(=\s*|^)until\b/, + /(=\s*|^)for\b/, + /^unless\b/, + /(=\s*|^)while\b/, + /(=\s*|^)begin\b/, + /(^| )case\b/, + /\bthen\b/, + /^class\b/, + /^rescue\b/, + /^def\b/, + /\bdo\b/, + /^else\b/, + /^elsif\b/, + /^ensure\b/, + /\bwhen\b/, + /\{[^\}]*$/, + /\[[^\]]*$/ +] + +# outdent regexp tests + +$outdentExp = [ + /^rescue\b/, + /^ensure\b/, + /^elsif\b/, + /^end\b/, + /^else\b/, + /\bwhen\b/, + /^[^\{]*\}/, + /^[^\[]*\]/ +] + +def makeTab(tab) + return (tab < 0)?"":$tabStr * $tabSize * tab +end + +def addLine(line,tab) + line.strip! + line = makeTab(tab)+line if line.length > 0 + return line + "\n" +end + +def beautifyRuby(path) + commentBlock = false + programEnd = false + multiLineArray = Array.new + multiLineStr = "" + tab = 0 + source = File.read(path) + dest = "" + source.split("\n").each do |line| + if(!programEnd) + # detect program end mark + if(line =~ /^__END__$/) + programEnd = true + else + # combine continuing lines + if(!(line =~ /^\s*#/) && line =~ /[^\\]\\\s*$/) + multiLineArray.push line + multiLineStr += line.sub(/^(.*)\\\s*$/,"\\1") + next + end + + # add final line + if(multiLineStr.length > 0) + multiLineArray.push line + multiLineStr += line.sub(/^(.*)\\\s*$/,"\\1") + end + + tline = ((multiLineStr.length > 0)?multiLineStr:line).strip + if(tline =~ /^=begin/) + commentBlock = true + end + end + end + if(commentBlock || programEnd) + # add the line unchanged + dest += line + "\n" + else + commentLine = (tline =~ /^#/) + if(!commentLine) + # throw out sequences that will + # only sow confusion + while tline.gsub!(/'.*?'/,"") + end + while tline.gsub!(/".*?"/,"") + end + while tline.gsub!(/\`.*?\`/,"") + end + while tline.gsub!(/\{[^\{]*?\}/,"") + end + while tline.gsub!(/\([^\(]*?\)/,"") + end + while tline.gsub!(/\/.*?\//,"") + end + while tline.gsub!(/%r(.).*?\1/,"") + end + tline.gsub!(/\\\"/,"'") + $outdentExp.each do |re| + if(tline =~ re) + tab -= 1 + break + end + end + end + if (multiLineArray.length > 0) + multiLineArray.each do |ml| + dest += addLine(ml,tab) + end + multiLineArray.clear + multiLineStr = "" + else + dest += addLine(line,tab) + end + if(!commentLine) + $indentExp.each do |re| + if(tline =~ re && !(tline =~ /\s+end\s*$/)) + tab += 1 + break + end + end + end + end + if(tline =~ /^=end/) + commentBlock = false + end + end + if(source != dest) + # make a backup copy + File.open(path + "~","w") { |f| f.write(source) } + # overwrite the original + File.open(path,"w") { |f| f.write(dest) } + end + if(tab != 0) + STDERR.puts "#{path}: Indentation error: #{tab}" + end +end + +if(!ARGV[0]) + STDERR.puts "usage: Ruby filenames to beautify." + exit 0 +end + +ARGV.each do |path| + beautifyRuby(path) +end diff --git a/amarok/src/scripts/ruby_debug/Makefile.am b/amarok/src/scripts/ruby_debug/Makefile.am new file mode 100644 index 00000000..7a763171 --- /dev/null +++ b/amarok/src/scripts/ruby_debug/Makefile.am @@ -0,0 +1,5 @@ +rubydebugdir = \ + $(kde_datadir)/amarok/scripts/ruby_debug + +rubydebug_DATA = \ + debug.rb diff --git a/amarok/src/scripts/ruby_debug/debug.rb b/amarok/src/scripts/ruby_debug/debug.rb new file mode 100755 index 00000000..cf1d1e4b --- /dev/null +++ b/amarok/src/scripts/ruby_debug/debug.rb @@ -0,0 +1,157 @@ +#!/usr/bin/env ruby +# +# Debug functions, emulating the ones we have in Amarok +# +# (c) 2006 Ian Monroe +# (c) 2006 Mark Kretschmann +# (c) 2003-5 Max Howell + + +$app_name = "" +$debug_prefix = "" #this is global, so doesn't seem like it would work well + + # + # @short Use this to label sections of your code + # + # Usage: + # + # debugMethod( :mymethod ) + # debug "output1" + # debug "output2" + # end + # + # Will output: + # + # BEGIN: mymethod() + # [prefix] output1 + # [prefix] output2 + # END: mymethod() - Took 0.1s + # + +module DebugMethods + #module adapted from code by Martin Traverso and Brian McCallister + #from http://split-s.blogspot.com/2006/02/design-by-contract-for-ruby.html + @@pending = Hash.new { |hash, key| hash[key] = {} } + + private + + def self.extract(this, method_name) + old_method = this.instance_method(method_name) if !method_name.nil? && this.method_defined?(method_name) + + return old_method, method_name + end + + def self.schedule(type, mod, method_name) + @@pending[mod][method_name] = {:type => type} + end + + def self.included(mod) + old_method_added = mod.method :method_added + + new_method_added = lambda { |id| + if @@pending.has_key? mod + # save the list of methods and clear the entry + # otherwise, we'll have infinite recursion on the call to mod.send(...) + hooks = [] + if @@pending[mod].has_key? id + hooks << @@pending[mod][id] + @@pending[mod].delete id + end + if @@pending[mod].has_key? nil + # hooks with no method name are to be added to method + # definition that follows them + hooks << @@pending[mod][nil] + @@pending[mod].delete nil + end + # define scheduled hooks + hooks.each { |entry| + mod.send entry[:type], id + } + end + old_method_added.call id + } + + class << mod; self; end.send :define_method, :method_added, new_method_added + + class << mod + + def debugMethod(method_name) + old_method, method_name = DebugMethods.extract self, method_name + if !old_method.nil? + self.send(:define_method, method_name) { |*args| + indent = " " * ( caller( 2 ).size ) * 2 + puts "#{$app_name}: #{indent}BEGIN: #{method_name.to_s}() " + t1 = Time.new + returnValue = old_method.bind(self).call(*args) + puts "#{$app_name}: #{indent}END: #{method_name.to_s} - Took #{Time.new - t1}s" + return returnValue + } + else + DebugMethods.schedule :debugMethod, self, method_name + end + end + + end + end +end + +def debug( str ) + indent = " " * ( caller( 2 ).size ) * 2 + prefix = $debug_prefix.empty?() ? "" : "[#{$debug_prefix}]" + puts( "#{$app_name}: #{indent}#{prefix} #{str}" ) +end + +def warning( str ) + indent = " " * ( caller( 2 ).size ) * 2 + prefix = $debug_prefix.empty?() ? "" : "[#{$debug_prefix}]" + puts( "#{$app_name}: #{indent}WARNING: #{prefix} #{str}" ) +end + +def error( str ) + indent = " " * ( caller( 2 ).size ) * 2 + prefix = $debug_prefix.empty?() ? "" : "[#{$debug_prefix}]" + puts( "#{$app_name}: #{indent}ERROR: #{prefix} #{str}" ) +end + +def fatal( str ) + indent = " " * ( caller( 2 ).size ) * 2 + prefix = $debug_prefix.empty?() ? "" : "[#{$debug_prefix}]" + puts( "#{$app_name}: #{indent}FATAL: #{prefix} #{str}" ) + + exit( 1 ) +end + + +#################################################################### +# MAIN +#################################################################### + + +if $0 == __FILE__ + +$app_name = "deBugger" +$debug_prefix = "header" + +class Foo + include DebugMethods + + debugMethod(:funA) + def funA() + funB() + end + + debugMethod(:funB) + def funB() + debug( "This is a test" ) + funC() + end + + debugMethod(:funC) + def funC() + warning( "What the hell is going on here" ) + end +end + + +Foo.new.funA() +end \ No newline at end of file diff --git a/amarok/src/scripts/score2rating/README b/amarok/src/scripts/score2rating/README new file mode 100644 index 00000000..f2622219 --- /dev/null +++ b/amarok/src/scripts/score2rating/README @@ -0,0 +1,39 @@ +
    Score2Rating (v3.0)
    + +

    +About:
    +Score2Rating is a script that converts the scores of your tracks in Amarok to ratings using some +pretty accurate intervals. (In the future, the intervals might become changeable). +This is mostly usable to give you a starting point for using the new rating system in Amarok 1.4. +Tracks that already have a rating will only be updated if the calculated rating is higher. +If you have kdialog installed, a nice progressbar will be displayed showing how the script progresses. +

    +

    +It is recommended that you back up your database (the statistics table at least) before +running this script. It should do no harm, but you can never be too safe... +

    + +

    +Usage:
    +Just start it and then click configure, and it will do everything automagically! +It will tell you tell you (using a popup in the playlist window) when it's done. +

    + +

    +Dependencies:
    +

      +
    • Amarok 1.4-beta3 or newer
    • +
    • Ruby (tested with 1.8)
    • +
    +

    + +

    +Credits:
    +

      +
    • Jocke Andersson (Firetech)
      +Idea and main coding.
    • +
    • Elliot Pahl (halcyonCorsair)
      +Half star patch (for Amarok 1.4-beta3 and newer) and some more work.
      +(Without Elliot, there would be no 2.x...)
    • +
    +

    diff --git a/amarok/src/scripts/score2rating/score2rating.rb b/amarok/src/scripts/score2rating/score2rating.rb new file mode 100755 index 00000000..29edd9df --- /dev/null +++ b/amarok/src/scripts/score2rating/score2rating.rb @@ -0,0 +1,77 @@ +#!/usr/bin/env ruby +# +# Score2Rating 3.0 (for Amarok 1.4) +# --------------------------------- +# +# First of all: +# PLEASE MAKE A BACKUP OF YOUR DATABASE BEFORE RUNNING THIS SCRIPT! +# It might do bad things, although it shouldn't. +# +# This script will convert the scores of your (played) tracks to ratings, +# mostly to give you somewhat of a starting point for the new rating system. +# + + +if !system( "dcop amarok playlist popupMessage \"Score2Rating started. Click the 'Configure' button in the script manager (with Score2Rating selected) to start the conversion. It is recommended that you make a backup of your Amarok database BEFORE doing this.\" > /dev/null 2>&1" ) then #Info message, and running check combined + print "ERROR: A suitable Amarok wasn't found running!\n" + exit(1) #Exit with error +end + +dialog = "" + +trap( "SIGTERM" ) { system( "dcop #{dialog} close" ) if dialog.length > 0 } + +###THRESHOLDS START### +thres = Array.new + +#Awful (1) +thres << 10 + +#Barely Tolerable (1.5) +thres << 30 + +#Tolerable (2) +thres << 45 + +#Okay (2.5) +thres << 60 + +#Good (3, Tracks played full length once (75) will be here) +thres << 70 + +#Very good (3.5) +thres << 80 + +#Excellent (4, Tracks played full length twice (87) will be here) +thres << 85 + +#Amazing (4.5, Tracks played full length three times (91) will be here) +thres << 90 + +#Favourite (5) +thres << 95 + +###THRESHOLDS END### +thres << 100 + +command = "" +while command != "configure" + command = /[A-Za-z]*/.match( gets().chomp() ).to_s() #Wait until user clicks on Configure +end + +dialog = `kdialog --title Score2Rating --icon amarok --progressbar "Converting Scores to Ratings..." 9`.chomp() +dialog = dialog.gsub( /DCOPRef\((.*),(.*)\)/, '\1 \2') #Convert the DCOPRef, Ruby doesn't seem to like it. + +for r in 2 .. 10 + + system( "dcop amarok collection query \"UPDATE statistics SET rating='#{r}' WHERE percentage >= '#{thres[r - 2]}' AND percentage <= '#{thres[r - 1]}' AND rating < '#{r}'\" > /dev/null" ) + + system( "dcop #{dialog} setProgress #{r - 1}" ) if dialog.length > 0 + +end + +system( "dcop #{dialog} close" ) if dialog.length > 0 +dialog = "" + +system( "dcop amarok playlist popupMessage \"Score2Rating is done! All your tracks (that have a score) should now have ratings. You will have to reload your playlist to see them, though.\"" ) +exit(0) diff --git a/amarok/src/scripts/score_default/COPYING b/amarok/src/scripts/score_default/COPYING new file mode 100644 index 00000000..6822c1a4 --- /dev/null +++ b/amarok/src/scripts/score_default/COPYING @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 51 Franklin Steet, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) 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 +this service 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 make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. 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. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute 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 and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +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 +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the 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 a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, 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. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE 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. + + 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 +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Steet, Fifth Floor, Boston, MA 02110-1301 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision 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, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This 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 Library General +Public License instead of this License. diff --git a/amarok/src/scripts/score_default/Makefile.am b/amarok/src/scripts/score_default/Makefile.am new file mode 100644 index 00000000..73122e71 --- /dev/null +++ b/amarok/src/scripts/score_default/Makefile.am @@ -0,0 +1,10 @@ +scoredefaultdir = \ + $(kde_datadir)/amarok/scripts/score_default + +scoredefault_SCRIPTS = \ + score_default.rb + +scoredefault_DATA = \ + COPYING \ + README \ + score_default.spec diff --git a/amarok/src/scripts/score_default/README b/amarok/src/scripts/score_default/README new file mode 100644 index 00000000..683c0a10 --- /dev/null +++ b/amarok/src/scripts/score_default/README @@ -0,0 +1,25 @@ +
    score_default
    + +

    +About:
    +Amarok's default scoring script. It attempts to keep a balanced score for your tracks, using the average percentage of the track's length you listen to. +

    + +

    +Usage:
    +Just run the script, and it will be used to calculate scores for your tracks from then on. +

    + +

    +Dependencies:
    +

      +
    • Amarok 1.4
    • +
    • Ruby 1.8
    • +
    +

    + +

    +Author:
    +Gábor Lehel (illissius@gmail.com) +

    + diff --git a/amarok/src/scripts/score_default/score_default.rb b/amarok/src/scripts/score_default/score_default.rb new file mode 100644 index 00000000..f80a3a7d --- /dev/null +++ b/amarok/src/scripts/score_default/score_default.rb @@ -0,0 +1,36 @@ +#!/usr/bin/env ruby +# +# Amarok Script for custom scoring +# +# (c) 2006 Gábor Lehel +# +# License: GNU General Public License V2 + +require 'uri' + +loop do + args = gets.chomp.split(" ") + + case args[0] + when "configure" + msg = 'This script does not require any configuration.' + `dcop amarok playlist popupMessage "#{msg}"` + + when "requestNewScore" + url = args[1] + prevscore = args[2].to_f + playcount = args[3].to_i + length = args[4].to_i + percentage = args[5].to_i + reason = args[6] + + if( playcount <= 0 ) # not supposed to be less, but what the hell. + newscore = ( prevscore + percentage ) / 2 + else + newscore = ( ( prevscore * playcount ) + percentage ) / ( playcount + 1 ) + end + + system( "dcop", "amarok", "player", "setScoreByPath", URI::decode( url ), newscore.to_s ) + end +end + diff --git a/amarok/src/scripts/score_default/score_default.spec b/amarok/src/scripts/score_default/score_default.spec new file mode 100644 index 00000000..6933e7c4 --- /dev/null +++ b/amarok/src/scripts/score_default/score_default.spec @@ -0,0 +1,2 @@ +name = Default +type = score diff --git a/amarok/src/scripts/score_impulsive/COPYING b/amarok/src/scripts/score_impulsive/COPYING new file mode 100644 index 00000000..6822c1a4 --- /dev/null +++ b/amarok/src/scripts/score_impulsive/COPYING @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 51 Franklin Steet, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) 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 +this service 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 make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. 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. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute 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 and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +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 +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the 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 a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, 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. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE 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. + + 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 +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Steet, Fifth Floor, Boston, MA 02110-1301 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision 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, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This 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 Library General +Public License instead of this License. diff --git a/amarok/src/scripts/score_impulsive/Makefile.am b/amarok/src/scripts/score_impulsive/Makefile.am new file mode 100644 index 00000000..c90e30e8 --- /dev/null +++ b/amarok/src/scripts/score_impulsive/Makefile.am @@ -0,0 +1,10 @@ +scoreimpulsivedir = \ + $(kde_datadir)/amarok/scripts/score_impulsive + +scoreimpulsive_SCRIPTS = \ + score_impulsive.rb + +scoreimpulsive_DATA = \ + COPYING \ + README \ + score_impulsive.spec diff --git a/amarok/src/scripts/score_impulsive/README b/amarok/src/scripts/score_impulsive/README new file mode 100644 index 00000000..25d26236 --- /dev/null +++ b/amarok/src/scripts/score_impulsive/README @@ -0,0 +1,25 @@ +
    score_impulsive
    + +

    +About:
    +A scoring script which is dominantly influenced by how much of a track you listened to on the most recent occasion. +

    + +

    +Usage:
    +Just run the script, and it will be used to calculate scores for your tracks from then on. +

    + +

    +Dependencies:
    +

      +
    • Amarok 1.4
    • +
    • Ruby 1.8
    • +
    +

    + +

    +Author:
    +Gábor Lehel (illissius@gmail.com) +

    + diff --git a/amarok/src/scripts/score_impulsive/score_impulsive.rb b/amarok/src/scripts/score_impulsive/score_impulsive.rb new file mode 100644 index 00000000..c41afe4b --- /dev/null +++ b/amarok/src/scripts/score_impulsive/score_impulsive.rb @@ -0,0 +1,31 @@ +#!/usr/bin/env ruby +# +# Amarok Script for custom scoring +# +# (c) 2006 Gábor Lehel +# +# License: GNU General Public License V2 + +require 'uri' + +loop do + args = gets.chomp.split(" ") + + case args[0] + when "configure" + msg = 'This script does not require any configuration.' + `dcop amarok playlist popupMessage "#{msg}"` + + when "requestNewScore" + url = args[1] + prevscore = args[2].to_f + playcount = args[3].to_i + length = args[4].to_i + percentage = args[5].to_i + reason = args[6] + + newscore = ( ( prevscore + percentage ) / 2 ).to_i + + system( "dcop", "amarok", "player", "setScoreByPath", URI::decode( url ), newscore.to_s ) + end +end diff --git a/amarok/src/scripts/score_impulsive/score_impulsive.spec b/amarok/src/scripts/score_impulsive/score_impulsive.spec new file mode 100644 index 00000000..834c157f --- /dev/null +++ b/amarok/src/scripts/score_impulsive/score_impulsive.spec @@ -0,0 +1,2 @@ +name = Impulsive +type = score diff --git a/amarok/src/scripts/templates/Makefile.am b/amarok/src/scripts/templates/Makefile.am new file mode 100644 index 00000000..7d08fad4 --- /dev/null +++ b/amarok/src/scripts/templates/Makefile.am @@ -0,0 +1,9 @@ +templatedir = \ + $(kde_datadir)/amarok/scripts/templates + +template_DATA = \ + python_qt_template.py \ + ruby_qt_template.rb \ + amarok.rb + + diff --git a/amarok/src/scripts/templates/amarok.rb b/amarok/src/scripts/templates/amarok.rb new file mode 100644 index 00000000..fbbcc7fb --- /dev/null +++ b/amarok/src/scripts/templates/amarok.rb @@ -0,0 +1,85 @@ +############################################################################ +# Copyright (C) 2005 by Ian Monroe # +# ian@monroe.nu # +# # +# This program is free software; you can redistribute it and#or modify # +# it under the terms of the GNU General Public License as published by # +# the Free Software Foundation; either version 2 of the License, or # +# (at your option) any later version. # +# # +# This program is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with this program; if not, write to the # +# Free Software Foundation, Inc., # +# 51 Franklin Steet, Fifth Floor, Boston, MA 02110-1301, USA. # +############################################################################ + +class QtIoListener < Qt::Object + slots 'dataRecieved()' + def initialize(app, iostream, lineHandler) + super(app) + @lineHandler = lineHandler + @iostream = iostream + streamListener = Qt::SocketNotifier.new(iostream.fileno, Qt::SocketNotifier.Read, app, 'stdinWatcher') + Qt::Object.connect(streamListener, SIGNAL("activated(int)"), self, SLOT("dataRecieved()") ) + + end + def dataRecieved + line = @iostream.gets + line.chomp! + @lineHandler.call(line) + end +end + +class AmaroKLib +#Either way, AmaroKLib depends of QtRuby by virtue of using the QtIoListener. +#Making a non-Qt alternative to QtIoListener shouldn't be too difficult, +#you'll just have to get your hands dirty with threads and then modify the definition of +#@listen in this class. And send me the class for inclusion in this template. :) + def initialize(app, commandSlots) + @listener = QtIoListener.new(app, STDIN, Proc.new { |line| + #puts "Recieved command #{line}" + case line + when 'configure' then commandSlots.configure + when 'trackChange' then commandSlots.trackChange + when 'engineStateChange: empty' then commandSlots.stateChange_empty + when 'engineStateChange: idle' then commandSlots.stateChange_idle + when 'engineStateChange: paused' then commandSlots.stateChange_paused + when 'engineStateChange: playing' then commandSlots.stateChange_playing + #not a command given by Amarok, helpful when debugging + when 'exit' then commandSlots.exit + else $stderr.print "Illegal command: #{line}" + end +} ) + end +end + +#make your own child class of AmaroKSlots to send to AmaroKLib. +class AmaroKSlots + def configure + nil + end + def stateChange_empty + nil + end + def stateChange_idle + nil + end + def stateChange_paused + nil + end + def stateChange_playing + nil + end + def trackChange + nil + end + #not a command given by Amarok, used when debugging + def exit + nil + end +end diff --git a/amarok/src/scripts/templates/python_qt_template.py b/amarok/src/scripts/templates/python_qt_template.py new file mode 100755 index 00000000..177d728e --- /dev/null +++ b/amarok/src/scripts/templates/python_qt_template.py @@ -0,0 +1,193 @@ +#!/usr/bin/env python + +############################################################################ +# Python-Qt template script for Amarok +# (c) 2005 Mark Kretschmann +# +# Depends on: Python 2.2, PyQt +############################################################################ +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +############################################################################ + +import ConfigParser +import os +import sys +import threading +import signal +from time import sleep + +try: + from qt import * +except: + os.popen( "kdialog --sorry 'PyQt (Qt bindings for Python) is required for this script.'" ) + raise + + +# Replace with real name +debug_prefix = "[Test Script]" + + +class ConfigDialog( QDialog ): + """ Configuration widget """ + + def __init__( self ): + QDialog.__init__( self ) + self.setWFlags( Qt.WDestructiveClose ) + self.setCaption( "Test Script - Amarok" ) + + foo = None + try: + config = ConfigParser.ConfigParser() + config.read( "testrc" ) + foo = config.get( "General", "foo" ) + except: + pass + + self.adjustSize() + + def save( self ): + """ Saves configuration to file """ + + self.file = file( "testrc", 'w' ) + + self.config = ConfigParser.ConfigParser() + self.config.add_section( "General" ) + self.config.set( "General", "foo", foovar ) + self.config.write( self.file ) + self.file.close() + + self.accept() + + +class Notification( QCustomEvent ): + __super_init = QCustomEvent.__init__ + def __init__( self, str ): + self.__super_init(QCustomEvent.User + 1) + self.string = str + +class Test( QApplication ): + """ The main application, also sets up the Qt event loop """ + + def __init__( self, args ): + QApplication.__init__( self, args ) + debug( "Started." ) + + # Start separate thread for reading data from stdin + self.stdinReader = threading.Thread( target = self.readStdin ) + self.stdinReader.start() + + self.readSettings() + + def readSettings( self ): + """ Reads settings from configuration file """ + + try: + foovar = config.get( "General", "foo" ) + + except: + debug( "No config file found, using defaults." ) + + +############################################################################ +# Stdin-Reader Thread +############################################################################ + + def readStdin( self ): + """ Reads incoming notifications from stdin """ + + while True: + # Read data from stdin. Will block until data arrives. + line = sys.stdin.readline() + + if line: + qApp.postEvent( self, Notification(line) ) + else: + break + + +############################################################################ +# Notification Handling +############################################################################ + + def customEvent( self, notification ): + """ Handles notifications """ + + string = QString(notification.string) + debug( "Received notification: " + str( string ) ) + + if string.contains( "configure" ): + self.configure() + + if string.contains( "engineStateChange: play" ): + self.engineStatePlay() + + if string.contains( "engineStateChange: idle" ): + self.engineStateIdle() + + if string.contains( "engineStateChange: pause" ): + self.engineStatePause() + + if string.contains( "engineStateChange: empty" ): + self.engineStatePause() + + if string.contains( "trackChange" ): + self.trackChange() + +# Notification callbacks. Implement these functions to react to specific notification +# events from Amarok: + + def configure( self ): + debug( "configuration" ) + + self.dia = ConfigDialog() + self.dia.show() + self.connect( self.dia, SIGNAL( "destroyed()" ), self.readSettings ) + + def engineStatePlay( self ): + """ Called when Engine state changes to Play """ + pass + + def engineStateIdle( self ): + """ Called when Engine state changes to Idle """ + pass + + def engineStatePause( self ): + """ Called when Engine state changes to Pause """ + pass + + def engineStateEmpty( self ): + """ Called when Engine state changes to Empty """ + pass + + def trackChange( self ): + """ Called when a new track starts """ + pass + + +############################################################################ + +def debug( message ): + """ Prints debug message to stdout """ + + print debug_prefix + " " + message + +def main( ): + app = Test( sys.argv ) + + app.exec_loop() + +def onStop(signum, stackframe): + """ Called when script is stopped by user """ + pass + +if __name__ == "__main__": + mainapp = threading.Thread(target=main) + mainapp.start() + signal.signal(15, onStop) + # necessary for signal catching + while 1: sleep(120) diff --git a/amarok/src/scripts/templates/ruby_qt_template.rb b/amarok/src/scripts/templates/ruby_qt_template.rb new file mode 100644 index 00000000..9e75f7a3 --- /dev/null +++ b/amarok/src/scripts/templates/ruby_qt_template.rb @@ -0,0 +1,65 @@ +#!/usr/bin/env ruby +############################################################################ +# Copyright (C) 2005 by Ian Monroe # +# ian@monroe.nu # +# # +# This program is free software; you can redistribute it and#or modify # +# it under the terms of the GNU General Public License as published by # +# the Free Software Foundation; either version 2 of the License, or # +# (at your option) any later version. # +# # +# This program is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with this program; if not, write to the # +# Free Software Foundation, Inc., # +# 51 Franklin Steet, Fifth Floor, Boston, MA 02110-1301, USA. # +############################################################################ + +require 'Qt' +require "#{File.dirname( File.expand_path(__FILE__))}/amarok.rb" + +#a simple method to call DCOP. If you use Korundum, you can use +#dR = KDE::DCOPRef.new("amarok", "player"); dR.artist instead +def dcop (val1, val2 = String.new) + str =`dcop amarok player #{val1} #{val2}` + str.chomp! if str != nil + return str +end + +#you can remove methods you don't need, they're all here just so that you can see + +class ExampleActions < AmaroKSlots + def initialize(app) + @app = app + end + def configure + puts "configure" + end + def stateChange_empty + puts "empty" + end + def stateChange_idle + puts "idle" + end + def stateChange_paused + puts "paused" + end + def stateChange_playing + puts "playing" + end + def trackChange + puts "track changed" + puts "Now playing #{dcop('title')} by #{dcop('artist')}" + end + def exit + @app.exit + end +end +a = Qt::Application.new(ARGV) +e = ExampleActions.new(a) +AmarokCommunication = AmaroKLib.new(a, e) +a.exec() \ No newline at end of file diff --git a/amarok/src/scripts/webcontrol/Globals.py b/amarok/src/scripts/webcontrol/Globals.py new file mode 100644 index 00000000..9f9d979e --- /dev/null +++ b/amarok/src/scripts/webcontrol/Globals.py @@ -0,0 +1,74 @@ +# for system call +import os + +# +# the port number to listen to +# +PORT = 4774 + +# +# execution path of script +# +EXEC_PATH = None + +# +# Use simple popen call in order not to depend on dcop python lib +# (since it does currently not work on my computer :) +# +def _initDcopCallPlayer(call): + return os.popen("dcop amarok player %s"%call) + +def _dcopCallPlayer(call): + return _initDcopCallPlayer(call) + +def _initDcopCallPlayerArg(call, val): + return os.popen("dcop amarok player %s %s"%(call,val)) + +def _dcopCallPlayerArg(call, val): + return _initDcopCallPlayerArg(call, val).read() + +def _initDcopCallPlaylist(call): + return os.popen("dcop amarok playlist %s"%call) + +def _initDcopCallPlaylistArg(call, val): + return os.popen("dcop amarok playlist %s %s"%(call,val)) + +def _dcopCallPlaylistArg(call, val): + return _initDcopCallPlaylistArg(call, val).read() + +class DelayedDcop: + def __init__(self, initcall, initcallarg, command, val = None): + self.initcall = initcall + self.initcallarg = initcallarg + self.value = None + self.fd = None + self.arg = val + self.command = command + + def init(self): + if not (self.value is None and self.fd is None): + return + if self.arg is None: + self.fd = self.initcall(self.command) + else: + self.fd = self.initcallarg(self.command, self.arg) + + def result(self): + self.init() + if self.value is None: + self.value = self.fd.read() + return self.value + +class PlayerDcop ( DelayedDcop ): + __super_init = DelayedDcop.__init__ + def __init__(self, command, val = None): + self.__super_init(_initDcopCallPlayer, + _initDcopCallPlayerArg, + command, val) + +class PlaylistDcop ( DelayedDcop ): + __super_init = DelayedDcop.__init__ + def __init__(self, command, val = None): + self.__super_init(_initDcopCallPlaylist, + _initDcopCallPlaylistArg, + command, val) diff --git a/amarok/src/scripts/webcontrol/Makefile.am b/amarok/src/scripts/webcontrol/Makefile.am new file mode 100644 index 00000000..293dbd79 --- /dev/null +++ b/amarok/src/scripts/webcontrol/Makefile.am @@ -0,0 +1,26 @@ +webcontroldir = \ + $(kde_datadir)/amarok/scripts/webcontrol + +webcontrol_SCRIPTS = \ + WebControl.py + +webcontrol_DATA = \ + Globals.py \ + Playlist.py \ + README \ + RequestHandler.py \ + WebControl.spec \ + WebPublisher.py \ + amarok_cut.png \ + controlbackground.png \ + main.css \ + main.js \ + player_end.png \ + player_pause.png \ + player_play.png \ + player_start.png \ + player_stop.png \ + template.thtml \ + vol_speaker.png \ + star.png \ + smallstar.png diff --git a/amarok/src/scripts/webcontrol/Playlist.py b/amarok/src/scripts/webcontrol/Playlist.py new file mode 100644 index 00000000..2338d183 --- /dev/null +++ b/amarok/src/scripts/webcontrol/Playlist.py @@ -0,0 +1,309 @@ +#!/usr/bin/env python +# -*- coding: Latin-1 -*- +# +# Author: Andr� Kelpe fs111 at web dot de +# : Jonas Drewsen kde at xspect dot dk +# : Peter Ndikuwera pndiku at gmail dot com +# +# License: GPL +# + +import user +import os +from xml.dom import minidom +import time + +import string + +import Globals + +# the current.xml file +PLAYLISTFILE = "%s/.kde/share/apps/amarok/current.xml"%(user.home) + +# the fields to be shown via http +FIELDS = ("TrackNo", "Title", "Artist", "Album", "Length", "Rating") + +class Track(object): + """Class that holds the information of one track in the current playlist""" + __slots__ = FIELDS + + max_field_value_lengths = [(0,"")] * len(FIELDS) + + def __init__(self, **kwargs): + for key,value in kwargs.iteritems(): + setattr(self, key, value) + + + def toRow(self, style='', id=None, trackno=None, reqid=None, sesid = None): + """Returns the a html-table-row with member values of this class""" + + trno = str(trackno) + astart = "") + + aend = "" + + tmp = [ '' + astart + i + aend +'' for i in [getattr(self,f) for f in self.__slots__ ] ] + + index = 0 +# for f in self.__slots__ : +# print string.strip(f) + + for i in [getattr(self,f) for f in self.__slots__ ]: + if len(string.strip(i)) > Track.max_field_value_lengths[index][0]: + Track.max_field_value_lengths[index] = (len(string.strip(i)),i) + index += 1 + + tr_style = '' + tr_id = '' + + if style: + tr_style='class="%s"'%(style) + + if trackno == 0: + tr_id = 'id="trackone"' + + if id: + tr_id = 'id="%s"'%(id) + + return '%s'%(tr_style, tr_id, ''.join(tmp)) + +class Playlist: + """The Playlist class represents the Playlist as one object. It holds all + needed information and does most of the work""" + + def __init__(self): + """The Constructor takes no arguments.""" + self.tracks = [] + self.encoding = "UTF-8" + #"ISO-8859-15" + self.fullPage = "" + self.templateFilename = "template.thtml" + self.templateLastChanged = -1 + self.templateLastChanged = self._loadHtmlTemplate() + + def _loadHtmlTemplate(self): + """Loads the global string template variables that are used for + rendering the page. The template can be changed by setting + self.templateFilename to a template file.""" + ep = Globals.EXEC_PATH + st = os.stat(ep + "/" + self.templateFilename)[8] + + if self.templateLastChanged != st: + tmp = open(ep + "/" + self.templateFilename).read() + a = compile(tmp, "", 'exec') + eval(a) + return st + + def toHtml(self, status): + """Returns a html representation of the whole Playlist""" + self.templateLastChanged = self._loadHtmlTemplate() + self.sync() + self.tracks = [] + self.mtime = self._getMtime() + self._buildDoc() + self._setFullPage(status) + return self.fullPage + + + def _createButton(self, name, action, reqid, sesid): + """Return a button to be used as an action""" + return ("") + + def _createVolume(self, vol_val, reqid, sesid): + """Return a HTML volume seletor.""" + + volume = '' + volume += ""; + + button = "
    "; + + for i in range(1,vol_val+1): + volume += ("") + + for i in range(vol_val+1, 11): + volume += ("") + + volume += "
    " + + "" + button + "" + + "" + button + "
    " + return volume + + def _createTimeStr(self, status): + """Returns the string representation of the the remaining time of the current song.""" + + _gmtime = time.gmtime(status.timeLeft()) + + hours = status.timeLeft() / (60*60) + mins = (status.timeLeft() % (60*60)) / 60 + secs = status.timeLeft() - 60 * mins - 60 * 60 * hours + + hours_str = str(hours) + ":" + if hours < 10: + hours_str = "0" + hours_str + if hours == 0: + hours_str = "" + + mins_str = str(mins) + ":" + if mins < 10: + mins_str = "0" + mins_str + + secs_str = str(secs) + if secs < 10: + secs_str = "0" + secs_str + + return hours_str + mins_str + secs_str + + def _trackTimeStr(self, trackTime): + """Returns the string representation of the track time of a song.""" + + _time = int(trackTime) + + hours = _time / (60*60) + mins = (_time % (60*60)) / 60 + secs = _time - 60 * mins - 60 * 60 * hours + + hours_str = str(hours) + ":" + if hours < 10: + hours_str = "0" + hours_str + if hours == 0: + hours_str = "" + + mins_str = str(mins) + ":" + if mins < 10: + mins_str = "0" + mins_str + + secs_str = str(secs) + if secs < 10: + secs_str = "0" + secs_str + + return hours_str + mins_str + secs_str + + def _ratingStars(self, rating): + """Returns a number of stars showing the rating of a song.""" + + _rating = int(rating) + + _numBig = _rating / 2 + _numSmall = _rating % 2 + + img_str="" + + for i in range(1,_numBig+1): + img_str = img_str + "" + + if _numSmall> 0: + img_str = img_str + "" + + return img_str + + def _createActions(self, status): + """Returns HTML for allactions that can be performed and for the time counter""" + + if not status.controlsEnabled(): + return "" + + reqid = "&reqid=" + str(status.reqid) + sesid = "&sesid=" + str(status.sesid) + + playpause = self._createButton("play", "play", reqid, sesid) + + if status.isPlaying(): + playpause = self._createButton("pause", "pause", reqid, sesid) + + buttons = (playpause + " " + + self._createButton("stop", "stop", reqid, sesid) + " " + + self._createButton("start", "prev", reqid, sesid) + " " + + self._createButton("end", "next", reqid, sesid)) + + vol_val = int(float(status.getVolume()) / 10.0 + 0.5) + volume = self._createVolume(vol_val, reqid, sesid) + + return actions % ( buttons, volume, self._createTimeStr(status) ) + + def _setFullPage(self, status): + """Renders the fullpage into the varialbe self.fullPage""" + counter = "" + if status.isPlaying(): + counter = "countdown(" + str(status.timeLeft()) + ");" + + self.fullPage = code % ( counter, + self._createActions(status).encode(self.encoding,'replace'), + os.environ['LOGNAME'], + self._createTable(status).encode(self.encoding,'replace') ) + + def _getMtime(self): + """gets the mtime from the current.xml file, to check if the current.xml + has been modified or not""" + return os.stat(PLAYLISTFILE)[-2] + + + def _buildDoc(self): + """Build the DOM-doc and calls the _extractTracks-Method""" + self.doc = minidom.parse(PLAYLISTFILE) + self._extractTracks() + + def _extractTracks(self): + """extracts all "item"-elements from the doc and creates an Track-object + to store the associated information""" + append = self.tracks.append + i = 1 + for item in self.doc.getElementsByTagName("item"): + curTrack = Track() + for elem in FIELDS: + try: + value = item.getElementsByTagName(elem)[0].firstChild.nodeValue + except: + value = ' ' + if elem == "Title": + value = value.strip() + if elem == "Length": + value = self._trackTimeStr(value); + if elem == "Rating": + value = self._ratingStars(value); + if elem == "TrackNo": + value = str(i) + i = i + 1 + setattr(curTrack, elem, value) + append(curTrack) + + + def _createTable(self, status): + """Returns the HTML-Table""" + tbl = tblhead + rows = self._createRows(status) + thead = "" + "".join(['%s'%(i) for i in FIELDS]) + "" + rowsstr = "".join(rows) + return tbl%("tracks", "" + thead + "" + rowsstr + "") + + def _createRows(self, status): + """Returns the table rows""" + retval = [] + i = 1 + reqid = "&reqid=" + str(status.reqid) + sesid = "&sesid=" + str(status.sesid) + curindex = status.getActiveIndex() + id = None + for track in self.tracks: + style = 'tr_one' + if i %2 == 0: + style = 'tr_two' + if curindex == (i-1): + id = "nowplaying" + retval.append(track.toRow(style=style, id=id, trackno=(i-1), reqid=reqid, sesid=sesid)) + i = i+1 + return retval + + def sync(self): + """Saves the current Amarok-Playlist to the current.xml file. Calls + amarok via dcop""" + os.system("dcop amarok playlist saveCurrentPlaylist") diff --git a/amarok/src/scripts/webcontrol/README b/amarok/src/scripts/webcontrol/README new file mode 100644 index 00000000..dd40f5a5 --- /dev/null +++ b/amarok/src/scripts/webcontrol/README @@ -0,0 +1,24 @@ +
    WEBCONTROL
    +About:
    +Generates a HTML file with the contents of the current playlist and the +ability to control amarok through that.
    +
    +It is based on playlist2html codebase.
    +
    +Using:
    +
    +This scripts starts a small http-server that serves the current-playlist as +HTML. Start it and point your browser to http://localhost:4774/ +You can configure the http-server to disable controlling features.
    +
    +Dependencies:
    +Python 2.2
    +PyQt
    +
    +License:
    +GPL V2
    +
    +Author:
    +Jonas Christian Drewsen ( kde at xspect dot dk )
    +Andr Kelpe ( fs111 at web dot de )
    +Peter C. Ndikuwera ( pndiku at gmail dot com )
    diff --git a/amarok/src/scripts/webcontrol/RequestHandler.py b/amarok/src/scripts/webcontrol/RequestHandler.py new file mode 100644 index 00000000..8519ef35 --- /dev/null +++ b/amarok/src/scripts/webcontrol/RequestHandler.py @@ -0,0 +1,266 @@ +#!/usr/bin/env python +# -*- coding: Latin-1 -*- + +""" + This scripts starts a small http-server that serves the current-playlist as + HTML. Start it and point your browser to http://localhost:4773/ + + Author: Andr Kelpe fs111 at web dot de + : Jonas Drewsen kde at xspect dot dk + + License: GPL + +""" +import SimpleHTTPServer +import BaseHTTPServer +from Playlist import Playlist +import Globals + +import time + +# necessary for <= python2.2 that cannot handle "infds" in var +import string + +PLIST = None + +# keep track of request ids in order to not repeat +# requests if user refreshes his browser +REQ_IDS = {} + +# +# Holding current AmarokStatus. A bunch of init_XXX functions in +# order to begin dcop requests as early as possible to avoid too +# much latency +# +class AmarokStatus: + + EngineEmpty = 1 + EngineIdle = 2 + EnginePause = 3 + EnginePlay = 4 + + allowControl = 0 + publish = 0 + playState = -1 + + dcop_isplaying = None + dcop_volume = None + dcop_trackcurrentindex = None + dcop_trackcurrenttime = None + dcop_tracktotaltime = None + + def __init__(self): + self.controls_enabled = 0 + self.time_left = None + + self.dcop_isplaying = Globals.PlayerDcop("isPlaying") + self.dcop_volume = Globals.PlayerDcop("getVolume") + self.dcop_trackcurrentindex = Globals.PlaylistDcop("getActiveIndex") + self.dcop_trackcurrenttime = Globals.PlayerDcop("trackCurrentTime") + self.dcop_tracktotaltime = Globals.PlayerDcop("trackTotalTime") + + def isPlaying(self): + if self.playState != -1: + res = self.playState == self.EnginePlay + else: + res = string.find(self.dcop_isplaying.result(), "true") >= 0 + if res: + self.playState = self.EnginePlay + else: + self.playState = self.EnginePause + + return res + + def getActiveIndex(self): + return int(self.dcop_trackcurrentindex.result()) + + def getVolume(self): + return int(self.dcop_volume.result()) + + def timeLeft(self): + cur = int(self.dcop_trackcurrenttime.result()) + total = int(self.dcop_tracktotaltime.result()) + return total - cur + + def controlsEnabled(self): + return self.allowControl + +class RequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler): + """We need our own 'RequestHandler, to handle the requests, that arrive at + our server.""" + + def _amarokPlay(self): + AmarokStatus.playState = AmarokStatus.EnginePlay + Globals._dcopCallPlayer("play") + + def _amarokPause(self): + AmarokStatus.playState = AmarokStatus.EnginePause + Globals._dcopCallPlayer("pause") + + def _amarokNext(self): + Globals._dcopCallPlayer("next") + + def _amarokGoto(self,index): + AmarokStatus.playState = AmarokStatus.EnginePlay + AmarokStatus.currentTrackIndex = int(index) + Globals._dcopCallPlaylistArg("playByIndex",index) + + def _amarokPrev(self): + Globals._dcopCallPlayer("prev") + + def _amarokStop(self): + Globals._dcopCallPlayer("stop") + AmarokStatus.playState = AmarokStatus.EngineStop + + def _amarokSetVolume(self, val): + Globals._dcopCallPlayerArg("setVolume",val) + + def _parseQueryVars(self): + querystr = self.path.split("?") + + qmap = {} + + if len(querystr) <= 1: + return qmap + + queries = querystr[-1].split("&"); + + for query in queries: + var = query.split("=") + if len(var) != 2: + continue + qmap[var[0]] = var[1] + + return qmap + + def _handleAction(self, qmap): + global REQ_IDS + + # get the sessions last reqid + try: + req_id = REQ_IDS[qmap["sesid"]] + except: + return 0 + + # abort a request that has already been completed + # probably a refresh from the users browser + if qmap.has_key("reqid") and req_id == int(qmap["reqid"]): + return 0 + + if qmap.has_key("action"): + a = qmap["action"] + if a == "stop": + self._amarokStop() + elif a == "play": + self._amarokPlay() + elif a == "pause": + self._amarokPause() + elif a == "prev": + self._amarokPrev() + elif a == "next": + self._amarokNext() + elif a == "goto": + self._amarokGoto(qmap["value"]) + elif a == "setvolume": + self._amarokSetVolume(qmap["value"]) + return 1 + + def _amarokStatus(self): + status = AmarokStatus() + return status + + def _sendFile(self, path): + # only allow doc root dir access + elem = self.path.split("/") + if len(elem): + path = elem[-1] + f = open(Globals.EXEC_PATH + "/" + path, 'r') + self.copyfile(f, self.wfile) + + def do_HEAD(self): + """Serve a HEAD request.""" + RequestHandler.extensions_map.update({ + '': 'application/octet-stream', # Default + '.png': 'image/png', + '.js': 'text/plain', + '.css': 'text/plain' + }) + f = self.send_head() + if f: + f.close() + + def _getSessionInfo(self, qmap): + # get the sessions last reqid + last_req_id = 0 + session_id = None + if qmap.has_key("sesid"): + session_id = qmap["sesid"] + if REQ_IDS.has_key(session_id): + last_req_id = REQ_IDS[session_id] + else: + REQ_IDS[session_id] = last_req_id + else: + # Create a session + session_id = str(time.time()) + REQ_IDS[session_id] = last_req_id + return session_id, last_req_id + + def do_GET(self): + """Overwrite the do_GET-method of the super class.""" + RequestHandler.extensions_map.update({ + '': 'application/octet-stream', # Default + '.png': 'image/png', + '.js': 'text/plain', + '.css': 'text/plain' + }) + + global REQ_IDS + + qmap = self._parseQueryVars() + session_id, last_req_id = self._getSessionInfo(qmap) + + if AmarokStatus.allowControl and self._handleAction(qmap): + last_req_id = last_req_id + 1 + REQ_IDS[session_id] = last_req_id + + newreqid = last_req_id + 1 + + # + # Surely there must be a better way that this:) + # + self.send_response(200) + if string.find(self.path, ".png") >= 0: + self.send_header("content-type","image/png") + self.end_headers() + self._sendFile(self.path) + elif string.find(self.path, ".js") >= 0: + self.send_header("content-type","text/plain") + self.end_headers() + self._sendFile(self.path) + elif string.find(self.path, ".css") >= 0: + self.send_header("content-type","text/css") + self.end_headers() + self._sendFile(self.path) + else: + status = self._amarokStatus() + status.dcop_volume.init() + status.dcop_trackcurrenttime.init() + status.dcop_tracktotaltime.init() + self.send_header("content-type","text/html") + self.send_header("Cache-Control","no-cache") + self.end_headers() + status.reqid = newreqid + status.sesid = session_id + self.wfile.write(PLIST.toHtml(status)) + +def main(): + """main is the starting-point for our script.""" + global PLIST + PLIST = Playlist() + srv = BaseHTTPServer.HTTPServer(('',Globals.PORT),RequestHandler) + srv.serve_forever() + + +if __name__ == "__main__": + main() + diff --git a/amarok/src/scripts/webcontrol/WebControl.py b/amarok/src/scripts/webcontrol/WebControl.py new file mode 100755 index 00000000..e399402f --- /dev/null +++ b/amarok/src/scripts/webcontrol/WebControl.py @@ -0,0 +1,289 @@ +#!/usr/bin/env python +############################################################################ +# WebControl script for Amarok +# (c) 2005 Jonas Drewsen +# (c) 2006 Peter C. Ndikuwera +# +# Depends on: Python 2.2, PyQt +# +############################################################################ +# Based on +# Python-Qt template script for Amarok +# (c) 2005 Mark Kretschmann +# +############################################################################ +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +############################################################################ + +import ConfigParser +import os +import sys +import socket +import signal +import threading +from time import sleep + +import Globals +from Playlist import Playlist +import RequestHandler +import BaseHTTPServer +from WebPublisher import * + +import time + +# necessary for <= python2.2 that cannot handle "infds" in var +import string + +try: + from qt import * +except: + os.popen( "kdialog --sorry 'PyQt (Qt bindings for Python) is required for this script.'" ) + raise + + +# Replace with real name +debug_prefix = "[WebControl Script]" + + +class ConfigDialog( QDialog ): + """ Configuration widget """ + + def __init__( self ): + QDialog.__init__( self ) + self.setWFlags( Qt.WDestructiveClose ) + self.setCaption( "WebControl - Amarok" ) + + self.config = ConfigParser.ConfigParser() + + allowControl = RequestHandler.AmarokStatus.allowControl + publish = RequestHandler.AmarokStatus.publish + try: + config = ConfigParser.ConfigParser() + config.read( "webcontrolrc" ) + allowControl = string.find(config.get( "General", "allowcontrol" ), "True") >= 0 + publish = string.find(config.get( "General", "publish" ), "True") >= 0 + except: + pass + + self.lay = QHBoxLayout( self ) + + self.vbox = QVBox( self ) + self.lay.addWidget( self.vbox ) + + self.hbox1 = QHBox( self.vbox ) + + self.allowControl = QCheckBox( QString("Allow control"), self.hbox1 ) + self.allowControl.setChecked(allowControl) + + self.hbox1 = QHBox( self.vbox ) + + self.publish = QCheckBox( QString("Publish"), self.hbox1 ) + self.publish.setChecked(publish) + + self.hbox = QHBox( self.vbox ) + + self.ok = QPushButton( self.hbox ) + self.ok.setText( "Ok" ) + + self.cancel = QPushButton( self.hbox ) + self.cancel.setText( "Cancel" ) + self.cancel.setDefault( True ) + + self.connect( self.ok, SIGNAL( "clicked()" ), self.save ) + self.connect( self.cancel, SIGNAL( "clicked()" ), self, SLOT( "reject()" ) ) + + self.adjustSize() + + def save( self ): + """ Saves configuration to file """ + + self.file = file( "webcontrolrc", 'w' ) + + self.config = ConfigParser.ConfigParser() + self.config.add_section( "General" ) + self.config.set( "General", "allowcontrol", self.allowControl.isChecked() ) + self.config.set( "General", "publish", self.publish.isChecked() ) + self.config.write( self.file ) + self.file.close() + debug( "Saved config" ) + self.accept() + + +class Notification( QCustomEvent ): + __super_init = QCustomEvent.__init__ + def __init__( self, str ): + self.__super_init(QCustomEvent.User + 1) + self.string = str + +class WebControl( QApplication ): + """ The main application, also sets up the Qt event loop """ + + def __init__( self, args ): + QApplication.__init__( self, args ) + debug( "Started." ) + + self.readSettings() + + self.t = threading.Thread( target = self.readStdin ) + self.t.start() + + RequestHandler.PLIST = Playlist() + + p_incr = 0 + + while p_incr < 10: + try: + p_i=p_incr+Globals.PORT + self.srv = BaseHTTPServer.HTTPServer(('',p_i),RequestHandler.RequestHandler) + publisher.port = p_i + break + except socket.error: + p_incr+=1 + + self.zeroconfPublishing() + self.snsrv = QSocketNotifier(self.srv.fileno(), QSocketNotifier.Read) + self.snsrv.connect( self.snsrv, SIGNAL('activated(int)'), self.readSocket ) + + def readSocket( self ): + # Got a read event on the HTTP server socket. + self.srv.handle_request() + + def readSettings( self ): + """ Reads settings from configuration file """ + config = ConfigParser.ConfigParser() + config.read( "webcontrolrc" ) + + try: + RequestHandler.AmarokStatus.allowControl = string.find(config.get( "General", "allowcontrol" ), "True") >= 0 + RequestHandler.AmarokStatus.publish = string.find(config.get( "General", "publish" ), "True") >= 0 + except: + debug( "No config file found, using defaults." ) + + + def postConfigure( self ): + self.readSettings() + self.zeroconfPublishing() + + def zeroconfPublishing( self ): + if RequestHandler.AmarokStatus.publish: + if not publisher.active: + threading.Thread(target = publisher.run).start() + else: + publisher.shutdown() + +############################################################################ +# Stdin-Reader Thread +############################################################################ + + def readStdin( self ): + while True: + line = sys.stdin.readline() + if line: + qApp.postEvent( self, Notification(line) ) + else: + break + +############################################################################ +# Notification Handling +############################################################################ + + def customEvent( self, notification ): + """ Handles the notifications """ + + string = QString(notification.string) + debug( "Received notification: " + str( string ) ) + + if string.contains( "configure" ): + self.configure() + + elif string.contains( "exit" ): + cleanup(None,None) + + elif string.contains( "engineStateChange: play" ): + self.engineStatePlay() + + elif string.contains( "engineStateChange: idle" ): + self.engineStateIdle() + + elif string.contains( "engineStateChange: pause" ): + self.engineStatePause() + + elif string.contains( "engineStateChange: empty" ): + self.engineStatePause() + + elif string.contains( "trackChange" ): + self.trackChange() + + else: + debug( "Unknown notification: " + str(string) + " -> ignoring") + +# Notification callbacks. Implement these functions to react to specific notification +# events from Amarok: + + def configure( self ): + debug( "configuration" ) + + self.dia = ConfigDialog() + self.dia.show() + self.connect( self.dia, SIGNAL( "destroyed()" ), self.postConfigure ) + + def engineStatePlay( self ): + """ Called when Engine state changes to Play """ + RequestHandler.AmarokStatus.dcop_trackcurrenttime = Globals.PlayerDcop("trackCurrentTime") + RequestHandler.AmarokStatus.dcop_trackcurrenttime.result() + RequestHandler.AmarokStatus.dcop_tracktotaltime = Globals.PlayerDcop("trackTotalTime") + RequestHandler.AmarokStatus.dcop_tracktotaltime.result() + RequestHandler.AmarokStatus.playState = RequestHandler.AmarokStatus.EnginePlay + + def engineStateIdle( self ): + """ Called when Engine state changes to Idle """ + RequestHandler.AmarokStatus.playState = RequestHandler.AmarokStatus.EngineIdle + + def engineStatePause( self ): + """ Called when Engine state changes to Pause """ + RequestHandler.AmarokStatus.playState = RequestHandler.AmarokStatus.EnginePause + + def engineStateEmpty( self ): + """ Called when Engine state changes to Empty """ + RequestHandler.AmarokStatus.playState = RequestHandler.AmarokStatus.EngineEmpty + + def trackChange( self ): + """ Called when a new track starts """ + RequestHandler.AmarokStatus.dcop_trackcurrentindex = Globals.PlaylistDcop("getActiveIndex") + RequestHandler.AmarokStatus.dcop_trackcurrentindex.result() + RequestHandler.AmarokStatus.dcop_trackcurrenttime = Globals.PlayerDcop("trackCurrentTime") + RequestHandler.AmarokStatus.dcop_trackcurrenttime.result() + RequestHandler.AmarokStatus.dcop_tracktotaltime = Globals.PlayerDcop("trackTotalTime") + RequestHandler.AmarokStatus.dcop_tracktotaltime.result() + + + + +############################################################################ + +def debug( message ): + """ Prints debug message to stdout """ + + print debug_prefix + " " + message + +def cleanup(sig,frame): + publisher.shutdown() + os._exit(0) + + +def guithread(): + app = WebControl( sys.argv ) + app.exec_loop() + +if __name__ == "__main__": + Globals.EXEC_PATH = os.path.abspath(sys.path[0]) + gui = threading.Thread(target=guithread) + gui.start() + signal.signal(signal.SIGTERM,cleanup) +# just wait quietly for the end + while 1: sleep(120) diff --git a/amarok/src/scripts/webcontrol/WebControl.spec b/amarok/src/scripts/webcontrol/WebControl.spec new file mode 100644 index 00000000..928fafd0 --- /dev/null +++ b/amarok/src/scripts/webcontrol/WebControl.spec @@ -0,0 +1,2 @@ +name = Web Control +type = generic diff --git a/amarok/src/scripts/webcontrol/WebPublisher.py b/amarok/src/scripts/webcontrol/WebPublisher.py new file mode 100644 index 00000000..0c200172 --- /dev/null +++ b/amarok/src/scripts/webcontrol/WebPublisher.py @@ -0,0 +1,34 @@ +############################################################################ +# Zeroconf support - publishing webcontrol page +# (c) 2005 Jakub Stachowski +# +# Depends on: Python 2.2, pyzeroconf 0.12+metaservice patch +############################################################################ +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +############################################################################ + +import os +import getpass +import sys +import string +# find directory containing common Zeroconf files +temp=sys.path[:] +sys.path.insert(0,os.path.abspath(os.path.dirname(sys.argv[0])+'/../common')) +if not os.getenv("KDEDIR") is None: sys.path.insert(0,os.getenv("KDEDIR")+"/share/apps/amarok/scripts/common") +if not os.getenv("KDEDIRS") is None: sys.path=[p+"/share/apps/amarok/scripts/common" for p in string.split(os.getenv("KDEDIRS"),os.pathsep)]+sys.path +from Publisher import * +sys.path=temp + +class WebPublisher(Publisher): + + port = None + + def services(self): + return [{ "name" : "Amarok WebControl for "+getpass.getuser(), "port": self.port, "type": "_http._tcp", "properties": {}}] + +publisher = WebPublisher() \ No newline at end of file diff --git a/amarok/src/scripts/webcontrol/amarok_cut.png b/amarok/src/scripts/webcontrol/amarok_cut.png new file mode 100644 index 00000000..6623e892 Binary files /dev/null and b/amarok/src/scripts/webcontrol/amarok_cut.png differ diff --git a/amarok/src/scripts/webcontrol/controlbackground.png b/amarok/src/scripts/webcontrol/controlbackground.png new file mode 100644 index 00000000..7915e638 Binary files /dev/null and b/amarok/src/scripts/webcontrol/controlbackground.png differ diff --git a/amarok/src/scripts/webcontrol/main.css b/amarok/src/scripts/webcontrol/main.css new file mode 100644 index 00000000..ba8908bf --- /dev/null +++ b/amarok/src/scripts/webcontrol/main.css @@ -0,0 +1,161 @@ +body { + font-size:10pt; + background-color: #9cb2cd; + color: white; + margin-left: 8px; + margin-right: 8px; +} + +.topnavi { + background: url("controlbackground.png") no-repeat #E1E1E1; +} + +/* +.topspace { + height: 25px; +} +*/ + +div.topfixed { + z-index: 10; + position: fixed; + top: 0px; + left: 0px; + right: 0px; + background-color: #9cb2cd; +/* margin-right: 8px;*/ +} + +table { + border: 1px white; +} + +.tracks { + z-index: 0; + position: relative; + top: 8px; + background-color:#596082; + border: 1px solid black +} + +table.trackheader { + background-color:#596082; + border: 1px solid black + visibility: hidden; + display: none; +} + + +tr.tr_one { + background-color: red; +/* visibility: collapse; */ + background-color: #394062; +/* display: none; */ +} + +tr.tr_two { + background-color: red; +/* display: none; */ +/* visibility: collapse; */ + background-color: #202050; +} + +tr#trackheader { +/* z-index: 10; */ +/* top: 30px; */ + visibility: visible; + display: table-row; + font-weight: bold; + background-color:#596082; +/* background-color: red; */ +} + +div.trackshmove { + height: 0.6em; +} + +dd.tr_one { + background-color: #394062; +} + +dd.tr_two { + background-color: #202050; +} + +.nowplaying { + border: 1pt solid red; +} + +th { + color: dark-grey; +} + +td { + font-family: Geneva, Arial, Helvetica, Verdana, sans-serif; + font-size: 10pt; + color: #ffffff; +} + +td.countdown, td#countdown { + color: #000000; +} + +.window { + background-color: #d9d9d9; + border: 1px solid black; + padding:15px; +} + +a.track:link { color: white } +a.track:visited { color: white } +a.track:active { color: white } +a.track:hover { color: white } + +a:link { + color: #074876; + border: 0px; + text-decoration: none; +} +a:visited { + color: #074876; + text-decoration: none; +} +a:active { + color: #074876; + text-decoration: none; +} + +a#amaroklink:hover { + text-decoration: none; + text-decoration: none; + background-color: #EEEEEE; + color: white; +} + +table.volume { + border: 0px solid #BBBBBB; + font-size: 16px; + font-family: fixed; +} + +td.volumeset { + border: 1pt solid #BBBBBB; + background-color: #749ce8; + height: 15px; + text-align: center; + width: 15px; +} + +td.volumeunset { + border-top: 1pt solid #FFFFFF; + border-bottom: 1pt solid #888888; + border-left: 1pt solid #BBBBBB; + border-right: 1pt solid #AAAAAA; + height: 15px; + text-align: center; + width: 15px; +} + +img { + border: 0; +} diff --git a/amarok/src/scripts/webcontrol/main.js b/amarok/src/scripts/webcontrol/main.js new file mode 100644 index 00000000..1f09d591 --- /dev/null +++ b/amarok/src/scripts/webcontrol/main.js @@ -0,0 +1,253 @@ +function push() { + var sub = this.length; + for (var i = 0; i < push.arguments.length; ++i) { + this[sub] = push.arguments[i]; + sub++; + } +} + +function pop() { + var lastElement = null; + if (this.length > 0) { + lastElement = this[this.length - 1]; + this.length--; + } + return lastElement; +} + +Array.prototype.push = push; +Array.prototype.pop = pop; + +function getById(id) { + if (document.layers){ + //Netscape 4 specific code + pre = 'document.'; + post = ''; + } + if (document.getElementById){ + //Netscape 6 specific code + pre = 'document.getElementById("'; + post = '")'; + } else + if (document.all){ + //IE4+ specific code + pre = 'document.all.'; + post = ''; + } + return eval(pre + id + post); +} + +// All this stuff because we want it to be reentrant +var animScriptArr = new Object(); +var nextAnimId = 0; +var animate_speed = 90; +function animScriptsHelper(index, delay) +{ + if (animScriptArr[index] == null) { + return; + } + script = animScriptArr[index].pop(); + if (script != null) { + eval(script); + } + if (animScriptArr[index].length > 1) { + setTimeout("animScriptsHelper(" + index + ", " + delay + ");", delay); + } else { + if (animScriptArr[index].length == 1) { + script = animScriptArr[index].pop(); + delete animScriptArr[index]; + if (script != null) { + eval(script); + } + } else { + delete animScriptArr[index]; + } + } +} + +function setBackgroundColor(r,g,b,rowid) +{ + getById(rowid).style.background = "rgb(" + r + "," + g + "," + b + ")"; +} + +function setColor(r,g,b, myid) +{ + var i, td_n_elems, td_elems = getById(myid).getElementsByTagName("a"); + td_n_elems = td_elems.length; + + for (i = 0; i < td_n_elems; i++) { + td_elems[i].style.color = "rgb(" + r + "," + g + "," + b + ")"; + } + +} + +function anim(direction) +{ + + // build color fade + var ci; + var count = 20; + var mult = Math.floor(256 / count); + var colorAnimScripts = new Array(); + var cid = 0; + var cid2 = 0; + var animid = "nowplaying"; + + if (direction == 1) { + colorAnimScripts[0] = "anim(0);"; + } else { + colorAnimScripts[0] = "anim(1);"; + } + + for (ci = 1; ci < count+1; ci++) { + if (direction) { + cid = count - (ci-1); + cid2 = (ci-1); + } else { + cid = (ci-1); + cid2 = count - (ci-1); + } + colorAnimScripts[ci] = + "setBackgroundColor(" + Math.floor(13.0 * (cid*mult/100.0) + 100.0) + "," + + Math.floor(13.0 * (cid*mult/100.0) + 100.0) + "," + + Math.floor(13.0 * (cid*mult/100.0) + 140.0) + ", '" + animid + "');"; + } + + setColor(255,255,0, "nowplaying"); + animScriptArr[0] = colorAnimScripts; + animScriptsHelper(0, animate_speed+0); +// animScriptArr[nextAnimId++] = colorAnimScripts; +// animScriptsHelper(nextAnimId-1, animate_speed+0); +} + + +var dateobj = -1; +var dhours = 0; +var dmins = 0; +var dsecs = 0; +function countdown(value) { + + // all this is done because setTimeout 1000, isn't really a second in js + + if (dateobj == -1) { + dateobj = new Date(); + dhours = dateobj.getUTCHours(); + dmins = dateobj.getUTCMinutes(); + dsecs = dateobj.getUTCSeconds(); + + // refresh automatically every 20 secs. + setTimeout("dateobj=-1;refreshPage();", 20000); + } + + dateobj2 = new Date(); + var hours_now = dateobj2.getUTCHours(); + var mins_now = dateobj2.getUTCMinutes(); + var secs_now = dateobj2.getUTCSeconds(); + + // difference (assume no number is more that an hour :) + + msecs = ((hours_now == dhours ? 0 : 1) * 60 * 60 + + (mins_now - dmins) * 60 + + (secs_now - dsecs)) * 1000; + + diff = value * 1000 - msecs; + tdo = new Date(diff); + + rhours = tdo.getUTCHours(); + rmins = tdo.getUTCMinutes(); + rsecs = tdo.getUTCSeconds(); + + var hours_str = (rhours == 0 ? "" : (rhours < 10 ? "0" + rhours : rhours) + ":"); + var mins_str = rmins < 10 ? "0" + rmins : rmins; + var secs_str = rsecs < 10 ? "0" + rsecs : rsecs; + + getById("countdown").innerHTML = hours_str + mins_str + ":" + secs_str; + + setHeaders(); + + if (diff > 0) { + setTimeout("countdown(" + value + ");", 500); + } else { + getById("countdown").innerHTML = "00:00"; + // go refresh this page + refreshPage(); + } +} + +function getPageScroll() +{ + var x,y; + if (self.pageYOffset) // all except Explorer + { + x = self.pageXOffset; + y = self.pageYOffset; + } + else if (document.documentElement && document.documentElement.scrollTop) + // Explorer 6 Strict + { + x = document.documentElement.scrollLeft; + y = document.documentElement.scrollTop; + } + else if (document.body) // all other Explorers + { + x = document.body.scrollLeft; + y = document.body.scrollTop; + } + return y; +} + +function setPageScroll(pxs) +{ + window.scrollTo(0, pxs); +} + +function setScrollInUrl(newUrl) +{ + + if (newUrl.match(/scroll=\d*/)) { + return newUrl.replace(/scroll=\d*/g, "scroll=" + getPageScroll()); + } else { + if (newUrl.match(/\?/)) { + return newUrl + "&scroll=" + getPageScroll(); + } else { + return newUrl + "?scroll=" + getPageScroll(); + } + } +} + +function refreshPage() +{ + document.location = setScrollInUrl("" + document.location); +} + +function rescroll() +{ + regex = new RegExp("scroll=[^\\&]+"); + var match = regex.exec(document.URL); + scrollPos = parseInt((match+"").substring(7)); + if (match) { + setPageScroll(scrollPos); + } +} + +function dolink(qstr) +{ + document.location = setScrollInUrl("?" + qstr); +} + +function setHeaders() +{ + var i, td_n_elems_dest, td_elems_dest = getById("trackheader").getElementsByTagName("th"); + td_n_elems = td_elems_dest.length; + + var td_n_elems, td_elems; + if (getById("trackone")) { + td_n_elems, td_elems = getById("trackone").getElementsByTagName("td"); + } else { + td_n_elems, td_elems = getById("nowplaying").getElementsByTagName("td"); + } + + for (i = 0; i < td_n_elems; i++) { + td_elems_dest[i].style.width = td_elems[i].offsetWidth; + } +} \ No newline at end of file diff --git a/amarok/src/scripts/webcontrol/player_end.png b/amarok/src/scripts/webcontrol/player_end.png new file mode 100644 index 00000000..d657ec49 Binary files /dev/null and b/amarok/src/scripts/webcontrol/player_end.png differ diff --git a/amarok/src/scripts/webcontrol/player_pause.png b/amarok/src/scripts/webcontrol/player_pause.png new file mode 100644 index 00000000..b14e5070 Binary files /dev/null and b/amarok/src/scripts/webcontrol/player_pause.png differ diff --git a/amarok/src/scripts/webcontrol/player_play.png b/amarok/src/scripts/webcontrol/player_play.png new file mode 100644 index 00000000..bc7336cb Binary files /dev/null and b/amarok/src/scripts/webcontrol/player_play.png differ diff --git a/amarok/src/scripts/webcontrol/player_start.png b/amarok/src/scripts/webcontrol/player_start.png new file mode 100644 index 00000000..74b9ca01 Binary files /dev/null and b/amarok/src/scripts/webcontrol/player_start.png differ diff --git a/amarok/src/scripts/webcontrol/player_stop.png b/amarok/src/scripts/webcontrol/player_stop.png new file mode 100644 index 00000000..d41d26c3 Binary files /dev/null and b/amarok/src/scripts/webcontrol/player_stop.png differ diff --git a/amarok/src/scripts/webcontrol/smallstar.png b/amarok/src/scripts/webcontrol/smallstar.png new file mode 100644 index 00000000..44ed12a3 Binary files /dev/null and b/amarok/src/scripts/webcontrol/smallstar.png differ diff --git a/amarok/src/scripts/webcontrol/star.png b/amarok/src/scripts/webcontrol/star.png new file mode 100644 index 00000000..757e9266 Binary files /dev/null and b/amarok/src/scripts/webcontrol/star.png differ diff --git a/amarok/src/scripts/webcontrol/template.thtml b/amarok/src/scripts/webcontrol/template.thtml new file mode 100644 index 00000000..f1e01be1 --- /dev/null +++ b/amarok/src/scripts/webcontrol/template.thtml @@ -0,0 +1,42 @@ +global code +global tblhead +global actions + +code = """ \n + \n + \n + \n + \n + \n + \n + \n + AmaroK playlist\n + \n +
    \n + \n +
    \n + %s
    \n +
    \n +
    current + amarok + playlist of %s
    \n +
    \n +
    +
     
    \n + %s + \n + \n""" + +tblhead = """ + + %s
    """ + +actions = """\n + \n + \n + + + +
    %s%sTime:\n + %s
    \n """ diff --git a/amarok/src/scripts/webcontrol/vol_speaker.png b/amarok/src/scripts/webcontrol/vol_speaker.png new file mode 100644 index 00000000..c631780c Binary files /dev/null and b/amarok/src/scripts/webcontrol/vol_speaker.png differ diff --git a/amarok/src/scrobbler.cpp b/amarok/src/scrobbler.cpp new file mode 100644 index 00000000..61837e55 --- /dev/null +++ b/amarok/src/scrobbler.cpp @@ -0,0 +1,1185 @@ +// (c) 2004 Christian Muehlhaeuser +// (c) 2004 Sami Nieminen +// (c) 2006 Shane King +// (c) 2006 Iain Benson +// (c) 2006 Alexandre Oliveira +// (c) 2006 Andy Kelk +// See COPYING file for licensing information. + +#define DEBUG_PREFIX "Scrobbler" + +#include "amarok.h" +#include "amarokconfig.h" +#include "collectiondb.h" +#include "config.h" +#include "debug.h" +#include "enginecontroller.h" +#include "playlist.h" +#include "scrobbler.h" +#include "statusbar.h" + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +//some setups require this +#undef PROTOCOL_VERSION + + +//////////////////////////////////////////////////////////////////////////////// +// CLASS Scrobbler +//////////////////////////////////////////////////////////////////////////////// + +Scrobbler* Scrobbler::instance() +{ + static Scrobbler scrobbler; + return &scrobbler; +} + + +Scrobbler::Scrobbler() + : EngineObserver( EngineController::instance() ) + , m_similarArtistsJob( 0 ) + , m_validForSending( false ) + , m_startPos( 0 ) + , m_submitter( new ScrobblerSubmitter() ) + , m_item( new SubmitItem() ) +{} + + +Scrobbler::~Scrobbler() +{ + delete m_item; + delete m_submitter; +} + + +/** + * Queries similar artists from Audioscrobbler. + */ +void Scrobbler::similarArtists( const QString & artist ) +{ + QString safeArtist = QDeepCopy( artist ); + if ( AmarokConfig::retrieveSimilarArtists() ) + { +// Request looks like this: +// http://ws.audioscrobbler.com/1.0/artist/Metallica/similar.xml + + m_similarArtistsBuffer = QByteArray(); + m_artist = artist; + + m_similarArtistsJob = KIO::get( "http://ws.audioscrobbler.com/1.0/artist/" + safeArtist + "/similar.xml", false, false ); + + connect( m_similarArtistsJob, SIGNAL( result( KIO::Job* ) ), + this, SLOT( audioScrobblerSimilarArtistsResult( KIO::Job* ) ) ); + connect( m_similarArtistsJob, SIGNAL( data( KIO::Job*, const QByteArray& ) ), + this, SLOT( audioScrobblerSimilarArtistsData( KIO::Job*, const QByteArray& ) ) ); + } +} + + +/** + * Called when the similar artists TransferJob finishes. + */ +void Scrobbler::audioScrobblerSimilarArtistsResult( KIO::Job* job ) //SLOT +{ + if ( m_similarArtistsJob != job ) + return; //not the right job, so let's ignore it + + if ( job->error() ) + { + warning() << "KIO error! errno: " << job->error() << endl; + return; + } + +// Result looks like this: +// +// +// +// Iron Maiden +// +// 100 +// http://www.last.fm/music/Iron+Maiden +// http://static.last.fm/proposedimages/thumbnail/6/1000107/264195.jpg +// http://static.last.fm/proposedimages/sidebar/6/1000107/264195.jpg +// 1 +// +// + + QDomDocument document; + if ( !document.setContent( m_similarArtistsBuffer ) ) + { + debug() << "Couldn't read similar artists response" << endl; + return; + } + + QDomNodeList values = document.elementsByTagName( "similarartists" ) + .item( 0 ).childNodes(); + + QStringList suggestions; + for ( uint i = 0; i < values.count() && i < 30; i++ ) // limit to top 30 artists + suggestions << values.item( i ).namedItem( "name" ).toElement().text(); + + debug() << "Suggestions retrieved (" << suggestions.count() << ")" << endl; + if ( !suggestions.isEmpty() ) + emit similarArtistsFetched( m_artist, suggestions ); + + m_similarArtistsJob = 0; +} + + +/** + * Called when similar artists data is received for the TransferJob. + */ +void Scrobbler::audioScrobblerSimilarArtistsData( KIO::Job* job, const QByteArray& data ) //SLOT +{ + if ( m_similarArtistsJob != job ) + return; //not the right job, so let's ignore it + + uint oldSize = m_similarArtistsBuffer.size(); + m_similarArtistsBuffer.resize( oldSize + data.size() ); + memcpy( m_similarArtistsBuffer.data() + oldSize, data.data(), data.size() ); +} + + +/** + * Called when the signal is received. + */ +void Scrobbler::engineNewMetaData( const MetaBundle& bundle, bool trackChanged ) +{ + //debug() << "engineNewMetaData: " << bundle.artist() << ":" << bundle.album() << ":" << bundle.title() << ":" << trackChanged << endl; + if ( !trackChanged ) + { + debug() << "It's still the same track." << endl; + m_item->setArtist( bundle.artist() ); + m_item->setAlbum( bundle.album() ); + m_item->setTitle( bundle.title() ); + return; + } + + //to work around xine bug, we have to explictly prevent submission the first few seconds of a track + //http://sourceforge.net/tracker/index.php?func=detail&aid=1401026&group_id=9655&atid=109655 + m_timer.stop(); + m_timer.start( 10000, true ); + + m_startPos = 0; + + // Plugins must not submit tracks played from online radio stations, even + // if they appear to be providing correct metadata. + if ( !bundle.streamUrl().isEmpty() ) + { + debug() << "Won't submit: It's a stream." << endl; + m_validForSending = false; + } + else if( bundle.podcastBundle() != NULL ) + { + debug() << "Won't submit: It's a podcast." << endl; + m_validForSending = false; + } + else + { + *m_item = SubmitItem( bundle.artist(), bundle.album(), bundle.title(), bundle.length() ); + m_validForSending = true; // check length etc later + } +} + + +/** + * Called when cue file detects track change + */ +void Scrobbler::subTrack( long currentPos, long startPos, long endPos ) +{ + //debug() << "subTrack: " << currentPos << ":" << startPos << ":" << endPos << endl; + *m_item = SubmitItem( m_item->artist(), m_item->album(), m_item->title(), endPos - startPos ); + if ( currentPos <= startPos + 2 ) // only submit if starting from the start of the track (need to allow 2 second difference for rounding/delay) + { + m_startPos = startPos * 1000; + m_validForSending = true; + } + else + { + debug() << "Won't submit: Detected cuefile jump to " << currentPos - startPos << " seconds into track." << endl; + m_validForSending = false; + } +} + + +/** + * Called when the signal is received. + */ +void Scrobbler::engineTrackPositionChanged( long position, bool userSeek ) +{ + //debug() << "engineTrackPositionChanged: " << position << ":" << userSeek << endl; + if ( !m_validForSending ) + return; + + if ( userSeek ) + { + m_validForSending = false; + debug() << "Won't submit: Seek detected." << endl; + return; + } + + if ( m_timer.isActive() ) + return; + + // Each track must be submitted to the server when it is 50% or 240 + // seconds complete, whichever comes first. + if ( position - m_startPos > 240 * 1000 || position - m_startPos > 0.5 * m_item->length() * 1000 ) + { + if ( m_item->valid() ) + m_submitter->submitItem( new SubmitItem( *m_item ) ); + else + debug() << "Won't submit: No artist, no title, or less than 30 seconds." << endl; + m_validForSending = false; + } +} + + +/** + * Applies settings from the config dialog. + */ +void Scrobbler::applySettings() +{ + m_submitter->configure( AmarokConfig::scrobblerUsername(), AmarokConfig::scrobblerPassword(), AmarokConfig::submitPlayedSongs() ); +} + + +//////////////////////////////////////////////////////////////////////////////// +// CLASS SubmitItem +//////////////////////////////////////////////////////////////////////////////// + + +SubmitItem::SubmitItem( + const QString& artist, + const QString& album, + const QString& title, + int length, + bool now) +{ + m_artist = artist; + m_album = album; + m_title = title; + m_length = length; + m_playStartTime = now ? QDateTime::currentDateTime( Qt::UTC ).toTime_t() : 0; +} + + +SubmitItem::SubmitItem( const QDomElement& element ) +{ + m_artist = element.namedItem( "artist" ).toElement().text(); + m_album = element.namedItem( "album" ).toElement().text(); + m_title = element.namedItem( "title" ).toElement().text(); + m_length = element.namedItem( "length" ).toElement().text().toInt(); + m_playStartTime = element.namedItem( "playtime" ).toElement().text().toUInt(); +} + + +SubmitItem::SubmitItem() + : m_length( 0 ) + , m_playStartTime( 0 ) +{ +} + + +bool SubmitItem::operator==( const SubmitItem& item ) +{ + bool result = true; + + if ( m_artist != item.artist() || m_album != item.album() || m_title != item.title() || + m_length != item.length() || m_playStartTime != item.playStartTime() ) + { + result = false; + } + + return result; +} + + +QDomElement SubmitItem::toDomElement( QDomDocument& document ) const +{ + QDomElement item = document.createElement( "item" ); + // TODO: In the future, it might be good to store url too + //item.setAttribute("url", item->url().url()); + + QDomElement artist = document.createElement( "artist" ); + QDomText artistText = document.createTextNode( m_artist ); + artist.appendChild( artistText ); + item.appendChild( artist ); + + QDomElement album = document.createElement( "album" ); + QDomText albumText = document.createTextNode( m_album ); + album.appendChild( albumText ); + item.appendChild( album ); + + QDomElement title = document.createElement( "title" ); + QDomText titleText = document.createTextNode( m_title ); + title.appendChild( titleText ); + item.appendChild( title ); + + QDomElement length = document.createElement( "length" ); + QDomText lengthText = document.createTextNode( QString::number( m_length ) ); + length.appendChild( lengthText ); + item.appendChild( length ); + + QDomElement playtime = document.createElement( "playtime" ); + QDomText playtimeText = document.createTextNode( QString::number( m_playStartTime ) ); + playtime.appendChild( playtimeText ); + item.appendChild( playtime ); + + return item; +} + + +//////////////////////////////////////////////////////////////////////////////// +// CLASS SubmitQueue +//////////////////////////////////////////////////////////////////////////////// + + +int SubmitQueue::compareItems( QPtrCollection::Item item1, QPtrCollection::Item item2 ) +{ + SubmitItem *sItem1 = static_cast( item1 ); + SubmitItem *sItem2 = static_cast( item2 ); + int result; + + if ( sItem1 == sItem2 ) + { + result = 0; + } + else if ( sItem1->playStartTime() > sItem2->playStartTime() ) + { + result = 1; + } + else + { + result = -1; + } + + return result; +} + + +//////////////////////////////////////////////////////////////////////////////// +// CLASS ScrobblerSubmitter +//////////////////////////////////////////////////////////////////////////////// + +QString ScrobblerSubmitter::PROTOCOL_VERSION = "1.1"; +QString ScrobblerSubmitter::CLIENT_ID = "ark"; +QString ScrobblerSubmitter::CLIENT_VERSION = "1.4"; +QString ScrobblerSubmitter::HANDSHAKE_URL = "http://post.audioscrobbler.com/?hs=true"; + + +ScrobblerSubmitter::ScrobblerSubmitter() + : m_username( 0 ) + , m_password( 0 ) + , m_submitUrl( 0 ) + , m_challenge( 0 ) + , m_scrobblerEnabled( false ) + , m_holdFakeQueue( false ) + , m_inProgress( false ) + , m_needHandshake( true ) + , m_prevSubmitTime( 0 ) + , m_interval( 0 ) + , m_backoff( 0 ) + , m_lastSubmissionFinishTime( 0 ) + , m_fakeQueueLength( 0 ) +{ + connect( &m_timer, SIGNAL(timeout()), this, SLOT(scheduledTimeReached()) ); + readSubmitQueue(); +} + + +ScrobblerSubmitter::~ScrobblerSubmitter() +{ + // need to rescue current submit. This may meant it gets submitted twice, + // but last.fm handles that, and it's better than losing it when you quit + // while a submit is happening + for ( QPtrDictIterator it( m_ongoingSubmits ); it.current(); ++it ) + m_submitQueue.inSort( it.current() ); + m_ongoingSubmits.clear(); + + saveSubmitQueue(); + + m_submitQueue.setAutoDelete( true ); + m_submitQueue.clear(); + m_fakeQueue.setAutoDelete( true ); + m_fakeQueue.clear(); +} + + +/** + * Performs handshake with Audioscrobbler. + */ +void ScrobblerSubmitter::performHandshake() +{ + QString handshakeUrl = QString::null; + uint currentTime = QDateTime::currentDateTime( Qt::UTC ).toTime_t(); + + if ( PROTOCOL_VERSION == "1.1" ) + { + // Audioscrobbler protocol 1.1 (current) + // http://post.audioscrobbler.com/?hs=true + // &p=1.1 + // &c= + // &v= + // &u= + handshakeUrl = + HANDSHAKE_URL + + QString( + "&p=%1" + "&c=%2" + "&v=%3" + "&u=%4" ) + .arg( PROTOCOL_VERSION ) + .arg( CLIENT_ID ) + .arg( CLIENT_VERSION ) + .arg( m_username ); + } + + else if ( PROTOCOL_VERSION == "1.2" ) + { + // Audioscrobbler protocol 1.2 (RFC) + // http://post.audioscrobbler.com/?hs=true + // &p=1.2 + // &c= + // &v= + // &u= + // &t= + // &a= + handshakeUrl = + HANDSHAKE_URL + + QString( + "&p=%1" + "&c=%2" + "&v=%3" + "&u=%4" + "&t=%5" + "&a=%6" ) + .arg( PROTOCOL_VERSION ) + .arg( CLIENT_ID ) + .arg( CLIENT_VERSION ) + .arg( m_username ) + .arg( currentTime ) + .arg( KMD5( KMD5( m_password.utf8() ).hexDigest() + + currentTime ).hexDigest() ); + } + + else + { + debug() << "Handshake not implemented for protocol version: " << PROTOCOL_VERSION << endl; + return; + } + + debug() << "Handshake url: " << handshakeUrl << endl; + + m_submitResultBuffer = ""; + + m_inProgress = true; + KIO::TransferJob* job = KIO::storedGet( handshakeUrl, false, false ); + connect( job, SIGNAL( result( KIO::Job* ) ), SLOT( audioScrobblerHandshakeResult( KIO::Job* ) ) ); +} + + +/** + * Sets item for submission to Audioscrobbler. Actual submission + * depends on things like (is scrobbling enabled, are Audioscrobbler + * profile details filled in etc). + */ +void ScrobblerSubmitter::submitItem( SubmitItem* item ) +{ + if ( m_scrobblerEnabled ) { + enqueueItem( item ); + + if ( item->playStartTime() == 0 ) + m_holdFakeQueue = true; // hold on to fake queue until we get it all and can compute when to submit + else if ( !schedule( false ) ) + announceSubmit( item, 1, false ); // couldn't perform submit immediately, let user know + } +} + + +/** + * Flushes the submit queues + */ +void ScrobblerSubmitter::performSubmit() +{ + QString data; + + // Audioscrobbler accepts max 10 tracks on one submit. + SubmitItem* items[10]; + for ( int submitCounter = 0; submitCounter < 10; submitCounter++ ) + items[submitCounter] = 0; + + if ( PROTOCOL_VERSION == "1.1" ) + { + // Audioscrobbler protocol 1.1 (current) + // http://post.audioscrobbler.com/v1.1-lite.php + // u= + // &s=& + // a[0]=&t[0]=&b[0]=& + // m[0]=&l[0]=&i[0]=